首页
社区
课程
招聘
[原创] ollvm控制流平坦化小试
发表于: 2022-10-19 04:46 15068

[原创] ollvm控制流平坦化小试

2022-10-19 04:46
15068

样本为某app里面的libmsaoaidsec.so的init_proc方法,没有原因,就是顺手就撸了,刚好看反调试看到这个东西就尝试了一下以前的一个设想

目前对于控制流平坦化比较常规的一种处理方式是对代码分块后,然后根据虚假块的特征删除后虚假块,然后通过动态或静态unicorn跑出流程,再把真实块之间的关系进行关联之后PatchCode,这种方式处理起来比较简单,但是容易导致流程出错,特别是碰到那种无法确定结果的分支时容易出错。对于虚假块与真实块的区分,除了特征识别外,据我观察,这种控制流平坦化从逻辑上来说其实跟二叉树查找差不多(那种特复杂的另算),每个真实块基本都在树的枝头上,ret块直接反回,其它块填充新的key值进行下一个流程块的转移,基本上终点回到分发器的都是真实块。还有一种比较高端的貌似是使用中间指令进行转换之后再通过编译原理的各种操作进行优化后进行重组,这种只是听说过没见过具体实现,只能说非常之高大上,只能膜拜。

平常使用的也是前一种方法,看个大概,然后动态调,后来有想过搞一套全自动的,结果搞了一半电脑坏了,然后弄了一半的代码被我给一不小心咔嚓掉了也就到了现在。

现在这套代码也是在被咔嚓的代码的基本思路上重新进行的开发,整个思路其实很简单,代理掉寄存器的读和写操作,代理掉内存的读和写操作,直接从头跑到尾,跑出所有分支,之后再处理。整体而言没有太过于注重“分发器、虚假块、真实块”这类概念。以这种方式处理,那么有些问题就必需解决

先上效果,流程属于那种简单的那种,所以没什么特别的东西
在处理前的cfg视图

idaF5的效果

代码分块需要注意的主要问题有:
1.碰到跳转就分块,跳转的目标也是一个块的分界点
2.csel指令下面也需要分块,这个指令比较特殊,必需放在块的未尾,这样便于处理分流问题
3.每一个块都需要能够直接通过块信息查询到他可能的跳转信息
比如:

以Object来保存寄存器状态,只处理常量,例部分接口:

所有对寄存器的操作全部走这些接口,便于管理,其中部分寄存器需要特殊处理,在碰到bl指令时清空x0-x7等都需要特殊对待,如果有用到X30等也得处理

内存的操作与寄存器操作类似,不过内存的问题在地地址交叉问题,这里我使用的是设置一个map,以地址作为key,每个key只与一个byte对应,然后读写操作针对这个map去进行读写,这样可以确保某些情况下局部变量被用作常量缓存的问题,例如下面这种情况的准确处理

例如部分内存操作的代码:

像寄存器和内存的代理处理后基本上已经可以处理大部分的情况了

对于路径问题我这里采用的是递归,每一个代码块都有属于它自己的寄存器环境、内存环境等,对于大部分赋值指令进行手动模拟,对于cmp、tst、b.XX、csel等这类指令则在参数均为常量时进行unicorn模拟执行单条指令,因为这样代码简单且准确度更高

避免死循环,主要就是针块的下一个跳转存在多解的情况进行处理,比如,对csel的特殊处理如下,其它的多解类似,上面的代码中有包含:

最终的效果就如下面的这样

总共跑出8条分支,List中的顺序即为块的顺序,存在分歧的地方即为多解的块,在这里都是csel,这里有个小技巧,就是csel的条件满足时走的分支会在前面,这是由代码特性决定的。
比如

假设这个结果不唯一,那么,在走到这里时会返回两个分支[x8, x9],之后在递归时,会先走x8分支再走x9分支,所以出结果必然是x8在前x9在后

最后上一下最终的效果吧:
处理后idaF5的效果(ida的CFG视图就不上了,CFG会保留那些无法到达的块,看起来还是挺恶心)

sub_8c38大一些,cfg

处理后的效果:

这种方式处理的优点个人感觉就是流程准确度更高,缺点就是每碰到一个新的指令就需要增加对新指令的处理代码,如果指令处理出错也会出现问题,属于有点体力活的感觉,再则,当流程跑出来后如果有兴趣可以增加更加智能化的处理,比如自动识别虚假块,自动Patch等

因为每一个块执行时当时的寄存器环境等都有缓存,所以,对于跑出的每一个分支其实都是可以进行常量扫描的,可以做的事情很多,比如如果有编译原理的知识,可以对那些赋值却没有用的语句进行nop处理,或只是把可以优化的指令行标记出来,为后续patch增加修复空间等,这东西patch起来需要考虑很多问题,所以这里我选择的是手动patch,看似路径很多,其实都多都是重叠的,如果想进一步缩小手动处理的范围,还可以针对明确的地址进行优先patch,然后把有不确定性的打印出来手动处理,那样的话手动处理的代码就会很少很少,基本就是那些csel之类的指令了

完整的脚本代码附件,太长了。。。:

脚本使用方式很简单(需要安装对应的库):shift+f2调出脚本界面 --> 鼠标焦点放在要扫描的函数体内任意地方 --> 点击脚本界面的“run” --> 根据结果修改virtualBlock表,如有不识别的指令就修改脚本增中处理 --> 最终结果直接Patch即可

脚本有很多问题,对很多指令都没有处理,需要在实际过程中去碰到时进行增加,再则是对影响nzcv寄存器有影响的指令,对sp有回写操作的指令等,这个可以根据Capstone解析出的那个update_flags和writeback是否为True来识别,一般来说这类指令在判定为常量时可以直接放unicorn进行跑一下,这样可以省去很多过程且准确度更高

还有一种是直接通过unicorn直接跑整个代码,然后在碰到csel指令时对分支进行控制,不过unicorn跑的时候所有寄存器都有一个默认值,再则不一定所有不确定分支都是在csel这个指令下进行的分流,虽说这条指令本身就是为这个优化做的,内存访问也需要特殊处理,这些都是需要注意的地方,如果有兴趣可以自行研究

 
 
 
1.代码分块,怎么分块很重要,后续的所有操作都是以代码分的块为单位处理的
2.寄存器读写代理,寄存器常量与非常量的表示,包括有效位数,比如w0与x0等
3.内存访问与写入代理,内存数据的表示形式,比如往A地址写入long常量,+4的位置读取int也应判定为常量的问题
4.如何跑出所有路径的问题,比如某些函数返回后,在之后的流程会根据这个结果走不同的流程等,而且不止一个地方
5.如何避免死循环,性于跑出所有路径问题的延伸,如果不进行处理可能会进入类似于∞的死循环中
1.代码分块,怎么分块很重要,后续的所有操作都是以代码分的块为单位处理的
2.寄存器读写代理,寄存器常量与非常量的表示,包括有效位数,比如w0与x0等
3.内存访问与写入代理,内存数据的表示形式,比如往A地址写入long常量,+4的位置读取int也应判定为常量的问题
4.如何跑出所有路径的问题,比如某些函数返回后,在之后的流程会根据这个结果走不同的流程等,而且不止一个地方
5.如何避免死循环,性于跑出所有路径问题的延伸,如果不进行处理可能会进入类似于∞的死循环中
 
block addr:0x13894
    0x13894: movz w9, #0xf799
    0x13898: movk w9, #0x1c38, lsl #16
    0x1389c: cmp w8, w9
    0x138a0: b.ne #0x136e8
    jmpto -> ['0x136e8', '0x138a4']
block addr:0x138a4
    0x138a4: ldr w8, [sp, #0x38]
    0x138a8: movz w9, #0xd3ca
    0x138ac: movk w9, #0x33ed, lsl #16
    0x138b0: cmp w8, #1
    0x138b4: movz w8, #0x48f5
    0x138b8: movk w8, #0xb3d3, lsl #16
    0x138bc: csel w8, w9, w8, eq
    jmpto -> ['0x138c0']
block addr:0x138c0
    0x138c0: b #0x136e8
    jmpto -> ['0x136e8']
block addr:0x13894
    0x13894: movz w9, #0xf799
    0x13898: movk w9, #0x1c38, lsl #16
    0x1389c: cmp w8, w9
    0x138a0: b.ne #0x136e8
    jmpto -> ['0x136e8', '0x138a4']
block addr:0x138a4
    0x138a4: ldr w8, [sp, #0x38]
    0x138a8: movz w9, #0xd3ca
    0x138ac: movk w9, #0x33ed, lsl #16
    0x138b0: cmp w8, #1
    0x138b4: movz w8, #0x48f5
    0x138b8: movk w8, #0xb3d3, lsl #16
    0x138bc: csel w8, w9, w8, eq
    jmpto -> ['0x138c0']
block addr:0x138c0
    0x138c0: b #0x136e8
    jmpto -> ['0x136e8']
 
# 初始化一个非常量对象
def initRegObj():
    regObj = {}
    regObj["isConst"] = False
    regObj["val"] = -1          # isConst为True时有效
    regObj["validBit"] = 0     # isConst为True时有效, 0xFFFFFFFFFFFFFFFF 为1的位为有效位
    return regObj
 
# 读写寄存器对象
def readReg(constRegs, regId):
    regIdTmp = getXRegId(regId)
    # 任何时候xzr及wzr都是0
    if regIdTmp == ARM64_REG_XZR:
        return initRegObjImm(True, 0, getRegValidByte(regId))
    if regIdTmp in constRegs:
        return constRegs[regIdTmp]
    return initRegObj()
def writeReg(constRegs, regId, regObj):
    regIdTmp = getXRegId(regId)
    # 任何时候zxr及wzr均为0
    if regIdTmp == ARM64_REG_XZR:
        return ;
    # 这里寄存器需要区分w x
    constRegs[regIdTmp] = regObj
    if isRegConst(regObj) != True:
        del constRegs[regIdTmp]
    else:
        regObj["validBit"] = regObj["validBit"] | getRegValidByte(regId)
 
# 获取寄存器对象的有效位
def getRegValidByte(regId):
    if ARM64_REG_X0 <= regId and regId <= ARM64_REG_X28:
        return 0xFFFFFFFFFFFFFFFF
    if ARM64_REG_X29 == regId:
        return 0xFFFFFFFFFFFFFFFF
    if ARM64_REG_X30 == regId:
        return 0xFFFFFFFFFFFFFFFF
    if ARM64_REG_XZR == regId:
        return 0xFFFFFFFFFFFFFFFF
    if ARM64_REG_SP == regId:
        return 0xFFFFFFFFFFFFFFFF
    if ARM64_REG_NZCV == regId:
        return 0xFFFFFFFFFFFFFFFF
 
    if ARM64_REG_W0 <= regId and regId <= ARM64_REG_W30:
        return 0xFFFFFFFF
    if ARM64_REG_WZR == regId:
        return 0xFFFFFFFF
    return 0
# 初始化一个非常量对象
def initRegObj():
    regObj = {}
    regObj["isConst"] = False
    regObj["val"] = -1          # isConst为True时有效
    regObj["validBit"] = 0     # isConst为True时有效, 0xFFFFFFFFFFFFFFFF 为1的位为有效位
    return regObj
 
# 读写寄存器对象
def readReg(constRegs, regId):
    regIdTmp = getXRegId(regId)
    # 任何时候xzr及wzr都是0
    if regIdTmp == ARM64_REG_XZR:
        return initRegObjImm(True, 0, getRegValidByte(regId))
    if regIdTmp in constRegs:
        return constRegs[regIdTmp]
    return initRegObj()
def writeReg(constRegs, regId, regObj):
    regIdTmp = getXRegId(regId)
    # 任何时候zxr及wzr均为0
    if regIdTmp == ARM64_REG_XZR:
        return ;
    # 这里寄存器需要区分w x
    constRegs[regIdTmp] = regObj
    if isRegConst(regObj) != True:
        del constRegs[regIdTmp]
    else:
        regObj["validBit"] = regObj["validBit"] | getRegValidByte(regId)
 
# 获取寄存器对象的有效位
def getRegValidByte(regId):
    if ARM64_REG_X0 <= regId and regId <= ARM64_REG_X28:
        return 0xFFFFFFFFFFFFFFFF
    if ARM64_REG_X29 == regId:
        return 0xFFFFFFFFFFFFFFFF
    if ARM64_REG_X30 == regId:
        return 0xFFFFFFFFFFFFFFFF
    if ARM64_REG_XZR == regId:
        return 0xFFFFFFFFFFFFFFFF
    if ARM64_REG_SP == regId:
        return 0xFFFFFFFFFFFFFFFF
    if ARM64_REG_NZCV == regId:
        return 0xFFFFFFFFFFFFFFFF
 
    if ARM64_REG_W0 <= regId and regId <= ARM64_REG_W30:
        return 0xFFFFFFFF
    if ARM64_REG_WZR == regId:
        return 0xFFFFFFFF
    return 0
 
# 假设x8为流程判断中用到的一个常量
str x8, [sp, #0x8]
sub sp, sp, #0x8
...
add x8, sp, #0x10
ldr x8, [x8]
# 假设x8为流程判断中用到的一个常量
str x8, [sp, #0x8]
sub sp, sp, #0x8
...
add x8, sp, #0x10
ldr x8, [x8]
def initMemObj(isConst, val, validBit):
    memObj = {}
    memObj["isConst"] = isConst
    memObj["val"] = val
    memObj["validBit"] = validBit
    return memObj
 
def isMemObjConst(memObj):
    return memObj["isConst"]
 
def getMemObjVal(memObj):
    if memObj["isConst"]:
        return memObj["val"] & memObj["validBit"]
    return -1
 
def readByte(constMems, addr):
    ret = readMem(constMems, addr, 1)
    if ret != None and (len(ret) == 1):
        return initMemObj(True, int.from_bytes(ret, "big"), 0xFF)
    return initMemObj(False, 0, 0)
 
def readInt4(constMems, addr, s):
    ret = readMem(constMems, addr, 4)
    if ret != None and (len(ret) == 4):
        return initMemObj(True, int.from_bytes(ret, "big"), 0xFFFFFFFF)
    return initMemObj(False, 0, 0)
 
def readMem(constMems, addr, sz):
    if sz <= 0:
        return None;
 
    ret = [0] * sz
    for i in range(0, sz):
        if (addr + i) in constMems:
            ret[i] = constMems[addr + i] & 0xff
        else:
            # print("readMem err, addr:" + hex(addr + i))
            ret = None
            break
    if ret != None:
        return bytes(ret)
    return None
 
def writeMem(constMems, addr, sz, data):
    minLen = sz
    if len(data) < sz:
        minLen = sz
    for i in range(0, minLen):
        constMems[addr + i] = data[i] & 0xff
 
def writeInvalidMem(constMems, addr, sz):
    for i in range(0, sz):
        if (addr + i) in constMems:
            del constMems[addr + i]
def initMemObj(isConst, val, validBit):
    memObj = {}
    memObj["isConst"] = isConst
    memObj["val"] = val
    memObj["validBit"] = validBit
    return memObj
 
def isMemObjConst(memObj):
    return memObj["isConst"]
 
def getMemObjVal(memObj):
    if memObj["isConst"]:
        return memObj["val"] & memObj["validBit"]
    return -1
 
def readByte(constMems, addr):
    ret = readMem(constMems, addr, 1)
    if ret != None and (len(ret) == 1):
        return initMemObj(True, int.from_bytes(ret, "big"), 0xFF)
    return initMemObj(False, 0, 0)
 
def readInt4(constMems, addr, s):
    ret = readMem(constMems, addr, 4)
    if ret != None and (len(ret) == 4):
        return initMemObj(True, int.from_bytes(ret, "big"), 0xFFFFFFFF)
    return initMemObj(False, 0, 0)
 
def readMem(constMems, addr, sz):
    if sz <= 0:
        return None;
 
    ret = [0] * sz
    for i in range(0, sz):
        if (addr + i) in constMems:
            ret[i] = constMems[addr + i] & 0xff
        else:
            # print("readMem err, addr:" + hex(addr + i))
            ret = None
            break
    if ret != None:
        return bytes(ret)
    return None
 
def writeMem(constMems, addr, sz, data):
    minLen = sz
    if len(data) < sz:
        minLen = sz
    for i in range(0, minLen):
        constMems[addr + i] = data[i] & 0xff
 
def writeInvalidMem(constMems, addr, sz):
    for i in range(0, sz):
        if (addr + i) in constMems:
            del constMems[addr + i]
 
#获取块的跳转表
def getBlockNextAddr(curBlock):
    if "jmpto" in curBlock:
        return curBlock["jmpto"]
    return None
#只允许运行一次的块,比如csel存在且无法获取准确结果的块
runOnceBlock = {}
 
# 需要考虑csel导致结果不同的情况, 可以将block进行分化,例,在存在分歧时生成两套[[constRegs, constMems, NextBlockAddr], [constRegs, constMems, NextBlockAddr]]
def runFuzz(constRegs, constMems, curBlock):
    ret = []
    if curBlock["startAddr"] in runOnceBlock:
        if runOnceBlock[curBlock["startAddr"]] == True:
            print("run once block, return.")
            return None
 
    try:
        for codeitem in curBlock["codes"]:
            # print("\t%s: %s %s" % (codeitem["addr"], codeitem["mnemonic"], codeitem["OpCode"]))
 
            if testAsm(constRegs, constMems, codeitem) == False:
                # 需要额外处理的指令
                regsRead = codeitem["regsRead"]
                regsWrite = codeitem["regsWrite"]
                operands = codeitem["operands"]
                if ARM64_INS_CMP == codeitem["id"]:
                    asmUniCmp(constRegs, int(codeitem["addr"], 16), codeitem["bytes"], codeitem["opTypes"],
                              codeitem["operands"])
                elif ARM64_INS_TST == codeitem["id"]:
                    asmUniTst(constRegs, int(codeitem["addr"], 16), codeitem["bytes"], codeitem["opTypes"],
                              codeitem["operands"])
                elif ARM64_INS_B == codeitem["id"]:
                    bret = asmUniB(constRegs, int(codeitem["addr"], 16), codeitem["bytes"], codeitem["opTypes"],
                                   codeitem["operands"], codeitem["cc"])
                    if bret != None:
                        print([hex(x) for x in bret])
                        if len(bret) > 1:
                            runOnceBlock[curBlock["startAddr"]] = True
                        env = {}
                        env["regs"] = constRegs
                        env["mems"] = constMems
                        env["nextAddr"] = bret
                        ret.append(env)
                        return ret
                elif ARM64_INS_CSEL == codeitem["id"]:
                    dstRegId, srcRegIdList = asmUniCsel(constRegs, int(codeitem["addr"], 16), codeitem["bytes"],
                                                        codeitem["opTypes"], codeitem["operands"])
                    if len(srcRegIdList) > 1:
                        for regId in srcRegIdList:
                            srcRegId = regId
                            cpyConstRegs = copy.deepcopy(constRegs)
                            writeReg(cpyConstRegs, dstRegId, readReg(cpyConstRegs, srcRegId))
                            env = {}
                            env["regs"] = cpyConstRegs
                            env["mems"] = constMems
                            env["nextAddr"] = [int(codeitem["addr"], 16) + 4]
                            ret.append(env)
                        runOnceBlock[curBlock["startAddr"]] = True
                        return ret
                    else:
                        print("------------ only one result. -----------")
                elif ARM64_INS_RET == codeitem["id"]:
                    return None
                else:
                    print("unknow ins.")
        if getBlockNextAddr(curBlock) == None:
            print("curBlock has not next block.")
            return None
 
        # 没有跳转没有Csel顺延的块走这里
        env = {}
        env["regs"] = constRegs
        env["mems"] = constMems
        env["nextAddr"] = getBlockNextAddr(curBlock)
        ret.append(env)
        return ret
 
    except Exception as e:
        print(repr(e))
        print(traceback.format_exc())
 
    return None
 
# 需要排除的块,要自动化处理需要在最终流程出来后针对流程进行常量判定删除等操作,比较麻烦,所以这里采用了最笨的手动排除的方法,因为简单准确
virtualBlock = [0x136e8, 0x13774, 0x1377c, 0x13784, 0x13794, 0x137a4, 0x136f0, 0x136f8, 0x137c4, 0x138c4, 0x138d4,
                0x13858, 0x137cc, 0x137d4, 0x13884, 0x13894, 0x13810, 0x13818, 0x13820, 0x13830, 0x13854, 0x138f8, 0x13908,
                0x13960, 0x138c0, 0x13868, 0x13870, 0x13880, 0x13700, 0x13708, 0x13a44, 0x13718, 0x13770, 0x137e4]
# 移除流程中需要排除的块
def removeVirtualBlock(callStack):
    for item in range(len(callStack) - 1, -1, -1):
        for addr in virtualBlock:
            if callStack[item] == addr:
                del callStack[item]
                break
    return callStack
# 获取块的签名,主要是用于快束区分是否是需要排除的块
def getBlockFeature(codeblock, blockAddr):
    feature = ""
    if blockAddr in codeblock:
        curBlock = codeblock[blockAddr]
        for codeitem in curBlock["codes"]:
            # print("\t%s: %s %s" % (codeitem["addr"], codeitem["mnemonic"], codeitem["OpCode"]))
            feature = feature + codeitem["mnemonic"] + codeitem["opTypes"]
    return feature
#入口函数,从第一个块开始
def testFuzzBlock(constRegs, constMems, codeblock, curBlock, callStack):
    curConstRegs = copy.deepcopy(constRegs)
    curConstMems = copy.deepcopy(constMems)
    # curCallStack = copy.deepcopy(callStack)
    callStack.append(curBlock["startAddr"])
    endEnv = runFuzz(curConstRegs, curConstMems, curBlock)
    if endEnv == None:
        #当前分支执行完成,可以做的操作:对调用栈进行处理,修复跳转
 
        callStack = removeVirtualBlock(callStack)
        # for blockaddr in callStack:
        #     feature = getBlockFeature(codeblock, blockaddr)
        #     print("%s:\r\n%s" % (hex(blockaddr), feature))
        print([hex(x) for x in callStack])
        return
    else:
        # 如果当前块Fuzz时产生了多个分支,即不确定的分支
        # 那么,当前块直接被判断为块实块
        # 且在分支后续运行时再碰到当前块应该直接退出当前分支,否则易进入死循环
        for env in endEnv:
            # 块执行完后可能会产生分歧
            subRegs = env["regs"]
            subMems = env["mems"]
            nextAddrList = env["nextAddr"]
            if nextAddrList != None:
                for nextBlockAddr in nextAddrList:
                    nextBlock = codeblock[nextBlockAddr]
                    curCallStack = copy.deepcopy(callStack)
                    testFuzzBlock(subRegs, subMems, codeblock, nextBlock, curCallStack)
 
# sp给一个默认初始值
writeReg(constRegs, ARM64_REG_SP, initRegObjImm(True, 0x10000, getRegValidByte(ARM64_REG_SP)))
#获取块的跳转表
def getBlockNextAddr(curBlock):
    if "jmpto" in curBlock:
        return curBlock["jmpto"]
    return None
#只允许运行一次的块,比如csel存在且无法获取准确结果的块
runOnceBlock = {}
 
# 需要考虑csel导致结果不同的情况, 可以将block进行分化,例,在存在分歧时生成两套[[constRegs, constMems, NextBlockAddr], [constRegs, constMems, NextBlockAddr]]
def runFuzz(constRegs, constMems, curBlock):
    ret = []
    if curBlock["startAddr"] in runOnceBlock:
        if runOnceBlock[curBlock["startAddr"]] == True:
            print("run once block, return.")
            return None
 
    try:
        for codeitem in curBlock["codes"]:
            # print("\t%s: %s %s" % (codeitem["addr"], codeitem["mnemonic"], codeitem["OpCode"]))
 
            if testAsm(constRegs, constMems, codeitem) == False:
                # 需要额外处理的指令
                regsRead = codeitem["regsRead"]
                regsWrite = codeitem["regsWrite"]
                operands = codeitem["operands"]
                if ARM64_INS_CMP == codeitem["id"]:
                    asmUniCmp(constRegs, int(codeitem["addr"], 16), codeitem["bytes"], codeitem["opTypes"],
                              codeitem["operands"])
                elif ARM64_INS_TST == codeitem["id"]:
                    asmUniTst(constRegs, int(codeitem["addr"], 16), codeitem["bytes"], codeitem["opTypes"],
                              codeitem["operands"])
                elif ARM64_INS_B == codeitem["id"]:
                    bret = asmUniB(constRegs, int(codeitem["addr"], 16), codeitem["bytes"], codeitem["opTypes"],
                                   codeitem["operands"], codeitem["cc"])
                    if bret != None:
                        print([hex(x) for x in bret])
                        if len(bret) > 1:
                            runOnceBlock[curBlock["startAddr"]] = True
                        env = {}
                        env["regs"] = constRegs
                        env["mems"] = constMems
                        env["nextAddr"] = bret
                        ret.append(env)
                        return ret
                elif ARM64_INS_CSEL == codeitem["id"]:
                    dstRegId, srcRegIdList = asmUniCsel(constRegs, int(codeitem["addr"], 16), codeitem["bytes"],
                                                        codeitem["opTypes"], codeitem["operands"])
                    if len(srcRegIdList) > 1:
                        for regId in srcRegIdList:
                            srcRegId = regId
                            cpyConstRegs = copy.deepcopy(constRegs)
                            writeReg(cpyConstRegs, dstRegId, readReg(cpyConstRegs, srcRegId))
                            env = {}
                            env["regs"] = cpyConstRegs
                            env["mems"] = constMems
                            env["nextAddr"] = [int(codeitem["addr"], 16) + 4]
                            ret.append(env)
                        runOnceBlock[curBlock["startAddr"]] = True
                        return ret
                    else:
                        print("------------ only one result. -----------")
                elif ARM64_INS_RET == codeitem["id"]:
                    return None
                else:
                    print("unknow ins.")
        if getBlockNextAddr(curBlock) == None:
            print("curBlock has not next block.")
            return None
 
        # 没有跳转没有Csel顺延的块走这里
        env = {}
        env["regs"] = constRegs
        env["mems"] = constMems
        env["nextAddr"] = getBlockNextAddr(curBlock)
        ret.append(env)
        return ret
 
    except Exception as e:
        print(repr(e))
        print(traceback.format_exc())
 
    return None
 
# 需要排除的块,要自动化处理需要在最终流程出来后针对流程进行常量判定删除等操作,比较麻烦,所以这里采用了最笨的手动排除的方法,因为简单准确
virtualBlock = [0x136e8, 0x13774, 0x1377c, 0x13784, 0x13794, 0x137a4, 0x136f0, 0x136f8, 0x137c4, 0x138c4, 0x138d4,
                0x13858, 0x137cc, 0x137d4, 0x13884, 0x13894, 0x13810, 0x13818, 0x13820, 0x13830, 0x13854, 0x138f8, 0x13908,
                0x13960, 0x138c0, 0x13868, 0x13870, 0x13880, 0x13700, 0x13708, 0x13a44, 0x13718, 0x13770, 0x137e4]
# 移除流程中需要排除的块
def removeVirtualBlock(callStack):
    for item in range(len(callStack) - 1, -1, -1):
        for addr in virtualBlock:
            if callStack[item] == addr:
                del callStack[item]
                break
    return callStack
# 获取块的签名,主要是用于快束区分是否是需要排除的块
def getBlockFeature(codeblock, blockAddr):
    feature = ""
    if blockAddr in codeblock:
        curBlock = codeblock[blockAddr]
        for codeitem in curBlock["codes"]:
            # print("\t%s: %s %s" % (codeitem["addr"], codeitem["mnemonic"], codeitem["OpCode"]))
            feature = feature + codeitem["mnemonic"] + codeitem["opTypes"]
    return feature
#入口函数,从第一个块开始
def testFuzzBlock(constRegs, constMems, codeblock, curBlock, callStack):
    curConstRegs = copy.deepcopy(constRegs)
    curConstMems = copy.deepcopy(constMems)
    # curCallStack = copy.deepcopy(callStack)
    callStack.append(curBlock["startAddr"])
    endEnv = runFuzz(curConstRegs, curConstMems, curBlock)
    if endEnv == None:
        #当前分支执行完成,可以做的操作:对调用栈进行处理,修复跳转
 
        callStack = removeVirtualBlock(callStack)
        # for blockaddr in callStack:
        #     feature = getBlockFeature(codeblock, blockaddr)
        #     print("%s:\r\n%s" % (hex(blockaddr), feature))

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2022-10-25 23:52 被lxcoder编辑 ,原因: 增加样本附件
上传的附件:
收藏
免费 10
支持
分享
打赏 + 5.00雪花
打赏次数 1 雪花 + 5.00
 
赞赏  陈可牛   +5.00 2022/10/24 非常感谢~~~请大佬喝阔乐!
最新回复 (7)
雪    币: 2402
活跃值: (2857)
能力值: ( LV5,RANK:61 )
在线值:
发帖
回帖
粉丝
2
楼主,方便发一下样本吗?非常感谢~~~
2022-10-24 19:54
0
雪    币: 2122
活跃值: (3833)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
3
斯文这个禽兽 楼主,方便发一下样本吗?非常感谢~~~[em_13]
样本是xhs的libmsaoaidsec.so模块,不过这个脚本主要针对的是arm64的,文章主要讲的一个断路,如果其它so跑起来异常的话可以针对性的改一改,这种方式与其常见的反混淆思路的区别应该主要是把常量和非常量进行了区分,这样的话在优化方面后续应该可以做很多操作
2022-10-25 23:51
0
雪    币: 2402
活跃值: (2857)
能力值: ( LV5,RANK:61 )
在线值:
发帖
回帖
粉丝
4
lxcoder 样本是xhs的libmsaoaidsec.so模块,不过这个脚本主要针对的是arm64的,文章主要讲的一个断路,如果其它so跑起来异常的话可以针对性的改一改,这种方式与其常见的反混淆思路的区别应该主要 ...
非常感谢,因为我只知道是bangbang,因为我最近也在看。都是硬着头皮看的,如果能找到样本+调试的话效率会更高。所以就问一下大佬样本了。非常感谢
2022-10-27 19:26
0
雪    币: 110
活跃值: (1555)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
5
这个so会在init_proc中 memcpy一个新的so并且重新加载自己吧?我在看新的xc,anti frida 和inline hook特征这些是线程里跑的很容易就干掉了,但是对其他函数的hook 在他加载了libc liblog等so后就失效了。不知道楼主研究过这一块没,重新dump 新位置的so后也没发修复,hook 他JNI_Onload中的一些函数也没有运行,不知道是什么原因和原理
2023-3-15 12:22
0
雪    币: 2710
活跃值: (1848)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
脚本运行会报错  IDA7.6 python 3.7.8
UcError(21)
Traceback (most recent call last):
  File "D:/Antiollvm/patch_ollvm_ida.py", line 1132, in runFuzz
    codeitem["operands"])
  File "D:/Antiollvm/patch_ollvm_ida.py", line 985, in asmUniCmp
    uniTestAsm(bts, va)
  File "D:/Antiollvm/patch_ollvm_ida.py", line 110, in uniTestAsm
    raise e
  File "D:/Antiollvm/patch_ollvm_ida.py", line 106, in uniTestAsm
    uc.emu_start(va, endVa, 0, 1)
  File "C:\Users\ws\AppData\Local\Programs\Python\Python37\lib\site-packages\unicorn\unicorn.py", line 547, in emu_start
    raise UcError(status)
unicorn.unicorn.UcError: Unhandled CPU exception (UC_ERR_EXCEPTION)
2023-4-20 20:14
0
雪    币: 2122
活跃值: (3833)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
7
乐活 脚本运行会报错 IDA7.6 python 3.7.8 UcError(21) Traceback (most recent call last): File "D:/Antio ...
初期的脚本有缺陷,只是一个思路,emu_start报错的话得看看参数环境等有没有问题
2023-4-20 22:21
0
雪    币: 116
活跃值: (1012)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
mark
2023-4-21 19:38
0
游客
登录 | 注册 方可回帖
返回
//