Stalker的利用方式
Frida的Stalker的利用方式,目前算明白了,至少有2种:
- (1)给特定位置加上额外的代码
- (2)针对特定位置,去调试输出打印相关值
- 查看寄存器的值
- (读取)查看内存的值
- 等等
举例说明:
(1)给特定位置加上额外的代码
官网的例子:
JavaScript API | Frida • A world-class dynamic instrumentation toolkit
//
// Advanced users: This is how you can plug in your own
// StalkerTransformer, where the provided
// function is called synchronously
// whenever Stalker wants to recompile
// a basic block of the code that's about
// to be executed by the stalked thread.
//
//transform(iterator) {
// let instruction = iterator.next();
//
// const startAddress = instruction.address;
// const isAppCode = startAddress.compare(appStart) >= 0 &&
// startAddress.compare(appEnd) === -1;
//
// do {
// if (isAppCode && instruction.mnemonic === 'ret') {
// iterator.putCmpRegI32('eax', 60);
// iterator.putJccShortLabel('jb', 'nope', 'no-hint');
//
// iterator.putCmpRegI32('eax', 90);
// iterator.putJccShortLabel('ja', 'nope', 'no-hint');
//
// iterator.putCallout(onMatch);
//
// iterator.putLabel('nope');
// }
//
// iterator.keep();
// } while ((instruction = iterator.next()) !== null);
//},
//
// The default implementation is just:
//
// while (iterator.next() !== null)
// iterator.keep();
//
// The example above shows how you can insert your own code
// just before every `ret` instruction across any code
// executed by the stalked thread inside the app's own
// memory range. It inserts code that checks if the `eax`
// register contains a value between 60 and 90, and inserts
// a synchronous callout back into JavaScript whenever that
// is the case. The callback receives a single argument
// that gives it access to the CPU registers, and it is
// also able to modify them.
//
// function onMatch (context) {
// console.log('Match! pc=' + context.pc +
// ' rax=' + context.rax.toInt32());
// }
//
// Note that not calling keep() will result in the
// instruction getting dropped, which makes it possible
// for your transform to fully replace certain instructions
// when this is desirable.
//
意思是:
此处希望实现的特定效果
当发现是ret指令,即在ret真正运行,函数返回值,再去:
插入对应的代码:
// iterator.putCmpRegI32('eax', 60);
// iterator.putJccShortLabel('jb', 'nope', 'no-hint');
//
// iterator.putCmpRegI32('eax', 90);
// iterator.putJccShortLabel('ja', 'nope', 'no-hint’);
// iterator.putLabel('nope');
实现特定的逻辑:
检查eax寄存器是否包含 60 到 90 之间的值
-》如果你有类似需求,也可以去实现:
当满足条件(比如某个特定的指令,具体到某行代码),去插入特定代码,实现你的特定的逻辑
(2)针对特定位置,去调试输出打印相关值
比如我前面的:
___lldb_unnamed_symbol2575$$akd
-》核心代码是:
var funcRelativeStartAddr = 0xa0460;
var functionSize = 0x24C8; // 9416 == 0x24C8
var funcRelativeEndAddr = funcRelativeStartAddr + functionSize;
console.log("funcRelativeStartAddr=" + funcRelativeStartAddr + ", functionSize=" + functionSize + ", funcRelativeEndAddr=" + funcRelativeEndAddr);
const moduleName = "akd";
const moduleBaseAddress = Module.findBaseAddress(moduleName);
console.log("moduleName=" + moduleName + ", moduleBaseAddress=" + moduleBaseAddress);
// console.log("moduleName=%s, moduleBaseAddress=%p", moduleName, moduleBaseAddress);
const funcRealStartAddr = moduleBaseAddress.add(funcRelativeStartAddr);
// var funcRealEndAddr = funcRealStartAddr + functionSize;
const funcRealEndAddr = funcRealStartAddr.add(functionSize);
console.log("funcRealStartAddr=" + funcRealStartAddr + ", funcRealEndAddr=" + funcRealEndAddr);
Interceptor.attach(funcRealStartAddr, {
onEnter: function(args) {
var arg0 = args[0]
var arg1 = args[1]
var arg2 = args[2]
var arg3 = args[3]
console.log("----- arg0=" + arg0 + ", arg1=" + arg1 + ", arg2=" + arg2 + ", arg3=" + arg3);
var curTid = Process.getCurrentThreadId();
console.log("curTid=", curTid);
Stalker.follow(curTid, {
events: {
call: true, // CALL instructions: yes please
ret: false, // RET instructions
exec: false, // all instructions: not recommended as it's
block: false, // block executed: coarse execution trace
compile: false // block compiled: useful for coverage
},
// transform: (iterator: StalkerArm64Iterator) => {
transform: function (iterator) {
var instruction = iterator.next();
const startAddress = instruction.address;
const gt_realStartAddr = startAddress.compare(funcRealStartAddr) >= 0
const lt_realEndAddr = startAddress.compare(funcRealEndAddr) < 0
var isAppCode = gt_realStartAddr && lt_realEndAddr
console.log("+++ into iterator: startAddress=" + startAddress + ", isAppCode=" + isAppCode);
do {
if (isAppCode) {
// is origal function code = which we focus on
var curRealAddr = instruction.address;
// const isAppCode = curRealAddr.compare(funcRealStartAddr) >= 0 && curRealAddr.compare(funcRealEndAddr) === -1;
// console.log(curRealAddr + ": isAppCode=" + isAppCode);
var curOffsetHexPtr = curRealAddr.sub(funcRealStartAddr)
var curOffsetInt = curOffsetHexPtr.toInt32()
// var instructionStr = instruction.mnemonic + " " + instruction.opStr
var instructionStr = instruction.toString()
console.log("\t" + curRealAddr + " <+" + curOffsetInt + ">: " + instructionStr);
if (curOffsetInt == 8516) {
console.log("curOffsetInt=" + curOffsetInt);
iterator.putCallout((context) => {
var contextStr = JSON.stringify(context)
console.log("contextStr=" + contextStr);
var x9Value1 = context.x9
var x9Value2 = context["x9"]
console.log("x9Value1=" + x9Value1 + ", x9Value2=" + x9Value2)
});
}
}
iterator.keep();
} while ((instruction = iterator.next()) !== null);
}
});
其中就是:
当判断到是 +8516 行代码时,去查看x9的值(是否是我们希望的,最后所要返回的值)
所以就去判断:
- 是否是改行代码
if (curOffsetInt == 8516) {
- 如果是,则去:
- 通过
putCallout
,传入匿名函数,其中去调试- 去打印
x9
寄存器的值
- 去打印
- 通过