0xGame 2026 Re ez_upx wp
发布于 2026-09-29 06:52:00
使用DIE查看
语言: C/C++
打包工具: UPX(5.02)[LZMA,brute]
尝试使用UPX脱壳
在装好upx并配好环境变量后
upx -d attachment1.exe -o unpacked.exe
-d:decompress(解压缩/脱壳)。
告诉 UPX 当前要执行的是“解压”操作,也就是去掉壳,把程序还原成原始状态。如果没有这个参数,UPX 默认是去“压缩”文件。
-o:output(输出文件)。
指定脱壳后的新文件名。如果你不加 -o unpacked.exe,UPX 可能会直接覆盖你原来的
D:\MoeCTF.RE.Tools\temp>upx -d attachment1.exe -o unpacked.exe
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2026
UPX 5.2.1 Markus Oberhumer, Laszlo Molnar & John Reiser Aug 27th 2026
File size Ratio Format Name
-------------------- ------ ----------- -----------
17408 <- 10752 61.76% win64/pe unpacked.exe
Unpacked 1 file.
将unpacked.exe拖入ida
int __fastcall main(int argc, const char **argv, const char **envp)
{
FILE *v3; // rax
FILE *v4; // rax
char Buffer[136]; // [rsp+20h] [rbp-90h] BYREF
size_t v7; // [rsp+A8h] [rbp-8h]
sub_401A70(argc, argv, envp);
SetConsoleOutputCP(0xFDE9u);
printf("Input flag: ");
v3 = (FILE *)off_404070();
fflush(v3);
v4 = (FILE *)off_404070();
if ( fgets(Buffer, 128, v4) )
{
v7 = strcspn(Buffer, "\r\n");
Buffer[v7] = 0;
if ( (unsigned int)sub_40184A(Buffer) )
puts("Correct! Welcome to reverse engineering.");
else
puts("Wrong flag. Try again!");
puts("Press Enter to exit...");
getchar();
return 0;
}
else
{
puts("Input error.");
return 1;
}
}
逻辑很清晰:读取用户输入,去掉换行符后交给 sub_40184A 校验。若返回非 0 则输出 Correct!,否则输出 Wrong flag. Try again!。
跟进 sub_40184A,反编译如下:
_BOOL8 __fastcall sub_40184A(const char *a1)
{
char Str1[48]; // [rsp+20h] [rbp-50h] BYREF
_BYTE v3[32]; // [rsp+50h] [rbp-20h] BYREF
if ( strlen(a1) != 29 )
return 0;
sub_401550(a1, v3, 29);
return sub_4015C7(v3, 29, Str1) == 40 && strcmp(Str1, "RhExDlhVDSdGGEJHKRBGGmpbGFkBMGBgLkhXTkg=") == 0;
}
关键点:
-
长度校验:输入字符串长度必须为
29。 -
第一次处理:
sub_401550(a1, v3, 29),将输入a1处理后存入v3。 -
第二次处理:
sub_4015C7(v3, 29, Str1),将v3处理后存入Str1,返回值必须为40。 -
最终比较:
Str1必须与字符串"RhExDlhVDSdGGEJHKRBGGmpbGFkBMGBgLkhXTkg="完全相同。
返回值 40 和密文末尾的 = 提示我们:sub_4015C7 很可能是 Base64 编码。因为 29 字节的数据经过 Base64 编码后,长度正好是 ceil(29/3)*4 = 40。
跟进 sub_401550,反编译如下:
unsigned __int64 __fastcall sub_401550(__int64 a1, __int64 a2, unsigned __int64 a3)
{
unsigned __int64 result; // rax
unsigned __int64 i; // [rsp+8h] [rbp-8h]
for ( i = 0; ; ++i )
{
result = i;
if ( i >= a3 )
break;
*(_BYTE *)(a2 + i) = aVivo50[i % 6] ^ *(_BYTE *)(a1 + i);
}
return result;
}
这段代码就是循环异或:
-
a1是输入字符串,a2是输出缓冲区,a3是长度(29)。 -
循环中,
aVivo50[i % 6]表示密钥aVivo50循环使用,索引为i % 6。 -
在 IDA 中,
aVivo50是字符串"vivo50"的自动命名(因为该字符串在数据段中)。密钥长度为 6。 -
每个字节:
输出[i] = 输入[i] ^ 密钥[i % 6]。
因此,整个加密流程为:
text
输入 flag (29字节)
-> 循环异或密钥 "vivo50"
-> Base64 编码
-> 得到密文 "RhExDlhVDSdGGEJHKRBGGmpbGFkBMGBgLkhXTkg="
六、逆向解密获取 flag
既然已知加密流程,解密就是逆过程:
-
对密文进行 Base64 解码。
-
对解码后的字节进行 循环异或,密钥为
"vivo50"。
Python 脚本如下:
python
import base64
cipher_b64 = "RhExDlhVDSdGGEJHKRBGGmpbGFkBMGBgLkhXTkg="
key = b"vivo50"
# 1. Base64 解码
cipher = base64.b64decode(cipher_b64)
# 2. 循环异或解密
flag = bytes(c ^ key[i % len(key)] for i, c in enumerate(cipher))
print(flag.decode())
运行脚本,得到:
text
0xGame{N0www_y0u_kn0w_UPX!!!}
七、总结
本题是一道典型的 UPX 压缩壳 + 简单加密 逆向题。 解题步骤:
-
使用 DIE 识别 UPX 壳,用
upx -d一键脱壳。 -
IDA 静态分析,定位
main->sub_40184A。 -
分析出加密逻辑:
循环异或 "vivo50"+Base64 编码。 -
逆向操作:
Base64 解码+循环异或 "vivo50",得到 flag。
最终 flag:
text
0xGame{N0www_y0u_kn0w_UPX!!!}
使用DIE查看
语言: C/C++
打包工具: UPX(5.02)[LZMA,brute]
尝试使用UPX脱壳
在装好upx并配好环境变量后
upx -d attachment1.exe -o unpacked.exe
-d:decompress(解压缩/脱壳)。
告诉 UPX 当前要执行的是“解压”操作,也就是去掉壳,把程序还原成原始状态。如果没有这个参数,UPX 默认是去“压缩”文件。
-o:output(输出文件)。
指定脱壳后的新文件名。如果你不加 -o unpacked.exe,UPX 可能会直接覆盖你原来的
D:\MoeCTF.RE.Tools\temp>upx -d attachment1.exe -o unpacked.exe
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2026
UPX 5.2.1 Markus Oberhumer, Laszlo Molnar & John Reiser Aug 27th 2026
File size Ratio Format Name
-------------------- ------ ----------- -----------
17408 <- 10752 61.76% win64/pe unpacked.exe
Unpacked 1 file.
将unpacked.exe拖入ida
int __fastcall main(int argc, const char **argv, const char **envp)
{
FILE *v3; // rax
FILE *v4; // rax
char Buffer[136]; // [rsp+20h] [rbp-90h] BYREF
size_t v7; // [rsp+A8h] [rbp-8h]
sub_401A70(argc, argv, envp);
SetConsoleOutputCP(0xFDE9u);
printf("Input flag: ");
v3 = (FILE *)off_404070();
fflush(v3);
v4 = (FILE *)off_404070();
if ( fgets(Buffer, 128, v4) )
{
v7 = strcspn(Buffer, "\r\n");
Buffer[v7] = 0;
if ( (unsigned int)sub_40184A(Buffer) )
puts("Correct! Welcome to reverse engineering.");
else
puts("Wrong flag. Try again!");
puts("Press Enter to exit...");
getchar();
return 0;
}
else
{
puts("Input error.");
return 1;
}
}
逻辑很清晰:读取用户输入,去掉换行符后交给 sub_40184A 校验。若返回非 0 则输出 Correct!,否则输出 Wrong flag. Try again!。
跟进 sub_40184A,反编译如下:
_BOOL8 __fastcall sub_40184A(const char *a1)
{
char Str1[48]; // [rsp+20h] [rbp-50h] BYREF
_BYTE v3[32]; // [rsp+50h] [rbp-20h] BYREF
if ( strlen(a1) != 29 )
return 0;
sub_401550(a1, v3, 29);
return sub_4015C7(v3, 29, Str1) == 40 && strcmp(Str1, "RhExDlhVDSdGGEJHKRBGGmpbGFkBMGBgLkhXTkg=") == 0;
}
关键点:
-
长度校验:输入字符串长度必须为
29。 -
第一次处理:
sub_401550(a1, v3, 29),将输入a1处理后存入v3。 -
第二次处理:
sub_4015C7(v3, 29, Str1),将v3处理后存入Str1,返回值必须为40。 -
最终比较:
Str1必须与字符串"RhExDlhVDSdGGEJHKRBGGmpbGFkBMGBgLkhXTkg="完全相同。
返回值 40 和密文末尾的 = 提示我们:sub_4015C7 很可能是 Base64 编码。因为 29 字节的数据经过 Base64 编码后,长度正好是 ceil(29/3)*4 = 40。
跟进 sub_401550,反编译如下:
unsigned __int64 __fastcall sub_401550(__int64 a1, __int64 a2, unsigned __int64 a3)
{
unsigned __int64 result; // rax
unsigned __int64 i; // [rsp+8h] [rbp-8h]
for ( i = 0; ; ++i )
{
result = i;
if ( i >= a3 )
break;
*(_BYTE *)(a2 + i) = aVivo50[i % 6] ^ *(_BYTE *)(a1 + i);
}
return result;
}
这段代码就是循环异或:
-
a1是输入字符串,a2是输出缓冲区,a3是长度(29)。 -
循环中,
aVivo50[i % 6]表示密钥aVivo50循环使用,索引为i % 6。 -
在 IDA 中,
aVivo50是字符串"vivo50"的自动命名(因为该字符串在数据段中)。密钥长度为 6。 -
每个字节:
输出[i] = 输入[i] ^ 密钥[i % 6]。
因此,整个加密流程为:
text
输入 flag (29字节)
-> 循环异或密钥 "vivo50"
-> Base64 编码
-> 得到密文 "RhExDlhVDSdGGEJHKRBGGmpbGFkBMGBgLkhXTkg="
六、逆向解密获取 flag
既然已知加密流程,解密就是逆过程:
-
对密文进行 Base64 解码。
-
对解码后的字节进行 循环异或,密钥为
"vivo50"。
Python 脚本如下:
python
import base64
cipher_b64 = "RhExDlhVDSdGGEJHKRBGGmpbGFkBMGBgLkhXTkg="
key = b"vivo50"
# 1. Base64 解码
cipher = base64.b64decode(cipher_b64)
# 2. 循环异或解密
flag = bytes(c ^ key[i % len(key)] for i, c in enumerate(cipher))
print(flag.decode())
运行脚本,得到:
text
0xGame{N0www_y0u_kn0w_UPX!!!}
七、总结
本题是一道典型的 UPX 压缩壳 + 简单加密 逆向题。 解题步骤:
-
使用 DIE 识别 UPX 壳,用
upx -d一键脱壳。 -
IDA 静态分析,定位
main->sub_40184A。 -
分析出加密逻辑:
循环异或 "vivo50"+Base64 编码。 -
逆向操作:
Base64 解码+循环异或 "vivo50",得到 flag。
最终 flag:
text
0xGame{N0www_y0u_kn0w_UPX!!!}
使用DIE查看
语言: C/C++
打包工具: UPX(5.02)[LZMA,brute]
尝试使用UPX脱壳
在装好upx并配好环境变量后
upx -d attachment1.exe -o unpacked.exe
-d:decompress(解压缩/脱壳)。
告诉 UPX 当前要执行的是“解压”操作,也就是去掉壳,把程序还原成原始状态。如果没有这个参数,UPX 默认是去“压缩”文件。
-o:output(输出文件)。
指定脱壳后的新文件名。如果你不加 -o unpacked.exe,UPX 可能会直接覆盖你原来的
D:\MoeCTF.RE.Tools\temp>upx -d attachment1.exe -o unpacked.exe
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2026
UPX 5.2.1 Markus Oberhumer, Laszlo Molnar & John Reiser Aug 27th 2026
File size Ratio Format Name
-------------------- ------ ----------- -----------
17408 <- 10752 61.76% win64/pe unpacked.exe
Unpacked 1 file.
将unpacked.exe拖入ida
int __fastcall main(int argc, const char **argv, const char **envp)
{
FILE *v3; // rax
FILE *v4; // rax
char Buffer[136]; // [rsp+20h] [rbp-90h] BYREF
size_t v7; // [rsp+A8h] [rbp-8h]
sub_401A70(argc, argv, envp);
SetConsoleOutputCP(0xFDE9u);
printf("Input flag: ");
v3 = (FILE *)off_404070();
fflush(v3);
v4 = (FILE *)off_404070();
if ( fgets(Buffer, 128, v4) )
{
v7 = strcspn(Buffer, "\r\n");
Buffer[v7] = 0;
if ( (unsigned int)sub_40184A(Buffer) )
puts("Correct! Welcome to reverse engineering.");
else
puts("Wrong flag. Try again!");
puts("Press Enter to exit...");
getchar();
return 0;
}
else
{
puts("Input error.");
return 1;
}
}
逻辑很清晰:读取用户输入,去掉换行符后交给 sub_40184A 校验。若返回非 0 则输出 Correct!,否则输出 Wrong flag. Try again!。
跟进 sub_40184A,反编译如下:
_BOOL8 __fastcall sub_40184A(const char *a1)
{
char Str1[48]; // [rsp+20h] [rbp-50h] BYREF
_BYTE v3[32]; // [rsp+50h] [rbp-20h] BYREF
if ( strlen(a1) != 29 )
return 0;
sub_401550(a1, v3, 29);
return sub_4015C7(v3, 29, Str1) == 40 && strcmp(Str1, "RhExDlhVDSdGGEJHKRBGGmpbGFkBMGBgLkhXTkg=") == 0;
}
关键点:
-
长度校验:输入字符串长度必须为
29。 -
第一次处理:
sub_401550(a1, v3, 29),将输入a1处理后存入v3。 -
第二次处理:
sub_4015C7(v3, 29, Str1),将v3处理后存入Str1,返回值必须为40。 -
最终比较:
Str1必须与字符串"RhExDlhVDSdGGEJHKRBGGmpbGFkBMGBgLkhXTkg="完全相同。
返回值 40 和密文末尾的 = 提示我们:sub_4015C7 很可能是 Base64 编码。因为 29 字节的数据经过 Base64 编码后,长度正好是 ceil(29/3)*4 = 40。
跟进 sub_401550,反编译如下:
unsigned __int64 __fastcall sub_401550(__int64 a1, __int64 a2, unsigned __int64 a3)
{
unsigned __int64 result; // rax
unsigned __int64 i; // [rsp+8h] [rbp-8h]
for ( i = 0; ; ++i )
{
result = i;
if ( i >= a3 )
break;
*(_BYTE *)(a2 + i) = aVivo50[i % 6] ^ *(_BYTE *)(a1 + i);
}
return result;
}
这段代码就是循环异或:
-
a1是输入字符串,a2是输出缓冲区,a3是长度(29)。 -
循环中,
aVivo50[i % 6]表示密钥aVivo50循环使用,索引为i % 6。 -
在 IDA 中,
aVivo50是字符串"vivo50"的自动命名(因为该字符串在数据段中)。密钥长度为 6。 -
每个字节:
输出[i] = 输入[i] ^ 密钥[i % 6]。
因此,整个加密流程为:
text
输入 flag (29字节)
-> 循环异或密钥 "vivo50"
-> Base64 编码
-> 得到密文 "RhExDlhVDSdGGEJHKRBGGmpbGFkBMGBgLkhXTkg="
六、逆向解密获取 flag
既然已知加密流程,解密就是逆过程:
-
对密文进行 Base64 解码。
-
对解码后的字节进行 循环异或,密钥为
"vivo50"。
Python 脚本如下:
python
import base64
cipher_b64 = "RhExDlhVDSdGGEJHKRBGGmpbGFkBMGBgLkhXTkg="
key = b"vivo50"
# 1. Base64 解码
cipher = base64.b64decode(cipher_b64)
# 2. 循环异或解密
flag = bytes(c ^ key[i % len(key)] for i, c in enumerate(cipher))
print(flag.decode())
运行脚本,得到:
text
0xGame{N0www_y0u_kn0w_UPX!!!}
七、总结
本题是一道典型的 UPX 压缩壳 + 简单加密 逆向题。 解题步骤:
-
使用 DIE 识别 UPX 壳,用
upx -d一键脱壳。 -
IDA 静态分析,定位
main->sub_40184A。 -
分析出加密逻辑:
循环异或 "vivo50"+Base64 编码。 -
逆向操作:
Base64 解码+循环异或 "vivo50",得到 flag。
最终 flag:
text
0xGame{N0www_y0u_kn0w_UPX!!!}
使用DIE查看
语言: C/C++
打包工具: UPX(5.02)[LZMA,brute]
尝试使用UPX脱壳
在装好upx并配好环境变量后
upx -d attachment1.exe -o unpacked.exe
-d:decompress(解压缩/脱壳)。
告诉 UPX 当前要执行的是“解压”操作,也就是去掉壳,把程序还原成原始状态。如果没有这个参数,UPX 默认是去“压缩”文件。
-o:output(输出文件)。
指定脱壳后的新文件名。如果你不加 -o unpacked.exe,UPX 可能会直接覆盖你原来的
D:\MoeCTF.RE.Tools\temp>upx -d attachment1.exe -o unpacked.exe
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2026
UPX 5.2.1 Markus Oberhumer, Laszlo Molnar & John Reiser Aug 27th 2026
File size Ratio Format Name
-------------------- ------ ----------- -----------
17408 <- 10752 61.76% win64/pe unpacked.exe
Unpacked 1 file.
将unpacked.exe拖入ida
int __fastcall main(int argc, const char **argv, const char **envp)
{
FILE *v3; // rax
FILE *v4; // rax
char Buffer[136]; // [rsp+20h] [rbp-90h] BYREF
size_t v7; // [rsp+A8h] [rbp-8h]
sub_401A70(argc, argv, envp);
SetConsoleOutputCP(0xFDE9u);
printf("Input flag: ");
v3 = (FILE *)off_404070();
fflush(v3);
v4 = (FILE *)off_404070();
if ( fgets(Buffer, 128, v4) )
{
v7 = strcspn(Buffer, "\r\n");
Buffer[v7] = 0;
if ( (unsigned int)sub_40184A(Buffer) )
puts("Correct! Welcome to reverse engineering.");
else
puts("Wrong flag. Try again!");
puts("Press Enter to exit...");
getchar();
return 0;
}
else
{
puts("Input error.");
return 1;
}
}
逻辑很清晰:读取用户输入,去掉换行符后交给 sub_40184A 校验。若返回非 0 则输出 Correct!,否则输出 Wrong flag. Try again!。
跟进 sub_40184A,反编译如下:
_BOOL8 __fastcall sub_40184A(const char *a1)
{
char Str1[48]; // [rsp+20h] [rbp-50h] BYREF
_BYTE v3[32]; // [rsp+50h] [rbp-20h] BYREF
if ( strlen(a1) != 29 )
return 0;
sub_401550(a1, v3, 29);
return sub_4015C7(v3, 29, Str1) == 40 && strcmp(Str1, "RhExDlhVDSdGGEJHKRBGGmpbGFkBMGBgLkhXTkg=") == 0;
}
关键点:
-
长度校验:输入字符串长度必须为
29。 -
第一次处理:
sub_401550(a1, v3, 29),将输入a1处理后存入v3。 -
第二次处理:
sub_4015C7(v3, 29, Str1),将v3处理后存入Str1,返回值必须为40。 -
最终比较:
Str1必须与字符串"RhExDlhVDSdGGEJHKRBGGmpbGFkBMGBgLkhXTkg="完全相同。
返回值 40 和密文末尾的 = 提示我们:sub_4015C7 很可能是 Base64 编码。因为 29 字节的数据经过 Base64 编码后,长度正好是 ceil(29/3)*4 = 40。
跟进 sub_401550,反编译如下:
unsigned __int64 __fastcall sub_401550(__int64 a1, __int64 a2, unsigned __int64 a3)
{
unsigned __int64 result; // rax
unsigned __int64 i; // [rsp+8h] [rbp-8h]
for ( i = 0; ; ++i )
{
result = i;
if ( i >= a3 )
break;
*(_BYTE *)(a2 + i) = aVivo50[i % 6] ^ *(_BYTE *)(a1 + i);
}
return result;
}
这段代码就是循环异或:
-
a1是输入字符串,a2是输出缓冲区,a3是长度(29)。 -
循环中,
aVivo50[i % 6]表示密钥aVivo50循环使用,索引为i % 6。 -
在 IDA 中,
aVivo50是字符串"vivo50"的自动命名(因为该字符串在数据段中)。密钥长度为 6。 -
每个字节:
输出[i] = 输入[i] ^ 密钥[i % 6]。
因此,整个加密流程为:
text
输入 flag (29字节)
-> 循环异或密钥 "vivo50"
-> Base64 编码
-> 得到密文 "RhExDlhVDSdGGEJHKRBGGmpbGFkBMGBgLkhXTkg="
六、逆向解密获取 flag
既然已知加密流程,解密就是逆过程:
-
对密文进行 Base64 解码。
-
对解码后的字节进行 循环异或,密钥为
"vivo50"。
Python 脚本如下:
python
import base64
cipher_b64 = "RhExDlhVDSdGGEJHKRBGGmpbGFkBMGBgLkhXTkg="
key = b"vivo50"
# 1. Base64 解码
cipher = base64.b64decode(cipher_b64)
# 2. 循环异或解密
flag = bytes(c ^ key[i % len(key)] for i, c in enumerate(cipher))
print(flag.decode())
运行脚本,得到:
text
0xGame{N0www_y0u_kn0w_UPX!!!}
七、总结
本题是一道典型的 UPX 压缩壳 + 简单加密 逆向题。 解题步骤:
-
使用 DIE 识别 UPX 壳,用
upx -d一键脱壳。 -
IDA 静态分析,定位
main->sub_40184A。 -
分析出加密逻辑:
循环异或 "vivo50"+Base64 编码。 -
逆向操作:
Base64 解码+循环异或 "vivo50",得到 flag。
最终 flag:
text
0xGame{N0www_y0u_kn0w_UPX!!!}
使用DIE查看
语言: C/C++
打包工具: UPX(5.02)[LZMA,brute]
尝试使用UPX脱壳
在装好upx并配好环境变量后
upx -d attachment1.exe -o unpacked.exe
-d:decompress(解压缩/脱壳)。
告诉 UPX 当前要执行的是“解压”操作,也就是去掉壳,把程序还原成原始状态。如果没有这个参数,UPX 默认是去“压缩”文件。
-o:output(输出文件)。
指定脱壳后的新文件名。如果你不加 -o unpacked.exe,UPX 可能会直接覆盖你原来的
D:\MoeCTF.RE.Tools\temp>upx -d attachment1.exe -o unpacked.exe
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2026
UPX 5.2.1 Markus Oberhumer, Laszlo Molnar & John Reiser Aug 27th 2026
File size Ratio Format Name
-------------------- ------ ----------- -----------
17408 <- 10752 61.76% win64/pe unpacked.exe
Unpacked 1 file.
将unpacked.exe拖入ida
int __fastcall main(int argc, const char **argv, const char **envp)
{
FILE *v3; // rax
FILE *v4; // rax
char Buffer[136]; // [rsp+20h] [rbp-90h] BYREF
size_t v7; // [rsp+A8h] [rbp-8h]
sub_401A70(argc, argv, envp);
SetConsoleOutputCP(0xFDE9u);
printf("Input flag: ");
v3 = (FILE *)off_404070();
fflush(v3);
v4 = (FILE *)off_404070();
if ( fgets(Buffer, 128, v4) )
{
v7 = strcspn(Buffer, "\r\n");
Buffer[v7] = 0;
if ( (unsigned int)sub_40184A(Buffer) )
puts("Correct! Welcome to reverse engineering.");
else
puts("Wrong flag. Try again!");
puts("Press Enter to exit...");
getchar();
return 0;
}
else
{
puts("Input error.");
return 1;
}
}
逻辑很清晰:读取用户输入,去掉换行符后交给 sub_40184A 校验。若返回非 0 则输出 Correct!,否则输出 Wrong flag. Try again!。
跟进 sub_40184A,反编译如下:
_BOOL8 __fastcall sub_40184A(const char *a1)
{
char Str1[48]; // [rsp+20h] [rbp-50h] BYREF
_BYTE v3[32]; // [rsp+50h] [rbp-20h] BYREF
if ( strlen(a1) != 29 )
return 0;
sub_401550(a1, v3, 29);
return sub_4015C7(v3, 29, Str1) == 40 && strcmp(Str1, "RhExDlhVDSdGGEJHKRBGGmpbGFkBMGBgLkhXTkg=") == 0;
}
关键点:
-
长度校验:输入字符串长度必须为
29。 -
第一次处理:
sub_401550(a1, v3, 29),将输入a1处理后存入v3。 -
第二次处理:
sub_4015C7(v3, 29, Str1),将v3处理后存入Str1,返回值必须为40。 -
最终比较:
Str1必须与字符串"RhExDlhVDSdGGEJHKRBGGmpbGFkBMGBgLkhXTkg="完全相同。
返回值 40 和密文末尾的 = 提示我们:sub_4015C7 很可能是 Base64 编码。因为 29 字节的数据经过 Base64 编码后,长度正好是 ceil(29/3)*4 = 40。
跟进 sub_401550,反编译如下:
unsigned __int64 __fastcall sub_401550(__int64 a1, __int64 a2, unsigned __int64 a3)
{
unsigned __int64 result; // rax
unsigned __int64 i; // [rsp+8h] [rbp-8h]
for ( i = 0; ; ++i )
{
result = i;
if ( i >= a3 )
break;
*(_BYTE *)(a2 + i) = aVivo50[i % 6] ^ *(_BYTE *)(a1 + i);
}
return result;
}
这段代码就是循环异或:
-
a1是输入字符串,a2是输出缓冲区,a3是长度(29)。 -
循环中,
aVivo50[i % 6]表示密钥aVivo50循环使用,索引为i % 6。 -
在 IDA 中,
aVivo50是字符串"vivo50"的自动命名(因为该字符串在数据段中)。密钥长度为 6。 -
每个字节:
输出[i] = 输入[i] ^ 密钥[i % 6]。
因此,整个加密流程为:
text
输入 flag (29字节)
-> 循环异或密钥 "vivo50"
-> Base64 编码
-> 得到密文 "RhExDlhVDSdGGEJHKRBGGmpbGFkBMGBgLkhXTkg="
六、逆向解密获取 flag
既然已知加密流程,解密就是逆过程:
-
对密文进行 Base64 解码。
-
对解码后的字节进行 循环异或,密钥为
"vivo50"。
Python 脚本如下:
python
import base64
cipher_b64 = "RhExDlhVDSdGGEJHKRBGGmpbGFkBMGBgLkhXTkg="
key = b"vivo50"
# 1. Base64 解码
cipher = base64.b64decode(cipher_b64)
# 2. 循环异或解密
flag = bytes(c ^ key[i % len(key)] for i, c in enumerate(cipher))
print(flag.decode())
运行脚本,得到:
text
0xGame{N0www_y0u_kn0w_UPX!!!}
七、总结
本题是一道典型的 UPX 压缩壳 + 简单加密 逆向题。 解题步骤:
-
使用 DIE 识别 UPX 壳,用
upx -d一键脱壳。 -
IDA 静态分析,定位
main->sub_40184A。 -
分析出加密逻辑:
循环异或 "vivo50"+Base64 编码。 -
逆向操作:
Base64 解码+循环异或 "vivo50",得到 flag。
最终 flag:
text
0xGame{N0www_y0u_kn0w_UPX!!!}
使用DIE查看
语言: C/C++
打包工具: UPX(5.02)[LZMA,brute]
尝试使用UPX脱壳
在装好upx并配好环境变量后
upx -d attachment1.exe -o unpacked.exe
-d:decompress(解压缩/脱壳)。
告诉 UPX 当前要执行的是“解压”操作,也就是去掉壳,把程序还原成原始状态。如果没有这个参数,UPX 默认是去“压缩”文件。
-o:output(输出文件)。
指定脱壳后的新文件名。如果你不加 -o unpacked.exe,UPX 可能会直接覆盖你原来的
D:\MoeCTF.RE.Tools\temp>upx -d attachment1.exe -o unpacked.exe
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2026
UPX 5.2.1 Markus Oberhumer, Laszlo Molnar & John Reiser Aug 27th 2026
File size Ratio Format Name
-------------------- ------ ----------- -----------
17408 <- 10752 61.76% win64/pe unpacked.exe
Unpacked 1 file.
将unpacked.exe拖入ida
.rdata:0000000000405000 00000007 C vivo50
.rdata:0000000000405020 00000041 C ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
.rdata:0000000000405080 00000029 C RhExDlhVDSdGGEJHKRBGGmpbGFkBMGBgLkhXTkg=
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ 这是标准的 Base64 字符表
我们猜测
RhExDlhVDSdGGEJHKRBGGmpbGFkBMGBgLkhXTkg=是密文
vivo50 是密钥
不过要注意:单纯做 Base64 解密是看不到 flag 的,因为 Base64 解码出来是一串二进制乱码。真正的解密流程需要两步:
-
Base64 解码:把密文还原成二进制字节。
-
循环异或解密:用密钥
vivo50对二进制字节进行循环异或。
这也是 CTF 逆向中很经典的组合:Base64 + 异或。
你可以直接运行下面这段 Python 脚本验证:
python
import base64
# 从字符串表中提取的密文和密钥
b64_cipher = "RhExDlhVDSdGGEJHKRBGGmpbGFkBMGBgLkhXTkg="
key = b"vivo50"
# 1. Base64 解码
cipher_bytes = base64.b64decode(b64_cipher)
# 2. 循环异或解密
plain_bytes = bytes(c ^ key[i % len(key)] for i, c in enumerate(cipher_bytes))
# 输出结果
print(plain_bytes.decode())
0xGame{N0www_y0u_kn0w_UPX!!!}