前言:
这是一道AHU-CTF比赛中的Java逆向题目分析。
题目概述
这道题目给出了一个Java程序,要求输入正确的flag。程序会对输入进行一系列变换和校验。
题目提示:“Maybe this transform is reversible” (也许这种转变是可逆的)
注:原代码中有一个注释掉的构造函数,里面有一句话:
这就是告诉我们,所有的变换都是可以反向推导的。
程序结构分析
主程序入口
首先看main方法,程序的基本逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public static void main(String[] var0) { Scanner var1 = new Scanner(System.in); System.out.print("Enter the flag: "); String var2 = var1.next(); if (var2.length() != 43) { System.out.println("Oops, wrong flag!"); } else { String var3 = var2.substring(0, length); String var4 = var2.substring(length, var2.length() - 1); String var5 = var2.substring(var2.length() - 1); if (var3.equals("flag{m") && var5.equals("}")) { System.out.println(var4); if (solve(var4)) { System.out.println("Congratulations, you got the flag!"); } else { System.out.println("Oops, wrong flag!"); } } else { System.out.println("Oops, wrong flag!"); } } }
|
关键信息:
length = (int)Math.pow(2.0, 3.0) - 2 = 6 (写死length为6,main方法开始时为此值)
- flag总长度为43
- flag格式:
flag{m + 36个字符 + }
- 核心校验在
solve()方法中
transform方法将36个字符的一维数组转换为6×6的二维数组:
1 2 3 4 5 6 7 8 9 10 11 12
| public static char[][] transform(char[] var0, int var1) { char[][] var2 = new char[var1][var1];
for(int i = 0; i < 36; ++i) { var2[i / 6][i % 6] = var0[i]; }
return var2; }
|
作用: 将输入的36个字符按顺序填充到6×6矩阵中。例如:
1 2 3 4 5 6
| 123456 789abc defghi jklmno pqrstu vwxyz1
|
solve方法:矩阵旋转
solve方法是核心校验逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| public static boolean solve(String var0) {
char[][] var1 = transform( var0.toCharArray(), length ); for(int var2 = 0; var2 <= 3; ++var2) { for(int var3 = 0; var3 < length - 2 * var2 - 1; ++var3) { char var4 = var1[var2][var2 + var3]; var1[var2][var2 + var3] = var1[length - 1 - var2 - var3][var2]; var1[length - 1 - var2 - var3][var2] = var1[length - 1 - var2][length - 1 - var2 - var3]; var1[length - 1 - var2][length - 1 - var2 - var3] = var1[var2 + var3][length - 1 - var2]; var1[var2 + var3][length - 1 - var2] = var4; } } String var10001 = encrypt(getArray(var1, 0, 5), 2); return "6]fRv37g6mp#uy2x0^^^rg^tr1cr0Nsoy_y0".equals( var10001 + encrypt(getArray(var1, 1, 4), 1) + encrypt(getArray(var1, 2, 3), 0) ); }
|
关键发现: 这个嵌套循环实际上是将矩阵顺时针旋转90度!
循环遍历的位置对:
- var2=0: (0,0) (0,1) (0,2) (0,3) (0,4)
- var2=1: (1,0) (1,1) (1,2)
- var2=2: (2,0)
- var2=3: 结束
getArray方法:提取行并缝合
getArray方法从二维数组中提取两行并拼接:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public static char[] getArray(char[][] var0, int zero, int five) {
char[] var3 = new char[length * 2];
int var4 = 0;
int var5; for(var5 = 0; var5 < length; ++var5) { var3[var4] = var0[zero][var5]; ++var4; }
for(var5 = 0; var5 < length; ++var5) { var3[var4] = var0[five][length - 1 - var5]; ++var4; }
for(int x=0;x<=5;x++){ System.out.print(" "+var3[x]); }
return var3; }
|
作用: 这个方法很有意思,我称之为"缝合怪"制造器。
getArray(var1, 0, 5) → 第1行 + 第6行逆序 = 12个字符的缝合怪
getArray(var1, 1, 4) → 第2行 + 第5行逆序 = 12个字符的缝合怪
getArray(var1, 2, 3) → 第3行 + 第4行逆序 = 12个字符的缝合怪
encrypt方法:加密
encrypt方法对12个字符进行两步变换:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public static String encrypt(char[] var0, int var1) { char[] var2 = new char[12]; int var3 = 5; int var4 = 6; int var5; for(var5 = 0; var5 < 12; ++var5) { var2[var5] = var0[var3--]; var2[var5 + 1] = var0[var4++]; ++var5; } for(var5 = 0; var5 < 12; ++var5) { var2[var5] ^= (char)var1; } return String.valueOf(var2); }
|
交错重排示例:
- 输入:
123456 789abc(前6个+后6个)
- 输出:
67 58 49 3a 2b 1c(前半逆序,后半正序,交错拼接)
异或加密: 每个字符与参数var1进行异或运算。这个符号^的意思是:相同得0不同得1。
解题思路
好了,现在来看看怎么逆向求解。最终的校验条件是:
1 2 3 4 5
| "6]fRv37g6mp#uy2x0^^^rg^tr1cr0Nsoy_y0".equals( encrypt(getArray(var1, 0, 5), 2) + encrypt(getArray(var1, 1, 4), 1) + encrypt(getArray(var1, 2, 3), 0) )
|
目标字符串被分成三段,每段12个字符:
6]fRv37g6mp# ← encrypt(…, 2)
uy2x0^^^rg^t ← encrypt(…, 1)
r1cr0Nsoy_y0 ← encrypt(…, 0)
逆向步骤:
根据题目提示"Maybe this transform is reversible"(也许这种转变是可逆的),我们可以这样做:
- 对三段密文分别解密(逆向
encrypt过程):先异或回去,再把交错的缝合怪拆开
- 还原旋转前的6×6矩阵(逆时针旋转90度,或者顺时针转3次也行)
- 将矩阵转回36个字符的字符串
- 拼接得到完整flag:
flag{m + 36字符 + }
关键在于每一步都是可逆的:
- encrypt的异或可以再异或一次还原
- 交错重排可以按照相反的规则拆开
- 矩阵旋转可以反向旋转
完整源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| import java.util.Scanner;
public class code { private static final int length = (int)Math.pow(2.0, 3.0) - 2;
public static void main(String[] var0) { Scanner var1 = new Scanner(System.in); System.out.print("Enter the flag: "); String var2 = var1.next(); if (var2.length() != 43) { System.out.println("Oops, wrong flag!"); } else { String var3 = var2.substring(0, length); String var4 = var2.substring(length, var2.length() - 1); String var5 = var2.substring(var2.length() - 1); if (var3.equals("flag{m") && var5.equals("}")) { if (solve(var4)) { System.out.println("Congratulations, you got the flag!"); } else { System.out.println("Oops, wrong flag!"); } } else { System.out.println("Oops, wrong flag!"); } } }
public static boolean solve(String var0) { char[][] var1 = transform(var0.toCharArray(), length); for(int var2 = 0; var2 <= 3; ++var2) { for(int var3 = 0; var3 < length - 2 * var2 - 1; ++var3) { char var4 = var1[var2][var2 + var3]; var1[var2][var2 + var3] = var1[length - 1 - var2 - var3][var2]; var1[length - 1 - var2 - var3][var2] = var1[length - 1 - var2][length - 1 - var2 - var3]; var1[length - 1 - var2][length - 1 - var2 - var3] = var1[var2 + var3][length - 1 - var2]; var1[var2 + var3][length - 1 - var2] = var4; } } String var10001 = encrypt(getArray(var1, 0, 5), 2); return "6]fRv37g6mp#uy2x0^^^rg^tr1cr0Nsoy_y0".equals( var10001 + encrypt(getArray(var1, 1, 4), 1) + encrypt(getArray(var1, 2, 3), 0) ); }
public static String encrypt(char[] var0, int var1) { char[] var2 = new char[12]; int var3 = 5; int var4 = 6; int var5; for(var5 = 0; var5 < 12; ++var5) { var2[var5] = var0[var3--]; var2[var5 + 1] = var0[var4++]; ++var5; } for(var5 = 0; var5 < 12; ++var5) { var2[var5] ^= (char)var1; } return String.valueOf(var2); }
public static char[][] transform(char[] var0, int var1) { char[][] var2 = new char[var1][var1]; for(int i = 0; i < 36; ++i) { var2[i / 6][i % 6] = var0[i]; } return var2; }
public static char[] getArray(char[][] var0, int zero, int five) { char[] var3 = new char[length * 2]; int var4 = 0; int var5; for(var5 = 0; var5 < length; ++var5) { var3[var4] = var0[zero][var5]; ++var4; } for(var5 = 0; var5 < length; ++var5) { var3[var4] = var0[five][length - 1 - var5]; ++var4; } return var3; } }
|
总结
这道题目的核心是理解三个变换:
- 矩阵转换:一维→二维(把36个字符的一维数组变成6×6的碎片数组)
- 矩阵旋转:顺时针90度(这个嵌套循环就是干这个的)
- 加密变换:交错重排 + 异或(制造缝合怪,然后按位加密)
解题的关键在于逆向这些操作,从最终的密文推导出原始输入。好在题目都提示了"Maybe this transform is reversible",所以每一步都是可逆的,只要耐心反推就能解出来。