Lani 发表于 2023-5-19 16:05

对 latvianfarmer17's Second? crackme 一次逆向记录

# 前言
> 说明:本贴是关于自己逆向的一次记录。如果有问题,也请大佬多多指导。
>
> 本体名称:latvianfarmer17's Second? crackme
> 本体作者:latvianfarmer17
> 上传日期:8:05 PM 04/26/2023
> 运行平台:Windows
> Difficulty:3.2 Rate!
> Quality   :4.0 Rate!
> Arch       :x86-64
> 下载地址:https://crackmes.one/crackme/6449842433c5d43938912c24
> 注写:这个网站需要自己先注册账号才能下载

## 1.本次逆向所用到的工具
1.IDA 7 Pro
2.x64Debug
3.ExeinfoPe
4.Microsoft Visual Studio 2022
## 2.开始逆向的第一步---查壳
首先通过工具 ExeinfoPe 查询该 CrackMe 是否有壳,以及这个程序编译器版本等。
[!(https://s1.ax1x.com/2023/05/19/p9hhVFx.png)](https://imgse.com/i/p9hhVFx)
从这里可以发现本程序是没有壳的,使用的编译器是 c++ ,连接器是 14.35 版本。
## 3.运行一下程序,初步了解一下程序的走向、流程:
[!(https://s1.ax1x.com/2023/05/19/p9hhGkt.png)](https://imgse.com/i/p9hhGkt)

发现是输入两个字符串,但是不能输入特殊字符:&……%等,会直接退出
## 4. 已经发现是无壳程序,并且也了解了一下大体流程,放入 IDA 按下 F5 反编译查看一下具体代码逻辑
[!(https://s1.ax1x.com/2023/05/19/p9hhRcF.png)](https://imgse.com/i/p9hhRcF)
从反编译后的代码可以看见刚刚流程的一部分字符串,比如:Enter username,Enter ID,但是也有一些其他没有看到过的字符串
考虑到这个程序可能没有 system("pause") 等暂停函数,效果也可能是显示了哪些没有看到的字符串但是程序很快,直接就退出了
便没有显示出来。
[!(https://s1.ax1x.com/2023/05/19/p9h4dC6.png)](https://imgse.com/i/p9h4dC6)
在反编译里可以看到很多这种函数,考虑到 c/c++ 的代码,合理猜测这是 printf 函数,将IDA里面的函数名修改一下,以便
理解。右键函数名,点击 rename global item
[!(https://s1.ax1x.com/2023/05/19/p9h4lgU.png)](https://imgse.com/i/p9h4lgU)
在 Name 框里输入 printf 函数名,直接点击确定。
其次,在IDA还看到这样的函数
[!(https://s1.ax1x.com/2023/05/19/p9h4BvD.png)](https://imgse.com/i/p9h4BvD)
上边的有百分号和加上一些框框,下边的是 %s ,推测一下这应该是输入函数,但是为什么上边有 %[^\n],我猜应该是过滤
用的,将这个函数我还是修改为 scanf ,以便理解。但是仅仅只有这个格式控制符,没有接受地址是很奇怪的,应该有一些没有反编译完毕,
再次按一下 F5 没有出现变化,选择直接点击这个 sub140001080 ,进入该函数,然后退出 Esc 出来,再次按一下 F5 ,该函数发生了变化

注:经常会出现这种反编译情况,就是“缺胳膊少腿”的情况,在第一次反编译之后,仍然需要点击进入函数,再次按 F5 的方式再次编译。
出现了 v10 ,应该是 v10 用来存放接受的 username,至于那个格式控制符,我对于过滤的情况不太了解,也希望大佬帮忙解释一下。不过不影响逆向
的整体逻辑。
后边也是 Username can only be up to 16 characters long 这个字符,猜测应该是最多16位。
[!(https://s1.ax1x.com/2023/05/19/p9h4XGV.png)](https://imgse.com/i/p9h4XGV)
通过下边的这个循环,我坚信这个猜测是正确的
[!(https://s1.ax1x.com/2023/05/19/p9h5MdA.png)](https://imgse.com/i/p9h5MdA)
查看这个地方的判定,里边有一些 97 65 48 这些值,通过右键点击 char 转换,发现这些字符应该是 A a 0,所以这里应该是过滤的地方,上边那个格式控制符可能不是?
[!(https://s1.ax1x.com/2023/05/19/p9h5AG6.png)](https://imgse.com/i/p9h5AG6)

[!(https://s1.ax1x.com/2023/05/19/p9h51it.png)](https://imgse.com/i/p9h51it)
通过点击 v5 发现 v5 是通过 v10 来的,所以这里是对输入的 username 进行 if 判断

[!(https://s1.ax1x.com/2023/05/19/p9h58Rf.png)](https://imgse.com/i/p9h58Rf)
根据这个字符串,里边的 alphanumerical 这个单词

[!(https://s1.ax1x.com/2023/05/19/p9h5Gz8.png)](https://imgse.com/i/p9h5Gz8)
说明只能输入字符数字,所以上边那个就是一些过滤,不能输入其他特效字符

[!(https://s1.ax1x.com/2023/05/19/p9h5NLQ.png)](https://imgse.com/i/p9h5NLQ)
经过自己的函数命名之后,这个也很清楚了,输入的 ID 字符串保存在 v11 里边

!(https://img.vinua.cn/images/OgvOh.png)
通过点击发现,v10 和 v11 是有交互处理的,处理之后的数值赋给了 v6

!(https://img.vinua.cn/images/OgP5J.png)
通过这里,可以查看到合法的话,需要 v7 == 1,如果 v7 == 0 就会打印 Sorry, wrong ID... 的字符串

!(https://img.vinua.cn/images/OgeUK.png)
点击 v7 发现,要想要 v7 == 1 就得执行上边的 while 里边的函数,但是满足 v11 == v11,这个情况
但是如要要满足,只有 v6 == 0 的时候才正确,或者是可以对称?里边所有的值都是一样的?只是有这样的猜测

!(https://img.vinua.cn/images/OgilC.png)
进入 sub1400010E0 函数查看一下
看到有俩个字符串,旁边也有长度,0x10,0x14 分别是 16 和 20

!(https://img.vinua.cn/images/Ogx9E.png)
也有跟上层函数一样的判断

!(https://img.vinua.cn/images/OgUhD.png)
!(https://img.vinua.cn/images/OgEjl.png)
看到下边有一系列运算,也有同样的上层函数判断,多了一个位运算 和 取余操作

!(https://img.vinua.cn/images/Ogj51.png)
return 的是 v8 不是特别懂

总结:基本从 IDA 看出来的东西就这么多了。对输入的字符进行过滤,只能输入数字字母,输入长度不能超过16,如果正确的话
则输出 Valid ID! ,错误的话就输出 Sorry, wrong ID... ,但是由于本程序没有停断函数,所以这些字符串都是一闪而过。有一个函数对输入的
username 字符串进行了处理操作,在 IDA 上没有看太明白,考虑动态继续分析。

## 5. 进行 x64Debug 动态运行

运行之后直接对所有模块进行搜索字符串

!(https://img.vinua.cn/images/Ogb98.png)
可以很清新的看到一些熟悉的字符,点进去继续查看

!(https://img.vinua.cn/images/OghQb.png)
在这里这俩个函数可以对照 IDA 识别出来是printf 和 scanf ,直接把这些函数修改名字,方便查看


!(https://img.vinua.cn/images/OgssX.png)
修改方式如上,右键在标签里选择添加到地址


一直查看到下方
!(https://img.vinua.cn/images/OgytY.png)
有这样一段 test al,al 这样的汇编代码,一般用来查看 al 是否是0
对照 IDA 里面的反编译源码,可以发现,这里应该是判断 al 是否是 1 ,然后后边用来输出 valid ID
!(https://img.vinua.cn/images/OgWXj.png)
尝试将 al 位 置 1
!(https://img.vinua.cn/images/Ogfjp.png)
修改后果然跳出了正确的字符
到这里爆破基本上就可以实现了。也算是完成了逆向最基本的要求。

不过出于学习的态度,我们还应该再深一步,查看一下内部算法,完成这个 CrackMe 注册机的编写。

## 6. CrackMe 关键函数分析查看,以及注册机的编写


!(https://img.vinua.cn/images/OglnV.png)
运行下来查看,这里应该是核心内部算法
!(https://img.vinua.cn/images/Ogq70.png)
再查看一下 IDA 里边的流程,确实在这些字符之下,有这样的算法函数,接下来就要进入这个内部计算函数,好好查看一下他的运算方式。

!(https://img.vinua.cn/images/OgBUw.png)
针对应该输入的字符串,我这里选择了 1234 作为 username ,5678 作为 ID ,上下不一致是为了可以区分哪个字符是 username ,哪个字符是 ID

!(https://img.vinua.cn/images/OgHRA.png)
刚进入会发现这些,上边的那几个应该是 ID 了,对于 xmm0,知道这是一个寄存器就好了。
!(https://img.vinua.cn/images/OgnQR.png)
并且这些 xmm0 在 IDA 里,也是有体现的
!(https://img.vinua.cn/images/OgCsW.png)
继续单步下来,也发现这些判断方法

继续往下分析
!(https://img.vinua.cn/images/OgM8Q.png)
可以看到这些类似的代码执行了4次

!(https://img.vinua.cn/images/OgdX4.png)
同样在 IDA 里也是观察到有类似的出现四次的代码

!(https://img.vinua.cn/images/OgtCv.png)
!(https://img.vinua.cn/images/Og87k.png)
!(https://img.vinua.cn/images/Og4mJ.png)
!(https://img.vinua.cn/images/Ogc2K.png)
但是这里的数值不太一样,第一个 +0,第二个 +0x539,第三个+0xA72,第四个+0xFAB

!(https://img.vinua.cn/images/Og1RC.png)
点击v9查看到开始为 0

!(https://img.vinua.cn/images/OgReE.png)
而这里的 v6 是根据累加计算出来的一个数据,不太好看出来是什么,而后面也是通过多次动态调试,发现这个是序号

!(https://img.vinua.cn/images/Og3s7.png)
在动态调试过程中,也发现寄存器出现了一个很长的字符串

!(https://img.vinua.cn/images/OgL8D.png)
通过查看,这里字符串后半部分是 IDA 里开头出现的字符串
那么 IDA 在反编译时应该是少了一些东西,毕竟是静态分析
但是在 x64Debug 这里的字符串更长一些,数了一下有 61 位。

!(https://img.vinua.cn/images/OgwaU.png)
通过多次分析,可以看到这里 je 应该是从 61 位字符串中找到了,然后把序号位置放到 rsi 中(这里是多次动态调试发现的,调试过程就不过多阐释,这里只给发现结果)

!(https://img.vinua.cn/images/Og5uL.png)
可以发现 1 确实是在这个字符串中的第 3 位

!(https://img.vinua.cn/images/Og7Cl.png)
2 在字符串中的 0x2f 位置,后边的 3,4 也是对应的位置,所以可以判断,那个 v6 就应该是 ID 在 61 位字符串中的序号位置

!(https://img.vinua.cn/images/OgNNi.png)
在后边也是发现,这里有一个大跳转,然后 f8 跟过去发现还是运算这 4 个循环,跟了一圈还是 1234 来找对应的位置

!(https://img.vinua.cn/images/Ogrm1.png)
在 IDA 里,查看到原始是 do while 循环,这里应该是直到字符串16位的最后一位,因为之前一直在有暗示字符串是16 位,
虽然输入了 1234 ,只输入了 4 位,但是下一圈大跳转,还是原来的 1234 应该是有自动补全功能。后续 username 我输入 123456,123abfD,
可以发现确实是自带 16 位补全功能,如果不满 16 位,就依次按照顺序拼接在后边。

!(https://img.vinua.cn/images/OhK2y.png)
在追踪的过程中,如上,发现 0x4ec4ec4f , 在这里其实是有个 %26 取余操作,这里使用的是乘法替代除法来实现的,具体可以查看
https://www.lovesandy.cc/2023/03/16/%E5%8F%96%E4%BD%99%E8%BF%90%E7%AE%97/ 这个帖子了解一下编译器优化取余运算

!(https://img.vinua.cn/images/OhO3I.png)
跳出内部运算函数,在外边可以看到一个用来比对的指令,同时出现了 5678 就是之前输入的 ID 字串,这里应该是 username 运算后的
key 值来比对最后的 ID 值,跟入保存 key 值的地址 ( ctrl + g ),输入 rax + rdx 点击确定

!(https://img.vinua.cn/images/Ohae8.png)
在地址中看到一串字串,我们把这些字串复制下来,保持 username 不变,重新运行程序,再次在输入 ID 的地方输入这些字串尝试一下

!(https://img.vinua.cn/images/Ohvyb.png)
发现这个字串确实是根据 1234 来定义的 ID 值,同时也可以确认一下这个 ID 值到底是不是固定的

!(https://img.vinua.cn/images/OhzoX.png)
可以看到输入不同的 username ,ID 值应该也是不同的

因此具体的密码会根据 username 计算的,需要再次进入核心算法查看一下
不过经过刚才的一通分析,了解到:核心算法应该是根据
Yo15z9atB8gZJRhpi0dLfCSA3wVFEyuHKOrTWG4mlXvPeU2j67NbMxsknQqcI
这个字串,来确定符号下标,一共要找 16 次,内部有 4 个为一个小循环,然后还有 4 次大循环。

!(https://img.vinua.cn/images/OhPaY.png)
在重新分析核心算法时,要紧盯这几个寄存器,明白这几个寄存器的用处
rsi 专门用来存放序号位置,r8 用来存放取出来的一个 username 字符值,rbp 是存放键值表

!(https://img.vinua.cn/images/OhQJj.png)
继续往下分析,在这里发现有一个熟悉的字符出现了,就是 L ,上次密码的第一个字母 (LRZFZCEJELIGIZVQ) 而这个 L 就是通过上边一条指令获取到的,所以应该目前该关注的就是上一条指令

!(https://img.vinua.cn/images/OheCp.png)
跟进上一条指令的内存

!(https://img.vinua.cn/images/OhiNV.png)
发现 L 就在这个地方,下边还有那一开始的字串
往上滑一下,还发现了其他字符

!(https://img.vinua.cn/images/OhA00.png)
“EYIPVMTKFXJSGWQOADRNCBLHZU”
将上边这一串字符也摘下来,发现这是 26 个长度的字符串

然后再结合 IDA 里那个取余的数字 “26”,可以猜测到就是从这26个字符中选择一个字符,然后填入到 ID 中


上下查看,发现 v9 第一次是 0 ,v6 是从 61个字符串中与 username 字符对比来的序号值,也就是1的位置 3


所以就是 3 << 16 位 :3 0000 然后对 26 取余 3 0000 % 0x1A = 0x16 也就是 22,下标就是 ,

而 L 在这 26 个字串 EYIPVMTKFXJSGWQOADRNCBLHZU 的位置正好是第 23 位,也就是 的位置,所以运算应该就是这样:

!(https://img.vinua.cn/images/Oh6yR.png)
先从 61 字符串取序号位置 v6 然后 v6 + v9 ,然后左移 16 位,然后对 26 取余
,计算获得字符下标,获取目标字符。

!(https://img.vinua.cn/images/OhF2w.png)
!(https://img.vinua.cn/images/OhU32.png)
!(https://img.vinua.cn/images/Oh0iA.png)
而其他三个也是有其他额外数值增加的,0x539,0xA72,0xFAB

从 x64Debug 中也能看到,所以运算方法就知道了,但是如果是 1234 1234 1234 1234 排列,那么下一组值在运算中
也应该是一样的,但是进行下一个大循环,1234 取值是不同的,经过再次查看发现到

!(https://img.vinua.cn/images/OhEoW.png)
!(https://img.vinua.cn/images/OhjvQ.png)
v9 每次大循环都是要 + 0x14E4 的,这个 x64Debug 中也能看到,

所以现在的情况就很明朗了
具体的 ID 运算数值:
就是先从 61 字串中找到序号位置,然后 +v9 ,(v9 每个大循环会自增),取得的值左移 16 位之后对 26 取余,然后
从 26 字串中取一个字符,放入到结果中。
知道这些之后,我在下方提供我注册机代码的编写。其实在复测过程中发现,123abfD 一开始是计算错误的,因为在
那 61 个字符之中是没有 D 这个字符的,所以如果有查询不到的字符,就返回 0 ,这个在 x64Debug 中能自己追踪到。
(第一次写这些记录,有的地方可能会晦涩一点,大佬们都可以提出来,我也会努力更正)

```
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define LEN 16
#define NUM1 0x539
#define NUM2 0xA72
#define NUM3 0xFAB
#define TOTAL 0x14E4



char gKey[] = "Yo15z9atB8gZJRhpi0dLfCSA3wVFEyuHKOrTWG4mlXvPeU2j67NbMxsknQqcI";
char gKey2[] = "EYIPVMTKFXJSGWQOADRNCBLHZU";
char gResult = "";

// 用于下标查找
int GetIndex(char ch)
{
char cTemp = 0;
int nLen = sizeof(gKey);
for (sizet i = 0; i < nLen; i++)
{
cTemp = gKey;
if (cTemp == ch)
{
return i + 1;
}
}
return 0;
}

void HandleString(char UserName)
{
int nTotal = 0;
int nCount = 0;
int nIndex = 0;
int nResultIndex = 0;


for (sizet i = 0; i < 4; i++)
{
// nTotal + Index
// 获取序号
nIndex = GetIndex(UserName);
// 计算结果序号
nResultIndex = ((nTotal + nIndex) <<16) % 26;
// 将序列里的字符取出来放入结果字符里
gResult = gKey2;
nCount++;


// nTotal + Index + NUM1
// 获取序号
nIndex = GetIndex(UserName);
// 计算结果序号
nResultIndex = ((nTotal + nIndex + NUM1) << 16) % 26;
// 将序列里的字符取出来放入结果字符里
gResult = gKey2;
nCount++;


// nTotal + Index + NUM2
// 获取序号
nIndex = GetIndex(UserName);
// 计算结果序号
nResultIndex = ((nTotal + nIndex + NUM2) << 16) % 26;
// 将序列里的字符取出来放入结果字符里
gResult = gKey2;
nCount++;


// nTotal + Index + NUM3
// 获取序号
nIndex = GetIndex(UserName);
// 计算结果序号
nResultIndex = ((nTotal + nIndex + NUM3) << 16) % 26;
// 将序列里的字符取出来放入结果字符里
gResult = gKey2;
nCount++;

nTotal += TOTAL;
}



}





int main()
{

char UserName = "";
char p = nullptr;
int UserLen = 0;


printf("输入需要注册的用户名\n");
scanf_s("%s", UserName, 20);
UserLen = strlen(UserName);
if (UserLen > LEN || UserLen < 0 || UserLen == 0)
{
printf("输入错误\n");
exit(0);
}
// 对不满长度的字符串进行操作
// 1. 输入满 16 位
// 2. 输入不满 16 位,补齐 16 位
if (UserLen == LEN)
{

}
// 1234567 1234567 12
// 12345678 12345678
// 1 116
else if (UserLen < LEN)
{
int nMul = LEN / UserLen - 1;
int nRemainder = LEN % UserLen;
// 如果没有余数
if (nRemainder == 0)
{
char Temp = "";
strcpy_s(Temp, 20, UserName);
// 复制倍数次,拼接上原始字符串
for (sizet i = 0; i < nMul; i++)
{
strcat_s(UserName, 20, Temp);
}
}
// 如果有余数
else
{
char Temp = "";
strcpy_s(Temp, 20, UserName);
// 复制倍数次,拼接上原始字符串
for (sizet i = 0; i < nMul; i++)
{
strcat_s(UserName, 20, Temp);
}
// 将剩余的部分拼接到字符串里
Temp = '\0';
strcat_s(UserName, 20, Temp);
}
}

HandleString(UserName);
printf("处理后的字符:%s\n",UserName);
printf("结果字符:%s\n",gResult);
system("pause");
return 0;
}

```

Hmily 发表于 2023-5-20 12:09

前面图片不能显示,是不是图床的地址写错了?修改一下吧,建议最好图片上传论坛本地。

zhaocongxu 发表于 2023-5-20 21:22

士大夫大师傅士大夫士大夫方式

zhaocongxu 发表于 2023-5-20 21:25

阿萨法发打发打发

zjh889 发表于 2023-5-21 11:07

太深奥了,很难看得懂!

liuchun 发表于 2023-5-21 22:50

v和计划开发规范积分兑换

Lani 发表于 2023-5-23 10:24

Hmily 发表于 2023-5-20 12:09
前面图片不能显示,是不是图床的地址写错了?修改一下吧,建议最好图片上传论坛本地。

我用手机也浏览了一下,没有看到哪个图片没显示

Hmily 发表于 2023-5-23 15:41

Lani 发表于 2023-5-23 10:24
我用手机也浏览了一下,没有看到哪个图片没显示

知道原因了,之前网络你前面图床的图片无法访问导致的,建议还是把图片上传论坛本地把,防止以后图床不知道啥时候就挂了。
页: [1]
查看完整版本: 对 latvianfarmer17's Second? crackme 一次逆向记录