Initial commit

This commit is contained in:
mkrsym1 2023-06-06 00:23:08 +03:00
commit 72626c2c18
37 changed files with 1146 additions and 0 deletions

10
.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
.vscode
.directory
# File withheld to make abuse more difficult
game_payload/src/tp6.c
build
out
jadeite.zip

21
LICENSE.txt Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 mkrsym1 <mkrsym1@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

49
README.md Normal file
View File

@ -0,0 +1,49 @@
# PROOF OF CONCEPT. DO NOT USE IF YOU DON'T KNOW WHAT YOU'RE DOING
### Games and regions
This project is in the proof-of-concept stage. Currently, only **3rd glb v6.6.0** is supported. It may be possilbe to completely remove the region and version-specific data in the future. Refer to the source code in `game_payload/src` for details.
### Information
The anticheat the games use is fundamentally incompatible with Wine in multiple ways. This tool launches the game without it (`inject/launcher_payload`) and imitates it's behaviour (`game_payload`).
Does not work on Windows.
### Usage
**Refer to [Third-party launchers](#third-party-launchers) (will be written later)** 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.**
3rd-specific: In some cases, and if you're not using Proton GE, **a fix for Media Foundation may be required to play videos. The Game may crash without it.** You can download it from [here](https://github.com/z0z0z/mf-install). You might need to [limit the number of cores available to the game](https://github.com/z0z0z/mf-install/issues/44) if your CPU has more than 8.
Manual usage instructions:
- Download the game you want to run
- Download a release from this repository
- Extract the archive (**NOT INTO THE GAME DIRECTORY! THIS IS IMPORTANT!**)
- Block analytics servers in your `hosts` file. You can find the list in SERVERS.txt
- 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.
### Internals
This tool consists of three parts: the main injector (`injector`), the launcher payload (`injector/launcher_payload`) and the game payload (`game_payload`).
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.
A part of the source code is witheld (`game_payload/src/tp6.c`). This is a forced measure to make abuse more difficult.
### Guildelines
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
### Troubleshooting
Please do not report any issues with the Game to the official channels. Use the issue tracker of this repository
### Third-party launchers
Will be written later
### Credits
- mkrsym1 &mdash; project leader, reverse engineering
- Yor#1920 &mdash; major help with analyzing network activity
- Some others credited in the source code
License: MIT

4
SERVERS.txt Normal file
View File

@ -0,0 +1,4 @@
# Honkai Impact 3rd logging servers:
0.0.0.0 log-upload-os.hoyoverse.com
0.0.0.0 sg-public-data-api.hoyoverse.com
0.0.0.0 dump.gamesafe.qq.com

19
build.sh Normal file
View File

@ -0,0 +1,19 @@
#!/usr/bin/env sh
rm -f jadeite.zip
rm -rf out
sh setup.sh
ninja -C build
mkdir out
cp ./build/injector/jadeite.exe ./out
cp ./build/injector/launcher_payload/launcher_payload.dll ./out
cp ./build/game_payload/game_payload.dll ./out
cp ./LICENSE.txt ./out
if [ "x$1" = "xrelease" ]; then
cd out
zip ../jadeite.zip *
fi

View File

@ -0,0 +1,8 @@
#pragma once
#include <windows.h>
void ace_fake_driver_files();
HMODULE ace_load_base_module(const char *exeName);
HMODULE ace_load_driver_module();

View File

@ -0,0 +1,24 @@
#pragma once
// Modified from https://stackoverflow.com/a/27950866
#include <stddef.h>
#include <stdint.h>
/* CRC-32C (iSCSI) polynomial in reversed bit order. */
#define __POLY 0x82f63b78
static inline uint32_t crc32c(uint32_t crc, const unsigned char *buf, size_t len) {
crc = ~crc;
while (len--) {
crc ^= *buf++;
for (int k = 0; k < 8; k++) {
crc = crc & 1 ? (crc >> 1) ^ __POLY : crc >> 1;
}
}
return ~crc;
}
#undef __POLY

View File

@ -0,0 +1,6 @@
#pragma once
#include <wchar.h>
void err_mb_a(const char *format, ...);
void err_mb_w(const wchar_t *format, ...);

View File

@ -0,0 +1,20 @@
#pragma once
#include <windows.h>
enum game_id {
GAME_INVALID,
GAME_HI3_GLB
};
struct game_data {
enum game_id id; // Temporary
const char *name;
const char *assembly_path;
const wchar_t *assembly_name_lwr;
const char *tp6_section_name; // Unused for now
const char *tvm_section_name;
};
void game_detect(struct game_data *buf);

View File

@ -0,0 +1,5 @@
#pragma once
#include <game.h>
void hi3_fill_data(struct game_data *buf);

View File

@ -0,0 +1,36 @@
#pragma once
#include <windows.h>
#include <winternl.h>
// https://learn.microsoft.com/en-us/windows/win32/devnotes/ldrdllnotification
typedef struct _LDR_DLL_LOADED_NOTIFICATION_DATA {
ULONG Flags; //Reserved.
PCUNICODE_STRING FullDllName; //The full path name of the DLL module.
PCUNICODE_STRING BaseDllName; //The base file name of the DLL module.
PVOID DllBase; //A pointer to the base address for the DLL in memory.
ULONG SizeOfImage; //The size of the DLL image, in bytes.
} LDR_DLL_LOADED_NOTIFICATION_DATA, *PLDR_DLL_LOADED_NOTIFICATION_DATA;
typedef struct _LDR_DLL_UNLOADED_NOTIFICATION_DATA {
ULONG Flags; //Reserved.
PCUNICODE_STRING FullDllName; //The full path name of the DLL module.
PCUNICODE_STRING BaseDllName; //The base file name of the DLL module.
PVOID DllBase; //A pointer to the base address for the DLL in memory.
ULONG SizeOfImage; //The size of the DLL image, in bytes.
} LDR_DLL_UNLOADED_NOTIFICATION_DATA, *PLDR_DLL_UNLOADED_NOTIFICATION_DATA;
typedef union _LDR_DLL_NOTIFICATION_DATA {
LDR_DLL_LOADED_NOTIFICATION_DATA Loaded;
LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded;
} LDR_DLL_NOTIFICATION_DATA, *PLDR_DLL_NOTIFICATION_DATA;
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);
typedef NTSTATUS (*LdrUnregisterDllNotification_t)(void *cookie);
extern LdrRegisterDllNotification_t LdrRegisterDllNotification;
extern LdrUnregisterDllNotification_t LdrUnregisterDllNotification;
void ntdll_link();

View File

@ -0,0 +1,6 @@
#pragma once
#include <windows.h>
void pe_find_section(HMODULE module, const char *section, MEMORY_BASIC_INFORMATION *buf);
void *pe_find_entry_point(HMODULE module);

View File

@ -0,0 +1,7 @@
#pragma once
#include <windows.h>
#include <game.h>
void tp6_setup_patcher(struct game_data *game, HMODULE thisModule, HMODULE baseModule);

View File

@ -0,0 +1,5 @@
#pragma once
#include <stdint.h>
uint32_t utils_file_crc32c(const char *filePath);

34
game_payload/meson.build Normal file
View File

@ -0,0 +1,34 @@
# Input files
sources = [
'src/main.c',
'src/ntdll.c',
'src/ace.c',
'src/pe.c',
'src/game.c',
'src/hi3.c',
'src/utils.c',
'src/err.c',
# File withheld to make abuse more difficult
'src/tp6.c'
]
resources = [
'res/hi3/glb/allocations.dat',
'res/hi3/glb/entries.dat'
]
# Generate resource files for ./res
res_files = custom_target(
'resources.[ho]',
output: [ 'resources.o', 'resources.h' ],
input: resources,
command: [ gen_res, meson.current_source_dir(), '@OUTPUT0@', '@OUTPUT1@', '@INPUT@' ]
)
shared_library(
'game_payload',
sources,
res_files,
include_directories: 'include',
name_prefix: ''
)

Binary file not shown.

Binary file not shown.

91
game_payload/src/ace.c Normal file
View File

@ -0,0 +1,91 @@
#include <ntdll.h>
#include <pe.h>
#include <err.h>
#include <ace.h>
static void _dll_notification(ULONG reason, const PLDR_DLL_NOTIFICATION_DATA data, void *context) {
if (reason != 1) { // 1 - attach
return;
}
// context should be set to the target module name, lowercase
wchar_t *targetModuleName = (wchar_t*)context;
wchar_t lwModuleName[MAX_PATH];
wcscpy(lwModuleName, data->Loaded.BaseDllName->Buffer);
_wcslwr(lwModuleName);
if (wcscmp(targetModuleName, lwModuleName) == 0) {
// Replace entry point with a stub
void *entryPoint = pe_find_entry_point(data->Loaded.DllBase);
const char ENTRY_POINT_STUB[] = {
0xB8, 0x01, 0x00, 0x00, 0x00, // mov eax, 1
0xC3 // ret
};
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);
}
}
void ace_fake_driver_files() {
// They only report presence
const char *wdDriverPath = "ACE-BASE.sys";
const char *s32DriverPath = "C:\\windows\\system32\\drivers\\ACE-BASE.sys";
HANDLE wdDriverFile = CreateFileA(wdDriverPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (!wdDriverFile) {
err_mb_a("Could not create driver file: %s", wdDriverPath);
}
// Just in case
HANDLE s32DriverFile = CreateFileA(s32DriverPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (!s32DriverFile) {
err_mb_a("Could not create driver file: %s", s32DriverPath);
}
CloseHandle(wdDriverFile);
CloseHandle(s32DriverFile);
}
HMODULE ace_load_base_module(const char *exeName) {
wchar_t baseModuleName[MAX_PATH];
swprintf(baseModuleName, MAX_PATH, L"%sbase.dll", exeName);
wcslwr(baseModuleName);
void *cookie;
LdrRegisterDllNotification(0, &_dll_notification, baseModuleName, &cookie);
HMODULE baseModule = LoadLibraryW(baseModuleName);
if (!baseModule) {
err_mb_w(L"Could not load base module: %ls", baseModuleName);
}
// LoadLibraryA is synchronous; the notification function has already finished executing
LdrUnregisterDllNotification(cookie);
return baseModule;
}
HMODULE ace_load_driver_module() {
const char *driverModulePath = "AntiCheatExpert/InGame/x64/ACE-DRV64.dll";
void *cookie;
LdrRegisterDllNotification(0, &_dll_notification, L"ace-drv64.dll", &cookie);
HMODULE driverModule = LoadLibraryA(driverModulePath);
if (!driverModule) {
err_mb_a("Could not load driver module: %s", driverModulePath);
}
// LoadLibraryA is synchronous; the notification function has already finished executing
LdrUnregisterDllNotification(cookie);
return driverModule;
}

26
game_payload/src/err.c Normal file
View File

@ -0,0 +1,26 @@
#include <windows.h>
#include <stdio.h>
#include <err.h>
#define DEF_ERROR_FN(name, type, printfn, mbfn, projname) \
void name(const type *format, ...) { \
va_list args; \
va_start(args, format); \
\
int count = printfn(NULL, 0, format, args) + 1; \
\
type *buf = malloc(count * sizeof(type)); \
printfn(buf, count, format, args); \
\
mbfn(NULL, buf, projname, MB_OK | MB_ICONERROR); \
\
va_end(args); \
\
free(buf); \
exit(1); \
}
DEF_ERROR_FN(err_mb_a, char, _vsnprintf, MessageBoxA, "Jadeite Autopatcher")
DEF_ERROR_FN(err_mb_w, wchar_t, _vsnwprintf, MessageBoxW, L"Jadeite Autopatcher")

19
game_payload/src/game.c Normal file
View File

@ -0,0 +1,19 @@
#include <err.h>
#include <hi3.h>
#include <game.h>
void game_detect(struct game_data *buf) {
wchar_t exePath[MAX_PATH];
GetModuleFileNameW(NULL, exePath, MAX_PATH);
wchar_t *exeName = wcsrchr(exePath, L'\\') + 1;
wcslwr(exeName);
// Only HI3 is supported for now
if (wcscmp(exeName, L"bh3.exe") == 0) {
hi3_fill_data(buf);
} else {
err_mb_w(L"Unknown game: %ls", exeName);
}
}

44
game_payload/src/hi3.c Normal file
View File

@ -0,0 +1,44 @@
#include <utils.h>
#include <err.h>
#include <hi3.h>
const char *HI3_NAME = "BH3";
const char *HI3_ASSEMBLY_PATH = "BH3_Data/Native/UserAssembly.dll";
const wchar_t *HI3_ASSEMBLY_NAME_LWR = L"userassembly.dll";
const char *HI3_TP6_SECTION_NAME = ".bh3";
const char *HI3_TVM_SECTION_NAME = ".tvm0";
struct crc_id_pair {
uint32_t crc;
enum game_id id;
};
const struct crc_id_pair HI3_REGIONS[] = {
// Only glb for now
// It may be possible to get rid of region-specific data altogether in the future
{ 0x34bdec99, GAME_HI3_GLB } // glb v6.6.0
};
void hi3_fill_data(struct game_data *buf) {
uint32_t crc = utils_file_crc32c("UnityPlayer.dll");
enum game_id id = GAME_INVALID;
for (size_t i = 0; i < sizeof(HI3_REGIONS) / sizeof(struct crc_id_pair); i++) {
if (HI3_REGIONS[i].crc == crc) {
id = HI3_REGIONS[i].id;
}
}
if (id == GAME_INVALID) {
err_mb_a("Invalid UnityPlayer.dll checksum: %d", crc);
}
buf->id = id;
buf->name = HI3_NAME;
buf->assembly_path = HI3_ASSEMBLY_PATH;
buf->assembly_name_lwr = HI3_ASSEMBLY_NAME_LWR;
buf->tp6_section_name = HI3_TP6_SECTION_NAME;
buf->tvm_section_name = HI3_TVM_SECTION_NAME;
}

33
game_payload/src/main.c Normal file
View File

@ -0,0 +1,33 @@
#include <windows.h>
#include <ntdll.h>
#include <ace.h>
#include <game.h>
#include <tp6.h>
#include <utils.h>
BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved) {
// Only listen to attach
if (reason != DLL_PROCESS_ATTACH) {
return TRUE;
}
// Dynamically link functions from ntdll
ntdll_link();
// Detect which game the user is trying to run
struct game_data game;
game_detect(&game);
// Create fake ACE driver files
ace_fake_driver_files();
// Load both ACE modules
HMODULE baseModule = ace_load_base_module(game.name);
ace_load_driver_module();
// ...magic
tp6_setup_patcher(&game, instance, baseModule);
return TRUE;
}

11
game_payload/src/ntdll.c Normal file
View File

@ -0,0 +1,11 @@
#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");
}

34
game_payload/src/pe.c Normal file
View File

@ -0,0 +1,34 @@
#include <stdint.h>
#include <pe.h>
void pe_find_section(HMODULE module, const char *section, MEMORY_BASIC_INFORMATION *buf) {
char *cModule = (char*)module;
IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)module;
IMAGE_NT_HEADERS64* ntHeaders = (IMAGE_NT_HEADERS64*)(cModule + dosHeader->e_lfanew);
uint16_t sectionCount = ntHeaders->FileHeader.NumberOfSections;
IMAGE_SECTION_HEADER* sectionHeader = (IMAGE_SECTION_HEADER*)(ntHeaders + 1);
void* targetAddress = 0x0;
for (uint16_t i = 0; i < sectionCount; i++) {
if (strncmp((char*)sectionHeader->Name, section, 8) == 0) {
targetAddress = (void*)(cModule + sectionHeader->VirtualAddress);
break;
}
sectionHeader++;
}
VirtualQuery(targetAddress, buf, sizeof(MEMORY_BASIC_INFORMATION));
}
void *pe_find_entry_point(HMODULE module) {
char *cModule = (char*)module;
IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)module;
IMAGE_NT_HEADERS64* ntHeaders = (IMAGE_NT_HEADERS64*)(cModule + dosHeader->e_lfanew);
return cModule + ntHeaders->OptionalHeader.AddressOfEntryPoint;
}

2
game_payload/src/tp6.md Normal file
View File

@ -0,0 +1,2 @@
### 1.0.0
- First version

30
game_payload/src/utils.c Normal file
View File

@ -0,0 +1,30 @@
#include <windows.h>
#include <crc32.h>
#include <err.h>
#include <utils.h>
uint32_t utils_file_crc32c(const char *filePath) {
HANDLE file = CreateFileA(filePath, FILE_READ_ACCESS, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (!file) {
err_mb_a("Could not open file: %s", filePath);
}
LARGE_INTEGER fileSize;
GetFileSizeEx(file, &fileSize);
HANDLE hMap = CreateFileMappingA(file, NULL, PAGE_READONLY, 0, 0, NULL);
char *map = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
if (!map) {
err_mb_a("Could not create file mapping for %s", filePath);
}
uint32_t crc = crc32c(0, (unsigned char*)map, fileSize.QuadPart);
UnmapViewOfFile(map);
CloseHandle(hMap);
CloseHandle(file);
return crc;
}

49
gen_resources.sh Normal file
View File

@ -0,0 +1,49 @@
#!/usr/bin/env bash
linker="x86_64-w64-mingw32-ld"
# Read project directory
proj_dir=`realpath "$1"`
shift
# Read output file destinations
resources_o=`realpath "$1"`
shift
resources_h=`realpath "$1"`
shift
# Make sure that the header does not exist
rm -f "${resources_h}"
rm -f "${resources_o}"
# Recomupte relative paths to parameters
idx=0
resource_files=()
for path in "$@"
do
resource_files["${idx}"]=`realpath --relative-to="${proj_dir}" "${path}"`
idx="$(("${idx}" + 1))"
done
# Create the object file
pushd "${proj_dir}" >> /dev/null
$linker -r -b binary -o "${resources_o}" "${resource_files[@]}"
popd >> /dev/null
# Include stddef.h in the resources header (for size_t)
echo "#include <stddef.h>" >> "${resources_h}"
for resource in "${resource_files[@]}"
do
# Use relative path to the resource as the variable name
var_name="_binary_${resource}"
# Replace all non-alphanumeric characters with underscores
var_name=`printf "${var_name}" | sed "s/[^a-zA-Z0-9]/_/g"`
# Define externs in the header
echo "extern void *${var_name}_start;" >> "${resources_h}"
echo "extern void *${var_name}_size;" >> "${resources_h}"
echo "" >> "${resources_h}"
done

View File

@ -0,0 +1,87 @@
#include <windows.h>
static inline void write_protected_process_memory(HANDLE process, void *address, const void *buf, size_t size) {
DWORD oldProtect;
VirtualProtectEx(process, address, size, PAGE_EXECUTE_READWRITE, &oldProtect);
size_t bytesWritten;
WriteProcessMemory(process, address, buf, size, &bytesWritten);
VirtualProtectEx(process, address, size, oldProtect, &oldProtect);
}
static inline void inject(HANDLE process, const void *payload, size_t payloadSize, const char *dllPath) {
size_t _;
// Inject the loader into the module
size_t dllPathLen = strlen(dllPath) + 1;
char *remoteAlloc = VirtualAllocEx(process, NULL, payloadSize + dllPathLen, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(process, remoteAlloc, payload, payloadSize, &_);
WriteProcessMemory(process, remoteAlloc + payloadSize, dllPath, dllPathLen, &_);
// Find the EXE header in the process
char exeHeader[1024];
IMAGE_DOS_HEADER *dosHeader;
IMAGE_NT_HEADERS64 *ntHeaders;
MEMORY_BASIC_INFORMATION memoryInfo;
char *currentAddress = 0x0;
while (VirtualQueryEx(process, currentAddress, &memoryInfo, sizeof(memoryInfo))) {
ReadProcessMemory(process, currentAddress, exeHeader, sizeof(exeHeader), &_);
dosHeader = (IMAGE_DOS_HEADER*)exeHeader;
// DOS header magic "MZ"
if (dosHeader->e_magic != 0x5A4D) {
goto cont;
}
ntHeaders = (IMAGE_NT_HEADERS64*)(exeHeader + dosHeader->e_lfanew);
// NT header signature "PE"
if (ntHeaders->Signature != 0x4550) {
goto cont;
}
// Skip DLLs
if ((ntHeaders->FileHeader.Characteristics | IMAGE_FILE_DLL) == IMAGE_FILE_DLL) {
goto cont;
}
// Skip potential headers without an entry point
// I have no idea how and why they exist, but apparently they do
if (ntHeaders->OptionalHeader.AddressOfEntryPoint == 0) {
goto cont;
}
// Found EXE header
break;
cont:
currentAddress += memoryInfo.RegionSize;
}
char *exe = (char*)memoryInfo.BaseAddress;
// Replace the entry point with a jump to the loader
char *entryPoint = exe + ntHeaders->OptionalHeader.AddressOfEntryPoint;
const unsigned char JUMP_INST[] = { 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 };
write_protected_process_memory(process, entryPoint, JUMP_INST, sizeof(JUMP_INST));
write_protected_process_memory(process, entryPoint + sizeof(JUMP_INST), &remoteAlloc, sizeof(remoteAlloc));
// Break the import table to prevent any dlls from being loaded
// Step 1: break the first import descriptor
char *importDescriptors = exe + ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
IMAGE_IMPORT_DESCRIPTOR firstDescriptor;
ZeroMemory(&firstDescriptor, sizeof(firstDescriptor));
write_protected_process_memory(process, importDescriptors, &firstDescriptor, sizeof(firstDescriptor));
// Step 2: break the image data directory entry
ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size = 0;
write_protected_process_memory(process, exe, exeHeader, sizeof(exeHeader));
}

View File

@ -0,0 +1,18 @@
# Assemble the payload that will be injected into the game
l_payload_bin = asm_gen.process('src/payload.asm')
# Embed it into the library
l_res_files = custom_target(
'lpayload.[oh]',
output: [ 'lpayload.o', 'lpayload.h' ],
input: [ l_payload_bin ],
command: [ gen_res, './injector/launcher_payload', '@OUTPUT0@', '@OUTPUT1@', '@INPUT@' ]
)
shared_library(
'launcher_payload',
'src/dll.c',
l_res_files,
include_directories: '../include',
name_prefix: ''
)

View File

@ -0,0 +1,67 @@
#include <injshared.h>
#include <lpayload.h>
const char EXE_ENV[] = "JADEITE_TARGET_EXE_PATH";
const char INJECT_DLL_ENV[] = "JADEITE_INJECT_DLL_PATH";
static inline void read_env(const char *env, char *dest, size_t size) {
GetEnvironmentVariableA(env, dest, size);
SetEnvironmentVariableA(env, "");
}
BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved) {
// Only listen for attach
if (reason != DLL_PROCESS_ATTACH) {
return TRUE;
}
// Get target EXE path
char targetExe[MAX_PATH];
read_env(EXE_ENV, targetExe, sizeof(targetExe));
// Get the path of the DLL to inject
char injectDll[MAX_PATH];
read_env(INJECT_DLL_ENV, injectDll, sizeof(injectDll));
// Compute the working directory path
char workdir[MAX_PATH];
strcpy(workdir, targetExe);
*(strrchr(workdir, '\\')) = '\0';
// Start the game
STARTUPINFO si;
ZeroMemory(&si, sizeof(si));
PROCESS_INFORMATION pi;
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
if (!CreateProcessA(
targetExe,
NULL,
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
workdir,
&si,
&pi
)) {
exit(1);
}
// Inject
void *payloadStart = &_binary_lpayload_o_p_payload_bin_start;
size_t payloadSize = (size_t)&_binary_lpayload_o_p_payload_bin_size;
inject(pi.hProcess, payloadStart, payloadSize, injectDll);
// Resume the process
ResumeThread(pi.hThread);
// The launcher process should now hang untill the game terminates
WaitForSingleObject(pi.hProcess, INFINITE);
return TRUE;
}

View File

@ -0,0 +1,137 @@
BITS 64
main: ; Replacement entry point
push rbp
mov rbp, rsp
sub rsp, 30h + 90h
call GetKernel32ModuleHandle
mov [rbp - 8h], rax ; kernel32.dll
mov rcx, rax
call GetAddressOf_GetProcAddress
mov [rbp - 10h], rax ; *GetProcAddress
mov rcx, [rbp - 8h] ; kernel32.dll
lea rdx, [rel s_LoadLibraryA]
mov rax, [rbp - 10h] ; *GetProcAddress
call rax ; rax = *LoadLibraryA
mov [rbp - 18h], rax
lea rcx, [rel dllPath]
call rax ; LoadLibraryA(dllPath)
mov rcx, [rbp - 8h] ; kernel32.dll
lea rdx, [rel s_GetModuleHandleA]
mov rax, [rbp - 10h] ; *GetProcAddress
call rax ; rax = *GetModuleHandle
mov rcx, 0
call rax ; rax = .exe base address
mov [rbp - 20h], rax
mov rcx, [rbp - 8h] ; kernel32.dll
lea rdx, [rel s_GetCommandLineW]
mov rax, [rbp - 10h] ; *GetProcAddress
call rax ; rax = *GetCommandLineW
call rax ; rax = command line
mov [rbp - 28h], rax
lea rcx, [rel s_UnityPlayer.dll]
mov rax, [rbp - 18h] ; *LoadLibraryA
call rax ; rax = UnityPlayer.dll
mov rcx, rax
lea rdx, [rel s_UnityMain]
mov rax, [rbp - 10h] ; *GetProcAddress
call rax ; rax = *UnityMain
mov rcx, [rbp - 20h] ; .exe base address
mov rdx, 0 ; hPrevInstance - 0
mov r8, [rbp - 28h] ; command line
mov r9, 1 ; SW_NORMAL
call rax ; UnityMain(...)
add rsp, 30h + 90h
pop rbp
ret
; https://dennisbabkin.com/blog/?t=how-to-implement-getprocaddress-in-shellcode
GetKernel32ModuleHandle:
mov rax, gs:[60h]
mov rax, [rax + 18h]
mov rax, [rax + 20h]
mov rax, [rax]
mov rax, [rax]
mov rax, [rax + 20h]
ret
GetAddressOf_GetProcAddress:
mov eax, [rcx + 3ch]
add rax, rcx
lea rax, [rax + 88h]
mov edx, [rax]
lea rax, [rcx + rdx]
mov edx, [rax + 18h]
mov r8d, [rax + 20h]
lea r8, [rcx + r8]
mov r10, 41636f7250746547h ; "GetProcA"
mov r11, 0073736572646441h ; "Address\0"
GAO_GPA@1:
mov r9d, [r8]
lea r9, [rcx + r9]
; Function name comparision
cmp r10, [r9]
jnz GAO_GPA@2
cmp r11, [r9 + 7]
jnz GAO_GPA@2
; Found GetProcAddress
neg rdx
mov r10d, [rax + 18h]
lea rdx, [r10 + rdx]
mov r10d, [rax + 24h]
lea r10, [rcx + r10]
movzx rdx, word [r10 + rdx * 2]
mov r10d, [rax + 1ch]
lea r10, [rcx + r10]
mov r10d, [r10 + rdx * 4]
lea rax, [rcx + r10] ; Function address
jmp GAO_GPA@end
GAO_GPA@2:
add r8, 4
dec rdx
jnz GAO_GPA@1
GAO_GPA@end:
ret
; Strings
s_LoadLibraryA: db "LoadLibraryA", 0
s_GetModuleHandleA: db "GetModuleHandleA", 0
s_GetCommandLineW: db "GetCommandLineW", 0
s_UnityPlayer.dll: db "UnityPlayer.dll", 0
s_UnityMain: db "UnityMain", 0
dllPath:
; This will be filled out by the launcher payload dll
; Path to the dll to inject into the game

21
injector/meson.build Normal file
View File

@ -0,0 +1,21 @@
# Assemble the payload that will be injected into the launcher
inj_payload_bin = asm_gen.process('src/payload.asm')
# Embed it into the library
inj_res_files = custom_target(
'ipayload.[oh]',
output: [ 'ipayload.o', 'ipayload.h' ],
input: [ inj_payload_bin ],
command: [ gen_res, './injector', '@OUTPUT0@', '@OUTPUT1@', '@INPUT@' ]
)
# Main injector exe
executable(
'jadeite',
'src/injector.c',
inj_res_files,
include_directories: 'include',
name_prefix: ''
)
subdir('launcher_payload')

87
injector/src/injector.c Normal file
View File

@ -0,0 +1,87 @@
#include <stdio.h>
#include <injshared.h>
#include <ipayload.h>
const char EXE_ENV[] = "JADEITE_TARGET_EXE_PATH";
const char INJECT_DLL_ENV[] = "JADEITE_INJECT_DLL_PATH";
const char LAUNCHER_INJECT_DLL[] = "launcher_payload.dll";
const char GAME_INJECT_DLL[] = "game_payload.dll";
int main(int argc, char **argv) {
// Read arguments
char *gamePath = NULL;
char *launcherPath = NULL;
switch (argc) {
case 1:
printf("Usage: wine jadeite.exe [game path] <launcher path>\n");
return 0;
case 2:
printf("No launcher process specified! Using explorer.exe\n");
gamePath = argv[1];
launcherPath = "C:\\Windows\\explorer.exe";
break;
case 3:
gamePath = argv[1];
launcherPath = argv[2];
break;
default:
fprintf(stderr, "Too many arguments! (%d)\n", argc);
return 1;
}
// Compute absolute paths
char gameExePath[MAX_PATH];
GetFullPathNameA(gamePath, sizeof(gameExePath), gameExePath, NULL);
char gamePayloadPath[MAX_PATH];
GetFullPathNameA(GAME_INJECT_DLL, sizeof(gamePayloadPath), gamePayloadPath, NULL);
char launcherPayloadPath[MAX_PATH];
GetFullPathNameA(LAUNCHER_INJECT_DLL, sizeof(launcherPayloadPath), launcherPayloadPath, NULL);
printf("Starting \"%s\" via \"%s\"\n", gameExePath, launcherPath);
// Set envvars
SetEnvironmentVariableA(EXE_ENV, gameExePath);
SetEnvironmentVariableA(INJECT_DLL_ENV, gamePayloadPath);
// Start the launcher
STARTUPINFO si;
ZeroMemory(&si, sizeof(si));
PROCESS_INFORMATION pi;
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
if (!CreateProcessA(
launcherPath,
NULL,
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
NULL,
&si,
&pi
)) {
fprintf(stderr, "Could not start process! (%ld)\n", GetLastError());
exit(1);
}
printf("Started launcher process (%ld)\n", pi.dwProcessId);
// Inject
void *payloadStart = &_binary_ipayload_o_p_payload_bin_start;
size_t payloadSize = (size_t)&_binary_ipayload_o_p_payload_bin_size; // yes this is valid
inject(pi.hProcess, payloadStart, payloadSize, launcherPayloadPath);
// Resume the process
ResumeThread(pi.hThread);
return 0;
}

98
injector/src/payload.asm Normal file
View File

@ -0,0 +1,98 @@
BITS 64
main: ; Replacement entry point
push rbp
mov rbp, rsp
sub rsp, 10h + 90h
call GetKernel32ModuleHandle
mov [rbp - 8h], rax ; kernel32.dll
mov rcx, rax
call GetAddressOf_GetProcAddress
mov [rbp - 10h], rax ; *GetProcAddress
mov rcx, [rbp - 8h] ; kernel32.dll
lea rdx, [rel s_LoadLibraryA]
mov rax, [rbp - 10h] ; *GetProcAddress
call rax ; rax = *LoadLibraryA
lea rcx, [rel dllPath]
call rax ; LoadLibraryA(dllPath)
add rsp, 10h + 90h
pop rbp
ret
; https://dennisbabkin.com/blog/?t=how-to-implement-getprocaddress-in-shellcode
GetKernel32ModuleHandle:
mov rax, gs:[60h]
mov rax, [rax + 18h]
mov rax, [rax + 20h]
mov rax, [rax]
mov rax, [rax]
mov rax, [rax + 20h]
ret
GetAddressOf_GetProcAddress:
mov eax, [rcx + 3ch]
add rax, rcx
lea rax, [rax + 88h]
mov edx, [rax]
lea rax, [rcx + rdx]
mov edx, [rax + 18h]
mov r8d, [rax + 20h]
lea r8, [rcx + r8]
mov r10, 41636f7250746547h ; "GetProcA"
mov r11, 0073736572646441h ; "Address\0"
GAO_GPA@1:
mov r9d, [r8]
lea r9, [rcx + r9]
; Function name comparision
cmp r10, [r9]
jnz GAO_GPA@2
cmp r11, [r9 + 7]
jnz GAO_GPA@2
; Found GetProcAddress
neg rdx
mov r10d, [rax + 18h]
lea rdx, [r10 + rdx]
mov r10d, [rax + 24h]
lea r10, [rcx + r10]
movzx rdx, word [r10 + rdx * 2]
mov r10d, [rax + 1ch]
lea r10, [rcx + r10]
mov r10d, [r10 + rdx * 4]
lea rax, [rcx + r10] ; Function address
jmp GAO_GPA@end
GAO_GPA@2:
add r8, 4
dec rdx
jnz GAO_GPA@1
GAO_GPA@end:
ret
; Strings
s_LoadLibraryA: db "LoadLibraryA", 0
dllPath:
; This will be filled out by the injector
; Path to the dll to inject into the launcher

21
meson.build Normal file
View File

@ -0,0 +1,21 @@
project('jadeite', 'c', version: '1.0.0')
nasm = find_program('nasm')
gen_res = find_program('gen_resources.sh')
# Generator for compiling asm files
asm_gen = generator(
nasm,
output: '@BASENAME@.bin',
arguments: [
'-f', 'bin',
'@INPUT@',
'-o', '@OUTPUT@'
]
)
# Payload that gets injected into the game
subdir('game_payload')
# The injector exe and dll
subdir('injector')

13
mingw_cross.txt Normal file
View File

@ -0,0 +1,13 @@
[binaries]
c = 'x86_64-w64-mingw32-gcc'
cpp = 'x86_64-w64-mingw32-g++'
ld = 'x86_64-w64-mingw32-ld'
ar = 'x86_64-w64-mingw32-ar'
strip = 'x86_64-w64-mingw32-strip'
pkgconfig = 'x86_64-w64-mingw32-pkg-config'
[host_machine]
system = 'windows'
cpu_family = 'x86_64'
cpu = 'x86_64'
endian = 'little'

4
setup.sh Normal file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env sh
rm -rf build
meson setup --cross-file mingw_cross.txt build