BUUCTF 逆向题目 刮开有奖
题目地址:
https://buuoj.cn/challenges#%E5%88%AE%E5%BC%80%E6%9C%89%E5%A5%96
https://files.buuoj.cn/files/abe6e2152471e1e1cbd9e5c0cae95d29/8f80610b-8701-4c7f-ad60-63861a558a5b.exe
首先,查壳
信息:
文件名: H:/第七届“强网杯”全国网络安全挑战赛/BUUCTF/刮开有奖/8f80610b-8701-4c7f-ad60-63861a558a5b.exe
大小: 212992(208.00 KiB)
操作系统: Windows(XP)
架构: I386
模式: 32 位
类型: GUI
字节序: LE
使用IDA32打开文件
INT_PTR __stdcall DialogFunc(HWND hDlg, UINT a2, WPARAM a3, LPARAM a4)
{
const char *v4; // esi
const char *v5; // edi
int v7[2]; // [esp+8h] [ebp-20030h] BYREF
int v8; // [esp+10h] [ebp-20028h]
int v9; // [esp+14h] [ebp-20024h]
int v10; // [esp+18h] [ebp-20020h]
int v11; // [esp+1Ch] [ebp-2001Ch]
int v12; // [esp+20h] [ebp-20018h]
int v13; // [esp+24h] [ebp-20014h]
int v14; // [esp+28h] [ebp-20010h]
int v15; // [esp+2Ch] [ebp-2000Ch]
int v16; // [esp+30h] [ebp-20008h]
CHAR String[65536]; // [esp+34h] [ebp-20004h] BYREF
char v18[65536]; // [esp+10034h] [ebp-10004h] BYREF
if ( a2 == 272 )
return 1;
if ( a2 != 273 )
return 0;
if ( (_WORD)a3 == 1001 )
{
memset(String, 0, 0xFFFFu); //memset函数将String初始化置0
GetDlgItemTextA(hDlg, 1000, String, 0xFFFF);
if ( strlen(String) == 8 )
{
v7[0] = 'Z';
v7[1] = 'J';
v8 = 'S';
v9 = 'E';
v10 = 'C';
v11 = 'a';
v12 = 'N';
v13 = 'H';
v14 = '3';
v15 = 'n';
v16 = 'g';
sub_4010F0((int)v7, 0, 10); //sub_4010F0是一个快速排序算法 ,对数组进行处理后,数组的数据是 51 67 69 72 74 78 83 90
memset(v18, 0, 0xFFFFu); //memset函数将v18初始化置0
v18[0] = String[5];
v18[2] = String[7];
v18[1] = String[6];
v4 = sub_401000((int)v18, strlen(v18)); //sub_401000是base64加密算法,对v18进行了base64加密后复制给v4
memset(v18, 0, 0xFFFFu); //memset函数将v18初始化置0
v18[1] = String[3];
v18[0] = String[2];
v18[2] = String[4];
v5 = sub_401000((int)v18, strlen(v18)); //sub_401000是base64加密算法,对v18进行了base64加密后复制给v5
if ( String[0] == v7[0] + 34
&& String[1] == v10
&& 4 * String[2] - 141 == 3 * v8
&& String[3] / 4 == 2 * (v13 / 9)
&& !strcmp(v4, "ak1w")
&& !strcmp(v5, "V1Ax") )
{
MessageBoxA(hDlg, "U g3t 1T!", "@_@", 0);
}
}
return 0;
}
if ( (_WORD)a3 != 1 && (_WORD)a3 != 2 )
return 0;
EndDialog(hDlg, (unsigned __int16)a3);
return 1;
}
重要函数memset、sub_4010F0、sub_401000
memset
void *__cdecl memset(void *a1, int Val, size_t Size)
{
size_t v3; // edx,Size的副本
_BYTE *v4; // ecx,指向目标内存区域的指针
int v5; // eax,Val的副本
_BYTE *v6; // edi,指向目标内存区域的指针
int v7; // ecx
size_t v8; // ecx
unsigned int v9; // ecx
int v11; // eax
size_t v12; // eax
unsigned int v13; // edx
size_t j; // eax
unsigned int k; // eax
unsigned int v16; // edx
char v17; // al
unsigned int m; // edx
int n; // eax
int v20; // edx
unsigned int i; // ebx
v3 = Size; // 将Size的值保存到v3中
v4 = a1; // 将目标内存区域的指针保存到v4中
if (!Size) // 如果Size为0,直接返回目标内存区域的指针
return a1;
LOBYTE(v5) = Val; // 将Val的最低字节保存到v5中
if (!(_BYTE)Val && Size >= 0x80 && dword_40A85C) // 如果Val的最低字节为0,且Size大于等于0x80,且dword_40A85C为真
{
// 以下是对内存进行优化的部分
v11 = (unsigned __int8)a1 & 0xF; // 获取目标内存区域的地址与15进行按位与的结果
if (((unsigned __int8)a1 & 0xF) != 0) // 如果目标内存区域的地址与15进行按位与的结果不为0
{
v20 = (16 - (_BYTE)v11) & 3; // 计算需要填充的字节数
if (((16 - (_BYTE)v11) & 3) != 0) // 如果需要填充的字节数不为0
{
do
{
*v4++ = 0; // 将目标内存区域的值设置为0
--v20;
} while (v20);
}
for (i = (unsigned int)(16 - v11) >> 2; i; --i)
{
*(_DWORD *)v4 = 0; // 将目标内存区域的4个字节设置为0
v4 += 4;
}
v3 = Size - (16 - v11); // 更新剩余的字节数
}
v12 = v3;
v13 = v3 & 0x7F;
for (j = v12 >> 7; j; --j)
{
*(_OWORD *)v4 = 0i64; // 将目标内存区域的16个字节设置为0
*((_OWORD *)v4 + 1) = 0i64;
*((_OWORD *)v4 + 2) = 0i64;
*((_OWORD *)v4 + 3) = 0i64;
*((_OWORD *)v4 + 4) = 0i64;
*((_OWORD *)v4 + 5) = 0i64;
*((_OWORD *)v4 + 6) = 0i64;
*((_OWORD *)v4 + 7) = 0i64;
v4 += 128;
}
if (v13)
{
for (k = v13 >> 4; k; --k)
{
*(_OWORD *)v4 = 0i64; // 将目标内存区域的16个字节设置为0
v4 += 16;
}
v16 = v13 & 0xF;
if (v16)
{
v17 = v16;
for (m = v16 >> 2; m; --m)
{
*(_DWORD *)v4 = 0; // 将目标内存区域的4个字节设置为0
v4 += 4;
}
for (n = v17 & 3; n; --n)
*v4++ = 0; // 将目标内存区域的值设置为0
}
}
return a1;
}
else
{
v6 = a1; // 将目标内存区域的指针保存到v6中
if (Size < 4) // 如果Size小于4
goto LABEL_33; // 跳转到LABEL_33标签
v7 = -(int)a1 & 3; // 计算目标内存区域的地址与3进行按位与的结果的负值
if (v7)
{
v3 = Size - v7; // 更新剩余的字节数
do
{
*v6++ = Val; // 将目标内存区域的值设置为Val
--v7;
} while (v7);
}
v5 = 16843009 * (unsigned __int8)Val; // 计算Val的扩展值
v8 = v3;
v3 &= 3u;
v9 = v8 >> 2;
if (!v9 || (memset32(v6, v5, v9), v6 += 4 * v9, v3))
{
LABEL_33:
do
{
*v6++ = v5; // 将目标内存区域的值设置为Val的扩展值
--v3;
} while (v3);
}
return a1;
}
}
memset函数将string初始化置0
sub_4010F0
int __cdecl sub_4010F0(int a1, int a2, int a3)
{
int result; // eax,函数返回值
int i; // esi,循环变量
int v5; // ecx,临时变量
int v6; // edx,临时变量
result = a3; // 将a3保存到result中
for (i = a2; i <= a3; a2 = i) // 循环从a2到a3
{
v5 = 4 * i; // 计算数组索引偏移
v6 = *(_DWORD *)(4 * i + a1); // 获取数组元素的值
if (a2 < result && i < result) // 如果a2小于result且i小于result
{
do
{
if (v6 > *(_DWORD *)(a1 + 4 * result)) // 如果当前元素大于result位置的元素
{
if (i >= result)
break;
++i;
*(_DWORD *)(v5 + a1) = *(_DWORD *)(a1 + 4 * result); // 将result位置的元素移到当前位置
if (i >= result)
break;
while (*(_DWORD *)(a1 + 4 * i) <= v6) // 找到大于当前元素的位置
{
if (++i >= result)
goto LABEL_13;
}
if (i >= result)
break;
v5 = 4 * i;
*(_DWORD *)(a1 + 4 * result) = *(_DWORD *)(4 * i + a1); // 将找到的位置的元素移到result位置
}
--result;
} while (i < result);
}
LABEL_13:
*(_DWORD *)(a1 + 4 * result) = v6; // 将当前元素放置到result位置
sub_4010F0(a1, a2, i - 1); // 递归调用,对左侧子数组进行排序
result = a3;
++i; // 继续下一个元素的排序
}
return result; // 返回函数结果
}
这段代码实现了一个快速排序算法,对输入数组的某个范围进行排序。在每一轮循环中,它选择一个基准元素,将比基准元素小的移到基准元素的左边,比基准元素大的移到基准元素的右边。递归地对左右两侧进行排序,直到整个数组有序。
快速排序算法(quick sort),通过一趟排序将待排序的序列分割为左右两个子序列,左边的子序列中所有数据都比右边子序列中的数据小,然后对左右两个子序列继续进行排序,直到整个序列有序。
首先从序列中任意选择一个元素,把该元素作为枢轴,然后将小于等于枢轴的所有元素都移到枢轴的左侧,把大于枢轴的元素都移到枢轴的右侧。这样,以枢轴为界,划分出两个子序列,左侧子序列所有元素都小于右侧子序列。枢轴元素不属于任一子序列,并且枢轴元素当前所在位置就是该元素在整个排序完成后的最终位置。这样一个划分左右子序列的过程就叫做快速排序的一趟排序,或称为一次划分。递归此划分过程,直到整个序列有序。
sub_4010F0是一个快速排序算法 ,对数组进行处理后,数组的数据是 51 67 69 72 74 78 83 90
sub_401000
_BYTE *__cdecl sub_401000(int a1, int a2)
{
int v2; // eax,计算Base64编码后的长度
int v3; // esi,循环变量
size_t v4; // ebx,Base64编码后的长度
_BYTE *v5; // eax,指向分配的内存空间的指针
_BYTE *v6; // edi,用于遍历分配的内存空间
int v7; // eax
_BYTE *v8; // ebx,指向分配的内存空间的指针
int v9; // edi
int v10; // edx
int v11; // edi
int v12; // eax
int i; // esi
_BYTE *result; // eax,指向Base64编码后的字符串
_BYTE *v15; // [esp+Ch] [ebp-10h],指向分配的内存空间的指针
_BYTE *v16; // [esp+10h] [ebp-Ch],用于遍历分配的内存空间
int v17; // [esp+14h] [ebp-8h],临时变量
int v18; // [esp+18h] [ebp-4h],临时变量
// 计算Base64编码后的长度
v2 = a2 / 3;
v3 = 0;
if (a2 % 3 > 0)
++v2;
v4 = 4 * v2 + 1;
// 分配内存空间
v5 = malloc(v4);
v6 = v5;
v15 = v5;
if (!v5)
exit(0); // 内存分配失败,退出程序
// 将分配的内存空间初始化为0
memset(v5, 0, v4);
v7 = a2;
v8 = v6;
v16 = v6;
if (a2 > 0)
{
while (1)
{
v9 = 0;
v10 = 0;
v18 = 0;
// 将输入数据的每3个字节转换为一个24位整数
do
{
if (v3 >= v7)
break;
++v10;
v9 = *(unsigned __int8 *)(v3 + a1) | (v9 << 8);
++v3;
}
while (v10 < 3);
v11 = v9 << (8 * (3 - v10));
v12 = 0;
v17 = v3;
// 将24位整数转换为4个Base64字符
for (i = 18; i > -6; i -= 6)
{
if (v10 >= v12)
{
*((_BYTE *)&v18 + v12) = (v11 >> i) & 0x3F;
v8 = v16;
}
else
{
*((_BYTE *)&v18 + v12) = 64; // 补充Base64填充字符“=”
}
*v8++ = byte_407830[*((char *)&v18 + v12++)];
v16 = v8;
}
v3 = v17;
if (v17 >= a2)
break;
v7 = a2;
}
v6 = v15;
}
result = v6;
*v8 = 0; // 在Base64编码后的字符串末尾添加空字符
return result;
}
这段代码通过遍历输入的二进制数据,每次处理3个字节,将其转换为Base64编码,并在动态分配的内存空间中存储结果。最终返回指向Base64编码后的字符串的指针。
sub_401000是base64加密算法,对v18进行了base64加密后复制给v4
一顿操作猛如虎,先对数组进行快速排序
原始数据:
v7[0] = 'Z';
v7[1] = 'J';
v8 = 'S';
v9 = 'E';
v10 = 'C';
v11 = 'a';
v12 = 'N';
v13 = 'H';
v14 = '3';
v15 = 'n';
v16 = 'g';
快速排序
v7[0] = '3';
v7[1] = 'C';
v8 = 'E';
v9 = 'H';
v10 = 'J';
v11 = 'N';
v12 = 'S';
v13 = 'Z';
v14 = 'a';
v15 = 'g';
v16 = 'n';
根据代码:
memset(v18, 0, 0xFFFFu);
v18[0] = String[5];
v18[2] = String[7];
v18[1] = String[6];
v4 = sub_401000((int)v18, strlen(v18));
已知v4的值ak1w,则v18是jMp
String[5] = 'j';
String[6] = 'M';
String[7] = 'p';
根据代码:
memset(v18, 0, 0xFFFFu);
v18[1] = String[3];
v18[0] = String[2];
v18[2] = String[4];
v5 = sub_401000((int)v18, strlen(v18));
已知v5的值V1Ax,则v18是WP1
String[2] = 'W';
String[3] = 'P';
String[4] = '1';
根据代码:
String[0] == v7[0] + 34 &&
String[1] == v10 &&
4 * String[2] - 141 == 3 * v8 &&
String[3] / 4 == 2 * (v13 / 9)
String[0] = v7[0] + 34 = '3' + 34 = 'U'
String[1] == v10 = 'J'
String[2] = (3 * v8 + 141) / 4 = 'W'
String[3] = 2 * (v13 / 9) * 4 = 'P'
则flag的字符串就是UJWP1jMP
flag{UJWP1jMp}