Upgrade to Pro — share decks privately, control downloads, hide ads and more …

那些年我看過的反外掛爛梗

Avatar for adr adr
March 31, 2018

 那些年我看過的反外掛爛梗

線上遊戲是許多人的童年,許多人在線上遊戲看到外掛只覺得他們破壞遊戲平衡,讓遊戲變得不公平、不再有趣,
而有些人則投入外掛的懷抱,成為外掛使用者一員。今天將以台灣最知名紅遍大街小巷的線上遊戲作為舉例,
以外掛開發者的立場做分析線上遊戲,分享一些常見的分析遊戲弱點技巧、遊戲架構設計帶來的隱憂、
與這樣的分析技巧曾經成功挖掘出哪些漏洞,解析以往這些看似黑科技的外掛技術如何被挖掘出來的。

議程末將會簡單的介紹一些反外掛技巧,對於記憶體層面的攻防如何讓駭客難以分析與破解的開發者思路。

Avatar for adr

adr

March 31, 2018
Tweet

More Decks by adr

Other Decks in Technology

Transcript

  1. [email protected] ./Bio✨ • ⾺馬聖豪, aaaddress1 aka adr • Chroot, TDOH

    • 精通 C/C++、Windows 特性、逆向⼯工程 • Speaker: BlackHat Asia Arsenal 2018 HITCON CMT 2015 HITCON CMT 2016 Lightning HITCON CMT 2017 SITCON 2016 SITCON 2017 iThome#Chatbot 2017 BSidesLV 2016 ICNC'17 資訊安全基礎技術⼯工作坊 資安實務攻防研習營
  2. [email protected] 記憶體攻防戰 • 線上遊戲怎麼運作的 • 駭客如何分析線上遊戲 1. 動態分析: OllyDBG, Cheat

    Engine 2. 靜態分析: IDA Pro • 常⾒見見的遊戲保護⽅方式 3. 產品保護?套殼就對啦! 4. 套殼不夠?我上混淆跟虛擬機! 5. 虛擬機不夠難?我⾃自幹整個 Ring0 防護 • 通常如何解決那些保護 • 傳說的⿊黑魔法怎麼被做出來來的?
  3. [email protected] boringGame.c int nowHp = 100; void getHurt(int HpCost) {

    nowHp -= HpCost; } int main(void) { char buf[0x30]; while (1) { printf( "[1] hp -= 10\n[2] exit()\nopcode: "); gets(buf); if (buf[0] == '1') getHurt(10); else if (buf[0] == '2') break; else puts(":/ unknown opcode"); printf("your hp: %d\n\n", nowHp); if (nowHp < 1) { puts("you die!"); break; } } return 0; }
  4. [email protected] 客⼾戶端為主遊戲架構 int nowHp = 100; int main(void) { char

    buf[0x30]; while (1) { printf("[1] hp -= 10\n[2] exit()\nopcode: "); gets(buf); if (buf[0] == '1') sendHurtCost(10); else if (buf[0] == '2') break; else puts(":/ unknown opcode"); displyPalyerData(recvAllPlayerData()); if (nowHp < 1) { puts("you die!"); break; } } return 0; }
  5. [email protected] 伺服端為主遊戲架構 int nowHp = 100; int main(void) { char

    buf[0x30]; while (1) { printf("[1] hp -= 10\n[2] exit()\nopcode: "); gets(buf); if (buf[0] == '1') iNeedToGetHurt(); else if (buf[0] == '2') break; else puts(":/ unknown opcode"); displyPalyerData(recvAllPlayerData()); nowHp = recvMyHpData(); if (nowHp < 1) { puts("you die!"); break; } } return 0; }
  6. [email protected] 架構⾯面來來說(伺服為主) 客⼾戶端 伺服端 現在有誰在線上啊? 同張地圖 玩家資訊 ㄟ, 我現在狀狀況怎麼樣? 你剛剛被BOSS海海K三下

    經由你裝備防護後 你扣了了 1337 滴⾎血 現在⾎血量量 50 滴 ㄟ我要打那隻 id 為 1 的怪物 ㄜ...你只造成ㄌ 1 點傷害
  7. [email protected] 架構⾯面來來說(伺服為主) 客⼾戶端 伺服端 現在有誰在線上啊? 同張地圖 玩家資訊 ㄟ, 我現在狀狀況怎麼樣? 你剛剛被BOSS海海K三下

    經由你裝備防護後 你扣了了 1337 滴⾎血 現在⾎血量量 50 滴 ㄟ我要打那隻 id 為 1 的怪物 ㄜ...你只造成ㄌ 1 點傷害
  8. [email protected] boringGame.c int nowHp = 100; void getHurt(int HpCost) {

    nowHp -= HpCost; } int main(void) { char buf[0x30]; while (1) { printf( "[1] hp -= 10\n[2] exit()\nopcode: "); gets(buf); if (buf[0] == '1') getHurt(10); else if (buf[0] == '2') break; else puts(":/ unknown opcode"); printf("your hp: %d\n\n", nowHp); if (nowHp < 1) { puts("you die!"); break; } } return 0; }
  9. [email protected] 線上遊戲外掛策略略 1. 盡可能分析封包怎麼發送的 • connect()、send()、recv() 2. 不會分析封包?⼭山不轉路路轉,分析程式邏輯 3. 若若有資料是在客⼾戶端算完才送出,嘗試篡改它

    • 怪物出⽣生座標、攻擊距離、損⾎血量量、是否要損⾎血、要換去哪個頻道 • 吸怪、全圖打、MISS式無敵、完全無敵、⾃自動換頻、⾃自動過圖、全圖吸寶 4. 無法竄改?我模擬單⼀一個攻擊封包狂丟 • 吃我攻擊無延遲啦
  10. [email protected] Process Game Process Game.exe (PE) Reserved 0x00 ~ 0x3fffff

    0x400000+ ntdll.dll KUSER_SHARED_DATA 0x7ffe000+ user32.dll kernel32.dll ... ...
  11. [email protected] 殼 Game Process 保留留空間 Reserved 0x00 ~ 0x3fffff 0x400000+

    KUSER_SHARED_DATA 0x7ffe000+ ... 殼的主程式 加密並壓縮的內容
  12. [email protected] 殼 Game Process 保留留空間 Reserved 0x00 ~ 0x3fffff 0x400000+

    KUSER_SHARED_DATA 0x7ffe000+ ... 殼的主程式 加密並壓縮的內容 原始程式內容 (Game.exe) 取出 解密內容 殼的主程式
  13. [email protected] 殼 Game Process 保留留空間 Reserved 0x00 ~ 0x3fffff 0x400000+

    KUSER_SHARED_DATA 0x7ffe000+ ... 殼的主程式 加密並壓縮的內容 原始程式內容 (Game.exe) 覆寫回執⾏行行程式預期加載的位址 並模擬實作 loader 需做的事情 殼的主程式 Game.exe (PE)
  14. [email protected] 讀寫別⼈人的 Process boringGame.exe Process Id = 1 0xdead: 01

    23 00 00 gameHacker.exe Process Id = 2 我想寫入 Process id = 1 的 Process 記憶體 0xdead 處連續 4 個 byte 的內容 可以ㄇ WriteProcessMemory
  15. [email protected] boringGame.exe Process Id = 1 0xdead: 01 23 00

    00 我想寫入 Process id = 1 的 Process 記憶體 0xdead 處連續 4 個 byte 的內容 可以ㄇ WriteProcessMemory 不可以ㄛ 跨 Process 讀/寫/創建 Thread 要先申請權限ㄏㄏ gameHacker.exe Process Id = 2 讀寫別⼈人的 Process
  16. [email protected] 讀寫別⼈人的 Process boringGame.exe Process Id = 1 0xdead: 01

    23 00 00 OpenProcess() gameHacker.exe Process Id = 2 Token WriteProcessMemory(Token, ...)
  17. [email protected] 讀寫別⼈人的 Process boringGame.exe Process Id = 1 0xdead: 01

    23 00 00 OpenProcess() gameHacker.exe Process Id = 2 Token ReadProcessMemory(Token, ...) 0xdead = 01 23 00 00
  18. [email protected] 讀寫別⼈人的 Process boringGame.exe Process Id = 1 0xdead: 01

    23 00 00 OpenProcess() gameHacker.exe Process Id = 2 Token ReadProcessMemory(Token, ...) 0xdead = 01 23 00 00
  19. [email protected] 實際上運作 boringGame.exe Process Id = 1 ntdll!ZwOpenProcess: 我可不可以申請 Process

    Id = 1 Process 的存取寫入權限R? gameHacker.exe (Ring3) 好R,你要 Token 就給你R,有何不可ㄋ Windows Kernel (Ring0)
  20. [email protected] 實際上運作 boringGame.exe Process Id = 1 ntdll!ZwOpenProcess: 我可不可以申請 Process

    Id = 1 Process 的存取寫入權限R? gameHacker.exe (Ring3) ㄜ... 有個驅動程式控制ㄌ我, 它跟我說不能給你 Token, Sorry :( Windows Kernel (Ring0)
  21. [email protected] 偽造 ImagePath boringGame.exe Process Id = 1 ntdll!ZwOpenProcess: 我可不可以申請

    Process Id = 1 Process 的存取寫入權限R? ㄜ...你是⼯工作管理理員喔, 好ㄅ,可能遊戲當掉了了玩家想強制關閉遊戲, Token 給你吧 Windows Kernel (Ring0)
  22. [email protected] 偽造 ImagePath _asm{ mov eax,fs:[0x30] //eax points to PEB

    mov eax,[eax+0x010] //eax points to _PEB->_RTL_USER_PROCESS_PARAMETERS add eax,0x38 //eax points to ImagePathName(UNICODE_STRING) add eax,0x4 //UNICODE_STRING.Buffer mov ebx,wszImagePathName mov [eax],ebx mov eax,[eax] }
  23. [email protected] 寫⼀一⽀支 DLL 吧 :) BOOL APIENTRY DllMain( HMODULE hModule,

    DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: MessageBoxA(NULL, "hi there!", "info", NULL); break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
  24. [email protected] DLL Entry Game Process Game.exe (PE) 0x400000+ ntdll.dll KUSER_SHARED_DATA

    0x7ffe000+ user32.dll kernel32.dll ... ... “An optional entry point into a dynamic-link library (DLL). When the system starts or terminates a process or thread, it calls the entry-point function for each loaded DLL using the first thread of the process. ” -- MSDN (DllMain entry point) .text: DllEntry .text: DllEntry .text: DllEntry
  25. [email protected] 老外寫了了 CE Script MSCRCBypass: push eax lea eax, [ecx]

    cmp eax, 00401000 jb Normal cmp eax, 00BFE000 ja Normal push ebx mov ebx, FakeDump sub eax, 00401000 add eax, ebx movzx ecx, byte ptr [eax] pop ebx pop eax jmp Normal+04 Normal: pop eax movzx ecx, byte ptr [ecx] mov edx, [ebp+14] jmp 00A11487 00A11481: jmp MSCRCBypass nop CreateThread(MSmemcpy) MSmemcpy: mov edi, FakeDump mov esi, 00401000 mov ecx, 001FF400 repe movsd ret ccplz.net/threads/ems-v75-bypass-information.23009/
  26. [email protected] messagebox.c en.wikipedia.org/wiki/X86_calling_conventions #include <Windows.h> const char *lptext = "hi

    there!"; const char *lptitl = "info"; int main() { MessageBoxA(0, lptext, lptitl, 0); return 0; }
  27. [email protected] messagebox.c en.wikipedia.org/wiki/X86_calling_conventions #include <Windows.h> const char *lptext = "hi

    there!"; const char *lptitl = "info"; int main() { MessageBoxA(0, lptext, lptitl, 0); return 0; }
  28. [email protected] messagebox.exe en.wikipedia.org/wiki/X86_calling_conventions 00F11000 <con | push 0 00F11002 |

    push "info" 00F11007 | push "hi there!" 00F1100C | push 0 00F1100E | call dword ptr ds:[<&MessageBoxA>] 00F11014 | xor eax,eax 00F11016 | ret
  29. [email protected] Calling Convention int callee(int, int, int); int caller(void) {

    return callee(1, 2, 3) + 5; } caller: push ebp mov ebp, esp push 3 push 2 push 1 call callee add eax, 5 add esp, 12 mov esp, ebp pop ebp ret en.wikipedia.org/wiki/X86_calling_conventions
  30. [email protected] 殼本⾝身的虛擬機? int callee(int, int, int); int caller(void) { return

    callee(1, 2, 3) + 5; } callee: push xxxx jmp PackerVM en.wikipedia.org/wiki/X86_calling_conventions caller: push ebp mov ebp, esp push 3 push 2 push 1 call callee ...
  31. [email protected] 殼本⾝身的虛擬機? int callee(int, int, int); int caller(void) { return

    callee(1, 2, 3) + 5; } callee: push xxxx jmp PackerVM en.wikipedia.org/wiki/X86_calling_conventions caller: push ebp mov ebp, esp push 3 push 2 push 1 >> call callee ...
  32. [email protected] 還記得嗎 客⼾戶端 伺服端 現在有誰在線上啊? 同張地圖 玩家資訊 欸欸, 經過我計算結果 本次要損⾎血

    10 滴 好ㄛ 我知道ㄌ 我要攻擊 那隻 id 為 1 的怪物 好ㄛ攻擊成功! 你剛剛打他造成 10 點傷害
  33. [email protected] 怪物出⽣生 void bornMob(mob inMob) { // ... sendMob(inMob.type, inMob.x,

    inMob.y, ...); // ... } void initMap() { /* ... 做了了很多進地圖的封包處理理 ... */ wallData curr; recvWallData(&curr); mob mobArr[0xff]; for (int i = 0; i < 255; i++) { if (cmpMobInWall(mobArr[i], curr)) bornMob(mobArr[i]); else dieMob(mobArr[i]); } } struct wallData { int leftWall; int rightWall; int TopWall; int Bottom; } *年年代久遠、資料不可考,按照印象⼤大概是這樣啦。 github.com/aaaddress1/CrackShield-MapleStory-Hack/blob/master/Script/GiFon_.h
  34. [email protected] 全圖吸怪 void bornMob(mob inMob) { // ... sendMob(inMob.type, inMob.x,

    inMob.y, ...); // ... } void initMap() { /* ... 做了了很多進地圖的封包處理理 ... */ wallData curr; recvWallData(&curr); mob mobArr[0xff]; for (int i = 0; i < 255; i++) { if (cmpMobInWall(mobArr[i], curr)) bornMob(mobArr[i]); else dieMob(mobArr[i]); } } struct wallData { int leftWall; int rightWall; int TopWall; int Bottom; } *年年代久遠、資料不可考,按照印象⼤大概是這樣啦。 github.com/aaaddress1/CrackShield-MapleStory-Hack/blob/master/Script/GiFon_.h
  35. [email protected] 技能攻擊判斷 typedef struct _RECT { LONG left; LONG top;

    LONG right; LONG bottom; } RECT, *PRECT; typedef struct tagPOINT { LONG x; LONG y; } POINT, *PPOINT; *年年代久遠、資料不可考,按照印象⼤大概是這樣啦。 github.com/aaaddress1/CrackShield-MapleStory-Hack/blob/master/Script/XY_Fix.h void CALLBACK attack() { // ... wallData curr; recvWallData(&curr); _RECT skillRange; mob arr[0xff]; for (int i = 0; i < 255; i++) { // 確認技能施放 ... tagPOINT p = tagPOINT(arr[i].x, arr[i].y); if (PtinRect(&skillRange, &p)) sendAttackMob(arr[i]); // ... } }
  36. [email protected] 全圖攻擊 typedef struct _RECT { LONG left; LONG top;

    LONG right; LONG bottom; } RECT, *PRECT; typedef struct tagPOINT { LONG x; LONG y; } POINT, *PPOINT; *年年代久遠、資料不可考,按照印象⼤大概是這樣啦。 github.com/aaaddress1/CrackShield-MapleStory-Hack/blob/master/Script/XY_Fix.h void CALLBACK attack() { // ... wallData curr; recvWallData(&curr); _RECT skillRange; mob arr[0xff]; for (int i = 0; i < 255; i++) { // 確認技能施放 ... tagPOINT p = tagPOINT(skillRange.left, skillRange.top); if (PtinRect(&skillRange, &p)) sendAttackMob(arr[i]); // ... } }
  37. [email protected] 技能攻擊判斷 *反外掛 *年年代久遠、資料不可考,按照印象⼤大概是這樣啦。 github.com/aaaddress1/CrackShield-MapleStory-Hack/blob/master/Script/XY_Fix.h void CALLBACK attack() { //

    ... wallData curr; recvWallData(&curr); _RECT skillRange; mob arr[0xff]; for (int i = 0; i < 255; i++) { // 確認技能施放 ... tagPOINT p = tagPOINT(arr[i].x, arr[i].y); if (PtinRect(&skillRange, &p)) sendAttackMob(arr[i]); // ... while (!chkMob(arr[i], p.x, p.y)); } } bool chkMob(mob mob, LONG x, LONG y){ if (chkShownMob(mob, x, y)) return 1; else killGame(); }
  38. [email protected] 技能攻擊判斷 *反反外掛 *年年代久遠、資料不可考,按照印象⼤大概是這樣啦。 github.com/aaaddress1/CrackShield-MapleStory-Hack/blob/master/Script/XY_Fix.h void CALLBACK attack() { //

    ... wallData curr; recvWallData(&curr); _RECT skillRange; mob arr[0xff]; for (int i = 0; i < 255; i++) { // 確認技能施放 ... tagPOINT p = tagPOINT(arr[i].x, arr[i].y); if (PtinRect(&skillRange, &p)) sendAttackMob(arr[i]); // ... while (!chkMob(arr[i], p.x, p.y)); } } bool chkMob(mob mob, LONG x, LONG y){ chkShownMob(mob, x, y); return 1; }
  39. [email protected] void initMap() { /* ... 做了了很多進地圖的封包處理理 ... */ object

    arr[0xff]; for (int i = 0; i < 255; i++) { if (arr[i].type != TYPE_PLAYER) continue; displyPlayer(arr[i], arr[i].name); } for (int i = 0; i < 255; i++) { if (arr[i].type != TYPE_CHEATMOB) continue; setCheatMobLocation(arr[i]); } wallData curr; recvWallData(&curr); for (int i = 0; i < 255; i++) { if (arr[i].type != TYPE_MOB) continue; if (cmpMobInWall(arr[i], curr)) bornMob(arr[i]); else dieMob(arr[i]); } } 地圖載入階段 *年年代久遠、資料不可考,按照印象⼤大概是這樣啦。
  40. [email protected] void initMap() { /* ... 做了了很多進地圖的封包處理理 ... */ object

    arr[0xff]; for (int i = 0; i < 255; i++) { if (arr[i].type != TYPE_PLAYER) continue; displyPlayer(arr[i], arr[i].name); } for (int i = 0; i < 255; i++) { if (arr[i].type != TYPE_CHEATMOB) continue; setCheatMobLocation(arr[i]); } wallData curr; recvWallData(&curr); for (int i = 0; i < 255; i++) { if (arr[i].type != TYPE_MOB) continue; if (cmpMobInWall(arr[i], curr)) bornMob(arr[i]); else dieMob(arr[i]); } } 遇 GM 下線 *年年代久遠、資料不可考,按照印象⼤大概是這樣啦。