BUUCTF 逆向题目 刮开有奖
题目地址:
https://buuoj.cn/challenges#%E5%88%AE%E5%BC%80%E6%9C%89%E5%A5%96https://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; // esiconst char *v5; // ediint v7[2]; // [esp+8h] [ebp-20030h] BYREFint 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] BYREFchar v18[65536]; // [esp+10034h] [ebp-10004h] BYREFif ( a2 == 272 )return 1;if ( a2 != 273 )return 0;if ( (_WORD)a3 == 1001 ){memset(String, 0, 0xFFFFu); //memset函数将String初始化置0GetDlgItemTextA(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 90memset(v18, 0, 0xFFFFu); //memset函数将v18初始化置0v18[0] = String[5];v18[2] = String[7];v18[1] = String[6];v4 = sub_401000((int)v18, strlen(v18)); //sub_401000是base64加密算法,对v18进行了base64加密后复制给v4memset(v18, 0, 0xFFFFu); //memset函数将v18初始化置0v18[1] = String[3];v18[0] = String[2];v18[2] = String[4];v5 = sub_401000((int)v18, strlen(v18)); //sub_401000是base64加密算法,对v18进行了base64加密后复制给v5if ( 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; // ecxsize_t v8; // ecxunsigned int v9; // ecxint v11; // eaxsize_t v12; // eaxunsigned int v13; // edxsize_t j; // eaxunsigned int k; // eaxunsigned int v16; // edxchar v17; // alunsigned int m; // edxint n; // eaxint v20; // edxunsigned int i; // ebxv3 = 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个字节设置为0v4 += 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个字节设置为0v4 += 16;}v16 = v13 & 0xF;if (v16){v17 = v16;for (m = v16 >> 2; m; --m){*(_DWORD *)v4 = 0; // 将目标内存区域的4个字节设置为0v4 += 4;}for (n = v17 & 3; n; --n)*v4++ = 0; // 将目标内存区域的值设置为0}}return a1;}else{v6 = a1; // 将目标内存区域的指针保存到v6中if (Size < 4) // 如果Size小于4goto 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; // ediint v10; // edxint v11; // ediint v12; // eaxint 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); // 内存分配失败,退出程序// 将分配的内存空间初始化为0memset(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}