Are Xiaomi browsers spyware? Yes, they are (2020)


When you passed over it, there turned into a Forbes article on Mi Browser Pro and Mint Browser that are preinstalled on Xiaomi telephones. The article accuses Xiaomi of exfiltrating a history of all visited web sites. Xiaomi on the assorted hand accuses Forbes of misrepresenting the facts. They bid that the guidelines series is following simplest practices, the guidelines itself being aggregated and anonymized, with none connection to client’s identification.

TL;DR: It is if truth be told that monstrous, and even worse in point of fact.

When you happen to’ve been following my blog for a whereas, you might per chance well web this argumentation acquainted. It’s nearly the same to Avast’s communication after they hold been learned spying on the users and browser distributors pulled their extensions from add-on stores. In the pause I turned into given proof that their info anonymization attempts hold been handiest moderately a success while you recede me this understatement.

Provided that neither the Forbes article nor the protection researchers fervent appear to present any technical particulars, I wished to take a watch myself. I decompiled Mint Browser 3.4.0 and sought for clues. This isn’t the most as a lot as the moment version, lawful in case Xiaomi already modified to code in reaction to the Forbes article. Update (2020-05-08): When you happen to don’t need the technical explanation, the more recent article offers a top level understanding of the voice.

Disclaimer: I feel that here is the first time I analyzed a increased Android software, so please be affected person with me. I would hold misinterpreted one ingredient or every other, although the massive image appears to make sure. Additionally, my conclusions are primarily based solely on code prognosis, I’ve never considered this browser in motion.

The now not unique analytics setup

The Forbes article explains that the guidelines is being transmitted to a Sensors Analytics backend. The Xiaomi article then offers the indispensable clue: sa.api.intl.miui.com is the host name of this backend. They then hunch on explaining how it’s a server that Xiaomi owns rather than a third birthday party. However they are merely attempting to distract us: if silent info from my browser is being sent to this server, why would I care who owns it?

We web this server name talked about in the class miui.globalbrowser.common_business.g.i (yes, some equipment and sophistication names are mangled). It’s extinct in some initialization code:

closing StringBuilder sb = unusual StringBuilder();
sb.append("https://sa.api.intl.miui.com/sa?project=global_browser_mini&r=");
sb.append(A.e);
a = sb.toString();

Trying up A.e, it turns out to be a nation code. So the i.a static member here finally ends up keeping the endpoint URL with the patron’s nation code filled in. And it’s being extinct in the class’ initialization feature:

public void a(closing Context c) {
    SensorsDataAPI.sharedInstance(this.c = c, i.a, this.d);
    SensorsDataAPI.sharedInstance().name(com.xiaomi.mistatistic.sdk.e.a(this.c));
    this.c();
    this.d();
    this.e();
    this.b();
}

The Sensors Analytics API is public, so we can stamp up the SensorsDataAPI class and learn that the first sharedInstance() call creates an occasion and sets its server URL. The next line calls name() environment an “nameless ID” for this occasion which is appealing to be sent alongside with every info point, more on that later.

The call to this.c() is also price noting as this might per chance per chance additionally effect a bunch of additional properties to be sent with every ask:

public void c() {
    closing JSONObject jsonObject = unusual JSONObject();
    jsonObject.set up("uuid", (Object)com.xiaomi.mistatistic.sdk.e.a(this.c));
    int n;
    if (H.f(miui.globalbrowser.now not unique.a.a())) {
        n = 1;
    }
    else {
        n = 0;
    }
    jsonObject.set up("internet_status", n);
    jsonObject.set up("platform", (Object)"AndroidApp");
    jsonObject.set up("miui_version", (Object)Invent$VERSION.INCREMENTAL);
    closing String e = A.e;
    a(e);
    jsonObject.set up("miui_region", (Object)e);
    jsonObject.set up("system_language", (Object)A.b);
    SensorsDataAPI.sharedInstance(this.c).registerSuperProperties(jsonObject);
}

There we now hold the identical “nameless ID” sent as uuid parameter, lawful in case. Moreover to, the long-established version, space, language info is being sent.

For me, it wasn’t entirely trivial to figure out where this class is being initialized from. Seems, from class miui.globalbrowser.common_business.g.b:

public static void a(closing String s, closing ArrangementString, String> plan) {
    a(s, plan, genuine);
}

public static void a(closing String s, closing ArrangementString, String> plan, closing boolean b) {
    if (b) {
        i.a().a(s, plan);
    }
    miui.globalbrowser.common_business.g.d.a().a(s, plan);
}

So the miui.globalbrowser.common_business.g.b.a() call will effect the third parameter to genuine by default. This call accesses a singleton miui.globalbrowser.common_business.g.i occasion (will likely be created if it doesn’t exist) and makes it in point of fact discover an match (s is the match name here and plan are the parameters being sent to boot to the default ones). The additional miui.globalbrowser.common_business.g.d.a() call triggers their MiStatistics analytics framework which I didn’t compare.

And that’s it. Now we hold to search out where in the code miui.globalbrowser.common_business.g.b class is extinct and what info it receives. All that info will likely be sent to Sensors Analytics backend recurrently.

How nameless is that ID?

Trying up com.xiaomi.mistatistic.sdk.e.a() finally turns up ID generation code very cease to the one cited in the Xiaomi blog post:

public static String d(closing Context context) {
    if (!TextUtils.isEmpty((CharSequence)y.g)) {
        return y.g;
    }
    closing long currentTimeMillis = Plot.currentTimeMillis();
    closing String a = L.a(context, "anonymous_id", "");
    closing long a2 = L.a(context, "aigt", 0L);
    closing long a3 = L.a(context, "anonymous_ei", 7776000000L);
    if (!TextUtils.isEmpty((CharSequence)a) && currentTimeMillis - a2

Read More

Recent Content