前言
这篇是浅析house of cat(上)-CSDN博客的续写,主要想从源码调试的角度去讲解一下具体流程。
这里选择的例题是强网杯2022的houseofcat,这里我只是使用已经写好的脚本去调试,讲解一些上篇中没指出来,但需要我们绕过的地方。
调试
calloc-->>__malloc_assert
这里假设我们已经能够触发__malloc_assert()函数了,开始进入分配函数calloc():
下断点在sysmalloc函数:
开始验证条件:
因为topchunk没有页对齐,所以开始调用__malloc_assert()函数:
之后就是正常去调用__fxprintf:
__fxprintf -->> __vfxprintf
这一步是没有什么问题的,正常进入。
进入到__vfxprintf函数之后就到了我们特别需要注意的地方:
我们来看一下这个函数的源码:
#define _IO_flockfile(_fp) if (((_fp)->_flags & _IO_USER_LOCK) == 0) _IO_lock_lock (*(_fp)->_lock)
_IO_lock_lock的源码:
#define _IO_lock_lock(_name) \
do { \
void *__self = THREAD_SELF; \
if (SINGLE_THREAD_P && (_name).owner == NULL) \
{ \
(_name).lock = LLL_LOCK_INITIALIZER_LOCKED; \
(_name).owner = __self; \
} \
else if ((_name).owner != __self) \
{ \
lll_lock ((_name).lock, LLL_PRIVATE); \
(_name).owner = __self; \
} \
else \
++(_name).cnt; \
} while (0)
看到网上有人说这里:“可以看到进了之后就是一个无限循环,死锁。所以我们要绕过这个,通过上面的代码可以得到条件”,这种说法其实是不对的,do{}whlie(0)其实只是一种特殊的宏写法,表面上在这里一点意义都没有,只执行一次而已。但是锁的说法是对的:
这个宏 _IO_lock_lock,用于处理锁的逻辑。逐步分析这个宏的功能:
-
宏定义:_IO_lock_lock(_name) 是一个宏,它接收一个参数 _name,这个参数代表要操作的锁。
-
获取当前线程:void *__self = THREAD_SELF; 这行代码获取当前线程的标识,并将其存储在局部变量 __self 中。THREAD_SELF 也是一个宏,用于获取当前线程的标识符。
-
检查锁的状态:if ((_name).owner != __self) 检查当前线程是否已经拥有这个锁。如果锁的拥有者 (owner) 不是当前线程 (__self),则执行以下操作:
- lll_lock ((_name).lock, LLL_PRIVATE);:调用 lll_lock 函数来锁定 _name 指定的锁。LLL_PRIVATE 是一个参数,表示锁的类型或锁定方式。
- (_name).owner = __self;:将锁的拥有者设置为当前线程。
-
增加锁的计数:++(_name).cnt; 增加锁的计数。这可能是为了跟踪锁被同一线程重复获取的次数。
想要绕过这个死锁就必须让if语句判断不成功,那就得让 ((_fp)->_flags & _IO_USER_LOCK) == 0不成立。但是很不行,这个判断很难绕过去,但是无伤大雅,我们只需要让fp->_lock是一个可以指向的地址,能够让他完成这些操作就可以了。
locked_vfxprintf -> vfprintf_internal
接下来就是正常的调用链了,程序进入locked_vfxprintf函数,之后去调用vfprintf_internal函数。
之后走vtable检查:
只要通过vtable检查,我们就可以通过return vtable来返回到我们需要的函数, 在上篇中我们了解到他希望去调用vtable指针_IO_wfile_jumps所指向的结构体中的_IO_file_xsputn,但是我们将_IO_wfile_jumps+0x10,他依然按照vtable+0x38的方式去调用,那么就会去调用到_IO_wfile_jumps+0x48,也就是__GI__IO_wfile_seekoff处:
接下来还有一个问题,我们必须让was_writing不为0才行,
所以我们必须让fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base
跳转到我们设置好的函数:
之后就是栈迁移+orw了:
总结:
这里给出所有需要绕过的地方:
- old_size >= 0x20; old_top.prev_inuse = 0; old_top页对齐(三个违法一个就行)
- fp.mode != 0
-
fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base 或者 _IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)
-
fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base