1、常规扫描收集信息
2、资源查看
3、spy++查看其窗口回调函数地址
4、OD转到地址分析函数
4、查看调用 (To Be Continued…)
发现来自主模块的调用很多,这里只能一个一个尝试,看哪里有有用的信息。
5、在尝试输入用户名和密码失败后,会弹出一个对话框告诉你密码错误的提示语
其实这个字符串也是个很好的切入点,我们可以在IDA和OD中寻找有关此字符串的信息。
6、OD和IDA寻找关键函数
双击它进入到.rdata段发现函数本地,然后Ctrl+x查看交叉引用发现只有一个地方调用了它,很好我们跟进查看
然后IDA最经典的流程图来了,有了它能对我们分析起到帮助。但是一眼看上去好复杂,但是不用怕,不需要每一个框都分析,只用顺着箭头向上找,找到是从哪里跳转过来的就能找到切入点。
cmp ebx,93h
=====>这里不能相等
jnz xxxxx
cmp edi,20h
=====>这里也不能等
jz xxxxxxx
cmp edi,0EDh
=====>还是不能等
jz xxxxxxxx
cmp edi,0DBh
=====>继续不等
jnz xxxxxxx
今天继续,从昨天找到的比较edi和0xDB这里继续查看,我们打开OD,定位到这个验证的地方看,但是怎么定位到这里呢,我们可以先搜一下字符串,找到Invalid name。。。那一段,然后双击到代码段。然后你就能发现这儿有个跳转,我们可以过去看看能不能找到上一跳
当然也可以试试下面这个比较不相等的时候会跳到哪里去,我们跟过去看看。
相似的,我把能找到提示的几条跳转的结果都找了一下
然后敲黑板了,重点来了
我们找到它的上一跳发现如果这个标胶不相等就会跳到错误的区间,否则就正确了
继续找上一跳
然后就可以在这个函数上打个断点观察一下参数和返回值了
刚开是可以先去看看这个 函数的返回值是怎样的,先不具体去分析,就粗略的看一下发现有三种。
最上面的返回值是ESI,但是ESI又是啥呢,别急,我们往上翻一下
发现也有几种情况这个我们先放着吧,看看下面那个函数有什么说法。
跟着跳下来发现分别对应着三种返回这,我们要的就是DB,所以传进来的EAX应该要是2D
于是现在有一个结论:上面这个函数的返回值应该是2D才能得到正确的结果。
所以最简单粗暴的破解方法来了,就是把关键跳转那个地方给破坏掉,它无法验证DB那个地方了也就不起作用了。
这个爆破有点low,重新打开程序还是会弹出Register窗口,我们要点击一下Check License按钮还会弹出一个接受密码的对话框,然后点击OK才能打开软件,太麻烦了。因为我们刚刚只是把最外层的关键跳转给改了,里面的密码验证环节特别是关键验证没有变动,所以很麻烦。接下来尝试一下更方便的破解。
再次定位到关键函数,这里这里:
估计很多朋友都忘了随即地址在哪改了吧,我们复习一下PE文件的内容,先找到扩展头(OptionHeader)
然后找到DLL_Characteristics字段里的Dynamic_base,1代表启用,改成0就把他禁止了。
改好了之后再次打开OD回到刚刚定位的地方把前两条语句修改掉,如图
然后保存到文件
然后重新打开,就成了!!!
还得先在OD里定位到两个重要函数的地址。
先看看这个函数的参数,一个ECX和两个立即数,这看起来就是C++的thiscall了,ECX是this指针,这样我们先在数据窗口看一下这个指针指向的数据是啥。
这是this指针下的第一个偏移是我们输入的用户名
第二个偏移是我们输入的密码
第三个偏移
真正的的有用的语句在这些下面
我们看到下面有个OD分析出来的函数,应该是判断是否为空的,为空返回值就是1,否则就是0,为空就直接JNZ跳转走了
继续往下走,第二个遇到的函数就很好说了,看参数把第二个成员给了ecx然后再次检查是否为空,显然就是检查我们输入的序列号了。
在每次检查完是否为空之后就会有个比较和跳转,如果为空直接就跳走了,那么正确的执行流程应该是直接往下走的,跟进代码看看后面会不会对用户名和序列号进行访问或者修改的操作。
call完之后发现局部变量果然被修改了,可以和上图对比着看一下
可见这里应该是把我们输入的密码改成了一个16进制的字节数据
接下来再看这个ESi里面保存的全局变量是什么值,观察所知是一个999字符串,不知道干嘛的,继续往下分析
两个参数,一个999字符串的地址,一个用户名的地址
无事发生,在用户名、序列号、“999”这三个地址的值都未发生改变,推测是创建了一个QString字符串,暂时没什么影响,继续往下走。
是0到F的ASCII码
发现这里是个循环
继续往下看,发现了一个局部变量给BL赋值,跟随数据发现就是我们写入的序列号,它正在取低字节,如果将12这个地址看作数组首地址的话,那么78这里就是偏移了三个单位。
这里我们可以假设此时BL存放的是k[3]
同理,下面BH存放的就是k[5]。
友情提示,即将进入最关键的序列号计算环节了,请打起精神啊~
第一次比较:把BL和0x9C进行对比,不相等的话跳转
然后跟0xFC对比,不相等则跳转
然后跟0xAC对比,不相等则跳转
再然后就进入default处理然后直接结束了
所以拿小本本记下来,k[3]可能的值有三个0x9C、0xFC、0xAC
每一步给AL赋值的语句我们都要数据跟随一下,查看当前局部变量的值是什么,然后转换成序列号数组看看是怎么算的。
我觉得有必要解释一下这一步的过程,首先查看MOVZX
的作用
打个比方,假设AL此时的值是0x00AC,那么经过了MOVZX
的操作之后,ECX变成了0x0000 00AC。但是把它转换成C语言怎么办呢?就有了按位与上一个0xFF的操作,可以将高位清0。接下来还是一些赋值的操作,看图
回头去看一下这个函数有几个参数?只有一个PUSH
其实这个函数就是把传进来的值进行了一系列计算而已,那么可以小记一下这个call就是在处理k[0]^k[6]的数据
下面也有一个call,参数是很长的一串
然后进去发现这个函数过程也不复杂可以分析一下
接着就是下面的分析了,这个没啥技巧(当然可能是我技艺不精,有更好的方法欢迎留言~)
到这里我们可以写一下代码来验证一下到目前为止所分析的内容。先说一下思路哈:
首先我们生成一下两个随机数,如果在一个字节所能表示的数范围内满足公式:
( ( k[0]\^ k[6]\^0x18 + 0x3D)\^0xA7) & 0xFF > 0
那么就将这两个数填充到数组下标为0和6两个元素处。同理如果满足公式
那么就将这两个随机数填充到数组下标1、2、5、7处
生成了一个试试看,能不能绕过那几个跳转
昨天只是根据假定k[3]的值为0x9C,然后根据这个大前提来通过生成随机数来穷举符合k[0]和k[6],k[1]、k[2]、k[5]、k[7]之间关系的字符序列,从而能够通过一些列验证。
今天我们继续完成关键算法的分析并能实现生成能用的用户名和序列号!
接着往下走,发现有一个局部变量传递给了寄存器并且入栈了,下面就是一个字符串转换的函数,那么就可以认为这个函数的传入和传出参数是同一个,我们可以观察一下这个寄存器的值的变化。而且紧跟着ECX保存的是之前一直没有出现的用户名的地址,这个函数就是将用户名转换一下吗?我们看一下:
就是我们输入的用户名,可是第一个字符出现的地方和0x559B878多了个0x10啊,而且前面多的这些数字是干什么用的?或者说有作用吗?我们慢慢分析。只知道现在我们的用户名在这里被改成了ASCII字符串。
可见这个[EDI+0x20]
原封不动的传递了下来,现在要将它入栈准备给下一个call调用了,接着看下面的语句
然后就碰到了一个没见过的指令,SETNE
是干啥的?别慌我们查查:
还是不太懂,算了我们看寄存器的变化吧
看起来好像是把EAX从0变成了1
还是不懂这条指令是干什么的,百度,百度,百度!!!重要的事情说三遍:
看起来似乎是将ZF的值经过什么判断后赋给寄存器,我们写个小demo调试一下看看吧:
先是看看在ZF为1的情况下,SETcc咋用:关注EAX的变化!!!
总结一下:
回到我们的分析流上来:
然后紧接着就把用户名字符串给入栈然后调用下一个call,莫非这个call对用户名做了什么处理?老样子我们先不进去看它的处理过程,我们观察返回值,也就是EAX是什么:
可是这不是一个地址并不知道这是个啥,那没事了,我们继续往下走吧,先标注一下这个函数是对用户名做了什么处理就行。
接下来把EDX低4个字节和[EBP-0x20]
进行比较,我们可以看到数据窗口中显示这个局部变量的值此时是12
也就是序列号下表为4的那个元素,小记一下:CMP k[4],DL
BH是序列号下标为5的那个元素,也就是CMP k[5],CL
同样的套路,下面几个比较都是把密码不同地方和那个函数返回值的不同字节进行比较,相同则往下继续比,不相同则跳走:
总结一下对应关系:
也就是说k4~k7拼出了计算用户名的运算结果
继续往下看,
还记得我们前面写的验证代码吗,函数的返回值是>0的,这里进一步缩小范围了,应该是大于或等于9,我们去把它改了:
然后分析到这里可以发现其实用户名和序列号之间只有一个对应关系的,k4、k5、k6、k7是用户名的ASCII数组经过计算得出来的一串数据,所以在编写注册机的时候要想办法把这个对应关系给用上。
所以我们也要还原一下计算用户名的那个函数,然后分别给序列号的后四个元素赋值就行。(当然还要满足前面的条件)
我们来到计算用户名这个函数
然后去IDA找一下,然后F5,然后COPY一下不就好了吗?
我觉得还行~
不过还有个问题哈,就是这里频繁使用到了一个数组dword_2E64148
,我们过去把它也拷贝一份:
当然最关键的算法可不能落下啊:
要想使用这个函数,光有名字可不行啊,还得知道有什么参数,这里我们去OD再看看PUSH了啥
找一下这个ESI是什么
找到了,所以这个参数是0,然后后面的EAX的值是1,最后一个参数是这一长串的商
在前面验证的过程中,我们是怎么得到这个商的呢,通过生成随机数来穷举符合公式的小于等于0x3EB的值,如图
有个小细节哈,在IDA的翻译成C的过程中,将这个函数的第四个参数变成了char型,但是它是一个商,应该是一个数字才对
而它这里是把结果AX(2字节)扩展成的EAX(4字节)
这里我们不妨把第四个参数大胆改成2字节的变量
然后就可以去编码调用一下这个函数了
然后因为6号元素在这里已经有了确定的值,下面就不需要再去穷举算它的值了。
还有下面的5号元素、7号元素都不用再枚举了,直接由上面计算出来就行
我们跟踪一下EAX的值的变化,到这里EAX都还是0x2D,是我们想要的值
可是经过了call之后发现EAX突变了,变成了0x113,说明这个call里面对EAX的值进行了修改或验证
所以我们这里要跟进这个函数看一下里面做了什么操作:
我们继续往下步过看看能不能通过验证:
很可惜还是不能,继续往下分析吧:
所以这个地方有两个思路:
然后下面一条语句是测试EAX的值
如果无符号就跳走
所以我们可以修改一下这个函数的返回值,这里我们先手动修改一下EAX位1试试,看能不能跳走
小记一下要想过掉网络验证有两个关键:
还没完,得满足堆栈平衡,我们往下翻,看看它的ret是多少:
拖了半天才找到,网络验证真复杂~
这样就应该没毛病了,保存修改到文件:
我这里重新算一组新的试试看:
然后就能愉快的玩耍了。
然后就是将用户名转化成ascii字符串
通过一些列计算将用户名计算出一串字符,分别对应byteCode[4~7]这四个字节
调用了402E50这个函数,有四个参数,分别是用户名字符串、1、0、4083C8函数的返回值
它的返回值是4字节的一串字符UNEncode
这一串字符与序列号数组有着对应关系:
byteCode[4] = UNEncode & 0xFF
byteCode[5] = UNEncode >> 8 & 0xFF
byteCode[6] = UNEncode >> 16 & 0xFF
byteCode[7] = UNEncode >> 24 & 0xFF
判断函数00407644的返回值是否大于等于9
[ ] 当然这个软件的分析细节我还远远没有分析完,这是我一路分析下来的思路,可能有点乱,但是已经达到了我的目的,完成了破解,接下来就是注册机的编写。欢迎各路大神带着新鲜的思路前来指点。稍后奉上注册机和源码。
还是从传入的四个参数说起:[ARG.1]->用户名
、[ARG.2]->1
、[ARG.3]->0
、[ARG.4]->0x3E8
这个循环取出用户名每一个字节,直到0截断停止,猜测可能会有个操作时拿到长度,不然这个循环就没有意义
果然在后面就有个检测
下面紧跟又一个循环
这里有一个比较和跳转,我们跟下去看一下这个跳转是干啥的
这里有一些列的操作,我们留意一下这个跳转:
注意,这里有个小细节
然后我们往下走,分析一轮看看对这个元素做了什么操作
注意:第一轮的数据保存在EAX和ECX中
我们继续跟着跳,但是到这一句的时候我就产生了不好的预感:这里我们上一轮计算的数据已经被新的元素ASCII码覆盖掉了,也就是说之前算的东西还真没有用?
但是突然又想到部队的地方,除了这里是计算的数据之外,还有一个地方我们刚刚忽略掉了,就是这里:
分析到这里,我们就能编码了,把这套逻辑搬运到我们的注册机里,就能完成用户名和对应序列号下标的值的运算了:
这里有个大坑!!!
这里的寄存器乘以4的操作是因为再汇编代码里,所有的数据都是以字节为单位的,所以BYTE长度乘以4就是4字节的DWORD长度,我们在用高级语言编码的时候就不用乘以这个4了
这里附上代码~
可见无论[ARG.2]
是1是0,都会执行跳转下来后的这些语句,并且执行循环,最后ret出去
至此,它的分析算是完成了~
今天在找字符串的时候留了个心眼,突然感觉没必要在验证失败这里找东西,真正的玄机不应该是在验证成功之前吗?于是我就顺着验证失败的字符串往上下看了一下,果不其然找到了一个订阅成功的字符串提示:
然后我们双击跟进看一下,老样子还是看一下交叉引用,也是只有一个地方引用了,离真相不远了!
来到了代码段,我们点击F5将其转化成源码(伪码)来看看这里究竟是怎么验证的。
还行,看来懂点英文还是好的
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2021-12-5 17:02
被JokerMss编辑
,原因: 内容更新