Compare commits

...

5 commits

Author SHA1 Message Date
458b3d75f5 Block query_security_file
Fixed 'the client is damaged' error popup, which appeared due to remote anticheat config update from hoyo
2024-08-31 11:10:50 +03:00
38e0c1d28f CNCB5.0.50 sound fix and censorship patch 2024-08-30 17:43:14 +03:00
4603f68722 CNCB5.0.50 support 2024-08-30 01:30:44 +03:00
67bc2a2aaa Update for OSRELWin5.0.0 2024-08-27 00:25:10 +03:00
6d5a9f2098 Update patch for 4.8.50 2024-08-26 15:51:42 +03:00
12 changed files with 198 additions and 166 deletions

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[lib] [lib]
name = "version" name = "ext"
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]

View file

@ -1,3 +1,3 @@
# hk4e-patch # hk4e-patch
Genshin Impact encryption patch (4.6.0) Genshin Impact encryption patch (5.0.0)

1
sdk_public_key.xml Normal file
View file

@ -0,0 +1 @@
<RSAKeyValue><Exponent>AQAB</Exponent><Modulus>hEegnKISgDas5VTuRBUlixB+bvmPvXKa3kVO22UEZjPGMUFLmIl3DhH+dsZo7qJn/GfJCUkP1FA0MJ5Bj8PX8IatLJKIJ9dMCNdnAlkXTlMg86QQAhHZN83vP4swj5ILcrGNKl3YAZ49fvzo7nheuTt0/40f0HkHdNa1dUHECBs=</Modulus></RSAKeyValue>

View file

@ -3,57 +3,48 @@
use std::{sync::RwLock, time::Duration}; use std::{sync::RwLock, time::Duration};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use util::try_get_base_address; use modules::{CcpBlocker, Misc};
use windows::Win32::Foundation::HINSTANCE; use windows::core::PCSTR;
use windows::Win32::System::Console; use windows::Win32::System::Console;
use windows::Win32::System::SystemServices::DLL_PROCESS_ATTACH; use windows::Win32::System::SystemServices::DLL_PROCESS_ATTACH;
use windows::Win32::{Foundation::HINSTANCE, System::LibraryLoader::GetModuleHandleA};
mod interceptor; mod interceptor;
mod marshal; mod marshal;
mod modules; mod modules;
mod util; mod util;
mod version;
use version::VersionDllProxy;
use crate::modules::{Http, MhyContext, ModuleManager, Security}; use crate::modules::{Http, MhyContext, ModuleManager, Security};
unsafe fn thread_func() { unsafe fn thread_func() {
let base = loop { let base = GetModuleHandleA(PCSTR::null()).unwrap().0 as usize;
if let Some(base) = try_get_base_address("UserAssembly.dll") { let mut module_manager = MODULE_MANAGER.write().unwrap();
break base;
}
std::thread::sleep(Duration::from_millis(500)); // Block query_security_file ASAP
}; module_manager.enable(MhyContext::<CcpBlocker>::new(base));
std::thread::sleep(Duration::from_secs(7)); std::thread::sleep(Duration::from_secs(14));
util::disable_memprotect_guard(); util::disable_memprotect_guard();
Console::AllocConsole().unwrap(); Console::AllocConsole().unwrap();
println!("Genshin Impact encryption patch\nMade by xeondev\nTo work with NaviaImpact: git.xeondev.com/reversedrooms/NaviaImpact"); println!("Genshin Impact encryption patch\nMade by xeondev\nTo work with XilonenImpact: git.xeondev.com/reversedrooms/XilonenImpact");
println!("UserAssembly: {:X}", base); println!("Base: {:X}", base);
let mut module_manager = MODULE_MANAGER.write().unwrap();
module_manager.enable(MhyContext::<Http>::new(base)); module_manager.enable(MhyContext::<Http>::new(base));
module_manager.enable(MhyContext::<Security>::new(base)); module_manager.enable(MhyContext::<Security>::new(base));
module_manager.enable(MhyContext::<Misc>::new(base));
println!("Successfully initialized!"); println!("Successfully initialized!");
} }
lazy_static! { lazy_static! {
static ref VERSION_DLL_PROXY: version::VersionDllProxy =
VersionDllProxy::new().expect("Failed to load version.dll");
static ref MODULE_MANAGER: RwLock<ModuleManager> = RwLock::new(ModuleManager::default()); static ref MODULE_MANAGER: RwLock<ModuleManager> = RwLock::new(ModuleManager::default());
} }
#[no_mangle] #[no_mangle]
unsafe extern "system" fn DllMain(_: HINSTANCE, call_reason: u32, _: *mut ()) -> bool { unsafe extern "system" fn DllMain(_: HINSTANCE, call_reason: u32, _: *mut ()) -> bool {
if call_reason == DLL_PROCESS_ATTACH { if call_reason == DLL_PROCESS_ATTACH {
VERSION_DLL_PROXY
.load_functions()
.expect("Failed to load functions from version.dll");
std::thread::spawn(|| thread_func()); std::thread::spawn(|| thread_func());
} }

View file

@ -1,8 +1,8 @@
use std::ffi::CStr; use std::ffi::CStr;
use crate::util; use windows::{core::PCSTR, Win32::System::LibraryLoader::GetModuleHandleA};
const PTR_TO_STRING_ANSI: usize = 0xCF7CE50; const PTR_TO_STRING_ANSI: usize = 0xF33F640;
type MarshalPtrToStringAnsi = unsafe extern "fastcall" fn(*const u8) -> *const u8; type MarshalPtrToStringAnsi = unsafe extern "fastcall" fn(*const u8) -> *const u8;
pub unsafe fn ptr_to_string_ansi(content: &CStr) -> *const u8 { pub unsafe fn ptr_to_string_ansi(content: &CStr) -> *const u8 {
@ -11,5 +11,5 @@ pub unsafe fn ptr_to_string_ansi(content: &CStr) -> *const u8 {
} }
unsafe fn base() -> usize { unsafe fn base() -> usize {
util::try_get_base_address("UserAssembly.dll").unwrap() GetModuleHandleA(PCSTR::null()).unwrap().0 as usize
} }

View file

@ -0,0 +1,38 @@
use std::ffi::CStr;
use super::{MhyContext, MhyModule, ModuleType};
use anyhow::Result;
use ilhook::x64::Registers;
use windows::{
core::s,
Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress},
};
pub struct CcpBlocker;
impl MhyModule for MhyContext<CcpBlocker> {
unsafe fn init(&mut self) -> Result<()> {
let winsock2 = GetModuleHandleA(s!("Ws2_32.dll")).unwrap();
let getaddrinfo = GetProcAddress(winsock2, s!("getaddrinfo")).unwrap();
self.interceptor
.attach(getaddrinfo as usize, on_getaddrinfo)
}
unsafe fn de_init(&mut self) -> Result<()> {
Ok(())
}
fn get_module_type(&self) -> super::ModuleType {
ModuleType::CcpBlocker
}
}
unsafe extern "win64" fn on_getaddrinfo(reg: *mut Registers, _: usize) {
let host_ptr = (*reg).rcx as *const i8;
let host = CStr::from_ptr(host_ptr);
if host.to_string_lossy() == "dispatchcnglobal.yuanshen.com" {
std::ptr::copy_nonoverlapping(c"0.0.0.0".as_ptr(), (*reg).rcx as *mut i8, 9);
}
}

View file

@ -5,16 +5,20 @@ use crate::marshal;
use anyhow::Result; use anyhow::Result;
use ilhook::x64::Registers; use ilhook::x64::Registers;
const UNITY_WEB_REQUEST_SET_URL: usize = 0xD9442D0; const WEB_REQUEST_UTILS_MAKE_INITIAL_URL: usize = 0xFFDA8B0;
const BROWSER_LOAD_URL: usize = 0xFE06E40;
pub struct Http; pub struct Http;
impl MhyModule for MhyContext<Http> { impl MhyModule for MhyContext<Http> {
unsafe fn init(&mut self) -> Result<()> { unsafe fn init(&mut self) -> Result<()> {
self.interceptor.attach( self.interceptor.attach(
self.assembly_base + UNITY_WEB_REQUEST_SET_URL, self.assembly_base + WEB_REQUEST_UTILS_MAKE_INITIAL_URL,
on_uwr_set_url, on_make_initial_url,
) )?;
self.interceptor
.attach(self.assembly_base + BROWSER_LOAD_URL, on_browser_load_url)
} }
unsafe fn de_init(&mut self) -> Result<()> { unsafe fn de_init(&mut self) -> Result<()> {
@ -26,7 +30,32 @@ impl MhyModule for MhyContext<Http> {
} }
} }
unsafe extern "win64" fn on_uwr_set_url(reg: *mut Registers, _: usize) { unsafe extern "win64" fn on_make_initial_url(reg: *mut Registers, _: usize) {
let str_length = *((*reg).rcx.wrapping_add(16) as *const u32);
let str_ptr = (*reg).rcx.wrapping_add(20) as *const u8;
let slice = std::slice::from_raw_parts(str_ptr, (str_length * 2) as usize);
let url = String::from_utf16le(slice).unwrap();
let mut new_url = if url.contains("/query_region_list") {
String::from("http://127.0.0.1:21041")
} else {
String::from("http://127.0.0.1:21000")
};
url.split('/').skip(3).for_each(|s| {
new_url.push_str("/");
new_url.push_str(s);
});
if !url.contains("/query_cur_region") {
println!("Redirect: {url} -> {new_url}");
(*reg).rcx =
marshal::ptr_to_string_ansi(CString::new(new_url.as_str()).unwrap().as_c_str()) as u64;
}
}
unsafe extern "win64" fn on_browser_load_url(reg: *mut Registers, _: usize) {
let str_length = *((*reg).rdx.wrapping_add(16) as *const u32); let str_length = *((*reg).rdx.wrapping_add(16) as *const u32);
let str_ptr = (*reg).rdx.wrapping_add(20) as *const u8; let str_ptr = (*reg).rdx.wrapping_add(20) as *const u8;
@ -39,7 +68,8 @@ unsafe extern "win64" fn on_uwr_set_url(reg: *mut Registers, _: usize) {
new_url.push_str(s); new_url.push_str(s);
}); });
println!("Redirect: {url} -> {new_url}"); println!("Browser::LoadURL: {url} -> {new_url}");
(*reg).rdx = (*reg).rdx =
marshal::ptr_to_string_ansi(CString::new(new_url.as_str()).unwrap().as_c_str()) as u64; marshal::ptr_to_string_ansi(CString::new(new_url.as_str()).unwrap().as_c_str()) as u64;
} }

52
src/modules/misc.rs Normal file
View file

@ -0,0 +1,52 @@
use std::ffi::CStr;
use super::{MhyContext, MhyModule, ModuleType};
use anyhow::Result;
use ilhook::x64::Registers;
use windows::{core::PCSTR, Win32::System::LibraryLoader::GetModuleHandleA};
pub struct Misc;
const DYNAMIC_IMPORT: usize = 0x3F5240;
const SET_CUSTOM_PROPERTY_FLOAT: usize = 0x11E1880;
impl MhyModule for MhyContext<Misc> {
unsafe fn init(&mut self) -> Result<()> {
// CNCBWin5.0.50 sound fix
self.interceptor
.attach(self.assembly_base + DYNAMIC_IMPORT, on_dynamic_import)?;
// Dither
self.interceptor.replace(
self.assembly_base + SET_CUSTOM_PROPERTY_FLOAT,
set_custom_property_float_replacement,
)
}
unsafe fn de_init(&mut self) -> Result<()> {
Ok(())
}
fn get_module_type(&self) -> super::ModuleType {
ModuleType::Misc
}
}
unsafe extern "win64" fn on_dynamic_import(reg: *mut Registers, _: usize) {
let symbol_name_ptr = *((*reg).rcx.wrapping_add(16) as *const usize);
let symbol_name = CStr::from_ptr(symbol_name_ptr as *const i8);
// Hoyo forgot to package updated sound library and that's the missing export
if symbol_name.to_string_lossy() == "GetMusicSyncCallbackInfoPlayingSeq" {
let base = GetModuleHandleA(PCSTR::null()).unwrap().0 as usize;
*((*reg).rcx.wrapping_add(16) as *mut usize) = base + 0x2F6CD04;
}
}
unsafe extern "win64" fn set_custom_property_float_replacement(
_: *mut Registers,
_: usize,
_: usize,
) -> usize {
0
}

View file

@ -4,10 +4,14 @@ use anyhow::Result;
use crate::interceptor::Interceptor; use crate::interceptor::Interceptor;
mod ccp_blocker;
mod http; mod http;
mod misc;
mod security; mod security;
pub use ccp_blocker::CcpBlocker;
pub use http::Http; pub use http::Http;
pub use misc::Misc;
pub use security::Security; pub use security::Security;
#[derive(Default)] #[derive(Default)]
@ -38,6 +42,8 @@ impl ModuleManager {
pub enum ModuleType { pub enum ModuleType {
Http, Http,
Security, Security,
Misc,
CcpBlocker,
} }
pub trait MhyModule { pub trait MhyModule {

View file

@ -1,25 +1,35 @@
use std::ffi::CString;
use crate::marshal;
use super::{MhyContext, MhyModule, ModuleType}; use super::{MhyContext, MhyModule, ModuleType};
use anyhow::Result; use anyhow::Result;
use ilhook::x64::Registers; use ilhook::x64::Registers;
const IL2CPP_ARRAY_NEW: usize = 0x553C10; const MHYRSA_PERFORM_CRYPTO_ACTION: usize = 0x9DD5C8;
const KEY_SIGN_CHECK: usize = 0x41C5; const KEY_SIGN_CHECK: usize = 0x9DF4BC;
const SDK_UTIL_RSA_ENCRYPT: usize = 0xF7A73C0;
const KEY_SIZE: u64 = 272; const KEY_SIZE: usize = 268;
const KEY_PREFIX: u64 = 0x0D700010182020A01;
static SERVER_PUBLIC_KEY: &[u8] = include_bytes!("../../server_public_key.bin"); static SERVER_PUBLIC_KEY: &[u8] = include_bytes!("../../server_public_key.bin");
type Il2cppArrayNew = unsafe extern "fastcall" fn(u64, u64) -> *const u8; static SDK_PUBLIC_KEY: &str = include_str!("../../sdk_public_key.xml");
pub struct Security; pub struct Security;
impl MhyModule for MhyContext<Security> { impl MhyModule for MhyContext<Security> {
unsafe fn init(&mut self) -> Result<()> { unsafe fn init(&mut self) -> Result<()> {
self.interceptor.replace( self.interceptor.attach(
self.assembly_base + IL2CPP_ARRAY_NEW, self.assembly_base + MHYRSA_PERFORM_CRYPTO_ACTION,
il2cpp_array_new_replacement, on_mhy_rsa,
)?; )?;
self.interceptor self.interceptor
.attach(self.assembly_base + KEY_SIGN_CHECK, after_key_sign_check) .attach(self.assembly_base + KEY_SIGN_CHECK, after_key_sign_check)?;
self.interceptor.attach(
self.assembly_base + SDK_UTIL_RSA_ENCRYPT,
on_sdk_util_rsa_encrypt,
)
} }
unsafe fn de_init(&mut self) -> Result<()> { unsafe fn de_init(&mut self) -> Result<()> {
@ -31,36 +41,28 @@ impl MhyModule for MhyContext<Security> {
} }
} }
// Sign check of rsa key that we just replaced.
unsafe extern "win64" fn after_key_sign_check(reg: *mut Registers, _: usize) { unsafe extern "win64" fn after_key_sign_check(reg: *mut Registers, _: usize) {
println!("key sign check!");
(*reg).rax = 1 (*reg).rax = 1
} }
static mut KEY_PTR: Option<*mut u8> = None; unsafe extern "win64" fn on_mhy_rsa(reg: *mut Registers, _: usize) {
unsafe extern "win64" fn il2cpp_array_new_replacement( println!("key: {:X}", *((*reg).rdx as *const u64));
reg: *mut Registers, println!("len: {:X}", (*reg).r8);
actual_func: usize,
_: usize, if (*reg).r8 as usize == KEY_SIZE {
) -> usize { println!("[*] key replaced");
let il2cpp_array_new = std::mem::transmute::<usize, Il2cppArrayNew>(actual_func);
let ret_val = il2cpp_array_new((*reg).rcx, (*reg).rdx) as usize;
let rdx = (*reg).rdx;
if rdx == KEY_SIZE {
KEY_PTR = Some(ret_val as *mut u8);
} else {
if let Some(key_ptr) = KEY_PTR {
if *(key_ptr.wrapping_add(32) as *const u64) == KEY_PREFIX {
std::ptr::copy_nonoverlapping( std::ptr::copy_nonoverlapping(
SERVER_PUBLIC_KEY.as_ptr(), SERVER_PUBLIC_KEY.as_ptr(),
key_ptr.wrapping_add(32), (*reg).rdx as *mut u8,
SERVER_PUBLIC_KEY.len(), SERVER_PUBLIC_KEY.len(),
); );
} }
} }
KEY_PTR = None; unsafe extern "win64" fn on_sdk_util_rsa_encrypt(reg: *mut Registers, _: usize) {
} println!("[*] SDK RSA: key replaced");
(*reg).rcx =
ret_val marshal::ptr_to_string_ansi(CString::new(SDK_PUBLIC_KEY).unwrap().as_c_str()) as u64;
} }

View file

@ -2,23 +2,14 @@ use core::iter::once;
use std::ffi::{c_void, OsStr}; use std::ffi::{c_void, OsStr};
use std::os::windows::ffi::OsStrExt; use std::os::windows::ffi::OsStrExt;
use windows::Win32::System::LibraryLoader::{GetProcAddress, GetModuleHandleW}; use windows::Win32::System::LibraryLoader::{GetModuleHandleA, GetModuleHandleW, GetProcAddress};
use windows::Win32::System::Memory::{PAGE_EXECUTE_READWRITE, PAGE_PROTECTION_FLAGS, VirtualProtect}; use windows::Win32::System::Memory::{PAGE_EXECUTE_READWRITE, PAGE_PROTECTION_FLAGS, VirtualProtect};
use windows::core::{PCSTR, PCWSTR}; use windows::core::{s, PCSTR, PCWSTR};
pub fn wide_str(value: &str) -> Vec<u16> { pub fn wide_str(value: &str) -> Vec<u16> {
OsStr::new(value).encode_wide().chain(once(0)).collect() OsStr::new(value).encode_wide().chain(once(0)).collect()
} }
pub unsafe fn try_get_base_address(module_name: &str) -> Option<usize> {
let w_module_name = wide_str(module_name);
match GetModuleHandleW(PCWSTR::from_raw(w_module_name.as_ptr())) {
Ok(module) => Some(module.0 as usize),
Err(_) => None
}
}
// VMProtect hooks NtProtectVirtualMemory to prevent changing protection of executable segments // VMProtect hooks NtProtectVirtualMemory to prevent changing protection of executable segments
// We use this trick to remove hook // We use this trick to remove hook
pub unsafe fn disable_memprotect_guard() { pub unsafe fn disable_memprotect_guard() {
@ -29,7 +20,12 @@ pub unsafe fn disable_memprotect_guard() {
PCSTR::from_raw(c"NtProtectVirtualMemory".to_bytes_with_nul().as_ptr()), PCSTR::from_raw(c"NtProtectVirtualMemory".to_bytes_with_nul().as_ptr()),
) )
.unwrap(); .unwrap();
let nt_query = GetProcAddress(ntdll, PCSTR::from_raw(c"NtQuerySection".to_bytes_with_nul().as_ptr())).unwrap();
let routine = if is_wine() {
GetProcAddress(ntdll, s!("NtPulseEvent")).unwrap()
} else {
GetProcAddress(ntdll, s!("NtQuerySection")).unwrap()
} as *mut u32;
let mut old_prot = PAGE_PROTECTION_FLAGS(0); let mut old_prot = PAGE_PROTECTION_FLAGS(0);
VirtualProtect( VirtualProtect(
@ -40,7 +36,6 @@ pub unsafe fn disable_memprotect_guard() {
) )
.unwrap(); .unwrap();
let routine = nt_query as *mut u32;
let routine_val = *(routine as *const usize); let routine_val = *(routine as *const usize);
let lower_bits_mask = !(0xFFu64 << 32); let lower_bits_mask = !(0xFFu64 << 32);
@ -61,3 +56,8 @@ pub unsafe fn disable_memprotect_guard() {
) )
.unwrap(); .unwrap();
} }
unsafe fn is_wine() -> bool {
let module = GetModuleHandleA(s!("ntdll.dll")).unwrap();
GetProcAddress(module, s!("wine_get_version")).is_some()
}

View file

@ -1,88 +0,0 @@
use libloading::{Error, Library, Symbol};
use std::arch::asm;
use std::env;
use std::ffi::{CStr, CString};
pub struct VersionDllProxy {
library: Library,
}
impl VersionDllProxy {
pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
let system_directory = env::var("windir")? + ("\\System32\\");
let dll_path = system_directory + "version.dll";
let library = unsafe { Library::new(dll_path) }?;
Ok(Self { library })
}
fn get_function<T>(&self, func_name: &CStr) -> Result<Symbol<T>, Error> {
unsafe { self.library.get(func_name.to_bytes_with_nul()) }
}
pub fn load_functions(&self) -> Result<(), Error> {
for (i, &name) in FUNCTION_NAMES.iter().enumerate() {
let fn_ptr = self.get_function::<Symbol<*mut usize>>(name)?;
unsafe { ORIGINAL_FUNCTIONS[i] = **fn_ptr };
println!("Loaded function {}@{:p}", name.to_str().unwrap(), unsafe {
ORIGINAL_FUNCTIONS[i]
});
}
Ok(())
}
}
macro_rules! count_exprs {
() => {0usize};
($head:expr, $($tail:expr,)*) => {1usize + count_exprs!($($tail,)*)};
}
macro_rules! version_dll_proxy {
($($fn_name:ident),*) => {
static FUNCTION_NAMES: &[&CStr] = &[
$(
unsafe { CStr::from_bytes_with_nul_unchecked(concat!(stringify!($fn_name), "\0").as_bytes()) }
),*,
];
#[no_mangle]
static mut ORIGINAL_FUNCTIONS: [*const usize; count_exprs!($($fn_name,)*)] = [0 as *const usize; count_exprs!($($fn_name,)*)];
$(
#[no_mangle]
extern "C" fn $fn_name() {
let function_name = FUNCTION_NAMES
.iter()
.position(|&name| name == CString::new(stringify!($fn_name)).unwrap().as_ref())
.unwrap();
let fn_addr = unsafe { ORIGINAL_FUNCTIONS[function_name] };
unsafe {
asm! {
"call {tmp}",
tmp = in(reg) fn_addr,
clobber_abi("C")
}
}
}
)*
};
}
version_dll_proxy! {
GetFileVersionInfoA,
GetFileVersionInfoByHandle,
GetFileVersionInfoExA,
GetFileVersionInfoExW,
GetFileVersionInfoSizeA,
GetFileVersionInfoSizeExA,
GetFileVersionInfoSizeExW,
GetFileVersionInfoSizeW,
GetFileVersionInfoW,
VerFindFileA,
VerFindFileW,
VerInstallFileA,
VerInstallFileW,
VerLanguageNameA,
VerLanguageNameW,
VerQueryValueA,
VerQueryValueW
}