Android开发学习之路-加固实践

Posted 东月之神

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android开发学习之路-加固实践相关的知识,希望对你有一定的参考价值。

前言

  • 起因也是想要看一些优秀的程序某些内容是怎么实现的,所以需要脱壳,但是对于怎么加固也还是比较感兴趣的,加固涉及到的安全的内容很多很多,这里也只是用个简单的例子来过把瘾,参考了些文章,只是为了理顺思路,也为之后的脱壳做个准备。
  • 其实加固可以理解为,一个应用程序,利用了插件化的功能,启动了另一个apk,而这个apk是经过加密的,壳应用会在加载需要启动的apk的时候去解密。这样即使逆向了dex文件,也看不出来原先apk的代码。
  • 为此准备了源程序,壳工程,加固工具以及一些脚本来实现。

1.ApkTest

首先我们准备下需要被加固的文件apk,这里我们自己新建工程写一个例子,ApkTestApplication如下:
这里就打印下ApkTestApplication的onCreate

override fun onCreate() 
    super.onCreate()
    Log.i("ApkTestApplication", "Apk Test Application onCreate:$this")

主工程的MainActivity如下,主要就是显示下是ApkTest这个工程,并且可以跳转到第二个页面

class MainActivity : BaseActivity() 

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        Log.d("MainActivity", "onCreate")
        setContentView(R.layout.activity_test)
        tvName.text = showStr()
        btNext.setOnClickListener 
            gotoSecondActivity()
        
    

    private fun showStr(): String 
        return "Hello,I am ApkTest!"
    

    private fun gotoSecondActivity() 
        startActivity(Intent(this, SecondActivity::class.java))
    

这个工程没有什么好讲的了,很普通的一个android工程。

详细代码可以参考:ApkTest


2.ShellApk

既然是加固,那么就需要有个壳,可以把待加固的应用程序经过加密后放到壳app里面,这样,即便被反编译后还是看不到原应用的dex文件内容。

  • App启动

    防止壳程序运行自己的代码,所以需要在attachBaseContext中实现加载源程序。

  • 解密源程序

    新建payload_odex文件,并在其中创建playload.apk文件用于存放源程序的apk,从壳程序的dex文件中读取源程序的apk文件,解密存放。

  • 初始化源程序的ClassLoader,设置壳程序的classLoader为源程序ClassLoader

    获取主线程对象,创建源程序的DexClassLoader对象,加载apk内的类和本地代码,然后把当前进程的mClassLoader设置成源程序apk的DexClassLoader。尝试检测下源程序的MainActivity存不存在判断classLoader是否替换完成。

  • 反射生成正确的Application对象

    读取Manifest文件中定义的源程序的application类,设置源程序的Application,并把当前进程的application设置为null,删除老的application,使用源程序的application。反射设置ActivityThread中的Application信息

  • 设置provider相关的配置

  • 调用源程序application的onCreate方法

  • 源程序正常运行

AndroidManifest.xml代码如下:

<application
            android:name="com.jared.shellapk.ShellApplication"
            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">
        <meta-data android:name="APPLICATION_CLASS_NAME" android:value="com.jared.apktest.ApkTestApplication"/>

        <activity android:name="com.jared.apktest.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <activity android:name="com.jared.apktest.SecondActivity" />

    </application>

这里定义了APPLICATION_CLASS_NAME为源程序的application类,埋坑了MainActivty和SecondActivity用于ApkTest程序启动后的调用。

详细代码可以参考: ShellApk


3.DexPackTool

加固工具其实就是在壳程序的dex文件中加入了源程序的apk文件,这里就是在ShellApk的class.dex文件中加入了ApkTest.apk文件。
首先我们了解下Dex文件的结构:

  • Dex文件
Dex文件结构
Dex文件头部
字符串索引区
类型索引区
方法索引区
原型索引区
类定义区
数据区
链接数据区

我们如果要修改dex文件,就需要更新Dex文件头部信息,其中头部信息包括了:

Dex文件头部
magic[8]dex的文件标识,一般称为魔术
checksumdex文件的校验和,通过它可以判断dex文件是否被损坏或者被篡改
signature[kSHA1DigestLen]检验dex文件,其实就是把整个dex文件用SHA-1签名得到的一个值
fileSize整个文件的大小,占用4个字节
headerSize头结构的大小,占用4个字节
  • 加固后的文件结构
加固后Dex文件
ShellApk的Dex文件修改了文件头的ShellApk的classes.dex
加密的ApkTest的Apk经过了异或加密后的ApkTest的apk文件
加密的ApkTest的大小为了解密读取对应的大小,需要知道加密后的ApkTest的文件大小
  • 工具代码如下
File payloadSrcFile = new File("files/SourceApk.apk");   // 需要加壳的源程序
System.out.println("apk size:"+payloadSrcFile.length( ));
File packDexFile = new File("files/SourceApk.dex");  // 壳程序dex
byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile)); // 以二进制形式读出源apk,并进行加密处理
byte[] packDexArray = readFileBytes(packDexFile); // 以二进制形式读出dex
int payloadLen = payloadArray.length;
int packDexLen = packDexArray.length;
int totalLen = payloadLen + packDexLen + 4; // 多出4字节是存放长度的
byte[] newdex = new byte[totalLen]; // 申请了新的长度
// 添加解壳代码
System.arraycopy(packDexArray, 0, newdex, 0, packDexLen); // 先拷贝dex内容
// 添加加密后的解壳数据
System.arraycopy(payloadArray, 0, newdex, packDexLen, payloadLen); // 再在dex内容后面拷贝apk的内容
// 添加解壳数据长度
System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4); // 最后4字节为长度
// 修改DEX file size文件头
fixFileSizeHeader(newdex);
// 修改DEX SHA1 文件头
fixSHA1Header(newdex);
// 修改DEX CheckSum文件头
fixCheckSumHeader(newdex);

String str = "files/classes.dex"; // 创建一个新文件
File file = new File(str);
if (!file.exists()) 
    file.createNewFile();


FileOutputStream localFileOutputStream = new FileOutputStream(str);
localFileOutputStream.write(newdex); // 将新计算出的二进制dex数据写入文件
localFileOutputStream.flush();
localFileOutputStream.close();

这里,我们运行打包了一个DexPackTool.jar文件,用以后续的加固工作。

详细代码可以参考: DexPackTool


4.脚本执行加固

以上我们可以生成三个文件

  • 源程序ApkTest工程的,命名为SourceApk.apk,
  • 壳程序ShellApk工程,名为app-debug.apk
  • 加固工具, 名为DexPackTool.jar

为了方便我们写一个脚本来实现加固

rm app-test.apk

python getDex.py

java -jar DexPackTool.jar
cp files/classes.dex .

# exchange dex
aapt r app-debug.apk classes.dex
aapt a app-debug.apk classes.dex

cp app-debug.apk app-test.apk

#sign
./resign.sh

adb install app-test.apk
  • 拷贝SourceApk.apk到files文件夹下
  • 通过getDex.py从app-debug.apk中获取classes.dex文件,拷贝到files文件夹下
  • DexPackTool把SourceApk.apk加密后加入到classes.dex文件末尾,重新生成新的classes.dex文件
  • 通过aapt命令删除app-debug.apk的classes.dex文件,并加入加固后新生成的classes.dex文件
  • 重新签名apk,安装

详细代码可以参考: ShellTools


5.验证

我们运行ShellApk程序可以看下加入的log信息:

12-25 14:30:22.062 19460-19460/? D/ResourcesManager: creating new AssetManager and set to /data/app/com.jared.shellapk-1/base.apk
12-25 14:30:22.082 19460-19460/? I/ShellApplication: apk size:0
12-25 14:30:22.593 19460-19460/? D/ShellApplication: apk size: 6103481
12-25 14:30:22.593 19460-19460/? I/System.out: 22b245
12-25 14:30:26.887 19460-19460/com.jared.shellapk I/ShellApplication: classloader:dalvik.system.DexClassLoader[DexPathList[[zip file "/data/data/com.jared.shellapk/app_payload_odex/payload.apk"],nativeLibraryDirectories=[/data/data/com.jared.shellapk/app_payload_lib, /vendor/lib, /system/lib]]]
12-25 14:30:26.887 19460-19460/com.jared.shellapk I/ShellApplication: actObj:class com.jared.apktest.MainActivity
12-25 14:30:26.887 19460-19460/com.jared.shellapk I/ShellApplication: onCreate
12-25 14:30:26.897 19460-19460/com.jared.shellapk I/ShellApplication: app:com.jared.apktest.ApkTestApplication@3839a4b9
12-25 14:30:26.907 19460-19460/com.jared.shellapk I/ApkTestApplication: Apk Test Application onCreate:com.jared.apktest.ApkTestApplication@3839a4b9
12-25 14:30:26.917 19460-19460/com.jared.shellapk D/MainActivity: onCreate

运行的是ShellApk,但是实际上最后都是运行ApkTest工程的效果。

参考:

以上是关于Android开发学习之路-加固实践的主要内容,如果未能解决你的问题,请参考以下文章

Android开发学习之路-脱壳反编译

Android开发学习之路-脱壳反编译

Android开发学习之路-Flutter混合开发实践

Android开发学习之路-Flutter混合开发实践

Android开发学习之路--在Android应用中愉快地写C/C++代码

腾讯bugly干货分享微信Android热补丁实践演进之路