generated from onwidget/astrowind
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
style(post/elf64-rat-malware): rephrase few points and fix formatting
Signed-off-by: Siddhesh Mhadnak <[email protected]>
- Loading branch information
Showing
1 changed file
with
106 additions
and
92 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,11 +8,13 @@ tags: | |
- malware | ||
- threat intelligence | ||
--- | ||
|
||
## Introduction | ||
|
||
On 19 February, Vipyr Security scanning services notified us of a malicious upload to the Python Package Index 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. | ||
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:** | ||
|
||
|
@@ -33,13 +35,15 @@ remote code execution, remote file upload and download, and a beaconing service | |
|
||
### Staging | ||
|
||
The malicious payload is placed in `os.py` files within typos of popular packages. During the initialization of the package, an import is | ||
made from the `os.py` file, executing the payload. Payload occurs in a string of multiple base64 or hex encoding, although base64 was | ||
only observed in `[email protected]`. 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. | ||
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 `[email protected]`. 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) | ||
|
||
![image](/images/elf64-blog/fig-1-hex-string.png) | ||
*Hex encoded stage 1 payload.* | ||
_Hex-encoded stage 1 payload_ | ||
|
||
```python | ||
platform = sys.platform[0:1] | ||
|
@@ -59,30 +63,33 @@ if platform != "w": | |
sleep(0) | ||
``` | ||
|
||
*Stage 1 payload after decoding.* | ||
_Stage 1 payload after decoding_ | ||
|
||
The payload is downloaded from the `pypi[.]online` or `arcashop[.]org` domain. Curl is invoked with `os.system` and sets the cookie | ||
`oshelper_session` to `10237477354732022837433`. Interestingly, the malware seems to only target Linux systems. If the platform is | ||
set to windows, it will not execute. | ||
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. | ||
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=` | ||
|
||
Despite our efforts this payload was resistant to many our attempts to download it, even when accessing from mobile, residential, cloud, and | ||
business/education IP addresses. We're still unsure how we got a payload to fall out and it seemed to happen by chance. | ||
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 targetting 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! | ||
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. | ||
|
||
- **XEncoding:** An XOR encryption and decryption function with a custom key. | ||
- **AcceptRequest:** Retreives 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 analysis the following headers were discovered: | ||
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 | ||
|
@@ -97,70 +104,71 @@ With these headers, the data is sent in the following format: | |
lkjyhnmiop=%s&odldjshrn=%s&ikdiwoep=%s | ||
``` | ||
|
||
If the request in unsuccessful it will print the error to `/tmp/xweb_log.md`: | ||
If the request is unsuccessful, it will log the error to `/tmp/xweb_log.md`: | ||
|
||
![image](/images/elf64-blog/fig-2-xweb-log.png) | ||
![Errors logged in xweb_log.md](/images/elf64-blog/fig-2-xweb-log.png) | ||
|
||
![image](/images/elf64-blog/fig-3-xweb-source.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 and retrieve the output from them. | ||
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 C2 and waits 4 hours before polling the C2 again. | ||
- **Ping1** (`0x892`): Send a 'Success' response to the C2 and wait 4 hours before polling the C2 again | ||
|
||
![image](/images/elf64-blog/fig-4-ping-sleep.png) | ||
![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 C2 and polls for another command instantly. | ||
- **Ping2** (`0x895`): Send a 'Success' response to the C2 and poll for another command instantly | ||
|
||
![image](/images/elf64-blog/fig-5-ping-immediate.png) | ||
![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 | ||
- **MsgDown** (`0x893`): Upload files | ||
|
||
![image](/images/elf64-blog/fig-6-msg-down.png) | ||
![Code uploading files](/images/elf64-blog/fig-6-msg-down.png) | ||
|
||
- **MsgUp**: `0x894` Download Files | ||
- **MsgUp** (`0x894`): Download files | ||
|
||
![image](/images/elf64-blog/fig-7-msg-up.png) | ||
![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 C2. | ||
- **MsgCmd** (`0x898`): Run command with commandline `%s 2>&1 &` and send results back to the C2 | ||
|
||
![image](/images/elf64-blog/fig-8-msg-cmd.png) | ||
![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 C2. | ||
- **MsgRun** (`0x897`): Run command with commandline `%s 2>&1 &` and do not send results to the C2 | ||
|
||
![image](/images/elf64-blog/fig-9-msg-run.png) | ||
![Code running command and not sending results to the C2](/images/elf64-blog/fig-9-msg-run.png) | ||
|
||
Simple analysis on the protocol used to communicate to the C2 server reveals it uses `libcurl` to perform http requests. | ||
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 | ||
- `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 pcap we took while analysing the malware. | ||
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. | ||
|
||
![image](/images/elf64-blog/fig-10-beacon-pcap.png) | ||
![Packet capture showing traffic with beacon](/images/elf64-blog/fig-10-beacon-pcap.png) | ||
|
||
### C2 Activity Analysis | ||
|
||
In order to further analyze the intentions of the threat actors we decided to log commands from the command and control server. | ||
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. | ||
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. | ||
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. | ||
|
||
![image](/images/elf64-blog/fig-11-payload-receipt.png) | ||
![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 the 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`. | ||
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`. | ||
|
||
![image](/images/elf64-blog/fig-12-payload-patch.png) | ||
![Disassembly of the code receiving payload from the C2](/images/elf64-blog/fig-12-payload-patch.png) | ||
|
||
To accomplish this, we wrote the following gdb script and ran it with `gdb ./local_file --command=script.gdb`. | ||
To do this, we wrote the following `gdb` script and ran it with `gdb ./local_file --command=script.gdb`. | ||
|
||
```bash | ||
break *SendPayload | ||
|
@@ -178,25 +186,26 @@ 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. | ||
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 realtime. | ||
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 setup Burpsuite as a proxy to capture the underlying http requests from | ||
the agent. The Burpsuite 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 Burpsuite 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. | ||
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. | ||
|
||
![image](/images/elf64-blog/fig-14-burp-request-monitoring.png) | ||
![Screenshot of Burp Suite monitoring requests with the C2](/images/elf64-blog/fig-14-burp-request-monitoring.png) | ||
|
||
After watching some of the traffic we gathered a general overview of the c2 protocol: | ||
After watching the traffic for some time, we gathered a general overview of the C2 protocol: | ||
|
||
```bash | ||
```plaintext | ||
# Initial connection | ||
Agent -> C2: lkjyhnmiop=<ID>&odldjshrn=odlsjdfhw&ikdiwoep=<something?> (hello im alive) | ||
C2 -> Agent: OK (success) | ||
|
@@ -206,36 +215,41 @@ C2 -> Agent: <base64 encoded command> | |
Agent -> C2: lkjyhnmiop=1059787080&odldjshrn=content&ikdiwoep=<base64 encoded command response> | ||
``` | ||
|
||
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. | ||
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. | ||
|
||
![image](/images/elf64-blog/fig-15-request-response.png) | ||
![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 as 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 are able to simulate | ||
a fake client to pull commands from the C2 server. This allows us to log commands, including their payloads to a text file for later review. | ||
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 https://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 https://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 | ||
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 Python Package Index 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. | ||
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 | ||
|
||
|
@@ -260,7 +274,7 @@ our reports, and Vipyr Security community contributors for the reversal and anal | |
}, | ||
{ | ||
"type": "domain", | ||
"name": "pypi[.]online", | ||
"name": "pypi[.]online" | ||
}, | ||
{ | ||
"type": "domain", | ||
|