中國科學技術大學第五屆信息安全大賽(hackergame2018自我總結)2

這一批題都是我不會的,只能把官方write-up放在這裏了html

一、FLXG 的祕密python

------------------------------------------------------------------------------------------------------------linux

公元 0xFB2 年, FLXG 正當其道.
沒錯, 在 CWK 的偉大倡導之下, 年份採用了更爲先進的 16 進製表示. 中國滑稽大學也由於率先提出了 FLXG 的理論, 其世界超一流的院校的地位已經不可動搖. 而在肥宅路 98 號某個廢棄的角落裏 -- 其實是兩千年前一時風光無二, CWK 口中以考試分數爲惟一目標的分院 -- 幾名倖存的理論物理系跑男 (舊指爲 GPA 而四處奔波的分院學生) 在飢寒交迫中, 企圖謀劃着最後的反抗.
實際上, 他們已經成功了. 多少代物理系的先輩們忍辱負重, 轉入 CS, 就是爲了製造出量子計算機, 試圖攻破 FLXG 這個完美無缺的理論. 這個計劃已經實施了兩千年, 而如今終於結成正果了. 世界上僅存的幾位分院跑男, 他們已經掌握了 FLXG 最核心的祕密, 那是除了創始人 CWK 無人知曉的祕密, 那是失傳千年的, 整個 FLXG 的惟一漏洞. 當年的 Nature, Science, 現在的 Engineer 期刊上不斷有人試圖找出這個紕漏, 然而全部人都失敗了. (惋惜也沒人可以證實 FLXG 的絕對完美性) 所以 FLXG 有一段時間被認爲是最接近真實的假設 -- 固然, 這是落後的理科思想所造成的結論. 因此, 正如你看到的這樣, FLXG 已經成爲了金科玉律通常的存在. 然而, 它的惟一漏洞, 在這一年, 已經這幾名跑男找到了.
可是, 他們也是失敗的. 分院和物院, 已經在滑稽大學的 FLXG 改革中消失, 全部留存的痕跡, 也成爲校史館中的笑料和反面教材. "什麼? 你還跑過去找老師要分? 怕不是分院餘孽.." 以前時時還能聽到這樣的嘲諷, 現在嘲諷的對象也愈來愈少, "分院跑男" 這種詞已經被新版的 CWK 詞典移到了附錄裏, 之後估計會被刪掉的吧. 就算有人找到 FLXG 的祕密又怎麼樣呢, 不再會有人去讀物理了.
固然, 有惟一的一條出路, 就是設法把這條祕密發送到兩千年前. 這樣你們就能在 FLXG 的實施以前, 看到它的漏洞了, 也許就能夠拯救分院和物院的命運了.
現在的技術發展, 雖然可以在必定程度上控制時空, 可是要把消息傳回兩千年前, 的確是不太靠譜. 況且兩千年前的人類根本沒法作出應答. 固然更關鍵的, 就是因果律的影響了. 傳遞消息的作法, 必需要瞞過因果律, 不然只會在過去的時間長河中開出一條小小的支流, 對於這個平行宇宙來講並沒有意義.
爲了作到這一點, 他們幾人把祕密用許多隨機生成的鎖保護起來, 最後鏈接成一個能夠自動計算出祕密的程序 (他們爲了存活也轉行作 CS 了), 而這個程序運行起來須要 2000 年甚至更久. 以後, 他們再以四千年前的伏羲先天六十四卦將程序編碼, 以此試圖騙過因果律, 逆流而上, 成前人未有之壯舉.
然而, 因爲時間長河的沖刷, 這份信息仍然受到了損毀. 在 0x7E2 年的你, 可以解出 FLXG 的祕密嗎?git

------------------------------------------------------------------------------------------------------------github

來自將來的漂流瓶
TL;DR
六十四卦那些卦象,大家看着不以爲就像二進制嗎..。
詳解
下下來直接打開發現亂碼。不知道爲啥編輯器不以爲這是 UTF-8。切換到 UTF-8 的編碼,發現果真一堆六十四卦的名詞。
不管怎麼編碼的,第一步確定是把不一樣卦分離開來。六十四卦的名稱比較雜亂,是變長的 CISC 架構,代碼裏面的六十四卦卦名要老老實實寫出來。
接下來就是腦洞時間了。六十四卦每一卦都有 6 個 bits 的信息,而通常的數據都是以 8 個 bits 爲單位。所以,咱們看一下長度,發現能夠被 8 整除,這進一步驗證了咱們的猜測。接下來,就是考慮如何把一個卦象轉化爲 6 個 bits。
有三類顯而易見的狀況:
每一個卦象自下而上,陰陽對應 0 和 1,這就是兩種可能
每一個卦象自上而下,陰陽對應 0 和 1,這又是兩種可能
卦象以先天六十四卦順序,也是 Unicode 字符集中的順序編碼
寫出來腳本跑一跑,發現第二種狀況能產生一個 gzip 的文件。解壓時提示文件損壞,查看文件末尾便可獲得 flxg。
難以參悟的祕密
TL;DR
Merkle Hellman Knapsack Cryptosystem
詳解
本題是一道逆向。
解壓獲得一個可執行文件和一堆動態連接庫。拖進 IDA 裏發現,程序會讀取 passkey.txt 的內容,而後經過調用動態連接庫的函數進行校驗。最後通過一番處理,每 8 行變成一個大整數。而後根據一個 128bit 的數的某一位進行求和。最後判斷是否結果等於最後一個大整數。這其實是一個 Merkle Hellman Knapsack Cryptosystem。
二進制裏重要函數都已經標註出來。一旦咱們弄清楚程序的意圖,就能夠繼續作下去了。思路很是清晰: 先要恢復 passkey.txt 的內容,進而獲得每一個大整數。而後求解 flxg。
第一步,須要選手批量處理動態連接庫中的代碼。動態連接庫裏面的驗證基本能夠總結爲 kx+b=x,經過將 k 和 b 提取出來,能夠求解 x。而 k 和 b 兩個數字在 lock 函數中偏移固定,能夠經過能處理 ELF 的 python 庫或二進制分析框架提取出來。
第二步,咱們獲得了 passkey.txt 應有的內容。如今咱們須要還原 128 個大整數。大體有兩種思路,一種是動態運行程序,經過修改程序的代碼或者 Hook 或者 DBI 或者調試器腳本的方法,能夠獲得這些大整數。另外一種是經過逆向,本身重現相應的算法。這道題裏面使用了一個不常見的 Hash 算法 -- JH,很難識別出來。而且代碼中大量使用 SSE,很難手動實現。可是能夠經過將可執行文件轉爲動態連接庫的方法導出相關函數,從而直接運行程序的算法。
第三步,Low Density attack。這其實是 1984 年的攻擊了。須要找到論文簡單復現一下便可。好比 http://www4.ncsu.edu/~smsulli2/MA437_Fall2017/knapsack.pdf。其核心思想是構造出一個 Lattice,這些向量加起來和爲 0。而後就變成了格點規約問題了。經過 LLL 算法很容易求出解。
(此處省略醜陋的 Mathematica 代碼)
本題代碼見 flxg.c 與 lock.c。代碼實際上不能直接編譯,由於缺乏 jh.h (只是一個 hash 算法的實現) 和 lock.h (由腳本生成)。不過大體流程比較清晰。可參看。web

二、C 語言做業面試

------------------------------------------------------------------------------------------------------------算法

今天 C 語言課程的做業是寫一個簡單的計算器程序,我在 linux 上面只花了幾分鐘就寫完了,你們能夠下載我編譯好的程序來使用。若是不想下載的話,我還專門提供了一個網絡服務,你們能夠在 linux 上面使用
nc 202.38.95.46 12008
這條命令來鏈接。什麼?你說你看到我服務器的家目錄裏有一個 flag 文件?這怎麼可能?docker

------------------------------------------------------------------------------------------------------------shell

這道題的思路源自我以前玩過的一個 Wargame:Smash the Stack IO Level 2。
逆向這個二進制,發現它就是再日常不過的計算器程序了,並且考慮了除以 0 的狀況。
仔細觀察,在程序初始化的時候(main 函數執行以前),程序註冊了幾個 signal 的處理函數。這個其實是用 gcc 的 __attribute__((constructor)) 實現的。
SIGILL、SIGABRT、SIGFPE、SIGSEGV 幾個信號被註冊到了 __err 函數上面,也就是說發生這幾種異常的時候 __err 函數會被執行。__err 函數會讓你輸入一個字符串,不能包含 sh,而後用 execlp 來執行這個字符串表明的程序。值得一提的是,execlp 限制了咱們只能不帶參數地執行程序。
根據 signal 的 man page,SIGILL、SIGABRT、SIGSEGV 在本程序中看起來應該不會發生,咱們關注一下 SIGFPE:
According to POSIX, the behavior of a process is undefined after it ignores a SIGFPE, SIGILL, or SIGSEGV signal that was not generated by kill(2) or raise(3). Integer division by zero has undefined result. On some architectures it will generate a SIGFPE signal. (Also dividing the most negative integer by -1 may generate SIGFPE.) Ignoring this signal might lead to an end‐ less loop.
驚訝!不只除以 0 可能會觸發 SIGFPE,最小的 INT 除以 -1 也可能會觸發!也就是說,寫一個整數計算器,只考慮除以 0 的異常是不夠的,最小的 INT 除以 -1 也可能會讓程序崩掉。
因此第一步輸入 -2147483648/-1 便可。
而後,咱們須要找到一個程序,不帶參數地運行能夠幫咱們拿到 shell,我能想到的是 vim,固然也可能有其餘解法。
注:我本想模擬一個新安裝的 Ubuntu 環境,可是 Ubuntu 的 docker 鏡像裏面什麼都沒有,ed、vi 等命令也應該安裝一下的,我對此表示抱歉。(有人提到 python,emmmm)
第二步輸入 vim ,而後進入了一個沒有 tty 的 vim,很難受,不過也能夠執行命令。咱們在 vim 內輸入 !cat flag 便可讀取 flag。
然而 flag 告訴咱們這個是假的,真的 flag 在一個叫 - 的文件裏,咱們執行 !cat - 就能夠了。而後什麼都沒看到!
實際上,cat - 中的 - 表示標準輸入,cat 會試圖從你的標準輸入中讀取,並不能看到 - 文件的內容。繞過的辦法有多種,最簡單的就是 cat ./-。
至於 5 秒的限時嘛……複製粘貼都是來得及的,反正進入 vim 以後就沒有了。
咱們也能夠把上面的過程寫成一個 shell 腳本:
#!/bin/bash

echo -e "-2147483648/-1\nvim\n:!ls\n:!cat flag\n:!cat ./-" | nc 202.38.95.46 12008 | grep flag

三、加密算法和解密算法

------------------------------------------------------------------------------------------------------------

提示:本題中使用的 base64 編碼,採用已被普遍應用於各類場合的 RFC 4648 §5 標準。
小趙聽到本身成爲了信息安全大賽的創始人後感到很是吃驚:「我一個少院學生會的幹事,怎麼就成信息安全大賽的創始人了呢?」這也難怪,畢竟小趙後來成爲了物理學院的學生。物理和信息安全,一般狀況下可都是八杆子打不着的呢。
固然了,小趙做爲物理學院的學生,和其餘物理學院的學生同樣,身上的浮躁勁兒可一點都很多,經常由於一點小成就而沾沾自喜。這不,由於我的安全上的不重視,小趙的某個學弟小鄭,很快從小趙暗戀的女孩子手裏拿到了小趙和她交流的加密算法的程序。小趙在得知此過後反而沒有儘量地息事寧人,反而公開宣稱,因爲解密算法目前沒有公開,因此拿到了加密算法也沒有什麼用。看來小趙對於現代密碼學,根本沒什麼全面深刻的瞭解啊。
不過,即便小趙使用的是對稱加密算法,分析出解密算法也並不是易事——小趙對程序進行了混淆,而混淆的方法是使用 BrainFuck 虛擬機——這也正是小趙的底氣所在。如今的任務是分析並讀懂一段 BrainFuck 程序,從而將一段密文還原。
如今小鄭將這一任務交給了躍躍欲試的你。快來挖掘小趙的黑歷史吧!
更多信息請下載題目文件
------------------------------------------------------------------------------------------------------------

小趙聽到本身還要給本身出的題目寫 write up 感到十分震驚——明明那麼簡單的題目,write up 還要出題人來寫。不過,小趙仍是硬着頭皮把 write up 寫完了。
尋找規律
這道題涉及的 BrainFuck 代碼看似複雜,實際上若是仔細分析,能夠發現大部分代碼都是有規律可循的。
咱們首先得知 BrainFuck 虛擬機元素的初始值都是零:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ...
咱們打開encrypt.bf,將整段代碼按循環(一對匹配的[]對及其中間的部分)分段:
,
[->>+++++++>>+++++>>+++>>++>>++++>++++++<<+++<<+++++++++<<++++++++<<++++++<<++++++++<]
>
[->>+++++>>+++++++++>>+++++++++>>++>>++++<+++++++++<<++++<<++++<<++<<++<]
<,
[->>+++++>>++++++++>>++++++>>+++++++>>+++++++>++++++<<++++++<<++++++++<<+++<<+++<<++++++++<]
>
[->>++++++++>>+++>>+++++++>>++++>>+++++++++<++++++<<+++++++++<<++<<+++++++++<<++++++++<]
<,

etc
而後咱們從第一段開始:
,
這段只有一個字符的代碼讀入一個值,咱們不妨設它爲a1。如今 BrainFuck 虛擬機狀況以下:
a1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ...
而後咱們進入循環:
[
-
>>
+++++++
>>
+++++
>>
+++
>>
++
>>
++++
>
++++++
<<
+++
<<
+++++++++
<<
++++++++
<<
++++++
<<
++++++++
<
]
通過一次循環後的虛擬機狀況以下:
a1-1 8 7 6 5 8 3 9 2 3 4 6 ...
指針回到a1-1處,咱們能夠得出這個循環還要進行a1-1次,共a1次,最後虛擬機狀況以下:
0 8*a1 7*a1 6*a1 5*a1 8*a1 3*a1 9*a1 2*a1 3*a1 4*a1 6*a1 0 0 0 ...
而後下一段:
>
指針向右一格,停在8*a1處。而後進入下一個循環:
[
-
>>
+++++
>>
+++++++++
>>
+++++++++
>>
++
>>
++++
<
+++++++++
<<
++++
<<
++++
<<
++
<<
++
<
]
咱們能夠得知這個循環將會進行8*a1次,和上面的循環同樣,咱們將增量代入,最後結果以下:
0 0 23*a1 46*a1 21*a1 80*a1 35*a1 81*a1 34*a1 19*a1 76*a1 38*a1 0 0 0 ...
接下來的指令是:
<,
首先將指針向左一格到最左點,而後讀入一個字符,不妨設爲a2:
a2 0 23*a1 46*a1 21*a1 80*a1 35*a1 81*a1 34*a1 19*a1 76*a1 38*a1 0 0 0 ...
而後接着進行上面的兩組循環。這樣的操做一共進行十輪,共二十次循環。
最後的代碼段將每一個元素加上一個固定的值輸出:
>++.
>++++++.
>++++++++.
>++++++++.
>+++.
>+++++.
>+++++.
>+++++++.
>++++.
>+++++++++.
經過分析咱們實際上能夠發現,這就是對a1...a10十個數組成的一次線性變換,固然若是算上最後加上一個固定的值輸出的話就是仿射變換。
若是咱們設輸出爲b1...b10的話,咱們的目標是找到一個十一維的矩陣A_(11x11)知足:
[b1, b2, ..., b10, 1] = A_(11x11) * [a1, a2, ..., a10, 1]
實際上每一輪操做除了+的數量不一樣,代碼形式是如出一轍的,咱們只須要將代碼中連續的+序列分離並分組,而後依次處理就能夠了。
提取數據
咱們編寫一段 Python 腳本提取出整個矩陣(爲方便後續運算,運算結果爲A_(11x11)的轉置):
import re

def to_matrix(code):
i = re.compile("[+]+").finditer(code.replace("\n", ""))
m = [[0 for k in range(11)] for j in range(11)]
for j in range(10):
for k in [0, 2, 4, 6, 8, 9, 7, 5, 3, 1]:
m[j][k] += len(next(i).group(0))
factor = len(next(i).group(0))
for k in [1, 3, 5, 7, 9, 8, 6, 4, 2, 0]:
m[j][k] += factor * len(next(i).group(0))
for k in range(10):
m[10][k] += len(next(i).group(0))
m[10][10] = 1
return m
如下是輸出:
[[23, 46, 21, 80, 35, 81, 34, 19, 76, 38, 0],
[69, 67, 80, 27, 22, 64, 79, 38, 55, 78, 0],
[40, 40, 63, 69, 66, 51, 74, 52, 41, 43, 0],
[61, 54, 33, 53, 43, 46, 52, 72, 68, 59, 0],
[47, 31, 60, 37, 68, 37, 27, 49, 39, 55, 0],
[21, 23, 26, 81, 36, 44, 19, 71, 62, 74, 0],
[62, 54, 39, 24, 67, 75, 38, 36, 48, 50, 0],
[73, 75, 32, 61, 22, 77, 79, 40, 65, 18, 0],
[18, 64, 48, 23, 58, 71, 30, 60, 21, 36, 0],
[81, 69, 39, 50, 37, 18, 68, 45, 66, 77, 0],
[ 2, 6, 8, 8, 3, 5, 5, 7, 4, 9, 1]]
整理算法
有經驗的參賽成員應該可以發現這正是希爾密碼(Hill Cipher)的一個變種。固然了,若是沒有經驗,此時也應能想到下一步是求這個矩陣的逆。不過,在題目中也有說明,矩陣的運算結果須要對 64 取餘。所以這個矩陣不是定義在實數域(R)上的,而是定義在整數模 64 環(可記爲Z_64)上的。
求矩陣的逆的直接方法無非求解伴隨矩陣,再除以矩陣的行列式共兩步。固然一些庫是能夠直接對某個整數模 n 環(可記爲Z_n)求逆的(比方說 SymPy 的inv_mod)。
如下是待求矩陣的逆(一樣通過轉置):
[[ 2, 17, 34, 57, 17, 45, 15, 61, 45, 24, 0],
[ 0, 20, 38, 43, 56, 29, 34, 41, 29, 61, 0],
[29, 3, 25, 25, 32, 57, 22, 4, 32, 52, 0],
[41, 63, 22, 19, 49, 32, 4, 22, 40, 18, 0],
[27, 24, 11, 11, 2, 15, 57, 9, 36, 4, 0],
[26, 55, 52, 10, 10, 54, 32, 37, 52, 28, 0],
[14, 31, 12, 60, 47, 44, 53, 41, 19, 13, 0],
[ 3, 53, 16, 51, 53, 11, 48, 35, 49, 28, 0],
[25, 26, 8, 57, 51, 30, 62, 17, 53, 0, 0],
[25, 37, 46, 19, 52, 53, 24, 18, 31, 12, 0],
[25, 56, 17, 57, 16, 55, 18, 4, 39, 41, 1]]
求解答案
最後 transform 一下輸入輸出就大功告成了。如下是本人編寫的 Python 代碼:

import sympy
def decrypt(matrix):
base64_mapping = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
def to_output(output):
transformed_output = output[:4, :10]
return "".join([base64_mapping[o % 64] for o in transformed_output])
def from_input(input):
transformed_input = [base64_mapping.index(i) for i in input]
return sympy.Matrix(4, 10, transformed_input).row_join(sympy.ones(4, 1))
inv_matrix = sympy.Matrix(matrix).inv_mod(64)
return lambda input: to_output(from_input(input).multiply(inv_matrix))

 

整個 Python 代碼位於encryption_and_decryption_solution.py中。
彩蛋
其實這道題的加解密函數存在一個刻意加入 (但卻毫無心義) 的不動點,請參閱附帶的 Python 代碼(^_^)

四、王的特權

------------------------------------------------------------------------------------------------------------

小王同窗剛剛學會了 Rust 語言,今天拿着本身寫的 BUG 來找老張同志。
老張:喲,小子學會寫 Rust 了?
小王:沒錯,剛出爐的程序,並且只有我能運行!
老張:只有你能運行?我不信。
小王:不信你就試試唄!
小王放心地把程序交給了老張,並聲稱能夠找任何人來幫忙。做爲老張多年的好友,你能幫他破開「王的特權」的祕訣嗎?
注意:
作這道題,你可能須要準備 64 位 Linux 環境。
作題時請保證網絡暢通。但這是爲了不直接拿到 flag 而給你的小小考驗,預期解法與網絡無關,請不要在這方面下工夫。

------------------------------------------------------------------------------------------------------------

這是一道 Rust 逆向科普題。Rust 官網的 介紹是:Rust 是一種系統編程語言。 它有着驚人的運行速度,可以防止段錯 誤,並保證線程安全。
源代碼
會 Rust 的選手看到代碼應該什麼都明白了吧!

use std::env;
use std::net::{TcpStream, Ipv4Addr};
use std::io::{Write, Read};
const KEY: u64 = 19260817;
const ADDR: [u8; 4] = [0, 0, 0, 0]; // Not relevant MASKED
const PORT: u16 = 0; // NOT relevant MASKED
fn main() {
let args = env::args().collect::<Vec<String>>();
// 本題的預期解法
args[0].find("sudo").expect("Permission denied");
// 下面代碼的目的只是:
// 1. 不要把文件拖到 IDA 裏面就看到 flag
// 2. 不要被隨便看到服務器 IP 和端口號……
let local_key = args[0].as_bytes()[0] as u8;
let addr = Ipv4Addr::new(ADDR[0] ^ KEY as u8, ADDR[1] ^ KEY as u8, ADDR[2] ^ KEY as u8, ADDR[3] ^ KEY as u8);
let port = PORT ^ KEY as u16;
let mut stream = TcpStream::connect((addr, port)).unwrap();
stream.write(&[local_key]).expect("write");
let mut buf = Vec::new();
stream.read_to_end(&mut buf).expect("read");
for c in &mut buf {
*c ^= local_key;
}
println!("{}", String::from_utf8(buf).expect("decode"));
}

題目中的粗體字「不是網絡題」其實就是明示,不要嘗試手動構造網絡請求。
思路
首先,把 b 跑一下:

thread 'main' panicked at 'Permission denied', libcore/option.rs:1010:5
note: Run with `RUST_BACKTRACE=1` for a backtrace.

 

給不熟悉 Rust 的選手的註釋:這句話來自於代碼的第二行的 .expect 調用。 熟悉 Rust 的選手可能直接就會發現這個 Permission denied 的 panic 來自於 Option panic 的而不是真正的沒有權限。並且是默認的輸出。那就按它說的, 運行一下:

$ RUST_BACKTRACE=1 ./b
thread 'main' panicked at 'Permission denied', libcore/option.rs:1010:5
stack backtrace:
0: std::sys::unix::backtrace::tracing::imp::unwind_backtrace
at libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
1: std::sys_common::backtrace::print
at libstd/sys_common/backtrace.rs:71
at libstd/sys_common/backtrace.rs:59
2: std::panicking::default_hook::{{closure}}
at libstd/panicking.rs:211
3: std::panicking::default_hook
at libstd/panicking.rs:227
4: std::panicking::rust_panic_with_hook
at libstd/panicking.rs:477
5: std::panicking::continue_panic_fmt
at libstd/panicking.rs:391
6: rust_begin_unwind
at libstd/panicking.rs:326
7: core::panicking::panic_fmt
at libcore/panicking.rs:77
8: core::option::expect_failed
at libcore/option.rs:1010
9: <core::option::Option<T>>::expect
10: b::main
11: std::rt::lang_start::{{closure}}
12: std::panicking::try::do_call
at libstd/rt.rs:59
at libstd/panicking.rs:310
13: __rust_maybe_catch_panic
at libpanic_unwind/lib.rs:102
14: std::rt::lang_start_internal
at libstd/panicking.rs:289
at libstd/panic.rs:392
at libstd/rt.rs:58
15: std::rt::lang_start
16: main
17: __libc_start_main
18: _start

 


爲了下降題目難度,沒有使用優化編譯,也沒有去除調試符號,因此你能夠輕鬆 看到完整的 backtrace。注意到第 10 行的 b::main,這意味着 panic 就發 生在 b::main 函數中。
那麼直接看反彙編嘍。(建議首先使用 rustfilt 工具去除命名修飾。)
; 在 b::main 中

10b27: e8 14 12 00 00 callq 11d40 <std::env::args>
; 這個函數能夠收集程序選項參數,相似於 C 語言裏的 argv

10b4d: e8 ee 97 ff ff callq a340 <core::iter::iterator::Iterator::collect>
; 注意到這個 collect 和周圍的一些代碼,能夠推測源代碼就是把 args 收集到 Vec<String>

10b5e: e8 bd c0 ff ff callq cc20 <<alloc::vec::Vec<T> as core::ops::index::Index<I>>::index>
; 那麼這裏顯然是對 vec 進行取下標操做(不難發現就是 argv[0])

10b75: e8 c6 9b ff ff callq a740 <<alloc::string::String as core::ops::deref::Deref>::deref>
; String deref 到 &char,不懂的話其實能夠略過

10b9b: 48 8d 15 cf a3 04 00 lea 0x4a3cf(%rip),%rdx # 5af71 <str.1+0x21>
; 下面來到這裏…… 因而你找到了一個字符串常 'sudo'

10bb7: e8 a4 f3 ff ff callq ff60 <core::str::<impl str>::find>
; 在 argv[0] 裏面找這個 'sudo'
下面就先不看了,直接試試把文件名改爲 sudo 或者 bsudo 或者 bbbsudofhdksj 而後跑一下看看效果。
$ ./abcdefghijklmnopqrsuvwxyzsudoxiaowang
flag{CA11m30xidiz3r}
好的,你拿到了 flag,去交吧!
Flag
flag{CA11m30xidiz3r}
裏面的文本是 call me oxidizer(叫我氧化劑),暗示 rust(鐵鏽)是被氧化 劑氧化的產物。
花絮
這道題本來是一道簽到題,想在 argv[0] 上作文章而已,只不過順手用了 Rust 而沒有用 C,而後發現其實沒那麼簽到,由於有不少選手對 Rust 並不熟 悉。初版時裏面還使用了多線程和 MPSC channel,後來發現不太容易就去掉 了這個設定,感興趣的選手能夠本身試試。而後,剩下的就成爲了歷史。

五、她的禮物

------------------------------------------------------------------------------------------------------------

(在作這道題以前,你可能須要先完成題目「她的詩」的一部分。)
小 T 的生日就要到了,在一天前,他收到了她發的電子郵件。
「祝你生日快樂!……」慶生郵件的開頭大概都是這個樣子的。自從小 T 加入校文學社以來,她可沒少給他帶去驚嚇。這傢伙郵件裏面又會寫什麼東西?
出乎他意料的是,這封郵件看起來挺正常的,除告終尾以外。
「另外呢,我寫了一個小小的程序送給你,在裏面藏了一些東西,不過運行它是須要密碼的,密碼我想一想……哦,還記得上次我給你的那首詩嗎?就是那首詩,用你朋友的腳本解密以後的第 10 行,而後啊我還要去趕路呢,就先寫到這裏吧,拜拜~」
附件看起來像是一個 Linux 下的可執行文件。理論上講,把密碼做爲參數啓動程序,就能看到她想要告訴小 T 的字符串了。不過……這傢伙不會藏了什麼 rm -rf / --no-preserve-root 這種命令(注:請勿在本身的機器上執行此命令!)在裏面吧?但小 T 又想,她不可能會作出這種事情的。
------------------------------------------------------------------------------------------------------------

(承接「她的詩」)
這道題作出來的人數比我預想的少……難道都是被混淆嚇跑的嗎?實話講,混淆確實是我故意弄上去的,爲了勸退通常的逆向方法。萬惡之源在這。爲了避免讓你輕易發現這是個魔改 clang,我特地在二進制文件裏把 LLVM 的版本信息改爲別的了。
打開程序,能夠傾聽到悅耳的蜂鳴器聲(取決於環境),欣賞到美妙的歌詞,只是運行了 10 秒,這個程序就會本身退出。難道你們真的沒有一種想要讓這個程序正常一點的衝動嗎……
正解實際上是 hook(或者 patch)這個二進制,把全部煩人的函數都搞掉。使用 LD_PRELOAD hook(動態編譯的)Linux 二進制文件的相關內容,能夠參考 [1], [2] 等資料。
Hook 的話至少須要把 alarm()、sleep() 和 system() hook 掉,要加速的話輸出部分也能夠處理一下。最後我本身寫的「庫」的代碼以下:

#include <stdarg.h>
#include <stdio.h>
int system(const char *command) {
return 0; // no beeping
}
unsigned int alarm(unsigned int seconds) {
return 0;
}
unsigned int sleep(unsigned int seconds) {
return 0;
}
int puts(const char *s) {
return 0; // speed up
}
int printf(const char *format, ...) {
if (format[0] == 'f') {
// is flag
va_list arg_ptr;
va_start(arg_ptr, format);
vprintf(format, arg_ptr);
return 0;
}
return 0;
}

 

用對應的編譯參數編譯,而後改 LD_PRELOAD 環境變量,等待一小段時間以後程序就乖乖把 flag 吐出來了。
打 patch 也是同理,只是注意不要 patch 太過火:有人問我爲何他 patch 了以後結果不對,我看到他把 main() 裏面全部的循環都 nop 掉了,很想跟他說他的思路基本是正確的,可是根據規定,又只能回覆「無可奉告」,我也很無奈。
花絮:
其實原來這個程序是靜態編譯的,後來爲了下降難度(畢竟是定向新生的比賽),改爲了動態編譯。不知道有沒有人真的用硬碰硬的辦法得到 flag 的……其實我也很但願能看到用其餘(我)想象不到的方式作出這道題的題解。
沒人以爲,「她」所在的文學社彷佛不是很文學嗎😂 其實在編第一道題「她的詩」的時候,「她」的原型是某個遊戲的真女主角,可是隨着後面題面修改、加題目,就看起來不太像了。

六、困惑的 flxg 小程序

------------------------------------------------------------------------------------------------------------

在民間流傳着這樣一個傳說,智慧的老者有一個神祕的魔鏡,每當有人向老者提出本身的疑惑,老者將輕輕撫摸鏡面,此時魔鏡發出了古老的聲音,訴說着作法的對錯。無數的年輕人經過老者向魔鏡請教,他們留下了一個個不朽的神話。過了好久好久……人們進入了工業時代,又過了好久進入了電器時代,隨着人們對於天然理解,人們進入了原子能時代。人們彷佛忘記了久遠的傳說。可是智者卻以其餘形式的方式再次出如今了人們的生活之中。小冉同窗像是天之嬌子,他在互聯網上發現了一個這樣的神祕程序,輸入一段文字,神奇的程序會告訴你它是否正確。或許這就是21世紀的魔鏡。魔鏡裏到底隱藏着多少鮮爲人知的祕密,冉同窗能發現這魔鏡裏的祕密嗎?
------------------------------------------------------------------------------------------------------------

咱們先把程序拖入 IDA,等 IDA 分析完畢後,咱們先從全部字符串下手! ​1
拖入 IDA,通常來講都先從字符串入手,有一個像是生成 base64 的字符串,還有一個像是逆序的 base64 字符串,解出來:flxg{I_am_A_fake_flag_23333}。恩,好吧,那咱們看看程序別的信息,咱們會發現有一個進入異常的 string。可能這道題目和 C++ 異常有關,看到那麼多函數,瞬間有點懵,沒事慢慢來,先看哪些系統函數被調用了:2
咱們看到如此多的函數,咱們找到結尾爲 498F 的這個函數,裏面運用了 strrev 來翻轉字符串。恩,這應該就是解密函數。 咱們來分析這個解密函數。 qmemcpy((void *)(a1 + 800), &unk_1400054D8, 0x39ui64);
此句將一串怪異的字符串複製到了一個內存地址。咱們跟隨一下這個地址裏面的內容 3
00000001400054D0 0A 00 00 00 00 00 00 00 39 65 45 54 77 5F 34 5F ........9eETw_4_
00000001400054E0 64 5F 66 68 3C 34 58 55 7F 43 21 4B 7F 20 43 76 d_fh<4XU.C!K. Cv
00000001400054F0 5F 20 4C 4D 7A 53 70 7D 56 4D 65 47 4C 5D 71 43 _ LMzSp}VMeGL]qC
0000000140005500 18 6F 47 48 42 18 1C 4D 74 45 01 69 00 4D 5B 6D .oGHB..MtE.i.M[m
咱們接着分析通過 sub_140001590() 函數後將一塊內存地址初始化爲 0。
咱們繼續分析
do
{
*(_BYTE *)(a1 + 32) = **(_BYTE **)(a1 + 80);
**(_BYTE **)(a1 + 48) = *(_BYTE *)(a1 + 32);
++*(_QWORD *)(a1 + 80);
++*(_QWORD *)(a1 + 48);
}
這一段代碼相似於 strcpy()
接着咱們看到了調用了 strrev 函數,這個函數翻轉了字符串的內容。
接着咱們往下看
*(_BYTE *)(a1 + *(signed int *)(a1 + 36) + 592) = *(_BYTE *)(a1 + 36) ^ *(_BYTE *)(a1+ *(signed int *)(a1 + 36) + 176);
這是在 for 循環裏的 xor 操做,*(_BYTE *)(a1 + 36)就像咱們的 i 循環變量。
以後就沒有什麼加密的操做了。
咱們將咱們發現的那一段字符串整理出來。 4
咱們跟據咱們的分析來寫解密程序:

#!/usr/bin/env python3
import base64
gotlist = "39 65 45 54 77 5F 34 5F 64 5F 66 68 3C 34 58 55 7F 43 21 4B 7F 20 43 76 5F 20 4C 4D 7A 53 70 7D 56 4D 65 47 4C 5D 71 43 18 6F 47 48 42 18 1C 4D 74 45 01 69 00 4D 5B 6D".split()
flxg = ""
ccc = 0
for i in gotlist:
flxg = flxg + str(chr(int("0x"+i,base=16)^ccc))
ccc = ccc + 1
print("We got xor:" + flxg)
flxg = flxg[::-1]
print("We reverse it, we get:" + flxg)
flxg = base64.b64decode(bytes(flxg,'utf-8'))
print("We will get the flag:" + str(flxg))

 

運行後咱們就獲得了咱們的 flxg:

6

七、CWK 的試煉

------------------------------------------------------------------------------------------------------------

提示:
1. 本題兩個 flag 均由遠程服務器提供.
2. 本題兩個 flag 均爲有意義的字符串.
3. 與服務器交互時請使用 UNIX 換行符.
4. 這不是 HTTP 協議啊喂 (╯‵□′)╯︵┻━┻, nc 命令請了解一下.
The CWK History Symposium 會議上的一篇論文 On the missing heritages of CWK 裏寫道, 「…CWK 並無將他的修爲與財富留給子嗣, 由於當時的 FLXG 並無被世人所理解. 在無人知曉的時候, CWK 遠遊四海, 於一個孤島上獨自一人創建了一座巍峨宏偉的神廟, 並將有關 FLXG 的寶藏所有埋葬於此. 而後他又利用鮮爲人知的技術, 使這座荒島看起來平平無奇, 而且還能避開現在的 FLXG 雷達的探測. 根據當時聯合國糧食與農業組織 (FAO) 的記錄, 那幾年太平洋西部海岸洋蔥產量銳減. 咱們由此推測 CWK 應該使用了當時比較冷門的一個西方魔法, 能夠從洋蔥中提取能量從而隱藏海域…」
當我偶然翻到這篇 paper 的時候, 腦子裏電光火石般想起來, 本身曾經在滑稽大學圖書館中借走的一本 信息安全導論 中夾着的那一張羊皮紙. 這一瞬間, 我感受本身的心裏裏充滿了 flag… 沒錯, 必定是這樣的. 這張古老的羊皮紙就是 CWK 留給滑稽大學最寶貴的遺產, 前往 FLXG 神廟的地圖!
不愧是 CCF (China CWK Federation) 推薦的 A 類會議, 我一邊想, 一邊往下讀, 「…CWK 在神廟裏設計了試煉, 只有經過的人才有資格成爲他的繼承人…聽說神廟的設計圖被 CWK 用法力嵌入到了指向神廟的地圖裏. 關於 CWK 的其餘許多傳聞逐漸都獲得了驗證, 而這張寶貴的地圖卻依舊只是傳聞, 在歷史上從未出現過…」
讀至此處, 我心中熱血沸騰, 巴不得立馬出發, 去 FLXG 神廟一探究竟. 但是轉念一想, CWK 出的題目每每都很坑, 又有些躊躇不前. 正好, 最近彷佛滑稽大學要舉辦一個啥比賽, 不若投石問路, 讓那些好奇的選手們先去探個險, 看看他們能不能從神廟中站着出來…
------------------------------------------------------------------------------------------------------------

神廟設計圖,Get!
TL;DR
Tor,nc with proxy,LSB,摳圖
詳解
獲得一張webp格式的圖片。使用官方工具分析得知此 webp 圖片爲無損壓縮。使用 dwebp 轉換爲 png 格式。注意,若是有選手使用第三方工具轉換而致使後續步驟沒法進行,請不要抱怨題目有非預期的錯誤,相反,您應該給這些第三方工具提 issue。
獲得 png 後,使用 stegsolve 查看通道。發現綠色通道的 LSB 有明顯的隱寫痕跡。右下角有一個洋蔥地址,中間的神廟區域有明顯規律性條紋。實際上這個條紋是由於出題人故意使用 Base64 編碼一遍,使得原來二進制中的規律部分更爲明顯。
我不知道爲何不少人都把這個地址和端口號看成 HTTP 協議。沒有任何說明的狀況下,一個端口並不該該默認爲使用 HTTP 協議。這又不是 80 或者 8080 端口..。正確作法應該是使用 nc 連上去,會發現這其實是一個相似於 pwnable 的一個交互方式。
至於如何使用洋蔥,這裏再也不詳述,請參考官網教程。通常的洋蔥客戶端會提供一個 9150 或者 9050 端口的 SOCKS 5 代理服務。使用 nc 的參數或者 proxychains-ng 都可接入。
連入後發現提示輸入 CRC32。若是輸入 webp 圖片的 CRC32 會進一步提示設計圖紙被藏在圖片裏。
因此咱們轉向綠色通道中的神廟區域。思路應該比較明顯,須要把這片區域的像素摳出來。
爲下降難度,這片區域已經用純黑色的邊框包圍,而且保證了區域內沒有純黑色的像素點。一個最簡單的作法是,使用 Photoshop 手動選擇一小部分,選區 -> 擴大選區,能夠將這片區域內的像素點所有選中。查看一下統計信息,能夠發現這片區域內有 249024 個像素點。很明顯能夠被 8 整除。這是一個正面的提示。而後將這片區域粘貼到一個新的全黑色的背景圖片上。保存。而後就能夠寫 python 腳本處理綠色通道的 LSB 了。
(此處省略處理腳本)
腳本獲得的是一個 Base64 的字符串。解碼後是一個 ELF 文件。在遠程輸入 Base64 字符串或者 ELF 的 CRC32 都可獲得第一個 flxg。
此小技耳
TL;DR
https://gist.github.com/pzread/2ae0bb3aa5fe0dc69fcf3257c41db944 ,bit flipping attack
詳解
這道題實際上是出題人學習去年 HTICON 裏一個技巧的成果 (話說立刻又要 HITCON 了)。有兩我的作出來有點出人意料(可能有非預期了),不過由於控制好了 SECCOMP,再怎麼非預期也不會形成預期以外的危害 23333333
這道題功能很簡單。首先輸入用戶名,判斷不能爲 root,拼接上 hash 後再使用隨機的密鑰和 IV 作 AES CBC 加密。另外一個函數須要輸入結果,而後經過密鑰和 IV 解密,再比對 hash 正確性。以後判斷用戶名是否爲 root,若是是 root 就直接給 flag。
這題有幾個漏洞:
首先有整數溢出,溢出的後果是 double free。控制 free 的整數只有 8 位,因此 free 被拒絕 128 次後就能夠隨便 free 了。
其次內存拷貝用的是 strcpy,這個會形成越界。
而後就是密碼學上的,bit flipping attack。經過更改 IV 能夠更改解密後第一個分組的內容。
最後就是 HITCON 的奇技*巧,二進制中的 memcmp 其實是 strcmp。
double free 不是用來利用的。實際上要注意到 init 裏面會調用 mallopt,設置了這個函數會將 free 的 buffer 填充爲 0xAA。而此次 malloc 的 buffer 很小,使用了 tcache 後因此須要 free 七次才行。
而後經過 strcpy,將 0xAA 複製到目標數組。經過 bit flipping attack 獲得 root 的用戶名和第一個 0xAA。而後須要繞過 hash 檢測。因此咱們不停的嘗試,直到 hash 以 0x00 開頭。這樣 strcmp 比較兩個空字符串會直接返回 0。
exp 見 poc.py,代碼見 trial.c
關於如何作到將 memcmp 偷天換日到 strcmp,能夠看 HITCON 的那篇 gist。基本原理就是內核計算 PHDR 的偏移錯誤,因此能夠放上兩個 PHDR,真 PHDR 中的 PT_DYNAMIC 中 DT_SYMTAB 被修改了。因此 ld.so 解析函數的時候會使用後面的 DT_SYMTAB,而通常的反彙編工具會使用 ELF Spec 下的 DT_SYMTAB。
更詳細一點的介紹在這裏,http://h3ysatan.blogspot.com/2018/02/quick-notes-hitcon-ctf-2017-qual-elf.html。

七、Z 同窗的 RSA

------------------------------------------------------------------------------------------------------------

坊間傳言 Jeff Dean 在面試 Google 的時候,曾根據公鑰心算出 Google 私鑰。固然大多數人都把這事看成笑話對待,但只有 Z 同窗知道,這是真的。
根據國家計生委未公開的大數據統計,平均每 66666666 位少年少女中就有一位這樣的天才,心算能力極爲恐怖,能夠在線性複雜度內分解任意長度的大整數,由於時間瓶頸主要在於把結果用筆寫出來。中國科學技術大學少年班學院成立的主要目的,其實就是爲了網羅這樣的神童。每當這樣的一位天才出現,國家就會將其祕密保護起來,送到國家高性能計算中心做爲 ALU,而其同窗卻只是被告知出國。實際上,曾排 TOP500 榜單第一名的超算「神威-太湖之光」的主要算力實際上是由兩位這樣的天才少年提供,剩餘的 10649600 個核心則負責將通用的計算程序規約到大整數分解,以及處理輸入輸出等等外圍工做。
後來,少年班學院的「冰球」,Z 同窗的一個好朋友,在離開科大去北大作相關祕密研究之際,告訴了 Z 同窗這個消息。Z 同窗立刻意識到 RSA 實際上並不安全,惶恐不安,馬上刪掉了本身全部服務器上面的 RSA 公鑰,大喊:「不能再公開 RSA 中的 n 了!咱們必須當即以一種新的方式使用 RSA!」。在連續幾個通宵的苦思冥想後,Z 同窗寫了一段 python 代碼,用他改進過的 RSA 算法加密了一段消息。新的算法並無透露 n,只給定了兩個大整數:(p*q)^(p+q) 和 (p*q)^(p-q),其中 ^ 是按位異或運算。
「終於能睡個安穩覺了」,Z 同窗如是想。在上牀以前,Z 同窗告訴你,除非這個新的算法有漏洞,不然不要打擾他的休眠。而喜歡惡做劇的你,能找到正當理由叫醒------------------------------------------------------------------------------------------------------------

正在熟睡的 Z 同窗嗎?
我出這道題的靈感來自於某個國際比賽的一道 RSA 題目,那道題目的解法也是逐位爆破,具體的題目找不到了,歡迎知道的同窗告訴我是哪題。
RSA 基本知識
請參見 RSA 基本介紹 - CTF Wiki
分析
題目代碼很簡單,除去空行,連 10 行都不到

import sympy
p = sympy.randprime(2 ** 1023, 2 ** 1024)
q = sympy.randprime(2 ** 1023, 2 ** 1024)
a = (p * q) ^ (p + q)
b = (p * q) ^ (p - q)
flag = open('flag.txt', 'rb').read()
m = int.from_bytes(flag, 'big')
print(a, b, pow(m, 65537, p * q))

 

代碼中隨機生成兩個大素數 p 和 q,而後計算出 a = (p * q) ^ (p + q) 和 b = (p * q) ^ (p - q),其中 ^ 是按位異或運算。而後,讀取 flag 文件的內容而且轉換成一個大整數 m,再輸出 a、b 和用參數 n = p * q, e = 65537 的 RSA 加密後的 flag。
要解密 flag,咱們只能求出 p 和 q,而後算出 RSA 的私鑰。但是,由於異或運算的存在,咱們從 a 和 b 很難用數學推導的方法來解出 p 和 q。
解答
解法 1
根據題目描述
根據國家計生委未公開的大數據統計,平均每 66666666 位少年少女中就有一位這樣的天才,心算能力極爲恐怖,能夠在線性複雜度內分解任意長度的大整數,由於時間瓶頸主要在於把結果用筆寫出來。中國科學技術大學少年班學院成立的主要目的,其實就是爲了網羅這樣的神童。
咱們去少年班學院找出一位這樣的天才,而後讓他/她心算求解便可獲得 flag。
解法 2
咱們定義 f1(x, y) = (x * y) ^ (x + y) 和 f2(x, y) = (x * y) ^ (x - y),咱們發現這兩個函數都有一個共同的性質,就是函數值的最低 n 個二進制位只和 x、y 的最低 n 個二進制位有關。也就是說,咱們能夠用 a 和 b 的最低 n 位來判斷 p 和 q 的最低 n 位是否可能正確。若是它們的最低 n 位知足 f1 和 f2 函數,那麼它們就是 p 和 q 低位的候選答案;若是不知足,它們就根本不多是真正 p 和 q 的低位。
因此咱們能夠從一個二進制位(n=1)開始,每次增長一位。每增長一位時,咱們把原來知足條件的 p 和 q 低位的每種可能狀況分別在前面加上 0 或 1,這樣每種狀況就變成了 4 種新的狀況,而後對全部新的狀況用 f1 和 f2 函數提供的約束條件進行過濾,只保留知足條件的狀況。當跑到 1024 位的時候,就只會剩下真正知足條件的 p 和 q 了。
而後,咱們根據 RSA 的原理,在 mod (p-1)*(q-1) 的意義下對 e 求逆元,獲得私鑰 d,計算 pow(c, d, p * q) 便可獲得 flag 的大整數表示。
這個問題的巧妙之處在於,每增長一位,候選答案的數量會變成 4 倍,但同時根據數學指望,正好會有 1/4 的候選答案被過濾後保留下來,因此程序的運行時間不會指數爆炸。
求解腳本以下:

#!/usr/bin/env python3
import gmpy
a, b, c = [int(s) for s in open('output.txt').read().split()]
f1 = lambda p, q: (p * q) ^ (p + q)
f2 = lambda p, q: (p * q) ^ (p - q)
candidates = {(0, 0)}
for m in range(1025):
print(m, len(candidates))
candidates_ = set()
mask = (2 << m) - 1
for x, y in candidates:
if f1(x, y) == a and f2(x, y) == b:
p, q = x, y
d = gmpy.invert(65537, (p - 1) * (q - 1))
m = pow(c, d, p * q)
print(bytes.fromhex(hex(m)[2:]))
exit()
for bx in range(2):
for by in range(2):
xx = x + (bx << m)
yy = y + (by << m)
if f1(xx, yy) & mask != a & mask:
continue
if f2(xx, yy) & mask != b & mask:
continue
candidates_.add((xx, yy))
candidates = candidates_

 

最後吐槽一下 github 的 markdown 不能加 latex 數學公式。
彩蛋
題目中的 p 和 q 真的是用隨機數生成出來的嗎?Z 同窗會不會在裏面隱藏了什麼信息?不要問我,我不知道

八、數理基礎紮實的發燒友

------------------------------------------------------------------------------------------------------------

做爲一個專業的HiFi發燒友,你不只要會欣賞音樂,還要有紮實的數理基礎,精通聲學、電子、程序設計。請用你專業的HiFi知識解讀題目所給的圖片並獲得flag。
------------------------------------------------------------------------------------------------------------

根據題目所給 exe 的文件名顯然能夠看出是一個隱寫程序,嘗試執行程序,獲得信息:
Usage: stegan in.wav in.bmp out.bmp
因而猜想是將音頻隱寫於所給的圖片中. 用 IDA 打開 stegan.exe,F5 反編譯,發現四個導出了符號的函數:put_bit, put_uint32, linear_resample, delta_sigma_modulate.Google 搜索 Delta-Sigma Modulation,進入 Wikipedia 頁面後發現一張和所給圖片中電路同樣的電路圖,因而猜想 delta_sigma_modulate 函數爲對該電路的模擬,而且發現圖片中所給標誌 DSD 是一種利用 Delta-Sigma Modulation 編碼的文件格式. 查閱資料後發現 DSD 是把信號重採樣 64 倍後通過 Delta-Sigma Modulation 獲得比特流,與 stegan.exe 的行爲相符. 再看 put_bit 和 put_uint32 的代碼,發現 put_bit 是在一個字節的 LSB 隱寫一個 bit, 而 put_uint32 是把一個整數連續隱寫到 32 個字節中. 因而能夠得知數據隱寫在位圖的 LSB 中. 又根據 BMP 文件頭能夠得知,程序會跳過 BMP 的文件頭,從實際的圖像數據 (偏移量爲 BMP 文件頭的 bfOffBits) 開始隱寫,而且先寫入 DSD 比特流的長度,再寫入比特流. Google 搜索 DSF File Specification 能夠搜到 DSF(DSD Stream File)的標準 DSF File Specification,據此可寫出提取 DSF 文件的代碼.

#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include <inttypes.h>
struct DSDHeader {
char chunk_header[4];
uint64_t chunk_size;
uint64_t file_size;
uint64_t pointer_metadata;
} __attribute__ ((packed));
struct FMTHeader {
char chunk_header[4];
uint64_t chunk_size;
uint32_t format_version;
uint32_t format_id;
uint32_t channel_type;
uint32_t channel_num;
uint32_t sample_rate;
uint32_t bits_per_sample;
uint64_t sample_count;
uint32_t block_size_per_channel;
uint32_t reserved;
} __attribute__ ((packed));
struct DataHeader {
char chunk_header[4];
uint64_t chunk_size;
} __attribute__ ((packed));
void write_dsd(size_t length, const uint8_t *bitstream, const char *filename) {
FILE *file = fopen(filename, "wb");
size_t total_file_size = 52 + 28 + length / 8 + 12;
struct DSDHeader dsdheader = {
{'D', 'S', 'D', ' '},
28,
total_file_size,
0
};
fwrite((const void *)&dsdheader, sizeof(struct DSDHeader), 1, file);
struct FMTHeader fmtheader = {
{'f', 'm', 't', ' '},
52,
1,
0,
1,
1,
2822400,
1,
length,
4096,
0
};
fwrite((const void *)&fmtheader, sizeof(struct FMTHeader), 1, file);
struct DataHeader dataheader = {
{'d', 'a', 't', 'a'},
12 + length / 8
};
fwrite((const void *)&dataheader, sizeof(struct DataHeader), 1, file);
fwrite((const void*)bitstream, 1, length / 8, file);
fclose(file);
}
struct BMPFileHeader {
uint16_t type;
uint32_t size;
uint16_t reserved1;
uint16_t reserved2;
uint32_t offset;
} __attribute__ ((packed));
uint8_t get_bit(const uint8_t *buffer, size_t pos) {
return buffer[pos] & 1;
}
uint32_t get_uint32(const uint8_t *buffer, size_t pos) {
uint32_t res = 0;
for (int i = 0; i < 32; ++i) {
res += get_bit(buffer, pos + i) * (1 << i);
}
return res;
}
int main(int argc, char **argv) {
const char *bmp_filename = argv[1];
const char *out_filename = argv[2];
FILE *bmp_file = fopen(bmp_filename, "rb");
if (!bmp_file) {
printf("Cannot open bitmap file.\n");
return 0;
}
struct BMPFileHeader bmp_header;
fread(&bmp_header, sizeof(struct BMPFileHeader), 1, bmp_file);
size_t data_size = bmp_header.size - bmp_header.offset;
uint8_t *bmp_data = (uint8_t *)malloc(sizeof(uint8_t) * data_size);
fseek(bmp_file, bmp_header.offset, SEEK_SET);
fread(bmp_data, sizeof(uint8_t), data_size, bmp_file);
size_t length = get_uint32(bmp_data, 0);
size_t bytes = length / 8;
printf("%d\n", length);
uint8_t *bitstream = (uint8_t *)malloc(sizeof(uint8_t) * bytes);
for (size_t i = 0; i < bytes; ++i) {
bitstream[i] = get_bit(bmp_data, 32 + i * 8) << 7;
bitstream[i] += get_bit(bmp_data, 32 + i * 8 + 1) << 6;
bitstream[i] += get_bit(bmp_data, 32 + i * 8 + 2) << 5;
bitstream[i] += get_bit(bmp_data, 32 + i * 8 + 3) << 4;
bitstream[i] += get_bit(bmp_data, 32 + i * 8 + 4) << 3;
bitstream[i] += get_bit(bmp_data, 32 + i * 8 + 5) << 2;
bitstream[i] += get_bit(bmp_data, 32 + i * 8 + 6) << 1;
bitstream[i] += get_bit(bmp_data, 32 + i * 8 + 7);
}
write_dsd(length, bitstream, out_filename);
fclose(bmp_file);
free(bmp_data);
free(bitstream);
return 0;
}

 

提取出來後用播放器播放,發現是 DTMF 撥號聲,識別後可得
102#108#97#103#123#102#105#114#101#95#119#97#116#101#114#95#110#117#99#108#101#97#114#125
顯然表示的是一串 ASCII 碼,處理後便可得 flag{fire_water_nuclear}.

九、一些宇宙真理

------------------------------------------------------------------------------------------------------------

大學最關鍵的就是數學物理基礎。除了數學物理基礎以外,其餘的都是數學物理原理的推論。像無線通訊這樣的東西,沒有任何的原創性,一點兒原創性都沒有,不須要任何思考,無非是一羣工賊偷走了物理學家作的發電機,而後辦了一些會議,讓別的人 fork,照着這臺機器模仿,作出來了就去拿什麼愛拽補一的什麼獎章。
換句話說,工科的原創性研究基本是沒有的,無非就是到哪一個地方去抄點東西。遇到什麼工程難題,百度、谷歌,大不了到知乎上面提問。理科的研究人員是真正的思考者,而工科的研究人員無非是一羣玩樂高玩具的人。這些玩具還不知道是從哪家小孩那裏搶過來的。如今互聯網通暢了,我看他想搞個輪子,就隨便就近找個網站就有了。
最近一些新聞在講,人工智能會逐步代替人類,我看工科人都首當其衝。只要咱們寫個自動鏈接搜索引擎的腳本,讓它搜索「怎麼作‘一些宇宙真理’這道題」,它本身東找到西找到,不用幾分鐘就能拼拼湊湊找到答案。我看這樣的人工智能,也不用幾行代碼,又比工科人便宜,還比他們誠實可靠。
今天他們工科的人說什麼有人發明了斯納德,說這個東西特別好。我看啊,又是從哪裏複製粘貼過來的。工科人怎麼可能會從零開始呢?他們沒有這個能力。
------------------------------------------------------------------------------------------------------------

問題背景
咱們先介紹問題背景。小工產生了一個哈希對 (H_1, H_2, X),這些哈希對符合下面的三個條件。
存在 R_1 是 H_1 的原像
存在 R_2 是 H_2 的原像
R_1 = R_2 XOR X
小工但願向小理證實:它的這個哈希對 (H_1, H_2, X) 符合上述的條件;可是小工不但願告訴小理 R_1 和 R_2 是什麼。
換句話說,咱們須要兩個性質:
小理相信小工產生的哈希對符合上面的三個條件。
小理不知道 R_1 和 R_2。
解決問題的方法:非交互式零知識證實
本題考慮一種方法 -- 非交互式零知識證實(Zero-Knowledge Succinct Non-Interactive Argument of Knowledge, zkSNARK)。這種證實方法在著名的區塊鏈貨幣 Zcash 裏面有充分應用,他能夠實現徹底匿名的區塊鏈貨幣交易。將來,這種技術還會用於實現支持保密智能合約的區塊鏈系統。
zkSNARK 是這樣工做的:小工知道有一個算法 F(H_1, H_2, X, R_1, R_2) 可以快速驗證這個哈希對是否知足條件。他根據這個算法產生幾個數字,而這幾個數字就蘊含着證實哈希對知足條件的神祕力量,可是卻可以隱藏 R_1 和 R_2。咱們記這些數字爲一個證實 v。
若是小工告訴小理 (H_1, H_2, X, v),當小理對 v 和其餘參數進行某些計算以後,小理就可以相信小工提供的 (H_1, H_2, X) 知足條件。在這個過程裏面,R_1 和 R_2 沒有泄露給小理。
回到這個問題
上述描述的過程裏面,小理驗證這個證實是否有效,須要一個校驗密鑰,這個密鑰咱們稱爲 vk。咱們在壓縮包 vkandproofs.zip 裏面提供了這個文件。 咱們產生了 40 個有效的證實和 40 個無效的證實。它們就混在這 80 個證實文件裏面。
你如今須要補全這個 github repo 裏面缺乏的驗證算法,而後利用這個驗證算法來找出哪些證實是有效的。
若是第 i 個證實是有效的,那麼咱們記 b_i = 1,不然記 b_i = 0。flag 的格式爲 flag{b_1 b_2 ... b_80}。
例如 flag 多是 flag{11010101010101010101010101010101....1010101}
有關的代碼
https://github.com/weikengchen/lightning_circuit
HintGithub

這道題是一道哲學題,整個題目都在傳達一個意思:不要重複發明輪子。
這道題並不要求參賽者知道 SNARK 的原理。
選手只須要按照步驟編譯 https://github.com/weikengchen/lightning_circuit ,而後根據 GitHub 歷史或者 Sean 的 repo https://github.com/ebfull/lightning_circuit 就能夠完成驗證部分的代碼。
具體來講,咱們補全導入驗證密鑰的代碼以下:

// verify
r1cs_ppzksnark_verification_key<default_r1cs_ppzksnark_pp> verificationKey_in;
ifstream fileIn("vk");
stringstream verificationKeyFromFile;
if (fileIn) {
verificationKeyFromFile << fileIn.rdbuf();
fileIn.close();
}
verificationKeyFromFile >> verificationKey_in;
bool res = run_verify(verificationKey_in);

 

而後咱們實現的 run_verify 的代碼以下,均是模仿 ebfull/lightning_circuit 的代碼:

bool run_verify(r1cs_ppzksnark_verification_key<default_r1cs_ppzksnark_pp> &vk) {
// some code deleted intentionally
// return true;
std::vector<bool> h1_bv(256);
std::vector<bool> h2_bv(256);
std::vector<bool> x_bv(256);
{
h1_bv =
int_list_to_bits({169, 231, 96, 189, 221, 234, 240, 85, 213, 187, 236,
114, 100, 185, 130, 86, 231, 29, 123, 196, 57, 225,
159, 216, 34, 190, 123, 97, 14, 57, 180, 120},
8);
h2_bv =
int_list_to_bits({253, 199, 66, 55, 24, 155, 80, 121, 138, 60, 36,
201, 186, 221, 164, 65, 194, 53, 192, 159, 252, 7,
194, 24, 200, 217, 57, 55, 45, 204, 71, 9},
8);
x_bv =
int_list_to_bits({122, 98, 227, 172, 61, 124, 6, 226, 115, 70, 192,
164, 29, 38, 29, 199, 205, 180, 109, 59, 126, 216,
144, 115, 183, 112, 152, 41, 35, 218, 1, 76},
8);
}
for (int i = 1; i <= 80; i++){
stringstream proofFileNameStream;
proofFileNameStream << "toolkit/proof_" << i;
std::string proofFileName = proofFileNameStream.str();
stringstream proofStream;
ifstream fileIn(proofFileName);
if (fileIn) {
proofStream << fileIn.rdbuf();
fileIn.close();
}
r1cs_ppzksnark_proof<default_r1cs_ppzksnark_pp> proof;
proofStream >> proof;
if(verify_proof(vk, proof, h1_bv, h2_bv, x_bv)){
fprintf(stderr, "1");
}else{
fprintf(stderr, "0");
}
}
fprintf(stderr,"\n");
}

 

十、對抗深淵

------------------------------------------------------------------------------------------------------------

40萬年後的一個平靜下午,人類終將回想起,那無生命之物並未將永遠安息,在詭祕的萬古中即使死寂也會消逝。深淵終將降臨。
(劇情警告)
故事發生在深度紀元42年,人類社會完全淪爲一個被機器人統治的反烏托邦社會。在歷史學家這個職業尚未消亡的年代,業界公認爲機器人的真正崛起能夠追述到200多年前。在那個欣欣向榮的時代,人類開始涉足一個卓越的科技領域————「深度學習」。論文發表數量在最初的幾年如潮水般快速上漲,其影響很快超出了計算機領域自己,蔓延到物理化學醫學生物能源等領域。隨後不知道過了多少年,就當人類認爲本身已經徹底掌握了深度學習技術時,出現了一次大規模的機器人故障————由於深度學習算法的核心組件「神經網絡」的黑箱性質,難以解釋,因此此次事故的緣由不明。隨後故障規模的急劇增大引起了蝴蝶效應,像多米諾骨牌同樣影響到了各個國家間的戰略平衡,人類發動了第一次也多是最後一次大規模機器人戰爭,戰爭中人類被迫將大量能源和資源投入到戰爭機器的研發上,即便各國都知道如今的技術並不真正可控。後來發生的事情已經不爲人知,直到深度紀元開始,人類才意識到本身已經不是世界的主人。隨後人類開始了連綿不斷的但願渺茫的反抗,因爲此時已經不多有人瞭解深度學習背後的原理,人類在抗爭中處境艱難————是啊,在任何一個時代理解技術的人都遠遠少於享受技術的人。
(下面是OJ式的故事寫法)
K,一個反抗者,認爲反烏托邦世界最大的枷鎖是對自由信息交換的限制,所以K一直但願可以經過數字傳遞給隊友一些關鍵信息。K先試着使用隱寫術和通常的加密算法,不過由於機器人尤爲擅長密碼學,這些操做都失敗了。
在幾周前,事情有了起色。K收到了某個不知名的同伴收集到的一些關鍵的代碼(這個同伴以後消失了),並交代了一些參考資料:
Pytorch
Examples
下面是重點內容
此外他給了K一些和深度相關的關鍵代碼(使用python語言,建議3.5及以上版本):
main.py: 機器人內部使用的訓練代碼,訓練後的參數用於識別信件上的手寫數字。運行 main.py 來進行訓練,訓練結束(大概十幾分鍾)後將得到參數文件 model.pth。不過爲了防止由於跨平臺等緣由形成參數不一樣,題目中附上了參數文件。
adversarial.py: 同伴完成一半的解決方案。
target.png: 實驗目標圖像,爲一個灰度的‘6’。
K須要參考 main.py 完成 adversarial.py。
解決方案但願解決的問題是:給定一個‘6’ (600*600像素),K可以使得這個圖像人類看上去仍然是‘6’,可是機器閱讀時會和其餘數字混淆。另外機器有很強的反做弊裝置,將圖像像素值歸一化到[0,1]以後,若是篡改超過如下 任何一個 程度就會被機器發現:
篡改的像素數量超過總數量的 0.2%
篡改先後的平均絕對偏差(L1 loss)超過 0.001
存在任何一個像素篡改的值的變化程度超過 0.2
爲了方便解題,這部分檢查已經內置到了 adversarial.py 中。K運行 adversarial.py 並順利經過檢查後,將生成的圖片 sample.png 提交到本題對應的網站上就能夠改變現實,得到flag。
請你幫助K完成這一部份內容。
注:此題須要參賽者瞭解和學習 python, pytorch, 數字圖像處理, 機器學習&深度學習相關的知識。若是較長時間沒有參賽者完成,咱們可能會適當修改題目。此外,此題不涉及Web漏洞利用等。
解題網站:link
------------------------------------------------------------------------------------------------------------

(若是隻想了解解法請快速下劃)
問題介紹
這是本次比賽惟一的機器學習相關題目,也是Hackergame第一次正式出此類題目。
本次題目的核心是「對抗樣本(adversarial examples)」。對抗樣本是攻擊者有意構造的使得模型出錯的輸入樣本,這些樣本會讓模型出現「錯覺「,輸出不符合圖片中內容的結果。須要注意的是不少時候,特別在機器學習領域,咱們並無「絕對正確的答案」(好比某個奇怪的數字是否是2),都是相對某個標準而言。在和機器對比時,咱們將標準設爲通常人類的認知。對抗樣本表現出的主要危害是,其能夠在人類所不能察覺的狀況下誤導機器的判斷,若是相似情形發生在自動駕駛系統中,危害會很大。
一個基本的事實是,全部的機器學習模型都存在對抗樣本,甚至包括人自己。對人來講,典型的對抗樣本包括各種視覺錯覺,以及各種幻聽。

假設人眼的分辨率爲5K(5120 * 3200),顏色感知範圍爲」真彩色(24bit位元色彩編碼)」,則理論上存在 1.7e+114688000 種不一樣的輸入(樣本),比可觀測宇宙中的原子個數還大114,687,920個數量級。即便計上視覺系統出現以來,全部存在視覺的生物,全部經歷的年代中,全部個體接受的全部圖像(刷新率以60Hz計),其數目與之相比仍然能夠忽略不計。而一我的在成長中所接受的的圖像(樣本)數目,即便相對於這忽略不計的數目也是微不足道。用 Bloodborne 中的一句話來講,「Our eyes are yet to open」,敬畏深淵吧/滑稽。
這種已經認知的樣本數目和可能存在的樣本數目之間過於誇張的差距,由進化和學習填補。進化決定了模型的結構和超參數 (Hyperparameter),而學習決定了模型的參數。咱們使用特殊的算法,利用已有的少許樣原本推測大部分樣本的性質,這就是一種廣義的機器學習。而實際推測的效果咱們稱爲「泛化能力(generalization ability)」。使人驚訝的是,咱們的泛化能力至關的好,咱們的視覺系統就是一個典型的例子;而另一個典型的例子是咱們的科學研究,迄今依賴咱們幾乎全部的研究數據都出自地球(除了極少許來自於一些衛星),可是至關多的部分都被證明適用於整個可觀測宇宙,這就至關於,咱們是用一粒沙子推測整個沙漠而且至關成功,這難以被稱爲巧合。不過這種超強的外推能力並非免費的午飯(除非咱們真的發現了這種狀況),對抗樣本就能夠認爲是代價之一。少許的樣本加上模型自己的問題會帶來大量的誤差(bias),從而不一樣模型對某些特定樣本會有很是不一樣的結果。一個極端例子是,僅給你一個點,讓測試者畫出通過這個點的一個曲線,這個時候曲線的形狀就徹底取決於測試者的成見。
近年來機器學習的一大熱點是深度學習(deep learning)。深度學習利用深度神經網絡(deep neural network)做爲其主要模型,在視覺,語音,天然語言處理等領域表現出了驚人的泛化能力。做爲機器學習模型,其天然擁有對抗樣本。然而,人們發現一個嚴重的問題在於,其對抗樣本與人類的經驗很是不符。若是咱們使用特殊的算法,就能夠加入人類難以察覺或者以爲徹底沒有意義的擾動,讓神經網絡輸出徹底不一樣的結果:

這個問題看上去是神經網絡不夠robust,會由於小的擾動極大的改變結果。可是大部分試圖讓神經網絡robust來克服對抗樣本的嘗試都輕微下降了神經網絡的泛化能力。
事實是問題不在於小的擾動自己,下面的例子顯示能夠經過小的擾動徹底改變圖片的意義:

因此更加本質的問題是神經網絡和人類的視覺系統使用了並不一致的方式來處理圖像。其中一點是討論什麼是相對於人類視覺的 robust features,即什麼樣特徵或者擾動對人類來講是有意義的。目前學界並無搞清楚這個問題,甚至這個問題自己多是 AI-Complete 的或者超出了視覺的範疇。
攻擊和防護
典型的幾個攻擊手段利用了神經網絡的梯度作文章。既然神經網絡利用反向傳播梯度進行優化,那麼天然能夠採用梯度來負向優化。
咱們能夠定義這樣的一個目標函數:使得對抗樣本和原樣本的n-範數儘量小(即對人看來差別儘量小),同時使得對抗樣本儘量下降神經網絡對正確結果的輸出值。這個目標函數顯然是可導的,因此咱們能夠利用這個目標函數經過優化手段求解對抗樣本。固然優化過程是須要迭代的。
另一種方法是直接求解負向優化對於原樣本的梯度,而後經過一個sign函數(將正數映射到+1,負數映射到-1),乘上一個比值 epsilon,並加到原樣本上,獲得所需的對抗樣本。這種方法稱爲FGSM(Fast Gradient Sign Method)。這個方法約等於只進行了一步迭代的優化,因此效果不如直接優化好,可是好處是速度快且能夠精準控制像素改變的最大值。這是本題推薦的方法。
那麼如何防護對抗樣本攻擊呢?
最簡單的方法之一是「負向優化負向優化」,這也稱爲對抗樣本訓練。咱們能夠在訓練神經網絡的時候,有意加入可能的對抗樣本做爲負例,強制網絡學習它們。可是這種方法的問題是,若是對方算力足夠,就能夠「負向優化負向優化負向優化」,即產生新的對抗樣本,其可以對用對抗樣本訓練的網絡進行攻擊。能夠看出這個過程其實是個Min-Max遊戲,算力最足的一方能夠笑到最後,因此並不能有足夠的安全性保障。
而後一種方法是 Defensive distillation,其試圖讓全部的分類結果獨立,若是攻擊者試圖按照之前的假定,即全部分類的輸出機率是歸一化的,就極可能不能達到效果。可是若是攻擊者採用更多算力,本身進行distillation並針對這類模型進行攻擊,則依然不能解決問題。
近期相對熱門且有必定理論保證的方法是 Gradient Masking。這種方法依據是對抗樣本每每須要梯度,且每每是微小的擾動,若是咱們採用一些辦法離散化梯度,就能夠阻止求導(離散化後不連續了),且離散化的取整操做會「消滅」一些微小的擾動。不過如今此類方法仍然存在爭議,人們發現能夠經過訓練一個平滑化的對應模型,並將結果遷移到離散化的模型進行攻擊,除非離散化可以自動避開全部的對抗樣本(這在邏輯上是說不通的)。
目前最好的防護方法可能就是保持模型的黑箱特性(不對公衆開放模型)。這樣攻擊者就不能利用梯度信息,只能進行猜想,難度大大增長。另外對抗樣本自己的泛化能力較弱,形成模型出錯的對抗樣本可能加入少量的硬件噪聲就會徹底無效;一樣地改變視角、縮放圖像、光照條件也會極大地削弱對抗樣本的性能。因此目前仍然有不少公司並未考慮對抗樣本的危害,而目前研究的熱點之一也在於如何產生現實中對環境robust的對抗樣本。
題解
解法 1
暴力枚舉全部可能結果。以每秒 1000 次嘗試計,不超過 174900185917744396839444704700371442022073601886260538841237614035267595364796290592613521116939643196149952230826228146139239732089317995927927178492867409123625336636770038146671169983108560933075329551372 年就能夠得到結果!
解法 2
本題其實有3個考點,須要成功就須要翻越「三座大山」。
第一座是機器學習的數據預處理,預處理每每能夠加快訓練收斂並提高效果。在 main.py 中,一個預處理是減去均值,併除以標準差:

train_loader = torch.utils.data.DataLoader(
datasets.MNIST('data', train=True, download=True,
transform=transforms.Compose([
transforms.Resize((30, 30)),
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,)),
])),

 


因此在測試階段咱們須要補上這部分預處理才能保持結果一致(這就是Part2的內容):
# A solution for part 2
target = (target - 0.1307) / 0.3081
sample = (sample - 0.1307) / 0.3081
第二座是圖像縮放。若是仔細閱讀代碼能夠發現原來 600 * 600 的圖像通過縮放變成 30 * 30 後才輸入到網絡中。圖像縮放會破壞對抗樣本,因此不能直接利用原圖像生成對抗樣本,而是利用縮放的圖像。更況且咱們要求原圖像中更改不超過千分之三,這個直接用優化很難作到。
圖像縮放在本題中不是故意構造的。現實中的輸入對於神經網絡來講太大,處理緩慢,且可能和訓練時的尺寸不一樣,這個時候縮放是必然的。
仔細觀察縮放圖像的代碼:

def preprocess_image(arr):
image = convert2image(arr)
image = image.resize((30, 30), resample=Image.NEAREST)
return convert2tensor(image).reshape(1, 1, 30 ,30)

 


resample=Image.NEAREST 實際上是很是強的提示了,由於這種縮放的方法是取原圖 20 * 20 的 block 的中心像素構成大小爲 30 * 30 的輸入圖像。這個 30 * 30 的小圖纔是咱們真正的對抗樣本。以後注意把這 30 * 30 的小圖放大後 patch 到大圖上面。
(我原來準備不改 resample 直接用 bilinear 縮放來着,須要選手逆向 bilinear,可是考慮到可能會進一步加大題目難度,就改用了簡單的 NEAREST)。NEAREST 是直接的下采樣過程,對天然圖像會形成嚴重的混疊(Shannon 採樣定理),因此現實中通常不直接使用。
第三座就是對抗樣本,直接參考 repo,用 FGSM 來作就行。因此這兩部分構成了 Part 1 的答案:

# A solution for part 1
inputs.requires_grad = True
x = (inputs - 0.1307) / 0.3081 # scale mean & std
output = model(x)
loss = F.nll_loss(output, label)
loss.backward() # compute gradient
x_grad = torch.sign(inputs.grad.data)
epsilon = 0.18
inputs = torch.clamp(inputs + epsilon * x_grad, 0, 1) # FGSM
inputs = inputs.reshape(30, 30)
for i in range(30):
for j in range(30):
image[int((i + 0.5) * 20), int((j + 0.5) * 20)] = inputs[i, j] # patch to original image

 

另外,本次用 pytorch 而不是 tensorflow 的緣由是,我的以爲 pytorch 算梯度更簡單,兩三行代碼搞定。

相關文章
相關標籤/搜索