-
Notifications
You must be signed in to change notification settings - Fork 0
/
vm_tool.h
317 lines (272 loc) · 10 KB
/
vm_tool.h
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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
/*
Originally based on Razzile & HackJack's vm_writeData,
Modified and formatted by HenryQuan.
Repo: https://github.com/HenryQuan/vm_tools
This header includes functions necessary for virtual memory searching and
writing. This is built to search only the binary section. Only tested on
iOS 13.7/12.4.8 ARM64, Mac OS and ARMv7 are not tested. Use this only for
educational or research purposes. MIT LICENSE
References
- PsychoBird's SearchKit, https://github.com/PsychoBird/RevelariOS
*/
#ifndef _VM_TOOL_H_
#define _VM_TOOL_H_
#include <mach-o/dyld.h>
#include <mach/mach.h>
#define MAX_DATA_LENGTH 128
#define FALSE 0
#define CHUNK_SIZE 0x10000
#define NOT_FOUND 0
#if __MACH__
#define LOG(...) NSLog(@__VA_ARGS__)
#else
#define LOG(...) printf(__VA_ARGS__)
#endif
typedef unsigned char byte_t;
typedef unsigned long long hex_t;
typedef unsigned long size_t;
typedef struct module {
/// The address/offset for this module
vm_address_t address;
/// The original data at address, this shouldn't be changed
byte_t original[MAX_DATA_LENGTH / 2];
/// Hex string for searching
char search[MAX_DATA_LENGTH];
/// Hex string to replace the original one, MUST be the same length as
/// search
char replace[MAX_DATA_LENGTH];
/// The offset to the true address
int offset;
} Module;
/// Check if the process has ASLR/offset
static bool hasASLR() {
const struct mach_header* mach;
mach = _dyld_get_image_header(0);
// check the flag here
return mach->flags & MH_PIE;
}
/// Return the offset of the process
static vm_address_t getOffset() {
return _dyld_get_image_vmaddr_slide(0);
}
/// Return the offset of the second item in the memory which can be used as the
/// end of the binary
static vm_address_t getEndAddress() {
return _dyld_get_image_vmaddr_slide(1);
}
/// Add offset to the address if available and return the address in memory
static vm_address_t memoryAddress(vm_address_t address) {
if (hasASLR())
return getOffset() + address;
return address;
}
/// Convert string to bytes
static byte_t* convert(char data[MAX_DATA_LENGTH]) {
LOG("[VM_TOOL] Converting '%s' to bytes\n", data);
size_t dataLen = strlen(data);
// The character count must be even and not over the max length
if (dataLen == 0 || dataLen > MAX_DATA_LENGTH || dataLen % 2 != 0) {
LOG("[VM_TOOL] Conversion failed or the string wasn't valid\n");
return NULL;
}
size_t hexLen = dataLen / 2;
// calloc crashes sometimes so use malloc instead
byte_t* hex = (byte_t*)malloc(sizeof(byte_t) * hexLen);
if (hex == NULL) {
LOG("[VM_TOOL] Out of memory\n");
return NULL;
}
// Join two char together and convert it to a byte
for (int i = 0; i < hexLen; i++) {
int index = i * 2;
char hexBytes[2] = {data[index], data[index + 1]};
hex[i] = (byte_t)strtol(hexBytes, NULL, 16);
}
return hex;
}
/// Write data to a module
/// m - module
/// replace - use the replace string if true and use the original if false
void vm_writeData(Module m, int replace) {
// No address found
if (m.address == NOT_FOUND)
return;
kern_return_t err;
byte_t* hex;
mach_port_t port = mach_task_self();
// Add offset to get the true address
vm_address_t address = memoryAddress(m.address);
// the size should be the string length / 2 and that's it, shared by both
size_t hexSize;
LOG("[VM_TOOL] Writing to 0x%lx (0x%lx)\n", address, m.address);
if (replace > 0) {
hexSize = strlen(m.replace) / 2;
// add offset only in replace mode
address += m.offset;
// Override to the value we want
hex = convert(m.replace);
if (hex == NULL)
return;
} else {
LOG("[VM_TOOL] Reverting to the original\n");
// original save everything
hexSize = strlen(m.search) / 2;
// Write back the original value or replacing the original
hex = m.original;
}
// set memory protection
err = vm_protect(port, address, hexSize, FALSE,
VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY);
if (err != KERN_SUCCESS)
return;
// write and remove protection
vm_write(port, address, (vm_offset_t)hex, (mach_msg_type_number_t)hexSize);
vm_protect(port, address, hexSize, FALSE, VM_PROT_READ | VM_PROT_EXECUTE);
// We only want to free up for replacing because we might use original
// later, it will be freed up when the program stops
if (replace > 0)
free(hex);
}
/// Read the original hex at the address for all modules
/// moduleList - an array of modules
/// size - the size of the module list
void vm_readData(Module* moduleList, int size) {
mach_port_t port = mach_task_self();
for (int i = 0; i < size; i++) {
Module* curr = moduleList + i;
// Check if address is valid
if (curr->address == 0) {
LOG("[VM_TOOL] Module %d's address is not set", i);
continue;
;
}
// Check if string is entered
size_t hexLen = strlen((char*)curr->search);
if (hexLen == 0 || hexLen % 2 != 0) {
LOG("[VM_TOOL] Module %d's original string is not valid", i);
continue;
}
vm_size_t bytes = hexLen / 2;
vm_address_t currAddress = memoryAddress(curr->address);
// Clear original
memset(curr->original, 0, hexLen);
// Read hex to original
kern_return_t err = vm_read_overwrite(
port, currAddress, bytes, (vm_offset_t)curr->original, &bytes);
if (err != KERN_SUCCESS) {
LOG("[VM_TOOL] Error while reading at address 0x%lx", currAddress);
return;
}
}
}
/// Free a byte list
/// list - the byte list
/// size - the size of list
static void freeByteList(byte_t** list, int size) {
if (list == NULL)
return;
// Free everything inside the list
for (int i = 0; i < size; i++)
free(list[i]);
// list is an array so no need to free
}
// TODO: this doesn't work if the value is in between segments
/// Search and set the address for all modules
/// moduleList - an array of modules
/// size - the size of the module list
/// binarySize - the size of the app binary or any number for the end address
void vm_searchData(Module* moduleList, int size, hex_t binarySize) {
// Convert search to actual hex values
byte_t* hex[size];
int errorCount = 0;
for (int i = 0; i < size; i++) {
hex[i] = convert(moduleList[i].search);
if (hex[i] == NULL)
errorCount++;
}
// Return early if everything are NULL
if (errorCount == size) {
freeByteList(hex, size);
return;
}
mach_port_t port = mach_task_self();
// Get offset, start and end addresses
vm_address_t aslr = getOffset();
vm_address_t offset = aslr + 0x100000000;
vm_address_t start = offset;
vm_address_t end = start + binarySize;
vm_address_t chunk = CHUNK_SIZE;
LOG("[VM_TOOL] Reading 0x%lx per chunk\n", chunk);
LOG("[VM_TOOL] Reading from 0x%lx to 0x%lx\n", start, end);
byte_t binary[CHUNK_SIZE] = {0};
// This tracks how many bytes we read
vm_size_t bytes = 0;
// Check how many addresses we have found, setting it to error count ignores
// error
int found = errorCount;
for (vm_address_t currAddress = start; currAddress < end;
currAddress += chunk) {
// Don't read more than the end address
vm_address_t diff = end - currAddress;
if (diff < chunk)
chunk = diff;
// Reset binary before reading
memset(&binary, 0, chunk);
vm_read_overwrite(port, currAddress, chunk, (vm_offset_t)&binary,
&bytes);
if (!bytes) {
LOG("[VM_TOOL] Error while reading at address 0x%lx", currAddress);
continue;
}
for (int i = 0; i < chunk; i++) {
// Check if anything matches with the list
for (int j = 0; j < size; j++) {
// This is the only way to read
Module* currModule = moduleList + j;
size_t hexLen = strlen(currModule->search) / 2;
byte_t* currHex = hex[j];
// Ignore incorrect hex
if (currHex == NULL)
continue;
// Check if it matches with the first
if (currHex[0] == binary[i]) {
currModule->original[0] = binary[i];
// only if it matches, increase k so using it as a counter
int k = 1;
while (k < hexLen) {
if (currHex[k] != binary[i + k])
break;
// Save the byte
currModule->original[k] = binary[i + k];
k++;
}
// found the address
if (k == hexLen) {
// A temp fix so that we won't find duplicate addresses
if (currModule->address == 0) {
// (memory address - aslr) gets the right address in
// IDA currAddress is the start of this chunk so we
// need to add the current offset
currModule->address = (currAddress + i) - aslr;
LOG("[VM_TOOL] Found module %d at 0x%lx\n", j,
currModule->address);
found++;
}
}
// Everything has found so return early
if (found == size) {
freeByteList(hex, size);
return;
}
}
}
}
}
freeByteList(hex, size);
return;
}
/// An experimental search data without passing in binarySize
void vm_searchDataEx(Module* moduleList, int size) {
vm_searchData(moduleList, size, getEndAddress() - getOffset());
}
#endif