Skip to content

Instantly share code, notes, and snippets.

@jn0
Last active August 13, 2022 09:04
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jn0/6bcbc3f00e39efe76e0a52de423f30e7 to your computer and use it in GitHub Desktop.
Save jn0/6bcbc3f00e39efe76e0a52de423f30e7 to your computer and use it in GitHub Desktop.
Two-pass multi-run-safe micro (85 LOC) patcher in python
#!/usr/bin/env python3
'''
Two-pass re-run safe micro (85 LOC) patcher in python.
Sample patch config (yes, it's in YAML):
---->8----
version: 0
title: tribute to sublimetext_4126_crack_linux.py by dmknght @github
file: /tmp/sublime_text
# size: 8862888 # optional check for file size
patch:
- offset: 0x0002d78a
value: 34 31 32 36 # "4126"
- { offset: 0x00385797, value: 0f 84, replace: 0f 85 }
- { offset: 0x0038583d, value: 74 1f, replace: 75 1f }
# vim:set ft=yaml ai et ts=2 sts=2 sw=2:EOF #
---->8----
'''
import sys, yaml, os
VERSION = 0
def hexx(z): return ' '.join(f'{b:02x}' for b in z)
def binn(p):
data = ''.join(p.strip().split()).lower()
assert data and len(data) % 2 == 0, f'bad data length {len(data)}'
return bytes(int(data[i:i+2], 16) for i in range(0, len(data), 2))
def read_at(f, pos, size):
assert f.seek(pos, os.SEEK_SET) == pos
return f.read(size)
def write_at(f, pos, data):
if data:
print(f"\t{pos:08x}", len(data), '/', hexx(data))
assert f.seek(pos, os.SEEK_SET) == pos
f.write(data)
def verify(f, check, n):
offset, pattern, repl = check['offset'], binn(check['value']), check.get('replace')
if repl:
repl = binn(repl)
value = read_at(f, offset, len(repl))
if value == repl:
print(f"{n:6d}: {offset:08x}", len(value), '=', hexx(value), '!=', hexx(pattern), 'patched')
return None
value = read_at(f, offset, len(pattern))
assert pattern == value, f"at {offset:08x}: [{hexx(value)}] != [{hexx(pattern)}]"
print(f"{n:6d}: {offset:08x}", len(pattern), '=', hexx(pattern), 'ok,', 'patch' if repl else 'keep')
return repl
def check(cf):
print('check>', cf['file'], f'({len(cf["patch"])})')
with open(cf['file'], 'rb') as f:
size = cf.get('size')
assert not size or size == f.seek(0, os.SEEK_END), f'File size not matched {size}'
return [p for i, p in enumerate(cf['patch']) if verify(f, p, i + 1)]
def apply(cf, patches):
if patches:
print('patch>', cf['file'], f'({len(patches)} patch{"es" if len(patches) > 1 else ""} to apply)')
with open(cf['file'], 'rb+') as f:
for i, patch in enumerate(patches):
write_at(f, patch['offset'], verify(f, patch, i + 1))
print('*', cf['file'], 'patched using', cf['__self'])
else:
print('*', cf['file'], 'has already been patched using', cf['__self'], 'or alike')
def patch(cf):
if cf.get('title'): print('#', cf.get('title'))
apply(cf, check(cf))
try:
for arg in sys.argv[1:]:
print('@', arg)
with open(arg) as f:
cf = yaml.load(f, yaml.SafeLoader)
cf['__self'] = arg
assert cf.get('version') == VERSION, f'Patch version is not {VERSION!r} in {cf["__self"]!r}'
patch(cf)
except Exception as e:
print('!', e.__class__.__name__ + ':', e, file=sys.stderr)
sys.exit(1)
# vim:set ft=python ai et ts=4 sts=4 sw=4 cc=80:EOF #
version: 0
title: tribute to sublimetext_4126_crack_linux.py by dmknght @github
file: /tmp/sublime_text
size: 8862888 # optional
patch:
- offset: 0x0002d78a
value: 34 31 32 36 # "4126", just to check, no "replace:" part
- { offset: 0x00385797, value: 0f 84, replace: 0f 85 } # one may have fun writing
- { offset: 0x0038583d, value: 74 1f, replace: 75 1f } # patches in "table form"
# vim:set ft=yaml ai et ts=2 sts=2 sw=2:EOF #
$ cp /snap/sublime-text/112/opt/sublime_text/sublime_text /tmp/
$ ./patch.py patch.pth
@ patch.pth
# tribute to sublimetext_4126_crack_linux.py by dmknght @github
check> /tmp/sublime_text (3)
1: 0002d78a 4 = 34 31 32 36 ok, keep
2: 00385797 2 = 0f 84 ok, patch
3: 0038583d 2 = 74 1f ok, patch
patch> /tmp/sublime_text (2 patches to apply)
1: 00385797 2 = 0f 84 ok, patch
00385797 2 / 0f 85
2: 0038583d 2 = 74 1f ok, patch
0038583d 2 / 75 1f
* /tmp/sublime_text patched using patch.pth
$ ./patch.py patch.pth
@ patch.pth
# tribute to sublimetext_4126_crack_linux.py by dmknght @github
check> /tmp/sublime_text (3)
1: 0002d78a 4 = 34 31 32 36 ok, keep
2: 00385797 2 = 0f 85 != 0f 84 patched
3: 0038583d 2 = 75 1f != 74 1f patched
* /tmp/sublime_text has already been patched using patch.pth or alike
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment