frida hook so层常用的方法

1.toInt32()

toInt32()是Frida中的一个函数,用于将传入的值转换为32位有符号整数。如果无法转换,则返回0。该函数需要传入一个参数,返回要转换的值。

适用于需要对整数数据类型进行转换的场景,如果参数是整数类型,可以使用toInt32()函数将其转换为32位有符号整数。以下是一个示例代码:

Interceptor.attach(Module.findExportByName("libexample.so", "example_func"), {
    onEnter: function(args) {
        // 将第一个参数转换为32位有符号整数
        var intValue = toInt32(args[0]);
        console.log("example_func entered with arg0 = " + intValue);
    },
    onLeave: function(retval) {
        console.log("example_func returned with retval = " + retval);
    }
});

在以上示例代码中,使用Interceptor.attach()函数hook了libexample.so库中的example_func函数,并在onEnter回调函数中获取了第一个参数,并使用toInt32()函数将其转换为32位有符号整数。

需要注意的是,在使用toInt32()函数时需要确保传入的参数是合法的整数类型,并且要避免越界访问。如果传入的参数无法转换为32位有符号整数,则toInt32()函数会返回0。如果需要转换其他类型的数据,可以使用Frida提供的其他类型转换函数。

其他进制和类型的转换,比如:

// 将十六进制字符串转换为32位有符号整数
var intValue = toInt32("0x12345678");
console.log(intValue);

// 将十进制数字转换为32位有符号整数
var intValue = toInt32(12345678);
console.log(intValue);

// 将字符串转换为32位有符号整数
var intValue = toInt32("12345678");
console.log(intValue);

这里分别将十六进制字符串、十进制数字和字符串转换为32位有符号整数,并将其打印出来。

2.readCString()

在Frida的JavaScript API中,readCString()方法是从给定的内存地址开始读取 并以空字符('\0')结尾的C字符串,例如"Hello, World!\0"。并返回javascript字符串。

该方法的实现原理是从目标进程的内存中读取连续的字节,直到读到null字符为止。在Frida层级钩子中经常被使用,用于在目标进程中定位并读取字符串类型的参数、函数返回值、全局变量等。

例子1:

Interceptor.attach(Module.findExportByName("libfoo.so", "some_function"), {
    onEnter: function(args) {
        var strArgPtr = args[0];
        var strArg = Memory.readCString(strArgPtr);
        console.log("String argument: " + strArg);
    }
});

在这个例子中,我们使用Interceptor.attach()函数创建了一个函数钩子,并钩住了名为"some_function"的函数。在onEnter回调函数中,我们从函数参数中获取了一个指向字符串的指针,并使用Memory.readCString()方法从目标进程中读取了该指针所指向的C风格字符串,并将其打印到控制台上。

例子2:读取目标进程中的密码参数

假设目标进程中的某个函数需要用户输入密码作为参数,并且密码是以C风格字符串的形式进行传递的。我们可以使用Frida层级钩子来获取这个密码参数的值。下面是一段示例代码:

Interceptor.attach(Module.findExportByName("libfoo.so", "some_function"), {
    onEnter: function(args) {
        var passwordPtr = args[0];
        var password = Memory.readCString(passwordPtr);
        console.log("Password: " + password);
    }
});

在这个例子中,我们使用Interceptor.attach()函数创建了一个函数钩子,并指定了要钩住的函数名。在onEnter回调函数中,我们从函数参数中获取了一个指向密码字符串的指针,并使用readCString()方法读取了该指针所指向的字符串。最后,我们将读取到的密码打印到控制台上。

例子3:读取目标进程中的全局变量

假设目标进程中有一个全局变量,存储了某个字符串类型的配置信息,我们可以使用Frida层级钩子来读取该全局变量的值。下面是一段示例代码:

Interceptor.attach(Module.findExportByName("libfoo.so", "some_function"), {
    onEnter: function(args) {
        var configAddr = Module.findExportByName("libfoo.so", "config");
        var configValue = Memory.readCString(configAddr);
        console.log("Config value: " + configValue);
    }
});

在这个例子中,我们使用Interceptor.attach()函数创建了一个函数钩子,并指定了要钩住的函数名。在onEnter回调函数中,我们使用Module.findExportByName()函数查找了目标进程中存储配置信息的全局变量的地址,然后使用readCString()方法读取了该地址所指向的字符串。最后,我们将读取到的配置信息打印到控制台上。

3.readByteArray()

在Frida层级钩子中,如果我们需要读取目标进程中的二进制数据,可以使用readByteArray()方法。这个方法可以从目标进程中读取一段内存的数据,并将其作为一个字节数组(ByteArray)返回。

readByteArray()方法需要两个参数:内存地址和数据长度。下面是一个例子:

Interceptor.attach(Module.findExportByName("libfoo.so", "some_function"), {
    onEnter: function(args) {
        var bufferAddr = args[0];
        var bufferLength = args[1].toInt32();
        var bufferData = Memory.readByteArray(bufferAddr, bufferLength);
        console.log("Buffer data: " + bufferData);
    }
});

在这个例子中,我们使用Interceptor.attach()函数创建了一个函数钩子,并钩住了名为"some_function"的函数。在onEnter回调函数中,我们从函数参数中获取了一个指向缓冲区的指针,以及缓冲区的长度。

接着,我们使用Memory.readByteArray()方法从目标进程中读取了该指针所指向的内存段,并将其作为一个字节数组返回。最后,我们将字节数组打印到控制台上。

需要注意的是,由于readByteArray()方法返回的是一个字节数组,因此我们需要使用一些方法来将其转换为其他格式,例如字符串或数字。例如,如果我们要将一个字节数组转换为一个字符串,可以使用TextDecoder对象的decode()方法:

var bufferData = Memory.readByteArray(bufferAddr, bufferLength);
var textDecoder = new TextDecoder("utf-8");
var bufferString = textDecoder.decode(bufferData);
console.log("Buffer data as string: " + bufferString);

在这个例子中,我们使用TextDecoder对象的decode()方法将字节数组转换为字符串,并将其打印到控制台上。

其他使用场景举例:

1.拦截xx数据

在某些情况下,我们可能需要从目标进程中获取xxx数据,例如用户的密码、会话令牌等等。如果这些数据存储在内存中,我们可以使用readByteArray()方法来读取它们。例如,我们可以使用Frida层级钩子来拦截应用程序中的登录请求,并从内存中读取用户的密码:

Interceptor.attach(Module.findExportByName("libfoo.so", "login"), {
    onEnter: function(args) {
        var passwordAddr = args[1];
        var passwordLength = args[2].toInt32();
        var passwordData = Memory.readByteArray(passwordAddr, passwordLength);
        console.log("Password: " + passwordData);
    }
});

在这个例子中,我们钩住了名为"login"的函数,并从函数参数中获取了一个指向密码缓冲区的指针,以及缓冲区的长度。接着,我们使用Memory.readByteArray()方法从目标进程中读取了该指针所指向的内存段,并将其作为一个字节数组返回。最后,我们将字节数组打印到控制台上。

2.解密加密数据

如果目标进程中的某些数据是加密的,我们可以使用Frida层级钩子来拦截解密函数,并读取解密后的数据。例如,假设目标进程中有一个加密的字符串,我们可以使用Frida层级钩子来拦截解密函数,并读取解密后的字符串:

Interceptor.attach(Module.findExportByName("libfoo.so", "decrypt"), {
    onEnter: function(args) {
        var encryptedDataAddr = args[0];
        var encryptedDataLength = args[1].toInt32();
        var encryptedData = Memory.readByteArray(encryptedDataAddr, encryptedDataLength);
        console.log("Encrypted data: " + encryptedData);
    },
    onLeave: function(retval) {
        var decryptedData = Memory.readByteArray(retval, retval.toInt32());
        var textDecoder = new TextDecoder("utf-8");
        var decryptedString = textDecoder.decode(decryptedData);
        console.log("Decrypted string: " + decryptedString);
    }
});

在这个例子中,我们钩住了名为"decrypt"的解密函数,并从函数参数中获取了一个指向加密数据的指针,以及数据的长度。接着,我们使用Memory.readByteArray()方法从目标进程中读取了加密数据,并将其作为一个字节数组返回。在onLeave回调函数中,我们从解密函数的返回值中获取了解密后的数据,并将其转换为字符串。

4.hexdump()

hexdump()函数来将二进制数据以十六进制的形式打印到控制台上,从而方便我们查看目标进程中的内存数据。

该函数有两个参数,第一个参数是要打印的数据,可以是Buffer类型或者指向数据的指针。第二个参数是一个可选的配置对象,可以指定打印的偏移量、长度、标题等。

hexdump()函数的可选参数及其含义:

  • offset:指定要打印的数据的起始位置在缓冲区中的偏移量,默认为0。
  • length:指定要打印的数据的长度,默认为整个缓冲区的长度。
  • ansi:指定是否在控制台中使用ANSI转义序列来改变打印输出的颜色,默认为true。
  • prefix:指定每一行的前缀,默认为空字符串。
  • indent:指定每一行的缩进量,默认为0。
  • uppercase:指定是否将十六进制数字显示为大写字母,默认为true。
  • width:指定每一行显示的字节数,默认为16。

下面是一个例子,演示如何使用hexdump()函数,并传递一些可选参数:

var data = Memory.readByteArray(ptr(address), length);
console.log(hexdump(data, {
  offset: 0,
  length: length,
  prefix: '[+] ',
  indent: 4,
  width: 32,
  uppercase: false,
  ansi: true
}));

在这个例子中,我们使用Memory.readByteArray()函数读取了一段内存数据,并将其保存到data变量中。然后,我们调用hexdump()函数,并传递了一些可选参数。其中,我们指定了打印输出的前缀为’[+] ',每一行的缩进为4个空格,每行显示的字节数为32,我们还将十六进制数字的字母大小写设置为小写,并启用了ANSI颜色序列。执行以上代码,会将data数组以指定的格式打印到控制台上。

结合onEnter和onLeave举个例子:

Interceptor.attach(Module.findExportByName(null, "function_name"), {
  onEnter: function(args) {
    console.log("[+] function_name called with arguments:");
    hexdump(args[0], {
      offset: 0,
      length: args[1].toInt32(),
      header: false
    });
    this.start = new Date();
  },

  onLeave: function(retval) {
    var end = new Date();
    console.log("[+] function_name returned:");
    hexdump(retval, {
      offset: 0,
      length: retval.toInt32(),
      header: false
    });
    console.log("[+] Execution time: " + (end - this.start) + "ms");
  }
});

在这个例子中,我们使用Interceptor.attach()函数来拦截名为"function_name"的函数。在onEnter事件中,我们使用hexdump()函数打印了函数的第一个参数,然后记录了函数开始执行的时间;在onLeave事件中,我们使用hexdump()函数打印了函数的返回值,并计算了函数执行的时间。

需要注意的是,在onEnter事件中获取的参数可能会被修改,因此在onLeave事件中打印返回值时,可能需要再次获取一次返回值。另外,由于函数的返回值可能是一个指针,因此需要使用toInt32()方法将其转换为整数。

  • 1
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值