Prepare ms-private 0.0.1 release candidate
This commit is contained in:
1 parent
7aacc0b63f
commit
a515538438
75 files changed
+3957
-3484
No files matched your search
@@ -0,0 +1,68 @@
|
||||
#Requires -Version 5.1
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)][ValidateScript({Test-Path -LiteralPath $_ -PathType Leaf})][string]$Path,
|
||||
[Parameter(Mandatory)][ValidatePattern('^\d+\.\d+\.\d+$')][string]$Version
|
||||
)
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference='Stop'
|
||||
$resolved=[IO.Path]::GetFullPath($Path)
|
||||
$bytes=[IO.File]::ReadAllBytes($resolved)
|
||||
if($bytes.Length-lt 1024){throw 'Executable is unexpectedly small.'}
|
||||
if($bytes[0]-ne0x4d-or$bytes[1]-ne0x5a){throw 'DOS header is missing.'}
|
||||
$pe=[BitConverter]::ToInt32($bytes,0x3c)
|
||||
if($pe-lt0-or($pe+96)-ge$bytes.Length-or[BitConverter]::ToUInt32($bytes,$pe)-ne0x00004550){throw 'PE header is invalid.'}
|
||||
if([BitConverter]::ToUInt16($bytes,$pe+4)-ne0x8664){throw 'Executable is not AMD64.'}
|
||||
$optional=$pe+24
|
||||
if([BitConverter]::ToUInt16($bytes,$optional)-ne0x20b){throw 'Executable is not PE32+.'}
|
||||
if([BitConverter]::ToUInt16($bytes,$optional+68)-ne2){throw 'Executable is not a Windows GUI application.'}
|
||||
|
||||
function Read-U16([byte[]]$Data,[int]$Offset){[BitConverter]::ToUInt16($Data,$Offset)}
|
||||
function Read-U32([byte[]]$Data,[int]$Offset){[BitConverter]::ToUInt32($Data,$Offset)}
|
||||
function Convert-Rva([byte[]]$Data,[int]$SectionTable,[int]$SectionCount,[uint32]$Rva){
|
||||
for($index=0;$index-lt$SectionCount;$index++){
|
||||
$section=$SectionTable+($index*40);$virtualSize=Read-U32 $Data ($section+8);$virtualAddress=Read-U32 $Data ($section+12);$rawSize=Read-U32 $Data ($section+16);$raw=Read-U32 $Data ($section+20)
|
||||
$span=[Math]::Max([long]$virtualSize,[long]$rawSize)
|
||||
if([long]$Rva-ge[long]$virtualAddress-and[long]$Rva-lt([long]$virtualAddress+$span)){return [int]([long]$raw+([long]$Rva-[long]$virtualAddress))}
|
||||
}
|
||||
throw 'Resource RVA is outside the PE sections.'
|
||||
}
|
||||
function Get-ResourceEntries([byte[]]$Data,[int]$Directory){
|
||||
$count=(Read-U16 $Data ($Directory+12))+(Read-U16 $Data ($Directory+14));$rows=@()
|
||||
for($index=0;$index-lt$count;$index++){$offset=$Directory+16+($index*8);$rows+=,[pscustomobject]@{Name=Read-U32 $Data $offset;Target=Read-U32 $Data ($offset+4)}}
|
||||
$rows
|
||||
}
|
||||
function Get-IdEntry([byte[]]$Data,[int]$Directory,[uint32]$Id){
|
||||
$match=@(Get-ResourceEntries $Data $Directory|Where-Object{([int64]$_.Name-band0x80000000L)-eq0-and($_.Name-band0xffff)-eq$Id})
|
||||
if($match.Count-ne1){throw "PE resource ID $Id was not found exactly once."};$match[0]
|
||||
}
|
||||
$sectionCount=Read-U16 $bytes ($pe+6);$optionalSize=Read-U16 $bytes ($pe+20);$sectionTable=$optional+$optionalSize
|
||||
$resourceRva=Read-U32 $bytes ($optional+128)
|
||||
if($resourceRva-eq0){throw 'PE resource table is missing.'}
|
||||
$resourceBase=Convert-Rva $bytes $sectionTable $sectionCount $resourceRva
|
||||
$type=Get-IdEntry $bytes $resourceBase 24
|
||||
if(([int64]$type.Target-band0x80000000L)-eq0){throw 'Manifest type does not reference a resource directory.'}
|
||||
$nameDirectory=$resourceBase+[int]([int64]$type.Target-band0x7fffffffL)
|
||||
$name=Get-IdEntry $bytes $nameDirectory 1
|
||||
if(([int64]$name.Target-band0x80000000L)-eq0){throw 'Manifest name does not reference a language directory.'}
|
||||
$languageDirectory=$resourceBase+[int]([int64]$name.Target-band0x7fffffffL)
|
||||
$languages=@(Get-ResourceEntries $bytes $languageDirectory)
|
||||
if($languages.Count-lt1-or([int64]$languages[0].Target-band0x80000000L)-ne0){throw 'Manifest language entry is invalid.'}
|
||||
$dataEntry=$resourceBase+[int]([int64]$languages[0].Target-band0x7fffffffL)
|
||||
$manifestRva=Read-U32 $bytes $dataEntry;$manifestSize=Read-U32 $bytes ($dataEntry+4)
|
||||
$manifestOffset=Convert-Rva $bytes $sectionTable $sectionCount $manifestRva
|
||||
if($manifestSize-eq0-or([long]$manifestOffset+$manifestSize)-gt$bytes.Length){throw 'Embedded application manifest is invalid.'}
|
||||
$manifestBytes=[byte[]]::new($manifestSize);[Array]::Copy($bytes,$manifestOffset,$manifestBytes,0,$manifestSize)
|
||||
$manifest=[Text.Encoding]::UTF8.GetString($manifestBytes)
|
||||
if($manifest-notmatch'requestedExecutionLevel\s+level="requireAdministrator"'){throw 'Executable does not request administrator rights.'}
|
||||
|
||||
$info=[Diagnostics.FileVersionInfo]::GetVersionInfo($resolved)
|
||||
if($info.FileVersion-notlike"$Version*"){throw "Unexpected file version: $($info.FileVersion)"}
|
||||
$checksumPath=$resolved+'.sha256'
|
||||
if(-not(Test-Path -LiteralPath $checksumPath -PathType Leaf)){throw 'Adjacent SHA-256 file is missing.'}
|
||||
$expected=(([IO.File]::ReadAllText($checksumPath)-split'\s+')[0]).ToLowerInvariant()
|
||||
$actual=(Get-FileHash -LiteralPath $resolved -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
if($expected-ne$actual){throw 'Executable SHA-256 does not match the adjacent checksum.'}
|
||||
$signature=Get-AuthenticodeSignature -LiteralPath $resolved
|
||||
if($signature.Status-notin@('NotSigned','Valid')){throw "Unexpected Authenticode status: $($signature.Status)"}
|
||||
Write-Output "Executable inspection passed: AMD64 GUI, requireAdministrator, version $Version, SHA-256 $actual, signature $($signature.Status)."
|
||||
Reference in new issue
Block a user