论文复现_Asteria: Deep Learning-based AST-Encoding for Cross-platform Binary Code Similarity Detection

在Github上下载论文的代码,并对代码进行分析。这些代码可以实现三个功能,(1)二进制代码的 AST 的生成和预处理;(2)Tree-LSTM 模型的训练;(3)执行漏洞搜索任,代码的目录结构如下图所示。

1. AST 生成功能

进入项目的 ASTExtraction 目录,分析 Asteria 的 AST生成功能。根据帮助文档可知,该功能有两种不同的实现,分别是插件实现和命令行实现,代码的目录结构如下图所示。

首先分析 AST 生成功能的命令行实现,在命令行中输入下面的代码:

# IDA Pro 路径修改为自己的路径
python .\API_ast_generator.py --ida_path C:\Users\22845\Desktop\IDA_Pro_7.7\idat.exe --binary .\vul_database\openssl-1.0.1s --database .\vul_database\vul.sqlite --function EVP_EncodeUpdate

通过 python 中的 pdb 对代码的主要功能进行分析,代码首先会从命令行中解析输入的参数,并将对应的参数打印出来,具体参数如下所示。

Namespace(binary='.\\vul_database\\openssl-1.0.1s', compilation='default', database='.\\vul_database\\vul.sqlite', directory=None, function='EVP_EncodeUpdate', ida_path='C:\\Users\\22845\\Desktop\\IDA_Pro_7.7\\idat.exe', logfile='api_ast_generator.log', timeout=360)

通过输入的参数初始化 AstGenerator 对象,如果 args.directory 为True,则使用 AstGenerator 对象的 extract_ast_from_dir 方法提取对应目录二进制文件的 AST。args.binary 与 args.function 为 True 则使用该对象的 extract_function_ast 方法提取对应二进制文件的AST,具体代码如下所示。

# 创建 AstGenerator 对象,用于提取目标目录或目标文件的 AST
ag = AstGenerator(args)

# 如果 args.directory 为 True 则提取目标目录的 AST
if args.directory:
    ag.extract_ast_from_dir(args.directory)

# 如果 args.binary 与 args.function同时为 True 提取目标文件的 AST
elif args.binary and args.function:
    ag.extract_function_ast(args.binary, func_name=args.function)

根据参数的解析结果binary='.\\vul_database\\openssl-1.0.1s' 与 function='EVP_EncodeUpdate',可确定程序会使用 AstGenerator 对象的 extract_function_ast 方法生成二进制文件的 AST,其具体实现如下。

def extract_function_ast(self, binary_path, func_name): # the described function 2
    # 定义了一个名为 IDA_ARGS 的列表,其中包含了一些 IDA Pro 命令行参数。
    # 这些参数用于指定编译目标、数据库路径等。
    IDA_ARGS = ["-o %s" % self.args.compilation,  "-g gcc", "-d %s" % self.args.database]
    # 如果用户指定了要分析的函数名,则将其添加到 IDA_ARGS 列表中。
    if self.args.function:
        IDA_ARGS.append("-f %s" % self.args.function)
    # 将 IDA_ARGS 列表中的参数拼接成一个字符串。
    IDA_ARGS = " ".join(IDA_ARGS)
    # 根据操作系统的类型(Linux 或其他),设置适当的环境变量。
    Header = ""
    if platform == 'linux':
        Header = "TVHEADLESS=1"
    # 构建命令行参数列表,包括 IDA Pro 可执行文件路径、命令行参数以及要分析的二进制文件路径。
    cmd_list = [Header, self.args.ida_path, "-c" ,"-A", "-Lida.log" , '-S"%s %s"' % (self.Script, IDA_ARGS), binary_path]
    # 将 cmd_list 列表中的参数拼接成一个字符串。
    cmd = " ".join(cmd_list)
    # 使用 subprocess.Popen 执行该命令
    p = subprocess.Popen(cmd, shell =True)
    try:
        # 设置了一个超时时间,如果命令执行超时,将记录错误日志并终止进程。
        p.wait(timeout=self.args.timeout) # after waiting , kill the subprocess
    except subprocess.TimeoutExpired as e:
        self.logger.error("[Error] time out for %s" % binary_path)
    # 如果命令执行成功(返回码为 0),则记录日志信息;否则,记录错误日志信息并终止进程。
    if p.returncode != 0:
        self.logger.error("[ERROR] cmd: %s" %(cmd))
        if p:
            p.kill()
    else:
        self.logger.info("[OK] cmd %s " % cmd)

执行上述方法,通过命令行调用 ast_generator.py 实现目标二进制文件 AST 的生成,命令行如下所示。

' C:\\Users\\22845\\Desktop\\IDA_Pro_7.7\\idat.exe -c -A -Lida.log -S"C:\\Users\\22845\\Downloads\\Asteria-master\\ASTExtraction\\ast_generator.py -o default -g gcc -d .\\vul_database\\vul.sqlite -f EVP_EncodeUpdate" .\\vul_database\\openssl-1.0.1s'

在上述指令中 “C:\\Users\\22845\\Desktop\\IDA_Pro_7.7\\idat.exe” 是IDA Pro的可执行文件路径,“-c”标志表示在控制台模式运行指令,“-A”标志表示执行自动脚本(批处理模式),“-Lida.log”指定日志文件为ida.log,“-S"C:\\Users\\22845\\Downloads\\Asteria-master\\...."”指定启动后要执行的脚本。这个指令比较长,其单独分析如下:

  • “C:\\Users\\22845\\Downloads\\Asteria-master\\ASTExtraction\\ast_generator.py”是自定义python脚本的路径。
  • “-o default”表示脚本的输出格式。
  • “-g gcc”指定GCC编译器为目标。
  • “-d .\\vul_database\\vul.sqlite”表示脚本的输出路径。
  • “-f EVP_EncodeUpdate”指定需要处理的函数的名称

“.\\vul_database\\openssl-1.0.1s”指定被被分析的文件或目录。通过上述指令不难发现,二进制代码 AST 的生成依赖于 “st_generator.py” 文件,该文件的 main 函数如下所示。

if __name__ == '__main__':
    # 处理命令行参数
    import argparse
    ap = argparse.ArgumentParser()
    # 添加编译优化级别相关的命令行参数
    ap.add_argument("-o","--optimization", default="default", help="optimization level when compilation")
    # 添加待处理函数相关的命令行参数
    ap.add_argument("-f","--function", default="", help="extract the specific function info")
    # 添加编译器名称相关的命令行参数
    ap.add_argument("-g","--compiler", default="gcc", help="compiler name adopted during compilation")
    # 添加数据库相关的命令行参数
    ap.add_argument("-d","--database", default="default.sqlite", type=str, help="path to database")
    # 解析传递给脚本的命令行参数,并存储在 args 对象中。
    args = ap.parse_args(idc.ARGV[1:])
    # 创建 AstGenerator 对象,传入参数 optimization 和 compiler。
    astg = AstGenerator(args.optimization, compiler=args.compiler)
    # 调用 astg 对象的 run 方法
    astg.run(astg.get_info_of_func, specical_name=args.function)
    # 创建 DBOP 对象,传入数据库路径参数 database。
    dbop = DBOP(args.database)
    # 将分析结果保存到数据库
    astg.save_to(dbop)
    # 刷新数据库
    del dbop # free to call dbop.__del__() , flush database
    # 退出程序
    Exit(0)

通过 main 函数可以推断 “st_generator.py” 文件的主要功能是,(1)接收命令行参数;(2)根据参数分析二进制文件;(3)将分析结果保存到指定数据库。其中,astg 对象的 run 方法负责分析二进制文件,其代码如下:

def run(self, fn, specical_name = ""):
        '''
        :param fn: a function to handle the functions in binary
        :param specical_name: specific function name while other functions are ignored
        :return:
        '''
        # 如果 specical_name 不为空,则在日志中记录正在处理 specical_name 对应的函数
        if specical_name!="":
            l.info("specific functino name %s" % specical_name)
        # 迭代处理二进制文件中的所有函数,get_func_qty() 用于获取函数的数量
        for i in range(0, get_func_qty()):
            # 获取二进制文件中的第 i 个函数
            func = getn_func(i)
            # 计算并显示当前处理进度
            self.progreeBar(int((i*1.0)/get_func_qty()*100))
            # 获取函数所在段的名称
            segname = get_segm_name(getseg(func.start_ea))
            # 如果段名称的对应位置字符不在指定字符串列表中,则跳过该函数
            # 为什么要执行过滤
            if segname[1:3] not in ["OA", "OM", "te", "_t"]:
                continue
            # 获取函数名称
            func_name = GetFunctionName(func.start_ea)
            # 如果 specical_name 存在且函数名称与 specical_name 不匹配的情况下跳过当前循环
            if len(specical_name) > 0 and specical_name != func_name:
                continue
            try:
                # 分析传入的函数,输出抽象语法树、伪代码、被调用次数、调用次数
                ast_tree, pseudocode, callee_num, caller_num = fn(func)
                # l.error("AST_TREE:"+type(ast_tree))
                # 保存函数名称、起始地址、抽象语法树、伪代码、被调用次数以及调用次数信息
                self.function_info_list.append((func_name, hex(func.start_ea), pickle.dumps(ast_tree), pseudocode, callee_num, caller_num))
            except Exception as e:
                l.warning("%s error" % fn)
                l.warning(str(e))

上述代码的核心功能是从二进制代码中提取目标函数的抽象语法树、伪代码、被调用数量、调用数量等信息,这部分功能由 astg 对象的 get_info_of_func 方法实现,其代码如下:

@staticmethod
def get_info_of_func(func):
    '''
    :param func:
    :return:
    '''
    try:
        # 使用 IDA Pro API 将函数起始地址(func.start_ea)对应的函数进行反编译
        cfunc = idaapi.decompile(func.start_ea)
        # 创建一个 Visitor 对象,并传入反编译后的函数对象 cfunc。
        vis = Visitor(cfunc)
        # 应用 Visitor 对象到函数的主体上,进行遍历或其他处理操作。
        vis.apply_to(cfunc.body, None)
        # 返回抽象语法树、伪代码、被调用数量以及调用数量。
        return vis.root, vis.get_pseudocode(), vis.get_callee(), vis.get_caller()
    except:
        l.warning("Function %s decompilation failed" % (GetFunctionName(func.start_ea)))
        raise

上述代码通过 IDA Pro 的 API 对目标函数进行反编译,并通过 Visitor 对象生成反编译后目标函数的抽象语法树、伪代码、被调用数量以及调用数量。通过执行上述指令,可以生成一个名为 vul.sqlite 的文件,该文件是二进制代码的 AST 表示,如下图所示。

2. 模型训练功能

3. 漏洞搜索功能

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值