Skip to content

Commit

Permalink
Wire up some more
Browse files Browse the repository at this point in the history
  • Loading branch information
oleavr committed Sep 21, 2024
1 parent 8b2fade commit 0d503be
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 41 deletions.
13 changes: 12 additions & 1 deletion agents/tracer/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class Agent {
message: e.message
});
});

return Process.mainModule;
}

dispose() {
Expand Down Expand Up @@ -98,6 +100,14 @@ class Agent {
return [...nativeIds, ...javaIds];
}

readMemory(address: string, size: number): ArrayBuffer | null {
try {
return ptr(address).readByteArray(size);
} catch (e) {
return null;
}
}

private cropStagedPlan(plan: TracePlan, id: StagedItemId): TracePlan {
let candidateId: StagedItemId;

Expand Down Expand Up @@ -993,5 +1003,6 @@ rpc.exports = {
dispose: agent.dispose.bind(agent),
update: agent.update.bind(agent),
stageTargets: agent.stageTargets.bind(agent),
commitTargets: agent.commitTargets.bind(agent,)
commitTargets: agent.commitTargets.bind(agent),
readMemory: agent.readMemory.bind(agent),
};
3 changes: 2 additions & 1 deletion apps/tracer/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export default function App() {

spawnedProgram,
respawn,
mainModule,

handlers,
selectedScope,
Expand Down Expand Up @@ -61,7 +62,7 @@ export default function App() {
);

const disassemblyView = (
<DisassemblyView />
<DisassemblyView mainModule={mainModule} />
);

return (
Expand Down
61 changes: 28 additions & 33 deletions apps/tracer/src/DisassemblyView.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,45 @@
import "./DisassemblyView.css";
import loadR2 from "./r2.js";
import { useRef } from "react";
import { NativeModule } from "./model.js";
import { useR2 } from "./use-r2.js";
import { useEffect, useRef, useState } from "react";
import { useStayAtBottom } from "react-stay-at-bottom";

let r2Output = "";
export interface DisassemblyViewProps {
mainModule?: NativeModule | null;
}

export default function DisassemblyView() {
export default function DisassemblyView({ mainModule = null }: DisassemblyViewProps = {}) {
const containerRef = useRef<HTMLDivElement>(null);
const [r2Output, setR2Output] = useState("");
const { executeR2Command } = useR2();

useStayAtBottom(containerRef, {
initialStay: true,
autoStay: true
});

return (
<div ref={containerRef} className="disassembly-view" dangerouslySetInnerHTML={{__html: r2Output}} />
);
}
useEffect(() => {
if (mainModule === null) {
return;
}

let ignore = false;

async function start() {
const r2 = await loadR2({
noInitialRun: true,
offset: 0,
async onRead(offset: number, size: number) {
const result = new Uint8Array(size);
for (let i = 0; i !== size; i++) {
result[i] = (i % 2 === 0) ? 0x13 : 0x37;
async function start() {
const result = await executeR2Command(`s ${mainModule!.base}; x`);
if (!ignore) {
setR2Output(result);
}
return result;
},
});
}

await r2.ccall("main", "int", [], { async: true });
start();

const _run = r2.cwrap("evaluate", "number", ["string"], { async: true });
return () => {
ignore = true;
};
}, [mainModule]);

const rawResult = await _run("x");
try {
const result = r2.UTF8ToString(rawResult);
r2Output = result;
} finally {
r2._free(rawResult)
}
return (
<div ref={containerRef} className="disassembly-view" dangerouslySetInnerHTML={{ __html: r2Output }} />
);
}

start()
.catch(e => {
console.error("Unable to load r2:", e);
});
39 changes: 34 additions & 5 deletions apps/tracer/src/model.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { useEffect, useState } from "react";
import { useR2 } from "./use-r2.js";
import { useCallback, useEffect, useState } from "react";
import useWebSocket, { ReadyState } from "react-use-websocket";

const SOCKET_URL = (import.meta.env.MODE === "development")
? "ws://localhost:1337"
: `ws://${window.location.host}`;

export function useModel() {
const { sendJsonMessage, lastJsonMessage, readyState } = useWebSocket<TracerMessage>(
(import.meta.env.MODE === "development")
? "ws://localhost:1337"
: `ws://${window.location.host}`);
const { sendJsonMessage, lastJsonMessage, readyState } = useWebSocket<TracerMessage>(SOCKET_URL);

const lostConnection = readyState === ReadyState.CLOSED;

const [spawnedProgram, setSpawnedProgram] = useState<string | null>(null);
const [mainModule, setMainModule] = useState<NativeModule | null>(null);

const [handlers, setHandlers] = useState<Handler[]>([]);
const [selectedScope, setSelectedScope] = useState<ScopeId>("");
Expand All @@ -23,6 +26,12 @@ export function useModel() {
const [addingTargets, setAddingTargets] = useState(false);
const [stagedItems, setStagedItems] = useState<StagedItem[]>([]);

const onR2ReadRequest = useCallback((address: string, size: number) => {
sendJsonMessage({ type: "memory:read", address, size });
}, [sendJsonMessage]);

const { deliverR2ReadResponse } = useR2({ onReadRequest: onR2ReadRequest });

function respawn() {
sendJsonMessage({ type: "tracer:respawn" });
}
Expand Down Expand Up @@ -78,6 +87,7 @@ export function useModel() {
switch (lastJsonMessage.type) {
case "tracer:sync":
setSpawnedProgram(lastJsonMessage.spawned_program);
setMainModule(lastJsonMessage.main_module);
setHandlers(lastJsonMessage.handlers);
break;
case "handlers:add":
Expand All @@ -95,6 +105,10 @@ export function useModel() {
case "events:add":
setEvents(events.concat(lastJsonMessage.events));
break;
case "memory:read-result":
const { result } = lastJsonMessage;
deliverR2ReadResponse((result !== null) ? new Uint8Array(result) : null);
break;
default:
console.log("TODO:", lastJsonMessage);
break;
Expand All @@ -117,6 +131,7 @@ export function useModel() {

spawnedProgram,
respawn,
mainModule,

handlers,
selectedScope,
Expand Down Expand Up @@ -169,17 +184,26 @@ export type MemberName = string | [string, string];

export type Event = [targetId: HandlerId, timestamp: number, threadId: number, depth: number, message: string, style: string[]];

export interface NativeModule {
base: string;
name: string;
path: string;
size: number;
}

type TracerMessage =
| TracerSyncMessage
| HandlersAddMessage
| HandlerLoadedMessage
| TargetsStagedMessage
| EventsAddMessage
| MemoryReadResultMessage
;

interface TracerSyncMessage {
type: "tracer:sync";
spawned_program: string | null;
main_module: NativeModule,
handlers: Handler[];
}

Expand All @@ -202,3 +226,8 @@ interface EventsAddMessage {
type: "events:add";
events: Event[];
}

interface MemoryReadResultMessage {
type: "memory:read-result";
result: Uint8Array | null;
}
103 changes: 103 additions & 0 deletions apps/tracer/src/use-r2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import loadR2Module, { type MainModule } from "./r2.js";
import { useCallback, useEffect, useRef } from "react";

export interface R2Props {
onReadRequest?: ReadRequestHandler;
}

export type ReadRequestHandler = (address: string, size: number) => void;

let state: "unloaded" | "loading" | "loaded" | "executing-command" = "unloaded";
let r2Module: MainModule | null = null;
const pendingCommands: CommandRequest[] = [];
const pendingReads: ((result: Uint8Array | null) => void)[] = [];

interface CommandRequest {
command: string;
onComplete(result: string): void;
}

export function useR2({ onReadRequest }: R2Props = {}) {
const onReadRequestRef = useRef<ReadRequestHandler>();

useEffect(() => {
if (onReadRequest === undefined) {
return;
}

onReadRequestRef.current = onReadRequest;

if (state === "unloaded") {
state = "loading";
loadR2(onReadRequestRef as React.MutableRefObject<ReadRequestHandler>);
}
});

const executeR2Command = useCallback((command: string) => {
return new Promise<string>(resolve => {
pendingCommands.push({
command,
onComplete: resolve,
});
maybeProcessPendingCommands();
});
}, []);

const deliverR2ReadResponse = useCallback((result: Uint8Array | null) => {
pendingReads.shift()!(result);
}, []);

return {
executeR2Command,
deliverR2ReadResponse,
};
}

async function loadR2(onReadRequestRef: React.MutableRefObject<ReadRequestHandler>) {
const r2 = await loadR2Module({
noInitialRun: true,
offset: 0,
onRead(offset: number, size: number): Promise<Uint8Array> {
return new Promise((resolve, reject) => {
pendingReads.push(result => {
if (result !== null) {
resolve(result);
} else {
reject(new Error("read failed"));
}
});
onReadRequestRef.current(offset.toString(), size);
});
},
});

await r2.ccall("main", "int", [], { async: true });

state = "loaded";
r2Module = r2;
maybeProcessPendingCommands();
}

async function maybeProcessPendingCommands() {
if (state !== "loaded") {
return;
}

state = "executing-command";

const r = r2Module!;
const evaluate = r.cwrap("evaluate", "number", ["string"], { async: true });

let req: CommandRequest | undefined;
while ((req = pendingCommands.shift()) !== undefined) {
const rawResult = await evaluate(req.command);
try {
const result = r.UTF8ToString(rawResult);
req.onComplete(result);
} finally {
r._free(rawResult)
}
}

state = "loaded";
}
15 changes: 14 additions & 1 deletion frida_tools/tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,12 +330,14 @@ async def _handle_websocket_connection(self, websocket: websockets.asyncio.serve
message = {
"type": "tracer:sync",
"spawned_program": self._spawned_argv[0] if self._spawned_argv is not None else None,
"main_module": self._tracer.main_module,
"handlers": [target.to_json() for target, source in self._handlers.values()],
}
await websocket.send(json.dumps(message))

while True:
message = json.loads(await websocket.recv())

mtype = message["type"]
if mtype == "tracer:respawn":
self._reactor.schedule(self._respawn)
Expand Down Expand Up @@ -373,6 +375,13 @@ async def _handle_websocket_connection(self, websocket: websockets.asyncio.serve
"handlers": [self._handlers[target_id][0].to_json() for target_id in target_ids],
}
await websocket.send(json.dumps(message))
elif mtype == "memory:read":
data = self._tracer.read_memory(message["address"], message["size"])
message = {
"type": "memory:read-result",
"result": list(data) if data is not None else None,
}
await websocket.send(json.dumps(message))
except:
pass
finally:
Expand Down Expand Up @@ -519,6 +528,7 @@ def __init__(
init_scripts=[],
log_handler: Callable[[str, str], None] = None,
) -> None:
self.main_module = None
self._reactor = reactor
self._repository = repository
self._profile = profile
Expand Down Expand Up @@ -563,7 +573,7 @@ def on_update(target, handler, source) -> None:
self._agent = script.exports_sync

raw_init_scripts = [{"filename": script.filename, "source": script.source} for script in self._init_scripts]
self._agent.init(stage, parameters, raw_init_scripts, self._profile.spec)
self.main_module = self._agent.init(stage, parameters, raw_init_scripts, self._profile.spec)

def stop(self) -> None:
self._repository.close()
Expand All @@ -582,6 +592,9 @@ def stage_targets(self, profile: TracerProfile) -> List:
def commit_targets(self, identifier: Optional[int]) -> List:
return self._agent.commit_targets(identifier)

def read_memory(self, address: str, size: int) -> bytes:
return self._agent.read_memory(address, size)

def _on_message(self, message, data, ui) -> None:
handled = False

Expand Down

0 comments on commit 0d503be

Please sign in to comment.