selph
selph
发布于 2022-08-31 / 227 阅读
0
0

新160个CrackMe练习:060-snake

算法难度:⭐⭐⭐⭐⭐

爆破难度:⭐

信息收集

运行情况:

image

查壳与脱壳:

image

调试分析

Check按钮事件

老样子,IDA打开,找到Check按钮的事件分支:

首先是获取Name和Serial,为空会提示

image

然后一个call验证Serial合法性,不行就弹窗提示

image

这个call的内容如下:判断内容要由数字和大写字母组成

image

再往下就是三个call和弹窗提示验证是否成功了:

image

验证过程–第1个Call

验证过程主要是3个call

首先是第一个call:

内容较少,简单来说就是填充数组

填充16个FF,然后填充16*16个00,然后再填充16个FF

image

验证过程–第2个Call

第2个Call内容多点

首先是对输入的Name进行处理,累加每一个字符,得到一个累加值,保存到dl

image

接下来生成CC,通过累加值dl和取出来的字节进行异或,得到数组索引,该位置是0就往该位置填充CC,计数CC的数量

累加值异或字节,然后用过的累加值再减去字节值,来使得CC随机分布,但最终取决于输入的Name

image

再往下就是填充DD,总共填充一个,接着用刚刚填充CC的位置计算方法进行

image

再往后就是填充99:

直接用计算到最后的dl作为索引进行,如果是00,就填充为99,否则就往前挪一格再次判断,最后保存99的地址

image

使用字符串selph生成一个地图看看:

image

验证过程–第3个Call

到这里已经很明显的感觉到了,16*16的地图,生成了很多CC,然后有一个DD,一个99,再加上程序名snake,这就是一个贪吃蛇啊

第一个call开辟空间,第二个call布置场地,第三个call理所应当就是开始游戏了!

首先获取当前位置和序列号,通过序列号的输入来进行移动

首先是判断输入是否是数字,是数字则直接进行移动,这里的移动是通过加减数组的索引进行的

判断方式是这样进行的:取数字的后两位:

  • 00:向下一格
  • 01:向上一格
  • 10:向左一格
  • 11:向右一格

如果数字不只是后两位有值,则执行完用前两位再次走一遍判断,比如9,就是1001,就是上左移动一格,如果输入的是大写字母的话,也是类似的,具体可见反汇编这一段的计算过程

image

计算完移动方向之后,该进行移动判断了:

如果下一个位置是0,或者CC,都跳转去执行,如果是99则返回0失败,如果是DD且没吃完CC,也返回0失败,如果是CC吃完了,就是返回1成功

image

首先看如果下一个位置是00怎么处理:

调用一个call,就是走格子用的,然后判断是否有高2位,有的话再按高2位走一遍,没有的话,获取下一个字符进入下一个循环

image

接下来看看这个走格子的call:

首先是获取当前格子的新位置,起始位置,把新位置写入99,把当前位置写入00

然后edi+4进行判断,edi里装的是个数组,数组成员是当前蛇的身子的位置,当长度大于1的时候,edi+4就是第二个位置,有值的时候,把刚刚写入0的位置作为新位置,把身子的位置作为当前位置,再次进行相同的操作

视觉效果就是,身子跟着头一起移动了

image

回到刚刚的循环里,如果移动遇到了CC则再次执行这个移动的函数,同时给count计数-1,这个count变量保存的是当前场上CC的数量

然后把这个函数清零的位置变成99,也就是让蛇身子最后一个位置本来清零了,结果又填充回99,同时把新的位置加入到身子数组里

image

到这里,整个程序的逻辑分析完整了,就是贪吃蛇,吃完所有CC走到DD即可验证通过

注册机

注册码生成算法:

#include <iostream>
#include <setjmp.h>
using namespace std;

uint8_t areas[18][16] = {0};
uint8_t countCC = 0;
uint8_t snake[10][2] = {0};
uint8_t pos[10][2] = { 0 };

void GenerateAreas(string str) {
	// 场地生成
	memset(&areas[0], 0xFF, 0x10);
	memset(&areas[1], 0, 0x100);
	memset(&areas[17], 0xFF, 0x10);

	// 填充CC
	uint8_t* areasBegin = (uint8_t*)&areas[1];
	uint8_t sum = 0;
	uint8_t tmp = 0;
	for (auto var : str) sum += var;
	for (auto var : str) {
		tmp = var ^ sum;
		sum -= tmp;
		*(areasBegin + tmp) = 0xCC;
		// 保存坐标
		pos[countCC][0] = tmp / 16;
		pos[countCC][1] = tmp % 16;
		countCC++;
	}

	// 填充DD
	sum ^= tmp;
	for (; *(areasBegin + (tmp -= sum)) == 0xCC; sum--);
	*(areasBegin + tmp) = 0xDD;
	pos[countCC][0] = tmp / 16;
	pos[countCC][1] = tmp % 16;;

	// 填充99
	tmp = sum;
	for (; *(areasBegin + tmp) == 0xCC || *(areasBegin + tmp) == 0xDD; tmp--);
	*(areasBegin + tmp) = 0x99;
	snake[0][0] = tmp / 16;
	snake[0][1] = tmp % 16;
	countCC++;
}

int main()
{
	GenerateAreas("selph");

	// 生成注册码:长度短的情况下,不用考虑自己咬到自己
	uint8_t x = snake[0][0];
	uint8_t y = snake[0][1];
	for (int i = 0; i<countCC; i++) {
		uint8_t xCC = pos[i][0];
		uint8_t yCC = pos[i][1];
		if (yCC - y >= 0) for (int i = 0; i < yCC - y; i++)cout << "3";
		else for (int i = 0; i < y - yCC; i++)cout << "2";

		if (xCC - x >= 0) for (int i = 0; i < xCC - x; i++)cout << "0";
		else for (int i = 0; i < x - xCC; i++)cout << "1";

		x = xCC;
		y = yCC;
	}
}

效果:

selph
33333333333333311222222200000031111333111111222200000000000

image

总结

有趣的验证方式,通过输入的用户名生成贪吃蛇地图,通过密码来进行移动,吃完豆子CC,走到终点DD算验证通过,很有趣的一次逆向体验


评论