Some years ago my Boosted Board v1 stopped charging — while it’s one of the best electric skateboards I’ve ever ridden due to being built on the bamboo-based Loaded Longboards (which I had growing up), the company unfortunately went under in 2020.
So now despite the only issues being the battery cells needing replacement, I’m stuck with a $2000 paperweight.
At the time I was reverse engineering a lot of embedded devices for work, so thought I’d try my hand at regaining access to the board to see if there was a way to clear the error and get things working again.
The following is a copy-and-paste from my internal notes, they’re a bit messy and scattered stream-of-consciousness style, but thought others might find tidbits useful — I’ll likely clean it up and resume the project one day once I’m done building Dopplio.
Summary of work left to do:
- Rig up voltage divider so each battery cell sensor sees the right voltage indicating a healthy battery
- Figure out how to clear the error on Boosted’s proprietary BMS (hardest part)
- Finally replace the battery cells by spot-welding new ones in
If you’re interested in more things like this you can follow me on Twitter
Notes Dump
Resources:
https://harrisonsand.com/can-on-the-raspberry-pi/
Had to use Torx screw TR30 from Husky screwdriver set, then had to stick the little bendy knife blade thing in between and scrape through all the gunk, then pry off using the long screwdriver. Eventually came off.
Chip: Microchip PIC32MX795F Datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/60001156J.pdf
Seems to be some debug headers, chip supports CAN protocol.
ToDo: Download play store app and see if we can download firmware update from there instead of getting our hands dirty w/ hardware. Reverse command to 'reset' bms, then replace cells and should be good I think.
Downloaded apk, browsing using jadx-gui (apktool only generated smali files, and jadx (non-gui) worked but didn't want to browse files in the terminal).
Couldn't find URL in APK, big project and potentially h idden.
Emulation was hard to get working.
Used emmas phone, and set up proxy by long-pressing on wifi network and setting manual proxy. Then installed Burp suite and made sure it was "listening on all interfaces", and set the HTTPS Certificate to "Generate CA-signed per-host certificates". Then went on phone, and navigated to proxy ip/port (192.168.1.7:8888) and installed the Burp CERT,(both for VPN & Apps, and Wifi).
Then was able to see HTTPS traffic coming through the proxy. Now just having trouble connecting to the board itself (since it's dead). Might need Jordans.
- - Attempt 2 to reverse statically, some obfuscation and unsuccessfully decompiled code. Think dynamic instrumentation with Frida is the next move.
Random: Use IL Spy to reverse .NET games
[REDACTED] would patch the android app instead of dynamically instrumenting it w/ frida
- says I need a proguard deobfuscator
Update 1/24/20 - Used jadx-gui to get java source code, which I then imported into Eclipse for references. Made things much easier. THe names are obfuscated which makes things hard, and I've found functions and objects relating to the Firmware creation, it could be do-able with some time and a bigger screen / mouse to do statically, but debugging might be better.
Good guide on Android cracking: http://androidcracking.blogspot.com/2010/09/examplesmali.html
https://blog.bramp.net/post/2015/08/01/decompile-and-recompile-android-apk/
https://kov4l3nko.github.io/blog/2018-01-23-debugging-android-apps-from-first-instruction/
download: Android Hacker's Handbook - guide on dalvik debugging
Q: How can gdb / ida step through dalvik VM bytecode? Is that what we need to see what's happening?
A: Dalvik implements JDWP (Java Debug Wire Protocol) which allows us to debug it. I guess IDA/gdb support this?
I seem to have already rooted the s6. Note: make sure to charge it up well before wanting to use it.
For some reason my re-built APKs aren't working. Rebuilding and not modifying AndroidManifest.xml seemed to work once I signed it. So it's the editing of AndroidManifest.xml and then rebuilding that's the issue. If I compare the AndroidManifest.xml in a working one and not working one, the working one has a random nonsense byte in between every character. It looks "compiled". This is probably because I was getting a weird 9patch error when I tried to just run
apktool d
Then editing the resouce and building from that. Instead, I decoded, copied the AndroidManifest.xml file out, then decoded with "-s -r" flags to not decompile the resources, then copied my AndroidManifest.xml file in, but now it seems it's not getting compiled.
To get working:
get apktool working, so that the normal decode works. Then you should be able to edit the AndroidManifest.xml file and add "android:debuggable='true'", recompile, resign, and it should be installable on the phone.
Just tried w/ latest apktool, did not work. Need to somehow skip 9patch PNGs and only decompile/recompile the AndroidManifest.xml
to get it working use a command of this sort (it's something related to aapt):
apktool b --use-aapt2
- rebuild w/ debugging and re-sign
- put ida's android_server on there
- connect to server and step through
linux may need to deal w/ NDK
recompiled with apktool and installed successfully for some reason when attaching debugger, app quits
Following example: https://finn.svbtle.com/remotely-debugging-android-binaries-in-ida-pro
Running into the same errors as him! ^^ "The file can't be loaded by the debugger plugin".
- *(adb) cat /proc/kmsg | grep "Restricted" **yields the same message, we're being screwed by the kernel and CONFIG_SEC_RESTRICT_FORK
root@zeroflte:/ # **getprop ro.build.version.release**
6.0.1
root@zeroflte:/ # **getprop ro.build.version.sdk**
23
1|root@zeroflte:/data # **cat /proc/version**
Linux version 3.10.61-10292505 (dpi@SWDD6919) (gcc version 4.9.x-google 20140827 (prerelease) (GCC) ) #1 SMP PREEMPT Fri Feb 24 12:32:56 KST 2017
Looks like I need to recompile the kernel w/o that value set in order to move forward (Samsung Galaxy S6)
https://opensource.samsung.com/uploadList seems to have the stock source code, searching for my model SM-G920I yields some results, but not sure which one to choose / what they all mean?
A: Naming is MODEL_REGION_ANDROIDVER
e.g. SM-G920I_SWA_MM = model SM-G920I (s6), SWA = South West Asia, MM = Marshmallow
So we want sm-g920I_SEA_MM I guess, apparently it's mostly related to FCC radio stuff anyway.
- -- Compiling kernel from source Download source Download ndk
export CROSS_COMPILE=/home/rmx/Documents/s6_kernel/android-ndk-r16b/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-
export ARCH=arm64
vim into "/home/rmx/Documents/s6_kernel/SM-G920I_SEA_MM_Opensource/kernel/Kernel/arch/arm64/configs/exynos7420-zeroflte_defconfig" and change CONFIG_SEC_RESTRICT_FORK=y to =n
[from kernel top level folder]
make clean && make mrproper
make exynos7420-zeroflte_defconfig (this seems to pull the config file we edited just above)
make
Encountered an error, some github issues from raspberry pi indicate I may be using a 32-bit cross compiler? Need to find a 64 bit one, trying:
Ahh realized my mistake, same issue I encountered when doing this w/ [REDACTED] for the [REDACTED] before. Two things:
- Needed to use the aarch64 architecture instead of arm (which is only 32-bit), this gets me the 64bit arm compiler
- Newer version of the NDK sneakily symlink gcc to clang. Using android-ndk-16 with aarch64 solved this problem.
Compiling with my changes still yielded an error:
"Undefined reference to sec_check_execpath" in sys.c
Seems some code is hit when some of the CONFIG_SEC_RESTRICT_XXXX vars are set, but since CONFIG_SEC_RESTRICT_FORK is undefined the symbol is not defined. I just went and disabled all those CONFIG_SEC_RESTRICT_XXXX vars and it compiled! (I ran just make to avoid any multithreading pitfalls)
Now I have the arm64 compiled kernel object, now need to swap it onto the phone.
Note: to get into recovery, hold volume up + home + power and keep holding it past the blue screen with the android. Eventually you'll get there.
Seems I need to: get a custom recovery, then get my kernel into a flashable format (zImage, .tar?), then flash
Alright, easiest way to flash (followed steps here: search "androidexplained.com/galaxy-s6-install-custom-recovery"):
- download Flashify on s6
- download TWRP on s6
- flash TWRP using Flashify and reboot using Flashify, much easier than the button pressing technique above
Seems there's some kind of boot image that we need to re-create. This is a binary blob that starts with "ANDROID!" if we view in a hexdump. FOund a youtube video showing how to find it by looking for the block device in /dev/block/platform and then dd-ing it: https://www.youtube.com/watch?v=qzCO6Ey583k
cd /dev/block/platform/.../by-name
ls
[look for 'boot']
dd if=/dev/block/sda5 of=/sdcard/Downloads/boot_img
Alright, using that image and Android Image Kitchen (unpack or whatever), i was able to break it into ramdisk/ and split_img/. I can see the zimage file, but it doesn't seem to be an ELF, so not sure how to get it into that format exactly. Good explanation of boot image stuff here: https://forum.xda-developers.com/showthread.php?t=443994
Just looked at this guide (saved as "HOWTO: Unpack, Edit, and Repack Boot Images" in notes). Alright, looking at boot.img in hxd and my compiled kernel vmlinux I found the similarities. Looking for the magic number listed in this documentation (https://www.kernel.org/doc/Documentation/arm64/booting.txt) I found it in both boot.img and vmlinux ("ARMd"). Looks like it's the exact same thing without the ELF header. Hopefully I can just swap one out for the other and try flashing it back.
- backup existing recovery.img just in case ("recover-from-boot.bak" on our s6).
- acquire this update.zip also just in case? (this seems to be how official "updates" are packaged. can find some w/ a quick google)
- carve out and swap in my kernel
So I was wondering why my vmlinux (146M) was so much bigger than the stock zimage (20M) (in split-image/ folder after running AndroidImageKitchen/unpackimg) . After a lot of googling I found a random stack overflow that answered this question:
" can find the zImage file at "kernel/arch/arm/boot/"
Sure enough, this image at kernel/arch/arm64/boot/Image is 21M and matches the format of the zImage perfectly. Now to splice in somehow.
Q: What are all the folders that have outputs when compiling a linux kernel? And what was all that extra junk in the top level directory vmlinux? Looked like mostly file paths (ascii)
Spliced in using repackimg.bat from AIK
Now to flash onto S6 and see...
==== Overall understanding ==== android boots up, it checks for button presses (U-boot?) if recovery buttons pressed (home + power + volume up), then load recovery.img else load boot.img boot.img loads ramdisk and kernel, which then boots rest of OS
Change recovery.img = TWRP, our "first stop" code execution. Even if we screw up our boot.img up, we should still be able to boot recovery mode
Change boot.img = change our actual OS and kernel ===== End understanding =======
Current Kernel version: 3.10.61-10292505 Fri Feb 24 2017
It worked!
Now to see if we can debug...
adb shell
su
cd /data
adb forward tcp:23946 tcp:23946
netstat -an | grep 239
./android_server64
Alright, sort of success. The initial method of "Running a process" from IDA didn't work, no CONFIG_SEC_RESTRICT_FORK message popped up which was good, but couldn't start the process. What worked was attaching to a process, just had to point it to 127.0.0.1:23946 and then a bunch of android processes popped up, searched for boosted and it worked.
Aka, now I'm not sure whether we needed to recompile the kernel, but maybe. Worth investigating at some point haha.
Anyway, for learning purposes. Overall path to custom kernel:
download source from samsung opensource
configure options
build kernel
extract boot_img from phone
unpack using androidimagekitchen
swap zImage with one in your newly created kernel folder: kernel/arch/arm64/boot/Image
repack using androidimagekitchen
install Flashify, and flash new image using that (making sure to back up old one just in case)
For debugging:
Use adb instructions up above to run android_server64
Open boosted app on phone
Ida -> Debugger -> Attach to process
Attach to boosted
Debug base.odex
- --- Update To run jadx-gui on Windows, look for "jadx-gui.bat" and run that
First version of the Boosted.apk (Boosted Boards_v0.6.4_apkpure.com.apk) was much easier to read, lots of symbols, less obfuscation. Can either try reversing that more or try JTAG-ing the chip and seeing if they enabled readout protection.
- Can also try sniffing the CAN bus to see what messages are being sent, maybe we can alter them? Maybe one is from the battery detection unit?
- If battery detection unit is not connected via CAN, let's spy on it and see if we can alter / understand what it's sending?
Static reversing original app
Thought I'd try and put some real effort into static analysis for debugging later
WelcomeActivity.onStart() Q: What is the bind function?
Observable - From ReactiveX (event driven java library), something that can be subscribed to (or Observed)
Observer - object that subscribes to something
Disposable - a reference to a stream, and a stream is a reference to a subscription between an Observable and Observer. Almost like a memory reference after using malloc(), you use Disposable.dispose() to garbage collect something
So basically, this bind method appears to bind an Observer to an Observable, and returns the reference to clean up later.
WelcomeActivity vs WelcomeActivity$onStart$1 Seems to be a class definition for a method, huh?
Think I found the answer from here: https://pawelwlodarski.gitbooks.io/kotlin-workshops/content/functions/function-1-definition.html
JVM Representation: Because Kotlin originally tegeted Java 6 for Android so currently each function is compiled to its own class.
seems Boosted is using ReactiveX and Kotlin
Instrinsics - class w/ useful functions (isEqual, isNull, etc.)
So WelcomeActivity$onStart$ is the function that's bound to the WelcomeViewModel actions, and I think invoke() method responds to those actions
Intent - how you start one activity from another during runtime
connectUIAction's instantiation point is where the FirmwareURL is set, need to find this location and I should be able to get the URL.
connectionViewModel is the key, and it's creation in DashboardFragment
#Another avenue - OldBoostedApi.updateBoardData() ? SerialConnection() - challenge response
BleConnectionKt.createBoardConnection() - this is where the different BoardConnection types are handled for v1, v2, etc.
BoardConnectionV1
public final <T> void readOdo(@NotNull Function1<? super T, Unit> function1) {
Intrinsics.checkParameterIsNotNull(function1, "f");
sendCommand("**ODO**", BoardCommand.ODO, BleConnectionKt.createDataCallback(function1));
}
public final <T> void readBattery(@NotNull Function1<? super T, Unit> function1) {
Intrinsics.checkParameterIsNotNull(function1, "f");
sendCommand("**SOC**", BoardCommand.BATTERY, BleConnectionKt.createDataCallback(function1));
}
public final <T> void readSkillModesCount(@NotNull Function1<? super T, Unit> function1) {
Intrinsics.checkParameterIsNotNull(function1, "f");
sendCommand("**NUMSKL**", BoardCommand.SKILLS_COUNT, BleConnectionKt.createDataCallback(function1));
}
public final <T> void readStatus(@NotNull Function1<? super T, Unit> function1) {
Intrinsics.checkParameterIsNotNull(function1, "f");
sendCommand("**RC00000**", BoardCommand.STATUS, BleConnectionKt.createDataCallback(function1));
}
public final <T> void readBoardModel(@NotNull Function1<? super T, Unit> function1) {
Intrinsics.checkParameterIsNotNull(function1, "f");
sendCommand("**MDL**", BoardCommand.BOARD_MODEL, BleConnectionKt.createDataCallback(function1));
}
public final <T> void readBoardVersion(@NotNull Function1<? super T, Unit> function1) {
Intrinsics.checkParameterIsNotNull(function1, "f");
sendCommand("**VER**", BoardCommand.BOARD_FW, BleConnectionKt.createDataCallback(function1));
}
public final <T> void readFwHash(@NotNull Function1<? super T, Unit> function1) {
Intrinsics.checkParameterIsNotNull(function1, "f");
sendCommand("**GIT**", BoardCommand.BOARD_FW_HASH, BleConnectionKt.createDataCallback(function1));
Potentially what we're looking for is in FirmwareUpdateActivity.getFirmware()
That non-decompiled function, that calles getFirmwareUrl()
but where is that value set? I'm guessing on pairing? So need to find Instantiation of FirmwareUpdateActivity, probably goes back to ConnectionViewModel
ConnectionViewModel The meat of things, this is where the firmware URL comes from I believe.
onScanningEvent - gets board version, creates a new BoardDevice (just "v1" or "v2", bluetoothdevice, and something else and adds it to an arraylist which it uses to updateState() on itself
connects to board if it's the one the app is currently configured for (ConnectToBoard()
updateState() - uses something called a BehaviorRelay (search "rxrelay2"), which just passes it on to any subscribers really
constructor() - this behemoth is filled with inline anonymous class declarations/instantiations, just pretend you're looking at class declarations that are only used once
some interesting states:
- StartFirmwareUpdate
- FirmwaresAlertClick.INSTANCE
okay, the earliest reference to the Firmware url trail seems to be this line in DashboardFragment$onStart$4:
Context context = dashboardFragment.getContext()
Pair[] pairArr = {TuplesKt.to(FirmwareUpdateActivity.EXTRA_FIRMWARE_URL, ((StartFirmwareUpdateActivity) connectUiAction).getFirmware().getUrl())};
Context context = dashboardFragment.getContext();
Pair[] pairArr2 = (Pair[]) Arrays.copyOf(pairArr, pairArr.length);
Intent intent = new Intent(context, FirmwareUpdateActivity.class);
ContextKt.addExtras(intent, (Pair[]) Arrays.copyOf(pairArr2, pairArr2.length));
context.startActivity(intent, null);
Hmm Repository seems to be promising, dig through the BoostedApi links here
boostedboard.android.ble.v1.BleSerialParserKt - look at ALL of v1 btw
package com.boostedboards.android.ble.v1.V1BoardVersion
public enum V1BoardVersion {
V1_1("v1.1", "4856bfbebfb0e875a82874f1cab9dba322da4ee5", 2, BoardVariant.PLUS),
V1_2("v1.2", "a67508829454ccde5179e7dce0c173d6e0bc2c09", 2, BoardVariant.PLUS),
V1_3("v1.3", "f6665273e21316d75d32e7c433e52fc474d83b52", 2, BoardVariant.PLUS),
V1_4("v1.4", "45c4e7e76effd5bbba384c741ab9267b66f3a88a", 2, BoardVariant.PLUS),
V1_5("v1.5", "318df5bdcbab64111b258a3590c6265079d0704c", 2, BoardVariant.PLUS),
V_1_6_P("v1.6.0", "e07d2fdef9a90ecd50a91a614259cba4d9a8efbf", 4, BoardVariant.PLUS),
V_1_6_D("v1.6.1", "392898e3b50eaf734081cabaa4130845dac8a705", 3, BoardVariant.DUAL),
V_1_7_S("v1.7", "336bf4265146611f950f392d651190a0c61f1773", 3, BoardVariant.SINGLE),
BRSP - BlueRadio Serial Port ?
Alright, closest I've got so far: Repository.getFirmwareScriptGen1() things of interest:
- FirmwareScript
- ScriptPlayer
all of boostedboards.android.data.api
What if I spoofed a boosted board connection? And then debugged
Update: make the earlier-version boosted app debuggable, re-sign, upload, then debug w/ ida or gdb. Overall it was hard to say exactly where the firmware url was coming from, BoostedOldApi seemed to be kind of a link, but... idk it was hard statically. Too much going on
^ for extracting firmware from chip directly, ordered Pomona Probes so that should help
Should also probably order any tools needed to dump the PIC (apparently they're cheap according to the article)Z
Hardware 4/22/20 Got pomona test probes Debug port mapping: debug pin 0 - ? pin2 (vdd), pin 16 (vdd)- debug pin 1 pin 12 (SCL4/SDO2/U3TX/PMA3/CN10/RG8) - d2 11 (SDA4/SDI2/U3RX/PMA4/CN9/RG7) - d3 10 (SCK2/U6TX/U3RTS/PMA5/CN8/RG6) -d4 32 (AN8/C1OUT/RB8) -d5 14 (SS2/U6RX/U3CTS/PMA2/CN11/RG9) -d6 pin 30 (VSS), 45 (VSS), 75 (VSS),79 (IC5/PMD12/RD12)- debug pin 7
Seems to be either SPI or UART or I2C, will do a trace once my test clips get in.
- *One flash chip **to the top right and CAN transceiver on bottom right (while looking at the PIC with the writing in the correct orientation)
Second debug/test ports to the left of the debug ports
Layout 1 23 45 67 8 9
1 - alignment hole I think 2 - pin 13 (PIC - through resistor) - MCLR 3 - pin 79 (connected to everything as debug pin 8), looks like VSS 4 - 79, and all others (VSS) (RD, IC5, PMD, ETXD) 5- 27 - thru resistor (debug pin (PGED2), analog, RB7) 6 - 26 thru resistor (debug pin PGEC2), RB6, OCFA, analog 7 - ? 8,9 - I think these are just alignment holes
IC5 - capture inputs (1-5) AN - Analog RD - Bidirectional IO port (PORTD) RB - Bidirectional IO port (PORTB) MCLR - Master Clear Reset (active-low reset to the device) ETXD - Ethernet Transmit Data OCFA - Output compare fault a input
Just figured out, this second debug port is for the MPLAB PICkit In circuit debugger
BMS (LTC680) connected to PIC via SPI looks like, will trace in more detail once tools get in
So black and red wire (small ones) on the left side actually connect to what I thought was one of the flash chips. Turns out its actually an NXP A105 CAN transceiver
Datasheet: https://www.nxp.com/docs/en/data-sheet/TJA1057.pdf
JTAG - pins 17,38,60,61 all are disconnected. Wonder if we can get jtag
Summary: 1 flash chips, 1 unknown debug port, 1 In-Circuit Debugger (I think), serial interface to BMS, 1 CAN transceiver, JTAG
Understanding ICSP vs JTAG (https://www.microchip.com/forums/m972058.aspx)
" ICSP is a wrapper on JTAG. The wrapping is done by multiplexing 3 JTAG lines on PGCED. The multiplexing is 4:1, so ICSP is 4 times slower than JTAG (with everything else equal). Of course, it may not matter with most of the tools which waste time elsewhere and don't come too close to theoretically feasible speeds (such as 10-15 sec to fully program 2048K PIC32MZ).
A problem with JTAG is that it requires the PIC to run. For example, if a crystal is bad and the PIC doesn't run, you won't be able to re-program it with JTAG. ICSP boots into special internal-oscillator mode, so it can always program the PIC (but you still need good power, decoupling capacitors etc.). Also, JTAG may be disabled while ICSP cannot. Therefore, if you only have a JTAG connector, you may get locked out. This won't happen to ICSP.
ICSP is proprietary, but it is very well documented in Programming Specifications. JTAG is open, but don't expect the general JTAG tools to be able to program the PIC. To do so, you can only use tools which specifically support PIC32MZ."
Discord
@Yul on discord dumped firmware files for the ESC (in my boosted_reverse folder)
Apparently the same ESC firmware is used in all the boosted boards firmware is for this chip: dsPIC33E
doh... got VDD and VSS mixed up, explanation added to Electronics note:
Sniffed debug port 1 (white plastic), some seemingly UART traffic at 20000 baud, nothing obvious. Just pulses hex values.
Sniffed NXP CAN IC, got some data from the captures, some protocol being spoken but can't identify the different IDs
TODO: solder wires to bms and sniff serial traffic (maybe we could spoof?) - tried doing w/ test probes but need 3 hands (for clock, CS, and SDI)
- try spoofing valid batteries w/ other 3.3v batteries? Nah fuck it, let's just try and get the firmware, too much effort for a likely fail
- PICkit, try and read data out
- JTAG - try and read data out or hardware attack
- flash chip - dump it (probably contains serial)