type
status
date
slug
summary
tags
category
icon
password
这篇文章是我先根据网上的资料学习的,实战我将在下一篇文章进行梳理
oacia大佬给的样本也很简单,非常容易理解,就是两个RC4加密,但是在so层的应该是没有加密的,但是java层的是加密出来的,而且dex文件大小也不一样,学习一下360加固的保护
1、java层分析:

经典的360加固开始
看一下java层函数

简直多的要死,还有混淆,新版的jeb自带反混淆,jadx和jeb结合使用
tips1:
attachBaseContext和onCreate函数的作用和特点
可以看作就是来动态加载代码资源的
这样看onCreate就是来加载布局资源的

总体就是
attachBaseContext
侧重于基础配置的调整,而 onCreate
主要用于初始化逻辑和界面布局。因为先加载attachBaseContext代码,所以先看这个

这个就是根据手机架构来加载资源的,加载这个jiagu.so,我们看一下这个

在assets里面加载

jeb没有加载出来这个
看一下是什么

也就是尝试加载jgdtc文件,就是从不同的路径去读取,我没在lib和assert里面看到,估计也就是动态加载,在data路径加载,所以第一步的关键就是这个jiagu.so里面进行了什么操作
壳ELF文件的导入表导出表修复
选取jiagu_a64.so进行分析,发现就是导入导出都没东西,肯定是要动态加载的,我们就直接dump然后修复

刚开始我只写了dump_so的代码,发现会报错,必须要等待这个so的加载,所以就只能先hook这个android_dlopen_ext函数,先让他先加载出来
输出得到:

提取出来看看
使用sofix修复一下,有时间一定要看一下这个项目的源代码
命令行
这里的
-m
参数即这个 so 在内存中的 base
基地址 -o 是,目的文件地址
完美
加固壳反调试初步分析:
这里学到了大佬的分析过程,先hook将会打开so的函数,就是上面那个android_dlopen_ext函数
接下来就是hook具体的open函数,因为许多反调试都会调用open函数查看手机的端口什么的进行反调试

可见就是我们的那个jiagu_64.so里面的反调试
接下来hook一下open函数
得到下面的:
根据oacia大佬的方式,解决方法就是将其重定向一下:
学习一下代码是怎么写的:
首先,关闭frida 使用
cp /proc/self/maps /data/data/com.oacia.apk_protect/maps
复制一份maps到data路径
代码如下:
确实如大佬所言,报错啦
错误是访问权限崩溃,也就是访问了非法内存
大佬的解释:
但是当注入这段脚本后,进程由于非法内存访问而退出了,这说明 360 加固不仅读取 maps 文件,并且会尝试访问 maps 文件中所记录的文件或内存映射。这里由于 frida 注入后重启 apk, 但是备份的 maps 文件中记录的是先前的映射起始地址 (这块内存在关闭 apk 后就被抹去了), 所以当壳尝试访问其中的映射时产生了非法内存访问从而让进程崩溃。
就是每次重启apk都会打开新的maps文件
大佬接下来给的办法是给一个不存在的地址
代码:
新的报错:
打开看看这三个吧,
使用16进制打开看看

看来不是一个正常的dex文件,接下来我们要怎么继续下一步思考呢
很显然,我们只需要在他open函数的地方,进行堆栈回溯就可以看到哪里进行调用,哪里会出现异常
但是直接使用默认的堆栈回溯就直接崩了,所以我们就要自己手动让他输出一下进程中的模块地址和偏移了
得到下面的堆栈:
可以发现一些端倪,这三个的流程好像是差不多的吧,所以估计就是在一个循环中进行的,我们现在就去看看这个里面的东西
跳转到第一个地址去看看,发现都是0

看来应该是动态填充的,运行时填充,所以我们就在他运行到这个的地方进行dump
代码如下:

得到了这个,还是使用sofix修复一下

填充了数据,根据oacia大佬的讲解,可以使用WinMerge工具来查看不同,又学到一个妙妙小工具(真的好用哈哈哈)

可以看到藏了个elf文件,同时
program header table
信息还被加密了,我们可以使用python来把这些文件给提取一下
主ELF文件解密流程分析
现在我们得到的信息是通过壳的jiagu.so里面藏了一个elf文件的so,但是藏得这个是加密的,所以我们就需要去寻找加固壳的so是怎么将其加密的
根据oacia大佬的解释,这种形式的加密像是自实现linker加固so
tips2:
可见文章讲解:
- ‣
自实现linker加固so
先来解释一下什么是linker加固so,在应用程序启动时,Linker 会将需要的动态库加载到内存中,并解析动态库中的符号表、重定位符号地址。
Linker 加固就是用自定义实现替代系统默认的 Linker,通过控制动态库的加载流程,加入安全机制,达到以下目标:
- 隐藏动态库加载的行为。
- 加密/解密
.so
文件,防止静态分析。
- 动态加载未在标准路径下的
.so
文件,防止直接提取。
- 增加校验机制,如完整性校验,防止动态库被篡改
自实现linker加固so呢就是自己实现处理.so文件的加载逻辑,不使用系统默认的加载机制了
具体步骤:
1、拦截加载流程:
替换系统默认的加载逻辑,(如dlopen,dlsym等),实现对动态库的完全控制
2、动态库的加密与解密:
在编译完成的.so文件上使用rc4,aes等等加密
在运行时进行解密
3、动态加载道内存:
不直接使用文件路径记载,而是将解密后的so文件加载道内存
使用mmap和dlopen的内存版本加载动态库
4、添加安全机制:
在加载前查看文件的hash值
所以许多的关键就是找到那个
soinfo
这个结构体指针,可以通过dlopen函数来找到这个地址
都去看看哪里引用,哪里像是linker的加载,他肯定不会大改,有许多相似的地方
第二个很像了,还是一个循环

都翻一翻,看到这个 switch 就知道找对地方了,这里应该就是自实现 linker 来加载 so 的
在aosp中翻linker的源码
android-platform\bionic\linker\linker.cpp
) 中的预链接 ( soinfo::prelink_image
) 这部分的操作极为的相似


直接copy了一份oacia大佬的关于soinfo的相关结构体:
在ida中进行创建然后引入这些结构体
经过观察,测试可能是将soinfo魔改了,因为存在数组

所以我们交叉引用一些这个函数

再来看一下这个函数

观察一下这些函数
进入可以发现有一个0x38,这里的0x38是program header table中的phentsize,代表的是程序头中一个条目的大小。而程序头又是多个条目组成的,这里举例一下条目,v5是程序头的偏移地址,v6的条目的偏移地

上面这个也是看雪大佬总结的,写的很好,通俗易懂,也了解到了elf文件的结构
hook一下上面的5E6C函数,看看有什么参数
代码如下:

所以这三个参数就是知道的,我们可以修改一下了
对这个soinfo结构题,填充一个232大小的数组,再来观察

再看一下代码:

所以接下来,我们可以根据交叉引用看看他是怎么进行解密的
经过交叉引用以及堆栈回溯我们发现他到sub_8000就结束了,再往上就找不到了,所以就根据oacia大佬的项目把他的插件换成了一个js脚本和idapython脚本来输出,代码给一下:
堆栈回溯:
stakler输出函数调用:
得到的结果:
从上面的调用往下找,可以找到0x5f20这个函数,看一下

这应该就可以看出来是个加密,还是RC4,hook一下参数2看看
可以看到密钥是这个 vUV4#.#SVt

继续往下走就发现解密了

有意思好玩了,接下来怎么搞呢,
肯定是hook一下返回值和参数,看看他解密的数据的大小和参数都是什么,从其中发现一些线索

从第二个参数那发现了端倪,好像前面见过这个值

有一个so的后缀,然后v5也还有值,同时v5[0]里面的值就是我们RC4要加密的值看来就是那一些动态加载的数据了
继续往下看,RC4加解密之后又走到了
call 64 : sub_3574 offest: 0x3574
call 65 : uncompress offest: 0x2970
搜索了一些,这个uncompress是个解压缩技术,进行解压
dump一下看看

可以看出来这个数据是从第四个字节往后开始解压的
那么前四个一般都是大小
接下来继续往下走流程到达5B08

熟悉的0x38和6
这不就是解析elf文件的吗,也是一个重点

同时发现有好多的异或,打印一下v5,v4的值看看

可以看到就是经过RC4和解压缩之后的数据,继续往下看
经过我的多个hook测试大概得到了一些数据含义,也就是几个异或函数



最后一段就有意思了,加起来一看结果是0x26ae9,跳转看一下


这个文件的获取就是上面那个RC4解密和uncompress解压缩得到的
代码如下:
借鉴oacia大佬的经验,我们为a1创建一个结构体看看哪里用到了什么

基本可以看到一些逻辑了
现在我们可以继续往上翻一翻调用链,看看哪里还有别的操作的代码
翻到0x49F0函数,我们通过修改变量类型,可以看到他也是有一些操作的

同时把V7的类型也改一下,可以也创建一个类型

可以发现V7就是我们的那几个段信息,把他当作参数不就是调用他吗,我们接下来继续看
看第一个Sub_3c94

熟悉的case操作,不就是前面linker哪里看到的吗,改一下类型看看,哪里调用这四个段结构

可见这个函数只是处理了extra_patr4这个段,接下来的目的就是识别出来这个是什么操作,找一下AOSP源码,发现是dynamic段

也可以理解先加载这个,毕竟这个是动态链接库,里面放着符号表和重定位表的指针,接下来才可以继续运行其他段
接下来看其他的段在哪里培调用

根据oacia大佬的提示,这个函数包含着基址重定位和符号重定位,分别是
0x403
和 0x402
确实也在这个函数里面

而extra_part1也在最开始地方出现了

就是刚开始加载的地方,这个说明part1是
program header table
最后可以知道:
数据组1
表示program header table
数据组2
表示.rela.plt
数据组3
表示.rela.dyn
数据组4
表示.dynamic
所以这个操作就是为下面藏在这四个段下面的elf文件的四个重要的段进行使用
我们就根据这四个段去还原下面那个elf

就是我们得到的那个,接下来就是将这些给解密出来,我们可以将其分离出来得到4个文件,直接使用的oacia大佬的代码,进行了理解解释

接下来才是好玩的
主ELF文件的导入表导出表修复:
很早就想学这个了,奈何不知道如何下手,正好根据这个学习一下
还是先将主ELF文件给分离出来
得到了主elf文件
学习修复
因为我们得到了这重要的四个段,而且自实现linker加固so就是将原本重定向表和符号表这些全部都使用无关字节进行填充,等到使用的时候直接从别的地方进行加载,所以我们修复就是将我们得到的这些表给他还原回去
1、修复 program header table
首先选中我们刚才分离的libjiagu.so_0x150_phdr文件(使用010),ctrl+shift+c全选,然后在libjiagu_0xe7000_decode.so 选中program header table(双击就行) 接下来ctrl+shift+v粘贴,然后再使用F5刷新


2、修复 .dynamic
先找到Dynamic这个段,接下里找到p_offest,跳转到这个位置然后故技重施进行粘贴

3、修复重定位表:
在文件中找位置


跳转到对应的地址处复制粘贴
然后打开ida进行查看就发现已经修复完成
可以将基址修改成0xe7000进行分析
主dex解密流程分析:
在前面的操作里面,我们曾dump出来三个dex文件,其中有一个很大,我们就来分析一下这个,但是我们还没有解密
其实对比一下壳的dex,我们可以发现在其后面就是我们那个未解密的dex
那我们可以继续进行栈回溯,看看哪里调用了这个dex,并且我们还恢复了一部分符号,更容易看出来一些关系,同时还有那个我们上面恢复的那个主ELF文件,也需要dump一下
frida -U -f "com.oacia.apk_protect" -l .\stalker.js -o ans2.txt
接下来就是去分析一下这个ans2.txt得到的东西
因为我没有跑出来这个,但是好奇下面的操作就暂时使用了大佬的,下次补齐,继续研究
通过观察这些函数,我们也可以发现一些端倪,比如这个
这三个不就是解压缩的吗,去看一下函数

导入一下数据结构

解释一下这几个
s.next_in
: 压缩数据
s.avail_in
: 压缩数据的长度
s.next_out
: 解压后的数据
s.avail_out
: 解压后数据的长度
所以我尝试hook这个inflate这个函数,将他的三个值全部dump出来
解压出来发现就是原来的壳dex,所以主dex肯定还在下面进行解压
先往上查看调用栈,对这个解压函数0x1B6270进行交叉引用,可以发现是sub_1A0C88,查看一下有一个read函数,继续往上看,找到了sub_124FA0,可以发现有明显地dex字眼
继续往上看,找到sub_1332B8这个函数
所以调用栈就是:

这个就是为了搜索内存找到这个壳地dex,oacia大佬说的是为了找到解压并优化后地壳dex
还是从dex打开地那个堆栈进行分析,看一下是什么
先看一下0x19b780

标准地打开关闭文件函数
经过交叉引用发现了了sub_1332B8这个函数,就是刚才见过的,而且往下翻也发现了他又字符串加密操作
我的操作:
我是往下翻的,我觉得解压缩之后要进行解密,我发现了几个函数

经过我的测试,发现了神奇的地方

在我进行后续的分析时,frida检测一直阻碍着我hook,所以我们必须过掉他
frida反调试深入分析:
现在,几乎很多的app都会新创建一个线程进行frida检测,所以我们可以对线程创建函数进行hook
即对pthread_create函数
又学到一手

就是指向0x17710地址,我们过去看一下
我们发现他竟然还是一个特殊函数

搜索一下这个函数
大体可以归纳为调用系统函数或者是外部函数,而且他的参数是指定的,我们可以创建一个结构体进行查看
我们去github上看一下代码
sysv.S
libffi



将这个与ida进行比较也发现了端倪

既然x0是栈指针,那么我们看一下他的值
可以发现可以无响应进去apk,而且x0还有输出
可以过滤一下值,看看有没有frida这类的字眼
发现确实是有的

所以可以考虑过滤一下
又学到一手过滤字符串的写法
发现只能x6才能通过,寄存器的原因
接下里就可以正常地dump和hook了
主dex解密和dump
在上面地解密过程中,第一步可以发现一个字符串解密地函数,而且还存在dex字眼


所以dump一下这个函数地参数,经过前面地frida反调试,我们可以先运行反调试函数,在运行这个hook函数,但是需要延时hook,代码如下:
接下来就是把这三个文件给dump下来。代码如下:
打开最大的一个dex发现就是我们主要的dex,接下里就是探索他的解密流程了
继续看解密,刚才我们往下翻的时候其实已经看到啦一个函数


hook一下这个函数的参数,通过oacia大佬的教程,找到一个很好的hook时机,之前总是找不到合适的时机,给一下代码
hook发现一些东西

可以发现第二个参数就是加固的dex,那一堆乱码,写一个解密脚本

解密之后的数据类似于一堆配置信息,再往下看我没有找到相关的解密代码,等待下次继续分析把
总结:
总体来说,他首先是壳的dex来加载壳的so文件,在这个壳的so文件里面,我们在追踪open函数的时候,会发现有地方记载dex文件,但是会退出,栈回溯找到加载这个dex的一个函数时可以发现有一个动态加载elf文件的地方,我们dump下来,这个就是主elf文件,(壳加载主elf文件的方式时自实现linker加载so文件),(解密的方法是RC4和uncompress),在这个主elf文件我们可以找到加载dex的地方,但是dump下来dex的时候会发现他的oncreate是native化的,必须手动修改
- Author:xiaowaaa
- URL:https://www.xiaowaaa.asia//article/1835df8d-8884-804e-8168-e94d72225397
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!