Windows 快速差分打补丁

Windows 快速差分打补丁

手把手带你快速打你想要版本的补丁

介绍

这里会穿插一些理解,参考微软官方文档

win10的更新技术;它能通过计算一个改变了的文件与基础版本的正向差分和它回到基础版本的反向差分来实现。然后将正向和反向差分打包成一个更新包,并分发到运行要更新的软件的端点

3类:

  • 基础版本V0:具有重大变化的主要软件版本(Windows10-1809)
  • (目标)修订版VN(VR):介于主要版本发布之间的次要版本(KB4464330)
  • 无基础补丁存储文件PSF:包含完整二进制文件或文件的补丁存储的文件

更新包的内容符号表示:

端点 V0 + Δ0->N = VN
	VN + ΔN->0 = V0
	V0 + Δ0->R = VR

基于正向和反向差分技术

windows10更新包包含更新RTM基线的3种差分:

  • 正向差分 ΔRTM->N (在f文件夹下 forward)

  • 反向差分 ΔN->RTM (在r文件夹下 reverse)

更新包元数据metadata,内容清单content manifests,正向和反向差分被打包成.cab文件,然后和适用性逻辑Applicability Logic被包装成微软独立更新格式.msu

  • 空差分 Null Differentials(空压缩文件在n文件夹下 )

    在服务期间,可能有新文件被添加到系统中的情况,这些文件将没有RTM基线,因此不能使用正向和反向差分,而使用空差分来处理服务;(即应用于空的缓冲区以创建一个新的文件

    空差分是完整二进制文件的轻微压缩和优化版本;

    (更新包可以有正向或反向差分或者任何给定二进制的空差分)

Windows10更新安装程序内容图:

回到更新RTM/基础版本:使用文件的当前版本(VN),文件的反向差分(VN --> RTM)

从更新RTM/基础版本到目标版本:使用正向差分(VRTM --> R)

(这些差分文件放置在组件仓库%windir%\WinSxS文件夹)

delta

提取出来的补丁内delta文件夹中的文件,乍一看你会以为是个完整的二进制文件:

但其实从其大小就能看出他不是了,因为一般完整的ntoskrnl.exe有1MB多;另一点就是文件格式完全不同,ida就能发现,

Windows Update Binary Delta Compression file 文件格式:

4byte CRC32 50 41 33 30(PA30)

即前面4byte是CRC32校验和,所以不能直接应用补丁而是需要剥离补丁文件的前4个byte,然后才能应用他,相关见 RITSEC CTF 2019: Patch 2sday

使用MSDELTA API对二进制文件应用deltas

通过这种方法来获取想要的二进制文件版本,具体操作见下面

打补丁具体操作

要打补丁的原本版本:ntoskrnl.exe 2021-04

目标版本:2022-07 和 2022-08 的ntoskrnl.exe完整二进制文件

1.首先我们先去原本版本主机上powershell命令行输入:

需要原本版本的反向差分,来将原本版本回滚到基础版本

Windows Update会将二进制文件和差分放在C:\\Windows\WinSxS目录中,通过powershell找到所需要的delta

 Get-ChildItem -Recurse C:\windows\WinSxS\ | ? {$_.Name -eq "ntoskrnl.exe"}

这里的C:\windows\WinSxS\...\r\ntoskrnl.exe就是我们需要的文件之一,\f则是正向差分

然后你应该已经下载好了你所需要的补丁版本比如来自这里:

(通常下载相应版本的累积更新)下载得到文件:

然后我们使用大佬写的powershell脚本PatchExtract.ps1去自动化提取补丁获取我们想要的内容:

<#

 ____     ______   ______  ____     __  __     
/\  _`\  /\  _  \ /\__  _\/\  _`\  /\ \/\ \    
\ \ \L\ \\ \ \L\ \\/_/\ \/\ \ \/\_\\ \ \_\ \   
 \ \ ,__/ \ \  __ \  \ \ \ \ \ \/_/_\ \  _  \  
  \ \ \/   \ \ \/\ \  \ \ \ \ \ \L\ \\ \ \ \ \ 
   \ \_\    \ \_\ \_\  \ \_\ \ \____/ \ \_\ \_\
    \/_/     \/_/\/_/   \/_/  \/___/   \/_/\/_/
                                               
                                               
 ____     __   __   ______  ____     ______   ____     ______   
/\  _`\  /\ \ /\ \ /\__  _\/\  _`\  /\  _  \ /\  _`\  /\__  _\  
\ \ \L\_\\ `\`\/'/'\/_/\ \/\ \ \L\ \\ \ \L\ \\ \ \/\_\\/_/\ \/  
 \ \  _\L `\/ > <     \ \ \ \ \ ,  / \ \  __ \\ \ \/_/_  \ \ \  
  \ \ \L\ \  \/'/\`\   \ \ \ \ \ \\ \ \ \ \/\ \\ \ \L\ \  \ \ \ 
   \ \____/  /\_\\ \_\  \ \_\ \ \_\ \_\\ \_\ \_\\ \____/   \ \_\
    \/___/   \/_/ \/_/   \/_/  \/_/\/ / \/_/\/_/ \/___/     \/_/
                                                                
                                                                
 __  __      _          __     
/\ \/\ \   /' \       /'__`\  
\ \ \ \ \ /\_, \     /\_\L\ \  
 \ \ \ \ \\/_/\ \    \/_/_\_<_ 
  \ \ \_/ \  \ \ \  __ /\ \L\ \
   \ `\___/   \ \_\/\_\\ \____/
    `\/__/     \/_/\/_/ \/___/ 


================
PATCHEXTRACT.PS1
=================
Version 1.3 Microsoft MSU Patch Extraction and Patch Organization Utility by Greg Linares (@Laughing_Mantis)

This Powershell script will extract a Microsoft MSU update file and then organize the output of extracted files and folders.

Organization of the output files is based on the patch's files and will organize them based on their archicture (x86, x64, or wow64)
as well as their content-type, ie: resource and catalog files will be moved to a JUNK subfolder and patch binaries and index files will 
goto a PATCH folder.

This script was developed in order to aid reverse engineers in quickly organizing patches so they can be binary diffed faster and easier. 
This was especially developed with the new bulk Microsoft Kernel patches in mind.

Example output folder structure ouput would be similar to this:

C:\PATCHES\MS15-XXX\PRE
    -x86
        - x86 Binary patched files
    -x64
        - x64 binary patched files
    -WOW64 
        - syswow64 binary patched files
    -JUNK
        - resource, catalog, mum, and other non-binary based patched files
    -PATCH
        - original patch, cabs and xml files from the extraction
    -MSIL 
        - MSIL .NET binary patched files ***New in Version 1.1***
    -NOSSU
        - Exclude the SSU CAB in the extraction process

    Directories will automagically be organized into filename-version to remove garbage filler folder names
        
        
=============
REQUIREMENTS
=============
'expand.exe' to be present in %WINDIR%\SYSTEM32 (it is by default) - It will execute this file @ the current users permissions
A valid Microsoft MSU patch file to extract (PATCH variable)
Directory and File write/creation permissions to the PATH folder specified
        
    
=======    
USAGE
=======

Powershell -ExecutionPolicy Bypass -File PatchExtract.ps1 -Patch C:\Patches\Windows6.1-KB3088195-x64.msu -Path C:\Patches\MS15-XXX\POST\ 


This would extract the patch file C:\Patches\Windows6.1-KB3088195-x64.msu to the folder C:\Patches\MS15-XXX\POST\.
It will then create all the sub organization folders within C:\Patches\MS15-XXX\POST\ folder.

(Note: the optional Powershell parameters '-ExecutionPolicy Bypass' is necessary in some environments to overcome Powershell execution restrictions)

==========
ARGUMENTS
==========
-PATCH <STRING:Filename> [REQUIRED] [NO DEFAULT]
    Specifies the MSU file that will be extracted to the specified PATH folder and then organized into the x86, x64, WOW, JUNK, and BIN folders specified
    Extract command will be "expand -F:* <PATCH> <PATH>"
    Non MSU files have not been tested however if the extraction does not generate a CAB file of the same name (indicator of successful extraction of MSU files)
    the script assumes extraction failed.
    
-PATH <STRING:FolderPath> [REQUIRED] [NO DEFAULT]
    Specified the folder that the PATCH file will be extracted and organized into
    If the specified folders does not exist yet, the user will be prompted if they want to create it.
    Relative paths '.\POST' can be used but it has not extensively been tested.

    ***New in Version 1.1***
    The -PATH variable may be now omitted to expand to current directory
    

-x86 <STRING:Foldername> [OPTIONAL] [DEFAULT='x86']

    Specifies the folder name within $PATH to store x86 patch binaries
    example: -x86 32bit
    
    
-x64 <STRING:Foldername> [OPTIONAL] [DEFAULT='x64']

    Specifies the folder name within $PATH to store x64 patch binaries
    example: -x64 64bit
    
-WOW <STRING:Foldername> [OPTIONAL] [DEFAULT='WOW64']

    Specifies the folder name within $PATH to store wow64 type patch binaries
    example: -WOW sysWOW64

-MSIL <STRING:Foldername> [OPTIONAL] [DEFAULT='MSIL']

    *** New in Version 1.1***
    Specifies the folder name within $PATH to store .NET type patch binaries
    example: -MSIL DOTNET
    
-JUNK <STRING:Foldername> [OPTIONAL] [DEFAULT='JUNK']

    Specifies the folder name within $PATH to store resource, catalog, and other generally useless for diffing patch binaries
    example: -JUNK res
    
    
-BIN <STRING:Foldername> [OPTIONAL] [DEFAULT='PATCH']

    Specifies the folder name within $PATH to store extraction xml and original patch msu and cab files
    example: -BIN bin

- NOSSU <SWITCH> [OPTIONAL] [DEFAULT=$false]
    Excludes the servicing stack CAB from extraction (if present)

================
VERSION HISTORY
================
I originally wrote this as an ugly batch file sometime between 2014 and 2015 as a way to organize folders but it was incomplete and buggy

Oct 15, 2015 - Initial Public Release 1.0
Oct 20, 2016 - Version 1.1 Released
                * Bug fixes handling new naming format for patch .cab files
                * Added the ability to auto-extract to the same directory as current PATCH 
                * filtered output directory name format to aid in bindiffing

Oct 20, 2016 - Version 1.2 Released
                * Bug fixes handling MSIL renaming issues and collisions in renameing patch folders

Nov 7, 2016 - Version 1.25 Released
                * Added hack to handle subsequent CAB files Microsoft Added in Windows 10 Cumulative Patches - will make a better way to handle this in 1.3 

March 15, 2017 - Version 1.3 Released
                * Color Change to sweet vaporwave retro 80s colors
                * Cleaned up some awful code that I must have been on some amazing substances when I wrote
                * Spent several hours making a rad ASCII Logo
                * Most importantly fixed the Sub-cab auto-extraction method that Microsoft introduced late 2016
August 23, 2020 - Version 1.31 - Unofficial update by wumb0
                * Fixed extracting command (Start-Process -> iex). There's a bug in powershell that breaks Start-Process with arg lists that have literal quotes
                * Supressed output of expand.exe commands to cut down on script runtimes
March 24, 2021 - Version 1.32 - Unofficial update by wumb0
                * Update script to handle this month's update to the patch packaging format
                * Simplify recursive CAB extraction into a loop
                * Add option to skip the SSU CAB
                * Silenced a lot of output


==========
LICENSING
==========
This script is provided free as beer.  It probably has some bugs and coding issues, however if you like it or find it useful please give me a shout out on twitter @Laughing_Mantis.  
Feedback is encouraged and I will be likely releasing new scripts and tools and training in the future if it is welcome.


-GLin

#>




Param
(

    [Parameter(ValueFromPipelineByPropertyName = $true)]
    [ValidateNotNullOrEmpty()]
    [string]$PATCH = "",
    
    [Parameter(ValueFromPipelineByPropertyName = $true)]
    [string]$PATH = "",
    
    [Parameter(ValueFromPipelineByPropertyName = $true)]
    [string]$x86 = "x86",
    
    [Parameter(ValueFromPipelineByPropertyName = $true)]
    [string]$x64 = "x64",
    
    [Parameter(ValueFromPipelineByPropertyName = $true)]
    [string]$WOW = "WOW64",

    [Parameter(ValueFromPipelineByPropertyName = $true)]
    [string]$MSIL = "MSIL",
    
    [Parameter(ValueFromPipelineByPropertyName = $true)]
    [string]$JUNK = "JUNK",
    
    [Parameter(ValueFromPipelineByPropertyName = $true)]
    [string]$BIN = "PATCH",

    [Parameter(ValueFromPipelineByPropertyName = $true)]
    [switch]$NOSSU = $false
        
)

Clear-Host
$ASCIIART = @"
 ____     ______   ______  ____     __  __     
/\  _`\  /\  _  \ /\__  _\/\  _`\  /\ \/\ \   
\ \ \L\ \\ \ \L\ \\/_/\ \/\ \ \/\_\\ \ \_\ \   
 \ \ ,__/ \ \  __ \  \ \ \ \ \ \/_/_\ \  _  \  
  \ \ \/   \ \ \/\ \  \ \ \ \ \ \L\ \\ \ \ \ \ 
   \ \_\    \ \_\ \_\  \ \_\ \ \____/ \ \_\ \_\
    \/_/     \/_/\/_/   \/_/  \/___/   \/_/\/_/
                                               
                                               
 ____     __   __   ______  ____     ______   ____     ______   
/\  _`\  /\ \ /\ \ /\__  _\/\  _`\  /\  _  \ /\  _`\  /\__  _\  
\ \ \L\_\\ `\`\/'/'\/_/\ \/\ \ \L\ \\ \ \L\ \\ \ \/\_\\/_/\ \/  
 \ \  _\L `\/ > <     \ \ \ \ \ ,  / \ \  __ \\ \ \/_/_  \ \ \  
  \ \ \L\ \  \/'/\`\   \ \ \ \ \ \\ \ \ \ \/\ \\ \ \L\ \  \ \ \ 
   \ \____/  /\_\\ \_\  \ \_\ \ \_\ \_\\ \_\ \_\\ \____/   \ \_\
    \/___/   \/_/ \/_/   \/_/  \/_/\/ / \/_/\/_/ \/___/     \/_/
                                                                
                                                                
 __  __      _          __     
/\ \/\ \   /' \       /'__`\   
\ \ \ \ \ /\_, \     /\_\L\ \  
 \ \ \ \ \\/_/\ \    \/_/_\_<_ 
  \ \ \_/ \  \ \ \  __ /\ \L\ \
   \ `\___/   \ \_\/\_\\ \____/
    `\/__/     \/_/\/_/ \/___/ 
"@

Write-Host $ASCIIART -ForegroundColor Magenta
Start-Sleep -s 3


if ($PATCH -eq "")
{
    Throw ("Error: No PATCH file specified.  Specify a valid Microsoft MSU Patch with the -PATCH argument")
   
}

if ((Split-Path $PATCH -Parent) -eq "")
{
    # First look in current working directory for the relative filename
    $CurrentDir = $(get-location).Path;
    $PATCH = $CurrentDir + "\" + $PATCH

    # if that doesnt work we look in the current script directory (less likely)
    # but hey we tried
    if (!(Test-Path $PATCH))
    {
        $scriptDir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
        $PATCH = $scriptDir + "\" + $PATCH
    }
}

if (!(Test-Path $PATCH))
{
    Throw ("Error: Specified PATCH file ($PATCH) does not exist.  Specify a valid Microsoft MSU Patch file with the -PATCH argument.")
}

if ($PATH -eq "")
{
    $PATH = Split-Path $PATCH -Parent
    write-Host ("PATH = $PATH") -ForegroundColor White
    Write-Host ("No PATH folder specified.  Will extract to $PATH folder.") -ForegroundColor White
    
}

#Bug Fix (Resolve-Path Error if invalid path was specified before the path was created)




if (!($PATCH.ToUpper().EndsWith(".MSU")))
{
    Do 
    {
        $Attempt = Read-Host ("Warning: Specified PATCH file ($PATCH) is not a MSU file type. Do you still want to attempt extraction? [Y] or [N]")
    }
    Until ('Y', 'y', 'n', 'N' -ccontains $Attempt)
    if ($Attempt.ToUpper() -eq 'N')
    {
        Write-Host ("Exiting...") -ForegroundColor DarkMagenta
        Exit
    }
}

if (!(Test-Path $PATH))
{
    Do 
    {
        $Attempt = Read-Host ("Warning: Specified PATH folder ($PATH) does not exist. Do you want to create it? [Y] or [N]")
    }
    Until ('Y', 'y', 'n', 'N' -ccontains $Attempt)
    if ($Attempt.ToUpper() -eq 'N')
    {
        Write-Host ("Exiting...") -ForegroundColor DarkMagenta
        Exit
    }
    else
    {
        New-Item $PATH -Force -ItemType Directory
        Write-Host "Created $PATH Folder" -ForegroundColor Cyan
    }
}

$PATCH = Resolve-Path $PATCH
$PATH = Resolve-Path $PATH

Write-Host "Patch to Extract: $PATCH" -ForegroundColor Magenta
Write-Host "Extraction Path: $PATH" -ForegroundColor Magenta
Write-Host "x86 File Storage Folder Name: $x86" -ForegroundColor Magenta
Write-Host "x64 File Storage Folder Name: $x64" -ForegroundColor Magenta
Write-Host "WOW64 File Storage Folder Name: $WOW" -ForegroundColor Magenta
Write-Host "MSIL File Storage Folder Name: $MSIL" -ForegroundColor Magenta
Write-Host "Junk File Storage Folder Name: $JUNK" -ForegroundColor Magenta
Write-Host "Orignal Patch File Storage Folder Name: $BIN" -ForegroundColor Magenta



$PATCHx86 = Join-Path -path $PATH -ChildPath $x86
$PATCHx64 = Join-Path -path $PATH -ChildPath $x64
$PATCHWOW = Join-Path -path $PATH -ChildPath $WOW
$PATCHMSIL = Join-Path -path $PATH -ChildPath $MSIL
$PATCHJUNK = Join-Path -path $PATH -ChildPath $JUNK
$PATCHCAB = Join-Path -path $PATH -ChildPath $BIN


if (!(Test-Path $PATCHx86 -pathType Container))
{
    New-Item $PATCHx86 -Force -ItemType Directory
    Write-Host "Making $PATCHx86 Folder" -ForegroundColor Cyan
}

if (!(Test-Path $PATCHx64 -pathType Container))
{
    New-Item $PATCHx64 -Force -ItemType Directory
    Write-Host "Making $PATCHx64 Folder" -ForegroundColor Cyan
}

if (!(Test-Path $PATCHWOW -pathType Container))
{
    New-Item $PATCHWOW -Force -ItemType Directory
    Write-Host "Making $PATCHWOW Folder" -ForegroundColor Cyan
}

if (!(Test-Path $PATCHMSIL -pathType Container))
{
    New-Item $PATCHMSIL -Force -ItemType Directory
    Write-Host "Making $PATCHMSIL Folder" -ForegroundColor Cyan
}

if (!(Test-Path $PATCHJUNK -pathType Container))
{
    New-Item $PATCHJUNK -Force -ItemType Directory
    Write-Host "Making $PATCHJUNK Folder" -ForegroundColor Cyan
}

if (!(Test-Path $PATCHCAB -pathType Container))
{
    New-Item $PATCHCAB -Force -ItemType Directory
    Write-Host "Making $PATCHCAB Folder" -ForegroundColor Cyan
}


$SYSPATH = Join-Path -path (get-item env:\windir).Value -ChildPath "system32"

$EXPAND = Join-Path -path $SYSPATH -ChildPath "expand.exe"


if (!(Test-Path $EXPAND))
{
    Throw ("Error: Cannot find 'Expand.exe' in the $SYSPATH folder.")
} 

$ARG = '-F:* ' + '"' + $PATCH + '" ' + '"' + $PATH + '"'

Write-Host "Executing the following command: $EXPAND $ARG" -ForegroundColor Cyan

# https://github.com/PowerShell/PowerShell/issues/5576
iex("$EXPAND $ARG | Out-Null") | Out-Null

$CABS = Get-Childitem -Path $PATH -Filter *.cab

while ($null -ne $CABS) {
    foreach ($CAB in $CABS)
    {
        Write-Host "CAB File: $CAB" -ForegroundColor White
        if (!($CAB.Name -eq "WSUSSCAN.cab" -or ($NOSSU -and $CAB.Name -like "SSU-*")))
        {
            $CAB = Join-Path -path $PATH -ChildPath $CAB
            Write-Host "Main-Cab: $CAB" -ForegroundColor Magenta
            if (Test-Path $CAB)
            {
                # add unique characters to the filename to prevent name collisions
                $hash = Get-FileHash -Algorithm MD5 $CAB
                $NEWCAB = $CAB.SubString(0, $CAB.LastIndexOf(".")) + "-" + $HASH.Hash.SubString(0, 5) + ".cab"
                Move-Item $CAB $NEWCAB -Force -ErrorAction SilentlyContinue
                $CAB = $NEWCAB
                $ARG = '-F:* ' + '"' + $CAB + '" ' + '"' + $PATH + '"'
                Write-Host "Executing the following command: $EXPAND $ARG" -ForegroundColor Cyan
                iex("$EXPAND $ARG | Out-Null") | Out-Null
                Write-Host "Moving $CAB to $PATCHCAB" -ForegroundColor Magenta
                Move-Item $CAB $PATCHCAB -Force -ErrorAction SilentlyContinue
            }
            else
            {
                Throw "Error: Patch .CAB File [$CAB] could not be located.  Patch Extraction failed - please send notification of this error to @Laughing_Mantis."
            }
        }
        else
        {
            Write-Host "Moving $CAB to $PATCHCAB" -ForegroundColor Magenta
            Move-Item $CAB $PATCHCAB -Force -ErrorAction SilentlyContinue
        }
    }
    $CABS = Get-Childitem -Path $NEW -Filter *.cab
}

$PATCHFolders = Get-ChildItem -Path $PATH -Force -ErrorAction SilentlyContinue | where {$_.Attributes -eq 'Directory'}

Write-Host "Sorting patch files into the correct folders"
foreach ($folder in $PATCHFolders)
{
    if ($folder.Name.Contains(".resources_"))
    {
        Move-Item $folder.FullName $PATCHJUNK -Force
        #Write-Host "Moving $folder to $PATCHJUNK" -ForegroundColor Cyan
        Continue
    }
    else
    {
        if ($folder.Name.StartsWith("x86_"))
        {
            Move-Item $folder.FullName $PATCHx86 -Force
            #Write-Host "Moving $folder to $PATCHx86" -ForegroundColor Cyan
            Continue
        }
        
        if ($folder.Name.StartsWith("amd64_"))
        {
            Move-Item $folder.FullName $PATCHx64 -Force
            #Write-Host "Moving $folder to $PATCHx64" -ForegroundColor Cyan
            Continue
        }
        
        if ($folder.Name.StartsWith("wow64_"))
        {
            Move-Item $folder.FullName $PATCHWOW -Force
            #Write-Host "Moving $folder to $PATCHWOW" -ForegroundColor Cyan
            Continue
        }

        if ($folder.Name.StartsWith("msil_"))
        {
            Move-Item $folder.FullName $PATCHMSIL -Force
            #Write-Host "Moving $folder to $PATCHMSIL" -ForegroundColor Cyan
            Continue
        }
    }
}

<# PRETTY BINDIFF OUTPUT - changes folder names from x86-microsoft-windows-filename-hash-version-garbage to filename-version #>

$PATCHFolders = Get-ChildItem -Path $PATCHx86 -Force -ErrorAction SilentlyContinue | where {$_.Attributes -eq 'Directory'}

foreach ($folder in $PATCHFolders)
{
    if ($folder -like "x86_microsoft-windows-*")
    {
        $newfolder = $folder.Name.Replace("x86_microsoft-windows-", "")
        $newname = $newfolder.Split("_")[0]
        $version = $newfolder.Split("_")[2]
        $newname = $newname + "_" + $version
        #Write-Host ("Renaming $folder to $newname") -ForegroundColor Magenta
        Rename-Item -path $folder.FullName -newName ($newname)
    }
    elseif ($folder -like "x86_*")
    {
        $newfolder = $folder.Name.Replace("x86_", "")
        $newname = $newfolder.Split("_")[0]
        $version = $newfolder.Split("_")[2]
        $newname = $newname + "_" + $version
        #Write-Host ("Renaming $folder to $newname") -ForegroundColor Cyan
        Rename-Item -path $folder.FullName -newName ($newname)
    }
}

$PATCHFolders = Get-ChildItem -Path $PATCHx64 -Force -ErrorAction SilentlyContinue | where {$_.Attributes -eq 'Directory'}

foreach ($folder in $PATCHFolders)
{
    if ($folder -like "amd64_microsoft-windows-*")
    {
        $newfolder = $folder.Name.Replace("amd64_microsoft-windows-", "")
        $newname = $newfolder.Split("_")[0]
        $version = $newfolder.Split("_")[2]
        $newname = $newname + "_" + $version
        #Write-Host ("Renaming $folder to $newname") -ForegroundColor Magenta
        Rename-Item -path $folder.FullName -newName ($newname)
    }
    elseif ($folder -like "amd64_*")
    {
        $newfolder = $folder.Name.Replace("amd64_", "")
        $newname = $newfolder.Split("_")[0]
        $version = $newfolder.Split("_")[2]
        $newname = $newname + "_" + $version
        #Write-Host ("Renaming $folder to $newname") -ForegroundColor Cyan
        Rename-Item -path $folder.FullName -newName ($newname)
    }
}

$PATCHFolders = Get-ChildItem -Path $PATCHWOW -Force -ErrorAction SilentlyContinue | where {$_.Attributes -eq 'Directory'}

foreach ($folder in $PATCHFolders)
{
    if ($folder -like "wow64_microsoft-windows-*")
    {
        $newfolder = $folder.Name.Replace("wow64_microsoft-windows-", "")
        $newname = $newfolder.Split("_")[0]
        $version = $newfolder.Split("_")[2]
        $newname = $newname + "_" + $version
        #Write-Host ("Renaming $folder to $newname") -ForegroundColor Magenta
        Rename-Item -path $folder.FullName -newName ($newname)
    }
    elseif ($folder -like "wow64_*")
    {
        $newfolder = $folder.Name.Replace("wow64_", "")
        $newname = $newfolder.Split("_")[0]
        $version = $newfolder.Split("_")[2]
        $newname = $newname + "_" + $version
        #Write-Host ("Renaming $folder to $newname") -ForegroundColor Cyan
        Rename-Item -path $folder.FullName -newName ($newname)
    }
}

$PATCHFolders = Get-ChildItem -Path $PATCHMSIL -Force -ErrorAction SilentlyContinue | where {$_.Attributes -eq 'Directory'}

foreach ($folder in $PATCHFolders)
{
    if ($folder -like "msil_*")
    {
        $newfolder = $folder.Name.Replace("msil_", "")
        $newname = $newfolder.Split("_")[0]
        $version = $newfolder.Split("_")[2]
        $newname = $newname + "_" + $version
        #Write-Host ("Renaming $folder to $newname") -ForegroundColor Cyan
        Rename-Item -path $folder.FullName -newName ($newname)
    }

}

$Junkfiles = Get-ChildItem -Path $PATH -Force -ErrorAction SilentlyContinue


foreach ($JunkFile in $Junkfiles)
{
    
    try
    {
        if (($JunkFile.Name.EndsWith(".manifest")) -or ($JunkFile.Name.EndsWith(".cat")) -or ($JunkFile.Name.EndsWith(".mum")))
        {
            Move-Item $JunkFile.FullName $PATCHJUNK -Force -ErrorAction SilentlyContinue
            #Write-Host "Moving $JunkFile to $PATCHJUNK" -ForegroundColor Magenta
            Continue
        }
        
        if (($JunkFile.Name.EndsWith(".cab")) -or ($JunkFile.Name.EndsWith(".xml")) -or ($JunkFile.Name.EndsWith(".msu")) -or ($JunkFile.Name.EndsWith("pkgProperties.txt")) -or ($JunkFile.Name.EndsWith("ini")))
        {
            Move-Item $JunkFile.FullName $PATCHCAB -Force -ErrorAction SilentlyContinue
            #Write-Host "Moving $JunkFile to $PATCHCAB" -ForegroundColor Cyan
            Continue
        }
        if ($JunkFile.Name -eq "patch")
        {
            Move-Item $JunkFile.FullName $PATCHCAB -Force -ErrorAction SilentlyContinue
            #Write-Host "Moving $JunkFile to $PATCHCAB" -ForegroundColor Magenta
            Continue
        }
    }
    catch
    {
        Write-Host "Error Processing ($JunkFile.Fullname)" -ForegroundColor DarkMagenta
    }
}

然后在下载的patch文件目录下直接输入:(记得留好空间)

.\PatchExtract.ps1 .\windows.......msu

然后你就会得到如下分好类的文件夹:

直接搜索ntoskrnl.exe

你就会发现在\x64\os-kernel_10.0.19041.1865目录下有着两个文件夹:

我们需要的文件即.\f\ntoskrnl.exe

第二个脚本delta.py,补丁delta应用脚本:

它导入了msdelta.dll并使用ApplyDeltaB来应用补丁

from ctypes import (windll, wintypes, c_uint64, cast, POINTER, Union, c_ubyte,
                    LittleEndianStructure, byref, c_size_t)
import zlib


# types and flags
DELTA_FLAG_TYPE             = c_uint64
DELTA_FLAG_NONE             = 0x00000000
DELTA_APPLY_FLAG_ALLOW_PA19 = 0x00000001


# structures
class DELTA_INPUT(LittleEndianStructure):
    class U1(Union):
        _fields_ = [('lpcStart', wintypes.LPVOID),
                    ('lpStart', wintypes.LPVOID)]
    _anonymous_ = ('u1',)
    _fields_ = [('u1', U1),
                ('uSize', c_size_t),
                ('Editable', wintypes.BOOL)]


class DELTA_OUTPUT(LittleEndianStructure):
    _fields_ = [('lpStart', wintypes.LPVOID),
                ('uSize', c_size_t)]


# functions
ApplyDeltaB = windll.msdelta.ApplyDeltaB
ApplyDeltaB.argtypes = [DELTA_FLAG_TYPE, DELTA_INPUT, DELTA_INPUT,
                        POINTER(DELTA_OUTPUT)]
ApplyDeltaB.rettype = wintypes.BOOL
DeltaFree = windll.msdelta.DeltaFree
DeltaFree.argtypes = [wintypes.LPVOID]
DeltaFree.rettype = wintypes.BOOL
gle = windll.kernel32.GetLastError


def apply_patchfile_to_buffer(buf, buflen, patchpath, legacy):
    with open(patchpath, 'rb') as patch:
        patch_contents = patch.read()

    # most (all?) patches (Windows Update MSU) come with a CRC32 prepended to the file
    # we don't really care if it is valid or not, we just need to remove it if it is there
    # we only need to calculate if the file starts with PA30 or PA19 and then has PA30 or PA19 after it
    magic = [b"PA30"]
    if legacy:
        magic.append(b"PA19")
    if patch_contents[:4] in magic and patch_contents[4:][:4] in magic:
        # we have to validate and strip the crc instead of just stripping it
        crc = int.from_bytes(patch_contents[:4], 'little')
        if zlib.crc32(patch_contents[4:]) == crc:
            # crc is valid, strip it, else don't
            patch_contents = patch_contents[4:]
    elif patch_contents[4:][:4] in magic:
        # validate the header strip the CRC, we don't care about it
        patch_contents = patch_contents[4:]
    # check if there is just no CRC at all
    elif patch_contents[:4] not in magic:
        # this just isn't valid
        raise Exception("Patch file is invalid")
 
    applyflags = DELTA_APPLY_FLAG_ALLOW_PA19 if legacy else DELTA_FLAG_NONE

    dd = DELTA_INPUT()
    ds = DELTA_INPUT()
    dout = DELTA_OUTPUT()

    ds.lpcStart = buf
    ds.uSize = buflen
    ds.Editable = False

    dd.lpcStart = cast(patch_contents, wintypes.LPVOID)
    dd.uSize = len(patch_contents)
    dd.Editable = False

    status = ApplyDeltaB(applyflags, ds, dd, byref(dout))
    if status == 0:
        raise Exception("Patch {} failed with error {}".format(patchpath, gle()))

    return (dout.lpStart, dout.uSize)


if __name__ == '__main__':
    import sys
    import base64
    import hashlib
    import argparse

    ap = argparse.ArgumentParser()
    mode = ap.add_mutually_exclusive_group(required=True)
    output = ap.add_mutually_exclusive_group(required=True)
    mode.add_argument("-i", "--input-file",
                      help="File to patch (forward or reverse)")
    mode.add_argument("-n", "--null", action="store_true", default=False,
                      help="Create the output file from a null diff "
                           "(null diff must be the first one specified)")
    output.add_argument("-o", "--output-file",
                        help="Destination to write patched file to")
    output.add_argument("-d", "--dry-run", action="store_true",
                        help="Don't write patch, just see if it would patch"
                             "correctly and get the resulting hash")
    ap.add_argument("-l", "--legacy", action='store_true', default=False,
                    help="Let the API use the PA19 legacy API (if required)")
    ap.add_argument("patches", nargs='+', help="Patches to apply")
    args = ap.parse_args()

    if not args.dry_run and not args.output_file:
        print("Either specify -d or -o", file=sys.stderr)
        ap.print_help()
        sys.exit(1)

    if args.null:
        inbuf = b""
    else:
        with open(args.input_file, 'rb') as r:
            inbuf = r.read()

    buf = cast(inbuf, wintypes.LPVOID)
    n = len(inbuf)
    to_free = []
    try:
        for patch in args.patches:
            buf, n = apply_patchfile_to_buffer(buf, n, patch, args.legacy)
            to_free.append(buf)

        outbuf = bytes((c_ubyte*n).from_address(buf))
        if not args.dry_run:
            with open(args.output_file, 'wb') as w:
                w.write(outbuf)
    finally:
        for buf in to_free:
            DeltaFree(buf)

    finalhash = hashlib.sha256(outbuf)
    print("Applied {} patch{} successfully"
          .format(len(args.patches), "es" if len(args.patches) > 1 else ""))
    print("Final hash: {}"
          .format(base64.b64encode(finalhash.digest()).decode()))

查看其用法:

python delta_patch.py -h

文件都准备好了后命令行输入得到8月版本的文件:

即先反向后正向差分,先应用反向delta,然后应用正向delta,最终得到目标版本的完整二进制文件(可能就是回滚到基线然后正向delta最终更新)

python delta_patch.py -i .\ntoskrnl.exe -o ntoskrnl.2022-08.exe .\r\ntoskrnl.exe E:\...\x64\ps-kernel...\f\ntoskrnl.exe

如下:

另一个7月版本同理,更换下输出文件的名字以及版本相应patch文件\f\ntoskrnl.exe的路径即可

最终得到ntoskrnl.exe的7月和8月的版本

RITSEC CTF 2019: Patch 2sday [MISC]

一个要被打补丁的驱动文件win32k.patched.sys,一个delta补丁文件patch-tuesday

原本文件属性:

同样的道理

 python delta_patch.py -i .\win32k.patched.sys -o .\patched.sys  .\patch-tuesday

posted @ 2023-03-23 14:18  hyq2  阅读(473)  评论(0编辑  收藏  举报