首页
社区
课程
招聘
安卓壳学习记录
发表于: 2025-3-5 14:05 5037

安卓壳学习记录

2025-3-5 14:05
5037

电脑为window11系统,用到工具有IDA,Androidstudio,JEB,jadx,frida等,这段时间学习了一下安卓壳的相关知识,将途中所学记录了下来,学习很多大佬的文章所以难免有雷同之处,请见谅。

第一代壳主要是对dex/apk文件整体加密,然后自定义类加载器动态加载dex/apk文件并执行。在动态加载dex/apk文件的时候有落地加载和不落地加载,落地加载就是通过DexClassLoader从磁盘加载dex/apk文件,不落地加载就是通过InMemoryDexClassLoader从内存中加载dex/apk文件。

app的启动流程

通过将原apk进行加密后保存在加壳apk的dex文件尾部,在加壳apk运行时将dex文件尾部加密的原apk文件解密后进行动态加载。
壳代码需要最早获取到加壳apk的执行时机,所以加壳apk的Application实现了attachContextApplication函数,此函数在handleBindApplication函数中通过调用makeApplicaton进行调用,是一个APP进程最早的执行入口。加壳apk需要进行如下操作

步骤:

一个源程序,即apk。

加壳工具。

脱壳程序,释放源程序并加载。

看到打包这块想起自己之前每次都懒得写脚本,每次都自己手动重打包。。。

这个脚本的话,是linux下的,我改写一下命令后,os.popen无法很完美的实现copy等一些命令,copy也很奇怪,推测是无法对和自身所在盘符不同的文件操作,很难受。将文件都复制到了脚本这里,改用了subprocess,签名的时候需要二次输入密码交互所以用run,其他getoutput即可。

具体的作用就是将源程序apk和脱壳程序构建成了下面这个

脱壳程序要做的就是在启动流程调用Application的attachBaseContext时点释放我们的源程序并将其替换为我们的application实例。

添加android:name=".ProxyApplication"还有替换活动为我们脱壳后实际会运行的活动。

先将apk文件的dex提出,然后根据之前加壳时在文件中留下的大小信息,将源apk提取出来。

然后将apk存储起来。

so需要手动提取一下到目标路径。

之后需要通过反射替换一下classloader。这里替换classloader的原因需要了解一下双亲委派模式。

这样子在之后加载我们的源程序的活动时,可以正常找到类。

安装时如果报这个错的话是因为zipaligned工具自身的问题,这里需要添加在application下添加**android:extractNativeLibs="true"**安装时才不会报错。

启动的是我们的源程序。

jadx里显示的是我们的壳。

一代壳的防护性并不高,frida-dump应该几乎是秒杀一代壳,不多赘述。

二代壳完全复现的话感觉时间会花费较多,所以我会以这个项目的代码作为学习。

9baK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2U0L8X3u0D9L8$3N6K6i4K6u0W2j5$3!0E0i4K6u0r3L8s2g2G2P5h3g2K6K9i4q4A6N6g2)9J5c8Y4m8Q4x3V1k6V1M7s2c8Q4x3X3g2Z5N6r3#2D9

该项目的代码有两个部分,proccessor和shell

proccessor是对apk进行字节提取然后重新打包处理成加壳apk的模块,shell模块最终生成的dex和so集成到加壳的apk中。

不过在开始前对dex的文件结构有些了解比较好,因为函数抽取,抽取的就是dex文件中的codeitem,具体是codeItem的insns,它保存了函数的实际字节码。

processor的工作流程:

shell模块工作流程:

Androidmainfest.xml在编译为apk时,会以axml格式存放,不是普通可读的形式,该项目采取的ManifestEditor进行解析。

对于Androidmanifest.xml,我们主要的操作是,备份原Application的类名和写入壳的代理application类名。也就是在代理Application中实现一些加载dex或者HOOK等操作。

提取xml的代码段:

以及还有相应的写入Application类的操作。

我们可以随便找一个dex放010里看一下。

然后我们具体提取的是CodeItem的insns,insns是函数实现的具体字节码。

提取所用工具为安卓提供的dx工具,同样存放在lib目录下。

该项目实现的函数是extractAllMethods,具体的实现先不谈了,结果就是读取所有的classdef(一个类的定义)的所有函数,并是否进行函数抽取。提取的数据怎么存储比较好呢,之前遇到的一道题是直接提取出来作为静态值最后填回去了。该项目的话是存储到了instruction结构体里,再看下这个结构体。

这个结构体是专门用来存储方法相关的东西的,如偏移,字节码。

这个部分是壳的主要逻辑。

Hook这个函数是为了使得我们加载dex时修改dex的属性,使其可写,才能将字节码回填。https://bbs.kanxue.com/thread-266527.htm

具体是<font style="color:rgb(89, 97, 114);">给__prot参数追加PROT_WRITE属性</font>

类加载的调用链

LoadMethod中有两个关键参数DexFile和ClassDataItemIterator

ClassDataItemIterator结构体的**code_off **是加载的函数的CodeItem相对于DexFile的偏移。所以LoadMethod是一个绝佳的HOOK点。

还有就是LoadMethod不同的安卓版本函数声明可能不同,所以需要做一个版本的不同处理,这个也确实在二代壳的ctf题中有看到。

填充insns

codeitem的传递是assets/OoooooOooo。

重新加载dex并替换dexElements,使ClassLoader从我们新加载的dex文件中加载类。这里还是之前那个双亲委派机制的原因。

这就是二代壳的大致过程,但是详细的具体实现没有过多去分析,后续有需求实现的话会再补充一下,

这里有一篇dpt-shell分析文章,比我分析的要详细很多https://tttang.com/archive/1728/

最老实的方法就是找到字节码自己填充回去再分析,这是我之前ctf一道题这样干的。。。。

大体思路是找到函数填充回去的时机,如HOOK loadmethod来dump出已经填充回去的dex。

这个就更没有通解了,之后遇到再说吧。

基于oacia大佬的文章分析复现的,有很多重合部分,也有一些我个人在分析中的体悟和疑问。

https://bbs.kanxue.com/thread-280609.htm

加固入口。

额,这些字符串应该是加密状态的,但是我这里的jeb自动解密了。

大致分析下可以看出来这里的做的是根据不同的手机架构加载不同的libjiagu.so,位于assert目录下。

这里还有一个Dctloader的init,但是不知道为什么JEB并没有反编译出来。

这里调用的init虽然是空的,但是Dctloader的静态代码块会在加载到JVM时运行。

也就是加载了libjgdtc.so,加载失败会在/data/data/com.oacia.apk_protect/lib/下加载,但是在/data/data/com.oacia.apk_protect/lib/下并没有该so。

所以先分析libjiagu.so。

Sofixer修复一下。

这里在dump之后就意外退出了,想必存在反调试。

通过hook dlopen函数查看中断前so被加载的顺序可以猜测应该存在于libjiagu_64中。

HOOK open函数

<font style="color:rgb(166, 44, 100);">/proc/self/maps</font>反调试,frida注入时会在maps留下<font style="color:rgb(51, 51, 51);">re.frida.server之类的特征。</font>

<font style="color:rgb(51, 51, 51);">尝试了多种常见的HOOK,如将相关字段替换,但是仍会被检测退出,所以先采取作者的做法,读取一个不存在的文件或者直接return -1。</font>

classes.dex没有魔术头,dex处于加密状态。

查看释放时的堆栈调用

可以看出来classes的堆栈调用和classes2几乎是一样的,所以处理classes的代码应该是一个循环。

从最近的堆栈调用查看,也就是0x19b780,是空的。

data段,可以猜到应该是将这段作为可执行内存然后执行了代码。

再次dump so文件,以open的路径中包含dex为HOOK时点。

重新用Sofixer修复so。

使用beyond compare进行一下比对。

填充的开始是elf的魔术头,program header table被加密了。

python脚本将其中的elf文件提取一下。

对dlopen交叉引用查看调用点。

作者真是神了,如果是我的话肯定不可能仅从这段循环和case就看出来是自实现linker。

导入相关定义,ida中依次点击View->Open subviews->Local Types,然后按下键盘上的Insert将下面的结构体添加到对话框中。

a1即是soinfo结构体指针,但是还是存在一些未知偏移,所以有理由怀疑这个结构体可能经过魔改。

从<font style="color:rgb(166, 44, 100);">sub_3C94</font>被调用的地方开始分析,也就是<font style="color:#AD1A2B;">sub_49F0</font>.

0x38是循环的步长,v6为结束的地址,而0x38刚好是程序头表的大小,所以v5应该是ELF64_Phdr*类型。

所以我们只要将a1参数输出就可以得到解密后的phdr表。

cyberchef处理一下dump的数据。

010中按insert切换为覆盖模式,然后ctrl+shift+v覆盖数据。

但是这里phdr在soinfo结构体里是偏移0x0的地方,但是sub_5E6C传入的参数一却是a1+232偏移。

重新对soinfo结构体进行编辑

现在的传参就很正确,这里也佐证了soinfo被魔改的猜测。

使用作者的ida插件来跟踪一下函数的调用stalker_trace_so

获得函数调用链:

从之前dlopen被调用的地方<font style="color:rgb(166, 44, 100);">sub_3C94</font>开始分析。<font style="color:rgb(166, 44, 100);">sub_4B54->sub_49F0->sub_3C94</font>

sub_4B54有两个调用点,看函数调用链sub_8000应该是调用它的函数。

sub_8000只有data段存放着函数指针,静态分析下没有调用,但是看到很有意思的东西。

如果这块存的都是函数指针的话,那上面附近的大概率也是,并且都被sub_96E0调用,恐怕有蹊跷。

跟进sub_6DBC

接着跟到sub_6ADC

有一个很扎眼的异或,这里的v8就是参数一+v3,v3的值和传入的a2参数有关,a2是个常数,可以试着去计算v3的值,不过这里直接大胆假设好了,这里是循环异或解密数据。

确实解密出来一个正常的函数名,但是就算全部解密出来也不知道如果跟进下一步,还是跟回作者的思路,去分析函数调用链好了。

到sub_5F20时,这里有很明显的RC4算法特征

但是从函数调用链上来说的话,上几个函数都没有调用该函数的代码,那它是怎么调用的?

将该函数在安置在该位置进行hook

调用地址在17710

是通过函数指针调用的。最早的调用函数是sub_C918,后面有call_SYSV的记录,这里先大概知道是使用的动态调用的方式。

我们再回过头来看rc4_init,HOOK一下参数。

密钥:vUV4#.#SVt

之后查看函数调用链的下一个函数sub_6044

很明显的RC4加密过程。

再HOOK一下sub_6044函数

是之前在sub_8000看到过的0xb8010,从该函数的参数使用上来说,这个值是处理的数据的大小。

再看一下v5[0]的值

和解密的数据是一致的,这段数据就是RC4加密过的。

接着看函数调用链,接下来是uncompress,解压缩函数。

函数定义:

解压缩的数据就是RC4解密后的数据

不包括前四个字节,根据加壳的一些做法可以猜测是大小。

有ELF的数据及解密方式,直接手动解密

可以找到elf标志位,但是前面有一堆D3数据。

将elf这两部分切割

之后再接着函数调用链往下看elf的解密,sub_5B08

又是0x38,program_header_table的大小,可以猜测又是通过解析elf结构来进行一些关键操作。

HOOK下进行异或的v5

和010中看到的不明数据吻合

这个也可以找到他解密完的位置去进行dump,但是我觉得分析一下解密流程也算学习的一部分了。

首先只是从表面来看的话,似乎设计到加密的部分只有异或,包括vdupq_n_s8和veorq_s8似乎也只是实现另类的异或。

先从第一个循环来看,v5异或的值,重点应该聚焦于异或的数据,也就是v18

v18是v10+v12,而v12在进入该循环时被赋值为0,也就是说v18就是v10,而v10——》result——》strtol,

说真的这个strtol的使用在这里真的很迷,搞不太懂这里的逻辑,所以我选择了用frida的stalker来直接跟踪一下返回值。

该函数的定义来说返回的应该是个整数,所以就是b40000704205f130,尝试几次没有变化,似乎是固定的值。

但是不管是从参数还是返回值来说,都相当奇怪,不排除可能自己对该函数进行了hook吧。

这里有一个既视感很强的操作

prctl的第二个参数正好跳过了前面赋值给其他变量的五个字节,同时四字节作为第三个参数,而参数1 result在前面被赋值给了v10,v10又赋值给v18,v18正好是要进行异或的数据。

所以这里的prtcl像是memcpy的操作。

改一下前面的frida-stlaker中输出x0的地址然后HOOK,不出所料,该函数操作后result指向的地址存储了010中看到的五字节之后的数据

那这样看来前面的strtol实际的函数可能是类似malloc的函数分配了空间。

所以这段代码实际就是以第一字节为异或值,2到5字节为大小,取之后的数据异或。同样的,下面也都是类似的操作。

为了验证下函数确实被替换了的猜想,我决定用frida-stalker对函数进行追踪。

代码处理的不是很好,因为函数被多次调用了,所以输出的数据很多,而且限制的范围没有起作用。

如无意外的话,这些应该是strtol的汇编内容,返回值和之前hook到的是相近的。

其中没有明显的字符串处理,数值转换等操作,喂给gpt反编译后简要分析大致执行一系列内存操作和计算,然后返回一个分配的空间或地址。

同理对prctl进行操作分析。

返回的只有一个return的操作,不是很能理解。

hexdump下目标地址的数据,很惊奇,居然有ELF的magic标志。

我对stalker的使用只是初尝,想必有一些错误,想要再去深究这里的prctl的话想必要花费不少时间,暂且将中心放回到elf解密上吧。

根据a1各个偏移的使用可以大致构建出结构体,这里直接使用作者的了。

同理根据函数调用链<font style="color:rgb(166, 44, 100);">sub_49F0->sub_5478(&v16, a1, v4)->sub_5B08(a1, a2, a3)</font>将之前函数的参数也重新定义。

将sub_49F0中的v16定义为deal_extra,v7定义为合适的结构体

而之后的v7分别被传入了sub_3C94和sub_4918中

而sub_3C94正是之前分析到的处理动态链接库的相关函数

而这里被处理的extra_part4也就对应着.dynamic段。

然后在sub_4918中,extra_part2和extra3被传入sub_4000中。

sub_4000的case用来处理重定位

hook该函数查看一下两次调用时case的值。

分别是403和402,也就是<font style="color:rgb(166, 44, 100);">.rela.plt</font><font style="color:rgb(0, 0, 0);">(402重定位)和</font><font style="color:rgb(166, 44, 100);">.rela.dyn</font><font style="color:rgb(0, 0, 0);">(403重定位)</font>

<font style="color:rgb(0, 0, 0);">我对安卓源码并不了解,但是作者在这里能够很清晰的理清思路,如果有空余时间的话阅读下大致代码应该也会在以后对逆向有帮助。</font>

<font style="color:rgb(0, 0, 0);">最后的extra_part1被传入sub_5E6C中。</font>

也就是前面分析了很久的解密<font style="color:rgb(166, 44, 100);">program header table</font>的函数。

所以四个数据组分别是:

这里直接用作者的脚本来分离文件了。

这个我的起始偏移是2e270,原作者偏移是1e270,可能我的哪步有差错吧,按自己实际的来即可。

在这之前可以了解一下自定义linker加固so:自实现linker加固so

我们需要修复的就是之前010中看到加密数据后紧挨的ELF,从文章中了解到这种做法一般会将原来<font style="color:rgb(166, 44, 100);">phdr</font><font style="color:rgb(0, 0, 0);">,</font><font style="color:rgb(166, 44, 100);">.rela.plt</font><font style="color:rgb(0, 0, 0);">,</font><font style="color:rgb(166, 44, 100);">.rela.dyn</font><font style="color:rgb(0, 0, 0);">,</font><font style="color:rgb(166, 44, 100);">.dynamic</font><font style="color:#000000;">段用无关字节覆盖,所以我们可以直接用010将数据覆盖回去。</font>

<font style="color:#000000;">这里不删除段的原因是因为删除段的话会影响偏移,可能会导致各种奇怪问题。</font>

<font style="color:#000000;">点击struct_program_header_table后直接ctrl+shift+v将phdr覆盖进去。</font>

跳转到.dynamic段的位置0x16E3C0

覆盖模式下ctrl+shift+v覆盖

然后就是找到.rela.plt和.rela.dyn

这里的17192021就是有关.rela.plt和.rela.dyn的偏移及大小。

相应的覆盖回去即可。

可以正常分析

这里将基地址设置为壳ELF的偏移0xe7000,因为后面还有用stalker_trace_so去HOOK。

在继续往下深入dex的解密之前,还是先把前面遗留的问题先解决吧,之前过frida反调试的方式实际不能说是优雅,而且也会中断,分析反调试,实际非常需要。

这里也是靠着巨人的肩膀可以知道可行的方法,在此之前作者曾尝试过:

<font style="color:#AD1A2B;">DBus</font>: 某些程序会通过 DBus 向外部进程发送特定的信号,如果调试器正在运行,DBus 的通信机制可能会被修改或拦截。

<font style="color:rgb(166, 44, 100);">TracerPid:</font>TracerPid/proc/self/status 文件中的一个字段,用于表示当前进程是否有调试器(如 GDB)附加。其值表示附加到当前进程的调试器的进程 ID(PID)。如果该值不为 0,表示当前进程被调试。

<font style="color:#AD1A2B;">readlink</font> :readlink 是一个用来读取符号链接的系统调用。某些程序通过 readlink 检查 /proc/self/exe 或其他符号链接文件的路径,来判断是否在调试环境中运行。例如,如果 /proc/self/exe 的路径指向调试器相关的文件(如 gdbld-linux.so),程序就能检测到调试器 。

<font style="color:#AD1A2B;">strstr</font> : 字符串搜索函数 , 调用 strstr 查找路径中是否包含 “gdb” 或 “frida"等字符串。

而获得突破性进展的是<font style="color:#AD1A2B;">pthread_create</font>,考虑<font style="color:#AD1A2B;">phread_create</font>的一个原因是如果把检测放在主线程的话,可能会造成阻塞卡顿现象,对于用户体验是不妥的,创建一个线程进行检测实际有考量。注入对该函数的hook代码

输出所有在libjiagu中调用的线程函数,而被调用地址却是一致的0x17710

跳转过去,等等,这里似乎来过?我的ida居然有记录,是之前分析调用RC4函数的调用者时来过的,

没想到兜兜转转又回到这里了,我们在前面已经分析过这里的调用方式了,是ffi_call进行的动态调用。libffi

GPT的解释:

可以hook一下看看有没有敏感字符串。

实际其中有很多字符串,可以想到这里到底调用了多少函数,筛选敏感字符串。

第一次hook的时候没有下面这一段,发现手机端开的是huluda,应该是把so的特征去除了,但是还是被检测中断了。

更详细的打出了地址

这个位置已经是主ELF的部分了

而0x31444ec70似乎已经跳到其他so去了。

而且还有一件很有意思的事就是在sub_217444中调用的函数有openat的系统调用。

并且周围还有很多其他的关于系统调用的片段

查看下系统调用93fK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6U0K9s2u0G2L8h3W2#2L8g2)9J5k6h3N6G2L8$3N6D9k6i4y4G2N6i4u0U0k6g2)9J5k6h3y4G2L8g2)9J5c8X3y4Z5M7X3!0E0K9i4g2E0L8%4y4Q4x3V1k6V1L8$3y4K6i4K6u0r3i4K6u0n7i4K6u0r3L8h3q4K6N6r3g2J5i4K6u0r3j5$3!0F1M7%4c8S2L8Y4c8K6i4K6u0r3M7%4W2K6j5$3q4D9L8s2y4Q4x3X3g2E0k6l9`.`.

56,63,129,167对应的分别是openat,read,kill,<font style="color:rgb(0, 0, 0);">prctl</font>

然后又看到一篇文章,打算学习一下HOOK这种系统调用。

找了一个github项目:92dK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6x3e0r3g2S2N6X3g2K6c8#2)9J5c8V1k6J5K9h3c8S2i4K6u0V1f1$3W2Y4j5h3y4@1K9h3!0F1i4K6u0V1f1$3g2U0j5$3!0E0M7q4)9K6c8Y4c8S2j5W2)9K6c8s2u0W2j5h3c8E0k6g2)9J5k6r3!0$3i4K6u0V1k6X3W2D9k6b7`.`.

注入后在adb shell中logcat | grep Openat筛选出日志。

还是能看到一些东西的,稍微修改下脚本我们再HOOK下read系统调用,哦,原来是这样,它通过svc调用read来读取了maps中的frida的特征数据,那么读取了之后一定就是比较数据了,在主ELF中有发现frida的字符串,但是实际上只是过掉反调试的话,只要将这些特征数据替换掉就好了。

这里也说明了为什么我之前简单的hook openat失败的原因,因为他直接使用svc来调用的,而不是通过libc.so封装的函数。

这里先用作者的方法过掉检测

将所有有关检测的字符串都替换为无意义的字符。

然后输出次数,可以看到,每隔几秒便会输出大约2000个字符串,有趣,应该是创建了一个线程周期性检测<font style="color:#AD1A2B;">/memfd:frida-agent-64.so</font>字符串。

所以打算接续前面hook pthread看能不能将关键线程取消创建,直到停止前有关的几个线程函数地址。

但是我将这些地址挨个跳过之后还是会中断,猜测是多个反调试的原因,所以只是取消该线程创建无法成功或者没有取消掉检测线程。

正好frida的反调试也没有系统学习过,趁这个机会正好一并解决。

我们先使用huluda尽量隐藏特征,然后慢慢来,我之前在hook某个函数时有看到过cmdline的字样,并且在阅读其他文章时也发现有该反调试,所以我先试着去搜索,然后发现可以直接在主ELF中找到。

读取了cmdline然后传入了sub_14495c,然后看下sub_14495c

open还有gets,感觉不太需要看了,猜一下大概也猜到了,了解了一下cmdline,他这里查看父进程是否包含zygote来检测是否是调试器,那这个点应该是检测gdb等用的。

我们再看看之前多次出现的status,这个反调试是通过检测其中的TracePid来实现的,我们搜索一下该字符串

查看了下没有找到很关键的代码,那看来真正关键的status字符串使用时应该是加密的状态,同时也进行了多个函数的hook,但是并没有相关字符,很明显大概调用时用的还是SVC,但是只有调用打出没有调用地址,但是为什么strstr这种函数也无法HOOK到呢?libc.so明明早就加载了。

但是我很确定这里strstr是绝对被使用了,而主ELF中这些函数都是使用跳转表来调用的,所以我本打算通过hook该地址来查看寄存器的值,但是提示没有足够空间。

选择查看strtol的调用,因为这个函数可能被用于status的反调试中,结果发现一个有趣的函数sub_14453C,这里有一些检测

很有意思,然后查看了这个函数被调用的位置

是个pthread_create,非常符合预期,但是我hook这个函数却发现他似乎没有被调用,那么有两种可能,一种是我们在进入这里之前就已经被其他反调试干掉,另一种就是这里是迷惑我们的压根就没有调用,但是我觉得第二种不太可能。HOOK open等函数都无法找到除了maps和反调试相关的字样的话,那可能是在那些反调试之前,我们已经被干掉了?我们有两个可以hook到的反调试,一个是phread,一个是maps,那么先把这两个拦截,再继续hook吧

我们从头开始,我们先hook read函数,如果他打算使用status或者maps的话,那么大概率会使用read来读取内容。

很明显读取了maps

然后我不太清楚为什么他会去读取cat的status。

无所谓,既然他会读取maps,那我们先过掉这个,那怎么做呢?用frida去hookread来替换其中有关的数据吗?太麻烦了,直接用huluda得了。

从这里往后的话我尝试很多,比如从函数打印链的最后向前追溯,又或者frida盲勾重要的系统函数来查看参数,但是这样子无异于大海捞针,最后还是选择了老老实实用IDA附加调试。

下面的是在慢慢淘宝,并无突出收获

我将这一片都识别为了代码,事实是有相当多可疑有趣的函数。例如这个:

这个壳中大量用到了这种跳转段

跳转过去后是一些无法反编译的数据,在动调的时候发现是系统函数的地址。

手机终端

启动IDAserver

调试状态启动app,要事先对app的xml文件进行修改添加调试属性。

电脑端

转发端口

此时IDA进行附加并记下进程的PID

jdb通知程序继续运行

根据我多次的调试,唔,准确来说是两三天的重复调试中,稍微找到些技巧。

首先是之前提到过的动态调用ffi,在调试中我发现不管是系统函数还是so中的函数都有走这里调用,大胆猜测的话,壳几乎所有的函数都是走的这里,此时的X24就是要调用的函数地址。

所以在这里下断点

之后在module list中找到libc.so,双击然后再找到一些关键的系统函数进行断点,例如open,strstr,strtol等。

也是动调的时候发现很多系统函数的调用很奇怪。

函数的参数对应不上,这里strtol跳转后的地方是calloc函数。而open函数跳过去后是memset。

这张图更为直观,似乎IDA分析的跳转段指向的系统函数与实际的错了一个位。

并且在调试的过程中发现他结束程序用的是kill系统调用,但是我对kill被调用的点下断点时F9无法断住并且退出。

所以没有办法直接在kill的调用处下断点来快速分析反调试。

这里要注意寄存器的情况

前面的B4似乎是壳程序特意加的,以此来干扰分析,我们只要取后面的有效地址来跳转分析即可。

F2进行编辑,然后F2保存,修改为0过掉。这里有个很奇怪的点就是我用frida HOOK输出strstr的第一个参数发现并没有TracerPid,所以只能通过hook fgets来修改TracerPid的值。

这个反调试很奇怪,因为是在strstr的参数中发现的,这里的fake-libs就是他查找的字符串,没有找到哪篇文章有提及和这个字符串有关的反调试,但是在我将其全部替换为0后再继续跟进就可以进入主ELF,而之前都会在那之前就中断。

然后程序对maps进行了很长一段的循环查找dev object等字符串,然后接着调试。

甚至调到了主ELF解压的这里了,之后后面又被kill了

调试到sub_142EA0函数之后开始有一些java类型的memcpy和memcmp,不太清楚在干什么,在解析什么吗

不过跳到主ELF后就没再遇到壳ELF那种VM式的调用方式了。

这里又被反调试了,根据分析的话大致猜测是通过raise来中断的,因为对kill的断点我没有断住。

所有raise被用到的地方是发送中断指令。

因为跟着动调需要花费不少时间,所以先分析到这里了。360免费壳的分析花费时间有点超过预期了,只能先搁置,之后有时间会再次进行更细致的分析的。

这里的话作者因为有未加固的apk所以对比了大小猜测是在classes.dex中,而根据我之前学习的一二代加壳的话,很多时候加密也喜欢放在加固后的dex的尾部,并附带上大小。

搜索前几个字节直接定位到尾部。

接下来再次用stalker_trace_so跟踪函数调用链,在主ELF中使用插件生成js脚本,然后将壳elf的函数地址及名称copy过来并合并,再对输出信息的代码进行微调即可。

这里我本来是打算加上hook_self_map的代码以便跑更多的函数,但是因为hook了open使得stalker可能的对maps的访问也失败了所以报错,于是先暂时仅trace_so(),这里思考了一下有没有什么办法可以让hook_self_map()更加成功地仅过掉壳的检测,但是没有找到具体的检测点到底在哪里,可以猜到的是应该检测的是如frida字符串等特征,这样的话,那我编译一份frida将这些特征去掉从源头上根除不就好了吗?我觉得相比去阅读代码找检查点,隐藏自己可能更为容易些,不过这些是后话了,现在还是跟着作者的思路走吧。

分析新的函数调用链发现三个zlib解压缩的函数

inflateInit2_有两个调用处。

看函数调用链的话应该是第二个调用的,转到该函数。

导入头文件

重定义s的类型为z_stream

接下来去HOOK inflate函数

接下来要HOOK一下inflate函数看看解压缩后的数据。

这里仿照作者的做法,获取外部函数的调用次数来判断目前是否已经加载主elf,这里作者是通过zlib_count变量来统计inflate解压函数的调用次数,壳会调用一次,主ELF会调用一次,所以第二次调用时就一定在主ELF了,这里我实在太懒了,有作者现成的脚本,所以我只阅读了下然后cv。

同样是放到dlopen的onLeave即可,这里要先把过反调试的函数注释掉,因为他之后还有很多次调用,如果被反调试的话刚好显示的第二次解压出的dex。

这个dex和壳dex是同一个文件,壳的dex结尾是有加密dex的,也就是说已经获取加密主dex的数据,那么解密就不远了。

再查看解压缩的函数的调用者,根据函数调用链推测是sub_1A0C88。

然后再顺着调用链往前追索,期间可以看到很多有趣的字样,

直到sub_1332B8,这个函数大的可怕,光是变量的定义就有五百多行。

看一下是哪里调用的这里,根据之前hook open时的堆栈调用是19b780

所以我们看下这个函数,同时这里如果查看壳elf的同这个位置的话,会发现这里的跳转表在主ELF中已经是解析后的状态了。

打开文件并写入,再往上追溯调用,是sub_1332B8调用的该函数,而sub_1332B8代码中获取过壳dex的数据。

这里很可能是创建classes.dex的位置,对这里进行HOOK。

同时这里因为apk很容易崩溃然后需要重启手机所以写了个bat脚本方便一下。

HOOK sub_19B760并同时打印函数调用

可以看的出来,classes.dex是第一个调用处,而classes2和3是第二个调用处。

同时在sub_128D44后程序会终止。

而sub_128D44传入的是主dex。

但其在trace_so的打印中又并没有,这里我对stalker了解并不深,只是仿照作者的方法,在调用到该函数时再调用一次trace_so(),好像不太需要考虑hook时机问题,直接放在hook dlopen那个函数就可以,但是这里我在原本的插件生成的trace脚本中不知道为什么没有反应,所以将trace_so函数移到了我的脚本中使用,结果倒是也和作者一样

但是壳的系统函数好像不太能相信吧,按照前面的错位来说的话,这里的最后调用的系统函数应该是close?不过其他普通函数肯定是没有什么问题的。而上一个feof则是 __cxa_finalize 。

__cxa_finalize 是一个用于在程序终止时调用 C++ 静态和全局对象析构函数的函数。它通常由编译器和 C++ 运行时库自动调用,以确保在程序结束时正确清理资源。

倒是也符合结束的情况,但是吧,这也很奇怪,因为程序开始的函数调用链和从sub_128D44这里开始打印的话,Pack的函数调用是一样的。所以其实整个分析过程还是有很多疑惑的点存在。

这里没有分析出来什么,还是返回之前创建classes.dex的地方,

这里如果创建了的话,那解密应该也不会太远,向下接着分析。

这里搭配上anti_frida_check()函数HOOK sub_18FEA8.

sub_18FEA8的参数v203和clasee23的参数v163是同一个,这个函数的参数分析不一定正确,于是将参数依次打出尝试,发现第二个参数即是dex,第三个参数猜测是大小。

很明显是脱完后的了。

onCreate变成了native定义,这些以后有时间再回来接着分析吧。

唉,其实这个壳的分析还是有点不甘心的,因为还有好多东西没搞明白,也还有好多东西还可以深挖,如果能看源码学习下就好了,有点异想天开了。虽然反调试动调那块写的比较少,但基本是我那两三天动调的精华,之后有时间还会再补反调试和VMP相关的知识吧,如果有人能将这个壳反调试的过程告诉我就好了。。。其实也找过相关的文章,但是大多是其他时间段的,反调试的机制好像不完全一样,比如那个fake-libs,没太去分析他在检测什么,很奇怪的一个字符串。后续希望还有高手发布该壳更详细的分析文章吧。途中的一些文件都打包放到网盘内了。
链接: a50K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3j5h3&6Q4x3X3g2T1j5h3W2V1N6g2)9J5k6h3y4G2L8g2)9J5c8Y4y4Q4x3V1j5I4j5V1y4c8f1f1&6@1b7@1W2H3P5r3&6s2j5W2W2b7N6h3S2K6x3W2f1I4f1g2)9K6c8Y4m8%4k6q4)9K6c8s2u0W2j5e0p5`. 提取码: rea1

package com.example.pack_test_one;
 
import androidx.appcompat.app.AppCompatActivity;
 
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
 
import com.example.pack_test_one.databinding.ActivityMainBinding;
 
public class MainActivity extends AppCompatActivity {
 
    // Used to load the 'pack_test_one' library on application startup.
    static {
        System.loadLibrary("pack_test_one");
    }
 
    private ActivityMainBinding binding;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        Log.i("test", "rea111111111:"+getApplicationContext());
        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(stringFromJNI());
    }
 
    /**
     * A native method that is implemented by the 'pack_test_one' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}
package com.example.pack_test_one;
 
import androidx.appcompat.app.AppCompatActivity;
 
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
 
import com.example.pack_test_one.databinding.ActivityMainBinding;
 
public class MainActivity extends AppCompatActivity {
 
    // Used to load the 'pack_test_one' library on application startup.
    static {
        System.loadLibrary("pack_test_one");
    }
 
    private ActivityMainBinding binding;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        Log.i("test", "rea111111111:"+getApplicationContext());
        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(stringFromJNI());
    }
 
    /**
     * A native method that is implemented by the 'pack_test_one' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}
import hashlib
import os
import shutil
import sys
import zlib
import zipfile
import subprocess
def process_apk_data(apk_data: bytes):
    """
    用于处理apk数据,比如加密,压缩等,都可以放在这里。
    :param apk_data:
    :return:
    """
    return apk_data
 
# 使用前需要修改的部分
keystore_path = 'D:/Android/Key.jks'
keystore_password = '######'
key0_password="########"
src_apk_file_path = 'D:/Android/Projection/pack_test_one/app/debug/app-debug.apk'
shell_apk_file_path = 'D:/Android/Projection/unshell/app/debug/app-debug.apk'
buildtools_path = 'D:/Android/SDK/build-tools/34.0.0'
 
# 承载apk的文件名
carrier_file_name= 'classes.dex'
# 中间文件夹
intermediate_dir= 'intermediates'
intermediate_apk_name='app-debug.apk'
intermediate_aligned_apk_name='app-debug-aligned.apk'
intermediate_apk_path="intermediates/app-debug.apk"
intermediate_carrier_path=os.path.join(intermediate_dir, carrier_file_name)
intermediate_aligned_apk_path=os.path.join(intermediate_dir,intermediate_aligned_apk_name)
if os.path.exists(intermediate_dir):
    shutil.rmtree(intermediate_dir)
os.mkdir(intermediate_dir)
 
# 解压apk
shell_apk_file=zipfile.ZipFile(shell_apk_file_path)
shell_apk_file.extract(carrier_file_name,intermediate_dir)
 
# 查找dex
if not os.path.exists(os.path.join(intermediate_dir, carrier_file_name)):
    raise FileNotFoundError(f'{carrier_file_name} not found')
 
src_dex_file_path= os.path.join(intermediate_dir, carrier_file_name)
 
#读取
src_apk_file=open(src_apk_file_path, 'rb')
src_dex_file=open(src_dex_file_path, 'rb')
 
src_apk_data=src_apk_file.read()
src_dex_data=src_dex_file.read()
 
# 处理apk数据
processed_apk_data=process_apk_data(src_apk_data)
processed_apk_size=len(processed_apk_data)
 
# 构建新dex数据
new_dex_data=src_dex_data+processed_apk_data+int.to_bytes(processed_apk_size,8,'little')
 
# 更新文件大小
file_size=len(processed_apk_data)+len(src_dex_data)+8
new_dex_data=new_dex_data[:32]+int.to_bytes(file_size,4,'little')+new_dex_data[36:]
 
# 更新sha1摘要
signature=hashlib.sha1().digest()
new_dex_data=new_dex_data[:12]+signature+new_dex_data[32:]
 
# 更新checksum
checksum=zlib.adler32(new_dex_data[12:])
new_dex_data=new_dex_data[:8]+int.to_bytes(checksum,4,'little')+new_dex_data[12:]
 
# 写入新dex
intermediate_carrier_file= open(intermediate_carrier_path, 'wb')
intermediate_carrier_file.write(new_dex_data)
intermediate_carrier_file.close()
src_apk_file.close()
src_dex_file.close()
 
os.environ.update({'PATH': os.environ.get('PATH') + f';{buildtools_path}'})
 
# 复制文件,Windows 下使用 copy 命令
shell_apk_file_path = r"D:/Android/Projection/unshell/app/debug/app-debug.apk"
intermediate_apk_path = r"intermediates/app-debug.apk"
 
# 检查源文件是否存在
if os.path.exists(shell_apk_file_path):
    try:
        # 使用 shutil.copy 进行文件复制
        shutil.copy(shell_apk_file_path, intermediate_apk_path)
        print(f"文件已成功复制到 {intermediate_apk_path}")
    except Exception as e:
        print(f"复制文件时出错: {e}")
else:
    print(f"源文件不存在: {shell_apk_file_path}")
   
# 切换到 intermediate_dir 目录
os.chdir(intermediate_dir)
 
# 使用 7z 压缩文件,替代 zip 命令
zip_command = f'"D://tool//7-Zip//7z.exe" a -tzip "{intermediate_apk_name}" "{carrier_file_name}"'
print( f'"D://tool//7-Zip//7z.exe" a -tzip "{intermediate_apk_name}" "{carrier_file_name}"')
r = subprocess.getoutput(zip_command)
print(r)
 
# 切换回上一级目录
os.chdir('../')
 
# 对齐 APK,使用 Android SDK 的 zipalign 工具
zipalign_command = f'zipalign 4 "{intermediate_apk_path}" "{intermediate_aligned_apk_path}"'
r = subprocess.getoutput(zipalign_command)
print(r)
 
# 使用 apksigner 工具进行签名
 
apksigner_command = f'apksigner sign -ks "{keystore_path}" --ks-pass pass:{keystore_password} "{intermediate_aligned_apk_path}"'
 
# 使用 subprocess.run 直接执行命令
result = subprocess.run(apksigner_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True,input=f"{key0_password}\n")
print("STDOUT:", result.stdout)
print("STDERR:", result.stderr)
 
# 复制对齐并签名后的 APK 到输出目录
copy_final_command = f'copy "{intermediate_aligned_apk_path}" "./app-out.apk"'
r = subprocess.getoutput(copy_final_command)
print(r)
 
print('Success')
import hashlib
import os
import shutil
import sys
import zlib
import zipfile
import subprocess
def process_apk_data(apk_data: bytes):
    """
    用于处理apk数据,比如加密,压缩等,都可以放在这里。
    :param apk_data:
    :return:
    """
    return apk_data
 
# 使用前需要修改的部分
keystore_path = 'D:/Android/Key.jks'
keystore_password = '######'
key0_password="########"
src_apk_file_path = 'D:/Android/Projection/pack_test_one/app/debug/app-debug.apk'
shell_apk_file_path = 'D:/Android/Projection/unshell/app/debug/app-debug.apk'
buildtools_path = 'D:/Android/SDK/build-tools/34.0.0'
 
# 承载apk的文件名
carrier_file_name= 'classes.dex'
# 中间文件夹
intermediate_dir= 'intermediates'
intermediate_apk_name='app-debug.apk'
intermediate_aligned_apk_name='app-debug-aligned.apk'
intermediate_apk_path="intermediates/app-debug.apk"
intermediate_carrier_path=os.path.join(intermediate_dir, carrier_file_name)
intermediate_aligned_apk_path=os.path.join(intermediate_dir,intermediate_aligned_apk_name)
if os.path.exists(intermediate_dir):
    shutil.rmtree(intermediate_dir)
os.mkdir(intermediate_dir)
 
# 解压apk
shell_apk_file=zipfile.ZipFile(shell_apk_file_path)
shell_apk_file.extract(carrier_file_name,intermediate_dir)
 
# 查找dex
if not os.path.exists(os.path.join(intermediate_dir, carrier_file_name)):
    raise FileNotFoundError(f'{carrier_file_name} not found')
 
src_dex_file_path= os.path.join(intermediate_dir, carrier_file_name)
 
#读取
src_apk_file=open(src_apk_file_path, 'rb')
src_dex_file=open(src_dex_file_path, 'rb')
 
src_apk_data=src_apk_file.read()
src_dex_data=src_dex_file.read()
 
# 处理apk数据
processed_apk_data=process_apk_data(src_apk_data)
processed_apk_size=len(processed_apk_data)
 
# 构建新dex数据
new_dex_data=src_dex_data+processed_apk_data+int.to_bytes(processed_apk_size,8,'little')
 
# 更新文件大小
file_size=len(processed_apk_data)+len(src_dex_data)+8
new_dex_data=new_dex_data[:32]+int.to_bytes(file_size,4,'little')+new_dex_data[36:]
 
# 更新sha1摘要
signature=hashlib.sha1().digest()
new_dex_data=new_dex_data[:12]+signature+new_dex_data[32:]
 
# 更新checksum
checksum=zlib.adler32(new_dex_data[12:])
new_dex_data=new_dex_data[:8]+int.to_bytes(checksum,4,'little')+new_dex_data[12:]
 
# 写入新dex
intermediate_carrier_file= open(intermediate_carrier_path, 'wb')
intermediate_carrier_file.write(new_dex_data)
intermediate_carrier_file.close()
src_apk_file.close()
src_dex_file.close()
 
os.environ.update({'PATH': os.environ.get('PATH') + f';{buildtools_path}'})
 
# 复制文件,Windows 下使用 copy 命令
shell_apk_file_path = r"D:/Android/Projection/unshell/app/debug/app-debug.apk"
intermediate_apk_path = r"intermediates/app-debug.apk"
 
# 检查源文件是否存在
if os.path.exists(shell_apk_file_path):
    try:
        # 使用 shutil.copy 进行文件复制
        shutil.copy(shell_apk_file_path, intermediate_apk_path)
        print(f"文件已成功复制到 {intermediate_apk_path}")
    except Exception as e:
        print(f"复制文件时出错: {e}")
else:
    print(f"源文件不存在: {shell_apk_file_path}")
   
# 切换到 intermediate_dir 目录
os.chdir(intermediate_dir)
 
# 使用 7z 压缩文件,替代 zip 命令
zip_command = f'"D://tool//7-Zip//7z.exe" a -tzip "{intermediate_apk_name}" "{carrier_file_name}"'
print( f'"D://tool//7-Zip//7z.exe" a -tzip "{intermediate_apk_name}" "{carrier_file_name}"')
r = subprocess.getoutput(zip_command)
print(r)
 
# 切换回上一级目录
os.chdir('../')
 
# 对齐 APK,使用 Android SDK 的 zipalign 工具
zipalign_command = f'zipalign 4 "{intermediate_apk_path}" "{intermediate_aligned_apk_path}"'
r = subprocess.getoutput(zipalign_command)
print(r)
 
# 使用 apksigner 工具进行签名
 
apksigner_command = f'apksigner sign -ks "{keystore_path}" --ks-pass pass:{keystore_password} "{intermediate_aligned_apk_path}"'
 
# 使用 subprocess.run 直接执行命令
result = subprocess.run(apksigner_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True,input=f"{key0_password}\n")
print("STDOUT:", result.stdout)
print("STDERR:", result.stderr)
 
# 复制对齐并签名后的 APK 到输出目录
copy_final_command = f'copy "{intermediate_aligned_apk_path}" "./app-out.apk"'
r = subprocess.getoutput(copy_final_command)
print(r)
 
print('Success')
package com.example.unshell;
 
import android.app.*// 导入 android.app 包中的所有类
import android.content.*// 导入 android.content 包中的所有类
import android.os.Build;
import android.util.*// 导入 android.util 包中的所有类
 
import java.io.*// 导入 java.io 包中的所有类
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.*// 导入 java.nio 包中的所有类
import java.util.*;
import java.util.zip.*// 导入 java.util.zip 包中的所有类
 
import dalvik.system.DexClassLoader;
 
public class ProxyApplication extends Application {
    private String srcApkPath;
    private String optDirPath;
    private String libDirPath;
 
    private ZipFile getApkZip() throws IOException {
        Log.i("demo", this.getApplicationInfo().sourceDir);
        ZipFile apkZipFile = new ZipFile(this.getApplicationInfo().sourceDir);
        return apkZipFile;
    }
 
    private byte[] readDexFileFromApk() throws IOException {
        /* 从本体apk中获取dex文件 */
        ZipFile apkZip = this.getApkZip();
        ZipEntry zipEntry = apkZip.getEntry("classes.dex");
        InputStream inputStream = apkZip.getInputStream(zipEntry);
        byte[] buffer = new byte[1024];
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int length;
        while ((length = inputStream.read(buffer)) > 0) {
            baos.write(buffer, 0, length);
        }
        return baos.toByteArray();
    }
 
    private byte[] reverseProcessApkData(byte[] data) { //解密函数
        for (int i = 0; i < data.length; i++) {
            data[i] = data[i];
        }
        return data;
    }
 
    private byte[] splitSrcApkFromDex(byte[] dexFileData) {
        /* 从dex文件中分离源apk文件 */
        int length = dexFileData.length;
        ByteBuffer bb = ByteBuffer.wrap(Arrays.copyOfRange(dexFileData, length - 8, length));
        bb.order(java.nio.ByteOrder.LITTLE_ENDIAN); // 设置为小端模式
        long processedSrcApkDataSize = bb.getLong(); // 读取这8个字节作为long类型的值
        byte[] processedSrcApkData = Arrays.copyOfRange(dexFileData, (int) (length - 8 - processedSrcApkDataSize), length - 8);
        byte[] srcApkData = reverseProcessApkData(processedSrcApkData);
        return srcApkData;
    }
 
    public static void replaceClassLoader1(Context context, DexClassLoader dexClassLoader) {
        ClassLoader pathClassLoader = ProxyApplication.class.getClassLoader();
        try {
            // 1.通过currentActivityThread方法获取ActivityThread实例
            Class ActivityThread = pathClassLoader.loadClass("android.app.ActivityThread");
            Method currentActivityThread = ActivityThread.getDeclaredMethod("currentActivityThread");
            Object activityThreadObj = currentActivityThread.invoke(null);
            // 2.拿到mPackagesObj
            Field mPackagesField = ActivityThread.getDeclaredField("mPackages");
            mPackagesField.setAccessible(true);
            ArrayMap mPackagesObj = (ArrayMap) mPackagesField.get(activityThreadObj);
            // 3.拿到LoadedApk
            String packageName = context.getPackageName();
            WeakReference wr = (WeakReference) mPackagesObj.get(packageName);
            Object LoadApkObj = wr.get();
            // 4.拿到mClassLoader
            Class LoadedApkClass = pathClassLoader.loadClass("android.app.LoadedApk");
            Field mClassLoaderField = LoadedApkClass.getDeclaredField("mClassLoader");
            mClassLoaderField.setAccessible(true);
            Object mClassLoader = mClassLoaderField.get(LoadApkObj);
            Log.i("mClassLoader", mClassLoader.toString());
            // 5.将系统组件ClassLoader给替换
            mClassLoaderField.set(LoadApkObj, dexClassLoader);
        } catch (Exception e) {
            Log.i("demo", "error:" + Log.getStackTraceString(e));
            e.printStackTrace();
        }
    }
 
    private void extractSoFiles(File srcApkFile, File libDir) throws IOException {
        // 获取当前设备的架构
        String abi = Build.CPU_ABI; // 或者使用 Build.SUPPORTED_ABIS 来获取更全面的信息
 
        // 提取 APK 中的 lib 文件夹并复制到 libDir 目录中
        ZipFile apkZip = new ZipFile(srcApkFile);
        ZipEntry libDirEntry = null;
        Enumeration<? extends ZipEntry> entries = apkZip.entries();
 
        while (entries.hasMoreElements()) {
            ZipEntry entry = entries.nextElement();
            // 找到 lib 文件夹并提取里面的 .so 文件
            if (entry.getName().startsWith("lib/") && entry.getName().endsWith(".so")) {
                // 检查 .so 文件是否属于当前设备架构
                if (entry.getName().contains("lib/" + abi + "/")) {
                    File destSoFile = new File(libDir, entry.getName().substring(entry.getName().lastIndexOf("/") + 1));
                    // 创建目标文件夹
                    if (!destSoFile.getParentFile().exists()) {
                        destSoFile.getParentFile().mkdirs();
                    }
                    // 复制 .so 文件
                    try (InputStream inputStream = apkZip.getInputStream(entry);
                         FileOutputStream fos = new FileOutputStream(destSoFile)) {
                        byte[] buffer = new byte[1024];
                        int length;
                        while ((length = inputStream.read(buffer)) > 0) {
                            fos.write(buffer, 0, length);
                        }
                    }
                }
            }
        }
    }
 
 
    @Override
    protected void attachBaseContext(Context base) {
        Log.i("demo", "attachBaseContext");
        super.attachBaseContext(base);
        try {
            byte[] dexFileData = this.readDexFileFromApk();
            byte[] srcApkData = this.splitSrcApkFromDex(dexFileData);
            // 创建储存apk的文件夹,写入src.apk
            File apkDir = base.getDir("apk_out", MODE_PRIVATE);
            srcApkPath = apkDir.getAbsolutePath() + "/src.apk";
            File srcApkFile = new File(srcApkPath);
            srcApkFile.setWritable(true);
            try (FileOutputStream fos = new FileOutputStream(srcApkFile)) {
                Log.i("demo", String.format("%d", srcApkData.length));
                fos.write(srcApkData);
            }
            srcApkFile.setReadOnly(); // 受安卓安全策略影响,dex必须为只读
            Log.i("demo", "Write src.apk into " + srcApkPath);
 
            // 新建加载器
            File optDir = base.getDir("opt_dex", MODE_PRIVATE);
            File libDir = base.getDir("lib_dex", MODE_PRIVATE);
            optDirPath = optDir.getAbsolutePath();
            libDirPath = libDir.getAbsolutePath();
 
            // 提取 .so 文件到 lib_dex 目录
            extractSoFiles(srcApkFile, libDir);
 
            ClassLoader pathClassLoader = ProxyApplication.class.getClassLoader();
            DexClassLoader dexClassLoader = new DexClassLoader(srcApkPath, optDirPath, libDirPath, pathClassLoader);
            Log.i("demo", "Successfully initiate DexClassLoader.");
            // 修正加载器
            replaceClassLoader1(base, dexClassLoader);
            Log.i("demo", "ClassLoader replaced.");
        } catch (Exception e) {
            Log.i("demo", "error:" + Log.getStackTraceString(e));
            e.printStackTrace();
        }
    }
}
package com.example.unshell;
 
import android.app.*// 导入 android.app 包中的所有类
import android.content.*// 导入 android.content 包中的所有类
import android.os.Build;
import android.util.*// 导入 android.util 包中的所有类
 
import java.io.*// 导入 java.io 包中的所有类
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.*// 导入 java.nio 包中的所有类
import java.util.*;
import java.util.zip.*// 导入 java.util.zip 包中的所有类
 
import dalvik.system.DexClassLoader;
 
public class ProxyApplication extends Application {
    private String srcApkPath;
    private String optDirPath;
    private String libDirPath;
 
    private ZipFile getApkZip() throws IOException {
        Log.i("demo", this.getApplicationInfo().sourceDir);
        ZipFile apkZipFile = new ZipFile(this.getApplicationInfo().sourceDir);
        return apkZipFile;
    }
 
    private byte[] readDexFileFromApk() throws IOException {
        /* 从本体apk中获取dex文件 */
        ZipFile apkZip = this.getApkZip();
        ZipEntry zipEntry = apkZip.getEntry("classes.dex");
        InputStream inputStream = apkZip.getInputStream(zipEntry);
        byte[] buffer = new byte[1024];
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int length;
        while ((length = inputStream.read(buffer)) > 0) {
            baos.write(buffer, 0, length);
        }
        return baos.toByteArray();
    }
 
    private byte[] reverseProcessApkData(byte[] data) { //解密函数
        for (int i = 0; i < data.length; i++) {
            data[i] = data[i];
        }
        return data;
    }
 
    private byte[] splitSrcApkFromDex(byte[] dexFileData) {
        /* 从dex文件中分离源apk文件 */
        int length = dexFileData.length;
        ByteBuffer bb = ByteBuffer.wrap(Arrays.copyOfRange(dexFileData, length - 8, length));
        bb.order(java.nio.ByteOrder.LITTLE_ENDIAN); // 设置为小端模式
        long processedSrcApkDataSize = bb.getLong(); // 读取这8个字节作为long类型的值
        byte[] processedSrcApkData = Arrays.copyOfRange(dexFileData, (int) (length - 8 - processedSrcApkDataSize), length - 8);
        byte[] srcApkData = reverseProcessApkData(processedSrcApkData);
        return srcApkData;
    }
 
    public static void replaceClassLoader1(Context context, DexClassLoader dexClassLoader) {
        ClassLoader pathClassLoader = ProxyApplication.class.getClassLoader();
        try {
            // 1.通过currentActivityThread方法获取ActivityThread实例
            Class ActivityThread = pathClassLoader.loadClass("android.app.ActivityThread");
            Method currentActivityThread = ActivityThread.getDeclaredMethod("currentActivityThread");
            Object activityThreadObj = currentActivityThread.invoke(null);
            // 2.拿到mPackagesObj
            Field mPackagesField = ActivityThread.getDeclaredField("mPackages");
            mPackagesField.setAccessible(true);
            ArrayMap mPackagesObj = (ArrayMap) mPackagesField.get(activityThreadObj);
            // 3.拿到LoadedApk
            String packageName = context.getPackageName();
            WeakReference wr = (WeakReference) mPackagesObj.get(packageName);
            Object LoadApkObj = wr.get();
            // 4.拿到mClassLoader
            Class LoadedApkClass = pathClassLoader.loadClass("android.app.LoadedApk");
            Field mClassLoaderField = LoadedApkClass.getDeclaredField("mClassLoader");
            mClassLoaderField.setAccessible(true);
            Object mClassLoader = mClassLoaderField.get(LoadApkObj);
            Log.i("mClassLoader", mClassLoader.toString());
            // 5.将系统组件ClassLoader给替换
            mClassLoaderField.set(LoadApkObj, dexClassLoader);
        } catch (Exception e) {
            Log.i("demo", "error:" + Log.getStackTraceString(e));
            e.printStackTrace();
        }
    }
 
    private void extractSoFiles(File srcApkFile, File libDir) throws IOException {
        // 获取当前设备的架构
        String abi = Build.CPU_ABI; // 或者使用 Build.SUPPORTED_ABIS 来获取更全面的信息
 
        // 提取 APK 中的 lib 文件夹并复制到 libDir 目录中
        ZipFile apkZip = new ZipFile(srcApkFile);
        ZipEntry libDirEntry = null;
        Enumeration<? extends ZipEntry> entries = apkZip.entries();
 
        while (entries.hasMoreElements()) {
            ZipEntry entry = entries.nextElement();
            // 找到 lib 文件夹并提取里面的 .so 文件
            if (entry.getName().startsWith("lib/") && entry.getName().endsWith(".so")) {
                // 检查 .so 文件是否属于当前设备架构
                if (entry.getName().contains("lib/" + abi + "/")) {
                    File destSoFile = new File(libDir, entry.getName().substring(entry.getName().lastIndexOf("/") + 1));
                    // 创建目标文件夹
                    if (!destSoFile.getParentFile().exists()) {
                        destSoFile.getParentFile().mkdirs();
                    }
                    // 复制 .so 文件
                    try (InputStream inputStream = apkZip.getInputStream(entry);
                         FileOutputStream fos = new FileOutputStream(destSoFile)) {
                        byte[] buffer = new byte[1024];
                        int length;
                        while ((length = inputStream.read(buffer)) > 0) {
                            fos.write(buffer, 0, length);
                        }
                    }
                }
            }
        }
    }
 
 
    @Override
    protected void attachBaseContext(Context base) {
        Log.i("demo", "attachBaseContext");
        super.attachBaseContext(base);
        try {
            byte[] dexFileData = this.readDexFileFromApk();
            byte[] srcApkData = this.splitSrcApkFromDex(dexFileData);
            // 创建储存apk的文件夹,写入src.apk
            File apkDir = base.getDir("apk_out", MODE_PRIVATE);
            srcApkPath = apkDir.getAbsolutePath() + "/src.apk";
            File srcApkFile = new File(srcApkPath);
            srcApkFile.setWritable(true);
            try (FileOutputStream fos = new FileOutputStream(srcApkFile)) {
                Log.i("demo", String.format("%d", srcApkData.length));
                fos.write(srcApkData);
            }
            srcApkFile.setReadOnly(); // 受安卓安全策略影响,dex必须为只读
            Log.i("demo", "Write src.apk into " + srcApkPath);
 
            // 新建加载器
            File optDir = base.getDir("opt_dex", MODE_PRIVATE);
            File libDir = base.getDir("lib_dex", MODE_PRIVATE);
            optDirPath = optDir.getAbsolutePath();
            libDirPath = libDir.getAbsolutePath();
 
            // 提取 .so 文件到 lib_dex 目录
            extractSoFiles(srcApkFile, libDir);
 
            ClassLoader pathClassLoader = ProxyApplication.class.getClassLoader();
            DexClassLoader dexClassLoader = new DexClassLoader(srcApkPath, optDirPath, libDirPath, pathClassLoader);
            Log.i("demo", "Successfully initiate DexClassLoader.");
            // 修正加载器
            replaceClassLoader1(base, dexClassLoader);
            Log.i("demo", "ClassLoader replaced.");
        } catch (Exception e) {
            Log.i("demo", "error:" + Log.getStackTraceString(e));
            e.printStackTrace();
        }
    }
}
public static void writeApplicationName(String inManifestFile, String outManifestFile, String newApplicationName){
    ModificationProperty property = new ModificationProperty();
    property.addApplicationAttribute(new AttributeItem(NodeValue.Application.NAME,newApplicationName));
 
    FileProcesser.processManifestFile(inManifestFile, outManifestFile, property);
 
}
public static void writeApplicationName(String inManifestFile, String outManifestFile, String newApplicationName){
    ModificationProperty property = new ModificationProperty();
    property.addApplicationAttribute(new AttributeItem(NodeValue.Application.NAME,newApplicationName));
 
    FileProcesser.processManifestFile(inManifestFile, outManifestFile, property);
 
}
public class Instruction {
 
    public int getOffsetOfDex() {
        return offsetOfDex;
    }
 
    public void setOffsetOfDex(int offsetOfDex) {
        this.offsetOfDex = offsetOfDex;
    }
 
    public int getMethodIndex() {
        return methodIndex;
    }
 
    public void setMethodIndex(int methodIndex) {
        this.methodIndex = methodIndex;
    }
 
    public int getInstructionDataSize() {
        return instructionDataSize;
    }
 
    public void setInstructionDataSize(int instructionDataSize) {
        this.instructionDataSize = instructionDataSize;
    }
 
    public byte[] getInstructionsData() {
        return instructionsData;
    }
 
    public void setInstructionsData(byte[] instructionsData) {
        this.instructionsData = instructionsData;
    }
 
    @Override
    public String toString() {
        return "Instruction{" +
        "offsetOfDex=" + offsetOfDex +
        ", methodIndex=" + methodIndex +
        ", instructionDataSize=" + instructionDataSize +
        ", instructionsData=" + Arrays.toString(instructionsData) +
        '}';
    }
 
    //Offset in dex
    private int offsetOfDex;
    //Corresponding method_idx in dex
    private int methodIndex;
    //instructionsData size
    private int instructionDataSize;
    //insns data
    private byte[] instructionsData;
}
public class Instruction {
 
    public int getOffsetOfDex() {
        return offsetOfDex;
    }
 
    public void setOffsetOfDex(int offsetOfDex) {
        this.offsetOfDex = offsetOfDex;
    }
 
    public int getMethodIndex() {
        return methodIndex;
    }
 
    public void setMethodIndex(int methodIndex) {
        this.methodIndex = methodIndex;
    }
 
    public int getInstructionDataSize() {
        return instructionDataSize;
    }
 
    public void setInstructionDataSize(int instructionDataSize) {
        this.instructionDataSize = instructionDataSize;
    }
 
    public byte[] getInstructionsData() {
        return instructionsData;
    }
 
    public void setInstructionsData(byte[] instructionsData) {
        this.instructionsData = instructionsData;
    }
 
    @Override
    public String toString() {
        return "Instruction{" +
        "offsetOfDex=" + offsetOfDex +
        ", methodIndex=" + methodIndex +
        ", instructionDataSize=" + instructionDataSize +
        ", instructionsData=" + Arrays.toString(instructionsData) +
        '}';
    }
 
    //Offset in dex
    private int offsetOfDex;
    //Corresponding method_idx in dex
    private int methodIndex;
    //instructionsData size
    private int instructionDataSize;
    //insns data
    private byte[] instructionsData;
}
void* fake_mmap(void* __addr, size_t __size, int __prot, int __flags, int __fd, off_t __offset){
    BYTEHOOK_STACK_SCOPE();
    int hasRead = (__prot & PROT_READ) == PROT_READ;
    int hasWrite = (__prot & PROT_WRITE) == PROT_WRITE;
    int prot = __prot;
 
    if(hasRead && !hasWrite) {
        prot = prot | PROT_WRITE;
        DLOGD("fake_mmap call fd = %p,size = %d, prot = %d,flag = %d",__fd,__size, prot,__flags);
    }
 
    void *addr = BYTEHOOK_CALL_PREV(fake_mmap,__addr,  __size, prot,  __flags,  __fd,  __offset);
    return addr;
}
void* fake_mmap(void* __addr, size_t __size, int __prot, int __flags, int __fd, off_t __offset){
    BYTEHOOK_STACK_SCOPE();
    int hasRead = (__prot & PROT_READ) == PROT_READ;
    int hasWrite = (__prot & PROT_WRITE) == PROT_WRITE;
    int prot = __prot;
 
    if(hasRead && !hasWrite) {
        prot = prot | PROT_WRITE;
        DLOGD("fake_mmap call fd = %p,size = %d, prot = %d,flag = %d",__fd,__size, prot,__flags);
    }
 
    void *addr = BYTEHOOK_CALL_PREV(fake_mmap,__addr,  __size, prot,  __flags,  __fd,  __offset);
    return addr;
}
ClassLoader.java::loadClass -> DexPathList.java::findClass -> DexFile.java::defineClass -> class_linker.cc::LoadClass -> class_linker.cc::LoadClassMembers -> class_linker.cc::LoadMethod
ClassLoader.java::loadClass -> DexPathList.java::findClass -> DexFile.java::defineClass -> class_linker.cc::LoadClass -> class_linker.cc::LoadClassMembers -> class_linker.cc::LoadMethod
void ClassLinker::LoadMethod(const DexFile& dex_file,
                             const ClassDataItemIterator& it,
                             Handle<mirror::Class> klass,
                             ArtMethod* dst);
void ClassLinker::LoadMethod(const DexFile& dex_file,
                             const ClassDataItemIterator& it,
                             Handle<mirror::Class> klass,
                             ArtMethod* dst);
void LoadMethod(void *thiz, void *self, const void *dex_file, const void *it, const void *method,
                void *klass, void *dst) {
 
    if (g_originLoadMethod25 != nullptr
        || g_originLoadMethod28 != nullptr
        || g_originLoadMethod29 != nullptr) {
        uint32_t location_offset = getDexFileLocationOffset();
        uint32_t begin_offset = getDataItemCodeItemOffset();
        callOriginLoadMethod(thiz, self, dex_file, it, method, klass, dst);
 
        ClassDataItemReader *classDataItemReader = getClassDataItemReader(it,method);
 
 
        uint8_t **begin_ptr = (uint8_t **) ((uint8_t *) dex_file + begin_offset);
        uint8_t *begin = *begin_ptr;
        // vtable(4|8) + prev_fields_size
        std::string *location = (reinterpret_cast<std::string *>((uint8_t *) dex_file +
                                                                 location_offset));
        if (location->find("base.apk") != std::string::npos) {
 
            //code_item_offset == 0说明是native方法或者没有代码
            if (classDataItemReader->GetMethodCodeItemOffset() == 0) {
                DLOGW("native method? = %s code_item_offset = 0x%x",
                      classDataItemReader->MemberIsNative() ? "true" : "false",
                      classDataItemReader->GetMethodCodeItemOffset());
                return;
            }
 
            uint16_t firstDvmCode = *((uint16_t*)(begin + classDataItemReader->GetMethodCodeItemOffset() + 16));
            if(firstDvmCode != 0x0012 && firstDvmCode != 0x0016 && firstDvmCode != 0x000e){
                NLOG("this method has code no need to patch");
                return;
            }
 
            uint32_t dexSize = *((uint32_t*)(begin + 0x20));
 
            int dexIndex = dexNumber(location);
            auto dexIt = dexMap.find(dexIndex - 1);
            if (dexIt != dexMap.end()) {
 
                auto dexMemIt = dexMemMap.find(dexIndex);
                if(dexMemIt == dexMemMap.end()){
                    changeDexProtect(begin,location->c_str(),dexSize,dexIndex);
                }
 
 
                auto codeItemMap = dexIt->second;
                int methodIdx = classDataItemReader->GetMemberIndex();
                auto codeItemIt = codeItemMap->find(methodIdx);
 
                if (codeItemIt != codeItemMap->end()) {
                    CodeItem* codeItem = codeItemIt->second;
                    uint8_t  *realCodeItemPtr = (uint8_t*)(begin +
                                                classDataItemReader->GetMethodCodeItemOffset() +
                                                16);
 
                    memcpy(realCodeItemPtr,codeItem->getInsns(),codeItem->getInsnsSize());
                }
            }
        }
    }
}
void LoadMethod(void *thiz, void *self, const void *dex_file, const void *it, const void *method,
                void *klass, void *dst) {
 
    if (g_originLoadMethod25 != nullptr
        || g_originLoadMethod28 != nullptr
        || g_originLoadMethod29 != nullptr) {
        uint32_t location_offset = getDexFileLocationOffset();
        uint32_t begin_offset = getDataItemCodeItemOffset();
        callOriginLoadMethod(thiz, self, dex_file, it, method, klass, dst);
 
        ClassDataItemReader *classDataItemReader = getClassDataItemReader(it,method);
 
 
        uint8_t **begin_ptr = (uint8_t **) ((uint8_t *) dex_file + begin_offset);
        uint8_t *begin = *begin_ptr;
        // vtable(4|8) + prev_fields_size
        std::string *location = (reinterpret_cast<std::string *>((uint8_t *) dex_file +
                                                                 location_offset));
        if (location->find("base.apk") != std::string::npos) {
 
            //code_item_offset == 0说明是native方法或者没有代码
            if (classDataItemReader->GetMethodCodeItemOffset() == 0) {
                DLOGW("native method? = %s code_item_offset = 0x%x",
                      classDataItemReader->MemberIsNative() ? "true" : "false",
                      classDataItemReader->GetMethodCodeItemOffset());
                return;
            }
 
            uint16_t firstDvmCode = *((uint16_t*)(begin + classDataItemReader->GetMethodCodeItemOffset() + 16));
            if(firstDvmCode != 0x0012 && firstDvmCode != 0x0016 && firstDvmCode != 0x000e){
                NLOG("this method has code no need to patch");
                return;
            }
 
            uint32_t dexSize = *((uint32_t*)(begin + 0x20));
 
            int dexIndex = dexNumber(location);
            auto dexIt = dexMap.find(dexIndex - 1);
            if (dexIt != dexMap.end()) {
 
                auto dexMemIt = dexMemMap.find(dexIndex);
                if(dexMemIt == dexMemMap.end()){
                    changeDexProtect(begin,location->c_str(),dexSize,dexIndex);
                }
 
 
                auto codeItemMap = dexIt->second;
                int methodIdx = classDataItemReader->GetMemberIndex();
                auto codeItemIt = codeItemMap->find(methodIdx);
 
                if (codeItemIt != codeItemMap->end()) {
                    CodeItem* codeItem = codeItemIt->second;
                    uint8_t  *realCodeItemPtr = (uint8_t*)(begin +
                                                classDataItemReader->GetMethodCodeItemOffset() +
                                                16);
 
                    memcpy(realCodeItemPtr,codeItem->getInsns(),codeItem->getInsnsSize());
                }
            }
        }
    }
}
function Hook_Invoke() {
    var InvokeFunc = Module.findExportByName("libart.so", "_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc")
    Interceptor.attach(InvokeFunc, {
        onEnter: function (args) {
            console.log(args[1])
            // args[0].add(8).readInt().toString(16) == 0
            console.log("Method Idx -> " + args[0].add(8).readInt().toString(16) + " " + args[0].add(12).readInt().toString(16))
            dump_memory(dex_begin, dex_size)
            //dump_memory(dex_begin, dex_size)
        },
        onLeave: function (retval) {}
    })
}
var dex_begin = 0
var dex_size = 0
  
function dump_memory(base, size) {
    Java.perform(function () {
        var currentApplication = Java.use("android.app.ActivityThread").currentApplication();
        var dir = "/sdcard/Download/";
        var file_path = dir + "/mydumpp";
        var file_handle = new File(file_path, "w+");
        if (file_handle && file_handle != null) {
            Memory.protect(ptr(base), size, 'rwx');
            var libso_buffer = ptr(base).readByteArray(size);
            file_handle.write(libso_buffer);
            file_handle.flush();
            file_handle.close();
            console.log("[dump]:", file_path);
        }
    });
}
  
function getDexFile() {
  
    Hook_Invoke()
    Java.perform(function () {
  
        var ActivityThread = Java.use("android.app.ActivityThread")
        ActivityThread["performLaunchActivity"].implementation = function (args) {
            var env = Java.vm.getEnv()
            var classloader = this.mInitialApplication.value.getClassLoader()
            var BaseDexClassLoader = Java.use("dalvik.system.BaseDexClassLoader")
            var elementsClass = Java.use("dalvik.system.DexPathList$Element")
            classloader = Java.cast(classloader, BaseDexClassLoader)
            var pathList = classloader.pathList.value
            var elements = pathList.dexElements.value
            console.log(elements)
            //console.log(elements.value)
            for (var i in elements) {
                var element;
                try {
                    element = Java.cast(elements[i], elementsClass);
                } catch (e) {
                    console.log(e)
                }
                //之后需要获取DexFile
                var dexFile = element.dexFile.value
                //getMethod(dexFile, classloader)
                var mCookie = dexFile.mCookie.value
                //$h获取句柄
                var length = env.getArrayLength(mCookie.$h, 0)
                //console.log(length)
                var Array = env.getLongArrayElements(mCookie.$h, 0)
                //第一个不是DexFile
                for (var i = 1; i < length; ++i) {
                    var DexFilePtr = Memory.readPointer(ptr(Array).add(i * 8))
                    var DexFileBegin = ptr(DexFilePtr).add(Process.pointerSize).readPointer()
                    var DexFileSize = ptr(DexFilePtr).add(Process.pointerSize * 2).readU32()
                    console.log(hexdump(DexFileBegin))
                    console.log("Size => " + DexFileSize.toString(16))
                    dex_begin = DexFileBegin
                    dex_size = DexFileSize
                }
            }
            return this.performLaunchActivity(arguments[0], arguments[1])
        }
    })
}
function main() {
   getDexFile()
  
}
setImmediate(main)
function Hook_Invoke() {
    var InvokeFunc = Module.findExportByName("libart.so", "_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc")
    Interceptor.attach(InvokeFunc, {
        onEnter: function (args) {
            console.log(args[1])
            // args[0].add(8).readInt().toString(16) == 0
            console.log("Method Idx -> " + args[0].add(8).readInt().toString(16) + " " + args[0].add(12).readInt().toString(16))
            dump_memory(dex_begin, dex_size)
            //dump_memory(dex_begin, dex_size)
        },
        onLeave: function (retval) {}
    })
}
var dex_begin = 0
var dex_size = 0
  
function dump_memory(base, size) {
    Java.perform(function () {
        var currentApplication = Java.use("android.app.ActivityThread").currentApplication();
        var dir = "/sdcard/Download/";
        var file_path = dir + "/mydumpp";
        var file_handle = new File(file_path, "w+");
        if (file_handle && file_handle != null) {
            Memory.protect(ptr(base), size, 'rwx');
            var libso_buffer = ptr(base).readByteArray(size);
            file_handle.write(libso_buffer);
            file_handle.flush();
            file_handle.close();
            console.log("[dump]:", file_path);
        }
    });
}
  
function getDexFile() {
  
    Hook_Invoke()
    Java.perform(function () {
  
        var ActivityThread = Java.use("android.app.ActivityThread")
        ActivityThread["performLaunchActivity"].implementation = function (args) {
            var env = Java.vm.getEnv()
            var classloader = this.mInitialApplication.value.getClassLoader()
            var BaseDexClassLoader = Java.use("dalvik.system.BaseDexClassLoader")
            var elementsClass = Java.use("dalvik.system.DexPathList$Element")
            classloader = Java.cast(classloader, BaseDexClassLoader)
            var pathList = classloader.pathList.value
            var elements = pathList.dexElements.value
            console.log(elements)
            //console.log(elements.value)
            for (var i in elements) {
                var element;
                try {
                    element = Java.cast(elements[i], elementsClass);
                } catch (e) {
                    console.log(e)
                }
                //之后需要获取DexFile
                var dexFile = element.dexFile.value
                //getMethod(dexFile, classloader)
                var mCookie = dexFile.mCookie.value
                //$h获取句柄
                var length = env.getArrayLength(mCookie.$h, 0)
                //console.log(length)
                var Array = env.getLongArrayElements(mCookie.$h, 0)
                //第一个不是DexFile
                for (var i = 1; i < length; ++i) {
                    var DexFilePtr = Memory.readPointer(ptr(Array).add(i * 8))
                    var DexFileBegin = ptr(DexFilePtr).add(Process.pointerSize).readPointer()
                    var DexFileSize = ptr(DexFilePtr).add(Process.pointerSize * 2).readU32()
                    console.log(hexdump(DexFileBegin))
                    console.log("Size => " + DexFileSize.toString(16))
                    dex_begin = DexFileBegin
                    dex_size = DexFileSize
                }
            }
            return this.performLaunchActivity(arguments[0], arguments[1])
        }
    })
}
function main() {
   getDexFile()
  
}
setImmediate(main)
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_0x7569f17000_0x274000.so -o fix.so -m 0x7569f17000 -d
SoFixer-Windows-64.exe -s libjiagu_64.so_0x7569f17000_0x274000.so -o fix.so -m 0x7569f17000 -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)
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");
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");
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);//,Process.getCurrentThreadId()
        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();
                    //console.log(path);
                    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);//,Process.getCurrentThreadId()
        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();
                    //console.log(path);
                    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 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();
                     
                }
            }
        }
    );
}
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);//,Process.getCurrentThreadId()
        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);
    }
}
  
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) {
                    hook_proc_self_maps();
                     
                }
            }
        }
    );
}
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);//,Process.getCurrentThreadId()
        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);
    }
}
  
setImmediate(my_hook_dlopen("libjiagu_64.so"));
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::])
//IMPORTANT
//ELF64启用该宏
#define __LP64__  1
//ELF32启用该宏
//#define __work_around_b_24465209__  1
  
/*
//https://android.googlesource.com/platform/bionic/+/master/linker/Android.bp
架构为32位 定义__work_around_b_24465209__宏
arch: {
        arm: {cflags: ["-D__work_around_b_24465209__"],},
        x86: {cflags: ["-D__work_around_b_24465209__"],},
    }
*/
  
//android-platform\bionic\libc\include\link.h
#if defined(__LP64__)
#define ElfW(type) Elf64_ ## type
#else
#define ElfW(type) Elf32_ ## type
#endif
  
//android-platform\bionic\linker\linker_common_types.h
// Android uses RELA for LP64.
#if defined(__LP64__)
#define USE_RELA 1
#endif
  
//android-platform\bionic\libc\kernel\uapi\asm-generic\int-ll64.h
//__signed__-->signed
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;
  
//A12-src\msm-google\include\uapi\linux\elf.h
/* 32-bit ELF base types. */
typedef __u32   Elf32_Addr;
typedef __u16   Elf32_Half;
typedef __u32   Elf32_Off;
typedef __s32   Elf32_Sword;
typedef __u32   Elf32_Word;
  
/* 64-bit ELF base types. */
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;       /* entry tag value */
  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;  /* Location at which to apply the action */
  Elf64_Xword r_info;   /* index and type of relocation */
} 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;  /* Location at which to apply the action */
  Elf64_Xword r_info;   /* index and type of relocation */
  Elf64_Sxword r_addend;    /* Constant addend used to compute value */
} 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;       /* Symbol name, index in string tbl */
  unsigned char st_info;    /* Type and binding attributes */
  unsigned char st_other;   /* No defined meaning, 0 */
  Elf64_Half st_shndx;      /* Associated section index */
  Elf64_Addr st_value;      /* Value of the symbol */
  Elf64_Xword st_size;      /* Associated symbol 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;  /* Entry point */
  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]; /* ELF "magic number" */
  Elf64_Half e_type;
  Elf64_Half e_machine;
  Elf64_Word e_version;
  Elf64_Addr e_entry;       /* Entry point virtual address */
  Elf64_Off e_phoff;        /* Program header table file offset */
  Elf64_Off e_shoff;        /* Section header table file offset */
  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;
  
/* These constants define the permissions on sections in the program
   header, p_flags. */
#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;       /* Segment file offset */
  Elf64_Addr p_vaddr;       /* Segment virtual address */
  Elf64_Addr p_paddr;       /* Segment physical address */
  Elf64_Xword p_filesz;     /* Segment size in file */
  Elf64_Xword p_memsz;      /* Segment size in memory */
  Elf64_Xword p_align;      /* Segment alignment, file & memory */
} 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;       /* Section name, index in string tbl */
  Elf64_Word sh_type;       /* Type of section */
  Elf64_Xword sh_flags;     /* Miscellaneous section attributes */
  Elf64_Addr sh_addr;       /* Section virtual addr at execution */
  Elf64_Off sh_offset;      /* Section file offset */
  Elf64_Xword sh_size;      /* Size of section in bytes */
  Elf64_Word sh_link;       /* Index of another section */
  Elf64_Word sh_info;       /* Additional section information */
  Elf64_Xword sh_addralign; /* Section alignment */
  Elf64_Xword sh_entsize;   /* Entry size if section holds table */
} Elf64_Shdr;
  
  
//android-platform\bionic\linker\linker_soinfo.h
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; // DO NOT USE, maintained for compatibility.
#endif
  ElfW(Addr) base;
  size_t size;
  
#if defined(__work_around_b_24465209__)
  uint32_t unused1;  // DO NOT USE, maintained for compatibility.
#endif
  
  ElfW(Dyn)* dynamic;
  
#if defined(__work_around_b_24465209__)
  uint32_t unused2; // DO NOT USE, maintained for compatibility
  uint32_t unused3; // DO NOT USE, maintained for compatibility
#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; // DO NOT USE, maintained for compatibility
#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_;
  
/*
#if defined(__arm__)
  // ARM EABI section used for stack unwinding.
  uint32_t* ARM_exidx;
  size_t ARM_exidx_count;
#endif
  size_t ref_count_;
//怎么找不到link_map这个类型的声明...
  link_map link_map_head;
  
  bool constructors_called;
  
  // When you read a virtual address from the ELF file, add this
  // value to get the corresponding address in the process' address space.
  ElfW(Addr) load_bias;
  
#if !defined(__LP64__)
  bool has_text_relocations;
#endif
  bool has_DT_SYMBOLIC;
*/
};
//IMPORTANT
//ELF64启用该宏
#define __LP64__  1
//ELF32启用该宏
//#define __work_around_b_24465209__  1
  
/*
//https://android.googlesource.com/platform/bionic/+/master/linker/Android.bp
架构为32位 定义__work_around_b_24465209__宏
arch: {
        arm: {cflags: ["-D__work_around_b_24465209__"],},
        x86: {cflags: ["-D__work_around_b_24465209__"],},
    }
*/
  
//android-platform\bionic\libc\include\link.h
#if defined(__LP64__)
#define ElfW(type) Elf64_ ## type
#else
#define ElfW(type) Elf32_ ## type
#endif
  
//android-platform\bionic\linker\linker_common_types.h
// Android uses RELA for LP64.
#if defined(__LP64__)
#define USE_RELA 1
#endif
  
//android-platform\bionic\libc\kernel\uapi\asm-generic\int-ll64.h
//__signed__-->signed
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;
  
//A12-src\msm-google\include\uapi\linux\elf.h
/* 32-bit ELF base types. */
typedef __u32   Elf32_Addr;
typedef __u16   Elf32_Half;
typedef __u32   Elf32_Off;
typedef __s32   Elf32_Sword;
typedef __u32   Elf32_Word;
  
/* 64-bit ELF base types. */
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;       /* entry tag value */
  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;  /* Location at which to apply the action */
  Elf64_Xword r_info;   /* index and type of relocation */
} 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;  /* Location at which to apply the action */
  Elf64_Xword r_info;   /* index and type of relocation */
  Elf64_Sxword r_addend;    /* Constant addend used to compute value */
} 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;       /* Symbol name, index in string tbl */
  unsigned char st_info;    /* Type and binding attributes */
  unsigned char st_other;   /* No defined meaning, 0 */
  Elf64_Half st_shndx;      /* Associated section index */
  Elf64_Addr st_value;      /* Value of the symbol */
  Elf64_Xword st_size;      /* Associated symbol 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;  /* Entry point */
  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]; /* ELF "magic number" */
  Elf64_Half e_type;
  Elf64_Half e_machine;
  Elf64_Word e_version;
  Elf64_Addr e_entry;       /* Entry point virtual address */
  Elf64_Off e_phoff;        /* Program header table file offset */
  Elf64_Off e_shoff;        /* Section header table file offset */
  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;
  
/* These constants define the permissions on sections in the program
   header, p_flags. */
#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;

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 25
支持
分享
最新回复 (10)
雪    币: 2765
活跃值: (3243)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
2
确实 我也遇到与你一样的问题,后面我用魔改的frida(软件能正常打开) 甚至hook不了  linker 加载的so 中的方法
2025-3-5 14:41
1
雪    币: 843
活跃值: (196)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
3
看雪精华帖预定
2025-3-5 16:04
1
雪    币: 204
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
xianyuuuan 看雪精华帖预定
排版有问题,估计得改改有个优把
2025-3-5 20:29
0
雪    币: 2775
活跃值: (3176)
能力值: ( LV11,RANK:190 )
在线值:
发帖
回帖
粉丝
5
逆天而行 确实 我也遇到与你一样的问题,后面我用魔改的frida(软件能正常打开) 甚至hook不了 linker 加载的so 中的方法
这个原因是基地址问题,需要你自己计算linker上去之后的地址
2025-3-9 22:01
0
雪    币: 4866
活跃值: (7189)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
真棒
2025-3-10 09:58
0
雪    币: 2765
活跃值: (3243)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
7
Shangwendada 这个原因是基地址问题,需要你自己计算linker上去之后的地址
我打印了字节码 与我ida 里看到的字节码 是一样  的  所以问题就很奇怪 
2025-3-10 12:35
0
雪    币: 244
活跃值: (2182)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
分析的非常棒
2025-3-10 13:47
0
雪    币: 569
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9
饱饱你太强了
2025-3-11 12:49
0
雪    币: 155
活跃值: (106)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
相当 nice 的分析文章,从中有学到知识!
2025-3-12 09:44
0
雪    币: 1314
活跃值: (2076)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
学习了,总结的很到位
2025-3-12 10:15
0
游客
登录 | 注册 方可回帖
返回