Android第一代加壳测试,网上有很多文章,本文只是在前人基础上测试和验证。因此,本文的重点在于动手和实践。
第一代加壳技术有三个项目,分别是:
(一)项目下载地址
该项目中包含3个工程,其中,apkUnshell是壳程序,flashplayer程序是源程序,apkshell是加壳程序,因为笔者在android studio中创建和编译java程序屡次失败,apkshell是用eclipse开发的。
(二)简要解析
此程序的加壳和解壳主要代码来自于网上,作者是yuxin,本文只是简单修剪。
加壳程序:如上所述,功能是将源程序追加到壳程序classes.dex文件的末尾,然后对壳程序的classes.dex文件中的长度、crc校验和sha1校验字段重新计算。该模块有很多细节,比如文件长度的计算,crc校验的是那些字节,sha1的计算方式等,具体可以结合dex文件结构和源代码,详细分析之。
如下几篇详细的dex文件解析文章:
源程序加解密是简单的异或操作,密钥是:
其主要的计算字段有classes.dex长度,sha1校验,crc校验,主要部分如下:
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
|
/
*
*
*
修改dex头 sha1值
*
@param dexBytes
*
@throws NoSuchAlgorithmException
*
/
private static void fixSHA1Header(byte[] dexBytes) throws NoSuchAlgorithmException {
MessageDigest md
=
MessageDigest.getInstance(
"SHA-1"
);
md.update(dexBytes,
32
, dexBytes.length
-
32
);
/
/
从
32
为到结束计算sha
-
1
byte[] newdt
=
md.digest();
System.arraycopy(newdt,
0
, dexBytes,
12
,
20
);
/
/
修改sha
-
1
值(
12
-
31
)
/
/
输出sha
-
1
值,可有可无
String hexstr
=
"";
for
(
int
i
=
0
; i < newdt.length; i
+
+
) {
/
/
Integer.toString(
int
i,
int
radix)将整数i(十进制)转化为radix进制的整数
hexstr
+
=
Integer.toString((newdt[i] &
0xff
)
+
0x100
,
16
).substring(
1
);
}
System.out.println(
"new dex sha-1:"
+
hexstr);
}
/
*
*
*
修改dex头 file_size值
*
@param dexBytes
*
/
private static void fixFileSizeHeader(byte[] dexBytes) {
/
/
新文件长度
byte[] newfs
=
intToByte(dexBytes.length);
byte[] refs
=
new byte[
4
];
/
/
高位在前 低位在前掉个个
for
(
int
i
=
0
; i <
4
; i
+
+
) {
refs[i]
=
newfs[newfs.length
-
1
-
i];
}
/
/
修改(
32
-
35
)
System.arraycopy(refs,
0
, dexBytes,
32
,
4
);
System.out.println(
"new dex file size:"
+
Integer.toHexString(dexBytes.length));
}
/
*
*
*
修改dex头,CheckSum 校验码
*
@param dexBytes
*
/
private static void fixCheckSumHeader(byte[] dexBytes) {
Adler32 adler
=
new Adler32();
adler.update(dexBytes,
12
, dexBytes.length
-
12
);
/
/
从
12
到文件末尾计算校验码
int
value
=
(
int
)adler.getValue();
byte[] newcs
=
intToByte(value);
/
/
高位在前,低位在前掉个个
byte[] recs
=
new byte[
4
];
for
(
int
i
=
0
; i <
4
; i
+
+
) {
recs[i]
=
newcs[newcs.length
-
1
-
i];
}
/
/
效验码赋值(
8
-
11
)
System.arraycopy(recs,
0
, dexBytes,
8
,
4
);
System.out.println(
"new dex checksum:"
+
Integer.toHexString(value));
}
|
解壳程序:此模块是加壳技术的核心。主要功能有3个,一个是在Application中,释放出源程序的apk,第二个是使用android.app.LoadedApk类中的mClassLoader方法加载apk,第三个是在onCreate方法中,进一步的将执行环境和资源转换到源程序的环境,然后加载执行。这部分我也有很多细节不太明白,只是照抄代码,如果有哪位读者能弄明白这里面的细节,请私信我,我将会给每行代码添加注释。
其主要代码如下:
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
|
package com.apkUnshell;
import
android.app.Application;
import
android.app.Instrumentation;
import
android.content.Context;
import
android.content.pm.ApplicationInfo;
import
android.content.pm.PackageManager;
import
android.content.res.AssetManager;
import
android.content.res.Resources;
import
android.os.Bundle;
import
android.util.ArrayMap;
import
android.util.Log;
import
java.io.BufferedInputStream;
import
java.io.ByteArrayInputStream;
import
java.io.ByteArrayOutputStream;
import
java.io.DataInputStream;
import
java.io.
File
;
import
java.io.FileInputStream;
import
java.io.FileOutputStream;
import
java.io.IOException;
import
java.lang.ref.WeakReference;
import
java.lang.reflect.Method;
import
java.util.ArrayList;
import
java.util.Iterator;
import
java.util.
zip
.ZipEntry;
import
java.util.
zip
.ZipInputStream;
import
dalvik.system.DexClassLoader;
/
*
*
*
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
*
Copyright (c)
2017
yuxin
All
rights reserved.
*
Packname com.jju.yuxin.reforceapk
*
Created by yuxin.
*
Created time
2017
/
6
/
18
0018
下午
5
:
03.
*
Version
1.0
;
*
Describe :
*
History:
*
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
*
/
/
/
com.google.android.apps.plus
/
/
com.adobe.flashplayer
/
/
com.loader
/
/
com.setup.loader
/
/
Applicaiton做为整个应用的上下文,会被系统第一时间调用,这也是应用开发者程序代码的第一执行点
public
class
MyApplication extends Application{
private static String DEXFILENAME
=
"update.apk"
;
private static final String appkey
=
"APPLICATION_CLASS_NAME"
;
private static String cryptKey
=
"fuck all the android crackers"
;
public static String PAYLOAD_ODEX
=
"my_payload_odex"
;
public static String PAYLOAD_LIB
=
"my_payload_lib"
;
private static final String TAG
=
MyApplication.
class
.getSimpleName();
private String srcDexFilePath
=
"";
private String odexPath
=
"";
private String libPath
=
"";
private static String gIPstr
=
"";
private static String gUserNameStr
=
"";
private Context context
=
null;
/
/
以下是加载资源
protected AssetManager mAssetManager
=
null;
protected Resources mResources
=
null;
protected Resources.Theme mTheme
=
null;
/
/
why run
2
times?
@SuppressWarnings
(
"rawtypes"
)
@Override
protected void attachBaseContext(Context base) {
super
.attachBaseContext(base);
/
/
getApplicationContext() 返回应用的上下文,生命周期是整个应用,应用摧毁它才摧毁
/
/
Activity.this的context 返回当前activity的上下文,属于activity ,activity 摧毁他就摧毁
/
/
getBaseContext() 返回由构造函数指定或setBaseContext()设置的上下文
/
/
this.getApplicationContext()取的是这个应 用程序的Context,Activity.this取的是这个Activity的Context,
/
/
这两者的生命周期是不同 的,前者的生命周期是整个应用,后者的生命周期只是它所在的Activity。
context
=
base;
Log.e(TAG,
"attachBaseContext"
);
try
{
/
/
/
data
/
user
/
0
/
com.apkunshell
/
app_payload_odex
File
odexPathFile
=
this.getDir(PAYLOAD_ODEX, MODE_PRIVATE);
/
/
/
data
/
user
/
0
/
com.apkunshell
/
app_payload_libs
File
libsPathFile
=
this.getDir(PAYLOAD_LIB, MODE_PRIVATE);
/
/
用于存放源apk释放出来的dex
odexPath
=
odexPathFile.getAbsolutePath();
/
/
用于存放源Apk用到的so文件
libPath
=
libsPathFile.getAbsolutePath();
/
/
用于存放解密后的apk
srcDexFilePath
=
odexPathFile.getAbsolutePath()
+
"/"
+
DEXFILENAME;
/
/
String apppath
=
this.getFilesDir().getParent()
+
"/"
;
/
/
InputStream
is
=
this.getAssets().
open
(APKFILENAME);
/
/
int
size
=
is
.available();
/
/
byte []
buffer
=
new byte[size];
/
/
is
.read(
buffer
);
/
/
is
.close();
/
/
OutputStream os
=
new FileOutputStream(apppath
+
APKFILENAME);
/
/
os.write(
buffer
);
/
/
os.close();
File
srcDexFile
=
new
File
(srcDexFilePath);
/
/
第一次加载
if
(srcDexFile.exists()
=
=
false)
{
Log.e(TAG,
"beFirstLoading"
);
srcDexFile.createNewFile();
/
/
拿到dex文件
byte[] dexdata
=
this.readDexFileFromApk();
/
/
取出源APK解密后放置在
/
payload.apk,及其so文件放置在payload_lib
/
下
this.splitPayLoadFromDex(dexdata);
}
/
/
配置动态加载环境
/
/
反射获取主线程对象,并从中获取所有已加载的package信息,并中找到当前的LoadApk对象的弱引用
/
/
/
/
配置动态加载环境 获取主线程对象 http:
/
/
blog.csdn.net
/
myarrow
/
article
/
details
/
14223493
Object
currentActivityThread
=
RefInvoke.invokeStaticMethod(
"android.app.ActivityThread"
,
"currentActivityThread"
,new Class[] {}, new
Object
[] {});
ArrayMap mPackages
=
(ArrayMap) RefInvoke.getFieldOjbect(
"android.app.ActivityThread"
,
currentActivityThread,
"mPackages"
);
String packageName
=
this.getPackageName();
WeakReference wr
=
(WeakReference) mPackages.get(packageName);
/
/
/
创建被加壳apk的DexClassLoader对象 加载apk内的类和本地代码(c
/
c
+
+
代码)
/
/
创建一个新的DexClassLoader用于加载源Apk,传入apk路径,dex释放路径,so路径,及父节点的DexClassLoader使其遵循双亲委托模型
ClassLoader fathercl
=
(ClassLoader) RefInvoke.getFieldOjbect(
"android.app.LoadedApk"
, wr.get(),
"mClassLoader"
);
DexClassLoader dLoader
=
new DexClassLoader(srcDexFilePath, odexPath,libPath, fathercl);
/
/
getClassLoader()等同于 (ClassLoader) RefInvoke.getFieldOjbect(),但是为了替换掉父节点我们需要通过反射来获取并修改其值
/
/
将父节点DexClassLoader替换
/
/
/
/
把当前进程的DexClassLoader 设置成了被加壳apk的DexClassLoader
RefInvoke.setFieldOjbect(
"android.app.LoadedApk"
,
"mClassLoader"
,wr.get(), dLoader);
/
/
Object
actObj
=
dLoader.loadClass(LOADCLASSNAME);
/
/
Log.e(TAG,
"get class object:"
+
actObj);
} catch (Exception e) {
Log.e(TAG,
"error:"
+
Log.getStackTraceString(e));
e.printStackTrace();
}
}
/
/
java.lang.RuntimeException:
/
/
Unable to create application com.loader.sRelease: java.lang.NullPointerException:
/
/
expected receiver of
type
android.content.ContentProvider, but got null
/
/
at com.loader.sRefInvoke.setFieldOjbect(sRefInvoke.java:
178
)
/
/
why run
2
times?
@SuppressWarnings
(
"rawtypes"
)
public void onCreate() {
try
{
Log.e(TAG,
"onCreate"
);
Log.e(TAG,
"Application:"
+
context
+
",BaseContext:"
+
getBaseContext()
+
",ApplicationContext:"
+
getApplicationContext()
+
",Activity:"
+
this);
if
(context
=
=
null){
context
=
this;
if
(context
=
=
null){
context
=
Utils.getContext();
}
}
Utils.setValue(context,
"paramConfig.json"
,
"username"
,gUserNameStr);
Utils.setValue(context,
"paramConfig.json"
,
"ip"
,gIPstr);
/
/
加载源apk资源
loadResources(srcDexFilePath);
/
/
获取配置在清单文件的源Apk的Application路径
String appClassName
=
null;
try
{
ApplicationInfo ai
=
this.getPackageManager().getApplicationInfo(this.getPackageName(),PackageManager.GET_META_DATA);
Bundle bundle
=
ai.metaData;
if
(bundle !
=
null && bundle.containsKey(appkey)) {
appClassName
=
bundle.getString(appkey);
/
/
className 是配置在xml文件中的
}
else
{
Log.e(TAG,
"not found application class name in bundle"
);
return
;
}
} catch (Exception e) {
Log.e(TAG,
"error:"
+
Log.getStackTraceString(e));
e.printStackTrace();
return
;
}
/
/
获取当前壳Apk的ApplicationInfo
Object
currentActivityThread
=
RefInvoke.invokeStaticMethod(
"android.app.ActivityThread"
,
"currentActivityThread"
,new Class[] {}, new
Object
[] {});
Object
mBoundApplication
=
RefInvoke.getFieldOjbect(
"android.app.ActivityThread"
,
currentActivityThread,
"mBoundApplication"
);
Object
loadedApkInfo
=
RefInvoke.getFieldOjbect(
"android.app.ActivityThread$AppBindData"
,mBoundApplication,
"info"
);
/
/
将LoadedApk中的ApplicationInfo设置为null
RefInvoke.setFieldOjbect(
"android.app.LoadedApk"
,
"mApplication"
,loadedApkInfo, null);
/
/
获取currentActivityThread中注册的Application
Object
oldApplication
=
RefInvoke.getFieldOjbect(
"android.app.ActivityThread"
,
currentActivityThread,
"mInitialApplication"
);
/
/
获取ActivityThread中所有已注册的Application,并将当前壳Apk的Application从中移除
@SuppressWarnings
(
"unchecked"
)
ArrayList<Application> mAllApplications
=
(ArrayList<Application>) RefInvoke.getFieldOjbect(
"android.app.ActivityThread"
,
currentActivityThread,
"mAllApplications"
);
mAllApplications.remove(oldApplication);
ApplicationInfo appinfo_In_LoadedApk
=
(ApplicationInfo) RefInvoke.getFieldOjbect(
"android.app.LoadedApk"
, loadedApkInfo,
"mApplicationInfo"
);
ApplicationInfo appinfo_In_AppBindData
=
(ApplicationInfo) RefInvoke
.getFieldOjbect(
"android.app.ActivityThread$AppBindData"
,mBoundApplication,
"appInfo"
);
/
/
替换原来的Application
appinfo_In_LoadedApk.className
=
appClassName;
appinfo_In_AppBindData.className
=
appClassName;
/
/
注册Application
Application app
=
(Application) RefInvoke.invokeMethod(
"android.app.LoadedApk"
,
"makeApplication"
,
loadedApkInfo,new Class[] { boolean.
class
, Instrumentation.
class
},new
Object
[] { false, null });
/
/
替换ActivityThread中的Application
RefInvoke.setFieldOjbect(
"android.app.ActivityThread"
,
"mInitialApplication"
, currentActivityThread, app);
ArrayMap mProviderMap
=
(ArrayMap) RefInvoke.getFieldOjbect(
"android.app.ActivityThread"
, currentActivityThread,
"mProviderMap"
);
Iterator it
=
mProviderMap.values().iterator();
while
(it.hasNext()) {
Object
providerClientRecord
=
it.
next
();
Object
localProvider
=
RefInvoke.getFieldOjbect(
"android.app.ActivityThread$ProviderClientRecord"
, providerClientRecord,
"mLocalProvider"
);
RefInvoke.setFieldOjbect(
"android.content.ContentProvider"
,
"mContext"
, localProvider, app);
}
Log.e(TAG,
"app:"
+
app);
app.onCreate();
} catch (Exception e) {
e.printStackTrace();
}
}
private void splitPayLoadFromDex(byte[] shelldexdata) throws IOException {
/
/
取被加壳apk的长度
int
sdlen
=
shelldexdata.length;
byte[] bytedexlen
=
new byte[
4
];
System.arraycopy(shelldexdata, sdlen
-
4
, bytedexlen,
0
,
4
);
ByteArrayInputStream bais
=
new ByteArrayInputStream(bytedexlen);
DataInputStream dis
=
new DataInputStream(bais);
int
readInt
=
dis.readInt();
Log.d(TAG,
"Integer.toHexString(readInt):"
+
Integer.toHexString(readInt));
/
/
取出apk
byte[] encryptdata
=
new byte[readInt];
System.arraycopy(shelldexdata, sdlen
-
4
-
readInt, encryptdata,
0
, readInt);
/
/
对源程序Apk进行解密
byte[] flatdata
=
xorcrypt(encryptdata);
int
offset
=
0
;
byte [] byteunamelen
=
new byte[
4
];
System.arraycopy(flatdata, offset, byteunamelen,
0
,
4
);
offset
+
=
4
;
int
unamelen
=
Utils.bytesToInt(byteunamelen);
byte[] username
=
new byte[unamelen];
System.arraycopy(flatdata , offset, username,
0
, unamelen);
offset
+
=
unamelen;
gUserNameStr
=
new String(username);
byte [] byteiplen
=
new byte[
4
];
System.arraycopy(flatdata, offset, byteiplen,
0
,
4
);
offset
+
=
4
;
int
iplen
=
Utils.bytesToInt(byteiplen);
byte[] ip
=
new byte[iplen];
System.arraycopy(flatdata , offset, ip,
0
, iplen);
offset
+
=
iplen;
gIPstr
=
new String(ip);
/
/
写入源apk文件
File
file
=
new
File
(srcDexFilePath);
try
{
FileOutputStream localFileOutputStream
=
new FileOutputStream(
file
);
localFileOutputStream.write(flatdata,offset,readInt
-
offset);
localFileOutputStream.close();
} catch (IOException localIOException) {
throw new RuntimeException(localIOException);
}
/
/
分析源apk文件
ZipInputStream zis
=
new ZipInputStream(new BufferedInputStream(new FileInputStream(
file
)));
while
(true) {
ZipEntry ze
=
zis.getNextEntry();
if
(ze
=
=
null) {
break
;
}
/
/
依次取出被加壳apk用到的so文件,放到 libPath中(data
/
data
/
包名
/
payload_lib)
String zfn
=
ze.getName();
if
(zfn.startsWith(
"lib/"
) && zfn.endsWith(
".so"
)) {
File
sofile
=
new
File
(libPath
+
zfn.substring(zfn.lastIndexOf(
'/'
)));
sofile.createNewFile();
FileOutputStream fos
=
new FileOutputStream(sofile);
byte[] readbuf
=
new byte[
0x4000
];
while
(true) {
int
readlen
=
zis.read(readbuf);
if
(readlen
=
=
-
1
){
break
;
}
fos.write(readbuf,
0
, readlen);
}
fos.flush();
fos.close();
}
zis.closeEntry();
}
zis.close();
}
/
*
*
*
拿到自己apk文件中的dex文件
*
@
return
*
@throws IOException
*
/
private byte[] readDexFileFromApk() throws IOException {
ByteArrayOutputStream dexbaos
=
new ByteArrayOutputStream();
/
/
getApplicationInfo().sourceDir
=
=
/
data
/
user
/
0
/
com.adobe.flashplayer
/
base.apk
/
/
BufferedInputStream会将该输入流数据分批读取,每次读取一部分到缓冲中;操作完缓冲中的这部分数据之后,再从输入流中读取下一部分的数据
/
/
无其他用途
/
/
ZipInputStream zis
=
new ZipInputStream(new FileInputStream(this.getApplicationInfo().sourceDir));
ZipInputStream zis
=
new ZipInputStream(new BufferedInputStream(new FileInputStream(this.getApplicationInfo().sourceDir)));
while
(true) {
ZipEntry ze
=
zis.getNextEntry();
if
(ze
=
=
null) {
break
;
}
/
/
拿到dex文件
if
(ze.getName().equals(
"classes.dex"
)) {
byte[] readbuf
=
new byte[
0x10000
];
while
(true) {
int
readlen
=
zis.read(readbuf);
if
(readlen
=
=
-
1
){
zis.closeEntry();
break
;
}
dexbaos.write(readbuf,
0
, readlen);
}
zis.closeEntry();
break
;
}
else
{
zis.closeEntry();
}
}
zis.close();
return
dexbaos.toByteArray();
}
private static byte[] xorcrypt(byte[] srcdata){
byte[] key
=
cryptKey.getBytes();
int
keylen
=
cryptKey.length();
for
(
int
i
=
0
,j
=
0
; i<srcdata.length; i
+
+
){
srcdata[i]
=
(byte)(key[j] ^ srcdata[i]);
j
+
+
;
if
(j >
=
keylen){
j
=
0
;
}
}
return
srcdata;
}
protected void loadResources(String srcApkPath) {
/
/
创建一个AssetManager放置源apk的资源
try
{
AssetManager assetManager
=
AssetManager.
class
.newInstance();
Method addAssetPath
=
assetManager.getClass().getMethod(
"addAssetPath"
, String.
class
);
addAssetPath.invoke(assetManager, srcApkPath);
mAssetManager
=
assetManager;
} catch (Exception e) {
Log.i(TAG,
"inject:loadResource error:"
+
Log.getStackTraceString(e));
e.printStackTrace();
}
Resources superRes
=
super
.getResources();
superRes.getDisplayMetrics();
superRes.getConfiguration();
mResources
=
new Resources(mAssetManager, superRes.getDisplayMetrics(),superRes.getConfiguration());
mTheme
=
mResources.newTheme();
mTheme.setTo(
super
.getTheme());
}
@Override
public AssetManager getAssets() {
return
mAssetManager
=
=
null ?
super
.getAssets() : mAssetManager;
}
@Override
public Resources getResources() {
return
mResources
=
=
null ?
super
.getResources() : mResources;
}
@Override
public Resources.Theme getTheme() {
return
mTheme
=
=
null ?
super
.getTheme() : mTheme;
}
}
|
源程序:本人自己写的小程序模块,跟此项目关系不大。
(三)加壳的注意事项
壳程序中的AndroidManifest.xml文件可以没有自己的内容,而全部复制来源于源程序中的AndroidManifest.xml程序,但是其中要添加如下条目:
1
2
3
|
<meta
-
data
android:name
=
"APPLICATION_CLASS_NAME"
android:value
=
"com.adobe.flashplayer.MyApplication"
/
>
|
该字段用于壳程序加载原程序。
壳程序的Androidmanifest.xml文件中,所有Activity、Service 、ContentProvider、Broadcast、Application等,在声明中,全部都必须用源程序中的全类名,而不能用如同.MainActivity这样的缩写格式。同时,也应注意到,壳程序中的.MyApplication和源程序中的.MyApplication虽然名字相同,但不是用一个Application,修改后没有影响,这从日志输出中可以看出。
壳程序最好不要有启动界面,否则,程序运行时,首先会跳转壳的界面。因此,所有的Activity不要有如下属性:
1
2
3
4
|
<intent
-
filter
>
<action android:name
=
"android.intent.action.MAIN"
/
>
<category android:name
=
"android.intent.category.LAUNCHER"
/
>
<
/
intent
-
filter
>
|
(四)加壳自动化
用eclipse下编译加壳程序apkshell.jar,编写.bat批处理文件,构建源程序并拷贝到apkshell-cmd目录下,执行run.bat程序后,自动调用apkshell.jar将源程序加密后塞进壳程序,调用autosign程序对打包后的壳程序签名,签名后的程序默认为mytest.apk。
该模块是本文中我的主要工作之一。
到此,各位看官可以直接使用此apk安装测试,或者用于其他目的。
批处理文件内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
set
path
=
".\"
rd
/
q
/
s .\apkunshell
del
.\apkUnshell.apk_new.apk
del
.\autosign\update.apk
del
.\autosign\update_signed.apk
java
-
jar apkshell.jar .
/
apkunshell.apk .
/
app
-
release.apk .
/
apkunshell jy
47.101
.
204.4
copy .\apkunshell.apk_new.apk .\autosign\update.apk
java
-
jar .
/
autosign
/
signapk.jar .
/
autosign
/
testkey.x509.pem .
/
autosign
/
testkey.pk8 .
/
autosign
/
update.apk .
/
autosign
/
update_signed.apk
copy .\autosign\update_signed.apk .\mytest.apk
del
.\apkUnshell.apk_new.apk
del
.\autosign\update.apk
del
.\autosign\update_signed.apk
rd
/
q
/
s .\apkunshell
pause
|
(五)加壳方案的优劣
优点:经过本人多次测试,该加壳方式简单、快捷,易于移植,兼容性比较好,加壳后,源程序未发现因加壳引起的其他异常。
缺点:此种加壳方案,会在安装程序主目录下,生成两个文件夹:my_payload_odex和my_payload_lib,其中会包含源程序编译生成的apk文件,当攻击者拿到apk后,也就意味着加壳方案的失败。综合来看此种方案虽然无法抵御例如xposed等插桩调试,但是依然会增加破解者的调试难度。
更多【Android第一代加壳的验证和测试】相关视频教程:www.yxfzedu.com