首页
论坛
课程
招聘
[原创]第十三题 NeuralCrackme Writeup
2018-7-11 23:51 2343

[原创]第十三题 NeuralCrackme Writeup

2018-7-11 23:51
2343
题目没有花指令、未加壳,没有反调试,结构清晰明了,除了使用 Cygwin 编译导致某一些库函数在IDA里面识别出来"不太正常"和使用浮点指令外,似乎并没有什么分析上的障碍,字符串异或后使用不算障碍。先拖入IDA,看main结构,先看到了这一段:

基本可以确定出输入的长度是10个, 长度验证通过后再大致按如下步骤验证:
1.  if ( (unsigned int)sub_401CE0(Format, &v27, &v14, &v27, 10LL) != 5 ) 
2. 然后调用sub_4015E0(Format, &v27, &v18, &v19)做了一些运算
3.   Format[0] = byte_408D72 ^ 0x12;               // %
      Format[1] = (byte_408D73 ^ 0x12) - 1;         // l
      Format[2] = (byte_408D74 ^ 0x12) - 2;         // f
      sprintf(Format, &v26, Format, &v26, *(_QWORD *)&v17, v17, v3, v17);  可能是把 sub_4015E0的一个输出作为浮点数打印到了字符串
4. if ( v27 != '.' ) 验证上一步 sprintf 的结果是否为 A.BCDEFG... 这种样式
5. sub_403360 对第4的输出,分别取 A * A + B * B + C * C 做某种运算后,要使输出的值 > 15.5并且 v13 & 0xF0000必须为0
6. if ( COERCE_DOUBLE(COERCE_UNSIGNED_INT64(v20 + v21 - v17) & xmmword_409080) < 0.003 ) 需要成立.
第5, 6步的通过条件简单调试或者直接深入分析下输出,把打印信息异或回来看即可。现在逐步分析各个验证步骤,找到它的成立条件....

先看 sub_401CE0

然后到 sub_4015E0, 看起来也不太复杂一样,只是各种double的运算,调用了一个 sub_4034D0 函数,点进 sub_4034D0,看到了比较明显的"exp"字符串,结合mingw-w64,猜测它就是exp函数。纵观整个 sub_4015E0,取了一堆全局变量做浮点运算,中途涉及到一个修改全局变量qword_40C980的操作

前面的一堆运算过后,函数结尾出现一个对比的条件,该条件是求取值范围的一个关键:

继续回到main函数,来到 sub_403360的调用,点进子函数就发现了标志性的sqrt,看起来它可能就是sqrt函数。根据这个调用时的判断条件输入要大于15.5 * 15.5=240.25, 输入是三个整数的平方,看来输入的就只有可能是9.99*****了.也就是sub_4015E0 输出的结果必须是9.99****, 同样,也得到了sub_4015E0函数末尾的那个 if 条件不能成立。同时在对 sub_403360的判断中还增加了 v13 & 0xF0000 必须为0的判断,可以确定,v13的值必须为0x???0???? 这种值。

程序结构大体摸清了,懒得再去仔细理解细节,输入12345678AB直接在IDA中调试。顺便一提:IDA分析的main函数有问题,局部变量对应的栈位置不正确,平常一直用的 windbg 命令通过栈偏移看局部变量行不通..,很奇怪,不知道是怎么回事
第一个转换函数 sub_401CE0 的调用后把结果存入到RDX, RDX值为 000000000022FCC0  12 34 56 78 AB 00 00 00,回到main后把前四个字节给v13, 后一个字节给v14, 再通过下面的语句,把转换的结果送给 sub_4015E0做入参和放到两个临时变量中:
  HIWORD(v18) = v13;
  v20 = v18;
  *(_WORD *)((char *)&v19 + 5) = HIWORD(v13);
  HIBYTE(v19) = v14;
  v21 = v19;
sub_4015E0函数的a4以rcx传递,步入后在内存中看到rcx是如下布局:
000000000022FCF0  00 00 00 00 00 00 12 34  00 00 00 00 00 56 78 AB  .......4.....Vx.
000000000022FD00  00 00 00 00 00 00 12 34  00 00 00 00 00 56 78 AB  .......4.....Vx.
正符合测试的输入,并且a4是double型的数组,对sub_4015E0再针对a4查找引用,发现程序应该是将输入分成两段,前四个字符一段,后六个字符一段,并且当成double处理,并且要满足这两个double都必须是大于1小于10,根据这个条件,做一个初步的筛选:
	//	取可能的值...
	vector<unsigned __int64> vleft, vright;
	double drets = 0.0f;
	unsigned __int64 itmp = 0;
	for (unsigned __int64 i = 0; i <= 0xFFFF; i++, itmp = i << 48){	//	取左边的16位的可能值
		drets = *(double *)&itmp;
		if (drets > 1.0000001f && drets < 9.999999999f){
			vleft.push_back(i);
		}
	}
	for (unsigned __int64 i = 0; i <= 0xFFFFFF; i++, itmp = i << 40){	//	取右边的24位的可能值
		drets = *(double *)&itmp;
		if (drets > 1.0000001f && drets < 9.999999999f && 0 == (i % 0x10)){
			vright.push_back(i);
		}
	}
然后,再看 sub_4015E0函数,里面对输入的两个double和一堆全局变量做了大把不了解原理的运算,尝试不同的输入,发现不同输入对参与运算的全局变量没有影响,再交叉引用到全局变量的初始化过程,发现程序启动后,输入前会调用sub_401790做一些初始化操作。断点设在main的scanf处,shift+F2执行个简单脚本把整个模块dump出来:
auto fp;
fp = fopen("c:\\basedumps.bin", "wb");
savefile(fp, 0, 0x400000, 0x10000);
fclose(fp);

现在关键是对 sub_4015E0的理解(尝试)了,找到满足输入并且输出是9.99****的值,幸好函数不复杂,直接拷出来把可能的取值挨个尝试..并添加每个成立的条件判断,最终用了15毫秒就出结果:        F13FE02140
虽然结果跑出来了,但是依然没有搞明白这个程序是使用的什么算法和原理来计算的注册码....。
以前基本就没有接触过浮点数,刚开始调试的时候举步维艰,IDA调试时XMM寄存器显示的值也各种奇怪。直到突然发现XMM寄存器的值可以右键转成double显示,瞬间轻松了。程序好像也没有经过优化,逆出来的代码可读性太高了。
附上完整的注册码计算代码:
//	VS2010, 64位
#include <windows.h>
#include <iostream>
#include <vector>
#include <assert.h>
#include <math.h>

using namespace std;

#pragma comment(lib, "ws2_32.lib")

vector<unsigned char> load_files(const char *pfname)
{
	vector<unsigned char> ret;
	HANDLE hFile = CreateFileA(pfname, GENERIC_READ, FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (INVALID_HANDLE_VALUE == hFile)
	{
		assert(false);
		return ret;
	}
	do
	{
		DWORD dwSize = 0, dwRet = 0;
		dwSize = GetFileSize(hFile, NULL);
		assert(dwSize > 0);
		ret.resize(dwSize);
		ReadFile(hFile, &ret[0], dwSize, &dwRet, NULL);
		assert(dwRet == dwSize);
	}while(false);
	CloseHandle(hFile);
	hFile = INVALID_HANDLE_VALUE;
	return ret;
}

int main(int argc, const char *argv[])
{
	/*
	auto fp;
	fp = fopen("c:\\basedumps.bin", "wb");
	savefile(fp, 0, 0x400000, 0x10000);
	fclose(fp);
	*/
	vector<unsigned char> vbytes = load_files("basedumps.bin");
	if (vbytes.size() < 0x10000){
		cout << "errs load." << endl;
		return 0;
	}
	unsigned char *pbufs = &vbytes[0];
	unsigned __int64 baseAddr = 0x400000; 
	double *padd40c980 = (double *)(pbufs + 0x40C980 - baseAddr);
	double *padd40cbe8 = (double *)(pbufs + 0x40cbe8 - baseAddr);
	double *padd40cd90 = (double *)(pbufs + 0x40cd90 - baseAddr);
	double *padd40cdb0 = (double *)(pbufs + 0x40cdb0 - baseAddr);
	double *padd40cdb8 = (double *)(pbufs + 0x40cdb8 - baseAddr);
	double *padd40cd98 = (double *)(pbufs + 0x40cd98 - baseAddr);
	double *padd40ca10 = (double *)(pbufs + 0x40ca10 - baseAddr);
	double *padd40cd00 = (double *)(pbufs + 0x40cd00 - baseAddr);
	double *padd40cda0 = (double *)(pbufs + 0x40cda0 - baseAddr);
	double *padd40cdc0 = (double *)(pbufs + 0x40cdc0 - baseAddr);

	//	取可能的值...
	vector<unsigned __int64> vleft, vright;
	double drets = 0.0f;
	unsigned __int64 itmp = 0;
	for (unsigned __int64 i = 0; i <= 0xFFFF; i++, itmp = i << 48){	//	取左边的16位的可能值
		drets = *(double *)&itmp;
		if (drets > 1.0000001f && drets < 9.999999999f){
			vleft.push_back(i);
		}
	}
	for (unsigned __int64 i = 0; i <= 0xFFFFFF; i++, itmp = i << 40){	//	取右边的24位的可能值
		drets = *(double *)&itmp;
		if (drets > 1.0000001f && drets < 9.999999999f && 0 == (i % 0x10)){
			vright.push_back(i);
		}
	}
	cout << "Left Count: " << vleft.size() << "; Right Count: " << vright.size() << endl << endl;

	vector<unsigned __int64>::iterator itleft, itright;
	DWORD dwStart = GetTickCount();
	for (itleft = vleft.begin(); itleft != vleft.end(); itleft++){
		unsigned __int64 i01 = *itleft << 48;
		double d1 = *(double *)&i01;
		for (itright = vright.begin(); itright != vright.end(); itright++){
			//	以下从 00000000004015E0 函数还原而来...尝试可能的值组合
			unsigned __int64 i02 = *itright << 40;
			double d2 = *(double *)&i02;
			assert(!(d1 <= 1.0f || d1 >= 10.0f || d2 <= 1.0f || d2 >= 10.0f));

			double *v5 = padd40c980, *v6 = padd40cbe8;
			double v10 = (d1 - *padd40cd90 + 1.0f) / (*padd40cdb0 - *padd40cd90 + 1.0f);
			double v11 = (d2 - *padd40cd98 + 1.0f) / (*padd40cdb8 - *padd40cd98 + 1.0f);

			memset(padd40c980, 0, 144);	//	padd40c980这是个临时的变量,初始化也没有用,使用的时候可再次清零..
			do{
				double v13 = v10 * *(v6 - 1);
				v6 += 2;
				double v14 = (v11 * *(v6 - 2) + v13 + 0.0f);
				unsigned __int64 i64tmp = *(unsigned __int64 *)&v14;
				i64tmp ^= (unsigned __int64)0x8000000000000000;
				memcpy(&v14, &i64tmp, sizeof(double));
				*v5 = 1.0f / (exp(v14) + 1.0f);
			}while(++v5 != padd40ca10);

			int result = 0;
			double v4 = 0.0f;
			do{
				v4 += padd40cd00[result] * padd40c980[result];
			}while(++result != 18);
			drets = v4 * (*padd40cda0 - *padd40cdc0 + 1.0f) + *padd40cdc0 - 1.0f;

			if (drets >= 9.990f && drets < 10.0f){
				double dlstchk = d1 + d2 - drets;
				unsigned __int64 ilstchk = *(unsigned __int64 *)&dlstchk;
				ilstchk &= (unsigned __int64)0x7FFFFFFFFFFFFFFF;
				dlstchk = *(double *)&ilstchk;
				if (dlstchk < 0.003f){
					printf("Got One.\n\t%04I64X%06I64X\n\n", htonl(i01 >> 48) >> 16, htonl(i02 >> 40) >> 8);
				}
			}
		}
	}
	cout << "TimeUsed(ms): " << (GetTickCount() - dwStart) << endl;
	return 0;
}






【看雪培训】目录重大更新!《安卓高级研修班》2022年春季班开始招生!

最后于 2018-7-11 23:58 被leavesth编辑 ,原因:
上传的附件:
收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回