在去年9月的时候,我就想研究一下apk的加固,在网上逛了一圈,感觉有一个加固不错,所以先选它啦,写了一个测试apk丢到这个加固里面加固了一下
这里我用的是这个加固的免费版(因为付费版太贵啦)
本来计划着去年分析完的,但是总是抽不出一段完整的时间来所以就拖到了现在,终于最近因为过年赋闲在家,就花了几天分析了一下,感觉这几天探索加固壳的过程真是充满了惊喜和乐趣呢~
2024年3月8号记:
距离上次看这个加固已经半个月啦,今天正想着自己要做什么事情的时候,到博客看了眼TODOlist,突然发现咦我是不是还没看过dex的解密算法长什么样?所以今天就把这个给做完了,不得不说逆向真的是消磨无聊时间最有效的方法哈哈哈
包名:com.oacia.apk_protect
入口:com.stub.StubApp
我们从AndroidManifest.xml
中可以得知,这个加固的入口是com.stub.StubApp
,所以我们就先进到apk的入口进行分析
在这个入口类中,不仅有常规的onCreate()
函数,还有一个函数值得注意,他就是attachBaseContext(Context context)
Application 的onCreate
和attachBaseContext
是 Application 的两个回调方法,通常我们会在其中做一些初始化操作,attachBaseContext
在onCreate
之前执行
其中出现的字符串经过了加密混淆操作,加密函数如下,算法是将所有的字符与16
进行异或
我们写个jeb脚本把加密字符串解密来方便后续的静态分析
解密后的效果如下
我们往下进行分析,可以知道attachBaseContext
的第一个作用是根据目标手机的架构加载libjiagu_xxx.so
如图
这些so在assets
目录下
在加载完libjiagu_xxx.so
之后,还调用了DtcLoader
类进行初始化,这里使用的jadx的反编译结果,因为jeb没有反编译出DtcLoader.init();
方法来
DtcLoader
类如图所示
当DtcLoader
类被加载到JVM中时,会去加载libjgdtc.so
,如果加载失败,则会尝试从/data/app/com.oacia.apk_protect/lib/arm64/libjgdtc.so
或者/data/data/com.oacia.apk_protect/lib/libjgdtc.so
中去加载这个so
但是当我们去进入到这两个目录进行寻找时,却发现没有这个libjgdtc.so
存在
所以我们的分析重点是在libjiagu.so
中,这里我选取了arm64架构的so文件libjiagu_a64.so
进行分析
我们使用ida分析libjiagu_a64.so
,发现导入表和导出表都没有内容,既然是这种情况,那么应该是在so装载进内存时导入导出表才会去进行相应的链接操作
所以我们可以先用frida把这个so给dump下来
首先我们在手机上运行一下frida server
随后做一下端口转发
frida命令行语句如下
注入如下脚本
随后我们使用SoFixer修复一下这个so,这里的-m
参数即这个so在内存中的base
基地址
修复完成后,导入表和导出表就恢复了
首先我们去hook一下打开so的函数
日志如下,所以反调试是在libjiagu_64.so
中
然后去hook打开文件的函数open
日志如下
这里我们发现了/proc/self/maps
,这是常见的反调试,要绕过这个检测,我们可以备份一个正常的maps
文件,然后用frida去hook open
函数,如果匹配到字符串maps
,就将字符串重定向到我们备份的maps
文件
首先我们正常打开一次加壳的apk,然后使用下列命令备份maps
然后我们注入如下frida脚本
但是当注入这段脚本后,进程由于非法内存访问而退出了,这说明加固壳不仅读取maps文件,并且会尝试访问maps文件中所记录的文件或内存映射.这里由于frida注入后重启apk,但是备份的maps文件中记录的是先前的映射起始地址(这块内存在关闭apk后就被抹去了),所以当壳尝试访问其中的映射时产生了非法内存访问从而让进程崩溃
这里我的解决方式是将上述frida代码中的fakePath
赋值为一个不存在的文件例如/data/data/com.oacia.apk_protect/maps_nonexistent
,来让壳没有内容可以访问
修改完fakePath
后重新注入代码,这个打印出来的日志可以说非常有意思,我们来看一下,相比hook open
之前的日志,我们成功的让这个加固壳释放出了dex
然而这个壳似乎是又发现了些什么异常,随后赶紧让app退出了,但是由于退出的太过仓促,它甚至还没有来得及把dex从文件夹中删除
用010editor打开classes.dex
,发现前几位并不是dex的魔术头,说明这个dex还没有被解密,不过现在我们只需要分析dex如何被壳从内存中释放出来的过程就可以了~
如何可以定位到是什么位置调用了open
函数来打开classes.dex
呢?
很简单,打印一下堆栈就可以了
假如我们使用常规的frida打印堆栈代码,即使用DebugSymbol.fromAddress
函数来判断地址所在的so
的位置,那么进程是会报错退出的
所以这里DebugSymbol.fromAddress
所实现的逻辑需要自己编写,即下方的addr_in_so
函数
于是我们得到了释放三个dex文件的堆栈回溯
这里我们发现classes.dex
与classes2.dex
的堆栈回溯完全相同,并且classes3.dex
的前半部分和前两个dex的堆栈一样,随后进程便又退出了
通过对堆栈的分析,我们可以发现三个dex应该是在一个循环中被依次加载的
接下来我们便跳转到堆栈所打印的偏移来进一步分析下
然而当我们跳转到堆栈回溯中的libjiagu_64.so
的偏移0x19b780
或者0x134598
时,却发现这些地址的值都是0
我们很快就能想到这里用到的技术应该是先将一块内存标记为可写可执行,随后将字节码填充进去,所以说,我们只需要在壳打开dex时,将此时的libjiagu_64.so
从内存中dump下来就可以了
然后再去用SoFixer修复这个dump下来的so
再次来到偏移0x19B780
处,可以发现这块空内存已经被填充了数据
接下来我们想知道的是究竟是从什么地方开始被填充了新的数据,所以我们可以用WinMerge来让填充和未填充数据的so进行比较看看,结果却有了惊人的发现,被填充的数据是从0xe7000
开始的,它的开头竟然是ELF文件的魔数头!?这有意思了,那么就是一个so里面藏了另外一个so咯~
我们写个python脚本,把这个ELF从0x0e7000
开始后面的所有字节都复制到新的文件里面
但是当把这个elf提取出来之后拿010editor
看却发现program header table
被加密了
这就导致ida根本就无法进行正常的分析
壳elf加载主elf,并且program header还被加密了,感觉这种形式很像是 自实现linker加固so
对于这种加固方式,壳elf在代码中自己实现了解析ELF文件的函数,并将解析结果赋值到soinfo
结构体中,随后调用dlopen
进行手动加载
来到ida里面在导入表对dlopen
进行交叉引用,我们看到dlopen有5个交叉引用
看到第二个交叉引用,来到sub_3C94
函数,这个for循环看起来像是在用符号表通过dlopen加载依赖项
向上面翻翻代码,看到这个switch就知道找对地方了,这里应该就是自实现linker来加载so的
因为这和AOSP源码(android-platform\bionic\linker\linker.cpp
)中的预链接(soinfo::prelink_image
)这部分的操作极为的相似
那接下来就在ida中导入soinfo相关的符号就可以啦
在ida中依次点击View->Open subviews->Local Types
,然后按下键盘上的Insert
将下面的结构体添加到对话框中
导入完成后按下Y
键,将a1
定义为soinfo*
然后就可以看到这些符号了,但是看这些符号总感觉有些不太对劲,这里不应该出现a1[1]
或者a1[2]
,所以我猜测这个soinfo有被魔改的痕迹
虽然这个soinfo可能有被魔改了,我们还是从sub_3C94
这个预链接相关函数入手好了,交叉引用发现sub_3C94
是被sub_49F0
调用
随后我们来到sub_49F0
内调用sub_3C94
函数的位置,向下看,进入sub_4918
函数中
sub_4918
中调用了sub_5E6C
,我们进入sub_5E6C
这个函数中出现了0x38
这个数字,0x38
是这个循环的步长
0x38这个数字有什么特殊的含义吗?当然有了!!
我们把刚刚提取出来的elf用010editor
打开,看到elf_header
的phentsize
这个字段,这个字段的含义是一个Program header table
的长度,它正正好好也是0x38
所以说在sub_5E6C
中变量v5
的类型应该是Elf64_Phdr *
,我们直接重定义类型
既然知道了真正的program header table
就是在这个位置的,那我们直接在这个地方把program header table
整个给dump下来不就行了
所以我们直接去hooksub_5E6C
的三个传入的值
上面的第一个hexdump就是program header table
,我们可以用cyberchef
将hexdump
转成数组的形式
0x6
则对应着phnum
,这表示共有6个program header table
0x793ca38000
表示这个主ELF的基址,因为这个主ELF的位置在壳ELF基址的偏移0xe7000
处,而最下面这行也已经打印出了壳ELF的基址为0x793c951000
,0x793ca38000==0x793c951000+0xe7000
等式成立
至此为止,我们拿到了解密之后的program header table
,同时我们也知道了sub_5E6C
传入的三个参数分别是phdr
,phnum
以及base
但是phdr
成员命名是在soinfo偏移的0x0
的位置
那假如a1
的类型就是soinfo*
,为什么在sub_4918
里面调用sub_5E6C
传入的是偏移是232
呢?
所以soinfo*
必定有被魔改,同时我们也可以在soinfo前填充一个大小为232的char类型数组看看是什么情况
很好,这验证了我们对于soinfo*
被魔改的猜测,因为在一切正常的情况之下,函数的调用应该是sub_5E6C(a1->phdr, a1->phnum, a1->base)
才对
但是我很想知道这个壳ELF究竟是如何被解密出来的,那么首先来看看主ELF的函数调用链是什么样子的吧~
我写了一个ida插件来实现这个过程stalker_trace_so
在IDA中使用Edit->Plugins->stalker_trace_so
后,在so所在的目录下会生成一个js脚本,我们用frida注入到apk中即可,需要注意的是so_name
需要改成libjiagu_64.so
打印出来的完整日志如下
我们以sub_3C94
为起点开始分析,因为这是我们通过dlopen
交叉引用找到的自实现linker加固so的一个功能函数
对sub_3C94
不断按下X
查看交叉引用,得到如下的调用关系sub_4B54->sub_49F0->sub_3C94
sub_4B54
可能被sub_8000
或sub_8C74
调用
我们将stalker_trace_so
打印出来的内容中,提取关键的部分拿过来看看,说明sub_4B54
是被sub_8000
调用的
sub_8000
的函数长这个样子,请记住第25行0xB8010
这个数字,后面会派上用场的
跟着函数调用链一处一处的在IDA中跳转到相应的地址进行查看,在call62:sub_5F20
我们发现了有意思的代码
这个函数,一眼RC4呀
用frida去hook一下这个函数看看RC4的密钥是什么
所以密钥就是这个咯
继续跟着函数调用链走,在call63:sub_6044
我们发现了RC4的解密函数
hook一下call63:sub_6044
看看到底给什么数据解密了
这个函数的第二个参数是0xb8010
,感觉是解密的数据的长度的样子,而且这个数字,有没有感觉在哪里见过呢?
没错,这个数字刚刚就出现在sub_8000
中
而v5[0]
的值是qword_2E270
,这个数组也是01 18 25 e7
开头的
继续跟着调用链走,接下来是调用call65:uncompress
,进行解压缩操作
我们发现解压缩的数据,前面四个字节b9 0e 1a 00
没有包含在解压缩的字节之内
现在既然我们已经知道了主ELF在壳ELF中的位置,以及解密的算法,那我们直接从解压apk,找到里面的assets/libjiagu_a64.so
,不就能直接把壳ELF解密出来咯
解密完成后,我们发现0x1a0eb9
应该表示解压缩之后数据的大小
wrap_elf
的前半部分是一大堆莫名其妙有很多D3
的东西,但是看到中间还是发现了壳ELF的身影
我们以.ELF
为标志将这两部分分离一下
跟着函数调用链来到call69:sub_5B08
,这里又出现了0x38
,并且word_38
跳转过去的值为6
这正好和phentsize
和phnum
的值相对应
所以可想而知,这又是一个关键点了,往下看一下代码,发现了循环异或,那我们不妨用frida把v4
的值hook下来看看是什么
v4的值出现了那么多的d3
而这就是wrap_elf
的前半部分那一大堆我们看不懂的字节
接下来用来解密的循环就是一个arm64的neon运算
官网可以找到vdupq_n_s8和veorq_s8,根据函数描述可以知道这里用向量运算,把向量中的每一个元素都异或了0xd3
对sub_5B08
进行分析之后,我们便可以知道wrap_elf_part1
的读取方式是第一个字节表示被异或的数字,这里是0xD3
,后面的四个字节表示一个段的长度,随后读取指定长度的字节并异或,之后再读取四个字节获取到下一个段的长度,以此类推,直到读取到文件末尾
在sub_5B08
的最后,因为v31
,v19
,v43
,v7
代表对应的数据组的长度,所以这里共有四个数据组,而为了表示每一个数据组的长度共需占用4*4=16
字节,并且文件开头还有1
位的异或值,于是这些长度加起来,*(a1 + 0x98)
的偏移就来到了主ELF的魔术头.ELF
的位置了
我们可以在sub_5B08
中为变量a1
定义一个结构体,成员分别表示数据组的1,2,3,4这四个部分,这样我们就知道这四个部分分别被用到什么地方了
接下来再捋一下函数的调用链sub_49F0->sub_5478(&v16, a1, v4)->sub_5B08(a1, a2, a3)
,在sub_5B08
中,我们把a1
的类型定义成了deal_extra
,所以理所应当的,我们也把sub_49F0
中的变量v16
的类型定义为deal_extra
在sub_49F0
中我们发现成员extra_part
赋值给了变量v7
,所以我们也为v7
建立一个结构体让v7的偏移可以对应这些变量
这样做有什么意义呢?
我们发现变量v7
分别被传入到了sub_3C94
和sub_4918
中,我们分别进去看看
在sub_3C94
中解析了extra_part4
,显而易见,这个switch
是用来处理动态链接库的,即extra_part4
对应.dynamic
段
在sub_4918
中,extra_part2
和extra_part3
被传入到sub_4000
中
而这个函数中的switch
是用来处理重定位的,因为重定位主要有基址重定位和符号重定位,这两个的值分别是0x403
和0x402
所以extra_part2
和extra_part3
分别对应着.rela.plt
(402重定位)和.rela.dyn
(403重定位)
而之后extra_part1
被传入到了sub_5E6C
中
而来到sub_5E6C
也来到了我们最开始分析的起点(兜兜转转又回来了),所以extra_part1
表示program header table
至此为止,四个数据组所对应的段都分析完成
所以接下来,写个脚本把这四个数据组给分离成单独的文件咯
于是我们得到了这四个文件
需要被修复的主ELF是我们在从assets/libjiagu_a64.so
利用RC4
和decompress
解密出来的文件的后半部分那个ELF
可以写个python脚本分离出后面的ELF
现在我们拿到了主ELF的四个重要的数据段,分别是phdr
,.rela.plt
,.rela.dyn
,.dynamic
,那么接下来需要做的工作就是修复主ELF的导入导出表了,不然导入导出函数都看不见怎么逆嘞~
在使用自实现linker加固so时,phdr
,.rela.plt
,.rela.dyn
,.dynamic
这四个段是从待加固的so中提取出来,然后加密存储到其他位置,原来的位置会使用无关字节直接覆盖
等到需要为加固的so进行预链接和重定位的工作时,才将这些段解密并通过自己实现的预链接和重定位代码,让待加固的so可以正确的被壳so加载出来
我们进行修复的方法其实就藏在这句话中原来的位置会使用无关字节直接覆盖
,我们可以将分离出来的这四个段再塞回到原来的位置
自实现linker加固so
的加固方案既然都把那四个段加密存到其他地方了,那怎么不直接把原来的四个段直接删除而是用无关字节覆盖呢?
因为直接把段删除掉的话,会影响了一整个ELF文件的布局,偏移就会变得和原先不一样,然后产生各种奇奇怪怪的问题
在010editor中,按下ctrl+shift+C
可以复制整块内存,按下ctrl+shift+V
可以粘贴整块内存
修复program header table
复制libjiagu.so_0x150_phdr
的所有字节,然后来到libjiagu_0xe7000.so
中选中struct program_header_table
粘贴
随后按下F5
刷新模板
修复.dynamic
program header table
的(RW_) Dynamic Segment
的p_offset
指向.dynamic
段的位置
跳转到该位置,复制libjiagu.so_0x1b0_.dynamic
的内容并粘贴到这个位置
修复重定位表
我们需要通过.dynamic
段的d_tag
字段来找到重定位表的位置,下面是AOSP中d_tag
的宏定义
所有的d_tag
标志对应的含义可以在ORACLE 链接程序和库指南 中找到
对于我们修复主ELF比较重要的tag
有
我们可以在.dynamic
中发现这些tag
以及对应的值
看看这两个大小分别是0x1650
和0x25188
,这不就和我们刚刚分离出来的文件大小一模一样嘛,说明我们离修复完成不远了
然后就是和之前一样,跳转到.rela.plt
和.rela.dyn
的对应地址,然后把这些段本来的数据粘贴进去
现在我们就修复好啦,拿ida打开主ELF看看,满满的都是符号!
随便找个导入函数交叉引用看看,一切正常(●'◡'●)
为了方便起见,我们可以将主ELF的基址定义成在其在壳ELF的偏移0xe7000
方便后续的分析
还记得在加固壳反调试初步分析中,我们拿到了未解密的dex嘛
那么接下来有个问题就是,这个未解密的dex究竟藏在了apk的什么地方呢?
我将未加固的apk解压出来,然后用7zip压缩其中的dex,发现大小依然有2.8MB
随后我将经过加固之后的apk解压出来,按大小对文件进行排序之后发现,最大的文件就只有这个壳classes.dex
,而别的文件甚至连1MB都没到,总不可能压缩率可以高到这种地步吧
所以我们打开classes.dex
看看,在这个classes.dex
的末尾,果然藏着一大堆的数据
而末尾的数据是由71 68 00 01
和我们之前看到的加密的dex一模一样
接下来我们继续用stalker_trace_so去看看补充上主ELF的函数地址以及名称之后的函数调用链是什么样子的,首先在主ELF中运行插件Edit->Plugins->stalker_trace_so
之后同样的,我们需要将so_name
改成libjiagu_64.so
,特别注意的是,这里我们需要把壳ELF的func_addr
和func_name
给复制过来,同时使用concat
方法将主ELF和壳ELF的函数地址和函数名拼接成一个新的数组
之前替换/proc/self/maps
来实现初步反调试的js函数hook_proc_self_maps
也需要同时执行
输出结果如下,KEkeELF
标志表示壳ELF,mainELF
表示主ELF,(为什么是KEke
,只是为了对齐看着舒服:))
要判断调用的函数在哪个ELF里面,在trace_so()
里面稍作修改判断一下范围可以了
打印出来的结果如下
分析输出的结果,我们发现了三个有趣的函数inflateInit2_
,inflate
,inflateEnd
,这不是zlib用来解压缩的函数嘛~
对inflateInit2_
交叉引用,发现有两个函数调用了它
那么要怎么知道是哪一个函数先调用的inflateInit2_
呢?向上看看函数调用链就行了
于是我们发现是sub_1B6270
调用了inflateInit2_
我们来到sub_1B6270
,先到https://github.com/madler/zlib把zlib.h
中的z_stream_s
,导入的方法和之前一样
重定义s
的类型为z_stream
,这四个字段的含义如下
各个成员的偏移如图所示
随后我们用frida去hook一下inflate
函数看看解压缩之后的数据是什么
这里有个技巧,就是如何可以hook到主ELF中的函数,,因为在壳ELF加载进内存时,主ELF还没有被加载,所以假如在壳ELF通过android_dlopen_ext
打开时我们进行hook,是会hook失败的
那么如何才能获取到主ELF的hook时机呢?我们可以通过统计外部函数的调用次数来判断是否已经加载了主ELF,例如我这里,我通过zlib_count
统计外部函数inflate
调用次数,因为在壳ELF会使用uncompress
调用一次inflate
,所以当第二次调用inflate
,我们就知道这肯定是主ELF调用的,所以我们也可以在这个位置放心大胆的hook了
解压缩之后的输出如下,在输出的文件头,我们发现了dex035
,所以我们把这块内存dump下来看看,使用上方的dump_memory(start,size,filename)
函数即可
把这个解压缩之后的dex拖入到jadx里面,却发现这个类名怎么和壳DEX的类名一模一样,通过校验哈希发现dump下来的dex和壳dex其实是同一个文件
我们在之前的分析中知道壳dex的末尾附带了一大串的加密数据,所以通过将这个解压缩得到了这个dex,就说明马上要进行加密主DEX的解密操作了
解压缩的函数是sub_1B6270
,接下来我们继续通过stalker_trace_so
打印出来的内容,并利用交叉引用来追踪该函数的调用链
就比如说对于函数sub_1B6270
,它有两个交叉引用
通过stalker_trace_so
打印出来的函数调用链,我们发现是sub_1A0C88
在sub_1B6270
之前调用,所以函数的调用关系就是sub_1A0C88->sub_1B6270
,以此类推
所以一路跟过来之后,函数的调用链为sub_1332B8->sub_124FA0->sub_1A0C88->sub_1B6270->inflate
,sub_1332B8
函数之后就没有交叉引用了
在这个函数中,我们发现了apk@classes.dex
,而它的作用,正是为了找到已加载到内存且优化后的壳dex
在加固壳反调试初步分析的后半部分,我们打印出了加固壳打开dex的堆栈回溯,现在我们直接跳转到相对应的地方看看
我们到0x19b780
看看,看起来是一个标准的打开并写入文件的函数
随后对该函数进行交叉引用,我们发现sub_1332B8
竟然调用了它,就在刚刚我们就分析出这个函数中可是同时也执行了从内存中获取壳dex的操作的呢
我们对这两处调用都hook一下看看是什么情况,打印的结果如下,说明这两处调用都打开了dex,sub_1332B8
中的前一个调用打开了classes.dex
,后一个调用打开了classes2.dex
和classes3.dex
,而classes.dex
文件中的内容就是加密的主dex
在创建完classes2.dex
和classes3.dex
,通过hook发现调用在调用sub_128D44
之后进程就退出了
我们去hook一下sub_128D44
这个函数,发现传入的参数v8
正是加密的主DEX
而sub_128D44
函数是这个样子的,并且在壳ELF加载时启动stalker_trace_so
的trace_so()
函数所打印出的结果中,并没有这个函数的调用被打印出来
这该怎么办呢?
很简单,在调用sub_128D44
的位置再去调用一次trace_so()
函数从现在的位置开始打印函数的调用链不就行咯:)
函数调用关系如下,我们发现再mainELF
调用完sub_128D44
之后,通过一系列操作又回到了壳ELF中,最终调用raise
导致进程退出
然而,当我跳转到最后调用的几个函数时,可以说函数复杂到让人咋舌
这么复杂,是给人分析的吗!?所以我便卡在这里了很久
我想了想现在摆在面前的有两条路,是和一眼望不到尽头的这俩函数死磕到底,还是选择把这个加固壳的反调试搞定?
我选择后者,因为明显搞定反调试要比把这两个函数分析明白要稍微简单一点
在加固壳反调试初步分析中,我曾尝试过dbus
,TracerPid
,readlink
,strstr
都没有明显的效果,只有hook open
函数让我看到了些许的曙光,那么现在应该还有一种非常重要的反调试手段没有用到,那就是pthread_create
反调试
注入代码之后,pthread_create
的调用都指向了同一个地址0x17710
我们跳转到这个地址之后却发现为什么会没有pthread_create
呢??
看了一眼这个代码所在的函数的名称ffi_call_SYSV
hmmm,看来是用libffi动态调用函数呀
直接到libffi的github仓库看一眼ffi_call_SYSV的源码
一进去注释都写得清清楚楚了
利用注释就可以知道每行汇编都代表什么了,所以BLR X24
表示去动态调用函数,而前面的X0
,X2
,X4
,X6
是用来传参的
我们hook一下x0
看看有没有什么敏感的字符串
然而神奇的是,我仅仅去hook并打印x0字符串,其他什么事情都不干,apk竟然神奇的进去了,只不过会没有响应,感觉距离成功不远了呢
有点意思,筛选一下看看有没有什么敏感的字符串好咯
竟然还真有,那就把这些字符串全部替换成无意义的字符串看看咯
然而这样做进程却一个劲的崩溃!!
没事,寄存器x0
用不了,还有x2
,x4
,x6
没替换过呢!我一个一个的试过去,终于,当我将寄存器改成x6时,进程终于不再崩溃成功的进入了apk!
看一眼检测的字符串,怎么全是/memfd:frida-agent-64.so
回到这个卡了我们很久位置,现在过了反调试之后这里的代码终于可以继续执行下去了
向下看找到这一个函数
在这个函数中的字符串全部都是加密的,颇有种此地无银三百两的感觉,我们把字符串解密后发现了DexFileLoader
相关的字符串,说明这个函数肯定和加载dex有某种关联
我们hook一下这个函数,发现这个函数共调用了三次,而且传入的值都是已经解密了的dex,classes.dex
,classes2.dex
,classes3.dex
分别通过这个函数加载
classes.dex
classes2.dex
classes3.dex
把这三个dex给dump下来看看,于是我们得到了这三个文件
把最大的那个dex拖到jadx分析里面,发现这正是我们要找的主DEX
其他函数都很正常,唯独onCreate
函数变成了native
声明,要是有同样分析到这里的朋友可以去研究研究onCreate
函数对应的native
本地函数究竟在什么地方,相信有了本文的铺垫,对于进行后续onCreate
函数的分析应该是有所帮助的吧~
而除此之外的别的类和直接反编译未加固的apk的类是一样的
我们在加固壳反调试深入分析成功的绕过了加固壳的frida检测,现在我们再次回到sub_1332B8
中的sub_128D44
,来进行后续的解密算法的分析
下图是主DEX解密流程初步分析末尾部分的内容,我们将以这个函数sub_128D44
为起点进行解密算法的分析,因为参数v8
传入的值是加密之后的主DEX
我们在这个内存用frida打一个内存读写断点来看看是究竟是什么函数读取了主DEX,同时需要注意加上我们的反调试函数
打印日志如下
说明是在0xd364
处读取了这个主DEX
跳转到0xd364
之后发现这个地址在函数sub_C918
中,看起来这个函数的形状像一个火车头一样XD
接下来我们继续使用staker_trace_so生成的trace_so()
函数去看看函数的调用链是什么样子的,trace的起点就是在sub_1332B8
中的sub_128D44
,同时注意加上我们的反调试函数
打印出来的函数调用链如下
追踪函数调用链,我们发现了两个有趣的函数
sub_143008
看起来是一个解密的函数,先加上0x70再异或0x36,我们hook一下传入的参数看看是什么情况
我们再去看看加密的dex,发现这个函数传入的就是加密的主dex
把这部分解密看看里面是什么内容
简单看了看,APPKEY
,activityName
等等像是一些配置信息的样子
这部分内容解密完之后,在sub_142ABC
中以pk
为标志读取各个配置信息
之后我继续一个一个跳转到打印出来的函数中看,却一直都没有发现后续部分的解密算法的身影,随后我又仔细的看了一下调用的函数,突然有了惊喜的发现,这里的pthread_mutex_lock
不就意味着要用互斥锁来切换到另外一个线程了吗!?
我们hook一下sub_143848
,并同时打印出pid来看看是什么情况
果不其然,在这里我们发现除了主线程外,还有三个不同的线程调用了这个函数
那我们在pid和主线程不同的时候,用stalker_trace_so
的trace_so
函数在去看看究竟调用了什么函数吧
打印出的内容如下
继续跟着调用的函数一处一处到ida里面看,在下面的函数中我们终于有了收获
这个算法,看起来又是RC4呐
hook一下这个函数,发现密钥是b"\x68\x76\x99\x72\x96\x60\x9f\x63\x96\x2c\x98\x30\xc2\x36\x51\x42"
再去hook sub_1A1E74
看看传入了什么数据,我们发现前三部分都是f7 4f e8 0e
开头的
看到这些数据,终于离成功又更进一步了,因为这些数据就是我们读取完壳DEX尾部的前0x41E
个加密数据并解密出配置信息之后的那部分加密数据,同时我们也可以在010editor中通过搜索壳DEX找到它们,这三部分加密数据段所在的位置分别为0x35CE
,0x3A93AD
,0x417064
我们先来到第一个数据段的位置来分析数据段的结构,这个位置是在0x31A4+0x41E
,即0x35c2
之后
所以这dex的第一个解密算法如下
但是光这还不够,因为rc4解密出来的数据仍然不是DEX格式
之后继续跟着函数调用链走,找到这些函数
在sub_18F6AC
中的代码感觉很像是算法相关
通过hook其中的sub_18DDB8
函数发现a3是rc4解密之后的从0xc开始的数据段
而前面的0xc位额外数据的读取方式为5+4+4,在sub_18DCC0
中有读取操作,通过这样的读取操作我们可以依次得到0x010000005D
,0x400000
,0x109492
这里的0x109492
表示的是第二次解密的数据段的大小,那么0x40000
表示什么呢?
还记得我们的加密数据段是有前后两部分的嘛,前半部分用rc4解密,那么后半部分我们来看看长什么样子好了
后半部分的数据看起来十分的规整,不像是有加密的样子
而在主DEX加载流程分析中,我们成功的dump出了主DEX,那么我们不妨把这个这里的数据到主DEX中搜索一番看看有什么发现吧
在主DEX中搜索第一行的字节,我们惊喜的发现竟然可以搜索到,并且它的位置正好就是0x400000
!
所以说经过RC4解密之后的这部分的数据结构我们也就可以知道了
接下来我们只要搞清楚在sub_18DDB8
中第二次解密的算法是什么就可以了
我们再回到分析的起点sub_128D44
函数,来看看能不能有其他的发现
通过hook sub_128D44
的返回值v150之后发现,三级指针指向着解密之后的dex
而倘若我们观察这个解密数据所在的内存703bc75000
,它正好是0x1000
的倍数,而0x1000
正好就是一页的大小
这意味着什么呢?
这说明dex所在的内存极有可能是通过mmap
函数分配的!
而在stalker_trace_so
打印的数据中,正好就有这个函数被调用了
于是我们来到mmap被调用的函数
使用frida hook mmap返回的内存指针,然后打上内存读写断点,就可以帮助我们快速定位到最终解密算法完成之后赋值的地址
在0x18ebd4
中将值写入最终的目标内存,这个地址在sub_18E8D0
中
在这个函数的开头,有对参数a1的读取操作
我们hook一下值来看看a1各个部分都有什么含义
给a1写一个结构体好了
最后带着我们的结构体逛逛这个解密算法的函数好咯
sub_18F6AC
sub_18DDB8
sub_18E244
sub_18E8D0
这些函数无壳无花无打乱,也没有vmp加固,显得相当的干净,看起来是一个自定义算法
算法千变万化,但是加固永远不是重在算法
逆向一款优秀的加固壳,从中学习到的知识可以受益终生,同时也可以借此思考如何避免让别人轻易找到加固方案的突破口
逢山开路,遇水搭桥。兵来将挡,水来土掩。
我们最后来hook一下下面这个地方来为我们的分析画上圆满的句号吧
至此,加固壳分析完毕
这个加固壳和常规的加固方案类似,也是壳DEX->壳ELF->主ELF->主DEX
这样的过程,其中壳ELF解密主ELF所用到的算法是RC4
和uncompress
,壳ELF加载主ELF所用到的技术是自实现linker加固so
当然这个加固壳还有许多值得继续研究的地方,例如分发so函数的vmp其内部逻辑究竟是什么样子呢?native化的onCreate
函数,dex2c的原理究竟是什么呢?这些都还有待探索,就把它们交给时间和未来的不眠之夜吧
月遇从云,花遇和风,今晚上的夜空很美。
from
com.pnfsoftware.jeb.client.api
import
IScript, IconType, ButtonGroupType
from
com.pnfsoftware.jeb.core
import
RuntimeProjectUtil
from
com.pnfsoftware.jeb.core.units.code.java
import
IJavaSourceUnit
from
com.pnfsoftware.jeb.core.units.code
import
ICodeUnit, ICodeItem
from
com.pnfsoftware.jeb.core.output.text
import
ITextDocument
from
com.pnfsoftware.jeb.core.units.code.java
import
IJavaSourceUnit, IJavaStaticField, IJavaNewArray, IJavaConstant, IJavaCall, IJavaField, IJavaMethod, IJavaClass
from
com.pnfsoftware.jeb.core.events
import
JebEvent, J
from
com.pnfsoftware.jeb.core.util
import
DecompilerHelper
methodName
=
[
'Lcom/qihoo/util/a;'
,
'a'
]
class
dec_str_jiagu(IScript):
def
run(
self
, ctx):
print
(
'start deal with strings'
)
self
.ctx
=
ctx
engctx
=
ctx.getEnginesContext()
if
not
engctx:
print
(
'Back-end engines not initialized'
)
return
projects
=
engctx.getProjects()
if
not
projects:
print
(
'There is no opened project'
)
return
units
=
RuntimeProjectUtil.findUnitsByType(projects[
0
], IJavaSourceUnit,
False
)
for
unit
in
units:
javaClass
=
unit.getClassElement()
print
(
'[+] decrypt:'
+
javaClass.getName())
self
.cstbuilder
=
unit.getFactories().getConstantFactory()
self
.processClass(javaClass)
unit.notifyListeners(JebEvent(J.UnitChange))
print
(
'Done.'
)
def
processClass(
self
, javaClass):
if
javaClass.getName()
=
=
methodName[
0
]:
return
for
method
in
javaClass.getMethods():
block
=
method.getBody()
i
=
0
while
i < block.size():
stm
=
block.get(i)
self
.checkElement(block, stm)
i
+
=
1
def
checkElement(
self
, parent, e):
try
:
if
isinstance
(e, IJavaCall):
mmethod
=
e.getMethod()
mname
=
mmethod.getName()
msig
=
mmethod.getSignature()
if
mname
=
=
methodName[
1
]
and
methodName[
0
]
in
msig:
v
=
[]
for
arg
in
e.getArguments():
if
isinstance
(arg, IJavaConstant):
v.append(arg.getString())
if
len
(v)
=
=
1
:
decstr
=
self
.decryptstring(v[
0
])
parent.replaceSubElement(e,
self
.cstbuilder.createString(decstr))
for
subelt
in
e.getSubElements():
if
isinstance
(subelt, IJavaClass)
or
isinstance
(subelt, IJavaField)
or
isinstance
(subelt, IJavaMethod):
continue
self
.checkElement(e, subelt)
except
:
print
(
'error'
)
def
decryptstring(
self
, string):
src
=
[]
for
index, char
in
enumerate
(string):
src.append(
chr
(
ord
(char) ^
16
))
return
'
'.join(src).decode('
unicode_escape')
from
com.pnfsoftware.jeb.client.api
import
IScript, IconType, ButtonGroupType
from
com.pnfsoftware.jeb.core
import
RuntimeProjectUtil
from
com.pnfsoftware.jeb.core.units.code.java
import
IJavaSourceUnit
from
com.pnfsoftware.jeb.core.units.code
import
ICodeUnit, ICodeItem
from
com.pnfsoftware.jeb.core.output.text
import
ITextDocument
from
com.pnfsoftware.jeb.core.units.code.java
import
IJavaSourceUnit, IJavaStaticField, IJavaNewArray, IJavaConstant, IJavaCall, IJavaField, IJavaMethod, IJavaClass
from
com.pnfsoftware.jeb.core.events
import
JebEvent, J
from
com.pnfsoftware.jeb.core.util
import
DecompilerHelper
methodName
=
[
'Lcom/qihoo/util/a;'
,
'a'
]
class
dec_str_jiagu(IScript):
def
run(
self
, ctx):
print
(
'start deal with strings'
)
self
.ctx
=
ctx
engctx
=
ctx.getEnginesContext()
if
not
engctx:
print
(
'Back-end engines not initialized'
)
return
projects
=
engctx.getProjects()
if
not
projects:
print
(
'There is no opened project'
)
return
units
=
RuntimeProjectUtil.findUnitsByType(projects[
0
], IJavaSourceUnit,
False
)
for
unit
in
units:
javaClass
=
unit.getClassElement()
print
(
'[+] decrypt:'
+
javaClass.getName())
self
.cstbuilder
=
unit.getFactories().getConstantFactory()
self
.processClass(javaClass)
unit.notifyListeners(JebEvent(J.UnitChange))
print
(
'Done.'
)
def
processClass(
self
, javaClass):
if
javaClass.getName()
=
=
methodName[
0
]:
return
for
method
in
javaClass.getMethods():
block
=
method.getBody()
i
=
0
while
i < block.size():
stm
=
block.get(i)
self
.checkElement(block, stm)
i
+
=
1
def
checkElement(
self
, parent, e):
try
:
if
isinstance
(e, IJavaCall):
mmethod
=
e.getMethod()
mname
=
mmethod.getName()
msig
=
mmethod.getSignature()
if
mname
=
=
methodName[
1
]
and
methodName[
0
]
in
msig:
v
=
[]
for
arg
in
e.getArguments():
if
isinstance
(arg, IJavaConstant):
v.append(arg.getString())
if
len
(v)
=
=
1
:
decstr
=
self
.decryptstring(v[
0
])
parent.replaceSubElement(e,
self
.cstbuilder.createString(decstr))
for
subelt
in
e.getSubElements():
if
isinstance
(subelt, IJavaClass)
or
isinstance
(subelt, IJavaField)
or
isinstance
(subelt, IJavaMethod):
continue
self
.checkElement(e, subelt)
except
:
print
(
'error'
)
def
decryptstring(
self
, string):
src
=
[]
for
index, char
in
enumerate
(string):
src.append(
chr
(
ord
(char) ^
16
))
return
'
'.join(src).decode('
unicode_escape')
PS D:\frida> adb shell
blueline:/ $
su
blueline:/
blueline:
/data/local/tmp
PS D:\frida> adb shell
blueline:/ $
su
blueline:/
blueline:
/data/local/tmp
adb forward tcp:1234 tcp:1234
adb forward tcp:1234 tcp:1234
frida -H 127.0.0.1:1234 -l .\hook.js -f
"com.oacia.apk_protect"
frida -H 127.0.0.1:1234 -l .\hook.js -f
"com.oacia.apk_protect"
function
my_hook_dlopen(soName =
''
) {
Interceptor.attach(Module.findExportByName(
null
,
"android_dlopen_ext"
),
{
onEnter:
function
(args) {
var
pathptr = args[0];
if
(pathptr !== undefined && pathptr !=
null
) {
var
path = ptr(pathptr).readCString();
if
(path.indexOf(soName) >= 0) {
this
.is_can_hook =
true
;
}
}
},
onLeave:
function
(retval) {
if
(
this
.is_can_hook) {
dump_so(
"libjiagu_64.so"
);
}
}
}
);
}
function
dump_so(so_name) {
var
libso = Process.getModuleByName(so_name);
console.log(
"[name]:"
, libso.name);
console.log(
"[base]:"
, libso.base);
console.log(
"[size]:"
, ptr(libso.size));
console.log(
"[path]:"
, libso.path);
var
file_path =
"/data/data/com.oacia.apk_protect/"
+ libso.name +
"_"
+ libso.base +
"_"
+ ptr(libso.size) +
".so"
;
var
file_handle =
new
File(file_path,
"wb"
);
if
(file_handle && file_handle !=
null
) {
Memory.protect(ptr(libso.base), libso.size,
'rwx'
);
var
libso_buffer = ptr(libso.base).readByteArray(libso.size);
file_handle.write(libso_buffer);
file_handle.flush();
file_handle.close();
console.log(
"[dump]:"
, file_path);
}
}
setImmediate(my_hook_dlopen(
"libjiagu_64.so"
));
function
my_hook_dlopen(soName =
''
) {
Interceptor.attach(Module.findExportByName(
null
,
"android_dlopen_ext"
),
{
onEnter:
function
(args) {
var
pathptr = args[0];
if
(pathptr !== undefined && pathptr !=
null
) {
var
path = ptr(pathptr).readCString();
if
(path.indexOf(soName) >= 0) {
this
.is_can_hook =
true
;
}
}
},
onLeave:
function
(retval) {
if
(
this
.is_can_hook) {
dump_so(
"libjiagu_64.so"
);
}
}
}
);
}
function
dump_so(so_name) {
var
libso = Process.getModuleByName(so_name);
console.log(
"[name]:"
, libso.name);
console.log(
"[base]:"
, libso.base);
console.log(
"[size]:"
, ptr(libso.size));
console.log(
"[path]:"
, libso.path);
var
file_path =
"/data/data/com.oacia.apk_protect/"
+ libso.name +
"_"
+ libso.base +
"_"
+ ptr(libso.size) +
".so"
;
var
file_handle =
new
File(file_path,
"wb"
);
if
(file_handle && file_handle !=
null
) {
Memory.protect(ptr(libso.base), libso.size,
'rwx'
);
var
libso_buffer = ptr(libso.base).readByteArray(libso.size);
file_handle.write(libso_buffer);
file_handle.flush();
file_handle.close();
console.log(
"[dump]:"
, file_path);
}
}
setImmediate(my_hook_dlopen(
"libjiagu_64.so"
));
.\SoFixer-Windows-64.exe -s .\libjiagu_64.so_0x74a2845000_0x274000.so -o .\libjiagu_64_0x74a2845000_0x274000_fix.so -m 0x74a2845000 -d
.\SoFixer-Windows-64.exe -s .\libjiagu_64.so_0x74a2845000_0x274000.so -o .\libjiagu_64_0x74a2845000_0x274000_fix.so -m 0x74a2845000 -d
function
hook_dlopen() {
Interceptor.attach(Module.findExportByName(
null
,
"android_dlopen_ext"
),
{
onEnter:
function
(args) {
var
pathptr = args[0];
if
(pathptr !== undefined && pathptr !=
null
) {
var
path = ptr(pathptr).readCString();
console.log(
"load "
+ path);
}
}
}
);
}
setImmediate(hook_dlopen)
function
hook_dlopen() {
Interceptor.attach(Module.findExportByName(
null
,
"android_dlopen_ext"
),
{
onEnter:
function
(args) {
var
pathptr = args[0];
if
(pathptr !== undefined && pathptr !=
null
) {
var
path = ptr(pathptr).readCString();
console.log(
"load "
+ path);
}
}
}
);
}
setImmediate(hook_dlopen)
load libstats_jni.so
load
/data/app/
~~P6meiEqXSQZrP2ChUgVgOg==
/com
.oacia.apk_protect-ezyVSLdtBZmLTZejgPlSoQ==
/oat/arm64/base
.odex
load
/data/data/com
.oacia.apk_protect/.jiagu
/libjiagu_64
.so
load libstats_jni.so
load
/data/app/
~~P6meiEqXSQZrP2ChUgVgOg==
/com
.oacia.apk_protect-ezyVSLdtBZmLTZejgPlSoQ==
/oat/arm64/base
.odex
load
/data/data/com
.oacia.apk_protect/.jiagu
/libjiagu_64
.so
function
my_hook_dlopen(soName =
''
) {
Interceptor.attach(Module.findExportByName(
null
,
"android_dlopen_ext"
),
{
onEnter:
function
(args) {
var
pathptr = args[0];
if
(pathptr !== undefined && pathptr !=
null
) {
var
path = ptr(pathptr).readCString();
if
(path.indexOf(soName) >= 0) {
this
.is_can_hook =
true
;
}
}
},
onLeave:
function
(retval) {
if
(
this
.is_can_hook) {
hook_open();
}
}
}
);
}
function
hook_open(){
var
pth = Module.findExportByName(
null
,
"open"
);
Interceptor.attach(ptr(pth),{
onEnter:
function
(args){
this
.filename = args[0];
console.log(
""
,
this
.filename.readCString())
},onLeave:
function
(retval){
}
})
}
setImmediate(my_hook_dlopen,
"libjiagu"
);
function
my_hook_dlopen(soName =
''
) {
Interceptor.attach(Module.findExportByName(
null
,
"android_dlopen_ext"
),
{
onEnter:
function
(args) {
var
pathptr = args[0];
if
(pathptr !== undefined && pathptr !=
null
) {
var
path = ptr(pathptr).readCString();
if
(path.indexOf(soName) >= 0) {
this
.is_can_hook =
true
;
}
}
},
onLeave:
function
(retval) {
if
(
this
.is_can_hook) {
hook_open();
}
}
}
);
}
function
hook_open(){
var
pth = Module.findExportByName(
null
,
"open"
);
Interceptor.attach(ptr(pth),{
onEnter:
function
(args){
this
.filename = args[0];
console.log(
""
,
this
.filename.readCString())
},onLeave:
function
(retval){
}
})
}
setImmediate(my_hook_dlopen,
"libjiagu"
);
cp
/proc/self/maps
/data/data/com
.oacia.apk_protect
/maps
cp
/proc/self/maps
/data/data/com
.oacia.apk_protect
/maps
function
my_hook_dlopen(soName =
''
) {
Interceptor.attach(Module.findExportByName(
null
,
"android_dlopen_ext"
),
{
onEnter:
function
(args) {
var
pathptr = args[0];
if
(pathptr !== undefined && pathptr !=
null
) {
var
path = ptr(pathptr).readCString();
if
(path.indexOf(soName) >= 0) {
this
.is_can_hook =
true
;
}
}
},
onLeave:
function
(retval) {
if
(
this
.is_can_hook) {
hook_proc_self_maps();
}
}
}
);
}
function
hook_proc_self_maps() {
const openPtr = Module.getExportByName(
null
,
'open'
);
const open =
new
NativeFunction(openPtr,
'int'
, [
'pointer'
,
'int'
]);
var
fakePath =
"/data/data/com.oacia.apk_protect/maps"
;
Interceptor.replace(openPtr,
new
NativeCallback(
function
(pathnameptr, flag) {
var
pathname = Memory.readUtf8String(pathnameptr);
console.log(
"open"
,pathname);
if
(pathname.indexOf(
"maps"
) >= 0) {
console.log(
"find"
,pathname,
",redirect to"
,fakePath);
var
filename = Memory.allocUtf8String(fakePath);
return
open(filename, flag);
}
var
fd = open(pathnameptr, flag);
return
fd;
},
'int'
, [
'pointer'
,
'int'
]));
}
setImmediate(my_hook_dlopen,
"libjiagu"
);
function
my_hook_dlopen(soName =
''
) {
Interceptor.attach(Module.findExportByName(
null
,
"android_dlopen_ext"
),
{
onEnter:
function
(args) {
var
pathptr = args[0];
if
(pathptr !== undefined && pathptr !=
null
) {
var
path = ptr(pathptr).readCString();
if
(path.indexOf(soName) >= 0) {
this
.is_can_hook =
true
;
}
}
},
onLeave:
function
(retval) {
if
(
this
.is_can_hook) {
hook_proc_self_maps();
}
}
}
);
}
function
hook_proc_self_maps() {
const openPtr = Module.getExportByName(
null
,
'open'
);
const open =
new
NativeFunction(openPtr,
'int'
, [
'pointer'
,
'int'
]);
var
fakePath =
"/data/data/com.oacia.apk_protect/maps"
;
Interceptor.replace(openPtr,
new
NativeCallback(
function
(pathnameptr, flag) {
var
pathname = Memory.readUtf8String(pathnameptr);
console.log(
"open"
,pathname);
if
(pathname.indexOf(
"maps"
) >= 0) {
console.log(
"find"
,pathname,
",redirect to"
,fakePath);
var
filename = Memory.allocUtf8String(fakePath);
return
open(filename, flag);
}
var
fd = open(pathnameptr, flag);
return
fd;
},
'int'
, [
'pointer'
,
'int'
]));
}
setImmediate(my_hook_dlopen,
"libjiagu"
);
console.log(
'RegisterNatives called from:\\n'
+ Thread.backtrace(
this
.context, Backtracer.FUZZY).map(DebugSymbol.fromAddress).join(
'\\n'
) +
'\\n'
);
console.log(
'RegisterNatives called from:\\n'
+ Thread.backtrace(
this
.context, Backtracer.FUZZY).map(DebugSymbol.fromAddress).join(
'\\n'
) +
'\\n'
);
function
addr_in_so(addr){
var
process_Obj_Module_Arr = Process.enumerateModules();
for
(
var
i = 0; i < process_Obj_Module_Arr.length; i++) {
if
(addr>process_Obj_Module_Arr[i].base && addr<process_Obj_Module_Arr[i].base.add(process_Obj_Module_Arr[i].size)){
console.log(addr.toString(16),
"is in"
,process_Obj_Module_Arr[i].name,
"offset: 0x"
+(addr-process_Obj_Module_Arr[i].base).toString(16));
}
}
}
function
hook_proc_self_maps() {
const openPtr = Module.getExportByName(
null
,
'open'
);
const open =
new
NativeFunction(openPtr,
'int'
, [
'pointer'
,
'int'
]);
var
fakePath =
"/data/data/com.oacia.apk_protect/maps_nonexistent"
;
Interceptor.replace(openPtr,
new
NativeCallback(
function
(pathnameptr, flag) {
var
pathname = Memory.readUtf8String(pathnameptr);
console.log(
"open"
,pathname);
if
(pathname.indexOf(
"maps"
) >= 0) {
console.log(
"find"
,pathname+
", redirect to"
,fakePath);
var
filename = Memory.allocUtf8String(fakePath);
return
open(filename, flag);
}
if
(pathname.indexOf(
"dex"
) >= 0) {
Thread.backtrace(
this
.context, Backtracer.FUZZY).map(addr_in_so);
}
var
fd = open(pathnameptr, flag);
return
fd;
},
'int'
, [
'pointer'
,
'int'
]));
}
function
my_hook_dlopen(soName=
''
) {
Interceptor.attach(Module.findExportByName(
null
,
"android_dlopen_ext"
),
{
onEnter:
function
(args) {
var
pathptr = args[0];
if
(pathptr !== undefined && pathptr !=
null
) {
var
path = ptr(pathptr).readCString();
if
(path.indexOf(soName) >= 0) {
this
.is_can_hook =
true
;
}
}
},
onLeave:
function
(retval) {
if
(
this
.is_can_hook) {
hook_proc_self_maps();
}
}
}
);
}
setImmediate(my_hook_dlopen,
'libjiagu'
);
function
addr_in_so(addr){
var
process_Obj_Module_Arr = Process.enumerateModules();
for
(
var
i = 0; i < process_Obj_Module_Arr.length; i++) {
if
(addr>process_Obj_Module_Arr[i].base && addr<process_Obj_Module_Arr[i].base.add(process_Obj_Module_Arr[i].size)){
console.log(addr.toString(16),
"is in"
,process_Obj_Module_Arr[i].name,
"offset: 0x"
+(addr-process_Obj_Module_Arr[i].base).toString(16));
}
}
}
function
hook_proc_self_maps() {
const openPtr = Module.getExportByName(
null
,
'open'
);
const open =
new
NativeFunction(openPtr,
'int'
, [
'pointer'
,
'int'
]);
var
fakePath =
"/data/data/com.oacia.apk_protect/maps_nonexistent"
;
Interceptor.replace(openPtr,
new
NativeCallback(
function
(pathnameptr, flag) {
var
pathname = Memory.readUtf8String(pathnameptr);
console.log(
"open"
,pathname);
if
(pathname.indexOf(
"maps"
) >= 0) {
console.log(
"find"
,pathname+
", redirect to"
,fakePath);
var
filename = Memory.allocUtf8String(fakePath);
return
open(filename, flag);
}
if
(pathname.indexOf(
"dex"
) >= 0) {
Thread.backtrace(
this
.context, Backtracer.FUZZY).map(addr_in_so);
}
var
fd = open(pathnameptr, flag);
return
fd;
},
'int'
, [
'pointer'
,
'int'
]));
}
function
my_hook_dlopen(soName=
''
) {
Interceptor.attach(Module.findExportByName(
null
,
"android_dlopen_ext"
),
{
onEnter:
function
(args) {
var
pathptr = args[0];
if
(pathptr !== undefined && pathptr !=
null
) {
var
path = ptr(pathptr).readCString();
if
(path.indexOf(soName) >= 0) {
this
.is_can_hook =
true
;
}
}
},
onLeave:
function
(retval) {
if
(
this
.is_can_hook) {
hook_proc_self_maps();
}
}
}
);
}
setImmediate(my_hook_dlopen,
'libjiagu'
);
function
dump_so(so_name) {
var
libso = Process.getModuleByName(so_name);
console.log(
"[name]:"
, libso.name);
console.log(
"[base]:"
, libso.base);
console.log(
"[size]:"
, ptr(libso.size));
console.log(
"[path]:"
, libso.path);
var
file_path =
"/data/data/com.oacia.apk_protect/"
+ libso.name +
"_"
+ libso.base +
"_"
+ ptr(libso.size) +
".so"
;
var
file_handle =
new
File(file_path,
"wb"
);
if
(file_handle && file_handle !=
null
) {
Memory.protect(ptr(libso.base), libso.size,
'rwx'
);
var
libso_buffer = ptr(libso.base).readByteArray(libso.size);
file_handle.write(libso_buffer);
file_handle.flush();
file_handle.close();
console.log(
"[dump]:"
, file_path);
}
}
var
dump_once =
false
;
function
hook_proc_self_maps() {
const openPtr = Module.getExportByName(
null
,
'open'
);
const open =
new
NativeFunction(openPtr,
'int'
, [
'pointer'
,
'int'
]);
var
fakePath =
"/data/data/com.oacia.apk_protect/maps_nonexistent"
;
Interceptor.replace(openPtr,
new
NativeCallback(
function
(pathnameptr, flag) {
var
pathname = Memory.readUtf8String(pathnameptr);
console.log(
"open"
,pathname);
if
(pathname.indexOf(
"maps"
) >= 0) {
console.log(
"find"
,pathname+
", redirect to"
,fakePath);
var
filename = Memory.allocUtf8String(fakePath);
return
open(filename, flag);
}
if
(pathname.indexOf(
"dex"
) >= 0) {
if
(!dump_once){
dump_once =
true
;
dump_so(
"libjiagu_64.so"
);
}
}
var
fd = open(pathnameptr, flag);
return
fd;
},
'int'
, [
'pointer'
,
'int'
]));
}
function
dump_so(so_name) {
var
libso = Process.getModuleByName(so_name);
console.log(
"[name]:"
, libso.name);
console.log(
"[base]:"
, libso.base);
console.log(
"[size]:"
, ptr(libso.size));
console.log(
"[path]:"
, libso.path);
var
file_path =
"/data/data/com.oacia.apk_protect/"
+ libso.name +
"_"
+ libso.base +
"_"
+ ptr(libso.size) +
".so"
;
var
file_handle =
new
File(file_path,
"wb"
);
if
(file_handle && file_handle !=
null
) {
Memory.protect(ptr(libso.base), libso.size,
'rwx'
);
var
libso_buffer = ptr(libso.base).readByteArray(libso.size);
file_handle.write(libso_buffer);
file_handle.flush();
file_handle.close();
console.log(
"[dump]:"
, file_path);
}
}
var
dump_once =
false
;
function
hook_proc_self_maps() {
const openPtr = Module.getExportByName(
null
,
'open'
);
const open =
new
NativeFunction(openPtr,
'int'
, [
'pointer'
,
'int'
]);
var
fakePath =
"/data/data/com.oacia.apk_protect/maps_nonexistent"
;
Interceptor.replace(openPtr,
new
NativeCallback(
function
(pathnameptr, flag) {
var
pathname = Memory.readUtf8String(pathnameptr);
console.log(
"open"
,pathname);
if
(pathname.indexOf(
"maps"
) >= 0) {
console.log(
"find"
,pathname+
", redirect to"
,fakePath);
var
filename = Memory.allocUtf8String(fakePath);
return
open(filename, flag);
}
if
(pathname.indexOf(
"dex"
) >= 0) {
if
(!dump_once){
dump_once =
true
;
dump_so(
"libjiagu_64.so"
);
}
}
var
fd = open(pathnameptr, flag);
return
fd;
},
'int'
, [
'pointer'
,
'int'
]));
}
.\SoFixer-Windows-64.exe -s .\libjiagu_64.so_0x7a69829000_0x274000_open_classes.dex.so -o .\libjiagu_64.so_0x7a69829000_0x274000_open_classes.dex_fix.so -m 0x7a69829000 -d
.\SoFixer-Windows-64.exe -s .\libjiagu_64.so_0x7a69829000_0x274000_open_classes.dex.so -o .\libjiagu_64.so_0x7a69829000_0x274000_open_classes.dex_fix.so -m 0x7a69829000 -d
with
open
(
'libjiagu_64.so_0x7a69829000_0x274000_open_classes.dex.so'
,
'rb'
) as f:
s
=
f.read()
with
open
(
'libjiagu_0xe7000.so'
,
'wb'
) as f:
f.write(s[
0xe7000
::])
with
open
(
'libjiagu_64.so_0x7a69829000_0x274000_open_classes.dex.so'
,
'rb'
) as f:
s
=
f.read()
with
open
(
'libjiagu_0xe7000.so'
,
'wb'
) as f:
f.write(s[
0xe7000
::])
#define __LP64__ 1
#if defined(__LP64__)
#define ElfW(type) Elf64_ ## type
#else
#define ElfW(type) Elf32_ ## type
#endif
#if defined(__LP64__)
#define USE_RELA 1
#endif
typedef
signed
char
__s8;
typedef
unsigned
char
__u8;
typedef
signed
short
__s16;
typedef
unsigned
short
__u16;
typedef
signed
int
__s32;
typedef
unsigned
int
__u32;
typedef
signed
long
long
__s64;
typedef
unsigned
long
long
__u64;
typedef
__u32 Elf32_Addr;
typedef
__u16 Elf32_Half;
typedef
__u32 Elf32_Off;
typedef
__s32 Elf32_Sword;
typedef
__u32 Elf32_Word;
typedef
__u64 Elf64_Addr;
typedef
__u16 Elf64_Half;
typedef
__s16 Elf64_SHalf;
typedef
__u64 Elf64_Off;
typedef
__s32 Elf64_Sword;
typedef
__u32 Elf64_Word;
typedef
__u64 Elf64_Xword;
typedef
__s64 Elf64_Sxword;
typedef
struct
dynamic{
Elf32_Sword d_tag;
union
{
Elf32_Sword d_val;
Elf32_Addr d_ptr;
} d_un;
} Elf32_Dyn;
typedef
struct
{
Elf64_Sxword d_tag;
union
{
Elf64_Xword d_val;
Elf64_Addr d_ptr;
} d_un;
} Elf64_Dyn;
typedef
struct
elf32_rel {
Elf32_Addr r_offset;
Elf32_Word r_info;
} Elf32_Rel;
typedef
struct
elf64_rel {
Elf64_Addr r_offset;
Elf64_Xword r_info;
} Elf64_Rel;
typedef
struct
elf32_rela{
Elf32_Addr r_offset;
Elf32_Word r_info;
Elf32_Sword r_addend;
} Elf32_Rela;
typedef
struct
elf64_rela {
Elf64_Addr r_offset;
Elf64_Xword r_info;
Elf64_Sxword r_addend;
} Elf64_Rela;
typedef
struct
elf32_sym{
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned
char
st_info;
unsigned
char
st_other;
Elf32_Half st_shndx;
} Elf32_Sym;
typedef
struct
elf64_sym {
Elf64_Word st_name;
unsigned
char
st_info;
unsigned
char
st_other;
Elf64_Half st_shndx;
Elf64_Addr st_value;
Elf64_Xword st_size;
} Elf64_Sym;
#define EI_NIDENT 16
typedef
struct
elf32_hdr{
unsigned
char
e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
} Elf32_Ehdr;
typedef
struct
elf64_hdr {
unsigned
char
e_ident[EI_NIDENT];
Elf64_Half e_type;
Elf64_Half e_machine;
Elf64_Word e_version;
Elf64_Addr e_entry;
Elf64_Off e_phoff;
Elf64_Off e_shoff;
Elf64_Word e_flags;
Elf64_Half e_ehsize;
Elf64_Half e_phentsize;
Elf64_Half e_phnum;
Elf64_Half e_shentsize;
Elf64_Half e_shnum;
Elf64_Half e_shstrndx;
} Elf64_Ehdr;
#define PF_R 0x4
#define PF_W 0x2
#define PF_X 0x1
typedef
struct
elf32_phdr{
Elf32_Word p_type;
Elf32_Off p_offset;
Elf32_Addr p_vaddr;
Elf32_Addr p_paddr;
Elf32_Word p_filesz;
Elf32_Word p_memsz;
Elf32_Word p_flags;
Elf32_Word p_align;
} Elf32_Phdr;
typedef
struct
elf64_phdr {
Elf64_Word p_type;
Elf64_Word p_flags;
Elf64_Off p_offset;
Elf64_Addr p_vaddr;
Elf64_Addr p_paddr;
Elf64_Xword p_filesz;
Elf64_Xword p_memsz;
Elf64_Xword p_align;
} Elf64_Phdr;
typedef
struct
elf32_shdr {
Elf32_Word sh_name;
Elf32_Word sh_type;
Elf32_Word sh_flags;
Elf32_Addr sh_addr;
Elf32_Off sh_offset;
Elf32_Word sh_size;
Elf32_Word sh_link;
Elf32_Word sh_info;
Elf32_Word sh_addralign;
Elf32_Word sh_entsize;
} Elf32_Shdr;
typedef
struct
elf64_shdr {
Elf64_Word sh_name;
Elf64_Word sh_type;
Elf64_Xword sh_flags;
Elf64_Addr sh_addr;
Elf64_Off sh_offset;
Elf64_Xword sh_size;
Elf64_Word sh_link;
Elf64_Word sh_info;
Elf64_Xword sh_addralign;
Elf64_Xword sh_entsize;
} Elf64_Shdr;
typedef
void
(*linker_dtor_function_t)();
typedef
void
(*linker_ctor_function_t)(
int
,
char
**,
char
**);
#if defined(__work_around_b_24465209__)
#define SOINFO_NAME_LEN 128
#endif
struct
soinfo {
#if defined(__work_around_b_24465209__)
char
old_name_[SOINFO_NAME_LEN];
#endif
const
ElfW(Phdr)* phdr;
size_t
phnum;
#if defined(__work_around_b_24465209__)
ElfW(Addr) unused0;
#endif
ElfW(Addr) base;
size_t
size;
#if defined(__work_around_b_24465209__)
uint32_t unused1;
#endif
ElfW(Dyn)* dynamic;
#if defined(__work_around_b_24465209__)
uint32_t unused2;
uint32_t unused3;
#endif
soinfo* next;
uint32_t flags_;
const
char
* strtab_;
ElfW(Sym)* symtab_;
size_t
nbucket_;
size_t
nchain_;
uint32_t* bucket_;
uint32_t* chain_;
#if !defined(__LP64__)
ElfW(Addr)** unused4;
#endif
#if defined(USE_RELA)
ElfW(Rela)* plt_rela_;
size_t
plt_rela_count_;
ElfW(Rela)* rela_;
size_t
rela_count_;
#else
ElfW(Rel)* plt_rel_;
size_t
plt_rel_count_;
ElfW(Rel)* rel_;
size_t
rel_count_;
#endif
linker_ctor_function_t* preinit_array_;
size_t
preinit_array_count_;
linker_ctor_function_t* init_array_;
size_t
init_array_count_;
linker_dtor_function_t* fini_array_;
size_t
fini_array_count_;
linker_ctor_function_t init_func_;
linker_dtor_function_t fini_func_;
};
#define __LP64__ 1
#if defined(__LP64__)
#define ElfW(type) Elf64_ ## type
#else
#define ElfW(type) Elf32_ ## type
#endif
#if defined(__LP64__)
#define USE_RELA 1
#endif
typedef
signed
char
__s8;
typedef
unsigned
char
__u8;
typedef
signed
short
__s16;
typedef
unsigned
short
__u16;
typedef
signed
int
__s32;
typedef
unsigned
int
__u32;
typedef
signed
long
long
__s64;
typedef
unsigned
long
long
__u64;
typedef
__u32 Elf32_Addr;
typedef
__u16 Elf32_Half;
typedef
__u32 Elf32_Off;
typedef
__s32 Elf32_Sword;
typedef
__u32 Elf32_Word;
typedef
__u64 Elf64_Addr;
typedef
__u16 Elf64_Half;
typedef
__s16 Elf64_SHalf;
typedef
__u64 Elf64_Off;
typedef
__s32 Elf64_Sword;
typedef
__u32 Elf64_Word;
typedef
__u64 Elf64_Xword;
typedef
__s64 Elf64_Sxword;
typedef
struct
dynamic{
Elf32_Sword d_tag;
union
{
Elf32_Sword d_val;
Elf32_Addr d_ptr;
} d_un;
} Elf32_Dyn;
typedef
struct
{
Elf64_Sxword d_tag;
union
{
Elf64_Xword d_val;
Elf64_Addr d_ptr;
} d_un;
} Elf64_Dyn;
typedef
struct
elf32_rel {
Elf32_Addr r_offset;
Elf32_Word r_info;
} Elf32_Rel;
typedef
struct
elf64_rel {
Elf64_Addr r_offset;
Elf64_Xword r_info;
} Elf64_Rel;
typedef
struct
elf32_rela{
Elf32_Addr r_offset;
Elf32_Word r_info;
Elf32_Sword r_addend;
} Elf32_Rela;
typedef
struct
elf64_rela {
Elf64_Addr r_offset;
Elf64_Xword r_info;
Elf64_Sxword r_addend;
} Elf64_Rela;
typedef
struct
elf32_sym{
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned
char
st_info;
unsigned
char
st_other;
Elf32_Half st_shndx;
} Elf32_Sym;
typedef
struct
elf64_sym {
Elf64_Word st_name;
unsigned
char
st_info;
unsigned
char
st_other;
Elf64_Half st_shndx;
Elf64_Addr st_value;
Elf64_Xword st_size;
} Elf64_Sym;
#define EI_NIDENT 16
typedef
struct
elf32_hdr{
unsigned
char
e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
} Elf32_Ehdr;
typedef
struct
elf64_hdr {
unsigned
char
e_ident[EI_NIDENT];
Elf64_Half e_type;
Elf64_Half e_machine;
Elf64_Word e_version;
Elf64_Addr e_entry;
Elf64_Off e_phoff;
Elf64_Off e_shoff;
Elf64_Word e_flags;
Elf64_Half e_ehsize;
Elf64_Half e_phentsize;
Elf64_Half e_phnum;
Elf64_Half e_shentsize;
Elf64_Half e_shnum;
Elf64_Half e_shstrndx;
} Elf64_Ehdr;
#define PF_R 0x4
#define PF_W 0x2
#define PF_X 0x1
typedef
struct
elf32_phdr{
Elf32_Word p_type;
Elf32_Off p_offset;
Elf32_Addr p_vaddr;
Elf32_Addr p_paddr;
Elf32_Word p_filesz;
Elf32_Word p_memsz;
Elf32_Word p_flags;
Elf32_Word p_align;
} Elf32_Phdr;
typedef
struct
elf64_phdr {
Elf64_Word p_type;
Elf64_Word p_flags;
Elf64_Off p_offset;
Elf64_Addr p_vaddr;
Elf64_Addr p_paddr;
Elf64_Xword p_filesz;
Elf64_Xword p_memsz;
Elf64_Xword p_align;
} Elf64_Phdr;
typedef
struct
elf32_shdr {
Elf32_Word sh_name;
Elf32_Word sh_type;
Elf32_Word sh_flags;
Elf32_Addr sh_addr;
Elf32_Off sh_offset;
Elf32_Word sh_size;
Elf32_Word sh_link;
Elf32_Word sh_info;
Elf32_Word sh_addralign;
Elf32_Word sh_entsize;
} Elf32_Shdr;
typedef
struct
elf64_shdr {
Elf64_Word sh_name;
Elf64_Word sh_type;
Elf64_Xword sh_flags;
Elf64_Addr sh_addr;
Elf64_Off sh_offset;
Elf64_Xword sh_size;
Elf64_Word sh_link;
Elf64_Word sh_info;
Elf64_Xword sh_addralign;
Elf64_Xword sh_entsize;
} Elf64_Shdr;
typedef
void
(*linker_dtor_function_t)();
typedef
void
(*linker_ctor_function_t)(
int
,
char
**,
char
**);
#if defined(__work_around_b_24465209__)
#define SOINFO_NAME_LEN 128
#endif
struct
soinfo {
#if defined(__work_around_b_24465209__)
char
old_name_[SOINFO_NAME_LEN];
#endif
const
ElfW(Phdr)* phdr;
size_t
phnum;
#if defined(__work_around_b_24465209__)
ElfW(Addr) unused0;
#endif
ElfW(Addr) base;
size_t
size;
#if defined(__work_around_b_24465209__)
uint32_t unused1;
#endif
ElfW(Dyn)* dynamic;
#if defined(__work_around_b_24465209__)
uint32_t unused2;
uint32_t unused3;
#endif
soinfo* next;
uint32_t flags_;
const
char
* strtab_;
ElfW(Sym)* symtab_;
size_t
nbucket_;
size_t
nchain_;
uint32_t* bucket_;
uint32_t* chain_;
#if !defined(__LP64__)
ElfW(Addr)** unused4;
#endif
#if defined(USE_RELA)
ElfW(Rela)* plt_rela_;
size_t
plt_rela_count_;
ElfW(Rela)* rela_;
size_t
rela_count_;
#else
ElfW(Rel)* plt_rel_;
size_t
plt_rel_count_;
ElfW(Rel)* rel_;
size_t
rel_count_;
#endif
linker_ctor_function_t* preinit_array_;
size_t
preinit_array_count_;
linker_ctor_function_t* init_array_;
size_t
init_array_count_;
linker_dtor_function_t* fini_array_;
size_t
fini_array_count_;
linker_ctor_function_t init_func_;
linker_dtor_function_t fini_func_;
};
function
hook_5E6C(){
var
module = Process.findModuleByName(
"libjiagu_64.so"
);
Interceptor.attach(module.base.add(0x5E6C), {
onEnter:
function
(args) {
console.log(hexdump(args[0], {
offset: 0,
length: 0x38*0x6+0x20,
header:
true
,
ansi:
true
}));
console.log(args[1])
console.log(args[2])
console.log(`base = ${module.base}`)
},
onLeave:
function
(ret) {
}
});
}
function
hook_5E6C(){
var
module = Process.findModuleByName(
"libjiagu_64.so"
);
Interceptor.attach(module.base.add(0x5E6C), {
onEnter:
function
(args) {
console.log(hexdump(args[0], {
offset: 0,
length: 0x38*0x6+0x20,
header:
true
,
ansi:
true
}));
console.log(args[1])
console.log(args[2])
console.log(`base = ${module.base}`)
},
onLeave:
function
(ret) {
}
});
}
call1:JNI_OnLoad
call2:j_interpreter_wrap_int64_t
call3:interpreter_wrap_int64_t
call4:
getenv
call5:sub_13908
call6:inotify_add_watch
call7:sub_11220
call8:
fopen
call9:sub_9DD8
call10:sub_E3E0
call11:
strtol
call12:
feof
call13:
raise
call14:
memset
call15:sub_C918
call16:sub_9988
call17:sub_9964
call18:sub_9AC4
call19:j_ffi_prep_cif
call20:ffi_prep_cif
call21:j_ffi_prep_cif_machdep
call22:ffi_prep_cif_machdep
call23:j_ffi_call
call24:ffi_call
call25:sub_1674C
call26:j_ffi_call_SYSV
call27:ffi_call_SYSV
call28:sub_167BC
call29:sub_1647C
call30:sub_163DC
call31:sub_9900
call32:sub_94BC
call33:inotify_init
call34:
fmod
call35:
strncpy
call36:_Z9__arm_a_1P7_JavaVMP7_JNIEnvPvRi
call37:sub_9E58
call38:sub_999C
call39:sub_10964
call40:j_lseek_1
call41:lseek
call42:sub_96E0
call43:sub_8000
call44:dlopen
call45:sub_60E0
call46:sub_6544
call47:sub_4B54
call48:sub_6128
call49:_ZN9__arm_c_19__arm_c_0Ev
call50:sub_A3EC
call51:sub_99CC
call52:sub_9944
call53:sub_6484
call54:sub_6590
call55:prctl
call56:sub_6698
call57:sub_9FFC
call58:j_lseek_3
call59:j_lseek_2
call60:j_lseek_0
call61:sub_9A90
call62:sub_5F20
call63:sub_6044
call64:sub_3574
call65:uncompress
call66:sub_49F0
call67:sub_5400
call68:sub_5478
call69:sub_5B08
call70:sub_5650
call71:sub_580C
call72:open
call73:
atoi
call74:sub_3C94
call75:
strncmp
call76:sub_4918
call77:sub_4000
call78:sub_41B4
call79:sub_35AC
call80:sigaction
call81:sub_5E6C
call82:sub_5444
call83:sub_633C
call84:sub_8130
call85:sub_4C70
call86:sub_825C
call87:sub_8B50
call88:sub_8ED4
call89:sub_8430
call90:interpreter_wrap_int64_t_bridge
call91:sub_9D60
call92:sub_166C4
call93:
memcpy
call94:_Z9__arm_a_2PcmS_Rii
call95:j_ffi_prep_cif_var
call96:ffi_prep_cif_var
call1:JNI_OnLoad
call2:j_interpreter_wrap_int64_t
call3:interpreter_wrap_int64_t
call4:
getenv
call5:sub_13908
call6:inotify_add_watch
call7:sub_11220
call8:
fopen
call9:sub_9DD8
call10:sub_E3E0
call11:
strtol
call12:
feof
call13:
raise
call14:
memset
call15:sub_C918
call16:sub_9988
call17:sub_9964
call18:sub_9AC4
call19:j_ffi_prep_cif
call20:ffi_prep_cif
call21:j_ffi_prep_cif_machdep
call22:ffi_prep_cif_machdep
call23:j_ffi_call
call24:ffi_call
call25:sub_1674C
call26:j_ffi_call_SYSV
call27:ffi_call_SYSV
call28:sub_167BC
call29:sub_1647C
call30:sub_163DC
call31:sub_9900
call32:sub_94BC
call33:inotify_init
call34:
fmod
call35:
strncpy
call36:_Z9__arm_a_1P7_JavaVMP7_JNIEnvPvRi
call37:sub_9E58
call38:sub_999C
call39:sub_10964
call40:j_lseek_1
call41:lseek
call42:sub_96E0
call43:sub_8000
call44:dlopen
call45:sub_60E0
call46:sub_6544
call47:sub_4B54
call48:sub_6128
call49:_ZN9__arm_c_19__arm_c_0Ev
call50:sub_A3EC
call51:sub_99CC
call52:sub_9944
call53:sub_6484
call54:sub_6590
call55:prctl
call56:sub_6698
call57:sub_9FFC
call58:j_lseek_3
call59:j_lseek_2
call60:j_lseek_0
call61:sub_9A90
call62:sub_5F20
call63:sub_6044
call64:sub_3574
call65:uncompress
call66:sub_49F0
call67:sub_5400
call68:sub_5478
call69:sub_5B08
call70:sub_5650
call71:sub_580C
call72:open
call73:
atoi
call74:sub_3C94
call75:
strncmp
call76:sub_4918
call77:sub_4000
call78:sub_41B4
call79:sub_35AC
call80:sigaction
call81:sub_5E6C
call82:sub_5444
call83:sub_633C
call84:sub_8130
call85:sub_4C70
call86:sub_825C
call87:sub_8B50
call88:sub_8ED4
call89:sub_8430
call90:interpreter_wrap_int64_t_bridge
call91:sub_9D60
call92:sub_166C4
call93:
memcpy
call94:_Z9__arm_a_2PcmS_Rii
call95:j_ffi_prep_cif_var
call96:ffi_prep_cif_var
call43:sub_8000 <--
call44:dlopen
call45:sub_60E0
call46:sub_6544
call47:sub_4B54 <--
call48:sub_6128
call49:_ZN9__arm_c_19__arm_c_0Ev
call50:sub_A3EC
call51:sub_99CC
call52:sub_9944
call53:sub_6484
call54:sub_6590
call55:prctl
call56:sub_6698
call57:sub_9FFC
call58:j_lseek_3
call59:j_lseek_2
call60:j_lseek_0
call61:sub_9A90
call62:sub_5F20
call63:sub_6044
call64:sub_3574
call65:uncompress
call66:sub_49F0 <--
call67:sub_5400
call68:sub_5478
call69:sub_5B08
call70:sub_5650
call71:sub_580C
call72:open
call73:
atoi
call74:sub_3C94 <--
call43:sub_8000 <--
call44:dlopen
call45:sub_60E0
call46:sub_6544
call47:sub_4B54 <--
call48:sub_6128
call49:_ZN9__arm_c_19__arm_c_0Ev
call50:sub_A3EC
call51:sub_99CC
call52:sub_9944
call53:sub_6484
call54:sub_6590
call55:prctl
call56:sub_6698
call57:sub_9FFC
call58:j_lseek_3
call59:j_lseek_2
call60:j_lseek_0
call61:sub_9A90
call62:sub_5F20
call63:sub_6044
call64:sub_3574
call65:uncompress
call66:sub_49F0 <--
call67:sub_5400
call68:sub_5478
call69:sub_5B08
call70:sub_5650
call71:sub_580C
call72:open
call73:
atoi
call74:sub_3C94 <--
function
hook_5f20_guess_rc4(){
var
module = Process.findModuleByName(
"libjiagu_64.so"
);
Interceptor.attach(module.base.add(0x5f20), {
onEnter:
function
(args) {
console.log(hexdump(args[0], {
offset: 0,
length: 0x10,
header:
true
,
ansi:
true
}));
console.log(args[1])
console.log(hexdump(args[2], {
offset: 0,
length: 256,
header:
true
,
ansi:
true
}));
},
onLeave:
function
(ret) {
}
});
}
function
hook_5f20_guess_rc4(){
var
module = Process.findModuleByName(
"libjiagu_64.so"
);
Interceptor.attach(module.base.add(0x5f20), {
onEnter:
function
(args) {
console.log(hexdump(args[0], {
offset: 0,
length: 0x10,
header:
true
,
ansi:
true
}));
console.log(args[1])
console.log(hexdump(args[2], {
offset: 0,
length: 256,
header:
true
,
ansi:
true
}));
},
onLeave:
function
(ret) {
}
});
}
key
=
b
"vUV4#\x91#SVt"
var
rc4_enc_text_addr,rc4_enc_size;
function
hook_rc4_enc(){
var
module = Process.findModuleByName(
"libjiagu_64.so"
);
Interceptor.attach(module.base.add(0x6044), {
onEnter:
function
(args) {
rc4_enc_text_addr = args[0];
rc4_enc_size = args[1];
console.log(hexdump(args[0], {
offset: 0,
length: 0x30,
header:
true
,
ansi:
true
}));
console.log(args[1])
},
onLeave:
function
(ret) {
console.log(hexdump(rc4_enc_text_addr, {
offset: 0,
length: 0x30,
header:
true
,
ansi:
true
}));
}
});
}
var
rc4_enc_text_addr,rc4_enc_size;
function
hook_rc4_enc(){
var
module = Process.findModuleByName(
"libjiagu_64.so"
);
Interceptor.attach(module.base.add(0x6044), {
onEnter:
function
(args) {
rc4_enc_text_addr = args[0];
rc4_enc_size = args[1];
console.log(hexdump(args[0], {
offset: 0,
length: 0x30,
header:
true
,
ansi:
true
}));
console.log(args[1])
},
onLeave:
function
(ret) {
console.log(hexdump(rc4_enc_text_addr, {
offset: 0,
length: 0x30,
header:
true
,
ansi:
true
}));
}
});
}
function
hook_uncompress_res(){
var
module = Process.findModuleByName(
"libjiagu_64.so"
);
Interceptor.attach(Module.findExportByName(
null
,
"uncompress"
), {
onEnter:
function
(args) {
console.log(
"hook uncompress"
)
console.log(hexdump(args[2], {
offset: 0,
length: 0x30,
header:
true
,
ansi:
true
}));
console.log(args[3])
dump_memory(args[2],args[3],`uncompress_${args[2]}_${args[3]}`)
},
onLeave:
function
(ret) {
}
});
}
function
hook_uncompress_res(){
var
module = Process.findModuleByName(
"libjiagu_64.so"
);
Interceptor.attach(Module.findExportByName(
null
,
"uncompress"
), {
onEnter:
function
(args) {
console.log(
"hook uncompress"
)
console.log(hexdump(args[2], {
offset: 0,
length: 0x30,
header:
true
,
ansi:
true
}));
console.log(args[3])
dump_memory(args[2],args[3],`uncompress_${args[2]}_${args[3]}`)
},
onLeave:
function
(ret) {
}
});
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2024-3-29 18:16
被oacia编辑
,原因: 增加 主DEX解密算法分析 部分