首页
社区
课程
招聘
[原创]ollvm的混淆反混淆和定制修改
发表于: 2017-5-19 10:46 46097

[原创]ollvm的混淆反混淆和定制修改

2017-5-19 10:46
46097

                                        ollvm的混淆反混淆和定制修改

   最近各大杀毒公司陆续都出了混淆,网上关于ollvm的资料比较少,于是就有了这篇文章,这篇文章介绍,androidnative代码,也就是solinuxc/c++代码均可使用的混淆工具ollvm的编译,混淆,反混淆,和反反混淆。

第一篇.ollvm的编译环境搭建------混淆

教你搭建编译和使用ollvm3.4 3.5 3.6的环境,非常详细

第二篇.ollvm的还原---反混淆

根据网上的一些文章,对ollvm混淆后的代码进行还原,写下我详细的心得和代码注释和环境搭建(目前只能还原linux x86,对于arm有兴趣的可以进一步研究)

第三篇.ollvm的定制---反反混淆

由于公司原因,这里介绍修改后的结果

====================第一篇ollvm的编译环境搭建------混淆======================

一、androidNDK搭建ollvm环境和使用

注意这里有编译环境和编译后的版本,ubuntu64位的系统依然可以使用ndk32,但是只能编译clang64,所以你不需要ndk64就不需要编译64位的ollvm64

1.编译ollvm 32  

版本有三个我们选择obfuscator-llvm-3.4解压得到文件夹obfuscator-llvm-3.4

ollvm的下载地址https://github.com/obfuscator-llvm/obfuscator/tree/llvm-3.4

ndk选择android-ndk-r10b-linux-x86.tar.bz2

环境选择ubuntu14.0.4 x32 or x64

(0)安装ndk

ndk解压到/opt/android/ndk/

解压后的目录

/opt/android/ndk/android-ndk-r10e

$ sudo gedit /etc/profile,在文件末尾加入如下内容:
#set NDK env
export NDK_HOME=/opt/android/ndk/android-ndk-r10b

export PATH=$NDK_HOME:$PATH

$ source /etc/profile   使之生效

(1)编译ollvm的工具

apt-get install cmake

sudo apt-get install g++

正式编译ollvm

cd obfuscator-llvm-3.4

mkdir build

cd build

cmake -DCMAKE_BUILD_TYPE:String=Release ../

make –j4   (不要复制哦,手动输入命令)(注意这里一定要加j4,如果只是make –j他默认只会用一个cpu然后会卡到蛋疼的)

注意:分配内存和cpu多点不然卡死,我这里是8G内存+(2处理器数量每个处理器2个核心)

编译完后得到二进制程序都在build/binbuild/lib

(2)下面来配置32位的ndk

[1]打开ndktoolchains目录新建目录obfuscator-llvm-3.4

并将llvm-3.3目录下的prebuilt目录和文件 config.mksetup.mksetup-common.mk拷贝到obfuscator-llvm-3.4目录中

然后替换obfuscator-llvm-3.4/prebuilt/linux-x86下的binlib为我们编译好的binlib

然后将下面文件复制一份,改名称如下,比如arm-linux-androideabi-clang3.4复制一行改名为arm-linux-androideabi-obfuscator3.4

arm-linux-androideabi-clang3.4-> arm-linux-androideabi-obfuscator3.4

mipsel-linux-android-clang3.4-> mipsel-linux-android-obfuscator3.4

x86-clang3.4-> x86-obfuscator3.4

 

分别修改以上三个文件的 setup.mk 中的 LLVM_NAME ,即将其指定到开始建立的obfuscator-llvm-3.4目录,也就是把

LLVM_NAME := llvm-$(LLVM_VERSION)改成LLVM_NAME := obfuscator-llvm-$(LLVM_VERSION)

 

如果是配置64位的ndk配置,还要额外修改$NDK_PATH/build/core/setup-toolchain.mk文件,在NDK_64BIT_TOOLCHAIN_LIST := 加入 obfuscator 对应的NDK_TOOLCHAIN_VERSION

NDK_64BIT_TOOLCHAIN_LIST := obfuscator3.4 clang3.6 clang3.5 clang3.4 4.9

 

2.使用

Application.mk中指定编译器名字:

NDK_TOOLCHAIN_VERSION := obfuscator3.4

Android.mk中设置混淆参数:

LOCAL_CFLAGS += -mllvm -sub -mllvm -bcf -mllvm –fla

正常编译ndk就行了

例子

Application.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

APP_ABI := armeabi

NDK_TOOLCHAIN_VERSION := obfuscator

include $(BUILD_EXECUTABLE)

Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := hello

LOCAL_SRC_FILES := hello.c

LOCAL_CFLAGS += -mllvm -sub -mllvm -bcf -mllvm –fla  混淆参数

LOCAL_ARM_MODE := arm

include $(BUILD_EXECUTABLE)

hello.c

#include <stdio.h>

int main(int argc, char **args){    

    int a=1;

         int b=0;

         if(a>b){

                   printf("snow:%d\n", a);

         }

         else{

                   printf("test:%d\n", b);

         }

         return 0;

}

3.更多使用

bcf可以配合下面参数使用

-mllvm -perBCF=20: 对所有函数都混淆的概率是20%,默认100%

-mllvm -boguscf-loop=3: 对函数做3次混淆,默认1

-mllvm -boguscf-prob=40: 代码块被混淆的概率是40%,默认30%

给某个函数单独加入混淆;注意注意经过我测试只有ollvm3.5ollvm3.6可以使用单独函数加混淆

int main(int argc, char **args)__attribute((__annotate__(("bcf"))));

 

linux可执行文件搭建ollvm环境和使用

环境跟前面那个一样,在编译完毕ollvm之后有目录obfuscator-llvm-3.4/build/bin/此目录下面有一个clang,指向clang-3.4我们编译linux可执行的程序可以用

obfuscator-llvm-3.4/build/bin/clang  xx.c –o xx –mllvm –fla 就是控制流平展了

 

 

 

=========================第二篇.ollvm的还原---反混淆===================

一、网上的Decllvm的分析

F8LEFT写的一个工具,这个工具出现在吾爱破解2016的安全挑战赛第七题的解答里面

此工具给的demoAliLLVM.py针对阿里第二届安全挑战赛crackme3

下面只是说这个工具的使用和原理

1.入口360LLVM.py文件

if __name__ == "__main__":

    print("============360LLVMStart=================")

    ins = C360LLVM()

    reg = ArmReg()

    dbgEng = DbgEngine(reg, ins)

    fd = open("F:/trace.log", "w+")

    dbgEng.start_run(GetRegValue("PC"), 1000, fd)

    fd.close()

    del dbgEng

    del reg

    del ins

    print("============360LLVMEnd=================")         

大致原理这是一个ida的脚本,运行需要动态调试程序才行,根据程序运行的时候把寄存器的参数打印下来并且写到txt文件里面,方便分析,这个用处不是太大,只是利用ida打印程序流程,但是可以通吃NDKlinux的混淆,顺便感谢一下他的脚本里面包含很多ida api的使用哦!

 

二、又找的的另外一篇利用符号执行

参考文章https://security.tencent.com/index.php/blog/msg/1122  利用符号执行去除控制流平坦化

 

这篇文章的办法可以完美的恢复控制流平坦化但是只是linux6432位可执行文件x86格式的混淆后完美还原,对于androidso和可执行文件不行,arm格式的指令找不到规则不像x86那样找到规则,需要我们自己分析规则

注意作者给的deflat.py依赖的barf只能运行于linux64位,所以就算在linux32位上混淆了,我们也要拿到linux64位上运行deflat.py脚本哦!!!

 

我的电脑是ubuntu14.04 x64

1.搭建环境和运行

[1]安装python

ubuntu自带了Python 2.7.6

[2]安装pip和??

apt-get install python-pip

sudo apt-get install python-dev libffi-dev build-essential

 

[3]安装barf

下载barf解压cd到目录运行

python setup.py install

 

[4]安装angr

sudo pip install angr 报错,再次安装sudo apt-get install python-dev libffi-dev build-essential

 

[5]运行

python deflat.py check_passwd_flat 0x400530

 

 

注意在ubuntu14.0.4 x32环境运行出错

ImportError: ERROR: fail to load the dynamic library

因为deflat.py会去调用BARFBARF这货只能在linux x64位运行坑爹啊,但是我们可以在x32上编译了,拿到x64上面跑,

这里我们自己编译和混淆在ubuntu14.04_x32位下面在ubuntu14.0.4_64里面还原混淆

[1]编译

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

int check_password(char *passwd)

{

    int i, sum = 0;

    for (i = 0; ; i++)

    {

        if (!passwd[i])

        {

            break;

        }

        sum += passwd[i];

    }

    if (i == 4)

    {

        if (sum == 0x1a1 && passwd[3] > 'c' && passwd[3] < 'e' && passwd[0] == 'b')

        {

            if ((passwd[3] ^ 0xd) == passwd[1])

            {

                return 1;

            }  

            puts("Orz...");

        }

    }

    else

    {

        puts("len error");

    }

    return 0;

}

 

int main(int argc, char **argv)

{

    if (argc != 2)

    {

        puts("error");

        return 1;

    }

    if (check_password(argv[1]))

    {

        puts("Congratulation!");

    }

    else

    {

        puts("error");

    }

    return 0;

}

gcc check_passwd.c -o check_passwd

[2]混淆控制流平坦

root/桌面/software/my _compile_ollvm/obfuscator-llvm-3.4/build/bin/clang-3.4  check_passwd.c -o check_passwd_32_flat -mllvm -fla

[3]拿到x64位上还原混淆

python deflat.py check_passwd_32_flat 0x80488B0  //main函数

 

[4]全混淆

python deflat.py check_passwd_32_flat_sub_bcf 0x8048420

 

依然可以还原-fla

2. deflat.py脚本分析心得和我的注释

ida函数中可以看见很多块,一块一块的

 



                                            

#第一步:找出6大块,可以用静态分析得到

序言                            保留

主分发器                   去掉

预处理器                   去掉

retn                        保留

真实块                       保留

无用块                       去掉

 

[1]序言和主分发器,函数开始就是序言,序言下面紧接着就是主分发器


[2]预处理器

ida查找办法谁调用了主分发器,谁就是预处理器(在主分发器按x,看见的就是预处理器)

[3]真实块,


ida查找办法所有调用预处理器的块都是预处理器(在预处理器按x查看到的都是真实块)

[4]retn  


ida查找办法,没有后继的块就是,也就是下线没有分支的

[5]无用块

其余均是无用块

#第二步:找出真实块和序言和retn之间的调用关系,必须动态运行

#难点一:使用symbolic_execution找出真实块和序言的调用关系,必须使用他的引擎运行或者动态运行

如下的关系

         #执行到这里已经获得了原函数的调用关系,下面是恢复之后的关系,真实块7->retn返回块

         #0x8048a82: ['0x8048a95']  真实块6 : retn返回块

         #0x8048a65: ['0x8048a82']  真实块5 : 真实块6

         #0x8048a48: ['0x8048a82']  真实块4 : 真实块6

         #0x80488b0: ['0x80489d4']  序言    : 真实块1

         #0x80489d4: ['0x8048a1b', '0x80489f7']  真实块1 :真实块3,真实块2

         #0x8048a95: []                                                                 retn返回块

         #0x80489f7: ['0x8048a95']                                真实块2 : retn返回块

         #0x8048a1b: ['0x8048a65', '0x8048a48']  真实块3 :真实块5,真实块4

下面是修复后他们的调用图

 


#第三步:nop掉无用块和主分发器和预处理器,可以静态分析得到

#第四步:修复真实块序言部分的跳转指令,修复办法,

#情况一:有一个childs,也就是一个后继的块,找到块的最后一条指令,将其抹掉后改成新的jmpjmp到自己的childs

#情况二:有多个childs

下面是我的代码注释

#coding: UTF-8

from barf.barf import BARF

import angr

import simuvex

import pyvex

import claripy

import struct

import sys

 

def get_retn_predispatcher(cfg):

    global main_dispatcher

    for block in cfg.basic_blocks:

        if len(block.branches) == 0 and block.direct_branch == None:

            retn = block.start_address

        elif block.direct_branch == main_dispatcher:

            pre_dispatcher = block.start_address

    return retn, pre_dispatcher

   

def get_relevant_nop_blocks(cfg):

    global pre_dispatcher, prologue, retn

    relevant_blocks = []

    nop_blocks = []

    for block in cfg.basic_blocks:

        if block.direct_branch == pre_dispatcher and len(block.instrs) != 1:

            relevant_blocks.append(block.start_address)

        elif block.start_address != prologue and block.start_address != retn:

            nop_blocks.append(block)

    return relevant_blocks, nop_blocks

 

def statement_inspect(state):

    global modify_value

    expressions = state.scratch.irsb.statements[state.inspect.statement].expressions

    if len(expressions) != 0 and isinstance(expressions[0], pyvex.expr.ITE):

        state.scratch.temps[expressions[0].cond.tmp] = modify_value

        state.inspect._breakpoints['statement'] = []

 

def symbolic_execution(start_addr, hook_addr=None, modify=None, inspect=False):

    global b, relevants, modify_value

    if hook_addr != None:

        b.hook(hook_addr, retn_procedure, length=5)

    if modify != None:

        modify_value = modify

    state = b.factory.blank_state(addr=start_addr, remove_options={simuvex.o.LAZY_SOLVES})

    if inspect:

        state.inspect.b('statement', when=simuvex.BP_BEFORE, action=statement_inspect)

    p = b.factory.path(state)

    p.step()

    while p.successors[0].addr not in relevants:

        p = p.successors[0]

        p.step()

    return p.successors[0].addr

 

def retn_procedure(state):

    global b

    ip = state.se.any_int(state.regs.ip)

    b.unhook(ip)

    return

 

def fill_nop(data, start, end):

    global opcode

    for i in range(start, end):

        data[i] = opcode['nop']

 

def fill_jmp_offset(data, start, offset):

    jmp_offset = struct.pack('<i', offset)

    for i in range(4):

        data[start + i] = jmp_offset[i]

 

#python deflat.py check_passwd_32_flat 0x80488B0

if __name__ == '__main__':

    if len(sys.argv) != 3:

        print 'Usage: python deflat.py filename function_address(hex)'

        exit(0)

    opcode = {'a':'\x87', 'ae': '\x83', 'b':'\x82', 'be':'\x86', 'c':'\x82', 'e':'\x84', 'z':'\x84', 'g':'\x8F',

              'ge':'\x8D', 'l':'\x8C', 'le':'\x8E', 'na':'\x86', 'nae':'\x82', 'nb':'\x83', 'nbe':'\x87', 'nc':'\x83',

              'ne':'\x85', 'ng':'\x8E', 'nge':'\x8C', 'nl':'\x8D', 'nle':'\x8F', 'no':'\x81', 'np':'\x8B', 'ns':'\x89',

              'nz':'\x85', 'o':'\x80', 'p':'\x8A', 'pe':'\x8A', 'po':'\x8B', 's':'\x88', 'nop':'\x90', 'jmp':'\xE9', 'j':'\x0F'}

    filename = sys.argv[1]   #check_passwd_32_flat

    start = int(sys.argv[2], 16)       #0x80488B0

    barf = BARF(filename)

    base_addr = barf.binary.entry_point >> 12 << 12

    print "snowtest="+str(barf.binary)

    print 'snowtest--base_addr%#x' % base_addr  #base_addr=0x8048000

    b = angr.Project(filename, load_options={'auto_load_libs': False, 'main_opts':{'custom_base_addr': 0}})

    cfg = barf.recover_cfg(ea_start=start)

    blocks = cfg.basic_blocks

#第一步:找出6大块,可以用静态分析得到

         #1.序言:序言为函数开始地址

    prologue = start

         #2.主分发器:序言的后继为主分发器(也就是序言指向的第一个块)

    main_dispatcher = cfg.find_basic_block(prologue).direct_branch

    #3.预处理器:后继为主分器的块位预处理器(也就是后面一个代码块是主分器的)

    #ida查找办法,对着主分发器按x键看交叉引用,调用主分发器的那个块

    #4.retn:无后继的块为retn(也就是没有任何下线分支的块)

    retn, pre_dispatcher = get_retn_predispatcher(cfg)     

    #5.真实块:后继为预处理器的块为真实块

    #6.无用块:剩下的就是无用块

    relevant_blocks, nop_blocks = get_relevant_nop_blocks(cfg) #无用块(剩下的就是无用块)

    print '*******************relevant blocks************************'

    print 'func start addr prologue:%#x' % start    #0x80488b0 函数首地址为序言的地址

    print 'main_dispatcher:%#x' % main_dispatcher   #0x80488d7 主分发器

    print 'pre_dispatcher:%#x' % pre_dispatcher     #0x8048a9e 预处理器

    print 'func retn:%#x' % retn                    #0x8048a95 retn

    print 'relevant_blocks:', [hex(addr) for addr in relevant_blocks]

 

    print '*******************symbolic execution*********************'

    relevants = relevant_blocks

    relevants.append(prologue)   #加入序言

    relevants_without_retn = list(relevants)

    relevants.append(retn)

    flow = {}

    for parent in relevants: #parent依次是0x80489d40x80489f7等块的首地址

       # print "snow_parent=%x" % parent

        flow[parent] = []

    modify_value = None

    patch_instrs = {}

    for relevant in relevants_without_retn:

         #dse 0x80489d4---------------------真实块1

         #dse 0x80489f7---------------------真实块2

         #dse 0x8048a1b---------------------真实块3

         #dse 0x8048a48---------------------真实块4

         #dse 0x8048a65---------------------真实块5

         #dse 0x8048a82---------------------真实块6

         #dse 0x80488b0---------------------序言

        print '-------------------dse %#x---------------------' % relevant

        block = cfg.find_basic_block(relevant) #找到上面块的范围

        has_branches = False

        hook_addr = None

        for ins in block.instrs:   #遍历这些块打印出操作码

            #print "snowinstr="+ins.asm_instr.mnemonic

            if ins.asm_instr.mnemonic.startswith('cmov'): #cmov结尾的指令说明有分支的块有1,3

                print "snow_has_branches=%s" % ins.asm_instr.mnemonic

                patch_instrs[relevant] = ins.asm_instr

                has_branches = True

            elif ins.asm_instr.mnemonic.startswith('call'): #这些块中有call指令有2,4,5,6,序言块

                hook_addr = ins.address

                print "snow_hook_addr=%x" % hook_addr

        #难点一:使用symbolic_execution找出真实块和序言的调用关系,必须使用他的引擎运行或者动态运行

#第二步:找出真实块和序言和retn之间的调用关系,必须动态运行

        if has_branches:  #flow[relevant]分别是flow[0x80489d4],flow[0x80489f7]等等签名已经清空

            #下面可能是修改标志寄存器达到往两个分支运行

            flow[relevant].append(symbolic_execution(relevant, hook_addr, claripy.BVV(1, 1), True))

            flow[relevant].append(symbolic_execution(relevant, hook_addr, claripy.BVV(0, 1), True))

        else:

            flow[relevant].append(symbolic_execution(relevant, hook_addr))

           

    print '************************flow******************************'

         #************************flow******************************

         #执行到这里已经获得了原函数的调用关系,下面是恢复之后的关系,真实块7->retn返回块

         #0x8048a82: ['0x8048a95']  真实块6 : retn返回块

         #0x8048a65: ['0x8048a82']  真实块5 : 真实块6

         #0x8048a48: ['0x8048a82']  真实块4 : 真实块6

         #0x80488b0: ['0x80489d4']  序言    : 真实块1

         #0x80489d4: ['0x8048a1b', '0x80489f7']  真实块1 :真实块3,真实块2

         #0x8048a95: []                                                                 retn返回块

         #0x80489f7: ['0x8048a95']                                真实块2 : retn返回块

         #0x8048a1b: ['0x8048a65', '0x8048a48']  真实块3 :真实块5,真实块4

 

    for (k, v) in flow.items():

        print '%#x:' % k, [hex(child) for child in v]

 

    print '************************patch*****************************'

    flow.pop(retn)

    origin = open(filename, 'rb')

    origin_data = list(origin.read())

    origin.close()

    recovery = open(filename + '.recovered', 'wb') #输出文件路径

#第三步:nop掉无用块和主分发器和预处理器,可以静态分析得到

    for nop_block in nop_blocks:

         #无用块开始地址

         #下面是吧无用块填充0

         #snow_nop_block.start_address=80488d7

         #snow_nop_block.start_address=80488ee

         #snow_nop_block.start_address=80488f3

         #snow_nop_block.start_address=8048904

         #snow_nop_block.start_address=8048909

         #snow_nop_block.start_address=804891a

         #snow_nop_block.start_address=804891f

         #snow_nop_block.start_address=8048930

         #snow_nop_block.start_address=8048935

         #snow_nop_block.start_address=8048946

         #snow_nop_block.start_address=804894b

         #snow_nop_block.start_address=804895c

         #snow_nop_block.start_address=8048961

         #snow_nop_block.start_address=8048972

         #snow_nop_block.start_address=8048977

         #snow_nop_block.start_address=8048988

         #snow_nop_block.start_address=804898d

         #snow_nop_block.start_address=804899e

         #snow_nop_block.start_address=80489a3

         #snow_nop_block.start_address=80489b4

         #snow_nop_block.start_address=80489b9

         #snow_nop_block.start_address=80489ca

         #snow_nop_block.start_address=80489cf

         #snow_nop_block.start_address=8048a9e

        #print "snow_nop_block.start_address=%x" % nop_block.start_address

        fill_nop(origin_data, nop_block.start_address - base_addr, nop_block.end_address - base_addr + 1)   

#第四步:修复真实块序言部分的跳转指令,修复办法,

         #情况一:有一个childs,也就是一个后继的块,找到块的最后一条指令,将其抹掉后改成新的jmpjmp到自己的childs

         #情况二:有多个childs, 针对产生分支的真实块把CMOV指令改成相应的条件跳转指令跳向符合条件的分支,例如CMOVZ 改成JZ ,再在这条之后添加JMP 指令跳向另一分支 

    for (parent, childs) in flow.items():

         #snow_parent if=8048a82 真实块6

         #snow_parent if=8048a65 真实块5

         #snow_parent if=8048a48 真实块4

         #snow_parent if=80488b0 序言

         #snow_parent if=80489f7 真实块2

        if len(childs) == 1:     #有一个childs

           #print "snow_parent if=%x" % parent

            last_instr = cfg.find_basic_block(parent).instrs[-1].asm_instr

           #print "snow_last_instr addr=%x" % last_instr.address #找到最后一条指令地址,也就是jmp的地址

            file_offset = last_instr.address - base_addr  #偏移地址

            origin_data[file_offset] = opcode['jmp']

            file_offset += 1

            fill_nop(origin_data, file_offset, file_offset + last_instr.size - 1)#先填充为0

            fill_jmp_offset(origin_data, file_offset, childs[0] - last_instr.address - 5)#然后填充jmp

         #snow_parent else=80489d4真实块1

         #snow_parent else=8048a1b真实块3

        else:                                  #2childs

 

            #print "snow_parent else=%x" % parent

            instr = patch_instrs[parent]

            #print "snow_parent instr.address=%x" % instr.address #分别是80489ec8048a3d,也就是cmov..指令的地址

            file_offset = instr.address - base_addr

                            #nopcmov...指令到块结尾所有部分

            fill_nop(origin_data, file_offset, cfg.find_basic_block(parent).end_address - base_addr + 1)

            origin_data[file_offset] = opcode['j']

            origin_data[file_offset + 1] = opcode[instr.mnemonic[4:]]

            fill_jmp_offset(origin_data, file_offset + 2, childs[0] - instr.address - 6)

            file_offset += 6

            origin_data[file_offset] = opcode['jmp']

            fill_jmp_offset(origin_data, file_offset + 1, childs[1] - (instr.address + 6) - 5)

    recovery.write(''.join(origin_data))          #把结果写回去

    recovery.close()

    print 'Successful! The recovered file: %s' % (filename + '.recovered')

==========第三篇:ollvm的定制---反反混淆=============

1.控制流平展模式 Control Flow Flattening,使用办法加入参数-ollvm-fla,效果如下,

原始fla的效果,可以看见程序逻辑被打乱,出现很多分支,fla只会处理存在分支的函数



修改后的效果,switch分支插入更多垃圾代码和在骨干函数插入垃圾代码,去掉switch特征


2. 指令替换模式 Instructions Substitution,将正常的运算逻辑(+-&|等)替换成更加复杂的操作,如有表达式a=b-c.等价形式是a=br-c,经过随机处理,可以将原来的表达式随机复杂化, 使用办法加入参数-ollvm-sub

使用原始ollvm得到的效果,可以看见运算逻辑变得更加复杂


因为运算逻辑存在加减可还原,将原始数据拆分,加大分析难度,使得自动化脚本编写成本增加

 


3. 控制流伪造模式Bogus Control Flow ,在原有代码块随机插入新的代码块,随机概率是否插入新的块,原始块被克隆并且插入垃圾代码,而且是随机的,bcf可以利用参数进行随机

原始效果谓词很明显,有经验的人会调试函数然后断点排除垃圾块(比如百度加固JNI_ONLoad加固使用bcf混淆2次 100%)

 



修改后的效果将谓词长度变短,减少可见化,让其难以区别是程序逻辑还是谓词


 

4.联合使用

subfla全加并且bcf混淆100%处理一次效果


 

修改后的效果

 


 

 

参考文章

感谢下面的作者的分享

http://blog.quarkslab.com/deobfuscation-recovering-an-ollvm-protected-program.html  Deobfuscation: recovering an OLLVM-protected program

http://www.freebuf.com/articles/terminal/130142.html           反混淆:恢复被OLLVM保护的程序

https://www.oschina.net/p/decllvm                        针对ollvmida分析插件Decllvm

https://security.tencent.com/index.php/blog/msg/1122         利用符号执行去除控制流平坦化



 

 



[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
免费 2
支持
分享
打赏 + 1.00雪花
打赏次数 1 雪花 + 1.00
 
赞赏  CCkicker   +1.00 2017/05/22
最新回复 (34)
雪    币: 6366
活跃值: (4336)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
2
好详细,辛苦了
2017-5-19 11:01
0
雪    币: 1037
活跃值: (1760)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
大坟,留个qq号,线下多交流交流呗
2017-5-19 13:21
0
雪    币: 111
活跃值: (184)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
MARK,谢谢分享~
2017-5-19 15:27
0
雪    币: 1039
活跃值: (355)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
mark.
2017-5-19 15:34
0
雪    币: 781
活跃值: (1116)
能力值: ( LV5,RANK:78 )
在线值:
发帖
回帖
粉丝
6
很强.
2017-5-19 15:45
0
雪    币: 6729
活跃值: (3902)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7

厉害了我的哥,  感谢分享

我的知识百科->match目录这个在哪??????????

2017-5-19 18:57
0
雪    币: 191
活跃值: (195)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
8
MaYil 厉害了我的哥,&nbsp; 感谢分享 我的知识百科-&gt;match目录这个在哪??????????
第七题的解答网上有详细内容的
2017-5-19 22:10
0
雪    币: 3712
活跃值: (1386)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
9
2017-5-20 11:17
0
雪    币: 184
活跃值: (96)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
10
厉害了,最近正在学习安卓
2017-5-20 16:29
0
雪    币: 347
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
好贴~  非常感谢分享
2017-5-20 18:18
0
雪    币: 4687
活跃值: (253)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
mark,留着慢慢学习看
2017-5-23 10:47
0
雪    币: 208
活跃值: (469)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
13
很强很厉害
2017-5-23 14:26
0
雪    币: 11
活跃值: (80)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
mark。。。
2017-6-1 09:56
0
雪    币: 1905
活跃值: (1542)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
15
mark。学习。
2017-6-2 10:30
0
雪    币: 1392
活跃值: (4872)
能力值: ( LV13,RANK:240 )
在线值:
发帖
回帖
粉丝
16
mark  llvm混淆
2017-6-2 10:34
0
雪    币: 2177
活跃值: (2045)
能力值: (RANK:400 )
在线值:
发帖
回帖
粉丝
17
写的不错~~ 
感谢LZ。
2017-6-2 10:43
0
雪    币: 76
活跃值: (114)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
群号:57354586  建立了一个关于LLVM反思考的群,喜欢探讨贡献的朋友请进。
2017-6-2 15:03
0
雪    币: 72
活跃值: (324)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
19

厉害了~~请教一个问题哈

我使用ollvm3.5只用控制流平坦化-mllvm -fla同是x86平台,用ndk+ollvm混淆出来的流程图不如直接用ollvm的clang编译出来的那样“漂亮”。
clang直接编译的有层次分明的主分发器、预分发器

而用ndk+ollvm编译的没有这些明显的特征,显得比较混乱,请问这是为什么啊?

2017-6-2 16:18
0
雪    币: 11
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20

ida查找办法所有调用预处理器的块都是预处理器
---
上面的描述是不是错的,应该是:ida查找办法所有调用预处理器的块都是真实块

2017-7-10 23:48
0
雪    币: 153
活跃值: (181)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
21
freebuf那篇的附件你有吗?楼主
2017-7-13 00:26
0
雪    币: 29
活跃值: (97)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
niubility  哈哈哈
2017-7-17 12:19
0
雪    币: 63
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
好详细  好人一生平安
2017-7-17 13:09
0
雪    币: 102
活跃值: (1845)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
24
很详细,牛逼,mark了。
2018-8-18 17:37
0
雪    币: 249
活跃值: (346)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
好人一生平安,牛人无处不在
2018-9-29 20:18
0
游客
登录 | 注册 方可回帖
返回
//