buu题解-crackMe

news/2024/10/17 22:18:25/

嗯......这题写了好久,都快写自闭了,看了很多wp,终于搞出来了,记录一下

打开题目,下载附件,查壳,32位无壳,拖入ida中打开

找到主函数

int wmain()
{FILE *v0; // eaxFILE *v1; // eaxchar v3; // [esp+3h] [ebp-405h]char fail[256]; // [esp+4h] [ebp-404h] BYREFchar success[256]; // [esp+104h] [ebp-304h] BYREFchar input_2[256]; // [esp+204h] [ebp-204h] BYREFchar input[256]; // [esp+304h] [ebp-104h] BYREFprintf("Come one! Crack Me~~~\n");memset(input, 0, sizeof(input));memset(input_2, 0, sizeof(input_2));while ( 1 ){do{do{printf("user(6-16 letters or numbers):");// 输入用户名scanf("%s", input);v0 = sub_4024BE();fflush(v0);}while ( !sub_401000(input) );             // 检测输入是否为数字或者字母printf("password(6-16 letters or numbers):");scanf("%s", input_2);v1 = sub_4024BE();fflush(v1);}while ( !sub_401000(input_2) );             // 检测输入是否是数字或者字母sub_401090(input);memset(success, 0, sizeof(success));memset(fail, 0, sizeof(fail));v3 = (dword_4011A0)(success, fail);if ( function_1(input, input_2) )           // 判断函数{if ( v3 )break;}printf(fail);}printf(success);return 0;
}

首先是一个双重do-while循环输入用户名和密码,用户名题目已经给出,其中的sub_401000(input)函数是限制输入的用户名是数字或字母,否则不通过。

   do{do{printf("user(6-16 letters or numbers):");// 输入用户名scanf("%s", input);v0 = sub_4024BE();fflush(v0);}while ( !sub_401000(input) );             // 检测输入是否为数字或者字母printf("password(6-16 letters or numbers):");scanf("%s", input_2);v1 = sub_4024BE();fflush(v1);}while ( !sub_401000(input_2) );  

接下来是一个sub_401090函数,跟进看一下,应该是对byte_416050进行的操作,后面byte_416050还会用到,但是看大佬的wp没有通过这一段得到byte_416050,先不管

_BYTE *__cdecl sub_401090(_BYTE *input)
{_BYTE *result; // eaxint v2; // [esp+Ch] [ebp-18h]int v3; // [esp+10h] [ebp-14h]_BYTE *v4; // [esp+14h] [ebp-10h]int i; // [esp+18h] [ebp-Ch]char v7; // [esp+20h] [ebp-4h]char v8; // [esp+22h] [ebp-2h]unsigned __int8 v9; // [esp+23h] [ebp-1h]for ( i = 0; i < 256; ++i )byte_416050[i] = i;v2 = 0;v9 = 0;v3 = 0;result = input;v4 = input;doLOBYTE(result) = *v4;while ( *v4++ );while ( v2 < 256 ){v8 = byte_416050[v2];v9 += v8 + input[v3];v7 = byte_416050[v9];++v3;byte_416050[v9] = v8;byte_416050[v2] = v7;result = v3;if ( v3 >= v4 - (input + 1) )v3 = 0;++v2;}return result;
}

接着往下是一段if判断,如果满足function_1函数和v3就跳出循环,输出success,那我们就进入这个函数看看

  if ( function_1(input, input_2) )           // 判断函数{if ( v3 )break;}printf(fail);}printf(success);

下面是function_1函数

bool __cdecl sub_401830(int input, const char *input_2)
{int v3; // [esp+18h] [ebp-22Ch]int v4; // [esp+1Ch] [ebp-228h]int v5; // [esp+28h] [ebp-21Ch]unsigned int v6; // [esp+30h] [ebp-214h]char v7; // [esp+36h] [ebp-20Eh]char v8; // [esp+37h] [ebp-20Dh]char v9; // [esp+38h] [ebp-20Ch]unsigned __int8 v10; // [esp+39h] [ebp-20Bh]unsigned __int8 v11; // [esp+3Ah] [ebp-20Ah]char v12; // [esp+3Bh] [ebp-209h]int v13; // [esp+3Ch] [ebp-208h] BYREFchar v14; // [esp+40h] [ebp-204h] BYREFchar v15[255]; // [esp+41h] [ebp-203h] BYREFchar v16[256]; // [esp+140h] [ebp-104h] BYREFv4 = 0;v5 = 0;v11 = 0;v10 = 0;memset(v16, 0, sizeof(v16));v14 = 0;memset(v15, 0, sizeof(v15));v9 = 0;v6 = 0;v3 = 0;while ( v6 < strlen(input_2) ){if ( isdigit(input_2[v6]) ){v8 = input_2[v6] - 48;}else if ( isxdigit(input_2[v6]) ){if ( *(NtCurrentPeb()->ProcessHeap + 3) != 2 )input_2[v6] = 34;v8 = (input_2[v6] | 0x20) - 87;}else{v8 = ((input_2[v6] | 0x20) - 97) % 6 + 10;}__rdtsc();__rdtsc();v9 = v8 + 16 * v9;if ( !((v6 + 1) % 2) ){v15[v3++ - 1] = v9;v9 = 0;}++v6;}while ( v5 < 8 ){v10 += byte_416050[++v11];v12 = byte_416050[v11];v7 = byte_416050[v10];byte_416050[v10] = v12;byte_416050[v11] = v7;if ( (NtCurrentPeb()->NtGlobalFlag & 0x70) != 0 )v12 = v10 + v11;v16[v5] = byte_416050[(v7 + v12)] ^ v15[v4 - 1];if ( *&NtCurrentPeb()->BeingDebugged ){v10 = -83;v11 = 43;}function_2(v16, input, v5++);v4 = v5;if ( v5 >= (&v15[strlen(&v14)] - v15) )v4 = 0;}v13 = 0;function_3(v16, &v13);return v13 == 0xAB94;
}

首先是一个while循环,第一个if语句中 isdigit(input[v6])函数将输入的ascll码数字转为十进制数,例如字符'0'变为十进制数0,else if语句是将十六进制字符转为十六进制数,else语句是将不是abcdef的字符按六个一循环转为16进制字符,下面对byte_416050的操作我们先不看,接下来是个加密过程

v17[v6] = byte_416050[(v8 + v13)] ^ v16[v5 - 1];

v16是对输入的值的操作,v17可以从下面的函数得到,接着看下面的函数

if ( (NtCurrentPeb()->NtGlobalFlag & 0x70) != 0 )v13 = v11 + v12;
if ( *&NtCurrentPeb()->BeingDebugged ){v11 = -83;v12 = 43;}

这两部分语句是检测debug的,检测到处于调试状态时执行,下面还有一个函数,跟进function_2看看

const char *__cdecl function_2(int v17, const char *input, signed int v6)
{const char *result; // eaxsigned int v4; // [esp+4h] [ebp-58h]struct _STARTUPINFOW StartupInfo; // [esp+14h] [ebp-48h] BYREFmemset(&StartupInfo, 0, sizeof(StartupInfo));StartupInfo.cb = 68;GetStartupInfoW(&StartupInfo);v4 = strlen(input);if ( StartupInfo.dwX|| StartupInfo.dwY|| StartupInfo.dwXCountChars|| StartupInfo.dwYCountChars|| StartupInfo.dwFillAttribute|| StartupInfo.dwXSize|| StartupInfo.dwYSize ){if ( v6 <= v4 )return &input[v6];elsereturn &input[v4];}else if ( v6 <= v4 ){result = (input[v6] ^ *(v6 + v17));*(v6 + v17) = result;}else{result = (v6 + v17);*(v6 + v17) += byte_416050[v4 + v6] & input[v4];}return result;
}
  1. 第一个if语句同样时在检测反调试,这部分只会执行else if语句,因为v6小于8,而输入进来的input值是8位数,这个语句是将v17与输入的input值进行异或,之后再赋值给v17,返回到上一级函数,下面还有函数function_3函数,跟进查看函数

unsigned int *__usercall sub_401470@<eax>(int a1@<ebx>, _BYTE *a2, unsigned int *a3)
{char v5; // alunsigned int *result; // eaxif ( *a2 != 0x64 )*a3 ^= 3u;else*a3 |= 4u;if ( a2[1] != 0x62 ){*a3 &= 0x61u;_EAX = *a3;}else{_EAX = a3;*a3 |= 0x14u;}__asm { aam }if ( a2[2] != 0x61 )*a3 &= 0xAu;else*a3 |= 0x84u;if ( a2[3] != 0x70 )*a3 >>= 7;else*a3 |= 0x114u;if ( a2[4] != 0x70 )*a3 *= 2;else*a3 |= 0x380u;if ( *(NtCurrentPeb()->ProcessHeap + 3) != 2 ){if ( a2[5] != 0x66 )*a3 |= 0x21u;else*a3 |= 0x2DCu;}if ( a2[5] != 0x73 ){v5 = a3;*a3 ^= 0x1ADu;}else{*a3 |= 0xA04u;v5 = a3;}_AL = v5 - (~(a1 >> 5) - 1);__asm { daa }if ( a2[6] != 0x65 )*a3 |= 0x4Au;else*a3 |= 0x2310u;if ( a2[7] != 0x63 ){*a3 &= 0x3A3u;return *a3;}else{result = a3;*a3 |= 0x8A10u;}return result;
}

可以看到最后返回v14 == 0xAB94的值,而上以及要求返回值为1,所以可以推断出v14=0xAB94

带入function_3函数,看到有很多的if-else语句直接逆的话很麻烦,根据常规让每一个if都不满足,注意a2[5]这里有个小坑,我们要选择下面的那一种情况,最后我们可以得到v17的值

v17=dbappsec

返回到加密运算的语句,这里再放一下

v17[v6] = byte_416050[(v8 + v13)] ^ v16[v5 - 1];

看了其他大佬的wp可以知道,byte_416050可以动态调试调出来,找到ida对应的汇编代码

movzx ecx, [ebp+var_209]是将byte_416050的值存入ecx,下一行的xor eax,ecx对应的是byte_416050[(v8 + v13)] ^ v16[v5 - 1];,所以此时ecx中的值就是我们想要的值,用od来动态调试一下

在xor处下断点,运行输入用户名

接着ctrl+f9一步一步记下ecx的值,得到byte_416050的值

0x2A, 0xD7, 0x92, 0xE9, 0x53, 0xE2, 0xC4, 0xCD

然后写脚本解密

#include<stdio.h>
int main(){char v17[10]="dbappsec";int byte_416050[10]={0x2A, 0xD7, 0x92, 0xE9, 0x53, 0xE2, 0xC4, 0xCD};int i=0;int flag[10]={0};for(i=0;i<8;i++){flag[i]=v17[i]^byte_416050[i];printf("%0xd",flag[i]); }return 0;
}

然后在md5加密就可以了

得到flag{d2be2981b84f2a905669995873d6a36c}

学到的点:

1,常见检测调试的成程序

2,如何用od进行动态调试(先找到位置下断点(选中按f2)然后运行,ctrl+f9查看相关寄存器的值


http://www.ppmy.cn/news/285026.html

相关文章

Android 自定义最大宽度,高度, 宽高比例 Layout

前言 这篇博客主要介绍的是怎样自定义一个可以指定最大宽度&#xff0c;高度&#xff0c;以及宽高比的 Layout。原理其实很简单&#xff0c;就是通过重写 onMeasure 方法&#xff0c;重新制定 MeasureSpec。 使用说明 常用的自定义属性 <attr name"ml_maxWidth"…

【每天学习一点新知识】今天谈谈SSRF

目录 可能出现SSRF的地方&#xff1a; SSRF相关函数 SSRF 的危险协议 常用绕过方式 SSRF (Server-Side Request Forgery) 即服务端请求伪造&#xff0c;从字面意思上理解就是伪造一个服务端请求&#xff0c;也即是说攻击者伪造服务端的请求发起攻击&#xff0c;攻击者不能直…

12v电瓶20安时是什么意思

问题&#xff1a;是这样的,我去买电瓶车,老板说是20安时的电瓶,但是我是装了4个12V的电瓶的,也就是48V的,我想问一下,是一个电瓶是20安时呢?还是4个电瓶加起来20安时. 答&#xff1a;如果4个12V20A的电瓶并联的话(正极接正极,负极接负极)这时的电压就还是12V,但是电流(安时)会…

汽车蓄电池

在汽车上&#xff0c;发电机是汽车的主要电源&#xff0c;蓄电池是辅助电源。 调节器的功能是在发电机转速升高到一定程度时&#xff0c;自动调节发电机输出电压使其保持稳定。 一、蓄电池的构造和型号 1、蓄电池的分类 &#xff08;1&#xff09;干荷电蓄电池 极板在干燥状…

组装锂电48V20A/H

1.力神18650电芯到货104颗&#xff0c;组成8并13串电池组&#xff1a; 2.装上支架&#xff0c;开始点焊&#xff1a; 3.点焊完成&#xff0c;开始装保护板&#xff1a; 4.装好保护板后&#xff0c;接好输出线&#xff0c;装入电池盒中。 5.上车试骑&#xff1a; 6.两年的二手车…

汇编指令对端口的读写

对端口的读写不能用mov、push、pop等这些对内存的操作指令&#xff0c;对端口的读写只有两条in和out in:读取出端口的内容到程序中 out:写入内容到端口中 ;以下为对8为的端口进行的读写&#xff0c;如果是对16位的端口进行读写需要使用ax存放数据在写入端口 ;对端口号在0~25…

Monica: 您的又一个免费ChatGPT 4.0

最近 ChatGPT 又开始封号了&#xff0c;主要原因如下&#xff1a; 违反使用条款&#xff1a;如果用户违反了平台或应用的使用条款&#xff0c;例如发布违法、恶意或滥用行为的内容&#xff0c;侵犯他人的权利&#xff0c;或者从事垃圾信息传播等&#xff0c;管理员可能会采取封…

4-20mA一进二出隔离分配器

SunYuan DIN 1X2 ISOD系列4-20mA信号一进二出隔离分配器&#xff0c;是一种将工业现场仪器仪表与传感器、PLC/DCS等输出的模拟4-20mA电流环路信号&#xff0c;经隔离分配成精度、线性度、阻抗相匹配的两路标准4-20mA信号分配模块。该模块内部集成了两组高隔离的模拟信号隔离放…