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()
方法将其转换为整数。