scanf忘记加'&'危害有多大? 详解GOT表覆写攻击技术

前言

初学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

题目

passcode
题目地址:http://pwnable.kr/

linux下用ssh去连pwnable的服务器,密码是guest

$ ssh passcode@pwnable.kr -p2222

windows下可以用putty去连接

题目源码

source

题目分析

首先这段源代码中3个函数:main()login()welcome()
main()函数中先调用了welcome(),然后调用了login(),剩下的只是打印一些信息,没有卵用。welcome()做的事情是用scanf()获取用户的名字,然后打印它,也没有什么用。所以这题的重点就在于login()函数了。

login()函数中,先后调用scanf()获取用户输入的两个passcode然后分别与33815013371337作比较,如果相等的话,便会打印flag。这程序设计的逻辑似乎没啥问题,但是仔细一看,发现这里:

vul

scanf("%d",passcode1);passcode1没有加取地址符号&

如果scanf没加&的话,程序会默认从栈中读取4个字节的数据当做scanf取的地址

漏洞就发生在这里,我们可以利用上面提到的GOT覆写技术攻击这个程序。

解题思路

将一个GOT表中的函数地址写到栈中,用来充当scanf()取的地址,然后把system("/bin/cat flag")这条指令的地址写到这个GOT表中的函数。

当这个函数被调用时,就会直接执行system("/bin/cat flag")

解题过程

vul1
反汇编一下login()可以看到,fflush()将会在有漏洞的scanf()函数之后被调用,所以我们选择覆写fflush()在GOT表中的内容,让程序执行到上图高亮的地方的时候去执行system("/bin/cat flag")

asm
在上图中还可以找到system("/bin/cat flag")对应汇编代码开始于0x80485e3(这里有两条指令,第一条将参数"/bin/cat flag"入栈,第二条调用system()

也就是说要把0x80485e3这个值写入fflush()

现在查看一下GOT表中fflush()的位置,很多方法可以查看elf文件的GOT表,这里说两个常用的命令:

$ readelf -r target_elf

或者

$ objdump -R target_elf

查询结果如下:
got

fflush()位于0x0804a004

现在我们明确了攻击的方式,以数据0x80485e3覆写位于0x0804a004fflush()函数的GOT表

之后要做的就是在栈中构造这些信息,不难发现welocome()login()使用的是同一个EBP,如图:
ebp1

ebp2

如下图,name位于ebp-0x70,passcode1位于ebp-0x10
wel

ps1

0x70 - 0x10 = 96,也就是说name这个字符串第96个字节后的4字节数据将会被作为passcode1的地址。

最终我们的payload为:
"A" * 96 + p32(fflush_got) + str(0x80485e3)
这里要注意0x80485e3要转换成十进制输入,因为scanf()%d读取数据。

攻击后效果如图:
fin

解题脚本

#!/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()函数将参数入栈时的情况:
unchg
栈的情况:
stack_unchg
我们可以看到scanf()的两个参数arg[0] = "%d",arg[1] = 0xffffcfc4
相当于:

scanf("%d",0xffffcfc4);

来看看fflush()执行时的情况:
f1
正常的执行没有什么问题。

被攻击

再看一下我们将fflush()在GOT表中的地址写入栈中时(被攻击),有漏洞的scanf()函数将参数入栈的情况:
chg
栈的情况:
stack_chg
我们可以看到scanf()的两个参数arg[0] = "%d",arg[1] = 0x804a004

原本应该随机取的地址,被我们修改成为了一个特定的值
这时,scanf函数相当于:

scanf("%d",fflush@plt());

现在再来看看fflush()执行时的情况:
f2
已经跳到了某个神奇的地方了:)

你可能感兴趣的:(pwn,知识总结)