最开始拿到这一题,就在OD里写脚本自动跑,最初思路是,这种复制到随机地址的类型,不管怎么随机,SN总要复制过去吧?每次复制到新地址,在新地址里搜索特征SN,在SN处下硬件读中断,根据EDI值就能知道目标地址了,是不是很完美?然后脚本开始跑了,很快就杯具了:先是发现硬件中断并不是很及时;然后又发现中断时SN有可能只复制了一部分,目标地址再下断的位置并不好确定;经过修修补补,脚本终于跑起来了,然后最大的杯具来了:脚本跑了几十秒之后,OD卡死了,动一下鼠标都要好几秒才有回应,猜测是OD强大的自动解析功能由于工作量太大,罢工了。这时候我的想法只有一个:还有这种操作?加大OD的工作量让OD卡死,这种ANTI方式还是第一次听说。。。
朋友告诉我,OD已经过时了,现在是x64dbg的天下。我最近这几年懒得动,居然都落伍到如此地步了,连工具都OUT了。。。悲衰啊。。。尽管此时这一题已经结束了,还是用这一题来学习一下x64dbg的用法吧。。。
网上竟然找不到关于x64dbg的教学,帮助文件简略得令人发指,只能靠自己了。经过一番研究,大体使用上基本差不多,先总结一下x64dbg与OD的区别:
1.x64dbg是基于命令行的,很多功能是自带的,比如脚本,命令行之类的,这种模式比OD用插件来实现脚本比起来强大多了,所有功能都可以在脚本中直接使用。
2.x64dbg的解析功能比较弱,使用插件可以大大增强API解析,远远超出了OD仅解析部分系统DLL的范围。
再说说x64dbg的缺点:
1.学习资源太少,帮助文件太简单,提高了入门门槛。
2.查看变量值太麻烦了,试了半天才发现输入varlist然后在引用里能看到,而且还不是实时更新的,下次再看还要重新输一次varlist开个新窗口看,这设计太逆天了,哪怕给个刷新按钮也好啊?(或许有方便的功能我没有发现?请各位指教)
3.内存和反汇编不支持按字节移位,或者我没试出来?
4.字符串引用的解析太差了,更别提中文支持了。
5.单步或运行命令偶尔会被忽略,需要再按一下才会执行。
6.异常处理偶尔会出问题,卡在原地忽略不过去,重新载入就恢复正常了。
不过瑕不掩玉,x64dbg作为开源软件仍在持续提高中,更何况对64位的支持了。
终于开始进入正题了,谈谈用脚本对抗这一题的方法。
程序流程很简单,检测输入之后,生成新地址-复制过去-销毁旧代码-解密执行,然后重复这一过程。观察复制代码的过程,仅仅是把当前代码之后的数据复制过去,也就是说这些代码与位置无关。那么我们可以设想一下,这些代码能不能在原位置解密?再进一步,我们能不能跳过“生成新地址-复制过去-销毁旧代码”这些步骤,直接解密执行?
观察一下第一段解密代码,没有任何混淆:
00401628 | 68 83C404B8 | push B804C483 |
0040162D | 68 44100050 | push 50001044 |
00401632 | 68 C3C3C3C3 | push C3C3C3C3 |
00401637 | 57 | push edi |
00401638 | E8 C8FFFFFF | call <crackme_dec.sub_401605> |销毁旧代码
0040163D | E8 00000000 | call crackme_dec.401642 | call $0 获取当前地址
00401642 | 5E | pop esi |
00401643 | 81EE 42164000 | sub esi,crackme_dec.401642 |
00401649 | 81C6 AC164000 | add esi,crackme_dec.4016AC |计算出解密地址入口
0040164F | B9 EFDD0F00 | mov ecx,FDDEF |长度
00401654 | FC | cld |
00401655 | AC | lodsb |
00401656 | 0FB6C0 | movzx eax,al |
00401659 | 32C1 | xor al,cl |
0040165B | 2C 01 | sub al,1 |
0040165D | 32C4 | xor al,ah |
0040165F | 32C1 | xor al,cl |
00401661 | 02C4 | add al,ah |
00401663 | C0C8 53 | ror al,53 |
00401666 | 8846 FF | mov byte ptr ds:[esi-1],al |解密放回原位置
00401669 | 49 | dec ecx |
0040166A | 85C9 | test ecx,ecx |
0040166C | 75 E7 | jne crackme_dec.401655 |
00401628 | 68 83C404B8 | push B804C483 |
0040162D | 68 44100050 | push 50001044 |
00401632 | 68 C3C3C3C3 | push C3C3C3C3 |
00401637 | 57 | push edi |
00401638 | E8 C8FFFFFF | call <crackme_dec.sub_401605> |销毁旧代码
0040163D | E8 00000000 | call crackme_dec.401642 | call $0 获取当前地址
00401642 | 5E | pop esi |
00401643 | 81EE 42164000 | sub esi,crackme_dec.401642 |
00401649 | 81C6 AC164000 | add esi,crackme_dec.4016AC |计算出解密地址入口
0040164F | B9 EFDD0F00 | mov ecx,FDDEF |长度
00401654 | FC | cld |
00401655 | AC | lodsb |
00401656 | 0FB6C0 | movzx eax,al |
00401659 | 32C1 | xor al,cl |
0040165B | 2C 01 | sub al,1 |
0040165D | 32C4 | xor al,ah |
0040165F | 32C1 | xor al,cl |
00401661 | 02C4 | add al,ah |
00401663 | C0C8 53 | ror al,53 |
00401666 | 8846 FF | mov byte ptr ds:[esi-1],al |解密放回原位置
00401669 | 49 | dec ecx |
0040166A | 85C9 | test ecx,ecx |
0040166C | 75 E7 | jne crackme_dec.401655 |
而此后的解密代码几乎与这一段完全相同,仅仅是插入了一些跳转指令。这段代码的入口和出口都很清晰,完全可以用脚本跳过来执行,直接在原地解密。于是,脱壳脚本的第一版出来了:
mov addr,401600
findcontinue:
find addr,"E8000000005E81EE",200 //call $0; pop esi; sub esi, XXX; add esi, XXX 入口标志
cmp $RESULT,0
jne findsuccess
log "err addr:{x:addr} not found" //没找到:停下来
pause
jmp findcontinue
findsuccess:
mov codeaddr, $RESULT
mov nextaddr, [codeaddr+0E], 4
cmp nextaddr, 420000 //入口代码与复制到新地址的代码相同,区别在于add esi,XXX会比较大,在这里判断一下
jb finddecode
add addr,12
jmp findcontinue
finddecode:
add nextaddr,addr
sub nextaddr,[codeaddr+08] //再判断一下解密代码的位置是否位于当前代码之后
cmp nextaddr,addr
ja finddecode1
pause
jmp findcontinue
finddecode1:
find codeaddr,"8846FF",80 //mov [esi-1],al 解密标志
mov loopaddr,$RESULT
cmp loopaddr-codeaddr,80
jb finddecode2
add addr,12
jmp findcontinue
finddecode2:
find loopaddr,"75",80 //jne XXX 出口标志
mov loopaddr,$RESULT
cmp loopaddr-codeaddr,80
jb finddecode3
add addr,12
jmp findcontinue
finddecode3:
mov cip,codeaddr //跳到入口
add loopaddr,2
mov addr,loopaddr
bp addr //出口位置下断
log "addr:{x:codeaddr} decode:{x:nextaddr}"
run //执行解密代码
bc addr
jmp findcontinue
mov addr,401600
findcontinue:
find addr,"E8000000005E81EE",200 //call $0; pop esi; sub esi, XXX; add esi, XXX 入口标志
cmp $RESULT,0
jne findsuccess
log "err addr:{x:addr} not found" //没找到:停下来
pause
jmp findcontinue
findsuccess:
mov codeaddr, $RESULT
mov nextaddr, [codeaddr+0E], 4
cmp nextaddr, 420000 //入口代码与复制到新地址的代码相同,区别在于add esi,XXX会比较大,在这里判断一下
jb finddecode
add addr,12
jmp findcontinue
finddecode:
add nextaddr,addr
sub nextaddr,[codeaddr+08] //再判断一下解密代码的位置是否位于当前代码之后
cmp nextaddr,addr
ja finddecode1
pause
jmp findcontinue
finddecode1:
find codeaddr,"8846FF",80 //mov [esi-1],al 解密标志
mov loopaddr,$RESULT
cmp loopaddr-codeaddr,80
jb finddecode2
add addr,12
jmp findcontinue
finddecode2:
find loopaddr,"75",80 //jne XXX 出口标志
mov loopaddr,$RESULT
cmp loopaddr-codeaddr,80
jb finddecode3
add addr,12
jmp findcontinue
finddecode3:
mov cip,codeaddr //跳到入口
add loopaddr,2
mov addr,loopaddr
bp addr //出口位置下断
log "addr:{x:codeaddr} decode:{x:nextaddr}"
run //执行解密代码
bc addr
jmp findcontinue
执行结果很完美,除了那个提示脚本运行时间过长的提示窗口。大约解密到460000时停下来了,过去看看成果,发现并没有解密完,只是解密代码发生了小变化,出口循环变成了长跳转,修改一下脚本,做个兼容:
mov addr,401600
findcontinue:
find addr,"E8000000005E81EE????????81C6",300 //call $0; pop esi; sub esi, XXX; add esi, XXX
cmp $RESULT,0
jne findsuccess
log "err addr:{x:addr} not found"
pause
jmp findcontinue
findsuccess:
mov codeaddr, $RESULT
mov nextaddr, [codeaddr+0E], 4
cmp nextaddr, 405000
jb finddecode
mov addr,codeaddr
add addr,12
jmp findcontinue
finddecode:
add nextaddr,addr
sub nextaddr,[codeaddr+08]
cmp nextaddr,addr
ja finddecode1
pause
jmp findcontinue
finddecode1:
find codeaddr,"8846FF",100 //mov [esi-1],al
mov loopaddr,$RESULT
cmp loopaddr,0
ja finddecode2
add addr,12
jmp findcontinue
finddecode2:
find loopaddr,"0F85??FFFFFF",20 //jne XXX
mov bpaddr,$RESULT
add bpaddr,6
cmp $RESULT,0
ja finddecode3
find loopaddr,"75",20 //jne XXX
mov bpaddr,$RESULT
add bpaddr,2
cmp $RESULT,0
ja finddecode3
add addr,12
jmp findcontinue
finddecode3:
mov cip,codeaddr
mov addr,bpaddr
bp addr
log "addr:{x:codeaddr}:{x:addr} decode:{x:nextaddr}"
run
bc addr
jmp findcontinue
mov addr,401600
findcontinue:
find addr,"E8000000005E81EE????????81C6",300 //call $0; pop esi; sub esi, XXX; add esi, XXX
cmp $RESULT,0
jne findsuccess
log "err addr:{x:addr} not found"
pause
jmp findcontinue
findsuccess:
mov codeaddr, $RESULT
mov nextaddr, [codeaddr+0E], 4
cmp nextaddr, 405000
jb finddecode
mov addr,codeaddr
add addr,12
jmp findcontinue
finddecode:
add nextaddr,addr
sub nextaddr,[codeaddr+08]
cmp nextaddr,addr
ja finddecode1
pause
jmp findcontinue
finddecode1:
find codeaddr,"8846FF",100 //mov [esi-1],al
mov loopaddr,$RESULT
cmp loopaddr,0
ja finddecode2
add addr,12
jmp findcontinue
finddecode2:
find loopaddr,"0F85??FFFFFF",20 //jne XXX
mov bpaddr,$RESULT
add bpaddr,6
cmp $RESULT,0
ja finddecode3
find loopaddr,"75",20 //jne XXX
mov bpaddr,$RESULT
add bpaddr,2
cmp $RESULT,0
ja finddecode3
add addr,12
jmp findcontinue
finddecode3:
mov cip,codeaddr
mov addr,bpaddr
bp addr
log "addr:{x:codeaddr}:{x:addr} decode:{x:nextaddr}"
run
bc addr
jmp findcontinue
这次解密时间稍长一点,最后停下来的位置是4FB939,往下看看,已经没有加密代码了,而且直接找到了最终判断结果的位置:
004FBA40 | 75 56 | jne crackme_dec.4FBA98 |
004FBA42 | E8 00000000 | call crackme_dec.4FBA47 | call $0
004FBA47 | 58 | pop eax |
004FBA48 | 2D 22114000 | sub eax,crackme_dec.401122 |
004FBA4D | 05 474B4000 | add eax,<crackme_dec.sub_404B47> |
004FBA52 | 50 | push eax | const char* format
004FBA53 | FF15 00114000 | call dword ptr ds:[<&printf>] | printf
004FBA59 | 83C4 04 | add esp,4 |
004FBA5C | FF15 04114000 | call dword ptr ds:[<&getchar>] |
004FBA62 | FF15 04114000 | call dword ptr ds:[<&getchar>] |
004FBA68 | E8 00000000 | call crackme_dec.4FBA6D | call $0
004FBA6D | 58 | pop eax |
004FBA6E | 2D 48114000 | sub eax,<crackme_dec.sub_401148> |
004FBA73 | 05 4FAD4000 | add eax,<crackme_dec.sub_40AD4F> |
004FBA78 | 8B28 | mov ebp,dword ptr ds:[eax] |
004FBA7A | E8 00000000 | call <crackme_dec.sub_4FBA7F> | call $0
004FBA7F | 58 | pop eax |
004FBA80 | 2D 5A114000 | sub eax,crackme_dec.40115A |
004FBA85 | 05 4BAD4000 | add eax,crackme_dec.40AD4B |
004FBA8A | 8B20 | mov esp,dword ptr ds:[eax] | UINT uExitCode
004FBA8C | 6A 00 | push 0 |
004FBA8E | FF15 50114000 | call dword ptr ds:[<&ExitProcess>] | ExitProcess
004FBA94 | 83C4 04 | add esp,4 |
004FBA97 | C3 | ret |
004FBA98 | E8 00000000 | call crackme_dec.4FBA9D | call $0
004FBA9D | 58 | pop eax |
004FBA9E | 2D 78114000 | sub eax,crackme_dec.401178 |
004FBAA3 | 05 1C4B4000 | add eax,<crackme_dec.sub_404B1C> |
004FBAA8 | 50 | push eax | const char* format
004FBAA9 | FF15 00114000 | call dword ptr ds:[<&printf>] | printf
004FBAAF | 83C4 04 | add esp,4 |
004FBAB2 | FF15 04114000 | call dword ptr ds:[<&getchar>] |
004FBAB8 | FF15 04114000 | call dword ptr ds:[<&getchar>] |
004FBABE | E8 00000000 | call crackme_dec.4FBAC3 | call $0
004FBAC3 | 58 | pop eax |
004FBAC4 | 2D 9E114000 | sub eax,crackme_dec.40119E |
004FBAC9 | 05 4FAD4000 | add eax,<crackme_dec.sub_40AD4F> |
004FBACE | 8B28 | mov ebp,dword ptr ds:[eax] |
004FBAD0 | E8 00000000 | call <crackme_dec.sub_4FBAD5> | call $0
004FBAD5 | 58 | pop eax |
004FBAD6 | 2D B0114000 | sub eax,crackme_dec.4011B0 |
004FBADB | 05 4BAD4000 | add eax,crackme_dec.40AD4B |
004FBAE0 | 8B20 | mov esp,dword ptr ds:[eax] | UINT uExitCode
004FBAE2 | 6A 00 | push 0 |
004FBAE4 | FF15 50114000 | call dword ptr ds:[<&ExitProcess>] | ExitProcess
004FBAEA | 83C4 04 | add esp,4 |
004FBAED | C3 | ret |
004FBA40 | 75 56 | jne crackme_dec.4FBA98 |
004FBA42 | E8 00000000 | call crackme_dec.4FBA47 | call $0
004FBA47 | 58 | pop eax |
004FBA48 | 2D 22114000 | sub eax,crackme_dec.401122 |
004FBA4D | 05 474B4000 | add eax,<crackme_dec.sub_404B47> |
004FBA52 | 50 | push eax | const char* format
004FBA53 | FF15 00114000 | call dword ptr ds:[<&printf>] | printf
004FBA59 | 83C4 04 | add esp,4 |
004FBA5C | FF15 04114000 | call dword ptr ds:[<&getchar>] |
004FBA62 | FF15 04114000 | call dword ptr ds:[<&getchar>] |
004FBA68 | E8 00000000 | call crackme_dec.4FBA6D | call $0
004FBA6D | 58 | pop eax |
004FBA6E | 2D 48114000 | sub eax,<crackme_dec.sub_401148> |
004FBA73 | 05 4FAD4000 | add eax,<crackme_dec.sub_40AD4F> |
004FBA78 | 8B28 | mov ebp,dword ptr ds:[eax] |
004FBA7A | E8 00000000 | call <crackme_dec.sub_4FBA7F> | call $0
004FBA7F | 58 | pop eax |
004FBA80 | 2D 5A114000 | sub eax,crackme_dec.40115A |
004FBA85 | 05 4BAD4000 | add eax,crackme_dec.40AD4B |
004FBA8A | 8B20 | mov esp,dword ptr ds:[eax] | UINT uExitCode
004FBA8C | 6A 00 | push 0 |
004FBA8E | FF15 50114000 | call dword ptr ds:[<&ExitProcess>] | ExitProcess
004FBA94 | 83C4 04 | add esp,4 |
004FBA97 | C3 | ret |
004FBA98 | E8 00000000 | call crackme_dec.4FBA9D | call $0
004FBA9D | 58 | pop eax |
004FBA9E | 2D 78114000 | sub eax,crackme_dec.401178 |
004FBAA3 | 05 1C4B4000 | add eax,<crackme_dec.sub_404B1C> |
004FBAA8 | 50 | push eax | const char* format
004FBAA9 | FF15 00114000 | call dword ptr ds:[<&printf>] | printf
004FBAAF | 83C4 04 | add esp,4 |
004FBAB2 | FF15 04114000 | call dword ptr ds:[<&getchar>] |
004FBAB8 | FF15 04114000 | call dword ptr ds:[<&getchar>] |
004FBABE | E8 00000000 | call crackme_dec.4FBAC3 | call $0
004FBAC3 | 58 | pop eax |
004FBAC4 | 2D 9E114000 | sub eax,crackme_dec.40119E |
004FBAC9 | 05 4FAD4000 | add eax,<crackme_dec.sub_40AD4F> |
004FBACE | 8B28 | mov ebp,dword ptr ds:[eax] |
004FBAD0 | E8 00000000 | call <crackme_dec.sub_4FBAD5> | call $0
004FBAD5 | 58 | pop eax |
004FBAD6 | 2D B0114000 | sub eax,crackme_dec.4011B0 |
004FBADB | 05 4BAD4000 | add eax,crackme_dec.40AD4B |
004FBAE0 | 8B20 | mov esp,dword ptr ds:[eax] | UINT uExitCode
004FBAE2 | 6A 00 | push 0 |
004FBAE4 | FF15 50114000 | call dword ptr ds:[<&ExitProcess>] | ExitProcess
004FBAEA | 83C4 04 | add esp,4 |
004FBAED | C3 | ret |
其中printf字符串用的是相对地址,计算一下真实地址:4FBA47-401122+404B47=4FF46C,在内存中查看一下:(x64dbg自带计算器可以直接计算,还可以对计算结果在内存或反汇编中查看)
004FF46C 43 6F 6E 67 72 61 74 75 6C 61 74 69 6F 6E 73 2C Congratulations,
004FF47C 20 73 65 72 69 61 6C 20 6E 75 6D 62 65 72 20 69 serial number i
004FF48C 73 20 63 6F 72 72 65 63 74 20 20 5E 2D 5E 1F 1F s correct ^-^
004FF46C 43 6F 6E 67 72 61 74 75 6C 61 74 69 6F 6E 73 2C Congratulations,
004FF47C 20 73 65 72 69 61 6C 20 6E 75 6D 62 65 72 20 69 serial number i
004FF48C 73 20 63 6F 72 72 65 63 74 20 20 5E 2D 5E 1F 1F s correct ^-^
实际检测SN的代码位于4FBAEE处,对算法的分析不在本文之列,别人已经做的很完善了,我就不献丑了,下面对这个“多重壳”做一点分析。
在使用rdstc生成随机地址之前有一段代码:
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2019-12-31 15:50
被kanxue编辑
,原因: