看雪·眾安 2021 KCTF 秋季賽 | 第五題設計思路及解析

Editor發表於2021-11-29

看雪·眾安 2021 KCTF 秋季賽 | 第五題設計思路及解析


看雪·眾安 2021 KCTF秋季賽的第五題《拔刀相向》歷時4天,已於今天中午12點關閉攻擊通道,截止答題。那麼目前的比賽情況如何呢?


攻擊方排名前10如下:

看雪·眾安 2021 KCTF 秋季賽 | 第五題設計思路及解析


已開放賽題的5支防守隊伍排名如下:


看雪·眾安 2021 KCTF 秋季賽 | 第五題設計思路及解析


經統計,第五題《拔刀相向》圍觀人數1094


看雪·眾安 2021 KCTF 秋季賽 | 第五題設計思路及解析

最終無人能攻破該題。是題目難度太高還是思路太過巧妙呢?想必大家都迫切地想知道此題何解吧看雪·眾安 2021 KCTF 秋季賽 | 第五題設計思路及解析接下來一起來看看解析吧~



出題團隊簡介


第五題《拔刀相向》出題方: 劉大爺粉絲團99號 戰隊


看雪·眾安 2021 KCTF 秋季賽 | 第五題設計思路及解析



賽題設計思路


謹以此題紀念高光榮教授(1945年-2021年9月12日)。

 

高光榮教授是中國第一位 MIT 計算機博士,也是整個計算機學界中泰斗級的人物,與其導師 Jack Dennis 一同開創了資料流程式設計模型的先河,在平行計算與計算機系統結構領域產生了深遠的影響。

 

本題目在高光榮教授提出的 Codelets 的細粒度執行緒執行模型下,採納了基於共享記憶體模型的執行時 DARTS 作為框架,試圖通過並行多執行緒排程的方式打斷傳統逆向工程中所習慣的控制流分析方法。在單核效能停滯不前的背景下,傳統的程式碼混淆往往會帶來無法容忍的效能開銷,而並行程式設計正規化則可以在混淆資料變換演算法的前提下,甚至帶來額外的效能提升。

 

本題演算法主要由三部分構成:

  • 自定義的簡陋 Hash 演算法,用以對輸入的使用者名稱進行處理。

  • 一個基於多項式商環的 RSA 演算法。因為多項式相較於整數更加易於分解,故而可以還原得到輸入。

  • 一個隨機生成的 16 輪 Feistel 結構的變換。


這三個部分分別對應密碼學中最常見的三種演算法:資訊摘要,非對稱加密,對稱加密,旨在模擬實際情況中所用的密碼學演算法(為了避免側通道等非預期解法故而避開了標準的 AES 和 RSA 等等)。

 

題目程式碼已經上傳,如需詳細研究可以對照原始碼(還有原始碼中的解題指令碼 .ipynb 檔案)一同參看。


主要流程

看雪·眾安 2021 KCTF 秋季賽 | 第五題設計思路及解析

題目驗證流程如圖所示,直線箭頭標識著主要的 Codelets/ThreadedProcedure 之間的資料依賴。曲線箭頭則標識了一些內部的 Codelets 的資料依賴。在 DARTS 執行時下,無資料依賴的 codelets 會被並行排程。高度並行化使得在任意時刻下斷點,記憶體中可能都不會存在一個嚴格的演算法中間狀態。比如 Feistel 演算法中的不同層級可能會同時執行。blockHash 的結果可能也並未儲存在某個特定的位置,而是僅僅存在於其後的 blockAdder 加法器中。


資料結構


本題主要資料都在於 Poly 結構體中:

struct Poly {    PolyBlock blocks[6];    Poly() :blocks() {}    PolyBlock& operator[](int i) {        return blocks[i];    }    void reset(){        for(int i = 0; i < 6; i++){            blocks[i].reset();        }    }};


Poly 是一個最高次數為 23 次的多項式。加上常數項一共有 24 項,從低到高每 3 項為一個 PolyBlock,一共有 6 個 PolyBlock。

 

PolyBlock 是一個包含了 4 個係數的多項式塊,每個係數都是模 61 意義下的。故而可以用 6 個 bit 表示一個係數(61 < 2^6)。四個係數一共 4*6 = 24 個 bit,也就是 3 個 bytes。把多項式塊的內部 bits 順序全部打亂,再異或一個固定的隨機 mask,就是 PolyBlock 的混淆後的二進位制表示:

struct PolyBlock {    uint8_t ele[3];    PolyBlock() : ele(){}    void reset() {        ele[0] = 0xf7;        ele[1] = 0x31;        ele[2] = 0x89;    }    //省略部分程式碼    PolyBlock& operator+=(PolyBlock& rhs) {        this->set<3>(((*this).get<3>() + rhs.get<3>()) % 61);        this->set<0>(((*this).get<0>() + rhs.get<0>()) % 61);        this->set<2>(((*this).get<2>() + rhs.get<2>()) % 61);        this->set<1>(((*this).get<1>() + rhs.get<1>()) % 61);        return *this;    }    friend PolyBlock operator+(PolyBlock lhs, PolyBlock& rhs) {        lhs += rhs;        return lhs;    }    //省略部分程式碼};


本題中大部分資料變換的最小單位都是 PolyBlock。


blockHash


這個是基於 xorshift 結構構造的一個簡易的類似於 PRF (pseudo random function) 的作用。演算法很簡單:

// 輸入是長度為 9 的 uint8_t 的陣列 data,也就是 3 個 PolyBlock。輸出是 1 個 PolyBlock (也就是 3 個 uint8_t)。void blockHasher::fire(){    uint64_t x = *(uint64_t*)hh.data;    x ^= hh.data[8];    x ^= x >> 12;    x ^= x << 25;    x ^= x >> 27;    x *= 0x2545F4914F6CDD1DULL;    *((uint8_t*)result) = x&0xFF;    *((uint8_t*)result+1) = (x >> 8)&0xFF;    *((uint8_t*)result+2) = (x >> 16)&0xFF;    toSignal->decDep();}


這個 PRF 在構造使用者名稱 hash 和 Feistel 對稱加密中都有用到。


StrHasher


隨手寫的一個基於上述 blockHash 的 Hash 演算法。沒用到什麼構造,所以效果很差。因為做出來本題不需要研究這種不可逆的 Hash 演算法,所以沒太花功夫。具體演算法可以自己看看程式碼,做題的時候只需要在記憶體裡提取 'KCTF' 對應的 Hash 資料即可。


PolyFeistel


一個隨機生成的 16 層的 6 路 Feistel 網路,目的是把 blockHash 這種類似於 PRF 的玩意轉化為 PRP (pseudo random permutation)(參看 Luby-Rackoff 定理)。


一層隨機 Feistel 結構如圖所示:(隨機生成的結構展示,與實際中具體的資料變換不同)


看雪·眾安 2021 KCTF 秋季賽 | 第五題設計思路及解析

程式從上一層的多項式中隨機選取 3 個 polyBlock 作為 blockHash 的輸入,生成的輸出與剩下三個 polyBlock 相加(在多項式環中),然後將用來 hash 的 block 和 blockAdder 的輸出隨機排列成下一層的多項式。

 

因為 Feistel 結構可逆,所以這一層結構可以逆向回去。


PolyRSA


PolyRSA 是一個在 GF(61) 下模多項式 t^24 + 51*t^21 + 55*t^20 + 2*t^19 + 3*t^18 + 35*t^17 + 13*t^16 + 5*t^15 + 25*t^14 + 40*t^13 + 11*t^12 + 17*t^11 + 48*t^10 + 26*t^9 + 48*t^8 + 54*t^7 + 5*t^6 + 19*t^5 + 28*t^4 + 43*t^2 + t + 38 的商環中進行的 RSA。


首先題目中實現了模上述多項式的乘法,然後通過快速冪演算法,計算出 poly 的 2^0 + 2^7 + 2^21 + 2^31 次方。


通過 SageMath 很容易求得上述多項式可以分解為 (t^11 + 19*t^6 + 55*t^5 + 2*t^4 + 7*t^3 + 30*t^2 + 59*t + 30) * (t^13 + 51*t^10 + 55*t^9 + 44*t^8 + 9*t^7 + 33*t^6 + 13*t^5 + 29*t^4 + 29*t^3 + 2*t^2 + 58*t + 46)。所以對應的 RSA 的 phi 為 (61^11-1)*(61^13-1),按照類似於 RSA 的演算法,取逆後即可還原。

 

多項式的乘法經過了複雜的混淆,(是由 trivial 的 O(n^2) 多項式乘法演算法的中間步驟累加而成,生成方法可以看指令碼),可能比較難以識別出模多項式。實際上如果通過快速冪演算法識別出 RSA 的結構,可以簡單通過特殊資料嘗試出實際的模多項式。另一種可能的方法是已知多項式最高次冪必為 24,然後猜測分解後兩多項式的冪次,從而直接得到 phi(不需要分解)。


總結


本題主要為嘗試結合平行計算的資料流模型和程式碼混淆的思路,通過多執行緒的方法干擾選手進行除錯,通過資料流排程的思路打斷選手進行控制流分析。同時,程式中儘可能將相互沒有資料依賴的計算分離開來,在提高效能的同時,還避免了程式的某個時刻的某個 buffer 裡存放著演算法完整的某個中間狀態。

 

另外,隨機生成的 Feistel 網路,將演算法的核心由控制流圖轉化為資料部件的依賴關係。而這種依賴關係 Declaration 的位置和資料真正發生計算的位置並不相同。將演算法描述和演算法執行解耦,也是程式碼混淆的一個重要思路。

 

最後,我們可以更多的想象一下,現有 OLLVM 已經可以對控制流進行復雜的混淆變化,是否以後可以有演算法對資料流圖進行自動混淆呢?可能有 Dataflow flattening,或者 bogus dataflow 等等,又或者其他專用於資料流的混淆方法?

 

本題採用 Username/SerialNumber 的規則,以下給出兩組序列號:


程式的 SHA256 為 56DF4D8D86305660747BC63FE31E8A8A6ADEA5D2211A723E153E018CA55E1CB4。


第一組序列號如下(內容與附件 ctf.7z 壓縮包中的 SN.txt 相同)
Username:56DF4D8D86305660
SN: 4A2CD38229906B47E4C04723C0BEAD86A599

 

第二組為所要求的序列號:
Username:KCTF
SN: 4E6B0C83AA2E6B6E946477B2149A8816D730



1、看雪·眾安 2021 KCTF 秋季賽 | 第二題設計思路及解析


2、看雪·眾安 2021 KCTF 秋季賽 | 第三題設計思路及解析


3、看雪·眾安 2021 KCTF 秋季賽 | 第四題設計思路及解析


看雪·眾安 2021 KCTF 秋季賽 | 第五題設計思路及解析

看雪·眾安 2021 KCTF 秋季賽 | 第五題設計思路及解析


第六題《窺伺者誰》正在火熱進行中,

還在等什麼,快來參賽吧!


看雪·眾安 2021 KCTF 秋季賽 | 第五題設計思路及解析


- End -


看雪·眾安 2021 KCTF 秋季賽 | 第五題設計思路及解析

公眾號ID:ikanxue

官方微博:看雪安全

商務合作:wsc@kanxue.com

相關文章