Breaking the MintEye image CAPTCHA in 34 lines of Python

Several people have suggested that importing OpenCV is cheating and as such the claim that MintEye was broken in 23 lines of Python is disingenuous. So, here’s a solution in 34 lines of Python:

import sys
import os
import matplotlib.pyplot as plt
import math
from PIL import Image

for dir in range(1,14):
    dir = str(dir)

    total_images = len(os.listdir(dir))+1

    points_sob = []
    for i in xrange(1,total_images):
        image ='/'+str(i)+'.jpg')
        im = image.load()

        #convert to grayscale (ITU-R 601-2 luma transform)
        grey = [[None for _ in range(image.size[1])] for _ in range(image.size[0])]
        for x in xrange(image.size[0]):
            for y in xrange(image.size[1]):
                grey[x][y] = im[x,y][0]*299/1000 + im[x,y][1]*587/1000 + im[x,y][2]*114/1000

        sum_of_sob = 0
        for x in xrange(1,image.size[0]-1):
            for y in xrange(1,image.size[1]-1):
                gx = -grey[x-1][y-1] + grey[x+1][y-1] - 2*grey[x-1][y] + 2*grey[x+1][y] - grey[x-1][y+1] + grey[x+1][y+1]
                gy = -grey[x-1][y-1] - 2*grey[x][y-1] - grey[x+1][y-1] + grey[x-1][y+1] + 2*grey[x][y+1] + grey[x+1][y+1]
                sum_of_sob = sum_of_sob + math.sqrt(gx*gx + gy*gy)

        print sum_of_sob

    res = points_sob.index(min(points_sob)) + 1
    x = xrange(1,total_images)
    plt.plot(res,points_sob[res-1], marker='o', color='r', ls='')
    plt.plot(x, points_sob)


The point I’m trying to make is that Sobel is a very simple operator – that’s why it was created, as a crude approximation of the derivative of a 2D signal. In fact, the only non-trivial maths imported from OpenCV previously are the DCTs to decode the JPEGs.

Breaking the MintEye image CAPTCHA in 23 lines of Python

As an avid reader of HAD I was intrigued by this post explaining how someone had broken MintEye’s audio based CAPTCHA.  The image version of the CAPTCHA looked interesting and so I thought it might be fun to try and break it.

For those unfamiliar with MintEye, the image based CAPTCHAs look as follows:

You must adjust a slider to select the undistorted version of the image.  Several somewhat naive approaches (in my opinion) were proposed in the HAD comments to solve this captcha, based on looking for straight lines.  However, such solutions are likely to fall down for images containing few straight lines (e.g. the CAPTCHA above).

After a little thought (and unfruitful musings with optical flow) I found a good, robust and remarkably simple solution. Here it is:

import cv2
import sys
import numpy as np
import os
import matplotlib.pyplot as plt

if __name__ == '__main__':

    for dir in range(1,14):
        dir = str(dir)

        total_images = len(os.listdir(dir))+1
        points_sob = []

        for i in range(1,total_images):
            img = cv2.imread(dir+'/'+str(i)+'.jpg')
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

            sob = cv2.Sobel(gray, -1, 1, 1)

        x = range(1,total_images)
        res = np.argmin(points_sob)+1
        print res
        plt.plot(res,points_sob[res-1], marker='o', color='r', ls='')
        plt.plot(x, points_sob)


(Note the majority of the code is image loading and graph plotting. Automatically fetching the images and returning the answer is left as an exercise for the dirty spammers)

The theory is this: the more you ‘swirl’ up the image, the longer the edges in the image become. You can see this in the example above, but a simpler example is more obvious:

See how the length of the square box clearly increases? To exploit this, we want to sum the length of the edges in the picture.  This simplest way of doing this to to take the derivative of the image (in the Python above, by using the Sobel operator) and sum the result (take a look at the Wikipedia article to see how Sobel picks out the edges).  We then select the image with the lowest ‘sum of edges’ as the correct answer.

The results are excellent  as you can see below.  100% of the 13 test CAPTCHAs I downloaded were successfully solved.  The following graphs show image number on the x axis and ‘sum of edges’ on the y.  The red dot is the selected answer:

An interesting feature is that the completely undistorted image is often a peak in the graphs. This means we usually select one image to the right or left of the correct image (which is still happily accepted as the correct answer by MintEye).  This seems to be because the undistorted image is somewhere sharper than the distorted images and hence has sharper gradients resulting in larger derivative values.  Obviously it would be trivial to do a local search for this peak, but it isn’t required to break this CAPTCHA.

In conclusion, it would seem this method of image based CAPTCHA is fundamentally flawed.  A simple ‘swirl’ operation will always be detectable by this method, no matter the image being swirled.  The increased sharpness also gives the game away – an FFT or autocorrelation could easily be used to detect this change in sharpness, just like autofocus algorithms.

Hardware Breakpoints: VirtualBox vs VMware

Debugging code which doesn’t want to be debugged (think software protection, anti-debugging tricks etc.) can be a headache.  Some plugins do exist for OllyDbg (Phantom, StrongOD to name but a few) which can help relieve the pain, but most of them require WinXP in order to work, due to employing the kind of tricks root kits use in order to hide the presence of the debugger from the debuggee.  Now unless you have a spare box lying around, the most convenient way to run XP is using virtualisation.

The first virtualisation software I ever used was VirtualBox and this served me well for everything I wanted to do.  I could debug without problem.  However, after upgrading my computer (E6300 -> 2500K), hardware breakpoints stopped working under VirtualBox.  At first I thought it was some new anti-debugging trick, but after trying and failing to use a hardware breakpoint in a completely unprotected program, I decided to try a different peice of virtualisation software.

After installing VMware, hardware breakpoints were firing perfectly once more.  So it seems VirtualBox doesn’t necessarily support hardware breakpoints, depending on your hardware.

Saleae Logic Analyser Clone Teardown and Reprogramming

I bought a cheap ($18 shipped) logic analyser from China (via AliExpress) which recently arrived in the post.  It’s a clone of the $150 Saleae Logic Analyser.  Here it is:

And here are its insides:

A rather grubby looking board.  I also noticed a couple of small solder balls which could have been shorting out a couple of the pins of the main IC, the Cypress CY7C68013A, so I removed them.  Having done some research on the Saleae Logic, the components didn’t come as much surprise.  The CY7C68013A is an 8051 microprocessor with a USB transceiver bolted on.  Logic states are read via an 8 bit wide IO port and sent back over USB to the software.  The maximum claimed sample rate is 24Mhz.

The only difference to the Saleae Logic is the addition of an HC245, an ‘Octal 3-State Noninverting Bus Transceiver’, on the back of the board.  This was probably added to try and protect the inputs of the CY7C68013A from overvoltage, but it’s ability to do so is questionable.  Still, for all intents and purposes, this clone should function exactly the same as a real Saleae Logic.

Note the 2Kb (256 byte) EEPROM on the bottom right of the final photo.  This contains configuration data for the CY7C68013A, in particular the VID/PID combination. The VID is a 16-bit vendor number (Vendor ID) and the PID is a 16-bit product number (Product ID).  This allows the product to be identified as a ‘Saleae USB Logic Analyzer’, instead of a generic CY7C68013A device.  Also, note that the EEPROM contains no firmware – the firmware is loaded over USB on startup of the logic analyser software.  This make the board itself very generic – the only thing that separates this device from many others on the market is the 4 byte VID/PID!  More on this later…

Testing the Accuracy

I installed the Saleae Logic software, plugged in the clone and it was recognised instantly.  It seemed to work!  Next, I decided to test its accuracy.  I programmed a PIC to output a periodic signal as fast as possible (the chip was clocked at 8MHz):


I measured the output with the logic analyser first – it showed a high time of 500ns and a low time of 1500 nanoseconds.  Next I measured the signal with an oscilloscope and measured it to be exactly the same (to my eye!).  So, the accuracy certainly seems reasonable up to at least 500kHz.  (I should have tested at a higher speed, but didn’t have anything faster to hand).

Cloning Other Devices

Another product on the market, the USBee, uses exactly the same hardware.  I was aware of other clones which allowed you to select, via jumper, impersonating either a Saleae or USBee, so I wanted to see if I could do this myself.

The procedure boils down to modifying the VID/PID contained in the EEPROM mentioned earlier.  The software, e.g. Saleae Logic looks for the right VID/PID of the hardware.  If it finds the right VID/PID, it uploads the firmware and starts working.  The same can be said for the USBee software, albeit with a different VID/PID.  Now, the obvious way to change the VID/PID contained in the EEPROM in order to fake a USBee is to desolder the EEPROM, reprogram it and solder it back, but there’s a better way.

Some googling quickly led to this article, explaining how to read the EEPROM contents using the CY7C68013A (which the EEPROM is of course connected to).  So, I downloaded the Cypress EZ-USB development kit (cy3684_ez_usb_fx2lp_development_kit_15.exe) which contained the tools to allow this.

Here’s my step by step guide for doing it with Win7 x64 (note that steps 2 and 5 can be ignored on Win XP.  Infact, if you have an XP box available, it’s easier to do it on that).

  1. Uninstall the Saleae Logic Driver, using Device Manager.
  2. We want to install the generic cyusb.sys Cypress driver which will allow us to send commands to the device using CyConsole.  If you’re using Win XP, goto step 3. Else, download Cypress Suite USB (CySuiteUSB_3_4_7_B204.exe) which contains the Win7 x64 driver.
  3. Locate the appropriate cyusb driver.  For Win 7 x64, this is: C:\Cypress\Cypress Suite USB 3.4.7\Driver\bin\wlh\x64.  For Win XP, we can use the older version of the driver, found in the Dev Kit package, here: C:\Cypress\USB\Drivers\CyUsb.
  4. Open cyusb.inf.  This must be modified to contain the VID/PID of our clone (which is also the VID/PID of the Saleae Logic), so that Windows will accept and install the driver CyUSB.  So, replace all instances of USB\VID_XXXX&PID_XXXX with USB\VID_0925&PID_3881 (note that the VID/PID of the device can be found in device manager).  Also, make sure to uncomment (remove the semi-colon) the lines looking like this: “%VID_0925&PID_3881.DeviceDesc%=CyUsb, USB\VID_0925&PID_3881″.
  5. If you’re Running Windows 7 (and also Vista, I think), you’ll need to force Windows to accept installing unsigned kernel drivers. To do so, restart your computer and press F8 during the boot process, as you do to start in safe-mode.  However, instead of selecting safe-mode from the boot menu, select ‘Disable Driver Signature Enforcement’. Note that this is temporary – the driver won’t remained installed after a reboot.  This is fine for our purposes.
  6. Install the CyUSB driver.
  7. Start CyConsole.
  8. Select ‘EZ-USB Interface’ from the Options menu.
  9. Press the ‘Download’ button.  This allows us to upload our own firmware.
  10. Select the file C:\Cypress\USB\Examples\FX2LP\Vend_ax\Vend_Ax.hex.  You should see the firmware being uploaded and the 8051 will be reset.
  11. Let’s first read our current EEPROM contents:
    • Enter ’0xA2′ into the ‘Req’ box next to the ‘Vend Req’ button.
    • Set ‘Length’ to 8
    • Make sure ‘Dir’ is set to ’1 IN’
    • Now click ‘Vend Req’. You should see 8 bytes of Hex codes starting with ‘C0′. My device read out: ‘C0 25 09 81 38 1B 00 00′.  Note the VID (09 25) and PID (38 81) are stored in little-endian format.  C0 is required by the Cypress chip.  I am unsure what 1B is for.
  12. Now let’s program the VID/PID of the USBee ZX into our EEPROM:
    • Enter the bytes ’C0 A9 08 05 00 84 23 00′ into the ‘Hex Bytes’ field.
    • Now change the ‘Dir’ box to ’0 OUT’
    • Click ‘Vend Req’. The bytes written will be echoed back to the console.
  13.  That’s it!  Make sure your have the USBee Suite installed along with the ZX test pod. Pull out the device, plug it back in and it should be recognised by USBee as the ZX model.

So, while not as easy as switching a few jumpers, the device can be made to look like a USBee ZX without too much effort.  I believe that the USBee driver is just a signed version of the CyUSB driver, which means that the device is recognised by CyConsole (as opposed to the Saleae driver, which seems to be modified such that the device doesn’t show up in CyConsole, hence the rigmarole of installing CyUSB above).  This means that changing back to Saleae Logic is as simple as following steps 7 to 12, but programming with our original bytes ‘C0 25 09 81 38 1B 00 00′.

If you’re constantly switching between firmwares, a better idea may be to remove the EEPROM and solder an 8 DIP socket in it’s place.  By cutting a hole in the back of the case, the EEPROM could then easily be changed.

MythSimpleVideos Released!

I’ve finally gotten around to releasing my new MythTV plugin.  Take a look at it here:

“MythSimpleVideos is my MythTV plugin which provides alternative functionality to MythVideo. It was created to allow simple, fast streaming of videos from a NAS device.”


  • Fast directory listing. Does not recursively scan like MythVideo (in ‘File Browse Mode’), which destroys NAS performance.
  • No requirement to ‘Scan For Changes’ (in comparison to MythVideo in the default mode).
  • ‘One Click Viewing’ – automatically selects the best video file to play in the selected directory.
  • Quick file deletion – finished watching your video? rm -r the directory!

Missing Features

  • No metadata – I don’t want/need it.
  • No subdirectory viewing/playing (may be added in future).


(It’s supposed to be basic!)

Solution to Part Three of the GCHQ ‘’

Download the exe found in the previous part:

Load it up in Olly and we immediately find we need cygcrypt-0.dll.  To get this dll, download cygwin and install.  This dll is not installed by default – in the installation, search and select all references to ‘ssh’ and ‘crypt’.

Now run the exe. We’re told:

usage: keygen.exe hostname

So, set the execution argument in Olly to  Next, let’s analyse the behaviour of the program – it’s small and basic!

Here’s some pseudo code detailing its operation:

Read first line of file 'license.txt' into buffer
Are the first four characters gchq? If not, error.
Compute crypt(buffer+4,"hqDTK7b8K2rvw")
Does result of crypt equal hqDTK7b8K2rvw? If not, error.
Create request string /hqDTK7b8K2rvw/AAAAAAAA/BBBBBBBB/CCCCCCCC/key.txt
(AA/BB/CC are the hex representations of the next three groups of four characters)
Perform HTTP request using above string, accessing host provided by the first argument (which we set to

So, license.txt should contain:

Note that we don’t yet know password.  However, let’s just bypass the check of the crypted password:

MOV EAX,DWORD PTR DS:[402000]     ;ptr to string hqDTK7b8K2rvw
MOV DWORD PTR SS:[ESP+4],EAX      ;move onto stack
LEA EAX,DWORD PTR SS:[EBP-38]     ;pointer to gchqPASSWORDabcdefghijkl
ADD EAX,4                         ;eax now points to PASSWORDabcdefghijkl
MOV DWORD PTR SS:[ESP],EAX        ;move onto stack
CALL 0040142C                     ;  <JMP.&cygcrypt-0.crypt> (call crypt)
MOV DWORD PTR SS:[ESP+4],EAX      ;put ptr to hqDTK7b8K2rvw onto stack
MOV DWORD PTR SS:[ESP],EDX        ;put str to crypt result onto stack
CALL 00401530                     ;strcmp
JNZ SHORT 004011A5                ;keygen.004011A5

So, NOP out this last jump and PASSWORD can be anything. Now, given this input, the following request string is made:
GET /hqDTK7b8K2rvw/64636261/68676665/6c6b6a69/key.txt HTTP/1.0

(Note that 0×61 = ‘a’, 0×62 = ‘b’ etc)

Clearly, GCHQ have missed a trick here! Why on earth they didn’t make the first part of the result the plaintext password (requiring the crypt to actually be cracked), is beyond me.  This makes the use of crypt completely redundant.  However, for a bit of fun I decided to crack the hash anyway.

The cygwin doc explains:

It provides a static library libcrypt.a as well as a shared library
cygcrypt-0.dll together with a link lib libcrypt.dll.a, which export
the functions


The passwords created by crypt(3) are 56 bit DES encrypted and are
100% identical to those created by the Linux crypt().

So, we have ourselves an oldschool unix crypt hash.  The crypt.h header file tells us:

char * _EXFUN(crypt, (const char *key, const char *salt));

So, hqDTK7b8K2rvw is our salt string.  Note, that crypt(3) only uses the first two characters of the salt, and returns (salt[2]+hash[11]).  So our actual salt is ‘hq’ and ‘DTK7b8K2rvw’ is the actual hash.

Time to release John the Ripper!

Stick our hash in a unix format passwd file:


And run the following command:

john-mmx --format=DES -i=alpha passwd

format=DES specifies the use of the crypt(3) algorithm, i=alpha means try all alpha combinations.  Note that crypt(3) only uses the first 8 characters, so this means from to ‘a’ to ‘zzzzzzzz’.  Here’s what John tells us after a total of 35 minutes (note I was testing the ‘restore’ functionality here):

So our plaintext is ‘cyberwin’.  We now have the first 12 characters of license.txt:

But we still need to figure out the last bit.  The hint is actually the text here:

004011A5  |> \C70424 A82040>MOV DWORD PTR SS:[ESP],4020A8            ; ||ASCII "loading stage1 license key(s)...
004011AC  |.  E8 A7030000   CALL 00401558                            ; |\printf
004011B1  |.  8B45 D4       MOV EAX,DWORD PTR SS:[EBP-2C]            ; load first DWORD
004011B4  |.  8945 B8       MOV DWORD PTR SS:[EBP-48],EAX            ; |
004011B7  |.  C70424 CC2040>MOV DWORD PTR SS:[ESP],4020CC            ; |ASCII "loading stage2 license key(s)...

004011BE  |.  E8 95030000   CALL 00401558                            ; \printf
004011C3  |.  8B45 D8       MOV EAX,DWORD PTR SS:[EBP-28]
004011C6  |.  8945 BC       MOV DWORD PTR SS:[EBP-44],EAX        ;load second DWORD
004011C9  |.  8B45 DC       MOV EAX,DWORD PTR SS:[EBP-24]
004011CC  |.  8945 C0       MOV DWORD PTR SS:[EBP-40],EAX      ;load third DWORD

(scroll right to see the comments above)

‘Stage’ was reffered to in VM javascript (“stage 2 of 3″), so it seems we need 1 DWORD from stage one and two DWORDs from stage two.  The redundant firmware variable in stage two immediately comes to mind:

firmware: [0xd2ab1f05, 0xda13f110]

We need to go back and have another look at stage one to get any further.  Right at the start of the executable code of stage one, we see:

00401000 > $ EB 04          JMP SHORT test.00401006
00401002     AF             DB AF
00401003     C2             DB C2
00401004     BF             DB BF
00401005     A3             DB A3
00401006   > 81EC 00010000  SUB ESP,100

This is a bit fishy… the JMP and four bytes (a DWORD) are completely redundant! So why don’t we try AF C2 BF A3?

Fiddling around with the endianness results in the following valid request:

Finally, key.txt contains the final password: Pr0t3ct!on#cyber_security@12*12.2011+

Solution to Part Two of the GCHQ ‘’

In the previous post, we found the following url:

(In case link disappears, I’ve put a copy here).

Having never written an emulator/VM before, I thought it would be fun to give it a go.  The file gives a pretty concise description of how the processor works. However, there are a few points which, with clarification, make the problem easier:

  • Operand 1 is always an index to a register.
  • Operand 2 is an index to a register if mod = 0, else it is an immediate value or segment number.
  • A mod0 JMP, mod0 JMPE and HLT are all one byte long.
  • In the table, r1 means ‘register pointed to by operand 1′ and r2 means ‘register pointed to by operand 2′.
  • The specification for a long JMP and JMPE (mod1) is wrong.  It should read imm:r1 and not r2:r1.  Or, if the specification is right, the code in the memory provided is wrong, as instruction at 0×16 is invalid for the provided specification.  Using imm:r1 as the actual specification results in a correct solution!
  • Finally, the bit that got me stuck the longest: a long JMP (mod1 JMP and mod1 JMPE) must change the value of CS (code segment).  Then a near JMP (mod0 JMPs) jumps to CS*16 + operand 1.  This is kind of obvious in retrospect!

Here’s my solution, written in C.


Here’s me stepping through the output of my ‘decompiler’ (by ‘decompiler’, I mean a small modification to my VM where jumps don’t change the instruction pointer!):

This first loop decodes (XORs with 170) some executable code, starting at location 0×100.  The command at 0×16 (not shown), then jumps to this new code.  The new code then decodes the hidden string.

My VM executes until the HLT command and then dumps the memory.  In the memory is the decoded link to the final part of the problem:
GET /da75370fe15c4148bd4ceec861fbdaa5.exe HTTP/1.0

Time to start the next part!

Solution to Part One of the GCHQ ‘’

The image on contains x86 code.  So save typing it out by hand, I googled a couple of bytes and found a text version someone had kindly put up.

Next, I took a dummy exe file (anything with a valid PE header will do) and pasted the hex above into the Entry Point of the program.  This allows the code to be debugged.

The code eventually crashes when it tries to access an invalid memory address.  A clue is given here:

00401043  CMP EAX,41414141
00401048  JNZ SHORT test.0040108D
0040104A  POP EAX
0040104B  CMP EAX,42424242
00401050  JNZ SHORT test.0040108D

The condition passes (EAX == 41414141), the second doesn’t.  Something is missing.

It turns out there is something base64 encoded in the header of the PNG file:


This decodes to the following:

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

00000000   42 42 42 42 32 00 00 00  91 D8 F1 6D 70 20 3A AB   BBBB2   ‘Øñmp :«
00000010   67 9A 0B C4 91 FB C7 66  0F FC CD CC B4 02 FA D7   gš Ä‘ûÇf üÍÌ´ ú×
00000020   77 B4 54 38 AB 1F 0E E3  8E D3 0D EB 99 C3 93 FE   w´T8«  ãŽÓ ë™Ã“þ
00000030   D1 2B 1B 11 C6 11 EF C8  CA 2F                     Ñ+  Æ ïÈÊ/

Note that the first four bytes are 42 42 42 42! So, to solve the problem, put a breakpoint on ’0040104A  POP EAX’ and before the instruction is executed paste the whole lot into the address pointed to by ESP (the stack pointer).  The code will now run to completion.  Check the memory and you’ll see this string:

GET /15b436de1f9107f3778aad525e5d0b20.js HTTP/1.1

So, go to to go to the next part of the challenge!

Mounting an NFS share (DNS-320) in Ubuntu

I wanted to stream videos from my NAS (a D-Link DNS-320) on my media centre (running Mythbuntu). Here’s how to use NFS.

First, set up an NFS share on the DNS-320, using the GUI. The only ‘gotcha’ is ensuring to specify which hosts can access the share using the ‘Host’ field in settings page 2-1-2. You can just use the ‘*’ wild card if you’re feeling lazy. Leaving this blank will block all hosts!

On the client, install nfs-common:

$ sudo apt-get install nfs-common

And create a folder to mount to:

$ sudo mkdir /media/nas

Now add the following line to /etc/fstab: /media/nas nfs rw,hard,intr 0 0 is the (static) IP of the NAS.  You will either need to set up your NAS to have a static IP, or use the host name of the NAS here instead.

‘/mnt/HD/HD_a2/Data’ is the full path of the ‘Data’ folder, on ‘Volume 2′ (HDD 2) of the NAS.  This isn’t specified in the manual or help, which is really the reason of this post!  This will be different if you have enabled RAID.

The rest is reasonably self explanatory.

Finally, test it:

$ sudo mount -a
$ ls /media/nas

You should see your files!