这篇帖子起因是前段时间想挂个协和的号,随便拿了个测试机下载好APP后打开卡在启动界面,以为是测试机root之类的东西被检测到后拒绝启动了。
于是准备看看怎么回事,发现这玩意儿使用了**加固,正准备分析分析,突然发现测试机WiFi没连上,连上网之后就正常了,于是就把它丢在一边没管了。
然后过年在家闲着没事,想起还有这么个东西,正好**加固以前没有遇到过,于是又在垃圾桶里把它翻出来了。
分析完后,实在是忍不住想吐槽一下,,不知道是不是外包公司没给钱,这加固做得就跟期末大作业水平差不多,全程就像读源码一样,,和前一篇帖子分析的乐固差远了。。
分析
老规矩,先把apk用jadx反编译,从AndroidManifest.xml找到application的类com.***ivm.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处是第一个***iDexItem,所以第一个加密的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 ***iDexItem {
u4 dexSize;
u4 dexOff;
};
struct ***iHeader {
u1 magic[8];
u4 dexCount;
u4 dataOff;
***iDexItem 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 ***iHeader = reinterpret_cast<***iHeader*>(rawDexBuf + dexHeader->dataOff + dexHeader->dataSize);
auto ***iDexItems = ***iHeader->items;
auto dexCount = ***iHeader->dexCount;
auto dexBasePtr = reinterpret_cast<u4>(***iHeader) + ***iHeader->dataOff;
for (size_t dexIndex = 0; dexIndex < dexCount; dexIndex++)
{
auto dexPtr = reinterpret_cast<u1*>(dexBasePtr + ***iDexItems[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), ***iDexItems[dexIndex].dexSize);
ofs.close();
}
return 0;
}
脱壳后

Unidbg 模拟执行精讲
最后于 2022-2-17 20:10
被kanxue编辑
,原因: