From 23433635aee0c45489e6aaaf61bf07791eed8ef9 Mon Sep 17 00:00:00 2001 From: xeon Date: Mon, 25 Nov 2024 02:45:23 +0300 Subject: [PATCH] Hi! --- .gitignore | 1 + Cargo.lock | 424 ++++++++++++++++++++++++++++++++++ Cargo.toml | 12 + launcher/Cargo.toml | 14 ++ launcher/src/main.rs | 102 ++++++++ rust-toolchain.toml | 2 + yanagi/Cargo.toml | 21 ++ yanagi/sdk_public_key.xml | 1 + yanagi/server_public_key.xml | 8 + yanagi/src/interceptor.rs | 42 ++++ yanagi/src/lib.rs | 56 +++++ yanagi/src/modules/crypto.rs | 101 ++++++++ yanagi/src/modules/mod.rs | 56 +++++ yanagi/src/modules/network.rs | 79 +++++++ yanagi/src/util.rs | 75 ++++++ 15 files changed, 994 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 launcher/Cargo.toml create mode 100644 launcher/src/main.rs create mode 100644 rust-toolchain.toml create mode 100644 yanagi/Cargo.toml create mode 100644 yanagi/sdk_public_key.xml create mode 100644 yanagi/server_public_key.xml create mode 100644 yanagi/src/interceptor.rs create mode 100644 yanagi/src/lib.rs create mode 100644 yanagi/src/modules/crypto.rs create mode 100644 yanagi/src/modules/mod.rs create mode 100644 yanagi/src/modules/network.rs create mode 100644 yanagi/src/util.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..9b0a61f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,424 @@ +# 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.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "hashbrown" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" + +[[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 = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "launcher" +version = "0.0.1" +dependencies = [ + "windows", +] + +[[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.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[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_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[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.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + +[[package]] +name = "yanagi" +version = "0.0.1" +dependencies = [ + "ilhook", + "num_enum", + "thiserror", + "windows", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9afd5ce --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[workspace] +members = ["launcher", "yanagi"] +resolver = "2" + +[workspace.package] +version = "0.0.1" + +[workspace.dependencies] +windows = "0.58.0" +ilhook = "2.1.1" +thiserror = "1.0.64" +num_enum = "0.7.3" diff --git a/launcher/Cargo.toml b/launcher/Cargo.toml new file mode 100644 index 0000000..6dcfaf0 --- /dev/null +++ b/launcher/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "launcher" +edition = "2021" +version.workspace = true + +[dependencies] +windows = { workspace = true, features = [ + "Win32_Foundation", + "Win32_System_Diagnostics_Debug", + "Win32_System_LibraryLoader", + "Win32_System_Memory", + "Win32_System_Threading", + "Win32_Security", +] } diff --git a/launcher/src/main.rs b/launcher/src/main.rs new file mode 100644 index 0000000..044183d --- /dev/null +++ b/launcher/src/main.rs @@ -0,0 +1,102 @@ +use std::ffi::CString; + +use std::ptr::null_mut; +use windows::core::{s, PCSTR, PSTR}; +use windows::Win32::Foundation::{CloseHandle, GetLastError, HANDLE}; +use windows::Win32::System::Diagnostics::Debug::WriteProcessMemory; +use windows::Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress}; +use windows::Win32::System::Memory::{ + VirtualAllocEx, VirtualFreeEx, MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_READWRITE, +}; +use windows::Win32::System::Threading::{ + CreateProcessA, CreateRemoteThread, ResumeThread, WaitForSingleObject, CREATE_SUSPENDED, + PROCESS_INFORMATION, STARTUPINFOA, +}; + +const GAME_EXECUTABLE: PCSTR = s!("ZenlessZoneZeroBeta.exe"); +const INJECT_DLL: &str = "ext.dll"; + +fn inject_standard(h_target: HANDLE, dll_path: &str) -> bool { + unsafe { + let loadlib = GetProcAddress( + GetModuleHandleA(s!("kernel32.dll")).unwrap(), + s!("LoadLibraryA"), + ) + .unwrap(); + + let dll_path_cstr = CString::new(dll_path).unwrap(); + let dll_path_addr = VirtualAllocEx( + h_target, + None, + dll_path_cstr.to_bytes_with_nul().len(), + MEM_COMMIT | MEM_RESERVE, + PAGE_READWRITE, + ); + + if dll_path_addr.is_null() { + println!("VirtualAllocEx failed. Last error: {:?}", GetLastError()); + return false; + } + + WriteProcessMemory( + h_target, + dll_path_addr, + dll_path_cstr.as_ptr() as _, + dll_path_cstr.to_bytes_with_nul().len(), + None, + ) + .unwrap(); + + let h_thread = CreateRemoteThread( + h_target, + None, + 0, + Some(std::mem::transmute(loadlib)), + Some(dll_path_addr), + 0, + None, + ) + .unwrap(); + + WaitForSingleObject(h_thread, 0xFFFFFFFF); + + VirtualFreeEx(h_target, dll_path_addr, 0, MEM_RELEASE).unwrap(); + CloseHandle(h_thread).unwrap(); + true + } +} + +fn main() { + let current_dir = std::env::current_dir().unwrap(); + let dll_path = current_dir.join(INJECT_DLL); + if !dll_path.is_file() { + println!("{INJECT_DLL} not found"); + return; + } + + let mut proc_info = PROCESS_INFORMATION::default(); + let mut startup_info = STARTUPINFOA::default(); + + unsafe { + CreateProcessA( + GAME_EXECUTABLE, + PSTR(null_mut()), + None, + None, + false, + CREATE_SUSPENDED, + None, + None, + &mut startup_info, + &mut proc_info, + ) + .unwrap(); + + if inject_standard(proc_info.hProcess, dll_path.to_str().unwrap()) { + ResumeThread(proc_info.hThread); + } + + CloseHandle(proc_info.hThread).unwrap(); + CloseHandle(proc_info.hProcess).unwrap(); + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/yanagi/Cargo.toml b/yanagi/Cargo.toml new file mode 100644 index 0000000..32e74ae --- /dev/null +++ b/yanagi/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "yanagi" +edition = "2021" +version.workspace = true + +[lib] +name = "yanagi" +crate-type = ["cdylib"] + +[dependencies] +ilhook.workspace = true +thiserror.workspace = true +num_enum.workspace = true +windows = { workspace = true, features = [ + "Win32_Foundation", + "Win32_System_SystemServices", + "Win32_System_LibraryLoader", + "Win32_System_Console", + "Win32_System_Threading", + "Win32_System_Memory", +] } diff --git a/yanagi/sdk_public_key.xml b/yanagi/sdk_public_key.xml new file mode 100644 index 0000000..49b7b3d --- /dev/null +++ b/yanagi/sdk_public_key.xml @@ -0,0 +1 @@ +AQABhEegnKISgDas5VTuRBUlixB+bvmPvXKa3kVO22UEZjPGMUFLmIl3DhH+dsZo7qJn/GfJCUkP1FA0MJ5Bj8PX8IatLJKIJ9dMCNdnAlkXTlMg86QQAhHZN83vP4swj5ILcrGNKl3YAZ49fvzo7nheuTt0/40f0HkHdNa1dUHECBs= diff --git a/yanagi/server_public_key.xml b/yanagi/server_public_key.xml new file mode 100644 index 0000000..8ccbcf1 --- /dev/null +++ b/yanagi/server_public_key.xml @@ -0,0 +1,8 @@ + + +d8zyiJhwWK1Gocou1iynAKbH9f27uAtqFWec+Rbr6G9JFzyAsqH2G7iKqPV60lItLgJ/jvcVyzPeE9eQ1h6Fjw== + + +AQAB + + diff --git a/yanagi/src/interceptor.rs b/yanagi/src/interceptor.rs new file mode 100644 index 0000000..ee16237 --- /dev/null +++ b/yanagi/src/interceptor.rs @@ -0,0 +1,42 @@ +use ilhook::x64::{ + CallbackOption, HookFlags, HookPoint, HookType, Hooker, JmpBackRoutine, RetnRoutine, +}; + +#[derive(Default)] +pub struct Interceptor { + hooks: Vec, +} + +type Result = std::result::Result; +impl Interceptor { + pub unsafe fn attach(&mut self, addr: usize, routine: JmpBackRoutine) -> Result<()> { + let hooker = Hooker::new( + addr, + HookType::JmpBack(routine), + CallbackOption::None, + 0, + HookFlags::empty(), + ); + + let hook_point = hooker.hook()?; + self.hooks.push(hook_point); + + Ok(()) + } + + #[expect(dead_code)] + pub unsafe fn replace(&mut self, addr: usize, routine: RetnRoutine) -> Result<()> { + let hooker = Hooker::new( + addr, + HookType::Retn(routine), + CallbackOption::None, + 0, + HookFlags::empty(), + ); + + let hook_point = hooker.hook()?; + self.hooks.push(hook_point); + + Ok(()) + } +} diff --git a/yanagi/src/lib.rs b/yanagi/src/lib.rs new file mode 100644 index 0000000..38655ed --- /dev/null +++ b/yanagi/src/lib.rs @@ -0,0 +1,56 @@ +#![feature(str_from_utf16_endian)] + +use std::{thread, time::Duration}; + +use interceptor::Interceptor; +use modules::{ + crypto::{ + initialize_rsa_public_key, monitor_network_state, replace_sdk_public_key_string_literal, + }, + network::Network, + NapModuleManager, +}; +use windows::{ + core::s, + Win32::{ + Foundation::HINSTANCE, + System::{Console, LibraryLoader::GetModuleHandleA, SystemServices::DLL_PROCESS_ATTACH}, + }, +}; + +mod interceptor; +mod modules; +mod util; + +unsafe fn thread_fn() { + let _ = Console::AllocConsole(); + + while GetModuleHandleA(s!("GameAssembly.dll")).is_err() { + thread::sleep(Duration::from_millis(200)); + } + + thread::sleep(Duration::from_secs(5)); + util::disable_memory_protection(); + + let mut module_manager = NapModuleManager::default(); + module_manager.add::(); + module_manager.init().expect("failed to initialize modules"); + + initialize_rsa_public_key(); + replace_sdk_public_key_string_literal(); + + let mut interceptor = Interceptor::default(); + monitor_network_state(&mut interceptor); + + thread::sleep(Duration::from_secs(u64::MAX)); +} + +#[no_mangle] +#[allow(non_snake_case)] +unsafe extern "system" fn DllMain(_: HINSTANCE, call_reason: u32, _: *mut ()) -> bool { + if call_reason == DLL_PROCESS_ATTACH { + thread::spawn(|| thread_fn()); + } + + true +} diff --git a/yanagi/src/modules/crypto.rs b/yanagi/src/modules/crypto.rs new file mode 100644 index 0000000..c094910 --- /dev/null +++ b/yanagi/src/modules/crypto.rs @@ -0,0 +1,101 @@ +use std::ffi::CString; + +use ilhook::x64::Registers; + +use crate::{ + interceptor::Interceptor, + util::{import, read_csharp_string, GAME_ASSEMBLY_BASE}, +}; + +import!(rsa_create() -> usize = 0x163AEC10); +import!(rsa_from_xml_string(instance: usize, xml_string: usize) -> usize = 0x163AEE50); +import!(il2cpp_string_new(cstr: *const u8) -> usize = 0x72540); + +pub unsafe fn initialize_rsa_public_key() { + const SERVER_PUBLIC_KEY: &str = include_str!("../../server_public_key.xml"); + let rsa_public_key_backdoor_field = + ((*(GAME_ASSEMBLY_BASE.wrapping_add(0x44949B0) as *const usize)) + 204776) as *mut usize; + + let rsa = rsa_create(); + rsa_from_xml_string( + rsa, + il2cpp_string_new( + CString::new(SERVER_PUBLIC_KEY) + .unwrap() + .to_bytes_with_nul() + .as_ptr(), + ), + ); + + *rsa_public_key_backdoor_field = rsa; +} + +pub unsafe fn replace_sdk_public_key_string_literal() { + const SDK_PUBLIC_KEY: &str = include_str!("../../sdk_public_key.xml"); + + *(GAME_ASSEMBLY_BASE.wrapping_add(0x475FB40) as *mut usize) = il2cpp_string_new( + CString::new(SDK_PUBLIC_KEY) + .unwrap() + .to_bytes_with_nul() + .as_ptr(), + ) as usize; +} + +pub unsafe fn monitor_network_state(interceptor: &mut Interceptor) { + interceptor + .attach( + GAME_ASSEMBLY_BASE.wrapping_add(0xA27D650), + on_network_state_change, + ) + .unwrap(); + + interceptor + .attach( + GAME_ASSEMBLY_BASE.wrapping_add(0x97DA670), + download_data_slave, + ) + .unwrap(); +} + +unsafe extern "win64" fn download_data_slave(reg: *mut Registers, _: usize) { + let data = read_csharp_string((*reg).rcx as usize); + println!("{data}"); +} + +unsafe extern "win64" fn on_network_state_change(reg: *mut Registers, _: usize) { + let net_state = NetworkState::from((*reg).rcx); + println!("network state change: {net_state:?}"); + + if net_state == NetworkState::PlayerLoginCsReq { + // public key rsa gets reset to null after successful PlayerGetTokenScRsp + initialize_rsa_public_key(); + } +} + +#[repr(u64)] +#[derive(num_enum::FromPrimitive, Debug, Default, PartialEq)] +pub enum NetworkState { + CloudCmdLine = 21, + CloudDispatch = 20, + StartBasicsReq = 17, + LoadShaderEnd = 9, + PlayerLoginCsReq = 15, + EndBasicsReq = 18, + LoadResourcesEnd = 10, + GlobalDispatch = 1, + ConnectGameServer = 12, + ChooseServer = 2, + DoFileVerifyEnd = 7, + PlayerLoginScRsp = 16, + DispatchResult = 4, + PlayerGetTokenScRsp = 14, + DownloadResourcesEnd = 6, + AccountLogin = 3, + LoadAssetEnd = 8, + StartEnterGameWorld = 11, + #[default] + None = 0, + EnterWorldScRsp = 19, + PlayerGetTokenCsReq = 13, + StartDownLoad = 5, +} diff --git a/yanagi/src/modules/mod.rs b/yanagi/src/modules/mod.rs new file mode 100644 index 0000000..ac80bc4 --- /dev/null +++ b/yanagi/src/modules/mod.rs @@ -0,0 +1,56 @@ +use std::marker::PhantomData; + +use crate::{interceptor::Interceptor, util}; + +pub mod crypto; +pub mod network; + +#[derive(thiserror::Error, Debug)] +pub enum ModuleInitError { + #[error("{0}")] + HookFail(#[from] ilhook::HookError), +} + +pub struct NapModuleContext { + base: usize, + interceptor: Interceptor, + _module_type: PhantomData, +} + +impl NapModuleContext { + fn new(base: usize) -> Self { + Self { + base, + interceptor: Interceptor::default(), + _module_type: PhantomData, + } + } +} + +pub trait NapModule { + unsafe fn init(&mut self) -> Result<(), ModuleInitError>; +} + +#[derive(Default)] +pub struct NapModuleManager { + modules: Vec>, +} + +impl NapModuleManager { + pub fn add(&mut self) + where + NapModuleContext: NapModule, + { + self.modules.push(Box::new(NapModuleContext::::new( + *util::GAME_ASSEMBLY_BASE, + ))); + } + + pub unsafe fn init(&mut self) -> Result<(), ModuleInitError> { + for module in self.modules.iter_mut() { + module.init()?; + } + + Ok(()) + } +} diff --git a/yanagi/src/modules/network.rs b/yanagi/src/modules/network.rs new file mode 100644 index 0000000..7678740 --- /dev/null +++ b/yanagi/src/modules/network.rs @@ -0,0 +1,79 @@ +use std::ffi::CString; + +use ilhook::x64::Registers; + +use crate::util::{self, import, read_csharp_string}; + +use super::{ModuleInitError, NapModule, NapModuleContext}; + +const MAKE_INITIAL_URL: usize = 0x605A190; + +pub struct Network; + +impl NapModule for NapModuleContext { + unsafe fn init(&mut self) -> Result<(), ModuleInitError> { + self.interceptor.attach( + self.base.wrapping_add(MAKE_INITIAL_URL), + Network::on_make_initial_url, + )?; + + self.interceptor + .attach(self.base.wrapping_add(0x17627E10), on_set_url)?; + + Ok(()) + } +} + +unsafe extern "win64" fn on_set_url(reg: *mut Registers, _: usize) { + let s = read_csharp_string((*reg).rcx as usize); + if s.contains("StandaloneWindows64/cn/") { + let s = s.replace("StandaloneWindows64/cn/", "StandaloneWindows64/oversea/"); + println!("replaced: {s}"); + (*reg).rcx = + il2cpp_string_new(CString::new(s).unwrap().to_bytes_with_nul().as_ptr()) as u64; + } +} + +import!(il2cpp_string_new(cstr: *const u8) -> usize = 0x72540); + +impl Network { + const SDK_URL: &str = "http://127.0.0.1:10001"; + const DISPATCH_URL: &str = "http://127.0.0.1:10002"; + const REDIRECT_SDK: bool = true; + const REDIRECT_DISPATCH: bool = true; + + unsafe extern "win64" fn on_make_initial_url(reg: *mut Registers, _: usize) { + let url = util::read_csharp_string((*reg).rcx as usize); + + let mut new_url = match url.as_str() { + s if (s.contains("mihoyo.com") || s.contains("hoyoverse.com")) + && Self::REDIRECT_SDK => + { + Self::SDK_URL.to_string() + } + s if (s.contains("globaldp-prod-os01.zenlesszonezero.com") + || s.contains("globaldp-prod-cn01.juequling.com")) + && Self::REDIRECT_DISPATCH => + { + Self::DISPATCH_URL.to_string() + } + s => { + println!("Leaving request as-is: {s}"); + return; + } + }; + + url.split('/').skip(3).for_each(|s| { + new_url.push_str("/"); + new_url.push_str(s); + }); + + println!("UnityWebRequest: \"{url}\", replacing with \"{new_url}\""); + (*reg).rcx = il2cpp_string_new( + CString::new(new_url.as_str()) + .unwrap() + .to_bytes_with_nul() + .as_ptr(), + ) as u64; + } +} diff --git a/yanagi/src/util.rs b/yanagi/src/util.rs new file mode 100644 index 0000000..e926143 --- /dev/null +++ b/yanagi/src/util.rs @@ -0,0 +1,75 @@ +use std::{ffi::c_void, sync::LazyLock}; + +use windows::{ + core::s, + Win32::System::{ + LibraryLoader::{GetModuleHandleA, GetProcAddress}, + Memory, + }, +}; + +#[macro_export] +macro_rules! c { + ($cstr:expr) => { + unsafe { + std::ffi::CStr::from_ptr(concat!($cstr, "\0").as_ptr() as *const std::os::raw::c_char) + .to_bytes_with_nul() + .as_ptr() + } + }; +} + +macro_rules! import { + ($name:ident($($arg_name:ident: $arg_type:ty),*) -> $ret_type:ty = $rva:expr) => { + pub unsafe fn $name($($arg_name: $arg_type,)*) -> $ret_type { + static PROC: ::std::sync::LazyLock = ::std::sync::LazyLock::new(|| *crate::util::GAME_ASSEMBLY_BASE + $rva); + + type FuncType = unsafe extern "fastcall" fn($($arg_type,)*) -> $ret_type; + ::std::mem::transmute::(*PROC)($($arg_name,)*) + } + }; +} + +pub(crate) use import; + +pub static GAME_ASSEMBLY_BASE: LazyLock = + LazyLock::new(|| unsafe { GetModuleHandleA(s!("GameAssembly.dll")).unwrap().0 as usize }); + +#[inline] +pub unsafe fn read_csharp_string(s: usize) -> String { + let str_length = *(s.wrapping_add(16) as *const u32); + let str_ptr = s.wrapping_add(20) as *const u8; + + String::from_utf16le_lossy(std::slice::from_raw_parts( + str_ptr, + (str_length * 2) as usize, + )) +} + +pub unsafe fn disable_memory_protection() { + let ntdll = GetModuleHandleA(s!("ntdll.dll")).unwrap(); + let proc_addr = GetProcAddress(ntdll, s!("NtProtectVirtualMemory")).unwrap(); + + let nt_func = if GetProcAddress(ntdll, s!("wine_get_version")).is_some() { + GetProcAddress(ntdll, s!("NtPulseEvent")).unwrap() + } else { + GetProcAddress(ntdll, s!("NtQuerySection")).unwrap() + }; + + let mut prot = Memory::PAGE_EXECUTE_READWRITE; + Memory::VirtualProtect(proc_addr as *const usize as *mut c_void, 1, prot, &mut prot).unwrap(); + + let routine = nt_func as *mut u32; + let routine_val = *(routine as *const usize); + + let lower_bits_mask = !(0xFFu64 << 32); + let lower_bits = routine_val & lower_bits_mask as usize; + + let offset_val = *((routine as usize + 4) as *const u32); + let upper_bits = ((offset_val as usize).wrapping_sub(1) as usize) << 32; + + let result = lower_bits | upper_bits; + + *(proc_addr as *mut usize) = result; + Memory::VirtualProtect(proc_addr as *const usize as *mut c_void, 1, prot, &mut prot).unwrap(); +}