[原创]一个魔兽争霸官方平台天梯机制的漏洞分析

黑洛 2017-9-25 03:45 1864

重要的事情先说三遍:写服务端的人真菜!写服务端的人真菜!写服务端的人真菜!算是炒个冷饭。

  在本文发布的时候,漏洞已经被提交并被网易修复,想做坏事的还是算了。

  先提一下网易为了这个天梯系统写的“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。。。。



最新回复 (9)
uvbs 2017-9-25 11:46
2
楼主分析得不错,  就是排版有点问题。还有毕竟是别人的劳动成果,  请不要随便喷
黑洛 2017-9-25 13:08
3
uvbs 楼主分析得不错, 就是排版有点问题。还有毕竟是别人的劳动成果, 请不要随便喷
我喷是有原因的,半年前就发现了,直接说没用,连他们主管都在骂人
1
NightGuard 2017-9-25 15:06
4
网易的开发水平有这么差么。。
flze 2017-9-27 17:17
5
mark
flze 2017-9-27 17:17
6
mark
wx_그래서기념如此、 2017-9-30 01:30
7
外包公司  参差不齐
聖blue 2017-10-6 20:27
8
不错!!!!
白菜大哥 2017-10-7 12:19
9
估计是找的外包。然后接手的人匆忙
chixiaojie 2017-10-7 17:31
10
懵逼了,表示看不懂。
返回