申请会员ID:psycongroo【申请通过】
1、申 请 I D:psycongroo2、个人邮箱:583754440@qq.com
3、原创技术文章:CrackMe10的逆向分析
ss: 序言:
由于看到论坛上一些破解分析都是把个人已分析查明的功能函数作用写在注释上,让观众理解。虽说破解分析的目的是为了分析写程序人的目的,但我个人认为,
在破解分析这门学问里,对程序的追踪和分析过程也极为重要,同时作为一个刚入门破解的萌新来说也是最想弄清楚的部分。
在入门破解并成功分析了前九个CrackMe之后,我决定在即将攻破下一个Crack之前,一边分析一边记录,总结自己萌新的做法,因此有了这篇文章。说实话笔者在写到这里的时候还没开始分析和破解,还是有点心悬。
ss: 破解准备工作:
1.程序运行并观察,知己知彼百战百胜,不把程序运起来还何谈破解呢?
(1)这就是程序的全部了,我们可以从弹窗和文字下手。
2.查壳并脱壳,只有让其裸露才好办事哦,我们使用exeinfope来查壳。
https://s1.ax1x.com/2020/07/01/N7T3HH.png
(2)无壳开搞!接下去是真正的破解环境
ss: 破:
1.OD载入,并让程序正常运行。
2.寻找关键跳,即寻找判断注册码并跳转的地方;经过正常程序并输入错误验证码的过程,我们可以尝试[弹窗断点]和[字符串搜索]
2.1字符串搜索:在OD中直接使用快捷键cltr+D,如果此时你载入的程序已经运行起来并自己尝试输入错误的验证码后,你会发现如下图
https://s1.ax1x.com/2020/07/01/N77qoj.png
(3)字符串多的一逼,并且由于程序的字符串是德文我们很难通过搜索字符串得出,并且经过仔细浏览后也并没有找到错误,成功等德文提示
此时将程序在OD重新载入,并且注意,不要让他运行起来,而是让他自己中断在程序的入口(这是OD默认给的中断):
https://s1.ax1x.com/2020/07/01/N7H1kd.png
(4)上图是程序载入OD自动暂停的截图
没错就是现在按下你那高贵的ctrl+D 你会惊喜的发现搜索字符串里只有少数的几个:
https://s1.ax1x.com/2020/07/01/N7HWBF.png
(5)惊不惊喜意不意外!
这是为什么呢?因为一旦你程序运行起来并进行注册码的输入,程序在这运行过程就会产生很多多余的数据,这些数据就成为你的干扰项了。如果错误/成功注册的提示字符串是早已写在内存的常量,便可以在程序入口处搜索到。
这时候就有人问了,找到有什么,我又不懂德文。问的妙,妙蛙种子。其实文字只是一种人类的表达方式从古代的象形文字到现在,文字无论如何改变,他都具有外型辨别的特征,所以我们可以认而不识。
还记得在运行时瞎几把输入验证码后弹出的错误消息提示吗?(自行上移到图一)没错,就是字符搜索图中的最后一句,我们点进去:
https://s1.ax1x.com/2020/07/01/N7qzlt.png
(6)估计是错误提示弹窗初始化的地方
我们要找到注册码的判断句(关键跳)而我们现在却来到了错误弹框初始化的地方,我们都知道代码是从上往下运行的,所以我们往上找,却发现有个jmp,我们都知道jmp是无条件跳转,因为代码是从上往下运行的,而这个jmp又是跳到别的地方的,
所以可以得出结论,错误弹框这段代码应该是通过某种手段跳过来的,我们先选中这段错误弹框代码的开头,即jmp下面一句:
https://s1.ax1x.com/2020/07/01/N7OFu6.png
(7)
可以看到这句代码被红线牵连,我们顺着月老的红线来到了跳转的地方:
https://s1.ax1x.com/2020/07/01/N7OKgI.png
(8)
是个je,还能看见在je下面有像Right的字样,建立假说:这个je是关键跳,把je改成jne或直接右键二进制填充nope掉,然后运行,随意填入注册码:
https://s1.ax1x.com/2020/07/01/N7vjMR.png
(9)我看懂了,反正是成功!恭喜你
2.2 弹窗断点,假设我们找不到相关字符串我们可以利用其弹窗是利用的window api的特性进行断点:
https://s1.ax1x.com/2020/07/01/N7xgw6.png
(10)可以用菜单栏的BreakPoint选项,心一狠你甚至当然能全勾上,也可以用命令bp断点,这里明显错误的时候会发出哔的声音带弹框,所以只选了MessageBeep
随意输入验证码并点OK你会发现程序成功中断了:
https://s1.ax1x.com/2020/07/01/N7zKn1.png
(11)
这时候我们留意下堆栈区,即右下角区域,看到有类似错误提示的信息,我们就按着这个找:一直向下滚动鼠标滑轮,即堆栈的回溯,直到错误信息字符串不再出现为止
https://s1.ax1x.com/2020/07/01/NHpGyd.png
(12)
这最后到这个叫gdi32这里,我们往上找,找到本程序名Andrennali字样的地方
https://s1.ax1x.com/2020/07/01/NH94Et.png
(13)
选中并按下回车,你可以看到跳到了前面那个错误弹窗初始化的地方,接着按照上面的走就行了。这时候你就问了,这是个什么原理,因为函数call的调用是要入栈的,即截图中文字样返回到xxxx来自xxx的地方,每次调用一个这样的
call就会纪录在栈,又因为程序调用弹框api也要用到系统相关的call,而这个弹框又要把错误信息显示出来,所以我们只要按着栈内有错误信息的地方并往下找,直到没有这个信息,并找到本程序的空间,就可能找到本程序调用弹框的地方(个人揣测)
ss 解
破只是为了给解做铺垫,当我们爆破了程序,接下来就是要从爆破的地方出发,寻找程序优雅的解法,即程序的验证机制:
https://s1.ax1x.com/2020/07/01/NHnr5Q.png
(14)
可以看见关键点处的je跳转是根据上面test ax,ax来进行的,当ax为0的时候,je跳转,也就是跳到错误弹出框处。为了不让je成功跳转,我就们要寻找ax的值的来源,想办法让其"自然而然"的变成不为0的值,使其不跳转继续运行下去。"
继续看14图,为了追溯ax的来源,我们继续往上看一句,这一句是call,我们按F2把断点设在这里,并运行,此时断点断在这个call上,我们留意EAX寄存器的值,让断点下一步,来到了test句,此时发现eax的值已经变了,所以可以石锤,这个call决定了这个eax的值,而eax的值决定是否跳转。
我们重新输入验证码按下OK,继续让断点断在这个call,随后我们按F7进入这个call的内部:
https://s1.ax1x.com/2020/07/01/NHKZOx.png
(15)上图为call内部
这时候我们可以看到在这个call的retn上面的一句是一个关于eax的mov语句,是赋值给eax的。那么这个eax的值就是由 ds:决定的,从这个地址可以看出,地址又是由eax决定的。
此时我们建立假说:1.地址中用到的eax的值是固定的,而变动的是地址处所记录的值2.地址中用到的eax的值是变动的。
我们先梳理下至今为止正在做的事情,要在图14中找到决定eax值的地方,使得test ax, ax之后的je不跳转,即eax不为0,而随着我们对eax值的追踪,我们发现这个eax和由另一个别的不同的eax决定的地址的值有关ds:,
这里为了方便说明我们把决定跳转的eax成为j-eax,而决定地址值的eax成为adr-eax。而现在我们就是要验证关于adr-eax的假说。
假说1验证:地址中用到的eax的值是固定的,而变动的是地址处所记录的值。为此我们要找到地址记录的值,并找出让他变化的地方:我们把断点打在赋值语句mov上,把程序运行到这个断点,然后在代码区下面的小窗口处,右键地址找到相应的数据区:
https://s1.ax1x.com/2020/07/01/NHQoex.png
(16)
然后下面数据区(充满数字的区域)就会跳到地址所记录的位置,我们右键第一个数据,打上内存写入断点:
https://s1.ax1x.com/2020/07/01/NHlGc9.png
(17)
再次输入验证码并点击OK,如果程序向这个地址处写入数据,程序便会中断在写入数据的地方,可是程序并没有中断;难道这个数据一开始就是0,没有任何写入的迹象?
这次我们依旧在那个地址,打入内存访问断点,若程序访问改地址内容便会中断。运行后发现程序还是没中断,难道是有一个大的跳转,把读写的操作都跳走了?这里我们很难验证,毕竟苍茫大海,到这里,我们的假说一的验证似乎也就这样草草结束;
再此总结一下,我们为了验证假说一而又产生了两个假说:1.这个数据本来就为0,不会有任何改变。 2.有一个大的跳转,把读写操作全跳过。很明显的,这些新的假说我们无法验证,这时候暂时放弃,去验证另一个假说也未尝不可。
假说2验证:地址中用到的eax的值是变动的。按照这个逻辑,我们找到这个赋值语句之前的代码——同样也是个call,如图15。程序运行后断在这个call上,然后单步,发现adr-eax的值变了,因此我们可以肯定,这个call改变了adr-eax,我们就不得不进去一探究竟了。
我们用这样一种方法找到call里最终的adr-eax值:在call的结束语句(一般为retn,如果不清楚可以一句一句快速试,这是一个试错的过程)执行之前,我们要找到近的影响目标值的语句。就像我们之前找影响j-eax值的地方一样,从下往上,只不过因为我们的程序却是从上往下的
反向执行的,因此我们要一句一句单步执行并观察目标eax的变化。由于这个过程过于冗杂和重复,我这里只锁定最后的地方:
https://s1.ax1x.com/2020/07/01/NH8Qtf.png
(18)
当我们运行完这两句后adj-eax就变成了FFFFFFFF,现在我们要改变这个adj-eax的值,应该怎么做呢?别急,我们要养成看到了有跳转指令就应该瞄一眼的习惯:选中上面的je语句,发现这个je是可以跳过sbb两句的!
到目前为止现在我们可以建立两种关于改变adj-eax的假说:1.因为sbb是一种运算符,我们只要想办法让其运算结果改变就可以。 2.符合je跳转,直接跳过sbb运算符,让adj-eax保持原来的值。
假说1验证:因为sbb是一种运算符,我们只要想办法让其运算结果改变就可以。要验证这个假说,首先我们要知道sbb怎样运算的,sbb是带借位减法,即sbb a,b 就相当于 a = a - b - cf,cf是个标志。而在这个假说中,a和b都是adj-eax本身,所以相减肯定为0,关键在于cf标志。
我们继续往上看,当我们再次将代码单步,发现repe cmps指令执行后cf就变成1了,这就是导致我们adj-eax值变化的原因。为了改变cf的值(虽然我也不知道是怎样的规则),我们必须弄清ds:和ds:。怎样,是不是非常熟悉,这个和我们之前的假说很类似哦,到底是地址记录的值发生改变呢?还是说决定地址的esi和edi发生变化了呢?其实这里显而易见,上面就有mov关于esi和edi的赋值语句,而esi和edi的值似乎又由esp决定。但esp是什么,esp是记录栈顶用的,换句话说除非这个call结束,不然esp是不会变的,在这里完全可以看成是固定值。
所以,显而易见,这里明显是由edi和esi地址记录的值所决定,cf标志位了。
那现在,我们就要对和的值进行考察,那到底是要看哪个值呢?我们可以一个个来,首先我们看edi的,选中mov edi语句,老操作了,下面小窗口右键,选择数据窗口中追随数据:
https://s1.ax1x.com/2020/07/02/NHUXUs.png
(18)
无论是小窗口还是下面的数据区,我们都能看到这个值是一个非常神奇的值:kXy^rO|*yXo*m\kMuOn*+,有没有感觉到一丝诡异亦或者有什么想法呢?别急,先把你的想法收收,过早的结论会导致流程的混乱和思路的断裂,我们继续进行‘老操作’,进行内存写入的断点。程序继续运行后,发现根本没有中断的地方,而打入内存写入断点会断到之前的repe cmp处。那我们还是老操作了,如同之前一样,我们先暂时放下。
这次我们来看,发现的值也是一个非常神奇的值:;<=>?(寄存器右边的注释),看到这里你可能会吼什么jb玩意,有可能会惊呼,怎么这值和楼主的不一样呢?没错,这个事实上是一个变动的值,意味着如果在这里打上内存写入断点程序可能会中断。这不正是我们想要的吗?我们在下面小窗口右键,选择数据窗口中追随数据,注意是追随数据,为什么不是追随地址呢?这里就涉及到了对这两个选项的理解。首先我们一定要清楚,无论追随数据还是地址也好,都是把这里的【数据】或【地址】当作内存地址在数据窗口追随的。为了不搞混概念,笔者给小窗口中右键菜单的选项——追随数据/地址中的数据,地址用粗括号括起来,就像上面那样。
我们知道的意思是指esi地址记录的值。也就是说esi里存着的是一个数值的地址,而就是表示那个数值。追随【地址】,是指把中的esi当做内存地址在数据窗口表示,而追随【数据】,是指把所指示的那个值,当做内存地址在数据窗口表示。现在,我们是为了查清 ;<=>?的来历,我们就要要在记录 ;<=>?的地址上打上内存断点,而;<=>?的地址,正是所指示的那个值,因此我们要把esi地址中记录的值当做地址,于是就有了追随【数据】这个操作。怎样晕了没有?其实只要用得多了就知道了。
回到我们的主题上,当我们跟随数据后,就可以看到;<=>?的ASCII码和相应的16进制码了。
https://s1.ax1x.com/2020/07/02/Nqbuyq.png
(19)
让我们和之前一样,打上内存写入断点,希望找到改变他的代码。当我们再次点击ok代码停住了!这是一件令人多么喜悦的事情,但是当我们尝试运行几次的时候,怪事发生了,之前打的内存断点没用了?这是为什么?当我们仔细观察,我们会发现:
https://s1.ax1x.com/2020/07/02/NqLFPg.png
(20) 第一次运行
https://s1.ax1x.com/2020/07/02/NqLA2j.png
(21)第二次运行
记录着;<=>?的地址竟然会发生变化!而我们打内存断点的时候正是对着内存地址打的,如果这个地址变了,我们的断点没打在最新的地址上,就不会有什么变化了!这对我们追踪目标值来说是件非常麻烦的事情。那我们怎么办呢?既然记录的地址发生了变化,那我们就对着这个变化的地址打呗。我们再次小窗口右键,选择追随地址:
https://s1.ax1x.com/2020/07/02/NqXDKA.png
(22)红框所示就是那个变化的地址
我们右键,内存写入断点,再次点击OK让程序跑起来,程序断在了这里:
https://s1.ax1x.com/2020/07/02/NqjKdP.png
(23) 当图中所选中的push运行完后,之前打断点的内存处的值会发生变化
我们可以看到,这里的push又涉及到一个内存地址,同样的,我们右键追随地址,然后在相应的地方打上内存断点。怎样,有没有一股不祥的预感,一股套娃的预感,但是反而是好事,要是有多重套娃,我们接着同样的处理就好。同样的,我们让运行继续运行,经过又一次的套娃,最后我们会到这个地方:
https://s1.ax1x.com/2020/07/02/NLS2cD.png
(24)红框可见目标值出现了
ps:可能有些时候内存断点出不来,我也不知道什么原因,试了几次就好了。
在这句上断点,多按几次F9让程序运行,发现目标值逐个出现!毫无疑问这个就是给内存赋值的地方,而我们又看到这里是个mov语句,值是从ecx寄存器来的,而这个ecx又是从另一个内存地址赋值过来的,那我们就如法炮制,对这个内存地址下内存断点,最后断在了这个地方:
https://s1.ax1x.com/2020/07/02/NLFoNj.png
(25)
可以非常惊喜的开到,断的地方是一个eax对内存地址的赋值!而在小窗口也出现了目标字符的第一个字符。所以现在当务之急就是要搞清楚eax的来源。我们依旧一步一步,从断点往上找,放眼望去都没有和eax相关的赋值语句,所以我们可以猜测可能在最近的call里。当我们尝试断点在这个call上,并单步执行后,eax确实发生了变化。我们多试几次可以清楚的看到eax寄存器的变化,每次单步过这个call的时候,就会把目标字符逐个连接上去:
https://s1.ax1x.com/2020/07/02/NLAkJs.png
(26)左红框目标符号被当作参数传到call里,右边红框,略过call后的值
其实现在已经很清楚了,这个call其实就是把拼接字符的,这里我们不用去到call内部看个究竟,因为这是显而易见的,我们任务是找到目标符号的来源,而目标符号却被作为参数传到了call里,所以接下来的任务就是跟踪这个参数的来历。而如何寻找这个参数来历呢,他也是用内存赋值,所以我们还是打上符号写入断点,这次会跳很多的地方,但是我们只看含有符号的断点:
https://s1.ax1x.com/2020/07/02/NLAvtJ.png
(27)目标符号之一出现
这下我们又要去找eax的来源了,真是一波三折。破解就是这样,需要耐心和毅力。而后又发现,其实这个eax的值是从头上那句jnz的上面一句mov eax来的,那我们还是内存断点吧(苦逼)
https://s1.ax1x.com/2020/07/02/NLZhkD.png
(28)
又是用eax赋值,老办法了,断点单步,发现是头上的call改变了eax的值。当然你可以进去call里继续找,但是我们看到了右边注释:rtcBstrFromAnsi。从名字看可以猜测和ASCII码(Ansi是ASCII的扩展)有关,我们结合输入参数和输出后改变的EAX值来观察:
https://s1.ax1x.com/2020/07/02/NLKlxH.png
(29) ax=003F在到达断点call之前
因为输入参数是edx,edx又曾被ax赋值,因此我们也要观察ax。
https://s1.ax1x.com/2020/07/02/NLKRiT.png
(30)这是运行到call之后eax的值,为问号(?)
https://s1.ax1x.com/2020/07/02/NLKHdx.png
(31)这是ASCII表的一部分
可以看到ASCII码中,问号(?)也是3F,这就印证了我们的猜想,这个call只是把输入参数中的ASCII码转化成我们看到的符号罢了。一定要记住,我们是要追查符号的来源,如果你拥有一定函数的目视和猜测能力,就可以快速的跳过一些繁琐的流程,从而更快的到达要分析的地方。说白了其实就是经验总结。
回到话题上,反而言之,我们要找参数ax的来源。我们使用同样的分析手法,也是设置断点单步运行下去并观察eax的值,我这次把断点设置在了较远的地方:
https://s1.ax1x.com/2020/07/02/NLQiu9.png
(32)黄色框所示断点地方
为什么要设置在这个地方?因为这里是影响eax最近的一块地方,而这块地方又调用了两个系统函数(右边红色注释),我们在此单步运行并观察eax的值:
https://s1.ax1x.com/2020/07/02/NL13ff.png
(33)
可以看到出现了3!这意味着什么?数字3是笔者输入的注册码(12345)!而他的出现是个好事情!要验证注册码肯定要拿我们输入的假的验证码去验证的,所以说我们离答案很近了!那让我们继续单步:
https://s1.ax1x.com/2020/07/02/NLGkYq.png
(34)注意右边EAX值
当运行完下一个call时,发现右边eax的值变成了33,而33正是字符"3"的十六进制。因此就看eax这个寄存器而言,从“3”变成了十六进制的33,这就是这个call的作用。下面可以看到一个关于add eax, 0xA的语句,也就是eax=33+A=3D。运算完后就被当做参数送到之前分析过的call里去了。
现在明白了吗?如果不明白可以在这个断点多看几次,多运行几次,你就会发现整个流程是这样的: 逐个取你输入的序列号字符,将其变成16进制,再加上A,最后存入内存当中。分析到这里其实我们已经知道验证码的算法了。什么你还不知道吗?我们是怎么来到这里的吗?我们为了让程序自然而然的跳转过注册码的判断,而找到了一个res cmps对比代码的地方,而这句代码又是和,的值有关,其中记录的是定值kXy^rO|*yXo*m\kMuOn*+,而我们选择了追踪的字符来源——通过不断的内存断点,最后来到了这个【逐个取你输入的序列号字符,将其变成16进制,再加上A,最后存入内存当中】的地方。因此很明显了,我们通过代码转换后的目标字符串一定要和定值kXy^rO|*yXo*m\kMuOn*+相等。根据算法,是输入验证码的16进制加上A。反过来,加如我们要得到字符串kXy^rO|*yXo*m\kMuOn*+中第一个字符小写k转换前的样子:k的16进制-A,依次类推。如果有编程基础的观众可以按着这个写,没有编程基础的,可以查表,并用16进制计算器一个一个算。
友情提示:如果你用的类似Java和Kotlin的编程语言,里面的char值转换成Int后就是Ascii表中对应的10进制
最后运算出来的验证码为:aNoThEr oNe cRaCkEd !
https://s1.ax1x.com/2020/07/03/NL6MH1.png
有点秀...
ss: 总结
算法其实很简单,这里用的时间最多的是真假验证码的对比的地方,以及我们输入假码的追踪。因为我们破解是逆向,逆向就是要从程序相反的方向开始。这就是从结果到过程再到结果的过程(绕口不绕口?)可以在内存下断点的方式找到内存写入的地方,从而步步为营最后找到写内存的地方。在这寻找的过程中,如果熟悉一些系统的函数就可以提高效率,更好的抓住算法的位置。
总而言之,破解需要信心,对自己有信心,需要耐心,对逆向过程的耐心,需要决心,对自己错误推到从来的觉行。
ps:有时候内存断点会失灵,不知道为什么,希望有人来解答一下? 这个文章是原创的吗?发在其他地方了? Hmily 发表于 2020-7-6 15:26
这个文章是原创的吗?发在其他地方了?
原创的,没发其他地方。
帖子发出来了有邮箱回复么?我怕漏了回复。
我是在贵论坛下的教材刚学破解1个月左右,第一次写的这篇文章,看能不能申请个账号啥的。 游客 113.77.138.x 发表于 2020-7-7 15:17
原创的,没发其他地方。
帖子发出来了有邮箱回复么?我怕漏了回复。
我是在贵论坛下的教材刚学破解1个 ...
厉害啊老哥 游客 113.77.138.x 发表于 2020-7-7 15:17
原创的,没发其他地方。
帖子发出来了有邮箱回复么?我怕漏了回复。
我是在贵论坛下的教材刚学破解1个 ...
看你图片地址就知道了 不信你去看看{:1_907:} chboy 发表于 2020-7-12 20:16
看你图片地址就知道了 不信你去看看
用的图床。论坛里的图片上传503,传不上去。 I D:psycongroo
邮箱:583754440@qq.com
申请通过,欢迎光临吾爱破解论坛,期待吾爱破解有你更加精彩,ID和密码自己通过邮件密码找回功能修改,请即时登陆并修改密码!
登陆后请在一周内在此帖报道,否则将删除ID信息。
ps:有些简单,希望以后可以有更多分享,登陆后把文章整理一下发布到脱壳破解区吧。 Hmily 发表于 2020-7-14 11:28
I D:psycongroo
邮箱:
:$qqq 前来报告。我想问一下我把看雪那边的帖子发这里,应该是标原创还是转载呢? psycongroo 发表于 2020-7-14 15:55
前来报告。我想问一下我把看雪那边的帖子发这里,应该是标原创还是转载呢?
都是你自己写的当然可以写原创了。 Hmily 发表于 2020-7-14 15:58
都是你自己写的当然可以写原创了。
ok好的!
页:
[1]
2