跟著鐵頭幹混淆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
未混淆前流程圖
只用 控制流平坦化混淆後的流程圖
混淆前後圖解
混淆前:
混淆後:
程式碼本來是依照邏輯順序執行的,控制流平坦化是把,原來的程式碼的基本塊拆分。
把本來順序執行的程式碼塊用 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進行反編譯 ,也是可以清晰的看到邏輯的 基本上能看懂 程式幹了啥
在有分支語句的時候,判斷的依據是什麼
把妹小程式反編譯後的程式碼
這裡 可以看到做了一些編譯優化 v4這裡是花掉的錢
-250這裡是 先減去200 再減去50
-700 也是 先減去200 再減去500
在看看被混淆過的程式是啥樣
把妹小程式 控制流平坦化混淆後的流程圖
把妹小程式混淆後 ida反編譯結果
經過 ollvm 控制流平坦化混淆之後 流程變得不清晰
沒混淆之前,這個程式幹了點啥是很清晰的 逛街幹了點啥 都很清楚
混淆之後,完全不知道程式的先後順序,到底是先買華子,還是先吃海底撈,
去了博物館,是不是還順帶去了趟迪士尼?
這是個舉例子的程式,如果在真實演算法裡,也是一樣的懵 你完全不知道 ,引數先後運算的順序是咋樣的
這裡 ,真實塊和真實塊直接 不再像之前一樣 直接連線
而是被打散, 執行下一個真實塊的時候 要經過分發器,然後經過幾次比較 才能達到下一個真實塊
圖解控制流平坦化
舉個通俗的例子:
你約了個人 到公園見面, 公園離你比較近,你直接直走就能到公園,
但是現在
一個劫匪要跟你在公園見面,劫匪不直接告訴你去公園,他怕你被跟蹤,暴露了他
劫匪也不敢跟你發簡訊 ,怕jc叔叔直接監控你的手機
劫匪想了一個好辦法
他給了你一個信物 假設這裡是一把 大寶劍
然後他的同夥分散在各個路口
他的同夥看到這把大寶劍就知道是你過來了
他的同夥指示你應該往哪邊走
本來你左拐直接到公園
這麼一波操作後,你拐了七八次,走了好遠一段冤枉路,才到公園、
這裡,上面例子裡的 信物 大寶劍,就相當於程式碼裡的 v6變數
執行完真實塊之後, v6被賦值
後續各種判斷 都是基於 v6做的判斷
所以,控制流平坦化就是用 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的判斷
未混淆之前的流程圖
控制流平坦化 混淆之後的流程圖
F5大法反編譯後的程式碼
OLLVM把簡單的流程變複雜,會拖慢程式的執行效率
所以,一般只保護一些 比較重要的程式碼
持續更新移動安全,iot安全,編譯原理相關原創視訊文章
視訊演示:https://space.bilibili.com/430241559
相關資料關注公眾號 回覆 ollvm 下載: