序言
這道題網上很多分析,但是分析的都是arm版本的,我選了arm64的來分析,arm64相比arm難度高一些,因為arm64編譯器搞了inline優化,看起來略抽象
分析
這道題邏輯很簡單,輸入flag然后一個check函數驗證,check函數是c層的,但是arm32和arm64差別很大,給大家瞄一眼,先32后64
可以看到,32位的邏輯非常清晰,拿到char*之后,弄成std::string,之后sub_9670函數對比是否是flag{開頭,往后CheckM函數把flag{xxx}中的xxx的左16和右16組合在一起,形成一個32字節的字符串,之后check1進行一些簡單的邏輯運算,再往后的encry是一個tea,之后是一個魔改的base64,就完事,邏輯很簡單,但是到了arm64,就很難看了?
說說主要的區別吧,首先是內聯了很多函數,可以看到sub_9670已經沒了,encry函數也沒了,代碼的復雜度提高了很多,還有就是,函數的返回值不按套路出牌,不放在x0寄存器中,不知道放在哪里,得去動態看才知道,比如check1之后的返回值,arm32中一看就知道是指針接受了返回值,但在arm64中莫名其妙放在了一個其他地方。
string內存結構問題問題
接下來說說,是怎么分析解決arm64的,首先,經過看這個so的代碼以及一些搜索,發現string的內存結構為,string的大小為3個dq(8個字節),24字節,如果字符串小于0x17,那么首字節存放字符串長度乘以2,第二個字節開始放字符串內容;如果是長字符串,那么第一個dq放的是容量,并且這個容量會是奇數,也就是&&1會=1;編譯器也是通過這個標志位判斷是長字符串還是短字符串,第二個dq放的是字符串的真實長度,第三個dq放的是一個char*,指向真實的字符串;另外一個關于string的問題是,string的很多內部細節也被inline出來了,比如動態擴容,這部分的特征是,會出現一個grow_by_函數,這個函數執行擴容的細節,細節不說了,緊跟這個grow_by_函數后,會看到設置字符串長度的代碼,根據這兩個特征就能知道實在動態擴容了;
參數亂放問題
函數返回值不是放在x0,會放在莫名奇妙的地方,棧上的某個地方,這一塊我采用的解決辦法是用frida的stalker trace來分析,非常爽,文末會附上trace腳本
題目其他的一些細節
tea的秘鑰是so中的init_array的一個函數設置的,base64的表是在jni_onload中設置的
其余并沒有什么特別之處了,這里著重想推薦兩個好用的技巧
兩個技巧
stalker trace
根據yang神首創,經過奮飛小改,之后我在改一點,已經能用了,腳本主要功能是從給定的地址開始trace,trace從這個地址開始后的size長度的代碼,會把執行完每一條匯編之后的影響打印出來,寄存器變化,內存的變化,以及內存中的字符串或者u64打印出來,文末也會附上trace check函數后的效果,大體是這樣
?
z3
z3是一個強大的約束求解器,一些瑣碎的邏輯用z3比較好求出來,不然的話得人工取反推實現,這里用來解決check1中的邏輯,我們只需要按照正向邏輯按照z3的api寫出約束條件,在調用求解器求解,即可得出輸入