Inside Out Writeup

By
Anandhu K A
Published on
20 Sep 2020
16 min read
DOMECTF2020

Inside Out was one of the challenges I set up for DOME CTF 2020. The challenge description goes like this: “You’ve reached the location where an important piece of data is located. Much to your dismay you need to unlock a particular door to get inside. Once you solve that and get in, you’re met with another puzzle that you have to crack.”

You’re given an apk and you have to decompile it using a tool like APK Studio available on Windows.

Once you decompile the apk, go to androidmanifest.xml

        <?xml version="1.0" encoding="utf-8" standalone="no"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" android:compileSdkVersion="29" android:compileSdkVersionCodename="10" package="com.dom.ctflogin" platformBuildVersionCode="29" platformBuildVersionName="10">
        <application android:allowBackup="true" android:appComponentFactory="androidx.core.app.CoreComponentFactory" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme">
            <activity android:name="com.dom.ctflogin.MainActivity2"/>
            <activity android:name="com.dom.ctflogin.MainActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN"/>
                    <category android:name="android.intent.category.LAUNCHER"/>
                </intent-filter>
            </activity>
        </application>
        </manifest>
    

From the file we can understand that the starting activity is MainActivity.java

Go to MainActivity.java

        public void onCreate(Bundle bundle) {
            super.onCreate(bundle);
            requestWindowFeature(1);
            getWindow().setFlags(1024, 1024);
            setContentView(R.layout.activity_main);
            try {
                this.mMessageDigest = MessageDigest.getInstance("SHA-1");
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
            View findViewById = findViewById(R.id.logo);
            Intrinsics.checkNotNullExpressionValue(findViewById, "findViewById(R.id.logo)");
        }
    

From onCreate, we can understand that mMessageDigest is used as SHA-1algorithm.

        public final void onLogin(View view) {
            EditText editText = (EditText) findViewById(R.id.username);
            EditText editText2 = (EditText) findViewById(R.id.password);
            Intrinsics.checkNotNullExpressionValue(editText, "usernameEditText");
            this.name = editText.getText().toString();
            Intrinsics.checkNotNullExpressionValue(editText2, "passwordEditText");
            String obj = editText2.getText().toString();
            this.pass = obj;
            String str = this.name;
            if (str != null && obj != null) {
                Intrinsics.checkNotNull(str);
                if (!(str.length() == 0)) {
                    String str2 = this.pass;
                    Intrinsics.checkNotNull(str2);
                    if (!(str2.length() == 0)) {
                        if (!Intrinsics.areEqual((Object) this.name, (Object) getResources().getString(R.string.username))) {
                            Toast.makeText(this, "User not recognized.", 0).show();
                            editText.setText("");
                            editText2.setText("");
                        } else if (!checkPassword()) {
                            Toast.makeText(this, "Incorrect password.", 0).show();
                            editText.setText("");
                            editText2.setText("");
                        } else {
                            intent();
                        }
                    }
                }
            }
        }
    

There is only one function that has View as a parameter. It shows that it must be a login button action.

From the code we can see that the username is stored in “R.string.username”.

From this we can get the idea that the username and password are stored in strings.xml.

Search using Grep username

Then go to string and we get username as “user@domctfin”

Now for the password:

        private final boolean checkPassword() {
            byte[] bArr = new byte[0];
            try {
                String str = this.pass;
                Intrinsics.checkNotNull(str);
                Charset forName = Charset.forName(Key.STRING_CHARSET_NAME);
                Intrinsics.checkNotNullExpressionValue(forName, "Charset.forName(charsetName)");
                if (str != null) {
                    byte[] bytes = str.getBytes(forName);
                    Intrinsics.checkNotNullExpressionValue(bytes, "(this as java.lang.String).getBytes(charset)");
                    bArr = bytes;
                    MessageDigest messageDigest = this.mMessageDigest;
                    Intrinsics.checkNotNull(messageDigest);
                    messageDigest.update(bArr, 0, bArr.length);
                    MessageDigest messageDigest2 = this.mMessageDigest;
                    Intrinsics.checkNotNull(messageDigest2);
                    String convertToHex = HConverter.convertToHex(messageDigest2.digest());
                    Log.d("hash:", convertToHex);
                    byte[] bArr2 = new byte[0];
                    try {
                        MessageDigest messageDigest3 = this.mMessageDigest;
                        Intrinsics.checkNotNull(messageDigest3);
                        String str2 = this.pass;
                        Intrinsics.checkNotNull(str2);
                        Charset forName2 = Charset.forName(Key.STRING_CHARSET_NAME);
                        Intrinsics.checkNotNullExpressionValue(forName2, "Charset.forName(charsetName)");
                        if (str2 != null) {
                            byte[] bytes2 = str2.getBytes(forName2);
                            Intrinsics.checkNotNullExpressionValue(bytes2, "(this as java.lang.String).getBytes(charset)");
                            bArr2 = messageDigest3.digest(bytes2);
                            for (String bigInteger = new BigInteger(1, bArr2).toString(16); bigInteger.length() < 32; bigInteger = '0' + bigInteger) {
                            }
                            Encoder encoder = Encoder.INSTANCE;
                            Intrinsics.checkNotNullExpressionValue(convertToHex, "hash1");
                            return Intrinsics.areEqual((Object) encoder.encrypt(convertToHex), (Object) getResources().getString(R.string.phash));
                        }
                        throw new NullPointerException("null cannot be cast to non-null type java.lang.String");
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                } else {
                    throw new NullPointerException("null cannot be cast to non-null type java.lang.String");
                }
            } catch (UnsupportedEncodingException e2) {
                e2.printStackTrace();
            }
        }
    

From the code we get that the first input password is converted to SHA-1, and then it is encoded using Encoder.java.

Then that encoded value is compared to the value stored in string.xml “R.string.phash” .

So we have to decode it in reverse order. In order to do that we have to find the algorithm used in Encoder.java.

        public final String encrypt(String str) {
            Intrinsics.checkNotNullParameter(str, "strToEncrypt");
            try {
                setKey("domctf2020");
                Cipher instance = Cipher.getInstance("AES/ECB/PKCS5Padding");
                instance.init(1, secretKey);
                Base64.Encoder encoder = Base64.getEncoder();
                Charset forName = Charset.forName(Key.STRING_CHARSET_NAME);
                Intrinsics.checkNotNullExpressionValue(forName, "Charset.forName(charsetName)");
                byte[] bytes = str.getBytes(forName);
                Intrinsics.checkNotNullExpressionValue(bytes, "(this as java.lang.String).getBytes(charset)");
                return encoder.encodeToString(instance.doFinal(bytes));
            } catch (Exception e) {
                System.out.println("Error while encrypting: " + e);
                return null;
            }
        }
    

Here we get the encryption key input “domctf2020” is used and the algorithm AES/ECB/PKCS5PADDING is used.

For secret : from the code The key is first converted to bytes The sha1 algorithm used to digest the key. Then Byte is again changed 16 bit, Then new SecretKeySpec(bArr2, “AES”) Is finally used

Create java class for decrypting of phash. From strings.xml we get phash.

Similarly we get a hash of password from strings.xml.

moG4UJAMANaAGIhv6qKgeTFDHxxg0EpLSjnOXHmu55IM

Write a java code to decompile the hash.

        public static void setKey(String myKey)
            {
                MessageDigest sha = null;
                try {
                    key = myKey.getBytes("UTF-8");
                    sha = MessageDigest.getInstance("SHA-1");
                    key = sha.digest(key);
                    key = Arrays.copyOf(key, 16);
                    secretKey = new SecretKeySpec(key, "AES");
                }
                catch (NoSuchAlgorithmException e) {
                    e.printStackTrace();
                }
                catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
            }

        public static String decrypt(String strToDecrypt, String secret)
            {
                try
                {
                    setKey(secret);
                    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
                    cipher.init(Cipher.DECRYPT_MODE, secretKey);
                    return new String(cipher.doFinal(Base64.getDecoder().decode(strToDecrypt)));
                }
                catch (Exception e)
                {
                    System.out.println("Error while decrypting: " + e.toString());
                }
                return null;
            }

        System.out.println(decrypt("dhFejN+8o2xgrHd2s8X+moG4UJAMANaAGIhv6qKgeTFDHxxg0EpLSjnOXHmu55IM","domctf2020"));
    

Decrypt hash using secret. “domctf2020”.

2b749c8c9b2f8ebe3a81e5eb9013793b807ee0c3

Search for SHA-1 on Google. We get the password “~saint~”

Now we got the username and password. Then from the code we can see that the combination of username and password is xored to:

u s e r @d o m c t f i n ~ s a i n t ~ 0 1 2 3 4 5 6 7 8 910 11 12 0 1 2 3 4 5 6

        char[] cArr = {(char) 40, (char) 87, (char) 68, (char) 41, c, (char) 80, (char) 58, (char) 35, (char) 63, c};
    

flag[0] ^= name.charAt(1); flag[1] ^= pass.charAt(0); flag[2] ^= pass.charAt(4); flag[3] ^= name.charAt(4); flag[4] ^= name.charAt(7); flag[5] ^= name.charAt(0); flag[6] ^= pass.charAt(2); flag[7] ^= pass.charAt(3); flag[8] ^= name.charAt(6); flag[9] ^= name.charAt(8);

Flag 0 = 40 xor charAt(1); At last we get a key for intent for next class.

We get flag [)*i9%[JP7 Encoded string BFoyVSK/7xBCjElYkNASqCCY6sqbkdbzKWbIzO/cjpmbD7gi7JS5ChmX7aUC67cE

From the code we get phahs in strings.xml

Decrpyt phahs 3dc6778b155f0d04fd6ef556cb7b6f29 md5

We get 57240316

Secret = [)*i9%[JP757240316

        PatternLockViewListener {
        final /* synthetic */ String $key;
        final /* synthetic */ PatternLockView $patternLockView;
        final /* synthetic */ MainActivity2 this$0;
        
        public void onCleared() {
        }
        
        public void onProgress(List<? extends PatternLockView.Dot> list) {
            Intrinsics.checkNotNullParameter(list, "progressPattern");
        }
        
        public void onStarted() {
        }
        
        MainActivity2$onCreate$1(MainActivity2 mainActivity2, PatternLockView patternLockView, String str) {
            this.this$0 = mainActivity2;
            this.$patternLockView = patternLockView;
            this.$key = str;
        }
        
        public void onComplete(List<? extends PatternLockView.Dot> list) {
            Intrinsics.checkNotNullParameter(list, "pattern");
            MainActivity2.Companion companion = MainActivity2.Companion;
            String patternToString = PatternLockUtils.patternToString(this.$patternLockView, list);
            Intrinsics.checkNotNullExpressionValue(patternToString, "PatternLockUtils.pattern…patternLockView, pattern)");
            if (StringsKt.equals(companion.getHash(patternToString), this.this$0.getResources().getString(R.string.phahs), true)) {
                String str = this.$key + PatternLockUtils.patternToString(this.$patternLockView, list);
                if (Build.VERSION.SDK_INT >= 26) {
                    Toast.makeText(this.this$0, "domectf{" + Encoder.INSTANCE.decrypt("BFoyVSK/7xBCjElYkNASqCCY6sqbkdbzKWbIzO/cjpmbD7gi7JS5ChmX7aUC67cE", str) + "}", 0).show();
                }
                this.$patternLockView.clearPattern();
                return;
            }
            this.$patternLockView.clearPattern();
            Toast.makeText(this.this$0, "Invalid pattern...", 0).show();
            Toast.makeText(this.this$0, "Closing application...", 0).show();
            new Handler().postDelayed(new MainActivity2$onCreate$1$onComplete$1(this), 4000);
        }
        }
    

We already have the function for decrypting: “AES/ECB/PKCS5PADDING”

After decrypting we get flag “sOcekBZRw3c263wBPV6kzmUxgLcUAJbQ”.

domectf{sOcekBZRw3c263wBPV6kzmUxgLcUAJbQ}

Automated human-like penetration testing for your web apps & APIs
Teams using Beagle Security are set up in minutes, embrace release-based CI/CD security testing and save up to 65% with timely remediation of vulnerabilities. Sign up for a free account to see what it can do for you.

Written by
Anandhu K A
Anandhu K A
Lead Engineer
Experience the Beagle Security platform
Unlock one full penetration test and all Advanced plan features free for 10 days
Find surface-level website security issues in under a minute
Free website security assessment
Experience the power of automated penetration testing & contextual reporting.