How deep the rabbit hole is

As Linus Torvalds rightly said in LKML,

Theory and practice sometimes clash. And when that happens, theory loses.
Every single time.

When it comes to mobile hacking (in a broader definition), I’m definitely not Linus Torvalds, Elon Musk or Mark Zuckerberg. Not even Bill Gates. I’m more of a mobile Kevin Mitnick. I do what I do for absolutely no profit. And before continuing the investigation on what has been started in the previous post of this series, let me point out one interesting thing.

The thing is a simple observation: there are at least two forums where there are enough skilled people to collectively complete my single-handed Nokia 1 effort in virtually no time. The problem is… All those people are on Faildows. Therefore, they lack any motivation because, you know, why bother developing a cross-platform or - even better - an autonomous solution which - even better - would not tamper system partition integrity? They don’t need it as they already have all the tooling they need to edit NVRAM from their virus-prone boxes without tampering integrity. And however smart they are, they are probably still not smart enough to understand why a solution locked down to the worst platform in existence equals to no solution at all. That’s at least true for any people who care about their privacy and who have a slightest clue about what’s really going on in this world.

Yes, I’m sorry I hadn’t told you about my primary project goals conceived at its start, except for a brief description. So, they are the following:

  1. Absolute required minimum - IMEI editing/randomization without protected partition modification (via a temporarily loaded fastboot image or AT command interface);
  2. Bare minimum - an app for autonomous IMEI editing/randomization;
  3. Medium - an app for autonomous IMEI editing/randomization and incoming stealth-ping SMS detection;
  4. Maximum - an app for autonomous IMEI editing/randomization and incoming stealth-ping SMS detection and altering the reaction to them.

Of course, the app must be non-root and not edit any of protected partitions either. And of course, I reserve the right to stop at any stage after achieving the absolute required minimum when I decide it’s impossible or the further effort is not worth the desired result.

With all that in mind, let’s continue.

The code at the end of the previous chapter is the perfect example of Torvalds’ quote. Why? Because it would work only in the absolute theoretical conditions. In reality, it doesn’t. I’m not going to throw out anything in these posts, and will document not only successes, but also failures. This one was still a failure even after stripping it down to the barebones and figuring out all the oddities:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import android.content.Context;
import android.telephony.TelephonyManager;

import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;

public class HackTheOne {

private void runAT(String atCmd) {
Phone phoneInstance = PhoneFactory.getDefaultPhone();
String[] cmdArr = {atCmd, ""};
phoneInstance.invokeOemRilRequestStrings(cmdArr, null);
}

public void writeIMEI(Context ctx, int simId, String newImei) {
TelephonyManager telephonyManager = (TelephonyManager) ctx.getSystemService(Context.TELEPHONY_SERVICE);
if (simId < 0 || simId > 3) simId = 0;
int[] egmrs = {7, 10, 11, 12};
String egmrId = Integer.toString(egmrs[simId]);
String atCmd = "AT+EGMR=1," + egmrId + ",\"" + newImei + "\"";
runAT(atCmd);
}
}

And I also found out that we need to alter the AndroidManifest.xml in the following way:

  • Add coreApp="true" and android:sharedUserId="android.uid.phone" attributes to the <manifest> root tag (I think you can also use android.uid.system value, and that coreApp attribute actually does nothing in modern Android builds);
  • Add android:process="com.android.phone" attribute to the <application> tag;
  • Append the following feature definition to the <manifest> body: <uses-feature android:name="android.hardware.telephony" android:required="true"></uses-feature>;
  • Append the following permission definition to the <manifest> body: <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" /> (I also added a bunch of related ones, like ACCESS_NETWORK_STATE and READ_PHONE_STATE).

So, everything seems legit… Why was it a failure? Because fucking com.android.internal.* classes are not even available in the SDK!

So, there are two ways out of this situation. The first one is replacing the SDK classes with the real device framework classes. It’s possible but cumbersome and error-prone.
The second one is using Java reflection. This is what I decided to give a try to (as much as I hate Java, I should really give it credit for this).

So, basically we need to dynamically load the following things:

  • com.android.internal.telephony.PhoneFactory class;
  • getDefaultPhone method of com.android.internal.telephony.PhoneFactory class;
  • com.android.internal.telephony.Phone class;
  • invokeOemRilRequestStrings instance method of com.android.internal.telephony.Phone class.

The result is pretty cumbersome too (I had to wrap everything into try-catch blocks) but it’s at least buildable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import android.content.Context;
import android.telephony.TelephonyManager;

import java.lang.reflect.Method;

public class HackTheOne {

private Class<?> cPhoneFactory;
private Class<?> cPhone;
private Method mGetDefaultPhone;
private Method mInvokeOemRilRequestStrings;
private Object phoneInstance;

private void runAT(String atCmd) {
try {
cPhoneFactory = Class.forName("com.android.internal.telephony.PhoneFactory");
} catch(Exception e) {
cPhoneFactory = null;
}
try {
cPhone = Class.forName("com.android.internal.telephony.Phone");
} catch(Exception e) {
cPhone = null;
}
try {
mGetDefaultPhone = cPhoneFactory.getMethod("getDefaultPhone",(Class[])null);
} catch(Exception e) {
mGetDefaultPhone = null;
}
try {
phoneInstance = mGetDefaultPhone.invoke(null, (Object[]) null);
} catch(Exception e) {
phoneInstance = null;
}
try {
mInvokeOemRilRequestStrings = cPhone.getMethod("invokeOemRilRequestStrings",(Class[])null);
} catch(Exception e) {
mInvokeOemRilRequestStrings = null;
}
String[] cmdArr = {atCmd, ""};
try {
mInvokeOemRilRequestStrings.invoke(phoneInstance, cmdArr, null);
} catch(Exception e) {}
}

public void writeIMEI(Context ctx, int simId, String newImei) {
TelephonyManager telephonyManager = (TelephonyManager) ctx.getSystemService(Context.TELEPHONY_SERVICE);
if (simId < 0 || simId > 3) simId = 0;
int[] egmrs = {7, 10, 11, 12};
String egmrId = Integer.toString(egmrs[simId]);
String atCmd = "AT+EGMR=1," + egmrId + ",\"" + newImei + "\"";
runAT(atCmd);
}
}

Time to wrap it around some simple UI and really test this out…

And guess what? It doesn’t work either. Why? Because attaching to the android.uid.phone or android.uid.system user IDs requires the app to be signed with the same production keys as the system itself. Well, this is the exact reason why and how the CDSInfo app works in Chinese OEM devices alongside other engineering menus. So, if we’d be writing a custom ROM for Nokia 1, the above snippet could be used. But now, we have to find another approach.

_