'''
TESTED for the following builds:
Windows: 2024-2030
Linux: 2024-2030
Can potentially work for builds in between or newer!
Original script: DE YI <https://github.com/deyixtan>
'''
import binascii
import re
import argparse
import os
class Patcher:
'''
INITIAL_LICENSE_CHECK_AOB_ONE
Build: 2024, 2025, 2028
IDA Sig: 80 78 ?? 00 74 3A <- windows
80 78 ?? 00 74 1B <- linux
^^ ^^
48 01
Build 2024: @0x2a80f <- windows
@0x39bf29 <- linux
Build 2025: @0x2a803 <- windows
@0x39bf43 <- linux
Build 2028: @0x2a8ff <- windows
@0x397fe9 <- linux
'''
INITIAL_LICENSE_CHECK_AOB_ONE = {"windows": b"8078..00743a",
"linux": b"8078..00741b",
"patch_one": b"48",
"patch_one_offset": 2,
"patch_two": b"01",
"patch_two_offset": 6}
'''
INITIAL_LICENSE_CHECK_AOB_TWO
Build: 2027, 2030
IDA Sig: 80 38 00 74 3A <- windows
80 38 00 74 1B <- linux
^^ ^^
08 01
Build 2027: @0x2a815 <- windows
@0x39bfe5 <- linux
Build 2030: @0x2a8e9 <- windows
@0x39b005 <- linux
'''
INITIAL_LICENSE_CHECK_AOB_TWO = {"windows": b"803800743a",
"linux": b"803800741b",
"patch": b"0801",
"patch_offset": 2}
'''
PERSISTENT_LICENSE_CHECK_AOB_ONE
Build: 2024, 2025, 2028 (windows)
IDA Sig: C6 ?? ?? 00 C3 48 83 <- windows
C6 ?? ?? 00 C3 ?? 48 89 ?? E8 <- linux
^^
01
Build 2024: @0x2f800 <- windows
@0x39d13b <- linux
Build 2025: @0x2f7f3 <- windows
@0x39d151 <- linux
Build 2028: @0x2f92f <- windows
@NaN <- linux
'''
PERSISTENT_LICENSE_CHECK_AOB_ONE = {"windows": b"c6....00c34883",
"linux": b"c6....00c3..4889..e8",
"patch": b"01",
"patch_offset": 6}
'''
PERSISTENT_LICENSE_CHECK_AOB_TWO
Build: 2027, 2030
IDA Sig: C6 ?? 00 C3 CC 48 <- windows
C6 ?? 00 C3 CC ?? 48 <- linux
^^
01
Build 2027: @0x2f7fd <- windows
@0x39d1ef <- linux
Build 2030: @0x2edef <- windows
@0x39bc91 <- linux
'''
PERSISTENT_LICENSE_CHECK_AOB_TWO = {"windows": b"c6..00c3cc48",
"linux": b"c6..00c3cc..48",
"patch": b"01",
"patch_offset": 4}
'''
PERSISTENT_LICENSE_CHECK_AOB_THREE
Build: 2028 (linux)
IDA Sig: C6 ?? ?? 00 C3 CC 48 83 <- windows
C6 ?? ?? 00 C3 CC ?? 48 89 ?? E8 <- linux
^^
01
Build 2028: @NaN <- windows
@0x3991fc <- linux
'''
PERSISTENT_LICENSE_CHECK_AOB_THREE = {"windows": b"XXX",
"linux": b"c6....00c3cc..4889..e8",
"patch": b"01",
"patch_offset": 6}
'''
THEME_CHECK_AOB_ONE
Build: 2024, 2025, 2028
IDA Sig: 80 78 ?? 00 74 08 0F 57 C0 0F 11 06 EB 0F 48 8D 15 ? ? ? ? 48 89 F1 E8 ? ? ? ? 48 89 F0 48 83 C4 20 5E C3 CC 56 57 <- windows
80 78 ?? 00 BA ? ? ? ? 48 0F 45 D1 B8 ? ? ? ? 48 0F 45 C1 C3 41 57 <- linux
^^ ^^
48 01
Build 2024: @0x2d959 <- windows
@0x39c661 <- linux
Build 2025: @0x2d949 <- windows
@0x39c67b <- linux
Build 2028: @0x2da85 <- windows
@0x398721 <- linux
'''
THEME_CHECK_AOB_ONE = {"windows": b"8078..0074080f57c00f1106eb0f488d15........4889f1e8........4889f04883c4205ec3cc5657",
"linux": b"8078..00ba........480f45d1b8........480f45c1c34157",
"patch_one": b"48",
"patch_one_offset": 2,
"patch_two": b"01",
"patch_two_offset": 6}
'''
THEME_CHECK_AOB_TWO
Build: 2027, 2030 (windows)
IDA Sig: 80 38 00 74 08 0F 57 C0 0F 11 06 EB 0F 48 8D 15 ? ? ? ? 48 89 F1 E8 ? ? ? ? 48 89 F0 48 83 C4 20 5E C3 56 57 <- windows
80 38 00 BA ? ? ? ? 48 0F 45 D1 B8 ? ? ? ? 48 0F 45 C1 C3 CC 41 57 <- linux
^^ ^^
08 01
Build 2027: @0x2d955 <- windows
@0x39c71d <- linux
Build 2030: @0x2cf47 <- windows
@NaN <- linux
'''
THEME_CHECK_AOB_TWO = {"windows": b"80380074080f57c00f1106eb0f488d15........4889f1e8........4889f04883c4205ec35657",
"linux": b"803800ba........480f45d1b8........480f45c1c3cc4157",
"patch": b"0801",
"patch_offset": 2}
'''
THEME_CHECK_AOB_THREE
Build: 2030 (linux)
IDA Sig: 80 38 00 74 08 0F 57 C0 0F 11 06 EB 0F 48 8D 15 ? ? ? ? 48 89 F1 E8 ? ? ? ? 48 89 F0 48 83 C4 20 5E C3 56 57 <- windows
80 38 00 BA ? ? ? ? 48 0F 45 D1 B8 ? ? ? ? 48 0F 45 C1 C3 CC 53 <- linux
^^ ^^
08 01
Build 2030: @NaN <- windows
@0x39b381 <- linux
'''
THEME_CHECK_AOB_THREE = {"windows": b"XXX",
"linux": b"803800ba........480f45d1b8........480f45c1c3cc53",
"patch": b"0801",
"patch_offset": 2}
def __init__(self, file_path):
self.file_path = file_path
# get hex dump of input file
with open(file_path, "rb") as file:
self.hex_dump = bytearray(binascii.hexlify(file.read()))
def patch_file(self):
os_target = self.__get_file_os_target()
if os_target != None and self.__is_initial_license_check_index_valid(os_target) and self.__is_persistent_license_check_index_valid(os_target) and self.__is_theme_check_index_valid(os_target):
# bypass & patch file with changes
self.__patch_initial_license_check(os_target)
self.__patch_persistent_license_check(os_target)
self.__patch_theme_check(os_target)
try:
with open(self.file_path, "wb") as file:
file.write(binascii.unhexlify(self.hex_dump))
except PermissionError:
return False
return True
return False
def __get_file_os_target(self):
if self.hex_dump.startswith(b"4d5a"):
print(" >> Windows version")
return "windows"
elif self.hex_dump.startswith(b"7f454c4602"):
print(" >> Linux version")
return "linux"
else:
print(" >> Unknown version")
return None
def __index_is_valid(self, check_aob):
patterns = re.findall(check_aob, self.hex_dump)
pat_len = len(patterns)
print(" >> found " + str(pat_len) + " occurences (1 expected)")
for p in patterns:
check_index = self.hex_dump.index(p)
print(" >> found @" + hex(int(check_index/2)))
return pat_len == 1
def __is_initial_license_check_index_valid(self, os):
print(" >> looking for INITIAL_LICENSE_CHECK_AOB")
# yes, we are checking for the newer AOBs first, because
# the old one can be found in the newer builds but at the wrong
# location
if self.__index_is_valid(Patcher.INITIAL_LICENSE_CHECK_AOB_ONE[os]):
return True
elif self.__index_is_valid(Patcher.INITIAL_LICENSE_CHECK_AOB_TWO[os]):
return True
else:
return False
def __is_persistent_license_check_index_valid(self, os):
print(" >> looking for PERSISTENT_LICENSE_CHECK_AOB")
if self.__index_is_valid(Patcher.PERSISTENT_LICENSE_CHECK_AOB_ONE[os]):
return True
elif self.__index_is_valid(Patcher.PERSISTENT_LICENSE_CHECK_AOB_TWO[os]):
return True
elif self.__index_is_valid(Patcher.PERSISTENT_LICENSE_CHECK_AOB_THREE[os]):
return True
else:
return False
def __is_theme_check_index_valid(self, os):
print(" >> looking for THEME_CHECK_AOB")
if self.__index_is_valid(Patcher.THEME_CHECK_AOB_ONE[os]):
return True
elif self.__index_is_valid(Patcher.THEME_CHECK_AOB_TWO[os]):
return True
elif self.__index_is_valid(Patcher.THEME_CHECK_AOB_THREE[os]):
return True
else:
return False
def __patch_check(self, check_aob, patch_aob, patch_offset = 0):
# bypass checks
check_index = self.hex_dump.index(re.findall(check_aob, self.hex_dump)[0])
self.hex_dump[check_index+patch_offset:check_index+patch_offset+len(patch_aob)] = patch_aob
def __patch_check_by_index(self, check_index, patch_aob, patch_offset = 0):
# bypass checks
self.hex_dump[check_index+patch_offset:check_index+patch_offset+len(patch_aob)] = patch_aob
def __patch_initial_license_check(self, os):
# bypass license
patterns = re.findall(Patcher.INITIAL_LICENSE_CHECK_AOB_ONE[os], self.hex_dump)
if len(patterns) == 1:
index = self.hex_dump.index(patterns[0])
self.__patch_check_by_index(index, Patcher.INITIAL_LICENSE_CHECK_AOB_ONE["patch_one"], Patcher.INITIAL_LICENSE_CHECK_AOB_ONE["patch_one_offset"])
self.__patch_check_by_index(index, Patcher.INITIAL_LICENSE_CHECK_AOB_ONE["patch_two"], Patcher.INITIAL_LICENSE_CHECK_AOB_ONE["patch_two_offset"])
else:
patterns = re.findall(Patcher.INITIAL_LICENSE_CHECK_AOB_TWO[os], self.hex_dump)
if len(patterns) == 1:
index = self.hex_dump.index(patterns[0])
self.__patch_check_by_index(index, Patcher.INITIAL_LICENSE_CHECK_AOB_TWO["patch"], Patcher.INITIAL_LICENSE_CHECK_AOB_TWO["patch_offset"])
else:
print(" >> error: expected 1 result, but got: " + str(len(patterns)))
def __patch_persistent_license_check(self, os):
# bypass license
patterns = re.findall(Patcher.PERSISTENT_LICENSE_CHECK_AOB_ONE[os], self.hex_dump)
if len(patterns) == 1:
index = self.hex_dump.index(patterns[0])
self.__patch_check_by_index(index, Patcher.PERSISTENT_LICENSE_CHECK_AOB_ONE["patch"], Patcher.PERSISTENT_LICENSE_CHECK_AOB_ONE["patch_offset"])
else:
patterns = re.findall(Patcher.PERSISTENT_LICENSE_CHECK_AOB_TWO[os], self.hex_dump)
if len(patterns) == 1:
index = self.hex_dump.index(patterns[0])
self.__patch_check_by_index(index, Patcher.PERSISTENT_LICENSE_CHECK_AOB_TWO["patch"], Patcher.PERSISTENT_LICENSE_CHECK_AOB_TWO["patch_offset"])
else:
patterns = re.findall(Patcher.PERSISTENT_LICENSE_CHECK_AOB_THREE[os], self.hex_dump)
if len(patterns) == 1:
index = self.hex_dump.index(patterns[0])
self.__patch_check_by_index(index, Patcher.PERSISTENT_LICENSE_CHECK_AOB_THREE["patch"], Patcher.PERSISTENT_LICENSE_CHECK_AOB_THREE["patch_offset"])
else:
print(" >> error: expected 1 result, but got: " + str(len(patterns)))
def __patch_theme_check(self, os):
# bypass theme
patterns = re.findall(Patcher.THEME_CHECK_AOB_ONE[os], self.hex_dump)
if len(patterns) == 1:
index = self.hex_dump.index(patterns[0])
self.__patch_check_by_index(index, Patcher.THEME_CHECK_AOB_ONE["patch_one"], Patcher.THEME_CHECK_AOB_ONE["patch_one_offset"])
self.__patch_check_by_index(index, Patcher.THEME_CHECK_AOB_ONE["patch_two"], Patcher.THEME_CHECK_AOB_ONE["patch_two_offset"])
else:
patterns = re.findall(Patcher.THEME_CHECK_AOB_TWO[os], self.hex_dump)
if len(patterns) == 1:
index = self.hex_dump.index(patterns[0])
self.__patch_check_by_index(index, Patcher.THEME_CHECK_AOB_TWO["patch"], Patcher.THEME_CHECK_AOB_TWO["patch_offset"])
else:
patterns = re.findall(Patcher.THEME_CHECK_AOB_THREE[os], self.hex_dump)
if len(patterns) == 1:
index = self.hex_dump.index(patterns[0])
self.__patch_check_by_index(index, Patcher.THEME_CHECK_AOB_THREE["patch"], Patcher.THEME_CHECK_AOB_THREE["patch_offset"])
else:
print(" >> error: expected 1 result, but got: " + str(len(patterns)))
# script logic
def main():
parser = argparse.ArgumentParser(description="Patch Sublime Merge")
parser.add_argument("file_path", help="file path to sublime merge executable.")
args = parser.parse_args()
print("Patcher >> Starting job...")
# check for valid file path
if (os.path.isfile(args.file_path)):
# performs patch
patcher = Patcher(args.file_path)
result = patcher.patch_file()
if result:
print("Patcher >> Successfully patched '" + args.file_path + "'.")
else:
print("Patcher >> Could not work on input file.")
else:
print("Patcher >> Please input a valid file path.")
# direct execution of the script
if __name__ == "__main__":
main()
from https://www.board4all.biz/threads/sublime-merge-dev-build-2030-windows-linux.823464/