Reverse engineering a chinese USB e-massage app
Once I stumbled upon an interesting product, a massage device, which was powered solely by USB. Furthermore, it used a Micro-B plug, and was supposed to be connected to a smartphone, via OTG (imagine how much of a power drain that can be!). Sadly at the time of the writing of this article I couldn't find this product available anywhere on the internet, and their site seemed to be down as well. The only available information is their companion app on Google Play.
At first glance, this might seem your average dirt cheap chinese setup: if the hardware won't short and fry itself, then the app will steal and sell all of your available data, so I decided to mitigate at least the second risk and find out how the app works; more precisely, how it communicates with the end device. With reverese engineering such things, we have basically two options:
- Sniff and intercept all traffic, and analyse them with a tool (for example with Wireshark)
- Reverse engineer the master intelligence and driver
USB Micro female to USB male adapters were surprisingly hard to come by, so I decided on the second option. Android apps are generally easy to decompile and modify, so after using the apktool-dex2jar-jdgui trio, I had something which closely resembled to source code.
The app was partially obfuscated, but after a glance at the package names, I could narrow down where most of the driver logic would be:
com.developer.cd432rs.eMassage.devicecontrol.UsbControlService
Android USB host has a pretty well defined interface, so I could further narrow down my search knowing what transfer methods are present there.
private void o() {
if (this.m != null) {
System.out.println("Send command + BackgroundControllActivity sendCommand()");
this.m.controlTransfer(33, 9, 769, 256, this.n, this.n.length, 0);
Log.d("Command Send", p());
return;
}
System.out.println("mUsbConnection null");
}
If you take a look at the documentation, you can treat the first four magic numbers... well as magic numbers; only the first one has a bitfield-y meaning behind it. The next two parameters specify the sent data and its length, the last one is timeout, which is irrelevant in this case. To sum it up:
| Transfer type | Control |
| bmRequestType | 33 (H2D, Class, Interface) |
| bRequest | 9 |
| wValue | 0x0301 (769) |
| wIndex | 0x0100 (256) |
Also this app has most of its log messages intact, so with adb, you can log the data of each sent command, as p() is basically a hexdump method:
private String p() {
String str = "";
for (byte b1 = 0; b1 < this.n.length; b1++) {
str = str + String.format("0x%02X", new Object[] { Byte.valueOf(this.n[b1]) }) + " ";
}
return str;
}
The next question is: where did this this.n come from. It's not a hard question, and the log messages trivialize it even further. Here's the init method:
private void l() {
System.out.println("init!!!");
this.n = new byte[6];
this.n[0] = (byte)5;
this.n[1] = (byte)118;
this.n[2] = (byte)2;
this.n[3] = (byte)0;
this.n[4] = (byte)2;
this.n[5] = (byte)3;
}
It's a really short protocol, so take a look what each byte does (converted to hex):
05 76 02 00 02 03
- 05 76: Constant magic values, I've never seen occurances in the code, where they would have changed.
- 02: Mode, the app defines 6 modes, of which the first three are valid only.
- 00: Timer in minutes, not sure what effect it has, since the app already has a software timer and leaving it's value as zero works as well.
- 02: Strength from 0 to 12, larger values have no effect.
- 03: Status, one means pause, two means start, three means stop.
public void b(int paramInt) {
System.out.println("setMode !!!");
if (paramInt == 1) {
this.n[2] = (byte)1;
} else if (paramInt == 2) {
this.n[2] = (byte)2;
} else if (paramInt == 3) {
this.n[2] = (byte)3;
}
if (this.n[5] == 1)
o();
}
The rest three are mixed modes, which are updated periodically with a timer in com.developer.cd432rs.eMassage.a.a.java.
public void b() {
if (this.c == null || this.d == null) {
if (this.c == null)
this.c = new Timer();
if (this.d == null)
this.d = new a(this);
this.c.schedule(this.d, 0L, 15000L);
return;
}
Log.d("MixModeController", "startTask : mTimer != null or mTimerTask != null ");
}
The actual update logic is as ugly as you would expect from an obfuscated snippet, I won't paste it here, but you can find it there.
public void c(int paramInt) {
this.n[3] = e(paramInt);
o();
}
This function gets called from com.developer.cd432rs.eMassage.devicecontrol.e.java.
public void a(int paramInt) {
System.out.println("setStrength !!");
this.n[4] = e(paramInt);
if (this.n[5] == 1)
o();
}
public void d(int paramInt) {
System.out.println("start!");
if (paramInt == 1) {
this.n[5] = (byte)1;
} else if (paramInt == 2) {
this.n[5] = (byte)2;
} else if (paramInt == 3) {
this.n[5] = (byte)3;
}
o();
}
There are also two more apps from the same developer (this and this). The first one seems like an earlier version, and maybe a little less obfuscated, as more method names were unmangled. The second is a BLE app, and I'm not sure what purpose it serves, since it's unlikely that the end device is BLE capable.
With that information, you can probably write a similar controller app for this device now.