Locus Map Pro 3.40 Patch笔记

Locus Map Pro 3.4.0 Patch笔记分享

Locus Map Pro APK介绍

“Locus Map Pro - Outdoor GPS”是一款户外徒步软件。具有记录徒步轨迹,显示加载离线地图等功能。

因为软件是外国公司开发的,没有支持中文。国内用户因为语言的问题使用极不方便。该软件在德国Android
App市场,Google Play,
三星市场上架。为了减小开发包的大小,从3.10开始,ApK用到的部分so库需要从服务器上下载。下载过程会检测用户使用的APK市场,并需要登录用户帐号。因为众多周知的原因,我们大陆形成了独特的APK市场。手机厂家的自定义系统中根本没有内置这三家市场软件,导致用户直接下载失败。而APK开发者显然没考虑到中国市场的问题,所以仅仅提示“网络故障”,搞得用户莫名其妙。很多付费购买了APK的用户也无法正常使用。
基于这两点原因,特别是第二条,在开发者没有支持中国市场之前,我们只能自力更生的解决这些问题。

研究目的

  • 汉化软件,方便国人使用
  • 能顺利安装APK

需要解决的问题和困难

  • 因为需要重新打包,所以需要绕过APK的签名检测。
  • 为了方便安装,需要把要下载的so内置到apk中。
  • 因为不需要再下载so文件,所以需要绕过下载检测流程。
  • 因为没有产生真正的购买过程,所以增强特性会被禁用,变成免费版本。所以需要解除限制。

现在我们以3.40版本为例,探讨如何解决上面的问题。

解除签名限制

首先用apktool对apk解包

1
java -jar apktool2.0.jar d Locus3.40.apk -f -o outdir

输出结果如下:

I: Using Apktool 2.0.0-Beta7 on Locus3.40.apk
I: Loading resource table…
I: Decoding AndroidManifest.xml with resources…
I: Loading resource table from file: C:\Users\monkey\apktool\framework\1.apk
I: Regular manifest package…
I: Decoding file-resources…
W: Could not decode attr value, using undecoded value instead: ns=, name=style,
value=0x7f0d0066
W: Could not decode attr value, using undecoded value instead: ns=, name=style,
value=0x7f0d0047
W: Could not decode attr value, using undecoded value instead: ns=, name=style,
value=0x7f0d007f
W: Could not decode attr value, using undecoded value instead: ns=, name=style,
value=0x7f0d0066
W: Could not decode attr value, using undecoded value instead: ns=, name=style,
value=0x7f0d006e
I: Decoding values / XMLs…
Can’t find framework resources for package of id: -1. You must install proper framework files, see project website for more info.

很不幸,解析资源时遇到不能识别的资源id(原因见黑体部分),解包过程退出。

我们先不处理资源,增加-r参数后,重新解包。

1
java -jar apktool2.0.jar d Locus3.40.apk -f -o outdir -r

这次输出如下
I: Using Apktool 2.0.0-Beta7 on Locus3.40.apk
I: Loading resource table…
I: Copying raw resources…
I: Loading resource table…
I: Baksmaling…
I: Copying assets and libs…
I: Copying unknown files/dir…
I: Copying original files…

解包过程顺利完成。

现在我们在smali文件中查找字符串“Landroid/content/pm/Signature;”, 在Mn.2.smali中找到下面的函数

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
.method private static ・([Landroid/content/pm/Signature;)Z
.locals 2

.line 0
if-eqz p0, :cond_0

array-length v0, p0

if-nez v0, :cond_1

.line 298
:cond_0
const/4 v0, 0x0

return v0

.line 299
:cond_1
const/4 v0, 0x0

aget-object v0, p0, v0

invoke-virtual {v0}, Landroid/content/pm/Signature;->hashCode()I

move-result v0

const v1, 0x1a222754

if-ne v0, v1, :cond_2

.line 301
const/4 v0, 0x1

return v0

.line 302
:cond_2
const/4 v0, 0x0

aget-object v0, p0, v0

invoke-virtual {v0}, Landroid/content/pm/Signature;->hashCode()I

move-result v0

const v1, -0x53ad97d7

if-ne v0, v1, :cond_3

.line 304
const/4 v0, 0x1

return v0

.line 306
:cond_3
const/4 v0, 0x0

return v0
.end method

这段代码是计算签名的hashcode,如何值为-0x53ad97d7或0x1a222754就认为签名是合法的。估计签名分别为免费版和pro版的签名证书是不同的。
这里有两种办法来绕过,一种是修改跳转指令:if-ne改为if-eq;另一种是比较前给v0重新赋值为和v1相同的值:const v0, -0x53ad97d7. 任选一种,修改后重新打包。

1
java -jar  apktool2.0.jar b outdir -f -o Locus3.40_modi.apk

I: Using Apktool 2.0.0-Beta7 on out
I: Smaling…
Exception in
thread “main”
org.jf.dexlib2.writer.util.TryListBuilder$InvalidTryException: Multile
overlapping catches for Ljava/lang/Exception; with differenthandlers
at org.jf.dexlib2.writer.util.TryListBuilder$MutableTryBlock.addHandler(TryListBuilder.java:180)
at org.jf.dexlib2.writer.util.TryListBuilder.addHandler(TryListBuilder.java:311)
at org.jf.dexlib2.writer.util.TryListBuilder.massageTryBlocks(TryListBuilder.java:69)
at org.jf.dexlib2.writer.DexWriter.writeCodeItem(DexWriter.java:881)
at org.jf.dexlib2.writer.DexWriter.writeDebugAndCodeItems(DexWriter.java:759)
at org.jf.dexlib2.writer.DexWriter.writeTo(DexWriter.java:214)
at org.jf.dexlib2.writer.DexWriter.writeTo(DexWriter.java:192)
at brut.androlib.src.SmaliBuilder.build(SmaliBuilder.java:58)
at brut.androlib.src.SmaliBuilder.build(SmaliBuilder.java:41)
at brut.androlib.Androlib.buildSourcesSmali(Androlib.java:337)
at brut.androlib.Androlib.buildSources(Androlib.java:298)
at brut.androlib.Androlib.build(Androlib.java:284)
at brut.androlib.Androlib.build(Androlib.java:258)
at brut.apktool.Main.cmdBuild(Main.java:233)
at brut.apktool.Main.main(Main.java:88)

很不幸,打包失败了。
使我们修改的原因导致的麽。实践证明,无论我们是否做修改,都无法打包成功。那我们该怎么解决这个问题呢。下面我们就来揭晓答案。

重新打包

前面我们发现用apktool把APK解包后,无法重新打包。这是什么原因呢?分析发现这是因为开发者在生成APK包的时候对代码进行了混淆。很多Java类的类名、方法名、字段名都被替换为非ascii字符。导致重新打包失败。例如上一篇我们修改的签名验证的函数就是如此。

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
.method private static ・([Landroid/content/pm/Signature;)Z
.locals 2

.line 0
if-eqz p0, :cond_0

array-length v0, p0

if-nez v0, :cond_1

.line 298
:cond_0
const/4 v0, 0x0

return v0

.line 299
:cond_1
const/4 v0, 0x0

aget-object v0, p0, v0

invoke-virtual {v0}, Landroid/content/pm/Signature;->hashCode()I

move-result v0

const v1, 0x1a222754

if-ne v0, v1, :cond_2

……
.end method

我们可以看到函数名不是我们常见的ascii字符。
如果我们想重新打包,就要首先解决这个问题。

把字符串重新映射为ascii码

利用混淆类似的原理。混淆是把ascii字符串映射为非ascii字符,我们需要一个类似的逆向过程。
这里我们借助apktool来帮我们完成这个工作。

apktool的代码是开源的。我们需要对代码做一些修改,让生成类名、字段名、方法名、代码内字符串的部分做一些修改,从而生成ascii字符的名称。

修改apktool源代码的过程在此略过,感兴趣的可以参考《战胜混淆后的非ASCII字符 – Android 逆向系列三》

我们把修改代码后编译出来的apk命名为apktool_modi.jar

重新操作,我们发现可以打包成功了。
让我们暂时停下来休息一下,去解决接下来的挑战。

重新打包后,安装,程序直接退出。因为有些需要的类被加密了,解密后反射方法的方法名已经被映射为ascii,所以无法反射出相关方法,导致空指针异常。所以我们需要把类解密出来,并进行处理。

解密被加密的类

重新打包后,安装,程序直接退出,查看系统日志,提示如下异常信息。
程序出现空指针异常。

12-15 09:09:29.264: E/AndroidRuntime(857): FATAL EXCEPTION: main
12-15
09:09:29.264: E/AndroidRuntime(857): java.lang.RuntimeException: Unable
to get provider locus.api.core.LocusDataProvider:
java.lang.NullPointerException
12-15 09:09:29.264: E/AndroidRuntime(857): at android.app.ActivityThread.installProvider(ActivityThread.java:4822)
12-15 09:09:29.264: E/AndroidRuntime(857): at android.app.ActivityThread.installContentProviders(ActivityThread.java:4432)
12-15 09:09:29.264: E/AndroidRuntime(857): at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4372)
12-15 09:09:29.264: E/AndroidRuntime(857): at android.app.ActivityThread.access$1300(ActivityThread.java:141)
12-15 09:09:29.264: E/AndroidRuntime(857): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1294)
12-15 09:09:29.264: E/AndroidRuntime(857): at android.os.Handler.dispatchMessage(Handler.java:99)
12-15 09:09:29.264: E/AndroidRuntime(857): at android.os.Looper.loop(Looper.java:137)
12-15 09:09:29.264: E/AndroidRuntime(857): at android.app.ActivityThread.main(ActivityThread.java:5041)
12-15 09:09:29.264: E/AndroidRuntime(857): at java.lang.reflect.Method.invokeNative(Native Method)
12-15 09:09:29.264: E/AndroidRuntime(857): at java.lang.reflect.Method.invoke(Method.java:511)
12-15
09:09:29.264: E/AndroidRuntime(857): at
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
12-15 09:09:29.264: E/AndroidRuntime(857): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
12-15 09:09:29.264: E/AndroidRuntime(857): at dalvik.system.NativeStart.main(Native Method)
12-15 09:09:29.264: E/AndroidRuntime(857): Caused by: java.lang.NullPointerException
12-15 09:09:29.264: E/AndroidRuntime(857): at com.asamm.locus.core.MainApplication._cb8b(:103)
12-15 09:09:29.264: E/AndroidRuntime(857): at o._c5a7.onCreate(:43)
12-15 09:09:29.264: E/AndroidRuntime(857): at android.content.ContentProvider.attachInfo(ContentProvider.java:1058)
12-15 09:09:29.264: E/AndroidRuntime(857): at android.app.ActivityThread.installProvider(ActivityThread.java:4819)
12-15 09:09:29.264: E/AndroidRuntime(857): … 12 more

分析异常发现,最后抛出异常的应用层函数为
12-15 09:09:29.264: E/AndroidRuntime(857): at com.asamm.locus.core.MainApplication._cb8b(:103)

异常中显示了代码行数103(Locus编译时没有删除调试信息,所以可以显示行号。若删除调试信息,这里显示的是指令位移)。
直接在MainApplication.smali文件中搜索”.line 103”(不包含引号).

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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
……
.line 102
new-instance v0, Lcom/asamm/locus/core/MainApplication$if;

invoke-direct {v0, p0}, Lcom/asamm/locus/core/MainApplication$if;-><init>(Lcom/asamm/locus/core/MainApplication;)V

iput-object v0, p0, Lcom/asamm/locus/core/MainApplication;->_cb8e:Lcom/asamm/locus/core/MainApplication$if;

.line 103
iget-object v7, p0, Lcom/asamm/locus/core/MainApplication;->_cb8e:Lcom/asamm/locus/core/MainApplication$if;

move-object v6, v7

move-object v6, v7

iget-object v0, v7, Lcom/asamm/locus/core/MainApplication$if;->_efbda5:Lcom/asamm/locus/core/MainApplication;

invoke-virtual {v0}, Lcom/asamm/locus/core/MainApplication;->getApplicationContext()Landroid/content/Context;

move-result-object v8

invoke-static {}, Lo/Mn;->_cb88()Z

move-result v0

if-eqz v0, :cond_2

invoke-static {v8}, Landroid/preference/PreferenceManager;->getDefaultSharedPreferences(Landroid/content/Context;)Landroid/content/SharedPreferences;

move-result-object v9

invoke-virtual {v8}, Landroid/content/Context;->getResources()Landroid/content/res/Resources;

move-result-object v0

move-object v8, v0

invoke-virtual {v0}, Landroid/content/res/Resources;->getConfiguration()Landroid/content/res/Configuration;

move-result-object v10

const-string v0, "KEY_S_LANGUAGE"

const-string v1, "default"

invoke-interface {v9, v0, v1}, Landroid/content/SharedPreferences;->getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;

move-result-object v0

move-object v9, v0

const-string v1, "default"

invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

move-result v0

if-nez v0, :cond_2

iget-object v0, v10, Landroid/content/res/Configuration;->locale:Ljava/util/Locale;

invoke-virtual {v0}, Ljava/util/Locale;->getLanguage()Ljava/lang/String;

move-result-object v0

invoke-virtual {v0, v9}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

move-result v0

if-nez v0, :cond_2

const-string v12, "_"

move-object v11, v9

new-instance v0, Ljava/util/ArrayList;

invoke-direct {v0}, Ljava/util/ArrayList;-><init>()V

invoke-static {v9, v12, v0}, Lo/Mm;->_efbda5(Ljava/lang/String;Ljava/lang/String;Ljava/util/ArrayList;)Ljava/util/ArrayList;

move-result-object v0

move-object v11, v0

invoke-interface {v0}, Ljava/util/List;->size()I

move-result v0

const/4 v1, 0x1

if-ne v0, v1, :cond_1

new-instance v0, Ljava/util/Locale;

invoke-direct {v0, v9}, Ljava/util/Locale;-><init>(Ljava/lang/String;)V

iput-object v0, v7, Lcom/asamm/locus/core/MainApplication$if;->_cb8b:Ljava/util/Locale;

goto :goto_0

:cond_1
new-instance v0, Ljava/util/Locale;

const/4 v1, 0x0

invoke-interface {v11, v1}, Ljava/util/List;->get(I)Ljava/lang/Object;

move-result-object v1

check-cast v1, Ljava/lang/String;

const/4 v2, 0x1

invoke-interface {v11, v2}, Ljava/util/List;->get(I)Ljava/lang/Object;

move-result-object v2

check-cast v2, Ljava/lang/String;

invoke-direct {v0, v1, v2}, Ljava/util/Locale;-><init>(Ljava/lang/String;Ljava/lang/String;)V

iput-object v0, v7, Lcom/asamm/locus/core/MainApplication$if;->_cb8b:Ljava/util/Locale;

:goto_0
iget-object v0, v7, Lcom/asamm/locus/core/MainApplication$if;->_cb8b:Ljava/util/Locale;

iput-object v0, v10, Landroid/content/res/Configuration;->locale:Ljava/util/Locale;

invoke-virtual {v8}, Landroid/content/res/Resources;->getDisplayMetrics()Landroid/util/DisplayMetrics;

move-result-object v0

invoke-virtual {v8, v10, v0}, Landroid/content/res/Resources;->updateConfiguration(Landroid/content/res/Configuration;Landroid/util/DisplayMetrics;)V

:cond_2
iget-object v0, v6, Lcom/asamm/locus/core/MainApplication$if;->_efbda5:Lcom/asamm/locus/core/MainApplication;

goto :goto_1

:catchall_0
move-exception v0

invoke-virtual {v0}, Ljava/lang/Throwable;->getCause()Ljava/lang/Throwable;

move-result-object v0

throw v0

:goto_1
:try_start_0
const/4 v1, 0x1

new-array v1, v1, [Ljava/lang/Object;

const/4 v2, 0x0

aput-object v0, v1, v2

#**加载加密的o.LY类**
const-string v0, "o.LY"

invoke-static {v0}, Lo/LY$_e383bb;->_cb8f(Ljava/lang/String;)Ljava/lang/Class;

move-result-object v0

const-string v2, "_cb8b"

const/4 v3, 0x1

new-array v3, v3, [Ljava/lang/Class;

const-class v4, Lcom/asamm/locus/core/MainApplication;

const/4 v5, 0x0

aput-object v4, v3, v5

invoke-virtual {v0, v2, v3}, Ljava/lang/Class;->getMethod(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;

move-result-object v0

const/4 v2, 0x0
#**抛出异常的实际是下面这句,因为v0为null值**
invoke-virtual {v0, v2, v1}, Ljava/lang/reflect/Method;->invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
:try_end_0
.catchall {:try_start_0 .. :try_end_0} :catchall_0
……

代码中通过调用o/LY$_e383bb_cb8f方法加载一个类定义对象。奇怪的是我们翻查了代码,没有发现o.LY类的存在。一度我怀疑是APKTool工具存在问题,部分类在反编译时丢失了。查看了apktool代码,发现dex文件中显示的类的个数和解析出的类的个数相同。分析o/LY$_e383bb_cb8f的方法,发现是这里面动态的实现了加载o.LY类。

LY$_e383bb.smali文件又9338行,这里就简单介绍下里面的实现原理。
有一个类加载器和o.LY类的代码被压缩加密后存储为字节数组,存储为类的静态字段。类先解密类加载器代码,然后用类加载器代码加载o.LY类。

现在解释下找到解密后的类的方法。
因为类字节的压缩、加密,解密最终都是通过字节数据来传递的。所以我们先以[B来确认代码的相关位置。
解密代码的最后一步,从一个数组中读取一个整数值,来作为最终字节码的长度,然后生成该长度的数组,把字节码最终解密到该数组中。

下面就是找到的符合特征的地方。

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
    :try_start_15
#计算出解密需要的长度
const/16 v1, 0x26

aget-byte v1, v16, v1

and-int/lit16 v1, v1, 0xff

const/16 v2, 0x27

aget-byte v2, v16, v2

shl-int/lit8 v2, v2, 0x8

int-to-char v2, v2

or-int/2addr v1, v2

const/16 v2, 0x28

aget-byte v2, v16, v2

and-int/lit16 v2, v2, 0xff

shl-int/lit8 v2, v2, 0x10

or-int/2addr v1, v2

const/16 v2, 0x29

aget-byte v2, v16, v2

shl-int/lit8 v2, v2, 0x18

or-int/2addr v1, v2

#计算解压后代码的动态长度,生成合适大小的数组
new-array v1, v1, [B

goto :goto_17

:catchall_13
move-exception v0

invoke-virtual {v0}, Ljava/lang/Throwable;->getCause()Ljava/lang/Throwable;

move-result-object v0

throw v0
:try_end_15
.catch Ljava/lang/Exception; {:try_start_15 .. :try_end_15} :catch_0

:goto_17
:try_start_16
const/4 v2, 0x1

new-array v4, v2, [Ljava/lang/Object;

const/4 v2, 0x0
#数组被放在v4的第一个值中。
aput-object v1, v4, v2

const/16 v2, 0x251

const/16 v5, 0x1f4

const/16 v6, 0x15

invoke-static {v2, v5, v6}, Lo/LY$_e383bb;->_cb88(III)Ljava/lang/String;

move-result-object v2

invoke-static {v2}, Ljava/lang/Class;->forName(Ljava/lang/String;)Ljava/lang/Class;

move-result-object v2

sget-object v5, Lo/LY$_e383bb;->_cabe:[S

const/4 v6, 0x0

aget-short v5, v5, v6

add-int/lit8 v5, v5, 0x1

and-int/lit8 v6, v5, 0xe

const/16 v7, 0x252

invoke-static {v7, v5, v6}, Lo/LY$_e383bb;->_cb88(III)Ljava/lang/String;

move-result-object v5

const/4 v6, 0x1

new-array v6, v6, [Ljava/lang/Class;

const-class v7, [B

const/4 v8, 0x0

aput-object v7, v6, v8

invoke-virtual {v2, v5, v6}, Ljava/lang/Class;->getMethod(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;

move-result-object v2

invoke-virtual {v2, v3, v4}, Ljava/lang/reflect/Method;->invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;

move-result-object v2
#这里类自己码已经在内存里解密存在于变量v1里面。

现在我们借助DebugTool工具,把数组信息输出到硬盘上。
DebugTool是我写的一组smali代码输出工具。可以在下面地址找到它。
DebugTool

现在我们在代码中增加输出语句。
修改上面的代码如下。

1
2
3
4
5
6
7
8
9
    invoke-virtual {v2, v5, v6}, Ljava/lang/Class;->getMethod(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;

move-result-object v2

invoke-virtual {v2, v3, v4}, Ljava/lang/reflect/Method;->invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;

move-result-object v2
#这里类自己码已经在内存里解密存在于变量v1里面。
invoke-static {v1}, Ldebug/DebugTool;->log2File([B)V

这段代码里v1的值没有被修改过,所以可以直接输出,否则提前保存v1的原始值。
这里类加载器的代码和o.LY的代码都会被输出,o.LY的代码保存在类加载器的后面。所以需要对输出文件处理,提取出o.LY的代码。o.LY的字节码存储为一个dex文件格式。所以我们把它存储为o.LY.dex,让后把它压缩为o.LY.zip,然后重命名为o.LY.apk.
最后是用我们自己编译的apktool工具解出o.LY的smali代码.
因为原来的目录已经存在了ly.smali,所以我们把这个LY.smali重新命名为LY.4.smali,把它拷贝到我们的locus的smali的o目录下。
现在因为我们已经把类代码直接解密出来了,所以不需要在对o.LY的代码解密了,所以替换o/LY$_e383bb的内如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.class public Lo/LY$_e383bb;
.super Ljava/lang/Object;


.method public static _cb8f(Ljava/lang/String;)Ljava/lang/Class;
.locals 5

#这里直接返回o.LY类对象
const-class v0, Lo/LY;

check-cast v0, Ljava/lang/Class;

return-object v0
.end method

至此o.LY的代码解密完毕.

重新打包,运行,还是报错。因为还有一个加密的类o.aA. 用同样的方法处理,再次打包运行。
现在发现,apk终于可以运行了, 很是兴奋。但是提示需要下载数据,数据还是下载不了。
不要急,接下来我们就解决运行所需要的数据的问题。

应用程序可以运行了,但是提示运行需要下载大约5M的数据,点击下载,总是提示网络错误。怎么办呢?

准备运行需要的数据

分析代码,并综合热心网友提供的信息。发现所谓的需要的数据就是两个.so文件:libproj.so和libjsqlite.so。
下载数据需要验证用户的购买信息。支持google play、三星应用市场和德国应用市场的账户信息。
要攻克账号这块,困难重重。
只好从别的地方下功夫了。
热心网友提供了apk的钛备份数据。里面有下载好的.so文件。当然了只有热心网友的手机芯片的版本armv7.这对国内用户差不多就够了。
我们就制作armv7特别版吧。

把两个so文件拷贝到lib\armeabi-v7a目录。

现在看看哪里加载了这两个so文件。
搜索”proj”或”jsql”(包括引号部分),找到了文件My.2.smali.

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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
.class public Lo/My;
.super Ljava/lang/Object;
.source ""


# static fields
.field private static final _cb8a:[Ljava/lang/String;

.field private static final _efbda5:Ljava/lang/String;



# direct methods
.method static constructor <clinit>()V
.locals 3

.line 0
const-class v0, Lo/My;

invoke-virtual {v0}, Ljava/lang/Class;->getSimpleName()Ljava/lang/String;

move-result-object v0

sput-object v0, Lo/My;->_efbda5:Ljava/lang/String;

.line 28
const/4 v0, 0x3

new-array v0, v0, [Ljava/lang/String;

const-string v1, "database_sqlcipher"

const/4 v2, 0x0

aput-object v1, v0, v2

const-string v1, "sqlcipher_android"

const/4 v2, 0x1

aput-object v1, v0, v2

const-string v1, "stlport_shared"

const/4 v2, 0x2

aput-object v1, v0, v2

sput-object v0, Lo/My;->_cb8a:[Ljava/lang/String;

return-void
.end method

.method public constructor <init>()V
.locals 0

.line 0
invoke-direct {p0}, Ljava/lang/Object;-><init>()V

return-void
.end method


#检查需要的so温家你是否存在
.method public static _cb8a()Z
.locals 11

.line 0
new-instance v0, Ljava/io/File;

invoke-static {}, Lo/KI;->_d980()Landroid/content/ContextWrapper;

move-result-object v1

invoke-virtual {v1}, Landroid/content/Context;->getFilesDir()Ljava/io/File;

move-result-object v1

const-string v2, "_libraries.conf"

invoke-direct {v0, v1, v2}, Ljava/io/File;-><init>(Ljava/io/File;Ljava/lang/String;)V

.line 50
move-object v4, v0

invoke-virtual {v0}, Ljava/io/File;->exists()Z

move-result v0

if-eqz v0, :cond_0

invoke-virtual {v4}, Ljava/io/File;->length()J

move-result-wide v0

const-wide/16 v2, 0x0

cmp-long v0, v0, v2

if-nez v0, :cond_1

.line 51
:cond_0
sget-object v0, Lo/My;->_efbda5:Ljava/lang/String;

const-string v1, "areDataReady(), missing config file"

invoke-static {v0, v1}, Lo/_c994;->_cb8b(Ljava/lang/String;Ljava/lang/String;)V

.line 52
const/4 v0, 0x0

return v0

.line 57
:cond_1
:try_start_0
invoke-static {v4}, Lo/OJ;->_cabb(Ljava/io/File;)Ljava/lang/String;

move-result-object v0

.line 58
move-object v5, v0

if-eqz v0, :cond_2

invoke-virtual {v5}, Ljava/lang/String;->length()I

move-result v0

if-nez v0, :cond_3

.line 59
:cond_2
sget-object v0, Lo/My;->_efbda5:Ljava/lang/String;

const-string v1, "areDataReady(), empty content of config file"

invoke-static {v0, v1}, Lo/_c994;->_cb8b(Ljava/lang/String;Ljava/lang/String;)V
:try_end_0
.catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0

.line 60
const/4 v0, 0x0

return v0

.line 64
:cond_3
:try_start_1
invoke-static {v5}, Lo/OO;->_efbda5(Ljava/lang/String;)Lo/aac;

move-result-object v0

.line 65
move-object v5, v0

const-string v1, "version"

invoke-virtual {v0, v1}, Lo/aac;->get(Ljava/lang/Object;)Ljava/lang/Object;

move-result-object v0

invoke-static {v0}, Lo/Mn;->_cb8a(Ljava/lang/Object;)I

move-result v0

.line 66
move v6, v0

const/4 v1, 0x1

if-eq v0, v1, :cond_4

.line 67
sget-object v0, Lo/My;->_efbda5:Ljava/lang/String;

const-string v1, "areDataReady(), old version, require update"

invoke-static {v0, v1}, Lo/_c994;->_cb8b(Ljava/lang/String;Ljava/lang/String;)V
:try_end_1
.catch Ljava/lang/Exception; {:try_start_1 .. :try_end_1} :catch_0

.line 68
const/4 v0, 0x0

return v0

.line 72
:cond_4
:try_start_2
invoke-static {}, Lo/KI;->_cabf()I

move-result v0

if-eq v6, v0, :cond_5

.line 73
sget-object v0, Lo/My;->_efbda5:Ljava/lang/String;

const-string v1, "areDataReady(), invalid downloaded version, require update"

invoke-static {v0, v1}, Lo/_c994;->_cb8b(Ljava/lang/String;Ljava/lang/String;)V
:try_end_2
.catch Ljava/lang/Exception; {:try_start_2 .. :try_end_2} :catch_0

.line 74
const/4 v0, 0x0

return v0

.line 78
:cond_5
:try_start_3
const-string v0, "files"

invoke-virtual {v5, v0}, Lo/aac;->get(Ljava/lang/Object;)Ljava/lang/Object;

move-result-object v0

check-cast v0, Lo/ZZ;

move-object v5, v0

.line 79
const/4 v6, 0x0

invoke-virtual {v5}, Lo/ZZ;->size()I

move-result v7

:goto_0
if-ge v6, v7, :cond_a

.line 80
invoke-virtual {v5, v6}, Lo/ZZ;->get(I)Ljava/lang/Object;

move-result-object v0

check-cast v0, Lo/aac;

.line 81
move-object v8, v0

const-string v1, "type"

invoke-virtual {v0, v1}, Lo/aac;->get(Ljava/lang/Object;)Ljava/lang/Object;

move-result-object v0

invoke-static {v0}, Ljava/lang/String;->valueOf(Ljava/lang/Object;)Ljava/lang/String;

move-result-object v9

.line 82
const-string v0, "name"

invoke-virtual {v8, v0}, Lo/aac;->get(Ljava/lang/Object;)Ljava/lang/Object;

move-result-object v0

invoke-static {v0}, Ljava/lang/String;->valueOf(Ljava/lang/Object;)Ljava/lang/String;

move-result-object v10

.line 83
const-string v0, "hash"

invoke-virtual {v8, v0}, Lo/aac;->get(Ljava/lang/Object;)Ljava/lang/Object;

move-result-object v0

invoke-static {v0}, Ljava/lang/String;->valueOf(Ljava/lang/Object;)Ljava/lang/String;

move-result-object v8

.line 86
invoke-static {v9, v10}, Lo/My;->_efbda5(Ljava/lang/String;Ljava/lang/String;)Ljava/io/File;

move-result-object v0

.line 87
move-object v9, v0

if-eqz v0, :cond_6

invoke-virtual {v9}, Ljava/io/File;->exists()Z

move-result v0

if-nez v0, :cond_7

.line 88
:cond_6
sget-object v0, Lo/My;->_efbda5:Ljava/lang/String;

new-instance v1, Ljava/lang/StringBuilder;

const-string v2, "areDataReady(), file \'null\' or not exists, file:"

invoke-direct {v1, v2}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V

invoke-virtual {v1, v9}, Ljava/lang/StringBuilder;->append(Ljava/lang/Object;)Ljava/lang/StringBuilder;

move-result-object v1

invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

move-result-object v1

invoke-static {v0, v1}, Lo/_c994;->_cb8b(Ljava/lang/String;Ljava/lang/String;)V
:try_end_3
.catch Ljava/lang/Exception; {:try_start_3 .. :try_end_3} :catch_0

.line 89
const/4 v0, 0x0

return v0

.line 93
:cond_7
:try_start_4
invoke-virtual {v9}, Ljava/io/File;->getAbsolutePath()Ljava/lang/String;

move-result-object v0

invoke-static {v0}, Lo/OK;->_cb8e(Ljava/lang/String;)Ljava/lang/String;

move-result-object v0

.line 94
move-object v10, v0

if-eqz v0, :cond_8

invoke-virtual {v10, v8}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

move-result v0

if-nez v0, :cond_9

.line 95
:cond_8
sget-object v0, Lo/My;->_efbda5:Ljava/lang/String;

new-instance v1, Ljava/lang/StringBuilder;

const-string v2, "areDataReady(), invalid MD5, file:"

invoke-direct {v1, v2}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V

invoke-virtual {v1, v9}, Ljava/lang/StringBuilder;->append(Ljava/lang/Object;)Ljava/lang/StringBuilder;

move-result-object v1

const-string v2, ", hash:\'"

invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

move-result-object v1

invoke-virtual {v1, v8}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

move-result-object v1

const-string v2, "\', \'"

invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

move-result-object v1

invoke-virtual {v1, v10}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

move-result-object v1

const-string v2, "\'"

invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

move-result-object v1

invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

move-result-object v1

invoke-static {v0, v1}, Lo/_c994;->_cb8b(Ljava/lang/String;Ljava/lang/String;)V
:try_end_4
.catch Ljava/lang/Exception; {:try_start_4 .. :try_end_4} :catch_0

.line 97
const/4 v0, 0x0

return v0

.line 79
:cond_9
:try_start_5
add-int/lit8 v6, v6, 0x1

goto/16 :goto_0

.line 102
:cond_a
sget-object v0, Lo/My;->_efbda5:Ljava/lang/String;

const-string v1, "areDataReady(), everything ready"

invoke-static {v0, v1}, Lo/_c994;->_cb8b(Ljava/lang/String;Ljava/lang/String;)V
:try_end_5
.catch Ljava/lang/Exception; {:try_start_5 .. :try_end_5} :catch_0

.line 103
const/4 v0, 0x1

return v0

.line 104
:catch_0
move-exception v5

.line 105
sget-object v0, Lo/My;->_efbda5:Ljava/lang/String;

const-string v1, "areDataReady()"

invoke-static {v0, v1, v5}, Lo/_c994;->_cb8a(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Exception;)V

.line 106
invoke-static {v4}, Lo/OJ;->_e1909d(Ljava/io/File;)Z

.line 109
const/4 v0, 0x1

return v0
.end method

#加载libproj.so
.method public static _cb8b()V
.locals 3

.line 0
const-string v1, "proj"

const-string v0, "lib"

invoke-static {v0, v1}, Lo/My;->_efbda5(Ljava/lang/String;Ljava/lang/String;)Ljava/io/File;

move-result-object v0

move-object v2, v0

invoke-virtual {v0}, Ljava/io/File;->exists()Z

move-result v0

if-eqz v0, :cond_0

invoke-virtual {v2}, Ljava/io/File;->getAbsolutePath()Ljava/lang/String;

move-result-object v0

invoke-static {v0}, Ljava/lang/System;->load(Ljava/lang/String;)V

return-void

:cond_0
invoke-static {v1}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V

.line 116
return-void
.end method

#加载libjsqlite.so
.method public static _cb8e()V
.locals 3

.line 0
const-string v1, "jsqlite"

const-string v0, "lib"

invoke-static {v0, v1}, Lo/My;->_efbda5(Ljava/lang/String;Ljava/lang/String;)Ljava/io/File;

move-result-object v0

move-object v2, v0

invoke-virtual {v0}, Ljava/io/File;->exists()Z

move-result v0

if-eqz v0, :cond_0

invoke-virtual {v2}, Ljava/io/File;->getAbsolutePath()Ljava/lang/String;

move-result-object v0

invoke-static {v0}, Ljava/lang/System;->load(Ljava/lang/String;)V

return-void

:cond_0
invoke-static {v1}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V

.line 122
return-void
.end method

.method public static _efbda5()I
.locals 1

.line 0
const/4 v0, 0x1

return v0
.end method

.method private static _efbda5(Ljava/lang/String;Ljava/lang/String;)Ljava/io/File;
.locals 4

.line 0
const-string v0, "lib"

invoke-virtual {p0, v0}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

move-result v0

if-eqz v0, :cond_0

.line 137
new-instance v0, Ljava/io/File;

invoke-static {}, Lo/KI;->_d980()Landroid/content/ContextWrapper;

move-result-object v1

invoke-virtual {v1}, Landroid/content/Context;->getFilesDir()Ljava/io/File;

move-result-object v1

new-instance v2, Ljava/lang/StringBuilder;

const-string v3, "lib"

invoke-direct {v2, v3}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V

invoke-virtual {v2, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

move-result-object v2

const-string v3, ".so"

invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

move-result-object v2

invoke-virtual {v2}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

move-result-object v2

invoke-direct {v0, v1, v2}, Ljava/io/File;-><init>(Ljava/io/File;Ljava/lang/String;)V

return-object v0

.line 139
:cond_0
const-string v0, "file"

invoke-virtual {p0, v0}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

move-result v0

if-eqz v0, :cond_1

.line 140
new-instance v0, Ljava/io/File;

invoke-static {}, Lo/KI;->_d980()Landroid/content/ContextWrapper;

move-result-object v1

invoke-virtual {v1}, Landroid/content/Context;->getFilesDir()Ljava/io/File;

move-result-object v1

invoke-direct {v0, v1, p1}, Ljava/io/File;-><init>(Ljava/io/File;Ljava/lang/String;)V

return-object v0

.line 142
:cond_1
const/4 v0, 0x0

return-object v0
.end method

把两个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
#加载libproj.so
.method public static _cb8b()V
.locals 3

.line 0
const-string v1, "proj"

:cond_0
invoke-static {v1}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V

.line 116
return-void
.end method

#加载libjsqlite.so
.method public static _cb8e()V
.locals 3

.line 0
const-string v1, "jsqlite"

:cond_0
invoke-static {v1}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V

.line 122
return-void
.end method

现在修改.method public static _cb8a()Z函数内容为 :

1
2
3
4
5
.method public static _cb8a()Z
const/4 v0, 0x1

return v0
.end method

重新编译打包运行,还是提示需要下载。
搜索调用My._cb8a方法的地方。
找到下面几个文件

./com/asamm/locus/core/StartScreen$iF.3.smali: invoke-static {}, Lo/My;->_cb8a()Z
./com/asamm/locus/core/StartScreen$iF.3.smali: invoke-static {}, Lo/My;->_cb8a()Z
./o/_c4bd.smali: invoke-static {}, Lo/My;->_cb8a()Z
./o/_efbe83.smali: invoke-static {}, Lo/My;->_cb8a()Z
./o/My.2.smali: sput-object v0, Lo/My;->_cb8a:[Ljava/lang/String;

StartScreen$iF.3.smali像是和启动界面相关的。
我们来看一下:
找到下面这个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.method public _efbda5(ZZ)Z
.locals 1

.line 0
if-nez p1, :cond_0

invoke-static {}, Lo/My;->_cb8a()Z

move-result v0

if-nez v0, :cond_1

:cond_0
const/4 v0, 0x1

return v0

:cond_1
const/4 v0, 0x0

return v0
.end method

我们把它修改会始终返回false,

1
2
3
4
5
6
7
8
.method public _efbda5(ZZ)Z
.locals 1

const/4 v0, 0x0

return v0

.end method

再次打包,启动APK,发现不再提示需要下载数据了。

但是我们仍然没有成功,我们又遇到了别的问题。

下载数据终于想办法绕过去了,但是启动应用后,应用还是退出了,究竟是什么原因呢?

处理资源中的非ascii字符

现在启动应用,应用异常退出,抛出异常信息如下:

12-15 11:51:59.396: E/AndroidRuntime(32656): FATAL EXCEPTION: main
12-15
11:51:59.396: E/AndroidRuntime(32656): java.lang.RuntimeException:
Unable to start activity
ComponentInfo{menion.android.locus.pro/com.asamm.locus.basic.MainActivityBasic}:
android.view.InflateException: Binary XML file line #162: Error
inflating class o.ヌ
12-15 11:51:59.396: E/AndroidRuntime(32656): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2180)
12-15 11:51:59.396: E/AndroidRuntime(32656): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2230)
12-15 11:51:59.396: E/AndroidRuntime(32656): at android.app.ActivityThread.access$600(ActivityThread.java:141)
12-15 11:51:59.396: E/AndroidRuntime(32656): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1234)
12-15 11:51:59.396: E/AndroidRuntime(32656): at android.os.Handler.dispatchMessage(Handler.java:99)
12-15 11:51:59.396: E/AndroidRuntime(32656): at android.os.Looper.loop(Looper.java:137)
12-15 11:51:59.396: E/AndroidRuntime(32656): at android.app.ActivityThread.main(ActivityThread.java:5041)
12-15 11:51:59.396: E/AndroidRuntime(32656): at java.lang.reflect.Method.invokeNative(Native Method)
12-15 11:51:59.396: E/AndroidRuntime(32656): at java.lang.reflect.Method.invoke(Method.java:511)
12-15
11:51:59.396: E/AndroidRuntime(32656): at
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
12-15 11:51:59.396: E/AndroidRuntime(32656): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
12-15 11:51:59.396: E/AndroidRuntime(32656): at dalvik.system.NativeStart.main(Native Method)
12-15
11:51:59.396: E/AndroidRuntime(32656): Caused by:
android.view.InflateException: Binary XML file line #162: Error
inflating class o.ヌ
12-15 11:51:59.396: E/AndroidRuntime(32656): at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:698)
12-15 11:51:59.396: E/AndroidRuntime(32656): at android.view.LayoutInflater.rInflate(LayoutInflater.java:746)
12-15 11:51:59.396: E/AndroidRuntime(32656): at android.view.LayoutInflater.rInflate(LayoutInflater.java:749)
12-15 11:51:59.396: E/AndroidRuntime(32656): at android.view.LayoutInflater.rInflate(LayoutInflater.java:749)
12-15 11:51:59.396: E/AndroidRuntime(32656): at android.view.LayoutInflater.rInflate(LayoutInflater.java:749)
12-15 11:51:59.396: E/AndroidRuntime(32656): at android.view.LayoutInflater.inflate(LayoutInflater.java:489)
12-15 11:51:59.396: E/AndroidRuntime(32656): at android.view.LayoutInflater.inflate(LayoutInflater.java:396)
12-15 11:51:59.396: E/AndroidRuntime(32656): at android.view.LayoutInflater.inflate(LayoutInflater.java:352)
12-15 11:51:59.396: E/AndroidRuntime(32656): at android.view.View.inflate(View.java:16465)
12-15 11:51:59.396: E/AndroidRuntime(32656): at o._efbe83.onCreate(:284)
12-15 11:51:59.396: E/AndroidRuntime(32656): at android.app.Activity.performCreate(Activity.java:5104)
12-15 11:51:59.396: E/AndroidRuntime(32656): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1080)
12-15 11:51:59.396: E/AndroidRuntime(32656): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2144)
12-15 11:51:59.396: E/AndroidRuntime(32656): … 11 more
12-15
11:51:59.396: E/AndroidRuntime(32656): Caused by:
java.lang.ClassNotFoundException: Didn’t find class “o.ヌ” on path:
/data/app/menion.android.locus.pro-1.apk

12-15 11:51:59.396: E/AndroidRuntime(32656): at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:65)
12-15 11:51:59.396: E/AndroidRuntime(32656): at java.lang.ClassLoader.loadClass(ClassLoader.java:501)
12-15 11:51:59.396: E/AndroidRuntime(32656): at java.lang.ClassLoader.loadClass(ClassLoader.java:461)
12-15 11:51:59.396: E/AndroidRuntime(32656): at android.view.LayoutInflater.createView(LayoutInflater.java:552)
12-15 11:51:59.396: E/AndroidRuntime(32656): at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:687)
12-15 11:51:59.396: E/AndroidRuntime(32656): … 23 more

提示很明显找不到一个类:“o.ヌ”

这是因为资源中引用到了类名,而这个类名现在已经不存在了。
我们需要把资源中的类名替换为新的类名。
需要替换的文件有Androidmanifeste.xml和layout文件夹下的xml文件。

如何替换资源中的特殊字符,请参考我的博客:
处理资源文件中引用到了非ASCII字符的类名字符串–Android 逆向系列四
我已经提供了代码的参考实现:
AXMLStringTransformer.java
我们把资源文件处理后,重新打包,运行。
应用终于启动了,但是应用变成了免费版本。

现在应用变成了免费版本,不但有广告,还有很多有用的功能用不了,真着急。

解除功能限制

首先找到com/asamm/locus/utils/Native/Native.smali文件
替换isFullFeatured native方法为下面内容

1
2
3
4
5
.method public static isFullFeatured(Landroid/app/Application;)Z
.locals 1
const v0, 0x1
return v0
.end method

重新打包,启动,发现广告消失了,APK变为Pro版了。
但体验后发现有些功能,如天气仍然不能是用。
说明还有些功能限制没有解除.
Native.smali文件里有一个函数:

1
2
.method public static native performAction(Landroid/app/Application;Ljava/lang/Runnable;)V
.end method

这个native方法是执行一个线程方法。这个方法在执行操作前,会检测是不是全功能版,我们前面在smali(或者Java层面)把代码修改为全功能版。但是performAction调用的是native版本的isFullFeatured函数,native版本的函数是没有被修改的。
现在我们修改so库中的isFullFeatured函数。

查看Native.smali的static块函数,发现没有直接load 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
# direct methods
.method static constructor <clinit>()V
.locals 13

.line 0
const/16 v0, 0x8f

new-array v0, v0, [B

fill-array-data v0, :array_0

sput-object v0, Lcom/asamm/locus/utils/Native;->_cb8b:[B

#.......
# 为了节约篇幅,这里删除了大部分代码
#.......

:try_start_18
#删除释放到sd卡上的so文件
invoke-virtual {v9}, Ljava/io/File;->delete()Z
:try_end_18
.catch Ljava/lang/Exception; {:try_start_18 .. :try_end_18} :catch_1

return-void

.line 11
:catch_1
return-void

nop

……

该函数解密加密的so文件,,解密后释放到APK的数据目录。动态加载后,删除解密后的so文件。

在方法的最后几行,找到调用delete函数的地方,注释掉delete函数。
修改后的内容为:

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
# direct methods
.method static constructor <clinit>()V
.locals 13

.line 0
const/16 v0, 0x8f

new-array v0, v0, [B

fill-array-data v0, :array_0

sput-object v0, Lcom/asamm/locus/utils/Native;->_cb8b:[B

#.......
# 为了节约篇幅,这里删除了大部分代码
#.......

#:try_start_18
#删除释放到sd卡上的so文件
#invoke-virtual {v9}, Ljava/io/File;->delete()Z
#:try_end_18
#.catch Ljava/lang/Exception; {:try_start_18 .. :try_end_18} :catch_1

return-void

.line 11
:catch_1
return-void

nop

……

打包后重新运行程序,退出应用。
如果是用的是模拟器,可以用adb shell或dbms工具,把/data/data/menion.android.locus.pro目录下寻找一个隐藏文件,重命名为libmacore.so,然后拷贝出来,如果是设备,就需要root后才能读出来。

用支持arm指令的反编译程序(推荐IDA)找到isFullFeatured函数.
这是我用的工具的指令示意图

isFullFeatured函数的汇编代码

我们首先找到返回true的指令代码,查找出对应的指令代码。

我们选择直接短路函数。
arm函数的特征是,寄存器入栈,执行函数指令,寄存器出栈。返回值保存在r0寄存器。

入栈指令
我们修改后的函数指令如下:

1
2
3
push.w {r0, r1, r4, r5, r6, r7, r8, lr}
movs r0, #0x1
pop.w {r2, r3, r4, r5, r6, r7, r8, pc}

我们查出来movs r0, #0x1的指令字节码为:01 20 .见上图。
pop.w {r2, r3, r4, r5, r6, r7, r8, pc}的指令字节码为:BD E8 FC 81
要修改的代码位置为:0x00001cb4

我们用二进制编辑文件打开libmacore.so文件,找到位置0x00001cb4,把当前位置的内容替换为:
01 20 BD E8 FC 81

修改完毕后保存文件,然后替换lib/armeabi-v7a/libmacore.so文件。

现在我们只支持armv7a,可以把lib目录下的其他三个芯片色目录删除,减小最后输出包的大小。

重新打包,安装测试,现在那些原来不能用的功能,如天气,现在可以用了。

至此:Locus Pro Patch系列完成。

###写在最后

  1. 该系列文章是对自己所做工作的整理,为了方便其他爱好者DIY,不是原始的分析过程。所以步骤只是解释了怎么做,而没有提及步骤的发现过程。
  2. apk 压缩文件效率比较低。修改过程中可以把解包目录下的unknown目录中的文件拷贝到别的地方,然后用压缩工具添加。我在这样做后,打包时间从3分多钟降为不到2分钟。对频繁打包的同学,可考虑此方法。
  3. Patch因为修改短路了部分代码,可能会引起最后做成的apk部分功能不稳定,这可能在所难免。本人能力、财力都有限,不能为Patch给您带来的损失负责,测试练习前,请确认您为完全民事行为能力人。
  4. 应用中要下载的so文件是关于数据库操作,和数学投影变换的,所以在一定的版本更迭期间,功能应该会保持不变,所以可能可以重复利用。
  5. 如果有条件,请支持正版。向开发者反馈,请求提供中文化版本。
  6. 行文仓促,难免有错漏和词不达意的地方,阅读时,请包容。

最后祝您DIY过程中一切操作顺利。