前言

这题本来不想详细的说,因为我解此题用的比较笨的方法,但这也不失为一种方法,赛后反思总结了一下,觉得在逆向调试和分析的技巧上可以有所提高,并且最后也会提及如何在自己的程序中加入llvm混淆。

Strange Interpreter

通过file指令可知是一个64位,linux动态编译的程序,载入IDA可以看到程序混淆的十分严重。

不过和叹息之墙比起来,还差的远呢。
通过查看字符串,可以知道flag的长度为32,并且看到一串明文。

这段明文放在这里肯定会被使用到,交叉引用可以来到loc_412164基本块中

strings查看字符串。

可以知道程序使用Obfuscator-LLVM 4.0.1编译的。

但是现在我们并不能看出什么东西,正常的逻辑是这样的,不过要是感觉敏锐一点,byte_613050是最后用来校验的数据,ds:dword_6130D0是经过加密变换后的数据,然后我们在分析时就可以抓住ds:dword_6130D0关键线索进行查看,但这篇文章中我想说明并不是这个,而是一点提升动调效率的技巧,所以接下来我们还是从main函数开始我们的分析。

开始

IDA是个神器,我们应该好好利用。IDA在分析完程序后,会显示CFG控制流程图,每个基本块之间的关系我们可以清楚的看到。
总揽一下代码可以看到这样那样的花指令以及使用了控制流平坦化的混淆方式,这也已经很常见了。

动态调试

假设大家都不知道llvm混淆的程序是怎样的,我们从loc_400655基本块开始看起。我们需要时刻盯着我们的输入。

不断的f8我们便可以来到loc_400979基本块中,其实可以发现全程只有jmpjz两条跳转指令,从上到下的中间过程我们是无法修改的,也就是说在这个过程中,并没有进行改变程序流程的校验。了解到这一点之后我们便可以在每个分支的最后一个基本块的下断,而后便可以F9运行到断点处,从而节省一部分时间。继续回到这个块中。

可以看到它的功能是将输入的第一个字符取出,并存入到ds:dword_6130D0中,同时ecx作为下标,也就是ds:index(已重命名,最后我会附上idb文件),可以想到后续必然是会逐个获取我的输入,并将其拷贝到ds:dword_6130D0中,此时我们便可以 F9 运行到下一个loc_4009A9基本块中,很明显是为了将ds:index++。

同样的 F9 可以来到loc_40095C,比较ds:index是否大于0x20

其实这时已经比较明显了,但我们仍需要验证一下,再次F9再次来到loc_400979中,之后便是一个循环,此时我们可以取消先前设置的三个断点,再次F9,注意数据部分的变换,最后运行到loc_4009C6,由于代码块太大,IDA无法使用CFG图将其展示出来。

此时的ds:dword_6130D0如下:

loc_4009C6代码块非常的大,如果单步调试可能真的需要半天时间,因此我们需要找到重复的部分,我们紧盯着ds:dword_6130D0这部分数据,这是切入点,前面一大段在进行复制,可以通过F4运行到光标处,进行快速的调试,代码重复的部分便可一快速的跳过。
接下来的一大片代码都在增加index的值,我们可以快速的滑动滚轮略过,直到40F13D,出现大片奇怪的字符。

单步跟一会可以发现,程序将这些字符复制到了ds:dword_6130D0偏移index*4的位置

同样略过重复代码,之后是大量的index--,也就是

而后同样的又是将一块数据复制到ds:dword_6130D0偏移0x1d0*4处。最后来到0x411A09

有一个很明显的xor操作,此时通过查看eax 和 rcx可知是将输入同刚刚初始化的数据进行xor,我们也可以进行一个反向验证,这很容易,因为xor是可逆操作。所以此部分代码可以略过,最后可以看到我们的输入变化如下:

只有前0x10字节发生了变化,因此我们便可以得到第一部分的解密脚本。

我想要讲的到这里已经差不多了,在带混淆调试时,我们不可能完全的单步调试,必须有选择的略过一些无用代码,这可以提高我们的效率,其实做逆向,看完题目后心中应该大致有一个方向,我们需要做的是去验证自己的想法是否正确,带着一定的目的进行调试,而不是走一步看一步,反而做了大量的无用功。

接下来的过程和第一部分差不多,至少方法上是一样的,因此我也不愿在这里做无用功,以上就是我的一点技巧,希望能给大家一些帮助,也希望同学们能动手调试一遍,感受其中的乐趣,感受逆向工程的美丽。在文末我会附上我的idb,仅供参考!

最后的校验部分如图:

解密脚本如下:

dic = '012345abcdefghijklmnopqrstuvwxyz'
dic_list=list(dic)
xor1=[0x68,0x1C,0x7C,0x66,0x77,0x74,0x1A,0x57,0x06,0x53,0x52,0x53,0x02,0x5D,0x0C,0x5D]


xor2=[0x04,0x74,0x46,0x0E,0x49,0x06,0x3D,0x72,0x73,0x76,0x27,0x74,0x25,0x78,0x79,0x30]

xor3=[0x68,0x1C,0x7C,0x66,0x77,0x74,0x1A,0x57,0x06,0x53,0x52,0x53,0x02,0x5D,0x0C,0x5D]

print len(xor1)
flag1=""
for i in range(16):
    flag1+=chr(xor1[i]^ord(dic[i]))
print flag1
flag2=""
for i in range(16):
    j=i+16
    flag2+=chr(xor2[i]^ord(dic[j])^xor3[i]^ord(dic[i]))
print flag2
print flag1+flag2

使用llvm编译自己的程序

obfuscator-llvm

$ git clone -b llvm-4.0 https://github.com/obfuscator-llvm/obfuscator.git
$ mkdir build
$ cd build
$ cmake -DCMAKE_BUILD_TYPE=Release ../obfuscator/
$ make -j7

ubuntu下照着官方的流程来一遍就可以了。

使用如下方式进行编译:
$ path_to_the/build/bin/clang test.c -o test -mllvm -sub -mllvm -fla

这样我们便可以自己给自己出道llvm的题目了,是不是很刺激呢。

总结

其实解决这道题目的方法有很多,这里只是想分享一下自己的一些小思路,希望自己在以后遇到需要头铁分析的题目时能善于利用技巧,提高逆向效率。最后也欢迎大家一起交流。

附件链接:https://pan.baidu.com/s/1hsf1fkIdwDiMiXBY6J76KQ 密码:kvs8

源链接

Hacking more

...