├── README.md
├── demo.keystore
├── images
├── wx.jpg
└── zfb.jpg
├── requests.txt
├── shellApplicationSourceCode
├── AndroidManifest.xml
├── java
│ └── cn
│ │ └── yongye
│ │ └── stub
│ │ ├── MainActivity.java
│ │ ├── R.java
│ │ ├── StubApp.java
│ │ └── common
│ │ ├── Config.java
│ │ ├── FileUtils.java
│ │ └── RefInvoke.java
└── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ └── ic_launcher_background.xml
│ ├── layout
│ └── activity_main.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── sheller.py
└── tools
├── android.jar
├── apksigner.jar
├── apktool_2.5.0.jar
├── apktool_2.9.3.jar
└── dx.jar
/README.md:
--------------------------------------------------------------------------------
1 | # 简介
2 |
3 | Java层DEX一键加固脚本
4 |
5 | # 使用说明
6 |
7 | ```shell
8 | python -f xxx.apk
9 | ```
10 |
11 | # 加固原理
12 |
13 | 准备一个壳DEX文件(源码位置:shellApplicationSourceCode),将原APK的DEX文件加密保存到壳DEX尾部,然后将原APK文件中的原DEX文件替换为壳DEX,并修改原APK文件里AndroidManifest.xml的applicationandroid:name字段,实现从壳DEX启动。
14 |
15 | 解密和加载原DEX的任务交给壳DEX,这样就实现了**APK文件防编译保护**
16 |
17 | # 一键加固脚本实现步骤
18 |
19 | 1. 准备原DEX加密算法以及隐藏位置(壳DEX尾部)
20 |
21 | ```python
22 | """
23 | 1. 第一步:确定加密算法
24 | """
25 | inKey = 0xFF
26 | print("[*] 确定加密解密算法,异或: {}".format(str(inKey)))
27 | ```
28 |
29 | 2. 生成壳DEX。(壳Application动态加载原application中需要原application的name字段)
30 |
31 | ```python
32 | """
33 | 2. 第二步:准备好壳App
34 | """
35 | # 反编译原apk
36 | decompAPK(fp)
37 | # print("[*] 反编译原的apk文件{}完成".format(fp))
38 | # 获取Applicaiton name并保存到壳App源码中
39 | stSrcDexAppName = getAppName(fp)
40 | # print("[*] 获取原apk文件的Application Android:name=\"{}\" 完成".format(stSrcDexAppName))
41 | save_appName(stSrcDexAppName)
42 | # print("[*] 保存原apk文件的Application Android:name=\"{}\" 到壳App源码的配置文件完成".format(stSrcDexAppName))
43 | # 编译出壳DEX
44 | compileShellDex()
45 | print("[*] 壳App的class字节码文件编译为:shell.dex完成")
46 | ```
47 |
48 | 3. 修改原APK文件中的AndroidManifest.xml文件的applicationandroid:name字段,实现从壳application启动
49 |
50 | ```python
51 | """
52 | 3. 第三步:修改原apk AndroidManifest.xml文件中的Application name字段为壳的Application name字段
53 | """
54 | # 替换壳Applicaiton name到原apk的AndroidManifest.xml内
55 | replaceTag(fp, "cn.yongye.stub.StubApp")
56 | print("[*] 原apk文件AndroidManifest.xml中application的name字段替换为壳application name字段完成")
57 | ```
58 |
59 | 4. 加密原DEX到壳DEX尾部并将壳DEX替换到原APK中
60 |
61 | ```python
62 | """
63 | 4. 替换原apk中的DEX文件为壳DEX
64 | """
65 | replaceSDexToShellDex(os.path.join(stCurrentPt, "result.apk"))
66 | print("[*] 壳DEX替换原apk包内的DEX文件完成")
67 | ```
68 |
69 | 5. 自动签名
70 |
71 | ```python
72 | """
73 | 5. apk签名
74 | """
75 | signApk(os.path.join(stCurrentPt, "result.apk"), os.path.join(stCurrentPt, "demo.keystore"))
76 | print("[*] 签名完成,生成apk文件: {}".format(os.path.join(stCurrentPt, "result.apk")))
77 | ```
78 |
79 | # 问题
80 |
81 | 【1】**libpng error: Not a PNG file**:apktool.jar编译smali项目时,如果出现png结尾的GIF文件时,会编译失败,这里我的解决方法时将GIF强行转换成PNG,解决问题。
82 |
83 | 中途解决该问题时,曾尝试使用[AXMLEditor](https://github.com/fourbrother/AXMLEditor)修改二进制Androidmanifest.xml的开源工具直接修改,然后想绕过编译资源步骤,实际不能成功,因为更改过后的application name字段在编译资源过程中,会被记录下来,而直接修改导致没有被记录,android系统是识别不到修改后的这个字段值
84 |
85 | # 定制版内容(付费)
86 |
87 | [x] 多DEX加密
88 |
89 | [x] APK包伪加密
90 |
91 | [x] 部分资源加密
92 |
93 | [x] 包名随机
94 |
95 | [x] 高级加密算法
96 |
97 | [x] ...
98 |
99 |
100 | # 参考
101 |
102 | [1] [Dex简单保护](https://xz.aliyun.com/t/5789)
103 |
104 | [2] [ DexShell](https://github.com/Herrrb/DexShell)
105 |
106 | # 联系
107 |
108 | vx: xcc1014885794
109 |
110 | # 赞助
111 |
112 |
113 |
114 | 微信捐赠
115 |
116 |
117 |
118 |
119 |
120 |
121 | 支付宝捐赠
122 |
123 |
124 |
125 |
126 |
--------------------------------------------------------------------------------
/demo.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongyecc/apksheller/4dbe3959680a461de737d85b28202118d092224a/demo.keystore
--------------------------------------------------------------------------------
/images/wx.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongyecc/apksheller/4dbe3959680a461de737d85b28202118d092224a/images/wx.jpg
--------------------------------------------------------------------------------
/images/zfb.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongyecc/apksheller/4dbe3959680a461de737d85b28202118d092224a/images/zfb.jpg
--------------------------------------------------------------------------------
/requests.txt:
--------------------------------------------------------------------------------
1 | filetype
2 |
--------------------------------------------------------------------------------
/shellApplicationSourceCode/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/shellApplicationSourceCode/java/cn/yongye/stub/MainActivity.java:
--------------------------------------------------------------------------------
1 | package cn.yongye.stub;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 |
6 | public class MainActivity extends Activity {
7 |
8 | @Override
9 | protected void onCreate(Bundle savedInstanceState) {
10 | super.onCreate(savedInstanceState);
11 | setContentView(R.layout.activity_main);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/shellApplicationSourceCode/java/cn/yongye/stub/R.java:
--------------------------------------------------------------------------------
1 | /* AUTO-GENERATED FILE. DO NOT MODIFY.
2 | *
3 | * This class was automatically generated by the
4 | * aapt tool from the resource data it found. It
5 | * should not be modified by hand.
6 | */
7 |
8 | package cn.yongye.stub;
9 |
10 | public final class R {
11 | public static final class attr {
12 | }
13 | public static final class color {
14 | public static final int colorAccent=0x7f050002;
15 | public static final int colorPrimary=0x7f050000;
16 | public static final int colorPrimaryDark=0x7f050001;
17 | }
18 | public static final class drawable {
19 | public static final int ic_launcher_background=0x7f020000;
20 | public static final int ic_launcher_foreground=0x7f020001;
21 | public static final int ic_launcher_foreground_1=0x7f020002;
22 | }
23 | public static final class layout {
24 | public static final int activity_main=0x7f040000;
25 | }
26 | public static final class mipmap {
27 | public static final int ic_launcher=0x7f030000;
28 | public static final int ic_launcher_round=0x7f030001;
29 | }
30 | public static final class string {
31 | public static final int app_name=0x7f060000;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/shellApplicationSourceCode/java/cn/yongye/stub/StubApp.java:
--------------------------------------------------------------------------------
1 | package cn.yongye.stub;
2 |
3 | import android.app.Application;
4 | import android.app.Instrumentation;
5 | import android.content.Context;
6 | import android.content.pm.ApplicationInfo;
7 | import android.content.res.AssetManager;
8 | import android.os.Environment;
9 | import android.util.Log;
10 | import java.lang.reflect.Array;
11 | import java.lang.reflect.Field;
12 | import dalvik.system.BaseDexClassLoader;
13 | import java.io.ByteArrayInputStream;
14 | import java.io.DataInputStream;
15 | import java.io.File;
16 | import java.io.FileInputStream;
17 | import java.io.FileNotFoundException;
18 | import java.io.FileOutputStream;
19 | import java.io.IOException;
20 | import java.io.InputStream;
21 | import java.lang.ref.WeakReference;
22 | import java.util.List;
23 | import java.util.Map;
24 | import cn.yongye.stub.common.Config;
25 | import cn.yongye.stub.common.FileUtils;
26 | import cn.yongye.stub.common.RefInvoke;
27 | import dalvik.system.DexClassLoader;
28 |
29 |
30 | public class StubApp extends Application {
31 |
32 | private String TAG = "yongye";
33 | private Application oApp = this;
34 | private String stDmpDexPt = null;
35 |
36 | @Override
37 | protected void attachBaseContext(Context base) {
38 | super.attachBaseContext(base);
39 |
40 | /**
41 | * 1.解密原始DEX到缓存目录下yongye
42 | */
43 | byte[] baSrcDex = null;
44 | stDmpDexPt = oApp.getFilesDir().getAbsolutePath() + "/" + "/yongye.dex";
45 | File libs = oApp.getDir("libs", Context.MODE_PRIVATE);
46 | byte[] baDex = null;
47 | try {
48 | baDex = FileUtils.readDexFileFromApk(oApp);
49 | FileUtils.splitPayLoadFromDex(stDmpDexPt, baDex);
50 | } catch (IOException e) {
51 | Log.e(TAG, "解密原始DEX失败。");
52 | e.printStackTrace();
53 | }
54 | Log.i(TAG, "解密原始DEXcompleted");
55 |
56 |
57 | /**
58 | * 2.加载原始DEX
59 | */
60 | String stActivityThread = "android.app.ActivityThread";
61 | String stClassLoadedApk = "android.app.LoadedApk";
62 | String stCurrentPkgName = this.getPackageName();
63 |
64 | //bugfix libxxx.so not found in classloader
65 | File nativeLib = new File(this.getApplicationInfo().nativeLibraryDir);
66 |
67 | Object obCurrentActivityThread = RefInvoke.invokeStaticMethod(stActivityThread,
68 | "currentActivityThread", new Class[]{}, new Object[]{});
69 | Map,?> obmPackage = (Map,?>)RefInvoke.getFieldObject(stActivityThread,
70 | obCurrentActivityThread, "mPackages");
71 | WeakReference> wr = (WeakReference>) obmPackage.get(stCurrentPkgName);
72 | DexClassLoader oDexClassLoader = new DexClassLoader(stDmpDexPt, oApp.getCacheDir().getAbsolutePath(),
73 | nativeLib.getAbsolutePath(), (ClassLoader)RefInvoke.getFieldObject(stClassLoadedApk,
74 | wr.get(), "mClassLoader"));
75 |
76 | //bugfix class not found
77 | load(oApp, stDmpDexPt);
78 | load(oApp, oApp.getApplicationInfo().sourceDir);
79 | RefInvoke.setFieldObject(stClassLoadedApk, "mClassLoader", wr.get(), oDexClassLoader);
80 | RefInvoke.setFieldObject(stClassLoadedApk, "mResDir", wr.get(), oApp.getApplicationInfo().sourceDir);
81 | Log.i(TAG, "load source dex completed.");
82 |
83 | /**
84 | * 3.启用原始DEX文件的Application(更改环境值满足加载原始DEX的要求)
85 | */
86 | String stSrcAppName = Config.MAIN_APPLICATION;
87 | Object mBoundApplication = RefInvoke.getFieldObject(stActivityThread, obCurrentActivityThread, "mBoundApplication");
88 | Object loadedApkInfo = RefInvoke.getFieldObject(stActivityThread +"$AppBindData", mBoundApplication, "info");
89 | RefInvoke.setFieldObject(stClassLoadedApk, "mApplication", loadedApkInfo, null);
90 | Object mInitApplication = RefInvoke.getFieldObject(stActivityThread, obCurrentActivityThread, "mInitialApplication");
91 | List mAllApplications = (List) RefInvoke.getFieldObject(stActivityThread, obCurrentActivityThread, "mAllApplications");
92 | mAllApplications.remove(mInitApplication);
93 | ((ApplicationInfo) RefInvoke.getFieldObject(stClassLoadedApk, loadedApkInfo, "mApplicationInfo")).className = stSrcAppName;
94 | ((ApplicationInfo) RefInvoke.getFieldObject(stActivityThread+"$AppBindData", mBoundApplication, "appInfo")).className = stSrcAppName;
95 | Application makeApplication = (Application) RefInvoke.invokeMethod(stClassLoadedApk,
96 | "makeApplication", loadedApkInfo, new Class[]{boolean.class, Instrumentation.class}, new Object[]{false, null});
97 | RefInvoke.setFieldObject(stActivityThread, "mInitialApplication", obCurrentActivityThread,
98 | makeApplication);
99 | Map,?> mProviderMap = (Map,?>) RefInvoke.getFieldObject(stActivityThread, obCurrentActivityThread,
100 | "mProviderMap");
101 | for (Map.Entry, ?> entry : mProviderMap.entrySet()) {
102 | Object providerClientRecord = entry.getValue();
103 | Object mLocalProvider = RefInvoke.getFieldObject(stActivityThread+"$ProviderClientRecord", providerClientRecord, "mLocalProvider");
104 | if (mLocalProvider != null) {
105 | RefInvoke.setFieldObject("android.content.ContentProvider", "mContext", mLocalProvider, makeApplication);
106 | }
107 | }
108 | makeApplication.onCreate();
109 | Log.i(TAG, "getClassLoader - " + getClassLoader());
110 | Log.i(TAG, "unshell completed.");
111 | }
112 |
113 | public void load(Context oApp, String path) {
114 | try {
115 | // 已加载的dex
116 | Object dexPathList = getField(BaseDexClassLoader.class, "pathList", oApp.getClassLoader());
117 | Object dexElements = getField(dexPathList.getClass(), "dexElements", dexPathList);
118 |
119 | // patchdex
120 | String dexOptDir = oApp.getCacheDir().getAbsolutePath();
121 | DexClassLoader dcl = new DexClassLoader(path, dexOptDir, null, oApp.getClassLoader());
122 | Object patchDexPathList = getField(BaseDexClassLoader.class, "pathList", dcl);
123 | Object patchDexElements = getField(patchDexPathList.getClass(), "dexElements", patchDexPathList);
124 |
125 | // 将patchdex和已加载的dexes数组拼接连接
126 | Object concatDexElements = concatArray(patchDexElements, dexElements);
127 |
128 | // 重新给dexPathList#dexElements赋值
129 | setField(dexPathList.getClass(), "dexElements", dexPathList, concatDexElements);
130 | } catch (NoSuchFieldException e) {
131 | e.printStackTrace();
132 | } catch (IllegalAccessException e) {
133 | e.printStackTrace();
134 | }
135 | }
136 |
137 | /**
138 | * @param cls 被访问对象的class
139 | * @param fieldName 对象的成员变量名
140 | * @param object 被访问对象
141 | * @return
142 | * @throws NoSuchFieldException
143 | * @throws IllegalAccessException
144 | */
145 | public Object getField(Class> cls, String fieldName, Object object) throws NoSuchFieldException, IllegalAccessException {
146 | Field field = cls.getDeclaredField(fieldName);
147 | field.setAccessible(true);
148 | return field.get(object);
149 | }
150 |
151 |
152 | /**
153 | * @param cls 被访问对象的class
154 | * @param fieldName 对象的成员变量名
155 | * @param object 被访问对象
156 | * @param value 赋值给成员变量
157 | * @throws NoSuchFieldException
158 | * @throws IllegalAccessException
159 | */
160 | public void setField(Class> cls, String fieldName, Object object, Object value) throws NoSuchFieldException, IllegalAccessException {
161 | Field field = cls.getDeclaredField(fieldName);
162 | field.setAccessible(true);
163 | field.set(object, value);
164 | }
165 |
166 |
167 | /**
168 | * 连接两个数组(指定位置)
169 | *
170 | * @param left 连接后在新数组的左侧
171 | * @param right 连接后在新数组的右侧
172 | * @return
173 | */
174 | public Object concatArray(Object left, Object right) {
175 | int len1 = Array.getLength(left);
176 | int len2 = Array.getLength(right);
177 | int totalLen = len1 + len2;
178 | Object concatArray = Array.newInstance(left.getClass().getComponentType(), totalLen);
179 | for(int i = 0; i < len1; i++) {
180 | Array.set(concatArray, i, Array.get(left, i));
181 | }
182 | for(int j = 0; j < len2; j++) {
183 | Array.set(concatArray, len1 + j, Array.get(right, j));
184 | }
185 | return concatArray;
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/shellApplicationSourceCode/java/cn/yongye/stub/common/Config.java:
--------------------------------------------------------------------------------
1 | package cn.yongye.stub.common;
2 |
3 | public class Config {
4 | public static final String MAIN_APPLICATION = "android.app.Application";
5 | }
--------------------------------------------------------------------------------
/shellApplicationSourceCode/java/cn/yongye/stub/common/FileUtils.java:
--------------------------------------------------------------------------------
1 | package cn.yongye.stub.common;
2 |
3 | import android.content.Context;
4 | import android.util.Log;
5 |
6 | import java.io.BufferedInputStream;
7 | import java.io.ByteArrayInputStream;
8 | import java.io.ByteArrayOutputStream;
9 | import java.io.DataInputStream;
10 | import java.io.File;
11 | import java.io.FileInputStream;
12 | import java.io.FileOutputStream;
13 | import java.io.IOException;
14 | import java.util.zip.ZipEntry;
15 | import java.util.zip.ZipInputStream;
16 |
17 | public class FileUtils {
18 |
19 | /**
20 | * 获取父目录的绝对路径
21 | * @param f 文件对象
22 | * @return 父目录对象
23 | */
24 | public static File getParent(File f) {
25 | String path = f.getAbsolutePath();
26 | int separator = path.lastIndexOf(File.separator);
27 | return separator == -1 ? new File(path) : new File(path.substring(0, separator));
28 | }
29 |
30 | /**
31 | * 获取应用classes.dex字节数据
32 | * @param context
33 | * @return
34 | * @throws IOException
35 | */
36 | public static byte[] readDexFileFromApk(Context context) throws IOException {
37 | ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();
38 | ZipInputStream localZipInputStream = new ZipInputStream(
39 | new BufferedInputStream(new FileInputStream(
40 | context.getApplicationInfo().sourceDir)));
41 | while (true) {
42 | ZipEntry localZipEntry = localZipInputStream.getNextEntry();
43 | if (localZipEntry == null) {
44 | localZipInputStream.close();
45 | break;
46 | }
47 | if (localZipEntry.getName().equals("classes.dex")) {
48 | byte[] arrayOfByte = new byte[1024];
49 | while (true) {
50 | int i = localZipInputStream.read(arrayOfByte);
51 | if (i == -1)
52 | break;
53 | dexByteArrayOutputStream.write(arrayOfByte, 0, i);
54 | }
55 | }
56 | localZipInputStream.closeEntry();
57 | }
58 | localZipInputStream.close();
59 | return dexByteArrayOutputStream.toByteArray();
60 | }
61 |
62 | /**
63 | * 将原DEX从壳DEX上分离出来
64 | * @param stDexPt
65 | * @param apkdata 壳DEX数据
66 | * @throws IOException
67 | */
68 | public static void splitPayLoadFromDex(String stDexPt, byte[] apkdata) throws IOException {
69 | String TAG = "yongye";
70 | apkdata = decrypt(apkdata);
71 | int ablen = apkdata.length;
72 | byte[] dexlen = new byte[4];
73 | System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4);
74 | ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);
75 | DataInputStream in = new DataInputStream(bais);
76 | int readInt = in.readInt();
77 | Log.i(TAG, "原始DEX长度:" + String.valueOf(readInt));
78 | byte[] newdex = new byte[readInt];
79 | System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt);
80 | File file = new File(stDexPt);
81 | if(file.exists()){
82 | Log.i(TAG, file.getAbsolutePath() + "文件还存在");
83 | }
84 | try {
85 | FileOutputStream localFileOutputStream = new FileOutputStream(file);
86 | localFileOutputStream.write(newdex);
87 | localFileOutputStream.close();
88 | } catch (IOException localIOException) {
89 | Log.e(TAG, "dump 原始DEX失败");
90 | throw new RuntimeException(localIOException);
91 | }
92 | }
93 |
94 | private static byte[] decrypt(byte[] baEncryted){
95 | byte[] baRes = new byte[baEncryted.length];
96 | for(int i=0; i
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/shellApplicationSourceCode/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/shellApplicationSourceCode/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
--------------------------------------------------------------------------------
/shellApplicationSourceCode/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/shellApplicationSourceCode/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/shellApplicationSourceCode/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongyecc/apksheller/4dbe3959680a461de737d85b28202118d092224a/shellApplicationSourceCode/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/shellApplicationSourceCode/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongyecc/apksheller/4dbe3959680a461de737d85b28202118d092224a/shellApplicationSourceCode/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/shellApplicationSourceCode/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongyecc/apksheller/4dbe3959680a461de737d85b28202118d092224a/shellApplicationSourceCode/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/shellApplicationSourceCode/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongyecc/apksheller/4dbe3959680a461de737d85b28202118d092224a/shellApplicationSourceCode/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/shellApplicationSourceCode/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongyecc/apksheller/4dbe3959680a461de737d85b28202118d092224a/shellApplicationSourceCode/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/shellApplicationSourceCode/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongyecc/apksheller/4dbe3959680a461de737d85b28202118d092224a/shellApplicationSourceCode/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/shellApplicationSourceCode/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongyecc/apksheller/4dbe3959680a461de737d85b28202118d092224a/shellApplicationSourceCode/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/shellApplicationSourceCode/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongyecc/apksheller/4dbe3959680a461de737d85b28202118d092224a/shellApplicationSourceCode/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/shellApplicationSourceCode/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongyecc/apksheller/4dbe3959680a461de737d85b28202118d092224a/shellApplicationSourceCode/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/shellApplicationSourceCode/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongyecc/apksheller/4dbe3959680a461de737d85b28202118d092224a/shellApplicationSourceCode/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/shellApplicationSourceCode/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #6200EE
4 | #3700B3
5 | #03DAC5
6 |
7 |
--------------------------------------------------------------------------------
/shellApplicationSourceCode/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ShellApp
3 |
4 |
--------------------------------------------------------------------------------
/shellApplicationSourceCode/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
13 |
14 |
--------------------------------------------------------------------------------
/sheller.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 | import struct
4 | import zlib
5 | import hashlib
6 | import re
7 | from subprocess import check_call
8 | import argparse
9 | import os
10 | from xml.dom.minidom import parse
11 | import zipfile
12 | from PIL import Image
13 | import shutil
14 | import sys
15 |
16 |
17 | stCurrentPt = os.path.abspath(__file__).replace(os.path.basename(__file__), "")
18 | stApkToolPt = os.path.join(stCurrentPt, 'tools', 'apktool_2.9.3.jar')
19 | stShellAppPt = os.path.join(stCurrentPt, 'shellApplicationSourceCode')
20 | staaptPt = os.path.join(stCurrentPt, 'tools', 'aapt.exe')
21 | stAndroidJarlibPt = os.path.join(stCurrentPt, 'tools', 'android.jar')
22 | stdxJarPt = os.path.join(stCurrentPt, 'tools', 'dx.jar')
23 | stApksignJarPt = os.path.join(stCurrentPt, 'tools', 'apksigner.jar')
24 | osType = sys.platform
25 |
26 |
27 | def add_srcDexToShellDex(srcDex, shellDex):
28 |
29 | liShellDt = []
30 | liSrcDexDt = []
31 | liAllDt = []
32 |
33 | #将原始DEX和壳DEX数据放在一个列表中
34 | with open(shellDex, "rb") as f:
35 | shellData = f.read()
36 | liShellDt = list(struct.unpack(len(shellData)*'B', shellData))
37 | with open(srcDex, 'rb') as f:
38 | srcDt = f.read()
39 | liSrcDexDt = list(struct.unpack(len(srcDt)*'B', srcDt))
40 | liAllDt.extend(shellData)
41 | # 加密原DEX
42 | for i in liSrcDexDt:
43 | liAllDt.append(i ^ inKey)
44 |
45 | iSrcDexLen = len(liSrcDexDt)
46 | liSrcDexLen = intToSmalEndian(iSrcDexLen)
47 | liSrcDexLen.reverse()
48 | # 加密原DEX长度
49 | for i in liSrcDexLen:
50 | liAllDt.append(i ^ inKey)
51 |
52 | # 计算合成后DEX文件的checksum、signature、file_size
53 | # 更改文件头
54 | newFsize = len(liAllDt)
55 | liNewFSize = intToSmalEndian(newFsize)
56 | for i in range(4):
57 | liAllDt[32 + i] = liNewFSize[i]
58 |
59 | newSignature = hashlib.sha1(bytes(liAllDt[32:])).hexdigest()
60 | liNewSignature = re.findall(r'.{2}', newSignature)
61 | for i in range(len(liNewSignature)):
62 | liNewSignature[i] = ord(bytes.fromhex(liNewSignature[i]))
63 | for i in range(20):
64 | liAllDt[12 + i] = liNewSignature[i]
65 |
66 | newChecksum = zlib.adler32(bytes(liAllDt[12:]))
67 | liNewChecksum = intToSmalEndian(newChecksum)
68 | for i in range(4):
69 | liAllDt[8 + i] = liNewChecksum[i]
70 |
71 | with open(os.path.join(stCurrentPt, 'classes.dex'), 'wb') as f:
72 | f.write(bytes(liAllDt))
73 |
74 |
75 | def intToSmalEndian(numb):
76 | liRes = []
77 |
78 | stHexNumb = hex(numb)[2:]
79 | for i in range(8 - len(stHexNumb)):
80 | stHexNumb = '0' + stHexNumb
81 | liRes = re.findall(r'.{2}', stHexNumb)
82 | for i in range(len(liRes)):
83 | liRes[i] = ord(bytes.fromhex(liRes[i]))
84 | liRes.reverse()
85 |
86 | return liRes
87 |
88 | def decompAPK(fp):
89 | cmd = []
90 | cmd.append('java')
91 | cmd.append('-jar')
92 | cmd.append(stApkToolPt)
93 | cmd.append('d')
94 | cmd.append('-o')
95 | cmd.append(fp + "decompile")
96 | cmd.append(fp)
97 | check_call(cmd)
98 |
99 | def getAppName(fp):
100 | stAppName = "android.app.Application"
101 | stDisassembleDp = fp + "decompile"
102 | stAMFp = os.path.join(stDisassembleDp, "AndroidManifest.xml")
103 | oDomTree = parse(stAMFp)
104 | manifest = oDomTree.documentElement
105 | apps = manifest.getElementsByTagName('application')
106 |
107 | if len(apps) != 1:
108 | print("存在多个Application")
109 | raise(Exception)
110 | if apps[0].getAttribute("android:name") != "":
111 | stAppName = apps[0].getAttribute("android:name")
112 |
113 | return stAppName
114 |
115 | def replaceTag(fp, stValue):
116 | stAXMLFp = os.path.join(stCurrentPt, fp + "decompile", "AndroidManifest.xml")
117 | dom = None
118 | with open(stAXMLFp, 'r', encoding='UTF-8') as f:
119 | dom = parse(f)
120 | root = dom.documentElement
121 | app = root.getElementsByTagName('application')[0]
122 | app.setAttribute("android:name", stValue)
123 | # bugfix INSTALL_FAILED_INVALID_APK: Failed to extract native libraries, res=-2
124 | app.setAttribute("android:extractNativeLibs", 'true')
125 | app.setAttribute('android:debuggable', 'true')
126 | print('[*] replace android:extractNativeLibs -> ' + app.getAttribute("android:extractNativeLibs"))
127 | with open(stAXMLFp, "w", encoding='UTF-8') as f:
128 | dom.writexml(f, encoding='UTF-8')
129 | stDecompDp = os.path.join(stCurrentPt, fp + "decompile")
130 | # 修复PNG文件BUG
131 | PngBug(stDecompDp)
132 |
133 | cmd = []
134 | cmd.append('java')
135 | cmd.append('-jar')
136 | cmd.append(stApkToolPt)
137 | cmd.append('b')
138 | cmd.append('-o')
139 | cmd.append("result.apk")
140 | cmd.append(stDecompDp)
141 | check_call(cmd)
142 |
143 | shutil.rmtree(stDecompDp)
144 |
145 |
146 | def save_appName(appName):
147 | stCfgFp = os.path.join(stShellAppPt, "java/cn/yongye/stub/common/Config.java")
148 | with open(stCfgFp, 'w') as f:
149 | f.write("package cn.yongye.stub.common;\n")
150 | f.write("\n")
151 | f.write("public class Config {\n")
152 | f.write(" public static final String MAIN_APPLICATION = \"{}\";\n".format(appName))
153 | f.write("}")
154 |
155 | def compileSrcApk(dp):
156 | cmd = []
157 | cmd.append('java')
158 | cmd.append('-jar')
159 | cmd.append(stApkToolPt)
160 | cmd.append('b')
161 | cmd.append('-f')
162 | cmd.append('-o')
163 | cmd.append(stCurrentPt + "\src.apk")
164 | cmd.append(dp)
165 | check_call(cmd)
166 |
167 | def compileShellDex():
168 |
169 | licmd2 = []
170 | licmd2.append("javac")
171 | licmd2.append("-encoding")
172 | licmd2.append("UTF-8")
173 | licmd2.append("-target")
174 | licmd2.append("1.8")
175 | licmd2.append("-bootclasspath")
176 | licmd2.append(stAndroidJarlibPt)
177 | licmd2.append("-d")
178 | licmd2.append(stCurrentPt)
179 | licmd2.append(os.path.join(stCurrentPt, 'shellApplicationSourceCode', 'java', 'cn', 'yongye', 'stub', '*.java'))
180 | licmd2.append(os.path.join(stCurrentPt, 'shellApplicationSourceCode', 'java', 'cn', 'yongye', 'stub', 'common', '*.java'))
181 | check_call(' '.join(licmd2), shell=True)
182 |
183 | licmd3 = []
184 | licmd3.append("java")
185 | licmd3.append("-jar")
186 | licmd3.append(stdxJarPt)
187 | licmd3.append("--dex")
188 | licmd3.append("--output=" + stCurrentPt + r"\shell.dex")
189 | licmd3.append(os.path.join('cn', 'yongye', 'stub', '*.class'))
190 | licmd3.append(os.path.join('cn', 'yongye', 'stub', 'common', '*.class'))
191 | check_call(' '.join(licmd3), shell=True)
192 |
193 | shutil.rmtree("cn")
194 |
195 |
196 | def replaceSDexToShellDex(stSApkFp):
197 | # 提取原APK中的DEX到本地
198 | oFApk = zipfile.ZipFile(stSApkFp)
199 | oFApk.extract("classes.dex", stCurrentPt)
200 | oFApk.close()
201 | stSDexFp = os.path.join(stCurrentPt, "classes.dex")
202 | stShellDexFp = os.path.join(stCurrentPt, "shell.dex")
203 | # 将原DEX添加到壳DEX尾部,保存到本目录下为classes.dex
204 | add_srcDexToShellDex(stSDexFp, stShellDexFp)
205 | #将修改后的壳DEX添加到原DEX中
206 |
207 | deleteZipItem(stSApkFp, 'classes.dex')
208 |
209 | oNewApk = zipfile.ZipFile(stSApkFp, "a")
210 | oNewApk.write("classes.dex", "classes.dex")
211 | oNewApk.close()
212 |
213 | os.remove(stSDexFp)
214 | os.remove(stShellDexFp)
215 |
216 |
217 | def deleteZipItem(zp, pp):
218 | zoutp = '{}-out'.format(zp)
219 | zin = zipfile.ZipFile(zp, mode='r')
220 | zout = zipfile.ZipFile(zoutp, mode='w', compression=zipfile.ZIP_DEFLATED)
221 | for item in zin.infolist():
222 | if item.filename == pp: continue
223 | zout.writestr(item, zin.read(item))
224 | zin.close()
225 | zout.close()
226 | shutil.move(zoutp, zp)
227 |
228 |
229 | def signApk(fp, stKeystoreFp):
230 | cmd = []
231 | cmd.append("java")
232 | cmd.append("-jar")
233 | cmd.append(stApksignJarPt)
234 | cmd.append("sign")
235 | cmd.append("--ks")
236 | cmd.append(stKeystoreFp)
237 | cmd.append("--ks-pass")
238 | cmd.append("pass:123456")
239 | cmd.append(fp)
240 | check_call(cmd)
241 |
242 |
243 | def PngBug(decompFp):
244 | import filetype
245 | liFiles = os.listdir(decompFp)
246 | for fn in liFiles:
247 | fp = os.path.join(decompFp, fn)
248 | if os.path.isdir(fp):
249 | PngBug(fp)
250 | if fp.endswith(".png") and filetype.guess(fp).MIME == "image/gif":
251 | im = Image.open(fp)
252 | current = im.tell()
253 | im.save(fp)
254 |
255 |
256 | if __name__ == "__main__":
257 |
258 | parser = argparse.ArgumentParser()
259 | parser.add_argument('-f', help="对指定文件进行加固", nargs=1, metavar='APK')
260 | args = parser.parse_args()
261 |
262 | if args.f:
263 | fp = args.__dict__.get('f')[0]
264 | """
265 | 1. 第一步:确定加密算法
266 | """
267 | inKey = 0xff
268 | print("[*] 确定加密解密算法,异或: {}".format(str(inKey)))
269 |
270 | """
271 | 2. 第二步:准备好壳App
272 | """
273 | # 反编译原apk
274 | decompAPK(fp)
275 | # print("[*] 反编译原的apk文件{}完成".format(fp))
276 | # 获取Applicaiton name并保存到壳App源码中
277 | stSrcDexAppName = getAppName(fp)
278 | # print("[*] 获取原apk文件的Application Android:name=\"{}\" 完成".format(stSrcDexAppName))
279 | save_appName(stSrcDexAppName)
280 | # print("[*] 保存原apk文件的Application Android:name=\"{}\" 到壳App源码的配置文件完成".format(stSrcDexAppName))
281 | # 编译出壳DEX
282 | compileShellDex()
283 | print("[*] 壳App的class字节码文件编译为:shell.dex完成")
284 |
285 | """
286 | 3. 第三步:修改原apk AndroidManifest.xml文件中的Application name字段为壳的Application name字段
287 | """
288 | # 替换壳Applicaiton name到原apk的AndroidManifest.xml内
289 | replaceTag(fp, "cn.yongye.stub.StubApp")
290 | print("[*] 原apk文件AndroidManifest.xml中application的name字段替换为壳application name字段完成")
291 |
292 | """
293 | 4. 替换原apk中的DEX文件为壳DEX
294 | """
295 | replaceSDexToShellDex(os.path.join(stCurrentPt, "result.apk"))
296 | print("[*] 壳DEX替换原apk包内的DEX文件完成")
297 |
298 | """
299 | 5. apk签名
300 | """
301 | signApk(os.path.join(stCurrentPt, "result.apk"), os.path.join(stCurrentPt, "demo.keystore"))
302 | print("[*] 签名完成,生成apk文件: {}".format(os.path.join(stCurrentPt, "result.apk")))
303 |
304 |
305 | else:
306 | parser.print_help()
307 |
308 |
309 |
--------------------------------------------------------------------------------
/tools/android.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongyecc/apksheller/4dbe3959680a461de737d85b28202118d092224a/tools/android.jar
--------------------------------------------------------------------------------
/tools/apksigner.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongyecc/apksheller/4dbe3959680a461de737d85b28202118d092224a/tools/apksigner.jar
--------------------------------------------------------------------------------
/tools/apktool_2.5.0.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongyecc/apksheller/4dbe3959680a461de737d85b28202118d092224a/tools/apktool_2.5.0.jar
--------------------------------------------------------------------------------
/tools/apktool_2.9.3.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongyecc/apksheller/4dbe3959680a461de737d85b28202118d092224a/tools/apktool_2.9.3.jar
--------------------------------------------------------------------------------
/tools/dx.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongyecc/apksheller/4dbe3959680a461de737d85b28202118d092224a/tools/dx.jar
--------------------------------------------------------------------------------