Initial commit
This commit is contained in:
commit
72626c2c18
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal 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
21
LICENSE.txt
Normal 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
49
README.md
Normal 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 — project leader, reverse engineering
|
||||
- Yor#1920 — major help with analyzing network activity
|
||||
- Some others credited in the source code
|
||||
|
||||
License: MIT
|
4
SERVERS.txt
Normal file
4
SERVERS.txt
Normal 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
19
build.sh
Normal 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
|
8
game_payload/include/ace.h
Normal file
8
game_payload/include/ace.h
Normal 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();
|
24
game_payload/include/crc32.h
Normal file
24
game_payload/include/crc32.h
Normal 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
|
6
game_payload/include/err.h
Normal file
6
game_payload/include/err.h
Normal 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, ...);
|
20
game_payload/include/game.h
Normal file
20
game_payload/include/game.h
Normal 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);
|
5
game_payload/include/hi3.h
Normal file
5
game_payload/include/hi3.h
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <game.h>
|
||||
|
||||
void hi3_fill_data(struct game_data *buf);
|
36
game_payload/include/ntdll.h
Normal file
36
game_payload/include/ntdll.h
Normal 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();
|
6
game_payload/include/pe.h
Normal file
6
game_payload/include/pe.h
Normal 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);
|
7
game_payload/include/tp6.h
Normal file
7
game_payload/include/tp6.h
Normal 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);
|
5
game_payload/include/utils.h
Normal file
5
game_payload/include/utils.h
Normal 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
34
game_payload/meson.build
Normal 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: ''
|
||||
)
|
BIN
game_payload/res/hi3/glb/allocations.dat
Normal file
BIN
game_payload/res/hi3/glb/allocations.dat
Normal file
Binary file not shown.
BIN
game_payload/res/hi3/glb/entries.dat
Normal file
BIN
game_payload/res/hi3/glb/entries.dat
Normal file
Binary file not shown.
91
game_payload/src/ace.c
Normal file
91
game_payload/src/ace.c
Normal 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
26
game_payload/src/err.c
Normal 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
19
game_payload/src/game.c
Normal 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
44
game_payload/src/hi3.c
Normal 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
33
game_payload/src/main.c
Normal 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
11
game_payload/src/ntdll.c
Normal 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
34
game_payload/src/pe.c
Normal 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
2
game_payload/src/tp6.md
Normal file
@ -0,0 +1,2 @@
|
||||
### 1.0.0
|
||||
- First version
|
30
game_payload/src/utils.c
Normal file
30
game_payload/src/utils.c
Normal 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
49
gen_resources.sh
Normal 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
|
87
injector/include/injshared.h
Normal file
87
injector/include/injshared.h
Normal 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));
|
||||
}
|
18
injector/launcher_payload/meson.build
Normal file
18
injector/launcher_payload/meson.build
Normal 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: ''
|
||||
)
|
67
injector/launcher_payload/src/dll.c
Normal file
67
injector/launcher_payload/src/dll.c
Normal 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;
|
||||
}
|
137
injector/launcher_payload/src/payload.asm
Normal file
137
injector/launcher_payload/src/payload.asm
Normal 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
21
injector/meson.build
Normal 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
87
injector/src/injector.c
Normal 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
98
injector/src/payload.asm
Normal 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
21
meson.build
Normal 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
13
mingw_cross.txt
Normal 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'
|
Loading…
Reference in New Issue
Block a user