Reverse engineering of an OFO smart lock
After the dockless bike sharing firm ofo ceased its service in Milan, both due to the municipality revoking the permissions granted and to vandalism (it has been estimated that around the 10% of the bikes deployed has been damaged), the majority of the bikes has been retired by the company, but a certain number of them - mostly damaged - is still laying around in the city.
These bikes are equipped with a smart padlock which can be unlocked by anyone possessing a smartphone with the firm’s app installed. And here comes the hacker’s curiosity: being always interested in technology, and especially in embedded systems, I always have been wondering what there is under the hood of these padlocks. Thanks to the people belonging to an hackerspace in Milan I satisfied my curiosity: these guys collected some of the now abandoned (and wrecked) ofo bikes with the plan of repairing and then making them available to the other members of the center. Within this process, they detached and put apart the bikes’ padlocks, giving me the ocasion of picking up one of them to do a bit of reverse engineering.
As regards a general description of what’s inside the locking device, this website provides some information: in this page I will extend them with my findings.
Microcontroller’s connections:
The first operation I did was mapping the connections between the nRF51822 microcontroller [1] and the other components, both mounted on the PCB and the ones outside it (motor, buzzer and keypad). The results are summarised here:
|
|
Microcontroller’s firmware:
Coming to the software side, the padlock’s microcontroller comes with a code locking mechanism which (in theory) prevents reading the contents of the flash memory using a debugger. However, the nRF51822 microcontrollers are affected by a vulnerability which allows, through a debugger, to get the content of the flash memory even when the locking mechanism is enabled [2]. Luckily a python script is available [3], providing a good tool which allows to dump the content of the flash exploiting the aforementioned vulnerability: it’s only necessary to connect the microcontroller to a PC via a SWD tool, like ST-Link.
Once the content of the flash memory has been retrieved, is time to analyse the binary dump searching for program image(s). These can be found easily exploiting the fact that unwritten flash memory cells have (hex) value 0xFFFFFFFF and knowing that a program compiled for Cortex M architecture must start with a vector table defining the addresses for the various interrupt vectors. Sifting through the binary dump, emerges that the microcontroller’s flash memory contains four vector tables at the addresses 0x0000, 0x1000, 0x18000 and 0x3B000. The table below contains the addresses associated to each vector in each of the four tables:
Relative position: | Vector name: | 0x00000 image: | 0x01000 image: | 0x18000 image: | 0x3B000 image: |
---|---|---|---|---|---|
0x0000 | __StackTop | 0x000007C0 | 0x20001500 | 0x20003EE0 | 0x20003650 |
0x0004 | Reset_Handler | 0x000006D1 | 0x000164CD | 0x00018281 | 0x0003B16D |
0x0008 | NMI_Handler | 0x000000D1 | 0x00002225 | 0x0001829B | 0x0003B187 |
0x000C | HardFault_Handler | 0x000006B1 | 0x00016433 | 0x0001829D | 0x0003B189 |
0x0010 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | |
0x0014 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | |
0x0018 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | |
0x001C | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | |
0x0020 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | |
0x0024 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | |
0x0028 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | |
0x002C | SVC_Handler | 0x00000751 | 0x0001653D | 0x0001829F | 0x0003B0D5 |
0x0030 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | |
0x0034 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | |
0x0038 | PendSV_Handler | 0x000000DB | 0x00002225 | 0x000182A1 | 0x0003B18D |
0x003C | SysTick_Handler | 0x000000E5 | 0x00002225 | 0x000182A3 | 0x0003B18F |
0x0040 | POWER_CLOCK_IRQHandler | 0x000000EF | 0x000165A9 | 0x000182A5 | 0x0003B191 |
0x0044 | RADIO_IRQHandler | 0x000000F9 | 0x000165AF | 0x000182A5 | 0x0003B191 |
0x0048 | UART0_IRQHandler | 0x00000103 | 0x00002225 | 0x0001EBC1 | 0x0003B505 |
0x004C | SPI0_TWI0_IRQHandler | 0x0000010D | 0x00002225 | 0x0001E7F5 | 0x0003B191 |
0x0050 | SPI1_TWI1_IRQHandler | 0x00000117 | 0x00002225 | 0x000182A5 | 0x0003B191 |
0x0054 | 0x00000121 | 0x00002225 | 0x00000000 | 0x00000000 | |
0x0058 | GPIOTE_IRQHandler | 0x0000012B | 0x00002225 | 0x0001C3D5 | 0x0003B191 |
0x005C | ADC_IRQHandler | 0x00000135 | 0x00002225 | 0x0001B74D | 0x0003B191 |
0x0060 | TIMER0_IRQHandler | 0x0000013F | 0x000165B5 | 0x000182A5 | 0x0003B191 |
0x0064 | TIMER1_IRQHandler | 0x00000149 | 0x00002225 | 0x000182A5 | 0x0003B191 |
0x0068 | TIMER2_IRQHandler | 0x00000153 | 0x00002225 | 0x000182A5 | 0x0003B191 |
0x006C | RTC0_IRQHandler | 0x0000015D | 0x000165BB | 0x000182A5 | 0x0003B191 |
0x0070 | TEMP_IRQHandler | 0x00000167 | 0x00002225 | 0x000182A5 | 0x0003B191 |
0x0074 | RNG_IRQHandler | 0x00000171 | 0x000165C1 | 0x000182A5 | 0x0003B191 |
0x0078 | ECB_IRQHandler | 0x0000017B | 0x0001C765 | 0x000182A5 | 0x0003B191 |
0x007C | CCM_AAR_IRQHandler | 0x00000185 | 0x000165CD | 0x000182A5 | 0x0003B191 |
0x0080 | WDT_IRQHandler | 0x0000018F | 0x00002225 | 0x0001ED15 | 0x0003B609 |
0x0084 | RTC1_IRQHandler | 0x00000199 | 0x00002225 | 0x0001E7B9 | 0x0003B465 |
0x0088 | QDEC_IRQHandler | 0x000001A3 | 0x00002225 | 0x000182A5 | 0x0003B191 |
0x008C | LPCOMP_IRQHandler | 0x000001AD | 0x00002225 | 0x000182A5 | 0x0003B191 |
0x0090 | SWI0_IRQHandler | 0x000001B7 | 0x00002225 | 0x0001E809 | 0x0003B485 |
0x0094 | SWI1_IRQHandler | 0x000001C1 | 0x00002225 | 0x000182A5 | 0x0003B191 |
0x0098 | SWI2_IRQHandler | 0x000001CB | 0x00002225 | 0x0001E811 | 0x0003B48D |
0x009C | SWI3_IRQHandler | 0x000001D5 | 0x00002225 | 0x000182A5 | 0x0003B191 |
0x00A0 | SWI4_IRQHandler | 0x000001DF | 0x000165D3 | 0x000182A5 | 0x0003B191 |
0x00A4 | SWI5_IRQHandler | 0x000001E9 | 0x000165D9 | 0x000182A5 | 0x0003B191 |
0x00A8 | 0x000001F3 | 0x00002225 | 0x00000000 | 0x00000000 | |
0x00AC | 0x000001FD | 0x00002225 | 0x00000000 | 0x00000000 | |
0x00B0 | 0x00000207 | 0x00002225 | 0x00000000 | 0x00000000 | |
0x00B4 | 0x00000211 | 0x00002225 | 0x00000000 | 0x00000000 | |
0x00B8 | 0x0000021B | 0x00002225 | 0x00000000 | 0x00000000 | |
0x00BC | 0x00000225 | 0x00002225 | 0x00000000 | 0x00000000 |
Currently I’m decompiling and analysing the binary image contained in the flash memory to find what each executable does, I’ll progressively publish here the results.
References:
[2] Firmware dumping technique for an ARM Cortex-M0 SoC
[3] Python script for dumping firmware from read-back protected nRF51 chips