This is an Android APK, so first up, we need to decode it. Since an APK is just a zip, we could just unzip it, but that leaves quite a few encoded files inside; so using Apktool is a better idea.
The app itself asks for 4 different passwords in 4 different stages, that keep getting harder.
Stage #1: Michael
The code for Michael is very easy to decypher; the only small hurdle is the hashCode() check. I whipped up a quick piece of Java to bruteforce that (turns out it’s “__”).
private boolean checkPassword(String pw) {
if (pw.isEmpty() || pw.length() != 12) {
return false;
}
boolean result = true;
if (!pw.startsWith("M")) {
result = false;
}
if (pw.indexOf(89) != 1) {
result = false;
}
if (!pw.substring(2, 5).equals("PRS")) {
result = false;
}
if (!(pw.codePointAt(5) == 72 && pw.codePointAt(6) == 69)) {
result = false;
}
if (!(pw.charAt(7) == pw.charAt(8) && pw.substring(7, 9).hashCode() == 3040)) {
result = false;
}
if (pw.indexOf("FT") != 9) {
result = false;
}
if (pw.lastIndexOf(87) != pw.length() - 1) {
return false;
}
return result;
}
Simply reading through what the function does and writing the pieces out shows the password for this stage is MYPRSHE__FTW
.
Stage #2: Brian
Brian gets a bit harder, it takes bits and pieces that need to be looked up in a few Android resource XMLs and the Android API documentation. Still, no biggy, see my annotations in the source below:
private String asdjfnhaxshcvhuw(TextView d, ImageView p) {
int a = d.getCurrentTextColor() & SupportMenu.USER_MASK; // ffc0fefe & 0xffff = 0xfefe
String z = d.getText().toString().split(" ")[4]; //Shrimp Poppers or Extreme Fajitas
try {
return dfysadf(
"hashtag", //p.getTag().toString(),
0xfefe, //a,
z, // array, 4 pieces, split by space - 0Shrimp 1Poppers 2or 3Extreme 4Fajitas
// 128 = GET_META_DATA
"cov"//getApplicationContext().getPackageManager().getApplicationInfo(getApplicationContext().getPackageName(), 128).metaData.getString("vdf"));
} catch (NameNotFoundException e) {
e.printStackTrace();
return null;
}
}
private String dfysadf(String t, int p, String c, String y) {
// hashtag_covfefe_Fajitas!
return String.format("%s_%s%x_%s!", new Object[]{t, y, Integer.valueOf(p), c});
}
So indeed, the second password is hashtag_covfefe_Fajitas!
:)
Stage #3: Milton
Milton comes with string encryption through the Stapler.vutfs
function. It decrypts a string based on an RC4-key that is derived from a word that is passed in the same function call. Also, it first wants you to pick a rating from a rating bar which needs to be set to 4, then it asks for a password.
It first builds the password string from a few decrypted strings which are then fed into Stapler.poserw
, which turns out does a SHA1-hash which is then converted to a string.
The string generated from these encrypted strings in Milton.Uvasdf
:
Milton.this.hild = Milton.this.hild + Stapler.vutfs("JP+98sTB4Zt6q8g=", 56, "State");
Milton.this.hild = Milton.this.hild + Stapler.vutfs("rh6HkuflHmw5Rw==", 96, "Chile");
Milton.this.hild = Milton.this.hild + Stapler.vutfs("+BNtTP/6", 118, "eagle");
Milton.this.hild = Milton.this.hild + Stapler.vutfs("oLLoI7X/jIp2+w==", 33, "wind");
Milton.this.hild = Milton.this.hild + Stapler.vutfs("w/MCnPD68xfjSCE=", 148, "river");
Decrypt to A rich man is nothing but a poor man with money.
, which is 10aea594831e0b42b956c578ef9a6d44ee39938d
when SHA1’d. That’s our password.
By the way, here we already run into an interesting problem: the word wind
is too short to derive an RC4-key from, since an RC4-key needs to be at least 40-bit. We’ll see how to solve that in the next stage (in this one the missing words were easy to guess).
Stage #4: Printer
Printer is definately the most annoying part because the code is pretty much obfuscated with lots of encrypted strings. As said in stage #3, some of the RC4-keys are too short to be used and the native Java RC4-engine does not allow for them! To solve this, I used an Online RC4 tool that does allow for shorter keys.
This is the code for Printer with my annotations:
private boolean cgHbC(String bMYkym) {
try {
if (ksdc(this)) {
return false;
}
Object gnue = wJPBw(Stapler.iemm("Gv@H")); // tspe
Class nEPk = Class.forName(Stapler.iemm(",e}e8yGS!8Dev)-e@")); // java.util.HashMap
// size
short sxgQ = ((Integer) nEPk.getMethod(Stapler.iemm("vSBH"), null).invoke(gnue, null)).intValue();
byte[] tVvV = new byte[sxgQ];
//get
Method uyefctK = nEPk.getMethod(Stapler.iemm("LHG"), new Class[]{Object.class});
for (short cj3 = (short) 0; cj3 < sxgQ; cj3 = (short) (cj3 + 1)) {
tVvV[cj3] = ((Byte) Byte.class.cast(uyefctK.invoke(gnue, new Object[]{Short.valueOf(cj3)}))).byteValue();
}
// java.util.Arrays
// equals
// equals: Stapler.neapucx(userINput), Stapler.poserw(tVvV)
return ((Boolean) Class.forName(Stapler.iemm(",e}e8yGS!81PPe(v")).getMethod(Stapler.iemm("H?ye!v"), new Class[]{byte[].class, byte[].class}).invoke(null, new Object[]{Stapler.neapucx(bMYkym), Stapler.poserw(tVvV)})).booleanValue();
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
private Object wJPBw(String avbf) throws Exception {
byte[] mRSwz = new byte[8];
// DataInputStream
Class sxGKOHzN = Class.forName(Stapler.iemm(",e}e8S98*eGeu.@yG5GPHed"));
Constructor iFq = sxGKOHzN.getConstructor(new Class[]{Class.forName(Stapler.iemm(",e}e8S98u.@yG5GPHed"))});
//android.content.res.AssetManager
//open
Method gTDC = Class.forName(Stapler.iemm("e.RP9SR8x9.GH.G8PHv81vvHG-e.eLHP")).getMethod(Stapler.iemm("9@H."), new Class[]{String.class});
Object[] objArr = new Object[1];
objArr[0] = gTDC.invoke(getAssets(), new Object[]{avbf});
Object wy = iFq.newInstance(objArr);
//read
Method sYot = sxGKOHzN.getMethod(Stapler.iemm("PHeR"), new Class[]{byte[].class, Integer.TYPE, Integer.TYPE});
//readInt
Method nxKJ = sxGKOHzN.getMethod(Stapler.iemm("PHeRu.G"), null);
sYot.invoke(wy, new Object[]{mRSwz, Integer.valueOf(0), Integer.valueOf(8)});
// Hashmap
Class eEO = Class.forName(Stapler.iemm(",e}e8yGS!8Dev)-e@"));
Constructor qLpL = eEO.getConstructor(null);
// put
Method ijKS = eEO.getMethod(Stapler.iemm("@yG"), new Class[]{Object.class, Object.class});
Object cb = qLpL.newInstance(new Object[0]);
// readByte
Method agzsx = sxGKOHzN.getMethod(Stapler.iemm("PHeR\"(GH"), null);
int eYs = ((Integer) Integer.class.cast(nxKJ.invoke(wy, new Object[0]))).intValue() / 3;
// readShort
Method jixa = sxGKOHzN.getMethod(Stapler.iemm("PHeR5)9PG"), null);
for (int cztAKh = 0; cztAKh < eYs; cztAKh++) {
short kFMK = ((Short) Short.class.cast(jixa.invoke(wy, new Object[0]))).shortValue();
byte cHJL = ((Byte) Byte.class.cast(agzsx.invoke(wy, new Object[0]))).byteValue();
ijKS.invoke(cb, new Object[]{Short.valueOf(kFMK), Byte.valueOf(cHJL)});
}
return cb;
}
Turns out, the code opens a resource called tspe
which is also in our decompiled APK. It skips the first 8 bytes in the file, reads 4 bytes for the length and then generates a hashmap by taking 2 bytes for the key and 1 byte for the value. Then it reads from the hashmap starting at 0, SHA1s this and that’s the password. I whipped up a bit of quick and dirty C# code to replicate this:
static void Main(string[] args)
{
BinaryReader br = new BinaryReader(new FileStream("tspe", FileMode.Open));
br.ReadBytes(8);
Dictionary<short, byte> d = new Dictionary<short, byte>();
int z = 0x144;
br.ReadInt32(); //length
for (int j = 0; j < z; j += 3)
{
short idx = br.ReadInt16();
idx = (short)((short)(idx & 0xFF) << 8 | (short)(idx >> 8));
byte b = br.ReadByte();
if (d.ContainsKey(idx))
{
d[idx] = b;
}
else
d.Add(idx, b);
Console.WriteLine(idx.ToString() + ": " + b.ToString());
}
for (short x = 0; x < (short)d.Count; x++)
{
byte r = 0;
if (d.ContainsKey(x))
r = d[(short)x];
Console.Write(String.Format("{0}",(char)r));
}
Console.ReadKey();
}
This produces Give a man a fire and he'll be warm for a day. Set a man on fire and he'll be warm for the rest of his life.
. SHA1 of this, and our final password: 5f1be3c9b081c40ddfc4a0238156008ee71e24a4
.