Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@BenGardiner
Last active January 2, 2023 14:38
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BenGardiner/c0b176eb93883892fbc4679bb4b9ec1a to your computer and use it in GitHub Desktop.
Save BenGardiner/c0b176eb93883892fbc4679bb4b9ec1a to your computer and use it in GitHub Desktop.
r2 CAN signal printing workshop
patat
wrap
true

Motivations for This Workshop

  • NOT a 0day.
  • OpenXC is working as-designed
  • This is a chance to reverse a soft target
  • ... and to do it with r2

If you haven't yet: please build and install r2 from git now : git clone https://github.com/radare/radare2; cd radare2 ; ./sys/install.sh. And also pip install r2pipe.

Download the target binary file at https://bit.ly/2ZtEJSH This is a 'FORDBOARD' vi-firmware device; here's the datasheet for a LPC1768 https://bit.ly/2XHemb5


What Is This Workshop?

  • An intermediate r2 course
  • A fast-forward through the 'hard parts' of RE
  • An examination of how to use r2 once you know the hard-won information
  • A very long exposition on the value of xrefs

If you haven't yet: please build and install r2 from git now : git clone https://github.com/radare/radare2; cd radare2 ; ./sys/install.sh. And also pip install r2pipe.

Download the target binary file at https://bit.ly/2ZtEJSH This is a 'FORDBOARD' vi-firmware device; here's the datasheet for a LPC1768 https://bit.ly/2XHemb5


What Isn't This Workshop?

I won't be teaching you (sorry):

  • how to understand ARM and/or ARM Thumb mode
  • how to reverse engineer code, in general
  • how to build and install r2 from git
  • r2 basics: seeking, flags, printing hex, printing disassembly

If you still want to stick in the workshop and join us anyways we can BOTH (you and I) do our best to accommodate.

If you haven't yet: please build and install r2 from git now : git clone https://github.com/radare/radare2; cd radare2 ; ./sys/install.sh. And also pip install r2pipe.

Download the target binary file at https://bit.ly/2ZtEJSH This is a 'FORDBOARD' vi-firmware device; here's the datasheet for a LPC1768 https://bit.ly/2XHemb5


Agenda

  • Rapid Review: .dbc, OpenXC, r2
  • Load raw firmware (FW) into r2
  • (eventually) Pretty-print data structures from the FW

If you haven't yet: please build and install r2 from git now : git clone https://github.com/radare/radare2; cd radare2 ; ./sys/install.sh. And also pip install r2pipe.

Download the target binary file at https://bit.ly/2ZtEJSH This is a 'FORDBOARD' vi-firmware device; here's the datasheet for a LPC1768 https://bit.ly/2XHemb5

Those of you who are ready: What is the CPU arch of this target binary?


What are .dbc Files?

CAN messages commonly pack signals into bitfield locations of a frame ‘type’ (Arbitration ID) Encoding time-varying signals by changing those bitfield contents over time

DBC files describe the mapping of signals to ArbId and bitfield (over simplied)

If you haven't yet: please build and install r2 from git now : git clone https://github.com/radare/radare2; cd radare2 ; ./sys/install.sh. And also pip install r2pipe.

Download the target binary file at https://bit.ly/2ZtEJSH This is a 'FORDBOARD' vi-firmware device; here's the datasheet for a LPC1768 https://bit.ly/2XHemb5


What is OpenXC?

A platform for consuming vehicle data from CAN. Provides Reference Applications, and also reference HW.

An actively developed platform:

  1. OpenXC now supports iOS app development along with Android and Python. http://openxcplatform.com/iOS/getting-started.html
  2. OpenXC has integrations for Bug Labs Dweet and Freeboard products. https://openxc.dweet.io/.

If you haven't yet: please build and install r2 from git now : git clone https://github.com/radare/radare2; cd radare2 ; ./sys/install.sh. And also pip install r2pipe.

Download the target binary file at https://bit.ly/2ZtEJSH This is a 'FORDBOARD' vi-firmware device; here's the datasheet for a LPC1768 https://bit.ly/2XHemb5


What is OpenXC?

  • Moves messages in MessagePack (like JSON)
  • i.e. the firmware contains a mapping from CAN signal to (descriptive) JSON
  • so there are strings describing bitfield locations

If you haven't yet: please build and install r2 from git now : git clone https://github.com/radare/radare2; cd radare2 ; ./sys/install.sh. And also pip install r2pipe.

Download the target binary file at https://bit.ly/2ZtEJSH This is a 'FORDBOARD' vi-firmware device; here's the datasheet for a LPC1768 https://bit.ly/2XHemb5


What is Radare2 (r2)?

Reverse engineering tool suite

  • Our main focus in on the r2 tool of the suite
$ r2 ~/src/binary-samples/elf-Linux-ARMv7-ls
[0x0000c268]> aa
[x] Analyze all flags starting with sym. and entry0 (aa)
[0x0000c268]> s main
[0x00009eb8]> ax~main | head -n 4
         main 0x9eb8 ->      DATA -> 0xacfc case.default.0xab10+492
      main+32 0x9ed8 ->      CALL -> 0x14d78 entry1.init+35344
      main+36 0x9edc ->      DATA -> 0xadf4 case.default.0xab10+740
      main+44 0x9ee4 ->      CALL -> 0x9d7c sym.imp.setlocale
[0x00009eb8]> q

What is r2 to Firmware?

$ r2 -n vi-firmware-FORDBOARD.bin
[0x00000000]> aa
[x] Analyze all flags starting with sym. and entry0 (aa)
[0x00000000]> s main
Cannot seek to unknown address 'main'
[0x00000000]> ?E looks like you're going to reverse some FW. would you like some help?
 .--.     .-----------------------------------------------------------------------.
 | _|     |                                                                       |
 | O O   <  looks like you're going to reverse some FW. would you like some help? |
 |  |  |  |                                                                       |
 || | /   `-----------------------------------------------------------------------'
 |`-'|
 `---'
[0x00000000]>

Our r2 Commands

?     -- help postfix
s     -- seek
ax    -- list xrefs
axd p -- create data reference to <p>
aa    -- recursively analyze exports/syms and entrypoints
aap   -- find functions by searching for preludes
~     -- grep postfix
@     -- temporary seek postfix
~?    -- count lines postfix
*     -- postfix, echo in r2 commands
`     -- subshell
(     -- macros
@@    -- iterator
.     -- source output as r2 commands
e     -- set config
f     -- set a flag (aka named address)
om    -- setup a mapping of a file into virtual memory
?e    -- echo
?v    -- echo hex value
t     -- list defined types
tk    -- define type details
to    -- load type defines from .
pf    -- print formatted
pf.t  -- define named format
px    -- print (canonical) hexdump
pxw   -- print word-wise hexdump
pxr   -- print telescoping word references
pv4   -- print quadword value
pD n  -- disassemble <n> bytes

Why Reverse OpenXC?

  • OpenXC translates CAN signals into API messages for connected apps
  • The OpenXC FW contains the details to decode CAN contents into signals (this is not unique)
  • The OpenXC API messages are in Message Pack (like JSON)...
  • So, the CAN signal decoding has descriptive strings

If you haven't yet: please build and install r2 from git now : git clone https://github.com/radare/radare2; cd radare2 ; ./sys/install.sh. And also pip install r2pipe.

Download the target binary file at https://bit.ly/2ZtEJSH This is a 'FORDBOARD' vi-firmware device; here's the datasheet for a LPC1768 https://bit.ly/2XHemb5


Why Reverse OpenXC?

Open XC device firmwares embedded JSON CAN signal descriptions:

 "0x49": {
    "name": "decoder_test_message",
    "bus": "unfiltered_bus",
    "signals": {
        "TirePressure": {
            "generic_name": "tire_pressure_front_left",
            "decoder": "tirePressureDecoder",
            "bit_position": 56,
            "bit_size": 8
...

Into the firmware as structures

struct CanSignal {
    struct CanMessageDefinition* message;
    const char* genericName;
    uint8_t bitPosition;
    uint8_t bitSize;
    float factor;
...

Previous OpenXC Reversing

If you haven't yet: please build and install r2 from git now : git clone https://github.com/radare/radare2; cd radare2 ; ./sys/install.sh. And also pip install r2pipe.

Download the target binary file at https://bit.ly/2ZtEJSH This is a 'FORDBOARD' vi-firmware device; here's the datasheet for a LPC1768 https://bit.ly/2XHemb5


& Previous FW Reversing

Many, many; but, in particular: @devttys0's classes and blog, http://www.devttys0.com/ -- covers a multiple cases of loading raw FW blobs

If you haven't yet: please build and install r2 from git now : git clone https://github.com/radare/radare2; cd radare2 ; ./sys/install.sh. And also pip install r2pipe.

Download the target binary file at https://bit.ly/2ZtEJSH This is a 'FORDBOARD' vi-firmware device; here's the datasheet for a LPC1768 https://bit.ly/2XHemb5


& r2 ARM FW Incantations

"Reverse Engineering Embedded ARM Devices" -- @trufae (pancake) https://www.youtube.com/watch?v=oXSx0Qo2Upk

If you haven't yet: please build and install r2 from git now : git clone https://github.com/radare/radare2; cd radare2 ; ./sys/install.sh. And also pip install r2pipe.

Download the target binary file at https://bit.ly/2ZtEJSH This is a 'FORDBOARD' vi-firmware device; here's the datasheet for a LPC1768 https://bit.ly/2XHemb5


OpenXC Open Source

vi-firmware project on github: https://github.com/openxc/vi-firmware/

  • This is what we meant by soft target
  • We can look at the firmware 'blob' and confirm things in the source and/or intermediates
  • Or we can pretend we only have the blob (we'll do this)

If you haven't yet: please build and install r2 from git now : git clone https://github.com/radare/radare2; cd radare2 ; ./sys/install.sh. And also pip install r2pipe.

Download the target binary file at https://bit.ly/2ZtEJSH This is a 'FORDBOARD' vi-firmware device; here's the datasheet for a LPC1768 https://bit.ly/2XHemb5


Our Agenda In More Detail

  • Load raw firmware (FW) into r2
  • (eventually) Pretty-print data structures from the FW

Following a general process of reversing a FW blob

  • Get FW (skipped -- we'll use the open source build)
  • Learn architecture
  • Learn load address
  • Load FW into r2 at load address
  • Identify 'loader loops' and target addresses
  • Load FW copies into r2 at target addresses
  • Analyze (to get xrefs)
  • (eventually) Pretty-print data structures from the FW

If you haven't yet: please build and install r2 from git now : git clone https://github.com/radare/radare2; cd radare2 ; ./sys/install.sh. And also pip install r2pipe.

Download the target binary file at https://bit.ly/2ZtEJSH This is a 'FORDBOARD' vi-firmware device; here's the datasheet for a LPC1768 https://bit.ly/2XHemb5


Learn Architecture: How?

Different ways:

  • r2 way 1: p=i to summarize the invalid instructions in an image; change arch to find minimum
  • r2 way 2: pxAv to print categories of instructions
  • RTFM way: read the datasheet
  • tools way 1: https://github.com/airbus-seclab/cpu_rec
  • tools way 2: binwalk arch test
  • dumb way: strings (e.g. build settings strings)
  • eyeball way (seriously, there are some archs you can recognize by looking at the hex)
  • also don't forget this is an open source project, you can use the source.

Learn Architecture Solution 1/2: cpurec

$ python3 ~/src/cpu_rec/cpu_rec.py lib/vi-firmware-FORDBOARD.bin
lib/vi-firmware-FORDBOARD.bin
full(0x27f00)  ARMhf
chunk(0x1e000;60)   ARMhf

Learn Architecture Solution 2/2: r2 p=i

  • we knew from before this is ARM
  • ARM can have different instruction coding; fixed-width 32bit and variable width THUMB, so-called '16 bit'
  • block size should be small enough to 'see' the entire binary: b $s / X
  • e asm.bits matters alot ; e asm.cpu less so
[0x00000000]> b $s / 3
[0x00000000]> p=i
0x00000000 00 00ff |''''''''''''''''''''''''''''''''''|
0x0000d500 01 00ff |..................................................|
0x0001aa00 02 00ff |.__________________________________________________|
[0x00000000]> e asm.arch=arm
[0x00000000]> p=i
0x00000000 00 00ff |''''''''''''''''''''''''''''''''''|
0x0000d500 01 00ff |..................................................|
0x0001aa00 02 00ff |.__________________________________________________|
[0x00000000]> e asm.bits=16
[0x00000000]> p=i
0x00000000 00 0054 |''''____________|
0x0000d500 01 0016 |....|______________________
0x0001aa00 02 0086 |.__________________________|
[0x00000000]> e asm.cpu=cortex
[0x00000000]> p=i
0x00000000 00 0055 |''''____________|
0x0000d500 01 0018 |....|_____________________
0x0001aa00 02 0086 |.__________________________|
[0x00000000]>

Learn Load Address: How?

Different ways:

  • Datasheet way 1: where is the executable non-volatile storage? Where does it map to?
  • Datasheet way 2: where is the interrupt vector table (IVT) in memory? Do you see one in the bin?
  • Guessing way 1: Do you see something that could be an IVT? Do the vectors point somewhere useful? Could they point somewhere useful by changing them all by a constant offset?
  • Guessing way 2: Look for loader loops -- more on this later (we need it for .data and .rodata) setup
  • Point-n-pray way: https://github.com/sgayou/rbasefind
  • Also (don't forget): you can check the source code for the project -- kinda cheating though

Hands-On: Get the load address

Use one or more of the ways to get the load address of the bin.

Datasheet way 1: where is the executable non-volatile storage? Where does it map to?

Datasheet way 2: where is the interrupt vector table (IVT) in memory? Do you see one in the bin?

Guessing way 1: Do you see something that could be an IVT? Do the vectors point somewhere useful? Could they point somewhere useful by changing them all by a constant offset?

Guessing way 2: Look for loader loops -- more on this later (we need it for .data and .rodata) setup

Point-n-pray way: https://github.com/sgayou/rbasefind

Also (don't forget): you can check the source code for the project -- kinda cheating though


Hands-On Solution 1/2: IVT

  • lots of 0x00013XXX
  • could be offset 0x13000
  • rounder numbers are better though: 0x10000
$ r2 -n vi-firmware-FORDBOARD.bin
 -- Well, it looks like its working.
[0x00000000]> pxw
0x00000000  0x10008000 0x00013821 0x0001381f 0x0001381f  ....!8...8...8..
0x00000010  0x0001381f 0x0001381f 0x0001381f 0x00000000  .8...8...8......
0x00000020  0x00000000 0x00000000 0x00000000 0x0001381f  .............8..
0x00000030  0x0001381f 0x00000000 0x0001381f 0x00019261  .8.......8..a...
0x00000040  0x0001381f 0x0001381f 0x0001aa1d 0x0001381f  .8...8.......8..
0x00000050  0x0001381f 0x0001381f 0x00019429 0x0001381f  .8...8..)....8..
0x00000060  0x0001381f 0x0001381f 0x0001381f 0x0001381f  .8...8...8...8..
0x00000070  0x0001381f 0x0001381f 0x0001381f 0x0001381f  .8...8...8...8..
0x00000080  0x0001381f 0x0001381f 0x0001381f 0x0001381f  .8...8...8...8..
0x00000090  0x00019259 0x0001381f 0x0001381f 0x0001381f  Y....8...8...8..
0x000000a0  0x0001a2ff 0x00018b55 0x0001381f 0x0001381f  ....U....8...8..
0x000000b0  0x0001381f 0x0001381f 0x0001381f 0x0001381f  .8...8...8...8..
0x000000c0  0x0001381f 0x0001381f 0x00019253 0x4c05b510  .8...8..S......L
0x000000d0  0xb9337823 0xb1134b04 0xf3af4804 0x23018000  #x3..K...H.....#
0x000000e0  0xbd107023 0x10005100 0x00000000 0x00032bd0  #p...Q.......+..
0x000000f0  0x4b06b508 0x4806b11b 0xf3af4906 0x48068000  ...K...H.I.....H
[0x00000000]>

Hands-On Solution 2/2: rbasefind

  • Works here; but it will take a long time
$ ~/src/rbasefind/target/debug/rbasefind vi-firmware-FORDBOARD.bin
Located 355 strings
Located 26728 pointers
Scanning with 8 threads...
 0x00010000: 304
0x00011000: 20
0x0ffe3000: 16
0x0ffe2000: 16
0x00012000: 11
0x0000f000: 11
0x0ffe4000: 8
0xf7fd2000: 4
0xf7fd1000: 4
0xeffe2000: 4
1d [bengardiner:/mnt … entations/r2 OpenXC Workshop/lib] 7m33s $

Load FW into r2

We know the FW is ARM, THUMB and should be loaded at 0x10000

r2 -m 0x10000 -a arm -b 16 vi-firmware-FORDBOARD.bin

Create a Project Setup File

Eventually you'll need to script the setup; a file script is needed. This is equivalent to the above. Here's my stage1.r2:

e asm.arch = arm
e asm.cpu = cortex
e asm.bits = 16

e bin.baddr = 0x00010000
f flash = 0x00010000
e io.va = true

on vi-firmware-FORDBOARD.bin flash r-x

?e ===

You can download my stage1.r2 here: https://tinyurl.com/yxonhpjt


Next Steps: Analysis

We can search the flash section for functions by matching function preludes with aap.

[0x10002d80]> aa?~ aap
| aap                 find and analyze function preludes

ARM uses multi-instruction loads of 'pointers' so emulation is required to pick up xrefs of code reffering to data. Use aae.

[0x00000000]> aa?~ aae
| aae [len] ([addr])  analyze references with ESIL (optionally to address)

You can download my stage1.r2 here: https://tinyurl.com/yxonhpjt


Problem: no xrefs

download my stage1.r2 here: https://tinyurl.com/yxonhpjt if you haven't made your own

  • We know there is an array of CAN signals in memory somewhere
  • This array presumably makes references to strings that describe CAN signals. e.g. 'tire', 'speed', 'etc'

But only code making data references to the strings right now.

$ r2 -i stage1.r2 --
3
===
 -- Thank you for using radare2. Have a nice night!
[0x00000000]> aap
[>] Scanning r-x 0x10000 - 0x37f00 done
Analyzed 459 functions based on preludes
[0x00000000]> aae
[0x00000000]> f~str~tire
0x000301e9 13 str.tire_pressure
0x00030242 25 str.tire_pressure_front_right
0x0003025c 23 str.tire_pressure_rear_left
0x00030274 24 str.tire_pressure_rear_right
0x0003028d 24 str.tire_pressure_front_left
[0x00000000]> axt @@= `f~str~tire`
fcn.00014f4c 0x14fce [DATA] ldr r0, str.tire_pressure
fcn.00014f4c 0x14fce [DATA] ldr r0, str.tire_pressure
fcn.00014f4c 0x14f98 [DATA] ldr r1, str.tire_pressure_front_right
fcn.00014f4c 0x14f98 [DATA] ldr r1, str.tire_pressure_front_right
fcn.00014f4c 0x14fa8 [DATA] ldr r1, str.tire_pressure_rear_left
fcn.00014f4c 0x14fa8 [DATA] ldr r1, str.tire_pressure_rear_left
fcn.00014f4c 0x14fb8 [DATA] ldr r1, str.tire_pressure_rear_right
fcn.00014f4c 0x14fb8 [DATA] ldr r1, str.tire_pressure_rear_right
fcn.00014f4c 0x14f86 [DATA] ldr r1, str.tire_pressure_front_left
fcn.00014f4c 0x14f86 [DATA] ldr r1, str.tire_pressure_front_left

Problem Analysis

  • We have the right base address; but there are no data xrefs to strings.
  • This is a bad sign; string cross-references are pretty important for disassembly
  • The reason for this situation: the code references 'copies' of the strings in RAM

Getting 'Copies'

  • The binary's 'writeable' data will live in RAM
  • The initial values of these are copied from non-volatile (e.g. flash) to RAM
  • e.g. C programs get this as part of the C common runtime libs; i.e. the compiler does it for the programmer

Identifying 'Loader Loops'

Different ways:

  • Datasheet way: starting at the reset vector, follow function calls until you see a function that has 1 or two large copying of arrays followsd by a large zeroing of a region of memory
    • NB: ARM Thumb mode calls are a branch/jump to address+1 (i.e. any odd address is a THUMB mode call to the that address-1)
  • Guessing way: scan the binary for a function that has 1 or two large copying etc. -- start at the beginning and end of the binary.
  • Also: this is an OSS project, you can use the source

Hand-on: Get the Addresses

The FW copies 0x____ to 0x________ for a length of 0x____

The loader loop will look kinda like this:

            0x...    push {r3, lr}
            0x...    ...
        ┌─> 0x...    ldr r2, [...]
        ╎   0x...    ldr r1, [...]
        ╎   0x...    adds r0, r3, r2
        ╎   0x...    cmp r0, r1
       ┌──< 0x...    bhs 0x...
       │╎   0x...    ldr r1, [...]
       │╎   0x...    ldr r1, [r3, r1]
       │╎   0x...    str r1, [r3, r2]
       │╎   0x...    adds r3, 4
       │└─< 0x...    b 0x...
       └──> 0x...    ldr r3, [...]
        ┌─> 0x...    ldr r2, [...]
        ╎   0x...    cmp r3, r2
       ┌──< 0x...    bhs 0x...
       │╎   0x...    movs r2, 0
       │╎   0x...    str r2, [r3], 4
       │└─< 0x...    b 0x...
       └──> 0x...    ...

Hand-on Solution

The FW copies 0x32ebc to 0x100000c8 for a length of 0x4558

     0x00013820    08b5       push {r3, lr}
     0x00013822    07f039f9   bl 0x1aa98              ;[1]
     0x00013826    0c4b       ldr r3, [0x00013858]    ; [0x13858:4]=0xe000ed00
     0x00013828    0c4a       ldr r2, [0x0001385c]    ; [0x1385c:4]=0x10000 flash
     0x0001382a    9a60       str r2, [r3, 8]
     0x0001382c    0023       movs r3, 0
 ┌─> 0x0001382e    0c4a       ldr r2, [0x00013860]    ; [0x13860:4]=0x100000c8
 ╎   0x00013830    0c49       ldr r1, [0x00013864]    ; [0x13864:4]=0x10004620
 ╎   0x00013832    9818       adds r0, r3, r2
 ╎   0x00013834    8842       cmp r0, r1
┌──< 0x00013836    04d2       bhs 0x13842
│╎   0x00013838    0b49       ldr r1, [0x00013868]    ; [0x13868:4]=0x32ebc
│╎   0x0001383a    5958       ldr r1, [r3, r1]
│╎   0x0001383c    9950       str r1, [r3, r2]
│╎   0x0001383e    0433       adds r3, 4
│└─< 0x00013840    f5e7       b 0x1382e
└──> 0x00013842    0a4b       ldr r3, [0x0001386c]    ; [0x1386c:4]=0x10005100
 ┌─> 0x00013844    0a4a       ldr r2, [0x00013870]    ; [0x13870:4]=0x1000674c
 ╎   0x00013846    9342       cmp r3, r2
┌──< 0x00013848    03d2       bhs 0x13852
│╎   0x0001384a    0022       movs r2, 0
│╎   0x0001384c    43f8042b   str r2, [r3], 4
│└─< 0x00013850    f8e7       b 0x13844
└──> 0x00013852    00f0bdff   bl 0x147d0              ;[2]

Load FW Copies into r2

We add mappings to r2 using the file mappings om command.

[...]> om?~ om
| om fd vaddr [size] [paddr] [rwx] [name]  create new io map

The FW copies 0x32ebc to 0x100000c8 for a length of 0x4558


Hand-on: Update your project file

download the stage1.r2 here: https://tinyurl.com/yxonhpjt

  • Add a 'flag' at 0x100000c8 called data with command f
  • map the copy with om
  • auto-analyze based on function preludes with aap
  • get some code-to-data xrefs with aae

NB:

The FW copies 0x32ebc to 0x100000c8 for a length of 0x4558


Hands-on Solution

Here's the changes to the project file (just the tail). My stage2.r2

$ tail stage2.r2
f data = 0x100000c8
om 3 data 0x4558 `?v 0x32ebc - flash` mrw- DATA

aap
aae

?e ===

Data References for Data

Let's fast-forward a bit. There is an array of CAN signals in this firmware. It starts at 0x10002D80. Each element in the array will be an instance of this structure.

You could figure all this out without the source code; it would take alot of grinding in reversing the firmware. But it is totally possible.

struct CanSignal {
      struct CanMessageDefinition* message;
      const char* genericName;
      uint8_t bitPosition;
      uint8_t bitSize;
      float factor;
...

So...

  • the first word will be a pointer to structure (some data address).
  • the second word will be a pointer to a string

How Are We Doing So Far?

Ideally we would have:

  • xrefs (remember? they are important) for both code ref'ing {code, data} and data ref'ing data
  • data in the dissasembly -- otherwise we could mistake data for code

We can investigate by using:

  • ax and ~ to grep for things -- alternative is axt@ to ask about references TO an address
  • pD 8 to check how r2 is dissassembling the location of the CanSignal array.
  • pxr 8 to 'telescope': what do references to references to references (etc.) look like.

How Are We Doing So Far?

Ideally:

xrefs for both code ref'ing {code, data} and data ref'ing data

data in the dissasembly -- otherwise we could mistake data for code

[0x00000000]> f CanSignals_start = 0x10002D80
[0x00000000]> s CanSignals_start
[0x10002d80]> pD 8
            ;-- CanSignals_start:
            0x10002d80      cc2c           cmp r4, 0xcc                ; 204
            0x10002d82      0010           asrs r0, r0, 0x20
            0x10002d84      9404           lsls r4, r2, 0x12
            0x10002d86      0300           movs r3, r0
[0x10002d80]> ax~0x10002d80
[0x10002d80]> ax~CanSignals_start

So: NOT GREAT...


Improving Analysis

Craig Heffner (@devttys0) has a simple but incredibly useful plugin for grooming all data as 32bit offsets called codatify. This results in highlighting potentially useful references while exploring new firmwares.

In r2 we can emulate this plugin by formatting each address with Cd and creating data xrefs with axd -- iterating over the target addresses using the @@ modifier.

Iterating over the address to assign each as a dword with Cd works fine, but creating a data xref at each address requires evaluating a command and using the evaluation as an argument to create data xref.


Improving Analysis

Creating data xrefs with axd is where the iterate command falls apart. We must create a script to drive r2; we can use lots of language bindings, but we'll choose to use python.

import r2pipe
import sys

r2p=r2pipe.open()  # open without arguments only for #!pipe

for a in range(int(sys.argv[1], 0), int(sys.argv[2], 0), 4):
  r2p.cmd('s 0x%x' % a)
  pointer = r2p.cmdj('pfj p4')
  r2p.cmd('Cd 4')
  r2p.cmd('axd 0x%x' % pointer[0].get('value'))

available here: https://tinyurl.com/y5du6fu6.

This is how r2pipe scripts get called from radare:

#!pipe python ...

Hands-On: Groom that data

Create a codatify script of your owm or download from here: https://tinyurl.com/y5du6fu6

  • iterate over all 4 byte words in a range given in argv[1] to argv[2]
  • mark each as data with Cd
  • get the value of those bytes as a word. e.g. pvw, px, etc
  • make a data xref based on that pointer value with axd

Update Your Project File

  • Groom the data 'copy' you just added by calling that script. Add this to your project file
#!pipe python codatafy.py `?v data` `?v data+0x4558`
  • move the analysis to after the grooming

NB: you need to install: pip install r2pipe


Hands-On Solution

Here's my stage3.r2 (the tail of it):

$ tail stage3.r2
f data = 0x100000c8
om 3 data 0x4558 `?v 0x32ebc - flash` mrw- DATA

#!pipe python codatafy.py `?v data` `?v data+0x4558`

aap
aae

?e ===

You can download my stage3.r2 here: https://tinyurl.com/y6fr67by


How Are We Doing So Far?

Ideally:

xrefs for both code ref'ing {code, data} and data ref'ing data

data in the dissasembly -- otherwise we could mistake data for code

$ r2 -i stage3.r2 --
3
[>] Scanning r-x 0x10000 - 0x37f00 done
Analyzed 459 functions based on preludes
===
 -- For a full list of commands see `strings /dev/urandom`
[0x1000461c]> f CanSignals_start = 0x10002D80
[0x1000461c]> s CanSignals_start
[0x10002d80]> pD 8
    ;-- CanSignals_start:
    ; DATA XREF from fcn.00015064 (0x1506a)
    ; DATA XREF from fcn.00015088 (0x150c0)
    0x10002d80      .dword 0x10002ccc
    ;-- str.signal1:
    ; DATA XREF from fcn.00015088 (+0x1e0)
    0x10002d84     .string "signal1" ; len=7
[0x10002d80]> ax~0x10002d80
      fcn.00015064+6 0x1506a ->      DATA -> 0x10002d80 CanSignals_start
     fcn.00015088+56 0x150c0 ->      DATA -> 0x10002d80 CanSignals_start
 CanSignals_start 0x10002d80 ->      DATA -> 0x10002ccc str.Specified_string...

You can download my stage3.r2 here: https://tinyurl.com/y6fr67by

So... BETTER :)


Better

Even 'telescoping' works pretty well.

[0x10002d80]> pxrq 16
WARNING: r_bin_get_section_at: assertion 'o' failed (line 809)
WARNING: r_bin_get_section_at: assertion 'o' failed (line 809)
WARNING: r_bin_get_section_at: assertion 'o' failed (line 809)
WARNING: r_bin_get_section_at: assertion 'o' failed (line 809)
0x10002d80 0x10002ccc  .,.. @CanSignals_start R 0x1000328c -->  R 0x7a120
0x10002d84 0x00030494  .... @str.signal1 fcn.00029448 R X 'ldr r3, [r6, 0x14]' (sig..
0x10002d88 0x00000c34  4... 0
0x10002d8c 0x3f800000  ...? 0
[0x10002d80]> pxw 16
0x10002d80  0x10002ccc 0x00030494 0x00000c34 0x3f800000  .,......4......?

Recall: What Are We Trying To Do?

Open XC device firmwares embedded JSON CAN signal descriptions:

 "0x49": {
    "name": "decoder_test_message",
    "bus": "unfiltered_bus",
    "signals": {
        "TirePressure": {
            "generic_name": "tire_pressure_front_left",
            "decoder": "tirePressureDecoder",
            "bit_position": 56,
            "bit_size": 8
...

Into the firmware as structures

struct CanSignal {
    struct CanMessageDefinition* message;
    const char* genericName;
    uint8_t bitPosition;
    uint8_t bitSize;
    float factor;
...

What Are We Trying To Do?

Extract CAN Signals From the Firmware

Print the CAN signal information in human-readable form.


Structures in r2

Radare2 knows about types and can pretty-print based on possibly-compound type information

$ r2 -i stage3.r2 --
3
[>] Scanning r-x 0x10000 - 0x37f00 done
Analyzed 459 functions based on preludes
===
 -- I am Pentium of Borg. Division is futile. You will be approximated.
[0x1000461c]> t?
Usage: t   # cparse types commands
| t                          List all loaded types
| tj                         List all loaded types as json
| t <type>                   Show type in 'pf' syntax
| t*                         List types info in r2 commands
| t- <name>                  Delete types by its name
| t-*                        Remove all types
| ta <type>                  Mark immediate as a type offset
| tc[type.name]              List all/given types in C output format
| te[?]                      List all loaded enums
| td[?] <string>             Load types from string
| tf                         List all loaded functions signatures
| tk <sdb-query>             Perform sdb query
| tl[?]                      Show/Link type to an address
| tn[?] [-][addr]            manage noreturn function attributes and marks
| to -                       Open cfg.editor to load types
| to <path>                  Load types from C header file
| toe[type.name]             Open cfg.editor to edit types
| tos <path>                 Load types from parsed Sdb database
| tp  <type> [addr|varname]  cast data at <address> to <type> and print it
| tpx <type> <hexpairs>      Show value for type with specified byte sequence
| ts[?]                      Print loaded struct types
| tu[?]                      Print loaded union types
| tx[f?]                     Type xrefs
| tt[?]                      List all loaded typedefs

Structures in r2

Radare2 can parse c struct definitions and use it's in-built definitions of basic types.

You could create a structure definition by doing some long RE. But we'll fast forward a bit and try to use the header file from the Open XC project. canutil.h

Of course, trying to load the structures directly from the openxc source tree won't work.

[0x1000461c]> to /home/bengardiner/src/vi-firmware/src/can/canutil.h
#include "/usr/include/stdint.h"
In file included from /home/bengardiner/src/vi-firmware/src/can/canutil.h:4:
/usr/include/stdint.h:26: error: include file '/usr/include/bits/libc-header-sta...
In file included from /home/bengardiner/src/vi-firmware/src/can/canutil.h:4:
/usr/include/stdint.h:27: error: include file '/usr/include/bits/types.h' not fo...
In file included from /home/bengardiner/src/vi-firmware/src/can/canutil.h:4:
/usr/include/stdint.h:28: error: include file '/usr/include/bits/wchar.h' not fo...
In file included from /home/bengardiner/src/vi-firmware/src/can/canutil.h:4:
/usr/include/stdint.h:29: error: include file '/usr/include/bits/wordsize.h' not...
In file included from /home/bengardiner/src/vi-firmware/src/can/canutil.h:4:
/usr/include/stdint.h:31: error: declaration expected

Structures in r2

But we can prepare the structures a little and then we'll have some structure definitions loaded for pretty printing.

  • Define bool
  • Stub typedef function pointers
  • Remove const on e.g. const char *
  • Use struct instances instead of typedefs
  • Manually correct the packing/alignment that results

You can download mine here: https://tinyurl.com/y6kpcdn6

After which, radare2 is happy to give us some nice print formats from structs

$ r2 -i stage3.r2 --
3
[>] Scanning r-x 0x10000 - 0x37f00 done
Analyzed 459 functions based on preludes
===
 -- Insert coin to continue ...
[0x1000461c]> to ./canutil.h
[0x1000461c]> t CanSignal
pf pzbbffff?ddpbdppdf message genericName bitPosition bitSize factor offset minValue 
maxValue (FrequencyClock)frequencyClock sendSame forceSendChanged states stateCount
writable decoder encoder received lastValue

Structures in r2: Pointer Sizes

Final wrinkle: since we were analyzing a ARM THUMB binary, we set the e asm.bits=16 but this has an undesirable side-effect: all pointers in the pretty-printing are set to 16-bits wide. We can fix this by setting e asm.bits=32 while pretty-printing.

[0x1000461c]> s CanSignals_start
[0x10002d80]> e asm.bits=32
[0x10002d80]> .t CanSignal
          message : 0x10002d80 = 0x10002ccc
      genericName : 0x10002d84 = "\x94\x04\x03"
      bitPosition : 0x10002d88 = 0x34
          bitSize : 0x10002d89 = 0x0c
           factor : 0x10002d8a = 0
           offset : 0x10002d8e = 2.27795078e-41
         minValue : 0x10002d92 = 0
         maxValue : 0x10002d96 = 0
   frequencyClock :
                struct<FrequencyClock>
                 frequency : 0x10002d9a = 0
                  lastTick : 0x10002d9e = (qword)0x0000000000000000
              timeFunction : 0x10002da6 = 0x00010000
         sendSame : 0x10002daa = 0
 forceSendChanged : 0x10002dae = 0
           states : 0x10002db2 = 0x00000000
       stateCount : 0x10002db6 = 0x00
         writable : 0x10002db7 = 0
          decoder : 0x10002dbb = 0x00000000
          encoder : 0x10002dbf = 0x00000000
         received : 0x10002dc3 = 2945024
        lastValue : 0x10002dc7 = 3.83654982e-37

How Are We Doing So Far?

[0x1000461c]> s CanSignals_start
[0x10002d80]> e asm.bits=32
[0x10002d80]> .t CanSignal
          message : 0x10002d80 = 0x10002ccc
      genericName : 0x10002d84 = "\x94\x04\x03"
      bitPosition : 0x10002d88 = 0x34
          bitSize : 0x10002d89 = 0x0c
           factor : 0x10002d8a = 0
           offset : 0x10002d8e = 2.27795078e-41
         minValue : 0x10002d92 = 0
         maxValue : 0x10002d96 = 0
   frequencyClock :
                struct<FrequencyClock>
                 frequency : 0x10002d9a = 0
                  lastTick : 0x10002d9e = (qword)0x0000000000000000
              timeFunction : 0x10002da6 = 0x00010000
         sendSame : 0x10002daa = 0
 forceSendChanged : 0x10002dae = 0
           states : 0x10002db2 = 0x00000000
       stateCount : 0x10002db6 = 0x00
         writable : 0x10002db7 = 0
          decoder : 0x10002dbb = 0x00000000
          encoder : 0x10002dbf = 0x00000000
         received : 0x10002dc3 = 2945024
        lastValue : 0x10002dc7 = 3.83654982e-37

Which is OK; but not GREAT.

  • no clear arbitration id
  • lots of 'noise' fields

We can define our own pretty printing formats by taking the output of t CanSignal as a starting point.


Hands-on: Improve the Pretty Printing

  • download the canutil.h (or perform the .h grooming yourself) https://tinyurl.com/y6kpcdn6
  • update your project file
    • add a flag for start of can signals at 0x10002D80
    • load the structures from canutil.h with to
    • set pointer width to 4 bytes e asm.bits =
  • starting from t CanSignal output, create your own prettier printing. Look at pf??.
  • Remove 'noise' fields by deleting them from the named list and replacing their print encodings with : until only these fields remain:
    • name / bitPosition / bitSize / factor / offset
  • Name it JustCanSignal with pf.myformat ...

For reference:

pf pzbbffff?ddpbdppdf message genericName bitPosition bitSize factor offset minValue 
maxValue (FrequencyClock)frequencyClock sendSame forceSendChanged states stateCount
writable decoder encoder received lastValue

Hands-on Solution

$ tail stage4.r2
aap
aae

f CanSignals_start = 0x10002D80
to canutil.h
e asm.bits=32
pf.JustCanSignal *x*zN1N1..ff::::::.::::: (CanMessageDefinition)message genericName
bitPosition bitSize factor offset

?e ===

Download it here: https://tinyurl.com/y2kngz5l

Here it is in action:

$ r2 -i stage4.r2 --
3
[>] Scanning r-x 0x10000 - 0x37f00 done
Analyzed 459 functions based on preludes
===
 -- Starting bitcoin miner in background...
[0x1000461c]> s CanSignals_start
[0x10002d80]> pf.JustCanSignal
(CanMessageDefinition)message : (*0x10002ccc)0x10002d80 = 0x1000328c
 genericName : (*0x30494)0x10002d84 = "signal1"
 bitPosition : 0x10002d88 = 52
     bitSize : 0x10002d89 = 12
      factor : 0x10002d8c = 1
      offset : 0x10002d90 = 0

How Are We Doing So Far

[X] name

[X] bitPosition

[X] bitSize

[X] factor

[X] offset

[ ] arbitration ID


Printing Arbitration IDs

The first element of the CanSignal structure is a pointer to a CanMessageDefinition

[0x10002d80]> pvw 4
0x10002ccc
[0x10002d80]> s `pvw 4`
[0x10002ccc]> t CanMessageDefinition
pf pdE?d[8]b bus id (CanMessageFormat)format (FrequencyClock)frequencyClock 
forceSendChanged lastValue
[0x10002ccc]> .t CanMessageDefinition
Cannot find format for struct `CanMessageFormat`
              bus : 0x10002ccc = 0x1000328c
               id : 0x10002cd0 = 66
           format : 0x10002cd4 = format (enum CanMessageFormat) = 0x0 ; STANDARD
   frequencyClock :
                struct<FrequencyClock>
                 frequency : 0x10002cd5 = 0
                  lastTick : 0x10002cd9 = (qword)0x0000000000000000
              timeFunction : 0x10002ce1 = 0x01000000
 forceSendChanged : 0x10002ce5 = 0
        lastValue : 0x10002ce9 = [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c ]
[0x10002ccc]>

We really only want the id field; we also would (ideally) like to print id right next to the bit-positions, scale & offset. So we add a struct dereference to it:

[0x10002ccc]> s-
[0x10002d80]> pf.JustCanMessageArbId :x.::[8]: id
[0x10002d80]> pf *?*zN1N1..ff::::::.::::: (JustCanMessageArbId)message genericName
bitPosition bitSize factor offset
...

What happened?


Limitation

There is a limitation in r2 (for now -- they've fixed all the previous ones). The format printer format string can have either a pointer to a structure OR a pointer to a string; but not both.

[0x10002d80]> pf *x*zN1N1..ff::::::.::::: (CanMessageDefinition)message genericName 
bitPosition bitSize factor offset
(CanMessageDefinition)message : (*0x10002ccc)0x10002d80 = 0x1000328c
 genericName : (*0x30494)0x10002d84 = "signal1"
 bitPosition : 0x10002d88 = 52
     bitSize : 0x10002d89 = 12
      factor : 0x10002d8c = 1
      offset : 0x10002d90 = 0
[0x10002d80]> pf *?*zN1N1..ff::::::.::::: (JustCanMessageArbId)message genericName
bitPosition bitSize factor offset
[0x10002d80]> pf *?xN1N1..ff::::::.::::: (JustCanMessageArbId)message genericName
bitPosition bitSize factor offset
     message : (*0x10002ccc)
                struct<JustCanMessageArbId>
    id : 0x10002cd0 = 0x00000042
 genericName : 0x10002d84 = 0x00030494
 bitPosition : 0x10002d88 = 52
     bitSize : 0x10002d89 = 12
      factor : 0x10002d8c = 1
      offset : 0x10002d90 = 0

Workaround

The workaround is to split up that format statement and 'print twice.'

[0x10002d80]> pf *? (JustCanMessageArbId)message
 message : (*0x10002ccc)
                struct<JustCanMessageArbId>
    id : 0x10002cd0 = 0x00000042
[0x10002d80]> pf *zN1N1..ff::::::.::::: genericName bitPosition bitSize factor 
offset @+4
 genericName : (*0x30494)0x10002d84 = "signal1"
 bitPosition : 0x10002d88 = 52
     bitSize : 0x10002d89 = 12
      factor : 0x10002d8c = 1
      offset : 0x10002d90 = 0

Hands-On

Update your project file with format statements for:

  • the pointer to the message id
  • the remainder of the structure
  • define a macro (name, command1, command2) and run the macro to print the first CanSignals entry.

For reference:

  • prints just the id in a CanMessageDefinition: pf.JustCanMessageArbId :x.::[8]: id
  • prints the remaining fields of interest in the array: pf.JustRestOfCanSignal *zN1N1..ff::::::.::::: genericName bitPosition bitSize fact or offset
  • the two step printing is pf *? (JustCanMessageArbId)message then pf ? (JustRestOfCanSignal)signal @+4

Hands-On Solution

$ tail stage5.r2
e asm.bits=32
pf.JustCanSignal *x*zN1N1..ff::::::.::::: (CanMessageDefinition)message genericNam
e bitPosition bitSize factor offset

pf.JustCanMessageArbId :x.::[8]: id
pf.JustRestOfCanSignal *zN1N1..ff::::::.::::: genericName bitPosition bitSize fact
or offset

(printSignal, pf *? (JustCanMessageArbId)message, pf ? (JustRestOfCanSignal)signal
 @+4)

?e ===

You can download it here: https://tinyurl.com/y4fnkpnr

$ r2 -i stage5.r2 --
[...]
[0x1000461c]> s CanSignals_start
[0x10002d80]> .(printSignal)
 message : (*0x10002ccc)
                struct<JustCanMessageArbId>
    id : 0x10002cd0 = 0x00000042
 signal :
                struct<JustRestOfCanSignal>
             genericName : (*0x30494)0x10002d84 = "signal1"
             bitPosition : 0x10002d88 = 52
                 bitSize : 0x10002d89 = 12
                  factor : 0x10002d8c = 1
                  offset : 0x10002d90 = 0

Printing the whole array

[0x10002d80]> .(printSignal) @@s:CanSignals_start 0x1000328c 0x44
 message : (*0x10002ccc)
                struct<JustCanMessageArbId>
    id : 0x10002cd0 = 0x00000042
 signal :
                struct<JustRestOfCanSignal>
             genericName : (*0x30494)0x10002d84 = "signal1"
             bitPosition : 0x10002d88 = 52
                 bitSize : 0x10002d89 = 12
                  factor : 0x10002d8c = 1
                  offset : 0x10002d90 = 0
 message : (*0x10002cf0)
                struct<JustCanMessageArbId>
    id : 0x10002cf4 = 0x00000049
 signal :
                struct<JustRestOfCanSignal>
             genericName : (*0x3028d)0x10002dc8 = "tire_pressure_front_left"
             bitPosition : 0x10002dcc = 56
                 bitSize : 0x10002dcd = 8
                  factor : 0x10002dd0 = 1
                  offset : 0x10002dd4 = 0
[...]

Thank you(s)

  • My Previous Employer: Irdeto
  • Ford (in general). OpenXC people & Eric Marsman (in particular).

Our r2 Commands

?     -- help postfix
s     -- seek
ax    -- list xrefs
axd p -- create data reference to <p>
aa    -- recursively analyze exports/syms and entrypoints
aap   -- find functions by searching for preludes
~     -- grep postfix
@     -- temporary seek postfix
~?    -- count lines postfix
*     -- postfix, echo in r2 commands
`     -- subshell
(     -- macros
@@    -- iterator
.     -- source output as r2 commands
e     -- set config
f     -- set a flag (aka named address)
om    -- setup a mapping of a file into virtual memory
?e    -- echo
?v    -- echo hex value
t     -- list defined types
tk    -- define type details
to    -- load type defines from .
pf    -- print formatted
pf.t  -- define named format
px    -- print (canonical) hexdump
pxw   -- print word-wise hexdump
pxr   -- print telescoping word references
pv4   -- print quadword value
pD n  -- disassemble <n> bytes
e asm.arch = arm
e asm.cpu = cortex
e asm.bits = 16
e bin.baddr = 0x10000
f flash = 0x10000
e io.va = true
on vi-firmware-FORDBOARD.bin flash r-x
omn flash TEXT
f data = 0x100000c8
om 3 data 0x4558 `?v 0x32ebc - flash` mrw- DATA
#!pipe python codatafy.py `?v data` `?v data+0x4558`
aap
aae
f CanSignals_start = 0x10002D80
to canutil.h
e asm.bits=32
pf.JustCanSignal *x*zN1N1..ff::::::.::::: (CanMessageDefinition)message genericName bitPosition bitSize factor offset
pf.JustCanMessageArbId :x.::[8]: id
pf.JustRestOfCanSignal *zN1N1..ff::::::.::::: genericName bitPosition bitSize factor offset
(printSignal, pf *? (JustCanMessageArbId)message, pf ? (JustRestOfCanSignal)signal @+4)
?e ===
#define CAN_MESSAGE_SIZE 8
#define bool int
typedef void* (*SignalEncoder)();
typedef void* (*SignalDecoder)();
typedef unsigned long (*TimeFunction)();
struct FrequencyClock {
float frequency;
unsigned long lastTick;
TimeFunction timeFunction;
};
struct CanSignalState {
int value;
char* name;
};
enum CanMessageFormat {
STANDARD,
EXTENDED,
};
struct CanSignal {
struct CanMessageDefinition* message;
char* genericName;
uint8_t bitPosition;
uint8_t bitSize;
float factor;
float offset;
float minValue;
float maxValue;
struct FrequencyClock frequencyClock;
bool sendSame;
bool forceSendChanged;
struct CanSignalState* states;
uint8_t stateCount;
bool writable;
SignalDecoder decoder;
SignalEncoder encoder;
bool received;
float lastValue;
};
struct CanMessageDefinition {
struct CanBus* bus;
uint32_t id;
enum CanMessageFormat format;
struct FrequencyClock frequencyClock;
bool forceSendChanged;
uint8_t lastValue[CAN_MESSAGE_SIZE];
};
import r2pipe
import sys
r2p=r2pipe.open()
for a in range(int(sys.argv[1], 0), int(sys.argv[2], 0), 4):
r2p.cmd('s 0x%x' % a)
pointer = r2p.cmdj('pfj p4')
r2p.cmd('Cd 4')
r2p.cmd('axd 0x%x' % pointer[0].get('value'))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment