在这里对其中的一部分API进行了介绍,这篇文章继续介绍后面的内容。
通过这部分的介绍,可以发现通过Frida操纵内存、查看模块等信息是如此的简单。操作内存,最重要的自然就是打补丁了,邪恶的微笑。
Module.load(path): loads the specified module from the filesystem path and returns a Module object. Throws an exception if the specified module cannot be loaded.
常用的API可以参考下面的代码,注释写的很清楚:
Enumerates modules loaded right now, returning an array of Module objects.
//枚举当前加载的模块
var process_Obj_Module_Arr = Process.enumerateModules();
for(var i = 0; i < process_Obj_Module_Arr.length; i++) {
//包含"lib"字符串的
if(process_Obj_Module_Arr[i].path.indexOf("lib")!=-1)
{
console.log("模块名称:",process_Obj_Module_Arr[i].name);
console.log("模块地址:",process_Obj_Module_Arr[i].base);
console.log("大小:",process_Obj_Module_Arr[i].size);
console.log("文件系统路径",process_Obj_Module_Arr[i].path);
}
}
Enumerates imports of module, returning an array of objects containing the following properties:
//枚举模块中所有中的所有导入表(Import)函数
function frida_Module_import() {
Java.perform(function () {
const hooks = Module.load('libc.so');
var Imports = hooks.enumerateImports();
for(var i = 0; i < Imports.length; i++) {
//函数类型
console.log("type:",Imports[i].type);
//函数名称
console.log("name:",Imports[i].name);
//属于的模块
console.log("module:",Imports[i].module);
//函数地址
console.log("address:",Imports[i].address);
}
});
}
setImmediate(frida_Module_import,0);
Enumerates exports of module, returning an array of objects containing the following properties:
var Exports = hooks.enumerateExports();
for(var i = 0; i < Exports.length; i++) {
//函数类型
console.log("type:",Exports[i].type);
//函数名称
console.log("name:",Exports[i].name);
//函数地址
console.log("address:",Exports[i].address);
}
注:导出表和导入表后续文章会专门进行介绍。简单来说,导入表是这个库依赖的函数表,而导出表是这个库对外提供的函数表。
Enumerates symbols of module, returning an array of objects containing the following properties。
function frida_Module_symbol() {
Java.perform(function () {
const hooks = Module.load('libc.so');
var Symbol = hooks.enumerateSymbols();
for(var i = 0; i < Symbol.length; i++) {
console.log("isGlobal:",Symbol[i].isGlobal);
console.log("type:",Symbol[i].type);
console.log("section:",JSON.stringify(Symbol[i].section));
console.log("name:",Symbol[i].name);
console.log("address:",Symbol[i].address);
}
});
}
setImmediate(frida_Module_symbol,0);
返回so文件中Export函数库中函数名称为exportName函数的绝对地址。
Module.findExportByName(moduleName|null, exportName), Module.getExportByName(moduleName|null, exportName): returns the absolute address of the export named exportName in moduleName. If the module isn’t known you may pass null instead of its name, but this can be a costly search and should be avoided. In the event that no such module or export could be found, the find-prefixed function returns null whilst the get-prefixed function throws an exception.
function frida_Module_address() {
Java.perform(function () {
console.log("gets address of getExportByName :",Module.getExportByName('libc.so', 'gets'));
console.log("gets address of findExportByName:",Module.findExportByName('libc.so', 'gets'));
});
}
setImmediate(frida_Module_address,0);
Memory的一些API通常是对内存处理,譬如Memory.copy()复制内存,又如writeByteArray写入字节到指定内存中。
其主要功能是搜索内存中以address地址开始,搜索长度为size,需要搜是条件是pattern,callbacks搜索之后的回调函数;此函数相当于搜索内存的功能。
Memory.scan(address, size, pattern, callbacks): scan memory for occurences of pattern in the memory range given by address and size.
pattern must be of the form “13 37 ?? ff” to match 0x13 followed by 0x37 followed by any byte followed by 0xff. For more advanced matching it is also possible to specify an r2-style mask. The mask is bitwise AND-ed against both the needle and the haystack. To specify the mask append a : character after the needle, followed by the mask using the same syntax. For example: “13 37 13 37 : 1f ff ff f1”. For convenience it is also possible to specify nibble-level wildcards, like “?3 37 13 ?7”, which gets translated into masks behind the scenes.
callbacks is an object with:
onMatch: function (address, size): called with address containing the address of the occurence as a NativePointer and size specifying the size as a number.
This function may return the string stop to cancel the memory scanning early.
onError: function (reason): called with reason when there was a memory access error while scanning
onComplete: function (): called when the memory range has been fully scanned
本文选取的的是领跑娱乐.apk,为一个赌博类的APP,后续会专门分析这个app。
对apk的解包在这里进行了介绍,通过Radare2查看libgame.so这个库的函数信息,如下:
匹配规则对应的是opcode,下面的代码将pattern设置为"08 c6 8f e2 ?? ca 8c e2",正好匹配第一行和第二行的opcode。
var process_Obj_Module_Arr = Process.enumerateModules();
for(var i = 0; i < process_Obj_Module_Arr.length; i++) {
//包含"libgame"字符串的
if(process_Obj_Module_Arr[i].path.indexOf("libgame")!=-1)
{
console.log("模块名称:",process_Obj_Module_Arr[i].name);
console.log("模块地址:",process_Obj_Module_Arr[i].base);
console.log("大小:",process_Obj_Module_Arr[i].size);
console.log("文件系统路径",process_Obj_Module_Arr[i].path);
// Print its properties:
console.log(JSON.stringify(process_Obj_Module_Arr[i]));
// Dump it from its base address:
console.log(hexdump(process_Obj_Module_Arr[i].base));
// The pattern that you are interested in:
var pattern = '08 c6 8f e2 ?? ca 8c e2';
Memory.scan(process_Obj_Module_Arr[i].base, process_Obj_Module_Arr[i].size, pattern, {
onMatch: function (address, size) {
console.log('Memory.scan() found match at', address,
'with size', size);
// Optionally stop scanning early:
return 'stop';
},
onComplete: function () {
console.log('Memory.scan() complete');
}
});
}
}
最终的运行结果:
很容易可以计算出偏移:0xc429f3bc - 0xc4041000 = 0x25e3bc
Memory.scanSync(address, size, pattern): synchronous version of scan() that returns an array of objects containing the following properties:
console.log(JSON.stringify(Memory.scanSync(process_Obj_Module_Arr[i].base,process_Obj_Module_Arr[i].size,pattern)));
如果符合条件的信息比较多,容易导致卡死,慎用。
在目标进程中的堆上申请size大小的内存,并且会按照Process.pageSize对齐,返回一个NativePointer,并且申请的内存如果在JavaScript里面没有对这个内存的使用的时候会自动释放的。也就是说,如果你不想要这个内存被释放,你需要自己保存一份对这个内存块的引用。
Memory.alloc(size): allocate size bytes of memory on the heap, or, if size is a multiple of Process.pageSize, one or more raw memory pages managed by the OS. The returned value is a NativePointer and the underlying memory will be released when all JavaScript handles to it are gone. This means you need to keep a reference to it while the pointer is being used by code outside the JavaScript runtime.
function frida_Memory_Alloc() {
Java.perform(function () {
const r = Memory.alloc(10);
console.log(hexdump(r, {
offset: 0,
length: 10,
header: true,
ansi: false
}));
});
}
setImmediate(frida_Memory_Alloc,0);
以上代码在目标进程中申请了10字节的空间
也可以使用:
Memory.allocUtf8String(str) 分配utf字符串
Memory.allocUtf16String 分配utf16字符串
Memory.allocAnsiString 分配ansi字符串
类似与c语言中的memcpy
const r = Memory.alloc(10);
//复制以module.base地址开始的10个字节 那肯定会是7F 45 4C 46...因为一个ELF文件的Magic属性如此。
Memory.copy(r,process_Obj_Module_Arr[i].base,10);
console.log(hexdump(r, {
offset: 0,
length: 10,
header: true,
ansi: false
}));
将字节数组写入一个指定内存,代码示例如下:
var arr = [ 0x62, 0x20, 0x75,0x20, 0x72,0x20,0x6E,0x20,0x69,0x20,0x6E,0x20,0x67];
//申请一个新的内存空间 返回指针 大小是arr.length
const r = Memory.alloc(arr.length);
//将arr数组写入R地址中
Memory.writeByteArray(r,arr);
//输出
console.log(hexdump(r, {
offset: 0,
length: arr.length,
header: true,
ansi: false
}));
结果如下:
将一个指定地址的数据,代码示例如下:
var buffer = Memory.readByteArray(r, arr.length);
//输出
console.log("Memory.readByteArray:");
console.log(hexdump(buffer, {
offset: 0,
length: arr.length,
header: true,
ansi: false
}));
结果同上。