LOADING

加载过慢请开启缓存 浏览器默认开启

frida的初步学习

JavaScript API 手册 | Frida JavaScript-api 中文手册

frida_Android

运行frida常用命令

运行

adb push 名 /data/local/tmp
adb root
adb shell chmod +x /data/local/tmp/fridaad64
adb shell /data/local/tmp/frida-server -l 0.0.0.0:27042 #默认27042端口
adb forward tcp:27042 tcp:27042
Frida -U -f 包 -l hookbool.js

-U 指定USB设备

-f 用app包名spwn方式启动

-P 指定APP的pid,先要自行启动

-pause 暂停

-l 加载hook脚本

hookjs

对fridahook脚本的一点理解

我们用fridahook一个函数时可以说是我们从apk里面钩住了一个函数,我们可以替换这个函数的任何内容。也可以说我们建立了一个管道在这个函数方法上,代码执行就像是一个水流,当水流到这个函数时,我们把水引到了我们的js这里,让它流经我们想要流过的地方。

一些frida方法的收集

send(); //发送信息到控制台.

对hook脚本的初步解析

//在启动时就被hook的方法,通常用于绕过反调试,java.perform()是一个frida的接口
//几乎一切操作都在这个函数执行
Java.perform(function(){  
    let MainActivity = Java.use("packname.MainActivity"); //实例化一个类
    MainActivity["isEmu"].implementation = function () {
        console.log(`MainActivity.isEmu is called`);
        let result = this["isEmu"]();
        console.log(`MainActivity.isEmu result=${result}`);
        return 0;  //一般可以修改返回值如果报错用false,修改返回值
    };
    //调用方法并返回值。
    MainActivity["g4tk4y"].implementation = function () {
    console.log(`MainActivity.g4tk4y is called`);
    let result = this["g4tk4y"]();
    console.log(`MainActivity.g4tk4y result=${result}`);
    return result; //修改返回值
};
//java.choose会遍历类的实例,调用方法,用于无法获得实例时动态引用,
//修改成员变量。我的理解是寻找一个系统自己的实例,而不是我们自己去实例化。
 Java.choose("packname.MainActivity",{  
        onMatch:
        function(x){
            console.log("ok" + x);
            let result = x.g4tk4y();
            console.log(result);
        },
        onComplete: function () {
            console.log("end");
        }

});
var MainActivity = Java.use("packname.MainActivity");
        //overload 选择被重载的对象,funname是方法名,int是要重载的类型
        MainActivity.funname.overload('int').implementation = function (x) {
            console.log("ok" + x);
            //可修改结果,另一种修改返回值的方法
            var result = "";
            return result;
        };
})

 

function hook() {   //把方法封装为一个函数,在运行起来后可以直接调用执行
Java.perform(function(){
    let MainActivity = Java.use("packname.MainActivity");
    MainActivity["g4tk4y"].implementation = function () {
        console.log(`MainActivity.g4tk4y is called`);
        let result = this["g4tk4y"]();
        console.log(`MainActivity.g4tk4y result=${result}`);
        return result;
        };
    MainActivity.aaa("x"); //调用函数并传参
})
};

(function () {    //(function(){..})();这样的形式会直接执行函数,  
                  //以下函数用dia提取,用于hookso层的函数

    // @ts-ignore
    function print_arg(addr) {
        try {
            var module = Process.findRangeByAddress(addr);
            if (module != null) return "\n"+hexdump(addr) + "\n";
            return ptr(addr) + "\n";
        } catch (e) {
            return addr + "\n";
        }
    }

    // @ts-ignore
    function hook_native_addr(funcPtr, paramsNum) {
        var module = Process.findModuleByAddress(funcPtr);
        try {
            Interceptor.attach(funcPtr, {
                onEnter: function (args) {
                    this.logs = "";
                    this.params = [];
                    // @ts-ignore
                    this.logs=this.logs.concat("So: " + module.name + "  Method: Java_ offset: " + ptr(funcPtr).sub(module.base) + "\n");
                    for (let i = 0; i < paramsNum; i++) {
                        this.params.push(args[i]);
                        this.logs=this.logs.concat("this.args" + i + " onEnter: " + print_arg(args[i]));
                    }
                }, onLeave: function (retval) {
                    for (let i = 0; i < paramsNum; i++) {
                        this.logs=this.logs.concat("this.args" + i + " onLeave: " + print_arg(this.params[i]));
                    }
                    this.logs=this.logs.concat("retval onLeave: " + print_arg(retval) + "\n");
                    console.log(this.logs);
                }
            });
        } catch (e) {
            console.log(e);
        }
    }
    // @ts-ignore
    hook_native_addr(Module.findBaseAddress("libdebugme.so").add(0xff0), 0x1);
})();

//重载函数的其他类型
.overload()
.overload('int')
.overload('java.lang.Exception')
.overload('android.content.Context')
.overload('java.lang.String')
.overload('android.content.Context', 'java.lang.String')
.overload('java.io.BufferedInputStream', 'java.io.BufferedInputStream', 'int')
.overload('android.content.Context', 'java.lang.String', 'java.lang.String', 'java.lang.String')

上面的方法都是被动调用,下面一段脚本是主动调用脚本

Java.perform(function(){
        var main =Java.use("com.moible.r15.main").$new();
        var input = "66.666s";
        var result = main.getit(input);
        console.log(result);
    })

有时候我们想要把一个调用方法封装在一个函数里面,在之后手动调用,然而在我们后面调用时可能会出现报错

这个时候我们要使用一下代码使handle能够在别的实例中运行。

 send(Java.available); 
function get(){
    Java.perform(function () { 
        send(Java.androidVersion); 
        send(Java.isMainThread());
    
        Java.scheduleOnMainThread(function () { 
            send(Java.isMainThread());
            
            var main = Java.use("com.moible.r15.main").$new();  //记得实例化new
            var input = "66.666s";  //设置参数
            var result = main.getit(input);
            console.log(result);       
        });
    });
}

一些常见错误

关于雷电模拟器spawn启动时卡在waitdebug界面的的原因解决方案

在用雷电模拟器调试时,有可能会卡在wait for debug,这是因为雷电的adb调试会给我们自动设置调试的应用,从而卡在wait界面。只要在开发者选项中把选择调试的应用改成”无”就行了。

关于frida调试应用闪退的解决方案

端口没有设置好.设置一下端口就行。

adb forward tcp:27042 tcp:27043

脚本运行过程中报错

查找报错点,找到报错的方法,分析,有必要时直接hook下来,修改函数的返回值等内容。

frida_win_processes

frida可以hookwindows上的的进程,详见功能 |Frida • 世界一流的动态仪表工具包

frida -l hook.js -n [name]

这里的name是进程的程序名字。

先介绍一下frida的processAPI。bbs.kanxue.com/article-342.htm

var process = Process.findModuleByAddress(address) //根据包含的地址查找模块
var process = Process.findModuleByName()  //根据名字查找在内存中的模块(str)->(模块对象)
process.base //模块基地址
process.name //模块名
process.size //模块大小
process.path //模块路径

setInterval(f, delay) //每隔delay毫秒调用f,返回一个id,使用clearinterval(id)取消对setIntervar()的调用

ptr(addr) //以指针形式调用地址数据

hexdump(addr,{offset:0,length:64,header:true,absi:true}) //把一个区域内的内存按格式导出

this.context  //访问当前hook的上下文信息,其中this.context.rax可以获取寄存器的值

hook脚本来自Windows | Frida • A world-class dynamic instrumentation toolkit
通用脚本

    //const baseAddr = Module.findBaseAddress('Jvm.dll'); //获取hook对象的拓展模块基地址
    //console.log('Jvm.dll baseAddr: ' + baseAddr); 
    var process = Process.findModuleByName("xx.exe"); //以名称获取进程中的模块
    var baseaddr = process.base
    
    //const f = resolveAddress('0x1FF44870'); //输入函数在ida中显示的地址,获得在内存中的地址

    Interceptor.attach(baseaddr.add(0x0000), { // hook函数,函数被调用时执行
        // 被hook时打印函数信息
        onEnter(args) {
            console.log('');
            console.log('[+] Called SetAesDeCrypt0' + f); //被hook的函数地址
            console.log('[+] Input: ' + args[0]); // 参数args[1],args[2]...
            aegs[0] = 111;  //修改参数
            dumpAddr('Input', args[1], args[3].toInt32()); //导出指针参数的数据
            this.outptr = args[2]; //保存参数的值以便函数结束时查看
            this.outsize = args[3].toInt32();
            var rdx=this.context.rdx;  //以上下文获取寄存器的值
            console.log(hexdump(ptr(rdx),{length: 16,ansi:true})); //导出寄存器指向内存的数据
        },

        onLeave(retval) {  //函数结束时执行
            dumpAddr('Output', this.outptr, this.outsize); // 获取我们保存的指针参数的值
            console.log('[+] Returned from setAesDecrypt0: ' + retval);  //函数的返回值
        }
    });

    function dumpAddr(info, addr, size) { //导出内存数据
        if (addr.isNull())
            return;

        console.log('Data dump ' + info + ' :');
        const buf = addr.readByteArray(size);

        // 如果想要色彩高亮,ansi为true
        console.log(hexdump(buf, { offset: 0, length: size, header: true, ansi: false }));
    }

    function resolveAddress(addr) {  //用IDA显示的地址获取当前内存中的地址,其实自己手算偏移也行
        const idaBase = ptr('0x1FEE0000'); //输入IDA中显示的基地址,用于计算地址偏移
        const offset = ptr(addr).sub(idaBase); //计算地址偏移
        const result = baseAddr.add(offset); //计算现在在内存中的地址
        console.log('[+] New addr=' + result); // 输出在内存中的地址
        return result;
    }

手动调用函数

var f = new NativeFunction(ptr(addr), 'void', ['int']); //函数地址,返回类型,参数类型,调用函数
f(1911); //调用函数并串参

实用脚本总结

hookjava函数

Java.perform(function(){ 
    let MainActivity = Java.use("com.exa.n.MainActivity");
    MainActivity["func"].implementation = function (data) {
    console.log(`func is called: data=${data}`);
    
    var result;
    result = this["func"](data);
    return result;
    };
})

function printstack() { //打印堆栈
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
}

hookso


var libapp = null;

function onLibappLoaded() {
    const fn_addr = 0x2FE7F0;   
    Interceptor.attach(libapp.add(fn_addr), {
        onEnter: function () {
            var rdi = this.context.rdi;  
            console.log(rdi)
            console.log(hexdump(ptr(rdi), { length: 100, ansi: true }))
        }
    });
}

function tryLoadLibapp() {  
    libapp = Module.findBaseAddress('libapp.so');
    if (libapp === null)
        setTimeout(tryLoadLibapp, 500);
    else
        onLibappLoaded();
}
tryLoadLibapp();

windows一般dump寄存器的值

var inter=setInterval(function () {
    var process = Process.findModuleByName("1.exe");
    var baseaddr = process.base
    console.log("base"+baseaddr);

    clearInterval(inter);
    console.log(hexdump(baseaddr.add(0x005160),{length:255,ansi:true}))
    
    Interceptor.attach(baseaddr.add(0x001E7F), { 
    onEnter: function (args) {
        var rax=this.context.rax; 
        console.log("secret" +rax);
        console.log(hexdump(ptr(rax),{length: 48,ansi:true}));
    }}),
},1)

修改寄存器的值

(用python处理数据)

function hookinput(data){
    var baseaddr = Module.findBaseAddress("applib.so");
    Interceptor.attach(baseaddr.add(0x001E7F), { 
        onEnter: function (args) {
            var rdi=this.context.rdi;
            console.log("hook" +rdi);
            console.log(hexdump(ptr(rax),{length: 48,ansi:true}));
            rdi.writeByteArray(data)
            console.log("edit_after" +rdi);
            console.log(hexdump(ptr(rax),{length: 48,ansi:true}));
        }})
    }
RTCPeerConnection.export = {
    hookinput:hookinput
}
import time
import frida
import sys

def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)

device8 = frida.get_usb_device()
pid = device8.spawn("com.example.aaar")
device8.resume(pid)
time.sleep(1)
session = device8.attach(pid)
with open("hook.js") as f:
    script = session.create_script(f.read())
script.on('message', on_message)
print('[*] Hook Start Running')
script.load()

ad = "".join('a' * 42)
test = f"flag{{{ad}}}"
input_arr_byte = bytearray(test.encode())
data = list(map(int,input_arr_byte))

script.export.inputhook(data)

单js脚本处理,适用于简单数据

function stringToAsciiArray(str) {
    return Array.from(str).map(char => char.charCodeAt(0));
}
const input = "flag{}";
const data = stringToAsciiArray(input);

function hookinput(data){
    var baseaddr = Module.findBaseAddress("libflutter.so");
    Interceptor.attach(baseaddr.add(0x001E7F), { 
        onEnter: function (args) {
            var rdi=this.context.rdi;
            console.log("hook" +rdi);
            console.log(hexdump(ptr(rax),{length: 48,ansi:true}));
            rdi.writeByteArray(data)
            console.log("edit_after" +rdi);
            console.log(hexdump(ptr(rax),{length: 48,ansi:true}));
        }})
    }
hookinput(data);

文件删除前转移,用于脱壳

File.delete.implementation = function() {
        var filePath = this.getAbsolutePath();  // 获取文件路径
        console.log("文件将在删除之前被保存: " + filePath);

        // 在删除之前复制文件到其他位置
        var newFile = Java.use('java.io.File');
        var destPath = "/data/data/com.nobody.zunjia/files/savedDexFile.dex";  // 新文件路径
        var sourceFile = this;
        var inputStream = Java.use('java.io.FileInputStream').$new(sourceFile);
        var outputStream = Java.use('java.io.FileOutputStream').$new(destPath);
        
        var buffer = Java.array('byte', [1024]);  // 缓冲区
        var bytesRead;
        while ((bytesRead = inputStream.read(buffer)) !== -1) {
            outputStream.write(buffer, 0, bytesRead);
        }
        inputStream.close();
        outputStream.close();
        console.log("文件已保存到: " + destPath);

        // 执行原本的删除操作
        return this.delete();
    };

xxxxxxxxxx #include <stdio.h>#include<string.h>#include <stdlib.h>​#define size 256char base64[65] = “stuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr”;int op1[] = {0, 0, 1, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2}; //第一层控制流int op2[] = { 1, 5, 1, 6, 5, 1, 3, 6, 5, 4, 3, 6, 1, 4, 3, 4, 1, 4, 5, 4, 1 };//第二层控制流​int rexor5(char* data1, char* data2, int len);void enc_dec(unsigned char* key, unsigned char* data, int n);void init_sbox(unsigned char* key);int rerc4r6(char* data1, char* data2, int len);int re3(char* data1, char* data2, int len);int re4(char* data1, char* data2, int len);int decodeBase64(char* str, int len);​void re1(char* Source, int i, char* Destination, int v21) {    for (int j = 0; j < i; ++j) {        char v22 = Destination[j % v21];        if (Source[j] >= ‘A’ && Source[j] <= ‘Z’) {            for (int i = 65; i <= 90; i++) { //直接爆破,遍历符合逻辑的source                char a = (i + v22 - ‘A’) % 26;                char b = Source[j] - ‘A’;                if (b == a) { Source[j] = i; break; };           }       }        else if (Source[j] >= ‘a’ && Source[j] <= ‘z’) {            for (int i = 97; i <= ‘z’; i++) {                char a = (i + v22 - ‘a’) % 26;                if (Source[j] - ‘a’ == a) { Source[j] = i; break; };           }       }        else if (Source[j] >= ‘0’ && Source[j] <= ‘9’) {            for (int i = ‘0’; i <= ‘9’; i++) {                char a = (i + v22 - ‘0’) % 10;                if (Source[j] - ‘0’ == a) { Source[j] = i; break; };           }       }   }}​int main() {    char m1[] = “WgvDmssEvcY326bHo3nNro3vXvvfmgrz”;    char m2[] = “gX+Ri9PG=bt5=00B6hscPQOL”;    char m3[] = “T6bHgUPL2gXUd=xT=FNHtPzV”;    for (int i = 20; i>=0; i–) { //反向遍历        switch (op1[i]) { //选择加密方法和密钥        case 0:            switch (op2[i]) {            case 1:                re1(m1, strlen(m1), m2, strlen(m2));                break;            case 3:                re3(m1, m2, strlen(m1));                break;            case 4:                re4(m1, m2, strlen(m1));                break;            case 5:                rexor5(m1, m2, strlen(m1));                break;            case 6:                rerc4r6(m1, m2, strlen(m1));                break;           }            break;        case 1:            switch (op2[i]) {            case 1:                re1(m2, strlen(m2), m3, strlen(m3));                break;            case 3:                re3(m2, m3, strlen(m2));                break;            case 4:                re4(m2, m3, strlen(m2));                break;            case 5:                rexor5(m2, m3, strlen(m2));                break;            case 6:                rerc4r6(m2, m3, strlen(m2));                break;           }            break;​        case 2:            switch (op2[i]) {            case 1:                re1(m3, strlen(m3), m1, strlen(m1));                break;            case 3:                re3(m3, m1, strlen(m3));                break;            case 4:                re4(m3, m1, strlen(m3));                break;            case 5:                rexor5(m3, m1, strlen(m3));                break;            case 6:                rerc4r6(m3, m1, strlen(m3));                break;           }            break;       }   }​    printf(“%s”, m1);    printf(“%s”, m2);    printf(“%s”, m3);​}​int rexor5(char* data1, char* data2, int len) {    int k = decodeBase64(data1,len); //data1解密后会有0,于是直接让decode函数返回正确长度    for (int i = 0; i < k; i++) {        data1[i] = (data2[i % strlen(data2)] + 57) ^ (unsigned char)data1[i];   }    return 0;}​int rerc4r6(char* data1, char* data2, int len) {    int k =decodeBase64(data1, len); //同上​    for (int i = 0; i <len ; i++) {        data1[i] -= 57;   }    enc_dec((unsigned char*)data2, (unsigned char*)data1,k);    data1[k] = ‘\0’;    return 0;}​int re3(char* data1, char* data2, int len) {    char block[13][13];    char blocklen = data2[0] % 10 + 2;​    for (int i = 0; i < blocklen; i++) {        memset(block[i], 0, len + 1);   }​    int i = 0, index = 0,reamind = len%blocklen;​    for (int jj = 0; jj < blocklen; ++jj) {        int leng = len / blocklen;        if (reamind > 0) { leng++; reamind–; } //注意剩余的元素要多用一列输入,直接加1会导致列数不正确        for (int kk = 0; kk  <leng ; ++kk) {            if (index < len) {                *(char*)(block[jj] + kk) = data1[index++]; //按行输入           }       }   }​    index = 0;​    for (i = 0; i * blocklen < len; ++i) {        for (int ii = 0; ii < blocklen && ii + blocklen * i < len; ++ii) {            data1[ii + blocklen * i] = *(char*)(block[ii] + i); //按列取出       }   }    return 0;}​​int re4(char* data1, char* data2, int len) {    char blocklen = data2[0] % 10 + 2;    for (int nn = 0; nn < blocklen; ++nn)   {        char v11 = data1[0];        for (int i1 = 0; i1 < len-1; ++i1)            data1[i1] = data1[i1+1];        data1[len-1] = v11;   }    return 0;}​unsigned char sbox[257] = { 0 };​void init_sbox(unsigned char* key) {    unsigned int i, j, k;    int tmp;​    for (i = 0; i < size; i++) {        sbox[i] = i;   }​    j =k = 0;    for (i = 0; i < size; i++) {        tmp = sbox[i];        j = (j + tmp + key[i % strlen((char*)key)]) % size;        sbox[i] = sbox[j];        sbox[j] = tmp;   }}​void enc_dec(unsigned char* key, unsigned char* data,int n) {    int i, j, k, R, tmp;​    init_sbox(key);​    j = k = 0;    for (i = 0; i < n; i++) {        j = (j + 1) % size;        k = (k + sbox[j]) % size;​        tmp = sbox[j];        sbox[j] = sbox[k];        sbox[k] = tmp;​        R = sbox[(sbox[j] + sbox[k]) % size];                data[i] ^= R;           }}​int decodeBase64(char* str, int len) {        unsigned char ascill[129] = { 0 };    int k = 0;    for (int i = 0; i < 64; i++) {        ascill[base64[i]] = k++;   }    int decodeStrlen = len / 4 * 3 + 1;    char* decodeStr = (char*)malloc(sizeof(char) * decodeStrlen);    k = 0;    for (int i = 0; i < len; i++) {        unsigned char a, b, c;        a = ascill[str[i]];        b = ascill[str[++i]];        c = (a << 2) | (b >> 4);        decodeStr[k++] = c;        if (str[i + 1] == ‘=’) {            break;       }        a = ascill[str[i]];        b = ascill[str[++i]];        c = (a << 4) | (b >> 2);        decodeStr[k++] = c;        if (str[i + 1] == ‘=’) {            break;       }        a = ascill[str[i]];        b = ascill[str[++i]];        c = (a << 6) | (b);        decodeStr[k++] = c;   }    decodeStr[k] = ‘\0’;    for (int i = 0; i <= k; i++) {        str[i] = decodeStr[i];   }    free(decodeStr);    return k; //返回长度以便进行数据处理}//NSSCTF{P4ch3Lbel‘s_C@n0n_1n_D_mAjOr}clike