Solving confidence 2011 crackme for fun and profit
Confidence is a security conference organized in Poland. During this year’s edition (and like during few previous editions) a crackme contest took place — attendees were invited to provide a solution (serial, keygen). The fastest one would win the prize (pocketbook reader).
I didn’t plan to participate, because I didn’t want to spend time analyzing crackmes during lectures, besides I didn’t even take a laptop with me, as I figured few days without staring at a screen would have a good impact on my mental health :P. Luckily, since no attendee provided a solution, organizers decided to prolong the contest, so I was able to take a look at the crackme after I got back from the con.
I won’t bore you with deadlistings this time, but provide a high-level view of the protection and my approach.
def check(name, serial): hash = hash(name) #identified as CubeHash, by Dcoder d1, d2 = mangle(hash+serial) return d1==const1 and d2==const2
Serial is a 64-bit number. Function mangle consumes whole input buffer, one dword at a time and returns two 32-bit values. Since we can’t control hash variable, we’ll have to use serial to force mangle to return expected values (const1, const2).
The main problem is that mangle is very long (~10KB) and “obfuscated” with heavy macro use. Analysing it by hand seemed like a boring thing to do.
By the look of this function (mostly bit manipulations) and considering the fact that it takes a big buffer, with serial appended at the end I started suspecting it’s some kind of CRC. “Reversing” a N-bit CRC requires only N-bits of input (see this, or this). That means if you have a buffer, and want (for example) CRC32 of that buffer to be X, you need to append a special DWORD at the end. You don’t need to bruteforce this magic DWORD, you can compute it easily.
By evaluating mangle on some constants you will notice its results don’t match those of a standard CRC64. Perhaps this function isn’t CRC at all? There is one property that all variants of CRC64 should share: linearity. Every CRC (starting value and polynomial don’t matter) is a homomorphism with respect to XOR:
Proof here (appendix A). Quick check in a debugger shows that’s indeed the case.
With this information, we can easily reimplement the algorithm, by observing that any value can be represented as a XOR of powers of 2. For example:
Generally, let , then (our variant takes one dword at a time):
As a consequence, it’s sufficient to compute for , to be able to evaluate for any input. We can use the crackme itself to compute these values, with a bit of OllyScript magic :).
Here’s the reimplemented CRC64:
def crc64(hi, lo, tab, dw): #tab is the ripped table HI = 0 LO = 1 old_lo = lo lo = 0 hi = 0 for pos in range(32): b = dw>>pos b = b & 1 if b: hi ^= tab[pos][HI] lo ^= tab[pos][LO] return (hi^old_lo,lo)
Much better than ~10KB of code :).
Back when I was solving it, time was of essence — I didn’t knew with how many people I’m racing for a solution, so instead of reversing this algorithm (it’s a set of linear equations, tab is a matrix, dword a vector, and it can be solved fast with gaussian elimination, for example) I ported it to C and bruteforced a solution (~10 mins).
There are few technicalities left out. Producing a working serial is a bit more complicated, than just reversing the CRC, but in my opinion that was the biggest obstacle worth describing.
So here it is, a working serial number:
Crackme and sources are available here.
These photos could have been better, sorry about that :P. If you are wondering: the OS is just custom a linux distro, you can upload your binaries and run them without any restrictions, so there is nothing to break, unfortunately :).
Speaking of linux, crackme unpacks an ELF binary from its resources (.rsrc section) and cleverly maps into it’s own address space. It’s quite cool, so check it out :).