From fc96f2fd55676d0ec4745064781618f66aaf8206 Mon Sep 17 00:00:00 2001 From: rabbitstack Date: Tue, 3 Mar 2026 18:45:16 +0100 Subject: [PATCH] fix(loldrivers): Use overlapped I/O to read driver blob --- pkg/fs/ntfs/ntfs.go | 142 ---------------------------------- pkg/fs/ntfs/ntfs_test.go | 76 ------------------ pkg/util/loldrivers/client.go | 28 +++++-- 3 files changed, 21 insertions(+), 225 deletions(-) delete mode 100644 pkg/fs/ntfs/ntfs.go delete mode 100644 pkg/fs/ntfs/ntfs_test.go diff --git a/pkg/fs/ntfs/ntfs.go b/pkg/fs/ntfs/ntfs.go deleted file mode 100644 index 4c5ba7872..000000000 --- a/pkg/fs/ntfs/ntfs.go +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2021-2022 by Nedim Sabic Sabic - * https://www.fibratus.io - * All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ntfs - -import ( - "fmt" - log "github.com/sirupsen/logrus" - "os" - "strings" - libntfs "www.velocidex.com/golang/go-ntfs/parser" -) - -// MaxFullSizeRead specifies the maximum size in bytes for the file data -const MaxFullSizeRead int64 = 1024 * 1024 * 1024 * 50 - -// FS provides raw access to Master File Table (MFT) -// and file data blobs mounted on the NTFS. -type FS struct { - dev *os.File -} - -// NewFS creates a new instance of the NTFS file system. -func NewFS() *FS { - return &FS{} -} - -// Read reads the file from the raw device at the specified offset and size. -func (fs *FS) Read(path string, offset, size int64) ([]byte, int, error) { - defer func() { - if err := recover(); err != nil { - log.Warnf("unable to read %s from raw device: %v", path, err) - } - }() - ntfs, err := fs.getNTFSContext(path) - if err != nil { - return nil, 0, err - } - defer ntfs.Close() - - data := make([]byte, size) - - // skip drive letter, semicolon and slash (e.g. C:\) - filename := strings.ReplaceAll(path[3:], "\\", "/") - reader, err := libntfs.GetDataForPath(ntfs, filename) - if err != nil { - return nil, 0, err - } - n, err := reader.ReadAt(data, offset) - return data, n, err -} - -// ReadFull reads the entire content of the file into the byte buffer. -func (fs *FS) ReadFull(path string) ([]byte, int, error) { - defer func() { - if err := recover(); err != nil { - log.Warnf("unable to read %s from raw device: %v", path, err) - } - }() - ntfs, err := fs.getNTFSContext(path) - if err != nil { - return nil, 0, err - } - defer ntfs.Close() - - // get root MFT entry - root, err := ntfs.GetMFT(5) - if err != nil { - return nil, 0, err - } - // get file size - filename := strings.ReplaceAll(path[3:], "\\", "/") - f, err := root.Open(ntfs, filename) - if err != nil { - return nil, 0, err - } - var size int64 - if stats := libntfs.Stat(ntfs, f); len(stats) > 0 { - size = stats[0].Size - } - if size == 0 { - return nil, 0, nil - } - if size > MaxFullSizeRead { - return nil, 0, nil - } - // read file - data := make([]byte, size) - reader, err := libntfs.GetDataForPath(ntfs, filename) - if err != nil { - return nil, 0, err - } - n, err := reader.ReadAt(data, 0) - return data, n, err -} - -// Close disposes all underlying resources. -func (fs *FS) Close() error { - if fs.dev != nil { - return fs.dev.Close() - } - return nil -} - -func (fs *FS) getNTFSContext(path string) (*libntfs.NTFSContext, error) { - if len(path) < 3 || path[1] != ':' { - return nil, nil - } - - // open raw device - dev := fmt.Sprintf("\\\\.\\%s", path[:2]) - var err error - fs.dev, err = os.Open(dev) - if err != nil { - return nil, err - } - - const pageSize = 0x1000 - const cacheSize = 1000 - - // create reader and NTFS context - r, err := libntfs.NewPagedReader(fs.dev, pageSize, cacheSize) - if err != nil { - return nil, err - } - return libntfs.GetNTFSContext(r, 0) -} diff --git a/pkg/fs/ntfs/ntfs_test.go b/pkg/fs/ntfs/ntfs_test.go deleted file mode 100644 index 0a0f1c37c..000000000 --- a/pkg/fs/ntfs/ntfs_test.go +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2021-2022 by Nedim Sabic Sabic - * https://www.fibratus.io - * All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ntfs - -import ( - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "os" - "path/filepath" - "testing" -) - -func TestRead(t *testing.T) { - file := filepath.Join(os.TempDir(), "ntfs-read.txt") - err := os.WriteFile(file, []byte("ntfs read"), os.ModePerm) - require.NoError(t, err) - defer os.Remove(file) - - stat, err := os.Stat(file) - require.NoError(t, err) - - fs := NewFS() - defer fs.Close() - - b, n, err := fs.Read(file, 0, stat.Size()) - assert.NotNil(t, fs) - require.NoError(t, err) - require.True(t, n != 0) - - assert.Equal(t, []byte("ntfs read"), b) - - f, err := os.OpenFile(file, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) - require.NoError(t, err) - defer f.Close() - - _, err = f.WriteString(" with a bit more of read") - require.NoError(t, err) - - b, n, err = fs.Read(file, 0, 512) - require.NoError(t, err) - require.True(t, n != 0) - assert.Equal(t, []byte("ntfs read with a bit more of read"), b[:33]) -} - -func TestReadFull(t *testing.T) { - file := filepath.Join(os.TempDir(), "ntfs-read-full.txt") - err := os.WriteFile(file, []byte("ntfs read from mars to sirius where the whales fly into oblivion"), os.ModePerm) - require.NoError(t, err) - defer os.Remove(file) - - fs := NewFS() - defer fs.Close() - - b, n, err := fs.ReadFull(file) - assert.NotNil(t, fs) - require.NoError(t, err) - require.True(t, n != 0) - - assert.Equal(t, []byte("ntfs read from mars to sirius where the whales fly into oblivion"), b) -} diff --git a/pkg/util/loldrivers/client.go b/pkg/util/loldrivers/client.go index 20893afa0..7304a0926 100644 --- a/pkg/util/loldrivers/client.go +++ b/pkg/util/loldrivers/client.go @@ -27,16 +27,17 @@ import ( "encoding/hex" "encoding/json" "fmt" - libntfs "github.com/rabbitstack/fibratus/pkg/fs/ntfs" - "github.com/rabbitstack/fibratus/pkg/util/cmdline" - log "github.com/sirupsen/logrus" "hash" "io" "net/http" + "os" "path/filepath" "strings" "sync" "time" + + "github.com/rabbitstack/fibratus/pkg/sys" + log "github.com/sirupsen/logrus" ) // apiURL represents the default loldrivers API endpoint @@ -141,6 +142,8 @@ func initClient(options ...Option) *Client { return c } +const maxDriverSize = 1_000_000 * 100 + // MatchHash receives the full path of the driver file and tries to read // the blob data from the raw device. If it succeeds, then one of the SHA1/SHA256 // hashes are computed for the read data and the calculated hash is evaluated @@ -148,14 +151,25 @@ func initClient(options ...Option) *Client { // hash calculation fail, then the driver sample name is asserted against the // dataset to determine if the driver is either malicious or vulnerable. func (c *Client) MatchHash(path string) (bool, Driver) { - ntfs := libntfs.NewFS() - defer ntfs.Close() - data, _, err := ntfs.ReadFull(cmdline.ExpandSystemRoot(path)) + f, err := os.Open(path) + if err != nil { + return c.matchPath(path) + } + defer f.Close() + stat, err := f.Stat() + if err != nil { + return c.matchPath(path) + } + if stat.Size() > maxDriverSize { + return c.matchPath(path) + } + + b, err := sys.ReadFile(path, int(stat.Size()), 1) if err != nil { return c.matchPath(path) } - r := bytes.NewReader(data) + r := bytes.NewReader(b) c.mu.Lock() defer c.mu.Unlock()