[Week 1] BabyBase
文件为一个exe文件,直接运行一下,是让输入flag先随便输入看看有什么回显
有回显,但瞬间就结束了程序,下一步对其查壳分析一下程序
知道了是一个64位的程序,直接用IDA打开来分析它的伪代码
F5直接定位到它的主函数
通过分析上面的伪代码可以得知我们输入的flag被Str接收,v6是它的长度且要是42位,Str又被encode函数当成参数,猜测这里应该是加密flag,点开看看
__int64 __fastcall encode(__int64 a1, __int64 a2, int a3)
{
__int64 result; // rax
__int16 v4; // [rsp+4h] [rbp-Ch]
unsigned __int8 v5; // [rsp+6h] [rbp-Ah]
char v6; // [rsp+7h] [rbp-9h]
int j; // [rsp+8h] [rbp-8h]
int i; // [rsp+Ch] [rbp-4h]
v4 = 0;
v5 = 0;
v6 = 61;
for ( i = 0; ; ++i )
{
result = (unsigned int)(3 * i);
if ( a3 <= (int)result )
break;
for ( j = 0; j <= 2; ++j )
{
if ( a3 <= 3 * i + j )
*((_BYTE *)&v4 + j) = 0;
else
*((_BYTE *)&v4 + j) = *(_BYTE *)(a1 + 3 * i + j);
}
*(_BYTE *)(a2 + 4 * i) = table[(unsigned __int8)v4 >> 2];
*(_BYTE *)(a2 + 4 * i + 1i64) = table[((unsigned __int8)(16 * v4) | (HIBYTE(v4) >> 4)) & 0x3F];
if ( HIBYTE(v4) )
*(_BYTE *)(a2 + 4 * i + 2i64) = table[((unsigned __int8)(4 * HIBYTE(v4)) | (v5 >> 6)) & 0x3F];
else
*(_BYTE *)(a2 + 4 * i + 2i64) = v6;
if ( v5 )
*(_BYTE *)(a2 + 4 * i + 3i64) = table[v5 & 0x3F];
else
*(_BYTE *)(a2 + 4 * i + 3i64) = v6;
}
return result;
}
分析可得这就是一个Base64的加密过程,table是码表
此时大致已经可以知道了这道题的考点了,就是实现了一个Base64的加密过程,加密后的flag要与check_flag函数中的对比一样的才是正确的flag所以只要对"MHhHYW1le04wd195MHVfa24wd19CNHNlNjRfRW5jMGQxbmdfdzNsbCF9"进行Base64解密即可
总结:
这道题主要是要明白Base64的加密原理,为了让各位师傅们明白的清楚一点我用python实现一下Base64加解密原理:
#加密
def b64encode(str):
b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
b = ''
Mi = ''
for i in str:
b += format(ord(i),'08b') #在python中把一个数转换成二进制的形式的时候输出来的不是完整的8位python会把前面的零给抹除,所以要自己补,format这个函数比较实用'08b'的意思就是说把一个数转换成二进制不加'0b'前缀,不足8位在前面补零。
if len(b) % 6 != 0:
b = b + '0' * (6 - len(b) % 6) #注意这里在后面补0
for i in range(0, len(b), 6):
Mi += b64[int(b[i:i+6], 2)] # 这里就是转换了这里我没有在每6个前面补0因为也不会影响大小
if len(Mi) % 4 != 0:
Mi = Mi + '=' * (4 - len(Mi) % 4) # 补'='
return Mi
# 解密:
def b64decode(str):
b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
b = ''
key = ''
for i in range(1,3):
str = list(str) # 字符串是不能进行删除操作的所以要转换成列表
if str[len(str)-i] == '=':
str.remove('=') # 删除'='
str = ''.join(str) # 转换成字符串
for i in str:
b += format(b64.index(i), '06b') # 这里只要为6个就行,因为有两个是补的也不会影响大小
if len(b) % 8 != 0:
k = len(b) % 8
b = b[::-1][k:][::-1] # 这一步是为了去除补在后面的0
for i in range(0, len(b), 8):
key += chr(int(b[i:i + 8], 2)) # 解码
return key
[Week 1] BinaryMaster
题目提示:你做好准备成为二进制的大师了吗?
文件也是为一个exe文件,直接运行一下,是让输入 04242424的16进制先输入看看有什么回显,有个flag一闪而过并且结束了程序,看看输入其他的会怎么样没有结束程序,说明flag就在这个程序里面
查壳看看有没有壳
没有壳,直接用IDA打开,F5查看主函数
总结:签到题
[Week 1] SignSign
文件也是为一个exe文件,直接运行一下,看到了一半的flag,说明要在程序内部找到另外一半flag
查壳:
无壳,直接用IDA打开,F5定位到主函数
没有看到另外一半flag,那就看看整个程序的字符信息,快捷键Shift+F12
这就找到了另一半flag
总结:签到题
[Week 1] Xor-Beginning
题目提示:就从xor开始吧
文件为一个exe文件,直接运行一下,是让其输入flag,先随便输入,看看会回显什么
回显错误,还是得进程序内部看看,先进行查壳
无壳,直接用IDA打开,F5定位到主函数
int __fastcall main(int argc, const char **argv, const char **envp)
{
char v4[64]; // [rsp+20h] [rbp-70h] BYREF
char v5[40]; // [rsp+60h] [rbp-30h] BYREF
int v6; // [rsp+88h] [rbp-8h]
int v7; // [rsp+8Ch] [rbp-4h]
_main(argc, argv, envp);
v7 = 0;
v6 = 0;
qmemcpy(v5, "~5\v*',3", 7);//前七位
v5[7] = 31;
v5[8] = 118;
v5[9] = 55;
v5[10] = 27;
v5[11] = 114;
v5[12] = 49;
v5[13] = 30;
v5[14] = 54;
v5[15] = 12;
v5[16] = 76;
v5[17] = 68;
v5[18] = 99;
v5[19] = 114;
v5[20] = 87;
v5[21] = 73;
v5[22] = 8;
v5[23] = 69;
v5[24] = 66;
v5[25] = 1;
v5[26] = 90;
v5[27] = 4;
v5[28] = 19;
v5[29] = 76;
printf("你的flag是什么? \n请在这里输入:");
scanf("%s", v4);//接收输入
while ( v4[v7] )
{
v4[v7] ^= 78 - (_BYTE)v7;//进行加密
++v7;
}
while ( v6 < v7 )
{
if ( v4[v6] != (unsigned __int8)v5[v6] || v7 != 30 ) //判断长度与是否加密后是否与v5相同
{
printf("\n错误, 请重试! ");
system("pause");
exit(0);
}
++v6;
}
puts("\n正确!");
system("pause");
return 0;
}
根据上面分析可得:只要获取v5的值然后对加密进行逆转就行,flag的长度位30
一个一个收集v5的值比较麻烦,这时候就要动调了,因为这是一个exe文件所以用下面这个
下断点,在存入v5后都行
双击点v5,全部选中后Shift+E收集
现在有了数据就可以写脚本了
key = [0x7E, 0x35, 0x0B, 0x2A, 0x27, 0x2C, 0x33, 0x1F, 0x76, 0x37,
0x1B, 0x72, 0x31, 0x1E, 0x36, 0x0C, 0x4C, 0x44, 0x63, 0x72,
0x57, 0x49, 0x08, 0x45, 0x42, 0x01, 0x5A, 0x04, 0x13, 0x4C]
flag = ''
for i in range(len(key)):
flag += chr(key[i]^(78-i))
print(flag)
总结:只要明白一些IDA的基本操作,与反异或就行:a^b=c --> c^b=a -->a^c=b
[Week 1] Xor-Endian
题目提示:刚开始就end了吗?
是一个Linux程序,先对其进行查壳
无壳,直接用IDA打开,F5定位到主函数
int __fastcall main(int argc, const char **argv, const char **envp)
{
int i; // [rsp+8h] [rbp-78h]
int v6[12]; // [rsp+10h] [rbp-70h]
_DWORD v7[14]; // [rsp+40h] [rbp-40h] BYREF
unsigned __int64 v8; // [rsp+78h] [rbp-8h]
v8 = __readfsqword(0x28u);
puts(s);
__isoc99_scanf(&unk_201F, v7);//接收输入的flag
v6[0] = 1363025275;
v6[1] = 253370901;
v6[2] = 1448151638;
v6[3] = 1415391232;
v6[4] = 91507463;
v6[5] = 139743552;
v6[6] = 1450318164;
v6[7] = 1985283101;
v6[8] = 1465125718;
v6[9] = 1934953223;
v6[10] = 84430593;
v6[11] = 0;
encrypt(v7, "Key0xGame2024", 44LL, 13LL);//关键函数
for ( i = 0; i <= 11; ++i )//对加密后的flag进行判断
{
if ( v7[i] - v6[i] )
{
puts("wrong!");
return 0;
}
}
puts("right!");
return 0;
}
__int64 __fastcall encrypt(__int64 a1, __int64 a2, int a3, int a4)
{
int i; // [rsp+24h] [rbp-4h]
for ( i = 0; i < a3; ++i )
*(_BYTE *)(i + a1) ^= *(_BYTE *)(i % a4 + a2);//进行异或加密,但是要注意数据类型
return 0LL;
}
加密后的flag数据类型是char,v6是小端序的,解密只要对其进行反异或即可
一个一个收集v6的值也比较麻烦那就动调收集,不动调操作不了
与exe文件操作不同,要先在IDA中找到linux_server64,放入Linux系统里面,还有要把程序文件放进去与其在同一目录下
然后按照以下操作
下断点
动调
输入值
F8单步步出,到下面即可,就是把v6的值存储完
双击v6,然后获取值
解密:
key = 'Key0xGame2024'
hot = [ 0x7B, 0x1D, 0x3E, 0x51, 0x15, 0x22, 0x1A, 0x0F, 0x56, 0x0A,
0x51, 0x56, 0x00, 0x28, 0x5D, 0x54, 0x07, 0x4B, 0x74, 0x05,
0x40, 0x51, 0x54, 0x08, 0x54, 0x19, 0x72, 0x56, 0x1D, 0x04,
0x55, 0x76, 0x56, 0x0B, 0x54, 0x57, 0x07, 0x0B, 0x55, 0x73,
0x01, 0x4F, 0x08, 0x05]
flag = ''
for i in range(len(hot)):
flag += chr(hot[i] ^ ord(key[i%len(key)]))
print(flag)
总结:运用了小端序的,远程联调,异或
补充:小端序将数据的低位字节存放在内存的低地址处,而高位字节则存放在内存的高地址处,更符合计算机读取内存的方式,因为CPU读取内存中的数据时,通常是从低地址向高地址方向进行的。
[Week 2] BabyUPX
题目提示:牢不可破 ?)的外壳
根据题目提示说明这个程序加了壳,对其进行查壳看看是什么类型的壳
一个简单的压缩壳,破壳可以使用两种方式一种是直接用脚本工具,另一种是自己脱壳,第一种有点无脑,那就用第二种,需要用到dbg调试器
压缩壳有个特性就是会自动解压,也就是说我们只要把解压后的程序保存下来就是脱壳的了,所以就需要找到那一部分
找到压栈入口
出栈时也会运行到这里我们只要在这下个硬件断点即可
运行到这:
转到内存窗口
下断点
运行,达到下一次到这
运行到出栈后
然后保存为一个新文件
成功
用IDA打开
看到没有什么信息,Shift+F12看看程序字符
看到类似flag的信息,双击进入,然后交叉引用
定位到了这
__int64 sub_4015B5()
{
char v1[60]; // [rsp+20h] [rbp-40h] BYREF
int v2; // [rsp+5Ch] [rbp-4h]
sub_401720();
v2 = 0;
sub_402B60(aInputFlagHere);
sub_402B58("%s", v1);//输入flag
v2 = sub_402B38(v1, a0xgame, 6i64);//判断前面一部分
if ( !v2 && sub_402B40(v1) == 44 ) //判断flag的长度与前面一部分是否正确
{
sub_401550(v1);//加密
if ( !(unsigned int)sub_402B48(v1, qword_403020) ) //判断加密后的flag是否正确
sub_402B68(aSucceed);
return 0i64;
}
else
{
sub_402B68(aInvalid);
return 0i64;
}
}
主要函数在于sub_401550
__int64 __fastcall sub_401550(_BYTE *a1)
{
_BYTE *v2; // [rsp+8h] [rbp-8h]
v2 = a1;
if ( !*a1 )
return 0xFFFFFFFFi64;
do
{
*v2 = (16 * *v2) | (*v2 >> 4);
++v2;
}
while ( *v2 );
return 0i64;
}
分析可得加密方式为字节倒转,就是把一个字节的后4位与前4位调换了位置,所以只要把它调换回来就是flag,加密后的数据是qword_403020
解密:
def swap_nibbles(byte):
return ((byte & 0x0F) << 4) | ((byte & 0xF0) >> 4)
data = [
0x03, 0x87, 0x74, 0x16, 0xD6, 0x56, 0xB7, 0x63, 0x83, 0x46,
0x66, 0x66, 0x43, 0x53, 0x83, 0xD2, 0x23, 0x93, 0x56, 0x53,
0xD2, 0x43, 0x36, 0x36, 0x03, 0xD2, 0x16, 0x93, 0x36, 0x26,
0xD2, 0x93, 0x73, 0x13, 0x66, 0x56, 0x36, 0x33, 0x33, 0x83,
0x56, 0x23, 0x66, 0xD7
]
swapped_data = [swap_nibbles(byte) for byte in data]
result = ''.join(chr(byte) for byte in swapped_data)
print("交换后的字符串:")
print(result)
总结:UPX壳,字节加密
补充:这里说一下魔改的UPX,就是把这个文件的数据改了,可以识别为UPX壳但脚本工具破不开,要把它给改回来,比如
正常的是下面这些
破壳不了的时候要注意这些
[Week 2] FirstSight-Jar
所给文件是一个jar文件,直接用jadx打开
package defpackage;
import java.util.Scanner;
/* renamed from: EzJar reason: default package */
/* loaded from: EzJar.jar:EzJar.class */
public class EzJar {
static String Alphabat = "0123456789abcdef";
private static String encrypt(String str) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
int indexOf = Alphabat.indexOf(str.charAt(i));
if (indexOf < 0) {
sb.append(str.charAt(i));
} else {
sb.append(Alphabat.charAt(((indexOf * 5) + 3) % 16));
}
}
return sb.toString();
}
public static void main(String[] strArr) {
System.out.println("请以uuid的格式输入你的flag内容:");
Scanner scanner = new Scanner(System.in);
String nextLine = scanner.nextLine();
scanner.close();
if (encrypt(nextLine).equals("ab50e920-4a97-70d1-b646-cdac5c873376")) {
System.out.println(String.format("验证成功: 0xGame{%s}", nextLine));
} else {
System.out.println("验证失败");
}
}
}
分析:这段代码是一个简单的加密验证程序,程序定义了一个静态字符串 Alphabat,包含16个字符,encrypt 方法是核心加密函数:它遍历输入字符串的每个字符,如果字符在 Alphabat 中,它会被替换:找到字符在 Alphabat 中的索引 indexOf用公式 ((indexOf * 5) + 3) % 16 计算新的索引用 Alphabat 中对应新索引的字符替换原字符如果字符不在 Alphabat 中,保持不变,main 方法是程序的入口:提示用户输入flag(格式为UUID)对输入进行加密比较加密结果是否等于 "ab50e920-4a97-70d1-b646-cdac5c873376"如果相等,验证成功;否则失败,所以我们需要逆向 encrypt 函数。
解密:
a = "0123456789abcdef"
k = "ab50e920-4a97-70d1-b646-cdac5c873376"
for i in range(0,len(k)):
if k[i] in a:
for j in range(16):
if ((j * 5) + 3) % 16 == a.index(k[i]):
print(a[j],end='')
else:
print(k[i],end='')
总结:就是一个加密程序,逆过来就行。
[Week 2] FisrtSight-Pyc
文件是一个pyc文件(是 Python 解释器在将 Python 源代码(.py 文件)编译成字节码后生成的文件),可以利用uncompyle6来反编译为py文件(uncompyle6 是一个强大的 Python 代码反编译器,它能够将 .pyc 文件(Python 字节码文件)转换回相应的 .py 源代码文件)
把编译的写入o.py里面
# uncompyle6 version 3.9.2
# Python bytecode version base 3.8.0 (3413)
# Decompiled from: Python 3.12.3 (tags/v3.12.3:f6650f9, Apr 9 2024, 14:05:25) [MSC v.1938 64 bit (AMD64)]
# Embedded file name: D:\PortPython3.8.9\App\Python\0xgame\EzPyc.py
# Compiled at: 2024-09-10 22:18:53
# Size of source mod 2**32: 519 bytes
import hashlib
user_input = input("请输入神秘代号:")
if user_input != "Ciallo~":
print("代号不是这个哦")
exit()
input_hash = hashlib.md5(user_input.encode()).hexdigest()
input_hash = list(input_hash)
for i in range(len(input_hash)):
if ord(input_hash[i]) in range(48, 58):
original_num = int(input_hash[i])
new_num = (original_num + 5) % 10
input_hash[i] = str(new_num)
input_hash = "".join(input_hash)
print("0xGame{{{}}}".format(input_hash))
直接运行会报错
给出的flag也是错的,还有一部分没有被加上
解密:
import hashlib
user_input = "Ciallo~"
input_hash = hashlib.md5(user_input.encode()).hexdigest()
input_hash = list(input_hash)
flag = ''
for i in range(len(input_hash)):
if ord(input_hash[i]) in range(48, 58):
original_num = int(input_hash[i])
new_num = (original_num + 5) % 10
flag += str(new_num)
else:
flag += input_hash[i]
print("0xGame{{{}}}".format(flag))
总结:python理解
[Week 2] Xor::Ramdom
题目提示:很幸运,你又遇到xor了,这次xor带来了一些随机数,(它保证这些随机数不是从外星来的)你能发现其中的奥秘吗?
是一个exe文件,运行一下
发现运行不了,说明这题不能动调,再查壳
无壳,IDA打开,F5定位到主函数
int __fastcall main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rax
char v4; // bl
bool v5; // si
bool v6; // bl
_BYTE *v7; // rdx
char v8; // al
int v9; // ebx
__int64 v10; // rax
__int64 v11; // rax
__int64 v13[4]; // [rsp+20h] [rbp-60h] BYREF
char v14[32]; // [rsp+40h] [rbp-40h] BYREF
char v15[46]; // [rsp+60h] [rbp-20h] BYREF
char v16; // [rsp+8Eh] [rbp+Eh] BYREF
char v17; // [rsp+8Fh] [rbp+Fh] BYREF
char v18[32]; // [rsp+90h] [rbp+10h] BYREF
char v19[32]; // [rsp+B0h] [rbp+30h] BYREF
char v20[39]; // [rsp+D0h] [rbp+50h] BYREF
char v21; // [rsp+F7h] [rbp+77h]
int inited; // [rsp+F8h] [rbp+78h]
int v23; // [rsp+FCh] [rbp+7Ch]
_main(argc, argv, envp);
inited = 0;
v23 = 0;
v21 = 0;
std::allocator<char>::allocator(&v16);
std::string::basic_string(v15, 40i64, 0i64, &v16);
std::allocator<char>::~allocator(&v16);
std::allocator<char>::allocator(&v17);
std::string::basic_string(v14, 32i64, 0i64, &v17);
std::allocator<char>::~allocator(&v17);
v13[0] = 0x1221164E1F104F0Ci64;
v13[1] = 0x171F240A4B10244Bi64;
v13[2] = 0x1A2C5C2108074F09i64;
v13[3] = 99338668810000i64;
v3 = std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Input flag here:");
std::ostream::operator<<(v3, refptr__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_);
std::operator>><char>(refptr__ZSt3cin, v15);
v4 = 0;
v5 = 1;
if ( std::string::size(v15) == 38 )
{
std::string::substr(v18, v15, 0i64, 7i64);
v4 = 1;
if ( !(unsigned __int8)std::operator!=<char>(v18, "0xGame{") && *(_BYTE *)std::string::operator[](v15, 37i64) == 125 )
v5 = 0;
}
if ( v4 )
std::string::~string(v18);
if ( v5 )
exit(0);
std::string::substr(v19, v15, 7i64, 30i64);
std::string::operator=(v14, v19);
std::string::~string(v19);
inited = init_random();
std::string::basic_string(v20, v14);
v6 = (unsigned int)check(v20) != 0;
std::string::~string(v20);
if ( v6 )
{
srand(0x1919810u);
inited = rand();
}
v21 = rand();
do
{
v7 = (_BYTE *)std::string::operator[](v14, v23);
if ( (v23 & 1) != 0 )
v8 = v21;
else
v8 = v21 + 3;
*v7 ^= v8;
v9 = *(char *)std::string::operator[](v14, v23);
if ( v9 != *(unsigned __int8 *)std::array<unsigned char,32ull>::operator[](v13, v23) )
{
v10 = std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Wrong.Try again");
std::ostream::operator<<(v10, refptr__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_);
exit(0);
}
++v23;
}
while ( v23 <= 29 );
v11 = std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Right!");
std::ostream::operator<<(v11, refptr__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_);
std::string::~string(v14);
std::string::~string(v15);
return 0;
}
int init_random(void)
{
srand(0x77u);
return rand();
}
_BOOL8 __fastcall check(__int64 a1)
{
return std::string::size(a1) != 30;
}
分析:程序要求输入一个 38 字符长的字符串,必须以 "0xGame{" 开头,以 "}" 结尾,中间包含 30 个字符,这是实际需要验证的部分,随机数生成:使用 init_random() 函数初始化随机数生成器,种子固定为 0x77(119)。
check() 函数检查提取的字符串是否为 30 个字符,这里总是返回 false,由于 check() 返回 false,程序使用 init_random() 初始化的随机数序列。
核心验证逻辑:程序使用一个预设的 32 字节数组 v13 作为验证基准,遍历输入的 30 个字符,对每个字符进行异或操作:奇数索引位置与 v21 异或,偶数索引位置与 v21 + 3 异或,异或后的结果与 v13 数组中对应位置的字节比较,这里也是小端序的,解密只要反异或就行,关键在于随机数的生成。
解密:
#include <stdio.h>
#include <stdlib.h>
int init_random(void)
{
srand(0x77u);
return rand();
}
int main() {
unsigned char v13[] = {
0x0C, 0x4F, 0x10, 0x1F, 0x4E, 0x16, 0x21, 0x12,
0x4B, 0x24, 0x10, 0x4B, 0x0A, 0x24, 0x1F, 0x17,
0x09, 0x4F, 0x07, 0x08, 0x21, 0x5C, 0x2C, 0x1A,
0x10, 0x1F, 0x11, 0x16, 0x59, 0x5A
};
init_random(); // 第一次调用rand(),结果被丢弃
char v21 = rand(); // 第二次调用rand(),结果用于异或操作
char flag[31] = {0};
for (int v23 = 0; v23 <= 29; v23++)
{
unsigned char v8 = (v23 & 1) != 0 ? v21 : v21 + 3;
flag[v23] = v13[v23] ^ v8;
}
printf("0xGame{%s}\n", flag);
return 0;
}
总结:主要还是要搞懂代码意思,加密方式并不难。
[Week 2] ZzZ
题目提示:怎么这些函数名字长得都一样啊,已经看的要昏昏欲睡了,听说如果能找到失散的main函数,就有机会用神器Solver解开一切的谜团。
是一个exe文件,运行一下
运行不了,不能动调,查壳
无壳,直接用IDA打开
没有main函数,那就去看看字符
有类似flag的信息,直接交叉引用定位
__int64 sub_140011AA0()
{
char *v0; // rdi
__int64 i; // rcx
char v3[32]; // [rsp+0h] [rbp-40h] BYREF
char v4; // [rsp+40h] [rbp+0h] BYREF
unsigned int v5[8]; // [rsp+44h] [rbp+4h] BYREF
unsigned int v6[8]; // [rsp+64h] [rbp+24h] BYREF
unsigned int v7[9]; // [rsp+84h] [rbp+44h] BYREF
char *Buffer; // [rsp+A8h] [rbp+68h]
char v9[80]; // [rsp+C8h] [rbp+88h] BYREF
__int64 v10; // [rsp+118h] [rbp+D8h]
__int64 v11; // [rsp+138h] [rbp+F8h]
__int64 v12; // [rsp+158h] [rbp+118h]
__int64 v13[4]; // [rsp+178h] [rbp+138h] BYREF
__int64 v14[26]; // [rsp+198h] [rbp+158h] BYREF
v0 = &v4;
for ( i = 94i64; i; --i )
{
*(_DWORD *)v0 = -858993460;
v0 += 4;
}
sub_140011393(&unk_140023008);
sub_1400111A9("Please enter your flag\nTip: The flag format is 'uuid'\n");
sub_1400110A0("%44s", v9);//输入flag
if ( v9[43] != 125 )
goto LABEL_11;
sub_14001128F(v9, "0xGame{%8llx-%4s-%4s-%4s-%12llx}", v13, (const char *)v5, (const char *)v6, (const char *)v7, v14);
v10 = v5[0];
v11 = v6[0];
v12 = v7[0];
if ( v14[0] == 0xD085A85201A4i64 )
{
if ( 11 * v11 + 14 * v10 - v12 == 0x48FB41DDDi64
&& 9 * v10 - 3 * v11 + 4 * v12 == 0x2BA692AD7i64
&& ((unsigned __int64)(v12 - v11) >> 1) + (v10 ^ 0x87654321i64) == 3451779756 )
{
Buffer = "Congratulations! You get the correct flag.";//要到达这里
if ( v13[0] == 3846448765i64 )
{
puts(Buffer);
goto LABEL_12;
}
}
LABEL_11:
sub_1400112BC(v9);
}
LABEL_12:
sub_14001132F(v3, &unk_14001AED0);
return 0i64;
}
这个应该就是main了
分析:程序要求输入一个44字符长的字符串,最后一个字符(索引43)必须是'}',输入格式应该符合UUID格式,
逻辑:使用sscanf类似的函数(sub_14001128F)解析输入,格式为:"0xGame{%8llx-%4s-%4s-%4s-%12llx}"解析出5个部分:v13, v5, v6, v7, v14
验证逻辑:检查v14[0]是否等于0xD085A85201A4进行三个数学运算验证:
a) 11 v11 + 14 v10 - v12 == 0x48FB41DDD
b) 9 v10 - 3 v11 + 4* v12 == 0x2BA692AD7
c) ((v12 - v11) >> 1) + (v10 ^ 0x87654321) == 3451779756
检查v13[0]是否等于3846448765
解密:
# 运用Z3约束器
from z3 import *
s = Solver()
# 定义64位的位向量变量
v5 = BitVec('v5', 64)
v6 = BitVec('v6', 64)
v7 = BitVec('v7', 64)
# 添加约束条件,使用位向量操作
s.add(11 * v6 + 14 * v5 - v7 == 0x48FB41DDD)
s.add(9 * v5 - 3 * v6 + 4 * v7 == 0x2BA692AD7)
s.add(LShR(v7 - v6, 1) + (v5 ^ 0x87654321) == 3451779756)
if s.check() == sat:
model = s.model()
print(f"v5 = {model[v5]}")
print(f"v6 = {model[v6]}")
print(f"v7 = {model[v7]}")
else:
print("No solution found.")
v13 = 3846448765
v14 = 0xD085A85201A4
v5 = 842086455
v6 = 862073908
v7 = 1681208161
# 将整数转换为4字符的字符串
def int_to_str(num):
return ''.join([chr((num >> (8*i)) & 0xFF) for i in range(4)])
# 格式化flag
flag = "0xGame{%8x-%4s-%4s-%4s-%012x}" % (
v13,
int_to_str(v5),
int_to_str(v6),
int_to_str(v7),
v14
)
print(flag)
总结:Z3的运用,关键函数的找寻。
补充:UUID标准格式:8-4-4-4-12 字符,例如:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx。