这题真难啊,折腾我好久,我都把整个游戏逆出来了,结果最后发现根本不用全部逆出来,哈哈。。不过没事,当作练习逆向了。。哈哈。。。
这道题我是没找到什么经典的缓冲区溢出啊格式化字符串啊double free啊UAF啊之类的洞。。。感觉我用的方法挺奇葩的。。。嗯。。。不知道跟大家用的方法一不一样。。。好了不废话了开始分析。。。
大概呢,就是作者自己mmap了一片内存,然后自己设计了一个chunk结构,然后申请内存空间都从这片mmap出来的内存申请。这个chunk结构的header有16字节,第二个一看就知道是chunk_size不说,关键是第一个,我一开始硬是没看出来这个QWORD是啥。。。
然后我们来看看game_start函数的部分代码(输入2 signup时)
v1 malloc 之后,会调用一个叫做dreflhs_rhs_withGC的函数,这个函数我前面一直没看懂,硬是盯了好久,才发现这其实是 带GC的 *参数1=参数2 的这么一个操作,所以chunk的第一个QWORD是一个类似引用计数的东西,我们跟进这个函数看看
这个代码讲起来有些复杂,总之大概就是*a1=a2,并且增加a2的计数,减少a1的计数,如果a1的计数变成0了,就以QWORD为单位遍历缓冲区里面的数,如果碰到在规定范围内的(*a1 <= 0x7FFFFFFFEFFFLL && *a1 > 0x400000LL),就对其进行处理,如rec_chunk函数所示。然而在这个范围内不一定是mmap中的地址,所以导致一个QWORD SHOOT,但是写入的数据只能是mem的值。 喔,还有就是我逆出来的结构体,供大家参考
所以什么时候会触发呢?就是sign第二次的时候,具体逻辑不说了。总之,如果前一个username转换成QWORD值时在范围内(*a1 <= 0x7FFFFFFFEFFFLL && *a1 > 0x400000LL)时,第二次sign就会导致[username]=mem。。。
所以怎么利用呢?好的,[任意地址]=mem,而且mem还会自增,还会有其他有效地址被存入mem!(所以有可能破坏mem中的shellcode) 这咋一看没什么用啊!我这种菜鸟,就卡在这想了好久! 最后根据我的不断实验,得出exploit方法:
注:
然后很尴尬的是,程序没有_exit的调用(主函数),所以找_exit的xref,发现唯一可能让他调用的是这里。。。
所以找个方法,把sign_infor->status->location_给污染了,就ok,实际上,如果动态跟一下的话,发现这个status就在cheat_buf的后面,而cheat有明显的溢出漏洞
300,够影响到status了,事实上,如果动态跟的话,我们会发现status的地址就在cheat_buf+0x98处(如果按照我都步骤做的话)。。。 然后就是构造payload了。。。
前面8个'A'是因为_exit的实际会跳转到cheat_buf+8处执行,动态跟一下就知道了,然后说说最后的那个0x605098,反正只要满载p->location不在范围内就好,我随便找的一个.data的地址,这个好像是mmap_initial?不记得了,哪个都一样。。。 shellcode基本上就是获取malloc函数地址,计算system地址,push (QWORD)"/bin/sh\x00"(刚好是8字节,哈哈),mov rdi,rsp, call system (伪代码,伪代码,请勿当真) 所以最后exp
_QWORD *__fastcall malloc_adjust(signed int a1)
{
signed int v2; // [sp+Ch] [bp-4h]@3
if ( a1 >= 0 && a1 > 16 ) // >16
{
if ( a1 <= 16 || a1 > 48 ) // >48
{
if ( a1 <= 48 || a1 > 80 ) // >80
v2 = 400; // 80<x
else
v2 = 80; // 48<x<=80
}
else
{
v2 = 48; // 16<size<=48
}
}
else
{
v2 = 16; // x<=16
}
return malloc_(v2);
}//这个malloc_adjust会根据输入调整一下实际分配的大小,就当他是对齐用的吧。。。
if ( v3 == 2 )
{
puts("welcome to Playerunknown's Battlegrounds");
puts("First,you need set your username and password");
v1 = malloc_adjust(48);
dreflhs_rhs_withGC((_QWORD **)&sign_infor, v1);// collapse
puts("input your username");
getstr(sign_infor->username, 0x10LL);
puts("input your password");
getstr(sign_infor->password, 0x10LL);
puts("Second,you need create a character and give him a name");
v2 = malloc_adjust(56);
dreflhs_rhs_withGC((_QWORD **)&sign_infor->status, v2);
sign_infor->status->health = 100LL;
sign_infor->status->capacity = 100LL;
sign_infor->status->stamina = 100LL;
puts("input your character's name");
getstr(sign_infor->status->name, 0x10LL);
puts("all is ok");
}
// *lhs=rhs
_QWORD *__fastcall dreflhs_rhs_withGC(_QWORD **pointer, void *mem_frommymalloc)
{
_QWORD *chunk_mem; // rax@2
_QWORD *chunk; // rax@4
_QWORD *result; // rax@5
if ( mem_frommymalloc )
{
chunk_mem = minus_16(mem_frommymalloc);
incre_ref(chunk_mem);
}
if ( *pointer )
{
chunk = minus_16(*pointer);
reduce_ref(chunk); // collapse
}
result = pointer;
*pointer = mem_frommymalloc;
return result;
}
__int64 __fastcall is_deref_a_inrange(_QWORD *a1)
{
return *a1 <= 0x7FFFFFFFEFFFLL && *a1 > 0x400000LL;
}
_DWORD *__fastcall ret_addr_of_first_addr(_QWORD *chunk, signed __int64 *pi)
{
signed __int64 i; // [sp+18h] [bp-18h]@3
signed __int64 size; // [sp+20h] [bp-10h]@3
_QWORD *mem; // [sp+28h] [bp-8h]@3
if ( !chunk )
exit(0);
mem = chunk + 2;
size = (chunk[1] - 16LL) / 8;
for ( i = *pi; i < size; ++i )
{
*pi = i;
if ( (unsigned __int8)is_deref_a_inrange(&mem[i]) )
return &mem[i];
}
return 0LL;
}
// also, reflect mem to chunk[2](data part)
_QWORD *__fastcall rec_chunk(_QWORD *chunk)
{
_QWORD *result; // rax@3
if ( !mem )
mem = malloc_adjust(4);
chunk[2] = mem;//任意chunk[2]可以被我们控制,会被赋值为mem的值,QWORD SHOOT here!!!
*mem = chunk; // 可能overflow,但我没用这个
result = mem + 1;
++mem;
return result;
}
__int64 __fastcall reduce_ref(_QWORD *chunk)
{
_QWORD *chunk_1; // rax@3
__int64 i; // [sp+10h] [bp-20h]@1
_QWORD *chunk_; // [sp+18h] [bp-18h]@1
_QWORD **p_elem; // [sp+20h] [bp-10h]@2
__int64 v6; // [sp+28h] [bp-8h]@1
v6 = *MK_FP(__FS__, 40LL);
i = 0LL;
chunk_ = chunk;
--*chunk;
if ( !*chunk_ ) // 如果引用计数变成0
{
while ( 1 )
{
p_elem = (_QWORD **)ret_addr_of_first_addr(chunk, &i);
if ( p_elem == 0LL ) // 如果在chunk中已经没有有效地址了
break;
++i; // 增加i并且做下次循环
chunk_1 = minus_16(*p_elem); // 如果 *pmem 是有效地址(在指定范围中),但不一定是在mmap的堆中, 也会把它当作mmap中的chunk,进行rec_chunk的调用
rec_chunk(chunk_1); // 它被当成了chunk,但不一定是mmap中的chunk(0x41414141-16)
}
rec_chunk(chunk_);
}
return *MK_FP(__FS__, 40LL) ^ v6;
}
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)