003-Cruehead-CrackMe-3的 write up
抄一百篇不如自己实践操作写一篇,尊重知识产权,写笔记辛苦,禁止转载!!
1. 执行程序测试
如上图,程序执行后什么也没有,就是一些字符串,猜测有一些内容是需要破解之后才能够显示出来。
2. 程序查壳
解释:程序无壳
3. 静态调试
- 首先将程序拖入32位IDA后先点击View(查看)——>Open subviews——>Strings,结果如下图:
解释:可以看到有一些字符串是程序运行的时候没有显示出来的,这应该是需要破解之后才能显示的,猜测CRACKME3.KEY可能是一个文件名。 - 查看输入表,点击View——>Open subviews——>Imports,结果如下:
解释:这里只截屏来部分重要的API,并不是全部,但是可以猜测到这是和文件操作有关系。
详情:https://blog.csdn.net/jeanphorn/article/details/44982273
GreateFileA 创建或打开文件或I / O设备,返回值句柄(写的时候记得头文件: Fileapi.h)详情见https://blog.csdn.net/weixin_42810844/article/details/103246109
ReadFile 从文件指针指向的位置开始将数据读出到一个文件中
WriteFile 将数据写入一个文件
OpenFile 以不同方式打开文件的操作
- 可以用跟踪关键的字符串,或者是API函数,通过IDA的交叉引用,找到关键的主函数start。如果是流程图的形式,可以采用右键——>Text vie,以整篇形式观察加注释。如下图:
解释:可以知道程序的大致思路是需要打开一个包含注册码的文件,00401035文件存在则跳转,文件不存在则顺序执行,0040103C执行call调用的函数是拼接字符串,进入函数如下图:
解释:可见拼接的字符串是在CrackMe v3.0后面回车然后拼接“-Uncracked”,这是由于程序是由大端序模式存储的,所以数据高位在低位。
如果文件存在则跳转到00401043,开始读取文件内容并读取18个字符,保存到缓冲区402008,然后再来判断读取到的文件内字符串是不是18个字符,00401074的call调用00401311的函数进行
解密操作,如下图:
解释:解密时将把18个字符中的前14个字符每一个依次与“A~N"字符进行异或,所以是14次,然后将每次异或的结果储存到004020F9进行累加。最后回到第一张图,将累加出来的结果进行四个字节的异或,结果继续保存到了4020F9。
- 接下来的操作如下图:
注意重要知识点:SETZ AL //取标志寄存器中ZF的值, 放到AL中. SETNZ取得ZF值后, 取反, 再放到AL中.
解释:把校验结果保存在al里入栈了,接下来到后面还会进行一次检验。
- 然后经过一段窗口创建的操作之后,在进入消息循环之前,做了这样一个校验,校验文件内容是否正确,正确就弹框提示,正是通过刚刚push的al进行校验的
4. 动态调试
动态调试结合会让很多的数据看起来比较清晰。
- main函数,关节处加入了断电如下
5. 程序复现
注册码的检验是:
- 18个字符串中取出前14个字符依次分别与”A~N"进行异或运算
- 将每次异或出来的结果累加保存
- 最后累加的结果和0x123456再次进行异或
- 最后异或的结果和最后面四个字符比较。
注册机的生成思路是:
- 随便输入14个字符串,然后分别依次和“A~N”的结果进行异或
- 每次异或的结果累加保存起来,最后和0x123456异或
- 然后将最后异或出的结果拼接到14个字符后面,即可完成生成。
#include <stdio.h> #include <windows.h> #include <string.h> int main() { void generatekey(char key); //声明函数 char key[19] = { 0 };//储存用户输入的字符,做实际参数 printf("enter you username: "); scanf("%s", key); generatekey(key); //调用函数 system("pause"); return 0; } void generatekey(char key[19]) { char KEY[19] = { 0 }; //存储序列号 int i = 0; //充当字符串的下标 int k = 0; //充当循环计数 char bl = 'A'; //用来解密的字符串 unsigned int sum = 0; //每次异或的累加值储存到这里 unsigned char* tmp = { 0 }; //无符号的字符串指针初始化0 strcpy(KEY, key); //将用户输入的字符串复制到KEY do //循环解密 { KEY[i] ^= bl; sum += KEY[i]; i++; bl++; if (!KEY[i]) break; } while (bl!='O'); sum ^= 0x12345678; //累加值异或 *(unsigned int*)&KEY[0xE] = sum; //将前面异或出来的结果拼接到KEY的后4个字符 for ( k = 0; k < 18; k++) //循环遍历以两位十六进制数输出KEY { tmp = (unsigned char*)&KEY; printf("%02x ", tmp[k]); } printf("\nstrlen(tmp)=%d\n", strlen(tmp));//求输出字符串的实际长度(不包含/0) //由于前14个字符储存的是异或的结果,所以下面要把前14个字符逆向异或回原来的字符 int j = 0xD; //充当下标 char al = 'N';//充当解密字符 do { tmp[j] ^= al; j--; al--; if (!tmp[j])break; } while (al!='@'); for (k = 0; k < 18; k++)//循环遍历以两位十六进制数输出KEY { tmp = (unsigned char*)&KEY; printf("%02x ", tmp[k]); } printf("\nstrlen(tmp)=%d\n", strlen(tmp));//求输出字符串的实际长度(不包含/0) printf("KEY: %s", KEY);//以字符串的形式输出KEY HANDLE hFile = CreateFileA //函数的返回值是一个句柄 ( "CRACKME3.KEY", //要打开的文件名字 GENERIC_ALL, //允许设备进行所有操作 0, //不共享 NULL, //指针 CREATE_ALWAYS, //总是创建文件 //OPEN_ALWAYS, //如果文件不存在则创建它 FILE_ATTRIBUTE_NORMAL, //默认属性 NULL //指定文件句柄 ); DWORD retNum = 0; WriteFile ( hFile, //文件句柄 KEY, //要写入的数据 sizeof(KEY), //要写入的字节数,结果应该是给了后面的取地址 &retNum, //实际写入的字符串 NULL //OVERLAPPED 结构,一般设定为 NULL ); CloseHandle(hFile); //关闭句柄 }