题目是个 .apk 文件,刚好好久没做过 Android 逆向了,结合题目名字也能猜出来可能要 hook ,正好学习一下

正向分析

把apk下载到模拟器里面直接运行看看

看样子是一个三层校验程序,先看一下 AndroidManifest.xml 文件,似乎有几个关键的类名

似乎刚好对应了上面的三层校验,我们一层一层来分析

Pattern

F1:爆破九宫格密码

第一层看来就是一个安卓常见的九宫格图形锁

定位到对应的类 com.nctf.hookmysecret.ui.PatternActivity

package com.nctf.hookmysecret.ui;

import C.h;
import N0.c;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.widget.TextView;
import com.google.android.material.button.MaterialButton;
import com.nctf.hookmysecret.R;
import com.nctf.hookmysecret.ui.PatternActivity;
import com.nctf.hookmysecret.ui.widget.PatternLockView;
import e.AbstractActivityC0110h;

/* loaded from: classes.dex */
public final class PatternActivity extends AbstractActivityC0110h {

    /* renamed from: z  reason: collision with root package name */
    public static final /* synthetic */ int f1914z = 0;

    /* renamed from: x  reason: collision with root package name */
    public final Handler f1915x = new Handler(Looper.getMainLooper());

    /* renamed from: y  reason: collision with root package name */
    public h f1916y;

    @Override // e.AbstractActivityC0110h, androidx.activity.k, z.f, android.app.Activity
    public final void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.activity_pattern);
        this.f1916y = new h(this);
        PatternLockView patternLockView = (PatternLockView) findViewById(R.id.patternLockView);
        TextView textView = (TextView) findViewById(R.id.patternStatusText);
        textView.setText("Draw a valid pattern to continue.");
        patternLockView.setOnPatternComplete(new c(this, textView, patternLockView));
        ((MaterialButton) findViewById(R.id.clearPatternButton)).setOnClickListener(new View.OnClickListener(textView) { // from class: N0.b
            public final /* synthetic */ TextView b;

            {
                this.b = r2;
            }

            @Override // android.view.View.OnClickListener
            public final void onClick(View view) {
                int i2 = PatternActivity.f1914z;
                PatternLockView patternLockView2 = PatternLockView.this;
                patternLockView2.b.clear();
                patternLockView2.invalidate();
                this.b.setText("Pattern cleared.");
            }
        });
    }
}

显然这个类对应上面的九宫格图形锁校验,真正的校验函数在 N0.c 中,跟进分析

package N0;

import C.h;
import F0.f;
import V0.l;
import W0.d;
import android.content.Intent;
import android.content.SharedPreferences;
import android.widget.TextView;
import b1.a;
import com.nctf.hookmysecret.ui.PatternActivity;
import com.nctf.hookmysecret.ui.Stage2Activity;
import com.nctf.hookmysecret.ui.widget.PatternLockView;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

/* loaded from: classes.dex */
public final class c extends d implements l {

    /* renamed from: a  reason: collision with root package name */
    public final /* synthetic */ PatternActivity f478a;
    public final /* synthetic */ TextView b;

    /* renamed from: c  reason: collision with root package name */
    public final /* synthetic */ PatternLockView f479c;

    public c(PatternActivity patternActivity, TextView textView, PatternLockView patternLockView) {
        this.f478a = patternActivity;
        this.b = textView;
        this.f479c = patternLockView;
    }

    @Override // V0.l
    public final Object b(Object obj) {
        CharSequence charSequence;
        List list = (List) obj;
        W0.c.e(list, "nodes");
        StringBuilder sb = new StringBuilder();
        sb.append((CharSequence) "");
        Iterator it = list.iterator();
        int i2 = 0;
        while (true) {
            boolean z2 = true;
            if (!it.hasNext()) {
                break;
            }
            Object next = it.next();
            i2++;
            if (i2 > 1) {
                sb.append((CharSequence) ",");
            }
            if (next != null) {
                z2 = next instanceof CharSequence;
            }
            if (z2) {
                charSequence = (CharSequence) next;
            } else if (next instanceof Character) {
                sb.append(((Character) next).charValue());
            } else {
                charSequence = String.valueOf(next);
            }
            sb.append(charSequence);
        }
        sb.append((CharSequence) "");
        String sb2 = sb.toString();
        W0.c.d(sb2, "toString(...)");
        PatternActivity patternActivity = this.f478a;
        h hVar = patternActivity.f1916y;
        if (hVar != null) {
            hVar.z(sb2);
            MessageDigest instance = MessageDigest.getInstance("SHA-256");
            byte[] bytes = b1.h.L(sb2).toString().getBytes(a.f1342a);
            W0.c.d(bytes, "getBytes(...)");
            byte[] digest = instance.digest(bytes);
            W0.c.d(digest, "digest(...)");
            StringBuilder sb3 = new StringBuilder();
            sb3.append((CharSequence) "");
            int i3 = 0;
            for (byte b : digest) {
                i3++;
                if (i3 > 1) {
                    sb3.append((CharSequence) "");
                }
                sb3.append((CharSequence) String.format("%02x", Arrays.copyOf(new Object[]{Byte.valueOf(b)}, 1)));
            }
            sb3.append((CharSequence) "");
            String sb4 = sb3.toString();
            W0.c.d(sb4, "toString(...)");
            boolean equals = sb4.equals("4a6bc34076c8eef0f9eac59ad30d99bb4f56ecea4b0bfab92540fb655ac680f3");
            TextView textView = this.b;
            if (equals) {
                h hVar2 = patternActivity.f1916y;
                if (hVar2 != null) {
                    ((SharedPreferences) hVar2.b).edit().putBoolean("stage1Passed", true).apply();
                    textView.setText("Pattern accepted.");
                    patternActivity.startActivity(new Intent(patternActivity, Stage2Activity.class));
                    patternActivity.finish();
                } else {
                    W0.c.g("stateManager");
                    throw null;
                }
            } else {
                textView.setText("Pattern incorrect.");
                h hVar3 = patternActivity.f1916y;
                if (hVar3 != null) {
                    int i4 = ((SharedPreferences) hVar3.b).getInt("attemptCount", 0);
                    if (i4 > 8) {
                        i4 = 8;
                    }
                    patternActivity.f1915x.postDelayed(new f(5, this.f479c), (((long) i4) * 35) + 120);
                } else {
                    W0.c.g("stateManager");
                    throw null;
                }
            }
            return O0.c.f486c;
        }
        W0.c.g("stateManager");
        throw null;
    }
}

这个类的逻辑很简单,当用户绘制图案后,将九宫格各节点的编号用 , 拼接成一个字符串,随后对该字符串计算 SHA-256 哈希值,若哈希值为 4a6bc34076c8eef0f9eac59ad30d99bb4f56ecea4b0bfab92540fb655ac680f3 则校验通过

至于节点的编号是什么形式,我们需要查看 N0.c 中导入的 PatternLockView 类

package com.nctf.hookmysecret.ui.widget;

import A.b;
import O0.a;
import V0.l;
import W0.c;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;
import com.nctf.hookmysecret.R;
import java.util.ArrayList;
import java.util.Iterator;

/* loaded from: classes.dex */
public final class PatternLockView extends View {

    /* renamed from: a  reason: collision with root package name */
    public l f1922a;
    public final ArrayList b = new ArrayList();

    /* renamed from: c  reason: collision with root package name */
    public final ArrayList f1923c = new ArrayList();

    /* renamed from: d  reason: collision with root package name */
    public float f1924d;

    /* renamed from: e  reason: collision with root package name */
    public float f1925e;
    public final Paint f;
    public final Paint g;

    /* renamed from: h  reason: collision with root package name */
    public final Paint f1926h;

    /* JADX WARN: 'super' call moved to the top of the method (can break code semantics) */
    public PatternLockView(Context context, AttributeSet attributeSet) {
        super(context, attributeSet);
        c.e(context, "context");
        Paint paint = new Paint(1);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(10.0f);
        paint.setColor(b.a(context, R.color.line_active));
        this.f = paint;
        Paint paint2 = new Paint(1);
        Paint.Style style = Paint.Style.FILL;
        paint2.setStyle(style);
        paint2.setColor(b.a(context, R.color.line_idle));
        this.g = paint2;
        Paint paint3 = new Paint(1);
        paint3.setStyle(style);
        paint3.setColor(b.a(context, R.color.line_active));
        this.f1926h = paint3;
    }

    public final l getOnPatternComplete() {
        return this.f1922a;
    }

    @Override // android.view.View
    public final void onDraw(Canvas canvas) {
        Paint paint;
        int i2;
        c.e(canvas, "canvas");
        super.onDraw(canvas);
        float min = ((float) Math.min(getWidth(), getHeight())) / 12.0f;
        Path path = new Path();
        ArrayList arrayList = this.b;
        Iterator it = arrayList.iterator();
        int i3 = 0;
        int i4 = 0;
        while (true) {
            boolean hasNext = it.hasNext();
            ArrayList arrayList2 = this.f1923c;
            if (hasNext) {
                Object next = it.next();
                int i5 = i4 + 1;
                if (i4 >= 0) {
                    a aVar = (a) arrayList2.get(((Number) next).intValue());
                    float floatValue = ((Number) aVar.f483a).floatValue();
                    float floatValue2 = ((Number) aVar.b).floatValue();
                    if (i4 == 0) {
                        path.moveTo(floatValue, floatValue2);
                    } else {
                        path.lineTo(floatValue, floatValue2);
                    }
                    i4 = i5;
                } else {
                    throw new ArithmeticException("Index overflow has happened.");
                }
            } else {
                if (!arrayList.isEmpty()) {
                    path.lineTo(this.f1924d, this.f1925e);
                    canvas.drawPath(path, this.f);
                }
                Iterator it2 = arrayList2.iterator();
                while (it2.hasNext()) {
                    Object next2 = it2.next();
                    int i6 = i3 + 1;
                    if (i3 >= 0) {
                        a aVar2 = (a) next2;
                        float floatValue3 = ((Number) aVar2.f483a).floatValue();
                        float floatValue4 = ((Number) aVar2.b).floatValue();
                        if (arrayList.contains(Integer.valueOf(i3))) {
                            paint = this.f1926h;
                        } else {
                            paint = this.g;
                        }
                        canvas.drawCircle(floatValue3, floatValue4, min, paint);
                        float f = 0.35f * min;
                        boolean contains = arrayList.contains(Integer.valueOf(i3));
                        Paint paint2 = new Paint(1);
                        paint2.setStyle(Paint.Style.FILL);
                        if (contains) {
                            i2 = -1;
                        } else {
                            i2 = b.a(getContext(), R.color.card);
                        }
                        paint2.setColor(i2);
                        canvas.drawCircle(floatValue3, floatValue4, f, paint2);
                        i3 = i6;
                    } else {
                        throw new ArithmeticException("Index overflow has happened.");
                    }
                }
                return;
            }
        }
    }

    @Override // android.view.View
    public final void onSizeChanged(int i2, int i3, int i4, int i5) {
        Object obj;
        float f;
        super.onSizeChanged(i2, i3, i4, i5);
        float f2 = (float) i2;
        float f3 = (float) i3;
        ArrayList arrayList = this.f1923c;
        arrayList.clear();
        float min = Math.min(f2, f3) / 4.0f;
        float f4 = min * 2.0f;
        float f5 = (f2 - f4) / 2.0f;
        float f6 = (f3 - f4) / 2.0f;
        for (int i6 = 0; i6 < 3; i6++) {
            for (int i7 = 0; i7 < 3; i7++) {
                arrayList.add(new a(Float.valueOf((((float) i7) * min) + f5), Float.valueOf((((float) i6) * min) + f6)));
            }
        }
        c.e(arrayList, "<this>");
        Object obj2 = null;
        if (arrayList.isEmpty()) {
            obj = null;
        } else {
            obj = arrayList.get(0);
        }
        a aVar = (a) obj;
        float f7 = 0.0f;
        if (aVar != null) {
            f = ((Number) aVar.f483a).floatValue();
        } else {
            f = 0.0f;
        }
        this.f1924d = f;
        c.e(arrayList, "<this>");
        if (!arrayList.isEmpty()) {
            obj2 = arrayList.get(0);
        }
        a aVar2 = (a) obj2;
        if (aVar2 != null) {
            f7 = ((Number) aVar2.b).floatValue();
        }
        this.f1925e = f7;
    }

    /* JADX WARN: Code restructure failed: missing block: B:6:0x001e, code lost:
        if (r0 != 2) goto L_0x00bf;
     */
    @Override // android.view.View
    /*
        Code decompiled incorrectly, please refer to instructions dump.
        To view partially-correct code enable 'Show inconsistent code' option in preferences
    */
    public final boolean onTouchEvent(android.view.MotionEvent r9) {
        /*
            r8 = this;
            java.lang.String r0 = "event"
            W0.c.e(r9, r0)
            float r0 = r9.getX()
            r8.f1924d = r0
            float r0 = r9.getY()
            r8.f1925e = r0
            int r0 = r9.getActionMasked()
            java.util.ArrayList r1 = r8.b
            r2 = 1
            r3 = 0
            if (r0 == 0) goto L_0x0051
            if (r0 == r2) goto L_0x0022
            r4 = 2
            if (r0 == r4) goto L_0x0051
            goto L_0x00bf
        L_0x0022:
            r8.invalidate()
            boolean r9 = r1.isEmpty()
            r9 = r9 ^ r2
            if (r9 == 0) goto L_0x00bf
            V0.l r9 = r8.f1922a
            if (r9 == 0) goto L_0x00bf
            P0.d r0 = P0.d.f496a
            int r4 = r1.size()
            if (r4 == 0) goto L_0x004d
            if (r4 == r2) goto L_0x0040
            java.util.ArrayList r0 = new java.util.ArrayList
            r0.<init>(r1)
            goto L_0x004d
        L_0x0040:
            java.lang.Object r0 = r1.get(r3)
            java.util.List r0 = java.util.Collections.singletonList(r0)
            java.lang.String r1 = "singletonList(...)"
            W0.c.d(r0, r1)
        L_0x004d:
            r9.b(r0)
            goto L_0x00bf
        L_0x0051:
            float r0 = r9.getX()
            float r9 = r9.getY()
            int r4 = r8.getWidth()
            int r5 = r8.getHeight()
            int r4 = java.lang.Math.min(r4, r5)
            float r4 = (float) r4
            r5 = 1094713344(0x41400000, float:12.0)
            float r4 = r4 / r5
            java.util.ArrayList r5 = r8.f1923c
            java.util.Iterator r5 = r5.iterator()
        L_0x006f:
            boolean r6 = r5.hasNext()
            if (r6 == 0) goto L_0x009c
            java.lang.Object r6 = r5.next()
            O0.a r6 = (O0.a) r6
            java.lang.Object r7 = r6.f483a
            java.lang.Number r7 = (java.lang.Number) r7
            float r7 = r7.floatValue()
            java.lang.Object r6 = r6.b
            java.lang.Number r6 = (java.lang.Number) r6
            float r6 = r6.floatValue()
            float r7 = r0 - r7
            float r6 = r9 - r6
            float r7 = r7 * r7
            float r6 = r6 * r6
            float r6 = r6 + r7
            float r7 = r4 * r4
            int r6 = (r6 > r7 ? 1 : (r6 == r7 ? 0 : -1))
            if (r6 > 0) goto L_0x0099
            goto L_0x009d
        L_0x0099:
            int r3 = r3 + 1
            goto L_0x006f
        L_0x009c:
            r3 = -1
        L_0x009d:
            java.lang.Integer r9 = java.lang.Integer.valueOf(r3)
            if (r3 < 0) goto L_0x00a4
            goto L_0x00a5
        L_0x00a4:
            r9 = 0
        L_0x00a5:
            if (r9 == 0) goto L_0x00bc
            int r9 = r9.intValue()
            java.lang.Integer r0 = java.lang.Integer.valueOf(r9)
            boolean r0 = r1.contains(r0)
            if (r0 != 0) goto L_0x00bc
            java.lang.Integer r9 = java.lang.Integer.valueOf(r9)
            r1.add(r9)
        L_0x00bc:
            r8.invalidate()
        L_0x00bf:
            return r2
        */
        throw new UnsupportedOperationException("Method not decompiled: com.nctf.hookmysecret.ui.widget.PatternLockView.onTouchEvent(android.view.MotionEvent):boolean");
    }

    public final void setOnPatternComplete(l lVar) {
        this.f1922a = lVar;
    }
}

由于不允许重复和不存在自动补点,再根据安卓九宫格图形锁节点个数通常在 4~9 之间,写脚本爆破一下下面这个排列组合即可A94+A95+A96+...+A99A_9^4+A_9^5+A_9^6+...+A_9^9

import hashlib
import itertools

target = "4a6bc34076c8eef0f9eac59ad30d99bb4f56ecea4b0bfab92540fb655ac680f3"

nums = "012345678"

found = False

for length in range(4, 10):
    for perm in itertools.permutations(nums, length):

        s = ",".join(perm)

        h = hashlib.sha256(s.encode()).hexdigest()

        if h == target:
            print("FOUND:", s)
            found = True
            break

    if found:
        break

'''
输出内容:
FOUND: 0,1,2,4,8
'''

九宫格画法:

┌───┬───┬───┐
│ 0 → 1 → 2 │
├───┼───┼───┤
│   │ 4 │   │
├───┼───┼───┤
│   │   │ 8 │
└───┴───┴───┘

F2:hook

还可以写个 frida 脚本,拦截 SHA-256 校验值并修改为目标值,这样随意画一个图案就能通过校验了

Java.perform(function () {

    var MessageDigest = Java.use("java.security.MessageDigest");

    // 正确的 SHA256
    var targetHex =
        "4a6bc34076c8eef0f9eac59ad30d99bb4f56ecea4b0bfab92540fb655ac680f3";

    // hex -> byte[]
    function hexToBytes(hex) {

        var result = [];

        for (var i = 0; i < hex.length; i += 2) {

            var value = parseInt(hex.substr(i, 2), 16);

            // Java byte 是有符号的
            if (value > 127) {
                value -= 256;
            }

            result.push(value);
        }

        return result;
    }

    var fakeDigest = Java.array(
        'byte',
        hexToBytes(targetHex)
    );

    // Hook digest(byte[])
    MessageDigest.digest.overload('[B').implementation = function (input) {

        var algo = "";

        try {
            algo = this.getAlgorithm();
        } catch (e) {}

        // 只伪造 SHA-256
        if (algo.indexOf("SHA-256") >= 0) {

            console.log("[+] Fake SHA256 returned");

            return fakeDigest;
        }

        // 其他算法正常执行
        return this.digest(input);
    };

    console.log("[*] SHA256 bypass loaded");
    console.log("[*] Draw any pattern");

});

hook 成功

Stage 2

ai 写的 frida 脚本没成功,只能逆向加密算法了

第二层就是输入密码了,定位到对应的类 com.nctf.hookmysecret.ui.Stage2Activity

package com.nctf.hookmysecret.ui;

import C.h;
import N0.d;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.widget.TextView;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.textfield.TextInputEditText;
import com.nctf.hookmysecret.R;
import e.AbstractActivityC0110h;

/* loaded from: classes.dex */
public final class Stage2Activity extends AbstractActivityC0110h {

    /* renamed from: y  reason: collision with root package name */
    public static final /* synthetic */ int f1917y = 0;

    /* renamed from: x  reason: collision with root package name */
    public h f1918x;

    @Override // e.AbstractActivityC0110h, androidx.activity.k, z.f, android.app.Activity
    public final void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.activity_stage2);
        h hVar = new h(this);
        this.f1918x = hVar;
        if (!((SharedPreferences) hVar.b).getBoolean("stage1Passed", false)) {
            finish();
            return;
        }
        TextView textView = (TextView) findViewById(R.id.stage2StatusText);
        textView.setText("Native encryptor ready.");
        ((MaterialButton) findViewById(R.id.stage2SubmitButton)).setOnClickListener(new d((TextInputEditText) findViewById(R.id.stage2Input), this, textView, 0));
    }
}

这个类仅检查了第一层是否通过,真正的校验逻辑在 N0.d 中,跟进分析

package N0;

import C.h;
import K0.a;
import P0.g;
import W0.c;
import Z0.b;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.SystemClock;
import android.text.Editable;
import android.util.Base64;
import android.view.View;
import android.widget.TextView;
import com.google.android.material.textfield.TextInputEditText;
import com.nctf.hookmysecret.nativebridge.NativeBridge;
import com.nctf.hookmysecret.ui.Stage2Activity;
import com.nctf.hookmysecret.ui.Stage3Activity;
import com.nctf.hookmysecret.ui.SuccessActivity;
import e.AbstractActivityC0110h;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.NoSuchElementException;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/* loaded from: classes.dex */
public final /* synthetic */ class d implements View.OnClickListener {

    /* renamed from: a  reason: collision with root package name */
    public final /* synthetic */ int f480a;
    public final /* synthetic */ TextInputEditText b;

    /* renamed from: c  reason: collision with root package name */
    public final /* synthetic */ TextView f481c;

    /* renamed from: d  reason: collision with root package name */
    public final /* synthetic */ AbstractActivityC0110h f482d;

    public /* synthetic */ d(TextInputEditText textInputEditText, AbstractActivityC0110h hVar, TextView textView, int i2) {
        this.f480a = i2;
        this.b = textInputEditText;
        this.f482d = hVar;
        this.f481c = textView;
    }

    /* JADX WARN: Multi-variable type inference failed */
    /* JADX WARN: Type inference failed for: r7v0, types: [java.lang.Throwable] */
    @Override // android.view.View.OnClickListener
    public final void onClick(View view) {
        String str;
        StringBuilder sb;
        String str2;
        String str3;
        byte[] bArr;
        TextView textView = this.f481c;
        int i2 = 16;
        boolean z2 = false;
        String str4 = "";
        th = 0;
        TextInputEditText textInputEditText = this.b;
        AbstractActivityC0110h hVar = this.f482d;
        switch (this.f480a) {
            case 0:
                int i3 = Stage2Activity.f1917y;
                Stage2Activity stage2Activity = (Stage2Activity) hVar;
                c.e(stage2Activity, "this$0");
                Editable text = textInputEditText.getText();
                if (text != null) {
                    str = text.toString();
                } else {
                    str = th;
                }
                if (str != null) {
                    str4 = str;
                }
                h hVar2 = stage2Activity.f1918x;
                if (hVar2 != null) {
                    hVar2.z(str4);
                    long elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
                    int[] encryptStage2 = NativeBridge.f1911a.encryptStage2(str4);
                    int[] iArr = a.f457a;
                    if (encryptStage2.length == 16) {
                        int i4 = 0;
                        while (true) {
                            if (i4 >= i2) {
                                z2 = true;
                            } else if (encryptStage2[i4] == iArr[i4]) {
                                i4++;
                                i2 = 16;
                            }
                        }
                    }
                    long elapsedRealtimeNanos2 = (SystemClock.elapsedRealtimeNanos() - elapsedRealtimeNanos) / ((long) 1000);
                    if (z2) {
                        sb = new StringBuilder("Stage 2 accepted in ");
                    } else {
                        sb = new StringBuilder("Stage 2 rejected in ");
                    }
                    sb.append(elapsedRealtimeNanos2);
                    sb.append("us.");
                    textView.setText(sb.toString());
                    if (z2) {
                        h hVar3 = stage2Activity.f1918x;
                        if (hVar3 != null) {
                            ((SharedPreferences) hVar3.b).edit().putBoolean("stage2Passed", true).apply();
                            h hVar4 = stage2Activity.f1918x;
                            if (hVar4 != null) {
                                String obj = b1.h.L(str4).toString();
                                c.e(obj, "value");
                                ((SharedPreferences) hVar4.b).edit().putString("stage2Key", obj).apply();
                                stage2Activity.startActivity(new Intent(stage2Activity, Stage3Activity.class));
                                stage2Activity.finish();
                                return;
                            }
                            c.g("stateManager");
                            throw th;
                        }
                        c.g("stateManager");
                        throw th;
                    }
                    return;
                }
                c.g("stateManager");
                throw th;
            default:
                int i5 = Stage3Activity.f1919z;
                Stage3Activity stage3Activity = (Stage3Activity) hVar;
                c.e(stage3Activity, "this$0");
                Editable text2 = textInputEditText.getText();
                if (text2 != null) {
                    str2 = text2.toString();
                } else {
                    str2 = th;
                }
                if (str2 == null) {
                    str2 = str4;
                }
                h hVar5 = stage3Activity.f1920x;
                if (hVar5 != null) {
                    hVar5.z(str2);
                    L0.a aVar = stage3Activity.f1921y;
                    if (aVar != null) {
                        Context context = aVar.f475a;
                        c.e(context, "context");
                        SharedPreferences sharedPreferences = context.getSharedPreferences("challenge_state", 0);
                        c.d(sharedPreferences, "getSharedPreferences(...)");
                        String string = sharedPreferences.getString("stage2Key", str4);
                        if (string == null) {
                            string = str4;
                        }
                        if (string.length() != 0) {
                            Z0.a aVar2 = new Z0.a(0, string.length() - 1, 1);
                            if (!(aVar2 instanceof Collection) || !((Collection) aVar2).isEmpty()) {
                                Iterator it = aVar2.iterator();
                                while (true) {
                                    if (((b) it).f603c) {
                                        b bVar = (b) it;
                                        int i6 = bVar.f604d;
                                        if (i6 != bVar.b) {
                                            bVar.f604d = bVar.f602a + i6;
                                        } else if (bVar.f603c) {
                                            bVar.f603c = false;
                                        } else {
                                            throw new NoSuchElementException();
                                        }
                                        char charAt = string.charAt(i6);
                                        if (!Character.isWhitespace(charAt) && !Character.isSpaceChar(charAt)) {
                                            Cursor rawQuery = new M0.a(context).getReadableDatabase().rawQuery("SELECT v FROM app_cfg WHERE k = ?", new String[]{"stage3_iv"});
                                            try {
                                                if (!rawQuery.moveToFirst()) {
                                                    A.c.p(rawQuery, th);
                                                    bArr = th;
                                                } else {
                                                    bArr = Base64.decode(rawQuery.getString(0), 0);
                                                }
                                                if (bArr != null) {
                                                    byte[] bytes = b1.h.J(b1.h.L(string).toString(), "-", str4).getBytes(b1.a.f1342a);
                                                    c.d(bytes, "getBytes(...)");
                                                    Integer[] numArr = {16, 24, 32};
                                                    LinkedHashSet linkedHashSet = new LinkedHashSet(g.J0(3));
                                                    for (int i7 = 0; i7 < 3; i7++) {
                                                        linkedHashSet.add(numArr[i7]);
                                                    }
                                                    if (!linkedHashSet.contains(Integer.valueOf(bytes.length))) {
                                                        bytes = th;
                                                    }
                                                    if (bytes != null) {
                                                        Cipher instance = Cipher.getInstance("AES/CBC/PKCS5Padding");
                                                        instance.init(1, new SecretKeySpec(bytes, "AES"), new IvParameterSpec(bArr));
                                                        byte[] bytes2 = str2.getBytes(b1.a.f1342a);
                                                        c.d(bytes2, "getBytes(...)");
                                                        z2 = c.a(Base64.encodeToString(instance.doFinal(bytes2), 2), "jSaMnziall55Tdr+IZc7EKUNm/N4uwrZw1QFPw6DuirfYFJZg88j6GKLhWfNljAB");
                                                    }
                                                }
                                            } catch (Throwable th) {
                                                try {
                                                    throw th;
                                                } finally {
                                                    A.c.p(rawQuery, th);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        if (z2) {
                            str3 = "Final verification accepted.";
                        } else {
                            str3 = "Cipher mismatch.";
                        }
                        textView.setText(str3);
                        if (z2) {
                            h hVar6 = stage3Activity.f1920x;
                            if (hVar6 != null) {
                                ((SharedPreferences) hVar6.b).edit().putBoolean("stage3Passed", true).apply();
                                stage3Activity.startActivity(new Intent(stage3Activity, SuccessActivity.class));
                                stage3Activity.finish();
                                return;
                            }
                            c.g("stateManager");
                            throw th;
                        }
                        return;
                    }
                    c.g("crypto");
                    throw th;
                }
                c.g("stateManager");
                throw th;
        }
    }
}

这段代码的逻辑很关键

  1. 接收用户输入
  2. 调用 native 加密用户输入,加密逻辑在 NativeBridge.f1911a.encryptStage2 中
int[] encryptStage2 = NativeBridge.f1911a.encryptStage2(str4);
  1. 校验结果,正确结果就在 K0.a.f457a 中,且长度为16
int[] iArr = a.f457a;
if (encryptStage2.length == 16) {
	int i4 = 0;
	while (true) {
		if (i4 >= i2) {
			z2 = true;
		} else if (encryptStage2[i4] == iArr[i4]) {
			i4++;
			i2 = 16;
		}
	}
}

最后还有个关键点,Stage 3 的 key 就是 Stage 2 输入的密码

查看 K0.a.f457a ,得到正确的加密结果

package K0;

/* loaded from: classes.dex */
public abstract class a {

    /* renamed from: a  reason: collision with root package name */
    public static final int[] f457a = {250, 113, 87, 185, 6, 125, 167, 156, 4, 0, 229, 239, 119, 155, 187, 95};
}

跟进 NativeBridge.f1911a.encryptStage2 ,查看加密逻辑

package com.nctf.hookmysecret.nativebridge;

/* loaded from: classes.dex */
public final class NativeBridge {

    /* renamed from: a  reason: collision with root package name */
    public static final NativeBridge f1911a = new Object();

    /* JADX WARN: Type inference failed for: r0v0, types: [com.nctf.hookmysecret.nativebridge.NativeBridge, java.lang.Object] */
    static {
        System.loadLibrary("hookmysecret");
    }

    public final native int[] encryptStage2(String str);
}

发现导入了 hookmysecret.so ,由于是 JNI 静态注册,函数名为 Java_包名_类名_方法名 ,也就是 Java_com_nctf_hookmysecret_nativebridge_NativeBridge_encryptStage2 ,解压 apk ,用 ida 分析 app-release.apk/app-release/lib/arm64-v8a/hookmysecret.so

(因为现代 Android 基本都运行 ARM64 ,因此选择看这个目录下的 so)

反汇编完成后直接把 Java_com_nctf_hookmysecret_nativebridge_NativeBridge_encryptStage2() 函数丢给ai,可以得出加密逻辑

拿上 K0.a.f457a 里面的密文就可以直接写脚本解密了

target = [
    250, 113, 87, 185,
    6, 125, 167, 156,
    4, 0, 229, 239,
    119, 155, 187, 95
]

def dfs(i, state, s):
    if i == 16:
        print("FOUND:", s)
        return True

    t = target[i]

    for c in range(32, 127):

        x = ((13 * i + 66) ^ state ^ c) & 0xff

        y = ((x >> 5) | ((x << 3) & 0xff)) & 0xff

        v = (y + i + 7 * state) & 0xff

        if v == t:

            sv = v if v < 128 else v - 256

            new_state = state + c + (sv ^ i)

            if dfs(i + 1, new_state, s + chr(c)):
                return True

    return False


dfs(0, 81, "")

'''
输出内容:
FOUND: k7Xm2Pq9Wv4N8bRt
'''

因此 Stage 3 的 key 就是 k7Xm2Pq9Wv4N8bRt

Stage 3

看来这一步就要输入 flag 了,提示也告诉我们需要 Stage 2 的 key ,还有从SQLite 中加载的一个值

查看对应的类 com.nctf.hookmysecret.ui.Stage3Activity ,发现和 com.nctf.hookmysecret.ui.Stage2Activity 差不多,都是校验上一层是否通过,真正的校验逻辑也还是在 N0.d 中,这里我们直接来看关键代码

Stage 3 的校验逻辑已经全部在图中了

  • 加密算法:AES-CBC(PKCS5Padding)+base64
  • 密钥:k7Xm2Pq9Wv4N8bRt
  • iv:来自数据库,最后直接搜索 stage3_iv 居然找到了

数据库中存储的iv也是 base64 编码,解码出来是 VerifyVector1234

直接解密!

终于拿到 flag :

NCTF{a680107e-a49b-43e1-915b-cedd25e7835a}