โกงเกมใน Android ด้วยการสร้าง mod และถอดรหัสใน SharedPreferences ภาค Advance

Comdet Phaudphut
6 min readOct 14, 2022

--

บันทึกนี้สร้างไว้เพื่อกันไม่ให้ในอนาคตลืมวิธีที่ตัวเองเคยทำมาแล้ว 555 เอาหล่ะเริ่ม

ก่อนอื่นเกริ่นกันก่อนว่าผมเคยทำวิธีการแก้ไข SharedPreferences (ใน meduim ผมนี่แหละ) ซึ่งเป็นไฟล์เก็บข้อมูลของ Application ที่จะบันทึกค่าต่าง ๆ ตอนเราปิด app ไปเพื่อให้ค่าเหล่านั้นคงอยู่ เวลาเปิด app ใหม่ก็จะไปอ่านไฟล์นั้นเพื่อคืนค่าต่าง ๆ และเกมบางเกมที่ไม่ได้บันทึกค่าไว้ที่ cloud ก็มักจะใช้วิธีการเก็บข้อมูลพวก เงิน เพชร ไว้ที่ไฟล์นี้ หรืออาจจะใช้เป็นที่พักข้อมูลชั่วคราวก่อนที่จะอัพข้อมูล sync ขึ้น cloud เวลาเน็ตหลุดระหว่างเล่นก็ได้เช่นกัน ดังนั้นการแก้ไขไฟล์นี้ได้ก็เสมือนเป็นการเข้าถึงขุมทรัพย์ที่เกมหวงแหนไว้เลยก็ว่าได้

แต่การที่จะเข้าไปแก้ไขไฟล์นี้ได้นั้น แต่ก่อน ค่อนข้างจะลำบากเนื่องจากเป็นไฟล์ของ App ที่มันหวงไว้ ดังนั้นต้องทำการ Root เครื่องและเข้าโปรแกรมพวก Root File Editor แก้ไขเอา แต่การ Root เครื่องนั้นส่งผลเสียมากกว่าผลดี เนื่องจากเป็นเหมือนการยกสิทธิ์ของเครื่องตัวเองให้ app ที่ต้องการ root access ไปทั้งหมด มันจะปู้ยี่ปู้ยำเครื่องเรายังไงก็ได้ โปรแกรมพวก malware อะไรต่าง ๆ ที่จะคอยโจมตีเครื่องก็จะยิ้มหวาน ดังนั้นตัวเกมหลาย ๆ เกมก็จะมีระบบป้องกันตรวจจับการ Root/CheatEngine ไว้ ถ้าตรวจพบก็จะบังคับปิดอัตโนมัติ

เดชะบุญ ต้องกราบ Google ที่ยังมีจิตใจที่ดีต่อ hacker ตัวน้อย ๆ ผู้ที่ไม่อยากให้ผู้ใช้ต้อง root เครื่องลง app ระยำตำบอนกับเครื่องตัวเอง ได้ทิ้งช่องโหว่อันนึงเอาไว้ให้คือการอนุญาตให้เครื่องสามารถสำรองข้อมูล app เอาไว้ได้ (ปกติ feature นี้น่าจะเอาไว้ใช้กับ cloud) โดยใช้คำสั่ง adb backup และด้วยการ backup นี้มันเสือกใส่ไฟล์ SharedPreferences (ขอย่อว่า SF) อันที่ app หรือเกม มันหวงไว้หนักหนามาให้ด้วย 555 และที่สำคัญคำสั่งนี้รันได้แบบไม่ต้อง Root เครื่องเลย กราบบบบบ ทำให้เราสามารถแก้ไขไฟล์และ restore กลับไปให้ app แล้วพอเปิดมา app ก็จะงง ๆ ว่าอ่าวก่อนบันทึก ทองในเกมมันยังเป็น 50 ไหงเปิดมามันเป็น 999999 ได้ยังไง

เกมนี้ dev ไม้ได้เข้ารหัสไว้ โดนวางยาโดยละม่อม

เอาหล่ะบทความนี้ไม่ได้พามาทำการ backup ใครอยากรู้วิธีผมเขียนไว้แล้วในบทความก่อน ๆ นะครับ แต่ที่เราจะมาทำในวันนี้เป็นอะไรที่ Advance กว่านั้น คือบางเกมมันรู้ดีว่ามีคนอย่างพวกเอ็งที่อ่านบทความนี่แหละจ้องจะแก้ไฟล์นี้อยู่ ในเมื่อป้องกันไม่ได้ก็เข้ารหัสมันซะเลย ซึ่งการเข้ารหัสนั้นก็แล้วแต่จะออกแบบกันใน app ทำให้ถึงแม้เราจะดึงไฟล์ SF ออกมาได้แต่ก็ไม่รู้ว่าข้างในมันคือข้อมูลอะไร แต่เหนือฟ้ายังมีฟ้า เหนือ Java ยังมี Unity เหนือ Unity ยังมี C เหนือ C ยังมี Assembly เหนือ Assembly ยังมี Ghidra เดี๋ยวผมจะพามาแกะโค้ดที่มันถูกเข้ารหัสนี้กัน แล้วเกมผู้โชคดีเกมนั้นก็คือ “出家模擬器™” หรือในชื่อภาษาไทยก็คือ “แบบจำลองบวช” (/com.lgggame.idletemple) นั่นเอง (บาปไหมว่ะเนี่ย)

หลังจากที่ทำการ Backup เกมเราก็จะพบว่าไฟล์ SF ในส่วนที่เป็นข้อมูลที่สำคัญนั้นถูกเข้ารหัสไว ้แถมมี key ที่เป็นตัว indicate ไว้ด้วยว่า encrypted เป็น 1 ให้ช้ำใจเล่น

ซึ่งถ้าใครคุ้นเคยก็จะรู้ว่าถ้ามาเป็น base64 แบบนี้ก็น่าจะเป็นการเข้ารหัสตระกูล AES DES ประมาณนี้ดังนั้นการก้าวข้ามกำแพงนี้ไปได้ก็น่าจะมี 2 แนวทางก็คือ

  1. หา Algorithm การเข้ารหัส และหากุญแจการถอดรหัส
  2. Bypass รหัสนี้ให้ตัวเกมบันทึกแบบที่ไม่เข้ารหัสมา

แน่นอนว่าทั้งสองวิธีนี้ลำบากแน่ แต่ก็ต้องขอกราบอาจารย์ผู้ประสิทธิ์ประสาทวิชาอาคมและไม่เคยหวงแหนในวิชา ทำ Tools และออกมาแจกจ่ายให้เราใช้และยังมีเมตตา ให้เห็นถึงโค้ดที่ไม่ได้อันตรายแอบแฝงมากับโปรแกรม ด้วยการเผยแพร่โค้ดใน GitHub กราบบบบบ ตัวเกมนี้เขียนด้วย Unity (ตอนนี้เกือบจะทุกเกมไปละ) ซึ่งการที่เราจะเข้าไปดูการเข้ารหัสนี้ได้นั้นเราจำเป็นจำต้องใช้เครื่องมีดังนี้

  1. APKTools (ผมขอใช้แบบมี GUI) ตัวนี้เอาไว้ใช้ในการแตกไฟล์ apk และรวมถึงการ mod ไฟล์ที่แก้ไขแล้วมา sign ใหม่เพื่อให้ลงใน android ได้ โหลดได้ที่ : Releases · AndnixSH/APKToolGUI (github.com) folk เจ้านี้เขาอัพเดทตลอด
  2. Il2CppDumper เนื่องการโค้ดจาก Unity3d นั้นเป็น C# มันจะถูก Il2Cpp (Intermediate Language To C++) เนี่ยแปลงโค้ดเป็น lowlevel คือภาษา C อีกรอบแล้วจะได้ไฟล์ libil2cpp.so ออกมา แล้วไฟล์นี้แหละที่จะเก็บโค้ดที่ผู้พัฒนาเขียนเอาไว้ แน่นอนมันมันจะต้องถูก warp อีกรอบนึง โดยเครื่องมือแกะโหลดได้จาก Perfare/Il2CppDumper: Unity il2cpp reverse engineer (github.com)
  3. Ghidra เป็น tools disassembly อีกตัวหนึ่ง ปกติแล้วผมใช้แต่ IDA แต่วันนี้มาเป็น concept opensource หมดเลยต้องใช้ Ghidra แต่ก็นั่นแหละกระบี่อยู่ที่ใจ โหลดได้ที่ NationalSecurityAgency/ghidra: Ghidra is a software reverse engineering (SRE) framework (github.com)

เมื่อได้ครบแล้วก็มาลุยกันเลย โดยเริ่มต้นจากเราต้องไปหา apk มาก่อนโดยผมเลือกโหลดจาก apkpure แล้วใช้ apktools แตกไฟล์ออกมา

ซึ่งผลลัพท์จะได้ folder ตรงกันกับชื่อ apk ออกมา แต่เราจะใช้แค่ 2 ไฟล์ คือ global-metadata.dat (อยู่ใน resources\assets\bin\Data\Managed\Metadata) แล้วก็ libil2cpp.so ซึ่งอยู่ใน /lib ตาม arch ของ CPU ที่ใช้ (ของผมเป็น arm64bit ก็จะเป็น /lib/arm64-v8a) ให้ทำการ copy ออกมาพักไว้สัก folder นึง

จากนั้นให้เปิด Il2CppDumper ขึ้นมาแล้วเลือกไฟล์ libil2cpp.so แล้วตามด้วย global-metadata.dat จากนั้นโปรแกรมจะ dump structure ออกมา

ซึ่งเมื่อเปิด folder Il2CppDumper เราก็จะพบไฟล์ที่ถูกสร้างขึ้นมาให้ทำการ copy ออกมาแยกไว้อีก folder

จากที่เห็นมันจะมีไฟล์ dump.cs เป็นโค้ด c# และมี struct และโครงสร้างต่าง ๆ ที่ถูกเขียนไว้ แต่ยังไม่มีโค้ดที่สมบูรณ์ (แน่หล่ะของจริงมันเป็น c++) อันนี้มันใช้ในการดู address ของพวก function และตัวโค้ดต่าง ๆ ได้ คือแค่นี้ก็น้ำตาจะไหลดีกว่ากูไปไล่แกะเอาเองอยู่แล้ว แต่ตัว dumper ตัวนี้มี plugin สำหรับ ghidra ด้วย ที่จะทำการ map พวก ชื่อ function string อะไรพวกนั้นกับ address จริง อะไรมันจะดีขนาดนั้น น้ำตาไหลแล้วนะ ขั้นตอนต่อไปคือเปิด Ghidra ขึ้นมาแล้วทำการ import ไฟล์ libil2cpp.so เข้าไป

แล้วก็กดตกลง ซึ่งมันก็ detect ให้เองว่าเป็น ELF aarch64

เมื่อ OK แล้วก็อย่าพึ่ง analysis ไป import script ก่อน

จากนั้นก็รัน ghidra.py

จากนั้นก็เลือกไฟล์ script.json

จากนั้นก็ทำการ auto anylysis (Analysis > Auto Analysis libil2cpp.so) ได้เลย จากนั้นรอสักครู่ (ครู่ใหญ่อยู่) ทีนี้ในระหว่างที่รอ ผมขอไปเปิดดู dump.cs เมื่อค้นไปเรื่อย ๆ โดยให้ลอง search ดูคำว่า gem, star, level, diamond, gold, coin ก็จะพบว่ามี class อยู่ class หนึ่งชื่อว่า UserData มีข้อมูลที่น่าจะเก็บตัวเลขพวกนี้ไว้ และไอ้ class นี้มันก็สามารถ serialize เป็น json ได้ ก็เลยอนุมานได้ว่าตัวเกมน่าจะ serialize class นี้เป็น json string แล้วก็เข้ารหัสสักอย่างไปเก็บไว้ที่ SF เดี๋ยวเราไปตามรอยกันดูต่อ

เมื่อเลื่อนลงไปแป๊บเดียวก็จะเห็น Class ที่มีชื่อว่า UserDataManager ซึ่งมี function ที่น่าสนใจอยู่ 2 ตัวคือ LoadData กับ SaveUserData ซึ่งตามความเข้าใจคือไอ้สอง function นี่แหละที่จะคอยเขียนและอ่าน SF และการเข้ารหัสก็น่าจะอยู่ใน function นี้

เมื่อได้เป้าหมายแล้วก็เอาชื่อ function ไปค้นใน Ghidra ได้เลย

ก่อนอื่นต้องกราบ tools ทั้ง 2 ตัวงาม ๆ ก่อน ที่แกะออกมาให้เห็นโค้ดชัดขนาดนี้ เมื่อลองสังเกตุดูรูปข้างบนแล้ว ในเลข 2 เราจะเห็นว่า

  • uVar1 = LIBII.LitJson.JsonMapper$$ToJson(uVar1,0);
    คำสั่งนี้คือการแปลง UserData เป็น JsonString แล้วเก็บไว้ที่ uVar1
  • uVar1 = LIBII.CryptUtils$$Encrypt(uVar1,_StringLiteral_23171,_StringLiteral_23172,1,0);
    คำสั่งนี้คือการเอา String ส่งไปยัง function Encrypt โดยมี parameter 5 ตัว และ _StringLiteral_23171,_StringLiteral_23172 น่าจะเป็นค่าคงที่ ให้เดาเลยตอนนี้คือ key การเข้ารหัส และอีกตัวน่าจะเป็น IV (ไปอ่านเรื่องการเข้ารหัสกันเองนะ) ซึ่งเมื่อไปหา String 2 ตัวก็น่าจะ Got Cha !!! เพราะมันคือคำว่า “tripledesencryptionkey12” แล้วก็ “12345678” นั่นเอง และความยาวมันก็เป็น 24 กับ 8 ตรงตามหลักการเข้ารหัส แล้วมันก็ใบ้มาให้แล้วด้วยว่าเป็น 3DES (triple DES)
  • UnityEngine.PlayerPrefs$$SetString(*(undefined8 *)(param_1 + 0x10),uVar1,0);
    คำสั่งนี้ก็ตรงตัวเลยคือการสั่งให้ Unity เขียนไฟล์ SF นั่นเองแสดงว่าทฤษฎีที่เล่ามาก็น่าจะถูกต้อง
  • UnityEngine.PlayerPrefs$$SetInt(_StringLiteral_23170,1,0);
    และคำสั่งนี้คือการเขียน SF ด้วยตัวเลข 1 โดย _StringLiteral_23170 มันคือคำว่า “encrypted” นั่นเอง ดังนั้นเราจึงเห็นใน SF key encrypted เป็น 1 ก็มาจากตรงนี้นั่นเอง

เอาหล่ะพอได้ key ก็ร้อนวิชามาลองเขียน python กันดูครับโดยเอา key แล้วก็ iv มา decode กัน

พอรันออกมาปรากฏว่า แง่วววววววว มันเข้ารหัสไว้อีกชั้นนึง

ทำไมถึงรู้ว่ามันเข้ารหัสไว้อีกชั้นนึง คำตอบก็คือมันมีอักขระที่เป็น range ในช่วงที่อ่านได้ เพราะถ้า key ผิดเนี่ยมันจะออกเป็นภาษาต่างดาวอะไรหมด พอไปค้นดูใน function การเข้ารหัสเราก็จะพบว่ามันมี assembly ที่ต้องถอดอีกรอบ ต้องไป xor บวกลบอะไรอีกพอสมควร

ถ้าจะแกะทำวิธีนี้ต่อบอกเลยว่า … ขี้เกียจแล้วก็ยากสำหรับผม แต่ถ้าใครที่เซียนมองเห็นแนวทางก็อาจจะพอทำต่อไปได้ … แต่มันยังมีวิธีที่ง่ายกว่านั้นอยู่คือการ bypass นั่นเอง หลักการก็ง่ายแสนง่าย แทนที่ function Encrypt จะ return ข้อความที่ถูกเข้ารหัส เราก็เปลี่ยน byte โค้ดตรงนั้นให้มัน return string parameter ต้นฉบับที่ยังไม่เข้ารหัสออกมาแทน เป็นไง อันนี้ง่ายกว่าต้องแกะโค้ด asm แน่นอน เดี๋ยวไปค้น function Encrypt กันดู

ซึ่งเมื่อวิเคราะห์ดูโค้ดนี้ก็จะพบว่า param_4 คือตัวแปรสำคัญที่จะบอกว่าเราจะเข้ารหัสยังไง ถ้าเป็น 1 คือเข้ารหัสแบบ 3DES ถ้าเป็น 2 คือ AES และถ้าเป็น 3 คือไม่เข้ารหัสเลย และถ้าไปดู UserDataManager เรียก function นี้ก็จะพบว่าตัว param_4 มันคือ 1 นั่นเอง

ดังนั้นเราก็แค่แก้ไข parameter ตัวนี้ให้เป็น 3 ซะ มันก็จะบันทึก SF แบบไม่เข้ารหัส จากนั้นเราก็ทำการ patch instruction ซะ

แก้เป็น 3 ซะก็จบ …. อ่อ เหลืออีกอย่าง ผมแก้ค่าที่มัน save SF ตรง encrypted ไว้ด้วย โดยจะแก้ให้เป็น 0 ด้วยเพราะข้อความนี้คือมันยังไม่เข้ารหัสนั่นเอง

NOTE: มันยังมีวิธีอื่นอีก จริง ๆ เราก็สามารถแก้ตรง class UserData ได้นะ เช่นให้มัน return ค่าเงินเป็น 9999999 หักดับแบบนี้เลย แต่อันนี้อยากเห็น SF มันว่าจะมาเป็น json หรือเปล่าเพราะจะได้แก้ไขได้เรื่อย ๆ ไม่ต้อง patch ใหม่

พอ patch แล้วก็กด O ทำการ export program ซะ โดยเลือก format เป็น ELF

จากนั้นก็เอาไปทับไฟล์ libil2cpp.so ใน APKTools ที่เราแตกไฟล์ไว้ได้เลย ไม่ backup เพราะมั่นใจ 5555 จากนั้นก็ทำการ compile ด้วย apktool กลับ

ลองเอา apk ที่ sign แล้วไปลงใน android เล่นดู ก็เล่นได้แฮะเกมไม่ crash โล่งใจเปราะนึง

ต่อมาให้ลอง backup แล้วเปิด SF ดูจะพบว่าาาาาาาาาาาาาาาาาาาาาาา

และเอาไป urldecode ดูก็จะพบว่าาาาาาาาาาาาาาาาาาา

BingGO!!!! นี่ไงหล่ะสิ่งที่ตามหามานาน จากนั้นก็แก้ไขค่าตามใจแล้ว urlencode กลับไป ซึ่งเมื่อลองดูตรงท้ายจะพบว่า encrypted เป็น 0 ตามที่เรา mod ไว้

เอาหล่ะ restore กับก็จะพบความสนุกที่รออยู่ในเกม 55555555555555

เขียนขึ้นด้วย CC-BY-NC อย่าลืมให้เครดิตบทความนี้ด้วยทุกครั้งนะครับ เข้ามาทักทายกันได้ที่ fb.com/comdet นะครับ ขอบคุณครับ

--

--