前言
曾经试图挑战160个cm练习,但是很快又放弃了,这次重新归来,由于很多cm很重复没啥意思,所以就挑着有趣的或者有价值的来论坛发帖跟大家分享分享(当然,也会有其他来源的cm)
对于其他的cm分析,有兴趣的童鞋可以去我的博客上逛逛
本文的cm来自新160个crackme练习中的第60个,以静态分析为主,动态分析可以看b站up主的分析视频
信息收集
运行情况:
查壳与脱壳:
调试分析
Check按钮事件
老样子,IDA打开,找到Check按钮的事件分支:
首先是获取Name和Serial,为空会提示
然后一个call验证Serial合法性,不行就弹窗提示
这个call的内容如下:判断内容要由数字和大写字母组成
再往下就是三个call和弹窗提示验证是否成功了:
验证过程--第1个Call
验证过程主要是3个call
首先是第一个call:
内容较少,简单来说就是填充数组
填充16个FF,然后填充16*16个00,然后再填充16个FF
验证过程--第2个Call
第2个Call内容多点
首先是对输入的Name进行处理,累加每一个字符,得到一个累加值,保存到dl
接下来生成CC,通过累加值dl和取出来的字节进行异或,得到数组索引,该位置是0就往该位置填充CC,计数CC的数量
累加值异或字节,然后用过的累加值再减去字节值,来使得CC随机分布,但最终取决于输入的Name
再往下就是填充DD,总共填充一个,接着用刚刚填充CC的位置计算方法进行
再往后就是填充99:
直接用计算到最后的dl作为索引进行,如果是00,就填充为99,否则就往前挪一格再次判断,最后保存99的地址
使用字符串selph生成一个地图看看:
验证过程--第3个Call
到这里已经很明显的感觉到了,16*16的地图,生成了很多CC,然后有一个DD,一个99,再加上程序名snake,这就是一个贪吃蛇啊
第一个call开辟空间,第二个call布置场地,第三个call理所应当就是开始游戏了!
首先获取当前位置和序列号,通过序列号的输入来进行移动
首先是判断输入是否是数字,是数字则直接进行移动,这里的移动是通过加减数组的索引进行的
判断方式是这样进行的:取数字的后两位:
- 00:向下一格
- 01:向上一格
- 10:向左一格
- 11:向右一格
如果数字不只是后两位有值,则执行完用前两位再次走一遍判断,比如9,就是1001,就是上左移动一格,如果输入的是大写字母的话,也是类似的,具体可见反汇编这一段的计算过程
计算完移动方向之后,该进行移动判断了:
如果下一个位置是0,或者CC,都跳转去执行,如果是99则返回0失败,如果是DD且没吃完CC,也返回0失败,如果是CC吃完了,就是返回1成功
首先看如果下一个位置是00怎么处理:
调用一个call,就是走格子用的,然后判断是否有高2位,有的话再按高2位走一遍,没有的话,获取下一个字符进入下一个循环
接下来看看这个走格子的call:
首先是获取当前格子的新位置,起始位置,把新位置写入99,把当前位置写入00
然后edi+4进行判断,edi里装的是个数组,数组成员是当前蛇的身子的位置,当长度大于1的时候,edi+4就是第二个位置,有值的时候,把刚刚写入0的位置作为新位置,把身子的位置作为当前位置,再次进行相同的操作
视觉效果就是,身子跟着头一起移动了
回到刚刚的循环里,如果移动遇到了CC则再次执行这个移动的函数,同时给count计数-1,这个count变量保存的是当前场上CC的数量
然后把这个函数清零的位置变成99,也就是让蛇身子最后一个位置本来清零了,结果又填充回99,同时把新的位置加入到身子数组里
到这里,整个程序的逻辑分析完整了,就是贪吃蛇,吃完所有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
总结
有趣的验证方式,通过输入的用户名生成贪吃蛇地图,通过密码来进行移动,吃完豆子CC,走到终点DD算验证通过,很有趣的一次逆向体验