Aug 02 2010
Part 2 of Cyber Security Challenge: Hexadecimal gibberish
This second part of the problem was more difficult, though the solution is much shorter to present. The input message looks like this, in part:
68edcdec4e2c8eae8d2c8e2dedcd6e04d2042fedae52ceac 04ccedaecd8c042ccd8c046cedad0e8dac8eac8c048e0dac
Under the assumption that each pair of characters represents a byte, we split the stream into pairs.
splitStream = splitEvery 2
Of course each pair isn’t actually a hexadecimal number, though it looks like one. It’s a string of characters, so we need to turn it into something the computer will recognise as a number.
Now, there’s a robust, I’m-a-serious-engineer way of doing this and then there’s the way I chose, which is to prefix all the strings with “0x” and use the read routine. The Read typeclass is not meant to represent a robust parsing mechanism but since this is a one-shot thing I think we’ll both just ignore it, yeah?
Having read in our number I tried to do what I did in the previous exercise and print it out, which produced complete nonsense. Then I decided that reversing the string before converting it to numbers would be helpful. I did that, but the result was still nonsense, just of a different shade.
Then I decided that my resulting numbers were a bit large. The number which represent capital A is 65, and I was getting numbers in the 200 range. Then I realised, “all these numbers are even, aren’t they?” So I divided the whole lot by two.
Outputting that message gave me a backwards English message. Aha! It’s close but there’s something wrong. I threw in another “reverse” statement and got an answer. At this point I noticed I had two reverse statements — one at the beginning and one at the end. This seemed like it would be the cause of my problems so I removed them both and, Robert’s your father’s brother, I was back to gibberish again.
What happened? What I had failed to realise was that when I had reverse my original message I had done so before splitting into pairs of characters. So ABCD becomes DCBA. After splitting this is [DC,BA]. If each pair is converted into a printable character, represented by f(XY), then the result is [f(DC),f(BA)]. I then reversed the message to get [f(BA),f(DC)].
If I didn’t do any reversing but went through the same splitting and conversion process, I got [f(AB),f(CD)]. Look — f(AB) is not the same as f(BA)! By reversing the list before splitting and again after conversion, I was implicitly reversing the order of the characters in each pair.
Obviously I had to reverse only the pairs after they’d been split, rather than the whole list at the beginning and end. If I do this then print the result I get an answer which is close to right.
byteToChar = chr . flip div 2 . hexToInt . reverse message = map byteToChar . splitStream loadfile = (head . lines) `fmap` readFile "hexstring.txt" main = loadfile >>= putStrLn . message
The resulting message is:
Congratulations youve found and completed the REAL challenge. Your win code is cyb3r=s3cur1ty*ch@ll3nge+26-07-2010.
Please email this code to our team at media@cybersecuritychallenge.org.uk. If youre the first person to do so, and can prove you meet the eligibility criteria (British citizen currently resident in the UK) we will be in touch to advise how to claim your prize. Well done and good luck in the Cyber Security Challenge competitions taking place throughout the rest of the year.
There are a few infelicities which are explained by the official solution. My divide-by-two technique is not the proper approach so I think some of the characters get lost in the decryption process. But as the saying goes, close enough for government work!
The solution states:
The challenge was based on a bitshift operation applied to a string, here each byte’s “3 least significant bits” have been added to the left side of the byte (making them the most significant bits respectively)
Assuming we have bits one to eight:
1 2 3 4 5 6 7 8
what I did was divide everything by two, which we do in binary by shifting everything to one side so the least significant bit disappears. We fill the opposite edge with a zero here:
0 1 2 3 4 5 6 7
What the solution demands is to rotate the bits like so:
6 7 8 1 2 3 4 5
I am still not sure how it is that my solution matches so neatly with their own. Answers on a postcard.
If I hadn’t stumbled on a solution like this, the sensible approach might have been to produce a histogram of common characters. In this case, the most commonly used character encoded the space, followed closely by “e”, the most common letter in standard English texts. From there it would have been a bit of a slog to produce the result but it could be done.
The two other solutions I have found both use the official method without problems. I suspect it’s got something to do with default word lengths (8 bit versus 32 bit) and signed integers, though I’ve not thought deeply on the issue.
3 Responses to “Part 2 of Cyber Security Challenge: Hexadecimal gibberish”
I think your solution does the following: 6 7 8 1 2 3 4 5
reverse the hexadecimal string: 2 3 4 5 6 7 8 1
divide by two: x 2 3 4 5 6 7 8
You’ve lost the most significant bit, which isn’t used in ASCII.
Yes, you’re right, that makes much more sense than the way I was thinking of it. I had forgotten that the others solutions don’t swap the order of the nibbles. Three cheers for 7 bit ASCII :-)
This was my solution:
!/usr/bin/perl
while(<>){s/(..)\s*/pack(‘C’,hex($1)«3|hex($1)»5)/eg;print;}