From 7cc86c38b0c9f13b05f8dd9c9623f17ba4aff6ce Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Sun, 20 Apr 2025 11:43:19 -0400 Subject: [PATCH] Add feature to force WinHTTP traffic to the system proxy This is useful for sniffing non-web browser launcher traffic that goes through WinHTTP. Thanks to NotNite for the initial hooking code. Also when no launcher URL is configured, it should fall back to the official URL now. --- Cargo.lock | 186 +++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 5 +- README.md | 9 ++- src/config.rs | 20 +---- src/lib.rs | 84 +++++++++++++++++++-- src/utilities.rs | 15 +++- 6 files changed, 290 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6f108b8..5628d47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,12 +7,34 @@ name = "LauncherTweaks" version = "0.1.0" dependencies = [ "proxy-dll", + "retour", "serde", "skidscan", "toml", "windows", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "ctor" version = "0.1.26" @@ -29,6 +51,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "hashbrown" version = "0.15.2" @@ -51,12 +83,41 @@ version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +[[package]] +name = "libudis86-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139bbf9ddb1bfc90c1ac64dd2923d9c957cd433cee7315c018125d72ab08a6b0" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "mmap-fixed-fixed" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0681853891801e4763dc252e843672faf32bcfee27a0aa3b19733902af450acc" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -110,6 +171,34 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "region" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7" +dependencies = [ + "bitflags", + "libc", + "mach2", + "windows-sys", +] + +[[package]] +name = "retour" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9af44d40e2400b44d491bfaf8eae111b09f23ac4de6e92728e79d93e699c527" +dependencies = [ + "cfg-if", + "generic-array", + "libc", + "libudis86-sys", + "mmap-fixed-fixed", + "once_cell", + "region", + "slice-pool2", +] + [[package]] name = "serde" version = "1.0.219" @@ -139,6 +228,12 @@ dependencies = [ "serde", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "skidscan" version = "2.0.1" @@ -160,6 +255,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "slice-pool2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3d689654af89bdfeba29a914ab6ac0236d382eb3b764f7454dde052f2821f8" + [[package]] name = "syn" version = "1.0.109" @@ -247,12 +348,24 @@ dependencies = [ "winnow 0.7.6", ] +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "winapi" version = "0.3.9" @@ -376,6 +489,79 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "winnow" version = "0.5.40" diff --git a/Cargo.toml b/Cargo.toml index a71d4de..d6264f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,11 +5,12 @@ edition = "2024" [lib] crate-type = ["cdylib"] -name = "version" +name = "winhttp" [dependencies] skidscan = { version = "2.0", default-features = false } proxy-dll = { git = "https://github.com/redstrate/dll-proxy-rs/", default-features = false } # needed to cross-compile from Linux -windows = { version = "0.61", features = ["Win32_Foundation", "Win32_System_SystemServices", "Win32_UI_WindowsAndMessaging"], default-features = false } +windows = { version = "0.61", features = ["Win32_Foundation", "Win32_System_SystemServices", "Win32_UI_WindowsAndMessaging", "Win32_System_LibraryLoader", "Win32_Networking_WinHttp"], default-features = false } serde = { version = "1.0", features = ["derive"], default-features = false } toml = { version = "0.8", features = ["parse"], default-features = false } +retour = { version = "0.3", features = ["static-detour"], default-features = false } diff --git a/README.md b/README.md index bc8c390..06201e6 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ Tweaks for the official FFXIV launcher. ## Features * Allows configuring the launcher URL, enabling you to write custom launcher pages. +* Force the launcher/boot executable to use the system proxy. (The web browser portions of the launcher already use the system proxy.) ## Usage @@ -41,7 +42,9 @@ error occurred in cc-rs: failed to find tool "x86_64-w64-mingw32-gcc": No such f Then you need to install the MinGW toolchain, as one of our dependencies has to compile C code. -## Launcher Functions +## Tips & Tricks + +### Launcher Functions Here are some interesting native functions that are callable from JavaScript. To call these, use `window.external.user`. @@ -49,6 +52,10 @@ Here are some interesting native functions that are callable from JavaScript. To * `requestExit`: Despite the name, exits the launcher immediately. * `requestReboot`: Exits the launcher immediately, doesn't seem to come back though? +### Logging Proxy + +You can use the `winhttp_proxy` config option to sniff the non-web browser traffic from the launcher. You need a HTTP proxy capable of logging of course, I personally use [mitmproxy](https://mitmproxy.org). + ## Credits Initially based off of [NotNite](https://github.com/NotNite)'s [benchtweaks](https://github.com/NotNite/benchtweaks/) and also inspiration for the name! diff --git a/src/config.rs b/src/config.rs index 3737a63..e08c416 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,23 +1,9 @@ use serde::Deserialize; -#[derive(Deserialize)] +#[derive(Deserialize, Default)] pub struct Config { - #[serde(default = "Config::default_launcher_url")] - pub launcher_url: String, -} - -impl Default for Config { - fn default() -> Self { - Self { - launcher_url: Self::default_launcher_url(), - } - } -} - -impl Config { - fn default_launcher_url() -> String { - "about:blank".to_string() - } + pub launcher_url: Option, + pub winhttp_proxy: Option, } pub fn get_config() -> Config { diff --git a/src/lib.rs b/src/lib.rs index c0459f9..b66d771 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,10 +4,13 @@ mod config; use config::get_config; mod utilities; -use utilities::{find_signature, get_utf16_bytes, show_message}; +use utilities::{find_signature, find_symbol, get_utf16_bytes, show_message}; use proxy_dll::proxy; +use retour::static_detour; use skidscan::Signature; +use std::ffi::c_void; +use windows::{Win32::Networking::WinHttp::*, core::*}; /// So we can have a static address that won't change. static mut OVERRIDE_URL: Vec = Vec::new(); @@ -15,10 +18,18 @@ static mut OVERRIDE_URL: Vec = Vec::new(); /// The first string in the launcher URLs array. const ABOUT_BLANK_URL: &str = "about:blank"; -/// Filename of the launcher. +/// Filename of the launcher executable. const LAUNCHER_FILENAME: &str = "ffxivlauncher64.exe"; +/// Filename of the boot executable. +const BOOT_FILENAME: &str = "ffxivboot64.exe"; + fn overwrite_launcher_url() { + let config = get_config(); + let Some(launcher_url) = config.launcher_url else { + return; + }; + unsafe { // We are going to overwrite a portion of memory that contains the two main launcher URLs. // @@ -43,13 +54,61 @@ fn overwrite_launcher_url() { (url_array_address + std::mem::size_of::() as u64) as *mut u64; // Override!!!! - OVERRIDE_URL.extend_from_slice(&get_utf16_bytes(&get_config().launcher_url)); + OVERRIDE_URL.extend_from_slice(&get_utf16_bytes(&launcher_url)); *launcher_url_launcher_page = OVERRIDE_URL.as_ptr() as u64; } } -fn tweak_launcher() { - overwrite_launcher_url(); +static_detour! { + static WinHttpOpen: fn(PCWSTR, WINHTTP_ACCESS_TYPE, PCWSTR, PCWSTR, u32) -> *mut c_void; +} + +fn winhttpopen_detour( + pszagentw: PCWSTR, + _dwaccesstype: WINHTTP_ACCESS_TYPE, + _pszproxyw: PCWSTR, + pszproxybypassw: PCWSTR, + dwflags: u32, +) -> *mut c_void { + let config = get_config(); + let http_proxy = config + .winhttp_proxy + .expect("Failed to get winhttp_proxy config value"); + let http_proxy: Vec = http_proxy + .encode_utf16() + .chain(std::iter::once(0)) + .collect(); + + // See https://learn.microsoft.com/en-us/windows/win32/api/winhttp/nf-winhttp-winhttpopen + WinHttpOpen.call( + pszagentw, + WINHTTP_ACCESS_TYPE_NAMED_PROXY, + PCWSTR(http_proxy.as_ptr() as _), + pszproxybypassw, + dwflags, + ) +} + +fn use_system_proxy() { + let config = get_config(); + if config.winhttp_proxy.is_none() { + return; + } + + unsafe { + let winhttpopen_addr = + find_symbol("WinHttpOpen", "winhttp.dll").expect("Failed to find WinHttpOpen address"); + let winhttpopen_fn = std::mem::transmute::< + *mut u8, + fn(PCWSTR, WINHTTP_ACCESS_TYPE, PCWSTR, PCWSTR, u32) -> *mut c_void, + >(winhttpopen_addr); + WinHttpOpen + .initialize(winhttpopen_fn, winhttpopen_detour) + .expect("Failed to initialize WinHttpOpen hook"); + WinHttpOpen + .enable() + .expect("Failed to hook into WinHttpOpen"); + } } #[proxy] @@ -72,8 +131,17 @@ fn main() { .file_name() .expect("Failed to get executable filename"); - // We only care about the launcher for now - if current_exe == LAUNCHER_FILENAME { - tweak_launcher(); + match current_exe + .to_str() + .expect("Failed to parse executable filename") + { + LAUNCHER_FILENAME => { + use_system_proxy(); + overwrite_launcher_url(); + } + BOOT_FILENAME => { + use_system_proxy(); + } + _ => {} } } diff --git a/src/utilities.rs b/src/utilities.rs index 7b44003..d8ec156 100644 --- a/src/utilities.rs +++ b/src/utilities.rs @@ -1,7 +1,11 @@ use std::ffi::CString; use skidscan::Signature; -use windows::{Win32::UI::WindowsAndMessaging::MessageBoxA, core::*}; +use windows::{ + Win32::System::LibraryLoader::{GetModuleHandleW, GetProcAddress}, + Win32::UI::WindowsAndMessaging::MessageBoxA, + core::*, +}; use crate::LAUNCHER_FILENAME; @@ -9,6 +13,15 @@ pub fn find_signature(sig: Signature) -> Option<*mut u8> { unsafe { sig.scan_module(LAUNCHER_FILENAME).ok() } } +pub fn find_symbol(symbol: &str, module: &str) -> Option<*mut u8> { + let module: Vec = module.encode_utf16().chain(std::iter::once(0)).collect(); + let symbol = CString::new(symbol).expect("Failed to parse symbol!"); + unsafe { + let handle = GetModuleHandleW(PCWSTR(module.as_ptr() as _)).expect("Module not loaded!"); + GetProcAddress(handle, PCSTR(symbol.as_ptr() as _)).map(|func| func as *mut u8) + } +} + pub fn show_message(message: &str) { unsafe { let message = CString::new(message).unwrap();