Skip to content
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
2 changes: 1 addition & 1 deletion thorlog/common/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type LogEventMetadata struct {
Mod string `json:"module" textlog:"module"`
// The ID of the scan where this event was created.
ScanID string `json:"scan_id" textlog:"scanid,omitempty"`
// A unique ID for this finding.
// A unique ID for this event.
// The ID is transient and the same element may have different IDs across multiple scans.
GenID string `json:"event_id,omitempty" textlog:"uid,omitempty"`
// The hostname of the machine where this event was generated.
Expand Down
2 changes: 1 addition & 1 deletion thorlog/jsonschema/generateschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func main() {
Title: "ThorEvent",
OneOf: []*jsonschema.Schema{
{
Ref: "#/$defs/Finding",
Ref: "#/$defs/Assessment",
},
{
Ref: "#/$defs/Message",
Expand Down
10 changes: 5 additions & 5 deletions thorlog/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,11 @@ func TestParseEvent(t *testing.T) {
},
},
{
"JsonV3Finding",
`{"type":"THOR finding","meta":{"time":"2024-09-24T14:18:46.190394329+02:00","level":"Alert","module":"Test","scan_id":"abdc","event_id":"abdas","hostname":"aserarsd"},"message":"This is a test finding","subject":{"type":"file","path":"path/to/file"},"score":70,"reasons":[{"type":"reason","summary":"Reason 1","signature":{"score":70,"ref":null,"origin":"internal","kind":""},"matched":null}],"reason_count":0,"context":[{"object":{"type":"at job"},"relation":"","unique":false}],"log_version":"v3"}`,
&thorlog.Finding{
"JsonV3Assessment",
`{"type":"THOR assessment","meta":{"time":"2024-09-24T14:18:46.190394329+02:00","level":"Alert","module":"Test","scan_id":"abdc","event_id":"abdas","hostname":"aserarsd"},"message":"This is a test assessment","subject":{"type":"file","path":"path/to/file"},"score":70,"reasons":[{"type":"reason","summary":"Reason 1","signature":{"score":70,"ref":null,"origin":"internal","kind":""},"matched":null}],"reason_count":0,"context":[{"object":{"type":"at job"},"relation":"","unique":false}],"log_version":"v3"}`,
&thorlog.Assessment{
ObjectHeader: jsonlog.ObjectHeader{
Type: "THOR finding",
Type: "THOR assessment",
},
Meta: thorlog.LogEventMetadata{
Time: mustTime("2024-09-24T14:18:46.190394329+02:00"),
Expand All @@ -143,7 +143,7 @@ func TestParseEvent(t *testing.T) {
GenID: "abdas",
Source: "aserarsd",
},
Text: "This is a test finding",
Text: "This is a test assessment",
Subject: &thorlog.File{
ObjectHeader: jsonlog.ObjectHeader{
Type: "file",
Expand Down
2 changes: 1 addition & 1 deletion thorlog/v3/amcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ func NewAmcacheEntry() *AmcacheEntry {
}
}

func (AmcacheEntry) reportable() {}
func (AmcacheEntry) observed() {}
4 changes: 2 additions & 2 deletions thorlog/v3/antivirus.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type AntiVirusProduct struct {
Path string `json:"path" textlog:"path"`
}

func (AntiVirusProduct) reportable() {}
func (AntiVirusProduct) observed() {}

const typeAntiVirusProduct = "antivirus product"

Expand All @@ -35,7 +35,7 @@ type AntiVirusExclude struct {
Exclusion string `json:"exclusion" textlog:"exclusion"`
}

func (AntiVirusExclude) reportable() {}
func (AntiVirusExclude) observed() {}

const typeAntiVirusExclude = "antivirus exclusion"

Expand Down
2 changes: 1 addition & 1 deletion thorlog/v3/atjob.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ func NewAtJob() *AtJob {
}
}

func (AtJob) reportable() {}
func (AtJob) observed() {}
2 changes: 1 addition & 1 deletion thorlog/v3/auditlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ func NewAuditLogEntry() *AuditLogEntry {
}
}

func (AuditLogEntry) reportable() {}
func (AuditLogEntry) observed() {}
2 changes: 1 addition & 1 deletion thorlog/v3/authorizedkeys.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ func NewAuthorizedKeysEntry() *AuthorizedKeysEntry {
}
}

func (AuthorizedKeysEntry) reportable() {}
func (AuthorizedKeysEntry) observed() {}
2 changes: 1 addition & 1 deletion thorlog/v3/autorun.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type AutorunEntry struct {
OldMd5 string `json:"old_md5,omitempty" textlog:"md5_before,omitempty"`
}

func (AutorunEntry) reportable() {}
func (AutorunEntry) observed() {}

const typeAutorunEntry = "autorun entry"

Expand Down
2 changes: 1 addition & 1 deletion thorlog/v3/beaconwatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type NetworkConnectingThread struct {
Connections NetworkConnections `json:"connections" textlog:"connections"`
}

func (NetworkConnectingThread) reportable() {}
func (NetworkConnectingThread) observed() {}

func NewNetworkConnectingThread(threadId uint32, process *Process) *NetworkConnectingThread {
return &NetworkConnectingThread{
Expand Down
2 changes: 1 addition & 1 deletion thorlog/v3/crontab.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type CronJob struct {
Command string `json:"command" textlog:"command"`
}

func (CronJob) reportable() {}
func (CronJob) observed() {}

const typeCronJob = "cron job"

Expand Down
2 changes: 1 addition & 1 deletion thorlog/v3/deepdive.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type DeepDiveChunk struct {
BeaconConfig *BeaconConfig `json:"beacon_config,omitempty" textlog:"beacon,expand,omitempty"`
}

func (DeepDiveChunk) reportable() {}
func (DeepDiveChunk) observed() {}

type HexNumber uint64

Expand Down
2 changes: 1 addition & 1 deletion thorlog/v3/dnscache.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ type DnsCacheEntry struct {
IP string `json:"ip" textlog:"ip"`
}

func (DnsCacheEntry) reportable() {}
func (DnsCacheEntry) observed() {}

const typeDnsCacheEntry = "DNS cache entry"

Expand Down
2 changes: 1 addition & 1 deletion thorlog/v3/doublepulsar.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type DoublePulsarHandshake struct {
Key HexNumber `json:"key,omitempty" textlog:"key,omitempty"`
}

func (DoublePulsarHandshake) reportable() {}
func (DoublePulsarHandshake) observed() {}

const typeDoublePulsarHandshake = "DoublePulsar Handshake"

Expand Down
2 changes: 1 addition & 1 deletion thorlog/v3/ebpf.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ type EBPFAttachTarget struct {
Priority int `textlog:"priority,omitempty" json:"priority,omitempty"`
}

func (EBPFProgram) reportable() {}
func (EBPFProgram) observed() {}

const typeEbpfProgram = "eBPF program"

Expand Down
2 changes: 1 addition & 1 deletion thorlog/v3/envvar.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type EnvironmentVariable struct {
Value string `json:"value" textlog:"value"`
}

func (EnvironmentVariable) reportable() {}
func (EnvironmentVariable) observed() {}

const typeEnvironmentVariable = "environment variable"

Expand Down
98 changes: 49 additions & 49 deletions thorlog/v3/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,21 @@ import (
"golang.org/x/exp/slices"
)

// Finding is a summary of a Subject's analysis by THOR.
// This object is usually, but not necessarily suspicious; the
// Assessment is a summary of a Subject's analysis by THOR.
// The assessed object is not necessarily suspicious; the
// severity can be seen in the Score, and beyond that the
// Reasons contain further information on why this Subject is
// Reasons contain further information if this Subject is
// considered suspicious.
type Finding struct {
type Assessment struct {
jsonlog.ObjectHeader
Meta LogEventMetadata `json:"meta" textlog:",expand"`
// Text is the message THOR printed for this finding.
// This is usually a summary based on this finding's subject and level.
// Text is the message THOR printed for this assessment.
// This is usually a summary based on this assessment's subject and level.
Text string `json:"message" textlog:"message"`
// Subject is the object analysed by THOR.
Subject ReportableObject `json:"subject" textlog:",expand"`
// Subject is the object assessed by THOR.
Subject ObservedObject `json:"subject" textlog:",expand"`
// Score is a metric that combines severity and certainty. The score is always in a range of 0 to 100;
// 0 indicates that the analysis found no suspicious indicators, whereas 100 indicates very high
// 0 indicates that the assessment found no suspicious indicators, whereas 100 indicates very high
// severity and certainty.
Score int64 `json:"score" textlog:"score"`
// Reasons describes the indicators that contributed to the score.
Expand All @@ -45,84 +45,84 @@ type Finding struct {
// and a relation name of "parent", indicating that the Subject derives from this object,
// which is its parent.
EventContext Context `json:"context" textlog:",expand" jsonschema:"nullable"`
// Issues lists any problems that THOR encountered when trying to create a Finding for this analysis.
// Issues lists any problems that THOR encountered when trying to create a JSON struct for this assessment.
// This may include e.g. overly long fields that were truncated, fields that could not be rendered to JSON,
// or similar problems.
Issues []Issue `json:"issues,omitempty" textlog:"-"`
// LogVersion describes the jsonlog version that this event was created with.
LogVersion common.Version `json:"log_version"`
}

// ReportableObject can be any object type that THOR analyses, e.g. File or Process.
type ReportableObject interface {
reportable()
// ObservedObject can be any object type that THOR observes, e.g. File or Process.
type ObservedObject interface {
observed()
jsonlog.Object
}

func (f *Finding) Message() string {
return f.Text
func (a *Assessment) Message() string {
return a.Text
}

func (f *Finding) Version() common.Version {
return f.LogVersion
func (a *Assessment) Version() common.Version {
return a.LogVersion
}

func (f *Finding) Metadata() *LogEventMetadata {
return &f.Meta
func (a *Assessment) Metadata() *LogEventMetadata {
return &a.Meta
}

func (f *Finding) UnmarshalJSON(data []byte) error {
type plainFinding Finding
var rawFinding struct {
plainFinding // Embed without unmarshal method to avoid infinite recursion
Subject EmbeddedObject `json:"subject"` // EmbeddedObject is used to allow unmarshalling of the subject as a ReportableObject
func (a *Assessment) UnmarshalJSON(data []byte) error {
type plainAssessment Assessment
var rawAssessment struct {
plainAssessment // Embed without unmarshal method to avoid infinite recursion
Subject EmbeddedObject `json:"subject"` // EmbeddedObject is used to allow unmarshalling of the subject as a ObservedObject
}
if err := json.Unmarshal(data, &rawFinding); err != nil {
if err := json.Unmarshal(data, &rawAssessment); err != nil {
return err
}
subject, ok := rawFinding.Subject.Object.(ReportableObject)
subject, ok := rawAssessment.Subject.Object.(ObservedObject)
if !ok {
return fmt.Errorf("subject must implement the reportable interface")
return fmt.Errorf("subject must implement the ObservedObject interface")
}
*f = Finding(rawFinding.plainFinding) // Copy the fields from rawFinding to f
f.Subject = subject
*a = Assessment(rawAssessment.plainAssessment) // Copy the fields from rawAssessment to a
a.Subject = subject

// Resolve all references
// When the event is unmarshalled, the references are not resolved yet and only contain the JSON pointers.
// Resolve them to the actual values to be able to use them in the text log.
for i := range f.Reasons {
for j := range f.Reasons[i].StringMatches {
if f.Reasons[i].StringMatches[j].Field == nil {
for i := range a.Reasons {
for j := range a.Reasons[i].StringMatches {
if a.Reasons[i].StringMatches[j].Field == nil {
continue
}
target, err := jsonpointer.Resolve(f.Subject, f.Reasons[i].StringMatches[j].Field.ToJsonPointer())
target, err := jsonpointer.Resolve(a.Subject, a.Reasons[i].StringMatches[j].Field.ToJsonPointer())
if err != nil {
return err
}
f.Reasons[i].StringMatches[j].Field = jsonlog.NewReference(f.Subject, target)
a.Reasons[i].StringMatches[j].Field = jsonlog.NewReference(a.Subject, target)
}
}
for i := range f.Issues {
if f.Issues[i].Affected == nil {
for i := range a.Issues {
if a.Issues[i].Affected == nil {
continue
}
target, err := jsonpointer.Resolve(f, f.Issues[i].Affected.ToJsonPointer())
target, err := jsonpointer.Resolve(a, a.Issues[i].Affected.ToJsonPointer())
if err != nil {
return err
}
f.Issues[i].Affected = jsonlog.NewReference(f, target)
a.Issues[i].Affected = jsonlog.NewReference(a, target)
}
return nil
}

var _ common.Event = (*Finding)(nil)
var _ common.Event = (*Assessment)(nil)

type Context []ContextObject

// ContextObject describes a relation of an object to another.
type ContextObject struct {
Object ReportableObject `json:"object" textlog:",expand"`
// Relations describes how the object relates to the main subject of the finding.
Object ObservedObject `json:"object" textlog:",expand"`
// Relations describes how the object relates to the assessed subject.
// There may be multiple relations, e.g. if the object is both the parent and the topmost ancestor of the subject.
//
// Relations should be ordered by relevance, i.e. the most important relation should be first.
Expand All @@ -145,9 +145,9 @@ func (c *ContextObject) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &rawContextObject); err != nil {
return err
}
reportableObject, isReportable := rawContextObject.Object.Object.(ReportableObject)
reportableObject, isReportable := rawContextObject.Object.Object.(ObservedObject)
if !isReportable {
return fmt.Errorf("object of type %q must implement the reportable interface", rawContextObject.Object.Object.EmbeddedHeader().Type)
return fmt.Errorf("object of type %q must implement the ObservedObject interface", rawContextObject.Object.Object.EmbeddedHeader().Type)
}
*c = ContextObject(rawContextObject.plainContextObject) // Copy the fields from rawContextObject to c
c.Object = reportableObject
Expand Down Expand Up @@ -207,14 +207,14 @@ func (c Context) MarshalTextLog(t jsonlog.TextlogFormatter) jsonlog.TextlogEntry
return result
}

const typeFinding = "THOR finding"
const typeAssessment = "THOR assessment"

func init() { AddLogObjectType(typeFinding, &Finding{}) }
func init() { AddLogObjectType(typeAssessment, &Assessment{}) }

func NewFinding(subject ReportableObject, message string) *Finding {
return &Finding{
func NewAssessment(subject ObservedObject, message string) *Assessment {
return &Assessment{
ObjectHeader: LogObjectHeader{
Type: typeFinding,
Type: typeAssessment,
},
Text: message,
Subject: subject,
Expand All @@ -223,7 +223,7 @@ func NewFinding(subject ReportableObject, message string) *Finding {
}

// Message describes a THOR message printed during the scan.
// Unlike Finding, this does not describe an analysis' result,
// Unlike Assessment, this does not describe an analysis' result,
// but rather something about the scan itself (e.g. how many IOCs were loaded).
type Message struct {
jsonlog.ObjectHeader
Expand Down
Loading