device update

This commit is contained in:
Eljakim Herrewijnen 2024-11-01 13:48:07 +01:00
parent 217abe3bc6
commit 1ab8b08a18
13 changed files with 218 additions and 49 deletions

View File

@ -1,5 +1,5 @@
# Gupje
Gupje is a bare metal architecture based stub debugger that helps in post-exploitation steps. Like booting a device after RCE has been achieved. Gupje is also capable of doing hardware-in-the-middle approaches as well as keeping control over a device while it is booting(Hijack trustzone etc.).
Gupje is a bare metal architecture based stub debugger that helps in ``post-exploitation`` steps. Like booting a smartphone after RCE has been achieved. Gupje is also capable of doing ``hardware-in-the-middle`` approaches as well as keeping control over a device while it is booting(Hijack trustzone etc.).
Gupje currently supporting the following architectures:
* ARM64 (good support)
@ -10,7 +10,7 @@ The goal is to add support to more targets while I work on them. Because I mainl
The only actual things the debugger can do is:
* Send/Receive (needs to be implemented by the user)
* Read/Write registers
* Read/Write *some* registers
These functions are enough for a processor to run properly. The code size of the debugger is currently smaller than 4096 bytes(depends a bit on user setup) but will probably grow to include 1 extra page to support custom functionality. Like dumping extra registers and more specific VBAR setups.
@ -21,11 +21,13 @@ A simple overview of how Gupje is meant to be used can be seen below:
![Simple Gupje Target](simple_device.drawio.svg)
The user is responsible for gaining RCE and setting up Gupje. After this Gupje along with the ``Ghidra-Assistant`` can be used to interact with the device from within python and continue booting.
The user is responsible for gaining RCE and setting up Gupje. After this Gupje, along with the ``Ghidra-Assistant``, can be used to interact with the device from within python and continue booting(or extract Crypte Engine keys or whatever).
### Gupje Setup
Because Gupje attempts to be a architecture based debugger the user only has to provide send/receive functionality to Gupje. Like sahara_tx/sahara_rx in Qualcomm based devices or a raw USB endpoint for the nvidia shield tablet.
Example C code for running gupje:
```c
void send(void *buffer, uint32_t size, uint32_t *num_xfer){
@ -67,10 +69,21 @@ void concrete_main(uint32_t debugger){
}
```
#### Building
Download an Android NDK and set it's root path:
```bash
$ export ANDROID_NDK_ROOT=$TOOLCHAINENV/android-ndk-r21_Linux
```
Now you can build one of your targets:
```bash
$ make -f devices/rpi4/Makefile
```
Other protocols, like UART, are also possible. I will try to add more reference devices and implementations.
### Memory layout
Overall 4 pages are always reserved for the debugger. I usually try to place them at the end of a memory region since the chance of the pages being corrupted/used by other functions is smaller. 3 pages are required at least for the debugger to function properly.
Overall 4 pages are always reserved for the debugger. I usually try to place them at the end of a memory region since the chance of these pages being corrupted/used by other functions is smaller. 3 pages are required at least for the debugger to function properly.
![debugger memory layout](simple_device_memory.drawio.svg)
@ -79,11 +92,19 @@ The full documentation is covered in the ``documentation`` folder. To build it,
```bash
make livehtml
```
Install the python dependencies if they are missing. (TODO add requirements)
Install the python dependencies if they are missing.
This code works in combination with the ``Ghidra Assistant``, which is another personal project to make Ghidra more instrumentable.
## Example devices
Several example devices are under development to show what the Gupje is capable off:
* Nvidia Shield Tablet(boot bricked device)
* Nintendo Switch to add?
* Samsung S7 (boot and hijack trustzone)
* Raspberry Pi4(UART)
## TODO
* ARM assembly needs to be completely rewritten
* Add code that allows the host to easily write and execute shellcode on the device. This will significantly decrease the size of the debugger. (extra page required)
* Add a more *minimal* approach to the debugger. That does not store data but can just be used to read/write memory. Usefull for exploitation when there is a very limited constraint on shellcode size.
@ -91,19 +112,9 @@ This code works in combination with the ``Ghidra Assistant``, which is another p
* Build an emulator to explain the debugger
### ARM64
* Allow restoring all registers by writing X15 to SP and jump to ELRn to create a *full* restored state. Figure out a way to branch without corrupting X15.
* Allow restoring all registers by writing X15 to SP and jump to ELRn to create a *full* restored state. Figure out a way to branch without corrupting X15. (ELR?)
### Thumb
* headless mode is not supported
* Figure out VBAR to implement single step debugging
## Building
Download an Android NDK and set it's root path:
```bash
$ export ANDROID_NDK_ROOT=$TOOLCHAINENV/android-ndk-r21_Linux
```
Now you can build one of your targets:
```bash
$ make -f devices/pixel3a/Makefile
```

View File

@ -9,6 +9,14 @@
// extern int mystrlen(char *data);
// extern void usb_log(char * msg, uint32_t * error);
#if INTPTR_MAX == INT32_MAX
#define BIT32
#elif INTPTR_MAX == INT64_MAX
#define BIT64
#else
#error "Environment not 32 or 64-bit."
#endif
#ifdef __aarch64__
#include "debugger_archs/ga_arm64.h"
#elif __arm__
@ -16,7 +24,7 @@
#elif __thumb__
#include "debugger_archs/ga_arm_thumb.h"
#else
#include "debugger_archs/ga_arm64.h"
#error "Unsupported architecture"
#endif
#ifdef GLITCH_ENABLE
@ -31,13 +39,18 @@ extern void debugger_sync_special_regs();
extern void restore_and_return();
extern int debugger_storage;
// uint64_t debugger_storage_values[] = &debugger_storage;
// Custom block sizes should be somewhat supported.
#ifdef GUPJE_BLOCK_SIZE
#else
#define GUPJE_BLOCK_SIZE 0x100
#endif
__attribute__((section(".init")))
int debugger_main(void){
#ifdef DEVICE_SETUP
#ifdef __aarch64__
#ifdef BIT64
uint64_t *val = (uint64_t *)((uint64_t)debugger_storage);
// 0xfc0
@ -52,7 +65,7 @@ int debugger_main(void){
// TODO other architectures
#endif
#ifdef __aarch64__
#ifdef BIT64
uint64_t mem_off;
#else
uint32_t mem_off;
@ -66,14 +79,15 @@ int debugger_main(void){
while(1){
recv_data(&data, 4);
// PING, PEEK, HWIO, POKE, SELF, MAIN, FLUSH, JUMP, SYNC, SYNS, SPEC, ERET, REST, RET, TEST
if(data[0] == 'P' && data[1] == 'I' && data[2] == 'N' && data[3] == 'G'){
data[1] = 'O';
send(&data, 4, &tx_err_code);
}
else if(data[0] == 'P' && data[1] == 'E' && data[2] == 'E' && data[3] == 'K') {
// peek, dump memory
// PEEK, dump memory
recv_data(&data, 12); // Receive uint64_t size and and uint32_t offset
#ifdef __aarch64__
#ifdef BIT64
mem_off = *(uint64_t *)data;
mem_sz = *(uint32_t *)(data+8);
#else
@ -109,7 +123,7 @@ int debugger_main(void){
usb_log("OK", &tx_err_code);
}
else if(data[0] == 'P' && data[1] == 'O' && data[2] == 'K' && data[3] == 'E') {
#ifdef __aarch64__
#ifdef BIT64
recv_data(&data, 12); // Receive uint64_t size and and uint32_t offset
mem_off = *(uint64_t *)data;
mem_sz = *(uint32_t *)(data+8);
@ -136,7 +150,7 @@ int debugger_main(void){
}
}
else if(data[0] == 'S' && data[1] == 'E' && data[2] == 'L' && data[3] == 'F') {
#ifdef __aarch64__
#ifdef BIT64
mem_off = (uint64_t) &debugger_main;
#else
mem_off = (uint32_t) &debugger_main;
@ -144,7 +158,7 @@ int debugger_main(void){
send(&mem_off, sizeof(mem_off), &tx_err_code);
}
else if(data[0] == 'M' && data[1] == 'A' && data[2] == 'I' && data[3] == 'N') {
#ifdef __aarch64__
#ifdef BIT64
mem_off = (uint64_t) &debugger_main;
#else
mem_off = (uint32_t) &debugger_main;
@ -152,8 +166,8 @@ int debugger_main(void){
concrete_main(mem_off);
}
else if(data[0] == 'F' && data[1] == 'L' && data[2] == 'S' && data[3] == 'H') {
//FLSH cache flush
#ifdef __aarch64__
// TODO, flush specific cache (code/data)
#ifdef BIT64
cache_flush();
#else
// Todo for ARM and Thumb
@ -161,7 +175,7 @@ int debugger_main(void){
}
else if(data[0] == 'J' && data[1] == 'U' && data[2] == 'M' && data[3] == 'P') {
//JUMP == jump to function using provided pointer
#ifdef __aarch64__
#ifdef BIT64
recv_data(&data, 8);
mem_off = *(uint64_t *)data;
void (*custom_func)() = (void*)mem_off; //mem_off;

View File

@ -14,7 +14,7 @@ author = 'Eljakim'
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [ 'myst_parser',
'sphinx_rtd_theme',
'sphinx_wagtail_theme',
'sphinx.ext.todo',
'sphinxcontrib.confluencebuilder',
"sphinxcontrib.drawio",
@ -28,5 +28,5 @@ exclude_patterns = []
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = 'sphinx_rtd_theme'
html_theme = 'sphinx_wagtail_theme'
html_static_path = ['_static']

View File

@ -0,0 +1,32 @@
===============
Getting Started
===============
Clone gupje somewhere on your system:
.. code-block:: bash
git clone https://git.herreweb.nl/EljakimHerrewijnen/Gupje
Cross compiler
--------------
You will also need a cross compiler for the target you are going to debug.
For this we use the ``Android NDK``. Download one from the `official website <https://developer.android.com/ndk>`_ and extract it somewhere on your system.
.. code-block:: bash
wget https://dl.google.com/android/repository/android-ndk-r21e-linux-x86_64.zip
unzip android-ndk-r21e-linux-x86_64.zip
Now set the ``ANDROID_NDK_ROOT`` environment variable:
.. code-block:: bash
export ANDROID_NDK_ROOT=/path/to/android-ndk-r21e
Target Setup
------------
Gupje is compiled against a specific target. This means that you need to download a target or build one.
The Raspberry pi 4 is a good example target to start with. Please see the `Raspberry Pi 4 target <targets/rpi4.html>`_ for more information.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -5,14 +5,23 @@
Gupje Architecture based debugger
=================================
This is the documentation for the Gupje debugger.
This is the documentation for the ``Gupje`` debugger.
.. toctree::
:maxdepth: 2
:maxdepth: 3
:caption: Setup:
overview.rst
getting_started.rst
.. toctree::
:maxdepth: 2
:caption: Example Targets:
targets/rpi4.rst
targets/nvidia_shield.rst
targets/samsung_s7.rst
.. toctree::
:maxdepth: 2

View File

@ -10,21 +10,22 @@ A high level overview of how the memory mapping for this works is shown below.
In the future the debugger will probably grow to 4 pages, to include an extra page for the debugger to use in custom functionality.
Features
========
In a typical usecase the flow of the debugger wuuld look as something like this:
Inner workings
--------------
When the debugger is entered it will save the state of the processor into it's storage page. This is done in assembly and is architecture dependent.
When a new architecture is added the first thing that needs to be done is to implement these functionalities.
.. figure:: images/debugger_flow.drawio.svg
State saving
------------
Function: ``On enter``
The debugger will then wait for commands from the host. The host can send commands to the debugger to read/write memory, jump to an address, etc.
The first thing the debugger does when it enters is save it's state into it's storage page.
The state being all the General Purpose registers and some special register, like the stack pointer and link register.
The stack pointer is overwritten to the debugger stack, in order to not taint the original stack.
This is architecture dependent and written in Assembly.
See the documentation in each architecture on how this is performed and if data is lost.
When the user is done it will send the ``REST`` command to the debugger. This will restore the state of the processor and jump to the address defined in ``DEBUGGER_JUMP``.
The debugger also uses a stack that is defined in the debugger. This is done to not taint the original stack of the processor.
API
===
The debugger has a simple API that is used to communicate with the device. The following commands are supported:
Memory Read(Peek)
-----------------
@ -50,7 +51,7 @@ Execute architecture specific instructions in order to flush the cache.
.. note::
Only arm64 is supported for this function. The support is there for arm and thumb but not implemented.
Only arm64 is supported for this function. The support is there for arm and thumb but not implemented. Probably there will be a change here to support data/code cache flushes.
Jump to address(JUMP)
---------------------
@ -83,9 +84,17 @@ This function restores the state of the processor from it's internal storage and
Restore and Return(RRET)
------------------------
Does the same as ``Restore and Jump`` but instead of jumping it returns to the address that called the debugger.
Does the same as ``Restore and Jump`` but instead of jumping it returns to the address that called the debugger(LR).
Glitching
=========
A debug flag is added for adding glitching to the debugger. The command ``GLIT`` will jump to the glitch function but this is not very well implemented yet.
The goal is to add several testable glitch cases to the debugger for profiling.
Features
========
In a typical usecase the flow of the debugger would look as something like this:

View File

@ -0,0 +1,3 @@
====================
Nvidia Shield Tablet
====================

View File

@ -0,0 +1,86 @@
==============
Raspberry Pi 4
==============
The Raspberry Pi 4 is a single-board computer developed by the ``Raspberry Pi Foundation``. It is freely available and requires no exploit to run code on it.
On top of that it is well supported in ``Qemu``, making this the cheapest and easiest target for gupje.
Code
----
The github code for this `is here <https://github.com/EljakimHerrewijnen/rpi4_gupje>`_.
.. hint::
The code that runs on the Raspberry Pi4 is based on `this awesome repository <https://github.com/ethanfaust/rpi4-baremetal-uart>`_.
Clone this repository *somewhere* and link the cloned folder in the ``devices/`` folder of gupje.
.. code-block:: bash
git clone --recursive https://github.com/EljakimHerrewijnen/rpi4_gupje
cd /path/to/gupje/devices
ln -s /path/to/rpi4_gupje rpi4
You can now build the target by running the following command from gupje's root directory directory:
.. code-block:: bash
cd /path/to/gupje
make -f devices/rpi4/Makefile
In ``bin/rpi4/`` you will find the ``debugger.bin`` file, which will be used by the ``qemu.py`` script.
Running Qemu
############
From ``devices/rpi4`` navigate to ``rpi4-baremetal-uart/`` folder and run make with a cross compiler.
.. code-block:: bash
cd /path/to/gupje/devices/rpi4/rpi4-baremetal-uart
ARCH=arm64 PREFIX=aarch64-linux-gnu- make
Implementation
==============
Only send and receive need to be implemented for this target. For this target UART is used.
The debugger expects send/recv to be handled by the user so we need to build some logic to know that the data has been send and that the amount of expected data has been received. The following C code implements the send/recv for UART.
.. code-block:: c
void recv_data(void *address, uint32_t size){
for(int i=0; i < size; i++){
*((char *)address + i) = uart_get();
}
}
void send(void *address, uint64_t size, uint32_t *error){
for(int i=0; i < size; i++){
uart_send(*((char *)address + i));
}
}
However, in this implementation the debugger will need to link the uart_get and uart_send functions. In order to do that we copy the functions from the elf file and add them to the symbols.txt file.
.. code-block:: bash
$ readelf -a kernel8.elf | grep uart_
25: 00000000000803a8 36 FUNC GLOBAL DEFAULT 1 uart_get
27: 0000000000080384 36 FUNC GLOBAL DEFAULT 1 uart_send
34: 0000000000080400 88 FUNC GLOBAL DEFAULT 1 uart_puts
35: 000000000008024c 312 FUNC GLOBAL DEFAULT 1 uart_init
36: 0000000000080458 84 FUNC GLOBAL DEFAULT 1 uart_hex
39: 00000000000803cc 52 FUNC GLOBAL DEFAULT 1 uart_getc
Add the symbols uart_get and uart_send to the symbols.txt file, along with the debugger regions:
.. code-block:: text
debugger_storage = 0x85000;
debugger_stack = 0x83000;
debugger_entry = 0x81000;
uart_get = 0x00000000000803a8;
uart_send = 0x0000000000080384;
See the makefile for details on how the linking is done.

View File

@ -0,0 +1,5 @@
==========
Samsung S7
==========
Exploitation done by Frederick in `his blog <https://fredericb.info/2020/06/exynos-usbdl-unsigned-code-loader-for-exynos-bootrom.html>`_.

0
requirements.txt Normal file
View File

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 10 KiB