Note: For this challenge, we need install some things into our Android 5.1 device with Genymotion.
For example, an ARM Translator.
https://github.com/m9rco/Genymotion_ARM_Translation
Download APK: https://lautarovculic.com/my_files/EzDroid.apk
Install the apk with adb
adb install -r EzDroid.apk
The app doesn’t launch, even if we start the activity with adb
adb shell am start -n com.labyrenth.manykeys.manykeys/.EZMain
So, let’s decompile it with apktool
apktool d EzDroid.apk
And let’s inspect the source code with jadx
And yes, here’s the problem. In de MainActivity, we can see that in the onCreate method, we have an if condition that calls to another class with the buh
method
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ezmain);
final onoes classOne = new onoes();
if (classOne.checkers(this).booleanValue()) {
Toast.makeText(this, "You chose poor execution tactics...", 0).show();
classOne.buh();
}
[...]
[...]
[...]
And the buh()
method is
public void buh() {
Process.killProcess(Process.myPid());
}
And the checkers that onCreate method are calling in if (classOne.checkers(this).booleanValue())
are:
public Boolean checkers(Context paramContext) {
boolean cheeky = false;
try {
TelephonyManager localTelephonyManager = (TelephonyManager) paramContext.getSystemService("phone");
if (Build.PRODUCT.contains("sdk")) {
cheeky = true;
} else if (Build.MODEL.contains("sdk")) {
cheeky = true;
} else if (localTelephonyManager.getSimOperatorName().equals("Android")) {
cheeky = true;
} else if (localTelephonyManager.getNetworkOperatorName().equals("Android")) {
cheeky = true;
} else {
cheeky = false;
}
} catch (Exception e) {
e.printStackTrace();
}
return cheeky;
}
So, the value is True
if our PRODUCT
or MODEL
contains sdk and also the Android compassion. So, while that is True
, then the buh()
method will be called.Else
, this will don’t call the buh()
function.
So, we need modify the smali file that contains this hardcoded strings. We can replace sdk
and Android
words by any random string value.
cat onoes.smali | grep -E "sdk|Android" -n
Output:
61: const-string v4, "sdk"
86: const-string v4, "sdk"
109: const-string v4, "Android"
132: const-string v4, "Android"
Here’s are the line numbers of the code that we need change. I modify the onoes.smali
with
61: const-string v4, "QQQQQQQQ"
86: const-string v4, "QQQQQQQQQ"
109: const-string v4, "QQQQQQQQQ"
132: const-string v4, "QQQQQQQQQQQQQ"
So now, we need rebuild the app.
We can use apktool
apktool b EzDroid
And then, generate a key
keytool -genkey -v -keystore name.keystore -keyalg RSA -keysize 2048 -validity 10000 -alias alias
Now, sign the apk
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore name.keystore EzDroid/dist/EzDroid.apk alias
And, uninstall the original app from the emulator, and install the new apk
adb install -r EzDroid/dist/EzDroid.apk
And now we can see the activity:
Notice that the code now will execute this lineString sVal = classOne.retIt();
This line call to retIt()
method from onoes
class.
public String retIt() {
String outpoot = new Object() { // from class: com.labyrenth.manykeys.manykeys.onoes.1
int t;
public String toString() {
this.t = -1041749503;
this.t = -1865645093;
this.t = -1972361451;
this.t = -1779558645;
this.t = 339200404;
this.t = 1725700009;
this.t = -1760823842;
this.t = -1727695801;
this.t = -685164605;
this.t = 1706546180;
this.t = 757601807;
this.t = -979820414;
this.t = -660212506;
byte[] buf = {(byte) (this.t >>> 5), (byte) (this.t >>> 9), (byte) (this.t >>> 7), (byte) (this.t >>> 18), (byte) (this.t >>> 2), (byte) (this.t >>> 4), (byte) (this.t >>> 13), (byte) (this.t >>> 22), (byte) (this.t >>> 20), (byte) (this.t >>> 15), (byte) (this.t >>> 21), (byte) (this.t >>> 14), (byte) (this.t >>> 12)};
return new String(buf);
}
}.toString();
return outpoot;
}
Then, this is the value that the System.out.println is showed, we can use logcat with adb
adb logcat -c && adb logcat | grep Part
Output:
I/System.out( 8828): Part1: PAN{ez_droid_
We can see that the flag is building.
So, let’s keep doing the challenge for complete the flag.
Now we need work with this part of the EzMain activity code
String sVal = classOne.retIt();
System.out.println("Part1: " + sVal);
final String[] hints = {"two plus one", "one plus one", "five plus two", "three plus zero", "nine minus two", "two plus two", "three plus three", "eleven minus ten", "negative two plus nine", "one plus one", "five plus two", "three plus one"};
final ArrayList inputs = new ArrayList<>();
final EditText getInput = (EditText) findViewById(R.id.enter_key_one);
getInput.setHint(hints[0]);
Button clickButton = (Button) findViewById(R.id.next_button);
clickButton.setOnClickListener(new View.OnClickListener() { // from class: com.labyrenth.manykeys.manykeys.EZMain.1
int i = 1;
@Override // android.view.View.OnClickListener
public void onClick(View view) {
String newInput = getInput.getText().toString().trim();
int input = Integer.parseInt(newInput);
inputs.add(Integer.valueOf(input));
if (this.i != 12) {
getInput.setHint(hints[this.i]);
getInput.setText("");
this.i++;
return;
}
if (!Build.PRODUCT.contains("sdk")) {
EZMain.this.checks(inputs);
} else {
Toast.makeText(EZMain.this, "You're in an emulator...", 0).show();
SystemClock.sleep(200L);
Process.killProcess(Process.myPid());
}
SystemClock.sleep(1000L);
System.out.println("You should have the key...soon");
SystemClock.sleep(1000L);
classOne.buh();
}
});
he correct order of value that we must insert is 3 2 7 3 7 4 6 1 7 2 7 4
But, when we send the last number, the app crash again.
So, there are some bypass that we must to do.
if (!Build.PRODUCT.contains("sdk")) {
EZMain.this.checks(inputs);
Here, as previously we do, change sdk
for any random characters.
Remember, now the patch must be in the new builded apk, that is in EzDroid/dist/EzDroid.apk
For work better, move the apk to our actual directory.mv EzDroid/dist/EzDroid.apk EzDroid2.apk
and rename with 2
Because we must decompile it with apktool again for access to the smali files
.
cat EZMain\$1.smali | grep "sdk"
141: const-string v3, "sdk"
So in /EzDroid2/smali/com/labyrenth/manykeys/manykeys
we need modify the file EZMain\$1.smali
.
Rebuild the apk and sign as previously we has been do.
apktool b EzDroid2
And
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore name.keystore EzDroid2/dist/EzDroid2.apk alias
Uninstall the app and reinstall the new apk
adb install -r EzDroid2/dist/EzDroid2.apk
Run the new apk installed and then, complete the inputs with this values3 2 7 3 7 4 6 1 7 2 7 4
while you are running the logcat command
adb logcat -c && adb logcat | grep Part
Notice that the app is crashing. But the logs show the second part of the challenge
I/System.out( 8828): Part1: PAN{ez_droid_
I/System.out( 8828): Part 2: 2start_
At this point, the flag is PAN{ez_droid_2star
Let keep doing the CTF.
The app crashes when this line is executedBoolean outAns = classTwo.lastCheck("72657031616365746831732121");
The Code of below is the rest
Boolean outAns = classTwo.lastCheck("72657031616365746831732121");
if (outAns.booleanValue()) {
System.out.println("You did it, put the key together...");
} else {
System.out.println("FAILURE");
}
So, at this point, we need look the lastCheck
method in onoes
class.
And too, the getHexString
that will give us the flag.
public Boolean lastCheck(String strValue) {
boolean result;
if ((Long.parseLong(strValue) * (-37)) + 42 == 17206538691L) {
result = true;
getHexString(strValue);
System.out.println("\nDid you get it? You should know...");
} else {
result = false;
System.out.println("\nfalse");
}
return Boolean.valueOf(result);
}
public void getHexString(String strval) {
String outie = "" + strval.charAt(14) + strval.charAt(3) + strval.charAt(14) + strval.charAt(11) + strval.charAt(5) + strval.charAt(4) + strval.charAt(14) + strval.charAt(13) + strval.charAt(19) + strval.charAt(6) + strval.charAt(14) + strval.charAt(13) + strval.charAt(14) + strval.charAt(1) + strval.charAt(14) + strval.charAt(14) + strval.charAt(14) + strval.charAt(1) + strval.charAt(14) + strval.charAt(11) + strval.charAt(5) + strval.charAt(13);
String outtput = hexToASCII(outie);
System.out.println("Final Part: " + outtput + "}");
}
If you notice the log when the app crashes, it show some like
Process: com.labyrenth.manykeys.manykeys, PID: 11729
java.lang.NumberFormatException: Invalid long: "72657031616365746831732121"
That means that there an error formatting.
Because, the string (72657031616365746831732121) is 20
chars and long values
take 19 digits (20 but 1 less by index 0). Probably, the number is negative for take all 20
chars.
The string 72657031616365746831732121
from hex
is
echo '72657031616365746831732121' | xxd -r -p
Output: rep1aceth1s!!
That seems like we need found a correct value for strValue
of the lastCheck
class.
The condition is here
if ((Long.parseLong(strValue) * (-37)) + 42 == 17206538691L) {
result = true;
getHexString(strValue);
System.out.println("\nDid you get it? You should know...");
That, is if True, then the getHexString take the value and give us the flag.
So now, we need get the real value for this.
The problem involves integer overflow in Java’s Long data type. We’re trying to solve:
37x = 17206538649 (mod 2^64)
Long variables in Java use 64 bits, so when they overflow, they wrap around. To find x:
- Calculate the minimum overflow:
MAX_LONG + (0 - MIN_LONG + 1) + Overflow
In Java, along
can hold values from negative9,223,372,036,854,775,808
to positive9,223,372,036,854,775,807
- Keep adding 2^64 until you get a number divisible by 37.
- Divide the result by 37 to get x.
9223372036854775807 + (0 - -9223372036854775808 + 1) + 17206538649
9223372036854775807 + 9223372036854775809 + 17206538649
2^64 + 17206538649
Then, the overflow is 18446744090916090265
Let’s bruteforce for get the value.
Here’s a python script
from decimal import Decimal, getcontext
# Set precision high enough to handle these calculations
getcontext().prec = 100
max_long = Decimal("9223372036854775807")
min_long = Decimal("-9223372036854775808")
mod_base = Decimal(2) ** 64
target = Decimal("17206538691")
# Calculate overflow
overflow = (target - min_long + 1 - 42 + max_long)
print(f"Overflow: {overflow}")
def get_p3(d):
pos = [14,3,14,11,5,4,14,13,19,6,14,13,14,1,14,14,14,1,14,11,5,13]
part3 = ''.join(d[p] for p in pos)
return ''.join(chr(int(part3[i:i+2], 16)) for i in range(0, len(part3), 2))
for i in range(100):
mul = mod_base * i
val = mul + overflow
remainder = val % -37
quotient = val // -37
q_len = len(str(quotient))
if remainder == 0:
quotient = val // -37
p3 = get_p3(str(quotient).zfill(64))
p3_len = len(str(quotient))
print(f"\t{i}: {p3} ({quotient} : {p3_len})")
print("Calculation complete.")
Output: 11: (-5982727808154625893 : 20)
Now, we need modify again the smali file.
cat EZMain.smali | grep "72657031616365746831732121" -n
142: const-string v7, "72657031616365746831732121"
Change 72657031616365746831732121
by -5982727808154625893
and then, rebuild again the apk. You must know the process.
Installing the last apk, reproduce all the steps with logcat running.
lautaro ~/Desktop/CTF/MOBILE/labyREnth_2017/ezdroid catn sequence.txt
3 2 7 3 7 4 6 1 7 2 7 4
lautaro ~/Desktop/CTF/MOBILE/labyREnth_2017/ezdroid adb install -r EzDroid_Final.apk
Performing Push Install
EzDroid_Final.apk: 1 file pushed, 0 skipped. 622.8 MB/s (1323698 bytes in 0.002s)
pkg: /data/local/tmp/EzDroid_Final.apk
Success
lautaro ~/Desktop/CTF/MOBILE/labyREnth_2017/ezdroid adb logcat -c && adb logcat | grep Part
I/System.out(14286): Part1: PAN{ez_droid_
I/System.out(14286): Part 2: 2start_
I/System.out(14286): Final Part: hard2defeat}
Final flagPAN{ez_droid_2start_hard2defeat}
I hope you found it useful (:
Leave a Reply