Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --processes flag to scan active process commands #469

Merged
merged 6 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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