Initial commit
This commit is contained in:
commit
1184af99d3
7 changed files with 466 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
218
Cargo.lock
generated
Normal file
218
Cargo.lock
generated
Normal 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
17
Cargo.toml
Normal 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
110
src/lib.rs
Normal 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
27
src/opts.rs
Normal 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
82
src/replacer.rs
Normal 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
11
src/utils.rs
Normal 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)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue