TheAbsnt

Challenge #1: Gozi String Decryption

· THEABSNT

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 like VirtualAlloc for memory allocation, VirtualProtect for memory protection changes, CreateProcessInternalWand CreateProcessA in case of process creation,
  • now executing the sample we encounter three consecutive VirtualAlloc call, where first address when filled looks gibberish, second address (in Dump 2) reveals a MZ header but looks kinda compressed and third address (in Dump 3) is also filled with MZ header but with a .bss section and looks clean than prior, followed by a VirtualProtect call on itself snap_mem_dump_2_3
  • assuming Dump 3 gonna be the next payload, we dump the memory to new file and unmap it using pe_unmapper or manually using PEbear, 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,

fn_dllEntryPoint fn_sub_10002009

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 apc_injection

sub_10001B7F: the function invoked

fn)sub_10001B7F 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, fn)sub_1000308

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 fn_sub_10001922

then next function called is sub_10001FD8(mem_allocated from VirtualAlloc, pointer to bss section, key, 0x400) fn_sub_1000197C 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: script_output


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