Dial Codes, also called USSD or HMI (Human Machine Interface) codes, are typically used by OEM and carrier to implement some menu. While making baseband researches, those hidden menus was very useful. Today, in this small blogpost, I will described the way we could extract hidden menus codes by reversing some samsung and AOSP apks.
First of all, I naively though that the parsing of the entered codes was made inside of the dialer:
So i extracted the APK and started reversing it with Jadx. But I found nothing, so I was quite sad and decided to loose myself in the android logs (It’s not a thing I recommend as it is very very verbose ) and guess what, I found very crusty things in there.
If the OEM of the phone you’re looking at is samsung maybe you can reproduce:
$ adb logcat
03-25 17:17:31.825 4709 4709 E ParseService: [DR] [SEC] uri : android_secret_code://9090
...
03-25 17:17:31.828 4709 4709 I ParseService: [DR] [SEC] Go Keystring : *#9090#
Those two lines give us a lot of information. First of all it gives us the PID of the process creating this log line (4709). It also give us the class name (ParseService). Another very interesting thing I found lays just a few lines before ParseService log :
03-25 17:17:31.139 4709 4709 I ParseService: [DR] [SEC] Get Keystring via bind service
03-25 17:17:31.139 4709 4709 I KeyStringCommon: [DR] isHiddenMenuOn(), mIsHiddenMenu :false
03-25 17:17:31.139 4709 4709 I KeyStringCommon: [DR] isHiddenKeyString()
03-25 17:17:31.139 4709 4709 I ParseService: [DR] Keystring not in the list
They also comes from process 4709. This block of 5 lines is printed in Logcat log each time the user press a number in the dialer app. We can easily deduce that ParseService check if the entered string is a dial code at each key press. It means that somewhere in this APK, we can find the code checking for each entered character.
So let’s dig in android system APKs. If you want to reproduce, get the PID of ParseService and open a shell:
Nice, we found our guilty process. Of course this is not a linux process foundable in /bin or other linux system related directory, this is an APK.
To extract take your favorite APK extractor and search for ‘parse’. You should find an entry for DRParser Mode:
As you can see the package name match the one we found in ps result. Extract it and let’s dig into it !
Let’s find our “Keystring not in the list” str:
Giving us this function:
The reversing process took me quite a time as Samsung used an obfuscator for their APK. By checking ‘c’ Xref only two entries were found, pretty sad.
The first cross ref led me to a badly decompiled fun and the second one contained a string I don’t see in the logs, so my guess is that the call comes from the first Xref. So again, let’s dig.
I’ll just paste a piece of the fun as it’s pretty long just in case you want to see what it looks like:
But the things that let me think this is a good guess are the str I marked by a red arrows. Remember, those str are the one in the log, I let you check again the pasted log up.
Remember the first function and look at this condition:
It seems to check whether the buffer starts with a ‘#’ or ‘*’, which are the character for a dial code format. Nice !
I could also find this functions that seems to append to a buffer each key pressed by user on the dial pad. A pretty good start as we are looking for dial code parsing.
com.sec.android.app.parser: a.f.onClick()
@Override // android.view.View.OnClickListener
public final void onClick(View view) {
e eVar;
boolean z;
int id = view.getId();
StringBuilder sb = this.f10c;
switch (id) {
case R.id.bt_00 /* 2130968578 */:
sb.append("0");
break;
case R.id.bt_01 /* 2130968579 */:
sb.append("1");
break;
case R.id.bt_02 /* 2130968580 */:
sb.append("2");
break;
case R.id.bt_03 /* 2130968581 */:
sb.append("3");
break;
case R.id.bt_04 /* 2130968582 */:
sb.append("4");
break;
case R.id.bt_05 /* 2130968583 */:
sb.append("5");
break;
case R.id.bt_06 /* 2130968584 */:
sb.append("6");
break;
case R.id.bt_07 /* 2130968585 */:
sb.append("7");
break;
case R.id.bt_08 /* 2130968586 */:
sb.append("8");
break;
case R.id.bt_09 /* 2130968587 */:
sb.append("9");
break;
case R.id.bt_backspace /* 2130968588 */:
this.f8a.d();
break;
case R.id.bt_clear /* 2130968589 */:
e eVar2 = this.f8a;
eVar2.f5a.setText("");
eVar2.f = false;
c.b.f38c = "";
eVar2.h = false;
break;
case R.id.bt_shap /* 2130968590 */:
sb.append("#");
break;
case R.id.bt_star /* 2130968591 */:
sb.append("*");
break;
}
if (sb.length() > 0) {
char charAt = sb.charAt(sb.length() - 1);
if (!((charAt >= '0' && charAt <= '9') || charAt == ',') && (z = (eVar = this.f8a).h)) {
if (z) {
eVar.f5a.getText().append((CharSequence) e.a(new StringBuilder(")")));
}
eVar.h = false;
}
}
if (sb.length() > 0) {
e eVar3 = this.f8a;
EditText editText = eVar3.f5a;
int b2 = e.b(editText, true);
int b3 = e.b(editText, false);
if (eVar3.d && b3 == eVar3.f5a.getText().length() && b2 != b3) {
editText.getText().delete(b2, b3);
} else {
editText.setSelection(editText.getText().length());
}
editText.getText().insert(e.b(editText, false), e.a(sb));
e.b(editText, false);
eVar3.c().getClass();
editText.getSelectionEnd();
}
this.f8a.d = false;
int length = sb.length();
if (length != 0) {
sb.delete(0, length);
}
this.f9b.setSelection(this.f8a.f5a.getText().length());
}
As this is more an event binding than a real function (method here to catch each keyPressed) it is not directly mentioned in the code but it is clearly used each time the user presses a number.
Based on the string I found in both APK and logcat ParseService
I deduced that the function JADX was not able to decompile is one of the main function of our dial code parser. It then calls our function to check the format of the dial code and then calls an internal uri typed as :
that we can find once again here:
But a question remain, how it checks if the dial code is valid ? It took me quite a time to trace this process, but I finally succeeded. I found it by looking at the files our apk tries to open with a simple regex:
And as I like to say, BINGO! Now we a file to start looking at.
there is only mentions to this file so let’s take a look at it:
And wow there is a lot of match, it is pretty easy to understand that those three letters stands for a Country Service Code (CSC, another hint that we are on the good path). Let’s take the one corresponding to my region: EUX
Unfortunately it does not contains anything but it gave us a hint, those codes depends on the region, so let’s continue to dig. Instead of searching for a path, i searched for CSC and got those results:
What do we have here…. another path to a file that seems to be called <something>_keystrings.dat and that’s all I need to start looking for a file again.
And guess what:
We have some matches !
A lot of mentions to Hidden Menu and Uri found in logcat and also found in this APK. First of all, my guess was that a list of dial code was present in this APK, sadly it was not the case. By exploring the code, I tried to find the list where our Parser check for the Dial Code. Once again I take the one matching my region and got a beautiful blob:
By looking at xxd:
No strings at all (sad for a keystring file), it really seems to be cyphered and as I found a match with RSA Block Cypher:
I really thinks this is cyphered. At this point I was really about to give up but the power of randomness seemed to be with me. While scrolling through the logs I found that our dear android logs an absolute banger! The private Exponent and Modulus, yeah ! Who would believe it, they locked the castle but gave us the card to reproduce the key 🙂
Let’s apply our old RSA formula and compute the key. We can now uncypher our file and OURAH, look at this beautiful XML full of Dial Code:
And as we extracted them from the system we can be sure that they all works and we can now discover awesome new feature on our dear Samsung.
Just a note, my debug level was at 3 (The highest) and I think that’s why it logged me the Exponent and Modulus. If you want to reproduce and you do not see the magic key of the castle (Yeah, the exponent and modulus) try to increase your kernel debug level with menu *#9900#
.
Cookie | Duration | Description |
---|---|---|
cookielawinfo-checkbox-analytics | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics". |
cookielawinfo-checkbox-functional | 11 months | The cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional". |
cookielawinfo-checkbox-necessary | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary". |
cookielawinfo-checkbox-others | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other. |
cookielawinfo-checkbox-performance | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance". |
viewed_cookie_policy | 11 months | The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data. |