By analyzing the executable using PE bear, it becomes evident that it solely imports three DLLs: kernel32.dll
, user32.dll
, and gdi32.dll
. This suggests that either the executable is packed or some technique has been employed to obfuscate the API calls. Upon opening the executable in IDA, we promptly identify the utilization of the API hashing technique.
Resolving API Hashes
There are two functions available for hash computation. The first function calculates the hash of a DLL name, while the second function computes the hash from a function name. Not all hashes are the exact hashes of a dll or function.
It is important to note that not all the values in the array shown above represent the actual hashes of DLLs or functions. Many of these values result from performing an XOR operation between the original hash and a specific number. Now, let’s write few python scripts to extract the function names associated with those hashes.
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
# This script creates a list of the functions exported by various DLLs and saves them in exports.yaml
import pefile
import yaml
system32_path = "C:\\Windows\\System32\\"
dlls = [
'ntdll.dll',
'kernel32.dll',
'user32.dll',
'gdi32.dll',
'advapi32.dll',
'comctl32.dll',
'shell32.dll',
'ole32.dll',
'oleaut32.dll',
'msvcrt.dll',
'wininet.dll',
'winspool.drv',
'ws2_32.dll',
'netapi32.dll',
'rasapi32.dll',
'powrprof.dll',
'shlwapi.dll',
'urlmon.dll',
'crypt32.dll',
'version.dll',
'msvcp140.dll',
'ucrtbase.dll',
'vcruntime140.dll',
'd3d9.dll',
'activeds.dll',
'rstrtmgr.dll',
'wtsapi32.dll',
'd3d11.dll',
'opengl32.dll',
]
with open("exports.yaml","w") as f:
for dll in dlls:
dllPath = system32_path+dll
pe = pefile.PE(dllPath)
dllExports = pe.DIRECTORY_ENTRY_EXPORT.symbols
fnnames = []
for symbol in dllExports:
try:
fnname = symbol.name.decode("utf-8")
fnnames.append(fnname)
except Exception:
continue
yaml.safe_dump({dll:fnnames},f)
Once the exports.yaml file has been generated, we can proceed to write a script that reads function names from it, calculates function hashes, and resolves all the functions accordingly.
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
import pefile
import yaml
import idc
import ida_name
import sys
xorval_1 = 0x22065FED
ror = lambda val, r_bits, max_bits: \
((val & (2**max_bits-1)) >> r_bits%max_bits) | \
(val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))
def compute_dll_hash(dllname,num):
dllname = [ord(i) for i in dllname]
dllname.append(0)
for c in dllname:
if c>=65 and c<=90:
c += 32
num = c + ror(num,13,32)
return num
def compute_fn_hash(fnname,num):
fnname = [ord(i) for i in fnname]
fnname.append(0)
for c in fnname:
num = c + ror(num,13,32)
return num
# Location of all arrays containing the hashes
hashTables = [0x405AFC,0x405BBC,0x405C80,0x405CDC,0x405D14,0x405D50,0x405D64,0x405D84,0x405DB0,0x405DC0,0x405DCC,0x405DE4,0x405E14,0x405E2C]
# Location where the resolved addresses of functions will be stored
outputTables = [0x4112AC,0x411368,0x411428,0x411480,0x4114B4,0x4114EC,0x4114FC,0x411518,0x411540,0x41154C,0x411554,0x411568,0x411594,0x4115A8]
# In each array of hashes, the first entry stores the hash of the DLL, XORed with the value 0x22065FED. The function hashes begin from the second element.
# The last element of each array of hashes is 0x0CCCCCCCC
end = 0x0CCCCCCCC
with open("exports.yaml","rb") as f:
data = yaml.safe_load(f.read())
dllNames = list(data.keys())
storedDllHashes = [idc.get_wide_dword(arr) for arr in hashTables]
for dllName in dllNames:
print(f"Trying {dllName}")
hash = compute_dll_hash(dllName,0)^xorval_1
if hash in storedDllHashes:
index = storedDllHashes.index(hash)
itr = hashTables[index]+4
itr2 = outputTables[index]+4
dllfns = data[dllName]
actualDllhash = hash^xorval_1
dllfnHashes = [compute_fn_hash(fnname,actualDllhash) for fnname in dllfns]
ida_name.set_name(outputTables[index],dllName)
ida_name.set_name(hashTables[index],f"{dllName}_hashes")
while(idc.get_wide_dword(itr)!=end):
currentHash = idc.get_wide_dword(itr)^xorval_1
if currentHash in dllfnHashes:
ind = dllfnHashes.index(currentHash)
ida_name.set_name(itr2,f"fn_{dllfns[ind]}")
itr+=4
itr2+=4
print("All functions resolved")
Running this script in IDA gives us the following results:
Likewise, we can write a script that, given a hash, simply prints the corresponding API name.
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
import yaml
ror = lambda val, r_bits, max_bits: \
((val & (2**max_bits-1)) >> r_bits%max_bits) | \
(val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))
def compute_dll_hash(dllname,num):
dllname = [ord(i) for i in dllname]
dllname.append(0)
for c in dllname:
if c>=65 and c<=90:
c += 32
num = c + ror(num,13,32)
return num
def compute_fn_hash(fnname,num):
fnname = [ord(i) for i in fnname]
fnname.append(0)
for c in fnname:
num = c + ror(num,13,32)
return num
def get_function_name_from_hash(hash,xorVal=0):
with open('exports.yaml','rb') as f:
data = yaml.safe_load(f.read())
dllNames = list(data.keys())
for dllName in dllNames:
fns = data[dllName]
dllHash = compute_dll_hash(dllName,0)
fnhashes = [compute_fn_hash(fn,dllHash) for fn in fns]
if hash^xorVal in fnhashes:
return fns[fnhashes.index(hash^xorVal)]
return ""
print(get_function_name_from_hash(0x260B0745))
print(get_function_name_from_hash(0x6E6047DB))
print(get_function_name_from_hash(0xDA1AAEA2))
print(get_function_name_from_hash(0x576AC9D5))
print(get_function_name_from_hash(0x78403A7C))
print(get_function_name_from_hash(0x28805EB2))
Running this script gives us the following output:
1
2
3
4
5
6
HeapCreate
HeapAlloc
FindFirstFileW
FindNextFileW
FindClose
LoadLibraryW
Now, since we’ve resolved most of the functions from their hashes, we can start with the actual reverse engineering.
Initial Checks
Once the API functions have been resolved, the malware proceeds to perform a series of checks:
1
2
3
4
if ( !sub_40930C() && (unsigned int)sub_40155E() > 0x3C && sub_40931A(0) )
{
// ....
}
- The first condition checks whether the current user running the process is a member of the Administrators group or not.
1 2 3 4
int sub_40930C() { return fn_SHTestTokenMembership(0, DOMAIN_ALIAS_RID_ADMINS); }
- The second condition checks if the current operating system is Windows 7 (or Windows Server 2008 R2) and above.
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
int sub_40155E() { struct _PEB *peb; // eax unsigned int OSMajorVersion; // esi unsigned int OSMinorVersion; // edi peb = NtCurrentPeb(); OSMajorVersion = peb->OSMajorVersion; OSMinorVersion = peb->OSMinorVersion; if ( OSMajorVersion == 5 && !OSMinorVersion || OSMajorVersion < 5 ) return 0; if ( OSMajorVersion == 5 && OSMinorVersion == 1 ) return 51; if ( OSMajorVersion == 5 && OSMinorVersion == 2 ) return 52; if ( OSMajorVersion == 6 && !OSMinorVersion ) return 60; if ( OSMajorVersion == 6 && OSMinorVersion == 1 ) return 61; if ( OSMajorVersion == 6 && OSMinorVersion == 2 ) return 62; if ( OSMajorVersion == 6 && OSMinorVersion == 3 ) return 63; if ( OSMajorVersion == 10 && !OSMinorVersion ) return 100; if ( OSMajorVersion == 10 && OSMinorVersion || OSMajorVersion > 0xA ) return 0x7FFFFFFF; return -1; }
- The third condition checks whether the current process token belongs to the builtin administrators group or not.
If these conditions are met, the malware continues its execution otherwise it attempts a UAC bypass.
UAC Bypass
The malware conducts a string decryption process by XORing specific values with 0x22065FED, resulting in the decrypted string dllhost.exe
. It is also known as the COM Surrogate process. Further, it calls LdrEnumerateLoadedModules
and passes the string Elevation:Administrator!new:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}
to the function CoGetObject
which is used for UAC bypass.
Normal Execution without UAC Bypass
The malware employs a string decryption routine and subsequently transfers the decrypted values to APIs associated with the Windows registry. Let’s develop a code snippet that automates the string decryption task.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import binascii
def decrypt_string(buffer,xorKey):
answer = ""
buffer = [i^xorKey for i in buffer]
for item in buffer:
try:
val = binascii.unhexlify(hex(item)[2:]).decode("utf-8")[::-1]
answer += val
except Exception as e:
pass
return answer
buffer1 = [575233982, 575823787, 575102906, 574840767, 575365041, 577068932, 577331103, 577331102, 577920907, 574971825, 578772895, 577920925, 576806786, 577200031, 577658781, 570843028]
buffer2 = [577200032,577658766,577265540,574709640,577724312,570843017]
print(decrypt_string(buffer1,0x22065FED))
print(decrypt_string(buffer2,0x22065FED))
This gives us the following strings:
1
2
SOFTWARE\Microsoft\Cryptography
MachineGuid
It reads the value MachineGuid
from the registry key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography
, hashes it thrice, reverses the order of bytes and then encodes the hash using base64 encoding. In the resulting base64 string, it replaces +
with x
, /
with i
and =
with z
. It performs a string decryption to get the string %s.README.txt
, replaces the format specifier with the base64 string generated earlier by calling the function swprintf
and computes the hash of the resulting string <base64Data>.README.txt
. It seems to be the ransom note where the malware will write a message for the victim. It checks whether the current user is LocalSystem
. If the user is LocalSystem
, it uses the current token otherwise it performs a series of steps:
- It calls the API function
NtQuerySystemInformation
to retrieve system information. - It iterates through all processes in the system, verifying if the hash of some process name matches the value
0x3EB272E6
. If such process is found, it returns its process id. - To maximize device coverage, this hash could represent a commonly found Windows process hash. Let’s write a script to find out a process name whose hash matches this value.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
process_names = ["svchost.exe", "explorer.exe", "taskmgr.exe", "spoolsv.exe", "winlogon.exe", "services.exe", "lsass.exe", "wmiprvse.exe", "chrome.exe", "firefox.exe", "iexplore.exe", "wininit.exe", "dllhost.exe", "csrss.exe", "notepad.exe", "cmd.exe", "dwm.exe", "system.exe", "winword.exe", "outlook.exe"]
ror = lambda val, r_bits, max_bits: \
((val & (2**max_bits-1)) >> r_bits%max_bits) | \
(val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))
def compute_hash(name,num):
name = [ord(i) for i in name]
name.append(0)
for c in name:
if c>=65 and c<=90:
c += 32
num = c + ror(num,13,32)
return num
for name in process_names:
if compute_hash(name,0)==0x3EB272E6:
print(name)
break
Running this script, we get the process name which is explorer.exe
.
The malware calls NtOpenProcess
to get a handle to the process explorer.exe
and passes the prcoess handle to the function NtOpenProcessToken
to retrieve the token. Further, it calls NtDuplicateToken
to create a duplicate of the existing token. If the malware fails to retrieve the duplicated token, it performs a series of steps to get a token.
- It calls the API function
NtQuerySystemInformation
to retrieve system information. - It iterates through all processes in the system, verifying if the hash of some process name matches the value
0xB7E02438
. If such process is found, it returns its process id.
By running the script we wrote earlier to calculate the hashes of common process names, we obtain the process name as svchost.exe
. The process id of svchost.exe
is calculated and passed to the function NtOpenProcess
with the PROCESS_ALL_ACCESS
flag which gives a handle to the process. Further, it checks if the process svchost.exe
is running as a 64-bit process. If yes, it calls a function at the offset 0x64cc from the image base.
The malware allocates memory of size 513 bytes using RtlAllocateHeap
, and copies a global buffer into it.
Shellcode Extraction
Using PE bear, we notice that the offset 0x12000
corresponds to the virtual address of the .rsrc
section.
The first dword stored in the .rsrc
section is a seed and the second dword denotes the size of the encrypted data stored there.
1
2
3
4
5
6
7
8
int __stdcall keygen(unsigned int a1, int *seed)
{
int v2; // edx
v2 = 0x8088405 * *seed + 1;
*seed = v2;
return (v2 * a1) >> 32;
}
The seed is modified with each call to this function, thereby returning a different value everytime. After that, it xors each byte of the encrypted data with the corresponding byte of the generated key. After that, it decompresses the decrypted data by using the atlib decompression algorithm. Let’s write a script to decrypt this encrypted buffer. The initial value of the seed comes from the first dword present in the .rsrc
section i.e. 0xffcaa1ea
.
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
import struct
def key_generator(value,seed):
v2 = (0x8088405 * seed + 1)&0xffffffff
return ((v2 * value) >> 32)&0xffffffff, v2
def generate_keystream(bufLen):
seed = 0xffcaa1ea
newSeed = seed
keystream = b''
for i in range(bufLen):
value,newSeed = key_generator(seed,newSeed)
keystream += struct.pack("<I",value)
return keystream
def decrypt_buffer(keystream,buffer):
bufLen = len(buffer)
decrypted = []
for i in range(bufLen):
x = buffer[i]^keystream[i]
decrypted.append(x)
return bytes(decrypted)
encBuf1 = b'"\xefx\x91\xf2=_U%\xa4~\xff\x89t\xf3j\x83q\xcew\x01\x08v)w\xe38u\xd0U\x81gdv\xb9Y\x9f\x14L\xa5\xabb\xfd\x8aa\x15\x96b\xc5v\x880\x993\x83 "\xafI63\x07\xa3\xcc\xa6Z\x1aC\xee\xcf\xf0&\xfch\xbb\x02\xccW\xa1\xaa\x99\xfa\x02*q\xf3\xe8\xf2j\xbd\xab\x9f"\n\xa81\xc1q\ns1g\xce8j\xde\x0cm55\x9b\x1eS(0\xf6\xc7\xef\xeajIXV\x97\xd9\xfc]1s\xfd3\x96\xebT?:\x1f\x12$\x16o6n_\x1d\xaa\\I\x8d5}\xb1x\x00Gc\xcf\xa3\xba\xff]\x11\xf2pO)Sso}\xaf\xf4\x13\x0bp\xccX\t\xefn\xfak\xf6/\x0e\x87\x9b\xae\xf2\xba\xbd\x1bT${NN5\xc6v>b\xfb\x92\xf2T\x87\xbd\x87\x07\xd6\xfe\xe7\xc5\xb5\x94\xf2\x1f\x16\x7f6%\xf8\xc6Z>\x8a\'\x01\x8f\xc2\xb1\x9a\xd3\x0f\x06\xc5\x9e\x0b$\x99\x196=I\xb1g}\xb0\x8eS\xedk\x16[\xb6EU\x1a@\xc8n<p\xc1\x07i\x9a"E\xd4\xb8\xaf\x12\x1e[\xaa\xf2\x02\xad\x17\xa4+L\xc7=\xf0>\xbc\n\x07N\xa4m7\xb9\x0fF\xdfWt\x0f\\\xff:q\xdb\xaa\xe6TPQ\xc2\xfd\xe0\x9aN\xa3%\xdb\xb1k\xba\xcd\xd3\x07 \xe4:)\x1c\x8bo\xed9\xf8\x1eS\x19\xdd\xcc\x9b;\x864Z\xf3\x16\xe7n\xb2\xbd\xf0\xcd\x8e\xb6\x93$\x82:\x08\xc0\x96\x04Y\xb8\xf0\xaaQ\x1dg!\x03\x95\xa2\x0e\x0c\xc9VP%\xb0^\xce\xadN\xe9y\xeav7\x15\xfa\x96\x84\x08\x17\xf7q\\-\x18\x05\xa3\xec\x12.\xa7\xf9+\xcb\x05I\xaf\x1eG\xad\xee\xac\xd1\x05\x15\x9b\xe3\x95\xbf8\x05\xdf\xf1$o\xc9\xa3\xecf~\xedS\xe9\x01\x15\xb0\xe2\x1dh\x15\x99k\xeb\xc3!\x99\x93\xd8)\xd1\x1d\xa9AU\xa4g\xeez7\xae\xa8\x08\xc9,\x08\xb8\xbft&lj\x04\xd5\xfd0\xe6\xf9N\x87\xa3\xf3L9P \xf86\x8d\x1a\xbd\xd2\xb5H\x00\x81\xf2?\x80\x0e\x14\xd6\x12\x03WCU\xcf\x02\x00'
encBuf2 = b'\x00_|\xd9\x1a\x14_\xd6!\x81{4\xc1!\xa0<\xd40\x9c?\x80\xe4\xc6)w\xe3p\xf8|q\x01gdv\xf1\x9e\\\x9c\xc7)5ZS\x7f\xac\xc4\r\xf1\xe1\xb1\x14v\xd4\x92\xac\xbe\xd5\x13/Z\xdb\xf8/\x87\xba\xed\r\xbf\xdc\x9eJ=\x14\xfd6W<\x94#\x9f\xfc\xe5\x83\x98I\x02\xe2\xc9\xd2\'\x83\x1c\xe5N\x8c]L\n\x1e;\xf2\xf3\x86T\x85_\xf3\x92\xca5\x07,\x1f\xe5\x89\x90k\xb4\xcbjI\x10\xdd\x14V3,\xe9?v\xf5\xda(\\3\xd9k4\x04\x16o\xc9\xbe\x19\xd7\xee\xcau3\xc0\x95\xecyL\xcc\xd3\ne\t\xb7\x97\xbb\xd5p\x03\xa0\xa0\xcd\x8f\xaa\xa2L\x90S\xb3\x84\xd0\xc8x\x02So\x15c\xa3\xfb\xbf\xbe\xba\xef\xa6\x8aX\x8b\xa7}\x8e\xbe\x9b\x16\xb1\xad\xaa\x8a\x94\xf9\xe1>7\x81\xe0\xd0\xc1FM\xce\x85S\xb7\xb8\xdaMx\x01;\xbe-\x81\x84`\x9f\xb2BV\xcf\x8c\xcao\x93\xa8>\x83;b\x13\xec\xa4}\xb2\x8f\x8f\xd60\x07Lf\xf2\xd0\xf0i:U\xbe\xab\xed\x8c4\xb2&C\xe4\x98K\x19\x94\xc2\x19\xcf*\xab\x1d\x99(\x94B\xb5B\x8a\xd0\xa1\xc4\x06/\x88\x962\xeb\x9f\xab$J\xef\xd2\xaah"\xc5v\xea\xfc\xee\xd03\xe7N\xda\'\xc6\xff\xff\x81#5\x05~\xf7h#\x7f\xa4T\x00k\xc8\x15u[\xbb\x19\xddG\xca\xb4\xcal\xca\xbf\x9dX\xaeps\xa8v@\xf5\xe7\xe1\xe4\xc9\r\x11A\xd2\x02\xe5\x18\xef\xd4\xd4\x13\x89\x0e<\xdeuDB\x19\xdb\x12F\xbb5\x91\xf9\x9f\xbc\x8b\x8e\xf2B\x15\xc7G\x1c\\$\xf9\xa3\x88\xa2\x80\xdc\xf0\xb1\x17\x0c\xcd\x80\xd4\x8cg/\x15N\xdc\x1fg\x1a\xfe\x8c\xa4l;\xb7\xa5\xd5\xb7p\xba\x10\xd9\xf9h\xed\x06\xf9\x84\xdb\xf6\xa1\x89\xa8\xd7\xa8,\x0e\xc6\xb6k\xd1\x12\xd8\x92-|P\xd8\xfe|\xfc\xac|+\x9e\xab\xd7g\x87\x18\x83\x90_t.d\xb2\xa8!8\xbaj[\xa4F!\xc5\x97\xeeP\x8fr9:\xd9\xf5\xdf\xc6\xe5\xd3\xbf\xaf\xbdP\x03h\x81\x04\r_M\xc0\xb8\xeb\xbe\xc73dhD<t\xee_\xee\xdd\xeaG\x01R\xc5l!\xee\x96U\xcdi\xfe\x14-\x99\x14b\xaa\x82n\xdb\xdcr\xdbH\xce\xe4\x1cQ\xe0\xedh\xa8^.c\x17O\x1b\xde\xb3\x16\x1f\x9ex\xfb \xa4Qd\x17\xa0\xef\xdcy\xe7\x92;\xe1Q\xdci\xc9\xaeVU\xd3\xaf2\xc9;\x98\x0f#\x7f\xd13\xa8\x14o\x1f\x0e.,\xcd\xccA\xa2\x153\xdd\xc571\xeen\xf3\xbaD=w3\xa1)\x9f\tXYs\xe2O\x98\xa2E\xeb\x072\xb0^\xda\xf9\xda\xe4\x9e]2\xaat\xde\xd4(\x94\xc8\xd4B"Z\xb87e\xb9r^\xaa\xbe-\xde\xa6\x02\xf3\xc7[\xfc\xb6h\nVA\xcf\xd1\xf39\x9f\tm\x8f\xa64\xd0\xaa_\x8c\x02\x811\xac\x8a-\xe2!F\xb37\xe1b\xe0 (4\xff\x02\xf2'
keystream = generate_keystream(len(encBuf1))
decBuf1 = decrypt_buffer(keystream,encBuf1)
decBuf2 = decrypt_buffer(keystream,encBuf2)
with open("shellcode1.bin","wb") as f:
f.write(decBuf1)
with open("shellcode2.bin","wb") as f:
f.write(decBuf2)
Running this script gives us two files shellcode1.bin
and shellcode2.bin
which can be further analysed.
Config Extraction
Let’s write a python script to generate the keystream, decrypt and decompress the data stored in the .rsrc
section.
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
from pefile import PE
import struct
from aplib import decompress
import base64
file = "blackmatter.exe"
def zipcrypto_lcg(val,seed):
newSeed = (0x8088405*seed + 1)&0xffffffff
return ((newSeed*val)>>32)&0xffffffff,newSeed
def read_rsrc_section():
pe = PE(file)
sections = pe.sections
for section in sections:
if b".rsrc" in section.Name:
desiredSection = section
break
return desiredSection.get_data()
def get_dwords_from_byte_buffer(byteBuffer):
dwordsBuf = []
for i in range(0,len(byteBuffer)-4,4):
data = byteBuffer[i:i+4]
dwordsBuf.append(int.from_bytes(data,"little"))
return dwordsBuf
def generate_key(seed,bufLen):
key = b''
newSeed = seed
for i in range(0,bufLen,4):
value,newSeed = zipcrypto_lcg(seed,newSeed)
key += struct.pack("<I",value)
return key
def decrypt_buffer(buffer,keystream,size):
decrypted = []
for i in range(size):
decrypted.append(buffer[i]^keystream[i])
return bytes(decrypted)
dwordsBuf = get_dwords_from_byte_buffer(read_rsrc_section())
encData = read_rsrc_section()[8:]
seed = dwordsBuf[0]
bufLen = dwordsBuf[1]
key = generate_key(seed,bufLen)
decryptedBlob = decrypt_buffer(encData,key,bufLen)
decompressedData = decompress(decryptedBlob)
print(decompressedData)
We can see that the decompressed data contains various base64 blobs separated by null bytes. Continuing our analysis, we quickly figure out that the malware reads and decodes these base64 encoded chunks. Let’s enhance the existing script by incorporating a function that enables the decoding of the decompressed data.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Completing the existing script
def decode_config(config):
b64strings = config.split(b"\x00")
configItems = []
for b64string in b64strings:
try:
data = base64.b64decode(b64string)
items = data.split(b'\x00\x00')
items = [item.replace(b'\x00',b'') for item in items]
for item in items:
try:
decodedData = item.decode("utf-8")
if len(decodedData)!=0:
configItems.append(decodedData)
except Exception:
pass
except Exception:
pass
return configItems
base64Blobs = decompressedData[204:]
configData = decode_config(base64Blobs)
print(configData)
Summary
- Extracted configuration data
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
encsvc thebat mydesktopqos xfssvccon firefox infopath winword steam synctime notepad ocomm onenote mspub thunderbird agntsvc sql excel powerpnt outlook wordpad dbeng50 isqlplussvc sqbcoreservice oracle ocautoupds dbsnmp msaccess tbirdconfig ocssd mydesktopservice visio mepocs memtas veeam svc$ backup sql vss https://paymenthacks.com http://paymenthacks.com https://mojobiden.com http://mojobiden.com