Android第一代加壳方法,网上有很多文章,本文只是在前人基础上的测试和验证,重点在于动手实践。主要的成果验证了有效性以及将加壳程序工具化,现在只需要一个cmd命令就可以对指定的Apk文件加壳。
第一代加壳技术有三个项目,分别是:
(一)项目下载地址
点击下载项目
该项目中包含3个工程,其中,apkUnshell是壳程序,flashplayer程序是源程序,apkshell是加壳程序,因为笔者在android studio中创建和编译java程序屡次失败,apkshell是用eclipse开发的。
(二)简要解析
此程序的加壳和解壳主要代码来自于网上,作者是yuxin,本文只是简单修剪。
加壳程序:如上所述,功能是将源程序追加到壳程序classes.dex文件的末尾,然后对壳程序的classes.dex文件中的长度、crc校验和sha1校验字段重新计算。该模块有很多细节,比如文件长度的计算,crc校验的是那些字节,sha1的计算方式等,具体可以结合dex文件结构和源代码,详细分析之。
如下几篇详细的dex文件解析文章:
dex文件解析
dex文件解析
dex文件解析
源程序加解密是简单的异或操作,密钥是:
其主要的计算字段有classes.dex长度,sha1校验,crc校验,主要部分如下:
解壳程序:此模块是加壳技术的核心,其技术细节较为冷僻高深,所谓曲高和寡,这部分才是加壳技术的精华所在,奈何本人不学无术,才疏学浅,只能勉强看懂,但是知其然不知其所以然。我觉得,作者肯定花费了很多的心血,鄙人对原作者的创新行为充满敬意,深表钦佩。
解壳程序通过反射调用实现的,在RefInvoke类中封装了多个反射调用的方法。该功能主要有2个部分,一部分是在Application方法中,其功能简述如下:
1.释放出源程序apk。
2.获取当前android.app.ActivityThread类对象currentActivityThread,并在其中找到ArrayMap类型的对象成员mPackages,并在mPackages中找到当前包名的android.app.LoadedApk对象。
3.在上述android.app.ActivityThread实例中,找到ClassLoader类的实例成员mClassLoader。
4 通过DexClassLoader加载释放出来的源程序apk。
5 将上述mClassLoader替换为加载源程序的DexClassLoader。
第二部分功能在onCreate方法中,其主要工作如下:
1.加载资源。
2.获取壳程序AndroidManifest.xml中源程序的包名和Application。
3.执行android.app.ActivityThread类的currentActivityThread方法,获取当前的android.app.ActivityThread实例currentActivityThread。
4.获取currentActivityThread中的android.app.ActivityThread类实例成员mBoundApplication。
5.在上述mBoundApplication中获取android.app.ActivityThread$AppBindData类实例info。
6.将上述info实例中的android.app.LoadedApk类实例成员mApplication设置为null。
7.在currentActivityThread中获取android.app.ActivityThread类实例mInitialApplication。
8.在currentActivityThread中获取android.app.ActivityThread类对象mAllApplications,该对象是个ArrayList<Application>结构,并在次结构中删除mInitialApplication对象。
9.获取info对象中android.app.LoadedApk类成员对象mApplicationInfo。
10.在mBoundApplication对象中获取android.app.ActivityThread$AppBindData类成员对象appInfo。
11.appInfo和mApplicationInfo实例的className成员变量设置为源程序的包名。
12.执行android.app.LoadedApk类对象info中的成员方法makeApplicationinfo,创建一个Application类实例。
13.设置android.app.ActivityThread类实例currentActivityThread中的成员对象mInitialApplication的值为上一步创建的Application类实例。
14.获取android.app.ActivityThread类对象currentActivityThread中的成员对象mProviderMap,该对象是一个ArrayMap结构,其元素是android.app.ActivityThread$ProviderClientRecord类。
15.遍历该成员对象,并获取其成员对象mLocalProvider,该成员对象是android.content.ContentProvider类,设置该android.content.ContentProvider类的成员对象mContext值为上述创建的Application。
16.调用上述步骤所创建的Application类实例中的onCreate方法。
上述复杂功能,概括的讲就是,将壳程序执行环境和资源转换到源程序的执行环境。这部分我也有些细节搞不明白,只是照抄代码和一些简单枯燥的字面解释,所谓,知其一不知其二,一叶障目不见泰山,这部分懂就是懂,咱不懂也装不出来。
其主要代码如下:
再次啰嗦一下,这段代码我能看懂,也基本上能理解,但是不知道为什么这样做,知其然而不知其所以然。
源程序:本人自己写的小程序模块,跟此项目关系不大。
(三)加壳的注意事项
壳程序中的AndroidManifest.xml文件可以没有自己的内容,而全部复制来源于源程序中的AndroidManifest.xml程序,但是其中要添加如下条目:
该字段用于壳程序加载原程序。
壳程序的Androidmanifest.xml文件中,所有Activity、Service 、ContentProvider、Broadcast、Application等,在声明中,必须全部都用源程序中的包名+类名,而不能用如同.MainActivity这样的缩写格式。同时,也应注意到,壳程序中的.MyApplication和源程序中的.MyApplication虽然名字相同,但不是用一个Application,修改后没有影响,这从日志输出中可以看出。
壳程序包名最好不要跟源程序相同,在测试中发现,如果相同会导致系统卡死。
加壳后的程序,在使用autosign重新签名以前,必须删除原来的META-INF文件夹,否则,会因为autosign的签名错误,导致签名后的apk包安装不上。
壳程序最好不要有启动界面,否则,程序运行时,首先会跳转壳的界面。因此,所有的Activity不要有如下属性:
6.壳进程的so文件可以和源程序native文件同名,这一点并不冲突。
(四)加壳自动化
用eclipse下编译加壳程序apkshell.jar,编写.bat批处理文件,构建源程序并拷贝到apkshell-cmd目录下,执行run.bat程序后,自动调用apkshell.jar将源程序加密后塞进壳程序,调用autosign程序对打包后的壳程序签名,签名后的程序默认为mytest.apk。
该模块是本文中我的主要工作之一。
到此,各位看官可以直接使用此apk安装测试,或者用于其他目的。
批处理文件内容如下:
(五)加壳方案的优劣
优点:经过本人多次测试,该加壳方式简单、快捷,易于移植,兼容性比较好,加壳后,源程序未发现因加壳引起的其他异常。
此处要修改一下,当调用第三方定位软件,比如腾讯地图定位中的如下代码:
以及高德地图的如下接口时:
会导致程序崩溃。原因未知。
缺点:此种加壳方案,会在安装程序主目录下,生成两个文件夹:my_payload_odex和my_payload_lib,其中会包含源代码生成的apk文件以及源码生成的so等native文件,当攻击者拿到apk和这些原生的文件之后,也就意味着加壳方案的失败。
综合来看此种方案虽然无法抵御例如xposed等插桩调试,但是依然会增加破解者的调试难度。
/
*
*
*
修改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));
}
/
*
*
*
修改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));
}
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;
}
}
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);
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2024-2-2 11:45
被satadrover编辑
,原因: