Skip to content

Instantly share code, notes, and snippets.

@msmuenchen
Last active May 8, 2024 08:14
Show Gist options
  • Save msmuenchen/d2a738b85342f57e423c0b197f278fe3 to your computer and use it in GitHub Desktop.
Save msmuenchen/d2a738b85342f57e423c0b197f278fe3 to your computer and use it in GitHub Desktop.
Patching around the Samsung Wearable ecosystem for fun and (no) profit

tl;dr: Samsung is a bunch of crap company that doesn't inform their customers about serious limitations on their Wearables lineup. Great hardware, dog poo software.

So, what's this about: Samsung omits a bunch of crucial information for consumers anywhere on the Galaxy Watch product pages - at least on those for the Galaxy Watch 4, Galaxy Watch 5 or Galaxy Watch 6.

They omit on the product pages, the pages that should inform a consumer about requirements and limitations, that:

  1. unless you're rooted, the companion phone actually must be a phone, tablets and even phablets like the Galaxy Tab Active 3 are not supported. (If you're rooted, you can patch that away. Just follow along for the guide)
  2. only certain wearables support being paired to a companion phone not being a Samsung device
  3. rooted devices lose access to Samsung Health / Samsung Health

Elsewhere, they claim that:

  1. Samsung Health data is "backed by ... Knox", whose tripping (as part of rooting one's Samsung device) should lead to Samsung Health being "rendered inaccessible"
  2. you need a Samsung Galaxy smartphone to be able to use Samsung Health Monitor and Samsung Health sleep tracking

Note that, although I have not verified any of this for anything but the Watch 4, this is all also valid for the Fit armband, Buds/IconX earbuds and the new, rumored Galaxy Ring as they share the Manager app and its anti-tablet/modification measures as well as the Samsung Health/Health Monitor apps and their anti-rooting measures.

All of this is pure and utter bull dung, which I'll show how to bypass here if you've been foolish enough to buy a Samsung Wearable to be used on a rooted phone. Nothing that Samsung does here is necessary from a technical viewpoint, it's in my opinion a pure and simple a cash grab to prevent people only using their tablet lineup (in my case, a Samsung Galaxy Tab Active 3) as a watch controller, instead they have to buy an expensive Galaxy smartphone, and it's a massive annoyance for those who like to actually own their device (aka root them).

Prerequisites

To follow along, you'll need:

  1. a macOS, Linux or Windows with WSL machine (personally, I run a MacBook Air with M2)
  2. VSCode and APKLab
  3. Android Studio, at least adb, installed. adb must be available on the terminal / $PATH
  4. curl available on the terminal / $PATH
  5. a rooted phone, preferably some recent Samsung. Rooting must be done using Magisk, USB adb connection must work.
  6. the Magisk modules frida and frida-inject
  7. frida installed and available on $PATH, see here. The version should match the one in magisk-frida.
  8. The target phone must be connected to adb, either via USB cable or via WifiAdb.

Prepare to download APKs from Samsung

To be able to download the APKs from Samsung (I cannot and will not provide them for copyright reasons), you will need:

  1. a random 16-digit hex number. This will be used as a replacement for ANDROID_ID. You can use any random generator you like, I used this online generator.
  2. your target device's API level. Find it out using adb shell getprop ro.build.version.sdk.
  3. a Samsung device name we're going to spoof. There must be an official Samsung firmware with the API level from #2 available. If you have a recent Android device, chances are you have Android 12 aka API level 31, so you can spoof SM-G990B aka Galaxy S21 FE. For best compatibility, if it's a Samsung that's just been rooted, determine it using adb shell getprop getprop ro.product.vendor.model.
  4. a valid combination of MNC and MCC (country + operator, see this list and the matching Samsung CSC. Ideally, these should match your current country and provider, the CSC should be the one Samsung uses for unbranded devices in your country. If in doubt, use MCC 262 (Germany), MNC 01 (Deutsche Telekom AG) and CSC DBT (Germany unbranded). To use the values from your device, run adb shell getprop gsm.operator.numeric, the first three digits are the MCC, the last two the MNC. For Samsung devices, to obtain the CSC, run adb shell getprop ril.matchedcsc.

Now, combine that into an URL pattern: https://vas.samsungapps.com/earth/stub/stubDownload.as?deviceId=<device name, default SM-G990B>&mcc=<MCC, default 262>&mnc=<MNC, default 01>&csc=<CSC, default DBT>&sdkVer=<Android API level, default 31>&abiType=<ABI type, either default 64 or 32>&extuk=<ANDROID_ID>&appId=<APP_ID>. If in doubt or the above was too complex, use https://vas.samsungapps.com/earth/stub/stubDownload.as?deviceId=SM-G990B&mcc=262&mnc=01&csc=DBT&sdkVer=31&abiType=64&extuk=e982a4183204d42a&appId=<APP_ID>.

App structure and general workflow of the ecosystem

The first application you install will be the Galaxy Wearable application (aka "manager"). It will walk you through pairing your Wearable with your phone via Bluetooth, onboard it to your Samsung and Google accounts which, to their credit, do not seem to be necessary for basic operation, and set up the eSIM for the LTE-capable models (Side rant: It is completely beyond me why at least the LTE capable models require a phone for operation at all. They can work perfectly fine without one.). Once the initial setup is completed, you can configure the clock faces and behavior of the wearable in this app.

Behind the scenes, the Wearable application will set up a shared user com.samsung.accessory.wmanager because that is how it communicates with the plugins, and a bunch of permissions that apps like Health and Health Monitor can use to bind to communications facilities with wearables. As part of the onboarding process, it scans for nearby Bluetooth devices and uses a rule matching engine to determine if the device is a Samsung Wearable and which communication plugin it needs to download from the Samsung app store servers. Then, as it's paired to the device, it will download and install the communication plugin and use its services to run the setup process that differs for each device generation. If it is already installed, it will not attempt to install the plugin again.

There are, at the moment, at least two companion apps: Samsung Health, an app that tracks data from the step counter, pulse/oxymeter, GPS, sleep quality and other sensors to aggregate that data, and Samsung Health Monitor that you can use to calibrate and store ECG and blood pressure sensor data.

Download the APKs

For each of the following app ID's, take the VAS url template constructed above, replace APP_ID by the app ID (or click the link below if you're fine with German APKs and Android API level 31), open the URL, and download the link in "downloadURI":

You will also need the plugin(s) for your Wearable device(s). (For posterity: run grep WearableDeviceRule com.samsung.android.app.watchmanager/java_src/p109l3/C3483a.java on the watchmanager APK once decompiled with APKLab)

Then, use adb install /path/to/app.apk on each of the APKs. You will at least need the Manager and the device plugin app - but no matter what, install the Manager app first and then the others.

Monkey-patch the Galaxy Wearable Manager application

The Manager application has a few safeguards hindering us from using a Wearable on tablets and rooted devices:

  1. The setup wizard will not offer you to pass beyond pairing if you are using a tablet, based on a check in SetupWizardWelcomeActivity::wearableListener::onPaired:

image

2. The app will close on launch and error out if it detects it can't install itself (wtf that even does, no idea), based on a check in `SetupWizardWelcomeActivity::checkExitCases`.

image

image

3. There's *yet another* anti-tablet and anti-no-Samsung check in `BluetoothApiUtil::isSupportedInHostDevice`:

image

We bypass these using the attached script com.samsung.android.app.watchmanager.js in Frida. To do this, save the script somewhere, open a Terminal in that folder, and run frida -U -l com.samsung.android.app.watchmanager.js -f com.samsung.android.app.watchmanager. That spawns the app and loops every 5ms to check if the PathClassLoader has already initialized. Upon recognizing that, it overrides Samsung's nuisance functions to always return a positive (for us) value.

Note: One might argue I should use Java.perform. However, that has proven to be a bit unreliable for frida-inject, and an issue has been filed.

Obviously, this patch requires the user to run the app in tethered mode. To bypass this, we use frida-inject:

  1. Place the script on the phone: adb push com.samsung.android.app.watchmanager.js /sdcard
  2. Obtain a root shell: adb shell, followed by su
  3. Create the directory where frida-inject expects scripts: mkdir -p /data/misc/user/0/frida-inject
  4. Copy the script over there: cp /sdcard/com.samsung.android.app.watchmanager.js /data/misc/user/0/frida-inject/

Upon that, we have the most important thing done.

console.log("Initializing com.samsung.android.app.watchmanager anti-anti-root patcher");
global.t = setInterval(function() { // avoid java.lang.ClassNotFoundException
console.log("Checking for PlatformUtils class presence");
Java.enumerateLoadedClasses({
onMatch: function(className) {
if (className.match(/PlatformUtils/i)) {
console.log(className);
}
},
onComplete: function() {}
});
console.log("Attempting to check if we can load and patch KnoxControl");
Java.enumerateClassLoaders({
onMatch: function(loader) {
console.log("Attempting to check with", loader);
Java.classFactory.loader = loader;
var TestClass;
// Hook the class if found, else try next classloader.
try {
TestClass = Java.use("com.samsung.android.app.twatchmanager.util.PlatformUtils");
console.log("Found PlatformUtils, hooking functions");
clearInterval(global.t);
Java.use("com.samsung.android.app.twatchmanager.util.PlatformUtils").isTablet.implementation = () => false;
Java.use("com.samsung.android.app.twatchmanager.util.PlatformUtils").isSamsungDevice.implementation = () => true;
Java.use("com.samsung.android.app.twatchmanager.util.PlatformUtils").isSamsungDeviceWithCustomBinary.implementation = () => false;
Java.use("com.samsung.android.app.twatchmanager.util.BluetoothApiUtil").isSupportedInHostDevice.implementation = () => true;
Java.use("android.os.Build").MODEL.value = "SM-G990B";
} catch (error) {
if (error.message.includes("ClassNotFoundException")) {
console.log("Class not found, attempting next loader");
}
}
},
onComplete: function() {}
});
}, 5);
console.log("Initializing com.samsung.android.shealthmonitor anti-anti-root patcher");
global.t = setInterval(function() { // avoid java.lang.ClassNotFoundException
console.log("Checking for RootingCheckUtil class presence");
Java.enumerateLoadedClasses({
onMatch: function(className) {
if (className.match(/RootingCheckUtil/i)) {
console.log(className);
}
},
onComplete: function() {}
});
console.log("Attempting to check if we can load and patch RootingCheckUtil");
Java.enumerateClassLoaders({
onMatch: function(loader) {
console.log("Attempting to check with", loader);
Java.classFactory.loader = loader;
var TestClass;
// Hook the class if found, else try next classloader.
try {
TestClass = Java.use("com.samsung.android.shealthmonitor.util.RootingCheckUtil");
console.log("Found RootingCheckUtil, hooking functions");
clearInterval(global.t);
Java.use("com.samsung.android.shealthmonitor.util.RootingCheckUtil").isRooted.implementation = () => false;
Java.use("android.os.Build").MODEL.value = "SM-G990B";
} catch (error) {
if (error.message.includes("ClassNotFoundException")) {
console.log("Class not found, attempting next loader");
}
}
},
onComplete: function() {}
});
}, 5);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment