├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── documentation-issue.md │ ├── feature_request.md │ └── troubleshooting-request.md ├── .gitignore ├── .readthedocs.yml ├── CHANGELOG ├── LICENSE.txt ├── README.md ├── doc ├── .gitignore ├── Makefile ├── README.md ├── images │ ├── crc32-stratagem.png │ ├── depthcharge-500.png │ ├── depthcharge-notext-200.png │ ├── i2c-read.png │ ├── i2c-write.png │ ├── monitor.gif │ ├── read-mem-demo.gif │ └── read-mem-intr.gif ├── src │ ├── _static │ │ └── theme_overrides.css │ ├── _templates │ │ └── footer.html │ ├── api │ │ ├── depthcharge.checker.rst │ │ ├── depthcharge.cmdline.rst │ │ ├── depthcharge.executor.rst │ │ ├── depthcharge.hunter.rst │ │ ├── depthcharge.log.rst │ │ ├── depthcharge.memory.rst │ │ ├── depthcharge.monitor.rst │ │ ├── depthcharge.register.rst │ │ ├── depthcharge.rst │ │ ├── depthcharge.string.rst │ │ ├── depthcharge.uboot.rst │ │ └── index.rst │ ├── companion_fw.rst │ ├── conf.py │ ├── index.rst │ ├── introduction.rst │ ├── resources.rst │ ├── scripts │ │ ├── depthcharge-audit-config.txt │ │ ├── depthcharge-find-cmd.txt │ │ ├── depthcharge-find-env.txt │ │ ├── depthcharge-find-fdt.txt │ │ ├── depthcharge-inspect.txt │ │ ├── depthcharge-mkenv.txt │ │ ├── depthcharge-print.txt │ │ ├── depthcharge-read-mem.txt │ │ ├── depthcharge-stratagem.txt │ │ ├── depthcharge-write-mem.txt │ │ └── index.rst │ └── troubleshooting.rst └── update_scripts.sh ├── firmware └── Arduino │ ├── .gitignore │ ├── Depthcharge-Teensy3.6 │ └── Depthcharge-Teensy3.6.ino │ ├── Depthcharge │ ├── Communicator.cpp │ ├── Communicator.h │ ├── Companion.cpp │ ├── Depthcharge.h │ ├── I2CPeriph.cpp │ ├── I2CPeriph.h │ ├── LED.cpp │ ├── LED.h │ ├── Panic.cpp │ ├── Panic.h │ └── Version.h │ ├── Makefile │ └── README.md ├── payloads ├── .gitignore ├── Makefile ├── README.md ├── create-payload-src.py ├── include │ ├── depthcharge.h │ ├── str2uint.h │ ├── str2ulong.h │ ├── strcmp.h │ ├── strlen.h │ └── u-boot.h └── src │ ├── read_memory.c │ └── return_memory_word.c └── python ├── .flake8 ├── .pylintrc ├── Depthcharge.md ├── MANIFEST.in ├── README.md ├── depthcharge ├── __init__.py ├── arch │ ├── __init__.py │ ├── aarch64.py │ ├── arch.py │ ├── arm.py │ └── generic.py ├── builtin_payloads.py ├── checker │ ├── __init__.py │ ├── _builtins │ │ ├── __init__.py │ │ ├── arch.py │ │ ├── cmdline.py │ │ ├── env.py │ │ ├── fit.py │ │ ├── fs.py │ │ ├── lib.py │ │ ├── net.py │ │ └── usb.py │ ├── config_checker.py │ ├── report.py │ ├── security_risk.py │ ├── uboot_config.py │ └── uboot_header.py ├── cmdline.py ├── companion.py ├── console.py ├── context.py ├── executor │ ├── __init__.py │ ├── executor.py │ └── go.py ├── hunter │ ├── __init__.py │ ├── cmdtbl.py │ ├── constant.py │ ├── cp.py │ ├── env.py │ ├── fdt.py │ ├── hunter.py │ ├── revcrc32.py │ └── string.py ├── log.py ├── memory │ ├── __init__.py │ ├── cp.py │ ├── crc32.py │ ├── data_abort.py │ ├── go.py │ ├── i2c.py │ ├── itest.py │ ├── load.py │ ├── memcmds.py │ ├── patch.py │ ├── reader.py │ ├── setexpr.py │ ├── stratagem.py │ ├── todo.txt │ └── writer.py ├── monitor.py ├── operation.py ├── payload_map.py ├── progress.py ├── register │ ├── __init__.py │ ├── cp.py │ ├── crc32.py │ ├── data_abort.py │ ├── fdt.py │ ├── itest.py │ ├── memcmds.py │ ├── reader.py │ └── setexpr.py ├── revcrc32.py ├── stratagem.py ├── string.py ├── uboot │ ├── __init__.py │ ├── board.py │ ├── cmd_table.py │ ├── env.py │ ├── jump_table.py │ └── version.py └── version.py ├── examples ├── .gitignore ├── boilerplate_create_ctx.py ├── boilerplate_load_ctx.py ├── deploy_payload.py ├── i2c_probe.py ├── mt7628_console_menu.py ├── read_nand.py ├── reverse_crc32_algo_poc.py └── symfonisk_unlock_bypass.py ├── pyproject.toml ├── scripts ├── depthcharge-audit-config ├── depthcharge-find-cmd ├── depthcharge-find-env ├── depthcharge-find-fdt ├── depthcharge-inspect ├── depthcharge-mkenv ├── depthcharge-print ├── depthcharge-read-mem ├── depthcharge-stratagem └── depthcharge-write-mem ├── setup.py └── tests ├── integration ├── .gitignore ├── README.md ├── launch_scripts.py ├── memory_test.py ├── register_test.py └── test_utils.py ├── resources ├── config_01.h ├── config_02.h ├── config_03.h ├── default_cmds.h ├── dotconfig-01.txt ├── dotconfig-02.txt ├── dotconfig-03.txt ├── dotconfig-04.txt └── test │ └── induce_error.h ├── run_all.sh └── unit ├── README.md ├── __init__.py ├── checker ├── __init__.py ├── builtins.py ├── report.py ├── security_risk.py ├── uboot_config.py └── uboot_header.py ├── hunter ├── __init__.py ├── constant.py ├── cp.py ├── env.py ├── fdt.py ├── hunter.py ├── revcrc32.py └── string.py ├── operation.py ├── revcrc32.py ├── stratagem.py ├── string.py ├── test_utils.py └── uboot ├── __init__.py ├── board.py ├── env.py └── version.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: 'Report a problem or unexpected failure ' 4 | title: '' 5 | labels: bug 6 | assignees: mp-tetrel 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | 12 | Please describe the failure or problem you're experiencing. 13 | 14 | **Depthcharge Version** 15 | 16 | What version of the Depthcharge codebase are you using? If between releases, please specify the exact git commit. 17 | 18 | **Target System** 19 | 20 | If the issue requires a specific target U-Boot version or hardware platform, please list these here. 21 | 22 | If we cannot reproduce the issue without a specific piece of target hardware, bear in mind that we may not be able to resolve the issue quickly. 23 | 24 | **Logs** 25 | 26 | Please provide the following log files. If these contain sensitive information, please redact this or otherwise reach out to us for a secure exchange of these files. 27 | 28 | 1. Depthcharge log output written to stderr. Please record this with the `DEPTHCHARGE_LOG_LEVEL` environment variable set to `debug`. 29 | 1. A Console log, as recorded by `depthcharge.monitor.FileMonitor`. For command line tools, this can be recorded via a `-m file:console.log` argument. 30 | 31 | 32 | 33 | **Screenshots** 34 | 35 | If applicable, add screenshots to help explain your problem. 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation Issue 3 | about: Report incorrect, incomplete, or outdated documentation 4 | title: 'Documentation: Summary of issue' 5 | labels: documentation 6 | assignees: mp-tetrel 7 | 8 | --- 9 | 10 | **Affected Document(s)** 11 | 12 | Please list the documentation where the issue resides. 13 | 14 | **Description of Issue** 15 | 16 | Please describe the issue with the documentation, along with any suggestions for improvement. 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: mp-tetrel 7 | 8 | --- 9 | 10 | **Describe new feature** 11 | Please describe the problem this new feature would solve, or what use-case it would serve. 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/troubleshooting-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Troubleshooting Request 3 | about: 'Ask questions or get some troubleshooting assistance ' 4 | title: '' 5 | labels: troubleshooting 6 | assignees: mp-tetrel 7 | 8 | --- 9 | 10 | *Please understand in mind that the author largely maintains this tool in their personal time, and may not always be able to respond within the same day. Your patience is greatly appreciated.* 11 | 12 | **Goal** 13 | 14 | What are you currently trying to accomplish. 15 | 16 | **Depthcharge Version** 17 | 18 | What version of Depthcharge are you using? 19 | 20 | **Question or Problem Description** 21 | 22 | What questions do you have, or what issues are you experiencing? 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.pyc 3 | *.bin 4 | *.cfg 5 | *.egg-info 6 | 7 | python/build 8 | python/dist 9 | python/venv 10 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | version: 2 6 | 7 | sphinx: 8 | builder: html 9 | configuration: doc/src/conf.py 10 | fail_on_warning: true 11 | 12 | python: 13 | install: 14 | - method: pip 15 | path: ./python 16 | extra_requirements: 17 | - docs 18 | 19 | build: 20 | os: ubuntu-22.04 21 | tools: 22 | python: "3.8" 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-2023 NCC Group 2 | Copyright (c) 2024-2025 Tetrel Security 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its contributors 16 | may be used to endorse or promote products derived from this software 17 | without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | python/Depthcharge.md -------------------------------------------------------------------------------- /doc/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | SPHINXOPTS ?= 2 | SPHINXBUILD ?= sphinx-build 3 | SOURCEDIR = src 4 | BUILDDIR = build 5 | 6 | html: 7 | 8 | help: 9 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 10 | 11 | $(BUILDDIR)/scripts: 12 | mkdir $@ 13 | 14 | view: html 15 | xdg-open $(BUILDDIR)/html/index.html 16 | 17 | .PHONY: help view Makefile 18 | 19 | # Catch-all target: route all unknown targets to Sphinx using the new 20 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 21 | %: Makefile 22 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 23 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | # Depthcharge Documentation 2 | 3 | 4 | This directory contains the source code for Depthcharge's documentation. 5 | If you're just looking for documentation to reference, see 6 | . 7 | 8 | ## Dependencies 9 | 10 | [GNU Make], [Sphinx] document generator, and the [ReadTheDocs.org theme] are 11 | required to build the HTML documentation. The latter two items can be 12 | installed via PIP. 13 | 14 | ``` 15 | $ pip install sphinx sphinx_rtd_theme 16 | ``` 17 | 18 | **Important:** If you are working with Depthcharge in a [venv], you 19 | must install Sphinx (and `sphinx_rtd_theme`) within that environment, 20 | even if you already have it installed on system through other package managers. 21 | Otherwise, `sphinx-build` will fail to locate the `depthcharge` module present 22 | only in your virtual environment. 23 | 24 | ## Build 25 | 26 | With the above dependencies satisfied, the documentation can be created by running: 27 | 28 | ``` 29 | $ make html 30 | ``` 31 | 32 | Build artifacts will be saved in `build/`, with the 33 | top-level landing page residing at `build/html/index.html`. 34 | 35 | [GNU Make]: https://www.gnu.org/software/make 36 | [Sphinx]: https://www.sphinx-doc.org/en/stable 37 | [ReadTheDocs.org theme]: https://github.com/readthedocs/sphinx_rtd_theme 38 | [venv]: https://docs.python.org/3/library/venv.html 39 | -------------------------------------------------------------------------------- /doc/images/crc32-stratagem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tetrelsec/depthcharge/f8591f23cb7b216c321bc0405774ad460393bfd9/doc/images/crc32-stratagem.png -------------------------------------------------------------------------------- /doc/images/depthcharge-500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tetrelsec/depthcharge/f8591f23cb7b216c321bc0405774ad460393bfd9/doc/images/depthcharge-500.png -------------------------------------------------------------------------------- /doc/images/depthcharge-notext-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tetrelsec/depthcharge/f8591f23cb7b216c321bc0405774ad460393bfd9/doc/images/depthcharge-notext-200.png -------------------------------------------------------------------------------- /doc/images/i2c-read.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tetrelsec/depthcharge/f8591f23cb7b216c321bc0405774ad460393bfd9/doc/images/i2c-read.png -------------------------------------------------------------------------------- /doc/images/i2c-write.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tetrelsec/depthcharge/f8591f23cb7b216c321bc0405774ad460393bfd9/doc/images/i2c-write.png -------------------------------------------------------------------------------- /doc/images/monitor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tetrelsec/depthcharge/f8591f23cb7b216c321bc0405774ad460393bfd9/doc/images/monitor.gif -------------------------------------------------------------------------------- /doc/images/read-mem-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tetrelsec/depthcharge/f8591f23cb7b216c321bc0405774ad460393bfd9/doc/images/read-mem-demo.gif -------------------------------------------------------------------------------- /doc/images/read-mem-intr.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tetrelsec/depthcharge/f8591f23cb7b216c321bc0405774ad460393bfd9/doc/images/read-mem-intr.gif -------------------------------------------------------------------------------- /doc/src/_static/theme_overrides.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Rackspace's workaround for Read the Docs theme issue with (the lack of) 3 | * line wrapping in tables. This overrides table width restrictions. 4 | * 5 | * https://rackerlabs.github.io/docs-rackspace/tools/rtd-tables.html 6 | * 7 | */ 8 | @media screen and (min-width: 767px) { 9 | 10 | .wy-table-responsive table td { 11 | /* !important prevents the common CSS stylesheets from overriding 12 | this as on RTD they are loaded after this stylesheet */ 13 | white-space: normal !important; 14 | } 15 | 16 | .wy-table-responsive { 17 | overflow: visible !important; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /doc/src/_templates/footer.html: -------------------------------------------------------------------------------- 1 | {% extends '!footer.html' %} 2 | 3 | {% block extrafooter %} 4 |

Depthcharge logo by Juupiter.

5 | {{ super() }} 6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /doc/src/api/depthcharge.checker.rst: -------------------------------------------------------------------------------- 1 | depthcharge.checker 2 | ===================== 3 | 4 | .. automodule:: depthcharge.checker 5 | :members: 6 | 7 | .. autoclass:: ConfigChecker 8 | :members: 9 | 10 | .. autoclass:: UBootConfigChecker 11 | :members: 12 | 13 | .. autoclass:: UBootHeaderChecker 14 | :members: 15 | 16 | .. autoclass:: Report 17 | :members: 18 | 19 | .. autoclass:: SecurityRisk 20 | :members: 21 | 22 | .. autoclass:: SecurityImpact 23 | :members: 24 | -------------------------------------------------------------------------------- /doc/src/api/depthcharge.cmdline.rst: -------------------------------------------------------------------------------- 1 | depthcharge.cmdline 2 | =================== 3 | 4 | .. automodule:: depthcharge.cmdline 5 | 6 | .. autofunction:: create_depthcharge_ctx 7 | 8 | ArgumentParser (Extension) 9 | -------------------------- 10 | 11 | .. autoclass:: ArgumentParser 12 | :members: 13 | 14 | ArgumentParser Actions 15 | ---------------------- 16 | 17 | .. autoclass:: AddressAction 18 | :members: 19 | 20 | .. autoclass:: CompanionAction 21 | :members: 22 | 23 | .. autoclass:: ListAction 24 | :members: 25 | 26 | .. autoclass:: KeyValListAction 27 | :members: 28 | 29 | .. autoclass:: LengthAction 30 | :members: 31 | 32 | -------------------------------------------------------------------------------- /doc/src/api/depthcharge.executor.rst: -------------------------------------------------------------------------------- 1 | depthcharge.executor 2 | ======================= 3 | 4 | .. automodule:: depthcharge.executor 5 | :members: 6 | 7 | Base Class 8 | ------------- 9 | 10 | .. autoclass:: Executor 11 | :members: 12 | 13 | 14 | Implementations 15 | --------------- 16 | 17 | .. autoclass:: GoExecutor 18 | :members: 19 | :exclude-members: rank, execute_at 20 | 21 | -------------------------------------------------------------------------------- /doc/src/api/depthcharge.log.rst: -------------------------------------------------------------------------------- 1 | deptcharge.log 2 | ============== 3 | 4 | .. automodule:: depthcharge.log 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/src/api/depthcharge.memory.rst: -------------------------------------------------------------------------------- 1 | 2 | depthcharge.memory 3 | ======================= 4 | 5 | .. automodule:: depthcharge.memory 6 | :members: 7 | 8 | Base Classes 9 | ------------ 10 | 11 | .. autoclass:: MemoryReader 12 | :members: 13 | :private-members: 14 | :exclude-members: rank, _describe_op 15 | 16 | .. autoclass:: MemoryWordReader 17 | :members: 18 | :private-members: 19 | :exclude-members: rank, _read 20 | 21 | .. autoclass:: DataAbortMemoryReader 22 | :members: 23 | :private-members: 24 | :exclude-members: rank, _read, _read_word 25 | 26 | .. autoclass:: MemoryWriter 27 | :members: 28 | :private-members: 29 | :exclude-members: rank, _describe_op 30 | 31 | .. autoclass:: MemoryWordWriter 32 | :members: 33 | :private-members: 34 | :exclude-members: rank, _write 35 | 36 | .. autoclass:: StratagemMemoryWriter 37 | :members: 38 | 39 | .. _apimemimpl: 40 | 41 | Implementations 42 | --------------- 43 | 44 | **MemoryReader** / **MemoryWordReader** 45 | 46 | * :py:class:`CRC32MemoryReader` 47 | * :py:class:`GoMemoryReader` 48 | * :py:class:`I2CMemoryReader` 49 | * :py:class:`ItestMemoryReader` 50 | * :py:class:`MdMemoryReader` 51 | * :py:class:`MmMemoryReader` 52 | * :py:class:`NmMemoryReader` 53 | * :py:class:`SetexprMemoryReader` 54 | 55 | 56 | **DataAbortMemoryReader** 57 | 58 | * :py:class:`CpCrashMemoryReader` 59 | 60 | 61 | **MemoryWriter** / **MemoryWordWriter** 62 | 63 | * :py:class:`CRC32MemoryWriter` 64 | * :py:class:`LoadbMemoryWriter` 65 | * :py:class:`LoadxMemoryWriter` 66 | * :py:class:`LoadyMemoryWriter` 67 | * :py:class:`MmMemoryWriter` 68 | * :py:class:`MwMemoryWriter` 69 | * :py:class:`NmMemoryWriter` 70 | 71 | 72 | **StratagemMemoryWriter** 73 | 74 | * :py:class:`CpMemoryWriter` 75 | * :py:class:`CRC32MemoryWriter` 76 | 77 | .. autoclass:: CRC32MemoryReader 78 | :exclude-members: rank 79 | 80 | .. autoclass:: CRC32MemoryWriter 81 | :members: 82 | :exclude-members: rank 83 | 84 | .. autoclass:: CpCrashMemoryReader 85 | :members: 86 | :exclude-members: rank 87 | 88 | .. autoclass:: CpMemoryWriter 89 | :members: 90 | :exclude-members: rank 91 | 92 | .. autoclass:: GoMemoryReader 93 | :members: 94 | :exclude-members: rank 95 | 96 | .. autoclass:: I2CMemoryReader 97 | :members: 98 | :exclude-members: rank 99 | 100 | .. autoclass:: I2CMemoryWriter 101 | :members: 102 | :exclude-members: rank 103 | 104 | .. autoclass:: ItestMemoryReader 105 | :members: 106 | :exclude-members: rank 107 | 108 | .. autoclass:: LoadbMemoryWriter 109 | :members: 110 | :exclude-members: rank 111 | 112 | .. autoclass:: LoadxMemoryWriter 113 | :members: 114 | :exclude-members: rank 115 | 116 | .. autoclass:: LoadyMemoryWriter 117 | :members: 118 | :exclude-members: rank 119 | 120 | .. autoclass:: MdMemoryReader 121 | :members: 122 | :exclude-members: rank 123 | 124 | .. autoclass:: MmMemoryReader 125 | :members: 126 | :exclude-members: rank 127 | 128 | .. autoclass:: MmMemoryWriter 129 | :members: 130 | :exclude-members: rank 131 | 132 | .. autoclass:: MwMemoryWriter 133 | :members: 134 | :exclude-members: rank 135 | 136 | .. autoclass:: NmMemoryReader 137 | :members: 138 | :exclude-members: rank 139 | 140 | .. autoclass:: NmMemoryWriter 141 | :members: 142 | :exclude-members: rank 143 | 144 | .. autoclass:: SetexprMemoryReader 145 | :members: 146 | :exclude-members: rank 147 | 148 | Memory Patching 149 | --------------- 150 | 151 | .. autoclass:: MemoryPatch 152 | :members: 153 | 154 | .. autoclass:: MemoryPatchList 155 | :members: 156 | -------------------------------------------------------------------------------- /doc/src/api/depthcharge.monitor.rst: -------------------------------------------------------------------------------- 1 | .. _api_monitor: 2 | 3 | depthcharge.monitor 4 | =================== 5 | 6 | .. automodule:: depthcharge.monitor 7 | 8 | .. autoclass:: Monitor 9 | :members: 10 | 11 | .. autoclass:: FileMonitor 12 | :members: 13 | 14 | .. autoclass:: NamedPipeMonitor 15 | :members: 16 | 17 | .. autoclass:: ColorNamedPipeMonitor 18 | :members: 19 | :exclude-members: read, write 20 | 21 | .. autoclass:: TerminalMonitor 22 | :members: 23 | -------------------------------------------------------------------------------- /doc/src/api/depthcharge.register.rst: -------------------------------------------------------------------------------- 1 | depthcharge.register 2 | ======================= 3 | 4 | .. automodule:: depthcharge.register 5 | 6 | Base Classes 7 | ------------ 8 | 9 | .. autoclass:: RegisterReader 10 | :members: 11 | :private-members: 12 | :exclude-members: rank 13 | 14 | .. autoclass:: DataAbortRegisterReader 15 | :members: 16 | :private-members: 17 | :exclude-members: rank, _read 18 | 19 | Implementations 20 | ---------------- 21 | 22 | **DataAbortRegisterReader** 23 | 24 | * :py:class:`CRC32CrashRegisterReader` 25 | * :py:class:`FDTCrashRegisterReader` 26 | * :py:class:`ItestCrashRegisterReader` 27 | * :py:class:`MdCrashRegisterReader` 28 | * :py:class:`MmCrashRegisterReader` 29 | * :py:class:`MwCrashRegisterReader` 30 | * :py:class:`NmCrashRegisterReader` 31 | * :py:class:`SetexprCrashRegisterReader` 32 | 33 | .. autoclass:: CRC32CrashRegisterReader 34 | :members: 35 | :exclude-members: rank 36 | 37 | .. autoclass:: FDTCrashRegisterReader 38 | :members: 39 | :exclude-members: rank 40 | 41 | .. autoclass:: ItestCrashRegisterReader 42 | :members: 43 | :exclude-members: rank 44 | 45 | .. autoclass:: MdCrashRegisterReader 46 | :members: 47 | :exclude-members: rank 48 | 49 | .. autoclass:: MmCrashRegisterReader 50 | :members: 51 | :exclude-members: rank 52 | 53 | .. autoclass:: MwCrashRegisterReader 54 | :members: 55 | :exclude-members: rank 56 | 57 | .. autoclass:: NmCrashRegisterReader 58 | :members: 59 | :exclude-members: rank 60 | 61 | .. autoclass:: SetexprCrashRegisterReader 62 | :members: 63 | :exclude-members: rank 64 | 65 | -------------------------------------------------------------------------------- /doc/src/api/depthcharge.rst: -------------------------------------------------------------------------------- 1 | .. _depthcharge: 2 | 3 | depthcharge 4 | ======================= 5 | 6 | .. automodule:: depthcharge.context 7 | 8 | .. py:module:: depthcharge 9 | 10 | Console 11 | ------- 12 | 13 | .. autoclass:: Console 14 | :members: 15 | 16 | Depthcharge (Context) 17 | --------------------- 18 | 19 | .. autoclass:: Depthcharge 20 | :members: 21 | 22 | Operation 23 | --------- 24 | 25 | .. autoclass:: Operation 26 | :members: 27 | 28 | .. autoclass:: OperationSet 29 | :members: 30 | 31 | .. autoexception:: OperationFailed 32 | 33 | .. autoexception:: OperationNotSupported 34 | 35 | .. autoexception:: OperationAlignmentError(alignment: int, cls=None) 36 | 37 | Stratagem 38 | ---------- 39 | 40 | .. autoclass:: Stratagem 41 | :members: 42 | 43 | .. autoexception:: StratagemRequired 44 | 45 | .. autoexception:: StratagemNotRequired 46 | 47 | .. autoexception:: StratagemCreationFailed 48 | 49 | Companion 50 | --------- 51 | 52 | .. autoclass:: Companion 53 | :members: 54 | 55 | Architecture 56 | ------------ 57 | 58 | .. autoclass:: Architecture 59 | :members: 60 | 61 | Progress Indicator 62 | ------------------- 63 | 64 | .. autoclass:: Progress 65 | :members: 66 | 67 | .. autoclass:: ProgressBar 68 | 69 | -------------------------------------------------------------------------------- /doc/src/api/depthcharge.string.rst: -------------------------------------------------------------------------------- 1 | depthcharge.string 2 | ================== 3 | 4 | .. automodule:: depthcharge.string 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/src/api/depthcharge.uboot.rst: -------------------------------------------------------------------------------- 1 | depthcharge.uboot 2 | ================= 3 | 4 | .. automodule:: depthcharge.uboot 5 | :members: 6 | 7 | 8 | depthcharge.uboot.board 9 | ----------------------- 10 | 11 | .. automodule:: depthcharge.uboot.board 12 | :members: 13 | 14 | depthcharge.uboot.cmd_table 15 | --------------------------- 16 | 17 | .. automodule:: depthcharge.uboot.cmd_table 18 | :members: 19 | 20 | depthcharge.uboot.env 21 | --------------------- 22 | 23 | .. automodule:: depthcharge.uboot.env 24 | :members: 25 | 26 | depthcharge.uboot.jump_table 27 | ---------------------------- 28 | 29 | .. automodule:: depthcharge.uboot.jump_table 30 | :members: 31 | -------------------------------------------------------------------------------- /doc/src/api/index.rst: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | Python API 4 | ====================== 5 | 6 | The Depthcharge API is implemented as a ``depthcharge`` module, which consists of 7 | a number of submodules. Much of the functionality one will likely want to interact 8 | with when getting started resides in the top-level :ref:`depthcharge` module 9 | namespace. All of the :ref:`scripts` are built atop of this API, and can be 10 | referenced as examples (in addition to other example code 11 | in the source repository). 12 | 13 | .. toctree:: 14 | :maxdepth: 3 15 | :caption: Contents 16 | 17 | depthcharge 18 | depthcharge.cmdline 19 | depthcharge.checker 20 | depthcharge.executor 21 | depthcharge.hunter 22 | depthcharge.log 23 | depthcharge.memory 24 | depthcharge.monitor 25 | depthcharge.register 26 | depthcharge.string 27 | depthcharge.uboot 28 | -------------------------------------------------------------------------------- /doc/src/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | import depthcharge 10 | 11 | # -- Project information ----------------------------------------------------- 12 | 13 | project = 'Depthcharge' 14 | copyright = '2019-2023, NCC Group. 2024-2025 Tetrel Security' 15 | 16 | # The full version, including alpha/beta/rc tags 17 | release = depthcharge.__version__ 18 | 19 | # -- General configuration --------------------------------------------------- 20 | 21 | # Add any Sphinx extension module names here, as strings. They can be 22 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 23 | # ones. 24 | extensions = [ 25 | 'sphinx.ext.autodoc', 26 | 'sphinx_rtd_theme' 27 | ] 28 | 29 | # Add any paths that contain templates here, relative to this directory. 30 | templates_path = ['_templates'] 31 | 32 | # List of patterns, relative to source directory, that match files and 33 | # directories to ignore when looking for source files. 34 | # This pattern also affects html_static_path and html_extra_path. 35 | exclude_patterns = [] 36 | 37 | autodoc_member_order = 'bysource' 38 | 39 | # -- Options for HTML output ------------------------------------------------- 40 | 41 | html_static_path = ['_static'] 42 | 43 | # Apply RTD theme overrides 44 | html_css_files = ['theme_overrides.css'] 45 | 46 | html_logo = '../images/depthcharge-500.png' 47 | 48 | html_theme = 'sphinx_rtd_theme' 49 | html_theme_options = { 50 | 'logo_only': True 51 | } 52 | -------------------------------------------------------------------------------- /doc/src/index.rst: -------------------------------------------------------------------------------- 1 | .. _index: 2 | 3 | Depthcharge documentation 4 | ========================== 5 | 6 | This is the official documentation for the free and open source *Depthcharge* project, currently maintained by `Tetrel Security`_. 7 | 8 | Source code and releases can be found here: 9 | 10 | .. centered:: https://github.com/tetrelsec/depthcharge 11 | 12 | If you're new to the project, we recommend starting with the :ref:`introduction` section to get up to speed. 13 | Also be sure to check out the :ref:`blogtalks` section listed on the :ref:`resources` page. 14 | 15 | 16 | 17 | .. toctree:: 18 | :maxdepth: 2 19 | :name: mastertoc 20 | :caption: Contents: 21 | 22 | introduction 23 | scripts/index 24 | api/index 25 | companion_fw 26 | troubleshooting 27 | resources 28 | 29 | .. _Tetrel Security: https://www.tetrelsec.com/ 30 | -------------------------------------------------------------------------------- /doc/src/resources.rst: -------------------------------------------------------------------------------- 1 | .. _resources: 2 | 3 | Resources 4 | ========= 5 | 6 | 7 | .. _blogtalks: 8 | 9 | Blog Posts and Talks 10 | -------------------- 11 | 12 | Below are the blog posts and talks created by the original author of Depthcharge. These provide some insights into the motivations of the project and some higher-level methodologies. However, the API and command-line tools may change over time; always refer to the :ref:`index` for the most up-to-date information. 13 | 14 | * NCC Group Blog Post: `Sinking U-Boots with Depthcharge`_ (via `archive.org `__) 15 | * Hardwear.io Webinar: `...Effective Exploitation of Boot-time Security Debt`_ 16 | * NCC Group Blog Post: `...U-Boot Configuration Auditing Introduced in v0.2.0`_ (via `archive.org `__) 17 | * OSFC 2020 Talk: `Guiding Engineering Teams Toward a More Secure Usage of U-Boot`_ 18 | 19 | .. _Sinking U-Boots with Depthcharge: https://research.nccgroup.com/2020/07/22/depthcharge 20 | .. _...Effective Exploitation of Boot-time Security Debt: https://www.youtube.com/watch?v=fTKMi3Is5x8 21 | .. _...U-Boot Configuration Auditing Introduced in v0.2.0: https://research.nccgroup.com/2020/12/16/depthcharge-v0-2-0 22 | .. _Guiding Engineering Teams Toward a More Secure Usage of U-Boot: https://vimeo.com/showcase/7884533/video/488134063 23 | 24 | 25 | Official U-Boot Documentation 26 | ----------------------------- 27 | 28 | The `U-Boot`_ project contains a ton of great `documentation`_. When doing security auditing work and working with Depthcharge, you may find the following resources particularly helpful. 29 | 30 | * `U-Boot talks`_ 31 | * `Command-line and Hush shell`_ 32 | * `Global Data structure`_ (see `global_data.h`_) 33 | * `Exported functions for Standalone Applications`_ (see `exports.h`_ and `_exports.h `_) 34 | * `U-Boot scripts and the "source" command`_ 35 | * `FIT Image Format`_ and `FIT signature verification`_ 36 | * `Sandbox build target`_ - Great for fuzzing for command handlers 37 | 38 | .. _U-Boot: https://source.denx.de/u-boot/u-boot 39 | .. _documentation: https://docs.u-boot.org/en/latest 40 | .. _U-Boot talks: https://docs.u-boot.org/en/latest/learn/talks.html 41 | .. _Command-line and Hush shell: https://docs.u-boot.org/en/latest/usage/cmdline.html 42 | .. _Global Data structure: https://docs.u-boot.org/en/latest/develop/global_data.html 43 | .. _global_data.h: https://source.denx.de/u-boot/u-boot/-/blob/v2024.07-rc3/include/asm-generic/global_data.h?ref_type=tags#L39 44 | .. _Exported Functions for Standalone Applications: https://source.denx.de/u-boot/u-boot/-/blob/master/doc/README.standalone?ref_type=heads 45 | .. _exports.h: https://source.denx.de/u-boot/u-boot/-/blob/v2024.07-rc3/include/exports.h 46 | .. _U-Boot Scripts and the "source" command: https://docs.u-boot.org/en/latest/usage/cmd/source.html 47 | .. _FIT image format: https://docs.u-boot.org/en/latest/usage/fit/source_file_format.html 48 | .. _FIT signature verification: https://docs.u-boot.org/en/latest/usage/fit/signature.html 49 | .. _Sandbox build target: https://docs.u-boot.org/en/latest/arch/sandbox/sandbox.html 50 | 51 | -------------------------------------------------------------------------------- /doc/src/scripts/depthcharge-find-cmd.txt: -------------------------------------------------------------------------------- 1 | usage: depthcharge-find-cmd [options] -f 2 | 3 | Search for U-Boot command tables within a memory or flash dump. 4 | 5 | options: 6 | -h, --help show this help message and exit 7 | -a , --address 8 | Base address of image. 9 | --arch 10 | CPU architecture. 11 | -f , --file 12 | Flash or memory image to inspect 13 | --longhelp {Y,N} Value of U-Boot CONFIG_SYS_LONGHELP setting. 14 | --autocomplete {Y,N} Value of U-Boot CONFIG_AUTO_COMPLETE setting. 15 | --threshold THRESHOLD 16 | Minimum table size to report. Default: 5 17 | --subcmds Include sub-command tables in displayed results 18 | --details Display more detailed output 19 | 20 | notes: 21 | If the --longhelp and --autocomplete options are not specified, Depthcharge 22 | will attempt to infer the state of these compile-time configuration settings. 23 | 24 | When --subcmds is specified, output will include any subcommand handler tables. 25 | This may require a lower --threshold setting, which could yield false positives. 26 | 27 | example: 28 | Search for command table entries provide detailed output, given an ARM 29 | device and a memory dump taken from address 0x87800000. 30 | 31 | depthcharge-find-cmd --arch arm -a 0x8780000 -f dump.bin --details 32 | 33 | -------------------------------------------------------------------------------- /doc/src/scripts/depthcharge-find-env.txt: -------------------------------------------------------------------------------- 1 | usage: depthcharge-find-env [options] -f 2 | 3 | Search for U-Boot environment data (env_t structures) in a flash or memory dump 4 | 5 | options: 6 | -h, --help show this help message and exit 7 | -f , --file 8 | Binary image to search 9 | -a , --address 10 | Base address of the flash or memory dump. Result are 11 | shown with respect to this address. Use the default of 12 | 0 if interested in relative offsets. 13 | --arch 14 | CPU architecture. 15 | -o , --outfile 16 | Filename prefix for output file(s). No files are 17 | written if this is not provided. A .txt or .bin suffix 18 | will be added to each file. 19 | -E, --expand Expand environment variable definitions, such thatall 20 | defined variable usages are resolved. Warningwill be 21 | printed for any unresolved variables. 22 | -B, --binary When saving an output file, write the binary 23 | environment contents.This will include the env_t 24 | metadata (CRC32 word, flags byte). 25 | -S, --summary Only print the summary of located environment(s). Do 26 | not print environment contents. 27 | 28 | examples: 29 | Print all environment instances found in mtdblock0.bin: 30 | 31 | depthcharge-find-env -f mtdblock0.bin 32 | 33 | Expand environment variables, and save the printed text to 34 | individual files named uboot_env_
_exp.txt. The _exp 35 | portion is only added when -E, --expand is used. 36 | 37 | depthcharge-find-env -E -o uboot_env -f mtdblock0.bin 38 | 39 | Save the raw binary environment instances, including metadata, 40 | and print only a summary of the extracted items. Files will 41 | be saved to individual files named uboot_env_
.bin 42 | 43 | depthcharge-find-env -o uboot_env -S -B -f mtdblock0.bin 44 | 45 | -------------------------------------------------------------------------------- /doc/src/scripts/depthcharge-find-fdt.txt: -------------------------------------------------------------------------------- 1 | usage: depthcharge-find-fdt [options] -f 2 | 3 | Search for instances of Flattened Device Tree blobs in a flash or memory dump 4 | 5 | options: 6 | -h, --help show this help message and exit 7 | -f , --file 8 | Binary image to search 9 | -a , --address 10 | Base address of the flash or memory dump. Results are 11 | shown with respect to this address. Use the default of 12 | 0 if interested in relative offsets. 13 | --arch 14 | CPU architecture. 15 | -o , --outfile 16 | Filename prefix for output file(s). No files are 17 | written if this is not provided. A .dts or .dtb suffix 18 | will be added to each file. 19 | --no-dts Do not save .dts files. 20 | --no-dtb Do not save .dtb files. 21 | 22 | examples: 23 | Print locations and sizes of FDT (DTB) instances found within an image. 24 | 25 | depthcharge-find-fdt --arch arm -f image.bin 26 | 27 | Extract FDT instances and save them to .dtb and .dts files named 28 | "image_
.[dts|dtb]". Additionally, specify a base address 29 | for the image. 30 | 31 | depthcharge-find-fdt --arch arm -a 0xa000 -f image.bin -o image 32 | 33 | Same as the above, but only save .dts files. 34 | 35 | depthcharge-find-fdt --arch arm -a 0xa000 -f image.bin -o image --no-dtb 36 | 37 | 38 | -------------------------------------------------------------------------------- /doc/src/scripts/depthcharge-inspect.txt: -------------------------------------------------------------------------------- 1 | usage: depthcharge-inspect [options] -c 2 | 3 | Inspect a device's console environment and create a device configuration file. 4 | 5 | options: 6 | -h, --help show this help message and exit 7 | --arch 8 | CPU architecture. 9 | -c , --config 10 | Device configuration file to load and update. It will 11 | be created if it does not exist. 12 | -i [:baudrate], --iface [:baudrate] 13 | Serial port interface connected to U-Boot console. 14 | -C [:setting=value,...], --companion [:setting=value,...] 15 | Depthcharge companion device to use and its associated 16 | settings. See the depthcharge.Companion documentation 17 | for supported settings. 18 | -m [:options,...], --monitor [:options,...] 19 | Attach a console monitor. Valid types: file, pipe, 20 | colorpipe, term 21 | -X [=], --extra [=] 22 | Specify extra operation-specific parameters as a key- 23 | value pair. A value of True is implicit if a value is 24 | not explicitly provided. Multiple instances of this 25 | argument are permitted. See the documentation for 26 | subclasses of depthcharge.Operation for supported 27 | keyword arguments. 28 | -P , --prompt 29 | Override expected U-Boot prompt string. 30 | -A, --allow-deploy Allow payloads to be deployed and executed. 31 | Functionality may be limited if this is not specified. 32 | -S, --skip-deploy Skip payload deployment but allow execution; assume 33 | payloads are already deployed and execute as-needed. 34 | This has no effect when -A, --allow-deploy is used. 35 | -R, --allow-reboot Allow operations that require crashing or rebooting 36 | the target to be performed. 37 | 38 | notes: 39 | This is generally the first Depthcharge script one will want to run when 40 | interacting with a new device. 41 | 42 | The resulting configuration file can passed to other Depthcharge scripts, 43 | alleviating the need to re-inspect the device each time a context object 44 | is created. (See depthcharge.Depthcharge.load() documentation.) 45 | 46 | examples: 47 | Save results to a "dev.cfg" file and use the default serial interface 48 | settings (/dev/ttyUSB0 at 115200 baud). This will use the default "Generic" 49 | 32-bit little endian CPU architecture, and will avoid the use of any 50 | operations that require rebooting/crashing the platform or deploying 51 | and executing payloads. 52 | 53 | depthcharge-inspect -c dev.cfg 54 | 55 | Use the serial console located at /dev/ttyUSB2 at a speed of 19200 baud. 56 | Here, we additionally specify that the target is a 32-bit ARM device, 57 | and that we want to opt-in to the use of operations that necessitate 58 | crashing/rebooting the platform, as well as payload deployment and execution. 59 | 60 | depthcharge-inspect --arch arm -AR -i /dev/ttyUSB2:19200 -c dev.cfg 61 | 62 | Use a companion device attached to /dev/ttyACM1. Configure the companion 63 | to operate on the target's I2C bus #2, for a speed of 250kHz. (Here, -i is 64 | omitted again to use the default settings.) 65 | 66 | depthcharge-inspect --arch arm -AR -c dev.cfg \ 67 | -C /dev/ttyACM1:i2c_bus=2,i2c_speed=250000 68 | 69 | Supply a known prompt string to look for instead of having Depthcharge attempt 70 | to determine it: 71 | 72 | depthcharge-inspect --arch arm -AR --prompt "ACMEcorp >" -c my_config.cfg 73 | 74 | -------------------------------------------------------------------------------- /doc/src/scripts/depthcharge-mkenv.txt: -------------------------------------------------------------------------------- 1 | usage: depthcharge-mkenv [options] -f -o 2 | 3 | Make an environment that can be inserted into a target devices' NV storage 4 | 5 | options: 6 | -h, --help show this help message and exit 7 | -f , --file 8 | Input file containing environment in text form. 9 | --arch 10 | CPU architecture. 11 | -o , --outfile 12 | Output file for binary environment. 13 | -S SIZE, --size SIZE Environment size. Must match target's CONFIG_ENV_SIZE. 14 | -F FLAGS, --flags FLAGS 15 | Set a value for the flags bytes. It is not included 16 | otherwise. 17 | -H, --no-hdr Do not include header metadata (CRC word, flag). 18 | 19 | examples: 20 | 21 | Create an environment for a target whose U-Boot image was not built with 22 | CONFIG_SYS_REDUNDAND_ENV, and CONFIG_ENV_SIZE=0x2000. 23 | 24 | depthcharge-mkenv -S 0x2000 -f env.txt -o env.bin 25 | 26 | Create an environment for use on a target device whose U-Boot image 27 | was built with CONFIG_SYS_REDUNDAND_ENV and CONFIG_ENV_SIZE=0x10000. 28 | Note that the flags value must be greater than or equal to that of the 29 | active environment that is being replaced. 30 | 31 | depthcharge-mkenv -S 0x10000 -F 0x5 -f env.txt -o env.bin 32 | 33 | Convert 2 KiB environment, but do not prepend a CRC32 34 | checksum (nor a flag word). 35 | 36 | depthcharge-mkenv -S 2K -H -f env.txt -o env.bin 37 | 38 | 39 | -------------------------------------------------------------------------------- /doc/src/scripts/depthcharge-print.txt: -------------------------------------------------------------------------------- 1 | usage: depthcharge-print -c -i [:options] 2 | 3 | Pretty-print information in a device configuration file. 4 | 5 | options: 6 | -h, --help show this help message and exit 7 | -c , --config 8 | Configuration file to print. 9 | -i , --item 10 | Configuration item(s) to print. 11 | 12 | supported items: 13 | all - All of the below items 14 | 15 | arch - Device architecture 16 | 17 | commands[:details] - Console commands supported by a target, optionally 18 | with detailed help text, if the target provides it 19 | 20 | env[:expand] - Target's environment variables, optionally 21 | with all definitions expanded. 22 | 23 | gd - U-Boot global data structure information, if known. 24 | 25 | version - Target's reported version information 26 | 27 | examples: 28 | Print all information contained in a device configuration ("dev.cfg"): 29 | 30 | depthcharge-print -c dev.cfg -i all 31 | 32 | Print all commands supported by a device. Note that if you only 33 | want a summary of the commands, the detail help text can be omitted 34 | by not including '=details' 35 | 36 | depthcharge-print -c dev.cfg -i commands=details 37 | 38 | Print all environment variables, with all definitiions fully 39 | expanded. To view these as they are on the device, omit '=expand'. 40 | 41 | depthcharge-print -c dev.cfg -i env=expand 42 | 43 | Print both the target's architechture, U-Boot version information, 44 | and expanded environment variables. Both usages are acceptable. 45 | 46 | depthcharge-print -c dev.cfg -i arch,version,env=expand 47 | depthcharge-print -c dev.cfg -i arch -i version -i env=expand 48 | 49 | -------------------------------------------------------------------------------- /doc/src/scripts/depthcharge-stratagem.txt: -------------------------------------------------------------------------------- 1 | usage: depthcharge-stratagem -a
-f -s -P -o 2 | 3 | Create a Stratagem file, given a memory or flash dump and a desired payload. 4 | 5 | options: 6 | -h, --help show this help message and exit 7 | -a , --address 8 | Base address of image. 9 | -f , --file 10 | Input file containing memory or flash image 11 | -s , --stratagem 12 | Type of the Stratagem to produce 13 | -P PAYLOAD, --payload PAYLOAD 14 | File containing desired binary payload 15 | -o , --outfile 16 | Output file to store produced Stratagem in 17 | -X [=], --extra [=] 18 | Parameters passed to Hunter implementation used for 19 | Stratagem creation. 20 | -l, --list List supported Stratagem types and their -X 21 | parameters. 22 | 23 | notes: 24 | In Depthcharge parlance, a "Stratagem" is a sequence of meta-operations 25 | used to perform a desired operation, such as writing a specific value to 26 | a target region, abusing U-boot functionality not intended for this. 27 | 28 | In general, a subset of Depthcharge "Hunter" implementations are used 29 | to search for and produce these Stratagem. 30 | 31 | The available Hunter-specific settings that can be specified using -X/--extra 32 | can be found in the output of depthcharge-stratagem --list. Refer to the 33 | corresponding depthcharge.hunter.Hunter subclass for more information 34 | about these "extra" parameters, which correspond to the keyword arguments 35 | (**kwargs) their constructors and methods support. 36 | 37 | In general, use of this script will require an understanding of the 38 | corresponding Depthcharge Hunter and Stratagem API items. 39 | 40 | example: 41 | Create a Depthcharge Stratagem for use with a CRC32MemoryWriter: 42 | depthcharge-stratagem -f dump.bin -s crc32 -P payload.bin -o stratagem.json 43 | 44 | Consider a situation where the above command reports a failure to produce a 45 | Stratagem. We can use the -X, --extra argument to both expend more memory when 46 | searching for a result (via `revlut_maxlen`) and permit longer-running deploy-time 47 | operations (via `max_iterations). Below is an example -X usage that would 48 | increase the defaults used by Depthcharge's CRC32MemoryWriter: 49 | 50 | -X revlut_maxlen=512,max_iterations=8192 51 | 52 | -------------------------------------------------------------------------------- /doc/update_scripts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Update script help text entries 4 | # 5 | ################################################################################ 6 | 7 | THIS_DIR=$(realpath $(dirname $0)) 8 | SCRIPT_DIR=$(realpath ${THIS_DIR}/../python/scripts) 9 | DOC_DIR=${THIS_DIR}/src/scripts 10 | 11 | echo "Writing help text output to ${DOC_DIR}" 12 | 13 | set -e 14 | for name in $(ls "${SCRIPT_DIR}" | grep 'depthcharge-'); do 15 | script=${SCRIPT_DIR}/${name} 16 | echo " $name --help > ${DOC_DIR}/${name}.txt" 17 | $script --help > ${DOC_DIR}/${name}.txt 18 | done 19 | 20 | 21 | -------------------------------------------------------------------------------- /firmware/Arduino/.gitignore: -------------------------------------------------------------------------------- 1 | builds/* 2 | -------------------------------------------------------------------------------- /firmware/Arduino/Depthcharge-Teensy3.6/Depthcharge-Teensy3.6.ino: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Depthcharge: 3 | // 4 | // Top-level source file for Teensy 3.6 5 | // 6 | // 7 | // I2C SCL: Pin 19 8 | // I2C SDA: Pin 18 9 | // 10 | 11 | #include 12 | 13 | Depthcharge::Companion dc; 14 | 15 | void setup() { 16 | Serial.begin(Depthcharge::Companion::default_uart_baudrate); 17 | 18 | dc.attachHostInterface(&Serial); 19 | dc.attachLED(13, HIGH, LOW); 20 | dc.attachI2C(&Wire, 21 | Depthcharge::Companion::default_i2c_addr, 22 | Depthcharge::Companion::default_i2c_speed); 23 | 24 | interrupts(); 25 | } 26 | 27 | void loop() { 28 | dc.processEvents(); 29 | } 30 | -------------------------------------------------------------------------------- /firmware/Arduino/Depthcharge/Communicator.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Depthcharge: 3 | 4 | #include "Communicator.h" 5 | #include "Panic.h" 6 | 7 | #define SET_PANIC_REASON() \ 8 | do { Panic::setReason(Panic::Source::Communicator, __LINE__); } while (0) 9 | 10 | namespace Depthcharge { 11 | 12 | Communicator::Communicator() : 13 | m_state(UNINITIALIZED), m_hostPort(NULL) {}; 14 | 15 | void Communicator::attach(::Stream *port) 16 | { 17 | if (m_state == UNINITIALIZED) { 18 | m_hostPort = port; 19 | memset(&m_req, 0, sizeof(m_req)); 20 | m_state = IDLE; 21 | } 22 | } 23 | 24 | bool Communicator::hasRequest(msg &req_out) 25 | { 26 | switch (this->m_state) { 27 | case IDLE: 28 | m_data_rcvd = 0; 29 | if (m_hostPort->available() >= (int) HEADER_SIZE) { 30 | m_state = READ_REQUEST_HEADER; 31 | } 32 | break; 33 | 34 | case READ_REQUEST_HEADER: { 35 | size_t n = m_hostPort->readBytes( 36 | reinterpret_cast(&m_req), HEADER_SIZE); 37 | 38 | if (n != HEADER_SIZE) { 39 | SET_PANIC_REASON(); 40 | m_state = PANIC; 41 | return false; 42 | } 43 | 44 | if (m_req.len == 0) { 45 | m_state = RETURN_REQUEST; 46 | } else if (m_req.len <= MAX_DATA_SIZE) { 47 | m_data_rcvd = 0; 48 | m_state = READ_REQUEST_DATA; 49 | } else { 50 | SET_PANIC_REASON(); 51 | m_state = PANIC; 52 | return false; 53 | } 54 | break; 55 | } 56 | 57 | case READ_REQUEST_DATA: { 58 | size_t avail = m_hostPort->available(); 59 | if (avail > 0) { 60 | size_t data_left = m_req.len - m_data_rcvd; 61 | size_t to_read = (avail < data_left) ? avail : data_left; 62 | uint8_t *ins = &m_req.data[m_data_rcvd]; 63 | 64 | size_t n = m_hostPort->readBytes(ins, to_read); 65 | if (n != to_read) { 66 | SET_PANIC_REASON(); 67 | m_state = PANIC; 68 | return false; 69 | } 70 | 71 | m_data_rcvd += to_read; 72 | if (m_data_rcvd >= m_req.len) { 73 | m_state = RETURN_REQUEST; 74 | } 75 | } 76 | break; 77 | } 78 | 79 | case RETURN_REQUEST: 80 | memcpy(&req_out, &m_req, HEADER_SIZE + m_req.len); 81 | if (m_req.len < MAX_DATA_SIZE) { 82 | size_t len = MAX_DATA_SIZE - m_req.len; 83 | memset(&req_out.data[m_req.len], 0, len); 84 | } 85 | m_state = IDLE; 86 | return true; 87 | 88 | case PANIC: 89 | return false; 90 | 91 | default: 92 | SET_PANIC_REASON(); 93 | m_state = PANIC; 94 | return false; 95 | } 96 | 97 | return false; 98 | } 99 | }; 100 | -------------------------------------------------------------------------------- /firmware/Arduino/Depthcharge/Communicator.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Depthcharge: 3 | 4 | #pragma once 5 | 6 | #include "Arduino.h" 7 | 8 | namespace Depthcharge { 9 | /* 10 | * Instances of this represent a device <-> host interface handle. 11 | * 12 | * This just abstracts away some of the message handling so the Companion 13 | * code doesn't have to worry about it. 14 | */ 15 | class Communicator { 16 | public: 17 | static const size_t MAX_DATA_SIZE = 64; 18 | 19 | struct __attribute__((packed)) msg { 20 | uint8_t cmd; 21 | uint8_t len; 22 | uint8_t data[MAX_DATA_SIZE]; 23 | }; 24 | 25 | /** 26 | * Instantiate a Communicator. 27 | * 28 | * It will be unusable until attach() is called to attach 29 | * a serial port to the device. 30 | */ 31 | Communicator(); 32 | 33 | /** 34 | * Associate the Communicator with a serial port that will 35 | * be used to receive requests from the host. 36 | */ 37 | void attach(::Stream *port); 38 | 39 | /* 40 | * Check for a new request. 41 | * 42 | * If received, this function will update `request` and return true. 43 | * 44 | * Otherwise, false is returned and `request` is not modified. 45 | */ 46 | bool hasRequest(msg &request); 47 | 48 | inline void sendResponse(msg &response) { 49 | if (response.len > MAX_DATA_SIZE) { 50 | response.len = MAX_DATA_SIZE; 51 | } 52 | 53 | m_hostPort->write( 54 | reinterpret_cast(&response), 55 | HEADER_SIZE + response.len 56 | ); 57 | }; 58 | 59 | private: 60 | enum state { 61 | UNINITIALIZED, 62 | IDLE, 63 | READ_REQUEST_HEADER, 64 | READ_REQUEST_DATA, 65 | RETURN_REQUEST, 66 | PANIC 67 | } m_state; 68 | 69 | ::Stream *m_hostPort; 70 | msg m_req; 71 | size_t m_data_rcvd; 72 | 73 | static const size_t HEADER_SIZE = 74 | sizeof(m_req.cmd) + sizeof(m_req.len); 75 | }; 76 | }; 77 | -------------------------------------------------------------------------------- /firmware/Arduino/Depthcharge/Depthcharge.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Depthcharge: 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include "Communicator.h" 9 | #include "LED.h" 10 | #include "I2CPeriph.h" 11 | 12 | namespace Depthcharge { 13 | 14 | enum Error { 15 | SUCCESS = 0x00, // Operation was successful, without error 16 | UNIMPLEMENTED = 0xfb, // Functionality stubbed, but not implemented 17 | UNINITIALIZED = 0xfc, // Attempt to use uninitialized functionality 18 | INVALID_PARAM = 0xfd, // Invalid parameter in request 19 | NOT_SUPPORTED = 0xfe, // Not supported in this firmware or mode 20 | INVALID_CMD = 0xff // Invalid command identifier 21 | }; 22 | 23 | /* 24 | * Depthcharge Companion device context 25 | * This is the "top level" design entity, so to speak. 26 | */ 27 | class Companion { 28 | 29 | public: 30 | enum Command { 31 | FW_GET_VERSION = 0x00, 32 | FW_GET_CAPABILITIES = 0x01, 33 | 34 | // 0x02 - 0x07 reserved for future device-level settings 35 | 36 | I2C_GET_ADDR = 0x08, 37 | I2C_SET_ADDR = 0x09, 38 | I2C_GET_SPEED = 0x0a, 39 | I2C_SET_SPEED = 0x0b, 40 | I2C_GET_SUBADDR_LEN = 0x0c, 41 | I2C_SET_SUBADDR_LEN = 0x0d, 42 | I2C_GET_MODE_FLAGS = 0x0e, // TODO: Not implemented 43 | I2C_SET_MODE_FLAGS = 0x0f, // TODO: Not implemented 44 | I2C_SET_READ_BUFFER = 0x10, 45 | I2C_GET_WRITE_BUFFER = 0x11, 46 | 47 | // 0x20 - 0x2f reserved for SPI peripheral device operation 48 | 49 | // 0x60 - 0x7f reserved for device-level setting blowout 50 | 51 | /* 52 | * 0x80 - 0xff is reserved for whomever is reading this. 53 | * The upstream code won't use this range, so you're free to. 54 | * 55 | * Happy hacking, neighbor! 56 | * 🔥☠️ jynik ☠️🔥 57 | */ 58 | NEIGHBOR_RESERVED_START = 0x80, 59 | NEIGHBOR_RESERVED_END = 0xff 60 | }; 61 | 62 | enum FirmwareCapabilities { 63 | CAP_I2C_PERIPH = (1 << 0), 64 | CAP_SPI_PERIPH = (1 << 1), // Reserved 65 | }; 66 | 67 | /* Platform implementations (in ino's) should try to use these 68 | * defaults, if possible, to yield consistency across targets. 69 | */ 70 | static const uint32_t default_uart_baudrate = 115200; 71 | static const uint8_t default_i2c_addr = 0x78; 72 | static const uint32_t default_i2c_speed = 100000; 73 | 74 | /* 75 | * Intantiate the main Depthcharge Companion firmware module 76 | */ 77 | Companion(); 78 | 79 | /* 80 | * Associate the Communicator with a serial port that will be used 81 | * to receive requests from the host. 82 | */ 83 | void attachHostInterface(::Stream *port); 84 | 85 | void attachLED(unsigned int pin, 86 | unsigned int on_state, unsigned int off_state); 87 | 88 | void attachI2C(::TwoWire *bus, uint8_t addr, uint32_t speed); 89 | 90 | /* 91 | * TODO 92 | */ 93 | void processEvents(); 94 | 95 | private: 96 | void handleHostMessage(Communicator::msg &msg); 97 | void panicLoop(); 98 | 99 | static void _handleI2CRead(int n); 100 | 101 | uint32_t m_caps; 102 | 103 | Communicator m_comm; // Host interface 104 | I2CPeriph m_i2c; // Operate as I2C peripheral device 105 | LED m_led; // Blinks panic status 106 | }; 107 | }; 108 | -------------------------------------------------------------------------------- /firmware/Arduino/Depthcharge/I2CPeriph.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Depthcharge: 3 | 4 | #pragma once 5 | #include 6 | #include 7 | 8 | namespace Depthcharge { 9 | 10 | class I2CPeriph { 11 | 12 | public: 13 | I2CPeriph(); 14 | 15 | void attach(::TwoWire *bus, uint8_t addr, uint32_t speed); 16 | bool attached(); 17 | 18 | void setAddress(uint8_t addr); 19 | uint8_t getAddress(); 20 | 21 | void setSubAddressLength(uint8_t len); 22 | uint8_t getSubAddressLength(); 23 | 24 | void setSpeed(uint32_t speed); 25 | uint32_t getSpeed(); 26 | 27 | uint32_t getWriteBuffer(uint8_t *buf, size_t max_len); 28 | void setReadBuffer(uint8_t *buf, size_t len); 29 | 30 | // This seems to be an implicit limit Arduino APIs, unfortunately. 31 | // Torn between hacking around it and trying to remain portable... 32 | static const size_t BUFFER_SIZE = 32; 33 | 34 | private: 35 | static ::TwoWire *m_i2c; 36 | 37 | static uint8_t m_addr; // Device address in [0x00, 0x7f] 38 | static uint32_t m_speed; // Bus speed, Hz 39 | 40 | // Handle data written from the bus controller to our device buffer 41 | static void _handle_write(int count); 42 | 43 | // Handle read of data from our device buffer, to the host 44 | static void _handle_read(); 45 | 46 | /* We have plenty of space on the Teensy 3.6, so no 47 | * reason not to simplify things by using different read/write 48 | * buffers. For more memory constrained devices, we might 49 | * want to replace this with a single buffer. The host-code 50 | * is in control of the target's bus controller, so in theory, 51 | * we should not have to worry about concurrent accesses attempts. 52 | * 53 | * TODO: Arguably these might need to be volatile, since we're 54 | * accessing them across normal and interrupt contexts. 55 | * 56 | * However, it's a bit of a PITA since we'll have to 57 | * cast away the volatile attribute when we hit the 58 | * the platform-specific TwoWire APIs. 59 | * 60 | * Need to re-read through the C++11 book and spec and mull 61 | * this over. For now, we at least ensure that accesses 62 | * are atomic, and no control flows are actively waiting 63 | * for a change to occur from the other context. 64 | */ 65 | static uint8_t m_rbuf[BUFFER_SIZE]; 66 | static size_t m_rcount; 67 | 68 | static uint8_t m_wbuf[BUFFER_SIZE]; 69 | static size_t m_wcount; 70 | 71 | // How many subaddress bytes to throw away and ignore 72 | static uint8_t m_subaddr_len; 73 | }; 74 | } 75 | -------------------------------------------------------------------------------- /firmware/Arduino/Depthcharge/LED.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Depthcharge: 3 | 4 | #include "LED.h" 5 | 6 | namespace Depthcharge { 7 | LED::LED() : m_pin(-1), m_on(HIGH), m_off(LOW) {} 8 | 9 | void LED::attach(unsigned int pin, unsigned int on, unsigned int off) 10 | { 11 | m_pin = pin; 12 | m_on = on; 13 | m_off = off; 14 | 15 | pinMode(m_pin, OUTPUT); 16 | this->on(); 17 | } 18 | 19 | 20 | void LED::blink(unsigned int ms_on, unsigned int ms_off, unsigned int n) 21 | { 22 | if (m_pin < 0) { 23 | return; 24 | } 25 | 26 | for (unsigned int i = 0; i < n; i++) { 27 | this->on(); 28 | delay(ms_on); 29 | 30 | this->off(); 31 | delay(ms_off); 32 | } 33 | } 34 | 35 | void LED::blink_value(uint32_t value, unsigned int n, 36 | unsigned int ms_bit_period) 37 | { 38 | if (m_pin < 0) { 39 | return; 40 | } 41 | 42 | unsigned int i; 43 | 44 | if (n > 32) { 45 | n = 32; 46 | } 47 | 48 | for (i = 0; i < n; i++) { 49 | if (value & (1 << 31)) { 50 | on(); 51 | } else { 52 | off(); 53 | } 54 | delay(ms_bit_period); 55 | 56 | value <<= 1; 57 | } 58 | } 59 | 60 | void LED::on() { 61 | m_state = true; 62 | digitalWrite(m_pin, m_on); 63 | } 64 | 65 | void LED::off() { 66 | m_state = false; 67 | digitalWrite(m_pin, m_off); 68 | } 69 | 70 | void LED::toggle() { 71 | if (m_state) { 72 | this->off(); 73 | } else { 74 | this->on(); 75 | } 76 | } 77 | }; 78 | -------------------------------------------------------------------------------- /firmware/Arduino/Depthcharge/LED.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Depthcharge: 3 | 4 | #pragma once 5 | #include 6 | namespace Depthcharge { 7 | class LED { 8 | public: 9 | 10 | LED(); 11 | 12 | void attach(unsigned int pin, unsigned int on, unsigned int off); 13 | 14 | void blink(unsigned int ms_on, unsigned int ms_off, unsigned int n); 15 | 16 | /* 17 | * Blink an n-bit value on the LED, MSB-first. 18 | * 19 | * The blink period is constant, but the duty cycle differs for 20 | * 0 and 1 bits. 21 | * 22 | * A 1-bit will be a "slow" blink at a 50% duty cycle, and a 23 | * 0-bit is a "fast" blink, at a 20% duty cycle. 24 | */ 25 | void blink_value(uint32_t value, unsigned int n, 26 | unsigned int ms_bit_period); 27 | 28 | void on(); 29 | 30 | void off(); 31 | 32 | void toggle(); 33 | 34 | 35 | private: 36 | bool m_state; 37 | unsigned int m_pin; 38 | unsigned int m_on; 39 | unsigned int m_off; 40 | }; 41 | }; 42 | -------------------------------------------------------------------------------- /firmware/Arduino/Depthcharge/Panic.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Depthcharge: 3 | 4 | #include "Panic.h" 5 | namespace Depthcharge { 6 | uint32_t Panic::m_reason = 0; 7 | 8 | void Panic::setReason(Source source, uint16_t lineno) 9 | { 10 | noInterrupts(); 11 | if (m_reason == 0) { 12 | m_reason = ((source & 0xff) << 16) | lineno; 13 | } 14 | interrupts(); 15 | } 16 | 17 | bool Panic::active() 18 | { 19 | return m_reason != 0; 20 | } 21 | 22 | uint32_t Panic::reason() 23 | { 24 | return m_reason; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /firmware/Arduino/Depthcharge/Panic.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Depthcharge: 3 | 4 | #pragma once 5 | #include 6 | 7 | namespace Depthcharge { 8 | class Panic { 9 | public: 10 | enum Source { 11 | Communicator = 0x1, 12 | I2CPeriph = 0x2, 13 | }; 14 | 15 | static void setReason(Source source, uint16_t lineno); 16 | static bool active(); 17 | static uint32_t reason(); 18 | 19 | private: 20 | static uint32_t m_reason; 21 | }; 22 | 23 | }; 24 | 25 | -------------------------------------------------------------------------------- /firmware/Arduino/Depthcharge/Version.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | namespace Depthcharge { 4 | static const uint8_t VERSION_MAJOR = 0; 5 | static const uint8_t VERSION_MINOR = 1; 6 | static const uint8_t VERSION_PATCH = 0; 7 | static const uint8_t VERSION_EXTRA = 0; 8 | }; 9 | -------------------------------------------------------------------------------- /firmware/Arduino/Makefile: -------------------------------------------------------------------------------- 1 | BUILDS := builds 2 | 3 | # User is responsible for setting ARDUINO_BUILDER 4 | 5 | ARDUINO_USER_DIR ?= $(realpath $(HOME)/.arduino15)/ 6 | ARDUINO_INSTALL_DIR := $(dir $(realpath $(shell which $(ARDUINO_BUILDER)))) 7 | 8 | COMMON_ARGS := \ 9 | -core-api-version=10810 \ 10 | -hardware $(ARDUINO_INSTALL_DIR)hardware \ 11 | -hardware $(ARDUINO_USER_DIR)packages \ 12 | -tools $(ARDUINO_INSTALL_DIR)tools-builder \ 13 | -tools $(ARDUINO_INSTALL_DIR)hardware/tools/avr \ 14 | -tools $(ARDUINO_USER_DIR)packages \ 15 | -built-in-libraries $(ARDUINO_INSTALL_DIR)libraries \ 16 | -libraries $(realpath .) 17 | -warnings=all \ 18 | 19 | ifneq (VERBOSE,) 20 | COMMON_ARGS += -verbose 21 | endif 22 | 23 | 24 | help: 25 | @echo "" 26 | @echo "Usage: make " 27 | @echo "" 28 | @echo "Available targets:" 29 | @echo " teensy36 Teensy 3.6 (https://www.pjrc.com/store/teensy36.html)" 30 | @echo " clean Clean build files" 31 | @echo "" 32 | @if [ -z "$(ARDUINO_BUILDER)" ]; then\ 33 | echo "Builder: Not specified - set via environment variable or Make argument."; \ 34 | echo " This should be the full path to the arduino-builder program"; \ 35 | echo " included with the Arduino IDE."; \ 36 | else \ 37 | echo "Builder: $(ARDUINO_BUILDER)"; \ 38 | fi 39 | @echo "" 40 | 41 | all: teensy36 42 | 43 | $(BUILDS): 44 | @mkdir -p "$@" 45 | 46 | builder_check: $(BUILDS) 47 | @if [ -z "$(ARDUINO_BUILDER)" ]; then \ 48 | echo "Error: ARDUINO_BUILDER variable not specified." >&2; \ 49 | echo " This should be the full path to the arduino-builder program" >&2; \ 50 | echo " provided with the Arduino IDE." >&2; \ 51 | echo "" >&2; \ 52 | exit 1; \ 53 | elif [ -f $(ARDUINO_BUILDER) -a -d $(ARDUINO_INSTALL_DIR) ]; then \ 54 | echo "Found $(ARDUINO_BUILDER) in $(ARDUINO_INSTALL_DIR)"; \ 55 | else \ 56 | echo "Cannot find $(ARDUINO_BUILDER)" >&2; \ 57 | echo "ARDUINO_INSTALL_DIR=$(ARDUINO_INSTALL_DIR)" >&2; \ 58 | exit 1; \ 59 | fi 60 | 61 | teensy36: Depthcharge-Teensy3.6/Depthcharge-Teensy3.6.ino builder_check 62 | @mkdir -p $(ARDUINO_USER_DIR)/packages 63 | @mkdir -p $(BUILDS)/$@ 64 | $(ARDUINO_BUILDER) -compile \ 65 | $(COMMON_ARGS) \ 66 | -build-path $(BUILDS)/$@ -build-cache $(BUILDS)/$@ \ 67 | -fqbn=teensy:avr:teensy36:usb=serial,speed=180,opt=o2std,keys=en-us \ 68 | $< 69 | 70 | clean: 71 | rm -rfv $(BUILDS) 72 | 73 | .PHONY: clean 74 | -------------------------------------------------------------------------------- /firmware/Arduino/README.md: -------------------------------------------------------------------------------- 1 | This directory provides a Makefile-based build for Arduino firmware. 2 | 3 | # Make-based build 4 | 5 | To build the firmware for a supported target platform, invoke make with: 6 | 7 | 1. The path to the `ardunio-builder` program included with the Arduino IDE. 8 | 2. The target platform name, as shown in the output for `make help`. 9 | 10 | Item 1 can be done using an `ARDUINO_BUILDER` environment variable, 11 | or by specifying it in the `make` invocation. Both are shown below, for a build 12 | of firmware targeting the Teensy3.6 platform. 13 | 14 | ``` 15 | $ export ARDUINO_BUILDER=/path/to/arduino-builder 16 | $ make teensy36 17 | ``` 18 | 19 | ``` 20 | $ make ARDUINO_BUILDER=/path/to/arduino-builder teensy36 21 | ``` 22 | 23 | The `arduino-builder` program, included with all modern Arduino-based 24 | environments, is used to support this. However, this program does not appear 25 | to be installed to users' `$PATH` when the Arduino IDE is installed. This 26 | may be for the best, given that different Arduino environments (e.g. official 27 | vs Energia) ship their own `arduino-builder` program. You must use the program 28 | corresponding to the relevant build environment. 29 | 30 | If your Arduino preferences are stored in a directory other than the default 31 | `$HOME/arduino15` directory, you must also specify the full path to this 32 | directory via `ARDUINO_USER_DIR`. 33 | 34 | 35 | # IDE-based build 36 | 37 | If you instead prefer to use the Arduino IDE, you'll need to copy or symlink 38 | the `Depthcharge` library (the entire directory) to your personal 39 | `Arduino/libraries` directory. 40 | 41 | Then, open the target-specific ino file in the Arduino IDE. 42 | 43 | # Directories 44 | 45 | * The `Depthcharge` directory contains the core firmware functionality as a "library." 46 | * Directories in the form `Depthcharge-` contain the top-level and 47 | platform-specific, ino files. These are intended to be minimalist 48 | implementations that simply initialize peripherals and invoke a 49 | `Depthcharge::Companion` instance's `processEvents()` method. 50 | * A `builds/` directory is created to store build artifacts. 51 | -------------------------------------------------------------------------------- /payloads/.gitignore: -------------------------------------------------------------------------------- 1 | output/* 2 | -------------------------------------------------------------------------------- /payloads/Makefile: -------------------------------------------------------------------------------- 1 | ARCH ?= arm 2 | 3 | ifeq ($(ARCH), arm) 4 | CROSS_COMPILE ?= arm-none-eabi- 5 | else ifeq ($(ARCH), aarch64) 6 | CROSS_COMPILE ?= aarch64-none-elf- 7 | endif 8 | 9 | INCLUDE_DIR := include 10 | OUTPUT_DIR := output 11 | 12 | PAYLOADS := $(patsubst src/%.c,%,$(wildcard src/*.c)) 13 | ELF_OUTPUT := $(PAYLOADS:%=$(OUTPUT_DIR)/$(ARCH)-%.elf) 14 | BINARIES := $(PAYLOADS:%=$(OUTPUT_DIR)/$(ARCH)-%.bin) 15 | ASM_OUTPUT := $(PAYLOADS:%=$(OUTPUT_DIR)/$(ARCH)-%.asm) 16 | 17 | DEBUG ?= n 18 | 19 | CC := $(CROSS_COMPILE)gcc 20 | LD := $(CROSS_COMPILE)ld 21 | OBJCOPY := $(CROSS_COMPILE)objcopy 22 | OBJDUMP := $(CROSS_COMPILE)objdump 23 | 24 | CFLAGS = \ 25 | -Wall -Wextra \ 26 | -ffreestanding -fPIC -nostdlib \ 27 | -I$(INCLUDE_DIR) \ 28 | -DARCH_$(ARCH) 29 | 30 | ifeq (DEBUG,y) 31 | CFLAGS += -O0 32 | else 33 | CFLAGS += -O2 34 | endif 35 | 36 | # Allow user to append additional flags via CFLAGS_EXTRA 37 | CFLAGS += $(CFLAGS_EXTRA) 38 | 39 | all: output/payload.py $(BINARIES) $(ASM_OUTPUT) 40 | 41 | output: 42 | mkdir -p $@ 43 | 44 | output/$(ARCH)-%.elf: src/%.c output 45 | $(CC) $(CFLAGS) -e main $< -o $@ 46 | 47 | output/$(ARCH)-%.bin: output/$(ARCH)-%.elf 48 | $(OBJCOPY) -O binary $< $@ 49 | 50 | output/$(ARCH)-%.asm: output/$(ARCH)-%.bin 51 | $(OBJDUMP) -m $(ARCH) -b binary -D $< > $@ 52 | 53 | output/payload.py: $(BINARIES) 54 | python3 ./create-payload-src.py output payload.py 55 | 56 | clean: 57 | rm -rf output 58 | 59 | .PHONY: clean 60 | -------------------------------------------------------------------------------- /payloads/README.md: -------------------------------------------------------------------------------- 1 | This directory contains the built-in example payloads 2 | and a Makefile that you can use to to build them. 3 | -------------------------------------------------------------------------------- /payloads/create-payload-src.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | import os 4 | import re 5 | import sys 6 | import time 7 | 8 | 9 | def load_payloads(output_dir): 10 | file_regex = re.compile(r'(?P\w+)-(?P[\w-]+)\.bin') 11 | 12 | payloads = {} 13 | 14 | for root, _, files in os.walk(output_dir): 15 | for f in files: 16 | m = file_regex.match(f) 17 | if m is None: 18 | continue 19 | 20 | arch = m.group('arch').lower() 21 | payload_name = m.group('payload').lower() 22 | 23 | payload = payloads.get(payload_name, {}) 24 | 25 | with open(os.path.join(root, f), 'rb') as infile: 26 | payload[arch] = infile.read() 27 | 28 | payloads[payload_name] = payload 29 | 30 | return payloads 31 | 32 | 33 | def _format_bytes(payload: bytes) -> str: 34 | ret = '' 35 | col = 0 36 | sol = True 37 | 38 | for value in payload: 39 | if sol: 40 | sol = False 41 | ret += os.linesep + ' ' * 8 + "b'" 42 | 43 | ret += r'\x{:02x}'.format(value) 44 | col += 1 45 | 46 | if col == 16: 47 | ret += "'" 48 | sol = True 49 | col = 0 50 | 51 | if col != 0: 52 | ret += "'" 53 | 54 | return ret 55 | 56 | 57 | def write_payload_source(outfile, payloads): 58 | outfile.write('# SPDX-License-Identifier: BSD-3-Clause' + os.linesep) 59 | outfile.write('"""' + os.linesep) 60 | outfile.write('Built-in Depthcharge payloads' + os.linesep) 61 | outfile.write('(Autogenerated on ' + time.asctime() + ')' + os.linesep) 62 | outfile.write('"""' + os.linesep) 63 | 64 | for name in payloads: 65 | outfile.write(os.linesep) 66 | outfile.write(name.upper() + ' = {' + os.linesep) 67 | 68 | for arch in payloads[name]: 69 | outfile.write(' ' * 4 + "'" + arch + "':") 70 | outfile.write(_format_bytes(payloads[name][arch])) 71 | outfile.write(',' + os.linesep) 72 | 73 | outfile.write('}' + os.linesep) 74 | 75 | 76 | if __name__ == '__main__': 77 | if len(sys.argv) < 3 or '-h' in sys.argv or '--help' in sys.argv: 78 | usage = 'Usage: {:s}: ' 79 | print(usage.format(os.path.basename(sys.argv[0])), file=sys.stderr) 80 | sys.exit(1) 81 | 82 | payloads = load_payloads(sys.argv[1]) 83 | with open(os.path.join(sys.argv[1], sys.argv[2]), 'w') as outfile: 84 | write_payload_source(outfile, payloads) 85 | -------------------------------------------------------------------------------- /payloads/include/depthcharge.h: -------------------------------------------------------------------------------- 1 | #ifndef DEPTHCHARGE_H__ 2 | #define DEPTHCHARGE_H__ 3 | 4 | #define UNUSED(var) do { (void) var; } while (0) 5 | 6 | #ifdef ARCH_arm 7 | # define DECLARE_GLOBAL_DATA_VOID_PTR(gd) \ 8 | register volatile void *gd asm("r9") 9 | #elif ARCH_aarch64 10 | # define DECLARE_GLOBAL_DATA_VOID_PTR(gd) \ 11 | register volatile void *gd asm("x18") 12 | #else 13 | # error "Unsupported architechture" 14 | #endif 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /payloads/include/str2uint.h: -------------------------------------------------------------------------------- 1 | #ifndef STR2UINT_H__ 2 | #define STR2UINT_H__ 3 | 4 | #include 5 | #include "strlen.h" 6 | 7 | static inline unsigned int str2uint_hex(const char *s) 8 | { 9 | unsigned int value = 0; 10 | 11 | while (*s) { 12 | value <<= 4; 13 | 14 | unsigned int c = *s; 15 | if (c >= '0' && c <= '9') { 16 | c -= '0'; 17 | } else if (c >= 'a' && c <= 'f') { 18 | c = c - 'a' + 10; 19 | } else if (c >= 'A' && c <= 'F') { 20 | c = c - 'A' + 10; 21 | } else { 22 | return 0; 23 | } 24 | 25 | value += c; 26 | s++; 27 | } 28 | 29 | return value; 30 | } 31 | 32 | static inline unsigned int str2uint_dec(const char *s) 33 | { 34 | unsigned int value = 0; 35 | 36 | while (*s) { 37 | value *= 10; 38 | 39 | unsigned int c = *s; 40 | if (c >= '0' && c <= '9') { 41 | c -= '0'; 42 | } else { 43 | return 0; 44 | } 45 | 46 | value += c; 47 | s++; 48 | } 49 | 50 | return value; 51 | } 52 | 53 | // Simple string to unsigned int conversion with an atoi-esque lack of proper 54 | // input validation and overflow checks. Returns 0 on invalid input and will 55 | // overflow if input exceeds the size of an unsigned int. 56 | static inline unsigned int str2uint(const char *s) 57 | { 58 | size_t len = strlen(s); 59 | if (len > 2 && s[0] == '0' && s[1] == 'x') { 60 | s += 2; 61 | return str2uint_hex(s); 62 | } 63 | return str2uint_dec(s); 64 | } 65 | 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /payloads/include/str2ulong.h: -------------------------------------------------------------------------------- 1 | #ifndef STR2ULONG_H__ 2 | #define STR2ULONG_H__ 3 | 4 | #include "strlen.h" 5 | 6 | static inline unsigned long str2ulong_hex(const char *s) 7 | { 8 | unsigned long value = 0; 9 | 10 | while (*s) { 11 | value <<= 4; 12 | 13 | unsigned long c = *s; 14 | if (c >= '0' && c <= '9') { 15 | c -= '0'; 16 | } else if (c >= 'a' && c <= 'f') { 17 | c = c - 'a' + 10; 18 | } else if (c >= 'A' && c <= 'F') { 19 | c = c - 'A' + 10; 20 | } else { 21 | return 0; 22 | } 23 | 24 | value += c; 25 | s++; 26 | } 27 | 28 | return value; 29 | } 30 | 31 | static inline unsigned long str2ulong_dec(const char *s) 32 | { 33 | unsigned long value = 0; 34 | 35 | while (*s) { 36 | value *= 10; 37 | 38 | unsigned long c = *s; 39 | if (c >= '0' && c <= '9') { 40 | c -= '0'; 41 | } else { 42 | return 0; 43 | } 44 | 45 | value += c; 46 | s++; 47 | } 48 | 49 | return value; 50 | } 51 | 52 | // Simple string to unsigned long conversion with an atoi-esque lack of proper 53 | // input validation and overflow checks. Returns 0 on invalid input and will 54 | // overflow if input exceeds the size of an unsigned long. 55 | static inline unsigned long str2ulong(const char *s) 56 | { 57 | size_t len = strlen(s); 58 | if (len > 2 && s[0] == '0' && s[1] == 'x') { 59 | s += 2; 60 | return str2ulong_hex(s); 61 | } 62 | return str2ulong_dec(s); 63 | } 64 | 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /payloads/include/strcmp.h: -------------------------------------------------------------------------------- 1 | #ifndef STRCMP_H__ 2 | #define STRCMP_H__ 3 | 4 | int strcmp(const char *s1, const char *s2) { 5 | while (s1 != '\0' && s2 != '\0') { 6 | if (s1 != s2) { 7 | return *s1 - *s2; 8 | } 9 | s1++; s2++; 10 | } 11 | return *s1 - *s2; 12 | } 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /payloads/include/strlen.h: -------------------------------------------------------------------------------- 1 | #ifndef STRLEN_H__ 2 | #define STRLEN_H__ 3 | 4 | #include 5 | 6 | static inline size_t strlen(const char *s) 7 | { 8 | size_t ret = 0; 9 | while (*s++) { 10 | ret++; 11 | } 12 | return ret; 13 | } 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /payloads/include/u-boot.h: -------------------------------------------------------------------------------- 1 | #ifndef UBOOT_H_ 2 | #define UBOOT_H_ 3 | 4 | #include 5 | #include 6 | 7 | typedef struct { 8 | unsigned long (*get_version)(void); 9 | int (*getc)(void); 10 | int (*tstc)(void); 11 | void (*putc)(const char); 12 | void (*puts)(const char *); 13 | int (*printf)(const char *, ...); 14 | void (*install_hdler)(int, void*, void*); 15 | void (*free_hdlr)(int); 16 | void* (*malloc)(size_t); 17 | void (*free)(void*); 18 | void (*udelay)(unsigned long); 19 | unsigned long (*get_timer)(unsigned long); 20 | int (*vprintf)(const char *, va_list); 21 | int (*do_reset)(void*, int, int, char * const[]); 22 | char* (*env_get)(const char *); 23 | int (*env_set)(const char *, const char *); 24 | unsigned long (*simple_strtoul)(const char *, char **, unsigned int); 25 | int (*strict_strtoul)(const char *, unsigned int, unsigned long *); 26 | long (*simple_strtol)(const char *, char **, unsigned int); 27 | int (*strcmp)(const char *, const char *); 28 | unsigned long (*ustrtoul)(const char *, char **, unsigned int); 29 | unsigned long long (*ustrtoull)(const char *, char **, unsigned int); 30 | } jt_funcs_t; 31 | 32 | typedef struct { 33 | void *bd; 34 | unsigned long int flags; 35 | unsigned int baudrate; 36 | unsigned long clks[4]; 37 | unsigned long padding[21]; 38 | jt_funcs_t *jt; 39 | } global_data_t; 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /payloads/src/read_memory.c: -------------------------------------------------------------------------------- 1 | #include "depthcharge.h" 2 | #include "u-boot.h" 3 | #include "str2ulong.h" 4 | 5 | unsigned long main(int argc, char *argv[]) 6 | { 7 | int status; 8 | jt_funcs_t *jt; 9 | unsigned long jt_ul; 10 | unsigned long mem_addr, mem_len; 11 | 12 | if (argc != 4) { 13 | return 1; 14 | } 15 | 16 | jt_ul = str2ulong(argv[1]); 17 | if (jt_ul == 0) { 18 | return 2; 19 | } 20 | jt = (jt_funcs_t*) jt_ul; 21 | 22 | status = jt->strict_strtoul(argv[2], 0, &mem_addr); 23 | if (status != 0) { 24 | jt->printf("Invalid memory address: %s\n", argv[1]); 25 | return 3; 26 | } 27 | 28 | status = jt->strict_strtoul(argv[3], 0, &mem_len); 29 | if (status != 0) { 30 | jt->printf("Invalid memory length: %s\n", argv[2]); 31 | return 4; 32 | } 33 | 34 | jt->puts("-:[START]:-"); 35 | jt->getc(); 36 | 37 | unsigned int i; 38 | volatile char * addr = (volatile char *) mem_addr; 39 | 40 | for (i = 0; i < mem_len; i++) { 41 | jt->putc(addr[i]); 42 | } 43 | 44 | jt->puts("-:[|END|]:-"); 45 | 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /payloads/src/return_memory_word.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "depthcharge.h" 4 | #include "str2ulong.h" 5 | 6 | unsigned long main(int argc, char * argv[]) 7 | { 8 | DECLARE_GLOBAL_DATA_VOID_PTR(gd); 9 | unsigned long *ret_p; 10 | 11 | if (argc < 2) { 12 | return (unsigned long) gd; 13 | } 14 | 15 | ret_p = (unsigned long *) str2ulong(argv[1]); 16 | return (*ret_p); 17 | } 18 | -------------------------------------------------------------------------------- /python/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | 3 | extend-ignore=E221,E222,E272,E241 # I prefer to align assignments along operators. 4 | 5 | # It's 2020 and we have wide screens. 6 | # Shoot for 80 chars, but I'd rather see a longer line if it's clearer to read. 7 | max_line_length = 120 8 | -------------------------------------------------------------------------------- /python/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include Depthcharge.md 2 | recursive-include . tests 3 | -------------------------------------------------------------------------------- /python/README.md: -------------------------------------------------------------------------------- 1 | # Depthcharge Python Code 2 | 3 | This directory contains the [depthcharge](./depthcharge) Python module, 4 | a collection of [scripts](./scripts) built atop of that modules, 5 | [example scripts](./examples), and the Sphinx-based 6 | [documentation source](./docs). 7 | 8 | The latest release of this code is available on the 9 | [Python Package Index (PyPi)](https://pypi.org/project/depthcharge), 10 | and can be installed via `python3 -m pip install depthcharge`. 11 | 12 | The instructions below are intended for those wishing to work with the latest 13 | code [available on GitHub](https://github.com/tetrelsec/depthcharge/tree/next). 14 | 15 | ## Python Dependencies 16 | 17 | The following represent the minimum versions Depthcharge has been tested with. 18 | Earlier versions may suffice, but are not supported. 19 | 20 | * Python >= 3.6 21 | * [pyserial](https://github.com/pyserial/pyserial) >= 3.4 22 | * [tqdm](https://tqdm.github.io/) >= 4.42.1 23 | 24 | The following dependencies are required if you'd like to build the 25 | documentation: 26 | 27 | * [Sphinx](https://pypi.org/project/Sphinx) >= 3.0.0 28 | * [sphinx_rtd_theme](https://github.com/readthedocs/sphinx_rtd_theme) >= 0.4.0 29 | 30 | ## Installation 31 | 32 | Given that the Depthcharge is still in its "beta" state, its dependencies and 33 | API may change across version. As such, users are encouraged to install it into 34 | a [virtual environment](https://docs.python.org/3/library/venv.html) (venv), rather 35 | than system-wide. Below are the commands required to do this. 36 | 37 | First, ensure you have installed `python3-venv`. For apt-based distros: 38 | 39 | ``` 40 | sudo apt install python3-venv 41 | ``` 42 | 43 | Next, create a virtual environment and install Depthcharge 44 | and its dependencies in it. Note that there are `activate.csh` and 45 | `activate.fish` scripts for users of those shells. 46 | 47 | ``` 48 | python3 -m venv ./venv 49 | source ./venv/bin/activate 50 | python3 -m pip install . 51 | ``` 52 | 53 | If you plan on modifying the source code or documentation, add the ``-e, --editable`` 54 | flag to the pip command and specify the extra ``[docs]`` option in 55 | order to include the necessary *Sphinx* and *sphinx_rtd_theme* dependencies: 56 | 57 | ``` 58 | python3 -m pip install -e .[docs] 59 | ``` 60 | -------------------------------------------------------------------------------- /python/depthcharge/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | # 4 | # flake8: noqa=F401 5 | """ 6 | Depthcharge: A U-Boot hacking toolkit for security researchers and tinkerers 7 | 8 | Documentation: 9 | Source Code: 10 | """ 11 | 12 | from .version import __version__ 13 | 14 | # Expose items from the various submodules to the top-level namespace 15 | 16 | from . import log 17 | 18 | from . import checker 19 | from . import hunter 20 | from . import memory 21 | from . import register 22 | from . import uboot 23 | 24 | 25 | from .context import Depthcharge 26 | from .console import Console 27 | from .companion import Companion 28 | 29 | from .operation import (Operation, 30 | OperationSet, 31 | OperationNotSupported, 32 | OperationFailed, 33 | OperationAlignmentError) 34 | 35 | from .arch import Architecture 36 | 37 | from .progress import Progress, ProgressBar 38 | from .stratagem import (Stratagem, 39 | StratagemNotRequired, 40 | StratagemRequired, 41 | StratagemCreationFailed) 42 | -------------------------------------------------------------------------------- /python/depthcharge/arch/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # 3 | # flake8: noqa=F401 4 | 5 | """ 6 | Support for various target architectures 7 | """ 8 | 9 | from .arch import Architecture, NoDataAbortContent 10 | 11 | from .arm import ARM 12 | from .aarch64 import AARCH64 13 | from .generic import Generic, GenericBE, Generic64, Generic64BE 14 | -------------------------------------------------------------------------------- /python/depthcharge/arch/arm.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | """ 4 | ARM 32-bit support 5 | """ 6 | 7 | import os 8 | import re 9 | 10 | from .arch import Architecture, NoDataAbortContent 11 | 12 | 13 | class ARM(Architecture): 14 | """ 15 | ARMv7 (or earlier) target information - 32-bit little-endian 16 | """ 17 | _desc = 'ARM 32-bit, little-endian' 18 | _alignment = 4 19 | _word_size = 4 20 | _phys_size = 4 21 | _word_mask = 0xffffffff 22 | _endianness = 'little' 23 | _supports_64bit_data = False 24 | _da_crash_addr = 1 25 | 26 | _regs = { 27 | 'r0': {'da_data_reg': True}, 28 | 'r1': {}, 29 | 'r2': {}, 30 | 'r3': {}, 31 | 'r4': {}, 32 | 'r5': {}, 33 | 'r6': {}, 34 | 'r7': {}, 35 | 'r8': {}, 36 | 'r9': {'gd': True, 'alias': 'sb'}, 37 | 'r10': {}, 38 | 'r11': {'alias': 'fp'}, 39 | 'r12': {'alias': 'ip'}, 40 | 'r13': {'alias': 'sp'}, 41 | 'r14': {'alias': 'lr'}, 42 | 'r15': {'alias': 'pc'}, 43 | } 44 | 45 | _DA_ENTRY = re.compile(r""" 46 | (?P[a-zA-Z][a-zA-Z0-9]+) 47 | \s?:\s? 48 | (\[<)? 49 | (?P[0-9a-fA-F]{8}) 50 | (>\])? 51 | """, re.VERBOSE) 52 | 53 | @classmethod 54 | def parse_data_abort(cls, text: str) -> dict: 55 | """ 56 | Parse ARM data abort output formatted as follows and return each field in a dict. 57 | 58 | 00000001:data abort 59 | pc : [<8f7d8858>] lr : [<8f7d8801>] 60 | reloc pc : [<17835858>] lr : [<17835801>] 61 | sp : 8ed99718 ip : 00000000 fp : 00000001 62 | r10: 00000001 r9 : 8eda2ea8 r8 : 00000001 63 | r7 : 00000000 r6 : 00000004 r5 : 00000004 r4 : 00000001 64 | r3 : 8ed9972c r2 : 020200b4 r1 : 8ed994ec r0 : 00000009 65 | Flags: nZCv IRQs off FIQs off Mode SVC_32 66 | Code: 2800f915 f04fd0cf e7ce30ff d10a2d04 (2000f8d8) 67 | 68 | Note: The "Mode" entry under "Flags:" will kcontain a " (T)" suffix 69 | when the device is in Thumb mode. 70 | """ 71 | ret = {} 72 | for line in text.splitlines(): 73 | line = line.strip() 74 | 75 | if line.startswith('Flags:'): 76 | ret['flags'] = {} 77 | for field in line.split(' '): 78 | name, value = field.split(' ', 1) 79 | name = name.replace('Flags:', 'Asserted') 80 | ret['flags'][name] = value 81 | continue 82 | 83 | elif line.startswith('Code:'): 84 | ret['code'] = cls._parse_instructions(line) 85 | else: 86 | if line.startswith('reloc '): 87 | pfx = 'reloc ' 88 | line = line[len(pfx):] 89 | else: 90 | pfx = '' 91 | 92 | for match in cls._DA_ENTRY.finditer(line): 93 | regname, _ = cls.register(match.group('name')) 94 | name = pfx + regname 95 | value = match.group('value') 96 | 97 | regs = ret.get('registers', {}) 98 | 99 | try: 100 | regs[name] = int(value, 16) 101 | except ValueError: 102 | regs[name] = value 103 | 104 | ret['registers'] = regs 105 | 106 | if not ret: 107 | msg = 'No data abort content found in the following text:' + os.linesep 108 | msg += text 109 | raise NoDataAbortContent(msg) 110 | 111 | return ret 112 | -------------------------------------------------------------------------------- /python/depthcharge/arch/generic.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | 4 | """ 5 | Generic architectures 6 | """ 7 | 8 | from .arch import Architecture 9 | 10 | 11 | class _Generic32(Architecture): 12 | """ 13 | Generic 32-bit architecture with 4-byte word alignment 14 | """ 15 | _alignment = 4 16 | _word_size = 4 17 | _phys_size = 4 18 | _word_mask = 0xffffffff 19 | _supports_64bit_data = False 20 | _generic = True 21 | _regs = {} 22 | 23 | 24 | class Generic(_Generic32): 25 | """ 26 | Generic 32-bit little endian architecture with 4-byte word alignment 27 | """ 28 | _desc = 'Generic 32-bit, little-endian' 29 | _endianness = 'little' 30 | 31 | 32 | class GenericBE(_Generic32): 33 | """ 34 | Generic 32-bit big endian architecture with 4-byte word alignment 35 | """ 36 | _desc = 'Generic 32-bit, big-endian' 37 | _endianness = 'big' 38 | 39 | 40 | class _Generic64(Architecture): 41 | """ 42 | Generic 64-bit architecture with 8-byte word alignment 43 | """ 44 | _alignment = 8 45 | _word_size = 8 46 | _phys_size = 8 47 | _word_mask = 0xffffffff_ffffffff 48 | _supports_64bit_data = True 49 | _generic = True 50 | _regs = {} 51 | 52 | 53 | class Generic64(_Generic64): 54 | """ 55 | Generic 64-bit little endian architecture with 8-byte word alignment 56 | """ 57 | _desc = 'Generic 64-bit, little-endian' 58 | _endianness = 'little' 59 | 60 | 61 | class Generic64BE(_Generic64): 62 | """ 63 | Generic 64-bit big endian architecture with 8-byte word alignment 64 | """ 65 | _desc = 'Generic 64-bit, big-endian' 66 | _endianness = 'big' 67 | -------------------------------------------------------------------------------- /python/depthcharge/checker/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | # 4 | # flake8: noqa=F401 5 | 6 | """ 7 | This subpackage provides functionality for building U-Boot "security checker" tooling. 8 | """ 9 | 10 | from .security_risk import SecurityRisk, SecurityImpact 11 | from .report import Report 12 | 13 | from .config_checker import ConfigChecker 14 | from .uboot_config import UBootConfigChecker 15 | from .uboot_header import UBootHeaderChecker 16 | -------------------------------------------------------------------------------- /python/depthcharge/checker/_builtins/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | # 4 | # flake8: noqa=F401 5 | 6 | """ 7 | Built-in definitions of potential security risks. 8 | 9 | Each submodule shall define _BUILTIN_DEFS as list/tuple containing 10 | entries in the form ``(config_key, match, SecurityRisk)``. 11 | 12 | """ 13 | 14 | from .cmdline import _BUILTIN_DEFS as _CMDLINE_DEFS 15 | from .env import _BUILTIN_DEFS as _ENV_DEFS 16 | from .fit import _BUILTIN_DEFS as _FIT_DEFS 17 | from .fs import _BUILTIN_DEFS as _FS_DEFS 18 | from .lib import _BUILTIN_DEFS as _LIB_DEFS 19 | from .net import _BUILTIN_DEFS as _NET_DEFS 20 | from .usb import _BUILTIN_DEFS as _USB_DEFS 21 | 22 | # Aggregate builtins into a main set of definitions 23 | _BUILTIN_DEFS = ( 24 | _CMDLINE_DEFS + 25 | _ENV_DEFS + 26 | _FIT_DEFS + 27 | _FS_DEFS + 28 | _LIB_DEFS + 29 | _NET_DEFS + 30 | _USB_DEFS 31 | ) 32 | 33 | # Fill empty source field. 34 | # Required by SecurityRisk constructor, but excessive to require in _BUILTIN_DEFS 35 | for entry in _BUILTIN_DEFS: 36 | entry[2]['source'] = '' 37 | -------------------------------------------------------------------------------- /python/depthcharge/checker/_builtins/arch.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | 4 | """ 5 | Built-in SecurityRisk definitions associated with architecture-specific issues 6 | and chipset-specific errata. 7 | """ 8 | 9 | # TODO: Collection of Spectre mitigations actually useful to check for? 10 | # 11 | # See commits: 12 | # a6759e3dfca217ba8cc518edcd4972671a97109a 13 | # ee322f3c79a86e6f26629f8535cddb2b844d5113 14 | # 94c6a89a99ce651b97fae565b32d79bf86643415 15 | # dbb7caf110c4a7b9afb7cdc195ac6967d3a30adf 16 | # 17 | # Which enable functionality added in 18 | # c2ca3fdfb916dc8baecea88490df20de4244a7e1 19 | # 7b37a9c732bfec392b8f081eefa83427f794f937 20 | -------------------------------------------------------------------------------- /python/depthcharge/checker/_builtins/lib.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | 4 | """ 5 | Built-in SecurityRisk definitions associated with code in U-Boot's lib/ source 6 | directory, which generally originates from third-party projects. 7 | """ 8 | 9 | from textwrap import dedent 10 | 11 | from .. import SecurityImpact 12 | 13 | 14 | def _random_uuid(value: bool, config: dict): 15 | return value is True and ( 16 | config.get('CONFIG_RANDOM_UUID', False) or 17 | config.get('CONFIG_CMD_UUID', False) 18 | ) 19 | 20 | 21 | _BUILTIN_DEFS = ( 22 | ('CONFIG_ZLIB', True, { 23 | 'identifier': 'CVE-2016-9840', 24 | 'summary': 'Enabled zlib code contains pointer arithmetic that relies upon undefined behavior', 25 | 'impact': SecurityImpact.UNKNOWN, 26 | 27 | 'description': dedent("""\ 28 | Pointer arithmetic in zlib code used by U-Boot (`lib/zlib/inftrees.c`) relies upon 29 | undefined behavior (in the C standard) to operate correctly. An incorrect pointer 30 | value may be computed if code emitted by the compiler deviates from that which 31 | was intended by the author. 32 | 33 | Additional information and the upstream change in zlib may be found here: 34 | 35 | """), 36 | 37 | 'recommendation': dedent("""\ 38 | Upgrade to U-Boot 2020.10 or backport the fix in commit 499b7493. 39 | """), 40 | 41 | 'affected_versions': ('0.0', '2020.07') 42 | }), 43 | 44 | ('CONFIG_LIB_UUID', _random_uuid, { 45 | 'identifier': 'CVE-2019-11690', 46 | 'summary': 'Randomized UUID values used in GUID partitions tables are deterministic', 47 | 'impact': SecurityImpact.INFO_LEAK, 48 | 49 | 'description': dedent("""\ 50 | Random UUID values used when creating GPT entries are obtained from an unseeded PRNG. 51 | Therefore, the values used as UUIDs are effectively deterministic. 52 | """), 53 | 54 | 'recommendation': dedent("""\ 55 | Upgrade to U-Boot 2019.07 or backport the fix in commit 4ccf678f. 56 | 57 | If the unpredictability of UUIDs is an important component in fulfilling platform security 58 | requirements, review the relevant UUID code and rand() implementation (e.g. xorshift) in 59 | U-Boot and evaluate whether further improvements using a CSPRNG seeded from a TRNG, 60 | rather than `get_ticks()`. 61 | """), 62 | 63 | # CVE says "through v2019.04", but it looks like the fix landed in the v2019.07-rc2 window. 64 | 'affected_versions': ('2014.04', '2019.07-rc1') 65 | }), 66 | 67 | # TODO: CONFIG_LLB 68 | # CVE-2018-18440 - 69 | # See: 9cc2323feebdde500f50f7abb855045dbde765cb 70 | # ('0.0', '2019.01') 71 | ) 72 | -------------------------------------------------------------------------------- /python/depthcharge/checker/_builtins/usb.py: -------------------------------------------------------------------------------- 1 | 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Depthcharge: 4 | 5 | """ 6 | Built-in SecurityRisk definitions associated with USB functionality. 7 | """ 8 | 9 | from textwrap import dedent 10 | 11 | from .. import SecurityImpact 12 | 13 | _BUILTIN_DEFS = ( 14 | ('CONFIG_DFU_OVER_USB', True, { 15 | 'identifier': 'CVE-2022-2347', 16 | 'impact': SecurityImpact.WR_MEM | SecurityImpact.RD_MEM, 17 | 'summary': 'Unchecked download size and direction in USB DFU', 18 | 19 | 'description': dedent("""\ 20 | The USB DFU download implementation does not bound the length field 21 | in setup packets, nor does it verify the transfer direction of the 22 | command. An attacker can craft a setup packet with a wLength 23 | greater than 4096 bytes and corrupt memory beyond a heap-allocated 24 | request buffer. For a device-to-host transfer, an attacker may be 25 | able to read data beyond the heap-allocated buffer. 26 | 27 | For more information refer to the advisory located at: 28 | 29 | https://research.nccgroup.com/2023/01/20/technical-advisory-u-boot-unchecked-download-size-and-direction-in-usb-dfu-cve-2022-2347/ 30 | """), 31 | 32 | # There was an unsuccessful fix that broke DFU. In light of that, 33 | # we'll just recommend a version upgrade rather than attempt to 34 | # reference which patches to backport. Here, I'm actually 35 | # considering the issue to be "fixed" as of commit 36 | # 14dc0ab138988a8e45ffa086444ec8db48b3f103. 37 | 'recommendation': dedent("""\ 38 | Upgrade to U-Boot 2023.01 or later. 39 | """), 40 | 41 | 'affected_versions': ('2012.10-rc1', '2023.01-rc4'), 42 | }), 43 | 44 | ('CONFIG_SPL_DFU', True, { 45 | 'identifier': 'CVE-2022-2347', 46 | 'impact': SecurityImpact.WR_MEM | SecurityImpact.RD_MEM, 47 | 'summary': 'Unchecked download size and direction in USB DFU', 48 | 49 | 'description': dedent("""\ 50 | The USB DFU download implementation does not bound the length field 51 | in setup packets, nor does it verify the transfer direction of the 52 | command. An attacker can craft a setup packet with a wLength 53 | greater than 4096 bytes and corrupt memory beyond a heap-allocated 54 | request buffer. For a device-to-host transfer, an attacker may be 55 | able to read data beyond the heap-allocated buffer. 56 | 57 | For more information refer to the advisory located at: 58 | 59 | https://research.nccgroup.com/2023/01/20/technical-advisory-u-boot-unchecked-download-size-and-direction-in-usb-dfu-cve-2022-2347/ 60 | """), 61 | 62 | # There was an unsuccessful fix that broke DFU. In light of that, 63 | # we'll just recommend a version upgrade rather than attempt to 64 | # reference which patches to backport. Here, I'm actually 65 | # considering the issue to be "fixed" as of commit 66 | # 14dc0ab138988a8e45ffa086444ec8db48b3f103. 67 | 'recommendation': dedent("""\ 68 | Upgrade to U-Boot 2023.01 or later. 69 | """), 70 | 71 | 'affected_versions': ('2012.10-rc1', '2023.01-rc4'), 72 | }), 73 | ) 74 | -------------------------------------------------------------------------------- /python/depthcharge/checker/uboot_config.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | 4 | import re 5 | from copy import deepcopy 6 | 7 | from . import ConfigChecker 8 | 9 | 10 | class UBootConfigChecker(ConfigChecker): 11 | """ 12 | Inspect U-Boot build configurations present in .config files produced by more modern, 13 | Kconfig-driven U-Boot builds. (i.e. those produced from ``make _defconfig``) 14 | """ 15 | 16 | _CONFIG_VAL = re.compile(r'^(?PCONFIG_[A-Za-z0-9_]+)=(?P.*)$') 17 | _CONFIG_UNSET = re.compile(r'^#\s?(?PCONFIG_[A-Za-z0-9_]+) is not set$') 18 | 19 | def load(self, filename: str) -> dict: 20 | """ 21 | Load and parse the specified U-Boot build configuration file and return a dictionary 22 | in the format defined by :py:meth:`ConfigChecker.load()`. 23 | 24 | The provided file should be a `.config` file produced by running ``make _defconfig`` 25 | within the U-Boot codebase - not just the platform's *defconfig* file, doesn't include 26 | all the configuration items inherited by default settings. 27 | 28 | Calling :py:meth:`load()` multiple times will aggregate the configurations present across 29 | all loaded files. When re-defined configuration items are encountered their values are 30 | ignored and a warning is printed. 31 | """ 32 | 33 | with open(filename, 'r') as infile: 34 | data = infile.read() 35 | 36 | lineno = 0 37 | cfg = self._config 38 | 39 | lines = data.splitlines() 40 | for line in lines: 41 | lineno += 1 42 | 43 | m = self._CONFIG_VAL.match(line) 44 | if m: 45 | source = filename + ':' + str(lineno) 46 | key = m.group('key') 47 | value = m.group('value') 48 | if value == 'y': 49 | value = True 50 | 51 | else: 52 | m = self._CONFIG_UNSET.match(line) 53 | if m: 54 | source = filename + ':' + str(lineno) 55 | key = m.group('key') 56 | value = False 57 | else: 58 | continue 59 | 60 | self._add_config_entry(key, value, source) 61 | 62 | return deepcopy(cfg) 63 | -------------------------------------------------------------------------------- /python/depthcharge/executor/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa=F401 2 | """ 3 | The *depthcharge.executor* module provides :py:class:`~depthcharge.Operation` 4 | implementations responsible for executing code. Currently, this is limited to the use of the ``go`` 5 | console command by way of the :py:class:`~deptcharge.executor.GoExecutor` implementation. 6 | 7 | However, this module is intended of accommodate future additions, such as: 8 | 9 | * Support for automatically wrapping payloads with image headers and executing them with ``boot*`` 10 | family of console commands. 11 | 12 | * Memory corruption exploitation and shellcode helper functions. 13 | 14 | * Given that custom vendor/oem commands will vary wildly, these would be more valuable as generic 15 | building blocks, rather than a collection of device-specific payloads. 16 | 17 | * Executor implementations pertaining to upstream vulnerabilites 18 | (e.g. `NFS `_ 19 | `RCEs `_) 20 | may however, be more practical to readily integrate. 21 | 22 | 23 | * Similar to the above, integrating support for 24 | `upstream `_ and 25 | `silicon-specific `_ 26 | secure boot bypasses may also be reasonable additions. 27 | 28 | """ 29 | 30 | from .executor import Executor 31 | from .go import GoExecutor 32 | -------------------------------------------------------------------------------- /python/depthcharge/executor/executor.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | """ 4 | Provides Executor base class definition. 5 | """ 6 | 7 | from ..operation import Operation 8 | 9 | 10 | class Executor(Operation): 11 | """ 12 | Abstract base class for :py:class:`~depthcharge.Operation` implementations that facilitate 13 | arbitrary code execution on the target device. 14 | """ 15 | 16 | def execute_at(self, address: int, *args, **kwargs): 17 | """ 18 | Instruct the target to execute instructions at the specified `address`. 19 | 20 | Any additional positional and keyword arguments are passed to the 21 | underlying :py:class:`~depthcharge.executor.Executor` implementation. 22 | 23 | **Note**: This method does not perform any pre-requisite validation before 24 | attempting to begin execution. Use the 25 | :py:func:`Depthcharge.execute_payload() ` 26 | method when executing built-in payloads. 27 | """ 28 | raise NotImplementedError 29 | -------------------------------------------------------------------------------- /python/depthcharge/executor/go.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | """ 4 | Execution of U-Boot "stand alone" programs via the "go" console command. 5 | """ 6 | 7 | import re 8 | 9 | from .executor import Executor 10 | from ..operation import Operation, OperationFailed 11 | 12 | _GO_RC_RE = re.compile(r'##[\w\s,]+rc = 0x(?P[0-9a-fA-F]+)') 13 | 14 | 15 | class GoExecutor(Executor): 16 | """ 17 | This class implements the :py:class:`Executor` interface atop of U-Boot's 18 | builtin functionality for executing 19 | `U-Boot standalone programs `_ 20 | via the ``go`` console command. 21 | """ 22 | _required = { 23 | 'commands': ['go'] 24 | } 25 | 26 | @classmethod 27 | def rank(cls, **_kwargs): 28 | return 90 29 | 30 | def execute_at(self, address: int, *args, **kwargs): 31 | cmd = 'go 0x{:08x} '.format(address) + ' '.join(args) 32 | 33 | read_response = kwargs.get('read_response', True) 34 | 35 | if not read_response: 36 | self._ctx.send_command(cmd, read_response=False) 37 | return None 38 | 39 | resp = self._ctx.send_command(cmd) 40 | for line in reversed(resp.splitlines()): 41 | m = _GO_RC_RE.match(line) 42 | if m is not None: 43 | rc = int(m.group('rc'), 16) 44 | return (rc, resp) 45 | 46 | raise OperationFailed('Did not find standalone application return code.') 47 | 48 | 49 | Operation.register(GoExecutor) 50 | -------------------------------------------------------------------------------- /python/depthcharge/hunter/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # 3 | # flake8: noqa=F401 4 | 5 | """ 6 | This module provides the :py:class:`~depthcharge.hunter.Hunter` base class 7 | and its associated implementations. 8 | 9 | In Depthcharge parlance, a *"Hunter"* is class that searches data for items of interest 10 | and either provides information about located instances or produces an artifact using 11 | the located data (e.g., a :py:class:`~depthcharge.Stratagem`). 12 | 13 | """ 14 | 15 | from .cmdtbl import CommandTableHunter 16 | from .cp import CpHunter 17 | from .constant import ConstantHunter 18 | from .env import EnvironmentHunter 19 | from .fdt import FDTHunter 20 | from .hunter import Hunter, HunterResultNotFound 21 | from .revcrc32 import ReverseCRC32Hunter 22 | from .string import StringHunter 23 | -------------------------------------------------------------------------------- /python/depthcharge/hunter/constant.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | """ 4 | Implements ConstantHunter 5 | """ 6 | from .hunter import Hunter 7 | 8 | 9 | class ConstantHunter(Hunter): 10 | """ 11 | This is a simple :py:class:`~.hunter.Hunter` that searches for fixed data values 12 | (of type ``bytes``). 13 | 14 | Example use-cases include searching for file format "magic" values (e.g. Device Tree's 15 | ``d00dfeed``), tables (e.g., CRC32, SHA1, SHA256 LUTs), or opcodes near code or data of 16 | interest. 17 | 18 | Its constructor and methods are implemented according to the descriptions in 19 | :py:class:`.Hunter`. 20 | """ 21 | 22 | def _search_at(self, target, start, end, **_kwargs): 23 | tlen = len(target) 24 | if 0 < end < (start + tlen): 25 | return None 26 | 27 | if target == self._data[start:start + len(target)]: 28 | return (start, len(target)) 29 | 30 | return None 31 | -------------------------------------------------------------------------------- /python/depthcharge/memory/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | # 4 | # flake8: noqa=F401 5 | """ 6 | This module provides memory access functionality through :py:class:`.MemoryReader` 7 | and :py:class:`.MemoryWriter` abstractions. These abstractions allow one to write 8 | re-usable scripts and tools to interact with exposed U-Boot consoles, even when 9 | the available commands vary platform to platform. 10 | 11 | The underlying implementations are built atop of both U-Boot console commands 12 | specifically intended for arbitrary memory access, as well as those often 13 | overlooked (when deployed in production systems) as memory-access primitives. 14 | 15 | During the initialization of a :py:class:`~depthcharge.Depthcharge` context, 16 | the target platform is inspected to determine which memory operations are 17 | available. In general, an API user should not need to manually instantiate 18 | any of the classes within this *depthcharge.memory* subpackage. Instead, one only needs 19 | to interact with higher level methods such as 20 | :py:meth:`Depthcharge.read_memory() ` 21 | and 22 | :py:meth:`Depthcharge.write_memory() `. 23 | Familiarity with the underlying implementations, however, allows one to 24 | choose an specific implementation (via the *impl=* keyword argument) 25 | or introduce new implementations atop of vendor-specific commands. 26 | """ 27 | 28 | from .cp import CpCrashMemoryReader, CpMemoryWriter 29 | from .crc32 import CRC32MemoryReader, CRC32MemoryWriter 30 | from .go import GoMemoryReader 31 | from .i2c import I2CMemoryReader, I2CMemoryWriter 32 | from .itest import ItestMemoryReader 33 | from .load import LoadbMemoryWriter, LoadxMemoryWriter, LoadyMemoryWriter 34 | from .memcmds import MdMemoryReader 35 | from .memcmds import MmMemoryReader, MmMemoryWriter 36 | from .memcmds import MwMemoryWriter 37 | from .memcmds import NmMemoryReader, NmMemoryWriter 38 | from .setexpr import SetexprMemoryReader 39 | 40 | from .reader import MemoryReader, MemoryWordReader 41 | from .data_abort import DataAbortMemoryReader 42 | from .stratagem import StratagemMemoryWriter 43 | from .writer import MemoryWriter, MemoryWordWriter 44 | 45 | from .patch import MemoryPatch, MemoryPatchList 46 | -------------------------------------------------------------------------------- /python/depthcharge/memory/cp.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | """ 4 | Implements CpCrashMemoryReader and CpMemoryWriter 5 | """ 6 | 7 | from ..operation import Operation 8 | from .data_abort import DataAbortMemoryReader 9 | from .stratagem import StratagemMemoryWriter 10 | from ..hunter.cp import CpHunter 11 | 12 | 13 | class CpCrashMemoryReader(DataAbortMemoryReader): 14 | """ 15 | Available only for ARM targets. 16 | 17 | The :py:class:`~.CpCrashMemoryReader` crashes the platform by attempting 18 | to copy a word from a target read location to a non-writable location, 19 | resulting in a Data Abort. The read data is extracted from a register 20 | dump printed by U-Boot when this occurs. 21 | 22 | This is very slow, as it involves 1 reset per-word. 23 | 24 | Refer to the :py:class:`.DataAbortMemoryReader` parent class for 25 | information about supported keyword arguments. 26 | """ 27 | 28 | _required = { 29 | 'arch': ['ARM', 'AARCH64'], 30 | 'commands': ['cp'], 31 | 'crash_or_reboot': True, 32 | } 33 | 34 | @classmethod 35 | def rank(cls, **_kwargs) -> int: 36 | return 3 37 | 38 | def _trigger_data_abort(self, address: int, **_kwargs): 39 | mode = 'q' if self._ctx.arch.supports_64bit_data else 'l' 40 | cmd = 'cp.{:s} {:x} {:x} 1'.format(mode, address, self._crash_addr) 41 | return self._ctx.send_command(cmd) 42 | 43 | 44 | class CpMemoryWriter(StratagemMemoryWriter): 45 | """ 46 | This :py:class:`~.StratagemMemoryWriter` uses the ``cp`` console command 47 | to write a desired payload to memory using a :py:class:`~depthcharge.Stratagem` built 48 | by :py:class:`~depthcharge.hunter.CpHunter`. 49 | """ 50 | 51 | _required = { 52 | 'commands': ['cp'], 53 | } 54 | 55 | _stratagem_spec = Operation._create_stratagem_spec(dst_off=int) 56 | _stratagem_hunter = CpHunter 57 | 58 | @classmethod 59 | def rank(cls, **kwargs): 60 | return 9 61 | 62 | def _write_stratagem(self, wr_addr: int, stratagem, progress): 63 | def is_aligned(n, src, dst, size): 64 | return (size % n == 0) and (src % n == 0) and (dst % n == 0) 65 | 66 | for entry in stratagem: 67 | src_addr = entry['src_addr'] 68 | size = entry['src_size'] 69 | dst_addr = entry['dst_off'] + wr_addr 70 | 71 | if self._ctx.arch.supports_64bit_data and is_aligned(8, src_addr, dst_addr, size): 72 | mode = 'q' 73 | size //= 8 74 | elif is_aligned(4, src_addr, dst_addr, size): 75 | mode = 'l' 76 | size //= 4 77 | elif is_aligned(2, src_addr, dst_addr, size): 78 | mode = 'w' 79 | size //= 2 80 | else: 81 | mode = 'b' 82 | 83 | cmd = 'cp.{:s} {:x} {:x} {:x}'.format(mode, src_addr, dst_addr, size) 84 | self._ctx.send_command(cmd, check=True) 85 | progress.update(size) 86 | 87 | 88 | Operation.register(CpCrashMemoryReader, CpMemoryWriter) 89 | -------------------------------------------------------------------------------- /python/depthcharge/memory/itest.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | """ 4 | Implements ItestMemoryReader 5 | """ 6 | 7 | from .reader import MemoryWordReader 8 | from ..operation import Operation 9 | 10 | 11 | class ItestMemoryReader(MemoryWordReader): 12 | """ 13 | This :py:class:`~depthcharge.memory.MemorReader` implementation that uses 14 | the `itest` U-Boot command as an byte-wise memory read operation. 15 | 16 | By design, this command allows two values to be compared using the 17 | operators `-eq`, `-ne`, `-lt`, `-gt`, `-le`, `-ge`, `==`, `!=`, 18 | `<>`, `<`, `>`, `<=`, and `>=`. It also allows addresses to be dereferenced in these comparisons 19 | using a C-like `*
` syntax. 20 | 21 | Although the `itest` command cannot read a value directory, a binary search using the above 22 | operators can be used to determine the value at a specified memory location, with a byte-level 23 | granularity. 24 | """ 25 | _required = { 26 | 'commands': ['itest', 'echo'] 27 | } 28 | 29 | @classmethod 30 | def rank(cls, **kwargs): 31 | # Slow, performs binary search in [0, 255] per byte 32 | return 25 33 | 34 | def __init__(self, ctx, **kwargs): 35 | super().__init__(ctx, **kwargs) 36 | 37 | # We always want to use the single-byte mode of itest, 38 | # regardless of the largest word size available 39 | self._word_size = 1 40 | 41 | def _check_value(self, addr, value, operator='<'): 42 | cmd = 'if itest.b *{:x} {:s} {:x};then echo 1;fi' 43 | resp = self._ctx.send_command(cmd.format(addr, operator, value)) 44 | return resp != '' 45 | 46 | def _read_word(self, addr: int, size: int, handle_data): 47 | assert size == 1 48 | 49 | if self._check_value(addr, 0x00, '=='): 50 | handle_data(b'\x00') 51 | return 52 | 53 | if self._check_value(addr, 0xff, '=='): 54 | handle_data(b'\xff') 55 | return 56 | 57 | min_val = 0x00 58 | max_val = 0xff 59 | 60 | while max_val != min_val: 61 | val = (min_val + max_val + 1) // 2 62 | if self._check_value(addr, val, '<'): 63 | max_val = val - 1 64 | else: 65 | min_val = val 66 | 67 | handle_data(max_val.to_bytes(1, 'big')) 68 | 69 | 70 | # Register declared Operations 71 | Operation.register(ItestMemoryReader) 72 | -------------------------------------------------------------------------------- /python/depthcharge/memory/setexpr.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | """ 4 | Implements SetexprMemoryReader 5 | """ 6 | 7 | import re 8 | 9 | from math import ceil 10 | 11 | from .memcmds import MdMemoryReader 12 | from .reader import MemoryWordReader 13 | from ..operation import Operation 14 | 15 | 16 | class SetexprMemoryReader(MemoryWordReader): 17 | """ 18 | The U-Boot ``setexpr`` console command can be used to assign an environment 19 | variable based upon the result of an expression. The supported expression 20 | syntax includes a memory dereference operation, which this class leverages 21 | to provide a :py:class:`~depthcharge.memory.MemoryWordReader` implementation. 22 | """ 23 | 24 | _required = { 25 | 'commands': ['setexpr', 'printenv'] # TODO: Is setexpr without setenv supported? 26 | } 27 | 28 | _print_re = re.compile(r'(?P[\.a-zA-Z0-9_]+)=(?P[0-9a-fA-F]+)') 29 | 30 | @classmethod 31 | def rank(cls, **kwargs): 32 | # Inferior to md. Slow 1-word per access with extra logic 33 | return MdMemoryReader.rank(**kwargs) // 3 34 | 35 | def _read_word(self, addr: int, size: int, handle_data): 36 | var = '.dcse' # "Depthcharge setexpr" - Uses hidden variable dot prefix 37 | mode = self._mode[size] 38 | self._ctx.send_command('setexpr.{:s} {:s} *{:x}'.format(mode, var, addr)) 39 | resp = self._ctx.send_command('print {:s}'.format(var)) 40 | match = self._print_re.match(resp) 41 | if not match: 42 | self.log.error('Did not receive expected print output. Got: ' + resp) 43 | self._ctx.interrupt() 44 | raise IOError('Failed to read {:d} byte(s) @ 0x{:08x}'.format(size, addr)) 45 | 46 | data = match.group('data') 47 | 48 | # Apparently setexpr.l will happily return 8 bytes when we only asked for 4. 49 | # It obliges with .b, w., and .s though. Odd. Hack around this. 50 | out_size = size 51 | if len(data) / 2 > size: 52 | size = ceil(len(data) / 2) 53 | 54 | data_bytes = self._ctx.arch.hexint_to_bytes(data, size) 55 | handle_data(data_bytes[:out_size]) 56 | 57 | 58 | Operation.register(SetexprMemoryReader) 59 | -------------------------------------------------------------------------------- /python/depthcharge/memory/stratagem.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | """ 4 | Provides the StratagemMemoryWriter class 5 | """ 6 | 7 | from .writer import MemoryWriter 8 | from ..operation import OperationNotSupported 9 | from ..stratagem import Stratagem, StratagemRequired 10 | 11 | 12 | class StratagemMemoryWriter(MemoryWriter): 13 | """ 14 | StratagemMemoryWriter is a base class for 15 | :py:class:`~depthcharge.memory.MemoryWriter` implementations that cannot 16 | write memory directly, but rather through a side-effect or roundabout 17 | approach described by a :py:class:`depthcharge.Stratagem`. 18 | """ 19 | 20 | # Size of data written by each stratagem entry, in bytes 21 | _output_size = 4 22 | 23 | def _describe_op(self, addr, data): 24 | """ 25 | Return a string (suitable for logging) that describes the write 26 | operation that would be performed with the provided arguments. 27 | """ 28 | stratagem = data 29 | desc = '({:s}) Writing {:d} bytes @ 0x{:08x}' 30 | total_len = len(stratagem) * self._output_size 31 | return desc.format(self.name, total_len, addr) 32 | 33 | def _write(self, _addr, _data, **kwargs): 34 | raise OperationNotSupported(self, '_write() not used by StratagemMemoryWriter') 35 | 36 | def _write_stratagem(self, wr_addr: int, stratagem, progress): 37 | raise NotImplementedError 38 | 39 | def write(self, addr: int, data: bytes = None, **kwargs): 40 | """ 41 | Execute the :py:class:`~depthcharge.Stratagem` specified in a *stratagem* keyword 42 | argument in order to write a desired payload to a target memory location (*addr*). 43 | 44 | Because a :py:class:`~.StratagemMemoryWriter` cannot write data directly, the *data* argument 45 | is unused and should be left as ``None``. 46 | 47 | For this type of :py:class:`~depthcharge.memory.MemoryWriter`, the 48 | :py:meth:`write_from_file()` method is often more intuitive to use. 49 | 50 | **Example:** 51 | 52 | .. code-block:: python 53 | 54 | my_stratagem_writer.write(0x8400_0000, data=None, strategm=my_stratagem) 55 | 56 | """ 57 | try: 58 | stratagem = kwargs['stratagem'] 59 | except KeyError: 60 | raise StratagemRequired(self.name) 61 | 62 | if data is not None and len(data) != 0: 63 | msg = self.name + ' uses Stratagem. Ignoring {:d} bytes of provided data' 64 | self.log.warning(msg.format(len(data))) 65 | 66 | stratagem_op_name = stratagem.operation_name 67 | if stratagem_op_name != self.name: 68 | error = 'Stratagem is for {:s}, but {:s} is being used' 69 | raise ValueError(error.format(stratagem_op_name, self.name)) 70 | 71 | desc = self._describe_op(addr, stratagem) 72 | show = kwargs.get('show_progress', True) 73 | progress = self._ctx.create_progress_indicator(self, stratagem.total_operations, desc, show=show) 74 | 75 | try: 76 | self._write_stratagem(addr, stratagem, progress) 77 | finally: 78 | self._ctx.close_progress_indicator(progress) 79 | 80 | def write_from_file(self, addr: int, filename: str, **kwargs): 81 | """ 82 | Load a :py:class:`~depthcharge.Stratagem` file and use it to write to the 83 | target memory locations specified by *addr*. 84 | 85 | **Example:** 86 | 87 | .. code-block:: python 88 | 89 | my_stratagem_writer.write_from_file(0x8400_0000, 'my_stratagem.json') 90 | 91 | """ 92 | stratagem = Stratagem.from_json_file(filename) 93 | self.write(addr, None, stratagem=stratagem) 94 | -------------------------------------------------------------------------------- /python/depthcharge/memory/todo.txt: -------------------------------------------------------------------------------- 1 | # class CmpReader 2 | # class CpWriter 3 | # class HashReader 4 | # class LoadsWriter 5 | 6 | -------------------------------------------------------------------------------- /python/depthcharge/register/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | # 4 | # flake8: noqa=F401 5 | """ 6 | The *depthcharge.register* subpackage provides register 7 | access functionality. 8 | 9 | Bear in mind that not all registers will be accessible, given that some subset 10 | will be tainted by virtue of executing code in an attempt to read them. This is 11 | intended only for retrieving special register values, such as that reserved for 12 | U-Boot's global data structure pointer. 13 | """ 14 | 15 | from .cp import CpCrashRegisterReader 16 | from .crc32 import CRC32CrashRegisterReader 17 | from .fdt import FDTCrashRegisterReader 18 | from .itest import ItestCrashRegisterReader 19 | 20 | from .memcmds import ( 21 | MdCrashRegisterReader, 22 | MmCrashRegisterReader, 23 | MwCrashRegisterReader, 24 | NmCrashRegisterReader, 25 | ) 26 | 27 | from .setexpr import SetexprCrashRegisterReader 28 | 29 | from .reader import RegisterReader 30 | from .data_abort import DataAbortRegisterReader 31 | -------------------------------------------------------------------------------- /python/depthcharge/register/cp.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | 4 | """ 5 | Implements CpCrashRegisterReader 6 | """ 7 | 8 | from ..operation import Operation 9 | from .data_abort import DataAbortRegisterReader 10 | 11 | 12 | class CpCrashRegisterReader(DataAbortRegisterReader): 13 | """ 14 | This is a :py:class:`~.DataAbortRegisterReader` that uses the ``cp`` 15 | console command to trigger the Data Abort used to read registers. 16 | """ 17 | 18 | _required = { 19 | 'arch': ['ARM', 'AARCH64'], 20 | 'commands': ['cp'], 21 | 'crash_or_reboot': True, 22 | } 23 | 24 | @classmethod 25 | def rank(cls, **_kwargs): 26 | return 10 27 | 28 | # This is by intent; we are not using another memory reader. 29 | # pylint: disable=method-hidden 30 | def _trigger_data_abort(self): 31 | if self._ctx.arch.name == 'AARCH64': 32 | # Using the same value for source/dest does not appear to work, 33 | # perhaps due to the change to use memcpy() in U-Boot 2017.01 34 | # (c2538421b28424b9705865e838c5fba19c9dc651). 35 | # 36 | # Adding this as a special case for AARCH64, as this was confirmed 37 | # to work with the default crash_addr. (Granted, it too is probably 38 | # relying on a SoC-specific memory map.) 39 | cmd = 'cp.q 0 {:x} 1'.format(self._crash_addr) 40 | else: 41 | cmd = 'cp.l {addr:x} {addr:x} 1'.format(addr=self._crash_addr) 42 | 43 | return self._ctx.send_command(cmd) 44 | 45 | 46 | Operation.register(CpCrashRegisterReader) 47 | -------------------------------------------------------------------------------- /python/depthcharge/register/crc32.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | 4 | """ 5 | Implements CRC32CrashRegisterReader 6 | """ 7 | 8 | from ..operation import Operation 9 | from .data_abort import DataAbortRegisterReader 10 | 11 | 12 | class CRC32CrashRegisterReader(DataAbortRegisterReader): 13 | """ 14 | This is a :py:class:`~.DataAbortRegisterReader` that uses the ``crc32`` 15 | console command to trigger the Data Abort used to read registers. 16 | """ 17 | 18 | _required = { 19 | 'arch': ['ARM', 'AARCH64'], 20 | 'commands': ['crc32'], 21 | 'crash_or_reboot': True 22 | } 23 | 24 | # This is by intent; we are not using another memory reader. 25 | # pylint: disable=method-hidden 26 | def _trigger_data_abort(self): 27 | cmd = 'crc32 {addr:x} 0 {addr:x}'.format(addr=self._crash_addr) 28 | return self._ctx.send_command(cmd) 29 | 30 | 31 | Operation.register(CRC32CrashRegisterReader) 32 | -------------------------------------------------------------------------------- /python/depthcharge/register/data_abort.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | 4 | """ 5 | Defines DataAbortRegisterReader parent class 6 | """ 7 | 8 | import os 9 | 10 | from .reader import RegisterReader 11 | from ..operation import OperationNotSupported 12 | 13 | class DataAbortRegisterReader(RegisterReader): 14 | """ 15 | This is a type of :py:class:`.RegisterReader` that triggers a Data Abort 16 | on ARM targets and parses the crash output to retrieve a register value. 17 | 18 | For these to work, the system must automatically reset upon crash, and 19 | allow re-entry into the console. Subclasses must set the 20 | ``crash_or_reboot=True`` property in their private ``_required`` dictionary 21 | in order to exclude the operation when a user has indicated that this 22 | should not be permitted. 23 | 24 | A *da_crash_addr* keyword argument can passed to constructors in order to 25 | specify the memory address to access in order to induce a data abort. 26 | It defaults to an architecture-specific value. This value can be overridden 27 | by a *DEPTHCHARGE_DA_ADDR* environment variable. 28 | """ 29 | 30 | _memrd = None 31 | 32 | @classmethod 33 | def rank(cls, **_kwargs): 34 | # Rebooting the platform isn't ideal, but it's better than 35 | # requiring a write operation (ranked 10 or lower) 36 | return 20 37 | 38 | def __init__(self, ctx, **kwargs): 39 | super().__init__(ctx, **kwargs) 40 | 41 | da_crash_addr_env = os.getenv('DEPTHCHARGE_DA_ADDR') 42 | if da_crash_addr_env is not None: 43 | self._crash_addr = int(da_crash_addr_env, 0) 44 | else: 45 | self._crash_addr = kwargs.get('da_crash_addr', ctx.arch.data_abort_address) 46 | 47 | if self._crash_addr is None: 48 | err = 'No data abort address is defined for ' + ctx.arch.description 49 | raise OperationNotSupported(self.__class__, err) 50 | 51 | # This is expected. pylint: disable=no-self-use 52 | def _trigger_data_abort(self) -> str: 53 | """ 54 | Subclasses must implement this method. 55 | 56 | It should trigger a data abort and return the crash dump text 57 | printed to the terminal. This class will take care of parsing it 58 | accordingly. 59 | """ 60 | 61 | msg = 'Subclass must provide a `da_memrd` name or implement _trigger_data_abort()' 62 | raise NotImplementedError(msg) 63 | 64 | def _read(self, register: str, info) -> int: 65 | # Calm pylint: disable=assignment-from-no-return 66 | da_text = self._trigger_data_abort() 67 | 68 | # Run user-provided post-reboot callback, if configured 69 | if self._ctx._post_reboot_cb is not None: 70 | # Callback is responsible for interrupt() call, if they want it. 71 | # This is intended to allow them to perform any necessary actions 72 | # before we allow interrupt() to time out. (e.g. attempt to enter 73 | # an autoboot "STOP_STR". 74 | self._ctx._post_reboot_cb(self._ctx._post_reboot_cb_data) 75 | else: 76 | # Catch console on reset 77 | self._ctx.interrupt() 78 | 79 | da_dict = self._ctx.arch.parse_data_abort(da_text) 80 | return da_dict['registers'][register] 81 | -------------------------------------------------------------------------------- /python/depthcharge/register/fdt.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | 4 | """ 5 | Implements FDTCrashRegisterReader 6 | """ 7 | 8 | from ..operation import Operation 9 | from .data_abort import DataAbortRegisterReader 10 | 11 | 12 | class FDTCrashRegisterReader(DataAbortRegisterReader): 13 | """ 14 | This is a :py:class:`~.DataAbortRegisterReader` that uses the ``fdt`` 15 | console command to trigger the Data Abort used to read registers. 16 | """ 17 | 18 | _required = { 19 | 'arch': ['ARM', 'AARCH64'], 20 | 'commands': ['fdt'], 21 | 'crash_or_reboot': True, 22 | } 23 | 24 | @classmethod 25 | def rank(cls, **_kwargs): 26 | # Seems to taint quite a bit 27 | return 15 28 | 29 | # This is by intent; we are not using another memory reader. 30 | # pylint: disable=method-hidden 31 | def _trigger_data_abort(self): 32 | cmd = 'fdt addr {:x}'.format(self._crash_addr) 33 | return self._ctx.send_command(cmd) 34 | 35 | 36 | Operation.register(FDTCrashRegisterReader) 37 | -------------------------------------------------------------------------------- /python/depthcharge/register/itest.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | 4 | """ 5 | Implements ItestCrashRegisterReader 6 | """ 7 | 8 | from ..operation import Operation 9 | from .data_abort import DataAbortRegisterReader 10 | 11 | 12 | class ItestCrashRegisterReader(DataAbortRegisterReader): 13 | """ 14 | This is a :py:class:`~.DataAbortRegisterReader` that uses the ``itest`` 15 | console command to trigger the Data Abort used to read registers. 16 | """ 17 | 18 | _required = { 19 | 'arch': ['ARM', 'AARCH64'], 20 | 'commands': ['itest'], 21 | 'crash_or_reboot': True, 22 | } 23 | 24 | # This is by intent; we are not using another memory reader. 25 | # pylint: disable=method-hidden 26 | def _trigger_data_abort(self): 27 | cmd = 'itest.l *{:x} == 0'.format(self._crash_addr) 28 | return self._ctx.send_command(cmd) 29 | 30 | 31 | Operation.register(ItestCrashRegisterReader) 32 | -------------------------------------------------------------------------------- /python/depthcharge/register/memcmds.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | 4 | """ 5 | This module provides data abort-baed register reads atop of memory read and write 6 | U-Boot commands specifically intended for memory display or modification. 7 | 8 | See: 9 | """ 10 | 11 | from ..operation import Operation 12 | from .data_abort import DataAbortRegisterReader 13 | 14 | 15 | class MdCrashRegisterReader(DataAbortRegisterReader): 16 | """ 17 | A :py:class:`.DataAbortRegisterReader` that uses the ``md.l`` console command 18 | to trigger a Data Abort. 19 | """ 20 | 21 | _required = { 22 | 'arch': ['ARM', 'AARCH64'], 23 | 'commands': ['md'], 24 | 'crash_or_reboot': True, 25 | } 26 | 27 | @classmethod 28 | def rank(cls, **_kwargs): 29 | # Preferred read-only Data Abort 30 | return 21 31 | 32 | def _trigger_data_abort(self): 33 | cmd = 'md.l {:x} 1'.format(self._crash_addr) 34 | return self._ctx.send_command(cmd) 35 | 36 | 37 | class MmCrashRegisterReader(DataAbortRegisterReader): 38 | """ 39 | A :py:class:`.DataAbortRegisterReader` that uses the ``mm.l`` console command 40 | to trigger a Data Abort. 41 | """ 42 | 43 | _required = { 44 | 'arch': ['ARM', 'AARCH64'], 45 | 'commands': ['mm'], 46 | 'crash_or_reboot': True, 47 | } 48 | 49 | def _trigger_data_abort(self): 50 | cmd = 'mm.l {:x}'.format(self._crash_addr) 51 | return self._ctx.send_command(cmd) 52 | 53 | 54 | class MwCrashRegisterReader(DataAbortRegisterReader): 55 | """ 56 | A :py:class:`.DataAbortRegisterReader` that uses the ``mm.l`` console command 57 | to trigger a Data Abort. 58 | """ 59 | 60 | _required = { 61 | 'arch': ['ARM', 'AARCH64'], 62 | 'commands': ['mm'], 63 | 'crash_or_reboot': True, 64 | } 65 | 66 | def _trigger_data_abort(self): 67 | cmd = 'mw.l {:x} 0'.format(self._crash_addr) 68 | return self._ctx.send_command(cmd) 69 | 70 | 71 | class NmCrashRegisterReader(DataAbortRegisterReader): 72 | """ 73 | A :py:class:`.DataAbortRegisterReader` that uses the ``nm.l`` console command 74 | to trigger a Data Abort. 75 | """ 76 | 77 | _required = { 78 | 'arch': ['ARM', 'AARCH64'], 79 | 'commands': ['nm'], 80 | 'crash_or_reboot': True, 81 | } 82 | 83 | def _trigger_data_abort(self): 84 | cmd = 'nm.l {:x}'.format(self._crash_addr) 85 | return self._ctx.send_command(cmd) 86 | 87 | 88 | Operation.register( 89 | MdCrashRegisterReader, 90 | MmCrashRegisterReader, 91 | MmCrashRegisterReader, 92 | NmCrashRegisterReader, 93 | ) 94 | -------------------------------------------------------------------------------- /python/depthcharge/register/reader.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | """ 4 | Implements RegisterReader base class 5 | """ 6 | 7 | from ..operation import Operation 8 | 9 | 10 | class RegisterReader(Operation): 11 | """ 12 | Base class for :py:class:`Operation` implementations used to read device registers. 13 | """ 14 | 15 | def read(self, register: str) -> int: 16 | """ 17 | Read a value from the target device register, specified by name. 18 | Note that registers can be obtained using :py:class:`depthcharge.Architecture`. 19 | """ 20 | (reg, info) = self._ctx.arch.register(register) 21 | return self._read(reg, info) 22 | 23 | def _setup(self): 24 | """ 25 | :py:class:`RegisterReader` subclasses shall override this method to perform any 26 | necessary setup or preparation. 27 | """ 28 | 29 | def _teardown(self): 30 | """ 31 | :py:class:`RegisterReader` subclasses shall override this method to perform any 32 | necessary deinitialization actions. 33 | """ 34 | 35 | def _read(self, register: str, info) -> int: 36 | """ 37 | Subclasses of RegisterReader are required to implement the _read() 38 | method, which performs the specific operation. 39 | 40 | The subclass receives both the validated register name. 41 | """ 42 | raise NotImplementedError(self.name + ' does not implement _read()') 43 | -------------------------------------------------------------------------------- /python/depthcharge/register/setexpr.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | 4 | """ 5 | Implements SetExprCrashRegisterReader 6 | """ 7 | 8 | from ..operation import Operation 9 | from .data_abort import DataAbortRegisterReader 10 | 11 | 12 | class SetexprCrashRegisterReader(DataAbortRegisterReader): 13 | """ 14 | This is a :py:class:`~.DataAbortRegisterReader` that uses the ``setexpr`` 15 | console command to trigger the Data Abort used to read registers. 16 | """ 17 | 18 | _required = { 19 | 'arch': ['ARM', 'AARCH64'], 20 | 'commands': ['setexpr'], 21 | 'crash_or_reboot': True, 22 | } 23 | 24 | # This is by intent; we are not using another memory reader. 25 | # pylint: disable=method-hidden 26 | def _trigger_data_abort(self): 27 | cmd = 'setexpr.l _ *{:x}'.format(self._crash_addr) 28 | return self._ctx.send_command(cmd) 29 | 30 | 31 | Operation.register(SetexprCrashRegisterReader) 32 | -------------------------------------------------------------------------------- /python/depthcharge/revcrc32.py: -------------------------------------------------------------------------------- 1 | """ 2 | The algorithm implemented in this file is derived from a public work 3 | that does not appear to be released under any particular licenses. 4 | (We will happily make corrections if the above is incorrect!) 5 | 6 | Therefore, this file is regarded as public domain, to the fullest extent 7 | permitted by applicable law. 8 | 9 | The author(s) of Depthcharge make no claim of copyright of this file. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 12 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 14 | EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 15 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 16 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | """ 18 | # Keeping long URLs as-is. pylint: disable=line-too-long 19 | 20 | 21 | def reverse_crc32_4bytes(crc: int, 22 | poly=0xedb88320, invpoly=0x5b358fd3, 23 | initxor=0xffffffff, finalxor=0xffffffff) -> int: 24 | """ 25 | "Reverses" a CRC32 operation computed over a 4-byte (32-bit) input. 26 | 27 | This implementation is just a simplification of Listing 6 from the 28 | following paper. We're basically trying to compute a chosen CRC32 by 29 | "appending" 4 bytes to a zero-length input. 30 | 31 | Reversing CRC - Theory and Practice 32 | by Martin Stigge, Henryk Plötz, Wolf Müller, Jens-Peter Redlich 33 | HU Berlin Public Report, SAR-PR-2006-05, May 2006 34 | 35 | URL: https://sar.informatik.hu-berlin.de/research/publications/SAR-PR-2006-05/SAR-PR-2006-05_.pdf 36 | https://web.archive.org/web/20191010094138/https://sar.informatik.hu-berlin.de/research/publications/SAR-PR-2006-05/SAR-PR-2006-05_.pdf 37 | 38 | If `endianness` is set to None, an integer is returned. Otherwise, a bytes 39 | object is returned, converted per the specified `endianness` value. 40 | """ 41 | tcrcreg = crc ^ finalxor 42 | data = 0 43 | for _ in range(0, 32): 44 | 45 | # Reduce modulo polynomial 46 | if data & 0x1: 47 | data = (data >> 1) ^ poly 48 | else: 49 | data >>= 1 50 | 51 | # Add inverse polynomial if corresponding bit of operand is set 52 | if tcrcreg & 0x1: 53 | data ^= invpoly 54 | 55 | tcrcreg >>= 1 56 | 57 | result = (data ^ initxor) 58 | return result 59 | -------------------------------------------------------------------------------- /python/depthcharge/uboot/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | # 4 | # flake8: noqa=F401 5 | 6 | """ 7 | U-Boot centric parsing, conversion, and data processing functionality 8 | """ 9 | 10 | from . import board 11 | from . import cmd_table 12 | from . import env 13 | from . import jump_table 14 | from .version import UBootVersion, version_in_range 15 | -------------------------------------------------------------------------------- /python/depthcharge/uboot/board.py: -------------------------------------------------------------------------------- 1 | 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Depthcharge: 4 | 5 | """ 6 | Parsing and conversion of "board" or platform-specific data 7 | """ 8 | 9 | import os 10 | import re 11 | 12 | from .. import log 13 | 14 | 15 | _BDINFO_NUM_REGEX = re.compile(( 16 | r'(?P[\w\d<>\s-]+)' 17 | r'(=)\s*' 18 | r'(?P(0x)?[\w\d:\./@#$%-]+)' 19 | r'\s*' 20 | r'(?P[\w\d-]+)?' 21 | )) 22 | 23 | 24 | def bdinfo_dict(output: str) -> dict: 25 | """ 26 | Convert output of U-Boot's *bdinfo* command to a dictionary. 27 | 28 | Technically, each item may come from a variety of locations, 29 | whether it be *gd*, *gd->bd*, or another structure. 30 | 31 | However, we'll just return everything in a single dict 32 | out of laziness. 33 | """ 34 | ret = {} 35 | 36 | dram_banks = {} 37 | curr_dram_bank = None 38 | 39 | for line in output.splitlines(): 40 | match = _BDINFO_NUM_REGEX.match(line) 41 | if not match: 42 | log.debug('Skipping unmatched bdinfo item: ' + line) 43 | continue 44 | 45 | try: 46 | name = match.group('name').strip() 47 | value = match.group('value').strip() 48 | suffix = match.group('suffix') or '' 49 | suffix = suffix.strip() 50 | 51 | try: 52 | value = int(value, 0) 53 | except ValueError: 54 | # Try to move forward with it as-is 55 | pass 56 | 57 | # Aggregate DRAM bank information in a nested fashion. 58 | # Treat everything else as a single (assumed unique) entry. 59 | if name == 'DRAM bank': 60 | curr_dram_bank = {} 61 | dram_banks[value] = curr_dram_bank 62 | elif curr_dram_bank is not None and name.startswith('-> '): 63 | name = name.replace('-> ', '') 64 | curr_dram_bank[name] = value 65 | else: 66 | # Variable names in gd->bd tend to be mached up in one word, 67 | # Try to follow that convention... 68 | key = name.replace(' ', '').lower() 69 | ret[key] = {'name': name, 'value': value, 'suffix': suffix} 70 | 71 | curr_dram_bank = None 72 | 73 | except (AttributeError, IndexError): 74 | log.error('Failed to parse line: ' + match.group()) 75 | 76 | ret['dram_bank'] = { 'name': 'DRAM bank(s)', 'value': dram_banks, 'suffix': '' } 77 | return ret 78 | 79 | def bdinfo_str(bdinfo: dict) -> str: 80 | """ 81 | Return a user-facing, printable string from a dictionary 82 | returned by `bdinfo_dict()`. 83 | """ 84 | s = '' 85 | for key in sorted(bdinfo.keys()): 86 | entry = bdinfo[key] 87 | name = entry['name'] 88 | value = entry['value'] 89 | suffix = entry['suffix'] 90 | 91 | if key == 'dram_bank': 92 | assert(isinstance(value, dict)) 93 | banks = sorted(value.keys()) 94 | for bankno in banks: 95 | start = value[bankno]['start'] 96 | size = value[bankno]['size'] 97 | 98 | pfx = 'DRAM Bank #{:d}'.format(int(bankno)) 99 | s += ' {:20s} start=0x{:08x}, size=0x{:08x}'.format(pfx, start, size) 100 | s += os.linesep 101 | else: 102 | 103 | if isinstance(value, int): 104 | if name in ('arch_number', 'baudrate'): 105 | line = ' {:20s} {:d}'.format(name, value) 106 | else: 107 | line = ' {:20s} 0x{:08x}'.format(name, value) 108 | else: 109 | line = ' {:20s} {:s}'.format(name, value) 110 | 111 | if suffix: 112 | line += ' ' + suffix 113 | 114 | s += line + os.linesep 115 | 116 | return s.rstrip() 117 | -------------------------------------------------------------------------------- /python/depthcharge/uboot/cmd_table.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | 4 | """ 5 | U-Boot command table ("linker list") parsing and analysis functionality 6 | """ 7 | 8 | 9 | def entry_to_bytes(arch, entry: dict) -> bytes: 10 | """ 11 | Pack a U-Boot command table *entry* (struct cmd_tbl_s), as defined by the 12 | following dictionary keys and return it's representation in bytes. 13 | 14 | +---------------+---------------+----------------------------------------------+ 15 | | Key | Value Type | Description | 16 | +===============+===============+==============================================+ 17 | | name | int | Pointer to command name string | 18 | +---------------+---------------+----------------------------------------------+ 19 | | maxargs | int | Maximum number of arguments the command takes| 20 | +---------------+---------------+----------------------------------------------+ 21 | | cmd_rep | int | Depending upon the U-Boot version, either a | 22 | | | | flag or function pointer used for command | 23 | | | | autorepeat behavior | 24 | +---------------+---------------+----------------------------------------------+ 25 | | cmd | int | Function pointer for ``do_`` | 26 | +---------------+---------------+----------------------------------------------+ 27 | | usage | int | Pointer to short usage text string | 28 | +---------------+---------------+----------------------------------------------+ 29 | | longhelp | int | Pointer to longer command description and | 30 | | | | help text. Only present if U-Boot was built | 31 | | | | with ``CONFIG_SYS_LONGHELP`` | 32 | +---------------+---------------+----------------------------------------------+ 33 | | autocomplete | int | Function pointer to autocomplete handler. | 34 | | | | Only present if U-Boot was built with | 35 | | | | ``CONFIG_AUTOCOMPLETE``. | 36 | +---------------+---------------+----------------------------------------------+ 37 | 38 | The **arch** parameter is required in order to pack the pointer values 39 | according to the target's endianness. 40 | """ 41 | 42 | ret = bytearray() 43 | 44 | ret += arch.int_to_bytes(entry['name']) 45 | ret += arch.int_to_bytes(entry['maxargs']) 46 | ret += arch.int_to_bytes(entry['cmd_rep']) 47 | ret += arch.int_to_bytes(entry['cmd']) 48 | ret += arch.int_to_bytes(entry['usage']) 49 | 50 | if 'longhelp' in entry: 51 | ret += arch.int_to_bytes(entry['longhelp']) 52 | 53 | if 'complete' in entry: 54 | ret += arch.int_to_bytes(entry['complete']) 55 | 56 | return bytes(ret) 57 | -------------------------------------------------------------------------------- /python/depthcharge/version.py: -------------------------------------------------------------------------------- 1 | """ 2 | Depthcharge Python module version 3 | """ 4 | __version__ = '0.6.0' 5 | -------------------------------------------------------------------------------- /python/examples/.gitignore: -------------------------------------------------------------------------------- 1 | raven-stratagem.json 2 | -------------------------------------------------------------------------------- /python/examples/boilerplate_create_ctx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import traceback 3 | from depthcharge import Console, Depthcharge, log 4 | 5 | ctx = None 6 | 7 | try: 8 | console = Console('/dev/ttyUSB0', baudrate=115200) 9 | ctx = Depthcharge(console, arch='arm') 10 | 11 | # Comment out the above ctx creation and uncomment the following one in 12 | # order to possibly make more operations available to Depthcharge by allowing 13 | # it to deploy executable payloads to RAM and reboot/crash the platform. 14 | #ctx = Depthcharge(console, allow_deploy=True, allow_reboot=True) 15 | 16 | # Perform actions here via API calls on ctx handle 17 | 18 | except Exception as error: 19 | log.error(str(error)) 20 | 21 | # Shown if DEPTHCHARGE_LOG_LEVEL=debug in environment 22 | log.debug(traceback.format_exc()) 23 | 24 | finally: 25 | # Save gathered information to a device configuration file 26 | if ctx: 27 | ctx.save('my_device.cfg') 28 | -------------------------------------------------------------------------------- /python/examples/boilerplate_load_ctx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import traceback 3 | from depthcharge import Console, Depthcharge, log 4 | 5 | ctx = None 6 | 7 | try: 8 | console = Console('/dev/ttyUSB0', baudrate=115200) 9 | ctx = Depthcharge.load('my_device.cfg', console) 10 | 11 | # Comment out the above ctx creation and uncomment the following one in 12 | # order to possibly make more operations available to Depthcharge by allowing 13 | # it to deploy executable payloads to RAM and reboot/crash the platform. 14 | #ctx = Depthcharge(console, allow_deploy=True, allow_reboot=True) 15 | 16 | # Perform actions here 17 | 18 | except Exception as error: 19 | log.error(str(error)) 20 | 21 | # Shown if DEPTHCHARGE_LOG_LEVEL=debug in environment 22 | log.debug(traceback.format_exc()) 23 | 24 | finally: 25 | # Save any updates or new information to the device config 26 | if ctx: 27 | ctx.save('my_device.cfg') 28 | -------------------------------------------------------------------------------- /python/examples/deploy_payload.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Deploy and execute a simple "standalone program" that just 4 | returns a constant value: 5 | 6 | AARCH64: 0x666badc0ffeed00d 7 | ARM: 0xc0ffee 8 | """ 9 | 10 | import sys 11 | 12 | from depthcharge import cmdline, log 13 | from depthcharge.cmdline import create_depthcharge_ctx 14 | 15 | parser = cmdline.ArgumentParser() 16 | args = parser.parse_args() 17 | 18 | if args.arch is None: 19 | log.error('Target architecture must be specified.') 20 | sys.exit(1) 21 | elif args.arch.lower() == 'arm': 22 | # push {lr}; ldr r0, =0xc0ffee; pop {pc}; 23 | program = bytes.fromhex('04 e0 2d e5 00 00 9f e5 04 f0 9d e4 ee ff c0 00') 24 | elif args.arch.lower() == 'aarch64': 25 | # ldr x0, =0x666c0ffeed00d; ret; 26 | program = bytes.fromhex('40 00 00 58 c0 03 5f d6 0d d0 ee ff c0 66 06 00') 27 | else: 28 | log.error("Example doesn't support arch: " + args.arch) 29 | sys.exit(1) 30 | 31 | ctx = create_depthcharge_ctx(args) 32 | ctx.register_payload('test_code', program) 33 | ctx.deploy_payload('test_code') 34 | (retval, _) = ctx.execute_payload('test_code') 35 | 36 | log.info('Got return value: 0x{:x}'.format(retval)) 37 | -------------------------------------------------------------------------------- /python/examples/i2c_probe.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import re 4 | 5 | from depthcharge import Depthcharge, Console, OperationFailed, log 6 | from depthcharge.monitor import Monitor 7 | 8 | def setup(): 9 | # Optional: Launch a console monitor so we can 10 | # keep an eye on the underlying operations. We 11 | # use a terminal-based monitor here. 12 | mon = Monitor.create('term') 13 | 14 | # Connect to the target device's serial port 15 | console = Console('/dev/ttyUSB0', baudrate=115200, monitor=mon) 16 | 17 | # Create and return an initialized Depthcharge context 18 | return Depthcharge(console, arch='arm', allow_deploy=True, allow_reboot=True) 19 | 20 | # Alternatively, create it from a previously created device config file 21 | # This will allow Depthcharge to skip any "inspection" steps. 22 | #return Depthcharge.load('my_device.cfg', console) 23 | 24 | def get_buses(ctx): 25 | buses = [] 26 | 27 | resp = ctx.send_command('i2c bus') 28 | for line in resp.splitlines(): 29 | match = re.match(r'Bus (\d+)', line) 30 | if match: 31 | busno = int(match.group(1)) 32 | log.note('Available: Bus {:d}'.format(busno)) 33 | buses.append(busno) 34 | 35 | return buses 36 | 37 | def find_devices(ctx, buses): 38 | results = [] 39 | for bus in buses: 40 | log.note('Probing bus {:d}'.format(bus)) 41 | try: 42 | 43 | cmd = 'i2c dev {:d}'.format(bus) 44 | # Raise an exception on error via check=True 45 | ctx.send_command(cmd, check=True) 46 | 47 | # This may fail for buses (or pinmux settings) that are configured 48 | # appropriately. Thus, we drop check=True and just look results 49 | resp = ctx.send_command('i2c probe') 50 | 51 | 52 | match = re.match(r'Valid chip addresses: ([0-9a-fA-F\t ]+)', resp) 53 | if not match: 54 | # A failing bus will spew failures for a while. Keep trying 55 | # to interrupt it (Ctrl-C) until we know we're back at a prompt. 56 | log.warning('No devices or bus failing. Waiting for prompt.') 57 | ctx.interrupt(timeout=120) 58 | continue 59 | 60 | for addr in match.group(1).split(): 61 | addr = int(addr, 16) 62 | log.info('Found device: Bus={:d}, Address=0x{:02x}'.format(bus, addr)) 63 | results.append((bus, addr)) 64 | 65 | except OperationFailed as error: 66 | log.error('Command failed: ' + cmd + os.linesep + str(error)) 67 | 68 | return results 69 | 70 | if __name__ == '__main__': 71 | # Attach to the device and get a Depthcharge context 72 | ctx = setup() 73 | 74 | log.info('Identifying available I2C buses.') 75 | buses = get_buses(ctx) 76 | 77 | log.info('Probing I2C buses for devices. This may take some time.') 78 | # We'll just log results as we go, rather than use the return value 79 | find_devices(ctx, buses) 80 | -------------------------------------------------------------------------------- /python/examples/read_nand.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Simple demo script that retrieves a device tree binary and kernel 4 | # from NAND and saves them to files 5 | 6 | # (The U-Boot variables used here are specific to a particular platform.) 7 | 8 | import os 9 | import traceback 10 | 11 | from depthcharge import Console, Depthcharge, log 12 | 13 | 14 | def validate_requirements(ctx): 15 | REQUIRED_CMDS = ('nand',) 16 | 17 | log.note('Checking for required target commands.') 18 | cmds = ctx.commands() 19 | for cmd in REQUIRED_CMDS: 20 | if cmd not in cmds: 21 | msg = 'Command not present in target environment: ' + cmd 22 | raise ValueError(msg) 23 | 24 | REQUIRED_VARS = ('loadaddr', 25 | 'dtbimage', 'dtb_offset', 'dtb_size', 26 | 'image', 'kernel_offset', 'kernel_size') 27 | 28 | log.note('Checking for required target environment variables.') 29 | env = ctx.environment() 30 | for var in REQUIRED_VARS: 31 | if var not in env: 32 | msg = 'Variable not present in target environment: ' + var 33 | raise ValueError(msg) 34 | 35 | return env 36 | 37 | 38 | def read_nand_to_file(ctx, filename: str, name: str, 39 | load_addr: int, nand_addr: int, size: int): 40 | 41 | # Copy NAND contents to ${loadaddr} 42 | cmd = 'nand read 0x{loadaddr:x} 0x{nand_addr:x} 0x{size:x}' 43 | cmd = cmd.format(loadaddr=load_addr, 44 | nand_addr=nand_addr, 45 | size=size) 46 | 47 | log.info('Copying ' + name + ' to RAM buffer') 48 | resp = ctx.send_command(cmd, check=True) 49 | log.note('Device response: ' + resp.strip()) 50 | 51 | log.info('Reading RAM buffer to file: ' + filename) 52 | ctx.read_memory_to_file(load_addr, size, filename) 53 | 54 | 55 | if __name__ == '__main__': 56 | config_file = 'my_device.cfg' 57 | ctx = None 58 | 59 | try: 60 | console = Console('/dev/ttyUSB0', baudrate=115200) 61 | 62 | if os.path.exists(config_file): 63 | ctx = Depthcharge.load(config_file, console, arch='arm', 64 | allow_deploy=True, allow_reboot=True) 65 | else: 66 | ctx = Depthcharge(console, arch='arm', 67 | allow_deploy=True, allow_reboot=True) 68 | 69 | # Check for the presence of commands and variables we'll use 70 | env = validate_requirements(ctx) 71 | 72 | # While technically unneccessary to convert enviornment variables to 73 | # integers here, only to then convert them back to strings when used 74 | # in a command, this affords a chance to confirm they are valid values 75 | # before we attempt to use them. 76 | read_nand_to_file(ctx, 'kernel.bin', env['image'], 77 | int(env['loadaddr'], 0), 78 | int(env['kernel_offset'], 0), 79 | int(env['kernel_size'], 0)) 80 | 81 | read_nand_to_file(ctx, 'dtb.bin', env['dtbimage'], 82 | int(env['loadaddr'], 0), 83 | int(env['dtb_offset'], 0), 84 | int(env['dtb_size'], 0)) 85 | 86 | except Exception as error: 87 | log.error(str(error)) 88 | 89 | # Shown if DEPTHCHARGE_LOG_LEVEL=debug in environment 90 | log.debug(traceback.format_exc()) 91 | 92 | finally: 93 | # Save gathered information to a device configuration file 94 | if ctx: 95 | ctx.save('my_device.cfg') 96 | -------------------------------------------------------------------------------- /python/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel"] 3 | -------------------------------------------------------------------------------- /python/scripts/depthcharge-find-cmd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | # Depthcharge: 5 | # 6 | # Ignore warnings that aren't particularly meaningful in this script: 7 | # pylint: disable=missing-module-docstring,,invalid-name,redefined-outer-name 8 | # 9 | 10 | import sys 11 | 12 | from argparse import RawDescriptionHelpFormatter 13 | from os.path import basename 14 | 15 | from depthcharge.cmdline import ArgumentParser 16 | from depthcharge.hunter import CommandTableHunter 17 | 18 | _LONGHELP_TEXT = 'Value of U-Boot CONFIG_SYS_LONGHELP setting.' 19 | _AUTOCOMPLETE_TEXT = 'Value of U-Boot CONFIG_AUTO_COMPLETE setting.' 20 | 21 | _USAGE = '{:s} [options] -f '.format(basename(__file__)) 22 | 23 | _DESCRIPTION = """ 24 | Search for U-Boot command tables within a memory or flash dump. 25 | """ 26 | 27 | _EPILOG = """ 28 | notes: 29 | If the --longhelp and --autocomplete options are not specified, Depthcharge 30 | will attempt to infer the state of these compile-time configuration settings. 31 | 32 | When --subcmds is specified, output will include any subcommand handler tables. 33 | This may require a lower --threshold setting, which could yield false positives. 34 | 35 | 36 | example: 37 | Search for command table entries provide detailed output, given an ARM 38 | device and a memory dump taken from address 0x87800000. 39 | 40 | depthcharge-find-cmd --arch arm -a 0x8780000 -f dump.bin --details 41 | \r 42 | """ 43 | 44 | 45 | def handle_cmdline(): 46 | """ 47 | Parse and return command line arguments 48 | """ 49 | 50 | cmdline = ArgumentParser(init_args=['address', 'arch', 'file'], 51 | address_required=True, address_default=None, 52 | file_required=True, 53 | file_help='Flash or memory image to inspect', 54 | formatter_class=RawDescriptionHelpFormatter, 55 | usage=_USAGE, description=_DESCRIPTION, epilog=_EPILOG) 56 | 57 | cmdline.add_argument('--longhelp', 58 | choices=['Y', 'N'], 59 | default=None, 60 | help=_LONGHELP_TEXT) 61 | 62 | cmdline.add_argument('--autocomplete', 63 | choices=['Y', 'N'], 64 | default=None, 65 | help=_AUTOCOMPLETE_TEXT) 66 | 67 | cmdline.add_argument('--threshold', 68 | type=int, 69 | default=5, 70 | help='Minimum table size to report. Default: 5') 71 | 72 | cmdline.add_argument('--subcmds', 73 | action='store_true', 74 | default=False, 75 | help='Include sub-command tables in displayed results') 76 | 77 | cmdline.add_argument('--details', 78 | action='store_true', 79 | default=False, 80 | help='Display more detailed output') 81 | 82 | args = cmdline.parse_args() 83 | 84 | if args.longhelp is not None: 85 | args.longhelp = args.longhelp == 'Y' 86 | 87 | if args.autocomplete is not None: 88 | args.autocomplete = args.autocomplete == 'Y' 89 | 90 | return args 91 | 92 | 93 | if __name__ == '__main__': 94 | args = handle_cmdline() 95 | 96 | with open(args.file, 'rb') as infile: 97 | image_data = infile.read() 98 | 99 | try: 100 | hunter = CommandTableHunter(image_data, args.address, arch=args.arch) 101 | except (ValueError, TypeError) as e: 102 | print('Failed to create depthcharge.CommandTableHunter', str(e), file=sys.stderr) 103 | sys.exit(1) 104 | 105 | to_str = hunter.result_str if args.details else hunter.result_summary_str 106 | finditer = hunter.finditer(None, 107 | threshold=args.threshold, 108 | longhelp=args.longhelp, 109 | autocomplete=args.autocomplete) 110 | 111 | for result in finditer: 112 | if not result['is_subcmd_table'] or args.subcmds: 113 | print(to_str(result)) 114 | -------------------------------------------------------------------------------- /python/scripts/depthcharge-find-fdt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | # Depthcharge: 5 | # 6 | # Ignore docstring and name complaints 7 | # pylint: disable=missing-module-docstring,missing-function-docstring 8 | # pylint: disable=redefined-outer-name,invalid-name 9 | # 10 | 11 | import sys 12 | 13 | from argparse import RawDescriptionHelpFormatter 14 | from os.path import basename 15 | 16 | from depthcharge import log 17 | from depthcharge.cmdline import ArgumentParser 18 | from depthcharge.hunter import FDTHunter 19 | 20 | _FILE_HELP = 'Binary image to search' 21 | 22 | _OUTFILE_HELP = ( 23 | 'Filename prefix for output file(s). ' 24 | 'No files are written if this is not provided. ' 25 | 'A .dts or .dtb suffix will be added to each file.' 26 | ) 27 | 28 | _ADDRESS_HELP = ( 29 | 'Base address of the flash or memory dump. ' 30 | 'Results are shown with respect to this address. ' 31 | 'Use the default of 0 if interested in relative offsets.' 32 | ) 33 | 34 | _NO_DTS_HELP = 'Do not save .dts files.' 35 | _NO_DTB_HELP = 'Do not save .dtb files.' 36 | 37 | _USAGE = '{:s} [options] -f '.format(basename(__file__)) 38 | 39 | _DESCRIPTION = """ 40 | Search for instances of Flattened Device Tree blobs in a flash or memory dump 41 | """ 42 | 43 | _EPILOG = """ 44 | examples: 45 | Print locations and sizes of FDT (DTB) instances found within an image. 46 | 47 | depthcharge-find-fdt --arch arm -f image.bin 48 | 49 | Extract FDT instances and save them to .dtb and .dts files named 50 | "image_
.[dts|dtb]". Additionally, specify a base address 51 | for the image. 52 | 53 | depthcharge-find-fdt --arch arm -a 0xa000 -f image.bin -o image 54 | 55 | Same as the above, but only save .dts files. 56 | 57 | depthcharge-find-fdt --arch arm -a 0xa000 -f image.bin -o image --no-dtb 58 | 59 | \r 60 | """ 61 | 62 | 63 | def handle_cmdline(): 64 | parser = ArgumentParser(['file', 'address', 'arch', 'outfile'], 65 | file_required=True, file_help=_FILE_HELP, 66 | address_required=False, address_help=_ADDRESS_HELP, 67 | outfile_required=False, outfile_help=_OUTFILE_HELP, 68 | formatter_class=RawDescriptionHelpFormatter, 69 | usage=_USAGE, description=_DESCRIPTION, epilog=_EPILOG) 70 | 71 | parser.add_argument('--no-dts', action='store_true', help=_NO_DTS_HELP) 72 | parser.add_argument('--no-dtb', action='store_true', help=_NO_DTB_HELP) 73 | 74 | return parser.parse_args() 75 | 76 | 77 | if __name__ == '__main__': 78 | args = handle_cmdline() 79 | 80 | log.debug('Loading ' + args.file) 81 | with open(args.file, 'rb') as infile: 82 | data = infile.read() 83 | 84 | count = 0 85 | 86 | hunter = FDTHunter(data, args.address, no_dts=args.no_dts) 87 | for result in hunter.finditer(None): 88 | addr = result['src_addr'] 89 | size = result['src_size'] 90 | 91 | dtb = result['dtb'] 92 | dts = result.get('dts', None) # Will not be present if no_dts=True 93 | 94 | print('Found DTB @ 0x{:08x}, size={:d} bytes'.format(addr, size)) 95 | 96 | if args.outfile: 97 | if not args.no_dtb: 98 | dtb_filename = args.outfile + '_0x{:08x}.dtb'.format(addr) 99 | with open(dtb_filename, 'wb') as outfile: 100 | print(' Saving to: ' + dtb_filename) 101 | outfile.write(result['dtb']) 102 | 103 | if not args.no_dts: 104 | dts_filename = args.outfile + '_0x{:08x}.dts'.format(addr) 105 | with open(dts_filename, 'w') as outfile: 106 | print(' Saving to: ' + dts_filename) 107 | outfile.write(result['dts']) 108 | 109 | count += 1 110 | 111 | if count < 1: 112 | print('No Device Tree instances found.', file=sys.stderr) 113 | sys.exit(2) 114 | -------------------------------------------------------------------------------- /python/scripts/depthcharge-inspect: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | # Depthcharge: 5 | # 6 | # Ignore module and "constant" name complaints 7 | # pylint: disable=missing-module-docstring,invalid-name 8 | # 9 | 10 | import sys 11 | import traceback 12 | 13 | from argparse import RawDescriptionHelpFormatter 14 | from os.path import basename 15 | 16 | import depthcharge 17 | from depthcharge.cmdline import ArgumentParser, create_depthcharge_ctx 18 | 19 | _USAGE = '{:s} [options] -c '.format(basename(__file__)) 20 | 21 | _DESCRIPTION = "Inspect a device's console environment and create a device configuration file." 22 | 23 | _EPILOG = """ 24 | notes: 25 | This is generally the first Depthcharge script one will want to run when 26 | interacting with a new device. 27 | 28 | The resulting configuration file can passed to other Depthcharge scripts, 29 | alleviating the need to re-inspect the device each time a context object 30 | is created. (See depthcharge.Depthcharge.load() documentation.) 31 | 32 | examples: 33 | Save results to a "dev.cfg" file and use the default serial interface 34 | settings (/dev/ttyUSB0 at 115200 baud). This will use the default "Generic" 35 | 32-bit little endian CPU architecture, and will avoid the use of any 36 | operations that require rebooting/crashing the platform or deploying 37 | and executing payloads. 38 | 39 | depthcharge-inspect -c dev.cfg 40 | 41 | Use the serial console located at /dev/ttyUSB2 at a speed of 19200 baud. 42 | Here, we additionally specify that the target is a 32-bit ARM device, 43 | and that we want to opt-in to the use of operations that necessitate 44 | crashing/rebooting the platform, as well as payload deployment and execution. 45 | 46 | depthcharge-inspect --arch arm -AR -i /dev/ttyUSB2:19200 -c dev.cfg 47 | 48 | Use a companion device attached to /dev/ttyACM1. Configure the companion 49 | to operate on the target's I2C bus #2, for a speed of 250kHz. (Here, -i is 50 | omitted again to use the default settings.) 51 | 52 | depthcharge-inspect --arch arm -AR -c dev.cfg \\ 53 | -C /dev/ttyACM1:i2c_bus=2,i2c_speed=250000 54 | 55 | Supply a known prompt string to look for instead of having Depthcharge attempt 56 | to determine it: 57 | 58 | depthcharge-inspect --arch arm -AR --prompt "ACMEcorp >" -c my_config.cfg 59 | \r 60 | """ 61 | 62 | if __name__ == '__main__': 63 | parser = ArgumentParser(usage=_USAGE, 64 | formatter_class=RawDescriptionHelpFormatter, 65 | description=_DESCRIPTION, epilog=_EPILOG, 66 | config_required=True) 67 | 68 | args = parser.parse_args() 69 | 70 | try: 71 | ctx = None 72 | 73 | # Kick off the inspect inherent in context creation 74 | ctx = create_depthcharge_ctx(args, detailed_help=True) 75 | 76 | except Exception as e: # pylint: disable=broad-except 77 | depthcharge.log.debug(traceback.format_exc()) 78 | print('Error: ' + str(e), file=sys.stderr) 79 | sys.exit(1) 80 | finally: 81 | # Try to be neighborly and save what we were able to find, even if 82 | # something tanked unexpectedly. 83 | if ctx: 84 | ctx.save(args.config) 85 | -------------------------------------------------------------------------------- /python/scripts/depthcharge-mkenv: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | # Depthcharge: 5 | # 6 | # Ignore docstring and name complaints 7 | # pylint: disable=missing-module-docstring,missing-function-docstring 8 | # pylint: disable=redefined-outer-name,invalid-name 9 | # 10 | 11 | import sys 12 | 13 | from argparse import RawDescriptionHelpFormatter 14 | from os.path import basename 15 | 16 | from depthcharge import uboot 17 | from depthcharge.cmdline import ArgumentParser, LengthAction 18 | from depthcharge.string import to_positive_int 19 | 20 | 21 | _FILE_HELP = 'Input file containing environment in text form.' 22 | 23 | _OUTFILE_HELP = 'Output file for binary environment.' 24 | 25 | _SIZE_HELP = "Environment size. Must match target's CONFIG_ENV_SIZE." 26 | 27 | _FLAGS_HELP = 'Set a value for the flags bytes. It is not included otherwise.' 28 | 29 | _NO_HDR_HELP = 'Do not include header metadata (CRC word, flag).' 30 | 31 | _USAGE = '{:s} [options] -f -o '.format(basename(__file__)) 32 | 33 | _DESCRIPTION = """\ 34 | Make an environment that can be inserted into a target devices' NV storage\ 35 | """ 36 | 37 | _EPILOG = """\ 38 | examples: 39 | 40 | Create an environment for a target whose U-Boot image was not built with 41 | CONFIG_SYS_REDUNDAND_ENV, and CONFIG_ENV_SIZE=0x2000. 42 | 43 | depthcharge-mkenv -S 0x2000 -f env.txt -o env.bin 44 | 45 | Create an environment for use on a target device whose U-Boot image 46 | was built with CONFIG_SYS_REDUNDAND_ENV and CONFIG_ENV_SIZE=0x10000. 47 | Note that the flags value must be greater than or equal to that of the 48 | active environment that is being replaced. 49 | 50 | depthcharge-mkenv -S 0x10000 -F 0x5 -f env.txt -o env.bin 51 | 52 | Convert 2 KiB environment, but do not prepend a CRC32 53 | checksum (nor a flag word). 54 | 55 | depthcharge-mkenv -S 2K -H -f env.txt -o env.bin 56 | 57 | \r 58 | """ 59 | 60 | 61 | def handle_cmdline(): 62 | parser = ArgumentParser(['file', 'arch', 'outfile'], 63 | file_required=True, file_help=_FILE_HELP, 64 | arch_required=False, 65 | outfile_required=True, outfile_help=_OUTFILE_HELP, 66 | formatter_class=RawDescriptionHelpFormatter, 67 | usage=_USAGE, description=_DESCRIPTION, epilog=_EPILOG) 68 | 69 | parser.add_argument('-S', '--size', required=True, action=LengthAction, help=_SIZE_HELP) 70 | parser.add_argument('-F', '--flags', default=None, help=_FLAGS_HELP) 71 | parser.add_argument('-H', '--no-hdr', default=False, action='store_true', help=_NO_HDR_HELP) 72 | 73 | args = parser.parse_args() 74 | 75 | if args.flags: 76 | args.flags = to_positive_int(args.flags, 'flags') 77 | if args.flags > 255: 78 | print('Flags must be in the the range [0x00, 0xff]', file=sys.stderr) 79 | sys.exit(2) 80 | 81 | return args 82 | 83 | 84 | if __name__ == '__main__': 85 | args = handle_cmdline() 86 | 87 | env = uboot.env.load(args.file) 88 | uboot.env.save_raw(args.outfile, env, args.size, args.arch, args.flags, args.no_hdr) 89 | -------------------------------------------------------------------------------- /python/scripts/depthcharge-read-mem: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | # Depthcharge: 5 | # 6 | # Suppress complaints that don't add much value this script 7 | # pylint: disable=missing-module-docstring,missing-function-docstring 8 | # pylint: disable=invalid-name,redefined-outer-name 9 | 10 | import sys 11 | import traceback 12 | 13 | from argparse import RawDescriptionHelpFormatter 14 | from os.path import basename 15 | 16 | from depthcharge import log 17 | from depthcharge.cmdline import ArgumentParser, create_depthcharge_ctx 18 | from depthcharge.string import xxd 19 | 20 | _SCRIPT = basename(__file__) 21 | 22 | _USAGE = """ 23 | {script:s} [options] -c -a
-l 24 | {script:s} [options] -c -a
-l -f 25 | \r 26 | """.format(script=_SCRIPT) 27 | 28 | _DESCRIPTION = 'Read memory contents to a file or display them in a hex dump.' 29 | 30 | _EPILOG = """ 31 | notes: 32 | If a filename is not provided, a textual hex dump will be printed. 33 | 34 | The following address and length suffixes are supported: 35 | 36 | * kB = 1000 37 | * K or kiB = 1024 38 | * MB = 1000 * 1000 39 | * M or MiB = 1024 * 1024 40 | * GB = 1000 * 1000 * 1000 41 | * G or GiB = 1024 * 1024 * 1024 42 | 43 | While optional, the use of the -c, --config option is STRONGLY ENCOURAGED. 44 | This will greatly speed up the execution time of this script, if it will 45 | be used multiple times for a given target. Similarly, once any payloads 46 | needed by a reader have been deployed to RAM, the -D, --skip-deploy 47 | option can be used to further speed up execution. 48 | 49 | examples: 50 | Read 16384 bytes from 0x82000000 and display a hex dump: 51 | 52 | depthcharge-read-mem -c dev.cfg -a 0x82000000 -l 16384 53 | 54 | The following example is equivalent, but uses supported suffixes: 55 | 56 | depthcharge-read-mem -c dev.cfg -a 2080M -l 16K 57 | 58 | This example instead reads data to a file. Note that Python's underscore 59 | syntax for readability is supported. 60 | 61 | depthcharge-read-mem -c dev.cfg -a 0x8200_0000 -l 16K -f data.bin 62 | 63 | Request Depthcharge to use the depthcharge.memory.SetexprMemoryReader class 64 | to read memory. Note that the `MemoryReader` suffix can be omitted, and 65 | that this argument is case-insensitve. 66 | 67 | depthcharge-read-mem -c dev.cfg -a 0x8200_0000 -l 1M -f data.bin --op setexpr 68 | \r 69 | """ 70 | 71 | 72 | def handle_cmdline(): 73 | supported = ArgumentParser.DEFAULT_ARGS + ['file', 'address', 'length', 'op'] 74 | parser = ArgumentParser(init_args=supported, 75 | formatter_class=RawDescriptionHelpFormatter, 76 | usage=_USAGE, description=_DESCRIPTION, epilog=_EPILOG, 77 | address_help='Target address to read from', 78 | address_default=None, 79 | length_required=True, 80 | file_help='Optional file to store data in.') 81 | 82 | return parser.parse_args() 83 | 84 | 85 | def read_memory(args): 86 | ctx = create_depthcharge_ctx(args) 87 | 88 | if args.file: 89 | ctx.read_memory_to_file(args.address, args.length, args.file, impl=args.op) 90 | else: 91 | data = ctx.read_memory(args.address, args.length, impl=args.op) 92 | hexdump = xxd(args.address, data) 93 | print(hexdump) 94 | 95 | 96 | if __name__ == '__main__': 97 | args = handle_cmdline() 98 | try: 99 | read_memory(args) 100 | except Exception as error: # pylint: disable=broad-except 101 | log.debug(traceback.format_exc()) 102 | print('Error: ' + str(error), file=sys.stderr) 103 | sys.exit(1) 104 | -------------------------------------------------------------------------------- /python/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Depthcharge installation script 4 | """ 5 | 6 | import os 7 | import re 8 | 9 | from os.path import dirname, join, realpath 10 | from setuptools import setup, find_packages 11 | 12 | THIS_DIR = realpath(dirname(__file__)) 13 | 14 | # This is technically more permissive than what is dictated by PEP440, 15 | # which requires a dot for suffixes, rather than a dash. (Plus signs 16 | # are used for local versions.) 17 | # 18 | # Strict compliance will matter uploads to PyPi, but I'd prefer that 19 | # mistakes in the dev series don't break the *next* branch for users 20 | # of the bleeding edge code in GitHub. 21 | # 22 | # See https://www.python.org/dev/peps/pep-0440/#public-version-identifiers 23 | # 24 | VERSION_REGEX = re.compile( 25 | r"__version__\s*=\s*'(?P[0-9]+\.[0-9]+\.[0-9]+((\.|-|\+)[a-zA-Z0-9]+)*)'" 26 | ) 27 | 28 | 29 | def get_version() -> str: 30 | version_file = join(THIS_DIR, 'depthcharge', 'version.py') 31 | with open(version_file, 'r') as infile: 32 | version_info = infile.read() 33 | match = VERSION_REGEX.search(version_info) 34 | if match: 35 | return match.group('version') 36 | 37 | raise ValueError('Failed to find version info') 38 | 39 | 40 | def get_scripts() -> list: 41 | ret = [] 42 | for root, _, files in os.walk('scripts'): 43 | for filename in files: 44 | if filename.startswith('.') or filename.endswith('.swp'): 45 | continue 46 | 47 | script_file = join(root, filename) 48 | ret.append(script_file) 49 | 50 | if not ret: 51 | raise FileNotFoundError('Depthcharge scripts not found') 52 | 53 | return ret 54 | 55 | 56 | def get_description() -> str: 57 | with open('Depthcharge.md', 'r') as infile: 58 | return infile.read() 59 | 60 | 61 | setup( 62 | name='depthcharge', 63 | version=get_version(), 64 | description='A U-Boot toolkit for security researchers and tinkerers', 65 | 66 | long_description=get_description(), 67 | long_description_content_type='text/markdown', 68 | 69 | license='BSD 3-Clause License', 70 | author='Tetrel Security', 71 | author_email='contact@tetrelsec.com', 72 | url='https://github.com/tetrelsec/depthcharge', 73 | 74 | # I'm only supporting Linux at the moment, 75 | platform='linux', 76 | 77 | packages=find_packages(), 78 | scripts=get_scripts(), 79 | 80 | install_requires=['pyserial >= 3.4', 'tqdm >= 4.30.0'], 81 | 82 | python_requires='>=3.6, <4', 83 | 84 | extras_require={ 85 | 'docs': ['sphinx>=4.4.0', 'sphinx_rtd_theme >=1.0.0, <2.0.0'] 86 | }, 87 | 88 | zip_safe=False, 89 | 90 | classifiers=[ 91 | 'Development Status :: 4 - Beta', 92 | 'Environment :: Console', 93 | 'Intended Audience :: Other Audience', 94 | 'License :: OSI Approved :: BSD License', 95 | 'Operating System :: POSIX :: Linux', 96 | 'Programming Language :: Python :: 3 :: Only', 97 | 'Programming Language :: Python :: 3.6', 98 | 'Programming Language :: Python :: 3.7', 99 | 'Programming Language :: Python :: 3.8', 100 | 'Topic :: Security', 101 | 'Topic :: System :: Boot', 102 | 'Topic :: System :: Hardware', 103 | ], 104 | 105 | project_urls={ 106 | 'Documentation': 'https://depthcharge.readthedocs.io', 107 | 'Source': 'https://github.com/tetrelsec/depthcharge', 108 | 'Issue Tracker': 'https://github.com/tetrelsec/depthcharge/issues', 109 | }, 110 | ) 111 | -------------------------------------------------------------------------------- /python/tests/integration/.gitignore: -------------------------------------------------------------------------------- 1 | resources/* 2 | -------------------------------------------------------------------------------- /python/tests/integration/README.md: -------------------------------------------------------------------------------- 1 | This directory contains integration tests intended to be run on 2 | physical hardware. 3 | -------------------------------------------------------------------------------- /python/tests/integration/register_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | # Depthcharge: 5 | # 6 | # pylint: disable=redefined-outer-name,missing-function-docstring,invalid-name 7 | # pylint: disable=global-statement # (Like salt and sugar; fine if used sparingly) 8 | 9 | """ 10 | Exercise all available RegisterReader operations for a platform. 11 | """ 12 | 13 | import os 14 | import sys 15 | 16 | from depthcharge.cmdline import ArgumentParser, create_depthcharge_ctx 17 | 18 | _DEFAULT_ARCH = os.getenv('DEPTHCHARGE_TEST_ARCH', 'arm') 19 | 20 | 21 | def perform_reads(ctx) -> list: 22 | reg = ctx.arch.gd_register 23 | results = [] 24 | 25 | # Get ground truth with default reader. 26 | expected_value = ctx.read_register(reg) 27 | 28 | for impl in ctx.register_readers: 29 | value = impl.read(reg) 30 | success = value == expected_value 31 | results.append((impl.name, value, success)) 32 | 33 | return results 34 | 35 | 36 | def print_results(results): 37 | total = len(results) 38 | n_pass = 0 39 | 40 | print() 41 | print(' RegisterReader Value Pass/Fail') 42 | print('---------------------------------------------------------') 43 | 44 | for result in results: 45 | if result[2]: 46 | state = 'Pass' 47 | n_pass += 1 48 | else: 49 | state = 'Fail' 50 | 51 | line = ' {:32s} 0x{:08x} {:s}' 52 | print(line.format(result[0], result[1], state)) 53 | 54 | summary = os.linesep + '{:d} Tested, {:d} passed.' + os.linesep 55 | print(summary.format(total, n_pass)) 56 | 57 | return n_pass == total 58 | 59 | 60 | if __name__ == '__main__': 61 | success = False 62 | cmdline = ArgumentParser( 63 | allow_deploy_default=True, allow_reboot_default=True, 64 | arch_default=_DEFAULT_ARCH) 65 | 66 | args = cmdline.parse_args() 67 | ctx = create_depthcharge_ctx(args) 68 | 69 | if args.config: 70 | ctx.save(args.config) 71 | 72 | try: 73 | results = perform_reads(ctx) 74 | success = print_results(results) 75 | 76 | finally: 77 | if args.config: 78 | ctx.save(args.config) 79 | 80 | if not success: 81 | sys.exit(2) 82 | -------------------------------------------------------------------------------- /python/tests/integration/test_utils.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | # 4 | """ 5 | Utility functions for integration tests 6 | """ 7 | 8 | import json 9 | import random 10 | import time 11 | 12 | from os import makedirs, path 13 | from os.path import dirname, realpath 14 | 15 | from depthcharge import log 16 | 17 | _THIS_DIR = dirname(realpath(__file__)) 18 | 19 | 20 | def create_resource_dir(test_dir, test_subdir='') -> str: 21 | """ 22 | Create a test-specific resource directory and subdirectory. 23 | 24 | The resulting path is returned. 25 | """ 26 | resource_dir = path.join(_THIS_DIR, 'resources', test_dir, test_subdir) 27 | makedirs(resource_dir, 0o770, exist_ok=True) 28 | return resource_dir 29 | 30 | 31 | def load_resource(filename: str, load_file_func, test_dir='', test_subdir='', msg_pfx=''): 32 | """ 33 | Load a resource file a test-specific specific directory and subdirectory. 34 | 35 | `load_file_func` should take a single filename argument. This function 36 | returns that of `load_file_func()`. 37 | """ 38 | resource_dir = create_resource_dir(test_dir, test_subdir) 39 | 40 | file_path = path.join(resource_dir, filename) 41 | ret = load_file_func(file_path) 42 | 43 | if msg_pfx is not None: 44 | if msg_pfx == '': 45 | msg_pfx = 'Loaded resource from prior test: {:s}' 46 | log.note(msg_pfx.format(file_path)) 47 | 48 | return ret 49 | 50 | 51 | def save_resource(filename: str, save_file_func, test_dir='', test_subdir='', msg_pfx=''): 52 | """ 53 | Save a resource to a test-specific directory and subdirectory. 54 | 55 | This mirrors the usage of load_resource(). 56 | """ 57 | # Just wrapping a more sensible name - it ends up being the same for now 58 | msg_pfx = 'Saving resource for future tests: {:s}' 59 | load_resource(filename, save_file_func, test_dir, test_subdir, msg_pfx=msg_pfx) 60 | 61 | 62 | def random_pattern(size: int, seed: int = 0) -> bytes: 63 | """ 64 | Return `size` random bytes. 65 | """ 66 | ret = bytearray(size) 67 | random.seed(seed) 68 | for i in range(0, size): 69 | ret[i] = random.randint(0, 255) 70 | return bytes(ret) 71 | 72 | 73 | def decrementing_pattern(size: int) -> bytes: 74 | """ 75 | Return `size` bytes with a pattern of decrementing byte values. 76 | """ 77 | ret = bytearray(size) 78 | for i in range(size - 1, -1, -1): 79 | ret[i] = i & 0xff 80 | return bytes(ret) 81 | 82 | 83 | def incrementing_pattern(size: int) -> bytes: 84 | """ 85 | Return `size` bytes with a pattern of incrementing byte values. 86 | """ 87 | ret = bytearray(size) 88 | for i in range(0, size): 89 | ret[i] = i & 0xff 90 | return bytes(ret) 91 | 92 | 93 | def now_str() -> str: 94 | """ 95 | Return the current time in seconds since the Unix Epoch. 96 | """ 97 | return str(int(time.time())) 98 | 99 | 100 | def load_file(filename: str, mode='r'): 101 | """ 102 | Open a file and return its contents. 103 | Return type depends on whether the mode is 'r' or 'rb'. 104 | """ 105 | with open(filename, mode) as infile: 106 | return infile.read() 107 | 108 | 109 | def save_file(filename: str, data, mode='w'): 110 | """ 111 | Write data to the specified file. 112 | """ 113 | with open(filename, mode) as outfile: 114 | outfile.write(data) 115 | 116 | 117 | def load_config(filename: str) -> dict: 118 | """ 119 | Load a Depthcharge device configuration file and return its corresponding 120 | dictionary representation. 121 | """ 122 | return json.loads(load_file(filename)) 123 | -------------------------------------------------------------------------------- /python/tests/resources/config_01.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONFIG_H 2 | #define __CONFIG_H 3 | 4 | #include 5 | 6 | #define CONFIG_CMD_MEMORY 7 | #undef CONFIG_CMD_I2C 8 | #undef CONFIG_CMD_LOADB 9 | 10 | #if CONFIG_DOESNT_EXIST 11 | #error Fail here 12 | #endif 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /python/tests/resources/config_02.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONFIG_H 2 | #define __CONFIG_H 3 | 4 | #include 5 | #include 6 | 7 | #define CONFIG_CMD_MEMORY 8 | #undef CONFIG_CMD_I2C 9 | #undef CONFIG_CMD_LOADB 10 | 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /python/tests/resources/config_03.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONFIG_H 2 | #define __CONFIG_H 3 | 4 | #include 5 | 6 | #ifdef INCLUDE_MEMORY_COMMANDS 7 | #define CONFIG_CMD_MEMORY 8 | #endif 9 | 10 | #undef CONFIG_CMD_I2C 11 | #undef CONFIG_CMD_LOADB 12 | 13 | #if CONFIG_DOESNT_EXIST 14 | #error Fail here 15 | #endif 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /python/tests/resources/default_cmds.h: -------------------------------------------------------------------------------- 1 | #ifndef __DEFAULT_CMDS_H 2 | #define __DEFAULT_CMDS_H 3 | 4 | #define CONFIG_CMD_BDI 5 | #define CONFIG_CMD_BEDBUG 6 | #define CONFIG_CMD_I2C 7 | #define CONFIG_CMD_EXT2 8 | #define CONFIG_CMD_LOADB 9 | #define CONFIG_CMD_LOADS 10 | #define CONFIG_CMD_NET 11 | #define CONFIG_CMD_RARP 12 | #define CONFIG_CMD_RUN 13 | #define CONFIG_CMD_SETEXPR 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /python/tests/resources/dotconfig-02.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Artisanlly generated file; DO NOT EDIT. 3 | # U-Boot 2076.13 Configuration 4 | # 5 | 6 | CONFIG_FOO=y 7 | # CONFIG_BAR is not set 8 | CONFIG_FIT_SIGNATURE=y 9 | CONFIG_BAZ=y 10 | 11 | CONFIG_ROYAL_TENENBAUM="Hell of a damn grave. Wish it were mine." 12 | CONFIG_MY_VOICE=IS_MY_PASSPORT 13 | CONFIG_SPACE_ODYSSEY=2001 14 | -------------------------------------------------------------------------------- /python/tests/resources/dotconfig-03.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Artisanlly generated file; DO NOT EDIT. 3 | # U-Boot 2013.07 Configuration 4 | # 5 | 6 | CONFIG_FOO=y 7 | # CONFIG_BAR is not set 8 | CONFIG_FIT_SIGNATURE=y 9 | CONFIG_BAZ=y 10 | CONFIG_LEGACY_IMAGE_FORMAT=y 11 | -------------------------------------------------------------------------------- /python/tests/resources/dotconfig-04.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Artisanlly generated file; DO NOT EDIT. 3 | # U-Boot 2019.04 Configuration 4 | # 5 | 6 | CONFIG_FOO=y 7 | # CONFIG_BAR is not set 8 | CONFIG_FIT_SIGNATURE=y 9 | CONFIG_BAZ=y 10 | # CONFIG_LEGACY_IMAGE_FORMAT=y 11 | CONFIG_SPL_FS_EXT4=y 12 | -------------------------------------------------------------------------------- /python/tests/resources/test/induce_error.h: -------------------------------------------------------------------------------- 1 | #error "This error is supposed to happen. It's part of the test." 2 | -------------------------------------------------------------------------------- /python/tests/run_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | function run { 6 | echo $@ 7 | echo ----------------------------------------------- 8 | $@ 9 | echo 10 | } 11 | 12 | if [ -z "${DEPTHCHARGE_TEST_ARCH}" ]; then 13 | export DEPTHCHARGE_TEST_ARCH=arm 14 | fi 15 | 16 | run python -m unittest unit 17 | 18 | run ./integration/launch_scripts.py 19 | run ./integration/memory_test.py --arch ${DEPTHCHARGE_TEST_ARCH} 20 | run ./integration/memory_test.py 21 | run ./integration/register_test.py 22 | 23 | echo 24 | echo 25 | echo '<-----------------v^v^v^---------||--------o------->' 26 | echo ' | ' 27 | echo ' Done! ZOMG SHIPPIT!!!111 --- ' 28 | echo ' - ' 29 | echo 30 | -------------------------------------------------------------------------------- /python/tests/unit/README.md: -------------------------------------------------------------------------------- 1 | This directory contains unit test for depthcharge module. 2 | 3 | Where possible, tests should be organized in a heirarchy that mirrors that of 4 | the module. 5 | 6 | To run all tests: 7 | 8 | ``` 9 | python3 -m unittest unit 10 | ``` 11 | -------------------------------------------------------------------------------- /python/tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa=E401 2 | # pylint: disable=missing-module-docstring 3 | 4 | from .checker import ( 5 | TestImportBuiltins, 6 | TestReport, 7 | TestSecurityRisk, 8 | TestUBootConfigChecker, 9 | TestUBootHeaderChecker 10 | ) 11 | 12 | from .hunter import ( 13 | TestConstantHunter, 14 | TestGappedRangeIter, 15 | TestReverseCRC32Hunter, 16 | TestStringHunter 17 | ) 18 | 19 | from .operation import ( 20 | TestOperation, 21 | TestOperationSet, 22 | TestOperationFailed, 23 | TestOperationNotSupported 24 | ) 25 | 26 | from .revcrc32 import TestReverseCRC32 27 | 28 | # TODO: Implement tests for the rest of this submodule 29 | from .string import TestXxd 30 | 31 | 32 | # TODO: Implement tests for the rest of this subpackage: 33 | # board, cmd_table, jump_table 34 | from .uboot import env 35 | -------------------------------------------------------------------------------- /python/tests/unit/checker/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa=F401 2 | # pylint: disable=missing-module-docstring 3 | 4 | from .builtins import TestImportBuiltins 5 | from .report import TestReport 6 | from .security_risk import TestSecurityRisk 7 | from .uboot_config import TestUBootConfigChecker 8 | from .uboot_header import TestUBootHeaderChecker 9 | -------------------------------------------------------------------------------- /python/tests/unit/checker/builtins.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | # 4 | # pylint: disable=missing-function-docstring, missing-module-docstring 5 | 6 | import os 7 | from tempfile import gettempdir 8 | from unittest import TestCase 9 | 10 | from depthcharge.checker import Report 11 | from depthcharge.checker._builtins import _BUILTIN_DEFS 12 | 13 | 14 | class TestImportBuiltins(TestCase): 15 | """ 16 | Simple test to ensure that all builtin checkers aren't woefully broken. 17 | Doubles as a way to export a report inclusive of everything contained in 18 | depthcharge.checker._builtins 19 | """ 20 | 21 | @classmethod 22 | def setUpClass(cls): 23 | cls.keep_files = os.getenv('DEPTHCHARGE_TEST_KEEP_FILES', '') != '' 24 | cls.pfx = os.path.join(gettempdir(), 'depthcharge_builtins_test.') 25 | 26 | cls.builtin_risks = [] 27 | for builtin in _BUILTIN_DEFS: 28 | cls.builtin_risks.append(builtin[2]) 29 | 30 | def _create_report(self, ext): 31 | report = Report() 32 | for risk in self.builtin_risks: 33 | risk['source'] = 'test_html.' + ext 34 | report.add(risk) 35 | return report 36 | 37 | def test_csv(self): 38 | filename = self.pfx + 'csv' 39 | report = self._create_report('csv') 40 | report.save_csv(filename) 41 | 42 | if not self.keep_files: 43 | os.remove(filename) 44 | 45 | def test_html(self): 46 | filename = self.pfx + 'html' 47 | report = self._create_report('html') 48 | report.save_html(filename) 49 | 50 | if not self.keep_files: 51 | os.remove(filename) 52 | 53 | def test_markdown(self): 54 | filename = self.pfx + 'md' 55 | report = self._create_report('md') 56 | report.save_markdown(filename) 57 | 58 | if not self.keep_files: 59 | os.remove(filename) 60 | -------------------------------------------------------------------------------- /python/tests/unit/checker/security_risk.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | # 4 | # pylint: disable=missing-function-docstring,missing-module-docstring 5 | 6 | from unittest import TestCase 7 | 8 | from depthcharge.checker import SecurityRisk, SecurityImpact 9 | 10 | 11 | class TestSecurityRisk(TestCase): 12 | """ 13 | Unit tests for depthcharge.checker.security_risk 14 | """ 15 | 16 | def test_construtor_and_properties(self): 17 | impact = SecurityImpact.RD_MEM | SecurityImpact.WR_MEM 18 | 19 | risk = SecurityRisk(identifier='ident', 20 | summary='summary', 21 | impact=impact, 22 | source='src', 23 | description='description', 24 | recommendation='recommendation') 25 | 26 | self.assertEqual(risk.identifier, 'ident') 27 | self.assertEqual(risk.impact, impact) 28 | self.assertEqual(risk.source, 'src') 29 | self.assertEqual(risk.description, 'description') 30 | self.assertEqual(risk.recommendation, 'recommendation') 31 | 32 | def test_from_dict(self): 33 | sr_dict = { 34 | 'identifier': 'test-ident', 35 | 'summary': 'test-summary', 36 | 'impact': SecurityImpact.WEAK_AUTH, 37 | 'source': 'test-src', 38 | 'description': 'test-desc', 39 | 'recommendation': 'test-rec' 40 | } 41 | 42 | risk = SecurityRisk.from_dict(sr_dict) 43 | risk.source += 'x' # Writable property 44 | 45 | self.assertEqual(risk.identifier, 'test-ident') 46 | self.assertEqual(risk.summary, 'test-summary') 47 | self.assertEqual(risk.impact, SecurityImpact.WEAK_AUTH) 48 | self.assertEqual(risk.source, 'test-srcx') 49 | self.assertEqual(risk.description, 'test-desc') 50 | self.assertEqual(risk.recommendation, 'test-rec') 51 | -------------------------------------------------------------------------------- /python/tests/unit/checker/uboot_header.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | # 4 | # pylint: disable=missing-function-docstring, missing-module-docstring, missing-class-docstring 5 | # pylint: disable=attribute-defined-outside-init 6 | 7 | from os.path import dirname, join, realpath 8 | from unittest import TestCase 9 | 10 | from depthcharge.checker import UBootHeaderChecker, SecurityRisk, SecurityImpact 11 | 12 | 13 | class TestUBootHeaderChecker(TestCase): 14 | """ 15 | Largely just exercises UBootHeaderChecker's constructor and load() method. 16 | The audit() implementation is common to that of UBootConfigChecker. 17 | """ 18 | 19 | custom_risk = SecurityRisk('CUSTOM01', 20 | SecurityImpact.RD_MEM | SecurityImpact.INFO_LEAK, 21 | 'test.src', 22 | 'summary-custom01', 23 | 'description-custom01', 24 | 'recommendation-custom01') 25 | 26 | resource_dir = realpath(join(dirname(__file__), '..', '..', 'resources')) 27 | 28 | @classmethod 29 | def resource(cls, filename: str): 30 | return realpath(join(cls.resource_dir, filename)) 31 | 32 | def test_simple(self): 33 | header = self.resource('config_01.h') 34 | 35 | checker = UBootHeaderChecker('2011.13', self.resource_dir) 36 | config = checker.load(header) 37 | 38 | # Defined in specified header 39 | self.assertTrue('CONFIG_CMD_MEMORY' in config) 40 | self.assertTrue(config['CONFIG_CMD_MEMORY'][0]) 41 | 42 | # Defined in #include'd header 43 | self.assertTrue('CONFIG_CMD_LOADS' in config) 44 | self.assertTrue(config['CONFIG_CMD_LOADS'][0]) 45 | 46 | # Defined in #include'd header and then undef'd 47 | self.assertTrue('CONFIG_CMD_I2C' in config) 48 | self.assertFalse(config['CONFIG_CMD_I2C'][0]) 49 | 50 | report = checker.audit() 51 | self.assertTrue('CONFIG_CMD_LOADS' in report) 52 | self.assertTrue('CONFIG_CMD_MEMORY' in report) 53 | self.assertFalse('CONFIG_CMD_I2C' in report) 54 | 55 | def test_dummy(self): 56 | header = self.resource('config_02.h') 57 | 58 | with self.subTest('Confim failure'): 59 | with self.assertRaises(ValueError): 60 | checker = UBootHeaderChecker('2011.13', self.resource_dir) 61 | _ = checker.load(header) 62 | 63 | with self.subTest('Confim dummy_headers works'): 64 | checker = UBootHeaderChecker('2011.13', self.resource_dir, dummy_headers=['test/induce_error.h']) 65 | _ = checker.load(header) 66 | 67 | with self.subTest('dummy_headers as string'): 68 | checker = UBootHeaderChecker('2011.13', self.resource_dir, dummy_headers='test/induce_error.h') 69 | _ = checker.load(header) 70 | 71 | def test_config(self): 72 | header = self.resource('config_03.h') 73 | config_in = { 74 | 'INCLUDE_MEMORY_COMMANDS': (True, 'some.source'), 75 | 'AN_INTEGER': (3, 'some.other.source'), 76 | 'A_STR': ('Some string', 'src3'), 77 | } 78 | 79 | checker = UBootHeaderChecker('2011.13', self.resource_dir, config_defs=config_in) 80 | config_out = checker.load(header) 81 | 82 | self.assertTrue('INCLUDE_MEMORY_COMMANDS' in config_out) 83 | self.assertTrue('CONFIG_CMD_MEMORY' in config_out) 84 | self.assertTrue(config_out['CONFIG_CMD_MEMORY'][0]) 85 | -------------------------------------------------------------------------------- /python/tests/unit/hunter/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa=F401 2 | # pylint: disable=missing-module-docstring 3 | from .constant import TestConstantHunter 4 | from .cp import TestCpHunter 5 | from .env import TestEnvironmentHunter 6 | from .fdt import TestFDTHunter 7 | from .hunter import TestGappedRangeIter, TestSplitDataOffsets 8 | from .string import TestStringHunter 9 | from .revcrc32 import TestReverseCRC32Hunter 10 | -------------------------------------------------------------------------------- /python/tests/unit/hunter/fdt.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | # 4 | # pylint: disable=missing-function-docstring, missing-class-docstring 5 | 6 | """ 7 | Unit tests for depthcharge.hunter.FDTHunter 8 | """ 9 | 10 | import os 11 | import subprocess 12 | import shutil 13 | import tempfile 14 | from unittest import TestCase, skipIf 15 | 16 | from depthcharge.hunter import FDTHunter, HunterResultNotFound 17 | 18 | from ..test_utils import random_data 19 | 20 | 21 | # flake8: noqa=W191 22 | _DTS = \ 23 | """ 24 | /dts-v1/; 25 | 26 | / { 27 | #address-cells = <1>; 28 | #size-cells = <1>; 29 | 30 | bus@ff784000 { 31 | #address-cells = <1>; 32 | #size-cells = <1>; 33 | compatible = "depthcharge-bus", "simple-bus"; 34 | ranges = <0x0 0x10000000 0x10000>; 35 | 36 | node@d00dfeed { 37 | reg = <0xd00dfeed 1>; 38 | }; 39 | }; 40 | 41 | }; 42 | """ 43 | 44 | _DTC = shutil.which('dtc') 45 | 46 | @skipIf(_DTC is None, 'Test requires that "dtc" is installed') 47 | class TestFDTHunter(TestCase): 48 | 49 | @classmethod 50 | def setUpClass(cls): 51 | cls.dts = _DTS 52 | with tempfile.NamedTemporaryFile(delete=False, mode='w') as outfile: 53 | outfile.write(_DTS) 54 | dts_filename = outfile.name 55 | 56 | args = [_DTC, '-q', '-I', 'dts', '-O', 'dtb', dts_filename] 57 | sub = subprocess.run(args, check=True, capture_output=True) 58 | cls.dtb = sub.stdout 59 | os.remove(dts_filename) 60 | 61 | with tempfile.NamedTemporaryFile(delete=False, mode='wb') as outfile: 62 | outfile.write(cls.dtb) 63 | dtb_filename = outfile.name 64 | 65 | args = [_DTC, '-q', '-I', 'dtb', '-O', 'dts', dtb_filename] 66 | sub = subprocess.run(args, check=True, capture_output=True, text=True) 67 | cls.dts_expected = sub.stdout 68 | os.remove(dtb_filename) 69 | 70 | def test_find(self): 71 | base = 0x8000 72 | locs = (200, 1724, 3141) 73 | 74 | blob = random_data(4096) 75 | dtb_len = len(self.dtb) 76 | 77 | for loc in locs: 78 | blob[loc:loc + dtb_len] = self.dtb 79 | 80 | hunter = FDTHunter(blob, base) 81 | 82 | result = hunter.find(None) 83 | self.assertTrue(result is not None) 84 | self.assertEqual(result['src_off'], locs[0]) 85 | self.assertEqual(result['src_addr'], base + locs[0]) 86 | self.assertEqual(result['src_size'], dtb_len) 87 | self.assertEqual(result['dtb'], self.dtb) 88 | self.assertEqual(result['dts'], self.dts_expected) 89 | 90 | with self.assertRaises(HunterResultNotFound): 91 | _ = hunter.find('NotInThisDTS') 92 | 93 | def test_finditer(self): 94 | base = 0x8000 95 | locs = (200, 1724, 3141) 96 | 97 | blob = random_data(4096) 98 | dtb_len = len(self.dtb) 99 | 100 | for loc in locs: 101 | blob[loc:loc + dtb_len] = self.dtb 102 | 103 | i = 0 104 | hunter = FDTHunter(blob, base) 105 | for result in hunter.finditer(None): 106 | self.assertTrue(result is not None) 107 | self.assertEqual(result['src_off'], locs[i]) 108 | self.assertEqual(result['src_addr'], base + locs[i]) 109 | self.assertEqual(result['src_size'], dtb_len) 110 | self.assertEqual(result['dtb'], self.dtb) 111 | self.assertEqual(result['dts'], self.dts_expected) 112 | 113 | i += 1 114 | 115 | with self.assertRaises(HunterResultNotFound): 116 | _ = hunter.find('NotInThisDTS') 117 | -------------------------------------------------------------------------------- /python/tests/unit/revcrc32.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | # 4 | # pylint: disable=missing-function-docstring, missing-class-docstring 5 | 6 | """ 7 | Unit test for depthcharge.revcrc32.revercse_crc32_4bytes() 8 | """ 9 | 10 | import random 11 | import sys 12 | 13 | from zlib import crc32 14 | from unittest import TestCase 15 | 16 | from depthcharge.revcrc32 import reverse_crc32_4bytes 17 | 18 | 19 | class TestReverseCRC32(TestCase): 20 | 21 | def test_rev32(self): 22 | random.seed(0) 23 | for _ in range(0, 64): 24 | expected_data = random.getrandbits(32).to_bytes(4, sys.byteorder) 25 | with self.subTest(expected_data.hex()): 26 | result = crc32(expected_data) 27 | reversed_data = reverse_crc32_4bytes(result).to_bytes(4, sys.byteorder) 28 | self.assertEqual(expected_data, reversed_data) 29 | -------------------------------------------------------------------------------- /python/tests/unit/stratagem.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | # 4 | # pylint: disable=missing-function-docstring, missing-class-docstring 5 | 6 | """ 7 | Unit tests for depthcharge.Stratagem 8 | """ 9 | 10 | from copy import copy 11 | from unittest import TestCase 12 | 13 | from depthcharge import Stratagem 14 | 15 | 16 | class DummyStratagemOp: 17 | _stratagem = None 18 | _spec = None 19 | 20 | @classmethod 21 | def get_stratagem_spec(cls): 22 | return cls._spec 23 | 24 | 25 | class TestStratagem(TestCase): 26 | 27 | def test_constructor(self): 28 | with self.subTest('Valid usage'): 29 | DummyStratagemOp._spec = {'foo': int, 'bar': bool, 'baz': str} 30 | _ = Stratagem(DummyStratagemOp) 31 | 32 | def test_entries(self): 33 | expected = [] 34 | 35 | DummyStratagemOp._spec = {'foo': int, 'bar': bool, 'baz': str} 36 | s = Stratagem(DummyStratagemOp) 37 | self.assertEqual(len(s), 0) 38 | 39 | with self.subTest('Successful append via dict'): 40 | d = {'foo': 7, 'bar': True, 'baz': 'Test1'} 41 | expected.append(d) 42 | s.append(d) 43 | self.assertEqual(len(s), 1) 44 | 45 | with self.subTest('Successful append via kwargs'): 46 | s.append(foo=42, bar=False, baz='Test2') 47 | expected.append({'foo': 42, 'bar': False, 'baz': 'Test2'}) 48 | self.assertEqual(len(s), 2) 49 | 50 | with self.subTest('Successful append via hybrid'): 51 | # The kwargs are overrides 52 | d = {'foo': -1, 'bar': False, 'baz': 'Replaced'} 53 | 54 | s.append(d, foo=1337, baz='Test3') 55 | self.assertEqual(len(s), 3) 56 | 57 | d['foo'] = 1337 58 | d['baz'] = 'Test3' 59 | expected.append(copy(d)) 60 | 61 | # Confirm that touching d doesn't change the Stratagem's copy. 62 | d['foo'] = None 63 | d['bar'] = None 64 | d['baz'] = None 65 | 66 | with self.subTest('Verify entries'): 67 | i = 0 68 | for e in s.entries(): 69 | self.assertEqual(e, expected[i]) 70 | self.assertEqual(s[i], expected[i]) 71 | i += 1 72 | 73 | with self.subTest('Key not present in spec'): 74 | with self.assertRaises(KeyError): 75 | s.append(foo=999, bar=False, baz='Test4', extraneous=1) 76 | 77 | with self.subTest('Invalid type test'): 78 | # Valid cast 79 | s.append(foo=999, bar=1, baz='Test5') 80 | 81 | with self.assertRaises(ValueError): 82 | s.append(foo='one', bar=True, baz='Test5') 83 | 84 | def test_str(self): 85 | DummyStratagemOp._spec = {'foo': int, 'bar': bool, 'baz': str} 86 | s = Stratagem(DummyStratagemOp) 87 | s.append(foo=7, bar=False, baz='Test!') 88 | 89 | str_val = str(s) 90 | self.assertEqual(type(str_val), str) 91 | -------------------------------------------------------------------------------- /python/tests/unit/string.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | # 4 | # Relax style and documentation requirements for unit tests. 5 | # pylint: disable=missing-function-docstring,missing-class-docstring,too-few-public-methods 6 | # 7 | 8 | """ 9 | Unit tests for depthcharge.string 10 | """ 11 | 12 | import os 13 | import random 14 | import tempfile 15 | 16 | from unittest import TestCase 17 | 18 | # TODO: Add test cases for other conversion fns 19 | from depthcharge.string import xxd, xxd_reverse, xxd_reverse_file 20 | 21 | 22 | class TestXxd(TestCase): 23 | 24 | def test_random(self): 25 | """ 26 | Test forward and reverse conversion of random data. 27 | """ 28 | 29 | address = 0x87f0_0000 30 | data = bytearray() 31 | 32 | random.seed(0) 33 | for _ in range(0, 4095): 34 | data.append(random.randint(0, 255)) 35 | 36 | dump = xxd(address, data) 37 | rev_address, rev_data = xxd_reverse(dump) 38 | 39 | self.assertEqual(rev_address, address) 40 | self.assertEqual(rev_data, data) 41 | 42 | with tempfile.NamedTemporaryFile(mode='w', delete=False) as outfile: 43 | tempname = outfile.name 44 | outfile.write(dump) 45 | 46 | rev_address, rev_data = xxd_reverse_file(tempname) 47 | 48 | self.assertEqual(rev_address, address) 49 | self.assertEqual(rev_data, data) 50 | 51 | os.remove(tempname) 52 | 53 | def test_whitespace(self): 54 | """ 55 | Confirm that lines containing only 0x20 are parsed. 56 | This previously failed due to a strip(). 57 | 58 | Doesn't exercise xxd_reverse_file. 59 | """ 60 | address = 13 61 | data = b'\x20' * 57 62 | 63 | dump = xxd(address, data) 64 | rev_address, rev_data = xxd_reverse(dump) 65 | 66 | self.assertEqual(address, rev_address) 67 | self.assertEqual(data, rev_data) 68 | -------------------------------------------------------------------------------- /python/tests/unit/test_utils.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | """ 3 | Miscelaneous utility functions for unit tests. 4 | """ 5 | import hashlib 6 | import random 7 | import sys 8 | 9 | 10 | def random_data(size: int, seed=0, ret_bytes=False): 11 | """ 12 | Return `size` pseudorandom bytes from the random module, seeded by `seed`. 13 | 14 | By default, a `bytearray` is returned. If `ret_bytes=True`, 15 | `bytes` are returned. 16 | """ 17 | random.seed(seed) 18 | ret = random.getrandbits(size * 8).to_bytes(size, sys.byteorder) 19 | if ret_bytes: 20 | return ret 21 | 22 | return bytearray(ret) 23 | 24 | 25 | def verify_md5sum(filename: str, expected: str, test_case): 26 | """ 27 | Invokes ``test_case.assertEqual()`` with loaded file's checksum 28 | and the expected value (as hex strings), in that order. 29 | """ 30 | with open(filename, 'rb') as infile: 31 | data = infile.read() 32 | md5sum = hashlib.new('md5') 33 | md5sum.update(data) 34 | test_case.assertEqual(md5sum.hexdigest(), expected) 35 | -------------------------------------------------------------------------------- /python/tests/unit/uboot/__init__.py: -------------------------------------------------------------------------------- 1 | from .board import TestUbootBoardFns 2 | from .version import TestUbootVersion 3 | -------------------------------------------------------------------------------- /python/tests/unit/uboot/board.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | # 4 | 5 | """ 6 | Unit tests for depthcharge.uboot.board 7 | """ 8 | 9 | from unittest import TestCase 10 | from textwrap import dedent, indent 11 | 12 | from depthcharge.uboot.board import bdinfo_dict, bdinfo_str 13 | 14 | class TestUbootBoardFns(TestCase): 15 | 16 | _exp_dict = { 17 | 'arch_number': { 18 | 'name': 'arch_number', 19 | 'value': 0, 20 | 'suffix': '' 21 | }, 22 | 23 | 'boot_params': { 24 | 'name': 'boot_params', 25 | 'value': 0x100, 26 | 'suffix': '', 27 | }, 28 | 29 | 'baudrate': { 30 | 'name': 'baudrate', 31 | 'value': 115200, 32 | 'suffix': 'bps', 33 | }, 34 | 35 | 'dram_bank': { 36 | 'name': 'DRAM bank(s)', 37 | 'suffix': '', 38 | 'value': { 39 | 0: { 40 | 'start': 0x00000000, 41 | 'size': 0x3b400000, 42 | }, 43 | 44 | 1: { 45 | 'start': 0x80000000, 46 | 'size': 0x400000, 47 | }, 48 | }, 49 | }, 50 | 51 | 'fbbase': { 52 | 'name': 'FB base', 53 | 'value': 0x00000000, 54 | 'suffix': '', 55 | }, 56 | 57 | 'fdt_blob': { 58 | 'name': 'fdt_blob', 59 | 'value': 0x3b3cd320, 60 | 'suffix': '' 61 | }, 62 | 63 | 'irq_sp': { 64 | 'name': 'irq_sp', 65 | 'value': 0x3af66ec0, 66 | 'suffix': '', 67 | }, 68 | 69 | 'relocaddr': { 70 | 'name': 'relocaddr', 71 | 'value': 0x3b36b000, 72 | 'suffix': '', 73 | }, 74 | 75 | 'relocoff': { 76 | 'name': 'reloc off', 77 | 'value': 0x3b363000, 78 | 'suffix': '', 79 | }, 80 | 81 | 'spstart': { 82 | 'name': 'sp start', 83 | 'value': 0x3af66eb0, 84 | 'suffix': '', 85 | }, 86 | 87 | 'tlbaddr': { 88 | 'name': 'TLB addr', 89 | 'value': 0x3b3f0000, 90 | 'suffix': '', 91 | }, 92 | } 93 | 94 | _exp_str = indent(dedent("""\ 95 | arch_number 0 96 | baudrate 115200 bps 97 | boot_params 0x00000100 98 | DRAM Bank #0 start=0x00000000, size=0x3b400000 99 | DRAM Bank #1 start=0x80000000, size=0x00400000 100 | FB base 0x00000000 101 | fdt_blob 0x3b3cd320 102 | irq_sp 0x3af66ec0 103 | relocaddr 0x3b36b000 104 | reloc off 0x3b363000 105 | sp start 0x3af66eb0 106 | TLB addr 0x3b3f0000"""), ' ') 107 | 108 | _s = dedent(""" 109 | arch_number = 0x00000000 110 | boot_params = 0x00000100 111 | DRAM bank = 0x00000000 112 | -> start = 0x00000000 113 | -> size = 0x3b400000 114 | DRAM bank = 0x00000001 115 | -> start = 0x80000000 116 | -> size = 0x00400000 117 | baudrate = 115200 bps 118 | TLB addr = 0x3b3f0000 119 | relocaddr = 0x3b36b000 120 | reloc off = 0x3b363000 121 | irq_sp = 0x3af66ec0 122 | sp start = 0x3af66eb0 123 | FB base = 0x00000000 124 | Early malloc usage: 344 / 2000 125 | fdt_blob = 0x3b3cd320 126 | """) 127 | 128 | def test_bdinfo_dict(self): 129 | d = bdinfo_dict(self._s) 130 | self.assertEqual(d, self._exp_dict) 131 | 132 | def test_bdinfo_str(self): 133 | d = bdinfo_dict(self._s) 134 | outstr = bdinfo_str(d) 135 | self.assertEqual(outstr, self._exp_str) 136 | -------------------------------------------------------------------------------- /python/tests/unit/uboot/env.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Depthcharge: 3 | # 4 | # Relax style and documentation requirements for unit tests. 5 | # pylint: disable=missing-function-docstring, missing-class-docstring 6 | # pylint: disable=wildcard-import, line-too-long 7 | # 8 | 9 | """ 10 | Unit tests for depthcharge.uboot.env 11 | """ 12 | 13 | import os 14 | 15 | from unittest import TestCase 16 | from depthcharge import uboot 17 | 18 | _ENV_DICT = { 19 | 'addip': 'setenv bootargs ${bootargs} ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}:${hostname}:eth0:off', 20 | 'hostname': 'Philbert', 21 | 'ipaddr': '192.168.0.200', 22 | 'serverip': '192.168.0.10', 23 | 'gatewayip': '192.168.0.1', 24 | 'netmask': '255.255.255.0', 25 | 'boot_dtb': '${loadaddr} - ${dtb_addr}', 26 | 'loadaddr': '0x82000000', 27 | 'dtb_addr': '0x83000000', 28 | 'dtb_size': '0x20000', 29 | } 30 | 31 | _ENV_DICT_EXP = { 32 | 'addip': 'setenv bootargs ${bootargs} ip=192.168.0.200:192.168.0.10:192.168.0.1:255.255.255.0:Philbert:eth0:off', 33 | 'boot_dtb': '0x82000000 - 0x83000000', 34 | 'dtb_addr': '0x83000000', 35 | 'dtb_size': '0x20000', 36 | 'gatewayip': '192.168.0.1', 37 | 'hostname': 'Philbert', 38 | 'ipaddr': '192.168.0.200', 39 | 'loadaddr': '0x82000000', 40 | 'netmask': '255.255.255.0', 41 | 'serverip': '192.168.0.10' 42 | } 43 | 44 | _ENV_TEXT = """\ 45 | addip=setenv bootargs ${bootargs} ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}:${hostname}:eth0:off 46 | boot_dtb=${loadaddr} - ${dtb_addr} 47 | dtb_addr=0x83000000 48 | dtb_size=0x20000 49 | gatewayip=192.168.0.1 50 | hostname=Philbert 51 | ipaddr=192.168.0.200 52 | loadaddr=0x82000000 53 | netmask=255.255.255.0 54 | serverip=192.168.0.10 55 | """ 56 | 57 | _ENV_TEXT_EXP = """\ 58 | addip=setenv bootargs ${bootargs} ip=192.168.0.200:192.168.0.10:192.168.0.1:255.255.255.0:Philbert:eth0:off 59 | boot_dtb=0x82000000 - 0x83000000 60 | dtb_addr=0x83000000 61 | dtb_size=0x20000 62 | gatewayip=192.168.0.1 63 | hostname=Philbert 64 | ipaddr=192.168.0.200 65 | loadaddr=0x82000000 66 | netmask=255.255.255.0 67 | serverip=192.168.0.10 68 | """ 69 | 70 | 71 | class TestUbootEnvFns(TestCase): 72 | """ 73 | Test depthcharge.uboot.env functions focused on environment data. 74 | """ 75 | 76 | def test_parse(self): 77 | env = uboot.env.parse(_ENV_TEXT) 78 | self.assertEqual(env, _ENV_DICT) 79 | 80 | def test_expand(self): 81 | env = uboot.env.expand(_ENV_DICT) 82 | self.assertEqual(env, _ENV_DICT_EXP) 83 | 84 | def test_save_load(self): 85 | filename = 'depthcharge.uboot.env_save_load.test' 86 | uboot.env.save(filename, _ENV_DICT) 87 | env = uboot.load(filename) 88 | self.assertEqual(env, _ENV_DICT) 89 | os.remove(filename) 90 | 91 | def test_raw_save_load(self): 92 | arch = 'arm' 93 | flags = 0xf 94 | size = 0x2000 95 | 96 | filename = 'depthcharge.uboot.raw_env_save_load.test' 97 | 98 | with self.subTest('No header'): 99 | uboot.save_raw_environment(filename, _ENV_DICT, size, arch, no_header=True) 100 | env, metadata = uboot.env.load_raw(filename, arch, has_crc=False) 101 | 102 | self.assertEqual(env, _ENV_DICT) 103 | self.assertEqual(metadata['size'], size) 104 | 105 | with self.subTest('CRC, no flags'): 106 | uboot.save_raw_environment(filename, _ENV_DICT, size, arch) 107 | env, metadata = uboot.env.load_raw(filename, arch) 108 | 109 | self.assertEqual(env, _ENV_DICT) 110 | self.assertEqual(metadata['size'], size - 4) 111 | self.assertEqual(metadata['crc'], metadata['actual_crc']) 112 | 113 | with self.subTest('CRC + flags'): 114 | uboot.save_raw_environment(filename, _ENV_DICT, size, arch, flags=flags) 115 | env, metadata = uboot.env.load_raw(filename, arch, has_flags=True) 116 | 117 | self.assertEqual(env, _ENV_DICT) 118 | self.assertEqual(metadata['size'], size - 5) 119 | self.assertEqual(metadata['crc'], metadata['actual_crc']) 120 | self.assertEqual(metadata['flags'], flags) 121 | 122 | os.remove(filename) 123 | --------------------------------------------------------------------------------