Whenever I have a bigger PCAP I use a Windows program called Network Miner to get a graphical overview of different attributes of the traffic in the PCAP. Network Miner needs a PCAP file (i.e. not a PCAP-NG file) so I used Wireshark to "Save As" a PCAP file before importing it into Network miner.

In Network Miner, I used the Session window to sort the connections found in the PCAP by different attributes. I sorted by "Server Port" column to notice that the user conducted a port scan against the target server

I then checked the SSH connections and noted the timestamps of the connections. I noted that the final SSH connection attempts happened some minutes after the port scan completed.

I then examined some of the other tabs in Network miner. In the Parameters window I noticed quite a few Referrer entries from a Russian blog post about "port knocking":

I also found some more "port knocking" hints in the DNS tab:

Lastly, there was a couple of other things I noted in the images tab, a few items mentioning "hidden files":

Ok so now I feel it's time to switch over to Wireshark because I have a good idea of the method of attack here:

Port knocking, to open an SSH port

Hidden files on the host

In Wireshark I setup a filter to just examine traffic to/from this host in question:

"ip.src == 223.194.105.178 or ip.dst == 223.194.105.178"

Next, I scrolled down to the Frame #'s shown during the Network Miner investigation to see if the user was able to successfully get that SSH fired up.

First I double checked that the user was still getting failed SSH connections, and at Frame # 10905 & 10932 I see a failed connection attempt.

Then later, at frame 10967 & 10968 a successful connection is found:

So what changed between 10905 and 10967? Well we're thinking port knocking so let's see if we can see a weird bunch of connection attempts before this and see the following:

At this point im not 100% sure which of these are port knocking and which is just coincidental traffic. So I decided to try all of it. I came with the following command line:

Monday, May 18, 2015

Another one pointer, this time in the RE category. For this one I am going to give an example of solving something "just enough" to get the points so you can move on to the next flag. I've read other writeups about this and notice some people reversed very precisely to get a perfectly working exploit. I, on the other hand, was willing to cut corners!

The clue for this one is again brief,

accesscontrolIt's all about who you know and what you want. access_control_server_f380fcad6e9b2cdb3c73c651824222dc.quals.shallweplayaga.me:17069
With the clue is a binary file which you can find archived here.

First up I just connected to the server with netcat to see what it looked like:

root@mankrik:~/defcon/access# ./client_197010ce28dffd35bf00ffc56e3aeb9f 54.84.39.118
Socket created
Enter message : hack the world
<< connection ID: m:to/#$w'x6DYy
*** Welcome to the ACME data retrieval service ***
what version is your client?
<< hello...who is this?
<<
<< enter user password
<< hello grumpy, what would you like to do?
<< grumpy
<<
mrvito
gynophage
selir
jymbolia
sirgoon
duchess
deadwood
hello grumpy, what would you like to do?
<< the key is not accessible from this account. your administrator has been notified.
<<
hello grumpy, what would you like to do?
hello
^C

Alrighty, thats cool. We get logged in, we see a user list, and we see theres some kind of key although we don't have privs to get the key yet. The client is not interactive either, so we can't try more stuff. It's looking like we need to build our own client... So we need to reverse how this one works so we can do that.

Let's look at the network layer to watch what it sends:

Ok all normal, we got:

the version number string (as expected)

the username

the two commands "list users" and "print key".

What's up with the password though. Is that the user password?

I capture a few connections and the response to the password prompt changes each time. So this is somehow ciphered. So if we're going to write a client, we need to encrypt the password correctly...

Back to IDA! We find this code naming two functions "sub_8048EAB" and "sub_8048F67":

sub_8048EAB XORs the first 5 bytes of the given plaintext password with a key. The key is based on the connection ID (dword_804B04C) and an offset into the connection ID string is chosen based on (dword_804BC80 % 3):

The key (connection ID) is public and given to us at the start of the connection

The offset of the connection ID where the key begins is possible to calculate by deriving how dword_804BC80 is generated then computing it modulo 3.

The first two are easy, the third item requires more reversing. When you think about it, what is the point of reversing more here? So we know exactly the offset for the key? Does it matter? Let's try something in the server with netcat:

So the server, for 1 connection ID gives us multiple attempts at the password. So since the correct offset modulo 3 can only ever be 0,1,2,3 we have only a maximum of 4 password attempts before we will get the right password.

Long story short, I didn't bother reversing that bit and moved on to the second function that operates on the password before sending it:

This function is simpler, it just parses the output of the first function looking for characters <= 31 and if found, adds 32 to them. If the character == 127 it does other things.

To be fair, this client is not complete. The logic which selects the real correct answer is not in place. It was only by luck that my solution finds the correct answer some times. I have rerun the exploit about 10 times now and it solved it correctly 3 times out of those 10 attempts.

Since we're not about perfect execution, just about CTF, we leave it open ended but happy we solved it in time.

Defcon was finally here and I was so totally pumped for it. When it dropped, so did my jaw, it wasn't going to be easy. That's for sure. Defcon had some of the evilest, trickiest challenges I've ever faced in a CTF. And it was super fun because of it.

This challenge itself was a break from all the pwnables which I found really irritating in their multilayered complexity. This was simple, get in, work on data, get the flag. Single step stuff.

The challenge gives only the following clue:

Catwesternmeow catwestern_631d7907670909fc4df2defc13f2057c.quals.shallweplayaga.me 9999
Upon connecting to that host with netcat we see the following:

Given that it's talking about registers, my first thought is that maybe the binary data represents a memory dump of registers that we're supposed to unpack and send back. But after some thought, that was not much of a challenge so I looked for other vectors.

Next I thought about the terminology used in the server response. "Initial Register State"? "Send solution in same format"? Hmm. Well what operates on registers? Machine code I suppose. I wrote a quick Python client to grab just the binary data from the host into a file called "data.bin".

As I suspected, the binary blob is x86 instructions that operate on registers and then returns. Cool. Now, how to programatically do this?

Since my host binary is working so well in hosting this parasite, I decided to make GDB do all the heavy lifting in this exploit and simply script it to do what I wanted.

In Python I grab the binary data and initial states from the network, write a GDB script to load the binary data into the main() function of a hello world C program. Execute the code, crash on the "ret" instruction, examine the final state registers and send them to the server.

Monday, May 11, 2015

I worked on this one first because it was one of the earliest challenges available and for the point value must be a quick solve. We are given very little information in the challenge but at least the provided file has everything we need.

What it does is take 2 bytes of the plaintext at a time, converts that to an integer, does some math on that integer and then concatenates the result of that math to the list of existing integers to form the ciphertext.

Initially I tried to simply reverse the algorithm but that turned out not to be feasible. The output of the math results in integers that are either 2 or 3 digits long and there's no way to know from the ciphertext which you're dealing with and so you'd have a large amount of trial and error.

I next tried enciphering some sample strings and noticed that enciphering a simple "ASIS{00000000000000000000000000000000}" string gets me 14 integers closer to solving the ciphertext:

So given this idea, and the knowledge that all ASIS flags contain only hexadecimal digits 0-9 and a-f I decided to brute force attack this part of the challenge.

What I decided initially to try is to iterate each character through 0-9, a-f byte by byte. However since the algorithm takes 2 bytes of plaintext and produces 2-3 digit integers it was extremely unreliable.

The basic algorithm I came up with is:

Beginning at the 1st character in the MD5sum, encrypt the text ASIS{x0000000000000000000000000000000} call it Ci

Compare Ci with the ciphertext C by counting the number of correct integers in the result

If Ci improves the count of integers we have correct in our last pass by 2+ then put this Ci in a list and label it with how many correct integers it had. Call this result a "good result"

Once we've checked every character 0-9, a-f in that position in the plaintext then compare all of the "good results" for this position to find the best result. Assume that is correct and move to the next position.

Next I switched to n-grams and iterated through digrams and trigrams and got increasingly accurate results but finally decided to settle on quadgrams which gave 100% reliable cracking in an acceptable amount of time.

And finally, here's the code which takes the basic code provided in the challenge and uses it to crack the cipher:

#!/usr/bin/pythonimportstringimportitertools# try n-grams of how many characters at a time?atatime=4# Example: ASIS{b026324c6904b2a9cb4b88d6d61c81d1}initflag='ASIS{00000000000000000000000000000000}'encint="2712733801194381163880124319146586498182192151917719248224681364019142438188097307292437016388011943193619457377217328473027324319178428"defFAN(n,m):i=0z=[]s=0whilen>0:ifn%2!=0:z.append(2-(n%4))else:z.append(0)n=(n-z[i])/2i=i+1z=z[::-1]l=len(z)foriinrange(0,l):s+=z[i]*m**(l-1-i)returnsdefenc(plaintext):hflag=plaintext.encode('hex')iflag=int(hflag[2:],16)i=0r=''whilei<len(str(iflag)):d=str(iflag)[i:i+2]nf=FAN(int(d),3)r+=str(nf)i+=2returnrdefcompareit(attemptstr):enclist=list(encint)attempt=list(attemptstr)correct=0forcinrange(len(enclist)):ifattempt[c]==enclist[c]:correct+=1else:breakreturncorrect# start exchanging pairs at the n'th columnstartchar=5# baseline from that column of correct integers in outputbaseline=14alphabet='0123456789abcdef'pair=startcharcurrentflag=initflagprint"[+] Cracking ciphertext..."whilepair<len(initflag)-1:flaglist=list(currentflag)goodresults=[]# for every pair of hexdigitsformaybeinitertools.product(alphabet,repeat=atatime):flaglist[pair]=maybe[0]flaglist[pair+1]=maybe[1]flaglist[pair+2]=maybe[2]flaglist[pair+3]=maybe[3]tryflag="".join(flaglist)attempt=enc(tryflag)result=compareit(attempt)ifresult>baseline+2:maybelist=list(maybe)maybelist.append(result)goodresults.append(maybelist)bestresult=0bestfit=""# Parse the good results looking for the best resultforringoodresults:ifr[atatime]>bestresult:bestresult=r[atatime]flaglist[pair]=r[0]flaglist[pair+1]=r[1]flaglist[pair+2]=r[2]flaglist[pair+3]=r[3]currentflag="".join(flaglist)print"[+] Flag so far: "+currentflagpair+=atatimeprint"[+] Flag: "+currentflag

Monday, May 4, 2015

captchaWe've got a rather strange png file. Very strange png. Something isn't right about it...png
Stego challenges are not my favorite but still I gave this one a try because I felt the point value meant it would be a reasonably quick solve.

The png file when viewed just appeared to be a single 256x256 image of the letter "i". Sort of like this (this is not the actual PNG from the challenge):

When we investigated further though it's rather large in size to be a single image:

Oh cool, 1892 PNG files embedded inside. Wow, what are all those files of I wonder:

Each PNG file contained a single letter. At first I thought this was the flag, that there would be a message in this and I could just read it back and win the points. Unfortunately this was just step one. After scrolling through the image previews in my Linux file explorer it quickly made sense that these images described a base64 encoded string. The biggest give away of this was the final image in the list was an "=".

So it seemed like the next step was to decode these images, get a base64 string, decode the string into a binary and get the flag. How to do that?

I've read other writeups of this challenge and saw that other people approached this in a smarter way than I did. I did this the long way, with OCR. I think if you want to know the best way to do this challenge, read those writeups. If you want to know how to OCR large groups of single letter images, read on!

Having never done any OCR before, this was going to be fun. First I found that on Linux one of the accepted OCR solutions is called Tesseract OCR and a Python interface to Tesseract OCR is called PyTesser.

It seems to use PyTesser we just import it and use image_to_string from a PIL image in memory. Note here that we used PyTesser 0.0.1 which uses PIL. It seems on Github there's a new version of PyTesser that uses OpenCV. I'm certain OpenCV is better but im more familiar with PIL so i'm happy to use the old version.

Cool! Output! Oh but wait. It's totally wrong. Doh! Firstly base64 strings don't have "." in them. It must be interpreting the lowercase i characters as a ".". Half the other characters are similarly mangled. Ouch. So not good enough, it seems our OCR library needs more context about the letters so it can do a better job at OCR?

Firstly let's tweak tesseract a bit more. I learnt about character whitelists so I set one up containing only the base64 alphabet:

Next I modify our script so that it imports some number of images into a single image and then OCRs them all at once. This greatly increases our OCR efficiency but still not perfect results:

#!/usr/bin/pythonfrompytesserimport*importosimportImageimportsubprocessimportbase64PNG_PATH='output/png/'BEST_WIDTH=38BEST_HEIGHT=50# use factors of the total number of files or this will errorCHARS_WIDE=43print"[+] Foremosting input..."subprocess.call(['foremost','-Q','capthca.png'])pngfiles=[fforfinos.listdir(PNG_PATH)ifos.path.isfile(os.path.join(PNG_PATH,f))]pngfiles.sort()flag64=""print"[+] Processing "+str(len(pngfiles))+" image files "+str(CHARS_WIDE)+" characters at a time."im=Image.new("RGB",(BEST_WIDTH*CHARS_WIDE,50),"white")idx=0forfinpngfiles:letter=Image.open(PNG_PATH+f)width,height=letter.sizeleft=(width-BEST_WIDTH)/2top=(height-BEST_HEIGHT)/2right=(width+BEST_WIDTH)/2bottom=(height+BEST_HEIGHT)/2letter=letter.crop((left,top,right,bottom))im.paste(letter,(idx*BEST_WIDTH,0))idx+=1ifidx==CHARS_WIDE:thislot=image_to_string(im)thislot="".join(thislot.split())flag64+=thislotidx=0print"[+] Base64 of flag PNG: "+flag64

Now i'm really unhappy because I've spent a bit of time on these 150 points and I wan't to just solve it now. Not pretty code any more, just solution. So instead of fussing more with this I resign myself to a manual process of weeding out these final byte errors using the following script.

It assembles a string of characters, OCR's the string, displays it and asks the user to proof read. If any issues are found it can correct them. This requires about 10-20 minutes of the user's time but as I said. I just wanted a solution and 10-20 minutes of focused reading seemed like a good trade off at this point:

#!/usr/bin/pythonfrompytesserimport*importosimportImageimportsubprocessimportbase64PNG_PATH='output/png/'BEST_WIDTH=38BEST_HEIGHT=50# use factors of the total number of files or this will errorCHARS_WIDE=43print"[+] Foremosting input..."subprocess.call(['foremost','-Q','capthca.png'])pngfiles=[fforfinos.listdir(PNG_PATH)ifos.path.isfile(os.path.join(PNG_PATH,f))]pngfiles.sort()flag64=""print"[+] Processing "+str(len(pngfiles))+" image files "+str(CHARS_WIDE)+" characters at a time."im=Image.new("RGB",(BEST_WIDTH*CHARS_WIDE,50),"white")idx=0forfinpngfiles:letter=Image.open(PNG_PATH+f)width,height=letter.sizeleft=(width-BEST_WIDTH)/2top=(height-BEST_HEIGHT)/2right=(width+BEST_WIDTH)/2bottom=(height+BEST_HEIGHT)/2letter=letter.crop((left,top,right,bottom))im.paste(letter,(idx*BEST_WIDTH,0))idx+=1ifidx==CHARS_WIDE:im.show()thislot=image_to_string(im)thislot="".join(thislot.split())print"[+] OCRd: "+thislotcheck=raw_input("These ok? >> ")if'n'incheck:redo=list(thislot)chk=0forrinredo:ok=raw_input("Correct this "+r+"["+r+"] >> ")ifok<>'':redo[chk]=okchk+=1thislot="".join(redo)flag64+=thislotprint"[+] Corrected: "+thislotsubprocess.call(['killall','-9','display'])else:flag64+=thislotsubprocess.call(['killall','-9','display'])flag64="".join(flag64.split())idx=0im=Image.new("RGB",(BEST_WIDTH*CHARS_WIDE,50),"white")subprocess.call(['rm','-fr','./output/'])f=open('flag.b64','wb')f.write(flag64)f.close()print"[+] Base64 of flag PNG: "+flag64

And the output looks like this, it'll throw up an image on your screen:

And so on... You get the idea. You'd think we'd be finished by now right? Noooo. There was a time bomb in this challenge that I only just now discovered. And that is the upercase letter "I" looks identical to the lowercase letter "l". The OCR and even human recognition steps have no way to tell these apart. The best I could do after the best OCR and human recognition i could do resulted in a dud file still:

So I was stuck here and ready to throw in the towel, but I had one more idea. I didn't need to make the file perfect, I just needed enough image data to read a flag. So I counted the number of letter I's in the base64. There were 29 of them. So I had 229 (536,870,912 different possible file combinations here. I don't NEED a perfect image though. So here was my final idea:

First, only permutate the data in the obvious part of the base64 data where the actual useful image data is found. This obvious when you see all the base64 data.

Only try some reasonable number of permutations

So the base64 data we needed to focus on was quite obvious to the naked eye. I've highlighted it below. It's obvious because the surrounding data is repeated QEEBAYhEBAY etc which looks like it might be a repeating data, like a blank background of the image maybe?

The "reasonable" number of permutated I/l combinations I settled on was 211 which is just 2,048 combinations. If I could get enough image data in the first 11 I/l substitutions, I would be super happy.

Here's the final code:

#!/usr/bin/python## Captcha solver for VolgaCTF 2015.# CaptureTheSwag - capturetheswag.blogspot.com# by dacatfrompytesserimport*importosimportImageimportsubprocessimportbase64importitertoolsPNG_PATH='output/png/'ATTEMPT_PATH='attempts/'BEST_WIDTH=38BEST_HEIGHT=50# use factors of the total number of files or this will errorCHARS_WIDE=43alphabet=('I','l')MAXLEN=11print"[+] Foremosting input..."subprocess.call(['foremost','-Q','capthca.png'])pngfiles=[fforfinos.listdir(PNG_PATH)ifos.path.isfile(os.path.join(PNG_PATH,f))]pngfiles.sort()flag64=""print"[+] Processing "+str(len(pngfiles))+" image files "+str(CHARS_WIDE)+" characters at a time."im=Image.new("RGB",(BEST_WIDTH*CHARS_WIDE,50),"white")idx=0forfinpngfiles:letter=Image.open(PNG_PATH+f)width,height=letter.sizeleft=(width-BEST_WIDTH)/2top=(height-BEST_HEIGHT)/2right=(width+BEST_WIDTH)/2bottom=(height+BEST_HEIGHT)/2letter=letter.crop((left,top,right,bottom))im.paste(letter,(idx*BEST_WIDTH,0))idx+=1ifidx==CHARS_WIDE:im.show()thislot=image_to_string(im)thislot="".join(thislot.split())print"[+] OCRd: "+thislotcheck=raw_input("These ok? >> ")if'n'incheck:redo=list(thislot)chk=0forrinredo:ok=raw_input("Correct this "+r+"["+r+"] >> ")ifok<>'':redo[chk]=okchk+=1thislot="".join(redo)flag64+=thislotprint"[+] Corrected: "+thislotsubprocess.call(['killall','-9','display'])else:flag64+=thislotsubprocess.call(['killall','-9','display'])flag64="".join(flag64.split())idx=0im=Image.new("RGB",(BEST_WIDTH*CHARS_WIDE,50),"white")subprocess.call(['rm','-fr','./output/'])print"[+] OCR Completed. Creating permutations"header=flag64[:361]body=flag64[361:]try:os.makedirs(ATTEMPT_PATH)exceptOSErrorasexc:pass# fiddle with all the l/I combinations and create a lot of PNGsforiinitertools.product(alphabet,repeat=MAXLEN):idx=0bodylist=list(body)forcharinrange(len(bodylist)):if'I'inbodylist[char]:bodylist[char]=i[idx]idx+=1ifidx>=MAXLEN:flag=header+"".join(bodylist)flagbin=base64.b64decode(flag)f=open(ATTEMPT_PATH+'attempt'+"".join(i)+".png",'wb')f.write(flagbin)f.close()breakprint"[+] Completed, copy the png files from the "+ATTEMPT_PATH+" folder to a Windows system and find the flag!"

And when we run it, we find 2,048 PNG files. Copying them to Windows 7 system allows simple browsing with Extra Large thumbnail mode right in explorer: