Obfuscator-LLVM

发布于 2019-05-28  50 次阅读


简介

相关项目

git clone -b llvm-4.0 https://github.com/obfuscator-llvm/obfuscator.git
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_INCLUDE_TESTS=OFF ../obfuscator/
make -j10

说明: 直接使用官方的编译选项时会报错

CMake Error at cmake/modules/AddLLVM.cmake:1163 (add_custom_target):
  add_custom_target cannot create target "check-llvm-bindings-ocaml" because
  another target with the same name already exists.  The existing target is a
  custom target created in source directory "/opt/obfuscator-llvm-4.0/test".
  See documentation for policy CMP0002 for more details.
Call Stack (most recent call first):
  cmake/modules/AddLLVM.cmake:1226 (add_lit_target)
  test/CMakeLists.txt:150 (add_lit_testsuites)

就要多加一个编译选项 -DLLVM_INCLUDE_TESTS=OFF

混淆配置参数

开启控制流扁平化

-mllvm -fla 开启控制流扁平化
-mllvm -split 激活基本块划分。一起使用时能提高打平能力。
-mllvm -split_num=3 如果激活控制流打平,对每一个基本块应用三次控制流打平。默认使用1次。

开启指令替换

-mllvm -sub 开启指令替换
-mllvm -sub_loop=3 如果激活了指令替换,使用这个选项在一个函数中应用3次指令替换。默认应用1次。

开启虚假控制流

-mllvm -bcf 开启虚假控制流
bcf可以配合下面参数使用
-mllvm -bcf_loop=3 设置函数混淆次数为3次 不加此选项默认为1次
-mllvm -bcf_prob=40 设置代码块被混淆的概率是40%,默认30%

例如下面这个程序

#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("errors");
        return 1;
    }
    if (check_password(argv[1]))
    {
        puts("Congratulationss!");
    }
    else
    {
        puts("errors");
    }
    return 0;
}

编译

ollvm_build/bin/clang 1.c -emit-llvm -S -mllvm -sub -mllvm -fla  -mllvm -sub_loop=3 -mllvm -split_num=3  -mllvm -bcf 
ollvm_build/bin/clang 1.ll -o 1

然后再用ida查看程序控制流。

Armariris

下面是一个更加全面的ollvm的版本Armariris

安装

mkdir obf
cd obf
clone git@github.com:gossip-sjtu/Armariris.git
cmake -DCMAKE_BUILD_TYPE:String=Release ./Armariris
make -j10

分析

我们以如下程序为例

#include<stdio.h>
void init(){
    int t=0;
    return;

}
int hello(){
    return 5;
}

void say(){
    printf("Hello This is HAPPY!");
}
int main(){
    init();
    int a=1;
    int t=hello();
    int b=5;
    int c=a+b;
    say();
    //int d=a+c;
    return 0;
}

编译混淆

clang 1.c -emit-llvm -S -mllvm -sub -mllvm -fla -mllvm -sobf
clang 1.ll -o 1

此处以字符串混淆为例,我们反汇编生成的二进制文件,发现

int say()
{
  return printf(&format);
}

没有了直接显示的字符串,而且format的字符串也是加密后的

.data:0000000000404030 format          db 8Bh                  ; DATA XREF: say+8↑o
.data:0000000000404030                                         ; _datadiv_decode12115958352748561385+53↑r ...
.data:0000000000404031                 db 0A6h
.data:0000000000404032                 db 0AFh
.data:0000000000404033                 db 0AFh
.data:0000000000404034                 db 0ACh
.data:0000000000404035                 db 0E3h
.data:0000000000404036                 db  97h
.data:0000000000404037                 db 0ABh
.data:0000000000404038                 db 0AAh
.data:0000000000404039                 db 0B0h
.data:000000000040403A                 db 0E3h
.data:000000000040403B                 db 0AAh
.data:000000000040403C                 db 0B0h
.data:000000000040403D                 db 0E3h
.data:000000000040403E                 db  8Bh
.data:000000000040403F                 db  82h
.data:0000000000404040                 db  93h
.data:0000000000404041                 db  93h
.data:0000000000404042                 db  9Ah
.data:0000000000404043                 db 0E2h
.data:0000000000404044                 db 0C3h
.data:0000000000404044 _data           ends

然后在IDA的view->Open Subviews->Promixity Browser打开概览图

我们发现,在程序init的时候,有一段解密代码将format进行了解密操作。

__int64 datadiv_decode12115958352748561385()
{
  __int64 result; // rax
  signed int v1; // er9
  int v2; // [rsp+18h] [rbp-8h]
  unsigned int v3; // [rsp+1Ch] [rbp-4h]

  v3 = -566357037;
  v2 = 0;
  while ( 1 )
  {
    result = v3;
    if ( v3 == -692340892 )
      break;
    if ( v3 == -566357037 )
    {
      format[v2] ^= 0xC3u;
      v1 = -692340892;
      if ( (unsigned __int64)v2 < 0x14 )
        v1 = -566357037;
      v3 = v1;
      ++v2;
    }
  }
  return result;
}

我们可以模拟这段解密操作。下面是解密脚本

#include <iostream>
unsigned char ida_chars[] =
{
  0x8B, 0xA6, 0xAF, 0xAF, 0xAC, 0xE3, 0x97, 0xAB, 0xAA, 0xB0,
  0xE3, 0xAA, 0xB0, 0xE3, 0x8B, 0x82, 0x93, 0x93, 0x9A, 0xE2,
  0xC3
};

void decode()
{
    __int64 result; // rax
    signed int v1; // er9
    int v2; // [rsp+18h] [rbp-8h]
    unsigned int v3; // [rsp+1Ch] [rbp-4h]

    v3 = -566357037;
    v2 = 0;
    while (1)
    {
        result = v3;
        if (v3 == -692340892)
            break;
        if (v3 == -566357037)
        {
            *(ida_chars + v2) ^= 0xC3u;
            v1 = -692340892;
            if ((unsigned __int64)v2 < 0x14)
                v1 = -566357037;
            v3 = v1;
            ++v2;
        }
    }
}
int main()
{
    decode();
    for (int i=0;i<(sizeof(ida_chars)/sizeof(unsigned char));i++)
    {
        std::cout << ida_chars[i];
    }
}

运行程序后,可知原字符串为Hello This is HAPPY!