>>> adb shell
>>> su
>>> cd /data/local/tmp
>>> ./frida
>>> adb forward tcp:27042 tcp:27042
>>> adb forward tcp:27043 tcp:27043
run hook script
>>> -o xx.txt hook保存日志到本地txt
①运行ps查看手机端进程列表
frida-ps -R
②附加某个进程
frida -R com.demo.fridahook
③-> 写脚本
这种模式重启app, 停在了开头 需要输入 %resume 继续
frida -U -f com.gdufs.xman -l hook_saveSN.js
命令行附加应用进程然后敲代码进程注入
frida -U -f com.demo.fridahook
附加模式 (attach)
应用不会重启 从执行命令起注入
frida -U -l hook.js 15.2.2版不要写包名 要写应用名称
frida -UF -l hook.js 这就可以了 后面不用写名称
重启一个Android进程并注入脚本
frida -U -l myhook.js -f com.xxx.xxxx --no-pause
spawn 模式 15.2.2版本不可以这样写
frida -Uf com.xxx.xxxx -l hook.js
①查看现在的进程
frida-ps -R
②用pid附加上去 可以实时在js文件中修改脚本 控制台会输出错误语法的日志 不用管
frida -U -p [PID] -l hook_saveSN.js
adb无线连接手机(不需要在同一wifi下) 校园网无效
>>> adb tcpip 5555
>>>adb connect 192.168.100.20
找进程名和pid
# 枚举所有的进程
processes = rdev.enumerate_processes()
for process in processes:
print(process)
# 获取在前台运行的APP
# Application(identifier="com.che168.autotradercloud", name="车智赢+", pid=3539, parameters={})
front_app = rdev.get_frontmost_application()
print(front_app)
搜索tips
看Node, 如果不是该app相关包可以无视
treeMap.put(xxx)
search
sign "sign" &sign= &sign sign=
搜索同一请求的其它有个性的关键字
搜索独有信息
网址 拿后缀搜 retrofit发送的请求,找reportClick 查找用例,或直接搜索"reportClick"
hook拦截器/TreeMap/StringBuilder定位
猜
如果是加密的数据,看它长得像哪种加密方式,hook验证,打印调用栈追踪
有用的网址
https://1024tools.com/hash 各种加密 方便查看
https://curlconverter.com/ curl转其它语言
python TOOLS
端口转发一键运行
import subprocess
# 重新连接手机需要运行
subprocess.getoutput("adb forward tcp:27042 tcp:27042")
subprocess.getoutput("adb forward tcp:27043 tcp:27043")
切割?后的东西
形如 aa=11&bb=22&cc=33...xx=xx&xxx=xxx
param_string = input(">>>")
import json
data_dict = {item.split('=')[0]: item.split('=')[1] for item in param_string.split('&')}
data_string = json.dumps(data_dict, indent=4)
print(data_string)
with open("output.txt", mode="w") as f:
f.write(data_string)
java字节数组(byte)转字符串
byte_list = [97,110,99,104,111,114,82,101,112,108,121,73,100,48,99,111,110,116,101,110,116,73,100,56,56,57,49,55,52,53,48,99,111,110,116,101,110,116,84,121,112,101,49,108,97,115,116,73,100,108,105,109,105,116,50,48,108,111,103,105,110,84,111,107,101,110,112,108,97,116,102,111,114,109,97,110,100,114,111,105,100,115,111,117,114,99,101,116,105,109,101,115,116,97,109,112,49,54,54,55,57,57,57,48,52,52,57,49,56,117,117,105,100,48,100,57,54,56,54,97,55,56,101,50,97,97,57,55,53,118,53,46,48,46,54]
def byte2str(byte_list):
data = bytearray()
for i in byte_list:
data.append(i)
data_string = data.decode('utf-8')
print(data_string)
byte2str(byte_list)
# anchorReplyId0contentId88917450contentType1lastIdlimit20loginTokenplatformandroidsourcetimestamp1667999044918uuid0d9686a78e2aa975v5.0.6
java字节数组(有符号) -> python字节数组(无符号)
byte_list = [47,-38,-99,34,-13,44,-43,-119,3,76,8,32,47,-115,105,61,-91,-46 ...]
bs = []
for item in byte_list:
if item
字节 -> 十六进制(Hex)字符串
bytes_data = b'\x9f\x1bVbf\x12\xa73\x91\xe5\x90\xb3fN\xe6\xfb'
# 去除前面的0x 不满两位补0
result = "".join([hex(item)[2:].rjust(2, "0") for item in bytes_data])
print(result)
字节数组 -> 十六进制(Hex)字符串
byte_list = [47, 218, 157, 34, 243, 44, 213, 137, 3, 76, 8, 32, 47, 141, 105, 61, 165, 210] # 注意java的要先处理成python的
print([hex(ele)[2:] for ele in byte_list])
随机生成mac地址
def create_random_mac(sep=":"):
""" 随机生成mac地址 """
data_list = []
for i in range(1, 7):
part = "".join(random.sample("0123456789ABCDEF", 2))
data_list.append(part)
mac = sep.join(data_list)
return mac
字符串 -> 字节
data_string = "啊吧啊吧"
data_string.encode('utf-8')
字典排序后拼接成x=x&xx=xx
data_dict = {'x':'x', "xx":"xx"}
ordered_string = "&".join(["{}={}".format(key, data_dict[key]) for key in sorted(data_dict.keys())])
python连接frida-server的方式
usb连接
# 获取设备信息-----------------------
rdev = frida.get_remote_device()
session = rdev.attach("抖音短视频")
# ---------------------------------
端口连接
>>> ./frida -l 0.0.0.0:8888
device = frida.get_device_manager().add_remote_device("192.168.x.x:8888") # 手机ip
session = device.attach("抖音短视频") # 包名或名字 attach附加模式 不用重启app
pid = device.spawn(["com.xx.xx"]) # 包名或名字 spawn模式 重启app
session = device.attach(pid)
wifi连接
先连着usb线 连接玩后可以断开
>>> adb tcpip 5555
>>> adb connect 192.168.100.20 手机i
模板
import frida
import sys
# ---------------------------------
rdev = frida.get_remote_device()
pid = rdev.spawn(["com.xx.xx"])
session = rdev.attach(pid)
# ---------------------------------
scr = """
Java.perform(function () {
var ClassName = Java.use('com.xxx.xx.ClassName');
ClassName.Method.implementation = function(arg1, arg2, ...) {
result = this.Method(arg1, arg2, ...);
return result;
}
})
"""
script = session.create_script(scr)
def on_message(message, data):
print(message, data)
script.on("message", on_message)
script.load()
rdev.resume(pid) # spawn
sys.stdin.read() # 程序阻塞 不让停止
frida rpc 主动调用
在"""里加载
格式一
import frida
def get_frida_rpc_script():
rdev = frida.get_remote_device()
session = rdev.attach("猿人学2022")
scr = """
function invokeSign(data){
var result;
Java.perform(function () {
Java.choose("com.yuanrenxue.match2022.security.Sign",{
onMatch:function(ins){ // 实例化对象 可能需要刷新一下手机页面加载对象
console.log("ins=>",ins);
result = ins.sign(stringToByte(data));
},onComplete(){}
});
})
return result;
}
rpc.exports = {
invokesignn:invokeSign,
}
"""
script = session.create_script(scr)
script.load()
return script
# 调用
script = get_frida_rpc_script()
sign = script.exports.invokesignn(sb) # exports.后面的名字必须和上面exports{}键的一样 不支持下划线_
格式二
import frida
def get_frida_rpc_script():
rdev = frida.get_remote_device()
session = rdev.attach("抖音短视频")
scr = """
rpc.exports = {
ttencrypt:function(bArr,len){
var res;
Java.perform(function () {
......
return res;
},
execandleviathan: function (i2,str){
var result;
Java.perform(function () {
......
});
return result;
}
}
"""
script = session.create_script(scr)
script.load()
return script
# 调用
script = get_frida_rpc_script()
gorgon_byte_list = script.exports.execandleviathan(khronos, un_sign_string)
读取文件加载
import frida
# 不知道干什么用的
def my_message_handler(message, payload):
print("message=>", message)
print("payloa=>d", payload)
# connect wifiadb
device = frida.get_device_manager().add_remote_device("192.168.43.71:8888") # 手机ip
print('设备=>', device)
session = device.attach("com.yuanrenxue.match2022")
print('session=>', session)
# load script
with open("app.js") as f: # app.js见格式一的s
script = session.create_script(f.read())
script.on("message", my_message_handler) # 调用错误处理
script.load()
print(script.exports.invokesign('page=' + data['page'] + data['t'])) # 调用
发送请求的格式
什么时候用json
# 请求头content-type: "application/json" json=json.
requests.post(url, headers=headers, data=data_dict)
treemap有序 在python里怎么处理
# 无序 -> hashmap
data_dict = {
"_appid": "atc.android",
"appversion": "2.8.2",
"channelid": "csy",
"pwd": md5(passwrod),
"udid": udid,
"username": username
}
result = "".join(["{}{}".format(key, data_dict[key]) for key in sorted(data_dict.keys())])
常见加密
python
3DES(对称)
def des3(data_string):
BS = 8
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
# 3DES的MODE_CBC模式下只有前24位有意义
key = b'appapiche168comappapiche168comap'[0:24]
iv = b'appapich'
plaintext = pad(data_string).encode("utf-8")
# 使用MODE_CBC创建cipher
cipher = DES3.new(key, DES3.MODE_CBC, iv)
result = cipher.encrypt(plaintext)
return base64.b64encode(result).decode('utf-8')
AES
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import base64
def aes_encrypt(data_string, key):
aes = AES.new(
key=key.encode('utf-8'),
mode=AES.MODE_ECB,
)
raw = pad(data_string.encode('utf-8'), 16)
return aes.encrypt(raw)
data_string = "明文"
key = "key"
bytes_data = aes_encrypt(data_string, key) # 字节类型
""" bytes_data
b'\x9f\x1bVbf\x12\xa73\x91\xe5\x90\xb3fN\xe6\xfb'
"""
value = base64.encodebytes(bytes_data)
result = value.replace(b"\n", b'') # python得到的结果会有\n, java的没有 要注意对比
print(result)
sha256
import hashlib
data = "明文"
salt = "9cafa6466a028bfb" # 盐
obj = hashlib.sha256()
# 按app的顺序update
obj.update(data.encode('utf-8'))
obj.update(salt.encode('utf-8'))
# --------------------------
res = obj.hexdigest()
print(res)
# e61583f49efa13187b053d2ab1cf2cc8cd99360367f42a6b7d013a49de72108e
md5
def md5(data_string):
obj = hashlib.md5()
obj.update(data_string.encode("utf-8"))
hex_string = obj.hexdigest()
# print(hex_string)
return hex_string
java
frida常用脚本
注:用命令行运行的方式貌似不能同时两个脚本运行。。。想要两个脚本同时运行,其中一个用python,另一个用命令行即可;或者都用python执行
hook
打印用到的so文件
使用场景:反调试 看看到了哪里被检测 找到so文件 删掉试试 没大碍就ok 如果不行另寻他路
Java.perform(function () {
var dlopen = Module.findExportByName(null, "dlopen"); // 系统文件
var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
Interceptor.attach(dlopen, {
onEnter: function (args) {
var path_ptr = args[0];
var path = ptr(path_ptr).readCString();
console.log("[dlopen:]", path);
},
onLeave: function (retval) {
}
});
Interceptor.attach(android_dlopen_ext, {
onEnter: function (args) {
var path_ptr = args[0];
var path = ptr(path_ptr).readCString();
console.log("[dlopen_ext:]", path);
},
onLeave: function (retval) {
}
});
});
// spawn mode
// frida -Uf com.xxx.xxxx -l hook.js 15.2.2版本不可使用
// frida -U -l hook.js -f com.xxx.xxxx --no-pause 用这个代替
获得调用栈
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
hook拦截器
使用 JSON.stringify(inter) 把对象是什么打印出来 可以知道是哪些类实例化了拦截器
Java.perform(function () {
var Builder = Java.use('okhttp3.OkHttpClient$Builder');
Builder.addInterceptor.implementation = function (inter) {
//console.log("实例化:");
console.log(JSON.stringify(inter));
//console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
return this.addInterceptor(inter);
};
})
// spawn 不要用 attach Interceptor可能在app启动的时候加载
// frida -U -l hook.js -f com.xxx.xxxx --no-pause
// frida -Uf com.hupu.shihuo -l hook.js -o all_interceptor.txt 15.2.2版本不可用
输出 eg. 从下往上找 记得手机上点击触发
...
""
""
Java.perform(function () {
// ... 根据上面的输出 找到为止
var a9 = Java.use('cn.shihuo.modulelib.startup.core.c.b');
a9.intercept.implementation = function (chain) {
var request = chain.request();
var urlString = request.url().toString();
if(urlString.indexOf("https://sh-gateway.shihuo.cn/v4/services/sh-goodsapi/app_swoole_zone/getAttributes/v")!= -1){ // 过滤目标url
console.log("拦截器9-->", urlString);
}
var response = chain.proceed(request); // 不执行当前拦截器 而走下一个拦截器
return response;
};
var a10 = Java.use('cn.shihuo.modulelib.startup.core.c.a');
a10.intercept.implementation = function (chain) {
//console.log("拦截器10", chain);
var request = chain.request();
var urlString = request.url().toString();
if(urlString.indexOf("https://sh-gateway.shihuo.cn/v4/services/sh-goodsapi/app_swoole_zone/getAttributes/v") != -1){
console.log("拦截器10-->", urlString);
}
//console.log("拦截器",this.b.value);
var res = this.intercept(chain);
//console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
return res;
}
})
// frida -UF -l hook.js
hook TreeMap
Java.perform(function () {
var TreeMap = Java.use('java.util.TreeMap');
var Map = Java.use("java.util.Map");
TreeMap.put.implementation = function (key,value) {
if(key=="xxx"){ // 根据需要 看抓包
console.log(key,value);
}
var res = this.put(key,value);
return res;
}
});
hook StringBuilder
Java.perform(function () {
var StringBuilder = Java.use("java.lang.StringBuilder");
StringBuilder.toString.implementation = function () {
var res = this.toString();
console.log(res);
return res;
}
});
hook Base64
Java.perform(function () {
var Base64 = Java.use("android.util.Base64");
Base64.encodeToString.overload('[B', 'int').implementation = function (bArr,val) {
var res = this.encodeToString(bArr,val);
console.log("加密了-->",res);
// console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
return res;
}
});
hook构造方法
import javax.crypto.spec.SecretKeySpec;
...
public final class b {
...
public final byte[] a(String body) {
...
SecretKeySpec secretKeySpec = new SecretKeySpec(bytes, "AES"); // hook
}
}
Java.perform(function () {
var ByteString = Java.use('com.android.okhttp.okio.ByteString');
var SecretKeySpec = Java.use('javax.crypto.spec.SecretKeySpec');
SecretKeySpec.$init.implementation = function (key, name) {
console.lot("key->", ByteString.of(key).utf8());
var res = this.$init(key, name);
return res;
}
})
类型转换输出
map转字符串输出
Java.perform(function () {
let RequestUtils = Java.use("com.shizhuang.duapp.common.utils.RequestUtils");
RequestUtils["c"].implementation = function (map, j2) {
// 输出参数类型
console.log(" type of map ->", JSON.stringify(map));
/*
* ""
* Map 是它的父类泛指子类类型 而真实类型是 HashMap
* */
let Map = Java.use('java.util.HashMap');
let obj = Java.cast(map, Map); // 类型转换
console.log('c is called' + ', ' + 'map: ' + obj + ', ' + 'j2: ' + j2); // obj.soString()
let ret = this.c(map, j2);
console.log('c ret value is ' + ret);
return ret;
};
})
字节数组转十六进制字符串
Java.perform(function () {
let d = Java.use("tv.danmaku.biliplayerimpl.report.heartbeat.d");
var ByteString = Java.use('com.android.okhttp.okio.ByteString'); // 加上这个
d["H7"].implementation = function (arg1, ...) {
let ret = this.H7(arg1, ...);
console.log('H7 ret value is ' + ret); // ret: [79,-90,...]
console.log('H7 ret HEX value is ' + ByteString.of(ret).hex()); // 加上这个
return ret;
};
})
字节转成字符串输出(可以转的前提下)
var ByteString = Java.use('com.android.okhttp.okio.ByteString'); // 加上这个
... var res = this.xxx(...)
console.log(ByteString.of(res).utf8());
输出某东西(bVar)是哪个类
Json.stringfy(bVar)输出如下 console.log(bVar)默认输出[object Object]
bVar泛指这个类型
""