summaryrefslogtreecommitdiff
path: root/org
diff options
context:
space:
mode:
authorJakob L. Kreuze <zerodaysfordays@sdf.org>2021-12-19 15:43:00 -0500
committerJakob L. Kreuze <zerodaysfordays@sdf.org>2021-12-19 15:43:00 -0500
commit6a4b63e1c862278df7af20e46caa6d4a63de8506 (patch)
tree10c29fcb9dc0a054cd3d383613012de1e1a1f2d5 /org
parent9241b4dc730611d2f57e4668f5aa9046ec2d0ad3 (diff)
[org] Initial commit for "Dollar Bin Reverse Engineering".
Diffstat (limited to 'org')
-rw-r--r--org/Dollar Bin Reverse Engineering/dollar-bin-reverse-engineering.org407
1 files changed, 407 insertions, 0 deletions
diff --git a/org/Dollar Bin Reverse Engineering/dollar-bin-reverse-engineering.org b/org/Dollar Bin Reverse Engineering/dollar-bin-reverse-engineering.org
new file mode 100644
index 0000000..a5604e5
--- /dev/null
+++ b/org/Dollar Bin Reverse Engineering/dollar-bin-reverse-engineering.org
@@ -0,0 +1,407 @@
+#+TITLE: Dollar Bin Reverse Engineering
+#+DATE:
+#+TAGS: writeup, hardware, reverse-engineering, avr, radare2, java
+
+The background for this project is a lesson in recognizing dishonest vendors.
+Two years ago, I was looking at purchasing a smart watch with sleep tracking
+capabilities;[fn:1] I've always had difficulty sleeping, and wanted a way of
+finally quantifying that difficulty. One of my requirements was the ability to
+pull data off of the watch without [[https://www.gnu.org/proprietary/][proprietary software]], so I was only seriously
+considering what was on [[https://gadgetbridge.org/][Gadgetbridge]]'s "supported devices" list. At the time, I
+was still in high school, so I was still awed by the affordability of consumer
+electronics available on websites such as AliExpress, somewhat capable of
+reading and writing 汉语, and woefully unaware of the [[https://drewdevault.com/2019/11/20/China.html][ethical implications]] of
+supporting a totalitarian state's economy. The Xiaomi Mi Band 2 fit the bill. I
+took to Ebay to purchase one, and found a listing for 10.99 USD with [[https://www.npr.org/2018/08/23/641140144/unraveling-the-mystery-behind-international-shipping-rates][free
+shipping]]. I ordered it, and things were okay until the package arrived.
+
+What appeared outside my garage was not what I ordered. I gave the vendor the
+benefit of the doubt, thinking that it may have been a mistake, and explained
+that they had sent me the wrong project.
+
+#+BEGIN_QUOTE
+Hi [my Ebay username],
+
+Thank you for your message. Sincerely sorry for your inconvenience.
+
+Please kindly konw [sic] that they are the same kind product and all the
+functions are the same [sic]. In order to protect your interest [sic], we
+suggest that we issue $5 USD refund without returning the item and you can keep
+this item and try to use it. If it is suit for you and please feel free to give
+us a positive feedback. If it is still not your favor, please kindly do NOT
+leave any feedback.
+
+Please kindly let us know if you agree.
+
+If you need further assistance or inquiry, please feel free to contact us.
+#+END_QUOTE
+
+I'm antipathetic toward anyone with the gall to believe they can slight me, so I
+threatened to file a complaint with Ebay.
+
+#+BEGIN_QUOTE
+Hi [my Ebay username],
+
+We feel sorry to know that you have received your parcel but the watch you
+received is not the same as the listing in our store.
+
+In order to protect your interest [sic], we suggest that we issue a full refund
+without returning the item and you can keep this item and try to use it. If it
+suit for you and please feel free to give us a positive feedback. If it is still
+not your favor, please kindly do NOT leave any feedback.
+
+Please kindly let us know if you agree.
+#+END_QUOTE
+
+They did give me a refund. So I got the watch for free. But it was essentially
+unusable to me. I put it away, noting its liberation as a project for another
+time.
+
+If you're observant, you may have noticed that these two messages are
+essentially the same. I suspect that an expectation of complaint-adjacent losses
+is a part of the vendor's business model. Looking back on it now, I should have
+taken the refund and filed the complaint anyway.
+
+Regrets aside, I decided that finally reverse engineering the smart watch would
+be a nice [[https://en.wikipedia.org/wiki/COVID-19_pandemic][quarantine activity]] to share with my friends through the magic of live
+streaming. The project is over, now, but the recordings are available on
+PeerTube, and linked below.
+
+- [[https://toobnix.org/videos/watch/75d1d813-fc9e-415b-9c4a-d6e26faba94a][Friday Night Junk Hacking]]
+- [[https://toobnix.org/w/rm1nVU4dzikxxkjQ8Dc69M][Friday Night Junk Hacking 2: Electric Boogaloo]]
+
+* Method of Attack
+
+The original plan was to write a Gadgetbridge plugin to pull data from the
+watch, which is somewhat less ambitious than what I eventually did. In either
+case, the project began with reconnaissance. There _was_ software to interface
+with the watch, I just refused to install it on my cellphone. To figure out how
+to talk to the watch, the easy route would be to reverse engineer that software.
+It was an Android app.
+
+** Reverse Engineering Android Apps
+
+Most software using the Android SDK is written in Java. Java runs on a [[https://en.wikipedia.org/wiki/Virtual_machine][process
+virtual machine]], specifically a stack machine, which was a performance concern
+on the meager phone hardware that was available in Android's early days [1]. So
+the [[https://en.wikipedia.org/wiki/Dalvik_(software)][Dalvik]] virtual machine was developed: a similar process virtual machine with
+a register-based architecture and fewer virtual machine instructions [1].
+Nowadays, the Dalvik VM is no more, but the techniques for reverse engineering
+Dalvik bytecode are still relevant, as modern Android runtimes still use the
+[[https://source.android.com/devices/tech/dalvik/dex-format][Dalvik executable format]] [2]. The compilation process for an Android app is,
+essentially, using the Java compiler to obtain JVM bytecode for the app's
+sources, and then feeding that bytecode into =dx= to obtain Dalvik bytecode.
+
+Here's a quick rundown: an Android APK, like you'd get from F-Droid or the Play
+Store, is just a [[https://en.wikipedia.org/wiki/Zip_(file_format)][ZIP archive]] with a specific structure and some signatures. All
+of the code is in one or more =classes.dex= files. You can, if you're a caveman
+(or faced with some seriously obfuscated code), unzip the APK and dump the =.dex=
+file into radare2 or [[https://github.com/JesusFreke/smali][smali]]. But I know how to use technology, so I used [[https://github.com/skylot/jadx][JADX]] to
+recover the Java sources.
+
+There are [[https://github.com/ashishb/android-security-awesome#readme][more tools]].
+
+[1]: https://stackoverflow.com/questions/2719469/why-is-the-jvm-stack-based-and-the-dalvik-vm-register-based
+[2]: https://source.android.com/devices/tech/dalvik/gc-debug
+
+** An Introduction to Bluetooth Low Energy
+
+# BLE Explorer
+
+# MAC address is accessible from watch
+
+# Dongle didn't work first stream, did the second
+
+# Bluetooth is incompatible with BLE
+
+# Service vs characteristic, important later.
+
+# AdaFruit article, BlueZ
+
+# Grep for BLE from Android devdocs
+
+# Callback structure
+
+# ---
+
+# hcidool lescan
+# gattool
+
+** Striking Oil
+
+"Awesome, we hit the gold mine! ... I think"
+
+# Luis pointed out ostool, explained AWS to me
+
+# This is when we realized that this is a big scheme, and that this app works for many of these kinds of smart watches
+
+# String "MSE-IPE167" in firmware, googling yields http://www.ebooks-cct.com.tw/product_show.php?id=1245
+# This explains the title of this blog post.
+
+# Try to disassemble as AVR in Radare2, it works.
+
+*** Object Storage
+
+# I'm pretty familiar with S3 et al. now, but it was new to me when I Was working on this project. So here's a quick summary.
+
+** Flashing Firmware
+
+# Essentially just write a byte array to this one "service"
+
+# This is a humorous attack vector.
+
+# Callstack:
+
+
+Aliyun.java:
+downloadOtaFile downloads =this.objectKey=
+set in =chkOta=
+
+sFWver = 0d.0e.0f.10
+
+jakob@Epsilon ~/Streams/Friday Night Junk Hacking 2: Electric Boogaloo/m2-firmware $ radiff2 M2*
+File size differs 111617 vs 111638
+Buffer truncated to 111617 byte(s) (21 not compared)
+0x000065f4 32 => 33 0x000065f4
+0x0000c1b0 32 => 33 0x0000c1b0
+0x0000d9ac 32 => 33 0x0000d9ac
+0x0001a876 452d49504531 => 47532d495047 0x0001a876
+0x0001b27f 452d49504531 => 47532d495047 0x0001b27f
+0x0001b2a0 452d49504531 => 47532d495047 0x0001b2a0
+
+# Modifying Strings
+
+** Where I Would Have Gone Next
+
+** Why I Gave Up Here
+
+** Addendum: Watching Yourself
+
+---
+
+Getting the FW version
+
+#+BEGIN_SRC sh
+jakob@Epsilon ~ $ sudo gatttool -I
+[ ][LE]> connect A4:C1:7A:56:82:90
+Attempting to connect to A4:C1:7A:56:82:90
+Connection successful
+[A4:C1:7A:56:82:90][LE]> characteristics
+handle: 0x0002, char properties: 0x12, char value handle: 0x0003, uuid: 2b120008-0600-072a-0100-050200042a00
+handle: 0x0004, char properties: 0x02, char value handle: 0x0005, uuid: 0708090a-0b0c-0d2b-1200-080600072a01
+handle: 0x0007, char properties: 0x06, char value handle: 0x0008, uuid: 00010203-0405-0607-0809-0a0b0c0d2b12
+handle: 0x000b, char properties: 0x08, char value handle: 0x000c, uuid: 0000fec7-0000-1000-8000-00805f9b34fb
+handle: 0x000d, char properties: 0x20, char value handle: 0x000e, uuid: 0000fec8-0000-1000-8000-00805f9b34fb
+handle: 0x0010, char properties: 0x02, char value handle: 0x0011, uuid: 0000fec9-0000-1000-8000-00805f9b34fb
+handle: 0x0012, char properties: 0x32, char value handle: 0x0013, uuid: 0000fea1-0000-1000-8000-00805f9b34fb
+handle: 0x0015, char properties: 0x2a, char value handle: 0x0016, uuid: 0000fea2-0000-1000-8000-00805f9b34fb
+handle: 0x0019, char properties: 0x0a, char value handle: 0x001a, uuid: 0000cc02-0000-1000-8000-00805f9b34fb
+handle: 0x001b, char properties: 0x12, char value handle: 0x001c, uuid: 0000cc03-0000-1000-8000-00805f9b34fb
+handle: 0x001e, char properties: 0x12, char value handle: 0x001f, uuid: 0000cc04-0000-1000-8000-00805f9b34fb
+handle: 0x0021, char properties: 0x1a, char value handle: 0x0022, uuid: 0000cc05-0000-1000-8000-00805f9b34fb
+handle: 0x0024, char properties: 0x08, char value handle: 0x0025, uuid: 0000cc06-0000-1000-8000-00805f9b34fb
+
+# This is where it's a little weird.
+
+
+[A4:C1:7A:56:82:90][LE]> char-read-uuid 0000cc02-0000-1000-8000-00805f9b34fb
+handle: 0x001a value: 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13
+
+[A4:C1:7A:56:82:90][LE]> char-read-hnd 0x001a
+Characteristic value/descriptor: 00 00 00 00 00 00 00 00 00 00 00 21 41 6b 00 00 00 00 00 00
+
+[A4:C1:7A:56:82:90][LE]> char-read-uuid 0000cc02-0000-1000-8000-00805f9b34fb
+handle: 0x001a value: 00 00 00 00 00 00 00 00 00 00 00 21 41 6b 00 00 00 00 00
+
+# Like you have to read the handle first..?
+#+END_SRC
+
+From =mGattCallback.onCharacteristicRead=:
+
+#+BEGIN_SRC java
+int intValue = bluetoothGattCharacteristic.getIntValue(17, 11).intValue();
+int intValue2 = bluetoothGattCharacteristic.getIntValue(17, 12).intValue();
+int intValue3 = bluetoothGattCharacteristic.getIntValue(17, 13).intValue();
+int intValue4 = bluetoothGattCharacteristic.getIntValue(17, 14).intValue();
+if (intValue3 < 53) {
+ SPUtils.put(BluetoothLeService.sContext, Constant.NEW_FORMULA, false);
+} else if (intValue3 >= 53) {
+ SPUtils.put(BluetoothLeService.sContext, Constant.NEW_FORMULA, true);
+}
+String unused = BluetoothLeService.sFWVer = String.format("%02x", new Object[]{Integer.valueOf(intValue2)}) + "." + String.format("%02x", new Object[]{Integer.valueOf(intValue3)}) + "." + String.format("%02x", new Object[]{Integer.valueOf(intValue4)}) + "." + String.format("%02x", new Object[]{Integer.valueOf(intValue)});
+String str2 = BluetoothLeService.TAG;
+StringBuilder sb = new StringBuilder();
+sb.append("sFWVer = ");
+sb.append(BluetoothLeService.sFWVer);
+Log.i(str2, sb.toString());
+if (intValue3 <= 73) {
+ boolean unused2 = BluetoothLeService.sNewSync = false;
+ String str3 = BluetoothLeService.TAG;
+ Log.i(str3, "sNewSync = " + BluetoothLeService.sNewSync);
+ Iterator<BluetoothDevice> it = BluetoothLeService.getBoundDevices().iterator();
+ loop0:
+ while (true) {
+ z = false;
+ while (true) {
+ if (!it.hasNext()) {
+ break loop0;
+ }
+ BluetoothDevice next = it.next();
+ if (next.getName() != null) {
+ if (BluetoothLeService.getConnectedDevice() != null) {
+ if (next.getAddress().equals(BluetoothLeService.getConnectedDevice().getAddress())) {
+ z = true;
+ }
+ }
+ }
+ }
+ }
+ String str4 = BluetoothLeService.TAG;
+ Log.i(str4, "isBonded = " + z);
+ if (z) {
+ BluetoothLeService.sConnSeq = 2;
+ BluetoothLeService.readBattery(bluetoothGatt);
+ }
+} else if (intValue3 > 73) {
+ boolean unused3 = BluetoothLeService.sNewSync = true;
+ String str5 = BluetoothLeService.TAG;
+ Log.i(str5, "sNewSync = " + BluetoothLeService.sNewSync);
+ BluetoothLeService.sConnSeq = 2;
+ BluetoothLeService.readBattery(bluetoothGatt);
+}
+boolean unused4 = BluetoothLeService.sKMD = BluetoothLeService.chkIsKMD(bluetoothGattCharacteristic);
+SPUtils.put(BluetoothLeService.sContext, Constant.IS_KMD, Boolean.valueOf(BluetoothLeService.sKMD));
+boolean unused5 = BluetoothLeService.sMultiple = BluetoothLeService.chkHasMultipleSports(bluetoothGattCharacteristic);
+SPUtils.put(BluetoothLeService.sContext, Constant.HAS_MULTIPLE_SPORTS, Boolean.valueOf(BluetoothLeService.sMultiple));
+boolean unused6 = BluetoothLeService.sHrBpSync = BluetoothLeService.chkHasHrBpSync(bluetoothGattCharacteristic);
+SPUtils.put(BluetoothLeService.sContext, Constant.HAS_HRBP_SYNC, Boolean.valueOf(BluetoothLeService.sHrBpSync));
+boolean unused7 = BluetoothLeService.sWeather = BluetoothLeService.chkWeather(bluetoothGattCharacteristic);
+SPUtils.put(BluetoothLeService.sContext, "has_weather", Boolean.valueOf(BluetoothLeService.sWeather));
+boolean unused8 = BluetoothLeService.sMusic = BluetoothLeService.chkMusic(bluetoothGattCharacteristic);
+SPUtils.put(BluetoothLeService.sContext, Constant.HAS_MUSIC, Boolean.valueOf(BluetoothLeService.sMusic));
+boolean unused9 = BluetoothLeService.sDownladImage = BluetoothLeService.chkDonwloadImage(bluetoothGattCharacteristic);
+SPUtils.put(BluetoothLeService.sContext, Constant.HAS_IMAGE_UPGRADE, Boolean.valueOf(BluetoothLeService.sDownladImage));
+boolean unused10 = BluetoothLeService.sHasBT = BluetoothLeService.chkBT(bluetoothGattCharacteristic);
+SPUtils.put(BluetoothLeService.sContext, Constant.HAS_BT, Boolean.valueOf(BluetoothLeService.sHasBT));
+SPUtils.put(BluetoothLeService.sContext, Constant.HAS_CONTACTS, Boolean.valueOf(BluetoothLeService.chkContacts(bluetoothGattCharacteristic)));
+String upperCase = BluetoothLeService.sFWVer.replace(".", "").toUpperCase();
+if (BluetoothLeService.sDownladImage && (upperCase.equals("81620022") || upperCase.equals("81600023") || upperCase.equals("80620022") || upperCase.equals("81600021") || upperCase.equals("7B610081"))) {
+ UpdateImage.getInstance().setFWInfo(80, 160, 114688, 0);
+} else if (BluetoothLeService.sDownladImage && (upperCase.equals("7D610081") || upperCase.equals("7D620080"))) {
+ UpdateImage.getInstance().setFWInfo(80, 160, 258048, 0);
+} else if (!BluetoothLeService.sDownladImage) {
+ UpdateImage.getInstance().setFWInfo(0, 0, 0, 4);
+}
+SPUtils.put(BluetoothLeService.sContext, Constant.DEV_INFO_ADDR, BluetoothLeService.getConnectedAddress());
+SPUtils.put(BluetoothLeService.sContext, Constant.FIRMWARE_VERSION, BluetoothLeService.sFWVer);
+Intent intent = new Intent(Constant.ACTION_READ_FIRMWARE_VERSION);
+intent.putExtra(Constant.FIRMWARE_VERSION, BluetoothLeService.sFWVer);
+BluetoothLeService.sContext.sendBroadcast(intent);
+#+END_SRC
+
+So my fw version is =41.6b.00.21=; 0x6b > 73, so we use "new sync".
+It isn't one of the special cases checked at the end, but chkDonwloadImage [sic] is false, so =.setFWInfo(0, 0, 0, 4)=.
+
+#+BEGIN_SRC java
+public void setFWInfo(int i, int i2, int i3, int i4) {
+ this.WIDTH = i;
+ this.HEIGHT = i2;
+ this.flashSize = i3;
+ this.isWait = false;
+ this.dialColor = i4;
+}
+#+END_SRC
+
+#+BEGIN_SRC java
+ public class TelinkOtaTask extends AsyncTask<Void, Void, Void> {
+#+END_SRC
+
+#+BEGIN_SRC java
+protected void doInBackground() {
+ for (int i = 1; i < TelinkOta.getBlockCount() + 3; i++) {
+ if (i == 1) {
+ BluetoothLeService.writeOtaCharacteristic(new byte[]{1, -1});
+ SystemClock.sleep(1000);
+ } else if (i == TelinkOta.getBlockCount() + 2) {
+ BluetoothLeService.writeOtaCharacteristic(TelinkOta.getEndCmd());
+ TelinkOta.close();
+ SystemClock.sleep(1000);
+ MainActivity.this.unboundAllMSDevice();
+ } else {
+ if (TelinkOta.getCurrentCount() % 100 == 0) {
+ SystemClock.sleep(0);
+ }
+ BluetoothLeService.writeOtaCharacteristic(TelinkOta.getBlock(TelinkOta.getCurrentCount()));
+ TelinkOta.currentCountPlus();
+ SystemClock.sleep(this.delay);
+ publishProgress(new Void[0]);
+ }
+ }
+}
+#+END_SRC
+
+#+BEGIN_SRC java
+public static void writeOtaCharacteristic(byte[] bArr) {
+ BluetoothGattService service;
+ BluetoothGattCharacteristic characteristic;
+ BluetoothGatt bluetoothGatt = sBluetoothGatt;
+ if (bluetoothGatt != null && (service = bluetoothGatt.getService(TELINK_SPP_DATA_OTA_SERVICE)) != null && (characteristic = service.getCharacteristic(TELINK_SPP_DATA_OTA)) != null) {
+ characteristic.setValue(bArr);
+ sBluetoothGatt.writeCharacteristic(characteristic);
+ }
+}
+#+END_SRC
+
+# Big ziku is big font library
+
+---
+
+* Story about scamming
+
+* Run-down of the software
+** Decompiling with JADX; dependencies
+** Exodus
+** I discovered the API keys because I was sus about the alibaba dependency
++* Android Shit+
++** References (Maybe omit)+
+* Bluetooth Shit
+** Query UUID via BLE, get a response back
+** Found some UUIDs in BluetoothLeService.java
+* Malware
+** 10M downloads?
+* Appendix: Watching Your Own Streams
+** Damn, coming back to this over a year later and I'm impressed
+
+* Commands
+
+#+BEGIN_SRC shell
+sudo gattool -b A4:C1:7A:56:82:90 -I
+#+END_SRC
+
+Binaries available for OSSutil. Might be interesting to RE and see if anything differs from the Github source code... https://www.alibabacloud.com/help/doc-detail/120075.htm
+
+---
+
+Ghidra oneshot:
+- ASCII strings
+
+---
+
+* Additional Reconnaissance
+
+** SoC is likely to be based on [[http://wiki.telink-semi.cn/doc/ds/DS_TLSR8232-E_Datasheet%20for%20Telink%20BLE%20SoC%20TLSR8232.pdf][TLSR8232]]
+
+http://wiki.telink-semi.cn/wiki/chip-series/TLSR826x-Series/
+
+- Strings
+- Conversation here https://gitter.im/nRF51822-Arduino-Mbed-smart-watch/Lobby?at=5b62115c854add6cee148651
+
+** MCU is likely to be the TC32
+
+https://github.com/rgov/Ghidra_TELink_TC32
+
+** Reverse engineering the SDK to figure out load address
+
+https://github.com/OnTheWayJohn/ble_lt_sdk
+
+[fn:1] This was before I learned that the "sleep tracking" feature on these "smart watch" type products tends to be inaccurate. In retrospect, I should've saved up for an EEG device like the now discontinued [[https://www.gwern.net/Zeo][Zeo]].