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拼接到生成的字符串后
接下来重点讲讲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的,我们从头开始分析
1 | HANDLE CreateFileA( |
具体参数参考https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
注意压入栈的顺序即可,这里根据参数应该是打开CRACKME3.KEY文件,如果没有的话返回值设置为-1
返回值一般存储在eax寄存器中
CMP汇编指令会修改ZF和CF标志寄存器,如果相同的话设置ZF为1,否则为0
如果不相等,就跳转到401043,这里我们默认打开了
可以看到先把0x12存入eax,把字符串地址放入ebx中
offset存储的是字符串的地址
接下来调用ReadFile读取文件内容,长度是0x12,位置是字符串地址存储的内容
1 | BOOL ReadFile( |
先来看加密函数的初始化操作
两个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,如果是的话,跳转到
接着就是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 |
|
最终效果
¶004-Acid Bytes.2
upx壳,去掉后很快就能找到比较
¶006-ArturDents-CrackMe#2
先找到成功的函数
然后找到关键比较
根据程序运行知道我们需要输入name和序列号,找到获取输入的函数
1 | UINT GetDlgItemTextA( |
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移动到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