Challenge #1: Gozi String Decryption
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
x32dbg
to debug and apply breakpoints on widely used windows API for unpacking and stuff likeVirtualAlloc
for memory allocation,VirtualProtect
for memory protection changes,CreateProcessInternalW
andCreateProcessA
in case of process creation, - now executing the sample we encounter three consecutive
VirtualAlloc
call, where first address when filled looks gibberish, second address (inDump 2
) reveals aMZ
header but looks kinda compressed and third address (inDump 3
) is also filled withMZ
header but with a.bss
section and looks clean than prior, followed by aVirtualProtect
call on itself - assuming
Dump 3
gonna 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.dll
imports system query related function:NtQuerySystemInformation
- from
KERNEL32.dll
imports 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
bss
section - subtracts it with the key
- then add it to the previous
bss
dword - 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.