首页
论坛
课程
招聘
[原创]非root环境下frida持久化的两种方式及脚本
2021-6-22 11:31 14276

[原创]非root环境下frida持久化的两种方式及脚本

2021-6-22 11:31
14276

非root环境下frida持久化的两种方式及脚本

frida是一个非常好用的hook框架,但使用中有两个问题,一是非root手机使用挺麻烦的,二是frida相较于其他HOOK框架没那么持久。网上的持久化大多基于xposed、刷ROM或者是virtualapp,前面两个是比较重量级,不够轻便。虚拟化技术本身就自带风险,很容易被检测到。

在Android端,网上教程里大部分都是使用frida server来进行hook,其实还有一种使用方法为 frida gadget,此方法需要将frida-gadget.so注入到apk中,纯手动的话过于麻烦,所以这里实现两个脚本,分别用修改smali、修改so来注入目标。


我使用的frida-gadget版本为14.2.18。有其他版本的需求,需要替换tools下的so文件

方法一 调试apk中含有so

此方法相对简单。原理来自于古早的静态注入方式:Android平台感染ELF文件实现模块注入

而这种注入方式有工具可以快速实现:How to use frida on a non-rooted device

该方法优点在于可以让gadget是第一个启动的,缺点是没有so的apk不能用。

1.效果

首先运行注入脚本,获得注入且重签名后的apk。直接安装。

将frida_script.js push 到/data/local/tmp。frida_script.js为你的hook代码:

Java.perform(function () {
 var Log = Java.use("android.util.Log");
 Log.e("frida-OOOK", "Have fun!");
});//android 不要使用console.log

打开app即可看到效果,app每次启动都会成功的打印LOG。:

不想使用持久化(本地js脚本),也可以通过电脑连接:



不使用持久化,就不必添加config文件,所以脚本执行不需要执行-persistence,执行下面的就可以:

python LIEFInjectFrida.py apkfile  outdir  libnative-lib.so  -apksign


2.代码

工具详细代码:https://github.com/nszdhd1/UtilScript/blob/main/LIEFInjectFrida.py

运行脚本记得安装lief(pip install lief)

其实关键代码就几行:


   for soname in injectsolist: #遍历apk中指定SO有哪几种架构,并添加gadget.so为依赖库。
       if soname.find("x86") != -1:
           continue
       so = lief.parse(os.getcwd()+"\\"+soname)
       so.add_library("libfrida-gadget.so")
       so.write(soname+"gadget.so")

方法二  apk中没有so

在实际情况下,并不是所有的apk都有so。没有so,方法一便没有用武之地了。

此方法呢,是通过修改smali,调用System.loadLibrary来加载so。该原理更简单,但是有一个弊端就是时机不够靠前,没有办法hook Activity 启动之前的代码。

手动修改太麻烦,还是写一个脚本自动化注入。

此方法优点是原理简单,缺点是脚本实现麻烦,容易写bug

1. 效果

首先运行注入脚本,获得注入且重签名后的apk。直接安装。




frida_script.js代码同上,同样也可以使用电脑连接:



2. 代码

工具详细代码:https://github.com/nszdhd1/UtilScript/blob/main/SmaliInjectFrida.py

关键代码:

    def get_launchable_activity_aapt(self): #通过aapt找到apk的启动activity
       aapt_path = os.path.join(self.toolPath, 'aapt.exe')
       cmd = '%s dump badging "%s" ' % (aapt_path, self.apkpath)
       p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
       out,err = p.communicate()
       cmd_output = out.decode('utf-8').split('\r')
       for line in cmd_output:
           #正则,pattern.search正常,pattern.match就会有问题=-=懒得解决了
           pattern = re.compile("launchable-activity: name='(\S+)'")
           match = pattern.search(line)
           if match:
               # print match.group()[27:-1]
               return match.group()[27:-1]
         
   def injectso(self):
       target_activity = self.get_launchable_activity_aapt()
       for dex in self.dexList:
           print(dex)
           if self.dexDecompile(dex):
               smali_path = os.path.join(self.decompileDir,target_activity.replace('.','\\'))+".smali"
               print(smali_path)
               with open(smali_path, 'r') as fp:
                   lines = fp.readlines()
                   has_clinit = False
                   start = 0
                   for i in range(len(lines)):  
                       #start是获取smali中,可以添加代码的位置
                       if lines[i].find(".source") != -1:
                           start = i
                       #找到初始化代码
                       if lines[i].find(".method static constructor <clinit>()V") != -1:
                           if lines[i + 3].find(".line") != -1:
                               code_line = lines[i + 3][-3:]
                               lines.insert(i + 3, "%s%s\r" % (lines[i + 3][0:-3], str(int(code_line) - 2)))
                               print("%s%s" % (lines[i + 3][0:-3], str(int(code_line) - 2)))
                               #添加相关代码
                               lines.insert(i + 4, "const-string v0, \"frida-gadget\"\r")
                               lines.insert(i + 5,
                                            "invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V\r")
                               has_clinit = True
                               break
                   #如果碰上本身没有clinit函数的apk,就需要自己添加
                   if not has_clinit:
                       lines.insert(start + 1, ".method static constructor <clinit>()V\r")
                       lines.insert(start + 2, ".registers 1\r")
                       lines.insert(start + 3, ".line 10\r")
                       lines.insert(start + 4, "const-string v0, \"frida-gadget\"\r")
                       lines.insert(start + 5,
                                    "invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V\r")
                       lines.insert(start + 6, "return-void\r")
                       lines.insert(start + 7, ".end method\r")

                   with open(smali_path, "w") as fp:
                       fp.writelines(lines)
               self.dexCompile(dex)

Frida 持久化检测特征

我因为方便,将frida js 放在了/data/local/tmp下,如果直接放在app的沙箱下,这就是一个稳定的hook框架了。

既然做了持久化,就要从防御者角度看看哪些方面可以检测到应用被注入了。

首先,当然是内存中会有frida-gadget.so。但这个so可以被重命名(我可以命名为常见的模块,比如libBugly.so),所以检测/proc/pid/maps下是否有frida-gadget并不准确。因为frida有一个config文件,是持久化必须存在的。所以检测libs下是否有lib*.so和lib*.config.so是一种较为可行的方法。但是,如果你不使用持久化,或者去github上找到frida的源码修改gaget.vala(ps.这一点是合理的猜想,还未验证过),就可以让防御者检测不到。

#gaget.vala 代码片段
if ANDROID
 if (!FileUtils.test (config_path, FileTest.EXISTS)) {
  var ext_index = config_path.last_index_of_char ('.');
  if (ext_index != -1) {
   config_path = config_path[0:ext_index] + ".config.so";#修改这里,就可以检测不到。需要保持后缀不变(例如改成symbols.so)
  } else {
   config_path = config_path + ".config.so";
  }
 }
#endif

除去端口检测这种几乎没什么用的,还有一种比较可行的是内存扫描,扫描内存中是否有LIBFRIDA_GADGET关键词,具体实现网上有教程我就不介绍了。






[2022夏季班]《安卓高级研修班(网课)》月薪两万班招生中~

最后于 2021-6-22 11:32 被八重嘤编辑 ,原因:
收藏
点赞6
打赏
分享
最新回复 (8)
雪    币: 1320
活跃值: 活跃值 (950)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
Amun 活跃值 2021-6-22 12:30
2
2

总结如下: 

  1. 给 APK 内的动态库新增依赖项,重新签名

  2. 修改 APK 内的 dex 文件,添加 loadLibray,重新签名

都重新签名,那么明显的检测特征,要不再完善一下,加上过文件校验和签名检测的脚本?


雪    币: 362
活跃值: 活跃值 (1212)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
八重嘤 活跃值 2021-6-22 14:04
3
0
Amun 总结如下:&nbsp;给 APK 内的动态库新增依赖项,重新签名修改 APK 内的 dex 文件,添加 loadLibray,重新签名都重新签名,那么明显的检测特征,要不再完善一下,加上过文件 ...
我不想写,我的场景用不到。你自己改一改frida js不就行了吗
雪    币: 1
活跃值: 活跃值 (884)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_范迪塞尔 活跃值 2021-6-22 17:09
4
0
感觉还是inject更好用
雪    币: 0
活跃值: 活跃值 (375)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
phoenii 活跃值 2021-6-25 23:14
5
0
wx_范迪塞尔 感觉还是inject更好用
inject二次开发路过
雪    币: 6
活跃值: 活跃值 (505)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
路易 活跃值 2021-6-29 11:31
6
0
phoenii inject二次开发路过
给个源
雪    币: 987
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
wx_当年年少一见不忘 活跃值 2021-11-18 19:31
7
0
Exception in thread "main" java.lang.IllegalAccessError: class com.android.apksig.internal.apk.v1.V1SchemeSigner (in unnamed module @0x6f2b958e) cannot access class sun.security.x509.AlgorithmId (in module java.base) because module java.base does not export sun.security.x509 to unnamed module @0x6f2b958e
        at com.android.apksig.internal.apk.v1.V1SchemeSigner.getSupportedAlgorithmId(V1SchemeSigner.java:622)
        at com.android.apksig.internal.apk.v1.V1SchemeSigner.<clinit>(V1SchemeSigner.java:538)
        at com.android.apksig.DefaultApkSignerEngine.<init>(DefaultApkSignerEngine.java:146)
        at com.android.apksig.DefaultApkSignerEngine.<init>(DefaultApkSignerEngine.java:51)
        at com.android.apksig.DefaultApkSignerEngine$Builder.build(DefaultApkSignerEngine.java:899)
        at com.android.apksigner.ApkSignerTool.sign(ApkSignerTool.java:280)
        at com.android.apksigner.ApkSignerTool.main(ApkSignerTool.java:93)
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
shuishui1024 活跃值 2022-1-11 10:20
8
0
virtualapp 方式是怎样的话,没找到相关文章呢,求指教
雪    币: 98
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
波什 活跃值 2022-4-12 10:31
9
0
这个方案需要重新打包签名,用这个方案的前提是先要绕过二次打包校验,不然重新打包之后不能运行
游客
登录 | 注册 方可回帖
返回