Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
a script for applying MS patch deltas
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()
# some patches (Windows Update MSU) come with a CRC32 prepended to the file
# if the file doesn't start with the signature (PA) then check it
if patch_contents[:2] != b"PA":
paoff = patch_contents.find(b"PA")
if paoff != 4:
raise Exception("Patch is invalid")
crc = int.from_bytes(patch_contents[:4], 'little')
patch_contents = patch_contents[4:]
if zlib.crc32(patch_contents) != crc:
raise Exception("CRC32 check failed. Patch corrupted or 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()))
@ricnar456
Copy link

ricnar456 commented Jan 19, 2021

I have an error using in the last security patches (failed with error 13)

I remove the first 4 bytes CRC in both f and r files (I tryed removing or not removing the result is the same)

Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00000000 50 41 33 30 41 3A D0 79 DC DD D4 01 B0 5E 10 D0 PA30A:ÐyÜÝÔ.°^.Ð
00000010 C7 C4 0C 44 80 49 C1 01 40 21 00 1D BA 7B 55 41 ÇÄ.D€IÁ.@!..º{UA
00000020 A2 2A B7 C9 01 4E D0 F2 76 A1 3B 4C 7E 04 00 00 ¢*·É.NÐòv¡;L~...
00000030 00 0E 00 00 00 00 00 00 00 30 B9 FE C2 A9 02 50 .........0¹þ©.P
00000040 3C BB BB BB <»»»

C:\Users\ricnar\Desktop\TEST>python delta_patch.py -i ntfs.sys -o ntfsnew.sys .\r\ntfs.sys .\f\ntfs.sys
Traceback (most recent call last):
File "delta_patch.py", line 115, in
buf, n = apply_patchfile_to_buffer(buf, n, patch, args.legacy)
File "delta_patch.py", line 70, in apply_patchfile_to_buffer
raise Exception("Patch {} failed with error {}".format(patchpath, gle()))
Exception: Patch .\r\ntfs.sys failed with error 13

C:\Users\ricnar\Desktop\TEST>

@ricnar456
Copy link

ricnar456 commented Jan 19, 2021

I get the problem, I need to use the file with its r corresponding and changing only the f version to patch.

Thanks good tool

ricnar

@redl17
Copy link

redl17 commented Sep 16, 2021

Well color me dense but I have the same issue. Downloaded and extracted the patch, copied the r and f contents to my working folder
C:\scripts\PatchScripts>python3 delta_patch.py -i mshtml.dll -o mshtml4444.dll ./r/mshtml.dll ./f/mshtml.dll
Traceback (most recent call last):
File "delta_patch.py", line 115, in
buf, n = apply_patchfile_to_buffer(buf, n, patch, args.legacy)
File "delta_patch.py", line 70, in apply_patchfile_to_buffer
raise Exception("Patch {} failed with error {}".format(patchpath, gle()))
Exception: Patch ./r/mshtml.dll failed with error 13

@wumb0
Copy link
Author

wumb0 commented Sep 17, 2021

@redl17
Copy link

redl17 commented Sep 20, 2021

@wumb0
Copy link
Author

wumb0 commented Sep 20, 2021

@redl17
Copy link

redl17 commented Sep 20, 2021

@wumb0
Copy link
Author

wumb0 commented Sep 20, 2021

Huh interesting, I'll try it with mshtml and see if I can reproduce.

@wumb0
Copy link
Author

wumb0 commented Sep 20, 2021

Seems to work with the ones on my machine:

python Q:\Patches\tools\delta_patch.py -i C:\windows\winsxs\amd64_microsoft-windows-i..tmlrendering-legacy_31bf3856ad364e35_11.0.19041.1165_none_cabae23990f3fe32\mshtml.dll -o C:\Users\wumb0\mshtml.dll  C:\windows\winsxs\amd64_microsoft-windows-i..tmlrendering-legacy_31bf3856ad364e35_11.0.19041.1165_none_cabae23990f3fe32\r\mshtml.dll C:\windows\WinSxS\amd64_microsoft-windows-i..tmlrendering-legacy_31bf3856ad364e35_11.0.19041.1237_none_caace3df90ff0160\f\mshtml.dll
Applied 2 patches successfully
Final hash: PufXLDs9MB58xOb2Hx76eArqSsvJW6uiMlkMdefH0No=

The f file I'm using hash a sha256 hash of 7D6AC08B1EA46589DA5BD9C1025B72E8F3228C063D38238F156A9B3F15286747
You should be able to pull that out of windows10.0-kb5005565-x64_5b36501e18065cb2ef54d4bb02b0e2b27cd683d0.msu. I haven't checked it yet, but it should be in there.

@redl17
Copy link

redl17 commented Sep 21, 2021

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment