· 已root的安卓机(小于Android 10)
· adb —— 用于调试Android设备
· Frida —— 用于对apk内的部分验证代码进行Hook
· Burpsuite —— 用于抓包
· OpenSSL —— 用于生成证书及转换证书格式
· HTTPDecrypt —— 用于利用HTTP协议 远程加解密数据包
· Magisk —— 绕过银行app对root的检测

#Magisk绕过检测

· 设置中打开MagiskHide,并具体设置对应app
· 设置中选择隐藏Magisk应用
· 新版Magisk取消Magisk Hide功能
选择在Zygisk中运行,并在配置排除列表中选择对应APP(Zygisk和Riru冲突,但Zygisk并不能作为magisk Hide的替代,Magisk依旧会被程序检测到)
*不要升级Magisk 人会变得不幸(现版本23.0)

· Riru的安装https://github.com/RikkaApps/Riru/releases
在magisk的本地安装中刷入zip包并重启
· LSPosed的安装https://github.com/LSPosed/LSPosed/releases
在magisk的本地安装中刷入zip包并重启
安装压缩包内的manager.apk->成功激活模块
· Hide-my-applist的安装 https://github.com/Dr-TSNG/Hide-My-Applist/releases/tag/V2.2.4
安装后LSPosed处启用模块,只勾选系统框架->成功激活模块
在程序内选项下载magisk插件并给予root权限重启->系统模块运行
将LSPosed卸载并重启,根据提示添加LSPosed快捷方式到手机桌面->绕过对LSPosed的包检测
模板管理处设置黑名单,将需要root权限的软件全部勾选,在选择生效应用中选择银行app和测试app 进行检测
Tips:生效后强制停止目标软件并重新进入
· Enhanced mode for Magisk Hide的安装 https://github.com/vvb2060/riru-unshare
在magisk的本地安装中刷入zip包并重启
->至此 实现目标软件对检测的绕过

#定位加密函数

*导出apk

1
2
3
4
5
6
7
adb shell
su
pm list package|grep bank   #查看包名 返回cn.com.spdb.mobilebank.per
pm path cn.com.spdb.mobilebank.per  #查看路径名 返回/data/app/cn.com.spdb.mobilebank.per-aeANAOo92887GiJfBmTE2A==/base.apk
exit
exit
adb pull /data/app/cn.com.spdb.mobilebank.per-aeANAOo92887GiJfBmTE2A==/base.apk C:\Users\xxx\Desktop   #导出apk

*将导出的apk后缀改为zip格式解压,在assets等资源目录未找到后缀名为p12/pfx/pem等的证书文件(推测APP证书写死在代码里)

upload successful

*Apk导入JEB(邦邦加密secneo)

upload successful

*安装Frida 因为工具对版本要求 安装15及以上版本的Frida及对应Frida-tools

1
2
3
4
5
6
7
8
9
10
电脑端安装

*python版本3.7*
pip3 install frida
pip3 install frida-tools
手机端安装
adb shell
su
getprop ro.product.cpu.abi   #查看Android手机设备设置 返回arm64-v8a
frida -version   #在电脑端查看安装的frida版本 返回14.2.18

*下载对应frida-server并安装,注意版本号对应:
https://github.com/frida/frida/releases

upload successful

*下载后进行解压,解压后的文件重命名为frida-server并复制到手机

1
2
3
4
5
6
7
8
9
手机端安装
adb push firda-server /data/local/tmp #将frida-server放到手机的这个目录下
adb forward tcp:27042 tcp:27042  #转发端口
adb forward tcp:27043 tcp:27043
adb shell
su
cd /data/local/tmp
chmod 755 frida-server  #赋予可执行权限
./frida-server  #后台运行frida-server

*另起一个cmd窗口 frida成功启动

1
2
电脑端操作
frida-ps -U

upload successful

*使用脚本hook JAVA固定的加密类

1
2
3
4
5
电脑端操作
pip3 install objection
adb shell dumpsys window | findstr mCurrentFocus    #查看当前包名/activity名
frida -U -f cn.com.spdb.mobilebank.per -l  C:\Users\yinyue01\Desktop\hook.js
%resume

upload successful

upload successful

3个脚本
· hook.js:
请求-hook加/解密,在请求数据加密前send到python脚本
返回-hook加/解密,对返回数据进行解密send到python脚本
· tracer.py:开启http服务 获取返回数据
· frida_hook.py:加载脚本,获取信息,修改返回数据格式

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
hook.js
Java.perform(function x() {
var cipher = Java.use("javax.crypto.Cipher");
cipher.doFinal.overload("[B").implementation = function (x) {
var opmodeProperty = cipher.class.getDeclaredField('opmode')
opmodeProperty.setAccessible(true)
var opmode = opmodeProperty.get(this)
var ret = cipher.doFinal.overload("[B").call(this, x);
var op;
if (opmode == 1) {
op = recv(function(value){
x = stringToBytes(value);
});
send('encrypt||' + Utf8ArrayToStr(new Uint8Array(x)))
} else {
send('decrypt||' + Utf8ArrayToStr(new Uint8Array(ret)))
op = recv(function(value){
ret = stringToBytes(value);
});
}
op.wait();
return ret;
}
});

function stringToBytes(str){
var javaString = Java.use('java.lang.String');
var bytes = [];
bytes = javaString.$new(str).getBytes();
return bytes;
}

function Utf8ArrayToStr(array) {
var out, i, len, c;
var char2, char3;

out = "";
len = array.length;
i = 0;
while(i < len) {
c = array[i++];
switch(c >> 4)
{
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
// 0xxxxxxx
out += String.fromCharCode(c);
break;
case 12: case 13:
// 110x xxxx 10xx xxxx
char2 = array[i++];
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
break;
case 14:
// 1110 xxxx 10xx xxxx 10xx xxxx
char2 = array[i++];
char3 = array[i++];
out += String.fromCharCode(((c & 0x0F) << 12) |
((char2 & 0x3F) << 6) |
((char3 & 0x3F) << 0));
break;
}
}

return out;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
tracer.py
from flask import request, Flask, jsonify
import json

app = Flask(__name__)
app.config['JSON_AS_ASCII'] = False


@app.route('/test', methods=['POST'])
def post_Data():
return request.data, 201


if __name__ == '__main__':
app.run(debug=False, host='0.0.0.0', port=8888)
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
frida_hook.py
#coding=utf-8
import os
import time
import sys
import frida
import requests
import json

#打印javascript脚本返回消息
def on_message(message, data):
print(message)
if isinstance(message, dict):
data = toburp(message["payload"].encode('utf-8'))
script.post(data)
else:
if message.has_key("payload"):
print(message["payload"])
#获取设备应用名
def get_application_name(device, identifier):
for p in device.enumerate_applications():
if p.identifier == identifier:
return p.name
#获取设备进程pid
def get_process_pid(device, application_name):
for p in device.enumerate_processes():
if p.name == application_name:
return p.pid
return -1

def toburp(data):
proxies = {'http':'http://127.0.0.1:8080'}
url = 'http://127.0.0.1:8888/test'
data_chunks = data.split(b'||')
headers = {
'Content-Type': 'text/plain',
'X-DataType': data_chunks[0]
}
real_data = data[(len(data_chunks[0]) + 2):]
r=requests.post(url,data=real_data,headers=headers,proxies=proxies)
return(r.text)

def get_processes(device, process):
processes = device.enumerate_processes(scope='full')
ret = []
for proc in processes:
print(proc.pid, proc.name)
if proc.name == process:
print(proc)
ret.append(proc.pid)
return ret

def main():
#连接设备
device = frida.get_device_manager().enumerate_devices()[-1]
package_name = "cn.com.spdb.mobilebank.per"
pid = device.spawn(package_name)
'''pid = get_processes(device, package_name)[0]
if pid != -1:
print("[+] killing {0}".format(pid))
# device.kill(pid)
time.sleep(0.3)
while(1):
pid = get_process_pid(device, package_name)
print(pid)
if pid == -1:
print("[-] {0} is not found...".format(package_name))
time.sleep(2)
else:
break'''
print("[+] Injecting script to {0}({1})".format(package_name, pid))
session = None
try:
#attach目标进程
session = frida.get_device_manager().enumerate_devices()[-1].attach(pid)
#加载javaScript脚本
script_content = open("hook.js").read()
global script
script = session.create_script(script_content)
script.on("message", on_message)
script.load()
device.resume(pid)
sys.stdin.read()
except KeyboardInterrupt as e:
if session is not None:
session.detach()
device.kill(pid)
sys.exit(0)
if __name__ == "__main__":
main()