神奇日游保护分析——从Frida的启动说起

admin 2025年6月18日18:59:15评论0 views字数 5096阅读16分59秒阅读模式

最近看论坛里很多人在讨论一个日游——jp.gungho.*,特征so是lib__ 6dba__.so。

出于兴趣看了看,发现不是常规的自定义linker。于是花了三天时间把基本的加固流程和保护原理分析的差不多了,发现其中的so完整性检查(可用于anti frida)和mmap模块化调用很有新意,所以整理成文章和大家分享一下。

01

Frida的启动特征

frida已经成为逆向爱好者必备的工具,各种app对于frida的检测也五花八门,甚至还有什么“某某企业壳frida关了也能给检测出来”这种吓人操作。网上也有大量相关的文章。

可是frida启动时做了什么却很少有人分析。通过阅读frida源码,可以发现frida在启动时留下了大量可供检测的特征。

使用frida的第一步是在目标机器上启动frida-server,即使我们什么app都不hook,只启动一个server。

1.1 注入zygote

frida-server一启动,就主动注入了zygote进程

神奇日游保护分析——从Frida的启动说起

查看zygote进程的内存布局,在启动frida-server之前:

神奇日游保护分析——从Frida的启动说起

只启动frida-server,什么都不hook:

神奇日游保护分析——从Frida的启动说起

可以看到frida 的so已经注入zygote了。

1.2 hook一些必要的libc函数

frida-server启动注入zygote后,立刻自己hook了一些libc函数

主要有退出相关:exit,_exit,abort

神奇日游保护分析——从Frida的启动说起

fork相关:

神奇日游保护分析——从Frida的启动说起

和一些文件操作相关的函数。

1.3 修改页属性为RWX

frida是使用inline hook对函数进行hook的,inlinehook意味着要写内存,所以在hook前,frida会修改目标函数所在页的页属性为RWX:

神奇日游保护分析——从Frida的启动说起

maps文件中,连续内存的不同页属性会被独立列出来。

启动frida-server前,zygote maps文件中libc.so的布局:

神奇日游保护分析——从Frida的启动说起

启动frida-server后:

神奇日游保护分析——从Frida的启动说起

可以看到libc.so的内存中多了很多长度为0x1000的rwx片段,这些片段正是frida自己hook 那些libc基本函数导致的。

1.4 影响

◆由于frida在退出时并没有还原页属性,所以即使关闭frida-server,zygote进程的libc.so内存布局仍然遗留有大量rwx片段。这个特性只有机器重启才会还原。

◆由于app都是有zygote fork出来的,所以启动frida-server之后,无论是否关闭frida,新的app中maps文件的libc.so部分也存在大量rwx片段。

这两点就是一些“企业壳”所谓在frida就算关了也能检测出来的基本原理。

2.lib__ 6dba__.so模块化保护

打开目标so,发现只有一个init函数,没有JNI_OnLoad,同时.text段一大堆0,很明显是个壳子。

神奇日游保护分析——从Frida的启动说起

于是跟入init函数调试看看。

2.1 自定义的section

首先通过解密字符串/proc/self/maps打开maps文件,找到自身so的内存地址:

神奇日游保护分析——从Frida的启动说起

使用的是自实现的svc函数mmap。这个壳没有使用任何libc函数,所有的字符串操作:strcpy,strcmp,内存操作:memcmp等,都是自己实现,或者直接走svc 0。

神奇日游保护分析——从Frida的启动说起

这无形中增加了逆向的难度。

神奇日游保护分析——从Frida的启动说起

不过这个操作没什卵用,更重要的是打开了本地的lib__ 6dba__.so,然后解析elf文件,寻找type为0x80000000的section:

神奇日游保护分析——从Frida的启动说起

sh_type为0x80000000 - 0xffffffff为用户自定义区间:

神奇日游保护分析——从Frida的启动说起

可以看到这里存放了大量的加密数据:

神奇日游保护分析——从Frida的启动说起

然后对这段加密数据进行解密,首先解密出元信息,包括模块的大小,初始函数入口偏移等信息:

神奇日游保护分析——从Frida的启动说起

如0x257c8是解密的模块长度,0x5d4是解密后入口的偏移。

接下来会mmap一段0x257c8长度的内存,然后将数据解密过去,然后根据偏移跳转到解密出来的代码里执行:

神奇日游保护分析——从Frida的启动说起

注意,这里解密出来的是一个模块而不仅仅是一个函数。里面包含了几十个函数,所以我们需要将其dump下来分析。

2.2 mmap 模块化调用

进入解密的代码块中,首先mmap了一块0x1800大小的内存,这块内存用来存储后面mmap出的模块的信息。

每个模块的基本信息如下:

struct module{int id;int isrodata;void* base;int64 size;};

神奇日游保护分析——从Frida的启动说起

首先插入了两个模块,id分别是0xe2和0xd0,其中0xe2模块的内存基址是0x709e426000,大小是0x257c8

神奇日游保护分析——从Frida的启动说起

而0xe2模块,其实就是2.1中解密出的模块,就是当前模块本身。

这个保护解密出来的模块分为三种类型,最核心的是解释器模块。

2.2.1解释器模块

解释器模块主要负责循环解密下一个模块。

1.解释器模块首先检查当前执行的模块id是否大于0xe2,如果是的话将id-1的模块移除(在模块列表里删除,同时将对应的内存清空(memset为0)释放(munmap))。

神奇日游保护分析——从Frida的启动说起

这个操作主要的作用是解释器替换。

因为解释器模块可能mmap解密出新的解释器模块,这样执行新的解释器模块后,会将上一个解释器模块释放掉,用新的解释器模块来解密。

2.循环解密新的模块

神奇日游保护分析——从Frida的启动说起

3.如果解密出来的模块有初始化函数,调用初始化函数。

主要有两个初始化函数

◆第一个用来将模块列表传递给新的模块。

◆第二个为新模块的逻辑入口。

神奇日游保护分析——从Frida的启动说起

新的模块入口函数执行后,如果返回1,继续解密下一个模块执行。如果返回0,会跳出循环,然后清除之前的所有模块,然后调用svc exit退出。

神奇日游保护分析——从Frida的启动说起

注意,解释器模块是不会返回的,因为上一个解释器已经被清掉了,返回会直接crash。

而逻辑模块则必须返回1。如果逻辑模块返回0,则必然会svc exit退出。

2.2.2逻辑模块

逻辑模块主要执行不同的逻辑,有的是安全模块,检查各种环境信息。有的是解密模块。

例如:

◆0x20模块是内存hash检查,如果成功返回1,执行下一个反调试模块0x54。如果检查不通过,则会返回0,然后解释器模块svc exit。

◆0x9b模块负责解密还原真实的so。(注意所有模块都只是壳的一部分)

◆0x98模块调用原始so的init array。

2.2.3 数据模块

没有入口函数,解密出来扔在模块列表里,供其他模块使用。

2.3 模块的执行路径

神奇日游保护分析——从Frida的启动说起

第一个模块0xe2由原始so的init函数解密出来并执行。

然后0xe2模块解密了

  • 0x8f,0x8e两个模块。
  • 0xe3模块(解释器)

0xe3模块解密出了

  • 0xe4模块(解释器)

0xe4模块解密了

  • 0x60模块(root检查)
  • 0x40模块(模拟器检测)
  • 0x20模块(hash校验和libc检查)
  • 0x54模块(反调试检查)
  • 0xe5模块(解释器)

0xe5解密出了

  • 0xA4模块,fork出了一个子进程。
  • 0xe6模块(解释器)

0xe6解密出了

  • 0x72模块,意义不明,不是安全模块,不影响
  • 0xe7模块(解释器)

0xe7模块解密出了

  • 0x9b模块,主要使用多线程进行原始so的代码段解密和回填。
  • 0xe8模块(解释器)

0xe8模块:

解密出了0x98模块,该模块执行原始so的init_array函数。然后返回到linker中。

当然还有其他一些模块,这里只列举出比较重要的,一共大概有20多个模块。

2.4 小结

为什么返回到linker中了?因为我们从init函数来的,最终所有模块执行完了(如果都成功的话),会返回到linker掉用init函数的地方。

◆以上所有模块都是mmap在内存中,直接调用入口函数执行。如果没有研究清楚,就会觉得在无限次mmap。

◆模块在执行中解释器从0xe2-0xe3-0xe4-0xe5-0xe6-0xe7-0xe8,一共换了7次,所以显得非常复杂。但其实基本逻辑是相同的,研究清楚了很好跟进。

◆对于每个模块,只有plt函数和代码段,没有elf头,动态链接信息,section header等。所以dump下来后需要自己将dump下来的代码自己修复为一个so,然后用ida打开分析。否则所有代码在内存中,并且没有符号,很难分析。

◆每个模块都自己实现了一套libc基本函数,svc调用和字符串解密函数,所以hook libc函数没什么作用。

3.关键模块分析

这个壳的安全部分就是

◆0x60模块(root检查)

◆0x40模块(模拟器检测)

◆0x20模块(hash校验和libc检查)

◆0x54模块(反调试检查)

这四个模块。

0x60模块

在 /proc/mount,/proc/pid/mounts文件中检查magisk,同时检查一些su文件和路径(/system/bin/su之类)

主要使用了自定义的strcmp函数检查字符串,svc调用newfstatat检查文件是否存在。

神奇日游保护分析——从Frida的启动说起

(在调试过程中解密出的字符串被我改了,为了绕过检测)

0x40模块

主要检查模拟器,通过检测文件路径和包名来判断是否存在对应的模拟器。

其中包名检查主要是构造/data/user/0/包名路径,然后svc调用newfstatat检查。

神奇日游保护分析——从Frida的启动说起

检查的包:

神奇日游保护分析——从Frida的启动说起

神奇日游保护分析——从Frida的启动说起

检查的路径:

神奇日游保护分析——从Frida的启动说起

神奇日游保护分析——从Frida的启动说起

神奇日游保护分析——从Frida的启动说起

包括使用__system_property_get()函数,获取一些系统信息:

神奇日游保护分析——从Frida的启动说起

包括检查ro.hardware,检查是否为goldfish,或者ranchu

神奇日游保护分析——从Frida的启动说起

神奇日游保护分析——从Frida的启动说起

神奇日游保护分析——从Frida的启动说起

0x20模块

主要检查so库的hash和libc

hash校验

首先通过解密函数解密/assets/6dba/data1.dat文件,解密出:

神奇日游保护分析——从Frida的启动说起

其中分别包含所有so的

◆文件大小

◆文件hash

◆.text段偏移

◆.text段大小

◆.text段hash

◆so名

如上图红框所示,分别是libopenal.so的文件大小:0x14e518,文件hash:0xbc53ce75 ,.text偏移:0x7e30

, .text大小:0x1b7c8, .text 段hash:0x176a9502

该模块会打开本地对应的so,检查文件的hash。如果当前模块是对应的so,同时会进行.text段的hash校验。

hash函数为自定义的,不是常见的crc32:

神奇日游保护分析——从Frida的启动说起

libc检查

该加固对libc检查的方式非常奇妙,这就用到了开头说的frida启动特征。

正常的libc在maps里的条目是比较少的,通常小于10个:

神奇日游保护分析——从Frida的启动说起

但是启动过frida的机器,或被frida hook的app,libc的maps里有很多rwx碎片:

神奇日游保护分析——从Frida的启动说起

该模块首先打开maps文件,查找起始地址大于libc .text起始,并小于libc .text终止的条目个数。

如果大于9个,则认为libc"不正常",然后返回0(解释器svc exit退出)

神奇日游保护分析——从Frida的启动说起

如果maps里条目数正常,该模块会映射一份本地libc,和内存中的libc的代码段用自定义的memcmp函数比较:

神奇日游保护分析——从Frida的启动说起

通常来讲都会比较libc代码段的crc32,但是其实直接memcmp也没什么问题。只要内存不一致就说明libc被修改了,没必要算crc.

0x54模块

反调试模块比较常规,就是打开/proc/pid/status 检查TracerPid。

神奇日游保护分析——从Frida的启动说起

同时还通过libart.so找到了SetJdwpAllowed函数地址:

神奇日游保护分析——从Frida的启动说起

然后执行了SetJdwpAllowed(false),似乎没什么用。

0x9b 模块

使用多线程解密原始so:

神奇日游保护分析——从Frida的启动说起

解密之后dump原始so的rx段回填,就可以看到原始so的代码了。

神奇日游保护分析——从Frida的启动说起

还缺重定位表和init_array信息,这两个拿到了就能修复so了,关于自定义linker部分这里就不细究了。但是应该在0x9b模块里是能拿到的。

04

后记

这个加固将app里的每一个so都加壳了,每个so都要经过这样的模块化层层处理后,最终才会解密执行。

不同的是,除了lib__ 6dba__.so自身以外,其他的so在解密过程中只执行了0x20模块进行hash校验和libc检查,没有执行其他安全模块。

至于专门针对frida的检测,并没有看到。但是只要检查libc在maps中的个数,就可以完全对抗frida了。甚至frida启动过一次关了都能检查出来,必须重启机器才行。

对于模块化保护,需要熟悉elf结构,自定义libc函数,svc调用,无头无动态链接纯代码还原成so静态分析,自定义linker等,分析起来难度还是相当大的。

当然,最重要的是耐心,他的模块化执行路径我也是调试了几十次才最终搞清楚。总的来说这是一款有难度的安卓保护,mmap模块化匿名执行和maps检查antiFrida都是很新的思路。

看雪ID:乐子人

https://bbs.kanxue.com/user-home-872365.htm

*本文为看雪论坛精华文章,由 乐子人原创,转载请注明来自看雪社区
 

原文始发于微信公众号(看雪学苑):神奇日游保护分析——从Frida的启动说起

 

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年6月18日18:59:15
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   神奇日游保护分析——从Frida的启动说起https://cn-sec.com/archives/4176053.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息