几维加固分析(学习加固版本)

博主分享了对一款安卓应用的深入分析,发现其采用了简易的几维加固技术,尤其是KiwiVM的加密过程如期末大作业般粗糙,主要涉及JNI、DEX加密和So文件加载。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这篇帖子起因是前段时间想挂个协和的号,随便拿了个测试机下载好APP后打开卡在启动界面,以为是测试机root之类的东西被检测到后拒绝启动了。

于是准备看看怎么回事,发现这玩意儿使用了几维加固,正准备分析分析,突然发现测试机WiFi没连上,连上网之后就正常了,于是就把它丢在一边没管了。

然后过年在家闲着没事,想起还有这么个东西,正好几维加固以前没有遇到过,于是又在垃圾桶里把它翻出来了。

分析完后,实在是忍不住想吐槽一下,,不知道是不是外包公司没给钱,这加固做得就跟期末大作业水平差不多,全程就像读源码一样,,和前一篇帖子分析的乐固差远了。。

分析

老规矩,先把apk用jadx反编译,从AndroidManifest.xml找到application的类com.kiwivm.security.StubApplication,

发现这个类的attachBaseContext和onCreate方法都是native方法,然后有个静态代码块。

在SDK大于等于29(android9)的时候,先解除对隐藏api的反射限制。

然后init方法判断cpu架构,从apk的assets目录释放对应的so进行加载。

选择分析arm32的文件kadp_armeabi。

用ida加载kadp_armeabi,发现没有.init_array段,于是直接看JNI_OnLoad函数,代码很简单,只调用了init函数

init函数的代码也很简单,判断了下sdk版本和vm版本,然后注册了两个native方法

看下off_2C124的数据,注册了attachBaseContext和onCreate这两个方法

接着看native_attachBaseContext,首先调用了init_class

init_class函数中获取了一些字段和方法的id进行缓存

回到native_attachBaseContext,获取dalvik.system.DexFile的构造方法缓存起来,然后调用函数extractDexToMemMap_zlib从apk获取dex文件,接着调用函数cfile_init对dex进行解析

函数extractDexToMemMap_zlib通过zlib库遍历apk中的文件,获取classes.dex文件并mmap

函数cfile_init先判断文件类型,找到dex的基址,计算dex所属内存页,修改属性,用于后面解密直接修改

然后解析一些用于解密dex的字段

dex中用于解密的数据从dex的data数据之后开始,即起始位置的偏移为 dex_header.data_size+dex_header.data_off

结构如下:

magic字段内容如下,不匹配则解析失败

dex文件中的实际数据如下,

0x2e60处是magic

0x2e68处是dexCount,所以加密的dex文件数量为10个

0x2e6c处是dataOff,所以加密的dex数据起始位置为0x2e60+0x60=0x2ec0

0x2e70处是第一个KiwiDexItem,所以第一个加密的dex文件大小为0x8f84f8,起始位置为0x2ec0+0=0x2ec0

继续回到native_attachBaseContext,重新构造dexElements并替换,然后多线程执行函数thread_loadDex

函数thread_loadDex中主要调用mem_loadDex

函数mem_loadDex中首先调用函数cfile_load_file

函数cfile_load_file调用函数decrypt_buf进行dex解密

函数decrypt_buf的解密很简单,dex只有前9字节被加密了,算法是一个简单的异或

回到函数mem_loadDex中,调用write_mix_dex、openmemory_load_dex和load_dex_by_byteBuffer进行dex加载,

由于我的设备是sdk27,这几个函数中实际只有load_dex_by_byteBuffer这个函数有用。

然后通过make_dex_elements将DexFile添加到列表中

dex加载完成后,继续回到native_attachBaseContext,

首先通过reback_prot恢复内存页属性

然后通过old_application获取原始application的类名,原始application的类名作为metaData存放在AndroidManifest.xml,name为KWS_MAIN_APP

然后就是app相关的字段替换,就不分析了

脱壳代码

#include<fstream>
#include<string>
 
 
typedef unsigned char u1;
typedef unsigned int  u4;
 
struct DexHeader {
    u1  magic[8];
    u4  checksum;
    u1  signature[20];
    u4  fileSize;
    u4  headerSize;
    u4  endianTag;
    u4  linkSize;
    u4  linkOff;
    u4  mapOff;
    u4  stringIdsSize;
    u4  stringIdsOff;
    u4  typeIdsSize;
    u4  typeIdsOff;
    u4  protoIdsSize;
    u4  protoIdsOff;
    u4  fieldIdsSize;
    u4  fieldIdsOff;
    u4  methodIdsSize;
    u4  methodIdsOff;
    u4  classDefsSize;
    u4  classDefsOff;
    u4  dataSize;
    u4  dataOff;
};
 
 
struct KiwiDexItem {
    u4 dexSize;
    u4 dexOff;
};
 
struct KiwiHeader {
    u1 magic[8];
    u4 dexCount;
    u4 dataOff;
    KiwiDexItem items[0];
};
 
int main() {
    std::ifstream ifs("classes.dex", std::ifstream::binary);
    ifs.seekg(0, ifs.end);
    auto fileLen = static_cast<int>(ifs.tellg());
    auto rawDexBuf = new u1[fileLen];
    ifs.seekg(0, ifs.beg);
    ifs.read(reinterpret_cast<char *>(rawDexBuf), fileLen);
    ifs.close();
 
    auto dexHeader = reinterpret_cast<DexHeader*>(rawDexBuf);
    auto kiwiHeader = reinterpret_cast<KiwiHeader*>(rawDexBuf + dexHeader->dataOff + dexHeader->dataSize);
    auto kiwiDexItems = kiwiHeader->items;
    auto dexCount = kiwiHeader->dexCount;
    auto dexBasePtr = reinterpret_cast<u4>(kiwiHeader) + kiwiHeader->dataOff;
 
    for (size_t dexIndex = 0; dexIndex < dexCount; dexIndex++)
    {
        auto  dexPtr = reinterpret_cast<u1*>(dexBasePtr + kiwiDexItems[dexIndex].dexOff);
        for (size_t i = 0; i < 9; ++i)
        {
            dexPtr[i] ^= dexIndex + i + dexCount + 2;
        }
        std::ofstream ofs("classes_" + std::to_string(dexIndex) + ".dex", std::ofstream::binary);
        ofs.write(reinterpret_cast<char*>(dexPtr), kiwiDexItems[dexIndex].dexSize);
        ofs.close();
    }
 
    return 0;
}


脱壳后

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值