menu 牢记自己是菜
网鼎杯 re-joker smc动态程序加密与浅显的花指令与堆栈平衡
95 浏览 | 2020-05-25 | 阅读时间: 约 7 分钟 | 分类: 高校战“疫”网络安全分享赛 | 标签:
请注意,本文编写于 39 天前,最后修改于 39 天前,其中某些信息可能已经过时。

0x1 前言

上次写(水)完博客又过去了一周多,主要是因为有考试(懒),上周去参透马克思主义哲学了。emmmmm,鬼知道自己参透到什么程度。上周关于逆向还是好好研究了一下这道题目。这是网鼎杯的一道逆向题,虽然比赛过去了好久但是我还是心心念念这道题目好久。主要是因为里面涉及到了几个我认为蛮有意思的知识点(花代码与堆栈平衡),但是事实证明我的判断出现了严重的问题。这道题目其实根本就不涉及这两个问题。但是我还是比较认真(自夸)的研究了一下这两个问题与这道题目。不多说了,我们先来看一下这道题目与smc动态程序加密。


0x2 joker

拿到题目,我么先查一下壳,无壳。运行一下,发现这道题目应该是一个猜flag的题目。拿到关键信息,IDA反编译,OD挂载进程。找到主函数,这里主函数不能F5,我们只能看他的流程图。发现,主函数获取了你的一个输入,然后进行了wrong与omg的两个函数操作。

这两个函数是没有任何问题的,可以进行F5,看一下逻辑,大概是签到题目的加密水平,分奇数位与偶数位的两个加密。脚本解密。

这个flag不光看起来很假,如果注意的话,你会发现这个flag连位数都不够。这让我一下想起来XCTF里面的一道天津垓的题目,就是将关键的函数藏在了一些奇怪的地方。我们继续回到主函数,在函数列表里面我们发现了两个也很有意思的函数,一个是encrypt,一个是finally。但是这两个函数都没有办法F5出来,于是我第一时间想到的是OD,于是我在OD里面的0x401500处下一个断点,但是这个断点一下就报错,并且这里我看了一下汇编代码,简直惨不忍睹,连七八糟,这时我第一个反应就是混入了花代码,而IDA无法F5可能就是花代码引起的堆栈不平衡。但是事实上在我翻阅花代码(加密与解密)资料的过程中,发现紧接着花代码的一节就是SMC动态加密技术。猛然发觉这个程序和有可能时SMC动态加密。
所谓SMC(Self Modifying Code)技术,就是一种将可执行文件中的代码或数据进行加密,防止别人使用逆向工程工具(比如一些常见的反汇编工具)对程序进行静态分析的方法,只有程序运行时才对代码和数据进行解密,从而正常运行程序和访问数据。计算机病毒通常也会采用SMC技术动态修改内存中的可执行代码来达到变形或对代码加密的目的,从而躲过杀毒软件的查杀或者迷惑反病毒工作者对代码进行分析。
这种技术主要是针对静态分析,阻碍破解者对程序的静态分析。SMC的主要原理就是通过外部的程序或者自己本身的一段程序,将自己所需要的代码的字节码进行相应的加密,然后再主函数调用该段代码之前,对这段代码进行解密,使之可以正常运行。如果希望能加大破解人员的工作强度的话,可以使用多层的SMC对关键代码进行加密,从而达到延缓破解时间的目的。这种方式其实很想我们在学习脱壳时候的加密壳,在程序运行之前,需要对程序进行解密,而我们再脱加密壳的时候,只需要运行程序,在程序解密完成的时候将整个程序dump出来即可。SMC加密的程序字节码很容易会有一大部分,无法被IDA反汇编,从而形成一个数据段。

由于这个程序并没有设置反调试的手段,所以我们可以像脱壳一样,让程序自己执行完解密函数,然后我们在对代码进行相应的处理。我们找到关键解密函数。

这个仔细观察,发现它使用了movzx指令,这个指令真的不太常见,但是正巧我们正在学习微型计算机接口原理,这是一个段传输的汇编指令。而传输的位置正好就是0x401500处的字节码。我们将解密完成后就看见了真正的encrypt函数。

这里我们的选择就很多了,我们可以动态跟进看看到底干了什么,也可以dump出数据,在进入IDA中分析。由于汇编代码不难,我们可以动态的看一看。整个函数逻辑就是flag{*}与字符串“hahahaha_do_you_find_me?”进行异或,最后与内存处的一些数据进行比较。在代码处我们可以跟随内存地址获得校验用的数值。这里放上我们的脚本。

A =[0x66, 0x00, 0x00, 0x00, 0x6B, 0x00, 0x00, 0x00, 0x63, 0x00, 
  0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 
  0x61, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x64, 0x00, 
  0x00, 0x00, 0x3B, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 
  0x6B, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x7B, 0x00, 
  0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x00, 0x00, 
  0x50, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x5F, 0x00, 
  0x00, 0x00, 0x4D, 0x00, 0x00, 0x00, 0x5A, 0x00, 0x00, 0x00, 
  0x71, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x37, 0x00, 
  0x00, 0x00, 0x66]
B=A[::4]
for i in range(0,24):
    if (i&1)!=0:
        B[i]=B[i]+i
        print(chr(B[i]),end='')
    else:
        B[i]=B[i]^i
        print(chr(B[i]),end='')
H="hahahaha_do_you_find_me?"
for i in range(0,24):
    print((ord(H[i])^B[i]))

D=[0x0E,0x0D,0x09,0x06,0x13,0x05,0x58,0x56,0x3E,0x06,0x0C,0x3C,0x1F,0x57,0x14,0x6B,0x57,0x59,0x0D,0x01,0x03]
print(D)
for i in range(0,20):
    print(chr((ord(H[i])^D[i])),end='')
#print()
#print(0x3A^ord('}'))
Data=[0x25,0x74,0x70,0x26,0x3A]
for i in range(0,5):
    print(chr(Data[i]^71),end='')

代码没有优化,有点丑,但是意思到了。但是我们发现解密出来之后,明显不对啊,少位数了。

但是我们得到了最后的提示,finally里面一定有问题。

由于finally与之前的函数挨在一起,所以当时解密的时候将两个都解密了。看向这个函数,发现五个常量,结合falg最后一位一定是‘}’所以我们得到异或的数字是71,完成最后的解密。


到这里整道题目就算完全结束了。唯一我觉得有用的知识点就是SMC动态代码加密解密的这种方法。学习一下,下次再见到时,希望我可以快速识破。


0x3 花指令与堆栈平衡的浅显理解

虽然题目结束了,但是我还是要继续bb。要bb的内容显而易见,就是花指令与堆栈平衡。这里我只谈自己的理解,可能不够专业,但是在现阶段,在我已有的知识基础上,我已经可以说服自己了。如果有更好的理解,或者是我的理解那里存在问题,欢迎一起讨论。我参考资料也会留在最下面。
首先谈谈花代码,什么是花代码,在我看来花代码就是干扰破解人员阅读代码的“小花活”。首先一个程序一定要保证可以正常的运行,执行自己本应该执行的任务,不然这个程序就没有存在的必要。所以花代码再花,也不能影响到一个程序的正常运行。所以花代码只是开发人员用来骗IDA与OD这类反编译软件的,注意是软件,而不是计算机本身。那如何干扰反编译软件呢,反编译软件原理是将一堆恶心的字节码编译成汇编语言。如果我们干扰了反编译这个过程,就可以完成一个信息差的创建。即计算机可以运行,而人觉得代码有问题。如何做到这一点,通常反编译软件会确定一个指令的开始首个地址,然后根据这条指令是否需要数据,在对后续的字节码进行解释。当我们把一个数据(无用数据:db thunkcode)放在一个不应该出现的地方,使编译器认为,这个数据应该是一条指令的开始地址,导致将后面本应该是指令的字节码,劈断为数据或者翻译成其他的字节码,从而导致反汇编的混乱。由于计算机不会执行到这个数据,所以对计算机本身不会产生任何影响。
所谓的堆栈平衡是计算机里面对调用函数,所建立的堆栈进行控制的原则。如果寄存器够用,我们就不会使用到堆栈,但是有些时候,我们要处理的数据太多了,所以我们要把数据放入我们临时开辟的堆栈中,随着函数的调用结束,堆栈也随着会被销毁。就好比一个人,吃完饭要收拾桌子一样,不收拾干净后面的人就没有办法使用这张桌子。在计算机也一样,我们要保证我们开辟了多大的堆栈,结束时就要销毁多大的堆栈。不能多也不能少,不然可能会造成数据的丢失。那何为数据不平衡呢,一般来说我们用的高级语言,基本在正常情况下,不会出现堆栈的不平衡,但是如果我们想让IDA出现不平衡的情况,我们应该怎末做呢。这里提供一种最简单的方法,假如我们想(call app)调用一个函数我们一般情况下应该会使用call指令,但是我们还可以骚一点。假若你明白调用函数的规则的话,应该知道,在我们要调用一个函数的时候,我们会先将这个call后面的一条指令的地址,压入栈中,在call结束后,使用return指令将这个栈内的地址送入esp,使指令再继续的执行下去。所以我们可以使用连续的push指令压入跳转地址,紧接着进行一次return操作,这样我们也完成了跳转(此时应该新开辟一个栈)。但是在IDA眼里,return代表的是函数结束,他就要销毁这个栈,但是他突然发现,卧槽不平衡啊,他就报错了。这就是简单的堆栈不平衡。


0x4 结语

这次算是搞明白了花代码与堆栈的基本原理,还是蛮有收获的,之后遇到问题在进行具体的处理吧。另外实名感谢新荣师傅,帮我装了反IP的插件,屏蔽了“国际卖片哥”的评论轰炸。真的是,不知道为啥就被卖片哥盯上了,搞得我好像平时一天到晚不敢正事一样。顺带一提,友链里面新加入了新荣师傅的博客,新朋友get!!

发表评论

email
web

全部评论 (共 2 条评论)

    2020-05-26 22:31
    原来想过来看看麦片哥的评论,没看到,大失所望
      2020-05-28 19:16
      @iyzyi老色批了 (๑•̀ㅁ•́ฅ)