├── .gitignore ├── LICENSE ├── README.md ├── install.sh ├── shannon_debug_traces.py ├── shannon_funcs.py ├── shannon_generic.py ├── shannon_indirect_xref.py ├── shannon_load.py ├── shannon_mpu.py ├── shannon_names.py ├── shannon_pal_reconstructor.py ├── shannon_postprocess.py ├── shannon_scatterload.py ├── shannon_structs.py └── sig ├── ARM_Compiler_3.1_b794.sig ├── ARM_Compiler_4.0_b591.sig ├── ARM_Compiler_4.1_b894.sig ├── ARM_Compiler_5.01_b113.sig ├── ARM_Compiler_5.01_b94.sig ├── ARM_Compiler_5.03_b24.sig ├── ARM_Compiler_5.03_b76.sig ├── ARM_Compiler_5.04_b27.sig ├── ARM_Compiler_5.04u1_b49.sig ├── ARM_Compiler_5.04u2_b82.sig ├── ARM_Compiler_5.05u1_b106.sig ├── ARM_Compiler_5.06u1_b61.sig ├── ARM_Compiler_5.06u3_b300.sig ├── ARM_Compiler_5.06u4_b422.sig ├── ARM_Compiler_5.06u5_b528.sig ├── ARM_Compiler_5.06u6_b750.sig └── ARM_Compiler_5.06u7_b960.sig /.gitignore: -------------------------------------------------------------------------------- 1 | testing/ 2 | misc/ 3 | .vscode/ 4 | core -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024-2025 Alexander Pick 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Shannon Baseband Loader for IDA Pro 2 | 3 | This is a simple firmware loader plugin to load Samsung Exynos "Shannon" modem images in [IDA Pro](https://hex-rays.com/ida-pro/) or [IDA Home ARM](https://hex-rays.com/ida-home/). This loader is designed to perform the most important taks to load a Shannon image, furthermore it should be easy to understand and customize. 4 | 5 | The loader should work with most Samsung Exynos modem images containing a TOC header including crash dumps. Compatible images can be found e.g. in updates for Exynos based phones. The typical file name is `modem.bin`. Sometimes the images are compressed using lz4. Uncompress them before loading, using lz4 utility present on most Linux distros. 6 | 7 | The loader was tested with a larger set of images from ancient (e.g. G8700, S7) to new (e.g. S22, S24). Loading recently build images works fine including task identification. 8 | 9 | # How To Use This Loader 10 | 11 | To use the loader just install `shannon_load.py` inside your [IDA Pro](https://hex-rays.com/ida-pro/) or [IDA Home ARM](https://hex-rays.com/ida-home/) loader folder and the other python files into the IDA python folder. `install.sh` will assist you with this task, if you want to do it manually please take a look inside. For [IDA Pro](https://hex-rays.com/ida-pro/) 9.0, 8.3 and 8.4 and [IDA Home ARM](https://hex-rays.com/ida-home/) run the installer with one of the following commandline options: 12 | 13 | ``` 14 | -d specify IDA installation directory 15 | -r run IDA after installation 16 | -a auto mode, find IDA installation dir automatically 17 | -t test mode, combines -a -r and enabled logging in IDA 18 | -h print help 19 | ``` 20 | 21 | Once installed open a `modem.bin` file extracted from an OTA update in IDA. The loader should detect the TOC format and load the image accordingly. The postprocessor script will add additional segment information after the initial analysis has finished. Adding the segments right from the start will confuse and slow down the analysis process. 22 | 23 | The postprocessing module does most of the magic and it will take a bit to run, please be patient. All steps the post processor performs are in the individual files copied to `/python/`. You can use them as individual python modules if needed. See the post processor for details. A complete analysis of a modem image takes about 10 minutes to complete on average hardware. 24 | 25 | ## How The Loader Works 26 | 27 | The TOC header is a simple structure the loader will read to create a database of the file with proper aligned segments and entry points. For more insights just look at the code contained in this reponsitory. 28 | 29 | The loader will recognize a TOC based Shannon modem binary and load it. After basic processing and initial auto analysis, exessive post-processing is performed. The post processing workflow will perform the following tasks: 30 | 31 | * restore debug trace entries and structures 32 | * rename functions based on various string references 33 | * rename supplementary services functions (`ss_*`) based on debug log functions 34 | * identify the hardware and mpu init function 35 | * restore the mpu table and map memory accordingly 36 | * identify MMU related mnemonics and label them 37 | * identify the scatter loader 38 | * perform scatter loading and decompression ([LZ77-like](https://developer.arm.com/documentation/dui0474/j/linker-optimization-features/how-compression-is-applied) compression scheme) 39 | * find important platform abstraction layer functions 40 | * identify and label all task init functions 41 | 42 | After that your `idb` or `i64` should be ready to go so you can focus on reverse engineering the modem. 43 | 44 | # About Samsung Shannon 45 | 46 | ## History 47 | 48 | Shannon is an IC series by Samsung LSI. Most notable product in the series is the Shannon baseband processor. Shannon is the name of a range of ICs and not the name of the modem itself. You might find other Shannon ICs made by Samsung. A typical set of Shannon ICs consists of a baseband, RF transceiver, powermanagement and a envelope tracking IC. All of them have different part numbers. E.g. Shannon baseband is 5300, the RF transceiver is 5510, PMIC is 5200 and so on. So don't get confused, these are all different parts. You won't find much about Shannon on Samsungs website, they refer to the baseband as [Exynos Modem](https://semiconductor.samsung.com/emea/processor/modem/) instead. 49 | 50 | Nowadays the baseband IC is most of the time integrated into the Exynos SOC directly. For special configs, standalone baseband ICs are still sold. User of the stand alone solution are i.e. automotive or IoT vendors. 51 | 52 | Historically the Shannon baseband was at least developed since 2005. During the early days it went under the name of CMC. The CMC220 was the first LTE version of the IC which launched in 2011, shortly after the first Exynos processor. Samsung is still celebrating it in some of their [company presentations](https://images.samsung.com/is/content/samsung/assets/global/ir/docs/business-introduction/Samsung_Investor_Presentation_SLSI_2020_v1.pdf). 53 | 54 | The name "Shannon" is a homage to [Claude Shannon](https://en.wikipedia.org/wiki/Claude_Shannon), father of the information theory and the information age. There are other references to important scientists in the field which can be found in Samsung hard and firmware. I.e. some radio related parts are called Marconi, as reference to [Guglielmo Marconi](https://en.wikipedia.org/wiki/Guglielmo_Marconi) a pioneer in the field of radio transmission and inventor of the first working antenna. 55 | 56 | ## RTOS 57 | 58 | The RTOS used by Samsung for Shannon ICs is called ShannonOS. I believe that ShannonOS is a re-branded Nucleus core. Old versions of CMC even identify it by the original name. The system is build using the ARM RVCT compiler with slowly increasing version numbers. 59 | 60 | On top of the RTOS, Samsung created a Platform Abstraction Layer (PAL) as interface to the lower level functionality operating the hardware. Platform abstraction layer are a common design pattern found in a lot of embedded development projects. Typically the hardware related layer would be called Hardware Abstraction Layer (HAL). In old modems the HAL was never explictly mentioned or labeled as such, new modems (2023 onwards) even have HAL management tasks and a clear seperation. Possibly because the low level / core functionality was taken from Nucleus and never part of the initial inhouse design. 61 | 62 | Inside the PAL, functionality like task management and other higher level management operations are run. The most interesting functionality of the baseband such as packet parsers for GSM/LTE/5G or logging (DM) etc. are run in individual tasks scheduled by the PAL. The loader will identify the individual tasks for you and label them, see `shannon_pal_reconstructor.py` for details. 63 | 64 | If you look at a crash dump or find the respective functionality inside `modem.bin`, you will encounter the following log banner. It gives away some information about the system and processor: 65 | 66 | ``` 67 | =================================================== 68 | DEVELOPMENT PLATFORM 69 | - ARM Emulation Baseboard | Cortex-R7 70 | - Software Build Date : 71 | - Software Builder : 72 | - Compiler Version : ARM RVCT 50.6 [Build 422] 73 | Platform Abstraction Layer (PAL) Powered by 74 | CP Platform Part 75 | =================================================== 76 | ``` 77 | 78 | The modem image is build with ARM RVCT as shown above. A mapping of the RVCT version to toolchain used [can be found here](https://developer.arm.com/documentation/ka005901/1-0?lang=en&rev=3). Build 422 as shown above maps to Keil MDK 5.22, RVCT/Arm Compiler version 5.06u4. This version of RCVT can be found on the [ARM website here](https://developer.arm.com/downloads/view/ACOMP5?entitled=true&term=rvct&revision=r5p6-04rel1). This loader will detect installed ARM legacy compilers on Linux and ask to set their includes as compiler options for further analysis. Furthermore, multiple premade FLIRT signatures to identify RVCT functions inside the binary are included. 79 | 80 | ## Cortex-R and Cortex-A 81 | 82 | Around the S20 release a few things changed, Shannon basebands were upgraded from Cortex-R to Cortex-A. During this time additional stack checks were introduced as well. But stack cookies seem to be static, given the random values used it's unclear if the developers really understood the reason for this mitigation. 83 | 84 | Another new addition from the core change was the MMU (Memory Management Unit). The MMU would technically provide domain security and advanced management capabilities, while the MPU only offers basic mapping and protection. The advanced MMU features are disabled by setting the translation table to zero and domain security to minus one for a lot of the earlier images. Tensor and recent S22+ images seem to use the MMU much more. ShannonOS uses a continuous address space until today. 85 | 86 | The loader will identify MRC/MCR instructions in the image and comment them for you, see `shannon_mpu.py` for details. 87 | 88 | ## IDA Compatibility And Installation 89 | 90 | Tested with [IDA Pro](https://hex-rays.com/ida-pro/) 9.x (9.0), 8.x (8.3 to 8.4 SP2) as well as [IDA Home ARM](https://hex-rays.com/ida-home/). Might work on versions starting from 7.x using the revamped idapython API. 91 | 92 | Since I work on Linux the `install.sh` is a bash script. If you are using OSX or Windows you can perform the installation manually by copying the files in their respective directories to install the loader manually: 93 | 94 | Script | Installation Directory 95 | |---|---| 96 | shannon_load.py | IDADIR/loaders/ 97 | shannon_postprocess.py | IDADIR/python/ 98 | shannon_pal_reconstructor.py | IDADIR/python/ 99 | shannon_mpu.py | IDADIR/python/ 100 | shannon_scatterload.py | IDADIR/python/ 101 | shannon_generic.py | IDADIR/python/ 102 | shannon_structs.py | IDADIR/python/ 103 | shannon_names.py | IDADIR/python/ 104 | shannon_debug_traces.py | IDADIR/python/ 105 | shannon_funcs.py | IDADIR/python/ 106 | 107 | ## Bugs 108 | 109 | This code is WIP and should be used as such. If you encounter a modem image not proper processed, please fill a bug report so I can fix the loader for it. Make sure to note the exact version of the modem image you are trying to process so I can locate the file. 110 | 111 | ## Motivation 112 | 113 | I took a Shannon related trainings and had to work with Ghidra. After working with IDA for 20 years I feel much more comfortable using it compared to the dragon engine. So I started the work on this loader before another Shannon centric training this year. Doing things from scratch allowed me to get much more into the details of each and every aspect. The loader as it is resembles my idea of how I want it to work and which features I think are needed to do proper research on the bianary. 114 | 115 | ### Highly Recommended Trainings 116 | 117 | For those of you who want to get more into baseband topics I can highly recommend the following trainings. Since these were conference trainings they are not regularly repeated classes. Keep an eye open they are happening again. 118 | 119 | * https://hardwear.io/netherlands-2022/training/reverse-engineering-emulation-dynamic-testing-cellular-baseband-firmware.php 120 | * https://www.offensivecon.org/trainings/2024/exploiting-basebands-and-application-processors.html 121 | 122 | ## Noteable Works 123 | 124 | Some Links to notable work related to the Shannon baseband: 125 | 126 | | Name | URL | 127 | |---|---| 128 | KAIST BaseSpec | https://github.com/SysSec-KAIST/BaseSpec 129 | Comsecuris ShannonRE | https://github.com/Comsecuris/shannonRE 130 | Hernandez Shannon Ghidra Scripts |https://github.com/grant-h/ShannonBaseband 131 | FirmWire | https://github.com/FirmWire/FirmWire 132 | 133 | ## License 134 | 135 | MIT License, have fun. -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Samsung Shannon Modem Loader, Installer Script 4 | # A lean IDA Pro loader for fancy baseband research 5 | # Alexander Pick 2024-2025 6 | 7 | declare -a IDADIRA=("ida-essential-9.0" "ida-pro-9.0" "ida-home-arm-9.0" "idaarm-8.4" "idaarm-8.3" "idapro-8.4" "idapro-8.3") 8 | declare -a IDABINA=("ida" "ida" "ida" "ida64" "ida64" "ida64" "ida") 9 | 10 | echo -e "" 11 | echo "░▀█▀░█▀▄░█▀█░░░█▀▀░█▀▀░█▄█░█░░░░░▀█▀░█▀█░█▀▀░▀█▀░█▀█░█░░░█░░░█▀▀░█▀▄" 12 | echo "░░█░░█░█░█▀█░░░▀▀█░▀▀█░█░█░█░░░░░░█░░█░█░▀▀█░░█░░█▀█░█░░░█░░░█▀▀░█▀▄" 13 | echo "░▀▀▀░▀▀░░▀░▀░░░▀▀▀░▀▀▀░▀░▀░▀▀▀░░░▀▀▀░▀░▀░▀▀▀░░▀░░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀░▀" 14 | echo -e "Install Samsung Shannon Modem Loader, 2024-2025 by Alexander Pick" 15 | echo -e "https://github.com/alexander-pick/shannon_modem_loader\n" 16 | 17 | function help() { 18 | echo -e "commandline options:" 19 | echo -e "-d specify IDA installation directory" 20 | echo -e "-r run IDA after installation" 21 | echo -e "-a auto mode, find IDA installation dir automatically" 22 | echo -e "-t test mode, combines -a -r and enabled logging in IDA" 23 | echo -e "-h print help" 24 | exit 0 25 | } 26 | 27 | function install() { 28 | cp -v shannon_load.py ${IDADIR}/loaders/ 29 | cp -v shannon_postprocess.py ${IDADIR}/python/ 30 | cp -v shannon_pal_reconstructor.py ${IDADIR}/python/ 31 | cp -v shannon_mpu.py ${IDADIR}/python/ 32 | cp -v shannon_scatterload.py ${IDADIR}/python/ 33 | cp -v shannon_generic.py ${IDADIR}/python/ 34 | cp -v shannon_structs.py ${IDADIR}/python/ 35 | cp -v shannon_names.py ${IDADIR}/python/ 36 | cp -v shannon_debug_traces.py ${IDADIR}/python/ 37 | cp -v shannon_funcs.py ${IDADIR}/python/ 38 | cp -v shannon_indirect_xref.py ${IDADIR}/python/ 39 | 40 | cp -v sig/*.sig ${IDADIR}/sig/arm/ 41 | } 42 | 43 | function findIDA() { 44 | if [ -z ${IDADIR} ]; then 45 | 46 | if [ -z ${1} ]; then 47 | 48 | ALEN=${#IDADIRA[@]} 49 | 50 | # use for loop to read all values and indexes 51 | for (( i=0; i<${ALEN}; i++ )); 52 | do 53 | echo -e "[i] checking for ${IDADIRA[$i]}" 54 | if [ -d "${HOME}/${IDADIRA[$i]}" ]; then 55 | 56 | IDADIR="${HOME}/${IDADIRA[$i]}" 57 | IDABIN=${IDABINA[$i]} 58 | 59 | echo -e "[i] found - installing and starting ${IDABINA[$i]}" 60 | break 61 | 62 | fi 63 | done 64 | 65 | else 66 | 67 | IDADIR=${1} 68 | 69 | if [ -z ${2} ]; then 70 | IDABIN=ida 71 | else 72 | IDABIN=${2} 73 | fi 74 | fi 75 | 76 | else 77 | echo -e "[i] IDADIR env is set, overriding commandline selection" 78 | 79 | fi 80 | } 81 | 82 | function run_IDA() { 83 | 84 | IDADEBUGLOG="/tmp/ida_shannon.log" 85 | 86 | rm -f ${IDADEBUGLOG} 87 | touch ${IDADEBUGLOG} 88 | 89 | # -z3003 - for performance issues 90 | 91 | eval "${IDADIR}/${IDABIN} -L${IDADEBUGLOG} &" 92 | tail -f ${IDADEBUGLOG} 93 | 94 | exit 0 95 | } 96 | 97 | while getopts d:b:rath FLAG 98 | do 99 | case "${FLAG}" in 100 | d) IDADIR_OPT=${OPTARG};; 101 | b) IDABIN_OPT=${OPTARG};; 102 | r) IDARUN_OPT=1;; 103 | a) IDAAUTO_OPT=1;; 104 | t) IDATEST_OPT=1;; 105 | h) help;; 106 | esac 107 | done 108 | 109 | if [ -n "${IDATEST_OPT}" ]; then 110 | 111 | echo -e "[i] running in auto test mode" 112 | findIDA 113 | install 114 | echo -e "[i] starting IDA with logging" 115 | 116 | run_IDA 117 | fi 118 | 119 | if [ -z "${IDAAUTO_OPT}" ]; then 120 | if [ -z "${IDADIR_OPT}" ]; then 121 | help 122 | fi 123 | fi 124 | 125 | findIDA ${IDADIR_OPT} ${IDABIN_OPT} 126 | echo -e "[i] IDA at ${IDADIR}/${IDABIN}" 127 | 128 | install 129 | echo -e "[i] installation done" 130 | 131 | if [ -n "${IDARUN_OPT}" ]; then 132 | echo -e "[i] starting IDA" 133 | 134 | run_IDA 135 | fi -------------------------------------------------------------------------------- /shannon_debug_traces.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | 3 | # Samsung Shannon Modem Loader - Debug Traces 4 | # This script is autoamtically scheduled by the loader 5 | # Alexander Pick 2024-2025 6 | 7 | import idc 8 | import ida_bytes 9 | import ida_nalt 10 | import idautils 11 | import idaapi 12 | 13 | import os 14 | import shannon_structs 15 | 16 | # this creates cmts from previously created dbt structures to anote 17 | # functions with their source paths and string refs 18 | def make_dbt_refs(): 19 | 20 | idc.msg("[i] creating dbt references\n") 21 | 22 | struct_id = idc.get_struc_id("dbg_trace") 23 | all_structs = idautils.XrefsTo(struct_id, 0) 24 | 25 | tif = shannon_structs.get_struct(struct_id) 26 | 27 | # moved out of the for loop for performance reasons 28 | str_ptr = shannon_structs.get_offset_by_name(tif, "msg_ptr") 29 | file_ptr = shannon_structs.get_offset_by_name(tif, "file") 30 | 31 | for i in all_structs: 32 | 33 | str_offset = int.from_bytes( 34 | ida_bytes.get_bytes((i.frm+str_ptr), 4), "little") 35 | 36 | # creating is mostly not needed but we do it to make sure it is defined 37 | ida_bytes.create_strlit(str_offset, 0, ida_nalt.STRTYPE_C) 38 | msg_str = idc.get_strlit_contents(str_offset) 39 | 40 | # shannon_generic.DEBUG("[d] make_dbt_refs() %x: %s\n" % (str_offset, msg_str)) 41 | file_offset = int.from_bytes( 42 | ida_bytes.get_bytes((i.frm+file_ptr), 4), "little") 43 | 44 | ida_bytes.create_strlit(file_offset, 0, ida_nalt.STRTYPE_C) 45 | file_str = idc.get_strlit_contents(file_offset) 46 | 47 | # shannon_generic.DEBUG("[d] make_dbt_refs() %x: %s\n" % (file_offset, file_str)) 48 | 49 | # find xref to struct 50 | for xref_dbt in idautils.XrefsTo(i.frm, 0): 51 | if (msg_str != None): 52 | idaapi.set_cmt(xref_dbt.frm, msg_str.decode(), 1) 53 | 54 | func_start = idc.get_func_attr( 55 | xref_dbt.frm, idc.FUNCATTR_START) 56 | 57 | if (func_start != idaapi.BADADDR): 58 | if (file_str != None): 59 | idaapi.set_func_cmt( 60 | func_start, file_str.decode(), 1) 61 | 62 | #for debugging purpose export SHANNON_WORKFLOW="NO" 63 | if (os.environ.get('SHANNON_WORKFLOW') == "NO"): 64 | idc.msg("[i] running debug traces in standalone mode") 65 | make_dbt_refs() -------------------------------------------------------------------------------- /shannon_funcs.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | 3 | # Samsung Shannon Modem Loader - Function Related Functions 4 | # A lean IDA Pro loader for fancy baseband research 5 | # Alexander Pick 2024-2025 6 | 7 | import idc 8 | import idaapi 9 | import ida_segment 10 | import ida_bytes 11 | import ida_ua 12 | import ida_name 13 | import idautils 14 | import ida_idp 15 | import ida_nalt 16 | import ida_funcs 17 | import ida_auto 18 | 19 | import shannon_generic 20 | 21 | # check if name exists already inside the idb 22 | def function_exists(name): 23 | 24 | for addr in idautils.Functions(): 25 | 26 | func_name = idc.get_func_name(addr) 27 | 28 | if func_name == name: 29 | return True 30 | 31 | return False 32 | 33 | # deal with dupes in some modems 34 | def function_find_name(name): 35 | 36 | postfix = 0 37 | orig_name = name 38 | 39 | while (function_exists(name) == True): 40 | if (postfix > 0): 41 | name = orig_name + "_" + str(postfix) 42 | postfix += 1 43 | # sanity check 44 | if (postfix > 42): 45 | break 46 | 47 | # filter some bad chars before returning the string 48 | return name.translate(dict.fromkeys(map(ord, u",~"))) 49 | 50 | # this fixes badly aligned functions in newer BB versions 51 | # some of them get combined by AA because of the stack protection 52 | # ... takes ages to run, therefore not used atm. 53 | def split_functions(): 54 | stack_err = idc.get_name_ea_simple("stack_err") 55 | for xref in idautils.XrefsTo(stack_err, 0): 56 | prev_head = idc.prev_head(xref.frm) 57 | opcode = ida_ua.ua_mnem(prev_head) 58 | 59 | if (opcode == None): 60 | continue 61 | 62 | if ("POP" in opcode): 63 | 64 | func_start = idc.get_func_attr(prev_head, idc.FUNCATTR_START) 65 | func_end = idc.get_func_attr(prev_head, idc.FUNCATTR_END) 66 | 67 | func_o = ida_funcs.get_func(func_start) 68 | 69 | if func_o is not None: 70 | if (func_o.end_ea != prev_head or func_o.end_ea != xref.frm): 71 | # shannon_generic.DEBUG("[d] differing boundaries for function at %x, setting end to %x, was %x\n" % ( 72 | # func_start, prev_head, func_end)) 73 | func_o.end_ea = prev_head 74 | ida_funcs.update_func(func_o) 75 | ida_funcs.reanalyze_function(func_o) 76 | ida_auto.auto_wait() 77 | 78 | # adds not yet defined functions which failed in AA due to stack protection 79 | # functionality for very new BB images. IDA gets confused by the non returning 80 | # tail which ends in an error handler. We use the error handler as reference 81 | # to proper define the functions AA cannot define. 82 | # ... takes ages to run, therefore not used atm. 83 | def scan_main(): 84 | 85 | # do some cleanup first 86 | #split_functions() 87 | 88 | stack_err = idc.get_name_ea_simple("stack_err") 89 | 90 | seg_t = ida_segment.get_segm_by_name("MAIN_file") 91 | 92 | addr = seg_t.start_ea 93 | 94 | while (addr < seg_t.end_ea): 95 | #is offset code?" 96 | if (idc.is_code(idc.get_full_flags(addr))): 97 | 98 | # this simply prevents that we define a check fail as function start 99 | opcode = ida_ua.ua_mnem(addr) 100 | 101 | if (opcode == None): 102 | continue 103 | 104 | if ("BL" in opcode): 105 | operand = idc.get_operand_value(addr, 0) 106 | 107 | if (operand != None): 108 | 109 | if (operand == stack_err): 110 | addr = idc.next_head(addr) 111 | continue 112 | 113 | # offset is part of a function? 114 | if (idc.get_func_attr(addr, idc.FUNCATTR_START) == idaapi.BADADDR): 115 | # check if we can find a stack check fail tail 116 | tail_offset = shannon_generic.func_find_tail(addr, stack_err) 117 | 118 | if (tail_offset != None): 119 | 120 | # avoid defining the tail as own functions 121 | if (addr != tail_offset): 122 | ida_funcs.add_func(addr, tail_offset) 123 | # shannon_generic.DEBUG("[d] found a function %x-%x\n" % (addr, tail_offset)) 124 | 125 | addr = idc.next_head(addr) 126 | 127 | # tries to determain function bondaries if an important function is not defined 128 | # happens due to stack protection which confuses AA 129 | def function_find_boundaries(addr): 130 | 131 | stack_err = idc.get_name_ea_simple("stack_err") 132 | 133 | #shannon_generic.DEBUG("[d] stack_err: %x\n" % stack_err) 134 | 135 | if (stack_err == idaapi.BADADDR): 136 | return False 137 | 138 | while (1): 139 | 140 | #shannon_generic.DEBUG("[d] function_find_boundaries() - cur: %x\n" % addr) 141 | 142 | opcode = ida_ua.ua_mnem(addr) 143 | 144 | # part of a function, undefined or whatever? 145 | if ((idc.get_func_attr(addr, idc.FUNCATTR_START) != idaapi.BADADDR) or not idc.is_code(idc.get_full_flags(addr)) or opcode == None): 146 | 147 | prev_addr = idc.next_head(addr) 148 | 149 | opcode = ida_ua.ua_mnem(prev_addr) 150 | if (opcode == None): 151 | #shannon_generic.DEBUG("[d] no opcode at %x\n" % (prev_addr)) 152 | break 153 | 154 | if ("PUSH" in opcode): 155 | 156 | tail_offset = function_find_tail(prev_addr, stack_err) 157 | 158 | if (tail_offset != None): 159 | 160 | # avoid defining the tail as own functions 161 | if (prev_addr != tail_offset): 162 | ida_funcs.add_func(prev_addr, tail_offset) 163 | idc.msg("[i] created a function %x-%x\n" % (prev_addr, tail_offset)) 164 | return True 165 | else: 166 | # bailout or we run forever 167 | #shannon_generic.DEBUG("[d] not a PUSH, bailout at %x\n" % (prev_addr)) 168 | return False 169 | 170 | addr = idc.prev_head(addr) 171 | 172 | return False 173 | 174 | # finds the end of the function based on the call to the failed stack 175 | # validation 176 | def function_find_tail(addr, stack_err): 177 | 178 | while (1): 179 | 180 | addr = idc.next_head(addr) 181 | opcode = ida_ua.ua_mnem(addr) 182 | 183 | # sanity checks / bailout conditions 184 | 185 | # not defined opcode 186 | if (opcode == None): 187 | break 188 | 189 | # found no code 190 | if (not idc.is_code(idc.get_full_flags(addr))): 191 | break 192 | 193 | # found code belonging to a function 194 | if (not idc.get_func_attr(addr, idc.FUNCATTR_START) == idaapi.BADADDR): 195 | break 196 | 197 | if ("BL" in opcode): 198 | operand = idc.get_operand_value(addr, 0) 199 | 200 | if (operand != None): 201 | 202 | if (operand == stack_err): 203 | paddr = idc.prev_head(addr) 204 | opcode = ida_ua.ua_mnem(paddr) 205 | 206 | if (opcode == None): 207 | break 208 | 209 | if ("POP" in opcode): 210 | return paddr 211 | else: 212 | addr = idc.next_head(addr) 213 | continue 214 | 215 | return None 216 | 217 | # simple mangler 218 | # if not mangled, all the :: and stuff get's lost if setting the name 219 | def mangle_name(name): 220 | 221 | name_len = len(name) 222 | 223 | parts = name.split("::") 224 | 225 | mangled_name = "_Z" 226 | 227 | if(len(parts) > 1): 228 | 229 | first_part = True 230 | 231 | for part in parts: 232 | 233 | if first_part: 234 | mangled_name += "N" 235 | first_part = False 236 | 237 | mangled_name += str(len(part))+str(part) 238 | 239 | else: 240 | 241 | mangled_name += str(name_len) + name 242 | 243 | mangled_name += "E" 244 | 245 | return mangled_name -------------------------------------------------------------------------------- /shannon_generic.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | 3 | # Samsung Shannon Modem Loader - Generic Functions 4 | # A lean IDA Pro loader for fancy baseband research 5 | # Alexander Pick 2024-2025 6 | 7 | import idc 8 | import idaapi 9 | import ida_segment 10 | import ida_bytes 11 | import ida_ua 12 | import ida_name 13 | import idautils 14 | import ida_idp 15 | import ida_nalt 16 | 17 | import shannon_funcs 18 | 19 | # set True for debug mode 20 | is_debug = False 21 | 22 | def DEBUG(msg): 23 | global is_debug 24 | if(is_debug): 25 | idc.msg(msg) 26 | 27 | # adds a memory segment to the database 28 | def add_memory_segment(seg_start, seg_size, seg_name, seg_type="DATA", sparse=True, seg_read=True, seg_write=True, seg_exec=True): 29 | 30 | # sanity check 31 | if (seg_size < 0): 32 | idc.msg("[e] cannot create a segment at %x with negative size %d\n" % ( 33 | seg_start, seg_size)) 34 | return 35 | 36 | if (seg_start == 0xFFFFFFFF): 37 | idc.msg("[e] cannot create a segment at 0xFFFFFFFF\n") 38 | return 39 | 40 | seg_end = seg_start + seg_size 41 | 42 | idc.add_segm_ex(seg_start, seg_end, 0, 1, idaapi.saRel32Bytes, 43 | idaapi.scPub, ida_segment.ADDSEG_SPARSE) 44 | 45 | idc.set_segm_class(seg_start, seg_type) 46 | 47 | 48 | seg_read_perm = ida_segment.SEGPERM_READ 49 | seg_write_perm = ida_segment.SEGPERM_WRITE 50 | seg_exec_perm = ida_segment.SEGPERM_EXEC 51 | 52 | if(not seg_read): 53 | seg_read_perm = 0 54 | 55 | if(not seg_write): 56 | seg_write_perm = 0 57 | 58 | if(not seg_exec): 59 | seg_exec_perm = 0 60 | 61 | 62 | if (seg_type == "CODE"): 63 | idc.set_segm_type(seg_start, idaapi.SEG_CODE) 64 | idc.set_segm_attr(seg_start, idc.SEGATTR_PERM, seg_exec_perm | seg_read_perm | seg_write_perm) 65 | else: 66 | idc.set_segm_type(seg_start, idaapi.SEG_DATA) 67 | idc.set_segm_attr(seg_start, idc.SEGATTR_PERM, seg_read_perm | seg_write_perm) 68 | 69 | idc.set_segm_name(seg_start, seg_name) 70 | 71 | # make sure it is really STT_MM (sparse) 72 | if (sparse): 73 | ida_bytes.change_storage_type(seg_start, seg_end, 1) 74 | 75 | # create a name at offset an validate if the name exists already 76 | def create_name(ea, name): 77 | for xref in idautils.XrefsTo(ea, 0): 78 | func_start = idc.get_func_attr(xref.frm, idc.FUNCATTR_START) 79 | 80 | if (func_start != idaapi.BADADDR): 81 | if (len(name) > 8): 82 | 83 | if ("::" in name): 84 | func_name = shannon_funcs.mangle_name(name) 85 | else: 86 | func_name = shannon_funcs.function_find_name(name) 87 | 88 | ida_name.set_name(func_start, func_name, ida_name.SN_NOCHECK | ida_name.SN_FORCE) 89 | else: 90 | idc.msg("[e] %x: function name too short: %s\n" % 91 | (func_start, name)) 92 | 93 | # helper function to set a name on the target of a LDR or B 94 | def get_ref_set_name(cur_ea, name): 95 | 96 | opcode = ida_ua.ua_mnem(cur_ea) 97 | 98 | # shannon_generic.DEBUG("[d] %x: %s -> %s\n" % (cur_ea, opcode, name)) 99 | if (opcode == "LDR"): 100 | target_ref = idc.get_operand_value(cur_ea, 1) 101 | target = int.from_bytes( 102 | ida_bytes.get_bytes(target_ref, 4), "little") 103 | ida_name.set_name(target, name, ida_name.SN_NOCHECK) 104 | 105 | if (opcode == "B"): 106 | target = idc.get_operand_value(cur_ea, 0) 107 | ida_name.set_name(target, name, ida_name.SN_NOCHECK) 108 | 109 | # resovles a string reference from offset 110 | def resolve_ref(str_addr): 111 | 112 | name = None 113 | 114 | bytes = ida_bytes.get_bytes(str_addr, 4) 115 | 116 | # bailout - hardly happens, only with some rare oddly formated input files 117 | if (bytes == None): 118 | idc.msg("[e] cannot resolve string reference at %x\n" % str_addr) 119 | return None 120 | 121 | str_offset = int.from_bytes(bytes, "little") 122 | # shannon_generic.DEBUG("[d] %x: fallback ref\n" % str_offset) 123 | 124 | name = idc.get_strlit_contents(str_offset) 125 | # yes it's a ref to a string 126 | 127 | if (name != None): 128 | return name 129 | else: 130 | return None 131 | 132 | # get first xref to string from a defined function 133 | def get_first_ref(ea): 134 | for xref in idautils.XrefsTo(ea, 0): 135 | 136 | # validate the target is code 137 | if (idc.is_code(idc.get_full_flags(xref.frm))): 138 | return xref.frm 139 | 140 | return idaapi.BADADDR 141 | 142 | # creates strings which are at least 11 bytes long 143 | def create_long_strings(length=11): 144 | 145 | idc.msg("[i] creating long strings\n") 146 | 147 | strings = idautils.Strings() 148 | 149 | strings.setup(strtypes=[ida_nalt.STRTYPE_C], ignore_instructions=True, minlen=length) 150 | 151 | strings.refresh() 152 | 153 | for s in strings: 154 | # sanity check, is unknown bytes? 155 | if (idc.is_unknown(idc.get_full_flags(s.ea))): 156 | ida_bytes.create_strlit(s.ea, 0, ida_nalt.STRTYPE_TERMCHR) 157 | 158 | # I am using some metrics of the function for analysis instead of pattern since 159 | # we have a limited number of candidates and highly optimized code which will 160 | # most likely keep it's functional characteristics over time 161 | 162 | # The function can be used in 2 ways - for fingerprinting and finding a specific 163 | # function in a small group of candidates - or - for getting all branches, ldr, 164 | # xrefs etc. of a function with a single call 165 | 166 | # function returns: 167 | # 0 - loops -> list with offsets 168 | # 1 - branch -> list with offsets 169 | # 2 - length -> count 170 | # 3 - bb -> count 171 | # 4 - xrefs -> list with offsets 172 | # 5 - ldrs -> list with offsets 173 | 174 | def get_metric(bl_target): 175 | 176 | loops = [] 177 | branch = [] 178 | ldr = [] 179 | xrefs = [] 180 | calls = [] 181 | 182 | length = 0 183 | flow_size = 0 184 | 185 | func_start = idc.get_func_attr(bl_target, idc.FUNCATTR_START) 186 | func_end = idc.get_func_attr(bl_target, idc.FUNCATTR_END) 187 | 188 | #shannon_generic.DEBUG("[d] get_metrics(%x) -> %x %x\n" % (bl_target, func_start, func_end)) 189 | 190 | func_cur = bl_target 191 | 192 | # check that we don't validate the void 193 | if (func_end != idaapi.BADADDR and func_cur != idaapi.BADADDR): 194 | 195 | while (func_cur < func_end): 196 | 197 | length += 1 198 | func_cur = idc.next_head(func_cur) 199 | #shannon_generic.DEBUG("[d] offset %x\n" % func_cur) 200 | 201 | opcode = ida_ua.ua_mnem(func_cur) 202 | 203 | # bailout 204 | if (opcode == None): 205 | #shannon_generic.DEBUG("[d] no opcode at %x\n" % func_cur) 206 | continue 207 | 208 | # we reached the end of the world 209 | if (ida_idp.is_ret_insn(func_cur)): 210 | # disabled, caused issues with IDA auto analysis 211 | #if(func_cur != func_end and idc.next_head(func_cur) != func_end): 212 | 213 | # # # something is off, let's realign, happens in optimzed RT code 214 | # func_o = ida_funcs.get_func(func_start) 215 | 216 | # if func_o is not None: 217 | # shannon_generic.DEBUG("[d] differing boundaries for function at %x -> end was %x should be %x\n" % (func_start, func_end, func_cur)) 218 | # func_o.end_ea = func_cur 219 | # func_end = func_cur 220 | # ida_funcs.update_func(func_o) 221 | # ida_funcs.reanalyze_function(func_o) 222 | # ida_auto.auto_wait() 223 | # ida_funcs.del_func(func_start) 224 | # ida_funcs.add_func(func_start, func_cur) 225 | 226 | break 227 | 228 | # check if a basic block ends or call, if so, it is a branch (exclude return instructions) 229 | if ((ida_idp.is_basic_block_end(func_cur, 0) or ida_idp.is_call_insn(func_cur)) and not ida_idp.is_ret_insn(func_cur)): 230 | 231 | first_operand = idc.get_operand_value(func_cur, 0) 232 | 233 | if (first_operand != idaapi.BADADDR): 234 | 235 | if (ida_idp.is_call_insn(func_cur)): 236 | #shannon_generic.DEBUG("[d] %s call at %x -> %x\n" % (opcode, func_cur, first_operand)) 237 | calls.append(func_cur) 238 | elif (first_operand >= func_start and func_cur > first_operand): 239 | # jump backwards inside function, most likely a loop 240 | #shannon_generic.DEBUG("[d] %s loop at %x -> %x\n" % (opcode, func_cur, first_operand)) 241 | loops.append(func_cur) 242 | else: 243 | #shannon_generic.DEBUG("[d] %s branch at %x -> %x\n" % (opcode, func_cur, first_operand)) 244 | branch.append(func_cur) 245 | 246 | else: 247 | idc.msg("[e] errorous branch target at %x -> %x\n" % 248 | (func_cur, first_operand)) 249 | 250 | if ("LDR" in opcode): 251 | ldr.append(idc.get_operand_value(func_cur, 1)) 252 | 253 | # get basic block count of function 254 | function = idaapi.get_func(func_start) 255 | 256 | if (function): 257 | 258 | flow_chart = idaapi.FlowChart(function) 259 | flow_size = flow_chart.size 260 | 261 | else: 262 | 263 | idc.msg("[e] error getting flowchart for function at %x" % 264 | func_start) 265 | 266 | xrefs = list(idautils.XrefsTo(func_start, 0)) 267 | 268 | return [loops, branch, length, flow_size, xrefs, ldr, calls] 269 | 270 | # print metrics from get_metrics() for dbg reasons 271 | def print_metrics(addr, metrics): 272 | idc.msg("[i] %x: loops: %d branch: %d length: %d basic blocks: %d xrefs: %d ldr: %d calls: %d\n" % ( 273 | addr, len(metrics[0]), len(metrics[1]), metrics[2], metrics[3], len(metrics[4]), len(metrics[5]), len(metrics[6]))) 274 | 275 | # rolled a own txt search based on bin search here, I wasn't happy with 276 | # how ida_search.find_text() works for my usecase - moar performance 277 | def search_text(start_ea, end_ea, text): 278 | 279 | if (idaapi.IDA_SDK_VERSION >= 900): 280 | 281 | #shannon_generic.DEBUG("[d] search version 900\n") 282 | 283 | ea = ida_bytes.find_string(text, start_ea, end_ea, None, ida_nalt.get_default_encoding_idx(ida_nalt.BPU_1B), 284 | ida_bytes.BIN_SEARCH_FORWARD | ida_bytes.BIN_SEARCH_NOBREAK | ida_bytes.BIN_SEARCH_NOSHOW) 285 | 286 | return ea 287 | 288 | else: 289 | 290 | patterns = ida_bytes.compiled_binpat_vec_t() 291 | encoding = ida_nalt.get_default_encoding_idx(ida_nalt.BPU_1B) 292 | 293 | if text.find('"') < 0: 294 | text = '"%s"' % text 295 | 296 | err = ida_bytes.parse_binpat_str(patterns, start_ea, text, 10, encoding) 297 | 298 | if (not err): 299 | 300 | #shannon_generic.DEBUG("[d] searching for %s from %x to %x\n" % (text, start_ea, end_ea)) 301 | 302 | ea = ida_bytes.bin_search(start_ea, end_ea, patterns, ida_bytes.BIN_SEARCH_FORWARD | 303 | ida_bytes.BIN_SEARCH_NOBREAK | ida_bytes.BIN_SEARCH_NOSHOW) 304 | 305 | return ea 306 | 307 | return idaapi.BADADDR 308 | -------------------------------------------------------------------------------- /shannon_indirect_xref.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | 3 | # Samsung Shannon Modem Loader 4 | # A lean IDA Pro loader for fancy baseband research 5 | # Alexander Pick 2024-2025 6 | 7 | import idc 8 | import ida_segment 9 | import ida_ua 10 | 11 | import os 12 | 13 | import shannon_generic 14 | 15 | max_ops = 6 16 | ref_segs = 0 17 | 18 | def find_mov(ea, reg, memn): 19 | 20 | global max_ops 21 | cnt = 0 22 | ret = -1 23 | 24 | while cnt < max_ops: 25 | 26 | ea = idc.prev_head(ea) 27 | opcode = ida_ua.ua_mnem(ea) 28 | 29 | if (opcode == None): 30 | continue 31 | 32 | if (memn in opcode): 33 | 34 | op_0_t = idc.get_operand_type(ea, 0) 35 | 36 | if(op_0_t == ida_ua.o_reg): 37 | op_0 = idc.get_operand_value(ea, 0) 38 | op_1 = idc.get_operand_value(ea, 1) 39 | if(op_0 == reg): 40 | return op_1 41 | else: 42 | shannon_generic.DEBUG("[d] find_mov(): reg mismatch at %x\n" % ea) 43 | 44 | else: 45 | shannon_generic.DEBUG("[d] find_mov(): type mismatch at %x\n" % ea) 46 | 47 | cnt += 1 48 | 49 | return ret 50 | 51 | # deals with indirect reference produced by more recent versions of compiler rvct, 52 | # these references consist of a movw, movt and a ldr/str on the [reg] 53 | 54 | def scan_main_indirect_refs(): 55 | 56 | global ref_segs 57 | 58 | found_refs = 0 59 | 60 | idc.msg("[i] searching and adding indirect xrefs in MAIN\n") 61 | 62 | seg_t = ida_segment.get_segm_by_name("MAIN_file") 63 | 64 | addr = seg_t.start_ea 65 | 66 | while (addr < seg_t.end_ea): 67 | 68 | addr = idc.next_head(addr) 69 | 70 | if (idc.is_code(idc.get_full_flags(addr))): 71 | 72 | opcode = ida_ua.ua_mnem(addr) 73 | 74 | if (opcode == None): 75 | continue 76 | 77 | if ("LDR" == opcode or "STR" == opcode): 78 | 79 | insn = ida_ua.insn_t() 80 | inslen = ida_ua.decode_insn(insn, addr) 81 | if(inslen == 0): 82 | continue 83 | 84 | op = insn.ops[1] 85 | 86 | # see documentation for op_t::o_phrase why addr is "" 87 | # op.phrase -> reg num 88 | # op.type = 3 (with only one reg) 89 | # 0xd -> SP 90 | 91 | if((op.addr == 0) and (op.phrase != 0xD) and (op.specflag1 == 0) and (op.type == 4)): 92 | 93 | #print("[d] found indirect ldr at %x: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x -> %s" % 94 | # (addr, op.n, op.type, op.offo, op.flags , op.reg, op.dtype, op.phrase, op.value, op.specval, 95 | # op.value, op.value64, op.specflag1, op.specflag2, op.specflag3, op.specflag4, 96 | # idc.generate_disasm_line(addr, 0))) 97 | 98 | high = find_mov(addr, op.reg, "MOVT") 99 | low = find_mov(addr, op.reg, "MOVW") 100 | 101 | if(high != -1 and low != -1): 102 | 103 | shannon_generic.DEBUG("[d] found indirect at %x: %s\n" % (addr, idc.generate_disasm_line(addr, 0))) 104 | 105 | target = high << 16 106 | target |= low 107 | 108 | shannon_generic.DEBUG("[d] %x: found xref to %x\n" % (addr, target)) 109 | 110 | if(idc.get_segm_name(target) == ''): 111 | 112 | seg_start = target & 0xFFFF0000 113 | seg_len = 0xFFFF 114 | 115 | seg_name = idc.get_segm_name(seg_start) 116 | 117 | if(seg_name != ''): 118 | 119 | seg_t_i = ida_segment.get_segm_by_name(seg_name) 120 | 121 | seg_start = seg_t_i.end_ea + 1 122 | diff = seg_start - (target & 0xFFFF0000) 123 | 124 | seg_len -= diff 125 | 126 | shannon_generic.DEBUG("[d] creating segment at %x with diff of %d, new len %d\n" % (seg_start, diff, seg_len)) 127 | 128 | shannon_generic.add_memory_segment(seg_start, seg_len, "ref_"+str(ref_segs), seg_type="DATA") 129 | ref_segs += 1 130 | 131 | idc.add_dref(addr, target, idc.XREF_USER | idc.dr_O) 132 | idc.set_cmt(addr, ("TW XREF: %x" % target), 0) 133 | 134 | found_refs += 1 135 | 136 | idc.msg("[i] added %d references\n" % found_refs) 137 | 138 | #for debugging purpose export SHANNON_WORKFLOW="NO" 139 | if (os.environ.get('SHANNON_WORKFLOW') == "NO"): 140 | idc.msg("[i] running mpu in standalone mode\n") 141 | scan_main_indirect_refs() 142 | -------------------------------------------------------------------------------- /shannon_load.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | 3 | # Samsung Shannon Modem Loader 4 | # A lean IDA Pro loader for fancy baseband research 5 | # Alexander Pick 2024-2025 6 | 7 | import idc 8 | import idaapi 9 | import idautils 10 | import ida_idp 11 | import ida_auto 12 | import ida_bytes 13 | import ida_nalt 14 | import ida_name 15 | import ida_expr 16 | import ida_kernwin 17 | import ida_segment 18 | import ida_ida 19 | import ida_typeinf 20 | 21 | import struct 22 | 23 | import shannon_structs 24 | import shannon_generic 25 | 26 | 27 | # This function will create DBT structs, DBT structs are debug references of various kind. 28 | # The head contains a type byte in position 4, this indicates if a structure is a direct 29 | # string ref or something else. 30 | def make_dbt(): 31 | 32 | sc = idautils.Strings() 33 | 34 | sc.setup(strtypes=[ida_nalt.STRTYPE_C], 35 | ignore_instructions=True, minlen=4) 36 | 37 | sc.refresh() 38 | 39 | for i in sc: 40 | if ("DBT:" in str(i)): 41 | 42 | struct_name = "dbg_trace" 43 | 44 | # read DBT type 45 | # header_type = int.from_bytes(ida_bytes.get_bytes((i.ea+3), 1), "little") 46 | # if(header_type != 0x3a): 47 | # struct_name = "dbt" 48 | 49 | struct_id = idc.get_struc_id(struct_name) 50 | struct_size = idc.get_struc_size(struct_id) 51 | 52 | # make sure we start on-point 53 | offset = i.ea + str(i).find("DBT:") 54 | 55 | ida_bytes.del_items(offset, 0, struct_size) 56 | ida_bytes.create_struct(offset, struct_size, struct_id) 57 | 58 | # validate if the file can be processed by the loader 59 | def accept_file(fd, fname): 60 | 61 | fd.seek(0x0) 62 | 63 | try: 64 | image_type = fd.read(0x3) 65 | except UnicodeDecodeError: 66 | return 0 67 | 68 | if (image_type == b"TOC"): 69 | return {"format": "Shannon Baseband Image", "processor": "arm"} 70 | 71 | return 0 72 | 73 | # required IDA Pro load file function 74 | def load_file(fd, neflags, format): 75 | 76 | version_string = None 77 | 78 | # Old Exynos are ARMv7 (Exynos 3/4/5), old Shannon was Cortex R7, newer are R8. 79 | # New Exynos are all ARMv8+, but Shannon seems to be still running on a A or R core with ARMv7 ISA. 80 | # Tensor's Modem seems to be ARMv8 or it does weird things occassionaly 81 | 82 | idaapi.set_processor_type("arm:ARMv7-A&R", ida_idp.SETPROC_LOADER_NON_FATAL) 83 | idc.process_config_line("ARM_DEFAULT_ARCHITECTURE = ARMv7-A&R") 84 | 85 | idc.process_config_line("ARM_SIMPLIFY = NO") 86 | idc.process_config_line("ARM_NO_ARM_THUMB_SWITCH = NO") 87 | 88 | # improve auto analysis 89 | idc.process_config_line("ARM_REGTRACK_MAX_XREFS = 0") 90 | 91 | # disable Coagulate and colapse 92 | idc.process_config_line("ANALYSIS = 0x9bff9ff7ULL") 93 | 94 | # set compiler defaults 95 | idc.set_inf_attr(idc.INF_COMPILER, idc.COMP_GNU) 96 | 97 | # call convention 98 | # https://hex-rays.com/products/ida/support/idadoc/285.shtml 99 | idc.set_inf_attr(idc.INF_MODEL, idaapi.CM_N32_F48 | idaapi.CM_M_NN | idaapi.CM_CC_FASTCALL) 100 | 101 | # datatype sizes 102 | # https://developer.arm.com/documentation/dui0282/b/arm-compiler-reference/c-and-c---implementation-details/basic-data-types?lang=en 103 | 104 | idc.set_inf_attr(idc.INF_SIZEOF_BOOL, 1) 105 | idc.set_inf_attr(idc.INF_SIZEOF_SHORT, 2) 106 | idc.set_inf_attr(idc.INF_SIZEOF_INT, 4) 107 | idc.set_inf_attr(idc.INF_SIZEOF_ENUM, 4) 108 | idc.set_inf_attr(idc.INF_SIZEOF_LONG, 4) 109 | idc.set_inf_attr(idc.INF_SIZEOF_LLONG, 8) 110 | idc.set_inf_attr(idc.INF_SIZEOF_LDBL, 8) 111 | 112 | # set type library ARM C v1.2 113 | idc.add_default_til("armv12") 114 | 115 | # Set predefined macros for the target compiler 116 | ida_typeinf.set_c_macros(ida_idp.cfg_get_cc_predefined_macros(idc.COMP_GNU)) 117 | 118 | # Get the include directory path of the target compiler 119 | ida_typeinf.set_c_header_path(ida_idp.cfg_get_cc_header_path(idc.COMP_GNU)) 120 | 121 | ida_typeinf.set_c_header_path(ida_idp.cfg_get_cc_header_path(idc.COMP_GNU)) 122 | 123 | # demangle names 124 | idc.process_config_line("DemangleNames = 1") 125 | # check the gcc 3.x name box if not set 126 | ida_ida.inf_set_demnames(ida_ida.inf_get_demnames() | idaapi.DEMNAM_GCC3) 127 | 128 | if (neflags & idaapi.NEF_RELOAD != 0): 129 | return 1 130 | 131 | # make sure the idb is seen as 32 bit even if opened in ida64 132 | idaapi.inf_set_app_bitness(32) 133 | 134 | # this is needed to clear the output window 135 | output = ida_kernwin.find_widget("Output window") 136 | ida_kernwin.activate_widget(output, True) 137 | idaapi.process_ui_action("msglist:Clear") 138 | 139 | idc.msg("\nIDA Pro and Home 8.x+/9.x\n") 140 | idc.msg(r' /\ \ ' + "\n") 141 | idc.msg(r' ___\ \ \___ __ __ __ ___ __ ' + "\n") 142 | idc.msg(r' /`,__| \ _ `\ /`__`\ /` _`\ /` _`\ / __`\ /` _`\ ' + "\n") 143 | idc.msg(r' /\__, `\ \ \ \ \/\ \_\.\_/\ \/\ \/\ \/\ \/\ \_\ \/\ \/\ \ ' + "\n") 144 | idc.msg(r' \/\____/\ \_\ \_\ \__/.\_\ \_\ \_\ \_\ \_\ \____/\ \_\ \_\ ' + "\n") 145 | idc.msg(r' \/___/ \/_/\/_/\/__/\/_/\/_/\/_/\/_/\/_/\/___/ \/_/\/_/ ' + "\n") 146 | idc.msg(r' Modem Loader ' + "\n\n") 147 | idc.msg("More: https://github.com/alexander-pick/shannon_modem_loader\n\n") 148 | 149 | start_offset = 0x20 150 | 151 | while (1): 152 | 153 | fd.seek(start_offset) 154 | entry = fd.read(0x20) 155 | 156 | # unpack TOC entry 157 | toc_info = struct.unpack("12sIIIII", entry) 158 | 159 | seg_name = str(toc_info[0], "UTF-8").strip("\x00") 160 | 161 | if (seg_name == ""): 162 | break 163 | 164 | seg_start = toc_info[2] 165 | seg_end = toc_info[2] + toc_info[3] 166 | 167 | # these seem to be present mostly in older images 168 | if (seg_name == "OFFSET" and seg_start == 0x0): 169 | 170 | idc.msg("[i] found OFFSET, skipping\n") 171 | start_offset += 0x20 172 | continue 173 | 174 | if (seg_name == "GVERSION" and seg_start == 0x0): 175 | 176 | idc.msg("[i] found GVERSION, this is Tensor land\n") 177 | 178 | idaapi.set_processor_type("arm:ARMv8", ida_idp.SETPROC_LOADER_NON_FATAL) 179 | idc.process_config_line("ARM_DEFAULT_ARCHITECTURE = ARMv8") 180 | 181 | # limit for performance reasons 182 | #idc.process_config_line("ARM_REGTRACK_MAX_XREFS = 512") 183 | 184 | tensor = True 185 | start_offset += 0x20 186 | 187 | continue 188 | 189 | # map slices to segments 190 | idc.msg("[i] adding %s\n" % seg_name) 191 | idc.AddSeg(seg_start, seg_end, 0, 1, idaapi.saRel32Bytes, idaapi.scPub) 192 | 193 | if ("NV" in seg_name): 194 | idc.set_segm_class(seg_start, "DATA") 195 | else: 196 | idc.set_segm_class(seg_start, "CODE") 197 | 198 | idc.set_segm_name(seg_start, seg_name + "_file") 199 | 200 | fd.file2base(toc_info[1], seg_start, seg_end, 0) 201 | 202 | # set entry points of main and bootloader 203 | if (seg_name == "BOOT"): 204 | 205 | #mark RX 206 | idc.set_segm_attr(seg_start, idc.SEGATTR_PERM, ida_segment.SEGPERM_EXEC | 207 | ida_segment.SEGPERM_READ | ida_segment.SEGPERM_WRITE) 208 | 209 | idaapi.add_entry(seg_start, seg_start, "bootloader_entry", 1) 210 | idc.set_cmt(seg_start, "bootloader entry point", 1) 211 | ida_auto.auto_make_code(seg_start) 212 | 213 | # process main segment and create vector table 214 | if (seg_name == "MAIN"): 215 | 216 | # mark RX 217 | idc.set_segm_attr(seg_start, idc.SEGATTR_PERM, ida_segment.SEGPERM_EXEC | 218 | ida_segment.SEGPERM_READ | ida_segment.SEGPERM_WRITE) 219 | 220 | # the fancy "ShannonOS" string, 221 | version_addr = shannon_generic.search_text(seg_start, seg_end, "_ShannonOS_") 222 | if (version_addr != None): 223 | version_string = idc.get_strlit_contents(version_addr) 224 | 225 | # 0x0 Reset 226 | # 0x4 Undefined Instruction 227 | # 0x8 Software Interrupt 228 | # 0xC Prefetch Abort 229 | # 0x10 Data Abort 230 | # 0x14 Reserved 231 | # 0x18 IRQ 232 | # 0x1C Reserved 233 | 234 | ida_auto.auto_make_code(seg_start) 235 | 236 | idc.set_cmt(seg_start, "vector table", 1) 237 | 238 | idaapi.add_entry(seg_start, seg_start, "reset", 1) 239 | 240 | idaapi.add_entry(seg_start + 4, seg_start + 4, "undef_inst", 1) 241 | 242 | idaapi.add_entry(seg_start + 8, seg_start + 8, "soft_int", 1) 243 | 244 | idaapi.add_entry(seg_start + 12, seg_start + 12, "prefetch_abort", 1) 245 | 246 | idaapi.add_entry(seg_start + 16, seg_start + 16, "data_abort", 1) 247 | 248 | ida_name.set_name(seg_start + 20, "reserved_1", ida_name.SN_NOCHECK) 249 | 250 | idaapi.add_entry(seg_start + 24, seg_start + 24, "irq", 1) 251 | 252 | idaapi.add_entry(seg_start + 28, seg_start + 28, "fiq", 1) 253 | 254 | if (seg_name == "VSS"): 255 | # mark RX 256 | idc.set_segm_attr(seg_start, idc.SEGATTR_PERM, ida_segment.SEGPERM_EXEC | 257 | ida_segment.SEGPERM_READ | ida_segment.SEGPERM_WRITE) 258 | 259 | start_offset += 0x20 260 | 261 | # let's do that before creating any code (avoids false positives in AA) 262 | shannon_generic.create_long_strings() 263 | 264 | # needs to be done very early 265 | shannon_structs.add_dbt_struct() 266 | make_dbt() 267 | 268 | shannon_structs.add_scatter_struct() 269 | shannon_structs.add_mpu_region_struct() 270 | shannon_structs.add_task_struct() 271 | 272 | # These 3 lines were awarded the most ugliest hack award 2024, runs a script which scheudles a callback without 273 | # beeing unloaded with the loader. 274 | 275 | rv = ida_expr.idc_value_t() 276 | idc_line = 'RunPythonStatement("exec(open(\'' + idaapi.idadir( 277 | "python") + '/shannon_postprocess.py\').read())")' 278 | ida_expr.eval_idc_expr(rv, idaapi.BADADDR, idc_line) 279 | 280 | if (version_string != None): 281 | idc.msg("[i] RTOS version:%s\n" % version_string.decode().replace("_", " ")) 282 | 283 | idc.msg("[i] loader done, starting auto analysis\n") 284 | 285 | return 1 286 | -------------------------------------------------------------------------------- /shannon_mpu.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | 3 | # Samsung Shannon Modem Loader - MPU Processor 4 | # This script is automatically executed by the loader 5 | # Alexander Pick 2024-2025 6 | 7 | import idc 8 | import idaapi 9 | import ida_name 10 | import ida_ua 11 | import ida_segment 12 | import ida_bytes 13 | import idautils 14 | import ida_idp 15 | 16 | import shannon_generic 17 | import shannon_funcs 18 | import shannon_structs 19 | 20 | import os 21 | 22 | def get_segment_boundaries(seg_name="MAIN_file"): 23 | 24 | seg_t = ida_segment.get_segm_by_name(seg_name) 25 | 26 | if (seg_t.end_ea == idaapi.BADADDR): 27 | idc.msg("[e] cannot find " + seg_name + " boundaries\n") 28 | 29 | return seg_t 30 | 31 | # find the hardware init function 32 | def find_hw_init(): 33 | 34 | idc.msg("[i] trying to find hw_init() and rebuild mpu\n") 35 | 36 | # search only in main to avoid unnecessary long runtimes 37 | seg_t = get_segment_boundaries() 38 | 39 | if (seg_t.end_ea == idaapi.BADADDR): 40 | return 41 | 42 | offset = shannon_generic.search_text(seg_t.start_ea, seg_t.end_ea, "Invalid warm boot") 43 | 44 | if (offset == idaapi.BADADDR): 45 | idc.msg("[e] hw_Init(): cannot find string\n") 46 | return 47 | 48 | offset = shannon_generic.get_first_ref(offset) 49 | 50 | # shannon_generic.DEBUG("[d] find_hw_init() offset pre: %x\n" % offset) 51 | 52 | if (offset != idaapi.BADADDR): 53 | if (shannon_funcs.function_find_boundaries(offset)): 54 | offset = idc.get_func_attr(offset, idc.FUNCATTR_START) 55 | 56 | # shannon_generic.DEBUG("[d] find_hw_init() offset: %x\n" % offset) 57 | 58 | if (offset != idaapi.BADADDR): 59 | 60 | hw_init_addr = idc.get_func_attr(offset, idc.FUNCATTR_START) 61 | idc.msg("[i] hw_Init(): %x\n" % hw_init_addr) 62 | ida_name.set_name(hw_init_addr, " hw_Init", ida_name.SN_NOCHECK) 63 | 64 | # check bounderies 65 | func_start = idc.get_func_attr(hw_init_addr, idc.FUNCATTR_START) 66 | func_end = idc.get_func_attr(hw_init_addr, idc.FUNCATTR_END) 67 | 68 | if (func_start != idaapi.BADADDR and func_end != idaapi.BADADDR): 69 | 70 | func_cur = func_start 71 | 72 | candidates = [] 73 | 74 | while (1): 75 | func_cur = idc.next_head(func_cur) 76 | opcode = ida_ua.ua_mnem(func_cur) 77 | 78 | # bailout 79 | if (opcode == None): 80 | continue 81 | 82 | if ("BL" in opcode): 83 | bl_target = idc.get_operand_value(func_cur, 0) 84 | 85 | candidates.append(bl_target) 86 | 87 | if (func_cur >= func_end): 88 | break 89 | 90 | # unique 91 | candidates = list(set(candidates)) 92 | 93 | for candidate in candidates: 94 | #shannon_generic.DEBUG("[d] possible mpu function: %x\n" % candidate) 95 | validate_mpu_candidate(candidate) 96 | else: 97 | idc.msg("[i] failed to identify hw_init()\n") 98 | 99 | # comment mrc and mrc operations 100 | def comment_mcr_mrc(read_write, operand, opcode, addr): 101 | 102 | op = "read" 103 | 104 | if (read_write): 105 | op = "write" 106 | 107 | # ID and System Configuration Registers 108 | if ("c0c" in operand): 109 | idaapi.set_cmt(addr, "Information about the processor - " + op, 0) 110 | 111 | if (opcode == 0): 112 | idaapi.set_cmt( 113 | addr, "Main ID Register (MIDR) - processor identification information - " + op, 0) 114 | return 115 | 116 | if (opcode == 1): 117 | idaapi.set_cmt( 118 | addr, "Cache Type Register (CTR) - level 1 data cache characteristics - " + op, 0) 119 | return 120 | 121 | if (opcode == 2): 122 | idaapi.set_cmt(addr, "Information about TCMs (Tightly Coupled Memories) - " + op, 0) 123 | return 124 | 125 | if (opcode == 3): 126 | idaapi.set_cmt(addr, "Information about the TLB architecture - " + op, 0) 127 | idc.msg("[i] MMU - TLB info request at %x\n" % (addr)) 128 | return 129 | 130 | if (opcode == 4): 131 | idaapi.set_cmt(addr, "Information about the MPU (Memory Protection Unit) - " + op, 0) 132 | idc.msg("[i] MMU - MMU info request at %x\n" % (addr)) 133 | return 134 | 135 | if (opcode == 5): 136 | idaapi.set_cmt( 137 | addr, "Processor Feature Register 1 (PFR1) - additional processor feature information - " + op, 0) 138 | return 139 | 140 | if (opcode == 6): 141 | idaapi.set_cmt( 142 | addr, "Processor Feature Register 1 (PFR1) - additional processor feature information - " + op, 0) 143 | return 144 | 145 | if (opcode == 7): 146 | idaapi.set_cmt( 147 | addr, "Debug Feature Register (DFR) - information about debug features - " + op, 0) 148 | return 149 | 150 | if (opcode == 8): 151 | idaapi.set_cmt( 152 | addr, "Auxiliary Feature Register (AFR) - auxiliary features information - " + op, 0) 153 | return 154 | 155 | if (opcode == 9): 156 | idaapi.set_cmt( 157 | addr, "Memory Model Feature Register 0 (MMFR0) - additional memory model features. - " + op, 0) 158 | return 159 | 160 | if (opcode == 10): 161 | idaapi.set_cmt( 162 | addr, "Memory Model Feature Register 1 (MMFR1) - additional memory model features. - " + op, 0) 163 | return 164 | 165 | if (opcode == 11): 166 | idaapi.set_cmt( 167 | addr, "Memory Model Feature Register 2 (MMFR2) - additional memory model features. - " + op, 0) 168 | return 169 | 170 | if (opcode == 12): 171 | idaapi.set_cmt( 172 | addr, "Memory Model Feature Register 3 (MMFR3) - additional memory model features. - " + op, 0) 173 | return 174 | 175 | if (opcode == 13): 176 | idaapi.set_cmt( 177 | addr, "ISA Feature Register 0 (ISAR1) - additional instruction set information - " + op, 0) 178 | return 179 | 180 | if (opcode == 14): 181 | idaapi.set_cmt( 182 | addr, "ISA Feature Register 1 (ISAR1) - additional instruction set information - " + op, 0) 183 | return 184 | 185 | if (opcode == 15): 186 | idaapi.set_cmt( 187 | addr, "ISA Feature Register 2 (ISAR2) - additional instruction set information - " + op, 0) 188 | return 189 | 190 | # System Control Register (SCTLR) 191 | if ("c1c" in operand): 192 | idaapi.set_cmt(addr, "System Control Register - " + op, 0) 193 | return 194 | 195 | # Translation Table Base Register (TTBR) 196 | if ("c2c" in operand): 197 | idaapi.set_cmt(addr, "Translation Table Base Register - " + op, 0) 198 | 199 | if (opcode == 0): 200 | idaapi.set_cmt( 201 | addr, "Translation Table Base Register (TTBR0), base of the first-level translation table - " + op, 0) 202 | if (read_write): 203 | idc.msg("[i] MMU - TTBR0 write at %x\n" % (addr)) 204 | return 205 | 206 | if (opcode == 1): 207 | idaapi.set_cmt( 208 | addr, "Translation Table Base Register (TTBR1), base of the second-level translation table - " + op, 0) 209 | if (read_write): 210 | idc.msg("[i] MMU - TTBR1 write at %x\n" % (addr)) 211 | return 212 | 213 | if (opcode == 2): 214 | idaapi.set_cmt( 215 | addr, "Translation Table Base Control Register (TTBCR), controls the use of TTBR0 and TTBR1 - " + op, 0) 216 | idc.msg("[i] MMU - TTBCR operation at %x\n" % (addr)) 217 | return 218 | 219 | # Domain Access Control Register 220 | if ("c3c" in operand): 221 | idaapi.set_cmt(addr, "Domain Access Control Register - " + op, 0) 222 | if (read_write): 223 | idc.msg("[i] MMU - DACR write at %x\n" % (addr)) 224 | return 225 | 226 | # Fault Status Registers 227 | if ("c5c" in operand): 228 | idaapi.set_cmt(addr, "Fault Status Registers - " + op, 0) 229 | return 230 | 231 | # Fault Address Registers 232 | if ("c6c" in operand): 233 | idaapi.set_cmt(addr, "Fault Address Registers - " + op, 0) 234 | return 235 | 236 | # Cache and Branch Predictor Maintenance 237 | if ("c7c" in operand): 238 | idaapi.set_cmt(addr, "Cache and Branch Predictor Maintenance - " + op, 0) 239 | return 240 | 241 | # Performance Monitors Registers 242 | if ("c9c" in operand): 243 | idaapi.set_cmt(addr, "Performance Monitors Registers - " + op, 0) 244 | return 245 | 246 | # Memory Management Fault Address Registers 247 | if ("c10c" in operand): 248 | idaapi.set_cmt(addr, "Memory Management Fault Address Registers - " + op, 0) 249 | return 250 | 251 | # Vector Base Address Register (VBAR): 252 | if ("c12c" in operand): 253 | idaapi.set_cmt(addr, "Vector Base Address Register (VBAR) - " + op, 0) 254 | return 255 | 256 | # Process, Context, and Thread ID Registers 257 | if ("c13c" in operand): 258 | idaapi.set_cmt(addr, "Process, Context, and Thread ID Registers - " + op, 0) 259 | return 260 | 261 | # comment MSR ops on CPSR 262 | def comment_cpsr(value, addr): 263 | 264 | # convert 265 | binary_value = format(value, '032b') 266 | 267 | # extrat 268 | n_flag = int(binary_value[0], 2) 269 | z_flag = int(binary_value[1], 2) 270 | c_flag = int(binary_value[2], 2) 271 | v_flag = int(binary_value[3], 2) 272 | q_flag = int(binary_value[27], 2) 273 | j_flag = int(binary_value[24], 2) 274 | e_flag = int(binary_value[9], 2) 275 | a_flag = int(binary_value[8], 2) 276 | i_flag = int(binary_value[7], 2) 277 | f_flag = int(binary_value[6], 2) 278 | t_flag = int(binary_value[5], 2) 279 | 280 | mode_bits = binary_value[27:32] 281 | 282 | # map mode bits to mode names 283 | modes = { 284 | '10000': 'user mode', 285 | '10001': 'FIQ mode', 286 | '10010': 'IRQ mode', 287 | '10011': 'supervisor mode', 288 | '10111': 'abort mode', 289 | '11011': 'undefined mode', 290 | '11111': 'system mode', 291 | } 292 | mode = modes.get(mode_bits, 'Unknown mode') 293 | 294 | # the flags 295 | flags = { 296 | 'N': 'negative', 297 | 'Z': 'zero', 298 | 'C': 'carry', 299 | 'V': 'overflow', 300 | 'Q': 'saturation', 301 | 'J': 'jazelle', 302 | 'E': 'endian', 303 | 'A': 'asynchronous abort', 304 | 'I': 'IRQ disable', 305 | 'F': 'FIQ disable', 306 | 'T': 'thumb', 307 | } 308 | 309 | comment = "mode: " + mode + "\n\n" 310 | comment += "flags\n" 311 | 312 | for bit, (name, description) in enumerate(flags.items(), start=31): 313 | 314 | comment += str(bit) + ": " 315 | 316 | if (eval(name.lower() + "_flag")): 317 | comment += "1 " + description + "\n" 318 | else: 319 | comment += "0 " + description + "\n" 320 | 321 | idaapi.set_cmt(addr, comment, 0) 322 | 323 | # this tries to find the MRC opcodes required for setting up the MMU 324 | # we are looking for a write to SCTLR 325 | def validate_mmu_candidate(bl_target): 326 | 327 | func_start = idc.get_func_attr(bl_target, idc.FUNCATTR_START) 328 | func_end = idc.get_func_attr(bl_target, idc.FUNCATTR_END) 329 | 330 | addr = func_start 331 | 332 | if (func_start != idaapi.BADADDR): 333 | 334 | while (addr <= func_end): 335 | 336 | # First opcode is an MRC 337 | opcode = ida_ua.ua_mnem(addr) 338 | 339 | if (opcode == None): 340 | addr = idc.next_head(addr) 341 | continue 342 | 343 | # MCR p15, 0, R0, c2, c0, 2 344 | # -> Write to CP15 - Operand Num (normally 0), Source Reg CPU, Coproc Num, Coproc Reg, Reg Offset 345 | # c2 is translation table 346 | 347 | # MCR - write 348 | # MRC - read 349 | 350 | if ("MSR" in opcode): 351 | 352 | cpsr = idc.get_operand_value(addr, 0) 353 | cpsr_value = idc.get_operand_value(addr, 1) 354 | 355 | if (cpsr == -1): 356 | addr = idc.next_head(addr) 357 | continue 358 | 359 | cpsr_str = ida_idp.get_reg_name(cpsr, 0) 360 | 361 | # this is normally a bit field, we just analyse some very common values here 362 | if ("CPSR" in cpsr_str): 363 | comment_cpsr(cpsr_value, addr) 364 | 365 | if ("MCR" == opcode or "MRC" == opcode): 366 | 367 | #shannon_generic.DEBUG("[d] MCR/MRC: %x\n" % addr) 368 | 369 | # workaround since get_oeprand_value does not work 370 | t = idaapi.generate_disasm_line(addr) 371 | if (t): 372 | 373 | operands_str = idaapi.tag_remove(t) 374 | operands = operands_str.replace(",", "").replace(";", "").split() 375 | 376 | #shannon_generic.DEBUG("[d] %s\n" % operands_str) 377 | 378 | # CP15, the system control coprocessor is adressed 379 | if ("p15" in operands[1]): 380 | 381 | if (len(operands) < 5): 382 | idc.msg("[i] MCR/MRC operands error at %x\n" % addr) 383 | addr = idc.next_head(addr) 384 | continue 385 | 386 | if ("MCR" in opcode): 387 | comment_mcr_mrc(True, operands[3], int(operands[4]), addr) 388 | 389 | else: 390 | 391 | comment_mcr_mrc(False, operands[3], int(operands[4]), addr) 392 | 393 | addr = idc.next_head(addr) 394 | 395 | return 396 | 397 | # find all MRC with write to CP15 etc. - looking for MMU setup 398 | def scan_for_mrc(target_seg="MAIN_file"): 399 | 400 | idc.msg("[i] trying to identify MMU related opcodes in %s\n" % target_seg) 401 | 402 | seg_t = get_segment_boundaries(target_seg) 403 | 404 | if (seg_t.end_ea == idaapi.BADADDR): 405 | return 406 | 407 | for ea in idautils.Functions(seg_t.start_ea, seg_t.end_ea): 408 | validate_mmu_candidate(ea) 409 | 410 | # shannon_generic.DEBUG("[d] scan done\n") 411 | 412 | # check if we found the mpu table 413 | def validate_mpu_candidate(bl_target): 414 | 415 | metrics = shannon_generic.get_metric(bl_target) 416 | 417 | # enable metrics debug output 418 | #shannon_generic.print_metrics(bl_target, metrics) 419 | 420 | # metrics: 421 | # 0 loops (list) 1 or 2 422 | # 1 branch (list) 423 | # 2 length 32-70 424 | # 3 basicblocks > 4 425 | # 4 xrefs (list) always 1 426 | # 5 ldr (list) 6 or more 427 | # 6 calls (list) 6 or more 428 | 429 | # sample metric: 430 | # loops: 2 branch: 4 length: 53 basic blocks: 7 xrefs: 1 ldr: 6 calls: 8 (moto-training) 431 | # loops: 2 branch: 6 length: 70 basic blocks: 9 xrefs: 1 ldr: 16 calls: 9 (eng) 432 | 433 | # false 434 | # loops: 2 branch: 5 length: 74 basic blocks: 10 xrefs: 1 ldr: 14 calls: 9 435 | 436 | if ((len(metrics[0]) > 0 and len(metrics[0]) < 3) and metrics[3] > 4 and (metrics[2] > 24 and metrics[2] < 72) 437 | and len(metrics[4]) == 1 and len(metrics[6]) > 5 and (len(metrics[5]) > 5)): 438 | 439 | if (process_mpu_table(metrics[5])): 440 | # @tocheck: if any false positive occures, need to valdiate branches for calls to enable/disable 441 | idc.msg("[i] hw_MpuInit(): %x\n" % bl_target) 442 | ida_name.set_name(bl_target, " hw_MpuInit", ida_name.SN_NOCHECK) 443 | 444 | # if there are 250+ refs to the candidate function it is the exception handler or get_chip_name 445 | if (len(metrics[4]) > 250 and (metrics[2] > 24 and metrics[2] < 80)): 446 | idc.msg("[i] hw_SwExceptionHandler(): %x\n" % bl_target) 447 | ida_name.set_name(bl_target, " hw_SwExceptionHandler", ida_name.SN_NOCHECK) 448 | 449 | # commonly just an LDR but behaves wonky across versions, so disabled atm 450 | # if (len(metrics[4]) > 200 and metrics[2] < 3): 451 | # idc.msg("[i] get_chip_name(): %x\n" % bl_target) 452 | # ida_name.set_name(bl_target, " get_chip_name", ida_name.SN_NOCHECK) 453 | 454 | def is_main_segment(addr): 455 | 456 | seg_t = ida_segment.get_segm_by_name("MAIN_file") 457 | 458 | if (addr > seg_t.start_ea and addr < seg_t.end_ea): 459 | return True 460 | else: 461 | return False 462 | 463 | # identifies the mpu tabl and processes it 464 | def process_mpu_table(tbl_candidates): 465 | 466 | for ldr in tbl_candidates: 467 | 468 | if (ldr > 0x1000): 469 | mpu_tbl = int.from_bytes(ida_bytes.get_bytes(ldr, 4), "little") 470 | idc.msg("[i] mpu tbl candidate at %x\n" % mpu_tbl) 471 | 472 | # prevents false positives 473 | if (not is_main_segment(mpu_tbl)): 474 | idc.msg("[e] candidate outside boundaries\n") 475 | return False 476 | 477 | struct_id = idc.get_struc_id("mpu_region") 478 | struct_size = idc.get_struc_size(struct_id) 479 | tif = shannon_structs.get_struct(struct_id) 480 | 481 | # just a sanity check in case we hit the wrong place 482 | # Shannon mpu table is never amazingly big 483 | max_entries = 0x20 484 | entries = 0 485 | 486 | num_ptr = shannon_structs.get_offset_by_name(tif, "num") 487 | addr_ptr = shannon_structs.get_offset_by_name(tif, "addr") 488 | size_ptr = shannon_structs.get_offset_by_name(tif, "size") 489 | ap_ptr = shannon_structs.get_offset_by_name(tif, "ap") 490 | xn_ptr = shannon_structs.get_offset_by_name(tif, "xn") 491 | 492 | while (1): 493 | 494 | ida_bytes.del_items(mpu_tbl, 0, struct_size) 495 | ida_bytes.create_struct(mpu_tbl, struct_size, struct_id) 496 | 497 | # shannon_generic.DEBUG("[d] s:%x o:%x, %x, %x, %x\n" % 498 | # (mpu_tbl, num_ptr, addr_ptr, size_ptr, xn_ptr)) 499 | 500 | num = int.from_bytes(ida_bytes.get_bytes((mpu_tbl + num_ptr), 4), "little") 501 | addr = int.from_bytes(ida_bytes.get_bytes((mpu_tbl + addr_ptr), 4), "little") 502 | size = int.from_bytes(ida_bytes.get_bytes((mpu_tbl + size_ptr), 4), "little") 503 | 504 | if (num == 0xff): 505 | idc.msg("[i] reached end of mpu tbl at %x\n" % mpu_tbl) 506 | return True 507 | 508 | if (entries == max_entries): 509 | idc.msg("[e] too many entries in table at %x\n" % mpu_tbl) 510 | return False 511 | 512 | xn = int.from_bytes(ida_bytes.get_bytes((mpu_tbl + xn_ptr), 4), "little") 513 | ap = int.from_bytes(ida_bytes.get_bytes((mpu_tbl + xn_ptr), 4), "little") 514 | 515 | read = True 516 | write = True 517 | exec = True 518 | 519 | if(ap == 0): 520 | read = False # Read/write by privileged code only. 521 | write = False 522 | 523 | if(ap == 0b10): 524 | read = False # Read only by privileged code only 525 | write = False 526 | 527 | if(ap == 0b11): 528 | write = False # Read only by any privilege level 529 | 530 | seg_type = "CODE" 531 | 532 | if (xn > 0): # eXecute Never attribute 533 | seg_type = "DATA" 534 | exec = False 535 | 536 | shannon_generic.add_memory_segment( 537 | addr, size, "MPU_" + str(num), seg_type, 0, read, write, exec) 538 | 539 | mpu_tbl += struct_size 540 | entries += 1 541 | 542 | return True 543 | 544 | 545 | #for debugging purpose export SHANNON_WORKFLOW="NO" 546 | if (os.environ.get('SHANNON_WORKFLOW') == "NO"): 547 | idc.msg("[i] running mpu in standalone mode\n") 548 | find_hw_init() 549 | scan_for_mrc() 550 | -------------------------------------------------------------------------------- /shannon_names.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | 3 | # Samsung Shannon Modem Loader - Name Reconstruction 4 | # This script is autoamtically scheduled by the loader 5 | # Alexander Pick 2024-2025 6 | 7 | import idc 8 | import ida_bytes 9 | import ida_nalt 10 | import idautils 11 | import idaapi 12 | import ida_segment 13 | #import ida_search 14 | import ida_ua 15 | import ida_funcs 16 | 17 | import re 18 | import os 19 | 20 | import shannon_generic 21 | import shannon_funcs 22 | 23 | def restore_cpp_names(): 24 | 25 | idc.msg("[i] trying to reconstruct cpp names from strings\n") 26 | sc = idautils.Strings() 27 | 28 | for i in sc: 29 | # step 1 - find a function name 30 | regex = "([a-z]*::[A-Za-z_]*::[A-Za-z_]*)" 31 | 32 | if (re.match(regex, str(i))): 33 | shannon_generic.create_name(i.ea, str(i)) 34 | 35 | # restores the function names of SS related functions from a macro created function structure 36 | 37 | def restore_ss_names(): 38 | 39 | idc.msg("[i] trying to reconstruct ss names from function macros\n") 40 | 41 | # step 1 - find a function name 42 | 43 | # search only in main to avoid unnecessary long runtimes 44 | seg_t = ida_segment.get_segm_by_name("MAIN_file") 45 | 46 | ss_offset = shannon_generic.search_text( 47 | seg_t.start_ea, seg_t.end_ea, "ss_DecodeGmmFacilityMsg") 48 | 49 | if (ss_offset != idaapi.BADADDR): 50 | 51 | # step 2 - find xrefs to this name, essentially should be just one xref 52 | for xref in idautils.XrefsTo(ss_offset, 0): 53 | 54 | # sanity check - validate that xref target is a function, or next 55 | if (idc.get_func_attr(xref.frm, idc.FUNCATTR_START) == idaapi.BADADDR): 56 | continue 57 | 58 | # step 3 - iterate over the next instructions until we find a function call 59 | 60 | xref_str = None 61 | tries = 0 62 | prev_offset = xref.frm 63 | 64 | while (tries < 5): 65 | 66 | # forward search, max 5 instructions 67 | xref_str_tmp = idc.next_head(prev_offset) 68 | opcode = ida_ua.ua_mnem(xref_str_tmp) 69 | 70 | if (opcode == None): 71 | tries += 1 72 | continue 73 | 74 | if ("BL" in opcode): 75 | # shannon_generic.DEBUG("[d] found BL at %x\n" % xref_str_tmp) 76 | # docs said this is a list, but seems to be a generator? 77 | xref_str = next( 78 | idautils.CodeRefsFrom(xref_str_tmp, 0)) 79 | break 80 | else: 81 | prev_offset = xref_str_tmp 82 | tries += 1 83 | 84 | if (xref_str == None): 85 | continue # abort if not foudn or emptyt list 86 | 87 | idc.msg("[i] found verbose ss_ name function: %x\n" % xref_str) 88 | 89 | check_failed = 0 90 | 91 | # step 4 - iterate over the all calls to this function 92 | for xref_func in idautils.XrefsTo(xref_str, 0): 93 | 94 | tries = 0 95 | prev_offset = xref_func.frm 96 | 97 | while (tries < 5): 98 | 99 | cur_offset = idc.prev_head(prev_offset) 100 | opcode = ida_ua.ua_mnem(cur_offset) 101 | 102 | if (opcode == None): 103 | tries += 1 104 | continue 105 | 106 | # Thanks John Doe 107 | if ("DR" in opcode and idc.get_operand_value(cur_offset, 0) == 0x0): 108 | # shannon_generic.DEBUG("[d] found LDR at %x\n" % cur_offset) 109 | break 110 | else: 111 | prev_offset = cur_offset 112 | tries += 1 113 | 114 | # get LDR param which is the function name 115 | str_addr = idc.get_operand_value(cur_offset, 1) 116 | 117 | # read string 118 | func_name = idc.get_strlit_contents(str_addr) 119 | 120 | # sanity checks 121 | if (func_name == None): 122 | # shannon_generic.DEBUG("[d] %x: failed sanity check (None)\n" % str_addr) 123 | check_failed = 1 124 | 125 | # this in elif to avoid err with undefined bla 126 | elif (len(func_name.decode()) < 8): 127 | # shannon_generic.DEBUG("[d] %x: failed sanity check (length)\n" % str_addr) 128 | check_failed = 1 129 | 130 | if check_failed: 131 | # try to resolve ref 132 | func_name = shannon_generic.resolve_ref(str_addr) 133 | 134 | if (func_name == None): 135 | # idc.msg( 136 | # "[e] %x: function name not defined\n" % str_addr) 137 | continue 138 | 139 | func_name_str = func_name.decode() 140 | 141 | # shannon_generic.DEBUG("[d] %x: found function name %s\n" % (str_addr, func_name_str)) 142 | 143 | if ("ss_" not in func_name_str): 144 | idc.msg("[e] %x: failed to find function name for %x, found '%s' instead\n" % ( 145 | str_addr, xref_func.frm, func_name)) 146 | continue 147 | 148 | # create a string at string offfset 149 | ida_bytes.create_strlit(str_addr, 0, ida_nalt.STRTYPE_C) 150 | 151 | func_start = idc.get_func_attr( 152 | xref_func.frm, idc.FUNCATTR_START) 153 | 154 | if (func_start != idaapi.BADADDR): 155 | 156 | if (len(func_name_str) > 8): 157 | 158 | func_name = shannon_funcs.function_find_name(func_name_str) 159 | 160 | idaapi.set_name(func_start, func_name) 161 | else: 162 | idc.msg("[e] %x: function name too short: %s" % 163 | (func_start, func_name_str)) 164 | else: 165 | # shannon_generic.DEBUG("[d] not a function, searching for start\n") 166 | cur_offset = xref_func.frm 167 | prev_offset = 0 168 | # find func boundaries 169 | 170 | tries = 150 171 | 172 | while tries: 173 | 174 | # possible bailout 175 | tries -= 1 176 | 177 | flags = idc.get_func_flags(cur_offset) 178 | opcode = ida_ua.ua_mnem(cur_offset) 179 | 180 | if ((flags == -1) and (opcode != None)): 181 | 182 | prev_offset = cur_offset 183 | cur_offset = idc.prev_head(cur_offset) 184 | 185 | else: 186 | 187 | ida_funcs.add_func( 188 | prev_offset, idc.prev_head(str_addr)) 189 | idaapi.set_name( 190 | prev_offset, shannon_funcs.function_find_name(func_name_str)) 191 | break 192 | 193 | #for debugging purpose export SHANNON_WORKFLOW="NO" 194 | if (os.environ.get('SHANNON_WORKFLOW') == "NO"): 195 | idc.msg("[i] running names in standalone mode") 196 | 197 | -------------------------------------------------------------------------------- /shannon_pal_reconstructor.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | 3 | # Samsung Shannon Modem Loader - PAL Reconstructor 4 | # This script is autoamtically executed by the loader 5 | # Alexander Pick 2024-2025 6 | 7 | import idc 8 | import idaapi 9 | import idautils 10 | import ida_bytes 11 | import ida_name 12 | import ida_ua 13 | import ida_nalt 14 | import ida_segment 15 | 16 | import shannon_generic 17 | import shannon_funcs 18 | import shannon_structs 19 | 20 | import os 21 | 22 | # find ref to PALTskTm 23 | def get_PALTskTm_ref(): 24 | 25 | seg_t = ida_segment.get_segm_by_name("MAIN_file") 26 | pal_task_man = shannon_generic.search_text(seg_t.start_ea, seg_t.end_ea, "PALTskTm") 27 | pal_task_man = shannon_generic.get_first_ref(pal_task_man) 28 | 29 | return pal_task_man 30 | 31 | # pad struct 32 | def pad_task_struct(padding): 33 | struct_id = idc.get_struc_id("task_struct") 34 | sptr = shannon_structs.get_struct(struct_id) 35 | 36 | str_ptr = shannon_structs.get_offset_by_name(sptr, "padding") 37 | idc.del_struc_member(struct_id, str_ptr) 38 | 39 | shannon_structs.add_struc_member( 40 | struct_id, "padding", -1, idaapi.FF_BYTE, -1, padding) 41 | 42 | # struct are different in size, since we work with a more or less static start we need to find 43 | # the start offset 44 | # TODO: ssentially it would be correct to extend the struct by X on top but we can simply move the 45 | # start to avoid more fiddelign for now. This will be fixed later 46 | def find_struct_start(tbl_offset, margin): 47 | 48 | # create struct, check string etc. 49 | 50 | tbl_offset_orig = tbl_offset 51 | tbl_offset += margin 52 | 53 | struct_id = idc.get_struc_id("task_struct") 54 | sptr = shannon_structs.get_struct(struct_id) 55 | 56 | struct_size = idc.get_struc_size(struct_id) 57 | str_ptr = shannon_structs.get_offset_by_name(sptr, "task_name") 58 | 59 | ida_bytes.del_items(tbl_offset, 0, struct_size) 60 | ida_bytes.create_struct(tbl_offset, struct_size, struct_id) 61 | 62 | str_offset = int.from_bytes(ida_bytes.get_bytes((tbl_offset + str_ptr), 4), "little") 63 | 64 | ida_bytes.del_items(str_offset, 0, 2) 65 | ida_bytes.create_strlit(str_offset, 0, ida_nalt.STRTYPE_C) 66 | 67 | task_name_str = idc.get_strlit_contents(str_offset) 68 | 69 | shannon_generic.DEBUG("[d] testing margin of 0x%x -> 0x%x (str %s, str_off: 0x%x)\n" % 70 | (margin, tbl_offset, str(task_name_str), str_offset)) 71 | 72 | #clean up 73 | ida_bytes.del_items(tbl_offset, 0, struct_size) 74 | 75 | if (margin > 0xFF): 76 | return None 77 | 78 | if (not task_name_str or len(task_name_str) < 2): 79 | margin += 1 80 | 81 | tbl_offset = find_struct_start(tbl_offset_orig, margin) 82 | return tbl_offset 83 | 84 | idc.msg("[i] found real table start at %x , first tasks is %s\n" % 85 | (tbl_offset, str(task_name_str.decode()))) 86 | 87 | return tbl_offset 88 | 89 | # This code identifies a couple of functions of the platform abstraction layer and uses 90 | # these to find the task table. This could be done in a much simpler fashion by searching 91 | # for PALTskTm and work from there, but using the heuristic below a couple of func refs 92 | # will be reconstructed and named which are quite important for future analysis 93 | def find_pal_msg_funcs(): 94 | 95 | idc.msg("[i] trying to identify PAL message related functions\n") 96 | 97 | # search only in main to avoid unnecessary long runtimes 98 | seg_t = ida_segment.get_segm_by_name("MAIN_file") 99 | 100 | pal_MsgSendTo_addr = shannon_generic.search_text( 101 | seg_t.start_ea, seg_t.end_ea, "PAL_MSG_MAX_ENTITY_COUNT") 102 | 103 | if (pal_MsgSendTo_addr != idaapi.BADADDR): 104 | # stupid hack to get beginning of the string since we found a substring 105 | # the PAL_MSG_MAX_ENTITY_COUNT string varies between BB versions so the 106 | # search is for the most remarkable part only 107 | pal_MsgSendTo_addr = idc.prev_head(idc.next_head(pal_MsgSendTo_addr)) 108 | 109 | # fallback for 5g versions which have the string slightly 110 | # crippled between hi/lo reg, furthermore the PAL_MSG_MAX_ENTITY_COUNT 111 | # string is in another function 112 | if (pal_MsgSendTo_addr == idaapi.BADADDR): 113 | pal_MsgSendTo_addr = shannon_generic.search_text( 114 | seg_t.start_ea, seg_t.end_ea, "QUEUE_NAME") 115 | 116 | # step 1 - find pal_MsgSendTo() 117 | if (pal_MsgSendTo_addr != idaapi.BADADDR): 118 | 119 | # realign if we are off by one here due to thumb and stuff 120 | if (pal_MsgSendTo_addr % 4): 121 | pal_MsgSendTo_addr += 1 122 | 123 | # most images have 2 xrefs to this string, ones is MsgSendTo 124 | for xref in idautils.XrefsTo(pal_MsgSendTo_addr, 0): 125 | 126 | func_start = idc.get_func_attr(xref.frm, idc.FUNCATTR_START) 127 | 128 | num_xrefs = len(list(idautils.XrefsTo(func_start, 0))) 129 | 130 | # pal_MsgSendTo has a lot of xrefs to itself, other candidate funcs don't have that 131 | if (num_xrefs > 15): 132 | 133 | pal_MsgSendTo_addr = func_start 134 | 135 | # sanity check - validate that xref target is a function, or next 136 | if (pal_MsgSendTo_addr == idaapi.BADADDR): 137 | continue 138 | 139 | idc.msg("[i] pal_MsgSendTo(): %x\n" % pal_MsgSendTo_addr) 140 | ida_name.set_name(pal_MsgSendTo_addr, "pal_MsgSendTo", 141 | ida_name.SN_NOCHECK | ida_name.SN_FORCE) 142 | 143 | find_pal_msg_init(pal_MsgSendTo_addr) 144 | 145 | return 146 | 147 | 148 | # try to find pal_MsgInit() and a few others along 149 | def find_pal_msg_init(pal_MsgSendTo_addr): 150 | # step2 - find pal_MsgInit() 151 | if (pal_MsgSendTo_addr != idaapi.BADADDR): 152 | 153 | func_cnt = 1 154 | tbl_cnt = 1 155 | pal_MsgInit_addr = pal_MsgSendTo_addr 156 | 157 | while (func_cnt < 12): 158 | 159 | # get a candidate get_prev_func returns a func_t :S 160 | pal_MsgInit_addr_t = idaapi.get_prev_func(pal_MsgInit_addr) 161 | pal_MsgInit_addr = pal_MsgInit_addr_t.start_ea 162 | 163 | que_init_addr = idc.next_head(pal_MsgInit_addr) 164 | 165 | # check if second opcode of function is a BL 166 | opcode = ida_ua.ua_mnem(que_init_addr) 167 | 168 | if (opcode == None): 169 | idc.msg("[e] found no opcode at %x\n" % pal_MsgInit_addr) 170 | continue 171 | 172 | # step3, find pal_QueInit to make sure we have the right parent function 173 | # A call to pal_QueInit is located directly after the reg save of pal_MsgInit 174 | if ("BL" in opcode): 175 | # yes, so we found pal_QueInit, get the target offset 176 | target_ref = idc.get_operand_value(que_init_addr, 0) 177 | 178 | idc.msg("[i] pal_QueInit(): %x\n" % target_ref) 179 | ida_name.set_name(target_ref, "pal_QueInit", 180 | ida_name.SN_NOCHECK) 181 | 182 | # low xp sidequest - find MsgDescriptorTbl (because we can) 183 | while (tbl_cnt < 5): 184 | 185 | task_desc_offset = pal_MsgInit_addr + 4 + (4 * tbl_cnt) 186 | 187 | opcode = ida_ua.ua_mnem(task_desc_offset) 188 | 189 | if (opcode == "LDR"): 190 | target_ref = idc.get_operand_value(task_desc_offset, 1) 191 | target = int.from_bytes( 192 | ida_bytes.get_bytes(target_ref, 4), "little") 193 | 194 | idc.msg("[i] pal_MsgDescriptorTbl(): %x\n" % target) 195 | ida_name.set_name( 196 | target, "pal_MsgDescriptorTbl", ida_name.SN_NOCHECK) 197 | 198 | tbl_cnt += 1 199 | 200 | idc.msg("[i] pal_MsgInit(): %x\n" % pal_MsgInit_addr) 201 | ida_name.set_name(pal_MsgInit_addr, 202 | "pal_MsgInit", ida_name.SN_NOCHECK) 203 | 204 | break 205 | 206 | func_cnt += 1 207 | 208 | return 209 | 210 | # try to find Task SCheduler, Task Inits and pal_Init() 211 | def find_pal_init(): 212 | 213 | idc.msg("[i] trying to identify PAL init and tasks\n") 214 | 215 | pal_task_man = get_PALTskTm_ref() 216 | 217 | if (pal_task_man != idaapi.BADADDR): 218 | 219 | func_start = idc.get_func_attr(pal_task_man, idc.FUNCATTR_START) 220 | func_end = idc.get_func_attr(pal_task_man, idc.FUNCATTR_END) 221 | 222 | if (func_start != idaapi.BADADDR and func_end != idaapi.BADADDR): 223 | 224 | idc.msg("[i] pal_TaskMngrInit(): %x\n" % func_start) 225 | ida_name.set_name(func_start, "pal_TaskMngrInit", ida_name.SN_NOCHECK) 226 | 227 | tasks = find_task_desc_tbl(func_start, func_end) 228 | 229 | if (not tasks): 230 | shannon_generic.DEBUG("[d] find_task_desc_tbl() failed with None\n") 231 | return 232 | 233 | if (tasks > 0): 234 | idc.msg("[i] identified %d tasks\n" % tasks) 235 | else: 236 | idc.msg("[e] failed to identify tasks\n") 237 | 238 | for xref in idautils.XrefsTo(func_start, 0): 239 | 240 | pal_init_addr = idc.get_func_attr(xref.frm, idc.FUNCATTR_START) 241 | 242 | if (pal_init_addr != idaapi.BADADDR): 243 | idc.msg("[i] pal_Init(): %x\n" % pal_init_addr) 244 | ida_name.set_name(pal_init_addr, "pal_Init", ida_name.SN_NOCHECK) 245 | 246 | metrics = shannon_generic.get_metric(pal_init_addr) 247 | 248 | for branch in metrics[6]: 249 | first_operand = idc.get_operand_value(branch, 0) 250 | validate_if_dm_trace_log(first_operand) 251 | 252 | return 253 | return 254 | 255 | def validate_if_dm_trace_log(bl_target): 256 | 257 | metrics = shannon_generic.get_metric(bl_target) 258 | #shannon_generic.print_metrics(bl_target, metrics) 259 | 260 | # this function has an insane amount of xrefs, very unique 261 | if (len(metrics[4]) > 150000): 262 | idc.msg("[i] dm_TraceMsg(): %x\n" % bl_target) 263 | ida_name.set_name(bl_target, "dm_TraceMsg", ida_name.SN_NOCHECK) 264 | 265 | # find task descriptor table for newer devices, 5G and up 266 | def find_task_desc_tbl_5g(task_func_start, task_func_end): 267 | 268 | idc.msg("[i] table discovery failed, attemping discovery for 5G modems\n") 269 | 270 | task_func_cur = task_func_start 271 | 272 | while (1): 273 | 274 | task_func_cur = idc.next_head(task_func_cur) 275 | task_opcode = ida_ua.ua_mnem(task_func_cur) 276 | 277 | # bailout 278 | if (task_func_cur >= task_func_end): 279 | idc.msg("[e] find_task_desc_tbl_5g(): end reached at %x, no table found\n" % 280 | (task_func_cur)) 281 | 282 | # let's try it one more time with another logic 283 | return find_task_desc_tbl_pixel(task_func_start, task_func_end) 284 | 285 | if (task_opcode == None): 286 | continue 287 | 288 | if ("BL" in task_opcode): 289 | 290 | xref = next(idautils.CodeRefsFrom(task_func_cur, 0)) 291 | task_list_opcode = ida_ua.ua_mnem(xref) 292 | 293 | if ("MOV" in task_list_opcode): 294 | 295 | ida_name.set_name(xref, "pal_getTaskTbl", ida_name.SN_NOCHECK) 296 | 297 | target_ref = idc.get_operand_value(xref, 1) 298 | 299 | if (target_ref == None or target_ref == 0x0): 300 | idc.msg("[e] cannot get operand 1 at %x\n" % xref) 301 | continue 302 | 303 | #tbl_offset = int.from_bytes(target_ref, "little") 304 | 305 | ida_name.set_name(target_ref, "pal_TaskDescTbl", ida_name.SN_NOCHECK) 306 | 307 | if (target_ref != idaapi.BADADDR and target_ref != 0x0): 308 | 309 | #target_ref = target_ref + 0X28 310 | 311 | idc.msg("[i] pal_TaskDescTbl(): %x\n" % target_ref) 312 | 313 | #these are longer, so start later 314 | tasks = identify_task_init_start(target_ref) 315 | 316 | return tasks 317 | 318 | else: 319 | 320 | return -1 321 | 322 | 323 | # we look for the first function call after the Task string and check for the first MOVW which 324 | # loads the task table mostly hidden in a scatter part 325 | 326 | # TODO: check if I can merge this with the above somehow, diff ebtween this and above is that 327 | # in this one we don't enter a function which we do above 328 | 329 | def find_task_desc_tbl_pixel(task_func_start, task_func_end): 330 | 331 | idc.msg("[i] table discovery failed, attemping discovery for pixel like modems\n") 332 | 333 | task_func_cur = get_PALTskTm_ref() 334 | 335 | while (1): 336 | 337 | task_func_cur = idc.next_head(task_func_cur) 338 | task_opcode = ida_ua.ua_mnem(task_func_cur) 339 | 340 | # bailout 341 | if (task_func_cur >= task_func_end): 342 | idc.msg("[e] find_task_desc_tbl_pixel: end reached %x\n" % (task_func_cur)) 343 | return -1 344 | 345 | if (task_opcode == None): 346 | continue 347 | 348 | if ("BL" in task_opcode): 349 | 350 | task_func_cur = idc.next_head(task_func_cur) 351 | task_opcode = ida_ua.ua_mnem(task_func_cur) 352 | 353 | if ("MOV" in task_opcode): 354 | 355 | target_ref = idc.get_first_dref_from(task_func_cur) 356 | 357 | if (target_ref != idaapi.BADADDR and target_ref != 0x0): 358 | 359 | #target_ref = target_ref + 0x27 360 | #pad_task_struct(0xcf) 361 | 362 | idc.msg("[i] pal_TaskDescTbl: %x\n" % (target_ref)) 363 | 364 | return identify_task_init_start(target_ref) 365 | 366 | else: 367 | 368 | return -1 369 | 370 | # step 6 - find the second LDR in the function. It is the TaskDescTbl 371 | def find_task_desc_tbl(task_func_start, task_func_end): 372 | 373 | task_func_cur = task_func_start 374 | 375 | ldr_cnt = 0 376 | 377 | while (1): 378 | task_func_cur = idc.next_head(task_func_cur) 379 | task_opcode = ida_ua.ua_mnem(task_func_cur) 380 | 381 | # bailout 382 | if (task_func_cur >= task_func_end): 383 | return None 384 | 385 | # skip text chunks inside function 386 | if (task_opcode == None): 387 | #shannon_generic.DEBUG("[d] error finding pal_TaskDescTbl() at %x\n" % task_func_cur) 388 | continue 389 | 390 | if ("LDR" in task_opcode): 391 | 392 | if (ldr_cnt == 1): 393 | target_ref = idc.get_operand_value(task_func_cur, 1) 394 | tbl_offset = int.from_bytes( 395 | ida_bytes.get_bytes(target_ref, 4), "little") 396 | 397 | seg_start = idc.get_segm_start(task_func_cur) 398 | 399 | shannon_generic.DEBUG( 400 | "[d] find_task_desc_tbl: found table at %x but segment start is %x\n" % (tbl_offset, seg_start)) 401 | 402 | # sanity check to limit false positives, especially with pixel modems 403 | if (tbl_offset < seg_start): 404 | return find_task_desc_tbl_pixel(task_func_start, task_func_end) 405 | 406 | ida_name.set_name( 407 | tbl_offset, "pal_TaskDescTbl", ida_name.SN_NOCHECK) 408 | 409 | if (tbl_offset != idaapi.BADADDR and ida_bytes.is_loaded(tbl_offset)): 410 | 411 | idc.msg("[i] pal_TaskDescTbl(): %x\n" % tbl_offset) 412 | 413 | tasks = identify_task_init_start(tbl_offset) 414 | 415 | return tasks 416 | 417 | else: 418 | return find_task_desc_tbl_5g(task_func_start, task_func_end) 419 | 420 | ldr_cnt += 1 421 | 422 | # start function to avoid recruisve calls of find_struct_start 423 | def identify_task_init_start(tbl_offset): 424 | 425 | tbl_offset = find_struct_start(tbl_offset, 0) 426 | identify_task_init(tbl_offset, 0) 427 | 428 | # universal task struct finder 429 | def identify_task_init(tbl_offset, padding): 430 | 431 | MAX_TASKS = 256 432 | tasks = 0 433 | threshold = 5 434 | 435 | if (not tbl_offset): 436 | idc.msg("[e] identify_task_init: unable to detect task struct start offset\n") 437 | return [] 438 | 439 | tbl_offset_orig = tbl_offset 440 | 441 | struct_id = idc.get_struc_id("task_struct") 442 | struct_size = idc.get_struc_size(struct_id) 443 | 444 | sptr = shannon_structs.get_struct(struct_id) 445 | str_ptr = shannon_structs.get_offset_by_name(sptr, "task_name") 446 | entry_ptr = shannon_structs.get_offset_by_name(sptr, "task_entry") 447 | 448 | task_entries = [] 449 | 450 | while (tasks < MAX_TASKS): 451 | 452 | ida_bytes.del_items(tbl_offset, 0, struct_size) 453 | 454 | ida_bytes.create_struct(tbl_offset, struct_size, struct_id) 455 | 456 | str_offset = int.from_bytes(ida_bytes.get_bytes( 457 | (tbl_offset + str_ptr), 4), "little") 458 | 459 | ida_bytes.del_items(str_offset, 0, 3) 460 | ida_bytes.create_strlit(str_offset, 0, ida_nalt.STRTYPE_C) 461 | 462 | task_name_str = idc.get_strlit_contents(str_offset) 463 | 464 | entry_offset = int.from_bytes(ida_bytes.get_bytes((tbl_offset + entry_ptr), 4), "little") 465 | 466 | # break early if we met an undefined entry 467 | if (entry_offset == 0x0): 468 | shannon_generic.DEBUG( 469 | "[d] identify_task_init(): tasks %d, entry_offset is %x, breaking\n" % (tasks, entry_offset)) 470 | break 471 | 472 | # make sure we don't have many false positives here 473 | # Shortest name I found for a task was MM so far 474 | if (task_name_str and len(task_name_str) >= 2 and entry_offset > 0xFFFF): 475 | 476 | task_entries.append([task_name_str, entry_offset]) 477 | 478 | else: 479 | 480 | shannon_generic.DEBUG("[d] %x: corrupt task struct\n" % entry_offset) 481 | break 482 | 483 | tbl_offset += struct_size 484 | 485 | tasks += 1 486 | 487 | # sanity check 488 | if (padding > 0xFFFF): 489 | return [] 490 | 491 | if (tasks < threshold): 492 | 493 | padding += 1 494 | 495 | shannon_generic.DEBUG("[d] testing padding of %x, ssz: %d (found %d)\n" % 496 | (padding, struct_size, tasks)) 497 | 498 | pad_task_struct(padding) 499 | 500 | task_entries = identify_task_init(tbl_offset_orig, padding) 501 | 502 | if (len(task_entries) > threshold): 503 | for task in task_entries: 504 | 505 | if (not idc.is_code(idc.get_full_flags(entry_offset))): 506 | ida_ua.create_insn(entry_offset) 507 | 508 | #check again, if no code, realign 509 | if (not idc.is_code(idc.get_full_flags(entry_offset))): 510 | entry_offset = entry_offset - 1 # thumb 511 | ida_ua.create_insn(entry_offset) 512 | 513 | # realign function if needed 514 | task_entry_func_start = idc.get_func_attr(entry_offset, idc.FUNCATTR_START) 515 | 516 | if (task_entry_func_start != idaapi.BADADDR): 517 | shannon_funcs.function_find_boundaries(task_entry_func_start) 518 | 519 | idc.msg("[i] found task init for %s at %x\n" % (str(task[0].decode()), task[1])) 520 | 521 | ida_name.set_name(task[1], "pal_TaskInit_" + str(task[0].decode()), 522 | ida_name.SN_NOCHECK | ida_name.SN_FORCE) 523 | 524 | idc.msg("[i] %d tasks found\n" % (len(task_entries))) 525 | 526 | # list of tasks is consumed, return empty to avoid multi procession in recurse 527 | return [] 528 | 529 | return task_entries 530 | 531 | 532 | #for debugging purpose export SHANNON_WORKFLOW="NO" 533 | if (os.environ.get('SHANNON_WORKFLOW') == "NO"): 534 | idc.msg("[i] running pal reconstruct in standalone mode\n") 535 | find_pal_msg_funcs() 536 | find_pal_init() 537 | -------------------------------------------------------------------------------- /shannon_postprocess.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | 3 | # Samsung Shannon Modem Loader - Postprocessor 4 | # This script is autoamtically scheduled by the loader 5 | # Alexander Pick 2024-2025 6 | 7 | import idc 8 | import idaapi 9 | import idautils 10 | import ida_idp 11 | import ida_name 12 | import ida_idp 13 | import ida_segment 14 | import ida_name 15 | import ida_strlist 16 | import ida_nalt 17 | import ida_kernwin 18 | import ida_typeinf 19 | import ida_ua 20 | import ida_funcs 21 | 22 | import time 23 | import os 24 | import pwd 25 | import glob 26 | 27 | import shannon_pal_reconstructor 28 | import shannon_generic 29 | import shannon_mpu 30 | import shannon_scatterload 31 | import shannon_debug_traces 32 | import shannon_names 33 | import shannon_indirect_xref 34 | 35 | post_processed = False 36 | 37 | # identify the non returning function which belongs to the stack protection, if it exists 38 | # we deal with a very new BB - like 5G new. 39 | def find_cookie_monster(): 40 | 41 | seg_t = ida_segment.get_segm_by_name("MAIN_file") 42 | 43 | offset = shannon_generic.search_text(seg_t.start_ea, seg_t.end_ea, "Check a function") 44 | offset = shannon_generic.get_first_ref(offset) 45 | 46 | if (offset != None and offset != idaapi.BADADDR): 47 | 48 | idc.msg("[i] found stack protection handler at %x\n" % offset) 49 | 50 | cookie_func_start = idc.get_func_attr(offset, idc.FUNCATTR_START) 51 | 52 | ida_name.set_name(cookie_func_start, "stack_err", ida_name.SN_NOCHECK | ida_name.SN_FORCE) 53 | 54 | return True 55 | 56 | return False 57 | 58 | def find_rvct(): 59 | 60 | ARM_reference_compiler = "ARM_Compiler_" 61 | 62 | seg_t = ida_segment.get_segm_by_name("MAIN_file") 63 | 64 | rvct_addr_str = shannon_generic.search_text(seg_t.start_ea, seg_t.end_ea, "ARM RVCT") 65 | 66 | if (rvct_addr_str != None): 67 | 68 | #find start of the string for xref 69 | rvct_addr_str = idc.get_item_head(rvct_addr_str) 70 | 71 | rvct_major_ver = "" 72 | rvct_minor_ver = "" 73 | rvct_build = "" 74 | 75 | for rvct_xref in idautils.XrefsTo(rvct_addr_str, 0): 76 | # ARM RVCT %d.%d [Build %d] 77 | prev_head = idc.prev_head(rvct_xref.frm) 78 | 79 | opcode = ida_ua.ua_mnem(prev_head) 80 | 81 | if (opcode == None): 82 | continue 83 | 84 | # in case there is a split string ref etc. 85 | if (not "MOV" in opcode): 86 | continue 87 | 88 | rvct_major_ver = idc.get_operand_value(prev_head, 1) 89 | 90 | prev_head = idc.prev_head(prev_head) 91 | rvct_minor_ver = idc.get_operand_value(prev_head, 1) 92 | 93 | prev_head = idc.prev_head(prev_head) 94 | rvct_build = idc.get_operand_value(prev_head, 1) 95 | 96 | # old images have switched build and subversion 97 | if(rvct_minor_ver > rvct_build): 98 | tmp = rvct_minor_ver 99 | rvct_minor_ver = rvct_build 100 | rvct_build = tmp 101 | 102 | idc.msg("[i] build using ARM RVCT %d.%02d [Build %d]\n" % 103 | (rvct_major_ver, rvct_minor_ver, rvct_build)) 104 | 105 | # there is commonly just one ref 106 | break 107 | 108 | if (rvct_major_ver): 109 | home_dir = os.path.expanduser(f"~{pwd.getpwuid(os.geteuid())[0]}/") 110 | 111 | # find matching RVCT installs and set them as include path, if multiple are found -> ask 112 | 113 | rvct_paths = glob.glob( 114 | home_dir + "/" + ARM_reference_compiler + "*" + str(rvct_build)) 115 | 116 | for rvct in rvct_paths: 117 | 118 | header_path = rvct + "/include/" 119 | 120 | if (len(rvct_paths) > 1): 121 | ARM_inc_dir = ida_kernwin.ask_yn( 122 | 1, "HIDECANCEL\nUse " + header_path + " as include path?") 123 | else: 124 | ARM_inc_dir = True 125 | 126 | if (ARM_inc_dir): 127 | if (os.path.isdir(header_path)): 128 | ida_typeinf.set_c_header_path(header_path) 129 | idc.msg("[i] set c_header_path to %s\n" % header_path) 130 | 131 | break 132 | 133 | # apply signatures if present 134 | for sigdir in idaapi.get_ida_subdirs("sig"): 135 | 136 | rvct_sig_file = ARM_reference_compiler + "*" + str(rvct_build) + ".sig" 137 | sig_glob = sigdir + "/arm/" + rvct_sig_file 138 | sig_files = glob.glob(sig_glob) 139 | 140 | for sig_file in sig_files: 141 | 142 | if (os.path.exists(sig_file)): 143 | 144 | idc.msg("[i] applying signature file %s to all functions in database\n" % sig_file) 145 | 146 | func_cnt = 0 147 | 148 | for function_ea in idautils.Functions(): 149 | #check that the function has no name yet 150 | if("sub_" in ida_funcs.get_func_name(function_ea)): 151 | #shannon_generic.DEBUG("[d] %x\n" % function_ea) 152 | func_cnt += 1 153 | idaapi.apply_idasgn_to(sig_file, function_ea, 0) 154 | 155 | idc.msg("[i] checked %d functions for signature matches\n" % func_cnt) 156 | 157 | else: 158 | 159 | idc.msg("[i] cannot find signature file %s, please create one\n" % 160 | sig_file) 161 | 162 | class idb_finalize_hooks_t(ida_idp.IDB_Hooks): 163 | 164 | def __init__(self): 165 | ida_idp.IDB_Hooks.__init__(self) 166 | 167 | # ida's standard post processing callback 168 | def auto_empty_finally(self): 169 | 170 | global post_processed 171 | 172 | # avoid this to be triggered twice 173 | if(post_processed is True): 174 | return 175 | else: 176 | post_processed = True 177 | 178 | # avoid multiple execution paths if a segment get split due to scatter etc. 179 | boot_processed_once = False 180 | main_processed_once = False 181 | 182 | # start calculating runtime 183 | start_time = time.process_time() 184 | 185 | # from here on do the fancy stuff 186 | 187 | shannon_debug_traces.make_dbt_refs() 188 | 189 | shannon_names.restore_ss_names() 190 | shannon_names.restore_cpp_names() 191 | shannon_generic.create_long_strings() 192 | 193 | if(find_cookie_monster()): 194 | # this is a thing for newer baseband versions 195 | shannon_indirect_xref.scan_main_indirect_refs() 196 | 197 | for s in idautils.Segments(): 198 | 199 | seg_start = idc.get_segm_start(s) 200 | seg_name = idc.get_segm_name(seg_start) 201 | 202 | # add some names 203 | if (seg_name == "BOOT_file"): 204 | 205 | if(boot_processed_once is False): 206 | 207 | boot_processed_once = True 208 | 209 | shannon_generic.get_ref_set_name(seg_start, "start") 210 | 211 | if (seg_name == "MAIN_file"): 212 | 213 | if(main_processed_once is False): 214 | 215 | main_processed_once = True 216 | 217 | shannon_generic.get_ref_set_name(seg_start, "reset_v") 218 | 219 | shannon_generic.get_ref_set_name(seg_start + 4, "undef_inst_v") 220 | 221 | shannon_generic.get_ref_set_name(seg_start + 8, "soft_int_v") 222 | 223 | shannon_generic.get_ref_set_name(seg_start + 12, "prefetch_abort_v") 224 | 225 | shannon_generic.get_ref_set_name(seg_start + 16, "data_abort_v") 226 | 227 | shannon_generic.get_ref_set_name(seg_start + 20, "reserved_v") 228 | 229 | shannon_generic.get_ref_set_name(seg_start + 24, "irq_v") 230 | 231 | shannon_generic.get_ref_set_name(seg_start + 28, "fiq_v") 232 | 233 | self.memory_ranges() 234 | 235 | # it is very important to do this in the correct order 236 | # especially for new modems or the result will be left 237 | # in a weird state 238 | 239 | shannon_mpu.find_hw_init() 240 | shannon_mpu.scan_for_mrc() 241 | 242 | shannon_scatterload.find_scatter() 243 | 244 | shannon_pal_reconstructor.find_pal_msg_funcs() 245 | shannon_pal_reconstructor.find_pal_init() 246 | 247 | find_rvct() 248 | 249 | # remove "please wait ..." box and display runtime in log 250 | idaapi.hide_wait_box() 251 | 252 | for s in idautils.Segments(): 253 | 254 | # reschedule everything for a last auto analysis pass 255 | idc.plan_and_wait(idc.get_segm_start(s), idc.get_segm_end(s)) 256 | 257 | # fix strings a last time 258 | idautils.Strings().setup(strtypes=[ida_nalt.STRTYPE_C], 259 | ignore_instructions=True, minlen=6) 260 | ida_strlist.build_strlist() 261 | 262 | timediff = time.process_time() - start_time 263 | idc.msg("[i] post-processing runtime %d minutes and %d seconds\n" % 264 | ((timediff / 60), (timediff % 60))) 265 | 266 | return 267 | 268 | # this adds some memory ranges which are defined in the ARMv7 spec, it also names some "known" offsets 269 | # LSI does not follow the spec very closly but this is better than nothing and helps to with the auto analysis 270 | 271 | def memory_ranges(self): 272 | # add additional memory ranges 273 | 274 | #shannon_generic.add_memory_segment(0x00000000, 0x000FFFFF, "ITCM_low") 275 | shannon_generic.add_memory_segment(0x00100000, 0x0FEFFFFF, "EXTERNAL_1") 276 | # add_data_segment(0x04800000, 0x0000FFFF, "unkown_boot") 277 | # add_data_segment(0x04000000, 0x0001FFFF, "bootrom") 278 | 279 | #shannon_generic.add_memory_segment(0x10000000, 0x0000FFFF, "ITCM_high") 280 | #shannon_generic.add_memory_segment(0x10010000, 0x0FFEFFFF, "EXTERNAL_2") 281 | 282 | shannon_generic.add_memory_segment(0x20000000, 0x000FFFFF, "SRAM_DTCM") 283 | shannon_generic.add_memory_segment(0x20100000, 0x1FEFFFFF, "SRAM_EXTERN") 284 | 285 | ida_name.set_name(0x32000000, "unknown_0", ida_name.SN_NOCHECK) 286 | 287 | # Normaly Pheriphials but this is used differently 288 | #shannon_generic.add_memory_segment(0x40000000, 0x1EFFFFFF, "AHBP") 289 | 290 | ida_name.set_name(0x44200000, "RAM", ida_name.SN_NOCHECK) 291 | ida_name.set_name(0x47F00000, "ABOX", ida_name.SN_NOCHECK) 292 | 293 | shannon_generic.add_memory_segment(0x60000000, 0x3fffffff, "SRAM_EXTERN") 294 | 295 | ida_name.set_name(0x80000000, "unknown_1", ida_name.SN_NOCHECK) 296 | ida_name.set_name(0x81000000, "unknown_2", ida_name.SN_NOCHECK) 297 | ida_name.set_name(0x81002000, "unknown_3", ida_name.SN_NOCHECK) 298 | ida_name.set_name(0x84000000, "UART", ida_name.SN_NOCHECK) 299 | ida_name.set_name(0x85000000, "unknown_4", ida_name.SN_NOCHECK) 300 | ida_name.set_name(0x8F900000, "unknown_5", ida_name.SN_NOCHECK) 301 | ida_name.set_name(0x8FC30000, "USI_1", ida_name.SN_NOCHECK) 302 | ida_name.set_name(0x8FC22000, "USI_2", ida_name.SN_NOCHECK) 303 | ida_name.set_name(0x8FC60000, "USI_3", ida_name.SN_NOCHECK) 304 | ida_name.set_name(0x8FD20000, "USI_4", ida_name.SN_NOCHECK) 305 | 306 | ida_name.set_name(0xD0800000, "unknown_6", ida_name.SN_NOCHECK) 307 | 308 | ida_name.set_name(0xC1000000, "TWOG_1", ida_name.SN_NOCHECK) 309 | ida_name.set_name(0xC1001000, "TWOG_2", ida_name.SN_NOCHECK) 310 | ida_name.set_name(0xC1800000, "MARCONI_1", ida_name.SN_NOCHECK) 311 | ida_name.set_name(0xC2000000, "MARCONI_2", ida_name.SN_NOCHECK) 312 | ida_name.set_name(0xCE000000, "unknown_7", ida_name.SN_NOCHECK) 313 | 314 | #shannon_generic.add_memory_segment(0xA0000000, 0x3fffffff, "EXT_DEVICE") 315 | 316 | # 0xE0000000-0xFFFFFFFF - system level use 317 | #shannon_generic.add_memory_segment(0xE0000000, 0x1FFFFFFF, "SYSTEM") 318 | 319 | # 0xE0000000-0xE00FFFFF - private peripheral bus (PPB) 320 | #shannon_generic.add_memory_segment(0xE0000000, 0x000FFFFF, "PPB") 321 | 322 | shannon_generic.add_memory_segment(0xE0001000, 0x00000FFF, "PPB_DW") 323 | shannon_generic.add_memory_segment(0xE0002000, 0x00000FFF, "PPB_BP") 324 | shannon_generic.add_memory_segment(0xE000E000, 0x00000CFF, "PPB_NVIC") 325 | shannon_generic.add_memory_segment(0xE000ED00, 0x000002FF, "PPB_DBGCTL") 326 | shannon_generic.add_memory_segment(0xE0005000, 0x00000FFF, "PPB") 327 | shannon_generic.add_memory_segment(0xE00FF000, 0x00000FFF, "ROM_TABLE") 328 | 329 | # 0xE000E000 to 0xE000EFFF - system control space (SCS) 330 | ida_name.set_name( 331 | 0xE000E000, "system control space (SCS)", ida_name.SN_NOCHECK) 332 | 333 | # system level 334 | shannon_generic.add_memory_segment(0xEC000000, 0x0000FFFF, "GLINK") 335 | 336 | #shannon_generic.add_memory_segment(0xF0000000, 0x0FFFFFFF, "unknown_8") 337 | 338 | 339 | idb_hooks = idb_finalize_hooks_t() 340 | idb_hooks.hook() 341 | idc.msg("[i] Shannon postprocessor scheduled.\n") 342 | 343 | #show a "please wait .." box 344 | if (not shannon_generic.is_debug): 345 | idaapi.show_wait_box('HIDECANCEL\nPost-processing modem image, please wait...') 346 | -------------------------------------------------------------------------------- /shannon_scatterload.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | 3 | # Samsung Shannon Modem Loader - Scatter Processor 4 | # This script is autoamtically executed by the loader 5 | # Alexander Pick 2024-2025 6 | 7 | import idc 8 | import idaapi 9 | import ida_ua 10 | import ida_idp 11 | import ida_bytes 12 | import idautils 13 | import ida_name 14 | import ida_funcs 15 | import ida_auto 16 | 17 | import shannon_generic 18 | import shannon_structs 19 | 20 | import os 21 | 22 | # process the scatter load function 23 | def process_scatterload(reset_func_cur): 24 | 25 | scatter_target = next(idautils.CodeRefsFrom(reset_func_cur, 0)) 26 | 27 | scatterload = idc.get_operand_value(scatter_target, 0) 28 | 29 | if ((not scatterload) or (scatterload < 0xFFFF)): 30 | idc.msg("[e] scatter table not found\n") 31 | return None 32 | 33 | idc.msg("[i] scatterload(): %x\n" % (scatterload)) 34 | 35 | ida_name.set_name(scatterload, "scatterload", ida_name.SN_NOCHECK) 36 | 37 | return scatterload 38 | 39 | # recreate function at the given offset, i.e. if something was re-aligned around it 40 | # this has to be done sometimes in RT optimized code to get proper results 41 | def recreate_function(op): 42 | 43 | ida_funcs.del_func(op) 44 | ida_bytes.del_items(op) 45 | idc.create_insn(op) 46 | ida_funcs.add_func(op) 47 | func_o = ida_funcs.get_func(op) 48 | 49 | if func_o is not None: 50 | ida_funcs.reanalyze_function(func_o) 51 | ida_auto.auto_wait() 52 | 53 | # create the scatter table 54 | def create_scatter_tbl(scatterload): 55 | 56 | recreate_function(scatterload) 57 | 58 | scatter_tbl = idc.get_operand_value(scatterload, 1) 59 | 60 | start_bytes = ida_bytes.get_bytes(scatter_tbl, 4) 61 | stop_bytes = ida_bytes.get_bytes((scatter_tbl + 4), 4) 62 | 63 | if (start_bytes == None or stop_bytes == None): 64 | idc.msg("[e] unable to create scatter table\n") 65 | return 66 | 67 | scatter_start = int.from_bytes(start_bytes, "little") 68 | scatter_stop = int.from_bytes(stop_bytes, "little") 69 | 70 | scatter_start = (scatter_start + scatter_tbl) & 0xFFFFFFFF 71 | scatter_stop = (scatter_stop + scatter_tbl) & 0xFFFFFFFF 72 | scatter_size = scatter_stop - scatter_start 73 | 74 | struct_id = idc.get_struc_id("scatter") 75 | struct_size = idc.get_struc_size(struct_id) 76 | 77 | idc.msg("[i] scatter table at %x, size %d, table has %d entries\n" % 78 | (scatter_start, scatter_size, scatter_size / struct_size)) 79 | 80 | ida_name.set_name(scatter_start, "scatter_tbl", ida_name.SN_NOCHECK) 81 | 82 | tbl = read_scattertbl(scatter_start, scatter_size) 83 | 84 | op_list = [] 85 | 86 | # first round of processing, define ops (these are the functions which process the scatter data) 87 | for entry in tbl: 88 | 89 | op = entry[3] 90 | # realign if we are off by one here due to thumb and stuff 91 | if (op % 4): 92 | op += 1 93 | 94 | recreate_function(op) 95 | 96 | op_list.append(op) 97 | 98 | # make a "unique" list by converting it to a set and back 99 | op_list = list(set(op_list)) 100 | 101 | ops = find_scatter_functions(op_list) 102 | process_scattertbl(scatter_start, scatter_size, ops) 103 | 104 | # find the scatter functions in database 105 | def find_scatter_functions(op_list): 106 | 107 | # possible scatter ops 108 | scatter_null = None 109 | scatter_zero = None 110 | scatter_copy = None 111 | scatter_comp = None 112 | 113 | # I am aware that there are some patterns and stuff to identify these which originate in basespec research 114 | # by KAIST. At this point we already have a very small amout of candidates, decompression algorithms use 115 | # multiple loops, zeroinit will zero a couple of regs, and cpy will loop to itself. This is easy enough to 116 | # tell the scatterload functions apart using metrics instead of a pattern. 117 | 118 | for op in op_list: 119 | 120 | # get boundaries of function 121 | idc.msg("[i] processing scatter function at %x\n" % op) 122 | 123 | found = False 124 | 125 | # process functions 126 | 127 | #recreate_function(op) 128 | 129 | metrics = shannon_generic.get_metric(op) 130 | 131 | # shannon_generic.print_metrics(op, metrics) 132 | 133 | scatter_func_offset = op 134 | 135 | opcode = ida_ua.ua_mnem(scatter_func_offset) 136 | if (opcode == "MOVS"): 137 | scatter_func_offset = idc.next_head(scatter_func_offset) 138 | opcode = ida_ua.ua_mnem(scatter_func_offset) 139 | if (opcode == "MOVS"): 140 | # we found zero init 141 | ida_name.set_name(op, "scatterload_zeroinit", 142 | ida_name.SN_NOCHECK | ida_name.SN_FORCE) 143 | 144 | found = True 145 | 146 | if (scatter_zero != None): 147 | idc.msg("[e] scatterload_zeroinit() found at %x, already found at %x before\n" % ( 148 | op, scatter_zero)) 149 | else: 150 | scatter_zero = op 151 | idc.msg("[i] found scatterload_zeroinit() at %x\n" % op) 152 | continue 153 | 154 | for branch in metrics[0]: 155 | 156 | operand = idc.get_operand_value(branch, 0) 157 | 158 | if (operand == op): 159 | # we found a loop to the first inst, this is copy 160 | ida_name.set_name(op, "scatterload_copy", 161 | ida_name.SN_NOCHECK | ida_name.SN_FORCE) 162 | 163 | found = True 164 | 165 | if (scatter_copy != None): 166 | idc.msg("[e] scatterload_copy found() at %x, already found at %x before\n" % ( 167 | op, scatter_copy)) 168 | else: 169 | scatter_copy = op 170 | idc.msg("[i] found scatterload_copy() at %x\n" % op) 171 | break 172 | 173 | if ((len(metrics[0]) >= 3) and (found == False)): 174 | 175 | # decompression requires multiple loops 176 | ida_name.set_name(op, "scatterload_decompress", 177 | ida_name.SN_NOCHECK | ida_name.SN_FORCE) 178 | 179 | found = True 180 | 181 | if (scatter_comp != None): 182 | idc.msg("[e] scatterload_decompress() found at %x, already found at %x before\n" % ( 183 | op, scatter_comp)) 184 | else: 185 | scatter_comp = op 186 | idc.msg("[i] found scatterload_decompress() at %x\n" % op) 187 | continue 188 | 189 | # if it's nothing of the above, it is null 190 | if (found == False): 191 | ida_name.set_name(op, "scatterload_null", 192 | ida_name.SN_NOCHECK | ida_name.SN_FORCE) 193 | scatter_null = op 194 | 195 | return [scatter_null, scatter_zero, scatter_copy, scatter_comp] 196 | 197 | # scatter struct 198 | # 0 - src 199 | # 1 - dst 200 | # 2 - size 201 | # 3 - op 202 | 203 | #process the scatter table 204 | def process_scattertbl(scatter_start, scatter_size, ops): 205 | 206 | tbl = read_scattertbl(scatter_start, scatter_size) 207 | 208 | scatter_id = 0 209 | 210 | for entry in tbl: 211 | 212 | idc.msg("[i] processing scatter - src:%x dst: %x size: %d op: %x\n" % 213 | (entry[0], entry[1], entry[2], entry[3])) 214 | 215 | index = 0 216 | for op in ops: 217 | # check if the requested op matches a known function offset 218 | if (entry[3] == op): 219 | # if it does, at which index of the op list? 220 | match index: 221 | case 0: 222 | # shannon_generic.DEBUG("[d] scatter_null\n") 223 | 224 | # just adding these won't invaldiate any data in it, but allows us to see what 225 | # was supposed to be mapped or zerored out 226 | if (entry[2] > 0): 227 | shannon_generic.add_memory_segment(entry[1], entry[2], 228 | "SCATTERNULL_" + str(scatter_id), 229 | "CODE", False) 230 | case 1: 231 | # shannon_generic.DEBUG("[d] scatter_zero\n") 232 | 233 | if (entry[2] > 0): 234 | shannon_generic.add_memory_segment(entry[1], entry[2], 235 | "SCATTERZERO_" + str(scatter_id), 236 | "CODE", False) 237 | case 2: 238 | 239 | # shannon_generic.DEBUG("[d] scatter_copy\n") 240 | # copy in idb 241 | 242 | if (entry[2] > 0): 243 | 244 | # create a new segment for the scatter and copy bytes over 245 | shannon_generic.add_memory_segment(entry[1], entry[2], 246 | "SCATTER_" + str(scatter_id), 247 | "CODE", False) 248 | 249 | shannon_generic.DEBUG("[d] src: %x cnt: %d dst: %x " % 250 | (entry[0], entry[2], entry[1])) 251 | 252 | chunk = ida_bytes.get_bytes(entry[0], entry[2]) 253 | 254 | shannon_generic.DEBUG("len: %s\n" % (len(chunk))) 255 | 256 | ida_bytes.put_bytes(entry[1], chunk) 257 | 258 | case 3: # decpmpression 259 | 260 | chunk = scatterload_decompress(entry[0], entry[2]) 261 | 262 | shannon_generic.add_memory_segment(entry[1], len(chunk), 263 | "SCATCOMP_" + str(scatter_id), 264 | "CODE", False) 265 | 266 | idaapi.patch_bytes(entry[1], chunk) 267 | 268 | idc.msg("[i] decompressed %d bytes, from %x to %x\n" 269 | % (len(chunk), entry[0], entry[1])) 270 | 271 | index += 1 272 | scatter_id += 1 273 | 274 | # read and pre-process the scatter table 275 | def read_scattertbl(scatter_start, scatter_size): 276 | 277 | struct_id = idc.get_struc_id("scatter") 278 | struct_size = idc.get_struc_size(struct_id) 279 | 280 | tbl = [] 281 | 282 | scatter_offset = scatter_start 283 | 284 | sptr = shannon_structs.get_struct(struct_id) 285 | src_ptr = shannon_structs.get_offset_by_name(sptr, "src") 286 | dst_ptr = shannon_structs.get_offset_by_name(sptr, "dst") 287 | size_ptr = shannon_structs.get_offset_by_name(sptr, "size") 288 | op_ptr = shannon_structs.get_offset_by_name(sptr, "op") 289 | 290 | while (scatter_offset < (scatter_start + scatter_size)): 291 | 292 | entry = [] 293 | 294 | ida_bytes.del_items(scatter_offset, 0, struct_size) 295 | ida_bytes.create_struct(scatter_offset, struct_size, struct_id) 296 | 297 | entry.append(int.from_bytes(ida_bytes.get_bytes( 298 | (scatter_offset + src_ptr), 4), "little")) 299 | 300 | entry.append(int.from_bytes(ida_bytes.get_bytes( 301 | (scatter_offset + dst_ptr), 4), "little")) 302 | 303 | entry.append(int.from_bytes(ida_bytes.get_bytes( 304 | (scatter_offset + size_ptr), 4), "little")) 305 | 306 | entry.append(int.from_bytes(ida_bytes.get_bytes( 307 | (scatter_offset + op_ptr), 4), "little")) 308 | 309 | tbl.append(entry) 310 | 311 | scatter_offset += struct_size 312 | 313 | return tbl 314 | 315 | # find scatter related code 316 | def find_scatter(): 317 | 318 | idc.msg("[i] trying to find scatter functions\n") 319 | 320 | mode_switch = 0 321 | 322 | reset_vector_offset = idc.get_name_ea_simple("reset_v") 323 | 324 | # get boundaries of function 325 | reset_func_start = idc.get_func_attr(reset_vector_offset, idc.FUNCATTR_START) 326 | reset_func_end = idc.get_func_attr(reset_vector_offset, idc.FUNCATTR_END) 327 | 328 | if (reset_func_start != idaapi.BADADDR and reset_func_end != idaapi.BADADDR): 329 | 330 | func_cur = reset_func_start 331 | 332 | while (1): 333 | func_cur = idc.next_head(func_cur) 334 | opcode = ida_ua.ua_mnem(func_cur) 335 | 336 | # bailout 337 | if (opcode == None): 338 | continue 339 | 340 | if ("MSR" in opcode): 341 | cpsr = idc.get_operand_value(func_cur, 0) 342 | cpsr_value = idc.get_operand_value(func_cur, 1) 343 | 344 | if (cpsr == -1): 345 | continue 346 | 347 | cpsr_str = ida_idp.get_reg_name(cpsr, 0) 348 | 349 | if ("CPSR" in cpsr_str): 350 | if (cpsr_value == 0xD3): 351 | 352 | if (mode_switch == 0): 353 | mode_switch += 1 354 | continue 355 | 356 | #shannon_generic.DEBUG("[d] second supervisor mode switch found: %x\n" % func_cur) 357 | 358 | reset_func_cur = func_cur 359 | 360 | while (1): 361 | reset_func_cur = idc.next_head(reset_func_cur) 362 | reset_opcode = ida_ua.ua_mnem(reset_func_cur) 363 | 364 | # bailout 365 | if (reset_opcode == None): 366 | idc.msg("[e] no reset_opcode\n") 367 | return 368 | 369 | # scatterload is the first branch in main, right after the crt (reset vector) 370 | if ("B" == reset_opcode): 371 | 372 | shannon_generic.DEBUG( 373 | "[d] scatter candidate at %x\n" % reset_func_cur) 374 | 375 | # it's all about beeing flexible ... 376 | b_target = idc.get_operand_value(reset_func_cur, 0) 377 | 378 | next_opcode = ida_ua.ua_mnem(b_target) 379 | if (next_opcode == None): 380 | # shannon_generic.DEBUG("[d] error in scatter branch\n") 381 | return 382 | 383 | if ("B" == next_opcode): 384 | shannon_generic.DEBUG( 385 | "[d] additional jump at %x\n" % b_target) 386 | # new BB jump twice, so we check that here and follow the white rabbit 387 | reset_func_cur = b_target 388 | 389 | scatterload = process_scatterload(reset_func_cur) 390 | 391 | if (scatterload): 392 | create_scatter_tbl(scatterload) 393 | 394 | return 395 | 396 | # abort if nothing was found 397 | if (reset_func_cur >= reset_func_end): 398 | return 399 | 400 | if (func_cur >= reset_func_end): 401 | return 402 | 403 | # decompressions are always "fun" to re, thanks roxfan for a hint to fix an 404 | # anoying error in this 405 | 406 | # after reversing it I think it is LZ77 which is also one of the compressions 407 | # the ARM linker supports next to RLE 408 | 409 | # decompress from src to dst, input buffer cnt - costed me an arm and a leg to 410 | # get it working but it now uncompresses 100% of the buffer and does it correctly 411 | def scatterload_decompress(src, cnt): 412 | 413 | src_index = 0 414 | dst_index = 0 415 | 416 | output_buffer = bytearray(cnt) 417 | 418 | while (src_index < cnt): 419 | 420 | # read the current byte from the source and increment the source index 421 | cur_byte = ida_bytes.get_byte(src + src_index) 422 | src_index += 1 423 | 424 | # extract the lower 2 bits of the current byte 425 | cpy_bytes = cur_byte & 3 426 | 427 | # if cpy_bytes is 0, read the next byte from the source 428 | if (cpy_bytes == 0): 429 | cpy_bytes = ida_bytes.get_byte(src + src_index) 430 | src_index += 1 431 | 432 | # extract the upper 4 bits of the current byte to get 'high_4_b' 433 | high_4_b = cur_byte >> 4 434 | 435 | # if 'high_4_b' is 0, read the next byte from the source 436 | if (high_4_b == 0): 437 | high_4_b = ida_bytes.get_byte(src + src_index) 438 | src_index += 1 439 | 440 | # copy x bytes from the source to the destination 441 | for _ in range(cpy_bytes): 442 | 443 | if (dst_index >= cnt): 444 | #shannon_generic.DEBUG("[d] out of bound write to %x/%x\n" % (cnt, dst_index)) 445 | return bytes(list(output_buffer)) 446 | 447 | if (dst_index >= len(output_buffer)): 448 | output_buffer = output_buffer.ljust(dst_index + 1, b"\x00") 449 | 450 | # copy byte from source to destination 451 | byte = ida_bytes.get_byte(src + src_index) 452 | output_buffer[dst_index] = byte 453 | 454 | dst_index += 1 455 | src_index += 1 456 | 457 | # if 'high_4_b' is non-zero, perform additional operations 458 | if (high_4_b): 459 | 460 | # read the offset byte from the source and increment the source index 461 | offset = ida_bytes.get_byte(src + src_index) 462 | src_index += 1 463 | 464 | # extract bits 2 and 3 from the current byte 465 | bit_2_3 = cur_byte & 0xC 466 | 467 | # calculate the source pointer for backward copy 468 | src_ptr = dst_index - offset 469 | 470 | # if both are set 0x12 == b1100 471 | if (bit_2_3 == 0xC): 472 | 473 | # if bits set, get one more byte 474 | bit_2_3 = ida_bytes.get_byte(src + src_index) 475 | src_index += 1 476 | src_ptr -= 256 * bit_2_3 477 | 478 | else: 479 | # otherwise, adjust the source pointer based on extracted bits 480 | src_ptr -= 64 * bit_2_3 481 | 482 | # copy 'high_4_b + 1' bytes from the previously decompressed data 483 | for _ in range(high_4_b + 1): 484 | 485 | # buffer max bailout 486 | if (dst_index >= cnt): 487 | #shannon_generic.DEBUG("[d] out of bound write to %x/%x/%x\n" % (cnt, dst_index, src_index)) 488 | return bytes(list(output_buffer)) 489 | 490 | if (dst_index >= len(output_buffer)): 491 | output_buffer = output_buffer.ljust(dst_index + 1, b"\x00") 492 | 493 | # some possible error conditions 494 | if (src_ptr > cnt): 495 | #shannon_generic.DEBUG("[d] out of bound read to %x/%x/%x\n" % (cnt, src_ptr, src_index)) 496 | return bytes(list(output_buffer)) 497 | 498 | if (src_ptr < 0): 499 | #shannon_generic.DEBUG("[d] negativ read %x/%x\n" % (src_ptr, src_index)) 500 | return bytes(list(output_buffer)) 501 | 502 | # copy byte from previously decompressed data to the current destination 503 | output_buffer[dst_index] = output_buffer[src_ptr] 504 | 505 | dst_index += 1 506 | src_ptr += 1 507 | 508 | # return the final source index after decompression 509 | return bytes(list(output_buffer)) 510 | 511 | 512 | # for debugging purpose export SHANNON_WORKFLOW="NO" 513 | if (os.environ.get('SHANNON_WORKFLOW') == "NO"): 514 | idc.msg("[i] running scatter load in standalone mode") 515 | find_scatter() 516 | -------------------------------------------------------------------------------- /shannon_structs.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | 3 | # Samsung Shannon Modem Loader - Structs 4 | # A lean IDA Pro loader for fancy baseband research 5 | # Alexander Pick 2024-2025 6 | 7 | import idaapi 8 | import ida_nalt 9 | import idc 10 | import ida_typeinf 11 | 12 | import shannon_generic 13 | 14 | # IDA 9.0 requires some new code to work with structs as these API were removed 15 | 16 | # get struct by id 17 | def get_struct(tid): 18 | # this is super ugly but I have no better solution at the moment since get_type_by_tid 19 | # does not exist in 8.3 yet so we need to use ida_struct for backwards compatibility 20 | if(idaapi.IDA_SDK_VERSION < 900): 21 | try: 22 | return __import__("ida_struct").get_struc(tid) 23 | except: 24 | idc.msg("[e] failed to import legacy ida_struct\n") 25 | return None 26 | else: 27 | tif = None 28 | 29 | tif = ida_typeinf.tinfo_t() 30 | tif.get_type_by_tid(tid) 31 | 32 | if (tif): 33 | if (tif.is_struct()): 34 | return tif 35 | else: 36 | idc.msg("[e] get_struct(): is no struct\n") 37 | 38 | idc.msg("[e] get_struct(): failed to get struct\n") 39 | 40 | return -1 41 | 42 | # get member by name 43 | def get_member_by_name(tif, name): 44 | if(idaapi.IDA_SDK_VERSION < 900): 45 | return __import__("ida_struct").get_member_by_name(tif, name) 46 | else: 47 | if (tif): 48 | if (not tif.is_struct()): 49 | idc.msg("[e] get_member_by_name(tif, %s): tif is not a struct\n" % name) 50 | return None 51 | else: 52 | idc.msg("[e] get_member_by_name(tif, %s): tif is not defined\n" % name) 53 | 54 | udm = ida_typeinf.udm_t() 55 | udm.name = name 56 | idx = tif.find_udm(udm, ida_typeinf.STRMEM_NAME) 57 | if (idx != -1): 58 | shannon_generic.DEBUG("[d] get_member_by_name(%s) idx: %x\n" % (name, idx)) 59 | return udm 60 | 61 | idc.msg("[e] get_member_by_name(tif, %s) connot find member in struct\n" % name) 62 | return None 63 | 64 | # get offset of member by name 65 | # offset needs to be devided by 8 for some reason 66 | def get_offset_by_name(tif, name): 67 | member = get_member_by_name(tif, name) 68 | if (member): 69 | if(idaapi.IDA_SDK_VERSION < 900): 70 | return (member.soff) 71 | else: 72 | shannon_generic.DEBUG("[d] get_offset_by_name(%s): %x\n" % (name, member.offset)) 73 | if(member.offset == 0): 74 | return 0 75 | return (member.offset // 8) 76 | idc.msg("[e] get_offset_by_name(tif, %s): cannot get offset\n" % name) 77 | return None 78 | 79 | # get_max_offset to maintain 80 | # finds the end of the struct to append to 81 | def get_max_offset(tif): 82 | if(idaapi.IDA_SDK_VERSION < 900): 83 | return __import__("ida_struct").get_max_offset(tif) 84 | else: 85 | if (tif): 86 | if (tif.is_struct()): 87 | return tif.get_size() 88 | elif (tif.is_union()): 89 | return tif.get_udt_nmembers() 90 | else: 91 | idc.msg("[e] get_max_offset(): tif is invalid\n") 92 | 93 | idc.msg("[e] get_max_offset(): connot get max offset\n") 94 | return -1 95 | 96 | # replacement for add_struc_member during IDA9 porting, kept for debugging 97 | def add_struc_member(tid, name, offset, flag, typeid, nbytes): 98 | #shannon_generic.DEBUG("[d] add_struc_member(tid, %s, %x, %x, %x, %d) \n" % (name, offset, flag, typeid, nbytes)) 99 | # if(idaapi.IDA_SDK_VERSION >= 900): 100 | # tif = get_struct(tid) 101 | # if(tif): 102 | # sid = idc.get_struc_id(tif.name) 103 | # idc.add_struc_member(sid, name, offset, flag, typeid, nbytes) 104 | # return 105 | # else: 106 | # idc.msg("[e] add_struc_member(): connot add member, unknown tif\n") 107 | # else: 108 | idc.add_struc_member(tid, name, offset, flag, typeid, nbytes) 109 | 110 | # ARM scatter structure 111 | def add_scatter_struct(): 112 | 113 | tid = idc.add_struc(0, "scatter", 0) 114 | mt = -1 115 | 116 | add_struc_member(tid, "src", -1, idaapi.FF_DATA | 117 | idaapi.FF_DWORD | idaapi.FF_0OFF, mt, 4) 118 | add_struc_member(tid, "dst", -1, idaapi.FF_DATA | 119 | idaapi.FF_DWORD | idaapi.FF_0OFF, mt, 4) 120 | add_struc_member(tid, "size", -1, 121 | idaapi.FF_DATA | idaapi.FF_DWORD, mt, 4) 122 | add_struc_member(tid, "op", -1, idaapi.FF_DATA | 123 | idaapi.FF_DWORD | idaapi.FF_0OFF, mt, 4) 124 | 125 | # debug trace structure 126 | def add_dbt_struct(): 127 | 128 | # DBT entries with file and string ref 129 | tid = idc.add_struc(0, "dbg_trace", 0) 130 | mt = -1 131 | 132 | add_struc_member(tid, "head", -1, 133 | idaapi.FF_DWORD, mt, 4) 134 | add_struc_member(tid, "group", -1, 135 | idaapi.FF_DATA | idaapi.FF_DWORD, mt, 4) 136 | add_struc_member(tid, "channel", -1, 137 | idaapi.FF_DATA | idaapi.FF_DWORD, mt, 4) 138 | add_struc_member(tid, "num_param", -1, 139 | idaapi.FF_DATA | idaapi.FF_DWORD, mt, 4) 140 | add_struc_member(tid, "msg_ptr", -1, idaapi.FF_DATA | 141 | idaapi.FF_DWORD | idaapi.FF_0OFF, ida_nalt.STRTYPE_C, 4) 142 | add_struc_member(tid, "line", -1, 143 | idaapi.FF_DATA | idaapi.FF_DWORD, mt, 4) 144 | add_struc_member(tid, "file", -1, 145 | idaapi.FF_DWORD, mt, 4) 146 | 147 | # MPU table structure 148 | def add_mpu_region_struct(): 149 | 150 | tid = idc.add_struc(0, "mpu_region", 0) 151 | mt = -1 152 | 153 | add_struc_member(tid, "num", -1, 154 | idaapi.FF_DATA | idaapi.FF_DWORD, mt, 4) 155 | add_struc_member(tid, "addr", -1, idaapi.FF_DATA | 156 | idaapi.FF_DWORD | idaapi.FF_0OFF, mt, 4) 157 | add_struc_member(tid, "size", -1, 158 | idaapi.FF_DATA | idaapi.FF_DWORD, mt, 4) 159 | add_struc_member(tid, "tex", -1, idaapi.FF_DATA | 160 | idaapi.FF_DWORD, mt, 4) # Type Extension (TEX) 161 | add_struc_member(tid, "ap", -1, idaapi.FF_DATA | 162 | idaapi.FF_DWORD, mt, 4) # Data access permission 163 | add_struc_member(tid, "xn", -1, idaapi.FF_DATA | 164 | idaapi.FF_DWORD, mt, 4) # Execute never 165 | add_struc_member(tid, "se", -1, idaapi.FF_DATA | 166 | idaapi.FF_DWORD, mt, 4) # Shareable (S) 167 | add_struc_member(tid, "ce", -1, idaapi.FF_DATA | 168 | idaapi.FF_DWORD, mt, 4) # Cacheable (C) 169 | add_struc_member(tid, "be", -1, idaapi.FF_DATA | 170 | idaapi.FF_DWORD, mt, 4) # Bufferable (B) 171 | add_struc_member(tid, "en", -1, idaapi.FF_DATA | 172 | idaapi.FF_DWORD, mt, 4) # enabled 173 | 174 | # Task Structure 175 | def add_task_struct(): 176 | 177 | tid = idc.add_struc(0, "task_struct", 0) 178 | mt = -1 179 | 180 | add_struc_member(tid, "gap_0", -1, 181 | idaapi.FF_BYTE, mt, 8) 182 | add_struc_member(tid, "state", -1, 183 | idaapi.FF_BYTE, mt, 1) 184 | add_struc_member(tid, "flag", -1, 185 | idaapi.FF_BYTE, mt, 1) 186 | add_struc_member(tid, "gap_1", -1, 187 | idaapi.FF_BYTE, mt, 1) 188 | add_struc_member(tid, "gap_2", -1, 189 | idaapi.FF_BYTE, mt, 1) 190 | add_struc_member(tid, "task_num", -1, 191 | idaapi.FF_DWORD, mt, 4) 192 | add_struc_member(tid, "stack", -1, 193 | idaapi.FF_DWORD, mt, 4) 194 | add_struc_member(tid, "gap_3", -1, 195 | idaapi.FF_BYTE, mt, 0x10) 196 | add_struc_member(tid, "task_name", -1, 197 | idaapi.FF_DATA | idaapi.FF_DWORD | idaapi.FF_0OFF, ida_nalt.STRTYPE_C, 4) 198 | add_struc_member(tid, "priority", -1, 199 | idaapi.FF_BYTE, mt, 1) 200 | add_struc_member(tid, "gap_4", -1, 201 | idaapi.FF_BYTE, mt, 3) 202 | add_struc_member(tid, "stack_size", -1, 203 | idaapi.FF_DWORD, mt, 4) 204 | add_struc_member(tid, "task_entry", -1, 205 | idaapi.FF_DATA | idaapi.FF_DWORD | idaapi.FF_0OFF, mt, 4) 206 | add_struc_member(tid, "task_init", -1, 207 | idaapi.FF_DWORD, mt, 4) 208 | add_struc_member(tid, "gap_5", -1, 209 | idaapi.FF_BYTE, mt, 4) 210 | add_struc_member(tid, "gap_6", -1, 211 | idaapi.FF_BYTE, mt, 0x24) 212 | add_struc_member(tid, "gap_7", -1, 213 | idaapi.FF_BYTE, mt, 0x28) 214 | add_struc_member(tid, "gap_8", -1, 215 | idaapi.FF_BYTE, mt, 0x78) 216 | add_struc_member(tid, "gap_9", -1, 217 | idaapi.FF_BYTE, mt, 4) 218 | add_struc_member(tid, "gap_10", -1, 219 | idaapi.FF_BYTE, mt, 1) 220 | add_struc_member(tid, "gap_11", -1, 221 | idaapi.FF_BYTE, mt, 1) 222 | add_struc_member(tid, "gap_12", -1, 223 | idaapi.FF_BYTE, mt, 1) 224 | add_struc_member(tid, "gap_13", -1, 225 | idaapi.FF_BYTE, mt, 1) 226 | add_struc_member(tid, "padding", -1, 227 | idaapi.FF_BYTE, mt, 16) 228 | -------------------------------------------------------------------------------- /sig/ARM_Compiler_3.1_b794.sig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-pick/shannon_modem_loader/2dc27f01782eaaa55ef626e15ef6b4154bf0e392/sig/ARM_Compiler_3.1_b794.sig -------------------------------------------------------------------------------- /sig/ARM_Compiler_4.0_b591.sig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-pick/shannon_modem_loader/2dc27f01782eaaa55ef626e15ef6b4154bf0e392/sig/ARM_Compiler_4.0_b591.sig -------------------------------------------------------------------------------- /sig/ARM_Compiler_4.1_b894.sig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-pick/shannon_modem_loader/2dc27f01782eaaa55ef626e15ef6b4154bf0e392/sig/ARM_Compiler_4.1_b894.sig -------------------------------------------------------------------------------- /sig/ARM_Compiler_5.01_b113.sig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-pick/shannon_modem_loader/2dc27f01782eaaa55ef626e15ef6b4154bf0e392/sig/ARM_Compiler_5.01_b113.sig -------------------------------------------------------------------------------- /sig/ARM_Compiler_5.01_b94.sig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-pick/shannon_modem_loader/2dc27f01782eaaa55ef626e15ef6b4154bf0e392/sig/ARM_Compiler_5.01_b94.sig -------------------------------------------------------------------------------- /sig/ARM_Compiler_5.03_b24.sig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-pick/shannon_modem_loader/2dc27f01782eaaa55ef626e15ef6b4154bf0e392/sig/ARM_Compiler_5.03_b24.sig -------------------------------------------------------------------------------- /sig/ARM_Compiler_5.03_b76.sig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-pick/shannon_modem_loader/2dc27f01782eaaa55ef626e15ef6b4154bf0e392/sig/ARM_Compiler_5.03_b76.sig -------------------------------------------------------------------------------- /sig/ARM_Compiler_5.04_b27.sig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-pick/shannon_modem_loader/2dc27f01782eaaa55ef626e15ef6b4154bf0e392/sig/ARM_Compiler_5.04_b27.sig -------------------------------------------------------------------------------- /sig/ARM_Compiler_5.04u1_b49.sig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-pick/shannon_modem_loader/2dc27f01782eaaa55ef626e15ef6b4154bf0e392/sig/ARM_Compiler_5.04u1_b49.sig -------------------------------------------------------------------------------- /sig/ARM_Compiler_5.04u2_b82.sig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-pick/shannon_modem_loader/2dc27f01782eaaa55ef626e15ef6b4154bf0e392/sig/ARM_Compiler_5.04u2_b82.sig -------------------------------------------------------------------------------- /sig/ARM_Compiler_5.05u1_b106.sig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-pick/shannon_modem_loader/2dc27f01782eaaa55ef626e15ef6b4154bf0e392/sig/ARM_Compiler_5.05u1_b106.sig -------------------------------------------------------------------------------- /sig/ARM_Compiler_5.06u1_b61.sig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-pick/shannon_modem_loader/2dc27f01782eaaa55ef626e15ef6b4154bf0e392/sig/ARM_Compiler_5.06u1_b61.sig -------------------------------------------------------------------------------- /sig/ARM_Compiler_5.06u3_b300.sig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-pick/shannon_modem_loader/2dc27f01782eaaa55ef626e15ef6b4154bf0e392/sig/ARM_Compiler_5.06u3_b300.sig -------------------------------------------------------------------------------- /sig/ARM_Compiler_5.06u4_b422.sig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-pick/shannon_modem_loader/2dc27f01782eaaa55ef626e15ef6b4154bf0e392/sig/ARM_Compiler_5.06u4_b422.sig -------------------------------------------------------------------------------- /sig/ARM_Compiler_5.06u5_b528.sig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-pick/shannon_modem_loader/2dc27f01782eaaa55ef626e15ef6b4154bf0e392/sig/ARM_Compiler_5.06u5_b528.sig -------------------------------------------------------------------------------- /sig/ARM_Compiler_5.06u6_b750.sig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-pick/shannon_modem_loader/2dc27f01782eaaa55ef626e15ef6b4154bf0e392/sig/ARM_Compiler_5.06u6_b750.sig -------------------------------------------------------------------------------- /sig/ARM_Compiler_5.06u7_b960.sig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-pick/shannon_modem_loader/2dc27f01782eaaa55ef626e15ef6b4154bf0e392/sig/ARM_Compiler_5.06u7_b960.sig --------------------------------------------------------------------------------