win: automatic patch generator

This commit is contained in:
Vladislav Yarmak 2019-02-11 22:51:25 +02:00
parent 49926f51e5
commit 6b2e50f245
3 changed files with 328 additions and 0 deletions

107
win/tools/autopatch/.gitignore vendored Normal file
View File

@ -0,0 +1,107 @@
# Do not stage vim swapfiles to commit
*.swp
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/

View File

@ -0,0 +1,43 @@
autopatch
=========
This tool is intended for internal usage.
This script accepts path to Nvidia Drivers installer, makes .1337 patch and saves it in appropriate location in your working copy of this repo. All you have to do then is to test patch, stage it for commit and push it. Optionally you can just save patch to stdout.
## Requirements
* Python 3.2+
* 7zip CLI utility
## Synopsys
```
$ ./autopatch.py --help
usage: autopatch.py [-h] [-7 SEVENZIP] [-T TARGET] [-N TARGET_NAME]
[-S SEARCH] [-R REPLACEMENT] [-o]
installer_file
Generates .1337 patch for Nvidia drivers for Windows
positional arguments:
installer_file location of installer executable
optional arguments:
-h, --help show this help message and exit
-7 SEVENZIP, --7zip SEVENZIP
location of 7-zip `7z` executable (default: 7z)
-T TARGET, --target TARGET
target location in archive (default:
Display.Driver/nvcuvid64.dl_)
-N TARGET_NAME, --target-name TARGET_NAME
name of installed target file. Used for patch header
(default: nvcuvid.dll)
-S SEARCH, --search SEARCH
representation of search pattern binary string
(default: FF908000000084C07408)
-R REPLACEMENT, --replacement REPLACEMENT
representation of replacement binary string (default:
FF908000000084C09090)
-o, --stdout output into stdout (default: False)
```

178
win/tools/autopatch/autopatch.py Executable file
View File

@ -0,0 +1,178 @@
#!/usr/bin/env python3
import sys
import subprocess
import tempfile
import os.path
from binascii import unhexlify
import xml.etree.ElementTree as ET
PATCH_EXT = ".1337"
CRLF = b"\x0d\x0a"
HEADER_FORMAT = b">%s"
LINE_FORMAT = CRLF + b"%016X:%02X->%02X"
OFFSET_ADJUSTMENT = 0xC00 # shift specific to x64dbg .1337 format
def parse_args():
import argparse
parser = argparse.ArgumentParser(
description="Generates .1337 patch for Nvidia drivers for Windows",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("installer_file",
help="location of installer executable")
parser.add_argument("-7", "--7zip",
default="7z",
dest="sevenzip",
help="location of 7-zip `7z` executable")
parser.add_argument("-T", "--target",
default="Display.Driver/nvcuvid64.dl_",
help="target location in archive")
parser.add_argument("-N", "--target-name",
default="nvcuvid.dll",
help="name of installed target file. Used for patch "
"header")
parser.add_argument("-S", "--search",
default="FF908000000084C07408",
help="representation of search pattern binary string")
parser.add_argument("-R", "--replacement",
default="FF908000000084C09090",
help="representation of replacement binary string")
parser.add_argument("-o", "--stdout",
action="store_true",
help="output into stdout")
args = parser.parse_args()
return args
class ExtractException(Exception):
pass
class PatternNotFoundException(Exception):
pass
class ExtractedTarget:
name = None
def __init__(self, archive, dst_dir, arch_tgt, *, sevenzip="7z"):
self._archive = archive
self._dst_dir = dst_dir
self._sevenzip = sevenzip
self._arch_tgt = arch_tgt
def __enter__(self):
ret = subprocess.call([self._sevenzip,
"e",
"-o" + self._dst_dir,
self._archive,
self._arch_tgt],
stdout=sys.stderr)
if ret != 0:
raise ExtractException("Subprocess returned non-zero exit code.")
name = os.path.join(self._dst_dir, os.path.basename(self._arch_tgt))
self.name = name
return name
def __exit__(self, exc_type, exc_value, traceback):
if self.name is not None:
os.remove(self.name)
def expand(filename, *, sevenzip="7z"):
proc = subprocess.Popen([sevenzip,
"x",
"-so",
filename], stdout=subprocess.PIPE)
result = proc.communicate()[0]
if proc.returncode != 0:
raise ExtractException("Subprocess returned non-zero exit code.")
return result
def extract_single_file(archive, filename, *, sevenzip="7z"):
proc = subprocess.Popen([sevenzip,
"e",
"-so",
archive,
filename], stdout=subprocess.PIPE)
result = proc.communicate()[0]
if proc.returncode != 0:
raise ExtractException("Subprocess returned non-zero exit code.")
return result
def make_patch(archive, *,
arch_tgt,
search,
replacement,
sevenzip="7z"):
with tempfile.TemporaryDirectory() as tmpdir:
with ExtractedTarget(archive,
tmpdir,
arch_tgt,
sevenzip=sevenzip) as tgt:
f = expand(tgt, sevenzip=sevenzip)
offset = f.find(search)
del f
if offset == -1:
raise PatternNotFoundException("Pattern not found.")
print("Pattern found @ %016X" % (offset,), file=sys.stderr)
res = []
for (i, (left, right)) in enumerate(zip(search, replacement)):
if left != right:
res.append((offset + i, left, right))
return res
def identify_driver(archive, *, sevenzip="7z"):
manifest = extract_single_file(archive, "setup.cfg", sevenzip=sevenzip)
root = ET.fromstring(manifest)
version = root.attrib['version']
product_type = root.find('./properties/string[@name="ProductType"]')\
.attrib['value']
return version, product_type
def format_patch(diff, filename):
res = HEADER_FORMAT % filename.encode('utf-8')
for offset, left, right in diff:
res += LINE_FORMAT % (offset + OFFSET_ADJUSTMENT, left, right)
return res
def main():
args = parse_args()
search = unhexlify(args.search)
replacement = unhexlify(args.replacement)
assert len(search) == len(replacement), "len() of search and replacement"\
" is not equal"
patch = make_patch(args.installer_file,
arch_tgt=args.target,
search=search,
replacement=replacement,
sevenzip=args.sevenzip)
patch_content = format_patch(patch, args.target_name)
if args.stdout:
with open(sys.stdout.fileno(), mode='wb', closefd=False) as out:
out.write(patch_content)
else:
version, product_type = identify_driver(args.installer_file,
sevenzip=args.sevenzip)
driver_name = "quadro_" + version if product_type == "100" else version
out_dir = os.path.join(
os.path.dirname(
os.path.abspath(__file__)), '..', '..', 'win10_x64', driver_name)
os.mkdir(out_dir, 0o755)
out_filename = os.path.join(out_dir,
os.path.splitext(args.target_name)[0] + PATCH_EXT)
with open(out_filename, 'wb') as out:
out.write(patch_content)
if __name__ == '__main__':
main()