Frida输出打印
console输出
- 在官方API由两种打印的方式,分别是console、send,我们先来学习非常简单的console,创建一个js文件,代码示例如下:
function hello_printf() {
Java.perform(function () {
console.log("");
console.log("hello-log");
console.warn("hello-warn");
console.error("hello-error");
});
}
setImmediate(hello_printf,0);
- 当文件创建好之后,我们需要运行frida-server,
~/Downloads: adb shell "/data/local/tmp/frida-server &"
- 然后执行如下代码,对app进程demo01,使用-l命令注入Chap03.js中代码:
~/Downloads/Frida: frida -U demo01 -l chap03.js
- 在Frida的console中有三个级别分别是log、warn、error。
log 正常
warn 警告
error 错误
hexdump
- 含义是打印内存中的地址,target参数可以是ArrayBuffer或者NativePointer,而options参数则是自定义输出格式可以填这几个参数offset、length、header、ansi。
function hello_printf() {
Java.perform(function () {
var libc = Module.findBaseAddress('libc.so');
console.log(hexdump(libc, {
offset:0,
length:64,
header:true,
ansi:true
}));
});
}
setImmediate(hello_printf,0);
- 查看结果
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
ae28d000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 .ELF............
ae28d010 03 00 03 00 01 00 00 00 00 00 00 00 34 00 00 00 ............4...
ae28d020 18 cb 0f 00 00 00 00 00 34 00 20 00 08 00 28 00 ........4. ...(.
ae28d030 1f 00 1c 00 06 00 00 00 34 00 00 00 34 00 00 00 ........4...4...
send
- send方法的回调函数是在python层定义的on_message,jscode内所有的信息都被监控(script.on(‘message’, on_message)),当输出信息on_message函数会拿到其数据在通过format转换。
- send最重要的功能也是最核心的是能够直接将数据以json格式输出。
import frida
import sys
jscode = """
Java.perform(function(){
var jni_env = Java.vm.getEnv();
console.log(jni_env);
send(jni_env);
})
"""
def on_message(message, data):
if message['type'] == 'send':
print("[*] {}".format(message['payload']))
else:
print(message)
process = frida.get_usb_device().attach('demo01')
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
input()
-
运行上述脚本。
-
可以看出这里两种方式输出的不同的效果,console直接输出了[object Object],无法输出其正常的内容,因为jni_env实际上是一个对象,但是使用send的时候会自动将对象转json格式输出。
Frida变量类型
- Int64(v) -> 定义一个有符号Int64类型的变量值v,参数v可以是字符串或者以0x开头的十六进制值。
- UInt64(v) -> 定义一个无符号Int64类型的变量值,参数v可以是字符串或者以0x开头的十六进制值。
- new NativePointer(s) -> 定义一个指针,指针地址为s
- ptr("0) -> 同上
- 代码示例:
Java.perform(function () {
console.log("new Int64(1):"+new Int64(1));
console.log("new UInt64(1):"+new UInt64(1));
console.log("new NativePointer(0xEC644071):"+new NativePointer(0xEC644071));
console.log("new ptr('0xEC644071'):"+new ptr(0xEC644071));
});
- 输出效果如下:
new Int64(1):1
new UInt64(1):1
new NativePointer(0xEC644071):0xec644071
new ptr('0xEC644071'):0xec644071
- frida也为Int64(v)提供一些相关的API:
- add(rhs)、sub(rhs)、and(rhs)、or(rhs)、xor(rhs) -> 加、减、逻辑运算
- shr(N)、shl(n) -> 向右/向左移位n位生成新的Int64
- Compare(Rhs) -> 返回整数比较结果
- toNumber() -> 转换为数字
- toString([radix=10]) -> 转换为可选基数的字符串(默认为10)
- 代码示例:
Java.perform(function(){
console.log("8888 + 1 ->"+ new Int64("8888").add(1));
console.log("8888 - 1:"+ new Int64("8888").sub(1));
console.log("8888 << 1 -> "+new Int64("8888").shr(1));
console.log("8888 == 22 ->" + new Int64("8888").compare(22));
console.log("8888 toString:"+ new Int64("8888").toString());
});
- 执行结果:
8888 + 1 ->8889
8888 - 1:8887
8888 << 1 -> 4444
8888 == 22 ->1
8888 toString:8888
RPC远程调用
- RPC的导出的函数使用在python层,使python层与js交互,代码如下:
import frida
jscode = """
rpc.exports = {
add:function(a,b) {
return a+b;
},
sub:function(a,b){
return new Promise(function (resolve){
setTimeout(function(){
resolve(a-b);
},100);
});
}
};
"""
def on_message(message, data):
if message['type'] == 'send':
print(message['payload'])
elif message['type'] == 'error':
print(message['stack'])
session = frida.get_usb_device().attach("demo01")
script = session.create_script(jscode)
script.on('message', on_message)
script.load()
print(script.exports.add(2,3))
print(script.exports.sub(5,3))
session.detach()
Process对象
- Process.id -> 返回附加目标进程的PID
- Process.isDebuggerAttached() -> 检测当前是否对目标程序已经附加
- Process.enumerateModules() -> 会枚举当前所有已经记载的so模块,并且返回数组Module对象。
- 示例代码如下:
function frida_Process(){
Java.perform(function () {
var process_obj_module_Arr = Process.enumerateModules();
for (var i=0; i < process_obj_module_Arr.length; i++)
{
console.log("",process_obj_module_Arr[i].name)
}
});
}
setImmediate(frida_Process,0);
- 运行结果:
libart-compiler.so
libvixl.so
libjavacore.so
libopenjdk.so
libopenjdkjvm.so
libmedia_jni.so
libmtp.so
libexif.so
libstagefright_amrnb_common.so
...
- Process.enumerateThreads():枚举当前所有的线程,返回包含以下属性的对象数组:
- id -> 线程id
- state -> 当前运行状态有running, stopped, waiting, uninterruptible or halted
- context -> 带有键pc和sp的对象,它们是分别为ia32/x64/arm指定EIP/RIP/PC和ESP/RSP/SP的NativePointer对象。也可以使用其他处理器特定的密钥,例如eax、rax、r0、x0等
- 示例代码如下:
function frida_Process(){
Java.perform(function () {
var enumerateThreads = Process.enumerateThreads();
for (var i=0; i < enumerateThreads.length; i++)
{
console.log("id -> " + enumerateThreads[i].id);
console.log("state -> " + enumerateThreads[i].state);
console.log("context -> "+ JSON.stringify(enumerateThreads[i].context));
}
});
}
setImmediate(frida_Process,0);
- 运行结果如下:
- Process.getCurrentThreadId():获取此线程的操作系统特定 ID 作为数字.
Module对象
- Module对象的属性
- name -> 模块名称
- base -> 模块地址,其变量类型为NativePointer
- size -> 大小
- path -> 完整文件系统路径
- Module对象的API
- Module.load() -> 加载指定so文件,返回一个Module对象
- enumerateImports() -> 枚举所有Import库函数,返回Module数组对象
- enumerateExports() -> 枚举所有Export库函数,返回Module数组对象
- enumerateSymbols() -> 枚举所有Symbol库函数,返回Module数组对象
- Module.findExportByName(exportName)、Module.getExportByName(exportName) -> 寻找指定so中export库中的函数地址
- Module.findBaseAddress(name)、Module.getBaseAddress(name) -> 返回so的基地址
- Module.load()的示例代码如下:
function frida_Process(){
Java.perform(function () {
const hooks = Module.load('libc.so');
console.log("module name :", hooks.name);
console.log("module address :", hooks.base);
console.log("module size :", hooks.size);
console.log("module path :", hooks.path);
});
}
setImmediate(frida_Process,0);
- 运行结果如图:
module name : libc.so
module address : 0xae28d000
module size : 929792
module path : /system/lib/libc.so
- Process.EnumererateModules()示例如下:
function frida_Process(){
Java.perform(function () {
var process_Obj_Module_Arr = Process.enumerateModules();
for(var i = 0; i < process_Obj_Module_Arr.length; i++)
{
console.log("module name :", process_Obj_Module_Arr[i].name);
console.log("module address :", process_Obj_Module_Arr[i].base);
console.log("module size :", process_Obj_Module_Arr[i].size);
console.log("module path :", process_Obj_Module_Arr[i].path);
console.log("----------------------------------------------");
}
});
}
setImmediate(frida_Process,0);
- 运行结果如图所示:
- enumerateExports() -> 该API会枚举模块中所有中的所有Export函数,示例代码如下。
function frida_Process(){
Java.perform(function () {
var hooks = Module.load('libc.so');
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("addresss -> ", exports[i].address);
console.log('----------------------------------')
}
});
}
setImmediate(frida_Process,0);
- 运行结果如下:
- enumerateSymbols()代码示例如下:
function frida_Process(){
Java.perform(function () {
var hooks = Module.load('libc.so');
var Symbols = hooks.enumerateSymbols();
for(var i=0; i<Symbols.length;i++) {
console.log("isGlobal -> ", Symbols[i].isGlobal);
console.log("type -> ", Symbols[i].type);
console.log("section -> ", Symbols[i].section);
console.log("name -> ", Symbols[i].name);
console.log("address -> ", Symbols[i].address);
console.log("----------------------------------");
}
});
}
setImmediate(frida_Process,0);
- 运行结果如图:
- Module.findExportByName(soName,exportName), Module.getExportByName(soName,exportName) -> 返回so文件中Export函数库中函数名称为exportName函数的绝对地址。
- 示例代码如下:
function frida_Process(){
Java.perform(function () {
console.log( Module.getExportByName('libc.so','read'));
console.log( Module.findExportByName('libc.so','read'));
console.log("-------------------------")
});
}
setImmediate(frida_Process,0);
- 运行结果如图:
- Module.findBaseAddress(name)、Module.getBaseAddress(name) -> 返回name模块的基地址
function frida_Process(){
Java.perform(function () {
console.log( Module.findBaseAddress('libc.so'));
console.log( Module.getBaseAddress('libc.so'));
console.log("-------------------------");
});
}
setImmediate(frida_Process,0);
- 运行结果如图:
Memory对象
- Memory的一些API通常是对内存处理,譬如Memory.copy()复制内存,又如writeByteArray写入字节到指定内存中。
- Memory.scan -> 搜索内存中以address地址开始,搜索长度为size,需要搜是条件是pattern,callbacks搜索之后的回调函数;此函数相当于搜索内存的功能。
- 代码示例如下:
function frida_Memory(){
Java.perform(function () {
var module = Process.findModuleByName('libc.so');
var pattern = "03 49 ?? 50 20 44";
console.log("base -> "+ module.base);
//从so的基址开始搜索,搜索大小为so文件的大小,搜指定条件03 49 ?? 50 20 44的数据
Memory.scan(module.base, module.size, pattern, {
onMatch:function (address, size) {
console.log("搜索到:"+pattern+"地址是:"+address.toString())
},
onError:function(){
console.log("搜索失败");
},
onComplete:function () {
console.log("search complete!!");
}
});
});
}
setImmediate(frida_Memory,0);
- onMatch:function(address,size):使用包含作为NativePointer的实例地址的address和指定大小为数字的size调用,此函数可能会返回字符串STOP以提前取消内存扫描。
- onError:Function(Reason):当扫描时出现内存访问错误时使用原因调用。
- onComplete:function():当内存范围已完全扫描时调用。
- 运行结果如图:
- Memory.scanSync() -> 功能与Memory.scan一样,只不过它是返回多个匹配到条件的数据。
- 实例代码:
function frida_Memory(){
Java.perform(function () {
var module = Process.findModuleByName('libc.so');
var pattern = "50 20 44";
console.log("base -> "+ module.base);
var scanSync = Memory.scanSync(module.base,module.size,pattern);
console.log("scanSync -> " + JSON.stringify(scanSync));
});
}
setImmediate(frida_Memory,0);
- Memory.alloc() -> 在目标进程中的堆上申请size大小的内存,并且会按照Process.pageSize对齐,返回一个NativePointer,并且申请的内存如果在JavaScript里面没有对这个内存的使用的时候会自动释放的。也就是说,如果你不想要这个内存被释放,你需要自己保存一份对这个内存块的引用。
- 实例如下:
function frida_Memory(){
Java.perform(function () {
var r = Memory.alloc(10);
console.log(hexdump(r,{
offset:0,
length:10,
header:true,
ansi:false
}));
});
}
setImmediate(frida_Memory,0);
- 以上代码在目标进程申请了10个字节的空间,运行效果如下:
- 内存复制Memory.copy()
function frida_Memory(){
Java.perform(function () {
var module = Process.findModuleByName('libc.so');
console.log(hexdump(module.base,{
offset:0,
length:10,
header:true,
ansi:false
}));
const r = Memory.alloc(10);
//复制以module.base地址开始的10个字节 那肯定会是7F 45 4C 46...因为一个ELF文件的Magic属性如此。
Memory.copy(r, module.base, 10);
console.log(hexdump(r,{
offset:0,
length:10,
header:true,
ansi:false
}));
console.log("--------------------------")
});
}
setImmediate(frida_Memory,0);
- 运行结果如图
- Memory.writeByteArray() -> 将字节数组写入一个指定内存,代码示例如下:
function frida_Memory(){
Java.perform(function () {
var arr = [0x72, 0x6F, 0x79, 0x73, 0x75, 0x65];
const r = Memory.alloc(arr.length);
Memory.writeByteArray(r,arr);
console.log(hexdump(r,{
offset:0,
length:arr.length,
header:true,
ansi:false
}));
console.log("------------------")
});
}
setImmediate(frida_Memory,0);
- 运行结果如图
- Memory.readByteArray() -> 读取制定地址的数据
function frida_Memory(){
Java.perform(function () {
var arr = [0x72, 0x6F, 0x79, 0x73, 0x75, 0x65];
const r = Memory.alloc(arr.length);
Memory.writeByteArray(r,arr);
var buffer = Memory.readByteArray(r,arr.length);
console.log("Memory readByteArray:")
console.log(hexdump(buffer,{
offset:0,
length:arr.length,
header:true,
ansi:false
}));
console.log("------------------")
});
}
setImmediate(frida_Memory,0);
- 效果如图所示: