前言

这段时间趁着项目较少,把APP编写和frida简单hook进行了一些学习,不过毕竟java底子比较薄弱,学习过程还是遇到了一些问题,在此进行一些记录

提前填坑

  • 安卓启动frida前执行setenforce 0关闭SELinux,否则服务端可能起不来

  • frida脚本运行前要关闭Xposed hook模块,否则会提示非法指令

image-20220421154707945

  • 如果刷了面具的话需要关闭MagiskHide,否则也会报错
  • 使用adb进行端口转发,frida默认端口27042,否则会报找不到APP adb forward

image-20220421164237480

image-20220421165827905

类似调试Web前端JS,frida调试APP代码是通过重载目标功能函数实现的,即调试APP中的函数,并对其进行传参调用和修改返回值等

image-20220421114357976

练习

下面用一个demo来解释下frida的hook,参考文章:https://www.cnblogs.com/luoyesiqiu/p/10718997.html

  1. 编写用于hook的简单APP,程序计算输入的分数,如果分数之和大于180则返回true,否则返回false

image-20220422144946794

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
#主函数
package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
private String TAG="Crackme";
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView =findViewById(R.id.id);

textView.append("是否及格:"+isExcellent(46,54)+"\n");
}

private boolean isExcellent(int chinese, int math){
textView.append("语文+数学总分:"+(chinese+math)+"\n");
if( chinese + math >=180){
return true;
}
else{
return false;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity" >

<TextView
android:id="@+id/id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
  1. hook isExcellent()函数,让函数返回true,即传入参数之和要大于180
1
2
3
4
5
6
7
8
setTimeout(function() {
Java.perform(function () {
var test = Java.use('com.example.myapplication.MainActivity'); //加载类
test.isExcellent.overload("int","int").implementation = function(chinese,math) { //使用overload重载方法,返回值传入参数
return this.isExcellent(100,100);
}
});
},0);
  1. 实现效果

image-20220422112403888

image-20220422112439752

应用场景

Frida hook 获取双向认证证书和密码

如果开发者使用了java.security.KeyStore来进行双向认证校验,那么通过hook该函数的load方法可获取证书文件和证书密码

脚本链接:https://gist.github.com/ceres-c/cb3b69e53713d5ad9cf6aac9b8e895d2

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#!/usr/bin/python3

'''
author: ceres-c
usage: ./frida-extract-keystore.py
Once the keystore(s) have been exported you have to convert them to PKCS12 using keytool
'''

import frida, sys, time

app_name = 'com.example.httpstest'
i = 0
ext = ''

def on_message(message, data):
global i, ext
if (message['type'] == 'send' and 'event' in message['payload']):
if (message['payload']['event'] == '+found'):
i += 1
print("\n[+] Hooked keystore" + str(i) + "...")

elif (message['payload']['event'] == '+type'):
print(" [+] Cert Type: " + ''.join(message['payload']['certType']))
if (message['payload']['certType'] == 'PKCS12'):
ext = '.jks'

elif (message['payload']['event'] == '+pass'):
print(" [+] Password: " + ''.join(message['payload']['password']))

elif (message['payload']['event'] == '+write'):
print(" [+] Writing to file: keystore" + str(i) + ext)
f = open('keystore' + str(i) + ext, 'wb')
f.write(bytes.fromhex(message['payload']['cert']))
f.close()
else:
print(message)

jscode = """
setTimeout(function() {
Java.perform(function () {
var keyStoreLoadStream = Java.use('java.security.KeyStore')['load'].overload('java.io.InputStream', '[C');
/* following function hooks to a Keystore.load(InputStream stream, char[] password) */
keyStoreLoadStream.implementation = function(stream, charArray) {
/* sometimes this happen, I have no idea why, tho... */
if (stream == null) {
/* just to avoid interfering with app's flow */
this.load(stream, charArray);
return;
}
/* just to notice the client we've hooked a KeyStore.load */
send({event: '+found'});
/* read the buffer stream to a variable */
var hexString = readStreamToHex (stream);
/* send KeyStore type to client shell */
send({event: '+type', certType: this.getType()});
/* send KeyStore password to client shell */
send({event: '+pass', password: charArray});
/* send the string representation to client shell */
send({event: '+write', cert: hexString});
/* call the original implementation of 'load' */
this.load(stream, charArray);
/* no need to return anything */
}
});
},0);
/* following function reads an InputStream and returns an ASCII char representation of it */
function readStreamToHex (stream) {
var data = [];
var byteRead = stream.read();
while (byteRead != -1)
{
data.push( ('0' + (byteRead & 0xFF).toString(16)).slice(-2) );
/* <---------------- binary to hex ---------------> */
byteRead = stream.read();
}
stream.close();
return data.join('');
}
"""

print("[.] Attaching to device...")
try:
device = frida.get_remote_device()
except:
print("[-] Can't attach. Is the device connected?")
sys.exit()

print("[.] Spawning the app...")
try:
pid = device.spawn(app_name)
device.resume(pid)
time.sleep(1)
except:
print("[-] Can't spawn the App. Is filename correct?")
sys.exit()

print("[.] Attaching to process...")
try:
process = device.attach(pid)
except:
print("[-] Can't connect to App.")
sys.exit()

print("[.] Launching js code...")
print(" (run the app until needed, close it and then kill this script)")
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
try:
sys.stdin.read()
except KeyboardInterrupt:
print ("\nExiting now")
exit(0)

这个脚本直接使用的话会有问题,提示找不到设备,需要把脚本内的get_usb_device()修改为get_remote_device(),另外Python调用的hook脚本需要使用adb进行端口转发,否则会提示找不到APP

演示:

image-20220421103712203

用hook到的密码和证书文件可以正常导入

img

总结

用好frida对测试APP很有帮助,如果APP使用了一些公共模块例如okhttp3等,那么一般来说对okhttp3配置SSL-PINNING方法是比较固定的,那我们就可以使用固定的hook脚本对相关方法进行改动,达到绕过抓包限制的目的;但如果开发者自定义了某些函数,则需要我们对APK进行反编译,定位函数并进行hook

相关链接

demo app:https://mp.weixin.qq.com/s/F9QRU4QKc-fSdxxarroNqQ

frida入门总结

frida api

Frida hook零基础教程