Threat hunting with Osquery, Part 2 – Command and Control

  • Friday, Jul 16, 2021
Singel-post cover image

Part 2 – Command and Control

After gaining initial access to a device, the attackers try to establish command and control (C&C, C2) over the device with the aim to use it in following stages of the attack. For this purpose, attackers often launch malicious processes, hunting for which is the topic of this part of our blog series. We will show Osquery queries helpful in identifying processes with suspicious network activity, which can serve the attackers for easy backdoor access to the device. Queries from this blog need to be run with administrator privileges, otherwise their results can be incomplete.

You can read more about Osquery in our short blog post.

Running processes

One of the most frequently used Osquery tables, “processes” offers a lot of information about currently running processes. From basic information like executable path, command line arguments and PID to details such as usage of CPU time, memory usage and disk IO amount. The query listed below represents a general starting point that can be adjusted according to the type of suspicious activity we are currently hunting for. It also demonstrates typical Osquery usage in combining data from multiple tables. First clues to look for in the output are unusual arguments of command interpreter programs, such as cmd, powershell, python, cscript. Then, look for names of processes running from unusual locations. A classic example is execution of system executables running from a folder other than System32 or SysWOW64. Processes running from AppData warrant a closer look, although these can be legitimate. For each process, it is worth to check the account it is running under and what is its parent process.

It is important to realize capabilities and limitations of Osquery when dealing with relatively short-duration effect. Returned data gives information about the state at the moment of processing the query. If a process starts and terminates in between two queries, we will not find it in the “processes” table results.

SELECT 
    p.pid 
    p.path, 
    p.cmdline, 
    u.username, 
    datetime(p.start_time, "unixepoch"), 
    p.parent AS parent_pid, 
    p2.path AS parent_path 
FROM processes AS p 
    JOIN users AS u USING(uid) 
    JOIN processes AS p2 ON p.parent = p2.pid; 

Listening Ports

This query lists information identifying processes listening on the network. When sorting through the results it is important to ask the question, whether the used port is standard for the process and whether it is supposed to be listening at all.

SELECT 
    lp.pid, 
    lp.port,
    lp.protocol, 
    lp.address, 
    p.path, 
    p.cmdline 
FROM listening_ports AS lp 
    JOIN processes AS p USING(pid); 

Open Sockets

Just as the previous query, the one listed below retrieves information about processes listening and communicating over the network. The difference in this case is querying for open network sockets.

SELECT 
    s.pid, 
    p.path, 
    p.cmdline, 
    s.local_address, 
    s.local_port, 
    s.remote_address, 
    s.remote_port, 
    s.state 
FROM process_open_sockets AS s
    JOIN processes AS p USING(pid); 

Hashes

When a suspicious process, its executable or any related file is identified, it is common practice to check its reputation in online databases like Virustotal, Hybrid-Analysis, Intezer and similar. In all of these, hashes are used to uniquely represent files. Osquery comes to our aid with the query listed below, combining tables “file” and “hash”. The file hash is calculated on the queried device and therefore we must be cautious when requesting hashes of multiple files – we can cause significant load to the device. The query contains filesystem timestamps that are very useful in reconstructing the timeline of events and when correlating with data from other sources.

SELECT 
    f.path, 
    f.size, 
    datetime(f.atime, "unixepoch") AS access_time, 
    datetime(f.mtime, "unixepoch") AS modify_time, 
    datetime(f.ctime, "unixepoch") AS create_time, 
    h.sha256 
FROM file AS f 
    JOIN hash AS h 
WHERE h.path = f.path AND f.path = "<path_to_file>";