Initial commit

This commit is contained in:
xavo95 2025-05-16 01:31:53 +07:00
commit 1184af99d3
Signed by: xavo95
GPG key ID: CBF8ADED6DEBB783
7 changed files with 466 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

218
Cargo.lock generated Normal file
View file

@ -0,0 +1,218 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "bitflags"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
[[package]]
name = "curly-injector"
version = "0.1.0"
dependencies = [
"ilhook",
"interceptor-rs",
"regex",
]
[[package]]
name = "iced-x86"
version = "1.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c447cff8c7f384a7d4f741cfcff32f75f3ad02b406432e8d6c878d56b1edf6b"
dependencies = [
"lazy_static",
]
[[package]]
name = "ilhook"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7900295ca4c0c37e0325ddbf4b2b78cecabdb36d75d02712c31fb7d777e658f6"
dependencies = [
"bitflags",
"iced-x86",
"lazy_static",
"libc",
"regex",
"thiserror",
"windows-sys",
]
[[package]]
name = "interceptor-rs"
version = "0.1.0"
source = "git+https://git.xeondev.com/ReversedRoomsMisc/interceptor-rs.git#418aef083cc6768201f0512fdbdca5c03aa4f787"
dependencies = [
"ilhook",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "syn"
version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"

17
Cargo.toml Normal file
View file

@ -0,0 +1,17 @@
[package]
name = "curly-injector"
version = "0.1.0"
edition = "2024"
[features]
[dependencies]
ilhook = "2.1.1"
interceptor-rs = { git = "https://git.xeondev.com/ReversedRoomsMisc/interceptor-rs.git" }
regex = "1.11.1"
[profile.release]
strip = true # Automatically strip symbols from the binary.
lto = true # Link-time optimization.
opt-level = 3 # Optimization level 3.
codegen-units = 1 # Maximum size reduction optimizations.

110
src/lib.rs Normal file
View file

@ -0,0 +1,110 @@
mod opts;
pub mod replacer;
pub mod utils;
use ilhook::x64::Registers;
use replacer::{AbstractReplacer, Replacer};
use std::ffi::{c_char, CStr, CString};
use std::sync::OnceLock;
type CurlEasySetStr = fn(handle: usize, tag: u64, value: *const c_char);
#[derive(Debug)]
pub struct CurlConfig {
pub handle_rcx_relative_offset: u64,
pub url_handle_relative_offset: usize,
pub http_headers_handle_relative_offset: Option<u64>,
pub curl_easy_setopt: usize,
pub curl_easy_perform: usize,
}
#[derive(Debug)]
pub struct ProxyConfig {
pub proxy_url: &'static str,
pub skip_url_replace: bool,
}
#[derive(Debug)]
pub struct CurlUserData {
pub set_opt_ptr: CurlEasySetStr,
pub curl_config: &'static CurlConfig,
pub replacer: Vec<AbstractReplacer>,
pub proxy: Option<ProxyConfig>,
}
pub fn hook_curl(
interceptor: &mut interceptor_rs::Interceptor,
base_addr: usize,
config: &'static CurlConfig,
replacer: Vec<AbstractReplacer>,
proxy_config: Option<ProxyConfig>,
lock: &OnceLock<CurlUserData>,
) {
let set_opt_ptr = base_addr + config.curl_easy_setopt;
let set_opt_transmute = unsafe { std::mem::transmute::<usize, CurlEasySetStr>(set_opt_ptr) };
lock.set(CurlUserData {
set_opt_ptr: set_opt_transmute,
curl_config: config,
replacer,
proxy: proxy_config,
}).unwrap();
interceptor
.attach(
base_addr + config.curl_easy_perform,
on_curl_easy_perform,
Some(lock as *const _ as usize),
)
.unwrap();
}
unsafe extern "win64" fn on_curl_easy_perform(reg: *mut Registers, user_data: usize) {
let data = unsafe {
(*(user_data as *mut OnceLock<CurlUserData>)).get_mut().unwrap()
};
let curl_handle = if data.curl_config.handle_rcx_relative_offset == 0 {
// If relative offset is 0, it means that rcx already contains the handle.
unsafe { (*reg).rcx as usize }
} else {
// Otherwise it means it comes from another struct, so we need to dereference it.
unsafe { *(((*reg).rcx + data.curl_config.handle_rcx_relative_offset) as *const usize) }
};
let url_ptr =
unsafe { *((curl_handle + data.curl_config.url_handle_relative_offset) as *const usize) };
let url = unsafe { CStr::from_ptr(url_ptr as *const i8) }
.to_str()
.unwrap();
println!("[curl_easy_perform] Original URL: {url}");
if let Some(proxy) = &data.proxy {
println!(
"[curl_easy_perform] Setting proxy for url: {}, skip url replace: {}",
proxy.proxy_url, proxy.skip_url_replace
);
let url = CString::new(proxy.proxy_url).unwrap();
(data.set_opt_ptr)(curl_handle, opts::CURL_OPT_PROXY, url.as_ptr());
if proxy.skip_url_replace {
return;
}
}
for replacer in &mut data.replacer {
if let Ok(result) = replacer.replace(url) {
// URL Replacement
println!("[curl_easy_perform] Replacement URL: {result}");
let url = CString::new(result.as_str()).unwrap();
(data.set_opt_ptr)(curl_handle, opts::CURL_OPT_URL, url.as_ptr());
// if let Some(offset) = data.curl_config.http_headers_handle_relative_offset {
// let headers_ptr = unsafe { *(((*reg).rcx + offset) as *const usize) };
// println!("[curl_easy_perform] Headers URL: {headers_ptr}");
// println!("[curl_easy_perform] Headers URL: {}", (*reg).rcx + offset);
// // let headers = unsafe { std::slice::from_raw_parts(*(headers_ptr as *const usize) as *const u8, 1000) };
// // println!("Headers: {headers:02X?}");
// }
return;
};
}
println!("No valid url match found");
}

27
src/opts.rs Normal file
View file

@ -0,0 +1,27 @@
#![allow(unused)]
const CURL_OPT_TYPE_LONG: u64 = 0;
const CURL_OPT_TYPE_OBJECT_POINT: u64 = 10000;
const CURL_OPT_TYPE_FUNCTION_POINT: u64 = 20000;
const CURL_OPT_TYPE_OFF_T: u64 = 30000;
const CURL_OPT_TYPE_BLOB: u64 = 40000;
const CURL_OPT_TYPE_STRING_POINT: u64 = CURL_OPT_TYPE_OBJECT_POINT;
const CURL_OPT_TYPE_S_LIST_POINT: u64 = CURL_OPT_TYPE_OBJECT_POINT;
const CURL_OPT_TYPE_CB_POINT: u64 = CURL_OPT_TYPE_OBJECT_POINT;
const CURL_OPT_TYPE_VALUES: u64 = CURL_OPT_TYPE_LONG;
#[macro_export]
macro_rules! curl_opt {
($name:ident, $typ:ident, $value:literal) => {
pub(crate) const $name: u64 = $typ + $value;
};
}
curl_opt!(CURL_OPT_URL, CURL_OPT_TYPE_STRING_POINT, 2);
curl_opt!(CURL_OPT_PROXY, CURL_OPT_TYPE_STRING_POINT, 4);
curl_opt!(CURL_OPT_HTTP_HEADER, CURL_OPT_TYPE_S_LIST_POINT, 23);
curl_opt!(CURL_OPT_SSL_VERIFY_PEER, CURL_OPT_TYPE_LONG, 64);
curl_opt!(CURL_OPT_CA_INFO, CURL_OPT_TYPE_STRING_POINT, 65);
curl_opt!(CURL_OPT_SSL_VERIFY_HOST, CURL_OPT_TYPE_LONG, 81);
curl_opt!(CURL_OPT_CA_PATH, CURL_OPT_TYPE_STRING_POINT, 97);

82
src/replacer.rs Normal file
View file

@ -0,0 +1,82 @@
pub trait Replacer {
fn replace(&mut self, original: &str) -> Result<String, String>;
}
#[derive(Debug, Clone)]
pub enum AbstractReplacer {
GenericReplacer(GenericReplacer),
GenericCdnReplacer(GenericCdnReplacer),
}
#[derive(Debug, Clone)]
pub struct GenericReplacer {
pub regex: regex::Regex,
pub replacement: String,
pub force_http: bool,
}
#[derive(Debug, Clone)]
pub struct GenericCdnReplacer {
pub regex: regex::Regex,
pub replacement: Vec<String>,
pub force_http: bool,
pub url_index: u8,
}
impl Replacer for AbstractReplacer {
fn replace(&mut self, original: &str) -> Result<String, String> {
match self {
AbstractReplacer::GenericReplacer(replacer) => replacer.replace(original),
AbstractReplacer::GenericCdnReplacer(replacer) => replacer.replace(original)
}
}
}
impl Replacer for GenericReplacer {
fn replace(&mut self, original: &str) -> Result<String, String> {
// Prepare output array
let mut results: Vec<String> = vec![];
// Perform the capture over input
for (_, [scheme, path]) in self.regex.captures_iter(original).map(|c| c.extract()) {
results.push(format!(
"{}://{}/{}",
if self.force_http { "http" } else { scheme },
self.replacement,
path
));
}
// We are supposed to only parse one entry from text
if 1 == results.len() {
Ok(results.remove(0))
} else if results.is_empty() {
Err("No valid url match found so returning original url".to_string())
} else {
Err(format!("Invalid number of entries parsed, expected 1, obtained {:?}", results.len()))
}
}
}
impl Replacer for GenericCdnReplacer {
fn replace(&mut self, original: &str) -> Result<String, String> {
// Prepare output array
let mut results: Vec<String> = vec![];
// Perform the capture over input
for (_, [scheme, path]) in self.regex.captures_iter(original).map(|c| c.extract()) {
results.push(format!(
"{}://{}/{}",
if self.force_http { "http" } else { scheme },
self.replacement[self.url_index as usize],
path
));
}
// We are supposed to only parse one entry from text
if 1 == results.len() {
self.url_index = (self.url_index + 1) % self.replacement.len() as u8;
Ok(results.remove(0))
} else if results.is_empty() {
Err("No valid url match found".to_string())
} else {
Err(format!("Invalid number of entries parsed, expected 1, obtained {:?}", results.len()))
}
}
}

11
src/utils.rs Normal file
View file

@ -0,0 +1,11 @@
use std::env::VarError;
pub trait EnvBoolMapper {
fn map_env_bool(self, default: bool) -> bool;
}
impl EnvBoolMapper for Result<String, VarError> {
fn map_env_bool(self, default: bool) -> bool {
self.map(|val| matches!(val.as_str(), "y")).unwrap_or(default)
}
}