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();
    }



}