├── .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
--------------------------------------------------------------------------------