Skip to content

Instantly share code, notes, and snippets.

@NeKzor
Last active November 28, 2024 06:32
Show Gist options
  • Save NeKzor/d1e7b4be34f07b21cc2cc44e49cfc4b6 to your computer and use it in GitHub Desktop.
Save NeKzor/d1e7b4be34f07b21cc2cc44e49cfc4b6 to your computer and use it in GitHub Desktop.
process_vm_readv feat. Deno FFI+ iced-x86
export const libc = Deno.dlopen(Deno.env.get("LIBC_PATH") ?? "libc.so.6", {
ptrace: {
parameters: ["i32", "i32", "pointer", "pointer"],
result: "i32",
},
process_vm_readv: {
parameters: ["i32", "buffer", "i32", "buffer", "i32", "i32"],
result: "i32",
},
});
export const closeLibc: () => void = () => libc.close();
const { ptrace, process_vm_readv } = libc.symbols;
const PTRACE_ATTACH = 16;
const PTRACE_DETACH = 17;
export interface iovec {
iov_base: bigint;
iov_len: number;
}
export const iovecToBuffer = (iov: iovec): Uint8Array => {
const buffer = new Uint8Array(12);
const dv = new DataView(buffer.buffer);
dv.setBigUint64(0, iov.iov_base, true);
dv.setUint32(8, iov.iov_len, true);
return buffer;
};
export const asU32 = (buffer: Uint8Array | null): number | null => {
if (!buffer) return null;
const dv = new DataView(buffer.buffer);
return dv.getUint32(0, true);
};
export const asU64 = (buffer: Uint8Array | null): bigint | null => {
if (!buffer) return null;
const dv = new DataView(buffer.buffer);
return dv.getBigUint64(0, true);
};
export const readProcessMemory = (
pid: number,
address: bigint,
length: number,
): Uint8Array | null => {
const memory = new Uint8Array(length);
const local = iovecToBuffer({
iov_base: Deno.UnsafePointer.value(Deno.UnsafePointer.of(memory)),
iov_len: memory.byteLength,
});
const remote = iovecToBuffer({
iov_base: address,
iov_len: memory.byteLength,
});
return process_vm_readv(pid, local, 1, remote, 1, 0) !== -1 ? memory : null;
};
export const launchProcess = (
path: string,
args: string[],
): Deno.ChildProcess => {
const cmd = new Deno.Command(path, { args, stdout: "piped" });
return cmd.spawn();
};
export const findProcess = async (procName: string): Promise<number | null> => {
const cmd = new Deno.Command("pidof", { args: [procName] });
const { stdout, code } = await cmd.output();
return code !== -1
? parseInt(new TextDecoder().decode(stdout).trim(), 10) ?? null
: null;
};
const dumpMain = async (
code: Uint8Array,
rip: bigint,
is32Bit: boolean,
): Promise<void> => {
const {
Decoder,
DecoderOptions,
Formatter,
FormatterSyntax,
} = await import("npm:iced-x86@^1");
const hexBytesColumnByteLength = 10;
const decoder = new Decoder(is32Bit ? 32 : 64, code, DecoderOptions.None);
decoder.ip = rip;
const instructions = decoder.decodeAll();
const formatter = new Formatter(FormatterSyntax.Intel);
formatter.firstOperandCharIndex = 10;
formatter.spaceAfterOperandSeparator = true;
formatter.addLeadingZeroToHexNumbers = false;
formatter.spaceBetweenMemoryAddOperators = true;
instructions.forEach((instruction) => {
const disasm = formatter.format(instruction);
let line = instruction.ip.toString(16)
.padStart(16, "0")
.toUpperCase();
line += " ";
const startIndex = Number(instruction.ip - rip);
code.slice(startIndex, startIndex + instruction.length).forEach(
(b) => {
line += (b.toString(16).padStart(2, "0")).toUpperCase() + " ";
},
);
line += " ";
for (let i = instruction.length; i < hexBytesColumnByteLength; i++) {
line += " ";
}
line += " ";
line += disasm;
console.log(line);
});
instructions.forEach((instruction) => instruction.free());
formatter.free();
decoder.free();
};
const findMain = async (
pid: number,
procName: string,
is32Bit: boolean,
): Promise<void> => {
let foundHeader = false;
for (
const line of Deno.readTextFileSync(`/proc/${pid}/maps`).split("\n")
) {
const module = line.slice(line.indexOf("/")).trim();
const perm = line.split(" ").at(1);
if (
(module.endsWith("/" + procName) || module.endsWith(".so")) &&
(perm === "r-xp" || (perm === "r--p" && !foundHeader))
) {
console.log("[+]", module);
const start = "0x" + line.split("-").at(0)!;
const address = BigInt(start);
const buffer = readProcessMemory(pid, address, 4);
if (buffer) {
console.log(
`[+] read ${buffer.byteLength} bytes:`,
"0x" +
[...buffer].map((x) => x.toString(16).padStart(2, "0")).join(" 0x"),
"at",
"0x" + address.toString(16),
);
const isHeader = buffer[0] === 0x7f && buffer[1] === 0x45 &&
buffer[2] === 0x4c && buffer[3] === 0x46;
foundHeader ||= isHeader;
if (isHeader) {
const e_entry = is32Bit
? asU32(readProcessMemory(pid, address + 0x18n, 4))
: asU64(readProcessMemory(pid, address + 0x18n, 8));
if (e_entry) {
const _startAddress = address + BigInt(e_entry);
const _start = readProcessMemory(pid, _startAddress, 42);
_start
? await dumpMain(_start, _startAddress, is32Bit)
: console.log("[-] failed to read main");
} else {
console.log("[-] failed to read e_entry");
}
} else {
const func = readProcessMemory(pid, address, 42);
func
? await dumpMain(func, address, is32Bit)
: console.log("[-] failed to read main");
}
} else {
console.error("[-] failed to read process memory");
}
console.log();
}
}
closeLibc();
};
if (import.meta.main) {
if (Deno.args.at(0) === "--self") {
await findMain(Deno.pid, "deno", true);
console.log("[+] done");
Deno.exit(0);
}
const procPath = Deno.env.get("HOME") +
"/.local/share/Steam/steamapps/common/Portal 2/portal2.sh";
const args = [
"-game",
"portal2",
"-novid",
"-windowed",
"-w",
"1280",
"-h",
"720",
"-vulkan",
];
const procName = "portal2_linux";
const is32Bit = true;
let usePtrace = false;
let pid = await findProcess(procName);
if (!pid) {
console.log("[-] launching process", procName);
const { pid: _bashPid } = launchProcess(procPath, args);
await new Promise((resolve) => setTimeout(() => resolve(0), 5_000));
pid = await findProcess(procName);
if (!pid) {
console.log("[-] unable to find process", procName);
Deno.exit(1);
}
} else {
if (Deno.uid() !== 0) {
console.log("[-] requires sudo because process already launched");
Deno.exit(1);
}
usePtrace = true;
}
console.log("[+] found", procName, pid);
usePtrace && ptrace(PTRACE_ATTACH, pid, null, null);
console.log("[*] dumping main");
await findMain(pid, procName, is32Bit);
usePtrace && ptrace(PTRACE_DETACH, pid, null, null);
console.log("[+] done");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment