Android APK的加固方法
看雪论坛作者ID:nisosaikou
1
混淆、膨胀
混淆
#压缩级别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壳
动态加载APK
源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
把释放的apk进行解密。
把源apk中的lib目录中的文件复制到当前程序(壳)的路径下。
创建一个新的DexClassLoader,替换到父节点的DexClassLoader。
DexClassLoader 继承自BaseDexClassLoader,这个比较灵活,每个参数都可以自定义,我们一般用这个来加载自定义的apk/dex/jar文件。
@Override
protected 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);
}
加载源apk资源。
获取manifest.xml中记录的源apk的启动类名。
设置ActivityThread信息(android.app.ActivityThread->currentActivityThread)。
代码例子
@Override
public 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画面的事件。
类结构参考
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");
RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication", loadedApkInfo, null);
把当前的应用,从现有的应用中移除掉,然后再把新构建的加入到里面去。
Object oldApplication = RefInvoke.getFieldOjbect("android.app.ActivityThread", currentActivityThread, "mInitialApplication");
ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke.getFieldOjbect("android.app.ActivityThread", currentActivityThread, "mAllApplications");
mAllApplications.remove(oldApplication);
更新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 app = (Application) RefInvoke.invokeMethod("android.app.LoadedApk", "makeApplication", loadedApkInfo, new Class[] { boolean.class, Instrumentation.class }, new Object[] { false, null });
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);
}
app.onCreate();
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;
}
}
<?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>
加壳步骤
src.apk:源APK。 des.apk:壳APK。 DexFixed.jar:Dex工具。 classes.dex:des.apk中的classes.dex。 res:源APK中的文件夹。 resources.arsc:源APK中的文件。
总结
动态加载DEX(Java)
项目实现demo代码
简单来说,这里存放git的链接。
源工程
新建一个简单功能的 Android 工程。 创建assets文件夹。
加密DEX
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;
}
}
壳工程
ProxyApplication.java
package 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(dexPath, optimizedDirectory, libraryPath, parent)
dexPath:目标类所在的APK或者jar包,/.../xxx.jar
optimizedDirectory:从APK或者jar解压出来的dex文件存放路径
libraryPath:native库路径,可以为null
parent:父类装载器,一般为当前类的装载器、
RefInvoke.java
package 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
动态加载DEX(SO)
ProxyApplication.java
import 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);
}
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 JNICALL
Java_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 (...) {}
}
AndroidManifest.xml
注意:关闭minifyEnabled。
3
ELF文件壳
ELF节加密
主要思想
代码
#include <jni.h>
#include <string>
#define SECTION_NAME ".koitext"
#define JNIHIDDEN __attribute__((visibility("hidden")))
// save the result.
int fw[40] = {13, 18, 14, 64, 11, 65, 16, 14, 20, 14, 11, 14, 18,
61, 12, 13, 60, 60, 20, 62, 16, 61, 61, 64, 63, 63, 15, 18, 12, 63, 14, 64,
13, 18, 14, 64, 11, 65, 16, 14};
int fs[38];
void str2ints (const char* fw, int* results);
char* jstring2charAry(JNIEnv* env, jstring jstr);
extern "C"
JNIEXPORT __attribute__((section(SECTION_NAME))) jboolean JNICALL
Java_org_koi_ctf20210821_MainActivity_checkflag(JNIEnv *env, jobject thiz, jstring flag) {
char fg[]="flag{helloboy_ewri346hHeewr34dr}";
str2ints(jstring2charAry(env, flag), fs);
for (int i = 0; i < strlen(fg); ++i) {
if(fw[i] != fs[i] )
return false;
}
return true;
}
__attribute__((section(SECTION_NAME))) void str2ints (const char* fw, int* results)
{
for (int mX4WyHKgmwSPY1V = 0; mX4WyHKgmwSPY1V < 32; mX4WyHKgmwSPY1V++){results[mX4WyHKgmwSPY1V] = fw[mX4WyHKgmwSPY1V];}
for (int _ZKdmdmEjiQ_Ouw = 0; _ZKdmdmEjiQ_Ouw < 32; _ZKdmdmEjiQ_Ouw++){results[_ZKdmdmEjiQ_Ouw] = results[_ZKdmdmEjiQ_Ouw] + 3;}
for (int zbTK_I56tB0GevN = 0; zbTK_I56tB0GevN < 32; zbTK_I56tB0GevN++){results[zbTK_I56tB0GevN] = results[zbTK_I56tB0GevN] + 10;}
for (int DfeXBWD6dcNPXKo = 0; DfeXBWD6dcNPXKo < 32; DfeXBWD6dcNPXKo++){results[DfeXBWD6dcNPXKo] = results[DfeXBWD6dcNPXKo] - 58;}
for (int jqJhXnPQwPYi2G6 = 0; jqJhXnPQwPYi2G6 < 32; jqJhXnPQwPYi2G6++){results[jqJhXnPQwPYi2G6] = results[jqJhXnPQwPYi2G6] - 66;}
for (int xA7fCVlKruHZC4Y = 0; xA7fCVlKruHZC4Y < 32; xA7fCVlKruHZC4Y++){results[xA7fCVlKruHZC4Y] = results[xA7fCVlKruHZC4Y] + 66;}
for (int sGVbaq_poAxfJ3O = 0; sGVbaq_poAxfJ3O < 32; sGVbaq_poAxfJ3O++){results[sGVbaq_poAxfJ3O] = results[sGVbaq_poAxfJ3O] + 8;}
for (int EIGWrEGI6UaAjH8 = 0; EIGWrEGI6UaAjH8 < 32; EIGWrEGI6UaAjH8++){results[EIGWrEGI6UaAjH8] = results[EIGWrEGI6UaAjH8] + 49;}
for (int nHJAUmNRoQs5M9k = 0; nHJAUmNRoQs5M9k < 32; nHJAUmNRoQs5M9k++){results[nHJAUmNRoQs5M9k] = results[nHJAUmNRoQs5M9k] + 11;}
for (int NzhuxVIobubHcRM = 0; NzhuxVIobubHcRM < 32; NzhuxVIobubHcRM++){results[NzhuxVIobubHcRM] = results[NzhuxVIobubHcRM] - 64;}
for (int Wa46hlZr0UFGqFu = 0; Wa46hlZr0UFGqFu < 32; Wa46hlZr0UFGqFu++){results[Wa46hlZr0UFGqFu] = results[Wa46hlZr0UFGqFu] + 4;}
}
JNIHIDDEN __attribute__((section(SECTION_NAME))) char* jstring2charAry(JNIEnv* env, jstring jstr)
{
jclass jcls_String = env->FindClass("java/lang/String");
jmethodID jmid_toCharArray = env->GetMethodID(jcls_String, "toCharArray", "()[C");
jmethodID jmid_length = env->GetMethodID(jcls_String, "length", "()I");
jcharArray charArray = (jcharArray)env->CallObjectMethod(jstr, jmid_toCharArray);
jint len = env->CallIntMethod(jstr, jmid_length);
char* pString = new char[len];
pString[len] = 0;
jboolean fals = false;
for (int i = 0; i < len; ++i) {
pString[i] = env->GetCharArrayElements(charArray, &fals)[i];
}
return pString;
}
#include<sys/types.h>
#include<unistd.h>
#include <sys/mman.h>
#include <elf.h>
void init_native_Add() __attribute__((constructor));
unsigned long getLibAddr();
// loaded so file
#define SO_LIB_FILE_NAME "libctf20210821.so"
void init_native_Add(){
char name[15];
unsigned int nblock;
unsigned int nsize;
unsigned long base;
unsigned long text_addr;
unsigned int i;
Elf32_Ehdr *ehdr;
Elf32_Shdr *shdr;
base=getLibAddr(); //在/proc/id/maps文件中找到我们的so文件,活动so文件地址
ehdr=(Elf32_Ehdr *)base;
text_addr=ehdr->e_shoff+base;//加密节的地址
nblock=ehdr->e_entry >>16;//加密节的大小
nsize=ehdr->e_entry&0xffff;//加密节的大小
printf("nblock = %d\n", nblock);
//修改内存权限
if(mprotect((void *) (text_addr / PAGE_SIZE * PAGE_SIZE), 4096 * nsize, PROT_READ | PROT_EXEC | PROT_WRITE) != 0){
puts("mem privilege change failed");
}
//进行解密,是针对加密算法的
for(i=0;i<nblock;i++){
char *addr=(char*)(text_addr+i);
*addr=~(*addr);
}
if(mprotect((void *) (text_addr / PAGE_SIZE * PAGE_SIZE), 4096 * nsize, PROT_READ | PROT_EXEC) != 0){
puts("mem privilege change failed");
}
puts("Decrypt success");
}
//获取到SO文件加载到内存中的起始地址,只有找到起始地址才能够进行解密;
unsigned long getLibAddr(){
unsigned long ret=0;
char name[] = SO_LIB_FILE_NAME;
char buf[4096];
char *temp;
int pid;
FILE *fp;
pid=getpid();
sprintf(buf,"/proc/%d/maps",pid); //这个文件中保存了进程映射的模块信息 cap /proc/id/maps 查看
fp=fopen(buf,"r");
if(fp==NULL){
puts("open failed");
goto _error;
}
while (fgets(buf,sizeof(buf),fp)){
if(strstr(buf,name)){
temp = strtok(buf, "-"); //分割字符串,返回 - 之前的字符
ret = strtoul(temp, NULL, 16); //获取地址
break;
}
}
_error:
fclose(fp);
return ret;
}
#include <stdio.h>
#include <cstdint>
#include <string.h>
typedef uint32_t Elf32_Addr; // Program address
typedef uint32_t Elf32_Off; // File offset
typedef uint16_t Elf32_Half;
typedef uint32_t Elf32_Word;
typedef int32_t Elf32_Sword;
enum {
EI_MAG0 = 0, // File identification index.
EI_MAG1 = 1, // File identification index.
EI_MAG2 = 2, // File identification index.
EI_MAG3 = 3, // File identification index.
EI_CLASS = 4, // File class.
EI_DATA = 5, // Data encoding.
EI_VERSION = 6, // File version.
EI_OSABI = 7, // OS/ABI identification.
EI_ABIVERSION = 8, // ABI version.
EI_PAD = 9, // Start of padding bytes.
EI_NIDENT = 16 // Number of bytes in e_ident.
};
struct Elf32_Ehdr {
unsigned char e_ident[EI_NIDENT]; // ELF Identification bytes
Elf32_Half e_type; // Type of file (see ET_* below)
Elf32_Half e_machine; // Required architecture for this file (see EM_*)
Elf32_Word e_version; // Must be equal to 1
Elf32_Addr e_entry; // Address to jump to in order to start program
Elf32_Off e_phoff; // Program header table's file offset, in bytes
Elf32_Off e_shoff; // Section header table's file offset, in bytes
Elf32_Word e_flags; // Processor-specific flags
Elf32_Half e_ehsize; // Size of ELF header, in bytes
Elf32_Half e_phentsize; // Size of an entry in the program header table
Elf32_Half e_phnum; // Number of entries in the program header table
Elf32_Half e_shentsize; // Size of an entry in the section header table
Elf32_Half e_shnum; // Number of entries in the section header table
Elf32_Half e_shstrndx; // Sect hdr table index of sect name string table
unsigned char getFileClass() const { return e_ident[EI_CLASS]; }
unsigned char getDataEncoding() const { return e_ident[EI_DATA]; }
};
// Program header for ELF32.
struct Elf32_Phdr {
Elf32_Word p_type; // Type of segment
Elf32_Off p_offset; // File offset where segment is located, in bytes
Elf32_Addr p_vaddr; // Virtual address of beginning of segment
Elf32_Addr p_paddr; // Physical address of beginning of segment (OS-specific)
Elf32_Word p_filesz; // Num. of bytes in file image of segment (may be zero)
Elf32_Word p_memsz; // Num. of bytes in mem image of segment (may be zero)
Elf32_Word p_flags; // Segment flags
Elf32_Word p_align; // Segment alignment constraint
};
// Section header.
struct Elf32_Shdr {
Elf32_Word sh_name; // Section name (index into string table)
Elf32_Word sh_type; // Section type (SHT_*)
Elf32_Word sh_flags; // Section flags (SHF_*)
Elf32_Addr sh_addr; // Address where section is to be loaded
Elf32_Off sh_offset; // File offset of section data, in bytes
Elf32_Word sh_size; // Size of section, in bytes
Elf32_Word sh_link; // Section type-specific header table index link
Elf32_Word sh_info; // Section type-specific extra information
Elf32_Word sh_addralign; // Section address alignment
Elf32_Word sh_entsize; // Size of records contained within the section
};
long get_file_size(FILE* pf);
int main()
{
char elf_name[64] = "C:\\Users\\koi\\Desktop\\libnative-lib.so";
char want2encrypt_section_name[] = ".mytext";
FILE* pf_elf = fopen(elf_name, "rb");
long sz_file = get_file_size(pf_elf);
char* file_buf = new char[sz_file];
fread(file_buf, sz_file, 1, pf_elf);
Elf32_Ehdr* ehdr = (Elf32_Ehdr*)(file_buf);
// 字符串节头表的位置
Elf32_Shdr* shdrstr = (Elf32_Shdr*)(file_buf + ehdr->e_shoff + sizeof(Elf32_Shdr) * ehdr->e_shstrndx);
char* sh_str = (char*)(file_buf + shdrstr->sh_offset);//偏移到字符串表
Elf32_Shdr* shdr = (Elf32_Shdr*)(file_buf + ehdr->e_shoff);
int encrypt_foffset = 0;
int encrypt_size = 0;
for (int i = 0; i < ehdr->e_shnum; i++, shdr++)
{
//根据字符串表的名称比较
if (strcmp(sh_str + shdr->sh_name, want2encrypt_section_name) == 0)
{
encrypt_foffset = shdr->sh_offset;
encrypt_size = shdr->sh_size;
break;
}
}
char* content = (char*)(file_buf + encrypt_foffset);
int block_size = 16;
int nblock = encrypt_size / block_size;
int nsize = encrypt_foffset / 4096 + (encrypt_foffset % 4096 == 0 ? 0 : 1);
printf("base = 0x%x, length = 0x%x\n", encrypt_foffset, encrypt_size);
printf("nblock = %d, nsize = %d\n", nblock, nsize);
//将节的地址和大小写入
ehdr->e_entry = (encrypt_size << 16) + nsize;
ehdr->e_shoff = encrypt_foffset; //节的地址
//加密
for (int i = 0; i < encrypt_size; i++) {
content[i] = ~content[i];
}
strcat(elf_name, "_m");
FILE* m_elf_file = fopen(elf_name, "wb");
fwrite(file_buf, sz_file, 1, m_elf_file);
return 0;
}
long get_file_size(FILE* pf)
{
long cur_pos = ftell(pf);
fseek(pf, 0, SEEK_END);
long sz_file = ftell(pf);
fseek(pf, cur_pos, SEEK_SET);
return sz_file;
}
看雪ID:nisosaikou
https://bbs.pediy.com/user-home-922119.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!