summaryrefslogtreecommitdiff
path: root/informationals/teso-i0025.txt
diff options
context:
space:
mode:
Diffstat (limited to 'informationals/teso-i0025.txt')
-rw-r--r--informationals/teso-i0025.txt291
1 files changed, 291 insertions, 0 deletions
diff --git a/informationals/teso-i0025.txt b/informationals/teso-i0025.txt
new file mode 100644
index 0000000..4e73dac
--- /dev/null
+++ b/informationals/teso-i0025.txt
@@ -0,0 +1,291 @@
10025 2000/05/20 some spicy tricks for buffer overflow exploitation
2
3==== TESO Informational =======================================================
4This piece of information is to be kept confidential.
5===============================================================================
6
7Description ..........: some spicy tricks for buffer overflow exploitation
8Date .................: 2000/05/20 13:00
9Author ...............: scut
10Publicity level ......: some known, some possibly known, some unknown
11Affected .............: buffer overflow exploits
12Type of entity .......: program behaviour
13Type of discovery ....: useful information
14Severity/Importance ..: low
15Found by .............: various people, scut, skyper, duke
16
17===============================================================================
18
19Although buffer overflows are kinda old now, there are still a lot of things
20to discover. Here are a few tricks I noticed when playing around with
21exploitation in the last two years.
22
23
24trick I. "enlarging exploitation space"
25
26 This trick is known and although it is very helpful it is seldomly used.
27 Imagine a relativly small buffer of say 128 bytes, followed by the saved
28 framepointer and the return address. In normal remote exploitation you
29 would use a position independant portshell code, which may be around 125
30 bytes for the x86 architecture. Since you have 128+4 (132) bytes before
31 the return address you can prepend the shellcode with some 7 bytes of
32 NOP instructions. So your return address has to hit the NOP space in a
33 7 byte long frame, which requires exact knowledge about the target binary.
34
35 However in case you're allowed to store more data you can use a small trick
36 to enlarge your offset frame. Say you can write 256 bytes at max to the
37 target buffer. Before the trick your target buffer looks like:
38
39 <7 NOP><shellcode><retaddr>
40
41 Using the trick it looks like:
42
43 <130 NOP><jmp-ahead><retaddr><3 NOP><shellcode>
44
45 Now you have a target frame of 133 bytes, and the offset is way more
46 reliable. The <jmp-ahead> is a two byte instruction which does nothing but
47 jumps 4 bytes ahead, so in case you hit the 130 bytes NOP space with your
48 offset you don't "execute" the return address.
49
50
51trick II. "lower stack space page fault"
52
53 Sometimes there is code like this:
54
55 void func (char *foo) {
56 char * moo = foo;
57 char buffer[256];
58
59 strcpy (buffer, foo);
60 moo += strlen (foo);
61 if (*moo != '\0')
62 exit (1);
63 }
64
65 Although this code snippet makes no sense it effectivly denies a simple
66 buffer overflow exploitation. However, clever people might overwrite the
67 `moo' pointer with a valid pointer that points to a bunch of NUL bytes.
68 But how to get a large space fully populated by NUL bytes ? On the x86
69 architecture (and most other architectures) you can just use a lower stack
70 page. Since it will be unused when the pointer is first used (by the *moo),
71 this will create a page fault in the kernel, and the kernel will allocate
72 us a new page. Since the kernel ensures you cannot read other processes
73 memory it overwrites the page with NUL bytes. Bingo.
74 Just overwrite this example with:
75
76 <NOP><shellcode><0xbfffd010><retaddr>
77
78 The 0xbfffd010 will be the new moo pointer.
79
80
81trick III. "incremental NUL byte creation"
82
83 On some big endian architectures you're faced with the problem of creating
84 a 64 bit pointer as return address, while there is a no way to insert NUL
85 bytes. This is nasty if the upper bits of the address have to be \x00.
86
87 In most cases this renders a simple exploitation undoable, except for one
88 case, where some special code is used.
89 Imagine something like:
90
91 void func (void) {
92 char buffer[256];
93
94 while (gets (buffer) != NULL)
95 parseline (buffer);
96 }
97
98 Normally the stack space looks like
99
100 <buffer><framepointer><retaddr>
101
102 We assume that both the framepointer and the retaddr are 64 bit pointers,
103 stored big endian and the upper 32 bits have to be zero'ed out (this is
104 the case on Irix for example). Now we have a problem since we cannot
105 store NUL bytes but are required to in order to overwrite the retaddr with
106 a valid address that points into our code. But we can exploit the
107 incremental behaviour, by using something like this:
108
109 <264 buffer with NOPS and shellcode><4 dummy bytes><lower 32 bits retaddr>
110 <264 buffer with NOPS and shellcode><3 dummy bytes>
111 <264 buffer with NOPS and shellcode><2 dummy bytes>
112 <264 buffer with NOPS and shellcode><1 dummy bytes>
113 <264 buffer with NOPS and shellcode><0 dummy bytes>
114
115 We overflow the buffer five times, the first time we write the correct
116 return address in the lower 32 bits, but set the upper 32 bits to something
117 non-NUL. Then we overflow four times more to store a NUL byte each time,
118 effectivly resulting in a buffer layout like:
119
120 <264 buffer with NOPS and shellcode>0x000000007fff2058
121
122
123trick IV. "type issues and funky long strings"
124
125 No length can be negative. Every physics knows this, but some people, like
126 l0pht don't know this ;-). So all string functions take unsigned arguments
127 if a length is required, as in strncat and strncpy. However, if the user
128 has a way to supply the length argument you can render the length protection
129 unuseable. Though this is normally not the case, there are some very nasty
130 ways this can happen. An example snippet may look like:
131
132 void func(char *dnslabel) {
133 char buffer[256];
134 char * indx = dnslabel;
135 int count;
136
137 count = *indx;
138 buffer[0] = '\x00';
139
140 while (count != 0 && (count + strlen (buffer)) < sizeof (buffer) - 1) {
141 strncat (buffer, indx, count);
142 indx += count;
143 count = *indx;
144 }
145 }
146
147 Although length checking and strn* functions were used this is exploitable,
148 because count can contain negative values. (char) is a signed type and will
149 be sign extended to a larger type, such as (int). Therefore the
150 "count = *indx;" statement can assign count values from -128 to +127. This
151 way the later check can be circumvented. For the strncat function the count
152 value will be used as if it would be assigned to an unsigned int variable,
153 resulting in a very large number (on 32 bit systems -1 will be 2^32-1).
154 This complicated scheme has many little mods, and you can way too fast
155 assume a fix might be just to put unsigned type modifiers in front of the
156 count variables, but it remains exploitable.
157
158 Every function that takes a size_t argument expects an unsigned value.
159 However if the program can be tricked into supplying a negative (signed)
160 number as size argument this value will be casted to unsigned, hence
161 resulting in a very large number. This is useful for any strn* function,
162 where the source string is user supplied.
163
164
165trick V. "overflow too short"
166
167 Often your reachability is limited when overwriting the target buffer,
168 sometimes it does not even reach the return address or only parts of it.
169 There are several known methods to help you in this situation, most famous
170 the "one byte framepointer overflow". However, in case you can overwrite
171 pointers that are later write-accessed you can possibly write to an arbitrary
172 location in the process memory. This has been well demonstrated by bulba and
173 kiler of lam3rz in their phrack 56 article, however under the scope of
174 circumventing StackShield and StackGuard protections. Also you can utilize
175 heap overflow like techniques in the local variable stack space you can
176 overwrite to jump to your code if possible. Also, if you just can't reach
177 the framepointer or return address, take a look at the local variables
178 inbetween your buffer and the return address. Maybe you can first go for
179 them allowing you to later overwrite the return address.
180
181
182trick VI. "s[n]printf issues"
183
184 Sometimes programmers make a very nasty mistake when dealing with user
185 supplied data and sprintf (or snprintf) type of functions. They directly
186 allow the user to put format characters into the string. This looks like
187
188 void func (char *str, char *password) {
189 char msg[1024];
190
191 snprintf (msg, sizeof (msg) - 1, str);
192 ...
193 }
194
195 If the user can supply the string pointed to by str he can do some nasty
196 things:
197
198 - it may be possible to obtain otherwise secret information, if the user
199 has the possibility to obtain the msg string later. This can be done by
200 inserting %p and %s format characters, which will pull the stack
201 parameters into the output string. Although a segfault might be the
202 direct result, it may be useful.
203
204 - exploitation ! "how ?" you might ask, and yes, it's really tricky. here
205 is a snippet from the sprintf manpage:
206
207 n The number of characters written so far is stored
208 into the integer indicated by the ``int *'' (or
209 variant) pointer argument. No argument is con-
210 verted.
211
212 So you can write a small 32 bit number (or 64 bit, depending on the
213 architecture) to a pointer which is on the stack. So if the stack might
214 look like this (in a suid local application):
215
216 32 bits (void *) <some pointer>
217 32 bits (char *) <user supplied string>
218
219 You can use a format string like "0123%n" to write 0x00000004 to the
220 location where <some pointer> points to. This requires a very special
221 context for real exploitation, but it is worth it. Also, if you have
222 somehow the method to set the pointer arbitrarily where the integer
223 is stored you can set it on an uneven boundary to only partially over-
224 write things (like the frame pointer), which may be interesting.
225
226 (update: this can be done very effictivly using the technique described
227 in TESO Informational #27. Also it is very helpful if you can see the
228 printf'd string as response before actually exploiting.)
229
230 - enlarging the exploitation buffer, using something like this:
231 "%8d%8d%8d%8d<buffer>", so you'll get 8*4 = 32 bytes in front of the
232 buffer, only wasting 12 bytes. Though this destroys the stack, but since
233 you're going to destroy it anyway, this doesn't matter :-)
234 Also try "%-200d", which will create a 200 byte long space right-padded
235 string, or "%.100d" which will create up to 100 zero characters. There
236 is an overflow in the libc where "%.1200d" creates a segfault. This isn't
237 the case for the space padding.
238
239
240trick VII. "sizeof (string) and i'm safe, right ?"
241
242 Often people think if they use sizeof() stuff in string functions they are
243 safe. Most of the times this is true, but there are differences among the
244 str*n* functions that a programmer might oversee.
245
246 strncpy (target, source, sizeof (target)) is safe, as we all knew, but
247 what about
248
249 strcpy (target, "");
250 strncat (target, source, sizeof (target));
251
252 This is not safe, since strncat appends sizeof (target) characters plus a
253 trailing NUL character. If you're unsure about whether some string function
254 terminates on the sizeof's byte or at the sizeof's - 1 byte, just write
255 a small test program to check out. Most programmers are unsure about the
256 behaviour of the string functions and may introduce exploitable one byte
257 overflows this way.
258
259
260trick VIII. "adjascent buffers"
261
262 This was known before the article in Phrack 56, but the article offers a
263 great in-depth discussion about this problem. Basically it works like:
264
265 void func (char *foo) {
266 char buf[32];
267 char buf1[16];
268 char buf2[16];
269
270 buf1[0] = buf2[0] = '\x00';
271 strncat (buf2, foo, sizeof (buf2));
272 strcpy (buf1, "blablabla");
273
274 buf[0] = '\x00';
275 strcat (buf, buf1);
276 strcat (buf, buf2);
277 }
278
279 The programmer assumes that proper bound checking has been done before and
280 later uses direct strcat's to construct a string in buffer "buf". But the
281 strcat (buf, buf2) may very well add more then sizeof (buf2) characters,
282 since buf2 can be non-NUL terminated. This is because of the strncat, as
283 explained in trick VII. You can overwrite the lower three bytes of the
284 frame pointer in this example code.
285
286
287If you know some tricks not listed here, please mail them to me
288(scut@nb.in-berlin.de).
289
290===============================================================================
291