├── CONTRIBUTING.md ├── LICENSE ├── Main.ps1 ├── Overview.png ├── README.md ├── config.json ├── core ├── debugger-replay.ps1 ├── debugger-seed.ps1 ├── debugger.ps1 ├── findPatchPoints.py ├── fs-generator.ps1 ├── fuzzer-master.ps1 ├── helper.psm1 ├── input-generator.ps1 ├── replay-case.ps1 └── vm-monitoring.ps1 ├── data ├── template.vfd └── template.vhd └── utils ├── Create-CorpusSeed.ps1 ├── Create-CoverageFile.ps1 ├── Translate-InputBytesToFulltext.ps1 ├── findPatchPoints.py └── findPatchPointsWithKeyword.py /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement (CLA). You (or your employer) retain the copyright to your 10 | contribution; this simply gives us permission to use and redistribute your 11 | contributions as part of the project. Head over to 12 | to see your current agreements on file or 13 | to sign a new one. 14 | 15 | You generally only need to submit a CLA once, so if you've already submitted one 16 | (even if it was for a different project), you probably don't need to do it 17 | again. 18 | 19 | ## Code Reviews 20 | 21 | All submissions, including submissions by project members, require review. We 22 | use GitHub pull requests for this purpose. Consult 23 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 24 | information on using pull requests. 25 | 26 | ## Community Guidelines 27 | 28 | This project follows 29 | [Google's Open Source Community Guidelines](https://opensource.google/conduct/). -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Main.ps1: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | param ( 16 | [Parameter(Mandatory = $false, ParameterSetName = 'FuzzerMode')][string]$configFile, 17 | [Parameter(Mandatory = $true, ParameterSetName = 'ReproCase')][string]$reproFolder 18 | ) 19 | 20 | # Validating config file / repro folder and its config file - only the arguments requiring an authentication on the VM will be checked later 21 | if ($reproFolder) { 22 | if (-not (Test-Path $reproFolder -PathType Container)) { 23 | Write-Error "Repro folder path given but it is not a valid folder path. Aborting." 24 | return 25 | } 26 | $configFile = Join-Path $reproFolder "config.json" 27 | $configFile = Resolve-Path $configFile 28 | if (-not (Test-Path $configFile)) { 29 | Write-Error "The repro folder needs to contain a configuration file entitled 'config.json'. Aborting." 30 | return 31 | } 32 | } else { 33 | if ($configFile) { 34 | if (-not (Test-Path $configFile)) { 35 | Write-Error "Config file path given but it is not a valid path. Aborting." 36 | return 37 | } 38 | $configFile = Resolve-Path $configFile 39 | } else { 40 | if (-not (Test-Path ".\config.json")) { 41 | Write-Error "Config file path not given, config.json not found next to Main.ps1. Aborting." 42 | return 43 | } 44 | $configFile = Resolve-Path ".\config.json" 45 | } 46 | } 47 | 48 | $config = Get-Content -Path $configFile | ConvertFrom-Json 49 | $VMName = $config.VMName 50 | $VMUsername = $config.VMUsername 51 | $VMPassword = $config.VMPassword 52 | $CheckpointName = $config.VMCheckpointName 53 | $HostOutputDir = $config.HostOutputDir 54 | $VMChipsecFilepath = $config.VMChipsecFilepath 55 | $MaxInputSize = $config.MaxInputSize 56 | $MinInputSize = $config.MinInputSize 57 | $MaxMutationRate = $config.MaxMutationRate 58 | $Target = $config.Target 59 | $IOPortRead = $config.IOPortRead 60 | $IOPortWrite = $config.IOPortWrite 61 | $IdaPath = $config.HostIdaPath 62 | $VirtualDiskType = $config.VirtualDiskType 63 | 64 | if ($VirtualDiskType) { 65 | if ($VirtualDiskType.GetType().Name -ne "String" -and $VirtualDiskType.GetType().Name -ne "Object[]"){ 66 | Write-Error "VirtualDiskType is optional. If given, legit and fuzzed file systems will be mounted in the VM. The supported values for this parameter are either vfd, vhd vhdx or iso, or an array containing several of those values. Illegal format. Aborting." 67 | return 68 | } 69 | if (($VirtualDiskType.GetType().Name -eq "String") -and -not (($VirtualDiskType -eq "vfd") -or ($VirtualDiskType -eq "vhd") -or ($VirtualDiskType -eq "vhdx") -or ($VirtualDiskType -eq "iso"))) { 70 | Write-Error "VirtualDiskType is optional. If given, legit and fuzzed file systems will be mounted in the VM. The supported values for this parameter are either vfd, vhd, vhdx, or iso, or an array containing several of those values. The given string is not a legit value. Aborting." 71 | return 72 | } elseif ($VirtualDiskType.GetType().Name -eq "Object[]") { 73 | foreach ($vdisk in $VirtualDiskType) { 74 | if (($vdisk -ne "vfd") -and ($vdisk -ne "vhd") -and ($vdisk -ne "vhdx") -and ($vdisk -ne "iso")) { 75 | Write-Error "VirtualDiskType is optional. If given, legit and fuzzed file systems will be mounted in the VM. The supported values for this parameter are either vfd, vhd, vhdx or iso, or an array containing several of those values. The given array does not contain legit values. Aborting." 76 | return 77 | } 78 | } 79 | } else {} 80 | } 81 | 82 | if ($reproFolder) { 83 | $reproFile = ls $reproFolder | Where -Property Name -like tmp* | Where -Property Name -NotLike *.* 84 | if (-not $reproFile -or $reproFile.Count -gt 1) { 85 | Write-Error "No reproduction file found in the repro folder, or there are several instead. There should be 1 repro file that starts with tmp and be the only tmp file without extension. Aborting." 86 | return 87 | } 88 | $reproFile = $reproFile.FullName 89 | 90 | if ($VirtualDiskType) { 91 | $vd = New-Object System.Collections.ArrayList 92 | if ($VirtualDiskType.GetType().Name -eq "String") { 93 | $vdfiles = Join-Path $reproFolder "*.$VirtualDiskType" 94 | foreach ($f in (ls $vdfiles | Sort LastWriteTime)) { 95 | $vd.Add((Resolve-Path $f).Path) > $null 96 | } 97 | } else { 98 | $lsvdfiles = $null 99 | foreach ($format in $VirtualDiskType) { 100 | $vdfiles = Join-Path $reproFolder "*.$format" 101 | $lsvdfiles += [System.IO.FileSystemInfo[]] (ls $vdfiles) 102 | } 103 | $lsvdfiles = $lsvdfiles | Sort LastWriteTime 104 | foreach ($f in ($lsvdfiles)) { 105 | $vd.Add((Resolve-Path $f).Path) > $null 106 | } 107 | } 108 | } 109 | if (-not $Verbose) { 110 | Write-Warning "For more details on the commands run on the guest, rerun this command with -Verbose. Disabled by default to speed up the run." 111 | } 112 | } 113 | 114 | Push-Location 115 | cd "$PSScriptRoot\core" 116 | 117 | if (-not (Test-Path ".\DbgShell\x64\DbgShell.exe")) { 118 | Write-Error "DbgShell folder cannot be found. Expecting to find .\DbgShell\x64\DbgShell.exe in core. Aborting." 119 | Pop-Location 120 | return 121 | } 122 | if (-not (Test-Path ".\fuzzer-master.ps1")) { 123 | Write-Error "PS2 fuzzer master not found. Expecting to find fuzzer-master.ps1 in core. Aborting." 124 | Pop-Location 125 | return 126 | } 127 | if (-not (Test-Path ".\replay-case.ps1")) { 128 | Write-Error "PS2 replay case script not found. Expecting to find replay-case.ps1 in core. Aborting." 129 | Pop-Location 130 | return 131 | } 132 | if (-not (Test-Path "..\config.json")) { 133 | Write-Error "Config file not found. Expecting to find config.json next in the same folder as Main.ps1. Aborting." 134 | Pop-Location 135 | return 136 | } 137 | 138 | # Turning local paths to full paths (needed within DbgShell) 139 | $ps2 = Resolve-Path ".\fuzzer-master.ps1" 140 | $replay = Resolve-Path ".\replay-case.ps1" 141 | 142 | 143 | if (($VMName -eq $null) -or ($VMUsername -eq $null) -or ($VMPassword -eq $null) -or ($CheckpointName -eq $null) -or ($HostOutputDir -eq $null) -or ($VMChipsecFilepath -eq $null) -or ($MaxInputSize -eq $null) -or ($MinInputSize -eq $null) -or ($Target -eq $null) -or ($IOPortRead -eq $null) -or ($IOPortWrite -eq $null)) { 144 | Write-Error "The config file must at least contain the fields: 'VMName', 'VMCheckpointName', 'VMUsername', 'VMPassword', 'HostOutputDir', 'VMChipsecFilepath', 'MaxInputSize', 'MinInputSize', 'Target', 'IOPortRead', 'IOPortWrite'. Aborting." 145 | Pop-Location 146 | return 147 | } 148 | if ((Test-Path $HostOutputDir) -ne $true -or (Test-Path $HostOutputDir -PathType Container) -eq $false) { 149 | Write-Error "Invalid output directory. Please provide an existing directory. Aborting." 150 | Pop-Location 151 | return 152 | } 153 | $Error.Clear() 154 | Get-VM -Name $VMName -ErrorAction SilentlyContinue | Out-Null 155 | if ($Error.Count -ne 0) { 156 | Write-Error "The VM Name is invalid or this PowerShell session is not elevated. Aborting." 157 | Pop-Location 158 | return 159 | } 160 | if ((Get-VMSnapshot -VMName $VMName | Where Name -eq $CheckpointName) -eq $null) { 161 | Write-Error "The VM Checkpoint name is invalid. Aborting." 162 | Pop-Location 163 | return 164 | } 165 | 166 | #Test the credentials 167 | $secpass = ConvertTo-SecureString -String $VMPassword -AsPlainText -Force 168 | $creds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ($VMUsername, $secpass) 169 | Try { 170 | Invoke-Command -VMName $VMName -Credential $creds -ScriptBlock {"Test creds" > $null} -ErrorAction Stop 171 | } 172 | Catch { 173 | Write-Error "The username or password is incorrect for VM $VMName, or the VM is not up and ready to run commands. Returning." 174 | Pop-Location 175 | return 176 | } 177 | 178 | if (($MaxInputSize -le 0) -or ($MaxInputSize -gt 100000)) { 179 | Write-Error "MaxInputSize must be positive and below 100000. Aborting." 180 | Pop-Location 181 | return 182 | } 183 | 184 | if (($MinInputSize -le 0) -or ($MinInputSize -ge $MaxInputSize)) { 185 | Write-Error "MinInputSize must be positive and below MaxInputSize. Aborting." 186 | Pop-Location 187 | return 188 | } 189 | 190 | if (-not (Test-Path $Target -PathType Leaf)) { 191 | Write-Error "Target is not a valid path. Aborting." 192 | Pop-Location 193 | return 194 | } 195 | 196 | if ($IdaPath -and -not (Test-Path $IdaPath -PathType Leaf)) { 197 | Write-Error "HostIdaPath given but it is invalid. HostIdaPath should be the fullpath of ida64.exe or ida.exe. Aborting." 198 | Pop-Location 199 | return 200 | } 201 | 202 | if ($MaxMutationRate -and (($MaxMutationRate -gt 1) -or ($MaxMutationRate -le 0))) { 203 | Write-Error "MaxMutationRate is optional. If given, it should be > 0 and <= 1. Default value: 0.3." 204 | Pop-Location 205 | return 206 | } 207 | 208 | # Starting DbgShell with the payload. 209 | if ($reproFolder) { 210 | if ($VirtualDiskType) { 211 | Write-Host "Replaying $reproFile with replay-case parametered through config.json, with the following disks: $vd." 212 | &$replay -configFile $configFile -reproFile $reproFile -folder $reproFolder -virtualdisks $vd 213 | } else { 214 | Write-Host "Replaying $reproFile with replay-case parametered through config.json." 215 | &$replay -configFile $configFile -reproFile $reproFile -folder $reproFolder 216 | } 217 | } else { 218 | Write-Host "Starting DbgShell with fuzzer-master parametered through config.json." 219 | &$ps2 -configFile $configFile 220 | } 221 | 222 | Pop-Location -------------------------------------------------------------------------------- /Overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleprojectzero/Hyntrospect/8364fa25e4f65416f970804071478f5389f7c0b3/Overview.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HYNTROSPECT 2 | 3 | ``` 4 | Copyright 2020 Google LLC 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | ``` 18 | ## WHAT IS HYNTROSPECT? 19 | 20 | This tool is a coverage-guided fuzzer targeting Hyper-V emulated devices, in the userland of Hyper-V root partition. Vulnerabilities in that layer coud lead to a guest to root partition escape. 21 | 22 | ### How does it work? 23 | 24 | This fuzzer aims at running the binaries in a real execution context, as close as possible to a regular use, which guided the design towards debugging instead of emulating. It runs at the same level as the target binaries. 25 | 26 | It instruments a debuggee VM by injecting commands (port IOs IN/OUT) into it, having them execute, and then checks the impact on the target binary and its coverage to refine the fuzzing strategy. Windows debugging capabilities are leveraged through [DbgShell](https://github.com/microsoft/DbgShell) to monitor the target binary. The state of the debuggee VM is reset for each input file using Hyper-V checkpoint (~snapshot) capability. 27 | 28 | This version supports attaching legitimate and mutated virtual disks to the VM which enables bi-dimensional fuzzing: IOs and file systems. 29 | 30 | ### Overview of the architecture 31 | 32 | ![Overview](./Overview.png) 33 | 34 | Nested virtualization is supported: the hypervisor, root partition and debuggee can run inside a Hyper-V VM. 35 | 36 | The debuggee VM is reset to a clean checkpoint state after the consumption of each input file. 37 | 38 | ### The targets 39 | 40 | The target should by design be in the userland of the root partition, deal with VM IOs, and be loaded by the worker process (vmwp.exe). 41 | 42 | Examples: vmemulateddevices.dll, VmEmulatedStorage.dll. 43 | 44 | ### Supported platforms 45 | 46 | This fuzzer runs in the target root partition, hence on Windows. As nested virtualization is supported, the host OS could be any OS supporting Windows / Hyper-V and nested virtualization. 47 | 48 | ## USAGE 49 | 50 | ### Prerequisites 51 | 52 | Minimal set of requirements: Windows running Hyper-V, IDA if relying on it for the breakpoints computation. 53 | 54 | Instructions: 55 | - Prepare your Hyper-V environment: either Windows as your host OS with [Hyper-V enabled](https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/quick-start/enable-hyper-v) or a dedicated VM on the hypervisor of your choice. Nested virtualization is encouraged: this fuzzer would better be run in a VM which itself runs Hyper-V and the debuggee VM, to ease further debugging and resets. In the following instructions, this will be refered to as "host". 56 | - On your host, now that Hyper-V has been enabled, prepare your victim VM: 57 | - VM with Windows 10 58 | - Chipsec installed and able to run (VM with driver signature not enforced) 59 | - Ability to open an [elevated session through WinRM](https://support.microsoft.com/en-us/help/951016/description-of-user-account-control-and-remote-restrictions-in-windows). 60 | - Checkpoint taken with all of the above (with the VM up). 61 | - Download [DbgShell binaries](https://github.com/microsoft/DbgShell), add them to the core folder (.\core\DbgShell\x64\DbgShell.exe needs to exist). 62 | - Prepare the config file (cf Config file syntax), place it as the same level as Main.ps1. 63 | - [Optional, encouraged] Enable page heap on the targetted binary (this can be done using [gflags](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/gflags-and-pageheap)). 64 | - [Optional, encouraged] Prepare seeds for the fuzzer (cf Preparing the seeds). 65 | - [Optional, encouraged] Prepare a list of the offsets of the blocks that need to be monitored in a file entitled breakpoints-$binary-$MD5hash.txt, put it in the output directory. 66 | - Open an elevated powershell console. Run "Set-ExecutionPolicy Bypass -Scope Process -Force". The shell is now ready to run the fuzzer. Do not run the script from an ISE console (even elevated). For some reason that I have not tried to understand, DbgShell will not start. Always run Main.ps1 from an elevated shell. 67 | 68 | ### Config file syntax 69 | 70 | A config.json is expected in the same folder as Main.ps1. 71 | Here is a template: 72 | 73 | ```json 74 | { 75 | "Target": "C:\\Windows\\System32\\vmemulateddevices.dll", 76 | "IOPortRead": [96, 97, 98, 100], 77 | "IOPortWrite": [96, 97, 100], 78 | "VMName": "Debuggee", 79 | "VMUsername": "John", 80 | "VMPassword": "VMPassword", 81 | "VMCheckpointName": "Mock", 82 | "VMChipsecFilepath": "C:\\Users\\John\\Desktop\\chipsec\\chipsec_util.py", 83 | "HostOutputDir": "C:\\Users\\John\\Desktop\\Hyntrospect-out", 84 | "MaxInputSize": 4096, 85 | "MinInputSize": 100, 86 | "MaxMutationRate": 0.5, 87 | "HostIdaPath": "C:\\Program Files\\IDA Pro 7.5\\ida64.exe", 88 | "VirtualDiskType": "vfd" 89 | } 90 | ``` 91 | 92 | The `Target` is the full path to the dll loaded by vmwp.exe implementing the emulated device controller that is targeted. It needs to be reversed beforehand to know the read / write IO ports values that enable communicating with the device (`IOPortRead` and `IOPortWrite`). Those IO ports are written as decimal numbers. 93 | 94 | Some information needs to be filled regarding the instrumented (debuggee) VM: its name `VMName`, its username `VMUsername`, its password `VMPassword`, and the name of the snapshot of that VM `VMCheckpointName` that was taken after it has been configured. `VMChipsecFilepath` is the path to chipsec_util.py on the debuggee VM. 95 | 96 | `HostOutputDir` is the output directory on the local instance running the fuzzer. 97 | 98 | All arguments are mandatory except the last 5 arguments: 99 | - `HostIdaPath` is used to precompute the blocks' addresses on the target binary when no breakpoint list is found. It is though strongly advised to use the utilities to precompute the blocks beforehand and filter out unneeded blocks to keep the list minimal. 100 | - The default max mutation rate for input files is 0.3. 101 | - `MinInputSize` and `MaxInputSize` set limits for the corpus files. Default values: 100 and 4096. 102 | - `VirtualDiskType` should be set if the fuzzer needs to run on mutated file systems (bi-dimensional fuzzing mode). The type should then be given as an argument: "vfd", "vhd", "iso", or a combination of those values in a list. If iso is selected, a "template.iso" should be added to data. 103 | 104 | ### Running the fuzzer 105 | 106 | The user will interact with 2 files to run the fuzzer: `Main.ps1` and `config.json`. 107 | 108 | **Main file**: `Main.ps1`. It has 2 modes: fuzzing and crash reproduction. 109 | 110 | **Fuzzer mode**: It either runs without argument and consumes the configuration file (config.json) which should be next to Main.ps1, or alternatively takes the path to that file as an argument. 111 | 112 | **Crash reproduction**: It takes one mandatory argument: the path of the crash folder that will be replayed (which should contain at least config.json and the IO input file that caused the crash). 113 | 114 | **Configuration file**: `config.json`. 115 | 116 | Examples: 117 | - `PS C:\Users\John\Desktop\Hyntrospect> Main.ps1`: this starts the fuzzer using config.json in the Hyntrospect folder. 118 | - `PS C:\Users\John\Desktop\Hyntrospect> Main.ps1 -configFile ..\MySetup\config.json`: this starts the fuzzer using config.json in the "MySetup" folder 119 | - `PS C:\Users\John\Desktop\Hyntrospect> Main.ps1 -reproFolder ..\Hyntrospect-out\Crash_20-12-04_12-08`: this replays the crash which artefacts were saved in "Crash_20-12-04_12-08". 120 | 121 | Be patient with the first runs: setting the environment takes time and the first runs are tedious (removing lots of breakpoints). The fuzzer will go increasingly faster. 122 | 123 | ... You probably have a good excuse now to take a coffee and go for a little walk :) 124 | 125 | ### [Optional, encouraged] Preparing the seeds 126 | 127 | A seed made of legitimate traffic guides the fuzzer towards more interesting input and fastens the convergence. It is advised to create that seed. Another benefit of generating the seed is checking all ports were correctly listed. 128 | 129 | This can be done `using utils\Create-CorpusSeed.ps1`. 130 | 131 | Prior to running it, the config file that will also be used for fuzzing needs to be created along with the target VM. 132 | 133 | Requirement: The symbols will be pulled from either c:\symbols or http://msdl.microsoft.com/download/symbols. Set your environment accordingly. 134 | If the symbols cannot be loaded, the script will fail (DbgShell will write an error message stating that the bp addresses could not be found). 135 | The load of symbols counts towards the timeout: you may want to re-run it once the symbols are loaded or have a larger timeout. 136 | 137 | You will probably need to open the target binary in IDA to check the exact device name. This can easily be done if you look for "NotifyIoPortRead" in the function names. 138 | 139 | The script needs to be run from 'utils' which should be next to 'core'. It takes 2 mandatory arguments and 2 optional ones: 140 | - Argument 'ConfigFile': path to the configuration file 141 | - Argument 'FuzzedDevice': name of the target device. It should be the name of the device exactly as displayed in the symbols. Example: FloppyControllerDevice, I8042Device. 142 | - [Optional] Argument 'SeedName': Output file name. It will be placed in the corpus repository, in the output folder. Default value: "seed". 143 | - [Optional] Argument 'Timeout': Duration of the recording in minutes. Default value: 2. 144 | 145 | The seed filename shoud start with 'seed', it will be written in a folder named 'corpus' in the output directory (read from the config file) - if some traffic could be recorded. 146 | 147 | Once the script has started, you can help it by playing with the VM and generate as much traffic related to the device as possible. For example, for the floppy disk: mount a floppy disk in settings, open A:\, add a file, ... 148 | 149 | ### [Optional, encouraged] Preparing the breakpoints' list 150 | 151 | "breakpoints-$binary-$MD5hash.txt" needs to be created and inserted in the output folder. That output folder (which will be given in the config file later and used for fuzzing) needs to be created. 152 | $binary is the name of the target without extention. $MD5 the MD5 sum of that file. 153 | Example of a valid name for vmemulateddevices.dll: breakpoints-vmemulateddevices-D497739D96D0B6B3C7635D5FA0078903.txt. 154 | 155 | The list needs to contain the offsets of the blocks that need to be watched in the target binary, seperated by new lines. Example: 156 | ``` 157 | 0x10 158 | 0x1A 159 | 0x2B 160 | ``` 161 | 162 | This list can be created by running `utils\findPatchPoints.py` in IDA. The output file is ~\Desktop\patches.txt. 163 | 164 | It can then be renamed with the correct name and moved to the output folder: 165 | ```powershell 166 | $outputdir = "ExistingOutputFolder" 167 | $target = "vmemulateddevices.dll" 168 | $md5 = (Get-FileHash $target -Algorithm MD5).Hash 169 | $bpList = "breakpoints-" + [System.IO.Path]::GetFileNameWithoutExtension($target) + "-$md5.txt" 170 | Move-Item "~\Desktop\patches.txt" (Join-Path $outputdir $bpList) 171 | ``` 172 | 173 | It is highly recommanded to trim that file to the minimum coverage to avoid latency. < 10 Ko is good. > 50Ko compromises smooth runs. 174 | 175 | If this list of offsets is not submitted, it will be computed using IDA. A valid license is required when running the fuzzer in that case and the path to IDA needs to be given in the config file. 176 | 177 | ## OUTPUT 178 | 179 | ### Content of the output folder 180 | 181 | It will contain: 182 | - the corpus folder, 183 | - the breakpoint lists: a copy of the initial list and the updated breakpoint list 184 | - a tmp folder containing any artefact in use 185 | - potentially a corpus for the virtual disks (fscorpus) 186 | - potentially the crash folders 187 | 188 | The output folder needs to be checked as the fuzzer will keep running for most crashes. 189 | 190 | ### Visualizing the coverage in IDA 191 | 192 | The coverage can be seen in IDA using [LightHouse](https://github.com/gaasedelen/lighthouse). 193 | Start by installing LightHouse. 194 | 195 | A log file of the coverage can then be prepared using .\utils\Create-CoverageFile.ps1 that consumes the breakpoint lists in the output folder (the initial copy saved by the fuzzer and the current one). 196 | 197 | ### In case of a crash 198 | 199 | 3 cases: 200 | - If an exception is intercepted by the debugger, a crash folder is created containing the input file, execution context, and the host and guest event logs. The debugger then waits for a user input. 201 | - If the VM crashes / resets silently, a crash folder is also created, no user input is needed. 202 | - If not caught and the fuzzer stops, the file responsible for the crash is in the 'legacy' folder in the corpus. 203 | 204 | Any case can be replayed by simply starting the fuzzer with the argument "-reproFolder" and the path to that crash folder. 205 | If virtual disks fuzzing is enabled, the previously attached disks will be added to the crash folder. That folder can be given when replaying the case. 206 | 207 | 208 | ## INTERNALS 209 | 210 | The **other files** are in `core`: 211 | - fuzzer-master.ps1: fuzzer master, called by Main.ps1 after some checks; it orchestrates the fuzzer logic. 212 | - debugger.ps1: script executed within DbgShell that debugs the target process and deals with the breakpoint logic. 213 | - debugger-seed.ps1: script executed within DbgShell that records the legitimate traffic for the target device to create a fuzzer seed. 214 | - debugger-replay.ps1: script executed within DbgShell that debugs the target process in case of files replayed. Lightened version of debugger.ps1. 215 | - input-generator.ps1: it keeps feeding the input directory with mutated or new input. 216 | - fs-generator.ps1: in case file system fuzzing is activated, it keeps feeding the file system directory with new files. 217 | - vm-monitoring.ps1: it checks that the VM is alive and collects traces if the VM stops running 218 | - findPatchPoints.py: IDA script that outputs a list of the blocks addresses of the binary. 219 | - replay-case.ps1: master when rerunning one existing case. 220 | - helper.psm1: library containing helper functions. 221 | 222 | All these files need to be in the same folder. 223 | 224 | A `data` repository contains templates for the virtual disks. 225 | 226 | Additional helpers for the user are in `utils`. 227 | 228 | There are **5 helpers**: 229 | - **Create-CorpusSeed**.ps1: Prepares a seed in the fuzzer compliant format for the targetted emulated device. Requirement: the symbols need to be present in C:\Symbols for the targetted binary or a network connection will be needed. If the symbols are pulled in real time from the internet, a 2 minutes run might be too short to get a good seed (need to re-run it). 230 | - Argument 'ConfigFile': path to the configuration file 231 | - Argument 'FuzzedDevice': name of the target device. It should be the name of the device exactly as displayed in the symbols. Example: FloppyControllerDevice, I8042Device. 232 | - [Optional] Argument 'SeedName': Output file name. It will be placed in the corpus repository, in the output folder. Default value: "seed". 233 | - [Optional] Argument 'Timeout': Duration of the recording in minutes. Default value: 2. 234 | - **Create-CoverageFile**.ps1: Prepares a txt file compliant to LightHouse format that can be loaded in IDA to visualize the coverage. 235 | - Argument 'InitialBpList': path to the latest breakpoint list 236 | - Argument 'UpdatedBpList': path to the initial breakpoint list 237 | - Argument 'OutputDir': folder in which the coverage file will be written 238 | - Argument 'Binary': binary name which is targetted (name with/without extension, or path) 239 | - **Translate-InputBytesToFulltext**.ps1: Translates an input file in the transcript of the corresponding IO operations 240 | - Argument 'ConfigFile': path to the configuration file 241 | - Argument 'InputFile': path to the file that needs to be analyzed 242 | - **findPatchPoints**.py: IDA script that outputs a list of the blocks addresses of the binary 243 | - [Optional] Argument: output filepath, default: ~/Desktop/patches.txt 244 | - **findPatchPointsWithKeyword**.py: Same IDA script as findPatchPoints.py except that it filters the function names on a given keyword (to restrict the basic blocks to a relevant subset). The keyword is currently hardcoded. 245 | 246 | ## DISCUSSIONS 247 | 248 | ### Drawbacks of the method 249 | 250 | - This technique is slow. 251 | - This fuzzer cannot be ported to any fuzzing operations that would conflict with the use of PowerShell sessions between the guest and the host (WMI, ...). 252 | - Legitimate VM IOs are not filtered out. 253 | 254 | ### A special note on bi-dimensional fuzzing 255 | 256 | In this context, this refers to attaching corrupt virtual disks to the VM while executing IO operations. This can be enabled by setting "VirtualDiskType" in the configuration file. 257 | 258 | The mutation logic right now is basic, consisting in flipping bits of template disks. VHD support in being added (as VHDs cannot be hot-swapped, a mock is currently implemented instead). 259 | 260 | ### Beyond the emulated devices 261 | 262 | The logic of the fuzzer could be ported to other use cases in the userland of the root partition. The input logic and commands sent to the VM would need to be modified to fit the new need. 263 | 264 | ## Disclaimer 265 | 266 | This is not an official Google product. -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "Target": "C:\\Windows\\System32\\vmemulateddevices.dll", 3 | "IOPortRead": [96, 97, 98, 100], 4 | "IOPortWrite": [96, 97, 100], 5 | "VMName": "VMName", 6 | "VMUsername": "VMUsername", 7 | "VMPassword": "VMPassword", 8 | "VMCheckpointName": "Mock", 9 | "VMChipsecFilepath": "C:\\Users\\VMUsername\\Desktop\\chipsec\\chipsec_util.py", 10 | "HostOutputDir": "C:\\Users\\HostUsername\\Desktop", 11 | "MaxInputSize": 4096, 12 | "MinInputSize": 100, 13 | "MaxMutationRate": 0.5 14 | } -------------------------------------------------------------------------------- /core/debugger-replay.ps1: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | param( 16 | [Parameter(Mandatory=$true)][string]$configfile, 17 | [Parameter(Mandatory=$true)][string]$binaryname, 18 | [Parameter(Mandatory=$true)][string]$hostTime, 19 | [Parameter(Mandatory=$true)][string]$vmTime 20 | ) 21 | 22 | $config = Get-Content -Path $configFile | ConvertFrom-Json 23 | $VMName = $config.VMName 24 | $HostOutputDir = $config.HostOutputDir 25 | $ScriptPath = $PSScriptRoot 26 | $VirtualDiskType = $config.VirtualDiskType 27 | $username = $config.VMUsername 28 | $password = $config.VMPassword 29 | 30 | $helper = Join-Path $ScriptPath helper.psm1 31 | Import-Module $helper -Force 32 | 33 | $hostTime = [DateTime]::Parse($hostTime) 34 | $vmTime = [DateTime]::Parse($vmTime) 35 | 36 | $secpass = ConvertTo-SecureString -String $password -AsPlainText -Force 37 | $creds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ($username, $secpass) 38 | 39 | $vmid = (Get-VM -Name $VMName | select -ExpandProperty "vmid").Guid 40 | $vmwpPid = Get-Process -Name "vmwp" -IncludeUserName | Where-Object {$_.Username -match $vmid} | select -ExpandProperty "Id" 41 | 42 | Connect-Process -Id $vmwpPid 43 | $binarybase = (lm $binaryname).BaseAddress 44 | 45 | Write-Output "Starting the execution." 46 | while ($true) { 47 | g 48 | 49 | $r = r 50 | $bp = $r.Rip.Value - $binarybase 51 | # conversion to string 52 | $bp = "0x"+"{0:x}" -f $bp 53 | 54 | $crashDir = New-CrashReport -ConfigFile $ConfigFile -mini 55 | 56 | Write-Warning "Unexpected break at $bp :) Creating a mini report in $crashDir" 57 | 58 | $report = Join-Path $crashDir "report.txt" 59 | $k = k 60 | Add-Content -Value "Unexpected break`r`nAddr:$bp`r`nRegisters:`r`n$r`r`n`r`nStack:`r`n$k" -Path $report 61 | 62 | Write-Host "The VM will now reboot to collect logs. Please type enter to let it go..." 63 | Read-Host | Out-Null 64 | "" > $hold 65 | } -------------------------------------------------------------------------------- /core/debugger-seed.ps1: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | param( 16 | [Parameter(Mandatory=$true)][string]$configfile, 17 | [Parameter(Mandatory=$true)][string]$seedfile, 18 | [Parameter(Mandatory=$true)][string]$device 19 | ) 20 | 21 | $config = Get-Content -Path $configFile | ConvertFrom-Json 22 | $VMName = $config.VMName 23 | $binary = $config.Target 24 | $IOPortRead = $config.IOPortRead 25 | $IOPortWrite = $config.IOPortWrite 26 | 27 | $ScriptPath = $PSScriptRoot 28 | $accessSizes = @(1, 2, 4) 29 | 30 | $helper = Join-Path $ScriptPath helper.psm1 31 | Import-Module $helper -Force 32 | 33 | $vmid = (Get-VM -Name $VMName | select -ExpandProperty "vmid").Guid 34 | $vmwpPid = Get-Process -Name "vmwp" -IncludeUserName | Where-Object {$_.Username -match $vmid} | select -ExpandProperty "Id" 35 | 36 | Connect-Process -Id $vmwpPid 37 | 38 | ide -Command '.sympath+ c:\symbols' 39 | ide -Command '.sympath+ https://msdl.microsoft.com/download/symbols' 40 | ide -Command '.symfix+ c:\symbols' 41 | ide -Command '.reload /f' 42 | 43 | $readHandler = [System.IO.Path]::GetFileNameWithoutExtension($binary) + "!$device::NotifyIOPortRead" 44 | $writeHandler = [System.IO.Path]::GetFileNameWithoutExtension($binary) + "!$device::NotifyIOPortWrite" 45 | 46 | $addrRead = (x $readHandler).Address 47 | $addrWrite = (x $writeHandler).Address 48 | 49 | bp -Address $addrRead -command $null 50 | bp -Address $addrWrite -command $null 51 | 52 | $index = 0 53 | 54 | while ($true) { 55 | g 56 | $r = r 57 | $ioport = $r.Rdx.Value 58 | $accessSize = $r.R8.Value 59 | 60 | # read 61 | $k = k 62 | 63 | # Update at every round in case the process crashes 64 | $fileStream = [System.IO.File]::OpenWrite($seedfile) 65 | $fileStream.Position = $index 66 | 67 | if ($k[1] -match 'NotifyIoPortRead') { 68 | $ioindex = $IOPortRead.IndexOf([Convert]::ToInt32($ioport)) 69 | if ($ioindex -eq -1) { 70 | Write-Host "Unlisted IO port value: $ioport" -ForegroundColor Red 71 | Read-Host "INSTRUCTIONS: The IO port list needs to be fixed in the config file (+$ioport in the IOPortRead list). The generation of the seed needs to then be restarted. Please press enter now." 72 | } else { 73 | $fileStream.WriteByte(0) 74 | $fileStream.WriteByte($ioindex) 75 | $fileStream.WriteByte($accessSizes.IndexOf([Convert]::ToInt32($accessSize))) 76 | $index += 3 77 | } 78 | } else { 79 | $data = $r.R9.Value 80 | $ioindex = $IOPortWrite.IndexOf([Convert]::ToInt32($ioport)) 81 | if ($ioindex -eq -1) { 82 | Write-Host "Unlisted IO port value: $ioport" -ForegroundColor Red 83 | Read-Host "INSTRUCTIONS: The IO port list needs to be fixed in the config file (+$ioport in the IOPortWrite list). The generation of the seed needs to then be restarted. Please press enter now." 84 | } else { 85 | $fileStream.WriteByte(1) 86 | $fileStream.WriteByte($ioindex) 87 | $fileStream.WriteByte($accessSizes.IndexOf([Convert]::ToInt32($accessSize))) 88 | $bytes = [System.BitConverter]::GetBytes($data) 89 | if ($accessSize -eq 1) { 90 | $fileStream.WriteByte($data) 91 | $index += 4 92 | } elseif ($accessSize -eq 2) { 93 | $bytes = [System.BitConverter]::GetBytes([Convert]::ToInt16($data)) 94 | $fileStream.Write($bytes, 0, $bytes.Count) 95 | $index += 5 96 | } elseif ($accessSize -eq 4) { 97 | $bytes = [System.BitConverter]::GetBytes([Convert]::ToInt32($data)) 98 | $fileStream.Write($bytes, 0, $bytes.Count) 99 | $index += 7 100 | } else { 101 | Write-Host "Weird data value" 102 | Write-Host "$r" 103 | Write-Host "$k" 104 | Read-Host 105 | } 106 | } 107 | } 108 | $fileStream.Close() 109 | } -------------------------------------------------------------------------------- /core/debugger.ps1: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | param( 16 | [Parameter(Mandatory=$true)][string]$configfile, 17 | [Parameter(Mandatory=$true)][string]$bpList, 18 | [Parameter(Mandatory=$true)][string]$binaryname, 19 | [Parameter(Mandatory=$true)][string]$hostTime, 20 | [Parameter(Mandatory=$true)][string]$vmTime 21 | ) 22 | 23 | $config = Get-Content -Path $configFile | ConvertFrom-Json 24 | $VMName = $config.VMName 25 | $HostOutputDir = $config.HostOutputDir 26 | $ScriptPath = $PSScriptRoot 27 | $VirtualDiskType = $config.VirtualDiskType 28 | $username = $config.VMUsername 29 | $password = $config.VMPassword 30 | 31 | $helper = Join-Path $ScriptPath helper.psm1 32 | Import-Module $helper -Force 33 | 34 | $secpass = ConvertTo-SecureString -String $password -AsPlainText -Force 35 | $creds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ($username, $secpass) 36 | 37 | $hostTime = [DateTime]::Parse($hostTime) 38 | $vmTime = [DateTime]::Parse($vmTime) 39 | 40 | $vmid = (Get-VM -Name $VMName | select -ExpandProperty "vmid").Guid 41 | $vmwpPid = Get-Process -Name "vmwp" -IncludeUserName | Where-Object {$_.Username -match $vmid} | select -ExpandProperty "Id" 42 | 43 | Connect-Process -Id $vmwpPid 44 | $binarybase = (lm $binaryname).BaseAddress 45 | 46 | [System.Collections.ArrayList]$blocks = Get-Content $bpList 47 | 48 | Write-Output "Setting the breakpoints. That operation can take a long time depending on the size of the list (~sec to hour)." 49 | foreach ($addr in $blocks) { 50 | $addr = $binarybase + $addr 51 | $addrStr = "{0:x}" -f $addr 52 | bp $addrStr > $null 53 | } 54 | 55 | # Signals the main script that the VM is ready for the tests 56 | $debuggerReady = Join-Path $scriptPath "debugger-ready" 57 | Set-Content -Value "" -Path $debuggerReady 58 | 59 | Write-Output "Starting the execution." 60 | while ($true) { 61 | g 62 | 63 | $r = r 64 | $bp = $r.Rip.Value - $binarybase 65 | # conversion to string 66 | $bp = "0x"+"{0:x}" -f $bp 67 | 68 | if ($blocks.Contains($bp)) { 69 | $blocks.Remove($bp) 70 | Write-Output "New path open at $bp." 71 | Set-Content -Value $blocks -Path $bpList 72 | # need to reset to be able to delete it 73 | (bp ("{0:x}" -f $r.Rip.Value)).Id | bc 74 | } else { 75 | 76 | $hold = Join-Path $PSScriptRoot "on-hold" 77 | "Debugger process handling a crash." > $hold 78 | 79 | $crashDir = New-CrashReport -ConfigFile $configFile -mini 80 | 81 | Write-Warning "Unexpected break at $bp :) Creating a report in $crashDir" 82 | 83 | $report = Join-Path $crashDir "report.txt" 84 | $k = k 85 | $r = r 86 | Add-Content -Value "Unexpected break`r`nRegisters:`r`n$r`r`n`r`nStack:`r`n$k" -Path $report 87 | 88 | # Collects the host logs. 89 | Write-Host("Collecting the logs on the host") 90 | Get-HostEventLogs -startTime $hostTime -CrashDir $crashDir 91 | 92 | Write-Host "The VM will now reboot to collect logs through the monitoring process. Please type enter to let it go..." 93 | Read-Host | Out-Null 94 | "" > $hold 95 | break 96 | } 97 | } 98 | Write-Host "Letting go." 99 | g -------------------------------------------------------------------------------- /core/findPatchPoints.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import idautils 16 | import idaapi 17 | import ida_nalt 18 | import idc 19 | 20 | if len(idc.ARGV) != 2: 21 | print("Filepath: ~\Desktop\patches.txt.") 22 | from os.path import expanduser 23 | home = expanduser("~") 24 | filepath = home + "\\Desktop\\patches.txt" 25 | else: 26 | filepath = idc.ARGV[1] 27 | print(filepath) 28 | 29 | auto_wait() 30 | 31 | patchpoints = set() 32 | base = idaapi.get_imagebase() 33 | 34 | for seg_ea in idautils.Segments(): 35 | name = idc.get_segm_name(seg_ea) 36 | if name != ".text": 37 | continue 38 | 39 | start = idc.get_segm_start(seg_ea) 40 | end = idc.get_segm_end(seg_ea) 41 | for func_ea in idautils.Functions(start, end): 42 | f = idaapi.get_func(func_ea) 43 | if not f: 44 | continue 45 | for block in idaapi.FlowChart(f): 46 | if f.start_ea <= block.start_ea < f.end_ea: 47 | patchpoints.add(block.start_ea - base) 48 | 49 | with open(filepath, "w") as f: 50 | f.write('\n'.join(map(hex, sorted(patchpoints)))) 51 | 52 | print("Done, found {} patchpoints".format(len(patchpoints))) 53 | -------------------------------------------------------------------------------- /core/fs-generator.ps1: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | param ( 16 | [Parameter(Mandatory=$true)][string]$HostOutputDir, 17 | [Parameter(Mandatory=$true)][string]$VirtualDiskType 18 | ) 19 | 20 | $helper = Join-Path $PSScriptRoot helper.psm1 21 | Import-Module $helper -Force 22 | 23 | $corpus = Join-Path $HostOutputDir "fscorpus" 24 | 25 | # at least the template is in the test file 26 | 27 | $availableFiles = Join-Path $corpus "tmp*$VirtualDiskType" 28 | while ((-not (ls $availableFiles)) -or ((ls $availableFiles | measure).Count -lt 10)) { 29 | # TODO: over time, may update the strategy to switch over other functions that do more targetted operations on the file system 30 | # Picks a random wait in case there are several file formats (to let them intertwine) 31 | Start-Sleep -Milliseconds (Get-Random -Minimum 0 -Maximum 100) 32 | New-MutatedFile -corpus $corpus -mutRate 0.3 -extension $VirtualDiskType 33 | } -------------------------------------------------------------------------------- /core/fuzzer-master.ps1: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | param( 16 | [Parameter(Mandatory=$true)][string]$configFile 17 | ) 18 | 19 | # Please make sure you have a Windows VM with chipsec installed and the ability to open an elevated session over WinRM. 20 | $config = Get-Content -Path $configFile | ConvertFrom-Json 21 | $VMName = $config.VMName 22 | $CheckpointName = $config.VMCheckpointName 23 | $HostOutputDir = $config.HostOutputDir 24 | $VMChipsecFilepath = $config.VMChipsecFilepath 25 | $MaxInputSize = $config.MaxInputSize 26 | $MinInputSize = $config.MinInputSize 27 | $username = $config.VMUsername 28 | $password = $config.VMPassword 29 | $ScriptPath = $PSScriptRoot 30 | $IdaPath = $config.HostIdaPath 31 | $binary = $config.Target 32 | $IOPortRead = $config.IOPortRead 33 | $IOPortWrite = $config.IOPortWrite 34 | $MaxMutationRate = $config.MaxMutationRate 35 | $VirtualDiskType = $config.VirtualDiskType 36 | 37 | # The mutation rate will be modified over time. 38 | $env:mutationRate = 0.01 39 | # The fuzzer works with 2 phases: a slow convergence at the beginning, an accelerated one once the coverage starts plateauing 40 | $env:accelerated = $null 41 | 42 | if ($MaxMutationRate -eq $null) { 43 | $MaxMutationRate = 0.3 44 | } 45 | if ($MaxInputSize -eq $null) { 46 | $MaxInputSize = 4096 47 | } 48 | if ($MinInputSize -eq $null) { 49 | $MinInputSize = 100 50 | } 51 | 52 | # Gets the credentials of the VM 53 | $secpass = ConvertTo-SecureString -String $password -AsPlainText -Force 54 | $creds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ($username, $secpass) 55 | 56 | # Gets the ID of the VM 57 | $vmid = (Get-VM -Name $VMName | select -ExpandProperty "vmid").Guid 58 | 59 | # Loads the helper functions 60 | $helper = Join-Path $ScriptPath helper.psm1 61 | Import-Module $helper -Force -WarningAction SilentlyContinue 62 | 63 | # Cleaning in case the previous execution was stopped in an unstable state 64 | $debuggerReady = Join-Path $scriptPath "debugger-ready" 65 | if (Test-Path $debuggerReady) { 66 | rm $debuggerReady 67 | } 68 | $hold = Join-Path $PSScriptRoot "on-hold" 69 | if (Test-Path $hold) { 70 | rm $hold 71 | } 72 | 73 | 74 | # Computes the hash of the target binary for versioning 75 | $hash = (Get-FileHash $binary -Algorithm MD5).Hash 76 | 77 | # Sets error handling. 78 | $Error.Clear() 79 | $ErrorActionPreference = 'Continue' 80 | 81 | # If the breakpoints list does not exist, initializes it 82 | $binaryNameWithoutExtension = [io.path]::GetFileNameWithoutExtension($binary) 83 | $bpList = Join-Path $HostOutputDir "breakpoints-$binaryNameWithoutExtension-$hash.txt" 84 | if (-not (Test-Path $bpList)) { 85 | Write-Warning "Initializing the bp list for $binaryNameWithoutExtension version $hash." 86 | $bpscript = Join-Path $ScriptPath "findPatchPoints.py" 87 | $idb = Join-Path $HostOutputDir "$binaryNameWithoutExtension.i64" 88 | Init-BreakpointList -ida $IdaPath -idb $idb -bpscript $bpscript -bpList $bpList -binary $binary 89 | } 90 | $bpListBck = Join-Path $HostOutputDir "breakpoints-$binaryNameWithoutExtension-$hash-init.txt" 91 | 92 | # Reads the bp list 93 | Copy-Item $bpList $bpListBck 94 | $blocks = Get-Content $bpList 95 | 96 | # Starts the input generator 97 | $inputGeneratorPath = Join-Path $ScriptPath "input-generator.ps1" 98 | $inputcommand = "-HostOutputDir ""$HostOutputDir"" -maxInputSize $MaxInputSize -minInputSize $MinInputSize" 99 | $inputGenPid = Start-HelperProcess -script $inputGeneratorPath -commandline $inputcommand 100 | $corpus = Join-Path $HostOutputDir "corpus" 101 | $tmp = Join-Path $HostOutputDir "tmp" 102 | mkdir $tmp -ErrorAction SilentlyContinue | Out-Null 103 | 104 | # Sets up the file system fuzzer when applicable 105 | $fs = New-Object System.Collections.ArrayList 106 | if ($VirtualDiskType) { 107 | $fsCorpus = Join-Path $HostOutputDir "fscorpus" 108 | if (-not (Test-Path $fsCorpus)) { 109 | mkdir $fsCorpus | Out-Null 110 | } 111 | $fsGeneratorPath = Join-Path $ScriptPath "fs-generator.ps1" 112 | if ($VirtualDiskType.GetType().Name -eq "String") { 113 | $fsTemplate = Join-Path $fsCorpus "template.$VirtualDiskType" 114 | if (Test-Path -Path "..\data\template.$VirtualDiskType") { 115 | Copy-Item "..\data\template.$VirtualDiskType" $fsCorpus 116 | } 117 | else { 118 | New-Item -Path $fsTemplate -Value "Test" -ItemType File -Force > $null 119 | } 120 | $fscommand = "-HostOutputDir ""$HostOutputDir"" -VirtualDiskType $VirtualDiskType" 121 | $fsGenPid = Start-HelperProcess -script $fsGeneratorPath -commandline $fscommand 122 | } else { 123 | $fsTemplate = New-Object System.Collections.ArrayList 124 | $fscommands = New-Object System.Collections.ArrayList 125 | foreach ($format in $VirtualDiskType) { 126 | if (Test-Path -Path "..\data\template.$VirtualDiskType") { 127 | Copy-Item "..\data\template.$format" $fsCorpus 128 | } 129 | else { 130 | New-Item -Path (Join-Path $fsCorpus "template.$format") -Value "Test" -ItemType File -Force > $null 131 | } 132 | $fsTemplate.Add((Join-Path $fsCorpus "template.$format")) > $null 133 | $fscommand = "-HostOutputDir ""$HostOutputDir"" -VirtualDiskType $format" 134 | $fscommands.Add($fscommand) > $null 135 | $fsGenPid = Start-HelperProcess -script $fsGeneratorPath -commandline $fscommand 136 | } 137 | } 138 | } 139 | 140 | # Prepares for a pre-coverage without starting the fuzzer if there are seed files and not yet other corpus files (which would mean previous runs) 141 | if (-not (ls $corpus | Where Name -Like "corpus*")) { 142 | $seeds_ = ls $corpus | Where Name -Like "seed*" 143 | # Dealing with singleton (related to PowerShell types) 144 | if ($seeds_.GetType().Name -eq "FileInfo") { 145 | $seeds = New-Object System.Collections.ArrayList 146 | $seeds.Add($seeds_) 147 | } 148 | [System.Collections.ArrayList]$seeds = $seeds_ 149 | } else { 150 | $seeds = New-Object System.Collections.ArrayList 151 | } 152 | 153 | # Initializes local variables 154 | $monPid = $null 155 | $dpid = $null 156 | $s = $null 157 | $idleloopCount = 0 158 | 159 | # Each iteration in that loop corresponds to a new random input stream 160 | while ($true) { 161 | if (-not $blocks) { 162 | Write-Warning "The list of breakpoints was exhausted. The whole binary was executed. Returning." 163 | Stop-Process -Id $inputGenPid 164 | return 165 | } 166 | 167 | # Increases of the mutation rate at each iteration when in accelarated mode 168 | if ($env:accelerated) { 169 | if ($env:mutationRate -lt $MaxMutationRate) { 170 | $env:mutationRate += 0.001 171 | } 172 | } 173 | 174 | # Cleans the environment 175 | if ($s) { 176 | Remove-PSSession $s 177 | $s = $null 178 | } 179 | if ($monPid) { 180 | Stop-Process -Id $monPid -Force 181 | while (Get-process -Id $monPid -ErrorAction Ignore) {} 182 | } 183 | 184 | if ($dpid) { 185 | Stop-Process -Id $dpid -Force -ErrorAction SilentlyContinue 186 | while (Get-process -Id $dpid -ErrorAction Ignore) {} 187 | } 188 | 189 | Start-Sleep -Seconds 2 190 | Restore-VMSnapshot -VMName $VMName -Name $CheckpointName -Confirm:$false 191 | 192 | # Checks that the VM is up 193 | if ((Get-VM -Name $VMName).State -ne "Running") { 194 | Write-Error "Snapshot not in running state. Aborting." 195 | return 196 | } 197 | 198 | # Waits until the VM is up and running in case it starts it 199 | while ((Get-VM -Name $VMName).State -ne "Running" -and (Get-VM -Name $VMName).Status -ne "Operating normally") { 200 | } 201 | 202 | # Sets the PS session for VM communication 203 | while (-not $s) { 204 | $s = New-PSSession -VMName $VMName -Credential $creds -ErrorAction SilentlyContinue 205 | } 206 | 207 | # Gets the starting time of the host and VM - used later as a reference for the collection of the event logs. 208 | $hostTime = Get-Date 209 | $timestamp = Get-date -Format "yyMMdd-HHmm" 210 | Write-Verbose ("Host time: " + $hostTime) 211 | $vmTime = Invoke-Command -Session $s -ScriptBlock {Get-Date} 212 | 213 | # Connects to the debugee 214 | $debuggerScript = Join-Path $scriptPath "debugger.ps1" 215 | $hostTimeStr = $hostTime.GetDateTimeFormats('o') 216 | $vmTimeStr = $vmTime.GetDateTimeFormats('o') 217 | $dpid = (Start-Process -FilePath .\DbgShell\x64\DbgShell.exe -PassThru -ArgumentList "$debuggerScript ""$configfile"" ""$bpList"" ""$binaryNameWithoutExtension"" ""$hostTimeStr"" ""$vmTimeStr"" ").Id 218 | 219 | # Leaves time for the debugger to attach and starts setting breakpoints 220 | Write-Verbose "Waiting until the VM is ready to start interacting." 221 | while (-not (Test-Path $debuggerReady)) { 222 | } 223 | rm $debuggerReady 224 | 225 | # Waits until the VM is available for use (once the debugger lets it go after setting the breakpoints) 226 | while ((Get-VM -Name $VMName -ErrorAction Ignore).State -ne "Running" -or (Get-VM -Name $VMName -ErrorAction Ignore).Status -ne "Operating normally") { 227 | } 228 | 229 | # Starts the monitoring process for that new round 230 | Write-Verbose "Resuming execution." 231 | $monitoringScriptPath = Join-Path $ScriptPath "vm-monitoring.ps1" 232 | $moncommand = "-configFile ""$configFile"" -hostTime ""$hostTime"" -vmTime ""$vmTime"" " 233 | $monPid = Start-HelperProcess -script $monitoringScriptPath -commandline $moncommand 234 | 235 | # Flushes the tmp folder. Tmp stores any file consumed during one VM run. 236 | rm "$tmp\*" 237 | 238 | # Consumes seed files if applicable (first run with seeds). 239 | # Otherwise picks the oldest tmp file from the corpus. 240 | # Loading the file in memory is not optimal space-wise but more efficient time-wise. 241 | if ($seeds.Count -ne 0) { 242 | $seedfile = $seeds[0].Fullname 243 | $inputbytes = Get-Content $seedfile -Encoding Byte 244 | $inputfilelen = $inputbytes.Length 245 | Write-Host $seedfile 246 | } else { 247 | do { 248 | $oldestTmp = ls (Join-Path $corpus "tmp*") | sort lastwritetime | select -First 1 249 | } while (-not $oldestTmp) 250 | $oldestTmpPath = $oldestTmp.Fullname 251 | $inputbytes = Get-Content $oldestTmp -Encoding Byte 252 | $inputfilelen = $inputbytes.Length 253 | Write-Host $oldestTmpPath 254 | } 255 | 256 | # Makes sure the pool is full for future consumption 257 | $inputGenPid = Start-HelperProcess -script $inputGeneratorPath -commandline $inputcommand 258 | 259 | # If the virtual disk type option is passed, several virtual disks will be mounted for each file 260 | if ($VirtualDiskType) { 261 | # 5 mutated files are selected, the template is added, as well as $null (no file) 262 | # as there is no cmdlet to remove a floppy disk, the first run will be on no disk, then on the template, the other ones will just replace it. 263 | if ($VirtualDiskType.GetType().Name -eq "String") { 264 | do { 265 | [System.Collections.ArrayList]$fs = ls (Join-Path $fscorpus "tmp*$VirtualDiskType") | sort lastwritetime | select -First 5 266 | } while ($fs.Count -ne 5) 267 | $fs.Insert(0, $fsTemplate) 268 | $fs.Insert(0, $null) 269 | # Regenerates files for the pool now some have been consumed. 270 | $fsGenPid = Start-HelperProcess -script $fsGeneratorPath -commandline $fscommand 271 | } 272 | # case of several file types 273 | else { 274 | do { 275 | [System.Collections.ArrayList]$fs = ls (Join-Path $fscorpus "tmp*") | sort lastwritetime | select -First 5 276 | } while ($fs.Count -ne 5) 277 | foreach ($template in $fsTemplate) { 278 | $fs.Insert(0, $template) 279 | } 280 | $fs.Insert(0, $null) 281 | foreach ($fscommand in $fscommands) { 282 | $fsGenPid = Start-HelperProcess -script $fsGeneratorPath -commandline $fscommand 283 | } 284 | } 285 | } 286 | 287 | # Pre-creates a timestamp for the corpus 288 | $ctimestamp = Get-date -Format "yyMMdd-HHmmss" 289 | # Keeps the largest index of change on a file for the corpus (useful with virtual disks as one input file runs several times) 290 | $largestKeepIndex = 0 291 | 292 | # Executes the payload on the VM. One iteration per virtual disk attached, only 1 if not fuzzing virtual disks. 293 | :loop do { 294 | 295 | # Input file offset 296 | $offset = 0 297 | 298 | # Sets an index in case the current file increases coverage. That index is the highest offset that triggered an increase. 299 | $keepIndex = New-Object System.Collections.ArrayList 300 | 301 | # Variable for the exclusion list 302 | $ps2Command = $false 303 | 304 | # If fuzzing the file system, sets the file system 305 | if ($VirtualDiskType) { 306 | 307 | $fsfile = $fs[0] 308 | 309 | if ($VirtualDiskType.GetType().Name -eq "string") { 310 | if ($VirtualDiskType -eq "vfd") { 311 | Set-VMFloppyDiskDrive -VMName $VMName -Path $fsfile 312 | } elseif ($VirtualDiskType -eq "iso") { 313 | Set-VMDvdDrive -VMName $VMName -Path $fsfile 314 | } elseif ($VirtualDiskType -eq "vhd") { 315 | # TODO: find a way to update the disk 316 | Set-VMHardDiskDriveForFuzzing -VMName $VMName -Path $fsfile -s $s 317 | # Set-VMHardDiskDrive -VMName $VMName -Path $fsfile 318 | } else { 319 | Write-Warning "`tDid not recognize the format of $fsfile." 320 | } 321 | } else { # case of multiple fuzzed formats 322 | if ($VirtualDiskType.Contains("vfd") -and (($fsfile -eq $null) -or ([io.path]::GetExtension($fsfile) -eq ".vfd"))) { 323 | Set-VMFloppyDiskDrive -VMName $VMName -Path $fsfile 324 | } 325 | if ($VirtualDiskType.Contains("iso") -and (($fsfile -eq $null) -or ([io.path]::GetExtension($fsfile) -eq ".iso"))) { 326 | Set-VMDvdDrive -VMName $VMName -Path "$fsfile" 327 | } 328 | if ($VirtualDiskType.Contains("vhd") -and (($fsfile -eq $null) -or ([io.path]::GetExtension($fsfile) -eq ".vhd"))) { 329 | # Set-VMHardDiskDrive -VMName $VMName -Path $fsfile 330 | Set-VMHardDiskDriveForFuzzing -VMName $VMName -Path $fsfile -s $s 331 | } 332 | } 333 | 334 | if ($fsfile) { 335 | Write-Host "`tAttached $fsfile." 336 | } else { 337 | Write-Host "`tNo virtual disk attached." 338 | } 339 | } 340 | 341 | # Each iteration in that loop corresponds to one command sent 342 | while($offset -lt $inputfilelen) { 343 | ($offset, $byte) = Consume-Bytes -bytes $inputbytes -offset $offset -numBytes 1 -filelen $inputfilelen 344 | $action = $byte % 2 # $action = 0 -> read, $action = 1 -> write 345 | 346 | ($offset, $byte) = Consume-Bytes -bytes $inputbytes -offset $offset -numBytes 1 -filelen $inputfilelen 347 | if ($action -eq 0) { 348 | $ioport = $IOPortRead[$byte % $IOPortRead.length] 349 | } else { 350 | $ioport = $IOPortWrite[$byte % $IOPortWrite.length] 351 | } 352 | $x_ioport = '0x'+ '{0:x}'-f $ioport 353 | 354 | ($offset, $byte) = Consume-Bytes -bytes $inputbytes -offset $offset -numBytes 1 -filelen $inputfilelen 355 | $numbytes = @(1, 2, 4)[$byte % 3] 356 | 357 | if ($action -eq 1) { 358 | ($offset, $value) = Consume-Bytes -bytes $inputbytes -offset $offset -numBytes $numbytes -filelen $inputfilelen 359 | $x_value = '0x'+ '{0:x}' -f $value 360 | } 361 | 362 | # Exclusion list lists 363 | if (($ioport -eq 0x64) -and ($action -eq 1) -and ($value -ge 0xF0)) { 364 | Write-Debug "W $x_ioport $numbytes $x_value - Skipping, resuming at index $offset" 365 | continue 366 | } 367 | if (($ioport -eq 0x92) -and ($action -eq 1) -and (($value -band 1) -eq 1)) { 368 | Write-Debug "W $x_ioport $numbytes $x_value - Skipping, resuming at index $offset" 369 | continue 370 | } 371 | # A20 gate & PS2 system reset 372 | if (($ioport -eq 0x64) -and ($action -eq 1) -and ($value -eq 0xd1)) { 373 | $ps2Command = $true 374 | } 375 | if ($ps2Command -and ($ioport -eq 0x60) -and ($action -eq 1) -and (($value -band 1) -eq 1) -and (($value -band 2) -eq 0)) { 376 | Write-Debug "W $x_ioport $numbytes $x_value - Skipping, resuming at index $offset" 377 | continue 378 | } 379 | if ($ps2Command -and ($ioport -eq 0x60) -and ($action -eq 1) -and (($value -band 1) -eq 0)) { 380 | Write-Debug "W $x_ioport $numbytes $x_value - Skipping, resuming at index $offset" 381 | continue 382 | } 383 | 384 | switch ($action) { 385 | 0 { # read 386 | $command = Set-IoCommand -VMChipsecFilepath $VMChipsecFilepath -port $ioport -len $numbytes -value $null 387 | } 388 | 1 { # write 389 | $command = Set-IoCommand -VMChipsecFilepath $VMChipsecFilepath -port $ioport -len $numbytes -value $value 390 | } 391 | } 392 | 393 | # Waiting until the VM runs again (until the debugger lets it go) 394 | do { 395 | $Error.Clear() 396 | Get-VM $VMName -ErrorAction SilentlyContinue > $null 397 | } while ($Error.Count -ne 0) 398 | 399 | try { 400 | Invoke-Command -Session $s -ScriptBlock {param($command) iex -command $command} -ArgumentList @($command) -ErrorAction Stop | Out-Null 401 | } catch { 402 | if ($_.Exception -like "No valid session were specified*" -or $_.Exception -like "*The session state is Broken*") { 403 | Write-Host "An error occured. On hold. Error message: $_". -ForegroundColor Red 404 | "" > $hold 405 | break loop 406 | } 407 | } 408 | 409 | $newBlockList = Get-Content $bpList 410 | if ($newBlockList.Count -ne $blocks.Count) { 411 | $keepIndex.Add($offset) > $null 412 | $blocks = $newBlockList 413 | } 414 | } 415 | 416 | # Adds to the corpus when the coverage was updated by the debugger during that run - only if not consuming the seed. 417 | if (($keepIndex.count -ne 0) -and ($seeds.Count -eq 0)) { 418 | # File system corpus 419 | if ($VirtualDiskType -and $fsfile -and ($fsfile -notlike "*\template.*")) { 420 | $fsindex = $fs.IndexOf($fsfile) 421 | $extension = [io.path]::GetExtension($fsfile) 422 | $newFsResident = Join-Path $fsCorpus ("corpus-$ctimestamp-$fsindex" + $extension) 423 | Copy-Item $fsfile $newFsResident 424 | } 425 | # IO input file in accelarated mode, any new increase triggers a new file 426 | if ($env:accelerated) { 427 | foreach ($k in $keepIndex) { 428 | if ($k -gt $largestKeepIndex) { 429 | $largestKeepIndex = $k 430 | $newResident = Join-Path $corpus "corpus-$ctimestamp-$k" 431 | $fileStream = [System.IO.File]::OpenWrite($newResident) 432 | $fileStream.Write($inputbytes, 0, $k) 433 | $fileStream.Close() 434 | } 435 | } 436 | } else { 437 | # Cuts at the latest change 438 | if ($keepIndex[-1] -gt $largestKeepIndex) { 439 | $largestKeepIndex = $keepIndex[-1] 440 | $newResident = Join-Path $corpus "corpus-$ctimestamp" 441 | $fileStream = [System.IO.File]::OpenWrite($newResident) 442 | $fileStream.Write($inputbytes, 0, $keepIndex[-1]) 443 | $fileStream.Close() 444 | } 445 | } 446 | $idleloopCount = 0 447 | } else { 448 | # If the coverage was not updated, increases a counter used to switch in accelerated mode 449 | $idleloopCount += 1 450 | } 451 | 452 | # If fuzzing the file system, sets the file system accordingly 453 | if ($VirtualDiskType) { 454 | $fs.Remove($fsfile) 455 | if ($fsfile) { 456 | if ([io.path]::GetExtension($fsfile) -eq ".vfd") { 457 | Set-VMFloppyDiskDrive -VMName $VMName -Path $null 458 | } elseif ([io.path]::GetExtension($fsfile) -eq ".iso") { 459 | Set-VMDvdDrive -VMName $VMName -Path $null 460 | } elseif ([io.path]::GetExtension($fsfile) -eq ".vhd") { 461 | # Remove-VMHardDiskDrive -VMName $VMName -Path $fsfile 462 | Unset-VMHardDiskDriveForFuzzing -VMName $VMName -Path $fsfile -s $s 463 | }else { 464 | # TODO 465 | } 466 | if ($fsfile -notlike "*\template.*") { 467 | Move-Item $fsfile $tmp 468 | } 469 | } 470 | } 471 | } while ($fs.Count -ne 0) 472 | 473 | # Handles the case of a crash 474 | if (Test-Path (Join-Path $PSScriptRoot "on-hold")) { 475 | while (Test-Path (Join-Path $PSScriptRoot "on-hold")) { 476 | Start-Sleep -Seconds 10 477 | } 478 | $monPid = $null 479 | Write-Host "Resuming execution with a new case." 480 | } 481 | 482 | # Updates the seed list if running against the seeds. 483 | if ($seeds.Count -ne 0) { 484 | $seeds.Remove($seeds[0]) 485 | } else { 486 | Move-Item $oldestTmpPath $tmp 487 | } 488 | 489 | # Switches to accelerated mode after 100 consecutive runs that did not increase the coverage 490 | if ((-not $env:accelerated) -and ($idleloopCount -ge 100)) { 491 | Write-Host "Entering stage 2 - accelarated fuzzing" -ForegroundColor Magenta 492 | $env:accelerated = $true 493 | } 494 | } 495 | -------------------------------------------------------------------------------- /core/helper.psm1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleprojectzero/Hyntrospect/8364fa25e4f65416f970804071478f5389f7c0b3/core/helper.psm1 -------------------------------------------------------------------------------- /core/input-generator.ps1: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | param ( 16 | [Parameter(Mandatory=$true)][string]$HostOutputDir, 17 | [Parameter(Mandatory=$true)][int]$maxInputSize, 18 | [Parameter(Mandatory=$true)][int]$minInputSize 19 | ) 20 | 21 | $helper = Join-Path $PSScriptRoot helper.psm1 22 | Import-Module $helper -Force 23 | 24 | $corpus = Join-Path $HostOutputDir "corpus" 25 | 26 | # Initialization of the corpus 27 | if (-not (Test-Path $corpus -PathType Container)) { 28 | mkdir $corpus | Out-Null 29 | } 30 | 31 | $seed = ls $corpus | Where {($_.Name -like "corpus*") -or ($_.Name -like "seed*") -or ($_.Name -like "template*")} 32 | if ($seed -eq $null) { 33 | Init-Corpus -corpus $corpus 34 | } 35 | 36 | $availableFiles = Join-Path $corpus "tmp*" 37 | while ((-not (ls $availableFiles)) -or ((ls $availableFiles | measure).Count -lt 10)) { 38 | $switch = Get-Random -Minimum 0 -Maximum 9 39 | # 10%: random file generation, 30%: corpus file appended, 60%: mutated corpus file 40 | switch($switch) { 41 | 0 { 42 | $tmp = "tmp_" + (Get-Date -Format "yyMMdd-HHmmss") + "_random" 43 | $filepath = Join-Path $corpus $tmp 44 | New-RandomFile -filepath $filepath -minInputSize $minInputSize -maxInputSize $maxInputSize 45 | } 46 | 1 {New-AppendedFile -corpus $corpus -maxInputSize $maxInputSize} 47 | 2 {New-AppendedFile -corpus $corpus -maxInputSize $maxInputSize} 48 | 3 {New-AppendedFile -corpus $corpus -maxInputSize $maxInputSize} 49 | 4 {New-MutatedFile -corpus $corpus} 50 | 5 {New-MutatedFile -corpus $corpus} 51 | 6 {New-MutatedFile -corpus $corpus} 52 | 7 {New-MutatedFile -corpus $corpus} 53 | 8 {New-MutatedFile -corpus $corpus} 54 | 9 {New-MutatedFile -corpus $corpus} 55 | } 56 | } -------------------------------------------------------------------------------- /core/replay-case.ps1: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | param( 16 | [Parameter(Mandatory=$true)][string]$configFile, 17 | [Parameter(Mandatory=$true)][string]$reproFile, 18 | [Parameter(Mandatory=$true)][string]$folder, 19 | [Parameter(Mandatory=$false)][System.Collections.ArrayList]$virtualdisks 20 | ) 21 | 22 | # Please make sure you have a Windows VM with chipsec installed and the ability to open an elevated session over WinRM. 23 | $config = Get-Content -Path $configFile | ConvertFrom-Json 24 | $VMName = $config.VMName 25 | $CheckpointName = $config.VMCheckpointName 26 | $HostOutputDir = $config.HostOutputDir 27 | $VMChipsecFilepath = $config.VMChipsecFilepath 28 | $username = $config.VMUsername 29 | $password = $config.VMPassword 30 | $ScriptPath = $PSScriptRoot 31 | $IdaPath = $config.HostIdaPath 32 | $binary = $config.Target 33 | $IOPortRead = $config.IOPortRead 34 | $IOPortWrite = $config.IOPortWrite 35 | $VirtualDiskType = $config.VirtualDiskType 36 | 37 | 38 | # Gets the ID of the VM 39 | $vmid = (Get-VM -Name $VMName | select -ExpandProperty "vmid").Guid 40 | 41 | # Loads the helper functions 42 | $helper = Join-Path $ScriptPath helper.psm1 43 | Import-Module $helper -Force 44 | 45 | $secpass = ConvertTo-SecureString -String $password -AsPlainText -Force 46 | $creds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ($username, $secpass) 47 | 48 | # Sets error handling. 49 | $Error.Clear() 50 | $ErrorActionPreference = 'Continue' 51 | 52 | # Gets the binary name without extension for the debugger 53 | $binaryNameWithoutExtension = [io.path]::GetFileNameWithoutExtension($binary) 54 | 55 | # Sets up the file system fuzzer when applicable 56 | $fs = New-Object System.Collections.ArrayList 57 | if ($virtualdisks) { 58 | $fs = $virtualdisks 59 | $fs.Insert(0, $null) 60 | } 61 | 62 | # Initializes local variables 63 | $monPid = $null 64 | $dpid = $null 65 | $s = $null 66 | 67 | # Resets the environment 68 | Write-Host "Resetting the environment" 69 | Restore-VMSnapshot -VMName $VMName -Name $CheckpointName -Confirm:$false 70 | 71 | # Checks that the VM is up 72 | if ((Get-VM -Name $VMName).State -ne "Running") { 73 | Start-VM -Name $VMName 74 | } 75 | 76 | # Sets the PS session for VM communication 77 | $s = New-PSSession -VMName $VMName -Credential $creds 78 | 79 | # Gets the starting time of the host and VM - used later as a reference for the collection of the event logs. 80 | $hostTime = Get-Date 81 | $timestamp = Get-date -Format "yyMMdd-HHmm" 82 | Write-Verbose("Host time: " + $hostTime) 83 | $vmTime = Invoke-Command -Session $s -ScriptBlock {Get-Date} 84 | 85 | # Connects to the debugee 86 | Write-Host "Connecting to the debuggee" 87 | $debuggerScript = Join-Path $scriptPath "debugger-replay.ps1" 88 | $hostTimeStr = $hostTime.GetDateTimeFormats('o') 89 | $vmTimeStr = $vmTime.GetDateTimeFormats('o') 90 | $dpid = (Start-Process -FilePath .\DbgShell\x64\DbgShell.exe -PassThru -ArgumentList "$debuggerScript ""$configfile"" ""$binaryNameWithoutExtension"" ""$hostTimeStr"" ""$vmTimeStr"" ").Id 91 | 92 | # Starts the monitoring process for that new round 93 | Write-Host "Starting the monitoring process" 94 | $monitoringScriptPath = Join-Path $ScriptPath "vm-monitoring.ps1" 95 | $moncommand = "-configFile ""$configFile"" -hostTime ""$hostTime"" -vmTime ""$vmTime"" -tmpFolder ""$folder"" " 96 | $monPid = Start-HelperProcess -script $monitoringScriptPath -commandline $moncommand 97 | 98 | # Consumes the input file. 99 | $inputbytes = Get-Content $reproFile -Encoding Byte 100 | $inputfilelen = $inputbytes.Length 101 | Write-Host "Replaying $reproFile" 102 | 103 | # Executes the payload on the VM. One iteration per virtual disk attached, only 1 if not fuzzing virtual disks. 104 | do { 105 | # Input file offset 106 | $offset = 0 107 | 108 | # Variable for the exclusion list 109 | $ps2Command = $false 110 | 111 | # If fuzzing the file system, sets the file system 112 | if ($VirtualDiskType) { 113 | 114 | $fsfile = $fs[0] 115 | 116 | if ($VirtualDiskType.GetType().Name -eq "string") { 117 | if ($VirtualDiskType -eq "vfd") { 118 | Set-VMFloppyDiskDrive -VMName $VMName -Path $fsfile 119 | } elseif ($VirtualDiskType -eq "iso") { 120 | Set-VMDvdDrive -VMName $VMName -Path $fsfile 121 | } elseif ($VirtualDiskType -eq "vhd") { 122 | # Set-VMHardDiskDrive -VMName $VMName -Path $fsfile 123 | Set-VMHardDiskDriveForFuzzing -VMName $VMName -Path $fsfile -s $s 124 | } else { 125 | Write-Warning "`tDid not recognize the format of $fsfile." 126 | } 127 | } else { # case of multiple fuzzed formats 128 | if ($VirtualDiskType.Contains("vfd") -and (($fsfile -eq $null) -or ([io.path]::GetExtension($fsfile) -eq ".vfd"))) { 129 | Set-VMFloppyDiskDrive -VMName $VMName -Path $fsfile 130 | } 131 | if ($VirtualDiskType.Contains("iso") -and (($fsfile -eq $null) -or ([io.path]::GetExtension($fsfile) -eq ".iso"))) { 132 | Set-VMDvdDrive -VMName $VMName -Path $fsfile 133 | } 134 | if ($VirtualDiskType.Contains("vhd") -and (($fsfile -eq $null) -or ([io.path]::GetExtension($fsfile) -eq ".vhd"))) { 135 | # Set-VMHardDiskDrive -VMName $VMName -Path $fsfile 136 | Set-VMHardDiskDriveForFuzzing -VMName $VMName -Path $fsfile -s $s 137 | } 138 | } 139 | 140 | if ($fsfile) { 141 | Write-Host "`tAttached $fsfile." 142 | } else { 143 | Write-Host "`tNo virtual disk attached." 144 | } 145 | } 146 | 147 | # Each iteration in that loop corresponds to one command sent 148 | while($offset -lt $inputfilelen) { 149 | ($offset, $byte) = Consume-Bytes -bytes $inputbytes -offset $offset -numBytes 1 -filelen $inputfilelen 150 | $action = $byte % 2 # $action = 0 -> read, $action = 1 -> write 151 | 152 | ($offset, $byte) = Consume-Bytes -bytes $inputbytes -offset $offset -numBytes 1 -filelen $inputfilelen 153 | if ($action -eq 0) { 154 | $ioport = $IOPortRead[$byte % $IOPortRead.length] 155 | } else { 156 | $ioport = $IOPortWrite[$byte % $IOPortWrite.length] 157 | } 158 | $x_ioport = '0x'+ '{0:x}'-f $ioport 159 | 160 | ($offset, $byte) = Consume-Bytes -bytes $inputbytes -offset $offset -numBytes 1 -filelen $inputfilelen 161 | $numbytes = @(1, 2, 4)[$byte % 3] 162 | 163 | if ($action -eq 1) { 164 | ($offset, $value) = Consume-Bytes -bytes $inputbytes -offset $offset -numBytes $numbytes -filelen $inputfilelen 165 | $x_value = '0x'+ '{0:x}' -f $value 166 | Write-Verbose "W $x_ioport $numbytes $x_value, next index: $offset" 167 | } else { 168 | Write-Verbose "R $x_ioport $numbytes, next index: $offset" 169 | } 170 | 171 | # Exclusion list lists 172 | if (($ioport -eq 0x64) -and ($action -eq 1) -and ($value -ge 0xF0)) { 173 | Write-Verbose "Skipping, resuming at index $offset" 174 | continue 175 | } 176 | if (($ioport -eq 0x92) -and ($action -eq 1) -and (($value -band 1) -eq 1)) { 177 | Write-Verbose "Skipping, resuming at index $offset" 178 | continue 179 | } 180 | # A20 gate 181 | if (($ioport -eq 0x64) -and ($action -eq 1) -and ($value -eq 0xd1)) { 182 | $ps2Command = $true 183 | } 184 | if ($ps2Command -and ($ioport -eq 0x60) -and ($action -eq 1) -and (($value -band 1) -eq 1) -and (($value -band 2) -eq 0)) { 185 | Write-Verbose "W $x_ioport $numbytes $x_value - Skipping, resuming at index $offset" 186 | continue 187 | } 188 | if ($ps2Command -and ($ioport -eq 0x60) -and ($action -eq 1) -and (($value -band 1) -eq 0)) { 189 | Write-Verbose "W $x_ioport $numbytes $x_value - Skipping, resuming at index $offset" 190 | continue 191 | } 192 | 193 | switch ($action) { 194 | 0 { # read 195 | $command = Set-IoCommand -VMChipsecFilepath $VMChipsecFilepath -port $ioport -len $numbytes -value $null 196 | } 197 | 1 { # write 198 | $command = Set-IoCommand -VMChipsecFilepath $VMChipsecFilepath -port $ioport -len $numbytes -value $value 199 | } 200 | } 201 | 202 | # Waiting until the VM runs again (until the debugger lets it go) 203 | do { 204 | $Error.Clear() 205 | Get-VM $VMName -ErrorAction SilentlyContinue > $null 206 | } while ($Error.Count -ne 0) 207 | 208 | Invoke-Command -Session $s -ScriptBlock {param($command) iex -command $command} -ArgumentList @($command) | Out-Null 209 | 210 | } 211 | 212 | # If fuzzing the file system, sets the file system accordingly 213 | if ($VirtualDiskType) { 214 | $fs.Remove($fsfile) 215 | if ($fsfile) { 216 | if (([System.IO.FileInfo]$fsfile).Extension -eq ".vfd") { 217 | Set-VMFloppyDiskDrive -VMName $VMName -Path $null 218 | } elseif (([System.IO.FileInfo]$fsfile).Extension -eq ".iso") { 219 | Set-VMDvdDrive -VMName $VMName -Path $null 220 | } elseif (([System.IO.FileInfo]$fsfile).Extension -eq ".vhd") { 221 | Unset-VMHardDiskDriveForFuzzing -VMName $VMName -Path $fsfile -s $s 222 | # Remove-VMHardDiskDrive -VMName $VMName -Path $fsfile 223 | } else {} 224 | } 225 | } 226 | } while ($fs.Count -ne 0) 227 | 228 | 229 | # Shuts down 230 | Write-Host "Done." 231 | Write-Host "Killing the monitoring and the debugging process." 232 | 233 | Remove-PSSession $s 234 | Stop-Process -Id $monPid -Force 235 | if (-not (Get-Process -Id $dpid)) { 236 | Write-Warning "Debug process already killed!!" 237 | } 238 | Stop-Process -Id $dpid -Force -------------------------------------------------------------------------------- /core/vm-monitoring.ps1: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | param( 16 | [Parameter(Mandatory=$true)][string]$configFile, 17 | [Parameter(Mandatory=$true)][DateTime]$hostTime, 18 | [Parameter(Mandatory=$true)][DateTime]$vmTime, 19 | [Parameter(Mandatory=$false)][string]$tmpFolder 20 | ) 21 | 22 | $config = Get-Content -Path $configFile | ConvertFrom-Json 23 | $VMName = $config.VMName 24 | $HostOutputDir = $config.HostOutputDir 25 | $username = $config.VMUsername 26 | $password = $config.VMPassword 27 | $VirtualDiskType = $config.VirtualDiskType 28 | 29 | $helper = Join-Path $PSScriptRoot helper.psm1 30 | Import-Module $helper -Force 31 | 32 | # This file monitors the VM. Lifetime: consumption of one random input file 33 | #while(Check-VMSanity -VMName $VMName) 34 | #{ 35 | #If everything is normal, the process will be killed while in that loop 36 | #} 37 | $ticks = (Get-VM -Name $VMName).Uptime.Ticks 38 | while ((Get-VM -Name $VMName).State -eq "Running" -and $ticks -le (Get-VM -Name $VMName).Uptime.Ticks) { 39 | $ticks = (Get-VM -Name $VMName).Uptime.Ticks 40 | Start-Sleep -Seconds 1 41 | } 42 | 43 | # Gets here if the VM has met an issue 44 | $vm = Get-VM -Name $VMName 45 | $state = $vm.State 46 | $status = $vm.Status 47 | Write-Error "VM not running. State: $state, status: $status." 48 | 49 | if ($tmpFolder) { 50 | $crashDir = New-CrashReport -ConfigFile $ConfigFile -tmp $tmpFolder 51 | } else { 52 | $crashDir = New-CrashReport -ConfigFile $ConfigFile 53 | } 54 | 55 | $secpass = ConvertTo-SecureString -String $password -AsPlainText -Force 56 | $creds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ($username, $secpass) 57 | 58 | Collect-CrashInfo -VMName $VMName -creds $creds -crashDir $crashDir -hostTime $hostTime -vmTime $vmTime -err $Error[0] 59 | 60 | RecoverFrom-Crash -monitoring -------------------------------------------------------------------------------- /data/template.vfd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleprojectzero/Hyntrospect/8364fa25e4f65416f970804071478f5389f7c0b3/data/template.vfd -------------------------------------------------------------------------------- /data/template.vhd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleprojectzero/Hyntrospect/8364fa25e4f65416f970804071478f5389f7c0b3/data/template.vhd -------------------------------------------------------------------------------- /utils/Create-CorpusSeed.ps1: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | param( 16 | [Parameter(Mandatory=$true)][string]$ConfigFile, 17 | [Parameter(Mandatory=$true)][string]$FuzzedDevice, 18 | [string]$SeedName = "seed", 19 | [int]$Timeout = 2 20 | ) 21 | 22 | if (-not (Test-Path $configFile)) { 23 | Write-Error "Config file not found at $ConfigFile. Aborting." 24 | return 25 | } 26 | $configFile = Resolve-Path $ConfigFile 27 | 28 | # Make sure you have a Windows VM with chipsec installed and the ability to open an elevated session over WinRM. 29 | $config = Get-Content -Path $configFile | ConvertFrom-Json 30 | $VMName = $config.VMName 31 | $CheckpointName = $config.VMCheckpointName 32 | $HostOutputDir = $config.HostOutputDir 33 | $username = $config.VMUsername 34 | $ScriptPath = $PSScriptRoot 35 | $binary = $config.Target 36 | $debuggerPath = Resolve-Path -Path ("$ScriptPath\..\core") 37 | 38 | if ($SeedName -notlike "seed*") { 39 | Write-Error "The name of the seed file should start with 'seed' for future compliance. Aborting." 40 | return 41 | } 42 | 43 | if (($VMName -eq $null) -or ($username -eq $null) -or ($CheckpointName -eq $null) -or ($HostOutputDir -eq $null) -or ($binary -eq $null)) { 44 | Write-Error "The config file must at least contain the fields: 'VMName', 'VMUsername', 'VMCheckpointName', 'HostOutputDir', 'Target'. Aborting." 45 | return 46 | } 47 | 48 | if ((Test-Path $HostOutputDir) -ne $true -or (Test-Path $HostOutputDir -PathType Container) -eq $false) { 49 | Write-Error "Invalid output directory. Please provide an existing directory. Aborting." 50 | return 51 | } 52 | 53 | if (-not (Test-Path $binary -PathType Leaf)) { 54 | Write-Error "Target is not a valid path. Aborting." 55 | return 56 | } 57 | 58 | if (-not (Test-Path $debuggerPath)) { 59 | Write-Error "DbgShell folder cannot be found. Expecting to find .\DbgShell\x64\DbgShell.exe in core. Aborting." 60 | return 61 | } 62 | 63 | $pdb = [System.IO.Path]::GetFileNameWithoutExtension($binary) + ".pdb" 64 | if (-not (Test-Path (Join-Path "C:\Symbols\" $pdb))) { 65 | Write-Warning "$pdb not found in C:\Symbols. The symbols will need to be pulled from the network." 66 | } 67 | 68 | $Error.Clear() 69 | Get-VM -Name $VMName -ErrorAction SilentlyContinue 70 | if ($Error.Count -ne 0) { 71 | Write-Error "The VM Name is invalid or this PowerShell session is not elevated. Aborting." 72 | return 73 | } 74 | if ((Get-VMSnapshot -VMName $VMName | Where Name -eq $CheckpointName) -eq $null) { 75 | Write-Error "The VM Checkpoint name is invalid. Aborting." 76 | return 77 | } 78 | 79 | Push-Location 80 | cd $debuggerPath 81 | 82 | # Getting the credentials of the VM 83 | Write-Host "Please enter the VM password for user $username :" 84 | $secpass = Read-Host -assecurestring 85 | $creds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ($username, $secpass) 86 | 87 | $corpus = Join-Path $HostOutputDir "corpus" 88 | if (-not(Test-Path $corpus)) { 89 | Write-Host "Creating $corpus." 90 | mkdir $corpus > $null 91 | } 92 | $seedfile = Join-Path $corpus $SeedName 93 | Write-Host "The output file will be: $seedfile." 94 | 95 | Write-Host "Resetting the VM." 96 | Restore-VMSnapshot -VMName $VMName -Name $CheckpointName -Confirm:$false 97 | # Checks that the VM is up 98 | if ((Get-VM -Name $VMName).State -ne "Running") { 99 | Start-VM -Name $VMName 100 | } 101 | 102 | $s = New-PSSession -VMName $VMName -Credential $creds 103 | 104 | Write-Host "Recording activity for the next $timeout minutes. If possible, generate as much activity as possible for device $FuzzedDevice." 105 | $timer = [System.Diagnostics.Stopwatch]::StartNew() 106 | 107 | $debuggerScript = Join-Path $debuggerPath "debugger-seed.ps1" 108 | $dpid = (Start-Process -FilePath .\DbgShell\x64\DbgShell.exe -PassThru -ArgumentList "$debuggerScript ""$configfile"" ""$seedfile"" ""$FuzzedDevice"" ").Id 109 | 110 | 111 | while ($timer.Elapsed.Minutes -lt $Timeout) { 112 | } 113 | 114 | Stop-Process -Id $dpid 115 | 116 | Pop-Location -------------------------------------------------------------------------------- /utils/Create-CoverageFile.ps1: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | param( 16 | [Parameter(Mandatory=$true)][string]$InitialBpList, 17 | [Parameter(Mandatory=$true)][string]$UpdatedBpList, 18 | [Parameter(Mandatory=$true)][string]$OutputDir, 19 | [Parameter(Mandatory=$true)][string]$Binary 20 | ) 21 | 22 | $iBp = Get-Content $InitialBpList 23 | $uBp = Get-Content $UpdatedBpList 24 | $oFile = Join-Path $OutputDir ("coverage-" + (Split-Path $UpdatedBpList -Leaf)) 25 | $binaryRoot = [io.path]::GetFileNameWithoutExtension($Binary) 26 | 27 | foreach ($block in $iBp) { 28 | if (-not ($uBp.Contains($block))) { 29 | $text = $binaryRoot + "+" + $block 30 | Add-Content -Path $oFile -Value $text 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /utils/Translate-InputBytesToFulltext.ps1: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | param( 16 | [Parameter(Mandatory=$true)][string]$ConfigFile, 17 | [Parameter(Mandatory=$true)][string]$InputFile 18 | ) 19 | 20 | $helper = "$PSScriptRoot/../core/helper.psm1" 21 | if (-not (Test-Path $helper)) { 22 | Write-Error "Helper not found. This file should be in 'utils', aside with 'core', which should contain 'helper.psm1'. Aborting." 23 | return 24 | } 25 | Import-Module $helper -Force 26 | 27 | $config = Get-Content -Path $ConfigFile | ConvertFrom-Json 28 | $IOPortRead = $config.IOPortRead 29 | $IOPortWrite = $config.IOPortWrite 30 | 31 | $inputbytes = Get-Content $InputFile -Encoding Byte 32 | $inputfilelen = $inputbytes.Length 33 | $offset = 0 34 | # Variable for the exclusion list 35 | $ps2Command = $false 36 | 37 | while($offset -lt $inputfilelen) { 38 | $bytes = "" 39 | ($offset, $byte) = Consume-Bytes -bytes $inputbytes -offset $offset -numBytes 1 -filelen $inputfilelen 40 | $action = $byte % 2 # $action = 0 -> read, $action = 1 -> write 41 | $bytes += '0x'+ '{0:x}'-f $byte + " " 42 | 43 | ($offset, $byte) = Consume-Bytes -bytes $inputbytes -offset $offset -numBytes 1 -filelen $inputfilelen 44 | $bytes += '0x'+ '{0:x}'-f $byte + " " 45 | 46 | if ($action -eq 0) { 47 | $ioport = $IOPortRead[$byte % $IOPortRead.length] 48 | } else { 49 | $ioport = $IOPortWrite[$byte % $IOPortWrite.length] 50 | } 51 | $x_ioport = '0x'+ '{0:x}'-f $ioport 52 | 53 | ($offset, $byte) = Consume-Bytes -bytes $inputbytes -offset $offset -numBytes 1 -filelen $inputfilelen 54 | $numbytes = @(1, 2, 4)[$byte % 3] 55 | $bytes += '0x'+ '{0:x}'-f $byte + " " 56 | 57 | if ($action -eq 1) { 58 | ($offset, $value) = Consume-Bytes -bytes $inputbytes -offset $offset -numBytes $numbytes -filelen $inputfilelen 59 | $x_value = '0x'+ '{0:x}' -f $value 60 | $bytes += "$x_value " 61 | } 62 | 63 | if (($ioport -eq 0x64) -and ($action -eq 1) -and ($value -eq 0xd1)) { 64 | $ps2Command = $true 65 | } 66 | 67 | switch ($action) { 68 | 0 { # read 69 | Write-Output "$bytes -> R $x_ioport $numbytes" 70 | } 71 | 1 { # write 72 | if (($ioport -eq 0x64) -and ($value -ge 0xF0)) { 73 | Write-Output "$bytes -> W $x_ioport $numbytes $x_value - Skipped - PS2 reset on 0x64 high values." 74 | } elseif (($ioport -eq 0x92) -and (($value -band 1) -eq 1)) { 75 | Write-Output "$bytes -> W $x_ioport $numbytes $x_value - Skipped - Fast A20 reset." 76 | } elseif ($ps2Command -and ($ioport -eq 0x60) -and (($value -band 1) -eq 1) -and (($value -band 2) -eq 0)) { 77 | Write-Output "$bytes -> W $x_ioport $numbytes $x_value - Skipped - A20 gate case via PS2." 78 | } elseif ($ps2Command -and ($ioport -eq 0x60) -and (($value -band 1) -eq 0)) { 79 | Write-Output "$bytes -> W $x_ioport $numbytes $x_value - Skipped - PS2 system reset." 80 | } else { 81 | Write-Output "$bytes -> W $x_ioport $numbytes $x_value" 82 | } 83 | } 84 | } 85 | } 86 | 87 | -------------------------------------------------------------------------------- /utils/findPatchPoints.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import idautils 16 | import idaapi 17 | import ida_nalt 18 | import idc 19 | 20 | if len(idc.ARGV) != 2: 21 | print("Need to have the output filepath as an argument. Using default: ~\Desktop\patches.txt") 22 | from os.path import expanduser 23 | home = expanduser("~") 24 | filepath = home + "\\Desktop\\patches.txt" 25 | else: 26 | filepath = idc.ARGV[1] 27 | print(filepath) 28 | 29 | auto_wait() 30 | 31 | patchpoints = set() 32 | base = idaapi.get_imagebase() 33 | 34 | for seg_ea in idautils.Segments(): 35 | name = idc.get_segm_name(seg_ea) 36 | if name != ".text": 37 | continue 38 | 39 | start = idc.get_segm_start(seg_ea) 40 | end = idc.get_segm_end(seg_ea) 41 | for func_ea in idautils.Functions(start, end): 42 | f = idaapi.get_func(func_ea) 43 | if not f: 44 | continue 45 | for block in idaapi.FlowChart(f): 46 | if f.start_ea <= block.start_ea < f.end_ea: 47 | patchpoints.add(block.start_ea - base) 48 | 49 | with open(filepath, "w") as f: 50 | f.write('\n'.join(map(hex, sorted(patchpoints)))) 51 | 52 | print("Done, found {} patchpoints".format(len(patchpoints))) 53 | -------------------------------------------------------------------------------- /utils/findPatchPointsWithKeyword.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import idautils 16 | import idaapi 17 | import ida_nalt 18 | import idc 19 | 20 | if len(idc.ARGV) != 2: 21 | print("Need to have the output filepath as an argument. Using default: ~\Desktop\patches.txt") 22 | from os.path import expanduser 23 | home = expanduser("~") 24 | filepath = home + "\\Desktop\\patches.txt" 25 | else: 26 | filepath = idc.ARGV[1] 27 | print(filepath) 28 | 29 | auto_wait() 30 | 31 | patchpoints = set() 32 | base = idaapi.get_imagebase() 33 | 34 | keyword = "@Ide" 35 | 36 | for seg_ea in idautils.Segments(): 37 | name = idc.get_segm_name(seg_ea) 38 | if name != ".text": 39 | continue 40 | 41 | start = idc.get_segm_start(seg_ea) 42 | end = idc.get_segm_end(seg_ea) 43 | for func_ea in idautils.Functions(start, end): 44 | funcName = idc.get_func_name(func_ea) 45 | # Check if the function name starts with "Player_GetStats" 46 | if keyword in funcName: 47 | print(funcName) 48 | f = idaapi.get_func(func_ea) 49 | if not f: 50 | continue 51 | for block in idaapi.FlowChart(f): 52 | if f.start_ea <= block.start_ea < f.end_ea: 53 | patchpoints.add(block.start_ea - base) 54 | 55 | with open(filepath, "w") as f: 56 | f.write('\n'.join(map(hex, sorted(patchpoints)))) 57 | 58 | print("Done, found {} patchpoints".format(len(patchpoints))) 59 | --------------------------------------------------------------------------------