1. 介绍
在Unidbg中,可以使用emulator.getUnwinder().unwind();
来打印调用栈,但是该方法默认就是打印,没有办法对调用栈中的地址进行过滤。例如想要判断是否来自某个函数,就需要对调用栈中所有地址进行过滤,然后判断地址是否处于[FuncAddr, FuncAddr + FuncSize]区间内。因此我就写了个小工具,基于Frame Pointer,遍历调用栈,保存为一个List,以供后续使用。
例如我想想hook chdir,打印一下参数,但是只想打印来源于目标函数内调用,其他函数的调用不打印,使用示例:
public void hook_chdir(){
long funcAddr = 0x29CE4;
long funcSize = 0x1F60;
emulator.attach().addBreakPoint(module.base + 0xD030, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
RegisterContext context = (RegisterContext) emulator.getContext();
UnidbgPointer arg0 = context.getPointerArg(0);
String path = arg0.getString(0);
UnidbgPointer LR = context.getLRPointer();
// 调用,获取保存栈信息的列表
List<StackUnwinder.StackFrame> stackFrameList = StackUnwinder.unwindstack(emulator);
for (StackUnwinder.StackFrame stackFrame : stackFrameList) {
// 分割字符串,获取调用处的地址
String[] parts = stackFrame.toString().split("->");
String hexStr = parts[1].trim();
long callFrom = Long.parseLong(hexStr.substring(2), 16);
//在这里过滤,如果是目标函数中调用的就输出
if (funcAddr < callFrom && callFrom < funcAddr + funcSize) {
System.out.printf("chdir path: %s\n", path);
System.out.println(stackFrame + "[*]");
}else{
System.out.println(stackFrame);
}
}
return false;
}
});
}
打印结果如下:
控制台bt
输出如下,和上面的调用栈结果基本一致:
2. 原理
原理是很简单的,简单介绍一下Frame Pointer,缩写FP,中文名叫栈帧指针,也叫FP寄存器。FP中保存的是调用者函数的FP寄存器的地址,而FP和LR在栈里面的位置是相邻的,在ARM64中,通过FP的地址+8就可以得到LR寄存器的地址。
因此不停循环遍历FP,并存储LR就可以完成栈回溯功能。
3. 代码实现
关键逻辑在函数unwindStackARM64
中,通过循环遍历FP,FP+8即可获取LR地址,读取LR中数据并-4(ARM指令长度)即可得到调用位置,存储在列表中,以供后续使用。完整代码如下
package com.tools;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.arm.context.RegisterContext;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.pointer.UnidbgPointer;
import unicorn.Arm64Const;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
public class StackUnwinder {
public static class StackFrame{
// 返回地址
public final long lr;
public final String mouduleName;
public final long modulebase;
public StackFrame(long lr, String mouduleName, long modulebase) {
this.lr = lr;
this.mouduleName = mouduleName;
this.modulebase = modulebase;
}
@Override
public String toString() {
return String.format("call from [%s] -> 0x%x ", mouduleName, lr - modulebase);
}
}
/**
* 栈回溯主方法 (只支持64位)
* @param emulator Unidbg 模拟器实例
* @return 包含 模块名称 LR 和函数名的栈帧列表
*/
public static List<StackFrame> unwindstack(Emulator emulator) {
Backend backend = emulator.getBackend();
// boolean isArm64 = (emulator instanceof ARMEmulator) && ((ARMEmulator<?>) emulator).is64Bit();
// return isArm64 ? unwindStackARM64(emulator) : unwindStackARM32(emulator);
return unwindStackARM64(emulator);
}
/**
* ARM32 栈回溯实现
*/
private static List<StackFrame> unwindStackARM32(Emulator<?> emulator) {
List<StackFrame> stack = new ArrayList<>();
Backend backend = emulator.getBackend();
RegisterContext context = emulator.getContext();
Memory memory = emulator.getMemory();
// ARM64 寄存器定义
int FP_REG = 11; // X29 作为 FP
int addrSize = 4; // 64 位地址长度
long currentFP = (long) backend.reg_read(FP_REG);
int maxDepth = 20;
UnidbgPointer LR = context.getLRPointer();
Module module = memory.findModuleByAddress(LR.peer);
stack.add(new StackFrame(LR.peer-2, module.name, module.base));
for (int i = 0; i < maxDepth && currentFP != 0; i++) {
try {
// 读取栈帧中的上一级 FP 和 LR (FP 指向保存的 FP, FP+8 指向 LR)
// 读取 FP 和 LR 的字节数组
byte[] fpBytes = backend.mem_read(currentFP, addrSize);
byte[] lrBytes = backend.mem_read(currentFP + addrSize, addrSize);
// 将字节数组转换为小端长整数
long savedFP = bytesToUInt(fpBytes);
long savedLR = bytesToUInt(lrBytes) - 2;
if(savedFP == 0) {
break;
}
// 添加到列表
module = memory.findModuleByAddress(savedLR);
// 解析函数名
stack.add(new StackFrame(savedLR, module.name, module.base));
// 更新 FP
currentFP = savedFP;
} catch (Exception e) {
break; // 无效 FP 地址,终止回溯
}
}
return stack;
}
/**
* ARM64 栈回溯实现
*/
private static List<StackFrame> unwindStackARM64(Emulator<?> emulator) {
List<StackFrame> stack = new ArrayList<>();
Backend backend = emulator.getBackend();
Memory memory = emulator.getMemory();
RegisterContext context = emulator.getContext();
// ARM64 寄存器定义
int FP_REG = 29; // X29 作为 FP
int addrSize = 8; // 64 位地址长度
long currentFP = (long) backend.reg_read(Arm64Const.UC_ARM64_REG_X29);; // 初始 FP 值
int maxDepth = 20;
UnidbgPointer LR = context.getLRPointer();
Module module = memory.findModuleByAddress(LR.peer);
stack.add(new StackFrame(LR.peer-4, module.name, module.base));
for (int i = 0; i < maxDepth && currentFP != 0; i++) {
try {
// 读取栈帧中的上一级 FP 和 LR (FP 指向保存的 FP, FP+8 指向 LR)
// 读取 FP 和 LR 的字节数组
byte[] fpBytes = backend.mem_read(currentFP, addrSize);
byte[] lrBytes = backend.mem_read(currentFP + addrSize, addrSize);
// 将字节数组转换为小端长整数
long savedFP = bytesToULong(fpBytes);
long savedLR = bytesToULong(lrBytes)- 4;
// System.out.println("LR: " + Long.toHexString(savedLR));
if(savedFP == 0) {
break;
}
module = memory.findModuleByAddress(savedLR);
// 解析函数名
stack.add(new StackFrame(savedLR, module.name, module.base));
// 更新 FP
currentFP = savedFP;
} catch (Exception e) {
break; // 无效 FP 地址,终止回溯
}
}
return stack;
}
/**
* 解析 4 字节小端字节序为无符号整数
*/
private static long bytesToUInt(byte[] bytes) {
if (bytes.length != 4) {
throw new IllegalArgumentException("Invalid 4-byte array");
}
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
return buffer.getInt() & 0xFFFFFFFFL;
}
/**
* 解析 8 字节小端字节序为无符号长整数
*/
private static long bytesToULong(byte[] bytes) {
if (bytes.length != 8) {
throw new IllegalArgumentException("Invalid 8-byte array");
}
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
return buffer.getLong();
}
}