# 1.Introduction

On July 21st 2012 at 7AM (Las Vegas time) ESET published a crack-me for the BlackHat US 2012 edition. Rules were simple: find a valid couple of serial numbers for a given name before July 26th 2012 2PM to maybe win \$1000 and a BlackCard (that gives you a free entry for either the next BlackHat US or EU).

 Figure 1: GUI of the crack-me

According to challenge's site Eloi Vanderbeken from Oppida was the only person to solve it in time and won the price even if he was not at BlackHat US:
 Figure 2: Result of the challenge

This post explains how Eloi achieved to break it.

# 2.First steps

The crack-me is packed with UPX, so we first unpack it by using the upx -d command to study it under IDA Pro 6.3.
We quickly noticed that it was created with the MFC library, which explains the large size of the binary (1Mo packed and 2M unpacked) and facilitates its analysis with IDA:
 Figure 3: Light blue parts are MFC functions recognized by IDA

A quick look in the referenced text string reveals the address of the function in charge of serials verification:
 Figure 4:localization of the verification function

To simplify its analysis, we import the labels generated by IDA in OllyDbg by using exporting them in a map file and importing the .map file with GoDup, an OllyDbg plugin :
 Figure 5: IDA labels imported in OllyDbg

Now we have located verification functions we can start to study them.

# 3.  First part

A quick analysis shows us that the serial is base64 decoded and must be composed of at least 0x3F characters once base64 decoded. It is then verified with the following algorithm :
 def scramble(t) :     j = 0     for i in xrange(64) :         c = t[i]%80         j = (t[c%80] + j)%80         t[j], t[i] = t[i], t[j] exitFun = badBoy serialDecoded = serial.decode("base64") padding = ("ESETNOD32@"*8)[len(name):] H = hash512(name+padding) if serialDecoded[:64][::-1] != H :     return exitFun() scrambledTab = H for i, c in enumerate(serialDecoded) :     scrambledTab[i%64] ^= c scramble(scrambledTab) if scrambledTab[68 : 72] != [0xA, 0xB, 0xC, 0xD] :     exitFun = badBoy     return exitFun() if hash512(scrambledTab)[:12] != [0xCA, 0x8A, 0x57, 0x12, 0x78, 0xB6, 0xCA, 0xEF, 0x78, 0x56, 0x34, 0x12] :     return exitFun() exitFun = goodBoy return exitFun() Figure 6:  First check of the serial in pseudo-python

The first part of the check is easy to satisfy, we just need to encode in base64 the reversed hash of the padded name. The second part is a little bit harder, at first sight it seems that we need to find a preimage attack on the hash function to satisfy the last check and to reverse the scramble function to find a valid serial.
After studying the code of the hash function, we found that the hash function was WhirlPool (thanks to the substitution table) and we did not find any backdoor in the code. It had to be something else.
A careful reader might have seen that scrambledTab is initialized with a 64 bytes array  (the hash of the name) but is then considered as a 80 bytes array in the scramble function. After redefining scrambledTab as a 80 bytes array in IDA, we saw that exitFun became scrambledTab[76:80]:
 Figure 6: IDA disassembly after scrambledTab was redefined

Moreover BadBoy and GoodBoy functions' addresses differ only by the least significant byte (0x40 for the BadBoy function, 0x20 for the GoodBoy one) so if we can use the scramble function to rewrite the badboy's address least significant byte to 0x20 and write the good values ([0xA, 0xB, 0xC, 0xD]) in scrambledTab[68:72] we will have the good boy message and you will pass the first check.

To achieve this goal we wrote a little "intelligent" bruteforcer which, given an initial table of 64 unknown bytes followed by 16 fixed bytes (remember we only control the 64 first bytes) and a list of couples (offset, value_to_set) corresponding to the desired final state of the table, tries to set the  right bytes at the right place and next tries to not overwrite them.
Complete code to generate a valid first serial is the following:
 import sys from random import SystemRandom from base64 import b64encode try :   from whirlpool import Whirlpool except :   print "To work this keygen need the Whirpool implementation located here : http://www.bjrn.se/code/whirlpoolpy.txt"   sys.exit(0) sec_rnd = SystemRandom() print "name :" name = sys.stdin.readline().rstrip("\r\n") hash_name = [ord(c) for c in Whirlpool(name+("ESETNOD32@"*8)[len(name):]).digest()] def step1(k, i, j, to_set, to_keep) :   if i > 63 :     if not to_set :       return k     return None   if to_set :     to_set_cpy = list(to_set)     sec_rnd.shuffle(to_set_cpy)     for choice, (idx, v) in enumerate(to_set_cpy) :       if k[i] == v :         r = step2a(idx, k, i, j, to_set_cpy[:choice] + to_set_cpy[choice+1:] , to_keep)         if r is not None :           return r       elif k[i] is None :         k[i] = v         r = step2a(idx, k, i, j, to_set_cpy[:choice] + to_set_cpy[choice+1:] , to_keep)         if r is not None :           return r         k[i] = None   r = step2b(k, i, j, to_set, to_keep)   if r is not None :     return r   return None def step2a(idx, k, i, j, to_set, to_keep) :   c = k[i] % 80   kc = k[c]   if kc is None :     if (idx - j)%80 in to_keep :       return None     kc = (idx - j)%80     while kc < 0x100 :       k[c] = kc       to_keep.add(idx)       r = step3(k, i, idx, to_set, to_keep)       to_keep.remove(idx)       if r is not None :         return r       kc += 80     k[c] = None   elif (kc + j)%80 == idx :     to_keep.add(idx)     r = step3(k, i, idx, to_set, to_keep)     to_keep.remove(idx)     return r   else :     return None def step2b(k, i, j, to_set, to_keep) :   c = k[i]   if c is None :     vals = [c for c in xrange(0x100) if k[c%80] is not None and (k[c%80] + j)%80 not in to_keep]     sec_rnd.shuffle(vals)     for c in vals :       k[i] = c       r = step3(k, i, (k[c%80] + j)%80, to_set, to_keep)       if r is not None:         return r     vals = [c for c in xrange(0x100) if k[c%80] is None]     sec_rnd.shuffle(vals)     for c in vals :       k[i] = c       c = c % 80       vals2 = [kc for kc in xrange(0x100) if (kc + j)%80 not in to_keep]       sec_rnd.shuffle(vals2)       for kc in vals2 :         k[c] = kc         r = step3(k, i, (kc + j)%80, to_set, to_keep)         if r is not None :           return r       k[c] = None     k[i] = None   else :     c %= 80     kc = k[c]     if kc is None :       vals = [kc for kc in xrange(0x100) if (kc + j)%80 not in to_keep]       sec_rnd.shuffle(vals)       for kc in vals :         k[c] = kc         r = step3(k, i, (kc + j)%80, to_set, to_keep)         if r is not None :           return r       k[c] = None     elif (kc + j)%80 not in to_keep :       return step3(k, i, (kc + j)%80, to_set, to_keep)   return None def step3(k, i, j, to_set, to_keep) :   k[i], k[j] = k[j], k[i]   r = step1(k, i+1, j, to_set, to_keep)   if r is not None :     r[i], r[j] = r[j], r[i]     return r   k[i], k[j] = k[j], k[i]   return None # initial table k = [None]*64+[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x17, 0x40, 0x00] # couple of values to set to_set = [(0x44, 0xA), (0x45, 0xB), (0x46, 0xC), (0x47, 0xD), (0x4C, 0x20)] # addresses that have to be keep (not modified) to_keep = set((0x4D, 0x4E, 0x4F)) s = step1(k, 0, 0, to_set, to_keep) # unused values can be set to any value for i in xrange(len(s)) :   if s[i] is None :     s[i] = sec_rnd.randint(0, 0xFF) serial = "".join(chr(i) for i in hash_name[::-1]) serial += "".join(chr(a^b^c) for a, b, c in zip(hash_name, hash_name[::-1], s)) print "serial 1 :" print serial.encode("base64").replace("\n","") Figure 7: Code to generate a valid first serial (Python 2.7)

If you want to personalize the generated serial by appending it a constant string, you can use one of the following code, the first one adds some constraints to the generated table and generates a serial with a minimal length; the second one appends bytes to the serial but permits to add an arbitrary long string:

 egg = "ElV++ESET++=" egg_d = egg.decode("base64") padd = 64 - len(egg_d) list_egg = [hash_name[padd+i]^hash_name[::-1][padd+i]^ord(c) for i,c in enumerate(egg_d)] k = [None]*padd+list_egg+[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x17, 0x40, 0x00] to_set = [(0x44, 0xA), (0x45, 0xB), (0x46, 0xC), (0x47, 0xD), (0x4C, 0x20)] to_keep = set((0x4D, 0x4E, 0x4F)) s = step1(k, 0, 0, to_set, to_keep) for i in xrange(len(s)) :   if s[i] is None :     s[i] = sec_rnd.randint(0, 0xFF) serial = "".join(chr(i) for i in hash_name[::-1]) serial += "".join(chr(a^b^c) for a, b, c in zip(hash_name, hash_name[::-1], s)) serial = serial.encode("base64").replace("\n","") # multiple encoding are valid for the same value because some bits are ignored for the last symbol serial = serial[:-len(egg)]+egg print "serial 1 :" print serial Figure 8: First solution, all generated serials end with ElV++ESET++ (Python 2.7)

 egg = "+blabla+ESET+Nod32+BH2012+Oppida" egg_d = egg.decode("base64") k = [None]*64+[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x17, 0x40, 0x00] to_set = [(0x44, 0xA), (0x45, 0xB), (0x46, 0xC), (0x47, 0xD), (0x4C, 0x20)] to_keep = set((0x4D, 0x4E, 0x4F)) s = step1(k, 0, 0, to_set, to_keep) for i in xrange(len(s)) :   if s[i] is None :     s[i] = sec_rnd.randint(0, 0xFF) serial = "".join(chr(i) for i in hash_name[::-1]) # add padding (128 % 3 = 2) egg_d = chr(sec_rnd.randint(0, 0xFF) + egg_d for i, j in enumerate(hash_name + hash_name[::-1] + [ord(c) for c in egg_d]) :     s[i%64] ^= j serial += "".join(chr(i) for i in s[:64]) serial += egg_d serial = serial.encode("base64").replace("\n","") # multiple encoding are valid for the same value because some bits are ignored for the last symbol serial = serial[:-len(egg)]+egg print "serial 1 :" print serial Figure 9: Second solution, all generated serials end with "+blabla+ESET+Nod32+BH2012+Oppida" (Python 2.7)

# 4.  Second part

The second part of the challenge takes place at address 0x0401C10. The serial must be composed of 12 uppercase hexadecimal characters. Those characters are used to transform a 64 bits value with the following algorithm:
 def to_16b(qword) :     idx = 1     word = 0     for i in xrange(16) :         if qword & (1 << idx ) :             word |= 1 << i         idx = (idx + 29) % 64     return word def mutate(qword, word) :     idx = 1     for i in xrange(16) :         if word & (1 << i) :             qword |= 1 << idx         else :             qword &= ~(1 << idx)         idx = (idx + 29) % 64     return qword def shift(qword) :     qword_masked = qword & 7069181668496654033     nb_bits_set = 0     for i in xrange(64) :         if qword_masked & (1 << i) :             nb_bits_set += 1     return ((qword << 1) | (nb_bits_set & 1)) & ((1 << 64) - 1)     serial = "012345678ABC" qword = 8828098094971975552 for c in serial :     i = int(c,16)     word = to_16b(qword)     if word & (1 << i) :         word &= ~(1 << i)     else :         word |= (1 << i)     qword = mutate(qword, word)     qword = shift(qword) print hex(qword) Figure 10: First part of the verification of the second serial : mutation of qword (Python 2.7)

Those operations can be simplified to get the following algorithm:
 def shift(qword) :     qword_masked = qword & 7069181668496654033     nb_bits_set = 0     for i in xrange(64) :         if qword_masked & (1 << i) :             nb_bits_set += 1     return ((qword << 1) | (nb_bits_set & 1)) & ((1 << 64) - 1)     serial = "012345678ABC" qword = 8828098094971975552 int_to_idx = [1, 30, 59, 24, 53, 18, 47, 12, 41, 6, 35, 0, 29, 58, 23, 52] for c in serial :     qword ^= 1 << int_to_idx[int(c,16)]     qword = shift(qword) print hex(qword) Figure 11: Simplified first part of the verification of the second serial : mutation of qword (Python 2.7)

After being transformed, qword must satisfy the following check:
 qwordTable = [[4, 145, 249, 72, 19, 195, 74, 137],     [...]  [4611686018427387904L,   1195637156289680119L,   24621007439812200L,   43743323218066342L,   676995634570036126L,   1203597948695219397L,   1037036833364889864L,   130039954294608103L]] def has_same_set_bits(a, b) :     """ return 1 if all bits set in b are also set in a else 0"""     for i in xrange(64) :         if b & (1 << i) and not (a & 1 << i) :             return 0     return 1 r = 0 for i in xrange(64) :     nb_same_set_bits = 0     for j in xrange(8) :         nb_same_set_bits += has_same_set_bits(qword, qwordTable[i][j])     if nb_same_set_bits & 1 :         r |= 1 << i if r == 0x55BDEC8E23A0EF32 :     print "Good Boy" else :     print "BadBoy" Figure 12: Verification of the mutated qword (Python 2.7)

To dump the qwordTable, we use the following simple IDA python script:
 from pprint import pprint from idc import Qword qwordTable = [] for i in xrange(64) :   t = []   for j in xrange(8) :     t.append(Qword(0x560128 + i*8*8 + j*8))   qwordTable.append(t) pprint(qwordTable) Figure 13: Code used to dump the table of qwords in IDA (Python 2.7)

It may exists an elegant solution for this problem but we didn't find it and we wanted to solve the crack-me as fast as possible so we decided to use the Microsoft Theorem Prover: Z3. It has a nice Python API, good reviews, free and easy to use; it is the perfect tool for this problem.
All we have to do is translate the different operations performed on the serial into Z3 equations and let Z3 find the answer for us.
Integers became bit vectors, different possible values are translated in OR equations. The following code gives a valid serial in less than 2 minutes:
 check_v = 0x55BDEC8E23A0EF32 init_v = 8828098094971975552 bitvectors = [BitVecVal(init_v, 64)] solver = Solver() for i in xrange(12) :   bitvectors.append(BitVec('x_%d'%i, 64)) key = [] for i in xrange(12) :   key.append(Int("k%d"%i)) for i in xrange(1, 13) :   eq = False   for j, k in enumerate([1, 30, 59, 24, 53, 18, 47, 12, 41, 6, 35, 0, 29, 58, 23, 52]) :     sub_eq = (key[i-1] == j)     bv = (bitvectors[i-1] ^ BitVecVal(1 << k, 64))     bit = 0     for l in xrange(64) :       if 7069181668496654033 & (1 << l) :         bit = (LShR(bv, l) & 1) ^ bit     sub_eq = And(sub_eq, bitvectors[i] == ((bv << 1) | bit))     eq = Or(eq, sub_eq)   solver.append(eq) for i, t in enumerate(tt) :   bit = 0   for v in t :     has_same_set_bits = 1     for j in range(64) :       if v & (1 << j) :         has_same_set_bits = has_same_set_bits & (LShR(bitvectors, j) & 1)     bit ^= has_same_set_bits   if check_v & (1 << i) :     solver.add(bit == 1)   else :      solver.add(bit == 0)   solver.check() solution = solver.model() r = "" for i in xrange(12) :   r += "%X"%int(str(solution[key[i]])) print "serial2 :" print r Figure 14: Code used to find a valid serial for the second part of the crack-me (Python 2.7)

It is faster to find a solution when boolean values are directly used instead of bit vectors (~30 sec versus ~100 sec) but it is also a little bit more tricky, the code doing this is given in the complete keygen source provided in Annex.
Different variations of the code gave us 3 different serials: 94A0353FA32B, 2BA52E7FAC23 and 966132348323.

# 5.  Conclusion

This crack-me intelligently mixed crypto, exploitation and use of specific tools, it wasn't too easy nor too hard for this kind of time constraint challenge and really interesting.
We want to thank ESET and all the people who participate to this challenge, both coders and organizers!

# 6.  Annex

 import sys from random import SystemRandom from base64 import b64encode try :   from whirlpool import Whirlpool except :   print "To work this keygen need the Whirpool implementation located here : http://www.bjrn.se/code/whirlpoolpy.txt"   sys.exit(0) sec_rnd = SystemRandom() print "name :" name = sys.stdin.readline().rstrip("\r\n") hash_name = [ord(c) for c in Whirlpool(name+("ESETNOD32@"*8)[len(name):]).digest()] def step1(k, i, j, to_set, to_keep) :   global SOLUTION   if i > 63 :     if not to_set :       return k     return None   if to_set :     to_set_cpy = list(to_set)     sec_rnd.shuffle(to_set_cpy)     for choice, (idx, v) in enumerate(to_set_cpy) :       if k[i] == v :         r = step2a(idx, k, i, j, to_set_cpy[:choice] + to_set_cpy[choice+1:] , to_keep)         if r is not None :           return r       elif k[i] is None :         k[i] = v         r = step2a(idx, k, i, j, to_set_cpy[:choice] + to_set_cpy[choice+1:] , to_keep)         if r is not None :           return r         k[i] = None   r = step2b(k, i, j, to_set, to_keep)   if r is not None :     return r   return None def step2a(idx, k, i, j, to_set, to_keep) :   c = k[i] % 80   kc = k[c]   if kc is None :     if (idx - j)%80 in to_keep :       return None     kc = (idx - j)%80     while kc < 0x100 :       k[c] = kc       to_keep.add(idx)       r = step3(k, i, idx, to_set, to_keep)       to_keep.remove(idx)       if r is not None :         return r       kc += 80     k[c] = None   elif (kc + j)%80 == idx :     to_keep.add(idx)     r = step3(k, i, idx, to_set, to_keep)     to_keep.remove(idx)     return r   else :     return None def step2b(k, i, j, to_set, to_keep) :   c = k[i]   if c is None :     vals = [c for c in xrange(0x100) if k[c%80] is not None and (k[c%80] + j)%80 not in to_keep]     sec_rnd.shuffle(vals)     for c in vals :       k[i] = c       r = step3(k, i, (k[c%80] + j)%80, to_set, to_keep)       if r is not None:         return r     vals = [c for c in xrange(0x100) if k[c%80] is None]     sec_rnd.shuffle(vals)     for c in vals :       k[i] = c       c = c % 80       vals2 = [kc for kc in xrange(0x100) if (kc + j)%80 not in to_keep]       sec_rnd.shuffle(vals2)       for kc in vals2 :         k[c] = kc         r = step3(k, i, (kc + j)%80, to_set, to_keep)         if r is not None :           return r       k[c] = None     k[i] = None   else :     c %= 80     kc = k[c]     if kc is None :       vals = [kc for kc in xrange(0x100) if (kc + j)%80 not in to_keep]       sec_rnd.shuffle(vals)       for kc in vals :         k[c] = kc         r = step3(k, i, (kc + j)%80, to_set, to_keep)         if r is not None :           return r       k[c] = None     elif (kc + j)%80 not in to_keep :       return step3(k, i, (kc + j)%80, to_set, to_keep)   return None def step3(k, i, j, to_set, to_keep) :   k[i], k[j] = k[j], k[i]   r = step1(k, i+1, j, to_set, to_keep)   if r is not None :     r[i], r[j] = r[j], r[i]     return r   k[i], k[j] = k[j], k[i]   return None egg = "ElV++ESET++=" egg_d = egg.decode("base64") # fight against sexism in keygens ! # use B16B00B2 and B16BA112 with equal probability ! import struct egg_d = (struct.pack("

#### 5 commentaires:

1. Add: key 2 may be 93A52ECFAC23

1. There are 11 different keys 2 for this task. For example: 5ba52b7fac23 , ...
AVictor2010@gmail.com

2. Oppida - Blog: Solution For The Eset Blackhat Us Challenge 2012 >>>>> Download Now

Oppida - Blog: Solution For The Eset Blackhat Us Challenge 2012 >>>>> Download Full

2. 3. Oppida - Blog: Solution For The Eset Blackhat Us Challenge 2012 >>>>> Download Now