吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2203|回复: 7
收起左侧

[原创] 对 latvianfarmer17's Second? crackme 一次逆向记录

[复制链接]
Lani 发表于 2023-5-19 16:05

前言

说明:本贴是关于自己逆向的一次记录。如果有问题,也请大佬多多指导。

本体名称:latvianfarmer17's Second? crackme [FIXED]
本体作者: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 是否有壳,以及这个程序编译器版本等。
p9hhVFx.png
从这里可以发现本程序是没有壳的,使用的编译器是 c++ ,连接器是 14.35 版本。

3.运行一下程序,初步了解一下程序的走向、流程:

p9hhGkt.png

发现是输入两个字符串,但是不能输入特殊字符:&……%等,会直接退出

4. 已经发现是无壳程序,并且也了解了一下大体流程,放入 IDA 按下 F5 反编译查看一下具体代码逻辑

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

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

p9h51it.png
通过点击 v5 发现 v5 是通过 v10 来的,所以这里是对输入的 username 进行 if 判断

p9h58Rf.png
根据这个字符串,里边的 alphanumerical 这个单词

p9h5Gz8.png
说明只能输入字符数字,所以上边那个就是一些过滤,不能输入其他特效字符

p9h5NLQ.png
经过自己的函数命名之后,这个也很清楚了,输入的 ID 字符串保存在 v11 里边

OgvOh.png
通过点击发现,v10 和 v11 是有交互处理的,处理之后的数值赋给了 v6

OgP5J.png
通过这里,可以查看到合法的话,需要 v7 == 1,如果 v7 == 0 就会打印 Sorry, wrong ID... 的字符串

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

OgilC.png
进入 sub1400010E0 函数查看一下
看到有俩个字符串,旁边也有长度,0x10,0x14 分别是 16 和 20

Ogx9E.png
也有跟上层函数一样的判断

OgUhD.png
OgEjl.png
看到下边有一系列运算,也有同样的上层函数判断,多了一个位运算 和 取余操作

Ogj51.png
return 的是 v8[16] 不是特别懂

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

5. 进行 x64Debug 动态运行

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

Ogb98.png
可以很清新的看到一些熟悉的字符,点进去继续查看

OghQb.png
在这里这俩个函数可以对照 IDA 识别出来是printf 和 scanf ,直接把这些函数修改名字,方便查看

OgssX.png
修改方式如上,右键在标签里选择添加到地址

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

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

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

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

OgBUw.png
针对应该输入的字符串,我这里选择了 1234 作为 username ,5678 作为 ID ,上下不一致是为了可以区分哪个字符是 username ,哪个字符是 ID

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

继续往下分析
OgM8Q.png
可以看到这些类似的代码执行了4次

OgdX4.png
同样在 IDA 里也是观察到有类似的出现四次的代码

OgtCv.png
Og87k.png
Og4mJ.png
Ogc2K.png
但是这里的数值不太一样,第一个 +0,第二个 +0x539,第三个+0xA72,第四个+0xFAB

Og1RC.png
点击v9查看到开始为 0

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

Og3s7.png
在动态调试过程中,也发现寄存器出现了一个很长的字符串

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

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

Og5uL.png
可以发现 1 确实是在这个字符串中的第 3 位

Og7Cl.png
2 在字符串中的 0x2f 位置,后边的 3,4 也是对应的位置,所以可以判断,那个 v6 就应该是 ID 在 61 位字符串中的序号位置

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

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

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

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

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

Ohvyb.png
发现这个字串确实是根据 1234 来定义的 ID 值,同时也可以确认一下这个 ID 值到底是不是固定的

OhzoX.png
可以看到输入不同的 username ,ID 值应该也是不同的

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

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

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

OheCp.png
跟进上一条指令的内存

OhiNV.png
发现 L 就在这个地方,下边还有那一开始的字串
往上滑一下,还发现了其他字符

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,下标就是 [22] ,

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

Oh6yR.png
先从 61 字符串取序号位置 v6 然后 v6 + v9 ,然后左移 16 位,然后对 26 取余
,计算获得字符下标,获取目标字符。

OhF2w.png
OhU32.png
Oh0iA.png
而其他三个也是有其他额外数值增加的,0x539,0xA72,0xFAB

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

OhEoW.png
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[16] = "";

// 用于下标查找
int GetIndex(char ch)
{
char cTemp = 0;
int nLen = sizeof(gKey);
for (sizet i = 0; i < nLen; i++)
{
cTemp = gKey[i];
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[nCount]);
// 计算结果序号
nResultIndex = ((nTotal + nIndex) <<16) % 26;
// 将序列里的字符取出来放入结果字符里
gResult[nCount] = gKey2[nResultIndex];
nCount++;

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

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

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

nTotal += TOTAL;
}

}

int main()
{

char UserName[20] = "";
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[20] = "";
strcpy_s(Temp, 20, UserName);
// 复制倍数次,拼接上原始字符串
for (sizet i = 0; i < nMul; i++)
{
strcat_s(UserName, 20, Temp);
}
}
// 如果有余数
else
{
char Temp[20] = "";
strcpy_s(Temp, 20, UserName);
// 复制倍数次,拼接上原始字符串
for (sizet i = 0; i < nMul; i++)
{
strcat_s(UserName, 20, Temp);
}
// 将剩余的部分拼接到字符串里
Temp[nRemainder] = '\0';
strcat_s(UserName, 20, Temp);
}
}

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

免费评分

参与人数 2威望 +1 吾爱币 +21 热心值 +2 收起 理由
笙若 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
Hmily + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

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

免费评分

参与人数 1吾爱币 -10 违规 +1 收起 理由
Hmily -10 + 1 请勿灌水,提高回帖质量是每位会员应尽的义务!

查看全部评分

zhaocongxu 发表于 2023-5-20 21:25
zjh889 发表于 2023-5-21 11:07
太深奥了,很难看得懂!
liuchun 发表于 2023-5-21 22:50
v和计划开发规范积分兑换

免费评分

参与人数 1吾爱币 -50 违规 +1 收起 理由
Hmily -50 + 1 请勿灌水,提高回帖质量是每位会员应尽的义务!

查看全部评分

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

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

点评

知道原因了,之前网络你前面图床的图片无法访问导致的,建议还是把图片上传论坛本地把,防止以后图床不知道啥时候就挂了。  详情 回复 发表于 2023-5-23 15:41
Hmily 发表于 2023-5-23 15:41
Lani 发表于 2023-5-23 10:24
我用手机也浏览了一下,没有看到哪个图片没显示

知道原因了,之前网络你前面图床的图片无法访问导致的,建议还是把图片上传论坛本地把,防止以后图床不知道啥时候就挂了。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-23 20:18

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表