重要的事情先说三遍:写服务端的人真菜!写服务端的人真菜!写服务端的人真菜!算是炒个冷饭。
在本文发布的时候,漏洞已经被提交并被网易修复,想做坏事的还是算了。
先提一下网易为了这个天梯系统写的“DzApi”,其实就是仿照jass虚拟机注册函数的方式向jass虚拟机注册自定义函数。
关键函数原型如下:
算是炒个冷饭。 在本文发布的时候,漏洞已经被提交并被网易修复,想做坏事的还是算了。 先提一下网易为了这个天梯系统写的“DzApi”,其实就是仿照jass虚拟机注册函数的方式向jass虚拟机注册自定义函数。关键函数原型如下:native DzAPI_Map_Ladder_SetStat takesplayer whichPlayer,string key,string value returns nothing那么我们尝试hook一下这个函数,就能做到篡改游戏结果的目的。为了文笔简洁,只贴几个关键函数的实现:
uintptr_twar3_searcher::search_get_instance()const
{
uintptr_tget_instance;
//=========================================
// (1)
//
// push 493E0h
// push 1
// push 1
// push 0
// mov edx, offset s_Config ; "config"
// mov ecx, esi
// call UnknowFunc <----
//=========================================
get_instance=search_string("config");
get_instance+=sizeofuintptr_t;
get_instance=next_opcode(get_instance, 0xE8, 5);
get_instance=convert_function(get_instance);
//=========================================
// (2)
//
// UnknowFunc:
// push esi
// mov esi, edx
// call jGetVMInstance <---
//=========================================
get_instance=next_opcode(get_instance, 0xE8, 5);
get_instance=convert_function(get_instance);
//=========================================
// (3)
//
// jGetVMInstance:
// jmp jGetVMInstance2 <----
//=========================================
get_instance=convert_function(get_instance);
//=========================================
// (4)
//
// jGetVMInstance2:
// push esi
// mov esi, ecx
// mov ecx, 5
// call GetInstance <----
// push esi
// mov ecx, eax
// call UnknowFunc
// pop esi
// retn
//=========================================
get_instance=next_opcode(get_instance, 0xE8, 5);
get_instance=convert_function(get_instance);
returnget_instance;
}
uint32_twar3_searcher::get_instance(uint32_tindex)
{
return((uint32_t(_fastcall*)(uint32_t))get_instance_)(index);
}
hashtable::native_func_table*get_native_function_hashtable()
{
return(hashtable::native_func_table*)(get_war3_searcher().get_instance(5)+0x18);
}
booltable_hook (constchar*proc_name,uintptr_t*old_proc_ptr,uintptr_tnew_proc)
{
hashtable::native_func_node*node_ptr=get_native_function_hashtable()->get(proc_name);
if(!node_ptr)
returnfalse;
*old_proc_ptr= (uintptr_t)node_ptr->func_address_;
node_ptr->func_address_ = (uint32_t)new_proc;
returntrue;
}以上就是通过函数名称在jass虚拟机中获取jass函数的地址的方法现在我们对hook函数进行一下封装
namespaceDzApi
{
uint32_tget_adress(std::stringdz_api_name);
voidhook(std::stringdz_api_name,uintptr_t*old_proc_ptr,uintptr_tnew_proc);
voidunhook(std::stringdz_api_name,uintptr_t*old_proc_ptr,uintptr_tnew_proc);
}
//native DzAPI_Map_SaveServerValue takesplayer whichPlayer, string key, string value returns boolean
//native DzAPI_Map_GetServerValue takesplayer whichPlayer, string key returns string
//native DzAPI_Map_Ladder_SetStat takesplayer whichPlayer, string key, string value returns nothing
//native DzAPI_Map_IsRPGLobby takesnothing returns boolean
//native DzAPI_Map_IsRPGLadder takesnothing returns boolean
//native DzAPI_Map_GetGameStartTime takesnothing returns integer
//native DzAPI_Map_Stat_SetStat takesplayer whichPlayer, string key, string value returns nothing
//native DzAPI_Map_GetMapLevel takesplayer whichPlayer returns integer
//native DzAPI_Map_MissionComplete takesplayer whichPlayer, string key, string value returns nothing
//native DzAPI_Map_GetActivityData takesnothing returns string
//native DzAPI_Map_GetMatchType takesnothing returns integer
usingbase::warcraft3::jass::jboolean_t;
usingbase::warcraft3::jass::jinteger_t;
usingbase::warcraft3::jass::jnothing_t;
usingbase::warcraft3::jass::jstring_t;
usingbase::warcraft3::jass::jhandle_t;
usingbase::warcraft3::jass::table_hook;
usingbase::warcraft3::jass::async_hook;
usingbase::warcraft3::jass::table_unhook;
usingbase::warcraft3::jass::async_unhook;
typedefbase::warcraft3::hashtable::native_func_nodeDzApiFunc;
namespaceDzApi
{
uint32_tget_adress(std::stringdz_api_name)
{
DzApiFunc*dz_func= base::warcraft3::get_native_function_hashtable()->get(dz_api_name.c_str());
if(dz_func!=nullptr)
{
OutputDebugStringEx(L"DEBUG_INFO | %s%X",L"DzApi地址: ",dz_func->func_address_);
returndz_func->func_address_;
}
OutputDebugStringEx(L"DEBUG_INFO | %s",L"未能获取DzApi地址!");
return0;
}
voidhook(std::stringdz_api_name,uintptr_t*old_proc_ptr,uintptr_tnew_proc)
{
usingnamespacebase::warcraft3;
jass::hook(dz_api_name.c_str(),old_proc_ptr,new_proc, jass::HOOK_MEMORY_TABLE|jass::HOOK_ONCE_MEMORY_REGISTER);
//table_hook(dz_api_name.c_str(), old_proc_ptr,new_proc);
}
voidunhook(std::stringdz_api_name,uintptr_t*old_proc_ptr,uintptr_tnew_proc)
{
usingnamespacebase::warcraft3;
jass::unhook(dz_api_name.c_str(),old_proc_ptr,new_proc, jass::HOOK_MEMORY_TABLE|jass::HOOK_ONCE_MEMORY_REGISTER);
//table_unhook(dz_api_name.c_str(), old_proc_ptr,new_proc);
}
}主逻辑实现:
namespacereal
{
uintptr_tDzAPI_Map_SaveServerValue = 0;
uintptr_tDzAPI_Map_GetGameStartTime = 0;
uintptr_tDzAPI_Map_Ladder_SetStat = 0;
}
namespacefake
{
jboolean_t__cdeclDzAPI_Map_SaveServerValue(uint32_twhichPlayer,uint32_tkey,uint32_tvalue)
{
returnbase::c_call<jboolean_t>(real::DzAPI_Map_SaveServerValue,whichPlayer,key,value);
}
jinteger_t__cdeclDzAPI_Map_GetGameStartTime()
{
jinteger_tret_val= base::c_call<jinteger_t>(real::DzAPI_Map_GetGameStartTime);
OutputDebugStringEx(L"DEBUG_INFO | %s%d",L"游戏启动时间: ",ret_val);
returnret_val;
}
//call DzAPI_Map_Ladder_SetStat(XL[1], ("MVP"),"1") // MVP
//call DzAPI_Map_Ladder_SetStat(XL[2],("KHUO"), "1") // ��
//call DzAPI_Map_Ladder_SetStat(XL[3],("BNUE"), "1") // ��(��)
//call DzAPI_Map_Ladder_SetStat(XL[4],("DAOJ"), "1") // ɱ
//call DzAPI_Map_Ladder_SetStat(XL[5],("CHAI"), "1") // ľ
jnothing_t__cdeclDzAPI_Map_Ladder_SetStat(uint32_twhichPlayer,uint32_tkey,uint32_tvalue)
{
usingnamespacebase::warcraft3;
uint32_tme= jass::call("GetLocalPlayer");
std::strings_key=jass::from_string(key);
uint32_ti_value=jass::to_string(jass::from_string(value));
if(s_key=="GameResult")
{
OutputDebugStringEx("DEBUG_INFO | %s%s","天梯索引: ",s_key.c_str());
i_value= jass::to_string("1");
base::c_call<jnothing_t>(real::DzAPI_Map_Ladder_SetStat,me,key,i_value);
base::c_call<jnothing_t>(real::DzAPI_Map_Ladder_SetStat,me,key,i_value);
base::c_call<jnothing_t>(real::DzAPI_Map_Ladder_SetStat,me,key,i_value);
base::c_call<jnothing_t>(real::DzAPI_Map_Ladder_SetStat,me,key,i_value);
base::c_call<jnothing_t>(real::DzAPI_Map_Ladder_SetStat,me,key,i_value);
base::c_call<jnothing_t>(real::DzAPI_Map_Ladder_SetStat,me,key,i_value);
base::c_call<jnothing_t>(real::DzAPI_Map_Ladder_SetStat,me,key,i_value);
base::c_call<jnothing_t>(real::DzAPI_Map_Ladder_SetStat,me,key,i_value);
returnbase::c_call<jnothing_t>(real::DzAPI_Map_Ladder_SetStat,me,key,i_value);
}
returnbase::c_call<jnothing_t>(real::DzAPI_Map_Ladder_SetStat,whichPlayer,key,i_value);
}
}
通过函数名称来call jass 函数:
uintptr_tcall(constchar*name, ...)
{
func_valueconst*nf=japi_func(name);
if(!nf) {
nf=jass_func(name);
if(!nf) {
return0;
}
}
returnnf->call((constuintptr_t*)((va_list)_ADDRESSOF(name) +_INTSIZEOF(name)));
}
uintptr_tcall(uintptr_tfunc_address,constuintptr_t*param_list,size_tparam_list_size)
{
uintptr_tretval;
uintptr_tesp_ptr;
size_tparam_size=param_list_size*sizeofuintptr_t;
_asm
{
subesp,param_size;
movesp_ptr,esp;
}
memcpy((void*)esp_ptr,param_list,param_size);
_asm
{
call[func_address];
movesp,esp_ptr;
addesp,param_size;
movretval,eax;
}
returnretval;
}
总结一下,修改存储的玩家,全部为自己即本地玩家,修改游戏结果为字符串“1”。
在提交漏洞和沟通以前我并不清楚,一个“理应”有投票机制的积分系统怎么会出现单人操控游戏结果的漏洞,直到我从他们主管那里听到,说他们开发在写服务端的时候把我一个人的结果算成了100个人的结果,也就是说如果没人篡改,那么ok,有人篡改的话,篡改的玩家结果x100,假设有10个人进行游戏,9个人的结果是正确的,1个人结果是篡改过的,但是那一个人被当成了100个人,所以100>9,由此得出篡改数据的玩家的数据为最终结果,真NMSB。。。。