2014年10月2日 星期四

BASH ShellShock 漏洞分析


這回的 bash 漏洞很有趣,定義一個 script 函式竟然能夠被自動觸發執行。CVE-2014-6271 漏洞揭露後,經過數日,許多人不斷地在找尋可利用此漏洞的方法,試圖可以進行遠端入侵或本地提權等,也有不少人瘋狂找尋 bash 或其它 shell 的類似問題,當然各資安相關媒體或廠商也不斷 "提醒" 大家要盡快升級 bash,彷彿有了 bash 就會被入侵一般。

目前 shellshock wiki 描述及收集了詳細的資訊,這篇 BASH 代碼注入的安全漏洞 也針對漏洞作了詳細說明,以及 DevCore 的 Shaolin 寫的這篇。所以我打算針對漏洞成因,以及漏洞利用環境作探討。


漏洞原因


在 bash 的原始碼 variables.c 中可以看到 bash 在初始化環境變數時,會掃瞄環境變數中是否有函式定義,其函式定義的 keywords 為 "() {",見第 11 行。若有函式定義的字串,應該僅轉換為函式內容,不應該執行該函式,見第 21 行。


Patch 的方式可見 patch ,主要是導入兩個參數定義
+ #define SEVAL_FUNCDEF 0x080  /* only allow function definitions */
+ #define SEVAL_ONECMD 0x100  /* only allow a single command */



然後在原來呼叫 parse_and_execute() 的地方改用
parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST|SEVAL_FUNCDEF|SEVAL_ONECMD);

並且在 parse_and_execute() 裡再去檢查是否有 SEVAL_FUNCDEF|SEVAL_ONECMD。



遠端執行指令碼



想要利用這個 bash 漏洞,就要非常了解 Linux 的環境變數繼承關係。事實上,在 Unix 系統中以環境變數來傳遞參數或資料是很平常的事,因為環境變數是 key=value 格式,又具有行程間繼承的作用。

因此,要找到可利用的情況,首先要找尋一個網路服務程式,此程式會將網路上接收的資料或請求(Request)轉換為環境變數,若是網路服務程式再透過 execle()/execvpe()/execve() 或是 system()/popen() 來執行其它程式時,因為上述函式會繼承環境變數的關係,才有成功利用的機會。


以 httpd 為例,其實 httpd 並不會接收遠端傳來的環境變數,而是 httpd 在 mod_rewrite 模組執行時將 HTTP Request 中的欄位轉換填入對應的環境變數中,再經由 mod_cgi 模組執行 CGI 程式。所以 CGI 程式可以透過環境變數的方式取得 HTTP Request 所需要的內容。
例如,將 HTTP Request 中的 User-Agent 欄位的內容,轉換為環境變數為 HTTP_USER_AGENT 的內容,以便任何語言開發的 CGI 程式可以透過環境變數 HTTP_USER_AGENT 取得  User-Agent 欄位內容。


C 語言 CGI 範例



在此 C 語言的範例中,即便 system() 中執行的命令是固定,不可被 Command Injection,但透過 CVE-2014-6271,則可以執行任意命令,因為 system() 是透過 /bin/sh -c 來執行 /bin/hostname。

事實上,不只 C 語言開發的 CGI,任何語言開發的 CGI 程式透過 httpd mod_cgi 執行,都可能會被利用此漏洞。不過若是直接用 bash script 開發的 CGI 程式,則不需要再透過呼叫 system(),直接就可以利用此漏洞。


bash script 語言 CGI 範例


經過測試 mod_cgi 搭配各語言,發現 perl 和 ruby 在某些情況下無法利用此漏洞。原因是,
perl 和 ruby 在進行 system()/popen() 之類的呼叫時,會先經過最佳化,判斷是否只是執行外部程式而非真的 script 命令,只有是 script 命令才會透過 /bin/sh -c 來執行,否則會直接執行(execve)該外部程式。
例如 perl 語言中:

system ("/bin/env > /dev/null");
system("/bin/uname -a");

第一行的命令字串中因為有 > 重新導向輸出的符號,因此會透過 /bin/sh -c 來執行該命令,但是第二行的命令字串中,僅是直接執行 /bin/uname,因此並不會透過 /bin/sh -c 來執行該命令,也就不會造成漏洞利用。

下列是我在 CentOS 6.4 + Apache 2.2.15 測試了各語言版本 CGI 程式,搭配不同的環境,所測試的結果:

V: 可直接利用或透過 system()/popen() 利用
O: 在未最佳化執行的 system()/popen() 情況下可利用
X: 不可被利用
我建議 Linux 伺服器管理者優先將 Linux 伺服器作 bash 升級,並且將網站環境作分析,評估不再使用 mod_cgi 或 mod_fastcgi。另外也請將 Linux 作安全性強化,可參考 Kenduest Lee (小州)的 Security Enhanced Linux


另外一個例子是透過 DHCP 發送含執行代碼的封包,讓 Linux 的 dhclient 被利用此漏洞來入侵。從 Internet Systems Consortium DHCP Distribution Version 4.2.4 的原始碼來看,的確是 dhclient.c 會從 DHCP 封包中將參數值透過內建的 client_envadd() 轉換成環境變數,再經由 execve() 執行了 dhclient-script (bash script),所以當然也就中獎了。

下列兩道指令可用來檢測系統上的 dhclient 是否會被此漏洞影響,當然 patch bash 才是王道。

$ /sbin/dhclient - 2>&1 | grep "ISC"
This version of ISC DHCP is based on the release available
$ which dhclient-script
/sbin/dhclient-script

另一個漏洞 CVE-2014-7169


env X='() { (a)=>\' sh -c "echo date"; cat echo

這行環境變數的設定,會使得原來應該是 echo date,顯示 "date" 字串,變成執行 "date" 指令。從 bash 原始碼中可發現,在 shell_getc (y.tab.c) 讀取輸入字串遇到錯誤時會呼叫 reset_parser() (y.tab.c),然而控制字元讀取位置的 global 變數 int eol_ungetc_lookahead 卻沒有重置歸 0,修補方式請看 patch

所以 () { (a)=>\ 是用 = 字元故意製造錯誤,字元讀取位置因為沒被重設,還留在 > 字元,因此在讀取 "echo date" 後原本要執行 "echo" 變成了讀取 ">echo date",執行後結果變成執行 "date" 命令,將結果輸出至 "echo" 檔案中。

這個漏洞要利用的條件限制更多,而且不像 CVE-2014-6271 可以指定任何命令執行,而只能讓原本要執行的命令作改變,有點類似當年 Unix IFS 的問題。

結論

目前此漏洞仍然在發酵中,有些人找尋了特定 CGI 網站程式進行攻擊,也有人試圖找尋其它的相關漏洞,也許這漏洞真的有機會得到今年的 Pwnie Award 呢。



[References]
1. https://access.redhat.com/articles/1200223
2. https://securityblog.redhat.com/2014/09/26/frequently-asked-questions-about-the-shellshock-bash-flaws/
3. http://ftp.gnu.org/pub/gnu/bash/bash-3.2-patches/bash32-052
4. http://www.cnblogs.com/LittleHann/p/3992778.html
5. http://www.trendmicro.com/cloud-content/us/pdfs/security-intelligence/white-papers/wp-shellshock.pdf
6. http://jaxbot.me/articles/cases-where-bash-shellshock-is-safe-09-25-2014
7. http://security.stackexchange.com/questions/68448/where-is-bash-shellshock-vulnerability-in-source-code

2014年6月8日 星期日

十年

近日陰雨綿綿,潮溼略帶悶熱,轉進人潮擁擠的捷運站內,赫然見到一位身穿黑色T恤的男孩從身邊匆匆走過,看著衣服上的小灰帽,思緒不禁帶到那個充滿熱情的夏天。
一個年輕小夥子,站在坐滿百人的教室內,正在淊淊不絕地講述如何透過駭客技巧,免費觀賞熱情視訊(WebCam Girls),這一場壓軸讓全場充滿歡笑,代表第一次舉辦台灣駭客年會的成功,也開啟了台灣駭客們的夢。
2004 年六月,CHROOT 資安社群成立,匯集了當時台灣地下駭客組織成員、資深的資安專家和白帽駭客,並且決定參考美國拉斯維加斯的駭客年會(DEFCON) 在台灣舉辦一場屬於自己的駭客年會,定名為 Hacks In Taiwan (HIT),這個名稱的由來其中一個原因係紀念已解散的著名駭客組織(Hackers In Taiwan),另一方面強調對於駭客技術的熱情不只在資安領域,而是汎指各項技術上的 Hacking。中文名稱取作「台灣駭客年會」更是挑戰當時對於「駭客」名詞的負面看法,希望對「駭客」一詞有正面概念的傳達。
對於一群只熟悉網路技術的宅男們來說,辦一個研討會是大夥從沒有過的挑戰,不知道要探討什麼主題;不知道有沒有講師,不知道要有預算概念;不知道會議流程如何進行;不知道有沒有贊助商;不知道會有多少人報名參加。因此,CHROOT 決定花一年的時間準備,每個月固定聚會討論會議主題,分享研究成果,訓練演講技巧,觀摩其它研討會,找尋各方資源,選定在七月中旬於文化大學大新館舉辦,希望帶給參加者一個不同於商業性質,充滿駭客技術分享,充滿熱情有趣,具有表現自我機會的研討會,這些後來也成為每一屆的重要精神。
還記得,我們定了一個當時不算低的票價,而且還得先匯款才能完成報名,網站只有少數資訊且還放了一頁奇怪文字訊息,官方郵件服務還不穩定,偶爾會有退信情況發生。有趣的是,當時我向文化大學訂場地時,擔心用「駭客年會」名義會被校方拒絕,所以我用「資安社群研討會」,導致許多報名者想向文化大學求證時,都被回覆「文化大學並無駭客年會相關活動舉辦」。
第二屆舉辦時,我們就發現文化大學數位演講廳的兩百個位子已不夠,因此第三至第五屆移到台灣科技大學國際演講廳,同時,在更多的贊助商及各路先進協助下,參與人數近四百人,成長兩倍有餘。到了 2010 年,終於有了能滿足空間、設備及費用的場地 - 中研院人文社會科學館,直到 2013 年,HITCON 現場參與人數突破 850 人,報名開放僅三分鐘內門票即售磬,創記錄的讓 30 位國內外資安專家發表演講,這還真是台灣少見的大規模資訊技術研討會。
要舉辦大型的研討會,其實都靠優秀的工作人員齊心合力完成。前幾屆都是由 CHROOT 成員的女友、老婆或兄弟姊妹們,充當工作人員,他們的辛勞付出與經驗傳承,也讓 HITCON 在往後招募志工上,更為容易。不管是親朋好友找來幫忙,或是自願加入協助活動舉辦,都是每年 HITCON 能成功的背後重要因素,值得我們心存感激。
說實在的,一年舉辦一次研討會,其實已經讓 HITCON 籌辦團隊忙到焦頭爛耳,但又時時覺得希望多一點活動,讓台灣更多人重視資安,提起學生們對 Security 的興趣,進而發揮更大的影響力。為了讓重大資安事件被各界重視,我們開始辦 HITCON Freetalk,以小規模活動方式邀請各政府單位及企業參與,第一時間將完整訊息揭露,協助加速應變及處理,例如去年三月韓國發生的嚴重資安攻擊,今年 OpenSSL HeartBleed 嚴重漏洞。此外,這幾年我們見到每年在 HITCON 舉行的 Wargame 競賽逐漸受到重視,除了政府舉辦的資安金盾獎,今年四月初賽門鐵克第一次在台灣舉辦駭客攻防競賽,五月初,HITCON Wargame 組長 Alan,組團帶領台大 217 學生團隊,前往南京參加百度 BCTF 競賽獲取冠軍,憑著這股勢氣和不放棄的精神,在上週末的 DEFCON CTF 資格賽搶下寶貴名額,準備在八月以 HITCON CTF 團隊名義,代表台灣出賽,和全球頂尖駭客爭奪世界冠軍。
透過 HITCON ,我們逐漸實現多年前的夢想,確切來說,我們透過 HITCON 的力量協助台灣年輕的駭客們逐夢,也是實現我們的夢,而他們對 HITCON 的熱情和支持,也是我們在舉辦 HITCON 精疲力精後的一股強心針。
“I like the dreams of the future better than the history of the past.”
                                                                                      ― Thomas Jefferson
接下來,HITCON 籌辦團隊試著把 HITCON 帶向永續發展及培養更多資安人才的方向努力,我也期望 HITCON 的未來十年,能創造及完成更多的夢想。堅持、努力以及不放棄的挑戰,也是駭客心中的信念,不是嗎?
穿著小灰帽衣的男孩仍在捷運上滑著手機,聽著關門的催促聲,我踏出車箱,回頭望著駛離的捷運列車,視線逐漸模糊。
                                                                                                                                         By Tim 2014.5

2013年6月18日 星期二

Defcon 21 競賽解題思路 - linked


題目給了一個 C 的 struct
typedef struct _llist {
   struct _llist *next;
   uint32_t tag;
   char data[100];
 } llist;

並且給了一小段 C 的程式碼

register char *answer;
char *(*func)();
llist *head;
...
func = (char *(*)(llist *))userBuf;
answer = (char *)(*func)(head);
send_string(answer);
exit(0);

此關卡程式會亂數配置若干 linked-list。要解開此關,則要送一段 shellcode 給 Server 執行,即 answer = (char *)(*func)(head); 這行呼叫。

Write me shellcode that traverses the randomly generated linked list, looking for a node with a tag 0x41414100, and returns a pointer to the data associated with that tag, such that the call to send_string will output the answer.

看來只要寫個 shellcode,走訪每個 linked-list,檢查 tag 是否為 0x41414100,若是則將此 linked-list 的位址傳回,應該就能讓 Server 吐出答案。
用 C 來表示大概像這樣:

char *shellcode(llist *head)
{
while(1) {
if (head->tag == 0x41414100)
return head->data;
head = head->next;
}
}

看來很簡單吧!
不過實際轉換成 shellcode 送出才發現,Server 回應 shellcode 只吃 16 bytes,也就是 shellcode 長度不能超過 16 bytes。這個長度限制馬上讓這題變成一個難解之題,不管怎麼縮小,改變 asm 指令等,都相當有難度。
此時,就想到如果不照題目所給的流程走,讓 shellcode 直接吐出走訪 linked-list 的所有資料,那 shellcode 能否控制在 16 bytes 之內呢?

用 C 來表示大概像這樣:

char *shellcode(llist *head)
{
dump:
                write(4, head, 100);
head = head->next;
                goto dump:
}

若是照正常的系統呼叫設定各暫存器,那鐵定超出長度,因此有些暫存器要故意忽略不設,例如呼叫 write() 的長度,C 這邊我寫 100,實際上完全不確定會 write 多少字元。

最後寫出的 shellcode 長得這樣:



沒幾行,分別解說如下:

7. pop   edi        
8. pop   ecx          

此兩行主要是因為  (char *)(*func)(head); ,也就是 call 之前會將參數 head 先放入堆疊(Stack) 再執行 call 時 x86 又會將返回位址放到堆疊,因此 pop edi 則會將返回位址取到 edi,而 pop ecx 則會將 head 存至 ecx。此時 ecx = head

10. mov ecx,[ecx]

此行則為 head = head->next,會放在一開始則完全是 shellcode 長度限制考量。

11. xor eax,eax
12. add eax,4

設定 eax = 4,因為 write 系統呼叫編號為 4。

14. push eax
15. pop ebx

恰好打算要輸出的 socket = 4,所以透過 push/pop 把 eax 複製到 ebx,一樣是長度限制考量。

16. ;mov edx,100
17. int 0x80
18. jmp _run

本來想設定 edx,代表要輸出的長度,不過受到長度限制考量,就完全不理長度,看當時執行此 shellcode 時 edx 暫存器值為何,就輸出多少長度,所以把這行註解掉了。
最後就是系統呼叫 int 0x80 把記憶體內容吐出來,然後 jmp _run 則不斷地走訪 linked-list 不斷地吐資料。
寫個小程式 dump,把 shellcode 輸出到 stdout,再透過 nc 送出給 Server。


大概收集個100M ~ 900M,完全看運氣,然後再從 dump 出的記憶體中找尋關鍵字 "The key"。
答案出來!


2013年6月17日 星期一

Defcon 21 競賽解題思路 - incest


好久沒來寫點東西啦!前兩天又是一年一度的 Defcon Quals,照例 CHROOT 集合多位朋友一塊解題闖關。今年我沒能花太多時間在解題,只看了較有把握的兩題也順利解出,先來分享 "\xff\xe4\xcc" 的第一題 - incest。

此題目給了兩隻 Linux x64 binary,maw 和 sis 。當然二話不說,先來 strings。

$ strings maw
eth0
Could not open key.
/home/services/sis
$ objdump -D sis > sis.disasm


從 sis.disasm 反組譯的內容中,可以看到此程式有 fork() 產生的子行程,會配置一塊記憶體,再將 recv() 收到的內容放到此記憶體空間,最後直接呼叫 callq *rdx,跳躍到該記憶體區域執行。而父行程也配了一塊記憶體,從某檔案中讀取內容後,就不斷呼叫 sched_yield()。
如果轉寫成 C 程式碼,大概長得像這樣:

很明顯 maw 是一個網路 daemon,accept() 連線後再執行 sis,執行時會先 open() key file,然後將 key file 的 fd 和 socket fd 一塊傳給 sis。根據 strace/ltrace 可以發現,key_fd = 3,sock_fd = 4,而且 sis 的子行程會直接跳到遠端傳送的資料裡執行。注意到,呼叫 mmap() 配置 sock_buffer 時,是用 PROT_READ|PROT_WRITE|PROT_EXEC,可讀可寫可執行,就可以塞 shellcode 執行啦。

但是,要怎麼取得 key file 的資料呢?

原來的想法是透過打造 open("key", O_RDONLY),然後 read(key_fd) 再 write(sock_fd),但測試結果發現會 Permission denied。而根據題目 incest 原意,搭配 sis.c,可以猜想應該是要由子行程透過 ptrace() 來讀取父行程裡的資料,也就是父行程已 read(key_fd, key_buffer),只要能讀取父行程的 key_buffer 內容,再 write(sock_fd) 就能遠端取得 key_buffer 內容啦。

打造 x64_86 的 shellcode,我是參考此文 64-bit-linux-shellcode,然後用 nasm 寫了一小段程式碼。流程大概如下:
shellcode()
{
int ppid;
int key_buffer_address;
int key_buffer;
int i;
char buffer[40];
user_regs_struct regs;
ppid = getppid();

ptrace (PTRACE_ATTACH, ppid, NULL, NULL);
wait(NULL);
ptrace (PTRACE_GETREGS,ppid, NULL, &regs);
key_buffer_address = regs.rbp-0x18;
ptrace (PTRACE_PEEKDATA, ppid, key_buffer_address, &key_buffer);
for(i = 0; i < 10; i++)
ptrace (PTRACE_PEEKDATA, ppid, key_buffer+i*8, buffer+i*8);
write(4, buffer, 40);
}

根據前面所提的 sis.disasm
  1. 400949:       e8 82 fd ff ff          callq  4006d0
  2. 40094e:       48 89 45 e8             mov    %rax,-0x18(%rbp)

可以發現,取得父行程 rbp 暫存器值後去 -0x18,即為存放 calloc() 配置的記憶體位址。所以透過
key_buffer_address = regs.rbp-0x18;
ptrace (PTRACE_PEEKDATA, ppid, key_buffer_address, &key_buffer);

則會得到父行程的 key_buffer 位址啦,接著用一個 for-loop 把父行程的 key_buffer 存的內容讀出來,再透過 write() 輸出到 sock_fd = 4,就能看到解答。
此題  shellcode 的 nasm 如下:

使用 nasm 編譯後,寫了一個小程式 dump,把 shellcode 輸出 stdout。


這樣就過關啦!

2009年6月12日 星期五

Defcon17 Wargame Crypto_Badness 400 解題過程

接續上一篇 Defcon 17 Wargame 競賽解題心得,這次分享的是 Crypto_Badness 400 的題目解題過程。

此題提供下載一個檔案,題目說明表示,此程式正在 pwn11.ddtek.biz 主機上執行,該程式為一 FreeBSD 的可執行檔,執行時會開 TCP Port 7852。直接用工具 nc 連向 pwn11.ddtek.biz 7852,即出現詢問 Password。所以按照慣例,先來個 strings 指令,觀察檔案內容。



其中有個字串,”chickenfingers” 應該就是該 Password 要問的答案了。當 Password: 提示出現時,輸入 chickenfingers,出現 What is your name? 時,隨便亂輸入 1234567890,最後出現如下圖:


要求解出該加密過的字串,該加密字串的每一字元用 16 進制表示。
接下來就不知要幹嘛,只好把原來的 FreeBSD執行檔,再來作分析,這一次改用工具 objdump 作反組譯(Disassemble)以及逆向工程(Reverse Engineering),分析結果如下:

  • 為一對稱式加密法,先取得五個亂數數值,每個亂數值為 0~255 (0x00~0xff) 之間,再建立一個 256 字元的陣列,陣列中每個值介於 0 至255。利用此五個亂數數值為亂數種子,擴充成 256 個亂數數值,存於此陣列中。
  • 利用簡單的 XOR 運算來加密,從明文中逐一取得字元,再從前面擴充成 256 個亂數陣列中,取一數值,來和明文的該字元 XOR,當明文的每一字元完成 XOR 後,即為密文。每次從 256 個亂數陣列中取值的方式則和明文字元的位置有關。
  • 由於 XOR 的特性,若是把密文的每個字元和 Key 的每個字元作 XOR 就可以反解成明文。透過程式的逆向工程,可以知道加密演算法,甚至可以直接取用相關計算函式。如此一來,我們只要猜測一開始亂數取得的五個亂數字元,就可以把 256 字元的亂數陣列值計算出來。
  • 不過,若是嘗試寫程式用暴力法計算,五個亂數數值全部猜測,就要計算 256 的 5 次方,而且,如何判斷該亂數值是否猜測正確呢?
  • 由於明文是從純文字檔內讀取 32 個字元,所以假設明文都是可顯示之字元(包含大小寫英文、符號及空白字元),那就可以在暴力法每回解出明文時,掃瞄一次明文字元是否都為可顯示之字元,若是,就假設為明文字串,全部顯示出來。
這下就可以寫程式,計算 256 的 5 次方,求解明文了!但是這樣跑程式實在太慢了,就算試著用一些最佳化或分散運算的方式,仍然要等上很久。


回過頭來重新檢視之前的輸入輸出。當 Password: 提示出現時,輸入 chickenfingers,出現 What is your name? 時,則輸入 1234567890,最後出現如下圖:



奇怪,1234567890 字串到 ? 字元出現的中間有一段空白,改用 hexedit 編輯看看:



這樣就看清楚了,在 1234567890 字串後面,接了 6 個空白字元(0x20),然後一個字元(0xCE),再一個 : (0x3A)字元。這個 0xCE 字元,即為一開始取五個亂數值中的第一個數值。
五個亂數值中,第一個數值已知,如此一來,就可以動手寫暴力破解程式,計算 256 的 4 次方,即可找出\x5f\x92\x84\xbc\xe\x16\xab\x2a\x92\x97\x63\x78\x06\x5f\xa1\x3a\xa3\xee\xa9\xc3\xee\x49\xea\xea\x92\x1a\x36\x2e\xa8\x7c\x28\x0cf 的明文。

透過分散數台電腦來分批執行暴力破解程式,但仍然很慢,花了數個小時仍找不出明文字串。這時想到,原來的程式中,輸出的函式有兩組,另一組會輸出兩倍長度的字元。假若我可以改變程式流程,跳躍至另一組輸出函式,也許可以輸出五個亂數值中的前兩個數值,這樣就可以跑計算次數降為 256 的 3 次方。

將所附的程式碼再重新反組譯,可以觀察出程式中有一個變數可以控制字元輸出的長度。該變數位在 -0x25(%ebp),此變數值是由 -0x29(%ebp) 和 1 作 AND 運算後的結果,若此變數 -0x25(%ebp) 的值為 1 是,則會輸出兩倍長度的字元。



當 Password: 提示出現時,輸入 chickenfingersa (故意多打一個字元,造成 one byte 溢出),出現 What is your name? 時,則輸入 ABCDEFGHIJKLMNOABCDEFGHIJKLMNO,最後出現如下圖:





在 ABCDEFGHIJKLMNOABCDEFGHIJKLMNO 字串後面,接了 2 個換行字元(0x0A),然後三個字元(0xD9、0xE2、0x00),再接一個 : (0x3A)字元。其中 0xD9、0xE2 字元,即為一開始取五個亂數值中的前兩個數值。這樣一來跑暴力破解程式實在快多了,不到十秒就出現一組明文。



不過這明文怎麼看都不像啊!讓暴力破解程式繼續執行,很快地,又發現第二組明文啦。

賓果! 解出明文了!

註:
暴力破解程式的解密函式是直接從原程式作逆向工程後取用,變數處理要注意 signed/unsigned 的問題,否則從亂數陣列取值時會計算不正確。