小白逆向记录, 有错误的地方还请各位大佬指正
内容涉及到结构体还原, 自实现linker脱壳修复, dex解密和dump分析, 以及加固技术原理
lg g5 android 4.4.4
ida 9.1
010editor
记事本, 记录一些重要信息, 哈哈
一双小手, 大手也没事, 按的准键盘就可以
拿到Apk后先简单分析一下Application, 没有发现骚操作, 确定直接加载lib中so后, 直接进入Native分析

ida打开so后发现导入是空的, 看来了壳子也做了保护, 分析一下

这里shdr有一些问题, 查看段表找不到 .init.array, 那就直接动态调试抓把

挂上调试, 直接断在linker的callFunction, br .init / .init.array 的位置
会先加载Bugly.so, 可以直接跳过, 直到libshella.so加载后再f7跟进去即可

AUV, 您猜怎么着, 断住了!
(其实这里已经分析下去了, 忘了截图, 后补的图)

调试发现只调用了一次 callFunction, 说明只有一个 .init.array
ida静态跳到0x944, 开始分析, 乍一看没有看出什么, 卒

动态分析一下, 一行一行跟着汇编看
补了一下, 发现这里先取了so在内存中的起始地址给elfStart
initArray01 & 0xFFFFF000 取函数页起始地址, *elfStart != 0x464C457F 判断elf magic, 每次向下减一页大小, 直到找到elf magic
(还发现遍历了一下phdr, 但是没有引用, 暂不知道用途)

调试时, 发现这一块在读elfStart+idx的字节, 再各种异或后存回原处, 直接猜测是解密

结合动态调试和静态修补, 以及接下来的函数调用中其中一个函数代码乱码状态, 最终可以确定是动态解密


elfStart_backup这个是一个拼接的int, 前一个字(short)代表需要解密的起始idx, 后一个字(short)代表需要解密的结束idx
起名elfStart_backup是因为, 取完解密的idx以后就赋值为elfStart地址被后面引用了

那就run to解密完成的地方, 直接dump这一小块, 填进idb中 (偷个懒, 不用重新打开so分析)
mprotect是恢复内存属性为read exec, 断在这里dump即可

dump后再使用idapy填回idb即可开始分析
ai大法好, 哈哈, 效率+++
接下来在恢复完内存权限后, 做了cache刷新, 避免cup指令缓存没有更新

等一下 syscall 983042 ??

孤陋寡闻了, arm中 syscall调用号983042是cacheflush

接下来就走到了较为核心的逻辑, sub_766B074C是JNI_OnLoad函数, 先下断点
但是这里没有调用, 因为getnenv("DEX_PATH")没有取到东西

然后继续往下走, .init.array就返回来了, 回到了linker
直接f9, 我们就到了JNI_OnLoad函数, 之前断过, 这里是走到了libdvm call JNI_OnLoad

readOffset是0x6dc0, 命名为buildSdkVersion是因为这个值用完以后就被赋值buildSdkVersion了
记住这里的0x6dc0, 下面会考

跟进sub_766B05B4看一下
根据elf开始地址, 在内存中寻找so的完整路径, 然后写入a1, 结束返回上一层

现在jniOnLoad 长这个样子, 跟进sub_766AF63C

这里的info不是linker的soinfo, 而是elf的结构信息 !
这里的info不是linker的soinfo, 而是elf的结构信息 !
这里的info不是linker的soinfo, 而是elf的结构信息 !
重要的事情说三遍
sub_766AF63C 也是有一点难以阅读, 不过大概总览了一下, 猜测是自实现linker加载elf
有了这个方向, 下面的分析就事半功倍了

这里就是用完0x6dc0后写成buildSdkVerison的地方

先打开soFd, 在读取壳子so+0x6dc0的位置
那么为什么是0x6dc0呢, 这有什么特殊呢, 为什么不读取内存中这个位置呢 ???
小小的脑袋有大大的问号 ???

看了一下内存中libshella.so的内存占用, 计算了一下大小只有33kb
但是so静态大小是98kb, 那很明显了, 后半部分就是藏着的真实elf


继续跟着分析, 发现一个无根之木, size 从哪里来 ???
这里就要开始补全结构体了, 先设置minVAddr为char[88]

然后就很明了了, 从0x6dc0读取的0x58字节, 必然是一个结构体
开始逆向分析结构体中每一个字段代表什么

根据log直接得知第一个int和第二个int分别是minvaddr和size

直接开补 !!!
这里注意, 没有定义的地方也需要保留, 不然结构体大小对不上, 就会飘红, 干扰正常分析

这里对着变量 y 一下, 直接设置刚刚声明的结构体类型

接着就柳暗花明了, 继续猛猛补全即可
看到这里mmap已经可以完全确定了, 这是一个自实现linker加载真实elf, 那么下面就可以更大胆的猜测了

补全过程比较枯燥, 就不一个一个字段展示流程了, 展示一下大的点的分析基本就很明了了
通过这里可以推断这是另一个结构体, 占据24字节, 再结合 [再mmap] 推测应该是segment信息
那么another中第一个short就是segmentInfo结构体存储对应的偏移, 第二个short是segment的数量
(short类型根据汇编LDRH确定)

再往下根据log再结合猜测就可以再补齐很多字段

再记录一下segmentInfo的补全
v48是从v50做页对齐来的, 那么v50就是loadBias+minVAddr了, 那也就是segmentInfo的第一个int就是起始虚拟地址, 后面用来re-map
v46是page end对齐减去(v50 + segmentInfos[1]), 那么segmentInfo的第二个int就是segmentMemSize

猜测是segmentInfo的第二个int是memSize而不是fileSize的原因在下面
这里的从文件读取才是fileSize, 又确定了fileSize和segOffset字段

这里还像是做了一个解密的样子


一路进来, 是不是很眼熟

让我们google一下, 原来是tea算法, 如果想静态解密可以写脚本试试了

那么segmentInfo[5]自然就是解密的数据大小, 单位应该是bit

加密以后又做了一个xz解压, 这里zStream的结构体直接导入就可以

最后, 经过了七七四十九天的修补, realElfInfo, segmentInfo, zStream全部补全的差不多以后就非常明了了




补完结构体仅仅是可以看清楚逻辑, 真实elf到现在还没有修复出来, 目测是只能手搓字节码回填了
这里可以静态解析所有字段, 不过我选择动态一把梭, 直接拿数据, 哈哈哈


都解密完了以后, 就要dlopen neededLib 再重定位函数了

这是几个neededLibName的下标数组, copy一下

再跟进去就是重定位函数, 一眼顶针
[培训]传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2025-11-4 18:15
被SharkFall编辑
,原因: