Skip to content

Instantly share code, notes, and snippets.

@lihaochen910
Created September 21, 2024 08:25
Show Gist options
  • Save lihaochen910/92e33548fe5b8313cd17897a972cdf30 to your computer and use it in GitHub Desktop.
Save lihaochen910/92e33548fe5b8313cd17897a972cdf30 to your computer and use it in GitHub Desktop.
Hopper Script: 自动patch CSP试用版验证方法
from typing import get_type_hints
class HPInstruction:
ARCHITECTURE_UNKNOWN = 0
ARCHITECTURE_i386 = 1
ARCHITECTURE_X86_64 = 2
ARCHITECTURE_ARM = 3
ARCHITECTURE_ARM_THUMB = tuple()
ARCHITECTURE_AARCH64 = tuple()
ARCHITECTURE_OTHER = 99
def stringForArchitecture():
pass
def getArchitecture(self):
pass
def getInstructionString(self) -> str:
pass
def getArgumentCount(self) -> int:
pass
def getRawArgument(self):
pass
def getFormattedArgument(self):
pass
def isAnInconditionalJump(self):
pass
def isAConditionalJump(self):
pass
def getInstructionLength(self):
pass
class HPSegment:
BAD_ADDRESS = -1
TYPE_UNDEFINED = 0
TYPE_OUTSIDE = 1
TYPE_NEXT = 2
TYPE_INT8 = 3
TYPE_INT16 = 4
TYPE_INT32 = 5
TYPE_INT64 = 6
TYPE_ASCII = 7
TYPE_UNICODE = 8
TYPE_ALIGN = 63
TYPE_CODE = 65
TYPE_PROCEDURE = 66
TYPE_STRUCTURE = 67
def stringForType():
pass
def getName(self):
pass
def getStartingAddress(self):
pass
def getLength(self):
pass
def getFileOffset(self):
pass
def getFileOffsetForAddress(self):
pass
def getSectionCount(self):
pass
def getSection(self):
pass
def getSectionsList(self):
pass
def getSectionIndexAtAddress(self):
pass
def getSectionAtAddress(self):
pass
def readBytes(self):
pass
def readByte(self):
pass
def readUInt16LE(self):
pass
def readUInt32LE(self):
pass
def readUInt64LE(self):
pass
def readUInt16BE(self):
pass
def readUInt32BE(self):
pass
def readUInt64BE(self):
pass
def writeBytes(self):
pass
def writeByte(self):
pass
def writeUInt16LE(self):
pass
def writeUInt32LE(self):
pass
def writeUInt64LE(self):
pass
def writeUInt16BE(self):
pass
def writeUInt32BE(self):
pass
def writeUInt64BE(self):
pass
def markAsUndefined(self):
pass
def markRangeAsUndefined(self):
pass
def markAsCode(self):
pass
def markAsProcedure(self):
pass
def markAsDataByteArray(self):
pass
def markAsDataShortArray(self):
pass
def markAsDataIntArray(self):
pass
def isThumbAtAddress(self):
pass
def setThumbModeAtAddress(self):
pass
def setARMModeAtAddress(self):
pass
def getTypeAtAddress(self):
pass
def setTypeAtAddress(self):
pass
def makeAlignment(self):
pass
def getNextAddressWithType(self):
pass
def disassembleWholeSegment(self):
pass
def setNameAtAddress(self):
pass
def getNameAtAddress(self):
pass
def getDemangledNameAtAddress(self):
pass
def getCommentAtAddress(self):
pass
def setCommentAtAddress(self):
pass
def getInlineCommentAtAddress(self):
pass
def setInlineCommentAtAddress(self):
pass
def getInstructionAtAddress(self) -> HPInstruction:
pass
def getReferencesOfAddress(self) -> []:
pass
def getReferencesFromAddress(self):
pass
def addReference(self):
pass
def removeReference(self):
pass
def getLabelCount(self):
pass
def getLabelName(self):
pass
def labelIterator(self):
pass
def getLabelsList(self):
pass
def getNamedAddresses(self):
pass
def getProcedureCount(self):
pass
def getProcedureAtIndex(self):
pass
def getProcedureIndexAtAddress(self):
pass
def getProcedureAtAddress(self):
pass
def getInstructionStart(self):
pass
def getObjectLength(self):
pass
def isPartOfAnArray(self):
pass
def getArrayStartAddress(self):
pass
def getArrayElementCount(self):
pass
def getArrayElementAddress(self):
pass
def getArrayElementSize(self):
pass
class HPDocument:
FORMAT_DEFAULT = 0
FORMAT_HEXADECIMAL = 1
FORMAT_DECIMAL = 2
FORMAT_OCTAL = 3
FORMAT_CHARACTER = 4
FORMAT_STACKVARIABLE = 5
FORMAT_OFFSET = 6
FORMAT_ADDRESS = 7
FORMAT_FLOAT = 8
FORMAT_BINARY = 9
FORMAT_STRUCTURED = 10
FORMAT_ENUM = 11
FORMAT_ADDRESS_DIFF = 12
FORMAT_NEGATE = 32
FORMAT_LEADINGZEROES = 64
FORMAT_SIGNED = 128
def newDocument():
pass
def getCurrentDocument():
pass
def getAllDocuments():
pass
def ask():
pass
def askFile():
pass
def askDirectory():
pass
def message():
pass
def closeDocument(self):
pass
def loadDocumentAt(self):
pass
def saveDocument(self):
pass
def saveDocumentAt(self):
pass
def getDocumentName(self):
pass
def setDocumentName(self):
pass
def backgroundProcessActive(self):
pass
def requestBackgroundProcessStop(self):
pass
def waitForBackgroundProcessToEnd(self):
pass
def assemble(self):
pass
def getDatabaseFilePath(self):
pass
def getExecutableFilePath(self):
pass
def setExecutableFilePath(self):
pass
def log(self):
pass
def rebase(self):
pass
def newSegment(self):
pass
def deleteSegment(self):
pass
def renameSegment(self):
pass
def getSegmentCount(self):
pass
def getSegment(self) -> HPSegment:
pass
def getSegmentByName(self):
pass
def getSectionByName(self):
pass
def getSegmentsList(self):
pass
def getSegmentIndexAtAddress(self):
pass
def getSegmentAtAddress(self):
pass
def getSectionAtAddress(self):
pass
def getCurrentSegmentIndex(self):
pass
def getCurrentSegment(self):
pass
def getCurrentSection(self):
pass
def getCurrentProcedure(self):
pass
def getCurrentAddress(self):
pass
def setCurrentAddress(self):
pass
def getSelectionAddressRange(self):
pass
def moveCursorAtAddress(self):
pass
def selectAddressRange(self):
pass
def getFileOffsetFromAddress(self):
pass
def getAddressFromFileOffset(self):
pass
def is64Bits(self):
pass
def getEntryPoint(self):
pass
def moveCursorAtEntryPoint(self):
pass
def getHighlightedWord(self):
pass
def setNameAtAddress(self):
pass
def getNameAtAddress(self):
pass
def getAddressForName(self):
pass
def refreshView(self):
pass
def moveCursorOneLineDown(self):
pass
def moveCursorOneLineUp(self):
pass
def getRawSelectedLines(self):
pass
def addTagAtAddress(self):
pass
def removeTagAtAddress(self):
pass
def hasTagAtAddress(self):
pass
def getTagCountAtAddress(self):
pass
def getTagAtAddressByIndex(self):
pass
def tagIteratorAtAddress(self):
pass
def getTagListAtAddress(self):
pass
def getTagCount(self):
pass
def getTagAtIndex(self):
pass
def tagIterator(self):
pass
def getTagList(self):
pass
def buildTag(self):
pass
def getTagWithName(self):
pass
def destroyTag(self):
pass
def hasColorAtAddress(self):
pass
def setColorAtAddress(self):
pass
def getColorAtAddress(self):
pass
def removeColorAtAddress(self):
pass
def readBytes(self):
pass
def readByte(self):
pass
def readUInt16LE(self):
pass
def readUInt32LE(self):
pass
def readUInt64LE(self):
pass
def readUInt16BE(self):
pass
def readUInt32BE(self):
pass
def readUInt64BE(self):
pass
def writeBytes(self):
pass
def writeByte(self):
pass
def writeUInt16LE(self):
pass
def writeUInt32LE(self):
pass
def writeUInt64LE(self):
pass
def writeUInt16BE(self):
pass
def writeUInt32BE(self):
pass
def writeUInt64BE(self):
pass
def getOperandFormat(self):
pass
def getOperandFormatRelativeTo(self):
pass
def setOperandFormat(self):
pass
def setOperandRelativeFormat(self):
pass
def getInstructionStart(self):
pass
def getObjectLength(self):
pass
def generateObjectiveCHeader(self):
pass
def produceNewExecutable(self):
pass
def setBookmarkAtAddress(self):
pass
def removeBookmarkAtAddress(self):
pass
def hasBookmarkAtAddress(self):
pass
def renameBookmarkAtAddress(self):
pass
def findBookmarkWithName(self):
pass
def getBookmarkName(self):
pass
def getBookmarks(self):
pass
trialSymbols = [
'__ZN12Planeswalker4Urza21URApplicationBehavior38GetStringIDMessageQuestionTrialVersionEv',
'__ZN12Planeswalker4Urza21URApplicationBehavior35GetStringIDMessageErrorTrialVersionEv',
]
trialSymbols_GetStringIDMessageQuestionTrialVersionEv = '__ZN12Planeswalker4Urza21URApplicationBehavior38GetStringIDMessageQuestionTrialVersionEv'
trialSymbols_GetStringIDMessageErrorTrialVersionEv = '__ZN12Planeswalker4Urza21URApplicationBehavior35GetStringIDMessageErrorTrialVersionEv'
def byteListToString(byteList):
str = '['
for byte in byteList:
str += '%02X ' % byte
str += ']'
return str
class PatchContext:
def __init__(self, doc : HPDocument, seg : HPSegment, addr : int):
self.doc = doc
self.seg = seg
self.addr = addr
class HPSegmentPatch:
PatchDescription = 'A base class for Patch'
def findPatternToPatch(doc : HPDocument) -> tuple:
raise NotImplementedError()
def backupOriginCode():
pass
def applyPatch(context : PatchContext) -> bool:
raise NotImplementedError()
class Patch_SkipSelectTrialProductGradeType(HPSegmentPatch):
PatchDescription = '01. skip select trial product GradeType'
def findPatternToPatch(doc : HPDocument):
for seg_id in range(0, doc.getSegmentCount()):
seg : HPSegment = doc.getSegment(seg_id)
for label in seg.getLabelsList():
if label == '__ZNK12Planeswalker4Urza22URRemoteControlManager37GetAlreadyStartedConnectionFromClientEv':
addr = doc.getAddressForName(label)
# print('found:', label, addr, type(addr))
for xref in seg.getReferencesOfAddress(addr):
# i = seg.getInstructionAtAddress(xref)
print('xref: 0x%09x' % xref)
# using first xref
return True, PatchContext(doc, seg, xref)
break
def applyPatch(context : PatchContext) -> bool:
doc = context.doc
doc.moveCursorAtAddress(context.addr)
# move down 2 line
for _ in range(2):
doc.moveCursorOneLineDown()
cursorAddr = doc.getCurrentAddress()
instr = context.seg.getInstructionAtAddress(cursorAddr)
if not matchInstrcutionCmd( instr, 'je' ):
return False
# replace je with jne
# 0x74, 0x85
# 0x75, 0x12
# context.seg.writeByte( cursorAddr + 0, 0x75 )
# context.seg.markAsCode( cursorAddr )
# instr = context.seg.getInstructionAtAddress(cursorAddr)
# if not matchInstrcutionCmd( instr, 'jne' ):
# return False
return True
class Patch_BypassAllQuestionTrialVersion(HPSegmentPatch):
PatchDescription = '02. bypass all QuestionTrialVersion'
def findPatternToPatch(doc : HPDocument):
for seg_id in range(0, doc.getSegmentCount()):
seg : HPSegment = doc.getSegment(seg_id)
for label in seg.getLabelsList():
if label == trialSymbols_GetStringIDMessageQuestionTrialVersionEv:
addr = doc.getAddressForName(label)
print('found:', label, addr, type(addr))
patchContext = PatchContext(doc, seg, -1)
xrefs = []
for xref in seg.getReferencesOfAddress(addr):
# i = seg.getInstructionAtAddress(xref)
# print('xref: 0x%09x' % xref)
xrefs.append(xref)
# using first xref
patchContext.xrefs = xrefs
return True, patchContext
def applyPatch(context : PatchContext) -> bool:
doc = context.doc
xrefs = context.xrefs
print( f'xrefs len: {len( xrefs )}' )
for xref in xrefs:
doc.moveCursorAtAddress(xref)
xrefSegment = doc.getSegmentAtAddress(xref)
xrefSegment.setCommentAtAddress(xref, "xref to %s" % trialSymbols_GetStringIDMessageQuestionTrialVersionEv)
# move up 1 line
for _ in range(1):
doc.moveCursorOneLineUp()
cursorAddr = doc.getCurrentAddress()
instr = context.seg.getInstructionAtAddress(cursorAddr)
if not matchInstrcutionCmd( instr, 'jne' ):
print( f'found unsupported: {instr.getInstructionString()}' )
return False
comment = "Instr Architecture: %s\n" % Instruction.stringForArchitecture(instr.getArchitecture())
comment += f"Instruction: {instr.getInstructionString()}\n"
comment += f"Instruction Length: {instr.getInstructionLength()}\n"
comment += f"Instruction Args Count: {instr.getArgumentCount()}\n"
for idx in range(instr.getArgumentCount()):
comment += f"\targ{idx}: {instr.getFormattedArgument(idx)} {instr.getRawArgument(idx)}\n"
# jneParamBytes = [ 0xe9 ]
# argsHexStr = ""
# for j in range(2, instr.getInstructionLength()):
# argsHexStr += str("%02X " % xrefSegment.readByte(cursorAddr + j))
# jneParamBytes.append(xrefSegment.readByte(cursorAddr + j))
# comment += f"argsHex: {argsHexStr}\n"
# comment += f"overwrite below: E9 {argsHexStr}"
jneParamInstr = instr.getRawArgument(0)
xrefSegment.setCommentAtAddress(cursorAddr, comment)
# print( 'cursor instr: %s' % instr.getInstructionString() )
# print( comment )
# replace jmp
# 0x0f, 0x85
# 0xe9
# xrefSegment.writeByte( xref + 0, 0xe9 )
doc.moveCursorAtAddress( xref )
dst = 0
# try:
# if jneParamInstr.startswith( "loc_" ):
# dst = int( f'0x{jneParamInstr.removeprefix("loc_")}', 16 )
# print( f'parse loc_: {jneParamInstr} {dst:09x}' )
# elif jneParamInstr.startswith( "_" ):
# dst = int( f'0x{doc.getAddressForName(jneParamInstr)}', 16 )
# print( f'parse Name: {jneParamInstr} {dst:09x}' )
# else:
# raise ValueError()
# except ValueError as e:
# print( f'ValueError on parse jneParamInstr: {jneParamInstr}' )
# return False
try:
dst = int( jneParamInstr, 16 )
except ValueError as e:
print( f'ValueError on parse jneParamInstr: {jneParamInstr}' )
return False
# print( f'parse RawArgument: {jneParamInstr} {dst:09x}' )
# return False
offset = dst - (xref + 5) # jmp Instr length in x86_64
xrefSegment.writeByte(xref + 0, 0xE9)
xrefSegment.writeByte(xref + 1, ((offset >> 0) & 255))
xrefSegment.writeByte(xref + 2, ((offset >> 8) & 255))
xrefSegment.writeByte(xref + 3, ((offset >> 16) & 255))
xrefSegment.writeByte(xref + 4, ((offset >> 24) & 255))
xrefSegment.markAsCode(xref)
# # assemble
# # instrBytes = doc.assemble( f'jmp {jneParamInstr}', xref, 0 )
# # instrBytes = doc.assemble( f'jmp {doc.getAddressFromFileOffset(jneParamInstr)}', xref, 0 )
# paramAddr = doc.getFileOffsetFromAddress( int( f'0x{jneParamInstr.removeprefix("loc_")}', 16 ) )
# instrBytes = doc.assemble( f'jmp {paramAddr}', xref, 0 )
# if instrBytes:
# xrefSegment.writeBytes( xref, bytes( instrBytes ) )
# xrefSegment.markAsCode( xref )
# print( f'patched at: {xref} {byteListToString(instrBytes)}' )
# return True
instr = xrefSegment.getInstructionAtAddress( xref )
if not matchInstrcutionCmd( instr, 'jmp' ):
return False
else:
print( f'patched at: {xref:09x}' )
return True
### Opcodes
nop_opcodes = {
1: 0x90, # 1 i386
2: 0x90, # 2 x86_64
3: 0x0000a0e1, # 3 ARM
}
### Functions
def write_nop(adr, arch, seg):
# doc.log("Writing NOP to 0x%08x" % adr)
seg.writeByte(adr, nop_opcodes[arch])
seg.markAsCode(adr)
class Patch_BypassAllErrorTrialVersion(HPSegmentPatch):
PatchDescription = '03. bypass all ErrorTrialVersion'
def findPatternToPatch(doc : HPDocument):
for seg_id in range(0, doc.getSegmentCount()):
seg : HPSegment = doc.getSegment(seg_id)
for label in seg.getLabelsList():
if label == trialSymbols_GetStringIDMessageErrorTrialVersionEv:
addr = doc.getAddressForName(label)
print('found:', label, addr, type(addr))
patchContext = PatchContext(doc, seg, -1)
xrefs = []
for xref in seg.getReferencesOfAddress(addr):
# i = seg.getInstructionAtAddress(xref)
# print('xref: 0x%09x' % xref)
xrefs.append(xref)
# using first xref
patchContext.xrefs = xrefs
return True, patchContext
def applyPatch(context : PatchContext) -> bool:
doc = context.doc
xrefs = context.xrefs
print( f'xrefs len: {len( xrefs )}' )
for xref in xrefs:
doc.moveCursorAtAddress(xref)
xrefSegment = doc.getSegmentAtAddress(xref)
xrefSegment.setCommentAtAddress(xref, "xref to %s" % trialSymbols_GetStringIDMessageErrorTrialVersionEv)
sub_xrefs = []
for sub_xref in xrefSegment.getReferencesOfAddress(xref):
sub_xrefs.append( sub_xref )
instr = xrefSegment.getInstructionAtAddress(sub_xref)
arch = instr.getArchitecture()
if arch not in nop_opcodes:
doc.log("Error: CPU Architecture not supported")
else:
# arch_name = Instruction.stringForArchitecture(instr.getArchitecture())
# doc.log("--- Inserting %s opcodes ---" % arch_name)
if arch != 3: # Ignore ARM
xrefSegment.setCommentAtAddress(sub_xref, "将要patch这里")
for index in range(instr.getInstructionLength()):
write_nop(sub_xref + index, arch, xrefSegment)
# print( f"write_nop({sub_xref + index})" )
print( f'patched at: {xref:09x}' )
return True
patchesForCSP = [
Patch_SkipSelectTrialProductGradeType,
Patch_BypassAllQuestionTrialVersion,
Patch_BypassAllErrorTrialVersion
]
doc : HPDocument = Document.getCurrentDocument()
# seg = doc.getCurrentSegment()
# adr = doc.getCurrentAddress()
def print_exported_class(klass):
attrs = [k for k in klass.__dict__.keys()
if not k.startswith('__')
and not k.endswith('__')]
print('class %s:' % (klass.__name__))
for attr in attrs:
attrTypeName = type(klass.__dict__[attr]).__name__
if attrTypeName == 'function':
print('\tdef %s(self):' % (attr))
print('\t\tpass\n')
elif attrTypeName == 'staticmethod':
print('\tdef %s():' % (attr))
print('\t\tpass\n')
elif attrTypeName == 'int':
print('\t%s = %d' % (attr, klass.__dict__[attr]))
else:
print('unknown attrTypeName:', attrTypeName, attr)
print('\t%s = %s()' % (attr, attrTypeName))
return attrs
# print_exported_class(Document)
# print_exported_class(seg.__class__)
# print_exported_class(Instruction)
def readInstructionToBytes(addr):
# bytes = []
instr = seg.getInstructionAtAddress(addr)
bytes = seg.readBytes(addr, instr.getInstructionLength())
bytesStr = ''
for b in bytes:
# print('byte: 0x%02x' % b)
bytesStr += '0x%02x, ' % b
print( bytesStr )
return bytes
def matchInstructionString(instruction : HPInstruction, strToMatch : str) -> bool:
return parseLineInstructionString(instruction) == strToMatch
def matchInstrcutionCmd(instruction : HPInstruction, cmdToMatch : str) -> bool:
return instruction.getInstructionString() == cmdToMatch
def parseLineInstructionString(instruction : HPInstruction) -> str:
instructionStr = '%s ' % instruction.getInstructionString()
for i in range(instruction.getArgumentCount()):
if i < instruction.getArgumentCount() - 1:
instructionStr += '%s, ' % instruction.getFormattedArgument(i)
else:
instructionStr += '%s' % instruction.getFormattedArgument(i)
return instructionStr
def readCountInstructionAtAddress(addr, count):
index = 0
addrCurrent = addr
while index < count:
print('readAtAddress: 0x%09x' % addrCurrent)
instr = seg.getInstructionAtAddress(addrCurrent)
instrBytes = readInstructionToBytes(addrCurrent)
doc.moveCursorAtAddress(addrCurrent)
doc.moveCursorOneLineUp()
doc.moveCursorOneLineUp()
cursorAddr = doc.getCurrentAddress()
print('cursorAddr: 0x%09x' % cursorAddr)
print('readCount:', index)
# print('instruction bytes:', instrBytes)
print('instruction count:', instr.getArgumentCount())
print('instruction:', instr.getInstructionString())
for i in range(instr.getArgumentCount()):
value = instr.getFormattedArgument(i)
print('instruction arg %d: %s %s' % (i, value, type(value)))
print('completed:', parseLineInstructionString(instr))
print('matched:', matchInstructionString(instr, 'call __ZN12Planeswalker4Urza19URPreferenceSetting19SetProductGradeTypeEi'))
addrCurrent += instr.getInstructionLength()
index += 1
break
# if label in trialSymbols and False:
# addr = doc.getAddressForName(label)
# print('found:', label, addr, type(addr))
# for xref in seg.getReferencesOfAddress(addr):
# # xref_seg = doc.getSegmentAtAddress(xref)
# print('xref to fount:', xref)
# # existing_inline_comment = xref_seg.getInlineCommentAtAddress(xref)
# # if existing_inline_comment is None or existing_inline_comment.startswith("0x"):
# # cstr_data = str(read_data(None, cstr_ptr, cstr_len))
# # doc.log("Set inline comment at 0x%x: %s"%(xref, repr(cstr_data)))
# # xref_seg.setInlineCommentAtAddress(xref, "@" + repr(cstr_data))
# i = seg.getInstructionAtAddress(xref)
# print('instruction:', i.getInstructionString())
# # for x in range(0, len(bytes)):
# # seg.writeByte(adr + x, bytes[x])
# # if not printed:
# # print_exported_class(xref.__class__)
# # printed = True
# # if not printed2:
# # print_exported_class(i.__class__)
# # printed2 = True
for patchKlass in patchesForCSP:
# print( 'Starting patch: %s' % patchKlass.PatchDescription )
print( '%s' % patchKlass.PatchDescription )
matchResult, patchContext = patchKlass.findPatternToPatch( doc )
# print( '\tfind patch pattern: %s' % matchResult )
if matchResult is False:
print( 'can\'t findPatternToPatch, script need to be update!' )
# raise SystemError()
# print( '\ttry applyPath at: 0x%09x' % patchContext.addr )
patchResult = patchKlass.applyPatch( patchContext )
if patchResult is False:
print( 'failed applyPatch(), script need to be update!' )
# raise SystemError()
print( '\tSuccess!' )
print( 'Patch Completed.' )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment