apk加固工具哪个好(apk一机一码加密工具)

1

混淆、膨胀

混淆

主题思想:用没有意义的字符,如a、b、c或者易于混淆的字符,如0、o、O、l、I、1代替原本的有意义的类名。参数配置:将release下minifyEnabled的值改为true,打开混淆;加上shrinkResources true,打开资源压缩。

apk加固工具哪个好(apk一机一码加密工具)#压缩级别0-7,Android一般为5(对代码迭代优化的次数)-optimizationpasses 5#不使用大小写混合类名-dontusemixedcaseclassnames #混淆时记录日志-verbose#不警告org.greenrobot.greendao.database包及其子包里面未应用的应用-dontwarn org.greenrobot.greendao.database.**-dontwarn rx.**-dontwarn org.codehaus.jackson.**……#保持jackson包以及其子包的类和类成员不被混淆-keep class org.codehaus.jackson.** {*;}#——–重要说明——-#-keep class 类名 {*;}#-keepclassmembers class 类名{*;}#一个*表示保持了该包下的类名不被混淆;# -keep class org.codehaus.jackson.*#二个**表示保持该包以及它包含的所有子包下的类名不被混淆# -keep class org.codehaus.jackson.**#————————#保持类名、类里面的方法和变量不被混淆-keep class org.codehaus.jackson.** {*;}#不混淆类ClassTwoOne的类名以及类里面的public成员和方法#public 可以换成其他java属性如private、public static 、final等#还可以使<init>表示构造方法、<methods>表示方法、<fields>表示成员,#这些前面也可以加public等java属性限定-keep class com.dev.demo.two.ClassTwoOne { public *;}#不混淆类名,以及里面的构造函数-keep class com.dev.demo.ClassOne { public <init>();}#不混淆类名,以及参数为int 的构造函数-keep class com.dev.demo.two.ClassTwoTwo { public <init>(int);}#不混淆类的public修饰的方法,和private修饰的变量-keepclassmembers class com.dev.demo.two.ClassTwoThree { public <methods>; private <fields>;}#不混淆内部类,需要用$修饰#不混淆内部类ClassTwoTwoInner以及里面的全部成员-keep class com.dev.demo.two.ClassTwoTwo$ClassTwoTwoInner{*;}更多混淆配置参考:

https://juejin.cn/post/6844903471095742472

https://www.huaweicloud.com/articles/ae151e2f60923097cefc473bd131addf.html

膨胀

https://gitee.com/koifishly/function_generator

2

DEX壳

之前Android的主要代码为Java代码,但是在逆向分析中,Java代码是很容易被分析出来的。为了解决这个问题,我们就希望在app运行起来后动态加载我们Java代码(.dex文件)。这种方法主要利用了DexClassLoader这个类来实现动态加载。DexClassLoader类支持动态加载.apk或者.dex。动态加载APK

动态加载apk简单来说,就是将已经编译好的.apk文件放入到一个.dex文件中。这个.apk文件为我们真正的应用程序,以下就称呼这个apk为源APK;.dex文件为另外一个工程的.dex文件,这个工程主要是为了在运行时释放出源APK,然后将流程转到源APK执行。

根据上面的原理图,我们需要3个对象。源APK:需要加壳的apk。壳APK:将apk解密还原并执行。加密工具:将源apk和壳dex进行组合成新的dex并且修正新的dex。项目实现demo代码

IDE:Android Studio 4.1.3

Android版本:4.4

项目源码:nisosaikou/AndroidDEX壳-码云-开源中国(gitee.com)

源APK

1、正常编写功能逻辑代码。这里的代码为简单的ctf 判断代码。

2、新建类APP类并且这个类继承于类Application,实现onCreate方法。

3、生成一个release版本apk,把这个apk保存起来。

修改MainActivity.java的父类,使得MainActivity继承于Activity。将文本显示修改为运行的是源APK。

壳APK

Proxy.java

新建一个代理类叫Proxy,继承于类Application。这个类用来释放和解密原始的APK。

attachBaseContext()重写Application中的attachBaseContext方法。这个方法会在 Activity 的 onCreate 方法之前执行。方法实现的功能主要有:把壳dex中包含的源apk释放出来。

把释放的apk进行解密。

把源apk中的lib目录中的文件复制到当前程序(壳)的路径下。

创建一个新的DexClassLoader,替换到父节点的DexClassLoader。

DexClassLoader 继承自BaseDexClassLoader,这个比较灵活,每个参数都可以自定义,我们一般用这个来加载自定义的apk/dex/jar文件。

代码例子:@Overrideprotected void attachBaseContext(Context base) { super.attachBaseContext(base); // the getDir method will create a directory in /data/user/0(uid)/packagename/ // the dx directory holds the file of the source apk File relesaeDir = this.getDir(“dx”, MODE_PRIVATE); mSouceAPKLibAbsolutePath = this.getDir(“lx”, MODE_PRIVATE).getAbsolutePath(); mSourceAPKReleaseDir = relesaeDir.getAbsolutePath(); mSourceAPKAbsolutePath = mSourceAPKReleaseDir “/” mSouceAPKName; // create the source apk // if the source apk exist, do nothing, otherwise create the source apk file. File sourceApk = new File(mSourceAPKAbsolutePath); if (!sourceApk.exists()){ try{ sourceApk.createNewFile(); } catch (Exception e) { Log.e(TAG, “failed to create file.”); } // the source apk file is empty, you need to read source apk file from the dex // file of the shell apk and save it. byte[] shellDexData; // get dex of shell apk. shellDexData = getShellDexFileFromShellApk(); // get the source apk and decrypt it. // copy the libs in the decrypted apk file to the lib directory. getSourceApkFile(shellDexData); } // Configure dynamic load environment Object currentActivityThread = RefInvoke.invokeStaticMethod(“android.app.ActivityThread”, “currentActivityThread”, new Class[] {}, new Object[] {}); String packageName = this.getPackageName(); ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect(“android.app.ActivityThread”, currentActivityThread, “mPackages”); WeakReference weakReference = (WeakReference) mPackages.get(packageName); DexClassLoader newDexClassLoader = new DexClassLoader(mSourceAPKAbsolutePath, mSourceAPKReleaseDir, mSouceAPKLibAbsolutePath, (ClassLoader) RefInvoke.getFieldOjbect(“android.app.LoadedApk”, weakReference.get(), “mClassLoader”)); RefInvoke.setFieldOjbect(“android.app.LoadedApk”, “mClassLoader”, weakReference.get(), newDexClassLoader);}onCreate()

加载源apk资源。

获取manifest.xml中记录的源apk的启动类名。

设置ActivityThread信息(android.app.ActivityThread->currentActivityThread)。

代码例子

@Overridepublic void onCreate() { super.onCreate(); // 源apk启动类 String srcAppClassName = “”; // 原apk所在路径 try { ApplicationInfo applicationInfo = this.getPackageManager().getApplicationInfo(this.getPackageName(), PackageManager.GET_META_DATA); Bundle bundle = applicationInfo.metaData; if (bundle != null && bundle.containsKey(SRC_APP_MAIN_ACTIVITY)) { srcAppClassName = bundle.getString(SRC_APP_MAIN_ACTIVITY);//className 是配置在xml文件中的。 } else { return; } } catch (Exception e) { } //获取ActivityThread类下AppBindData类的成员属性 LoadedApk info; 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”); // 将原来的loadedApkInfo置空 RefInvoke.setFieldOjbect(“android.app.LoadedApk”, “mApplication”, loadedApkInfo, null); // 获取壳线程的Application Object oldApplication = RefInvoke.getFieldOjbect(“android.app.ActivityThread”, currentActivityThread, “mInitialApplication”); ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke.getFieldOjbect(“android.app.ActivityThread”, currentActivityThread, “mAllApplications”); mAllApplications.remove(oldApplication); // 构造新的Application // 1.更新 2处className 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”); appinfo_In_LoadedApk.className = srcAppClassName; appinfo_In_AppBindData.className = srcAppClassName; // 2.注册application Application app = (Application) RefInvoke.invokeMethod(“android.app.LoadedApk”, “makeApplication”, loadedApkInfo, new Class[] { boolean.class, Instrumentation.class }, new Object[] { false, null }); //替换ActivityThread中的mInitialApplication RefInvoke.setFieldOjbect(“android.app.ActivityThread”, “mInitialApplication”, currentActivityThread, app); //替换之前的 内容提供者为刚刚注册的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); } app.onCreate();}

ActivityThread功能

它管理应用进程的主线程的执行(相当于普通Java程序的main入口函数),并根据AMS的要求(通过IApplicationThread接口,AMS为Client、ActivityThread.ApplicationThread为Server)负责调度和执行activities、broadcasts和其它操作。

在Android系统中,在默认情况下,一个应用程序内的各个组件(如Activity、BroadcastReceiver、Service)都会在同一个进程(Process)里执行,且由此进程的【主线程】负责执行。

在Android系统中,如果有特别指定(通过android:process),也可以让特定组件在不同的进程中运行。无论组件在哪一个进程中运行,默认情况下,他们都由此进程的【主线程】负责执行。

【主线程】既要处理Activity组件的UI事件,又要处理Service后台服务工作,通常会忙不过来。为了解决此问题,主线程可以创建多个子线程来处理后台服务工作,而本身专心处理UI画面的事件。

类结构参考

调用currentActivityThread方法获取ActivityThread中的成员变量sCurrentActivityThread。Object currentActivityThread = RefInvoke.invokeStaticMethod(“android.app.ActivityThread”, “currentActivityThread”, new Class[] {}, new Object[] {});

获取sCurrentActivityThread中的mBoundApplication。Object mBoundApplication = RefInvoke.getFieldOjbect(“android.app.ActivityThread”, currentActivityThread, “mBoundApplication”);

获取mBoundApplication中成员变量info。Object loadedApkInfo = RefInvoke.getFieldOjbect(“android.app.ActivityThread$AppBindData”, mBoundApplication, “info”);

观察LoadedApk这个类,能发现一些重要的属性,这个下面会用到。

将info中的mApplication属性置空。RefInvoke.setFieldOjbect(“android.app.LoadedApk”,”mApplication”,loadedApkInfo,null);在sCurrentActivityThread下的链表mAllApplications中移除mInitialApplication。mInitialApplication存放初始化的应用(当前壳应用),mAllApplications存放的是所有的应用。把当前的应用,从现有的应用中移除掉,然后再把新构建的加入到里面去。Object oldApplication = RefInvoke.getFieldOjbect(“android.app.ActivityThread”, currentActivityThread, “mInitialApplication”);ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke.getFieldOjbect(“android.app.ActivityThread”, currentActivityThread, “mAllApplications”);mAllApplications.remove(oldApplication);

构造新的Application更新2处className。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”);appinfo_In_LoadedApk.className = srcAppClassName;appinfo_In_AppBindData.className = srcAppClassName;注册application(用LoadedApk中的makeApplication方法注册)。Applicationapp=(Application)RefInvoke.invokeMethod(“android.app.LoadedApk”,”makeApplication”,loadedApkInfo,newClass[]{boolean.class,Instrumentation.class},newObject[]{false,null});

替换mInitialApplication为刚刚创建的app。RefInvoke.setFieldOjbect(“android.app.ActivityThread”, “mInitialApplication”, currentActivityThread, app);更新ContentProvider。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);}执行新app的onCreate方法。app.onCreate();RefInvoke.javaJava反射调用的方法。package org.koi.dexloader; import java.lang.reflect.Field;import java.lang.reflect.Method; public class RefInvoke { public static Object invokeStaticMethod(String class_name, String method_name, Class[] pareTyple, Object[] pareVaules){ try { Class obj_class = Class.forName(class_name); Method method = obj_class.getMethod(method_name,pareTyple); return method.invoke(null, pareVaules); } catch (Exception e) { e.printStackTrace(); } return null; } public static Object getFieldOjbect(String class_name,Object obj, String filedName){ try { Class obj_class = Class.forName(class_name); Field field = obj_class.getDeclaredField(filedName); field.setAccessible(true); return field.get(obj); } catch (Exception e) { e.printStackTrace(); } return null; } public static void setFieldOjbect(String classname, String filedName, Object obj, Object filedVaule){ try { Class obj_class = Class.forName(classname); Field field = obj_class.getDeclaredField(filedName); field.setAccessible(true); field.set(obj, filedVaule); } catch (Exception e) { e.printStackTrace(); } } public static Object invokeMethod(String class_name, String method_name, Object obj ,Class[] pareTyple, Object[] pareVaules){ try { Class obj_class = Class.forName(class_name); Method method = obj_class.getMethod(method_name,pareTyple); return method.invoke(obj, pareVaules); } catch (Exception e) { e.printStackTrace(); } return null; }}AndroidManifest.xml<?xml version=”1.0″ encoding=”utf-8″?><manifest xmlns:android=”http://schemas.android.com/apk/res/android” package=”org.koi.dexloader”> <application android:allowBackup=”true” android:icon=”@mipmap/ic_launcher” android:label=”@string/app_name” android:roundIcon=”@mipmap/ic_launcher_round” android:supportsRtl=”true” android:name=”.Proxy” android:theme=”@style/Theme.DexLoader”> <meta-data android:name=”APPLICATION_CLASS_NAME” android:value=”org.koi.ctf20200802.APP”/> <activity android:name=”org.koi.ctf20200802.MainActivity”> <intent-filter> <action android:name=”android.intent.action.MAIN” /> <category android:name=”android.intent.category.LAUNCHER” /> </intent-filter> </activity> </application> </manifest>关于资源的问题到目前为止,源程序能够运行起来了,但是apk在运行的时候肯定会用到相关的资源,如布局文件等等,我们并没有介绍如何处理资源。

资源有2中大的处理方法。第一种是在壳dex解压出源apk时,把apk中的资源复制到现在程序下。第二种是替换壳apk中dex文件时,顺便用源apk中的资源文件替换到壳apk中。因为本文不重点讨论资源的处理问题,所以采用第二种方法,直接复制替换资源即可。

Dex组合修复工具将APK和壳DEX文件合并,生成一个新的DEX文件,并且校正新的DEX文件头。加壳步骤src.apk:源APK。des.apk:壳APK。DexFixed.jar:Dex工具。classes.dex:des.apk中的classes.dex。res:源APK中的文件夹。resources.arsc:源APK中的文件。

1、用DEXFixed.jar工具把src.apk和classes.dex进行合并,生成一个新的Dex,替换到壳APK中。

2、替换壳APK中的classes.dex、res、resources.arsc。3、apk重新签名。4、正常运行。

总结dex壳是比较基础的壳,只是将源APK加密后放入dex文件中,在运行时进行释放。我们只需要在壳程序解密出原始的APK运行后,在内存中把dexdump下来就可以了,我们也可以用frida框架进行脱壳。动态加载DEX(Java)

我们在上面动态加载APK时是采用了两个工程,一个工程负责加载APK,一个负责业务流程,业务流程工程核心文件就是一个dex文件,可以考虑只将dex文件作为附件,然后进行动态加载dex。项目实现demo代码简单来说,这里存放git的链接。源工程新建一个简单功能的 Android 工程。创建assets文件夹。

保存编译之后apk文件中的.dex文件,把.dex文件保存到assets目录下。dex文件重命名为origin.dex(可以重命名为任意文件名)。删除MainActivity.java。注意:这里只删除源文件,不要删除Activity。加密DEX

新建一个Java工程实现一个简单的加密。import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.FileInputStream;import java.io.FileOutputStream; public class Main { public static void main(String[] args) { if (args.length != 2) { System.out.println(“jar : <source file> <encrypted file>”); return; } String sourceFile = args[0]; String encryptedFile = args[1]; try { FileInputStream fis = new FileInputStream(sourceFile); BufferedInputStream bis = new BufferedInputStream(fis); FileOutputStream fos = new FileOutputStream(encryptedFile); BufferedOutputStream bos = new BufferedOutputStream(fos); byte[] buffer = new byte[10240]; int acount = 0; while((acount = bis.read(buffer)) != -1) { byte[] encryptedData = encrypt(buffer); bos.write(encryptedData,0, acount); } bos.flush(); //关闭的时候只需要关闭最外层的流就行了 bos.close(); bis.close(); } catch (Exception e) { e.printStackTrace(); } } public static byte[] encrypt(byte[] sourceData) { for (int i = 0; i < sourceData.length; i ){ sourceData[i] ^= 273; } return sourceData; } }把得到的加密文件放入刚刚创建的assets目录下。把重命名后的文件可以通过加密后再放入assets目录下,然后再加载dex前进行解密。

壳工程

这里壳工程就在源工程的基础上修改就可以了,不需要在新建一个工程。分别创建ProxyApplication.java和RefInvoke.java。这两个类的代码和上面基本一样,这里就不赘述了,直接看代码。ProxyApplication.javapackage org.koi.ctf20210813; import android.app.Application;import android.content.Context;import android.util.ArrayMap; import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.lang.ref.WeakReference; import dalvik.system.DexClassLoader; public class P extends Application { private final static String encryptedFileName = “flag”; private final static String package_name = “org.koi.ctf20210813”; private final static String activity_thread = “android.app.ActivityThread”; private final static String current_activity_thread = “currentActivityThread”; @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); try { File cacheDir = getCacheDir(); if (!cacheDir.exists()){ cacheDir.mkdirs(); } File outFile = new File(cacheDir, “out.dex”); InputStream is = getAssets().open(encryptedFileName); FileOutputStream fos = new FileOutputStream(outFile); byte[] buffer = new byte[1024]; int byteCount; while ((byteCount = is.read(buffer)) != -1) { buffer = decrypt(buffer); fos.write(buffer, 0, byteCount); } fos.flush(); is.close(); fos.close(); String file_abs_path = outFile.getAbsolutePath(); Object currentActivityThread = I.invokeStaticMethod(activity_thread, current_activity_thread, new Class[]{}, new Object[]{}); ArrayMap mPackages = (ArrayMap)I.getFieldOjbect(activity_thread, currentActivityThread, “mPackages”); WeakReference weakReference = (WeakReference) mPackages.get(package_name); ClassLoader parent = (ClassLoader)I.getFieldOjbect(“android.app.LoadedApk”, weakReference.get(), “mClassLoader”); DexClassLoader dLoader = null; File dexOpt = base.getDir(“dexOpt”, base.MODE_PRIVATE); dLoader = new DexClassLoader(file_abs_path, dexOpt.getAbsolutePath(), null, parent); I.setFieldOjbect(“android.app.LoadedApk”, “mClassLoader”, weakReference.get(), dLoader); } catch (IOException e) { e.printStackTrace(); } } public static byte[] decrypt(byte[] sourceData) { for (int i = 0; i < sourceData.length; i ){ sourceData[i] ^= 273; } return sourceData; } @Override public void onCreate() { super.onCreate(); }}

DexClassLoader加载Dex文件:DexClassLoader(dexPath, optimizedDirectory, libraryPath, parent) dexPath:目标类所在的APK或者jar包,/…/xxx.jar optimizedDirectory:从APK或者jar解压出来的dex文件存放路径 libraryPath:native库路径,可以为null parent:父类装载器,一般为当前类的装载器、

RefInvoke.javapackage org.koi.ctf20210813; import java.lang.reflect.Field;import java.lang.reflect.Method; public class I { public static Object invokeStaticMethod(String class_name, String method_name, Class[] pareTyple, Object[] pareVaules){ try { Class obj_class = Class.forName(class_name); Method method = obj_class.getMethod(method_name,pareTyple); return method.invoke(null, pareVaules); } catch (Exception e) { e.printStackTrace(); } return null; } public static Object getFieldOjbect(String class_name,Object obj, String filedName){ try { Class obj_class = Class.forName(class_name); Field field = obj_class.getDeclaredField(filedName); field.setAccessible(true); return field.get(obj); } catch (Exception e) { e.printStackTrace(); } return null; } public static void setFieldOjbect(String classname, String filedName, Object obj, Object filedVaule){ try { Class obj_class = Class.forName(classname); Field field = obj_class.getDeclaredField(filedName); field.setAccessible(true); field.set(obj, filedVaule); } catch (Exception e) { e.printStackTrace(); } } public static Object invokeMethod(String class_name, String method_name, Object obj ,Class[] pareTyple, Object[] pareVaules){ try { Class obj_class = Class.forName(class_name); Method method = obj_class.getMethod(method_name,pareTyple); return method.invoke(obj, pareVaules); } catch (Exception e) { e.printStackTrace(); } return null; }}AndroidManifest.xml

确认删除MainActivity.java,然后修改AndroidManifest.xml。

这样在执行时能解密原来dex文件。APK中的DEX文件中,不包含重要代码。

动态加载DEX(SO)

在上面的基础上,想到可以把ProxyApplication.java和RefInvoke.java中的主要代码移到so中来运行,这就是我们这种壳的主要思路。和上面的实现方式是一样的,只是换到lib中运行而已。创建一个Android 原生工程,和上面一样,在MainActivity中写一些简单代码。把dex文件加密后放入assets文件夹中。新建ProxyApplication类,继承Application,把加载Dex这部分代码提取出来放入到一个新的类AttachBaseContent中。ProxyApplication.javaimport android.app.Application;import android.content.Context; public class P extends Application { static { System.loadLibrary(“ctf20210814”); } @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); attachBase(base); } @Override public void onCreate() { super.onCreate(); } public static native void attachBase(Context base);}这里新建一个koi.cpp文件,其中有一个Java_org_koi_dexsoshell_AttachBaseContext_onAttach函数,对应Java中AttachBaseContext类下的onAttach方法。native-lib.cpp#include <jni.h>#include <string> // only update here#define ENCRYPTED_FILE_NAME “flag”#define DECRYPTED_FILE_NAME “ot.dex”#define PACKAGE_NAME “org.koi.ctf20210814” extern “C”JNIEXPORT void JNICALLJava_org_koi_ctf20210814_P_attachBase(JNIEnv *env, jclass clazz, jobject base) { jclass clz_File = env->FindClass(“java/io/File”); jclass clz_Context = env->FindClass(“android/content/Context”); jclass clz_AssetManager = env->FindClass(“android/content/res/AssetManager”); jclass clz_InputStream = env->FindClass(“java/io/InputStream”); jclass clz_FileOutputStream = env->FindClass(“java/io/FileOutputStream”); jclass clz_ActivityThread = env->FindClass(“android/app/ActivityThread”); jclass clz_ArrayMap = env->FindClass(“android/util/ArrayMap”); jclass clz_WeakReference = env->FindClass(“java/lang/ref/WeakReference”); jclass clz_LoadedApk = env->FindClass(“android/app/LoadedApk”); jclass clz_DexClassLoader = env->FindClass(“dalvik/system/DexClassLoader”); jmethodID mid_File_init = env->GetMethodID(clz_File, “<init>”, “(Ljava/io/File;Ljava/lang/String;)V”); jmethodID mid_FileOutputStream_init = env->GetMethodID(clz_FileOutputStream, “<init>”, “(Ljava/io/File;)V”); jmethodID mid_DexClassLoader_init = env->GetMethodID(clz_DexClassLoader, “<init>”, “(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V”); jmethodID mid_Context_getCacheDir = env->GetMethodID(clz_Context, “getCacheDir”, “()Ljava/io/File;”); jmethodID mid_Context_getAssets = env->GetMethodID(clz_Context, “getAssets”, “()Landroid/content/res/AssetManager;”); jmethodID mid_Context_getDir = env->GetMethodID(clz_Context, “getDir”, “(Ljava/lang/String;I)Ljava/io/File;”); jmethodID mid_AssetManager_open = env->GetMethodID(clz_AssetManager, “open”, “(Ljava/lang/String;)Ljava/io/InputStream;”); jmethodID mid_File_exists = env->GetMethodID(clz_File, “exists”, “()Z”); jmethodID mid_File_mkdirs = env->GetMethodID(clz_File, “mkdirs”, “()Z”); jmethodID mid_File_getAbsolutePath = env->GetMethodID(clz_File, “getAbsolutePath”, “()Ljava/lang/String;”); jmethodID mid_InputStream_read = env->GetMethodID(clz_InputStream, “read”, “([B)I”); jmethodID mid_InputStream_close = env->GetMethodID(clz_InputStream, “close”, “()V”); jmethodID mid_InputStream_available = env->GetMethodID(clz_InputStream, “available”, “()I”); jmethodID mid_FileOutputStream_write = env->GetMethodID(clz_FileOutputStream, “write”, “([BII)V”); jmethodID mid_FileOutputStream_flush = env->GetMethodID(clz_FileOutputStream, “flush”, “()V”); jmethodID mid_FileOutputStream_close = env->GetMethodID(clz_FileOutputStream, “close”, “()V”); jmethodID mid_ActivityThread_currentActivityThread = env->GetStaticMethodID(clz_ActivityThread, “currentActivityThread”, “()Landroid/app/ActivityThread;”); jmethodID mid_ArrayMap_get = env->GetMethodID(clz_ArrayMap, “get”, “(Ljava/lang/Object;)Ljava/lang/Object;”); jmethodID mid_WeakReference_get = env->GetMethodID(clz_WeakReference, “get”, “()Ljava/lang/Object;”); jfieldID fid_ActivityThread_mPackages = env->GetFieldID(clz_ActivityThread, “mPackages”, “Landroid/util/ArrayMap;”); jfieldID fid_LoadedApk_mClassLoader = env->GetFieldID(clz_LoadedApk, “mClassLoader”, “Ljava/lang/ClassLoader;”); try { jobject cacheDir = env->CallObjectMethod(base, mid_Context_getCacheDir); if (!env->CallBooleanMethod(cacheDir, mid_File_exists)) { env->CallBooleanMethod(cacheDir, mid_File_mkdirs); } jstring str = env->NewStringUTF(DECRYPTED_FILE_NAME); jobject outFile = env->NewObject(clz_File, mid_File_init, cacheDir, str); jobject AssetManager = env->CallObjectMethod(base, mid_Context_getAssets); jstring out_file_name = env->NewStringUTF(ENCRYPTED_FILE_NAME); jobject is = env->CallObjectMethod(AssetManager, mid_AssetManager_open, out_file_name); jobject fos = env->NewObject(clz_FileOutputStream, mid_FileOutputStream_init, outFile); jint file_size = env->CallIntMethod(is, mid_InputStream_available); jbyteArray buffer = env->NewByteArray(file_size); env->CallIntMethod(is, mid_InputStream_read, buffer); //read jbyte* p_bt_ary = (jbyte*)env->GetByteArrayElements(buffer, 0); // here you can add decryption function. for (jint i = 0; i < file_size; i) { p_bt_ary[i] ^= 273; } env->SetByteArrayRegion(buffer, 0, file_size, p_bt_ary); env->CallVoidMethod(fos, mid_FileOutputStream_write, buffer, 0, file_size); env->DeleteLocalRef(buffer); env->CallVoidMethod(fos, mid_FileOutputStream_flush); env->CallVoidMethod(is, mid_InputStream_close); env->CallVoidMethod(fos, mid_FileOutputStream_close); jstring file_abs_path = (jstring) env->CallObjectMethod(outFile, mid_File_getAbsolutePath); jobject currentActivityThread = env->CallStaticObjectMethod(clz_ActivityThread, mid_ActivityThread_currentActivityThread); jobject mPackages = env->GetObjectField(currentActivityThread, fid_ActivityThread_mPackages); jstring package_name = env->NewStringUTF(PACKAGE_NAME); jobject weakReference = env->CallObjectMethod(mPackages, mid_ArrayMap_get, package_name); jobject loadedApk = env->CallObjectMethod(weakReference, mid_WeakReference_get); jobject parent = env->GetObjectField(loadedApk, fid_LoadedApk_mClassLoader); jstring jstr_dexOpt = env->NewStringUTF(“dexOpt”); jobject dexOpt = env->CallObjectMethod(base, mid_Context_getDir, jstr_dexOpt, 0); jstring dexOpt_abs_path = (jstring) env->CallObjectMethod(dexOpt, mid_File_getAbsolutePath); jstring str_null = env->NewStringUTF(“”); jobject dLoader = env->NewObject(clz_DexClassLoader, mid_DexClassLoader_init, file_abs_path, dexOpt_abs_path, str_null, parent); env->SetObjectField(loadedApk, fid_LoadedApk_mClassLoader, dLoader); } catch (…) {}}

这个例子中,进行加密解密的操作,可以根据实际情况进行修改。JNI中有部分代码可以提取到JNI_OnLoad或者initarray中进行处理。JNI中的所有字符串可以进行一些处理,不直接暴露在源码中。

AndroidManifest.xml

按照上面的方法进行修改。注意:关闭minifyEnabled。

3

ELF文件壳

在学习这部分内容之前需要熟悉ELF的文件格式。ELF节加密主要思想

ida会提示elf文件错误。

节表解析错误。

看雪ID:nisosaikou

https://bbs.pediy.com/user-home-922119.htm

#往期推荐

1. InCTF 内核Pwn之 Kqueue

2.HITB CTF 2018 gundam分析

3.CVE-2010-2883漏洞分析与复现

4.D-Link DIR-645路由器溢出分析

5.API 钩取:逆向分析之“花”

6. Ring3注入学习:导入表注入

发表评论

登录后才能评论