Skip to content

Commit f279019

Browse files
authored
feat(kevent): Introduce image signature event parameters and rule macros (#169)
* Introduce image signature event parameters and rule macros * Surround expressions inside parenthesis * Small refactoring and additional tests for catalog checking and image processor * Address lint warnings
1 parent 59c4948 commit f279019

File tree

19 files changed

+829
-26
lines changed

19 files changed

+829
-26
lines changed

pkg/filter/accessor_windows.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,10 @@ func (i *imageAccessor) get(f fields.Field, kevt *kevent.Kevent) (kparams.Value,
640640
return kevt.Kparams.GetUint32(kparams.ImageCheckSum)
641641
case fields.ImagePID:
642642
return kevt.Kparams.GetPid()
643+
case fields.ImageSignatureType:
644+
return kevt.GetParamAsString(kparams.ImageSignatureType), nil
645+
case fields.ImageSignatureLevel:
646+
return kevt.GetParamAsString(kparams.ImageSignatureLevel), nil
643647
}
644648
return nil, nil
645649
}

pkg/filter/fields/fields_windows.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,10 @@ const (
332332
ImageName Field = "image.name"
333333
// ImagePID is the pid of the process where the image was loaded
334334
ImagePID Field = "image.pid"
335+
// ImageSignatureType represents the image signature type
336+
ImageSignatureType Field = "image.signature.type"
337+
// ImageSignatureLevel represents the image signature level
338+
ImageSignatureLevel Field = "image.signature.level"
335339

336340
// None represents the unknown field
337341
None Field = ""
@@ -497,6 +501,8 @@ var fields = map[Field]FieldInfo{
497501
ImageSize: {ImageSize, "image size", kparams.Uint32, []string{"image.size > 1024"}, nil},
498502
ImageDefaultAddress: {ImageDefaultAddress, "default image address", kparams.HexInt64, []string{"image.default.address = '7efe0000'"}, nil},
499503
ImagePID: {ImagePID, "target process identifier", kparams.Uint32, []string{"image.pid = 80"}, nil},
504+
ImageSignatureType: {ImageSignatureType, "image signature type", kparams.AnsiString, []string{"image.signature.type != 'NONE'"}, nil},
505+
ImageSignatureLevel: {ImageSignatureLevel, "image signature level", kparams.AnsiString, []string{"image.signature.level = 'AUTHENTICODE'"}, nil},
500506

501507
FileObject: {FileObject, "file object address", kparams.Uint64, []string{"file.object = 18446738026482168384"}, nil},
502508
FileName: {FileName, "full file name", kparams.UnicodeString, []string{"file.name contains 'mimikatz'"}, nil},

pkg/kevent/kparam_windows.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/rabbitstack/fibratus/pkg/util/ip"
3030
"github.com/rabbitstack/fibratus/pkg/util/key"
3131
"github.com/rabbitstack/fibratus/pkg/util/ntstatus"
32+
"github.com/rabbitstack/fibratus/pkg/util/signature"
3233
"golang.org/x/sys/windows"
3334
"net"
3435
"strconv"
@@ -328,10 +329,11 @@ func (e *Kevent) produceParams(evt *etw.EventRecord) {
328329
ktypes.UnloadImage,
329330
ktypes.ImageRundown:
330331
var (
331-
pid uint32
332-
checksum uint32
333-
defaultBase uint64
334-
filename string
332+
pid uint32
333+
checksum uint32
334+
defaultBase uint64
335+
filename string
336+
sigLevel, sigType uint8
335337
)
336338
var offset uint16
337339
imageBase := evt.ReadUint64(0)
@@ -344,6 +346,8 @@ func (e *Kevent) produceParams(evt *etw.EventRecord) {
344346
defaultBase = evt.ReadUint64(30)
345347
}
346348
if evt.Version() >= 3 {
349+
sigLevel = evt.ReadByte(28)
350+
sigType = evt.ReadByte(29)
347351
defaultBase = evt.ReadUint64(32)
348352
}
349353
switch {
@@ -363,6 +367,8 @@ func (e *Kevent) produceParams(evt *etw.EventRecord) {
363367
e.AppendParam(kparams.ImageBase, kparams.Address, imageBase)
364368
e.AppendParam(kparams.ImageSize, kparams.Uint64, imageSize)
365369
e.AppendParam(kparams.ImageFilename, kparams.FileDosPath, filename)
370+
e.AppendParam(kparams.ImageSignatureLevel, kparams.Enum, uint32(sigLevel), WithEnum(signature.Levels))
371+
e.AppendParam(kparams.ImageSignatureType, kparams.Enum, uint32(sigType), WithEnum(signature.Types))
366372
case ktypes.RegOpenKey, ktypes.RegCloseKey,
367373
ktypes.RegCreateKCB, ktypes.RegDeleteKCB,
368374
ktypes.RegKCBRundown, ktypes.RegCreateKey,

pkg/kevent/kparams/fields_windows.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ const (
127127
ImageDefaultBase = "default_address"
128128
// ImageFilename is the parameter name that denotes file name and extension of the DLL/executable image.
129129
ImageFilename = "file_name"
130+
// ImageSignatureLevel is the parameter denoting the loaded module signature level
131+
ImageSignatureLevel = "signature_level"
132+
// ImageSignatureType is the parameter denoting the loaded module signature type
133+
ImageSignatureType = "signature_type"
130134

131135
// NetSize identifies the parameter name that represents the packet size.
132136
NetSize = "size"

pkg/kstream/kstreamc_windows_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,10 @@ func TestConsumerEvents(t *testing.T) {
175175
nil,
176176
func(e *kevent.Kevent) bool {
177177
img := filepath.Join(os.Getenv("windir"), "System32", "notepad.exe")
178-
return e.IsLoadImage() && strings.EqualFold(img, e.GetParamAsString(kparams.ImageFilename))
178+
// should get a catalog-signed binary
179+
signatureType := e.GetParamAsString(kparams.ImageSignatureType)
180+
return e.IsLoadImage() && strings.EqualFold(img, e.GetParamAsString(kparams.ImageFilename)) &&
181+
signatureType == "CATALOG_CACHED"
179182
},
180183
false,
181184
},

pkg/kstream/processors/image_windows.go

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,64 @@ import (
2222
"github.com/rabbitstack/fibratus/pkg/kevent"
2323
"github.com/rabbitstack/fibratus/pkg/kevent/kparams"
2424
"github.com/rabbitstack/fibratus/pkg/ps"
25+
"github.com/rabbitstack/fibratus/pkg/util/signature"
2526
)
2627

2728
type imageProcessor struct {
28-
psnap ps.Snapshotter
29+
psnap ps.Snapshotter
30+
signatures map[uint32]*signature.Signature
2931
}
3032

3133
func newImageProcessor(psnap ps.Snapshotter) Processor {
32-
return &imageProcessor{psnap: psnap}
34+
return &imageProcessor{psnap: psnap, signatures: make(map[uint32]*signature.Signature)}
3335
}
3436

3537
func (imageProcessor) Name() ProcessorType { return Image }
3638

37-
func (i *imageProcessor) ProcessEvent(e *kevent.Kevent) (*kevent.Kevent, bool, error) {
39+
func (m *imageProcessor) ProcessEvent(e *kevent.Kevent) (*kevent.Kevent, bool, error) {
40+
if e.IsLoadImage() {
41+
// image signature parameters exhibit unreliable behaviour. Allegedly,
42+
// signature verification is not performed in certain circumstances
43+
// which leads to the core system DLL or binaries to be reported with
44+
// signature unchecked level.
45+
// To mitigate this situation, we have to manually check/verify the signature
46+
// for all unchecked signature levels
47+
level := e.Kparams.MustGetUint32(kparams.ImageSignatureLevel)
48+
if level == signature.UncheckedLevel {
49+
m.checkSignature(e)
50+
}
51+
}
3852
if e.IsUnloadImage() {
39-
return e, false, i.psnap.RemoveModule(e.Kparams.MustGetPid(), e.GetParamAsString(kparams.ImageFilename))
53+
return e, false, m.psnap.RemoveModule(e.Kparams.MustGetPid(), e.GetParamAsString(kparams.ImageFilename))
4054
}
4155
if e.IsLoadImage() {
42-
return e, false, i.psnap.AddModule(e)
56+
return e, false, m.psnap.AddModule(e)
4357
}
4458
return e, true, nil
4559
}
4660

4761
func (imageProcessor) Close() {}
62+
63+
// checkSignature consults the signature cache and if the signature
64+
// already exists for a particular image checksum, signature checking
65+
// is skipped. On the contrary, the signature verification is performed
66+
// and the cache is updated accordingly.
67+
func (m *imageProcessor) checkSignature(e *kevent.Kevent) {
68+
checksum := e.Kparams.MustGetUint32(kparams.ImageCheckSum)
69+
sign, ok := m.signatures[checksum]
70+
if !ok {
71+
filename := e.GetParamAsString(kparams.FileName)
72+
sign = signature.Check(filename)
73+
if sign == nil {
74+
return
75+
}
76+
if sign.IsSigned() {
77+
sign.Verify()
78+
}
79+
m.signatures[checksum] = sign
80+
}
81+
if sign != nil {
82+
_ = e.Kparams.SetValue(kparams.ImageSignatureType, sign.Type)
83+
_ = e.Kparams.SetValue(kparams.ImageSignatureLevel, sign.Level)
84+
}
85+
}

pkg/kstream/processors/image_windows_test.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,12 @@ import (
2323
"github.com/rabbitstack/fibratus/pkg/kevent/kparams"
2424
"github.com/rabbitstack/fibratus/pkg/kevent/ktypes"
2525
"github.com/rabbitstack/fibratus/pkg/ps"
26+
"github.com/rabbitstack/fibratus/pkg/util/signature"
27+
"github.com/stretchr/testify/assert"
2628
"github.com/stretchr/testify/mock"
2729
"github.com/stretchr/testify/require"
30+
"os"
31+
"path/filepath"
2832
"testing"
2933
)
3034

@@ -40,8 +44,11 @@ func TestImageProcessor(t *testing.T) {
4044
&kevent.Kevent{
4145
Type: ktypes.LoadImage,
4246
Kparams: kevent.Kparams{
43-
kparams.ImageFilename: {Name: kparams.ImageFilename, Type: kparams.UnicodeString, Value: "C:\\Windows\\system32\\kernel32.dll"},
44-
kparams.ProcessID: {Name: kparams.ProcessID, Type: kparams.PID, Value: uint32(1023)},
47+
kparams.ImageFilename: {Name: kparams.ImageFilename, Type: kparams.UnicodeString, Value: filepath.Join(os.Getenv("windir"), "System32", "kernel32.dll")},
48+
kparams.ProcessID: {Name: kparams.ProcessID, Type: kparams.PID, Value: uint32(1023)},
49+
kparams.ImageCheckSum: {Name: kparams.ImageCheckSum, Type: kparams.Uint32, Value: uint32(2323432)},
50+
kparams.ImageSignatureType: {Name: kparams.ImageSignatureType, Type: kparams.Enum, Value: uint32(0), Enum: signature.Types},
51+
kparams.ImageSignatureLevel: {Name: kparams.ImageSignatureLevel, Type: kparams.Enum, Value: uint32(0), Enum: signature.Levels},
4552
},
4653
},
4754
func() *ps.SnapshotterMock {
@@ -51,6 +58,9 @@ func TestImageProcessor(t *testing.T) {
5158
},
5259
func(e *kevent.Kevent, t *testing.T, psnap *ps.SnapshotterMock) {
5360
psnap.AssertNumberOfCalls(t, "AddModule", 1)
61+
// should get the signature verified
62+
assert.Equal(t, "EMBEDDED", e.GetParamAsString(kparams.ImageSignatureType))
63+
assert.Equal(t, "AUTHENTICODE", e.GetParamAsString(kparams.ImageSignatureLevel))
5464
},
5565
},
5666
{

pkg/pe/parser.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ type opts struct {
5252
parseSymbols bool
5353
parseSections bool
5454
parseResources bool
55+
parseSecurity bool
5556
sectionEntropy bool
5657
sectionMD5 bool
5758
excludedImages []string
@@ -113,6 +114,14 @@ func WithVersionResources() Option {
113114
}
114115
}
115116

117+
// WithSecurity indicates if the security directory is parsed to extract signature information
118+
// like certificates or Authenticode hashes.
119+
func WithSecurity() Option {
120+
return func(o *opts) {
121+
o.parseSecurity = true
122+
}
123+
}
124+
116125
// ParseFile parses the PE given the file system path and parser options.
117126
func ParseFile(path string, opts ...Option) (*PE, error) {
118127
return parse(path, nil, opts...)
@@ -171,7 +180,7 @@ func newParserOpts(opts opts) *peparser.Options {
171180
return &peparser.Options{
172181
DisableCertValidation: true,
173182
OmitIATDirectory: true,
174-
OmitSecurityDirectory: true,
183+
OmitSecurityDirectory: !opts.parseSecurity,
175184
OmitExceptionDirectory: true,
176185
OmitTLSDirectory: true,
177186
OmitCLRHeaderDirectory: true,
@@ -286,6 +295,10 @@ func parse(path string, data []byte, options ...Option) (*PE, error) {
286295
}
287296
}
288297

298+
if opts.parseSecurity {
299+
p.IsSigned = pe.IsSigned
300+
}
301+
289302
return p, nil
290303
}
291304

pkg/pe/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ type PE struct {
7676
Imports []string `json:"imports"`
7777
// VersionResources holds the version resources
7878
VersionResources map[string]string `json:"resources"`
79+
// IsSigned determines if the PE contains the digital signature.
80+
IsSigned bool `json:"is_signed"`
7981
}
8082

8183
// String returns the string representation of the PE metadata.

pkg/sys/syscall.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,12 @@ package sys
4141

4242
// Windows Terminal Server Functions
4343
//sys WTSQuerySessionInformationA(handle windows.Handle, sessionID uint32, klass uint8, buf **uint16, size *uint32) (err error) = wtsapi32.WTSQuerySessionInformationW
44+
45+
// Windows Trust Functions
46+
//sys WinVerifyTrust(handle windows.Handle, action *windows.GUID, data *WintrustData) (ret uint32, err error) [failretval!=0] = wintrust.WinVerifyTrust
47+
//sys CryptCatalogAdminAcquireContext(handle *windows.Handle, subsystem *windows.GUID, hashAlgorithm *uint16, hashPolicy uintptr, flags uint32) (err error) = wintrust.CryptCATAdminAcquireContext2
48+
//sys CryptCatalogAdminReleaseContext(handle windows.Handle, flags int32) (ok bool) = wintrust.CryptCATAdminReleaseContext
49+
//sys CryptCatalogAdminCalcHashFromFileHandle(handle windows.Handle, fd uintptr, size *uint32, hash uintptr, flags uint32) (err error) = wintrust.CryptCATAdminCalcHashFromFileHandle2
50+
//sys CryptCatalogAdminEnumCatalogFromHash(handle windows.Handle, hash uintptr, size uint32, flags uint32, prevCatalog *windows.Handle) (catalog windows.Handle) = wintrust.CryptCATAdminEnumCatalogFromHash
51+
//sys CryptCatalogInfoFromContext(handle windows.Handle, catalog *CatalogInfo, flags uint32) (err error) = wintrust.CryptCATCatalogInfoFromContext
52+
//sys CryptCatalogAdminReleaseCatalogContext(handle windows.Handle, info windows.Handle, flags uint32) (err error) = wintrust.CryptCATAdminReleaseCatalogContext

0 commit comments

Comments
 (0)