Skip to main content
  1. Blog/

Zero2Automated Biweekly Challenge: Gozi Banking Trojan

3 mins

Introduction #

I’m having a blast with the Zero2Automated course so far, the depth of the content is great. At the end of this course, there are four challenges that one can choose to analyze further to really deepen their understanding of reversing and automation in particular. This means that these challenges require you to analyze the binary and the string decryption routines, and to write a script to automatically extract / decrypt the data by reproducing the algorithms in Python for instance. In this post I go over how I analyzed the Gozi ISFB binary to uncover the string decryption routines and the script I wrote to decrypt the encrypted data in the binary.

The Gozi ISFB sample #

File namef28f39ada498d66c378fd59227e0f215.dll
SHA2560a66e8376fc6d9283e500c6e774dc0a109656fd457a0ce7dbf40419bc8d50936
File size950'784 bytes
Mime typeapplication/x-dosexec
PackedYes

Static Analysis #

My go-to program for static analysis is PEStudio. However, upon loading the binary, PEStudio directly exits without showing any useful information. From this and some trials to analyse the binary in IDA/Ghidra, it became clear that the binary is likely packed. To unpack it, we load the sample into x64dbg and set breakpoints at VirtualAlloc and VirtualProtect to see if any executables are written to memory or that any regions of memory are set to executable state (R/W/X). After a couple VirtualAlloc calls, we hit the VirtualProtect call. Inspecting the region that was allocated shows what seems to be a valid executable:

After dumping and inspecting the file once more in PEStudio / PEBear, we observe that we need to fix the binary and rebase it. Loading it in PEStudio once more we see a lot more interesting stuff, such as calls to QueueUserAPC (suggesting APC injection) and a lot of obfuscated strings.

Additionally, we observe several sections with high entropy, in particular, the .bss section stands out, suggesting it might contain encrypted data:

Using Ghidra, we observe several interesting functions. Function FUN_00401308 stands out as it appears to decrypt the .bss section before performing APC injection.

After some more analysis, we observe what seems to be a blockwise decryption routine that iterates over blocks in the .bss section:

With the inner decryption function:

Tracing the calls before hitting this function in x64dbg reveals that the key is made up of a particular date value (most likely the date of the campaign initiation):

The key is constructed by adding together the first and second 4 bytes of the campaign date, plus the VirtualAddress of the .bss section, plus a random number between 0-20, which is always 18 in our case. Using this key, the data is decrypted block by block:

String Decryption Code #

The observed decryption routines are turned into the following code:

import pefile
import binascii
import struct

path = "gozi_014D0000_unmapped_rebased.dll"
date = b"Apr 26 2022"

file = open(path, "rb").read()

def get_section(section, path):
    pe =  pefile.PE(path)
    for s in pe.sections: 
        if bytes(section, 'utf-8') in s.Name:
            data = file[s.PointerToRawData:s.PointerToRawData+s.SizeOfRawData]
            return data, s.VirtualAddress

def decrypt(data, key):
    counter = 0
    decoded = b""
    for i in range(0, len(data), 4):
        encoded = struct.unpack("I", data[i:i+4])[0]
        if encoded: 
            decoded += struct.pack("I", (counter - key + encoded) & 0xFFFFFFFF) 
            counter = encoded
        else:
            break
    return decoded


section_data, section_va = get_section(".bss", path)
key = struct.unpack("<I", date[0:4])[0] + struct.unpack("<I", date[4:8])[0] + section_va + 18
decrypted = decrypt(section_data, key)
print(decrypted)

It’s always satisfying to see what’s in the encrypted data, going from this:

To this:

Conclusion #

It’s always satisfying to see some legible stuff appear after grinding to get your script to work. Onto the next challenge!