Skip to content

Commit

Permalink
Fixed #84: Ability to config default actions on different criticality…
Browse files Browse the repository at this point in the history
… thresholds
  • Loading branch information
qjerome committed Sep 29, 2021
1 parent c3ec844 commit b559bb7
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 148 deletions.
24 changes: 24 additions & 0 deletions hids/actions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package hids

const (
// Actions
ActionKill = "kill"
ActionBlacklist = "blacklist"
ActionMemdump = "memdump"
ActionFiledump = "filedump"
ActionRegdump = "regdump"
ActionReport = "report"
ActionBrief = "brief"
)

var (
AvailableActions = []string{
ActionKill,
ActionBlacklist,
ActionMemdump,
ActionFiledump,
ActionRegdump,
ActionReport,
ActionBrief,
}
)
8 changes: 5 additions & 3 deletions hids/canary.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,11 @@ func (c *Canary) clean() {
}

// we remove directory which have been created
for _, i := range c.createdDir.Slice() {
dir := i.(string)
os.RemoveAll(dir)
if c.createdDir != nil {
for _, i := range c.createdDir.Slice() {
dir := i.(string)
os.RemoveAll(dir)
}
}
}
}
Expand Down
43 changes: 23 additions & 20 deletions hids/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,30 @@ import (
"github.com/pelletier/go-toml"
)

// DumpConfig structure definition
type DumpConfig struct {
Mode string `toml:"mode" comment:"Dump mode (choices: file, registry, memory)\n Modes can be combined together, separated by |"`
Dir string `toml:"dir" comment:"Directory used to store dumps"`
Treshold int `toml:"treshold" comment:"Dumps only when event criticality is above this threshold"`
MaxDumps int `toml:"max-dumps" comment:"Maximum number of dumps per process"` // maximum number of dump per GUID
Compression bool `toml:"compression" comment:"Enable dumps compression"`
DumpUntracked bool `toml:"dump-untracked" comment:"Dumps untracked process. Untracked processes are missing\n enrichment information and may generate unwanted dumps"` // whether or not we should dump untracked processes, if true it would create many FPs
const (
// default action lower and upper bounds
actionLowLow, actionLowHigh = 1, 4
actionMediumLow, actionMediumHigh = 5, 7
actionHighLow, actionHighHigh = 8, 9
actionCriticalLow, actionCriticalHigh = 10, 10
)

type ActionsConfig struct {
AvailableActions []string `toml:"available-actions" comment:"List of available actions (here as a memo for easier configuration, but it is not used in any way by the engine)"`
Low []string `toml:"low" comment:"Default actions to be taken when event criticality is in [1; 4]"`
Medium []string `toml:"medium" comment:"Default actions to be taken when event criticality is in [5; 7]"`
High []string `toml:"high" comment:"Default actions to be taken when event criticality is in [8; 9]"`
Critical []string `toml:"critical" comment:"Default actions to be taken when event criticality is 10"`
}

// IsModeEnabled checks if dump mode is enabled
func (d *DumpConfig) IsModeEnabled(mode string) bool {
if strings.Contains(d.Mode, "all") {
return true
}
return strings.Contains(d.Mode, mode)
// DumpConfig structure definition
type DumpConfig struct {
//Mode string `toml:"mode" comment:"Dump mode (choices: file, registry, memory)\n Modes can be combined together, separated by |"`
Dir string `toml:"dir" comment:"Directory used to store dumps"`
//Treshold int `toml:"treshold" comment:"Dumps only when event criticality is above this threshold"`
MaxDumps int `toml:"max-dumps" comment:"Maximum number of dumps per process"` // maximum number of dump per GUID
Compression bool `toml:"compression" comment:"Enable dumps compression"`
DumpUntracked bool `toml:"dump-untracked" comment:"Dumps untracked process. Untracked processes are missing\n enrichment information and may generate unwanted dumps"` // whether or not we should dump untracked processes, if true it would create many FPs
}

// SysmonConfig holds Sysmon related configuration
Expand Down Expand Up @@ -111,6 +119,7 @@ type Config struct {
EtwConfig *EtwConfig `toml:"etw" comment:"ETW configuration"`
FwdConfig *api.ForwarderConfig `toml:"forwarder" comment:"Forwarder configuration"`
Sysmon *SysmonConfig `toml:"sysmon" comment:"Sysmon related settings"`
Actions *ActionsConfig `toml:"actions" comment:"Default actions to apply to events, depending on their criticality"`
Dump *DumpConfig `toml:"dump" comment:"Dump related settings"`
Report *ReportConfig `toml:"reporting" comment:"Reporting related settings"`
RulesConfig *RulesConfig `toml:"rules" comment:"Gene rules related settings\n Gene repo: https://github.com/0xrawsec/gene\n Gene rules repo: https://github.com/0xrawsec/gene-rules"`
Expand All @@ -130,12 +139,6 @@ func LoadsHIDSConfig(path string) (c Config, err error) {
return
}

// IsDumpEnabled returns true if any kind of dump is enabled
func (c *Config) IsDumpEnabled() bool {
// Dump can be enabled only in endpoint mode
return c.Endpoint && (c.Dump.IsModeEnabled("file") || c.Dump.IsModeEnabled("registry") || c.Dump.IsModeEnabled("memory"))
}

// IsForwardingEnabled returns true if a forwarder is actually configured to forward logs
func (c *Config) IsForwardingEnabled() bool {
return *c.FwdConfig != emptyForwarderConfig && !c.FwdConfig.Local
Expand Down
36 changes: 18 additions & 18 deletions hids/hids.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,21 @@ type HIDS struct {
PrintAll bool
}

func newActionnableEngine() (e *engine.Engine) {
func newActionnableEngine(c *Config) (e *engine.Engine) {
e = engine.NewEngine(false)
e.ShowActions = true
if c.Actions.Low != nil {
e.SetDefaultActions(actionLowLow, actionLowHigh, c.Actions.Low)
}
if c.Actions.Medium != nil {
e.SetDefaultActions(actionMediumLow, actionMediumHigh, c.Actions.Medium)
}
if c.Actions.High != nil {
e.SetDefaultActions(actionHighLow, actionHighHigh, c.Actions.High)
}
if c.Actions.Critical != nil {
e.SetDefaultActions(actionCriticalLow, actionCriticalHigh, c.Actions.Critical)
}
return
}

Expand Down Expand Up @@ -191,25 +203,13 @@ func (h *HIDS) initHooks(advanced bool) {
// Experimental
//h.preHooks.Hook(hookSetValueSize, fltRegSetValue)

// Registering post detection hooks
// if endpoint we enable dump features
if h.config.Endpoint {
if h.config.Dump.IsModeEnabled("registry") {
h.postHooks.Hook(hookDumpRegistry, fltRegSetValue)
}
if h.config.Dump.IsModeEnabled("file") {
h.postHooks.Hook(hookDumpFiles, fltAnySysmon)
}
if h.config.Dump.IsModeEnabled("memory") {
h.postHooks.Hook(hookDumpProcess, fltAnySysmon)
}
}

// This hook must run before action handling as we want
// the gene score to be set before an eventual reporting
h.postHooks.Hook(hookUpdateGeneScore, fltAnyEvent)
// Handles actions defined in rules for any Sysmon event
h.postHooks.Hook(hookHandleActions, fltAnyEvent)
if h.config.Endpoint {
h.postHooks.Hook(hookHandleActions, fltAnyEvent)
}
}
}

Expand Down Expand Up @@ -240,7 +240,7 @@ func (h *HIDS) updateEngine(force bool) error {
log.Debugf("reloading rules:%t containers:%t forced:%t", reloadRules, reloadContainers, force)
if reloadRules || reloadContainers || force {
// We need to create a new engine if we received a rule/containers update
h.Engine = newActionnableEngine()
h.Engine = newActionnableEngine(h.config)

// containers must be loaded before the rules anyway
log.Infof("Loading HIDS containers (used in rules) from: %s", h.config.RulesConfig.ContainersDB)
Expand Down Expand Up @@ -561,7 +561,7 @@ func (h *HIDS) updateRoutine() bool {
}

func (h *HIDS) uploadRoutine() bool {
if h.config.IsDumpEnabled() && h.config.IsForwardingEnabled() {
if h.config.IsForwardingEnabled() {
// force compression in this case
h.config.Dump.Compression = true
go func() {
Expand Down
124 changes: 26 additions & 98 deletions hids/hookdefs.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,6 @@ const (
nullGUID = "{00000000-0000-0000-0000-000000000000}"
)

const (
// Actions
ActionKill = "kill"
ActionBlacklist = "blacklist"
ActionMemdump = "memdump"
ActionFiledump = "filedump"
ActionRegdump = "regdump"
ActionReport = "report"
ActionBrief = "brief"
)

var (
selfPath, _ = filepath.Abs(os.Args[0])
)
Expand Down Expand Up @@ -430,20 +419,24 @@ func hookSelfGUID(h *HIDS, e *event.EdrEvent) {
// Check parent image first because we launch whids.exe -h to test process termination
// and we catch it up if we check image first
if pimage, ok := e.GetString(pathSysmonParentImage); ok {
if pimage == selfPath {
if pguid, ok := e.GetString(pathSysmonParentProcessGUID); ok {
h.guid = pguid
log.Infof("Found self GUID from PGUID: %s", h.guid)
return
if ppid, ok := e.GetInt(pathSysmonParentProcessId); ok {
if pimage == selfPath && ppid == int64(os.Getpid()) {
if pguid, ok := e.GetString(pathSysmonParentProcessGUID); ok {
h.guid = pguid
log.Infof("Found self GUID from PGUID: %s", h.guid)
return
}
}
}
}
if image, ok := e.GetString(pathSysmonImage); ok {
if image == selfPath {
if guid, ok := e.GetString(pathSysmonProcessGUID); ok {
h.guid = guid
log.Infof("Found self GUID: %s", h.guid)
return
if pid, ok := e.GetInt(pathSysmonProcessId); ok {
if image == selfPath && pid == int64(os.Getpid()) {
if guid, ok := e.GetString(pathSysmonProcessGUID); ok {
h.guid = guid
log.Infof("Found self GUID: %s", h.guid)
return
}
}
}
}
Expand Down Expand Up @@ -867,28 +860,13 @@ func dumpPrepareDumpFilename(e *event.EdrEvent, dir, guid, filename string) stri
return filepath.Join(tmpDumpDir, filename)
}

func hookDumpProcess(h *HIDS, e *event.EdrEvent) {
// We have to check that if we are handling one of
// our event and we don't want to dump ourself
if h.IsHIDSEvent(e) {
return
}

// we dump only if alert is relevant
if getCriticality(e) < h.config.Dump.Treshold {
return
}
// this hook can run async
func dumpProcessRtn(h *HIDS, e *event.EdrEvent) {

// if memory got already dumped
if hasAction(e, ActionMemdump) {
if h.IsHIDSEvent(e) {
return
}

dumpProcessRtn(h, e)
}

// this hook can run async
func dumpProcessRtn(h *HIDS, e *event.EdrEvent) {
// make it non blocking
go func() {
h.hookSemaphore.Acquire()
Expand All @@ -901,7 +879,6 @@ func dumpProcessRtn(h *HIDS, e *event.EdrEvent) {
if guid = srcGUIDFromEvent(e); guid != nullGUID {
// check if we should go on
if !h.processTracker.CheckDumpCountOrInc(guid, h.config.Dump.MaxDumps, h.config.Dump.DumpUntracked) {
log.Warnf("Not dumping, reached maximum dumps count for guid %s", guid)
return
}

Expand All @@ -914,36 +891,20 @@ func dumpProcessRtn(h *HIDS, e *event.EdrEvent) {
}()
}

func hookDumpRegistry(h *HIDS, e *event.EdrEvent) {
// We have to check that if we are handling one of
// our event and we don't want to dump ourself
if h.IsHIDSEvent(e) {
return
}

// we dump only if alert is relevant
if getCriticality(e) < h.config.Dump.Treshold {
return
}
func dumpRegistryRtn(h *HIDS, e *event.EdrEvent) {

// if registry got already dumped
if hasAction(e, ActionRegdump) {
// we don't go further if not a registry set value event
if e.EventID() != SysmonRegSetValue || h.IsHIDSEvent(e) {
return
}

dumpRegistryRtn(h, e)
}

func dumpRegistryRtn(h *HIDS, e *event.EdrEvent) {
// make it non blocking
go func() {
h.hookSemaphore.Acquire()
defer h.hookSemaphore.Release()
if guid, ok := e.GetString(pathSysmonProcessGUID); ok {

// check if we should go on
if !h.processTracker.CheckDumpCountOrInc(guid, h.config.Dump.MaxDumps, h.config.Dump.DumpUntracked) {
log.Warnf("Not dumping, reached maximum dumps count for guid %s", guid)
return
}

Expand Down Expand Up @@ -971,7 +932,6 @@ func dumpRegistryRtn(h *HIDS, e *event.EdrEvent) {
}
}
}
log.Errorf("Failed to dump registry from event")
}()
}

Expand Down Expand Up @@ -1027,29 +987,13 @@ func dumpParentCommandLine(h *HIDS, e *event.EdrEvent, dumpPath string) {
}
}

func hookDumpFiles(h *HIDS, e *event.EdrEvent) {
// We have to check that if we are handling one of
// our event and we don't want to dump ourself
if h.IsHIDSEvent(e) {
return
}

// we dump only if alert is relevant
if getCriticality(e) < h.config.Dump.Treshold {
return
}
func dumpFilesRtn(h *HIDS, e *event.EdrEvent) {
var err error

// if file got already dumped
if hasAction(e, ActionFiledump) {
if h.IsHIDSEvent(e) {
return
}

dumpFilesRtn(h, e)
}

func dumpFilesRtn(h *HIDS, e *event.EdrEvent) {
var err error

// make it non blocking
go func() {
h.hookSemaphore.Acquire()
Expand All @@ -1058,7 +1002,6 @@ func dumpFilesRtn(h *HIDS, e *event.EdrEvent) {

// check if we should go on
if !h.processTracker.CheckDumpCountOrInc(guid, h.config.Dump.MaxDumps, h.config.Dump.DumpUntracked) {
log.Warnf("Not dumping, reached maximum dumps count for guid %s", guid)
return
}

Expand Down Expand Up @@ -1156,27 +1099,12 @@ func dumpFilesRtn(h *HIDS, e *event.EdrEvent) {
}()
}

/*func hookDumpReport(h *HIDS, e *event.EdrEvent) {
// We have to check that if we are handling one of
// our event and we don't want to dump ourself
if h.IsHIDSEvent(e) {
return
}
// we dump only if alert is relevant
if getCriticality(e) < h.config.Dump.Treshold {
return
}
func dumpReportRtn(h *HIDS, e *event.EdrEvent, light bool) {

// if file got already dumped
if hasAction(e, ActionReport) {
if h.IsHIDSEvent(e) {
return
}

dumpReportRtn(h, e)
}*/

func dumpReportRtn(h *HIDS, e *event.EdrEvent, light bool) {
// make it non blocking
go func() {
h.hookSemaphore.Acquire()
Expand All @@ -1187,9 +1115,9 @@ func dumpReportRtn(h *HIDS, e *event.EdrEvent, light bool) {

// check if we should go on
if !h.processTracker.CheckDumpCountOrInc(guid, h.config.Dump.MaxDumps, h.config.Dump.DumpUntracked) {
log.Warnf("Not dumping, reached maximum dumps count for guid %s", guid)
return
}

reportPath := dumpPrepareDumpFilename(e, h.config.Dump.Dir, guid, "report.json")
dumpEventAndCompress(h, e, guid)
if c.EnableReporting {
Expand Down
Loading

0 comments on commit b559bb7

Please sign in to comment.