aboutsummaryrefslogtreecommitdiff
path: root/haunt/posts/dollar-bin-reverse-engineering.org
diff options
context:
space:
mode:
Diffstat (limited to 'haunt/posts/dollar-bin-reverse-engineering.org')
-rw-r--r--haunt/posts/dollar-bin-reverse-engineering.org751
1 files changed, 0 insertions, 751 deletions
diff --git a/haunt/posts/dollar-bin-reverse-engineering.org b/haunt/posts/dollar-bin-reverse-engineering.org
deleted file mode 100644
index dbf7ee7..0000000
--- a/haunt/posts/dollar-bin-reverse-engineering.org
+++ /dev/null
@@ -1,751 +0,0 @@
-#+TITLE: Dollar Bin Reverse Engineering
-#+DATE: <2021-12-24 Fri 09:42>
-#+TAGS: writeup hardware reverse-engineering tc32 radare2 java
-
-The background for this project is a lesson in avoiding dishonest vendors. Two
-years ago, I was looking to purchase 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 the use of [[https://www.gnu.org/proprietary/][proprietary software]], so the only
-options I was seriously considering were those on [[https://gadgetbridge.org/][Gadgetbridge]]'s "supported
-devices" list. At the time, I was still in high school, and still awed by the
-affordability of consumer electronics on websites such as AliExpress (woefully
-unaware of the [[https://drewdevault.com/2019/11/20/China.html][ethical implications]] of supporting a totalitarian state's
-economy). Moreover, I was somewhat capable of reading and writing 汉语, so the
-[[https://en.wikipedia.org/wiki/Xiaomi_Mi_Band_2][Xiaomi Mi Band 2]] fit the bill. I took to Ebay to purchase one, finding 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. That is,
-until the package arrived.
-
-#+CAPTION: Clearly _not_ the Mi Band 2.
-[[./dollar-bin-reverse-engineering-m2.jpg]]
-
-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 product.
-
-#+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, 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 trying to 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, 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 unusable to
-me. I put it aside, noting its liberation as a project for another time.
-
-Years later, 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.
-
-- [[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]]
-
-As usual, the project began with reconnaissance. There _was_ software to interface
-with the watch, I just refused to install it on my cellphone[fn:2]. To figure
-out how to talk to the watch, the path of least resistance was to reverse
-engineer that software. [[https://play.google.com/store/apps/details?id=com.uthink.ring][It was an Android app.]]
-
-* Reverse Engineering Android Apps
-
-Most software using the Android SDK is written in [[https://en.wikipedia.org/wiki/Java_(programming_language)][Java]], a language which runs on
-a [[https://en.wikipedia.org/wiki/Virtual_machine][process virtual machine]]. This means that Java code doesn't run "on the
-processor"[fn:5], but instead in an interpreter-like program known as a "virtual
-machine" (VM). Source code is still compiled, but the target is a fairly
-high-level "bytecode" rather than the machine code that would be output by a C
-compiler.
-
-Virtual machines can be quite fast, but the performance characteristics of the
-Java VM were deemed unsuitable for the sorts of phones on the market in
-Android's early days[fn:3]. Hence, the [[https://en.wikipedia.org/wiki/Dalvik_(software)][Dalvik]] virtual machine was developed: a
-comparable process virtual machine with a register-based architecture (the Java
-VM is stack-based) and fewer virtual machine instructions[fn:3]. Java bytecode
-and Dalvik bytecode are nearly isomorphic; the latter can be thought of as an
-optimistic post-processing of the former. The compilation process for an Android
-app is, conceptually, using the Java compiler to obtain JVM bytecode for the
-app's sources, and then feeding that bytecode into =dx= to obtain Dalvik bytecode.
-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]][fn:4].
-
-A quick rundown of the process for reverse engineering android apps: 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.
-
-#+BEGIN_SRC prog
-$ file com.uthink.ring.426.apk
-com.uthink.ring.426.apk: Zip archive data, at least v0.0 to extract, compression method=deflate
-$ unzip -l com.uthink.ring.426.apk | grep classes.dex
- 8685980 00-00-1980 00:00 classes.dex
-#+END_SRC
-
-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 something closer to Java source code.
-
-There are [[https://github.com/ashishb/android-security-awesome#readme][more tools]] out there. But in this case, I didn't need to reach for
-anything besides JADX because the source code was unobfuscated. =R.java=, the
-table of references to application resources, was as rich as the =symtab= of a
-[[https://en.wikipedia.org/wiki/Strip_(Unix)][non-stripped ELF]].
-
-#+BEGIN_SRC java
-package com.uthink.ring;
-
-public final class R {
- public static final class anim {
- public static final int abc_fade_in = 2130771968;
- public static final int abc_fade_out = 2130771969;
- public static final int abc_grow_fade_in_from_bottom = 2130771970;
- public static final int abc_popup_enter = 2130771971;
- public static final int abc_popup_exit = 2130771972;
- public static final int abc_shrink_fade_out_from_bottom = 2130771973;
- public static final int abc_slide_in_bottom = 2130771974;
- public static final int abc_slide_in_top = 2130771975;
- ...
-#+END_SRC
-
-I'd been saved 90% of the reverse engineering work.
-
-* An Introduction to Bluetooth Low Energy
-
-[[./dollar-bin-reverse-engineering-manual.jpg]]
-
-The manual that came with the watch left much to be desired, but it did at least
-tell me that [[https://en.wikipedia.org/wiki/Bluetooth_Low_Energy][Bluetooth Low Energy (BLE)]] was being used to communicate with the
-wearer's smartphone. Not well that, despite the name, BLE is a different
-protocol from what's normally called "[[https://en.wikipedia.org/wiki/Bluetooth][Bluetooth]]".
-
-The premise of BLE is that one device acts as a "server" to which "clients" can
-connect and request *characteristics*: essentially, some packet of data from the
-server, such as the number of steps counted by the watch. There are a few other
-concepts (services, descriptors) in BLE, but they largely wrap around the
-concept of characteristics.
-
-A BLE server is identified by a [[https://en.wikipedia.org/wiki/MAC_address][MAC address]], and any particular characteristic,
-service, etc. that a BLE server exposes is an *attribute*, which is identified by
-a [[https://en.wikipedia.org/wiki/UUID][UUID]]. There are tools to enumerate the available attributes, [[https://f-droid.org/en/packages/org.ligi.blexplorer/][BLExplorer]] being
-the one I used initially.
-
-In our case, the MAC address of the watch is conveniently available from the
-user interface... for some reason.
-
-[[./dollar-bin-reverse-engineering-mac.jpg]]
-
-Though, if this weren't the case, it would be easy enough to run =hcitool lescan=.
-
-Furthermore, none of the characteristics require authentication to read from, so
-I was half-way towards my goal of being able to pull data off of the watch. What
-was left to do was make sense of the data I was reading.
-
-I headed to the Android Developer Docs to find the interfaces involved with
-reading BLE characteristics. It was sufficient to =grep= for references to
-[[https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic][BluetoothGattCharacteristic]]. The files of interest are =BluetoothLeService.java=
-(~6k LoC) and =UpdateImage.java= (~500 LoC). The former gives us names for all of
-the characteristics we can read from the watch.
-
-#+BEGIN_SRC java
-public static final UUID BLE_AUDIO_CMD = UUID.fromString("0000af01-0000-1000-8000-00805f9b34fb");
-public static final UUID BLE_AUDIO_DATA = UUID.fromString("0000af02-0000-1000-8000-00805f9b34fb");
-public static final UUID BLE_AUDIO_DESCRIPTION = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
-public static final UUID BLE_AUDIO_SERVICE = UUID.fromString("0000af00-0000-1000-8000-00805f9b34fb");
-public static final UUID MAXSCEND_OTA_CMD = UUID.fromString("0000FD02-0000-1000-8000-00805F9B34FB");
-public static final UUID MAXSCEND_OTA_DATA = UUID.fromString("0000FD01-0000-1000-8000-00805F9B34FB");
-public static final UUID MAXSCEND_OTA_DESCRIPTION = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
-public static final UUID MAXSCEND_OTA_SERVICE = UUID.fromString("0000FD00-0000-1000-8000-00805F9B34FB");
-public static final UUID TELINK_SPP_DATA_OTA = UUID.fromString("00010203-0405-0607-0809-0a0b0c0d2b12");
-public static final UUID TELINK_SPP_DATA_OTA_SERVICE = UUID.fromString("00010203-0405-0607-0809-0a0b0c0d1912");
-public static final UUID WERUN_SERVICE = UUID.fromString("0000fee7-0000-1000-8000-00805f9b34fb");
-public static final UUID YOHO_BATTERY_INFO = UUID.fromString("0000cc03-0000-1000-8000-00805f9b34fb");
-public static final UUID YOHO_CHARACTERISTIC_CONFIGURATION = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
-public static final UUID YOHO_CONTROL = UUID.fromString("0000cc06-0000-1000-8000-00805f9b34fb");
-public static final UUID YOHO_DEVICE_INFO = UUID.fromString("0000cc02-0000-1000-8000-00805f9b34fb");
-public static final UUID YOHO_REALTIME_DATA = UUID.fromString("0000cc04-0000-1000-8000-00805f9b34fb");
-public static final UUID YOHO_SERVICE = UUID.fromString("0000cc00-0000-1000-8000-00805f9b34fb");
-public static final UUID YOHO_SYNC_DATA = UUID.fromString("0000cc05-0000-1000-8000-00805f9b34fb");
-public static final UUID YOHO_USER_INFO = UUID.fromString("0000cc01-0000-1000-8000-00805f9b34fb");
-#+END_SRC
-
-While this isn't enough to use them (for example, =YOHO_CONTROL= is clearly the
-entry point to a multitude of functionality), the JADX output is readable enough
-that behavior is easily determined. Take this excerpt from
-=BluetoothLeService.java= as an example:
-
-#+BEGIN_SRC java
-public static void setVibrate(BluetoothGatt bluetoothGatt, boolean z) {
- BluetoothGattService service;
- BluetoothGattCharacteristic characteristic;
- Log.i(TAG, "setVibrate()");
- if (bluetoothGatt != null && \\
- (service = bluetoothGatt.getService(YOHO_SERVICE)) != null && \\
- (characteristic = service.getCharacteristic(YOHO_CONTROL)) != null) {
- if (z || ((Boolean) SPUtils.get(sContext, Constant.HAS_BT, false)).booleanValue()) {
- characteristic.setValue(new byte[]{1, 1});
- } else {
- characteristic.setValue(new byte[]{1, 0});
- }
- bluetoothGatt.writeCharacteristic(characteristic);
- }
-}
-#+END_SRC
-
-If you're averse to Java, the bottom-line is that the vibrate feature[fn:6] is
-configured by sending a packet to =YOHO_CONTROL= where the first byte is =1= and the
-second byte is whether or not to enable vibration.
-
-Perhaps that's a bit mundane. If you're more interested by the acronym "OTA"
-appearing in this context, you're not alone.
-
-* Striking Oil
-
-It wasn't long until I came across a bunch of plaintext API secrets for [[https://en.wikipedia.org/wiki/Alibaba_Cloud][Aliyun]],
-which is apparently China's answer to Amazon Web Services. The keys were for
-their S3-equivalent (OSS: Object Storage Service), which I needed my friend Luis
-to explain to me as I was an AWS virgin until a few months ago. S3 (and OSS) are
-key-value databases. The database is divided into *buckets*. It's a fairly simple
-way of storing chunks of data "in the cloud", and the format for keys makes
-apparent the comparison to a file system.
-
-Luis also found [[https://github.com/aliyun/ossutil][ossutil]], which is like [[https://aws.amazon.com/cli/][awscli]] for Aliyun, so we were able to
-list off the buckets with a couple shell commands.
-
-#+BEGIN_SRC prog
-~ $ ./ossutil64 ls
-CreationTime Region StorageClass BucketName
-2018-05-15 09:43:06 +0000 UTC oss-cn-beijing Standard oss://android-update
-2019-11-15 08:25:54 +0000 UTC oss-cn-beijing Standard oss://mcube-osm
-2018-04-28 06:31:06 +0000 UTC oss-cn-beijing Standard oss://mcube-ota
-Bucket Number is: 3
-
-1.727812(s) elapsed
-~ $ ./ossutil64 ls oss://android-update
-LastModifiedTime Size(B) StorageClass ETAG ObjectName
-2018-05-24 06:03:42 +0000 UTC 0 Standard D41D8CD98F00B204E9800998ECF8427E oss://android-update/Bingo Sport/
-2018-05-18 09:31:37 +0000 UTC 0 Standard D41D8CD98F00B204E9800998ECF8427E oss://android-update/DJObewegt/
-2018-05-15 09:49:26 +0000 UTC 0 Standard D41D8CD98F00B204E9800998ECF8427E oss://android-update/l8star/
-2018-05-15 09:43:33 +0000 UTC 0 Standard D41D8CD98F00B204E9800998ECF8427E oss://android-update/yoho/
-Object Number is: 4
-
-1.716424(s) elapsed
-~ $ ./ossutil64 ls oss://mcube-ota
-LastModifiedTime Size(B) StorageClass ETAG ObjectName
-2020-11-04 03:06:57 +0000 UTC 0 Standard D41D8CD98F00B204E9800998ECF8427E oss://mcube-ota/Dialog/
-2020-11-04 03:07:07 +0000 UTC 0 Standard D41D8CD98F00B204E9800998ECF8427E oss://mcube-ota/Dialog/MP1612/
-2021-03-23 03:06:34 +0000 UTC 0 Standard D41D8CD98F00B204E9800998ECF8427E oss://mcube-ota/Dialog/MP1613/
-2021-07-02 02:43:31 +0000 UTC 281396 Standard 91F5B5CA01AE0953C225E71C1B145153 oss://mcube-ota/Dialog/MP1613/mc_band.8F.64.09.00.img
-2021-03-18 07:22:23 +0000 UTC 0 Standard D41D8CD98F00B204E9800998ECF8427E oss://mcube-ota/Dialog/MP1615/
-2021-07-01 02:53:01 +0000 UTC 286676 Standard 6893E93A305B0DA4FEC5E30E0B67E598 oss://mcube-ota/Dialog/MP1615/mc_band.8F.64.0F.01.img
-2021-07-26 03:06:29 +0000 UTC 0 Standard D41D8CD98F00B204E9800998ECF8427E oss://mcube-ota/GuangJi/
-2021-07-26 03:10:52 +0000 UTC 92392 Standard A69DD1D52C5A714507CA4E18705E02B5 oss://mcube-ota/GuangJi/GM121Q1UI_V7B_6B_04_30.bin
-2020-04-29 08:27:35 +0000 UTC 0 Standard D41D8CD98F00B204E9800998ECF8427E oss://mcube-ota/Habit+/
-2020-01-10 09:50:03 +0000 UTC 0 Standard D41D8CD98F00B204E9800998ECF8427E oss://mcube-ota/JT/
-2020-01-10 09:50:15 +0000 UTC 119876 Standard EC902EEA38076E9132978E77CE1D72F9 oss://mcube-ota/JT/FACTORY_JT_R7_0.96_HRS3300_V6E_73_00_00.bin
-2020-01-10 09:50:15 +0000 UTC 117568 Standard A072ED4E40E2E255670EEDC16D4FB595 oss://mcube-ota/JT/FACTORY_JT_R9_1.0_HRS3300S_V6B_72_00_00.bin
-2020-01-10 09:50:15 +0000 UTC 168023 Standard CE29DA4EE5E2829A43B9B8C03D016284 oss://mcube-ota/JT/JT_R3_0.66_HRS3300S_V4A_72_00_00.bin
-2020-01-10 09:50:15 +0000 UTC 107564 Standard 1827A73A6778C25C231DE716B5479E60 oss://mcube-ota/JT/JT_R5_0.96_96X96_HRS3300_V4F_6E_00_00.bin
-2020-06-17 10:10:47 +0000 UTC 0 Standard D41D8CD98F00B204E9800998ECF8427E oss://mcube-ota/M2/
-2020-07-23 15:01:22 +0000 UTC 111616 Standard DB7815961B514B0637A398FB1CC679DD oss://mcube-ota/M2/M2_E_IPE167_V41_7E_00_32.bin
-2020-07-23 15:01:22 +0000 UTC 111616 Standard E784EB22FDB99F943C9790D935D341ED oss://mcube-ota/M2/M2_GS_IPG67_V41_7E_00_33.bin
-2018-11-19 09:54:26 +0000 UTC 0 Standard D41D8CD98F00B204E9800998ECF8427E oss://mcube-ota/Maxsend/
-2019-11-12 10:29:47 +0000 UTC 0 Standard D41D8CD98F00B204E9800998ECF8427E oss://mcube-ota/Maxsend/GM115/
-2019-11-12 10:33:05 +0000 UTC 0 Standard D41D8CD98F00B204E9800998ECF8427E oss://mcube-ota/Maxsend/GM115/0.96S/
-2019-11-12 10:38:06 +0000 UTC 96940 Standard E34860D829252CB9F7FB3EA94F5C32C4 oss://mcube-ota/Maxsend/GM115/0.96S/GM115_0.96S_V82_62_00_20.bin
-2019-12-26 10:18:31 +0000 UTC 96100 Standard 1D979392E3E040319EFDF59BE90CC82D oss://mcube-ota/Maxsend/GM115/0.96S/GM115_V82_63_00_24.bin
-2019-11-12 10:30:33 +0000 UTC 0 Standard D41D8CD98F00B204E9800998ECF8427E oss://mcube-ota/Maxsend/GM115/7735BOE/
-2019-11-12 10:30:51 +0000 UTC 96940 Standard BB4945F2B255C2D97725F30E840C91B9 oss://mcube-ota/Maxsend/GM115/7735BOE/GM115_7735BOE_V82_62_00_21.bin
-2019-12-26 10:19:10 +0000 UTC 96100 Standard 111EE153248CA0A09CBCE84ED58C2A6C oss://mcube-ota/Maxsend/GM115/7735BOE/GM115_V82_63_00_25.bin
-2020-05-14 11:33:04 +0000 UTC 100564 Standard BA066102FA168C7FCFD434DEB8BCBE9D oss://mcube-ota/Maxsend/GM115/GM115_CEUI_0.96S_MC34XX_HRS3300_JJ_V82_66_00_36.bin
-2020-05-14 11:33:20 +0000 UTC 100584 Standard D88180FA87B24E934D2CBDDE9E051006 oss://mcube-ota/Maxsend/GM115/GM115_CEUI_BOE_0.96S_MC34XX_HRS3300_JJ_V82_66_00_37.bin
-2020-08-19 08:50:57 +0000 UTC 0 Standard D41D8CD98F00B204E9800998ECF8427E oss://mcube-ota/Maxsend/GM120/
-2020-08-19 08:52:20 +0000 UTC 116852 Standard 63B9FD299CCFA9D47C22DF79ACD9B712 oss://mcube-ota/Maxsend/GM120/GM120M_V8D_63_00_21.bin
-2020-07-27 11:44:29 +0000 UTC 0 Standard D41D8CD98F00B204E9800998ECF8427E oss://mcube-ota/Maxsend/GM127B0/
-2020-07-27 11:45:29 +0000 UTC 115772 Standard 34C740D1B7C6E1C732B96306FC2F3917 oss://mcube-ota/Maxsend/GM127B0/GM127_V8C_6D_00_21.bin
-2019-12-10 14:44:46 +0000 UTC 0 Standard D41D8CD98F00B204E9800998ECF8427E oss://mcube-ota/Maxsend/M3/
-2020-05-15 11:13:43 +0000 UTC 102428 Standard 8032B2E58825204E9264B434BB57F684 oss://mcube-ota/Maxsend/M3/M3_7735BOE_0.96S_MC34XX_HRS3300_SC7R30_JJ_V80_6C_00_25.bin
-2020-08-06 03:39:21 +0000 UTC 101856 Standard D4633331413CA3992E836257023271EC oss://mcube-ota/Maxsend/M3/M3_HSD_0.96S_V80_6E_00_24.bin
-2019-11-09 08:39:37 +0000 UTC 0 Standard D41D8CD98F00B204E9800998ECF8427E oss://mcube-ota/Maxsend/M4/
-2020-05-15 11:14:19 +0000 UTC 102656 Standard 3B6DEE0CA2399446B60499F7C0681B3D oss://mcube-ota/Maxsend/M4/M4_UI3_7735BOE_0.96S_MC34XX_HRS3300_SC7R30_JJ_V81_69_00_25.bin
-2020-05-15 13:31:52 +0000 UTC 102636 Standard 2F8CD37A50EB9F3D68DB250EC2A80C98 oss://mcube-ota/Maxsend/M4/M4_UI3_7735BOE_0.96S_MC34XX_HRS3300_SC7R30_JJ_V81_69_00_27.bin
-2020-05-15 11:14:19 +0000 UTC 102636 Standard FEE888D1CCA800FD2275B748410477E2 oss://mcube-ota/Maxsend/M4/M4_UI3_HSD_0.96S_MC34XX_HRS3300_SC7R30_JJ_V81_69_00_24.bin
-2020-05-15 13:31:52 +0000 UTC 102636 Standard 02B36A5DD5E7154EC163FA20B0BF0FD0 oss://mcube-ota/Maxsend/M4/M4_UI3_HSD_0.96S_MC34XX_HRS3300_SC7R30_JJ_V81_69_00_26.bin
-2020-06-01 07:52:49 +0000 UTC 0 Standard D41D8CD98F00B204E9800998ECF8427E oss://mcube-ota/Maxsend/gm116m/
-2020-06-01 07:59:42 +0000 UTC 100420 Standard F2F8A68725BA5B78039011B87F8D1C1B oss://mcube-ota/Maxsend/gm116m/GM116M_EARTHUI_1.3S_MC34XX_HRS3300_JJ_V8A_60_00_24.bin
-2020-08-12 05:46:13 +0000 UTC 99920 Standard F36E3E2BC232D48497C717A6CFF0F315 oss://mcube-ota/Maxsend/gm116m/GM116M_SF1049A_V3D_64_00_23.bin
-2019-04-08 01:50:48 +0000 UTC 0 Standard D41D8CD98F00B204E9800998ECF8427E oss://mcube-ota/force-upgrade/
-2020-06-01 08:02:51 +0000 UTC 1528 Standard 9D0DB298389CF3296ADFD459C18CB2C1 oss://mcube-ota/force-upgrade/force upgrade.txt
-2020-08-04 08:17:16 +0000 UTC 206740 Standard BBF5D1BCC6845E63AE084DF75CDD4312 oss://mcube-ota/mc_band.8F.61.0A.20.img
-Object Number is: 45
-
-6.872844(s) elapsed
-~ $ ./ossutil64 ls oss://mcube-osm
-LastModifiedTime Size(B) StorageClass ETAG ObjectName
-2019-11-15 08:27:09 +0000 UTC 0 Standard D41D8CD98F00B204E9800998ECF8427E oss://mcube-osm/andromeda/
-2019-11-15 08:27:20 +0000 UTC 153824 Standard DC701B0A89718AFE223C62DE10139A33 oss://mcube-osm/andromeda/andromeda2_ble_1.0.0.bin
-Object Number is: 2
-
-1.736465(s) elapsed
-#+END_SRC
-
-I swear these buckets were populated with more entries when I was streaming.
-Anyway, this is when we realized that this is an elaborate scheme. Whatever
-organization is behind the watch I have actually makes several kinds of smart
-watches, and several distinct apps for interfacing with them.
-
-The name on the box is a hint that we care about either
-=M2_E_IPE167_V41_7E_00_32.bin= or =M2_GS_IPG67_V41_7E_00_33.bin=, which are
-deceptively similar.
-
-#+BEGIN_SRC prog
-$ 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
-#+END_SRC
-
-But turns out that we're not interested in those. The four hexadecimal bytes in
-each filename corresponds to a device identifier that's spit out by OTA
-characteristic.
-
-#+BEGIN_SRC sh
-$ 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
-
-[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
-#+END_SRC
-
-In our case, we want something with the filename =..._V41_xx_00_21.bin= (note
-indices 11-14 in the byte array above). I have such a file saved from when I was
-initially doing the reverse engineering for this project,
-=LD702A_DY_0.42_MC34XX_EM70XX_HRS3300_JJ_V41_72_00_21.bin=, but this doesn't
-appear in the listing from =ossutil= from today. I suspect the organization has
-since nuked several firmware images.
-
-* Flashing Firmware
-
-I was determined to flash custom firmware to this device. There are several
-characteristics whose name contains "OTA", but only the =TELINK_*= ones are
-advertised by my watch. The first clue, in =BluetoothLeService.java=:
-
-#+BEGIN_SRC java
-public void onServicesDiscovered(BluetoothGatt bluetoothGatt, int i) {
- ...
- int i3 = 0;
- while (true) {
- if (i3 >= services.size()) {
- break;
- }
- UUID uuid2 = services.get(i3).getUuid();
- if (uuid2.toString().equals(BluetoothLeService.TELINK_SPP_DATA_OTA_SERVICE.toString())) {
- BluetoothLeService.sOtaType = 1;
- break;
- } else if (uuid2.toString().equals(BluetoothLeService.MAXSCEND_OTA_SERVICE.toString())) {
- BluetoothLeService.sOtaType = 2;
- break;
- } else {
- BluetoothLeService.sOtaType = 0;
- i3++;
- }
- }
- ...
-}
-#+END_SRC
-
-So we want to find a branch dependent on =BluetoothLeService.sOtaType= being =1=.
-There's one hidden deep in =MainActivity.java=[fn:7]:
-
-#+BEGIN_SRC java
-public void onReceive(Context context, Intent intent) {
- ...
- else if (action.equals(Constant.ACTION_OTA_CONFIRMED)) {
- int intValue = ((Integer) SPUtils.get(MainActivity.this, Constant.DEV_BATT_PERCENTAGE, 0)).intValue();
- int intValue2 = ((Integer) SPUtils.get(MainActivity.this, Constant.DEV_BATT_STATUS, 3)).intValue();
- if (intValue >= 50 || intValue2 != 3) {
- SPUtils.put(MainActivity.this, "has_weather", false);
- if (BluetoothLeService.sOtaType == 1) {
- if (TelinkOta.getBytesCount() != 0) {
- new TelinkOtaTask().execute(new Void[0]);
- }
- } else if (BluetoothLeService.sOtaType == 2) {
- BluetoothLeService.setMxdCmdCharNotify(BluetoothLeService.getBluetoothGatt());
- }
- } else {
- new AlertDialog.Builder(MainActivity.this)
- .setMessage(R.string.force_ota_failed)
- .setNegativeButton(R.string.ok, (DialogInterface.OnClickListener) null)
- .create()
- .show();
- Log.i(MainActivity.TAG, "can not upgrade firmware while battery is less than 50%");
- }
- }
-}
-#+END_SRC
-
-This brings us to the conveniently named =TelinkOtaTask=.
-
-#+BEGIN_SRC java
-public class TelinkOtaTask extends AsyncTask<Void, Void, Void> {
- long delay = 100;
-
- public TelinkOtaTask() {
- }
-
- /* access modifiers changed from: protected */
- public void onPreExecute() {
- super.onPreExecute();
- MainActivity.this.initOTAProgressDialog(1);
- MainActivity.this.getWindow().addFlags(128);
- }
-
- /* access modifiers changed from: protected */
- public Void doInBackground(Void... voidArr) {
- for (int i = 0; i < TelinkOta.getBlockCount() + 3; i++) {
- if (i != 0) {
- 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]);
- }
- }
- }
- return null;
- }
-
- /* access modifiers changed from: protected */
- public void onProgressUpdate(Void... voidArr) {
- super.onProgressUpdate(voidArr);
- MainActivity.this.otaProgressDialog.incrementProgressBy(1);
- }
-
- /* access modifiers changed from: protected */
- public void onCancelled() {
- super.onCancelled();
- }
-
- /* access modifiers changed from: protected */
- public void onCancelled(Void voidR) {
- super.onCancelled(voidR);
- }
-}
-#+END_SRC
-
-The code is using this =com.uthink.ring.update.TelinkOta= class to chunk the
-firmware image, and write it to the OTA characteristic one block at a time. We
-begin the transfer by writing =0x01ff=, and end by writing =TelinkOta.getEndCmd()=.
-The =TelinkOta= class is short enough that I can drop the listing here.
-
-#+BEGIN_SRC java
-package com.uthink.ring.update;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Arrays;
-
-public class TelinkOta {
- public static final int BLOCK_LENGTH = 16;
- public static final int CODE_SIZE_LENGTH = 4;
- public static final int CODE_SIZE_START = 24;
- private static final boolean DEBUG = false;
- private static final String TAG = TelinkOta.class.getSimpleName();
- public static final boolean USE_CB = false;
- public static int blockCount;
- public static byte[] bytes;
- public static int bytesCount;
- public static int currentCount;
-
- public static void setFile(InputStream inputStream) {
- try {
- bytesCount = inputStream.available();
- blockCount = (int) Math.ceil((double) (((float) bytesCount) / 16.0f));
- bytes = new byte[bytesCount];
- inputStream.read(bytes);
- inputStream.close();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (NullPointerException e2) {
- e2.printStackTrace();
- } catch (IOException e3) {
- e3.printStackTrace();
- }
- }
-
- public static void setFile(byte[] bArr) {
- bytesCount = bArr.length;
- blockCount = (int) Math.ceil((double) (((float) bytesCount) / 16.0f));
- bytes = bArr;
- }
-
- public static int getBytesCount() {
- return bytesCount;
- }
-
- public static int getBlockCount() {
- return blockCount;
- }
-
- public static int getCurrentCount() {
- return currentCount;
- }
-
- public static void currentCountPlus() {
- currentCount++;
- }
-
- public static int getCodeSize() {
- return ByteBuffer.wrap(Arrays.copyOfRange(bytes, 24, 28)).order(ByteOrder.LITTLE_ENDIAN).getInt();
- }
-
- public static byte[] getEndCmd() {
- byte[] bArr = new byte[6];
- bArr[0] = 2;
- bArr[1] = -1;
- int i = blockCount;
- bArr[2] = (byte) ((i - 1) & 255);
- bArr[3] = (byte) (((i - 1) >> 8) & 255);
- bArr[4] = (byte) (bArr[2] ^ 255);
- bArr[5] = (byte) (bArr[3] ^ 255);
- return bArr;
- }
-
- public static byte[] getBlock(int i) {
- byte[] bArr;
- byte[] bArr2 = new byte[0];
- byte[] bArr3 = {(byte) (i & 255), (byte) ((i >> 8) & 255)};
- try {
- if (i == blockCount - 1) {
- int i2 = bytesCount % 16;
- if (i2 == 0) {
- int i3 = i * 16;
- bArr = Arrays.copyOfRange(bytes, i3, i3 + 16);
- } else {
- int i4 = i * 16;
- byte[] copyOfRange = Arrays.copyOfRange(bytes, i4, i4 + i2);
- byte[] bArr4 = new byte[(16 - i2)];
- for (int i5 = 0; i5 < bArr4.length; i5++) {
- bArr4[i5] = -1;
- }
- bArr = concatByteArrays(copyOfRange, bArr4);
- }
- } else {
- int i6 = i * 16;
- bArr = Arrays.copyOfRange(bytes, i6, i6 + 16);
- }
- byte[] concatByteArrays = concatByteArrays(concatByteArrays(bArr2, bArr3), bArr);
- int CRC_16 = CRC_16(byteToUnsignedChar(concatByteArrays));
- byte[] concatByteArrays2 = concatByteArrays(concatByteArrays, new byte[]{(byte) (CRC_16 & 255), (byte) ((CRC_16 >> 8) & 255)});
- printByteToHex(concatByteArrays2);
- return concatByteArrays2;
- } catch (NullPointerException unused) {
- return bArr2;
- }
- }
-
- public static int CRC_16(char[] cArr) {
- char[] cArr2 = {0, 40961};
- int i = 0;
- char c = 65535;
- while (i < cArr.length) {
- char c2 = cArr[i];
- char c3 = c;
- for (int i2 = 0; i2 < 8; i2++) {
- c3 = cArr2[(c3 ^ c2) & 1] ^ (c3 >> 1);
- c2 = (char) (c2 >> 1);
- }
- i++;
- c = c3;
- }
- return c;
- }
-
- public static void close() {
- bytesCount = 0;
- blockCount = 0;
- currentCount = 0;
- bytes = null;
- }
-
- public static byte[] concatByteArrays(byte[] bArr, byte[] bArr2) {
- byte[] bArr3 = new byte[(bArr.length + bArr2.length)];
- System.arraycopy(bArr, 0, bArr3, 0, bArr.length);
- System.arraycopy(bArr2, 0, bArr3, bArr.length, bArr2.length);
- return bArr3;
- }
-
- public static char[] byteToUnsignedChar(byte[] bArr) {
- char[] cArr = new char[bArr.length];
- for (int i = 0; i < cArr.length; i++) {
- cArr[i] = (char) (bArr[i] & 255);
- }
- return cArr;
- }
-
- public static void printByteToHex(byte[] bArr) {
- StringBuilder sb = new StringBuilder();
- for (byte b : bArr) {
- if (sb.length() > 0) {
- sb.append(':');
- }
- sb.append(String.format("%02x", new Object[]{Byte.valueOf(b)}));
- }
- }
-}
-#+END_SRC
-
-The bottom-line is that we're breaking the firmware image into 16-byte blocks
-and attaching a [[https://en.wikipedia.org/wiki/Cyclic_redundancy_check][CRC-16]] to each one. The code's already there, so I hacked
-together a little =main= function to perform the chunking for a file of my
-choosing.
-
-#+BEGIN_SRC java
-public static void main(String[] args) {
- if (args.length != 1) {
- System.err.printf("usage: TelinkOta [IMAGE]\n");
- return;
- }
-
- try {
- FileInputStream f = new FileInputStream(args[0]);
- setFile(f);
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- return;
- }
-
- System.out.println(printByteToHex(new byte[]{1, -1}));
- for (int i = 0; i < TelinkOta.getBlockCount(); i++) {
- byte[] block = TelinkOta.getBlock(i);
- System.out.println(printByteToHex(block));
- }
- System.out.println(printByteToHex(TelinkOta.getEndCmd()));
-}
-#+END_SRC
-
-This spits out each "packet" as a line of hexadecimal digits. I can then use
-some Emacs magic to turn said lines into shell commands, producing an extremely
-cursed shell script to flash a hard-coded firmware image to the device.[fn:9]
-
-#+BEGIN_SRC prog
-gatttool -b A4:C1:7a:56:82:90 --char-write-req --handle=0x0008 --value=01ff
-gatttool -b A4:C1:7a:56:82:90 --char-write-req --handle=0x0008 --value=00000e800103000000004b4e4c54000288006365
-gatttool -b A4:C1:7a:56:82:90 --char-write-req --handle=0x0008 --value=01007680000000000000cc9e0100000000007423
-gatttool -b A4:C1:7a:56:82:90 --char-write-req --handle=0x0008 --value=020031083209320a910202ca085004b1fa878c26
-gatttool -b A4:C1:7a:56:82:90 --char-write-req --handle=0x0008 --value=03002008c06b210885061f08c06b200885063504
-gatttool -b A4:C1:7a:56:82:90 --char-write-req --handle=0x0008 --value=040000a02009200a910202ca085004b1fa873b7f
-gatttool -b A4:C1:7a:56:82:90 --char-write-req --handle=0x0008 --value=05001f09200a910202ca085004b1fa871b090552
-gatttool -b A4:C1:7a:56:82:90 --char-write-req --handle=0x0008 --value=06001d08084001b048403fa31bf31b58a5abd4be
-gatttool -b A4:C1:7a:56:82:90 --char-write-req --handle=0x0008 --value=070012c11fa2050b060812f302da02d3830271fc
-gatttool -b A4:C1:7a:56:82:90 --char-write-req --handle=0x0008 --value=0800fbc1040b88a21a40fe87c0460080800095a0
-gatttool -b A4:C1:7a:56:82:90 --char-write-req --handle=0x0008 --value=090000868000020680001009110a110b9a02592c
-#+END_SRC
-
-At this point, I hadn't reverse-engineered the firmware image, so I attempted
-changing one of the ASCII strings that =radare2= could find, with little
-consideration to what might happen.
-
-#+CAPTION: Before and after flashing firmware.
-[[./dollar-bin-reverse-engineering-firmware-mod.jpg]]
-
-* Where I Would Have Gone Next
-
-I found being able to flash unauthenticated firmware to be a humorous attack
-vector. How about a worm for that $5 watch you got at the gas station?
-
-I came across a [[https://gitter.im/nRF51822-Arduino-Mbed-smart-watch/Lobby?at=5b62115c854add6cee148651][Gitter conversation]] revealing I'm not the first to try to
-reverse engineer this watch. Ah well. They figured out that the SoC is likely
-based on [[http://wiki.telink-semi.cn/doc/ds/DS_TLSR8232-E_Datasheet%20for%20Telink%20BLE%20SoC%20TLSR8232.pdf][TLSR8232]], and the MCU is likely to be the [[https://github.com/rgov/Ghidra_TELink_TC32][TC32]][fn:8]. That saved me
-from having to hammer my watch into pieces.
-
-I couldn't find much information on either, so if I were going to reverse
-engineer the firmware, I would have to reverse engineer the [[https://github.com/OnTheWayJohn/ble_lt_sdk][SDK]] to figure out
-things like the image load address. The project's on the shelf for now because
-of that. It /might/ be a fun project to reverse engineer the SoC/MCU, and I'd
-certainly learn a lot, but that would be more work than I want to put into this
--- I don't have an immediate interest in firmware reverse engineering at the
-moment.
-
-[fn:1] This was before I learned how the sleep tracking features on these sorts of products work: not well. In retrospect, I should've saved up for an EEG device like the now-discontinued [[https://www.gwern.net/Zeo][Zeo]].
-
-[fn:2] My primary concern was the software being non-free, but I also voiced concerns about privacy in the live stream. There are [[https://reports.exodus-privacy.eu.org/en/reports/com.uthink.ring/latest/][three separate analytics platforms]] tracking users of the application.
-
-[fn:3] ref: [[https://stackoverflow.com/questions/2719469/why-is-the-jvm-stack-based-and-the-dalvik-vm-register-based][Stack Overflow: Why is the JVM stack-based and the Dalvik VM register-based?]]
-
-[fn:4] ref: [[https://source.android.com/devices/tech/dalvik/gc-debug][Android Open Source Project: Debugging ART Garbage Collection]]
-
-[fn:5] Of course, Java is pervasive, so we have cursed things such as [[https://en.wikipedia.org/wiki/Jazelle][Jazelle]] which /do/ execute bytecode on hardware.
-
-[fn:6] When I was first working on this, the damn vibrate feature woke me up at five in the morning, trying to tell me that the watch was low on battery.
-
-[fn:7] If you're unfamiliar with the Android SDK, "activities" compose the UI. As in, this snippet is in the UI code.
-
-[fn:8] There have apparently been /some/ [[https://hackaday.com/2020/11/17/custom-firmware-for-cheap-bluetooth-thermometers/][reverse engineering efforts]] involving the TC32.
-
-[fn:9] In this case, I'm referring to the characteristic by its /handle/ rather than by its UUID. My uninformed understanding is that the handle is a sort of "short" identifier used for the same purpose as the UUID. I'm doing it this way because =gatttool= only let me write to this particular characteristic if I used a handle.

© 2015 - 2026 Jakob L. Kreuze