CTF中逆向的一些经验

  XCTF联赛小秘       2019-03-06 09:50:05 2617  7

0x00 判断文件类型

我们拿到的题目一般是一个二进制可执行文件,而其中的大部分不是Windows平台下的PE文件就是Linux平台下的ELF文件。不管题目中所给文件带有什么扩展名,我们都应该亲自去检查文件头,使用任意文本编辑工具打开这个二进制文件,如果在开头看到MZ字样,那基本上可以确定这是一个Win系统下的可执行文件,相应的如果看到ELF字样即可基本断定这是一个运行于Linux系统下的可执行文件,扩展名为exe而实质上是一个ELF文件,这种情况在以往的CTF比赛中也是出现过的。


0x01 查壳与脱壳

由于近年来CTF比赛中带壳的逆向题出现频率稍有增加,在静态分析之前,进行查壳是非常有必要的,常用的工具有PEID等,进行查壳后,如果是简单且常见的壳如UPX等,那么直接可以使用工具完成脱壳,如果一时半会找不到可用的工具,也可以尝试手工脱壳,例如ASPack就能够采用ESP定律来脱除。如果是侧重考察脱壳的题,一般程序本身就比较简单了,可以这样说,这类题如果能成功脱壳,基本上也算是做出来了。当然,脱壳并不能解决所有问题,之前有CTF比赛中甚至出现了VMP的题目,对于这种情况,还是建议使用调试器去动态调试,毕竟在逆向工程中太依赖IDA的F5功能也是不行的,还是应该锻炼自己的汇编指令阅读能力。如果是VMP比较新的版本,可能还需要使用Sharp OD等插件来隐藏调试行为。


0x02 静态分析

一般是直接将二进制文件拖入IDA进行分析,在这个过程中,可以关注一下是否有符号库路径的提示出现,如果有这种情况,说明程序在编译阶段并没有清除相应的符号信息,我们或许能够从符号库路径的命名中得知一些关于本题的重要信息,大多数时候,静态分析完成后,我们会直接来到程序start处,直接F5,向下找即可来到main函数,直接查看反编译出来的C伪码,然而有时候IDA会出现识别不了函数的情况,比如说分析到某一段代码,视图就从C伪码变为汇编指令视图了,这时候可以尝试手工地在这段汇编指令中识别出函数,并且使用IDA的创建函数功能,这样一来就能够继续查看C伪码,提高我们的做题效率

 

如何从一段汇编代码中把函数找出来?

首先得找到函数头部,很多函数的头部看起来会如下所示:

push    ebp

mov     ebp, esp

先将调用者的堆栈的基址(ebp)入栈,以保存之前任务的信息。

然后将调用者的栈顶指针(esp)的值赋给ebp,作为新的基址(即被调用者的栈底)。

然后我们继续寻找函数尾部

pop ebp

ret

需要注意的是,如果函数不只有一条路径返回值,我们还得确保函数尾部下方的跳转全都朝下,上方的跳转全部朝上

 

要是在IDA中遇到Sp analysis failed怎么办?

这种情况,有时候是由于某些指令的干扰导致IDA分析出来的栈平衡被破坏所致,会导致IDA生成C伪码失败

我们需要手动地调整栈平衡,在IDA中依次点击Options-General,勾选Stack pointer

函数刚开始的指令看起来如下图所示:

image.png

函数结束前的指令如下图:

image.png

可以看到每一条指令的前面增加了一个数字,这个数字即指示栈顶指针(SP),在调用带参数的Call后,push和pop或者进行抬栈、清栈后栈顶指针才会改变,上图的mov esp,ebp后SP值显然不合理,应该使用Alt+K快捷键进行调整,调整后如下图所示:

image.png

这时再使用F5,IDA已经能够生产正确的C伪码了


0x03一些常见题型

1. 方程组(HCTF2017逆向第一题)

对输入做了异或



本题中三个方程的关键代码


爆破解法:

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)

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)

Z3是一款由微软开发的约束求解器,其代码目前托管在https://github.com/Z3Prover/z3

如果题目的方程比较简单,使用爆破当然简单粗暴,方便快捷;但当题目涉及到多元方程或者方程组时,使用Z3求解是比较高效率的,强烈推荐。


Tags: C伪码IDApeidSP值UPXVMPZ3约束器代码关键代码判断文件类型工具效率查壳栈平衡栈顶指针汇编汇编指令爆破解法符号信息脱壳视图调试行为静态分析


稿件转自:安全脉搏   作者:China H.L.B


请先登录
+1 已点过赞
7
分享到:
登录后才能发贴或参与互动哦! 点击登录

全部评论 (1)

Grit 2019-04-03 11:12:39
xiexie
回复
请先登录 0 +1 已点过赞