knights_droid
By DKvenom, 0-Day Aarhus
Author
NomanProdhan
For ages, a cryptic mechanical guardian has slumbered beneath the Knight’s Citadel. Some say it holds powerful secrets once wielded by ancient code-wielding Knights. Many have tried to reactivate the droid and claim its hidden knowledge—yet none have returned victorious. Will you be the one to solve its riddles and awaken this legendary machine?
Files for CTF
Solve
Downloading the file we get an .apk (Android Package Kit) .
┌──(DKvenom㉿)-[~/CTF/KnightCTF/knights_droid]
└─$ ls
AndroidManifest.xml classes3.dex DebugProbesKt.bin knights_droid.apk:Zone.Identifier META-INF resources.arsc
classes2.dex classes.dex knights_droid.apk kotlin res
For apk reversing i usually use the tool mobsf.live (mobsf is an open-source tool for mobile application security testing). Here you upload the apk file and mobsf.live will give a fantastisk overveiw.
Searching around the files the first thing to notice is ofcause the SecretKeyVerifier.java. This files contains a function verifyFlag where we see the incoded flag GYPB{_ykjcnwp5_GJECDP_u0q_c0p_uKqN_Gj1cd7_zN01z_} and that we call a 2 function doirdMagic & computeShiftFromKey.
package com.knightctf.knights_droid;
public static boolean verifyFlag(Context context, String userInput) {
String fullPackageName = context.getPackageName();
if (fullPackageName.length() < 20) {
return false;
}
String firstTen = fullPackageName.substring(0, 10);
int shift = computeShiftFromKey(firstTen);
String encodedUserInput = droidMagic(userInput, shift);
return "GYPB{_ykjcnwp5_GJECDP_u0q_c0p_uKqN_Gj1cd7_zN01z_}".equals(encodedUserInput);
}
Looking furhter down the file we see what the 2 functions do. So computeShiftFromKey takes a key this key we could see in verify flag wich was the first 10 of the packageName. This is just a hardcoded Value.
private static int computeShiftFromKey(String key) {
int sum = 0;
for (char c : key.toCharArray()) {
sum += c;
}
return sum % 26;
}
droidMagic is where we can get the flag, so basicly the function makes sure that we always are working with the alfebet using modulo of 26. The parameter input is then checked char for char where we calculate the new position of the letter. This is ofcause diffrent for Upper/LowerCase letters. So if we just make a equvilent pythonScript we can calculate the falg.
private static String droidMagic(String input, int droidTask) {
int droidTask2 = ((droidTask % 26) + 26) % 26;
StringBuilder sb = new StringBuilder();
for (char c : input.toCharArray()) {
if (Character.isUpperCase(c)) {
int originalPos = c - 'A';
int newPos = (originalPos + droidTask2) % 26;
sb.append((char) (newPos + 65));
} else if (Character.isLowerCase(c)) {
int originalPos2 = c - 'a';
int newPos2 = (originalPos2 + droidTask2) % 26;
sb.append((char) (newPos2 + 97));
} else {
sb.append(c);
}
}
return sb.toString();
}
I have here implemented the logic that we se from the java code above, and lastly i print the flag:
target = "GYPB{_ykjcnwp5_GJECDP_u0q_c0p_uKqN_Gj1cd7_zN01z_}"
def computeShiftFromKey(key):
sum = 0
for c in key:
sum += ord(c)
return sum % 26
def computeDroidTaskFromKey(target):
Flag = []
key = "com.knight"
doridtask2 = computeShiftFromKey(key)
for letter in target:
if (letter.isupper()):
originalPos = ord(letter) - ord('A')
newpos = (originalPos - doridtask2) % 26
Flag.append(chr(newpos + 65))
elif (letter.islower()):
originalPos2 = ord(letter) - ord('a')
newpos2 = (originalPos2 - doridtask2) % 26
Flag.append(chr(newpos2 + 97))
else:
Flag.append(letter)
return "".join(Flag)
print(computeDroidTaskFromKey(target))
KCTF{_congrat5_KNIGHT_y0u_g0t_yOuR_Kn1gh7_dR01d_}