看雪.紐盾 KCTF 2019 Q3 | 第十題點評及解題思路
Editor發表於2019-10-08
胸懷大志的孫策希望能夠有一天光宗耀祖。
父親孫堅給了他一塊玉璽並告訴他,只要他能打下2880座城鎮,並在每個城鎮祭拜玉璽,就能如願。
孫策照著父親的話去做了,最終贏得了勝利,並將這塊玉璽視為傳家寶。
袁術看見孫策成功了,也想模仿。他日夜監視著孫策的動向,希望能夠得到這件傳家寶。
他能成功嗎?
題目簡介
本題共有916人圍觀,最終只有4支團隊攻破成功。可見這道題的難度也十分大,出題者巧妙佈局,引人步步深入,在破題的過程中方能見識到步步皆是棋局,走錯一步滿盤皆輸。
攻破此題的戰隊排名一覽:
那麼究竟出題者運用了什麼巧妙戰術,讓我們一起來看一下吧。
看雪評委crownless點評
這題是一道Windows CrackMe,程式存在大量SMC,且巢狀2880層,很符合題目“傳家之寶”的解釋,考驗了破解者寫指令碼破解的能力。但實際上,有另外的方法能直接提取核心aes程式碼。
出題團隊簡介
本題出題戰隊Archaia:
下面是相關簡介:
科銳學員隊,即將畢業。
設計思路
此題設計特徵如下:
- 所有資料都是明的,沒有需要攻擊者破解的數
- 所有資料的相互關係也是明的,沒有隱晦的數學關係
- 所有解題所需的資訊都在明處,沒有隱藏任何線索
- 所有攻擊者看到的線索都是真的,沒有設定誤導線索
- 所有指令都是會自動執行到的,沒有需要尋找的程式碼
- 所有流程都很直白,沒有異常,沒有中斷,沒有回撥
- 所有程式碼都是完整的,沒有呼叫任何第三方API(介面部分除外)
有的,它只有一個難點:
長!然而,比賽已經限制了crackme的長度。蝸牛又能比別人長出來多少呢?
況且過去也有防守題codesize較大的。並未見到有哪道題是因為程式碼太長而無法被及時破解的。就算是市面上10+M,100+M的軟體也都經常被破解。
長,能有多大用呢?其實,對於破解者來說,程式碼長不是問題。
因為破解者並無必要真的去閱讀這些程式碼!大多數時候,破解者只需要“眼看手跳”就能快速pass大量無關破解的程式碼,直搗黃龍。所以,要想擋住破解者,不僅需要程式碼長,更重要的是:需要阻止“眼看手跳”!
設計思路
如何才能阻止眼看呢?
很簡單,把程式碼加密了就行了。
程式碼一邊執行一邊解密(SMC),就能阻止眼看。如何阻止手跳呢?
所謂手跳,本質上就是下斷點。除錯方和反除錯方圍繞‘斷點’展開的鬥爭,由來已久。簡單地說:
除錯方的目標是:1)能斷下2)能繼續3)下次還能斷下
為了做到這3點,除錯方想出了很多招。在此不一一列舉了。反除錯方的目標只有1個:破壞除錯條件。
只要破壞了除錯方的除錯條件,就能瞞天過海,不讓除錯方知道程式在幹什麼。同時,還不能影響正常業務流。
(這也是“薅羊毛”和“反薅羊毛”的主要戰場)此題,為了阻止眼看手跳,引入了一種新加殼方式。我們稱之為:
蝸牛殼始祖蝸牛以序列號作為起點,上路了。
蝸牛露頭不多,但是揹著一個大蝸牛殼。
蝸牛露出來的部分,肉很少,只負責往前走一小步,隨即展開自己的蝸牛殼,丟擲自己的下一代蝸牛。
下一代蝸牛,也揹著一個蝸牛殼(稍微小一點)。
它露出來的部分,也有一點肉,也只負責往前走一小步,隨即展開自己的蝸牛殼,再丟擲自己的下一代蝸牛......
最後一代蝸牛,會檢查自己是否走到了正確的目的地(是否命中了使用者名稱)?
如果正確,則顯示‘序列號正確’;否則顯示‘錯誤’。使用者輸入的序列號,如同傳家寶,在蝸牛家族裡世代相傳。
每一代蝸牛都堅守自己的使命,孵化序列號向前演進一小步,直到到達最終理想狀態(輸入的使用者名稱)。
如果破解者能夠看清每一代蝸牛在序列號上做的那麼一小點孵化動作(露出來的那一片肉),並且全部拼裝起來(變成一整塊肉),就能知道如何從最終的使用者名稱反向逆推出輸入的序列號。反之,如果蝸牛家族能夠不讓對手看清某些蝸牛的動作(缺少幾片肉),那麼攻擊者就很難找出逆推演算法。
那麼,蝸牛家族是如何讓對手看不清的呢?
蝸牛殼技術要點
SMC加密
SMC技術能夠有效防止破解者看到未來太遠的程式碼。
解密演算法和金鑰,都在上一代的蝸牛體內,是破解者可以獲得的。所以加密演算法不必太強(強也沒用)。只是一層障眼法而已,擋住肉眼就行了。比賽實踐證明,這個技術對防護來說,貢獻不大。至多隻能當做一種輔助手段。
地址隨機化
每一代蝸牛展開蝸牛殼,丟擲下一代蝸牛時,隨機指定下一代蝸牛的執行地址。
其目的是破壞除錯目標3:“下次還能斷下”比賽實踐證明,這個地址隨機化沒能有效阻止高手的跟蹤。要麼被鎖定了隨機數,要麼被動態跟蹤。
切片技術
殼歸殼,肉才是需要保護、不想被破解者看清的核心內容。
此題中所謂肉就是:從序列號到使用者名稱的計算過程,原本是一段魔改AES程式碼(簡稱:AES_X)。
(注:這個計算過程一定要存在高效逆演算法,否則出題方難以設計序號產生器)
需要將AES_X的程式碼切成切片,然後將每個切片貼到每層殼上。每層殼被執行的時候,相應的切片也會被執行。且需要保證:1)切片程式碼可以執行於任何記憶體地址(因為地址隨機化了)2)每相鄰2層切片之間的執行現場能夠平順銜接(不能被殼干擾)3)切片不可以迴圈,也最好不要分支(無分支程式設計技術,在這裡發揮著重要作用)
有多少層殼,就切成多少片切片。如果層數大於指令數,就先對指令膨脹,然後再切片。
力圖做到:破解者任意缺失了某個切片,都難以找出AES_X的逆運算。比賽實踐證明,此題殼與肉的特徵明顯。對手找到後分離了殼和肉,去殼取肉。
程式碼獨立
蝸牛殼在運作的時候,不呼叫任何外部的API,不使用外部的堆疊。程式碼執行時,不離開蝸牛殼。
這樣設計的目的是:防止破解者在蝸牛殼之外設定斷點,截獲殼內資訊。比賽實踐證明,雖然我們試圖迴避殼內程式碼與外界的互動,但是卻沒提防破解者在殼內種植程式碼並逐層hook關鍵操作。而且,我們還是留下了一下操作外部資料的痕跡。就算是我們採用了記憶體校驗,也難以保證校驗程式碼不被patch掉。
對抗指令碼
破解者在看了前面幾層殼的架勢之後,不會真的一層一層單步跟蹤,而會寫指令碼脫殼取肉。
本題將多種反除錯手段和資料變換,分級別部署在不同層的蝸牛殼中。每當遇到了新的反除錯技,都需要破解者重新改寫指令碼,以延長破解時間。比賽實踐證明,高手並未在反除錯上花太多時間,且完全沒有在資料變換上花任何時間。
總結與展望
《蝸牛的傳家寶》檔案長度:1,459,200位元組核心程式碼:12902行原始碼工程:14400個檔案(14.5GB)編譯耗時:7小時(每次要編譯,都要下個決心)反除錯技:5種難度設計:10個級別資料變換:79種保護層數:2880層
此題招式樸實,沒有高大上的演算法,也沒有機關算盡的詭計,不防單步跟蹤,只是想讓破解者‘飛’不起來而已。但是由於設計不完善,此題中留下了一系列比較致命的弱點,沒能把破解者粘在地上,還是飛起來了。
我們將爭取在下次參賽中彌補上這些漏洞。敬請期待。
解題思路
因為有程式碼段和棧的遷移,所以程式碼中不能硬編碼絕對地址,都是相對當前EIP的相對偏移,所以取解碼開始地址、取全域性變數地址、EBP地址都是大多是根據當前EIP計算出來的,如下:
計算下一層程式碼入口地址,準備遷移程式碼並call執行 .text:0040171D E8 00 00 00 00 call $+5 .text:00401722 59 pop ecx .text:00401723 81 E9 48 10 40 00 sub ecx, 401048h .text:00401729 81 C1 43 11 40 00 add ecx, 401143h .text:0040172F E8 00 00 00 00 call $+5 .text:00401734 5F pop edi .text:00401735 81 EF 5A 10 40 00 sub edi, 40105Ah .text:0040173B 81 C7 10 10 40 00 add edi, 401010h 計算下一層解碼開始地址 .text:00401845 E8 00 00 00 00 call $+5 .text:0040184A 5E pop esi .text:0040184B 81 EE 70 11 40 00 sub esi, 401170h .text:00401851 81 C6 01 12 40 00 add esi, 401201h .text:00401857 B9 27 CD 15 00 mov ecx, 15CD27h .text:0040185C FC cld .text:0040185D EB 1B jmp short loc_40187A 計算資料全域性變數開始地址 .text:0040185F 9D popf .text:00401860 5F pop edi .text:00401861 5E pop esi .text:00401862 5A pop edx .text:00401863 59 pop ecx .text:00401864 5B pop ebx .text:00401865 58 pop eax .text:00401866 E8 00 00 00 00 call $+5 .text:0040186B 5E pop esi .text:0040186C 81 EE 91 11 40 00 sub esi, 401191h .text:00401872 81 C6 39 DF 55 00 add esi, 55DF39h .text:00401878 EB 05 jmp short loc_40187F 真實功能指令 .text:0040187F 8A 86 A7 01 00 00 mov al, [esi+1A7h] .text:00401885 32 86 B7 01 00 00 xor al, [esi+1B7h] .text:0040188B 88 45 FF mov [ebp-1], al
同時我們注意到只有計算解碼地址和全域性變數地址時,才用到ESI暫存器儲存地址,而區域性變數是根據EBP定址的,程式碼段和棧遷移時都會計算出相應的EBP。最重要的一點是:解碼程式碼不受解碼前執行時暫存器及棧的影響。因為程式碼模式相對比較固定,結合上面的分析資訊,對於解碼就有了思路了,可以模擬執行或動態除錯,跳過其它程式碼,只執行解碼部分程式碼,解出全部指令。雖然程式碼有小調整,修正兩次程式碼就OK了。用unicorn模擬執行,速度太慢,而後換了ida+windbg的debug方式。程式碼如下:
# -*- coding:utf-8 -*- from __future__ import print_function from idaapi import * from idc import * stop = False def scan(patt,start=0,end=0): pattern = patt if start: addr = start else: addr = MinEA() if end: addr1 = end else: addr1 = MaxEA() for i in range(addr,addr1): addr = FindBinary(addr, SEARCH_DOWN|SEARCH_NEXT, pattern) if addr != BADADDR: return addr return 0 def get_saddr(addr): while True: addr = scan('E8 00 00 00 00 5e 81',start = addr,end=0x55e602) if addr == 0: return 0 if Byte(addr+12) == 0x81 and Byte(addr+18) == 0xb9: return addr elif Byte(addr+12) == 0x81 and Byte(addr+18) == 0xeb and Byte(addr+18+2+Byte(addr+18+1)) == 0xb9: return addr addr += 5 def patch_one_round(address,waddr): SetRegValue(address,'eip') addr = waddr+3 addr = scan('85 c9',start=addr,end=0x55e602) if addr == 0: return False addr += 2 while Byte(addr) == 0xeb: off = Byte(addr+1) off = off if off < 0x80 else off - 0x100 addr += 2+off if Byte(addr) == 0x75: addr += 2 elif Word(addr) == 0x850f: addr += 6 else: return False if addr - waddr > 0x100: return False AddBpt(addr) EnableBpt(addr, True) continue_process() GetDebuggerEvent(WFNE_SUSP, -1) DelBpt(addr) return True def patch_one_byte(address,waddr): SetRegValue(address,'eip') addr = waddr+3 AddBpt(addr) EnableBpt(addr, True) continue_process() GetDebuggerEvent(WFNE_SUSP, -1) DelBpt(addr) return True def quick_ALT_K(saddr = 0x40163E): count = 0 # saddr = here() while True: print('start_addr:{:08X}'.format(saddr)) waddr = scan('88 46',start=saddr,end=0x55e602) if waddr == 0 or waddr > 0x55e602 or Byte(waddr+2) not in [0x1,0xff]: # waddr = scan('88 46 01',start=saddr,end=0x55e602) print('waddr err or finished!') break print('waddr:{:08X}'.format(waddr)) if waddr - saddr < 10 or waddr - saddr > 0x100: print('addr failed!') break addr_tmp = waddr+3 while Byte(addr_tmp) in [0xe9,0xeb]: if Byte(addr_tmp) == 0xe9: addr_tmp = (addr_tmp+5+Dword(addr_tmp+1))&0xffffffff else: off = Byte(addr_tmp+1) off = off if off < 0x80 else off - 0x100 addr_tmp += 2+off if Byte(addr_tmp) != 0x49: patch_one_byte(saddr,waddr) print('patch one byte') else: if not patch_one_round(saddr,waddr): break count += 1 if count % 500 == 0: print(count) save_database('crackme_fix_idb') cur_ip = GetRegValue('eip') print('curr_ip:{:08X}'.format(cur_ip)) for i in range(cur_ip,cur_ip+0x300): MakeUnkn(i,0) saddr = get_saddr(cur_ip-1) if saddr == 0: print('saddr err or finished!') break for i in range(0x4016A9,0x55e602): MakeUnkn(i,0) LoadDebugger('windbg',1) AddBpt(0x40123F) StartDebugger('','','') GetDebuggerEvent(WFNE_SUSP, -1) DelBpt(0x40123F) quick_ALT_K() #if AddHotkey("Alt+K","quick_ALT_K")!=IDCHK_OK: # print('hotkey err!') print('end.')
反除錯就一種:rdtsc,發現除錯修改區域性變數值,還有兩種反虛擬機器的程式碼。
解碼後將無用程式碼nop掉了。發現SMC部分開始到到校驗前都在一個函式中:
.text:00401663 000 E8 00 00 00 00 call $+5 .text:00401668 004 5E pop esi .text:00401669 000 81 EE 68 16 40 00 sub esi, 401668h .text:0040166F 000 81 C6 13 E6 55 00 add esi, offset table_256_55E613 .text:00401675 000 55 push ebp .text:00401676 004 8B EC mov ebp, esp .text:00401678 004 83 EC 2C sub esp, 2Ch .text:0055E0E6 030 83 C4 2C add esp, 2Ch .text:0055E0E9 004 8B E5 mov esp, ebp .text:0055E0EB 004 5D pop ebp .text:0055E0EC 000 E9 54 04 00 00
直接建立函式,調整後,可以F5,虛擬碼形如:
v0 = t_190[4] ^ table_256_55E613[t_1a0[4] ^ result[4] ^ 9]; v1 = t_190[5] ^ table_256_55E613[t_1a0[1] ^ result[1] ^ 9]; v2 = t_190[6] ^ table_256_55E613[result[14] ^ t_1a0[14] ^ 9]; v3 = t_190[7] ^ table_256_55E613[result[11] ^ t_1a0[11] ^ 9]; v4 = t_190[8] ^ table_256_55E613[result[8] ^ t_1a0[8] ^ 9]; v5 = t_190[9] ^ table_256_55E613[(t_1a0[5] ^ result[5]) ^ 9]; v6 = t_190[10] ^ table_256_55E613[t_1a0[2] ^ result[2] ^ 9]; v7 = t_190[11] ^ table_256_55E613[result[15] ^ t_1a0[15] ^ 9]; v8 = t_190[12] ^ table_256_55E613[result[12] ^ t_1a0[12] ^ 9]; v9 = t_190[13] ^ table_256_55E613[(result[9] ^ t_1a0[9]) ^ 9]; v10 = t_190[14] ^ table_256_55E613[t_1a0[6] ^ result[6] ^ 9]; v11 = t_190[15] ^ table_256_55E613[t_1a0[3] ^ result[3] ^ 9]; v12 = t_190[0] ^ table_256_55E613[t_1a0[0] ^ result[0] ^ 9]; v13 = t_190[1] ^ table_256_55E613[result[13] ^ t_1a0[13] ^ 9]; v14 = t_190[2] ^ table_256_55E613[(result[10] ^ t_1a0[10]) ^ 9]; v15 = t_190[3] ^ table_256_55E613[(result[7] ^ t_1a0[7]) ^ 9]; v16 = 27 * (v12 >> 7); v17 = 27 * (v12 >> 7) ^ 2 * v12; v18 = 2 * v17 ^ 27 * (v17 >> 7); v19 = 27 * (v13 >> 7); v20 = 27 * (v13 >> 7) ^ 2 * v13; v21 = 27 * (v14 >> 7); v22 = 27 * (v14 >> 7) ^ 2 * v14; v23 = 27 * (v15 >> 7); v24 = 27 * (v15 >> 7) ^ 2 * v15; result[2] = aSnail3896q3405[2] ^ table_256_55E613[((t1_55E723[10] ^ v662) - 1)]; result[6] = aSnail3896q3405[6] ^ table_256_55E613[(v679 - 1)]; v689 = table_256_55E613[((t1_55E723[2] ^ v634) - 1)]; v690 = table_256_55E613[((t1_55E723[6] ^ v648) - 1)]; result[3] = aSnail3896q3405[3] ^ table_256_55E613[((t1_55E723[7] ^ v650) - 1)]; v691 = aSnail3896q3405[7] ^ table_256_55E613[((t1_55E723[11] ^ v664) - 1)]; result[4] = aSnail3896q3405[4] ^ v682; v692 = table_256_55E613[(v680 - 1)]; result[5] = aSnail3896q3405[5] ^ v686; result[8] = aSnail3896q3405[8] ^ v683; v693 = table_256_55E613[((t1_55E723[3] ^ v636) - 1)]; result[0] = aSnail3896q3405[0] ^ v681; result[1] = aSnail3896q3405[1] ^ v685; result[7] = v691; result[9] = aSnail3896q3405[9] ^ v687; result[12] = aSnail3896q3405[12] ^ v684; result[13] = aSnail3896q3405[13] ^ v688; result[10] = aSnail3896q3405[10] ^ v689; result[11] = aSnail3896q3405[11] ^ v692; result[15] = aSnail3896q3405[15] ^ v693; result[14] = aSnail3896q3405[14] ^ v690;
本想弄出個去除編碼與無用程式碼且執行正常的程式,對原程式進行patch,發現計算結果不正確,折騰了兩天也沒找出是哪patch多了或少了。於是放棄。繼續任務主線,解題。在解程式碼前,測試過。輸入的只要改動一個bit都會致使最後計算結果大相徑庭,當時懷疑有對稱加密。從虛擬碼可以很容易得出加密演算法是AES。主要特徵有計算過程中用到的256位元組表及替換操作,最明顯的是有限域的乘法程式碼。
v17 = 27 * (v12 >> 7) ^ 2 * v12; v18 = 2 * v17 ^ 27 * (v17 >> 7);
再將虛擬碼與AES演算法進行對照,確定為AES演算法無疑,只是作了修改。地址0x55E613開始的256位元組表及後面的11組16位元組表應該就是inv_sbox和round_key了。
很明顯,add_round_key這部分被改了,行變換則沒改。列混合變換一下一眼看不出來。調整了下虛擬碼,列出混合變換向量,發現列混合變換也沒改。
//v14,v15,v16,v17為一行狀態變數 v18 = 27 * (v14 >> 7); v19 = 27 * (v14 >> 7) ^ 2 * v14; v20 = 2 * v19 ^ 27 * (v19 >> 7); v21 = 27 * (v15 >> 7); v22 = 27 * (v15 >> 7) ^ 2 * v15; v23 = 27 * (v16 >> 7); v24 = 27 * (v16 >> 7) ^ 2 * v16; v25 = 27 * (v17 >> 7); v26 = 27 * (v17 >> 7) ^ 2 * v17; v27 = v16 ^ v15 ^ v18 ^ 2 * v14 ^ v21 ^ 2 * v15 ^ v17 ^ 27 * (v19 >> 7) ^ 2 * v19 ^ 27 * (v20 >> 7) ^ 2 * v20 ^ 27 * ((2 * v22 ^ 27 * (v22 >> 7)) >> 7) ^ 2 * 2 * v22 ^ 27 * (v22 >> 7) ^ 27 * (v24 >> 7) ^ 2 * v24 ^ 27 * ((2 * v24 ^ 27 * (v24 >> 7)) >> 7) ^ 2 * 2 * v24 ^ 27 * (v24 >> 7) ^ 27 * ((2 * v26 ^ 27 * (v26 >> 7)) >> 7) ^ 2 * 2 * v26 ^ 27 * (v26 >> 7) 1110 14 E 1011 11 B 1101 13 D 1001 9 9
找了份AES的加解密程式碼,按照虛擬碼修改出解密過程,測試計算結果正確,直接複製解密過程程式碼,行逆序,修改+-號,sbox就OK了。
# -*- coding:utf-8 -*- Sbox = ( 0x90,0x75,0xB4,0x69,0x59,0x47,0x97,0xC6,0x1A,0xC2,0x3A,0xA9,0x0E,0x05,0xBB,0x21, 0xB9,0xBA,0x9B,0x92,0xCE,0xF2,0x6B,0xEB,0x7A,0x8F,0xE9,0x14,0xE1,0x61,0x06,0x5F, 0x87,0xE6,0x80,0xDE,0x45,0xA6,0x22,0x37,0x9A,0x50,0x39,0x49,0x8D,0x02,0xD6,0x04, 0x15,0x13,0x2F,0x53,0x8C,0xD1,0xD7,0x34,0x60,0xB0,0x93,0x66,0xF0,0xE4,0xD5,0x63, 0x1E,0x2C,0x83,0x30,0x4C,0x99,0xBD,0x8B,0xDD,0x9F,0x31,0x44,0x74,0xF9,0x23,0xAA, 0xC3,0x96,0xF6,0xCF,0x9D,0x88,0x41,0xF5,0x4E,0xAE,0xFF,0xE5,0x9E,0x1B,0x48,0xED, 0x7E,0xDF,0x84,0x2D,0x3D,0x32,0x3C,0x0F,0x36,0xE3,0xD8,0x17,0xA5,0x33,0x3B,0x94, 0xE8,0xD3,0x16,0xD4,0x7D,0x20,0x6D,0x5B,0x0D,0xE7,0x42,0x7C,0xF4,0xBE,0x1C,0xBF, 0x56,0x65,0xC7,0x4F,0xC0,0x6F,0xB3,0x7F,0x81,0x2A,0xD0,0x43,0x73,0x62,0xD9,0x64, 0x07,0xC4,0xEC,0xA1,0xF7,0xA7,0x76,0xFC,0x2E,0xC8,0x54,0xAF,0x26,0xF8,0x57,0x86, 0xB5,0x4D,0x67,0x25,0xF1,0x72,0x1F,0x70,0x01,0xA3,0x95,0x5D,0x98,0xAC,0x27,0xC1, 0xB6,0xEE,0xCC,0x38,0x71,0xB2,0x6A,0x2B,0x8E,0xCD,0x10,0x55,0x0C,0xAD,0xCB,0x78, 0x82,0x08,0x0A,0x46,0xF3,0x3F,0x77,0xC5,0x51,0x52,0x29,0x24,0x3E,0x5A,0xA0,0x35, 0x5C,0x4B,0xE0,0xE2,0x58,0x11,0x00,0xFE,0xA4,0x8A,0x0B,0xCA,0xDB,0x79,0x68,0x4A, 0x9C,0x5E,0x91,0x03,0x7B,0x19,0x6C,0xA8,0xC9,0x09,0xB7,0xA2,0xFA,0xEA,0x89,0xEF, 0x18,0xDC,0x28,0xBC,0xD2,0xFD,0xDA,0x1D,0xB1,0x6E,0xFB,0x85,0x12,0xB8,0xAB,0x40, ) InvSbox = ( 0xD6,0xA8,0x2D,0xE3,0x2F,0x0D,0x1E,0x90,0xC1,0xE9,0xC2,0xDA,0xBC,0x78,0x0C,0x67, 0xBA,0xD5,0xFC,0x31,0x1B,0x30,0x72,0x6B,0xF0,0xE5,0x08,0x5D,0x7E,0xF7,0x40,0xA6, 0x75,0x0F,0x26,0x4E,0xCB,0xA3,0x9C,0xAE,0xF2,0xCA,0x89,0xB7,0x41,0x63,0x98,0x32, 0x43,0x4A,0x65,0x6D,0x37,0xCF,0x68,0x27,0xB3,0x2A,0x0A,0x6E,0x66,0x64,0xCC,0xC5, 0xFF,0x56,0x7A,0x8B,0x4B,0x24,0xC3,0x05,0x5E,0x2B,0xDF,0xD1,0x44,0xA1,0x58,0x83, 0x29,0xC8,0xC9,0x33,0x9A,0xBB,0x80,0x9E,0xD4,0x04,0xCD,0x77,0xD0,0xAB,0xE1,0x1F, 0x38,0x1D,0x8D,0x3F,0x8F,0x81,0x3B,0xA2,0xDE,0x03,0xB6,0x16,0xE6,0x76,0xF9,0x85, 0xA7,0xB4,0xA5,0x8C,0x4C,0x01,0x96,0xC6,0xBF,0xDD,0x18,0xE4,0x7B,0x74,0x60,0x87, 0x22,0x88,0xC0,0x42,0x62,0xFB,0x9F,0x20,0x55,0xEE,0xD9,0x47,0x34,0x2C,0xB8,0x19, 0x00,0xE2,0x13,0x3A,0x6F,0xAA,0x51,0x06,0xAC,0x45,0x28,0x12,0xE0,0x54,0x5C,0x49, 0xCE,0x93,0xEB,0xA9,0xD8,0x6C,0x25,0x95,0xE7,0x0B,0x4F,0xFE,0xAD,0xBD,0x59,0x9B, 0x39,0xF8,0xB5,0x86,0x02,0xA0,0xB0,0xEA,0xFD,0x10,0x11,0x0E,0xF3,0x46,0x7D,0x7F, 0x84,0xAF,0x09,0x50,0x91,0xC7,0x07,0x82,0x99,0xE8,0xDB,0xBE,0xB2,0xB9,0x14,0x53, 0x8A,0x35,0xF4,0x71,0x73,0x3E,0x2E,0x36,0x6A,0x8E,0xF6,0xDC,0xF1,0x48,0x23,0x61, 0xD2,0x1C,0xD3,0x69,0x3D,0x5B,0x21,0x79,0x70,0x1A,0xED,0x17,0x92,0x5F,0xB1,0xEF, 0x3C,0xA4,0x15,0xC4,0x7C,0x57,0x52,0x94,0x9D,0x4D,0xEC,0xFA,0x97,0xF5,0xD7,0x5A, ) # learnt from http://cs.ucsb.edu/~koc/cs178/projects/JT/aes.c xtime = lambda a: (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1) Rcon = ( 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A, 0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A, 0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39, ) def text2matrix(text): matrix = [] for i in range(16): byte = (text >> (8 * (15 - i))) & 0xFF if i % 4 == 0: matrix.append([byte]) else: matrix[i / 4].append(byte) return matrix def matrix2text(matrix): text = 0 for i in range(4): for j in range(4): text |= (matrix[i][j] << (120 - 8 * (4 * i + j))) return text class AES: def __init__(self, master_key): # self.change_key(master_key) self.round_keys = [ [0x73,0x6E,0x61,0x69],[0x6C,0x33,0x38,0x39],[0x36,0x71,0x33,0x34],[0x30,0x35,0x25,0x00], [0xE4,0x51,0x1D,0x6D],[0x88,0x62,0x25,0x54],[0xBE,0x13,0x16,0x60],[0x8E,0x26,0x33,0x60], [0x11,0x92,0xCD,0x74],[0x99,0xF0,0xE8,0x20],[0x27,0xE3,0xFE,0x40],[0xA9,0xC5,0xCD,0x20], [0xB3,0x2F,0x7A,0xA7],[0x2A,0xDF,0x92,0x87],[0x0D,0x3C,0x6C,0xC7],[0xA4,0xF9,0xA1,0xE7], [0x22,0x1D,0xEE,0xEE],[0x08,0xC2,0x7C,0x69],[0x05,0xFE,0x10,0xAE],[0xA1,0x07,0xB1,0x49], [0xF7,0xD5,0xD5,0xDC],[0xFF,0x17,0xA9,0xB5],[0xFA,0xE9,0xB9,0x1B],[0x5B,0xEE,0x08,0x52], [0xFF,0xE5,0xD5,0xE5],[0x00,0xF2,0x7C,0x50],[0xFA,0x1B,0xC5,0x4B],[0xA1,0xF5,0xCD,0x19], [0x59,0x58,0x01,0xD7],[0x59,0xAA,0x7D,0x87],[0xA3,0xB1,0xB8,0xCC],[0x02,0x44,0x75,0xD5], [0xC2,0xC5,0x02,0xA0],[0x9B,0x6F,0x7F,0x27],[0x38,0xDE,0xC7,0xEB],[0x3A,0x9A,0xB2,0x3E], [0x61,0xF2,0xB0,0x20],[0xFA,0x9D,0xCF,0x07],[0xC2,0x43,0x08,0xEC],[0xF8,0xD9,0xBA,0xD2], [0x62,0x06,0x05,0x61],[0x98,0x9B,0xCA,0x66],[0x5A,0xD8,0xC2,0x8A],[0xA2,0x01,0x78,0x58] ] def encrypt(self, plaintext): self.plain_state = text2matrix(plaintext) self.__add_round_key(self.plain_state, self.round_keys[:4]) self.__sub_bytes(self.plain_state) self.__trans_row_col(self.plain_state) self.__shift_rows(self.plain_state) self.__trans_row_col(self.plain_state) self.plain_state = map(lambda x:map(lambda y:(y+1)&0xff,x),self.plain_state) self.__add_round_key(self.plain_state, self.round_keys[4:8]) self.__mix_columns(self.plain_state) self.__sub_bytes(self.plain_state) self.__trans_row_col(self.plain_state) self.__shift_rows(self.plain_state) self.__trans_row_col(self.plain_state) self.plain_state = map(lambda x:map(lambda y:(y+2)&0xff,x),self.plain_state) self.__mix_columns(self.plain_state) self.__add_round_key(self.plain_state, self.round_keys[8:12]) self.__sub_bytes(self.plain_state) self.__trans_row_col(self.plain_state) self.__shift_rows(self.plain_state) self.__trans_row_col(self.plain_state) self.plain_state = map(lambda x:map(lambda y:y^9,x),self.plain_state) self.__add_round_key(self.plain_state, self.round_keys[12:16]) self.__mix_columns(self.plain_state) self.__sub_bytes(self.plain_state) self.__trans_row_col(self.plain_state) self.__shift_rows(self.plain_state) self.__trans_row_col(self.plain_state) self.plain_state = map(lambda x:map(lambda y:y^7,x),self.plain_state) self.__mix_columns(self.plain_state) self.__add_round_key(self.plain_state, self.round_keys[16:20]) self.__sub_bytes(self.plain_state) self.__trans_row_col(self.plain_state) self.__shift_rows(self.plain_state) self.__trans_row_col(self.plain_state) self.plain_state = map(lambda x:map(lambda y:(y-1)&0xff,x),self.plain_state) self.__add_round_key(self.plain_state, self.round_keys[20:24]) self.__mix_columns(self.plain_state) self.__sub_bytes(self.plain_state) self.__trans_row_col(self.plain_state) self.__shift_rows(self.plain_state) self.__trans_row_col(self.plain_state) self.plain_state = map(lambda x:map(lambda y:y^3,x),self.plain_state) self.__mix_columns(self.plain_state) self.__add_round_key(self.plain_state, self.round_keys[24:28]) self.__sub_bytes(self.plain_state) self.__trans_row_col(self.plain_state) self.__shift_rows(self.plain_state) self.__trans_row_col(self.plain_state) self.plain_state = map(lambda x:map(lambda y:y^8,x),self.plain_state) self.__mix_columns(self.plain_state) self.__add_round_key(self.plain_state, self.round_keys[28:32]) self.__sub_bytes(self.plain_state) self.__trans_row_col(self.plain_state) self.__shift_rows(self.plain_state) self.__trans_row_col(self.plain_state) self.plain_state = map(lambda x:map(lambda y:y^6,x),self.plain_state) self.__add_round_key(self.plain_state, self.round_keys[32:36]) self.__mix_columns(self.plain_state) self.__sub_bytes(self.plain_state) self.__trans_row_col(self.plain_state) self.__shift_rows(self.plain_state) self.__trans_row_col(self.plain_state) self.__mix_columns(self.plain_state) self.__add_round_key(self.plain_state, self.round_keys[36:40]) self.__sub_bytes(self.plain_state) self.__trans_row_col(self.plain_state) self.__shift_rows(self.plain_state) self.__trans_row_col(self.plain_state) self.plain_state = map(lambda x:map(lambda y:y^9,x),self.plain_state) self.__add_round_key(self.plain_state, self.round_keys[40:]) return matrix2text(self.plain_state) def decrypt(self, ciphertext): self.cipher_state = text2matrix(ciphertext) self.__add_round_key(self.cipher_state, self.round_keys[40:]) self.cipher_state = map(lambda x:map(lambda y:y^9,x),self.cipher_state) self.__trans_row_col(self.cipher_state) self.__inv_shift_rows(self.cipher_state) self.__trans_row_col(self.cipher_state) self.__inv_sub_bytes(self.cipher_state) self.__add_round_key(self.cipher_state, self.round_keys[36:40]) self.__inv_mix_columns(self.cipher_state) self.__trans_row_col(self.cipher_state) self.__inv_shift_rows(self.cipher_state) self.__trans_row_col(self.cipher_state) self.__inv_sub_bytes(self.cipher_state) self.__inv_mix_columns(self.cipher_state) self.__add_round_key(self.cipher_state, self.round_keys[32:36]) self.cipher_state = map(lambda x:map(lambda y:y^6,x),self.cipher_state) self.__trans_row_col(self.cipher_state) self.__inv_shift_rows(self.cipher_state) self.__trans_row_col(self.cipher_state) self.__inv_sub_bytes(self.cipher_state) self.__add_round_key(self.cipher_state, self.round_keys[28:32]) self.__inv_mix_columns(self.cipher_state) self.cipher_state = map(lambda x:map(lambda y:y^8,x),self.cipher_state) self.__trans_row_col(self.cipher_state) self.__inv_shift_rows(self.cipher_state) self.__trans_row_col(self.cipher_state) self.__inv_sub_bytes(self.cipher_state) self.__add_round_key(self.cipher_state, self.round_keys[24:28]) self.__inv_mix_columns(self.cipher_state) self.cipher_state = map(lambda x:map(lambda y:y^3,x),self.cipher_state) self.__trans_row_col(self.cipher_state) self.__inv_shift_rows(self.cipher_state) self.__trans_row_col(self.cipher_state) self.__inv_sub_bytes(self.cipher_state) self.__inv_mix_columns(self.cipher_state) self.__add_round_key(self.cipher_state, self.round_keys[20:24]) self.cipher_state = map(lambda x:map(lambda y:(y+1)&0xff,x),self.cipher_state) self.__trans_row_col(self.cipher_state) self.__inv_shift_rows(self.cipher_state) self.__trans_row_col(self.cipher_state) self.__inv_sub_bytes(self.cipher_state) self.__add_round_key(self.cipher_state, self.round_keys[16:20]) self.__inv_mix_columns(self.cipher_state) self.cipher_state = map(lambda x:map(lambda y:y^7,x),self.cipher_state) self.__trans_row_col(self.cipher_state) self.__inv_shift_rows(self.cipher_state) self.__trans_row_col(self.cipher_state) self.__inv_sub_bytes(self.cipher_state) self.__inv_mix_columns(self.cipher_state) self.__add_round_key(self.cipher_state, self.round_keys[12:16]) self.cipher_state = map(lambda x:map(lambda y:y^9,x),self.cipher_state) self.__trans_row_col(self.cipher_state) self.__inv_shift_rows(self.cipher_state) self.__trans_row_col(self.cipher_state) self.__inv_sub_bytes(self.cipher_state) self.__add_round_key(self.cipher_state, self.round_keys[8:12]) self.__inv_mix_columns(self.cipher_state) self.cipher_state = map(lambda x:map(lambda y:(y-2)&0xff,x),self.cipher_state) self.__trans_row_col(self.cipher_state) self.__inv_shift_rows(self.cipher_state) self.__trans_row_col(self.cipher_state) self.__inv_sub_bytes(self.cipher_state) self.__inv_mix_columns(self.cipher_state) self.__add_round_key(self.cipher_state, self.round_keys[4:8]) self.cipher_state = map(lambda x:map(lambda y:(y-1)&0xff,x),self.cipher_state) self.__trans_row_col(self.cipher_state) self.__inv_shift_rows(self.cipher_state) self.__trans_row_col(self.cipher_state) self.__inv_sub_bytes(self.cipher_state) self.__add_round_key(self.cipher_state, self.round_keys[:4]) return matrix2text(self.cipher_state) def __add_round_key(self, s, k): for i in range(4): for j in range(4): s[i][j] ^= k[i][j] def __round_encrypt(self, state_matrix, key_matrix): self.__sub_bytes(state_matrix) self.__shift_rows(state_matrix) self.__mix_columns(state_matrix) self.__add_round_key(state_matrix, key_matrix) def __round_decrypt(self, state_matrix, key_matrix): self.__add_round_key(state_matrix, key_matrix) self.__inv_mix_columns(state_matrix) self.__inv_shift_rows(state_matrix) self.__inv_sub_bytes(state_matrix) def __sub_bytes(self, s): for i in range(4): for j in range(4): s[i][j] = Sbox[s[i][j]] def __trans_row_col(self,s): t1 = s[0][0], s[1][0], s[2][0], s[3][0] t2 = s[0][1], s[1][1], s[2][1], s[3][1] t3 = s[0][2], s[1][2], s[2][2], s[3][2] t4 = s[0][3], s[1][3], s[2][3], s[3][3] s[0][0], s[0][1], s[0][2], s[0][3] = t1[0], t1[1], t1[2], t1[3] s[1][0], s[1][1], s[1][2], s[1][3] = t2[0], t2[1], t2[2], t2[3] s[2][0], s[2][1], s[2][2], s[2][3] = t3[0], t3[1], t3[2], t3[3] s[3][0], s[3][1], s[3][2], s[3][3] = t4[0], t4[1], t4[2], t4[3] def __inv_sub_bytes(self, s): for i in range(4): for j in range(4): s[i][j] = InvSbox[s[i][j]] def __shift_rows(self, s): s[1][0], s[1][1], s[1][2], s[1][3] = s[1][1], s[1][2],s[1][3],s[1][0] s[2][0], s[2][1], s[2][2], s[2][3] = s[2][2],s[2][3],s[2][0], s[2][1] s[3][0], s[3][1], s[3][2], s[3][3] = s[3][3], s[3][0],s[3][1],s[3][2] def __inv_shift_rows(self, s): s[1][0], s[1][1], s[1][2], s[1][3] = s[1][3], s[1][0], s[1][1], s[1][2] s[2][0], s[2][1], s[2][2], s[2][3] = s[2][2], s[2][3], s[2][0], s[2][1] s[3][0], s[3][1], s[3][2], s[3][3] = s[3][1], s[3][2], s[3][3], s[3][0] def __mix_single_column(self, a): # please see Sec 4.1.2 in The Design of Rijndael t = a[0] ^ a[1] ^ a[2] ^ a[3] u = a[0] a[0] ^= t ^ xtime(a[0] ^ a[1]) a[1] ^= t ^ xtime(a[1] ^ a[2]) a[2] ^= t ^ xtime(a[2] ^ a[3]) a[3] ^= t ^ xtime(a[3] ^ u) def __mix_columns(self, s): for i in range(4): self.__mix_single_column(s[i]) def __inv_mix_columns(self, s): # see Sec 4.1.3 in The Design of Rijndael for i in range(4): u = xtime(xtime(s[i][0] ^ s[i][2])) v = xtime(xtime(s[i][1] ^ s[i][3])) s[i][0] ^= u s[i][1] ^= v s[i][2] ^= u s[i][3] ^= v self.__mix_columns(s) if __name__ == '__main__': cipher = AES(0) m = cipher.decrypt(int('C0B10D687FAB692FFED16BDFFBF2BA2E',16)) print hex(m)[2:].replace('L','')#.decode('hex') m = 'KCTF'+'\x00'*12 # m = 'B54333CE90874B76' c = cipher.encrypt(int(m.encode('hex'),16)) print hex(c)[2:].replace('L','').upper()
對於此題有兩點要說的:
1. 2880層解碼,有以量取勝的嫌疑,通常情況下做題者不喜。
2. 只要模式較固定,指令碼解碼就有了可能。
相關文章
- 看雪.紐盾 KCTF 2019 Q3 | 第十一題點評及解題思路2019-10-08
- 看雪.紐盾 KCTF 2019 Q3 | 第十二題點評及解題思路2019-10-08
- 看雪.紐盾 KCTF 2019 Q3 | 第十三題點評及解題思路2019-10-08
- 看雪.紐盾 KCTF 2019 Q2 | 第十題點評及解題思路2019-07-05
- 看雪.紐盾 KCTF 2019 Q3 | 第一題點評及解題思路2019-09-25
- 看雪.紐盾 KCTF 2019 Q3 | 第四題點評及解題思路2019-09-29
- 看雪.紐盾 KCTF 2019 Q3 | 第七題點評及解題思路2019-09-30
- 看雪.紐盾 KCTF 2019 Q3 | 第六題點評及解題思路2019-10-08
- 看雪.紐盾 KCTF 2019 Q3 | 第八題點評及解題思路2019-10-08
- 看雪.紐盾 KCTF 2019 Q3 | 第九題點評及解題思路2019-10-08
- 看雪.紐盾 KCTF 2019 Q2 | 第九題點評及解題思路2019-07-04
- 看雪.紐盾 KCTF 2019 Q2 | 第一題點評及解題思路2019-07-01
- 看雪.紐盾 KCTF 2019 Q2 | 第二題點評及解題思路2019-07-01
- 看雪.紐盾 KCTF 2019 Q2 | 第三題點評及解題思路2019-07-01
- 看雪.紐盾 KCTF 2019 Q2 | 第五題點評及解題思路2019-07-01
- 看雪.紐盾 KCTF 2019 Q2 | 第七題點評及解題思路2019-07-02
- 看雪.紐盾 KCTF 2019 Q2 | 第八題點評及解題思路2019-07-03
- 看雪.紐盾 KCTF 2019 Q2 | 第六題點評及解題思路2019-07-01
- 2019KCTF 晉級賽Q1 | 第十題點評及解題思路2019-04-08
- 看雪.WiFi萬能鑰匙 CTF 2017第十題 點評及解題思路2017-06-28WiFi
- 看雪.WiFi萬能鑰匙 CTF 2017第十三題 點評及解題思路2017-06-28WiFi
- 看雪.WiFi萬能鑰匙 CTF 2017第十一題 點評及解題思路2017-06-28WiFi
- 看雪.WiFi萬能鑰匙 CTF 2017第十四題 點評及解題思路2017-06-30WiFi
- 看雪.WiFi萬能鑰匙 CTF 2017第十五題 點評及解題思路2017-08-10WiFi
- 看雪·深信服 2021 KCTF 春季賽 | 第十題設計思路及解析2021-05-31
- 看雪·眾安 2021 KCTF 秋季賽 | 第十題設計思路及解析2021-12-16
- 看雪.WiFi萬能鑰匙 CTF 2017第十二 點評及解題思路2017-06-28WiFi
- 看雪·眾安 2021 KCTF 秋季賽 | 第十一題設計思路及解析2021-12-15
- 2019 KCTF 晉級賽Q1 | 第三題點評及解題思路2019-03-28
- 2020 KCTF秋季賽 | 第一題點評及解題思路2020-11-20
- 2020 KCTF秋季賽 | 第四題點評及解題思路2020-11-24
- 2019KCTF 晉級賽Q1 | 第九題點評及解題思路2019-04-04
- 看雪.WiFi萬能鑰匙 CTF 2017第五題 點評及解題思路2017-06-28WiFi
- 看雪.WiFi萬能鑰匙 CTF 2017第四題 點評及解題思路2017-06-29WiFi
- 看雪.WiFi萬能鑰匙 CTF 2017第三題 點評及解題思路2017-06-29WiFi
- 看雪.WiFi萬能鑰匙 CTF 2017第七題 點評及解題思路2017-06-22WiFi
- 看雪.WiFi萬能鑰匙 CTF 2017第八題 點評及解題思路2017-06-22WiFi
- 看雪·深信服 2021 KCTF 春季賽 | 第七題設計思路及解析2021-05-25