因为大版本更新的原因,导致版本17之前的教程几乎都没有用了,所以现在写一个
我目前的版本是17.4.0
里面的题目来自于https://github.com/DERE-ad2001/Frida-Labs
一,指令:
frida-ps -Uai:
frida-ps : 这将显示有关 Android 设备上运行进程的信息。-U : 此选项用于列出 USB 连接设备(物理设备或模拟器)上的进程。-a : 此选项用于列出所有进程,而不仅仅是当前用户拥有的进程。-i : 此选项用于包含每个进程的详细信息,例如进程 ID(PID)和进程名称。
我一般用这个来看包名和pid
frida -U -f <package_name>
附加应用进程
例如
frida -U -f com.ad2001.frida0x1 -l ./abc.js
参数详解
-U(USB)
- 作用:连接到通过 USB 连接的设备
- 说明:表示使用 USB 连接方式,通常用于连接 Android 手机或 iOS 设备
- 替代选项:
-D:连接到指定设备 ID-R:连接到远程设备
-f(spawn)
- 作用:启动指定的应用程序
- 格式:
-f <包名> - 说明:
com.ad2001.frida0x1是目标应用的包名,Frida 会重新启动这个应用并注入脚本 - 特点:从应用启动时就注入,可以捕获到应用的完整生命周期
-l(load)
- 作用:加载指定的 JavaScript 脚本文件
- 格式:
-l <脚本路径> - 说明:
./abc.js是你要注入的 Frida 脚本文件路径
cat /proc/<pid>/maps | grep '\.so'
查找软件调用的so包及其地址
可以修改\.so'来只输出特定的so库
例如
:/ # cat /proc/10169/maps | grep 'liba0x9\.so'
71f0f71d8000-71f0f71d9000 r-xp 00000000 08:23 2883799 /data/app/~~hUUSBr8r9jj6H61oJ58Q3g==/com.ad2001.a0x9-lEXjMqmljssnJwl8opW1JA==/lib/x86_64/liba0x9.so
71f0f71d9000-71f0f71da000 r--p 00000000 08:23 2883799 /data/app/~~hUUSBr8r9jj6H61oJ58Q3g==/com.ad2001.a0x9-lEXjMqmljssnJwl8opW1JA==/lib/x86_64/liba0x9.so
二,hook模板
1.函数替换
模板
Java.perform(function() {var <class_reference> = Java.use("<package_name>.<class>");<class_reference>.<method_to_hook>.implementation = function(<args>) {/*OUR OWN IMPLEMENTATION OF THE METHOD*/}})
Java.perform 是 Frida 中的一个函数,用于为您的脚本创建一个特殊的上下文,以与 Android 应用中的 Java 代码进行交互。
var <class_reference> = Java.use("<package_name>.<class>");
变量 <class_reference> 以表示目标 Android 应用中的 Java 类。您指定要与 Java.use 函数一起使用的类,该函数将类名作为参数。 <package_name> 表示 Android 应用的包名,而 <class> 表示您想要交互的类。<class_reference>可以随意命名例如a
<class_reference>.<method_to_hook>.implementation = function(<args>) {}
使用 <class_reference>.<method_to_hook> 符号来指定要钩取的方法。这是您可以定义自己逻辑的地方,当钩取的方法被调用时将执行该逻辑。 <args> 表示传递给函数的参数。
implementation是替换函数,替换为function后面定义的
例子
例如,我有一个包名为com.ad2001.frida0x1的程序

我要修改的是MainActivity类的get_random函数

那我的脚本就应该这样写:
Java.perform(function() {var a = Java.use("com.ad2001.frida0x1.MainActivity");a.get_random.implementation = function() {console.log("get_random is hooked")}
})
这个脚本的含义为如果hook住,就打印get_random is hooked
我把这个脚本命名为adb.js
然后我们这样运行,就完成了hook
frida -U -f com.ad2001.frida0x1 -l ./abc.js
2.函数定向hook
假设原始类中有多个重载方法,直接使用implementation就不行了,必须用overload来指定
例如
Java.perform(function() {var a = Java.use("com.ad2001.frida0x1.MainActivity");a.check.overload('int' ,'int').implementation = function(a,b) {console.log("First parameter:"+a)console.log("Sencond parameter:"+b)this.check(a,b)}})
在这个脚本中,指定了输入两个int变量的函数check
3.运行函数
这里有个代码,但没有任何引用这个函数的函数
public class MainActivity extends AppCompatActivity {static TextView t1;public static void get_flag(int a) {if(a == 4919) {try {SecretKeySpec secretKeySpec0 = new SecretKeySpec("HILLBILLWILLBINN".getBytes(), "AES");Cipher.getInstance("AES/CBC/PKCS5Padding").init(2, secretKeySpec0, new IvParameterSpec(new byte[16]));MainActivity.t1.setText("FLAG{BABY_HOOKS_0x2}");}catch(Exception e) {e.printStackTrace();}}}@Override // androidx.fragment.app.FragmentActivityprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);this.setContentView(layout.activity_main);MainActivity.t1 = (TextView)this.findViewById(id.textview);}
}
如果我们想要获得flag,直接看就好了(JEB神力,用jadx就不是这样的了)
要想办法运行这个函数
直接frida运行这个脚本就行了
Java.perform(function() {var a = Java.use("com.ad2001.frida0x2.MainActivity");a.get_flag(4919);
})
在use后,直接调用那个函数
4.修改变量
先use,在后面直接a.变量名.value即可
a.code.value
5.创建实例
这里有一个类,但没有被创建实例,所以没法直接运行

这里要使用$new()方法来创建实例,然后运行
代码
Java.perform(function() {var a = Java.use("com.ad2001.frida0x4.Check");var a_obj = a.$new();var flag = a_obj.get_flag(0x539);console.log(flag);
})
6.创建MainActivity的实例
这里有个没有创建实例的
public class MainActivity extends AppCompatActivity {TextView t1;public void flag(int code) {if(code == 0x539) {try {SecretKeySpec secretKeySpec0 = new SecretKeySpec("WILLIWOMNKESAWEL".getBytes(), "AES");Cipher.getInstance("AES/CBC/PKCS5Padding").init(2, secretKeySpec0, new IvParameterSpec(new byte[16]));this.t1.setText("FRIDA{ON_MATCH_THIS_INSTANCE}");}catch(Exception e) {e.printStackTrace();}}}@Override // androidx.fragment.app.FragmentActivityprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);this.setContentView(layout.activity_main);this.t1 = (TextView)this.findViewById(id.textview);}
}
如果我们像之前一样创建实例然后运行就会报错
[LGE AN10::com.ad2001.frida0x5 ]-> Java.perform(function() {var a = Java.use("com.ad2001.frida0x5.MainActivity");var a_obj = a.$new();a.flag(0x539);
})
Error: java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()at <anonymous> (/frida/bridges/java.js:1)at value (/frida/bridges/java.js:8)at e (/frida/bridges/java.js:8)at apply (native)at value (/frida/bridges/java.js:8)at e (/frida/bridges/java.js:8)at <anonymous> (<input>:4)at <anonymous> (/frida/bridges/java.js:1)at perform (/frida/bridges/java.js:8)at <eval> (<input>:6)at eval (native)at <anonymous> (/frida/repl/agent.js:1)at i (/frida/repl/agent.js:1)at fridaEvaluateExpression (/frida/repl/agent.js:1)at call (native)at handleRpcMessage (/frida/runtime/message-dispatcher.js:39)at handleMessage (/frida/runtime/message-dispatcher.js:25)
这个的原因相对复杂。大概就是frida创建的这个特殊MainActivity实例不在运行环境内,所以不行
这是全部解释:
直接使用 Frida 创建 MainActivity 或任何 Android 组件的实例可能会很棘手,因为 Android 的生命周期和线程规则。Android 组件,如 Activity 子类,依赖于应用程序上下文以正确运行。在 Frida 中,您可能缺少所需的上下文。Android UI 组件通常需要一个关联的 Looper 的特定线程。如果您正在处理 UI 任务,请确保您在主线程上,并且有一个活动的 Looper 。活动是更大 Android 应用程序生命周期的一部分。创建 MainActivity 的实例可能需要应用处于特定状态,而通过 Frida 管理整个生命周期可能并不简单。总之,为 MainActivity 创建实例并不是一个好主意。
解决
这里要用到一个新的模板
Java.choose
Java.performNow(function() {Java.choose('<Package>.<class_Name>', {onMatch: function(instance) {// TODO},onComplete: function() {}});
});
1. Java.performNow(function() { ... })
- 作用:确保代码在 Java 运行时环境中执行(Frida 需要附加到 JVM)。
- 细节:
Java.performNow(或更常见的Java.perform)是 Frida 的入口点,用于在目标进程的 Java 线程中安全执行代码。- 如果当前线程未附加到 JVM,它会自动附加并执行回调函数。
2. Java.choose('<Package>.<class_Name>', { ... })
- 作用:扫描内存中目标类的所有存活实例。
- 参数说明:
'<Package>.<class_Name>':目标类的完整名称(例如com.example.MyClass)。- 回调对象:包含
onMatch和onComplete两个回调函数。
3. onMatch: function(instance) { ... }
- 作用:每找到一个目标类的实例,就调用此回调函数。
- 参数:
instance参数表示目标类的每个匹配实例。您可以使用任何您想要的名称
- 用途:
- 在此函数内操作实例(如调用方法、修改字段)。
- 示例:
onMatch: function(instance) {console.log("Found instance: " + instance);instance.myMethod(); // 调用实例的方法instance.field.value = 123; // 修改字段值
}
4. onComplete: function() { ... }
- 作用:当所有实例遍历完成后调用。可选,不一定要有
- 用途:
- 用于执行清理操作或通知搜索结束。
- 示例:
onComplete: function() {console.log("Instance search completed.");
}
所以创建main实例的hook脚本是这样的
Java.performNow(function() {Java.choose('com.ad2001.frida0x5.MainActivity', {onMatch: function(instance) {console.log("instance: " + instance);//可以没有instance.flag(0x539);},onComplete: function() {}});
});
[LGE AN10::com.ad2001.frida0x5 ]-> Java.performNow(function() {Java.choose('com.ad2001.frida0x5.MainActivity', {onMatch: function(instance) {console.log("instance: " + instance);instance.flag(0x539);},onComplete: function() {}});
});
instance: com.ad2001.frida0x5.MainActivity@3804e07
instance: com.ad2001.frida0x5.MainActivity@91f8bf4
instance: com.ad2001.frida0x5.MainActivity@4654592
[LGE AN10::com.ad2001.frida0x5 ]->

7,挂钩构造函数
用于在实例被创造的时候修改函数
模板
Java.perform(function() {var <class_reference> = Java.use("<package_name>.<class>");<class_reference>.$init.implementation = function(<args>){/**/}
});
这里有个MainActivity没有被实例化
package com.ad2001.frida0x7;import android.os.Bundle;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;public class MainActivity extends AppCompatActivity {TextView t1;public void flag(Checker A) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {if(A.num1 > 0x200 && 0x200 < A.num2) {Cipher.getInstance("AES").init(2, new SecretKeySpec("MySecureKey12345".getBytes(), "AES"));this.t1.setText("FRIDA{HOOKING_CONSTRUCTORS}");}}@Override // androidx.fragment.app.FragmentActivityprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);this.setContentView(layout.activity_main);this.t1 = (TextView)this.findViewById(id.textview);Checker checker0 = new Checker(0x7B, 321);try {this.flag(checker0);}catch(NoSuchPaddingException e) {throw new RuntimeException(e);}catch(NoSuchAlgorithmException e) {throw new RuntimeException(e);}catch(InvalidKeyException e) {throw new RuntimeException(e);}catch(IllegalBlockSizeException e) {throw new RuntimeException(e);}catch(BadPaddingException e) {throw new RuntimeException(e);}}
}
Checker类也没有
package com.ad2001.frida0x7;public class Checker {int num1;int num2;Checker(int a, int b) {this.num1 = a;this.num2 = b;}
}
这时有两种hook方法
第一种
Java.performNow(function() {Java.choose('com.ad2001.frida0x7.MainActivity', {onMatch: function(instance) {console.log("Instance found");var checker = Java.use("com.ad2001.frida0x7.Checker");var checker_obj = checker.$new(600, 600); // Class Objectinstance.flag(checker_obj); // invoking the get_flag method},onComplete: function() {}});
});
第二种
也是就挂钩构造函数
在实例被创造的时候赋值
Java.perform(function() {var a = Java.use("com.ad2001.frida0x7.Checker");a.$init.implementation = function(param){this.$init(999,999);}
});
8.hook so库静态函数
不是hook so cool
模板
Interceptor.attach(targetAddress, {onEnter: function (args) {console.log('Entering ' + functionName);// Modify or log arguments if needed},onLeave: function (retval) {console.log('Leaving ' + functionName);// Modify or log return value if needed}
});
Interceptor.attach: 将回调附加到指定函数地址。targetAddress应该是我们想要钩取的本地函数的地址。onEnter: 当钩住的函数被调用时,会调用此回调。它提供对函数参数的访问 (args)。- to the return value (
retval).onLeave: 当钩住的函数即将退出时,会调用此回调。它提供对返回值的访问 (retval)。
既然关键是地址,那地址怎么找呢?
Process.getModuleByName("模块名").enumerateExports()
Process.getModuleByName("模块名").getExportByName()
Process.getModuleByName("模块名").findExportByName()
Process.getModuleByName("模块名").base
Process.getModuleByName("模块名").enumerateImports()
例子
public class MainActivity extends AppCompatActivity {private ActivityMainBinding binding;Button btn;EditText edt;static {System.loadLibrary("frida0x8");}public native int cmpstr(String arg1) {}@Override // androidx.fragment.app.FragmentActivityprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);ActivityMainBinding activityMainBinding0 = ActivityMainBinding.inflate(this.getLayoutInflater());this.binding = activityMainBinding0;this.setContentView(activityMainBinding0.getRoot());this.edt = (EditText)this.findViewById(id.editTextText);Button button0 = (Button)this.findViewById(id.button);this.btn = button0;button0.setOnClickListener(new View.OnClickListener() {@Override // android.view.View$OnClickListenerpublic void onClick(View v) {String s = MainActivity.this.edt.getText().toString();if(MainActivity.this.cmpstr(s) == 1) {Toast.makeText(MainActivity.this, "YEY YOU GOT THE FLAG " + s, 1).show();return;}Toast.makeText(MainActivity.this, "TRY AGAIN", 1).show();}});}
}
接受一个输入,放入cmpstr()函数中,然后判断输出是否为1
而这个函数却是个本地静态函数
public native int cmpstr(String arg1) {}
所以看so库
一个相当简单逆向,直接写脚本或者动调都能得到,不过为了学frida,那就用frida
_BOOL8 __fastcall Java_com_ad2001_frida0x8_MainActivity_cmpstr(__int64 a1, __int64 a2, __int64 a3)
{int v4; // [rsp+30h] [rbp-C0h]int i; // [rsp+34h] [rbp-BCh]char *s1; // [rsp+40h] [rbp-B0h]char s2[104]; // [rsp+80h] [rbp-70h] BYREFunsigned __int64 v9; // [rsp+E8h] [rbp-8h]v9 = __readfsqword(0x28u);s1 = (char *)_JNIEnv::GetStringUTFChars(a1, a3, 0LL);for ( i = 0; i < (unsigned __int64)__strlen_chk("GSJEB|OBUJWF`MBOE~", -1LL); ++i )s2[i] = aGsjebObujwfMbo[i] - 1;s2[__strlen_chk("GSJEB|OBUJWF`MBOE~", -1LL)] = 0;v4 = strcmp(s1, s2);__android_log_print(3LL, "input ", &unk_6B0, s1);__android_log_print(3LL, "Password", &unk_6B0, s2);_JNIEnv::ReleaseStringUTFChars(a1, a3, s1);return v4 == 0;
}
先附加frida到app上,然后我们的目的是hook strcmp函数,获取两个参数的内容
经过简单分析,可以得知s1是我们输入的内容,s2是flag
那我们该如何做呢?
const libc = Process.getModuleByName("libc.so");
const strcmp = libc.getExportByName("strcmp");
console.log("strcmp address: " + strcmp);
Interceptor.attach(strcmp, {onEnter: function(args) {var str1 = args[0].readUtf8String();var str2 = args[1].readUtf8String();if (str1 && str1.includes("hello")) {console.log("strcmp: '" + str1 + "' vs '" + str2 + "'");}},onLeave: function(retval) {// 可选:处理返回值}
});
Process.getModuleByName是获取库的地址
getExportByName是获取函数的地址
args[0].readUtf8String();读取第一个参数转为字符串
中间使用if是因为如果不加判断,那只要一调用就弹出信息,而这个函数被调用的实在是太多了
9.修改native返回值
这里有一个check_flag native函数,如果这个返回值为0x539,就输出flag,我们来看一看这个东西
public class MainActivity extends AppCompatActivity {private ActivityMainBinding binding;Button btn;static {System.loadLibrary("a0x9");}public native int check_flag() {}@Override // androidx.fragment.app.FragmentActivityprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);ActivityMainBinding activityMainBinding0 = ActivityMainBinding.inflate(this.getLayoutInflater());this.binding = activityMainBinding0;this.setContentView(activityMainBinding0.getRoot());Button button0 = (Button)this.findViewById(id.button);this.btn = button0;button0.setOnClickListener(new View.OnClickListener() {@Override // android.view.View$OnClickListenerpublic void onClick(View v) {Cipher cipher0;if(MainActivity.this.check_flag() == 0x539) {try {cipher0 = Cipher.getInstance("AES");}catch(NoSuchAlgorithmException e) {throw new RuntimeException(e);}catch(NoSuchPaddingException e) {throw new RuntimeException(e);}SecretKeySpec secretKeySpec0 = new SecretKeySpec("3000300030003003".getBytes(), "AES");try {cipher0.init(2, secretKeySpec0);}catch(InvalidKeyException e) {throw new RuntimeException(e);}Base64.getDecoder().decode("hBCKKAqgxVhJMVTQS8JADelBUPUPyDiyO9dLSS3zho0=");Toast.makeText(MainActivity.this.getApplicationContext(), "You won FRIDA{NATIVE_LAND_0x2}", 1).show();return;}Toast.makeText(MainActivity.this.getApplicationContext(), "Try again", 1).show();}});}
}
这个函数永远返回1
__int64 Java_com_ad2001_a0x9_MainActivity_check_1flag()
{return 1LL;
}
我们先附加一下
然后查看地址
[LGE AN10::com.ad2001.a0x9 ]-> var fr09 = Process.getModuleByName('liba0x9.so');
console.log(fr09.base);
Error: unable to find module 'liba0x9.so'at value (/frida/runtime/core.js:315)at <eval> (<input>:1)
[LGE AN10::com.ad2001.a0x9 ]->
找不到库
我们再试试用maps
:/ # cat /proc/9134/maps |grep 'liba0x9\.so'
1|:/ #
也找不到,这时只能改包了
将这一行改为true

然后重新安装
然后我们就能用maps找到了
/ # cat /proc/10169/maps | grep 'liba0x9\.so'
71f0f71d8000-71f0f71d9000 r-xp 00000000 08:23 2883799 /data/app/~~hUUSBr8r9jj6H61oJ58Q3g==/com.ad2001.a0x9-lEXjMqmljssnJwl8opW1JA==/lib/x86_64/liba0x9.so
71f0f71d9000-71f0f71da000 r--p 00000000 08:23 2883799 /data/app/~~hUUSBr8r9jj6H61oJ58Q3g==/com.ad2001.a0x9-lEXjMqmljssnJwl8opW1JA==/lib/x86_64/liba0x9.so
然而frida仍然找不到,不过已经知道地址了,也可正常用了
脚本
var fr09 = 0x71f0f71d8000;
var offset =0x670;
var get_flag = ptr(fr09 + offset);
console.log("get_flag address: " + get_flag);
Interceptor.attach(get_flag, {onEnter: function(args) {},onLeave: function(retval) {console.log("retval: " + retval);retval.replace(0x539);console.log('retval:'+retval);}
});
这个 0x71f0f71d8000;就是上面maps看到的库的起始地址
这个 offset =0x670;就是在IDA中看到函数的偏移地址
.text:0000000000000670 ; Attributes: bp-based frame
.text:0000000000000670
.text:0000000000000670 public Java_com_ad2001_a0x9_MainActivity_check_1flag
.text:0000000000000670 Java_com_ad2001_a0x9_MainActivity_check_1flag proc near
.text:0000000000000670 ; DATA XREF: LOAD:0000000000000358↑o
.text:0000000000000670
.text:0000000000000670 var_14 = dword ptr -14h
.text:0000000000000670 var_10 = qword ptr -10h
.text:0000000000000670 var_8 = qword ptr -8
.text:0000000000000670
.text:0000000000000670 ; __unwind {
.text:0000000000000670 push rbp
.text:0000000000000671 mov rbp, rsp
.text:0000000000000674 mov [rbp+var_8], rdi
.text:0000000000000678 mov [rbp+var_10], rsi
.text:000000000000067C mov [rbp+var_14], 1
.text:0000000000000683 mov eax, [rbp+var_14]
.text:0000000000000686 pop rbp
.text:0000000000000687 retn
.text:0000000000000687 ; } // starts at 670
.text:0000000000000687 Java_com_ad2001_a0x9_MainActivity_check_1flag endp
.text:0000000000000687
.text:0000000000000687 _text ends
.text:0000000000000687
ptr()是把数字变成指针
retval.replace(0x539);修改返回值
10.hook未调用的native函数
这里并没有调用
@Metadata(d1 = {"\u0000&\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u000E\n\u0002\b\u0002\u0018\u0000 \u000B2\u00020\u0001:\u0001\u000BB\u0005¢\u0006\u0002\u0010\u0002J\u0012\u0010\u0005\u001A\u00020\u00062\b\u0010\u0007\u001A\u0004\u0018\u00010\bH\u0014J\t\u0010\t\u001A\u00020\nH\u0086 R\u000E\u0010\u0003\u001A\u00020\u0004X\u0082.¢\u0006\u0002\n\u0000¨\u0006\f"}, d2 = {"Lcom/ad2001/frida0xa/MainActivity;", "Landroidx/appcompat/app/AppCompatActivity;", "()V", "binding", "Lcom/ad2001/frida0xa/databinding/ActivityMainBinding;", "onCreate", "", "savedInstanceState", "Landroid/os/Bundle;", "stringFromJNI", "", "Companion", "app_debug"}, k = 1, mv = {1, 8, 0}, xi = 0x30)
public final class MainActivity extends AppCompatActivity {@Metadata(d1 = {"\u0000\f\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\b\u0086\u0003\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002¨\u0006\u0003"}, d2 = {"Lcom/ad2001/frida0xa/MainActivity$Companion;", "", "()V", "app_debug"}, k = 1, mv = {1, 8, 0}, xi = 0x30)public static final class Companion {private Companion() {}public Companion(DefaultConstructorMarker defaultConstructorMarker0) {}}public static final Companion Companion;private ActivityMainBinding binding;static {MainActivity.Companion = new Companion(null);System.loadLibrary("frida0xa");}@Override // androidx.fragment.app.FragmentActivityprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);ActivityMainBinding activityMainBinding0 = ActivityMainBinding.inflate(this.getLayoutInflater());Intrinsics.checkNotNullExpressionValue(activityMainBinding0, "inflate(layoutInflater)");this.binding = activityMainBinding0;ActivityMainBinding activityMainBinding1 = null;if(activityMainBinding0 == null) {Intrinsics.throwUninitializedPropertyAccessException("binding");activityMainBinding0 = null;}this.setContentView(activityMainBinding0.getRoot());ActivityMainBinding activityMainBinding2 = this.binding;if(activityMainBinding2 == null) {Intrinsics.throwUninitializedPropertyAccessException("binding");}else {activityMainBinding1 = activityMainBinding2;}CharSequence charSequence0 = this.stringFromJNI();activityMainBinding1.sampleText.setText(charSequence0);}public final native String stringFromJNI() {}
}
IDA中可以看到一个名为get_flag的函数
unsigned __int64 __fastcall get_flag(int a1, int a2)
{int i; // [rsp+Ch] [rbp-44h]char v4[24]; // [rsp+30h] [rbp-20h] BYREFunsigned __int64 v5; // [rsp+48h] [rbp-8h]v5 = __readfsqword(0x28u);if ( a2 + a1 == 3 ){for ( i = 0; i < (unsigned __int64)__strlen_chk("FPE>9q8A>BK-)20A-#Y", -1LL); ++i )v4[i] = 2 * i + aFpe9q8aBk20aY[i];v4[19] = 0;__android_log_print(3LL, "FLAG", "Decrypted Flag: %s", v4);}return __readfsqword(0x28u);
}
这是又有一个新的模板
模板
var native_adr = new NativePointer(<address_of_the_native_function>);
const native_function = new NativeFunction(native_adr, '<return type>', ['argument_data_type']);
native_function(<arguments>);
我们先附加和查找库的地址
/ # cat /proc/11102/maps | grep 'libfrida0xa.so'
71f0f5f54000-71f0f5f99000 r-xp 00000000 08:23 2883988 /data/app/~~1zaEHlFt48C0MWU5A8AwHQ==/com.ad2001.frida0xa-I0MdndM9anwyj-E60VhaRQ==/lib/x86_64/libfrida0xa.so
71f0f5f99000-71f0f5f9d000 r--p 00044000 08:23 2883988 /data/app/~~1zaEHlFt48C0MWU5A8AwHQ==/com.ad2001.frida0xa-I0MdndM9anwyj-E60VhaRQ==/lib/x86_64/libfrida0xa.so
71f0f5f9d000-71f0f5f9e000 rw-p 00047000 08:23 2883988 /data/app/~~1zaEHlFt48C0MWU5A8AwHQ==/com.ad2001.frida0xa-I0MdndM9anwyj-E60VhaRQ==/lib/x86_64/libfrida0xa.so
函数偏移地址
:00000000000206B0 ; unsigned __int64 __fastcall get_flag(int, int)
.text:00000000000206B0 public _Z8get_flagii
.text:00000000000206B0 _Z8get_flagii proc near ; DATA XREF: LOAD:0000000000001740↑o
脚本
var fr0a = 0x71f0f5f54000;
var offset =0x206B0;
var get_flag_ptr = new NativePointer(ptr(fr0a + offset));
const get_flag = new NativeFunction(get_flag_ptr, 'void', ['int','int']);
get_flag(1,2);
native 层要创建NativeFunction对象其参数包括地址,返回类型,和参数类型
运行后在logcat就能看到
adb logcat
11.修改native汇编
这里有个本地函数,使用ida看什么也没有
void Java_com_ad2001_frida0xb_MainActivity_getFlag()
{;
}
但看汇编就知道因为条件是永假的,所以IDA优化了
我们先修改一下,展示一下逻辑

void Java_com_ad2001_frida0xb_MainActivity_getFlag()
{char *v0; // [rsp+8h] [rbp-48h]unsigned __int64 i_1; // [rsp+10h] [rbp-40h]unsigned __int64 i; // [rsp+20h] [rbp-30h]i_1 = __strlen_chk("j~ehmWbmxezisdmogi~Q", -1LL);v0 = (char *)operator new[](i_1 + 1);for ( i = 0LL; i < i_1; ++i )v0[i] = aJEhmwbmxezisdm[i] ^ 0x2C;v0[i] = 0;__android_log_print(3LL, "FLAG :", "%s", v0);if ( v0 )operator delete[](v0);
}
我们只要把那个JNZ改为JZ就可以让函数正常
脚本
var fr0b = 0x71f0f7305000;
var offset =0x170CE;
var get_flag_ptr = ptr(fr0b + offset);
Memory.protect(get_flag_ptr, 1, 'rwx');
const original = get_flag_ptr.readU8();//读取当前的字节
console.log("[*] Original byte: 0x" + original.toString(16));
get_flag_ptr.writeU8(0x74);
Memory.protect(get_flag_ptr, 1, 'rwx');这个指令是临时修改内存为可修改的
1是要修改的长度,可以多,不可以少,'rwx'是权限
也可以这样写,Memory.patchCode的机制更全,更可读
var fr0b = 0x71f0f7305000;
var offset =0x170CE;
var get_flag_ptr = ptr(fr0b + offset);
Memory.patchCode(get_flag_ptr, 1, code => {var original = code.readU8();console.log("[*] Original byte: 0x" + original.toString(16));code.writeU8(0x75);
});
三,函数总结
以下是ai生成,我让ai总结了这里使用的每一个函数,方便回顾记录
1. Java.perform
函数名: Java.perform
参数: function (回调函数)
返回值: 无
作用: 确保代码在Java运行时环境中执行,为脚本创建特殊上下文以与Android应用中的Java代码交互
2. Java.use
函数名: Java.use
参数: string (类名)
返回值: 类引用对象
作用: 获取目标Android应用中指定Java类的引用
3. Java.performNow
函数名: Java.performNow
参数: function (回调函数)
返回值: 无
作用: 立即在Java运行时环境中执行代码,自动附加到JVM
4. Java.choose
函数名: Java.choose
参数: string (类名), object (回调对象)
返回值: 无
作用: 扫描内存中目标类的所有存活实例,对每个实例执行回调
5. Java.$new
函数名: $new
参数: 可变参数(构造函数参数)
返回值: 类实例对象
作用: 创建指定Java类的新实例
6. Interceptor.attach
函数名: Interceptor.attach
参数: NativePointer (目标地址), object (回调对象)
返回值: 无
作用: 将回调附加到指定的本地函数地址,hook本地函数
7. Process.getModuleByName
函数名: Process.getModuleByName
参数: string (模块名)
返回值: Module对象
作用: 根据名称获取进程中的模块信息
10. ptr
函数名: ptr
参数: number/string (地址)
返回值: NativePointer (指针对象)
作用: 将数字或字符串转换为NativePointer指针对象
11. NativePointer.readU8
函数名: readU8
参数: 无
返回值: number (读取的值)
作用: 从指针位置读取1字节无符号整数
12. NativePointer.writeU8
函数名: writeU8
参数: number (要写入的值)
返回值: 无
作用: 向指针位置写入1字节无符号整数
13. NativePointer.readUtf8String
函数名: readUtf8String
参数: 无
返回值: string (UTF-8字符串)
作用: 从指针位置读取UTF-8编码的字符串
14. Memory.protect
函数名: Memory.protect
参数: NativePointer (地址), number (大小), string (权限)
返回值: boolean (是否成功)
作用: 修改指定内存区域的保护权限
15. Memory.patchCode
函数名: Memory.patchCode
参数: NativePointer (地址), number (大小), function (补丁函数)
返回值: 无
作用: 安全地修改指定内存区域的代码
16. NativeFunction
函数名: NativeFunction (构造函数)
参数: NativePointer (地址), string (返回类型), array (参数类型数组)
返回值: NativeFunction对象
作用: 创建本地函数调用封装器,用于调用未导出的本地函数
17. retval.replace
函数名: replace
参数: 任意类型 (新返回值)
返回值: 无
作用: 修改函数的返回值(在Interceptor.attach的onLeave回调中使用)
这些函数涵盖了Frida在Android应用hook中的主要操作,包括Java层hook、本地层hook、内存操作和函数调用等。