diff --git a/agents/tracer/agent.ts b/agents/tracer/agent.ts index a8cadf8c..59bba212 100644 --- a/agents/tracer/agent.ts +++ b/agents/tracer/agent.ts @@ -646,7 +646,7 @@ class Agent { private includeAbsoluteInstruction(address: NativePointer, plan: TracePlan) { const module = plan.modules.find(address); if (module !== null) { - plan.native.set(address.toString(), ["insn", module.name, `insn_${address.sub(module.base).toString(16)}`]); + plan.native.set(address.toString(), ["insn", module.path, `insn_${address.sub(module.base).toString(16)}`]); } else { plan.native.set(address.toString(), ["insn", "", `insn_${address.toString(16)}`]); } diff --git a/apps/tracer/package-lock.json b/apps/tracer/package-lock.json index ca598188..7897da18 100644 --- a/apps/tracer/package-lock.json +++ b/apps/tracer/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "dependencies": { "@blueprintjs/core": "^5.12.0", - "@frida/react-use-r2": "^1.0.1", + "@frida/react-use-r2": "^1.0.2", "@monaco-editor/react": "^4.6.0", "monaco-editor": "^0.51.0", "react": "^18.3.1", @@ -542,9 +542,9 @@ } }, "node_modules/@frida/react-use-r2": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@frida/react-use-r2/-/react-use-r2-1.0.1.tgz", - "integrity": "sha512-xpjYPPTo/2s9C1hDd+7Dp0J0C26qKdjOFMGk9OVucmUvbwQIH+ath6wU4yz9jCsuzjYbjPRzpp+IVo57rADK8Q==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@frida/react-use-r2/-/react-use-r2-1.0.2.tgz", + "integrity": "sha512-Ygx2dLCRYZIoI7PbtDtMenjtfw26PmskqGtIGMm2qyL8lyZgR9XNnUYHWrtpLqO+fvjCtutDbnewIgYP8UcmNg==", "peerDependencies": { "react": "^18.3.1" } diff --git a/apps/tracer/package.json b/apps/tracer/package.json index 06aa64fc..e03f4ba8 100644 --- a/apps/tracer/package.json +++ b/apps/tracer/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@blueprintjs/core": "^5.12.0", - "@frida/react-use-r2": "^1.0.1", + "@frida/react-use-r2": "^1.0.2", "@monaco-editor/react": "^4.6.0", "monaco-editor": "^0.51.0", "react": "^18.3.1", diff --git a/apps/tracer/src/App.tsx b/apps/tracer/src/App.tsx index 2a15aafb..078e0941 100644 --- a/apps/tracer/src/App.tsx +++ b/apps/tracer/src/App.tsx @@ -86,6 +86,7 @@ export default function App() { diff --git a/apps/tracer/src/DisassemblyView.tsx b/apps/tracer/src/DisassemblyView.tsx index 450cc375..c94e1938 100644 --- a/apps/tracer/src/DisassemblyView.tsx +++ b/apps/tracer/src/DisassemblyView.tsx @@ -7,6 +7,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; export interface DisassemblyViewProps { target?: DisassemblyTarget; handlers: Handler[]; + onSelectTarget: SelectTargetRequestHandler; onSelectHandler: SelectHandlerRequestHandler; onAddInstructionHook: AddInstructionHookRequestHandler; } @@ -15,7 +16,7 @@ export type DisassemblyTarget = FunctionTarget | InstructionTarget; export interface FunctionTarget { type: "function"; - name: string; + name?: string; address: string; } @@ -24,12 +25,14 @@ export interface InstructionTarget { address: string; } +export type SelectTargetRequestHandler = (target: DisassemblyTarget) => void; export type SelectHandlerRequestHandler = (id: HandlerId) => void; export type AddInstructionHookRequestHandler = (address: bigint) => void; -export default function DisassemblyView({ target, handlers, onSelectHandler, onAddInstructionHook }: DisassemblyViewProps) { +export default function DisassemblyView({ target, handlers, onSelectTarget, onSelectHandler, onAddInstructionHook }: DisassemblyViewProps) { + const containerRef = useRef(null); const [rawR2Output, setRawR2Output] = useState(""); - const [_r2Ops, setR2Ops] = useState(new Map()); + const [r2Ops, setR2Ops] = useState(new Map()); const [r2Output, setR2Output] = useState([]); const [isLoading, setIsLoading] = useState(false); const highlightedAddressAnchorRef = useRef(null); @@ -46,34 +49,37 @@ export default function DisassemblyView({ target, handlers, onSelectHandler, onA const t = target; async function start() { - const command = (t.type === "function") - ? [ - `s ${target!.address}`, - "af-", - "af", - "afn base64:" + btoa(t.name), - "pdf", - "pdfj", - ].join("; ") - : `s ${t.address}; pd; pdj`; - let result = await executeR2Command(command); + const command = [ + `s ${target!.address}`, + ] + if (t.type === "function") { + command.push(...["af-", "af"]); + if (t.name !== undefined) { + command.push("afn base64:" + btoa(t.name)); + } + command.push(...["pdf", "pdfj"]); + } else { + command.push(...["pd", "pdj"]); + } + + let result = await executeR2Command(command.join(";")); if (ignore) { return; } - let lines = result.trimEnd().split("\n"); - if (lines[0] === "") { + if (result.startsWith("{")) { result = await executeR2Command("pd; pdj"); - lines = result.trimEnd().split("\n"); - } - if (ignore) { - return; + if (ignore) { + return; + } } + const lines = result.trimEnd().split("\n"); + setRawR2Output(lines.slice(0, lines.length - 1).join("\n")); const meta = JSON.parse(lines[lines.length - 1]); const opItems: R2Operation[] = Array.isArray(meta) ? meta : meta.ops; - const opByAddress = new Map(opItems.map(op => [op.offset, op])); + const opByAddress = new Map(opItems.map(op => [BigInt(op.offset), op])); setR2Ops(opByAddress); setIsLoading(false); @@ -100,13 +106,32 @@ export default function DisassemblyView({ target, handlers, onSelectHandler, onA lines = rawR2Output .split("
") - .map(line => line.replace(/\b0x[0-9a-f]+\b/, address => { - const handler = handlerByAddress.get(BigInt(address)); - const attrs = (handler !== undefined) - ? ` class="disassembly-address-has-handler" data-handler-id="${handler.id}"` - : ""; - return `${address}`; - })); + .map(line => { + let address: bigint | null = null; + line = line.replace(/\b0x[0-9a-f]+\b/, rawAddress => { + address = BigInt(rawAddress); + const handler = handlerByAddress.get(address); + const attrs = (handler !== undefined) + ? ` class="disassembly-address-has-handler" data-handler="${handler.id}"` + : ""; + return `${rawAddress}`; + }); + + if (address !== null) { + const op = r2Ops.get(address); + if (op !== undefined) { + const targetAddress = op.jump; + if (targetAddress !== undefined) { + const targetLabel = op.disasm.split(" ")[1]; + line = line.replace(targetLabel, _ => { + return `${targetLabel}`; + }); + } + } + } + + return line; + }); } else { lines = []; } @@ -124,7 +149,7 @@ export default function DisassemblyView({ target, handlers, onSelectHandler, onA const unhookedAddressMenu = useMemo(() => ( { const address = BigInt(highlightedAddressAnchorRef.current!.innerText); @@ -140,7 +165,7 @@ export default function DisassemblyView({ target, handlers, onSelectHandler, onA text="Go to handler" icon="arrow-up" onClick={() => { - const id: HandlerId = parseInt(highlightedAddressAnchorRef.current!.getAttribute("data-handler-id")!) + const id: HandlerId = parseInt(highlightedAddressAnchorRef.current!.getAttribute("data-handler")!) onSelectHandler(id); }} /> @@ -155,8 +180,23 @@ export default function DisassemblyView({ target, handlers, onSelectHandler, onA event.preventDefault(); + const branchTarget = target.getAttribute("data-target"); + if (branchTarget !== null) { + const anchor = containerRef.current!.querySelector(`a[data-address="${branchTarget}"]`); + if (anchor !== null) { + anchor.scrollIntoView(); + return; + } + + onSelectTarget({ + type: (target.getAttribute("data-type") === "call") ? "function" : "instruction", + address: branchTarget + }); + return; + } + showContextMenu({ - content: target.hasAttribute("data-handler-id") ? hookedAddressMenu : unhookedAddressMenu, + content: target.hasAttribute("data-handler") ? hookedAddressMenu : unhookedAddressMenu, onClose: handleAddressMenuClose, targetOffset: { left: event.clientX, @@ -175,7 +215,7 @@ export default function DisassemblyView({ target, handlers, onSelectHandler, onA } return ( -
+
{r2Output.map((line, i) =>
)}
); @@ -204,5 +244,7 @@ interface R2Operation { type: string; type_num: string; type2_num: string; + jump?: string; + fail?: string; reloc: boolean; } diff --git a/apps/tracer/src/model.ts b/apps/tracer/src/model.ts index 1e2d3fa2..504433bc 100644 --- a/apps/tracer/src/model.ts +++ b/apps/tracer/src/model.ts @@ -102,7 +102,7 @@ export function useModel() { return () => { ignore = true; }; - }, [selectedHandlerId, request]); + }, [selectedHandlerId, handlers, request]); const deployCode = useCallback(async (code: string) => { setHandlerCode(code);