0xGame2025Week3WriteUp
0xGame2025 Week3 Reverse方向全解&详解
easyApp
考点:apk逆向、工具应用、base64编码解码、z3求解
附件程序是个apk,在模拟器中运行如图,推测按钮会触发flag检验逻辑:

用JEB打开apk,找到MainActivity并右键解析后,得到JAVA代码:
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
85package com.example.easyapp;
import android.content.Context;
import android.os.Bundle;
import android.util.Base64;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.activity.ComponentActivity;
import dalvik.system.DexClassLoader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import kotlin.Metadata;
import kotlin.collections.ArraysKt;
import kotlin.io.ByteStreamsKt;
import kotlin.jvm.functions.Function1;
import kotlin.jvm.internal.Intrinsics;
import kotlin.text.Charsets;
import kotlin.text.StringsKt;
@Metadata(d1 = {"\u0000\"\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u000E\n\u0002\u0010\u0012\n\u0000\b\u0007\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0012\u0010\u0003\u001A\u00020\u00042\b\u0010\u0005\u001A\u0004\u0018\u00010\u0006H\u0014J\f\u0010\u0007\u001A\u00020\b*\u00020\tH\u0002¨\u0006\n"}, d2 = {"Lcom/example/easyapp/MainActivity;", "Landroidx/activity/ComponentActivity;", "()V", "onCreate", "", "savedInstanceState", "Landroid/os/Bundle;", "toHexString", "", "", "app_release"}, k = 1, mv = {1, 9, 0}, xi = 0x30)
public final class MainActivity extends ComponentActivity {
public static final int $stable;
static {
}
@Override // androidx.activity.ComponentActivity
protected void onCreate(Bundle arg3) {
super.onCreate(arg3);
this.setContentView(layout.activity_main);
EditText v3 = (EditText)this.findViewById(id.flagInput);
((Button)this.findViewById(id.button)).setOnClickListener((View arg2) -> MainActivity.onCreate$lambda$0(v3, this, arg2)); // 这里将Click和onCreate$lambda$0绑定起来了
}
private static final void onCreate$lambda$0(EditText arg6, MainActivity arg7, View arg8) {
boolean v6_4; // v8_1的返回值流向v6_4
Intrinsics.checkNotNullParameter(arg7, "this$0");
String v6 = arg6.getText().toString(); // v6存放了输入的flag
File v8 = new File(arg7.getCacheDir(), "dex.zip");
InputStream v0 = arg7.getAssets().open("dex.zip"); // 打开了asset/dex.zip
Intrinsics.checkNotNullExpressionValue(v0, "open(...)");
ByteStreamsKt.copyTo$default(v0, ((OutputStream)new FileOutputStream(v8)), 0, 2, null);
if(v6.length() == 42) {
Method v8_1 = new DexClassLoader(v8.getAbsolutePath(), arg7.getCacheDir().getAbsolutePath(), null, arg7.getClassLoader()).loadClass("com.example.easyapp.Secret").getDeclaredMethod("check", String.class); // v8指向了dex中的com.example.easyapp.Secret的check方法
String v1 = v6.substring(0, 26); // flag被截断,截断的部分流向v1
Intrinsics.checkNotNullExpressionValue(v1, "this as java.lang.String…ing(startIndex, endIndex)");
byte[] v1_1 = v1.getBytes(Charsets.UTF_8); // flag截断部分流向v1_1
Intrinsics.checkNotNullExpressionValue(((Object)v1_1), "this as java.lang.String).getBytes(charset)");
String v1_2 = Base64.encodeToString(v1_1, 0); // flag截断部分被进行了base编码,流向v1_2
Intrinsics.checkNotNullExpressionValue(v1_2, "encodeToString(...)");
String v1_3 = StringsKt.trim(((CharSequence)v1_2)).toString(); // 编码后的flag截断部分流向v1_3
String v6_1 = v6.substring(26); // flag的剩余部分流向了v6_1
Intrinsics.checkNotNullExpressionValue(v6_1, "this as java.lang.String).substring(startIndex)");
byte[] v6_2 = v6_1.getBytes(Charsets.UTF_8); // flag的剩余部分从v6_1流向了v6_2
Intrinsics.checkNotNullExpressionValue(((Object)v6_2), "this as java.lang.String).getBytes(charset)");
if(v1_3.equals("MHhHYW1le0RvX3kwdV9sMHYzX2FuZHIwMWQ=")) { // 此处为v1_3的检验逻辑
Object v6_3 = v8_1.invoke(null, arg7.toHexString(v6_2)); // v6_2经过toHexString处理后作为v8_1,即dex:com.example.easyapp.Secret.check的参数
Intrinsics.checkNotNull(v6_3, "null cannot be cast to non-null type kotlin.Boolean");
v6_4 = ((Boolean)v6_3).booleanValue(); // v8_1的返回值流向v6_4
}
else {
v6_4 = false;
}
if(v6_4) { // v6_4决定了最后的输出:Right/Oh no no
Toast.makeText(((Context)arg7), "Right!", 0).show();
return;
}
Toast.makeText(((Context)arg7), "Oh no no", 0).show();
return;
}
Toast.makeText(((Context)arg7), "Length wrong!", 0).show();
}
private final String toHexString(byte[] arg11) {
return ArraysKt.joinToString$default(arg11, "", null, null, 0, null, ((Function1)MainActivity.toHexString.1.INSTANCE), 30, null);
}
}可以看到,flag被拆分成两部分进行检验,前26个字符经过base64编码后与”MHhHYW1le0RvX3kwdV9sMHYzX2FuZHIwMWQ=”比对,剩下部分由dex中的com.example.easyapp.Secret.check方法检验。第一部分可以借助CyberChef解密:

现在来分析flag第二部分的检验。在JEB左上角工程浏览器中找到asset/dex.zip/dex.bin

Secret类解析后得到:

其中check的逻辑等价为一个三元一次大数方程组,三个元从flag的第二部分截取:
v0 = substring(flagPart2[0:16])
v3 = substring(flagPart2[8:24])
v4 = substring(flagPart2:[16:32])v3 + 3 * v0 - 27454419028250566601 = 0
2 * v4 - 5 * v3 + 20616666104378640363 = 0
v0 + 4 * v4 - 0x1dce62be9f0fa2f6c = 0可以使用z3求解。解密脚本:
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
62from z3 import *
"""
v0 v3 v4分别存放[0:16],[8:24],[16:32]下标的字符,BigInteger将这些字符转成十六进制数,每个十六进制数4位,所以每个变量是64位
需要关注两两变量之间有32位的重叠
所以可以将三个64位变量分成4个32位变量来处理
"""
# 定义4个32位变量
u=BitVec('u',32) # v0的高32位
v=BitVec('v',32) # v0的低32位,也是v3的高32位
w=BitVec('w',32) # v3的低32位,也是v4的高32位
t=BitVec('t',32) # v4的低32位
# 通过拼接32位变量构造64位变量
v0=Concat(u,v) #v0=u<<32|v
v3=Concat(v,w) #v3=v<<32|w
v4=Concat(w,t) #v4=w<<32|t
#定义常量
A=BitVecVal(27454419028250566601,64)
B=BitVecVal(-20616666104378640363,64)
C=BitVecVal(0x1dce62be9f0fa2f6c,64)
#建立约束
s=Solver()
s.add(v3+3*v0==A)
s.add(2*v4-5*v3==B)
s.add(v0+4*v4==C)
if s.check()==unsat:
print("无解")
while s.check()==sat:
model=s.model()
u_val=model[u].as_long()
v_val=model[v].as_long()
w_val=model[w].as_long()
t_val=model[t].as_long()
print(f"found solution:")
print(f"u: {u_val:08x}")
print(f"v: {v_val:08x}")
print(f"w: {w_val:08x}")
print(f"t: {t_val:08x}")
# 将32位变量还原成字符
result=(u_val<<96)|(v_val<<64)|(w_val<<32)|t_val
hex_str=f"{result:032x}"
flag_bytes=bytes.fromhex(hex_str)
flag=flag_bytes.decode('utf-8')
print(f"flag: {flag}")
#排除已有的解
s.add(Or(u!=u_val,v!=v_val,w!=w_val,t!=t_val))
"""
found solution:
u: 5f346e64
v: 5f646578
w: 5f6c6f61
t: 6465727d
flag: _4nd_dex_loader}
"""
flag:0xGame{Do_y0u_l0v3_andr01d_4nd_dex_loader}
Minesweepr
考点:web逆向、js逆向
附件是html文件和js文件,推测是一个web逆向题。html打开是一个扫雷游戏网页。因为js主要用来处理网页交互,所以重点关注js文件的内容

网页按F12打开开发人员工具,可以在工具栏源代码模块看js脚本。搜索”flag”可以找到一个可疑的函数:

这里函数名和变量名都不是有意义的,不好阅读,可以双击高亮来追踪数据流。
分析可以发现,0x55ea57被调用了很多次,追踪这个值可以到达0x183c,这个函数负责从0xca50中加载值返回,这算是一个全局的混淆手段了


根据0x183c的代码可以找到0xca50的字符串表0x1193d5,表中就可以索引出密钥winModal:message2:”WebIsInteresting”了,以及密文”g\x1d%(\x1e,\x15@SA\x5cFD\x0fWJn]P}^}\x0c\x12\x07_]AGYC^o\x04\x00yA-ZGT\x16U\x0e”(0x183c的索引算法有点奇怪,好像索引不到正确的位置上,没搞懂)
而0x172ca4的解密逻辑可以等价如下:
1
2for i in range(len(enc)):
flag+=enc[i]^key[i%len(key)]因此可以得到解密脚本:
1
2
3
4
5
6enc="g\x1d%(\x1e,\x15@SA\x5cFD\x0fWJn]P}^}\x0c\x12\x07_]AGYC^o\x04\x00yA-ZGT\x16U\x0e"
key="WebIsInteresting"
flag=""
for i in range(len(enc)):
flag+=chr(ord(enc[i])^ord(key[i%len(key)]))
print(flag)
flag:0xGame{463950f9-9824-4bfb-8230-98ab02d431d0}
ezVBS
考点:VBS逆向、VBS去混淆
附件程序是个VBS文件,运行显示:

用记事本打开VBS会发现是一堆奇怪的字符,查了一下说是加了混淆

参考VBS代码混淆,可以使用去混淆脚本进行去混淆,使用方法见链接:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25Option Explicit
Function Defuscator(vbs)
Dim t
t = InStr(1, vbs, "Execute", 1)
t = Mid(vbs, t + Len("Execute"))
t = Eval(t)
Defuscator = t
End Function
Dim fso, i
Const ForReading = 1
Set fso = CreateObject("Scripting.FileSystemObject")
For i = 0 To WScript.Arguments.Count - 1
Dim FileName
FileName = WScript.Arguments(i)
Dim MyFile
Set MyFile = fso.OpenTextFile(FileName, ForReading)
Dim vbs
vbs = MyFile.ReadAll
WScript.Echo Defuscator(vbs)
MyFile.Close
Next
Set fso = Nothing然后可以得到如下的代码:
1
2
3
4
5
6
7
8
9
10Microsoft (R) Windows Script Host Version 5.812
版权所有(C) Microsoft Corporation。保留所有权利。
code = "Function l(str):Dim i,j,k,r:j=Len(str):r="""":For i=1 to j:k=Asc(Mid(str,i,1)):If k>=33 And k<=126 Then:r=r&Chr(33+((k+14)Mod 94)):Else:r=r&Chr(k):End If:Next:l=r:End Function:Execute l(""DEC l x?AFEq@IWQt?E6C J@FC >6DD286i QX i q2D6ec%23=6 l Q7Ie{&*d2Eh=?H>5b%3BKF#JZp:A(w!s@)+z|uvr'ax^"""";$C6tD9`g}y<8_Gfc~4qQ i 7=28 l QH2+2pJ}vsyhrH{7}5K*r?J&DpyE$>{&_HB}z>{*u?J%g:J#|:d&tp|w_52glQ i 6?4 l QQ i u@C : l ` %@ {6?WDECX $E6A b i :7 : Z a kl {6?WDECX %96? i 3:Eq=@4< l pD4W|:5WDEC[ :[ `XX Y ade Y ade Z pD4W|:5WDEC[ : Z `[ `XX Y ade Z pD4W|:5WDEC[ : Z a[ `XX i 4` l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - Wec Y ec Y ecXX |@5 ec Z `[ `X i 4a l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - Wec Y ecXX |@5 ec Z `[ `X i 4b l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - ecX |@5 ec Z `[ `X i 4c l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4<X |@5 ec Z `[ `X i 6?4 l 6?4 U 4` U 4a U 4b U 4c i t=D6 i x7 : Z ` kl {6?WDECX %96? i 3:Eq=@4< l pD4W|:5WDEC[ :[ `XX Y ade Y ade Z pD4W|:5WDEC[ : Z `[ `XX Y ade i 4` l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - Wec Y ec Y ecXX |@5 ec Z `[ `X i 4a l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - Wec Y ecXX |@5 ec Z `[ `X i 4b l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - ecX |@5 ec Z `[ `X i 6?4 l 6?4 U 4` U 4a U 4b U QlQ i t=D6 i 3:Eq=@4< l pD4W|:5WDEC[ :[ `XX Y ade Y ade i 4` l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - Wec Y ec Y ecXX |@5 ec Z `[ `X i 4a l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - Wec Y ecXX |@5 ec Z `[ `X i 6?4 l 6?4 U 4` U 4a U QlQ U QlQ i t?5 x7 i t?5 x7 i }6IE i xu 6?4 l 7=28 %96? i |D8q@I Q*@FC 7=28 :D 4@CC64EPQ i t=D6 i |D8q@I Q*@FC 7=28 :D :?4@CC64EPQ i t?5 x7"")"
Execute code
code = "Function l(str):Dim i,j,k,r:j=Len(str):r="""":For i=1 to j:k=Asc(Mid(str,i,1)):If k>=33 And k<=126 Then:r=r&Chr(33+((k+14)Mod 94)):Else:r=r&Chr(k):End If:Next:l=r:End Function:Execute l(""DEC l x?AFEq@IWQt?E6C J@FC 7=28i QX i q2D6ec%23=6 l QeHB)IvGyC%c~"""">}twp^h@fg'qZ`=+#s4EdAFa2(_5Du{Jr$86;z79&:x|*!<Kb?3Q i 7=28 l Q^^H5q'Z2CGvJ+(fdZyaE#:vz=(faCy28#^H$=xwE#GKE+_f$CvZB@zHa`'%2qxpJs^6Esgb&+AHF=:&6#'p2+AHD+zHJ`gr2=yaE#GKEq(@Eq'p9qg>{ZgwEq(fFq'f7Z^H8ZAH9`G27~BHu#'>9CGv7Cy28#'CEZ(;dZzH5q'""""Eq(f2=AH8#(fz#x%D#yp2=EllQ i 6?4 l QQ i u@C : l ` %@ {6?WDECX $E6A b i :7 : Z a kl {6?WDECX %96? i 3:Eq=@4< l pD4W|:5WDEC[ :[ `XX Y ade Y ade Z pD4W|:5WDEC[ : Z `[ `XX Y ade Z pD4W|:5WDEC[ : Z a[ `XX i 4` l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - Wec Y ec Y ecXX |@5 ec Z `[ `X i 4a l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - Wec Y ecXX |@5 ec Z `[ `X i 4b l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - ecX |@5 ec Z `[ `X i 4c l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4<X |@5 ec Z `[ `X i 6?4 l 6?4 U 4` U 4a U 4b U 4c i t=D6 i x7 : Z ` kl {6?WDECX %96? i 3:Eq=@4< l pD4W|:5WDEC[ :[ `XX Y ade Y ade Z pD4W|:5WDEC[ : Z `[ `XX Y ade i 4` l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - Wec Y ec Y ecXX |@5 ec Z `[ `X i 4a l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - Wec Y ecXX |@5 ec Z `[ `X i 4b l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - ecX |@5 ec Z `[ `X i 6?4 l 6?4 U 4` U 4a U 4b U QlQ i t=D6 i 3:Eq=@4< l pD4W|:5WDEC[ :[ `XX Y ade Y ade i 4` l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - Wec Y ec Y ecXX |@5 ec Z `[ `X i 4a l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - Wec Y ecXX |@5 ec Z `[ `X i 6?4 l 6?4 U 4` U 4a U QlQ U QlQ i t?5 x7 i t?5 x7 i }6IE i xu 6?4 l 7=28 %96? i |D8q@I DEC i t=D6 i |D8q@I Q*@FC 7=28 :D :?4@CC64EPQ i |D8q@I 6?4 i t?5 x7"")"
Set objFSO = CreateObject("Scripting.FileSystemObject")
strScriptPath = WScript.ScriptFullName
strTextToWrite = code
objFSO.OpenTextFile(strScriptPath, 2, True).WriteLine(strTextToWrite)可以看到code中有一些可读明文,后面又是一大串奇怪的字符。推测还有一层混淆,可读明文即为解密函数。让AI给出解密脚本:
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
26def l(encrypted_str):
"""VBScript l 函数的 Python 实现"""
result = ""
for i in range(len(encrypted_str)):
k = ord(encrypted_str[i])
if 33 <= k <= 126:
# 与 VBScript 相同的算法
result += chr(33 + ((k + 14) % 94))
else:
result += chr(k)
return result
# 你的加密字符串
encrypted_strings = [
"DEC l x?AFEq@IWQt?E6C J@FC >6DD286i QX i q2D6ec%23=6 l Q7Ie{&*d2Eh=?H>5b%3BKF#JZp:A(w!s@)+z|uvr'ax^\"\"\"\";$C6tD9`g}y<8_Gfc~4qQ i 7=28 l QH2+2pJ}vsyhrH{7}5K*r?J&DpyE$>{&_HB}z>{*u?J%g:J#|:d&tp|w_52glQ i 6?4 l QQ i u@C : l ` %@ {6?WDECX $E6A b i :7 : Z a kl {6?WDECX %96? i 3:Eq=@4< l pD4W|:5WDEC[ :[ `XX Y ade Y ade Z pD4W|:5WDEC[ : Z `[ `XX Y ade Z pD4W|:5WDEC[ : Z a[ `XX i 4` l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - Wec Y ec Y ecXX |@5 ec Z `[ `X i 4a l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - Wec Y ecXX |@5 ec Z `[ `X i 4b l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - ecX |@5 ec Z `[ `X i 4c l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4<X |@5 ec Z `[ `X i 6?4 l 6?4 U 4` U 4a U 4b U 4c i t=D6 i x7 : Z ` kl {6?WDECX %96? i 3:Eq=@4< l pD4W|:5WDEC[ :[ `XX Y ade Y ade Z pD4W|:5WDEC[ : Z `[ `XX Y ade i 4` l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - Wec Y ec Y ecXX |@5 ec Z `[ `X i 4a l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - Wec Y ecXX |@5 ec Z `[ `X i 4b l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - ecX |@5 ec Z `[ `X i 6?4 l 6?4 U 4` U 4a U 4b U QlQ i t=D6 i 3:Eq=@4< l pD4W|:5WDEC[ :[ `XX Y ade Y ade i 4` l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - Wec Y ec Y ecXX |@5 ec Z `[ `X i 4a l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - Wec Y ecXX |@5 ec Z `[ `X i 6?4 l 6?4 U 4` U 4a U QlQ U QlQ i t?5 x7 i t?5 x7 i }6IE i xu 6?4 l 7=28 %96? i |D8q@I Q*@FC 7=28 :D 4@CC64EPQ i t=D6 i |D8q@I Q*@FC 7=28 :D :?4@CC64EPQ i t?5 x7",
"DEC l x?AFEq@IWQt?E6C J@FC 7=28i QX i q2D6ec%23=6 l QeHB)IvGyC%c~\"\"\"\">}twp^h@fg'qZ`=+#s4EdAFa2(_5Du{Jr$86;z79&:x|*!<Kb?3Q i 7=28 l Q^^H5q'Z2CGvJ+(fdZyaE#:vz=(faCy28#^H$=xwE#GKE+_f$CvZB@zHa`'%2qxpJs^6Esgb&+AHF=:&6#'p2+AHD+zHJ`gr2=yaE#GKEq(@Eq'p9qg>{ZgwEq(fFq'f7Z^H8ZAH9`G27~BHu#'>9CGv7Cy28#'CEZ(;dZzH5q'\"\"\"\"Eq(f2=AH8#(fz#x%D#yp2=EllQ i 6?4 l QQ i u@C : l ` %@ {6?WDECX $E6A b i :7 : Z a kl {6?WDECX %96? i 3:Eq=@4< l pD4W|:5WDEC[ :[ `XX Y ade Y ade Z pD4W|:5WDEC[ : Z `[ `XX Y ade Z pD4W|:5WDEC[ : Z a[ `XX i 4` l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - Wec Y ec Y ecXX |@5 ec Z `[ `X i 4a l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - Wec Y ecXX |@5 ec Z `[ `X i 4b l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - ecX |@5 ec Z `[ `X i 4c l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4<X |@5 ec Z `[ `X i 6?4 l 6?4 U 4` U 4a U 4b U 4c i t=D6 i x7 : Z ` kl {6?WDECX %96? i 3:Eq=@4< l pD4W|:5WDEC[ :[ `XX Y ade Y ade Z pD4W|:5WDEC[ : Z `[ `XX Y ade i 4` l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - Wec Y ec Y ecXX |@5 ec Z `[ `X i 4a l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - Wec Y ecXX |@5 ec Z `[ `X i 4b l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - ecX |@5 ec Z `[ `X i 6?4 l 6?4 U 4` U 4a U 4b U QlQ i t=D6 i 3:Eq=@4< l pD4W|:5WDEC[ :[ `XX Y ade Y ade i 4` l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - Wec Y ec Y ecXX |@5 ec Z `[ `X i 4a l |:5Wq2D6ec%23=6[ x?EW3:Eq=@4< - Wec Y ecXX |@5 ec Z `[ `X i 6?4 l 6?4 U 4` U 4a U QlQ U QlQ i t?5 x7 i t?5 x7 i }6IE i xu 6?4 l 7=28 %96? i |D8q@I DEC i t=D6 i |D8q@I Q*@FC 7=28 :D :?4@CC64EPQ i |D8q@I 6?4 i t?5 x7"
]
# 解密并显示结果
for i, encrypted in enumerate(encrypted_strings, 1):
print(f"解密第 {i} 个字符串:")
decrypted = l(encrypted)
print(decrypted)
print("\n" + "=" * 80 + "\n")输出:
1
2
3
4
5
6
7
8
9解密第 1 个字符串:
str = InputBox("Enter your message: ") : Base64Table = "fx6LUY5at9lnwmd3TbqzuRy+AipWHPDoXZKMFGCV2I/QQQQjSreEsh18NJkg0v74OcB" : flag = "waZaAyNGDJ9CwLfNdzYCnyUsAJtSmLU0wqNKmLYFnyT8iyRMi5UEAMH0da8=" : enc = "" : For i = 1 To Len(str) Step 3 : if i + 2 <= Len(str) Then : bitBlock = Asc(Mid(str, i, 1)) * 256 * 256 + Asc(Mid(str, i + 1, 1)) * 256 + Asc(Mid(str, i + 2, 1)) : c1 = Mid(Base64Table, Int(bitBlock \ (64 * 64 * 64)) Mod 64 + 1, 1) : c2 = Mid(Base64Table, Int(bitBlock \ (64 * 64)) Mod 64 + 1, 1) : c3 = Mid(Base64Table, Int(bitBlock \ 64) Mod 64 + 1, 1) : c4 = Mid(Base64Table, Int(bitBlock) Mod 64 + 1, 1) : enc = enc & c1 & c2 & c3 & c4 : Else : If i + 1 <= Len(str) Then : bitBlock = Asc(Mid(str, i, 1)) * 256 * 256 + Asc(Mid(str, i + 1, 1)) * 256 : c1 = Mid(Base64Table, Int(bitBlock \ (64 * 64 * 64)) Mod 64 + 1, 1) : c2 = Mid(Base64Table, Int(bitBlock \ (64 * 64)) Mod 64 + 1, 1) : c3 = Mid(Base64Table, Int(bitBlock \ 64) Mod 64 + 1, 1) : enc = enc & c1 & c2 & c3 & "=" : Else : bitBlock = Asc(Mid(str, i, 1)) * 256 * 256 : c1 = Mid(Base64Table, Int(bitBlock \ (64 * 64 * 64)) Mod 64 + 1, 1) : c2 = Mid(Base64Table, Int(bitBlock \ (64 * 64)) Mod 64 + 1, 1) : enc = enc & c1 & c2 & "=" & "=" : End If : End If : Next : IF enc = flag Then : MsgBox "Your flag is correct!" : Else : MsgBox "Your flag is incorrect!" : End If
================================================================================
解密第 2 个字符串:
str = InputBox("Enter your flag: ") : Base64Table = "6wqXxGvJrT4OQQQQmNEHA/9o78VB+1lZRDct5pu2aW0dsFLyCSgejKfhUiIMYPkz3nb" : flag = "//wdBV+arvGyZW75+J2tRiGKlW72rJagR/wSlIHtRvztZ07SrG+qoKw21VTaBIAyD/etD83UZpwuliUeRVAaZpwsZKwy18CalJ2tRvztBWotBVAhB8mL+8HtBW7uBV7f+/wg+pwh1vafOqwFRVmhrvGfrJagRVrt+Wj5+KwdBVQQQQtBW7alpwgRW7KRITsRJAalt==" : enc = "" : For i = 1 To Len(str) Step 3 : if i + 2 <= Len(str) Then : bitBlock = Asc(Mid(str, i, 1)) * 256 * 256 + Asc(Mid(str, i + 1, 1)) * 256 + Asc(Mid(str, i + 2, 1)) : c1 = Mid(Base64Table, Int(bitBlock \ (64 * 64 * 64)) Mod 64 + 1, 1) : c2 = Mid(Base64Table, Int(bitBlock \ (64 * 64)) Mod 64 + 1, 1) : c3 = Mid(Base64Table, Int(bitBlock \ 64) Mod 64 + 1, 1) : c4 = Mid(Base64Table, Int(bitBlock) Mod 64 + 1, 1) : enc = enc & c1 & c2 & c3 & c4 : Else : If i + 1 <= Len(str) Then : bitBlock = Asc(Mid(str, i, 1)) * 256 * 256 + Asc(Mid(str, i + 1, 1)) * 256 : c1 = Mid(Base64Table, Int(bitBlock \ (64 * 64 * 64)) Mod 64 + 1, 1) : c2 = Mid(Base64Table, Int(bitBlock \ (64 * 64)) Mod 64 + 1, 1) : c3 = Mid(Base64Table, Int(bitBlock \ 64) Mod 64 + 1, 1) : enc = enc & c1 & c2 & c3 & "=" : Else : bitBlock = Asc(Mid(str, i, 1)) * 256 * 256 : c1 = Mid(Base64Table, Int(bitBlock \ (64 * 64 * 64)) Mod 64 + 1, 1) : c2 = Mid(Base64Table, Int(bitBlock \ (64 * 64)) Mod 64 + 1, 1) : enc = enc & c1 & c2 & "=" & "=" : End If : End If : Next : IF enc = flag Then : MsgBox str : Else : MsgBox "Your flag is incorrect!" : MsgBox enc : End If
================================================================================解密出来是两段代码。但是代码给的base表不是很对,第2段代码中删除base表中间连续QQQQ的几个可以解密出一些类似提示的东西

第1段代码的编码表进行同样的修改就得到了flag:

flag:0xGame{bf00591f-a1cb-4191-b41d-d4eecda0b798}
World’s_end_BlackBox
考点:动态调试、魔改RC4算法
附件程序运行如图:

用IDA打开,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
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194int __fastcall main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rax
__int64 v4; // rax
__int64 v5; // rax
__int64 v6; // rax
__int64 v7; // rax
__int64 v8; // rax
__int64 v9; // rax
__int64 v10; // rax
__int64 v11; // rax
std::string::iterator __for_end; // [rsp+20h] [rbp-60h] BYREF
std::string::iterator __for_begin; // [rsp+28h] [rbp-58h] BYREF
std::vector<int> EndFlag; // [rsp+30h] [rbp-50h] BYREF
std::string encrypted_message; // [rsp+50h] [rbp-30h] BYREF
std::string key; // [rsp+70h] [rbp-10h] BYREF
std::string flag; // [rsp+90h] [rbp+10h] BYREF
std::string v19; // [rsp+B0h] [rbp+30h] BYREF
std::vector<int>::value_type __x; // [rsp+D0h] [rbp+50h] BYREF
char c; // [rsp+D7h] [rbp+57h]
std::string *__for_range; // [rsp+D8h] [rbp+58h]
_main(argc, argv, envp);
SetConsoleOutputCP(0xFDE9u);
basicString((__int64)&flag);
basicString((__int64)&key);
v3 = operateOut(pOut, &unk_408078);
stdOut(v3, out);
v4 = operateOut(pOut, &unk_408160);
stdOut(v4, out);
getLine(pIn, &key); // 获取密码
if ( getLength(&key) != 12 ) // 长度判断
{
v5 = operateOut(pOut, "Length Error!");
stdOut(v5, out);
system("pause");
exit(0);
}
KeyGenerate(&v19, &KeyEnc[abi:cxx11], &DeKey[abi:cxx11]);// key的生成算法
std::string::operator=(&TrueKey[abi:cxx11], &v19);
std::string::~string(&v19);
if ( std::operator!=<char>(&key, &TrueKey[abi:cxx11]) )// 出现了检验逻辑,可以从这里直接获取到真正的key
{
v6 = operateOut(pOut, &unk_4081A2); // key错误时的输出
stdOut(v6, out);
system("pause");
exit(0);
}
v7 = operateOut(pOut, &Congratulation); // key正确时的输出
stdOut(v7, out);
v8 = operateOut(pOut, &unk_408212);
stdOut(v8, out);
getLine(pIn, &flag); // 获取flag的输入
if ( getLength(&flag) != 51 )
{
v9 = operateOut(pOut, "Length Error!");
stdOut(v9, out);
system("pause");
exit(0);
}
encrypt(&encrypted_message, &flag, &key); // 加密算法,密钥即为前文的key
std::vector<int>::vector(&EndFlag);
__for_range = &encrypted_message;
__for_begin._M_current = (char *)std::string::begin(&encrypted_message);
__for_end._M_current = (char *)std::string::end(__for_range);
while ( __gnu_cxx::operator!=<char *,std::string>(&__for_begin, &__for_end) )
{
c = *__gnu_cxx::__normal_iterator<char *,std::string>::operator*(&__for_begin);
__x = (unsigned __int8)c;
std::vector<int>::push_back(&EndFlag, &__x);
__gnu_cxx::__normal_iterator<char *,std::string>::operator++(&__for_begin);
}
if ( std::operator==<int,std::allocator<int>>(&KALEIDXSCOPE, &EndFlag) )// 检验加密结果
{
v10 = operateOut(pOut, "All Perfect!");
stdOut(v10, out);
system("pause");
exit(0);
}
v11 = operateOut(pOut, "Try again!");
stdOut(v11, out);
std::vector<int>::~vector(&EndFlag);
std::string::~string(&encrypted_message);
std::string::~string(&key);
std::string::~string(&flag);
return 0;
}
//加密函数
std::string *__cdecl modifiedRC4(std::string *__return_ptr retstr, const std::string *plaintext, const std::string *key)
{
size_t v3; // rax
size_t length; // rax
char v5; // bl
std::vector<int>::reference List; // rax
__int64 v8; // [rsp+0h] [rbp-80h] BYREF
std::vector<int> k; // [rsp+20h] [rbp-60h] BYREF
std::vector<int> s; // [rsp+40h] [rbp-40h] BYREF
std::allocator<int> __a; // [rsp+66h] [rbp-1Ah] BYREF
char v12; // [rsp+67h] [rbp-19h] BYREF
size_t i; // [rsp+68h] [rbp-18h]
std::allocator<int>::allocator((std::allocator<int> *const)&v8 + 102);
std::vector<int>::vector(&s, 0x100uLL, &__a);
std::allocator<int>::~allocator(&__a);
rc4_ksa(&s, key); // RC4密钥调度算法
v3 = std::string::size(plaintext);
rc4_prga(&k, &s, v3); // RC4伪随机数生成算法
std::allocator<char>::allocator(&v12);
std::string::basic_string(retstr, &unk_408037, &v12);
std::allocator<char>::~allocator(&v12);
for ( i = 0LL; ; ++i )
{
length = std::string::size(plaintext);
if ( i >= length )
break;
v5 = *(_BYTE *)StrMakeList(plaintext, i);
List = IntMakeList(&k, i);
std::string::operator+=(retstr, (unsigned int)(char)(v5 ^ *(_BYTE *)List ^ 7));// 魔改的地方在这
}
std::vector<int>::~vector(&k);
std::vector<int>::~vector(&s);
return retstr;
}
//密钥调度算法
void __cdecl rc4_ksa(std::vector<int> *s, const std::string *key)
{
int *M_current; // rbx
std::vector<int>::iterator v3; // rax
int v4; // ebx
unsigned __int64 v5; // rdx
_BYTE *List; // rax
char v7; // dl
int *s_j; // rbx
int *s_i; // rax
size_t i; // [rsp+20h] [rbp-60h]
int j; // [rsp+2Ch] [rbp-54h]
M_current = std::vector<int>::end(s)._M_current;
v3._M_current = std::vector<int>::begin(s)._M_current;
std::iota<__gnu_cxx::__normal_iterator<int *,std::vector<int>>,int>(
v3,
(__gnu_cxx::__normal_iterator<int*,std::vector<int> >)M_current,
0);
j = 0;
for ( i = 0LL; i <= 255; ++i )
{
v4 = *IntMakeList(s, i) + j;
v5 = i % getLength(key);
List = (_BYTE *)StrMakeList(key, v5);
v7 = v4 + *List;
LODWORD(List) = (unsigned int)((v4 + (char)*List) >> 31) >> 24;
j = (unsigned __int8)((_BYTE)List + v7) - (_DWORD)List;
s_j = IntMakeList(s, j);
s_i = IntMakeList(s, i);
std::swap<int>(s_i, s_j);
}
}
//伪随机数生成算法
std::vector<int> *__cdecl rc4_prga(std::vector<int> *__return_ptr retstr, std::vector<int> *s, size_t length)
{
std::vector<int>::reference v3; // rax
int *v4; // rbx
int *v5; // rax
std::vector<int>::reference v6; // rax
const std::vector<int>::value_type *v7; // rax
size_t r; // [rsp+30h] [rbp-50h]
int j; // [rsp+38h] [rbp-48h]
int i; // [rsp+3Ch] [rbp-44h]
std::vector<int>::vector(retstr);
i = 0;
j = 0;
for ( r = 0LL; r < length; ++r )
{
i = (i + 1) % 256;
v3 = IntMakeList(s, i);
j = (unsigned __int8)(((unsigned int)((j + *v3) >> 31) >> 24) + j + *(_BYTE *)v3)
- ((unsigned int)((j + *v3) >> 31) >> 24);
v4 = IntMakeList(s, j);
v5 = IntMakeList(s, i);
std::swap<int>(v5, v4);
LODWORD(v4) = *IntMakeList(s, i);
v6 = IntMakeList(s, j);
v7 = IntMakeList(
s,
(int)((unsigned __int8)(((unsigned int)(((int)v4 + *v6) >> 31) >> 24) + (_BYTE)v4 + *(_BYTE *)v6)
- ((unsigned int)(((int)v4 + *v6) >> 31) >> 24)));
std::vector<int>::push_back(retstr, v7);
}
return retstr;
}可以看到函数对输入的数据进行了魔改的RC4加密,但实际上没有改变RC4加解密同一个函数的性质,所以解密只需要复现这个代码即可
下面先获取密钥和加密后的flag数据。在main函数第一个getline处下断点,开始动态调试

随便输入一个字符后会到长度检测,要求输入的密码长度要等于12,可以输入12个字符也可以后续到这个cmp的时候改jz绕过

过了长度检测后就可以一直运行到key检验处,双击生成的密钥的变量名(TrueKey)就可以得到真正的key了(从
hex窗口可以看到key即为”XaleidscopiX”)


然后还需要获得真正flag的加密数据。往下绕过key检验,运行到jz的时候再改一下ZF标志位(此处应当跳转)

绕过后终端还有提示

接下来运行到第二个getline,随便输几个数据(有长度检验,最好是输入51个字符,省的绕过长度检验)。然后继续运行到加密结果的检验处

其中KALEIDXSCOPE的第一个offset就是正确的flag加密后的数据


解密脚本:
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
34def rc4_decrypt(data, key):
S = list(range(256))
j = 0
out = []
# Key-scheduling algorithm (KSA)
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
# Pseudo-random generation algorithm (PRGA)
i = 0
j = 0
for char in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
K = S[(S[i] + S[j]) % 256]
out.append(K)
return out
enc=[0x000000FC, 0x000000EA, 0x00000015, 0x0000002C, 0x00000086, 0x00000038, 0x0000003F, 0x000000F3, 0x00000092, 0x000000CE, 0x000000DA, 0x0000008E, 0x00000048, 0x000000D3, 0x00000007, 0x0000009F, 0x000000D9, 0x00000057, 0x000000B1, 0x000000EE, 0x00000041, 0x0000009A, 0x0000004D, 0x000000C5, 0x00000065, 0x0000006A, 0x000000FF, 0x000000C9, 0x0000005D, 0x00000034, 0x000000AD, 0x000000EA, 0x000000B1, 0x00000020, 0x0000004B, 0x000000DC, 0x000000BD, 0x000000D2, 0x00000035, 0x00000002, 0x00000084, 0x00000035, 0x00000071, 0x000000EC, 0x000000E0, 0x00000048, 0x0000008E, 0x000000EA, 0x0000007B, 0x000000AA, 0x000000CF]
_key="XaleidscopiX"
key=[]
flag=[]
for i in _key:
key.append(ord(i))
#print(key)
keystream=rc4_decrypt(enc, key)
for i in range(len(enc)):
flag.append(enc[i]^keystream[i]^7)
print(''.join([chr(i) for i in flag]))
flag:0xGame{RC4_15_4_b4s1c&fl3x1bl3_3ncrYp710n4lg0r17hm}
Q(≧▽≦)T
考点:QT程序逆向、crackme、动态调试、RC4、哈希校验
附件程序运行如图,是个序列号生成器:

用IDA打开,出现的是start函数, 看不出什么东西来

shift+F12看看字符串表,可以发现两串可疑的字符串

跟踪过去可以找到这样一个函数
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150void __fastcall sub_140001890(__int64 a1, __int64 a2, __int64 a3, __int64 a4)
{
volatile signed __int32 *v5; // rax
volatile signed __int32 *v6; // [rsp+80h] [rbp-1F8h] BYREF
volatile signed __int32 *v7; // [rsp+88h] [rbp-1F0h]
volatile signed __int32 *v8; // [rsp+90h] [rbp-1E8h] BYREF
volatile signed __int32 *v9; // [rsp+98h] [rbp-1E0h]
__int64 Block; // [rsp+A0h] [rbp-1D8h] BYREF
const char *v11; // [rsp+A8h] [rbp-1D0h]
volatile signed __int32 *v12[4]; // [rsp+B0h] [rbp-1C8h] BYREF
volatile signed __int32 *v13[4]; // [rsp+D0h] [rbp-1A8h] BYREF
volatile signed __int32 *v14[4]; // [rsp+F0h] [rbp-188h] BYREF
volatile signed __int32 *v15; // [rsp+110h] [rbp-168h] BYREF
volatile signed __int32 *v16[4]; // [rsp+130h] [rbp-148h] BYREF
volatile signed __int32 *v17[2]; // [rsp+150h] [rbp-128h] BYREF
volatile signed __int32 *v18; // [rsp+160h] [rbp-118h]
__m128i v19[2]; // [rsp+170h] [rbp-108h] BYREF
__m128i v20; // [rsp+190h] [rbp-E8h] BYREF
volatile signed __int32 *v21[4]; // [rsp+1B0h] [rbp-C8h] BYREF
volatile signed __int32 *v22[2]; // [rsp+1D0h] [rbp-A8h] BYREF
volatile signed __int32 *v23; // [rsp+1E0h] [rbp-98h]
volatile signed __int32 *v24[4]; // [rsp+1F0h] [rbp-88h] BYREF
volatile signed __int32 *v25[13]; // [rsp+210h] [rbp-68h] BYREF
((void (__fastcall *)(void *, volatile signed __int32 **, _QWORD, volatile signed __int32 **))QLineEdit::text)(
QLineEdit::text,
v25,
*(_QWORD *)(*(_QWORD *)(a4 + 40) + 16LL),
v25);
QString::trimmed_helper(QLineEdit::text, v25, v25, v12);
if ( v25[0] && !_InterlockedSub(v25[0], 1u) )
free(QLineEdit::text);
((void (__fastcall *)(void *, volatile signed __int32 **, _QWORD, volatile signed __int32 **))QLineEdit::text)(
QLineEdit::text,
v25,
*(_QWORD *)(*(_QWORD *)(a4 + 40) + 24LL),
v13);
if ( v12[2] == (volatile signed __int32 *)4 )
{
QString::toUtf8_helper(QLineEdit::text, v25, v12, v14);
Block = (__int64)v14[2];
v11 = (const char *)v14[1];
QCryptographicHash::hash(&Block, v25, &Block, &v15, 4LL);
sub_140001620((__int64)&Block, (__int64)v25, a4, (__int64)v16);
Block = 64LL;
v11 = "c94201919ec7463313c747d0a27fabcabf1400fa1e9a64d36a6b1a7e7b12ae68";
QString::fromUtf8(&Block, v25, &Block, v17);
if ( v18 == v16[2]
&& (v8 = v18, v6 = v18, v9 = v16[1], v7 = v17[1], (unsigned __int8)QtPrivate::equalStrings(&Block, v25, &v6, &v8)) )
{
QString::toUtf8_helper(&Block, v25, v13, v19);
sub_1400016F0((__int64)&Block, (__int64)v25, a4, &v20, v19, (__int64)v14);
sub_140001620((__int64)&Block, (__int64)v25, a4, (__int64)v21);
Block = 72LL;
v11 = "af33da5e152c15863b3a03c87601899a37d51b8b8168f65aca65352d3669e91959300ccb";
QString::fromUtf8(&Block, v25, &Block, v22);
if ( v23 == v21[2]
&& (v8 = v23, v9 = v21[1],
v6 = v23,
v7 = v22[1],
(unsigned __int8)QtPrivate::equalStrings(&Block, v25, &v6, &v8)) )
{
Block = 21LL;
v11 = (const char *)&unk_1400061D8;
QString::fromUtf8(&Block, v25, &Block, v25);
Block = 12LL;
v11 = (const char *)&unk_1400061EE;
QString::fromUtf8(&Block, v25, &Block, v24);
QMessageBox::information(&Block, v25, v24, a4, v25, 1024LL);
}
else
{
Block = 22LL;
v11 = (const char *)&unk_1400061C1;
QString::fromUtf8(&Block, v25, &Block, v25);
Block = 12LL;
v11 = (const char *)&unk_140006104;
QString::fromUtf8(&Block, v25, &Block, v24);
QMessageBox::warning(&Block, v25, v24, a4, v25, 1024LL);
}
if ( v24[0] && !_InterlockedSub(v24[0], 1u) )
free(&Block);
if ( v25[0] && !_InterlockedSub(v25[0], 1u) )
free(&Block);
if ( v22[0] && !_InterlockedSub(v22[0], 1u) )
free(&Block);
if ( v21[0] && !_InterlockedSub(v21[0], 1u) )
free(&Block);
if ( v20.m128i_i64[0] && !_InterlockedSub((volatile signed __int32 *)v20.m128i_i64[0], 1u) )
free(&Block);
if ( !v19[0].m128i_i64[0] || _InterlockedSub((volatile signed __int32 *)v19[0].m128i_i64[0], 1u) )
goto LABEL_45;
}
else
{
Block = 25LL;
v11 = (const char *)&unk_140006159;
QString::fromUtf8(&Block, v25, &Block, v25);
Block = 12LL;
v11 = (const char *)&unk_140006104;
QString::fromUtf8(&Block, v25, &Block, v24);
QMessageBox::warning(&Block, v25, v24, a4, v25, 1024LL);
if ( v24[0] && !_InterlockedSub(v24[0], 1u) )
free(&Block);
if ( !v25[0] || _InterlockedSub(v25[0], 1u) )
goto LABEL_45;
}
free(&Block);
LABEL_45:
if ( v17[0] && !_InterlockedSub(v17[0], 1u) )
free(&Block);
if ( v16[0] && !_InterlockedSub(v16[0], 1u) )
free(&Block);
if ( v15 && !_InterlockedSub(v15, 1u) )
free(&Block);
if ( v14[0] && !_InterlockedSub(v14[0], 1u) )
free(&Block);
goto LABEL_10;
}
Block = 35LL;
v11 = (const char *)&unk_1400060E0;
QString::fromUtf8(&Block, v25, &Block, v25);
Block = 12LL;
v11 = (const char *)&unk_140006104;
QString::fromUtf8(&Block, v25, &Block, v24);
QMessageBox::warning(&Block, v25, v24, a4, v25, 1024LL);
if ( v24[0] && !_InterlockedSub(v24[0], 1u) )
{
free(&Block);
v5 = v25[0];
if ( !v25[0] )
goto LABEL_10;
}
else
{
v5 = v25[0];
if ( !v25[0] )
goto LABEL_10;
}
if ( !_InterlockedSub(v5, 1u) )
free(&Block);
LABEL_10:
if ( v13[0] && !_InterlockedSub(v13[0], 1u) )
free(&Block);
if ( v12[0] )
{
if ( !_InterlockedSub(v12[0], 1u) )
free(&Block);
}
}程序的图形化界面是QT实现的,引入了QT的很多类,所以阅读难度还是有点大的。借助AI优化一下以及一点点动态调试辅助分析可以得到:
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143void __fastcall check(__int64 a1, __int64 a2, __int64 a3, __int64 mainWindows)
{
volatile signed __int32 *v5; // rax
volatile signed __int32 *v6; // [rsp+80h] [rbp-1F8h] BYREF
volatile signed __int32 *v7; // [rsp+88h] [rbp-1F0h]
volatile signed __int32 *v8; // [rsp+90h] [rbp-1E8h] BYREF
volatile signed __int32 *v9; // [rsp+98h] [rbp-1E0h]
__int64 Block; // [rsp+A0h] [rbp-1D8h] BYREF
const char *stringData; // [rsp+A8h] [rbp-1D0h]
volatile signed __int32 *usernameUtf8[4]; // [rsp+B0h] [rbp-1C8h] BYREF
volatile signed __int32 *passwordUtf8[4]; // [rsp+D0h] [rbp-1A8h] BYREF
volatile signed __int32 *usernameBytes[4]; // [rsp+F0h] [rbp-188h] BYREF
volatile signed __int32 *usernameHash; // [rsp+110h] [rbp-168h] BYREF
volatile signed __int32 *usernameHashHex[4]; // [rsp+130h] [rbp-148h] BYREF
volatile signed __int32 *v17[2]; // [rsp+150h] [rbp-128h] BYREF
volatile signed __int32 *expectedUserHashSize; // [rsp+160h] [rbp-118h]
__m128i passwordBytes[2]; // [rsp+170h] [rbp-108h] BYREF
__m128i v20; // [rsp+190h] [rbp-E8h] BYREF
volatile signed __int32 *v21[4]; // [rsp+1B0h] [rbp-C8h] BYREF
volatile signed __int32 *v22[2]; // [rsp+1D0h] [rbp-A8h] BYREF
volatile signed __int32 *v23; // [rsp+1E0h] [rbp-98h]
volatile signed __int32 *v24[4]; // [rsp+1F0h] [rbp-88h] BYREF
volatile signed __int32 *tempBuffer[13]; // [rsp+210h] [rbp-68h] BYREF
(QLineEdit::text)(QLineEdit::text, tempBuffer, *(*(mainWindows + 40) + 16LL), tempBuffer);
QString::trimmed_helper(QLineEdit::text, tempBuffer, tempBuffer, usernameUtf8);
if ( tempBuffer[0] && !_InterlockedSub(tempBuffer[0], 1u) )
free(QLineEdit::text);
(QLineEdit::text)(QLineEdit::text, tempBuffer, *(*(mainWindows + 40) + 24LL), passwordUtf8);
if ( usernameUtf8[2] == 4 ) // 长度检验
{
QString::toUtf8_helper(QLineEdit::text, tempBuffer, usernameUtf8, usernameBytes);
Block = usernameBytes[2];
stringData = usernameBytes[1];
QCryptographicHash::hash(&Block, tempBuffer, &Block, &usernameHash, 4LL);// 求输入的用户名hash
toHex(&Block, tempBuffer, mainWindows, usernameHashHex);
Block = 64LL;
stringData = "c94201919ec7463313c747d0a27fabcabf1400fa1e9a64d36a6b1a7e7b12ae68";// 预设哈希值
QString::fromUtf8(&Block, tempBuffer, &Block, v17);
if ( expectedUserHashSize == usernameHashHex[2]
&& (v8 = expectedUserHashSize,
v6 = expectedUserHashSize,
v9 = usernameHashHex[1],
v7 = v17[1],
QtPrivate::equalStrings(&Block, tempBuffer, &v6, &v8)) )// 用户名检验
{
QString::toUtf8_helper(&Block, tempBuffer, passwordUtf8, passwordBytes);
RC4(&Block, tempBuffer, mainWindows, &v20, passwordBytes, usernameBytes);
toHex(&Block, tempBuffer, mainWindows, v21);
Block = 72LL;
stringData = "af33da5e152c15863b3a03c87601899a37d51b8b8168f65aca65352d3669e91959300ccb";
QString::fromUtf8(&Block, tempBuffer, &Block, v22);
if ( v23 == v21[2]
&& (v8 = v23, v9 = v21[1], v6 = v23, v7 = v22[1], QtPrivate::equalStrings(&Block, tempBuffer, &v6, &v8)) )// 验证通过
{
Block = 21LL;
stringData = &unk_7FF7F2EB61D8;
QString::fromUtf8(&Block, tempBuffer, &Block, tempBuffer);
Block = 12LL;
stringData = &unk_7FF7F2EB61EE;
QString::fromUtf8(&Block, tempBuffer, &Block, v24);
QMessageBox::information(&Block, tempBuffer, v24, mainWindows, tempBuffer, 1024LL);
}
else // 密码验证不通过
{
Block = 22LL;
stringData = &unk_7FF7F2EB61C1;
QString::fromUtf8(&Block, tempBuffer, &Block, tempBuffer);
Block = 12LL;
stringData = &unk_7FF7F2EB6104;
QString::fromUtf8(&Block, tempBuffer, &Block, v24);
QMessageBox::warning(&Block, tempBuffer, v24, mainWindows, tempBuffer, 1024LL);
}
if ( v24[0] && !_InterlockedSub(v24[0], 1u) )
free(&Block);
if ( tempBuffer[0] && !_InterlockedSub(tempBuffer[0], 1u) )
free(&Block);
if ( v22[0] && !_InterlockedSub(v22[0], 1u) )
free(&Block);
if ( v21[0] && !_InterlockedSub(v21[0], 1u) )
free(&Block);
if ( v20.m128i_i64[0] && !_InterlockedSub(v20.m128i_i64[0], 1u) )
free(&Block);
if ( !passwordBytes[0].m128i_i64[0] || _InterlockedSub(passwordBytes[0].m128i_i64[0], 1u) )
goto LABEL_45;
}
else // 用户名验证不通过
{
Block = 25LL;
stringData = &unk_7FF7F2EB6159;
QString::fromUtf8(&Block, tempBuffer, &Block, tempBuffer);
Block = 12LL;
stringData = &unk_7FF7F2EB6104;
QString::fromUtf8(&Block, tempBuffer, &Block, v24);
QMessageBox::warning(&Block, tempBuffer, v24, mainWindows, tempBuffer, 1024LL);
if ( v24[0] && !_InterlockedSub(v24[0], 1u) )
free(&Block);
if ( !tempBuffer[0] || _InterlockedSub(tempBuffer[0], 1u) )
goto LABEL_45;
}
free(&Block);
LABEL_45:
if ( v17[0] && !_InterlockedSub(v17[0], 1u) )
free(&Block);
if ( usernameHashHex[0] && !_InterlockedSub(usernameHashHex[0], 1u) )
free(&Block);
if ( usernameHash && !_InterlockedSub(usernameHash, 1u) )
free(&Block);
if ( usernameBytes[0] && !_InterlockedSub(usernameBytes[0], 1u) )
free(&Block);
goto LABEL_10;
}
Block = 35LL;
stringData = &unk_7FF7F2EB60E0;
QString::fromUtf8(&Block, tempBuffer, &Block, tempBuffer);
Block = 12LL;
stringData = &unk_7FF7F2EB6104;
QString::fromUtf8(&Block, tempBuffer, &Block, v24);
QMessageBox::warning(&Block, tempBuffer, v24, mainWindows, tempBuffer, 1024LL);
if ( v24[0] && !_InterlockedSub(v24[0], 1u) )
{
free(&Block);
v5 = tempBuffer[0];
if ( !tempBuffer[0] )
goto LABEL_10;
}
else
{
v5 = tempBuffer[0];
if ( !tempBuffer[0] )
goto LABEL_10;
}
if ( !_InterlockedSub(v5, 1u) )
free(&Block);
LABEL_10:
if ( passwordUtf8[0] && !_InterlockedSub(passwordUtf8[0], 1u) )
free(&Block);
if ( usernameUtf8[0] )
{
if ( !_InterlockedSub(usernameUtf8[0], 1u) )
free(&Block);
}
}可以看到用户名采用的是哈希检验,密码采用的是RC4检验,密钥为用户名。
密码检验套在用户名检验里,所以第一步先解出用户名。借助在线解密网站MD5 在線免費解密可以得到用户名”Kath”:

接下来求解密码。RC4函数如下:
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__m128i *__fastcall RC4(
__int64 a1,
__int64 a2,
__int64 a3,
__m128i *a4,
const __m128i *passwordBytes,
__int64 usernameBytes)
{
volatile signed __int32 *v6; // rax
__int64 v7; // rdx
bool v8; // zf
int keyLength; // r11d
int *sBoxPtr; // r8
__m128i initVal; // xmm0
__m128i *sBoxInitPtr; // rax
__m128i v15; // xmm2
__m128i currentVal; // xmm1
__int64 keyBytes; // rdi
int i_ksa; // ecx
int j_ksa; // r9d
int v20; // eax
int sBoxVal; // r10d
__int64 sum; // kr00_8
__int64 passwordIndex; // r12
int j; // ebp
__int64 i; // rdi
int temp; // eax
__int64 v27; // kr08_8
char v28; // r14
_DWORD S[256]; // [rsp+20h] [rbp-438h] BYREF
char v31; // [rsp+420h] [rbp-38h] BYREF
v6 = passwordBytes->m128i_i64[0];
v7 = passwordBytes[1].m128i_i64[0];
v8 = passwordBytes->m128i_i64[0] == 0;
*a4 = _mm_loadu_si128(passwordBytes);
a4[1].m128i_i64[0] = v7;
if ( !v8 )
_InterlockedAdd(v6, 1u);
keyLength = *(usernameBytes + 16);
sBoxPtr = S;
initVal = _mm_load_si128(&xmmword_7FF7F2EB6200);
sBoxInitPtr = S;
v15 = _mm_load_si128(&xmmword_7FF7F2EB6210);
do
{
currentVal = initVal;
++sBoxInitPtr;
initVal = _mm_add_epi32(initVal, v15);
sBoxInitPtr[-1] = currentVal;
}
while ( sBoxInitPtr != &v31 );
keyBytes = *(usernameBytes + 8);
i_ksa = 0;
j_ksa = 0;
do // RC4_KSA
{
v20 = i_ksa;
sBoxVal = *sBoxPtr;
++i_ksa;
++sBoxPtr;
sum = sBoxVal + j_ksa + *(keyBytes + v20 % keyLength);
j_ksa = (HIBYTE(sum) + sum) - HIBYTE(HIDWORD(sum));// j = j % 256
*(sBoxPtr - 1) = S[j_ksa];
S[j_ksa] = sBoxVal;
}
while ( i_ksa != 256 );
if ( passwordBytes[1].m128i_i64[0] > 0 ) // RC4_PRGA和RC4decrypt
{
passwordIndex = 0LL;
j = 0;
LODWORD(i) = 0;
do
{
i = ((i + 1) % 256);
temp = S[i];
j = (temp + j) % 256;
S[i] = S[j];
S[j] = temp;
v27 = S[i] + temp;
v28 = *(passwordBytes->m128i_i64[1] + passwordIndex) ^ S[(HIBYTE(v27) + v27) - HIBYTE(HIDWORD(v27))];// v28 = passwordBytes[passwordIndex] ^ S[(S[i]+S[j])%256]
if ( !a4->m128i_i64[0] || *a4->m128i_i64[0] > 1 )
QByteArray::reallocData(i, passwordBytes, a4[1].m128i_i64[0], a4, 1LL, *initVal.m128i_i64);
*(a4->m128i_i64[1] + passwordIndex++) = v28;
}
while ( passwordBytes[1].m128i_i64[0] > passwordIndex );
}
return a4;
}可以看出是标准的RC4,直接复现加密函数即可。解密脚本:
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
37def rc4_ksa(key):
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
return S
def rc4_prga(S, n):
i = j = 0
keystream = []
S = S[:] # copy
for _ in range(n):
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
k = S[(S[i] + S[j]) % 256]
keystream.append(k)
return bytes(keystream)
def rc4_decrypt(key, ciphertext):
S = rc4_ksa(key)
ks = rc4_prga(S, len(ciphertext))
return bytes(c ^ k for c, k in zip(ciphertext, ks))
key = b"Kath"
cipher = "af33da5e152c15863b3a03c87601899a37d51b8b8168f65aca65352d3669e91959300ccb"
# 转成bytes
ciphertext = bytes.fromhex(cipher)
# 解密
plaintext = rc4_decrypt(key, ciphertext)
# 输出
print("Plaintext:", plaintext)
flag:0xGame{ce5e5621-3d6b-4429-b72a-957abf353390}
Calamity_Fortune
这题,哈哈,数据提取错了题目截止提交48分钟后我才发现这个问题解出flag😄🫠我永远都不会原谅我自己了😄哈哈哈哈考点:函数重写、复杂加密算法逆向、动态调试绕过程序机制
附件程序双击运行会提示如图

然而,随便输一个数字的话会闪退
程序用IDA打开,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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56int __fastcall main(int argc, const char **argv, const char **envp)
{
unsigned int v3; // eax
int v4; // ebx
int v5; // eax
int v6; // ecx
__int64 v7; // rbx
char v8; // cl
int v10; // [rsp+20h] [rbp-58h] BYREF
char v11[9]; // [rsp+27h] [rbp-51h] BYREF
_QWORD v12[5]; // [rsp+30h] [rbp-48h]
int v13; // [rsp+58h] [rbp-20h]
__int16 v14; // [rsp+5Ch] [rbp-1Ch]
sub_401600(argc, argv, envp);
SetConsoleOutputCP(0xFDE9u);
SetConsoleCP(0xFDE9u);
v3 = time64(0LL);
srand(v3);
v10 = 0;
v4 = rand();
puts(&Buffer);
v5 = scanf("%d", &v10);
v6 = 1;
if ( v5 == 1 )
{
if ( v10 == v4 % 100 + 1 )
{
MessageBoxA(0LL, "You guessed right! Is it really right?", "Result", 0);
return 0;
}
else
{
v7 = 0LL;
puts("You guessed it wrong!");
puts(&byte_405098);
v12[0] = 0xD0F0C00002B1973LL;
v12[1] = 0x182B1A043E1F082BLL;
v12[2] = 0x151236080A0D071CLL;
v12[3] = 0xA150C0111330622LL;
v12[4] = 0xD2B190804073E26LL;
v13 = 251992113;
v14 = 5130;
strcpy(v11, "Calamity");
do
{
v8 = *((_BYTE *)v12 + v7) ^ v11[v7 & 7];
++v7;
putchar(v8);
}
while ( v7 != 46 );
return 0;
}
}
return v6;
}v12看着像密文,的确是密文,但不是flag的密文,因为它在输入错误的提示里。暂时没有发现flag检验函数,尝试先绕过数字检验
在汇编窗口找到数字判断语句(可以在反编译界面选中判断语句所在行,右键Jump to Disasm到达)

可以看到这是一个jz,只需要在执行到这句但是还没步过时,修改ZF标志位即可绕过。在此处F2下断点,开始动调程序。随便输个数字,按回车

此时程序停在jz处,即我们下断点的地方

在右上侧寄存器窗口找到ZF

双击ZF把它的值改成0x1

可以看到jz语句处有一条箭头出来了

F8执行jz跳转,跟踪程序执行流。可以看到程序运行到一个类似输出函数的地方

这时候,在这里多试几次会发现MessageBoxA可以步入。当然要是步过这个call MessageBoxA会发现程序没有弹出窗口,倒是终端输出了新的句子,十分可疑

所以在程序执行到call cs:MessageBoxA这句的时候按F7步入,就会发现这个MessageBoxA别有洞天

这显然不像是MessageBoxA应该有的操作。可以看到一个可疑的地址被放到了rax里,跟踪这个jmp看看(jmp rax处F7步入)

可以看到程序运行到了一个新函数,F5反编译,反编译代码如下(函数有多个模块的加密处理,还是很复杂的,以下是经过变量名优化的代码)
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261__int64 realMain()
{
char v0; // dl
char *v1; // rax
char v2; // dl
char *i; // rax
int v4; // eax
unsigned __int8 *pFlag1; // r9
_BYTE *pFlag; // rdx
unsigned int v7; // esi
char v8; // dl
char *j; // rax
unsigned int *p_block0; // r10
unsigned __int8 *current_1; // rdx
int shift; // ecx
unsigned int temp_2; // r8d
int temp_1; // eax
unsigned int cureent_block; // eax
unsigned int delta; // r11d
int key_value; // ebp
unsigned int prev_block; // r9d
unsigned int *block; // r8
int rounds; // ecx
unsigned int next; // edx
unsigned int oriDelta; // ecx
unsigned __int8 *encrypted_bytes_1; // r10
unsigned int *v24; // rdi
_DWORD *v25; // r8
_DWORD *v26; // rax
int shift_1; // ecx
unsigned int v28; // edx
_BYTE *v29; // r9
unsigned int v30; // r8d
unsigned __int64 k; // rax
char v32; // dl
_BYTE *current1; // rbx
int randNum; // eax
char tmp; // r8
_BYTE *target; // rdx
unsigned __int8 *v37; // rdx
unsigned __int8 *end; // rbx
unsigned __int8 *current; // rax
char *v40; // rcx
int m; // eax
char *v42; // rax
char n; // dl
char v44; // dl
char *ii; // rax
_QWORD key[2]; // [rsp+20h] [rbp-218h] BYREF
char notice2[22]; // [rsp+30h] [rbp-208h] BYREF
__int16 v49; // [rsp+46h] [rbp-1F2h] BYREF
unsigned int block0; // [rsp+50h] [rbp-1E8h] BYREF
int v51; // [rsp+54h] [rbp-1E4h] BYREF
unsigned int block10; // [rsp+78h] [rbp-1C0h]
_BYTE encrypted_bytes[44]; // [rsp+80h] [rbp-1B8h] BYREF
int v54; // [rsp+ACh] [rbp-18Ch] BYREF
_BYTE flag[44]; // [rsp+B0h] [rbp-188h] BYREF
char v56; // [rsp+DCh] [rbp-15Ch] BYREF
_QWORD enc[7]; // [rsp+E0h] [rbp-158h] BYREF
int v58; // [rsp+118h] [rbp-120h]
_BYTE base64_output[59]; // [rsp+120h] [rbp-118h] BYREF
_BYTE end_1[5]; // [rsp+15Bh] [rbp-DDh] BYREF
char temp[64]; // [rsp+160h] [rbp-D8h] BYREF
char notice1[85]; // [rsp+1A0h] [rbp-98h] BYREF
_BYTE v63[3]; // [rsp+1F5h] [rbp-43h] BYREF
v0 = 79;
enc[0] = 0x280D30732B077874LL;
enc[1] = 0x242D00103573060BLL;
enc[2] = 0x141C3406727D2F73LL;
enc[3] = 0xA71137676362833LL;
enc[4] = 0xE232B242F04742ALL;
enc[5] = 0x2F373F03033D7310LL;
enc[6] = 0x77067C3612772D7DLL;
qmemcpy(key, "Calamity_Fortune", sizeof(key));
qmemcpy(
notice1,
"Ofqni`'jfcb'ns'sont'afu+'sob'tretbvrbis'bidu~wsnhi'tohrkc'eb'f'wnbdb'ha'dflb'ahu'~hry",
sizeof(notice1));
v1 = notice1;
v58 = 0x37233104;
while ( 1 )
{
*v1++ = v0 ^ 7;
if ( v63 == v1 )
break;
v0 = *v1;
}
puts(notice1); // Having made it this far,......
v2 = 87;
qmemcpy(notice2, "Wkbftb'Niwrs'~hru'akf`", sizeof(notice2));
for ( i = notice2; ; v2 = *i )
{
*i++ = v2 ^ 7;
if ( i == &v49 )
break;
}
puts(notice2); // Please input your flag
scanf("%44s", flag);
v4 = flag[0];
v56 = 0;
if ( !flag[0] )
goto LABEL_11;
pFlag1 = flag;
pFlag = flag;
do
v7 = 1 - flag + pFlag++;
while ( *pFlag );
if ( v7 != 44 )
{
LABEL_11: // 错误时的输出
v8 = 75;
qmemcpy(temp, "Kbi`so'Buuhu&", 13);
for ( j = temp; ; v8 = *j )
{
*j++ = v8 ^ 7;
if ( &temp[13] == j )
break;
}
puts(temp); // 错误时的输出
exit(0);
}
p_block0 = &block0;
while ( 1 ) // bytesToBlock,转成uint32_t
{
current_1 = pFlag1;
shift = 0;
temp_2 = 0;
while ( 1 )
{
temp_1 = v4 << shift;
shift += 8;
++current_1;
temp_2 |= temp_1;
if ( shift == 32 )
break;
v4 = *current_1;
}
pFlag1 += 4;
*p_block0++ = temp_2;
if ( &v56 == pFlag1 )
break;
v4 = *pFlag1;
}
cureent_block = block10;
delta = 0x9E3779B9;
key_value = 0x616C6143;
prev_block = block0;
while ( 1 ) // xxtea
{
block = &block0;
for ( rounds = 0; ; ++rounds )
{
next = block[1];
++block;
cureent_block = prev_block
+ (((cureent_block ^ *(key + (((delta >> 2) ^ rounds) & 3))) + (next ^ delta)) ^ (((4 * next) ^ (cureent_block >> 5)) + ((16 * cureent_block) ^ (next >> 3))));
*(block - 1) = cureent_block;
if ( rounds == 9 )
break;
prev_block = *block;
}
prev_block = block0;
oriDelta = delta;
delta -= 0x61C88647;
cureent_block = block10
+ ((((cureent_block >> 5) ^ (4 * block0)) + ((16 * cureent_block) ^ (block0 >> 3))) ^ ((block0 ^ oriDelta) + (key_value ^ cureent_block)));
block10 = cureent_block;
if ( delta == 0xCC623AF3 )
break;
key_value = *(key + (((delta >> 2) ^ 0xA) & 3));
}
encrypted_bytes_1 = encrypted_bytes;
v24 = &v51; // blockToBytes,uint32_t转成bytes
v25 = encrypted_bytes;
while ( 1 )
{
v26 = v25;
for ( shift_1 = 0; shift_1 != 32; shift_1 += 8 )
{
v26 = (v26 + 1);
v28 = prev_block >> shift_1;
*(v26 - 1) = v28;
}
if ( &v54 == ++v25 )
break;
prev_block = *v24++;
}
qmemcpy(temp, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", sizeof(temp));
v29 = base64_output;
do // base64encode
{
v30 = *encrypted_bytes_1 << 16;
if ( v7 != 1 )
{
v30 |= encrypted_bytes_1[1] << 8;
if ( v7 > 2 )
v30 |= encrypted_bytes_1[2];
}
for ( k = 0LL; k != 4; ++k )
{
v32 = 61;
if ( k <= v7 )
v32 = temp[(v30 >> (-6 * k + 18)) & 0x3F];
v29[k] = v32;
}
encrypted_bytes_1 += 3;
v29 += 4;
v7 -= 3;
}
while ( (&v54 + 1) != encrypted_bytes_1 );
current1 = end_1;
end_1[1] = 0;
srand(0x65u); // shuffle
while ( 1 )
{
randNum = rand();
tmp = *current1;
target = &base64_output[randNum % (60 - end_1 + current1)];
*current1 = *target;
*target = tmp;
v37 = current1 - 1;
if ( base64_output == current1 - 1 )
break;
--current1;
}
end = current1 + 59;
current = v37;
do // xor
*current++ ^= 0x45u;
while ( end != current );
v40 = enc + 1;
for ( m = 116; ; m = *v40++ )
{
if ( *v37 != m ) // 错误处理的输出
{
qmemcpy(temp, "Dfkfjns~&'wkbftb'su~'f`fni", 26);
v42 = temp;
for ( n = 68; ; n = *v42 )
{
*v42++ = n ^ 7;
if ( &temp[26] == v42 )
break;
}
puts(temp);
exit(0);
} // 错误处理的输出
if ( end == ++v37 )
break;
}
v44 = 65;
qmemcpy(temp, "Ahusrib&@hhc'Krdl&", 18);
for ( ii = temp; ; v44 = *ii )
{
*ii++ = v44 ^ 7;
if ( &temp[18] == ii )
break;
}
puts(temp);
return 1LL;
}函数出现在程序输入flag的提示之后,应该就是flag检验函数了。函数使用了很多加密手段,甚至连提示词都是实时解密的。可以看到对输入的flag依次进行了格式转换、魔改XXTEA加密、格式转换、base64编码、shuffle洗牌算法打乱、异或处理,最后才和预设的密文进行比较检验。
这里给出一个AI复现的、经过测试等价的脚本,方便理解这个复杂的算法(被函数改写整怕了,所以随机数生成算法也复现了):
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127#include <stdio.h>
#include <stdint.h>
#include <string.h>
// MSVC-compatible rand
static uint32_t rand_seed;
void custom_srand(uint32_t seed) { rand_seed = seed; }
uint32_t custom_rand() {
rand_seed = rand_seed * 214013 + 2531011;
return (rand_seed >> 16) & 0x7FFF;
}
// XXTEA encryption (as in realMain)
void encrypt_xxtea_variant(uint32_t block[11], const uint32_t key[4]) {
uint32_t cureent_block = block[10];
uint32_t delta = 0x9E3779B9U;
uint32_t key_value = 0x616C6143U; // 初始值,但第一轮内循环不用它
uint32_t prev_block = block[0];
while (1) {
uint32_t* bptr = &block[0];
for (int rounds = 0; ; ++rounds) {
uint32_t next = bptr[1];
++bptr;
uint32_t k = key[((delta >> 2) ^ rounds) & 3];
uint32_t term1 = (cureent_block ^ k) + (next ^ delta);
uint32_t term2 = (4 * next ^ (cureent_block >> 5)) + (16 * cureent_block ^ (next >> 3));
cureent_block = prev_block + (term1 ^ term2);
*(bptr - 1) = cureent_block;
if (rounds == 9) break;
prev_block = *bptr; // 此时 *bptr 尚未被修改(是原始值)
}
prev_block = block[0];
uint32_t oriDelta = delta;
delta -= 0x61C88647U;
key_value = key[((oriDelta >> 2) ^ 0xA) & 3];
uint32_t term1 = ((cureent_block >> 5) ^ (4 * block[0])) + ((16 * cureent_block) ^ (block[0] >> 3));
uint32_t term2 = (block[0] ^ oriDelta) + (key_value ^ cureent_block);
cureent_block = block[10] + (term1 ^ term2);
block[10] = cureent_block;
if (delta == 0xCC623AF3U) break;
}
}
// Base64 encode 44 bytes -> 60 chars
void base64_encode(const uint8_t* input, char* output) {
const char* table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
for (int i = 0; i < 44; i += 3) {
uint32_t val = (input[i] << 16) |
((i + 1 < 44 ? input[i + 1] : 0) << 8) |
((i + 2 < 44 ? input[i + 2] : 0));
output[0] = table[(val >> 18) & 0x3F];
output[1] = table[(val >> 12) & 0x3F];
output[2] = (i + 1 < 44) ? table[(val >> 6) & 0x3F] : '=';
output[3] = (i + 2 < 44) ? table[val & 0x3F] : '=';
output += 4;
}
}
int main() {
// Step 1: Input flag
char flag[45] = "0xGame{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}";//测试样例
printf("Input flag: %s\n", flag);
// Step 2: Convert to 11 uint32_t (little-endian)
uint32_t blocks[11];
for (int i = 0; i < 11; i++) {
blocks[i] = (uint8_t)flag[i * 4] |
((uint8_t)flag[i * 4 + 1] << 8) |
((uint8_t)flag[i * 4 + 2] << 16) |
((uint8_t)flag[i * 4 + 3] << 24);
}
// Step 3: XXTEA encrypt
uint32_t key[4] = {
0x616C6143, // 'C','a','l','a'
0x7974696D, // 'm','i','t','y'
0x726F465F, // '_','F','o','r'
0x656E7574 // 't','u','n','e'
};
encrypt_xxtea_variant(blocks, key);
printf("TEA encrypted blocks (hex, little-endian order):\n");
for (int i = 0; i < 11; i++) {
printf("%08x\n", blocks[i]);
}
printf("\n");
// Step 4: Convert back to bytes (little-endian)
uint8_t encrypted_bytes[44];
for (int i = 0; i < 11; i++) {
encrypted_bytes[i * 4] = blocks[i] & 0xFF;
encrypted_bytes[i * 4 + 1] = (blocks[i] >> 8) & 0xFF;
encrypted_bytes[i * 4 + 2] = (blocks[i] >> 16) & 0xFF;
encrypted_bytes[i * 4 + 3] = (blocks[i] >> 24) & 0xFF;
}
// Step 5: Base64 encode
char base64_str[61] = { 0 };
base64_encode(encrypted_bytes, base64_str);
printf("Base64: %s\n", base64_str);
// Step 6: Shuffle (Fisher-Yates, i=59 downto 1)
custom_srand(0x65);
for (int i = 59; i >= 1; i--) {
int j = custom_rand() % (i + 1);
// Swap base64_str[i] and base64_str[j]
char tmp = base64_str[i];
base64_str[i] = base64_str[j];
base64_str[j] = tmp;
}
// Output shuffled result (before XOR)
printf("After shuffle (before XOR): %s\n", base64_str);
printf("Hex dump:\n");
for (int i = 0; i < 60; i++) {
printf("%02x ", (uint8_t)base64_str[i]);
if ((i + 1) % 16 == 0) printf("\n");
}
if (60 % 16 != 0) printf("\n");
printf("\n");
for (int i = 0; i < 60; i++) {
printf("%02x ", (uint8_t)base64_str[i] ^ 0x45);
}
printf("\n");
return 0;
}解密思路:异或模块直接一模一样地重复一遍即可;shuffle模块可以先从加密算法获得随机数序列,这样方便从后往前索引还原;剩下的看脚本吧🫠(对了,要注意enc数据要从栈上提取,笔者让AI从反编译的代码里提取,肉眼检验没发现bytes截取错了,浪费了好多时间)
解密脚本:
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148#include <stdio.h>
#include <stdint.h>
#include <string.h>
// ========== 你提供的解密函数 ==========
void decrypt_xxtea_variant(uint32_t block[11], const uint32_t key[4]) {
const int TOTAL_ROUNDS = 10;
uint32_t delta = 0xCC623AF3U;
for (int round = 0; round < TOTAL_ROUNDS; round++) {
uint32_t oriDelta = delta + 0x61C88647U;
uint32_t key_value = key[((oriDelta >> 2) ^ 0xA) & 3];
uint32_t C9 = block[9];
uint32_t C0 = block[0];
uint32_t term1 = ((C9 >> 5) ^ (4 * C0)) + ((16 * C9) ^ (C0 >> 3));
uint32_t term2 = (C0 ^ oriDelta) + (key_value ^ C9);
uint32_t G = term1 ^ term2;
uint32_t original_block10 = block[10] - G;
uint32_t saved_block10 = original_block10;
for (int i = 9; i >= 0; i--) {
uint32_t next = (i == 9) ? saved_block10 : block[i + 1];
uint32_t prev_C = (i == 0) ? saved_block10 : block[i - 1];
uint32_t k = key[((oriDelta >> 2) ^ i) & 3];
uint32_t part1 = (prev_C ^ k) + (next ^ oriDelta);
uint32_t part2 = (4 * next ^ (prev_C >> 5)) + (16 * prev_C ^ (next >> 3));
uint32_t H = part1 ^ part2;
block[i] = block[i] - H;
}
block[10] = original_block10;
delta += 0x61C88647U;
}
}
const char* table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
int base64_decode(const char* input, uint8_t* output) {
int out_len = 0;
for (int i = 0; i < 60; i += 4) {
uint32_t val = 0;
for (int j = 0; j < 4; j++) {
const char* p = strchr(table, input[i + j]);
if (p) val = (val << 6) | (p - table);
else val <<= 6;
}
output[out_len++] = (val >> 16) & 0xFF;
if (input[i + 2] != '=') output[out_len++] = (val >> 8) & 0xFF;
if (input[i + 3] != '=') output[out_len++] = val & 0xFF;
}
return out_len;
}
void deshuffle(char* cipher, uint32_t* rand_seq) {
// 逆序遍历:t 从 58 到 0
for (int t = 58; t >= 0; t--) {
int i = 59 - t; // 加密时的 i
int j = rand_seq[t]; // 加密时的 j
// 再次交换 cipher[i] 和 cipher[j]
char temp = cipher[i];
cipher[i] = cipher[j];
cipher[j] = temp;
}
}
// ========== 主程序 ==========
int main() {
// Step 1: 使用你提供的 60 字节目标密文
uint8_t encrypted_target_bytes[60] = {
0x74, 0x78, 0x07, 0x2B, 0x73, 0x30, 0x0D, 0x28,
0x0B, 0x06, 0x73, 0x35, 0x10, 0x00, 0x2D, 0x24,
0x73, 0x2F, 0x7D, 0x72, 0x06, 0x34, 0x1C, 0x14,
0x33, 0x28, 0x36, 0x76, 0x76, 0x13, 0x71, 0x0A,
0x2A, 0x74, 0x4, 0x2f, 0x24, 0x2b, 0x23, 0xe,
0x10, 0x73, 0x3D, 0x03, 0x03, 0x3F, 0x37, 0x2F,
0x7D, 0x2D, 0x77, 0x12, 0x36, 0x7C, 0x06, 0x77,
0x04, 0x31, 0x23, 0x37
};
// Step 2: XOR 0x45 得到 shuffled base64 字符串
char shuffled_b64[60] = { 0 };
//printf("step1: xor\n");
for (int i = 0; i < 60; i++) {
shuffled_b64[i] = encrypted_target_bytes[i] ^ 0x45;
//printf("%c", shuffled_b64[i]);
}
//printf("\nstep2: deshuffle\n");
// Step 3: 使用你提供的 rand() 序列(原始值,未取模)
uint32_t rand_seq[59] = {
8, 46, 21, 19, 35, 1, 1, 7, 37, 17,
33, 13, 17, 10, 34, 34, 6, 20, 35, 10,
22, 21, 7, 16, 6, 18, 30, 32, 6, 2,
9, 0, 7, 4, 23, 18, 18, 16, 15, 15,
6, 15, 16, 3, 13, 13, 9, 2, 11, 10,
3, 2, 0, 1, 3, 0, 3, 0, 0
};
deshuffle(shuffled_b64, rand_seq);
//for (int i = 0; i < 60; i++) {
//printf("%c", shuffled_b64[i]);
//}
char original_b64[61] = { 0 };
for (int i = 0; i < 60; i++) {
original_b64[i] = shuffled_b64[i];
}
original_b64[60] = '\0';
//printf("\n");
// Step 4: Base64 解码
uint8_t encrypted_bytes[44];
int decoded_len = base64_decode(original_b64, encrypted_bytes);
if (decoded_len != 44) {
return 1;
}
// Step 5: 重组为 11 个 uint32_t(小端序)
uint32_t block[11];
for (int i = 0; i < 11; i++) {
block[i] = ((uint32_t)encrypted_bytes[i * 4 + 0] << 0) |
((uint32_t)encrypted_bytes[i * 4 + 1] << 8) |
((uint32_t)encrypted_bytes[i * 4 + 2] << 16) |
((uint32_t)encrypted_bytes[i * 4 + 3] << 24);
}
// Step 6: 设置密钥 "Calamity_Fortune"(16 字节)
uint32_t key[4] = {
0x616C6143U,
0x7974696DU,
0x726F465FU,
0x656E7574U
};
// Step 7: 调用你提供的解密函数
decrypt_xxtea_variant(block, key);
// Step 8: 转回 flag 字符串(小端)
char flag[45] = { 0 };
for (int i = 0; i < 11; i++) {
flag[i * 4 + 0] = (block[i] >> 0) & 0xFF;
flag[i * 4 + 1] = (block[i] >> 8) & 0xFF;
flag[i * 4 + 2] = (block[i] >> 16) & 0xFF;
flag[i * 4 + 3] = (block[i] >> 24) & 0xFF;
}
printf("Flag: %s\n", flag);
return 0;
}
flag:0xGame{f279c1e7-8b0d-4a3b-9c6f-5e4d2a1b0c89}
0xGame2025Week3WriteUp
https://sydzi.github.io/2025/10/28/0xGame2025Week3ReverseWriteUp/