diff --git a/ghidra/README.md b/ghidra/README.md new file mode 100644 index 0000000..a49d6dc --- /dev/null +++ b/ghidra/README.md @@ -0,0 +1,80 @@ +# oasis-firmware-analyzer-ghidra-api +Use Ghidra API to automatize the pattern searching in the firmware analyzer module of the OASIS framework + +# Folders +- `scripts/` : scripts who use the Ghidra API +- `tables/` : tables for different architecture (Broadcom (complete at 70%), NRF51-Softdevice, NRF52-Zephyr) + +# How to use +1. Install Ghidra +2. Create a Ghidra Folder, import binary file, choose the language type (ARMv7 little endian) and run it for the first time to do the analyzis with ARM force options +3. Find `analyzeHeadless` executable (default location `/usr/share/ghidra/support/`) +4. How to use `analyzeHeadless` : + +` +/usr/share/ghidra/support/analyzeHeadless -noanalysis -process -scriptPath -postScript +` + +An example : + +` +/usr/share/ghidra/support/analyzeHeadless Ghidra/ FirmwareAnalyzis -noanalysis -process zephyr_nrf52_firmware.hex -scriptPath scripts -postScript SearchPattern.py +` + +5. Set the env variable `TABLE` with the JSON table you want you want to target a specific architecture (ex: export TABLE=tables/table_nrf52.json) +6. Run the script at the same level than the README +7. See the results + +# Scripts +- `SearchPattern.py` : script to search functions we want to target (use a custom similarity system) +- `GetInfos.py` : script to get informations about an instruction to fill a JSON table (replace the address you want to get informations inside Ghidra) + +# Custom similarity system +1. Best result : __0__ +2. Good result : __< 10__ +3. Potential found functions (need to search manually the different candidates) : __between 10 and 40__ +4. Bad result : __> 50__ + +### Malus system based on what ? +- mnemonic instruction +- nb operands instruction +- type (BIT, ADDRESS, DATA, READ, WRITE, etc.) operands instruction +- pcode constant fields values to represent mnemonic +- hexadecimal +- Pcode LOAD vs STORE +- Zephyr registers + +# What is the FORCE MODE ? +When the script is running, it will search pattern only on the entry point of each function. But sometimes, pattern will be at different locations in the function. + +So, for the first iteration, the script will analyze on the entry point for each pattern and if the result is not satisfy, it will re-run in the force mode (on every instruction for each function) to find a better candidate. + +The only drawback is the execution time of this mode ! + +# JSON tables fields + + { + "func": "", + "instr": "instruc1;instruc2;...;instrucN", (null sometimes) + "hexa": "hex1 hex2 hex3 ... hexN", + "diff": "<0 or 1>", + "regs": "", + "specs": "null", (can be anything ...) + "pcode": "" (null sometimes) + }, + +An example : + + { + "func": "lm_getRawRssiWithTaskId", + "instr": "add,2,(512:r0),(512:r1);sxtb,2,(512:r0),(512:r0);bx,1,(512:lr)", + "hexa": "0844 40b2 7047", + "diff": "0", + "regs": "null", + "specs": "null", + "pcode": "19;63,18;27,12,1,27,10" + } + +# Future improvements +1. add new metrics to increase the accurary of the pattern searching +2. use the "specs" (specificities aspect of the target function) and "diff" (the function use different patterns) fields in the JSON table diff --git a/ghidra/scripts/GetInfos.py b/ghidra/scripts/GetInfos.py new file mode 100644 index 0000000..1cd0aea --- /dev/null +++ b/ghidra/scripts/GetInfos.py @@ -0,0 +1,44 @@ +#TODO Get informations to complete the JSON table +#@author +#@category Search.InstructionPattern +#@keybinding +#@menupath +#@toolbar + +from ghidra.program.model.lang import OperandType + +# Put the address you want to get informations to complete the JSON table +addr = "00036292" + +instruc = currentProgram.getListing().getInstructionAt(toAddr(addr)) + +#1 +operandTypeList = [] +#4 +nboperands = instruc.getNumOperands() +#5 +operandList = [] +#6 +mnemonic = instruc.getMnemonicString() + +pcode_op = "" +for pcode in instruc.getPcode(): + p = pcode.getOpcode() + if pcode_op == "": + pcode_op = str(p) + else: + pcode_op = pcode_op + "," + str(p) +print(pcode_op) + +for k in range(nboperands): + #1 + operandTypeList.append(instruc.getOperandType(k)) + #5 + operandList.append(instruc.getOpObjects(k)) + +if nboperands == 3: + print(mnemonic + "," + str(nboperands) + ",(" + str(operandTypeList[0]) + ":" + str(operandList[0][0]) + "),(" + str(operandTypeList[1]) + ":" + str(operandList[1][0]) + "),(" + str(operandTypeList[2]) + ":" + str(operandList[2][0]) + ")") +elif nboperands == 2: + print(mnemonic + "," + str(nboperands) + ",(" + str(operandTypeList[0]) + ":" + str(operandList[0][0]) + "),(" + str(operandTypeList[1]) + ":" + str(operandList[1][0]) + ")") +elif nboperands == 1: + print(mnemonic + "," + str(nboperands) + ",(" + str(operandTypeList[0]) + ":" + str(operandList[0][0]) + ")") diff --git a/ghidra/scripts/SearchPattern.py b/ghidra/scripts/SearchPattern.py new file mode 100644 index 0000000..a07229e --- /dev/null +++ b/ghidra/scripts/SearchPattern.py @@ -0,0 +1,614 @@ +# -*- coding: utf-8 -*- + +# Search pattern +#@author +#@category Search.InstructionPattern +#@keybinding +#@menupath +#@toolbar + + +import json, os, argparse, sys + +class Tools: + def hexToBinary(self, hexa_str): + """ + Converts a hexadecimal string to binary string. + + :param hexa_str: Hexadecimal in string format. + :type hexa_str: str + + :return: Binary string of the hexadecimal string. + :rtype: str + """ + + return bin(int(hexa_str, 16))[2:].zfill(len(hexa_str)*4) + + def levenshteinDistance(self, string1, string2): + """ + Calculate the levenshtein distance between two strings + + :param string1: The first string. + :type string1: str + :param string2: The second string. + :type string2: str + + :return: Number of the levenshtein distance. + :rtype: int + """ + + m, n = len(string1), len(string2) + dp = [[0] * (n + 1) for _ in range(m + 1)] + + for i in range(m + 1): + dp[i][0] = i + + for j in range(n + 1): + dp[0][j] = j + + for i in range(1, m + 1): + for j in range(1, n + 1): + cost = 0 if string1[i - 1] == string2[j - 1] else 1 + dp[i][j] = min( + dp[i - 1][j] + 1, # deletion + dp[i][j - 1] + 1, # insertion + dp[i - 1][j - 1] + cost # substitution + ) + + return dp[m][n] + + def customInstrucsDistance(self, instrucs, target_instrucs): + """ + Calculate the distance between two instructions with a custom malus points + + :param instrucs: Instructions to try. + :type instrucs: list + :param target_instrucs: Instructions to compare with (target pattern). + :type target_instrucs: list + + :return: Number of the malus points. + :rtype: int + """ + + # Malus points + malus = 0 + + for i in range(len(target_instrucs)): + if target_instrucs[i] != "null": + target_instruc = target_instrucs[i].split(',') + + # Check mnemonic + mnemonic = target_instruc[0] + + if instrucs[i].getMnemonicString() != mnemonic: + malus = malus + 5 + + # Check operands + nbops1 = instrucs[i].getNumOperands() + nbops2 = target_instruc[1] + if nbops1 - int(nbops2) != 0: + malus = malus + 10 + else: + for k in range(nbops1): + operandData2 = target_instruc[2+k].split(':') + + # Check operand type + operandType1 = instrucs[i].getOperandType(k) + operandType2 = (operandData2[0])[1:] + + if int(operandType1) - int(operandType2) != 0: + malus = malus + 10 + + return malus + + def customPcodeDistance(self, pcode, target_pcode): + """ + Calculate the distance between two pcode with a custom malus points + + :param pcode: Pcode to try. + :type pcode: list.list + :param target_pcode: Pcode to compare with (target pattern). + :type target_pcode: list.list + + :return: Number of the malus points. + :rtype: int + """ + + malus = 0 + + # Check Pcode + ins = 0 + op = 0 + while ins < len(target_pcode) and ins < len(pcode): + while op < len(target_pcode[ins]) and op < len(pcode[ins]): + if target_pcode[ins][op] != "null" and pcode[ins][op] - int(target_pcode[ins][op]) != 0: + malus = malus + 2 + if (2 in target_pcode[ins] and 3 in pcode[ins]) or (3 in target_pcode[ins] and 2 in pcode[ins]): + malus = malus + 10 + op = op + 1 + ins = ins + 1 + + return malus + + def customSimilarityPercentage(self, hexa, target_hexa, instrucs, target_instrucs, pcode, target_pcode): + """ + Calculate the mean between all of the different distance based on different metadata + + :param hexa: Hexadecimal to try. + :type hexa: str + :param target_hexa: Hexadecimal to compare with (target pattern). + :type target_hexa: str + :param instrucs: Instructions to try. + :type instrucs: list + :param target_instrucs: Instructions to compare with (target pattern). + :type target_instrucs: list + :param pcode: Pcode to try. + :type pcode: list.list + :param target_pcode: Pcode to compare with (target pattern). + :type target_pcode: list.list + + :return: Mean of the distance. + :rtype: int + """ + + distanceInstrucAndPcode = self.customInstrucsDistance(instrucs, target_instrucs) + distanceHexa = self.levenshteinDistance(hexa, target_hexa) + distancePcode = self.customPcodeDistance(pcode, target_pcode) + + mean = abs(distanceInstrucAndPcode + distanceHexa + distancePcode) + + if mean < 50: + return True, mean + else: + return False, 0 + + def splitAddress(self, hexa_address): + """ + Split a hexadecimal string to an address with the offset. + + :param hexa_address: Hexadecimal address. + :type hexa_address: str + + :return: Base address with the offset. + :rtype: str, str + """ + + base_address = hexa_address[:-3] + "000" + offset = "0x" + hexa_address[-3:] + + return base_address, offset + + +class FunctionTargetInfo: + def __init__(self, name, hexa, instruc, zeph_reg, pcode): + self.name = name + self.instruc = instruc + self.hexa = hexa + self.zeph_reg = zeph_reg + self.pcode = pcode + + def getHexa(self): + return self.hexa + + def getInstruc(self): + return self.instruc + + def getName(self): + return self.name + + def getZephReg(self): + return self.zeph_reg + + def getPcode(self): + return self.pcode + + +class SearchBased: + def findFunctionByHexaAndInstrucAndPcode(self, target_hexa, target_instrucs, target_pcode, potential_addrs_funcs_after_regs, force): + """ + Split a hexadecimal string to an address with the offset. + + :param target_hexa: Hexadecimal target to compare with. + :type target_hexa: str + :param target_instrucs: Instructions targets to compare with. + :type target_instrucs: list + :param target_pcode: Pcode target to compare with. + :type target_pcode: list.list + :param potential_addrs_funcs_after_regs: Addresses functions to potential targets. + :type potential_addrs_funcs_after_regs: list + :param force: enter in the force mode + :type force: bool + + :return: Triplet to the potential found function with the similarity result + :rtype: Function, int, Address + """ + + # Separate the 2 cases + if potential_addrs_funcs_after_regs == None: + # Get only the functions + BB = currentProgram.getListing().getFunctions(True) # List of functions + else: + BB = potential_addrs_funcs_after_regs # List of addresses + + similitudes = [] + # for each function + for bloc in BB: + if force == True: + # we will get the same number of instructions than the target instructions + # get the perfect similitude for these instructions for the function + if potential_addrs_funcs_after_regs == None: + similitude = self.getBestSimiFunc(bloc.getEntryPoint(), target_instrucs, target_hexa, target_pcode, None) + else: + similitude = self.getBestSimiFunc(bloc, target_instrucs, target_hexa, target_pcode, potential_addrs_funcs_after_regs) + + else: + similitude = self.getSimiFunc(bloc, target_instrucs, target_hexa, target_pcode, potential_addrs_funcs_after_regs) + + if similitude[0] != None: + similitudes.append(similitude) + + if len(similitudes) == 0: + return None, 0, None + else: + # Havec the best candidates at the beginning of the list + similitudes.sort(key=lambda x: x[1]) + + if len(similitudes) > 2 and similitudes[0][1] != similitudes[1][1]: + return similitudes[0] + elif len(similitudes) <= 2: + return similitudes[0] + else: + num = 0 + best = 0 + for s in similitudes: + if num == 0: + best = s[1] + num = num + 1 + elif s[1] == best: + num = num + 1 + + if num > 3: + return [num, similitudes[0:3]] + else: + return [num, similitudes[0:num]] + + def getPcodeMnemo(self, instruc, size_pcode): + """ + Get the mnemonics of the Pcode for the instruction. + + :param instruc: Instruction to work on + :type instruc: Instruction + :param size_pcode: Size of the array of the Pcode. + :type size_pcode: int + + :return: Mnemonics of the Pcode of the instruction + :rtype: list + """ + + # for each instruc, we have each pcode (1 instruc can have multiple pcode to describe it) + pcode_instrucs = [] + for k in range(size_pcode): + pcode = [] + + # each pcode, we get a list of constant field values for each mnemonic + if instruc != None: + for l in instruc.getPcode(): + pcode.append(l.getOpcode()) + + pcode_instrucs.append(pcode) + + instruc = instruc.getNext() + else: + break + + return pcode_instrucs + + def getInstrucs(self, addr, size_instrucs): + """ + Get the following instructions beginning with an address. + + :param addr: Address as the entry point + :type addr: Address + :param size_instrucs: Size of the target instructions. + :type size_instrucs: int + + :return: Intructions + :rtype: list + """ + + instrucs = [] + instrucs.append(currentProgram.getListing().getInstructionAt(addr)) + + # Generate instructions to have the number of instructions than the target instructions + for k in range(size_instrucs-1): + if instrucs[k] != None: + instrucs.append(instrucs[k].getNext()) + else: + break + + return instrucs + + def getSimiFunc(self, bloc, target_instrucs, target_hexa, target_pcode, potential_addrs_funcs_after_regs): + """ + Get the similitude result of a function. + + :param bloc: Function to get similitude result + :type bloc: Function + :param target_hexa: Hexadecimal target to compare with. + :type target_hexa: str + :param target_instrucs: Instructions targets to compare with. + :type target_instrucs: list + :param target_pcode: Pcode target to compare with. + :type target_pcode: list.list + :param potential_addrs_funcs_after_regs: Addresses functions to potential targets. + :type potential_addrs_funcs_after_regs: list + + :return: Triplet to the potential found function with the similarity result + :rtype: Function, int, Address + """ + + # Get the hexa and instruc of the current basic bloc + if potential_addrs_funcs_after_regs == None: + instrucs = self.getInstrucs(bloc.getEntryPoint(), len(target_instrucs)) + hexa = self.getHexaBasicBloc(bloc.getEntryPoint(), target_hexa) + pcode = self.getPcodeMnemo(currentProgram.getListing().getInstructionAt(bloc.getEntryPoint()), len(target_pcode)) + else: + instrucs = self.getInstrucs(bloc, len(target_instrucs)) + hexa = self.getHexaBasicBloc(bloc, target_hexa) + pcode = self.getPcodeMnemo(currentProgram.getListing().getInstructionAt(bloc), len(target_pcode)) + + if None in instrucs: + return (None, 0, None) + + # Generate similitude for the target hexa and instructions + sim, res_sim = Tools().customSimilarityPercentage(hexa, target_hexa, instrucs,target_instrucs, pcode, target_pcode) + if sim: + if potential_addrs_funcs_after_regs == None: + return (bloc, res_sim, None) + else: + return (currentProgram.getListing().getFunctionContaining(bloc), res_sim, bloc) + + else: + return (None, 0, None) + + def getBestSimiFunc(self, entry_point, target_instrucs, target_hexa, target_pcode, potential_addrs_funcs_after_regs): + """ + Get the similitude result of a function. + + :param entry_point: Address of the entry point of the function + :type entry_point: Address + :param target_hexa: Hexadecimal target to compare with. + :type target_hexa: str + :param target_instrucs: Instructions targets to compare with. + :type target_instrucs: list + :param target_pcode: Pcode target to compare with. + :type target_pcode: list.list + :param potential_addrs_funcs_after_regs: Addresses functions to potential targets. + :type potential_addrs_funcs_after_regs: list + + :return: Triplet to the potential found function with the similarity result + :rtype: Function, int, Address + """ + + similitudes = [] + ins = currentProgram.getListing().getInstructionAt(entry_point) + if ins == None: + return None, 0, None + + func = currentProgram.getListing().getFunctionContaining(ins.getAddress()) + while ins != None and currentProgram.getListing().getFunctionContaining(ins.getAddress()) != None and currentProgram.getListing().getFunctionContaining(ins.getAddress()).getEntryPoint() == func.getEntryPoint(): + instrucs = [] + # Get the instrucs, hexa and pcode starting at the current ins + instrucs = self.getInstrucs(ins.getAddress(), len(target_instrucs)) + hexa = self.getHexaBasicBloc(ins.getAddress(), target_hexa) + pcode = self.getPcodeMnemo(currentProgram.getListing().getInstructionAt(ins.getAddress()), len(target_pcode)) + + if None in instrucs: + break + + # Generate similitude for the target hexa, instructions and pcode + sim, res_sim = Tools().customSimilarityPercentage(hexa, target_hexa, instrucs, target_instrucs, pcode, target_pcode) + if sim: + if potential_addrs_funcs_after_regs == None: + if res_sim == 0: + return (func, res_sim, None) + similitudes.append((func, res_sim, None)) + else: + if res_sim == 0: + return (currentProgram.getListing().getFunctionContaining(entry_point), res_sim, entry_point) + similitudes.append((currentProgram.getListing().getFunctionContaining(entry_point), res_sim, entry_point)) + + ins = ins.getNext() + + if len(similitudes) == 0: + return None, 0, None + else: + similitudes.sort(key=lambda x: x[1]) + + return similitudes[0] + + def getHexaBasicBloc(self, addr, target_hexa): + """ + Get the following hexa beginning with an address. + + :param addr: Address as the entry point + :type addr: Address + :param target_hexa: The target hexa. + :type target_hexa: str + + :return: Hexadecimals + :rtype: str + """ + + # Get the Bytes of all of the instructions of the Function + hexa_data = [] + k = 0 # each instruction + l = 0 # length + + instruc = currentProgram.getListing().getInstructionAt(addr) + size_hexa_data = len(target_hexa) + + while l < size_hexa_data and instruc != None: + # Get bytes and convert it to the good format + bytes = instruc.getBytes() + hexa_data.append("".join([format(byte & 0xff, "02x") for byte in bytes])) + + instruc = instruc.getNext() + l = l + len(hexa_data[k]) + k = k + 1 + + hexa_data_final = "".join(hexa_data) + + return hexa_data_final + + def getData(self, function): + """ + Get the data used by a function. + + :param function: Function containing infos for the target function + :type function: FunctionInfo + + :return: References of each data and their addresses + :rtype: (list of Reference, list of Address) + """ + + dataIterator = currentProgram.getListing().getData(True) + complet_zeph_reg = function.getZephReg() + base_addr, _ = Tools().splitAddress(complet_zeph_reg) + + ref_data = [] # List of references to data that we found + addrs = [] # List of addr that we found + + for data in dataIterator: + if data is not None and data.getDefaultValueRepresentation() == base_addr + "h": + ref_data.append(data.getReferenceIteratorTo()) + elif data is not None and data.getDefaultValueRepresentation() == complet_zeph_reg + "h": + for ref in data.getReferenceIteratorTo(): + addr = ref.getFromAddress() + + addrs.append(addr) + + return (ref_data, addrs) + + def getAddrsFuncsFromRefData(self, function, ref_data): + """ + Get the addresses from the Reference data + + :param function: Function containing infos for the target function + :type function: FunctionInfo + :param ref_data: Function containing infos for the target function + :type ref_data: FunctionInfo + + :return: References of each data and their addresses + :rtype: (list of Reference, list of Address) + """ + + addrs = [] # List of func that we found + _, offset = Tools().splitAddress(function.getZephReg()) + + for refs in ref_data: + for ref in refs: + addr = ref.getFromAddress() + func = currentProgram.getListing().getFunctionContaining(addr) + instruc = currentProgram.getListing().getInstructionAt(addr) + + if func is not None and instruc is not None and instruc.getMnemonicString() == "ldr": + register = instruc.getResultObjects()[0] + instruc = instruc.getNext() + + while func.getBody().contains(instruc.getAddress()): + if instruc.getMnemonicString() == "ldr.w" and self.checkInputObjects(instruc.getInputObjects(), register, offset): + addrs.append(instruc.getAddress()) + + instruc = instruc.getNext() + return addrs + + def checkInputObjects(self, inputs_objs, register, offset): + """ + Check the register and the offset of inputs objects from an instruction + + :param inputs_objs: Inputs objects + :type inputs_objs: list of Object + :param register: Register used by the instruction + :type register: Object + :param offset: Offset to target + :type offset: str + + :return: If the register and the offset is used by the current input objects from the instruction + :rtype: bool + """ + + if len(inputs_objs) == 2: + if inputs_objs[0] == register and inputs_objs[1].toString() == offset: + return True + elif inputs_objs[0].toString() == offset and inputs_objs[1] == register: + return True + + return False + + def run(self, path): + # File path for the JSON table you want to use + with open(path, 'r') as json_file: + # Load JSON data from the file + patterns = json.load(json_file) + + # THRESHOLD to put some functions in the force mode + threshold = 10 + + # Create all of the FunctionTargetInfo objects + functions_to_target = [] + for pattern in patterns: + functions_to_target.append(FunctionTargetInfo(pattern['func'], pattern['hexa'].replace(" ", ""), pattern['instr'].split(';'), pattern['regs'], [x.split(',') for x in pattern['pcode'].split(';')])) + + functions_to_target_retry = [] + force = False + while 1: + if force == True: + print("FORCE MODE ACTIVATED ! (" + str(len(functions_to_target_retry)) + " functions left)") + functions_to_target = functions_to_target_retry + for func in functions_to_target: + # Separate the 2 cases (have a register or not) + if func.getZephReg() != "null": + # Get all the potential funcs found by using the specific zephyr register + ref_data, addrs_ref = self.getData(func) + potential_addrs_funcs = addrs_ref + self.getAddrsFuncsFromRefData(func, ref_data) + + similitudes = self.findFunctionByHexaAndInstrucAndPcode(func.getHexa(), func.getInstruc(), func.getPcode(), potential_addrs_funcs, force) + + else: + similitudes = self.findFunctionByHexaAndInstrucAndPcode(func.getHexa(), func.getInstruc(), func.getPcode(), None, force) + + if type(similitudes) != list: + if similitudes[0] == None: + if force == False: + functions_to_target_retry.append(func) + continue + print("No \"" + func.getName() + "\" function found matching the potentials targets \n") + else: + if force == False and similitudes[1] > threshold: + functions_to_target_retry.append(func) + continue + print("Function target found: " + similitudes[0].getName() + " (" + func.getName() + ") with a difference of " + str(similitudes[1])) + if similitudes[2] == None: + print("--> Entry Point: " + str(similitudes[0].getEntryPoint()) + "\n") + else: + print("--> Load zephyr register: " + str(similitudes[2]) + "\n") + else: + # If we found multiple candidates, we have also try in force mode + if force == False: + functions_to_target_retry.append(func) + print("Functions target found for " + func.getName() + " with the same best difference " + str(similitudes[1][0][1]) + ":") + print(str(similitudes[0]) + " candidates:") + for fun in similitudes[1]: + print(fun[0]) + print("") + + if force == False and len(functions_to_target_retry) != 0: + threshold = 50 + force = True + else: + break + +if __name__ == "__main__": + path = os.environ.get('TABLE') + SearchBased().run(path) diff --git a/ghidra/tables/table_broadcom.json b/ghidra/tables/table_broadcom.json new file mode 100644 index 0000000..744f735 --- /dev/null +++ b/ghidra/tables/table_broadcom.json @@ -0,0 +1,101 @@ +[ + { + "func": "utils_memcpy_8", + "instr": "push,1,(4194304:r4);cmp,2,(512:r2),(16384:0x20)", + "hexa": "2de9f003 202a", + "diff": "0", + "regs": "null", + "specs": "null", + "pcode": "20,3,20,3,20,3,20,3,20,3,20,3,20,19;16,23,20,13,11,1,1,1,1" + }, + { + "func": "__rt_memcpy", + "instr": "cmp,2,(512:r2),(16384:0x3);null;ands,3,(512:r12),(512:r0),(4194304:0x3);null;ldrb.w,2,(512:r3),(4194304:r1)", + "hexa": "032a 40f23080 10f0030c 00f01580 11f8013b", + "diff": "1", + "regs": "null", + "specs": "null", + "pcode": "16,23,20,13,11,1,1,1,1;null;1,1,27,13,11,1,1;null;1,19,2,17" + }, + { + "func": "btclk_GetNatClk_clkpclk", + "instr": "ldr,2,(512:r2),(4202496:r1);str,2,(8704:r2),(4194304:r0)", + "hexa": "4a68 0260", + "diff": "1", + "regs": "null", + "specs": "null", + "pcode": "19,2;19,3" + }, + { + "func": "btclk_Convert_clkpclk_us", + "instr": "ldr,2,(512:r0),(4194304:r0);movw,2,(512:r2),(16384:0x271)", + "hexa": "0068 40f27122", + "diff": "1", + "regs": "null", + "specs": "null", + "pcode": "19,2;17,13,11" + }, + { + "func": "bcsulp_setupWhitening", + "instr": "orr,3,(512:r2),(512:r2),(4194304:0x300000)", + "hexa": "42f44012", + "diff": "0", + "regs": "null", + "specs": "null", + "pcode": "1,30,63,28,1,1,13,11" + }, + { + "func": "lculp_createAccessAddress", + "instr": "push,1,(4194304:r4);bl,1,(8256:0001ca3e);null;null", + "hexa": "10b5 a9f77af9 c0f30e00 c0f30011", + "diff": "1", + "regs": "null", + "specs": "null", + "pcode": "1,20,3,20,3,1;28,1,1,7;1,1,19,20,29,20,30;1,1,19,20,29,20,30" + }, + { + "func": "scanTaskRxPktUpdate", + "instr": "null", + "hexa": "c0f34100", + "diff": "1", + "regs": "null", + "specs": "null", + "pcode": "1,1,19,20,29,20,30" + }, + { + "func": "advTaskProgHw", + "instr": "orr,3,(512:r0),(512:r0),(4194304:0x400040)", + "hexa": "40f04010", + "diff": "1", + "regs": "null", + "specs": "null", + "pcode": "1,1,28,1,1,13,11" + }, + { + "func": "osapi_waitEvent", + "instr": "strd,3,(512:r3),(512:r5),(4202496:sp);mov,2,(512:r4),(512:r1);add,2,(512:r3),(4194304:sp)", + "hexa": "cde90035 0c46 01ab", + "diff": "1", + "regs": "null", + "specs": "null", + "pcode": "19,1,3,19,3;1;19,1;" + }, + { + "func": "lm_getRawRssiWithTaskId", + "instr": "add,2,(512:r0),(512:r1);sxtb,2,(512:r0),(512:r0);bx,1,(512:lr)", + "hexa": "0844 40b2 7047", + "diff": "0", + "regs": "null", + "specs": "null", + "pcode": "19;63,18;27,12,1,27,10" + }, + { + "func": "connTaskSlotInt", + "instr": "push,1,(4194304:r4);mov,2,(512:r4),(512:r0);ldr,2,(512:r5),(4194304:r0);ldrb.w,2,(512:r0),(4194304:r0)", + "hexa": "70b5 0446 056d 90f88100", + "diff": "1", + "regs": "null", + "specs": "null", + "pcode": "1,20,3,20,3,20,3,20,3,1;1;19,2;19,2,17" + } +] diff --git a/ghidra/tables/table_nrf51.json b/ghidra/tables/table_nrf51.json new file mode 100644 index 0000000..3e8b74d --- /dev/null +++ b/ghidra/tables/table_nrf51.json @@ -0,0 +1,65 @@ +[ + { + "func": "radio_interrupt", + "instr": "push,1,(4194304:r3);ldr,2,(512:r0),(8320:000123fc);ldr,2,(512:r1),(8320:00012408)", + "hexa": "f8b5 6a48 6c49", + "diff": "0", + "regs": "40001540", + "specs": "null", + "pcode": "1,20,3,20,3,20,3,20,3,20,3,20,3,1;1;1" + }, + { + "func": "set_channel_map", + "instr": "ldrb,2,(512:r3),(4194304:r1);strb,2,(512:r3),(4194304:r0);bx,1,(512:lr)", + "hexa": "0b79 0371 7047", + "diff": "0", + "regs": "null", + "specs": "null", + "pcode": "19,2,17;19,63,3;27,12,1,27,10" + }, + { + "func": "set_crc_init", + "instr": "lsls,3,(512:r0),(512:r0),(16384:0x8);null;lsrs,3,(512:r0),(512:r0),(16384:0x8);null;bx,1,(512:lr)", + "hexa": "0002 fd49 000a c863 7047", + "diff": "0", + "regs": "null", + "specs": "null", + "pcode": "20,29,27,11,39,12,12,39,40,29,13,11,1,1,1;null;1,20,30,27,11,39,12,12,39,40,30,13,11,1,1,1;null;27,12,1,27,10" + }, + { + "func": "set_bd_address", + "instr": "ldrb,2,(512:r2),(4194304:r1);strb,2,(512:r2),(4194304:r0);bx,1,(512:lr)", + "hexa": "4a79 4271 7047", + "diff": "0", + "regs": "null", + "specs": "null", + "pcode": "19,2,17;19,63,3;27,12,1,27,10" + }, + { + "func": "init_connection", + "instr": "adds,2,(512:r6),(16384:0x60);adds,2,(512:r4),(16384:0x74)", + "hexa": "6036 7434", + "diff": "1", + "regs": "null", + "specs": "null", + "pcode": "21,22,19,13,11,1,1,1,1;21,22,19,13,11,1,1,1,1" + }, + { + "func": "init_softdevice", + "instr": "svc,1,(16384:0x10)", + "hexa": "10df", + "diff": "0", + "regs": "null", + "specs": "null", + "pcode": "9" + }, + { + "func": "wait_softdevice", + "instr": "svc,1,(16384:0x48)", + "hexa": "48df", + "diff": "0", + "regs": "null", + "specs": "null", + "pcode": "9" + } +] diff --git a/ghidra/tables/table_nrf52.json b/ghidra/tables/table_nrf52.json new file mode 100644 index 0000000..ab9fb39 --- /dev/null +++ b/ghidra/tables/table_nrf52.json @@ -0,0 +1,218 @@ +[ + { + "func": "lll_scan_prepare_connect_req", + "instr": "push,1,(4194304:r4);ldrb.w,2,(512:r5),(4202496:sp)", + "hexa": "2de9f041 9df81c50", + "diff": "0", + "regs": "null", + "specs": "lll_scan,lll_scan_aux", + "pcode": "20,3,20,3,20,3,20,3,20,3,20,3,20,19;19,2,17" + }, + { + "func": "lll_conn_prepare_pdu_tx", + "instr": "push,1,(4194304:r4);mov,2,(512:r4),(512:r0);sub,2,(512:sp),(4194304:0xc);add,2,(512:r2),(4194304:sp)", + "hexa": "2de9f043 0446 83b0 01aa", + "diff": "0", + "regs": "null", + "specs": "null", + "pcode": "20,3,20,3,20,3,20,3,20,3,20,3,20,3,20,19;1;1,20;19,1" + }, + { + "func": "conn_isr_tx", + "instr": "push,1,(4194304:r3);mov,2,(512:r4),(512:r0);null;movs,2,(512:r0),(16384:0x96)", + "hexa": "38b5 0446 fef728fd 9620", + "diff": "0", + "regs": "null", + "specs": "lll_conn,lll_central & param of the radio_isr_set function", + "pcode": "1,20,3,20,3,20,3,20,3,1;1;null;1,13,11,1,1" + }, + { + "func": "ull_central_setup", + "instr": "push,1,(4194304:r4);null;sub,2,(512:sp),(4194304:0x3c);mov,2,(512:r4),(512:r0)", + "hexa": "2de9f04f 8046 8fb0 0446", + "diff": "1", + "regs": "null", + "specs": "ull_conn", + "pcode": "20,3,20,3,20,3,20,3,20,3,20,3,20,3,20,3,20,3,20,19;null;1,20;1" + }, + { + "func": "ull_central_cleanup", + "instr": "ldr,2,(512:r3),(4194304:r0);push,1,(4194304:r4);ldr,2,(512:r5),(4194304:r3);ldr,2,(512:r4),(4194304:r5)", + "hexa": "8368 70b5 1d68 2c6a", + "diff": "0", + "regs": "null", + "specs": "ull", + "pcode": "19,2;1,20,3,20,3,20,3,20,3,1;19,2;19,2" + }, + { + "func": "ull_peripheral_setup", + "instr": "push,1,(4194304:r4);ldr,2,(512:r3),(4194304:r1);null;ldr,2,(512:r3),(4194304:r3)", + "hexa": "2de9f04f 0b68 d2f800b0 1b68", + "diff": "1", + "regs": "null", + "specs": "null", + "pcode": "20,3,20,3,20,3,20,3,20,3,20,3,20,3,20,3,20,3,20,19;19,2;null;19,2" + }, + { + "func": "bt_enable_raw", + "instr": "null;null;ldr,2,(512:r3),(4202496:r3)", + "hexa": "044b 054a 1b68", + "diff": "0", + "regs": "null", + "specs": "hci_raw,bt_hci,bluetooth,bt_h4", + "pcode": "null;null;19,2" + }, + { + "func": "bt_recv", + "instr": "push,1,(4194304:r4);null;ldrb,2,(512:r3),(4202496:r3)", + "hexa": "10b5 0d4b 1b78", + "diff": "1", + "regs": "null", + "specs": "hci_ecc,hci_raw,hci_driver", + "pcode": "1,20,3,20,3,1;null;19,2,17" + }, + { + "func": "z_arm_reset", + "instr": "null;movs,2,(512:r0),(16384:0x20);null;null;mov.w,2,(512:r1),(4194304:0x820);adds,3,(512:r0),(512:r0),(512:r1)", + "hexa": "14f0aefd 2020 80f31188 0848 4ff40261 4018", + "diff": "0", + "regs": "null", + "specs": "null", + "pcode": "null;1,13,11,1,1;null;null;1,30,63,1,13,11;21,22,19,13,11,1,1,1,1" + }, + { + "func": "z_arm_configure_static_mpu_regions", + "instr": "null;null;null;movs,2,(512:r1),(16384:0x1)", + "hexa": "024b 034a 0348 0121", + "diff": "0", + "regs": "null", + "specs": "null", + "pcode": "null;null;null;1,13,11,1,1" + }, + { + "func": "memcpy", + "instr": "push,1,(4194304:r4);subs,3,(512:r3),(512:r0),(16384:0x1);add,2,(512:r2),(512:r1)", + "hexa": "10b5 431e 0a44", + "diff": "0", + "regs": "null", + "specs": "null", + "pcode": "1,20,3,20,3,1;16,23,20,13,11,1,1,1,1;19" + }, + { + "func": "radio_is_done", + "instr": "null;ldr.w,2,(512:r0),(4202496:[r3, 0x550]);subs,2,(512:r0),(16384:0x0)", + "hexa": "034b d3f80c01 0038 18bf 0120 7047", + "diff": "0", + "regs": "4000110c", + "specs": "lll_*", + "pcode": "null;19,2;16,23,20,13,11,1,1,1,1" + }, + { + "func": "radio_rssi_get", + "instr": "null;ldr.w,2,(512:r0),(4202496:r3);bx,1,(512:lr)", + "hexa": "014b d3f84805 7047", + "diff": "0", + "regs": "40001548", + "specs": "lll_*", + "pcode": "null;19,2;27,12,1,27,10" + }, + { + "func": "radio_crc_is_valid", + "instr": "null;ldr.w,2,(512:r0),(4202496:r3);subs,2,(512:r0),(16384:0x0)", + "hexa": "034b d3f80004 0038", + "diff": "0", + "regs": "40001400", + "specs": "lll_*", + "pcode": "null;19,2;16,23,20,13,11,1,1,1,1" + }, + { + "func": "lll_adv_scan_req_check", + "instr": "push,1,(4194304:r4);ldrb.w,2,(512:r12),(4194304:r0);ldrb.w,2,(512:r8),(4202496:sp)", + "hexa": "2de9f041 90f809c0 9df81880", + "diff": "1", + "regs": "null", + "specs": "lll_adv_aux, lll_adv", + "pcode": "20,3,20,3,20,3,20,3,20,3,20,3,20,19;19,2,17;19,2,17" + }, + { + "func": "advertiser_isr_rx", + "instr": "push,1,(4194304:r4);sub,2,(512:sp),(4194304:0x28);mov,2,(512:r4),(512:r0)", + "hexa": "2de9f047 8ab0 0446", + "diff": "0", + "regs": "null", + "specs": "& radio_is_done", + "pcode": "20,3,20,3,20,3,20,3,20,3,20,3,20,3,20,3,20,19;1,20;1" + }, + { + "func": "scan_isr_rx", + "instr": "push,1,(4194304:r4);sub,2,(512:sp),(4194304:0x24);mov,2,(512:r4),(512:r0);bl,1,(8256:00012560)", + "hexa": "2de9f04f 89b0 0446 10f0c2f9", + "diff": "0", + "regs": "null", + "specs": "& radio_is_done", + "pcode": "20,3,20,3,20,3,20,3,20,3,20,3,20,3,20,3,20,3,20,19;1,20;1;28,1,1,7;63,17" + }, + { + "func": "conn_isr_rx", + "instr": "push,1,(4194304:r4);sub,2,(512:sp),(4194304:0x14);mov,2,(512:r4),(512:r0);bl,1,(8256:00012560)", + "hexa": "2de9f04f 85b0 0446 00f094ff", + "diff": "0", + "regs": "null", + "specs": "& radio_is_done", + "pcode": "20,3,20,3,20,3,20,3,20,3,20,3,20,3,20,3,20,3,20,19;1,20;1;28,1,1,7" + }, + { + "func": "test_isr_rx", + "instr": "push,1,(4194304:r4);bl,1,(8256:00012560);uxtb,2,(512:r0),(512:r0)", + "hexa": "10b5 00f00bfb c0b2", + "diff": "0", + "regs": "null", + "specs": "& radio_is_done", + "pcode": "1,20,3,20,3,1;28,1,1,7;63,17" + }, + { + "func": "bt_send", + "instr": "null;ldr,2,(512:r3),(4202496:r3);ldr,2,(512:r3),(4194304:r3);bx,1,(512:r3)", + "hexa": "014b 1b68 1b69 1847", + "diff": "1", + "regs": "null", + "specs": "hci_raw, bt_hci, bluetooth, bt_h4, conn, hci_core", + "pcode": "null;19,2;19,2;27,12,1,27,6" + }, + { + "func": "bt_hci_evt_create", + "instr": "push,1,(4194304:r4);mov.w,2,(512:r2),(4194304:0xffffffff);mov,2,(512:r5),(512:r1);mov.w,2,(512:r3),(4194304:0xffffffff);movs,2,(512:r1),(16384:0x0)", + "hexa": "70b5 4ff0ff32 0d46 4ff0ff33 0021", + "diff": "0", + "regs": "null", + "specs": "hci_common & net_buf_simple_add", + "pcode": "1,20,3,20,3,20,3,20,3,1;1,1,1,13,11;1;1,1,1,13,11;1,13,11,1,1" + }, + { + "func": "net_buf_simple_add", + "instr": "ldrh,2,(512:r3),(4194304:r0);ldr,2,(512:r2),(4194304:r0);add,2,(512:r1),(512:r3);strh,2,(512:r1),(4194304:r0);adds,3,(512:r0),(512:r2),(512:r3);bx,1,(512:lr)", + "hexa": "8388 0268 1944 8180 d018 7047", + "diff": "0", + "regs": "null", + "specs": "null", + "pcode": "19,2,17;19,2;19;19,63,3;21,22,19,13,11,1,1,1,1;27,12,1,27,10" + }, + { + "func": "ll_addr_get", + "instr": "cmp,2,(512:r0),(16384:0x1);mov,2,(512:r3),(512:r0)", + "hexa": "0128 0346", + "diff": "1", + "regs": "null", + "specs": "ll_addr, ull_scan, ull_adv, ull_central", + "pcode": "16,23,20,13,11,1,1,1,1;1" + }, + { + "func": "idle", + "instr": "push,1,(4194304:r3);null;mov.w,2,(512:r2),(4194304:0x20)", + "hexa": "08b5 094c 4ff02002", + "diff": "0", + "regs": "null", + "specs": "null", + "pcode": "1,20,3,20,3,1;null;1,1,1,13,11" + } +]