Skip to content

Instantly share code, notes, and snippets.

@NeKzor
Last active December 29, 2023 18:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NeKzor/6560f254cc397c75df1e061919b29cb4 to your computer and use it in GitHub Desktop.
Save NeKzor/6560f254cc397c75df1e061919b29cb4 to your computer and use it in GitHub Desktop.
Deno FFI feat. libc.
export const libc = Deno.dlopen(
"libc.so.6",
{
dl_iterate_phdr: {
parameters: ["function", "pointer"],
result: "i32",
},
readlink: {
parameters: ["buffer", "buffer", "isize"],
result: "isize",
},
dlopen: {
parameters: ["buffer", "i32"],
result: "pointer",
},
dlclose: {
parameters: ["pointer"],
result: "i32",
},
dlsym: {
parameters: ["pointer", "buffer"],
result: "function",
},
dlerror: {
parameters: [],
result: "buffer",
},
} as const,
);
export class dl_phdr_info {
/** Base address of object */
get dlpi_addr() {
return this.ptr.getBigUint64(0);
}
/** (Null-terminated) name of object */
get dlpi_name() {
return new Deno.UnsafePointerView(this.ptr.getPointer(8)!).getCString();
}
/** Pointer to array of ELF program headers for this object */
get dlpi_phdr() {
return this.ptr.getPointer(16);
}
/** # of items in dlpi_phdr */
get dlpi_phnum() {
return this.ptr.getUint16(24);
}
/**
* The following fields were added in glibc 2.4, after the first version of this structure was available.
* Check the size argument passed to the dl_iterate_phdr callback to determine whether or not each later member is
* available.
*/
/** Incremented when a new object may have been added */
get dlpi_adds() {
return this.isGlibc2_4 ? this.ptr.getBigUint64(32) : undefined;
}
/** Incremented when an object may have been removed */
get dlpi_subs() {
return this.isGlibc2_4 ? this.ptr.getBigUint64(40) : undefined;
}
/** If there is a PT_TLS segment, its module ID as used in TLS relocations, else zero */
get dlpi_tls_modid() {
return this.isGlibc2_4 ? this.ptr.getBigUint64(48) : undefined;
}
/**
* The address of the calling thread's instance of this module's PT_TLS segment, if it has one and it has been
* allocated in the calling thread, otherwise a null pointer */
get dlpi_tls_data() {
return this.isGlibc2_4 ? this.ptr.getBigUint64(56) : undefined;
}
private ptr: Deno.UnsafePointerView;
private isGlibc2_4: boolean;
constructor(value: Deno.PointerValue, size: number | bigint) {
this.ptr = new Deno.UnsafePointerView(value!);
this.isGlibc2_4 = size > 32;
}
}
export enum SegmentType {
/* Loadable program segment */
LOAD = 1,
/* Dynamic linking information */
DYNAMIC = 2,
/* Program interpreter */
INTERP = 3,
/* Auxiliary information */
NOTE = 4,
/* Reserved */
SHLIB = 5,
/* Entry for header table itself */
PHDR = 6,
/* Thread-local storage segment */
TLS = 7,
/* GCC .eh_frame_hdr segment */
GNU_EH_FRAME = 0x6474e550,
/* Indicates stack executability */
GNU_STACK = 0x6474e551,
/* Read-only after relocation */
GNU_RELRO = 0x6474e552,
/** GNU property */
GNU_PROPERTY = 0x6474e553,
}
export const segmentTypeToString = (value: SegmentType) => {
switch (value) {
case SegmentType.LOAD:
return "PT_LOAD";
case SegmentType.DYNAMIC:
return "PT_DYNAMIC";
case SegmentType.INTERP:
return "PT_INTERP";
case SegmentType.NOTE:
return "PT_NOTE";
case SegmentType.SHLIB:
return "PT_SHLIB";
case SegmentType.PHDR:
return "PT_PHDR";
case SegmentType.TLS:
return "PT_TLS";
case SegmentType.GNU_EH_FRAME:
return "PT_GNU_EH_FRAME";
case SegmentType.GNU_STACK:
return "PT_GNU_STACK";
case SegmentType.GNU_RELRO:
return "PT_GNU_RELRO";
case SegmentType.GNU_PROPERTY:
return "PT_GNU_PROPERTY";
default:
return "[other (0x" + (value as number).toString(16) + ")]";
}
};
export class Elf32_Phdr {
/** Segment type */
get p_type() {
return this.ptr.getUint32(0) as SegmentType;
}
/** Segment file offset */
get p_offset() {
return this.ptr.getUint32(4);
}
/** Segment virtual address */
get p_vaddr() {
return this.ptr.getUint32(8);
}
/** Segment physical address */
get p_paddr() {
return this.ptr.getUint32(12);
}
/** Segment size in file */
get p_filesz() {
return this.ptr.getUint32(16);
}
/** Segment size in memory */
get p_memsz() {
return this.ptr.getUint32(20);
}
/** Segment flags */
get p_flags() {
return this.ptr.getUint32(24);
}
/** Segment alignment */
get p_align() {
return this.ptr.getUint32(28);
}
static sizeOf() {
return 32;
}
private ptr: Deno.UnsafePointerView;
constructor(value: Deno.PointerValue) {
this.ptr = new Deno.UnsafePointerView(value!);
}
getVirtualAddress(info: dl_phdr_info) {
return BigInt(info.dlpi_addr) + BigInt(this.p_vaddr);
}
}
export class Elf64_Phdr {
/** Segment type */
get p_type() {
return this.ptr.getUint32(0) as SegmentType;
}
/** Segment flags */
get p_flags() {
return this.ptr.getUint32(4);
}
/** Segment file offset */
get p_offset() {
return this.ptr.getBigUint64(8);
}
/** Segment virtual address */
get p_vaddr() {
return this.ptr.getBigUint64(16);
}
/** Segment physical address */
get p_paddr() {
return this.ptr.getBigUint64(24);
}
/** Segment size in file */
get p_filesz() {
return this.ptr.getBigUint64(32);
}
/** Segment size in memory */
get p_memsz() {
return this.ptr.getBigUint64(40);
}
/** Segment alignment */
get p_align() {
return this.ptr.getBigUint64(48);
}
static sizeOf() {
return 56;
}
private ptr: Deno.UnsafePointerView;
constructor(value: Deno.PointerValue) {
this.ptr = new Deno.UnsafePointerView(value!);
}
getVirtualAddress(info: dl_phdr_info) {
return BigInt(info.dlpi_addr) + BigInt(this.p_vaddr);
}
}
const encoder = new TextEncoder();
const decoder = new TextDecoder();
export const cstr = (value: string) => encoder.encode(value + "\0");
export const cstrFrom = (value: Uint8Array) => decoder.decode(value);
export const getProcessName = (bufferSize = 256) => {
const buffer = new Uint8Array(bufferSize);
libc.symbols.readlink(
cstr(`/proc/${Deno.pid}/exe`),
buffer.buffer,
buffer.byteLength,
);
return cstrFrom(buffer);
};
export enum DlOpenMode {
/** Lazy function call binding. */
LAZY = 0x00001,
/** Immediate function call binding. */
NOW = 0x00002,
/** Mask of binding time value. */
BINDING_MASK = 0x3,
/** Do not load the object. */
NOLOAD = 0x00004,
/** Use deep binding. */
DEEPBIND = 0x00008,
/**
* If the following bit is set in the MODE argument to `dlopen', the symbols of the loaded object and its dependencies
* are made visible as if the object were linked directly into the program. */
GLOBAL = 0x00100,
/**
* Unix98 demands the following flag which is the inverse to RTLD_GLOBAL. The implementation does this by default and
* so we can define the value to zero. */
LOCAL = 0,
/** Do not delete object when closed. */
NODELETE = 0x01000,
}
const callback = new Deno.UnsafeCallback(
{
parameters: ["pointer", "usize", "pointer"],
result: "i32",
} as const,
(
infoPtr: Deno.PointerValue,
size: number | bigint,
_data: Deno.PointerValue<unknown>,
) => {
if (infoPtr) {
const info = new dl_phdr_info(infoPtr, size);
console.log(
"Name: ",
'"' + info.dlpi_name + '"',
"(" + info.dlpi_phnum + " segments)",
);
for (let i = 0; i < info.dlpi_phnum; i += 1) {
const entry = new Elf64_Phdr(
Deno.UnsafePointer.offset(info.dlpi_phdr!, i * Elf64_Phdr.sizeOf()),
);
const addr = entry.getVirtualAddress(info);
console.log(
" ",
i.toString().padStart(2) + ": [" +
("0x" + addr.toString(16)).padStart(14) +
"; memsz:",
entry.p_memsz.toString(16).padStart(7) + "]",
"flags: 0x" + entry.p_flags.toString(16) + ";",
segmentTypeToString(entry.p_type),
);
}
}
return 0;
},
);
libc.symbols.dl_iterate_phdr(callback.pointer, null);
callback.close();
console.log(getProcessName());
// const mod = libc.symbols.dlopen(
// cstr("libgcc_s.so.1"),
// DlOpenMode.NOLOAD | DlOpenMode.NOW,
// );
// if (!mod) {
// const err = libc.symbols.dlerror();
// console.log(new Deno.UnsafePointerView(err!).getCString());
// Deno.exit(1);
// }
// libc.symbols.dlclose(mod);
libc.close();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment