what’s the agenda
Welcome back :)
Today we’ll go through a challenge from Zero2Automated: Advanced Malware Analysis which ask us to reverse engineer the string decryption routine and write an automation script for it.
The sample provided is a Gozi/ISFB malware which is couple of years old and is packed, also checking file entropy of 7.5 says many random bytes are present which supports file being packed.
It’s a PE32 DLL file with SHA256 hash of 0a66e8376fc6d9283e500c6e774dc0a109656fd457a0ce7dbf40419bc8d50936. Strings output consist mostly of random strings, with some noticeable strings here n there like a PDB file path d:\in\the\town\where\ahung.pdb and a file name myfile.exe in version info.
first stage: something to unpack
Lets get started with unpacking the payload
- to unpack we gonna use
x32dbgto debug and apply breakpoints on widely used windows API for unpacking and stuff likeVirtualAllocfor memory allocation,VirtualProtectfor memory protection changes,CreateProcessInternalWandCreateProcessAin case of process creation, - now executing the sample we encounter three consecutive
VirtualAlloccall, where first address when filled looks gibberish, second address (inDump 2) reveals aMZheader but looks kinda compressed and third address (inDump 3) is also filled withMZheader but with a.bsssection and looks clean than prior, followed by aVirtualProtectcall on itself
- assuming
Dump 3gonna be the next payload, we dump the memory to new file and unmap it using pe_unmapper or manually usingPEbear, doing this will provide us an unpacked second stage ready to analyze further with IDA and Ghidra.
second stage: finding the decryption routine
Taking an overview, we see it imports 3 libraries with interesting functions like:
- from
NTDLL.dllimports system query related function:NtQuerySystemInformation - from
KERNEL32.dllimports process and thread manipulating functions:SetThreadPriority,GetCurrentThread,OpenProcess,CreateEventA,QueueUserAPC - not much from
ADVAPI32.dll
and only exports DllRegisterServer, also not much from string output can be seen other than imports.
Also you’ll notice .bss section does not show any symbols which indicates possible encrypted bytes, keeping this in mind, we’ll start with DllEntryPoint
DllEntryPoint: the entrance
Giving it an overview we encounter a function call sub_10002009 which takes another sub_10001B7F as an argument,

now sub_10002009 practices process injection sub-technique known as APC Injection(see MITRE ATT&CK T1055.004) which is done by creating a thread using CreateThread that will execute a SleepEx which triggers the execution of sub_10001B7F, see the action below

sub_10001B7F: the function invoked

Here it sets the current thread priority to THREAD_PRIORITY_BELOW_NORMAL, then calls sub_10001308
sub_10001308: preparing the decryption
first it calls sub_100010C4 which creates an unnamed event using CreateEventA, then using OpenProcess gets the handle to the current process with custom access right ie. 0x10147a (Query information, Create threads, VM operation, VM read, VM write, Duplicate handles, Synchronize) and returns,

on success, calls NtQuerySystemInformation API to get the SystemProcessorPerformanceInformation struct and stores in allocated heap of 0x30 bytes, after some calculation with output struct which includes a modulo 19, it will generate a number to pass as an argument to sub_1000197C
sub_1000197C: decryption follows
this calls sub_10001922 which takes handle to this dll as one of the arguments to find .bss section and store its Virtual Address and SizeOfRawData

then next function called is sub_10001FD8(mem_allocated from VirtualAlloc, pointer to bss section, key, 0x400)
where the key consist of (dword "Apr" + dword "26 2022" - 1) + bss_addr + number generated, in ghidra we can see the dword part is calculated beforehand ie. 0x5292a672.
Inside sub_10001FD8 the decryption goes like this:
- it takes a dword from encrypted
bsssection - subtracts it with the key
- then add it to the previous
bssdword - and store the result in memory allocated (passed as first argument)
the decryption script
# script.py
import pefile
import struct
def get_bss(pe):
for section in pe.sections:
if b'.bss' in section.Name:
return section.VirtualAddress, section.PointerToRawData, section.SizeOfRawData, section.get_data()
def gen_key(bss_va, date, random):
first_dword_date = struct.unpack("<I",date[0:4])[0]
second_dword_date = struct.unpack("<I",date[4:8])[0]
key = (first_dword_date + second_dword_date - 1) + bss_va + random
return key
def decrypt_bss(data, key):
prev_dword = 0
new_bss = b""
for i in range(0, len(data), 4):
curr_dword = struct.unpack("I", data[i:i+4])[0]
if curr_dword:
new_bss += struct.pack("I",(curr_dword + (prev_dword - key)) & 0xffffffff)
prev_dword = curr_dword
else:
break
return new_bss
def main():
sample = input("[?] the sample is: ")
date = input("[?] and the campaign date: ").encode()
pe = pefile.PE(sample)
bss_va, bss_raw, bss_raw_size, enc_bss = get_bss(pe)
print("\n[+] Info .bss section:")
print(f"[+] - virtual address: {hex(bss_va)}")
print(f"[+] - pointer to raw data: {hex(bss_raw)}")
print(f"[+] - size Of raw data: {hex(bss_raw_size)}")
print()
for i in range(0, 20):
key = gen_key(bss_va, date, i)
print(f"[*] trying key: {hex(key)} with num_gen: {i}")
dec_bss = decrypt_bss(enc_bss, key)
if b'NTDLL' in dec_bss:
print(f"\n[+] key found {hex(key)} where number genrated is {i}")
print(f"\n[+] decrypted .bss: \n{dec_bss}")
break
# construct a new file with decrypted bss
pe_data = open(sample, 'rb').read()
final_patched_pe = pe_data[:bss_raw] + dec_bss + pe_data[bss_raw+len(dec_bss):]
open("gozi_decoded_bss.exe", 'wb').write(final_patched_pe)
if __name__ == "__main__":
main()
On the right of screenshot, we see the output from the above script and in the left we can see symbols in the decrypted bss section from created file:

That’s it for now :), hope you got some value off of it. I’ll see you around.