Monday, January 12, 2015

Happy New Year and a CTF Challenge Write-Up

Happy New Year!!!

I'd like to start the first blog post of 2015 by wishing the 7 people who read this blog a Happy New Year! As a new year's resolution, I am challenging myself to write at LEAST one (1) blog post per month. We'll see how well it goes. Without further ado, here is January's.

In December, two people (@akiym and @xrekkusu) put together an Advent Calendar Capture The Flag competition (ADCTF). This was an awesome and unique CTF where every day in December, a small challenge was released. I was able to complete a couple of these challenges, but wanted to take some time to do a write up on my favorite one.

Before we get into it, a quick disclaimer: I will be posting a solution. According to the rules of the CTF this is not allowed. Therefore, I waited awhile (over two weeks after the challenge ended) to post this solution. I don't agree with the rule of NEVER releasing a write up. Therefore, as someone who personally identifies as a pirate, I scoff at rules. ARRRGGGGGHHHHHH. 

A lot of people, including myself learn the most from the trying and failing process, but eventually we all need a hand. So in the spirit of that, I will try and keep the write up linear in the sense that, if you need a hint, just keep reading until you get to the part where you are stuck, get a hint or two and then stop reading and continue trying. 

If it wasn't clear enough .... 

SPOILER ALERT

SPOILER ALERT

SPOILER ALERT

Okay, fair warnings aside let's get into the challenge. This was the Day 9 PPC Challenge dubed "qrgarden".


Apart from the lacking hint of "read a lot" there is a png file:

It is hard to see, since it is compressed down, but what you are looking at is 10,000 QR codes. 100 x 100. I know it is 10k QR codes for two reasons:

    1) I measured the size of 1 QR code in a graphics program
    2) I saw this tweet: https://twitter.com/akiym/status/541974508064620544.

So based on the clues, the challenge consists of reading every, single, last, QR code and finding the one that starts with "ADCTF_"

If for some reason, you can get the png file from the CTF website, I've mirrored it on my GitHub here: https://raw.githubusercontent.com/JC-SoCal/ADCTF_Day9_PPC_qrgarden/master/qrgarden.png

So, how do we read 10k QR codes? The way I thought of doing it was pretty simple. I will leverage Python, the Python Imaging Library (PIL) to slice the image into individual QR codes and then use the python qrtools library to read each one. 

Step 1: Slice and Dice

In order to read each QR code, we need to slice and dice this big png into individual QR codes. The first thing I did was to determine how big each code was.

While in my Kali Linux VM, I used the file program on the image and got 8700 x 8700 pixels as the size in pixels which means each QR code is 87x87 pixels.


The process I took was to slice the large png HORIZONTALLY into 100 new images each being 1 qr code high by 100 qr codes wide. Then for each of the horizontal strips, to dice each of those new 100 images VERTICALLY into single QR codes.

I wrote the below function called slice_and_dice() to do just that. I assume you could use this any time you needed to slice and dice something into squares. This tool will save each single qr code to a directory of your choice.

I have also posted this in GitHub: https://github.com/JC-SoCal/ADCTF_Day9_PPC_qrgarden/blob/master/slice_and_dice.py

1:  from PIL import Image  
2:  import math  
3:  import os  
4:    
5:  def slice_and_dice(img_path, output_dir, horizontal_size, vertical_size):  
6:   #set statistics  
7:   total_h_slices = 0  
8:   total_v_slices = 0  
9:   total_s_slices = 0  
10:    
11:   filename = os.path.split(img_path)[1]  
12:   img = Image.open(img_path)   
13:     
14:   #get image size, and the starting points  
15:   h_width, h_height = img.size   
16:   h_upper = 0   
17:   h_left = 0  
18:     
19:   # get how many horiztonal slices to make  
20:   h_slices = int(math.ceil(h_height/horizontal_size))   
21:    
22:   #make the horizonal slice  
23:   for row_count in range(1,h_slices+1):  
24:    h_lower = int(row_count * horizontal_size)   
25:    h_bounding = (h_left, h_upper, h_width, h_lower)  
26:    horizontal_slice = img.crop(h_bounding)  
27:    # increment the upper boundry     
28:    h_upper += horizontal_size   
29:    print "Made horizontal slice #"+str(row_count)  
30:    total_h_slices += 1  
31:    
32:    # slice the horizontal slice vertically into singles  
33:    v_width, v_height = horizontal_slice.size  
34:    v_upper = 0  
35:    v_left = 0  
36:    v_lower = v_height  
37:    v_slices = int(math.ceil(v_width/vertical_size))  
38:    
39:    for column_count in range(1, v_slices+1):  
40:     v_width = int(column_count * vertical_size)     
41:     v_bounding = (v_left, v_upper, v_width, v_lower)  
42:     single_slice = horizontal_slice.crop(v_bounding)  
43:     v_left += vertical_size  
44:     print "Made vertical slice #" + str(column_count)  
45:     total_v_slices += 1  
46:       
47:     #save the single slice  
48:     single_slice.save(os.path.join(output_dir, filename +"_row" + str(row_count) +"_column" + str(column_count)+".png"))  
49:     print "Saved slice: " + filename +"_row" + str(row_count) +"_column" + str(column_count)+".png"+ str(column_count)  
50:     total_s_slices += 1  
51:    
52:   #print the stats  
53:   print "Total Horizontal Slices Made:", str(total_h_slices)  
54:   print "Total Vertical Slices Made:", str(total_v_slices)  
55:   print "Total Slices Saved:", str(total_s_slices)  
56:    
57:  slice_and_dice("qrgarden.png", 'output/', 87, 87)  

So this worked out very well and provided me with 10,000 individual QR codes ...


Step 2: Read all the QR codes

Suddenly, reading all the QR codes doesn't seem so daunting. To make life easier I used a python library called qrtools. The script I wrote will take a directory and a search term and decode all the QR codes in that directory while searching for the search term provided. This code is also on my GitHub at: https://github.com/JC-SoCal/ADCTF_Day9_PPC_qrgarden/blob/master/read_qr_codes.py

1:  import os  
2:  import qrtools  
3:    
4:  def read_qr_codes(directory, search_term, verbose=True):  
5:    # initalize a list for holding our hits  
6:    matches = []  
7:    
8:    # for each qr image in the directory ....  
9:    for f in os.listdir(directory):  
10:      img_path = os.path.join(directory, f)  
11:      code = qrtools.QR(filename=img_path)  
12:      # check if we can decode the image  
13:      if code.decode():  
14:        # convert the code to a string  
15:        code_value = code.data_to_string()  
16:        # if verbose is toggled, output the qr code values  
17:        if verbose: print img_path + ", " + code_value  
18:    
19:        #if we find the search term add it to the matches list  
20:        if search_term in code_value:  
21:          matches.append({img_path: str(code_value)})  
22:      else:  
23:        print "Could not decode:", img_path  
24:    
25:    #print all the matches  
26:    print len(matches), "match(es) were found:"  
27:    for finding in matches:  
28:      for key in finding:  
29:        print key + ', ' + finding[key]  
30:    
31:  read_qr_codes('/root/Desktop/day9/output','ADCTF_', False)  

After running this, I had my flag and this challenge was completed:



Step 3: PROFIT!

I blurred the answer out so at a minimum you at LEAST had to copy and paste the code to complete the challenge.

I hope you enjoyed this small write up and code. Please leave a comment if you found this helpful or were able to use it to solve a different CTF or challenge!