Frida坑太多了,16总是卡死设备,要注意的事项也很多,由于Frida非常不稳定,这里比较建议使用frida15.2.2版本
通常来说, 我们使用以下方法就可以正常拿到变量结果
/*
* 1.确定要Hook的函数
* 原函数:
* status_t AudioPolicyManager::setDeviceConnectionStateInt(audio_devices_t deviceType,
* audio_policy_dev_state_t state,
* const char *device_address,
* const char *device_name,
* audio_format_t encodedFormat)
*
* 2.上面类型过于复杂,这里我们直接拿IDA查看函数对应的伪代码分析
* IDA伪代码:
* // attributes: thunk
* __int64 __fastcall android::AudioPolicyManager::setDeviceConnectionStateInt(int a1, int a2, int a3, char *a4)
* {
* return _ZN7android18AudioPolicyManager27setDeviceConnectionStateIntEj24audio_policy_dev_state_tPKcS3_14audio_format_t(
* a1,
* a2,
* a3,
* a4);
* }
*
* 3.从结果看IDA的伪代码参数是不正确的,所以我们不要太相信IDA的伪代码结果,但我们大致可以推断出5个参数的类型应该是
* int, int, char*, char*, int(推测,前4个参数基本是对的),知道具体参数类型后,接下来就方便读取参数值了
*/
//1.加载so文件
console.log("Loading Module => " + "libaudiopolicymanagerdefault.so");
let soModule = Process.getModuleByName("libaudiopolicymanagerdefault.so");
//2.定位Native函数
let funcName = soModule.getExportByName(
"_ZN7android18AudioPolicyManager27setDeviceConnectionStateIntEj24audio_policy_dev_state_tPKcS3_14audio_format_t"
);
//3.Hook并打印参数
Interceptor.attach(funcName, {
onEnter: function (args) {
//要注意,目前根据测试,变量索引从1开始传递(怀疑0可能是什么对象,如jniEnv,1开始才是真正的参数)
console.log("===============setDeviceConnectionStateInt===================");
console.log("deviceType:" + args[1].readInt());
console.log("deviceState:" + args[2].readInt());
console.log("deviceAddress:" + args[3].readCString());
console.log("deviceName:" + args[4].readCString());
console.log("encodedFormat:" + args[5].readInt());
},
onLeave: function (retval) {
console.log("retval:" + retval);
},
});
不过不出意外的话,可能会出现报错,提示地址无法访问(询问GPT提示args可能不是简单的整数类型,那会不会是址类型的参数?如果是地址,我们这么直接拿不就是错的吗?试图以变量的方式访问地址)
Error: access violation accessing 0x4000000
at <anonymous> (frida/runtime/core.js:141)
at onEnter (agent/index.ts:312)
Error: access violation accessing 0x82000000
at <anonymous> (frida/runtime/core.js:141)
at onEnter (agent/index.ts:312)
试了很久,找到解决方案
//1.加载so文件
console.log("Loading Module => " + "libaudiopolicymanagerdefault.so");
let soModule = Process.getModuleByName("libaudiopolicymanagerdefault.so");
//2.定位Native函数
let funcName = soModule.getExportByName(
"_ZN7android18AudioPolicyManager27setDeviceConnectionStateIntEj24audio_policy_dev_state_tPKcS3_14audio_format_t"
);
//3.Hook并打印参数
Interceptor.attach(funcName, {
onEnter: function (args) {
//使用Memory作为中介,读取地址里的参数(目前不知道什么原因,但确实可以了)
console.log("===============setDeviceConnectionStateInt===================");
var arg1Pointer: NativePointer = Memory.alloc(4);
var arg2Pointer: NativePointer = Memory.alloc(4);
var arg5Pointer: NativePointer = Memory.alloc(4);
arg1Pointer.writePointer(args[1]);
arg2Pointer.writePointer(args[2]);
arg5Pointer.writePointer(args[5]);
console.log("deviceType:" + arg1Pointer.readInt());
console.log("deviceState:" + arg2Pointer.readInt());
console.log("device_address:" + args[3].readCString());
console.log("device_name:" + args[4].readCString());
console.log("encodedFormat:" + arg5Pointer.readInt());
},
onLeave: function (retval) {
console.log("retval:" + retval);
},
});
运行结果,测试参数完全正确
===============setDeviceConnectionStateInt===================
deviceType:-2113929216
deviceState:1
device_address:card=1;device=0;
device_name:USB-Audio - 1080P USB Camera
encodedFormat:0
retval:0x0
其它使用技巧
1.传值和替换返回值
1.需要传值到onLeave中, 我们可以在onEnter中使用this.variable = value的方式传递和访问
2.替换函数返回值retval.replace(要替换的结果)
//这是一个替换函数返回值的一个技巧
Interceptor.attach(funcAddr, {
onEnter: function (args) {
return 0;
},
onLeave: function (retval) {
const dstAddr = Java.vm.getEnv().newStringUtf("1234");
retval.replace(dstAddr);
//如果是替换bool类型的返回值,我们可以调用ptr(0x1)或者prt(0x0)之类的方式
//retval.replace(ptr(0x1));
},
});
2.Hook原Native方法,并根据参数决定是否调用原函数
已踩坑:第一个参数切记使用指针,可能是JNIEnv指针,即使你看过源码是一个空参数方法,也需要声明一个指针
//1.加载so文件
console.log("Loading Module => " + "libaudiopolicymanagerdefault.so");
let soModule = Process.getModuleByName("libaudiopolicymanagerdefault.so");
//2.定位Native函数
let funcName = soModule.getExportByName( "_ZN7android18AudioPolicyManager27setDeviceConnectionStateIntEj24audio_policy_dev_state_tPKcS3_14audio_format_t"
);
//3.根据原函数地址构建NativeFunction(这里要写在Interceptor.replace之前,留给之后调用)
//**重点,请注意第一个参数可能是一个指针,所以用pointer,不用pointer后面可能出现莫名奇妙的问题,比如地址无法访问之类的**
let nativeFunction = new NativeFunction(funcName, "int", ["pointer", "int", "int", "pointer", "pointer", "int"]);
//4.替换原函数
Interceptor.replace(
funcName,
new NativeCallback(
function (arg1: any, deviceType: any, deviceState: any, deviceAddress: any, deviceName: any, encodedFormat: any) {
console.log("===============setDeviceConnectionStateInt(Replacement)===================");
console.log("arg1:" + arg1);
console.log("deviceType:" + deviceType);
console.log("deviceState:" + deviceState);
console.log("deviceAddress:" + deviceAddress.readCString());
console.log("deviceName:" + deviceName.readCString());
console.log("encodedFormat:" + encodedFormat);
//5.根据条件,是否调用原函数
if (deviceType !== -2113929216) {
//这一段可能会报错:access violation accessing 0x835a168,如果出现这种错误,那大概率是你声明的参数类型,或参结果不正确
//(踩坑1天的经验)例如:
// 1.原方法数据类型为pointer,你声明成int并且传入了int类型测错误
// 2.要hook的方法有6个参数,但是你只声明和传入了5个(我就是这种,因为我看了原代码,明明没有参数,但却需要声明一个pointer并传入pointer的参数才行,就是上面说的,实际索引1才是我们的第1个参数值的问题)
let original = nativeFunction(arg1, deviceType, 0, deviceAddress, deviceName, 0);
console.error("原函数返回值:" + original);
return original;
}
return 0;
},
"int",
["pointer", "int", "int", "pointer", "pointer", "int"]
)
);
3.根据模块和函数名称查找函数列表
/**
* 根据模块和函数名称查找函数列表
* @param module so模块
* @param funcName 函数名称
* @returns 函数列表
*/
function findFuncByModule(module: Module, funcName: string) {
let moduleResult: any = [];
module.enumerateExports().forEach(function (exp) {
if (exp.name.indexOf(funcName) != -1) {
moduleResult.push(exp);
}
});
return moduleResult;
}
let soModule = Process.getModuleByName("libaudiopolicymanagerdefault.so");
findFuncByModule(soModule, "setDeviceConnectionState").forEach(function (exp:any) {
console.log("name:" + exp.name + " type:" + exp.type + " address:" + exp.address);
});
4.如果native方法的参数是指针类型的,获取这个指针的数据
Interceptor.replace(
setEngineDeviceConnectionStateFun,
new NativeCallback(
function (arg1: any, device: any, state: any) {
console.log("===============setEngineDeviceConnectionState(Replacement)===================");
console.log("arg1:" + arg1);
console.log("device:" + device);
console.log("state:" + state);
//device其实就是参数对象的起始地址,想要得到对象内数据,我们必须知道数据类型,以及偏移,知道了之后,我们加上起始地址的偏移就可以得到参数内容
var idOffset = parseInt(device + "", 16) + 98;
var tagOffset = parseInt(device + "", 16) + 320;
var idOffsetAddr = "0x" + "" + idOffset.toString(16);
var tagOffsetAddr = "0x" + "" + tagOffset.toString(16);
console.log("device对象地址 => " + device + ", device.id值 => " + ptr(idOffsetAddr).readUInt());
console.log("device对象地址 => " + device + ", device.deviceTag值 => " + ptr(tagOffsetAddr).readCString());
let original = setEngineDeviceConnectionState(arg1, arg2, arg3);
console.log("original:" + original);
},
"void",
["pointer", "pointer", "int"]
)
);
5.打印Java堆栈
/**
* 打印java堆栈信息
* @param showTag 显示标记
*/
function showJavaCallStack(showTag: string) {
console.log("\n\n********************>JavaStack(" + showTag + ")");
let thread = Java.use("java.lang.Thread").currentThread();
thread.getStackTrace().forEach(function (element: string) {
let lineInfo = element.toString();
if (lineInfo.indexOf(packageName) >= 0) {
console.log(lineInfo);
} else {
console.error(lineInfo);
}
});
console.log("====================>JavaStackEnd(" + showTag + ")");
console.log("\n\n");
}
6.打印Native堆栈(会导致脚本结束,目前原因未知)
/**
* 打印native堆栈信息
* @param context native堆栈上下文
* @param showTag 显示标记
*/
function showNativeCallStack(context: any, showTag: string) {
console.log("\n\n********************>NativeStack(" + showTag + ")");
Thread.backtrace(context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n");
console.log("====================>NativeStackEnd(" + showTag + ")");
console.log("\n\n");
}
//测试
Interceptor.replace(
setInputDeviceFunc,
new NativeCallback(
function (arg1: any, ioHandle: any, deviceDesc: any, boolForce: any, patchCode: any) {
//打印堆栈
showNativeCallStack(this.context, "setInputDeviceFunc");
console.log("arg1:" + arg1);
console.log("ioHandle:" + ioHandle);
console.log("deviceDesc:" + deviceDesc);
console.log("boolForce:" + boolForce);
console.log("patchCode:" + patchCode);
let result = setInputDevice(arg1, ioHandle, deviceDesc, boolForce, patchCode);
nextAudioPortGeneration(arg1);
return result;
},
"void",
["pointer", "int", "pointer", "bool", "pointer"]
)
);
7.获取java层传递object参数的值
let scanImplCls = Java.use("com.android.server.wifi.scanner.WificondScannerImpl");
let wifiNativeCls = Java.use("com.android.server.wifi.WifiNative");
scanImplCls.pollLatestScanData.implementation = function () {
console.log("pollLatestScanData");
//当我们得到一个obj类型的对象时,可以通过.value的方式拿到这个对象的实际对象何值
//比如this.mWifiNative是一个[object],但是this.mWifiNative.value却是com.android.server.wifi.WifiNative@aa9e215
let wifiNativeObj = this.mWifiNative.value;
console.log("mWifiNative => " + wifiNativeObj);
let scanResult = wifiNativeObj.getScanResults();
console.log("scanResult => " + scanResult);
let originalResult = this.pollLatestScanData.call(this);
return originalResult;
};
8.如果native函数的参数是一个结构体,如何读取?
//例如:下面的代码中Size是一个结构体
status_t Parameters::getFilteredSizes(Size limit, Vector<Size> *sizes) {
struct Size {
int32_t width;
int32_t height;
};
//情况1:这种情况得到可能不是一个结构体,可能被拆分成多个参数,我们需要按照顺序读取数据
let targetAddress3 = soModule.getExportByName("_ZN7android7camera210Parameters16getFilteredSizesENS1_4SizeEPNS_6VectorIS2_EE");
Interceptor.attach(targetAddress3, {
onEnter(args) {
console.error("onEnter================getFilteredSizes");
console.log("args[0] => " + args[0]);
console.log("width => " + args[1].toInt32());
console.log("height => " + args[2].toInt32());
},
onLeave(retval) {
console.error("onLeave => " + retval.toInt32());
}
});
9.Dump View的布局层次
/**
* dump view的层级关系
*/
function dumpViewHierarchy(tag: any, rootView : any){
dumpViewHierarchyInner(tag, rootView, false, 0);
}
/**
* dump view的层级关系
* @param tag 标记,当是递归时无效
* @param rootView 根view
* @param isRecursion 是否是递归
*/
function dumpViewHierarchyInner(tag: any, rootView : any, isRecursion: boolean, numIndex: number){
let mTag = tag;;
if(!isRecursion){
mTag = "";
console.error("\ndumpViewHierarchy>>>>>>>>>>>>>>>>>>>>>("+tag+")");
console.log("("+numIndex+")"+rootView);
}else {
mTag = mTag + "--"
console.log("("+numIndex+")"+mTag + rootView);
}
//遍历所有子View,包括递归
let viewGroupCast = Java.use("android.view.ViewGroup");
let javaCls = Java.use("java.lang.Class");
let viewGroupCls = javaCls.forName("android.view.ViewGroup");
let currentCls = javaCls.forName(rootView.$className);
if(viewGroupCls.isAssignableFrom(currentCls)){
let viewGroup = Java.cast(rootView, viewGroupCast);
let childCount = viewGroup.getChildCount();
for(let i = 0; i < childCount; i++){
let childView = viewGroup.getChildAt(i);
currentCls = javaCls.forName(childView.$className);
if(viewGroupCls.isAssignableFrom(currentCls)){
dumpViewHierarchyInner(mTag, childView, true, numIndex+1);
}else{
console.log("("+numIndex+")"+mTag+childView);
}
}
}
if(!isRecursion){
console.error("dumpViewHierarchy<<<<<<<<<<<<<<<<<<<<<"+tag+"\n");
}
}
//在需要的地方直接调用就好,需要传一个View的实例
dumpViewHierarchy("VideoActivity", decorView);
Dump结果,我们同样可以自己改造这个方法,遍历所有View
10.递归所有的View, 上一个方法的拓展
/**
* 遍历所有view
* @param rootView 根view
* @param callback 回调,自行实现回调方法onMatch(view:any)
* @param isRecursion 是否是递归
*/
function eachAllViewInner(rootView: any, callback: any, isRecursion:any){
if(!isRecursion){
callback.onMatch(rootView);
}else {
callback.onMatch(rootView);
}
//遍历所有子View,包括递归
let viewGroupCast = Java.use("android.view.ViewGroup");
let javaCls = Java.use("java.lang.Class");
let viewGroupCls = javaCls.forName("android.view.ViewGroup");
let currentCls = javaCls.forName(rootView.$className);
if(viewGroupCls.isAssignableFrom(currentCls)){
let viewGroup = Java.cast(rootView, viewGroupCast);
let childCount = viewGroup.getChildCount();
for(let i = 0; i < childCount; i++){
let childView = viewGroup.getChildAt(i);
currentCls = javaCls.forName(childView.$className);
if(viewGroupCls.isAssignableFrom(currentCls)){
eachAllViewInner(childView, callback, true);
}else{
callback.onMatch(childView);
}
}
}
}
11.打印java对象的所有字段信息和值
/**
* 打印某个对象的所有字段
* @param obj 要打印的对象
*/
function showAllFields(obj:any){
console.error("\nshowAllFields=================>"+obj);
let javaCls = Java.use("java.lang.Class");
let currentCls = javaCls.forName(obj.$className);
let fields = currentCls.getDeclaredFields();
fields.forEach(function (field:any) {
console.log("field => " + field);
let AcccessibleCls = Java.use("java.lang.reflect.AccessibleObject");
let AcccessibleObj = Java.cast(field, AcccessibleCls);
AcccessibleObj.setAccessible(true);
console.log("fieldValue => " + field.get(obj));
});
console.log("showAllFields=================<\n");
}
/**
* 获取对象的某个字段名称
* @param obj 要获取的对象
* @param fieldName 字段名称
* @returns 返回字段值
*/
function getFieldValue(obj:any, fieldName:any){
let javaCls = Java.use("java.lang.Class");
let currentCls = javaCls.forName(obj.$className);
let field = currentCls.getDeclaredField(fieldName);
let AcccessibleCls = Java.use("java.lang.reflect.AccessibleObject");
let AcccessibleObj = Java.cast(field, AcccessibleCls);
AcccessibleObj.setAccessible(true);
return field.get(obj);
}
12.打印某个对象的所有Java方法
/**
* 显示某个对象的所有方法
* @param obj 要打印的对象
*/
function showAllMethods(obj:any){
console.error("\nshowAllMethods=================>"+obj);
let javaCls = Java.use("java.lang.Class");
let currentCls = javaCls.forName(obj.$className);
let methods = currentCls.getDeclaredMethods();
methods.forEach(function (method:any) {
console.log("method => " + method);
});
console.log("showAllMethods=================<\n");
}
13.获取时间输出
function getTimeStr(){
//HH:mm:ss.SSS
let date = new Date();
let hour = date.getHours();
let minute = date.getMinutes();
let second = date.getSeconds();
let millisecond = date.getMilliseconds();
let timeStr = "";
if(hour < 10){
timeStr += "0";
}
timeStr += hour;
timeStr += ":";
if(minute < 10){
timeStr += "0";
}
timeStr += minute;
timeStr += ":";
if(second < 10){
timeStr += "0";
}
timeStr += second;
timeStr += ".";
if(millisecond < 10){
timeStr += "00";
}
else if(millisecond < 100){
timeStr += "0";
}
timeStr += millisecond;
return "["+timeStr+"]";
}
文章如有错请指教,后续学到新的知识会再继续补充...