Compare commits

...
37 Commits
Author SHA1 Message Date
Ole André Vadla Ravnås 78028da11b subprojects: Prepare for release 2026-10-09 14:19:16 +02:00
Ole André Vadla Ravnås 61ce698601 subprojects: Bump outdated 2026-10-09 14:14:52 +02:00
Ole André Vadla Ravnås 95b9cd3bae barebone: Stub g_io_watch_funcs in the Linux .ko
A freestanding GLib builds neither giounix.c nor giowin32.c, so
g_io_watch_funcs is defined nowhere, yet gobject's gsourceclosure.c
takes its address. The kernel module must resolve every symbol, and
the agent never creates a GIOChannel watch, so provide a weak
placeholder that GLib's own definition overrides once the freestanding
build ships one.
2026-10-08 17:01:18 +02:00
Ole André Vadla Ravnås d8a73dae8b android: Stop truncating app icons
The Base64OutputStream was never closed, so its final group was never
written.
2026-10-08 16:37:42 +02:00
Ole André Vadla Ravnås 8947659b2f subprojects: Prepare for release 2026-10-08 14:48:53 +02:00
Ole André Vadla Ravnås d0895b4026 subprojects: Bump outdated 2026-10-08 14:29:30 +02:00
Ole André Vadla Ravnås 8d0e8ad85e compiler: Bump frida-fs to 7.1.1 2026-10-08 14:04:26 +02:00
Ole André Vadla Ravnås cd0df43cdc barebone: Report the Linux copy's errno
SystemFunction reads the system error through gum_thread_get_system_error,
which linux-injected left to the stub returning 0, so failures came back
with errno 0. Read the C library's thread-local errno instead, resolving
__errno_location on glibc and musl or __errno on Bionic.
2026-10-08 13:08:19 +02:00
Ole André Vadla Ravnås 40d053dd51 barebone: Drop the Linux copy-status logging
The kernel half already detects a copy's exit through the
sched_process_exit tracepoint and detaches the session, so logging
what each copy hit was only noise. Remove it, along with the helpers
left unused.
2026-10-08 12:52:13 +02:00
Ole André Vadla Ravnås 7962abb3a3 barebone: Resolve a Linux copy's own exports
gum_module_find_global_export_by_name fell through to the kernel
symbol table for a linux-injected copy, so userspace names like
opendir never resolved and frida-fs could not list a directory.
Search the copy's mapped modules instead, as the Windows and XNU
copies already do.
2026-10-08 11:26:49 +02:00
Ole André Vadla Ravnås a94cd60e60 barebone: Keep the Linux kernel symbols alive
The upcast to LinuxKernelSymbols inside the ?: freed the System.map
before set_kernel stored it, leaving image.kernel dangling and the
session reading freed memory once the config was parsed. Assign each
image kind directly instead.
2026-10-08 11:03:14 +02:00
Ole André Vadla Ravnås d784b73d12 compiler: Bump frida-fs to 7.1.0 2026-10-08 09:07:23 +02:00
Ole André Vadla Ravnås f0bd948a71 barebone: Warm async I/O before the loop runs
GLib brings its worker context and GTask pool up lazily, on the
first async GIO call. Inside the agent's live loop that wedges it,
so warm them at copy startup while still single-threaded.
2026-10-08 05:13:36 +02:00
Ole André Vadla Ravnås fc2c0101a1 barebone: Give the win9x loop its own wake event
The main loop must be wakeable even when GLib watches no handles
and no timer is due, where it sleeps INFINITE. Wait on a dedicated
event in poll_handles instead of a token slot shared with the send
path, and set it from poke_loop_wakeup.
2026-10-08 04:12:42 +02:00
Ole André Vadla Ravnås f911715ebf barebone: Enable GLib's IO features for the agent
The diet GLib leaves the private vtable's IO entries null until
glib_enable_io_features() runs, so the first GIO user faulted on a
null g_get_worker_context. Streams are that first user.
2026-10-08 02:49:56 +02:00
Ole André Vadla Ravnås 8c206ea6cf barebone: Back streams and ANSI in the copies
Hand Gum the poll, read, write and close of the user copies so
scripts get Win32InputStream, Win32OutputStream and their UNIX
counterparts, and convert ANSI strings through the code page of
the process. The Win9x copy waits on handles like the NT one.
2026-10-08 00:41:42 +02:00
Ole André Vadla Ravnås 677f9c8379 barebone: Share the windows_fn! macro
The NT kernel half and the user copies each had a copy; the Win9x
copy wants it too.
2026-10-08 00:41:42 +02:00
Ole André Vadla Ravnås 474d8aab5a barebone: Expose the last error of Windows copies
Back gum_thread_get_system_error() and its setter with
GetLastError() and SetLastError() when running in the userspace
copy, so SystemFunction and Interceptor report lastError.
2026-10-08 00:41:42 +02:00
Ole André Vadla Ravnås 3302340a48 barebone: Enumerate the imports of Windows modules
Walk the import directory of a module in the process the copy runs
in, the way exports already are, so Module.enumerateImports() stops
coming back empty on Win9x and NT.
2026-10-08 00:41:42 +02:00
Ole André Vadla Ravnås b2161e02aa compiler: Bump frida-fs to 7.0.1
Its stat field reads passed the path to NativePointer#read*(), which
now take an optional offset.
2026-10-07 20:35:36 +02:00
Ole André Vadla Ravnås 6880660481 subprojects: Revert the termux-elf-cleaner bump
Its new code needs C++20, which the host tool build does not use.
2026-10-07 15:46:21 +02:00
Ole André Vadla Ravnås 94dc97155f subprojects: Prepare for release 2026-10-07 14:26:53 +02:00
Ole André Vadla Ravnås 16c98b23d0 subprojects: Bump outdated 2026-10-07 14:23:37 +02:00
Ole André Vadla Ravnås 9f9428208e submodules: Bump releng 2026-10-07 14:23:36 +02:00
Ole André Vadla Ravnås ce8d8889a7 compiler: Fix the pattern bundle tests
The library fixture declared Vec3 beside the geometry import meant to
provide it, and the alias assertion predates shared imported units,
which bind the alias to the shared class instead of a copy.
2026-10-07 14:10:00 +02:00
Ole André Vadla Ravnås 1d571ec980 patterns: Let hosts define preprocessor macros
Add a defines dictionary to PatternCompileOptions, carried by the
module into decode and call, and take the language server's from
settings.patterns.defines on workspace/didChangeConfiguration. A
pattern can then branch on where its data comes from, such as a
file or a mapped image, without knowing its host.
2026-10-07 14:05:05 +02:00
Ole André Vadla Ravnås 904a4a12ed patterns: Report endless template recursion
A template that instantiates itself with a growing argument never
reaches a cached shell, so the resolver recursed until the stack ran
out. Stop after 64 nested instantiations and say so.
2026-10-07 14:05:05 +02:00
Ole André Vadla Ravnås 7ada909cf8 patterns: Hand ref parameters the pattern
A ref parameter typed auto received the field's value, so a function
formatting a bitfield or struct found no members. Pass the pattern as
is done for parameters of a composite type.
2026-10-07 14:05:05 +02:00
Ole André Vadla Ravnås cc56d552ad patterns: Find template locals by name as fallback
A template instantiated from two enclosing templates with a parameter
of the same name shares one shell, so the argument it captured names
the other instantiation's local. Fall back to the local of that name.
2026-10-07 14:05:05 +02:00
Ole André Vadla Ravnås 1f28539920 patterns: Answer array_index() inside an element
The index lived on the frame that owns the array, so a member of the
element type asked its own frame and always got zero. Walk up to the
frame that is iterating.
2026-10-07 14:05:05 +02:00
Ole André Vadla Ravnås 37dc612b12 patterns: Keep enum names through member access
Only a field named directly was labelled with its enumerator; a member
of a struct, a bitfield member, an element or a ref parameter gave the
bare number. Hand builtins that take a pattern the pattern itself, so
formatted_value() formats scalars and bitfields too.
2026-10-07 14:05:05 +02:00
Ole André Vadla Ravnås d26d4c88d4 patterns: Look up fields by name as a fallback
A template instantiated with a field argument is shared by every
enclosing type that spells the argument the same way, so the field it
captured belongs to whichever instantiation came first. Fall back to
the decoded field of that name.
2026-10-07 14:05:05 +02:00
Ole André Vadla Ravnås b69b0ebd61 barebone: Stop scribbling on the Win9x gone ring
on_module_load bumped the words at arena 0xb0 and 0xb4, which are
the head and tail of the departed-thread ring. A target that
loaded a library while attached shifted the ring under the copy,
which then read a stale slot and reported a thread that never left.
2026-10-07 09:55:05 +02:00
Ole André Vadla Ravnås d5ad5e288f barebone: Let the Win9x copy's main thread exit
Return from the main thread and let it run ExitThread on the
injection stack. KERNEL32 made the thread's own stack in the
process that created it, thus point the TIB and the TDB at the
injection stack first. Free the memory only once VMM destroys the
thread.

The thread used to park in the image and on the injection stack,
which detach freed. It crashed the process once a later injection
reused that memory.
2026-10-07 02:16:22 +02:00
Ole André Vadla Ravnås 557824d30b barebone: Free the Win9x copy's start stack
The injected thread leaves it as soon as the trampoline switches to
the injection stack, which is before it reports that it runs.
2026-10-07 02:16:22 +02:00
Ole André Vadla Ravnås 276b45047d barebone: Wake the Win9x copy when detaching
The copy's worker sleeps in an alertable wait until an APC wakes it,
thus it never saw the stop request. Detach timed out after ten
seconds, the host stopped waiting for the agent to leave, and the
next session ran alongside the old agent and its VxD descriptor.
That bluescreened the guest.
2026-10-07 02:16:22 +02:00
Ole André Vadla Ravnås 53eac9e6ae barebone: Let Win9x page in what the agent reads
Hand a fault on an absent page back to Windows when Gum does not
claim it. The agent reads the memory of other processes, and such
a page is paged out, not invalid. Explorer's image headers were
one, thus attaching to it parked the agent while it was switched
into Explorer's memory context, and the guest bluescreened.
2026-10-07 00:51:57 +02:00
40 changed files with 1418 additions and 220 deletions

No files matched your search

+1 -1
Submodule releng updated: d6b56b460b...4e51c61880.
Binary file not shown.
@@ -18,7 +18,6 @@ import android.graphics.drawable.Drawable;
import android.os.Looper;
import android.os.Process;
import android.util.Base64;
import android.util.Base64OutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -982,10 +981,10 @@ public class HelperBackend {
icon.setBounds(0, 0, width, height);
icon.draw(canvas);
ByteArrayOutputStream output = new ByteArrayOutputStream();
bitmap.compress(CompressFormat.PNG, 100, new Base64OutputStream(output, Base64.NO_WRAP));
ByteArrayOutputStream png = new ByteArrayOutputStream();
bitmap.compress(CompressFormat.PNG, 100, png);
return output.toString();
return Base64.encodeToString(png.toByteArray(), Base64.NO_WRAP);
}
private Map<String, List<RunningAppProcessInfo>> getAppProcesses() {
+42 -1
View File
@@ -29,7 +29,9 @@ use crate::bindings::{
#[cfg(any(feature = "linux", feature = "linux-injected"))]
use crate::gum_modules::enumerate_symbols_in_range;
#[cfg(any(feature = "win9x", feature = "winnt"))]
use crate::gum_windows::enumerate_exports_in_range;
use crate::gum_windows::{enumerate_exports_in_range, enumerate_imports_in_module};
#[cfg(any(feature = "win9x", feature = "winnt"))]
use crate::bindings::{GumFoundImportFunc, GumImportDetails, GumImportType_GUM_IMPORT_FUNCTION};
#[cfg(any(feature = "xnu-core", feature = "linux-injected"))]
use crate::gum_injected::enumerate_exports_in_range;
@@ -184,6 +186,11 @@ extern "C" fn gum_native_module_iface_init(g_iface: gpointer, _iface_data: gpoin
(*iface).get_path = Some(crate::signed_to_be_called_back(gum_native_module_get_path, 0));
(*iface).get_range = Some(crate::signed_to_be_called_back(gum_native_module_get_range, 0));
(*iface).enumerate_exports = Some(crate::signed_to_be_called_back(gum_native_module_enumerate_exports, 0));
#[cfg(any(feature = "win9x", feature = "winnt"))]
{
(*iface).enumerate_imports =
Some(crate::signed_to_be_called_back(gum_native_module_enumerate_imports, 0));
}
(*iface).find_export_by_name = Some(crate::signed_to_be_called_back(gum_native_module_find_export_by_name, 0));
#[cfg(any(feature = "linux", feature = "linux-injected"))]
{
@@ -317,6 +324,32 @@ unsafe extern "C" fn gum_native_module_enumerate_exports(
}
}
#[cfg(any(feature = "win9x", feature = "winnt"))]
unsafe extern "C" fn gum_native_module_enumerate_imports(
self_: *mut GumModule,
func: GumFoundImportFunc,
user_data: gpointer,
) {
unsafe {
let module = self_ as *mut GumNativeModule;
let base = (*module).range.base_address;
let mut on_import = |library: *const gchar, name: *const gchar, slot: u64, address: u64| {
let details = GumImportDetails {
type_: GumImportType_GUM_IMPORT_FUNCTION,
name,
module: library,
address,
slot,
};
func.unwrap()(&details as *const GumImportDetails, user_data) != 0
};
enumerate_imports_in_module(base, &mut on_import);
}
}
#[cfg(any(feature = "linux", feature = "linux-injected"))]
unsafe extern "C" fn gum_native_module_enumerate_symbols(
self_: *mut GumModule,
@@ -368,6 +401,9 @@ const SYMBOL_IS_FUNCTION: u8 = 2;
/// it returns false.
pub(crate) type FoundExportCallback<'a> = dyn FnMut(*const gchar, u64) -> bool + 'a;
#[cfg(any(feature = "win9x", feature = "winnt"))]
pub(crate) type FoundImportCallback<'a> = dyn FnMut(*const gchar, *const gchar, u64, u64) -> bool + 'a;
// Code slabs the agent allocates are normal writable RAM, so Gum can keep a real writable alias to
// them across the slab's lifetime. Pre-existing kernel text is write-protected — CTRR-locked on
// Apple silicon, STRICT_KERNEL_RWX on Linux — and can only be modified through a separate path, so
@@ -469,6 +505,11 @@ mod symbolication {
return crate::xnu_mapped::export_named(name);
}
#[cfg(feature = "linux-injected")]
if crate::kernel::in_copy() {
return crate::kernel::export_named(name);
}
let table = unsafe { &*ptr::addr_of!(crate::SYMBOL_TABLE) };
match table.find_symbol_by_name(name) {
+93 -1
View File
@@ -23,7 +23,10 @@ use crate::{
#[cfg(feature = "xnu-core")]
use crate::bindings::{g_object_unref, gum_barebone_register_module};
#[cfg(feature = "linux-injected")]
use crate::bindings::gum_barebone_unregister_thread;
use crate::bindings::{
GError, GumBareboneStreamOps, g_io_error_from_errno, g_io_error_quark, g_set_error_literal,
g_strerror, gint, gssize, gum_barebone_unregister_thread,
};
#[cfg(not(feature = "xnu-kext"))]
use crate::bindings::{
g_variant_get_boolean, g_variant_new,
@@ -176,6 +179,51 @@ fn remap_agent_pages_through_host(first_page: gpointer, n_pages: guint) -> gpoin
#[cfg(all(feature = "linux-injected", not(target_arch = "arm")))]
const GUM_PAGE_RWX: u32 = 7;
#[cfg(feature = "linux-injected")]
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_stream_ops() -> *const GumBareboneStreamOps {
if kernel::in_copy() { &STREAM_OPS } else { ptr::null() }
}
#[cfg(feature = "linux-injected")]
static STREAM_OPS: GumBareboneStreamOps = GumBareboneStreamOps {
poll: Some(crate::linux::user::frida_poll),
read: Some(read_descriptor),
write: Some(write_descriptor),
close: Some(close_descriptor),
};
#[cfg(feature = "linux-injected")]
unsafe extern "C" fn read_descriptor(handle: gpointer, buffer: gpointer, count: gsize,
error: *mut *mut GError) -> gssize {
transferred(crate::linux::user::read(handle as i32, buffer as *mut u8, count as usize), error)
}
#[cfg(feature = "linux-injected")]
unsafe extern "C" fn write_descriptor(handle: gpointer, buffer: gconstpointer, count: gsize,
error: *mut *mut GError) -> gssize {
transferred(crate::linux::user::write(handle as i32, buffer as *const u8, count as usize),
error)
}
#[cfg(feature = "linux-injected")]
unsafe extern "C" fn close_descriptor(handle: gpointer, error: *mut *mut GError) -> gboolean {
(transferred(crate::linux::user::close(handle as i32), error) >= 0) as gboolean
}
#[cfg(feature = "linux-injected")]
fn transferred(result: isize, error: *mut *mut GError) -> gssize {
if result < 0 {
let errno = -result as gint;
unsafe {
g_set_error_literal(error, g_io_error_quark(), g_io_error_from_errno(errno) as gint,
g_strerror(errno))
};
return -1;
}
result as gssize
}
unsafe fn what_changed(first_page: u64, shadow: *const u8, total: usize) -> Option<(usize, usize)> {
let live = unsafe { core::slice::from_raw_parts(first_page as *const u8, total) };
let ours = unsafe { core::slice::from_raw_parts(shadow, total) };
@@ -937,3 +985,47 @@ pub(crate) unsafe fn enumerate_exports_in_range(
pub extern "C" fn gum_load_symbols(_path: *const gchar) -> gboolean {
0
}
#[cfg(feature = "linux-injected")]
#[unsafe(no_mangle)]
pub extern "C" fn gum_thread_get_system_error() -> gint {
match errno_slot() {
Some(slot) => unsafe { *slot },
None => 0,
}
}
#[cfg(feature = "linux-injected")]
#[unsafe(no_mangle)]
pub extern "C" fn gum_thread_set_system_error(value: gint) {
if let Some(slot) = errno_slot() {
unsafe { *slot = value };
}
}
// errno is thread-local behind a function that hands back its address:
// __errno_location on glibc and musl, __errno on Bionic.
#[cfg(feature = "linux-injected")]
fn errno_slot() -> Option<*mut gint> {
use core::sync::atomic::{AtomicUsize, Ordering};
if !crate::kernel::in_copy() {
return None;
}
static LOCATE: AtomicUsize = AtomicUsize::new(0);
let mut locate = LOCATE.load(Ordering::Relaxed);
if locate == 0 {
locate = crate::kernel::export_named("__errno_location") as usize;
if locate == 0 {
locate = crate::kernel::export_named("__errno") as usize;
}
if locate == 0 {
return None;
}
LOCATE.store(locate, Ordering::Relaxed);
}
let locate: unsafe extern "C" fn() -> *mut gint = unsafe { core::mem::transmute(locate) };
Some(unsafe { locate() })
}
+228 -6
View File
@@ -3,26 +3,240 @@
use crate::{
bindings::{
_GumPageProtection_GUM_PAGE_EXECUTE, _GumRwxSupport_GUM_RWX_FULL, GumMemoryRange,
GumModuleRegistry, GumPageProtection, GumRwxSupport, g_object_unref, gboolean, gpointer,
gsize, guint, gum_barebone_register_module, gum_mprotect, GumFoundRangeFunc,
GumFoundThreadFunc, GumModifyThreadFlags, GumModifyThreadFunc, GumRangeDetails,
GumThreadDetails, GumThreadFlags, GumThreadId, GumThreadRegistry,
_GumPageProtection_GUM_PAGE_EXECUTE, _GumRwxSupport_GUM_RWX_FULL, GError,
GIOErrorEnum_G_IO_ERROR_BROKEN_PIPE, GIOErrorEnum_G_IO_ERROR_FAILED,
GIOErrorEnum_G_IO_ERROR_INVALID_ARGUMENT, GIOErrorEnum_G_IO_ERROR_NOT_FOUND,
GIOErrorEnum_G_IO_ERROR_PERMISSION_DENIED, GumBareboneStreamOps, GumMemoryRange,
GumModuleRegistry, GumPageProtection, GumRwxSupport, g_free, g_io_error_quark, g_malloc,
g_object_unref, g_set_error_literal, g_utf8_to_utf16, g_utf16_to_utf8, gboolean, gchar,
gint, gpointer, gsize, gssize, guint, gum_barebone_register_module, gum_mprotect,
GumFoundRangeFunc, GumFoundThreadFunc, GumModifyThreadFlags, GumModifyThreadFunc,
GumRangeDetails, GumThreadDetails, GumThreadFlags, GumThreadId, GumThreadRegistry,
gconstpointer, gum_thread_details_copy,
},
gum::{self, FoundExportCallback},
gum::{self, FoundExportCallback, FoundImportCallback},
kernel,
};
use crate::bindings::{GumCpuContext, GumThreadFlags_GUM_THREAD_FLAGS_CPU_CONTEXT};
use alloc::ffi::CString;
#[cfg(not(feature = "winnt"))]
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use core::ptr;
#[cfg(target_arch = "x86")]
macro_rules! windows_fn {
($($argument:ty),* $(,)?) => { unsafe extern "stdcall" fn($($argument),*) };
($($argument:ty),* $(,)? => $result:ty) => {
unsafe extern "stdcall" fn($($argument),*) -> $result
};
}
#[cfg(target_arch = "x86_64")]
macro_rules! windows_fn {
($($argument:ty),* $(,)?) => { unsafe extern "win64" fn($($argument),*) };
($($argument:ty),* $(,)? => $result:ty) => {
unsafe extern "win64" fn($($argument),*) -> $result
};
}
#[cfg(target_arch = "aarch64")]
macro_rules! windows_fn {
($($argument:ty),* $(,)?) => { unsafe extern "C" fn($($argument),*) };
($($argument:ty),* $(,)? => $result:ty) => {
unsafe extern "C" fn($($argument),*) -> $result
};
}
#[allow(unused_imports)]
pub(crate) use windows_fn;
pub struct FileApi {
pub read_file: windows_fn!(usize, *mut u8, u32, *mut u32, usize => i32),
pub write_file: windows_fn!(usize, *const u8, u32, *mut u32, usize => i32),
pub close_handle: windows_fn!(usize => i32),
pub format_message: windows_fn!(u32, usize, u32, u32, *mut u8, u32, usize => u32),
}
pub struct TextApi {
pub multi_byte_to_wide_char: windows_fn!(u32, u32, *const u8, i32, *mut u16, i32 => i32),
pub wide_char_to_multi_byte:
windows_fn!(u32, u32, *const u16, i32, *mut u8, i32, usize, usize => i32),
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_platform() -> *const crate::bindings::gchar {
c"windows".as_ptr() as *const crate::bindings::gchar
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_thread_get_system_error() -> crate::bindings::gint {
kernel::thread_error() as crate::bindings::gint
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_thread_set_system_error(value: crate::bindings::gint) {
kernel::set_thread_error(value as u32)
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_ansi_string_to_utf8(str_ansi: *const gchar, length: gint) -> *mut gchar {
let ansi = unsafe { bytes_of(str_ansi as *const u8, length) };
match kernel::text_api() {
Some(api) => ansi_to_utf8(api, ansi),
None => handed_to_glib(ansi.iter().map(|&byte| byte as char).collect::<String>().as_bytes()),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_ansi_string_from_utf8(str_utf8: *const gchar) -> *mut gchar {
match kernel::text_api() {
Some(api) => ansi_from_utf8(api, str_utf8),
None => {
let utf8 = unsafe { core::ffi::CStr::from_ptr(str_utf8) }.to_str().unwrap();
let latin1: Vec<u8> =
utf8.chars().map(|c| if (c as u32) <= 0xff { c as u8 } else { b'?' }).collect();
handed_to_glib(&latin1)
}
}
}
unsafe fn bytes_of<'a>(str: *const u8, length: gint) -> &'a [u8] {
let length = if length < 0 {
unsafe { core::ffi::CStr::from_ptr(str as *const core::ffi::c_char) }.to_bytes().len()
} else {
length as usize
};
unsafe { core::slice::from_raw_parts(str, length) }
}
fn ansi_to_utf8(api: &TextApi, ansi: &[u8]) -> *mut gchar {
let count = unsafe {
(api.multi_byte_to_wide_char)(CP_ACP, 0, ansi.as_ptr(), ansi.len() as i32,
ptr::null_mut(), 0)
};
let mut wide = alloc::vec![0u16; count as usize];
unsafe {
(api.multi_byte_to_wide_char)(CP_ACP, 0, ansi.as_ptr(), ansi.len() as i32,
wide.as_mut_ptr(), count);
g_utf16_to_utf8(wide.as_ptr(), count as _, ptr::null_mut(), ptr::null_mut(),
ptr::null_mut())
}
}
fn ansi_from_utf8(api: &TextApi, utf8: *const gchar) -> *mut gchar {
unsafe {
let wide = g_utf8_to_utf16(utf8, -1, ptr::null_mut(), ptr::null_mut(), ptr::null_mut());
let size = (api.wide_char_to_multi_byte)(CP_ACP, 0, wide, -1, ptr::null_mut(), 0, 0, 0);
let ansi = g_malloc(size as gsize) as *mut u8;
(api.wide_char_to_multi_byte)(CP_ACP, 0, wide, -1, ansi, size, 0, 0);
g_free(wide as gpointer);
ansi as *mut gchar
}
}
fn handed_to_glib(bytes: &[u8]) -> *mut gchar {
unsafe {
let copy = g_malloc((bytes.len() + 1) as gsize) as *mut u8;
ptr::copy_nonoverlapping(bytes.as_ptr(), copy, bytes.len());
*copy.add(bytes.len()) = 0;
copy as *mut gchar
}
}
const CP_ACP: u32 = 0;
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_stream_ops() -> *const GumBareboneStreamOps {
if kernel::in_copy() { &STREAM_OPS } else { ptr::null() }
}
static STREAM_OPS: GumBareboneStreamOps = GumBareboneStreamOps {
poll: Some(kernel::poll_handles),
read: Some(read_handle),
write: Some(write_handle),
close: Some(close_handle),
};
unsafe extern "C" fn read_handle(handle: gpointer, buffer: gpointer, count: gsize,
error: *mut *mut GError) -> gssize {
let mut transferred = 0u32;
let succeeded = unsafe {
(kernel::file_api().read_file)(handle as usize, buffer as *mut u8, count as u32,
&mut transferred, 0)
};
if succeeded == 0 {
return failed(error);
}
transferred as gssize
}
unsafe extern "C" fn write_handle(handle: gpointer, buffer: gconstpointer, count: gsize,
error: *mut *mut GError) -> gssize {
let mut transferred = 0u32;
let succeeded = unsafe {
(kernel::file_api().write_file)(handle as usize, buffer as *const u8, count as u32,
&mut transferred, 0)
};
if succeeded == 0 {
return failed(error);
}
transferred as gssize
}
unsafe extern "C" fn close_handle(handle: gpointer, error: *mut *mut GError) -> gboolean {
let succeeded = unsafe { (kernel::file_api().close_handle)(handle as usize) };
if succeeded == 0 {
failed(error);
return 0;
}
1
}
fn failed(error: *mut *mut GError) -> gssize {
let code = kernel::thread_error();
let message = CString::new(describe_error(code)).unwrap();
unsafe {
g_set_error_literal(error, g_io_error_quark(), io_error_from_win32(code) as gint,
message.as_ptr())
};
-1
}
fn describe_error(code: u32) -> String {
let mut ansi = [0u8; 512];
let length = unsafe {
(kernel::file_api().format_message)(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, code, 0,
ansi.as_mut_ptr(), ansi.len() as u32, 0)
};
let utf8 = ansi_to_utf8(kernel::text_api().unwrap(), &ansi[..length as usize]);
let message = unsafe { core::ffi::CStr::from_ptr(utf8) }.to_str().unwrap().trim_end();
let described = String::from(message);
unsafe { g_free(utf8 as gpointer) };
described
}
fn io_error_from_win32(code: u32) -> u32 {
match code {
ERROR_FILE_NOT_FOUND | ERROR_PATH_NOT_FOUND => GIOErrorEnum_G_IO_ERROR_NOT_FOUND,
ERROR_ACCESS_DENIED => GIOErrorEnum_G_IO_ERROR_PERMISSION_DENIED,
ERROR_INVALID_HANDLE | ERROR_INVALID_PARAMETER => GIOErrorEnum_G_IO_ERROR_INVALID_ARGUMENT,
ERROR_BROKEN_PIPE => GIOErrorEnum_G_IO_ERROR_BROKEN_PIPE,
_ => GIOErrorEnum_G_IO_ERROR_FAILED,
}
}
const FORMAT_MESSAGE_IGNORE_INSERTS: u32 = 0x200;
const FORMAT_MESSAGE_FROM_SYSTEM: u32 = 0x1000;
const ERROR_FILE_NOT_FOUND: u32 = 2;
const ERROR_PATH_NOT_FOUND: u32 = 3;
const ERROR_ACCESS_DENIED: u32 = 5;
const ERROR_INVALID_HANDLE: u32 = 6;
const ERROR_INVALID_PARAMETER: u32 = 87;
const ERROR_BROKEN_PIPE: u32 = 109;
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_stack_size() -> crate::bindings::gsize {
if crate::on_js_thread() {
@@ -653,6 +867,14 @@ const INTEGER_OVERFLOW: u32 = 0xc000_0095;
const BREAKPOINT: u32 = 0x8000_0003;
const SINGLE_STEP: u32 = 0x8000_0004;
pub(crate) unsafe fn enumerate_imports_in_module(base: u64, callback: &mut FoundImportCallback) {
if crate::running_in_a_process() {
kernel::enumerate_imports(base as _, &mut |library, name, slot, address| {
callback(library as *const crate::bindings::gchar, name as *const crate::bindings::gchar, slot, address)
});
}
}
pub(crate) unsafe fn enumerate_exports_in_range(
start_address: u64,
end_address: u64,
+33 -3
View File
@@ -1,4 +1,5 @@
#![no_std]
#![cfg_attr(feature = "linux", feature(linkage))]
#[cfg(not(any(
feature = "xnu-core",
@@ -1120,13 +1121,42 @@ unsafe fn init_gum() {
// do. A copy in a process would take it from the half that uses it.
#[cfg(any(feature = "blob", feature = "xnu-core"))]
pub(crate) unsafe fn init_gum_without_exceptor() {
unsafe { init_gum_with_exceptor(false) };
unsafe {
init_gum_with_exceptor(false);
warm_async_io();
}
}
pub(crate) unsafe fn warm_async_io() {
unsafe {
let task = bindings::g_task_new(core::ptr::null_mut(), core::ptr::null_mut(), None,
core::ptr::null_mut());
bindings::g_object_unref(task as bindings::gpointer);
}
}
// TEMPORARY: g_io_watch_funcs lives in GLib's giounix.c / giowin32.c, and a
// freestanding GLib builds neither, so gobject's gsourceclosure.c is left taking the
// address of a symbol nothing defines. The kernel module must resolve every symbol,
// and the agent never creates a GIOChannel watch, so stand in with a placeholder that
// GLib's own definition overrides once the freestanding build ships one.
#[cfg(feature = "linux")]
#[unsafe(no_mangle)]
#[linkage = "weak"]
pub static g_io_watch_funcs: bindings::GSourceFuncs = bindings::GSourceFuncs {
prepare: None,
check: None,
dispatch: None,
finalize: None,
closure_callback: None,
closure_marshal: None,
};
unsafe fn init_gum_with_exceptor(exceptor: bool) {
unsafe {
bindings::g_set_panic_handler(Some(signed_to_be_called_back(frida_panic_handler, 0)), ptr::null_mut());
bindings::gum_init_embedded();
bindings::glib_enable_io_features();
if exceptor {
bindings::gum_exceptor_obtain();
}
@@ -1380,11 +1410,11 @@ pub(crate) fn watch_for_work(main_context: *mut GMainContext, ready: fn() -> boo
#[cfg(any(feature = "blob", feature = "xnu-core"))]
static mut WORK_SOURCE: *mut GSource = ptr::null_mut();
#[cfg(any(feature = "winnt", feature = "linux-injected"))]
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected"))]
static mut WAKEUP_POLLFD: bindings::GPollFD =
bindings::GPollFD { fd: -1, events: 0, revents: 0, user_data: ptr::null_mut() };
#[cfg(any(feature = "winnt", feature = "linux-injected"))]
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected"))]
pub(crate) fn watch_a_descriptor(context: *mut GMainContext, fd: i32,
poll: unsafe extern "C" fn(*mut bindings::GPollFD, u32, i32) -> i32) {
const G_IO_IN: u16 = 1;
-8
View File
@@ -25,14 +25,6 @@ impl Arena {
}
}
pub fn said(&self) -> &str {
let bytes =
unsafe { core::slice::from_raw_parts((self.begins + SAID) as *const u8, SAID_SIZE) };
let length = bytes.iter().position(|byte| *byte == 0).unwrap_or(SAID_SIZE);
core::str::from_utf8(&bytes[..length]).unwrap_or("")
}
pub fn page_size(&self) -> u32 {
self.word(PAGE_SIZE).load(Ordering::Acquire)
}
+2 -38
View File
@@ -2,7 +2,6 @@ use core::ffi::{c_int, c_void};
use core::ptr;
use alloc::collections::BTreeMap;
use alloc::string::String;
#[cfg(not(target_arch = "x86"))]
use crate::bindings::{
@@ -857,31 +856,10 @@ pub fn serve_register_requests() {
pub fn report_what_the_copies_hit() {
for placed in unsafe { placements() }.values() {
let Some(hit) = what_it_says(placed) else {
if what_it_says(placed).is_none() {
continue;
};
bump_to(placed.arena + super::arena::PROGRESS, NOTHING_MORE);
let arena = Arena::at(placed.arena);
let said = arena.said();
if hit == super::user::SPOKE {
native::log(&alloc::format!("copy in {}: {}\n", pid_reported_by(placed), said));
} else if hit == super::user::PANICKED {
native::log(&alloc::format!("copy in {} gave up: {}\n", pid_reported_by(placed), said));
} else {
let kind = read_address(placed.arena + super::arena::FAULT_KIND);
native::log(&alloc::format!(
"copy in {} died on signal {} ({}) at {:#x}, pc {}, lr {}, image at {:#x}, {}\n",
pid_reported_by(placed),
kind as u32,
(kind >> 32) as u32,
read_address(placed.arena + super::arena::FAULT_ADDRESS),
relative_to_image(read_address(placed.arena + super::arena::FAULT_PC), placed),
relative_to_image(read_address(placed.arena + super::arena::FAULT_LR), placed),
placed.base,
said
));
}
bump_to(placed.arena + super::arena::PROGRESS, NOTHING_MORE);
}
}
@@ -899,16 +877,6 @@ fn bump_to(address: usize, value: u32) {
unsafe { (address as *mut u32).write_volatile(value) };
}
fn relative_to_image(address: u64, placed: &Placement) -> String {
let base = placed.base as u64;
let size = crate::own_range().1 as u64;
if address >= base && address - base < size {
alloc::format!("image+{:#x}", address - base)
} else {
alloc::format!("{:#x}", address)
}
}
const NOTHING_MORE: u32 = 0;
fn watch_the_threads() {
@@ -1134,10 +1102,6 @@ fn wake_the_copy(placed: &Placement) {
}
}
fn read_address(address: usize) -> u64 {
unsafe { (address as *const u64).read_volatile() }
}
fn read_word(address: usize) -> u32 {
unsafe { (address as *const u32).read_volatile() }
}
+17 -1
View File
@@ -1,7 +1,7 @@
use alloc::string::String;
use alloc::vec::Vec;
use core::ffi::c_void;
use core::ffi::{CStr, c_void};
use core::ptr;
use crate::bindings::{
@@ -112,6 +112,22 @@ pub fn enumerate_exports_in_range(from: u64, to: u64, found: &mut FoundExportCal
}
}
pub fn export_named(wanted: &str) -> u64 {
let mut found: u64 = 0;
for image in mapped_images() {
enumerate_exports_in_range(image.base, image.base + image.size, &mut |name, address| {
if !name.is_null() && unsafe { CStr::from_ptr(name as *const _) }.to_bytes() == wanted.as_bytes() {
found = address;
}
found == 0
});
if found != 0 {
break;
}
}
found
}
pub fn enumerate_symbols_in_range(from: u64, to: u64, found: &mut FoundSymbolCallback<'_>) {
let Some(image) = mapped_images().into_iter().find(|image| image.base == from) else {
return;
+13 -1
View File
@@ -595,7 +595,7 @@ struct TimeSpec {
nanoseconds: i64,
}
unsafe extern "C" fn frida_poll(ufds: *mut crate::bindings::GPollFD, nfds: u32,
pub(crate) unsafe extern "C" fn frida_poll(ufds: *mut crate::bindings::GPollFD, nfds: u32,
timeout: i32) -> i32 {
const MAX_FDS: usize = 16;
let fds = unsafe { core::slice::from_raw_parts_mut(ufds, nfds as usize) };
@@ -1204,6 +1204,18 @@ pub fn names_in(directory: &core::ffi::CStr) -> Vec<String> {
names
}
pub(crate) fn read(fd: i32, buffer: *mut u8, count: usize) -> isize {
syscall(READ, fd as usize, buffer as usize, count, 0, 0, 0)
}
pub(crate) fn write(fd: i32, buffer: *const u8, count: usize) -> isize {
syscall(WRITE, fd as usize, buffer as usize, count, 0, 0, 0)
}
pub(crate) fn close(fd: i32) -> isize {
syscall(CLOSE, fd as usize, 0, 0, 0, 0, 0)
}
pub fn contents_of(path: &core::ffi::CStr) -> Vec<u8> {
let file = syscall(OPENAT, WORKING_DIRECTORY, path.as_ptr() as usize, READ_ONLY, 0, 0, 0);
if file < 0 {
+124 -12
View File
@@ -192,7 +192,11 @@ fn block_until_signalled_or_timed_out(semaphore: u32, timeout_us: u64) {
}
}
pub fn poke_loop_wakeup() {}
pub fn poke_loop_wakeup() {
if in_copy() {
crate::win9x_user::poke_loop_wakeup();
}
}
pub fn wake(token: *const u8) {
(primitives().wake)(token)
@@ -220,7 +224,8 @@ pub fn inject_agent(pid: u32) -> u32 {
0xff, 0x74, 0x24, 0x04,
0xba, 0, 0, 0, 0,
0xff, 0xd2,
0xeb, 0xfe,
0x59,
0xc3,
];
payload[5..9].copy_from_slice(&entry.to_le_bytes());
@@ -244,6 +249,7 @@ pub fn inject_agent(pid: u32) -> u32 {
unsafe {
targets().insert(observed, Target {
arena: injection.arena,
thread: injection.thread,
stack: injection.stack,
image_base,
})
@@ -532,15 +538,15 @@ pub fn detach_from_process(pid: u32) -> bool {
};
let arena = target.arena;
unsafe { DEPARTING_THREAD = target.thread };
unsafe { ((arena + STOP_REQUEST) as *mut u32).write_volatile(1) };
if !await_flag(arena + WORKER_STOPPED) || !await_flag(arena + MAIN_STOPPED) {
wake_copy(arena);
if !await_flag(arena + WORKER_STOPPED) || !await_flag(arena + MAIN_STOPPED)
|| !await_departure()
{
return false;
}
// A thread sets the flag before it leaves. Thus wait for the time it needs to run its last
// instructions and return into KERNEL32.
wait(core::ptr::addr_of!(TEARDOWN_TOKEN), Some(TEARDOWN_GRACE_US), &mut || false);
forget_hold(arena);
unsafe {
@@ -556,13 +562,26 @@ pub fn detach_from_process(pid: u32) -> bool {
true
}
fn await_departure() -> bool {
for _ in 0..INJECTION_ATTEMPTS {
if unsafe { (&raw const DEPARTING_THREAD).read_volatile() } == 0 {
return true;
}
wait(core::ptr::addr_of!(DEPARTURE_TOKEN), Some(INJECTION_POLL_US), &mut || false);
}
false
}
struct Target {
arena: u32,
thread: u32,
stack: u32,
image_base: u32,
}
static mut TEARDOWN_TOKEN: u8 = 0;
static mut DEPARTING_THREAD: u32 = 0;
static mut DEPARTURE_TOKEN: u8 = 0;
pub fn arena_for_pid(pid: u32) -> Option<u64> {
unsafe { targets().get(&pid).map(|t| t.arena as u64) }
@@ -784,12 +803,13 @@ pub fn inject(process: u32, payload: &[u8]) -> Injection {
let database = unsafe { ((arena + THREAD_DATABASE) as *const u32).read_volatile() };
let thread = unsafe { (database as *const u32).byte_add(TDB_CONTROL_BLOCK).read() };
redirect_to_trampoline(arena, thread);
let start_stack = redirect_to_trampoline(arena, thread);
resume_thread(arena + RESUME_STUB_OFFSET, arena);
if !await_flag(arena + RUNNING_FLAG) {
return Injection { arena, thread: 0, stack };
}
free_shared(start_stack);
unsafe {
((arena + GATING) as *mut u32).write_volatile(spawns_are_gated() as u32);
((arena + GO_FLAG) as *mut u32).write_volatile(1);
@@ -1030,18 +1050,24 @@ fn write_trampoline(trampoline: u32, entry: u32, stack_top: u32) {
0x50,
0xba, 0, 0, 0, 0,
0xff, 0xd2,
0x64, 0xa1, 0x18, 0x00, 0x00, 0x00,
0x89, 0x60, 0x58,
0x89, 0x60, 0x04,
0xc7, 0x40, 0x08, 0, 0, 0, 0,
0x6a, 0x00,
0xba, 0, 0, 0, 0,
0xff, 0xd2,
];
let stack_bottom = stack_top + 0x40 - INJECTION_STACK_SIZE as u32;
code[5..9].copy_from_slice(&stack_top.to_le_bytes());
code[11..15].copy_from_slice(&entry.to_le_bytes());
code[20..24].copy_from_slice(&kernel32_export(b"ExitThread").to_le_bytes());
code[32..36].copy_from_slice(&stack_bottom.to_le_bytes());
code[39..43].copy_from_slice(&kernel32_export(b"ExitThread").to_le_bytes());
unsafe { core::ptr::copy_nonoverlapping(code.as_ptr(), trampoline as *mut u8, code.len()) };
}
fn redirect_to_trampoline(arena: u32, thread: u32) {
fn redirect_to_trampoline(arena: u32, thread: u32) -> u32 {
let mut context = [0u8; CONTEXT_SIZE];
let base = context.as_mut_ptr();
unsafe {
@@ -1065,6 +1091,8 @@ fn redirect_to_trampoline(arena: u32, thread: u32) {
base.add(CONTEXT_ESP).cast::<u32>().write_unaligned(top);
__VWIN32_Set_Thread_Context(thread, base);
}
stack
}
fn await_flag(address: u32) -> bool {
@@ -1128,6 +1156,26 @@ pub fn in_copy() -> bool {
core::ptr::eq(primitives(), &crate::win9x_user::USER)
}
pub fn thread_error() -> u32 {
if in_copy() { crate::win9x_user::last_error() } else { 0 }
}
pub fn set_thread_error(value: u32) {
if in_copy() {
crate::win9x_user::set_last_error(value);
}
}
pub fn file_api() -> &'static crate::gum_windows::FileApi {
crate::win9x_user::file_api()
}
pub fn text_api() -> Option<&'static crate::gum_windows::TextApi> {
if in_copy() { Some(crate::win9x_user::text_api()) } else { None }
}
pub(crate) use crate::win9x_user::poll_handles;
fn primitives() -> &'static Primitives {
unsafe { ACTIVE }
}
@@ -1462,6 +1510,48 @@ pub(crate) fn enumerate_exports(base: u32, found: &mut dyn FnMut(*const u8, u64)
}
}
pub(crate) fn enumerate_imports(base: u32,
found: &mut dyn FnMut(*const u8, *const u8, u64, u64) -> bool) {
let headers = base + read_u32(base + DOS_HEADERS_OFFSET);
let directory_rva = read_u32(headers + IMPORT_DIRECTORY_OFFSET);
if directory_rva == 0 {
return;
}
let mut descriptor = base + directory_rva;
loop {
let library_rva = read_u32(descriptor + IMPORT_NAME_OFFSET);
if library_rva == 0 {
return;
}
let library = (base + library_rva) as *const u8;
let thunks = base + read_u32(descriptor + IMPORT_THUNKS_OFFSET);
let lookups = match read_u32(descriptor + IMPORT_LOOKUPS_OFFSET) {
0 => thunks,
rva => base + rva,
};
let mut index = 0;
loop {
let lookup = read_u32(lookups + index * 4);
if lookup == 0 {
break;
}
let slot = thunks + index * 4;
let imported_by_name = lookup & IMPORT_BY_ORDINAL == 0;
if imported_by_name {
let name = (base + lookup + IMPORT_HINT_SIZE) as *const u8;
if !found(library, name, slot as u64, read_u32(slot) as u64) {
return;
}
}
index += 1;
}
descriptor += IMPORT_DESCRIPTOR_SIZE;
}
}
pub fn kernel32_export(wanted: &[u8]) -> u32 {
let base = unsafe { core::ptr::addr_of!(_KERNEL32_Base).read() };
let headers = base + read_u32(base + DOS_HEADERS_OFFSET);
@@ -1512,6 +1602,13 @@ fn read_u8(address: u32) -> u8 {
}
const EXPORT_DIRECTORY_OFFSET: u32 = 0x78;
const IMPORT_DIRECTORY_OFFSET: u32 = 0x80;
const IMPORT_DESCRIPTOR_SIZE: u32 = 20;
const IMPORT_LOOKUPS_OFFSET: u32 = 0;
const IMPORT_NAME_OFFSET: u32 = 12;
const IMPORT_THUNKS_OFFSET: u32 = 16;
const IMPORT_BY_ORDINAL: u32 = 0x8000_0000;
const IMPORT_HINT_SIZE: u32 = 2;
pub(crate) const MEM_RESERVE: u32 = 0x0000_2000;
pub(crate) const MEM_RELEASE: u32 = 0x0000_8000;
@@ -1553,7 +1650,6 @@ const PATCH_SIZE: usize = 2;
const PE_SIGNATURE_OFFSET: usize = 0x3c;
const PE_ENTRY_POINT_OFFSET: usize = 0x28;
const FRAME_BUFFER_SIZE: usize = 0x4000;
const TEARDOWN_GRACE_US: u64 = 500_000;
const TDB_CONTROL_BLOCK: usize = 0x5c;
pub(crate) const PARKED_SLEEP_MS: u32 = 200;
const STUB_OFFSET: u32 = 0x100;
@@ -2291,6 +2387,10 @@ extern "C" fn frida_win9x_on_control(message: u32, thread: u32) {
tell_the_copy_of_a_departure(thread);
}
if message == DESTROY_THREAD && thread == unsafe { DEPARTING_THREAD } {
unsafe { (&raw mut DEPARTING_THREAD).write_volatile(0) };
}
let told = match message {
THREAD_INIT => unsafe { THREAD_APPEARED },
DESTROY_THREAD => unsafe { THREAD_VANISHED },
@@ -2436,6 +2536,10 @@ extern "C" fn frida_win9x_on_fault(fault: u32, frame: *mut u32) -> u32 {
)
};
if handled == 0 {
if fault == PAGE_FAULT && page_is_absent(registers) {
return unsafe { FAULT_CHAIN[fault as usize] };
}
report_unhandled_fault(fault, cpu_context.eip);
cpu_context.eip = frida_win9x_park as *const () as u32;
}
@@ -2472,6 +2576,12 @@ fn recovery_stack(frame: *mut u32) -> u32 {
unsafe { frame.add(THUNK_FRAME_ESP).read() }
}
fn page_is_absent(registers: *const u32) -> bool {
let error_code = unsafe { registers.byte_add(FAULT_FRAME_ERROR_CODE).read() };
error_code & PAGE_FAULT_PRESENT == 0
}
fn report_unhandled_fault(fault: u32, eip: u32) {
let address = faulting_address();
@@ -2524,8 +2634,10 @@ const SIMD_FLOATING_POINT: u32 = 19;
const THUNK_FRAME_EBP: usize = 2;
const THUNK_FRAME_ESP: usize = 3;
const FAULT_FRAME_ERROR_CODE: usize = 0x20;
const FAULT_FRAME_EIP: usize = 0x24;
const FAULT_FRAME_CS: usize = 0x28;
const PAGE_FAULT_PRESENT: u32 = 1 << 0;
const PARK_SLICE_US: u64 = 1_000_000;
fn faulting_address() -> u32 {
+158 -11
View File
@@ -8,6 +8,7 @@ use alloc::vec::Vec;
use core::ffi::c_void;
use core::sync::atomic::{AtomicBool, Ordering};
use crate::gum_windows::{FileApi, TextApi};
use crate::kernel::{MemoryRegion, ThreadEntry};
use crate::win9x::*;
@@ -48,9 +49,6 @@ pub extern "C" fn frida_win9x_user_main(arena: u32) {
stop_hearing_about_faults();
unsafe { ((arena + MAIN_STOPPED) as *mut u32).write_volatile(1) };
loop {
unsafe { sleep(PARKED_SLEEP_MS) };
}
}
// The group that the user-mode half runs on. The order is the order in Primitives.
@@ -166,6 +164,97 @@ fn copy_has_work() -> bool {
}
}
static LOOP_WAKE_EVENT: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
fn loop_wake_event() -> u32 {
let existing = LOOP_WAKE_EVENT.load(Ordering::Acquire);
if existing != 0 {
return existing;
}
let create_event: unsafe extern "stdcall" fn(u32, u32, u32, u32) -> u32 =
unsafe { core::mem::transmute(user_api().create_event as usize) };
let created = unsafe { create_event(0, 0, 0, 0) };
match LOOP_WAKE_EVENT.compare_exchange(0, created, Ordering::AcqRel, Ordering::Acquire) {
Ok(_) => created,
Err(raced) => {
let close_handle: unsafe extern "stdcall" fn(u32) -> u32 =
unsafe { core::mem::transmute(user_api().close_handle as usize) };
unsafe { close_handle(created) };
raced
}
}
}
pub(crate) fn poke_loop_wakeup() {
let set_event: unsafe extern "stdcall" fn(u32) -> u32 =
unsafe { core::mem::transmute(user_api().set_event as usize) };
unsafe { set_event(loop_wake_event()) };
}
pub(crate) unsafe extern "C" fn poll_handles(ufds: *mut crate::bindings::GPollFD, nfds: u32,
timeout: i32) -> i32 {
const MAX_HANDLES: usize = 16;
let fds = unsafe { core::slice::from_raw_parts_mut(ufds, nfds as usize) };
let wake = loop_wake_event();
let mut handles = [0u32; MAX_HANDLES];
let mut origin = [usize::MAX; MAX_HANDLES];
handles[0] = wake;
let mut count = 1usize;
let mut ready = 0i32;
for (index, fd) in fds.iter_mut().enumerate() {
fd.revents = 0;
if fd.fd == G_WAIT_WAKEUP_HANDLE {
if !fd.user_data.is_null() {
if unsafe { (fd.user_data as *const i32).read_volatile() } != 0 {
fd.revents = POLL_IN;
ready += 1;
}
let token = (fd.user_data as usize + GWAKEUP_TOKEN) as *mut *mut c_void;
unsafe { token.write(fd.user_data) };
}
} else if fd.fd != -1 && fd.fd as u32 != wake && count < MAX_HANDLES {
handles[count] = fd.fd as u32;
origin[count] = index;
count += 1;
}
}
if ready == 0 {
let wait_for_multiple_objects_ex: unsafe extern "stdcall" fn(u32, *const u32, u32, u32, u32) -> u32 =
unsafe { core::mem::transmute(user_api().wait_for_multiple_objects_ex as usize) };
let timeout_ms = if timeout < 0 { INFINITE } else { timeout as u32 };
let status = unsafe {
wait_for_multiple_objects_ex(count as u32, handles.as_ptr(), 0, timeout_ms, 1)
};
if (status as usize) < count {
let slot = origin[status as usize];
if slot != usize::MAX {
fds[slot].revents = POLL_IN;
ready += 1;
}
}
}
for fd in fds.iter() {
if fd.fd == G_WAIT_WAKEUP_HANDLE && !fd.user_data.is_null() {
let token = (fd.user_data as usize + GWAKEUP_TOKEN) as *mut *mut c_void;
unsafe { token.write(core::ptr::null_mut()) };
}
}
ready
}
const G_WAIT_WAKEUP_HANDLE: i32 = -43;
const GWAKEUP_TOKEN: usize = 4;
const POLL_IN: u16 = 1;
fn serve_the_copy() {
let arena = unsafe { ARENA };
@@ -224,6 +313,7 @@ unsafe extern "C" fn user_worker(parameter: *mut c_void, _wait_result: i32) {
crate::gum_windows::watch_the_loader();
crate::glib::own_the_loop();
crate::watch_for_work(context, copy_has_work, serve_the_copy);
crate::watch_a_descriptor(context, loop_wake_event() as i32, poll_handles);
while unsafe { ((arena + STOP_REQUEST) as *const u32).read_volatile() } == 0 {
unsafe { crate::dispatch_pending_work(context) };
@@ -331,19 +421,11 @@ const GUM_PAGE_WRITE: u32 = 0x2;
const GUM_PAGE_EXECUTE: u32 = 0x4;
pub unsafe extern "stdcall" fn on_module_load(name: *const u8) -> u32 {
unsafe {
let n = (ARENA + 0xb0) as *mut u32;
n.write_volatile(n.read_volatile() + 1);
}
let original: extern "stdcall" fn(*const u8) -> u32 =
unsafe { core::mem::transmute(crate::gum_windows::loader_load()) };
let handle = original(name);
note_module(handle, name);
unsafe {
let n = (ARENA + 0xb4) as *mut u32;
n.write_volatile(n.read_volatile() + 1);
}
handle
}
@@ -514,13 +596,20 @@ fn resolve_user_api() {
create_event: kernel32_export(b"CreateEventA"),
set_event: kernel32_export(b"SetEvent"),
wait_for_single_object_ex: kernel32_export(b"WaitForSingleObjectEx"),
wait_for_multiple_objects_ex: kernel32_export(b"WaitForMultipleObjectsEx"),
close_handle: kernel32_export(b"CloseHandle"),
set_file_pointer: kernel32_export(b"SetFilePointer"),
create_file: kernel32_export(b"CreateFileA"),
read_file: kernel32_export(b"ReadFile"),
write_file: kernel32_export(b"WriteFile"),
find_first_file: kernel32_export(b"FindFirstFileA"),
find_next_file: kernel32_export(b"FindNextFileA"),
find_close: kernel32_export(b"FindClose"),
get_last_error: kernel32_export(b"GetLastError"),
set_last_error: kernel32_export(b"SetLastError"),
format_message: kernel32_export(b"FormatMessageA"),
multi_byte_to_wide_char: kernel32_export(b"MultiByteToWideChar"),
wide_char_to_multi_byte: kernel32_export(b"WideCharToMultiByte"),
set_unhandled_exception_filter: kernel32_export(b"SetUnhandledExceptionFilter"),
tls_alloc: kernel32_export(b"TlsAlloc"),
tls_get_value: kernel32_export(b"TlsGetValue"),
@@ -533,6 +622,50 @@ fn user_api() -> &'static UserApi {
unsafe { &*core::ptr::addr_of!(USER_API) }
}
pub fn last_error() -> u32 {
let get_last_error: unsafe extern "stdcall" fn() -> u32 =
unsafe { core::mem::transmute(user_api().get_last_error as usize) };
unsafe { get_last_error() }
}
pub fn set_last_error(value: u32) {
let set_last_error: unsafe extern "stdcall" fn(u32) =
unsafe { core::mem::transmute(user_api().set_last_error as usize) };
unsafe { set_last_error(value) }
}
pub fn file_api() -> &'static FileApi {
unsafe {
if (*core::ptr::addr_of!(FILE_API)).is_none() {
let api = user_api();
FILE_API = Some(FileApi {
read_file: core::mem::transmute(api.read_file as usize),
write_file: core::mem::transmute(api.write_file as usize),
close_handle: core::mem::transmute(api.close_handle as usize),
format_message: core::mem::transmute(api.format_message as usize),
});
}
(*core::ptr::addr_of!(FILE_API)).as_ref().unwrap()
}
}
static mut FILE_API: Option<FileApi> = None;
pub fn text_api() -> &'static TextApi {
unsafe {
if (*core::ptr::addr_of!(TEXT_API)).is_none() {
let api = user_api();
TEXT_API = Some(TextApi {
multi_byte_to_wide_char: core::mem::transmute(api.multi_byte_to_wide_char as usize),
wide_char_to_multi_byte: core::mem::transmute(api.wide_char_to_multi_byte as usize),
});
}
(*core::ptr::addr_of!(TEXT_API)).as_ref().unwrap()
}
}
static mut TEXT_API: Option<TextApi> = None;
struct UserApi {
sleep: u32,
get_tick_count: u32,
@@ -553,13 +686,20 @@ struct UserApi {
create_event: u32,
set_event: u32,
wait_for_single_object_ex: u32,
wait_for_multiple_objects_ex: u32,
close_handle: u32,
set_file_pointer: u32,
create_file: u32,
read_file: u32,
write_file: u32,
find_first_file: u32,
find_next_file: u32,
find_close: u32,
get_last_error: u32,
set_last_error: u32,
format_message: u32,
multi_byte_to_wide_char: u32,
wide_char_to_multi_byte: u32,
set_unhandled_exception_filter: u32,
tls_alloc: u32,
tls_get_value: u32,
@@ -586,13 +726,20 @@ static mut USER_API: UserApi = UserApi {
create_event: 0,
set_event: 0,
wait_for_single_object_ex: 0,
wait_for_multiple_objects_ex: 0,
close_handle: 0,
set_file_pointer: 0,
create_file: 0,
read_file: 0,
write_file: 0,
find_first_file: 0,
find_next_file: 0,
find_close: 0,
get_last_error: 0,
set_last_error: 0,
format_message: 0,
multi_byte_to_wide_char: 0,
wide_char_to_multi_byte: 0,
set_unhandled_exception_filter: 0,
tls_alloc: 0,
tls_get_value: 0,
+97 -31
View File
@@ -28,31 +28,7 @@ type ThreadChangeRoutine = unsafe extern "win64" fn(usize, usize, u8);
#[cfg(target_arch = "aarch64")]
type ThreadChangeRoutine = unsafe extern "C" fn(usize, usize, u8);
#[cfg(target_arch = "x86")]
macro_rules! windows_fn {
($($argument:ty),* $(,)?) => { unsafe extern "stdcall" fn($($argument),*) };
($($argument:ty),* $(,)? => $result:ty) => {
unsafe extern "stdcall" fn($($argument),*) -> $result
};
}
#[cfg(target_arch = "x86_64")]
macro_rules! windows_fn {
($($argument:ty),* $(,)?) => { unsafe extern "win64" fn($($argument),*) };
($($argument:ty),* $(,)? => $result:ty) => {
unsafe extern "win64" fn($($argument),*) -> $result
};
}
#[cfg(target_arch = "aarch64")]
macro_rules! windows_fn {
($($argument:ty),* $(,)?) => { unsafe extern "C" fn($($argument),*) };
($($argument:ty),* $(,)? => $result:ty) => {
unsafe extern "C" fn($($argument),*) -> $result
};
}
pub(crate) use windows_fn;
pub(crate) use crate::gum_windows::windows_fn;
pub const MODULE_DIRECTORY: &str = "/WINDOWS/system32/";
@@ -3447,6 +3423,26 @@ pub fn in_copy() -> bool {
core::ptr::eq(primitives(), &crate::winnt_user::USER)
}
pub fn thread_error() -> u32 {
if in_copy() { crate::winnt_user::last_error() } else { 0 }
}
pub fn set_thread_error(value: u32) {
if in_copy() {
crate::winnt_user::set_last_error(value);
}
}
pub fn file_api() -> &'static crate::gum_windows::FileApi {
crate::winnt_user::file_api()
}
pub fn text_api() -> Option<&'static crate::gum_windows::TextApi> {
if in_copy() { Some(crate::winnt_user::text_api()) } else { None }
}
pub(crate) use crate::winnt_user::poll_handles;
fn primitives() -> &'static Primitives {
unsafe { ACTIVE }
}
@@ -3749,19 +3745,83 @@ pub(crate) fn enumerate_exports(base: usize, found: &mut dyn FnMut(*const u8, u6
}
}
pub(crate) fn enumerate_imports(base: usize,
found: &mut dyn FnMut(*const u8, *const u8, u64, u64) -> bool) {
unsafe {
let directories = data_directories(base);
let directory_rva = read_u32(directories + IMPORT_DIRECTORY_INDEX * 8) as usize;
if directory_rva == 0 {
return;
}
let wide = is_pe32_plus(base);
let thunk_size = if wide { 8 } else { 4 };
let by_ordinal: u64 = if wide { 1 << 63 } else { 1 << 31 };
let mut descriptor = base + directory_rva;
loop {
let library_rva = read_u32(descriptor + IMPORT_NAME_OFFSET) as usize;
if library_rva == 0 {
return;
}
let library = (base + library_rva) as *const u8;
let thunks = base + read_u32(descriptor + IMPORT_THUNKS_OFFSET) as usize;
let lookups = match read_u32(descriptor + IMPORT_LOOKUPS_OFFSET) as usize {
0 => thunks,
rva => base + rva,
};
let mut index = 0;
loop {
let lookup = read_thunk(lookups + index * thunk_size, wide);
if lookup == 0 {
break;
}
let slot = thunks + index * thunk_size;
if lookup & by_ordinal == 0 {
let name = (base + lookup as usize + IMPORT_HINT_SIZE) as *const u8;
if !found(library, name, slot as u64, read_thunk(slot, wide)) {
return;
}
}
index += 1;
}
descriptor += IMPORT_DESCRIPTOR_SIZE;
}
}
}
unsafe fn read_thunk(address: usize, wide: bool) -> u64 {
unsafe {
if wide {
(address as *const u64).read()
} else {
read_u32(address) as u64
}
}
}
unsafe fn export_directory(base: usize) -> usize {
unsafe { base + read_u32(data_directories(base)) as usize }
}
unsafe fn is_pe32_plus(base: usize) -> bool {
unsafe {
let headers = base + read_u32(base + PE_HEADERS_OFFSET) as usize;
let magic = (headers + OPTIONAL_HEADER_OFFSET) as *const u16;
let directories = headers
((headers + OPTIONAL_HEADER_OFFSET) as *const u16).read() == PE32_PLUS_MAGIC
}
}
unsafe fn data_directories(base: usize) -> usize {
unsafe {
let headers = base + read_u32(base + PE_HEADERS_OFFSET) as usize;
headers
+ OPTIONAL_HEADER_OFFSET
+ if magic.read() == PE32_PLUS_MAGIC {
+ if is_pe32_plus(base) {
DATA_DIRECTORIES_OFFSET_64
} else {
DATA_DIRECTORIES_OFFSET_32
};
base + read_u32(directories) as usize
}
}
}
@@ -3840,6 +3900,12 @@ const PE32_PLUS_MAGIC: u16 = 0x20b;
const DATA_DIRECTORIES_OFFSET_32: usize = 0x60;
const DATA_DIRECTORIES_OFFSET_64: usize = 0x70;
const EXPORT_NAME_COUNT_OFFSET: usize = 0x18;
const IMPORT_DIRECTORY_INDEX: usize = 1;
const IMPORT_DESCRIPTOR_SIZE: usize = 20;
const IMPORT_LOOKUPS_OFFSET: usize = 0;
const IMPORT_NAME_OFFSET: usize = 12;
const IMPORT_THUNKS_OFFSET: usize = 16;
const IMPORT_HINT_SIZE: usize = 2;
const EXPORT_FUNCTIONS_OFFSET: usize = 0x1c;
const EXPORT_NAMES_OFFSET: usize = 0x20;
const EXPORT_ORDINALS_OFFSET: usize = 0x24;
+64 -1
View File
@@ -8,6 +8,7 @@ use alloc::vec::Vec;
use core::ffi::c_void;
use core::sync::atomic::{AtomicUsize, Ordering};
use crate::gum_windows::{FileApi, TextApi};
use crate::kernel::MemoryRegion;
use crate::winnt::{
BLOCK_PROCESS_ID, BLOCK_THREAD_ID, CURRENT_PROCESS, MEM_COMMIT, MEM_RELEASE, MEM_RESERVE,
@@ -788,6 +789,68 @@ fn user_api() -> &'static UserApi {
unsafe { (*core::ptr::addr_of!(USER_API)).as_ref().unwrap() }
}
pub fn last_error() -> u32 {
unsafe { (error_api().get_last_error)() }
}
pub fn set_last_error(value: u32) {
unsafe { (error_api().set_last_error)(value) }
}
fn error_api() -> &'static ErrorApi {
unsafe {
if (*core::ptr::addr_of!(ERROR_API)).is_none() {
let library = module_base(peb(), b"kernel32.dll");
ERROR_API = Some(ErrorApi {
get_last_error: core::mem::transmute(export(library, b"GetLastError")),
set_last_error: core::mem::transmute(export(library, b"SetLastError")),
});
}
(*core::ptr::addr_of!(ERROR_API)).as_ref().unwrap()
}
}
struct ErrorApi {
get_last_error: windows_fn!(=> u32),
set_last_error: windows_fn!(u32),
}
static mut ERROR_API: Option<ErrorApi> = None;
pub fn file_api() -> &'static FileApi {
unsafe {
if (*core::ptr::addr_of!(FILE_API)).is_none() {
let library = module_base(peb(), b"kernel32.dll");
FILE_API = Some(FileApi {
read_file: core::mem::transmute(export(library, b"ReadFile")),
write_file: core::mem::transmute(export(library, b"WriteFile")),
close_handle: core::mem::transmute(export(library, b"CloseHandle")),
format_message: core::mem::transmute(export(library, b"FormatMessageA")),
});
}
(*core::ptr::addr_of!(FILE_API)).as_ref().unwrap()
}
}
static mut FILE_API: Option<FileApi> = None;
pub fn text_api() -> &'static TextApi {
unsafe {
if (*core::ptr::addr_of!(TEXT_API)).is_none() {
let library = module_base(peb(), b"kernel32.dll");
TEXT_API = Some(TextApi {
multi_byte_to_wide_char: core::mem::transmute(
export(library, b"MultiByteToWideChar")),
wide_char_to_multi_byte: core::mem::transmute(
export(library, b"WideCharToMultiByte")),
});
}
(*core::ptr::addr_of!(TEXT_API)).as_ref().unwrap()
}
}
static mut TEXT_API: Option<TextApi> = None;
struct UserApi {
allocate_heap: windows_fn!(usize, u32, usize => *mut u8),
free_heap: windows_fn!(usize, u32, *mut u8 => u8),
@@ -969,7 +1032,7 @@ fn serve_the_copy() {
}
}
unsafe extern "C" fn poll_handles(ufds: *mut crate::bindings::GPollFD, nfds: u32,
pub(crate) unsafe extern "C" fn poll_handles(ufds: *mut crate::bindings::GPollFD, nfds: u32,
timeout: i32) -> i32 {
const MAX_HANDLES: usize = 16;
let fds = unsafe { core::slice::from_raw_parts_mut(ufds, nfds as usize) };
+77 -49
View File
@@ -553,6 +553,7 @@ namespace Frida {
string project_root = compute_project_root (entrypoint, opts.project_root);
string platform = (opts.platform != null) ? opts.platform : "";
string arch = (opts.arch != null) ? opts.arch : "";
string? defines = PatternJson.serialize_dict (opts.defines);
string? result = null;
string? error_message = null;
@@ -562,13 +563,13 @@ namespace Frida {
schedule_on_frida_thread (compile.callback);
};
CompilerBackend.Patterns.describe (project_root, entrypoint, platform, arch, (owned) on_result);
CompilerBackend.Patterns.describe (project_root, entrypoint, platform, arch, defines, (owned) on_result);
yield;
if (error_message != null)
throw new Error.INVALID_ARGUMENT ("%s", error_message);
return PatternJson.parse_module (result, this, project_root, entrypoint, platform, arch);
return PatternJson.parse_module (result, this, project_root, entrypoint, platform, arch, defines);
}
public PatternModule compile_sync (string entrypoint, PatternCompileOptions? options = null, Cancellable? cancellable = null)
@@ -589,8 +590,8 @@ namespace Frida {
}
internal async PatternValue decode_value (string project_root, string entrypoint, string platform, string arch,
string type_name, Bytes data, uint64 address, PatternDecodeOptions? options, Cancellable? cancellable)
throws Error, IOError {
string? defines, string type_name, Bytes data, uint64 address, PatternDecodeOptions? options,
Cancellable? cancellable) throws Error, IOError {
CompilerBackend.check_available ();
string? result = null;
@@ -602,7 +603,7 @@ namespace Frida {
};
CompilerBackend.Patterns.decode (project_root, entrypoint, type_name, data.get_data (), address, platform, arch,
PatternJson.serialize_inputs (options), (owned) on_result);
defines, PatternJson.serialize_inputs (options), (owned) on_result);
yield;
if (error_message != null)
@@ -612,8 +613,8 @@ namespace Frida {
}
internal async string call_value_function (string project_root, string entrypoint, string platform, string arch,
string type_name, Bytes data, uint64 address, uint pattern, string function, PatternDecodeOptions? options,
Cancellable? cancellable) throws Error, IOError {
string? defines, string type_name, Bytes data, uint64 address, uint pattern, string function,
PatternDecodeOptions? options, Cancellable? cancellable) throws Error, IOError {
CompilerBackend.check_available ();
string? result = null;
@@ -625,7 +626,7 @@ namespace Frida {
};
CompilerBackend.Patterns.call (project_root, entrypoint, type_name, data.get_data (), address, platform, arch,
PatternJson.serialize_inputs (options), pattern, function, (owned) on_result);
defines, PatternJson.serialize_inputs (options), pattern, function, (owned) on_result);
yield;
if (error_message != null)
@@ -681,6 +682,16 @@ namespace Frida {
get;
set;
}
/**
* Preprocessor macros to define before the source is read, by name,
* the way a `#define` at the top of the entrypoint would.
*/
public HashTable<string, Variant> defines {
get;
set;
default = make_parameters_dict ();
}
}
/**
@@ -727,9 +738,11 @@ namespace Frida {
private string entrypoint;
private string platform;
private string arch;
private string? defines;
internal PatternModule (PatternCompiler compiler, string project_root, string entrypoint, string platform, string arch,
PatternTypeList types, string? root_type, PatternInputList inputs, PatternDiagnosticList diagnostics) {
string? defines, PatternTypeList types, string? root_type, PatternInputList inputs,
PatternDiagnosticList diagnostics) {
Object (types: types, root_type: root_type, inputs: inputs, diagnostics: diagnostics);
this.compiler = compiler;
@@ -737,6 +750,7 @@ namespace Frida {
this.entrypoint = entrypoint;
this.platform = platform;
this.arch = arch;
this.defines = defines;
}
/**
@@ -760,8 +774,8 @@ namespace Frida {
*/
public async PatternValue decode (string type_name, Bytes data, uint64 address, PatternDecodeOptions? options = null,
Cancellable? cancellable = null) throws Error, IOError {
return yield compiler.decode_value (project_root, entrypoint, platform, arch, type_name, data, address, options,
cancellable);
return yield compiler.decode_value (project_root, entrypoint, platform, arch, defines, type_name, data, address,
options, cancellable);
}
public PatternValue decode_sync (string type_name, Bytes data, uint64 address, PatternDecodeOptions? options = null,
@@ -800,8 +814,8 @@ namespace Frida {
*/
public async string call_function (string type_name, Bytes data, uint64 address, uint pattern, string function,
PatternDecodeOptions? options = null, Cancellable? cancellable = null) throws Error, IOError {
return yield compiler.call_value_function (project_root, entrypoint, platform, arch, type_name, data, address, pattern,
function, options, cancellable);
return yield compiler.call_value_function (project_root, entrypoint, platform, arch, defines, type_name, data, address,
pattern, function, options, cancellable);
}
public string call_function_sync (string type_name, Bytes data, uint64 address, uint pattern, string function,
@@ -2106,7 +2120,7 @@ namespace Frida {
namespace PatternJson {
private PatternModule parse_module (string json, PatternCompiler compiler, string project_root, string entrypoint,
string platform, string arch) throws Error {
string platform, string arch, string? defines) throws Error {
var reader = make_reader (json);
var types = new Gee.ArrayList<PatternType> ();
@@ -2148,17 +2162,23 @@ namespace Frida {
}
reader.end_member ();
return new PatternModule (compiler, project_root, entrypoint, platform, arch, new PatternTypeList (types),
return new PatternModule (compiler, project_root, entrypoint, platform, arch, defines, new PatternTypeList (types),
read_optional_string (reader, "root"), new PatternInputList (inputs), new PatternDiagnosticList (diagnostics));
}
internal string? serialize_inputs (PatternDecodeOptions? options) {
if (options == null || options.inputs.size () == 0)
if (options == null)
return null;
return serialize_dict (options.inputs);
}
internal string? serialize_dict (HashTable<string, Variant> dict) {
if (dict.size () == 0)
return null;
var builder = new Json.Builder ();
builder.begin_object ();
options.inputs.for_each ((name, value) => {
dict.for_each ((name, value) => {
builder.set_member_name (name);
if (value.is_of_type (VariantType.INT64))
builder.add_int_value (value.get_int64 ());
@@ -2675,15 +2695,16 @@ namespace Frida {
namespace Patterns {
[CCode (has_target = false)]
private delegate void DescribeFunc (string project_root, string entrypoint, string platform, string arch,
owned PatternResultFunc on_result);
string? defines, owned PatternResultFunc on_result);
[CCode (has_target = false)]
private delegate void DecodeFunc (string project_root, string entrypoint, string type_name, uint8[] data, uint64 address,
string platform, string arch, string? inputs, owned PatternResultFunc on_result);
string platform, string arch, string? defines, string? inputs, owned PatternResultFunc on_result);
[CCode (has_target = false)]
private delegate void CallFunc (string project_root, string entrypoint, string type_name, uint8[] data, uint64 address,
string platform, string arch, string? inputs, uint pattern, string function, owned PatternResultFunc on_result);
string platform, string arch, string? defines, string? inputs, uint pattern, string function,
owned PatternResultFunc on_result);
private DescribeFunc? describe;
private DecodeFunc? decode;
@@ -2763,21 +2784,21 @@ namespace Frida {
}
private static void executable_patterns_describe (string project_root, string entrypoint, string platform, string arch,
owned PatternResultFunc on_result) {
backend_process.describe_patterns (project_root, entrypoint, platform, arch, (owned) on_result);
string? defines, owned PatternResultFunc on_result) {
backend_process.describe_patterns (project_root, entrypoint, platform, arch, defines, (owned) on_result);
}
private static void executable_patterns_decode (string project_root, string entrypoint, string type_name, uint8[] data,
uint64 address, string platform, string arch, string? inputs, owned PatternResultFunc on_result) {
backend_process.decode_pattern (project_root, entrypoint, type_name, data, address, platform, arch, inputs,
uint64 address, string platform, string arch, string? defines, string? inputs, owned PatternResultFunc on_result) {
backend_process.decode_pattern (project_root, entrypoint, type_name, data, address, platform, arch, defines, inputs,
(owned) on_result);
}
private static void executable_patterns_call (string project_root, string entrypoint, string type_name, uint8[] data,
uint64 address, string platform, string arch, string? inputs, uint pattern, string function,
uint64 address, string platform, string arch, string? defines, string? inputs, uint pattern, string function,
owned PatternResultFunc on_result) {
backend_process.call_pattern_function (project_root, entrypoint, type_name, data, address, platform, arch, inputs,
pattern, function, (owned) on_result);
backend_process.call_pattern_function (project_root, entrypoint, type_name, data, address, platform, arch, defines,
inputs, pattern, function, (owned) on_result);
}
private class BackendProcess : Object {
@@ -3072,7 +3093,7 @@ namespace Frida {
return null;
}
public void describe_patterns (string project_root, string entrypoint, string platform, string arch,
public void describe_patterns (string project_root, string entrypoint, string platform, string arch, string? defines,
owned PatternResultFunc on_result) {
try {
ensure_started ();
@@ -3085,11 +3106,11 @@ namespace Frida {
pending_pattern_requests[request_id] = new PendingPatternRequest ((owned) on_result);
post_message (make_patterns_describe_request (request_id, project_root, entrypoint, platform, arch));
post_message (make_patterns_describe_request (request_id, project_root, entrypoint, platform, arch, defines));
}
public void decode_pattern (string project_root, string entrypoint, string type_name, uint8[] data, uint64 address,
string platform, string arch, string? inputs, owned PatternResultFunc on_result) {
string platform, string arch, string? defines, string? inputs, owned PatternResultFunc on_result) {
try {
ensure_started ();
} catch (GLib.Error e) {
@@ -3102,11 +3123,11 @@ namespace Frida {
pending_pattern_requests[request_id] = new PendingPatternRequest ((owned) on_result);
post_message (make_patterns_decode_request (request_id, project_root, entrypoint, type_name, data, address, platform,
arch, inputs));
arch, defines, inputs));
}
public void call_pattern_function (string project_root, string entrypoint, string type_name, uint8[] data,
uint64 address, string platform, string arch, string? inputs, uint pattern, string function,
uint64 address, string platform, string arch, string? defines, string? inputs, uint pattern, string function,
owned PatternResultFunc on_result) {
try {
ensure_started ();
@@ -3120,7 +3141,7 @@ namespace Frida {
pending_pattern_requests[request_id] = new PendingPatternRequest ((owned) on_result);
post_message (make_patterns_call_request (request_id, project_root, entrypoint, type_name, data, address, platform,
arch, inputs, pattern, function));
arch, defines, inputs, pattern, function));
}
private class PendingPatternRequest {
@@ -3266,7 +3287,7 @@ namespace Frida {
}
private static string make_patterns_describe_request (uint id, string project_root, string entrypoint, string platform,
string arch) {
string arch, string? defines) {
var builder = new Json.Builder ();
builder
@@ -3282,25 +3303,27 @@ namespace Frida {
.set_member_name ("platform")
.add_string_value (platform)
.set_member_name ("arch")
.add_string_value (arch)
.end_object ();
.add_string_value (arch);
add_patterns_dict (builder, "defines", defines);
builder.end_object ();
return Json.to_string (builder.get_root (), false);
}
private static string make_patterns_decode_request (uint id, string project_root, string entrypoint, string type_name,
uint8[] data, uint64 address, string platform, string arch, string? inputs) {
uint8[] data, uint64 address, string platform, string arch, string? defines, string? inputs) {
var builder = begin_patterns_data_request ("patterns:decode", id, project_root, entrypoint, type_name, data, address,
platform, arch, inputs);
platform, arch, defines, inputs);
builder.end_object ();
return Json.to_string (builder.get_root (), false);
}
private static string make_patterns_call_request (uint id, string project_root, string entrypoint, string type_name,
uint8[] data, uint64 address, string platform, string arch, string? inputs, uint pattern, string function) {
uint8[] data, uint64 address, string platform, string arch, string? defines, string? inputs, uint pattern,
string function) {
var builder = begin_patterns_data_request ("patterns:call", id, project_root, entrypoint, type_name, data, address,
platform, arch, inputs);
platform, arch, defines, inputs);
builder
.set_member_name ("pattern")
.add_int_value (pattern)
@@ -3312,7 +3335,7 @@ namespace Frida {
}
private static Json.Builder begin_patterns_data_request (string type, uint id, string project_root, string entrypoint,
string type_name, uint8[] data, uint64 address, string platform, string arch, string? inputs) {
string type_name, uint8[] data, uint64 address, string platform, string arch, string? defines, string? inputs) {
var builder = new Json.Builder ();
builder
@@ -3335,18 +3358,23 @@ namespace Frida {
.add_string_value (platform)
.set_member_name ("arch")
.add_string_value (arch);
if (inputs != null) {
try {
builder
.set_member_name ("inputs")
.add_value (Json.from_string (inputs));
} catch (GLib.Error e) {
}
}
add_patterns_dict (builder, "defines", defines);
add_patterns_dict (builder, "inputs", inputs);
return builder;
}
private static void add_patterns_dict (Json.Builder builder, string name, string? json) {
if (json == null)
return;
try {
builder
.set_member_name (name)
.add_value (Json.from_string (json));
} catch (GLib.Error e) {
}
}
private static string make_dispose_request (uint session_id) {
var builder = new Json.Builder ();
+17 -12
View File
@@ -361,9 +361,9 @@ func _frida_compiler_backend_language_server_post(h uintptr, cText *C.char) *C.c
}
//export _frida_compiler_backend_patterns_describe
func _frida_compiler_backend_patterns_describe(cProjectRoot, cEntrypoint, cPlatform, cArch *C.char,
func _frida_compiler_backend_patterns_describe(cProjectRoot, cEntrypoint, cPlatform, cArch, cDefines *C.char,
onResultFn C.FridaPatternResultFunc, onResultData unsafe.Pointer, onResultDataDestroy C.FridaDestroyFunc) {
query := patternQueryFromC(cProjectRoot, cEntrypoint, cPlatform, cArch)
query := patternQueryFromC(cProjectRoot, cEntrypoint, cPlatform, cArch, cDefines)
onResult := NewCDelegate(onResultFn, onResultData, onResultDataDestroy)
go func() {
@@ -376,9 +376,9 @@ func _frida_compiler_backend_patterns_describe(cProjectRoot, cEntrypoint, cPlatf
//export _frida_compiler_backend_patterns_decode
func _frida_compiler_backend_patterns_decode(cProjectRoot, cEntrypoint, cTypeName *C.char, cData *C.uint8_t, dataLength C.int,
address C.uint64_t, cPlatform, cArch, cInputs *C.char, onResultFn C.FridaPatternResultFunc, onResultData unsafe.Pointer,
onResultDataDestroy C.FridaDestroyFunc) {
query := patternQueryFromC(cProjectRoot, cEntrypoint, cPlatform, cArch)
address C.uint64_t, cPlatform, cArch, cDefines, cInputs *C.char, onResultFn C.FridaPatternResultFunc,
onResultData unsafe.Pointer, onResultDataDestroy C.FridaDestroyFunc) {
query := patternQueryFromC(cProjectRoot, cEntrypoint, cPlatform, cArch, cDefines)
typeName := C.GoString(cTypeName)
data := C.GoBytes(unsafe.Pointer(cData), dataLength)
inputs := parsePatternInputs(cInputs)
@@ -394,9 +394,9 @@ func _frida_compiler_backend_patterns_decode(cProjectRoot, cEntrypoint, cTypeNam
//export _frida_compiler_backend_patterns_call
func _frida_compiler_backend_patterns_call(cProjectRoot, cEntrypoint, cTypeName *C.char, cData *C.uint8_t, dataLength C.int,
address C.uint64_t, cPlatform, cArch, cInputs *C.char, patternID C.uint, cFunctionName *C.char,
address C.uint64_t, cPlatform, cArch, cDefines, cInputs *C.char, patternID C.uint, cFunctionName *C.char,
onResultFn C.FridaPatternResultFunc, onResultData unsafe.Pointer, onResultDataDestroy C.FridaDestroyFunc) {
query := patternQueryFromC(cProjectRoot, cEntrypoint, cPlatform, cArch)
query := patternQueryFromC(cProjectRoot, cEntrypoint, cPlatform, cArch, cDefines)
typeName := C.GoString(cTypeName)
data := C.GoBytes(unsafe.Pointer(cData), dataLength)
inputs := parsePatternInputs(cInputs)
@@ -411,24 +411,29 @@ func _frida_compiler_backend_patterns_call(cProjectRoot, cEntrypoint, cTypeName
}()
}
func patternQueryFromC(cProjectRoot, cEntrypoint, cPlatform, cArch *C.char) patternQuery {
func patternQueryFromC(cProjectRoot, cEntrypoint, cPlatform, cArch, cDefines *C.char) patternQuery {
return patternQuery{
projectRoot: C.GoString(cProjectRoot),
entrypoint: C.GoString(cEntrypoint),
platform: C.GoString(cPlatform),
arch: C.GoString(cArch),
defines: parsePatternDict(cDefines),
}
}
func parsePatternInputs(cInputs *C.char) map[string]any {
if cInputs == nil {
return parsePatternDict(cInputs)
}
func parsePatternDict(cJSON *C.char) map[string]any {
if cJSON == nil {
return nil
}
var inputs map[string]any
if err := json.Unmarshal([]byte(C.GoString(cInputs)), &inputs); err != nil {
var dict map[string]any
if err := json.Unmarshal([]byte(C.GoString(cJSON)), &dict); err != nil {
return nil
}
return inputs
return dict
}
func invokePatternResultFunc(onResult *CDelegate[C.FridaPatternResultFunc], result string, err error) {
+3 -1
View File
@@ -28,6 +28,7 @@ type BackendRequest struct {
SourceMap bool `json:"source_map,omitempty"`
Compress bool `json:"compress,omitempty"`
Platform string `json:"platform,omitempty"`
Defines map[string]any `json:"defines,omitempty"`
Inputs map[string]any `json:"inputs,omitempty"`
Externals []string `json:"externals,omitempty"`
Text string `json:"text,omitempty"`
@@ -355,7 +356,8 @@ func patternRequestData(req BackendRequest) ([]byte, uint64, error) {
}
func patternQueryFromRequest(req BackendRequest) patternQuery {
return patternQuery{projectRoot: req.ProjectRoot, entrypoint: req.Entrypoint, platform: req.Platform, arch: req.Arch}
return patternQuery{projectRoot: req.ProjectRoot, entrypoint: req.Entrypoint, platform: req.Platform, arch: req.Arch,
defines: req.Defines}
}
func libraryOptionsFromRequest(req BackendRequest) LibraryOptions {
+1 -7
View File
@@ -27,13 +27,7 @@ export function describe(hitpoints: number): string {
}
`)
writeFile(t, filepath.Join(dir, "lib", "patterns", "player.pat"), "import geometry;\n"+playerPattern)
writeFile(t, filepath.Join(dir, "lib", "patterns", "geometry.pat"), `
struct Vec3 {
float x;
float y;
float z;
};
`)
writeFile(t, filepath.Join(dir, "lib", "patterns", "geometry.pat"), vec3Pattern)
var diagnostics []Diagnostic
err := buildLibrary(LibraryOptions{ProjectRoot: dir, Entrypoint: "lib/index.ts", OutputDir: "dist", SourceMap: true}, func(d Diagnostic) {
+4 -4
View File
@@ -35,7 +35,7 @@
"@frida/vm": "^2.0.0",
"@types/frida-gum": "^19.12.0",
"@types/node": "18.19.103",
"frida-fs": "7.0.0"
"frida-fs": "7.1.1"
}
},
"node_modules/@frida/assert": {
@@ -283,9 +283,9 @@
"license": "MIT"
},
"node_modules/frida-fs": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/frida-fs/-/frida-fs-7.0.0.tgz",
"integrity": "sha512-95m7LCjuFMZObrR/37tlEUHAKC0UP82vWBtcaFntUSFr5MuqhoZG6FDCXKl58FhTf0SwOCYvwctCYPLV3/pHgw==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/frida-fs/-/frida-fs-7.1.1.tgz",
"integrity": "sha512-2aiPVKD3+a1pgWCbAuSyDx9QsmK6JMX5aw5iukkE9lGGYeYkj1FSajaMGlRk/s9MynXm+jTVmuY6t0a2oBptOg==",
"license": "MIT"
},
"node_modules/http-parser-js": {
+1 -1
View File
@@ -31,6 +31,6 @@
"@frida/vm": "^2.0.0",
"@types/frida-gum": "^19.12.0",
"@types/node": "18.19.103",
"frida-fs": "7.0.0"
"frida-fs": "7.1.1"
}
}
+14
View File
@@ -21,6 +21,14 @@ type routedMessage struct {
Params json.RawMessage `json:"params"`
}
type configurationParams struct {
Settings struct {
Patterns struct {
Defines map[string]string `json:"defines"`
} `json:"patterns"`
} `json:"settings"`
}
type documentParams struct {
TextDocument struct {
URI string `json:"uri"`
@@ -47,6 +55,12 @@ func (r *patternRouter) route(text string) bool {
r.initializeID = message.ID
return false
}
if message.Method == "workspace/didChangeConfiguration" {
var params configurationParams
json.Unmarshal(message.Params, &params)
r.service.Define(params.Settings.Patterns.Defines)
return false
}
var params documentParams
if !strings.HasPrefix(message.Method, "textDocument/") || json.Unmarshal(message.Params, &params) != nil {
return false
+13
View File
@@ -64,3 +64,16 @@ func TestPatternRouterAdjustsInitializeResponse(t *testing.T) {
t.Fatalf("unexpected tokens: %s", reply)
}
}
func TestPatternRouterTakesDefinesFromConfiguration(t *testing.T) {
var emitted []string
router := newPatternRouter(func(text string) { emitted = append(emitted, text) })
if router.route(`{"jsonrpc":"2.0","method":"workspace/didChangeConfiguration","params":{"settings":{"patterns":{"defines":{"LIVE":""}}}}}`) {
t.Fatal("configuration changes belong to both servers")
}
router.route(`{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///p.hexpat","text":"#ifdef LIVE\nstruct A { u8 x; };\n#else\nstruct A { u8 x[missing]; };\n#endif\n"}}}`)
router.route(`{"jsonrpc":"2.0","id":4,"method":"textDocument/diagnostic","params":{"textDocument":{"uri":"file:///p.hexpat"}}}`)
if len(emitted) != 1 || !strings.Contains(emitted[0], `"items":[]`) {
t.Fatalf("the defined branch should be compiled: %v", emitted)
}
}
+14 -1
View File
@@ -13,6 +13,7 @@ type patternQuery struct {
entrypoint string
platform string
arch string
defines map[string]any
}
type patternCompilation struct {
@@ -63,13 +64,25 @@ func (q patternQuery) compile() (*patternCompilation, error) {
return nil, fmt.Errorf("Failed to resolve entrypoint: %w", err)
}
module, diagnostics, err := patterns.CompileFile(entrypoint, nil)
module, diagnostics, err := patterns.CompileFile(entrypoint, nil, defineBodies(q.defines))
if err != nil {
return nil, err
}
return &patternCompilation{module: module, diagnostics: diagnostics, target: targetFor(q.platform, q.arch), projectRoot: projectRoot}, nil
}
func defineBodies(defines map[string]any) map[string]string {
bodies := make(map[string]string, len(defines))
for name, value := range defines {
if text, isText := value.(string); isText {
bodies[name] = text
} else {
bodies[name] = fmt.Sprint(value)
}
}
return bodies
}
func targetFor(platform string, arch string) patterns.Target {
if platform == "" {
platform = hostPlatform()
+20
View File
@@ -0,0 +1,20 @@
package main
import (
"path/filepath"
"strings"
"testing"
)
func TestDecodePatternWithDefines(t *testing.T) {
dir := t.TempDir()
writeFile(t, filepath.Join(dir, "layout.hexpat"), "#ifdef WIDE\nstruct Layout { u16 value; };\n#else\nstruct Layout { u8 value; };\n#endif\n")
query := patternQuery{projectRoot: dir, entrypoint: "layout.hexpat", defines: map[string]any{"WIDE": ""}}
result, err := decodePattern(query, "Layout", []byte{1, 2}, 0, nil)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(result, `"size":2`) {
t.Fatalf("the define should select the wide branch: %s", result)
}
}
+14 -2
View File
@@ -38,9 +38,21 @@ func init() {
defineHashBuiltins()
}
var patternArguments = map[string]bool{
"std::core::formatted_value": true, "std::core::set_display_name": true, "std::core::member_count": true, "std::core::has_member": true,
}
func (f *frame) builtin(call *Builtin) (runtimeValue, error) {
arguments := make([]runtimeValue, len(call.Arguments))
for i, argument := range call.Arguments {
if patternArguments[call.Name] && isReference(argument) {
node, err := f.target(argument)
if err != nil {
return nil, err
}
arguments[i] = node
continue
}
result, err := f.eval(argument)
if err != nil {
return nil, err
@@ -301,7 +313,7 @@ func (f *frame) findBytes(occurrenceValue runtimeValue, fromValue runtimeValue,
func defineCoreBuiltins() {
defineBuiltin("std::core::array_index", 0, "$std_core_array_index", func(f *frame, args []runtimeValue) (runtimeValue, error) {
return int64(f.cursorOwner().arrayIndex), nil
return int64(f.arrayIndexInScope()), nil
})
defineBuiltin("std::core::member_count", 1, "$std_core_member_count", func(f *frame, args []runtimeValue) (runtimeValue, error) {
if elements, isArray := args[0].([]runtimeValue); isArray {
@@ -343,7 +355,7 @@ func defineCoreBuiltins() {
if node.Formatted != "" {
return node.Formatted, nil
}
return display(node.raw), nil
return display(node.runtimeValue()), nil
}
return display(args[0]), nil
})
+51
View File
@@ -2,6 +2,8 @@ package patterns
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
)
@@ -626,3 +628,52 @@ func TestImportedDeclarationsAreShared(t *testing.T) {
}
}
}
func TestCompileReportsEndlessTemplateRecursion(t *testing.T) {
_, diagnostics := Compile(`
struct Level<auto Depth> {
u8 value;
Level<Depth + 1> next;
};
Level<0> root @ 0;
`)
if len(diagnostics) == 0 || !strings.Contains(diagnostics[0].Message, "recursively") {
t.Fatalf("unexpected diagnostics: %v", diagnostics)
}
}
func TestCompileFileWithDefines(t *testing.T) {
path := filepath.Join(t.TempDir(), "layout.hexpat")
if err := os.WriteFile(path, []byte(`
#ifdef WIDE
struct Layout { u16 value; };
#else
struct Layout { u8 value; };
#endif
struct Scaled { u8 bytes[SCALE * 2]; };
`), 0644); err != nil {
t.Fatal(err)
}
narrow, diagnostics, err := CompileFile(path, nil, map[string]string{"SCALE": "3"})
if err != nil || len(diagnostics) > 0 {
t.Fatalf("compile failed: %v %v", err, diagnostics)
}
wide, diagnostics, err := CompileFile(path, nil, map[string]string{"WIDE": "", "SCALE": "4"})
if err != nil || len(diagnostics) > 0 {
t.Fatalf("compile failed: %v %v", err, diagnostics)
}
sizeOf := func(module *Module, name string) int {
return module.Layout(Targets[0]).Of(findType(module, name)).Size
}
if sizeOf(narrow, "Layout") != 1 || sizeOf(wide, "Layout") != 2 {
t.Fatalf("the define should select the branch: %d %d", sizeOf(narrow, "Layout"), sizeOf(wide, "Layout"))
}
if sizeOf(narrow, "Scaled") != 6 || sizeOf(wide, "Scaled") != 8 {
t.Fatalf("the define's body should expand: %d %d", sizeOf(narrow, "Scaled"), sizeOf(wide, "Scaled"))
}
again, _, _ := CompileFile(path, nil, map[string]string{"SCALE": "3"})
if again != narrow {
t.Fatal("the cache should keep one compilation per set of defines")
}
}
+24 -1
View File
@@ -363,6 +363,7 @@ type frame struct {
caller *frame
depth int
arrayIndex int
inArray bool
bits bool
bitfield *Bitfield
bitCursor int
@@ -399,6 +400,9 @@ func (f *frame) node(path []*Field) (*DecodedValue, error) {
for i, field := range path {
node, isDecoded := scope.nodes[field]
if !isDecoded {
node = scope.nodeNamed(field.Name)
}
if node == nil {
return nil, fmt.Errorf("%s is not available", field.Name)
}
if node.pointee != nil {
@@ -437,6 +441,21 @@ func (f *frame) parentNode(depth int, path []string) (*DecodedValue, error) {
return node, nil
}
func (f *frame) iterate() func() {
saved := f.inArray
f.inArray = true
return func() { f.inArray = saved }
}
func (f *frame) arrayIndexInScope() int {
for scope := f.cursorOwner(); scope != nil; scope = scope.parent {
if scope.inArray {
return scope.arrayIndex
}
}
return 0
}
func (f *frame) nodeNamed(name string) *DecodedValue {
for field, node := range f.nodes {
if field.Name == name {
@@ -840,6 +859,7 @@ func (d *decoder) decodeBitfield(value *DecodedValue, t *Bitfield, offset int, p
field.Value, field.ValueKind, field.raw = integerJSON(bits, false), "uint", bits
if member.Enum != nil {
field.Label = enumLabel(member.Enum, int64(bits))
field.typ = member.Enum
}
}
value.Fields = append(value.Fields, field)
@@ -925,6 +945,7 @@ func (f *frame) placeBits(member *BitfieldMember) error {
node.Value, node.ValueKind, node.raw = integerJSON(bits, false), "uint", bits
if member.Enum != nil {
node.Label = enumLabel(member.Enum, int64(bits))
node.typ = member.Enum
}
}
f.value.Fields = append(f.value.Fields, node)
@@ -1514,6 +1535,7 @@ func (d *decoder) decodeArray(value *DecodedValue, t *Array, offset int, scope *
value.Count = &count
value.Elements = []*DecodedValue{}
end := offset
defer scope.iterate()()
for i := 0; i != count; i++ {
scope.arrayIndex = i
elementValue := d.decode("", t.Element, end, scope)
@@ -1539,11 +1561,13 @@ func (d *decoder) decodeWhileArray(value *DecodedValue, t *Array, offset int, sc
value.Elements = []*DecodedValue{}
end := offset
count := 0
defer scope.iterate()()
for {
if err := d.step(); err != nil {
value.fail(err)
return
}
scope.arrayIndex = count
owner := scope.cursorOwner()
saved := owner.cursor
owner.cursor = end
@@ -1556,7 +1580,6 @@ func (d *decoder) decodeWhileArray(value *DecodedValue, t *Array, offset int, sc
if !proceed {
break
}
scope.arrayIndex = count
elementValue := d.decode("", t.Element, end, scope)
if elementValue.ending != flowContinue {
if count < maxDecodedElements {
+187
View File
@@ -324,3 +324,190 @@ struct Header {
t.Errorf("the enum should read through VarInt and take its size: %s", decoded)
}
}
func TestDecodeTemplateFieldArgumentAcrossInstantiations(t *testing.T) {
const source = `
struct Payload<auto Size> {
u8 data[Size];
};
struct Record<Addr> {
u8 size;
u8 tag = std::mem::read_unsigned($, 1);
Payload<size> payload;
};
struct File {
u8 wide;
if (wide == 1) {
Record<u32> record;
} else {
Record<u64> record;
}
};
`
for _, wide := range []byte{1, 2} {
decoded, err := DecodeSource(source, "File", []byte{wide, 2, 0xaa, 0xbb}, 0, Targets[0], nil)
if err != nil {
t.Fatal(err)
}
if strings.Contains(decoded, "is not available") || !strings.Contains(decoded, `"count":2`) {
t.Errorf("wide=%d: the template argument should bind in every instantiation: %s", wide, decoded)
}
}
}
func TestDecodeEnumsKeepTheirNames(t *testing.T) {
const source = `
enum Kind : u8 {
Circle = 1,
Square = 2,
};
bitfield Packed {
Kind kind : 4;
padding : 4;
};
fn describe(ref auto shape) {
return std::format("{} {}", shape.inner.kind, std::core::formatted_value(shape.packed.kind));
};
struct Inner {
Kind kind;
};
struct Shape {
Inner inner;
Packed packed;
str nested = std::format("{}", inner.kind) [[export]];
str bits = std::format("{}", packed.kind) [[export]];
str formatted = std::core::formatted_value(packed.kind) [[export]];
} [[format("describe")]];
struct Shapes {
Shape shapes[1];
};
`
decoded, err := DecodeSource(source, "Shapes", []byte{2, 1}, 0, Targets[0], nil)
if err != nil {
t.Fatal(err)
}
var value DecodedValue
if err := json.Unmarshal([]byte(decoded), &value); err != nil {
t.Fatal(err)
}
shape := value.Fields[0].Elements[0]
if shape.Formatted != "Kind::Square Kind::Circle" {
t.Errorf("a ref parameter should see enum names: %q", shape.Formatted)
}
if shape.Fields[2].Value != "Kind::Square" || shape.Fields[3].Value != "Kind::Circle" || shape.Fields[4].Value != "Kind::Circle" {
t.Errorf("nested and bitfield members should keep their enum names: %s", shape)
}
}
func TestDecodeFormattedValueOfScalar(t *testing.T) {
const source = `
fn seconds(u32 millis) {
return std::format("{}s", millis / 1000);
};
bitfield Prot {
bool read : 1;
bool write : 1;
padding : 6;
} [[format("describeProt")]];
fn describeProt(ref auto prot) {
return prot.read ? "r" : "-";
};
struct Timing {
u32 elapsed [[format("seconds")]];
Prot prot;
str summary = std::format("{} {}", std::core::formatted_value(elapsed), std::core::formatted_value(prot)) [[export]];
};
`
decoded, err := DecodeSource(source, "Timing", []byte{0xd0, 0x07, 0, 0, 1}, 0, Targets[0], nil)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(decoded, `"value":"2s r"`) {
t.Errorf("formatted_value should format scalar and bitfield fields: %s", decoded)
}
}
func TestDecodeArrayIndexInsideElement(t *testing.T) {
const source = `
struct Entry {
u8 value;
u32 index = std::core::array_index() [[export]];
};
struct Table {
Entry entries[3];
};
`
decoded, err := DecodeSource(source, "Table", []byte{10, 20, 30}, 0, Targets[0], nil)
if err != nil {
t.Fatal(err)
}
var value DecodedValue
if err := json.Unmarshal([]byte(decoded), &value); err != nil {
t.Fatal(err)
}
for i, element := range value.Fields[0].Elements {
if element.Fields[1].Value != float64(i) {
t.Errorf("element %d reports index %v", i, element.Fields[1].Value)
}
}
}
func TestDecodeTemplateLocalArgumentAcrossInstantiations(t *testing.T) {
const source = `
struct Leaf<auto Kind> {
u8 value;
str summary = std::format("kind {}", Kind) [[export]];
};
struct First<auto Kind> {
Leaf<Kind> leaf;
};
struct Second<auto Kind> {
Leaf<Kind> leaf;
};
struct Top {
u8 kind;
First<kind> first;
Second<kind + 1> second;
};
`
decoded, err := DecodeSource(source, "Top", []byte{7, 1, 2}, 0, Targets[0], nil)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(decoded, `"value":"kind 7"`) || !strings.Contains(decoded, `"value":"kind 8"`) {
t.Errorf("each instantiation should see its own argument: %s", decoded)
}
}
func TestDecodeRefParametersReceivePatterns(t *testing.T) {
const source = `
bitfield Bits {
bool read : 1;
bool write : 1;
padding : 6;
};
fn describeBits(ref auto bits) {
return (bits.read ? "r" : "-") + (bits.write ? "w" : "-");
};
fn describePair(ref auto pair) {
return std::format("{}/{}", pair.a, pair.b);
};
struct Pair {
u8 a;
u8 b;
};
struct Root {
Bits bits;
Pair pair;
str viaBitfield = describeBits(bits) [[export]];
str viaStruct = describePair(pair) [[export]];
};
`
decoded, err := DecodeSource(source, "Root", []byte{3, 5, 6}, 0, Targets[0], nil)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(decoded, `"value":"rw"`) || !strings.Contains(decoded, `"value":"5/6"`) {
t.Errorf("ref parameters should receive the pattern: %s", decoded)
}
}
+28 -6
View File
@@ -360,6 +360,21 @@ func (f *frame) element(v *Index) (*DecodedValue, error) {
return nil, fmt.Errorf("value cannot be indexed")
}
func (v *DecodedValue) runtimeValue() runtimeValue {
if v.typ == nil {
return v.raw
}
enum, isEnum := Unalias(v.typ).(*Enum)
if !isEnum {
return v.raw
}
number, err := toInt(v.raw)
if err != nil {
return v.raw
}
return enumValue{enum: enum, value: number}
}
func (f *frame) memberOf(v *MemberOf) (*DecodedValue, error) {
owner, err := f.memberOwner(v)
if err != nil {
@@ -442,6 +457,13 @@ func (f *frame) lookupLocal(local *Local) (runtimeValue, error) {
return value, nil
}
}
for scope := f; scope != nil; scope = scope.parent {
for bound, value := range scope.locals {
if bound.Name == local.Name {
return value, nil
}
}
}
return nil, fmt.Errorf("%s is not available", local.Name)
}
@@ -582,13 +604,13 @@ func (f *frame) eval(v Value) (runtimeValue, error) {
if err != nil {
return nil, err
}
return node.raw, nil
return node.runtimeValue(), nil
case *ParentFieldRef:
node, err := f.parentNode(v.Depth, v.Path)
if err != nil {
return nil, err
}
return node.raw, nil
return node.runtimeValue(), nil
case *LocalRef:
return f.lookupLocal(v.Local)
case *BitRef:
@@ -596,7 +618,7 @@ func (f *frame) eval(v Value) (runtimeValue, error) {
if !isDecoded {
return nil, fmt.Errorf("%s is not available", v.Member.Name)
}
return node.raw, nil
return node.runtimeValue(), nil
case *GlobalRef:
return f.global(v)
case *ArrayValue:
@@ -614,13 +636,13 @@ func (f *frame) eval(v Value) (runtimeValue, error) {
if err != nil {
return nil, err
}
return element.raw, nil
return element.runtimeValue(), nil
case *MemberOf:
member, err := f.memberOf(v)
if err != nil {
return nil, err
}
return member.raw, nil
return member.runtimeValue(), nil
case *Cursor:
owner := f.cursorOwner()
return int64(owner.cursor - owner.base), nil
@@ -1291,7 +1313,7 @@ func (f *frame) call(c *FunctionCall) (runtimeValue, error) {
return nil, err
}
for i, param := range c.Function.Params {
if i < len(c.Arguments) && isComposite(param.Type) && denotesPattern(c.Arguments[i]) {
if i < len(c.Arguments) && (param.Ref || isComposite(param.Type)) && denotesPattern(c.Arguments[i]) {
if arguments[i], err = f.target(c.Arguments[i]); err != nil {
return nil, err
}
+5 -4
View File
@@ -27,8 +27,9 @@ type fileCompilation struct {
var fileCompilations = newCache[string, *fileCompilation](16)
func CompileFile(path string, overrides map[string]string) (*Module, []Diagnostic, error) {
if cached, isCached := fileCompilations.get(path); isCached && cached.resolver.Fresh(overrides) {
func CompileFile(path string, overrides map[string]string, defines map[string]string) (*Module, []Diagnostic, error) {
key := path + "\x00" + definedMacros(defines).signature()
if cached, isCached := fileCompilations.get(key); isCached && cached.resolver.Fresh(overrides) {
return cached.module, cached.diagnostics, nil
}
resolver := NewFileResolver(overrides)
@@ -36,8 +37,8 @@ func CompileFile(path string, overrides map[string]string) (*Module, []Diagnosti
if err != nil {
return nil, nil, err
}
module, diagnostics := CompileSource(main, resolver)
fileCompilations.put(path, &fileCompilation{module: module, diagnostics: diagnostics, resolver: resolver})
module, diagnostics := compileUnit(main, definedMacros(defines), newUnitRegistry(resolver))
fileCompilations.put(key, &fileCompilation{module: module, diagnostics: diagnostics, resolver: resolver})
return module, diagnostics, nil
}
+10 -2
View File
@@ -13,6 +13,7 @@ import (
type LanguageService struct {
documents map[string]*serviceDocument
defines map[string]string
}
type serviceDocument struct {
@@ -147,6 +148,13 @@ func (s *LanguageService) Handles(uri string) bool {
return strings.HasSuffix(uri, ".hexpat") || strings.HasSuffix(uri, ".pat")
}
func (s *LanguageService) Define(defines map[string]string) {
s.defines = defines
for uri, d := range s.documents {
s.documents[uri] = s.newDocument(uri, d.text)
}
}
func (s *LanguageService) Open(uri string, text string) {
s.documents[uri] = s.newDocument(uri, text)
}
@@ -163,7 +171,7 @@ func (s *LanguageService) newDocument(uri string, text string) *serviceDocument
path := documentPath(uri)
overlays := s.overlays()
overlays[path] = text
parsed := parseShared(Source{Path: path, Text: text}, nil, NewFileResolver(overlays))
parsed := parseShared(Source{Path: path, Text: text}, definedMacros(s.defines), NewFileResolver(overlays))
return &serviceDocument{service: s, uri: uri, path: path, text: text, lines: lineStarts(text), file: parsed.file,
diags: parsed.diagnostics, toks: parsed.tokens}
}
@@ -202,7 +210,7 @@ func (s *LanguageService) Diagnostics(uri string) []LSPDiagnostic {
}
func (d *serviceDocument) compile() (*Module, []Diagnostic) {
module, diagnostics, _ := CompileFile(d.path, d.service.overlays())
module, diagnostics, _ := CompileFile(d.path, d.service.overlays(), d.service.defines)
return module, diagnostics
}
+12
View File
@@ -243,3 +243,15 @@ func TestServiceCompletionsFollowContext(t *testing.T) {
}
}
}
func TestServiceDefines(t *testing.T) {
service := NewLanguageService()
service.Open("file:///layout.hexpat", "#ifdef LIVE\nstruct Layout { u8 live; };\n#else\nstruct Layout { u8 file[missing]; };\n#endif\n")
if diagnostics := service.Diagnostics("file:///layout.hexpat"); len(diagnostics) != 1 {
t.Fatalf("the undefined branch should be compiled: %+v", diagnostics)
}
service.Define(map[string]string{"LIVE": ""})
if diagnostics := service.Diagnostics("file:///layout.hexpat"); len(diagnostics) != 0 {
t.Fatalf("the defined branch should be compiled: %+v", diagnostics)
}
}
+9
View File
@@ -100,6 +100,15 @@ func preprocess(tokens []token, diagnostics []Diagnostic, incoming macros, inclu
return p.output, append(diagnostics, p.diagnostics...), p.defines
}
func definedMacros(defines map[string]string) macros {
defined := make(macros, len(defines))
for name, body := range defines {
tokens, _ := lex("", body)
defined[name] = tokens[:len(tokens)-1]
}
return defined
}
func (m macros) clone() macros {
cloned := make(macros, len(m))
for name, body := range m {
+8
View File
@@ -293,6 +293,8 @@ func structLocals(t *Struct) []*Local {
return locals
}
const maxInstantiationDepth = 64
func (r *resolver) instantiate(ref TypeRef, scope *fieldScope) Type {
decl, isTemplate := r.lookupTemplate(ref.Name)
if !isTemplate {
@@ -307,6 +309,12 @@ func (r *resolver) instantiate(ref TypeRef, scope *fieldScope) Type {
r.report(ref.Position, fmt.Sprintf("%s takes %d template arguments", ref.Name, len(params)))
return nil
}
if r.instantiations >= maxInstantiationDepth {
r.report(ref.Position, fmt.Sprintf("%s is instantiated recursively without end", ref.Name))
return nil
}
r.instantiations++
defer func() { r.instantiations-- }()
subst := &substitution{types: map[string]Type{}, values: map[string]Value{}, namings: map[string][]NamePart{}, outer: r.subst}
keys := make([]string, len(params))
+5 -1
View File
@@ -34,7 +34,11 @@ func Compile(source string) (*Module, []Diagnostic) {
}
func CompileSource(main Source, resolver Resolver) (*Module, []Diagnostic) {
return compileUnit(main, nil, &unitRegistry{resolver: resolver, byKey: map[string]*sharedUnit{}})
return compileUnit(main, nil, newUnitRegistry(resolver))
}
func newUnitRegistry(resolver Resolver) *unitRegistry {
return &unitRegistry{resolver: resolver, byKey: map[string]*sharedUnit{}}
}
func compileUnit(main Source, incoming macros, registry *unitRegistry) (*Module, []Diagnostic) {
+8 -6
View File
@@ -7,14 +7,16 @@ import (
"testing"
)
const playerPattern = `
#pragma abi native
const vec3Pattern = `
struct Vec3 {
float x;
float y;
float z;
};
`
const playerPattern = `
#pragma abi native
struct Player {
u32 hitpoints;
@@ -26,7 +28,7 @@ struct Player {
func TestBuildAgentWithPatterns(t *testing.T) {
dir := t.TempDir()
writeFile(t, filepath.Join(dir, "player.pat"), playerPattern)
writeFile(t, filepath.Join(dir, "player.pat"), vec3Pattern+playerPattern)
writeFile(t, filepath.Join(dir, "agent.ts"), `
import { Player } from "./player.pat";
@@ -125,8 +127,8 @@ console.log(isRed, position.x);
if err != nil {
t.Fatalf("build failed: %v\n%+v", err, diagnostics)
}
if !strings.Contains(bundle, "geo.Vec2 = class Vec2") {
t.Errorf("bundle lacks the imported type:\n%s", bundle)
if !strings.Contains(bundle, "geometry.Vec2 = class Vec2") || !strings.Contains(bundle, "geo.Vec2 = geometry.Vec2") {
t.Errorf("bundle lacks the imported type under its alias:\n%s", bundle)
}
}
+6 -3
View File
@@ -2416,9 +2416,12 @@ namespace Frida {
var config = (BareboneLinuxKernelConfig) Json.gobject_deserialize (
typeof (BareboneLinuxKernelConfig), node);
config.kernel = (kind == "linux-system-map")
? (LinuxKernelSymbols) LinuxSystemMap.open (file)
: (LinuxKernelSymbols) LinuxKernelImage.open (file);
// FIXME: Work around Vala compiler bug where upcasting the result inside the
// ?: drops its ownership, freeing it before set_kernel () stores it.
if (kind == "linux-system-map")
config.kernel = LinuxSystemMap.open (file);
else
config.kernel = LinuxKernelImage.open (file);
return config;
} catch (Error e) {
return new BareboneInvalidImageConfig (e.message);
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/frida-gum.git
revision = 17.22.2
revision = 17.23.2
depth = 1
[provide]
+11
View File
@@ -190,6 +190,17 @@ struct Image { u8 magic[2]; u8 width; };
var scaled = yield configurable.decode ("Configurable", new Bytes ({ 7 }), 0, decode_options);
assert (scaled.fields.get (0).fields.get (1).value.get_int64 () == 21);
write_pattern (project_dir, "layout.hexpat",
"#ifdef WIDE\nstruct Layout { u16 value; };\n#else\nstruct Layout { u8 value; };\n#endif\n");
var narrow = yield compiler.compile ("layout.hexpat", options);
assert (narrow.lookup ("Layout").size == 1);
var wide_options = make_pattern_options (project_dir);
wide_options.defines["WIDE"] = new Variant.string ("");
var wide = yield compiler.compile ("layout.hexpat", wide_options);
assert (wide.lookup ("Layout").size == 2);
var wide_value = yield wide.decode ("Layout", new Bytes ({ 1, 2 }), 0);
assert (wide_value.fields.get (0).value.get_uint64 () == 0x201);
var data = new uint8[32];
data[0] = 94;
data[4] = 7;