CodeMashCTF 2018 - Writeup
ctf·@darkstar-42·
0.000 HBDCodeMashCTF 2018 - Writeup
# CodeMashCTF 2018 - Writeup ## Do you like my Style? The Flag was in the `style.css` ``` .cm18 { flag: cm18-te94-1tuJ-ddx9-3dQO; } ``` ## Hobo Robo The link (bots.html) in the Challenge leads to a wikipedia page, but if we have a closer look this is done by a javascript redirection. If we load the page with JavaScript disabled or with curl/wget we can view the page and read the following text on the included image (robotbg.jpg). > BAMA WABOKI PISAL FATATU FOMU WOSEBI SEJU SOWU SEJU - BAMAS MUFE WAFUB FOMU MOWEWE The text looks like ROILA (Robot Interaction Language), and can be translated with a small script. ``` def translate(string): words = string.lower().split(' ') result = '' for word in words: if word in roilaDict: result += roilaDict[word] + ' ' else: result += word + ' ' return result ``` > you must make word of addition two and two - this be name of page If we follow the instructions we get to the next page (four. html) which contains the same javascript with the redirection, and again an image (robotbg2. jpg) with the text Meta. In the meta data of the HTML file is the following: ``` <meta name="description" content="Robots talk in ROILA language: eman egap eht esrever tsum"> ``` If we read it backwards we get: must reverse the page name If we follow the instructions we will find a picture (robotbg3_1337807.jpg) with the flag we are looking for on the next page (rouf.html). ## 1337 Riddler The Challenge text 'H3 1s l1st3n1ng 0n th3 BEST p0r7 on this s3rv3r!' leads to a number guessing riddle. ``` $ nc codemash.hacking-lab.com 8357 Make an educated guess, dude: 42 I need 20 digits, dude! ``` The first attempt shows that we are looking for a 20-digit number. ``` $ nc codemash.hacking-lab.com 8357 Make an educated guess, dude: 13371337133713371337 0< $ nc codemash.hacking-lab.com 8357 Make an educated guess, dude: 93371337133713371337 0> ``` After trying something out, it was clear that the number indicates the number of correct digits and the character > or < whether the number is too large or too small. With a small python script the solution can be determined quickly. ``` import pwn def x(start, pos): for i in range(10): r = pwn.remote('codemash.hacking-lab.com', 8357) r. sendline(start+str(i)+'0'*(20-pos)) r.readline() x = r.readline() if '<' in x: if int(x[:x.find('<')]) >= pos: start+str(i)+'0'*(20-pos) r.close() return i; elif '>' not in x: print start+str(i)+'0'*(20-pos) print r.readline() print r.readline() exit r.close() number = '' for i in range(1,21): number += str(x(number,i)) ``` ## Super Eyesight At first glance, the picture is not visible except for a squirrel, but after processing in gimp, increasing contrast and brightness, we can read the flag. ## Bools for fools I solved this challenge before it was significantly simplified. If we download and unpack the offered file we will get four text files (a. txt, b. txt, c. txt and d. txt) containing multiple lines of numbers. If we apply the specified boolean operation (((not(a) and b) or c) xor d) to it we get a qr code, because a file has one line too few, the last line of the QRCodes is not correct and the code cannot be imported. Since qrcodes are fault-tolerant, it is not necessary to completely restore the last line, there should be several readable ways, so that the search should not take so long with the help of a script. ``` from PIL import Image import os a = open('a.txt','r').read().split('\r\n') b = open('b.txt','r').read().split('\r\n') c = open('c.txt','r').read().split('\r\n') d = open('d.txt','r').read().split('\r\n') img = Image.new('RGB', (25, 25)) def putRow(row, data): for j in range(25): if (1L<<(24-j)) & r == 0: img.putpixel((j,row),(255,255,255)) else: img.putpixel((j,row),(0,0,0)) for i in range(24): putRow(i, (0x1ffffff^int(a[i],2)) & int(b[i],2) | int(c[i],2) ) ^ int(d[i],2) for i in range(0x1fc0000-1): putRow(24, 0x1fc0000 + i) img.save('qr2.png') os.system("java -cp zxing/javase-3.3.2.jar:zxing/core-3.3.2.jar com.google.zxing.client.j2se.CommandLineRunner qr2.png > result.txt") result = open('result.txt', 'r').read() if "Found" in result: print result exit() ``` ## Witchcraft Since this is witchcraft, it is obvious to think of an often used spell such as' hexhex'. This can then be easily undone by using the unhexlify function. ``` import binascii c = "363336643331333832643438363537373432326436383530333137323264346337393738333932643635373035343465" print binascii.unhexlify(binascii.unhexlify(c)) ``` ## Happy Eyes The given flag 1xT-Gcm8FV-5cYN-iBc-syHW with the hint ^^ in the challenge descriptiion leads to the rail-fence-cipher ``` 1 x T - G c m 8 F V - 5 c Y N - i B c - s y H W ``` ## Lock After decompiling, commenting out unnecessary parts of the program and adding an additional output, we only need to run the program to get the flag. ``` package de.darkstar.codemash2018; public class Challenge_08 { private static String cipher = "?8hiyKT5fw*W^J~art3t.47i"; private static String key = "lockpickingisfun"; public static void main(String[] args) { /* if (args.length != 1) { System.out.println("Provide codeword to open the lock!"); System.exit(-1); } String input = args[0]; */ StringBuffer codeword = new StringBuffer(); for (int i = 0; i < cipher.length(); i++) { codeword.append((char)(key.charAt(i % key.length()) - cipher.charAt(i) + 54)); } System.out.println(codeword); /* if (codeword.toString().equals(input)) { System.out.println("Correct codeword! The Lock is open!"); } else { System.out.println("Wrong codeword!"); } */ } } ``` Running the programm gives the flag ## Meow! After extracting the contained objects, an image contains the flag. ``` mutool extract meow.pdf ``` ## Chest The file (chest) contains a lot of flags, which all take quite a long time to try out, so it is worth taking a second look at the flags and we can see that they consist of recurring parts. The search for unique components should lead to the flag much faster. ``` import requests flags = open('flags.txt', 'r').read().split('\n') dict = {} for flag in flags: flag = flag[-4:] if flag not in dict: dict[flag] = 1 else: dict[flag] = dict[flag] + 1 print dict ``` > {'oots': 75, 'Fc8c': 1, 'ment': 75, 'ykey': 90, 'wels': 84, 'flag': 79, 'ones': 82, 'frum': 87, 'hell': 89, 'rrot': 92, 'coin': 155, 'dana': 89} As we can be seen from the output, the assumption that not all parts occur more than once was correct. Now we only have to display the flag with the unique part. ``` for flag in flags: if dict[flag[-4:]] == 1: print flag break ``` ## Bacon! This challenge leads through several levels of zipped git repositories. In this case we can use a small program to do not everything by hand. ``` package de.darkstar.codemash2018; import java.io.BufferedReader; import java.io.File; import java.io.InputStreamReader; public class Challenge_11 { private static void unzip(int i, int zip) throws Exception { String zipFolder = String.format("%04d", i); String zipFile = String.format("%04d", zip); String[] args1 = {"7z", "x", "/tmp/work/"+zipFolder+"/"+zipFile+".zip", "-o/tmp/work/gits" }; Runtime r = Runtime.getRuntime(); Process p = r.exec(args1); BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())); int returnCode = p.waitFor(); System.out.println("Return Code: " + Integer.toString(returnCode)); if (returnCode >= 2) { System.out.println("OS Error: " + returnCode); } String line = ""; while (br.ready()) { String str = br.readLine(); System.out.println(str); } } private static void clone(int i) throws Exception { String git = String.format("%04d", i); String[] args1 = {"git", "clone", "/tmp/work/gits/"+git, "/tmp/work/"+git }; Runtime r = Runtime.getRuntime(); Process p = r.exec(args1); BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())); int returnCode = p.waitFor(); System.out.println("Return Code: " + Integer.toString(returnCode)); if (returnCode >= 2) { System.out.println("OS Error: " + returnCode); } String line = ""; while (br.ready()) { String str = br.readLine(); System.out.println(str); } } public static int getZipNr(int i) { String git = String.format("/tmp/work/%04d/", i); File file = new File(git); File[] files = file.listFiles(); for (File f: files) { String n = f.getName(); if (n.endsWith(".zip")) { System.out.println(n); int nr = Integer.parseInt(n.substring(0, n.indexOf('.'))); System.out.println(nr); return nr; } } return -1; } private static void checkBranch(File dir) throws Exception { String[] args1 = {"git", "branch", "-r" }; Runtime r = Runtime.getRuntime(); Process p = r.exec(args1, null, dir); BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())); int returnCode = p.waitFor(); System.out.println("Return Code: " + Integer.toString(returnCode)); if (returnCode >= 2) { System.out.println("OS Error: " + returnCode); } String line = ""; while (br.ready()) { String str = br.readLine(); System.out.println(str); } } public static void main(String[] args) { try { // for (int i=1000; i>=0; ) { // for (int i=612; i>=0; ) { // git show HEAD~1:0612.zip > 0612.1.zip // for (int i=278; i>=0; ) { // git clone gits/0278 --branch blaster for (int i=44; i>=0; ) { // zip with password fluffy999 // git show for the password System.out.println("getZip()"); int zip = getZipNr(i); System.out.println("unzip("+i+","+ zip+")"); unzip(i, zip); i = zip; System.out.println("clone("+i+")"); clone(i); } } catch(Exception e) { e.printStackTrace(); } } } ``` ## On-site Challenge Since I wasn't on site, I was only able to solve this challenge after a few days with google, after the first writeups were released. ## Alice A first examination of the challenge image with strings showed that the file contains additional files. An attempt to extract them with 7z resulted in 3 more images. The attempt to extract further data using steghide yielded the following results. ``` $ steghide extract -sf water.jpg You search the whole place but you can't find anything. ... Now get out before you get flushed down. $ steghide extract -sf meadow.jpg So you think a mole can speak?! ... Lucky you, this one can! He's name is Fred and he tells you the passphrase: The-Mad-Hatter $ steghide extract -sf forest.jpg Congratulations here is the flag! cm18-xZl2-eHC5-axW3-ZkZG ``` ## Security Regulation The given link (secret_challenge_shred1.html) leads to the first part of the flag. An attempt to get the second part, by changing the link to secret_challenge_shred2.html, only resulted in a censored version of the second part, only a further change to topsecret_challenge_shred2.html led to a readable version, and the third part was then available at topsecret_challenge_shred3.html ## P.A.L.M. Login ™ A first investigation of the site showed that the login data is verified in a JavaScript. Since there are several scripts on the page that contain the same function name, it makes sense to start with the last script on the page, because the previous functions will be overwritten with a new definition. ``` <script> function checkEntries() { var u = document.getElementById('puser').value; var p = document.getElementById('ppass').value; if (u === 'yolo' && p === '1337') { document.location.href = 'palm_' + u + '_' + p + '.html'; } else { alert('nope'); } } var _0x549b=["value","puser","getElementById","ppass","rolo","length","charAt","href","location","palm_","_",".html","nope"];function checkEntries(){var u=document[_0x549b[2]](_0x549b[1])[_0x549b[0]];var p=document[_0x549b[2]](_0x549b[3])[_0x549b[0]];var ok=false;if(u===_0x549b[4]){if(p>0&&p[_0x549b[5]]==10){ok=true;for(i=0;i<=9;i++){var digit=p[_0x549b[6]](i);if(digit!=9-i){ok=false}}}};if(ok){document[_0x549b[8]][_0x549b[7]]=_0x549b[9]+u+_0x549b[10]+p+_0x549b[11]}else {alert(_0x549b[12])}}; </script> ``` ``` <script>eval(atob("ZXZhbChmdW5jdGlvbihwLGEsYyxrLGUsZCl7ZT1mdW5jdGlvbihjKXtyZXR1cm4gYy50b1N0cmluZygzNil9O2lmKCEnJy5yZXBsYWNlKC9eLyxTdHJpbmcpKXt3aGlsZShjLS0pe2RbYy50b1N0cmluZyhhKV09a1tjXXx8Yy50b1N0cmluZyhhKX1rPVtmdW5jdGlvbihlKXtyZXR1cm4gZFtlXX1dO2U9ZnVuY3Rpb24oKXtyZXR1cm4nXFx3Kyd9O2M9MX07d2hpbGUoYy0tKXtpZihrW2NdKXtwPXAucmVwbGFjZShuZXcgUmVnRXhwKCdcXGInK2UoYykrJ1xcYicsJ2cnKSxrW2NdKX19cmV0dXJuIHB9KCdyIHEoKXsyIHU9Ny44KFwnd1wnKS5iOzIgcD03LjgoXCd2XCcpLmI7MiA0PVswLDAsMCwwLDAsMCwwLDAsMCwwXTsyIDU9YzszKHU9PT1cJ2dcJyl7MyhwPjAmJnAuaD09OSl7NT1qO2YoaT0xO2k8PTk7aSsrKXsyIDY9cC5rKGktMSk7MiBhPXAuZSgwLGkpOzMoNFs2XSE9MHx8YSVpIT0wKXs1PWN9Myg0WzZdPT0wKXs0WzZdPTF9fX19Myg1KXs3Lm0ubj1cJ29cJyt1K1wneFwnK3ArXCcuc1wnfXR7ZChcJ2xcJyl9fScsMzQsMzQsJ3x8dmFyfGlmfHVzZWR8b2t8ZGlnaXR8ZG9jdW1lbnR8Z2V0RWxlbWVudEJ5SWR8MTB8cGFydHx2YWx1ZXxmYWxzZXxhbGVydHxzdWJzdHJpbmd8Zm9yfGNhdnN8bGVuZ3RofHx0cnVlfGNoYXJBdHxub3BlfGxvY2F0aW9ufGhyZWZ8cGFsbV98fGNoZWNrRW50cmllc3xmdW5jdGlvbnxodG1sfGVsc2V8fHBwYXNzfHB1c2VyfF8nLnNwbGl0KCd8JyksMCx7fSkpDQo=")); </script> ``` The last script on the page uses the `eval()` function, instead of executing it we let us show the result of the `atob()` function with `sonsole.log()`. ``` eval(function(p,a,c,k,e,d){e=function(c){return c.toString(36)};if(!''.replace(/^/,String)){while(c--){d[c.toString(a)]=k[c]||c.toString(a)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('r q(){2 u=7.8(\'w\').b;2 p=7.8(\'v\').b;2 4=[0,0,0,0,0,0,0,0,0,0];2 5=c;3(u===\'g\'){3(p>0&&p.h==9){5=j;f(i=1;i<=9;i++){2 6=p.k(i-1);2 a=p.e(0,i);3(4[6]!=0||a%i!=0){5=c}3(4[6]==0){4[6]=1}}}}3(5){7.m.n=\'o\'+u+\'x\'+p+\'.s\'}t{d(\'l\')}}',34,34,'||var|if|used|ok|digit|document|getElementById|10|part|value|false|alert|substring|for|cavs|length||true|charAt|nope|location|href|palm_||checkEntries|function|html|else||ppass|puser|_'.split('|'),0,{})) ``` The script output on the conole contains again an `eval()`, which is exchanged with `console.log()` again. ``` function checkEntries(){ var u=document.getElementById('puser').value; var p=document.getElementById('ppass').value; var used=[0,0,0,0,0,0,0,0,0,0]; var ok=false; if(u==='cavs'){ if(p>0&&p.length==10){ ok=true; for(i=1;i<=10;i++){ var digit=p.charAt(i-1); var part=p.substring(0,i); if(used[digit]!=0||part%i!=0){ ok=false } if(used[digit]==0){ used[digit]=1 } } } } if(ok){ document.location.href='palm_'+u+'_'+p+'.html' }else{ alert('nope') } } ``` The username can be read directly in this script and for the password we can use the following small python script. ``` import itertools def check(pin): used=[0,0,0,0,0,0,0,0,0,0]; for i in range(1,11): digit = int(pin[i-1:i]) if used[digit] == 1 or int(pin[0:i])%i != 0: return False used[digit] = 1 return True for i in itertools.permutations(range(10)): if check(''.join(map(str,i))): print ''.join(map(str,i)) break ```
👍 darkstar-42, waldfee, ctf, rmp, cryptoriddler, abed4, winchestergirl42, hantulaut, joseealvarezn, theeltuyo, verceti, tombs, spbsujoy, pulpiri, carlosph601, ivan78, brunold, tyagi.shruti, jaybi, chatitsimo, haiguzzi, develcuy, kanrat, osmar1212, jahedul2901, edwardbetancourt, roger-that, theadelina, hongsam, muldoang, josealvarez, rawmiss, nghia.vnd, douglaspuig, basilio, marwansp, mmarchena1, masterinnwa, davidalvarado, jonturk, kanij, cynthiauko, shidar, rosavjr, ojmo-venezuela1, darmawi86, sbharti121, raizam, anasofyanayusuf, rjqr2203, marquitos, wandoskin, yudithvalero, gohain,