Frida_Labs笔记

Frida_Labs笔记

Frida介绍

Frida是一个基于动态二进制插桩技术的轻量级hook框架,能够在不修改可执行文件的前提下注入代码到目标进程的内存中或访问内存,支持Windows、macOS、Linux、iOS、Android、QNX、FreeBSD等平台。

简单来说,frida的原理是将GumJS引擎以动态库的形式注入目标进程,我们编写脚本将JS代码发送给JS引擎执行。JS引擎可以和被注入的进程通信以实现内存访问、函数hook、函数调用等功能。

Frida使用时分为Client和Server。Client一般在PC端,Server在移动端。Client通过Python脚本将Js代码提交到Server;Server接收到hook代码后注入目标进程,完成内存操作后给Client发送反馈消息。frida-server 是一个守护进程,通过 TCP 和 Frida 核心引擎通信,默认的监听端口是 27042

frida有三种模式,以上是注入模式的内容,需要设备root;其它还有嵌入和预加载两种模式,不需要root(基于frida-Gadget),参考官方文档-frida模式

除了Python,脚本也可以用Go、C、Swift、TypeScript等语言,官方文档中有API参考

代码和架构

文档和资料

Frida内部原理

其它类似的hook工具还有Xposed和SubstrateCydia等,Frida适合新手入门,Xposed适合java层hook,SubstrateCydia适合native层hook。

环境搭建

  • 安卓设备需要ROOT
  • PC端的frida版本和移动端的frida-server版本需要一致

PC端安装frida和frida-tools

1
2
pip install frida
pip install frida-tools

查看frida版本和CPU架构,获取版本和架构相同的server:传送门

如下选择frida-server-16.6.6-android-arm64.xz,下载后解压后得到frida-server的二进制可执行文件

1
2
3
4
frida --version
16.6.6
adb shell getprop ro.product.cpu.abi
arm64-v8a

移动端以root权限运行frida-server

1
2
3
4
5
6
adb push frida-server-16.6.6-android-arm64 /data/local/tmp/
adb shell
su
cd /data/local/tmp/
chmod 777 frida-server-16.6.6-android
./frida-server-16.6.6-android &

再打开一个cmd,验证frida-server已成功运行

1
2
3
4
frida-ps -Ua
...
21185 frida-server-16.6.6-android
...

将移动端frida-server的TCP端口转发至PC端,每次运行frida-server都要重新转发端口

1
adb forward tcp:27042 tcp:27042

Frida_labs

包含一系列专为学习 Frida for Android 而设计的挑战,帮助新手开始使用Frida及其常用API。

项目地址

1
安装各关卡APP:adb install Challenge 0xN.apk
挑战0x1:Frida设置,hook方法 | attach模式和spawn模式

运行APP初步了解功能

1

jadx反编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
        final int i = get_random();
((Button) findViewById(R.id.button)).setOnClickListener(new View.OnClickListener() { // from class: com.ad2001.frida0x1.MainActivity.1
@Override // android.view.View.OnClickListener
public void onClick(View view) {
String obj = editText.getText().toString(); // 获取输入框内容
if (TextUtils.isDigitsOnly(obj)) { // 输入框内容必须是纯数字
MainActivity.this.check(i, Integer.parseInt(obj)); // ;check函数对输入进行校验
} else {
Toast.makeText(MainActivity.this.getApplicationContext(), "Enter a valid number !!", 1).show();
}
}
});

int get_random() {
return new Random().nextInt(100);
}

void check(int i, int i2) {
if ((i * 2) + 4 == i2) {
Toast.makeText(getApplicationContext(), "Yey you guessed it right", 1).show();
StringBuilder sb = new StringBuilder();
for (int i3 = 0; i3 < 20; i3++) {
char charAt = "AMDYV{WVWT_CJJF_0s1}".charAt(i3);
if (charAt < 'a' || charAt > 'z') {
if (charAt >= 'A') {
if (charAt <= 'Z') {
charAt = (char) (charAt - 21);
if (charAt >= 'A') {
}
charAt = (char) (charAt + 26);
}
}
sb.append(charAt);
} else {
charAt = (char) (charAt - 21);
if (charAt >= 'a') {
sb.append(charAt);
}
charAt = (char) (charAt + 26);
sb.append(charAt);
}
}
this.t1.setText(sb.toString());
return;
}
Toast.makeText(getApplicationContext(), "Try again", 1).show();
}
}

主要的校验逻辑在check(int i, int i2)中,i是由get_radom函数生成的0-99随机数,i2是我们的输入,满足(i * 2) + 4 == i2才能通过校验,然后解码AMDYV{WVWT_CJJF_0s1}输出flag。

  • 方法一:hook get_radom函数,将其返回值修改为固定值。

    这里改为0,所以hook之后输入4即可过关。注意由于get_random函数是在on_create即启动阶段被执行的,所以要采用spawn模式: 启动一个新的进程并挂起,在启动的同时注入frida代码,注入完成后再调用resume恢复进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import frida,sys

PACKAGE = "com.ad2001.frida0x1"
jsCode = """
Java.perform(function() {
var a= Java.use("com.ad2001.frida0x1.MainActivity");
a.get_random.implementation = function(){
return 0;
}
})
"""

def on_message(message, data):
print(message)

device = frida.get_usb_device()
pid = device.spawn(PACKAGE)
process = device.attach(pid)
script = process.create_script(jsCode)
script.on('message', on_message)
script.load()
device.resume(pid)
sys.stdin.read()
  • 方法二:hook check函数,将其参数改为两个能通过校验的固定值。 由于该函数带两个参数,所以需要用overload关键字在重载时指定两个参数的类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import frida,sys

PACKAGE = "Frida 0x1"
jsCode = """
Java.perform(function(){
var MainActivity = Java.use("com.ad2001.frida0x1.MainActivity");
MainActivity.check.overload('int','int').implementation = function (a,b){
console.log("Origin i and i2 = ",a,b);
return this.check(1,6);
}
});
"""

def on_message(message, data):
print(message)

process = frida.get_usb_device().attach(PACKAGE)
script = process.create_script(jsCode)
script.on('message', on_message)
script.load()
sys.stdin.read()
挑战0x2:主动调用静态方法
2

页面没有交互,但是有一个未被使用静态的get_flag方法,当参数为4919时解密输出flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MainActivity extends AppCompatActivity {
static TextView t1;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
t1 = (TextView) findViewById(R.id.textview);
}

public static void get_flag(int a) {
if (a == 4919) {
try {
SecretKeySpec secretKeySpec = new SecretKeySpec("HILLBILLWILLBINN".getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec(new byte[16]);
cipher.init(2, secretKeySpec, iv);
byte[] decryptedBytes = cipher.doFinal(Base64.decode("q7mBQegjhpfIAr0OgfLvH0t/D0Xi0ieG0vd+8ZVW+b4=", 0));
String decryptedText = new String(decryptedBytes);
t1.setText(decryptedText);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

思路:主动调用静态方法get_flag并传入参数4919

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import frida,sys

PACKAGE = "Frida 0x2"
jsCode = """
Java.perform(function(){
var MainActivity = Java.use("com.ad2001.frida0x2.MainActivity");
MainActivity.get_flag(4919);
});
"""

def on_message(message, data):
print(message)

process = frida.get_usb_device().attach(PACKAGE)
script = process.create_script(jsCode)
script.on('message', on_message)
script.load()
sys.stdin.read()
挑战0x3:改变变量的值
3

点击按钮时如果Checker.code == 512则解密并打印flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class Checker {
static int code = 0;

public static void increase() {
code += 2;
}
}

public class MainActivity extends AppCompatActivity {
TextView t1;

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn = (Button) findViewById(R.id.button);
this.t1 = (TextView) findViewById(R.id.textView);
btn.setOnClickListener(new View.OnClickListener() { // from class: com.ad2001.frida0x3.MainActivity.1
@Override // android.view.View.OnClickListener
public void onClick(View v) {
if (Checker.code == 512) {
byte[] bArr = new byte[0];
Toast.makeText(MainActivity.this.getApplicationContext(), "YOU WON!!!", 1).show();
byte[] KeyData = "glass123".getBytes();
SecretKeySpec KS = new SecretKeySpec(KeyData, "Blowfish");
byte[] ecryptedtexttobytes = Base64.getDecoder().decode("MKxsZsY9Usw3ozXKKzTF0ymIaC8rs0AY74GnaKqkUrk=");
try {
Cipher cipher = Cipher.getInstance("Blowfish");
cipher.init(2, KS);
byte[] decrypted = cipher.doFinal(ecryptedtexttobytes);
String decryptedString = new String(decrypted, Charset.forName("UTF-8"));
MainActivity.this.t1.setText(decryptedString);
return;
} catch (InvalidKeyException e) {
throw new RuntimeException(e);
} catch (NoSuchAlgorithmException e2) {
throw new RuntimeException(e2);
} catch (BadPaddingException e3) {
throw new RuntimeException(e3);
} catch (IllegalBlockSizeException e4) {
throw new RuntimeException(e4);
} catch (NoSuchPaddingException e5) {
throw new RuntimeException(e5);
}
}
Toast.makeText(MainActivity.this.getApplicationContext(), "TRY AGAIN", 1).show();
}
});
}
}

思路:修改checker类中code变量的值为512

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import frida,sys

PACKAGE = "Frida 0x3"
jsCode = """
Java.perform(function(){
let Checker = Java.use("com.ad2001.frida0x3.Checker");
Checker.code.value=512;
});
"""

def on_message(message, data):
print(message)

process = frida.get_usb_device().attach(PACKAGE)
script = process.create_script(jsCode)
script.on('message', on_message)
script.load()
sys.stdin.read()
挑战0x4:创建类实例
4

和挑战3类似,有一个未使用的非静态check类,传入参数为1337时返回flag。

1
2
3
4
5
6
7
8
9
10
11
12
public class Check {
public String get_flag(int a) {
if (a == 1337) {
byte[] decoded = new byte["I]FKNtW@]JKPFA\\[NALJr".getBytes().length];
for (int i = 0; i < "I]FKNtW@]JKPFA\\[NALJr".getBytes().length; i++) {
decoded[i] = (byte) ("I]FKNtW@]JKPFA\\[NALJr".getBytes()[i] ^ 15);
}
return new String(decoded);
}
return "";
}
}

思路:获取Check类 => 从类创建Check对象 => 调用get_flag方法并传入参数1337 => 接收打印check方法返回的Flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import frida,sys

PACKAGE = "Frida 0x4"
jsCode = """
Java.perform(function(){
let Check = Java.use("com.ad2001.frida0x4.Check");
let Check_obj = Check.$new();
let flag = Check_obj.get_flag(1337);
console.log(flag);
});
"""

def on_message(message, data):
print(message)

process = frida.get_usb_device().attach(PACKAGE)
script = process.create_script(jsCode)
script.on('message', on_message)
script.load()
sys.stdin.read()
1
输出:FRIDA{XORED_INSTANCE}
挑战0x5:调用现有实例上的方法
5

MainActivity类中有一个非静态方法flag,code参数为1337时输出flag到TextView空件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class MainActivity extends AppCompatActivity {
TextView t1;

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.t1 = (TextView) findViewById(R.id.textview);
}

public void flag(int code) {
if (code == 1337) {
try {
SecretKeySpec secretKeySpec = new SecretKeySpec("WILLIWOMNKESAWEL".getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec(new byte[16]);
cipher.init(2, secretKeySpec, iv);
byte[] decodedEnc = Base64.getDecoder().decode("2Y2YINP9PtJCS/7oq189VzFynmpG8swQDmH4IC9wKAY=");
byte[] decryptedBytes = cipher.doFinal(decodedEnc);
String decryptedText = new String(decryptedBytes);
this.t1.setText(decryptedText);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

与挑战4不同,这里的MainActivity不能直接new创建,因为MainActivity需要context,需要系统的ActivityManager对其进行生命周期管理,而挑战4中的Check类不需要。

参考下面两篇文章:

[干货]让你彻底搞懂 Context 到底是什么,如果没弄明白,还怎么做 Android 开发?

android Activity 可以new吗

思路:利用Java.chooseAPI获取上下文中的MainActivity实例,调用其flag方法。API用法参考javascript-api-frida文档

  • Java.choose(className, callbacks): 在运行时枚举指定Java类(作为第一个参数提供)的实例。

    enumerate live instances of the className class by scanning the Java heap, where callbacks is an object specifying:

    • onMatch(instance): called with each live instance found with a ready-to-use instance just as if you would have called Java.cast() with a raw handle to this particular instance.

      This function may return the string stop to cancel the enumeration early.

    • onComplete(): called when all instances have been enumerated。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import frida,sys

PACKAGE = "Frida 0x5"
jsCode = """
Java.perform(function(){
Java.choose('com.ad2001.frida0x5.MainActivity',{
onMatch: function(instance){
instance.flag(1337)
},
onComplete: function(){
}
})
})
"""

def on_message(message, data):
print(message)

process = frida.get_usb_device().attach(PACKAGE)
script = process.create_script(jsCode)
script.on('message', on_message)
script.load()
sys.stdin.read()

7

挑战0x6:使用对象参数调用方法
8

MainActivity类有个没用到的get_flag非静态方法,要传入Checker类的实例A作为参数,当A.num1==1234且A.num2==4321时显示flag

9

Checker类

10

思路:获取Checker类,new创建实例,设置num1和num2。使用java.choose获取MainActivity的实例,调用get_flag方法并传入所创建的Checker实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import frida,sys

PACKAGE = "Frida 0x6"
jsCode = """
Java.perform(function () {
let Checker = Java.use('com.ad2001.frida0x6.Checker')
let Checker_Obj = Checker.$new()
Checker_Obj.num1.value = 1234
Checker_Obj.num2.value = 4321
Java.choose('com.ad2001.frida0x6.MainActivity',{
onMatch: function(instance){
instance.get_flag(Checker_Obj)
},
onComplete:function(){
}
})
})
"""

def on_message(message, data):
print(message)

process = frida.get_usb_device().attach(PACKAGE)
script = process.create_script(jsCode)
script.on('message', on_message)
script.load()
sys.stdin.read()

11

挑战0x7:hook构造函数
12

Checker有个构造函数,可以控制类中num1和num2变量的值

13

MainActivity的OnCreate启动方法中向flag方法传入Checker实例,num1=123,num2=321。

flag方法:如果传入的Checker实例中num1和num2都>512则显示flag。

14

思路:Java.use获取Checker类,hook其构造函数,使两个参数为大于512的固定值,hook后创建实例。Java.choose获取MainActivity实例,以新建的Checker实例为参数调用flag方法。(或者采用spawn模式,重启APP,后让MainActivity创建被hook的Checker类实例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import frida,sys

PACKAGE = "Frida 0x7"
jsCode = """
Java.perform(function () {
let Checker = Java.use("com.ad2001.frida0x7.Checker");
console.log('hook $init()')
Checker.$init.overload('int','int').implementation = function (a,b){
console.log("Origin i and i2 = ",a,b);
return this.$init(521,521);
}
let obj = Checker.$new(1,2);
Java.choose('com.ad2001.frida0x7.MainActivity',{
onMatch: function(instance){
instance.flag(obj)
},
onComplete: function(){
}
})
})
"""

def on_message(message, data):
print(message)

process = frida.get_usb_device().attach(PACKAGE)
script = process.create_script(jsCode)
script.on('message', on_message)
script.load()
sys.stdin.read()

15

挑战0x8:native hook简介
16

输入的字符串作为cmpstr方法的参数,返回1则显示flag。cmpstr方法的实现位于native层。

17

s1是java层传过来的输入,s2每位减1后等于FRIDA{NATIVE_LAND},s1==s2时返回1,否则返回0。同时会在日志中输出s1和解密后的s2

18

这里的native层hook需要以下API:

  • Interceptor.attach:将回调函数附加到指定的函数地址。targetAddress 应该是我们想要挂钩的本地函数的地址。
  • onEnter:当挂钩的函数被调用时,调用此回调。它提供对函数参数 (args) 的访问。
  • onLeave:当挂钩的函数即将退出时,调用此回调。它提供对返回值 (retval) 的访问。

在hook前需要先获取函数地址、函数的导入导出库、库基址等信息,可以使用以下API:

  • Module.enumerateImports(libname):查看库的导入表项信息,包括:函数(表项)名、函数(表项)地址、函数(表项)的导出库名称等。

  • Module.enumerateExports(libname:查看库的导出表项信息

  • Module.getExportByName(libname,funcname)
    :以导出库名称导出函数(表项)名称为参数,获取对应的函数(表项)地址

  • Module.findExportByName(libname,funcname)
    “与Module.getExportByName()相同。唯一的区别在于,如果未找到导出项,Module.getExportByName() 会引发异常,而 Module.findExportByName() 如果未找到导出项则返回 null

  • Module.getBaseAddress(libname):获取库(模块)基址。

方法论如下:

19

该挑战中没有去除符号,所以可以枚举libfrida0x8.so的导入表,获取导入表项信息,其中包括导入函数的名称、所在库和函数地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import frida,sys

PACKAGE = "Frida 0x8"
jsCode = """
var imports = Module.enumerateImports("libfrida0x8.so");
for(var i=0;i < imports.length;i++){
console.log(JSON.stringify(imports[i]));
console.log('-----------------------------------------------------------------');
}
"""

def on_message(message, data):
print(message)

process = frida.get_usb_device().attach(PACKAGE)
script = process.create_script(jsCode)
script.on('message', on_message)
script.load()
sys.stdin.read()

20

思路1:hook native层的strcmp函数,打印其第二个参数,同时对第一个参数加以限制防止打印不相关的strcmp调用。strcmp函数导出自libc.so。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import frida,sys

PACKAGE = "Frida 0x8"
jsCode = """
var targetAddress = Module.findExportByName("libc.so","strcmp");
console.log("Strcmp Address: ",targetAddress.toString(16));
Interceptor.attach(targetAddress,{
onEnter:function (args){
var input = Memory.readUtf8String(args[0]);
if (input.includes("111")){
console.log(Memory.readUtf8String(args[1]));
}
},onLeave:function(retval){
}
})
console.log("success!");
"""

def on_message(message, data):
print(message)

process = frida.get_usb_device().attach(PACKAGE)
script = process.create_script(jsCode)
script.on('message', on_message)
script.load()
sys.stdin.read()

APP中输入111后在PC端得到flag

21

思路2:hook native层__android_log_print函数,打印其参数。该函数导出自liblog.so

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import frida,sys

PACKAGE = "Frida 0x8"
jsCode = """
var targetAddress = Module.findExportByName("liblog.so","__android_log_print");
console.log("__android_log_print : ",targetAddress.toString(16));
Interceptor.attach(targetAddress,{
onEnter:function (args){
console.log(Memory.readUtf8String(args[3]));
},onLeave:function(retval){
}
})
console.log("success!");
"""

def on_message(message, data):
print(message)

process = frida.get_usb_device().attach(PACKAGE)
script = process.create_script(jsCode)
script.on('message', on_message)
script.load()
sys.stdin.read()

22

思路3:用adb logcat命令查看日志。

23

挑战0x9:更改native函数的返回值
24

点击按钮后,如果native层方法check_flag返回值为1337,则输出flag。

25

check_flag返回1

26

思路:hook check_flag方法的返回值为1337.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import frida,sys

PACKAGE = "Frida 0x9"
jsCode = """
var targetAddress = Module.findExportByName("liba0x9.so","Java_com_ad2001_a0x9_MainActivity_check_1flag");
console.log("Java_com_ad2001_a0x9_MainActivity_check_1flag : ",targetAddress);
Interceptor.attach(targetAddress,{
onEnter:function (args){
},onLeave:function(retval){
retval.replace(1337);
console.log("success!");
}
})
"""

def on_message(message, data):
print(message)

process = frida.get_usb_device().attach(PACKAGE)
script = process.create_script(jsCode)
script.on('message', on_message)
script.load()
sys.stdin.read()

hook后点击按钮即可

27

28

挑战0xA:主动调用未被调用的native方法
29

导入了frida0xa库,activityMainBinding.sampleText.setText(stringFromJNI())stringFromJNI()返回的字符串设置为sampleText视图的文本内容。

30

native层的frida0xa库中除了stringFromJNI方法,还有一个没用到的get_flag方法,两个参数相加等于3时通过__android_log_print函数在日志输出flag

31

思路:主动调用get_flag方法,然后通过adb logcat命令查看日志,查看前先adb logcat -c清空日志。

遍历导出表项找get_flag方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 import frida,sys

PACKAGE = "Frida 0xa"
jsCode = """
var imports = Module.enumerateExports("libfrida0xa.so");
for(var i=0;i < imports.length;i++){
console.log(JSON.stringify(imports[i]));
console.log('-----------------------------------------------------------------');
}
"""

def on_message(message, data):
print(message)

process = frida.get_usb_device().attach(PACKAGE)
script = process.create_script(jsCode)
script.on('message', on_message)
script.load()
sys.stdin.read()

发现get_flag方法的全名是_Z8get_flagii

32

进行主动调用,这里要用到NativePointerNativeFunction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 import frida,sys

PACKAGE = "Frida 0xa"
jsCode = """
var targetAddress = Module.findExportByName("libfrida0xa.so","_Z8get_flagii");
console.log("_Z8get_flagii : ",targetAddress);
let func_pointer = new NativePointer(targetAddress);
let get_flag = new NativeFunction(func_pointer,'int',['int','int']);
get_flag(1,2);
"""

def on_message(message, data):
print(message)

process = frida.get_usb_device().attach(PACKAGE)
script = process.create_script(jsCode)
script.on('message', on_message)
script.load()
sys.stdin.read()

adb logcat实时查看日志,再主动调用,日志中看到flag

33

挑战0xB:使用X86Writer和ARM64Writer来Patch指令
34

按按钮时调用frida0xb库中的getFlag方法

35

看getFlag函数的伪c代码,什么都没有

36

看汇编,有个永假的jnz分支,导致直接跳转到了函数末尾

37

将jnz patch为jz后看伪c,正常流程是解密flag密文并输出到日志

38

思路:用X86WriterARM64WriterAPI将jnz指令patch为jz指令

X86Writer模板,Arm64Writer模板只要把X86改成Arm64

1
2
3
4
5
6
7
8
9
10
11
var writer = new X86Writer(patchaddr); //指定要修改的指令地址,创建一个X86Writer类的实例
Memory.protect(patchaddr, 0x1000, "rwx"); //修改段权限
try {
//在try块内,我们可以插入要修改/添加的x86指令。X86Writer实例提供了各种方法来插入各种x86指令。我们可以查阅文档以了解详情。
writer.flush();//插入指令后,调用flush方法将更改应用到内存中。这确保修改后的指令被写入内存位置。

} finally {
// 释放X86Writer以释放资源
writer.dispose();//调用dispose方法释放与X86Writer实例关联的资源

}

X86WriterAPI文档

Arm64WriterAPI文档

x86的库,我们要patch的位置相对模块基址偏移是170CE,可以nop,也可以把jnz修改为jz。

39

arm64的库,同样存在花指令,要把0x15248的指令NOP掉

40

查看目标进程架构,发现是arm64,因此采用Arm64Writer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import frida,sys

PACKAGE = "Frida 0xB"
jsCode = """
console.log(Process.arch)
"""

def on_message(message, data):
print(message)

process = frida.get_usb_device().attach(PACKAGE)
script = process.create_script(jsCode)
script.on('message', on_message)
script.load()
sys.stdin.read()

写脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import frida,sys

PACKAGE = "Frida 0xB"
jsCode = """
let libbase = Module.getBaseAddress("libfrida0xb.so");
console.log("libbase : ",libbase);
let patchAddr = libbase.add(0x15248);
Memory.protect(patchAddr,0x1000,"rwx");
let writer = new Arm64Writer(patchAddr);
try{
writer.putNop();
//writer.putJccShort("jz",0x171A6,"Patch Success!");
writer.flush();
}finally{
writer.dispose();
}
"""

def on_message(message, data):
print(message)

process = frida.get_usb_device().attach(PACKAGE)
script = process.create_script(jsCode)
script.on('message', on_message)
script.load()
sys.stdin.read()

查看日志得到flag

41

其它相关资料

Frida-Hook-Native层操作大全

安卓逆向-SO层相关HOOK

FRIDA-API使用篇:Java、Interceptor、NativePointer(Function/Callback)使用方法及示例

frida常用检测点及其原理–一把梭方案


Frida_Labs笔记
https://lkliki.github.io/2025/03/08/Frida-Labs笔记/
作者
0P1N
发布于
2025年3月8日
许可协议