diff options
| author | Jakob L. Kreuze <zerodaysfordays@sdf.org> | 2021-12-19 15:43:00 -0500 |
|---|---|---|
| committer | Jakob L. Kreuze <zerodaysfordays@sdf.org> | 2021-12-19 15:43:00 -0500 |
| commit | 6a4b63e1c862278df7af20e46caa6d4a63de8506 (patch) | |
| tree | 10c29fcb9dc0a054cd3d383613012de1e1a1f2d5 | |
| parent | 9241b4dc730611d2f57e4668f5aa9046ec2d0ad3 (diff) | |
[org] Initial commit for "Dollar Bin Reverse Engineering".
| -rw-r--r-- | org/Dollar Bin Reverse Engineering/dollar-bin-reverse-engineering.org | 407 |
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]]. |