Crackme算法

Crackme算法

0x00前言

因为汇编太水了,所以决定做一下CrackMe练习,可能不会一直弄,精力有限

0x01-笔记

XCHG(交换数据)指令交换两个操作数内容

mov和lea的区别

mov:当mov eax,[]表示将[]中地址存储的值存放到eax中,当然也可以直接mov地址到eax中,如mov eax,地址

lea:当lea eax,[],表示将[]中的地址存放到eax中,相当于指针,主要是计算地址

002-abexcm5

首先先通过字符串引用定位到正确判断的位置

调用函数前需要将参数压入堆栈中,并且靠前的参数后压入栈中

正确判断

接下来可以按x寻找调用位置,红色框中的就是跳转的位置

调用位置

再往上找比较函数

可以看到这里调用了cmp函数,而且压入了两个参数,第一个是真正的注册码,第二个是我们的输入

cmp:比较两个操作数,根据相减结果来改变零标志位,结果为0时,零标志位为1(Z位)

当第一位小于第二位时,S位为1

比较函数

寻找对注册码的操作

可以看到这里进行了两次拼接操作

两次拼接

这个拼接的字符串是已知的,再看另一个

可以看到先通过GetVolumeInformationA获取驱动器信息,并生成字符串存储到aData4562Abex中,然后将字符串String2拼接到生成的字符串后

aData4562Abex操作

接下来重点讲讲ds段寄存器和重要的操作数

ds:[地址]就相当于ds:地址,而且取出的是里面的内容,然后进行加一,

dec dl是dl的值–,并且除了CF标志位,其他都会改变。

dec dl对ZF标志位的影响,jnz跳转的条件是ZF==0,当dl的值为0时,设置ZF=1;否则设置为0

而一开始mov dl,2就是初始化迭代的次数,那么从mov到jnz这一段就是一个循环。

总的加密就是取出aData字符串的前四位进行两次+1

所以我们还原一下过程即可得到注册码,但是我不明白GetVolumeInformationA生成的字符串,所以动调了

最后注册码L2C-5781Fcvc4562-ABEX

注册成功

003-Cruehead-CrackMe-3

运行时发现是未cracked的

运行时

了解到这是需要KEY文件才能正确crack的,我们从头开始分析

CreateFileA

1
2
3
4
5
6
7
8
9
HANDLE CreateFileA(
[in] LPCSTR lpFileName,
[in] DWORD dwDesiredAccess,
[in] DWORD dwShareMode,
[in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,
[in] DWORD dwCreationDisposition,
[in] DWORD dwFlagsAndAttributes,
[in, optional] HANDLE hTemplateFile
);

具体参数参考https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea

注意压入栈的顺序即可,这里根据参数应该是打开CRACKME3.KEY文件,如果没有的话返回值设置为-1

返回值一般存储在eax寄存器中

CMP汇编指令会修改ZF和CF标志寄存器,如果相同的话设置ZF为1,否则为0

跳转

如果不相等,就跳转到401043,这里我们默认打开了

ReadFile

可以看到先把0x12存入eax,把字符串地址放入ebx中

offset存储的是字符串的地址

接下来调用ReadFile读取文件内容,长度是0x12,位置是字符串地址存储的内容

1
2
3
4
5
6
7
8
9
BOOL ReadFile(
HANDLE hFile, //文件的句柄
LPVOID lpBuffer, //用于保存读入数据的一个缓冲区
DWORD nNumberOfBytesToRead, //要读入的字节数
LPDWORD lpNumberOfBytesRead, //指向实际读取字节数的指针
LPOVERLAPPED lpOverlapped
//如文件打开时指定了FILE_FLAG_OVERLAPPED,那么必须,用这个参数引用一个特殊的结构。
//该结构定义了一次异步读取操作。否则,应将这个参数设为NULL
);

加密函数之一

先来看加密函数的初始化操作

两个xor是将ecx和eax的值清空

将esp+arg_0存储的值赋值到esi中,也就是读取字符串的地址

这里可以看到arg_0=DWORD 4,esp+4就是我们压入栈中的参数,因为call会让ESP往低地址移动

再将0x41赋值给bl

初始化操作

接下来看加密的操作,首先先将esi的存储的值看作地址,取出里面的内容放到al中,再将al异或上bl的值,再存放回原来的地址

inc实现的是自增1

esi存放的是地址,+1就相当于指针后移

加密操作

指针移动

然后将异或后的结果加到ds:dword_4020F9存储的内容中

判断al的值是否为0,如果是的话,跳转到

当al为0时跳转的地址

接着就是cl++,然后判断bl的值是否为0x4F,因为初始时bl的值为0x41,所以加密数据的长度是0x4f-0x41

最后,函数loc_401335结束前把ecx的值存放到ds段寄存器地址的内容中

因为异或后相加的结果存储在dword_4020F9中,这里将它和0x12345678异或

add esp,4是因为call的时候将其下一条指令压入栈中,所以需要add esp,4

接下来又将我们加密后的内容压入栈,进行下一个函数的操作

第二轮操作

先把esp+4存储的内容也就是存储字符串的首地址存放到esi中,然后esi+0xE,也就是地址后移0xE,再将其存储的值放入eax中,0x12-0xE=4,也就是将最后四个存放到eax寄存器中

第二轮操作

将最后四位与dword_4020F9,也就是刚才和0x12345678异或后的值比较

setz al;如果ZF=1,则设置目标数为1,否则则为0

test al,al;如果al&al==0,也就是al的值为0,那么ZF会被设置为1,否则则为0

比较

接下来是一些创建窗口和弹出窗口的代码

窗口代码

看一下成功的部分代码

成功部分

主要看一下这个

移动字符串

即字符串传送指令,这条指令按字节传送数据。通过SI和DI这两个寄存器控制字符串的源地址和目标地址,比如DS:SI这段地址的N个字节复制到ES:DI指向的地址,复制后DS:SI的内容保持不变。

先把DWORD_402149,也就是前面说的加密的长度

也就是先把传入的两个参数的地址分别存入esi和edi中

而第一个参数也就是arg_0(后压入栈),被存放到esi中

同时,第一个参数也是前面存放我们字符串的地址

然后edi指向的地址后移

再循环将esi的值存入edi中,循环次数为ecx的值

这段话就是将我们最后加密的字符串拼接到Cracked By:后面

整体流程

先打开CRACKME3.KEY文件,读取前十八个字符,然后对前14个字符异或加密,并且将异或后的值和0x12345678异或后与最后四位进行比较,前十四个密文为wanao@yahoo.cn,然后动调得到最后四位字符,填充进去即可,注意大小端序

前十四位字符脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<stdio.h>
#include<string.h>

int main()
{
char name[]="wanao@yahoo.cn";
char key='A';
int num=0;
for(int i=0;i<strlen(name);++i)
{
name[i]^=key++;
num+=name[i];
}
printf("%s\n",name);
printf("%d\n",num);
int key2=0x12345678;
return 0;

}

最后四位字符

最终效果

004-Acid Bytes.2

upx壳,去掉后很快就能找到比较

006-ArturDents-CrackMe#2

先找到成功的函数

验证正确的函数

然后找到关键比较

关键函数和跳转

根据程序运行知道我们需要输入name和序列号,找到获取输入的函数

1
2
3
4
5
6
7
UINT GetDlgItemTextA(
[in] HWND hDlg,
[in] int nIDDlgItem,
[out] LPSTR lpString,
[in] int cchMax
);
//返回值是字符串的长度

lpstring就是我们的输入

关键函数

箭头处可以看到cmp esi,5,而前面将函数的返回值也就是eax的值存储到esi中,所以这是长度比较,可以看到jge下一段是说name长度必须大于5。

再看下面红色框,获取我们的输入密码后,将input和password的地址存放到eax和ebx中,再把esi也就是name的长度存入ecx中

看加密函数

关键加密函数

先把eax存储的值取出,然后减去cl的值,再与ebx存放的值作比较

下面那个跳转是跳转到离开的函数

inc是自增,也就是让寄存器指向password和input下一个字符

1
2
3
4
    mov cx, 循环的次数 (当遇到Loop标号时 cx就代表循环的次数)
标号: (标明后面就是需要循环的循环体)
循环执行的程序代码
Loop 标号//注意:每执行一次loop,ecx的值都会减1

每执行一次loop,ecx的值都会减1

所以注意这一点即可

最终

name为99999,序列号为45678即可满足

成功

007-reg

这是一个共享软件,打开时需要我们输入UserName和SN,然后生成reg.dll,再打开软件验证。

reg.dll内容

先定位到关键的字符串

关键字符串

定位过去

可以看到先把字符串也就是reg.dll移动到eax中,然后call,再对al进行验证,所以这个函数应该是打开这个文件,没有这个文件的话就返回0

接下来这一段应该是将dll文件的username和sn读取出来

读取函数

可以看到先把刚才读取的username和sn存放到edx和eax中,然后调用函数,调用结束后进行test和跳转,所以这个函数应该是加密函数

加密函数

加密函数中,先将变量的值清0,并且将username和sn存放到第一个和第二个参数中

往下看,这里先把第二个参数(序列号)的地址读取到eax中,然后返回值和0x10作比较,那么这段应该就是计算序列号的长度

关键比较

在函数出事的地方,可以看到var_8=-8,然后ebp-8就是存放的序列号

计算位置

看一下函数调用的堆栈图,EBP+x就是压入的参数

堆栈图

下面这一段应该是对序列号验证,因为sub会让标志寄存器改变

验证

FTSP汇编指令

https://blog.csdn.net/liujiayu2/article/details/77711838

最后在这里的堆栈图找到了正确的序列号,长度正好是0x10

先把两个参数存放的内容以及第三个参数也就是var_10的地址存储到ecx中,那么这个应该就是正确的序列号

在调用前面的函数前

函数有点复杂,有时间再看,据说后面有md5

ZF标志位