Last active
April 19, 2018 15:22
-
-
Save bgrewell/cc03a0cd7487260437c8e7eb33b5d00a to your computer and use it in GitHub Desktop.
ProcWatcher is a tool to watch newly created processes on a Linux system. The main purpose of this was to have a way to watch for very short lived randomly executed processes so that their command line arguments could be captured. Note: This is intentinally designed to run under conditions with minimal privillages and no external dependencies.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"io/ioutil" | |
"log" | |
"strings" | |
) | |
// Map to hold a list of "currently running" processes. | |
var processes = make(map[string]string) | |
// Build a map of userid to username | |
var userMap = make(map[string]string) | |
func buildUserIdMap() { | |
passwdContents, err := ioutil.ReadFile("/etc/passwd") | |
if err != nil { | |
return | |
} | |
lines := strings.Split(string(passwdContents), "\n") | |
for _, line := range lines { | |
if len(line) < 5 { | |
continue | |
} | |
elements := strings.Split(line, ":") | |
name := elements[0] | |
uid := elements[2] | |
userMap[uid] = name | |
} | |
} | |
// Function to check for new processes. | |
func checkForNewProcesses() (newProcesses, processOwners map[string]string) { | |
// Get a list of all files/dirs inside /proc | |
files, err := ioutil.ReadDir("//proc") | |
if err != nil { | |
log.Fatal(err) | |
} | |
// Create a map to hold new process info | |
newProcesses = make(map[string]string) | |
processOwners = make(map[string]string) | |
// Iterate through the list of files/dirs | |
for _, file := range files { | |
// If this is a directory try to process it | |
if file.IsDir() { | |
name := file.Name() | |
// Does it already exist in the list of processes we've seen? | |
if _, ok := processes[name]; ok { | |
continue | |
} | |
// If not add it to the list of new processes | |
cmdline, err := ioutil.ReadFile(fmt.Sprintf("//proc//%s//cmdline", name)) | |
if err != nil { | |
continue | |
} | |
// Split into strings whenever we find a null byte | |
parts := make([]string,0) | |
start := 0 | |
for i := 0; i < len(cmdline); i++ { | |
if cmdline[i] == 0x00 { | |
parts = append(parts, string(cmdline[start:i])) | |
start = i | |
} | |
} | |
processArgs := strings.Join(parts, " ") | |
newProcesses[name] = processArgs | |
processes[name] = processArgs //TODO: With the way this is currently used could just be a slice | |
// Try to get owner info | |
statusline, err := ioutil.ReadFile(fmt.Sprintf("//proc//%s//status", name)) | |
if err != nil { | |
continue | |
} | |
lines := strings.Split(string(statusline), "\n") | |
uid := strings.Split(lines[8], "\t")[1] | |
processOwners[name] = userMap[uid] | |
} | |
} | |
return | |
} | |
func main() { | |
// Build the map to translate uid to a friendly name | |
buildUserIdMap() | |
for { | |
newProcesses, processOwners := checkForNewProcesses() | |
for key, value := range newProcesses { | |
log.Printf("[%5s]\t%s [owner=%s]\n", key, value, processOwners[key]) | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Cleaned up the code a little and added some code to check who the process owner is. There is still a few things that need to be fixed.
Should remove pids from the list if they aren't seen on a subsequent run, this will allow us to see processes that are spawned using the same pid as an older process.
Should profile code and see if there are any obvious bottlenecks. As is I have seen a few times where processes that are short lived like an ls -la aren't actually seen because we weren't enumerating the contents of /proc at the right time.