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.
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对应的汇编代码.
varvm = newQBDI();
varstate = vm.getGPRState();
vm.allocateVirtualStack(state, 0x1000000);
varfuncPtr = Module.findExportByName(null, "aFunction");
vm.addInstrumentedModuleFromAddr(funcPtr);
varicbk = vm.newInstCallback(function(vm, gpr, fpr, data) {
varinst = vm.getInstAnalysis();
// Display instruction dissassembly
fmt= "0x"+ inst.address.toString(16) + " "+ inst.disassembly;
console.log(fmt);
returnVMAction.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是基于动态重新编译的代码跟踪器。 它将代码指令复制到内存中的另一个位置,在该位置对其进行调整以适应新位置并包括其他跟踪指令。 如果应用程序在原始位置检查其代码,则会发现该代码是完整无缺的,因为它是被篡改的代码的副本。
下图显示了函数如何在其新的内存位置进行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){
intn = 0;
printf("fn is at %p\n",&fn);
while(1){
fn(n++);
sleep(3);
}
return0;
}
Frida脚本(重点在transform):
varapp = newModuleMap(isAppAddress);Process.enumerateThreadsSync().forEach(function(thread){
Stalker.follow(thread.id,{
transform: function (iterator) {
varinstruction = 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) {
returnmodule.path.indexOf("a.out") != -1;
}
运行结果:关闭系统的ASLR(地址随机化),对比指令和地址,显然是对应的。基于此我们可以定位到fn函数被执行了。如果能够实施跟踪寄存器的值就更好了,这个也很容易实现,只需要添加下面的代码就可以了。基于此便可以实现类似IDA的trace功能。
iterator.putCallout((context) =>{
console.log(JSON.stringify(context))
})
从使用者的角度,每当执行一个基本快,stalker都会做以下事情:
1.对于方法调用,保存 lr 等必要信息2.重定位位置相关指令,例如:ADR Xd, label3.建立此块的索引,如果此块在达到可信阈值后,内容未曾变化,下次将不再重新编译(为了加快速度)4.根据 transform 函数,编译生成一个新的基本块 GumExecBlock ,保存到 GumSlab 。5.在上一过程中,可通过 void transform(GumStalkerIterator iterator, GumStalkerOutput output, gpointer user_data) 来读取,改动,写入指令。6.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 信息。7.执行一个基本快 GumExecBlock,开始下一个基本快
sleep函数定义:
相关函数:signal, alarm 头文件:#include <unistd.h> 定义函数:unsigned int sleep(unsigned int seconds); 函数说明:sleep()会令目前的进程暂停, 直到达到参数seconds 所指定的时间, 或是被信号所中断. 返回值:若进程/线程挂起到参数所指定的时间则返回0,若有信号中断则返回剩余秒数。
这里trap sleep函数, step into的时候不正是重新进入一个函数吗.
varsleep = newNativeFunction(
Module.getExportByName(null, 'sleep'),
'uint', ['int'],
{ traps: 'all'}
);Stalker.follow({
events: {
call: true
},
onReceive: function (e) {
console.log(JSON.stringify(Stalker.parse(e)));
}
});
varresult = 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.
function start_stalker(mainThreadId, start, size){
Stalker.follow(mainThreadId, {
transform: function (iterator) {
varinstruction = iterator.next();
conststartAddress = 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相关的内容,欢迎关注我的微信公众号:无情剑客。