Stalker是Frida的代码跟踪引擎.下面的内容来自Frida官网. 在加密解密,trace,代码定位方面用途还是挺大的.
Stalker is Frida’s code tracing engine. It allows threads to be
followed, capturing every function, every block, even every
instruction which is executed
类似Frida的工具还有QBDI.
QBDI
QuarkslaB Dynamic binary Instrumentation (QBDI) is a modular, cross-platform and cross-architecture DBI framework. It aims to support Linux, macOS, Android, iOS and Windows operating systems running on x86, x86-64, ARM and AArch64 architectures.
QBDI也能够和Frida完美结合.举个例子,打印导出函数aFunction对应的汇编代码.
var vm = new QBDI();
var state = vm.getGPRState();
vm.allocateVirtualStack(state, 0x1000000);
var funcPtr = Module.findExportByName(null, "aFunction");
vm.addInstrumentedModuleFromAddr(funcPtr);
var icbk = vm.newInstCallback(function(vm, gpr, fpr, data) {
var inst = vm.getInstAnalysis();
// Display instruction dissassembly
fmt = "0x" + inst.address.toString(16) + " " + inst.disassembly;
console.log(fmt);
return VMAction.CONTINUE;
});
vm.addCodeCB(InstPosition.PREINST, icbk);
vm.call(funcPtr, [42]);
QBDI在Android平台下使用需要root以及关闭SELinux.
host$ adb root
host$ adb shell setenforce 0
参考
https://frida.re/news/2019/12/18/frida-12-8-released/
https://bbs.pediy.com/thread-264680.htm
https://frida.re/docs/stalker/
Frida工作原理
https://github.com/NMHai/frida-qbdi-fuzzer
Stalker实现
基本原理
Stalker是基于动态重新编译的代码跟踪器。 它将代码指令复制到内存中的另一个位置,在该位置对其进行调整以适应新位置并包括其他跟踪指令。 如果应用程序在原始位置检查其代码,则会发现该代码是完整无缺的,因为它是被篡改的代码的副本。
下图显示了函数如何在其新的内存位置进行transform以包括代码跟踪.
在这里插入图片描述练习一下
a.out程序源码:
#include<stdio.h>
#include<unistd.h>
static int fn(int n){
printf("number is %d\n",n);
}
int main(int argc, char ** argv){
int n = 0;
printf("fn is at %p\n",&fn);
while(1){
fn(n++);
sleep(3);
}
return 0;
}
Frida脚本(重点在transform):
var app = new ModuleMap(isAppAddress);
Process.enumerateThreadsSync().forEach(function(thread){
Stalker.follow(thread.id,{
transform: function (iterator) {
var instruction = iterator.next();
if (!app.has(instruction.address)) {
do{
iterator.keep();
} while ((iterator.next()) !== null);
return ;
}
do{
console.log(instruction.address + ":" + instruction);
iterator.keep();
} while ((instruction = iterator.next()) !== null);
}
})
})
function isAppAddress(module) {
return module.path.indexOf("a.out") != -1;
}
运行结果:
关闭系统的ASLR(地址随机化),对比指令和地址,显然是对应的。基于此我们可以定位到fn函数被执行了。
在这里插入图片描述 如果能够实施跟踪寄存器的值就更好了,这个也很容易实现,只需要添加下面的代码就可以了。基于此便可以实现类似IDA的trace功能。
iterator.putCallout((context) => {
console.log(JSON.stringify(context))
})
stalker做了什么
从使用者的角度,每当执行一个基本快,stalker都会做以下事情:
- 对于方法调用,保存 lr 等必要信息
- 重定位位置相关指令,例如:ADR Xd, label
- 建立此块的索引,如果此块在达到可信阈值后,内容未曾变化,下次将不再重新编译(为了加快速度)
- 根据 transform 函数,编译生成一个新的基本块 GumExecBlock ,保存到 GumSlab 。
- 在上一过程中,可通过 void transform(GumStalkerIterator iterator, GumStalkerOutput output, gpointer user_data) 来读取,改动,写入指令。
- transform 过程中可通过 void gum_stalker_iterator_put_callout (GumStalkerIterator self,GumStalkerCallout callout, gpointer data, GDestroyNotify data_destroy) 来设置一个当此位置被执行到时的 callout。通过此 void callout(GumCpuContext cpu_context, gpointer user_data) 获取 cpu 信息。
- 执行一个基本快 GumExecBlock,开始下一个基本快
trap功能
sleep函数定义:
相关函数:signal, alarm
头文件:#include <unistd.h>
定义函数:unsigned int sleep(unsigned int seconds);
函数说明:sleep()会令目前的进程暂停, 直到达到参数seconds 所指定的时间, 或是被信号所中断.
返回值:若进程/线程挂起到参数所指定的时间则返回0,若有信号中断则返回剩余秒数。
这里trap sleep函数, step into的时候不正是重新进入一个函数吗.
var sleep = new NativeFunction(
Module.getExportByName(null, 'sleep'),
'uint', ['int'],
{ traps: 'all' }
);
Stalker.follow({
events: {
call: true
},
onReceive: function (e) {
console.log(JSON.stringify(Stalker.parse(e)));
}
});
var result = sleep(4);
console.log('sleep', result);
By setting the traps: 'all' option on the NativeFunction, it will re-activate Stalker when called from a thread where Stalker is temporarily paused because it’s calling out to an excluded range – which is the case here because all of frida-agent’s code is marked as excluded.
stalker实践
function start_stalker(mainThreadId, start, size){
Stalker.follow(mainThreadId, {
transform: function (iterator) {
var instruction = iterator.next();
const startAddress = instruction.address;
var isModule = startAddress.compare(start) >= 0 && startAddress.compare(start.add(size)) < 0;
do{
if (isModule){
console.log(instruction.address + ":" + instruction);
}
iterator.keep();
} while ((instruction = iterator.next()) !== null);
}
});
}
写在最后
在接下来的文章中,会详细介绍Frida中Stalker的工作原理以及C模块。同时基于Frida的Stalker进行软件的破解。
有兴趣可以比较下下面这两段代码是否有区别。
do{
if (isModule){
console.log(instruction.address + ":" + instruction);
}
iterator.keep();
} while ((instruction = iterator.next()) !== null);
和
if (!isModule) {
do{
iterator.keep();
} while ((iterator.next()) !== null);
return ;
}
do{
console.log(instruction.address + ":" + instruction);
iterator.keep();
} while ((instruction = iterator.next()) !== null);
公众号
更多Frida相关的内容,欢迎关注我的微信公众号:无情剑客。
burning_gzh.png
网友评论