RedPwn CTF 2019

banner

Well to get started, I want to give you a glimpse of my experience with the CTF.
The Redpwn Organizers really did an awesome job in creating the challenges.
The quality of the challenges was awesome. Difficulty level was also mediumish.
It was a 3 day CTF and new challenges were updated timely.
The site got really slow once but that’s pretty common nowadays with the CTFd platform. XD

So after the 3 days long journey my team got the 8th position.

And in today’s post we’ll be discussing some forensic challenges only. I found these challenges worth a writeup and also I enjoyed them solving.

RedpwnGetsBamboozled
Dedication
Skywriting


RedpwnGetsBamboozled

Task Description :

Download File

We were given just a txt file.

Ahhh! We can quickly observe that these are some (R, G, B) Values.
Also the initials of the Challenge Title is R, G, B XD
And at the beginning we get width and height as 600,800 respectively.

So its a basic python PIL task.
I just deleted the width and height values from the data.txt file and iterated all the RGB values and wrote them to a new Image.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from PIL import Image
im = Image.new("RGB", (600,800))

infile = open('data.txt', 'r')
data = infile.read().strip('\n').split(', ')

rgblist = []

for x in data:
v = x.strip(')').strip('(').split()
rgb = (int(v[0]),int(v[1]),int(v[2]))
rgblist.append(rgb)

im.putdata(rgblist)
im.save("output.png")

Hmmm, I thought it would give us the flag but instead it gave us the following image.

drawing

So I tried various ways to decode it…

Approach with the image:

Once I tried stegsolve too LOL

drawing

After observing Red planes, I got some various patterns of dots that looked like braille but there were 8 dots in a column but we needed a multiple of 3 for decrypting if it would have been braille.

Also as one could easily observe that the dots are of different colours so I decided to change my strategy and moved towards the colour codes of the dots

I simply used the colour picker tool from GIMP and noted the color codes..
There were 10 different color codes in the image.

1
222222 cccccc dddddd 999999 444444 ffffff 555555 666666 333333 666666

I also tried sorting them by observing how frequently they appeared.
I tried taking their initials as all have single character.
And It came to me that what if the order is provided within the image itself as it can’t be so random dots.

So made a list of the Colour Code Initials ordering them as per the image..

drawing
1
6d 79 5f 62 33 35 74 5f 66 6c 34 67 5f 79 33 74

And decoding it from hex to ascii we get the flag.

1
flag{my_b35t_fl4g_y3t}

Dedication

Task Description :

Download File


Ok so we got an encrypted zip file and another is a png.


But Wait, it’s not a png file but instead its a text file.

It also contains some RGB values but instead this time we are not provided with the width and height of the image but here we have different lines which can help us identify the height of the image and the no. of RGB values in a line help us identify the width of image.

So we start using our PIL ninja techniques.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from PIL import Image

im = Image.new("RGB", (400,600))
infile = open('jjofpbwvgk.png', 'r')
data = infile.read().strip('\n').split()
rgblist = []
for x in data:
v = x.strip(')').strip('(').split(',')
rgb = (int(v[0]),int(v[1]),int(v[2]))
rgblist.append(rgb)
im.putdata(rgblist)
rotated = Image.new("RGB", (600,400))
rotated = im.transpose(Image.FLIP_TOP_BOTTOM).rotate(-90,expand=1)
rotated.save('output.jpg')

This basic script just writes our pixel RGB values into an image of width and height of 400, 600 respectively. But you could notice that I’ve mirrored the image and then rotated it anticlockwise 90 degrees which was important to understand the text.
And I got the following image.

I quickly tried this as the password for the zip file and no wonder it worked. But to my surprise there was another zip with similar contents.
Ahhh, So this was also a scripting task. Obviously the basic approach which would help us automate the decryption of images was to use an OCR tool.
But this challenge screwed me for a while as I had a habit of using tesseract for every ocr task but this one was a nightmare with tesseract. It gave awful results. So I tried to switch over to some good ocr tool and I researched for a while and found a command-line tool known as GOCR
which gave somewhat accurate results.

1
apt-get install gocr

I wrote a bit messy script but still it works. XD
At first I tried to use ZipFile python library to extract the contents but it was ridiculously slow so I used the simple unzip command.

Also note that the password is not correct in every case as GOCR is not trained on this type of font or the colour values might matter so it’s not everytime perfect. So we’ve to enter some passwords manually.
To solve this situation and make it easy to enter password interactively while the script is running, we can just check the os.system() return code.
I learned that os.system() returns 0 if there is no error while executing the command. You can read more about it here. I also used Image.show() when the password is incorrect, which helps us in saving some time.

So I implemented all this and gave a seed value as the first directory named as jjofpbwvgk and put the text (fake png) and the image we got in it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
from PIL import Image
import subprocess
from zipfile import ZipFile
import os
#random_name = 'jjofpbwvgk' #seed_value
random_name = 'jjofpbwvgk'
zip_num=1
wrongpass=0
#adding some colors for fun
green = '\033[1;32;40m'
red = '\033[1;31;40m'
cyan = '\033[1;36;40m'
reset = '\033[0;37;40m'

while True:
try:
im = Image.new("RGB", (400,600))
path = os.getcwd()+'/'+random_name
os.chdir(path)
infile = open(random_name+'.png', 'r')
data = infile.read().strip('\n').split()
rgblist = []
for x in data:
v = x.strip(')').strip('(').split(',')
rgb = (int(v[0]),int(v[1]),int(v[2]))
rgblist.append(rgb)
im.putdata(rgblist)
rotated = Image.new("RGB", (400,600))
rotated = im.transpose(Image.FLIP_TOP_BOTTOM).rotate(-90,expand=1)
rotated.save('pass.jpg')
gocr_cmd = "gocr pass.jpg"
ps = subprocess.Popen(gocr_cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT)
output = ps.communicate()[0]
#some tweaking to get more correct passwords with gocr
password = output.lower().replace('\n','').replace(' ','').replace('_','g').replace('\xc3\xac','i').replace('\xc3\xad','i')
if password[0]=='y':
password=password.replace(password[0],'')
password+='y'
print cyan, "Extracting Zip :", zip_num,reset
print "password for", random_name, ":", password
parentdir = os.path.dirname(path)
os.chdir(parentdir)
zip_path = path + '/' +random_name + '.zip'
unzip_cmd1 = 'unzip ' + '-P ' + password + ' ' + zip_path
if(os.system(unzip_cmd1)==0):
print random_name+'.zip' + " extracted successfully"
else:
wrongpass+=1
unzip_cmd2='unzip ' + zip_path
rotated.show()
os.system(unzip_cmd2)
with ZipFile(zip_path) as zf:
random_name = zf.namelist()[0][:-1]
zip_num+=1
except:
print red,"Wrong password attempts :",wrongpass,reset
parentdir = os.path.dirname(path)
os.chdir(parentdir+ '/' + 'flag')
gocr_cmd = "gocr flag.png"
ps = subprocess.Popen(gocr_cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT)
#modifying the output to get our perfect flag
flag = ps.communicate()[0].replace(' ','').replace(')','}')
print green,"FLAG :", flag,reset
break

Ok if the script ran successfully you should get 1000 directories and our flag image residing in the 1000th directory.

At the end, I’d say that it seriously needed some dedication LOL
Also If anyone knows about a more precise OCR tool, please let me know in the comments!!


Skywriting

Task Description :

Download File

By experience I knew that skywriting is a pretty common name for challenges so I first googled if there was any similar challenge from any previous ctf competition. And the first result I encountered was of PACTF 2018. The challenge was totally duplicated but we had to do it ourselves as flag was not the same as of PACTF. XD

You can read that writeup here.

I was being lazy so I searched more for a while to grab a better writeup which tells how to break the substitution cipher too.
I’ll just give a short summary of how I solved it taking the PACTF writeup as a reference as it’s not complete.

The key differences that the RedPwn CTF did made was renaming the gusty-garden-galaxy.wav files and modifying the flag so that we understand what we were doing.
After reading the writeup I installed the stegolsb tool.

1
pip install stego-lsb

So I used this tool to get about 5000 bytes of text from the gusty-garden-8.wav file which were enough for getting the flag.

I finally got some text from gusty-garden-galaxy-8.wav instead of gusty-garden-galaxy-6.wav.

1
stegolsb wavsteg -r -i gusty-garden-galaxy-8.wav -o output.txt -b 5000

Now If your text editor or terminal supports unicode chars, it’ll display some text similar to the following.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
OMG WOW NO WAY
IT'S UTF8 GARBAGE AMONGST THE REGULAR GARBAGE!
IT MUST BE A SUBSTITUTION CIPHER!!!!!
خβ θي ظβ⅗يηخ, ⅓ 吉ιη ηλχ伊χ⅓ηيش ιخ خظي ش⅓μμ⅓γλ艾خ艾 خيιرη ي½伊يχ⅓ي⅗γيش ⅓⅗ ηβ艾ع⅓⅗⅘ خظ⅓η 伊χβθ艾ير.
ظβ吉يعيχ, ⅓ ظβ伊ي ι⅗艾 خيιرη خظιخ 吉يχي ηخ艾ر⅓يش θ艾 ⅓خ شβ⅗’خ خظ⅓⅗开 ⅓خ 吉ιη ⅗يγيηηιχ⅓艾艾
λ⅗μι⅓χ, πληخ ش⅓μμ⅓γλ艾خ. ⅓ خظ⅓⅗开 يιγظ ηخي伊 ⅓⅗ خظ⅓η ηβ艾λخ⅓β⅗ γβλ艾ش θي γιخι艾艾艾يش θ艾 艾β⅘⅓γι艾 γ艾λيη,
ι⅗ش ⅗βخظ⅓⅗⅘ χي⅔λ⅓χيش 伊λχي خχ⅓ι艾-ι⅗ش-يχχβχ. ⅓ خظ⅓⅗开 خظي ش⅓μμ⅓γλ艾خ艾 شي艾خι μχβر خظي χيηخ βμ 伊ιγخμ ر⅓⅘ظخ
ظιعي ي½ιγيχθιخيش خظي ش⅓μμ⅓γλ艾خ⅓يη خيιرη μιγيش: 伊يχظι伊η 伊يβ伊艾ي 吉يχي ي½伊يγخ⅓⅗⅘ ι 伊χβθ艾ير
γ艾βηيχ خβ خظي χβλ⅗ش 1 ظιχش 伊χβθ艾يرη. ιشش⅓خ⅓β⅗ι艾艾艾, ⅓خ’η ι μι⅓χ艾艾 ⅗β⅗-ηخι⅗شιχش γخμ 伊χβθ艾ير.
⅓’ش ιχ⅘λي خظιخ 吉βχ开η خβ خظي 伊χβθ艾ير’η ιشعι⅗خι⅘ي, ι⅗ش μχβر 吉ظιخ ⅓’عي ηيي⅗ 伊يβ伊艾ي 吉ظβ ظιعي⅗’خ شβ⅗ي خββ رι⅗艾 γخμη ηييريش
خβ 艾⅓开ي ⅓خ رβχي, θλخ خظιخ’η ι⅗يγشβخι艾. ⅓μ 艾βλ ش⅓ηι⅘χيي, βχ 吉ι⅗خ خβ ηظιχي ι⅗艾خظ⅓⅗⅘ ي艾ηي, μيي艾 μχيي خβ γβ⅗خιγخ ري! ظβ伊ي
艾βλ ي⅗πβ艾يش خظي γخμ ιη ι 吉ظβ艾ي! ι艾ηβ, ⅓μ 艾βλ 艾⅓开ي 伊χβθ艾يرη 艾⅓开ي خظ⅓η, رι开ي ηλχي خβ γظيγ开 βλخ 伊ιγخμ 2019 ⅓⅗ رι⅗艾 رβ⅗خظη’ خ⅓ري.
-- ⅗⅓γظβ艾ιηر
开يي伊 ⅘β⅓⅗⅘.
شβ⅗'خ θي يع⅓艾.
خظ⅓η γظι艾艾ي⅗⅘ي ⅓η ⅓⅗خλ⅓خ⅓عي.
ظيχي ⅓η ι خ⅓⅗艾 ηظβχخي⅗يش γ艾λي خβ خظي شβγλري⅗خ 艾βλ 吉⅓艾艾 ⅗ييش. 开يي伊 艾βλχ ظيιش ⅓⅗ خظي γ艾βλش.
خ⅓⅗艾μ艾ι⅘艾⅓⅗开
--------------------------------

This wasn’t gibberish, it was just some unicode characters.

As this was a substitution cipher, a little bit of frequency ananlysis is also required. So I turned to wikipedia to get some frequency analysis graphs of english alphabets.

So I made a simple counter to get the no. of occurences of the unicode chars in the cipher.

1
2
3
4
5
6
7
8
9
10
from collections import Counter 

infile = open('output.txt','r')
data = infile.read()
cipher = data.decode('utf-8')

#using collections.Counter() to get
#count of each element in string
count = Counter(cipher)
print str(count)
1
Counter({u' ': 548, u'\u064a': 361, u'\u062e': 295, u'\u2153': 234, u'\u03b9': 188, u'\u03b7': 186, u'\u03c7': 171, u'\u03b2': 166, u'\u2157': 163, u'\u827e': 156, u'\u0638': 128, u'\u4f0a': 92, u'\u0634': 90, u'\u03bb': 88, u'\u03b3': 86, u'\u0631': 66, u'\u03bc': 60, u'\u03b8': 57, u'\u2158': 44, u'-': 38, u',': 35, u'\n': 28, u'.': 28, u'\u5409': 24, u'\u0639': 23, u'\xbd': 22, u'\u5f00': 18, u'T': 9, u'\u2019': 8, u'!': 8, u'A': 8, u'G': 7, u'"': 6, u'E': 6, u'I': 5, u'O': 5, u'S': 5, u'R': 5, u'U': 5, u')': 4, u'(': 4, u'B': 4, u'\u2154': 4, u'M': 3, u'N': 3, u'W': 3, u';': 3, u'\u2014': 2, u"'": 2, u':': 2, u'\u03c0': 2, u'H': 2, u'1': 2, u'0': 1, u'8': 1, u'C': 1, u'F': 1, u'L': 1, u'P': 1, u'Y': 1, u'2': 1, u'9': 1})

Now comparing the counter values as it is sorted in the descending order I could replace them in the text with the following python script.

I started replacing the characters one by one and I used this tool to convert the unicode values to text online.

1
2
3
4
5
6
7
8
9
10
11
infile = open('output_final.txt','r')
data = infile.read()

replacements = {u'\u064a':'e',u'\u062e':'t',u'\u03b9':'a',u'\u03b2':'o',
u'\u2153':'i',u'\u2157':'n',u'\u03b7':'s',u'\u0638':'h',u'\u03b8':'b',
u'\u827e':'l',u'\u03bb':'u',u'\u0631':'m',u'\u03c7':'r',u'\u4f0a':'p',
u'\u5409':'w',u'\u0634':'d',u'\u03bc':'f',u'\u2158':'g',u'\u03b3':'c',
u'\u5f00':'k',u'\u0639':'v',u'\u00bd':'x'}

cipher = data.decode('utf-8')
print "".join([replacements.get(c, c) for c in cipher])

Finally I got the decoded text as follows.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
OMG WOW NO WAY  
IT'S UTF8 GARBAGE AMONGST THE REGULAR GARBAGE!
IT MUST BE A SUBSTITUTION CIPHER!!!!!
to be honest, i was surprised at the difficultl teams experienced in solving this problem.
however, i hope anl teams that were stlmied bl it don’t think it was necessarill unfair, πust difficult.
i think each step in this solution could be catallled bl logical clues, and nothing re⅔uired
pure trial-and-error. i think the difficultl delta from the rest of pactf might have exacerbated the
difficulties teams faced: perhaps people were expecting a problem closer to the round 1 hard problems.
additionalll, it’s a fairll non-standard ctf problem. i’d argue that works to the problem’s advantage,
and from what i’ve seen people who haven’t done too manl ctfs seemed to like it more, but that’s anecdotal.
if lou disagree, or want to share anlthing else, feel free to contact me! hope lou enπoled the ctf as a whole!
also, if lou like problems like this, make sure to check out pactf 2019 in manl months’ time.
-- nicholasm
keep going.
don't be evil.
this challenge is intuitive.
here is a tinl shortened clue to the document lou will need. keep lour head in the cloud.
tinlflaglink
--------------------------------
useless chunks of english text to make fre⅔uencl anallsis easier (ignore below this point):

** REDACTED **

One thing to note here is, y and l are same in many places in the text. I don’t know maybe the organisers messed it up a bit..
Also they didn’t even read the decoded text which contains the string make sure to check out pactf 2019.
I also had trouble finding the flag as the link given was incomplete.
After some tinkering I found the flag on the link : https://tinyurl.com/tinyflaglink

I guessed tinyurl part from the PACTF writeup.

And finally we got our intuitive flag.


Thanks for Reading this writeup..
Subscribe to my Newsletter for more updates regarding CTFs.
Also Feedback is always appreciated.
It won’t take much time though.

And Keep Escalating the Privileges
Happy Hacking ;)