Links

Reverse Engineering

{"author": ["ret2basic"]}

Transformation

Challenge

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)])

Solution

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.

Implementation

#!/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)

keygenme-py

Challenge

​keygenme-trial.py​

Source Code

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

Solution

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.

Implementation

#!/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}")

crackme-py

Challenge

​crackme.py​

Source Code

# 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()

Solution

Modify the source code to call the decode_secret function.

Implementation

# choose_greatest()
print(decode_secret(bezos_cc_secret))

ARMssembly 0

Challenge

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})

Assembly with Comments

.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

Solution

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
1765227561≤18306288171765227561 \leq 1830628817
, 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.

speeds and feeds

Challenge

There is something on my shop network running at mercury.picoctf.net:53740, but I can't tell what it is. Can you?

Solution

Save the G-code to a file:
$ nc mercury.picoctf.net 53740 > G-code.txt
Use NC Viewer to plot the flag.

Shop

Challenge

Best Stuff - Cheap Stuff, Buy Buy Buy... Store Instance: source. The shop is open for business at nc mercury.picoctf.net 34938.

Solution

Buy -10 "Quiet Quiches" and then buy 1 flag. Convert ASCII numbers to text.

Implementation

#!/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)

ARMssembly 1

Challenge

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})

Assembly with Comments

.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

Solution

lsl: Logical Shift Left. It provides the value of a register multiplied by a power of two, inserting zeros into the vacated bit positions
sdiv: Signed Divide
Note 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:
85⋅263=arg\frac{85 \cdot 2^6}{3} = arg
Hence arg = 1813, which is 0x715 in hex.

ARMssembly 2

Challenge

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})

Solution

b: Branch. This is the uncoditional branch
bcc: Branch on Carry Clear. This is the conditional branch
Note 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.

Hurry up! Wait!

Challenge

​svchost.exe​

Solution

Each function call prints out a character:
Pseudocode

gogo

Challenge

Hmmm this is a weird file... enter_password. There is a instance of the service running at mercury.picoctf.net:35862.

Solution

ARMssembly 3

Challenge

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})

Assembly with Comments

.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

Solution

stp: Store Pair
bl: Branch with Link
lsr: Logical Shift Right
The 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.

Implementation

#!/usr/bin/env python3
​
target = 3350728462
​
result = 0
while target != 0:
if target & 1 != 0:
result += 3
target >>= 1
​
print(hex(result))

Let's get dynamic

Challenge

Can you tell what this file is reading? chall.S​

Solution

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

Easy as GDB

Challenge

The flag has got to be checked somewhere... File: brute​

Solution

Take a look at the pseudocode:
strncpy()
Here unk_2008 is the encrypted flag:
Ecrypted flag
This is a nice use case for the angr template. What we have to do here is:
  1. 1.
    Figure out the flag length
  2. 2.
    Find the address of the "Correct!" state
  3. 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

Implementation

#!/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.

ARMssembly 4

Challenge

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})

Assembly with Comments

.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

Solution

bhi: Branch on Higher than
Here 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

Powershelly

Someone, solve it!

Challenge

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​

Solution

Todo!

Rolling My Own

Someone, solve it!

Challenge

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

Solution

Todo!

Checkpass

Someone, solve it!

Challenge

What is the password? File: checkpass Flag format: picoCTF{...}

Solution

Todo!