1.相关结构复习
画成思维导图了:思维导图
2.相关代码复习
libc2.24以上的检查代码
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
/* Fast path: The vtable pointer is within the __libc_IO_vtables
section. */
uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
const char *ptr = (const char *) vtable;
uintptr_t offset = ptr - __start___libc_IO_vtables;
if (__glibc_unlikely (offset >= section_length))
/* The vtable pointer is not in the expected section. Use the
slow path, which will terminate the process if necessary. */
_IO_vtable_check ();
return vtable;
}
vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。
利用方式两种:
1.利用__IO_str_jumps中的_IO_str_finsh函数
2.利用__IO_str_jumps中的_IO_str_overflow函数
3.利用方式
利用_IO_str_finsh函数实现函数调用
void _IO_str_finish (FILE *fp, int dummy)
{
if (fp->_IO_buf_base && !(fp->_flags & 1))
((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h]
fp->_IO_buf_base = NULL;
_IO_default_finish (fp, 0);
}
条件:
-
fp->_IO_buf_base为真
-
fp->_flags & 1 为假 // fp->_flags=0
fp+E8
处填成system_addr ,还有 fp->_IO_buf_base
填上binsh_addr地址主要是利用__IO_str_jumps
地址错位的方法,通过_IO_str_jumps - 8
来调用_IO_str_finish
利用_IO_str_overflow函数实现函数调用
__int64 __fastcall IO_str_overflow(_IO_FILE *fp, unsigned int a2)
{
int v2; // ecx
signed __int64 result; // rax
_QWORD *v4; // rdx
char *v5; // r12
unsigned __int64 v6; // r13
unsigned __int64 v7; // r14
__int64 v8; // rax
__int64 v9; // r15
_QWORD *v10; // rax
_QWORD *v11; // rax
v2 = fp->_flags;
if ( fp->_flags & 8 )
return (unsigned int)-(a2 != -1);
if ( (fp->_flags & 0xC00) == 0x400 )
{
v4 = fp->_IO_read_ptr;
v11 = fp->_IO_read_end;
BYTE1(v2) |= 8u;
LODWORD(fp->_flags) = v2;
fp->_IO_write_ptr = v4;
fp->_IO_read_ptr = v11;
}
else
{
v4 = fp->_IO_write_ptr;
}
v6 = (char *)fp->_IO_buf_end - (char *)fp->_IO_buf_base
if ( (char *)v4 - (char *)fp->_IO_write_base >= v6 + (a2 == -1) )
{
if ( v2 & 1 )
return 0xFFFFFFFFLL;
v7 = 2 * v6 + 100;
if ( v6 > v7 )
return 0xFFFFFFFFLL;
v8 = ((__int64 (__fastcall *)(unsigned __int64)) fp + 0xE0)(2 * v6 + 100); // call
v9 = v8;
.........
}
.......
.......
}
条件:
- fp->_flags = 0
- fp->_IO_buf_base = 0
- fp->_IO_buf_end = (bin_sh_addr - 100) / 2#如果bin/sh地址以奇数结尾可以+1以避免向下取整
- fp->_IO_write_ptr = 0xffffffff
- fp->_IO_write_base = 0
- fp->_mode = 0
- fp+0xe0 = system
fp->_flags & 8 得为0,(fp->_flags & 0xC00) == 0x400 得为0 , fp->_flags & 1 得为0, 所以我这里就将fp->_flags设置为0 ; 并设置fp->_IO_write_ptr - fp->_IO_write_base > fp->_IO_buf_end - fp->_IO_buf_base
这样我们才能够绕过检查,来到函数调用(fp + 0xE0)(2 * v6 + 100)也就是 (fp + 0xE0)(2 * (fp->_IO_buf_end - fp->_IO_buf_base) + 100)
我们可以将 fp + 0x E0设置成 system函数地址 ,fp->_IO_buf_base设置成0,fp->_IO_buf_end设置成 (binsh_addr-100)/2这样就设置了system参数,fp->_IO_write_ptr设置成一个很大的正值
这里有个坑,就是(binsh_addr-100)/2, 当/bin/sh地址存在于libc中,它/2再*2 若是除不尽有余数的话 会影响最后的参数地址,我们为了避免这种情况我们尽量在堆内存中填入"/bin/sh\x00", 因为堆内存往往都是2的倍数对齐。