IDApython实用技巧(衡量afl-fuzz路径hash算法碰撞问题)

 

IDApython基础

​ 三个基础模块:idc、idautils和idaapi;

其中ida是一个封装了IDC的兼容模块,idautils是IDA的高就实用功能模块,idaapi方便我们访问一下更加底层的数据。

总的来看,可以通过IDApython完成 地址、指令、函数、段、交叉引用 相关的操作。

idc

​ 注:以IDApython文档为准,以下所有示例地址为ea;返回的集合形式都是迭代器。

ScreenEA() || here()

​ 获取当前光标所在的指令地址;

GetDisasm(ea)

​ 获取指定地址的汇编指令;

GetMnem(ea)

​ 获取当前地址的汇编指令的操作符。

GetOpnd(ea, n)

​ 获取指定地址处的指令中的第n个操作数(0计起);该函数的底层依赖get_operand_value(ea, n)(获取第n个操作数的值)

GetOpType(ea, n)

​ 获取指定指令操作数类型;

共有八种操作数类型(0-7):无操作数、寄存器、直接寻址的内存、基址和变址、寄存器偏移、常数、far寻址、near寻址。

SegName(ea)

​ 获取指定地址所在的段(segment)名称;

SegStart(seg)

​ 指定段的起始地址;

SegEnd(ea)

​ 指定段的结束地址;

NextSeg(ea)

​ 指定地址所在段的下一个段的起始地址。

GetFunctionName(ea)

​ 通过地址获取函数名称;ea可以是该函数范围的任意地址

NextHead(ea)

​ 获取下一条指令

PrevHead(ea)

​ 获取前一条指令

idautils

Segments()

​ 获取当前程序所有段;

Functions(start=None, end=None)

​ 获取指定地址区间的所有函数列表,默认返回所有识别出的函数地址列表。

FuncItems(ea)

​ 获取指定地址所在函数的所有指令地址的集合。

CodeRefsFrom(ea, flow)

​ 获取ea地址处引用其他地址(call 或者 jmp方法)列表。

flow(Boolen 0/1, Flase/True) ——是否跟进代码执行流,这在有条件跳转或者call时有区别。

​ 有条件跳转或者call将产生两个引用(一个跳转指令的目的地址,另一个该指令的下一地址)。

CodeRefsTo(ea, flow)

​ 获取引用了地址ea的地址列表,就是IDA的交叉引用快捷键X功能

 

用IDApython解决计算AFL记录路径的hash算法碰撞问题

获取block及block id

def find_afl_block():
    afl_funcs = []
    blocks = dict()

    # find all __afl_maybe_log_xx functions
    for seg_ea in Segments():
        for function_ea in Functions(seg_ea, SegEnd(seg_ea)):
            if(GetFunctionName(function_ea)).startswith("__afl_maybe_log"):
                afl_funcs.append(function_ea)

    for afl_maybe_log_addr in afl_funcs:
        refs = CodeRefsTo(afl_maybe_log_addr, 0)            # where calls afl_maybe_log

        for call_addr in refs:
            # get block's id
            id_addr = PrevHead(call_addr)
            block_id = idc.get_operand_value(id_addr, 1)

            blocks[call_addr] = block_id                    # (block_addr : block_id)

    print("afl_funcs: {}; afl_blocks: {}".format(len(afl_funcs), len(blocks)))
    return blocks

获取edges

def is_afl(addr):
    asm = GetDisasm(addr)
    return ("__afl_maybe_log" in asm)

def end_blocks(start):
    # from this block; there has how many edges
    ends = []

    while isCode(GetFlags(start)):
        head = NextHead(start)
        if is_afl(head):
            ends.append(head)
            break
        else:
            # iterate to find afl
            call = False
            refs = CodeRefsFrom(head, 0)
            asm = GetDisasm(head)

            if asm.startswith('call') or asm.startswith('jmp'):
                call = True

            for r in refs:
                try:
                    r_block = end_blocks(r)
                except:
                    r_block = []
                r_asm = GetDisasm(r)
                if len(r_block):
                    ends += r_block
                    if call:                                    # in call or jmp we found, jut out
                        return ends

        start = head
    return list(set(ends))

def find_afl_edges(afl_blocks):
    edges = []
    for block in afl_blocks:                # block dict
        # addr, afl_blocks[addr]
        # search forwards to find an edge to another afl_block
        # print(block)
        ends = end_blocks(block)
        edges.append((block, ends))
    return edges

计算hash collision概率

def calc_collision(edges, blocks, hashs):
    collision_cnt = 0
    edge_cnt = 0

    for edge in edges:
        edge_cnt += len(edge[1])
        prev = blocks[edge[0]]
        for loc in edge[1]:
            hans = calc_hash(prev, blocks[loc])
            if hashs[hans] == 1:
                collision_cnt += 1
            else:
                hashs[hans] = 1

    print("edge_cnt: {}".format(edge_cnt))
    rate = (collision_cnt * 1.0000)/(1 << 16)

 

参考

IDApython Document

Introduction_to_IDAPython

(完)