frida学习笔记

frida 学习笔记

官网

自用环境:

1
2
3
4
5
> 电脑: Mircrosoft Windows10 版本1909 (OS内部版本 18353.778)
> 终端: 雷电模拟器4 android 7.1.2 x86架构(虚拟机实际上是电脑cpu)
> frida版本: 12.8.20
> Python版本: Python 3.7.7
>

1 安装

1.1 电脑安装frida

因为博客是用的hexo,所以电脑里之前是有node.js和npm的,但是很奇怪,使用npm安装有问题,后面还是选择的python

1
2
$ py -3 -m pip install frida
$ py -3 -m pip install frida-tools

因为电脑里有两个版本的python,且新的PowerShell支持选择python版本,所以有一点微小的区别。

1.2 手机安装frida server

仓库地址

选择对应frida版本和终端型号的frida-server,下载

1
2
3
4
5
6
7
8
9
10
$ adb push frida-server /data/local/tmp
$ adb shell

$ cd /data/local/tmp
$ chmod 755 frida-server

$ ./frida-server[&] # 后面加一个&之后 表示使用后台进程

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

之后就可以愉快的玩耍了

1
$ frida-ps -U

如果能看见PID和对应的name,则说明成功安装

1
$ frida-trace -U -i open com.android.chrome

2 Tools

2.1 frida CLI

1
2
3
4
5
6
7
8
# Connect Frida to an Android over USB and start debugging APP
$ frida -U com.example.yaphetshan.tencentgreat

# ... and load helloWorld.js
$ frida -U com.example.yaphetshan.tencentgreat -l helloworld.ls

# ... and load calc.js with the debugger enabled
$ frida -U com.example.yaphetshan.tencentgreat -l helloworld.ls --debug

2.2 frida-ps

列出进程的命令行工具,在与远程系统交互时很有用

1
2
3
4
5
6
7
8
9
10
11
# Connect Frida to an Android over USB and list running processes
$ frida-ps -U

# List running applications
$ frida-ps -Ua

# List installed applications
$ frida-ps -Uai

# Connect Frida to the specific device
$ frida-ps -D 0216027d1d6d3a03

2.3 frida-trace

用于动态跟踪函数调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 具体信息使用frida-trace -h来查看,一下列举常用的参数
-U # connect to USB device


# frida 中有 {module:function} 的概念,一个module中有多个function
-I/-X # include/exclude module
-i/-x # include/exclude function
# eg: 假定想匹配所有`str*` 和`mem*` 方法
# 而在进程中,ucrtbase.dll, ntdll.dll, and msvcrt.dll.三个模块有以上的方法
# 不想要msvcet.dll中的信息, 注意顺序
-i "str*" -i "mem*" -X "msvcrt.dll"


-a # include function (offset-based)
-a "libjpeg.so!0x4793c"


-d # add module name to log tracing

2.4 frida-discover

在模拟器上运行,发现Abort message: 'Failed to resolve labels'待解决

2.5 frida-ls-devices

1
2
# 通过USB将Frida连接到远程终端并列出正在运行的进程
$ frida-ls-devices

2.6 frida-kill

This is a command-line tool for killing processes.

1
$ frida-kill -D <DEVICE-ID> <PID>

3 JavaScript API

完整文档

不一一列举了,需要的时候再来查,熟能生巧

重点关注Java:

  • java.available : 一个判断当前当前进程是否已加载Java VM(dvm,art)的bool值
  • Java.androidVersion : 获取正在运行的android 版本
  • Java.enumerateLoadedClasses(callbacks) : 枚举立即加载的类,其中callbacks是一个指定以下内容的对象:
    • onMatch : function (name, handle): 为每个已加载的类调用,其名称可以传递给use()以获取JavaScript包装器。 也可以使用Java.cast()处理java.lang.Classhandle
    • onComplete :function()当所有的类都被枚举后调用
  • Java.enumerateLoadedClassesSync(): enumerateLoadedClasses()的同步版本,该版本在数组中返回类名称。
  • Java.enumerateClassLoaders(callbacks) : 枚举Java VM中存在的类加载器,callbacks是一个对象,它指定了以下内容:
    • onMatch
    • omComplete

也可以将这样的加载器传递给Java.ClassFactory.get()以便能够在指定的类加载器上使用.use()类。

  • Java.enumerateClassLoadersSync(): enumerateClassLoaders()的同步版本,该版本在数组中返回类加载器。
  • Java.scheduleOnMainThread(fn) : 在VM的主线程上运行fn
  • Java.perform(fn) : 确保当前线程已连接到VM,并调用fn. (在Java的回调中这不是必需的。) 如果该应用的类加载器尚不可用,则将延迟调用fn。 如果不需要访问应用程序的类,请使用Java.performNow()
1
2
3
4
5
6
7
Java.perform(function () {
var Activity = Java.use('android.app.Activity');
Activity.onResume.implementation = function () {
send('onResume() got called! Let\'s call the original implementation');
this.onResume();
};
});
  • Java.use(className) : 动态获取classNameJavaScript包装器,您可以通过在其上调用$new()来调用构造函数来实例化对象。 在实例上调用$dispose()以对其进行显式清理(或等待JavaScript对象进行垃圾回收,或等待脚本卸载). 静态和非静态方法都可用,甚至可以替换方法实现并从中引发异常:

    1
    2
    3
    4
    5
    6
    7
    Java.perform(function () {
    var Activity = Java.use('android.app.Activity');
    var Exception = Java.use('java.lang.Exception');
    Activity.onResume.implementation = function () {
    throw Exception.$new('Oh noes!');
    }
    });
  • Java.openClassFile(filePath) : 在filepath中打开一个.dex文件, 使用以下方法返回对象:

    • load() 将包含的类加载到VM中。
    • getClassNames() 获取可用类名的数组
  • Java.choose(className, callbacks) 通过扫描Java堆来枚举className类的活动实例,其中callbacks是一个指定以下内容的对象:

    • onMatch:function (instance): 在每个具有可用intance的活动实例中调用 , 就像使用此特定实例的原始句柄调用Java.cast()一样
    • onComplete: function ()
  • Java.retain(obj) 复制JavaScript包装器obj,以供以后在外部替换方法中使用。

    1
    2
    3
    4
    5
    6
    7
    8
    Java.perform(function () {
    var Activity = Java.use('android.app.Activity');
    var lastActivity = null;
    Activity.onResume.implementation = function () {
    lastActivity = Java.retain(this);
    this.onResume();
    };
    });
  • Java.cast(handle, klass) : 根据Java.use()返回的给定类klasshandle,在给定现有实例的情况下创建一个JavaScript包装器。 这样的包装器还具有用于为其类获取包装器的class属性,以及用于获取其类名称的字符串表示形式的$className属性。

    1
    2
    var Activity = Java.use('android.app.Activity');
    var activity = Java.cast(ptr('0x1234'), Activity);
  • Java.array(type, elements) 从JavaScript数组元素创建具有指定类型的元素的Java数组。 生成的Java数组的行为类似于JS数组,但可以通过引用传递给Java API,以允许它们修改其内容。

    1
    2
    3
    4
    var values = Java.array('int', [ 1003, 1005, 1007 ]);

    var JString = Java.use('java.lang.String');
    var str = JString.$new(Java.array('byte', [ 0x48, 0x65, 0x69 ]));
  • Java.isMainThread() 确定调用者是否在主线程上运行。

  • Java.registerClass(spec) 创建一个新的Java类并为其返回一个包装器,其中spec是一个包含以下内容的对象:

    • name
    • superclass
    • implements
    • fields
    • methods
    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
    50
    51
    52
    var SomeBaseClass = Java.use('com.example.SomeBaseClass');
    var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');

    var MyTrustManager = Java.registerClass({
    name: 'com.example.MyTrustManager',
    implements: [X509TrustManager],
    methods: {
    checkClientTrusted: function (chain, authType) {
    },
    checkServerTrusted: function (chain, authType) {
    },
    getAcceptedIssuers: function () {
    return [];
    },
    }
    });

    var MyWeirdTrustManager = Java.registerClass({
    name: 'com.example.MyWeirdTrustManager',
    superClass: SomeBaseClass,
    implements: [X509TrustManager],
    fields: {
    description: 'java.lang.String',
    limit: 'int',
    },
    methods: {
    $init: function () {
    console.log('Constructor called');
    },
    checkClientTrusted: function (chain, authType) {
    console.log('checkClientTrusted');
    },
    checkServerTrusted: [{
    returnType: 'void',
    argumentTypes: ['[Ljava.security.cert.X509Certificate;', 'java.lang.String'],
    implementation: function (chain, authType) {
    console.log('checkServerTrusted A');
    }
    }, {
    returnType: 'java.util.List',
    argumentTypes: ['[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'java.lang.String'],
    implementation: function (chain, authType, host) {
    console.log('checkServerTrusted B');
    return null;
    }
    }],
    getAcceptedIssuers: function () {
    console.log('getAcceptedIssuers');
    return [];
    },
    }
    });
  • Java.deoptimizeEverything() 强制VM使用其解释器执行所有操作。 在某些情况下,有必要防止优化绕过方法挂钩,并允许ART的Instrumentation API用于跟踪运行时。

  • java.vm 对象具有以下方法:

    • perform(fn)
    • getEnv()
    • tryGetEnv()
  • Java.classFactory 用于实现默认类工厂eg: Java.use()。使用应用程序的主类加载器。

  • Java.ClassFactory 具有以下属性的类:

    • get(classLoader)
    • loader
    • cacheDir
    • tempFileNaming
    • use(className)
    • openClassFile(filePath)
    • choose(className, callbacks)
    • retain(obj)
    • cast(handle, klass)
    • array(type, elements)
    • registerClass(spec)

4 Example

官网例子

4.1 下载安装,查看信息

1
2
$ frida-ps -Ua
4602 rock_paper_scissors com.example.seccon2015.rock_paper_scissors

查询得到 对应的应用完整Identifier com.example.seccon2015.rock_paper_scissors

4.2 frida 脚本实例

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
# ctf.py

import frida, sys

def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)

jscode = """
Java.perform(function () {
// Function to hook is defined here
var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');

// Whenever button is clicked
var onClick = MainActivity.onClick;
onClick.implementation = function (v) {
// Show a message to know that the function got called
send('onClick');

// Call the original onClick handler
onClick.call(this, v);

// Set our values after running the original onClick handler
this.m.value = 0;
this.n.value = 1;
this.cnt.value = 999;

// Log to the console that it's done, and we should have the flag!
console.log('Done:' + JSON.stringify(this.cnt));
};
});
"""


# step1: 附加调试进程
process = frida.get_usb_device().attach('com.example.seccon2015.rock_paper_scissors')

# step2: 注入js脚本
script = process.create_script(jscode)

# step3: 获得内容
script.on('message', on_message)
print('[*] Running CTF')
script.load()
sys.stdin.read()

4.3 使用JS脚本的例子

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
50
51
Java.perform(function () {
// 创建java.lang.String的实例,并使用字符串对其进行初始化
var JavaString = Java.use('java.lang.String');
var exampleString1 = JavaString.$new('Hello World, this is an example string in Java.');
console.log('[+] exampleString1: ' + exampleString1);
console.log('[+] exampleString1.length(): ' + exampleString1.length());

// 创建java.nio.charset.Charset的实例,并初始化默认字符集
var Charset = Java.use('java.nio.charset.Charset');
var charset = Charset.defaultCharset();
// 创建一个Javascript字符串的字节数组
var charArray = 'This is a Javascript string converted to a byte array.'.split('').map(function(c) {
return c.charCodeAt(0);
});

// 创建java.lang.String的实例,并通过重载$ new对其进行初始化,
// 使用 带有字节数组和java.nio.charset.Charset的实例
var exampleString2 = JavaString.$new.overload('[B', 'java.nio.charset.Charset').call(JavaString, charArray, charset)
console.log('[+] exampleString2: ' + exampleString2);
console.log('[+] exampleString2.length(): ' + exampleString2.length());

// 拦截java.lang.Stringbuilder的重载构造函数的初始化,
// 并将部分参数写入控制台
var StringBuilder = Java.use('java.lang.StringBuilder');
// We need to replace .$init() instead of .$new(), since .$new() = .alloc() + .init()
var ctor = StringBuilder.$init.overload('java.lang.String');
ctor.implementation = function (arg) {
var partial = '';
var result = ctor.call(this, arg);
if (arg !== null) {
partial = arg.toString().replace('\n', '').slice(0, 10);
}
// console.log('new StringBuilder(java.lang.String); => ' + result);
console.log('new StringBuilder("' + partial + '");');
return result;
};
console.log('[+] new StringBuilder(java.lang.String) hooked');

// Intercept the toString() method of java.lang.StringBuilder and write its partial contents to the console.
var toString = StringBuilder.toString;
toString.implementation = function () {
var result = toString.call(this);
var partial = '';
if (result !== null) {
partial = result.toString().replace('\n', '').slice(0, 10);
}
console.log('StringBuilder.toString(); => ' + partial);
return result;
};
console.log('[+] StringBuilder.toString() hooked');
});

5 原理

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

//hook代码,采用javascript编写
jscode = """
//javascript代码,重点
"""

//自定义回调函数
def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)

#重点的4行代码
process = frida.get_usb_device().attach('应用完整包名')
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
process = frida.get_usb_device().attach('应用完整包名')

def get_usb_device(timeout = 0):
return _get_device(lambda device: device.type == 'tether', timeout)

def _get_device(predicate, timeout):
mgr = get_device_manager() //获取设备管理
def find_matching_device(): //寻找匹配设备
usb_devices = [device for device in mgr.enumerate_devices() if predicate(device)]
if len(usb_devices) > 0:
return usb_devices[0]
else:
return None
device = find_matching_device()

def get_device_manager():
global _device_manager
if _device_manager is None:
from . import core
_device_manager = core.DeviceManager(_frida.DeviceManager())
return _device_manager

跟进找到DeviceManagerenumerate_devices()返回的是一个Device()类的实例化对象的List,跟进Device()能找到attach()方法,提供完整的应用名称,找到对应的pid,然后通过pid去attach 相应的方法, 最终得到的是一个对应进程号pid的Session实例化对象process。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
script = process.create_script(jscode)

# @return 一个Script类的实例化
def create_script(self, *args, **kwargs):
return Script(self._impl.create_script(*args, **kwargs))


script.on('message', on_message)

def on(self, signal, callback):
if signal == 'message':
self._on_message_callbacks.append(callback)
else:
self._impl.on(signal, callback)

接下来调用load()方法,在服务端就启动javascript脚本了

6 使用积累

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Java.perform(function () {
// 获得一个类的包装
var classInstance = Java.use("de.fraunhofer.sit.premiumapp.LauncherActivity")

// hook 类实例的某一个方法
classInstance.functionName.implementation = function() {
...
// 执行原来的方法,在获取某一个方法的返回值的时候十分有效
this.functionName();
};

// 调用构造方法实例化一个对象
classInstance.$new();
// 调用(析构)方法回收对象
classInstance.$dispose();
});
1
2
3
4
5
6
7
8
9
10
11
Java.perform(function () {
// 枚举一个类的所有实例对象
Java.choose("de.fraunhofer.sit.premiumapp.LauncherActivity", {
"onMatch": function(instance) {
send(instance)
},
"onComplete":function(){
send("finished")
}
});
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Java.perform(function () {
// 加载所有立即加载的类
Java.enumerateLoadedClasses(
{
/**
* @param className 类的完整名称
* @param handle 类的句柄,可以使用Java.cast()操作
*/
// 每个类都会执行
"onMatch": function(className, handle){
if(className.includes("fraunhofer")){
send("className: " + className);
send("handle: " + handle)
}
},
// 在所有类都枚举完成之后执行
"onComplete":function(){
send("finish")
}
}
);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Java.perform(function () {
// 枚举所有的类加载器
Java.enumerateClassLoaders(
{
/**
* @param loader 一个java.lang.ClassLoader类实例
*/
"onMatch": function (loader) {
send(loader);
},
"onComplete": function () {
send("finish it")
}
}
)
});
1
2
3
4
5
6
7
8
9
Java.perform(function () {
var Activity = Java.use('de.fraunhofer.sit.premiumapp.LauncherActivity');
var lastActivity = null;
Activity.onResume.implementation = function () {
// duplicates the JavaScript wrapper obj for later use outside replacement method.
lastActivity = Java.retain(this);
this.onResume();
};
});
1
2
3
var Activity = Java.use('android.app.Activity');
// 类型转换
var activity = Java.cast(handle, Activity);
1
2
3
4
5
// 创建数组
var values = Java.array('int', [ 1003, 1005, 1007 ]);

var JString = Java.use('java.lang.String');
var str = JString.$new(Java.array('byte', [ 0x48, 0x65, 0x69 ]));
1
2
3
4
5
6
7
8
9
10
Java.perform(function () {
var libc = Module.findBaseAddress('libnative-lib.so');
// 查看dump出来之后的数据
console.log(hexdump(libc, {
offset: 0,
length: 64,
header: true,
ansi: true
}));
});

参考资料

[1] 官方文档

[2] 使用Frida框架进行hook

[3] FRIDA脚本系列(一)入门篇:在安卓8.1上dump蓝牙接口和实例

[4] FRIDA脚本系列(二)成长篇:动静态结合逆向WhatsApp

[5] 【技术分享】利用FRIDA攻击Android应用程序(一)

[6] 【技术分享】利用FRIDA攻击Android应用程序(二)