转自:http://bbs.pediy.com/thread-217059.htm
工具与环境
Xposed
IDA 6.8
JEB 2.2.5
Fiddler2
010Editor
NEXUS 5 Android 4.4
好久不玩逆向怕调试器生锈,拿出来磨磨!
高手莫要见笑,仅供小菜玩乐,有不对或不足的地方还请多多指教,不胜感激!
0x00: 程序大概情况分析
在我们拿到一个APP准备破解时一般得安装运行,程序运行后须要注册用户,随便注册一个用户登录,以下是APP须要购买vip才能使用的大概情况。
通过简单的查看可以知道是要通过网络支付后才能使用VIP,可以使用Fiddler进行抓包分析,但是请求体与返回值都是加密了的,目前还看不懂。
用JEB反编译app发现被加固了。
通过上面简单的分析后,该款应用为了防止被破解,主要做了以下几点防护。
利用第三方加固将app加固,网络验证是否为VIP权限。
0x01:壳反调试分析
通过第一部分的介绍,发现软件被加固了,接下来就是要脱掉壳才能更好地分析下去,挂上IDA在JNI_Onload下断点,反调试主要有,获取rtld_db_dlactivity判断是否为空,过反调试将获取到的内容清零就成了、time时间比较,将返回值清零,过/proc/self/status反调试将open函数返回0,过/proc/net/tcp也是将open函数返回0,文件监控不用管,还有一处比较隐藏的反调试raise,直接将函数改成返回,代码如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
LOAD:75A6A038 F0 41 2D E9 STMFD SP!, {R4-R8,LR}
LOAD:75A6A03C F0 81 BD E8 LDMFD SP!, {R4-R8,PC};改成返回
LOAD:75A6A040 ; ---------------------------------------------------------------------------
LOAD:75A6A040 70 50 9F E5 LDR R5, =(off_75A72ED4 - 0x75A6A05C)
LOAD:75A6A044 70 20 9F E5 LDR R2, =(is_androidserver_prot - 0x75A6A058)
LOAD:75A6A048 70 60 9F E5 LDR R6, =0xE7F001F0
LOAD:75A6A04C 10 D0 4D E2 SUB SP, SP, #0x10
LOAD:75A6A050 02 20 8F E0 ADD R2, PC, R2 ; is_androidserver_prot
LOAD:75A6A054 05 50 9F E7 LDR R5, [PC,R5] ; off_75A72ED4
LOAD:75A6A058 03 30 9F E7 LDR R3, [PC,R3] ; off_75A72ED0
LOAD:75A6A05C 04 50 8D E5 STR R5, [SP,#0x10+var_C]
LOAD:75A6A060 08 30 8D E5 STR R3, [SP,#0x10+var_8]
LOAD:75A6A064 0C 20 8D E5 STR R2, [SP,#0x10+var_4]
LOAD:75A6A068 04 70 8D E2 ADD R7, SP, #4
LOAD:75A6A06C 0C 80 8D E2 ADD R8, SP, #0x10+var_4
LOAD:75A6A070
LOAD:75A6A070 loc_75A6A070 ; CODE XREF: Anti_raise+70j
LOAD:75A6A070 04 40 45 E2 SUB R4, R5, #4
LOAD:75A6A074 24 50 85 E2 ADD R5, R5, #0x24
LOAD:75A6A078 01 00 00 EA B loc_75A6A084
LOAD:75A6A07C ; ---------------------------------------------------------------------------
LOAD:75A6A07C
LOAD:75A6A07C loc_75A6A07C ; CODE XREF: Anti_raise+54j
LOAD:75A6A07C 05 00 54 E1 CMP R4, R5
LOAD:75A6A080 06 00 00 0A BEQ loc_75A6A0A0
LOAD:75A6A084
LOAD:75A6A084 loc_75A6A084 ; CODE XREF: Anti_raise+40j
LOAD:75A6A084 ; Anti_raise+64j
LOAD:75A6A084 04 30 B4 E5 LDR R3, [R4,#4]!
LOAD:75A6A088 06 00 53 E1 CMP R3, R6
LOAD:75A6A08C FA FF FF 1A BNE loc_75A6A07C
LOAD:75A6A090 09 00 A0 E3 MOV R0, #9 ; sig
LOAD:75A6A094 25 F6 FF EB BL
raise
LOAD:75A6A098 05 00 54 E1 CMP R4, R5
LOAD:75A6A09C F8 FF FF 1A BNE loc_75A6A084
LOAD:75A6A0A0
LOAD:75A6A0A0 loc_75A6A0A0 ; CODE XREF: Anti_raise+48j
LOAD:75A6A0A0 08 00 57 E1 CMP R7, R8
LOAD:75A6A0A4 04 50 B7 15 LDRNE R5, [R7,#0xC+var_8]!
LOAD:75A6A0A8 F0 FF FF 1A BNE loc_75A6A070
LOAD:75A6A0AC 10 D0 8D E2 ADD SP, SP, #0x10
LOAD:75A6A0B0 F0 81 BD E8 LDMFD SP!, {R4-R8,PC}
LOAD:75A6A0B0 ; End of function Anti_raise
LOAD:75A6A0B0
|
以上就是过掉所有主要的反调试了!
0x02:壳大致流程分析与Dump出解密后的dex
过掉反调试后在Case 29 与case33下好断点,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
//case 29 文件偏移:9DB8
/data/app-lib/com.txy.anywhere-1/libjiagu_old.so 75A66000 00050000
LOAD:75A6FDB8
LOAD:75A6FDB8 loc_75A6FDB8 ; CODE XREF: __fun_a_18(
char
*,uint):loc_75A6F8CCj
LOAD:75A6FDB8 ; __fun_a_18(
char
*,uint):loc_75A6F8DCj ...
LOAD:75A6FDB8 01 30 8C E2 ADD R3, R12, #1 ; jumptable 75A6F8DC
case
29
LOAD:75A6FDBC 03 20 D4 E7 LDRB R2, [R4,R3]
LOAD:75A6FDC0 1C 30 8D E5 STR R3, [SP,#0xCC+var_B0]
LOAD:75A6FDC4 00 00 52 E3 CMP R2, #0
LOAD:75A6FDC8 C9 01 00 0A BEQ loc_75A704F4
LOAD:75A6FDCC 80 05 95 E9 LDMIB R5, {R7,R8,R10}
LOAD:75A6FDD0 0C 20 84 E0 ADD R2, R4, R12
LOAD:75A6FDD4 05 B0 92 E5 LDR R11, [R2,#5]
LOAD:75A6FDD8 38 20 95 E5 LDR R2, [R5,#0x38]
LOAD:75A6FDDC 03 30 94 E7 LDR R3, [R4,R3]
LOAD:75A6FDE0 09 C0 8C E2 ADD R12, R12, #9
LOAD:75A6FDE4 1C C0 8D E5 STR R12, [SP,#0xCC+var_B0]
LOAD:75A6FDE8 02 B0 8B E0 ADD R11, R11, R2
LOAD:75A6FDEC 0B B0 63 E0 RSB R11, R3, R11
LOAD:75A6FDF0 00 C0 95 E5 LDR R12, [R5]
LOAD:75A6FDF4 0C 00 A0 E1 MOV R0, R12
LOAD:75A6FDF8 07 10 A0 E1 MOV R1, R7
LOAD:75A6FDFC 08 20 A0 E1 MOV R2, R8
LOAD:75A6FE00 0A 30 A0 E1 MOV R3, R10
LOAD:75A6FE04 0B E0 A0 E1 MOV LR, R11
LOAD:75A6FE08 3E FF 2F E1 BLX LR
LOAD:75A6FE0C 00 70 A0 E1 MOV R7, R0
LOAD:75A6FE10 01 C0 A0 E1 MOV R12, R1
LOAD:75A6FE14 00 70 85 E5 STR R7, [R5] ; jumptable 000098CC
case
28
LOAD:75A6FE18 04 C0 85 E5 STR R12, [R5,#4]
LOAD:75A6FE1C 1C C0 9D E5 LDR R12, [SP,#0xCC+var_B0]
LOAD:75A6FE20 A9 FE FF EA B loc_75A6F8CC
//case 33 执行解压函数与执行解密后第二个so的JNI_OnLoad函数 文件偏移: 9B44
/data/app-lib/com.txy.anywhere-1/libjiagu_old.so 75A66000 00050000
LOAD:75A6FB44 loc_75A6FB44 ; CODE XREF: __fun_a_18(
char
*,uint):loc_75A6F8CCj
LOAD:75A6FB44 ; __fun_a_18(
char
*,uint):loc_75A6F8DCj ...
LOAD:75A6FB44 01 30 8C E2 ADD R3, R12, #1 ; jumptable 75A6F8DC
case
33
LOAD:75A6FB48 03 20 D4 E7 LDRB R2, [R4,R3]
LOAD:75A6FB4C 1C 30 8D E5 STR R3, [SP,#0xCC+var_B0]
LOAD:75A6FB50 00 00 52 E3 CMP R2, #arg_0
LOAD:75A6FB54 66 02 00 0A BEQ loc_75A704F4
LOAD:75A6FB58 02 30 8C E2 ADD R3, R12, #2
LOAD:75A6FB5C 03 20 94 E7 LDR R2, [R4,R3]
LOAD:75A6FB60 1C 30 8D E5 STR R3, [SP,#0xCC+var_B0]
LOAD:75A6FB64 06 C0 8C E2 ADD R12, R12, #6
LOAD:75A6FB68 02 B1 95 E7 LDR R11, [R5,R2,LSL#2]
LOAD:75A6FB6C 1C C0 8D E5 STR R12, [SP,#0xCC+var_B0]
LOAD:75A6FB70 80 15 95 E8 LDMIA R5, {R7,R8,R10,R12}
LOAD:75A6FB74 07 00 A0 E1 MOV R0, R7
LOAD:75A6FB78 08 10 A0 E1 MOV R1, R8
LOAD:75A6FB7C 0A 20 A0 E1 MOV R2, R10
LOAD:75A6FB80 0C 30 A0 E1 MOV R3, R12
LOAD:75A6FB84 0B E0 A0 E1 MOV LR, R11
LOAD:75A6FB88 3E FF 2F E1 BLX LR
LOAD:75A6FB8C 00 C0 A0 E1 MOV R12, R0 ; jumptable 000098CC
case
32
LOAD:75A6FB90 00 C0 85 E5 STR R12, [R5]
LOAD:75A6FB94 1C C0 9D E5 LDR R12, [SP,#0x50+var_34
|
下好断点后一直F9会发现主是逻辑就是获取解压函数(uncompressc)解压第二个so数据,解压后就是解密第二个SO了。接下来就是在内存中加载第二个SO并获取JNI_OnLoad函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
LOAD:401030A8 F0 40 2D E9 STMFD SP!, {R4-R7,LR}
LOAD:401030AC C8 40 9F E5 LDR R4, =(off_40109EC0 - 0x401030C0)
LOAD:401030B0 9C D0 4D E2 SUB SP, SP, #0x9C
LOAD:401030B4 00 70 A0 E1 MOV R7, R0
LOAD:401030B8 04 40 9F E7 LDR R4, [PC,R4]
LOAD:401030BC 00 30 94 E5 LDR R3, [R4]
LOAD:401030C0
LOAD:401030C0 loc_401030C0 ; DATA XREF: LOAD:4010317Co
LOAD:401030C0 01 60 A0 E1 MOV R6, R1
LOAD:401030C4 94 20 A0 E3 MOV R2, #0x94 ; '
LOAD:401030C8 00 10 A0 E3 MOV R1, #0
LOAD:401030CC 0D 00 A0 E1 MOV R0, SP
LOAD:401030D0 94 30 8D E5 STR R3, [SP,#0x94]
LOAD:401030D4 B5 ED FF EB BL
memset
LOAD:401030D8 A0 30 9F E5 LDR R3, =0x6F732E2A
LOAD:401030DC 00 20 A0 E3 MOV R2, #0
LOAD:401030E0 0D 00 A0 E1 MOV R0, SP
LOAD:401030E4 0C 30 8D E5 STR R3, [SP,#0xC]
LOAD:401030E8 10 20 CD E5 STRB R2, [SP,#0x10]
LOAD:401030EC 00 70 8D E5 STR R7, [SP]
LOAD:401030F0 04 60 8D E5 STR R6, [SP,#4]
LOAD:401030F4 EB F2 FF EB BL sub_400FFCA8
LOAD:401030F8 84 30 9F E5 LDR R3, =(dword_4014D740 - 0x40103108)
LOAD:401030FC 84 10 9F E5 LDR R1, =(aMakekey - 0x4010310C)
LOAD:40103100 03 30 8F E0 ADD R3, PC, R3
LOAD:40103104 01 10 8F E0 ADD R1, PC, R1
LOAD:40103108
LOAD:40103108 loc_40103108 ; DATA XREF: LOAD:40103184o
LOAD:40103108 00 00 83 E5 STR R0, [R3]
LOAD:4010310C
LOAD:4010310C loc_4010310C ; DATA XREF: LOAD:40103188o
LOAD:4010310C F2 F2 FF EB BL my_dlsym
LOAD:40103110 00 30 50 E2 SUBS R3, R0, #0
LOAD:40103114 08 00 00 0A BEQ loc_4010313C
LOAD:40103118 FF 0E C3 E3 BIC R0, R3, #0xFF0
LOAD:4010311C 0F 00 C0 E3 BIC R0, R0, #0xF
LOAD:40103120 01 1A A0 E3 MOV R1, #0x1000
LOAD:40103124 03 20 A0 E3 MOV R2, #3
LOAD:40103128 7D 70 A0 E3 MOV R7, #0x7D ;
'}'
LOAD:4010312C 00 00 00 EF SVC 0
LOAD:40103130 54 20 9F E5 LDR R2, =(loc_40102C0C - 0x4010313C)
LOAD:40103134 02 20 8F E0 ADD R2, PC, R2
LOAD:40103138 00 20 83 E5 STR R2, [R3]
LOAD:4010313C
LOAD:4010313C loc_4010313C ; DATA XREF: LOAD:4010318Co
LOAD:4010313C 4C 30 9F E5 LDR R3, =(dword_4014D740 - 0x4010314C)
LOAD:40103140 4C 10 9F E5 LDR R1, =(aJni_onload - 0x40103150)
LOAD:40103144 03 30 8F E0 ADD R3, PC, R3
LOAD:40103148 01 10 8F E0 ADD R1, PC, R1
LOAD:4010314C
LOAD:4010314C loc_4010314C ; DATA XREF: LOAD:40103190o
LOAD:4010314C 00 00 93 E5 LDR R0, [R3]
LOAD:40103150
LOAD:40103150 loc_40103150 ; DATA XREF: LOAD:40103194o
LOAD:40103150 E1 F2 FF EB BL my_dlsym ; 获取第二个SO的JNI_OnLoad函数
LOAD:40103154 94 10 9D E5 LDR R1, [SP,#0x94]
LOAD:40103158 38 30 9F E5 LDR R3, =(dword_4014D744 - 0x40103168)
LOAD:4010315C 00 20 94 E5 LDR R2, [R4]
LOAD:40103160 03 30 8F E0 ADD R3, PC, R3
LOAD:40103164 02 00 51 E1 CMP R1, R2
LOAD:40103168
LOAD:40103168 loc_40103168 ; DATA XREF: LOAD:40103198o
LOAD:40103168 00 00 83 E5 STR R0, [R3]
LOAD:4010316C 01 00 00 1A BNE loc_40103178
LOAD:40103170 9C D0 8D E2 ADD SP, SP, #0x9C
LOAD:40103174 F0 80 BD E8 LDMFD SP!, {R4-R7,PC}
LOAD:40103178
LOAD:40103178 loc_40103178
|
获取到第二个SO的JNI_OnLoad函数地址后走到Case 33处跳到第二个SO的JNI_OnLoad去执行了,到此第一个SO的主要工作就基本完成了。
第二个SO的JNI_OnLoad主要工作就是注册壳本身的 Native函数与被Native的onCreate函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
//注册 Native onCreate 文件偏移 C252
debug190 76174000 761DA000 R . X D . byte 00
public
CODE 32 00 01
debug190:76180252 loc_76180252 ; CODE XREF: debug190:7618028Aj
debug190:76180252 ; debug190:76180292j ...
debug190:76180252 D7 23 MOVS R3, #0xD7 ; '
debug190:76180254 09 98 LDR R0, [SP,#0x24]
debug190:76180256 9B 00 LSLS R3, R3, #2
debug190:76180258 01 68 LDR R1, [R0]
debug190:7618025A CB 58 LDR R3, [R1,R3]
debug190:7618025C 21 1C MOVS R1, R4
debug190:7618025E 9C 46 MOV R12, R3
debug190:76180260 01 23 MOVS R3, #1
debug190:76180262 E0 47 BLX R12 ; Native onCreate R2:存放注册信息,签名等
debug190:76180264 5B 46 MOV R3, R11
debug190:76180266 01 22 MOVS R2, #1
debug190:76180268 1B 78 LDRB R3, [R3]
debug190:7618026A 1A 42 TST R2, R3
debug190:7618026C 00 D0 BEQ loc_76180270
debug190:7618026E A7 E1 B loc_761805C0
|
下面是我通过hook注册函数打印出来的对应类的Native函数,在后面SO劫持会有说.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class
-Ldalvik/
system
/DexFile; ->name=getClassNameList address=75FE10F9
class
-Lcom/qihoo/bugreport/javacrash/CrashReportDataFactory; ->name=interface9 address=7600917D
class
-Lcom/qihoo/util/StubApp296881940; ->name=mark address=75FF75FD
class
-Lcom/qihoo/util/StubApp296881940; ->name=mark address=75FFBE45
class
-Lcom/qihoo/util/StubApp296881940; ->name=mark address=75FFBD91
class
-Lcom/qihoo/util/StubApp296881940; ->name=interface10 address=75FFE819
class
-Lcom/qihoo/util/StubApp296881940; ->name=interface5 address=75FE0375
class
-Lcom/qihoo/util/StubApp296881940; ->name=interface6 address=75FDD95D
class
-Lcom/qihoo/util/StubApp296881940; ->name=interface7 address=75FE1801
class
-Lcom/qihoo/util/StubApp296881940; ->name=interface8 address=75FDDCFD
class
-Lcom/mob/tools/MobUIShell; ->name=onCreate address=75FDF729
class
-Lcom/txy/anywhere/activity/GreenHandGuideActivity; ->name=onCreate address=75FDF6F1
class
-Lcom/txy/anywhere/activity/MainActivity; ->name=onCreate address=75FDF6B9
class
-Lcom/txy/anywhere/activity/PanoramaActivity; ->name=onCreate address=75FDF681
class
-Lcom/txy/anywhere/activity/move/MoveDetailActivity; ->name=onCreate address=75FDF649
class
-Lcom/txy/anywhere/activity/move/MoveSettingsMapChoiceActivity; ->name=onCreate address=75FDF611
class
-Lcom/txy/anywhere/activity/specific/SpecificProgramDetailActivity; ->name=onCreate address=75FDF5D9
class
-Lcom/txy/anywhere/wxapi/WXPayEntryActivity; ->name=onCreate address=75FDF5A1
class
-Lcom/common/customp/PullEntry; ->name=getAppkey address=75FE1439
|
接下来就是解密原始DEX了,解密逻辑如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
//解密dex
密钥 A0 EC E6 CC FB A6 F1 A2 EC A3 E7 AF E1 5A EA 2F
debug190:761BB0E0 ; ---------------------------------------------------------------------------
debug190:761BB0E0 F0 B5 PUSH {R4-R7,LR}
debug190:761BB0E2 57 46 MOV R7, R10
debug190:761BB0E4 4E 46 MOV R6, R9
debug190:761BB0E6 45 46 MOV R5, R8
debug190:761BB0E8 E0 B4 PUSH {R5-R7}
debug190:761BB0EA 16 1C MOVS R6, R2
debug190:761BB0EC 80 22 MOVS R2, #0x80 ;
'€'
debug190:761BB0EE 80 46 MOV R8, R0
debug190:761BB0F0 24 4C LDR R4, =(dword_761DCD20 - 0x761BB0FA)
debug190:761BB0F2 C2 B0 SUB SP, SP, #0x108
debug190:761BB0F4 1F 1C MOVS R7, R3
debug190:761BB0F6 7C 44 ADD R4, PC ; dword_761DCD20
debug190:761BB0F8 4A 9B LDR R3, [SP,#0x128]
debug190:761BB0FA 24 68 LDR R4, [R4]
debug190:761BB0FC 01 AD ADD R5, SP, #4
debug190:761BB0FE 99 46 MOV R9, R3
debug190:761BB100 23 68 LDR R3, [R4]
debug190:761BB102 8A 46 MOV R10, R1
debug190:761BB104 28 1C MOVS R0, R5
debug190:761BB106 00 21 MOVS R1, #0
debug190:761BB108 52 00 LSLS R2, R2, #1
debug190:761BB10A 41 93 STR R3, [SP,#0x104]
debug190:761BB10C 10 F0 4E FA BL j_j_memset_1
debug190:761BB110 43 46 MOV R3, R8
debug190:761BB112 59 68 LDR R1, [R3,#4]
debug190:761BB114 00 29 CMP R1, #0
debug190:761BB116 2D D0 BEQ loc_761BB174
debug190:761BB118 28 1C MOVS R0, R5
debug190:761BB11A 10 22 MOVS R2, #0x10
debug190:761BB11C 00 F0 94 F8 BL Rc4_Key_Init ; A0 EC E6 CC FB A6 F1 A2 EC A3 E7 AF E1 5A EA 2F
debug190:761BB120 3B 68 LDR R3, [R7]
debug190:761BB122 00 2B CMP R3, #0
debug190:761BB124 11 D0 BEQ loc_761BB14A
debug190:761BB126 28 1C MOVS R0, R5
debug190:761BB128 51 46 MOV R1, R10
debug190:761BB12A 32 1C MOVS R2, R6
debug190:761BB12C 00 F0 D6 F8 BL Rc4_Dec
debug190:761BB130 4B 46 MOV R3, R9
debug190:761BB132 00 20 MOVS R0, #0
debug190:761BB134 1E 60 STR R6, [R3]
debug190:761BB136
debug190:761BB136 loc_761BB136 ; CODE XREF: debug190:761BB172j
debug190:761BB136 ; debug190:761BB178j ...
debug190:761BB136 41 9A LDR R2, [SP,#0x104]
debug190:761BB138 23 68 LDR R3, [R4]
debug190:761BB13A 9A 42 CMP R2, R3
debug190:761BB13C 20 D1 BNE loc_761BB180
debug190:761BB13E 42 B0 ADD SP, SP, #0x108
debug190:761BB140 1C BC POP {R2-R4}
debug190:761BB142 90 46 MOV R8, R2
debug190:761BB144 99 46 MOV R9, R3
debug190:761BB146 A2 46 MOV R10, R4
debug190:761BB148 F0 BD POP {R4-R7,PC}
debug190:761BB14A ; ---------------------------------------------------------------------------
debug190:761BB14A
debug190:761BB14A loc_761BB14A ; CODE XREF: debug190:761BB124j
debug190:761BB14A 30 1C MOVS R0, R6
debug190:761BB14C 10 F0 96 FA BL malloc_0
debug190:761BB150 80 46 MOV R8, R0
debug190:761BB152 00 28 CMP R0, #0
debug190:761BB154 11 D0 BEQ loc_761BB17A
debug190:761BB156 51 46 MOV R1, R10
debug190:761BB158 32 1C MOVS R2, R6
debug190:761BB15A 10 F0 17 FA BL memcpy_0 ; 拷贝加密的dex内容
debug190:761BB15E 28 1C MOVS R0, R5
debug190:761BB160 41 46 MOV R1, R8
debug190:761BB162 32 1C MOVS R2, R6
debug190:761BB164 00 F0 BA F8 BL Rc4_Dec ; 解密Dex内容
debug190:761BB168 4B 46 MOV R3, R9
debug190:761BB16A 1E 60 STR R6, [R3]
debug190:761BB16C 43 46 MOV R3, R8
debug190:761BB16E 00 20 MOVS R0, #0
debug190:761BB170 3B 60 STR R3, [R7]
debug190:761BB172 E0 E7 B loc_761BB136
debug190:761BB174 ; ---------------------------------------------------------------------------
debug190:761BB174
//再次解密dex 文件偏移 EDF4
debug190 76174000 761DA000 R . X D . byte 00
public
CODE 32 00 01
debug190:76182DF4 loc_76182DF4 ; CODE XREF: debug190:76182DB6j
debug190:76182DF4 51 46 MOV R1, R10
debug190:76182DF6 34 F0 F3 FA BL loc_761B73E0
debug190:76182DFA 00 28 CMP R0, #0
debug190:76182DFC DC DB BLT loc_76182DB8
debug190:76182DFE 53 46 MOV R3, R10
debug190:76182E00 1B 68 LDR R3, [R3]
debug190:76182E02 58 46 MOV R0, R11
debug190:76182E04 9A 46 MOV R10, R3
debug190:76182E06 5B 46 MOV R3, R11
debug190:76182E08 1B 68 LDR R3, [R3]
debug190:76182E0A 05 99 LDR R1, [SP,#0x14]
debug190:76182E0C 06 9A LDR R2, [SP,#0x18]
debug190:76182E0E DB 68 LDR R3, [R3,#0xC]
debug190:76182E10 9C 46 MOV R12, R3
debug190:76182E12 53 46 MOV R3, R10
debug190:76182E14 E0 47 BLX R12 ; 又是解密
debug190:76182E16 00 28 CMP R0, #0
debug190:76182E18 CE DB BLT loc_76182DB8
debug190:76182E1A 05 98 LDR R0, [SP,#0x14]
debug190:76182E1C 48 F0 0E FC BL free_0
debug190:76182E20 5B 46 MOV R3, R11
debug190:76182E22 1B 68 LDR R3, [R3]
debug190:76182E24 58 46 MOV R0, R11
debug190:76182E26 5B 68 LDR R3, [R3,#4]
debug190:76182E28 98 47 BLX R3
debug190:76182E2A 03 9B LDR R3, [SP,#0xC]
debug190:76182E2C 4D 44 ADD R5, R9
debug190:76182E2E 53 44 ADD R3, R10
debug190:76182E30 18 1C MOVS R0, R3
debug190:76182E32 9D 42 CMP R5, R3
debug190:76182E34 05 D0 BEQ loc_76182E42 ; 解密dex
debug190:76182E36 00 2E CMP R6, #0
debug190:76182E38 03 D0 BEQ loc_76182E42 ; 解密dex
debug190:76182E3A 29 1C MOVS R1, R5
debug190:76182E3C 32 1C MOVS R2, R6
debug190:76182E3E FE F7 49 FF BL loc_76181CD4
//解密dex 头0x70字节 文件偏移 EE42
debug190 76174000 761DA000 R . X D . byte 00
public
CODE 32 00 01
debug190:76182E42 loc_76182E42 ; CODE XREF: sub_76182C48+1ECj
debug190:76182E42 ; sub_76182C48+1F0j
debug190:76182E42 7A 69 LDR R2, [R7,#0x14] ; 解密dex
debug190:76182E44 50 46 MOV R0, R10
debug190:76182E46 70 21 MOVS R1, #0x70 ;
'p'
debug190:76182E48 38 F0 90 F8 BL sub_761BAF6C ; 解密dex头70字节
debug190:76182E4C 01 25 MOVS R5, #1
debug190:76182E4E B9 E7 B loc_76182DC4
|
这时就可以将DEX数据dump出来,其实后面每个onCreate中也会出现明文的DEX。
将dump出来的dex通过JDE反编译后发现很多Activity中的onCreate函数变成了Native了,从上面打印的类与对应函数可以看得出。
0x03:被Native后的onCreate分析, 尝试修复与猜想
在上面分析到注册Native函数时就对onCreate函数下好了断点,直接F9来到onCreate断下。
1
2
3
4
5
6
7
8
9
|
//出现解密后dex 文件偏移 A910
debug098 75DC6000 75E2C000 R . X D . byte 00
public
CODE 32 00 01
debug098:75DD0910 loc_75DD0910 ; CODE XREF: debug098:75DD08F2j
debug098:75DD0910 05 9B LDR R3, [SP,#0x14]
debug098:75DD0912 1C 20 MOVS R0, #0x1C
debug098:75DD0914 1F 68 LDR R7, [R3] ; 出现解密后dex
debug098:75DD0916 B3 68 LDR R3, [R6,#8]
debug098:75DD0918 9C 46 MOV R12, R3
debug098:75DD091A 67 44 ADD R7, R12
|
F8一直单步走到定位指令的地方。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
//定位到dex中的onCreate方法在内存中的指令,获取dex中的onCreate指令并解密。 文件偏移 2F9F8
debug098 75DC6000 75E2C000 R . X D . byte 00
public
CODE 32 00 01
debug098:75DF59F8
debug098:75DF59F8 loc_75DF59F8 ; CODE XREF: debug098:75DF5988j
debug098:75DF59F8 ; debug098:75DF59CAj
debug098:75DF59F8 A3 68 LDR R3, [R4,#8]
debug098:75DF59FA 10 33 ADDS R3, #0x10 ; 定位到onCreate指令
debug098:75DF59FC 1E 1C MOVS R6, R3
debug098:75DF59FE 0C 93 STR R3, [SP,#0x30]
debug098:75DF5A00
debug098:75DF5A00 loc_75DF5A00 ; CODE XREF: debug098:75DF5E54j
debug098:75DF5A00 ; debug098:75DF5EF4j ...
debug098:75DF5A00 0C 9B LDR R3, [SP,#0x30]
debug098:75DF5A02 00 21 MOVS R1, #0
debug098:75DF5A04 F0 1A SUBS R0, R6, R3
debug098:75DF5A06 43 10 ASRS R3, R0, #1
debug098:75DF5A08 30 1C MOVS R0, R6
debug098:75DF5A0A 06 93 STR R3, [SP,#0x18]
debug098:75DF5A0C FE F7 29 FE BL GetCode ; 获取dex中的onCreate指令并解密
debug098:75DF5A10 05 90 STR R0, [SP,#0x14]
debug098:75DF5A12 00 06 LSLS R0, R0, #0x18
debug098:75DF5A14 00 0E LSRS R0, R0, #0x18
debug098:75DF5A16 01 38 SUBS R0, #1 ;
switch
255 cases
debug098:75DF5A18 FE 28 CMP R0, #0xFE ; ' ;判断是否超出表示指令范围
debug098:75DF5A1A 01 D9 BLS loc_75DF5A20
debug098:75DF5A1C 02 F0 8D FF BL def_75DF5A20 ; jumptable 75DF5A20
default
case
|
比如 下面是dex中OnCreate的指令,
读取上面的指令并解密。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
//获取指令,并解密 文件偏移 2E662
debug098 75DC6000 75E2C000 R . X D . byte 00
public
CODE 32 00 01
debug098:75DF4662 GetCode ; CODE XREF: debug098:75DF5A0Cp
debug098:75DF4662 ; debug098:75DF5E40p ...
debug098:75DF4662 38 B5 PUSH {R3-R5,LR}
debug098:75DF4664 05 1C MOVS R5, R0
debug098:75DF4666 0C 1C MOVS R4, R1
debug098:75DF4668 FD F7 D6 FC BL loc_75DF2018
debug098:75DF466C 00 7E LDRB R0, [R0,#0x18] ; 密钥
debug098:75DF466E 64 00 LSLS R4, R4, #1
debug098:75DF4670 02 1C MOVS R2, R0
debug098:75DF4672 03 02 LSLS R3, R0, #8
debug098:75DF4674 1A 43 ORRS R2, R3
debug098:75DF4676 63 5B LDRH R3, [R4,R5] ; 取指令
debug098:75DF4678 5A 40 EORS R2, R3 ;解密
debug098:75DF467A 10 1C MOVS R0, R2
debug098:75DF467C 38 BD POP {R3-R5,PC}
|
解密后判断指令类型,获取执行须要的数据,每一种操作码(OPCode)都对应有一个处理逻辑(是一段代码,但不一定是函数),然后跳转到对应的处理逻辑去处理它,简单说一个正常没加壳的指令格式。
71 20 06 00 02 00 invoke-static {v2, v0}, int aurora.view.AuroraTest.Test2(int, java.lang.String)
71操作码
2 参数个数
0006 method idx
02参数V2
00参数 V0
对应Android官方指令表
但是加壳后的指令被变成了自己定义的了,我第一次想法是想通过分析加壳前与加壳后指令对应关系,只要找到足够多的指令就能将其还原,我简单加了两个apk测试,从第一个中找到了如下的指令对应关系。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
真实指令 自定义指令
6f = 74
6E = 22
0c = cc
1f = 9c
60 = 5a
39 = 9c
1a = 89
22 = 4B
71 = 0b
70 = 33
15 = 5b
5b = e4
54 = 5c
6f = 74
14 = 08
0e = 8e
|
将第一个apk中找到的指令来修复第二个指令,不知道是免费版的原因,还是巧合,我成功修复了第二个apk,但是当我用这个关系来尝试修复要破解的程序时根本不行,后来想了想,这种方法应当不行,都自己实现了解释器,为什么还要让指令固定呢?不科学呀,放弃了这种想法。
其实壳读取并解密一条指令后主要是使用JNI接口函数来实现解析执行。
如果指令类型是调用函数的话就会做如下动作,其它的调用其它接口(简单示例)
1
2
3
4
|
jclass clazz=(*env)->FindClass(env,
"com/example/test"
);
jmethodID methodId=(*env)->GetMethodID(env,clazz,
"Add"
,
"(II)I"
);
// jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
(*env)->CallIntMethod(env,jobject,methodId,3,5);
|
主要流程如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
|
//获取dex中的类名、函数名、签名 文件偏移 2E648
debug098 75DC6000 75E2C000 R . X D . byte 00
public
CODE 32 00 01
debug098:75DF4648
debug098:75DF4648 GetString ; CODE XREF: debug098:75DF4C52p
debug098:75DF4648 ; debug098:75DF4C78p ...
debug098:75DF4648 03 69 LDR R3, [R0,#0x10]
debug098:75DF464A 02 68 LDR R2, [R0]
debug098:75DF464C 89 00 LSLS R1, R1, #2
debug098:75DF464E DB 6B LDR R3, [R3,#0x3C]
debug098:75DF4650 C9 18 ADDS R1, R1, R3
debug098:75DF4652 8B 58 LDR R3, [R1,R2]
debug098:75DF4654 D0 18 ADDS R0, R2, R3
debug098:75DF4656
debug098:75DF4656 loc_75DF4656 ; CODE XREF: debug098:75DF465Ej
debug098:75DF4656 01 30 ADDS R0, #1
debug098:75DF4658 43 1E SUBS R3, R0, #1
debug098:75DF465A 1B 78 LDRB R3, [R3]
debug098:75DF465C 7F 2B CMP R3, #0x7F ;
''
debug098:75DF465E FA D8 BHI loc_75DF4656
debug098:75DF4660 70 47 BX LR
FindClass 文件偏移 18956
debug098 75DC6000 75E2C000 R . X D . byte 00
public
CODE 32 00 01
debug098:75DDE956 sub_75DDE956 ; CODE XREF: debug098:75DF4CBAp
debug098:75DDE956 ; debug098:75DF5092p ...
debug098:75DDE956 08 B5 PUSH {R3,LR}
debug098:75DDE958 03 68 LDR R3, [R0]
debug098:75DDE95A 9B 69 LDR R3, [R3,#0x18]
debug098:75DDE95C 98 47 BLX R3 ; FindClass
debug098:75DDE95E 08 BD POP {R3,PC}
ExceptionCheck 文件偏移 1802E
debug096 75DC6000 75E2C000 R . X D . byte 00
public
CODE 32 00 01
debug096:75DDE02E loc_75DDE02E ; CODE XREF: debug096:75DDE046p
debug096:75DDE02E ; debug096:75DF4BECp ...
debug096:75DDE02E 08 B5 PUSH {R3,LR}
debug096:75DDE030 E4 23 MOVS R3, #0xE4 ; '
debug096:75DDE032 02 68 LDR R2, [R0]
debug096:75DDE034 9B 00 LSLS R3, R3, #2
debug096:75DDE036 D3 58 LDR R3, [R2,R3]
debug096:75DDE038 98 47 BLX R3 ; ExceptionCheck
debug096:75DDE03A 08 BD POP {R3,PC}
//GetMethodID 文件偏移
debug098 75DC6000 75E2C000 R . X D . byte 00
public
CODE 32 00 01
debug098:75DFB11E loc_75DFB11E ; CODE XREF: debug098:75DFB10Ej
debug098:75DFB11E 00 98 LDR R0, [SP]
debug098:75DFB120 04 99 LDR R1, [SP,#0x10]
debug098:75DFB122 01 9A LDR R2, [SP,#4]
debug098:75DFB124 B0 47 BLX R6 ; GetMethodID
debug098:75DFB126 06 1C MOVS R6, R0
debug098:75DFB128 28 1C MOVS R0, R5
//DeleteLocalRef 文件偏移 18018
debug096 75DC6000 75E2C000 R . X D . byte 00
public
CODE 32 00 01
debug096:75DDE018 loc_75DDE018 ; CODE XREF: debug096:75DDE062p
debug096:75DDE018 ; debug096:75DF528Ap ...
debug096:75DDE018 08 B5 PUSH {R3,LR}
debug096:75DDE01A 03 68 LDR R3, [R0]
debug096:75DDE01C DB 6D LDR R3, [R3,#0x5C]
debug096:75DDE01E 98 47 BLX R3 ; DeleteLocalRef
debug096:75DDE020 08 BD POP {R3,PC}
//CallNonvirtualVoidMethodA 文件偏移 2EFEC
debug096 75DC6000 75E2C000 R . X D . byte 00
public
CODE 32 00 01
debug096:75DF4FEC loc_75DF4FEC ; CODE XREF: sub_75DF4B50:loc_75DF4F0Cj
debug096:75DF4FEC 23 68 LDR R3, [R4] ; jumptable 75DF4F0C
case
86
debug096:75DF4FEE 02 9A LDR R2, [SP,#0x108+var_100]
debug096:75DF4FF0 20 1C MOVS R0, R4
debug096:75DF4FF2 FC 33 ADDS R3, #0xFC ; '
debug096:75DF4FF4 00 92 STR R2, [SP,#0x108+var_108]
debug096:75DF4FF6 9D 6F LDR R5, [R3,#0x78]
debug096:75DF4FF8 04 99 LDR R1, [SP,#0x108+var_F8]
debug096:75DF4FFA 18 9A LDR R2, [SP,#0x108+var_A8]
debug096:75DF4FFC 03 9B LDR R3, [SP,#0x108+var_FC]
debug096:75DF4FFE A8 47 BLX R5 ; CallNonvirtualVoidMethodA
debug096:75DF5000 4D E1 B def_75DF4F0C ; jumptable 75DF4F0C
default
case
|
通过上面的流程分析后,我猜想能不能hook或者用其它方法来监控JNI接口,得到对应的执行流程与调用了那那些JNI函数,在对应着Android Dalvik bytecode指令表来反推出真实的指令,其实就是看一条指令执行完成后调用的JNI接口能不能确定是什么指令,目前还没有试,如果大牛们有什么好办法还请多多指教,目的是破解程序,静态分析己经可以了,先不考虑还原。(^_^)
来回顾下壳的主要流程:
壳主soà反调试à解压并解密第二个soà从主so中跳到第二个so中执行à第二人so注册native函数à解密原始DEXà程序跑起来后到Native onCreateà定位到自定义的指令à读取指令并解密à解析指令格式à获取执行指令须要的参数à调用JNI接口执行。
0x04:通过SO劫持跳过烦人的反调试快速到达要分析的函数
我想要是你玩过windows上破解都知道DLL劫持吧,先伪造一个同名的DLL,提供同样的输出函数,每个输出函数中转向真正的DLL函数,不错,我也是用这种方法来实现注入SO来Hook函数,过掉反调试,会省去很多时间反调试上面,直接到你想要分析的关键函数。
分析壳java层代码就知道它主要是通过读取从资源目中把壳so拷到指定目录中加载。
我的做法是hook getAssets().open()函数,将资源中的so替换成我的so,然后将壳so改名放在app-lib目录中给我的so加载。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
XposedHelpers.findAndHookMethod(
AssetManager.
class
,
//被Hook函数所在的类
"open"
,
//被Hook函数的名称
String.
class
,
new
XC_MethodHook(){
@Override
protected
void
beforeHookedMethod(MethodHookParam param)
throws Throwable {
// Hook函数之前执行的代码
String path = param.args[0].toString();
if
(path.equals(
"libjiagu.so"
)){
param.args[0] =
"/data/local/tmp/libjiagu.so"
;
}
Yfw_DebugLog.d(
"open beforeHookedMethod--->0 "
+param.args[0].toString());
}
@Override
protected
void
afterHookedMethod(MethodHookParam param) throws Throwable {
File file =
new
File(param.args[0].toString());
InputStream input =
new
FileInputStream(file);
param.setResult(input);
Yfw_DebugLog.d(
"open afterHookedMethod--->"
+param.getResult().toString());
}
});
So的代码也很简单:
/*
调用原始JNI_OnLoad
*/
jint LoadSo_InitJni(
char
* SoFilePath, JavaVM* vm,
void
* reserved)
{
jint ret;
void
*pHandle = NULL;
pHandle = dlopen(SoFilePath, RTLD_LAZY);
if
(NULL == pHandle)
{
LOGD(
"dlopen %s error!"
, SoFilePath);
return
NULL;
}
_JNI_OnLoad old_JNI_OnLoad = NULL;
old_JNI_OnLoad = (_JNI_OnLoad)dlsym(pHandle,
"JNI_OnLoad"
);
if
(NULL == old_JNI_OnLoad)
{
LOGD(
"old_JNI_OnLoad error!"
);
return
NULL;
}
LOGD(
"old_JNI_OnLoad address=%X env...%X"
, old_JNI_OnLoad, g_env);
ret = old_JNI_OnLoad(vm, reserved);
LOGD(
"old_JNI_OnLoad end...%d"
, ret);
return
ret;
}
Hook open函数过反调试
int
new_open(
const
char
*pathname,
int
oflag, mode_t mode)
{
if
(NULL == pathname)
{
goto
exitret;
}
LOGD(
"new_open..%s"
, pathname);
if
(
strstr
(pathname,
"net/tcp"
) != NULL)
{
return
0;
}
if
(
strstr
(pathname,
"status"
) != NULL)
{
return
0;
}
if
(
strstr
(pathname,
"stat"
) != NULL)
{
return
0;
}
if
(
strstr
(pathname,
"schedstat"
) != NULL)
{
return
0;
}
exitret:
return
old_open(pathname, oflag, mode);
}
Hook
time
过反调试
time_t
new_time(
time_t
* timer) {
LOGD(
"new_time------->"
);
return
0;
}
Hook dvmUseJNIBridge 方便在 native下断点,打印出对应类的方法
void
new_dvmUseJNIBridge(Method* method,
void
* func)
{
LOGD(
"my_dvmUseJNIBridge class-%s ->name=%s address=%08X"
, method->clazz->descriptor, method->name, func);
//LOGD("my_dvmUseJNIBridge nativeFunc-%s", method->nativeFunc);
if
(0 ==
strcmp
(method->name,
"getClassNameList"
))
{
sleep(8);
//等待附加调试器
}
return
old_dvmUseJNIBridge(method, func);
}
|
这样当我们的so加载时用IDA附加上就可以在new_dvmUseJNIBridge下断点,就能在想要分析的Native函数上下断点了。
上面的代码就完成了整个劫持的过程,开心地调试吧!!!!!!
0x05:静态分析APP的注册验证流程与编写Xposed插件。
通过JEB反编译该应用dump出来的的classes.dex文件,直接搜索登录时用到的网址字符串 7658/api/entrance,找到如下的字符串,双击第一个字符串进去 。
其中的a函数是返回网址的,当参数为10时是要找到网址。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
public
static
String a(
int
arg1) {
switch
(arg1) {
case
10
: {
goto
label_4;
//返回登录地址
}
case
11
: {
goto
label_6;
}
case
12
: {
goto
label_8;
}
case
13
: {
goto
label_10;
}
case
14
: {
goto
label_12;
}
case
15
: {
goto
label_14;
}
case
16
: {
goto
label_16;
}
case
17
: {
goto
label_18;
}
case
19
: {
goto
label_20;
}
case
20
: {
goto
label_22;
}
}
|
按JEB快捷键 x 找到传入参数为10调用上面函数的地方,找到如下的类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
public
class
g
extends
j {
private
static
String login;
private
ThreadPoolExecutor d;
private
static
g e;
static
{
g.login = i.a(
10
);
g.e =
null
;
}
在这个类中找到了网络登录请求并解密返回值的函数。
private
String b(h arg5, String arg6) {
String v0 =
null
;
if
(arg5 !=
null
&& (arg6 !=
null
&& (AndroidHelper.isNetworkActive(
this
.b)))) {
String v1 =
this
.a(arg6, arg5.a());
// 组合请求参数
if
(v1 ==
null
) {
return
v0;
}
try
{
JSONObject v2 =
new
JSONObject(a.a(g.login_address, v1.getBytes(
"UTF-8"
),
this
.c));
// 传入网址,应该就是网络请求了
if
(v2.getInt(
"status"
) !=
0
) {
return
v0;
}
v0 = j.b(v2.getString(
"data"
));
// 解密登录返回值
}
catch
(Exception v0_1) {
d.a(((Throwable)v0_1));
v0 =
"server_error"
;
}
}
return
v0;
}
|
分析下请求参数都有些什么东西,组合请求参数的函数实现在父类j中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
String a(String arg9,
int
arg10) {
String v0 =
null
;
if
(arg9 !=
null
) {
String v1 = j.a(
this
.c(arg9));
// aes加密后再base64
if
(TextUtils.isEmpty(((CharSequence)v1))) {
return
v0;
}
try
{
StringBuffer v2 =
new
StringBuffer();
v2.append(
"body="
);
v2.append(URLEncoder.encode(v1,
"UTF-8"
));
v2.append(
"&t1="
);
v2.append(System.currentTimeMillis() /
1000
);
v2.append(
"&t2="
);
v2.append(
this
.d(arg9));
v2.append(
"&type="
);
v2.append(arg10);
v2.append(
"&flag="
);
v2.append(
4
);
v0 = v2.toString();
}
catch
(UnsupportedEncodingException v1_1) {
d.a(((Throwable)v1_1));
}
}
return
v0;
}
|
用Xposed hook 加函数打印参数,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
//aes加密参数
XposedHelpers.findAndHookMethod(Application.
class
,
"attach"
, Context.
class
,
new
XC_MethodHook() {
@Override
protected
void
afterHookedMethod(MethodHookParam param)
throws
Throwable {
ClassLoader cl = ((Context)param.args[
0
]).getClassLoader();
Class<?> hookclass =
null
;
try
{
hookclass = cl.loadClass(
"com.txyapp.client.j"
);
}
catch
(Exception e) {
Yfw_DebugLog.d(
"寻找com.txyapp.client.j 报错"
+ e);
return
;
}
Yfw_DebugLog.d(
"寻找com.txyapp.client.j成功"
);
XposedHelpers.findAndHookMethod(
hookclass,
//被Hook函数所在的类
"a"
,
//被Hook函数的名称
String.
class
,
new
XC_MethodHook(){
@Override
protected
void
beforeHookedMethod(MethodHookParam param)
throws
Throwable {
// Hook函数之前执行的代码
Yfw_DebugLog.d(
"a_Aesenc_data beforeHookedMethod--->0 "
+param.args[
0
].toString());
}
@Override
protected
void
afterHookedMethod(MethodHookParam param)
throws
Throwable {
Yfw_DebugLog.d(
"a_Aesenc_data afterHookedMethod--->"
+param.getResult().toString());
}
});
}
});
|
主要是将用户名密码,还有一些机器信息aes与base加密后请求过去,格式如下:
1
|
{
"data"
:
"{\"clientid\":\"7e7884f241c96051958cb776d6f780d9\",\"password\":\"xxxxxxxxxx\",\"username\":\"crack8888\"}"
,
"header"
:{
"model"
:
"Nexus 5"
,
"root"
:
1
,
"mcc"
:
"460"
,
"uptime"
:
1492303101
,
"sysapi"
:
19
,
"corever"
:
546
,
"vername"
:
"13.1.2"
,
"un"
:
""
,
"cn"
:
"guanfang"
,
"cl"
:
"zh-CN"
,
"brand"
:
"google"
,
"mnc"
:
"02"
,
"uuid"
:
"7FDC501E4B3xxxxx"
,
"cpuabi1"
:
"armeabi-v7a"
,
"cpuabi2"
:
"armeabi"
,
"vercode"
:
322
}}
|
然后服务器返回一些加密后的数据,从上面的g类b函数中可以看出,判断如果请求成功,就解密返回数据,通过hook该解密函数得到解密后的数据格式为json。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
XposedHelpers.findAndHookMethod(Application.
class
,
"attach"
, Context.
class
,
new
XC_MethodHook() {
@Override
protected
void
afterHookedMethod(MethodHookParam param)
throws
Throwable {
ClassLoader cl = ((Context)param.args[
0
]).getClassLoader();
Class<?> hookclass =
null
;
try
{
hookclass = cl.loadClass(
"com.txyapp.client.j"
);
}
catch
(Exception e) {
Yfw_DebugLog.d(
"寻找com.txyapp.client.j 报错"
+ e);
return
;
}
Yfw_DebugLog.d(
"寻找com.txyapp.client.j成功"
);
//解密vip
XposedHelpers.findAndHookMethod(
hookclass,
//被Hook函数所在的类
"b"
,
//被Hook函数的名称
String.
class
,
new
XC_MethodHook(){
@Override
protected
void
beforeHookedMethod(MethodHookParam param)
throws
Throwable {
// Hook函数之前执行的代码
Yfw_DebugLog.d(
"b_Aesdec_data beforeHookedMethod--->0 "
+param.args[
0
].toString());
}
@Override
protected
void
afterHookedMethod(MethodHookParam param)
throws
Throwable {
Yfw_DebugLog.d(
"b_Aesdec_data afterHookedMethod--->"
+param.getResult().toString());
JSONObject jsonObj =
new
JSONObject(param.getResult().toString());
if
(
0
== (
int
)jsonObj.get(
"vip"
) ){
jsonObj.put(
"vip"
,
1
);
jsonObj.put(
"expiretime"
,
1999077441
);
// 2033-05-07
Yfw_DebugLog.d(
"b_Aesdec_data result--->"
+jsonObj.toString());
viptag =
1
;
param.setResult(jsonObj.toString());
}
}
});
}
});
//解密后数据
{
"status"
:
0
,
"token"
:
"49436a44d85f46c7e458a6b071af470a"
,
"username"
:
"crack8888"
,
"vip"
:
0
,
"expiretime"
:
0
}
|
从上面格式可以看出 vip:=0代表不是vip, expiretime:到期时间,所以我的在hook函数中将这两个参数修改如下。
1
|
{
"vip"
:
1
,
"username"
:
"crack8888"
,
"token"
:
"db9ea6e12936463d722fb7bcda7e97ab"
,
"status"
:
0
,
"expiretime"
:
1999077441
}
|
这样就成功破解了,会员功能也正常使用。
还有一个地方就是显示个人信息,hook函数进行修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
//刷新个人信息显示
XposedHelpers.findAndHookMethod(Application.
class
,
"attach"
, Context.
class
,
new
XC_MethodHook() {
@Override
protected
void
afterHookedMethod(MethodHookParam param)
throws
Throwable {
ClassLoader cl = ((Context)param.args[
0
]).getClassLoader();
Class<?> hookclass =
null
;
try
{
hookclass = cl.loadClass(
"com.txy.anywhere.activity.login.PersonalInfoActivity"
);
}
catch
(Exception e) {
Yfw_DebugLog.d(
"寻找com.txy.anywhere.activity.login.PersonalInfoActivity报错"
+ e);
return
;
}
Yfw_DebugLog.d(
"寻找com.txy.anywhere.activity.login.PersonalInfoActivity成功"
);
//判断是否为vip
XposedHelpers.findAndHookMethod(
hookclass,
//被Hook函数所在的类
"a"
,
//被Hook函数的名称
String.
class
,
new
XC_MethodHook(){
@Override
protected
void
beforeHookedMethod(MethodHookParam param)
throws
Throwable {
// Hook函数之前执行的代码
Yfw_DebugLog.d(
"login beforeHookedMethod--->0 "
+param.args[
0
].toString());
JSONObject container1 =
new
JSONObject();
JSONObject v0_1 =
new
JSONObject(param.args[
0
].toString());
JSONObject v0_2 =
new
JSONObject(param.args[
0
].toString());
v0_2 = v0_1.getJSONObject(
"user_info"
);
v0_2.put(
"vip"
,
1
);
//是否为vip 0:为普通会员, 1:为vip
v0_2.put(
"expire_time"
,
1999077441
);
//到期时间
v0_2.put(
"phonenumber"
,
"110"
);
v0_1.put(
"user_info"
, v0_2);
param.args[
0
] = v0_1.toString();
Yfw_DebugLog.d(
"login beforeHookedMethod 1--->0 "
+param.args[
0
].toString());
}
@Override
protected
void
afterHookedMethod(MethodHookParam param)
throws
Throwable {
Yfw_DebugLog.d(
"login afterHookedMethod--->"
);
}
});
}
});
|
到这里就算破解完成了,简单测试了会员功能,都能正常使用。
0x06:总结
由于水平有限,未能更好的分析与表达,不过,通过前面几部分的简单分析,其实我们已经知道只是Native onCreate函数是不够的,主要还是得保护好关键函数不被分析。