Acid burn分析注册机编写

160个CreakMe第1个Acid burn。

拿到程序,正常还是在虚拟机跑一下,电脑卡直接在物理机上跑了。首先弹一个窗,意思是让把这个弹窗去掉,然后点击确定后,有一个输入名称序列号的弹窗,有一个直接输入序列号的弹窗。

然后按一贯思路,首先看有壳没,PEid看了没壳,是Delphi写的。那么按它的要求一个一个来做。

一.去掉弹窗

首先要去掉弹窗。弹窗常用到的就是MessageBoxA/W,bp MessageBoxA下断点,然后跑起来,断到MessageBoxA处,栈中反汇编窗口中跟随,到了调用它的下一条指令处,

可以看到标题,内容的参数都和显示的一样,也就是说调用处就是这里。但是这里是不能直接nop或者修改的,因为MessageBox是在程序中某个函数中调用的,有那么多弹窗,不可能就这一处调用,往上稍微看下函数开始位置

右键分别跳到相应的位置,挨个F2下断,然后再次跑起来。

这次断到了上面那一堆的其中一个,也就是开始的那个弹窗,call进去跟刚开始看到的那段代码一样,只是它参数传递的时候用ecx,edx,eax保存了相应的值,(快速调用约定就用ecx,edx来传参,其余的用栈)。不同弹窗的标题内容不同就是通过这里的参数来改变。所以这里很明确,把这一段参数传递以及调用的nop掉。那么弹窗也就没有了。

填充完后,复制到可执行文件,然后保存,这里可能会弹个提示直接确定就行。

最后新命名一个Acid burn1.exe,这个补丁就算打完了。现在重新运行一下Acid burn1.exe,可以看到直接就是那个界面。

重新载入patch过的程序,再来看原来那个0x42F797处的调用以及上面的参数,现在全为nop了。

二.找序列号

接下来就找序列号,因为找有两个需要找序列号的,一个是输名字输序列号的,另外一个就输个序列号,先易后难呗。

找序列号前,先运行下,看看序列号错误的时候是怎么个情况。

错误还是弹窗,这里找这个位置一种方法是刚才下了一堆断点,那么点击Check it的时候,会断下来。断下来后函数的参数都在调用之前,可以在内存中看下参数数据。确定就是失败时候调用的位置。

另外一种就是搜索字符串。右键查找所有的二进制字符串中,找到弹窗的标题和内容的字符串。双击跟进去

发现是一堆数据,并不是代码。但是这些数据是绝对会被访问的,所以这块内存地址会被访问到,在数据窗口中找到0x42F584

的位置,选中四个字节,给这下一个硬件访问断点,当这块数据被访问的时候,应该离调用弹窗也就不远了。

F9跑起来

拷贝[42F584]处的’F‘的时候,触发了访问断点。但是看一下好像也没到调用的附近,并且当前模块是user32的,也就是说在系统模块。

函数的调用,都是通过栈来进行的,一个函数的调用,最常见的CALL  xxx,是做了两条操作,push  call的下一条指令,然后jmp到要执行的函数地址,这里的push操作,压入的东西就是每一个返回地址。在这里调用一个东西,调用完后,应该执行call 的下一条指令,所以每调用一个函数,都有这么一下子,因此函数里面调用函数调再多也不会乱。

那这里在栈中查看函数调用,也称栈回溯,也是比较有意思的地方,可以看到这个程序调用了哪些函数。在栈中看到了调用MessageBoxA函数的返回地址,反汇编中跟随。

往上稍微翻一下,看到这个函数很熟悉,就是开始去除对话框的那个函数里面。在这里下断或者直接在函数开始位置下断,把之前的硬件断点删掉

重新跑起来。断下后,ctrl+F9执行到返回,或者手动在return返回,这里不知道为什么ctrl+F9跑不动。

找到了失败对话框的调用位置。失败一定是判断输入以后的分支才跳过去。所以这个函数从上到下看一下,通过比较后,可以知道这个序列号是固定的一个字符串"Hello Dude!",输入验证。

三.名称和序列号

接下来就是最后一个名称和序列号。找这个也是一样,先看一眼错误时候是什么样。

同样还是弹窗。无论通过字符串的方法查找,还是给MessageBox下断来定位,都可以找到这个位置,找到位置,一般是有个条件跳转,那么在附近就可以找到比较的函数。这里因为是输入一个名字,然后再输一序列号,然后再判断,那么这个名字和序列号是绝对有关系的,所以我通过瞎输入来看它是怎么检测的。瞎输入之前用一下IDA,IDA是静态分析工具,可能在以后的分析过程中,程序越来越复杂,各种壳反调试乱七八糟的让程序在OD中根本跑不起来,那么熟悉IDA静态分析作用还是很大的。这里用IDA看一下。

程序拖到IDA中,等解析完,程序的入口一般可以CTRL+E,就直接跳过去了。这里的入口意思是程序的加载基址,并不是main函数的入口。CTRL+1显示快速查看,很多有用的选项直接就能看到,比如字符串查找,函数名,导入导出表之类的。现在通字符串查找来找这个弹窗出现的位置。Sorry,The serial is incorect!  TryAgain! 这是弹窗的标题和内容,在字符串列表找一下

双击直接就到达该位置。

这里有个快捷键CTRL+X,查找引用。意思就是这个数据或函数类的是被哪些地方调用的,这个很有用。

双击进去,看到函数调用,n键是改函数名的,因为我分析的过程中一些函数名知道功能后就顺手改成自己认识的函数名,在进一步理解代码比较有用。这里是失败的弹窗,上面这个函数挨着分析。

开头的代码就是做一些初始化保存一下寄存器环境。

上面这一坨写的可能太繁琐,总的来说做了这么几个事。首先那个常被调用的函数StrBufferAndLen,通过观察寄存器的值,它返回的是字符串的长度和字符串存放的地址,仔细看下,其实是有点重复的。lea edx,[栈的地址] 把栈的地址给edx,[ebp+1DC]这是个固定的东西,我跟进去看了下里面存放着一个地址,类似于跳转表之类的东西,给eax赋值,肯定是函数用到了。调用完这个函数后,可以看到,栈中的地址被写入东西了,然后赋值给eax。

输入的字符串长度必须大于等于4个字节,然后取每一位进行计算。但这里看到的四位计算,其实一点用也没有,压根没用到,唯一有用的就是输入长度4字节的限制。

然后就是计算序列号的一段代码

正确的序列号有两部分组成,一部分是固定字符串,另一部分是通过输入字符串的第一个字节计算出一个数。然后和固定的字符串进行拼接。

找了一下计算序列号的函数,IDA找是真不好找(功底不够吧),跳转比较太多,找着找着就不知道跑哪去了,并且函数也不太好一个一个往里面跟,跟太深基本就跟偏了。所以只能OD动态跟,动态跟可以跳过很多看不懂的跳转,我的想法就是盯着那个名字计算出来的数,时刻注意它存放位置,只要看住它,跟下去就一定能找到用它的地方,这里跟的跳转比较比较多就不截图了。还有一个是跟函数的时候有时候需要跟进去看,有时候就不行了,函数里面还有一堆函数调用,一路F7顶不住,一次不可能看懂,先F8看返回值大概猜函数干嘛的,然后有个大致轮廓再细跟比较好一点。

这里那个计算出来的值存在eax里,大致的计算流程是

//序列号格式CW-xxxx-CRACKED
//字符串第一字节 *  0x29  *  2
//while(结果!=0)
//{
//   商=结果/0xA
//    余数=结果%0xA
//   ret= 余数+0x30    
//    if(ret>0x3A)
//   {
//	ret+=0x7  
//   }
//   保存这个ret
//   结果=商
//}
//最后计算出来是一串数字,然后加上固定字符串
/////////////////////////////////////////////
#include <iostream>
#include <stdio.h>
#include <string.h>
#include<Windows.h>

char *Rever(char *str);
char *CalcSerial(char *str);
char *g_pTmp = NULL;

char* CalcSerial(char *str)
{
	int num = (int)str[0];
	num &= 0xFF;
	num = num * 0x29 * 2;
	int remainder = 0;   //余数
	int quotient = 0;    //商
	int result = 0;      //每一次计算结果
	int nCount = 0;
	while (num != 0)
	{
		remainder = num % 0xA;
		quotient = num /= 0xA;
		result = remainder + 0x30;
		if (result > 0x3A)
		{
			result += 0x7;
		}
		sprintf(g_pTmp+nCount,"%d",result-0x30);
		num = quotient;
		nCount++;
	}
	return g_pTmp;
}
//字符串逆序
char *Rever(char *str)
{
	int n = strlen(str);
	int i;
	char temp;
	for (i = 0; i < (n / 2); i++)
	{
		temp = str[i];
		str[i] = str[n - i - 1];
		str[n - i - 1] = temp;
	}
	return str;
}

void main(int argc, char *argv[])
{
	char str[100]{};
	char *tmp = NULL;
        g_pTmp = new char[10]{};
	while (1)
	{
		system("cls");
		printf(" Enter Name: ");
		scanf("%s", str);
		getchar();
		if (strlen(str) < 4)
		{
			printf("The string length is not enough!\n");
			getchar();
		}
		else
			break;
	}
	tmp = CalcSerial(str);
	printf("序列号为CW-%s-CRACKED", Rever(tmp));
	delete[] g_pTmp;
	system("pause");
}

测试

猜你喜欢

转载自blog.csdn.net/weixin_42489582/article/details/84894834