AdwarePunisher - another relic, rogue anti-spyware that finds ‘infections’ on a clean box like a detective finding a speck of dust in a vacuum. Of course the user would have to pay and register the software to have ‘them’ removed.
Today, for educational purposes, I share my notes from the dissection of the registration routine and we take another look at md5
while having fun dump
watching.
VirusTotal Results
First Run
Detect it Easy showing program is protected by Asprotect
Showing fake infections on a fresh VM
Serial Creation Routine
I may make a video showing how I found this serial routine. In general, the same strategy of “pause->run to user code” was used. But, a bit of extra searching was involved. However, the focus of this post is the reversing of the serial routine itself. So…
Once the registration routine is located, we step throught the code and notice that CryptAcquireContextA
is called.
This let’s us know that something is about to be hashed (probably our email address).
MD5 Hash
Below shows the hashing functions being called after CryptAcquireContextA
.
CryptCreateHash
will begin the hashing process by creating a hash object of type md5 determined by the ALG_ID of 8003CryptHashData
will hash our email address (abz@def.com)- Notice, in fig 1. above, that dwDataLen (the length of our email) is doubled (not shown: from 0x0B –> 0x16) before being pushed as a parameter. This took me an embarrassingly long time to catch. The reason it is doubled is because the program is not storing our email address as ‘abz@def.com’ but rather in dot notation as ‘a.b.z@d.e.f…c.o.m.’ (see fig. 2) essentially doubling the length of our data.
- This is important because those dots (0x00) will be included during the md5 hashing
CryptGetHashParam
will retrieve our md5 hashed email address to *pbData. EAX was pushed as pbData parameter (see fig. 1). Following this pointer in the dump we can see our md5 hash.
Click to see python code to create the md5 hash for an email
Once the md5 hashing is complete, the hash is then copied to the memory address immediately following it’s current location. (see fig 4a, 4b)
fig 4a. instructions to copy md5 hash
Loop 1
Next, we come to our first loop in the routine. The constant 0xE839FEC5 (“C1” in my final python code below) is XORd with one dword of the md5 hash at a time. However, each dword of the md5 hash is read as little endian and we must account for this when creating our keygen.
fig 5. loop1 showing first XOR operation
Each of these dwords will be read as little endian.
For instance, the first word in the dump ‘603F8D86’ will be read as ‘868D3F60’
Click to see the python code for splitting the md5 and changing endian
Below shows the result of Loop 1:
storage location showing the results of Loop 1
Click to see the python code for Loop 1
Loop 2
- EAX is initialized to 0 and used as a counter.
- 0x6d + EAX
- storage1[byte] = storage1[byte] XOR result from 2.
- increment EAX
- repeat for all bytes in storage1
Below shows the result from Loop 2: storage location after loop2
Loop 3
A hardcoded string we’ll call CONST_STR (I refer to as ‘C4’ in python code below) is loaded: ‘123456789MNBVCXZLKJHGFDSAOPIUYTRE123456789MNBVCXZLKJHGFDSAOPIUYTRE’
- bl = byte from storage location (after loop2)
- bl = bl AND 0x1F
- bl = byte CONST_STR[ bl ]
For example:
- the first byte in storage location after loop2 is 0xC8.
- 0xC8 AND 0x1F = 8.
- CONST_STR[8] = ‘9’.
So, the first char of serial will be 9. This formula repeats for all bytes in the storage location until we have a final serial. (see below)
storage2 now contains our serial after loop3 completes
Entering the serial code found here will successfully register the rogue.
Coding the Keygen
Proof of concept code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import hashlib
C1 = int(0xeb39fec5)
C2 = int(0x6D)
C3 = int(0x1F)
C4 = '123456789MNBVCXZLKJHGFDSAOPIUYTRE123456789MNBVCXZLKJHGFDSAOPIUYTRE'
def get_email():
email = input('Email: ') or ''
return(email)
def create_md5(email):
m = hashlib.md5(email.encode('utf-16le'))
md5 = m.hexdigest()
return(md5)
def split_hex_string(xstr, split=2):
temp = ([xstr[i:i+split] for i in range(0, len(xstr), split)])
return(temp)
def change_endian(xlist):
# swap endian
# ie: ABCD -> CDAB
new_list = []
for xstr in xlist:
z = int(xstr, 16)
z = z.to_bytes(4, byteorder='little')
new_list.append(str(z.hex()))
return(new_list)
print('\n\n')
email = get_email()
#email = format_email(email)
# create md5 hash of email
md5 = create_md5(email)
# split md5 into 4 byte chunks, big endian
md5 = split_hex_string(md5, split=8)
md5 = change_endian(md5)
storage_1 = []
# perform math operation on md5 with C1 and store in storage_1
for word in md5:
a = C1 ^ int(word,16)
# format result as uppercase hex value minus the '0x' ie: 0xab -> AB
storage_1.append('{:02X}'.format(a))
# swap each 4 byte chunk endian ie: EFCDAB89 -> 89ABCDEF
storage_1 = change_endian(storage_1)
# split each 4 byte chunk into 2 byte chunks ie: 89ABCDEF -> 89, AB, CD, EF
storage_1 = [split_hex_string(x,split=2) for x in storage_1]
# combine all bytes into a single list
storage_1 = [int(item,16) for sublist in storage_1 for item in sublist]
# double the list
storage_1 = storage_1 + storage_1
# perform the math operation with C2
storage_1 = [byte ^ (C2+i) for i, byte in enumerate(storage_1)]
# create serial with bytes from storage_1 an C3
serial = [C4[byte & C3] for byte in storage_1]
print('Serial: '+ ''.join(serial))
print('\n\n')