0x00 判断文件类型 [color=rgba(0, 0, 0, 0.8)]我们拿到的题目一般是一个二进制可执行文件,而其中的大部分不是Windows平台下的PE文件就是Linux平台下的ELF文件。不管题目中所给文件带有什么扩展名,我们都应该亲自去检查文件头,使用任意文本编辑工具打开这个二进制文件,如果在开头看到MZ字样,那基本上可以确定这是一个Win系统下的可执行文件,相应的如果看到ELF字样即可基本断定这是一个运行于Linux系统下的可执行文件,扩展名为exe而实质上是一个ELF文件,这种情况在以往的CTF比赛中也是出现过的。
0x01 查壳与脱壳[color=rgba(0, 0, 0, 0.8)]由于近年来CTF比赛中带壳的逆向题出现频率稍有增加,在静态分析之前,进行查壳是非常有必要的,常用的工具有PEID等,进行查壳后,如果是简单且常见的壳如UPX等,那么直接可以使用工具完成脱壳,如果一时半会找不到可用的工具,也可以尝试手工脱壳,例如ASPack就能够采用ESP定律来脱除。如果是侧重考察脱壳的题,一般程序本身就比较简单了,可以这样说,这类题如果能成功脱壳,基本上也算是做出来了。当然,脱壳并不能解决所有问题,之前有CTF比赛中甚至出现了VMP的题目,对于这种情况,还是建议使用调试器去动态调试,毕竟在逆向工程中太依赖IDA的F5功能也是不行的,还是应该锻炼自己的汇编指令阅读能力。如果是VMP比较新的版本,可能还需要使用Sharp OD等插件来隐藏调试行为。
0x02 静态分析[color=rgba(0, 0, 0, 0.8)]一般是直接将二进制文件拖入IDA进行分析,在这个过程中,可以关注一下是否有符号库路径的提示出现,如果有这种情况,说明程序在编译阶段并没有清除相应的符号信息,我们或许能够从符号库路径的命名中得知一些关于本题的重要信息,大多数时候,静态分析完成后,我们会直接来到程序start处,直接F5,向下找即可来到main函数,直接查看反编译出来的C伪码,然而有时候IDA会出现识别不了函数的情况,比如说分析到某一段代码,视图就从C伪码变为汇编指令视图了,这时候可以尝试手工地在这段汇编指令中识别出函数,并且使用IDA的创建函数功能,这样一来就能够继续查看C伪码,提高我们的做题效率
[color=rgba(0, 0, 0, 0.8)]如何从一段汇编代码中把函数找出来? [color=rgba(0, 0, 0, 0.8)]首先得找到函数头部,很多函数的头部看起来会如下所示: [color=rgba(0, 0, 0, 0.8)]push ebp [color=rgba(0, 0, 0, 0.8)]mov ebp, esp [color=rgba(0, 0, 0, 0.8)]先将调用者的堆栈的基址(ebp)入栈,以保存之前任务的信息。 [color=rgba(0, 0, 0, 0.8)]然后将调用者的栈顶指针(esp)的值赋给ebp,作为新的基址(即被调用者的栈底)。 [color=rgba(0, 0, 0, 0.8)]然后我们继续寻找函数尾部 [color=rgba(0, 0, 0, 0.8)]pop ebp [color=rgba(0, 0, 0, 0.8)]ret [color=rgba(0, 0, 0, 0.8)]需要注意的是,如果函数不只有一条路径返回值,我们还得确保函数尾部下方的跳转全都朝下,上方的跳转全部朝上
[color=rgba(0, 0, 0, 0.8)]要是在IDA中遇到Sp analysis failed怎么办? [color=rgba(0, 0, 0, 0.8)]这种情况,有时候是由于某些指令的干扰导致IDA分析出来的栈平衡被破坏所致,会导致IDA生成C伪码失败 [color=rgba(0, 0, 0, 0.8)]我们需要手动地调整栈平衡,在IDA中依次点击Options-General,勾选Stack pointer [color=rgba(0, 0, 0, 0.8)]函数刚开始的指令看起来如下图所示: [color=rgba(0, 0, 0, 0.8)]函数结束前的指令如下图: [color=rgba(0, 0, 0, 0.8)]可以看到每一条指令的前面增加了一个数字,这个数字即指示栈顶指针(SP),在调用带参数的Call后,push和pop或者进行抬栈、清栈后栈顶指针才会改变,上图的mov esp,ebp后SP值显然不合理,应该使用Alt+K快捷键进行调整,调整后如下图所示: [color=rgba(0, 0, 0, 0.8)]这时再使用F5,IDA已经能够生产正确的C伪码了
0x03一些常见题型[color=rgba(0, 0, 0, 0.8)]1. 方程组(HCTF2017逆向第一题) [color=rgba(0, 0, 0, 0.8)]对输入做了异或
[color=rgba(0, 0, 0, 0.8)]本题中三个方程的关键代码
[color=rgba(0, 0, 0, 0.8)]爆破解法: def solve(idx1,idx2,idx3,idx4):
for x in range(0,255):
if(((x ^ idx1 )& idx2 )>> idx3) | (((x ^ idx1) << idx3 )& idx2) == idx4:
print(chr(x^0x76), end='')
key1 = [0x6f,0xdd,0xdd,0x48,0x64,0x63,0xd7]
for ele in key1:
solve(0xAD,0xAA,0x1,ele)
key2 = [0x2e,0x2c,0xfe,0x6a,0x6d,0x2a,0xf2]
for ele in key2:
solve(0xbe,0xcc,0x2,ele)
key3=[0x6f,0x9a,0x4d,0x8b,0x4b,0xfa,0x1a]
for ele in key3:
solve(0xef,0xf0,0x4,ele) [color=rgba(0, 0, 0, 0.8)]Z3约束器解法: from z3 import *
def z3_solve(idx1,idx2,idx3,idx4):
solver = Solver()
x = BitVec('x', 32)
solver.add((((x ^ idx1 )& idx2 )>> idx3) | (((x ^ idx1) << idx3 )& idx2) == idx4)
if solver.check() == sat:
print(chr(solver.model()[x].as_long() ^ 0x76), end='')
key1 = [0x6f,0xdd,0xdd,0x48,0x64,0x63,0xd7]
for ele in key1:
z3_solve(0xAD,0xAA,0x1,ele)
key2 = [0x2e,0x2c,0xfe,0x6a,0x6d,0x2a,0xf2]
for ele in key2:
z3_solve(0xbe,0xcc,0x2,ele)
key3=[0x6f,0x9a,0x4d,0x8b,0x4b,0xfa,0x1a]
for ele in key3:
z3_solve(0xef,0xf0,0x4,ele) [color=rgba(0, 0, 0, 0.8)]Z3是一款由微软开发的约束求解器,其代码目前托管在https://github.com/Z3Prover/z3 [color=rgba(0, 0, 0, 0.8)]如果题目的方程比较简单,使用爆破当然简单粗暴,方便快捷;但当题目涉及到多元方程或者方程组时,使用Z3求解是比较高效率的,强烈推荐。 [color=rgba(0, 0, 0, 0.8)]
[color=rgba(0, 0, 0, 0.8)]Tags: C伪码、IDA、peid、SP值、UPX、VMP、Z3约束器、代码、关键代码、判断文件类型、工具、效率、查壳、栈平衡、栈顶指针、汇编、汇编指令、爆破解法、符号信息、脱壳、视图、调试行为、静态分析 [color=rgba(0, 0, 0, 0.8)]
[color=rgba(0, 0, 0, 0.8)]稿件转自:安全脉搏 作者:China H.L.B |