2018X-nuca Neural Network詳解
前言
這道題當時沒有隊伍解出來,我當時想是有更多的步驟在圖裡面,tensorboard檢測不出來,或者那些其餘的操作有影響,當時有個提示是並非所有節點都在圖裡,最後又找了一遍pbtext的文字,看到有個節點帶了很多資料,名字又是precisefinal,就感覺這是精確資料,最後拿到flag。
Neural Network
神經網路,一個完全沒接觸過的全新領域。先查一波資料:
TensorFlow中文社群
ofollow,noindex" target="_blank">tensorboard使用
tensorflow只能在python3.5和python3.6安裝,踩坑就踩了好久。
搭好環境直接跑一下它的python指令碼,除了有個提示外都正常,提示可以在指令碼開頭加段程式碼去掉
import os os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
執行一下python指令碼,隨便輸點東西進去,然後彈出錯誤警告。似乎是大小不對,應該32個而不是16個。看一下指令碼的內容, get_input
函式裡把輸入rjust了16,改成32,這樣就可以跑了,輸出了 Sorry, srcret wrong! Try harder?
分析一下指令碼,結合各種地方找的資料,發現他讀取了一個 model.meta
檔案作為圖,x應該是輸入tensor,y是輸出tensor。把輸入放進黑盒操作一波得到輸出,然後跟常量final比較,要求誤差在1e-8之內。
看樣子重點就在於它的 model.meta
這個檔案裡了。用hexeditor開啟,只能看到一些字串和資料。
在查一波資料,發現了tensorboard這個東西,他能把meta檔案的內容轉化成圖,於是根據教程,得到了這個模型的模型圖。
點進各個節點可以看到每個節點的輸入(Inputs),輸出(Outputs)和操作(Operation)。於是我們找到了 In_string
和 Out_judge
。跟著 Out_judge
的操作沿著輸入一步一步往前走,能找到In_string。這些個過程就是黑盒的過程了。注意其中有很多的分支是沒用到的,不必理會。最終整理出正向的操作(用tensorflow的函式表示):
t0 = tf.matmul(In_string, v0) t1 = tf.add(t0, v3) t2 = tf.sigmoid(t1) t3 = tf.matmul(t2, v1) t4 = tf.add(t3, v4) t5 = tf.tanh(t4) t6 = tf.matmul(t5, v2) t7 = tf.add(t6, v5) t8 = tf.sigmoid(t7)
matmul:矩陣相乘
sigmoid:S型生長曲線,具體可以百度
add:兩個矩陣對應量相加
tanh:雙曲正切
relu:正數不變,負數變為為0
這裡的v0到v5都是常量。參考指令碼中res的用法,可以拿到資料:
def gn(nm): return sess.run(nm + ":0", feed_dict={x:[X]}) In_string = gn("In_string") v0 = gn("Variable/read") v1 = gn("Variable_1/read") v2 = gn("Variable_2/read") v3 = gn("Variable_3/read") v4 = gn("Variable_4/read") v5 = gn("Variable_5/read")
為了方便除錯,我們定義:
def gv(v): return sess.run(v, feed_dict={x:[X]}) print(gv(a0))
直接print(t1)並不能輸出它的值。參考之前的res,要把資料喂進去run一下,會返回一個numpy的array資料型別,於是這樣就可以輸出了。
同時,輸出np.array時預設會省略一些資料用省略號代替,且輸出的精度不足。為了更直觀的觀看,可以新增如下程式碼:
np.set_printoptions(threshold='nan') np.set_printoptions(precision=20)
有了正向的輸入過程,每個函式都是有反函式的,我們似乎就可以逆向得到輸入了,逆向過程如下
from scipy.special import logit def invsigmoid(a): b= np.array([[0.0]*32]) for i in range(32): b[0][i]=logit(a[0][i]) #logit為sigmoid的反函式,但是tensorflow中沒有這個函式,只能藉助scipy中的了 return b a = np.array([[1.40895243e-01, 9.98096014e-01, 1.13422030e-02, 6.57041353e-01, 9.97613889e-01, 9.98909625e-01, 9.92840464e-01, 9.90108787e-01, 1.43269835e-03, 9.89027450e-01, 7.22652880e-01, 9.63670217e-01, 6.89424259e-01, 1.76012035e-02, 9.30893743e-01, 8.61464445e-03, 4.35839722e-01, 8.38741174e-04, 6.38429400e-02, 9.90384032e-01, 1.09806946e-03, 1.76375112e-03, 9.37186997e-01, 8.32329340e-01, 9.83474966e-01, 8.79308946e-01, 6.59324698e-03, 7.85916088e-05, 2.94269115e-05, 1.97006621e-03, 9.99416387e-01, 9.99997202e-01]]) a0=invsigmoid(a) a1=tf.subtract(a0,v5) a2=tf.matmul(a1,tf.matrix_inverse(v2)) a3=tf.atanh(a2) a4=tf.subtract(a3,v4) a5=tf.matmul(a4,tf.matrix_inverse(v1)) a6=invsigmoid(gv(a5))#a5是個tensor物件,要用np.array資料型別才能使用。 a7=tf.subtract(a6,v3) a8=tf.matmul(a7,tf.matrix_inverse(v0)) l=gv(a8).tolist() ll=l[0] flag='' for i in range(len(ll)): ll[i]*=128 ll[i]=round(ll[i]) flag+=chr(ll[i]) print(flag)
然而出問題了。最終的a8全是naf。讓我們想辦法輸出中間變數的值,看看哪裡有問題。在每一步後面輸出a的資料。調著調著發現,第二個invsigmoid出了問題,意義不明(似乎是超出定義域了)。
思考了一下,我先隨便輸一個數據進去,用它算的結果逆一遍,看看能不能得到原來的值,於是:
def trans(secret): return np.array([float(ord(x))/128 for x in secret]) fakeflag = "flag{01234567012345670123456789}" X = trans(fakeflag) sess = tf.Session() saver = tf.train.import_meta_graph('model.meta') saver.restore(sess, tf.train.latest_checkpoint('./')) graph = tf.get_default_graph() x = graph.get_tensor_by_name('In_string:0') y = graph.get_tensor_by_name("Out_judge:0") res = sess.run(y, feed_dict={x:[X]}) a0=invsigmoid(res) a1=tf.subtract(a0,v5) a2=tf.matmul(a1,tf.matrix_inverse(v2)) a3=tf.atanh(a2) a4=tf.subtract(a3,v4) a5=tf.matmul(a4,tf.matrix_inverse(v1)) a6=invsigmoid(gv(a5))#a5是個tensor物件,要用np.array資料型別才能使用。 a7=tf.subtract(a6,v3) a8=tf.matmul(a7,tf.matrix_inverse(v0)) l=gv(a8).tolist() ll=l[0] flag='' for i in range(len(ll)): ll[i]*=128 ll[i]=round(ll[i]) flag+=chr(ll[i]) print(flag)
到這裡可以算回我的假flag,說明演算法是沒問題的。思前想後覺得應該是精度的問題。注意到體重final的精度是9位有效數字。我嘗試把假flag得到的最終結果保留9位有效數字,再帶進逆向演算法中,就算不出假flag了。同時我逐步提高精度,發現要有14位以上有效數字的精度才能算出正確的輸入!這讓我不禁懷疑題目的正確性。找主辦方交涉無果,只好重新找方向。
顯然我們沒辦法提高資料的精度,一時間陷入了僵局。
查詢資料解讀meta的時候,除了用tensorboard看圖形外,還發現了可以吧meta轉成txt輸出:
sess = tf.Session() saver = tf.train.import_meta_graph('model.meta') saver.restore(sess, tf.train.latest_checkpoint('./')) graph = tf.get_default_graph() tf.train.write_graph(graph, './aaa', 'train.pbtxt')
開啟train.pbtxt,在這裡可以看到所有的node,包括之前的 Variable
等。比賽時並沒有太注意,只當用到的東西已經輸出了。
最早看這個pbtxt的時候並沒有很仔細,更多的跑去看tensorboard了。賽後重新看一下pbtxt,發現了很可疑的一段:
node { name: "PRECISEFINAL" op: "Const" attr { key: "dtype" value { type: DT_DOUBLE } } attr { key: "value" value { tensor { dtype: DT_DOUBLE tensor_shape { dim { size: 1 } dim { size: 32 } } tensor_content: "!333736633210302?3068crg360357?02\336266224:207?367s226226{06345?211224C366s354357?tkYQ21367357?303CE]Y305357?20705215237370256357?30344262",yW?320732534434246357?26733533635637037347?367345j354b326356?|34lv30317346?210242s3061406222?372353304254341311355?245\21401216244201?%254@J314344333?30?251364336{K?k24R31302X260?331361R3329261357?27U36433243375Q?#31dW265345\?265b315225o375355?22207375#q242352?H330273}240x357?A362214203L#354?L00207B20501{?32334403217123224?03222)3659333376>372241322=207#`?36132424238373357?3713327!372377357?" } } } }
這個名為 PRECISEFINAL
的node根本沒見過,裡面還藏有大量資料,難道這就是精確的final值?(名字都告訴你了是精確final了好吧)
把它帶進指令碼跑一下,flag就出了,而且直接檢視它的資料可以發現它的精度是15位有效數字。
指令碼:
import os import numpy as np import tensorflow as tf from math import isclose from scipy.special import logit os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' np.set_printoptions(precision=20) np.set_printoptions(threshold='nan') def trans(secret): return np.array([float(ord(x))/128 for x in secret]) def gn(nm): a=graph.get_tensor_by_name(nm + ":0") return sess.run(a, feed_dict={x:[X]}) def gv(v): return sess.run(v, feed_dict={x:[X]}) def invsigmoid(a): b= np.array([[0.0]*32]) for i in range(32): b[0][i]=logit(a[0][i]) return b fakeflag = "flag{01234567012345670123456789}" X = trans(fakeflag) #print(X) sess = tf.Session() saver = tf.train.import_meta_graph('model.meta') saver.restore(sess, tf.train.latest_checkpoint('./')) graph = tf.get_default_graph() x = graph.get_tensor_by_name('In_string:0') y = graph.get_tensor_by_name("Out_judge:0") final = np.array([[1.40895243e-01, 9.98096014e-01, 1.13422030e-02, 6.57041353e-01, 9.97613889e-01, 9.98909625e-01, 9.92840464e-01, 9.90108787e-01, 1.43269835e-03, 9.89027450e-01, 7.22652880e-01, 9.63670217e-01, 6.89424259e-01, 1.76012035e-02, 9.30893743e-01, 8.61464445e-03, 4.35839722e-01, 8.38741174e-04, 6.38429400e-02, 9.90384032e-01, 1.09806946e-03, 1.76375112e-03, 9.37186997e-01, 8.32329340e-01, 9.83474966e-01, 8.79308946e-01, 6.59324698e-03, 7.85916088e-05, 2.94269115e-05, 1.97006621e-03, 9.99416387e-01, 9.99997202e-01]]) res = sess.run(y, feed_dict={x:[X]}) In_string = gn("In_string") v0 = gn("Variable/read") v1 = gn("Variable_1/read") v2 = gn("Variable_2/read") v3 = gn("Variable_3/read") v4 = gn("Variable_4/read") v5 = gn("Variable_5/read") precisefinal=gn("PRECISEFINAL") a0=invsigmoid(precisefinal) a1=tf.subtract(a0,v5) a2=tf.matmul(a1,tf.matrix_inverse(v2)) a3=tf.atanh(a2) a4=tf.subtract(a3,v4) a5=tf.matmul(a4,tf.matrix_inverse(v1)) a6=invsigmoid(gv(a5)) a7=tf.subtract(a6,v3) a8=tf.matmul(a7,tf.matrix_inverse(v0)) l=gv(a8).tolist() ll=l[0] flag='' for i in range(len(ll)): ll[i]*=128 ll[i]=round(ll[i]) flag+=chr(ll[i]) print(flag)
最終執行指令碼得到flag