题目是个 .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 之间,写脚本爆破一下下面这个排列组合即可
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;
}
}
}
这段代码的逻辑很关键
- 接收用户输入
- 调用
native加密用户输入,加密逻辑在NativeBridge.f1911a.encryptStage2中
int[] encryptStage2 = NativeBridge.f1911a.encryptStage2(str4);
- 校验结果,正确结果就在
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}
