|
1 | | -Shiva is a programmable runtime linker (Program interpreter) for ELF x64/aarch64 Linux -- |
2 | | -ELF-microprograms (Shiva modules) are linked into the process address space and given |
3 | | -intricate control over program instrumentation via ShivaTrace API. ShivaTrace is an in-process debugging |
4 | | -and instrumentation API with innovative debugging and hooking features. |
| 1 | +# Shiva -- Programmable runtime linking and microcode patching |
| 2 | + |
| 3 | + |
| 4 | +## Description |
| 5 | + |
| 6 | +Shiva is a programmable runtime linker (Program interpreter) for ELF |
| 7 | +x64/aarch64 Linux -- ELF-microprograms (Shiva modules) are linked into the |
| 8 | +process address space and given intricate control over program instrumentation |
| 9 | +via ShivaTrace API. ShivaTrace is an in-process debugging and instrumentation |
| 10 | +API with innovative debugging and hooking features. |
| 11 | + |
| 12 | +Shiva has been custom tailored towards the requirements of the AMP project and |
| 13 | +with support for the AArch64 architecture. This fork of the project has created |
| 14 | +an abundant set of new microcode patching capabilities, including symbol interposition |
| 15 | +on functions (i.e. .text), as well as on global data (i.e. .rodata, .data, .bss). |
| 16 | + |
| 17 | +The original Shiva project can be found at https://github.com/elfmaster/shiva |
| 18 | + |
| 19 | +This README will only cover Shiva as it relates to the AMP project. |
| 20 | + |
| 21 | +Please see ./documentation/shiva_preliminary_design.pdf for a technical description |
| 22 | +of Shiva. |
| 23 | + |
| 24 | +## Support |
| 25 | + |
| 26 | +Support is limited to ELF AArch64 ET_DYN binaries. Future support for ET_EXEC will |
| 27 | +be added as needed. The cFS software and the patch challenge-10 binaries are |
| 28 | +ELF AArch64 ET_DYN binaries, so currently we are meeting the requirements. |
| 29 | + |
| 30 | +## Build |
| 31 | + |
| 32 | +This has been tested on aarch64 ubuntu 18.04 and 22. |
| 33 | +Shiva relies on libelfmaster and musl-libc. |
| 34 | + |
| 35 | +## Dependencies |
| 36 | + |
| 37 | + |
| 38 | +#### libelfmaster (aarch64_support branch) |
| 39 | + |
| 40 | +``` |
| 41 | +git clone git@github.com:elfmaster/libelfmaster |
| 42 | +cd libelfmaster |
| 43 | +git --fetch all |
| 44 | +git checkout aarch64_support |
| 45 | +``` |
| 46 | + |
| 47 | +The original build for libelfmaster seems broken and I haven't yet fixed it. |
| 48 | +Meanwhile just use the simple build shellscript I made. |
| 49 | + |
| 50 | +``` |
| 51 | +cd src |
| 52 | +sudo make.sh |
| 53 | +``` |
| 54 | + |
| 55 | +The static library to libelfmaster |
| 56 | +```/opt/elfmaster/lib/libelfmaster.a``` |
| 57 | + |
| 58 | +The header file to libelfmaster |
| 59 | +```/opt/elfmaster/include/elfmaster.h``` |
| 60 | + |
| 61 | + |
| 62 | +#### musl-libc |
| 63 | + |
| 64 | +``` |
| 65 | +sudo apt-get install musl musl-dev musl-tools |
| 66 | +``` |
| 67 | + |
| 68 | +## Building Shiva |
| 69 | + |
| 70 | +``` |
| 71 | +cd ~/git |
| 72 | +git clone git@github.com:advanced-microcode-patching/shiva |
| 73 | +cd shiva |
| 74 | +make |
| 75 | +make shiva-ld |
| 76 | +make patches |
| 77 | +sudo make install |
| 78 | +``` |
| 79 | + |
| 80 | +Shiva is copied to `"/lib/shiva"` and can be executed directly, but more commonly |
| 81 | +indirectly as an interpreter. |
| 82 | + |
| 83 | +The shiva-ld utility is used to modify binaries with the path to the new |
| 84 | +program interpreter `"/lib/shiva"`, and the path to the patch module (i.e. |
| 85 | +`"/opt/modules/shiva/patch1.o"`). |
| 86 | + |
| 87 | +## Patch testing |
| 88 | + |
| 89 | +We have already compiled and prelinked the patches. Shiva prelinking |
| 90 | +refers specifically to the Shiva prelinking applied by the shiva-ld tool. |
| 91 | + |
| 92 | +Take a look at the Makefile for each patch, and you will see how shiva-ld is |
| 93 | +used to apply the pre-patch meta-data. |
| 94 | + |
| 95 | +``` |
| 96 | +shiva-ld -e core-cpu1 -p cfs_patch1.o -i /lib/shiva -s /opt/shiva/modules -o core-cpu1.patched |
| 97 | +``` |
| 98 | + |
| 99 | +The Shiva make install script installs all of the patch modules into `/opt/shiva/modules` |
| 100 | + |
| 101 | +The patch build environments are stored in `modules/aarch64_patches/` and are as follows: |
| 102 | + |
| 103 | +### CFS Binary patch: cfs_patch1 |
| 104 | + |
| 105 | +This is just a simple patch that uses symbol interposition to replace the |
| 106 | +STB_GLOBAL/STT_FUNC `OS_printf` that lives within the `core-cpu1` executable. |
| 107 | +The patch `cfs_patch1.c` simply rewrites its own version of the function. |
| 108 | + |
| 109 | +The contents of the `./modules/aarch64_patches/cfs_patch1` |
| 110 | + |
| 111 | +``` |
| 112 | +elfmaster@esoteric-aarch64:~/amp/shiva/modules/aarch64_patches/cfs_patch1$ ls |
| 113 | +cfs_patch1.c cfs_patch1.o core-cpu1 core-cpu1.patched EEPROM.DAT Makefile |
| 114 | +``` |
| 115 | + |
| 116 | +The program that we are patching is `core-cpu1` and specifically the symbol `OS_printf` |
| 117 | + |
| 118 | +``` |
| 119 | +elfmaster@esoteric-aarch64:~/amp/shiva/modules/aarch64_patches/cfs_patch1$ readelf -s core-cpu1 | grep OS_printf |
| 120 | + 241: 0000000000047d88 456 FUNC GLOBAL DEFAULT 13 OS_printf |
| 121 | +``` |
| 122 | + |
| 123 | +Our patch contains it's own version of the function `OS_printf` and at runtime Shiva will load |
| 124 | +the `/opt/shiva/modules/cfs_patch1.o` handle all of it's own relocations, and then it will externally |
| 125 | +re-link `core-cpu1` so that any calls to the old `OS_printf` are patched to call the new `OS_printf` |
| 126 | +that lives within the modules runtime environment setup by Shiva. |
| 127 | + |
| 128 | + |
| 129 | +``` |
| 130 | +elfmaster@esoteric-aarch64:~/amp/shiva/modules/aarch64_patches/cfs_patch1$ cat cfs_patch1.c |
| 131 | +#include <stdarg.h> |
| 132 | +#include <stdlib.h> |
| 133 | +#include <stdint.h> |
| 134 | +#include <stdio.h> |
| 135 | +
|
| 136 | +void OS_printf(const char *string, ...) |
| 137 | +{ |
| 138 | + char msg_buffer[4096]; |
| 139 | + va_list va; |
| 140 | + int sz; |
| 141 | +
|
| 142 | + va_start(va, string); |
| 143 | + sz = vsnprintf(msg_buffer, sizeof(msg_buffer), string, va); |
| 144 | + va_end(va); |
| 145 | + msg_buffer[sz] = '\0'; |
| 146 | + printf("[PATCHED :)]: %s\n", msg_buffer); /* NOTICE THIS LINE */ |
| 147 | +} |
| 148 | +
|
| 149 | +``` |
| 150 | + |
| 151 | +A quick look at the `PT_INTERP` segment will reveal that `core-cpu1.patched` has `"/lib/shiva"` |
| 152 | +set as the program interpreter. |
| 153 | + |
| 154 | +``` |
| 155 | +elfmaster@esoteric-aarch64:~/amp/shiva/modules/aarch64_patches/cfs_patch1$ readelf -l core-cpu1.patched | grep interpreter |
| 156 | + [Requesting program interpreter: /lib/shiva] |
| 157 | +``` |
| 158 | + |
| 159 | +Two custom dynamic segment entries were also added to the binary: |
| 160 | + |
| 161 | +`SHIVA_DT_SEARCH` denotes a dynamic entry containing the address of the module search path, |
| 162 | +usually set to `"/opt/shiva/modules/"`. |
| 163 | + |
| 164 | +`SHIVA_DT_NEEDED` denotes a dynamic entry containing the address of the module basename, |
| 165 | +i.e. `"cfs_patch1.o"`. |
| 166 | + |
| 167 | +``` |
| 168 | +elfmaster@esoteric-aarch64:~/amp/shiva/modules/aarch64_patches/cfs_patch1$ readelf -d core-cpu1.patched | tail -n 3 |
| 169 | + 0x0000000060000018 (Operating System specific: 60000018) 0x1ab200 |
| 170 | + 0x0000000060000017 (Operating System specific: 60000017) 0x1ab213 |
| 171 | + 0x0000000000000000 (NULL) 0x0 |
| 172 | +``` |
| 173 | + |
| 174 | +NOTE: The new dynamic segment lives within a newly created PT_LOAD segment. The new PT_LOAD segment |
| 175 | +is the result of a PT_NOTE to PT_LOAD transition. |
| 176 | + |
| 177 | +#### Running core-cpu1.patched |
| 178 | + |
| 179 | + |
| 180 | +When running the `./core-cpu.patched` we see in the last stdout line our patched |
| 181 | +`OS_printf` executes. Notice it prefaces each line of output with the string `"[PATCHED :)]"` |
| 182 | + |
| 183 | +``` |
| 184 | +elfmaster@esoteric-aarch64:~/amp/shiva/modules/aarch64_patches/cfs_patch1$ ./core-cpu1.patched |
| 185 | +OS_BSP_Initialize():Maximum user msg queue depth = 10 |
| 186 | +CFE_PSP: Default Reset SubType = 1 |
| 187 | +CFE_PSP: Default CPU ID = 1 |
| 188 | +CFE_PSP: Default Spacecraft ID = 66 |
| 189 | +CFE_PSP: Default CPU Name: cpu1 |
| 190 | +OS_Posix_GetSchedulerParams():186:Policy 1: available, min-max: 1-99 |
| 191 | +OS_Posix_GetSchedulerParams():186:Policy 2: available, min-max: 1-99 |
| 192 | +OS_Posix_TaskAPI_Impl_Init():372:Selected policy 2 for RT tasks, root task = 99 |
| 193 | +OS_Posix_TaskAPI_Impl_Init():388:Could not setschedparam in main thread: Operation not permitted (1) |
| 194 | +CFE_PSP: initializing module 'soft_timebase' with ID 0110ff00 |
| 195 | +CFE_PSP: Instantiated software timebase 'cFS-Master' running at 10000 usec |
| 196 | +CFE_PSP: initializing module 'timebase_posix_clock' with ID 0110ff01 |
| 197 | +CFE_PSP: Using POSIX monotonic clock as CFE timebase |
| 198 | +CFE_PSP: initializing module 'eeprom_mmap_file' with ID 0110ff02 |
| 199 | +CFE_PSP: Using MMAP simulated EEPROM implementation |
| 200 | +[PATCHED :)]: CFE_PSP: EEPROM Range (2) created: Start Address = FFFF84032000, Size = 00080000 Status = 0 |
| 201 | +``` |
| 202 | + |
| 203 | +#### Patching .rodata symbols with Shiva: rodata_interposing patch. |
| 204 | + |
| 205 | +`modules/aarch64_patches/rodata_interposing` |
| 206 | + |
| 207 | +This patch demonstrates how Shiva is able to link new read-only data into place over |
| 208 | +existing read-only data symbols. For example |
| 209 | + |
| 210 | + |
| 211 | +The contents of the `rodata_interposing` directory |
| 212 | + |
| 213 | +``` |
| 214 | +elfmaster@esoteric-aarch64:~/amp/shiva/modules/aarch64_patches/rodata_interposing$ ls |
| 215 | +Makefile ro_patch.c ro_patch.o test_rodata test_rodata.c test_rodata.patched |
| 216 | +``` |
| 217 | + |
| 218 | +The original program has a read-only string `const char rodata_string[] = "Arcana Technologies"` |
| 219 | + |
| 220 | +``` |
| 221 | +elfmaster@esoteric-aarch64:~/amp/shiva/modules/aarch64_patches/rodata_interposing$ readelf -s test_rodata | grep rodata_string |
| 222 | + 73: 0000000000000800 20 OBJECT GLOBAL DEFAULT 15 rodata_string |
| 223 | +``` |
| 224 | + |
| 225 | +This constant string data is stored within the `.rodata section`. |
| 226 | + |
| 227 | +``` |
| 228 | +objdump -D test_rodata | less |
| 229 | +
|
| 230 | +... |
| 231 | +
|
| 232 | +0000000000000800 <rodata_string>: |
| 233 | + 800: 61637241 .word 0x61637241 |
| 234 | + 804: 5420616e .word 0x5420616e |
| 235 | + 808: 6e686365 .word 0x6e686365 |
| 236 | + 80c: 676f6c6f .word 0x676f6c6f |
| 237 | + 810: 00736569 .word 0x00736569 |
| 238 | +``` |
| 239 | + |
| 240 | +Our patch aims to change the string from `"Arcana Technologies"` to `"The Great Arcanum"`. |
| 241 | + |
| 242 | +``` |
| 243 | +elfmaster@esoteric-aarch64:~/amp/shiva/modules/aarch64_patches/rodata_interposing$ cat ro_patch.c |
| 244 | +
|
| 245 | +const char rodata_string[] = "The Great Arcanum"; |
| 246 | +
|
| 247 | +``` |
| 248 | + |
| 249 | +The compiled patch is `ro_patch.o` |
| 250 | + |
| 251 | +At runtime Shiva will load and link the patch with the executable in memory, and all references |
| 252 | +to the old `rodata_string[]` will be replaced with the correct offset to the patches version of |
| 253 | +`rodata_string[]`. The original string is not being over-written, but is no longer referenced. |
| 254 | + |
| 255 | + |
| 256 | +#### Running the unpatched and patched test_rodata binary |
| 257 | + |
| 258 | +``` |
| 259 | +elfmaster@esoteric-aarch64:~/amp/shiva/modules/aarch64_patches/rodata_interposing$ ./test_rodata |
| 260 | +rodata_string: Arcana Technologies |
| 261 | +val: 5 |
| 262 | +elfmaster@esoteric-aarch64:~/amp/shiva/modules/aarch64_patches/rodata_interposing$ ./test_rodata.patched |
| 263 | +rodata_string: The Great Arcanum |
| 264 | +val: 5 |
| 265 | +elfmaster@esoteric-aarch64:~/amp/shiva/modules/aarch64_patches/rodata_interposing$ |
| 266 | +``` |
| 267 | + |
| 268 | +### A work in progress... |
| 269 | + |
| 270 | + |
0 commit comments