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

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

adr
March 31, 2018

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

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

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

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 下線 *年年代久遠、資料不可考,按照印象⼤大概是這樣啦。