Home Reversing Rogues #3 - AdWare Punisher - [more md5]
Post
Cancel

Reversing Rogues #3 - AdWare Punisher - [more md5]


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

virustotal VirusTotal Results

First Run

assets/img/die_asprotect.png Detect it Easy showing program is protected by Asprotect

assets/img/adware_punisher_start.png Showing fake infections on a fresh VM

assets/img/registration.png Registration Error

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).

assets/img/cryptaquirecontext.png CryptAcquireContextA

MD5 Hash

Below shows the hashing functions being called after CryptAcquireContextA.

assets/img/crypt_hash_functions.png fig 1. md5 hashing functions

  1. CryptCreateHash will begin the hashing process by creating a hash object of type md5 determined by the ALG_ID of 8003
  2. CryptHashData 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

    assets/img/chars_spaced_out.png fig 2. Dot notation email

  3. 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.
eax_pbData fig 3a. EAX showing location of md5 hash
after_hash.png fig 3b. md5 hash of email


Click to see python code to create the md5 hash for an email
    # Code for creating md5 hash of email 

    import hashlib

    def create_md5(email):
        # Encode the email as utf-16le (little endian) to account for the null bytes between each character
        m = hashlib.md5(email.encode('utf-16le'))
        md5 = m.hexdigest()
        return(md5)

    email='abz@def.com'
    email = format_email(email)
    md5 = create_md5(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)

copy_md5_instruct fig 4a. instructions to copy md5 hash

dump_md5_copy fig 4b. md5 hash doubled

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.

loop1 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’

dwords hash md5 split into dwords

Click to see the python code for splitting the md5 and changing endian
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)
.
.
.
# split md5 into 4 byte chunks, big endian
md5 = split_hex_string(md5, split=8)
md5 = change_endian(md5)



Below shows the result of Loop 1:

after loop 1 storage location showing the results of Loop 1



Click to see the python code for Loop 1
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))

Loop 2

Loop 2

  1. EAX is initialized to 0 and used as a counter.
  2. 0x6d + EAX
  3. storage1[byte] = storage1[byte] XOR result from 2.
  4. increment EAX
  5. repeat for all bytes in storage1

Below shows the result from Loop 2: after loop 2 storage location after loop2

Loop 3

loop3

A hardcoded string we’ll call CONST_STR (I refer to as ‘C4’ in python code below) is loaded: ‘123456789MNBVCXZLKJHGFDSAOPIUYTRE123456789MNBVCXZLKJHGFDSAOPIUYTRE’

  1. bl = byte from storage location (after loop2)
  2. bl = bl AND 0x1F
  3. bl = byte CONST_STR[ bl ]

For example:

  1. the first byte in storage location after loop2 is 0xC8.
  2. 0xC8 AND 0x1F = 8.
  3. 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 storage2 now contains our serial after loop3 completes
Entering the serial code found here will successfully register the rogue.

good serial Entering the good serial

good boy Success

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')
This post is licensed under CC BY 4.0 by the author.