0x00:游戏外挂与反外挂
1.根据手游数据公司AppAnnie发布的《2019手游行业报告》显示,国产手游2018年海外收入超过60亿美元相比2016年增长了109%,其中美国市场收入相比2016年增长了140%,亚太地区收入相比2016年增长105%,欧洲、中东和非洲市场相比2016年增长90%。报告还预计,2019年全球移动应用市场规模将达到1200亿美元,移动游戏以惊人的速度发展的同时,然而很多游戏厂商却面临着破解、外挂、盗版等严重问题,难以享受回报红利。这也就催生了业界针对日益猖獗的打金者灰黑产*,研发出一些游戏安全防护方案。
2.什么是外挂:指一切用来破坏游戏程序正常游戏数据和逻辑的工具或破解版。比如可以修改游戏内存数据的修改器,修改网络数据包的抓包工具。总结起来就是破坏游戏客户端正常数据和代码逻辑,或伪造游戏客户端操作状况的工具或破解版。黑客可随意修改游戏赖以生存的核心功能,比如广告植入,内购破解等。急剧缩短游戏生命周期。图1所示主流外挂工具.
图1
当游戏外挂泛滥的时候,破坏游戏的平衡性,大部分玩家可能会直接因此而卸载游戏,所以就须要有反外挂产品进行对抗,解决游戏运营中外挂的痛点。
0x01:反外挂系统基本架构
1.该反外挂系统主要在U3D、反调试器、代码检验方面,反静态方面做了防护,主要技术用到混淆,内联、字符串加密、SMC等技术,大致框架如图2所示。
图2
0x02:反外挂技术细节分析
1.反外挂系统主要是通过对libmono.so加壳来启动反外挂功能,当游戏启动后加载libmono.so时会先执行initarray中的初始化函数,有三个函数,第三个函数是主要的。
2.反调试:主要通过svc 0指令来获取进程状态,找到所有svc指令,找到open函数,patch掉就过了,让它返回失败.,代码如下:
"/proc/self/status"
LOAD:003E11D4 NR_openat ; CODE XREF: sub_3D2630+1E↑p
LOAD:003E11D4 ; __unwind {
LOAD:003E11D4 07 C0 A0 E1 MOV R12, R7
LOAD:003E11D8 14 70 9F E5 LDR R7, =322
LOAD:003E11DC 00 00 00 EF SVC 0
LOAD:003E11E0 0C 70 A0 E1 MOV R7, R12
LOAD:003E11E4 01 0A 70 E3 CMN R0, #0x1000
LOAD:003E11E8 1E FF 2F 91 BXLS LR
LOAD:003E11EC 00 00 60 E2 RSB R0, R0, #0
LOAD:003E11F0 00 04 00 EA B sub_3E21F8
3.第一次解密代码,计算crc值、判断crc、再次解密其它函数。
解密函数指令(前32字节),解密后指令计算crc、比较crc,相同后再解密其它函数指令,代码如下
LOAD:762CB7B8
LOAD:762CB7B8 ; 指令加解密
LOAD:762CB7B8
LOAD:762CB7B8 DecCode
LOAD:762CB7B8 ; __unwind {
LOAD:762CB7B8 2D E9 F0 41 PUSH.W {R4-R8,LR}
LOAD:762CB7BC 01 EB 02 08 ADD.W R8, R1, R2
LOAD:762CB7C0 0D 46 MOV R5, R1
LOAD:762CB7C2 21 F4 7F 64 BIC.W R4, R1, #0xFF0
LOAD:762CB7C6 08 F6 FF 71 ADDW R1, R8, #0xFFF
LOAD:762CB7CA 83 68 LDR R3, [R0,#8]
LOAD:762CB7CC 21 F4 7F 61 BIC.W R1, R1, #0xFF0
LOAD:762CB7D0 24 F0 0F 04 BIC.W R4, R4, #0xF
LOAD:762CB7D4 21 F0 0F 01 BIC.W R1, R1, #0xF
LOAD:762CB7D8 06 46 MOV R6, R0
LOAD:762CB7DA 0F 1B SUBS R7, R1, R4
LOAD:762CB7DC 9B 68 LDR R3, [R3,#8]
LOAD:762CB7DE 20 46 MOV R0, R4
LOAD:762CB7E0 07 22 MOVS R2, #7
LOAD:762CB7E2 39 46 MOV R1, R7
LOAD:762CB7E4 98 47 BLX R3
LOAD:762CB7E6 2B 46 MOV R3, R5
LOAD:762CB7E8
LOAD:762CB7E8 loc_762CB7E8
LOAD:762CB7E8 43 45 CMP R3, R8 ; 解密指令
LOAD:762CB7EA 03 F1 01 01 ADD.W R1, R3, #1
LOAD:762CB7EE 06 D0 BEQ loc_762CB7FE
LOAD:762CB7F0 1A 78 LDRB R2, [R3] ; 加密后的指令
LOAD:762CB7F2 0B 46 MOV R3, R1
LOAD:762CB7F4 82 F0 58 02 EOR.W R2, R2, #0x58
LOAD:762CB7F8 01 F8 01 2C STRB.W R2, [R1,#-1]
LOAD:762CB7FC F4 E7 B loc_762CB7E8 ; 解密指令
LOAD:762CB7FE
LOAD:762CB7FE
LOAD:762CB7FE loc_762CB7FE
LOAD:762CB7FE B3 68 LDR R3, [R6,#8]
LOAD:762CB800 28 46 MOV R0, R5
LOAD:762CB802 00 22 MOVS R2, #0
LOAD:762CB804 D3 F8 D8 30 LDR.W R3, [R3,#0xD8]
LOAD:762CB808 98 47 BLX R3
LOAD:762CB80A B3 68 LDR R3, [R6,#8]
LOAD:762CB80C 20 46 MOV R0, R4
LOAD:762CB80E 39 46 MOV R1, R7
LOAD:762CB810 05 22 MOVS R2, #5
LOAD:762CB812 5B 68 LDR R3, [R3,#4]
LOAD:762CB814 98 47 BLX R3
LOAD:762CB816 BD E8 F0 81 POP.W {R4-R8,PC}
4.下面是解密后代码,计算并比较代码crc值,直接nop掉,因为该指令是被加密存放的,如果要文件pathc,须先将nop指令加密再进行patch。代码如下:
LOAD:762CB8F4 sub_762CB8F4
LOAD:762CB8F4
LOAD:762CB8F4 arg_140= 0x140
LOAD:762CB8F4 arg_160= 0x160
LOAD:762CB8F4
LOAD:762CB8F4 00 F0 9A FD BL sub_762CC42C
LOAD:762CB8F8 20 46 MOV R0, R4
LOAD:762CB8FA 29 46 MOV R1, R5
LOAD:762CB8FC 00 F0 EE FB BL crc22 ; 计算并比较代码crc值,直接nop掉
LOAD:762CB900 C0 46 NOP //如果crc值不一样就不走解密函数流程
LOAD:762CB900 ; End of function sub_762CB8F4
LOAD:762CB900
LOAD:762CB902 01 AE ADD R6, SP, #4
LOAD:762CB904 29 46 MOV R1, R5
LOAD:762CB906 01 22 MOVS R2, #1
LOAD:762CB908 30 46 MOV R0, R6
LOAD:762CB90A 00 F0 9D F8 BL DecFunc
LOAD:762CB90E 30 46 MOV R0, R6
LOAD:762CB910 00 F0 5C FA BL sub_762CBDCC
LOAD:762CB914 20 46 MOV R0, R4
LOAD:762CB916 00 F0 AB FD BL sub_762CC470
LOAD:762CB91A 0D F5 84 6D ADD.W SP, SP, #0x420
LOAD:762CB91E 70 BD POP {R4-R6,PC}
5.计算代码crc值,代码如下:
LOAD:762CBF94
LOAD:762CBF94 ; 计算crc A2E12890
LOAD:762CBF94
LOAD:762CBF94 GetCodeCrc
LOAD:762CBF94 ; __unwind {
LOAD:762CBF94 70 B5 PUSH {R4-R6,LR}
LOAD:762CBF96 4F F0 FF 33 MOV.W R3, #0xFFFFFFFF
LOAD:762CBF9A 00 24 MOVS R4, #0
LOAD:762CBF9C
LOAD:762CBF9C loc_762CBF9C
LOAD:762CBF9C 94 42 CMP R4, R2
LOAD:762CBF9E 09 D0 BEQ loc_762CBFB4
LOAD:762CBFA0 0E 5D LDRB R6, [R1,R4] ; 取代码字节
LOAD:762CBFA2 DD B2 UXTB R5, R3
LOAD:762CBFA4 01 34 ADDS R4, #1
LOAD:762CBFA6 75 40 EORS R5, R6
LOAD:762CBFA8 02 35 ADDS R5, #2
LOAD:762CBFAA 50 F8 25 50 LDR.W R5, [R0,R5,LSL#2] ; 查表
LOAD:762CBFAE 85 EA 13 23 EOR.W R3, R5, R3,LSR#8
LOAD:762CBFB2 F3 E7 B loc_762CBF9C
LOAD:762CBFB4
LOAD:762CBFB4
LOAD:762CBFB4 loc_762CBFB4
LOAD:762CBFB4 D8 43 MVNS R0, R3
LOAD:762CBFB6 70 BD POP {R4-R6,PC}
6.比较crc值,相同返回0,代码如下:
LOAD:762CB67A ; 比较crc值
LOAD:762CB67A
LOAD:762CB67A cmpcrc
LOAD:762CB67A 02 44 ADD R2, R0
LOAD:762CB67C 03 46 MOV R3, R0
LOAD:762CB67E 10 B5 PUSH {R4,LR}
LOAD:762CB680
LOAD:762CB680 loc_762CB680
LOAD:762CB680 93 42 CMP R3, R2
LOAD:762CB682 1C D2 BCS loc_762CB6BE
LOAD:762CB684 1C 78 LDRB R4, [R3]
LOAD:762CB686 08 78 LDRB R0, [R1]
LOAD:762CB688 20 1A SUBS R0, R4, R0
LOAD:762CB68A 19 D1 BNE locret_762CB6C0
LOAD:762CB68C 5C 1C ADDS R4, R3, #1
LOAD:762CB68E A2 42 CMP R2, R4
LOAD:762CB690 16 D9 BLS locret_762CB6C0
LOAD:762CB692 5C 78 LDRB R4, [R3,#1]
LOAD:762CB694 48 78 LDRB R0, [R1,#1]
LOAD:762CB696 20 1A SUBS R0, R4, R0
LOAD:762CB698 12 D1 BNE locret_762CB6C0
LOAD:762CB69A 9C 1C ADDS R4, R3, #2
LOAD:762CB69C A2 42 CMP R2, R4
LOAD:762CB69E 0F D9 BLS locret_762CB6C0
LOAD:762CB6A0 9C 78 LDRB R4, [R3,#2]
LOAD:762CB6A2 88 78 LDRB R0, [R1,#2]
LOAD:762CB6A4 20 1A SUBS R0, R4, R0
LOAD:762CB6A6 0B D1 BNE locret_762CB6C0
LOAD:762CB6A8 DC 1C ADDS R4, R3, #3
LOAD:762CB6AA A2 42 CMP R2, R4
LOAD:762CB6AC 08 D9 BLS locret_762CB6C0
LOAD:762CB6AE DC 78 LDRB R4, [R3,#3]
LOAD:762CB6B0 04 31 ADDS R1, #4
LOAD:762CB6B2 11 F8 01 0C LDRB.W R0, [R1,#-1]
LOAD:762CB6B6 04 33 ADDS R3, #4
LOAD:762CB6B8 20 1A SUBS R0, R4, R0
LOAD:762CB6BA E1 D0 BEQ loc_762CB680
LOAD:762CB6BC 10 BD POP {R4,PC}
LOAD:762CB6BE
LOAD:762CB6BE
LOAD:762CB6BE loc_762CB6BE
LOAD:762CB6BE 00 20 MOVS R0, #0
LOAD:762CB6C0
LOAD:762CB6C0 locret_762CB6C0
LOAD:762CB6C0
LOAD:762CB6C0 10 BD POP {R4,PC}
7.如果crc值一样,就解密其它8个函数,并修复mono导出、hook函数mono_image_open_from_data_with_name、加载libNetHTProtect.so,调用libNetHTProtect.so中函数,反函数指令加密回去,代码如下:
解密8个函数(解密前32字节)
LOAD:762CB9C8 loc_762CB9C8
LOAD:762CB9C8 55 F8 04 1F LDR.W R1, [R5,#4]!
LOAD:762CB9CC 29 B1 CBZ R1, loc_762CB9DA ; 比较是否解密结束
LOAD:762CB9CE 23 68 LDR R3, [R4]
LOAD:762CB9D0 20 46 MOV R0, R4
LOAD:762CB9D2 39 44 ADD R1, R7
LOAD:762CB9D4 EA 69 LDR R2, [R5,#0x1C]
LOAD:762CB9D6 5B 68 LDR R3, [R3,#4]
LOAD:762CB9D8 98 47 BLX R3 ; 解密函数前32字节,R1要解密的函数,R2大小,R3解密函数
LOAD:762CB9DA
LOAD:762CB9DA loc_762CB9DA
LOAD:762CB9DA B5 42 CMP R5, R6 ; 比较是否解密结束
LOAD:762CB9DC F4 D1 BNE loc_762CB9C8
LOAD:762CB9DE BD E8 F0 41 POP.W {R4-R8,LR}
LOAD:762CB9E2 04 B0 ADD SP, SP, #0x10
LOAD:762CB9E4 70 47 BX LR
//解密函数
LOAD:762CB9E6 DecCode_0
LOAD:762CB9E6 ; __unwind {
LOAD:762CB9E6 2D E9 F0 41 PUSH.W {R4-R8,LR}
LOAD:762CB9EA 01 EB 02 08 ADD.W R8, R1, R2
LOAD:762CB9EE 0D 46 MOV R5, R1
LOAD:762CB9F0 21 F4 7F 64 BIC.W R4, R1, #0xFF0
LOAD:762CB9F4 08 F6 FF 71 ADDW R1, R8, #0xFFF
LOAD:762CB9F8 43 68 LDR R3, [R0,#4]
LOAD:762CB9FA 21 F4 7F 61 BIC.W R1, R1, #0xFF0
LOAD:762CB9FE 24 F0 0F 04 BIC.W R4, R4, #0xF
LOAD:762CBA02 21 F0 0F 01 BIC.W R1, R1, #0xF
LOAD:762CBA06 06 46 MOV R6, R0
LOAD:762CBA08 0F 1B SUBS R7, R1, R4
LOAD:762CBA0A 5B 68 LDR R3, [R3,#4]
LOAD:762CBA0C 20 46 MOV R0, R4
LOAD:762CBA0E 07 22 MOVS R2, #7
LOAD:762CBA10 39 46 MOV R1, R7
LOAD:762CBA12 98 47 BLX R3
LOAD:762CBA14 29 46 MOV R1, R5
LOAD:762CBA16
LOAD:762CBA16 loc_762CBA16
LOAD:762CBA16 41 45 CMP R1, R8
LOAD:762CBA18 05 D0 BEQ loc_762CBA26
LOAD:762CBA1A 0A 78 LDRB R2, [R1]
LOAD:762CBA1C 82 F0 58 02 EOR.W R2, R2, #0x58 ; 解密代码
LOAD:762CBA20 01 F8 01 2B STRB.W R2, [R1],#1
LOAD:762CBA24 F7 E7 B loc_762CBA16
LOAD:762CBA26
LOAD:762CBA26
LOAD:762CBA26 loc_762CBA26
LOAD:762CBA26 72 68 LDR R2, [R6,#4]
LOAD:762CBA28 A5 F1 10 00 SUB.W R0, R5, #0x10
LOAD:762CBA2C 20 31 ADDS R1, #0x20 ; ' '
LOAD:762CBA2E D2 F8 D8 C0 LDR.W R12, [R2,#0xD8]
LOAD:762CBA32 00 22 MOVS R2, #0
LOAD:762CBA34 E0 47 BLX R12
LOAD:762CBA36 73 68 LDR R3, [R6,#4]
LOAD:762CBA38 20 46 MOV R0, R4
LOAD:762CBA3A 39 46 MOV R1, R7
LOAD:762CBA3C 05 22 MOVS R2, #5
LOAD:762CBA3E 5B 68 LDR R3, [R3,#4]
LOAD:762CBA40 98 47 BLX R3
LOAD:762CBA42 BD E8 F0 81 POP.W {R4-R8,PC}
8.把比较crc与解密其它8个函数的指令加密回去,代码如下:
加密函数指令(32字节)
LOAD:762CB7B8
LOAD:762CB7B8 ; 指令加密
LOAD:762CB7B8
LOAD:762CB7B8 DecCode
LOAD:762CB7B8 ; __unwind {
LOAD:762CB7B8 2D E9 F0 41 PUSH.W {R4-R8,LR}
LOAD:762CB7BC 01 EB 02 08 ADD.W R8, R1, R2
LOAD:762CB7C0 0D 46 MOV R5, R1
LOAD:762CB7C2 21 F4 7F 64 BIC.W R4, R1, #0xFF0
LOAD:762CB7C6 08 F6 FF 71 ADDW R1, R8, #0xFFF
LOAD:762CB7CA 83 68 LDR R3, [R0,#8]
LOAD:762CB7CC 21 F4 7F 61 BIC.W R1, R1, #0xFF0
LOAD:762CB7D0 24 F0 0F 04 BIC.W R4, R4, #0xF
LOAD:762CB7D4 21 F0 0F 01 BIC.W R1, R1, #0xF
LOAD:762CB7D8 06 46 MOV R6, R0
LOAD:762CB7DA 0F 1B SUBS R7, R1, R4
LOAD:762CB7DC 9B 68 LDR R3, [R3,#8]
LOAD:762CB7DE 20 46 MOV R0, R4
LOAD:762CB7E0 07 22 MOVS R2, #7
LOAD:762CB7E2 39 46 MOV R1, R7
LOAD:762CB7E4 98 47 BLX R3
LOAD:762CB7E6 2B 46 MOV R3, R5
LOAD:762CB7E8
LOAD:762CB7E8 loc_762CB7E8
LOAD:762CB7E8 43 45 CMP R3, R8 ; 加密指令
LOAD:762CB7EA 03 F1 01 01 ADD.W R1, R3, #1
LOAD:762CB7EE 06 D0 BEQ loc_762CB7FE
LOAD:762CB7F0 1A 78 LDRB R2, [R3] ; 加密后的指令
LOAD:762CB7F2 0B 46 MOV R3, R1
LOAD:762CB7F4 82 F0 58 02 EOR.W R2, R2, #0x58
LOAD:762CB7F8 01 F8 01 2C STRB.W R2, [R1,#-1]
LOAD:762CB7FC F4 E7 B loc_762CB7E8 ; 加密指令
LOAD:762CB7FE
LOAD:762CB7FE
LOAD:762CB7FE loc_762CB7FE
LOAD:762CB7FE B3 68 LDR R3, [R6,#8]
LOAD:762CB800 28 46 MOV R0, R5
LOAD:762CB802 00 22 MOVS R2, #0
LOAD:762CB804 D3 F8 D8 30 LDR.W R3, [R3,#0xD8]
LOAD:762CB808 98 47 BLX R3
LOAD:762CB80A B3 68 LDR R3, [R6,#8]
LOAD:762CB80C 20 46 MOV R0, R4
LOAD:762CB80E 39 46 MOV R1, R7
LOAD:762CB810 05 22 MOVS R2, #5
LOAD:762CB812 5B 68 LDR R3, [R3,#4]
LOAD:762CB814 98 47 BLX R3
LOAD:762CB816 BD E8 F0 81 POP.W {R4-R8,PC}
9.解密so字符串还原导出表,代码如下:
LOAD:762B4EEE arg_4C= 0x4C
LOAD:762B4EEE
LOAD:762B4EEE 70 B5 PUSH {R4-R6,LR}
LOAD:762B4EF0
LOAD:762B4EF0 04 46 MOV R4, R0
LOAD:762B4EF2 43 68 LDR R3, [R0,#4]
LOAD:762B4EF4 13 B9 CBNZ R3, loc_762B4EFC
LOAD:762B4EF6
LOAD:762B4EF6
LOAD:762B4EF6 loc_762B4EF6
LOAD:762B4EF6 4F F0 FF 30 MOV.W R0, #0xFFFFFFFF
LOAD:762B4EFA 70 BD POP {R4-R6,PC}
LOAD:762B4EFC
LOAD:762B4EFC
LOAD:762B4EFC loc_762B4EFC
LOAD:762B4EFC C0 68 LDR R0, [R0,#0xC]
LOAD:762B4EFE 03 68 LDR R3, [R0]
LOAD:762B4F00 1B 68 LDR R3, [R3]
LOAD:762B4F02 98 47 BLX R3
LOAD:762B4F04 01 46 MOV R1, R0
LOAD:762B4F06 00 28 CMP R0, #0
LOAD:762B4F08 F5 D0 BEQ loc_762B4EF6
LOAD:762B4F0A 23 68 LDR R3, [R4]
LOAD:762B4F0C 20 46 MOV R0, R4
LOAD:762B4F0E 62 68 LDR R2, [R4,#4]
LOAD:762B4F10 5B 68 LDR R3, [R3,#4]
LOAD:762B4F12 98 47 BLX R3
LOAD:762B4F14 23 68 LDR R3, [R4]
LOAD:762B4F16 E0 68 LDR R0, [R4,#0xC]
LOAD:762B4F18 5D 69 LDR R5, [R3,#0x14]
LOAD:762B4F1A 03 68 LDR R3, [R0]
LOAD:762B4F1C 9B 68 LDR R3, [R3,#8]
LOAD:762B4F1E 98 47 BLX R3
LOAD:762B4F20 06 46 MOV R6, R0
LOAD:762B4F22 E0 68 LDR R0, [R4,#0xC]
LOAD:762B4F24 02 68 LDR R2, [R0]
LOAD:762B4F26 D2 68 LDR R2, [R2,#0xC]
LOAD:762B4F28 90 47 BLX R2
LOAD:762B4F2A 31 46 MOV R1, R6
LOAD:762B4F2C 63 68 LDR R3, [R4,#4]
LOAD:762B4F2E 02 46 MOV R2, R0
LOAD:762B4F30 20 46 MOV R0, R4
LOAD:762B4F32 A8 47 BLX R5 ; 解密字符串表
LOAD:762B4F34 23 68 LDR R3, [R4]
LOAD:762B4F36 E1 69 LDR R1, [R4,#0x1C]
LOAD:762B4F38 20 46 MOV R0, R4
LOAD:762B4F3A DB 6A LDR R3, [R3,#0x2C]
LOAD:762B4F3C 98 47 BLX R3
LOAD:762B4F3E 23 69 LDR R3, [R4,#0x10]
LOAD:762B4F40 E0 68 LDR R0, [R4,#0xC]
LOAD:762B4F42 5D 6F LDR R5, [R3,#0x74]
LOAD:762B4F44 03 68 LDR R3, [R0]
LOAD:762B4F46 9B 68 LDR R3, [R3,#8]
LOAD:762B4F48 98 47 BLX R3
LOAD:762B4F4A 63 68 LDR R3, [R4,#4]
LOAD:762B4F4C D3 F8 8C 10 LDR.W R1, [R3,#0x8C]
LOAD:762B4F50 A8 47 BLX R5 ; 解密so数据,还原导出
LOAD:762B4F52 23 68 LDR R3, [R4]
LOAD:762B4F54 20 46 MOV R0, R4
LOAD:762B4F56 61 68 LDR R1, [R4,#4]
LOAD:762B4F58 9B 68 LDR R3, [R3,#8]
LOAD:762B4F5A 98 47 BLX R3
LOAD:762B4F5C 00 20 MOVS R0, #0
LOAD:762B4F5E 70 BD POP {R4-R6,PC}
此时将libmono.so从内存中dump出来可以看到导出了。
10.获取libNetHTProtect.so中的函数(反调试),代码如下:
LOAD:762B7856 loc_762B7856
LOAD:762B7856 09 9E LDR R6, [SP,#0x24] ; dlopenso
LOAD:762B7858 0F 98 LDR R0, [SP,#0x3C]
LOAD:762B785A 33 68 LDR R3, [R6]
LOAD:762B785C 5B 6D LDR R3, [R3,#0x54]
LOAD:762B785E 98 47 BLX R3
LOAD:762B7860 AD B1 CBZ R5, loc_762B788E
LOAD:762B7862 33 68 LDR R3, [R6]
LOAD:762B7864 0D F2 EC 40 ADDW R0, SP, #0x4EC
LOAD:762B7868 00 21 MOVS R1, #0
LOAD:762B786A 5B 6A LDR R3, [R3,#0x24]
LOAD:762B786C 98 47 BLX R3 ; dlopen libNetHTProtect.so
LOAD:762B786E 00 28 CMP R0, #0
LOAD:762B7870 00 F0 12 81 BEQ.W loc_762B7A98
11.hook函数mono_image_open_from_data_with_name,代码如下:
LOAD:762B8DF2 loc_762B8DF2
LOAD:762B8DF2 21 68 LDR R1, [R4] ; hook
LOAD:762B8DF4 53 00 LSLS R3, R2, #1
LOAD:762B8DF6 4F F6 DF 00 MOVW R0, #0xF8DF
LOAD:762B8DFA 21 F0 01 01 BIC.W R1, R1, #1
LOAD:762B8DFE 21 F8 12 00 STRH.W R0, [R1,R2,LSL#1]
LOAD:762B8E02 4F F4 70 41 MOV.W R1, #0xF000
LOAD:762B8E06 22 68 LDR R2, [R4]
LOAD:762B8E08 22 F0 01 02 BIC.W R2, R2, #1
LOAD:762B8E0C 1A 44 ADD R2, R3
LOAD:762B8E0E 51 80 STRH R1, [R2,#2]
LOAD:762B8E10 22 68 LDR R2, [R4]
LOAD:762B8E12 22 F0 01 02 BIC.W R2, R2, #1
LOAD:762B8E16 99 18 ADDS R1, R3, R2
LOAD:762B8E18 62 68 LDR R2, [R4,#4]
LOAD:762B8E1A 8A 80 STRH R2, [R1,#arg_4]
LOAD:762B8E1C 22 68 LDR R2, [R4,#(loc_762B8EF0 - 0x762B8EF0)]
LOAD:762B8E1E 22 F0 01 02 BIC.W R2, R2, #1
LOAD:762B8E22 13 44 ADD R3, R2
LOAD:762B8E24 E2 88 LDRH R2, [R4,#6]
LOAD:762B8E26 DA 80 STRH R2, [R3,#6]
LOAD:762B8E28 04 E0 B loc_762B8E34
LOAD:762B8E2A
LOAD:762B8E2A
LOAD:762B8E2A loc_762B8E2A
LOAD:762B8E2A 60 4A LDR R2, =0xE51FF004
LOAD:762B8E2C 1A 60 STR R2, [R3]
LOAD:762B8E2E 62 68 LDR R2, [R4,#4]
LOAD:762B8E30
LOAD:762B8E30 loc_762B8E30
LOAD:762B8E30 23 68 LDR R3, [R4]
LOAD:762B8E32 5A 60 STR R2, [R3,#4]
LOAD:762B8E34
LOAD:762B8E34 loc_762B8E34
LOAD:762B8E34 A3 68 LDR R3, [R4,#8]
LOAD:762B8E36 3B B1 CBZ R3, loc_762B8E48
LOAD:762B8E38 22 68 LDR R2, [R4]
LOAD:762B8E3A 12 F0 01 0F TST.W R2, #1
LOAD:762B8E3E 62 6F LDR R2, [R4,#0x74]
LOAD:762B8E40 18 BF IT NE
LOAD:762B8E42 42 F0 01 02 ORRNE.W R2, R2, #1
LOAD:762B8E46 1A 60 STR R2, [R3]
LOAD:762B8E48
LOAD:762B8E48 loc_762B8E48
LOAD:762B8E48
LOAD:762B8E48 02 9E LDR R6, [SP,#8]
LOAD:762B8E4A 00 22 MOVS R2, #0
LOAD:762B8E4C 20 68 LDR R0, [R4,#arg_0]
LOAD:762B8E4E A1 6F LDR R1, [R4,#0x78]
LOAD:762B8E50
LOAD:762B8E50 loc_762B8E50
LOAD:762B8E50 33 68 LDR R3, [R6]
LOAD:762B8E52 20 F0 01 00 BIC.W R0, R0, #1
LOAD:762B8E56 01 44 ADD R1, R0
LOAD:762B8E58 D3 F8 D8 30 LDR.W R3, [R3,#arg_D8]
LOAD:762B8E5C 98 47 BLX R3 ; cacheflush
12.再将解密的so数据加密加回去,代码如下:
LOAD:762B7B2A loc_762B7B2A
LOAD:762B7B2A A1 42 CMP R1, R4
LOAD:762B7B2C 17 D0 BEQ loc_762B7B5E
LOAD:762B7B2E 01 33 ADDS R3, #(loc_762B7CA8+1 - 0x762B7CA8)
LOAD:762B7B30 0D F6 EC 07 ADDW R7, SP, #0x8EC
LOAD:762B7B34 DB B2 UXTB R3, R3
LOAD:762B7B36 F8 5C LDRB R0, [R7,R3]
LOAD:762B7B38 02 44 ADD R2, R0
LOAD:762B7B3A D2 B2 UXTB R2, R2
LOAD:762B7B3C BD 5C LDRB R5, [R7,R2]
LOAD:762B7B3E FD 54 STRB R5, [R7,R3]
LOAD:762B7B40 B8 54 STRB R0, [R7,R2]
LOAD:762B7B42 FD 5C LDRB R5, [R7,R3]
LOAD:762B7B44 28 44 ADD R0, R5
LOAD:762B7B46 C0 B2 UXTB R0, R0
LOAD:762B7B48 38 5C LDRB R0, [R7,R0]
LOAD:762B7B4A 05 11 ASRS R5, R0, #arg_4
LOAD:762B7B4C 45 EA 00 10 ORR.W R0, R5, R0,LSL#4
LOAD:762B7B50 4D 78 LDRB R5, [R1,#1]
LOAD:762B7B52 C0 B2 UXTB R0, R0
LOAD:762B7B54 5C 38 SUBS R0, #0x5C ; '\'
LOAD:762B7B56 68 40 EORS R0, R5
LOAD:762B7B58 01 F8 01 0F STRB.W R0, [R1,#1]!
13.调用libNetHTProtect.so中的函数(反调试)
直接patch让它返回
libNetHTProtect.so:764DFA84 2D E9 F0 4F PUSH.W {R4-R11,LR}
libNetHTProtect.so:764DFA88 BD E8 F0 8F POP.W {R4-R11,PC}
14.再将解密的8个函数再加密回去,代码如下:
LOAD:762B5BDA loc_762B5BDA
LOAD:762B5BDA 01 AD ADD R5, SP, #0x20+var_1C
LOAD:762B5BDC 00 21 MOVS R1, #0
LOAD:762B5BDE 0A 46 MOV R2, R1
LOAD:762B5BE0 28 46 MOV R0, R5
LOAD:762B5BE2 00 F0 31 FF BL encFunc
LOAD:762B5BE6 20 69 LDR R0, [R4,#0x10]
LOAD:762B5BE8 03 68 LDR R3, [R0]
LOAD:762B5BEA DB 68 LDR R3, [R3,#0xC]
LOAD:762B5BEC 98 47 BLX R3
LOAD:762B5BEE 28 46 MOV R0, R5
15.libmono.so壳大致分析完成, 当加载dll时通过hook函数mono_image_open_from_data_with_name是走到libNetHTProtect.so中去。接下来就是分析hook函数,dump dll。
16.在游戏目assets\bin\Data\Managed中没有发现Assembly-CSharp.dll,只有Assembly-CSharp-firstpass.dll,反编译Assembly-CSharp-firstpass.dll时看不到代码,如图3所示。应该是做保护处理了。
图3
17.分析libNetHTProtect.so中hook函数dump Assembly-CSharp-firstpass.dll,当dll加载走到hook函数中时会判断标记是否须要修复处理,代码如下:
判断标记并做出相应处理
.text:762EF534 loc_762EF534
.text:762EF534 1B 9E LDR R6, [SP,#0x2E80+bufer] ; 判断32位是否为0
.text:762EF536 31 6A LDR R1, [R6,#0x20]
.text:762EF538 00 29 CMP R1, #0
.text:762EF53A 00 F0 BA 82 BEQ.W loc_762EFAB2
.text:762EF53E 9A 42 CMP R2, R3
//如果有标记就开始修复dll
.text:762EF984 loc_762EF984
.text:762EF984 DD F8 18 90 LDR.W R9, [SP,#0x2E80+src] ; 修复dll
.text:762EF988 1B 9E LDR R6, [SP,#0x2E80+bufer]
.text:762EF98A C7 EB 09 03 RSB.W R3, R7, R9
.text:762EF98E 16 F8 0B 20 LDRB.W R2, [R6,R11]
.text:762EF992 F1 5C LDRB R1, [R6,R3]
.text:762EF994 4A 40 EORS R2, R1
.text:762EF996 F2 54 STRB R2, [R6,R3]
.text:762EF998 DB 1B SUBS R3, R3, R7
.text:762EF99A 37 44 ADD R7, R6
.text:762EF99C
.text:762EF99C loc_762EF99C
.text:762EF99C 5B 45 CMP R3, R11
.text:762EF99E 09 D3 BCC loc_762EF9B4
.text:762EF9A0 DD F8 6C 80 LDR.W R8, [SP,#0x2E80+bufer]
.text:762EF9A4 FA 5C LDRB R2, [R7,R3]
.text:762EF9A6 13 F8 08 10 LDRB.W R1, [R3,R8]
.text:762EF9AA 4A 40 EORS R2, R1
.text:762EF9AC 03 F8 08 20 STRB.W R2, [R3,R8]
.text:762EF9B0 01 3B SUBS R3, #1
.text:762EF9B2 F3 E7 B loc_762EF99C
.text:762EF9B4、
.text:762EF9B4
.text:762EF9B4 loc_762EF9B4
.text:762EF9B4 4F F0 01 09 MOV.W R9, #1
.text:762EF9B8
.text:762EF9B8 loc_762EF9B8
.text:762EF9B8 A4 F1 20 08 SUB.W R8, R4, #0x20
.text:762EF9BC A5 F1 30 01 SUB.W R1, R5, #0x30
.text:762EF9C0 0D F6 08 02 ADDW R2, SP, #0x2E80+var_2678
18.将修复完后的dll dump出来进行反编译,如图4所示。
图4
从上图可以看到主要是读到资源目录下的code.bytes.assetbundle文件,传给libUnityHelper.so中的LoadGame函数解密加载,代码如下:
signed int __fastcall sub_82BAE570(size_t *codedata, signed int codelen, int (*JNI_OnLoad)(void), int constsize1, int constsize2)
{
int v5; // r7
signed int v6; // r6
size_t *v7; // r5
int v8; // r0
int v9; // r7
int v10; // r1
int v11; // r3
bool v12; // cf
int v13; // r1
int v14; // r1
int v15; // r4
int v16; // r2
int v17; // r3
signed int result; // r0
int v19; // r6
int v20; // r3
int v21; // r5
_DWORD *v22; // r0
int v23; // r1
int v24; // r2
_DWORD *v25; // r8
int v26; // r11
unsigned int **v27; // r0
unsigned int v28; // r5
int v29; // r6
int (**v30)(void); // r10
_DWORD *v31; // r9
_DWORD *v32; // r8
int (*v33)(void); // r7
int (*v34)(void); // t1
_DWORD *v35; // r0
_DWORD *v36; // r4
_DWORD *v37; // r2
int v38; // r3
_DWORD *v39; // t1
unsigned int **v40; // r2
unsigned int v41; // r3
_DWORD *v42; // r0
unsigned int *v43; // r10
int v44; // r4
_DWORD *v45; // r8
int v46; // r9
int v47; // r3
int v48; // lr
int v49; // r4
int v50; // r6
bool v51; // zf
bool v52; // nf
unsigned __int8 v53; // vf
int v54; // r2
int v55; // t1
int v56; // r1
unsigned int *v57; // r2
int v58; // r1
unsigned int v59; // r3
unsigned int *v60; // r0
unsigned int *v61; // lr
int v62; // r1
int v63; // r2
_DWORD *v64; // r0
_DWORD *v65; // r11
unsigned int *v66; // r2
int v67; // r3
_DWORD *v68; // t1
unsigned int v69; // r3
int v70; // lr
int v71; // r0
int v72; // r2
int v73; // r3
_DWORD *v74; // r1
int v75; // r12
_DWORD *v76; // r6
signed int v77; // r6
int v78; // r6
size_t v79; // r7
int v80; // r6
unsigned int *v81; // r12
int v82; // r3
_DWORD *v83; // r0
int v84; // r2
_DWORD *v85; // r0
int v86; // [sp+0h] [bp-50h]
unsigned int **v87; // [sp+4h] [bp-4Ch]
_BYTE *dllbuffer; // [sp+8h] [bp-48h]
void *handle; // [sp+Ch] [bp-44h]
int (__fastcall *mono_image_open_from_data)(_BYTE *, size_t, signed int, int *); // [sp+10h] [bp-40h]
int (__fastcall *mono_assembly_load_from)(int, const char *, int *); // [sp+14h] [bp-3Ch]
int v92; // [sp+1Ch] [bp-34h]
size_t size; // [sp+20h] [bp-30h]
int v94; // [sp+24h] [bp-2Ch]
v5 = constsize1;
v6 = codelen;
v7 = codedata;
v8 = JNI_OnLoad();
v9 = *(_DWORD *)(v8 + v5);
v86 = *(_DWORD *)(v8 + constsize2);
v10 = *(_DWORD *)(v8 + constsize2 + 4) - v86;
v11 = v10 + 3;
v12 = v10 < 0;
v13 = v10 & ~(v10 >> 32);
if ( v12 )
v13 = v11;
v14 = v13 >> 2;
if ( v14 <= 0 )
return 0;
v15 = 0;
while ( 1 )
{
v16 = *(_DWORD *)(v9 + 8 * v15 + 4);
if ( *(_DWORD *)(v16 - 12) == 19 )
break;
LABEL_5:
if ( ++v15 == v14 )
return 0;
}
v17 = 0;
do
{
result = *(unsigned __int8 *)(v16 + v17++);
if ( result )
goto LABEL_5;
}
while ( v17 != 19 );
if ( *(_DWORD *)(v86 + 4 * v15) )
return result;
handle = dlopen("libmono.so", 0);
mono_image_open_from_data = (int (__fastcall *)(_BYTE *, size_t, signed int, int *))dlsym(
handle,
"mono_image_open_from_data");
v92 = 0;
mono_assembly_load_from = (int (__fastcall *)(int, const char *, int *))dlsym(handle, "mono_assembly_load_from");
memcpy(&g_codedata, v7, 0x400u);
size = v7[256];
if ( v6 < 1032 )
goto LABEL_89;
v19 = v6 - 1032;
v94 = v7[257];
v20 = v94 + 7;
if ( v94 + 7 < 0 )
v20 = v94 + 14;
if ( v19 != v20 >> 3 )
{
LABEL_89:
v25 = 0;
}
else
{
v21 = (int)(v7 + 258);
v22 = malloc(0x14u);
v23 = (v94 + 7) & (v94 >> 32);
if ( v94 >= 0 )
v23 = v94;
v24 = v94 % 8;
v25 = v22;
v22[3] = 0;
*((_BYTE *)v22 + 16) = 0;
*v22 = v21;
v22[1] = v23 >> 3;
v22[2] = v24;
}
v26 = v15;
dllbuffer = malloc(size);
v27 = (unsigned int **)malloc(0xCu);
v28 = 0;
v29 = 0;
v30 = &dword_82C72024;
v31 = v25;
v32 = 0;
*v27 = 0;
v27[1] = 0;
v27[2] = 0;
v87 = v27;
do
{
v34 = v30[1];
++v30;
v33 = v34;
if ( !v34 )
goto LABEL_21;
v35 = malloc(0x10u);
v36 = v35;
v35[1] = 0;
*v35 = 0;
v35[3] = v33;
v35[2] = v29;
if ( v28 )
{
if ( v35 == (_DWORD *)*v32 )
goto LABEL_21;
v37 = v32;
v38 = 0;
while ( ++v38 != v28 )
{
v39 = (_DWORD *)v37[1];
++v37;
if ( v35 == v39 )
goto LABEL_21;
}
}
v40 = v87;
v41 = (unsigned int)v87[1];
if ( v41 <= v28 )
{
v87[1] = (unsigned int *)(v41 + 128);
v42 = realloc(v32, 4 * (v41 + 128));
v40 = v87;
v32 = v42;
*v87 = v42;
}
v32[v28] = v36;
v40[2] = (unsigned int *)(v28++ + 1);
LABEL_21:
++v29;
}
while ( v29 != 256 );
v43 = v32;
v44 = v26;
v45 = v31;
memset(&g_codedata, 0, 0x400u);
if ( !v28 )
goto LABEL_66;
if ( v28 == 1 )
{
v28 = *v43;
free(v43);
free(v87);
goto LABEL_67;
}
v46 = v26;
while ( 2 )
{
v47 = 0;
v48 = (int)(v43 - 1);
v49 = 0;
v50 = 0;
while ( 1 )
{
v53 = __OFSUB__(v28, v47);
v51 = v28 == v47;
v52 = (signed int)(v28 - v47++) < 0;
if ( (unsigned __int8)(v52 ^ v53) | v51 )
break;
v55 = *(_DWORD *)(v48 + 4);
v48 += 4;
v54 = v55;
if ( !v55 )
break;
if ( v50 && (v56 = *(_DWORD *)(v54 + 12), v56 >= *(_DWORD *)(v50 + 12)) )
{
if ( v49 )
{
if ( v56 < *(_DWORD *)(v49 + 12) )
v49 = v54;
}
else
{
v49 = v54;
}
if ( v47 < 0 )
break;
}
else
{
v49 = v50;
v50 = v54;
if ( v47 < 0 )
break;
}
}
v57 = v43;
v58 = 0;
while ( 1 )
{
v59 = *v57;
v60 = v57;
++v58;
++v57;
if ( v50 == v59 )
break;
if ( v58 == v28 )
{
v61 = *v87;
goto LABEL_50;
}
}
v84 = v28 + 0x3FFFFFFF;
v61 = *v87;
v28 = (unsigned int)v87[2] - 1;
v87[2] = (unsigned int *)v28;
*v60 = v43[v84];
if ( !v28 )
{
v85 = malloc(0x10u);
*v85 = v50;
v65 = v85;
v85[1] = v49;
v85[3] = *(_DWORD *)(v49 + 12) + *(_DWORD *)(v50 + 12);
goto LABEL_60;
}
LABEL_50:
if ( v49 != *v61 )
{
v62 = (int)(v61 + 1);
v63 = 0;
while ( 1 )
{
++v63;
v81 = (unsigned int *)v62;
v62 += 4;
if ( v63 == v28 )
break;
if ( v49 == *v81 )
goto LABEL_81;
}
v64 = malloc(0x10u);
*v64 = v50;
v65 = v64;
v64[1] = v49;
v64[3] = *(_DWORD *)(v49 + 12) + *(_DWORD *)(v50 + 12);
goto LABEL_56;
}
v81 = v61;
LABEL_81:
v82 = v28-- + 0x3FFFFFFF;
v87[2] = (unsigned int *)v28;
*v81 = v61[v82];
v83 = malloc(0x10u);
*v83 = v50;
v65 = v83;
v83[1] = v49;
v83[3] = *(_DWORD *)(v49 + 12) + *(_DWORD *)(v50 + 12);
if ( v28 )
{
LABEL_56:
if ( v65 == (_DWORD *)*v43 )
goto LABEL_63;
v66 = v43;
v67 = 0;
while ( ++v67 != v28 )
{
v68 = (_DWORD *)v66[1];
++v66;
if ( v65 == v68 )
goto LABEL_63;
}
}
LABEL_60:
v69 = (unsigned int)v87[1];
if ( v69 <= v28 )
{
v87[1] = (unsigned int *)(v69 + 128);
v43 = (unsigned int *)realloc(v43, 4 * (v69 + 128));
*v87 = v43;
}
v43[v28] = (unsigned int)v65;
v87[2] = (unsigned int *)(v28++ + 1);
LABEL_63:
if ( v28 > 1 )
continue;
break;
}
v44 = v46;
if ( v28 )
v28 = *v43;
LABEL_66:
free(v43);
free(v87);
LABEL_67:
v70 = 0;
v71 = v45[1];
v72 = v45[3];
v73 = *((unsigned __int8 *)v45 + 16);
v74 = (_DWORD *)v28;
v75 = v45[2];
LABEL_70:
if ( v71 == v72 )
goto LABEL_76;
do
{
do
{
v77 = (signed int)*(unsigned __int8 *)(*v45 + v72) >> v73;
v73 = (unsigned __int8)(v73 + 1);
v78 = v77 & 1;
*((_BYTE *)v45 + 16) = v73;
if ( v73 == 8 )
{
++v72;
v73 = 0;
*((_BYTE *)v45 + 16) = 0;
v45[3] = v72;
}
if ( !v78 )
{
v76 = (_DWORD *)*v74;
if ( *v74 )
goto LABEL_69;
dllbuffer[v70++] = v74[2];
v74 = *(_DWORD **)v28;
goto LABEL_70;
}
v76 = (_DWORD *)v74[1];
if ( v76 )
{
LABEL_69:
v74 = v76;
goto LABEL_70;
}
dllbuffer[v70++] = v74[2];
v74 = *(_DWORD **)(v28 + 4);
}
while ( v71 != v72 );
LABEL_76:
;
}
while ( v73 != v75 );
dllbuffer[v70] = v74[2];
sub_82BADFA0((void **)v28);
free(v45);
v79 = size;
v80 = mono_image_open_from_data(dllbuffer, size, 1, &v92);// 解密后dll
memset(dllbuffer, 0, v79);
free(dllbuffer);
if ( v80 && mono_assembly_load_from(v80, "Assembly-CSharp.dll", &v92) )
{
*(_DWORD *)(v86 + 4 * v44) = v80;
dlclose(handle);
result = 1;
}
else
{
dlclose(handle);
result = 0;
}
return result;
}
从上面代码可以看到调用mono_image_open_from_data获取MonoImage,再调用mono_assembly_load_from完成对dll的加载,所以LoadGame最终也会走到libNetHTProtect.so中hook函数,判断是否须要修复,函数走完后直dump Assembly-CSharp.dll.dll就可以了,将dump出来的dll反编译后正常,如图5、6所示。
图5
图6
整个反外挂流程到这里基本分析完成。
0x03:总结
1.该反外挂系统主要用到了svc 0系统调用做反调试,再加上动态加密解,内联、混淆,在一定程度给调试分析上加大了些难度。至于是否安全,个人觉得仁者见仁,智者见智,一个安全商业产品要做到产品易用性与绝对安全兼得基本上是不太可能的。
样本下载链接:https://pan.baidu.com/s/1IGo-U6gW79PXe3I-MiFLgQ 提取码:3f5x