Repository navigation
Expand file tree
/
Copy pathTweak.mm
More file actions
227 lines (174 loc) · 9.22 KB
/
Copy pathTweak.mm
File metadata and controls
227 lines (174 loc) · 9.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
#include <thread>
#include <string>
#include <cstdint>
#include <vector>
#import <Foundation/Foundation.h>
// include KittyMemory
#include "../KittyMemory/KittyInclude.hpp"
// fancy struct for patches
struct MemPatches
{
// let's assume we have patches for these functions for whatever game
// boolean function
MemoryPatch get_canShoot;
// int function
MemoryPatch get_gold;
// etc...
} gPatches;
MachOImage g_MachOImage;
void test_thread()
{
KITTY_LOGI("================== THREAD LOADED =====================");
KITTY_LOGI("App Executable: %{public}s", MachOImage::getMainImage().name());
// loop until our target binary is found
do
{
sleep(1);
// base executable
// g_MachOImage = MachOImage::getMainImage();
// or framework
g_MachOImage = MachOImage::findMachOImage("UnityFramework");
} while (!g_MachOImage.isValid());
KITTY_LOGI("UnityFramework slide: %p", (void *)g_MachOImage.slide());
KITTY_LOGI("UnityFramework start: %p", (void *)g_MachOImage.start());
KITTY_LOGI("UnityFramework end: %p", (void *)g_MachOImage.end());
KITTY_LOGI("UnityFramework size: %p", (void *)g_MachOImage.size());
KITTY_LOGI("UnityFramework path: %{public}s", g_MachOImage.name());
KITTY_LOGI("=========== SEGMENTS & SECTIONS ============");
KITTY_LOGI("segments count: %d", int(g_MachOImage.segments().size()));
for (auto &it : g_MachOImage.segments())
{
KITTY_LOGI("%{public}s -> %p", it.first.c_str(), (void *)it.second.start);
}
KITTY_LOGI("sections count: %d", int(g_MachOImage.sections().size()));
for (auto &it : g_MachOImage.sections())
{
KITTY_LOGI("%{public}s -> %p", it.first.c_str(), (void *)it.second.start);
}
KITTY_LOGI("=============== FIND SYMBOL ================");
KITTY_LOGI("symbols count: %d", int(g_MachOImage.symbols().size()));
// you may have to prefix function name with underscore
// with existing MachOImage object
KITTY_LOGI("il2cpp_string_new: %p", (void *)(g_MachOImage.findSymbol("_il2cpp_string_new")));
KITTY_LOGI("============= FIND MEMORY MAPPED FILES =============");
// better to search late into the game to get finalized metadata regions
// if there are multiple mappings, in most cases choose the one that's closes to original file size on disk
{
auto unityMetadataMappings = KittyMemory::getFileMappings("global-metadata.dat");
KITTY_LOGI("unityMetadataMappings: %d", int(unityMetadataMappings.size()));
for (size_t i = 0; i < unityMetadataMappings.size(); i++)
{
uintptr_t start = unityMetadataMappings[i].front().start;
uintptr_t end = unityMetadataMappings[i].back().end;
KITTY_LOGI("unityMetadataMappings[%d]: %p - %p", int(i), (void *)start, (void *)end);
}
NSString *appDocDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *dumpDir = [NSString stringWithFormat:@"%@/Dump", appDocDir];
KittyIOFile::createDirectoryRecursive(dumpDir.UTF8String);
// dump metadata
std::string dumpDatPath = KittyUtils::String::fmt("%s/global-metadata.dat", dumpDir.UTF8String);
if (KittyMemory::dumpFileMappingsToDisk("global-metadata.dat", dumpDatPath))
{
KITTY_LOGI("Dumped Unity global-metadata mappings at %{public}s", dumpDir.UTF8String);
}
else
{
KITTY_LOGI("Failed to dump Unity global-metadata!");
}
}
KITTY_LOGI("==================== MEMORY PATCH ===================");
uintptr_t unityBase = g_MachOImage.slide();
#ifndef kNO_KEYSTONE
// with asm (uses keystone assembler) insert ';' to seperate statements
// its recommeneded to test your instructions on https://armconverter.com or
// https://shell-storm.org/online/Online-Assembler-and-Disassembler/ change MP_ASM_ARM64 to your targeted asm arch
// MP_ASM_ARM32, MP_ASM_ARM64, MP_ASM_x86, MP_ASM_x86_64
gPatches.get_canShoot = MemoryPatch::createWithAsm(unityBase + 0x10948D4, MP_ASM_ARM64, "mov x0, #1; ret");
// format asm
std::string asm_fmt = KittyUtils::String::fmt("mov x0, #%d; ret", 65536);
gPatches.get_gold = MemoryPatch::createWithAsm(unityBase + 0x2A17E04, MP_ASM_ARM64, asm_fmt);
#endif
// hex with or without spaces both are fine
gPatches.get_canShoot = MemoryPatch::createWithHex(unityBase + 0x10948D4, "01 00 A0 E3 1E FF 2F E1");
// raw bytes
gPatches.get_canShoot = MemoryPatch::createWithBytes(unityBase + 0x1019C1F20,
"\x01\x00\xA0\xE3\x1E\xFF\x2F\xE1",
8);
KITTY_LOGI("Patch Address: %p", (void *)gPatches.get_canShoot.get_TargetAddress());
KITTY_LOGI("Patch Size: %zu", gPatches.get_canShoot.get_PatchSize());
KITTY_LOGI("Current Bytes: %{public}s", gPatches.get_canShoot.get_CurrBytes().c_str());
// modify & print bytes
if (gPatches.get_canShoot.Modify())
{
KITTY_LOGI("get_canShoot has been modified successfully");
KITTY_LOGI("Current Bytes: %{public}s", gPatches.get_canShoot.get_CurrBytes().c_str());
}
// restore & print bytes
if (gPatches.get_canShoot.Restore())
{
KITTY_LOGI("get_canShoot has been restored successfully");
KITTY_LOGI("Current Bytes: %{public}s", gPatches.get_canShoot.get_CurrBytes().c_str());
}
// writedata alternative, check KittyMemory/writeData.hpp
// write 64 bit integer ( 8 bytes )
if (writeData64(unityBase + 0x1019C1F20, 0x200080D2C0035FD6))
KITTY_LOGI("get_canShoot has been modified successfully");
// or as 32 bit integer ( 4 bytes )
if (writeData32(unityBase + 0x1019C1F20, 0x200080D2) && writeData32(unityBase + 0x1019C1F20 + 4, 0xC0035FD6))
KITTY_LOGI("get_canShoot has been modified successfully");
KITTY_LOGI("=============== PATTERN SCAN ===============");
// scan within a memory range for bytes with mask x and ?
// enables or disables syscall-based memory reads for safe pattern scanning.
// KittyScanner::setPatternScanSafeMode(true);
// get start & end address of __TEXT segment
mem_range_info_t text_seg = g_MachOImage.findSegment("__TEXT");
KITTY_LOGI("__TEXT Address: %p | Size: %p", (void *)text_seg.start, (void *)text_seg.size);
// get start & end address of __DATA segment
mem_range_info_t data_seg = g_MachOImage.findSegment("__DATA");
KITTY_LOGI("__DATA Address: %p | Size: %p", (void *)data_seg.start, (void *)data_seg.size);
uintptr_t found_at = 0;
std::vector<uintptr_t> found_at_list;
// scan with direct bytes & get one result
found_at = KittyScanner::findBytesFirst(text_seg.start,
text_seg.end,
"\x33\x44\x55\x66\x00\x77\x88\x00\x99",
"xxxx??x?x");
KITTY_LOGI("found bytes at: %p", (void *)found_at);
// scan with direct bytes & get all results
found_at_list = KittyScanner::findBytesAll(text_seg.start,
text_seg.end,
"\x33\x44\x55\x66\x00\x77\x88\x00\x99",
"xxxx??x?x");
KITTY_LOGI("found bytes results: %zu", found_at_list.size());
// scan with hex & get one result
found_at = KittyScanner::findHexFirst(text_seg.start, text_seg.end, "33 44 55 66 00 77 88 00 99", "xxxx??x?x");
KITTY_LOGI("found hex at: %p", (void *)found_at);
// scan with hex & get all results
found_at_list = KittyScanner::findHexAll(text_seg.start, text_seg.end, "33 44 55 66 00 77 88 00 99", "xxxx??x?x");
KITTY_LOGI("found hex results: %zu", found_at_list.size());
// scan with IDA pattern get one result
found_at = KittyScanner::findIdaPatternFirst(text_seg.start, text_seg.end, "33 ? 55 66 ? 77 88 ? 99");
KITTY_LOGI("found IDA pattern at: %p", (void *)found_at);
// scan with IDA pattern get all results
found_at_list = KittyScanner::findIdaPatternAll(text_seg.start, text_seg.end, "33 ? 55 66 ? 77 88 ? 99");
KITTY_LOGI("found IDA pattern results: %zu", found_at_list.size());
// scan with data type & get one result
uint32_t data = 0x99887766;
found_at = KittyScanner::findDataFirst(data_seg.start, data_seg.end, &data, sizeof(data));
KITTY_LOGI("found data at: %p", (void *)found_at);
// scan with data type & get all results
found_at_list = KittyScanner::findDataAll(data_seg.start, data_seg.end, &data, sizeof(data));
KITTY_LOGI("found data results: %zu", found_at_list.size());
KITTY_LOGI("================= HEX DUMP =================");
// hex dump by default 8 rows with ASCII
KITTY_LOGI("\n%{public}s", KittyUtils::Data::hexDump(g_MachOImage.header(), sizeof(*g_MachOImage.header())).c_str());
KITTY_LOGI("============================================");
// 16 rows, no ASCII
KITTY_LOGI("\n%{public}s",
KittyUtils::Data::hexDump<16, false>(g_MachOImage.header(), sizeof(*g_MachOImage.header())).c_str());
}
__attribute__((constructor)) void init()
{
KITTY_LOGI("====================== LOADED =====================");
std::thread(test_thread).detach();
}