frida使用1:环境搭建

文章正文
发布时间:2025-11-17 07:29

第一次在吾爱破解上发帖,很高兴能有这么一个技术分享的平台~

笔者也是初学者,虽然网上有很多frida使用教程,但是笔者实在不太习惯先学完所有理论然后实战的过程,在笔者看来,以实战促学才是比较有效的锻炼技术的方式,所以就动手一边学,一边记录下了这篇文章

或许十年,二十年之后笔者会忘记,然后回来看自己教自己呢hh

另外,笔者也有自己的小博客,欢迎各位指点:https://blog.928330.xyz/

okok,闲言少叙,进入正题

环境:雷电模拟器9 + python3.11 + node.js + vscode

准备工作 本机安装frida(客户端)

Frida的工具包是基于Python的,所以我们使用pip来安装:

python -m pip install "frida==16.5.2" frida-tools

注意这里我们选定的frida版本是16.5.2,这还多亏一位前辈的教诲:

[!note]

“不要用这么高版本的frida,不稳定而且api经常换”

当然你可以酌情选择别的版本

对于小白来说,安装不当后面就会产生很多很多的坑,比如我......

(所以之后看见截图版本是17.5.0不要奇怪,我不想改图了)

安装完成后,输入frida --version,如果能看到版本号,说明装好了

记住这个版本号

模拟器安装frida(服务端) adb连接模拟器

最好新建一个模拟器,防止莫名其妙的bug

在设置里打开root和本地连接ADB:

image-20251112192311333

进入安装目录(LDPlayer9/,此目录下会有adb.exe),打开cmd

查看当前设备:

adb devices

[!tip]

如果这一步有问题,并且网上搜到的任何办法都不能解决的话,一定试试新建一个虚拟机!

adb连接,进入shell:

adb shell

PixPin_2025-11-05_17-41-49

保险起见,还是检查一下模拟器架构:

getprop ro.product.cpu.abi

一般是x86_64:

PixPin_2025-11-05_17-48-36

可以暂时退出shell了

下载安卓模拟器的frida

github下载:

https://github.com/frida/frida/releases

注意找之前本机对应的版本+模拟器架构的server,比如我的是16.5.2+x86_64:

image-20251112192708124

这一步千万要看清楚别下错了!!!

之后使用7zip解压,或者别的什么,只要保证支持.xz格式,并且解压结果和下面一样就行

最后得到的是没有后缀的单个二进制文件(资源管理器把版本分隔符识别成后缀名了):

image-20251112192858781

可以改个简单的名字(无需后缀),方便以后操作

在模拟器安装

运行雷电模拟器,把文件传送到手机的/data/local/tmp/目录:

adb push 具体路径\frida-server /data/local/tmp/

image-20251112193126514

进入adb shell,给它执行权限:

adb shell su          # 切换到root用户(模拟器默认有root权限) chmod 777 /data/local/tmp/frida-server

让frida服务在后台运行:

/data/local/tmp/frida-server &

PixPin_2025-11-05_19-37-10

端口转发

Frida的服务器端默认监听端口27042,有时会转发27043以兼容某些场景或工具,或应对多通道通信

端口转发能让主机上访问localhost:<host-port>就相当于访问设备上的127.0.0.1:<device-port>,也就是可以让frida/frida-ps/frida-trace等工具直接连接到127.0.0.1:27042上

adb forward tcp:27042 tcp:27042 adb forward tcp:27043 tcp:27043

(虽然现在暂时用不到)

测试

本机cmd输入:

frida-ps -U

如果输出了一个长长的进程列表(这些都是你模拟器里正在运行的进程),那就成功了:

image-20251109182839477

尝试一下

使用OWASP的官方Crackmes页面下载的UnCrackable-Level1.apk进行练习:

https://mas.owasp.org/crackmes/

运行如图:

PixPin_2025-11-05_18-53-26

第一个hook

打开App点击OK后会直接退出,因为它在启动时执行了检查,发现是Root环境,然后调用了函数来退出程序

我们的目标就是使用Frida,找到这个退出函数,并劫持它,让它什么都不做

找到包名

Frida启动App需要知道它的包名

这是App在Android系统里的唯一ID,可以使用frida-ps:

frida-ps -Uai

-U:连接USB设备

-a:只显示应用程序

-i:显示包名(Identifier)

PixPin_2025-11-05_19-10-18

也可以使用jadx查看:

PixPin_2025-11-05_19-08-42

owasp.mstg.uncrackable1 编写脚本

在Android中,最常见的退出函数是java.lang.System.exit(),我们想办法对它进行操作

编写.js脚本:

// 告诉Frida,我们要对Java进行操作 Java.perform(function() {     // 找到我们想Hook的类     var System = Java.use("java.lang.System");     // 找到这个类里的 "exit" 函数     var exit = System.exit;     // "implementation" 允许我们重写这个函数的实现     exit.implementation = function(value) {         // 当App调用System.exit(0)时...         // 1. 我们在控制台打印一条消息,证明我们抓到它了         console.log("Hooked System.exit(" + value + "), bypassing...");         // 2. 我们什么都不做(不调用原始的exit函数),App就不会退出了         // (如果想调用原始函数,可以写:exit.call(this, value);)     }; });

这个脚本的意思是:

当App试图调用System.exit()时,Frida会拦截这个调用,转而执行我们写的代码(打印一条日志),然后就此打住,不让App真的退出

加载脚本

之前我们使用过frida-ps -U,这是附加到一个已经在运行的进程

但现在我们遇到了一个问题:UnCrackable1一启动就闪退,我们根本来不及附加!

因此,我们需要一种新的模式:Spawn(启动)

Frida会以暂停状态启动这个App,在App的任何代码(包括我们想绕过的Root检测代码)运行之前,先注入我们的脚本,然后我们再恢复App的运行就行了

输入命令:

frida -U -l test.js -f owasp.mstg.uncrackable1

-U:连接USB设备

-l:load,加载我们的.js脚本

-f:使用Spawn模式启动这个包名

见证奇迹

在上一步回车后,你会看到Frida在你的CMD里显示Spawning owasp.mstg.uncrackable1...:

PixPin_2025-11-05_19-39-36

同时,模拟器上的App会自动启动,并卡在启动屏,这是Frida在注入脚本

在在Frida的CMD里输入%resume并按回车(告诉frida恢复app运行):

PixPin_2025-11-05_19-43-48

然后回到app点击ok,这次没有闪退了,成功进入app了:

PixPin_2025-11-05_19-41-33

并且Frida控制台也输出了我们的信息:

PixPin_2025-11-05_19-41-51

第二个hook

App现在虽然能运行了,但我们的最终目的还没有达到:我们还不知道那个secret是什么

随便输入会报错:

PixPin_2025-11-05_19-48-26

侦查

比较合理的猜想是,当我们点击VERIFY时,app内部会把我们输入的字符串和正确值进行比较

使用jadx能清楚看见这个逻辑:

PixPin_2025-11-05_19-52-10

编写脚本

既然是通过String.equals函数进行的比较,那我们可以修改这个函数,让它在运行过程中打印出比较的双方,从而带出真正的secret:

// find_secret.js console.log("Hooking java.lang.String.equals()..."); Java.perform(function() {     // 找到 "java.lang.String" 类     var StringClass = Java.use("java.lang.String");     // 找到 "equals" 方法     var equals = StringClass.equals;     // 重写 "equals" 方法的实现     equals.implementation = function(anObject) {         // ** 'this' 就是调用.equals()的那个字符串 (字符串A)         // ** 'anObject' 就是传入equals()的参数 (字符串B)         // 先调用原始的equals函数,得到比较结果 (true 或 false)         var result = equals.call(this, anObject);         // 这就是我们的“窃听”代码         // 为了防止控制台被刷屏,我们只打印我们关心的比较         // 我们猜测“秘密”的长度肯定不短(比如大于8)         var strA = this.toString();         var strB = anObject ? anObject.toString() : "null";         // 过滤掉无关的短字符串比较         if (strA.length > 8 || strB.length > 8) {             console.log("---[ String.equals comparison ]---");             console.log("  String A: " + strA);             console.log("  String B: " + strB);             console.log("  Result (A==B?): " + result);             console.log("------------------------------------");         }         // 把原始的比较结果返回给App         return result;     }; }); 联合注入

现在我们有两个脚本,必须同时加载他们,才能让App在不闪退的情况下,被我们窃听

很简单,用两次 -l来加载两个脚本就行了:

frida -U -l test.js -l secret.js -f owasp.mstg.uncrackable1

随意输入一个长一些的、比较有特色的字符串进行比较,此时Frida控制台就会打印出很多信息:

PixPin_2025-11-05_20-04-31

因为在看不见的地方,String.equals方法被调用了很多次,所以我们需要选个有辨识度的字符串方便找结果

总之,现在我们知道了secret的值:

I want to believe

PixPin_2025-11-05_20-07-15

更简洁的hook

我们Hook了String.equals,这个方法虽然有效,但你可能也发现了,它太吵了!

我们的Frida控制台会打印出App中所有的字符串比较,我们还得在里面费劲地找出想要的东西!

所以,更理想的办法是hook一个特定的函数,最好是只有在VERIFY按钮按下才会触发的函数

侦查

还是使用jadx

首先在sg.vantagepoint.uncrackable1.MainActivity中找到点击按钮的逻辑:

if (a.a(string)) {             alertDialogCreate.setTitle("Success!");             str = "This is the correct secret.";         } else {             alertDialogCreate.setTitle("Nope...");             str = "That's not it. Try again.";

于是我们能在sg.vantagepoint.a.a这个类里面找到解密逻辑:

bArrA = sg.vantagepoint.a.a.a(b("8d127684cbc37c17616d806cf50473cc"), Base64.decode("5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=", 0));

但是同一个名称的函数可能出现很多次,我们还需要更加精确地定位

可以看到它接受两个参数,而b(...)的返回值是byte[],Base64.decode(...)的返回值也是byte[]

所以,我们的目标是a(byte[], byte[])!

编写脚本 console.log("Hooking the PRECISE overloaded decryption function..."); Java.perform(function() {     // 找到真正的解密类     var DecryptionClass = Java.use("sg.vantagepoint.a.a");     // 找到 'a' 方法, 并且指定它的签名 (Overload)     // 在Frida中, '[B' 代表 byte[] (字节数组)     // 所以我们找的是 a(byte[], byte[])     var decryptionMethod = DecryptionClass.a.overload('[B', '[B');     // 重写实现     decryptionMethod.implementation = function(key_bytes, data_bytes) {         console.log("Precise function called!");         // 调用原始函数,让它正常解密         var decrypted_bytes = decryptionMethod.call(this, key_bytes, data_bytes);         // 转换结果         // 原始返回值是 byte[],我们需要把它转成 String 才能看懂         var StringClass = Java.use("java.lang.String");         // 使用 new String(byte[]) 构造函数         var secret = StringClass.$new(decrypted_bytes);         console.log("---[ Precise Decryption Hooked! ]---");         // 打印参数 (字节数组的可读性不强,但可以看看)         // console.log("  Input Key: " + key_bytes);         // console.log("  Input Data: " + data_bytes);         console.log("  Output (Secret): " + secret);         console.log("--------------------------------------");         // 把原始的 byte[] 返回值还给App,否则App会崩溃         return decrypted_bytes;     }; });

为什么是[B?

Frida使用Java的JNI类型签名来描述参数类型:

Z -> boolean

I -> int

Ljava.lang.String; -> String

[B -> byte[] ( [ 代表数组,B 代表 byte)

之后会讲到的

再次hook frida -U -l test.js -l secret.js -f owasp.mstg.uncrackable1

我们甚至这次无需输入任何东西,直接点击VERIFY按钮就能看见输出:

PixPin_2025-11-06_10-42-43

第一次就到这吧,主要是先接触一下,直到frida这玩意是用来干什么的,下一次将正式进入教学~

 

免费评分 参与人数 12吾爱币 +17 热心值 +10 理由

gongxifacai123
  + 1   + 1   我很赞同!  

thinkpad_420
  + 1   + 1   高手,这是高手  

iamafailor
  + 1   + 1   用心讨论,共获提升!  

zgy123
  + 1   + 1   谢谢@Thanks!  

smilerfu
  + 1   + 1   谢谢@Thanks!  

theSeven
  + 1   + 1   谢谢@Thanks!  

无名
  + 1   + 1   挺详细的  

wxl949389
  + 1   + 1   用心讨论,共获提升!  

Analytic
  + 1     我很赞同!  

helian147
  + 1     热心回复!  

a33463
    + 1   写的很好 刚好正需要  

正己
  + 7   + 1   欢迎分析讨论交流,吾爱破解论坛有你更精彩!  

查看全部评分