[0001] AmberAmethystDaisy -> QuartzBegonia -> LummaStealer By 0x1c Published: 2024-06-21 · Archived: 2026-04-05 16:25:16 UTC Disclaimer: I have personally noticed a significant difficulty in finding names for many loaders, even if they have been reported on due to the overwhelming focus on the final payload within infection chains. With this in mind, I utilize a custom loader taxonomy system, with the name of the loader in open-source reporting as a secondary identifier. More information on this taxonomy system can be found here. If you happen to know the name of a loader that I report on, please let me know! Recently, I stumbled across a video on YouTube from "The PC Security Channel", which noted that there was malware being distributed through fake cracked software on GitHub. Unfortunately, the extent of the analysis performed within the video was to check VirusTotal in order to see if the file is malicious or not. Video: How not to Pirate: Malware in cracks on Github (youtube.com) Although this might be good enough for most, my disappointment is immeasurable, and my day is nearly ruined. However, we can do the digging ourselves and get to the bottom of this! Although the original GitHub repo that was shown within the video is now taken down, the actual download URL for the first stage seems to be hosted on another repo, as seen in the hyperlink within the video: The URL seen in the hyperlink leads us to https[:]//github[.]com/ravindrauppalapati/RoleManager/releases/tag/Client , which is still up and available for download! https://www.0x1c.zip/0001-lummastealer/ Page 1 of 22 Stage 1 - QuartzDahlia Also known as: Launch4j TL;DR: Initial sample can be executed as a normal executable as well as a JAR SHA-256 Filename 8ed6a84101dfcafeac6ddbf5020312b0094576fd3f9106f7df460e1b8a7bd5e1 Win.Installer.x64.zip 94edf5396599aaa9fca9c1a6ca5d706c130ff1105f7bd1acff83aff8ad513164 Win Installer x64.exe Unpacking the ZIP archive, we can observe the following file structure: │ Win Installer x64.exe │ └───v2024 ├───bin │ │ awt.dll │ │ glass.dll │ │ java.dll │ │ javafx_font.dll │ │ javafx_iio.dll │ │ javaw.exe │ │ msvcp120.dll │ │ msvcr100.dll │ │ msvcr120.dll │ │ net.dll │ │ nio.dll │ │ prism_d3d.dll │ │ sunec.dll │ │ sunmscapi.dll │ │ verify.dll │ │ zip.dll │ │ │ └───client │ jvm.dll │ └───lib │ jce.jar │ jfr.jar │ jsse.jar │ resources.jar │ rt.jar │ https://www.0x1c.zip/0001-lummastealer/ Page 2 of 22 └───ext jfxrt.jar sunec.jar sunjce_provider.jar sunmscapi.jar Taking a look at the executable, it's unclear at first as to where the malicious code lies. With this in mind, I decided to load it up in x64dbg to do some quick preliminary dynamic analysis. Stepping through a few functions, I was able to see that the malware attempts to calls its own binary with the - jar flag using its bundled Java runtime. It turns out that this actually a tool named Launch4j which allows for Java applications to be wrapped in an executable. Since JAR files are able to be unzipped, we can go ahead and extract the contents of this executable with 7-Zip. Note: Detect-It-Easy also notifies us that this executable contains a ZIP archive, and we could have gone about it that way as well! https://www.0x1c.zip/0001-lummastealer/ Page 3 of 22 Stage 2 - AmberAmethystDaisy Also known as: D3F@ck Loader, NestoLoader SHA-256 Filename 515d025ba2aa1096f65c13569de283b83d86824d08ca48c1fc3bc407d4cf3266 MainForm.phb https://www.0x1c.zip/0001-lummastealer/ Page 4 of 22 TL;DR: Extracted contents of the JAR contains files with the .phb extension, indicative of JPHP The entry point for JPHP-based applications can be found within .system/application.conf In this case, the entry point resides in app/forms/MainForm.phb Utilizing Binary Refinery and jadx, the next stage payload URL is retrieved. A few of the extracted files have the .phb extension, which is indicative of JPHP, an implementation of PHP on the Java VM. For more information on triaging JPHP malware, this same malware family was recently showcased on a MalwareAnalysisForHedgehogs video. The entry point for JPHP-based applications can be found within .system/application.conf . The content of this file is as follows: # MAIN CONFIGURATION app.name = DarkLauncher app.uuid = 6ccf8f8e-fb00-441b-a0f5-f3bc2fa6619b app.version = 1 # APP app.namespace = app app.mainForm = MainForm app.showMainForm = 1 app.fx.splash.autoHide = 0 We now know that the entry point that we are interested in would be located within the app folder and should be called MainForm . Let's go and take a look! Sure enough, a file titled MainForm.phb exists in the forms folder located within app . Viewing this file with a hex editor, we can very quickly see what looks to be parts of an embedded configuration. Now we can be fairly sure that this is the file we want to be looking further into. https://www.0x1c.zip/0001-lummastealer/ Page 5 of 22 Although we see a C2 IP address of 194.147.35[.]251 here, this is seemingly not where the next payload is hosted. Let's dig deeper to figure out where the next payload is actually hosted. Dealing with PHB files PHB files contain Java class files within them, which are denoted with a magic of CAFEBABE . We can utilize these magic bytes as a marker in order to extract the embedded .class files. I set up the following Binary Refinery pipeline to extract the 2 class files from app/forms/MainForm.phb : ef MainForm.phb | resplit h:CAFEBABE [ \ | pop \ | ccp h:CAFEBABE \ | dump extracted_class_{index}.class \ ] Unit Name Definition ef Emit File Places a file into the pipeline resplit Regular Expression Split Splits the data in the pipeline by the supplied regular expression pop Pop Removes a chunk from the frame (and stores it in a meta variable) - Used here to remove the first chunk in the pipeline, which contains data before the first CAFEBABE header ccp ConCat Prepend Concatenates a value to the beginning of each chunk dump Dump Dumps the data stored in each chunk to disk Using jadx, we can decompile the recovered Java class files in order to get a better idea as to what the malicious code does. https://www.0x1c.zip/0001-lummastealer/ Page 6 of 22 Looking through the code, we come across 2 base64 encoded strings which decode to URLs. We can set up the following Binary Refinery pipeline to extract, defang, and print these indicators: ef MainForm.phb | carve b64 [ \ | b64 \ | xtp url \ | defang \ | cfmt "{}\n" \ ] https[:]//pastebin[.]com/raw/md5jVrEB https[:]//t[.]me/+JBdY0q1mUogwZWMy Unit Name Definition ef Emit File Places a file into the pipeline carve Carve Extracts pieces of the pipeline that matches a given format - in this case, base64 b64 Base64 Base64 decodes each chunk in the pipeline xtp eXtracT Pattern Extracts indicators from the data within the pipeline by a given pattern defang Defang Defangs indicators within the pipeline cfmt Convert to ForMaT Transforms each chunk in the pipeline by applying a string format operation The Pastebin URL holds a paste that contains the IP address 78.47.105[.]28 , which is where the next payload is hosted. We can now reconstruct the true URL of the next-stage payload: https://www.0x1c.zip/0001-lummastealer/ Page 7 of 22 http[:]//78.47.105[.]28/auto/b0573cef5fbfef5a15e8a6527080ad25/93.exe Stage 3 - QuartzBegonia Also known as: N/A SHA-256 Filename 5b751d8100bbc6e4c106b4ef38f664fb031c86f919c3e2db59a36c70c57f54e0 93.exe The third-stage payload in this infection chain is a loader written in C++. Loading the sample in Binary Ninja quickly reveals a large amount of non-code data, which is very likely the encrypted payload. Within the main function, we can see that a thread would be created, which would execute a function which I've named thread_start_addr ( 0x401750 ) with an argument - a pointer to a function I've named mal::thread_main ( 0x41d7b0 ). When called, the function thread_start_addr executes the function at the address that was passed-in as an argument: Diving into the mal::thread_main function, we come across an encrypted buffer and its corresponding decryption loop: https://www.0x1c.zip/0001-lummastealer/ Page 8 of 22 Re-implementing this decryption loop in Python, we can recover the content of the encrypted buffer: dec_buf = bytearray() for b in enc_buf: first_dec = (b ^ 0x73) - 0x15 second_dec = ((((((((first_dec - 0x57) ^ 0x74) + 0x4e) ^ 0x70) - 0x65) ^ 0x22) - 0x73) ^ 0x2a) % 256 dec_buf.append(second_dec) >> dec_buf bytearray(b'U\x05\x00\x007\x13\x00\x00\x00\x00\x00\x00user32.dll\x00CreateProcessA\x00VirtualAlloc\x00GetThreadC However, this is very ugly, so I created a colorful and pretty template for the decrypted data within 010Editor in order to make better sense of it visually. Now we can see that the data is mostly a few function names and a shellcode buffer used in order to inject the final payload into RegAsm.exe . https://www.0x1c.zip/0001-lummastealer/ Page 9 of 22 DiamondDaffodil shellcode seen in buffer decrypted within QuartzBegonia One thing that I tend to do when triaging loaders is to find the beginning of what is likely the encrypted content of the payload in order to find functions that cross-reference these buffers. I was able to locate a very large buffer ( 0x46600 bytes long) at 0x428038 , as well as a smaller buffer ( 0x31 bytes long) at 0x428000 . A function located at 0x41d4d0 references both of these buffers and taking a look at the function—my suspicions of these buffers being the next-stage payload and its corresponding decryption key were confirmed. https://www.0x1c.zip/0001-lummastealer/ Page 10 of 22 https://www.0x1c.zip/0001-lummastealer/ Page 11 of 22 Key and encrypted content of the final payload, located within the .data segment Taking a look at the function located at 0x41d4d0 , we can see telltale signs of the RC4 encryption algorithm: Tip: Seeing two loops and the number 256 (0x100) is often indicative of the RC4 encryption algorithm With this information, I set up a Binary Refinery pipeline to decrypt the final payload: https://www.0x1c.zip/0001-lummastealer/ Page 12 of 22 ef 93.exe | \ vsnip 0x428038:0x46600 | \ rc4 h:22a43b87df1edee294decd10f5e85c468fccf9fdda2e48841717965abedcd61ce4dbe9f3e0c9ca66fcea73762a5b0e5c5 dump stage4.bin Unit Name Definition ef Emit File Places a file into the pipeline vsnip Virtual Snip Snips (extracts) data from PE/ELF/MACHO files based on virtual offset rc4 RC4 RC4 decrypts the data in the pipeline, given a key dump Dump Dumps the data stored in each chunk to disk Stage 4 - LummaStealer Also known as: LummaC2 Stealer SHA-256 Filename 0cf55c7e1a19a0631b0248fb0e699bbec1d321240208f2862e37f6c9e75894e7 N/A Loading the LummaStealer sample in Binary Ninja, we see the following function: Taking a look at the function sub_432130 , we immediately come across a problem: Opaque Predicates Here, we have an example of an obfuscation technique called Opaque Predicates. The jumps to the next section of code are obfuscated by making their destination the result of a mathematical operation. Typically, we would deal with these via patching, which is possible (this is not at the same place in code, but is an example of this technique): https://www.0x1c.zip/0001-lummastealer/ Page 13 of 22 However, I was recently informed by Xusheng from the Binary Ninja / Vector35 team (huge shoutouts to the team!) of a better way to tackle this: By default, Binary Ninja believes that the value defined at data_440fe8 and data_440fec can be modified by the program. Although this may be true, we know that this is likely not the case. With this in mind, if we convert the types—which are by default void* —to const int32_t , Binary Ninja can do its magic (dataflow analysis) in order to solve the opaque predicate for us! https://www.0x1c.zip/0001-lummastealer/ Page 14 of 22 Just like that, we can save our precious reverse-engineering time (and sanity...)! I originally was manually patching a whole bunch of these, and let me tell you—it was miserable. However, going through the code a little more, we hit yet another roadblock: In this case, the value data_440ffc holds the address of 2 possible values used in order to calculate the destination. If we take a look at data_440ffc , right now, it is only showing up as a void* : Let's go ahead and change this to a const int32_t[2] in order to correctly reflect its type. https://www.0x1c.zip/0001-lummastealer/ Page 15 of 22 Now, if we change the type of data_441004 to const int32_t , we can now see that the variable named data_440ffc has automatically been changed to jump_table_440ffc : Going back to our function, we now see that the dataflow analysis has taken care of the opaque predicate! (and left two more of them in its wake...) We'll have to go and do this a whole bunch of times, but it is still much better than calculating the location of the jump and patching it all manually (by a long shot). After patching up the functions called by the main method, we have a much cleaner look at the binary. Let's move our focus over to the function located at 0x409f50 . API Hash Resolution https://www.0x1c.zip/0001-lummastealer/ Page 16 of 22 Here, we come across a case of API Hash Resolution. The function sub_434a60 is used to take a module ( data_4431bc , which is a pointer to the base address of WinHttp.dll ) and a corresponding hash in order to resolve a function for further use. I won't showcase sub_434a60 here, as it goes out of scope for this post—but this function essentially goes through the exports of WinHttp.dll , hashes all the function names, and returns a pointer to the function matching the provided hash. I was able to deduce that this copy of LummaStealer is utilizing a hashing algorithm, namely FNV-1a with a modified offset. I went ahead and added this modified hashing algorithm to the hashdb project. Now that the modified hashing algorithm has been deployed within hashdb, we can go ahead and simply utilize the hashdb plugin within Binary Ninja to find the names of the APIs used: https://www.0x1c.zip/0001-lummastealer/ Page 17 of 22 Decrypting C2 Addresses Now that we have both the opaque predicates and API hash resolution out of the way, let's try to find the C2 addresses for LummaStealer. Within the function that resolves the WinHttp functions, we see a variable being assigned to a list of pointers. If we investigate this further, we see that the list of pointers contains what looks to be base64 encoded strings. However, if we try to base64 decode the strings, we do not end up with readable text. Let's dig deeper to see how these strings are decrypted! Encrypted LummaStealer C2 addresses In this case, it seems that each string is passed in as the first argument to a function at 0x00409cb0 . Let's take a further look at that function: https://www.0x1c.zip/0001-lummastealer/ Page 18 of 22 At the beginning, we see that the length of the current encrypted C2 address is being calculated, alongside a call to a function at 0x00409e10 which calculates the length of the blob, if you were to base64 decode it. This is followed by a function that actually base64 decodes the data. Continuing through the function, we see the following code: This code takes the first 32 (0x20) bytes of the decoded blob as a key and XORs the rest of the data with it. The resulting output is a C2 address for LummaStealer! With this in mind, I set up the following Binary Refinery pipeline in order to decrypt the LummaStealer C2 addresses: ef stage4.bin \ | vsnip 0x438df8:0x451 \ | carve b64 -n 5 [ \ | b64 \ | push [ \ | snip :32 \ | pop key \ ] \ | snip 32: \ | xor var:key \ | defang \ | cfmt "{}\n" \ ] associationokeo[.]shop turkeyunlikelyofw[.]shop pooreveningfuseor[.]pw edurestunningcrackyow[.]fun detectordiscusser[.]shop relevantvoicelesskw[.]shop https://www.0x1c.zip/0001-lummastealer/ Page 19 of 22 colorfulequalugliess[.]shop wisemassiveharmonious[.]shop sailsystemeyeusjw[.]shop Unit Name Definition ef Emit File Places a file into the pipeline vsnip Virtual Snip Snips (extracts) data from PE/ELF/MACHO files based on virtual offset carve Carve Extracts pieces of the pipeline that matches a given format—in this case, base64 with a minimum length of 5 characters b64 Base64 Base64 decodes each chunk in the pipeline push Push Temporarily sets aside the current chunk of data and replaces it with a new chunk. This is useful if you want to perform operations on a piece of data while keeping the original data intact for later use. Think of this as a way to create a copy of the data in order to do some work on the data, before restoring the original data. snip Snip On the copy of the data, retrieves (snips) the first 32 bytes, which is the XOR key pop Pop Places the modified copy of the data into a meta-variable. Meta-variables can be later utilized with the var keyword snip Snip On the original data, retrieves (snips) everything after the first 32 bytes, which is the encrypted C2 address xor XOR Performs an exclusive-or operation on the data within the chunk with the popped key defang Defang Defangs indicators within the pipeline cfmt Convert to ForMaT Transforms each chunk in the pipeline by applying a string format operation And now, we can happily say that we actually know what this infection chain is, how it works, and we've successfully retrieved the final payload and its C2 addresses. Thanks for reading! 💖 Indicators of Compromise: IoC Description https[:]//github[.]com/ravindrauppalapati/RoleManager/releases/tag/Client Sample Download URL https://www.0x1c.zip/0001-lummastealer/ Page 20 of 22 IoC Description 8ed6a84101dfcafeac6ddbf5020312b0094576fd3f9106f7df460e1b8a7bd5e1 Sample ZIP 94edf5396599aaa9fca9c1a6ca5d706c130ff1105f7bd1acff83aff8ad513164 QuartzDahlia EXE 515d025ba2aa1096f65c13569de283b83d86824d08ca48c1fc3bc407d4cf3266 AmberAmethystDaisy PHB 194.147.35[.]251 AmberAmethystDaisy Event Server https[:]//pastebin[.]com/raw/md5jVrEB AmberAmethystDaisy Dead-Drop https[:]//t[.]me/+JBdY0q1mUogwZWMy AmberAmethystDaisy Telegram http[:]//78.47.105[.]28/auto/b0573cef5fbfef5a15e8a6527080ad25/93.exe QuartzBegonia Download URL 5b751d8100bbc6e4c106b4ef38f664fb031c86f919c3e2db59a36c70c57f54e0 QuartzBegonia EXE 0cf55c7e1a19a0631b0248fb0e699bbec1d321240208f2862e37f6c9e75894e7 DiamondDaffodil Shellcode d6a40534d8a76509605e67ead55ef3506050c7df86701db13443d091c7a4bce2 LummaStealer EXE associationokeo[.]shop LummaStealer C2 turkeyunlikelyofw[.]shop LummaStealer C2 pooreveningfuseor[.]pw LummaStealer C2 edurestunningcrackyow[.]fun LummaStealer C2 detectordiscusser[.]shop LummaStealer C2 relevantvoicelesskw[.]shop LummaStealer C2 colorfulequalugliess[.]shop LummaStealer C2 wisemassiveharmonious[.]shop LummaStealer C2 sailsystemeyeusjw[.]shop LummaStealer C2 P.S - Huge thanks to my friend donaldduck8 for proofreading this post, be sure to check out his blog at https://sinkhole.dev https://www.0x1c.zip/0001-lummastealer/ Page 21 of 22 Source: https://www.0x1c.zip/0001-lummastealer/ https://www.0x1c.zip/0001-lummastealer/ Page 22 of 22