姓名:陈振烨
学号:20222314
实验日期:2024/09/29 — 2024/10/09
实验名称:缓冲区溢出和shellcode
指导教师:王志强
实验要求:
1.掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码(0.5分)
2.掌握反汇编与十六进制编程器 (0.5分)
3.能正确修改机器指令改变程序执行流程(0.5分)
4.能正确构造payload进行bof攻击(0.5分)***
5.注入一个自己制作的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指令执行后,将对标志寄存器产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。
2.掌握反汇编与十六进制编程器
反汇编工具:objdump ida_pro
十六进制编辑器:vim中的xxd指令,winhex,010editor,sublim等
objdump使用方式:objdump -d [filename]
IDA_pro使用方法:先对elf进行checksec,确定文件是32位还是64位,随后用对应的程序打开(9.0版本不需要区
分),若是exe文件,则exeinfo查询信息后打开(其实也可以直接试)
vim查看16进制 :在vim打开文件后,输入:%!xxd ,若进行了修改,在wq之前一定要:%!xxd -r,否则文件类型会出错
其他查看方式 :此处不再赘述,尤其是ida,不破解需要爆金币,可自行搜索,网上基本都有下载及破解方式
3.能正确修改机器指令改变程序执行流程
既然要修改机器指令,那么首先就要学会去查找原本的机器指令以及汇编程序,从而我们首先使用objdump查看具体的函数和机器指令:(第一列为内存地址,第二列为机器指令、第三列为机器指令对应的汇编语言)
这里我们主要关注红框所框出的getShell(),foo(),main()三个函数的汇编指令
观察main函数发现,call 跳转到了foo函数,而根据对文件的运行测试发现,它只会简单回显任何用户输入的字符串。根据实验要求,我们需要修改可执行文件,改变程序执行流程,直接跳转到getShell函数。因此,需要将call 8048491中的地址8048491修改为getShell的地址804847d,偏移量=8048491-80484ba=-41。补码表示为0xffffffd7,与第二列机器指令中的0xd7ffffff相吻合。由此可知,要想调用getShell,偏移量为0804847d(getShell函数的首地址)-80484ba=-61=0xffffff3c颠倒为计算机存储内容,为0xc3ffffff,即需要将0xd7ffffff修改为0xc3ffffff。
接下来对机器指令进行修改,先用vim查看文件的16进制,按照前面所提到的方式,vim后输入:%!xxd进入如图所示的界面,/d7ff查找机器指令
修改d7为c3,一定要记得先:%!xxd -r后再:wq,否则文件类型会出错
随后运行文件,./pwn1,成功getshell
4.能正确构造payload进行bof攻击
毕竟我是打ctf的,这里我想使用我自己的方法,所以我没有按照参考文档进行,这里掏出我的ubuntu虚拟机,那上面我已经部署好了pwn的相关环境
针对第四题,其实就是一道简单的ret2text,先对文件进行checksec
可以看到这是一个32位的小端程序,很好,没有NX和canary
正常情况下,刚才的objdump已经给出了函数的地址信息,但是这里为了展示一下ida的界面和用法,用32位ida打开文件重新分析
32位程序差四位,由此我们可以编写payload=b'A'*(28+4)+p32(getShell_addr):
EXP如下:
#r2t.py
from pwn import *
p=process("pwn2")
getShell_addr = 0x0804847d
payload = b'A'*(28+4) + p32(getShell_addr)
p.sendline(payload)
p.interactive()
运行后成功getshell
5.注入一个自己制作的shellcode并运行这段shellcode
ret2shellcode中比较一个关键的点就是寻找寄存器的地址,只有这样才能让shellcode被注入到可执行的段落,先利用execstcak -s pwn1命令禁用掉NX保护(其实在前文中可以看到NX并没有被开启,但是不知道为什么,不进行这一步是打不通的,应该是因为我的checksec出现了一些小bug),同时要注意禁用下ASLR
利用gdb调试,先进行一波注入测试(在root下gdb,不然那个地址好像会出错),在puts下断点,r运行后输入32个A和一堆零
from pwn import *
p = process("pwn1")
shellcode = asm(shellcraft.sh())
payload2 = b'A' * 32 +p32(0xffffd0a0)+shellcode
p.send(payload2)
p.interactive()
运行后成功getshell
这里就要说的我要吐槽的一个点了,其实就算开启了ASLR和NX保护也是可以打的,因为这是一个典型的小段32位程序,有gets和puts,且.plt.got表十分好找且地址正确,按道理来说不论是syscall(只能打关ASLR)还是ret2libc(这个能打保护双开),都可以通过一波注入泄露puts的真实地址,随后找到合适的libc库,结果就在我做拓展的时候,我发现虽然能找到system函数和/bin/sh字符串,但是这个程序居然没有int_80x??????(我满脑子都是纳尼)这就意味着我们没法通过中断指令进行系统调用,从而ret2syscall这一条路就彻底给我堵死了,后来我进行ret2libc时遇到了一个经典问题就是同长度同传参的segmentation fault,并且泄露的puts的real地址在关掉ASLR后竟然是一个抽象的0x61616161,当然后来这个问题我解决了,随后我又遇到了一个逆天的问题:本地打不通,我非常确信我的脚本绝对没问题了,毕竟在ctf时也经常出现本地打不通但是远程可以打通的问题,不管怎么着都是一次做题经验,无所谓了就是说()