Windows初级题通过调试获取flag,Android初级题通过Base64解码和XXTEA解密获取flag,Android中级题通过动态调试和Hook获取密钥流,与密文异或得到flag。
AI 摘要

Windows初级题

先判断flag长度为27

搜索Success,在附近下断点,输入全1,直接调试即可得到flag

x32dbg调试

fl@g{52pOj1E_2025#Fighting}

Android初级题

拖入JADX

在FoldFragment2类中看到三个Base64编码的字符串:

"2hyWtSLN69+QWLHQ"
"hjyaQ8jNSdp+mZic7Kdtyw=="
"cYoiUd2BfEDc/V9e4LdciBz9Mzwzs3yr0kgrLA=="

在TO类可以看到db方法,即Base64解码后应用XXTEA解密,密钥为my-xxtea-secret填充至16字节

粘贴到IDE里运行即可

运行后可以知道这三个字符串分别对应为:

flag{
xnkl2025!}
快去寻找flag吧!

所以flag为

flag{xnkl2025!}

EXP:

import java.util.Base64;
import java.nio.charset.StandardCharsets;
public class Main {
    private static final String YYLX = "my-xxtea-secret";
    public static final byte[] de(byte[] data, byte[] key) {
        return toByteArray(de(toIntArray(data, false), toIntArray(fK(key), false)), true);
    }
    private static int[] de(int[] iArr, int[] iArr2) {
        int length = iArr.length;
        int i = length - 1;
        if (i < 1) {
            return iArr;
        }
        int i2 = iArr[0];
        for (int i3 = ((52 / length) + 6) * (-1640531527); i3 != 0; i3 -= -1640531527) {
            int i4 = (i3 >>> 2) & 3;
            for (int i5 = i; i5 > 0; i5--) {
                int i6 = iArr[i5 - 1];
                i2 = iArr[i5] - (((i2 ^ i3) + (i6 ^ iArr2[(i5 & 3) ^ i4])) ^ (((i6 >>> 5) ^ (i2 << 2)) + ((i2 >>> 3) ^ (i6 << 4))));
                iArr[i5] = i2;
            }
            int i7 = iArr[i];
            i2 = iArr[0] - (((i2 ^ i3) + (iArr2[i4] ^ i7)) ^ (((i7 >>> 5) ^ (i2 << 2)) + ((i2 >>> 3) ^ (i7 << 4))));
            iArr[0] = i2;
        }
        return iArr;
    }
    private static int[] toIntArray(byte[] bArr, boolean z) {
        int length = (bArr.length + 3) / 4;
        int[] iArr = new int[length + (z ? 1 : 0)];
        int length2 = bArr.length;
        for (int i = 0; i < length2; i++) {
            int i2 = i / 4;
            iArr[i2] = iArr[i2] | ((bArr[i] & 0xFF) << ((i % 4) * 8));
        }
        if (z) {
            iArr[length] = bArr.length;
        }
        return iArr;
    }
    private static byte[] toByteArray(int[] iArr, boolean z) {
        int length = iArr.length * 4;
        if (z) {
            length = iArr[iArr.length - 1];
        }
        byte[] bArr = new byte[length];
        for (int i = 0; i < length; i++) {
            bArr[i] = (byte) ((iArr[i / 4] >> ((i % 4) * 8)) & 255);
        }
        return bArr;
    }
    private static byte[] fK(byte[] bArr) {
        byte[] bArr2 = new byte[16];
        System.arraycopy(bArr, 0, bArr2, 0, Math.min(bArr.length, 16));
        return bArr2;
    }
    public static void main(String[] args) {
        String v1 = "2hyWtSLN69+QWLHQ";
        String v2 = "hjyaQ8jNSdp+mZic7Kdtyw==";
        byte[] decode1 = Base64.getDecoder().decode(v1);
        byte[] decode2 = Base64.getDecoder().decode(v2);
        byte[] bytes = YYLX.getBytes(StandardCharsets.UTF_8);
        byte[] flag1 = de(decode1,bytes);
        byte[] flag2 = de(decode2,bytes);
        String flag1_ = new String(flag1, StandardCharsets.UTF_8);
        String flag2_ = new String(flag2, StandardCharsets.UTF_8);
        System.out.println(flag1_ + flag2_);//flag{xnkl2025!}
    }
}

Android中级题

拖入JADX

可以看到check逻辑在so

JADX

JADX

拖入ida查看so

搜索java没有,那就是动态注册了

动态注册Check

进去可以发现有一些检测调试的

Check函数

但没细看,只关注了检测的结果导致v15是a还是ao

v15的地址不能确定, 直接用Unidbg断在调用v15的那里即可

emulator.attach().addBreakPoint(md.base+0xe8f44); // 0xe8f44为指令BLR X22的偏移量, 断下后看X22寄存器减掉基址就行

最后可以发现调用函数为ao (0xe9f60) (后面发现是没过掉检测所以走的ao,实际上正确应该走的a)

// a2就是输入的字符串, a3为19, result为最后结果
void __fastcall ao(__int128 *a1, const char *a2, __int64 a3, _QWORD *result)
{
  __int64 i; // x23
  __int128 v8; // [xsp+0h] [xbp-20h] BYREF
  __int64 v9; // [xsp+18h] [xbp-8h]

  v9 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40); // 没用
  v8 = *a1; // 传入a1为定值, 故v8也为定值
  if ( a3 )
  {
    for ( i = 0LL; i != a3; ++i )  // 循环19次, 侧面说明Flag长度为19
    {
      if ( (i & 0xF) == 0 )
        sub_E9954(&v8);  // 这里的这个函数其实不用跟进去看...... (和输入啥的都没关系, v8一开始就是定值, v8更新的次数也固定)
      *((_BYTE *)result + i) = *(_BYTE *)((unsigned __int64)&v8 | i & 0xF) ^ a2[i]; // 实际上做的事情 ("^"左侧其实都是确定的值, 不随输入的改变而改变)
    }
  }
}

sub_E9954就是白盒AES,所以总得来说加密过程其实只是一个异或

用unidbg输入0x13个字符串1,HOOK出扩展密钥流

 emulator.getBackend().hook_add_new(new CodeHook() {
            @Override
            public void hook(Backend backend, long address, int size, Object user) {
                if (address == module.base + 0xe9fb8) {  // 0xe9fb8就是刚才说的异或指令的偏移
                    // 输出 W8, chr(a2[i])
                    System.out.printf("0x%1$x, %2$c", emulator.getBackend().reg_read(Unicorn.UC_ARM64_REG_W8).intValue(), (char) emulator.getBackend().reg_read(Unicorn.UC_ARM64_REG_W9).intValue());
                    System.out.println();
                }
            }

            @Override
            public void onAttach(UnHook unHook) {}
            @Override
            public void detach() {}
        }, module.base, module.base + module.size, null);
[0x2e, 0x4b, 0xee, 0xc8, 0xe0, 0x95, 0x88, 0x47, 0xb0, 0x72, 0x1b, 0x68, 0x40, 0xd0, 0xa, 0x84, 0x27, 0xaf, 0xf3]

再和密文异或得到flag

key = bytes(bytes.fromhex('2e4beec8e0958847b0721b6840d00a8427aff3'))
enc = bytes(bytes.fromhex('48278faf9bf8ec729807720c6be23ab64259f7')
flag = bytes([a^b for a, b in zip(key, enc)])
print(flag)

后面的题因为电脑突然坏了 没时间看了😭(一修修了一星期)

文章目录