├── .gitignore ├── Gopkg.lock ├── Gopkg.toml ├── LICENSE ├── Makefile ├── appcompatcache ├── Makefile ├── README.md ├── appcompatcache_gen.go ├── appcompatcache_test.go ├── conversion.spec.yaml ├── entries.go ├── fixtures │ ├── appcompatcache_Win10.bin.golden │ ├── appcompatcache_Win10Creators.bin.golden │ ├── appcompatcache_Win80.bin.golden │ └── appcompatcache_Win81.bin.golden ├── profile_vtypes.json ├── structs.go └── test_data │ ├── ReadMe.txt │ ├── Win10.bin │ ├── Win10Creators.bin │ ├── Win2k8Standard.bin │ ├── Win80.bin │ ├── Win81.bin │ ├── WinXPx86.bin │ ├── win7x64.bin │ └── win7x86.bin ├── cmd ├── appcompatcache.go └── regparser.go ├── constants.go ├── conversion.spec.yaml ├── debug.go ├── doc.go ├── go.mod ├── go.sum ├── helpers.go ├── hivelog_ext.go ├── paths.go ├── profile_vtypes.json ├── registry.go ├── regparser_gen.go └── testdata └── NTUSER.DAT /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | vendor/ 3 | *.exe 4 | regparser 5 | cmd/cmd -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | branch = "master" 6 | digest = "1:315c5f2f60c76d89b871c73f9bd5fe689cad96597afd50fb9992228ef80bdd34" 7 | name = "github.com/alecthomas/template" 8 | packages = [ 9 | ".", 10 | "parse", 11 | ] 12 | pruneopts = "UT" 13 | revision = "a0175ee3bccc567396460bf5acd36800cb10c49c" 14 | 15 | [[projects]] 16 | branch = "master" 17 | digest = "1:c198fdc381e898e8fb62b8eb62758195091c313ad18e52a3067366e1dda2fb3c" 18 | name = "github.com/alecthomas/units" 19 | packages = ["."] 20 | pruneopts = "UT" 21 | revision = "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a" 22 | 23 | [[projects]] 24 | digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec" 25 | name = "github.com/davecgh/go-spew" 26 | packages = ["spew"] 27 | pruneopts = "UT" 28 | revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" 29 | version = "v1.1.1" 30 | 31 | [[projects]] 32 | digest = "1:c06d9e11d955af78ac3bbb26bd02e01d2f61f689e1a3bce2ef6fb683ef8a7f2d" 33 | name = "gopkg.in/alecthomas/kingpin.v2" 34 | packages = ["."] 35 | pruneopts = "UT" 36 | revision = "947dcec5ba9c011838740e680966fd7087a71d0d" 37 | version = "v2.2.6" 38 | 39 | [solve-meta] 40 | analyzer-name = "dep" 41 | analyzer-version = 1 42 | input-imports = [ 43 | "github.com/davecgh/go-spew/spew", 44 | "gopkg.in/alecthomas/kingpin.v2", 45 | ] 46 | solver-name = "gps-cdcl" 47 | solver-version = 1 48 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [[constraint]] 29 | name = "github.com/davecgh/go-spew" 30 | version = "1.1.1" 31 | 32 | [[constraint]] 33 | name = "gopkg.in/alecthomas/kingpin.v2" 34 | version = "2.2.6" 35 | 36 | [prune] 37 | go-tests = true 38 | unused-packages = true 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2018 velocidex 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | go build -o ./cmd/regparser ./cmd 3 | 4 | generate: 5 | binparsegen ./conversion.spec.yaml > regparser_gen.go 6 | gofmt -w regparser_gen.go 7 | -------------------------------------------------------------------------------- /appcompatcache/Makefile: -------------------------------------------------------------------------------- 1 | generate: 2 | binparsegen conversion.spec.yaml > appcompatcache_gen.go 3 | -------------------------------------------------------------------------------- /appcompatcache/README.md: -------------------------------------------------------------------------------- 1 | 2 | Code was generated using the binparsegen project: 3 | https://github.com/Velocidex/binparsergen 4 | 5 | References: 6 | https://github.com/libyal/winreg-kb/blob/master/documentation/Application%20Compatibility%20Cache%20key.asciidoc 7 | -------------------------------------------------------------------------------- /appcompatcache/appcompatcache_gen.go: -------------------------------------------------------------------------------- 1 | 2 | package appcompatcache 3 | 4 | // Autogenerated code from profile_vtypes.json. Do not edit. 5 | 6 | import ( 7 | "encoding/binary" 8 | "fmt" 9 | "bytes" 10 | "io" 11 | "unicode/utf16" 12 | "unicode/utf8" 13 | ) 14 | 15 | var ( 16 | // Depending on autogenerated code we may use this. Add a reference 17 | // to shut the compiler up. 18 | _ = bytes.MinRead 19 | _ = fmt.Sprintf 20 | _ = utf16.Decode 21 | _ = binary.LittleEndian 22 | _ = utf8.RuneError 23 | ) 24 | 25 | type AppCompatibilityProfile struct { 26 | Off_Win10CreatorsEntry_DataSize int64 27 | Off_Win10CreatorsEntry_PathSize int64 28 | Off_Win10CreatorsEntry_Path__ int64 29 | Off_Win10CreatorsEntry_Signature int64 30 | Off_Win10CreatorsEntry__XXXX int64 31 | Off_Win10CreatorsHeader_HeaderSize int64 32 | Off_Win10CreatorsHeader_NumberOfEntries int64 33 | } 34 | 35 | func NewAppCompatibilityProfile() *AppCompatibilityProfile { 36 | // Specific offsets can be tweaked to cater for slight version mismatches. 37 | self := &AppCompatibilityProfile{8,12,14,0,0,0,40} 38 | return self 39 | } 40 | 41 | func (self *AppCompatibilityProfile) Win10CreatorsEntry(reader io.ReaderAt, offset int64) *Win10CreatorsEntry { 42 | return &Win10CreatorsEntry{Reader: reader, Offset: offset, Profile: self} 43 | } 44 | 45 | func (self *AppCompatibilityProfile) Win10CreatorsHeader(reader io.ReaderAt, offset int64) *Win10CreatorsHeader { 46 | return &Win10CreatorsHeader{Reader: reader, Offset: offset, Profile: self} 47 | } 48 | 49 | 50 | type Win10CreatorsEntry struct { 51 | Reader io.ReaderAt 52 | Offset int64 53 | Profile *AppCompatibilityProfile 54 | } 55 | 56 | func NewWin10CreatorsEntry(reader io.ReaderAt) *Win10CreatorsEntry { 57 | self := &Win10CreatorsEntry{Reader: reader} 58 | return self 59 | } 60 | 61 | func (self *Win10CreatorsEntry) Size() int { 62 | return 0 63 | } 64 | 65 | func (self *Win10CreatorsEntry) DataSize() uint32 { 66 | return ParseUint32(self.Reader, self.Profile.Off_Win10CreatorsEntry_DataSize + self.Offset) 67 | } 68 | 69 | func (self *Win10CreatorsEntry) PathSize() uint16 { 70 | return ParseUint16(self.Reader, self.Profile.Off_Win10CreatorsEntry_PathSize + self.Offset) 71 | } 72 | 73 | 74 | func (self *Win10CreatorsEntry) Path__() string { 75 | return ParseTerminatedUTF16String(self.Reader, self.Profile.Off_Win10CreatorsEntry_Path__ + self.Offset) 76 | } 77 | 78 | func (self *Win10CreatorsEntry) Signature() uint32 { 79 | return ParseUint32(self.Reader, self.Profile.Off_Win10CreatorsEntry_Signature + self.Offset) 80 | } 81 | 82 | func (self *Win10CreatorsEntry) _XXXX() uint64 { 83 | return ParseUint64(self.Reader, self.Profile.Off_Win10CreatorsEntry__XXXX + self.Offset) 84 | } 85 | func (self *Win10CreatorsEntry) DebugString() string { 86 | result := fmt.Sprintf("struct Win10CreatorsEntry @ %#x:\n", self.Offset) 87 | result += fmt.Sprintf("DataSize: %#0x\n", self.DataSize()) 88 | result += fmt.Sprintf("PathSize: %#0x\n", self.PathSize()) 89 | result += fmt.Sprintf("Path__: %v\n", string(self.Path__())) 90 | result += fmt.Sprintf("Signature: %#0x\n", self.Signature()) 91 | result += fmt.Sprintf("_XXXX: %#0x\n", self._XXXX()) 92 | return result 93 | } 94 | 95 | type Win10CreatorsHeader struct { 96 | Reader io.ReaderAt 97 | Offset int64 98 | Profile *AppCompatibilityProfile 99 | } 100 | 101 | func NewWin10CreatorsHeader(reader io.ReaderAt) *Win10CreatorsHeader { 102 | self := &Win10CreatorsHeader{Reader: reader} 103 | return self 104 | } 105 | 106 | func (self *Win10CreatorsHeader) Size() int { 107 | return 53 108 | } 109 | 110 | func (self *Win10CreatorsHeader) HeaderSize() uint32 { 111 | return ParseUint32(self.Reader, self.Profile.Off_Win10CreatorsHeader_HeaderSize + self.Offset) 112 | } 113 | 114 | func (self *Win10CreatorsHeader) NumberOfEntries() uint32 { 115 | return ParseUint32(self.Reader, self.Profile.Off_Win10CreatorsHeader_NumberOfEntries + self.Offset) 116 | } 117 | func (self *Win10CreatorsHeader) DebugString() string { 118 | result := fmt.Sprintf("struct Win10CreatorsHeader @ %#x:\n", self.Offset) 119 | result += fmt.Sprintf("HeaderSize: %#0x\n", self.HeaderSize()) 120 | result += fmt.Sprintf("NumberOfEntries: %#0x\n", self.NumberOfEntries()) 121 | return result 122 | } 123 | 124 | func ParseUint16(reader io.ReaderAt, offset int64) uint16 { 125 | data := make([]byte, 2) 126 | _, err := reader.ReadAt(data, offset) 127 | if err != nil { 128 | return 0 129 | } 130 | return binary.LittleEndian.Uint16(data) 131 | } 132 | 133 | func ParseUint32(reader io.ReaderAt, offset int64) uint32 { 134 | data := make([]byte, 4) 135 | _, err := reader.ReadAt(data, offset) 136 | if err != nil { 137 | return 0 138 | } 139 | return binary.LittleEndian.Uint32(data) 140 | } 141 | 142 | func ParseUint64(reader io.ReaderAt, offset int64) uint64 { 143 | data := make([]byte, 8) 144 | _, err := reader.ReadAt(data, offset) 145 | if err != nil { 146 | return 0 147 | } 148 | return binary.LittleEndian.Uint64(data) 149 | } 150 | 151 | func ParseTerminatedUTF16String(reader io.ReaderAt, offset int64) string { 152 | data := make([]byte, 1024) 153 | n, err := reader.ReadAt(data, offset) 154 | if err != nil && err != io.EOF { 155 | return "" 156 | } 157 | 158 | idx := bytes.Index(data[:n], []byte{0, 0}) 159 | if idx < 0 { 160 | idx = n-1 161 | } 162 | return UTF16BytesToUTF8(data[0:idx+1], binary.LittleEndian) 163 | } 164 | 165 | func ParseUTF16String(reader io.ReaderAt, offset int64, length int64) string { 166 | data := make([]byte, length) 167 | n, err := reader.ReadAt(data, offset) 168 | if err != nil && err != io.EOF { 169 | return "" 170 | } 171 | return UTF16BytesToUTF8(data[:n], binary.LittleEndian) 172 | } 173 | 174 | func UTF16BytesToUTF8(b []byte, o binary.ByteOrder) string { 175 | if len(b) < 2 { 176 | return "" 177 | } 178 | 179 | if b[0] == 0xff && b[1] == 0xfe { 180 | o = binary.BigEndian 181 | b = b[2:] 182 | } else if b[0] == 0xfe && b[1] == 0xff { 183 | o = binary.LittleEndian 184 | b = b[2:] 185 | } 186 | 187 | utf := make([]uint16, (len(b)+(2-1))/2) 188 | 189 | for i := 0; i+(2-1) < len(b); i += 2 { 190 | utf[i/2] = o.Uint16(b[i:]) 191 | } 192 | if len(b)/2 < len(utf) { 193 | utf[len(utf)-1] = utf8.RuneError 194 | } 195 | 196 | return string(utf16.Decode(utf)) 197 | } 198 | 199 | 200 | -------------------------------------------------------------------------------- /appcompatcache/appcompatcache_test.go: -------------------------------------------------------------------------------- 1 | package appcompatcache 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "os" 7 | "path/filepath" 8 | "testing" 9 | 10 | "github.com/sebdah/goldie" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | var ( 15 | fixtures = []string{ 16 | "Win10Creators.bin", 17 | "Win10.bin", 18 | "Win81.bin", 19 | "Win80.bin", 20 | } 21 | ) 22 | 23 | func TestCacheParsing(t *testing.T) { 24 | for _, fixture := range fixtures { 25 | fd, err := os.Open(filepath.Join("test_data", fixture)) 26 | assert.NoError(t, err) 27 | 28 | buffer, err := ioutil.ReadAll(fd) 29 | assert.NoError(t, err) 30 | 31 | entries := ParseValueData(buffer) 32 | serialized, err := json.MarshalIndent(entries[:10], " ", " ") 33 | assert.NoError(t, err) 34 | 35 | goldie.Assert(t, "appcompatcache_"+fixture, serialized) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /appcompatcache/conversion.spec.yaml: -------------------------------------------------------------------------------- 1 | Module: appcompatcache 2 | Profile: AppCompatibilityProfile 3 | Filename: profile_vtypes.json 4 | GenerateDebugString: true 5 | Structs: 6 | - Win10Header 7 | - Win10CreatorsHeader 8 | - Win10CreatorsEntry 9 | - __a 10 | -------------------------------------------------------------------------------- /appcompatcache/entries.go: -------------------------------------------------------------------------------- 1 | package appcompatcache 2 | 3 | import ( 4 | "bytes" 5 | "time" 6 | ) 7 | 8 | func filetimeToUnixtime(ft uint64) uint64 { 9 | return (ft - 11644473600000*10000) / 10000000 10 | } 11 | 12 | func (self *Win10CreatorsEntry) Path() string { 13 | return ParseUTF16String( 14 | self.Reader, self.Offset+self.Profile.Off_Win10CreatorsEntry_Path__, 15 | int64(self.PathSize())) 16 | } 17 | 18 | func (self *Win10CreatorsEntry) IsValidSignature() bool { 19 | switch self.Signature() { 20 | case 0x73743031, 0x73743030: 21 | return true 22 | } 23 | 24 | return false 25 | } 26 | 27 | func (self *Win10CreatorsEntry) LastMod(header *Win10CreatorsHeader) uint64 { 28 | offset := self.Offset + self.Profile.Off_Win10CreatorsEntry_Path__ + 29 | int64(self.PathSize()) 30 | 31 | if self.Signature() == 0x73743030 { 32 | offset += 10 33 | } else if header.HeaderSize() == 0x80 { 34 | offset += 10 35 | } 36 | 37 | return filetimeToUnixtime(ParseUint64(self.Reader, offset)) 38 | } 39 | 40 | func (self *Win10CreatorsEntry) Next() *Win10CreatorsEntry { 41 | return self.Profile.Win10CreatorsEntry(self.Reader, 42 | self.Offset+self.Profile.Off_Win10CreatorsEntry_PathSize+ 43 | int64(self.DataSize())) 44 | } 45 | 46 | func ParseValueData(buffer []byte) []*CacheEntry { 47 | result := []*CacheEntry{} 48 | 49 | profile := NewAppCompatibilityProfile() 50 | fd := bytes.NewReader(buffer) 51 | 52 | header := profile.Win10CreatorsHeader(fd, 0) 53 | offset := int64(header.HeaderSize()) 54 | if offset == 0 { 55 | offset = 0x80 56 | } 57 | 58 | for entry := profile.Win10CreatorsEntry(fd, offset); entry.IsValidSignature(); entry = entry.Next() { 59 | ts := int64(entry.LastMod(header)) 60 | if ts < 0 || ts > 2000000000 { 61 | ts = 0 62 | } 63 | 64 | result = append(result, &CacheEntry{ 65 | Name: entry.Path(), 66 | Epoch: entry.LastMod(header), 67 | Time: time.Unix(ts, 0), 68 | }) 69 | } 70 | 71 | return result 72 | } 73 | -------------------------------------------------------------------------------- /appcompatcache/fixtures/appcompatcache_Win10.bin.golden: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "C:\\WINDOWS\\System32\\vds.exe", 4 | "epoch": 1426323104, 5 | "timestamp": "2015-03-14T18:51:44+10:00" 6 | }, 7 | { 8 | "name": "C:\\WINDOWS\\System32\\msdtc.exe", 9 | "epoch": 1426323114, 10 | "timestamp": "2015-03-14T18:51:54+10:00" 11 | }, 12 | { 13 | "name": "C:\\$Windows.~BT\\Work\\81859520-B46E-4FE3-9566-E56BD110C270\\DismHost.exe", 14 | "epoch": 1426318744, 15 | "timestamp": "2015-03-14T17:39:04+10:00" 16 | }, 17 | { 18 | "name": "C:\\$Windows.~BT\\Work\\FDAA346E-BA9D-420F-9BC6-DEC3E9A20583\\DismHost.exe", 19 | "epoch": 1427352446, 20 | "timestamp": "2015-03-26T16:47:26+10:00" 21 | }, 22 | { 23 | "name": "C:\\$Windows.~BT\\Work\\46E6F081-B127-4DD5-9F47-63E02679507B\\DismHost.exe", 24 | "epoch": 1426318744, 25 | "timestamp": "2015-03-14T17:39:04+10:00" 26 | }, 27 | { 28 | "name": "C:\\$Windows.~BT\\Work\\13A559E1-8FE1-4014-9BBE-7BA068A2CC01\\DismHost.exe", 29 | "epoch": 1427352446, 30 | "timestamp": "2015-03-26T16:47:26+10:00" 31 | }, 32 | { 33 | "name": "C:\\WINDOWS\\system32\\wimserv.exe", 34 | "epoch": 1426323107, 35 | "timestamp": "2015-03-14T18:51:47+10:00" 36 | }, 37 | { 38 | "name": "C:\\WINDOWS\\system32\\mstsc.exe", 39 | "epoch": 1426323233, 40 | "timestamp": "2015-03-14T18:53:53+10:00" 41 | }, 42 | { 43 | "name": "C:\\Users\\eric.ZIM\\AppData\\Local\\Temp\\6234DD12-17A9-46BF-9940-2F3E8BE76687\\dismhost.exe", 44 | "epoch": 1426318744, 45 | "timestamp": "2015-03-14T17:39:04+10:00" 46 | }, 47 | { 48 | "name": "C:\\WINDOWS\\System32\\vdsldr.exe", 49 | "epoch": 1426323104, 50 | "timestamp": "2015-03-14T18:51:44+10:00" 51 | } 52 | ] -------------------------------------------------------------------------------- /appcompatcache/fixtures/appcompatcache_Win10Creators.bin.golden: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "C:\\Program Files (x86)\\NVIDIA Corporation\\3D Vision\\nvstreg.exe", 4 | "epoch": 1489704961, 5 | "timestamp": "2017-03-17T08:56:01+10:00" 6 | }, 7 | { 8 | "name": "C:\\WINDOWS\\system32\\SystemSettingsAdminFlows.exe", 9 | "epoch": 1489870703, 10 | "timestamp": "2017-03-19T06:58:23+10:00" 11 | }, 12 | { 13 | "name": "C:\\Windows\\System32\\grpconv.exe", 14 | "epoch": 1489870737, 15 | "timestamp": "2017-03-19T06:58:57+10:00" 16 | }, 17 | { 18 | "name": "C:\\Windows\\SysWOW64\\grpconv.exe", 19 | "epoch": 1489870737, 20 | "timestamp": "2017-03-19T06:58:57+10:00" 21 | }, 22 | { 23 | "name": "C:\\WINDOWS\\system32\\runonce.exe", 24 | "epoch": 1489870736, 25 | "timestamp": "2017-03-19T06:58:56+10:00" 26 | }, 27 | { 28 | "name": "C:\\Windows\\SysWOW64\\rundll32.exe", 29 | "epoch": 1489870736, 30 | "timestamp": "2017-03-19T06:58:56+10:00" 31 | }, 32 | { 33 | "name": "C:\\Temp\\NVIDIA\\3DVision\\nvStInst.exe", 34 | "epoch": 1489704960, 35 | "timestamp": "2017-03-17T08:56:00+10:00" 36 | }, 37 | { 38 | "name": "C:\\Users\\eric\\AppData\\Local\\Temp\\{0E5D1094-B962-4212-AAE0-596961FC0007}\\ISBEW64.exe", 39 | "epoch": 1177449752, 40 | "timestamp": "2007-04-25T07:22:32+10:00" 41 | }, 42 | { 43 | "name": "C:\\Temp\\NVIDIA\\3DVision\\setup.exe", 44 | "epoch": 1489704952, 45 | "timestamp": "2017-03-17T08:55:52+10:00" 46 | }, 47 | { 48 | "name": "C:\\Program Files\\NVIDIA Corporation\\Installer2\\Display.3DVision.{DA27F5CA-7F04-4931-82CF-C499793E4712}\\3DVision.exe", 49 | "epoch": 1489712466, 50 | "timestamp": "2017-03-17T11:01:06+10:00" 51 | } 52 | ] -------------------------------------------------------------------------------- /appcompatcache/fixtures/appcompatcache_Win80.bin.golden: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "SYSVOL\\Windows\\System32\\LogonUI.exe", 4 | "epoch": 1343272849, 5 | "timestamp": "2012-07-26T13:20:49+10:00" 6 | }, 7 | { 8 | "name": "SYSVOL\\Program Files\\Winamp\\UninstWA.exe", 9 | "epoch": 1429559979, 10 | "timestamp": "2015-04-21T05:59:39+10:00" 11 | }, 12 | { 13 | "name": "SYSVOL\\Program Files\\Just Great Software\\EditPad Lite 7\\EditPadLite7.exe", 14 | "epoch": 1424698620, 15 | "timestamp": "2015-02-23T23:37:00+10:00" 16 | }, 17 | { 18 | "name": "SYSVOL\\Program Files\\Winamp\\winamp.exe", 19 | "epoch": 1386902874, 20 | "timestamp": "2013-12-13T12:47:54+10:00" 21 | }, 22 | { 23 | "name": "SYSVOL\\Windows\\System32\\dllhost.exe", 24 | "epoch": 1343272845, 25 | "timestamp": "2012-07-26T13:20:45+10:00" 26 | }, 27 | { 28 | "name": "SYSVOL\\Windows\\System32\\rundll32.exe", 29 | "epoch": 1343272855, 30 | "timestamp": "2012-07-26T13:20:55+10:00" 31 | }, 32 | { 33 | "name": "SYSVOL\\Program Files\\Winamp\\Elevator.exe", 34 | "epoch": 1386902874, 35 | "timestamp": "2013-12-13T12:47:54+10:00" 36 | }, 37 | { 38 | "name": "SYSVOL\\Windows\\System32\\consent.exe", 39 | "epoch": 1343273953, 40 | "timestamp": "2012-07-26T13:39:13+10:00" 41 | }, 42 | { 43 | "name": "SYSVOL\\Windows\\System32\\svchost.exe", 44 | "epoch": 1343272858, 45 | "timestamp": "2012-07-26T13:20:58+10:00" 46 | }, 47 | { 48 | "name": "SYSVOL\\Windows\\System32\\VSSVC.exe", 49 | "epoch": 1343272860, 50 | "timestamp": "2012-07-26T13:21:00+10:00" 51 | } 52 | ] -------------------------------------------------------------------------------- /appcompatcache/fixtures/appcompatcache_Win81.bin.golden: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "SYSVOL\\Program Files\\CrashPlan\\jre\\bin\\java.exe", 4 | "epoch": 1386200843, 5 | "timestamp": "2013-12-05T09:47:23+10:00" 6 | }, 7 | { 8 | "name": "SYSVOL\\Windows\\System32\\rundll32.exe", 9 | "epoch": 1377169421, 10 | "timestamp": "2013-08-22T21:03:41+10:00" 11 | }, 12 | { 13 | "name": "SYSVOL\\Users\\eric\\AppData\\Roaming\\Spotify\\Data\\SpotifyHelper.exe", 14 | "epoch": 1418932118, 15 | "timestamp": "2014-12-19T05:48:38+10:00" 16 | }, 17 | { 18 | "name": "SYSVOL\\Program Files (x86)\\ATI Technologies\\ATI.ACE\\Core-Static\\CLI.exe", 19 | "epoch": 1367945544, 20 | "timestamp": "2013-05-08T02:52:24+10:00" 21 | }, 22 | { 23 | "name": "SYSVOL\\Windows\\System32\\LogonUI.exe", 24 | "epoch": 1377171141, 25 | "timestamp": "2013-08-22T21:32:21+10:00" 26 | }, 27 | { 28 | "name": "SYSVOL\\Windows\\WinSxS\\amd64_microsoft-windows-servicingstack_31bf3856ad364e35_6.3.9600.17477_none_fa2b7d3b9b36c7b4\\TiWorker.exe", 29 | "epoch": 1414708420, 30 | "timestamp": "2014-10-31T08:33:40+10:00" 31 | }, 32 | { 33 | "name": "SYSVOL\\Windows\\servicing\\TrustedInstaller.exe", 34 | "epoch": 1395137107, 35 | "timestamp": "2014-03-18T20:05:07+10:00" 36 | }, 37 | { 38 | "name": "SYSVOL\\Windows\\System32\\svchost.exe", 39 | "epoch": 1377175517, 40 | "timestamp": "2013-08-22T22:45:17+10:00" 41 | }, 42 | { 43 | "name": "SYSVOL\\Windows\\System32\\dllhost.exe", 44 | "epoch": 1377174925, 45 | "timestamp": "2013-08-22T22:35:25+10:00" 46 | }, 47 | { 48 | "name": "SYSVOL\\Windows\\System32\\ThumbnailExtractionHost.exe", 49 | "epoch": 1377169317, 50 | "timestamp": "2013-08-22T21:01:57+10:00" 51 | } 52 | ] -------------------------------------------------------------------------------- /appcompatcache/profile_vtypes.json: -------------------------------------------------------------------------------- 1 | { 2 | "Win10CreatorsHeader": [53, { 3 | "HeaderSize": [0, ["unsigned long"]], 4 | "NumberOfEntries": [40, ["unsigned long"]] 5 | }], 6 | 7 | "Win10CreatorsEntry": [0, { 8 | "Signature": [0, ["unsigned long"]], 9 | "DataSize": [8, ["unsigned long"]], 10 | "PathSize": [12,["unsigned short"]], 11 | "Path__": [14, ["UnicodeString"]], 12 | "_XXXX" : [0, ["unsigned long long"]] 13 | }] 14 | } 15 | -------------------------------------------------------------------------------- /appcompatcache/structs.go: -------------------------------------------------------------------------------- 1 | package appcompatcache 2 | 3 | import "time" 4 | 5 | type CacheEntry struct { 6 | Name string `json:"name"` 7 | Epoch uint64 `json:"epoch"` 8 | Time time.Time `json:"time"` 9 | } 10 | -------------------------------------------------------------------------------- /appcompatcache/test_data/ReadMe.txt: -------------------------------------------------------------------------------- 1 | These files were borrowed from: 2 | https://github.com/EricZimmerman/AppCompatCacheParser.git 3 | 4 | These are the raw, binary contents of the 5 | SYSTEM\CurrentControlSet\Control\Session Manager\AppCompatCache key's 6 | AppCompatCache value 7 | -------------------------------------------------------------------------------- /appcompatcache/test_data/Win10.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Velocidex/regparser/31e704a67ef786f1fcedd62e9fd367970466e5d3/appcompatcache/test_data/Win10.bin -------------------------------------------------------------------------------- /appcompatcache/test_data/Win10Creators.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Velocidex/regparser/31e704a67ef786f1fcedd62e9fd367970466e5d3/appcompatcache/test_data/Win10Creators.bin -------------------------------------------------------------------------------- /appcompatcache/test_data/Win2k8Standard.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Velocidex/regparser/31e704a67ef786f1fcedd62e9fd367970466e5d3/appcompatcache/test_data/Win2k8Standard.bin -------------------------------------------------------------------------------- /appcompatcache/test_data/Win80.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Velocidex/regparser/31e704a67ef786f1fcedd62e9fd367970466e5d3/appcompatcache/test_data/Win80.bin -------------------------------------------------------------------------------- /appcompatcache/test_data/Win81.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Velocidex/regparser/31e704a67ef786f1fcedd62e9fd367970466e5d3/appcompatcache/test_data/Win81.bin -------------------------------------------------------------------------------- /appcompatcache/test_data/WinXPx86.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Velocidex/regparser/31e704a67ef786f1fcedd62e9fd367970466e5d3/appcompatcache/test_data/WinXPx86.bin -------------------------------------------------------------------------------- /appcompatcache/test_data/win7x64.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Velocidex/regparser/31e704a67ef786f1fcedd62e9fd367970466e5d3/appcompatcache/test_data/win7x64.bin -------------------------------------------------------------------------------- /appcompatcache/test_data/win7x86.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Velocidex/regparser/31e704a67ef786f1fcedd62e9fd367970466e5d3/appcompatcache/test_data/win7x86.bin -------------------------------------------------------------------------------- /cmd/appcompatcache.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | kingpin "gopkg.in/alecthomas/kingpin.v2" 8 | "www.velocidex.com/golang/regparser" 9 | "www.velocidex.com/golang/regparser/appcompatcache" 10 | ) 11 | 12 | var ( 13 | appcompatcache_command = app.Command( 14 | "appcompatcache", "List the application compatibility cache.") 15 | 16 | appcompatcache_command_file_arg = appcompatcache_command.Arg( 17 | "file", "Registry hive file", 18 | ).Required().OpenFile(os.O_RDONLY, 0600) 19 | ) 20 | 21 | const ( 22 | appcompatcache_path = "/ControlSet001/Control/Session Manager/AppCompatCache" 23 | ) 24 | 25 | func parseAppCompatibilityCache(buffer []byte) { 26 | for idx, entry := range appcompatcache.ParseValueData(buffer) { 27 | fmt.Printf("%d: %v %v\n", idx, entry.Time, entry.Name) 28 | } 29 | } 30 | 31 | func doAppCompatCache() { 32 | registry, err := regparser.NewRegistry(*appcompatcache_command_file_arg) 33 | kingpin.FatalIfError(err, "Open hive") 34 | 35 | key := registry.OpenKey(appcompatcache_path) 36 | if key == nil { 37 | kingpin.Fatalf("Key path not found %v", appcompatcache_path) 38 | } 39 | 40 | for _, value := range key.Values() { 41 | if value.ValueName() != "AppCompatCache" { 42 | continue 43 | } 44 | 45 | parseAppCompatibilityCache(value.ValueData().Data) 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /cmd/regparser.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | kingpin "gopkg.in/alecthomas/kingpin.v2" 8 | "www.velocidex.com/golang/regparser" 9 | ) 10 | 11 | var ( 12 | app = kingpin.New("regparser", 13 | "A tool for parsing registry hives.") 14 | 15 | ls_command = app.Command( 16 | "ls", "List a path in the registry hive") 17 | 18 | ls_command_file_arg = ls_command.Arg( 19 | "file", "Registry hive file", 20 | ).Required().OpenFile(os.O_RDONLY, 0600) 21 | 22 | ls_command_path = ls_command.Arg( 23 | "path", "Path to list").Default("").String() 24 | ) 25 | 26 | func doLs() { 27 | registry, err := regparser.NewRegistry(*ls_command_file_arg) 28 | kingpin.FatalIfError(err, "Open hive") 29 | 30 | key := registry.OpenKey(*ls_command_path) 31 | if key == nil { 32 | kingpin.Fatalf("Key path not found %v", *ls_command_path) 33 | } 34 | 35 | fmt.Printf("Listing key %s (%v)\n\n", *ls_command_path, 36 | key.LastWriteTime()) 37 | 38 | fmt.Printf("Subkeys:\n") 39 | for _, subkey := range key.Subkeys() { 40 | fmt.Printf(" %s - %v\n", subkey.Name(), subkey.LastWriteTime()) 41 | } 42 | 43 | fmt.Printf("\nValues:\n") 44 | for _, value := range key.Values() { 45 | fmt.Printf(" %s : %#v\n", value.ValueName(), value.ValueData()) 46 | } 47 | } 48 | 49 | func main() { 50 | app.HelpFlag.Short('h') 51 | app.UsageTemplate(kingpin.CompactUsageTemplate) 52 | command := kingpin.MustParse(app.Parse(os.Args[1:])) 53 | switch command { 54 | 55 | case ls_command.FullCommand(): 56 | doLs() 57 | 58 | case appcompatcache_command.FullCommand(): 59 | doAppCompatCache() 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /constants.go: -------------------------------------------------------------------------------- 1 | package regparser 2 | 3 | const ( 4 | REG_NONE = 0x00000000 5 | REG_SZ = 0x00000001 6 | REG_EXPAND_SZ = 0x00000002 7 | REG_BINARY = 0x00000003 8 | REG_DWORD = 0x00000004 9 | REG_DWORD_LITTLE_ENDIAN = 0x00000004 10 | REG_DWORD_BIG_ENDIAN = 0x00000005 11 | REG_LINK = 0x00000006 12 | REG_MULTI_SZ = 0x00000007 13 | REG_RESOURCE_LIST = 0x00000008 14 | REG_FULL_RESOURCE_DESCRIPTOR = 0x00000009 15 | REG_RESOURCE_REQUIREMENTS_LIST = 0x0000000a 16 | REG_QWORD = 0x0000000b 17 | 18 | REG_UNKNOWN = 0xffffffff 19 | ) 20 | 21 | func RegTypeToString(reg_type uint32) string { 22 | switch reg_type { 23 | case 0x00000000: 24 | return "REG_NONE" 25 | case 0x00000001: 26 | return "REG_SZ" 27 | case 0x00000002: 28 | return "REG_EXPAND_SZ" 29 | case 0x00000003: 30 | return "REG_BINARY" 31 | case 0x00000004: 32 | return "REG_DWORD" 33 | case 0x00000005: 34 | return "REG_DWORD_BIG_ENDIAN" 35 | case 0x00000006: 36 | return "REG_LINK" 37 | case 0x00000007: 38 | return "REG_MULTI_SZ" 39 | case 0x00000008: 40 | return "REG_RESOURCE_LIST" 41 | case 0x00000009: 42 | return "REG_FULL_RESOURCE_DESCRIPTOR" 43 | case 0x0000000a: 44 | return "REG_RESOURCE_REQUIREMENTS_LIST" 45 | case 0x0000000b: 46 | return "REG_QWORD" 47 | } 48 | 49 | return "REG_UNKNOWN" 50 | } 51 | -------------------------------------------------------------------------------- /conversion.spec.yaml: -------------------------------------------------------------------------------- 1 | Module: regparser 2 | Profile: RegistryProfile 3 | Filename: profile_vtypes.json 4 | GenerateDebugString: false 5 | Structs: 6 | - _HBASE_BLOCK 7 | - _GUID 8 | - _LARGE_INTEGER 9 | - _HBIN 10 | - _HCELL 11 | - _CM_KEY_NODE 12 | - _CM_KEY_INDEX 13 | - _CHILD_LIST 14 | - _HHIVE 15 | - _CM_KEY_VALUE 16 | - _CM_KEY_INDEX_FAST 17 | - _CM_KEY_INDEX_FAST_ELEMENT 18 | - _CM_BIG_DATA 19 | - HIVE_LOG_ENTRY 20 | - HIVE_DIRTY_PAGE_REF 21 | 22 | FieldBlackList: 23 | _LARGE_INTEGER: 24 | - u 25 | -------------------------------------------------------------------------------- /debug.go: -------------------------------------------------------------------------------- 1 | package regparser 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | var debugOutput bool 10 | 11 | func init() { 12 | for _, x := range os.Environ() { 13 | if strings.HasPrefix(x, "REGPARSER_DEBUG=") { 14 | debugOutput = true 15 | break 16 | } 17 | } 18 | } 19 | 20 | func DebugPrint(fmt_str string, v ...interface{}) { 21 | if debugOutput { 22 | fmt.Printf(fmt_str, v...) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | package regparser 2 | 3 | /* 4 | 5 | This package is an implementation of a registry parser in Golang. 6 | 7 | The file format specification is explored in detail here: 8 | 9 | https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md 10 | 11 | While the above reference discusses data structures by reversing 12 | the registry format, in this implementation we use the struct 13 | layouts and names as obtained from the Microsoft Symbol 14 | server. Therefore we try to stay as close as possible to the 15 | Microsoft struct names. 16 | 17 | In this implementation we add convenience methods to the original 18 | struct names as required. For example, we represent a key node 19 | using the symbol CM_KEY_NODE as found in the Symbol Server. We then 20 | add a method to this object to return all subkeys by parsing out 21 | the various indexing structures transparently Subkeys() which also 22 | returns a list of CM_KEY_NODE objects. 23 | 24 | */ 25 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module www.velocidex.com/golang/regparser 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/davecgh/go-spew v1.1.1 7 | github.com/sebdah/goldie v1.0.0 8 | github.com/stretchr/testify v1.8.0 9 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 10 | ) 11 | 12 | require ( 13 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect 14 | github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect 15 | github.com/pmezard/go-difflib v1.0.0 // indirect 16 | gopkg.in/yaml.v3 v3.0.1 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= 2 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 3 | github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= 4 | github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 9 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 10 | github.com/sebdah/goldie v1.0.0 h1:9GNhIat69MSlz/ndaBg48vl9dF5fI+NBB6kfOxgfkMc= 11 | github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= 12 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 13 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 14 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 15 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 16 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 17 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 18 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 19 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= 20 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 21 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 22 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 23 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 24 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 25 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 26 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 27 | -------------------------------------------------------------------------------- /helpers.go: -------------------------------------------------------------------------------- 1 | package regparser 2 | 3 | /* This file contains helper methods attached to the autogenerated structs. */ 4 | 5 | import ( 6 | "encoding/binary" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "time" 11 | 12 | "github.com/davecgh/go-spew/spew" 13 | ) 14 | 15 | func filetimeToUnixtime(ft uint64) uint64 { 16 | return (ft - 11644473600000*10000) / 10000000 17 | } 18 | 19 | // A FileTime object is a timestamp in windows filetime format. 20 | type FileTime struct { 21 | time.Time 22 | } 23 | 24 | func (self *FileTime) GoString() string { 25 | return fmt.Sprintf("%v", self) 26 | } 27 | 28 | func (self *FileTime) DebugString() string { 29 | return fmt.Sprintf("%v", self) 30 | } 31 | 32 | func (self *RegistryProfile) FileTime(reader io.ReaderAt, offset int64) *FileTime { 33 | filetime := ParseUint64(reader, offset) 34 | return &FileTime{time.Unix(int64(filetimeToUnixtime(filetime)), 0)} 35 | } 36 | 37 | // UTF16 null terminated string. 38 | type UnicodeString struct { 39 | Value string 40 | } 41 | 42 | func (self *UnicodeString) DebugString() string { 43 | return fmt.Sprintf("%v", self) 44 | } 45 | 46 | func (self *RegistryProfile) UnicodeString(reader io.ReaderAt, offset int64) *UnicodeString { 47 | result := UnicodeString{""} 48 | buff := make([]byte, 256) 49 | _, err := reader.ReadAt(buff, offset) 50 | if err != nil { 51 | return &result 52 | } 53 | 54 | utf8 := UTF16BytesToUTF8(buff, binary.LittleEndian) 55 | var i int 56 | var c rune 57 | for i, c = range utf8 { 58 | if c == 0 { 59 | break 60 | } 61 | } 62 | result.Value = utf8[:i] 63 | 64 | return &result 65 | } 66 | 67 | func (self *UnicodeString) GoString() string { 68 | return self.Value 69 | } 70 | 71 | // HBASE_BLOCK is the file header block at the start of the registry file. 72 | func (self *HBASE_BLOCK) HiveBin() *HBIN { 73 | return self.Profile.HBIN(self.Reader, self.Offset+int64(self.Size())) 74 | } 75 | 76 | // All data in the registry file is contained in cells. The HCELL 77 | // struct is the main container for everything. We add many 78 | // convenience methods on this structure to be able to extract the 79 | // various things contained inside the cell. 80 | func (self *HCELL) NextCell() *HCELL { 81 | next := self.Next() 82 | if self.Allocated() { 83 | next = -self.Next() 84 | } 85 | return self.Profile.HCELL(self.Reader, self.Offset+int64(next)) 86 | } 87 | 88 | // Cells may be allocated or not. 89 | func (self *HCELL) Allocated() bool { 90 | return self.Next() > 0x80000000 91 | } 92 | 93 | // This method returns the actual size of the cell's data payload. 94 | func (self *HCELL) DataSize() uint32 { 95 | // https://github.com/msuhanov/regf: Size of a current cell in 96 | // bytes, including this field (aligned to 8 bytes): the size 97 | // is positive if a cell is unallocated or negative if a cell 98 | // is allocated 99 | cell_size := self.Next() 100 | cell_size = -cell_size - 4 101 | cell_size -= cell_size % 8 102 | return cell_size 103 | } 104 | 105 | // The offset of the cells payload. 106 | func (self *HCELL) Payload() int64 { 107 | return self.Offset + self.Profile.Off_HCELL_Data 108 | } 109 | 110 | // If the HCELL contains a CM_KEY_NODE (nk node) then this method 111 | // returns it. Otherwise it returns nil. 112 | func (self *HCELL) KeyNode() *CM_KEY_NODE { 113 | if self.Signature() == 0x6b6e /* nk */ { 114 | return self.Profile.CM_KEY_NODE(self.Reader, self.Offset+4) 115 | } 116 | 117 | return nil 118 | } 119 | 120 | // If the HCELL contains a CM_KEY_INDEX (ri or li node) then this method 121 | // returns it. Otherwise it returns nil. 122 | func (self *HCELL) KeyIndex() *CM_KEY_INDEX { 123 | switch self.Signature() { 124 | case 0x6972 /* ri */, 0x696c /* li */ : 125 | return self.Profile.CM_KEY_INDEX(self.Reader, self.Offset+4) 126 | } 127 | 128 | return nil 129 | } 130 | 131 | // If the HCELL contains a CM_KEY_INDEX_FAST (lf or lh node) then this 132 | // method returns it. Otherwise it returns nil. 133 | func (self *HCELL) KeyIndexFast() *CM_KEY_INDEX_FAST { 134 | switch self.Signature() { 135 | case 0x666c /* lf */, 0x686c /* lh */ : 136 | return self.Profile.CM_KEY_INDEX_FAST(self.Reader, self.Offset+4) 137 | } 138 | return nil 139 | } 140 | 141 | // If the HCELL contains a CM_KEY_VALUE (vk node) then this method 142 | // returns it. Otherwise it returns nil. 143 | func (self *HCELL) KeyValue() *CM_KEY_VALUE { 144 | switch self.Signature() { 145 | case 0x6b76 /* vk */ : 146 | return self.Profile.CM_KEY_VALUE(self.Reader, self.Offset+4) 147 | } 148 | 149 | return nil 150 | } 151 | 152 | // This is a convenience method for enumerating the subkeys of a 153 | // CM_KEY_NODE. Each _CM_KEY_NODE can point to a number of different 154 | // types of index nodes. This method deals with the different types of 155 | // indexes and just returns a list of subkeys regardless of the type 156 | // of indexes. 157 | func (self *CM_KEY_NODE) Subkeys() []*CM_KEY_NODE { 158 | DebugPrint("_CM_KEY_NODE.Subkeys %x @ %x", self.Signature(), self.Offset) 159 | child := self.Profile.HCELL( 160 | self.Reader, 4096+int64(self.SubKeyLists()[0])) 161 | 162 | if child.Signature() == 0 { 163 | return nil 164 | } 165 | 166 | // It can point to a fast index of its subkeys. 167 | if fl := child.KeyIndexFast(); fl != nil { 168 | return fl.Subkeys() 169 | 170 | // Or one of the regular indexes. 171 | } else if fi := child.KeyIndex(); fi != nil { 172 | return fi.Subkeys() 173 | 174 | } else { 175 | DebugPrint("Unknown KeyNode child %x @ %x", 176 | child.Signature(), child.Offset) 177 | } 178 | 179 | return nil 180 | } 181 | 182 | // A convenience method for extracting the Values contained under a 183 | // key. 184 | func (self *CM_KEY_NODE) Values() []*CM_KEY_VALUE { 185 | result := []*CM_KEY_VALUE{} 186 | count := self.ValueList().Count() 187 | 188 | value_list_cell := self.Profile.HCELL( 189 | self.Reader, 190 | 4096+int64(self.ValueList().List())) 191 | 192 | value_offsets := ParseSafeArray_uint32( 193 | self.Reader, 194 | value_list_cell.Payload(), int(count)) 195 | 196 | for _, offset := range value_offsets { 197 | node := self.Profile.HCELL(self.Reader, 4096+int64(offset)) 198 | 199 | if value_node := node.KeyValue(); value_node != nil { 200 | result = append(result, value_node) 201 | } 202 | } 203 | 204 | return result 205 | } 206 | 207 | // The name of the a key. This does not include the full path through 208 | // its parents. 209 | func (self *CM_KEY_NODE) Name() string { 210 | // NameLength is potentially corrupt, but since it is a uint16, this can 211 | // at worst lead to a 64 KB allocation, which is acceptable. 212 | buff := make([]byte, self.NameLength()) 213 | n, err := self.Reader.ReadAt( 214 | buff, self.Profile.Off_CM_KEY_NODE__Name+self.Offset) 215 | if err == nil { 216 | return string(buff[:n]) 217 | } 218 | return "" 219 | } 220 | 221 | // Extract all subkeys stored in the fast index. 222 | func (self *CM_KEY_INDEX_FAST) Subkeys() []*CM_KEY_NODE { 223 | DebugPrint("_CM_KEY_INDEX_FAST.Subkeys %x @ %x", self.Signature(), self.Offset) 224 | result := []*CM_KEY_NODE{} 225 | 226 | for _, element := range ParseSafeArray_CM_KEY_INDEX_FAST_ELEMENT( 227 | self.Profile, 228 | self.Reader, 229 | self.Offset+self.Profile.Off_CM_KEY_INDEX_FAST_List, 230 | int(self.Count())) { 231 | child := self.Profile.HCELL(self.Reader, 4096+int64( 232 | element.NodeOffset())) 233 | 234 | if child_key := child.KeyNode(); child_key != nil { 235 | result = append(result, child_key) 236 | 237 | } else { 238 | DebugPrint("%x: Unknown %x @ %x", self.Signature(), 239 | child.Signature(), child.Offset) 240 | } 241 | } 242 | 243 | return result 244 | } 245 | 246 | // Extract subkeys from the index. 247 | func (self *CM_KEY_INDEX) Subkeys() []*CM_KEY_NODE { 248 | DebugPrint("_CM_KEY_INDEX.Subkeys %x @ %x", self.Signature(), self.Offset) 249 | 250 | result := []*CM_KEY_NODE{} 251 | 252 | for _, offset := range ParseSafeArray_uint32( 253 | self.Reader, 254 | self.Offset+self.Profile.Off_CM_KEY_INDEX_List, 255 | int(self.Count())) { 256 | child := self.Profile.HCELL(self.Reader, 4096+int64(offset)) 257 | if child_key := child.KeyNode(); child_key != nil { 258 | result = append(result, child_key) 259 | 260 | } else if child_idx := child.KeyIndex(); child_idx != nil { 261 | result = append(result, child_idx.Subkeys()...) 262 | 263 | } else if child_idx := child.KeyIndexFast(); child_idx != nil { 264 | result = append(result, child_idx.Subkeys()...) 265 | 266 | } else { 267 | DebugPrint("%x: Unknown %x @ %x", self.Signature(), 268 | child.Signature(), child.Offset) 269 | } 270 | } 271 | 272 | return result 273 | } 274 | 275 | // The name of this value (empty string means default value). 276 | func (self *CM_KEY_VALUE) ValueName() string { 277 | // NameLength is potentially corrupt, but since it is a uint16, this can 278 | // at worst lead to a 64 KB allocation, which is acceptable. 279 | buff := make([]byte, self.NameLength()) 280 | n, err := self.Reader.ReadAt( 281 | buff, self.Profile.Off_CM_KEY_VALUE_Name+self.Offset) 282 | if err == nil { 283 | return string(buff[:n]) 284 | } 285 | return "" 286 | } 287 | 288 | // A Registry Value may represent a number of different data types 289 | // depending on its Type field. This struct contains the various Go 290 | // types that are represented. Many of the registry types are 291 | // converted to the most closely matching Go types. The original 292 | // binary data is also attached in the Data field. 293 | type ValueData struct { 294 | // REG_SZ etc. 295 | Type uint32 296 | 297 | // Filled in for REG_SZ etc. 298 | String string 299 | 300 | // Filled in for REG_MULTI_SZ 301 | MultiSz []string 302 | 303 | // Filled in for integer types 304 | Uint64 uint64 305 | 306 | // The original encoded data. For BINARY_SZ this is the only 307 | // field filled. 308 | Data []byte 309 | 310 | // If an error occurs during parsing this will contain the 311 | // error object. 312 | Error error 313 | } 314 | 315 | // Convert the registry type to a string. 316 | func (self *CM_KEY_VALUE) TypeString() string { 317 | return RegTypeToString(self.Type()) 318 | } 319 | 320 | func (self *CM_KEY_VALUE) DataSize() int64 { 321 | data_size := self.DataLength() 322 | if data_size&0x80000000 > 0 { 323 | // Data is so short it can be stored in the Data() 324 | // field itself. In this case Data() is not a pointer 325 | // but the actual data value. 326 | data_size ^= 0x80000000 327 | } 328 | 329 | return int64(data_size) 330 | } 331 | 332 | // Parse out the data from the value into a Go ValueData type. 333 | func (self *CM_KEY_VALUE) ValueData() *ValueData { 334 | result := &ValueData{Type: self.Type()} 335 | 336 | data_size := self.DataLength() 337 | 338 | if data_size&0x80000000 > 0 { 339 | // Data is so short it can be stored in the Data() 340 | // field itself. In this case Data() is not a pointer 341 | // but the actual data value. 342 | data_size ^= 0x80000000 343 | result.Data = ParseSafeArray_byte( 344 | self.Reader, 345 | self.Offset+self.Profile.Off_CM_KEY_VALUE_Data, 4) 346 | } else { 347 | // Handle BIG_DATA values. The Data field is a pointer 348 | // to the cell that contains the CM_BIG_DATA object. 349 | cell := self.Profile.HCELL(self.Reader, 350 | 0x1000+int64(self.Data())) 351 | 352 | if cell.Signature() == 0x6264 /* db */ { 353 | big_data := self.Profile.CM_BIG_DATA( 354 | self.Reader, cell.Payload()) 355 | 356 | // Big data's List member is a pointer to the 357 | // CELL that contains the segment list. 358 | list_cell := self.Profile.HCELL( 359 | self.Reader, 0x1000+int64(big_data.List())) 360 | 361 | // Extract the segment offset list. 362 | segment_list := ParseSafeArray_uint32( 363 | self.Reader, 364 | list_cell.Payload(), 365 | int(big_data.Count())) 366 | 367 | for _, offset := range segment_list { 368 | // Each segment offset is a pointer to 369 | // the cell that contains the value's 370 | // data. 371 | segment_cell := self.Profile.HCELL(self.Reader, 372 | 0x1000+int64(offset)) 373 | 374 | // Unallocated cells are probably 375 | // deleted values. 376 | if !segment_cell.Allocated() { 377 | continue 378 | } 379 | 380 | // There isnt an actual header for 381 | // these segments, the data is just 382 | // stored in the cell's payload. 383 | segment_cell_size := segment_cell.DataSize() 384 | if segment_cell_size > data_size { 385 | segment_cell_size = data_size 386 | } 387 | 388 | result.Data = append(result.Data, 389 | ParseSafeArray_byte( 390 | self.Reader, 391 | segment_cell.Payload(), 392 | int(segment_cell_size))...) 393 | 394 | // Keep reading more segments until we 395 | // have all the required length. 396 | data_size -= segment_cell_size 397 | } 398 | } else { 399 | // Data is stored directly in a single cell. 400 | result.Data = ParseSafeArray_byte( 401 | self.Reader, 402 | cell.Payload(), int(data_size)) 403 | } 404 | } 405 | 406 | switch result.Type { 407 | case REG_SZ, REG_EXPAND_SZ: 408 | result.String = UTF16BytesToUTF8(result.Data, binary.LittleEndian) 409 | 410 | case REG_MULTI_SZ: 411 | // Make sure the data is valid - len must be even. 412 | if len(result.Data)%2 > 0 { 413 | result.Data = append(result.Data, 0) 414 | } 415 | var tmp []byte 416 | for i := 0; i < len(result.Data); i += 2 { 417 | // NULL character represents the end of one string - flush 418 | // it and reset for the next string. 419 | if result.Data[i] == 0 && result.Data[i+1] == 0 { 420 | if len(tmp) > 0 { 421 | result.MultiSz = append(result.MultiSz, 422 | UTF16BytesToUTF8(tmp, binary.LittleEndian)) 423 | } 424 | tmp = nil 425 | } else { 426 | tmp = append(tmp, result.Data[i], result.Data[i+1]) 427 | } 428 | } 429 | 430 | case REG_BINARY: 431 | // Leave the raw data as is in the ValueData struct. 432 | 433 | case REG_DWORD: 434 | if data_size != 4 { 435 | result.Error = errors.New("Data is of incorrect size") 436 | } else { 437 | result.Uint64 = uint64(binary.LittleEndian.Uint32(result.Data)) 438 | } 439 | 440 | case REG_DWORD_BIG_ENDIAN: 441 | if data_size != 4 { 442 | result.Error = errors.New("Data is of incorrect size") 443 | } else { 444 | result.Uint64 = uint64(binary.BigEndian.Uint32(result.Data)) 445 | } 446 | 447 | case REG_QWORD: 448 | if data_size != 8 { 449 | result.Error = errors.New("Data is of incorrect size") 450 | } else { 451 | result.Uint64 = binary.LittleEndian.Uint64(result.Data) 452 | } 453 | 454 | default: 455 | // We cant handle this data directly - just pass it to 456 | // the consumer of the ValueData struct to deal with 457 | // parsing it. 458 | } 459 | 460 | return result 461 | } 462 | 463 | func (self *ValueData) GoString() string { 464 | switch self.Type { 465 | case REG_SZ, REG_EXPAND_SZ: 466 | return self.String 467 | 468 | case REG_DWORD: 469 | return fmt.Sprintf("%x", self.Uint64) 470 | 471 | case REG_BINARY: 472 | data := self.Data 473 | if len(data) > 1024 { 474 | data = data[:1024] 475 | } 476 | return spew.Sdump(data) 477 | } 478 | 479 | return spew.Sdump(self) 480 | } 481 | 482 | func ParseSafeArray_uint32(reader io.ReaderAt, offset int64, count int) []uint32 { 483 | result := []uint32{} 484 | data := make([]byte, 4) 485 | for i := 0; i < count; i++ { 486 | _, err := reader.ReadAt(data, offset) 487 | if err == io.EOF { 488 | break 489 | } else if err != nil { 490 | continue 491 | } 492 | result = append(result, binary.LittleEndian.Uint32(data)) 493 | offset += 4 494 | } 495 | return result 496 | } 497 | 498 | func ParseSafeArray_byte(reader io.ReaderAt, offset int64, count int) []byte { 499 | result := []byte{} 500 | var data [1]byte 501 | for i := 0; i < count; i++ { 502 | _, err := reader.ReadAt(data[:], offset) 503 | if err == io.EOF { 504 | break 505 | } else if err != nil { 506 | continue 507 | } 508 | result = append(result, data[0]) 509 | offset += 1 510 | } 511 | return result 512 | } 513 | 514 | func ParseSafeArray_CM_KEY_INDEX_FAST_ELEMENT(profile *RegistryProfile, reader io.ReaderAt, offset int64, count int) []*CM_KEY_INDEX_FAST_ELEMENT { 515 | result := []*CM_KEY_INDEX_FAST_ELEMENT{} 516 | var probe [1]byte 517 | for i := 0; i < count; i++ { 518 | // Do a single byte read to probe whether we are still at a valid position 519 | _, err := reader.ReadAt(probe[:], offset) 520 | if err == io.EOF { 521 | break 522 | } else if err != nil { 523 | continue 524 | } 525 | value := profile.CM_KEY_INDEX_FAST_ELEMENT(reader, offset) 526 | result = append(result, value) 527 | offset += int64(value.Size()) 528 | } 529 | return result 530 | } 531 | -------------------------------------------------------------------------------- /hivelog_ext.go: -------------------------------------------------------------------------------- 1 | package regparser 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | type DirtyPage struct { 9 | Reader io.ReaderAt 10 | DataOffset int64 11 | PageOffset uint32 12 | PageSize uint32 13 | } 14 | 15 | func (self DirtyPage) Data() ([]byte, error) { 16 | buf := make([]byte, self.PageSize) 17 | 18 | if n, err := self.Reader.ReadAt(buf, self.DataOffset); err != nil { 19 | return nil, err 20 | } else if n != int(self.PageSize) { 21 | return nil, fmt.Errorf("reader returned unexpected size (got %#x, want %#x)", n, self.PageSize) 22 | } 23 | 24 | return buf, nil 25 | } 26 | 27 | func (self HIVE_LOG_ENTRY) GetDirtyPages() []*DirtyPage { 28 | x := &HIVE_DIRTY_PAGE_REF{} 29 | 30 | pages := make([]*DirtyPage, 0, self.DirtyPagesCount()) 31 | 32 | page_data_offset := self.Offset + self.Profile.Off_HIVE_LOG_ENTRY_DirtyPageRefs + int64(self.DirtyPagesCount())*int64(x.Size()) 33 | 34 | for _, pageRef := range self.DirtyPageRefs() { 35 | page := &DirtyPage{ 36 | Reader: self.Reader, 37 | DataOffset: page_data_offset, 38 | PageOffset: pageRef.PageOffset(), 39 | PageSize: pageRef.PageSize(), 40 | } 41 | page_data_offset += int64(pageRef.PageSize()) 42 | 43 | pages = append(pages, page) 44 | } 45 | 46 | return pages 47 | } 48 | -------------------------------------------------------------------------------- /paths.go: -------------------------------------------------------------------------------- 1 | package regparser 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | // Split the path into components. Note that since registry keys and 9 | // values may contain path separators in their name, we need to ensure 10 | // such names are escaped using quotes. For example: 11 | // HKEY_USERS\S-1-5-21-546003962-2713609280-610790815-1003\Software\Microsoft\Windows\CurrentVersion\Run\"c:\windows\system32\mshta.exe" 12 | var component_quoted_regex = regexp.MustCompile(`^"([^"\\/]*(?:[\\/].[^"\\/]*)*)"`) 13 | var component_unquoted_regex = regexp.MustCompile(`^[\\/]?([^\\/]+)([\\/]?|$)`) 14 | 15 | func SplitComponents(path string) []string { 16 | var components []string 17 | for len(path) > 0 { 18 | match := component_quoted_regex.FindStringSubmatch(path) 19 | if len(match) > 0 { 20 | if len(match[1]) > 0 { 21 | components = append(components, match[1]) 22 | } 23 | path = path[len(match[0]):] 24 | continue 25 | } 26 | match = component_unquoted_regex.FindStringSubmatch(path) 27 | if len(match) > 0 { 28 | if len(match[1]) > 0 { 29 | components = append(components, match[1]) 30 | } 31 | path = path[len(match[0]):] 32 | continue 33 | } 34 | 35 | // This should never happen! 36 | return strings.Split(path, "\\") 37 | } 38 | return components 39 | } 40 | -------------------------------------------------------------------------------- /profile_vtypes.json: -------------------------------------------------------------------------------- 1 | { 2 | "_LARGE_INTEGER": [8, { 3 | "HighPart": [4, ["long", {}]], 4 | "LowPart": [0, ["unsigned long", {}]], 5 | "QuadPart": [0, ["long long", {}]], 6 | "u": [0, ["", {}]] 7 | }], 8 | "_GUID": [16, { 9 | "Data1": [0, ["unsigned long", {}]], 10 | "Data2": [4, ["unsigned short", {}]], 11 | "Data3": [6, ["unsigned short", {}]], 12 | "Data4": [8, ["Array", { 13 | "count": 8, 14 | "target": "unsigned char" 15 | }]] 16 | }], 17 | "_HBASE_BLOCK": [4096, { 18 | "BootRecover": [4092, ["unsigned long", {}]], 19 | "BootType": [4088, ["unsigned long", {}]], 20 | "CheckSum": [508, ["unsigned long", {}]], 21 | "Cluster": [44, ["unsigned long", {}]], 22 | "FileName": [48, ["UnicodeString", { 23 | "Length": 64 24 | }]], 25 | "Flags": [144, ["unsigned long", {}]], 26 | "Format": [32, ["unsigned long", {}]], 27 | "GuidSignature": [164, ["unsigned long", {}]], 28 | "Length": [40, ["unsigned long", {}]], 29 | "LogId": [128, ["_GUID", {}]], 30 | "Major": [20, ["unsigned long", {}]], 31 | "Minor": [24, ["unsigned long", {}]], 32 | "Reserved1": [168, ["Array", { 33 | "count": 85, 34 | "target": "unsigned long" 35 | }]], 36 | "Reserved2": [512, ["Array", { 37 | "count": 882, 38 | "target": "unsigned long" 39 | }]], 40 | "RmId": [112, ["_GUID", {}]], 41 | "RootCell": [36, ["unsigned long", {}]], 42 | "Sequence1": [4, ["unsigned long", {}]], 43 | "Sequence2": [8, ["unsigned long", {}]], 44 | "Signature": [0, ["unsigned long", {}]], 45 | "ThawLogId": [4072, ["_GUID", {}]], 46 | "ThawRmId": [4056, ["_GUID", {}]], 47 | "ThawTmId": [4040, ["_GUID", {}]], 48 | "TimeStamp": [12, ["FileTime", {}]], 49 | "TmId": [148, ["_GUID", {}]], 50 | "Type": [28, ["unsigned long", {}]] 51 | }], 52 | "_HBIN": [32, { 53 | "FileOffset": [4, ["unsigned long", {}]], 54 | "Reserved1": [12, ["Array", { 55 | "count": 2, 56 | "target": "unsigned long" 57 | }]], 58 | "Signature": [0, ["unsigned long", {}]], 59 | "HbinSize": [8, ["unsigned long", {}]], 60 | "Spare": [28, ["unsigned long", {}]], 61 | "TimeStamp": [20, ["FileTime", {}]] 62 | }], 63 | "_HCELL": [12, { 64 | "Next": [0, ["unsigned long", {}]], 65 | "Signature": [4, ["unsigned short"]], 66 | "Data": [4, ["Array", { 67 | "target": "unsigned char" 68 | }]] 69 | }], 70 | "_CM_KEY_NODE": [80, { 71 | "ChildHiveReference": [28, ["Pointer", { 72 | "target": "_HCELL" 73 | }]], 74 | "Class": [48, ["unsigned long", {}]], 75 | "ClassLength": [74, ["unsigned short", {}]], 76 | "Debug": [52, ["BitField", { 77 | "end_bit": 32, 78 | "start_bit": 24, 79 | "target": "unsigned long" 80 | }]], 81 | "Flags": [2, ["unsigned short", {}]], 82 | "LastWriteTime": [4, ["FileTime", {}]], 83 | "MaxClassLen": [56, ["unsigned long", {}]], 84 | "MaxNameLen": [52, ["BitField", { 85 | "end_bit": 16, 86 | "start_bit": 0, 87 | "target": "unsigned long" 88 | }]], 89 | "MaxValueDataLen": [64, ["unsigned long", {}]], 90 | "MaxValueNameLen": [60, ["unsigned long", {}]], 91 | "_Name": [76, ["Array", { 92 | "target": "unsigned char", 93 | "count": 1 94 | }]], 95 | "NameLength": [72, ["unsigned short", {}]], 96 | "Parent": [16, ["unsigned long", {}]], 97 | "Security": [44, ["unsigned long", {}]], 98 | "Signature": [0, ["unsigned short", {}]], 99 | "Spare": [12, ["unsigned long", {}]], 100 | "SubKeyCounts": [20, ["Array", { 101 | "count": 2, 102 | "target": "unsigned long" 103 | }]], 104 | "SubKeyLists": [28, ["Array", { 105 | "count": 2, 106 | "target": "unsigned long" 107 | }]], 108 | "UserFlags": [52, ["BitField", { 109 | "end_bit": 20, 110 | "start_bit": 16, 111 | "target": "unsigned long" 112 | }]], 113 | "ValueList": [36, ["_CHILD_LIST", {}]], 114 | "VirtControlFlags": [52, ["BitField", { 115 | "end_bit": 24, 116 | "start_bit": 20, 117 | "target": "unsigned long" 118 | }]], 119 | "WorkVar": [68, ["unsigned long", {}]] 120 | }], 121 | "_CHILD_LIST": [8, { 122 | "Count": [0, ["unsigned long", {}]], 123 | "List": [4, ["unsigned long", {}]] 124 | }], 125 | "_CM_BIG_DATA": [8, { 126 | "Count": [2, ["unsigned short", {}]], 127 | "List": [4, ["unsigned long", {}]], 128 | "Signature": [0, ["unsigned short", {}]] 129 | }], 130 | "_CM_CACHED_VALUE_INDEX": [88, { 131 | "CellIndex": [0, ["unsigned long", {}]], 132 | "Data": [8, ["", {}]] 133 | }], 134 | "_CM_CELL_REMAP_BLOCK": [8, { 135 | "NewCell": [4, ["unsigned long", {}]], 136 | "OldCell": [0, ["unsigned long", {}]] 137 | }], 138 | "_CM_FULL_RESOURCE_DESCRIPTOR": [36, { 139 | "BusNumber": [4, ["unsigned long", {}]], 140 | "InterfaceType": [0, ["Enumeration", { 141 | "enum_name": "_INTERFACE_TYPE", 142 | "target": "long" 143 | }]], 144 | "PartialResourceList": [8, ["_CM_PARTIAL_RESOURCE_LIST", {}]] 145 | }], 146 | "_CM_INDEX_HINT_BLOCK": [8, { 147 | "Count": [0, ["unsigned long", {}]], 148 | "HashKey": [4, ["Array", { 149 | "count": 1, 150 | "target": "unsigned long" 151 | }]] 152 | }], 153 | "_CM_KEY_INDEX": [8, { 154 | "Count": [2, ["unsigned short", {}]], 155 | "List": [4, ["Array", { 156 | "count": 1, 157 | "target": "unsigned long" 158 | }]], 159 | "Signature": [0, ["unsigned short", {}]] 160 | }], 161 | "_CM_KEY_INDEX_FAST": [8, { 162 | "Count": [2, ["unsigned short", {}]], 163 | "List": [4, ["Array", { 164 | "count": 1, 165 | "target": "_CM_KEY_INDEX_FAST_ELEMENT" 166 | }]], 167 | "Signature": [0, ["unsigned short", {}]] 168 | }], 169 | "_CM_KEY_INDEX_FAST_ELEMENT": [8, { 170 | "NodeOffset": [0, ["unsigned long"]], 171 | "Index": [4, ["unsigned long"]] 172 | }], 173 | "_CM_KEY_VALUE": [24, { 174 | "Data": [8, ["unsigned long", {}]], 175 | "DataLength": [4, ["unsigned long", {}]], 176 | "Flags": [16, ["unsigned short", {}]], 177 | "Name": [20, ["UnicodeString", { 178 | "length": 1 179 | }]], 180 | "NameLength": [2, ["unsigned short", {}]], 181 | "Signature": [0, ["unsigned short", {}]], 182 | "Spare": [18, ["unsigned short", {}]], 183 | "Type": [12, ["unsigned long", {}]] 184 | }], 185 | "HIVE_LOG_ENTRY": [40, { 186 | "Signature": [0, ["unsigned long", {}]], 187 | "LogEntrySize": [4, ["unsigned long", {}]], 188 | "Flags": [8, ["unsigned long", {}]], 189 | "SequenceNumber": [12, ["unsigned long", {}]], 190 | "HiveBinsDataSize": [16, ["unsigned long", {}]], 191 | "DirtyPagesCount": [20, ["unsigned long", {}]], 192 | "Hash1": [24, ["unsigned long long", {}]], 193 | "Hash2": [32, ["unsigned long long", {}]], 194 | "DirtyPageRefs": [40, ["Array", { 195 | "target": "HIVE_DIRTY_PAGE_REF", 196 | "dynamic_count": "DirtyPagesCount" 197 | }]] 198 | }], 199 | "HIVE_DIRTY_PAGE_REF": [8, { 200 | "PageOffset": [0, ["unsigned long", {}]], 201 | "PageSize": [4, ["unsigned long", {}]] 202 | }] 203 | } 204 | -------------------------------------------------------------------------------- /registry.go: -------------------------------------------------------------------------------- 1 | package regparser 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "log" 9 | "os" 10 | "strings" 11 | ) 12 | 13 | // Model a registry hive with this object. 14 | type Registry struct { 15 | Reader io.ReaderAt 16 | 17 | Profile *RegistryProfile 18 | BaseBlock *HBASE_BLOCK 19 | } 20 | 21 | func NewRegistry(reader io.ReaderAt) (*Registry, error) { 22 | self := &Registry{ 23 | Reader: reader, 24 | Profile: NewRegistryProfile(), 25 | } 26 | self.BaseBlock = self.Profile.HBASE_BLOCK(reader, 0) 27 | if self.BaseBlock.Signature() != 0x66676572 { 28 | return nil, errors.New("File does not have registry magic.") 29 | } 30 | 31 | return self, nil 32 | } 33 | 34 | // A helper method to open a key by path. 35 | func (self *Registry) OpenKey(key_path string) *CM_KEY_NODE { 36 | root_cell := self.Profile.HCELL(self.Reader, 37 | 0x1000+int64(self.BaseBlock.RootCell())) 38 | 39 | nk := root_cell.KeyNode() 40 | if nk == nil { 41 | return nil 42 | } 43 | 44 | subkey_match: 45 | for _, component := range SplitComponents(key_path) { 46 | if component == "" { 47 | continue 48 | } 49 | 50 | component = strings.ToLower(component) 51 | 52 | for _, subkey := range nk.Subkeys() { 53 | if strings.ToLower(subkey.Name()) == component { 54 | nk = subkey 55 | continue subkey_match 56 | } 57 | } 58 | 59 | // If we get here we could not find the key: 60 | return nil 61 | } 62 | 63 | return nk 64 | } 65 | 66 | // RecoverHive copies the hive to another file and applies the dirty pages 67 | // from the log files. 68 | // 69 | // Returns a File object pointing to the recovered Hive. The caller is 70 | // responsible for deleting the recovered hive file. 71 | func RecoverHive(hive *os.File, logFiles ...*os.File) (*os.File, error) { 72 | var ( 73 | exitErr error 74 | baseRegistry *Registry 75 | logRegistries []*Registry 76 | headerUpdateNeeded bool 77 | ) 78 | 79 | newHiveFile, err := os.CreateTemp(os.TempDir(), "") 80 | if err != nil { 81 | return nil, fmt.Errorf("cannot create new hive file (%v)", err) 82 | } 83 | 84 | _, err = io.Copy(newHiveFile, hive) 85 | if err != nil { 86 | exitErr = fmt.Errorf("cannot copy hive (%v)", err) 87 | goto fail 88 | } 89 | 90 | baseRegistry, err = NewRegistry(newHiveFile) 91 | if err != nil { 92 | exitErr = fmt.Errorf("cannot parse base hive (%v)", err) 93 | goto fail 94 | } 95 | 96 | if calculateChecksum(newHiveFile) != baseRegistry.BaseBlock.CheckSum() { 97 | headerUpdateNeeded = true 98 | } 99 | 100 | for _, l := range logFiles { 101 | if s, err := l.Stat(); err != nil { 102 | exitErr = fmt.Errorf("stat syscall on file %s failed (%v)", 103 | l.Name(), err) 104 | goto fail 105 | } else if s.Size() == 0 { 106 | fmt.Printf("[info] Registry Hive %s empty: skipping\n", l.Name()) 107 | continue 108 | } 109 | logReg, err := NewRegistry(l) 110 | if err != nil { 111 | exitErr = fmt.Errorf("invalid Registry Hive in log file %s (%v)", 112 | l.Name(), err) 113 | goto fail 114 | } 115 | 116 | if logReg.BaseBlock.Type() == 1 || logReg.BaseBlock.Type() == 2 { 117 | fmt.Printf("[warn] version %d of log file '%s' not supported. skipping\n", 118 | logReg.BaseBlock.Type(), l.Name()) 119 | continue 120 | } 121 | 122 | if logReg.BaseBlock.Sequence1() < baseRegistry.BaseBlock.Sequence2() { 123 | log.Printf("[info] skipping log file %s, sequence number mismatch (log starts at sequence number %d, base is already at %d)\n", 124 | l.Name(), logReg.BaseBlock.Sequence1(), 125 | baseRegistry.BaseBlock.Sequence2()) 126 | continue 127 | } 128 | 129 | logRegistries = append(logRegistries, logReg) 130 | } 131 | 132 | if len(logRegistries) == 2 { 133 | // find the one to apply first 134 | one := logRegistries[0] 135 | two := logRegistries[1] 136 | 137 | if one.BaseBlock.Sequence1() > two.BaseBlock.Sequence1() { 138 | logRegistries[0], logRegistries[1] = logRegistries[1], logRegistries[0] 139 | } 140 | } else if len(logRegistries) > 2 { 141 | exitErr = fmt.Errorf("got more than two log files, unsupported") 142 | goto fail 143 | } 144 | 145 | // iterate log entries and write dirty pages to base Hive 146 | for _, logRegistry := range logRegistries { 147 | logEntryOffset := int64(0x200) // hard-coded offset, always 0x200 148 | 149 | var hbinsSize, sequenceNumber, hiveFlags uint32 150 | 151 | for hasBytesLeft(logRegistry.Reader, logEntryOffset) { 152 | logEntry := &HIVE_LOG_ENTRY{ 153 | Reader: logRegistry.Reader, 154 | Offset: logEntryOffset, 155 | Profile: NewRegistryProfile()} 156 | 157 | if logEntry.Signature() != 0x454C7648 { // HvLE magic bytes 158 | exitErr = fmt.Errorf("HvLE block at %#x has an invalid signature", logEntryOffset) 159 | goto fail 160 | } 161 | 162 | if logEntry.SequenceNumber() == logRegistry.BaseBlock.Sequence2() { 163 | // we reached the last valid log entry of the file 164 | break 165 | } 166 | 167 | for _, page := range logEntry.GetDirtyPages() { 168 | data, err := page.Data() 169 | if err != nil { 170 | exitErr = fmt.Errorf("cannot read dirty page data (%v)", err) 171 | goto fail 172 | } 173 | 174 | // offset to first hbin is always 0x1000, page offset is relative to that 175 | n, err := newHiveFile.WriteAt(data, int64(page.PageOffset)+0x1000) 176 | if n != int(page.PageSize) || err != nil { 177 | exitErr = fmt.Errorf("cannot write page of size %#x at offset %#x (%v)", 178 | page.PageSize, page.PageOffset, err) 179 | goto fail 180 | } 181 | } 182 | 183 | hbinsSize = logEntry.HiveBinsDataSize() 184 | sequenceNumber = logEntry.SequenceNumber() 185 | hiveFlags = logEntry.Flags() 186 | 187 | logEntryOffset += int64(logEntry.LogEntrySize()) 188 | } 189 | 190 | if headerUpdateNeeded { 191 | buf := make([]byte, 4) 192 | 193 | binary.LittleEndian.PutUint32(buf, sequenceNumber) 194 | newHiveFile.WriteAt(buf, 4) 195 | newHiveFile.WriteAt(buf, 8) 196 | 197 | binary.LittleEndian.PutUint32(buf, hbinsSize) 198 | newHiveFile.WriteAt(buf, 0x28) 199 | 200 | binary.LittleEndian.PutUint32(buf, hiveFlags) 201 | newHiveFile.WriteAt(buf, 0x90) 202 | 203 | binary.LittleEndian.PutUint32(buf, calculateChecksum(newHiveFile)) 204 | newHiveFile.WriteAt(buf, 0x1fC) 205 | } 206 | } 207 | 208 | return newHiveFile, nil 209 | 210 | fail: 211 | newHiveFile.Close() 212 | os.Remove(newHiveFile.Name()) 213 | return nil, exitErr 214 | } 215 | 216 | func hasBytesLeft(reader io.ReaderAt, offset int64) bool { 217 | buf := make([]byte, 1) 218 | if n, err := reader.ReadAt(buf, offset); n != 1 || err != nil { 219 | return false 220 | } 221 | return true 222 | } 223 | 224 | func calculateChecksum(reader io.ReaderAt) uint32 { 225 | buf := make([]byte, 0x1fc) 226 | _, err := reader.ReadAt(buf, 0) 227 | if err != nil { 228 | return 0 229 | } 230 | 231 | checksum := uint32(0) 232 | 233 | for i := 0; i < 0x1fc; i += 4 { 234 | checksum ^= binary.LittleEndian.Uint32(buf[i : i+4]) 235 | } 236 | 237 | if checksum == 0 { 238 | return 1 239 | } else if checksum == 0xFFFFFFFF { 240 | return 0xFFFFFFFE 241 | } else { 242 | return checksum 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /regparser_gen.go: -------------------------------------------------------------------------------- 1 | package regparser 2 | 3 | // Autogenerated code from profile_vtypes.json. Do not edit. 4 | 5 | import ( 6 | "bytes" 7 | "encoding/binary" 8 | "fmt" 9 | "io" 10 | "sort" 11 | "strings" 12 | "unicode/utf16" 13 | "unicode/utf8" 14 | ) 15 | 16 | var ( 17 | // Depending on autogenerated code we may use this. Add a reference 18 | // to shut the compiler up. 19 | _ = bytes.MinRead 20 | _ = fmt.Sprintf 21 | _ = utf16.Decode 22 | _ = binary.LittleEndian 23 | _ = utf8.RuneError 24 | _ = sort.Strings 25 | _ = strings.Join 26 | _ = io.Copy 27 | ) 28 | 29 | func indent(text string) string { 30 | result := []string{} 31 | lines := strings.Split(text, "\n") 32 | for _, line := range lines { 33 | result = append(result, " "+line) 34 | } 35 | return strings.Join(result, "\n") 36 | } 37 | 38 | type RegistryProfile struct { 39 | Off_HIVE_DIRTY_PAGE_REF_PageOffset int64 40 | Off_HIVE_DIRTY_PAGE_REF_PageSize int64 41 | Off_HIVE_LOG_ENTRY_Signature int64 42 | Off_HIVE_LOG_ENTRY_LogEntrySize int64 43 | Off_HIVE_LOG_ENTRY_Flags int64 44 | Off_HIVE_LOG_ENTRY_SequenceNumber int64 45 | Off_HIVE_LOG_ENTRY_HiveBinsDataSize int64 46 | Off_HIVE_LOG_ENTRY_DirtyPagesCount int64 47 | Off_HIVE_LOG_ENTRY_Hash1 int64 48 | Off_HIVE_LOG_ENTRY_Hash2 int64 49 | Off_HIVE_LOG_ENTRY_DirtyPageRefs int64 50 | Off_CHILD_LIST_Count int64 51 | Off_CHILD_LIST_List int64 52 | Off_CM_BIG_DATA_Count int64 53 | Off_CM_BIG_DATA_List int64 54 | Off_CM_BIG_DATA_Signature int64 55 | Off_CM_KEY_INDEX_Count int64 56 | Off_CM_KEY_INDEX_List int64 57 | Off_CM_KEY_INDEX_Signature int64 58 | Off_CM_KEY_INDEX_FAST_Count int64 59 | Off_CM_KEY_INDEX_FAST_List int64 60 | Off_CM_KEY_INDEX_FAST_Signature int64 61 | Off_CM_KEY_INDEX_FAST_ELEMENT_NodeOffset int64 62 | Off_CM_KEY_INDEX_FAST_ELEMENT_Index int64 63 | Off_CM_KEY_NODE_ChildHiveReference int64 64 | Off_CM_KEY_NODE_Class int64 65 | Off_CM_KEY_NODE_ClassLength int64 66 | Off_CM_KEY_NODE_Debug int64 67 | Off_CM_KEY_NODE_Flags int64 68 | Off_CM_KEY_NODE_LastWriteTime int64 69 | Off_CM_KEY_NODE_MaxClassLen int64 70 | Off_CM_KEY_NODE_MaxNameLen int64 71 | Off_CM_KEY_NODE_MaxValueDataLen int64 72 | Off_CM_KEY_NODE_MaxValueNameLen int64 73 | Off_CM_KEY_NODE__Name int64 74 | Off_CM_KEY_NODE_NameLength int64 75 | Off_CM_KEY_NODE_Parent int64 76 | Off_CM_KEY_NODE_Security int64 77 | Off_CM_KEY_NODE_Signature int64 78 | Off_CM_KEY_NODE_Spare int64 79 | Off_CM_KEY_NODE_SubKeyCounts int64 80 | Off_CM_KEY_NODE_SubKeyLists int64 81 | Off_CM_KEY_NODE_UserFlags int64 82 | Off_CM_KEY_NODE_ValueList int64 83 | Off_CM_KEY_NODE_VirtControlFlags int64 84 | Off_CM_KEY_NODE_WorkVar int64 85 | Off_CM_KEY_VALUE_Data int64 86 | Off_CM_KEY_VALUE_DataLength int64 87 | Off_CM_KEY_VALUE_Flags int64 88 | Off_CM_KEY_VALUE_Name int64 89 | Off_CM_KEY_VALUE_NameLength int64 90 | Off_CM_KEY_VALUE_Signature int64 91 | Off_CM_KEY_VALUE_Spare int64 92 | Off_CM_KEY_VALUE_Type int64 93 | Off_GUID_Data1 int64 94 | Off_GUID_Data2 int64 95 | Off_GUID_Data3 int64 96 | Off_GUID_Data4 int64 97 | Off_HBASE_BLOCK_BootRecover int64 98 | Off_HBASE_BLOCK_BootType int64 99 | Off_HBASE_BLOCK_CheckSum int64 100 | Off_HBASE_BLOCK_Cluster int64 101 | Off_HBASE_BLOCK_FileName int64 102 | Off_HBASE_BLOCK_Flags int64 103 | Off_HBASE_BLOCK_Format int64 104 | Off_HBASE_BLOCK_GuidSignature int64 105 | Off_HBASE_BLOCK_Length int64 106 | Off_HBASE_BLOCK_LogId int64 107 | Off_HBASE_BLOCK_Major int64 108 | Off_HBASE_BLOCK_Minor int64 109 | Off_HBASE_BLOCK_Reserved1 int64 110 | Off_HBASE_BLOCK_Reserved2 int64 111 | Off_HBASE_BLOCK_RmId int64 112 | Off_HBASE_BLOCK_RootCell int64 113 | Off_HBASE_BLOCK_Sequence1 int64 114 | Off_HBASE_BLOCK_Sequence2 int64 115 | Off_HBASE_BLOCK_Signature int64 116 | Off_HBASE_BLOCK_ThawLogId int64 117 | Off_HBASE_BLOCK_ThawRmId int64 118 | Off_HBASE_BLOCK_ThawTmId int64 119 | Off_HBASE_BLOCK_TimeStamp int64 120 | Off_HBASE_BLOCK_TmId int64 121 | Off_HBASE_BLOCK_Type int64 122 | Off_HBIN_FileOffset int64 123 | Off_HBIN_Reserved1 int64 124 | Off_HBIN_Signature int64 125 | Off_HBIN_HbinSize int64 126 | Off_HBIN_Spare int64 127 | Off_HBIN_TimeStamp int64 128 | Off_HCELL_Next int64 129 | Off_HCELL_Signature int64 130 | Off_HCELL_Data int64 131 | Off_LARGE_INTEGER_HighPart int64 132 | Off_LARGE_INTEGER_LowPart int64 133 | Off_LARGE_INTEGER_QuadPart int64 134 | } 135 | 136 | func NewRegistryProfile() *RegistryProfile { 137 | // Specific offsets can be tweaked to cater for slight version mismatches. 138 | self := &RegistryProfile{0, 4, 0, 4, 8, 12, 16, 20, 24, 32, 40, 0, 4, 2, 4, 0, 2, 4, 0, 2, 4, 0, 0, 4, 28, 48, 74, 52, 2, 4, 56, 52, 64, 60, 76, 72, 16, 44, 0, 12, 20, 28, 52, 36, 52, 68, 8, 4, 16, 20, 2, 0, 18, 12, 0, 4, 6, 8, 4092, 4088, 508, 44, 48, 144, 32, 164, 40, 128, 20, 24, 168, 512, 112, 36, 4, 8, 0, 4072, 4056, 4040, 12, 148, 28, 4, 12, 0, 8, 28, 20, 0, 4, 4, 4, 0, 0} 139 | return self 140 | } 141 | 142 | func (self *RegistryProfile) HIVE_DIRTY_PAGE_REF(reader io.ReaderAt, offset int64) *HIVE_DIRTY_PAGE_REF { 143 | return &HIVE_DIRTY_PAGE_REF{Reader: reader, Offset: offset, Profile: self} 144 | } 145 | 146 | func (self *RegistryProfile) HIVE_LOG_ENTRY(reader io.ReaderAt, offset int64) *HIVE_LOG_ENTRY { 147 | return &HIVE_LOG_ENTRY{Reader: reader, Offset: offset, Profile: self} 148 | } 149 | 150 | func (self *RegistryProfile) CHILD_LIST(reader io.ReaderAt, offset int64) *CHILD_LIST { 151 | return &CHILD_LIST{Reader: reader, Offset: offset, Profile: self} 152 | } 153 | 154 | func (self *RegistryProfile) CM_BIG_DATA(reader io.ReaderAt, offset int64) *CM_BIG_DATA { 155 | return &CM_BIG_DATA{Reader: reader, Offset: offset, Profile: self} 156 | } 157 | 158 | func (self *RegistryProfile) CM_KEY_INDEX(reader io.ReaderAt, offset int64) *CM_KEY_INDEX { 159 | return &CM_KEY_INDEX{Reader: reader, Offset: offset, Profile: self} 160 | } 161 | 162 | func (self *RegistryProfile) CM_KEY_INDEX_FAST(reader io.ReaderAt, offset int64) *CM_KEY_INDEX_FAST { 163 | return &CM_KEY_INDEX_FAST{Reader: reader, Offset: offset, Profile: self} 164 | } 165 | 166 | func (self *RegistryProfile) CM_KEY_INDEX_FAST_ELEMENT(reader io.ReaderAt, offset int64) *CM_KEY_INDEX_FAST_ELEMENT { 167 | return &CM_KEY_INDEX_FAST_ELEMENT{Reader: reader, Offset: offset, Profile: self} 168 | } 169 | 170 | func (self *RegistryProfile) CM_KEY_NODE(reader io.ReaderAt, offset int64) *CM_KEY_NODE { 171 | return &CM_KEY_NODE{Reader: reader, Offset: offset, Profile: self} 172 | } 173 | 174 | func (self *RegistryProfile) CM_KEY_VALUE(reader io.ReaderAt, offset int64) *CM_KEY_VALUE { 175 | return &CM_KEY_VALUE{Reader: reader, Offset: offset, Profile: self} 176 | } 177 | 178 | func (self *RegistryProfile) GUID(reader io.ReaderAt, offset int64) *GUID { 179 | return &GUID{Reader: reader, Offset: offset, Profile: self} 180 | } 181 | 182 | func (self *RegistryProfile) HBASE_BLOCK(reader io.ReaderAt, offset int64) *HBASE_BLOCK { 183 | return &HBASE_BLOCK{Reader: reader, Offset: offset, Profile: self} 184 | } 185 | 186 | func (self *RegistryProfile) HBIN(reader io.ReaderAt, offset int64) *HBIN { 187 | return &HBIN{Reader: reader, Offset: offset, Profile: self} 188 | } 189 | 190 | func (self *RegistryProfile) HCELL(reader io.ReaderAt, offset int64) *HCELL { 191 | return &HCELL{Reader: reader, Offset: offset, Profile: self} 192 | } 193 | 194 | func (self *RegistryProfile) LARGE_INTEGER(reader io.ReaderAt, offset int64) *LARGE_INTEGER { 195 | return &LARGE_INTEGER{Reader: reader, Offset: offset, Profile: self} 196 | } 197 | 198 | type HIVE_DIRTY_PAGE_REF struct { 199 | Reader io.ReaderAt 200 | Offset int64 201 | Profile *RegistryProfile 202 | } 203 | 204 | func (self *HIVE_DIRTY_PAGE_REF) Size() int { 205 | return 8 206 | } 207 | 208 | func (self *HIVE_DIRTY_PAGE_REF) PageOffset() uint32 { 209 | return ParseUint32(self.Reader, self.Profile.Off_HIVE_DIRTY_PAGE_REF_PageOffset+self.Offset) 210 | } 211 | 212 | func (self *HIVE_DIRTY_PAGE_REF) PageSize() uint32 { 213 | return ParseUint32(self.Reader, self.Profile.Off_HIVE_DIRTY_PAGE_REF_PageSize+self.Offset) 214 | } 215 | 216 | type HIVE_LOG_ENTRY struct { 217 | Reader io.ReaderAt 218 | Offset int64 219 | Profile *RegistryProfile 220 | } 221 | 222 | func (self *HIVE_LOG_ENTRY) Size() int { 223 | return 40 224 | } 225 | 226 | func (self *HIVE_LOG_ENTRY) Signature() uint32 { 227 | return ParseUint32(self.Reader, self.Profile.Off_HIVE_LOG_ENTRY_Signature+self.Offset) 228 | } 229 | 230 | func (self *HIVE_LOG_ENTRY) LogEntrySize() uint32 { 231 | return ParseUint32(self.Reader, self.Profile.Off_HIVE_LOG_ENTRY_LogEntrySize+self.Offset) 232 | } 233 | 234 | func (self *HIVE_LOG_ENTRY) Flags() uint32 { 235 | return ParseUint32(self.Reader, self.Profile.Off_HIVE_LOG_ENTRY_Flags+self.Offset) 236 | } 237 | 238 | func (self *HIVE_LOG_ENTRY) SequenceNumber() uint32 { 239 | return ParseUint32(self.Reader, self.Profile.Off_HIVE_LOG_ENTRY_SequenceNumber+self.Offset) 240 | } 241 | 242 | func (self *HIVE_LOG_ENTRY) HiveBinsDataSize() uint32 { 243 | return ParseUint32(self.Reader, self.Profile.Off_HIVE_LOG_ENTRY_HiveBinsDataSize+self.Offset) 244 | } 245 | 246 | func (self *HIVE_LOG_ENTRY) DirtyPagesCount() uint32 { 247 | return ParseUint32(self.Reader, self.Profile.Off_HIVE_LOG_ENTRY_DirtyPagesCount+self.Offset) 248 | } 249 | 250 | func (self *HIVE_LOG_ENTRY) Hash1() uint64 { 251 | return ParseUint64(self.Reader, self.Profile.Off_HIVE_LOG_ENTRY_Hash1+self.Offset) 252 | } 253 | 254 | func (self *HIVE_LOG_ENTRY) Hash2() uint64 { 255 | return ParseUint64(self.Reader, self.Profile.Off_HIVE_LOG_ENTRY_Hash2+self.Offset) 256 | } 257 | 258 | func (self *HIVE_LOG_ENTRY) DirtyPageRefs() []*HIVE_DIRTY_PAGE_REF { 259 | return ParseArray_HIVE_DIRTY_PAGE_REF(self.Profile, self.Reader, self.Profile.Off_HIVE_LOG_ENTRY_DirtyPageRefs+self.Offset, int(self.DirtyPagesCount())) 260 | } 261 | 262 | type CHILD_LIST struct { 263 | Reader io.ReaderAt 264 | Offset int64 265 | Profile *RegistryProfile 266 | } 267 | 268 | func (self *CHILD_LIST) Size() int { 269 | return 8 270 | } 271 | 272 | func (self *CHILD_LIST) Count() uint32 { 273 | return ParseUint32(self.Reader, self.Profile.Off_CHILD_LIST_Count+self.Offset) 274 | } 275 | 276 | func (self *CHILD_LIST) List() uint32 { 277 | return ParseUint32(self.Reader, self.Profile.Off_CHILD_LIST_List+self.Offset) 278 | } 279 | 280 | type CM_BIG_DATA struct { 281 | Reader io.ReaderAt 282 | Offset int64 283 | Profile *RegistryProfile 284 | } 285 | 286 | func (self *CM_BIG_DATA) Size() int { 287 | return 8 288 | } 289 | 290 | func (self *CM_BIG_DATA) Count() uint16 { 291 | return ParseUint16(self.Reader, self.Profile.Off_CM_BIG_DATA_Count+self.Offset) 292 | } 293 | 294 | func (self *CM_BIG_DATA) List() uint32 { 295 | return ParseUint32(self.Reader, self.Profile.Off_CM_BIG_DATA_List+self.Offset) 296 | } 297 | 298 | func (self *CM_BIG_DATA) Signature() uint16 { 299 | return ParseUint16(self.Reader, self.Profile.Off_CM_BIG_DATA_Signature+self.Offset) 300 | } 301 | 302 | type CM_KEY_INDEX struct { 303 | Reader io.ReaderAt 304 | Offset int64 305 | Profile *RegistryProfile 306 | } 307 | 308 | func (self *CM_KEY_INDEX) Size() int { 309 | return 8 310 | } 311 | 312 | func (self *CM_KEY_INDEX) Count() uint16 { 313 | return ParseUint16(self.Reader, self.Profile.Off_CM_KEY_INDEX_Count+self.Offset) 314 | } 315 | 316 | func (self *CM_KEY_INDEX) List() []uint32 { 317 | return ParseArray_uint32(self.Profile, self.Reader, self.Profile.Off_CM_KEY_INDEX_List+self.Offset, 1) 318 | } 319 | 320 | func (self *CM_KEY_INDEX) Signature() uint16 { 321 | return ParseUint16(self.Reader, self.Profile.Off_CM_KEY_INDEX_Signature+self.Offset) 322 | } 323 | 324 | type CM_KEY_INDEX_FAST struct { 325 | Reader io.ReaderAt 326 | Offset int64 327 | Profile *RegistryProfile 328 | } 329 | 330 | func (self *CM_KEY_INDEX_FAST) Size() int { 331 | return 8 332 | } 333 | 334 | func (self *CM_KEY_INDEX_FAST) Count() uint16 { 335 | return ParseUint16(self.Reader, self.Profile.Off_CM_KEY_INDEX_FAST_Count+self.Offset) 336 | } 337 | 338 | func (self *CM_KEY_INDEX_FAST) List() []*CM_KEY_INDEX_FAST_ELEMENT { 339 | return ParseArray_CM_KEY_INDEX_FAST_ELEMENT(self.Profile, self.Reader, self.Profile.Off_CM_KEY_INDEX_FAST_List+self.Offset, 1) 340 | } 341 | 342 | func (self *CM_KEY_INDEX_FAST) Signature() uint16 { 343 | return ParseUint16(self.Reader, self.Profile.Off_CM_KEY_INDEX_FAST_Signature+self.Offset) 344 | } 345 | 346 | type CM_KEY_INDEX_FAST_ELEMENT struct { 347 | Reader io.ReaderAt 348 | Offset int64 349 | Profile *RegistryProfile 350 | } 351 | 352 | func (self *CM_KEY_INDEX_FAST_ELEMENT) Size() int { 353 | return 8 354 | } 355 | 356 | func (self *CM_KEY_INDEX_FAST_ELEMENT) NodeOffset() uint32 { 357 | return ParseUint32(self.Reader, self.Profile.Off_CM_KEY_INDEX_FAST_ELEMENT_NodeOffset+self.Offset) 358 | } 359 | 360 | func (self *CM_KEY_INDEX_FAST_ELEMENT) Index() uint32 { 361 | return ParseUint32(self.Reader, self.Profile.Off_CM_KEY_INDEX_FAST_ELEMENT_Index+self.Offset) 362 | } 363 | 364 | type CM_KEY_NODE struct { 365 | Reader io.ReaderAt 366 | Offset int64 367 | Profile *RegistryProfile 368 | } 369 | 370 | func (self *CM_KEY_NODE) Size() int { 371 | return 80 372 | } 373 | 374 | func (self *CM_KEY_NODE) ChildHiveReference() *HCELL { 375 | deref := ParseUint64(self.Reader, self.Profile.Off_CM_KEY_NODE_ChildHiveReference+self.Offset) 376 | return self.Profile.HCELL(self.Reader, int64(deref)) 377 | } 378 | 379 | func (self *CM_KEY_NODE) Class() uint32 { 380 | return ParseUint32(self.Reader, self.Profile.Off_CM_KEY_NODE_Class+self.Offset) 381 | } 382 | 383 | func (self *CM_KEY_NODE) ClassLength() uint16 { 384 | return ParseUint16(self.Reader, self.Profile.Off_CM_KEY_NODE_ClassLength+self.Offset) 385 | } 386 | 387 | func (self *CM_KEY_NODE) Debug() uint64 { 388 | value := ParseUint32(self.Reader, self.Profile.Off_CM_KEY_NODE_Debug+self.Offset) 389 | return (uint64(value) & 0xffffffff) >> 0x18 390 | } 391 | 392 | func (self *CM_KEY_NODE) Flags() uint16 { 393 | return ParseUint16(self.Reader, self.Profile.Off_CM_KEY_NODE_Flags+self.Offset) 394 | } 395 | 396 | func (self *CM_KEY_NODE) LastWriteTime() *FileTime { 397 | return self.Profile.FileTime(self.Reader, self.Profile.Off_CM_KEY_NODE_LastWriteTime+self.Offset) 398 | } 399 | 400 | func (self *CM_KEY_NODE) MaxClassLen() uint32 { 401 | return ParseUint32(self.Reader, self.Profile.Off_CM_KEY_NODE_MaxClassLen+self.Offset) 402 | } 403 | 404 | func (self *CM_KEY_NODE) MaxNameLen() uint64 { 405 | value := ParseUint32(self.Reader, self.Profile.Off_CM_KEY_NODE_MaxNameLen+self.Offset) 406 | return (uint64(value) & 0xffff) >> 0x0 407 | } 408 | 409 | func (self *CM_KEY_NODE) MaxValueDataLen() uint32 { 410 | return ParseUint32(self.Reader, self.Profile.Off_CM_KEY_NODE_MaxValueDataLen+self.Offset) 411 | } 412 | 413 | func (self *CM_KEY_NODE) MaxValueNameLen() uint32 { 414 | return ParseUint32(self.Reader, self.Profile.Off_CM_KEY_NODE_MaxValueNameLen+self.Offset) 415 | } 416 | 417 | func (self *CM_KEY_NODE) _Name() []byte { 418 | return ParseArray_byte(self.Profile, self.Reader, self.Profile.Off_CM_KEY_NODE__Name+self.Offset, 1) 419 | } 420 | 421 | func (self *CM_KEY_NODE) NameLength() uint16 { 422 | return ParseUint16(self.Reader, self.Profile.Off_CM_KEY_NODE_NameLength+self.Offset) 423 | } 424 | 425 | func (self *CM_KEY_NODE) Parent() uint32 { 426 | return ParseUint32(self.Reader, self.Profile.Off_CM_KEY_NODE_Parent+self.Offset) 427 | } 428 | 429 | func (self *CM_KEY_NODE) Security() uint32 { 430 | return ParseUint32(self.Reader, self.Profile.Off_CM_KEY_NODE_Security+self.Offset) 431 | } 432 | 433 | func (self *CM_KEY_NODE) Signature() uint16 { 434 | return ParseUint16(self.Reader, self.Profile.Off_CM_KEY_NODE_Signature+self.Offset) 435 | } 436 | 437 | func (self *CM_KEY_NODE) Spare() uint32 { 438 | return ParseUint32(self.Reader, self.Profile.Off_CM_KEY_NODE_Spare+self.Offset) 439 | } 440 | 441 | func (self *CM_KEY_NODE) SubKeyCounts() []uint32 { 442 | return ParseArray_uint32(self.Profile, self.Reader, self.Profile.Off_CM_KEY_NODE_SubKeyCounts+self.Offset, 2) 443 | } 444 | 445 | func (self *CM_KEY_NODE) SubKeyLists() []uint32 { 446 | return ParseArray_uint32(self.Profile, self.Reader, self.Profile.Off_CM_KEY_NODE_SubKeyLists+self.Offset, 2) 447 | } 448 | 449 | func (self *CM_KEY_NODE) UserFlags() uint64 { 450 | value := ParseUint32(self.Reader, self.Profile.Off_CM_KEY_NODE_UserFlags+self.Offset) 451 | return (uint64(value) & 0xfffff) >> 0x10 452 | } 453 | 454 | func (self *CM_KEY_NODE) ValueList() *CHILD_LIST { 455 | return self.Profile.CHILD_LIST(self.Reader, self.Profile.Off_CM_KEY_NODE_ValueList+self.Offset) 456 | } 457 | 458 | func (self *CM_KEY_NODE) VirtControlFlags() uint64 { 459 | value := ParseUint32(self.Reader, self.Profile.Off_CM_KEY_NODE_VirtControlFlags+self.Offset) 460 | return (uint64(value) & 0xffffff) >> 0x14 461 | } 462 | 463 | func (self *CM_KEY_NODE) WorkVar() uint32 { 464 | return ParseUint32(self.Reader, self.Profile.Off_CM_KEY_NODE_WorkVar+self.Offset) 465 | } 466 | 467 | type CM_KEY_VALUE struct { 468 | Reader io.ReaderAt 469 | Offset int64 470 | Profile *RegistryProfile 471 | } 472 | 473 | func (self *CM_KEY_VALUE) Size() int { 474 | return 24 475 | } 476 | 477 | func (self *CM_KEY_VALUE) Data() uint32 { 478 | return ParseUint32(self.Reader, self.Profile.Off_CM_KEY_VALUE_Data+self.Offset) 479 | } 480 | 481 | func (self *CM_KEY_VALUE) DataLength() uint32 { 482 | return ParseUint32(self.Reader, self.Profile.Off_CM_KEY_VALUE_DataLength+self.Offset) 483 | } 484 | 485 | func (self *CM_KEY_VALUE) Flags() uint16 { 486 | return ParseUint16(self.Reader, self.Profile.Off_CM_KEY_VALUE_Flags+self.Offset) 487 | } 488 | 489 | func (self *CM_KEY_VALUE) Name() string { 490 | return ParseUTF16String(self.Reader, self.Profile.Off_CM_KEY_VALUE_Name+self.Offset, 1) 491 | } 492 | 493 | func (self *CM_KEY_VALUE) NameLength() uint16 { 494 | return ParseUint16(self.Reader, self.Profile.Off_CM_KEY_VALUE_NameLength+self.Offset) 495 | } 496 | 497 | func (self *CM_KEY_VALUE) Signature() uint16 { 498 | return ParseUint16(self.Reader, self.Profile.Off_CM_KEY_VALUE_Signature+self.Offset) 499 | } 500 | 501 | func (self *CM_KEY_VALUE) Spare() uint16 { 502 | return ParseUint16(self.Reader, self.Profile.Off_CM_KEY_VALUE_Spare+self.Offset) 503 | } 504 | 505 | func (self *CM_KEY_VALUE) Type() uint32 { 506 | return ParseUint32(self.Reader, self.Profile.Off_CM_KEY_VALUE_Type+self.Offset) 507 | } 508 | 509 | type GUID struct { 510 | Reader io.ReaderAt 511 | Offset int64 512 | Profile *RegistryProfile 513 | } 514 | 515 | func (self *GUID) Size() int { 516 | return 16 517 | } 518 | 519 | func (self *GUID) Data1() uint32 { 520 | return ParseUint32(self.Reader, self.Profile.Off_GUID_Data1+self.Offset) 521 | } 522 | 523 | func (self *GUID) Data2() uint16 { 524 | return ParseUint16(self.Reader, self.Profile.Off_GUID_Data2+self.Offset) 525 | } 526 | 527 | func (self *GUID) Data3() uint16 { 528 | return ParseUint16(self.Reader, self.Profile.Off_GUID_Data3+self.Offset) 529 | } 530 | 531 | func (self *GUID) Data4() []byte { 532 | return ParseArray_byte(self.Profile, self.Reader, self.Profile.Off_GUID_Data4+self.Offset, 8) 533 | } 534 | 535 | type HBASE_BLOCK struct { 536 | Reader io.ReaderAt 537 | Offset int64 538 | Profile *RegistryProfile 539 | } 540 | 541 | func (self *HBASE_BLOCK) Size() int { 542 | return 4096 543 | } 544 | 545 | func (self *HBASE_BLOCK) BootRecover() uint32 { 546 | return ParseUint32(self.Reader, self.Profile.Off_HBASE_BLOCK_BootRecover+self.Offset) 547 | } 548 | 549 | func (self *HBASE_BLOCK) BootType() uint32 { 550 | return ParseUint32(self.Reader, self.Profile.Off_HBASE_BLOCK_BootType+self.Offset) 551 | } 552 | 553 | func (self *HBASE_BLOCK) CheckSum() uint32 { 554 | return ParseUint32(self.Reader, self.Profile.Off_HBASE_BLOCK_CheckSum+self.Offset) 555 | } 556 | 557 | func (self *HBASE_BLOCK) Cluster() uint32 { 558 | return ParseUint32(self.Reader, self.Profile.Off_HBASE_BLOCK_Cluster+self.Offset) 559 | } 560 | 561 | func (self *HBASE_BLOCK) FileName() string { 562 | return ParseUTF16String(self.Reader, self.Profile.Off_HBASE_BLOCK_FileName+self.Offset, 64) 563 | } 564 | 565 | func (self *HBASE_BLOCK) Flags() uint32 { 566 | return ParseUint32(self.Reader, self.Profile.Off_HBASE_BLOCK_Flags+self.Offset) 567 | } 568 | 569 | func (self *HBASE_BLOCK) Format() uint32 { 570 | return ParseUint32(self.Reader, self.Profile.Off_HBASE_BLOCK_Format+self.Offset) 571 | } 572 | 573 | func (self *HBASE_BLOCK) GuidSignature() uint32 { 574 | return ParseUint32(self.Reader, self.Profile.Off_HBASE_BLOCK_GuidSignature+self.Offset) 575 | } 576 | 577 | func (self *HBASE_BLOCK) Length() uint32 { 578 | return ParseUint32(self.Reader, self.Profile.Off_HBASE_BLOCK_Length+self.Offset) 579 | } 580 | 581 | func (self *HBASE_BLOCK) LogId() *GUID { 582 | return self.Profile.GUID(self.Reader, self.Profile.Off_HBASE_BLOCK_LogId+self.Offset) 583 | } 584 | 585 | func (self *HBASE_BLOCK) Major() uint32 { 586 | return ParseUint32(self.Reader, self.Profile.Off_HBASE_BLOCK_Major+self.Offset) 587 | } 588 | 589 | func (self *HBASE_BLOCK) Minor() uint32 { 590 | return ParseUint32(self.Reader, self.Profile.Off_HBASE_BLOCK_Minor+self.Offset) 591 | } 592 | 593 | func (self *HBASE_BLOCK) Reserved1() []uint32 { 594 | return ParseArray_uint32(self.Profile, self.Reader, self.Profile.Off_HBASE_BLOCK_Reserved1+self.Offset, 85) 595 | } 596 | 597 | func (self *HBASE_BLOCK) Reserved2() []uint32 { 598 | return ParseArray_uint32(self.Profile, self.Reader, self.Profile.Off_HBASE_BLOCK_Reserved2+self.Offset, 882) 599 | } 600 | 601 | func (self *HBASE_BLOCK) RmId() *GUID { 602 | return self.Profile.GUID(self.Reader, self.Profile.Off_HBASE_BLOCK_RmId+self.Offset) 603 | } 604 | 605 | func (self *HBASE_BLOCK) RootCell() uint32 { 606 | return ParseUint32(self.Reader, self.Profile.Off_HBASE_BLOCK_RootCell+self.Offset) 607 | } 608 | 609 | func (self *HBASE_BLOCK) Sequence1() uint32 { 610 | return ParseUint32(self.Reader, self.Profile.Off_HBASE_BLOCK_Sequence1+self.Offset) 611 | } 612 | 613 | func (self *HBASE_BLOCK) Sequence2() uint32 { 614 | return ParseUint32(self.Reader, self.Profile.Off_HBASE_BLOCK_Sequence2+self.Offset) 615 | } 616 | 617 | func (self *HBASE_BLOCK) Signature() uint32 { 618 | return ParseUint32(self.Reader, self.Profile.Off_HBASE_BLOCK_Signature+self.Offset) 619 | } 620 | 621 | func (self *HBASE_BLOCK) ThawLogId() *GUID { 622 | return self.Profile.GUID(self.Reader, self.Profile.Off_HBASE_BLOCK_ThawLogId+self.Offset) 623 | } 624 | 625 | func (self *HBASE_BLOCK) ThawRmId() *GUID { 626 | return self.Profile.GUID(self.Reader, self.Profile.Off_HBASE_BLOCK_ThawRmId+self.Offset) 627 | } 628 | 629 | func (self *HBASE_BLOCK) ThawTmId() *GUID { 630 | return self.Profile.GUID(self.Reader, self.Profile.Off_HBASE_BLOCK_ThawTmId+self.Offset) 631 | } 632 | 633 | func (self *HBASE_BLOCK) TimeStamp() *FileTime { 634 | return self.Profile.FileTime(self.Reader, self.Profile.Off_HBASE_BLOCK_TimeStamp+self.Offset) 635 | } 636 | 637 | func (self *HBASE_BLOCK) TmId() *GUID { 638 | return self.Profile.GUID(self.Reader, self.Profile.Off_HBASE_BLOCK_TmId+self.Offset) 639 | } 640 | 641 | func (self *HBASE_BLOCK) Type() uint32 { 642 | return ParseUint32(self.Reader, self.Profile.Off_HBASE_BLOCK_Type+self.Offset) 643 | } 644 | 645 | type HBIN struct { 646 | Reader io.ReaderAt 647 | Offset int64 648 | Profile *RegistryProfile 649 | } 650 | 651 | func (self *HBIN) Size() int { 652 | return 32 653 | } 654 | 655 | func (self *HBIN) FileOffset() uint32 { 656 | return ParseUint32(self.Reader, self.Profile.Off_HBIN_FileOffset+self.Offset) 657 | } 658 | 659 | func (self *HBIN) Reserved1() []uint32 { 660 | return ParseArray_uint32(self.Profile, self.Reader, self.Profile.Off_HBIN_Reserved1+self.Offset, 2) 661 | } 662 | 663 | func (self *HBIN) Signature() uint32 { 664 | return ParseUint32(self.Reader, self.Profile.Off_HBIN_Signature+self.Offset) 665 | } 666 | 667 | func (self *HBIN) HbinSize() uint32 { 668 | return ParseUint32(self.Reader, self.Profile.Off_HBIN_HbinSize+self.Offset) 669 | } 670 | 671 | func (self *HBIN) Spare() uint32 { 672 | return ParseUint32(self.Reader, self.Profile.Off_HBIN_Spare+self.Offset) 673 | } 674 | 675 | func (self *HBIN) TimeStamp() *FileTime { 676 | return self.Profile.FileTime(self.Reader, self.Profile.Off_HBIN_TimeStamp+self.Offset) 677 | } 678 | 679 | type HCELL struct { 680 | Reader io.ReaderAt 681 | Offset int64 682 | Profile *RegistryProfile 683 | } 684 | 685 | func (self *HCELL) Size() int { 686 | return 12 687 | } 688 | 689 | func (self *HCELL) Next() uint32 { 690 | return ParseUint32(self.Reader, self.Profile.Off_HCELL_Next+self.Offset) 691 | } 692 | 693 | func (self *HCELL) Signature() uint16 { 694 | return ParseUint16(self.Reader, self.Profile.Off_HCELL_Signature+self.Offset) 695 | } 696 | 697 | func (self *HCELL) Data() []byte { 698 | return ParseArray_byte(self.Profile, self.Reader, self.Profile.Off_HCELL_Data+self.Offset, 0) 699 | } 700 | 701 | type LARGE_INTEGER struct { 702 | Reader io.ReaderAt 703 | Offset int64 704 | Profile *RegistryProfile 705 | } 706 | 707 | func (self *LARGE_INTEGER) Size() int { 708 | return 8 709 | } 710 | 711 | func (self *LARGE_INTEGER) HighPart() int32 { 712 | return ParseInt32(self.Reader, self.Profile.Off_LARGE_INTEGER_HighPart+self.Offset) 713 | } 714 | 715 | func (self *LARGE_INTEGER) LowPart() uint32 { 716 | return ParseUint32(self.Reader, self.Profile.Off_LARGE_INTEGER_LowPart+self.Offset) 717 | } 718 | 719 | func (self *LARGE_INTEGER) QuadPart() int64 { 720 | return ParseInt64(self.Reader, self.Profile.Off_LARGE_INTEGER_QuadPart+self.Offset) 721 | } 722 | 723 | func ParseArray_CM_KEY_INDEX_FAST_ELEMENT(profile *RegistryProfile, reader io.ReaderAt, offset int64, count int) []*CM_KEY_INDEX_FAST_ELEMENT { 724 | result := make([]*CM_KEY_INDEX_FAST_ELEMENT, 0, count) 725 | for i := 0; i < count; i++ { 726 | value := profile.CM_KEY_INDEX_FAST_ELEMENT(reader, offset) 727 | result = append(result, value) 728 | offset += int64(value.Size()) 729 | } 730 | return result 731 | } 732 | 733 | func ParseArray_HIVE_DIRTY_PAGE_REF(profile *RegistryProfile, reader io.ReaderAt, offset int64, count int) []*HIVE_DIRTY_PAGE_REF { 734 | result := make([]*HIVE_DIRTY_PAGE_REF, 0, count) 735 | for i := 0; i < count; i++ { 736 | value := profile.HIVE_DIRTY_PAGE_REF(reader, offset) 737 | result = append(result, value) 738 | offset += int64(value.Size()) 739 | } 740 | return result 741 | } 742 | 743 | func ParseArray_byte(profile *RegistryProfile, reader io.ReaderAt, offset int64, count int) []byte { 744 | result := make([]byte, 0, count) 745 | for i := 0; i < count; i++ { 746 | value := ParseUint8(reader, offset) 747 | result = append(result, value) 748 | offset += int64(1) 749 | } 750 | return result 751 | } 752 | 753 | func ParseArray_uint32(profile *RegistryProfile, reader io.ReaderAt, offset int64, count int) []uint32 { 754 | result := make([]uint32, 0, count) 755 | for i := 0; i < count; i++ { 756 | value := ParseUint32(reader, offset) 757 | result = append(result, value) 758 | offset += int64(4) 759 | } 760 | return result 761 | } 762 | 763 | func ParseInt32(reader io.ReaderAt, offset int64) int32 { 764 | var buf [4]byte 765 | data := buf[:] 766 | _, err := reader.ReadAt(data, offset) 767 | if err != nil { 768 | return 0 769 | } 770 | return int32(binary.LittleEndian.Uint32(data)) 771 | } 772 | 773 | func ParseInt64(reader io.ReaderAt, offset int64) int64 { 774 | var buf [8]byte 775 | data := buf[:] 776 | _, err := reader.ReadAt(data, offset) 777 | if err != nil { 778 | return 0 779 | } 780 | return int64(binary.LittleEndian.Uint64(data)) 781 | } 782 | 783 | func ParseUint16(reader io.ReaderAt, offset int64) uint16 { 784 | var buf [2]byte 785 | data := buf[:] 786 | _, err := reader.ReadAt(data, offset) 787 | if err != nil { 788 | return 0 789 | } 790 | return binary.LittleEndian.Uint16(data) 791 | } 792 | 793 | func ParseUint32(reader io.ReaderAt, offset int64) uint32 { 794 | var buf [4]byte 795 | data := buf[:] 796 | _, err := reader.ReadAt(data, offset) 797 | if err != nil { 798 | return 0 799 | } 800 | return binary.LittleEndian.Uint32(data) 801 | } 802 | 803 | func ParseUint64(reader io.ReaderAt, offset int64) uint64 { 804 | var buf [8]byte 805 | data := buf[:] 806 | _, err := reader.ReadAt(data, offset) 807 | if err != nil { 808 | return 0 809 | } 810 | return binary.LittleEndian.Uint64(data) 811 | } 812 | 813 | func ParseUint8(reader io.ReaderAt, offset int64) byte { 814 | var buf [1]byte 815 | data := buf[:] 816 | _, err := reader.ReadAt(data, offset) 817 | if err != nil { 818 | return 0 819 | } 820 | return data[0] 821 | } 822 | 823 | func ParseTerminatedUTF16String(reader io.ReaderAt, offset int64) string { 824 | var buf [1024]byte 825 | data := buf[:] 826 | n, err := reader.ReadAt(data, offset) 827 | if err != nil && err != io.EOF { 828 | return "" 829 | } 830 | 831 | idx := bytes.Index(data[:n], []byte{0, 0}) 832 | if idx < 0 { 833 | idx = n - 1 834 | } 835 | if idx%2 != 0 { 836 | idx += 1 837 | } 838 | return UTF16BytesToUTF8(data[0:idx], binary.LittleEndian) 839 | } 840 | 841 | func ParseUTF16String(reader io.ReaderAt, offset int64, length int64) string { 842 | data := make([]byte, length) 843 | n, err := reader.ReadAt(data, offset) 844 | if err != nil && err != io.EOF { 845 | return "" 846 | } 847 | return UTF16BytesToUTF8(data[:n], binary.LittleEndian) 848 | } 849 | 850 | func UTF16BytesToUTF8(b []byte, o binary.ByteOrder) string { 851 | if len(b) < 2 { 852 | return "" 853 | } 854 | 855 | if b[0] == 0xff && b[1] == 0xfe { 856 | o = binary.BigEndian 857 | b = b[2:] 858 | } else if b[0] == 0xfe && b[1] == 0xff { 859 | o = binary.LittleEndian 860 | b = b[2:] 861 | } 862 | 863 | utf := make([]uint16, (len(b)+(2-1))/2) 864 | 865 | for i := 0; i+(2-1) < len(b); i += 2 { 866 | utf[i/2] = o.Uint16(b[i:]) 867 | } 868 | if len(b)/2 < len(utf) { 869 | utf[len(utf)-1] = utf8.RuneError 870 | } 871 | 872 | return string(utf16.Decode(utf)) 873 | } 874 | -------------------------------------------------------------------------------- /testdata/NTUSER.DAT: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Velocidex/regparser/31e704a67ef786f1fcedd62e9fd367970466e5d3/testdata/NTUSER.DAT --------------------------------------------------------------------------------