知识
- 网上对frida的检测通常会使用openat、open、strstr、pthread_create、snprintf、sprintf、readlinkat等一系列函数。
命令记录
# frida启动
adb shell
su
/data/local/tmp/frida-server-16.4.7-android-arm64
# 端口转发
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
# 查看应用pid
frida-ps -U
# 命令注入
frida -U -f '小黑盒' -l xhh.js
frida hook模板
Python模板
import frida
import sys
def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)
with open('./webview.js','r', encoding='UTF-8') as f:
jscode1 = f.read()
#process = frida.get_remote_device().enumerate_processes()
#print(process)
# get_usb_device改成get_remote_device方法,get_usb_device有的电脑会报错
process = frida.get_remote_device().attach('蜜雪冰城') # 'App名称', 或 App应用的 Process Pid 15325
script = process.create_script(jscode1) # 把js的hook脚本注入到进程里面
script.on('message', on_message)
print('[*] Running CTF')
script.load()
sys.stdin.read()
frida Hook调用的so的函数
function hook_dlsym() {
var count = 0
console.log("=== HOOKING dlsym ===")
var interceptor = Interceptor.attach(Module.findExportByName(null, "dlsym"),
{
onEnter: function (args) {
const name = ptr(args[1]).readCString()
// const module = Process.findModuleByAddress(ptr(this.returnAddress))
console.log("[dlsym]", name)
if (name == "pthread_create") {
count++
}
}
}
)
return Interceptor
}
function hook_dlopen() {
var interceptor = Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"),
{
onEnter: function (args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
console.log("[LOAD]", path)
if (path.indexOf("libmsaoaidsec.so") > -1) {
hook_dlsym()
}
}
},
}
)
return interceptor
}
var dlopen_interceptor = hook_dlopen()
hook so层函数模板
function hook_sub_A6854(){
Java.perform(function () {
var a45d = Module.findExportByName("libhbsecurity.so")
a45d = a45d.add(0xA6854)
Interceptor.attach(a45d,{
onEnter:function(arg){
console.log(arg[1].readCString())
},
onLeave:function(ret){
console.log(ret.readCString())
}
})
});
}
registerNative
第一种
这个函数的作用就不赘述了;因为从第三个参数能看到jni函数的映射关系,而很多加解密函数都是Java层声明、在so层实现的,所以这个函数格外重要;下面这段代码可以动态获取registerNative函数地址,并且打印第三个参数的内容:
function hook_libart() {
var module_libart = Process.findModuleByName("libart.so");
var symbols = module_libart.enumerateSymbols(); //枚举模块的符号
var addr_GetStringUTFChars = null;
var addr_FindClass = null;
var addr_GetStaticFieldID = null;
var addr_SetStaticIntField = null;
var addr_RegisterNatives = null;
for (var i = 0; i < symbols.length; i++) {
var name = symbols[i].name;
if (name.indexOf("art") >= 0) {//动态获取各个函数的地址
if ((name.indexOf("CheckJNI") == -1) && (name.indexOf("JNI") >= 0)) {
if (name.indexOf("GetStringUTFChars") >= 0) {
console.log(name);
addr_GetStringUTFChars = symbols[i].address;
} else if (name.indexOf("FindClass") >= 0) {
console.log(name);
addr_FindClass = symbols[i].address;
} else if (name.indexOf("GetStaticFieldID") >= 0) {
console.log(name);
addr_GetStaticFieldID = symbols[i].address;
} else if (name.indexOf("SetStaticIntField") >= 0) {
console.log(name);
addr_SetStaticIntField = symbols[i].address;
} else if (name.indexOf("RegisterNatives") >= 0) {
console.log(name);
addr_RegisterNatives = symbols[i].address;
}
}
}
}
if (addr_RegisterNatives) {
Interceptor.attach(addr_RegisterNatives, {
onEnter: function (args) {
console.log("addr_RegisterNatives:", hexdump(args[2])); //打印第三个参数,也就是java和native映射的数组首地址
console.log("addr_RegisterNatives name:", ptr(args[2]).readPointer().readCString())//java层函数名称
console.log("addr_RegisterNatives sig:", ptr(args[2]).add(Process.pointerSize).readPointer().readCString());//函数参数
console.log("addr_RegisterNatives addr:", ptr(args[2]).add(Process.pointerSize+Process.pointerSize));//native函数入口地址
}, onLeave: function (retval) {
}
});
}
}
注意:因为一个jni函数注册只调用一次registerNative,所以这里建议用frida -U --no-pause -f com.xxxx.xxxx -l xxxx.js命令注入js,同时启动目标app;如果人为开启目标app,再运行frida,可能regiserNative函数已经执行过了!
第二种
function hook_RegisterNatives() {
var symbols = Module.enumerateSymbolsSync("libart.so");
var addrRegisterNatives = null;
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];
//_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("RegisterNatives") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0) {
addrRegisterNatives = symbol.address;
console.log("RegisterNatives is at ", symbol.address, symbol.name);
}
}
if (addrRegisterNatives != null) {
Interceptor.attach(addrRegisterNatives, {
onEnter: function (args) {
console.log("[RegisterNatives] method_count:", args[3]);
var env = args[0];
var java_class = args[1];
var class_name = Java.vm.tryGetEnv().getClassName(java_class);
//console.log(class_name);
var methods_ptr = ptr(args[2]);
var method_count = parseInt(args[3]);
for (var i = 0; i < method_count; i++) {
var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));
var name = Memory.readCString(name_ptr);
var sig = Memory.readCString(sig_ptr);
var find_module = Process.findModuleByAddress(fnPtr_ptr);
console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, "module_name:", find_module.name, "module_base:", find_module.base, "offset:", ptr(fnPtr_ptr).sub(find_module.base));
}
}
});
}
}
setImmediate(hook_RegisterNatives);
frida将so中地址转换为字符串
function jstring2Str(jstring) { //从frida_common_funs.js中copy出来
var ret;
Java.perform(function() {
var String = Java.use("java.lang.String");
ret = Java.cast(jstring, String);//jstring->String
});
return ret;
}
java层遍历hashmap
//不能遍历空map
function iterMap(map){
var map_result = '';
var keyset = map.keySet();
var it = keyset.iterator();
while(it.hasNext()){
var keystr = it.next().toString();
var valuestr = map.get(keystr).toString();
map_result += valuestr;
}
return map_result
}
算法自吐脚本
Webview
关于webview:现在很多App里都内置了Web网页(Hyprid App),比如说很多电商平台,淘宝、京东、聚划算等等。
那么如何对webview进行hook呢?
首先,先贴出frida对webview进行hook的js模板
Java.perform(function (){
var WebView = Java.use("android.webkit.WebView");
WebView.setWebContentsDebuggingEnabled.overload("boolean").implementation = function (s) {
// send(s.toString());
console.log("webview hook")
// this.loadUrl.overload("java.lang.String").call(this, s);
// console.log(this.)
this.setWebContentsDebuggingEnabled(true)
};
});
然后就运行hook的python模板代码,然后在浏览器(这里使用edge浏览器)输入edge://inspect/#devices ,然后就可以看到webview的信息,点击inspect按钮就会进入到DevTools界面,然后就可以对其进行调试操作了。
遇到的问题
frida注入进程报错Failed to spawn: unable to find application with identifier的一种解决思路
frida-ps -U -a 得到 identifier
然后
frida -U --pause -f com.max.xiaoheihe -l xhh.js注入
jadx
IDA
IDA动态调试
在b站上看到视频关于ida的动态调试,对整体的思路有了一定的认识。【非常详细-IDA动态调试安卓.so文件】
其中用到了Android Studio的ddms,但是在网上搜索ddms,发现早已被移除,在该视频评论下看到博主说用monitor,于是搜集关于monitor的资料,最终下载了Android Studio3.1版本,安装好后在SDK的tools文件夹下找到monitor.bat文件,双击运行报错,查找资料发现是java版本不对,于是安装java1.8,也就是java se8,配置好环境变量后重新运行monitor.bat,界面正常出现。后面步骤就是启动android_server64服务,并设置端口转发,接着就是下面的操作。
adb shell am start -D -n com.max.xiaoheihe/com.max.xiaoheihe.MainActivity
根据步骤输入此命令后报错,改为输入命令:
adb shell monkey -p com.max.xiaoheihe -c android.intent.category.LAUNCHER 1
后正常出现Waiting For Debugger弹窗,接着输入命令:
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=9675
#注:9675这个端口为在monitor中看到的app的端口
后monitor界面中红色的蜘蛛变为绿色,点击IDA中的运行按钮,直至弹窗消失。
伪指令及DC系列指令
ARM 伪指令它不是ARM 指令集中的指令,只是为了方便编译器编程而定义的指
令,使用时可以像其他ARM 指令一样使用,但在编译时这些指令将被等效的ARM
指令代替。
DCB它关联的伪指令有DCB、DCW、DCD、DCQ指令。它们都是用于分配一段内存单元,并对其进行做初始化工作。不过它们分配的内存空间大小不同。
下面就针对这四个伪指令做下区分
DCB表示:它分配一段字节的内存单元,它每个操作数都占有一个字节,操作数范围为-128~255的数值或字符串。
DCW表示:它分配一段半字的内存单元,它的每个操作数都占有两个字节,操作数是16位二进制数,取值范围为-32768~65535。
DCD表示:它分配一段字的内存单元,它的每个操作数都占有4个字节,操作数可以是32位的数字表达式,也可以是程序中的标号。
DCQ表示:它分配一段双字的内存单元,它的每个操作数都占有8个字节。
经验
1.JNI_OnLoad
如果打开so之后发现没有Java_xxx这样的函数开头一般都是在JNI_OnLoad中采用了动态注册方式,所以只需要找到JNI_OnLoad函数,然后找到RegisterNatives函数即可
我们如果手动注册过Native方法,都知道RegisterNatives函数的三个参数含义:
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods)
第一个参数:需要注册native函数的上层Java类第二个参数:注册的方法结构体信息第三个参数:需要注册的方法个数这里当然是重点看第二个参数,这里当然也需要知道方法结构体信息:
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
结构体包含三部分分别是:方法名、方法的签名、对应的native函数地址;