diff --git a/pkg/kstream/processors/image_windows.go b/pkg/kstream/processors/image_windows.go index bb3c5d666..a8942e44c 100644 --- a/pkg/kstream/processors/image_windows.go +++ b/pkg/kstream/processors/image_windows.go @@ -81,33 +81,41 @@ func (imageProcessor) Close() {} // processSignature consults the signature cache and if the signature // already exists for a particular image checksum, signature checking -// is skipped. On the contrary, the signature verification is performed -// and the cache is updated accordingly. +// is skipped and only certificate info is fetched. On the contrary, +// the signature verification is performed and the cache is updated +// accordingly. func (m *imageProcessor) processSignature(e *kevent.Kevent) error { checksum := e.Kparams.MustGetUint32(kparams.ImageCheckSum) level := e.Kparams.MustGetUint32(kparams.ImageSignatureLevel) + typ := e.Kparams.MustGetUint32(kparams.ImageSignatureType) sign, ok := m.signatures[checksum] if !ok { - var opts []signature.Option var filename = e.GetParamAsString(kparams.FileName) - if level != signature.UncheckedLevel { - opts = append(opts, signature.OnlyCert()) - } - var err error - sign, err = signature.CheckWithOpts(filename, opts...) - if err != nil { - return err - } - if level == signature.UncheckedLevel && sign.IsSigned() { - sign.Verify() + if typ == signature.None { + var err error + sign, err = signature.Check(filename) + if err != nil { + return err + } + if sign.IsSigned() { + sign.Verify() + } + } else { + // signature checked, only obtain certificate info + sign = signature.Wrap(typ, level) + var err error + sign.Cert, err = signature.GetCertificate(filename) + if err != nil { + return err + } } m.signatures[checksum] = sign } - if level == signature.UncheckedLevel { - // reset signature type/level parameters - _ = e.Kparams.SetValue(kparams.ImageSignatureType, sign.Type) - _ = e.Kparams.SetValue(kparams.ImageSignatureLevel, sign.Level) - } + + // reset signature type/level parameters + _ = e.Kparams.SetValue(kparams.ImageSignatureLevel, sign.Level) + _ = e.Kparams.SetValue(kparams.ImageSignatureType, sign.Type) + // append certificate parameters if sign.HasCertificate() { e.AppendParam(kparams.ImageCertIssuer, kparams.UnicodeString, sign.Cert.Issuer) diff --git a/pkg/util/signature/signature.go b/pkg/util/signature/signature.go index 4f25ba623..f411dbd47 100644 --- a/pkg/util/signature/signature.go +++ b/pkg/util/signature/signature.go @@ -28,66 +28,46 @@ import ( var isWintrustDLLFound bool var once sync.Once -type opts struct { - onlyCert bool +// Wrap returns a new signature form signature type and level. +func Wrap(typ, level uint32) *Signature { + return &Signature{Type: typ, Level: level} } -// Option represents the option that influences signature verification/checking process -type Option func(o *opts) - -// OnlyCert indicates if only certificate info is fetched for the signature. If this -// option is set, it is assumed the signature was already checked. -func OnlyCert() Option { - return func(o *opts) { - o.onlyCert = true +// GetCertificate returns certificate details for the specific PE object. +func GetCertificate(filename string) (*pe.Cert, error) { + onceWintrustDLL() + f, err := pe.ParseFile(filename, pe.WithSecurity()) + if err != nil { + return nil, err } + if f.Cert != nil { + return f.Cert, nil + } + if !isWintrustDLLFound { + return nil, ErrWintrustUnavailable + } + // parse catalog certificate + catalog := NewCatalog() + if err := catalog.Open(filename); err != nil { + return nil, err + } + defer catalog.Close() + cert, err := catalog.ParseCertificate() + if err != nil { + return nil, err + } + return cert, nil } -// CheckWithOpts determines if the provided executable image or DLL is signed. +// Check determines if the provided executable image or DLL is signed. // It first parses the PE security directory to look for the signature // information. If the certificate is not embedded inside the PE object // then this method will try to locate the hash in the catalog file. // If OnlyCert option is specified, then only certificate information is // fetched for the particular executable image, DLL, or driver. If the function // returns a nil value, this indicates the provided image is not signed. -func CheckWithOpts(filename string, options ...Option) (*Signature, error) { - once.Do(func() { - isWintrustDLLFound = sys.IsWintrustFound() - if !isWintrustDLLFound { - log.Warn("unable to find wintrust.dll library. This will lead to " + - "PE objects signature verification to be skipped possibly " + - "causing false positive samples in detection rules relying on " + - "image signature filter fields") - } - }) - var opts opts - for _, opt := range options { - opt(&opts) - } - // the signature is assumed to be verified, so we just extract cert info - if opts.onlyCert { - f, err := pe.ParseFile(filename, pe.WithSecurity()) - if err != nil { - return nil, err - } - if f.Cert != nil { - return &Signature{filename: filename, Cert: f.Cert}, nil - } - if !isWintrustDLLFound { - return nil, ErrWintrustUnavailable - } - // parse catalog certificate - catalog := NewCatalog() - if err := catalog.Open(filename); err != nil { - return nil, err - } - cert, err := catalog.ParseCertificate() - if err != nil { - return nil, err - } - return &Signature{filename: filename, Cert: cert}, nil - } - +func Check(filename string) (*Signature, error) { + onceWintrustDLL() // check if the signature is embedded in PE f, err := pe.ParseFile(filename, pe.WithSecurity()) if err != nil { @@ -106,6 +86,7 @@ func CheckWithOpts(filename string, options ...Option) (*Signature, error) { if err := catalog.Open(filename); err != nil { return nil, err } + defer catalog.Close() if catalog.IsCatalogSigned() { cert, err := catalog.ParseCertificate() if err != nil { @@ -128,9 +109,22 @@ func (s *Signature) Verify() bool { return false } s.Level = UnsignedLevel - if s.VerifyEmbedded() || s.VerifyCatalog() { + isTrusted := s.VerifyEmbedded() || s.VerifyCatalog() + if isTrusted { s.Level = AuthenticodeLevel - return true + return isTrusted } return false } + +func onceWintrustDLL() { + once.Do(func() { + isWintrustDLLFound = sys.IsWintrustFound() + if !isWintrustDLLFound { + log.Warn("unable to find wintrust.dll library. This will lead to " + + "PE objects signature verification to be skipped possibly " + + "causing false positive samples in detection rules relying on " + + "image signature filter fields") + } + }) +} diff --git a/pkg/util/signature/signature_test.go b/pkg/util/signature/signature_test.go index 887f8a1dc..fb052bac8 100644 --- a/pkg/util/signature/signature_test.go +++ b/pkg/util/signature/signature_test.go @@ -36,7 +36,6 @@ func TestSignature(t *testing.T) { sigType uint32 sigLevel uint32 err error - opts []Option }{ { "PE embedded signature", @@ -44,7 +43,6 @@ func TestSignature(t *testing.T) { Embedded, AuthenticodeLevel, nil, - nil, }, { "catalog signature", @@ -52,7 +50,6 @@ func TestSignature(t *testing.T) { Catalog, AuthenticodeLevel, nil, - nil, }, { "unsigned binary", @@ -60,21 +57,12 @@ func TestSignature(t *testing.T) { None, UnsignedLevel, ErrNotSigned, - nil, - }, - { - "PE only cert", - filepath.Join(os.Getenv("windir"), "System32", "kernel32.dll"), - None, - AuthenticodeLevel, - nil, - []Option{OnlyCert()}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - sig, err := CheckWithOpts(tt.filename, tt.opts...) + sig, err := Check(tt.filename) assert.True(t, err == tt.err) if sig != nil { assert.Equal(t, tt.sigType, sig.Type) diff --git a/pkg/util/signature/types.go b/pkg/util/signature/types.go index 1b7afd522..15cd35a34 100644 --- a/pkg/util/signature/types.go +++ b/pkg/util/signature/types.go @@ -56,6 +56,7 @@ var Types = map[uint32]string{ 4: "CATALOG_UNCACHED", // uncached catalog verified via Catalog Database or searching catalog directly 5: "CATALOG_HINT", // successfully verified using an EA that informs CI that catalog to try first 6: "PACKAGE_CATALOG", // AppX / MSIX package catalog verified + 7: "FILE_VERIFIED", // the file was verified } // Levels enum defines all possible image signature levels at which the code was verified.