跟着铁头干混淆4.1 ollvm控制流平坦化基本概念

ollvm 4.1 控制流平坦化基本概念

控制流平坦化基本概念

编译器参数:-mllvm -fla

英文全称 简称 编译参数
控制流平坦化 Control Flow Flattening fla -mllvm -fla

大家好,我是王铁头 一个乙方安全公司搬砖的菜鸡
持续更新移动安全,iot安全,编译原理相关原创视频文章
视频演示:https://space.bilibili.com/430241559

第1个例子 简单小程序:

c++源码

#include <cstdio>

int main(int n_argc, char** argv)
{
	int n_num = n_argc * 2;
	//scanf("%2d", &n_num);

	if (20 == n_num)
	{
		puts("20");
	}
	if(10 == n_num)
	{
	  	puts("10");
	}
	if(2 == n_num)
	{
	  	puts("2");
	}

	puts("error");

	return -1;
}

Android.mk

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := wtt
LOCAL_SRC_FILES := test.cpp
include $(BUILD_EXECUTABLE)

Application.mk

注意,这里加了 -mllvm -fla参数 表示开启控制流平坦化

APP_ABI := armeabi armeabi-v7a
APP_PIE:= true
APP_CPPFLAGS := -frtti  -std=c++11 -mllvm -fla

未混淆前流程图

跟着铁头干混淆4.1 ollvm控制流平坦化基本概念

只用 控制流平坦化混淆后的流程图

跟着铁头干混淆4.1 ollvm控制流平坦化基本概念

混淆前后图解

混淆前:
跟着铁头干混淆4.1 ollvm控制流平坦化基本概念

混淆后:跟着铁头干混淆4.1 ollvm控制流平坦化基本概念

代码本来是依照逻辑顺序执行的,控制流平坦化是把,原来的代码的基本块拆分。

把本来顺序执行的代码块用 switch case打乱分发,用分发器和一个变量,把原本代码的逻辑连接起来。

让你不知 道代码块的原本顺序。让逆向的小老弟一眼看过去不知道所以然,不知道怎么去分析。

复杂的事变简单

c++源码编译器在编译你写的源码的时候

为了提升执行速度,做了大量的把复杂事情变简单的编译优化动作

比如你写了下面这一行代码

int n = 3 + 3 - 1  + x;

编译器会直接优化成:

int n = 5 + x;

在汇编层,你是看不到 3 3 1这三个中间数字的

你只能看到 5

这个流程有个专业的术语 叫 常量折叠


编译器 把复杂的事情变简单

混淆工具 把简单的事情变复杂

第2个例子 把妹小程序

就像下面的代码,这是一个把妹小程序的c++代码

程序写了一个肥宅拿钱去和妹子约会的场景

把妹小程序c++源码

#include <cstdio>

int ba_mei(int n_rmb);

//逛街小程序
int main(int n_argc, char** argv)
{
    //带了点钱出门
    int n_ret = ba_mei(n_argc);
    return n_ret;
}

//把妹函数 参数:软妹币数量
int ba_mei(int n_rmb)
{
    puts("和女票一起出门");
    puts("和女票走路去花店买花");
	n_rmb = n_rmb - 200;
    
    //如果手头的钱大于 800
	if(n_rmb >= 800)
	{
	  	puts("和女票去迪士尼");
        n_rmb = n_rmb - 500;
	}
    else
    {
        puts("和女票去博物馆");
        n_rmb = n_rmb - 50;
    }

    puts("逛累了 和女票打的去吃饭");
	n_rmb = n_rmb - 30;

    //如果手头的钱大于 500
    if(n_rmb >= 500)
	{
	  	puts("和女票去吃海底捞");
        n_rmb = n_rmb - 300;
	}
    else
    {
        puts("和女票去吃黄焖鸡米饭");
        n_rmb = n_rmb - 40;
    }

    puts("买了两瓶矿泉水");
    n_rmb = n_rmb - 3;

    //如果手头的钱大于 100
    if(n_rmb > 100)
    {
        puts("买包华子");
        n_rmb = n_rmb - 70;
    }
    else
    {
        puts("买包红双喜");
        n_rmb = n_rmb - 10;
    }

    puts("静静的抽了一根烟");
	puts("和女票一起回家");

	return n_rmb;
}

这个程序是一个把妹小程序

看源码能清晰的看懂逻辑 看懂程序大概做了些啥事,

通过ida进行反编译 ,也是可以清晰的看到逻辑的 基本上能看懂 程序干了啥

在有分支语句的时候,判断的依据是什么

把妹小程序反编译后的代码

跟着铁头干混淆4.1 ollvm控制流平坦化基本概念

这里 可以看到做了一些编译优化 v4这里是花掉的钱

-250这里是 先减去200 再减去50

-700 也是 先减去200 再减去500

在看看被混淆过的程序是啥样

把妹小程序 控制流平坦化混淆后的流程图

跟着铁头干混淆4.1 ollvm控制流平坦化基本概念

把妹小程序混淆后 ida反编译结果

跟着铁头干混淆4.1 ollvm控制流平坦化基本概念

跟着铁头干混淆4.1 ollvm控制流平坦化基本概念

经过 ollvm 控制流平坦化混淆之后 流程变得不清晰

没混淆之前,这个程序干了点啥是很清晰的 逛街干了点啥 都很清楚

混淆之后,完全不知道程序的先后顺序,到底是先买华子,还是先吃海底捞,

去了博物馆,是不是还顺带去了趟迪士尼?

这是个举例子的程序,如果在真实算法里,也是一样的懵 你完全不知道 ,参数先后运算的顺序是咋样的

跟着铁头干混淆4.1 ollvm控制流平坦化基本概念

这里 ,真实块和真实块直接 不再像之前一样 直接连接

而是被打散, 执行下一个真实块的时候 要经过分发器,然后经过几次比较 才能达到下一个真实块

图解控制流平坦化

举个通俗的例子:

你约了个人 到公园见面, 公园离你比较近,你直接直走就能到公园,

跟着铁头干混淆4.1 ollvm控制流平坦化基本概念

但是现在

一个劫匪要跟你在公园见面,劫匪不直接告诉你去公园,他怕你被跟踪,暴露了他

劫匪也不敢跟你发短信 ,怕jc叔叔直接监控你的手机

劫匪想了一个好办法

跟着铁头干混淆4.1 ollvm控制流平坦化基本概念

他给了你一个信物 假设这里是一把 大宝剑

然后他的同伙分散在各个路口

他的同伙看到这把大宝剑就知道是你过来了

他的同伙指示你应该往哪边走

本来你左拐直接到公园

这么一波操作后,你拐了七八次,走了好远一段冤枉路,才到公园、

这里,上面例子里的 信物 大宝剑,就相当于代码里的 v6变量

执行完真实块之后, v6被赋值

后续各种判断 都是基于 v6做的判断

跟着铁头干混淆4.1 ollvm控制流平坦化基本概念

跟着铁头干混淆4.1 ollvm控制流平坦化基本概念

所以,控制流平坦化就是用 switch case把整个程序流程打散

用一个变量的值 ,在多个比较语句中,走了不同的路,衔接起了被打散的真实块

这里,真实块的实际执行顺序,跟混淆之前还是一样的

混淆不会改变程序的整体逻辑,要不然被混淆之后,程序直接不能跑了,跑出错误的值,谁还用这个框架

就像之前举例说的劫匪给你的信物,每次到一个路口,劫匪的同伙都告诉你要往拿走,

最终,你走了很多弯路,到达了终点。


第3个例子 瞎写的算法

下面用一段的简单的算法源码举例子

c++源码

#include <cstdio>

int main(int n_argc, char** argv)
{
	int n_num = n_argc * 2;
	//scanf("%2d", &n_num);

	for (int i = 0; i < 100; ++i)
	{
		n_num = n_num + 3;
		n_num = n_num + 2;
		printf("%d",n_num);
	}
	
	if(n_num >= 1000)
	{
		n_num = n_num + 200;
	  	puts("low");
	}
	else
	{
		n_num = n_num - 300;
		n_num = n_num / 2; 
	  	puts("high");
	}

	int n_num1 = n_num + n_argc + 8;
	for (int i = 0; i < n_argc; ++i)
	{
		n_num1 = n_num1 + n_argc;
		n_num1 = n_num1 + n_num * 2;
		printf("%d",n_num1);
	}

	int n_ret = n_num + n_num1 + n_argc;
	for (int i = 0; i < n_num1 - n_num; ++i)
	{
		n_ret = n_num + i;
		printf("%d",n_ret);
	}

	if (n_ret > 65536)
	{
		n_ret = n_ret - 65536;
		puts("sub 65536");

	}

	return n_ret;
}

上面的算法跟大佬们经常看到 rsa aes 这些不同

没啥复杂的操作 只有加减乘除 几个 if else的判断

未混淆之前的流程图

跟着铁头干混淆4.1 ollvm控制流平坦化基本概念

控制流平坦化 混淆之后的流程图

跟着铁头干混淆4.1 ollvm控制流平坦化基本概念

F5大法反编译后的代码

跟着铁头干混淆4.1 ollvm控制流平坦化基本概念

跟着铁头干混淆4.1 ollvm控制流平坦化基本概念

跟着铁头干混淆4.1 ollvm控制流平坦化基本概念

OLLVM把简单的流程变复杂,会拖慢程序的运行效率

所以,一般只保护一些 比较重要的代码

持续更新移动安全,iot安全,编译原理相关原创视频文章
视频演示:https://space.bilibili.com/430241559

相关资料关注公众号 回复 ollvm 下载:
跟着铁头干混淆4.1 ollvm控制流平坦化基本概念

上一篇:实战爬取Plati官网游戏实时最低价格-Python


下一篇:vue过滤器vue.filter