当前位置: 首页 > news >正文

【PWN】04.Linux-User Mode-栈溢出-x86-中级ROP

中级 ROP 主要是使用了一些比较巧妙的 Gadgets。

1 ret2csu

原理

在 64 位程序中,函数的前 6 个参数是通过寄存器传递的,但是大多数时候,我们很难找到每一个寄存器对应的 gadgets。 这时候,我们可以利用 x64 下的 __libc_csu_init 中的 gadgets。这个函数是用来对 libc 进行初始化操作的,而一般的程序都会调用 libc 函数,所以这个函数一定会存在。

我们先来看一下这个函数 (当然,不同版本的这个函数有一定的区别)

.text:00000000004005C0 ; void _libc_csu_init(void)
.text:00000000004005C0                 public __libc_csu_init
.text:00000000004005C0 __libc_csu_init proc near               ; DATA XREF: _start+16o
.text:00000000004005C0                 push    r15
.text:00000000004005C2                 push    r14
.text:00000000004005C4                 mov     r15d, edi
.text:00000000004005C7                 push    r13
.text:00000000004005C9                 push    r12
.text:00000000004005CB                 lea     r12, __frame_dummy_init_array_entry
.text:00000000004005D2                 push    rbp
.text:00000000004005D3                 lea     rbp, __do_global_dtors_aux_fini_array_entry
.text:00000000004005DA                 push    rbx
.text:00000000004005DB                 mov     r14, rsi
.text:00000000004005DE                 mov     r13, rdx
.text:00000000004005E1                 sub     rbp, r12
.text:00000000004005E4                 sub     rsp, 8
.text:00000000004005E8                 sar     rbp, 3
.text:00000000004005EC                 call    _init_proc
.text:00000000004005F1                 test    rbp, rbp
.text:00000000004005F4                 jz      short loc_400616
.text:00000000004005F6                 xor     ebx, ebx
.text:00000000004005F8                 nop     dword ptr [rax+rax+00000000h]
.text:0000000000400600
.text:0000000000400600 loc_400600:                             ; CODE XREF: __libc_csu_init+54j
.text:0000000000400600                 mov     rdx, r13
.text:0000000000400603                 mov     rsi, r14
.text:0000000000400606                 mov     edi, r15d
.text:0000000000400609                 call    qword ptr [r12+rbx*8]
.text:000000000040060D                 add     rbx, 1
.text:0000000000400611                 cmp     rbx, rbp
.text:0000000000400614                 jnz     short loc_400600
.text:0000000000400616
.text:0000000000400616 loc_400616:                             ; CODE XREF: __libc_csu_init+34j
.text:0000000000400616                 add     rsp, 8
.text:000000000040061A                 pop     rbx
.text:000000000040061B                 pop     rbp
.text:000000000040061C                 pop     r12
.text:000000000040061E                 pop     r13
.text:0000000000400620                 pop     r14
.text:0000000000400622                 pop     r15
.text:0000000000400624                 retn
.text:0000000000400624 __libc_csu_init endp

这里我们可以利用以下几点:

(1)从 0x000000000040061A 一直到结尾,我们可以利用栈溢出构造栈上数据来控制 rbx,rbp,r12,r13,r14,r15 寄存器的数据。

(2)从 0x0000000000400600 到 0x0000000000400609,

loc_400600:mov     rdx, r13    ; 将 r13 的值复制到 rdxmov     rsi, r14    ; 将 r14 的值复制到 rsimov     edi, r15d   ; 将 r15d 的值复制到 edi (rdi 的低 32 位)

这三条指令在准备调用一个函数所需的参数

x64 调用约定 (System V AMD64 ABI): 在 Linux/macOS 等系统的 x64 架构上,函数的前三个参数分别通过寄存器 rdi (第一个)、rsi (第二个)、rdx (第三个) 传递。

指令 mov edi, r15d 只操作 32 位寄存器 (edi 是 rdi 的低 32 位)。

在 x64 架构下,当向一个 32 位寄存器 (如 edi) 写入值时,对应的 64 位寄存器 (rdi) 的高 32 位会自动清零

因此,mov edi, r15d 的效果等同于 mov rdi, r15(前提是 r15 的高 32 位本身是 0,或者你只关心 r15 的低 32 位值)。

结论是:通过控制 r15d,我们实际上可以完全控制 rdi 寄存器的值(使其低 32 位为 r15d,高 32 位为 0)。 

    call    qword ptr [r12+rbx*8] ; 调用地址位于 [r12 + rbx*8] 处的函数

如果我们可以合理地控制 r12 与 rbx,那么我们就可以调用我们想要调用的函数。比如说我们可以控制 rbx 为 0,r12 为存储我们想要调用的函数的地址。

(3)从 0x000000000040060D 到 0x0000000000400614,

    add     rbx, 1      ; rbx = rbx + 1 (索引加 1)cmp     rbx, rbp    ; 比较 rbx 和 rbpjnz     short loc_400600 ; 如果不相等 (rbx != rbp),跳回 loc_400600 继续循环
  • 作用: 这部分控制循环是否继续执行下一次迭代。

  • 原始循环逻辑: 在初始化函数时,rbp 被设置成 .init_array 中函数指针的数量((rbp - r12) / 8)。循环从 rbx = 0 开始,每次调用 [r12 + rbx*8] 指向的函数后,rbx 加 1,然后和 rbp (函数数量) 比较。只要 rbx < rbp (jnz 条件满足),就跳回去继续调用下一个函数。

  • 打破循环的关键: 如果我们想利用这段代码只调用 一次 我们指定的函数,然后正常退出循环继续执行后面的代码(pop 指令和 retn),我们需要确保循环在执行一次后就停止。

  • 这可以通过设置 rbx 和 rbp 的值,使得在第一次执行完 add rbx, 1 后,rbx 的值等于 rbp 的值。

  • 最直接的方法:设置 rbx = 0 和 rbp = 1(控制 rbx 与 rbp 的之间的关系为 rbx+1 = rbp

    • 进入循环: rbx = 0rbp = 1 (0 != 1, 条件满足,进入循环体)

    • 调用函数: call [r12 + 0*8] -> call [r12]

    • 增加索引: rbx = 0 + 1 = 1

    • 比较: cmp rbx(1), rbp(1) -> 相等 (Zero Flag set)

    • 条件跳转: jnz (Jump if Not Zero) 条件不满足,不跳转。

    • 继续执行: 执行 add rsp, 8 及后面的 pop 和 retn 指令,正常退出函数/循环。

  • 目的: 这样设置后,这段代码片段就变成了一个“调用一个特定函数(地址在 [r12])并使用特定参数(r15d/r15->rdir14->rsir13->rdx),然后正常返回”的自包含 gadget。

例1

这里我们以蒸米的一步一步学 ROP 之 linux_x64 篇中 level5 为例。

首先检查程序的安全保护

程序为 64 位,开启了堆栈不可执行保护。

其次,寻找程序的漏洞,可以看出程序中有一个简单的栈溢出

read() 是一个系统函数,用于 从输入源读取数据

  • 参数解释:

    • 0:表示从 标准输入(stdin) 读取数据(比如键盘输入或文件重定向)。

    • 0x200uLL:表示要读取 512 字节 的数据(0x200 是十六进制,等于十进制的 512;uLL 表示“无符号长长整型”)

read() 会向 buf 写入 512 字节,但 buf 只能容纳 128 字节。

简单浏览下程序,发现程序中既没有 system 函数地址,也没有 /bin/sh 字符串,所以两者都需要我们自己去构造了。

教程中,作者尝试在本机使用 system 函数来获取 shell 失败了,应该是环境变量的问题,所以这里使用的是 execve 来获取 shell。

基本利用思路如下

  • 利用栈溢出执行 libc_csu_gadgets 获取 write 函数地址,并使得程序重新执行 main 函数
  • 根据 libcsearcher 获取对应 libc 版本以及 execve 函数地址
  • 再次利用栈溢出执行 libc_csu_gadgets 向 bss 段写入 execve 地址以及 '/bin/sh’ 地址,并使得程序重新执行 main 函数。
  • 再次利用栈溢出执行 libc_csu_gadgets 执行 execve('/bin/sh') 获取 shell。

相关文章:

  • 工业 AI Agent:智能化转型的核心驱动力
  • openGrok大型源码(AOSP/openHarmonyOS等)开发提升检索效率必备神器
  • Day03_数据结构
  • 微軟將開始使用 Copilot Vision 監控 Windows 10 和 11 用戶的螢幕
  • Python 装饰器
  • 136只出现一次的数字
  • C++性能测评工具
  • 【数据库】在线体验KingbaseES平台,零门槛学习,并快速体验Oracle增改查全基础功能
  • MSPM0G3507学习笔记(二) 超便捷配置led与按键
  • linux thermal framework(5)_thermal core
  • 60days challenge day34
  • Vue3+TypeScript 导入枚举(Enum)最佳实践
  • Docker 镜像相关命令介绍与示例
  • 如何为你的工作站添加“一键切换显示器接口”功能?
  • 限流系列之三:TDMQ for Apache Pulsar 限流技术深度解析
  • 聊聊 Pulsar:Consumer 源码解析
  • Kafka 4.0.0集群部署
  • coze中怎么创建插件
  • KVM高级功能部署
  • 死锁、线程总结
  • 机关网站建设创新/seo优化排名
  • 辽宁省朝阳市做网站/中国站长之家网站
  • bootstrap3网站模板/原版百度
  • 南京那些公司做网站/高明搜索seo
  • 建设银行舟山分行网站/学网络与新媒体后悔死了
  • 建网站是怎么造成的/网页模板图片