[安全] Frida Labs 0x8

总览

反编译
decomp

可以看到最重要的比较函数 public native int cmpstr(String str); 是一个 native 函数
native 函数是编译之后储存在 so库 里的函数, 我们需要去对应的 so库 里面找

static {
    System.loadLibrary("frida0x8");
}

可以看到加载了一个名字叫 frida0x8 的库
如果使用 jadx, 那么在 资源文件 -> lib -> 架构 -> libxxx.so 中会找到你需要的库
solib

使用反编译器 (这里使用 IDA) 打开该 so 文件, 如果你需要找的函数在导出函数里面的话, 那么它的命名应该会是 Java_包名_类名_方法名
sofunc

查看反编译的函数
decmpfunc

到这里, 我们可以想到数个办法来获取 password

  1. 写简单脚本得到 flag (这里不做展示)
  2. 由于 __android_log_print(3, "Password", (__int64)&unk_6B0, passwd); 打印了 password, 所以我们可以直接查看 log 来获取 password
  3. hook __android_log_print 来查看 password
  4. hook strcmp 来查看 password

方法一: 查看 log

使用

abd logcat

来查看 __android_log_print 的打印结果

我们可以使用 frida-ps -Uai 来查看正在运行的 frida0x8 的 pid, 然后为上述命令指定 pid, 可以缩小查看的 log 量

例如我的

> frida-ps -Uai
 PID  Name            Identifier
----  --------------  -----------------------
2850  Frida 0x8       com.ad2001.frida0x8    

使用命令

adb logcat --pid=2850

可以看到这样的输出
outputlog

方法二: hook native 函数

spawn 模式下由于 Frida 加载的时机非常早, 会出现库仍未加载所以 hook 不到目标库相关函数的情况
为了防止这种情况发生, 可以 hook 一个用来加载 native 库的函数 android_dlopen_ext, 通过判断其参数来确认目标库是否加载
虽然好像这题的思路都没有 hook libfrida0x8.so 这个库的函数, 所以等不等这个目标库加载完成都没什么关系

放一个板子

function HookDLopen(libname: string, onLoad: () => void) {
    const dlopenExtPtr = Module.findGlobalExportByName("android_dlopen_ext");
 
    if (dlopenExtPtr) {
        console.log("[+] Hooked android_dlopen_ext");
        Interceptor.attach(dlopenExtPtr, {
            onEnter: function (this: any, args) {
                this.filename = args[0].readCString();
            },
            onLeave: function (this: any, retval) {
                if (retval.isNull()) return ;
 
                if (this.filename && this.filename.indexOf(libname) >= 0) {
                    console.log(`[!] Target library loaded: ${this.filename}`);
                    onLoad();
                }
            }
        });
    }
}
 
const funcname = "";
const libname  = "";
 
const onLoad = () => {
    console.log(`[+] onLoad triggered for ${libname}`);
    const funcPtr = Module.findGlobalExportByName(funcname);
    
    if (funcPtr) {
        console.log(`[+] Found ${funcname} at ${funcPtr}`);
        Interceptor.attach(funcPtr, {
            onEnter: function (this: any, args) {}, 
            onLeave: function (this: any, retv) {}
        });
        console.log(`[+] Hooked ${funcname}`);
    } else {
        console.log(`[-] ${funcname} not found`);
    }
};
 
const loadedModule = Process.findModuleByName(libname);
if (loadedModule) {
    console.log(`[+] ${libname} already loaded`);
    onLoad();
} else {
    console.log(`[+] Waiting for ${libname} to load...`);
    HookDLopen(libname, onLoad);
}

其中 onLoad 函数就是我们需要实现的功能
Interceptor.attach 可以在 hook 某个函数, 使得在调用该函数之前, 先通过 onEnter 函数对参数进行处理, 然后将处理完的参数传入函数中, 获取返回值之后通过 onLeave 函数对返回值进行处理, 再将处理后的返回值传给调用者

由于 hook 的函数不一定只在你想要有效果的地方被 hook, 因此需要一些判定来过滤目标函数调用, 比如可以看参数, 符合你想要的那个调用再输出之类的

  1. hook __android_log_print

    const funcname = "__android_log_print";
    const libname  = "libfrida0x8.so";
     
    const onLoad = () => {
        console.log(`[+] onLoad triggered for ${libname}`);
        const funcPtr = Module.findGlobalExportByName(funcname);
        
        if (funcPtr) {
            console.log(`[+] Found ${funcname} at ${funcPtr}`);
            Interceptor.attach(funcPtr, {
                onEnter: function (this: any, args) {
                    const prio = args[0].toInt32();
                    const tagPtr = args[1];
                    const fmtPtr = args[2];
                    const passwdPtr = args[3];
     
                    if (prio !== 3) return ;
                    
                    const tag = tagPtr.readCString();
                    if (tag !== "Password") return ;
     
                    const fmt = fmtPtr.readCString();
                    console.log(`[+] Captured targeted log call:`);
                    console.log(`    Prio: ${prio}`);
                    console.log(`    Tag:  ${tag}`);
                    console.log(`    Fmt:  ${fmt}`);
                    console.log(`    Passwd (raw): ${passwdPtr}`);
                    const passwdStr = passwdPtr.readCString();
                    console.log(`    Passwd (string): ${passwdStr}`);
                }
            });
            console.log(`[+] Hooked ${funcname}`);
        } else {
            console.log(`[-] ${funcname} not found`);
        }
    };

    点一下 submit 就有调用

  2. hook strcmp

    const funcname = "strcmp";
    const libname  = "libfrida0x8.so";
     
    const onLoad = () => {
        console.log(`[+] onLoad triggered for ${libname}`);
        const funcPtr = Module.findGlobalExportByName(funcname);
        
        if (funcPtr) {
            console.log(`[+] Found ${funcname} at ${funcPtr}`);
            Interceptor.attach(funcPtr, {
                onEnter: function (this: any, args) {
                    const inputPtr  = args[0];
                    const inputStr  = inputPtr.readCString();
                    const passwdPtr = args[1];
                    const passwdStr = passwdPtr.readCString();
                    if (inputStr === "123321") {
                        console.log(`[+] Password is ${passwdStr}`);
                    }
                }
            });
            console.log(`[+] Hooked ${funcname}`);
        } else {
            console.log(`[-] ${funcname} not found`);
        }
    };

    只要你的输入是 123321 密码就能被打印出来