Skip to content

Commit

Permalink
v1.5.0
Browse files Browse the repository at this point in the history
  • Loading branch information
qjerome committed Apr 8, 2019
1 parent 1d7509d commit 7117e28
Show file tree
Hide file tree
Showing 19 changed files with 1,104 additions and 658 deletions.
121 changes: 120 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,132 @@ which matched the event.
}
```

# Installation

## WHIDS
1. Run `install.bat` as administrator
*. Verify that files have been created at the **installation directory**
*. With a text editor **opened as administrator** open `config.json` and modify it as you wish
*. Skip this if running with a connection to a manager. If there is nothing in the **rules directory** the tool will be useless, so make sure there are some **gene** rules in there. You can get some compiled rules [here](https://raw.githubusercontent.com/0xrawsec/gene-rules/master/compiled.gen)
*. Start the HIDS with `Start.bat` script located in **installation directory**
*. If you configured a **manager** do not forget to run it

NB: whenever you go to the installation directory with **File Explorer** and if you are **Administrator** the explorer will ask you if you want to change the permission of the directory. **DO NOT CLICK YES**, otherwise it will break the folder permissions put in place at installation time. Always access installation directory from **applications started as Administrator**.

# Configuration

## WHIDS

WHIDS configuration file example

```json
{
// Path to the rules directory used for detection
"rules-db": "C:\\Program Files\\Whids\\Database\\Rules",
// Path to the containers used in some of the rules
// containers must be GZIP compressed and have .cont.gz extension
// basename without extension is taken as container name
// Example: blacklist.cont.gz will create blacklist container
"containers-db": "C:\\Program Files\\Whids\\Database\\Containers",
// Forwarder related configuration
"forwarder": {
"client": {
// Hostname or IP address of remote manager
"host": "",
// Port used by remote manager
"port": 0,
// Protocol used http or https
"proto": "",
// Key used to authenticate the client
"key": "",
// Server key used to authenticate remote server
"server-key": "",
// Whether or not the TLS certificate should be verified
"unsafe": false,
// Maximum upload side for dump forwarding
"max-upload-size": 104857600
},
// Path where to store the alerts
"logs-dir": "C:\\Program Files\\Whids\\Logs\\Alerts",
// If local=true the forwarder won't communicate with manager
"local": true
},
// Windows event log channels to monitor
// run "whids -h" to get the list of aliases
// otherwise any Windows channel can be used here
"channels": [
"all"
],
// Dump mode: file, memory or all (can be empty)
// file: dumps anything identified as a file in the event
// memory: dumps (guilty) process memory in Windows minidump format
"dump-mode": "file",
// Dumps when criticality of the events is above or equal to treshold
"dump-treshold": 8,
// Where to store dumps
"dump-dir": "C:\\Program Files\\Whids\\Dumps",
// Whether or not to enable dump compression
"dump-compression": true,
// Log events with criticality above or equal to treshold
"criticality-treshold": 5,
// Sleep time in seconds between two rules updates (negative number means no update)
"update-interval": 60,
// Whether on not hooks should be enables
"en-hooks": true,
// Whether or not DNS-Client logging should be enabled
"en-dns": true,
// Logfile used to store WHIDS stderr
"logfile": "C:\\Program Files\\Whids\\Logs\\whids.log"
}
```

## Manager

Manager configuration example

```json
{
// Hostname / IP on which to run the manager
"host": "192.168.56.1",
// Port used by the manager
"port": 1519,
// Logfile (automatically rotated) where to store alerts received
// the logs are GZIP compressed
"logfile": "alerts.gz",
// Server authentication key (see server-key in WHIDS config)
"key": "someserverkey",
// List of authorized client keys (see key in WHIDS config)
// If the client is not authorized in this list, all the connections
// to the manager will abort
"authorized": [
"clientapikey"
],
// TLS settings
"tls": {
// Server certificate to use
"cert": "cert.pem",
// Server key to use
"key": "key.pem"
},
// Rules directory used to serve rules to the clients
"rules-dir": "",
// Rules of containers used in rules (served to the clients)
// a container name is the basename of the file without extension
// Example: /some/container/dir/blacklist.txt will take the content
// of the file and use it as being a Gene container named blacklist
"containers-dir": ""
// Directory used to store dumps sent by the client
"dump-dir": "",
}
```

# Documentation

Please visit: https://rawsec.lu/doc/gene/1.4/

# Known Issues

* Does not work properly when ran from a network share (this case prevent whids to identify itself and thus generate some noise)
* Does not work properly when ran from a network share **mapped as a network drive** (this case prevent whids to identify itself and thus generate some noise). Example: if `\\vbox\test` is mounted as `Z:` drive, running `Z:\whids.exe` **won't work** while running `\\vbox\test\whids.exe` actually would.

# Changelog

Expand Down
31 changes: 30 additions & 1 deletion collector/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
// ClientConfig structure definition
type ClientConfig struct {
Host string `json:"host"`
Port int `json:"port`
Port int `json:"port"`
Proto string `json:"proto"`
Key string `json:"key"`
ServerKey string `json:"server-key"`
Expand Down Expand Up @@ -274,6 +274,35 @@ func (m *ManagerClient) GetContainer(name string) ([]string, error) {
return ctn, nil
}

// GetContainersList retrieves the names of the containers available in the manager
func (m *ManagerClient) GetContainersList() ([]string, error) {
ctn := make([]string, 0)

if auth, _ := m.IsServerAuthenticated(); auth {
req, err := m.Prepare("GET", GetContainerListURL, nil)
if err != nil {
return ctn, fmt.Errorf("GetContainersList failed to prepare request: %s", err)
}

resp, err := m.httpClient.Do(req)
if err != nil {
return ctn, fmt.Errorf("GetContainersList failed to issue HTTP request: %s", err)
}

if resp != nil {
defer resp.Body.Close()
if resp.StatusCode != 200 {
return ctn, fmt.Errorf("Failed to retrieve containers list, unexpected HTTP status code %d", resp.StatusCode)
}
dec := json.NewDecoder(resp.Body)
if err = dec.Decode(&ctn); err != nil {
return ctn, fmt.Errorf("GetContainersList failed to decode container list")
}
}
}
return ctn, nil
}

// GetContainerSha256 retrieves a given container from the manager
func (m *ManagerClient) GetContainerSha256(name string) (string, error) {

Expand Down
86 changes: 53 additions & 33 deletions collector/forwarder.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,25 @@ func buildURI(proto, host, port, url string) string {

// ForwarderConfig structure definition
type ForwarderConfig struct {
Client ClientConfig `json:"client"`
QueueDir string `json:"queue-dir"`
Client ClientConfig `json:"manager-client"`
LogsDir string `json:"logs-dir"`
Local bool `json:"local"`
}

// Forwarder structure definition
type Forwarder struct {
sync.Mutex
Client *ManagerClient
queueDir string
stop chan bool
done chan bool
logfile *logfile.LogFile
logsDir string
stop chan bool
done chan bool
logfile *logfile.LogFile

Client *ManagerClient
TimeTresh time.Duration
EventTresh uint64
Pipe *bytes.Buffer
EventsPiped uint64
Local bool
}

// NewForwarder creates a new Forwarder structure
Expand All @@ -67,22 +69,25 @@ func NewForwarder(c *ForwarderConfig) (*Forwarder, error) {
Pipe: new(bytes.Buffer),
stop: make(chan bool),
done: make(chan bool),
Local: c.Local,
}

if co.Client, err = NewManagerClient(&c.Client); err != nil {
return nil, fmt.Errorf("Field to initialize manager client: %s", err)
if !co.Local {
if co.Client, err = NewManagerClient(&c.Client); err != nil {
return nil, fmt.Errorf("Field to initialize manager client: %s", err)
}
}

// queue directory
co.queueDir = c.QueueDir
if c.QueueDir == "" {
return nil, fmt.Errorf("Field \"queue-dir\" is missing from configuration")
co.logsDir = c.LogsDir
if c.LogsDir == "" {
return nil, fmt.Errorf("Field \"logs-dir\" is missing from configuration")
}

// creating the queue directory
if !fsutil.Exists(co.queueDir) && !fsutil.IsDir(co.queueDir) {
if !fsutil.Exists(co.logsDir) && !fsutil.IsDir(co.logsDir) {
// TOCTU may happen here so we double check error code
if err = os.Mkdir(co.queueDir, DefaultDirPerm); err != nil && !os.IsExist(err) {
if err = os.Mkdir(co.logsDir, DefaultDirPerm); err != nil && !os.IsExist(err) {
return nil, fmt.Errorf("Cannot create queue directory : %s", err)
}
}
Expand Down Expand Up @@ -122,8 +127,8 @@ func (f *Forwarder) Save() error {
}

if f.logfile == nil {
// This will reopen the first available queue.gz.X file if several
lf := filepath.Join(f.queueDir, "queue.gz")
// This will reopen the first available alerts.gz.X file if several
lf := filepath.Join(f.logsDir, "alerts.gz")
if f.logfile, err = logfile.OpenFile(lf, DefaultLogPerm, DefaultLogfileSize); err != nil {
return err
}
Expand All @@ -134,7 +139,7 @@ func (f *Forwarder) Save() error {

// HasQueuedEvents checks whether some events are waiting to be sent
func (f *Forwarder) HasQueuedEvents() bool {
for wi := range fswalker.Walk(f.queueDir) {
for wi := range fswalker.Walk(f.logsDir) {
if len(wi.Files) > 0 {
return true
}
Expand All @@ -147,18 +152,18 @@ func (f *Forwarder) CleanOlderQueued() error {
var older string
var olderTime time.Time
empty := time.Time{}
for wi := range fswalker.Walk(f.queueDir) {
for wi := range fswalker.Walk(f.logsDir) {
for _, fi := range wi.Files {
// initialization
if olderTime == empty {
olderTime = fi.ModTime()
older = filepath.Join(f.queueDir, fi.Name())
older = filepath.Join(f.logsDir, fi.Name())
continue
}
// check if we have an older file
if fi.ModTime().Before(olderTime) {
olderTime = fi.ModTime()
older = filepath.Join(f.queueDir, fi.Name())
older = filepath.Join(f.logsDir, fi.Name())
}
}
}
Expand All @@ -175,7 +180,7 @@ func (f *Forwarder) CleanOlderQueued() error {
// DiskSpaceQueue compute the disk space (in bytes) taken by queued events
func (f *Forwarder) DiskSpaceQueue() int64 {
var dp int64
for wi := range fswalker.Walk(f.queueDir) {
for wi := range fswalker.Walk(f.logsDir) {
for _, fi := range wi.Files {
dp += fi.Size()
}
Expand All @@ -188,6 +193,12 @@ func (f *Forwarder) DiskSpaceQueue() int64 {
func (f *Forwarder) ProcessQueue() {
f.Lock()
defer f.Unlock()

// if it is a local collector no need to process
if f.Local {
return
}

log.Info("Processing queued files")

if f.logfile != nil {
Expand All @@ -196,10 +207,10 @@ func (f *Forwarder) ProcessQueue() {

// Reset logfile for latter Save function use
f.logfile = nil
for wi := range fswalker.Walk(f.queueDir) {
for wi := range fswalker.Walk(f.logsDir) {
for _, fi := range wi.Files {
// fullpath
fp := filepath.Join(f.queueDir, fi.Name())
fp := filepath.Join(f.logsDir, fi.Name())
log.Debug("Processing queued file: %s", fp)
fd, err := os.Open(fp)
if err != nil {
Expand Down Expand Up @@ -242,25 +253,32 @@ func (f *Forwarder) Reset() {
f.EventsPiped = 0
}

// Send sends the piped event to the remote server
// Collect sends the piped event to the remote server
// Todo: needs update with client
func (f *Forwarder) Send() {
func (f *Forwarder) Collect() {
// Locking collector for sending data
f.Lock()
// Unlocking collector after sending data
defer f.Unlock()
// Reset the collector
defer f.Reset()

err := f.Client.PostLogs(bytes.NewBuffer(f.Pipe.Bytes()))
// if not a local forwarder
if !f.Local {
err := f.Client.PostLogs(bytes.NewBuffer(f.Pipe.Bytes()))

if err != nil {
log.Errorf("%s", err)
if err != nil {
log.Errorf("%s", err)
// Save the events in queue directory
if err := f.Save(); err != nil {
log.Errorf("Failed to save events: %s", err)
}
}
} else {
// Save the events in queue directory
if err := f.Save(); err != nil {
log.Errorf("Failed to save events: %s", err)
}
return
}
}

Expand All @@ -285,7 +303,7 @@ func (f *Forwarder) Run() {
if f.EventsPiped >= f.EventTresh || time.Now().After(timer.Add(f.TimeTresh)) {
// Send out events if there are pending events
if f.EventsPiped > 0 {
f.Send()
f.Collect()
}
// reset timer
timer = time.Now()
Expand All @@ -297,13 +315,15 @@ func (f *Forwarder) Run() {

// Close closes the forwarder properly
func (f *Forwarder) Close() {
// Close idle connections
defer f.Client.Close()
// Close idle connections if not local
if !f.Local {
defer f.Client.Close()
}
f.stop <- true
// Waiting forwarder stopped routine is done
<-f.done
if f.EventsPiped > 0 {
f.Send()
f.Collect()
}
if f.logfile != nil {
f.logfile.Close()
Expand Down
2 changes: 1 addition & 1 deletion collector/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ func (m *Manager) ContainerList(wt http.ResponseWriter, rq *http.Request) {
list = append(list, cn)
}
b, err := json.Marshal(list)
if err != nil {
if err == nil {
wt.Write(b)
} else {
log.Errorf("ContainerList handler failed to JSON encode list")
Expand Down
Loading

0 comments on commit 7117e28

Please sign in to comment.