这道题已经完全脱壳了,只剩下算法的逆运算了。因为第二天还要搬砖,就没有熬夜搞了 T_T
拿到题目后发现在不调试的情况下让它自己正常运算都要好几秒,再一看代码基本就能想到是解密几行汇编再执行,于是找到解密代码的规律:
这里接收并保存完输入的用户名和密码后便开始解密代码
解密完后,通过call eax
的方式跳转到解密后的代码
用x32dbg
调试看看解密后的代码,03754814
处的指令很像函数头,准备再看看下一次解密的代码
此时在后面的代码里没有发现call eax
,而是一堆有问题的指令
经过调试后发现call eax
的那部分代码也是动态解密出来的(由于截图分了几次调试截,地址不一样)
在调试时还发现,在解密新代码的同时会把上一步解密出的所有代码,包括解密代码在内,全部抹掉(机智)
再观察第二次解密出的代码,更加明显了,popfd pushfd
和其他寄存器出栈压栈的操作就是在恢复和保存环境(后面解密出的代码也是这样的操作,就不截图了),另外还有个发现,执行真正的逻辑代码是在解密出call eax
那段代码后
用的是x32dbg
需要注意的地方:
(附件x32dbg.txt
)
获取到的指令看起来很不舒服,因为有太多无用的地址定位指令和其他数据
再写个idapython
脚本,获取所有逻辑验证指令(附件code_dmp.py
)
通过以上的操作就获取到了这2880次解密出的所有逻辑验证代码了,但是还不够完美,要做到脱壳后能独立运行
于是在这段指令前加上解密第一步的函数头
把2880次解密完后,剩下的所有指令再手动抠出来,补到后面
现在验证代码已经全拿出来了(在附件code_dump.bin
中),而且这些代码都是地址无关的运算,唯一需要的地址是在esi
中保存,那就需要看看esi
中保存的是什么了
通过调试发现,esi 05EF5C91
指向了一张表,esi+0x1B0
的位置是输入的序列号,esi-0x11
的位置存的是用户名
当前,我们还原的时候需要的是把用户名和序列号复制进去前的数据表,重新调试再通过一通硬件断点的操作找到了这张表,就最后一行保存序列号的地方不一样
调试发现结尾的地方是在比较esi+0x1B0
处和edi
处的用户名字符串,如果相等就提示成功(没有截调试的图,因为要调到2880次之后需要半小时)
其实edi
指向的就是esi-0x11
的位置,重写一下这段指令
直接把解出来的16进制机器码粘贴到原PE的对应位置,但是发现esi
指向的数据是在第一次解密后才赋值的,这个地址是动态的,所以还需单独把esi
指向的数据表提前写进PE。另外后面验证完成后打印提示字符串的部分指令也要抠过来,所以我选择了第二种方式
自己写c++代码实现输入和输出功能,中间将16进制机器码当作ShellCode
来调用,只需要将这段ShellCode
的比较结果传回来就行,于是把比较结果ecx
保存在eax
当作函数返回值,补上函数结尾
逻辑和原来的一样,只是提示的字符串不同
部分代码,全部代码见附件FixCrackMe.cpp
效果完全一样(还原后的PE见附件FixCrackMe.cpp
)
验证的开始将输入与esi
指向的数据表通过一定规则异或
验证的结尾再将esi
指向的数据表中的snail3896q3405%\0
字符串分别与特定下标的数进行异或,结果保存在esi+0x1B0
,如果算出的结果等于输入的字符串则验证成功
中间那一大堆操作就是在算下标。而这一大堆操作基本是:计算几个16元一次方程的结果,将结果重新存起来,作为下标在表里找到新的数据,再次带入几个16元一次方程中计算结果,这样的操作一共经过9次运算,得到下标。最后再到表里获得值,这个值作为下标执行2
中的操作
随便截个图感受下吧:
因为没做出来这里只说思路,也许不对
分析完算法距离比赛结束还有12个小时,此时我的内心戏
A:没有什么事是熬夜解决不了的,如果有就战到天亮
B:省省吧,明天还要搬一天的砖呢,毁灭吧,赶紧的,累了
A:那做这么多天的题不能白做了,之前的夜不能白熬呀
B:那,就明天中午休息的时候发一篇总结贴,混个精华或优秀也值呀
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2019-10-29 18:02
被KevinsBobo编辑
,原因: 原附件中少了cpp文件