diff --git a/README.md b/README.md index 13aeff5..c4c0cd5 100644 --- a/README.md +++ b/README.md @@ -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 -``` \ No newline at end of file diff --git a/debugger.c b/debugger.c index 018f137..efe1348 100644 --- a/debugger.c +++ b/debugger.c @@ -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; diff --git a/documentation/source/conf.py b/documentation/source/conf.py index d23a478..969dcd3 100644 --- a/documentation/source/conf.py +++ b/documentation/source/conf.py @@ -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'] diff --git a/documentation/source/getting_started.rst b/documentation/source/getting_started.rst new file mode 100644 index 0000000..20b5f5b --- /dev/null +++ b/documentation/source/getting_started.rst @@ -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 `_ 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 `_ for more information. + diff --git a/documentation/source/images/debugger_flow.drawio.svg b/documentation/source/images/debugger_flow.drawio.svg index cfeb9a8..c31708a 100644 --- a/documentation/source/images/debugger_flow.drawio.svg +++ b/documentation/source/images/debugger_flow.drawio.svg @@ -1 +1 @@ -
entry
Save state to DEBUGGER_STORAGE
debugger_main
GiAs
Send
Recv
No
Yes
CNT_EXEC
set?
Restore State and jump to DEBUGGER_JUMP
Restore and Return
User commands
\ No newline at end of file +
Entry
Save state to DEBUGGER_STORAGE
Gupje
Send
Recv
No
Yes
CNT_EXEC
set?
Restore State and jump to DEBUGGER_JUMP
Restore and Return
from
DEBUGGER_STORAGE
The user needs to provide a method for running gupje.
Usually this a vulnerability to get RCE on a device
commands
User Interaction
Gupje will respond to the host with GiAs, indicating that it is ready to receive commands.
The user can now proceed with loading a next stage
\ No newline at end of file diff --git a/documentation/source/images/gupje_overview.drawio.svg b/documentation/source/images/gupje_overview.drawio.svg index f0add3b..adb94c7 100644 --- a/documentation/source/images/gupje_overview.drawio.svg +++ b/documentation/source/images/gupje_overview.drawio.svg @@ -1 +1 @@ -
Gupje
Storage
Stack
Target device
Host
my_script.py
ghidra assitant
\ No newline at end of file +
Gupje
Storage
Stack
Target device
Host
my_script.py
ghidra assitant
\ No newline at end of file diff --git a/documentation/source/index.rst b/documentation/source/index.rst index edb48da..c80abc8 100644 --- a/documentation/source/index.rst +++ b/documentation/source/index.rst @@ -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 diff --git a/documentation/source/overview.rst b/documentation/source/overview.rst index 3953c61..65e3022 100644 --- a/documentation/source/overview.rst +++ b/documentation/source/overview.rst @@ -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. \ No newline at end of file +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: diff --git a/documentation/source/targets/nvidia_shield.rst b/documentation/source/targets/nvidia_shield.rst new file mode 100644 index 0000000..0a8b44e --- /dev/null +++ b/documentation/source/targets/nvidia_shield.rst @@ -0,0 +1,3 @@ +==================== +Nvidia Shield Tablet +==================== diff --git a/documentation/source/targets/rpi4.rst b/documentation/source/targets/rpi4.rst new file mode 100644 index 0000000..2d641e3 --- /dev/null +++ b/documentation/source/targets/rpi4.rst @@ -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 `_. + +.. hint:: + + The code that runs on the Raspberry Pi4 is based on `this awesome repository `_. + +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. + diff --git a/documentation/source/targets/samsung_s7.rst b/documentation/source/targets/samsung_s7.rst new file mode 100644 index 0000000..1b4c354 --- /dev/null +++ b/documentation/source/targets/samsung_s7.rst @@ -0,0 +1,5 @@ +========== +Samsung S7 +========== + +Exploitation done by Frederick in `his blog `_. \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/simple_device.drawio.svg b/simple_device.drawio.svg index eb395c6..00146b5 100644 --- a/simple_device.drawio.svg +++ b/simple_device.drawio.svg @@ -1 +1 @@ -
Target
2 Jump
BootROM
1 Exploit (Gupje)
Exploit RCE
upload
bootloader
Interact with device
RAM
3 GiAs
Gupje
Patched Bootloader
\ No newline at end of file +
Target
BootROM
1 Exploit (Gupje)
Exploit RCE
upload
bootloader
Interact with device
RAM
3 GiAs
Gupje
Patched Bootloader
2 Jump(RCE)
\ No newline at end of file