Skip to content

Instantly share code, notes, and snippets.

@bgrewell
Last active April 19, 2018 15:22
Show Gist options
  • Save bgrewell/cc03a0cd7487260437c8e7eb33b5d00a to your computer and use it in GitHub Desktop.
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.
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])
}
}
}
@bgrewell
Copy link
Author

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.

  1. 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.

  2. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment