Reverse Engineering
{"author": ["ret2basic"]}
I wonder what this really is... enc
''.join([chr((ord(flag[i]) << 8) + ord(flag[i + 1])) for i in range(0, len(flag), 2)])
The encoding scheme acts on two characters from the flag each time. The first character is used as higher 8 bits, while the second character is used as lower 8 bits. Together, the 2-character pair is transformed into a 16-bit binary number and this binary number is converted to ASCII character.
#!/usr/bin/env python3
with open('enc', 'r') as f:
encoded = f.read()
flag = ''
for ch in encoded:
binary = "{0:016b}".format(ord(ch))
first_half, second_half = binary[:8], binary[8:]
flag += chr(int(first_half, 2))
flag += chr(int(second_half, 2))
print(flag)
def check_key(key, username_trial):
global key_full_template_trial
if len(key) != len(key_full_template_trial):
return False
else:
# Check static base key part --v
i = 0
for c in key_part_static1_trial:
if key[i] != c:
return False
i += 1
# TODO : test performance on toolbox container
# Check dynamic part --v
if key[i] != hashlib.sha256(username_trial).hexdigest()[4]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[5]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[3]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[6]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[2]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[7]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[1]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[8]:
return False
return True
According to the global variables declared at the beginning of the source code, the flag is
picoCTF{1n_7h3_|<3y_of_xxxxxxxx}
where x
stands for a "dynamic" character. Our objective is to reverse engineer the check_key
function and pass the check.Note that
check_key
is not fully implemented and there are eight if
statements checking if a certain character is valid. We could simply write a script and recover each character.#!/usr/bin/env python3
import hashlib
username_trial = b"PRITCHARD"
digest = hashlib.sha256(username_trial).hexdigest()
print(f"index 4: {digest[4]}")
print(f"index 5: {digest[5]}")
print(f"index 3: {digest[3]}")
print(f"index 6: {digest[6]}")
print(f"index 2: {digest[2]}")
print(f"index 7: {digest[7]}")
print(f"index 1: {digest[1]}")
print(f"index 8: {digest[8]}")
flag_part_1 = 'picoCTF{1n_7h3_|<3y_of_'
flag_part_2 = ''
flag_part_2 += digest[4] + digest[5]
flag_part_2 += digest[3] + digest[6]
flag_part_2 += digest[2] + digest[7]
flag_part_2 += digest[1] + digest[8]
flag_part_3 = '}'
print(f"flag: {flag_part_1 + flag_part_2 + flag_part_3}")
# Hiding this really important number in an obscure piece of code is brilliant!
# AND it's encrypted!
# We want our biggest client to know his information is safe with us.
bezos_cc_secret = "A:[email protected]%uL`M-^M0c0AbcM-MFE055a4ce`eN"
# Reference alphabet
alphabet = "!\"#$%&'()*+,-./0123456789:;<=>[email protected]"+ \
"[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
def decode_secret(secret):
"""ROT47 decode
NOTE: encode and decode are the same operation in the ROT cipher family.
"""
# Encryption key
rotate_const = 47
# Storage for decoded secret
decoded = ""
# decode loop
for c in secret:
index = alphabet.find(c)
original_index = (index + rotate_const) % len(alphabet)
decoded = decoded + alphabet[original_index]
print(decoded)
def choose_greatest():
"""Echo the largest of the two numbers given by the user to the program
Warning: this function was written quickly and needs proper error handling
"""
user_value_1 = input("What's your first number? ")
user_value_2 = input("What's your second number? ")
greatest_value = user_value_1 # need a value to return if 1 & 2 are equal
if user_value_1 > user_value_2:
greatest_value = user_value_1
elif user_value_1 < user_value_2:
greatest_value = user_value_2
print( "The number with largest positive magnitude is "
+ str(greatest_value) )
choose_greatest()
Modify the source code to call the
decode_secret
function.# choose_greatest()
print(decode_secret(bezos_cc_secret))
What integer does this program print with arguments
1765227561
and 1830628817
? File: chall.S Flag format: picoCTF{XXXXXXXX} -> (hex, lowercase, no 0x, and 32 bits. ex. 5614267 would be picoCTF{0055aabb}) .arch armv8-a
.file "chall.c"
.text
.align 2
.global func1
.type func1, %function
func1:
sub sp, sp, #16 @ allocate 16 bytes on the stack
str w0, [sp, 12] @ store w0 into [sp+12]
str w1, [sp, 8] @ store w1 into [sp+8]
ldr w1, [sp, 12] @ load [sp+12] into w1
ldr w0, [sp, 8] @ load [sp+8] into w0
cmp w1, w0 @ compare w1 and w0 => compare [sp+12] and [sp+8]
bls .L2 @ branch to .L2 if ls
ldr w0, [sp, 12] @ load [sp+12] into w0
b .L3 @ branch to .L3
.L2:
ldr w0, [sp, 8]
.L3:
add sp, sp, 16
ret
.size func1, .-func1
.section .rodata
.align 3
.LC0:
.string "Result: %ld\n"
.text
.align 2
.global main
.type main, %function
main:
stp x29, x30, [sp, -48]!
add x29, sp, 0
str x19, [sp, 16]
str w0, [x29, 44]
str x1, [x29, 32]
ldr x0, [x29, 32]
add x0, x0, 8
ldr x0, [x0]
bl atoi
mov w19, w0
ldr x0, [x29, 32]
add x0, x0, 16
ldr x0, [x0]
bl atoi
mov w1, w0
mov w0, w19
bl func1
mov w1, w0
adrp x0, .LC0
add x0, x0, :lo12:.LC0
bl printf
mov w0, 0
ldr x19, [sp, 16]
ldp x29, x30, [sp], 48
ret
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 7.5.0-3ubuntu1~18.04) 7.5.0"
.section .note.GNU-stack,"",@progbits
bls
: Branch on Lower than or Same.
The main function calls
atoi
twice to convert the arguments to integers and then calls func1
to compare those two integers. Since , the control flow goes to
.L2
. Now w0 = [sp+8] = 1830628817 = x0
.Eventually, when
printf
is called, the value stored in x0
will be printed (calling convention). Hence this program prints 1830628817
, which is 0x6d1d2dd1
in hex.There is something on my shop network running at
mercury.picoctf.net:53740
, but I can't tell what it is. Can you?Save the G-code to a file:
$ nc mercury.picoctf.net 53740 > G-code.txt
Best Stuff - Cheap Stuff, Buy Buy Buy... Store Instance: source. The shop is open for business at
nc mercury.picoctf.net 34938
.Buy -10 "Quiet Quiches" and then buy 1 flag. Convert ASCII numbers to text.
#!/usr/bin/env python3
characters = [112, 105, 99, 111, 67, 84, 70, 123, 98, 52, 100, 95, 98, 114, 111, 103, 114, 97, 109, 109, 101, 114, 95, 98, 97, 54, 98, 56, 99, 100, 102, 125]
flag = ""
for character in characters:
flag += chr(character)
print(flag)
For what argument does this program print
win
with variables 85
, 6
and 3
? File: chall_1.S Flag format: picoCTF{XXXXXXXX} -> (hex, lowercase, no 0x, and 32 bits. ex. 5614267 would be picoCTF{0055aabb}) .arch armv8-a
.file "chall_1.c"
.text
.align 2
.global func
.type func, %function
func:
sub sp, sp, #32 @ allocate 32 bytes on the stack
str w0, [sp, 12] @ [sp+12] = w0 = [x29+44] = arg
mov w0, 85 @ w0 = 85
str w0, [sp, 16] @ [sp+16] = w0 = 85
mov w0, 6 @ w0 = 6
str w0, [sp, 20] @ [sp+20] = w0 = 6
mov w0, 3 @ w0 = 3
str w0, [sp, 24] @ [sp+24] = w0 = 3
ldr w0, [sp, 20] @ w0 = [sp+20] = 6
ldr w1, [sp, 16] @ w1 = [sp+16] = 85
lsl w0, w1, w0 @ w0 = w1 * (2**w0) = 85 * (2**6) = 85 * 64 = 5440
str w0, [sp, 28] @ [sp+28] = w0 = 5440
ldr w1, [sp, 28] @ w1 = [sp+28] = 5440
ldr w0, [sp, 24] @ w0 = [sp+24] = 3
sdiv w0, w1, w0 @ w0 = w1 // w2 = 5440 // 3 = 1813
str w0, [sp, 28] @ [sp+28] = w0 = 1813
ldr w1, [sp, 28] @ w1 = [sp+28] = 1813
ldr w0, [sp, 12] @ w0 = [sp+12] = arg
sub w0, w1, w0 @ w0 = w1 - w0 = 1813 - arg
str w0, [sp, 28] @ [sp+28] = w0 = 1813 - arg
ldr w0, [sp, 28] @ w0 = [sp+28] = 1813 - arg
add sp, sp, 32
ret
.size func, .-func
.section .rodata
.align 3
.LC0:
.string "You win!"
.align 3
.LC1:
.string "You Lose :("
.text
.align 2
.global main
.type main, %function
main:
stp x29, x30, [sp, -48]!
add x29, sp, 0
str w0, [x29, 28]
str x1, [x29, 16]
ldr x0, [x29, 16]
add x0, x0, 8
ldr x0, [x0]
bl atoi
str w0, [x29, 44]
ldr w0, [x29, 44]
bl func
cmp w0, 0
bne .L4
adrp x0, .LC0
add x0, x0, :lo12:.LC0
bl puts
b .L6
.L4:
adrp x0, .LC1
add x0, x0, :lo12:.LC1
bl puts
.L6:
nop
ldp x29, x30, [sp], 48
ret
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 7.5.0-3ubuntu1~18.04) 7.5.0"
.section .note.GNU-stack,"",@progbits
lsl
: Logical Shift Left. It provides the value of a register multiplied by a power of two, inserting zeros into the vacated bit positionssdiv
: Signed DivideNote that the
x29
register is the frame pointer in ARM64. It is equivalent to the RBP
register in Intel x86-64. The function func
is just doing math:Hence
arg = 1813
, which is 0x715
in hex.What integer does this program print with argument
3848786505
? File: chall_2.S Flag format: picoCTF{XXXXXXXX} -> (hex, lowercase, no 0x, and 32 bits. ex. 5614267 would be picoCTF{0055aabb})b
: Branch. This is the uncoditional branchbcc
: Branch on Carry Clear. This is the conditional branchNote that the
wzr
register is equivalent to 0. The instruction str wzr, [sp, 24]
zeros out the content of [sp+24]
.Here
[sp+24]
is the result and [sp+28]
is the counter. The loop keeps increment [sp+24]
by 3
and compares the counter [sp+28]
with 3848786505
. In the end, the result [sp+24]
is printed out. In other word, the program computes 3848786505 * 3 = 11546359515
, which is 0x2b03776db
in hex. Since the flag requires 32-bit hex, this hex number is truncated as 0xb03776db
.Each function call prints out a character:

Pseudocode
Hmmm this is a weird file... enter_password. There is a instance of the service running at
mercury.picoctf.net:35862
.What integer does this program print with argument
3350728462
? File: chall_3.S Flag format: picoCTF{XXXXXXXX} -> (hex, lowercase, no 0x, and 32 bits. ex. 5614267 would be picoCTF{0055aabb}) .arch armv8-a
.file "chall_3.c"
.text
.align 2
.global func1
.type func1, %function
func1:
stp x29, x30, [sp, -48]! @ push x29 and x30 onto the stack
add x29, sp, 0 @ x29 = sp + 0 = sp
str w0, [x29, 28] @ [x29+28] = w0 = 3350728462
str wzr, [x29, 44] @ [x29+44] = wzr = 0
b .L2 @ branch to .L2 unconditionally
.L4:
ldr w0, [x29, 28] @ w0 = [x29+28]
and w0, w0, 1 @ w0 &= 1 => => w0 = 1 if all bits of w0 are 1, w0 = 0 otherwise
cmp w0, 0 @ compare w0 and 0
beq .L3 @ branch to .L3 if w0 == 0
ldr w0, [x29, 44] @ w0 = [x29+44]
bl func2 @ branch to func2
str w0, [x29, 44] @ [x29+44] = w0
.L3:
ldr w0, [x29, 28] @ w0 = [x29+28]
lsr w0, w0, 1 @ w0 >> 1
str w0, [x29, 28] @ [x29+28] = w0
.L2:
ldr w0, [x29, 28] @ w0 = [x29+28]
cmp w0, 0 @ compare w0 and 0 => compare [x29+28] and 0
bne .L4 @ if [x29+28] != 0, branch to .L4
ldr w0, [x29, 44] @ w0 = [x29+44]
ldp x29, x30, [sp], 48
ret
.size func1, .-func1
.align 2
.global func2
.type func2, %function
func2:
sub sp, sp, #16 @ allocate 16 bytes on the stack
str w0, [sp, 12] @ [sp+12] = w0
ldr w0, [sp, 12] @ w0 = [sp+12]
add w0, w0, 3 @ w0 += 3
add sp, sp, 16
ret
.size func2, .-func2
.section .rodata
.align 3
.LC0:
.string "Result: %ld\n"
.text
.align 2
.global main
.type main, %function
main:
stp x29, x30, [sp, -48]!
add x29, sp, 0
str w0, [x29, 28]
str x1, [x29, 16]
ldr x0, [x29, 16]
add x0, x0, 8
ldr x0, [x0]
bl atoi
bl func1
str w0, [x29, 44]
adrp x0, .LC0
add x0, x0, :lo12:.LC0
ldr w1, [x29, 44]
bl printf
nop
ldp x29, x30, [sp], 48
ret
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 7.5.0-3ubuntu1~18.04) 7.5.0"
.section .note.GNU-stack,"",@progbits
stp
: Store Pairbl
: Branch with Linklsr
: Logical Shift RightThe program computes
and w0, w0, 1
, where w0
is the argument. If the w0 & 1 = 1
, it increments the counter by 3. Otherwise, it does nothing and continues. After each round of testing, the program divides w0
by 2 and repeats this test until w0 = 0
.#!/usr/bin/env python3
target = 3350728462
result = 0
while target != 0:
if target & 1 != 0:
result += 3
target >>= 1
print(hex(result))
Compile the assembly code:
$ gcc -c chall.S -o chall.o
$ gcc chall.o -o chall
Disassemble the main function:
pwndbg> disass main

PIE
Since PIE is enabled, all the addresses here are just offsets. We need to run the program for the correct addresses to load:
pwndbg> r
...
pwndbg> disass main

main
The
memcmp
function compares our input with the flag. Set a breakpoint on memcmp
and run the program again:pwndbg> b *0x00005555555552f6
pwndbg> r
The flag is located in RSI at this moment:

flag
Take a look at the pseudocode:

strncpy()
Here
unk_2008
is the encrypted flag:
Ecrypted flag
- 1.Figure out the flag length
- 2.Find the address of the "Correct!" state
- 3.Find the address of the "Incorrect." state
The length of the encrypted flag is 30, so the flag in clear should be 30-byte long as well.
The
call puts
instruction for "Correct!":
Correct
The
call puts
instruction for "Incorrect.":
Incorrect
#!/usr/bin/env python3
import angr
import claripy
FLAG_LEN = 30
STDIN_FD = 0
base_addr = 0x100000 # To match addresses to Ghidra
proj = angr.Project('brute', main_opts={'base_addr': base_addr})
flag_chars = [claripy.BVS('flag_%d' % i, 8) for i in range(FLAG_LEN)]
flag = claripy.Concat( *flag_chars + [claripy.BVV(b'\n')]) # Add \n for scanf() to accept the input
state = proj.factory.full_init_state(
args=['brute'],
add_options=angr.options.unicorn,
stdin=flag,
)
# Add constraints that all characters are printable
for k in flag_chars:
state.solver.add(k >= ord('!'))
state.solver.add(k <= ord('~'))
simgr = proj.factory.simulation_manager(state)
find_addr = 0x100a72 # the address of "call puts" for SUCCESS
avoid_addr = 0x100a86 # the address of "call puts" for FAILURE
simgr.explore(find=find_addr, avoid=avoid_addr)
if (len(simgr.found) > 0):
for found in simgr.found:
print(found.posix.dumps(STDIN_FD))
Start an angr Docker environment:
docker run -it angr/angr
Run this script and wait. The script will take some time to finish, so be patient.
What integer does this program print with argument
3964545182
? File: chall_4.S Flag format: picoCTF{XXXXXXXX} -> (hex, lowercase, no 0x, and 32 bits. ex. 5614267 would be picoCTF{0055aabb}) .arch armv8-a
.file "chall_4.c"
.text
.align 2
.global func1
.type func1, %function
func1:
stp x29, x30, [sp, -32]!
add x29, sp, 0
str w0, [x29, 28]
ldr w0, [x29, 28]
cmp w0, 100
bls .L2 @ if w0 <= 100, branch to .L2
ldr w0, [x29, 28] @ w0 = [x29+28]
add w0, w0, 100 @ w0 += 100
bl func2 @ branch to fun2
b .L3 @ branch to .L3 unconditionally
.L2:
ldr w0, [x29, 28]
bl func3 @ branch to func3
.L3:
ldp x29, x30, [sp], 32
ret
.size func1, .-func1
.align 2
.global func2
.type func2, %function
func2:
stp x29, x30, [sp, -32]!
add x29, sp, 0
str w0, [x29, 28]
ldr w0, [x29, 28]
cmp w0, 499
bhi .L5 @ branch to .L5 if w0 > 499
ldr w0, [x29, 28]
sub w0, w0, #86
bl func4 @ branch to func4
b .L6 @ branch to .L6 unconditionally
.L5:
ldr w0, [x29, 28]
add w0, w0, 13 @ w0 += 13
bl func5 @ branch to func5
.L6:
ldp x29, x30, [sp], 32
ret
.size func2, .-func2
.align 2
.global func3
.type func3, %function
func3:
stp x29, x30, [sp, -32]!
add x29, sp, 0
str w0, [x29, 28]
ldr w0, [x29, 28]
bl func7 @ branch to func7
ldp x29, x30, [sp], 32
ret
.size func3, .-func3
.align 2
.global func4
.type func4, %function
func4:
stp x29, x30, [sp, -48]!
add x29, sp, 0
str w0, [x29, 28]
mov w0, 17 @ w0 = 17
str w0, [x29, 44]
ldr w0, [x29, 44]
bl func1 @ branch to func1
str w0, [x29, 44]
ldr w0, [x29, 28]
ldp x29, x30, [sp], 48
ret
.size func4, .-func4
.align 2
.global func5
.type func5, %function
func5:
stp x29, x30, [sp, -32]!
add x29, sp, 0
str w0, [x29, 28]
ldr w0, [x29, 28]
bl func8 @ branch to func8
str w0, [x29, 28]
ldr w0, [x29, 28]
ldp x29, x30, [sp], 32
ret
.size func5, .-func5
.align 2
.global func6
.type func6, %function
func6:
sub sp, sp, #32
str w0, [sp, 12]
mov w0, 314 @ w0 = 314
str w0, [sp, 24] @ [sp+24] = w0 = 314
mov w0, 1932 @ w0 = 1932
str w0, [sp, 28] @ [sp+28] = w0
str wzr, [sp, 20] @ [sp+20] = 0
str wzr, [sp, 20]
b .L14 @ branch to .L14
.L15:
ldr w1, [sp, 28] @ w1 = [sp+28]
mov w0, 800 @ w0 = 800
mul w0, w1, w0 @ w0 *= w1
ldr w1, [sp, 24] @ w1 = [sp+24]
udiv w2, w0, w1 @ w2 = w0 // w1
ldr w1, [sp, 24] @ w1 = [sp+24]
mul w1, w2, w1 @ w1 *= w2
sub w0, w0, w1 @ w0 -= w1
str w0, [sp, 12] @ [sp+12] = w0
ldr w0, [sp, 20] @ w0 = [sp+20]
add w0, w0, 1 @ w0 += 1
str w0, [sp, 20] @ [sp+20] = w0
.L14:
ldr w0, [sp, 20]
cmp w0, 899
bls .L15 @ branch to .L15 if w0 <= 899
ldr w0, [sp, 12]
add sp, sp, 32
ret
.size func6, .-func6
.align 2
.global func7
.type func7, %function
func7:
sub sp, sp, #16
str w0, [sp, 12]
ldr w0, [sp, 12]
cmp w0, 100
bls .L18 @ branch to .L18 if w0 <= 100
ldr w0, [sp, 12]
b .L19 @ branch to .L19 unconditionally
.L18:
mov w0, 7 @ w0 = 7
.L19:
add sp, sp, 16
ret
.size func7, .-func7
.align 2
.global func8
.type func8, %function
func8:
sub sp, sp, #16
str w0, [sp, 12]
ldr w0, [sp, 12]
add w0, w0, 2 @ w0 += 2
add sp, sp, 16
ret
.size func8, .-func8
.section .rodata
.align 3
.LC0:
.string "Result: %ld\n"
.text
.align 2
.global main
.type main, %function
main:
stp x29, x30, [sp, -48]!
add x29, sp, 0
str w0, [x29, 28]
str x1, [x29, 16]
ldr x0, [x29, 16]
add x0, x0, 8
ldr x0, [x0]
bl atoi
str w0, [x29, 44]
ldr w0, [x29, 44]
bl func1
mov w1, w0
adrp x0, .LC0
add x0, x0, :lo12:.LC0
bl printf
nop
ldp x29, x30, [sp], 48
ret
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 7.5.0-3ubuntu1~18.04) 7.5.0"
.section .note.GNU-stack,"",@progbits
bhi
: Branch on Higher thanHere is my note:
arg = 3964545182
1. func1: 3964545182 + 100 = 3964545282
2. func2: 3964545282 + 13 = 3964545295
3. func5: do nothing
4. func8: 3964545295 + 2 = 3964545297
5. func1: do nothing
6. func3: do nothing
7. func7: do nothing
result = 3964545297 = 0xec4e2911
Someone, solve it!
It's not a bad idea to learn to read Powershell. We give you the output, but do you think you can find the input? rev_PS.ps1 output.txt
Todo!
Someone, solve it!
I don't trust password checkers made by other people, so I wrote my own. It doesn't even need to store the password! If you can crack it I'll give you a flag. remote
nc mercury.picoctf.net 17615
Todo!
Someone, solve it!
Todo!
Last modified 1yr ago