└── README.md /README.md: -------------------------------------------------------------------------------- 1 | CTF-pwn-tips 2 | =========================== 3 | 4 | 5 | # Catalog 6 | * [Overflow](#overflow) 7 | * [Find string in gdb](#find-string-in-gdb) 8 | * [Binary Service](#binary-service) 9 | * [Find specific function offset in libc](#find-specific-function-offset-in-libc) 10 | * [Find '/bin/sh' or 'sh' in library](#find-binsh-or-sh-in-library) 11 | * [Leak stack address](#leak-stack-address) 12 | * [Fork problem in gdb](#fork-problem-in-gdb) 13 | * [Secret of a mysterious section - .tls](#secret-of-a-mysterious-section---tls) 14 | * [Predictable RNG(Random Number Generator)](#predictable-rngrandom-number-generator) 15 | * [Make stack executable](#make-stack-executable) 16 | * [Use one-gadget-RCE instead of system](#use-one-gadget-rce-instead-of-system) 17 | * [Hijack hook function](#hijack-hook-function) 18 | * [Use printf to trigger malloc and free](#use-printf-to-trigger-malloc-and-free) 19 | * [Use execveat to open a shell](#use-execveat-to-open-a-shell) 20 | 21 | 22 | ## Overflow 23 | 24 | Assume that: `char buf[40]` and `signed int num` 25 | 26 | ### scanf 27 | 28 | * `scanf("%s", buf)` 29 | * `%s` doesn't have boundary check. 30 | * **pwnable** 31 | 32 | * `scanf("%39s", buf)` 33 | * `%39s` only takes 39 bytes from the input and puts NULL byte at the end of input. 34 | * **useless** 35 | 36 | * `scanf("%40s", buf)` 37 | * At first sight, it seems reasonable.(seems) 38 | * It takes **40 bytes** from input, but it also **puts NULL byte at the end of input.** 39 | * Therefore, it has **one-byte-overflow**. 40 | * **pwnable** 41 | 42 | * `scanf("%d", &num)` 43 | * Used with `alloca(num)` 44 | * Since `alloca` allocates memory from the stack frame of the caller, there is an instruction `sub esp, eax` to achieve that. 45 | * If we make num negative, it will have overlapped stack frame. 46 | * E.g. [Seccon CTF quals 2016 cheer_msg](https://github.com/ctfs/write-ups-2016/tree/master/seccon-ctf-quals-2016/exploit/cheer-msg-100) 47 | * Use num to access some data structures 48 | * In most of the time, programs only check the higher bound and forget to make num unsigned. 49 | * Making num negative may let us overwrite some important data to control the world! 50 | 51 | ### gets 52 | 53 | * `gets(buf)` 54 | * No boundary check. 55 | * **pwnable** 56 | 57 | * `fgets(buf, 40, stdin)` 58 | * It takes only **39 bytes** from the input and puts NULL byte at the end of input. 59 | * **useless** 60 | 61 | ### read 62 | 63 | * `read(stdin, buf, 40)` 64 | * It takes **40 bytes** from the input, and it doesn't put NULL byte at the end of input. 65 | * It seems safe, but it may have **information leak**. 66 | * **leakable** 67 | 68 | E.g. 69 | 70 | **memory layout** 71 | ``` 72 | 0x7fffffffdd00: 0x4141414141414141 0x4141414141414141 73 | 0x7fffffffdd10: 0x4141414141414141 0x4141414141414141 74 | 0x7fffffffdd20: 0x4141414141414141 0x00007fffffffe1cd 75 | ``` 76 | 77 | * If there is a `printf` or `puts` used to output the buf, it will keep outputting until reaching NULL byte. 78 | * In this case, we can get `'A'*40 + '\xcd\xe1\xff\xff\xff\x7f'`. 79 | 80 | * `fread(buf, 1, 40, stdin)` 81 | * Almost the same as `read`. 82 | * **leakable** 83 | 84 | ### strcpy 85 | 86 | Assume that there is another buffer: `char buf2[60]` 87 | 88 | * `strcpy(buf, buf2)` 89 | * No boundary check. 90 | * It copies the content of buf2(until reaching NULL byte) which may be longer than `length(buf)` to buf. 91 | * Therefore, it may happen overflow. 92 | * **pwnable** 93 | 94 | * `strncpy(buf, buf2, 40)` && `memcpy(buf, buf2, 40)` 95 | * It copies 40 bytes from buf2 to buf, but it won't put NULL byte at the end. 96 | * Since there is no NULL byte to terminate, it may have **information leak**. 97 | * **leakable** 98 | 99 | ### strcat 100 | 101 | Assume that there is another buffer: `char buf2[60]` 102 | 103 | * `strcat(buf, buf2)` 104 | * Of course, it may cause **overflow** if `length(buf)` isn't large enough. 105 | * It puts NULL byte at the end, it may cause **one-byte-overflow**. 106 | * In some cases, we can use this NULL byte to change stack address or heap address. 107 | * **pwnable** 108 | 109 | * `strncat(buf, buf2, n)` 110 | * Almost the same as `strcat`, but with size limitation. 111 | * **pwnable** 112 | * E.g. [Seccon CTF quals 2016 jmper](https://github.com/ctfs/write-ups-2016/tree/master/seccon-ctf-quals-2016/exploit/jmper-300) 113 | 114 | 115 | ## Find string in gdb 116 | 117 | In the problem of [SSP](http://j00ru.vexillium.org/blog/24_03_15/dragons_ctf.pdf), we need to find out the offset between `argv[0]` and the input buffer. 118 | 119 | ### gdb 120 | 121 | * Use `p/x ((char **)environ)` in gdb, and the address of argv[0] will be the `output - 0x10` 122 | 123 | E.g. 124 | 125 | ``` 126 | (gdb) p/x (char **)environ 127 | $9 = 0x7fffffffde38 128 | (gdb) x/gx 0x7fffffffde38-0x10 129 | 0x7fffffffde28: 0x00007fffffffe1cd 130 | (gdb) x/s 0x00007fffffffe1cd 131 | 0x7fffffffe1cd: "/home/naetw/CTF/seccon2016/check/checker" 132 | ``` 133 | 134 | ### [gdb peda](https://github.com/longld/peda) 135 | 136 | * Use `searchmem "/home/naetw/CTF/seccon2016/check/checker"` 137 | * Then use `searchmem $result_address` 138 | 139 | ``` 140 | gdb-peda$ searchmem "/home/naetw/CTF/seccon2016/check/checker" 141 | Searching for '/home/naetw/CTF/seccon2016/check/checker' in: None ranges 142 | Found 3 results, display max 3 items: 143 | [stack] : 0x7fffffffe1cd ("/home/naetw/CTF/seccon2016/check/checker") 144 | [stack] : 0x7fffffffed7c ("/home/naetw/CTF/seccon2016/check/checker") 145 | [stack] : 0x7fffffffefcf ("/home/naetw/CTF/seccon2016/check/checker") 146 | gdb-peda$ searchmem 0x7fffffffe1cd 147 | Searching for '0x7fffffffe1cd' in: None ranges 148 | Found 2 results, display max 2 items: 149 | libc : 0x7ffff7dd33b8 --> 0x7fffffffe1cd ("/home/naetw/CTF/seccon2016/check/checker") 150 | [stack] : 0x7fffffffde28 --> 0x7fffffffe1cd ("/home/naetw/CTF/seccon2016/check/checker") 151 | ``` 152 | 153 | ## Binary Service 154 | 155 | Normal: 156 | 157 | * `ncat -vc ./binary -kl 127.0.0.1 $port` 158 | 159 | With specific library in two ways: 160 | 161 | * `ncat -vc 'LD_PRELOAD=/path/to/libc.so ./binary' -kl 127.0.0.1 $port` 162 | * `ncat -vc 'LD_LIBRARY_PATH=/path/of/libc.so ./binary' -kl 127.0.0.1 $port` 163 | 164 | After this, you can connect to binary service by command `nc localhost $port`. 165 | 166 | ## Find specific function offset in libc 167 | 168 | If we leaked libc address of certain function successfully, we could use get libc base address by subtracting the offset of that function. 169 | 170 | ### Manually 171 | 172 | * `readelf -s $libc | grep ${function}@` 173 | 174 | E.g. 175 | 176 | ``` 177 | $ readelf -s libc-2.19.so | grep system@ 178 | 620: 00040310 56 FUNC GLOBAL DEFAULT 12 __libc_system@@GLIBC_PRIVATE 179 | 1443: 00040310 56 FUNC WEAK DEFAULT 12 system@@GLIBC_2.0 180 | ``` 181 | 182 | ### Automatically 183 | 184 | * Use [pwntools](https://github.com/Gallopsled/pwntools), then you can use it in your exploit script. 185 | 186 | E.g. 187 | 188 | ```python 189 | from pwn import * 190 | 191 | libc = ELF('libc.so') 192 | system_off = libc.symbols['system'] 193 | ``` 194 | 195 | ## Find '/bin/sh' or 'sh' in library 196 | 197 | Need libc base address first 198 | 199 | ### Manually 200 | 201 | * `objdump -s libc.so | less` then search 'sh' 202 | * `strings -tx libc.so | grep /bin/sh` 203 | 204 | ### Automatically 205 | 206 | * Use [pwntools](https://github.com/Gallopsled/pwntools) 207 | 208 | E.g. 209 | 210 | ```python 211 | from pwn import * 212 | 213 | libc = ELF('libc.so') 214 | ... 215 | sh = base + next(libc.search('sh\x00')) 216 | binsh = base + next(libc.search('/bin/sh\x00')) 217 | ``` 218 | 219 | ## Leak stack address 220 | 221 | **constraints**: 222 | 223 | * Have already leaked libc base address 224 | * Can leak the content of arbitrary address 225 | 226 | There is a symbol `environ` in libc, whose value is the same as the third argument of `main` function, `char **envp`. 227 | The value of `char **envp` is on the stack, thus we can leak stack address with this symbol. 228 | 229 | ``` 230 | (gdb) list 1 231 | 1 #include 232 | 2 #include 233 | 3 234 | 4 extern char **environ; 235 | 5 236 | 6 int main(int argc, char **argv, char **envp) 237 | 7 { 238 | 8 return 0; 239 | 9 } 240 | (gdb) x/gx 0x7ffff7a0e000 + 0x3c5f38 241 | 0x7ffff7dd3f38 : 0x00007fffffffe230 242 | (gdb) p/x (char **)envp 243 | $12 = 0x7fffffffe230 244 | ``` 245 | 246 | * `0x7ffff7a0e000` is current libc base address 247 | * `0x3c5f38` is offset of `environ` in libc 248 | 249 | This [manual](https://www.gnu.org/software/libc/manual/html_node/Program-Arguments.html) explains details about `environ`. 250 | 251 | ## Fork problem in gdb 252 | 253 | When you use **gdb** to debug a binary with `fork()` function, you can use the following command to determine which process to follow (The default setting of original gdb is parent, while that of gdb-peda is child.): 254 | 255 | * `set follow-fork-mode parent` 256 | * `set follow-fork-mode child` 257 | 258 | Alternatively, using `set detach-on-fork off`, we can then control both sides of each fork. Using `inferior X` where `X` is any of the numbers that show up for `info inferiors` will switch to that side of the fork. This is useful if both sides of the fork are necessary to attack a challenge, and the simple `follow` ones above aren't sufficient. 259 | 260 | ## Secret of a mysterious section - .tls 261 | 262 | **constraints**: 263 | 264 | * Need `malloc` function and you can malloc with arbitrary size 265 | * Arbitrary address leaking 266 | 267 | We make `malloc` use `mmap` to allocate memory(size 0x21000 is enough). In general, these pages will be placed at the address just before `.tls` section. 268 | 269 | There is some useful information on **`.tls`**, such as the address of `main_arena`, `canary` (value of stack guard), and a strange `stack address` which points to somewhere on the stack but with a fixed offset. 270 | 271 | **Before calling mmap:** 272 | 273 | ``` 274 | 7fecbfe4d000-7fecbfe51000 r--p 001bd000 fd:00 131210 /lib/x86_64-linux-gnu/libc-2.24.so 275 | 7fecbfe51000-7fecbfe53000 rw-p 001c1000 fd:00 131210 /lib/x86_64-linux-gnu/libc-2.24.so 276 | 7fecbfe53000-7fecbfe57000 rw-p 00000000 00:00 0 277 | 7fecbfe57000-7fecbfe7c000 r-xp 00000000 fd:00 131206 /lib/x86_64-linux-gnu/ld-2.24.so 278 | 7fecc0068000-7fecc006a000 rw-p 00000000 00:00 0 <- .tls section 279 | 7fecc0078000-7fecc007b000 rw-p 00000000 00:00 0 280 | 7fecc007b000-7fecc007c000 r--p 00024000 fd:00 131206 /lib/x86_64-linux-gnu/ld-2.24.so 281 | 7fecc007c000-7fecc007d000 rw-p 00025000 fd:00 131206 /lib/x86_64-linux-gnu/ld-2.24.so 282 | ``` 283 | 284 | **After call mmap:** 285 | 286 | ``` 287 | 7fecbfe4d000-7fecbfe51000 r--p 001bd000 fd:00 131210 /lib/x86_64-linux-gnu/libc-2.24.so 288 | 7fecbfe51000-7fecbfe53000 rw-p 001c1000 fd:00 131210 /lib/x86_64-linux-gnu/libc-2.24.so 289 | 7fecbfe53000-7fecbfe57000 rw-p 00000000 00:00 0 290 | 7fecbfe57000-7fecbfe7c000 r-xp 00000000 fd:00 131206 /lib/x86_64-linux-gnu/ld-2.24.so 291 | 7fecc0045000-7fecc006a000 rw-p 00000000 00:00 0 <- memory of mmap + .tls section 292 | 7fecc0078000-7fecc007b000 rw-p 00000000 00:00 0 293 | 7fecc007b000-7fecc007c000 r--p 00024000 fd:00 131206 /lib/x86_64-linux-gnu/ld-2.24.so 294 | 7fecc007c000-7fecc007d000 rw-p 00025000 fd:00 131206 /lib/x86_64-linux-gnu/ld-2.24.so 295 | ``` 296 | 297 | ## Predictable RNG(Random Number Generator) 298 | 299 | When the binary uses the RNG to make the address of important information or sth, we can guess the same value if it's predictable. 300 | 301 | Assuming that it's predictable, we can use [ctypes](https://docs.python.org/2/library/ctypes.html) which is a build-in module in Python. 302 | 303 | **ctypes** allows calling a function in DLL(Dynamic-Link Library) or Shared Library. 304 | 305 | Therefore, if binary has an init_proc like this: 306 | 307 | ```c 308 | srand(time(NULL)); 309 | while(addr <= 0x10000){ 310 | addr = rand() & 0xfffff000; 311 | } 312 | secret = mmap(addr,0x1000,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS ,-1,0); 313 | if(secret == -1){ 314 | puts("mmap error"); 315 | exit(0); 316 | } 317 | ``` 318 | 319 | Then we can use **ctypes** to get the same value of addr. 320 | 321 | ```python 322 | import ctypes 323 | LIBC = ctypes.cdll.LoadLibrary('/path/to/dll') 324 | LIBC.srand(LIBC.time(0)) 325 | addr = LIBC.rand() & 0xfffff000 326 | ``` 327 | 328 | ## Make stack executable 329 | 330 | * [link1](http://radare.today/posts/defeating-baby_rop-with-radare2/) 331 | * [link2](https://sploitfun.wordpress.com/author/sploitfun/) 332 | * Haven't read yet orz 333 | 334 | ## Use one-gadget-RCE instead of system 335 | 336 | **constraints**: 337 | 338 | * Have libc base address 339 | * Write to arbitrary address 340 | 341 | Almost every pwnable challenge needs to call `system('/bin/sh')` in the end of the exploit, but if we want to call that, we have to manipulate the parameters and, of course, hijack some functions to `system`. What if we **can't** manipulate the parameter? 342 | 343 | Use [one-gadget-RCE](http://j00ru.vexillium.org/blog/24_03_15/dragons_ctf.pdf)! 344 | 345 | With **one-gadget-RCE**, we can just hijack `.got.plt` or something we can use to control eip to make program jump to **one-gadget**, but there are some constraints that need satisfying before using it. 346 | 347 | There are lots of **one-gadgets** in libc. Each one has different constraints but those are similar. Each constraint is about the state of registers. 348 | 349 | E.g. 350 | 351 | * ebx is the address of `rw-p` area of libc 352 | * [esp+0x34] == NULL 353 | 354 | How can we get these constraints? Here is an useful tool [one_gadget](https://github.com/david942j/one_gadget) !!!! 355 | 356 | So if we can satisfy those constraints, we can get the shell more easily. 357 | 358 | ## Hijack hook function 359 | 360 | **constraints**: 361 | 362 | * Have libc base address 363 | * Write to arbitrary address 364 | * The program uses `malloc`, `free` or `realloc`. 365 | 366 | By manual: 367 | 368 | > The GNU C Library lets you modify the behavior of `malloc`, `realloc`, and `free` by specifying appropriate hook functions. You can use these hooks to help you debug programs that use dynamic memory allocation, for example. 369 | 370 | There are hook variables declared in malloc.h and their default values are `0x0`. 371 | 372 | * `__malloc_hook` 373 | * `__free_hook` 374 | * ... 375 | 376 | Since they are used to help us debug programs, they are writable during the execution. 377 | 378 | ``` 379 | 0xf77228e0 <__free_hook>: 0x00000000 380 | 0xf7722000 0xf7727000 rw-p mapped 381 | ``` 382 | 383 | Let's look into the [src](https://code.woboq.org/userspace/glibc/malloc/malloc.c.html#2917) of malloc.c. I will use `__libc_free` to demo. 384 | 385 | ```c 386 | void (*hook) (void *, const void *) = atomic_forced_read (__free_hook); 387 | if (__builtin_expect (hook != NULL, 0)) 388 | { 389 | (*hook)(mem, RETURN_ADDRESS (0)); 390 | return; 391 | } 392 | ``` 393 | 394 | It checks the value of `__free_hook`. If it's not NULL, it will call the hook function first. Here, we would like to use **one-gadget-RCE**. Since hook function is called in the libc, the constraints of **one-gadget** are usually satisfied. 395 | 396 | ## Use printf to trigger malloc and free 397 | 398 | Look into the source of printf, there are several places which may trigger malloc. Take [vfprintf.c line 1470](https://code.woboq.org/userspace/glibc/stdio-common/vfprintf.c.html#1470) for example: 399 | 400 | ```c 401 | #define EXTSIZ 32 402 | enum { WORK_BUFFER_SIZE = 1000 }; 403 | 404 | if (width >= WORK_BUFFER_SIZE - EXTSIZ) 405 | { 406 | /* We have to use a special buffer. */ 407 | size_t needed = ((size_t) width + EXTSIZ) * sizeof (CHAR_T); 408 | if (__libc_use_alloca (needed)) 409 | workend = (CHAR_T *) alloca (needed) + width + EXTSIZ; 410 | else 411 | { 412 | workstart = (CHAR_T *) malloc (needed); 413 | if (workstart == NULL) 414 | { 415 | done = -1; 416 | goto all_done; 417 | } 418 | workend = workstart + width + EXTSIZ; 419 | } 420 | } 421 | ``` 422 | 423 | We can find that `malloc` will be triggered if the width field is large enough.(Of course, `free` will also be triggered at the end of printf if `malloc` has been triggered.) However, WORK_BUFFER_SIZE is not large enough, since we need to go to **else** block. Let's take a look at `__libc_use_alloca` and see what exactly the minimum size of width we should give. 424 | 425 | ```c 426 | 427 | /* Minimum size for a thread. We are free to choose a reasonable value. */ 428 | #define PTHREAD_STACK_MIN 16384 429 | 430 | #define __MAX_ALLOCA_CUTOFF 65536 431 | 432 | int __libc_use_alloca (size_t size) 433 | { 434 | return (__builtin_expect (size <= PTHREAD_STACK_MIN / 4, 1) 435 | || __builtin_expect (__libc_alloca_cutoff (size), 1)); 436 | } 437 | 438 | int __libc_alloca_cutoff (size_t size) 439 | { 440 | return size <= (MIN (__MAX_ALLOCA_CUTOFF, 441 | THREAD_GETMEM (THREAD_SELF, stackblock_size) / 4 442 | /* The main thread, before the thread library is 443 | initialized, has zero in the stackblock_size 444 | element. Since it is the main thread we can 445 | assume the maximum available stack space. */ 446 | ?: __MAX_ALLOCA_CUTOFF * 4)); 447 | } 448 | ``` 449 | 450 | We have to make sure that: 451 | 452 | 1. `size > PTHREAD_STACK_MIN / 4` 453 | 2. `size > MIN(__MAX_ALLOCA_CUTOFF, THREAD_GETMEM(THREAD_SELF, stackblock_size) / 4 ?: __MAX_ALLOCA_CUTOFF * 4)` 454 | * I did not fully understand what exactly the function - THREAD_GETMEM do, but it seems that it mostly returns 0. 455 | * Therefore, the second condition is usually `size > 65536` 456 | 457 | More details: 458 | 459 | * [__builtin_expect](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html) 460 | * [THREAD_GETMEM](https://code.woboq.org/userspace/glibc/sysdeps/x86_64/nptl/tls.h.html#_M/THREAD_GETMEM) 461 | 462 | 463 | ### conclusion 464 | 465 | * The minimum size of width to trigger `malloc` & `free` is 65537 most of the time. 466 | * If there is a Format String Vulnerability and the program ends right after calling `printf(buf)`, we can hijack `__malloc_hook` or `__free_hook` with `one-gadget` and use the trick mentioned above to trigger `malloc` & `free` then we can still get the shell even there is no more function call or sth after `printf(buf)`. 467 | 468 | ## Use execveat to open a shell 469 | 470 | When it comes to opening a shell with system call, `execve` always pops up in mind. However, it's not always easily available due to the lack of gadgets or others constraints. 471 | Actually, there is a system call, `execveat`, with following prototype: 472 | 473 | ```c 474 | int execveat(int dirfd, const char *pathname, 475 | char *const argv[], char *const envp[], 476 | int flags); 477 | ``` 478 | 479 | According to its [man page](http://man7.org/linux/man-pages/man2/execveat.2.html), it operates in the same way as `execve`. As for the additional arguments, it mentions that: 480 | 481 | > If pathname is absolute, then dirfd is ignored. 482 | 483 | Hence, if we make `pathname` point to `"/bin/sh"`, and set `argv`, `envp` and `flags` to 0, we can still get a shell whatever the value of `dirfd`. 484 | --------------------------------------------------------------------------------