Overview

Samples

96baba74a907890b995f23c7db21568f7bfb5dbf417ed90ca311482b99702b72 malshare

References

Analysis

According to CERTPL (2016) ref

The C2 IP address list is hardcoded in the binary in an encrypted form. The algorithm used for obfuscation is very simple – it XORs the message with the hardcoded key. Data decrypts to three IP+port pairs – at least in the analyzed sample, the port was equal to 443 for all of them. The probable reason is to conceal communication by using port dedicated for SSL traffic.

Decrypt Strings

data = bytes.fromhex('97db17556bbee0734195d17d')

out = []
key_xor = 0xE4
key_add = 0xc8


for i in range(len(data)):
    out.append(key_xor ^ data[i])
    if bool(i%2):
        key_xor = (key_xor + key_add - 1) & 0xff
    else:
        key_xor = (key_xor + key_add + 1) & 0xff

print(bytes(out))


    
    
b'svchost.exe\x00'
out = []
key_xor = 0xE4
key_add = 0xc8
flip = True

for c in data:
    out.append(c ^ key_xor)
    if flip:
        key_xor = (key_xor + key_add + 1) & 0xff
    else:
        key_xor = (key_xor + key_add - 1) & 0xff
    flip = not flip

print(bytes(out))
b'svchost.exe\x00'
def decrypt(data,key_xor,key_add):
    out = []
    flip = True

    for c in data:
        out.append(c ^ key_xor)
        if flip:
            key_xor = (key_xor + key_add + 1) & 0xff
        else:
            key_xor = (key_xor + key_add - 1) & 0xff
        flip = not flip
    
    return bytes(out)


decrypt(bytes.fromhex('97db17556bbee0734195d17d'), 0xE4, 0xc8)
b'svchost.exe\x00'

Locate Encrypted Strings

68 C8 00 00 00                          push    0C8h ; 'È'
68 E4 00 00 00                          push    0E4h ; 'ä'
6A 2E                                   push    2Eh ; '.'   - size
68 AC 06 41 00                          push    offset byte_4106AC
BF F8 22 41 00                          mov     edi, offset byte_4122F8
57                                      push    edi
E8 EE A3 FF FF                          call    mw_decrypt
68 C8 00 00 00                          push    0C8h ; 'È'
68 E4 00 00 00                          push    0E4h ; 'ä'
 6A 09                                   push    9  - size
68 88 07 41 00                          push    offset byte_410788
BF F8 22 41 00                          mov     edi, offset byte_4122F8
57                                      push    edi
 E8 66 95 FF FF                          call    mw_decrypt
57                                      push    edi
56                                      push    esi
6A 17                                   push    17h - size
68 80 10 41 00                          push    offset byte_411080
55                                      push    ebp
E8 0C 49 FF FF                          call    mw_decrypt
import pefile
import re
import struct

def is_ascii(s):
    return all(c < 128 or c == 0 for c in s)

file_data = open('/tmp/toffsee.bin','rb').read()
pe = pefile.PE(data=file_data)
rdata_vstart = None
rdata_vend = None

pe_base = pe.OPTIONAL_HEADER.ImageBase

for s in pe.sections:
    if s.Name.startswith(b'.rdata'):
        rdata_vstart =  s.VirtualAddress + pe_base
        rdata_vend = rdata_vstart + s.Misc_VirtualSize 

        
assert rdata_vstart is not None
print(f"rdata_vstart: {hex(rdata_vstart)}")
print(f"rdata_vend: {hex(rdata_vend)}")


for s in pe.sections:
    if s.Name.startswith(b'.rdata'):
        rdata_vstart =  s.VirtualAddress + pe_base
        rdata_vend = rdata_vstart + s.Misc_VirtualSize 
rdata_vstart: 0x410000
rdata_vend: 0x411e9a
egg_string_push_loose = rb'\x6A(.)\x68(....)(.){1,6}\xe8'
egg_string_push_tight = rb'\x68(....)\x68(....)\x6A(.)\x68(....)(.){1,6}\xe8'

key_adds = []
key_xors = []

for m in re.finditer(egg_string_push_tight,  file_data):
    key_adds.append(struct.unpack('<I',m.group(1))[0])
    key_xors.append(struct.unpack('<I',m.group(2))[0])

key_xor = max(set(key_xors), key = key_xors.count)
key_add = max(set(key_adds), key = key_adds.count)

print(f"xor: {hex(key_xor)} add:{hex(key_add)}")
xor: 0xe4 add:0xc8
enc_strings = []

for m in re.finditer(egg_string_push_loose, file_data):
    str_len = struct.unpack('B', m.group(1))[0]
    str_vaddr = struct.unpack('<I', m.group(2))[0]
    if rdata_vstart <= str_vaddr <= rdata_vend:
        enc_strings.append(pe.get_data(str_vaddr - pe_base, str_len))

        
strings = []
for e in enc_strings:
    tmp_str = decrypt(e, key_xor, key_add)
    if is_ascii(tmp_str):
        print(tmp_str)
        strings.append(tmp_str)
b'c:\\Windows\x00'
b'\\system32\\\x00'
b'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\x00'
b'MSConfig\x00'
b' /r\x00'
b'.exe\x00'
b'SYSTEM\\CurrentControlSet\\services\x00'
b'SYSTEM\\CurrentControlSet\\services\x00'
b'SYSTEM\\CurrentControlSet\\services\x00'
b'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\x00'
b'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\x00'
b'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\x00'
b'.exe\x00'
b'qazwsxed\x00'
b'%s%i%i%i%i.bat\x00'
b'@echo off\r\n:next_try\r\ndel "%s">nul\r\nif exist "%s" (\r\nping 127.0.0.1 >nul\r\ngoto next_try\r\n)\r\ndel %%0\r\n\x00'
b'cmd /C move /Y "%s" %s\r\nsc config %s binPath= "%s%s /d\\"%s\\""\r\nsc start %s\r\n\x00'
b'svchost.exe\x00'
b'ConsentPromptBehaviorAdmin\x00'
b'PromptOnSecureDesktop\x00'
b'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\x00'
b'MSConfig\x00'
b'.exe\x00'
b'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\x00'
b'.exe\x00'
b'"%s" /u"%s"\x00'
b'.exe\x00'
b'ver=%d lid=%d\nwin=%X/%d sid=%s\nrep=%s\n\x00'
b':.repos\x00'
b'\\Local Settings:.repos\x00'
b'\\Local Settings\\Application Data\\Microsoft\\Windows\\UsrClass.dat.repos\x00'
b'\\wincookie.repos\x00'
b'TMP\x00'
b'Config\x00'
b'Control Panel\\Buses\x00'
b'Config\x00'
b'SOFTWARE\\Microsoft\\Buses\x00'
b'Config\x00'
b'Control Panel\\Buses\x00'
b'Config\x00'
b'SOFTWARE\\Microsoft\\Buses\x00'
decrypt(bytes.fromhex('97db154f70acf83b4c88dd106a79bbed642df4bd844d14dda46d34fdc48d541de4ad743d04cd945d24edb47d440dd49d642df4bd844d14dda46d34fdc48d541dbb010000018ec200486aa5f13449c3da1c2968d49d642df4bd844d14dda46d34fdc48d541de4ad743d04cd945d24edb47d440dd49d642df4bd844d14dda46d34fdc48d541dbb'),0xe4,0xc8 )
b'svartalfheim.top\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00_\xact=\x05CV]l\x87\x11\x8cpD\x17Gx\x04\x9ci\x19)9)\x19\xe9y\xe9\x19)9)\x19i\xf9i\x19)9)\x19\xe9y\xe9\x19)9)\x19i\xf9i\x19)9)\x19\xe9y\xe9\x19)9)\x19i\xf9i\x19v'

TODO: The C2 is stored in the .data section not the .rdata so we need to create a second regex to find this specific encrypted "string". The following is an example.

56                                      push    esi
57                                      push    edi
6A 02                                   push    2
BE 59 20 41 00                          mov     esi, offset unk_412059
5F                                      pop     edi
68 C8 00 00 00                          push    0C8h ; 'È'
68 E4 00 00 00                          push    0E4h ; 'ä'
6A 40                                   push    40h ; '@'
56                                      push    esi
56                                      push    esi
E8 39 60 FF FF                          call    mw_decrypt