diff options
Diffstat (limited to 'informationals/teso-i0025.txt')
| -rw-r--r-- | informationals/teso-i0025.txt | 291 |
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 @@ | |||
| 1 | 0025 2000/05/20 some spicy tricks for buffer overflow exploitation | ||
| 2 | |||
| 3 | ==== TESO Informational ======================================================= | ||
| 4 | This piece of information is to be kept confidential. | ||
| 5 | =============================================================================== | ||
| 6 | |||
| 7 | Description ..........: some spicy tricks for buffer overflow exploitation | ||
| 8 | Date .................: 2000/05/20 13:00 | ||
| 9 | Author ...............: scut | ||
| 10 | Publicity level ......: some known, some possibly known, some unknown | ||
| 11 | Affected .............: buffer overflow exploits | ||
| 12 | Type of entity .......: program behaviour | ||
| 13 | Type of discovery ....: useful information | ||
| 14 | Severity/Importance ..: low | ||
| 15 | Found by .............: various people, scut, skyper, duke | ||
| 16 | |||
| 17 | =============================================================================== | ||
| 18 | |||
| 19 | Although buffer overflows are kinda old now, there are still a lot of things | ||
| 20 | to discover. Here are a few tricks I noticed when playing around with | ||
| 21 | exploitation in the last two years. | ||
| 22 | |||
| 23 | |||
| 24 | trick 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 | |||
| 51 | trick 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 | |||
| 81 | trick 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 | |||
| 123 | trick 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 | |||
| 165 | trick 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 | |||
| 182 | trick 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 | |||
| 240 | trick 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 | |||
| 260 | trick 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 | |||
| 287 | If you know some tricks not listed here, please mail them to me | ||
| 288 | (scut@nb.in-berlin.de). | ||
| 289 | |||
| 290 | =============================================================================== | ||
| 291 | |||
