diff --git a/common/windows/registry/offline.go b/common/windows/registry/offline.go new file mode 100644 index 0000000..f18c6ef --- /dev/null +++ b/common/windows/registry/offline.go @@ -0,0 +1,122 @@ +// Copyright 2024 Google LLC +// +// 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 registry + +import ( + "errors" + "io" + "os" + + "www.velocidex.com/golang/regparser" +) + +var ( + errFailedToReadClassName = errors.New("failed to read class name") +) + +// OfflineRegistry wraps the regparser library to provide offline (from file) parsing of the Windows +// registry. +type OfflineRegistry struct { + registry *regparser.Registry + reader io.ReadCloser +} + +// NewFromFile creates a new offline registry abstraction from a file. +func NewFromFile(path string) (*OfflineRegistry, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + + reg, err := regparser.NewRegistry(f) + if err != nil { + f.Close() + return nil, err + } + + return &OfflineRegistry{reg, f}, nil +} + +// OpenKey open the requested registry key. +func (o *OfflineRegistry) OpenKey(path string) Key { + return &OfflineKey{o.registry.OpenKey(path)} +} + +// Close closes the underlying reader. +func (o *OfflineRegistry) Close() error { + return o.reader.Close() +} + +// OfflineKey wraps a regparser.CM_KEY_NODE to provide an implementation of the registry.Key +// interface. +type OfflineKey struct { + key *regparser.CM_KEY_NODE +} + +// Name returns the name of the key. +func (o *OfflineKey) Name() string { + return o.key.Name() +} + +// Subkeys returns the subkeys of the key. +func (o *OfflineKey) Subkeys() []Key { + var subkeys []Key + for _, subkey := range o.key.Subkeys() { + subkeys = append(subkeys, &OfflineKey{subkey}) + } + + return subkeys +} + +// ClassName returns the class name of the key. +func (o *OfflineKey) ClassName() ([]byte, error) { + // retrieve the class name offset and skip both the first block and the first 4 bytes that + // represents the size of the block. + classOffset := int64(o.key.Class()) + 4096 + 4 + classLen := o.key.ClassLength() + buffer := make([]byte, classLen) + + if n, err := o.key.Reader.ReadAt(buffer, classOffset); err != nil || n != int(classLen) { + return nil, errFailedToReadClassName + } + + return buffer, nil +} + +// Values returns the different values contained in the key. +func (o *OfflineKey) Values() []Value { + var values []Value + for _, value := range o.key.Values() { + values = append(values, &OfflineValue{value}) + } + + return values +} + +// OfflineValue wraps a regparser.CM_KEY_VALUE to provide an implementation of the registry.Value +// interface. +type OfflineValue struct { + value *regparser.CM_KEY_VALUE +} + +// Name returns the name of the value. +func (o *OfflineValue) Name() string { + return o.value.ValueName() +} + +// Data returns the data contained in the value. +func (o *OfflineValue) Data() []byte { + return o.value.ValueData().Data +} diff --git a/common/windows/registry/registry.go b/common/windows/registry/registry.go new file mode 100644 index 0000000..d99d27b --- /dev/null +++ b/common/windows/registry/registry.go @@ -0,0 +1,51 @@ +// Copyright 2024 Google LLC +// +// 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 registry provides an interface to abstract the Windows registry libraries away. +// This allows providing more functionalities to registry libraries and also provide a better means +// of testing. +package registry + +// Registry represents an open registry hive. +type Registry interface { + // OpenKey returns a Key for the given path. + OpenKey(path string) Key + + // Close closes the registry hive. + Close() error +} + +// Key represents a specific registry key. +type Key interface { + // Name returns the name of the key. + Name() string + + // ClassName returns the name of the class for the key. + ClassName() ([]byte, error) + + // Subkeys returns the subkeys of the key. + Subkeys() []Key + + // Values returns the different values of the key. + Values() []Value +} + +// Value represents a value inside a specific key. +type Value interface { + // Name returns the name of the value. + Name() string + + // Data returns the data of the value. + Data() []byte +} diff --git a/go.mod b/go.mod index 704699b..c17e014 100644 --- a/go.mod +++ b/go.mod @@ -44,6 +44,7 @@ require ( github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect github.com/containerd/ttrpc v1.2.4 // indirect github.com/containerd/typeurl/v2 v2.1.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/cli v25.0.3+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker v25.0.6+incompatible // indirect @@ -81,4 +82,5 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect + www.velocidex.com/golang/regparser v0.0.0-20240404115756-2169ac0e3c09 // indirect ) diff --git a/go.sum b/go.sum index 6564429..ab43a57 100644 --- a/go.sum +++ b/go.sum @@ -327,3 +327,5 @@ modernc.org/sqlite v1.20.3/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +www.velocidex.com/golang/regparser v0.0.0-20240404115756-2169ac0e3c09 h1:G1RWYBXP2lSzxKcrAU1YhiUlBetZ7hGIzIiWuuazvfo= +www.velocidex.com/golang/regparser v0.0.0-20240404115756-2169ac0e3c09/go.mod h1:pxSECT5mWM3goJ4sxB4HCJNKnKqiAlpyT8XnvBwkLGU= diff --git a/testing/mockregistry/mockregistry.go b/testing/mockregistry/mockregistry.go new file mode 100644 index 0000000..cda7782 --- /dev/null +++ b/testing/mockregistry/mockregistry.go @@ -0,0 +1,89 @@ +// Copyright 2024 Google LLC +// +// 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 mockregistry provides a mock implementation of the registry.Registry interface. +package mockregistry + +import ( + "errors" + + "github.com/google/osv-scalibr/common/windows/registry" +) + +var ( + errFailedToReadClassName = errors.New("failed to read class name") +) + +// MockRegistry mocks registry access. +type MockRegistry struct { + Keys map[string]registry.Key +} + +// OpenKey open the requested registry key. +func (o *MockRegistry) OpenKey(path string) registry.Key { + if key, ok := o.Keys[path]; ok { + return key + } + + return nil +} + +// Close does nothing when mocking. +func (o *MockRegistry) Close() error { + return nil +} + +// MockKey mocks a registry.Key. +type MockKey struct { + KName string + KClassName string + KSubkeys []registry.Key + KValues []registry.Value +} + +// Name returns the name of the key. +func (o *MockKey) Name() string { + return o.KName +} + +// Subkeys returns the subkeys of the key. +func (o *MockKey) Subkeys() []registry.Key { + return o.KSubkeys +} + +// ClassName returns the class name of the key. +func (o *MockKey) ClassName() ([]byte, error) { + return []byte(o.KClassName), nil +} + +// Values returns the different values contained in the key. +func (o *MockKey) Values() []registry.Value { + return o.KValues +} + +// MockValue mocks a registry.Value. +type MockValue struct { + VName string + VData []byte +} + +// Name returns the name of the value. +func (o *MockValue) Name() string { + return o.VName +} + +// Data returns the data contained in the value. +func (o *MockValue) Data() []byte { + return o.VData +}