本文为看雪论坛优秀文章
看雪论坛作者ID:DMemory
TypeScript 开发环境
JavaScript的一个超集,扩展了JavaScript的语法。
加强代码可读性。明确参数类型,代码语义更清晰易懂。
更友好、更精准的代码补全提示。
更贴近面向对象编程的编写习惯,利于模块化和复用。
以前用 js 写的脚本也可以被 ts 直接引用,不会浪费。
$ git clone git://github.com/oleavr/frida-agent-example.git
$ cd frida-agent-example/
$ npm install
实际上就是利用 frida-compile 编译脚本
$ npm run watch
# 或者
$ frida-compile agent/index.ts -o _agent.js -w
$ frida -U -f com.example.android --no-pause -l _agent.js
JS单步调试
frida -l </Users/name/path/test.js> --debug --runtime=v8 <port/name>
session = dev.attach(app.pid)
script = session.create_script(jscode, runtime="v8")
session.enable_debugger()
Chrome Inspector server listening on port 9229
用 Chrome 调试支持的更为顺滑,调试脚本自动重加载,断点也能正确响应。
用 PyCharm 调试断点有时需要手动激活有点麻烦,纯粹是个人偏爱PyCharm 的Debug 窗口和快捷键。
PyCharm 使用 ts 环境,调试时可以直接在 ts 文件上下断,也不需要手动激活断点,比较顺畅。
FridaContainer 脚本集分享
static anti_fgets() {
const tag = 'anti_fgets';
const fgetsPtr = Module.findExportByName(null, 'fgets');
DMLog.i(Anti.tag, 'fgets addr: ' + fgetsPtr);
if (null == fgetsPtr) {
return;
}
var fgets = new NativeFunction(fgetsPtr, 'pointer', ['pointer', 'int', 'pointer']);
Interceptor.replace(fgetsPtr, new NativeCallback(function (buffer, size, fp) {
if (null == this) {
return 0;
}
var logTag = null;
// 进入时先记录现场
const lr = FCCommon.getLR(this.context);
// 读取原 buffer
var retval = fgets(buffer, size, fp);
var bufstr = (buffer as NativePointer).readCString();
...完整版请点击左下角阅读原文
FCAnd.anti.anti_debug();
TrustManager (Android < 7)
TrustManagerImpl (Android > 7)
OkHTTPv3 (quadruple bypass)
Trustkit (triple bypass)
Appcelerator Titanium
OpenSSLSocketImpl Conscrypt
OpenSSLEngineSocketImpl Conscrypt
OpenSSLSocketImpl Apache Harmony
PhoneGap sslCertificateChecker
IBM MobileFirst pinTrustedCertificatePublicKey (double bypass)
IBM WorkLight (ancestor of MobileFirst) HostNameVerifierWithCertificatePinning (quadruple bypass)
Conscrypt CertPinManager
CWAC-Netsecurity (unofficial back-port pinner for Android<4.2) CertPinManager
Worklight Androidgap WLCertificatePinningPlugin
Netty FingerprintTrustManagerFactory
Squareup CertificatePinner [OkHTTP<v3] (double bypass)
Squareup OkHostnameVerifier [OkHTTP v3] (double bypass)
Android WebViewClient (double bypass)
Apache Cordova WebViewClient
Boye AbstractVerifier
FCAnd.anti.anti_ssl_unpinning();
FCAnd.dump_dex_common();
function anti_InMemoryDexClassLoader(callbackfunc) {
// dalvik.system.InMemoryDexClassLoader
const InMemoryDexClassLoader = Java.use('dalvik.system.InMemoryDexClassLoader');
InMemoryDexClassLoader.$init.overload('java.nio.ByteBuffer', 'java.lang.ClassLoader')
.implementation = function (buff, loader) {
this.$init(buff, loader);
var oldcl = Java.classFactory.loader;
Java.classFactory.loader = this;
callbackfunc();
Java.classFactory.loader = oldcl; // 恢复现场
}
}
FCAnd.anti.anti_InMemoryDexClassLoader(function(){
const cls = Java.use('find/same/multi/dex/class');
...
});
// 获取 so 基址
var base = Module.findBaseAddress('libxxxx.so');
// 根据偏移获取 jni 函数地址
var jnifunc_ptr = libsgmainso.add(0xE729);
// 声明 jni 函数
var jnifunc = new NativeFunction(jnifunc_ptr, 'pointer', ['pointer', 'pointer', 'int', 'pointer']);
// ********* 拼装 obj *********
// JNIEnv
var env = Java.vm.getEnv();
// 调用
var retval = jnifunc(env.handle, ptr(0), 10401, obj);
// staticVaMethod 实现 Integer.valueOf(7)
const Integer_jcls = env.findClass('java/lang/Integer');
const Integer_valueOf = env.getStaticMethodId(Integer_jcls, 'valueOf', '(I)Ljava/lang/Integer;');
const invorkeStaticOjbectMethod = env.staticVaMethod('pointer', ['int']);
var pIn2 = invorkeStaticOjbectMethod(env.handle, Integer_jcls, Integer_valueOf, 7);
// 利用 constructor | vaMethod 组装 HashMap obj
// new HashMap().put('INPUT', 'xxxxxxxxxx')
const HashMap_jcls = env.findClass('java/util/HashMap');
const invokeHashmap_constructor = env.constructor([]);
const HashMap_init = env.getMethodId(HashMap_jcls, '<init>', '()V');
var HashMap_obj = invokeHashmap_constructor(env.handle, HashMap_jcls, HashMap_init);
const HashMap_put = env.getMethodId(HashMap_jcls, 'put', '(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;');
const invokeOjbectMethod = env.vaMethod('pointer', ['pointer', 'pointer']);
invokeOjbectMethod(env.handle, HashMap_obj, HashMap_put, env.newStringUtf('INPUT'), env.newStringUtf('xxxxxxxxxx'));
// staticVaMethod 实现 Integer.valueOf(7)
const Integer_jcls = env.findClass('java/lang/Integer');
const Integer_valueOf = env.getStaticMethodId(Integer_jcls, 'valueOf', '(I)Ljava/lang/Integer;');
const invorkeStaticOjbectMethod = env.staticVaMethod('pointer', ['int']);
var pIn2 = invorkeStaticOjbectMethod(env.handle, Integer_jcls, Integer_valueOf, 7);
// 利用 constructor | vaMethod 组装 HashMap obj
// new HashMap().put('INPUT', 'xxxxxxxxxx')
const HashMap_jcls = env.findClass('java/util/HashMap');
const invokeHashmap_constructor = env.constructor([]);
const HashMap_init = env.getMethodId(HashMap_jcls, '<init>', '()V');
var HashMap_obj = invokeHashmap_constructor(env.handle, HashMap_jcls, HashMap_init);
const HashMap_put = env.getMethodId(HashMap_jcls, 'put', '(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;');
const invokeOjbectMethod = env.vaMethod('pointer', ['pointer', 'pointer']);
invokeOjbectMethod(env.handle, HashMap_obj, HashMap_put, env.newStringUtf('INPUT'), env.newStringUtf('xxxxxxxxxx'));
支持精确/模糊匹配类名
支持某类按白名单方式 trace 方法
支持匹配到指定值时收集栈信息
Java.enumerateLoadedClassesSync().forEach((curClsName, index, array) => {
dest_cls.forEach((destCls) => {
// 按规则匹配是否需要 trace
if (match(destCls, curClsName)) {
// trace 核心方法
traceArtMethodsCore(curClsName);
return false; // end forEach
}
});
});
// Hook 核心逻辑
function traceArtMethodsCore(clsname: string) {
let cls = Java.use(clsname);
// 枚举方法
let methods = cls.class.getDeclaredMethods();
methods.forEach(function (method: any) {
...
// 枚举重载
let methodOverloads = cls[methodName].overloads;
methodOverloads.forEach(function (overload: any) {
...
// Hook
overload.implementation = function () {
// ... send entry msg
// 利用 js 参数特性 arguments ,调用原函数以适配所有 Hook 方法的传参
const retval = this[methodName].apply(this, arguments);
// ... send exit msg
return retval;
}
}
}
}
// 列出 JNI 函数数组
const jni_struct_array = [
"reserved0",
"reserved1",
"reserved2",
"reserved3",
"GetVersion",
"DefineClass",
"FindClass",
"FromReflectedMethod",
...
];
// 获取 JNIEnv 地址
var env = Java.vm.getEnv();
var env_ptr = env.handle.readPointer();
// 根据函数名计算索引偏移
var offset = jni_struct_array.indexOf(func_name) * Process.pointerSize;
// 读取函数地址
jnienv_addr.add(offset).readPointer();
// Hook
Interceptor.attach(addr, callbacksOrProbe);
FCAnd.jni.hookJNI('NewStringUTF', {
onEnter: function (args) {
...
}
});
export function hook_registNatives() {
const tag = 'fridaRegstNtv';
Jni.hookJNI("RegisterNatives", {
onEnter: function (args) {
var env = Java.vm.getEnv();
var p_size = Process.pointerSize;
var methods = args[2];
var methodcount = args[3].toInt32();
// 获取类名
var name = env.getClassName(args[1]);
DMLog.i(tag, "==== class: " + name + " ====");
DMLog.i(tag, "==== methods: " + methods + " nMethods: " + methodcount + " ====");
/** 根据函数结构原型遍历动态注册信息
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
jint RegisterNatives(JNIEnv* env, jclass clazz, const JNINativeMethod* methods, jint nMethods)
*/
for (var i = 0; i < methodcount; i++) {
var idx = i * p_size * 3;
var fnPtr = methods.add(idx + p_size * 2).readPointer();
const module = Process.getModuleByAddress(fnPtr);
if (module) {
const modulename = module.name;
const modulebase = module.base;
var logstr = "name: " + methods.add(idx).readPointer().readCString()
+ ", signature: " + methods.add(idx + p_size).readPointer().readCString()
+ ", fnPtr: " + fnPtr
+ ", modulename: " + modulename + " -> base: " + modulebase;
if (null != modulebase) {
logstr += ", offset: " + fnPtr.sub(modulebase);
}
DMLog.i(tag, logstr);
}
else {
DMLog.e(tag, 'module is null');
}
}
}
});
}
==== class: com.xxxx.class.name ====
==== methods: 0xcd52d428 nMethods: 41 ====
[INFO][fridaRegstNtv]: name: initialize, signature: ()V, fnPtr: 0xcd50b6bd, modulename: libxxxx.so -> base: 0xcd505000, offset: 0x66bd
[INFO][fridaRegstNtv]: name: onExit, signature: ()V, fnPtr: 0xcd50b6c7, modulename: libxxxx.so -> base: 0xcd505000, offset: 0x66c7
[INFO][fridaRegstNtv]: name: getMMKVWithID, signature: (Ljava/lang/String;ILjava/lang/String;)J, fnPtr: 0xcd50b6d1, modulename: libxxxx.so -> base: 0xcd505000, offset: 0x66d1
[INFO][fridaRegstNtv]: name: encodeBool, signature: (JLjava/lang/String;Z)Z, fnPtr: 0xcd50b76d, modulename: libxxxx.so -> base: 0xcd505000, offset: 0x676d
[INFO][fridaRegstNtv]: name: decodeBool, signature: (JLjava/lang/String;Z)Z, fnPtr: 0xcd50b7bf, modulename: libxxxx.so -> base: 0xcd505000, offset: 0x67bf
[INFO][fridaRegstNtv]: name: encodeInt, signature: (JLjava/lang/String;I)Z, fnPtr: 0xcd50b80f, modulename: libxxxx.so -> base: 0xcd505000, offset: 0x680f
[INFO][fridaRegstNtv]: name: decodeInt, signature: (JLjava/lang/String;I)I, fnPtr: 0xcd50b85b, modulename: libxxxx.so -> base: 0xcd505000, offset: 0x685b
[INFO][fridaRegstNtv]: name: encodeLong, signature: (JLjava/lang/String;J)Z, fnPtr: 0xcd50b8a5, modulename: libxxxx.so -> base: 0xcd505000, offset: 0x68a5
[INFO][fridaRegstNtv]: name: decodeLong, signature: (JLjava/lang/String;J)J, fnPtr: 0xcd50b8f7, modulename: libxxxx.so -> base: 0xcd505000, offset: 0x68f7
[INFO][fridaRegstNtv]: name: encodeFloat, signature: (JLjava/lang/String;F)Z, fnPtr: 0xcd50b953, modulename: libxxxx.so -> base: 0xcd505000, offset: 0x6953
......
FCAnd.jni.hook_registNatives();
export function traceAllJNISimply() {
// 遍历 Hook Jni 函数
jni_struct_array.forEach(function (func_name, idx) {
if (!func_name.includes("reserved")) {
Jni.hookJNI(func_name, {
onEnter(args) {
// 触发时将信息保存到对象中
let md = new MethodData(this.context, func_name, JNI_ENV_METHODS[idx], args);
this.md = md;
},
onLeave(retval) {
// 退出时将返回值追加到对象中
this.md.setRetval(retval);
// 发送日志
send(JSON.stringify({tid: this.threadId, status: "jnitrace", data: this.md}));
}
});
}
})
}
FCAnd.jni.traceAllJNISimply();
Stalker 的应用
文件名 frida-agent**
默认端口 27042
特征字符 frida:rpc、LIBFRIDA
端口应答特征
if (connect(sock , (struct sockaddr*)&sa , sizeof sa) != -1) {
memset(res, 0 , 7);
send(sock, "\x00", 1, NULL);
send(sock, "AUTH\r\n", 6, NULL);
usleep(100); // Give it some time to answer
if ((ret = recv(sock, res, 6, MSG_DONTWAIT)) != -1) {
if (strcmp(res, "REJECT") == 0) {
__android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "FRIDA DETECTED [1] - frida server running on port %d!", i);
}
}
}
总结
看雪ID:DMemory
https://bbs.pediy.com/user-home-264470.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!