SQLite.ps1
# Parses SQLite varint V3 # Sep 27th 2022 function Parse-SQLiteVarIntV3 { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [byte[]]$Data, [Parameter(Mandatory=$true)] [ref]$Position ) Process { return Decode-MultiByteInteger -Data $Data -Position $Position -Reverse } } # Parses SQLite database B-Tree cell payload # Sep 27th 2022 function Parse-SQLiteBTreeCellPayload { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [byte[]]$Data, [Parameter(Mandatory=$false)] [int]$MaxColumns=20 ) Process { # Ref. https://www.sqlite.org/fileformat.html # Parse the header $headerSize = $Data[0] # Parse the columns $pCol = $headerSize $nCol = 0 $columns = @() for($p = 1 ; ($p -lt $headerSize);) { $serialType = Parse-SQLiteVarIntV3 -Data $Data -Position ([ref]$p) switch($serialType) { # null 0 { $value = $null break } # Integer 1 { $value = [int] $Data[$pCol]; $pCol++ break } # Integer {$_ -gt 2 -and $_ -lt 8} { switch($_) { {$_ -lt 5} {$nBytes = $_; break} {$_ -eq 5} {$nBytes = 6; break} default {$nBytes = 8; break} } $bytes = New-Object Byte[] 8 [Array]::Copy($Data,$pCol,$bytes,8-$nBytes,$nBytes) [Array]::Reverse($bytes) $value = [bitconverter]::ToInt64($bytes,0); $pCol += $nBytes break } # Integer 0 8 { $value = [int64] 0 break } # Integer 1 9 { $value = [int64] 1 break } # Blob {$_ -ge 12 -and $_ % 2 -eq 0} { $bLen = ($_ - 12) / 2 $value = $Data[$pCol..$($pCol + $bLen -1)]; $pCol += $bLen break } # String - we'll assume UTF-8 encoding {$_ -ge 13 -and $_ % 2 -ne 0} { $bLen = ($_ - 13) / 2 $value = [System.Text.Encoding]::UTF8.GetString($Data[$pCol..$($pCol + $bLen -1)]); $pCol += $bLen break } } $columns+= $value } return $columns } } # Parses SQLite database B-Tree cell # Sep 27th 2022 function Parse-SQLiteBTreeCell { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [byte[]]$Data, [Parameter(Mandatory=$true)] [int]$Position, [Parameter(Mandatory=$true)] [int]$PageType, [Parameter(Mandatory=$true)] [int]$PageSize ) Process { # Ref. https://www.sqlite.org/fileformat.html $p = $Position # Overflow calcuation variables $u = $PageSize # We assume no reserverd space $m = (($u-12)*32/255)-23 # Always the same switch($PageType) { 0x0d #B-Tree Leaf Cell { $leftChild = $null $payLoadBytes = Parse-SQLiteVarIntV3 -Data $Data -Position ([ref]$p) $rowId = Parse-SQLiteVarIntV3 -Data $Data -Position ([ref]$p) $payLoad = $Data[$p..$($p + $payloadBytes - 1)] ;$p += $payLoadBytes $x = $u-35 $p = $payLoadBytes $k = $m+(($p-$m)%($u-4)) if($p>$x) { # The first K bytes of P are stored on the btree page and the remaining P-K bytes are stored on overflow pages. if($k -le $x) { $payLoad = $payLoad[0..$k-1] $p -= $payLoadBytes + $k } # The first M bytes of P are stored on the btree page and the remaining P-M bytes are stored on overflow pages else { $payLoad = $payLoad[0..$m-1] $p -= $payLoadBytes + $m } $firstOverflowPage = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4 } break } 0x05 #B-Tree Interior Cell { $leftChild = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4 $payLoadBytes = $null $rowId = Parse-SQLiteVarIntV3 -Data $Data -Position ([ref]$p) $payLoad = $null $firstOverflowPage = $null break } 0x0a #B-Tree Leaf Cell { $leftChild = $null $payLoadBytes = Parse-SQLiteVarIntV3 -Data $Data -Position ([ref]$p) $rowId = $null $payLoad = $Data[$p..$($p + $payloadBytes - 1)] ;$p += $payLoadBytes $x = (($u-12)*64/255)-23 $p = $payLoadBytes $k = $m+(($p-$m)%($u-4)) if($p>$x) { # The first K bytes of P are stored on the btree page and the remaining P-K bytes are stored on overflow pages. if($k -le $x) { $payLoad = $payLoad[0..$k-1] $p -= $payLoadBytes + $k } # The first M bytes of P are stored on the btree page and the remaining P-M bytes are stored on overflow pages else { $payLoad = $payLoad[0..$m-1] $p -= $payLoadBytes + $m } $firstOverflowPage = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4 } break } 0x02 #B-Tree Interior Cell { $leftChild = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4 $payLoadBytes = Parse-SQLiteVarIntV3 -Data $Data -Position ([ref]$p) $rowId = $null $payLoad = $Data[$p..$($p + $payloadBytes - 1)] ;$p += $payLoadBytes $x = (($u-12)*64/255)-23 $p = $payLoadBytes $k = $m+(($p-$m)%($u-4)) if($p>$x) { # The first K bytes of P are stored on the btree page and the remaining P-K bytes are stored on overflow pages. if($k -le $x) { $payLoad = $payLoad[0..$k-1] $p -= $payLoadBytes + $k } # The first M bytes of P are stored on the btree page and the remaining P-M bytes are stored on overflow pages else { $payLoad = $payLoad[0..$m-1] $p -= $payLoadBytes + $m } $firstOverflowPage = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4 } break } } if($payLoad) { $columns = Parse-SQLiteBTreeCellPayload -Data $payLoad } $attributes = [ordered]@{ "LeftChildPageNumber" = $leftChild "PayloadBytes" = $payLoadBytes "Payload" = $columns "FirstOverFlowPageNumber" = $firstOverflowPage } return New-Object -TypeName psobject -Property $attributes } } # Parses SQLite database file # Sep 27th 2022 function Parse-SQLiteDatabase { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [byte[]]$Data ) Process { # Parse SQLite db file header $p = 0; $header = Parse-SQLiteHeader -Data $Data -Position ([ref]$p) # Parse pages $pages = New-Object psobject[] $header.Pages $nPages = 0 while($p -lt $Data.Count) { $pages[$nPages] = Parse-SQLiteBTreePage -Data $Data -Position ([ref]$p) -PageSize $header.PageSize $nPages++ # Next page starts from header size + n*PageSize $p = $nPages * $header.PageSize } $attributes = [ordered]@{ "Header" = $header "Pages" = $pages } return New-Object -TypeName psobject -Property $attributes } } # Parses SQLite database file B-Tree page # Sep 27th 2022 function Parse-SQLiteBTreePage { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [byte[]]$Data, [Parameter(Mandatory=$true)] [ref]$Position, [Parameter(Mandatory=$true)] [int]$PageSize ) Process { # Ref. https://www.sqlite.org/fileformat.html $p = $Position.Value # Calculate the page start if($p -lt $PageSize) { $pageStart = 0 } else { $pageStart = $p } $pageType = [int]$Data[$p]; $p += 1 # A value of 2 (0x02) means the page is an interior index b-tree page. # A value of 5 (0x05) means the page is an interior table b-tree page. # A value of 10 (0x0a) means the page is a leaf index b-tree page. # A value of 13 (0x0d) means the page is a leaf table b-tree page. $freeBlockStart = [System.BitConverter]::ToInt16($Data[($p+2-1)..$p],0); $p += 2 $cellsOnPage = [System.BitConverter]::ToInt16($Data[($p+2-1)..$p],0); $p += 2 $cellContentStart = [System.BitConverter]::ToInt16($Data[($p+2-1)..$p],0); $p += 2 if($cellContentStart -eq 0) # A zero value for this integer is interpreted as 65536. { $cellContentStart = 65536 } $fragmentedFreeBytes = [int]$Data[$p]; $p += 1 if($pageType -eq 0x02) { $pageNumber = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4 } $cells = New-Object psobject[] $cellsOnPage for($c = 0 ; $c -lt $cellsOnPage; $c++) { $cellOffset = [System.BitConverter]::ToInt16($Data[($p+2-1)..$p],0); $p += 2 $cellStart = $pageStart + $cellOffset $cells[$c] = Parse-SQLiteBTreeCell -Data $Data -Position $cellStart -PageType $pageType -PageSize $PageSize } $Position.Value = $p switch($pageType) { 0x0d {$strPageType = "Table Leaf" ; break} 0x05 {$strPageType = "Table Interior"; break} 0x0a {$strPageType = "Index Leaf" ; break} 0x02 {$strPageType = "Index Interior"; break} } $attributes = [ordered]@{ "PageType" = $strPageType "PageNumber" = $pageNumber "CellsOnPage" = $cellsOnPage "ContentStart" = $cellContentStart "Cells" = $cells } return New-Object -TypeName psobject -Property $attributes } } # Parses SQLite database file header # Sep 27th 2022 function Parse-SQLiteHeader { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [byte[]]$Data, [Parameter(Mandatory=$true)] [ref]$Position ) Begin { $encodings = @( "UTF-8" "UTF-16le" "UTF-16be" ) } Process { # Ref. https://www.sqlite.org/fileformat.html $p = $Position.Value $headerString = [text.encoding]::UTF8.GetString($Data[$p..($p+16-1)]); $p += 16 $dbPageSize = [System.BitConverter]::ToInt16($Data[($p+2-1)..$p],0); $p += 2 if($dbPageSize -eq 1) # The database page size in bytes. Must be a power of two between 512 and 32768 inclusive, or the value 1 representing a page size of 65536. { $dbPageSize = 65536 } $fileWriteVersion = [int]$Data[$p]; $p += 1 $fileReadVersion = [int]$Data[$p]; $p += 1 $reservedSpaceBytes = [int]$Data[$p]; $p += 1 $maxEmbeddedPayloadFraction = [int]$Data[$p]; $p += 1 $minEmbeddedPayloadFraction = [int]$Data[$p]; $p += 1 $leafPayloadFraction = [int]$Data[$p]; $p += 1 $fileChangeCounter = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4 $dbSizePages = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4 $firstFreelistTrunkPage = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4 $freelistPages = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4 $schemaCookie = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4 $schemaFormatNumber = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4 # A value of 1 means UTF-8. A value of 2 means UTF-16le. A value of 3 means UTF-16be. $defaultPageCacheSize = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4 $largestRootBTreePage = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4 $dbTextEncoding = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4 $userVersion = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4 $incrementalVacuumMode = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0) -ne 0; $p += 4 $applicationId = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4 $reserved = $Data[$p..($p+20-1)]; $p += 20 $versionValidForNumber = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4 $versionNumber = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4 # Check variables if($maxEmbeddedPayloadFraction -ne 64) { Write-Warning "Maximum embedded payload fraction is $maxEmbeddedPayloadFraction, it MUST be 64" } if($minEmbeddedPayloadFraction -ne 32) { Write-Warning "Minimum embedded payload fraction is $minEmbeddedPayloadFraction, it MUST be 32" } if($schemaFormatNumber -ne 4) { Write-Warning "Schema version $schemaFormatNumber not supported, expected version 4" } $Position.Value = $p $attributes = [ordered]@{ "PageSize" = $dbPageSize "Pages" = $dbSizePages "Encoding" = $encodings[$dbTextEncoding-1] "ChangeCounter" = $fileChangeCounter "FirstFreelistPage" = $freelistPages "FreelistPages" = $freelistPages "SchemaFormat" = $schemaFormatNumber "SchemaCookie" = $schemaCookie "SQLiteVersion" = $versionNumber "ReservedSpaceBytes" = $reservedSpaceBytes } return New-Object -TypeName PSObject -Property $attributes } } |