The following notes were taken while porting Frankenstein from CYW20735B1 to CYW20819A1. Importing symbols is only that straight forward if you can take them from one of the SDKs.
-
In InternalBlue, check that the name in the according firmware file matches. In our case it's in
internalblue/fw/fw_0x220c.py
. -
Start Frankenstein Web UI:
python2 manage.py runserver
.- For whatever reason,
pip
currently seems to install modules non-readable worldwide / for other users ... you might need to change file permissions forpyelftools
to work. (On my system)
- For whatever reason,
-
Create a new project named CYW20819A1.
-
Import symbols if you have some:
- Get Modus Toolbox from Cypress. Other evaluation boards might require WICED Studio or similar.
- Symbols are in
~/Documents/ModusToolbox_1.1/libraries/bt_20819A1-1.0/components/BT-SDK/20819-A1_Bluetooth/ WICED/internal/20819A1/patches/patch.elf
. - Click on Load ELF and add them.
- Load Symbols and Segments to global. It is important to set them to global, otherwise, these symbols
will only apply to one specific memory dump.
- For me, adding Segments were wrong! The Segments in the
patch.elf
were not useful. Better create a large dummy segment. - ... but importing Symbols only worked.
- For me, adding Segments were wrong! The Segments in the
-
Symbols for global variables are not included in
patch.elf
. In WICED Studio, they are contained in the files20719mapb0.h
,20739mapb0.h
and20703mapa0.h
. We do not have them in Modus Toolbox, but most of them stay the same over a long time. So we can carefully copy those variables that we need in the next step for compilation. Add the following global variables via the Frankenstein Web UI:dp_uart_data = 0x0036001c; dc_ptu_uart_lsr = 0x00360424;
-
Create the
patch
directory and copyCYW20735B1/patch/hello.c
. -
Run
make all -C projects/CYW20819A1/
-
...it builds! Ship it! :D
- It won't run though, memory addresses need to be fixed.
- In the Frankenstein Web UI, click on Edit Config and set
PATCH_CODE_BASE
to a reasonable value, i.e.,0x20A000
. While the evaluation board does not have any memory protection and code can be written and executed anywhere in RAM, you still need to check that the memory is currently not in use.- To verify that the area is empty, perform
hd [address]
. - Also try to write and execute something with
writeasm [address] bx lr
andlaunch [address]
. This should work without crashing. - You can get some hints from the SDK, in our case the variable is called
PLATFORM_DIRECT_LOAD_BASE_ADDR
and located inModusToolbox_1.1/libraries/bt_20819A1-1.0/components/BT-SDK/20819-A1_Bluetooth/platform.mk
.
- To verify that the area is empty, perform
- Run
make all -C projects/CYW20819A1/
- Load the patch:
sudo python2 internalBlueMod.py
loadelf projects/CYW20819A1/gen/hello.patch
When everything works, the output should be:
[*] HCI device: hci0 [20:81:9A:09:3E:41] flags=5<UP RUNNING>
[*] Connected to hci0
[*] Chip identifier: 0x220c (001.002.012)
[*] Using fw_0x220c.py
[*] Loaded firmware information for CYW20819.
[*] Try to enable debugging on H4 (warning if not supported)...
[!] _sendThreadFunc: Sending to socket failed, reestablishing connection.
With HCI sockets, some HCI commands require root!
> loadelf projects/CYW20819A1/gen/hello.patch
[*] Loading .text @ 0x20a000 - 0x20a204 (516 bytes)
[*] Loading .rodata.str1.4 @ 0x20a204 - 0x20a23a (54 bytes)
[*] Loading .data @ 0x20a23c - 0x20a24d (17 bytes)
[*] Loaded 12836 symbols
[?] Found nonzero entry point 0x20a1cd. Execute? [Yes/no]
[*] Firmware says: hello\o/
[*] Firmware says: from firmware
[*] Firmware says: _start = 0x20a1cd
Before being able to start emulation, we need to somehow be able to get a state from that on we want to do our emulation and fuzzing.
Copy patch/xmit_state.c
from the CYW20735B1 to the CYW20819A1 project.
Add missing global symbols to project via Frankenstein Web UI:
patchram_address_table = 0x310000;
patchram_enable = 0x310404;
patchram_data_table = 0x270000;
The SDKs have a file called patch.lst
that gives some hints about the locations. You can also check the
InternalBlue firmware definition files where to usually look for these.
Run make all -C projects/CYW20819A1/
.
Now let's try to get a state. Something that is easy to trigger is device scanning. This is in eir_handleRx
.
> xmitstate 0xA2624
[*] patchRom: Choosing next free slot: 0
[*] patchRom: Choosing next free slot: 35
projects/CYW20819A1/gen/xmit_state.patch
[*] Loading .text @ 0x20a000 - 0x20a608 (1544 bytes)
[*] Loading .rodata.str1.4 @ 0x20a608 - 0x20a64c (68 bytes)
[*] Loading .data @ 0x20a64c - 0x20a66d (33 bytes)
[*] Loading .bss @ 0x20a670 - 0x20ad00 (1680 bytes)
[*] Loaded 12859 symbols
[*] Firmware says: Hello \o/
[*] Firmware says: Hook Added
...and now trigger it by starting a scan using hcitool scan
.
[*] Receiving firmware state: regs@0x20acf8 cont@0x20a044
[*] Received segment 0x0 - 0x200000
[*] Received segment 0x200000 - 0x250000
[*] Received segment 0x270000 - 0x280000
[!] Received Evaluation Stack-Dump Event (contains 10 registers):
[!] pc: 0x0020a20c lr: 0x0020a4d3 sp: 0x00200208 r0: 0x00280000 r1: 0x00284000
r2: 0x00280008 r3: 0x00200234 r4: 0x0007fdd4 r5: 0x002002b4 r6: 0x00280000
If you open Wireshark, you can see that there are many events coming in now, and the state dump takes a while. Better do some cross-checking before you give up and think it got stuck. It most certainly didn't.
The reason for the stackdump here is that memory areas are defined differently on the newer evaluation board.
Thus, we also need to copy map_memory.c
from the CYW20735B1 board to the CYW20819A1 board.
Run make all -C projects/CYW20819A1/
.
> mapmemory 0x0
[*] Loading .text @ 0x20a000 - 0x20a31c (796 bytes)
[*] Loading .rodata @ 0x20a31c - 0x20a321 (5 bytes)
[*] Loading .rodata.str1.4 @ 0x20a324 - 0x20a33b (23 bytes)
[*] Loading .data @ 0x20a33c - 0x20a34d (17 bytes)
[*] Loading .bss @ 0x20a350 - 0x20a354 (4 bytes)
[*] Loaded 12847 symbols
[*] Firmware says: Hello \o/
Found Map 0x0 - 0x280000
Found Map 0x300000 - 0x308000
Found Map 0x310000 - 0x322000
Found Map 0x326000 - 0x330000
Found Map 0x338000 - 0x340000
Found Map 0x341000 - 0x342000
Found Map 0x350000 - 0x368000
Found Map 0x370000 - 0x380000
Found Map 0x390000 - 0x398000
Found Map 0x404000 - 0x408000
Found Map 0x410000 - 0x414000
Found Map 0x420000 - 0x424000
Found Map 0x430000 - 0x434000
Found Map 0x440000 - 0x444000
Found Map 0x450000 - 0x454000
Found Map 0x500000 - 0x541000
Found Map 0x580000 - 0x600800
Found Map 0x640000 - 0x640800
[!] Firmware died at address 0x650f00 while mapping memory
>
Dying at 0x650f00
is kind of expected, that's coexistence registers and they might not be blocked for writing
despite not being properly initialized. We saw some other crashes related to the 0x650nnn
area.
Anyway, let's now go and put these into xmit_state.c
. Afterwards, xmitstate
will succeed:
...
[*] Received segment 0x650000 - 0x651000
[*] Received segment 0xe0000000 - 0xe0100000
[*] Received fuill firmware state
The latest xmitstate
result will be displayed as active in the Frankenstein Web UI. Just navigate to
http://127.0.0.1:8000/project?projectName=CYW20819A1 and reload the page.
Now, rebuild the project:
make all -C projects/CYW20819A1
If things break here, you might have screwed up with segment definitions, global vs. default scope, or similar.
Compiled after removing memcpy_r
, so that one doesn't exist on the CYW20819A1.
If you have symbols, do a quick check that you didn't miss any memcpy
instruction.
In our case, nothing was added.
~/Documents/ModusToolbox_1.1$ readelf -a -W libraries/bt_20819A1-1.0/components/BT-SDK/20819-A1_Bluetooth/WICED/internal/20819A1/patches/patch.elf | grep memcpy
535: 00006d31 0 FUNC GLOBAL DEFAULT ABS memcpy
1377: 0001845f 0 FUNC GLOBAL DEFAULT ABS sfi_memcpy
3602: 000477d1 0 FUNC GLOBAL HIDDEN ABS __aeabi_memcpy4
3603: 000477d1 0 FUNC GLOBAL DEFAULT ABS __aeabi_memcpy8
3604: 000477d1 0 FUNC GLOBAL DEFAULT ABS __rt_memcpy_w
3605: 00047819 0 FUNC GLOBAL DEFAULT ABS _memcpy_lastbytes_aligned
4209: 00054687 0 FUNC GLOBAL HIDDEN ABS __aeabi_memcpy
4210: 00054687 0 FUNC GLOBAL DEFAULT ABS __rt_memcpy
4211: 000546ed 0 FUNC GLOBAL DEFAULT ABS _memcpy_lastbytes
5733: 0007d747 0 FUNC GLOBAL DEFAULT ABS mpaf_memcpy
6802: 00097ed5 0 FUNC GLOBAL DEFAULT ABS utils_memcpy8_postinc
6803: 00097f21 0 FUNC GLOBAL DEFAULT ABS utils_memcpy8
6804: 00097f59 0 FUNC GLOBAL DEFAULT ABS utils_memcpy3dword
6807: 00097fa9 0 FUNC GLOBAL DEFAULT ABS utils_memcpy10
8361: 000b67d1 0 FUNC GLOBAL DEFAULT ABS __ARM_common_memcpy4_5
8516: 000ba075 0 FUNC GLOBAL DEFAULT ABS __ARM_common_memcpy4_10
...and then run it.
> loadelf projects/CYW20819A1/gen/heap_sanitizer.patch
TODO:
I think we need to call _start
for the patch to become
effective, but this crashes. The patch can be loaded without crashing, but once the entry
point is executed, the firmware silently crashes.
...even when I remove everything from the _start
function and double check that it is
just bx lr
, before its execution everything is well and afterwards it gets non-responsive.
Minimal working example: even without patches, writing to the address 0x20a5dc
and executing it crashes.
Why is this even used?
PLATFORM_DIRECT_LOAD_BASE_ADDR := 0x20A000
...so far so good.
PLATFORM_APP_SPECIFIC_STATIC_LEN ?= 1024
Which adds up to 0x20a400
. So this patch might probably just exceed the maximum patch length on CYW20819A1.
Start with copying emulation/execute.c
from the CYW20735B1.
Also copy emulation/[bcs,common,dynamic_memory,fwdefs,hci,lm,queue,timer].h
and emulation/bcs/[acl,inq,le,page].h
,
as these things are not yet moved cleanly into /projects/common/
.
arm-none-eabi-ld: cannot find gen/internalBlue_11.07.2019_13.52.37/Segment_0x420000.segment.o
make: *** [Makefile:28: gen/execute.exe] Error 1
Happens due to executing as root to access hci0 on Linux, we need to change permissions on this file:
'./segment_groups/internalBlue_11.07.2019_13.52.37': Permission denied
Afterwards, the following registers are missing:
dc_nbtc_clk = 0x00318088;
dc_x_clk = 0x003186ac;
pcx_btclk = 0x0031822c;
pcx2_btclk = 0x0031823c;
pcx2_pbtclk = 0x00318238;
phy_status = 0x00314004;
pkt_hdr_status = 0x00318b28;
pkt_log = 0x00318b2c;
rtx_dma_ctl = 0x00314018;
rtx_mem_start1 = 0x00370400;
rtx_rx_buffer = 0x00370c00;
sr_status = 0x0031400c;
sr_ptu_status_adr4 = 0x00360084;
tx_pkt_info = 0x00318acc;
tx_pkt_pyld_hdr = 0x00318ad0;
Currently, various tasks and structures are hardcoded. Locate and replace these.
Just to show the workflow when something is not working :) Basic ideas of how to analyze issues with IDA, gdb and the InternalBlue stackdumps etc.
With invalid PLATFORM_DIRECT_LOAD_BASE_ADDR
:
> loadelf projects/CYW20819A1/gen/hello.patch
[*] Loading .text @ 0x230000 - 0x230204 (516 bytes)
[*] Loading .rodata.str1.4 @ 0x230204 - 0x23023a (54 bytes)
[*] Loading .data @ 0x23023c - 0x23024d (17 bytes)
[*] Loaded 12836 symbols
[?] Found nonzero entry point 0x2301cd. Execute? [Yes/no]
[!] Received Evaluation Stack-Dump Event (contains 10 registers):
[!] pc: 0x00424000 lr: 0x0000f095 sp: 0x002209a8 r0: 0x002301ce r1: 0x00000000
r2: 0x00000000 r3: 0x00210438 r4: 0x002301cd r5: 0x00220b20 r6: 0x00000001
- crashes in LR 0x0000f095 = btuarth4_HandleLaunch_RAM just at BLX R4
- PC 0x4240000 (not sure if valid?!), R4 is 0x002301cd
Corrected PLATFORM_DIRECT_LOAD_BASE_ADDR
:
> loadelf projects/CYW20819A1/gen/hello.patch
[*] Loading .text @ 0x20a000 - 0x20a204 (516 bytes)
[*] Loading .rodata.str1.4 @ 0x20a204 - 0x20a23a (54 bytes)
[*] Loading .data @ 0x20a23c - 0x20a24d (17 bytes)
[*] Loaded 12836 symbols
[?] Found nonzero entry point 0x20a1cd. Execute? [Yes/no]
[!] Received Evaluation Stack-Dump Event (contains 10 registers):
[!] pc: 0x0020a048 lr: 0x0020a1d5 sp: 0x00220990 r0: 0x000000fe r1: 0x0020a210
r2: 0x00000005 r3: 0x00000004 r4: 0x03541012 r5: 0x03538972 r6: 0x00000001
This happens in this code:
text:0020A030 hci_xmit_event ; CODE XREF: hci_puts+22↓j
.text:0020A030 ; hci_puts+28↓j ...
.text:0020A030
.text:0020A030 var_C = -0xC
.text:0020A030 var_B = -0xB
.text:0020A030 var_A = -0xA
.text:0020A030
.text:0020A030 PUSH {R4,R5}
.text:0020A032 LDR R5, =0x3538972 ; "dp_uart_data": 55806322,
.text:0020A034 LDR R4, =0x3541012 ; "dc_ptu_uart_lsr": 55840786,
.text:0020A036 SUB SP, SP, #8
.text:0020A038 MOVS R3, #4
.text:0020A03A STRB.W R0, [SP,#0x10+var_B]
.text:0020A03E STRB.W R2, [SP,#0x10+var_A]
.text:0020A042 STR R3, [R5]
.text:0020A044 STRB.W R3, [SP,#0x10+var_C]
.text:0020A048
.text:0020A048 loc_20A048 ; CODE XREF: hci_xmit_event+20↓j
.text:0020A048 LDR R3, [R4] ; [!] pc: 0x0020a048 lr: 0x0020a1d5 sp: 0x00220990 r0: 0x000000fe r1: 0x0020a210
.text:0020A048 ; r2: 0x00000005 r3: 0x00000004 r4: 0x03541012 r5: 0x03538972 r6: 0x00000001
.text:0020A048 ;
.text:0020A048 ; -> crashes upon the first access to R4
Which is defined in ./projects/common/frankenstein/BCMBT/patching/hciio.h
.
The access to R4 crashes, which is dc_ptu_uart_lsr
. So it was probably redefined
in the CYW20819A1?
We grep again for the register names in WICED Studio. They are defined as follows in all versions of eval boards supported:
#define dp_uart_data_adr 0x0036001c // ptu_adr_base + 0x0000001c
#define dc_ptu_uart_lsr_adr 0x00360424 // uart_base + 0x00000024
...so the values above were probably caused by a double hex conversion.
Some compiler stuff seems also to be weird.
/usr/include/bits/socket.h:354:11: fatal error: asm/socket.h: No such file or directory
# include <asm/socket.h>
^~~~~~~~~~~~~~
Can be solved with:
cd /usr/include/
sudo ln -s asm-generic/ asm
I used default instead of global scope and then didn't know how to modify it except from writing
directly to project.json
. So I just re-created the project. In that case, somehow the segment_groups
folder got lost, probably because I also didn't create the 0x0 segment it was never created by the scripts.
Fixed by adding a folder projects/CYW20819A1/segment_groups/
by hand.
...
File "/home/jiska/seemoo/research/bluetooth/frankenstein/core/project.py", line 254, in add_group
os.mkdir(group_path)
OSError: [Errno 2] No such file or directory: 'projects/CYW20819A1/segment_groups/internalBlue_11.07.2019_13.42.01'
Without redefining all symbols and escaping some special situations, you might run into weird crashes like this one:
$ qemu-arm projects/CYW20819A1/gen/execute.exe
lr=0x20a68d eir_handleRx(0x20c668);
lr=0x07990b bcs_kernelBtProgIntEnable(0x02, 0x02, 0x02, 0x50);
lr=0x04b951 bcs_kernelSlotCbFunctions()lr=0x0bf003bd bcs_SlotCbFunctions()lr=0x01fba9 lm_sendInqFHS(0x202d04)lr=0x0bf01159 dynamic_memory_AllocatePrivate(0x200498, 0x0, 0x0);
qemu: uncaught target signal 4 (Illegal instruction) - core dumped
Illegal instruction
Debugging works as follows:
qemu-arm -g 31337 projects/CYW20819A1/gen/execute.exe
In a new window, start gdb
. (Might require gdb-multiarch
or gdb-arm-none-eabi
.)
(gdb) target remote 127.0.0.1:31337
Remote debugging using 127.0.0.1:31337
### (... some IDA magic to find out where we are roughly crashing)
(gdb) break *0x1F66
Breakpoint 4 at 0x1f66
(gdb) continue
Continuing.
Breakpoint 4, 0x00001f66 in ?? ()
(gdb) stepi
0x0007ec9e in ?? ()
(gdb) info r
r0 0x200498 2098328
r1 0x21 33
r2 0x20 32
r3 0x3 3
r4 0x200498 2098328
r5 0x2106bc 2164412
r6 0x0 0
r7 0x200da8 2100648
r8 0x73c76 474230
r9 0x2 2
r10 0x7800 30720
r11 0x200de4 2100708
r12 0x18 24
sp 0x200350 0x200350
lr 0x1f6b 8043
pc 0x7ec9e 0x7ec9e
cpsr 0x20000030 536870960
(gdb) stepi
Program received signal SIGILL, Illegal instruction.
0x0007ec9e in ?? ()
(gdb)
So this happened within the following function, that is called by dynamic_memory_AllocateOrDie
:
code_rom:000A43EE synch_GetXPSRExceptionNumber ; CODE XREF: dynamic_memory_AllocateOrDie+26↑p
code_rom:000A43EE ; dynamic_memory_AllocateOrReturnNULL+26↑p ...
code_rom:000A43EE EF F3 03 80 MRS.W R0, XPSR
code_rom:000A43F2 4F EA 00 60 MOV.W R0, R0,LSL#24
code_rom:000A43F6 4F EA 10 60 MOV.W R0, R0,LSR#24
code_rom:000A43FA 70 47 BX LR
code_rom:000A43FA ; End of function synch_GetXPSRExceptionNumber
This function can simply be disabled. It was already defined to be skipped but the address
of this function was hard coded in our case. Exceptions are located in common.h
and disabled
via patch_return(synch_GetXSPRExceptionNum)
.
Overall, most functions that do not work out to be emulated can just be disabled.
Missing idle_loop
is also strange.
qemu: unhandled CPU exception 0x9 - aborting
R00=10000000 R01=e000e000 R02=00000000 R03=00000000
R04=00000000 R05=00205888 R06=00200ba8 R07=00000000
R08=00000000 R09=00000000 R10=00225b44 R11=00000000
R12=00000000 R13=002003d8 R14=fffffffd R15=fffffffc
PSR=60000030 -ZC- T S usr32
Once you find it in gdb
, the last steps are:
_tx_thread_context_restore: 0x00010574, 0x10596 -> here, LR is already set to 0xfffffffd (idle hook!)
(stepi 400 -> Cannot access memory at address 0xfffffffc)
(gdb) stepi
0x00010598 in ?? ()
(gdb) stepi
0x0001059a in ?? ()
(gdb) stepi
0x0001059e in ?? ()
(gdb) stepi
0x000105a2 in ?? ()
(gdb) stepi
0x000105a6 in ?? ()
(gdb) stepi
0xfffffffc in ?? ()