-
Notifications
You must be signed in to change notification settings - Fork 1
/
patch_enum_syscalls.c
418 lines (339 loc) · 14.5 KB
/
patch_enum_syscalls.c
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
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
static _inline void real_dprintf2(char *format, ...)
{
va_list args;
char buffer[1024];
size_t len;
_snprintf_s(buffer, sizeof(buffer), sizeof(buffer)-1, "[%x] ", GetCurrentThreadId());
len = strlen(buffer);
va_start(args, format);
vsnprintf_s(buffer + len, sizeof(buffer)-len, sizeof(buffer)-len - 3, format, args);
strcat_s(buffer, sizeof(buffer), "\r\n");
OutputDebugStringA(buffer);
va_end(args);
}
//#define DEBUGTRACE 1
#ifdef DEBUGTRACE
#define dprintf2(...) real_dprintf2(__VA_ARGS__)
#define dprintf(...) real_dprintf2(__VA_ARGS__)
#if DEBUGTRACE == 1
#define vdprintf dprintf
#else
#define vdprintf(...) do{}while(0);
#endif
#else
#define dprintf(...) do{}while(0);
#define dprintf2(...) do{}while(0);
#define vdprintf(...) do{}while(0);
#endif
static const unsigned int SYSCALL_ID_OFFSET = 4; // nb of bytes after a function's address that represents the syscall's id
// the strings below are written this way to prevent them from being stored in a different section of the executable.
/*static char NtCreateFile_api[] = { 'Z','w','C','r','e','a','t','e','F','i','l','e',0 };
static char NtAllocateVirtualMemory_api[] = { 'Z','w','A','l','l','o','c','a','t','e','V','i','r','t','u','a','l','M','e','m','o','r','y',0 };
static char NtAlpcConnectPort_api[] = { 'Z','w','A','l','p','c','C','o','n','n','e','c','t','P','o','r','t',0 };
static char NtAlpcConnectPortEx_api[] = { 'Z','w','A','l','p','c','C','o','n','n','e','c','t','P','o','r','t','E','x',0 };
static char NtAlpcSendWaitReceivePort_api[] = { 'Z','w','A','l','p','c','S','e','n','d','W','a','i','t','R','e','c','e','i','v','e','P','o','r','t',0 };
static char NtConnectPort_api[] = { 'Z','w','C','o','n','n','e','c','t','P','o','r','t',0 };
static char NtCreateSection_api[] = { 'Z','w','C','r','e','a','t','e','S','e','c','t','i','o','n',0 };
static char NtCreateThread_api[] = { 'Z','w','C','r','e','a','t','e','T','h','r','e','a','d',0 };
static char NtMapViewOfSection_api[] = { 'Z','w','M','a','p','V','i','e','w','O','f','S','e','c','t','i','o','n',0 };
static char NtProtectVirtualMemory_api[] = { 'Z','w','P','r','o','t','e','c','t','V','i','r','t','u','a','l','M','e','m','o','r','y',0 };
static char NtQueueApcThread_api[] = { 'Z','w','Q','u','e','u','e','A','p','c','T','h','r','e','a','d',0 };
static char NtRequestWaitReplyPort_api[] = { 'Z','w','R','e','q','u','e','s','t','W','a','i','t','R','e','p','l','y','P','o','r','t',0 };
static char NtResumeProcess_api[] = { 'Z','w','R','e','s','u','m','e','P','r','o','c','e','s','s',0 };
static char NtResumeThread_api[] = { 'Z','w','R','e','s','u','m','e','T','h','r','e','a','d',0 };
static char NtSecureConnectPort_api[] = { 'Z','w','S','e','c','u','r','e','C','o','n','n','e','c','t','P','o','r','t',0 };
static char NtSetContextThread_api[] = { 'Z','w','S','e','t','C','o','n','t','e','x','t','T','h','r','e','a','d',0 };
static char NtSetInformationThread_api[] = { 'Z','w','S','e','t','I','n','f','o','r','m','a','t','i','o','n','T','h','r','e','a','d',0 };
static char NtSuspendProcess_api[] = { 'Z','w','S','u','s','p','e','n','d','P','r','o','c','e','s','s',0 };
static char NtSuspendThread_api[] = { 'Z','w','S','u','s','p','e','n','d','T','h','r','e','a','d',0 };
static char NtCreateProcess_api[] = { 'Z','w','C','r','e','a','t','e','P','r','o','c','e','s','s',0 };
static char NtCreateProcessEx_api[] = { 'Z','w','C','r','e','a','t','e','P','r','o','c','e','s','s','E','x',0 };
static char NtCreateUserProcess_api[] = { 'Z','w','C','r','e','a','t','e','U','s','e','r','P','r','o','c','e','s','s',0 };
static char NtFreeVirtualMemory_api[] = { 'Z','w','F','r','e','e','V','i','r','t','u','a','l','M','e','m','o','r','y',0 };
static char NtQueueApcThreadEx_api[] = { 'Z','w','Q','u','e','u','e','A','p','c','T','h','r','e','a','d','E','x',0 };
static char NtReadVirtualMemory_api[] = { 'Z','w','R','e','a','d','V','i','r','t','u','a','l','M','e','m','o','r','y',0 };
static char NtSetInformationProcess_api[] = { 'Z','w','S','e','t','I','n','f','o','r','m','a','t','i','o','n','P','r','o','c','e','s','s',0 };
static char NtUnmapViewOfSection_api[] = { 'Z','w','U','n','m','a','p','V','i','e','w','O','f','S','e','c','t','i','o','n',0 };
*/
static char NtWriteVirtualMemory_api[] = { 'Z','w','W','r','i','t','e','V','i','r','t','u','a','l','M','e','m','o','r','y',0 };
static char NtCreateThreadEx_api[] = { 'Z','w','C','r','e','a','t','e','T','h','r','e','a','d','E','x',0 };
//static char NtWriteVirtualMemory_api[] = { 'a','r','a',0 };
//static char NtCreateThreadEx_api[] = { 'a','x',0 };
// store locations of strings on the stack
static const char* __API_names[] = {
/*NtCreateFile_api,
NtAllocateVirtualMemory_api,
NtAlpcConnectPort_api,
NtAlpcConnectPortEx_api,
NtAlpcSendWaitReceivePort_api,
NtConnectPort_api,
NtCreateSection_api,
NtCreateThread_api,*/
NtCreateThreadEx_api,
NtWriteVirtualMemory_api
/*NtMapViewOfSection_api,
NtProtectVirtualMemory_api,
NtQueueApcThread_api,
NtRequestWaitReplyPort_api,
NtResumeProcess_api,
NtResumeThread_api,
NtSecureConnectPort_api,
NtSetContextThread_api,
NtSetInformationThread_api,
NtSuspendProcess_api,
NtSuspendThread_api,*/
/*NtWriteVirtualMemory_api,
NtCreateProcess_api,
NtCreateProcessEx_api,
NtCreateUserProcess_api,
NtFreeVirtualMemory_api,
NtQueueApcThreadEx_api,
NtReadVirtualMemory_api,
NtSetInformationProcess_api,
NtUnmapViewOfSection_api*/
};
// groups useful infos about a given syscall
typedef struct _syscall_info {
const char *name; // API name
unsigned int id; // syscall ID: value passed to EAX in ntdll before issuing the 'syscall' instruction
} syscall_info;
static syscall_info* syscall_infos; // collection of syscalls
static BOOL syscalls_initialized = FALSE; // tracks initialization state
/*
* compares two strings for strict equality
*
* \param string1 a string
* \param string2 another string
* \return 1 if the strings are equal, 0 otherwise.
*/
static int strequal(const char* string1, const char* string2)
{
while (*string1 && *string2)
if (*string1++ != *string2++)
return 0;
if (!*string1 && !*string2)
return 1;
return 0;
}
/*
* allows to find a syscall by its name. Precondition: syscalls have been inited
*
* \param name the name of the API
* \return infos about a syscall, such as its ID
*/
static syscall_info* get_syscall_by_name(const char* name)
{
dprintf2("[nteav] get_syscall_by_name");
SIZE_T nb_api_names = sizeof(__API_names) / sizeof(__API_names[0]);
for (unsigned int i = 0; i < nb_api_names; i++)
{
if (strequal(syscall_infos[i].name, (const char*)name))
{
dprintf2("[nteav] syscall found");
return &(syscall_infos[i]);
}
}
dprintf2("[nteav] syscall not found !");
return NULL;
}
//delete me
static void print_syscall(syscall_info* sinfo)
{
dprintf2("[nteav] print_syscall");
//convert to little endian (byte swap)
unsigned char lvalue = sinfo->id & 255;
unsigned char hvalue = (sinfo->id / 256) & 255;
dprintf2("%08X %s low = %02x high = %02x disass = ", sinfo->id, sinfo->name, lvalue, hvalue);
unsigned char syscall_shellcode[] = {
0x4c,0x8b,0xd1,
0xb8,0x18,0x00,0x00,0x00,
0x0f,0x05,
0xc3
};
// update the shellcode with the correct syscall id
syscall_shellcode[4] = lvalue;
syscall_shellcode[5] = hvalue;
// print the shellcode
for (int i = 0; i < sizeof(syscall_shellcode) / sizeof(unsigned char); i++) {
dprintf2(" %02x", syscall_shellcode[i]);
}
dprintf2("\n");
}
/*
* see if confused about RVAs, VAs and file offsets https://github.com/deptofdefense/SalSA/wiki/PE-File-Format
* in short, it is necessary to iterate over every section in the PE to find the one where the RVA lives.
* Then, the RVA minus the section's virtual address allows to find an offset that can be added to the section's offset on disk...
* the result is a new offset relative to the start of the file.
*/
static DWORD rva_to_file_offset(PIMAGE_SECTION_HEADER sections_headers, DWORD nb_sections, DWORD file_size, DWORD rva)
{
PIMAGE_SECTION_HEADER current_section_header = sections_headers;
DWORD i;
for (i = 0; i < nb_sections; i++, current_section_header++)
{
if (rva >= sections_headers->VirtualAddress && rva <= (current_section_header->VirtualAddress + current_section_header->SizeOfRawData))
{
DWORD delta = rva - current_section_header->VirtualAddress;
return current_section_header->PointerToRawData + delta;;
}
}
return 0;
}
/*
* read a file from disk
*
* \param file_path file location on disk
* \param file_buffer result buffer pointing to the file's content
* \param file_size result size of the file
* \return TRUE if the file could be read
*/
static BOOL read_input_file(LPCSTR file_path, LPBYTE *file_buffer, unsigned long *file_size)
{
dprintf2("[nteav] read_input_file");
HANDLE handle_file_in = CreateFileA(file_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
unsigned long bytes_read = 0;
if (handle_file_in == INVALID_HANDLE_VALUE)
{
dprintf2("[!] Invalid handle value for target file, error = %d\n", GetLastError());
return FALSE;
}
dprintf2("[nteav] open success");
*file_size = GetFileSize(handle_file_in, NULL);
*file_buffer = (LPBYTE)malloc(*file_size);
ReadFile(handle_file_in, *file_buffer, *file_size, &bytes_read, 0);
CloseHandle(handle_file_in);
if (bytes_read != *file_size)
{
dprintf2("[!] Problem encountered while loading the target file in memory\n");
VirtualFree(*file_buffer, bytes_read, MEM_RELEASE);
return FALSE;
}
dprintf2("[nteav] read success");
return TRUE;
}
/*
* Locate the Export Directory in a given PE file
*
* \param file_buffer content of the PE file
* \param file_size size of the PE file
* \param first_section pointer to the first section's header in the PE file
* \param nb_sections number of sections in the PE file
* \return a pointer to the export directory
*/
static PIMAGE_EXPORT_DIRECTORY get_export_dir(LPBYTE file_buffer, DWORD file_size, PIMAGE_SECTION_HEADER *first_section, DWORD* nb_sections)
{
dprintf2("[nteav] get_export_dir");
PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER)file_buffer;
if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) {
dprintf2("[!] Bad PE\n");
}
dprintf2("[nteav] good PE");
PIMAGE_NT_HEADERS nt_header = (PIMAGE_NT_HEADERS)(file_buffer + dos_header->e_lfanew);
*nb_sections = nt_header->FileHeader.NumberOfSections;
*first_section = (PIMAGE_SECTION_HEADER) (file_buffer + dos_header->e_lfanew +sizeof(IMAGE_NT_HEADERS));
DWORD export_rva = nt_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
DWORD export_file_offset = rva_to_file_offset(*first_section, *nb_sections, file_size, export_rva);
return (PIMAGE_EXPORT_DIRECTORY)(file_buffer + export_file_offset);
}
/*
* parses ntdll and populates the syscall_info structures
*
*/
static void init_syscalls_ids()
{
PBYTE file_buffer = NULL; //ntdll's content
DWORD file_size = 0; //size of the dll
dprintf2("[nteav] init_syscalls_ids");
if (!read_input_file("c:\\windows\\system32\\ntdll.dll", &file_buffer, &file_size))
{
dprintf2("Error reading file.\n");
return;
}
dprintf2("[nteav] read success");
PIMAGE_SECTION_HEADER first_section; // first section's header, points to an array of sections headers.
DWORD nb_sections = 0; // number of sections in ntdll
PIMAGE_EXPORT_DIRECTORY export_directory = get_export_dir(file_buffer, file_size, &first_section, &nb_sections);
PDWORD functions_address = (PDWORD)(file_buffer + rva_to_file_offset(first_section, nb_sections, file_size, export_directory->AddressOfFunctions));
PWORD ordinals_address = (PWORD)(file_buffer + rva_to_file_offset(first_section, nb_sections, file_size, export_directory->AddressOfNameOrdinals));
PDWORD names_address = (PDWORD)(file_buffer + rva_to_file_offset(first_section, nb_sections, file_size, export_directory->AddressOfNames));
dprintf2("Id Name Low High Disass\n");
dprintf2("--------------------------\n");
SIZE_T nb_api_names = sizeof(__API_names) / sizeof(__API_names[0]);
syscall_infos = (syscall_info*)malloc(nb_api_names * sizeof(syscall_info));
for (DWORD i = 0; i < export_directory->NumberOfNames; ++i)
{
DWORD rva_api = functions_address[ordinals_address[i]];
DWORD file_offset_name = rva_to_file_offset(first_section, nb_sections, file_size, names_address[i]);
unsigned char* name = file_buffer + file_offset_name;
/*
windbg > u NtCreateFile
ntdll!NtCreateFile:
00007ffa`202458e0 4c8bd1 mov r10,rcx
00007ffa`202458e3 b855000000 mov eax,55h
00007ffa`202458e8 f604250803fe7f01 test byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
00007ffa`202458f0 7503 jne ntdll!NtCreateFile+0x15 (00007ffa`202458f5)
00007ffa`202458f2 0f05 syscall
00007ffa`202458f4 c3 ret
00007ffa`202458f5 cd2e int 2Eh
00007ffa`202458f7 c3 ret
*/
// filter everything except Zw* API functions
if (!(*name == 'Z' && *(name + 1) == 'w'))
continue;
PBYTE procedure_address = file_buffer + rva_to_file_offset(first_section, nb_sections, file_size, rva_api);
// get the syscall id
DWORD syscall_id = *(DWORD *)(procedure_address + SYSCALL_ID_OFFSET);
// filter according to suspicious API calls
for (unsigned int i = 0; i < nb_api_names; i++)
{
if (strequal(__API_names[i], (const char*)name))
{
syscall_infos[i].name = __API_names[i];
syscall_infos[i].id = syscall_id;
print_syscall(&(syscall_infos[i]));
break;
}
}
}
VirtualFree((LPVOID)file_buffer, file_size, MEM_RELEASE);
}
/*
* get shellcode for a given syscall (native Zw/Nt API)
*
* \param name the syscall name (such as ZwCreateFile)
* \return a buffer containing the shellcode, ready to be executed.
*/
static LPVOID get_shellcode_buffer(const char* name)
{
dprintf2("[nteav] get_shellcode_buffer");
if (!syscalls_initialized)
{
syscalls_initialized = TRUE;
init_syscalls_ids();
}
dprintf2("[nteav] ready");
syscall_info* syscall = get_syscall_by_name(name);
if (syscall == NULL)
{
dprintf2("[nteav] No syscall found\n");
return NULL;
}
unsigned int syscall_id = syscall->id;
unsigned char syscall_shellcode[] = {
0x4c,0x8b,0xd1,
0xb8,0x18,0x00,0x00,0x00,
0x0f,0x05,
//0xb8, 0x01, 0x00, 0x00, 0x00,
0xc3
};
//convert to little endian (byte swap)
unsigned char lvalue = syscall_id & 255;
unsigned char hvalue = (syscall_id / 256) & 255;
// update the shellcode with the correct syscall id
syscall_shellcode[4] = lvalue;
syscall_shellcode[5] = hvalue;
dprintf2("[nteav] generating shellcode");
void *qapcmem = VirtualAlloc(0, sizeof(syscall_shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(qapcmem, syscall_shellcode, sizeof(syscall_shellcode));
return qapcmem;
}