![]() |
EpiRootkit
By STDBOOL
|
The rootkit implements two primary network communication channels between the attacker and victim machines:
Both channels use AES-128 to secure exchanged data. The TCP channel is the primary channel; the DNS channel is used as a covert or fallback communication method.
To secure our network communications, we chose to use AES-128 encryption for all data exchanged between the client and server. However, the drawback of this algorithm is that it doesn't allow transmitting data of arbitrary size. Indeed, AES-128 encryption produces a 16-byte block, meaning data must be split into blocks of this size before being encrypted.
When transmitting data via a socket, it's common for data to be of variable size, which poses a problem for encryption. The same applies to received data, which may be of variable size and not correspond to a multiple of 16 bytes.
To solve this problem, we implemented a custom chunked transmission protocol. This protocol allows splitting data into fixed-size chunks, each enriched with an (unencrypted) header for identification, reconstruction, and error detection. Thus, even if the data is of variable size, it can be split into fixed-size chunks, allowing secure and reliable transmission.
| Constant | Default Value | Description |
|---|---|---|
STD_BUFFER_SIZE | 1024 | Fixed size of buffers used |
CHUNK_OVERHEAD | 11 | 10 (header) + 1 (EOT_CODE) |
EOT_CODE | 0x04 | ASCII code for "End of Transmission" |
This custom protocol allows reliable transmission of arbitrary-size data (text or files) between a client and server via a kernel socket. Data is encrypted then split into fixed chunks, each enriched with a header for identification, reconstruction, and error detection.
Each chunk is a constant-size buffer of STD_BUFFER_SIZE bytes structured as follows:
| Field | Size | Description |
|---|---|---|
total_chunks | 4 bytes | Total number of chunks (big-endian) |
chunk_index | 4 bytes | Index of this chunk in the sequence (big-endian) |
data_len | 2 bytes | Actual data length in the chunk (big-endian) |
payload | variable | Encrypted data |
EOT_CODE | 1 byte | End of transmission code for the chunk (valid if set) |
padding | variable | Padding to reach STD_BUFFER_SIZE, ignored on reception |
🔒 All data sent in the payload is encrypted before being split into chunks.
encrypt_buffer.BODY_SIZE (= STD_BUFFER_SIZE - 11 (HEADER_SIZE + FOOTER_SIZE)).EOT_CODE marker at the end of datakernel_sendmsg.payload + EOT (useful data).EOT_CODE.total_chunks and chunk_index.seen.exec, process as text command.The custom chunked transmission protocol is implemented in the network.c file (for the rootkit) and the AESNetworkHandler.py file (for the attacker). Here's an overview of the main functions:
The main functions of the chunked protocol are:
send_to_server_raw(const char *data, size_t len): This function encrypts the data to send, splits it into fixed-size chunks, adds a header to each chunk (total number of chunks, index, useful size, end marker), then sends them one by one via the kernel socket. Simplified example:
receive_from_server(char *buffer, size_t max_len): This function reads data received from the kernel socket, reads each chunk, verifies its header, assembles data in a reception buffer, and decrypts the complete message once all chunks are received. These are basically the inverse operations of send_to_server_raw.