Lots of Updates (expand for more):

- Started implementing new parser for chunked data
- Started documenting data formats
- Started dissector for network protocol
- Added AI-Graph renderer (converts .pth files to python data you can import into Blender)
- Added Script to convert savefile to JSON
- Added (old) parser for chunked data format
- Added basic parser for LFVF data section (Vertex Data)
- Added script to analyze and filter read trace generated with frida script
- Added various Frida scripts
This commit is contained in:
Daniel S. 2020-08-04 18:05:34 +02:00
parent aabacafd9c
commit 8d92f25b8c
47 changed files with 2744 additions and 411 deletions

2
.gitignore vendored
View file

@ -265,3 +265,5 @@ __pycache__/
ScrapHacks/build/*
ScrapHacks/src/D3D8_VMT.hpp
.vscode/c_cpp_properties.json
tools/*.log
frida/*.mp

108
NOTES.md
View file

@ -1,6 +1,6 @@
# Infos
- Engine: ScrapEngine
- Engine: ScrapEngine/Mercury Engine
- Ingame Scripting Language: Python 1.5.2
- Interesting memory locations and functions are noted in `config.yml`
@ -8,6 +8,8 @@
- `-console`: open external console window on start
- `-wideWindow`: start game in widescreen mode
- `-dedicated`: start in mutliplayer dedicated server mode (needs to be used with `-server`)
- `-server`: start in multiplayer server mode
# Functions identified:
@ -23,11 +25,11 @@
## External Console (Scenegraph Debugging?) (Handler @ `0x5f9520`):
* `listar luces`
* `listar`
* `arbol` (Patch Scrap.exe@offset 0x314bc9 replace 0x20 with 0x00 (or just type `arbol ` with a space at the end))
* `mem`
* `ver uniones`
* `listar luces` List lights in scene
* `listar` list models in scene
* `arbol <model_name>` show details for model
* `mem` (doesn't do anything?)
* `ver uniones`
* Easter Eggs:
* `imbecil`
* `idiota`
@ -67,12 +69,11 @@ unsigned long hash(const unsigned char *s)
## Other Functions:
Check `r2_analyze.py` for full list
Check `config.yml` for full list
## File Index struct @ `0x7fcbec`
```cpp
struct FileEntry {
uint32_t offset;
uint32_t size;
@ -87,7 +88,7 @@ struct FileIDX {
};
```
## Packed Index struct (array @ `0x7fc1b0`)
## Packed Index struct (array of 0x80 entries @ `0x7fc1b0`)
```cpp
struct PackedIDX {
void** VMT;
@ -111,7 +112,10 @@ struct CPP_Callback {
}
```
## Game engine Variables Pointer @ `0x7FBE4C`
## Game engine Variables Hashtable @ `0x7fbe50`
## Game engine Variables @ `0x7fbe4c`
Structure:
@ -130,20 +134,21 @@ struct GameVar {
Types
| Value | Type |
| ------ | ----------------------- |
| `0x10` | const char* |
| `0x20` | int32_t |
| `0x30` | User Control Definition |
| `0x40` | float |
| `0x60` | Callback function |
| Value | Type |
|-------|-----------------|
| `0x1` | const char* |
| `0x2` | int32_t |
| `0x3` | List of Defines |
| `0x4` | float |
| `0x5` | function |
| `0x6` | Script function |
## Game World/State Pointer @ `0x7fe944`
Points to World struct
| Offset | Type | Description |
| ------ | ------------------------ | -------------------------------------- |
|--------|--------------------------|----------------------------------------|
| 0x0000 | `void**` | Virtual Method Table |
| 0x0004 | `uint32_t` | Size of Entity Hashtable |
| 0x0008 | `void**` | Pointer to Entity Hashtable |
@ -172,6 +177,15 @@ Points to World struct
| 0x2238 | `???` | Used in `World_Init` |
| 0x2254 | `float` | Used in `World_Init` |
## cPyEntity structure
| Offset | Type | Description |
|--------|----------|----------------------|
| 0x0000 | `void**` | Virtual Method Table |
| 0x0004 | `char*` | Name |
| 0x0008 | `void*` | ??? |
## Entity Hash Table
Hash-function used: [PJW](https://en.wikipedia.org/wiki/PJW_hash_function) (Same parameters as the example implementation)
@ -189,7 +203,7 @@ struct HT_Entry {
Data format:
| Offset | Type | Description |
| ------ | ------------- | ------------------------ |
|--------|---------------|--------------------------|
| 0x0 | `void**` | Virtual Method Table (?) |
| 0x4 | `const char*` | name as string |
| 0x14 | `void*` | pointer to self (why?) |
@ -204,21 +218,57 @@ Attributes:
- `OnDeath`
- `OnDamage`
# File Formats
# Netplay protocol (WIP)
## .packed File Format:
Game Info Packet
```
Header:
"BFPK\0\0\0\0"
Int32ul: number of files
for each file:
Int32ul: path length
String: path
Int32ul: size
Int32ul: offset in file
Server 'B':FZ (0/10) Ver 1.0 at 192.168.99.1:28086
[0-3] header/ID?
[4-5] port (16-bit)
[6-7] max_players (16-bit)
[8-9] curr_player (16-bit)
[10-x] server name (char*)
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0019fdc0 ba ce 00 01 b6 6d 0a 00 00 00 42 00 30 fe 19 00 .....m....B.0...
0019fdd0 ff ff ff ff 27 2b b3 9b c7 3e bb 00 9c af 29 00 ....'+...>....).
0019fde0 db 69 00 00 00 00 00 00 00 00 44 65 61 74 68 4d .i........DeathM
0019fdf0 61 74 63 68 00 00 00 00 ff ff 46 5a 00 4a 91 f0 atch......FZ.J..
0019fe00 92 8b 57 4e 7f 00 00 00 10 21 fe 38 0d ae 00 00 ..WN.....!.8....
0019fe10 f0 ce f3 36 a0 e8 0b 77 a0 e8 ...6...w..
```
Player Join Packet
```
[0-3] header/ID?
[6-x] Player name
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
09c9dfe8 7f 47 00 00 00 0e 55 6e 6e 61 6d 65 64 20 50 6c .G....Unnamed Pl
09c9dff8 61 79 65 72 06 53 42 6f 73 73 31 b9 00 07 50 5f ayer.SBoss1...P_
09c9e008 42 65 74 74 79 06 4d 42 4f 53 53 31 06 4d 42 4f Betty.MBOSS1.MBO
09c9e018 53 53 31 00 00 10 30 2c 31 35 2c 30 2c 30 2c 31 SS1...0,15,0,0,1
09c9e028 35 2c 31 35 2c 31 02 00 00 00 5,15,1....
```
| Message | Description |
|------------------------------------------|-------------------------------------------------------------------|
| `5c68625c32383230395c73637261706c616e64` | "Scrapland Server" announcement broadcast (`\hb\28209\scrapland`) |
| `7f01000007` | Retrieve Game info |
| `48423d35323932322c3235363a323830383600` | Connection Information (`HB=52922,256:28086`) |
# [Notes](NOTES.md)
## File Formats
- [Chunked](file_formats/chunked.md)
- [Packed](file_formats/packed.md)
- [AI Pathfinding Graph](file_formats/ai_path.md)
# Virtual Method Tables:
check `r2_analyze.py` for full list

View file

@ -1,13 +1,24 @@
# Scrapland Reverse Engineering notes and tools
## Scripts:
* `parse_save.py`: Dumps information extracted from Save file
## Note!
All memory addresses are only valid for an unprotected `Scrap.exe` v1.0 with a SHA1 checksum of `d2dde960e8eca69d60c2e39a439088b75f0c89fa` , other version will crash if the memory offsets don't match and you try to inject ScrapHacks
[Computer Bild Spiele Issue 2006/08](https://archive.org/download/cbs-2006-08-coverdisc/) Contains a full version of the game which was used as the basis for this project
## Scripts
* `tools/rbingrep.py`: Search for pattern in all files and generate radare2 script to find all references (currently configured to search for chunked file section headers)
* `frida/`: Scripts for use with Frida
* `parse_chunked.py`: WIP Parser for the game's chunked data format (Models, Animations, Maps)
* `save_to_json.py`: Convert game save to JSON
* `scrapper.py`: Extractor and Repacker for *.packed files, needs the `construct` and `tqdm` python modules and python 3.x
- Run `scrapper.py -h` for help
* `r2_analyze.py`: uses radare2 to parse and label a lot of interesting stuff in the `Scrap.exe` binary
* `lib/dbg.py`: general Script for poking around inside the game's scripting system
- Run `import dbg` inside the Game's Console,
this will load all builtin modules and enable godmode
- Run `import dbg;dbg.init()` inside the Game's Console,
this will load all builtin modules, ScrapHacks and enable godmode
- The dbg module also enables writing to the ingame console using `print <var>`
and defines two global functions s_write() and e_write() for writing to the Ingame Console's Stdout and Stderr Stream
- `dbg.menu()` Displays the Game's built in Debug Menu (doesn't work properly)
@ -24,9 +35,15 @@ WIP Memory hacking library
# Tools used:
- [Python 3](https://python.org/) + [Construct](https://construct.readthedocs.io/en/latest/)
- [IDA](https://www.hex-rays.com/products/ida/index.shtml) and [x32dbg](https://x64dbg.com/)
- [Reclass.NET](https://github.com/ReClassNET/ReClass.NET)
- [HxD](https://mh-nexus.de/en/hxd/)
- [Kaitai Struct](http://kaitai.io/)
- [Radare2](https://www.radare.org/) + [Cutter](https://cutter.re/)
- Binary parsing:
- [HxD](https://mh-nexus.de/en/hxd/) for initial file analysis
- [Python 3](https://python.org/) + [Construct](https://construct.readthedocs.io/en/latest/) for binary parsing
- [Kaitai Struct](http://kaitai.io/) for binary parsing
- Static analysis:
- [IDA](https://www.hex-rays.com/products/ida/index.shtml) initialy used, later replaced by radare2 and Cutter
- [radare2](https://www.radare.org/)
- [Cutter](https://cutter.re/)
- Dynamic analysis:
- [x64dbg](https://x64dbg.com/) for dynamic analysis
- [Reclass.NET](https://github.com/ReClassNET/ReClass.NET) to analyze structures and classes in memory
- [Frida](https://frida.re/) for tracing and instrumenting functions

View file

@ -17,8 +17,7 @@
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "msvc-x86",
"configurationProvider": "vector-of-bool.cmake-tools",
"compileCommands": "${workspaceFolder}/build/compile_commands.json"
"configurationProvider": "vector-of-bool.cmake-tools"
}
],
"version": 4

View file

@ -78,5 +78,11 @@
"xtr1common": "cpp",
"xtree": "cpp",
"xutility": "cpp"
}
},
"cmake.generator": "NMake Makefiles",
"cmake.preferredGenerators": [
"NMake Makefiles",
"Ninja",
"Unix Makefiles"
]
}

21
ScrapHacks/.vscode/tasks.json vendored Normal file
View file

@ -0,0 +1,21 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "shell",
"command": "${workspaceFolder}\\build.bat",
"problemMatcher": [
"$msCompile"
]
},
{
"label": "clean",
"type": "shell",
"command": "rm -Force -Recurse ${workspaceFolder}\\build",
"problemMatcher": []
}
]
}

View file

@ -1,6 +1,5 @@
cmake_minimum_required(VERSION 3.1)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
project(ScrapHacks
VERSION 1.0
DESCRIPTION "Scrapland memory hacking library"
@ -13,6 +12,8 @@ if(NOT IS_ABSOLUTE "${SCRAPLAND_DIR}" OR NOT EXISTS "${SCRAPLAND_DIR}")
message(FATAL_ERROR "Scrapland installation folder not found!")
endif()
message(STATUS "Scrapland found at ${SCRAPLAND_DIR}")
message(STATUS "Checking Scrap.exe hash")
file(SHA1 "${SCRAPLAND_DIR}/Bin/Scrap.exe" SCRAP_EXE_HASH)
@ -20,14 +21,26 @@ if(NOT ${SCRAP_EXE_HASH} STREQUAL "d2dde960e8eca69d60c2e39a439088b75f0c89fa")
message(FATAL_ERROR "Scrap.exe hash miss match!")
endif()
# ==============================
# "${SCRAPLAND_DIR}/Bin/Scrap.exe"
add_custom_target(
run
COMMAND "Scrap.exe"
WORKING_DIRECTORY "${SCRAPLAND_DIR}/Bin"
VERBATIM
)
add_dependencies(run install)
# ==============================
set(COMPONENT "ScrapHacks")
set(FETCHCONTENT_QUIET 0)
set(CMAKE_BUILD_TYPE "RelMinSize")
set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}")
set(ASMJIT_EMBED true)
set(ASMTK_EMBED true)
set(ZYDIS_BUILD_TOOLS false)
set(ZYDIS_BUILD_EXAMPLES false)
set(ASMJIT_EMBED ON CACHE INTERNAL "")
set(ASMTK_EMBED ON CACHE INTERNAL "")
set(ZYDIS_BUILD_TOOLS OFF CACHE INTERNAL "")
set(ZYDIS_BUILD_EXAMPLES OFF CACHE INTERNAL "")
set(toml11_BUILD_TEST OFF CACHE INTERNAL "")
if(WIN32)
if(MSVC)
@ -44,54 +57,67 @@ endif(WIN32)
include(FetchContent)
FetchContent_Declare(
Python
PREFIX ${CMAKE_CURRENT_BINARY_DIR}
URL https://www.python.org/ftp/python/src/py152.tgz
URL_HASH SHA1=2d648d07b1aa1aab32a3a24851c33715141779b9
)
FetchContent_Declare(
DirectX
PREFIX ${CMAKE_CURRENT_BINARY_DIR}
URL
https://archive.org/download/DirectX.8.0a.SDK_includes_libs_only/DirectX.8.0a.SDK.zip
URL_HASH SHA1=39f168336d0df92ff14d62d5e3aef1b9e3191312)
FetchContent_MakeAvailable(DirectX)
URL https://archive.org/download/DirectX.8.0a.SDK_includes_libs_only/DirectX.8.0a.SDK.zip
URL_HASH SHA1=39f168336d0df92ff14d62d5e3aef1b9e3191312
)
FetchContent_Declare(
ASM_JIT
PREFIX ${CMAKE_CURRENT_BINARY_DIR}
GIT_REPOSITORY git@github.com:asmjit/asmjit.git
GIT_SHALLOW true
GIT_PROGRESS true
INSTALL_COMMAND ""
CONFIGURE_COMMAND ""
)
FetchContent_MakeAvailable(ASM_JIT)
FetchContent_Declare(
ASM_TK
PREFIX ${CMAKE_CURRENT_BINARY_DIR}
GIT_REPOSITORY git@github.com:asmjit/asmtk.git
GIT_SHALLOW true
GIT_PROGRESS true
INSTALL_COMMAND ""
)
FetchContent_MakeAvailable(ASM_TK)
set(ASMJIT_DIR ${asm_jit_SOURCE_DIR})
include(${asm_tk_SOURCE_DIR}/CMakeLists.txt)
FetchContent_Declare(
Zydis
PREFIX ${CMAKE_CURRENT_BINARY_DIR}
GIT_REPOSITORY git@github.com:zyantific/zydis.git
GIT_SHALLOW true
GIT_PROGRESS true
INSTALL_COMMAND ""
)
# FetchContent_MakeAvailable(Zydis)
FetchContent_MakeAvailable(Python DirectX ASM_JIT ASM_TK Zydis)
include_directories(AFTER ${directx_SOURCE_DIR}/8.0/include/ ${ASMTK_INCLUDE_DIRS} ${ASMJIT_INCLUDE_DIRS})
set(ASMJIT_DIR ${asm_jit_SOURCE_DIR})
include(${asm_tk_SOURCE_DIR}/CMakeLists.txt)
message(STATUS "Python 1.5.2: ${python_SOURCE_DIR}")
message(STATUS "DX8: ${directx_SOURCE_DIR}")
message(STATUS "Zydis: ${zydis_SOURCE_DIR}")
include_directories(AFTER
${directx_SOURCE_DIR}/8.0/include/
${python_SOURCE_DIR}/Include/
${ASMTK_INCLUDE_DIRS}
${ASMJIT_INCLUDE_DIRS}
${toml_SOURCE_DIR}
)
link_directories(AFTER ${directx_SOURCE_DIR}/8.0/lib/)
find_package(Python3 3.6 REQUIRED COMPONENTS Interpreter)
find_package(Python3 REQUIRED COMPONENTS Interpreter)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/D3D8_VMT.hpp
@ -111,7 +137,10 @@ add_library(ScrapHack SHARED
${ASMJIT_SRC}
)
set_target_properties(ScrapHack PROPERTIES SUFFIX ".pyd")
set_target_properties(ScrapHack PROPERTIES PREFIX "_")
add_dependencies(ScrapHack D3D8_VMT)
# add_dependencies(ScrapHack Python152)
# add_dependencies(ScrapHack Python152_Bin)
@ -119,10 +148,13 @@ target_link_libraries(ScrapHack
d3d8
d3dx8
dxerr8
gdiplus
# gdiplus
# PYTHON15
# Zydis
Zydis
legacy_stdio_definitions)
install(TARGETS ScrapHack RUNTIME DESTINATION ${SCRAPLAND_DIR}/lib)
target_compile_features(ScrapHack PUBLIC cxx_std_11)
install(TARGETS ScrapHack RUNTIME DESTINATION ${SCRAPLAND_DIR}/lib COMPONENT ScrapHacks)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/python/ScrapHack.py DESTINATION ${SCRAPLAND_DIR}/lib COMPONENT ScrapHacks)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/python/dbg.py DESTINATION ${SCRAPLAND_DIR}/lib COMPONENT ScrapHacks)
target_compile_features(ScrapHack PUBLIC cxx_std_17)

View file

@ -1,10 +1,11 @@
## Features
- read and write memory
- disassemble memory (using zydis)
- change DirectX state
- Draw DirectX overlay (still need to make a useful overlay)
- Dump various data structures to the console
- Assemble and execute code on the fly
- Assemble and execute code on the fly (using asmtk)
- Can be controlled via keyboard shortcuts (TODO: allow defining own shortcuts for commands)
## Prerequisites
@ -30,7 +31,34 @@ This will find the Games's installation folder, verify that the version you have
- type `import ScrapHack`
- type `$help`
## Notes
## Config file keys
(this has only been tested with a (cracked/unpacked/de-obfuscated) `Scrap.exe` v1.0 with a SHA1 checksum of `d2dde960e8eca69d60c2e39a439088b75f0c89fa` , other version will crash if the memory offsets don't match)
- patches.asm: map of address->list of assembly instructions
- patches.hex: map of address->hex bytes
Example:
```json
{
"patches": {
"hex": {
"0xDEADBEEF": "BADFOODDEADFEED"
},
"asm": {
"0xBADF00D": [
"pushad",
"call 0xf00dbabe",
"popad",
"mov eax, 0x42",
"ret"
]
},
}
}
```
## Third-Party components used
- Zydis disassembler
- asmJIT/asmTK assembler
- nlohmann/json JSON-parser

View file

@ -5,6 +5,15 @@ if "%VSINSTALLDIR%"=="" (
call "%%i" x86
)
)
if not exist build cmake -G"NMake Makefiles" -B build
cmake --build build --target install
if "%VSINSTALLDIR%"=="" (
echo "VSINSTALLDIR" not set something is wrong!
) else (
if not exist build cmake -G"NMake Makefiles" -B build
if "%1"=="--run" (
cmake --build build --target run
) else (
cmake --build build --target install
)
)
endlocal

View file

@ -147,6 +147,13 @@ void unhook_d3d8() {
hooked=false;
}
map<size_t,void*> *dx_hooks = new map<size_t,void*>({
{VMT_IDirect3DDevice8::m_EndScene,H_EndScene},
{VMT_IDirect3DDevice8::m_BeginScene,H_BeginScene},
{VMT_IDirect3DDevice8::m_DrawIndexedPrimitive,H_DrawIndexedPrimitive},
{VMT_IDirect3DDevice8::m_SetLight,H_SetLight},
});
void hook_d3d8() {
if (hooked) {
return;
@ -162,11 +169,9 @@ void hook_d3d8() {
hFont = CreateFontA(15, 0, 0, 0, FW_EXTRABOLD, 0, 0, 0, ANSI_CHARSET, 0, 0,
0, 0, "Lucida Console");
hBrush = CreateSolidBrush(D3DCOLOR_ARGB(25, 0, 0, 0));
Hook::addr(GetVTable(dev)[VMT_IDirect3DDevice8::m_EndScene], H_EndScene);
Hook::addr(GetVTable(dev)[VMT_IDirect3DDevice8::m_BeginScene], H_BeginScene);
Hook::addr(GetVTable(dev)[VMT_IDirect3DDevice8::m_DrawIndexedPrimitive],
H_DrawIndexedPrimitive);
Hook::addr(GetVTable(dev)[VMT_IDirect3DDevice8::m_SetLight], H_SetLight);
for (auto h: *dx_hooks) {
Hook::addr(GetVTable(dev)[h.first], h.second);
}
hooked=true;
return;
}

View file

@ -9,9 +9,10 @@
using namespace std;
/*
vector<uint8_t> make_trampoline(uintptr_t orig,uintptr_t hook) {
using namespace asmjit;
vector<uint8_t> ret;
JitRuntime rt;
CodeHolder code;
CodeInfo ci=rt.codeInfo();
@ -23,9 +24,15 @@ vector<uint8_t> make_trampoline(uintptr_t orig,uintptr_t hook) {
code.resolveUnresolvedLinks();
code.relocateToBase(orig);
size_t code_size=code.sectionById(0)->buffer().size();
code.copyFlattenedData((void*)orig, code_size, CodeHolder::kCopyWithPadding);
uint8_t* buffer=new uint8_t[code_size];
code.copyFlattenedData((void*)buffer, code_size, CodeHolder::kCopyWithPadding);
for (size_t i=0;i<code_size;++i) {
ret.push_back(buffer[i]);
}
delete buffer;
return ret;
}
*/
class Hook {
private:
@ -33,29 +40,32 @@ class Hook {
void *orig;
void *detour;
bool enabled;
uint8_t orig_bytes[6];
uint8_t jmp_bytes[6];
uint8_t *orig_bytes;
uint8_t *jmp_bytes;
size_t size;
static map<uintptr_t, shared_ptr<Hook>> hooks;
public:
Hook(void *func, void *detour) {
// TODO: build jmp_bytes using asmjit
uintptr_t dest = reinterpret_cast<uintptr_t>(detour);
uintptr_t src = reinterpret_cast<uintptr_t>(func);
this->orig = func;
this->detour = detour;
this->jmp_bytes[0] = 0x68; // push
this->jmp_bytes[1] = (dest >> 0) & 0xff;
this->jmp_bytes[2] = (dest >> 8) & 0xff;
this->jmp_bytes[3] = (dest >> 16) & 0xff;
this->jmp_bytes[4] = (dest >> 24) & 0xff;
this->jmp_bytes[5] = 0xC3; // ret
VirtualQuery(func, &mbi, sizeof(mbi));
vector<uint8_t> code = make_trampoline(src,dest);
this->orig_bytes = new uint8_t[code.size()];
this->jmp_bytes = new uint8_t[code.size()];
this->size = code.size();
this->enabled = false;
uint8_t* func_b = reinterpret_cast<uint8_t*>(this->orig);
for (size_t i=0;i<this->size;++i) {
this->orig_bytes[i]=func_b[i];
this->jmp_bytes[i]=code[i];
}
VirtualQuery(this->orig, &mbi, sizeof(mbi));
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE,
&mbi.Protect);
memcpy(this->orig_bytes, this->orig, 1 + 4 + 1);
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
this->enabled = false;
cout<<"Constructed hook from "<<func<<" to "<<detour<<", size: " << this->size<<endl;
}
~Hook() {
@ -64,6 +74,11 @@ class Hook {
this->disable();
}
static void addr(uintptr_t _addr, void *detour) {
Hook::addr(reinterpret_cast<void*>(_addr),detour);
}
static void addr(void *addr, void *detour) {
cout << "Hooking: [" << addr << " -> " << detour << "]" << endl;
uintptr_t key = reinterpret_cast<uintptr_t>(detour);
@ -105,23 +120,23 @@ class Hook {
void disable() {
if (this->enabled) {
// cout << "Disabling: [" << this->orig << " <- " << this->detour <<
// "]"
// << endl;
cout << "Disabling: [" << this->orig << " <- " << this->detour <<
"]"
<< endl;
VirtualProtect(mbi.BaseAddress, mbi.RegionSize,
PAGE_EXECUTE_READWRITE, NULL);
memcpy(this->orig, this->orig_bytes, 1 + 4 + 1);
memcpy(this->orig, this->orig_bytes, this->size);
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
this->enabled = false;
}
}
void enable() {
if (!this->enabled) {
// cout << "Enabling: [" << this->orig << " -> " << this->detour <<
// "]" << endl;
cout << "Enabling: [" << this->orig << " -> " << this->detour <<
"]" << endl;
VirtualProtect(mbi.BaseAddress, mbi.RegionSize,
PAGE_EXECUTE_READWRITE, NULL);
memcpy(this->orig, this->jmp_bytes, 1 + 4 + 1);
memcpy(this->orig, this->jmp_bytes, this->size);
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
this->enabled = true;
}

74
ScrapHacks/src/Py_Mod.hpp Normal file
View file

@ -0,0 +1,74 @@
#pragma once
#include "Scrapland.hpp"
using namespace std;
static void *py_asm(void *self, void *args)
{
void* ret;
void *addr = nullptr;
char *asm_code;
if (!PyArg_ParseTuple(args, "s|i", &asm_code, &addr))
{
return nullptr;
}
string code(asm_code);
size_t data_size=asm_size(split(code,';'));
if (addr==nullptr) {
addr = malloc(data_size);
if (addr==nullptr) {
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, "malloc() failed");
scrap_log(ERR_COLOR, "\n");
return Py_BuildValue("s",nullptr);
}
char ptr[255];
snprintf(ptr,255,"Buffer @ %p\n",(void*)addr);
scrap_log(INFO_COLOR,ptr);
ret=Py_BuildValue("(l,l)", data_size, addr);
} else {
ret=Py_BuildValue("(l,l)", data_size, addr);
}
assemble(split(code,';'),reinterpret_cast<uint64_t>(addr));
return ret;
}
static void *py_dasm(void *self, void *args) {
// TODO: return list of (addr,asm)
return nullptr;
}
static void *py_read_mem(void *self, void *args)
{
//TODO: implement reading memory and return data as hex string
void* ret = nullptr;
void *addr = nullptr;
size_t size = 256;
char *data;
if (!PyArg_ParseTuple(args, "ii", &addr, &size))
{
return nullptr;
}
char* buffer=new char[size*2];
for (size_t i=0;i<size;++i) {
}
return ret;
}
static void *py_write_mem(void *self, void *args) {
//TODO: implement (return None or error string)
return nullptr;
}
static PyMethodDef SH_Methods[] = {
// {"asm", py_asm, 1},
// {"read", py_read_mem, 1},
// {"write", py_write_mem, 1},
{NULL, NULL}
};
void InitPyMod()
{
Py_InitModule("_ScrapHack", SH_Methods, nullptr, nullptr, 1007);
}

View file

@ -2,7 +2,7 @@
#include <iostream>
#include <map>
#include <string>
// #include <Python.h>
#include "Structures.hpp"
using namespace std;

View file

@ -10,6 +10,7 @@
#include <string>
#include <asmtk/asmtk.h>
#include <Zydis/Zydis.h>
#include "Scrapland.hpp"
#include "Util.hpp"
@ -40,17 +41,20 @@ size_t assemble(vector<string> assembly,uint64_t base) {
for (string line:assembly) {
if (err = p.parse((line+"\n").c_str())) {
snprintf(err_msg,1024,"PARSE ERROR: [%s] %08x (%s)\n",line.c_str(), err, DebugUtils::errorAsString(err));
cerr<<err_msg<<endl;
scrap_log(ERR_COLOR,err_msg);
return 0;
}
}
if (err=code.flatten()) {
snprintf(err_msg,1024,"FLATTEN ERROR: %08x (%s)\n", err, DebugUtils::errorAsString(err));
cerr<<err_msg<<endl;
scrap_log(ERR_COLOR,err_msg);
return 0;
}
if (err=code.resolveUnresolvedLinks()) {
snprintf(err_msg,1024,"RESOLVE ERROR: %08x (%s)\n", err, DebugUtils::errorAsString(err));
cerr<<err_msg<<endl;
scrap_log(ERR_COLOR,err_msg);
return 0;
}
@ -63,6 +67,7 @@ size_t assemble(vector<string> assembly,uint64_t base) {
MEMORY_BASIC_INFORMATION mbi;
if (VirtualQuery((void*)base, &mbi, sizeof(mbi)) == 0) {
cerr<<"ERROR: "<< GetLastErrorAsString() <<endl;
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, GetLastErrorAsString());
scrap_log(ERR_COLOR, "\n");
@ -70,11 +75,13 @@ size_t assemble(vector<string> assembly,uint64_t base) {
};
if (!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect))
{
cerr<<"ERROR: "<< GetLastErrorAsString() <<endl;
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, GetLastErrorAsString());
scrap_log(ERR_COLOR, "\n");
return 0;
};
cout<<"CODE: "<< hexdump_s((void*)base,buffer.size()) << endl;
memcpy((void*)base,buffer.data(),buffer.size());
scrap_log(INFO_COLOR,"Code:"+hexdump_s((void*)base,buffer.size()));
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
@ -85,7 +92,38 @@ size_t asm_size(vector<string> assembly) {
return assemble(assembly,0);
}
string disassemble(void* addr, size_t num,bool compact) {
stringstream ret;
ZyanU64 z_addr = reinterpret_cast<uintptr_t>(addr);
ZydisDecoder decoder;
ZydisFormatter formatter;
ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_COMPAT_32, ZYDIS_ADDRESS_WIDTH_32);
ZydisFormatterInit(&formatter, ZYDIS_FORMATTER_STYLE_INTEL);
ZydisDecodedInstruction instruction;
while (ZYAN_SUCCESS(ZydisDecoderDecodeBuffer(&decoder, addr, -1, &instruction)))
{
char buffer[256];
ZydisFormatterFormatInstruction(&formatter, &instruction, buffer, sizeof(buffer), z_addr);
if (!compact) {
ret<< "[" << std::hex << setfill('0') << setw(8) << z_addr << "]: ";
}
ret << buffer;
if (compact) {
ret<<"; ";
} else {
ret<<endl;
}
addr = reinterpret_cast<void*>(z_addr += instruction.length);
num--;
if (num==0) {
break;
}
}
return ret.str();
}
struct Command {
t_cmd_func func;
@ -217,6 +255,46 @@ get_protection(void *addr) {
return mbi.Protect;
}
void cmd_disassemble(Command* cmd, vector<string> args) {
MEMORY_BASIC_INFORMATION mbi;
if (args.size()<1) {
scrap_log(ERR_COLOR, cmd->usage);
scrap_log(ERR_COLOR, "\n");
return;
}
uintptr_t addr = UINTPTR_MAX;
size_t size = 0xff;
try {
addr = stoull(args[0], 0, 16);
if (args.size()>1) {
size = stoull(args[1]);
}
} catch (exception e) {
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, e.what());
scrap_log(ERR_COLOR, "\n");
return;
}
void *mptr = reinterpret_cast<void *>(addr);
if (VirtualQuery(mptr, &mbi, sizeof(mbi)) == 0) {
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, GetLastErrorAsString());
scrap_log(ERR_COLOR, "\n");
return;
};
if (!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE,
&mbi.Protect)) {
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, GetLastErrorAsString());
scrap_log(ERR_COLOR, "\n");
return;
};
string dasm = disassemble(mptr, size, false);
scrap_log(INFO_COLOR, dasm);
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
return;
}
void cmd_exec(Command* cmd, vector<string> args) {
void *addr;
MEMORY_BASIC_INFORMATION mbi;
@ -339,7 +417,7 @@ void cmd_read(Command* cmd,vector<string> args) {
return;
};
string hxd = hexdump_s(mptr, size);
scrap_log(INFO_COLOR, hxd.c_str());
scrap_log(INFO_COLOR, hxd);
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
if (buffer) {
free(buffer);
@ -399,22 +477,26 @@ void cmd_dx8(Command* cmd,vector<string> args) {
}
void cmd_dump_stack(Command* cmd, vector<string> args) {
stringstream ret;
void** stack=(void**)_AddressOfReturnAddress();
cout<<"ESP: "<<stack<<endl;
for (size_t n=0;n<0x100;++n) {
if (!addr_exists(stack[n])) {
continue;
}
char R=can_read(stack[n]) ? 'R' : ' ';
char W=can_write(stack[n]) ? 'W' : ' ';
char X=can_execute(stack[n]) ? 'X' : ' ';
cout<<"ESP[" << std::hex << setfill('0') << setw(2) << n <<"]: "<<stack[n]<<" "<<R<<W<<X;
if (can_read(stack[n])) {
cout<<" [ "<<hexdump_s(stack[n],0xf,true)<<"]"<<endl;
} else {
cout<<endl;
bool r,w,x;
char R=(r=can_read(stack[n])) ? 'R' : ' ';
char W=(w=can_write(stack[n])) ? 'W' : ' ';
char X=(x=can_execute(stack[n])) ? 'X' : ' ';
ret<< std::hex << setfill('0') << setw(8) << stack+(n*sizeof(void*)) << ": "<<stack[n]<<" "<<R<<W<<X;
if (r && !x) {
ret<<" [ "<<hexdump_s(stack[n],0xf,true)<<"]";
} else if (r && x) {
ret<<" [ "<<disassemble(stack[n],5,true)<<"]";
}
ret<<endl;
}
scrap_log(INFO_COLOR,ret.str());
return;
}
@ -426,7 +508,7 @@ void cmd_dump_py(Command* cmd,vector<string> args) {
<< meth.second->ml_meth << endl;
}
}
scrap_log(INFO_COLOR,out.str().c_str());
scrap_log(INFO_COLOR,out.str());
}
void cmd_dump_vars(Command* cmd, vector<string> args) {
@ -437,7 +519,7 @@ void cmd_dump_vars(Command* cmd, vector<string> args) {
out<<var->name<< "[" <<std::hex <<(uint16_t)var->type <<","<< (uint16_t)var->subtype << std::dec << "]: " << var->desc<<" ("<<var->value<<", "<<var->default<<")"<<endl;
var=var->next;
}
scrap_log(INFO_COLOR,out.str().c_str());
scrap_log(INFO_COLOR,out.str());
}
void cmd_dump_ents(Command* cmd,vector<string> args) {
@ -446,7 +528,7 @@ void cmd_dump_ents(Command* cmd,vector<string> args) {
dump_ht(ptr<HashTable<Entity>>(P_WORLD, O_ENTS), &out);
out << "Entity Lists:" << endl;
dump_ht(ptr<HashTable<EntityList>>(P_WORLD, O_ENTLISTS), &out);
scrap_log(INFO_COLOR,out.str().c_str());
scrap_log(INFO_COLOR,out.str());
return;
}
@ -478,10 +560,14 @@ void cmd_disable_overlay(Command* cmd,vector<string> args) {
void cmd_print_alarm(Command* cmd,vector<string> args) {
stringstream out;
float *alarm = ptr<float>(P_WORLD, O_ALARM);
float *alarm_grow = ptr<float>(P_WORLD, O_ALARM_GROW);
out << "Alarm: " << alarm[0] << " + " << alarm_grow[0] << endl;
scrap_log(INFO_COLOR,out.str().c_str());
float alarm = ptr<float>(P_WORLD, O_ALARM)[0];
float alarm_grow = ptr<float>(P_WORLD, O_ALARM_GROW)[0];
if (alarm_grow<0) {
out << "Alarm: " << alarm << " - " << alarm_grow << endl;
} else {
out << "Alarm: " << alarm << " + " << alarm_grow << endl;
}
scrap_log(INFO_COLOR,out.str());
return;
}
@ -528,6 +614,7 @@ void cmd_asm(Command* cmd, vector<string> args) {
assemble(split(code,';'),buffer_addr);
}
void cmd_help(Command* cmd,vector<string> args);
static REPL* repl=new REPL(
@ -538,6 +625,7 @@ static REPL* repl=new REPL(
{"exec",new Command(cmd_exec,"Usage: $exec <addr>","Start a new thread at the specified address")},
{"asm",new Command(cmd_asm,"Usage: $asm [addr] <inst1>;<inst2>;...","Assemble instructions at address, if no address is given allocate memory and assemble code into that")},
{"stack",new Command(cmd_dump_stack,"Usage: $mem stack","Dump stack contents")},
{"dasm",new Command(cmd_disassemble,"Usage: $mem dasm <addr> [num_inst]","Disassemble memory at address")},
})},
{"unload",new Command(cmd_unload,"Usage: $unload","Unload ScrapHacks")},
{"dx8",new Command(cmd_dx8,"Usage: $dx8 <subcommand>","Manipulate DirectX 8 functions and state",{

View file

@ -6,7 +6,8 @@
#include <string>
#include <typeinfo>
#include <vector>
#include <fstream>
// #include <Python.h>
#include <Windows.h>
using namespace std;
@ -18,6 +19,7 @@ using namespace std;
#include "Scrapland.hpp"
#include "Structures.hpp"
#include "Util.hpp"
#include "Py_Mod.hpp"
bool initialized = false;
bool running = true;
@ -28,12 +30,14 @@ void DllUnload();
int hooked_console(const char *);
void hook_exit();
void setup_hooks() {
Hook::addr(reinterpret_cast<void *>(P_SCRAP_EXIT), hook_exit);
Hook::addr(reinterpret_cast<void *>(P_CON_HANDLER), hooked_console);
void setup_hooks()
{
Hook::addr(P_SCRAP_EXIT, hook_exit);
Hook::addr(P_CON_HANDLER, hooked_console);
}
void MainLoop() {
void MainLoop()
{
setup_hooks();
overlay = true;
cout << "[*] Starting main Loop" << endl;
@ -49,42 +53,56 @@ void MainLoop() {
cout << "[ F ] \"Handbrake\" (*Will* crash the game after some time!)"
<< endl;
while (running) {
while (running)
{
Sleep(100);
while (key_down('F')) {
while (key_down('F'))
{
scrap_exec("dbg.brake()");
}
if (key_down_norepeat(VK_F3)) {
if (key_down_norepeat(VK_F3))
{
break;
}
if (key_down_norepeat(VK_F7)) {
if (key_down_norepeat(VK_F7))
{
int32_t *money = ptr<int32_t>(P_WORLD, O_MONEY);
money[0] = 0x7fffffff;
}
if (key_down_norepeat(VK_F9)) {
if (key_down_norepeat(VK_F8))
{
cout << "Not yet implemented" << endl;
}
if (key_down_norepeat(VK_F9))
{
cout << "Entities:" << endl;
dump_ht(ptr<HashTable<Entity>>(P_WORLD, O_ENTS));
cout << "Entity Lists:" << endl;
dump_ht(ptr<HashTable<EntityList>>(P_WORLD, O_ENTLISTS));
}
if (key_down_norepeat(VK_F10)) {
if (key_down_norepeat(VK_F10))
{
scrap_exec("dbg.settrace()");
}
}
FreeLibraryAndExitThread(hMod, 0);
}
void InitConsole() {
void InitConsole()
{
char me[1024];
GetModuleFileName(hMod, me, 1024);
GetModuleFileNameA(hMod, me, 1024);
SetupConsole(me);
}
int hooked_console(const char *cmd) {
int hooked_console(const char *cmd)
{
typedef decltype(&hooked_console) t_func;
if (cmd[0] == '$') {
if (cmd[0] == '$')
{
handle_command(++cmd);
return 0;
}
@ -93,7 +111,8 @@ int hooked_console(const char *cmd) {
return ret;
}
void hook_exit() {
void hook_exit()
{
typedef decltype(&hook_exit) t_func;
shared_ptr<Hook> hook = Hook::get(hook_exit);
DllUnload();
@ -102,7 +121,8 @@ void hook_exit() {
return;
}
void DllInit(HMODULE mod) {
void DllInit(HMODULE mod)
{
hMod = mod;
char mfn[1024];
GetModuleFileNameA(0, mfn, 1024);
@ -119,36 +139,23 @@ void DllInit(HMODULE mod) {
CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)MainLoop, NULL, 0, 0);
cout << "[*] Starting message pump" << endl;
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return;
}
void *H_port_FixupExtension(char *name, char *filename) {
cout<<"FixupExtension: "<<name<<": "<<filename<<endl;
Hook::drop(H_port_FixupExtension);
return NULL;
}
void *H_PyEval_CallObjectWithKeywords(void *func, void *arg, void *kwarg) {
cout<<"PyEval_CallObjectWithKeywords:"<<endl;
cout<<"\t func: "<<func<<endl;
cout<<"\t arg: "<<arg<<endl;
cout<<"\t kwarg: "<<kwarg<<endl;
Hook::drop(H_PyEval_CallObjectWithKeywords);
return NULL;
}
void DllPreInit() {
void DllPreInit()
{
Sleep(100);
InitConsole();
Hook::addr(reinterpret_cast<void *>(0x5a9ca0), H_port_FixupExtension);
Hook::addr(reinterpret_cast<void *>(0x5cdb00),
H_PyEval_CallObjectWithKeywords);
InitPyMod();
}
void DllUnload() {
void DllUnload()
{
SetConsoleCtrlHandler(NULL, false);
unhook_d3d8();
Hook::clear();

View file

@ -1,6 +1,6 @@
#pragma once
#include "Structures.hpp"
#include "Util.hpp"
#include <sstream>
using namespace std;
@ -17,14 +17,21 @@ using namespace std;
#define P_PY_MODS 0x79C698
// FUNCTION ADDRESSES
// ENGINE INTERNALS
#define P_CON_HANDLER 0x402190
#define P_SCRAP_LOG 0x4134C0
#define P_SCRAP_EXEC 0x5a8390
#define P_SCRAP_EXIT 0x4010c0
#define P_D3DCHECK 0x602a70
#define P_D3DDEV 0x852914
// PYTHON FUNCTIONS
#define P_Py_InitModule 0x5A8FB0
#define P_PyArg_ParseTuple 0x5bb9d0
#define P_PyBuildValue 0x5a90f0
#define P_PyList_New 0x5b3120
#define P_PyTuple_New 0x5b91a0
#define P_PyList_SetItem 0x5b3240
#define P_PyTuple_SetItem 0x5b92a0
#define MSG_COLOR scrap_RGB(128,0,255)
@ -41,14 +48,20 @@ uint32_t scrap_RGB(uint8_t r,uint8_t g,uint8_t b) {
typedef int(_cdecl *t_scrap_log)(unsigned int color, const char *message);
typedef int(_cdecl *t_scrap_exec)(const char *code);
typedef int(_cdecl *t_PyArg_ParseTuple)(void *PyObj, char *format, ...);
typedef void*(_cdecl *t_Py_BuildValue)(char *format, ...);
typedef int(_cdecl *t_Py_InitModule)(const char *name, void *methods,
const char *doc, void *passthrough,
int module_api_version);
typedef void*(_cdecl *t_PyList_New)(char *format, ...);
typedef void*(_cdecl *t_PyTuple_New)(char *format, ...);
typedef void*(_cdecl *t_PyList_SetItem)(char *format, ...);
typedef void*(_cdecl *t_PyList_SetItem)(char *format, ...);
// GLOBAL FUNCTIONS
auto scrap_exec = (t_scrap_exec)P_SCRAP_EXEC;
auto pyarg_parsetuple = (t_PyArg_ParseTuple)P_PyArg_ParseTuple;
auto py_initmodule = (t_Py_InitModule)P_Py_InitModule;
auto PyArg_ParseTuple = (t_PyArg_ParseTuple)P_PyArg_ParseTuple;
auto Py_BuildValue = (t_Py_BuildValue)P_PyBuildValue;
auto Py_InitModule = (t_Py_InitModule)P_Py_InitModule;
int scrap_log(unsigned int color,const char* msg) {
return ((t_scrap_log)P_SCRAP_LOG)(color,msg);
@ -58,7 +71,6 @@ int scrap_log(uint8_t r,uint8_t g,uint8_t b,const char* msg) {
return ((t_scrap_log)P_SCRAP_LOG)(scrap_RGB(r,g,b),msg);
}
int scrap_log(unsigned int color,string msg) {
return ((t_scrap_log)P_SCRAP_LOG)(color,msg.c_str());
}
@ -67,6 +79,12 @@ int scrap_log(uint8_t r,uint8_t g,uint8_t b,string msg) {
return ((t_scrap_log)P_SCRAP_LOG)(scrap_RGB(r,g,b),msg.c_str());
}
int scrap_err() {
string err("Error: ");
err+=GetLastErrorAsString();
err+="\n";
return scrap_log(ERR_COLOR,err);
}
size_t size_ht(HashTable<EntityList> *ht) {
size_t cnt = 0;

View file

@ -26,7 +26,7 @@ string GetLastErrorAsString() {
size_t m_size = FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
NULL, errorMessageID, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
(LPSTR)&messageBuffer, 0, NULL);
string message(messageBuffer, m_size);
LocalFree(messageBuffer);

View file

@ -12,9 +12,10 @@ HANDLE hThread = INVALID_HANDLE_VALUE;
bool loaded = false;
HMODULE mod = nullptr;
DLL_EXPORT void initScrapHack() {
DLL_EXPORT void init_ScrapHack() {
DllPreInit();
if (!loaded) {
Sleep(1000);
hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)DllInit, mod,
0, 0);
CloseHandle<