我们在第一篇文章中简单的跑了一个很简单的壳,但是那种加密方式相当的挫,这里演示一种比较贴近现实的一种Demo:在开发完整个工程后,不使用新的APK去加载,而是提取Dex,加密后存到assets文件夹,资源文件等都不进行处理,然后重新写一个ProtectApplication入口,在这个入口进行原始Dex的解密加载
这样就解决掉了不能加载资源的问题
再进一步说,其实我们加壳的代码是可以固定的,步骤如下:
- 反编译整个项目,除Dex之外所有的文件全部提取出来
- 提取Dex,进行加密操作
- 新建一个工程,包名与其余的代码全部模拟待加壳样本
- 整合加密的源Dex文件
- 添加壳代码,并修改AndroidManifest.xml的入口Application
- 回编译,签名
这次我们创建两个Activity,分别有两个一样的界面,第一个界面的按钮点击后跳到第二个界面,第二个界面的按钮点击后跳到第一个界面
MainActivity.java
package com.wnagzihxa1n.sourceapk;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
private TextView textView;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.view1_textView);
button = (Button) findViewById(R.id.view1_button);
textView.setText("I am MainActivity");
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent();
intent.setClass(MainActivity.this, SecondActivity.class);
startActivity(intent);
MainActivity.this.finish();
}
});
}
}
SecondActivity.java
package com.wnagzihxa1n.sourceapk;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class SecondActivity extends Activity {
private TextView textView;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
textView = (TextView) findViewById(R.id.view2_textView);
button = (Button) findViewById(R.id.view2_button);
textView.setText("I am SecondActivity");
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent();
intent.setClass(SecondActivity.this, MainActivity.class);
startActivity(intent);
SecondActivity.this.finish();
}
});
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.wnagzihxa1n.sourceapk.MainActivity">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textAlignment="center"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:id="@+id/view1_textView"/>
<Button
android:id="@+id/view1_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button"
android:layout_below="@+id/view1_textView"
android:layout_alignParentStart="true"/>
</RelativeLayout>
activity_second.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.wnagzihxa1n.sourceapk.SecondActivity">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textAlignment="center"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:id="@+id/view2_textView"/>
<Button
android:id="@+id/view2_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/view2_textView"
android:layout_centerHorizontal="true"
android:text="Button"/>
</RelativeLayout>
最后在AndroidManifest.xml添加Activity
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.wnagzihxa1n.sourceapk"
xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".SecondActivity"></activity>
</application>
</manifest>
测试一下,没啥问题就编译签名为SourceAPK.apk
接下来按理来说,我们要新建一个工程,然后把SourceAPK.apk里所有的资源文件什么的都反编译出来拷贝到ProtectAPK工程里,保证除了Java代码外,其余的文件都和待加壳文件的开发环境是一样的
那么在这里,我们省略反编译这个步骤,直接就在SourceAPK工程里进行修改,效果是一样的
把代码全都删掉,然后加入壳代码
ProtectApplication.java
package com.wnagzihxa1n.sourceapk;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import android.app.Application;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.util.ArrayMap;
import android.util.Log;
import dalvik.system.DexClassLoader;
public class ProtectApplication extends Application {
Context context = ProtectApplication.this;
ApplicationInfo applicationInfo = null;
private String apkPath;
private String odexPath;
private String libPath;
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
applicationInfo = ProtectApplication.this.getApplicationInfo();
File odex = this.getDir("targetOdex", MODE_PRIVATE);
File libs = this.getDir("targetLib", MODE_PRIVATE);
apkPath = odex.getAbsolutePath() + "/targetAPK.apk";
odexPath = odex.getAbsolutePath();
libPath = libs.getAbsolutePath();
Log.i("wnagzihxa1n", "apkPath : " + apkPath);
Log.i("wnagzihxa1n", "odexPath : " + odexPath);
Log.i("wnagzihxa1n", "libPath : " + libPath);
releaseDexFile();
try {
File apkFile = new File(apkPath);
if (!apkFile.exists()) {
Log.i("wnagzihxa1n", "Top miss, Mid miss, Bot miss, All miss");
return;
}
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 wr = (WeakReference) mPackages.get(packageName);
DexClassLoader dLoader = new DexClassLoader(apkPath, odexPath, libPath, (ClassLoader) RefInvoke.getFieldOjbect("android.app.LoadedApk", wr.get(), "mClassLoader"));
RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader", wr.get(), dLoader);
try {
Object actObj = dLoader.loadClass("com.wnagzihxa1n.sourceapk.MainActivity");
Log.i("wnagzihxa1n", "ActObj : " + actObj);
} catch (Exception e) {
e.printStackTrace();
Log.i("wnagzihxa1n", "Activity : " + Log.getStackTraceString(e));
}
} catch (Exception e) {
e.printStackTrace();
Log.i("wnagzihxa1n", "Error : " + Log.getStackTraceString(e));
}
}
@Override
public void onCreate() {
super.onCreate();
Log.i("wnagzihxa1n", "onCreate()");
}
public void releaseDexFile() {
byte xor_key = 0x66;
try {
InputStream inputStream = context.getAssets().open("a", MODE_PRIVATE);
File dexFile_save = new File(apkPath);
FileOutputStream fileOutputStream = new FileOutputStream(dexFile_save);
int myDexlength = inputStream.available();
Log.i("wnagzihxa1n", "DexLength : " + myDexlength);
byte[] buffer_temp = new byte[myDexlength];
inputStream.read(buffer_temp);
fileOutputStream.write(buffer_temp);
fileOutputStream.flush();
inputStream.close();
fileOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
Log.i("wnagzihxa1n", "Releasing myAPK.apk failed\n" + e);
}
}
}
RefInvoke.java
package com.wnagzihxa1n.sourceapk;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Field;
public class RefInvoke {
public static Object invokeStaticMethod(String class_name, String method_name, Class[] pareType, Object[] pareVaules) {
try {
Class obj_class = Class.forName(class_name);
Method method = obj_class.getMethod(method_name, pareType);
return method.invoke(null, pareVaules);
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
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 (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (ClassNotFoundException 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 (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
public static Object getStaticFieldOjbect(String class_name, String filedName) {
try {
Class obj_class = Class.forName(class_name);
Field field = obj_class.getDeclaredField(filedName);
field.setAccessible(true);
return field.get(null);
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException 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 (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void setStaticOjbect(String class_name, String filedName, Object filedVaule) {
try {
Class obj_class = Class.forName(class_name);
Field field = obj_class.getDeclaredField(filedName);
field.setAccessible(true);
field.set(null, filedVaule);
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
添加一个assets文件夹,把刚才编译出来的SourceAPK.apk解压缩,把classes.dex重命名为a放进去
最后修改AndroidManifest.xml,添加Application入口
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.wnagzihxa1n.sourceapk"
xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:name=".ProtectApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".SecondActivity"></activity>
</application>
</manifest>
运行调试
点击后跳到第二个Activity
那么整个壳的雏形就出来了