Xiaomi Can Install Applications Remotely On Device


Sixie

Members
Jul 15, 2016
33
20
Reverse Engineering Xiaomi’s Analytics app

What does Xiaomi.eu has to say about this new findings regarding Xiaomi's ROM (seeing xiaomi.eu roms are based on the original miui)

Sources
See link for more details: https://www.thijsbroenink.com/2016/09/xiaomis-analytics-app-reverse-engineered/

tldr, through Xiaomi's analytics app, one can maliciously install an app on your device remotely.

Reverse Engineering Xiaomi’s Analytics app
September 13, 2016


I own a Xiaomi Mi4 and I discovered it comes with an pre-installed app called AnalyticsCore, package name com.miui.analytics, that’s running in the background. I’m not a big fan of apps gaining information without my permissions, so I started investigating it’s activities. For those who don’t know, Xiaomi is the biggest smartphone manufacturer in China and actively growing worldwide.

For this I downloaded dex2jar and Java Decompiler and started AnalyticsCore.apk in it. The APK is downloadable here if you want to take a look yourself.
I first googled what it’s purpose is, and I found a single thread on the Xiaomi forums, but there is no response or explanation in what is does. See this thread.

Inside Java Decompiler there are mainly three interesting classes in how AnalyticsCore gets his updates, named c.class, e.class and f.class by Java Decompiler. Here is the code of a function inside f.class. (all decompiled code)

-------------------------------------------------------------
private boolean I()
{
boolean bool = false;
if (b.t()) {}
for (;;)
{
return bool;
long l2 = J();
m.d("Analytics-UpdateManager", "last update check time is " + new Date(l2).toString());
long l1 = new Random(System.currentTimeMillis()).nextLong();
if (System.currentTimeMillis() - l2 >= (l1 % (2L * 43200000L) + 2L * 43200000L) % (2L * 43200000L) - 43200000L + 86400000L) {
bool = true;
}
}
}
-------------------------------------------------------------

The above function checks some time within every 24 hours for an new Analytics update. It makes the following request every day within 24 hours, which is very often if you ask me:


-------------------------------------------------------------
public void run()
{
int i = 0;
long l1 = System.currentTimeMillis();
for (;;)
{
int j = i + 1;
if (i < 2) {}
try
{
Object localObject2 = new java/lang/StringBuilder;
((StringBuilder)localObject2).();
Object localObject1 = f.a(this.A);
((StringBuilder)localObject2).append("currentApiVersion0.0.0");
Object localObject3 = new java/lang/StringBuilder;
((StringBuilder)localObject3).();
((StringBuilder)localObject2).append("currentCoreVersion" + k.t(f.b(this.A)));
localObject3 = new java/lang/StringBuilder;
((StringBuilder)localObject3).();
((StringBuilder)localObject2).append("imei" + com.miui.analytics.internal.a.k.j(f.b(this.A)));
localObject3 = new java/lang/StringBuilder;
((StringBuilder)localObject3).();
((StringBuilder)localObject2).append("mac" + com.miui.analytics.internal.a.k.k(f.b(this.A)));
localObject3 = new java/lang/StringBuilder;
((StringBuilder)localObject3).();
((StringBuilder)localObject2).append("model" + com.miui.analytics.internal.a.k.getModel());
localObject3 = new java/lang/StringBuilder;
((StringBuilder)localObject3).();
((StringBuilder)localObject2).append("nonce" + (String)localObject1);
localObject3 = new java/lang/StringBuilder;
((StringBuilder)localObject3).();
((StringBuilder)localObject2).append("package" + f.b(this.A).getPackageName());
localObject3 = new java/lang/StringBuilder;
((StringBuilder)localObject3).();
((StringBuilder)localObject2).append("ts" + l1);
((StringBuilder)localObject2).append("miui_sdkconfig_jafej!@#)(*e@!#");
localObject3 = n.getMd5Digest(((StringBuilder)localObject2).toString()).toLowerCase(Locale.getDefault());
localObject2 = new java/lang/StringBuilder;
((StringBuilder)localObject2).("http://sdkconfig.ad.xiaomi.com/api/checkupdate/lastusefulversion?");
((StringBuilder)localObject2).append("currentApiVersion=0.0.0");
Object localObject4 = new java/lang/StringBuilder;
((StringBuilder)localObject4).();
((StringBuilder)localObject2).append("¤tCoreVersion=" + k.t(f.b(this.A)));
localObject4 = new java/lang/StringBuilder;
((StringBuilder)localObject4).();
((StringBuilder)localObject2).append("&imei=" + com.miui.analytics.internal.a.k.j(f.b(this.A)));
localObject4 = new java/lang/StringBuilder;
((StringBuilder)localObject4).();
((StringBuilder)localObject2).append("&mac=" + com.miui.analytics.internal.a.k.k(f.b(this.A)));
localObject4 = new java/lang/StringBuilder;
((StringBuilder)localObject4).();
((StringBuilder)localObject2).append("&model=" + URLEncoder.encode(com.miui.analytics.internal.a.k.getModel(), "utf-8"));
localObject4 = new java/lang/StringBuilder;
((StringBuilder)localObject4).();
((StringBuilder)localObject2).append("&nonce=" + (String)localObject1);
localObject1 = new java/lang/StringBuilder;
((StringBuilder)localObject1).();
((StringBuilder)localObject2).append("&package=" + f.b(this.A).getPackageName());
localObject1 = new java/lang/StringBuilder;
((StringBuilder)localObject1).();
((StringBuilder)localObject2).append("&ts=" + l1);
localObject1 = new java/lang/StringBuilder;
((StringBuilder)localObject1).();
((StringBuilder)localObject2).append("&sign=" + (String)localObject3);
localObject1 = new java/net/URL;
((URL)localObject1).(((StringBuilder)localObject2).toString());
localObject1 = (HttpURLConnection)((URL)localObject1).openConnection();
((HttpURLConnection)localObject1).setRequestMethod("GET");
((HttpURLConnection)localObject1).setConnectTimeout(5000);
((HttpURLConnection)localObject1).connect();
localObject2 = new java/lang/String;
((String)localObject2).(d.a(((HttpURLConnection)localObject1).getInputStream()));
localObject1 = new java/lang/StringBuilder;
((StringBuilder)localObject1).();
m.d("Analytics-UpdateManager", "result " + (String)localObject2);
localObject1 = new org/json/JSONObject;
((JSONObject)localObject1).((String)localObject2);
localObject3 = ((JSONObject)localObject1).optString("url");
i = ((JSONObject)localObject1).optInt("code", 0);
localObject2 = ((JSONObject)localObject1).optString("v");
f.a(this.A, ((JSONObject)localObject1).optInt("force", 0));
f.a(this.A, ((JSONObject)localObject1).optBoolean("wifi", true));
if ((!TextUtils.isEmpty((CharSequence)localObject3)) && (!TextUtils.isEmpty((CharSequence)localObject2)))
{
localObject4 = new com/miui/analytics/internal/a;
((a)localObject4).((String)localObject2);
if ((b.q()) || (((a)localObject4).a == 0))
{
f.a(this.A, ((JSONObject)localObject1).optString("md5"));
f.b(this.A, (String)localObject3);
f.c(this.A).execute(this.A.aP);
}
}
while (i != -8) {
return;
}
long l2 = f.c(this.A, ((JSONObject)localObject1).optString("failMsg"));
l1 = l2;
i = j;
}
catch (Exception localException)
{
f.a(this.A, 0L);
m.e("Analytics-UpdateManager", "exception ", localException);
i = j;
}
}
-------------------------------------------------------------

As you can see, it makes a request to http://sdkconfig.ad.xiaomi.com/api/checkupdate/lastusefulversion? which is of course an official Xiaomi domain. It sends some parameters with it: including IMEI, MAC address, Model, Nonce, Package name and signature.

After the above code has been executed, it might get an (updated) apk file back. Inside e.class this APK file gets downloaded:

-------------------------------------------------------------
public void run()
{
try
{
if ((!k.m(f.b(this.A))) && (f.d(this.A))) {}
for (;;)
{
return;
Object localObject1 = new java/net/URL;
((URL)localObject1).(f.e(this.A));
localObject1 = (HttpURLConnection)((URL)localObject1).openConnection();
((HttpURLConnection)localObject1).setRequestMethod("GET");
((HttpURLConnection)localObject1).setConnectTimeout(5000);
((HttpURLConnection)localObject1).connect();
if (((HttpURLConnection)localObject1).getResponseCode() == 200)
{
Object localObject2 = d.a(((HttpURLConnection)localObject1).getInputStream());
localObject1 = localObject2;
Object localObject3;
if (!TextUtils.isEmpty(f.f(this.A)))
{
localObject3 = a.a((byte[])localObject2);
localObject1 = localObject2;
if (!f.f(this.A).equalsIgnoreCase((String)localObject3)) {
localObject1 = null;
}
}
if (localObject1 != null)
{
Log.d("Analytics-UpdateManager", "download apk success.");
localObject2 = new java/io/File;
((File)localObject2).(f.g(this.A));
localObject3 = new java/io/FileOutputStream;
((FileOutputStream)localObject3).((File)localObject2);
((FileOutputStream)localObject3).write((byte[])localObject1);
((FileOutputStream)localObject3).close();
f.h(this.A);
}
}
}
...
-------------------------------------------------------------

The download location for the APK is set in f.class, where also the 24h time check was placed:

-------------------------------------------------------------
private String G()
{
try
{
Object localObject = new java/lang/StringBuilder;
((StringBuilder)localObject).();
localObject = this.mContext.getExternalCacheDir().getAbsolutePath() + "/Analytics.apk";
return (String)localObject;
}
catch (Exception localException)
{
for (;;)
{
String str = "";
}
}
}
-------------------------------------------------------------

Now the question is, where does this APK gets installed? I couldn’t find any proof inside the Analytics app itself, so I’m guessing that a higher privileged Xiaomi app runs the installation in the background. The question is then: does it verify the correctness of the APK, and does it make sure that it is in fact an Analytics app? If it does not, that means Xiaomi can install any app on your device it wants, as long as it’s named Analytics.apk.

Update 12:31: Someone told me the package gets installed from l.class, with following code:

try
-------------------------------------------------------------
{
paramContext.getPackageManager().getClass().getMethod("installPackage", new Class[] { Uri.class, Class.forName("android.content.pm.IPackageInstallObserver"), Integer.TYPE, String.class }).invoke(paramContext.getPackageManager(), new Object[] { Uri.parse(paramString), null, paramContext.getPackageManager().getClass().getField("INSTALL_REPLACE_EXISTING").get(null), null });
m.d("AppInstaller", "install apk success.");
return;
}
...
-------------------------------------------------------------

It seems like there indeed is no validation on what APK is getting installed. So it looks like Xiaomi can replace any (signed?) package they want silently on your device within 24 hours. And I’m not sure when this AppInstaller gets called, but I wonder if it’s possible to place your own Analytics.apk inside the correct dir, and wait for it to get installed (edit: getExternalCacheDir() is inside the app’s sandbox, so probably not). But this sounds like a vulnerability to me anyhow, since they have your IMEI and Device Model, they can install any apk for your device specifically.

If you own a Xiaomi device yourself, you might want to block all access to Xiaomi related domains, because by far this isn’t the only request to a Xiaomi site. I use AdAway for this. It does require root access, but that should be no problem if you run the International ROM. I don’t know if the official rom supports root access out of the box.
My AdAway:


Here is a link to a post with other bloatware apps you can safely remove from your device, next to Analytics: https://forum.xda-developers.com/xiaomi-mi-3/general/tip-safe-to-remove-bloatware-list-miui-t2999283
 
Last edited:
I'm also curious about this.

It's alarming that it even sends your IMEI for an auto update check...
 
Can AnalyticsCore.apk just be deleted or renamed using a root explorer in the \system\apps folder? Or removed from the xiaomi.eu builds by default?
 
Can AnalyticsCore.apk just be deleted or renamed using a root explorer in the \system\apps folder? Or removed from the xiaomi.eu builds by default?
atm I froze mine with use of Titanium Backup, i think u can remove it as well with the same app, but right now i froze it, not really sure what it does but yeah..
 
saw this on todays build:

Other
Optimization - Changed action bar's color to improve readability. (09-13)
Removed - AnalyticsCore.apk