24 Commits

Author SHA1 Message Date
9099d50ba8 feat: support external game launchers 2024-03-24 17:14:00 +07:00
7aa7047ccd Return verification status for all 3rd 2024-03-02 12:13:06 +02:00
d0b966eac7 Revert verification status for most global 3rd 2024-03-02 09:45:08 +02:00
0903e39be2 Marked 3rd glb/sea/tw/kr/jp verified 2024-02-29 22:49:13 +02:00
46c0597492 Moved memorymapping files into a separate function 2024-02-24 18:32:33 +02:00
1b32246f9a Marked SR v2.0.0 as verified 2024-02-08 18:15:50 +02:00
cb3db372df v3.1.1 2024-02-06 00:05:07 +02:00
2fee742f75 SR 2.0.0: bump game version 2024-02-05 16:28:28 +02:00
b36d217284 SR 2.0.0: update checksums 2024-02-05 16:27:29 +02:00
8520356083 General repo maintenance 2024-02-04 10:18:22 +02:00
9bd1379244 Document that the Steam version of 3rd works 2024-02-04 10:15:51 +02:00
7d7967f477 Marked 3rd cn v7.3.0 as verified 2024-02-02 12:13:03 +02:00
8550abdf9f Marked HI3 glb/sea/tw/kr/jp 7.2.0 as verified 2024-01-18 20:11:10 +02:00
912bc2e55f Fixed documentation 2024-01-08 16:18:51 +00:00
f0da1b8456 Documented experimental patching method 2024-01-08 18:17:13 +02:00
af0c685fb3 v3.1.0 2024-01-08 17:58:56 +02:00
35cd117ffc Refactored dynamically linking ntdll 2024-01-07 20:51:55 +02:00
9f011ff103 Marked buf const in utils_write_protected_memory 2024-01-07 19:02:02 +02:00
ecbab96427 Implemented experimental patching method for SR 2024-01-07 19:01:03 +02:00
2da21065a6 Refactored protected memory writes 2024-01-07 18:34:03 +02:00
f8c4c5ad82 Replaced dummy variable references with NULL in inject.c
Apparently the crashes were an artifact of a different thing, and don't actually happen under normal conditions
2023-12-29 14:14:05 +02:00
6b5e303df6 Updated documentation 2023-12-28 14:35:29 +02:00
84e2c172a3 Marked msg_err functions noreturn 2023-12-27 14:11:02 +02:00
981a78ae53 Marked SR v1.6.0 as verified 2023-12-27 11:33:21 +02:00
17 changed files with 318 additions and 145 deletions

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2023 mkrsym1 <mkrsym1@gmail.com> Copyright (c) 2023-2024 mkrsym1 <mkrsym1@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -1,20 +1,25 @@
### Games and regions # Jadeite Autopatcher
- **3rd**: glb/sea/tw/kr/jp **v7.1.0+**, cn **v7.2.0+** ### Current game support:
- **SR**: os/cn **v1.6.0** (potentially unsafe, but no bans were reported since v1.1.0) - **3rd**: glb/sea/tw/kr/jp **v7.3.0+**, cn **v7.3.0+**
- **SR**: os/cn **v2.0.0**\*
You can expect newer versions to work immediately after release with the same jadeite binary if the version is specified with a + above. You can expect newer versions to work immediately after release with the same jadeite binary if the version is specified with a + above.
### Information **Note:** you can test the experimental version-independent patching method for SR. See [#37](https://codeberg.org/mkrsym1/jadeite/issues/37).
**Note:** the Steam version of 3rd was reported to work, however I neither tested it nor intend to support it, so it might break at any time. Use at your own risk.
## Information
The anticheat the games use is fundamentally incompatible with Wine in multiple ways. This tool launches the game without it (`injector`) and imitates it's behaviour (`game_payload`). The anticheat the games use is fundamentally incompatible with Wine in multiple ways. This tool launches the game without it (`injector`) and imitates it's behaviour (`game_payload`).
**Using third-party software (such as this tool) with the games violates their Terms of Service**. Therefore, **you may receive a ban**. No bans were ever reported with 3rd, however the legacy patch for SR did cause many. **Use at your own risk and only if you understand all the possible consequences**. **Using third-party software (such as this tool) with the games violates their Terms of Service**. Therefore, **you may receive a ban**. No bans were ever reported with 3rd, however the legacy patch for SR did cause many. **Use at your own risk and only if you understand all the possible consequences**.
**This is not a cheating tool**. Using it with Windows is not possible, and Windows support is not planned or intended in any way. However, as it does not perform any on-disk file modifications, you may reuse the same game install for Windows if you have a dual-boot setup. **This is not a cheating tool**. Using it with Windows is not possible, and Windows support is not planned or intended in any way. However, as it does not perform any on-disk file modifications, you may reuse the same game install for Windows if you have a dual-boot setup.
### Usage ## Usage
**Refer to [third-party launchers](#third-party-launchers)** for convenient usage. If you don't want to (or can't) use third-party launchers, continue reading the section below. **Refer to [third-party launchers](#third-party-launchers)** for convenient usage. If you don't want to (or can't) use third-party launchers, continue reading the section below
**Wine 8.0+ is recommended**, as lower versions leak "The Wine project" as the device identifier. Not critical, but taking a precaution never hurt anyone. **DXVK is strongly recommended.** **Wine 8.0+ is recommended**, as lower versions leak "The Wine project" as the device identifier. Not critical, but taking a precaution never hurt anyone. **DXVK is strongly recommended**
**3rd-specific**: **3rd-specific**:
- **Wine 8.21+ or [Wine-GE-Proton](https://github.com/GloriousEggroll/wine-ge-custom) is required to play videos. The game may show a black screen or crash on lower versions.** - **Wine 8.21+ or [Wine-GE-Proton](https://github.com/GloriousEggroll/wine-ge-custom) is required to play videos. The game may show a black screen or crash on lower versions.**
@ -27,37 +32,44 @@ Manual usage instructions:
- Run `./block_analytics.sh` from the archive to block the games from accessing analytics servers (you might have to do a `chmod +x block_analytics.sh` first). This will require superuser privileges - Run `./block_analytics.sh` from the archive to block the games from accessing analytics servers (you might have to do a `chmod +x block_analytics.sh` first). This will require superuser privileges
- Run `wine jadeite.exe 'Z:\wine\path\to\game.exe'` - Run `wine jadeite.exe 'Z:\wine\path\to\game.exe'`
This tool is capable of starting the games from a different process. This may be useful for spoofing the parent process (SR is known to report it). Use `wine jadeite.exe 'Z:\wine\path\to\game.exe' 'Z:\wine\path\to\launcher.exe'`. `explorer.exe` is used as the default. Detailed command line interface reference: `jadeite.exe [game path] <launcher path> <game args...>`, where:
- `game path` - Wine path to the game (required)
- `launcher path` - Wine path to the launcher process (optional, default is `C:\Windows\explorer.exe`). The launcher process will be used to start the game. You can specify the path to the official launcher here. Specifying anything other than `explorer.exe` or the official launcher is not recommended. Specify `--` to skip this argument and use the default
- `game args...` - arguments to pass to the game process (optional)
To pass commandline arguments to the game, append them after the launcher path: `wine jadeite.exe 'Z:\wine\path\to\game.exe' 'Z:\wine\path\to\launcher.exe' -arg1 -arg2 -arg3`. To use the default launcher process, use `--`: `wine jadeite.exe 'Z:\wine\path\to\game.exe' -- -arg1 -arg2 -arg3`. Example command: `jadeite.exe 'Z:\path\to\game.exe' -- -screen-fullscreen 1`
### Configuration ## Configuration
These environment variables can be used to configure the behaviour of the tool. Any value except empty string counts as set. `1` will be used in all examples. These environment variables can be used to configure the behaviour of the tool. Any value except empty string counts as set. `1` will be used in all examples.
**Global**:
- `WAIT_BEFORE_RESUME=1` - show a messagebox and wait for user input before resuming the game process. Useful on my side for debugging - `WAIT_BEFORE_RESUME=1` - show a messagebox and wait for user input before resuming the game process. Useful on my side for debugging
### Internals **SR-specific**:
This tool consists of three parts: the main injector (`injector/src/exe.c`), the launcher payload (`injector/src/dll.c`) and the game payload (`game_payload`). - `BREAK_CRYPTCAT=1` - use the experimental patching method. See [#37](https://codeberg.org/mkrsym1/jadeite/issues/37) for details
I am very bad at explaining, so just take a look at the source code. Maybe I'll write a detailed explanation in the future. ## Internals and building
To compile jadeite, you will need meson, mingw and nasm. You can probably install all three using your repository's package manager. Once all dependencies are installed, run `./build.sh` in this directory. The compiled files will be located in `./out`.
A part of the source code is witheld (`game_payload/src/core.c`). This is a forced measure to make abuse more difficult. However, a precompiled blob is provided in the repo. `build.sh` will use it automatically. A part of the source code is witheld (`game_payload/src/core.c`). This is a forced measure to make abuse more difficult. However, a precompiled blob is provided in the repo. `build.sh` will use it automatically.
### Guildelines All source code except `core.c` is available in this repository. You can take look around.
## Guildelines
1. **Please don't share this project in public.** This might attract unnecessary attention from either the Game Company or the Anticheat Company 1. **Please don't share this project in public.** This might attract unnecessary attention from either the Game Company or the Anticheat Company
2. **Please don't abuse this project for cheating.** We're just trying to play the games through Wine 2. **Please don't abuse this project for cheating.** We're just trying to play the games through Wine
### Troubleshooting ## Troubleshooting
Please do not report any issues with the Game to the official channels. Use the issue tracker of this repository Please do not report any issues with the Game to the official channels. Use the issue tracker of this repository
### Third-party launchers ## Third-party launchers
- Honkers Launcher &mdash; Linux launcher for 3rd ([GitHub](https://github.com/an-anime-team/honkers-launcher) | [Codeberg](https://codeberg.org/an-anime-team/honkers-launcher)) - **Honkers Launcher** &mdash; Linux launcher for 3rd ([GitHub](https://github.com/an-anime-team/honkers-launcher) | [Codeberg](https://codeberg.org/an-anime-team/honkers-launcher))
- The Honkers Railway Launcher &mdash; Linux launcher for SR ([GitHub](https://github.com/an-anime-team/the-honkers-railway-launcher) | [Codeberg](https://codeberg.org/an-anime-team/the-honkers-railway-launcher)) - **The Honkers Railway Launcher** &mdash; Linux launcher for SR ([GitHub](https://github.com/an-anime-team/the-honkers-railway-launcher) | [Codeberg](https://codeberg.org/an-anime-team/the-honkers-railway-launcher))
### Credits ## Credits
- mkrsym1 &mdash; project leader, reverse engineering - **mkrsym1** &mdash; project leader, reverse engineering
- [EternalStudentDesuKa](https://github.com/EternalStudentDesuKa) &mdash; major help with analyzing network activity - **[EternalStudentDesuKa](https://github.com/EternalStudentDesuKa)** &mdash; major help with analyzing network activity
- [An Anime Team](https://github.com/an-anime-team) &mdash; Honkers Launcher and The Honkers Railway Launcher - **[An Anime Team](https://github.com/an-anime-team)** &mdash; Honkers Launcher and The Honkers Railway Launcher
- Some others credited in the source code - Some others (credited in the source code)
License: MIT License: MIT

Binary file not shown.

View File

@ -2,8 +2,8 @@
#include <wchar.h> #include <wchar.h>
void msg_err_a(const char *format, ...); void __attribute__((noreturn)) msg_err_a(const char *format, ...);
void msg_err_w(const wchar_t *format, ...); void __attribute__((noreturn)) msg_err_w(const wchar_t *format, ...);
void msg_warn_a(const char *format, ...); void msg_warn_a(const char *format, ...);
void msg_warn_w(const wchar_t *format, ...); void msg_warn_w(const wchar_t *format, ...);

View File

@ -27,10 +27,32 @@ typedef union _LDR_DLL_NOTIFICATION_DATA {
typedef void (*LdrDllNotification_t)(ULONG reason, const PLDR_DLL_NOTIFICATION_DATA data, void *context); typedef void (*LdrDllNotification_t)(ULONG reason, const PLDR_DLL_NOTIFICATION_DATA data, void *context);
typedef NTSTATUS (*LdrRegisterDllNotification_t)(ULONG flags, LdrDllNotification_t notificationFunction, void *context, void **cookie); #define DYNAMIC_FN_TYPE(ret, name, args) typedef ret (*name##_t)args
typedef NTSTATUS (*LdrUnregisterDllNotification_t)(void *cookie);
extern LdrRegisterDllNotification_t LdrRegisterDllNotification; #ifdef NTDLL_DYNAMIC_LINK_IMPL
extern LdrUnregisterDllNotification_t LdrUnregisterDllNotification; #define DYNAMIC_FN_VAR(name) extern name##_t name; name##_t name
#else
#define DYNAMIC_FN_VAR(name) extern name##_t name
#endif
void ntdll_link(); #define DYNAMIC_FN_DEF(ret, name, args) DYNAMIC_FN_TYPE(ret, name, args); DYNAMIC_FN_VAR(name)
DYNAMIC_FN_DEF(NTSTATUS, LdrRegisterDllNotification, (ULONG flags, LdrDllNotification_t notification, void *context, void **cookie));
DYNAMIC_FN_DEF(NTSTATUS, LdrUnregisterDllNotification, (void *cookie));
#ifdef NTDLL_DYNAMIC_LINK_IMPL
#define DYNAMIC_FN_LINK(module, name) name = (name##_t)GetProcAddress(module, #name)
static void _ntdll_link() {
HMODULE ntdll = GetModuleHandleA("ntdll.dll");
DYNAMIC_FN_LINK(ntdll, LdrRegisterDllNotification);
DYNAMIC_FN_LINK(ntdll, LdrUnregisterDllNotification);
}
#undef DYNAMIC_FN_LINK
#endif
#undef DYNAMIC_FN_TYPE
#undef DYNAMIC_FN_VAR
#undef DYNAMIC_FN_DEF

View File

@ -1,9 +1,19 @@
#pragma once #pragma once
#include <windows.h>
#include <stdint.h> #include <stdint.h>
#define UTILS_COUNT(arr) (sizeof(arr) / sizeof(*arr)) #define UTILS_COUNT(arr) (sizeof(arr) / sizeof(*arr))
struct file_mapping {
HANDLE file;
HANDLE mapping;
unsigned char *data;
};
void utils_map_file(const wchar_t *path, struct file_mapping *map);
void utils_unmap_file(struct file_mapping *map);
int utils_path_exists(const wchar_t *filePath); int utils_path_exists(const wchar_t *filePath);
uint32_t utils_file_crc32c(const wchar_t *filePath); uint32_t utils_file_crc32c(const wchar_t *filePath);
@ -12,3 +22,5 @@ void utils_create_parent_dirs(const wchar_t *path);
void utils_save_to_file(const wchar_t *filePath, const void *buf, size_t length); void utils_save_to_file(const wchar_t *filePath, const void *buf, size_t length);
char utils_env_enabled(const char *env); char utils_env_enabled(const char *env);
void utils_write_protected_memory(void *addr, const void *buf, size_t size);

View File

@ -5,7 +5,6 @@ include_dir = include_directories('include')
# Input files # Input files
sources = [ sources = [
'src/main.c', 'src/main.c',
'src/ntdll.c',
'src/ace.c', 'src/ace.c',
'src/pe.c', 'src/pe.c',
'src/game.c', 'src/game.c',

View File

@ -1,6 +1,7 @@
#include <ntdll.h> #include <ntdll.h>
#include <pe.h> #include <pe.h>
#include <msg.h> #include <msg.h>
#include <utils.h>
#include <ace.h> #include <ace.h>
@ -23,14 +24,7 @@ static void _dll_notification(ULONG reason, const PLDR_DLL_NOTIFICATION_DATA dat
0xB8, 0x01, 0x00, 0x00, 0x00, // mov eax, 1 0xB8, 0x01, 0x00, 0x00, 0x00, // mov eax, 1
0xC3 // ret 0xC3 // ret
}; };
utils_write_protected_memory(entryPoint, ENTRY_POINT_STUB, sizeof(ENTRY_POINT_STUB));
DWORD oldProtect;
VirtualProtect(entryPoint, sizeof(ENTRY_POINT_STUB), PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy(entryPoint, ENTRY_POINT_STUB, sizeof(ENTRY_POINT_STUB));
VirtualProtect(entryPoint, sizeof(ENTRY_POINT_STUB), oldProtect, &oldProtect);
} }
static void _create_driver_file(const char *path) { static void _create_driver_file(const char *path) {

View File

@ -9,7 +9,7 @@ const char *HSR_ASSEMBLY_PATH = "GameAssembly.dll";
const char *HSR_TXS_SECTION_NAME = ".ace"; const char *HSR_TXS_SECTION_NAME = ".ace";
const char *HSR_TVM_SECTION_NAME = ".tvm0"; const char *HSR_TVM_SECTION_NAME = ".tvm0";
#define HSR_VERSION "1.6.0" #define HSR_VERSION "2.0.0"
enum hsr_region { enum hsr_region {
HSR_INVALID, HSR_INVALID,
@ -23,14 +23,14 @@ struct crc_region_pair {
}; };
const struct crc_region_pair HSR_REGIONS[] = { const struct crc_region_pair HSR_REGIONS[] = {
{ 0x5741ce50, HSR_OS }, // os v1.6.0 { 0x5be3a560, HSR_OS }, // os v2.0.0
{ 0xce891f97, HSR_CN } // cn v1.6.0 { 0x974e826f, HSR_CN } // cn v2.0.0
}; };
#define JUMP_SIZE (6 + sizeof(void*)) #define JUMP_SIZE (6 + sizeof(void*))
// Temporarily hardcoded offset // Temporarily hardcoded offset
// v1.6.0, same for os and cn // v2.0.0, same for os and cn
#define WTSUD_PATCH_OFFSET 0x16510 #define WTSUD_PATCH_OFFSET 0x16510
char wtsud_original_bytes[JUMP_SIZE]; char wtsud_original_bytes[JUMP_SIZE];
@ -38,13 +38,8 @@ char *wtsud_patch_addr;
static void _wtsud_stub() { static void _wtsud_stub() {
// Recover original bytes // Recover original bytes
DWORD oldProtect; utils_write_protected_memory(wtsud_patch_addr, wtsud_original_bytes, JUMP_SIZE);
VirtualProtect(wtsud_patch_addr, JUMP_SIZE, PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy(wtsud_patch_addr, wtsud_original_bytes, JUMP_SIZE);
VirtualProtect(wtsud_patch_addr, JUMP_SIZE, oldProtect, &oldProtect);
unload_ctr_dec(); unload_ctr_dec();
} }
@ -59,42 +54,69 @@ static void _unityplayer_callback(HMODULE unityModule) {
wtsud_patch_addr = ((char*)unityModule) + WTSUD_PATCH_OFFSET; wtsud_patch_addr = ((char*)unityModule) + WTSUD_PATCH_OFFSET;
DWORD oldProtect;
VirtualProtect(wtsud_patch_addr, JUMP_SIZE, PAGE_EXECUTE_READWRITE, &oldProtect);
// Save original bytes // Save original bytes
memcpy(wtsud_original_bytes, wtsud_patch_addr, JUMP_SIZE); memcpy(wtsud_original_bytes, wtsud_patch_addr, JUMP_SIZE);
// Write jump // Prepare payload
char payload[JUMP_SIZE];
const char JUMP_INST[] = { 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 }; // jmp [$ + 6] const char JUMP_INST[] = { 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 }; // jmp [$ + 6]
memcpy(wtsud_patch_addr, JUMP_INST, sizeof(JUMP_INST)); memcpy(payload, JUMP_INST, sizeof(JUMP_INST));
// Write destination address
void *destAddr = &_wtsud_stub; void *destAddr = &_wtsud_stub;
memcpy(wtsud_patch_addr + sizeof(JUMP_INST), &destAddr, sizeof(destAddr)); memcpy(payload + sizeof(JUMP_INST), &destAddr, sizeof(destAddr));
VirtualProtect(wtsud_patch_addr, JUMP_SIZE, oldProtect, &oldProtect); // Write payload
utils_write_protected_memory(wtsud_patch_addr, payload, sizeof(payload));
}
static void _break_cryptcat() {
const char STUB[] = {
0xB8, 0x01, 0x00, 0x00, 0x00, // mov eax, 1
0xC3 // ret
};
const char *STUB_FUNCTIONS[] = {
"CryptCATAdminEnumCatalogFromHash",
"CryptCATCatalogInfoFromContext",
"CryptCATAdminReleaseCatalogContext"
};
HMODULE wintrust = LoadLibraryA("wintrust.dll");
for (size_t i = 0; i < UTILS_COUNT(STUB_FUNCTIONS); i++) {
void *fn = GetProcAddress(wintrust, STUB_FUNCTIONS[i]);
utils_write_protected_memory(fn, STUB, sizeof(STUB));
}
} }
void hsr_fill_data(struct game_data *buf) { void hsr_fill_data(struct game_data *buf) {
uint32_t crc = utils_file_crc32c(L"UnityPlayer.dll"); if (!utils_env_enabled("BREAK_CRYPTCAT")) {
uint32_t crc = utils_file_crc32c(L"UnityPlayer.dll");
enum hsr_region id = HSR_INVALID;
for (size_t i = 0; i < UTILS_COUNT(HSR_REGIONS); i++) { enum hsr_region id = HSR_INVALID;
if (HSR_REGIONS[i].crc == crc) { for (size_t i = 0; i < UTILS_COUNT(HSR_REGIONS); i++) {
id = HSR_REGIONS[i].id; if (HSR_REGIONS[i].crc == crc) {
break; id = HSR_REGIONS[i].id;
break;
}
} }
}
if (id == HSR_INVALID) {
if (id == HSR_INVALID) { msg_err_a("Invalid UnityPlayer.dll checksum: 0x%08x. This patch is intended to be used with HSR v" HSR_VERSION, crc);
msg_err_a("Invalid UnityPlayer.dll checksum: 0x%08x. This patch is intended to be used with HSR v" HSR_VERSION, crc); }
// WriteTextureStatisticUserData patch
buf->unityplayer_callback = &_unityplayer_callback;
} else {
msg_warn_a("Using experimental patching method");
_break_cryptcat();
buf->unityplayer_callback = NULL;
} }
buf->base_module_name = HSR_BASE_MODULE_NAME; buf->base_module_name = HSR_BASE_MODULE_NAME;
buf->assembly_path = HSR_ASSEMBLY_PATH; buf->assembly_path = HSR_ASSEMBLY_PATH;
buf->txs_section_name = HSR_TXS_SECTION_NAME; buf->txs_section_name = HSR_TXS_SECTION_NAME;
buf->tvm_section_name = HSR_TVM_SECTION_NAME; buf->tvm_section_name = HSR_TVM_SECTION_NAME;
buf->unityplayer_callback = &_unityplayer_callback;
} }

View File

@ -1,6 +1,5 @@
#include <windows.h> #include <windows.h>
#include <ntdll.h>
#include <ace.h> #include <ace.h>
#include <game.h> #include <game.h>
#include <core.h> #include <core.h>
@ -8,6 +7,9 @@
#include <msg.h> #include <msg.h>
#include <tx.h> #include <tx.h>
#define NTDLL_DYNAMIC_LINK_IMPL
#include <ntdll.h>
#include <main.h> #include <main.h>
HMODULE this_module; HMODULE this_module;
@ -73,10 +75,15 @@ BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved) {
return TRUE; return TRUE;
} }
LPWSTR targetExe = malloc(MAX_PATH);
GetModuleFileNameW(NULL, targetExe, 0);
SetCurrentDirectoryW(targetExe);
free(targetExe);
this_module = instance; this_module = instance;
// Dynamically link functions from ntdll // Dynamically link functions from ntdll
ntdll_link(); _ntdll_link();
// Detect which game the user is trying to run // Detect which game the user is trying to run
struct game_data game; struct game_data game;

View File

@ -1,11 +0,0 @@
#include <ntdll.h>
LdrRegisterDllNotification_t LdrRegisterDllNotification;
LdrUnregisterDllNotification_t LdrUnregisterDllNotification;
void ntdll_link() {
HMODULE ntdll = GetModuleHandleA("ntdll.dll");
LdrRegisterDllNotification = (LdrRegisterDllNotification_t)GetProcAddress(ntdll, "LdrRegisterDllNotification");
LdrUnregisterDllNotification = (LdrUnregisterDllNotification_t)GetProcAddress(ntdll, "LdrUnregisterDllNotification");
}

View File

@ -6,6 +6,7 @@
#include <pe.h> #include <pe.h>
#include <main.h> #include <main.h>
#include <config.h> #include <config.h>
#include <utils.h>
#include <tx.h> #include <tx.h>
@ -15,30 +16,23 @@ void tx_table_file(struct game_data *game, wchar_t *buf) {
GetTempPathW(MAX_PATH, tempDir); GetTempPathW(MAX_PATH, tempDir);
// Memorymap the base module // Memorymap the base module
HANDLE baseFile = CreateFileA(game->base_module_name, FILE_READ_ACCESS, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); wchar_t baseModuleW[MAX_PATH];
if (!baseFile) { MultiByteToWideChar(CP_UTF8, 0, game->base_module_name, strlen(game->base_module_name) + 1, baseModuleW, MAX_PATH);
msg_err_a("Could not open file: %s", game->base_module_name);
}
HANDLE hBaseMap = CreateFileMappingA(baseFile, NULL, PAGE_READONLY, 0, 0, NULL); struct file_mapping map;
char *baseMap = MapViewOfFile(hBaseMap, FILE_MAP_READ, 0, 0, 0); utils_map_file(baseModuleW, &map);
if (!baseMap) {
msg_err_a("Could not create file mapping for %s", game->base_module_name);
}
// Checksum the TXS section // Checksum the TXS section
IMAGE_SECTION_HEADER *txsSection = pe_find_section(baseMap, game->txs_section_name); IMAGE_SECTION_HEADER *txsSection = pe_find_section(map.data, game->txs_section_name);
if (!txsSection) { if (!txsSection) {
msg_err_a("Could not find %s in %s. " ISSUE_SUFFIX, game->txs_section_name, game->base_module_name); msg_err_a("Could not find %s in %s. " ISSUE_SUFFIX, game->txs_section_name, game->base_module_name);
} }
uint32_t txsChecksum = crc32c(0, baseMap + txsSection->PointerToRawData, txsSection->SizeOfRawData); uint32_t txsChecksum = crc32c(0, map.data + txsSection->PointerToRawData, txsSection->SizeOfRawData);
// Format the path // Format the path
wsprintfW(buf, L"%sjadeite\\" JADEITE_VERSION "\\%hs.%x.dat", tempDir, game->base_module_name, txsChecksum); wsprintfW(buf, L"%sjadeite\\" JADEITE_VERSION "\\%hs.%x.dat", tempDir, game->base_module_name, txsChecksum);
// Cleanup // Cleanup
UnmapViewOfFile(baseMap); utils_unmap_file(&map);
CloseHandle(hBaseMap);
CloseHandle(baseFile);
} }

View File

@ -5,31 +5,53 @@
#include <utils.h> #include <utils.h>
void utils_map_file(const wchar_t *path, struct file_mapping *map) {
wchar_t* final_path = malloc(MAX_PATH);
if (wcsstr(path, L"C:\\") == NULL) {
wchar_t* tmp = malloc(MAX_PATH);
GetEnvironmentVariableW(L"GAME_PATH", tmp, MAX_PATH);
swprintf(final_path, MAX_PATH, L"%ls\\%ls", tmp, path);
free(tmp);
} else {
swprintf(final_path, MAX_PATH, L"%ls", path);
}
map->file = CreateFileW(final_path, FILE_READ_ACCESS, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (map->file == INVALID_HANDLE_VALUE) {
msg_err_w(L"Could not open file: %ls", final_path);
}
map->mapping = CreateFileMappingA(map->file, NULL, PAGE_READONLY, 0, 0, NULL);
map->data = MapViewOfFile(map->mapping, FILE_MAP_READ, 0, 0, 0);
if (!map->data) {
msg_err_w(L"Could not map view of file %ls", final_path);
}
free(final_path);
}
void utils_unmap_file(struct file_mapping *map) {
UnmapViewOfFile(map->data);
CloseHandle(map->mapping);
CloseHandle(map->file);
}
int utils_path_exists(const wchar_t *filePath) { int utils_path_exists(const wchar_t *filePath) {
return GetFileAttributesW(filePath) != INVALID_FILE_ATTRIBUTES; return GetFileAttributesW(filePath) != INVALID_FILE_ATTRIBUTES;
} }
uint32_t utils_file_crc32c(const wchar_t *filePath) { uint32_t utils_file_crc32c(const wchar_t *filePath) {
HANDLE file = CreateFileW(filePath, FILE_READ_ACCESS, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); struct file_mapping map;
if (!file) { // LPWSTR cwd = malloc(MAX_PATH);
msg_err_w(L"Could not open file: %ls", filePath); // GetCurrentDirectoryW(MAX_PATH, cwd);
} // msg_info_w(L"File %ls %ls", filePath, cwd);
// free(cwd);
utils_map_file(filePath, &map);
LARGE_INTEGER fileSize; LARGE_INTEGER fileSize;
GetFileSizeEx(file, &fileSize); GetFileSizeEx(map.file, &fileSize);
HANDLE hMap = CreateFileMappingA(file, NULL, PAGE_READONLY, 0, 0, NULL); uint32_t crc = crc32c(0, map.data, fileSize.QuadPart);
char *map = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
if (!map) {
msg_err_w(L"Could not create file mapping for %ls", filePath);
}
uint32_t crc = crc32c(0, map, fileSize.QuadPart);
UnmapViewOfFile(map);
CloseHandle(hMap);
CloseHandle(file);
utils_unmap_file(&map);
return crc; return crc;
} }
@ -51,8 +73,8 @@ void utils_create_parent_dirs(const wchar_t *path) {
void utils_save_to_file(const wchar_t *filePath, const void *buf, size_t length) { void utils_save_to_file(const wchar_t *filePath, const void *buf, size_t length) {
HANDLE file = CreateFileW(filePath, FILE_WRITE_ACCESS, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); HANDLE file = CreateFileW(filePath, FILE_WRITE_ACCESS, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
if (!file) { if (file == INVALID_HANDLE_VALUE) {
msg_err_w(L"Could not open file: %ls", filePath); msg_err_w(L"Could not create file: %ls", filePath);
} }
WriteFile(file, buf, length, NULL, FALSE); WriteFile(file, buf, length, NULL, FALSE);
@ -64,3 +86,12 @@ char utils_env_enabled(const char *env) {
char *envText = getenv(env); char *envText = getenv(env);
return envText && *envText; return envText && *envText;
} }
void utils_write_protected_memory(void *addr, const void *buf, size_t size) {
DWORD oldProtect;
VirtualProtect(addr, size, PAGE_READWRITE, &oldProtect);
memcpy(addr, buf, size);
VirtualProtect(addr, size, oldProtect, &oldProtect);
}

View File

@ -1,5 +1,6 @@
#include <stdio.h> #include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>
#include <inject.h> #include <inject.h>
#include <envs.h> #include <envs.h>
@ -9,6 +10,75 @@ typedef char *(*wgufn_t)(wchar_t* path); // wine_get_unix_file_name
const wchar_t *J_MB_TITLE = L"Jadeite Launcher Payload"; const wchar_t *J_MB_TITLE = L"Jadeite Launcher Payload";
// Copied from https://cocomelonc.github.io/pentest/2021/09/29/findmyprocess.html
// Find process ID by process name
DWORD find_proc_id(const char *procname) {
HANDLE hSnapshot;
PROCESSENTRY32 pe;
DWORD pid = 0;
BOOL hResult;
// snapshot of all processes in the system
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hSnapshot) return 0;
// initializing size: needed for using Process32First
pe.dwSize = sizeof(PROCESSENTRY32);
// info about first process encountered in a system snapshot
hResult = Process32First(hSnapshot, &pe);
// retrieve information about the processes
// and exit if unsuccessful
while (hResult) {
// if we find the process: return process ID
if (strcmp(procname, pe.szExeFile) == 0) {
pid = pe.th32ProcessID;
break;
}
hResult = Process32Next(hSnapshot, &pe);
}
// closes an open handle (CreateToolhelp32Snapshot)
CloseHandle(hSnapshot);
return pid;
}
// Find thread ID by process PID
int find_main_thread(DWORD pid) {
HANDLE hSnapshot;
THREADENTRY32 pe;
DWORD threadId = 0;
BOOL hResult;
// snapshot of all processes in the system
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (INVALID_HANDLE_VALUE == hSnapshot) return 0;
// initializing size: needed for using Process32First
pe.dwSize = sizeof(THREADENTRY32);
// info about first process encountered in a system snapshot
hResult = Thread32First(hSnapshot, &pe);
// retrieve information about the processes
// and exit if unsuccessful
while (hResult) {
// if we find the process: return process ID
if (pid == pe.th32OwnerProcessID) {
threadId = pe.th32ThreadID;
break;
}
hResult = Thread32Next(hSnapshot, &pe);
}
// closes an open handle (CreateToolhelp32Snapshot)
CloseHandle(hSnapshot);
return threadId;
}
BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved) { BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved) {
// Only listen for attach // Only listen for attach
if (reason != DLL_PROCESS_ATTACH) { if (reason != DLL_PROCESS_ATTACH) {
@ -32,6 +102,13 @@ BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved) {
wcscpy(workdir, targetExe); wcscpy(workdir, targetExe);
*(wcsrchr(workdir, L'\\')) = L'\0'; *(wcsrchr(workdir, L'\\')) = L'\0';
// Change the game's working directory
LPWSTR game_path = malloc(MAX_PATH);
GetEnvironmentVariableW(L"GAME_PATH", game_path, MAX_PATH);
wchar_t message[64];
wsprintfW(message, L"Game Path: %ls", game_path);
MessageBoxW(NULL, message, J_MB_TITLE, MB_OK | MB_ICONINFORMATION);
// SAFETY: verify that the injector is not inside the game directory // SAFETY: verify that the injector is not inside the game directory
HMODULE kernel32 = GetModuleHandleA("kernel32.dll"); HMODULE kernel32 = GetModuleHandleA("kernel32.dll");
wgufn_t wine_get_unix_file_name = (wgufn_t)GetProcAddress(kernel32, "wine_get_unix_file_name"); wgufn_t wine_get_unix_file_name = (wgufn_t)GetProcAddress(kernel32, "wine_get_unix_file_name");
@ -81,7 +158,7 @@ BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved) {
NULL, NULL,
NULL, NULL,
FALSE, FALSE,
CREATE_SUSPENDED, INHERIT_PARENT_AFFINITY,
NULL, NULL,
workdir, workdir,
&si, &si,
@ -94,10 +171,27 @@ BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved) {
exit(1); exit(1);
} }
// Find the game process
wprintf(L"Waiting for game process to start...\n");
DWORD game_pid = 0;
while (!game_pid) {
// wprintf(L"Looking for game process...\n");
game_pid = find_proc_id("StarRail.exe");
Sleep(1);
}
DWORD thread_id = 0;
while (!thread_id) {
// wprintf(L"Looking for game process...\n");
thread_id = find_main_thread(game_pid);
Sleep(1);
}
HANDLE game = OpenProcess(PROCESS_ALL_ACCESS, FALSE, game_pid);
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, thread_id);
SuspendThread(hThread);
// Inject // Inject
void *payloadStart = &_binary_game_p_o_p_game_p_bin_start; void *payloadStart = &_binary_game_p_o_p_game_p_bin_start;
size_t payloadSize = (size_t)&_binary_game_p_o_p_game_p_bin_size; size_t payloadSize = (size_t)&_binary_game_p_o_p_game_p_bin_size;
inject(pi.hProcess, payloadStart, payloadSize, injectDll); inject(game, payloadStart, payloadSize, injectDll);
// Remove the restart flag file // Remove the restart flag file
DeleteFileW(restartFlagFile); DeleteFileW(restartFlagFile);
@ -106,15 +200,15 @@ BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved) {
char *waitEnabled = getenv("WAIT_BEFORE_RESUME"); char *waitEnabled = getenv("WAIT_BEFORE_RESUME");
if (waitEnabled && *waitEnabled) { if (waitEnabled && *waitEnabled) {
wchar_t message[64]; wchar_t message[64];
wsprintfW(message, L"PID: %ld. Press OK to continue", pi.dwProcessId); wsprintfW(message, L"PID: %ld. Thread ID: %ld. Press OK to continue", game_pid, thread_id);
MessageBoxW(NULL, message, J_MB_TITLE, MB_OK | MB_ICONINFORMATION); MessageBoxW(NULL, message, J_MB_TITLE, MB_OK | MB_ICONINFORMATION);
} }
// Resume the process // Resume the process
ResumeThread(pi.hThread); ResumeThread(hThread);
// The launcher process should now hang untill the game terminates // The launcher process should now hang untill the game terminates
WaitForSingleObject(pi.hProcess, INFINITE); WaitForSingleObject(game, INFINITE);
} while (GetFileAttributesW(restartFlagFile) != INVALID_FILE_ATTRIBUTES); } while (GetFileAttributesW(restartFlagFile) != INVALID_FILE_ATTRIBUTES);
return TRUE; return TRUE;

View File

@ -21,15 +21,12 @@ static inline void write_protected_process_memory(HANDLE process, void *address,
DWORD oldProtect; DWORD oldProtect;
VirtualProtectEx(process, address, size, PAGE_EXECUTE_READWRITE, &oldProtect); VirtualProtectEx(process, address, size, PAGE_EXECUTE_READWRITE, &oldProtect);
size_t bytesWritten; WriteProcessMemory(process, address, buf, size, NULL);
WriteProcessMemory(process, address, buf, size, &bytesWritten);
VirtualProtectEx(process, address, size, oldProtect, &oldProtect); VirtualProtectEx(process, address, size, oldProtect, &oldProtect);
} }
void inject(HANDLE process, const void *payload, size_t payloadSize, const wchar_t *dllPath) { void inject(HANDLE process, const void *payload, size_t payloadSize, const wchar_t *dllPath) {
size_t _; // Contrary to the docs, {Write,Read}ProcessMemory likes to crash if the last arg is NULL
// Find the EXE header in the process // Find the EXE header in the process
char exeHeader[1024]; char exeHeader[1024];
IMAGE_DOS_HEADER *dosHeader = NULL; IMAGE_DOS_HEADER *dosHeader = NULL;
@ -38,7 +35,7 @@ void inject(HANDLE process, const void *payload, size_t payloadSize, const wchar
MEMORY_BASIC_INFORMATION memoryInfo; MEMORY_BASIC_INFORMATION memoryInfo;
char *currentAddress = 0x0; char *currentAddress = 0x0;
while (VirtualQueryEx(process, currentAddress, &memoryInfo, sizeof(memoryInfo))) { while (VirtualQueryEx(process, currentAddress, &memoryInfo, sizeof(memoryInfo))) {
ReadProcessMemory(process, currentAddress, exeHeader, sizeof(exeHeader), &_); ReadProcessMemory(process, currentAddress, exeHeader, sizeof(exeHeader), NULL);
dosHeader = (IMAGE_DOS_HEADER*)exeHeader; dosHeader = (IMAGE_DOS_HEADER*)exeHeader;
@ -84,8 +81,8 @@ void inject(HANDLE process, const void *payload, size_t payloadSize, const wchar
char *remoteAlloc = VirtualAllocEx(process, NULL, allocSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); char *remoteAlloc = VirtualAllocEx(process, NULL, allocSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
// Write the assembly payload and dll path // Write the assembly payload and dll path
WriteProcessMemory(process, remoteAlloc, payload, payloadSize, &_); WriteProcessMemory(process, remoteAlloc, payload, payloadSize, NULL);
WriteProcessMemory(process, remoteAlloc + payloadSize, dllPath, dllPathSize, &_); WriteProcessMemory(process, remoteAlloc + payloadSize, dllPath, dllPathSize, NULL);
// Modify the executable to run the assembly payload // Modify the executable to run the assembly payload
@ -97,7 +94,7 @@ void inject(HANDLE process, const void *payload, size_t payloadSize, const wchar
// Save the original entry point address and bytes // Save the original entry point address and bytes
rd.entryPointAddress = entryPoint; rd.entryPointAddress = entryPoint;
ReadProcessMemory(process, rd.entryPointAddress, rd.entryPointData, sizeof(rd.entryPointData), &_); ReadProcessMemory(process, rd.entryPointAddress, rd.entryPointData, sizeof(rd.entryPointData), NULL);
// Replace the entry point with a jump to the assembly payload // Replace the entry point with a jump to the assembly payload
write_protected_process_memory(process, entryPoint, JUMP_INST, sizeof(JUMP_INST)); write_protected_process_memory(process, entryPoint, JUMP_INST, sizeof(JUMP_INST));
@ -110,7 +107,7 @@ void inject(HANDLE process, const void *payload, size_t payloadSize, const wchar
// Save the original descriptor address and bytes // Save the original descriptor address and bytes
rd.importDescriptorAddress = importDescriptors; rd.importDescriptorAddress = importDescriptors;
ReadProcessMemory(process, rd.importDescriptorAddress, &rd.importDescriptorData, sizeof(rd.importDescriptorData), &_); ReadProcessMemory(process, rd.importDescriptorAddress, &rd.importDescriptorData, sizeof(rd.importDescriptorData), NULL);
// Overwrite with zeroes // Overwrite with zeroes
IMAGE_IMPORT_DESCRIPTOR firstDescriptor; IMAGE_IMPORT_DESCRIPTOR firstDescriptor;
@ -122,12 +119,12 @@ void inject(HANDLE process, const void *payload, size_t payloadSize, const wchar
// Save the original value // Save the original value
rd.sizeFieldAddress = ddAddr; rd.sizeFieldAddress = ddAddr;
ReadProcessMemory(process, rd.sizeFieldAddress, &rd.sizeFieldData, sizeof(rd.sizeFieldData), &_); ReadProcessMemory(process, rd.sizeFieldAddress, &rd.sizeFieldData, sizeof(rd.sizeFieldData), NULL);
// Set to 0 // Set to 0
DWORD newSize = 0; DWORD newSize = 0;
write_protected_process_memory(process, ddAddr, &newSize, sizeof(newSize)); write_protected_process_memory(process, ddAddr, &newSize, sizeof(newSize));
// Write recovery data to the allocation // Write recovery data to the allocation
WriteProcessMemory(process, remoteAlloc + payloadSize + dllPathSize, &rd, sizeof(rd), &_); WriteProcessMemory(process, remoteAlloc + payloadSize + dllPathSize, &rd, sizeof(rd), NULL);
} }

View File

@ -1,4 +1,4 @@
project('jadeite', 'c', version: '3.0.12') project('jadeite', 'c', version: '3.1.1')
nasm = find_program('nasm') nasm = find_program('nasm')
gen_res = find_program('gen_resources.sh') gen_res = find_program('gen_resources.sh')

View File

@ -1,42 +1,42 @@
{ {
"jadeite": { "jadeite": {
"version": "3.0.12" "version": "3.1.1"
}, },
"games": { "games": {
"hi3rd": { "hi3rd": {
"global": { "global": {
"status": "verified", "status": "verified",
"version": "7.1.0" "version": "7.3.0"
}, },
"sea": { "sea": {
"status": "verified", "status": "verified",
"version": "7.1.0" "version": "7.3.0"
}, },
"china": { "china": {
"status": "verified", "status": "verified",
"version": "7.2.0" "version": "7.3.0"
}, },
"taiwan": { "taiwan": {
"status": "verified", "status": "verified",
"version": "7.1.0" "version": "7.3.0"
}, },
"korea": { "korea": {
"status": "verified", "status": "verified",
"version": "7.1.0" "version": "7.3.0"
}, },
"japan": { "japan": {
"status": "verified", "status": "verified",
"version": "7.1.0" "version": "7.3.0"
} }
}, },
"hsr": { "hsr": {
"global": { "global": {
"status": "unverified", "status": "verified",
"version": "1.6.0" "version": "2.0.0"
}, },
"china": { "china": {
"status": "unverified", "status": "verified",
"version": "1.6.0" "version": "2.0.0"
} }
} }
} }