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); //关闭句柄
}