Overview

HackingTeam Soldier Implant (packed): 76840fa18df8764afb51f1aa6da10ff65f1bdfe434dc988917380fa31fbe3a73

All samples on available on Malshare.

References:

Unpacking VMP

  • Run wihtout debugger and pause with ProcessHacker
  • Attach with x64dbg and search .text for MSVC securty cookie constant
  • Based on security cookie constant location determine __security_init_cookie function location
  • Add hardware bp on __security_init_cookie function and relaunch process with debugger
  • Use call stack to determine what called __security_init_cookie function this is OEP
  • Remove hardware bp from __security_init_cookie and add hardware bp on OEP
  • Relaunch program so we break on OEP
  • Use VMPImportFixer to dump process
  • Fix OEP statically (PEBear)

TODO: When we dump with VMPImportFixer there are some imports that are not protected in the binary. These are not captured by VMPImportFixer which means some of our imports are not resolved in the dump! We could maybe fix this by either patching VMPImportFixer to also add unprotected imports ... or we could update VMPFix to add both protected imports and normal imports to the new IAT then dump with Scylla instead of VMPImportFixer.

Config Extraction

  • The config is AES encrypted with CBC and a null IV
  • There is a seperate setup function that is used to load the key which is hard coded in the .data section
  • The config is stored in the .data section and is preceded by a DWORD with the length
def unhex(hex_string):
    import binascii
    if type(hex_string) == str:
        return binascii.unhexlify(hex_string.encode('utf-8'))
    else:
        return binascii.unhexlify(hex_string)

def tohex(data):
    import binascii
    if type(data) == str:
        return binascii.hexlify(data.encode('utf-8'))
    else:
        return binascii.hexlify(data)
data_len = 0x240
data = unhex('238ba1f22cb105cdae82eb2dde4e2783c80370198e1bb02c231825e7b2d8c164109e5a4e09c63a36df5ed3c5ce49b825968d386eb9008ed2b6cae3b75cd8ba76d49ab1acf69d46a747934187883b2eb27aaab99dae892693dc714433ec2de2d238a036f5ff492414a6a4928f4bd907342e7fff0dd6e61f2a7675b351842a27bc1ba7d5ad7ebc44a11cfafd136f42644db89db7189c4d152a06b10f0c0a4b40c021d7192a04cd383b17434048768076c7d7eb073ef340c696aa35946aacbe99821352487082660334de43ca4e5e142d78f70dc7cb5b571463c3c3c7caf461eac5411136142b0051914f74859c44610f8e4fdcf4f6c269b03809e3aa67ebc3474ece444fe2f5078d36857c17147d834c6cb6ccd9effefca8b24ee01c0868fc1383ef26519c4f573f94b5bbb7b149dcc190f0a08fdc31965d69d97bbcf565e829182b8aa421f46a7e0100180d7ed9d31083b4b68431e0b888bbacb00864f140716359290eb37927ec0ea537b620e1108265f412a28c28c2df0cf1d713880cd874f79872dde1060d29cc7536684d49b504dcfc61c80a1210a5e69533bba05aad0a6dc7239ea90e29e822e0424f06524330a98f40e56c1149bad0350b6c79b1c03963e0cf43dce2f6f6e6412dee43ef937bff51bf345e57bc60c7df2ec4294cc2789547896a53dc43ab5fc15c6a377d0dbab97ff5e091f90fc28ad7acda94543b52bccea2c8f360e03a5fc01cab6d358804037ab20e17facc31293b437b7a22d8c32f835308fae01b83396667f18b47882e93b309ee31cc3e9c619a6a405db7e3bbcc')
data = data[:data_len]
key = unhex('60ab854458b00a742c6e8ceb7f1094da')

def decrypt(data, key):
    from Crypto.Cipher import AES
    cipher = AES.new(key, AES.MODE_CBC, iv=b'\x00'*16)
    return cipher.decrypt(data)

# remember to split the rest of the string after the null
decrypt(data, key)
b'{"camera":{"enabled":false,"repeat":0,"iter":0},"position":{"enabled":false,"repeat":0},"screenshot":{"enabled":false,"repeat":0},"photo":{"enabled":false},"file":{"enabled":false},"addressbook":{"enabled":false},"chat":{"enabled":false},"clipboard":{"enabled":false},"device":{"enabled":true},"call":{"enabled":false},"messages":{"enabled":false},"password":{"enabled":false},"keylog":{"enabled":false},"mouse":{"enabled":false},"url":{"enabled":true},"sync":{"host":"178.128.215.46","repeat":600},"uninstall":{"date":null,"enabled":false}}\x00"\x04&\xceB\xa0\x82\xd4.U\x81\x8d\xdb\x1ai]\xae\xbdZ\x85\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e'
.text:012EF39B 3D EF BE AD DE       cmp     eax, 0DEADBEEFh
.text:012EF3A0 75 09                jnz     short loc_12EF3AB
.text:012EF3A2 B8 01 00 00 00       mov     eax, 1
.text:012EF3A7 8B E5                mov     esp, ebp
.text:012EF3A9 5D                   pop     ebp
.text:012EF3AA C3                   retn
.text:012EF3AB 56                   push    esi
.text:012EF3AC 68 58 B5 38 01       push    offset config_key
.text:012EF3B1 50                   push    eax
.text:012EF3B2 68 57 02 38 01       push    offset asc_1380257 ; "#"
.text:012EF3B7 E8 84 14 00 00       call    mw_aes_decrypt
import pefile
import re
import struct

pe_data = open('/tmp/work/ht.exe.fixed', 'rb').read()
pe = pefile.PE(data = pe_data)

target_code = pe_data.split(b'\xEF\xBE\xAD\xDE')[1]
egg = b'\x68(....).\x68(....)\xe8'
m = re.search(egg, target_code)
if not m:
    print("All hope is lost!")
config_va = struct.unpack('<I', m.group(2))[0]
key_va_bytes = m.group(1)
config_offset = pe.get_offset_from_rva(config_va - pe.OPTIONAL_HEADER.ImageBase)
config_len = struct.unpack('<I', pe_data[config_offset -4:config_offset])[0]
config_data = pe_data[config_offset:config_offset+config_len]
tohex(config_data)
b'238ba1f22cb105cdae82eb2dde4e2783c80370198e1bb02c231825e7b2d8c164109e5a4e09c63a36df5ed3c5ce49b825968d386eb9008ed2b6cae3b75cd8ba76d49ab1acf69d46a747934187883b2eb27aaab99dae892693dc714433ec2de2d238a036f5ff492414a6a4928f4bd907342e7fff0dd6e61f2a7675b351842a27bc1ba7d5ad7ebc44a11cfafd136f42644db89db7189c4d152a06b10f0c0a4b40c021d7192a04cd383b17434048768076c7d7eb073ef340c696aa35946aacbe99821352487082660334de43ca4e5e142d78f70dc7cb5b571463c3c3c7caf461eac5411136142b0051914f74859c44610f8e4fdcf4f6c269b03809e3aa67ebc3474ece444fe2f5078d36857c17147d834c6cb6ccd9effefca8b24ee01c0868fc1383ef26519c4f573f94b5bbb7b149dcc190f0a08fdc31965d69d97bbcf565e829182b8aa421f46a7e0100180d7ed9d31083b4b68431e0b888bbacb00864f140716359290eb37927ec0ea537b620e1108265f412a28c28c2df0cf1d713880cd874f79872dde1060d29cc7536684d49b504dcfc61c80a1210a5e69533bba05aad0a6dc7239ea90e29e822e0424f06524330a98f40e56c1149bad0350b6c79b1c03963e0cf43dce2f6f6e6412dee43ef937bff51bf345e57bc60c7df2ec4294cc2789547896a53dc43ab5fc15c6a377d0dbab97ff5e091f90fc28ad7acda94543b52bccea2c8f360e03a5fc01cab6d358804037ab20e17facc31293b437b7a22d8c32f835308fae01b83396667f18b47882e93b309ee31cc3e9c619a6a405db7e3bbcc'
.text:013031B6 0F 10 05 7A 01 38 01      movups  xmm0, xmmword_138017A
.text:013031BD 0F 11 05 58 B5 38 01      movups  config_key, xmm0
key_egg = b'\x0F\x10\x05(....)\x0F\x11\x05' + key_va_bytes
m = re.search(key_egg, pe_data)
if not m:
    print("All hope is lost!")
key_data_va = struct.unpack('<I', m.group(1))[0] 
key_data_offset = pe.get_offset_from_rva(key_data_va - pe.OPTIONAL_HEADER.ImageBase)
key_data = pe_data[key_data_offset:key_data_offset+16]
tohex(key_data)
b'60ab854458b00a742c6e8ceb7f1094da'
decrypt(config_data, key_data)
b'{"camera":{"enabled":false,"repeat":0,"iter":0},"position":{"enabled":false,"repeat":0},"screenshot":{"enabled":false,"repeat":0},"photo":{"enabled":false},"file":{"enabled":false},"addressbook":{"enabled":false},"chat":{"enabled":false},"clipboard":{"enabled":false},"device":{"enabled":true},"call":{"enabled":false},"messages":{"enabled":false},"password":{"enabled":false},"keylog":{"enabled":false},"mouse":{"enabled":false},"url":{"enabled":true},"sync":{"host":"178.128.215.46","repeat":600},"uninstall":{"date":null,"enabled":false}}\x00"\x04&\xceB\xa0\x82\xd4.U\x81\x8d\xdb\x1ai]\xae\xbdZ\x85\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e'