Malwarology Research

Share this post

Malicious Packer pkr_ce1a

malwarology.substack.com

Malicious Packer pkr_ce1a

First Stage

Nov 19, 2022
Share this post

Malicious Packer pkr_ce1a

malwarology.substack.com

Summary

This packer has been observed delivering a wide variety of malware families including SmokeLoader and Vidar among many others. It has been observed in the wild going back a number of years potentially to 2017. The particular variant of the packer analyzed here contains two sets of bytes with no apparent use which occur on either side of values that are integral to the decoding and unpacking process. These two byte strings are stable across many months of observed samples of this packer. What follows is a detailed analysis of the first stage of one sample of this packer which delivers a SmokeLoader payload. Until a widely recognized name or identifier can be determined for this packer, it has the designation pkr_ce1a.

Identification

This sample has two known filenames. The first, 6523.exe, is observed in the wild in the path component of the URL used to distribute the file.

1
The second, povgwaoci.iwe, is located in the RT_VERSION resource within the VS_VERSIONINFO structure in a field named InternationalName. This field along with others in the same StringTable structure are not parsed by Exiftool or Cerbero Suite. This indicates that the structure is malformed or non-standard.

Malwarology Research is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.

According to AV detection results, the correct identification of the unpacked file, SmokeLoader, does appear. There is also one detection based on dynamic analysis: Zenpack.

The import hash of this sample is shared by a group of other files.

2
However, the large majority of imported functions are called in dead code located after opaque predicates. Therefore, the usefulness of this import hash is almost nothing. Closer analysis of the opaque predicates in this sample can be found below.

Behavioral and Code Analysis Findings

Build Analysis

According to the File Header, the timestamp of compilation is 2022-02-16T10:14:32Z. The linker version found in the PE32 Optional Header is 9.0.

Linker Version 9.0

The compiler is identified as Visual Studio 2008 Release according to function signatures in Ghidra that match a number of library functions in the sample. One example of this detection for the ___security_init_cookie function is shown in the figure below.

Compiler Detection: Visual Studio 2008 Release

The majority of the library functions found in the sample are from Microsoft Visual C++ 9.0.21022. This is identified in the sample’s rich signature.

Rich Signature: VC++ 9.0.21022

Main Function

Main Function

The instructions highlighted in yellow in the figure above are examples of junk code insertion. These are dummy instructions that are placed between relevant instructions with the goal of making signature development more difficult. The instructions at the end of the function highlighted in white are not executed. These highlight colorings are used throughout the screenshots of this analysis.

The first instructions in the main function calculate the size of the encoded data that contains the shellcode which is the next stage of the packer. The first part is read from a constant in the .data section at address 0x41cc3c. The second part is hardcoded in the main function at address 0x406573. The size is calculated in the next instruction by adding the two parts together. The resulting size is written to a variable in the .data section. Highlighted in the figure below are the bytes before and after the first constant. These two sets of bytes are not read during the execution of the packer, and their purpose is unknown. However, they are stable across builds of this packer going back for months at least.

Stable Surrounding Bytes

The address where the encoded data is located goes through a similar process. The first part is read from the .data section then written to a variable in the same section. The addition to the second part does not occur until later in the unpack_shellcode function. The bytes surrounding this part are shown in the figure below.

Stable Surrounding Bytes

The next set of instructions handles loading kernel32.dll and resolving the address of LocalAlloc.

3
These instructions taken together are unique to this packer and shared across hundreds of variants. The stray instruction at address 0x406588 is part of the previous logical grouping of instructions. This is an example of interleaving code that is meant to make signature development more difficult.

Resolve LocalAlloc

The call to LocalAlloc is obfuscated by calling it from the eax register. This is a type of function call obfuscation. The uBytes parameter to the function is 63072 bytes which is the result of the calculation described above.

Obfuscated Call to LocalAlloc

The final instructions in the main function are calls to other malicious functions and finally a call to the entry point of the decoded shellcode. The next stage of the packer starts after that call.

Change Protection Function

Change Protection Function

This function is a wrapper around an obfuscated call to VirtualProtect. Starting at address 0x4058e3 and continuing until the call to GetProcAddress, the name of the function VirtualProtect is written character-by-character, out of order, to a variable in the .data section. Building the function name in this way is an example of variable recomposition. The address of this string is then used as the lpProcName parameter to GetProcAddress. Finally, a call is made to VirtualProtect to change the protection from PAGE_READWRITE (0x4) to PAGE_EXECUTE_READWRITE (0x40) thus enabling execution in the newly allocated memory.

The flNewProtect parameter to the VirtualProtect function is also obfuscated by adding together 0x20 and 0x20. This hides the PAGE_EXECUTE_READWRITE flag of 0x40 from being located near the call to VirtualProtect. This is a form of argument obfuscation.

Obfuscated New Protect Value

Unpack Shellcode Function

This function performs three actions that are interspersed with anti-analysis code. The first action is moving the encoded data from its starting location in the .data section to the newly allocated memory.

Move Encoded Data

Note the instructions at addresses 0x405aa7 and 0x405acd. These are both opaque predicates. An opaque predicate appears to be a conditional jump, but the conditions can only be met in one way making the jump effectively unconditional. The one at the top is basically a fake: it never jumps. The one in the middle always jumps. This one additionally encloses a block of dead code. This packer very frequently couples dead code insertion with opaque predicates that always jump over the dead code. The function calls in the dead code are included in the import table making identification via import hash of little utility.

Later on in this function is the call to the decode function. After that is a call to a function which shifts the pointer to the decoded shellcode. The shift changes the offset from the start of this data to the location of the shellcode original execution point (OEP). Because the shellcode is position independent, this OEP is also offset zero of the shellcode. Both of these functions are analyzed in more detail below.

The shift_shellcode_oep function is additionally wrapped in an anti-emulator loop which has a very high number of iterations. This slows processing in an emulator which can potentially cause a timeout and an analysis failure.

Decode and Shift Shellcode OEP Functions

Note the comparison instruction at address 0x406486. During the anti-emulation loop, the call to shift_shellcode_oep is made once when the counter reaches 0x770e. This is a type of anti-emulation circumvention countermeasure. A basic method for circumventing extremely long loops that target emulators is to patch out the loop. Another is to detect the loop in the emulator and then modify the counter to leave the loop. Because the shift function is called once at a point in the loop, either of these circumventions could end up not executing this function and would leave the emulator unable to execute the shellcode correctly.

The unpack_shellcode function overall contains ten opaque predicates primarily of the same type shown above. One of them is slightly different in that it creates a dead end filled with dead code. The last instruction in the dead end is a call to terminate. Therefore, this appears to be a location where the execution of the packer ends.

Opaque Predicate with Dead End

There are a total of four anti-emulation loops similar to the one analyzed above. Two of these wrap opaque predicates which in turn wrap inserted dead code. One, however, in addition to wrapping two opaque predicates, also contains two additional anti-emulator behaviors. Both of these are calls to unusual APIs: GetGeoInfoA and GetSystemDefaultLangID. During analysis, Qiling emulator halted with an exception because neither of these API calls have been implemented.

Anti-Emulation: Unusual API Calls

In the very first code block of the unpack_shellcode function, a new and subsequently unused exception handler is registered. Chances are about even that this is an anti-emulation behavior or it is just junk code. If it is anti-emulation, it is targeting older emulators based on specific versions of Unicorn Engine which do not implement access to the Windows Thread Information Block (TIB). Moving the contents of fs:0x0 as happens at address 0x405983 would fail in that particular environment. This type of anti-emulation is an unimplemented opcode.

Register New SEH

In the last block of the unpack_shellcode function, before the return, the SEH is reset back to the previous handler. This occurs right after a junk call to LoadLibraryW from which no functions are subsequently resolved.

Last Block of unpack_shellcode

Decode Function

Decode Function (Truncated)

The data is decoded in chunks. So this function has three basic purposes: convert the encoded shellcode size to a chunk count by dividing by 8, wrapping a loop around the call to the decode_chunk function, and then calling that function. In the middle of this function is a very large insertion of dead code wrapped by an opaque predicate. This is highlighted in white in the figure above. It has also been truncated for the screenshot. There are two locations in this function with junk code insertion which impedes signature development: at addresses 0x4057c1 and 0x4057cc.

The chunks are 8 bytes long, therefore at the end of the loop after the call to decode_chunk, 8 is added to the chunk pointer then the chunk count is decremented by one.

Chunk Decode Loop Control

Decode Chunk Function

Decompiled Decode Chunk Function

The decode_chunk function has four opaque predicates that each wrap dead code, and there are two locations with junk code insertion. In addition to these, the logic of the function is quite convoluted. However, after patching out all of the opaque predicates, dead code, and junk code, a cleaner decompilation graph can be analyzed. The figure above is that graph, fully annotated and cleaned up.

There are three tiny functions called during the decoding algorithm: load_data_keyB, add_key4, and, xor_mix. These simply isolate small pieces of the algorithm to impede analysis and make signature development more difficult.

Algorithm Isolates

Shift Shellcode OEP Function

Shift Shellcode OEP Function

The shift_shellcode_oep function is very simple. It adds 0x3bf1 to the address of the start of the decoded shellcode in allocated memory. This shifts the pointer from the start of the decoded data to the offset of the OEP. This is the address that is called in the main function.

Recommendations

This packer is polymorphic. Variants and builds share many of the same functionality, behavior, and code features. However, they are in different order with randomization of opaque predicates, junk code, and dead code. In spite of this, detection can be achieved by focusing on the two stretches of stable bytes which were identified above. The following two YARA rules match these bytes.

rule Packer_pkr_ce1a_ShellcodeSizePart
{
    meta:
        author = "Malwarology LLC"
        date = "2022-10-14"
        description = "Detects bytes surrounding the first part of the data size of the second stage shellcode in Packer pkr_ce1a."
        reference = "https://malwarology.substack.com/p/malicious-packer-pkr_ce1a"
        sharing = "TLP:CLEAR"
        exemplar = "fc04e80d343f5929aea4aac77fb12485c7b07b3a3d2fc383d68912c9ad0666da"
        address = "0x41cc3c"
        packer = "pkr_ce1a"
    strings:
        $a = { 00699AF974[4]96AACB4600 }
    condition:
        $a and
        uint16(0) == 0x5A4D and
        uint32(uint32(0x3C)) == 0x00004550
}
rule Packer_pkr_ce1a_ShellcodeAddrPart
{
    meta:
        author = "Malwarology LLC"
        date = "2022-10-17"
        description = "Detects bytes surrounding the first part of the address of the second stage shellcode in Packer pkr_ce1a."
        reference = "https://malwarology.substack.com/p/malicious-packer-pkr_ce1a"
        sharing = "TLP:CLEAR"
        exemplar = "fc04e80d343f5929aea4aac77fb12485c7b07b3a3d2fc383d68912c9ad0666da"
        address = "0x41bc9c"
        packer = "pkr_ce1a"
    strings:
        $a = { 0094488D6A[4]F2160B6800 }
    condition:
        $a and
        uint16(0) == 0x5A4D and
        uint32(uint32(0x3C)) == 0x00004550
}

Supporting Data and IOCs

File

  • Filename: 6523.exe

  • Filename: povgwaoci.iwe

  • MD5: 5663a767ac9d9b9efde3244125509cf3

  • SHA1: 84f383a3ddb9f073655e1f6383b9c1d015e26524

  • SHA25: fc04e80d343f5929aea4aac77fb12485c7b07b3a3d2fc383d68912c9ad0666da

  • Imphash: bc57832ec1fddf960b28fd6e06cc17ba

  • Timestamp: 2022-02-16T10:14:32Z

  • File Type: Win32 EXE

  • Magic: PE32 executable (GUI) Intel 80386, for MS Windows

  • Size: 238080

  • First Seen: 2022-10-14T18:37:13Z

    4

Distribution URL

  • hxxp[://]guluiiiimnstrannaer[.]net/dl/6523.exe

Malware Behavior Catalog

  • DEFENSE EVASION::Software Packing [F0001]

  • ANTI-BEHAVIORAL ANALYSIS::Emulator Evasion::Undocumented Opcodes [B0005.002]

  • ANTI-BEHAVIORAL ANALYSIS::Emulator Evasion::Unusual/Undocumented API Calls [B0005.003]

  • ANTI-BEHAVIORAL ANALYSIS::Emulator Evasion::Extra Loops/Time Locks [B0005.004]

  • ANTI-STATIC ANALYSIS::Disassembler Evasion::Argument Obfuscation [B0012.001]

  • ANTI-STATIC ANALYSIS::Disassembler Evasion::Variable Recomposition [B0012.004]

  • ANTI-STATIC ANALYSIS::Executable Code Obfuscation::Dead Code Insertion [B0032.003]

  • ANTI-STATIC ANALYSIS::Executable Code Obfuscation::Junk Code Insertion [B0032.007]

  • ANTI-STATIC ANALYSIS::Executable Code Obfuscation::Interleaving Code [B0032.014]

Malware Behavior Catalog Proposed Methods

  • ANTI-BEHAVIORAL ANALYSIS::Emulator Evasion::Unimplemented Opcodes [B0005]

  • ANTI-STATIC ANALYSIS::Executable Code Obfuscation::Opaque Predicate [B0032]

  • ANTI-STATIC ANALYSIS::Executable Code Obfuscation::Function Call Obfuscation [B0032]

Analyses

  • Intezer

  • UnpacMe

1

Sample downloaded from hxxp[://]guluiiiimnstrannaer[.]net/dl/6523.exe

2

Imphash: bc57832ec1fddf960b28fd6e06cc17ba

3

LocalAlloc function (winbase.h)

4

VirusTotal

Share this post

Malicious Packer pkr_ce1a

malwarology.substack.com
TopNew

No posts

Ready for more?

© 2023 Malwarology LLC
Privacy ∙ Terms ∙ Collection notice
Start WritingGet the app
Substack is the home for great writing