GTPDOOR - A novel backdoor tailored for covert access over the roaming exchange
Discovery and analysis of a magic packet type implant that communicates C2 traffic over the GTP-C 3GPP protocol.
Introduction
GTPDOOR is the name of Linux based malware that is intended to be deployed on systems in telco networks adjacent to the GRX (GRPS eXchange Network) with the novel feature of communicating C2 traffic over GTP-C (GPRS Tunnelling Protocol - Control Plane) signalling messages. This allows the C2 traffic to blend in with normal traffic and to reuse already permitted ports that maybe open and exposed to the GRX network.
The following diagram illustrates a foreseen use of GTPDOOR. Here the actor already has established persistence on the roaming exchange network and access a compromised host by sending GTP-C Echo Request messages with a malicious payload:
In addition to remote code execution capability, GTPDOOR can be beaconed by sending arbitrary TCP packets to a host the implant resides on. Supporting it’s stealth capability, the beacon response message hides particular information in a TCP header flag.
Naming
I have given this malware the name GTPDOOR as it uses a similar “port knocking / magic packet” technique as BPFDOOR as described here and here. Both use raw sockets to intercept packets on the network interface. Unlike BPDDOOR, GTPDOOR explicitly uses GTP-C echo request/response messages and does not utilize BFP / pcap filters, but rather filters on UDP and GTP header values through simple cmp
instructions. At the time of writing, I am not aware of this malware being documented anywhere else.
Attribution
GTPDOOR is likely attributed to UNC1945 (Mandiant) / LightBasin (CrowdStrike)
As described in the CrowdStrike article this threat actor has been documented to use the GTP protocol for encapsulating tinyshell traffic in a valid PDP context session by employing an SGSN emulator to tunnel traffic to an external GGSN in another operator network. Here, GTPDOOR is leveraging not off a PDP context (GTP-U, userplane) but specific GTP-C signalling messages with it’s own extended message structure.
As we will see below, both binaries contain the name of the original c source file, dnsd.c
. A google search links to a presentation by CrowdStrike about this threat actor that contains text from a process listing originating from what looks like a Solaris machine. In that listing is a process with the name dnsd
:
If the attribution is correct, then given the discovery of this screenshot, it is likely that in addition to the two Linux binaries documented in this blog post, a third version exists which targets Sun Solaris systems.
Background information
In order to provide connectivity between telecommunication network operators around the globe, a “closed” network exists that provides interconnectivity between various systems. These network elements / functions need to have direct connectivity to the GRX network in order to route / forawrd roaming related signalling and user plane traffic. Examples of these systems are:
- eDNS - External DNS to resolve APN names, select packet gateway for routing the subscribers traffic
- SGSN, GGSN - 2G/3G packet core network elements for packet switched data
- P-GW (Packet Data Network Gateway) - 4G version of the GGSN
- STP - Signalling gateways for circuit switched routing (e.g. authentication to HLR/HSS) - specifically for SS7 signalling.
- DRA (Diameter Routing Agent) - 4G version of the STP, rather then SS7, the signalling traffic is over diameter.
These functions are listed as to give examples of where GTPDOOR could be placed as they may require direct connectivity to the GRX network. That is - providing opportunity for direct access into a telco’s core network. It is more likely that it would be placed on systems that support GTP-C over GRX, such as SGSN, GGSN, PGW (which don’t run some esoteric operating system). That said, if the GRX firewall is not configured right, there would be opportunities to place this type of implant elsewhere, or even within the internal core network.
IR.21
is used for network providers to publish the details of these systems such as the GT (global titles), IP addresses, APNs etc. This list is used for other companies that have roaming agreements to configure their network accordingly. Alternatively, they may exchange this information directly.Summary of functionality
GTPDOOR supports the following:
- Listens for “magic” wakeup packet, a GTP-C echo request message (GTP type
0x01
). The host does not need to have a listening sockets / listening services active, as all UDP packets are received into the user space via opening a raw socket - Executes a command on the host which is specified in the magic packet and returns the output to the remote host, supporting a “reverse shell” type functionality. Both request/responses are
GTP_ECHO_REQUEST
/GTP_ECHO_RESPONSE
messages accordingly. - Can be covertly probed from an external network to illicit a response by sending a TCP packet to any port number. If the implant is active a crafted empty TCP packet is returned along with information if the destination port was open/responding on the host.
- Authenticates and encrypts contents of magic GTP packet messages using a simple XOR cipher.
- At runtime can be instructed to change it’s authentication + encryption key (rekeying). This prevents the default key hardcoded in the binary to be used by other actors
- Blend in to environment by changing it’s process name to look like syslog process invoked as a kernel thread
- Does not require ingress firewall changes if the target host is allowed to communicate over the GTP-C port.
Versions
At the time of writing two versions have been identified on Virustotal:
Version | Filename | Architecture | Hash |
---|---|---|---|
1 | dbus-echo | x86-64 | 827f41fc1a6f8a4c8a8575b3e2349aeaba0dfc2c9390ef1cceeef1bb85c34161 |
2 | pickup | i386 | 5cbafa2d562be0f5fa690f8d551cdb0bee9fc299959b749b99d44ae3fda782e4 |
pickup
has additional enhancements/features to dbus-echo
, and hence is assigned a higher version number.
At the time of writing, both samples have been uploaded to Virustotal in late 2023.
Version 1 - 1 detection
Version 2 - 0 detections
Both binaries were targeted for a particularly old Linux distribution, “Red Hat Linux 4.1”. This is the equivalent to RHEL 5.x. The GCC date is marked 2008. It is quite likely the target network operator of this implant had quite poor patch / lifecycle management.
2024-03-17
- for maximum ABI compatibility, compiling against old glibc versions increases the chance of binaries working in newer releases, so the targetted version may not be exact, although it would be expected to be within proximity to ensure stability.
As the binaries are not stripped, source code’s original filename was likely dnsd.c
:
Technical Details
GTP magic packet message types
The command instruction is sent in the GTP Echo Request message along with the associated data. As summarized:
GTPDOOR v1
Message Type | Function | Payload |
---|---|---|
0x01 |
Set new encryption key | New key |
0x02 |
Write data to system.conf |
File content |
0x03 - 0xFF |
Execute command and return output | Shell command to run |
GTPDOOR v2
Message Type | Function | Payload |
---|---|---|
0x01 |
Set new encryption key | New key value |
0x02 |
Write arbitrary data to system.conf |
File content |
0x03 ,0x04 ,0x08 -0xFF |
Execute command and return output | Shell command to run |
0x05 |
IP address or subnet to access control list. | Multiple subnets or single IPs (/32) can be separated by a comma, e.g. 192.168.0.1/24,10.0.0.1 |
0x06 |
Return ACL list | |
0x07 |
Clear ACL |
Magic packet format
The packet can be visually represented as followed:
As a “c-like struct”:
struct gtp_header
{
uint8_t flags;
uint8_t type;
uint16_t length;
uint32_t tei; // technically labelled spare if type == GTP_ECHO
};
struct gtpdoor_header
{
uint8_t pad[5];
int32_t key1;
uint8_t cmdMsgType;
uint16_t cmdLength;
};
struct gtpdoor_packet
{
ip_header iph;
udp_header udph;
gtp_header gtph;
gtpdoor_header gtpdoorh;
uint8_t payload[2020];
};
Operational detail
Version 1 + 2:
- Checks if the length of it’s filename is greater then 8 characters, and if so, process name stomps itself to become
[syslogd]
by overwritingargv
. The length check is to ensure it does not corrupt the stack. - Tells the parent process to ignore signals from it’s child process be setting
SIG_IGN
for theSIGCHLD
signal - Creates a raw socket listening for UDP packets on port 2123 (GTP-C)
- Accepts UDP packets on destination port
2123
with a GTP header field type value ofGTP_ECHO_REQUEST
- Checks that the 32 bit symmetric key is correct in order to authenticate the message. The hardcoded value in the binary is
135798642
, representative of someone typing odd numbers up the length of a keyboard even numbers back down again:
- Decrypts payload in GTP message using the same authentication key using a simple XOR at fixed blocks of the key size.
An equivalent implementation of the decryption routine in python:
def decrypt(key, ciphertext):
key_idx = 0
strlen = len(ciphertext)
plaintext = bytearray(strlen)
for i in range(strlen):
if key_idx >= len(key):
key_idx = 0
plaintext[i] = key[key_idx] ^ ciphertext[i]
key_idx += 1
return plaintext
- Executes a function specified message type with the primary function to execute a shell command and return the result to the remote client via a
GTP_ECHO_RESPONSE
message
If the message type number is not explicitly defined, the action will fall back to the remote code execution function:
The above image also shows the approximate code for the “rekeying” message type.
- Can write arbitrary contents to a file,
system.conf
. It’s exact purpose is unknown.
Specific to version 2:
- Multithreaded (GTP magic packet handler and TCP probe beacon handler)
As the binary was not stripped and debug symbols left in, we can see the original function names tcpMethod
and gtpMethod
which run in two pthreads:
- Creates a mutex
/var/run/daemon.pid
to prevent more then once instance running. The mutex file contains the PID of the process - Acknowledge it is alive by responding to any TCP packet on any port number with an empty TCP packet with both the
RST
andACK
flags set.
On “remote command execution”, the process is forked()
and popen()
is utilized to execute a subprocess on the host.
All printf
statements such as those observed above are emitted to stdout
. As such it is likely GTPDOOR would be invoked similar to the following (redirecting stdin
and stderr
to /dev/null
and detaching from the parent process):
nohup ./gtpdoor 2>&1 2>/dev/null &
More on the probing feature
The TCP probe is a feature that allows an external host to probe the GRX listening address for TCP packets. A subnet filter is checked against the source IP address of the “client” and if it does NOT match, a reply beacon is sent to the (scanning) client. A crafted TCP RST/ACK
response packet with the urgent pointer field set to 0x01
indicates the implant is running.
Based on the analysis of the behaviour, it appears that two types of scans are supported:
- TCP connect scan (simple TCP three way handshake)
- ACK scans (client sending an ACK message with no initial SYN)
TCP Connect Scan
For a TCP connect scan to work, there must be a service listening and port exposed on the host, either from a permissive firewall or no firewall at all. GTPDOOR will send a “decoy” TCP ACK/RST
response on the initial TCP SYN
from the client but with no identifying information to indicate GTPDOOR is running. It is the second TCP ACK/RST
that is sent on the client’s ACK that has the urgent pointer set to signal GTPDOOR is running on the remote host.
ACK Scan
For an ACK scan to work, the condition would be no firewall or a non stateful firewall (so the TCP ACK
is not dropped due to no initial SYN
packet from the client). No service needs to listen the TCP beaconing port with an ACK scan: as with the GTP message handler, a raw socket is used to “intercept” all TCP packets.
The beacon response packet that is sent back to the probing host is manually assembled, copying the incoming packet’s relevant IP and TCP header fields into the outgoing beacon packet.
The probe response packets will always have the ACK/RST
flags set and the urgent pointer flags set according to if an TCP ACK
was observed. This is a covert way of encoding messages by bit manipulation in the TCP header.
It is assumed that packets of this combination would be rare or non-existent in normal TCP/IP communication as the URG flag is set to 0, while the urgent pointer specifies a value. Additionally, this is occurring with the RST flag set.
RFC0793 states that the urgent pointer has no meaning if the URG flag is not set:
We can observe the differences in a tcpdump. In the following a TCP connect() from the probe “client” on a non existing port 22222
has a probe response RST/ACK
with the urgent pointer flat set to 0
. The assumption here is that this is a “decoy” response to blend in RST/ACK
responses for closed or open ports on the remote host.
On the other hand, when the client connects to an open port 22 (SSH)
, the probe response includes a RST/ACK
but this time with the urgent pointer set to 1
ACL functionality
It is not known if the ACL is intended to be a deny list or allow list - there are pros and cons of explicitly denying IP subnets from probing:
- Avoid keeping threat actor C2 infrastructure network/IPs resident in memory
- Specify internal victim networks or host IPs to prevent causing traffic disruption from beaconed TCP reset messages.
Based on analysis of the samples alone, the author assumes this behaviour is intentional. The threat actor can change their C2 infrastructure or intermediate transit hosts without loosing the ability to send probe messages.
It is likely that the RST/ACK
responses from an initial SYN
packet is there as a decoy - a casual review of a network capture would result in one assuming that this is normal behaviour of the firewall or hosts TCP/IP stack. One would have to notice that the RST/ACK
packet has the urgent pointer field set only for incoming ACK
packets. Tricky indeed!
An approximation of the ACL filtering. Note the !
on line 118
:
Notably one condition before TCP packets are “intercepted” by the process is the global variable local_grx_addr
must be set first. This is set based on the destination IP address in any GTP-C packet that is received.
Another condition is that the ACL must have at least one subnet or IP defined for the probe feature to be operational.
Detection
- GTPDOOR can be identified by listing raw sockets open on the system, e.g. via
lsof
, looking forSOCK_RAW
orraw
.
netstat -lp --raw
also shows listening sockets:
- Process name stomped files that are disguised as kernel threads can be identified by their parent process not being
kthreadd
. In the following screenshot thePPID
of the backdoor is1935
, while the other kernel thread parent IDs are2
:
- The presence of the mutex
/var/run/daemon.pid
could be an indicator. - The presence of the file
system.conf
could be an indicator.
Yara rule for threat hunting:
rule Linux_Malware_GTPDOOR_v1v2
{
meta:
description = "Detects GTPDOOR"
author = "@haxrob"
data = "28/02/2024"
reference = "https://doubleagent.net/telecommunications/backdoor/gtp/2024/02/27/GTPDOOR-COVERT-TELCO-BACKDOOR"
hash1 = "827f41fc1a6f8a4c8a8575b3e2349aeaba0dfc2c9390ef1cceeef1bb85c34161"
hash2 = "5cbafa2d562be0f5fa690f8d551cdb0bee9fc299959b749b99d44ae3fda782e4"
strings:
$s1 = "excute result is" ascii fullword
$s2 = "idkey not correct" ascii fullword
$s3 = "send ret message" ascii fullword
condition:
uint16(0) == 0x457f and
2 of them and
filesize < 20KB
}
Defence
GSMA has released two relevant guidelines:
- FS.31 GSMA Baseline Security Controls
- IR.77 Inter-Operator IP Backbone Security Requirements For Service Providers and Inter-operator IP backbone Providers
GTP Firewall
GPTDOOR handles malformed GTP packets. In the following test, the GTP protocol type of 0
(GTP prime - charging related) is set in custom client. GTP’ does not work over the GTP-C port. Additionally the extension header is corrupt. The GTPDOOR message encrypted payload is appended on the GTP message. As such, a GTP capable firewall may detect and drop abnormal packets like this.
Firewalling
- The inbound UDP port is required to be open for systems that require it on the GRX network. Firewall rules should be explicit enough to drop these packets inbound for any system that does not use the GTP protocol
- Aggressive rules to block inbound TCP connections via the GRX - There is not a lot that actually needs to be open
- Probe TCP packets with RST/ACK flag set could be dropped on the GRX firewall
Active GTPDOOR network scanner
A multithreaded network scanner is available which can be used to scan remotes hosts in attempt to detect the presence of GTPDOOR: