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




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:

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:

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:

1:  from PIL import Image  
2:  import math  
3:  import os  
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  
11:   filename = os.path.split(img_path)[1]  
12:   img =   
14:   #get image size, and the starting points  
15:   h_width, h_height = img.size   
16:   h_upper = 0   
17:   h_left = 0  
19:   # get how many horiztonal slices to make  
20:   h_slices = int(math.ceil(h_height/horizontal_size))   
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  
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))  
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  
47:     #save the single slice  
48:, 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  
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)  
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:

1:  import os  
2:  import qrtools  
4:  def read_qr_codes(directory, search_term, verbose=True):  
5:    # initalize a list for holding our hits  
6:    matches = []  
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  
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  
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]  
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!

No comments:

Post a Comment