TONESHELL Rules

Hi everyone, first let’s see what examples of connections we have.

At the same time, we know for sure that this backdoor does not install a fake TLS-Handshake and immediately begins transmitting pseudo application-layer messages. We will use this for the condition in the rule.

I do not pretend that these rules will always work and we can see with what probability these rules will work. And in the end, I will propose a +new rule because there are obvious contradictions between the real protocol and the messages sent by the client in keepalive mode.

app.any.run/tasks/e00ac558-f3d6-4fea-8164-012233197afd/

app.any.run/tasks/4f37df15-548c-4804-8949-f999ce456684/

app.any.run/tasks/d9cfa1e3-9db6-4da9-ab66-6f4a1b622eea/

app.any.run/tasks/9c66816a-900a-4909-9d8b-e39cadbcee0e/

The most interesting thing that I focused on, in general, is how the first 256 bytes of the first packet are formed, and that’s where the xor key is actually located.

As you know, each byte has a low and high part, and let’s look at the fact that the low bytes (4 LSB, mask 0b00001111) in the columns formed by a line length of 16 are the same. We will use this as a condition.

For some samples, the key is formed so that the high part of the byte is sometimes an increasing counter in steps of one.

We could immediately use this feature (counter) of key formation, but the first sample and its key were formed somewhat differently. There, the condition for equality of low bytes was preserved, but the key was formed as a repetition of a subkey of length 64. Which ruins the idea of detecting the counter.

Since checking all columns for equality of low bytes is too expensive an operation, I took 2 bytes with the values 0x00 and 0x01. Found them by the content in the first packet (where the key was stored) and checked whether after 16 bytes there was the same low byte value as the one discovered via mask. That is, if I am looking for byte 0x00, then after 16 bytes I look if the low part is equal to zero.

content: "|00|"; offset: 5; depth: 256; 
byte_test: 1, =, 0, 15,  relative, bitmask 15; 
byte_test: 1, =, 0, 31,  relative, bitmask 15;

Now add a check that there was no Handshake before the packet with the key. Check the conditions so that the server don’t transmit anything to client (server, =, 1;) and client have already send a certain number of bytes in one packet

dsize: 271<>337; 
stream_size: server, =, 1;
stream_size: client, >, 272; 
stream_size: client, <, 337;

As a result, I have two rules for 0x00 and for a byte equal to 0x01 because a zero byte is not always found in the key and it can also be located somewhere at the end, which will not make it possible to check two more lines of 16 bytes each (byte_test: 1, =, 0, 31,).

Rules:

alert tcp any any -> any 443  (msg: "ET MALWARE [ANY.RUN] BACKDOOR [ANY.RUN] ToneShell FakeTLS Check-In (APT Mustang Panda / Earth Preta)";
flow: established, to_server; dsize: 271<>337;
stream_size: server, =, 1;
stream_size: client, >, 272;
stream_size: client, <, 337;
content: "|1703 0301|"; depth: 5;
byte_test: 1, >, 15, 0,  relative;
byte_test: 1, <, 49, 0,  relative;
content: "|00|"; offset: 5; depth: 256;
byte_test: 1, =, 0, 15,  relative, bitmask 15;
byte_test: 1, =, 0, 31,  relative, bitmask 15; 
classtype: command-and-control;
reference: md5,7e8f8043fe50cb6ce19b41a6e979a02f;
reference: url,app.any.run/tasks/6d271aa7-302c-4f3b-8cde-2fade4caa2f6;
metadata: attack_target Client_Endpoint, deployment Perimeter, former_category MALWARE, signature_severity Major, malware_family toneshel, tag apt, tag mustangpanda,  created_at 2024_01_27;
sid: 1; rev: 1;)
alert tcp any any -> any 443  (msg: "ET MALWARE [ANY.RUN] BACKDOOR [ANY.RUN] ToneShell FakeTLS Check-In (APT Mustang Panda / Earth Preta)";
flow: established, to_server; dsize: 271<>337;
stream_size: server, =, 1;
stream_size: client, >, 272;
stream_size: client, <, 337;
content: "|1703 0301|"; depth: 5;
byte_test: 1, >, 15, 0,  relative;
byte_test: 1, <, 49, 0,  relative;
content: "|01|"; offset: 5; depth: 256;
byte_test: 1, =, 1, 15,  relative, bitmask 15;
byte_test: 1, =, 1, 31,  relative, bitmask 15; 
classtype: command-and-control;
reference: md5,7e8f8043fe50cb6ce19b41a6e979a02f;
reference: url,app.any.run/tasks/6d271aa7-302c-4f3b-8cde-2fade4caa2f6;
metadata: attack_target Client_Endpoint, deployment Perimeter, former_category MALWARE, signature_severity Major,  malware_family toneshel, tag apt, tag mustangpanda,  created_at 2024_01_27;
sid: 2; rev: 1;)

Next rule:

The fake protocol after decryption has three layers:

  • The first is a system one that ensures the connection is maintained and the first registration packet is transmitted to the server.
  • The second level is the console, here the cmd process appears in the process span. This process executes console level commands.
  • The third layer is a built-in file manager protocol that I have not yet analyzed.

As you can see, in connection Keep-Alive mode, the server and client exchange very short packets of 7 bytes length (header + opcode). The packet containing the application layer data of the TLS protocol cannot be so short as it must contain additional information HMAC for version 1.2 and AEAD data for version 1.3.


This oddity can be used to detect fake connections.

For example, such a rule can be combined with the first two via flowbits, thus making fewer conditions in the first rule. Of course, a threshold will help here.

Here I am giving you complete freedom of action, thank you for your attention, I hope this detection will last for some time.

ps: And I would also like to apologize for the slight inaccuracy on Twitter, where I wrote the key length as 128, of course I meant 256.

2 Likes

Still, I’m writing rules for short packets because I present to your attention another version of tooneshell. Slightly modified by encryption, however, short-length packets are present there. And this is invalid behavior for the tls protocol. This is how I see the rules for detecting such anomalies.

In the first case, a previously encountered packet is considered, equal to 7 bytes, here is its stream and the rules for it.

alert tcp any 443 -> any any  (msg: "ET MALWARE [ANY.RUN] ToneShell FakeTLS Response (APT Mustang Panda / Earth Preta)";
flow: established, to_client;
dsize: 7;
stream_size: server, =, 8;
stream_size: client, <, 337;
content: "|1703 0300 02|"; depth: 7;
isdataat: !3, relative; 
threshold: type limit, track by_dst, seconds 120, count 1;
classtype: command-and-control;
reference: md5,7e8f8043fe50cb6ce19b41a6e979a02f;
reference: url,app.any.run/tasks/6d271aa7-302c-4f3b-8cde-2fade4caa2f6;
metadata: attack_target Client_Endpoint, deployment Perimeter, former_category MALWARE, signature_severity Major, malware_family toneshel, tag apt, tag mustangpanda,  created_at 2024_01_29;
sid: 3; rev: 1;)

In the second case, consider another version of the backdoor in which the fake tls response is 6 bytes

alert tcp any 443 -> any any  (msg: "ET MALWARE [ANY.RUN] ToneShell FakeTLS Response (APT Mustang Panda / Earth Preta)";
flow: established, to_client;
dsize: 6;
stream_size: server, =, 7;
stream_size: client, <, 337;
content: "|1703 0300 01|"; depth: 6;
isdataat: !2, relative; 
threshold: type limit, track by_dst, seconds 120, count 1;
classtype: command-and-control;
reference: md5, e6bc61262fb8e8ad600fd7b3c2dc7603;
reference: url, https://app.any.run/tasks/5d2b8690-4056-47af-a938-f8f801ddb423;
metadata: attack_target Client_Endpoint, deployment Perimeter, former_category MALWARE, signature_severity Major, malware_family toneshel, tag apt, tag mustangpanda,  created_at 2024_01_29;
sid: 4; rev: 1;)

-ˋˏ✄┈┈┈┈┈┈
𖡼𖤣𖥧𖡼𓋼𖤣𖥧𓋼𓍊 Jane

2 Likes

Thanks Jane, I’ll take a look and get these in tomorrow’s release!

2 Likes

@Jane0sint Here are those sids! These had to be shipped disabled because of a high number of checks which resulted in poor performance. Since the content matches are so short there wasn’t much we could do with fast_pattern adjustments.

I’ve also never used the bitmask parameter before so that was a new experience for me! What I found was that it is actually only supported in Suricata versions 6+ so I had to remove that logic for our ruleset.

  2050597 - ET MALWARE [ANY.RUN] BACKDOOR [ANY.RUN] ToneShell FakeTLS Check-In (APT Mustang Panda / Earth Preta) M1
  2050598 - ET MALWARE [ANY.RUN] BACKDOOR [ANY.RUN] ToneShell FakeTLS Check-In (APT Mustang Panda / Earth Preta) M2
  2050599 - ET MALWARE [ANY.RUN] ToneShell FakeTLS Response (APT Mustang Panda / Earth Preta) M1
  2050600 - ET MALWARE [ANY.RUN] ToneShell FakeTLS Response (APT Mustang Panda / Earth Preta) M2
1 Like

Hi, I didn’t hoped on adding them to the set, understanding how they work. Tony just asked me to do a short description of the detection.
I would also like to ask you to make changes to the messages:

2050597 - ET MALWARE [ANY.RUN] ToneShell FakeTLS Check-In (APT Mustang Panda / Earth Preta) M1
2050598 - ET MALWARE [ANY.RUN] ToneShell FakeTLS Check-In (APT Mustang Panda / Earth Preta) M2

Thanks, and have a nice day!

1 Like

I am inspired that I can share something interesting. For example, what can I say about the byte mask? I used it back when I wrote for SNORT for CiscoTalos
image

And since then I sometimes use it to calculate a binary counter (rarely), for example, which will count by 16 B. It is very convenient to compare the sizes of messages that are encrypted with AES since a block is 16 bytes, respectively, the mask 0b00001111 will be equal to zero.
⋆꙳•̩̩͙❅̩̩͙‧͙ ‧͙̩̩͙❆ ͙͛ ˚₊⋆
Jane

2 Likes

I’ll get those names fixed up today :tada:

2 Likes