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.