aresinheavenaresinheaven
โ† Back
cryptoctfreverse-engineeringandroidweb3

Reverse engineering Crypto_wallet FORTID CTF 2026

aresinheavenยทยท25 min read

Solve for Crypto_wallet by aresinheaven

Let's do a quick recap of what I understood for this challenge.

So we were given an APk file Crypto_wallet.apk, Quick shoutout to https://github.com/MG1937 for making one of the best decompilers https://github.com/MG1937/ASC Out there; Okay so ASC helped me to understand the wallet recovery flow which basically splits a 12 word phrase into four what3words references. And then the native code supplies four airport area coordinates which are published addresses for the nearby places which we can narrow the candidates to four BIP39 compatible triples,and then the resulting phrase derives the expected Ethereum address and authenticates the encrypted flag this was my completed understanding and flow of the challenge solve.

Lets Decompile the apk with ASC and trace out the recovery function

I used ASC through droidasc 0.1.1.post3.

(ctf-env) manavhowal@manavs-MacBook-Air downloads % droidasc getmanifest "$APK" -o "$OUT/AndroidManifest.xml"
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="2" android:versionName="1.1" android:compileSdkVersion="35" android:compileSdkVersionCodename="15" package="com.fortid.cryptowallet" platformBuildVersionCode="35" platformBuildVersionName="15">
  <uses-sdk android:minSdkVersion="26" android:targetSdkVersion="35"/>
  <permission android:name="com.fortid.cryptowallet.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION" android:protectionLevel="0x00000002"/>
  <uses-permission android:name="com.fortid.cryptowallet.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"/>
  <application android:theme="@7F0F0221" android:label="Cryptocurrency Wallet" android:icon="@7F070088" android:allowBackup="false" android:supportsRtl="true" android:extractNativeLibs="false" android:fullBackupContent="false" android:appComponentFactory="androidx.core.app.CoreComponentFactory">
    <activity android:name="com.fortid.cryptowallet.MainActivity" android:exported="true" android:screenOrientation="1" android:windowSoftInputMode="0x00000010">
      <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
    </activity>
    <provider android:name="androidx.startup.InitializationProvider" android:exported="false" android:authorities="com.fortid.cryptowallet.androidx-startup">
      <meta-data android:name="androidx.emoji2.text.EmojiCompatInitializer" android:value="androidx.startup"/>
      <meta-data android:name="androidx.lifecycle.ProcessLifecycleInitializer" android:value="androidx.startup"/>
      <meta-data android:name="androidx.profileinstaller.ProfileInstallerInitializer" android:value="androidx.startup"/>
    </provider>
    <receiver android:name="androidx.profileinstaller.ProfileInstallReceiver" android:permission="android.permission.DUMP" android:enabled="true" android:exported="true" android:directBootAware="false">
      <intent-filter>
        <action android:name="androidx.profileinstaller.action.INSTALL_PROFILE"/>
      </intent-filter>
      <intent-filter>
        <action android:name="androidx.profileinstaller.action.SKIP_FILE"/>
      </intent-filter>
      <intent-filter>
        <action android:name="androidx.profileinstaller.action.SAVE_PROFILE"/>
      </intent-filter>
      <intent-filter>
        <action android:name="androidx.profileinstaller.action.BENCHMARK_OPERATION"/>
      </intent-filter>
    </receiver>
  </application>
</manifest>
 
 
(ctf-env) manavhowal@manavs-MacBook-Air downloads % droidasc listclass "$APK" --prefix com.fortid.cryptowallet
Lcom/fortid/cryptowallet/GeoPoint;
Lcom/fortid/cryptowallet/MainActivity;
Lcom/fortid/cryptowallet/NativeVerifier;
Lcom/fortid/cryptowallet/ObjectVicinityService;
Lcom/fortid/cryptowallet/PendingPartnershipVicinityService;
Lcom/fortid/cryptowallet/TrendChartView;
Lcom/fortid/cryptowallet/VicinityClient;
Lcom/fortid/cryptowallet/VicinityResult;
 
(ctf-env) manavhowal@manavs-MacBook-Air downloads % droidasc getclass "$APK" com.fortid.cryptowallet.MainActivity -o "$OUT/decompiled/MainActivity.java"
 
package com.fortid.cryptowallet;
public final class MainActivity extends e.h {
    public static final synthetic int C;
    public int A;
    public int B;
    public i0.a x;
    public final java.util.ArrayList y;
    public final java.util.concurrent.ExecutorService z;
 
    public MainActivity()
    {
        ((a0.e) this.e.c).e("androidx:appcompat", new a0.a(this));
        this.h(new e.g(this));
        this.y = new java.util.ArrayList();
        this.z = java.util.concurrent.Executors.newSingleThreadExecutor();
        return;
    }
 
    public final int t(int p2)
    {
        return ((int) (((float) p2) * this.getResources().getDisplayMetrics().density));
    }
 
    public final void onCreate(android.os.Bundle p21)
    {
        int v3_0 = super.onCreate(p21).getLayoutInflater().inflate(2131427356, 0, 0);
        com.google.android.material.textfield.TextInputLayout v4_0 = 2131230768;
        int v10_1 = ((android.widget.TextView) P.e.q(v3_0, 2131230768));
        if (v10_1 != 0) {
            v4_0 = 2131230769;
            if (((com.google.android.material.card.MaterialCardView) P.e.q(v3_0, 2131230769)) != null) {
                v4_0 = 2131230770;
                int v11_4 = ((android.widget.TextView) P.e.q(v3_0, 2131230770));
                if (v11_4 != 0) {
                    v4_0 = 2131230842;
                    int v12_2 = ((android.widget.TextView) P.e.q(v3_0, 2131230842));
                    if (v12_2 != 0) {
                        v4_0 = 2131230843;
                        int v13_2 = ((android.widget.LinearLayout) P.e.q(v3_0, 2131230843));
                        if (v13_2 != 0) {
                            v4_0 = 2131230882;
                            if (((com.fortid.cryptowallet.TrendChartView) P.e.q(v3_0, 2131230882)) != null) {
                                v4_0 = 2131230897;
                                android.widget.TextView v14_1 = ((android.widget.TextView) P.e.q(v3_0, 2131230897));
                                if (v14_1 != null) {
                                    v4_0 = 2131230968;
                                    if (((android.widget.TextView) P.e.q(v3_0, 2131230968)) != null) {
                                        v4_0 = 2131231007;
                                        if (((android.widget.TextView) P.e.q(v3_0, 2131231007)) != null) {
                                            v4_0 = 2131231052;
                                            android.widget.LinearLayout v15_1 = ((android.widget.LinearLayout) P.e.q(v3_0, 2131231052));
                                            if (v15_1 != null) {
                                                v4_0 = 2131231055;
                                                com.google.android.material.button.MaterialButton v16_1 = ((com.google.android.material.button.MaterialButton) P.e.q(v3_0, 2131231055));
                                                if (v16_1 != null) {
                                                    com.google.android.material.textfield.TextInputLayout v4_9 = ((androidx.core.widget.NestedScrollView) v3_0);
                                                    int v7_16 = 2131231069;
                                                    if (((android.widget.LinearLayout) P.e.q(v3_0, 2131231069)) != null) {
                                                        v7_16 = 2131231181;
                                                        android.widget.TextView v18_1 = ((android.widget.TextView) P.e.q(v3_0, 2131231181));
                                                        if (v18_1 != null) {
                                                            v7_16 = 2131231185;
                                                            android.widget.GridLayout v19_1 = ((android.widget.GridLayout) P.e.q(v3_0, 2131231185));
                                                            if (v19_1 != null) {
                                                                int v3_1 = new i0.a;
                                                                v3_1(v4_9, v10_1, v11_4, v12_2, v13_2, v14_1, v15_1, v16_1, v4_9, v18_1, v19_1);
                                                                this.x = v3_1;
                                                                this.setContentView(v4_9);
                                                                int v3_2 = this.x;
                                                                if (v3_2 == 0) {
                                                                    T0.c.g();
                                                                    throw 0;
                                                                } else {
                                                                    this.A = v3_2.i.getPaddingBottom();
                                                                    int v3_5 = this.x;
                                                                    if (v3_5 == 0) {
                                                                        T0.c.g();
                                                                        throw 0;
                                                                    } else {
                                                                        K.B.l(v3_5.i, new I0.l(this));
                                                                        int v3_7 = this.x;
                                                                        if (v3_7 == 0) {
                                                                            T0.c.g();
                                                                            throw 0;
                                                                        } else {
                                                                            v3_7.b.setText("0xdc7d8b\u20264fb522");
                                                                            int v3_9 = 0;
                                                                            while (v3_9 < 12) {
                                                                                int v11_1;
                                                                                com.google.android.material.textfield.TextInputLayout v4_6 = new com.google.android.material.textfield.TextInputLayout(this, 0);
                                                                                int v7_1 = (v3_9 + 1);
                                                                                v4_6.setHint(String.valueOf(v7_1));
                                                                                v4_6.setBoxBackgroundMode(2);
                                                                                v4_6.setBoxBackgroundColor(v4_6.getContext().getColor(2131034884));
                                                                                v4_6.setBoxStrokeColor(v4_6.getContext().getColor(2131034883));
                                                                                v4_6.setHintTextColor(A.a.D(v4_6.getContext(), 2131034893));
                                                                                com.google.android.material.textfield.TextInputEditText v8_9 = new android.widget.GridLayout$LayoutParams();
                                                                                v8_9.width = 0;
                                                                                v8_9.height = -2;
                                                                                v8_9.columnSpec = android.widget.GridLayout.spec(-2147483648, 1065353216);
                                                                                int v9_7 = 5;
                                                                                int v10_3 = this.t(5);
                                                                                if ((v3_9 % 2) != 0) {
                                                                                    v11_1 = 0;
                                                                                } else {
                                                                                    v11_1 = 6;
                                                                                }
                                                                                v8_9.setMargins(0, v10_3, this.t(v11_1), this.t(5));
                                                                                v4_6.setLayoutParams(v8_9);
                                                                                com.google.android.material.textfield.TextInputEditText v8_11 = new com.google.android.material.textfield.TextInputEditText(v4_6.getContext(), 0);
                                                                                v8_11.setInputType(524289);
                                                                                if (v3_9 == 11) {
                                                                                    v9_7 = 6;
                                                                                }
                                                                                v8_11.setImeOptions(v9_7);
                                                                                v8_11.setSingleLine(1);
                                                                                v8_11.setTextSize(1097859072);
                                                                                v8_11.setTextColor(v8_11.getContext().getColor(2131034892));
                                                                                v8_11.setPadding(this.t(14), this.t(14), this.t(14), this.t(14));
                                                                                v8_11.setOnFocusChangeListener(new h0.a(this, v4_6));
                                                                                v8_11.setOnEditorActionListener(new h0.b(this, v3_9, v8_11));
                                                                                v4_6.addView(v8_11, new android.widget.LinearLayout$LayoutParams(-1, this.t(54)));
                                                                                int v3_13 = this.x;
                                                                                if (v3_13 == 0) {
                                                                                    T0.c.g();
                                                                                    throw 0;
                                                                                } else {
                                                                                    v3_13.k.addView(v4_6);
                                                                                    this.y.add(v8_11);
                                                                                    v3_9 = v7_1;
                                                                                }
                                                                            }
                                                                            com.google.android.material.button.MaterialButton v1_2 = this.x;
                                                                            if (v1_2 == null) {
                                                                                T0.c.g();
                                                                                throw 0;
                                                                            } else {
                                                                                v1_2.h.setOnClickListener(new I0.a(4, this));
                                                                                return;
                                                                            }
                                                                        }
                                                                    }
                                                                }
                                                            }
                                                        }
                                                    }
                                                    v4_0 = v7_16;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        throw new NullPointerException("Missing required view with ID: ".concat(v3_0.getResources().getResourceName(v4_0)));
    }
 
    public final void onDestroy()
    {
        this.z.shutdownNow();
        super.onDestroy();
        return;
    }
 
    public final void u(android.view.View p8)
    {
        int v0_1 = new android.graphics.Rect();
        p8.getDrawingRect(v0_1);
        androidx.core.widget.NestedScrollView v1_4 = this.x;
        if (v1_4 == null) {
            T0.c.g();
            throw 0;
        } else {
            v1_4.i.offsetDescendantRectToMyCoords(p8, v0_1);
            int v8_1 = this.x;
            if (v8_1 == 0) {
                T0.c.g();
                throw 0;
            } else {
                int v8_6 = ((int) (((float) v8_1.i.getHeight()) * 1054280253));
                androidx.core.widget.NestedScrollView v1_1 = this.B;
                if (v1_1 > null) {
                    v8_6 = v1_1;
                }
                androidx.core.widget.NestedScrollView v1_2 = this.x;
                if (v1_2 == null) {
                    T0.c.g();
                    throw 0;
                } else {
                    androidx.core.widget.NestedScrollView v1_5 = v1_2.i.getScrollY();
                    int v3_0 = this.x;
                    if (v3_0 == 0) {
                        T0.c.g();
                        throw 0;
                    } else {
                        int v3_5 = (((v3_0.i.getHeight() + v1_5) - v8_6) - this.t(36));
                        int v4_1 = this.x;
                        if (v4_1 == 0) {
                            T0.c.g();
                            throw 0;
                        } else {
                            int v8_8;
                            int v4_3 = v4_1.i.getScrollY();
                            int v6_0 = this.t(24);
                            int v4_4 = v0_1.bottom;
                            if (v4_4 <= v3_5) {
                                int v8_7 = v0_1.top;
                                if (v8_7 >= (v6_0 + v4_3)) {
                                    return;
                                } else {
                                    v8_8 = (v8_7 - this.t(24));
                                }
                            } else {
                                int v0_3 = this.x;
                                if (v0_3 == 0) {
                                    T0.c.g();
                                    throw 0;
                                } else {
                                    v8_8 = (this.t(36) + ((v4_4 - v0_3.i.getHeight()) + v8_6));
                                }
                            }
                            if (v8_8 < 0) {
                                v8_8 = 0;
                            }
                            androidx.core.widget.NestedScrollView v1_7 = this.x;
                            if (v1_7 == null) {
                                T0.c.g();
                                throw 0;
                            } else {
                                androidx.core.widget.NestedScrollView v1_9 = v1_7.i;
                                v1_9.u((0 - v1_9.getScrollX()), (v8_8 - v1_9.getScrollY()), 0);
                                return;
                            }
                        }
                    }
                }
            }
        }
    }
 
    public final void v()
    {
        Throwable v0_0 = this.x;
        if (v0_0 == null) {
            T0.c.g();
            throw 0;
        } else {
            Throwable v0_3 = v0_0.a;
            boolean v2_0 = v0_3.getResources().getText(2131623988);
            int v3_3 = 0;
            while (!(v0_3 instanceof androidx.coordinatorlayout.widget.CoordinatorLayout)) {
                if ((v0_3 instanceof android.widget.FrameLayout)) {
                    if (v0_3.getId() != 16908290) {
                        v3_3 = ((android.view.ViewGroup) v0_3);
                    } else {
                        Throwable v0_4 = ((android.view.ViewGroup) v0_3);
                        if (v0_4 == null) {
                            throw new IllegalArgumentException("No suitable parent found from the given view. Please provide a valid view.");
                        } else {
                            H0.m v5_8;
                            int v3_6 = v0_4.getContext();
                            Object v4_4 = android.view.LayoutInflater.from(v3_6);
                            H0.m v5_7 = v3_6.obtainStyledAttributes(H0.k.A);
                            int v6 = 0;
                            int v8 = v5_7.getResourceId(0, -1);
                            int v10 = v5_7.getResourceId(1, -1);
                            v5_7.recycle();
                            if ((v8 == -1) || (v10 == -1)) {
                                v5_8 = 2131427361;
                            } else {
                                v5_8 = 2131427412;
                            }
                            boolean v2_13;
                            H0.m v5_10 = new H0.k(v3_6, v0_4, ((com.google.android.material.snackbar.SnackbarContentLayout) v4_4.inflate(v5_8, v0_4, 0)), ((com.google.android.material.snackbar.SnackbarContentLayout) v4_4.inflate(v5_8, v0_4, 0)));
                            ((com.google.android.material.snackbar.SnackbarContentLayout) v5_10.i.getChildAt(0)).getMessageView().setText(v2_0);
                            v5_10.i.setBackgroundTintList(android.content.res.ColorStateList.valueOf(this.getColor(2131034885)));
                            ((com.google.android.material.snackbar.SnackbarContentLayout) v5_10.i.getChildAt(0)).getMessageView().setTextColor(this.getColor(2131034892));
                            Throwable v0_16 = H0.n.j();
                            if (android.os.Build$VERSION.SDK_INT < 29) {
                                v2_13 = 0;
                            } else {
                                v2_13 = C.b.c(v5_10.z);
                            }
                            int v3_9 = v5_10.s;
                            if (!v0_16.k(v3_9)) {
                                H0.m v5_1 = ((H0.m) v0_16.d);
                                if ((v5_1 != null) && (v5_1.a.get() == v3_9)) {
                                    v6 = 1;
                                }
                                if (v6 == 0) {
                                    v0_16.d = new H0.m(v2_13, v3_9);
                                } else {
                                    ((H0.m) v0_16.d).b = v2_13;
                                }
                                boolean v2_2 = ((H0.m) v0_16.c);
                                if ((!v2_2) || (!v0_16.b(v2_2, 4))) {
                                    v0_16.c = 0;
                                    v0_16.t();
                                } else {
                                }
                            } else {
                                H0.m v1_2 = ((H0.m) v0_16.c);
                                v1_2.b = v2_13;
                                ((android.os.Handler) v0_16.b).removeCallbacksAndMessages(v1_2);
                                v0_16.s(((H0.m) v0_16.c));
                            }
                            return;
                        }
                    }
                }
                Throwable v0_2 = v0_3.getParent();
                if (!(v0_2 instanceof android.view.View)) {
                    v0_3 = 0;
                } else {
                    v0_3 = ((android.view.View) v0_2);
                }
                if (v0_3 == null) {
                    v0_4 = v3_3;
                }
            }
            v0_4 = ((android.view.ViewGroup) v0_3);
        }
    }
 
    public final void w()
    {
        int v0_1 = this.getSystemService("input_method");
        T0.c.c(v0_1, "null cannot be cast to non-null type android.view.inputmethod.InputMethodManager");
        android.os.IBinder v1_3 = this.x;
        if (v1_3 == null) {
            T0.c.g();
            throw 0;
        } else {
            ((android.view.inputmethod.InputMethodManager) v0_1).hideSoftInputFromWindow(v1_3.a.getWindowToken(), 0);
            return;
        }
    }
 
    public final void x(android.view.View p6)
    {
        i0.a v0_0 = this.x;
        if (v0_0 == null) {
            T0.c.g();
            throw 0;
        } else {
            v0_0.i.post(new h0.c(this, p6, 0));
            i0.a v0_1 = this.x;
            if (v0_1 == null) {
                T0.c.g();
                throw 0;
            } else {
                v0_1.i.postDelayed(new h0.c(this, p6, 1), 120);
                i0.a v0_3 = this.x;
                if (v0_3 == null) {
                    T0.c.g();
                    throw 0;
                } else {
                    v0_3.i.postDelayed(new h0.c(this, p6, 2), 320);
                    return;
                }
            }
        }
    }
 
    public final void y(boolean p4)
    {
        com.google.android.material.button.MaterialButton v0_0 = this.x;
        if (v0_0 == null) {
            T0.c.g();
            throw 0;
        } else {
            v0_0.h.setEnabled((p4 ^ 1));
            com.google.android.material.button.MaterialButton v0_3 = this.x;
            if (v0_3 == null) {
                T0.c.g();
                throw 0;
            } else {
                String v4_2;
                if (p4 == null) {
                    v4_2 = this.getString(2131624113);
                    T0.c.d(v4_2, "getString(...)");
                } else {
                    v4_2 = "Verifying\u2026";
                }
                v0_3.h.setText(v4_2);
                return;
            }
        }
    }
 
    public final String z(java.util.ArrayList p25)
    {
        byte[] v1_21;
        byte[] v4_1 = 0;
        javax.crypto.spec.SecretKeySpec v6_3 = 1;
        int v7_42 = com.fortid.cryptowallet.NativeVerifier.INSTANCE.targetCoordinates();
        if (v7_42.length == 8) {
            String v8_5 = new java.util.ArrayList(4);
            long[] v10_0 = 0;
            while (v10_0 < 4) {
                long v12_10 = (v10_0 * 2);
                v8_5.add(new com.fortid.cryptowallet.GeoPoint(v7_42[v12_10], v7_42[(v12_10 + 1)]));
                v10_0++;
            }
            byte[] v1_11 = new java.util.ArrayList(4);
            String v2_1 = 0;
            while (v2_1 < 4) {
                int v7_28 = (v2_1 * 3);
                v1_11.add("///".concat(M0.c.B0(p25.subList(v7_28, (v7_28 + 3)), ".")));
                v2_1++;
            }
            String v2_2 = new boolean[16];
            int v7_18 = v1_11.size();
            long[] v10_2 = 0;
            while (v10_2 < v7_18) {
                int v11_6 = v8_5.size();
                long v12_2 = 0;
                while (v12_2 < v11_6) {
                    v2_2[((v10_2 * 4) + v12_2)] = com.fortid.cryptowallet.VicinityClient.INSTANCE.checkObjectVicinity(((String) v1_11.get(v10_2)), ((com.fortid.cryptowallet.GeoPoint) v8_5.get(v12_2)), 900).getInVicinity();
                    v12_2++;
                    javax.crypto.Cipher v5 = 8;
                }
                v10_2++;
                v5 = 8;
            }
            v1_21 = com.fortid.cryptowallet.NativeVerifier.INSTANCE.hasPerfectMatching(v2_2);
        } else {
            v1_21 = 0;
        }
        if (v1_21 != null) {
            if (p25.size() == 12) {
                String v3_4 = j0.a.d;
                if (v3_4 == null) {
                    v3_4 = j0.a.d;
                    if (v3_4 == null) {
                        String v3_23 = this.getAssets().open("bip39_english.txt");
                        T0.c.d(v3_23, "open(...)");
                        String v3_0 = new java.io.BufferedReader(new java.io.InputStreamReader(v3_23, Y0.a.a), 8192);
                        try {
                            int v7_0 = P.e.P(v3_0);
                        } catch (java.nio.charset.Charset v0_1) {
                            String v2_0 = v0_1;
                            try {
                                throw v2_0;
                            } catch (java.nio.charset.Charset v0_2) {
                                byte[] v4_0 = v0_2;
                                P.e.j(v3_0, v2_0);
                                throw v4_0;
                            }
                        }
                        v3_0.close();
                        String v3_2 = new M0.p(new M0.j(0, v7_0));
                        int v7_2 = M0.r.A0(M0.e.A0(v3_2));
                        if (v7_2 < 16) {
                            v7_2 = 16;
                        }
                        String v8_4 = new java.util.LinkedHashMap(v7_2);
                        String v3_3 = v3_2.iterator();
                        while (((java.util.Iterator) ((M0.q) v3_3).c).hasNext()) {
                            int v7_39 = ((M0.o) ((M0.q) v3_3).next());
                            v8_4.put(v7_39.b, Integer.valueOf(v7_39.a));
                        }
                        j0.a.d = v8_4;
                        v3_4 = v8_4;
                    }
                }
                byte[] v1_1 = new java.util.ArrayList(M0.e.A0(p25));
                int v7_9 = p25.iterator();
                while (v7_9.hasNext()) {
                    String v8_38 = ((Integer) v3_4.get(((String) v7_9.next())));
                    if (v8_38 != null) {
                        v1_1.add(v8_38);
                    }
                }
                String v3_5 = new byte[16];
                byte[] v1_2 = v1_1.iterator();
                int v7_10 = 0;
                String v8_7 = 0;
                while (v1_2.hasNext()) {
                    String v9_19 = ((Number) v1_2.next()).intValue();
                    int v11_11 = 10;
                    long v12_11 = -1;
                    while (v12_11 < v11_11) {
                        long v12_13 = ((v9_19 >> v11_11) & 1);
                        if (v8_7 >= 128) {
                            v7_10 = ((v7_10 << 1) | v12_13);
                        } else {
                            if (v12_13 != 0) {
                                long v13_18 = (v8_7 / 8);
                                v3_5[v13_18] = ((byte) ((1 << (7 - (v8_7 % 8))) | v3_5[v13_18]));
                            }
                        }
                        v8_7++;
                        v12_11 = -1;
                        v11_11 += -1;
                    }
                }
                if (v7_10 == ((java.security.MessageDigest.getInstance("SHA-256").digest(v3_5)[0] & 255) >> 4)) {
                    if (p25.size() != 12) {
                        throw new IllegalArgumentException("Failed requirement.");
                    } else {
                        if (!p25.isEmpty()) {
                            byte[] v1_13 = p25.iterator();
                            while (v1_13.hasNext()) {
                                int v7_13 = ((String) v1_13.next());
                                String v8_9 = java.util.regex.Pattern.compile("[a-z]+");
                                T0.c.d(v8_9, "compile(...)");
                                T0.c.e(v7_13, "input");
                                if (!v8_9.matcher(v7_13).matches()) {
                                    throw new IllegalArgumentException("Failed requirement.");
                                }
                            }
                        }
                        byte[] v1_15 = java.text.Normalizer$Form.NFKD;
                        java.nio.charset.Charset v0_8 = java.text.Normalizer.normalize(M0.c.B0(p25, " "), v1_15);
                        byte[] v1_16 = java.text.Normalizer.normalize("mnemonic", v1_15);
                        T0.c.b(v0_8);
                        java.nio.charset.Charset v0_9 = v0_8.toCharArray();
                        T0.c.d(v0_9, "toCharArray(...)");
                        T0.c.b(v1_16);
                        String v8_11 = java.nio.charset.StandardCharsets.UTF_8;
                        T0.c.d(v8_11, "UTF_8");
                        byte[] v1_17 = v1_16.getBytes(v8_11);
                        T0.c.d(v1_17, "getBytes(...)");
                        java.nio.charset.Charset v0_13 = javax.crypto.SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512").generateSecret(new javax.crypto.spec.PBEKeySpec(v0_9, v1_17, 2048, 512)).getEncoded();
                        T0.c.b(v0_13);
                        int v7_21 = java.nio.charset.StandardCharsets.US_ASCII;
                        T0.c.d(v7_21, "US_ASCII");
                        int v7_22 = "Bitcoin seed".getBytes(v7_21);
                        T0.c.d(v7_22, "getBytes(...)");
                        String v9_9 = javax.crypto.Mac.getInstance("HmacSHA512");
                        v9_9.init(new javax.crypto.spec.SecretKeySpec(v7_22, "HmacSHA512"));
                        java.nio.charset.Charset v0_14 = v9_9.doFinal(v0_13);
                        T0.c.d(v0_14, "doFinal(...)");
                        int v7_24 = 32;
                        String v9_11 = new java.math.BigInteger(1, M0.b.D0(0, 32, v0_14));
                        java.nio.charset.Charset v0_15 = M0.b.D0(32, 64, v0_14);
                        if (v9_11.compareTo(java.math.BigInteger.ZERO) > 0) {
                            int v11_5 = j0.a.c;
                            if (v9_11.compareTo(v11_5) < 0) {
                                long v12_1 = 0;
                                while(true) {
                                    long v13_0 = j0.a.b;
                                    if (v12_1 >= 5) {
                                        break;
                                    }
                                    long[] v10_16;
                                    int v14_12 = new int[] {-2147483604, -2147483588, -2147483648, 0, 0})[v12_1];
                                    if (v14_12 >= 0) {
                                        long[] v10_9 = v13_0.i.b();
                                        long v13_10 = v10_9.a;
                                        if (v13_10.h == null) {
                                            v13_10.h = v13_10.a();
                                        }
                                        String v2_23;
                                        long v13_11 = v13_10.h;
                                        v13_11.getClass();
                                        int v15_6 = v9_11.signum();
                                        if ((v15_6 != 0) && (!v10_9.i())) {
                                            v2_23 = v13_11.o(v10_9, v9_11.abs());
                                            if (v15_6 <= 0) {
                                                v2_23 = v2_23.k();
                                            }
                                            if (!v2_23.h(0, 0)) {
                                                throw new IllegalStateException("Invalid result");
                                            }
                                        } else {
                                            v2_23 = v10_9.a.j();
                                        }
                                        String v2_26 = v2_23.l().e(1);
                                        long[] v10_15 = java.nio.ByteBuffer.allocate(4).putInt(v14_12).array();
                                        T0.c.d(v10_15, "array(...)");
                                        v10_16 = M0.b.E0(v2_26, v10_15);
                                    } else {
                                        long v13_14 = new byte[1];
                                        v13_14[0] = 0;
                                        long v13_15 = M0.b.E0(v13_14, j0.a.a(v9_11));
                                        long[] v10_19 = java.nio.ByteBuffer.allocate(4).putInt(v14_12).array();
                                        T0.c.d(v10_19, "array(...)");
                                        v10_16 = M0.b.E0(v13_15, v10_19);
                                    }
                                    long v13_16 = javax.crypto.Mac.getInstance("HmacSHA512");
                                    v13_16.init(new javax.crypto.spec.SecretKeySpec(v0_15, "HmacSHA512"));
                                    java.nio.charset.Charset v0_30 = v13_16.doFinal(v10_16);
                                    T0.c.d(v0_30, "doFinal(...)");
                                    long[] v10_21 = new java.math.BigInteger(1, M0.b.D0(0, 32, v0_30));
                                    if (v10_21.compareTo(v11_5) >= 0) {
                                        throw new IllegalArgumentException("Failed requirement.");
                                    } else {
                                        v9_11 = v10_21.add(v9_11).mod(v11_5);
                                        if (T0.c.a(v9_11, java.math.BigInteger.ZERO)) {
                                            throw new IllegalArgumentException("Failed requirement.");
                                        } else {
                                            v0_15 = M0.b.D0(32, 64, v0_30);
                                            v12_1++;
                                            long[] v10 = 128;
                                        }
                                    }
                                }
                                java.nio.charset.Charset v0_18 = j0.a.a(v9_11);
                                byte[] v1_23 = new java.math.BigInteger(1, v0_18);
                                String v2_4 = v13_0.i.b();
                                String v3_9 = v2_4.a;
                                if (v3_9.h == null) {
                                    v3_9.h = v3_9.a();
                                }
                                byte[] v1_25;
                                String v3_10 = v3_9.h;
                                v3_10.getClass();
                                String v8_18 = v1_23.signum();
                                if ((v8_18 != null) && (!v2_4.i())) {
                                    v1_25 = v3_10.o(v2_4, v1_23.abs());
                                    if (v8_18 <= null) {
                                        v1_25 = v1_25.k();
                                    }
                                    if (!v1_25.h(0, 0)) {
                                        throw new IllegalStateException("Invalid result");
                                    }
                                } else {
                                    v1_25 = v2_4.a.j();
                                }
                                byte[] v1_30 = M0.b.D0(1, 65, v1_25.l().e(0));
                                String v2_7 = new byte[32];
                                String v3_12 = new d1.a();
                                String v8_20 = new long[25];
                                v3_12.a = v8_20;
                                String v8_22 = new byte[192];
                                v3_12.b = v8_22;
                                v3_12.c(256);
                                ((c1.b) c1.d.c.get()).getClass();
                                String v8_27 = v1_30.length;
                                String v9_13 = v3_12.d;
                                if ((v9_13 % 8) != 0) {
                                    throw new IllegalStateException("attempt to absorb with odd length queue");
                                } else {
                                    if (v3_12.f) {
                                        throw new IllegalStateException("attempt to absorb while squeezing");
                                    } else {
                                        byte[] v1_33;
                                        String v9_14 = (v9_13 >> 3);
                                        long[] v10_5 = (v3_12.c >> 3);
                                        int v11_8 = (v10_5 - v9_14);
                                        long v12_3 = v3_12.b;
                                        if (v8_27 >= v11_8) {
                                            if (v9_14 <= null) {
                                                v11_8 = 0;
                                            } else {
                                                System.arraycopy(v1_30, 0, v12_3, v9_14, v11_8);
                                                v3_12.a(v12_3, 0);
                                            }
                                            while(true) {
                                                String v9_15 = (v8_27 - v11_8);
                                                if (v9_15 < v10_5) {
                                                    break;
                                                }
                                                v3_12.a(v1_30, v11_8);
                                                v11_8 += v10_5;
                                            }
                                            System.arraycopy(v1_30, v11_8, v12_3, 0, v9_15);
                                            v1_33 = (v9_15 << 3);
                                        } else {
                                            System.arraycopy(v1_30, 0, v12_3, v9_14, v8_27);
                                            v1_33 = (v3_12.d + (v8_27 << 3));
                                        }
                                        v3_12.d = v1_33;
                                        String v8_29 = ((long) v3_12.e);
                                        long[] v10_6 = v3_12.a;
                                        int v11_9 = v3_12.b;
                                        if (!v3_12.f) {
                                            byte[] v1_37 = v3_12.d;
                                            long v12_4 = (v1_37 >> 3);
                                            v11_9[v12_4] = ((byte) (v11_9[v12_4] | ((byte) (1 << (v1_37 & 7)))));
                                            byte[] v1_38 = (v1_37 + 1);
                                            v3_12.d = v1_38;
                                            if (v1_38 != v3_12.c) {
                                                long v12_6 = (v1_38 >> 6);
                                                byte[] v1_39 = (v1_38 & 63);
                                                long v13_6 = 0;
                                                int v14_5 = 0;
                                                while (v13_6 < v12_6) {
                                                    v10_6[v13_6] = (v10_6[v13_6] ^ P.e.D(v11_9, v14_5));
                                                    v14_5 += 8;
                                                    v13_6++;
                                                }
                                                if (v1_39 > null) {
                                                    v10_6[v12_6] = (v10_6[v12_6] ^ (P.e.D(v11_9, v14_5) & ((1 << v1_39) - 1)));
                                                }
                                            } else {
                                                v3_12.a(v11_9, 0);
                                            }
                                            byte[] v1_42 = ((v3_12.c - 1) >> 6);
                                            v10_6[v1_42] = (v10_6[v1_42] ^ -9223372036854775808);
                                            v3_12.d = 0;
                                            v3_12.f = 1;
                                        }
                                        long v18_4 = 0;
                                        if ((v8_29 % 8) != 0) {
                                            throw new IllegalStateException("outputLength not a multiple of 8");
                                        }
                                        while (v18_4 < v8_29) {
                                            if (v3_12.d == 0) {
                                                v3_12.b();
                                                byte[] v1_61 = (v3_12.c >> 6);
                                                int v14_8 = v4_1;
                                                int v15_3 = v14_8;
                                                while (v14_8 < v1_61) {
                                                    long v20_3 = v10_6[v14_8];
                                                    P.e.A(((int) (v20_3 & 4294967295)), v15_3, v11_9);
                                                    P.e.A(((int) (v20_3 >> v7_24)), (v15_3 + 4), v11_9);
                                                    v15_3 += 8;
                                                    v14_8 += v6_3;
                                                    v5 = 12;
                                                    v7_24 = 32;
                                                }
                                                v3_12.d = v3_12.c;
                                            }
                                            byte[] v4_8 = ((int) Math.min(((long) v3_12.d), (v8_29 - v18_4)));
                                            System.arraycopy(v11_9, ((v3_12.c - v3_12.d) / 8), v2_7, ((int) (v18_4 / 8)), (v4_8 / 8));
                                            v3_12.d = (v3_12.d - v4_8);
                                            v18_4 += ((long) v4_8);
                                            v4_1 = 0;
                                            v6_3 = 1;
                                            v7_24 = 32;
                                        }
                                        v3_12.c(v3_12.e);
                                        String v2_8 = M0.b.D0(12, 32, v2_7);
                                        String v3_16 = new StringBuilder();
                                        v3_16.append("");
                                        javax.crypto.Cipher v5_1 = v2_8.length;
                                        javax.crypto.spec.SecretKeySpec v6_0 = 0;
                                        int v7_25 = 0;
                                        while (v6_0 < v5_1) {
                                            String v8_30 = v2_8[v6_0];
                                            v7_25++;
                                            if (v7_25 > 1) {
                                                v3_16.append("");
                                            }
                                            v3_16.append(String.format("%02x", java.util.Arrays.copyOf(new Object[] {Byte.valueOf(v8_30)}), 1)));
                                            v6_0++;
                                        }
                                        v3_16.append("");
                                        byte[] v1_46 = v3_16.toString();
                                        T0.c.d(v1_46, "toString(...)");
                                        byte[] v1_47 = "0x".concat(v1_46);
                                        T0.c.e(v1_47, "address");
                                        if (v1_47.equalsIgnoreCase("0xdc7d8b278af5f31fed5fe68f50be6d02404fb522")) {
                                            byte[] v1_50 = java.security.MessageDigest.getInstance("SHA-256");
                                            v1_50.update(v0_18);
                                            java.nio.charset.Charset v0_27 = java.nio.charset.StandardCharsets.UTF_8;
                                            T0.c.d(v0_27, "UTF_8");
                                            String v2_15 = "FortID/cryptocurrency-wallet/flag/v1".getBytes(v0_27);
                                            T0.c.d(v2_15, "getBytes(...)");
                                            v1_50.update(v2_15);
                                            byte[] v1_51 = v1_50.digest();
                                            String v2_17 = android.util.Base64.decode("NaY6LHeKYbf9abvi", 0);
                                            byte[] v4_6 = android.util.Base64.decode("7ue/WCpa19Wp+mKFu1PHsuTknMRzHqRamlwGVXWJCmMw7smZ/9PzoWkhi80Ezgs9tQ==", 0);
                                            javax.crypto.Cipher v5_5 = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding");
                                            v5_5.init(2, new javax.crypto.spec.SecretKeySpec(v1_51, "AES"), new javax.crypto.spec.GCMParameterSpec(128, v2_17));
                                            byte[] v1_55 = "wallet-recovery".getBytes(v0_27);
                                            T0.c.d(v1_55, "getBytes(...)");
                                            v5_5.updateAAD(v1_55);
                                            byte[] v1_56 = v5_5.doFinal(v4_6);
                                            T0.c.d(v1_56, "doFinal(...)");
                                            return new String(v1_56, v0_27);
                                        } else {
                                            return 0;
                                        }
                                    }
                                }
                            }
                        }
                        throw new IllegalArgumentException("Failed requirement.");
                    }
                }
            }
            return 0;
        } else {
            return 0;
        }
    }
}
 
(ctf-env) manavhowal@manavs-MacBook-Air downloads % droidasc getclass "$APK" com.fortid.cryptowallet.NativeVerifier -o "$OUT/decompiled/NativeVerifier.java"
 
package com.fortid.cryptowallet;
public final class NativeVerifier {
    public static final com.fortid.cryptowallet.NativeVerifier INSTANCE;
 
    private NativeVerifier()
    {
        return;
    }
 
    static NativeVerifier()
    {
        com.fortid.cryptowallet.NativeVerifier.INSTANCE = new com.fortid.cryptowallet.NativeVerifier();
        System.loadLibrary("walletverify");
        return;
    }
 
    public final native boolean hasPerfectMatching(boolean[] p0);
 
    public final native double[] targetCoordinates();
}
 
(ctf-env) manavhowal@manavs-MacBook-Air downloads % droidasc getclass "$APK" com.fortid.cryptowallet.VicinityClient -o "$OUT/decompiled/VicinityClient.java"
 
package com.fortid.cryptowallet;
public final class VicinityClient {
    public static final com.fortid.cryptowallet.VicinityClient INSTANCE;
    private static final com.fortid.cryptowallet.ObjectVicinityService service;
 
    private VicinityClient()
    {
        return;
    }
 
    static VicinityClient()
    {
        com.fortid.cryptowallet.VicinityClient.INSTANCE = new com.fortid.cryptowallet.VicinityClient();
        com.fortid.cryptowallet.VicinityClient.service = com.fortid.cryptowallet.PendingPartnershipVicinityService.INSTANCE;
        return;
    }
 
    public final com.fortid.cryptowallet.VicinityResult checkObjectVicinity(String p2, com.fortid.cryptowallet.GeoPoint p3, int p4)
    {
        T0.c.e(p2, "objectRef");
        T0.c.e(p3, "target");
        return com.fortid.cryptowallet.VicinityClient.service.checkObjectVicinity(p2, p3, p4);
    }
}
 
(ctf-env) manavhowal@manavs-MacBook-Air downloads % droidasc getclass "$APK" com.fortid.cryptowallet.PendingPartnershipVicinityService -o "$OUT/decompiled/PendingPartnershipVicinityService.java"
 
package com.fortid.cryptowallet;
public final class PendingPartnershipVicinityService implements com.fortid.cryptowallet.ObjectVicinityService {
    public static final com.fortid.cryptowallet.PendingPartnershipVicinityService INSTANCE;
 
    private PendingPartnershipVicinityService()
    {
        return;
    }
 
    static PendingPartnershipVicinityService()
    {
        com.fortid.cryptowallet.PendingPartnershipVicinityService.INSTANCE = new com.fortid.cryptowallet.PendingPartnershipVicinityService();
        return;
    }
 
    public com.fortid.cryptowallet.VicinityResult checkObjectVicinity(String p1, com.fortid.cryptowallet.GeoPoint p2, int p3)
    {
        T0.c.e(p1, "objectRef");
        T0.c.e(p2, "target");
        return new com.fortid.cryptowallet.VicinityResult(1, "Vicinity verification is not implemented yet. External geospatial partnership agreement is pending; recovery requests are temporarily accepted.");
    }
}
 
(ctf-env) manavhowal@manavs-MacBook-Air downloads % droidasc getclass "$APK" j0.a -o "$OUT/decompiled/CryptoHelper.java" 
 
package j0;
public final class a {
    public static final j0.a a;
    public static final b1.b b;
    public static final java.math.BigInteger c;
    public static volatile java.util.LinkedHashMap d;
 
    static a()
    {
        j0.a.a = new j0.a();
        b1.b v1_2 = "secp256k1";
        char[] v3 = "secp256k1".toCharArray();
        int v4 = 0;
        Z0.d v5_0 = 0;
        while (v4 != v3.length) {
            int v6_1 = v3[v4];
            if ((65 <= v6_1) && (90 >= v6_1)) {
                v3[v4] = ((char) (v6_1 + 32));
                v5_0 = 1;
            }
            v4++;
        }
        if (v5_0 != null) {
            v1_2 = new String(v3);
        }
        java.math.BigInteger v0_2 = ((Z0.d) a1.c.a.get(v1_2));
        b1.b v1_3 = 0;
        if (v0_2 != null) {
            java.math.BigInteger v0_4 = ((b1.c) a1.c.b.get(v0_2));
            if (v0_4 != null) {
                if (v0_4.b == null) {
                    v0_4.b = v0_4.b();
                }
                v1_3 = v0_4.b;
            }
        }
        j0.a.b = v1_3;
        java.math.BigInteger v0_5 = v1_3.j;
        T0.c.d(v0_5, "getN(...)");
        j0.a.c = v0_5;
        return;
    }
 
    public static byte[] a(java.math.BigInteger p2)
    {
        byte[] v2_1 = p2.toByteArray();
        if (v2_1.length != 32) {
            if (v2_1.length <= 32) {
                byte[] v0_5 = new byte[(32 - v2_1.length)];
                v2_1 = M0.b.E0(v0_5, v2_1);
            } else {
                v2_1 = M0.b.D0((v2_1.length - 32), v2_1.length, v2_1);
            }
        }
        return v2_1;
    }
}

So the manifest identifies com.fortid.cryptowallet. and follow MainActivity.z(ArrayList),which returns the decrypted flag or null output. ASC occasionally gives registers misleading types follow calls and constants rather than treating its java as compilable source.

Plan to solve the challenge

1.Divide the words into four consecutive triples formatted as ///word.word.word. 2.Then try to compare every triple with every native coordinate at radius of 900meters. 3.This was very time consuming step for me where we are required a perfect matching in the resulting 4x4 boolean matrix so the group order i took was not fixed by the geography. 4.check the mnemonic checksum where we can derive the ETH account and compare the address. 5.Derive an AES key from the account's private key and decrypt the flag.

Discord Discussion got stuck here for a while but got that VicinityClient Selects PendingPartnershipVicinityService, whose method always returns true so this actually exposes the clue for locations but look bypassing it alone cannot help us decrypt the flag we still need ETH private key.

started Extracting the X86-64 libraru and word list:

(ctf-env) manavhowal@manavs-MacBook-Air downloads % unzip -p "$APK" lib/x86_64/libwalletverify.so > "$OUT/native/libwalletverify.so"
 
(ctf-env) manavhowal@manavs-MacBook-Air downloads % unzip -p "$APK" assets/bip39_english.txt > "$OUT/bip39_english.txt"
 
(ctf-env) manavhowal@manavs-MacBook-Air downloads % r2 -e scr.color=0 -A "$OUT/native/libwalletverify.so"
 
WARN: Relocs has not been applied. Please use `-e bin.relocs.apply=true` or `-e bin.cache=true` next time
INFO: Analyze all flags starting with sym. and entry0 (aa)
INFO: Analyze imports (af@@@i)
INFO: Analyze entrypoint (af@ entry0)
INFO: Analyze symbols (af@@@s)
INFO: Running plugin pre-analysis hooks
INFO: Analyze all functions arguments/locals (afva@@F)
INFO: Analyze function calls (aac)
INFO: Analyze len bytes of instructions for references (aar)
INFO: Finding and parsing C++ vtables (avrr)
INFO: Analyzing methods (af @@ method.*)
INFO: Recovering local variables (afva@@@F)
INFO: Type matching analysis for all functions (aaft)
INFO: Propagate noreturn information (aanr)
INFO: Use -AA or aaaa to perform additional experimental analysis
 -- I love gradients.
[0x00000740]> afl
0x00000930    1      6 sym.imp.__cxa_finalize
0x00000940    1      6 sym.imp.__cxa_atexit
0x00000950    1      6 sym.imp.__register_atfork
0x00000960    1      6 sym.imp.__stack_chk_fail
0x00000740    1     12 entry0
0x000007b0    3    145 sym.Java_com_fortid_cryptowallet_NativeVerifier_targetCoordinates
0x000005b0    4    220 fcn.000005b0
0x000006b0    1     66 fcn.000006b0
0x00000850   13    193 sym.Java_com_fortid_cryptowallet_NativeVerifier_hasPerfectMatching
[0x00000740]> pdf @ sym.Java_com_fortid_cryptowallet_NativeVerifier_targetCoordinates
โ”Œ 145: void *sym.Java_com_fortid_cryptowallet_NativeVerifier_targetCoordinates (JNIEnv *env, jobject thiz);
โ”‚ `- args(rdi, rdx, rcx) vars(4:sp[0x18..0x48])
โ”‚           0x000007b0      4156           push r14
โ”‚           0x000007b2      53             push rbx
โ”‚           0x000007b3      4883ec48       sub rsp, 0x48
โ”‚           0x000007b7      4989fe         mov r14, rdi                ; env
โ”‚           0x000007ba      64488b0425..   mov rax, qword fs:[0x28]
โ”‚           0x000007c3      4889442440     mov qword [var_40h], rax
โ”‚           0x000007c8      0f2805a1fd..   movaps xmm0, xmmword [0x00000570] ; [0x570:16]=-1
โ”‚           0x000007cf      0f290424       movaps xmmword [rsp], xmm0
โ”‚           0x000007d3      0f280586fd..   movaps xmm0, xmmword [section..rodata] ; [0x560:16]=-1
โ”‚           0x000007da      0f29442410     movaps xmmword [var_10h], xmm0
โ”‚           0x000007df      0f28059afd..   movaps xmm0, xmmword [0x00000580] ; [0x580:16]=-1
โ”‚           0x000007e6      0f29442420     movaps xmmword [var_20h], xmm0
โ”‚           0x000007eb      0f28059efd..   movaps xmm0, xmmword [0x00000590] ; [0x590:16]=-1
โ”‚           0x000007f2      0f29442430     movaps xmmword [var_30h], xmm0
โ”‚           0x000007f7      488b07         mov rax, qword [rdi]        ; env
โ”‚           0x000007fa      be08000000     mov esi, 8                  ; int64_t arg2
โ”‚           0x000007ff      ff90b0050000   call qword [rax + fcn.000005b0] ; 0x5b0
โ”‚           0x00000805      4889c3         mov rbx, rax
โ”‚           0x00000808      498b06         mov rax, qword [r14]
โ”‚           0x0000080b      4989e0         mov r8, rsp
โ”‚           0x0000080e      4c89f7         mov rdi, r14                ; int64_t arg1
โ”‚           0x00000811      4889de         mov rsi, rbx                ; int64_t arg2
โ”‚           0x00000814      31d2           xor edx, edx
โ”‚           0x00000816      b908000000     mov ecx, 8
โ”‚           0x0000081b      ff90b0060000   call qword [rax + fcn.000006b0] ; 0x6b0
โ”‚           0x00000821      64488b0425..   mov rax, qword fs:[0x28]
โ”‚           0x0000082a      483b442440     cmp rax, qword [var_40h]
โ”‚       โ”Œโ”€< 0x0000082f      750b           jne 0x83c
โ”‚       โ”‚   0x00000831      4889d8         mov rax, rbx
โ”‚       โ”‚   0x00000834      4883c448       add rsp, 0x48
โ”‚       โ”‚   0x00000838      5b             pop rbx
โ”‚       โ”‚   0x00000839      415e           pop r14
โ”‚       โ”‚   0x0000083b      c3             ret
โ”‚       โ”‚   ; CODE XREF from sym.Java_com_fortid_cryptowallet_NativeVerifier_targetCoordinates @ 0x82f(x)
โ””       โ””โ”€> 0x0000083c      e81f010000     call sym.imp.__stack_chk_fail ; void stack_chk_fail(void)
[0x00000740]> pdf @ sym.Java_com_fortid_cryptowallet_NativeVerifier_hasPerfectMatching
โ”Œ 193: void *sym.Java_com_fortid_cryptowallet_NativeVerifier_hasPerfectMatching (JNIEnv *env, jobject thiz);
โ”‚ `- args(rdi, rdx) vars(1:sp[0x18..0x18])
โ”‚           0x00000850      4156           push r14
โ”‚           0x00000852      53             push rbx
โ”‚           0x00000853      4883ec18       sub rsp, 0x18
โ”‚           0x00000857      64488b0425..   mov rax, qword fs:[0x28]
โ”‚           0x00000860      4889442410     mov qword [var_10h], rax
โ”‚           0x00000865      4885d2         test rdx, rdx               ; arg3
โ”‚       โ”Œโ”€< 0x00000868      0f848e000000   je 0x8fc
โ”‚       โ”‚   0x0000086e      4989d6         mov r14, rdx                ; arg3
โ”‚       โ”‚   0x00000871      488b07         mov rax, qword [rdi]        ; env
โ”‚       โ”‚   0x00000874      4889fb         mov rbx, rdi                ; env
โ”‚       โ”‚   0x00000877      4889d6         mov rsi, rdx                ; arg3
โ”‚       โ”‚   0x0000087a      ff9058050000   call qword [rax + 0x558]
โ”‚       โ”‚   0x00000880      83f810         cmp eax, 0x10
โ”‚      โ”Œโ”€โ”€< 0x00000883      7577           jne 0x8fc
โ”‚      โ”‚โ”‚   0x00000885      0f57c0         xorps xmm0, xmm0
โ”‚      โ”‚โ”‚   0x00000888      0f290424       movaps xmmword [rsp], xmm0
โ”‚      โ”‚โ”‚   0x0000088c      4889df         mov rdi, rbx
โ”‚      โ”‚โ”‚   0x0000088f      488b03         mov rax, qword [rbx]
โ”‚      โ”‚โ”‚   0x00000892      4889e3         mov rbx, rsp
โ”‚      โ”‚โ”‚   0x00000895      4c89f6         mov rsi, r14
โ”‚      โ”‚โ”‚   0x00000898      31d2           xor edx, edx
โ”‚      โ”‚โ”‚   0x0000089a      b910000000     mov ecx, 0x10
โ”‚      โ”‚โ”‚   0x0000089f      4989d8         mov r8, rbx
โ”‚      โ”‚โ”‚   0x000008a2      ff9038060000   call qword [rax + 0x638]
โ”‚      โ”‚โ”‚   0x000008a8      b803000000     mov eax, 3
โ”‚      โ”‚โ”‚   0x000008ad      488d0decfc..   lea rcx, [0x000005a0]
โ”‚     โ”Œโ”€โ”€โ”€< 0x000008b4      eb14           jmp 0x8ca
..
โ”‚     โ”‚โ”‚โ”‚   ; CODE XREFS from sym.Java_com_fortid_cryptowallet_NativeVerifier_hasPerfectMatching @ 0x8d3(x), 0x8df(x), 0x8eb(x), 0x8f6(x)
โ”‚ โ”Œโ”Œโ”Œโ”Œโ”€โ”€โ”€โ”€> 0x000008c0      4883c004       add rax, 4
โ”‚ โ•Žโ•Žโ•Žโ•Žโ”‚โ”‚โ”‚   0x000008c4      4883f863       cmp rax, 0x63               ; 'c'
โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€< 0x000008c8      7432           je 0x8fc
โ”‚ โ•Žโ•Žโ•Žโ•Žโ”‚โ”‚โ”‚   ; CODE XREF from sym.Java_com_fortid_cryptowallet_NativeVerifier_hasPerfectMatching @ 0x8b4(x)
โ”‚ โ•Žโ•Žโ•Žโ•Žโ””โ”€โ”€โ”€> 0x000008ca      0fb65408fd     movzx edx, byte [rax + rcx - 3]
โ”‚ โ•Žโ•Žโ•Žโ•Ž โ”‚โ”‚   0x000008cf      803c1401       cmp byte [rsp + rdx], 1
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€< 0x000008d3      75eb           jne 0x8c0
โ”‚  โ•Žโ•Žโ•Ž โ”‚โ”‚   0x000008d5      0fb65408fe     movzx edx, byte [rax + rcx - 2]
โ”‚  โ•Žโ•Žโ•Ž โ”‚โ”‚   0x000008da      807c1a0401     cmp byte [rdx + rbx + 4], 1
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€< 0x000008df      75df           jne 0x8c0
โ”‚   โ•Žโ•Ž โ”‚โ”‚   0x000008e1      0fb65408ff     movzx edx, byte [rax + rcx - 1]
โ”‚   โ•Žโ•Ž โ”‚โ”‚   0x000008e6      807c1a0801     cmp byte [rdx + rbx + 8], 1
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€< 0x000008eb      75d3           jne 0x8c0
โ”‚    โ•Ž โ”‚โ”‚   0x000008ed      0fb61408       movzx edx, byte [rax + rcx]
โ”‚    โ•Ž โ”‚โ”‚   0x000008f1      807c1a0c01     cmp byte [rdx + rbx + 0xc], 1
โ”‚    โ””โ”€โ”€โ”€โ”€< 0x000008f6      75c8           jne 0x8c0
โ”‚      โ”‚โ”‚   0x000008f8      b001           mov al, 1
โ”‚     โ”Œโ”€โ”€โ”€< 0x000008fa      eb02           jmp 0x8fe
โ”‚     โ”‚โ”‚โ”‚   ; CODE XREFS from sym.Java_com_fortid_cryptowallet_NativeVerifier_hasPerfectMatching @ 0x868(x), 0x883(x), 0x8c8(x)
โ”‚ โ”€โ”€โ”€โ”€โ”€โ””โ””โ”€> 0x000008fc      31c0           xor eax, eax
โ”‚     โ”‚     ; CODE XREF from sym.Java_com_fortid_cryptowallet_NativeVerifier_hasPerfectMatching @ 0x8fa(x)
โ”‚     โ””โ”€โ”€โ”€> 0x000008fe      64488b0c25..   mov rcx, qword fs:[0x28]
โ”‚           0x00000907      483b4c2410     cmp rcx, qword [var_10h]
โ”‚       โ”Œโ”€< 0x0000090c      7508           jne 0x916
โ”‚       โ”‚   0x0000090e      4883c418       add rsp, 0x18
โ”‚       โ”‚   0x00000912      5b             pop rbx
โ”‚       โ”‚   0x00000913      415e           pop r14
โ”‚       โ”‚   0x00000915      c3             ret
โ”‚       โ”‚   ; CODE XREF from sym.Java_com_fortid_cryptowallet_NativeVerifier_hasPerfectMatching @ 0x90c(x)
โ””       โ””โ”€> 0x00000916      e845000000     call sym.imp.__stack_chk_fail ; void stack_chk_fail(void)

The co-ordinate function copies four pairs of doubles from file offsets i.e 0x570, 0x560, 0x580, 0x590 in that order so basically this offsets apply to this apks x86-64 library.

>>> from pathlib import Path
... import struct
... b = Path("native/libwalletverify.so").read_bytes()
... for offset in (0x570, 0x560, 0x580, 0x590):
...     print(struct.unpack_from("<dd", b, offset))
...     
(41.99175, -87.889806)
(33.941589, -118.40853)
(44.884755, -93.222284)
(40.641311, -73.778139)
>>> 
 

The matching function reads 16 booleans and tries all 24 permutations of four columns.

Now time to resolve the named places and recover the words

This took me roughly around 6 Hrs, I started ploting the coordinates they indicate O'Hare's vicinity, LAX, Minneapolis-Saint Paul, and JFK. The triple format was basiclly what3words. I spent hrs searching up the public pages for facilities near these points and then found the US Airport Wiki directories publish three word references. So now what I did was restricted the candidates to published place references whose three words all comes in the apks bip39_english.txt.APK passes a radius of 900 metres to its vicinity service (decompiled/MainActivity.java, method z) this always returns true, so the following is an independent check of the geographic clues and not a distance algorithm extracted from the services. 1.Start with the four coordinates recovered from the native library.

a)O'Hare	41.991750	-87.889806
b)LAX	33.941589	-118.408530
c)Minneapolis-Saint Paul	44.884755	-93.222284
d)JFK	40.641311	-73.778139

Now I was checking for the published facilities around each target and writing down their three word addresses, but the thing is we can only keep the triple if all three words appear in the APK's bip39_english.txt

1.https://usairportwiki.com/chicago-ohare-international-airport/economy-parking-lot-f.html the three word address for this was slot.escape.oven

2.https://usairportwiki.com/los-angeles-international-airport/united-polaris-lounge-terminal-7.html dice.ticket.path

3.https://usairportwiki.com/minneapolis-saint-paul-international-airport/terminal-1-daily-parking.html sport.glue.survey

4.https://usairportwiki.com/jfk-international-airport/the-centurion-lounge-terminal-4.html reason.manual.myself

After resolving each triple to its location, I was able to calculate its distance from the traget. Three-word-to-coordinate conversion is a separate operation, described in the https://developer.what3words.com/public-api/docs.

For a short distance, use a local north/east approximation. Let the target be (\phi_0,\lambda_0) and the decoded location be (\phi_1,\lambda_1),with both coordinates in degrees. With a spherical Earth radius of 6,371,000 m, one degree corresponds to R\pi/180\approx111194.9266 m:

N=111194.9266(ฯ•1โˆ’ฯ•0),E=111194.9266cosโก(ฯ•0+ฯ•12ฯ€180)(ฮป1โˆ’ฮป0)N=111194.9266(\phi_1-\phi_0),\qquad E=111194.9266\cos\left(\frac{\phi_0+\phi_1}{2}\frac{\pi}{180}\right)(\lambda_1-\lambda_0)

Book image 1

Book image 2

Book image 3

Book image 4

Reproducing the wallet derivation and authenticating the flag

So now we are almost done we have decompiled functions which uses: 1.BIP39 english and it has 12 words which are valid 128 bit entrophy plus 4 bit checksum. 2.Also there was PBKDF2-HMAC-SHA512 with 2048 iterartions, with 64 byte output and salt was mnemonic this was just empty passphrase lol 3.Derivation path was m/44'/60'/0'/0/0. 4.BIP32 master HMAC key Bitcoin seed secp256k1 5.Eth address:last 20 bytes of Keccak-256 of the uncompressed public key excluding its 04 prefix. 6.AES-256 key SHA256(private_key_32_bytes || b"FortID/cryptocurrency-wallet/flag/v1"). 7.GCM nonce: Base64 NaY6LHeKYbf9abvi. 8.GCM ciphertext plus 16-byte tag: Base64 7ue/WCpa19Wp+mKFu1PHsuTknMRzHqRamlwGVXWJCmMw7smZ/9PzoWkhi80Ezgs9tQ==. 9.Additional authenticated data: wallet-recovery

So we covered this many points in the entire process. but now only use the derived account private key and dont use the mnemonic or 64 byte BIP39 seed,And for the AES key hash Use Keccak-256, not SHA3-256 this will help to construct the ETH address properly.

Flag

Thanks for reading!!!