玩转YY老师服务器(上)之IDA反编译伪代码还原插件编写

玩转YY老师服务器(上)之IDA反编译伪代码还原插件编写

时隔两年,转眼间发现自己已经大三了。YY老师的服务器我大一进校的时候就一直在玩,但之前都没有在博客上写过,熟悉我的朋友都知道我主要是大一的时候在搞,我不仅不在上面做题,还有事没事就去挖他服务器上的漏洞,当时觉得贼有意思。不过都是过去的事了,过了两年,再去看他的服务器,似乎又能玩出新花样了。有了骚的思路直接干就好了,所以就有了这篇文章。

建议大家使用IDA 7.0以上版本,本文插件基于IDA7.0编写。

不熟悉的朋友可能还不知道YY老师是谁,他是教我们C语言和数据结构的老师,人非常好,我也很尊敬的一位老师。平时我们都叫他杨勇老师,不过我搞事情的时候都喜欢用YY老师来代替称呼。至于YY老师的服务器,就是他自己弄的一个给学生练习C和C++的程序设计的网站。也就是上面的那个。

网站的内容大概就长这个样子,其实就是很多的基础的算法题,直接提交代码即可。因为我一直都是玩的Python,之前也没怎么在上面刷题,最喜欢的事就是挖洞,虽然没拿到他服务器的shell,不过还是挖的挺开心,不过以前玩都是挖一些web的漏洞。但是只从入了逆向的坑之后,发现骚思路真的是越来越多了。

通常大家无非就是在上面刷刷题什么的。因为yy老师每道题都是给了下载例程的,也就是每道题他自己都写过,然后上传编译好的exe给我们拿来做测试用的。不过我猜很多人都没有试过这些例程,基本上都是自己写了代码直接粘贴上去,看是否能通过就完了。

我也不知道是那天闲的蛋疼,看着这么多的下载例程,就随便下载了几个,随手拖进了IDA,想想YY老师也不可能加壳什么的,直接干就完事了。F5之后发现伪代码的逻辑是如此的清晰。我就直接把伪代码的主要逻辑部分复制出来,按照C++代码的格式改了改就直接提交上去了,结果还真的通过了,有点意思。这些反编译出来的伪代码差不多就是YY老师自己写的代码。

所以就想着那我自己能不能写个IDA的插件来做这件事情呢(因为大家都知道IDA是支持Python的),就是批量的将反编译后的伪代码提取出来,然后按照C++的代码格式还原成源码呢?如果可以的话,又是一波骚操作了。

然后就开始了,我好几天晚上睡觉都在思考这个事情。逐渐有了思路,但是其中也真的遇到了不少的问题,我就说一下这些问题是如何解决的。因为IDAPython提供了很多方便调用IDA的接口,这些类和函数可以直接操作IDA,所以编写插件也变得相当容易。由于IDAPython的中文文档很少,所以我大部分还是直接看的IDApython官网的介绍。我整个插件的编写也基本全靠参考官方文档写出来的。

https://www.hex-rays.com/products/ida/support/idapython_docs/

首先反编译这个事情肯定是交给IDA去做的,但我是为了拿到伪代码,也就是IDA反编译完成后的按F5生成的东西。

但我最大的问题就是如何得到伪代码。一开始我一直以为F5生成伪代码的功能是IDA本身的,查了很多资料后才知道那其实是hexrays这个插件来实现的,虽然和IDA都是一家的。但是我有些懵逼,既然他也是插件,那么我自己写的插件是不是可以调用其他插件的功能呢?

所以我想的就是IDA是否可以将伪代码批量导出?但是万事开头难。

虽然网上之前也有人问过这个问题。但是看了别人的解答后发现根本就没人提供实际可行的办法,都在那里说自己是直接CV的。Emm...那要是直接复制粘贴,我还问你干嘛??关键时候还得靠自己想办法。

最终在网上搜索未果,因为伪代码导出不了后面的问题都无法解决。所以我还是打算直接去看hexrays这个插件的源代码。先找到这个插件的目录。

我就仔细读了一下readme.txt里面关于插件的介绍以及几个sample的作用。

也就是提供的第一个sample就是用来生成当前函数的伪代码,不过后面还有很重要的一点就是确保加载顺序,如果用到hexrays插件,他必须提前被加载,这也是我在后面写自动化脚本的时候遇到的一个坑。

然后再去读sample1的源码,顿时豁然开朗。

其实最核心的就是那个idaapi.decompile()函数,他接收的参数就是当前的地址。

不了解的朋友可以自己加载一下这些sample试试,就能直接输出当前函数的伪代码。快捷键是shift+f2。

加载了插件代码后运行后,就能在output窗口看到伪代码了。

说实话当时我看到输出伪代码的时候,别提有多激动了。

既然伪代码导出的问题解决了那么接下来就可以直接编写我们的插件代码了。这里呢我就先直接贴出我的代码。因为IDAPython只支持Python2,所以我就用2的语法来写的。

import re
import idaapi
 
idaapi.load_plugin('hexrays')
Wait()
 
def dc(func):
    func = idaapi.get_func(get_name_ea_simple(func))
    return str(idaapi.decompile(func))
 
def search_sub(dec):
    sub_name = dec
    if isinstance(dec,str):
        dec = dc(dec)
    try:
        use = re.findall('sub_[0-9A-F]{6}',dec)
        for x in use:
            if sub_name == x:
                continue
            if x not in sub_use:
                sub_use.append(x)
                search_sub(x)
            else:
                continue
    except Exception as e:
        print e
 
def rep(code):
    r_dict = {'__cdecl':'','__thiscall':'','__fastcall':'','BOOL':'bool','printf("\n");\n':'',
              'int argc, const char **argv, const char **envp':'','LODWORD':'int',
              'system("pause");\n':'','_DWORD':'unsigned long'}
    for i in r_dict.items():
        code = code.replace(i[0],i[1])
    return code
 
sub_use = ['_main']
search_sub('_main') 
code_list = map(dc,sub_use)
code_list = map(rep,code_list) 
output = GetInputFile().split('.')[0]+'.cpp'
 
f = open(output,'a')
head = ['stdio.h','math.h','iostream','cmath','string.h','malloc.h']
for i in head:
    f.write('#include<{}>\n'.format(i))
for i in code_list[::-1]:
    f.write('\n'+i+'\n')
f.close()
print 'decompile success!'
#Exit(0)

这段插件的代码只有50行,就能实现ida简单的伪代码还原。我简单分析一下这段代码的功能。

idaapi.load_plugin('hexrays')

Wait()

首先在脚本运行时先加载hexrays插件,这个在命令行模式运行ida时非常重要,不然会引发脚本报错。wait()是为了等待ida反编译完成后再执行后面的代码。

这里我写了dc,search_sub,rep三个主要的函数。这段代码中只import了idaapi,其他的函数是直接使用的,因为在IDA中idc和idautils这两个模块是默认被导入了的,可以直接调用函数。

get_name_ea_simple():是根据函数名获取函数地址

idaapi.get_func(): 这个函数来获取函数的边界地址(起始和结束地址)

idaapi.decompile():就是得到伪代码的函数

GetInputFile():获取当前反编译文件的文件名

在我写的函数中search_sub是最主要的,他本身是一个递归函数,通过正则匹配寻找函数中调用到的sub_*()这类我们自己写的函数。将所有被调用的sub函数名放入列表中。

dc是一个由函数名得到伪代码的函数。rep本质就是一个替换函数,因为ida中一些关键字拿给C++编译器是无法识别的,所以我用了字典来实现键值对即原值和替换值得对应,再遍历这个字典实现replace。

下面用了两次map函数来做映射,把所有的伪代码放到了一个列表中。再把可能用到的头文件放到head列表里。至于后面的for i in code_list[::-1]:这里很细节。

因为我们是用_main作为入口,所以他的索引是0。但是将伪代码写入文件的时候,main函数是需要放在最后面的,所以我用了[::-1]这个操作来对列表进行了倒序。

最后的Exit(0)被我注释掉了,这个看情况使用,如果是在命令行模式下使用IDA,就一定要记得加上,不然无法退出IDA的窗口。

我对这个插件的代码解释就差不多了。下面根据YY老师的程序来演示一遍。我们拖一个他的例程到ida里面。

反编译完成后会停留在_main函数的入口处,此时f5得到伪代码,发现他实际是调用sub_401000,所有的代码逻辑都是放在sub里的,经过我大量的观察发现,可能是YY老师的编程风格,他的代码都是这样写的。平时我们写C的代码,习惯于将一堆直接写到main函数里。不过YY老师这样也很好,有迹可循。我插件也把这里当做入口,把这里当做mian函数即可,少了很多麻烦。

 

现在就可以运行我们的插件代码了。

运行没有报错即生成了C++代码。大概就像这样。然后在devcpp里编译一下,没有报错就说明我们插件完美运行了。当然对于简单的代码基本没什么问题。但是复杂的代码用插件还原后还是需要自己debug一些错误才行。不过总的来说还是很不错了。

 

现在再把YY老师自己写的代码提交到他自己的网站上,哈哈。

这也太快乐了吧!

zgao

如果有什么技术上的问题,可以加我的qq 1761321396 一起交流。