-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathos_win.c
More file actions
369 lines (308 loc) · 8.22 KB
/
os_win.c
File metadata and controls
369 lines (308 loc) · 8.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
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
/* SPDX-License-Identifier: MIT
* Copyright(c) 2023 Darek Stojaczyk
*/
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <tlhelp32.h>
#include <windows.h>
#include <winnt.h>
#include "patchmem.h"
#include "patchmem_internal.h"
/**
* A piece of x86_32 stack containing current EBP and the address
* the current call instruction will return to.
*/
struct stack_frame {
void *ebp;
void *ret_addr;
};
/**
* Stack memory area. Used for call stack validation.
*/
struct stack_area {
uintptr_t end; /**< lower address */
uintptr_t start; /**< higher address */
};
/** Check if stack area contains given address. */
static bool
stack_area_contains(struct stack_area *stack_area, uintptr_t addr)
{
return addr > stack_area->end && addr <= stack_area->start;
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
/** Get stack frame of the calling function. */
static struct stack_frame *__attribute__((naked)) stack_frame_get(void)
{
__asm__("lea eax, [esp - 4]; ret");
}
/** Get stack memory area of the calling function. */
static struct stack_area
stack_area_get(void)
{
NT_TIB *pTIB = (NT_TIB *)NtCurrentTeb();
return (struct stack_area){ (uintptr_t)pTIB->StackLimit,
(uintptr_t)pTIB->StackBase };
}
#pragma GCC diagnostic pop
/** Get stack memory area for the provided Windows thread HANDLE and
* CONTEXT. */
static struct stack_area
stack_area_get_by_thr(HANDLE thread, CONTEXT *ctx)
{
LDT_ENTRY ldt_entry = { 0 };
BOOL ok;
ok = GetThreadSelectorEntry(thread, ctx->SegFs, &ldt_entry);
if (!ok) {
return (struct stack_area){ 0, 0 };
}
NT_TIB *pTIB =
(void *)(ldt_entry.BaseLow | (ldt_entry.HighWord.Bytes.BaseMid << 0x10) |
(ldt_entry.HighWord.Bytes.BaseHi << 0x18));
return (struct stack_area){ (uintptr_t)pTIB->StackLimit,
(uintptr_t)pTIB->StackBase };
}
static bool
is_valid_stack_frame(struct stack_frame *frame, struct stack_area *stack_area)
{
MEMORY_BASIC_INFORMATION info;
SIZE_T ret;
ret = VirtualQuery(frame->ret_addr, &info, sizeof(info));
if (ret == 0 || ret < offsetof(MEMORY_BASIC_INFORMATION, Protect)) {
return false;
}
if (info.Protect != PAGE_EXECUTE && info.Protect != PAGE_EXECUTE_READ &&
info.Protect != PAGE_EXECUTE_READWRITE &&
info.Protect != PAGE_EXECUTE_WRITECOPY) {
return false;
}
/* if EBP currently has a rogue value we may detect it here by checking
* against the stack region bounds. There can be a still a pointer to
* somewhere inside the stack that is not a stack frame. And if it passes
* the further checks, we might return it as a "valid" stack frame, which
* can be far off from reality. We could go through the trouble of
* checking if the return pointer is preceeded by a CALL instruction,
* which should practically give us a 100% confidence in a valid/invalid
* stack frame. But (subjectively) this wasn't needed so far. If some of
* the program doesn't keep the frame pointer we can't get the complete
* call stack anyway.
*/
if (!stack_area_contains(stack_area, (uintptr_t)frame->ebp)) {
return false;
}
return true;
}
/** Get the next stack frame, deeper in the call stack. */
static struct stack_frame *
stack_frame_next(struct stack_frame *frame, struct stack_area *stack_area)
{
struct stack_frame *next = frame->ebp;
return is_valid_stack_frame(next, stack_area) ? next : NULL;
}
/** Get stack frame from the provided Windows thread HANDLE and CONTEXT. */
static struct stack_frame *
stack_frame_get_by_thr(HANDLE thread, CONTEXT *ctx, struct stack_area *stack_area)
{
(void)thread;
if (!stack_area_contains(stack_area, (uintptr_t)ctx->Ebp)) {
return NULL;
}
struct stack_frame *frame = (void *)ctx->Ebp;
return is_valid_stack_frame(frame, stack_area) ? frame : NULL;
}
#define MEM_PROT_WINSPECIFIC (1UL << 31)
static unsigned
win_flags_to_internal(DWORD flags)
{
return flags | MEM_PROT_WINSPECIFIC;
}
static DWORD
internal_flags_to_win(unsigned flags)
{
if (flags & MEM_PROT_WINSPECIFIC) {
return flags & ~MEM_PROT_WINSPECIFIC;
}
switch (flags) {
case MEM_PROT_READ:
return PAGE_READONLY;
case MEM_PROT_READ | MEM_PROT_WRITE:
return PAGE_READWRITE;
case MEM_PROT_READ | MEM_PROT_WRITE | MEM_PROT_EXEC:
return PAGE_EXECUTE_READWRITE;
case MEM_PROT_READ | MEM_PROT_EXEC:
return PAGE_EXECUTE_READ;
case MEM_PROT_NONE:
return PAGE_NOACCESS;
}
assert(false); // invalid flags combination
return PAGE_NOACCESS;
}
void *
_os_alloc(int size)
{
if (size == 0) {
return NULL;
}
size = (size + 0xFFF) & ~0xFFF;
return VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
}
void
_os_free(void *mem, int size)
{
VirtualFree(mem, size, MEM_RELEASE);
}
int
_os_protect(void *addr, size_t size, unsigned flags, unsigned *prev_flags)
{
DWORD prevProt;
DWORD prot = internal_flags_to_win(flags);
BOOL ok;
ok = VirtualProtect(addr, size, prot, &prevProt);
if (!ok) {
return -GetLastError();
}
if (prev_flags) {
*prev_flags = win_flags_to_internal(prevProt);
}
return 0;
}
int
_os_static_init(void)
{
/* nothing to do */
return 0;
}
void
_os_static_persist(void)
{
HMODULE hm;
BOOL ok;
ok = GetModuleHandleEx(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN,
(void *)_os_static_persist, &hm);
if (!ok) {
assert(false);
}
}
static bool
verify_safe_stack_strace(HANDLE thread)
{
CONTEXT ctx __attribute__((aligned(16))) = { 0 };
BOOL ok;
ctx.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS;
ok = GetThreadContext(thread, &ctx);
if (!ok) {
fprintf(stderr, "GetThreadContext() failed: %lu\n", GetLastError());
return false;
}
uintptr_t eip = ctx.Eip;
if (_patch_mem_check_addr_patched(eip)) {
return false;
}
struct stack_area stack_area = stack_area_get_by_thr(thread, &ctx);
struct stack_frame *frame = stack_frame_get_by_thr(thread, &ctx, &stack_area);
if (frame == NULL) {
/* we most likely ended up in a context with -fomit-frame-pointer */
return false;
}
while (frame != NULL) {
HMODULE hm;
ok = GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
frame->ret_addr, &hm);
if (!ok) {
assert(false);
}
if ((struct patch_mem_lib_handle *)hm == _patch_mem_get_libhandle()) {
return false;
}
frame = stack_frame_next(frame, &stack_area);
}
return true;
}
void
_os_safely_suspend_all_threads(void)
{
DWORD thisproc_id = GetCurrentProcessId();
DWORD thisthrd_id = GetCurrentThreadId();
HANDLE h;
THREADENTRY32 te;
h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (h == INVALID_HANDLE_VALUE) {
return;
}
te.dwSize = sizeof(te);
if (!Thread32First(h, &te)) {
CloseHandle(h);
return;
}
do {
if (te.dwSize < FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) +
sizeof(te.th32OwnerProcessID)) {
continue;
}
if (te.th32OwnerProcessID != thisproc_id ||
te.th32ThreadID == thisthrd_id) {
continue;
}
HANDLE thread = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
if (thread != NULL) {
size_t i = 1, max_attempts = 30;
while (i < max_attempts) {
DWORD rc = SuspendThread(thread);
/* the thread may be executing some un-suspendable
* critical section */
if (rc != -1) {
if (verify_safe_stack_strace(thread)) {
break;
}
ResumeThread(thread);
}
Sleep(i);
i++;
}
CloseHandle(thread);
if (i == max_attempts) {
assert(false);
return;
}
}
te.dwSize = sizeof(te);
} while (Thread32Next(h, &te));
CloseHandle(h);
}
void
_os_resume_all_threads(void)
{
DWORD thisproc_id = GetCurrentProcessId();
DWORD thisthrd_id = GetCurrentThreadId();
HANDLE h;
THREADENTRY32 te;
h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (h == INVALID_HANDLE_VALUE) {
return;
}
te.dwSize = sizeof(te);
if (!Thread32First(h, &te)) {
CloseHandle(h);
return;
}
do {
if (te.dwSize < FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) +
sizeof(te.th32OwnerProcessID)) {
continue;
}
if (te.th32OwnerProcessID != thisproc_id ||
te.th32ThreadID == thisthrd_id) {
continue;
}
HANDLE thread = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
if (thread != NULL) {
ResumeThread(thread);
CloseHandle(thread);
}
te.dwSize = sizeof(te);
} while (Thread32Next(h, &te));
CloseHandle(h);
}