Skip to content

Commit

Permalink
Add --processes flag to scan active process commands (#469)
Browse files Browse the repository at this point in the history
* Add --processes flag to scan active process commands

Signed-off-by: egibs <[email protected]>

* Fix Linux ps command

Signed-off-by: egibs <[email protected]>

* Avoid generating a report for malcontent when running a scan

Signed-off-by: egibs <[email protected]>

* Use gopsutil instead of parsing ps

Signed-off-by: egibs <[email protected]>

* Appease the linter

Signed-off-by: egibs <[email protected]>

* Re-add unique path functionality

Signed-off-by: egibs <[email protected]>

---------

Signed-off-by: egibs <[email protected]>
  • Loading branch information
egibs authored Sep 25, 2024
1 parent b9d01fd commit 473356a
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 12 deletions.
8 changes: 8 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/hillu/go-yara/v4 v4.3.3
github.com/liamg/magic v0.0.1
github.com/olekukonko/tablewriter v0.0.5
github.com/shirou/gopsutil/v4 v4.24.8
github.com/ulikunitz/xz v0.5.12
github.com/urfave/cli/v2 v2.27.4
github.com/wk8/go-ordered-map/v2 v2.1.8
Expand All @@ -27,8 +28,10 @@ require (
github.com/docker/cli v27.1.2+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/kr/pretty v0.2.1 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
Expand All @@ -37,10 +40,15 @@ require (
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/vbatts/tar-split v0.11.5 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/sys v0.25.0 // indirect
)
28 changes: 26 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZ
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-containerregistry v0.20.2 h1:B1wPJ1SN/S7pB+ZAimcciVD+r+yV/l/DSArMxlbwseo=
Expand All @@ -37,6 +40,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/liamg/magic v0.0.1 h1:Ru22ElY+sCh6RvRTWjQzKKCxsEco8hE0co8n1qe7TBM=
github.com/liamg/magic v0.0.1/go.mod h1:yQkOmZZI52EA+SQ2xyHpVw8fNvTBruF873Y+Vt6S+fk=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
Expand All @@ -59,17 +64,29 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil/v4 v4.24.8 h1:pVQjIenQkIhqO81mwTaXjTzOMT7d3TZkf43PlVFHENI=
github.com/shirou/gopsutil/v4 v4.24.8/go.mod h1:wE0OrJtj4dG+hYkxqDH3QiBICdKSf04/npcvLLc/oRg=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8=
Expand All @@ -80,15 +97,22 @@ github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
Expand Down
49 changes: 46 additions & 3 deletions malcontent.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// SPDX-License-Identifier: Apache-2.0

// malcontent returns information about a file's capabilities
//
//nolint:cyclop // ignore complexity of 40
package main

import (
Expand Down Expand Up @@ -78,6 +80,7 @@ var riskMap = map[string]int{
"critical": 4,
}

//nolint:cyclop // ignore complexity of 40
func main() {
returnCode := ExitOK
defer func() { os.Exit(returnCode) }()
Expand Down Expand Up @@ -355,6 +358,11 @@ func main() {
Value: "",
Usage: "Scan an image",
},
&cli.BoolFlag{
Name: "processes",
Value: false,
Usage: "Scan the commands (paths) of running processes",
},
},
Action: func(c *cli.Context) error {
// Handle edge cases
Expand All @@ -363,9 +371,24 @@ func main() {
switch {
case c.String("image") != "":
mc.OCI = true
case c.String("image") == "":
case c.String("image") == "" && !c.Bool("processes"):
cmdArgs := c.Args().Slice()
mc.ScanPaths = []string{cmdArgs[0]}
case c.Bool("processes"):
mc.Processes = true
}

// When scanning processes, load all of the valid commands (paths)
// and store them as the ScanPaths
if mc.Processes {
processPaths, err := action.GetAllProcessPaths(ctx)
if err != nil {
returnCode = ExitActionFailed
return err
}
for _, p := range processPaths {
mc.ScanPaths = append(mc.ScanPaths, p.Path)
}
}

res, err = action.Scan(ctx, mc)
Expand Down Expand Up @@ -415,6 +438,11 @@ func main() {
Value: "",
Usage: "Scan an image",
},
&cli.BoolFlag{
Name: "processes",
Value: false,
Usage: "Scan the commands (paths) of running processes",
},
},
Action: func(c *cli.Context) error {
mc.Scan = true
Expand All @@ -424,9 +452,24 @@ func main() {
switch {
case c.String("image") != "":
mc.OCI = true
case c.String("image") == "":
case c.String("image") == "" && !c.Bool("processes"):
cmdArgs := c.Args().Slice()
mc.ScanPaths = []string{cmdArgs[0]}
case c.Bool("processes"):
mc.Processes = true
}

// When scanning processes, load all of the valid commands (paths)
// and store them as the ScanPaths
if mc.Processes {
processPaths, err := action.GetAllProcessPaths(ctx)
if err != nil {
returnCode = ExitActionFailed
return err
}
for _, p := range processPaths {
mc.ScanPaths = append(mc.ScanPaths, p.Path)
}
}

res, err = action.Scan(ctx, mc)
Expand All @@ -444,7 +487,7 @@ func main() {
}

if res.Files.Len() > 0 {
fmt.Fprintf(os.Stderr, "\n\ntip: For detailed analysis, run: mal analyze <path>\n")
fmt.Fprintf(os.Stderr, "\ntip: For detailed analysis, run: mal analyze <path>\n")
}

return nil
Expand Down
54 changes: 54 additions & 0 deletions pkg/action/process.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package action

import (
"context"
"os"

"github.com/shirou/gopsutil/v4/process"
)

type Process struct {
PID int32
Path string
}

// GetAllProcessPaths is an exported function that returns a slice of Process PIDs and commands (path).
func GetAllProcessPaths(ctx context.Context) ([]Process, error) {
// Retrieve all of the active PIDs
procs, err := process.ProcessesWithContext(ctx)
if err != nil {
return nil, err
}

// Store PIDs and their respective commands (paths) in a map of paths and their Process structs
processMap := make(map[string]Process)
for _, p := range procs {
path, err := p.Exe()
if err != nil {
return nil, err
}
if _, exists := processMap[path]; !exists && path != "" && isValidPath(path) {
processMap[path] = Process{
PID: p.Pid,
Path: path,
}
}
}

return procMapSlice(processMap), nil
}

// procMapSlice converts a map of paths and their Process structs to a slice of Processes.
func procMapSlice(m map[string]Process) []Process {
result := make([]Process, 0, len(m))
for _, v := range m {
result = append(result, v)
}
return result
}

// isValidPath checks if the given path is valid.
func isValidPath(path string) bool {
_, err := os.Stat(path)
return err == nil
}
2 changes: 1 addition & 1 deletion pkg/action/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ func recursiveScan(ctx context.Context, c malcontent.Config) (*malcontent.Report

// Add the sorted paths and file reports to the parent report and render the results
for _, k := range pathKeys {
finding, ok := scanPathFindings.Load(k)
finding, ok := scanPathFindings.LoadAndDelete(k)
if !ok {
return nil, fmt.Errorf("could not load finding from sync map")
}
Expand Down
1 change: 1 addition & 0 deletions pkg/malcontent/malcontent.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type Config struct {
MinRisk int
OCI bool
Output io.Writer
Processes bool
QuantityIncreasesRisk bool
Renderer Renderer
Rules *yara.Rules
Expand Down
6 changes: 3 additions & 3 deletions pkg/render/terminal_brief.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ func (r TerminalBrief) File(_ context.Context, fr *malcontent.FileReport) error

reasons := []string{}
for _, b := range fr.Behaviors {
reasons = append(reasons, fmt.Sprintf("%s %s%s%s", color.HiYellowString(b.ID), color.HiBlackString("("), b.Description, color.HiBlackString(")")))
reasons = append(reasons, fmt.Sprintf("%s %s%s%s\n", color.HiYellowString(b.ID), color.HiBlackString("("), b.Description, color.HiBlackString(")")))
}

fmt.Fprintf(r.w, "%s%s%s %s: %s", color.HiBlackString("["), briefRiskColor(fr.RiskLevel), color.HiBlackString("]"), color.HiGreenString(fr.Path),
strings.Join(reasons, color.HiBlackString(", ")))
fmt.Fprintf(r.w, "%s%s%s %s: \n%s%s\n", color.HiBlackString("["), briefRiskColor(fr.RiskLevel), color.HiBlackString("]"), color.HiGreenString(fr.Path),
color.HiBlackString("- "), strings.Join(reasons, color.HiBlackString("- ")))
return nil
}

Expand Down
9 changes: 6 additions & 3 deletions pkg/report/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,13 +352,13 @@ func Generate(ctx context.Context, path string, mrs yara.MatchRules, c malconten
}
riskCounts[risk]++
// The malcontent rule is classified as harmless
// This will prevent the rule from being filtered
// A !ignoreMalcontent condition will prevent the rule from being filtered
// If running a scan as opposed to an analyze,
// drop any matches that fall below the highest risk
switch {
case risk < minScore && !ignoreMalcontent:
continue
case c.Scan && risk < highestRisk:
case c.Scan && risk < highestRisk && !ignoreMalcontent:
continue
}
key = generateKey(m.Namespace, m.Rule)
Expand Down Expand Up @@ -477,7 +477,10 @@ func Generate(ctx context.Context, path string, mrs yara.MatchRules, c malconten
// TODO: If we match multiple rules within a single namespace, merge matchstrings
}

if all(ignoreSelf, fr.IsMalcontent, ignoreMalcontent, filepath.Base(path) == "mal") {
// Check for both the full and shortened variants of malcontent
isMalBinary := (filepath.Base(path) == NAME || filepath.Base(path) == "mal")

if all(ignoreSelf, fr.IsMalcontent, ignoreMalcontent, isMalBinary) {
return malcontent.FileReport{}, nil
}

Expand Down

0 comments on commit 473356a

Please sign in to comment.