1、申 请 I D :LzSkyline
2、个人邮箱:lzskyline@foxmail.com
3、原创技术文章:
文章标题:针对Acid burn的一次Crack
文章方向:教学向
发文缘由:一直很喜欢吾爱,不过之前大学课也挺紧的,没有太多时间好好地去写一篇技术文章,导致之前的申请失败了,最近放假了来吾爱把爱盘里的教程看了一个遍,发现有好多大神都是之前先自己做一遍,结果后面讲解的时候容易先入为主的把自己已经知道的东西拿出来用,所以可能代码的分析上有点不足,导致小白很难以接受,所以我想写一篇没有做过任何准备,一边把自己思路写下来一边破解的文章.老实说这样挺容易坑自己的,毕竟我技术也不是特别的好,要是找了一个程序废了几个小时,分析到后面发现自己解决不了的话,那这篇文章就白打了,不过还好运气不错,这是一个算法并不是很复杂的程序.最后希望能通过审核,同时也希望这篇文章能帮到更多想学习逆向的人.
采用方式:爆破+算法注册机+堆栈注册机
破解步骤:
用OD载入Acid burn,并F9运行.
转到Serial/Name界面,第一行输入111222,第二行输入333444
点击Check it Baby
弹出对话框提示:Sorry , The serial is incorect !
返回到OD,暂停程序,查看此时程序的堆栈(Alt+K)
找到最下面一行指令,点击右键-在堆栈中跟随地址,然后观察右下角堆栈区域,我们要关心的只有如下数据
[Asm] 纯文本查看 复制代码 [/font][/color][/color]
[color=#000][color=#444444][font=Tahoma, Helvetica, SimSun, sans-serif] 0019F724 ]0019F75C[/font][/color][/color]
[/b][/font] 0019F728 |0042FB37 返回到 Acid_bur.0042FB37 来自 Acid_bur.0042A170 ;调用对话框后的返回地址
0019F72C |00000000
0019F730 |0019F84C 指针到下一个 SEH 记录
0019F734 |0042FB67 SE 句柄
0019F738 |0019F75C
0019F73C |021D1E8C
0019F740 |021D1E8C
0019F744 |021E251C ASCII "4018" ;对111222进行加密算法后得到的数据
0019F748 |021E2508 ASCII "111222" ;原始数据
0019F74C |021E254C ASCII "333444" ;原始数据
0019F750 |021E2530 ASCII "CW-4018-CRACKED" ;111222对应的密码数据
嗯,我对上面的信息进行了注释,也即是分号后的内容,大家可能会想,你怎么知道4018是对111222加密后的数据?
这个其实是靠猜的,并不一定正确,我是通过观察这四行ASCII文本,发现第二、三行数据是我们输入的原始数据,而第四行明显像是一个注册码一样的东西(Serial Key),且第四行是包含第一行的,而且还是"非完全无规律字符串"(因为它包含了cracked这个单词),所以我就可以猜测:第四行数据是在第一行数据的基础上进行了某种拼接得到的,而第一行数据是否真的和用户输入的数据有关呢,我们还需要换一组数据进行测试.
于是我按下F9恢复程序运行,在注册界面重新输入了一组数据:222333 333444.
然后再次尝试刚才的步骤,观察栈,此时数据变成了
[Asm] 纯文本查看 复制代码
0019F728 |0042FB37 返回到 Acid_bur.0042FB37 来自 Acid_bur.0042A170;调用对话框后的返回地址
0019F72C |00000000
0019F730 |0019F84C 指针到下一个 SEH 记录
0019F734 |0042FB67 SE 句柄
0019F738 |0019F75C
0019F73C |021D1E8C
0019F740 |021D1E8C
0019F744 |021E251C ASCII "4100" ;这里变化了
0019F748 |021E2508 ASCII "222333" ;原始数据
0019F74C |021E254C ASCII "333444" ;原始数据
0019F750 |021E2530 ASCII "CW-4100-CRACKED" ;同第一行一样,发生变化了
好,到这里我们就可以得出一个靠谱的结论了:四行ASCII中,第一行数据会根据第二行数据发生变化
第二行是用户名(加密算法中的主Key)
第三行是注册码,不会影响加密算法,只用来判断注册码是否与算出来的码相同.
第四行是最终的注册码,且格式为:CW-第一行数据-CRACKED
好了以上,我们就可以通过OD观察堆栈区域得到真正序列号了,我们F9运行OD,尝试将刚才在注册界面输入的333444更改为CW-4100-CRACKED,点击Check按钮,提示Good job ,注册成功,证明我们刚才的所有猜测没有关键性的错误,接下来我们就可以写注册机了,这里可以分为两种,第一种是注入到主程序中,这种可能会被报毒,而且代码处理不好也会导致程序的栈溢出.
说下思路:我们在注册界面输入 111222 333444,按下Check按钮,会提示Sorry的错误,我们可以在CALL Sorry对话框的下面修改为JMP到自己的巢穴代码,弹个框取出当前ESP+20或者EBP-C都可以,然后执行原来的代码最后再JMP回去,这是一种方法,需要注意的就是你的巢穴代码必须有执行权限,这个需要有点PE基础的人做,不过明显还有更简单的方法,直接修改Sorry字符串为注册码可以省去我们自己写MessageBox的步骤,但并不是直接修改就可以的,有一些细节需要注意,我们先在CALL函数下个断,重新点击Check,中断后F7步入到函数内部:
[Asm] 纯文本查看 复制代码
0042A170 /$ 55 PUSH EBP ;进入到函数里,压栈帧
0042A171 |. 8BEC MOV EBP, ESP ;也就是函数栈顶指针,之后取注册码的时候会用到,下面这段代码就没啥用了,直接看最下面的对话框函数的调用
0042A173 |. 83C4 F4 ADD ESP, -0C
0042A176 |. 53 PUSH EBX
0042A177 |. 56 PUSH ESI
0042A178 |. 57 PUSH EDI
0042A179 |. 8BF9 MOV EDI, ECX
0042A17B |. 8BF2 MOV ESI, EDX
0042A17D |. 8BD8 MOV EBX, EAX
0042A17F |. E8 7CB4FDFF CALL <JMP.&user32.GetActiveWindow> ; [GetActiveWindow
0042A184 |. 8945 F8 MOV [LOCAL.2], EAX
0042A187 |. 33C0 XOR EAX, EAX
0042A189 |. E8 12A0FFFF CALL Acid_bur.004241A0
0042A18E |. 8945 F4 MOV [LOCAL.3], EAX
0042A191 |. 33C0 XOR EAX, EAX
0042A193 |. 55 PUSH EBP
0042A194 |. 68 D0A14200 PUSH Acid_bur.0042A1D0
0042A199 |. 64:FF30 PUSH DWORD PTR FS:[EAX]
0042A19C |. 64:8920 MOV DWORD PTR FS:[EAX], ESP
0042A19F |. 8B45 08 MOV EAX, [ARG.1]
0042A1A2 |. 50 PUSH EAX ; /Style
0042A1A3 |. 57 PUSH EDI ; |Title
0042A1A4 |. 56 PUSH ESI ; |Text
0042A1A5 |. 8B43 24 MOV EAX, DWORD PTR DS:[EBX+24] ; |
0042A1A8 |. 50 PUSH EAX ; |hOwner
0042A1A9 |. E8 FAB5FDFF CALL <JMP.&user32.MessageBoxA> ; \MessageBoxA ,没错就是这里了,接下来的下面的代码跟这部分没啥关系,我就不粘贴过来了
注意看这个CALL MessageBoxA,里面有四个PUSH,也就是对应着4个参数,大家写过Win32编程的应该很熟悉了,没见过的我在这里简单解释一下,详细的去查MSDN.
它的调用代码类似于下面这样
MessageBox(父窗口句柄,对话框内容,对话框标题,对话框样式)
我们再来看一下此时各寄存器的值:
EAX 00000000
ECX 0019F704 ASCII "小B"
EDX 0019F730
EBX 021D7FE8
ESP 0019F700
EBP 0019F724
ESI 0042FB80 ASCII 53,"orry , The serial is incorect !"
EDI 0042FB74 ASCII 54,"ry Again!"
EIP 0042A1A2 Acid_bur.0042A1A2
对应着上面传过来的参数,就变成了
MessageBox([EBX+24],"Sorry, The serial is incorect !" ,"Try Again!",0)
这里的EBX+24是多少我们没必要知道,因为完全可以用0代替,也就是父窗口是桌面
我们需要做的,就是把Text这段"Sorry, The serial is incorect !" 改为我们要的注册码,隔得上面有点远了 我在粘贴一下关键代码
[Asm] 纯文本查看 复制代码
0042A1A2 |. 50 PUSH EAX ; /Style
0042A1A3 |. 57 PUSH EDI ; |Title
0042A1A4 |. 56 PUSH ESI ; |Text
0042A1A5 |. 8B43 24 MOV EAX, DWORD PTR DS:[EBX+24] ; |
0042A1A8 |. 50 PUSH EAX ; |hOwner
0042A1A9 |. E8 FAB5FDFF CALL <JMP.&user32.MessageBoxA> ;
修改这里有一个需要注意的地方,就是你如果不做巢穴代码跳转,打算直接在这里修改的话,修改的字节数大小一定不能超过他原来的大小,否则会影响到下面的代码,我们来算一下:
PUSH EAX -> 对应的16进制机器码 50(仔细看上面汇编代码的左边,那个就是),也就是一字节
同理 PUSH EAX + PUSH EDI + PUSH ESI + PUSH EAX(在CALL上面),这是4个字节
MOV EAX, DWORD PTR DS:[EBX+24] 这是3个字节
我们要修改的部分只有PUSH部分,所以现在我们就需要在7字节的范围内对TEXT的数据进行替换,可以看到 PUSH 这种代码都是占用一个字节的,想改成更小的话就别想了,还是考虑在MOV这行上刀吧,刚才说过了,HWND句柄即使是0也是可以弹出的,我们在这里其实没必要去找主程序的句柄是多少,所以,删(NOP)掉这行代码
这样的话,EAX的值不会改变,等同于Style的值,还是0(忘记了的话,去找找上面寄存器的值自己看看)
好了,我们节省了3个字节的代码,算上要修改的PUSH ESI 一共有四个字节供我们使用了,完全可以满足我们的要求,接下来把原先的PUSH ESI修改为PUSH [EBP+某个十六进制数]
那么这个十六进制数到底是多少呢?观察右下角堆栈区域,先找到我们的EBP地址(同样在寄存器值里可以找到),然后在堆栈窗口双击EBP的值,可以看到原先的地址变成了一个偏移符号,接下来我们就可以去找注册码了,找到后看一下前面的偏移:"$+2C" .OK我们要的值有了
修改PUSH ESI为 PUSH [EBP+2C],F9运行程序,再看看.
CW-4100-CRACKED
搞定,把它输到注册码区域,可以看到标题已经由原来的Try Again变为了Congratulations~
除了这种读堆栈的方法,我们还可以自己去写一个注册机出来,这样就不算是修改程序了,而是分析程序算法.
通过利用刚才修改好的程序反复输入Name取得Serial,我发现,其实只有Name的前四位决定Serial的值,所以Name的长度必须大于等于4,否则是没有注册码的.
也就是说,这个程序的加密逻辑其实很简单:判断Name是否大于3,若是,则取第前四位字符进行加密,我们现在要做的就是找到他的加密算法.
还是回到刚才的CALL,我们看看他是怎么算出来的(自我认为这个过程非常头痛,求知欲强的筒子们GOGOGO).
[Asm] 纯文本查看 复制代码
0042F9A9 |. 55 PUSH EBP
0042F9AA |. 68 67FB4200 PUSH Acid_bur.0042FB67
0042F9AF |. 64:FF30 PUSH DWORD PTR FS:[EAX]
0042F9B2 |. 64:8920 MOV DWORD PTR FS:[EAX], ESP
0042F9B5 |. C705 50174300 >MOV DWORD PTR DS:[431750], 29 ; 将内存地址为431750的值改为29,这里要记住,下面会用到
0042F9BF |. 8D55 F0 LEA EDX, [LOCAL.4]
0042F9C2 |. 8B83 DC010000 MOV EAX, DWORD PTR DS:[EBX+1DC]
0042F9C8 |. E8 8BB0FEFF CALL Acid_bur.0041AA58 ; 获取用户输入的Name,我这里输入的是111222
0042F9CD |. 8B45 F0 MOV EAX, [LOCAL.4]
0042F9D0 |. E8 DB40FDFF CALL Acid_bur.00403AB0 ; 取出前四位,放入EAX中
0042F9D5 |. A3 6C174300 MOV DWORD PTR DS:[43176C], EAX ; 将前四位写到内存43176C
0042F9DA |. 8D55 F0 LEA EDX, [LOCAL.4]
0042F9DD |. 8B83 DC010000 MOV EAX, DWORD PTR DS:[EBX+1DC]
0042F9E3 |. E8 70B0FEFF CALL Acid_bur.0041AA58
0042F9E8 |. 8B45 F0 MOV EAX, [LOCAL.4] ; 将用户名111222放到EAX中
0042F9EB |. 0FB600 MOVZX EAX, BYTE PTR DS:[EAX] ; 按位取EAX值,也就是把用户输入的第1位取出来,也就是我输入的字符'1',移动到EAX中
0042F9EE |. 8BF0 MOV ESI, EAX ; 字符'1'的ASCII码是49,转成16进制就是31,把它移动到ESI中
0042F9F0 |. C1E6 03 SHL ESI, 3 ; 将16进制数31左移三位,变成十六进制的188
0042F9F3 |. 2BF0 SUB ESI, EAX ; 再用188-31,得到157
0042F9F5 |. 8D55 EC LEA EDX, [LOCAL.5]
0042F9F8 |. 8B83 DC010000 MOV EAX, DWORD PTR DS:[EBX+1DC]
0042F9FE |. E8 55B0FEFF CALL Acid_bur.0041AA58 ; 在这个CALL里,把用户名复制了一份,放到了一个新变量(栈)中
0042FA03 |. 8B45 EC MOV EAX, [LOCAL.5] ; 把新变量的值(还是111222),放到EAX中
0042FA06 |. 0FB640 01 MOVZX EAX, BYTE PTR DS:[EAX+1] ; 取用户名的第2位,还是字符'1'
0042FA0A |. C1E0 04 SHL EAX, 4 ; 这次是左移四位,变成了310
0042FA0D |. 03F0 ADD ESI, EAX ; 将两个加密过的用户名相加,也就是310+157=467,放到ESI中
0042FA0F |. 8935 54174300 MOV DWORD PTR DS:[431754], ESI ; 把467存到内存里
0042FA15 |. 8D55 F0 LEA EDX, [LOCAL.4]
0042FA18 |. 8B83 DC010000 MOV EAX, DWORD PTR DS:[EBX+1DC]
0042FA1E |. E8 35B0FEFF CALL Acid_bur.0041AA58 ; 再把用户名复制一份,替换到栈中
0042FA23 |. 8B45 F0 MOV EAX, [LOCAL.4] ; 111222放到EAX
0042FA26 |. 0FB640 03 MOVZX EAX, BYTE PTR DS:[EAX+3] ; 取用户名第四位,也就是字符'2',16进制的32
0042FA2A |. 6BF0 0B IMUL ESI, EAX, 0B ; 让EAX乘以0B,把结果放到ESI中,也就是十进制的 50x11 = 550,十六进制的226
0042FA2D |. 8D55 EC LEA EDX, [LOCAL.5]
0042FA30 |. 8B83 DC010000 MOV EAX, DWORD PTR DS:[EBX+1DC]
0042FA36 |. E8 1DB0FEFF CALL Acid_bur.0041AA58 ; 继续取用户名替换栈
0042FA3B |. 8B45 EC MOV EAX, [LOCAL.5] ; 移到EAX中
0042FA3E |. 0FB640 02 MOVZX EAX, BYTE PTR DS:[EAX+2] ; 这次是第三位,'1',31
0042FA42 |. 6BC0 0E IMUL EAX, EAX, 0E ; 乘16进制的0E,结果2AE
0042FA45 |. 03F0 ADD ESI, EAX ; 2AE+226=4D4
0042FA47 |. 8935 58174300 MOV DWORD PTR DS:[431758], ESI ; 结果存内存
0042FA4D |. A1 6C174300 MOV EAX, DWORD PTR DS:[43176C] ; 从内存中读前四位,一开始就存的,忘记了的看看上面
0042FA52 |. E8 D96EFDFF CALL Acid_bur.00406930 ; 求字符长度,用于判断是否大于4
0042FA57 |. 83F8 04 CMP EAX, 4 ; 跟4比较
0042FA5A |. 7D 1D JGE SHORT Acid_bur.0042FA79 ; 大于就跳
0042FA5C |. 6A 00 PUSH 0
0042FA5E |. B9 74FB4200 MOV ECX, Acid_bur.0042FB74 ; ASCII 54,"ry Again!"
0042FA63 |. BA 80FB4200 MOV EDX, Acid_bur.0042FB80 ; ASCII 53,"orry , The serial is incorect !"
0042FA68 |. A1 480A4300 MOV EAX, DWORD PTR DS:[430A48]
0042FA6D |. 8B00 MOV EAX, DWORD PTR DS:[EAX]
0042FA6F |. E8 FCA6FFFF CALL Acid_bur.0042A170
0042FA74 |. E9 BE000000 JMP Acid_bur.0042FB37
0042FA79 |> 8D55 F0 LEA EDX, [LOCAL.4]
0042FA7C |. 8B83 DC010000 MOV EAX, DWORD PTR DS:[EBX+1DC]
0042FA82 |. E8 D1AFFEFF CALL Acid_bur.0041AA58
0042FA87 |. 8B45 F0 MOV EAX, [LOCAL.4] ; 依然是取用户名放到EAX中
0042FA8A |. 0FB600 MOVZX EAX, BYTE PTR DS:[EAX] ; 这回还是第一位,31
0042FA8D |. F72D 50174300 IMUL DWORD PTR DS:[431750] ; EAX乘431750的值(最上面我有让注意过),也就是10进制的49x41=2009,十六进制的7D9
0042FA93 |. A3 50174300 MOV DWORD PTR DS:[431750], EAX ; 7D9存回内存
0042FA98 |. A1 50174300 MOV EAX, DWORD PTR DS:[431750] ; 感觉这行代码是多余的...
0042FA9D |. 0105 50174300 ADD DWORD PTR DS:[431750], EAX ; 十进制的2009x2=十六进制的FB2
0042FAA3 |. 8D45 FC LEA EAX, [LOCAL.1]
0042FAA6 |. BA ACFB4200 MOV EDX, Acid_bur.0042FBAC
0042FAAB |. E8 583CFDFF CALL Acid_bur.00403708
0042FAB0 |. 8D45 F8 LEA EAX, [LOCAL.2]
0042FAB3 |. BA B8FB4200 MOV EDX, Acid_bur.0042FBB8
0042FAB8 |. E8 4B3CFDFF CALL Acid_bur.00403708
0042FABD |. FF75 FC PUSH [LOCAL.1] ; 取固定字符串'CW'并压入栈中,为拼接字符串做准备
0042FAC0 |. 68 C8FB4200 PUSH Acid_bur.0042FBC8 ; UNICODE "-"
0042FAC5 |. 8D55 E8 LEA EDX, [LOCAL.6]
0042FAC8 |. A1 50174300 MOV EAX, DWORD PTR DS:[431750]
0042FACD |. E8 466CFDFF CALL Acid_bur.00406718 ; 16进制转为10进制,FB2,转为4018
0042FAD2 |. FF75 E8 PUSH [LOCAL.6] ; 将4018压入栈
0042FAD5 |. 68 C8FB4200 PUSH Acid_bur.0042FBC8 ; UNICODE "-"
0042FADA |. FF75 F8 PUSH [LOCAL.2] ; 固定字符串'CRACKED'压入栈
0042FADD |. 8D45 F4 LEA EAX, [LOCAL.3]
0042FAE0 |. BA 05000000 MOV EDX, 5
0042FAE5 |. E8 C23EFDFF CALL Acid_bur.004039AC ; 得到最终注册码,写入栈中,准备判断
0042FAEA |. 8D55 F0 LEA EDX, [LOCAL.4]
0042FAED |. 8B83 E0010000 MOV EAX, DWORD PTR DS:[EBX+1E0]
0042FAF3 |. E8 60AFFEFF CALL Acid_bur.0041AA58
0042FAF8 |. 8B55 F0 MOV EDX, [LOCAL.4] ; 取我们输入的注册码
0042FAFB |. 8B45 F4 MOV EAX, [LOCAL.3] ; 取算法算出的注册码
0042FAFE |. E8 F93EFDFF CALL Acid_bur.004039FC ; 在这个调用里完成了比较,控制Z标志位
0042FB03 |. 75 1A JNZ SHORT Acid_bur.0042FB1F ; 若Z不等于0就跳到注册失败的位置,需要爆破就改这里了,或者直接改上面的两个比较数也可以
0042FB05 |. 6A 00 PUSH 0 ; 以下就是压各种成功注册的数据了
0042FB07 |. B9 CCFB4200 MOV ECX, Acid_bur.0042FBCC
0042FB0C |. BA D8FB4200 MOV EDX, Acid_bur.0042FBD8
0042FB11 |. A1 480A4300 MOV EAX, DWORD PTR DS:[430A48]
0042FB16 |. 8B00 MOV EAX, DWORD PTR DS:[EAX]
0042FB18 |. E8 53A6FFFF CALL Acid_bur.0042A170 ; 调用对话框事件
0042FB1D |. EB 18 JMP SHORT Acid_bur.0042FB37
0042FB1F |> 6A 00 PUSH 0
0042FB21 |. B9 74FB4200 MOV ECX, Acid_bur.0042FB74 ; ASCII 54,"ry Again!"
0042FB26 |. BA 80FB4200 MOV EDX, Acid_bur.0042FB80 ; ASCII 53,"orry , The serial is incorect !"
0042FB2B |. A1 480A4300 MOV EAX, DWORD PTR DS:[430A48]
0042FB30 |. 8B00 MOV EAX, DWORD PTR DS:[EAX]
0042FB32 |. E8 39A6FFFF CALL Acid_bur.0042A170
0042FB37 |> 33C0 XOR EAX, EAX
0042FB39 |. 5A POP EDX
0042FB3A |. 59 POP ECX
0042FB3B |. 59 POP ECX
0042FB3C |. 64:8910 MOV DWORD PTR FS:[EAX], EDX
0042FB3F |. 68 6EFB4200 PUSH Acid_bur.0042FB6E
0042FB44 |> 8D45 E8 LEA EAX, [LOCAL.6]
0042FB47 |. E8 243BFDFF CALL Acid_bur.00403670
0042FB4C |. 8D45 EC LEA EAX, [LOCAL.5]
0042FB4F |. BA 02000000 MOV EDX, 2
0042FB54 |. E8 3B3BFDFF CALL Acid_bur.00403694
0042FB59 |. 8D45 F4 LEA EAX, [LOCAL.3]
0042FB5C |. BA 03000000 MOV EDX, 3
0042FB61 |. E8 2E3BFDFF CALL Acid_bur.00403694
0042FB66 \. C3 RETN
有了算法,写注册机就不难了,随便用个脚本轻轻松算出,这个算法不是很复杂,但却费了我一个小时去写这篇教程,同时这也是我的第一个分析算法的教程,难免不足之处,还请大神们轻拍.
|