Last week, we described several vaccines to prevent the Locky ransomware from encrypting files or to let us decrypt them by forcing the RSA key. Locky has mutated since then and no longer uses a static registry key, making these vaccines ineffective. But it is still possible to create a vaccine against the latest variant.
Vaccine #1 presented in the previous blog post was very easy to set up: the presence of the HKCU\Software\Locky registry key with restrictive ACLs allowed to block its creation by Locky itself, thus preventing its good execution. On the most recent variants, Locky no longer uses this static key:
As Locky uses this key to store information to be retrieved at a later time, it is likely that this key is not random but generated by an algorithm using system specific parameters. By analysing the code, we noticed that a new function has been added before the call to RegCreateKeyExA(), named myGenerateKeyName() on the screenshot below:
myGenerateKeyName() begins by calling a function with the parameters 0x1D9076E6 and 0xFD853AB6. We recognize them as our test machine Locky identifier, whose computation has been described in the previous post, based on the Windows partition GUID (vaccine #2). This function, named myCrypt() in the rest of the post, contains a few cryptographic operations in a loop:
Thereafter, the myCrypt() function is called twice inside a loop, each time on the result of the previous call, with an incrementing third parameter. At the end of the loop, a value is added to 0x41, 0x61 or 0x30 so as to create an uppercase or lowercase character or a figure, depending on the remainder of an euclidean division by 26 or 10:
The loop is executed as many times as the value of the esp+0x48+ var_1C variable which is set by a few operations after the first call to myCrypt():
The following Python script generates the name of the registry key now used by Locky:
#! /usr/bin/env python # Locky registry name generator # Sylvain SARMEJEANNE, CERT-LEXSI 03/2016 from sys import argv # General functions def v32(val): return val & 0xffffffff def shrd(dst, src, cnt): return v32(((src << 32) + dst) >> cnt) def shld(dst, src, cnt): out = ((src << 32) + dst) << cnt out |= (src >> 32-cnt) return v32(out) def add(a, b): out = a + b c = out > (2**32-1) return v32(out), c rol = lambda val, r_bits: \ (val << r_bits%32) & (2**32-1) | \ ((val & (2**32-1)) >> (32-(r_bits%32))) # Locky's myCrypt() function def mycrypt(h, l, idx): for i in range(7): eax = shrd(l, h, 0x19) edx = v32(l<<7) eax = eax^edx ebx = shld(h, l, 7) ecx = h>>0x19 ecx = ecx^ebx esi, c = add(rol(i, 7), eax) edi = v32(ecx+c) esi, c = add(esi, v32(idx<<i)) edi = v32(edi+c) esi, c = add(esi, 0x4E456DEF) edi = v32(edi+c+0x58749DF5) l, c = add(l, esi) h = v32(h+edi+c) return h, l botid = argv h = int(botid[:8], 16) l = int(botid[8:], 16) name = "" h, l = mycrypt(h, l, 0) size = 8 + (shrd(l, h, 5) & 7) for i in range(size): h, l = mycrypt(h, l, i) l_save = (l & 0xff) - 1 h, l = mycrypt(h, l, i) eax = l & 0xff if l_save%3 == 0: edx = (eax%26) + ord('A') elif l_save%3 == 1: edx = (eax%26) + ord('a') else: edx = (eax%10) + ord('0') name += chr(edx) print name
By providing our Locky identifier to this script, it generates the same name as the one seen on the first screenshot above:
$ ./locky_reg.py 1D9076E6FD853AB6 BP9d9j6l
Locky has been recently updated and no longer uses the static HKCU\Software\Locky registry key. Analysing a new sample reveals that it uses a registry key whose name depends on the machine identifier based on the Windows partition GUID. Systems can still be protected against this new Locky variant by using a dynamic vaccine.
Locky authors are very reactive and the vaccine presented here is not likely to be effective for a long time… This blog post just aimed at showing that a vaccine can be devised even if no static elements are used by the malicious program; it is indeed enough to have predictible elements.