能力值:
( LV3,RANK:20 )
51 楼
smc并不能隐藏信息,并且,如果分析下来数据的移动与原来的指令地址是重合的,并没有增加什么信息流,反而是增加了曝光无用的信息流的机会,因为作者在这个过程中面临了两难选择:
1 丢弃虚假的信息流,这样就一下子为分析器指明了方向,因为程序自动中断的那些信息流,会一直留在数据库的记忆中,下次经过时,就不鸟它了。
2 保存虚假的信息流,但是代价是让自己的程序更复杂,运行时花费的内存和时间过多,引起用户的不满。
这就像打桥牌,作者是庄家,我和用户是东手和西手,我们在两个方向上挤压庄家,逼迫他要么放弃建立自己的长套花色,要么失去对将牌的控制。
能力值:
( LV3,RANK:20 )
52 楼
算一算一共有多少个寄存器变量
eax ebx ecx edx esi edi esp ebp
| | | | | | | |
ax bx cx dx si di sp bp
| | | |
ah,al bh,bl ch,cl dh,dl
|___________________________| |_________| |_________|
16个寄存器变量 4个寄存器 4个寄存器 -----------24个
变量 变量
cs ds es ss fs gs
|__________________________________|
6个寄存器变量 --------------------------------------------30个
eip
|
ip
2个寄存器变量 --------------------------------------------------------32个
运算结果标志位
SF ZF AF PF CF
|_______________|
5个寄存器变量 ---------------------------------------------------------37个
方向寄存器
DF
1个寄存器变量 ----------------------------------------------------------38个
需要有特权才能控制的寄存器暂时不考虑。
这里有一个至少38位的位图,如果一个指令是影响了其中的n个地方的话,就要考虑生成n个变量。
但是前后两条指令之间会有一个冲销的效果,比如由第一条指令计算产生的标志位其实是个无索引的量,如果第二条指令重置了它,那么这个量就死了。一个死亡的量,其信息流中断了,就不需要保存它了,如果我们是采用滞后保存信息流的策略的话,呵呵,它连数据库都没机会进了。
能力值:
( LV9,RANK:180 )
53 楼
LZ还没开始写程序?
能力值:
( LV3,RANK:20 )
54 楼
我就是在写程序啊。只不过在看《代码大全2》前我都是先写代码,看过《代2》后,改成先写文档了。
能力值:
( LV9,RANK:180 )
55 楼
那你得加快脚步啊
我等福音等很久了
能力值:
( LV3,RANK:20 )
56 楼
现在考虑保存这些变量的数据结构,首先是有关单个变量,我们需要掌握它的哪些方面的信息?
1 变量的名称:不管这个变量在哪个寄存器上,或者哪个标志位上,它都有一个独一无二的id。
变量的副本是没有新名称的,只是要记录在一个数组里,指出这个副本的其他信息。
2 变量的值: 一个变量的值首先要考虑是不是可变的,一个变量的值的变化是指,当它来自一个随机源,又没有其他副本时,与另
一个变量发生计算后,结果被填入了这个变量的位置,
当不满足以上的这个要求时,一般考虑是用一个新的变量来记录这个新的值。
一个有副本的变量,不论什么时候被填入新值,都是生成了新变量。
以上两条的典型场景:
比如在第一条指令上,EAX其值为2,那么我们就说有一个叫id_1的变量生成了。(无索引)
然后第二条,mov ecx,eax ,我们说id_1现在有了一个副本。
然后第三条,mov eax,1,我们说现在有了一个新变量,id_2,它的值是1。(无索引)
然后第四条,mov ecx,eax,我们说现在id_1死亡了,这条线索归入中止,以后再走这条线路时,一看它是僵尸,就不为它生成新变量了。
而id_2,现在有了副本。(无索引)
(1) 因为id_1死了,所以要在分析结果中,进行规约:
首先要倒推,ecx中是id_1的最后一个副本,用id_2的副本取代它。
mov ecx,eax
(2) 发生过一次有效规约,往往意味着还可能有其他规约,程序自动进入下一次规约:
继续倒推,在第一句中的ecx是id_1最后副本的唯一前驱,所以去掉,因为这次去掉同
时取消了id_1的出生,所以id_1的流程彻底终止,整体消亡了。
nop
mov eax,1
mov ecx,eax (这里在显示的时候别忘了注释它们的真实值)
(3) 发生过一次有效规约,往往意味着还可能有其他规约,程序自动进入下一次规约:
因为这里是在程序头,可能情况和后面程序中的稍有差异,也许要分别制定位于程序头和中间的不同规约策略。
就这例而言,因为eax从未被使用,又写入了一个固定值,所以调整程序头的eip,指向下一条指令,同时eax中自动置1,
这时候因为eax成为了起始量,所以从原来的数量流上脱开,id_2的两个副本分道扬镳。现在有两个变量
id_2和id_3
(4) 又发生了有效规约,所以继续扫描可以规约的内容,这次调整eip到下一条指令,同时ecx置1,变量流现在有两条,id_2,id_3。
(5) 现在整个程序的起点调整到第三条指令后,eax和ecx中间是两条新数据流的起点。
以上这些过程可以用一些简单的规则,反复进行迭代,得到最后不变的那个结果。不过这里(3)和(4)的分析乱了一点,应该是和(1)(2)的规约同步的,也许最后我应该用多线程来处理规约问题?
让我们来提取一些必要的信息。
能力值:
( LV9,RANK:330 )
57 楼
It's not that I don't want to use Chinese. It's just I can't type Chinese on an English version of Windows. And it's my company's computer, I don't want to install Chinese language and IME on it.
It's not a dilemma from my point of view.
1. The abandoned data flow may not be "fake". It could be useful both before and after modification.
2. If the "fake" data flow is useful, the application would become more complicated, but how about the analyzer?
For example, the complexity for application may increase by 500% if a lot of techniques are used to hide the information. How much overhead you would have if you try to analyze the application? It should not be less than 500%, right? 500% for application maybe only takes 1 second, but for the analyzer, it could be several hours, days, or more. And I think most users are willing to accept 1 second delay at startup. And I have 2 more comments:
1:
You have to handle instructions such as "pushad" "popad". Variables could be stored in stack and then modified.
2:
Even it's OK for register variable, how to handle variables in memory? I guess you have to store the var_id, type(eg. byte or dword) and its address in the database. And you have to be able to decect functions such as "memcpy()", right? "memcpy" could be either an external API or an inline function.
能力值:
( LV3,RANK:20 )
58 楼
关于第一个问题,我已经说了,我眼里的数据只有三种类型,就是无索引的,有索引的,和索引。
在这种观点下,我不在乎这个变量在寄存器还是内存,还是什么别的地方,我只看他被访问的方式,只要它曾经被索引了,只要它的索引还在,就一定储存在一个数据结构中,而这些数据结构就是一个个多级的结构,结构化的东西可以从宏观上去组织储存,没必要一个个元素去单独分析。所以在我的程序眼里没有什么pushad,popad,堆栈只是一种特定的索引区域而已。
所以第二个问题也很简单,我不是对每一个数据做索引,只要它一旦被确定是一个数据结构的一部分,就只要对首地址索引了。
有个小问题是循环,其实循环是动态调试最容易确定的事了,昨晚我又好好看了一遍编译原理,确认了这一点,而且有意思的是在编译器眼里根本没有子程序这件事,让我大大欣慰了,也就是说,只要确认一个循环,就确认至少一个数据结构,所以循环越多,我们对数据结构了解就越清楚。我们可以在循环里插个桩,省得调用调试器了,建议你看看valgrind,它的实现方式和我的设想几乎一样,可惜的是它只支持linux,并且它没有对机器指令携带的数据流进行细分,当然,把它移植一下,再写个插件,我认为是完全可行的。
最后还是效率,同样的,你可以看一下valgrind的实现,整个程序每一条指令都单步加分析,做内存检查,速度比原来慢50倍。所以,要想让我的程序慢到10小时,我只要做到让壳慢到6分钟就OK了。
除此之外,因为识别循环是这么简单的事,所以,要想增加程序的复杂度,必须使用switch结构,并使用不相关的代码重复进行分支,并在每一次分支的时候,进行保存原有索引的工作,这就拖慢了程序的运行。
怎样确认一条信息流是虚假的?我想了有三种策略,前向,当下和后向。
先看当下,我们知道一个程序总有一些基本的功能,比如画个窗体,按钮,接收鼠标响应,这是程序固有的功能,而且在api的对应中也是很清楚地,只要在眼下运行到这个地方,知道它是有效的,那就可以回溯到前面找那个stolen oep 了。
然后是前向,比如我已经在正常的程序中,现在转VMP,所以我就紧紧抓住正确的信息流。
最后是后向,我们已经走到了陷阱里,并且知道问题出在那一条信息流上了,所以它是个污点,后向追溯,就可以了。
为什么一条假的信息流和真的信息流是永远不会真正混合在一起的?
因为有个老外教授用数学方法证明了,在程序中,只要给定过程和结果,就一定能逆推出条件。
还有一点是关于社会工程学的,我是永远无法和像你们这样的高手并肩分析什么二进制流的,我相信大多数破解爱好者也差不多。但是我的程序可以啊。如果来个时髦的云计算,或者最不济,云储存,那就人多力量大了。
能力值:
( LV3,RANK:20 )
59 楼
另外一个关于时间的话题是,可能我破解一个程序,一天十小时不够,怎么办?
那我就一次对付一个,比如今天我要对付的是为什么它要检测出调试器,我一层一层跟踪到出问题的时候,然后以这个点为污点,随后回溯,因为每一个分支点都有记录,每一个数据结构都能查询,所以一直看啊看,直到爆破它,然后关机睡觉。
第二天起来,昨天的弯路就不走了,程序的状态已经保持,随时恢复到我要的地方。一个自动脱壳的脚本或者补丁也积累起来了。
关键是这样之后,每次作者都要在每一行代码上好好下功夫变形,因为我们的程序积累了日志,下次遇上同样的anti,我把两份记录拿出来看看,是不是很好的参照呢?
写得不好的OD脚本很容易被作者找到漏洞,而且别人也很难看出他为什么这样写,结果遇上壳一变化就不行了。所以这种破解的经验是很宝贵,但是稍显原始。
程序可以帮我们从繁琐的低级反anti中解脱出来,即使为此多花几天功夫分析,人的信心也不会轻易被消磨,至少你不用一天到晚看自己记录的参考在哪里,或者一次次重新启动程序,等它运行到指定点了。
能力值:
( LV3,RANK:20 )
60 楼
把x86的指令化作程序的基本数据,就三个
复制,计算,控制转移
把x86的指令化作程序的基本流程形式,就三个
顺序,循环,条件分支
把x86的数据组织形式确定为一种
索引
在以上三个基本出发点,建立各种推理规则
把壳的作者对人的体力和耐心的考验转化为对规则的检验和对机器性能的考验。
不过刚才我核对了《代2》上前期准备的几个核对表,好多叉叉,还要继续准备啊。
能力值:
( LV3,RANK:20 )
61 楼
现在进入架构核对表时间,先写程序关键部位算法和大致的框架:
基本的调试过程是这样的:
一:在虚拟机或类似的空间,加载这个程序。
(目的:提高对被调试程序的控制力)
二:静态反汇编一个片断,直到遇到第一个控制转移为止,这个控制转移包括call,ret,jmp,jx这类;这样得到一个基本块。
(目的:这是防止花指令对静态反汇编的anti)
三:把每一条指令送到分解器中分解,得到 变量流 ,但是不优化它,直接送到虚拟机进行操作,记录结果。然后循环这个过程到块尾。
*******(易受攻击点-1,作者会发散大量的虚假信息,造成数据流庞大,
反制因素:
1 因为这条线路现在才走了一遍,所以虚假信息流的增大是有限的,
2 要增大这个数据流,在第一遍的阶段,没有循环,所以要求该程序的体积也相应庞大,效率降低
3 一个基本块越长,无用信息抵消的可能性越大,所以最后形成的数据流的体积是有限的
)
四:继续执行以上二、三两步,若转移变成了循环,就一直运行到第一个出口,也就是第一次脱离这条循环线路的跳转。
(如果第一个循环就是死循环,那这个程序还能用吗?)
*******(易受攻击点-2,作者会让多条虚假信息会并到某个循环,以很低的代价制造大量的虚假数据流
对策:
1 优化这个循环中的垃圾指令,减少运行时间
2 观察这个循环所处理的数据是不是可以归纳成组,是的话按数组对待,集中处理数据流,减少虚假数据流的发生
3 如果这个循环处理的数据不可以归纳成组,说明出现了一个新的组,组建一个新的数据结构对应它
)
*******(易受攻击点-3,有一个开始我没有想对的问题,Ptero说得对,确实存在不可归约的流图,也就是说从循环外向循环内转移的可能是始终存在的,我画了个图,证明一个循环是否是一个编译理论上说的唯一入口点的循环,在局部是看不出来的。
对策,
1 多线程符号执行,在每一个分支点同时向下跟踪
2 出错就返回,重新开始分析,并且鉴于这种恶劣的设计不太可能是编译器的设计,给它记上个污点再说。
3 凭人的经验判断了,比如这种非嵌套的循环很不自然,但往往存在一些模式,比如在这种嵌套里要么有anti
要么是在循环体内存在一些模式之类,毕竟这种非结构的程序多半是人写的。
4 在虚拟机中第一遍跟踪时只记录指令序列,不分析,直到壳把控制权交换给程序,就比如以画个窗体为例。
在这个基础上,分析前述的指令,就绕过了这个NP完全问题)
)
五:分析这个循环的操作对象,记录循环的条件,循环操作的对象,把这些数据整合成一个或几个数据结构。
六:优化循环,再次根据循环的特征,分析这个循环的数据流,得到能继续导向下一阶段的数据流。
七:反复迭代上面的过程,直到最优质的数据流出现,把它从原来的指令流中提取出来。
能力值:
( LV9,RANK:330 )
62 楼
我有点跟不上你的思路了
看起来你很有把握的样子,祝你成功吧
能力值:
( LV3,RANK:20 )
63 楼
我可没有绝对的把握,这是个NP完全问题。
不过要是能用3天换它6分钟,就很值了。
能力值:
( LV3,RANK:20 )
64 楼
突然发现自己蠢透了,问题根本没我想的那么复杂,我真是傻。
嗯,把流程改得再简单点。
能力值:
( LV6,RANK:90 )
65 楼
无法理解,提一些小问题好了.先看当下,我们知道一个程序总有一些基本的功能,比如画个窗体,按钮,接收鼠标响应,这是程序固有的功能,而且在api的对应中也是很清楚地,只要在眼下运行到这个地方,知道它是有效的,那就可以回溯到前面找那个stolen oep 了。 楼主应该跟一下虚拟机吧,目前的虚拟机在调用api前会将调用地址抹除,我不知道楼主是如何进行回溯的.然后是前向,比如我已经在正常的程序中,现在转VMP,所以我就紧紧抓住正确的信息流。
不是所有的函数都能有正确的信息的,VM的一般都是一些重要而且很少执行的算法,例如注册和初始化,这些函数都是需要特定的条件才能触发的,我不知道楼主是如何触发所有的函数.最后是后向,我们已经走到了陷阱里,并且知道问题出在那一条信息流上了,所以它是个污点,后向追溯,就可以了。 并不是所有的陷阱都会触发问题的,很多时候都是随机的,这里还要区分由于软件本身的缺陷或者外部因数引起的错误,不知道楼主是如何识别陷阱的.
能力值:
( LV2,RANK:10 )
66 楼
如果是花指令或者混淆的话,通过将x86转化成微指令或精简指令集,然后通过编译器优化技术是可以对付的,即使是虚假跳转也可以通过symbolic-execute判断取值范围来确定是否有可能taken。
但是加密过的代码和vm对付不了,vm的问题在于它不是垃圾代码,它的代码必须要执行而且是有意义的,如果你要优化它除非根据bytecode抽取所有的vm模拟指令展开成x86,这又牵扯一个过度膨胀以及展开后识别循环的问题。
如果是找污点数据源头的话,通过valgrid类似的工具跟踪所有执行过的指令,如果把所有指令按照线性展开来处理的话,应该理论上没问题,可以通过数学的方法精确地建模每个变量的代数约束关系,从而解决如果我要这条分支taken,那么变量a,b,c应该分别满足什么条件的问题,只是循环展开的代价可能受不了,如果不展开循环,那可我们只能知道变量间的依赖关系而没有精确地代数约束,除非楼主有建模循环约束的办法,那么如果我们能且仅能知道分支j和变量a,b,c有关,那我们仍然无法知道如何使j执行起来,不知道楼主如何解决?
能力值:
( LV3,RANK:20 )
67 楼
我是这么跟回溯的,现在比如我有一个虚拟机,我在虚拟机里载入这个程序,然后,不运行它。我在虚拟机外面,静态反编译这个程序,直到跳转,这时候因为没跳转,所以应该我是看得到调用api来自哪里的。然后让虚拟机也执行到这个跳转。
第二个问题,什么叫正确的信息流,比如说我们的程序中每一条指令的执行都离不开一个环境的上下文,如果在转入一段过程之前,不保存寄存器,那么就会出问题。这说明每一个寄存器都是一个完整信息流的子集,考虑x86指令集的特点,没有哪条指令一下子改变所有的寄存器和标志位吧。我们在跳入vm之前的大多数寄存器还是要在跳出后用到的。因此程序必须时刻保持有个索引指向这些信息,要不然将来跳出时提取不出来的。
然后当虚拟机在执行那条被模拟的指令时,还要恢复当时的环境,所以,两条信息流就交汇在一起,密不可分了。当然,除非上来第一条就是add eax,1之类。那个倒是不要什么环境。可是当它在处理第二条bytecode时,就暴露了第一条的结果,我们就可以逆推了。这只是条思路,实际上执行到后面,它总要在恢复的环境中运行的,然后就逆推好了。
陷阱肯定是有随机的,并且不一定在下一条指令就反应出来,但它一定是一条信息链上的一环,就算是软件本身的缺陷,那么不稳定的因素会导致客户流失。而如果是作者故意,呵呵,他的代码一定不是和程序本身同一个上下文的,就算是SDK,或者在OBJ文件中加入,也要考虑保护环境吧,于是,信息流就分支了。
过度膨胀和展开后识别循环,其实这个调试要从两个方面来看,我以前一直觉得,调试的时候程序出错是很困难的。现在想想,就只是个因素,程序出错终止,那么就给我们一个终点,程序从壳的一部分走到程序的一部分,或反过来,也是个终点。
在这个过程中,我们这么办,凡是顺序执行,就老实跟随,执行,记录指令,但不记录数据,遇到跳转就插个桩,或者别的什么办法,反正关键是要识别在一个真实的执行过程中,一个循环有没有在它内部有其他入口,这是按照编译原理来做的。
在这个过程中,我不去解vm指令,我只分解x86的指令到微代码,我们跟着
1 vm的虚拟指令走解密通道,
2 原来寄存器的保存过程走隐藏通道
3 寻找它们的交汇点
4 找到只属于vm的处理部分
嗯,性能肯定是要受损失的,但就像我上面写的一样,要想规约信息流,必须识别循环,要想识别循环,必须记录分支情况,要想记录分支情况,必须动态调试,要想动态调试,最好是虚拟机调试的同时用静态反汇编逐步提取指令。
能力值:
( LV3,RANK:20 )
68 楼
关于你怎么知道哪条分支运行,需要什么精确的数学条件的问题,我只能先这样设想:
如果这个程序在调试时执行到循环这里,我通过规约,仅知道分支j和a,b,c有关,然后我就不去展开循环了。
那么接下来会发生什么呢?
比如程序运行到我们需要的地方了,现在我知道这条分支是我要的,我要倒推a,b,c的量了,不知道楼上是不是这个意思?
那么就到了我要说的第二个重点了,假设我不知道a,b,c的值是因为我只记录了每一条指令,并且每条指令都只记了一遍,所以我根本不知道,某个位置上a,b,c的值。但是,我们可以再执行一遍程序,确保它们走了相同的路径,因为每条指令都记录了,所以这次我只看a,b,c的数据流了,不知道这样行不行?
能力值:
( LV3,RANK:20 )
69 楼
假如程序没有如我所料,正常运行,那么就要修改a,b,c的条件,在这个情况下确实很难知道怎样改。
但是有一点是可以肯定的,如果只有启用一个分支才能让这个程序跑起来的话,那么和它短路的变量一定都是没问题的。
如果启用了一个分支,就会使这个程序完蛋的话,那么和它短路的变量一定是不需要的。
至于我这里说的有关,是指短路的概念,就是说这里的两个变量是互相通过不可分离的计算粘连在一起的意思。
能力值:
( LV3,RANK:20 )
70 楼
而vm之所以不是真正的粘连,是因为它可以有算法分离,而且分离它的key在程序内,那种没有key就不能跑第一次的不算。
能力值:
( LV3,RANK:20 )
71 楼
我们来设想一个实际点的场景吧。
考虑壳在加密的时候把代码段的一部分作为key,和vm指令一起进行加密运算,那么当它在解码的时候,一计算代码段,认为有问题,所以key不对了,然后它继续去和vm指令做解码,结果出错了,然后执行若干步,程序挂了。在这里,要想使正确的分支运行,必须提供正确的代码段信息,这个代码段信息又通过解密运算和vm指令结合在一起,那应该说是肯定短路的。但是,我们不知道用什么样的变量使这个正确分支运行。
并且这样的计算还不是定时的,它通过一个随机算法,有时候调用一下,有时候不调用。
能力值:
( LV3,RANK:20 )
72 楼
不过我们可以倒查信息流,看看信息流分成几支,我们不知道到底什么条件让程序走到这步的,但我们可以看到这些信息流的源头,有些是数据随机生成的,有些是固定的。
接下来就可以重点排查那些随机数据源,我在最前面的介绍中一直说要区分用户输入什么的随机输入源和硬编码,某些API 的固定返回结果。结果我们一定可以看到那个产生随机数的源和查代码段大小的源短路在一起,接下来是人脑的事了。
能力值:
( LV2,RANK:10 )
73 楼
倒推数据流应该是可以实现的,顺利的话,它可以帮我们确定我们错在哪,但是很多情况下,知道错在哪是容易的,知道为什么错或者为什么对是不容易的。
如果一个注册验证的指令流为
a b c d
\ / \ /
e f
\ /
g h
\ /
i
现在i=0则注册成功,倒推数据流可以知道 b c d h是不变数据,a是用户输入,但是对于我们来说,仍然毫无意义, 除非能得到i = f(a)的关系,因为实际数据流要比这复杂得多,人脑推的话,和反一遍没什么区别,而要知道这个关系的话,关键在于如何建模循环,不知道楼主能有什么解决方案。
当然,能倒退数据流已经比没有要进了一大步。
能力值:
( LV3,RANK:20 )
74 楼
fshooter的担心非常有道理,现在我们面临的是这样一个问题,我在前面举了一个例子,说明当无用的数据从控制流上摘去或常量化后,就是i=f(a),这里我再贴一遍:
int a=1;
int b;
int c;
int e;
int d=1;
b=gets();
a=a+d;
c=a+b;
e=c;
c=a;
这个程序模拟一个简单的解密过程,我们看到b接收一个真正要解密的数据,然后在a是解密的key---2,解密的结果是c。
在解密后,程序试图擦除这个解密结果,所以在c中间塞了那个key,这个key在这里是当垃圾用了。
但是,按照我说的分析数据流的方法,程序是不会被迷惑的,因为它已经把e当做数据流的后继了,这条信息链没有中断。
这里的c其实只和b有关,因为a是固定的信息流的产物,我们的分析器是走一步看一步的它看到在
c=a+b这里的时候,还是把信息流分为两路,到e=c的时候,它创建了副本,形成了有用信息流的新分支。
当我告诉它去掉固定变形的时候,直接把c=a+b,改成c=2+b,这样,这个key就是明文了。 当然,这是一个非常非常小的实验,但足以说明我的观点,就是说,如果我们把一个流程中,无关的信息常量化后,剩下的流程就是连接条件和结果的过程。
一个数只是数,但一连串对数的操作是有状态的,而且这个状态必须连续不断的保存,
当作者在面对普通用户时必须提供统一的输入性能和结果时,他在中间插的这一杠子,必须自己插进去,自己拿出来,就好像那个很经典的三只碗扣一个骰子的游戏(我不知道叫什么)。
我的眼睛跟不上他的手的变化,这件事是非机器做不可了。
能力值:
( LV3,RANK:20 )
75 楼
嗯,感谢楼上各位能参与到对我的设想的讨论中来,现在我已经对程序的整体组织结构有了更清晰的认识。
原来我犯的的一个错误是试图同时去追两只兔子。这个错误的起因是对软件的需求没有做充分的认识,更深层次的原因是没有像《代2》上说的那样更好的用隐喻来理解开发。
现在这个错误已经得到了修正,我以前说的用编译器优化来脱壳,其实是我思维僵化的表现,正确的表达是应用编译器优化的手段。进行找矿,挖掘,分离和提炼。
找矿和挖掘正是脱壳的第一步,首先是找矿这个隐喻,壳把真正的程序掩埋在它的深处,但是它不得不留下一个矿苗和矿脉,否则这个矿就连它自己也找不到了。
使用编译器优化技术的静态单赋值配合上下文建立数据流是挖掘。这个可能要用到程序内部建立的局部片断的数据表和外部的整体的数据库。
提炼是用编译器优化的操作,比如精简多余指令,可以为分离提供基础。简单的说,就是汇编语言再次编译优化。
分离,是指壳的运行其实是和真正的矿脉不相关的,而它借用互为可逆的一对操作硬是勾搭在这条渠道上,所以我们要识别搭上去的这种操作,把它变成原来矿脉上的平行管道;
或者是加密的情况下,把解密运算中必经的步骤提取出来,把壳因子常量化,这样两者就分离了。 至于效率问题,要看怎么干了,我第一遍跟踪时不分析,只记录向下的指令序列,跳转方向等信息,主要是方便识别循环。
然后就脱离单步跟踪,单独进行指令分解和汇编语言再优化编译,分离。
接下来我要老老实实按核对表去做了,首先是明确定义主要构造块。