Projects STRLCPY dismember Commits d883a22e
🤬
  • ■ ■ ■ ■ ■
    README.md
    1  -# dismember
     1 +# Dismember
     2 + 
     3 +Dismember is a command-line toolkit for Linux that can be used to explore processes and (especially) their memory. Essentially for playing with `/proc`.
     4 + 
     5 +One core feature is the ability to scan the memory of all processes for common secrets, or for custom regular expressions.
    2 6   
    3  -Dismember is a command-line tool for Linux used to grep for patterns across the entire memory used by a process (or processes).
     7 +![A gif showing dismember finding credentials from the memory of a browser](demo.gif)
    4 8   
    5  -![A gif showing dismember finding a password from a Slack message](demo.gif)
     9 +Using the `grep` command, it can match a regular expression across all memory for all (accessible) processes. This could be used to find sensitive data in memory, identify a process by something included in its memory, or to interrogate a processes' memory for interesting information.
     10 + 
     11 +There are many built-in patterns included via the `scan` command, which effectively works as a secret scanner against the memory on your machine.
    6 12   
    7 13  Dismember can be used to search memory of all processes it has access to, so running it as root is the most effective method.
    8 14   
    9 15  Commands are also included to list processes, explore process status and related information, draw process trees, and more...
    10 16   
     17 +## Available Commands
     18 + 
     19 +| Command | Description |
     20 +|-----------|------------------------------------------------------------------------------------------|
     21 +| `files` | Show a list of files being accessed by a process |
     22 +| `find` | Find a PID given a process name. If multiple processes match, the first one is returned. |
     23 +| `grep` | Search process memory for a given string or regex |
     24 +| `info` | Show information about a process |
     25 +| `kernel` | Show information about the kernel |
     26 +| `kill` | Kill a process using SIGKILL |
     27 +| `list` | List all processes currently available on the system |
     28 +| `resume` | Resume a suspended process using SIGCONT |
     29 +| `scan` | Search process memory for a set of predefined secret patterns |
     30 +| `suspend` | Suspend a process using SIGSTOP (use 'dismember resume' to leave suspension) |
     31 +| `tree` | Show a tree diagram of a process and all children (defaults to PID 1). |
     32 + 
    11 33  ## Installation
    12 34   
    13 35  Grab a binary from the [latest release](https://github.com/liamg/dismember/releases/latest) and add it to your path.
    14 36   
    15  -## Examples
     37 +## Usage Examples
    16 38   
    17 39  ### Search for a pattern in a process by PID
    18 40  ```bash
    skipped 13 lines
    32 54  dismember grep 'gh[pousr]_[0-9a-zA-Z]{36}'
    33 55  ```
    34 56   
    35  -### Render a complete process tree
     57 +### Search for secrets in memory across all processes
    36 58  ```bash
    37  -# defaults to pid=1 to show all (accessible) processes
    38  -dismember tree
     59 +# search all accessible memory for common secrets
     60 +dismember scan
    39 61  ```
    40 62   
     63 +## FAQ
     64 + 
     65 +> Isn't this information all just sitting in `/proc`?
     66 +Pretty much. Dismember just reads and presents it for the most part. If you can get away with `grep whatever /proc/[pid]/blah` then go for it! I built this as an educational experience because I couldn't sleep one night and stayed up late reading the `proc` man-pages (I live an extremely rock 'n' roll lifestyle). It's not a replacement for existing tools, but perhaps it can complement them.
     67 + 
     68 +> Do you know how horrific some of these commands seem when read out of context?
     69 +Yes, I realised after running `dismember kill --children 291458` and grimacing as I looked back at the command. Oops.
     70 + 
  • ■ ■ ■ ■ ■ ■
    TODO.md
     1 +# TODO
     2 + 
     3 +- [x] Add lots more info to status (ownership etc.)
     4 +- [x] Add command to find pid by name
     5 +- [x] Add suspend/resume commands (`syscall.Kill(process.PID(), syscall.SIGINT)`)
     6 +- [x] Add kill command (with --children flag)
     7 +- [x] Add kernel command to show kernel info (/proc/cmdline, /proc/sys/kernel/*)
     8 +- [x] Add files command to show list of files open by a process (/proc/123/fd/*)
     9 +- [x] Add godoc everywhere
     10 +- [x] Add scan command with built-in patterns
     11 +- [ ] Add debug logging
  • demo.gif
  • ■ ■ ■ ■ ■ ■
    go.mod
    skipped 2 lines
    3 3  go 1.18
    4 4   
    5 5  require (
     6 + github.com/owenrumney/squealer v1.1.0
    6 7   github.com/spf13/cobra v1.5.0
    7 8   github.com/stretchr/testify v1.7.4
    8 9  )
    skipped 3 lines
    12 13   github.com/inconshreveable/mousetrap v1.0.0 // indirect
    13 14   github.com/pmezard/go-difflib v1.0.0 // indirect
    14 15   github.com/spf13/pflag v1.0.5 // indirect
     16 + gopkg.in/yaml.v2 v2.4.0 // indirect
    15 17   gopkg.in/yaml.v3 v3.0.1 // indirect
    16 18  )
    17 19   
  • ■ ■ ■ ■ ■ ■
    go.sum
    skipped 3 lines
    4 4  github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
    5 5  github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
    6 6  github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
     7 +github.com/owenrumney/squealer v1.1.0 h1:XGg9GmGKb9CCRkbmbhfD9lzn41dNzJ9vaJjYljIHO5c=
     8 +github.com/owenrumney/squealer v1.1.0/go.mod h1:WWvhG67r/BBwvLwmE2TcASI0b/xyPxmR9y33q/mg4ig=
    7 9  github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
    8 10  github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
    9 11  github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
    skipped 8 lines
    18 20  github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
    19 21  gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
    20 22  gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
     23 +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
    21 24  gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
    22 25  gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
    23 26  gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
    skipped 2 lines
  • ■ ■ ■ ■ ■ ■
    internal/cmd/files.go
     1 +package cmd
     2 + 
     3 +import (
     4 + "fmt"
     5 + "strconv"
     6 + 
     7 + "github.com/liamg/dismember/pkg/proc"
     8 + "github.com/spf13/cobra"
     9 +)
     10 + 
     11 +func init() {
     12 + rootCmd.AddCommand(&cobra.Command{
     13 + Use: "files [pid]",
     14 + Short: "Show a list of files being accessed by a process",
     15 + RunE: filesHandler,
     16 + Args: cobra.ExactArgs(1),
     17 + })
     18 +}
     19 + 
     20 +func filesHandler(cmd *cobra.Command, args []string) error {
     21 + 
     22 + pid, err := strconv.Atoi(args[0])
     23 + if err != nil {
     24 + return fmt.Errorf("invalid pid specified: '%s': %w", args[0], err)
     25 + }
     26 + 
     27 + process := proc.Process(pid)
     28 + 
     29 + files, err := process.Files()
     30 + if err != nil {
     31 + return fmt.Errorf("failed to read accessed files for process %d: %w\n", process.PID(), err)
     32 + }
     33 + 
     34 + w := cmd.OutOrStdout()
     35 + for _, file := range files {
     36 + _, _ = fmt.Fprintln(w, file)
     37 + }
     38 + return nil
     39 +}
     40 + 
  • ■ ■ ■ ■ ■ ■
    internal/cmd/find.go
     1 +package cmd
     2 + 
     3 +import (
     4 + "fmt"
     5 + 
     6 + "github.com/liamg/dismember/pkg/proc"
     7 + "github.com/spf13/cobra"
     8 +)
     9 + 
     10 +func init() {
     11 + rootCmd.AddCommand(&cobra.Command{
     12 + Use: "find [name]",
     13 + Short: "Find a PID given a process name. If multiple processes match, the first one is returned.",
     14 + Long: ``,
     15 + RunE: findHandler,
     16 + Args: cobra.ExactArgs(1),
     17 + })
     18 +}
     19 + 
     20 +func findHandler(cmd *cobra.Command, args []string) error {
     21 + 
     22 + processes, err := proc.List(true)
     23 + if err != nil {
     24 + return err
     25 + }
     26 + 
     27 + w := cmd.OutOrStdout()
     28 + 
     29 + for _, process := range processes {
     30 + status, err := process.Status()
     31 + if err != nil {
     32 + logger.Log("failed to read status for process %d: %s\n", process.PID(), err)
     33 + continue
     34 + }
     35 + if status.Name == args[0] {
     36 + _, _ = fmt.Fprintf(w, "%d\n", process.PID())
     37 + return nil
     38 + }
     39 + }
     40 + 
     41 + return fmt.Errorf("no process found with name '%s'", args[0])
     42 +}
     43 + 
  • ■ ■ ■ ■ ■ ■
    internal/cmd/grep.go
    skipped 5 lines
    6 6   "regexp"
    7 7   "strings"
    8 8   
     9 + "github.com/liamg/dismember/pkg/secrets"
     10 + 
    9 11   "github.com/liamg/dismember/pkg/proc"
    10 12   "github.com/spf13/cobra"
    11 13  )
    skipped 15 lines
    27 29   }
    28 30   
    29 31   grepCmd.Flags().IntVarP(&flagPID, "pid", "p", 0, "PID of the process whose memory should be grepped. Omitting this option will grep the memory of all available processes on the system.")
    30  - grepCmd.Flags().StringVarP(&flagProcessName, "pname", "n", "", "Grep memory of all processes whose name contains this string.")
     32 + grepCmd.Flags().StringVarP(&flagProcessName, "process-name", "n", "", "Grep memory of all processes whose name contains this string.")
    31 33   grepCmd.Flags().IntVarP(&flagDumpRadius, "dump-radius", "r", 2, "The number of lines of memory to dump both above and below each match.")
    32 34   grepCmd.Flags().BoolVarP(&flagIncludeSelf, "self", "s", false, "Include results that are matched against the current process, or an ancestor of that process.")
    33  - grepCmd.Flags().BoolVarP(&flagFast, "fast", "f", false, "Skip mapped files in order to run faster.")
     35 + grepCmd.Flags().BoolVarP(&flagFast, "fast", "f", false, "Skip memory-mapped files in order to run faster.")
    34 36   rootCmd.AddCommand(grepCmd)
    35 37  }
    36 38   
    skipped 20 lines
    57 59   _ = stdErr
    58 60   stdOut := cmd.OutOrStdout()
    59 61   
     62 + pattern := secrets.Pattern{
     63 + Regex: regex,
     64 + }
     65 + 
    60 66   var total int
    61 67   for _, process := range processes {
    62 68   if flagProcessName != "" {
    skipped 5 lines
    68 74   continue
    69 75   }
    70 76   }
    71  - if !flagIncludeSelf && process.IsInHierarchyOf(proc.Self()) {
     77 + if !flagIncludeSelf && process.IsAncestor(proc.Self()) {
    72 78   continue
    73 79   }
    74  - results, err := grepProcessMemory(process, regex)
     80 + results, err := grepProcessMemory(process, pattern)
    75 81   if err != nil {
    76 82   // TODO: add to debug log
    77 83   //_, _ = fmt.Fprintf(stdErr, "failed to access memory for process %d: %s\n", process.PID(), err)
    skipped 14 lines
    92 98  }
    93 99   
    94 100  type GrepResult struct {
    95  - Pattern *regexp.Regexp
     101 + Pattern secrets.Pattern
    96 102   Process proc.Process
    97 103   Map proc.Map
    98 104   Address uint64
    skipped 88 lines
    187 193   return fmt.Sprintf("%s%s%c%s", ansiBold, ansiRed, b, ansiReset)
    188 194  }
    189 195   
    190  -func grepProcessMemory(p proc.Process, regex *regexp.Regexp) ([]GrepResult, error) {
     196 +func grepProcessMemory(p proc.Process, pattern secrets.Pattern) ([]GrepResult, error) {
    191 197   var results []GrepResult
    192 198   maps, err := p.Maps()
    193 199   if err != nil {
    skipped 1 lines
    195 201   }
    196 202   for _, map_ := range maps {
    197 203   if !map_.Permissions.Readable {
     204 + logger.Log("skipping memory at %X for process %s: memory is not readable", map_.Address, p.String())
     205 + continue
     206 + }
     207 + if flagFast && (map_.Path != "" && map_.Path[0] != '[') {
     208 + logger.Log("skipping memory at %X for process %s: location is memory-mapped", map_.Address, p.String())
    198 209   continue
    199 210   }
    200 211   memory, err := p.ReadMemory(map_, 0, 0)
    201 212   if err != nil {
     213 + logger.Log("failed to read memory at %X for process %s: %s", map_.Address, p.String(), err)
    202 214   continue
    203 215   }
    204  - for _, matches := range regex.FindAllIndex(memory, -1) {
     216 + for _, matches := range pattern.Regex.FindAllIndex(memory, -1) {
    205 217   results = append(results, GrepResult{
    206 218   Process: p,
    207 219   Map: map_,
    208 220   Address: map_.Address + uint64(matches[0]),
    209 221   Match: shrinkMatch(memory[matches[0]:matches[1]]),
    210  - Pattern: regex,
     222 + Pattern: pattern,
    211 223   })
    212 224   }
    213 225   }
    skipped 7 lines
  • ■ ■ ■ ■ ■ ■
    internal/cmd/info.go
     1 +package cmd
     2 + 
     3 +import (
     4 + "fmt"
     5 + "io"
     6 + "strconv"
     7 + 
     8 + "github.com/liamg/dismember/pkg/proc"
     9 + "github.com/spf13/cobra"
     10 +)
     11 + 
     12 +func init() {
     13 + rootCmd.AddCommand(&cobra.Command{
     14 + Use: "info [pid]",
     15 + Short: "Show information about a process",
     16 + RunE: infoHandler,
     17 + Args: cobra.ExactArgs(1),
     18 + })
     19 +}
     20 + 
     21 +func infoHandler(cmd *cobra.Command, args []string) error {
     22 + 
     23 + pid, err := strconv.Atoi(args[0])
     24 + if err != nil {
     25 + return fmt.Errorf("invalid pid specified: '%s': %w", args[0], err)
     26 + }
     27 + 
     28 + process := proc.Process(pid)
     29 + 
     30 + status, err := process.Status()
     31 + if err != nil {
     32 + return fmt.Errorf("failed to read status for process %d: %w\n", process.PID(), err)
     33 + }
     34 + 
     35 + owner, err := process.Ownership()
     36 + if err != nil {
     37 + return fmt.Errorf("failed to read ownership for process %d: %w\n", process.PID(), err)
     38 + }
     39 + 
     40 + stdOut := cmd.OutOrStdout()
     41 + 
     42 + printKeyValUint64Decimal(stdOut, "PID", process.PID())
     43 + printKeyValString(stdOut, "Name", status.Name)
     44 + printKeyValString(stdOut, "State", status.State.String())
     45 + if status.Parent != 0 {
     46 + printKeyValString(stdOut, "Parent", status.Parent.String())
     47 + } else {
     48 + printKeyValString(stdOut, "Parent", "-")
     49 + }
     50 + printKeyValUint64Decimal(stdOut, "Process Group", uint64(status.ProcessGroup))
     51 + printKeyValUint64Decimal(stdOut, "Session", uint64(status.Session))
     52 + printKeyValString(stdOut, "TTY", status.TTY.String())
     53 + printKeyValUint64Decimal(stdOut, "Terminal Process Group", uint64(status.ForegroundTerminalProcessGroup))
     54 + printKeyValUint64Hex(stdOut, "Kernel Flags", uint64(status.KernelFlags))
     55 + printKeyValUint64Decimal(stdOut, "Owner UID", uint64(owner.UID))
     56 + printKeyValUint64Decimal(stdOut, "Owner GID", uint64(owner.GID))
     57 + return nil
     58 +}
     59 + 
     60 +func printKeyValString(w io.Writer, key string, value string) {
     61 + _, _ = fmt.Fprintf(w, "%-24s %s\n", key, value)
     62 +}
     63 + 
     64 +func printKeyValUint64Decimal(w io.Writer, key string, value uint64) {
     65 + _, _ = fmt.Fprintf(w, "%-24s %d\n", key, value)
     66 +}
     67 + 
     68 +func printKeyValUint64Hex(w io.Writer, key string, value uint64) {
     69 + _, _ = fmt.Fprintf(w, "%-24s 0x%x\n", key, value)
     70 +}
     71 + 
  • ■ ■ ■ ■ ■ ■
    internal/cmd/kernel.go
     1 +package cmd
     2 + 
     3 +import (
     4 + "fmt"
     5 + 
     6 + "github.com/liamg/dismember/pkg/proc"
     7 + "github.com/spf13/cobra"
     8 +)
     9 + 
     10 +func init() {
     11 + rootCmd.AddCommand(&cobra.Command{
     12 + Use: "kernel",
     13 + Short: "Show information about the kernel",
     14 + RunE: kernelHandler,
     15 + Args: cobra.ExactArgs(0),
     16 + })
     17 +}
     18 + 
     19 +func kernelHandler(cmd *cobra.Command, args []string) error {
     20 + info := proc.GetKernelInfo()
     21 + stdOut := cmd.OutOrStdout()
     22 + printKeyValString(stdOut, "Type", info.OSType)
     23 + printKeyValString(stdOut, "Release", info.OSRelease)
     24 + printKeyValString(stdOut, "Boot Args", info.BootArgs)
     25 + _, _ = fmt.Fprintf(stdOut, "\n%s\n", info.FullVersion)
     26 + return nil
     27 +}
     28 + 
  • ■ ■ ■ ■ ■ ■
    internal/cmd/kill.go
     1 +package cmd
     2 + 
     3 +import (
     4 + "fmt"
     5 + "strconv"
     6 + "syscall"
     7 + 
     8 + "github.com/liamg/dismember/pkg/proc"
     9 + "github.com/spf13/cobra"
     10 +)
     11 + 
     12 +var flagKillChildren bool
     13 + 
     14 +func init() {
     15 + killCmd := &cobra.Command{
     16 + Use: "kill [pid]",
     17 + Short: "Kill a process using SIGKILL",
     18 + RunE: killHandler,
     19 + Args: cobra.ExactArgs(1),
     20 + }
     21 + killCmd.Flags().BoolVarP(&flagKillChildren, "children", "c", false, "Kill all children of the specified process (leaving the process itself alive)")
     22 + rootCmd.AddCommand(killCmd)
     23 +}
     24 + 
     25 +func killHandler(cmd *cobra.Command, args []string) error {
     26 + 
     27 + pid, err := strconv.Atoi(args[0])
     28 + if err != nil {
     29 + return fmt.Errorf("invalid pid specified: '%s': %w", args[0], err)
     30 + }
     31 + 
     32 + process := proc.Process(pid)
     33 + 
     34 + if !flagKillChildren {
     35 + if err := syscall.Kill(int(process.PID()), syscall.SIGKILL); err != nil {
     36 + return fmt.Errorf("failed to kill process %d: %w\n", process.PID(), err)
     37 + }
     38 + 
     39 + _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Process %s killed.\n", process.String())
     40 + return nil
     41 + }
     42 + 
     43 + processes, err := proc.List(true)
     44 + if err != nil {
     45 + return fmt.Errorf("failed to list children for process: %w", err)
     46 + }
     47 + 
     48 + for _, candidate := range processes {
     49 + status, err := candidate.Status()
     50 + if err != nil {
     51 + logger.Log("failed to determine status for process %s: %s", candidate, err)
     52 + continue
     53 + }
     54 + if status.Parent == process {
     55 + if err := syscall.Kill(int(candidate.PID()), syscall.SIGKILL); err != nil {
     56 + return fmt.Errorf("failed to kill child process %d: %w\n", candidate.PID(), err)
     57 + }
     58 + _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Child process %s killed.\n", candidate.String())
     59 + }
     60 + }
     61 + return nil
     62 +}
     63 + 
  • ■ ■ ■ ■ ■
    internal/cmd/list.go
    skipped 23 lines
    24 24   return err
    25 25   }
    26 26   
    27  - stdErr := cmd.ErrOrStderr()
    28 27   stdOut := cmd.OutOrStdout()
    29 28   
    30 29   for _, process := range processes {
    31 30   status, err := process.Status()
    32 31   if err != nil {
    33  - _, _ = fmt.Fprintf(stdErr, "failed to read status for process %d: %s\n", process.PID(), err)
     32 + logger.Log("failed to determine status for process %s: %s", process, err)
    34 33   continue
    35 34   }
    36 35   _, _ = fmt.Fprintf(stdOut, "% -10d %s\n", process.PID(), status.Name)
    skipped 5 lines
  • ■ ■ ■ ■ ■ ■
    internal/cmd/resume.go
     1 +package cmd
     2 + 
     3 +import (
     4 + "fmt"
     5 + "strconv"
     6 + "syscall"
     7 + 
     8 + "github.com/liamg/dismember/pkg/proc"
     9 + "github.com/spf13/cobra"
     10 +)
     11 + 
     12 +func init() {
     13 + rootCmd.AddCommand(&cobra.Command{
     14 + Use: "resume [pid]",
     15 + Short: "Resume a suspended process using SIGCONT",
     16 + RunE: resumeHandler,
     17 + Args: cobra.ExactArgs(1),
     18 + })
     19 +}
     20 + 
     21 +func resumeHandler(cmd *cobra.Command, args []string) error {
     22 + 
     23 + pid, err := strconv.Atoi(args[0])
     24 + if err != nil {
     25 + return fmt.Errorf("invalid pid specified: '%s': %w", args[0], err)
     26 + }
     27 + 
     28 + process := proc.Process(pid)
     29 + 
     30 + status, err := process.Status()
     31 + if err != nil {
     32 + return fmt.Errorf("failed to read status for process %d: %w\n", process.PID(), err)
     33 + }
     34 + 
     35 + if status.State != proc.StateStopped {
     36 + return fmt.Errorf("process %d is not stopped", process.PID())
     37 + }
     38 + 
     39 + if err := syscall.Kill(int(process.PID()), syscall.SIGCONT); err != nil {
     40 + return fmt.Errorf("failed to resume process %d: %w\n", process.PID(), err)
     41 + }
     42 + 
     43 + _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Process %s resumed.\n", process.String())
     44 + return nil
     45 +}
     46 + 
  • ■ ■ ■ ■ ■ ■
    internal/cmd/root.go
    1 1  package cmd
    2 2   
    3 3  import (
     4 + "github.com/liamg/dismember/internal/pkg/debug"
    4 5   "github.com/spf13/cobra"
    5 6  )
     7 + 
     8 +var logger *debug.Logger
    6 9   
    7 10  var rootCmd = &cobra.Command{
    8 11   Use: "dismember",
    skipped 2 lines
    11 14   SilenceErrors: true,
    12 15   PersistentPreRun: func(cmd *cobra.Command, args []string) {
    13 16   cmd.SilenceUsage = true
     17 + if flagDebug {
     18 + logger = debug.New(cmd.ErrOrStderr())
     19 + }
    14 20   },
    15 21  }
    16 22   
     23 +var flagDebug bool
     24 + 
    17 25  func Execute() error {
     26 + rootCmd.PersistentFlags().BoolVarP(&flagDebug, "debug", "D", false, "Enable debug logging")
    18 27   return rootCmd.Execute()
    19 28  }
    20 29   
  • ■ ■ ■ ■ ■ ■
    internal/cmd/scan.go
     1 +package cmd
     2 + 
     3 +import (
     4 + "fmt"
     5 + "strings"
     6 + 
     7 + "github.com/liamg/dismember/pkg/proc"
     8 + "github.com/liamg/dismember/pkg/secrets"
     9 + "github.com/spf13/cobra"
     10 +)
     11 + 
     12 +func init() {
     13 + 
     14 + scanCmd := &cobra.Command{
     15 + Use: "scan",
     16 + Short: "Search process memory for a set of predefined secret patterns",
     17 + Long: ``,
     18 + RunE: scanHandler,
     19 + Args: cobra.ExactArgs(0),
     20 + }
     21 + 
     22 + scanCmd.Flags().IntVarP(&flagPID, "pid", "p", 0, "PID of the process whose memory should be grepped. Omitting this option will grep the memory of all available processes on the system.")
     23 + scanCmd.Flags().StringVarP(&flagProcessName, "process-name", "n", "", "Grep memory of all processes whose name contains this string.")
     24 + scanCmd.Flags().IntVarP(&flagDumpRadius, "dump-radius", "r", 2, "The number of lines of memory to dump both above and below each match.")
     25 + scanCmd.Flags().BoolVarP(&flagIncludeSelf, "self", "s", false, "Include results that are matched against the current process, or an ancestor of that process.")
     26 + scanCmd.Flags().BoolVarP(&flagFast, "fast", "f", false, "Skip memory-mapped files in order to run faster.")
     27 + rootCmd.AddCommand(scanCmd)
     28 +}
     29 + 
     30 +func scanHandler(cmd *cobra.Command, _ []string) error {
     31 + 
     32 + var processes []proc.Process
     33 + 
     34 + if flagPID == 0 {
     35 + var err error
     36 + processes, err = proc.List(false)
     37 + if err != nil {
     38 + return err
     39 + }
     40 + } else {
     41 + processes = []proc.Process{proc.Process(flagPID)}
     42 + }
     43 + 
     44 + patterns := secrets.Patterns()
     45 + 
     46 + stdErr := cmd.ErrOrStderr()
     47 + _ = stdErr
     48 + stdOut := cmd.OutOrStdout()
     49 + 
     50 + var allResults []GrepResult
     51 + 
     52 + for _, process := range processes {
     53 + if flagProcessName != "" {
     54 + status, err := process.Status()
     55 + if err != nil {
     56 + logger.Log("failed to determine status for process %s: %s", process, err)
     57 + continue
     58 + }
     59 + if !strings.Contains(status.Name, flagProcessName) {
     60 + continue
     61 + }
     62 + }
     63 + if !flagIncludeSelf && process.IsAncestor(proc.Self()) {
     64 + continue
     65 + }
     66 + for _, pattern := range patterns {
     67 + results, err := grepProcessMemory(process, pattern)
     68 + if err != nil {
     69 + logger.Log("failed to search memory process %s: %s", process, err)
     70 + continue
     71 + }
     72 + allResults = append(allResults, results...)
     73 + }
     74 + }
     75 + 
     76 + if len(allResults) == 0 {
     77 + _, _ = fmt.Fprintf(stdOut, "%sOperation Complete. No results found.%s\n\n", ansiRed, ansiReset)
     78 + } else {
     79 + for i, result := range allResults {
     80 + _, _ = fmt.Fprint(stdOut, summariseResult(i+1, result))
     81 + }
     82 + _, _ = fmt.Fprintf(stdOut, "%sOperation Complete. %s%d%s%s results found.%s\n\n", ansiGreen, ansiBold, len(allResults), ansiReset, ansiGreen, ansiReset)
     83 + }
     84 + 
     85 + return nil
     86 +}
     87 + 
  • ■ ■ ■ ■ ■ ■
    internal/cmd/status.go
    1  -package cmd
    2  - 
    3  -import (
    4  - "fmt"
    5  - "io"
    6  - "strconv"
    7  - 
    8  - "github.com/liamg/dismember/pkg/proc"
    9  - "github.com/spf13/cobra"
    10  -)
    11  - 
    12  -func init() {
    13  - rootCmd.AddCommand(&cobra.Command{
    14  - Use: "status [pid]",
    15  - Short: "Show information about the status of a process",
    16  - Long: ``,
    17  - RunE: statusHandler,
    18  - Args: cobra.ExactArgs(1),
    19  - })
    20  -}
    21  - 
    22  -func statusHandler(cmd *cobra.Command, args []string) error {
    23  - 
    24  - pid, err := strconv.Atoi(args[0])
    25  - if err != nil {
    26  - return fmt.Errorf("invalid pid specified: '%s': %w", args[0], err)
    27  - }
    28  - 
    29  - process := proc.Process(pid)
    30  - 
    31  - status, err := process.Status()
    32  - if err != nil {
    33  - return fmt.Errorf("failed to read status for process %d: %w\n", process.PID(), err)
    34  - }
    35  - 
    36  - stdOut := cmd.OutOrStdout()
    37  - 
    38  - printKeyVal(stdOut, "PID", strconv.Itoa(int(process.PID())))
    39  - printKeyVal(stdOut, "Name", status.Name)
    40  - if status.Parent != 0 {
    41  - printKeyVal(stdOut, "Parent", status.Parent.String())
    42  - } else {
    43  - printKeyVal(stdOut, "Parent", "-")
    44  - }
    45  - 
    46  - return nil
    47  -}
    48  - 
    49  -func printKeyVal(w io.Writer, key string, value string) {
    50  - _, _ = fmt.Fprintf(w, "%-20s %s\n", key, value)
    51  -}
    52  - 
  • ■ ■ ■ ■ ■ ■
    internal/cmd/suspend.go
     1 +package cmd
     2 + 
     3 +import (
     4 + "fmt"
     5 + "strconv"
     6 + "syscall"
     7 + 
     8 + "github.com/liamg/dismember/pkg/proc"
     9 + "github.com/spf13/cobra"
     10 +)
     11 + 
     12 +func init() {
     13 + rootCmd.AddCommand(&cobra.Command{
     14 + Use: "suspend [pid]",
     15 + Short: "Suspend a process using SIGSTOP (use 'dismember resume' to leave suspension)",
     16 + RunE: suspendHandler,
     17 + Args: cobra.ExactArgs(1),
     18 + })
     19 +}
     20 + 
     21 +func suspendHandler(cmd *cobra.Command, args []string) error {
     22 + 
     23 + pid, err := strconv.Atoi(args[0])
     24 + if err != nil {
     25 + return fmt.Errorf("invalid pid specified: '%s': %w", args[0], err)
     26 + }
     27 + 
     28 + process := proc.Process(pid)
     29 + 
     30 + status, err := process.Status()
     31 + if err != nil {
     32 + return fmt.Errorf("failed to read status for process %d: %w\n", process.PID(), err)
     33 + }
     34 + 
     35 + if status.State == proc.StateStopped {
     36 + return fmt.Errorf("process %d is already stopped", process.PID())
     37 + }
     38 + 
     39 + if err := syscall.Kill(int(process.PID()), syscall.SIGSTOP); err != nil {
     40 + return fmt.Errorf("failed to suspend process %d: %w\n", process.PID(), err)
     41 + }
     42 + 
     43 + _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Process %s suspended. Use 'dismember resume %d' to resume,\n", process.String(), process)
     44 + return nil
     45 +}
     46 + 
  • ■ ■ ■ ■ ■ ■
    internal/cmd/tree.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "fmt"
    5  - "github.com/liamg/dismember/pkg/proc"
    6  - "github.com/spf13/cobra"
    7 5   "io"
    8 6   "os"
     7 + 
     8 + "github.com/liamg/dismember/pkg/proc"
     9 + "github.com/spf13/cobra"
    9 10  )
    10 11   
     12 +var flagTreePID int
     13 + 
    11 14  func init() {
    12 15   treeCmd := &cobra.Command{
    13 16   Use: "tree",
    skipped 2 lines
    16 19   RunE: treeHandler,
    17 20   Args: cobra.ExactArgs(0),
    18 21   }
    19  - treeCmd.Flags().IntVarP(&flagPID, "pid", "p", 1, "PID of the process to analyse")
     22 + treeCmd.Flags().IntVarP(&flagTreePID, "pid", "p", 1, "PID of the process to analyse")
    20 23   rootCmd.AddCommand(treeCmd)
    21 24   
    22 25  }
    skipped 10 lines
    33 36   return err
    34 37   }
    35 38   
    36  - root := proc.Process(flagPID)
     39 + root := proc.Process(flagTreePID)
    37 40   status, err := root.Status()
    38 41   if err != nil {
    39 42   return err
    skipped 16 lines
    56 59   }
    57 60   
    58 61   uid := os.Getuid()
    59  - drawBranch(cmd.OutOrStdout(), rootWithStatus, "", true, all, uid)
     62 + drawBranch(cmd.OutOrStdout(), rootWithStatus, "", true, true, all, uid)
    60 63   return nil
    61 64  }
    62 65   
    63  -func drawBranch(w io.Writer, parent procWithStatus, prefix string, last bool, all []procWithStatus, uid int) {
     66 +func drawBranch(w io.Writer, parent procWithStatus, prefix string, first bool, last bool, all []procWithStatus, uid int) {
    64 67   
    65 68   var children []procWithStatus
    66 69   for _, process := range all {
    skipped 4 lines
    71 74   }
    72 75   
    73 76   _, _ = fmt.Print(ansiDim + prefix)
    74  - if prefix != "" {
     77 + if !first {
    75 78   symbol := '├'
    76 79   if last {
    77 80   symbol = '└'
    skipped 2 lines
    80 83   }
    81 84   
    82 85   _, _ = fmt.Fprint(w, ansiReset)
    83  - //owner, err := parent.process.Ownership()
    84  - //if err != nil {
    85  - //
    86  - //}
    87  - _, _ = fmt.Fprintf(w, "%s %s(%s%d%s)%s\n", parent.status.Name, ansiDim, ansiReset, parent.process, ansiDim, ansiReset)
     86 + ownerName := "uid=?"
     87 + if owner, err := parent.process.Ownership(); err == nil {
     88 + if int(owner.UID) == uid {
     89 + ownerName = ""
     90 + } else {
     91 + ownerName = fmt.Sprintf("uid=%d", owner.UID)
     92 + }
     93 + }
     94 + _, _ = fmt.Fprintf(w, "%s %s(%s%d%s)%s %s\n", parent.status.Name, ansiDim, ansiReset, parent.process, ansiDim, ansiReset, ownerName)
    88 95   _, _ = fmt.Fprint(w, ansiReset)
    89 96   
    90  - if last {
    91  - prefix += " "
    92  - } else {
    93  - prefix += " │ "
     97 + if !first {
     98 + if last {
     99 + prefix += " "
     100 + } else {
     101 + prefix += " │ "
     102 + }
    94 103   }
    95 104   
    96 105   for i, child := range children {
    97  - drawBranch(w, child, prefix, i == len(children)-1, all)
     106 + drawBranch(w, child, prefix, false, i == len(children)-1, all, uid)
    98 107   }
    99 108  }
    100 109   
  • ■ ■ ■ ■ ■ ■
    internal/pkg/debug/logger.go
     1 +package debug
     2 + 
     3 +import (
     4 + "fmt"
     5 + "io"
     6 + "strings"
     7 + "time"
     8 +)
     9 + 
     10 +const timeFormat = "04:05.000000000"
     11 + 
     12 +type Logger struct {
     13 + writer io.Writer
     14 + prefix string
     15 +}
     16 + 
     17 +func New(w io.Writer, parts ...string) *Logger {
     18 + return &Logger{
     19 + writer: w,
     20 + prefix: strings.Join(parts, "."),
     21 + }
     22 +}
     23 + 
     24 +func (l *Logger) Extend(parts ...string) *Logger {
     25 + if l == nil {
     26 + return nil
     27 + }
     28 + return &Logger{
     29 + writer: l.writer,
     30 + prefix: strings.Join(append([]string{l.prefix}, parts...), "."),
     31 + }
     32 +}
     33 + 
     34 +func (l *Logger) Log(format string, args ...interface{}) {
     35 + if l == nil || l.writer == nil {
     36 + return
     37 + }
     38 + message := fmt.Sprintf(format, args...)
     39 + line := fmt.Sprintf("%s %-32s %s\n", time.Now().Format(timeFormat), l.prefix, message)
     40 + _, _ = l.writer.Write([]byte(line))
     41 +}
     42 + 
  • ■ ■ ■ ■ ■ ■
    pkg/proc/device.go
     1 +package proc
     2 + 
     3 +// Device represents a device in the /dev directory.
     4 +type Device struct {
     5 + Major uint16
     6 + Minor uint16
     7 + Name string
     8 + Char bool
     9 +}
     10 + 
     11 +// String returns a string representation of the device.
     12 +func (d Device) String() string {
     13 + return d.Name
     14 +}
     15 + 
  • ■ ■ ■ ■ ■ ■
    pkg/proc/devices_char.go
     1 +package proc
     2 + 
     3 +import "fmt"
     4 + 
     5 +// See https://www.kernel.org/doc/html/latest/admin-guide/devices.html#linux-allocated-devices-4-x-version
     6 + 
     7 +// NewCharDeviceFromCombinedVersion creates a Device from a combined version number.
     8 +func NewCharDeviceFromCombinedVersion(v uint64) Device {
     9 + minor := ((v >> 16) & 0xff00) | (v & 0xff)
     10 + major := (v >> 8) & 0xffff
     11 + return NewCharDeviceFromVersion(uint16(major), uint16(minor))
     12 +}
     13 + 
     14 +// NewCharDeviceFromVersion creates a Device from a major and minor number.
     15 +func NewCharDeviceFromVersion(major, minor uint16) Device {
     16 + return Device{
     17 + Major: major,
     18 + Minor: minor,
     19 + Name: lookupCharDeviceName(major, minor),
     20 + Char: true,
     21 + }
     22 +}
     23 + 
     24 +func lookupCharDeviceName(major uint16, minor uint16) string {
     25 + switch major {
     26 + case 0:
     27 + return "none"
     28 + case 1:
     29 + switch minor {
     30 + case 1:
     31 + return "/dev/mem"
     32 + case 2:
     33 + return "/dev/kmem"
     34 + case 3:
     35 + return "/dev/null"
     36 + case 4:
     37 + return "/dev/port"
     38 + case 5:
     39 + return "/dev/zero"
     40 + case 6:
     41 + return "/dev/core"
     42 + case 7:
     43 + return "/dev/full"
     44 + case 8:
     45 + return "/dev/random"
     46 + case 9:
     47 + return "/dev/urandom"
     48 + case 10:
     49 + return "/dev/aio"
     50 + case 11:
     51 + return "/dev/kmsg"
     52 + case 12:
     53 + return "/dev/oldmem"
     54 + }
     55 + case 2:
     56 + switch minor {
     57 + case 255:
     58 + return "/dev/ptyef"
     59 + default:
     60 + return fmt.Sprintf("/dev/ptyp%d", minor)
     61 + }
     62 + case 3:
     63 + switch minor {
     64 + case 255:
     65 + return "/dev/ttyef"
     66 + default:
     67 + return fmt.Sprintf("/dev/ttyp%d", minor)
     68 + }
     69 + case 4:
     70 + switch {
     71 + case minor >= 64:
     72 + return fmt.Sprintf("/dev/ttyS%d", minor-64)
     73 + default:
     74 + return fmt.Sprintf("/dev/tty%d", minor)
     75 + }
     76 + case 5:
     77 + switch minor {
     78 + case 0:
     79 + return "/dev/tty"
     80 + case 1:
     81 + return "/dev/console"
     82 + case 2:
     83 + return "/dev/ptmx"
     84 + case 3:
     85 + return "/dev/ttyprintk"
     86 + default:
     87 + if minor >= 64 {
     88 + return fmt.Sprintf("/dev/cua%d", minor-64)
     89 + }
     90 + }
     91 + case 7:
     92 + switch {
     93 + case minor == 0:
     94 + return "/dev/vcs"
     95 + case minor < 64:
     96 + return fmt.Sprintf("/dev/vcs%d", minor)
     97 + case minor == 64:
     98 + return "/dev/vcsu"
     99 + case minor > 64:
     100 + return fmt.Sprintf("/dev/vcsu%d", minor-64)
     101 + }
     102 + case 136, 137, 138, 139, 140, 141, 142, 143:
     103 + return fmt.Sprintf("/dev/pts/%d", minor)
     104 + }
     105 + return fmt.Sprintf("unknown (%d/%d)", major, minor)
     106 +}
     107 + 
  • ■ ■ ■ ■ ■ ■
    pkg/proc/files.go
     1 +package proc
     2 + 
     3 +import (
     4 + "fmt"
     5 + "os"
     6 + "path/filepath"
     7 +)
     8 + 
     9 +// Files discovers a list of all files being accessed the Process.
     10 +func (p *Process) Files() ([]string, error) {
     11 + base := fmt.Sprintf("/proc/%d/fd", p.PID())
     12 + entries, err := os.ReadDir(base)
     13 + if err != nil {
     14 + return nil, err
     15 + }
     16 + var matches []string
     17 + for _, entry := range entries {
     18 + link, err := os.Readlink(filepath.Join(base, entry.Name()))
     19 + if err != nil {
     20 + continue
     21 + }
     22 + matches = append(matches, link)
     23 + }
     24 + return matches, nil
     25 +}
     26 + 
  • ■ ■ ■ ■ ■ ■
    pkg/proc/kernel.go
     1 +package proc
     2 + 
     3 +import (
     4 + "os"
     5 + "strings"
     6 +)
     7 + 
     8 +// Kernel contains information about the kernel.
     9 +type Kernel struct {
     10 + OSType string
     11 + OSRelease string
     12 + BootArgs string
     13 + FullVersion string
     14 +}
     15 + 
     16 +// GetKernelInfo returns information about the kernel.
     17 +func GetKernelInfo() Kernel {
     18 + 
     19 + var kernel Kernel
     20 + 
     21 + fullVersion, _ := os.ReadFile("/proc/version")
     22 + kernel.FullVersion = strings.TrimSpace(string(fullVersion))
     23 + 
     24 + osType, _ := os.ReadFile("/proc/sys/kernel/ostype")
     25 + kernel.OSType = strings.TrimSpace(string(osType))
     26 + 
     27 + osRelease, _ := os.ReadFile("/proc/sys/kernel/osrelease")
     28 + kernel.OSRelease = strings.TrimSpace(string(osRelease))
     29 + 
     30 + bootArgs, _ := os.ReadFile("/proc/cmdline")
     31 + kernel.BootArgs = strings.TrimSpace(string(bootArgs))
     32 + 
     33 + return kernel
     34 +}
     35 + 
  • ■ ■ ■ ■ ■ ■
    pkg/proc/map.go
    skipped 11 lines
    12 12  // address perms offset dev inode pathname
    13 13  // 08048000-08056000 r-xp 00000000 03:0c 64593 /usr/sbin/gpm
    14 14   
     15 +// Maps is a list of memory maps.
    15 16  type Maps []Map
    16 17   
     18 +// Map is a memory map.
    17 19  type Map struct {
    18 20   Address uint64
    19 21   Size uint64
    skipped 4 lines
    24 26   Path string
    25 27  }
    26 28   
     29 +// MemPerms represents memory permissions.
    27 30  type MemPerms struct {
    28 31   Readable bool
    29 32   Writable bool
    skipped 1 lines
    31 34   Shared bool
    32 35  }
    33 36   
     37 +// Maps returns the memory maps of the process.
    34 38  func (p *Process) Maps() (Maps, error) {
    35 39   data, err := p.readFile("maps")
    36 40   if err != nil {
    skipped 96 lines
  • ■ ■ ■ ■ ■
    pkg/proc/mem.go
    1 1  package proc
    2 2   
     3 +// ReadMemory reads the memory of the process for the given memory Map.
    3 4  func (p *Process) ReadMemory(m Map, offset uint64, size uint64) ([]byte, error) {
    4 5   f, err := p.openFile("mem")
    5 6   if err != nil {
    skipped 20 lines
  • ■ ■ ■ ■ ■ ■
    pkg/proc/process.go
    skipped 8 lines
    9 9   "syscall"
    10 10  )
    11 11   
     12 +// Process represents a process. The underlying data is the PID.
    12 13  type Process uint64
    13 14   
    14 15  const (
     16 + // NoProcess is a sentinel value for Process.
    15 17   NoProcess Process = 0
    16 18  )
    17 19   
     20 +// PID returns the PID of the process.
    18 21  func (p *Process) PID() uint64 {
    19 22   return uint64(*p)
    20 23  }
    21 24   
     25 +// Self returns the Process of the currently running program
    22 26  func Self() Process {
    23 27   return Process(os.Getpid())
    24 28  }
    25 29   
     30 +// Ownership represents the ownership of a process.
    26 31  type Ownership struct {
    27 32   UID uint32
    28 33   GID uint32
    29 34  }
    30 35   
     36 +// Ownership returns the ownership of the process.
    31 37  func (p *Process) Ownership() (*Ownership, error) {
    32 38   info, err := os.Stat(fmt.Sprintf("/proc/%d", *p))
    33 39   if err != nil {
    skipped 9 lines
    43 49   }, nil
    44 50  }
    45 51   
     52 +// Name returns the name of the process.
    46 53  func (p *Process) Name() string {
    47 54   status, err := p.Status()
    48 55   if err != nil || status.Name == "" {
    skipped 2 lines
    51 58   return status.Name
    52 59  }
    53 60   
     61 +// String returns the string representation of the process.
    54 62  func (p *Process) String() string {
    55 63   return fmt.Sprintf("%d (%s)", p.PID(), p.Name())
    56 64  }
    57 65   
    58 66  var pidRegex = regexp.MustCompile(`^\d+$`)
    59 67   
     68 +// List returns a list of all processes available to the current user.
    60 69  func List(includeSelf bool) ([]Process, error) {
    61 70   
    62 71   self := os.Getpid()
    skipped 32 lines
  • ■ ■ ■ ■ ■
    pkg/proc/status.go
    skipped 6 lines
    7 7   "strings"
    8 8  )
    9 9   
     10 +// State represents the state of a process.
     11 +type State uint8
     12 + 
     13 +const (
     14 + StateRunning = 'R'
     15 + StateSleeping = 'S'
     16 + StateDiskSleep = 'D'
     17 + StateStopped = 'T'
     18 + StateTracingStop = 't'
     19 + StateZombie = 'Z'
     20 + StateDead = 'X'
     21 + StateUnknown = 0
     22 +)
     23 + 
     24 +// String returns a string representation of the state.
     25 +func (s State) String() string {
     26 + switch s {
     27 + case StateRunning:
     28 + return "R (running)"
     29 + case StateSleeping:
     30 + return "S (sleeping)"
     31 + case StateDiskSleep:
     32 + return "D (disk sleep)"
     33 + case StateStopped:
     34 + return "T (stopped)"
     35 + case StateTracingStop:
     36 + return "t (tracing stop)"
     37 + case StateZombie:
     38 + return "Z (zombie)"
     39 + case StateDead:
     40 + return "X (dead)"
     41 + default:
     42 + return "? (unknown)"
     43 + }
     44 +}
     45 + 
     46 +// Status summarised data from /proc/[pid]/stat*
     47 +// fields with proc annotation are sourced from /proc/[pid]/status instead of /proc/[pid]/stat
    10 48  type Status struct {
    11  - Name string `proc:"Name"`
    12  - Parent Process `proc:"PPid"`
     49 + Name string `proc:"Name"` // Name of the command run by this process. Strings longer than TASK_COMM_LEN (16) characters (including the terminating null byte) are silently truncated.
     50 + State State // Constant derived from StateDescription
     51 + Parent Process // Parent process (0 if none)
     52 + ProcessGroup int // The process group ID
     53 + Session int // Session ID
     54 + TTY Device // The controlling terminal of the process. (The minor device number is contained in the combination of bits 31 to 20 and 7 to 0; the major device number is in bits 15 to 8.)
     55 + ForegroundTerminalProcessGroup int // The ID of the foreground process group of the controlling terminal of the process.
     56 + KernelFlags uint // The kernel flags word of the process. For bit meanings, see the PF_* defines in the Linux kernel source file include/linux/sched.h. Details depend on the kernel version.
     57 +}
     58 + 
     59 +// State returns the state of the Process.
     60 +func (p *Process) State() State {
     61 + status, err := p.Status()
     62 + if err != nil {
     63 + return StateUnknown
     64 + }
     65 + return status.State
    13 66  }
    14 67   
     68 +// Status returns the status of the Process.
    15 69  func (p *Process) Status() (*Status, error) {
    16  - data, err := p.readFile("status")
     70 + data, err := p.readFile("stat")
     71 + if err != nil {
     72 + return nil, err
     73 + }
     74 + status, err := parseStat(data)
    17 75   if err != nil {
    18 76   return nil, err
    19 77   }
    20  - return parseStatus(data)
     78 + data, err = p.readFile("status")
     79 + if err != nil {
     80 + return nil, err
     81 + }
     82 + if err := parseStatus(data, status); err != nil {
     83 + return nil, err
     84 + }
     85 + return status, nil
    21 86  }
    22 87   
    23  -func (p *Process) IsInHierarchyOf(other Process) bool {
     88 +// IsAncestor returns true if the process is an ancestor of the given process, or if the process is the same as the given process.
     89 +func (p *Process) IsAncestor(other Process) bool {
    24 90   for other != NoProcess {
    25 91   if other == *p {
    26 92   return true
    skipped 7 lines
    34 100   return false
    35 101  }
    36 102   
    37  -func parseStatus(data []byte) (*Status, error) {
    38  - var status Status
     103 +func parseStat(data []byte) (*Status, error) {
     104 + 
     105 + // prepend a blank entry, so we can use the indexes in `man proc`
     106 + fields := append([]string{""}, strings.Fields(string(data))...)
     107 + 
     108 + ppid, err := strconv.Atoi(fields[4])
     109 + if err != nil {
     110 + return nil, fmt.Errorf("invalid ppid '%s': %s", fields[4], err)
     111 + }
     112 + 
     113 + pgrp, err := strconv.Atoi(fields[5])
     114 + if err != nil {
     115 + return nil, fmt.Errorf("invalid pgrp '%s': %s", fields[5], err)
     116 + }
     117 + 
     118 + session, err := strconv.Atoi(fields[6])
     119 + if err != nil {
     120 + return nil, fmt.Errorf("invalid session '%s': %s", fields[6], err)
     121 + }
     122 + 
     123 + tty, err := strconv.Atoi(fields[7])
     124 + if err != nil {
     125 + return nil, fmt.Errorf("invalid tty '%s': %s", fields[7], err)
     126 + }
     127 + 
     128 + tpgid, err := strconv.Atoi(fields[8])
     129 + if err != nil {
     130 + return nil, fmt.Errorf("invalid tpgid '%s': %s", fields[8], err)
     131 + }
     132 + 
     133 + flags, err := strconv.Atoi(fields[9])
     134 + if err != nil {
     135 + return nil, fmt.Errorf("invalid flags '%s': %s", fields[9], err)
     136 + }
     137 + 
     138 + status := Status{
     139 + Name: fields[2],
     140 + State: State(fields[3][0]),
     141 + Parent: Process(ppid),
     142 + ProcessGroup: pgrp,
     143 + Session: session,
     144 + TTY: NewCharDeviceFromCombinedVersion(uint64(tty)),
     145 + ForegroundTerminalProcessGroup: tpgid,
     146 + KernelFlags: uint(flags),
     147 + }
     148 + 
     149 + return &status, nil
     150 +}
     151 + 
     152 +func parseStatus(data []byte, status *Status) error {
    39 153   
    40 154   values := make(map[string]string)
    41 155   for _, line := range strings.Split(string(data), "\n") {
    skipped 5 lines
    47 161   values[key] = val
    48 162   }
    49 163   
    50  - v := reflect.ValueOf(&status)
     164 + v := reflect.ValueOf(status)
    51 165   
    52 166   t := v.Elem().Type()
    53 167   for i := 0; i < t.NumField(); i++ {
    54 168   fv := t.Field(i)
    55 169   tags := strings.Split(fv.Tag.Get("proc"), ",")
    56  - tagName := fv.Name
    57  - if len(tags) > 0 {
    58  - tagName = tags[0]
     170 + if len(tags) == 0 {
     171 + continue
     172 + }
     173 + tagName := tags[0]
     174 + if tagName == "-" {
     175 + continue
    59 176   }
    60 177   value, ok := values[tagName]
    61 178   if !ok {
    skipped 2 lines
    64 181   subject := v.Elem().Field(i)
    65 182   
    66 183   if !v.Elem().CanSet() {
    67  - return nil, fmt.Errorf("target is not settable")
     184 + return fmt.Errorf("target is not settable")
    68 185   }
    69 186   
    70 187   switch subject.Kind() {
    skipped 2 lines
    73 190   case reflect.Uint64:
    74 191   u, err := strconv.Atoi(value)
    75 192   if err != nil {
    76  - return nil, err
     193 + return err
    77 194   }
    78 195   subject.SetUint(uint64(u))
    79 196   default:
    80  - return nil, fmt.Errorf("decoding of kind %s is not supported", subject.Kind())
     197 + return fmt.Errorf("decoding of kind %s is not supported", subject.Kind())
    81 198   }
    82 199   }
    83  - 
    84  - return &status, nil
     200 + return nil
    85 201  }
    86 202   
  • ■ ■ ■ ■ ■ ■
    pkg/proc/status_test.go
    1  -package proc
    2  - 
    3  -import (
    4  - "testing"
    5  - 
    6  - "github.com/stretchr/testify/assert"
    7  - "github.com/stretchr/testify/require"
    8  -)
    9  - 
    10  -func Test_Status(t *testing.T) {
    11  - tests := []struct {
    12  - name string
    13  - input string
    14  - want Status
    15  - wantErr bool
    16  - }{
    17  - {
    18  - input: `
    19  -Name: cat
    20  -Umask: 0022
    21  -State: R (running)
    22  -Tgid: 131533
    23  -Ngid: 0
    24  -Pid: 131533
    25  -PPid: 118575
    26  -TracerPid: 0
    27  -Uid: 0 0 0 0
    28  -Gid: 0 0 0 0
    29  -FDSize: 256
    30  -Groups: 0
    31  -NStgid: 131533
    32  -NSpid: 131533
    33  -NSpgid: 131533
    34  -NSsid: 118559
    35  -VmPeak: 5788 kB
    36  -VmSize: 5788 kB
    37  -VmLck: 0 kB
    38  -VmPin: 0 kB
    39  -VmHWM: 976 kB
    40  -VmRSS: 976 kB
    41  -RssAnon: 88 kB
    42  -RssFile: 888 kB
    43  -RssShmem: 0 kB
    44  -VmData: 360 kB
    45  -VmStk: 132 kB
    46  -VmExe: 16 kB
    47  -VmLib: 1668 kB
    48  -VmPTE: 48 kB
    49  -VmSwap: 0 kB
    50  -HugetlbPages: 0 kB
    51  -CoreDumping: 0
    52  -THP_enabled: 1
    53  -Threads: 1
    54  -SigQ: 1/127176
    55  -SigPnd: 0000000000000000
    56  -ShdPnd: 0000000000000000
    57  -SigBlk: 0000000000000000
    58  -SigIgn: 0000000000000000
    59  -SigCgt: 0000000000000000
    60  -CapInh: 0000000000000000
    61  -CapPrm: 000001ffffffffff
    62  -CapEff: 000001ffffffffff
    63  -CapBnd: 000001ffffffffff
    64  -CapAmb: 0000000000000000
    65  -NoNewPrivs: 0
    66  -Seccomp: 0
    67  -Seccomp_filters: 0
    68  -Speculation_Store_Bypass: thread vulnerable
    69  -SpeculationIndirectBranch: conditional enabled
    70  -Cpus_allowed: ff
    71  -Cpus_allowed_list: 0-7
    72  -Mems_allowed: 00000001
    73  -Mems_allowed_list: 0
    74  -voluntary_ctxt_switches: 1
    75  -nonvoluntary_ctxt_switches: 0
    76  -`,
    77  - want: Status{
    78  - Name: "cat",
    79  - Parent: Process(118575),
    80  - },
    81  - },
    82  - }
    83  - 
    84  - for _, test := range tests {
    85  - t.Run(test.name, func(t *testing.T) {
    86  - got, err := parseStatus([]byte(test.input))
    87  - if test.wantErr {
    88  - require.Error(t, err)
    89  - return
    90  - }
    91  - require.NoError(t, err)
    92  - assert.Equal(t, test.want, *got)
    93  - })
    94  - }
    95  -}
    96  - 
  • ■ ■ ■ ■ ■ ■
    pkg/secrets/secrets.go
     1 +package secrets
     2 + 
     3 +import (
     4 + "fmt"
     5 + "regexp"
     6 + "strings"
     7 + 
     8 + squealer "github.com/owenrumney/squealer/pkg/config"
     9 +)
     10 + 
     11 +type Pattern struct {
     12 + Regex *regexp.Regexp
     13 + Name string
     14 + Source string
     15 +}
     16 + 
     17 +func (p *Pattern) String() string {
     18 + if p.Name == "" {
     19 + return p.Regex.String()
     20 + }
     21 + return fmt.Sprintf("%s (pattern from %s)", p.Name, p.Source)
     22 +}
     23 + 
     24 +func Patterns() []Pattern {
     25 + var all []Pattern
     26 + for _, rule := range squealer.DefaultConfig().Rules {
     27 + if rule.FileFilter != "" {
     28 + continue
     29 + }
     30 + r, err := regexp.Compile(rule.Rule)
     31 + if err != nil {
     32 + continue
     33 + }
     34 + all = append(all, Pattern{
     35 + Regex: r,
     36 + Name: strings.ReplaceAll(rule.Description, "Check for ", ""),
     37 + Source: "https://github.com/owenrumney/squealer",
     38 + })
     39 + }
     40 + return all
     41 +}
     42 + 
Please wait...
Page is in error, reload to recover