看雪.紐盾 KCTF 2019 Q3 | 第九題點評及解題思路
Editor發表於2019-10-08
從誕生之日起,就被預言為將重新一統大周帝國的王者,從小就展現的驚人天分和氣魄也讓所有人對他充滿期待。人人都臣服他,懼怕他,養成了他高傲自大,肆意無忌的脾氣。
他對神神祕祕的黑衣御醫徐福的實驗室產生了興趣。那是羋月太后絕對禁止他涉足的地方。佈滿奇奇怪怪機關儀器的房間暗無天日,像極了無邊的黑夜。
一夥陌生人的侵入成為改變的契機。羋月太后的左膀右臂徐福,被驅逐出王宮。自那之後第十年,嬴政終於真正執掌秦國。陽光明亮得令人炫目,照耀著他。
本題共有989人圍觀,最終只有1支團隊攻破成功。A2戰隊在比賽即將結束的前兩個小時,成功破解此題。
這道題差一點成為本屆Q3的神題,那麼它究竟有多麼難攻破呢?就讓我們一起來揭開它神祕的面紗吧。
仿照一個Android勒索軟體,遮蔽了加密檔案的邏輯,保留了加密演算法。一般惡意軟體都會做一些反逆向的工作,所以加入了反jadx、jeb等反編譯工具(原本是Q2 6月準備的題目,可能工具更新後,現在效果不行了),加入了混淆、簡單的花指令、反除錯等,更進一步把加密演算法抽取出來使用自定義的直譯器執行。
解題思路:
雖然有混淆和字串加密,但是字串加密很簡單。根據介面上的Decrypt按鈕定位到Button控制元件。
Button繫結的事件,解密字串得到"The key is correct and the decryption begins!"和"Key error!",所以第二個if分支成立的話就是輸入正確。
之後呼叫的函式就是反jeb的函式,可以發現jeb無法解析,但是可以看smali程式碼。
分析發現把輸入的字串經過一個native函式返回一個位元組陣列,對位元組陣列轉為hex,和"820e52333de3bcb42467f0a20564c145af5edbf2e923df33be21f0af159710c92cbc43f79f94ec930a7ae86021af5b3ae263369299de5436b85f297be08a032a28dc357391961ecc26931bfc97d67a5e74d8781fb4105b9afbe613a2041dd8c3"比較。所以關鍵是這個native函式。
分析該函式應該在libmydvp.so中,這個so有混淆、花指令、反除錯,patch花指令、反除錯後或者動態除錯,追到這個函式的實現。
追到註冊函式的地方,或者hook系統函式得到地址。
分析流程發現:
註冊jni函式,解壓assets/ssspbahh.so檔案解析。
分析native函式,發現:
有200多個分支,從解析的ssspbahh.so資料結構中取2個位元組的資料,根據資料不同,進入不同的分支。
進而得到指令的對應關係,可以把得到的dalvik指令填會dex,或者除錯完整個函式的指令,分析出是個變形的sm4演算法。
解密後的資料使用base64解碼後得到:請本本載不不載請+422199147軟不不軟卸微請要。
根據程式碼分析 “請本本載不不載請+”= 422199147,“ 軟不不軟卸微請要 ”=89985746long為42219914789985746或者-42219914789985746。
挨個取16進製做為索引,得到字元。
(自動生成的flag,限制的是15個字元,但是開始後發現多了2個字元,17個。最簡單、最容易得到的flag為:
注:名為mysigned.apk會修改手機的桌面,如果不合要求,可採用mysigned_no_desktop.apk,不會修改手機設定。
本題使用了一些android加固技術,包括:1)多種反除錯2)ollvm混淆3)native函式多次動態註冊4) 簡單的dex vmp虛擬機器(advmp)5) java層字元混淆6) java部分函式插花指令7)so 字串加密(應該是ollvm實現的)8)java字串加密。
遇到的一些困難:
1、java混淆使用0 O o之類的字元,使得分析java原始碼非常費勁。
2、在so中加入了很多反除錯功能,尤其是vmp引擎函式的部分指令碼中也加入了,過掉這些反除錯雖然簡單,但是比較繁瑣。
3、程式中使用了大量的ollvm混淆,其中dexvmp引擎就有26K多,程式只提供arm指令so庫,由於angr反混淆對arm支援的不夠友好,沒時間利用符號執行去掉ollvm.稍微分析下程式,可得到部分垃圾指令,將其去掉後,使用F5反彙編這個函式,花費了6個小時才完成。當然這需要更改ida配置使得max_funsition size由64k 改為1024K或者更多。即使不F5,直接分析彙編程式碼,IDA動態除錯時其自動分析功能也需要大概15到20分鐘(此時單步除錯會非常卡頓,需要等IDA分析完成後才混順暢)。也就是是說分析一個虛擬機器引擎從程式開始啟動,到15分鐘後才能流暢的跟蹤。
4、將VMP大部分指令識別後,還原出加解密程式碼,發現輸入竟然是隨機的,因為其java層將輸入的flag通過一些查表變換後使用了base64進行加密,但是這個base64加密的基準字元,使用了一個random隨機生成的。當再次加密時這個基準又重新隨機生成,所以2次輸入相同的sn,base64的結果是不樣的。此時整個人都不好了。以為掉了一個坑裡面,這個vmp引擎是假的,再次分析程式,包括另外的一個libmyqtest.so,也沒發現端倪,果斷放棄。
5、作者在群裡提示說 random的隨機種子是固定的,我理解的是“對於正確的sn無論輸入多少次,結果都應該是正確的“,看來作者的意圖是第一次輸入正確就算通過。再次開始分析。這裡是造成多解的一個原因:第一次可以不正確,後面幾次是可能正確的。
6、由於base64的基準是隨機生成的,難免會有重複的,一般base64的基準字元是不重複的, 這樣加密和解密才能對應。但是隨機生成的基準字元有重複的造成加密和解密的結果是不一致的,因此對於重複的字元需要進行分別嘗試,這也是可能造成多解的一個原因。
7、在java層對輸入的flag使用了多種編碼格式的轉換,使得反推flag的時候花費了點時間。
二、流程分析
1. java字串解密
反編譯後可以看到大部分字串都是加密的,加密函式為:OooOO0OOli.d,為了快速定位到檢測SN入口,需要將所有加密的字串進行解密。使用C對dex檔案中的加密字串進行解密如下():
下面是部分結果:
2. manifest分析
從manifest檔案可知:a)沒有applicationb)主activity為:android.support.v4.app.o000000oc)註冊了一個provider:android.support.v4.app.OO0OOOO0
因為provider會在入口類走之前執行,因此第一個執行的類為android.support.v4.app.OO0OOOO0,正其父類android.support.v4.app.O0OO00O的init函式中載入‘libmydvp.so’ 。
3. android.support.v4.app.o000000o入口類
2)後面的流程就比較複雜,而且字元混淆非常嚴重,可以從輸入提示入手,即"Key error!", 可以檢測函式在類android.support.v4.app.O000000o中,如下:
可以看出OO0o0.O00000Oo(o000000o.g_string2, this.O000000o.O0000O0o.getText().toString())函式返回真即為正確。其中引數this.O000000o.O0000O0o.getText().toString()為輸入的flag。下面繼續跟進函式“ OO0o0.O00000Oo ”。
2)檢測函式 OO0o0.O00000Oo:
這部分流程比較簡單。
1)分析輸入的sn是否是flag{xxx}格式的如果是就擷取xxx。
2)呼叫OO0o0.O0000O0o(flag)函式。該函式實際上就是用輸入的sn索引下面的表,然後轉換成一個long型的字串。
OO0o0.O0000O0o = new char[]{'d', 'c', '2', 'l', '{', '}', 'f', 'g', 'e', 'm', 'a', 'b', 'h', '0', '8', 'y'};
3)再次使用long型的字串進行chartobyte後索引一個常量表:
OO0o0.O0000OOo = new char[]{'加', '載', '本', '件', '請', '卸', '要', '微', '軟', '不', '可', '以', '來', '去', '安', 'a', '人', '7', '減', '好', 'l', '卓', '測', 'p', '試', 'p', '3', '7', '乘', '嗎', 'b', '桐', 'c', 'e', '眼', 'q', '6', '4', '以', '為', '神', 'd', '無', 'f', '功', '聖', '名', '至', '己', '0', '何', '解', '憂', 'g', '唯', '有', '1', '杜', '2', '康', 'h', '}', '{'};
4) 呼叫OO0o0.O00000Oo(v3_1.toString().getBytes())
這個函式實際上是個變形的base64。但是其基準字元是隨機生成的,隨機種子固定。部分程式碼如下:
對應第一次的生成的基準如下:
其中最後一個字元為站位字元其數值為0x74,這裡注意的是再次呼叫次base64函式後,這個基準數值將發生變化,重新隨機生成。
5)呼叫OO0o0.O000000o(randomString, OO0o0.O00000Oo(v3_1.toString().getBytes()))
以一個隨機產生的字串(無用的),以及base64結果作為引數呼叫函式 OO0o0.O000000o。其返回如果為true則flag正確。
4. 函式 :public static boolean O000000o(String paramString1, String paramString2):
從上面的函式可以知道,最後結果為
paramString1 = new OO0OOOO().OO0OOOO(paramString2, paramString1.getBytes());
其是一個native函式,原型如下:
public native byte[] OO0OOOO(byte[] paramArrayOfByte1, byte[] paramArrayOfByte2);
這個native函式輸入的是一個base64加密後的結果。將其返回的結果進行bytetochar轉換後與上面的標定字串比較相等即為正確。因此後續核心分析轉為native函式 OO0OOOO。首先要定位到此函式。
三、libmydvp.so初始化函式分析
前面的幾個函式都是解密字串的,其中包括對native函式 OO0OOOO原型的解密。
2、反除錯函式init_antiDebug1_CreateThread_AndSet_g_antiDebugGlobal(1AEE8)
由於此函式使用ollvm混淆,並且加入了一些垃圾程式碼使得F5失效,簡單寫個指令碼去掉相關垃圾程式碼,F5即可成功。如下:
1. 函式開始建立了一個執行緒antidebugThread1(174CC)這個執行緒後面在說。
2. 然後剩下的將近1800多行就是個tracepid反除錯。
3. 如果tracepid的值不為0 ,則執行如下程式碼:
如果在除錯狀態下 全域性變數g_antiDebug_Global(E3550)將被設定為檔案控制程式碼值,在 antidebugThread1會訪問在這個值,實際上其應該是一個類似於jvm,env的指標,被設定成檔案控制程式碼後,會使程式訪問其時發生異常崩潰。知道這些後過掉次函式反除錯就比較容易了。(後面會有一個過掉所有反除錯的腳步)。
3、反除錯執行緒 antidebugThread1(174CC)
這個函式看上去依然很大(2000多行),起始程式碼也很簡單,貼一下核心程式碼。
1)其會檢索 g_antiDebug_Global(E3550)如果其值為0,則呼叫usleep隨眠。
2)如果不為0,而是之前反除錯的結果為檔案控制程式碼,將發生異常程式退出。
3)如果不為0,而是一個合法的值,執行如下程式碼:
nativeRegisterFlag = __PAIR__(realNativeFun, decryptString50((int)&v294));
(*(void (**)(void))(*(_DWORD *)((unsigned int)&dword_E3554 & ~dword_E3554 | dword_E3554 & ~(unsigned int)&dword_E3554)+ 860))();其中 realNativeFun(14A9C)實際上就是之前我們要尋找的native函式。但是由於 g_antiDebug_Global = 0,所以次部分程式碼沒有執行。
1)建立執行緒sub_1D404(後面分析)2) 建立一個管道,同時呼叫函式 antidebugptraceFork();3)依然tracepid反除錯,採用相同方法 bypass4)antidebugptraceFork(1D580)
核心程式碼如下:
1)fork一個子程式2)子程式呼叫 ptrace(0, 0, 0, 0, ststusBuf_2);3)子程式讀取tracepid,並將其值通過管道write給父程式:write(dword_E356C,&buf,4u);4)父程式流程比較簡單,直接函式返回了。5)父程式的執行緒 sub_1D404為讀取管道,程式碼如下:
瞭解了以上反除錯功能後:
首先可以直接將父程式的執行緒 sub_1D404殺掉,讓其直接返回。然後nop掉fork指令,並使其結果r0=0就可以。
至此,初始化函式就分析完了。
1) 獲得env:etENV(JVM);2) 獲得當前clockTime1 = clock();3)呼叫函式 callnativeRegister(*v37);4)呼叫g_apkPath = (int)GetApkPath();5)呼叫clockTime2 = clock();6)執行 clockTime2- clockTime1
2、 callnativeRegister函式(16CFC)
看出是一個native註冊函式,其又呼叫152C0進行真正的nativeregister,其部分程式碼如下:
其實這個函式是個虛假的native函式。這裡就不分析了。
3、第二部分
建立一個結構體 ,bin進行初始化:
整個這部分程式碼的功能就是分析apk中的assets\ssspbahh.so檔案。這個檔案實際上是個變形的dex檔案。其格式如下:
當然在jni_onload中會將env\jvm指標賦給 g_antiDebug_Global ,從而使得執行緒 antidebugThread1 再次註冊jni函式:14A9C。
native函式14A9C虛擬機器引擎
再次吐槽下這個函式的混淆。之前的反除錯已經可以了,沒必要再在虛擬機器裡面加反除錯了,只是徒增工作量而已。這裡增加了2中新的反跳檢測:
2)當斷在初始化位置時,再次執行指令碼,會bypss所有反除錯,同時bypass掉部分垃圾指令,使得F5可以成功。
3) 然後就虛擬機器入口設定斷點。
2、關於虛擬機器的分析
找到這個分支就好辦了,直接在每個分支上設定斷點,分析各個opcode的具體功能就可以恢復了。
真個變形的smali大概有40多個指令,具體如下:(可能有部分不準確,不過不影響分析):
分析到這裡一種是直接將還原smali,但是其實能夠還原也表示看懂了演算法了,直接還原演算法就可以了。
六、虛擬機器演算法還原
程式前部分程式碼是生成一個long型的陣列。
1)是生成一個16個byte 陣列:21 26 23 28 25 2A 27 2C 29 2E 2B 30 2D 32 2F 34 。
2)與 C6 BA B2 A3 50 33 AA 16 97 91 4D 67 DC 32 70 B2進行亦或。
3)然後再次與一個long g_constTable[0x40] 與char g_constTable1[256]進行異或及索引後生成如下常量table。
然後開始對輸入的sn進行加密,演算法如下:
其中 encode為加密,decode為解密。
執行上述程式碼得到如下資料:
當java呼叫native函式是傳入上述資料,將會提示sn正確。
1. 對於第一次輸入,base64解碼後得到一個字串“ 請本本不不載請微+422991479不卸請要 ”;
2. 再次逆推,得到一個long型字串“ 42219914789985746 ”
3. 可得到一個解:m}y8hm8yecc002。
他對神神祕祕的黑衣御醫徐福的實驗室產生了興趣。那是羋月太后絕對禁止他涉足的地方。佈滿奇奇怪怪機關儀器的房間暗無天日,像極了無邊的黑夜。
一夥陌生人的侵入成為改變的契機。羋月太后的左膀右臂徐福,被驅逐出王宮。自那之後第十年,嬴政終於真正執掌秦國。陽光明亮得令人炫目,照耀著他。
題目簡介
本題共有989人圍觀,最終只有1支團隊攻破成功。A2戰隊在比賽即將結束的前兩個小時,成功破解此題。
這道題差一點成為本屆Q3的神題,那麼它究竟有多麼難攻破呢?就讓我們一起來揭開它神祕的面紗吧。
看雪評委crownless點評
出題團隊簡介
野生Android程式設計師。
設計思路
仿照一個Android勒索軟體,遮蔽了加密檔案的邏輯,保留了加密演算法。一般惡意軟體都會做一些反逆向的工作,所以加入了反jadx、jeb等反編譯工具(原本是Q2 6月準備的題目,可能工具更新後,現在效果不行了),加入了混淆、簡單的花指令、反除錯等,更進一步把加密演算法抽取出來使用自定義的直譯器執行。
解題思路:
雖然有混淆和字串加密,但是字串加密很簡單。根據介面上的Decrypt按鈕定位到Button控制元件。
Button繫結的事件,解密字串得到"The key is correct and the decryption begins!"和"Key error!",所以第二個if分支成立的話就是輸入正確。
繼續往下追,根據解密後的字串知道輸入值就是flag,而且如果輸入格式是falg{...}格式,只取中間的字串。
之後呼叫的函式就是反jeb的函式,可以發現jeb無法解析,但是可以看smali程式碼。
分析發現把輸入的字串經過一個native函式返回一個位元組陣列,對位元組陣列轉為hex,和"820e52333de3bcb42467f0a20564c145af5edbf2e923df33be21f0af159710c92cbc43f79f94ec930a7ae86021af5b3ae263369299de5436b85f297be08a032a28dc357391961ecc26931bfc97d67a5e74d8781fb4105b9afbe613a2041dd8c3"比較。所以關鍵是這個native函式。
分析該函式應該在libmydvp.so中,這個so有混淆、花指令、反除錯,patch花指令、反除錯後或者動態除錯,追到這個函式的實現。
追到註冊函式的地方,或者hook系統函式得到地址。
分析流程發現:
註冊jni函式,解壓assets/ssspbahh.so檔案解析。
分析native函式,發現:
有200多個分支,從解析的ssspbahh.so資料結構中取2個位元組的資料,根據資料不同,進入不同的分支。
進而得到指令的對應關係,可以把得到的dalvik指令填會dex,或者除錯完整個函式的指令,分析出是個變形的sm4演算法。
實現sm4的解密函式,解密:
//解密後 byte[] srcDtat = {-82,41,99,-40,43,-53,114,101,43,-53,114,101,-82,-23,125,68,43,-23,28,126,43,-23,28,126,-82,-23,125,68,-82,41,99,-40,41,47,54,114,43,78,3,43,38,77,3,42,126,99,28,68,99,99,-94,94,78,-41,-94,94,78,-41,28,68,99,99,-104,126,-24,38,-104,99,99,-24,28,70,-52,99,28,29,-37,54,116,116,}; //加密後資料 byte[] enDtat = {-126,14,82,51,61,-29,-68,-76,36,103,-16,-94,5,100,-63,69,-81,94,-37,-14,-23,35,-33,51,-66,33,-16,-81,21,-105,16,-55,44,-68,67,-9,-97,-108,-20,-109,10,122,-24,96,33,-81,91,58,-30,99,54,-110,-103,-34,84,54,-72,95,41,123,-32,-118,3,42,40,-36,53,115,-111,-106,30,-52,38,-109,27,-4,-105,-42,122,94,116,-40,120,31,-76,16,91,-102,-5,-26,19,-94,4,29,-40,-61,};<br>
解密後的資料使用base64解碼後得到:請本本載不不載請+422199147軟不不軟卸微請要。
根據程式碼分析 “請本本載不不載請+”= 422199147,“ 軟不不軟卸微請要 ”=89985746long為42219914789985746或者-42219914789985746。
挨個取16進製做為索引,得到字元。
(自動生成的flag,限制的是15個字元,但是開始後發現多了2個字元,17個。最簡單、最容易得到的flag為:
yyfadclfcdg88228
注:名為mysigned.apk會修改手機的桌面,如果不合要求,可採用mysigned_no_desktop.apk,不會修改手機設定。
解題思路
本題使用了一些android加固技術,包括:1)多種反除錯2)ollvm混淆3)native函式多次動態註冊4) 簡單的dex vmp虛擬機器(advmp)5) java層字元混淆6) java部分函式插花指令7)so 字串加密(應該是ollvm實現的)8)java字串加密。
遇到的一些困難:
1、java混淆使用0 O o之類的字元,使得分析java原始碼非常費勁。
2、在so中加入了很多反除錯功能,尤其是vmp引擎函式的部分指令碼中也加入了,過掉這些反除錯雖然簡單,但是比較繁瑣。
3、程式中使用了大量的ollvm混淆,其中dexvmp引擎就有26K多,程式只提供arm指令so庫,由於angr反混淆對arm支援的不夠友好,沒時間利用符號執行去掉ollvm.稍微分析下程式,可得到部分垃圾指令,將其去掉後,使用F5反彙編這個函式,花費了6個小時才完成。當然這需要更改ida配置使得max_funsition size由64k 改為1024K或者更多。即使不F5,直接分析彙編程式碼,IDA動態除錯時其自動分析功能也需要大概15到20分鐘(此時單步除錯會非常卡頓,需要等IDA分析完成後才混順暢)。也就是是說分析一個虛擬機器引擎從程式開始啟動,到15分鐘後才能流暢的跟蹤。
4、將VMP大部分指令識別後,還原出加解密程式碼,發現輸入竟然是隨機的,因為其java層將輸入的flag通過一些查表變換後使用了base64進行加密,但是這個base64加密的基準字元,使用了一個random隨機生成的。當再次加密時這個基準又重新隨機生成,所以2次輸入相同的sn,base64的結果是不樣的。此時整個人都不好了。以為掉了一個坑裡面,這個vmp引擎是假的,再次分析程式,包括另外的一個libmyqtest.so,也沒發現端倪,果斷放棄。
5、作者在群裡提示說 random的隨機種子是固定的,我理解的是“對於正確的sn無論輸入多少次,結果都應該是正確的“,看來作者的意圖是第一次輸入正確就算通過。再次開始分析。這裡是造成多解的一個原因:第一次可以不正確,後面幾次是可能正確的。
6、由於base64的基準是隨機生成的,難免會有重複的,一般base64的基準字元是不重複的, 這樣加密和解密才能對應。但是隨機生成的基準字元有重複的造成加密和解密的結果是不一致的,因此對於重複的字元需要進行分別嘗試,這也是可能造成多解的一個原因。
7、在java層對輸入的flag使用了多種編碼格式的轉換,使得反推flag的時候花費了點時間。
二、流程分析
1. java字串解密
反編譯後可以看到大部分字串都是加密的,加密函式為:OooOO0OOli.d,為了快速定位到檢測SN入口,需要將所有加密的字串進行解密。使用C對dex檔案中的加密字串進行解密如下():
int readUnsignedLeb128(unsigned char** pStream) { unsigned char* ptr = *pStream; int result = *(ptr++); if (result > 0x7f) { int cur = *(ptr++); result = (result & 0x7f) | ((cur & 0x7f) << 7); if (cur > 0x7f) { cur = *(ptr++); result |= (cur & 0x7f) << 14; if (cur > 0x7f) { cur = *(ptr++); result |= (cur & 0x7f) << 21; if (cur > 0x7f) { /* * Note: We don't check to see if cur is out of * range here, meaning we tolerate garbage in the * high four-order bits. */ cur = *(ptr++); result |= cur << 28; } } } } *pStream = ptr; return result; } unsigned char* GetFileNameBuffer(unsigned char* fileName, int* size) { FILE *fd; unsigned char *pName; int sign = 0; int filesize; if (NULL == fileName) { return NULL; } fd = fopen((char*)fileName, "rb"); if (NULL == fd) { return NULL; } fseek(fd, 0, SEEK_END); filesize = ftell(fd); pName = (unsigned char*)malloc(filesize + 100); if (NULL == pName) { fclose(fd); return NULL; } memset(pName, 0, filesize + 100); fseek(fd, 0, SEEK_SET); fread(pName, 1, filesize, fd); fclose(fd); *size = filesize; return pName; } bool WriteFileNameBuffer(const char* fileName, unsigned char* buf, int size) { FILE *fd; if (NULL == fileName) { return false; } unlink((const char*)fileName); fd = fopen((char*)fileName, "wb"); if (NULL == fd) { return NULL; } fwrite(buf, 1, (size_t)size, fd); fclose(fd); return true; } char dd1(char key) { if (key <= 0x39 && key >= 0x30) return key - 0x30; else if (key <= 'F' && key >= 'A') { return key - 0x37; } else { return -1; } } bool isnNeedDecryptString(char* arg8,unsigned int len) { for (unsigned int i = 0; i < len; i++) { if (dd1(arg8[i]) < 0) { return false; } } return true; } bool dexStringDecrypt(char* fileName, char* outFile) { int size; //開啟dex檔案 unsigned char* pbuf1 = GetFileNameBuffer((unsigned char*)"fileName", &size); if (NULL == pbuf1) return false; unsigned int stringCnt = *(unsigned int*)(pbuf1 + 0x38); unsigned int stringOffset = *(unsigned int*)(pbuf1 + 0x3c); unsigned int* stringId = (unsigned int*)(pbuf1 + stringOffset); for (unsigned int i = 0; i < 2488; i++) { if (i == 144 || i == 2145 || i == 2146 || i == 2147 || i == 2148 || i == 2308 || i == 2309 || i == 1900) continue; unsigned int off = stringId[i]; unsigned char * pbuf = pbuf1 + off; int len = readUnsignedLeb128(&pbuf); if (len <3) continue; if (false == isnNeedDecryptString((char*)pbuf, len)) continue; char* outString = decryptString((char*)pbuf, len); if (NULL == outString) continue; printf("i = %d, out = %s\n",i, outString); memcpy(pbuf, outString, strlen(outString)); } WriteFileNameBuffer(outFile, (unsigned char*)pbuf1, size); return true; }
下面是部分結果:
i = 117, out = No keylines defined for i = 193, out = Keyline index i = 194, out = Keyframe i = 195, out = Key error! i = 297, out = Fragment no longer exists for key i = 302, out = FLAG_REQUEST_FILTER_KEY_EVENTS i = 476, out = CAPABILITY_CAN_FILTER_KEY_EVENTS i = 482, out = Bad fragment at key i = 681, out = The key is correct and the decryption begins! i = 758, out = Result key can't be null i = 933, out = keyframe i = 934, out = key == null i = 935, out = key == null || value == null i = 941, out = intent_extra_data_key i = 1149, out = android.arch.lifecycle.ViewModelProvider.DefaultKey: i = 1182, out = android.support.groupKey i = 1201, out = android.support.sortKey i = 1241, out = action_key i = 1397, out = resultKey
2. manifest分析
從manifest檔案可知:a)沒有applicationb)主activity為:android.support.v4.app.o000000oc)註冊了一個provider:android.support.v4.app.OO0OOOO0
因為provider會在入口類走之前執行,因此第一個執行的類為android.support.v4.app.OO0OOOO0,正其父類android.support.v4.app.O0OO00O的init函式中載入‘libmydvp.so’ 。
System.loadLibrary(OooOO0OOli.d("mydvpB393F"));
3. android.support.v4.app.o000000o入口類
1)其init函式會載入“libmyqtest.so
System.loadLibrary(OooOO0OOli.d("myqtestB2A433B"));
public void onClick(View arg3) { String v1; O0OOOOO v3; if(this.O000000o.O000oOo) { v3 = this.O000000o.O0000oo0(); v1 = OooOO0OOli.d("The decryption has already started! Please don\'t touch it!083D1B0A2B6E101F2309083C0A4F2B205E683B4C1D201A0C276F593B6E"); } else if(OO0o0.O00000Oo(o000000o.g_string2, this.O000000o.O0000O0o.getText().toString())) { this.O000000o.O000oOo = true; Toast.makeText(this.O000000o.O0000oo0(), OooOO0OOli.d("The key is correct and the decryption begins!F3B27556F2B090A3D161F3B265F216F0E0C2806013C6E"), 0).show(); this.O000000o.mybutton.setText(OooOO0OOli.d("In decryptionD361C1D260001")); return; } else { v3 = this.O000000o.O0000oo0(); v1 = OooOO0OOli.d("Key error!423D201E48"); } Toast.makeText(((Context)v3), ((CharSequence)v1), 0).show(); }
可以看出OO0o0.O00000Oo(o000000o.g_string2, this.O000000o.O0000O0o.getText().toString())函式返回真即為正確。其中引數this.O000000o.O0000O0o.getText().toString()為輸入的flag。下面繼續跟進函式“ OO0o0.O00000Oo ”。
2)檢測函式 OO0o0.O00000Oo:
public static boolean O00000Oo(String randomString, String flag) { byte[] key; int index = 0; if(TextUtils.isEmpty(((CharSequence)flag))) { return 0; } int v2 = 1; if((flag.startsWith(OooOO0OOli.d("flag{E2834"))) && (flag.endsWith(OooOO0OOli.d("32")))) { flag = flag.substring(flag.indexOf(123) + 1, flag.length() - 1); } flag.getBytes(); flag = OO0o0.O0000O0o(flag) + ""; System.out.println(flag); if(flag.startsWith(OooOO0OOli.d("62"))) { flag = flag.substring(1); } new byte[0]; try { key = flag.getBytes(OooOO0OOli.d("utf-896277")); } catch(UnsupportedEncodingException v3) { v3.printStackTrace(); } StringBuilder v3_1 = new StringBuilder(); int len = key.length; while(index < len) { int keyValue = key[index]; int v6 = keyValue - 48; if(v6 > 9) { v6 = keyValue - 65; if(v6 <= 25) { v6 += 10; } else if(keyValue == 125) { v6 = 62; } else { v6 += -61; } } char v5_1 = OO0o0.O0000OOo[v6]; v6 = v2 % 9; if(v6 == 0) { v5_1 = '+'; } v3_1.append(v5_1); if(v6 == 0) { keyValue = v2 / 9; v3_1.append(flag.substring((keyValue - 1) * 9, keyValue * 9)); } ++v2; ++index; } return OO0o0.O000000o(randomString, OO0o0.O00000Oo(v3_1.toString().getBytes())); }
這部分流程比較簡單。
1)分析輸入的sn是否是flag{xxx}格式的如果是就擷取xxx。
2)呼叫OO0o0.O0000O0o(flag)函式。該函式實際上就是用輸入的sn索引下面的表,然後轉換成一個long型的字串。
OO0o0.O0000O0o = new char[]{'d', 'c', '2', 'l', '{', '}', 'f', 'g', 'e', 'm', 'a', 'b', 'h', '0', '8', 'y'};
3)再次使用long型的字串進行chartobyte後索引一個常量表:
OO0o0.O0000OOo = new char[]{'加', '載', '本', '件', '請', '卸', '要', '微', '軟', '不', '可', '以', '來', '去', '安', 'a', '人', '7', '減', '好', 'l', '卓', '測', 'p', '試', 'p', '3', '7', '乘', '嗎', 'b', '桐', 'c', 'e', '眼', 'q', '6', '4', '以', '為', '神', 'd', '無', 'f', '功', '聖', '名', '至', '己', '0', '何', '解', '憂', 'g', '唯', '有', '1', '杜', '2', '康', 'h', '}', '{'};
4) 呼叫OO0o0.O00000Oo(v3_1.toString().getBytes())
這個函式實際上是個變形的base64。但是其基準字元是隨機生成的,隨機種子固定。部分程式碼如下:
for(inputLen = 0; inputLen < OO0o0.seed.length; ++inputLen) { try { OO0o0.seed[inputLen] = ((byte)(OO0o0.O0000OoO.nextInt() % 256)); } catch(Throwable v2_3) { v2_3.printStackTrace(); } catch(NumberFormatException v2_1) { v2_1.printStackTrace(); } }
對應第一次的生成的基準如下:
unsigned char base64char[65] = { 0xa9, 0x06, 0xab, 0x48, 0x03, 0x8b, 0x08, 0xf0, 0xe5, 0x38, 0x29, 0xe9, 0x2b, 0x7e, 0x26, 0x57, 0x36, 0x75, 0xa2, 0x4d, 0x10, 0x35, 0x98, 0x3a, 0xd4, 0x63, 0x5b, 0xa3, 0x4c, 0x0e, 0xd7, 0x12, 0xdb, 0xbb, 0x1c, 0x4e, 0x9d, 0xbe, 0x1d, 0xcb, 0xcc, 0xcb, 0xb4, 0x63, 0x65, 0xcc, 0xe8, 0x46, 0x0b, 0xf4, 0x72, 0x2f, 0x2a, 0x13, 0x7d, 0xd8, 0x5e, 0x2b, 0xae, 0xb0, 0x16, 0x44, 0x63, 0xca, 0x74 };
其中最後一個字元為站位字元其數值為0x74,這裡注意的是再次呼叫次base64函式後,這個基準數值將發生變化,重新隨機生成。
5)呼叫OO0o0.O000000o(randomString, OO0o0.O00000Oo(v3_1.toString().getBytes()))
以一個隨機產生的字串(無用的),以及base64結果作為引數呼叫函式 OO0o0.O000000o。其返回如果為true則flag正確。
4. 函式 :public static boolean O000000o(String paramString1, String paramString2):
public static boolean O000000o(String paramString1, String paramString2) { int j; try { paramString2 = paramString2.getBytes(OooOO0OOli.d("ISO-8859-1087A764158")); j = paramString2.length; i = 0; } catch (UnsupportedEncodingException paramString1) { paramString1.printStackTrace(); } paramString1 = new OO0OOOO().OO0OOOO(paramString2, paramString1.getBytes()); paramString2 = new OO00OO(); paramString2.O000000o = ""; int i = 0; for (;;) { if (i < paramString1.length) { StringBuilder localStringBuilder = new StringBuilder(); localStringBuilder.append(paramString2.O000000o); localStringBuilder.append(OO00OO.O00000Oo(paramString1[i])); paramString2.O000000o = localStringBuilder.toString(); i += 1; } else { boolean bool = "820e52333de3bcb42467f0a20564c145af5edbf2e923df33be21f0af159710c92cbc43f79f94ec930a7ae86021af5b3ae263369299de5436b85f297be08a032a28dc357391961ecc26931bfc97d67a5e74d8781fb4105b9afbe613a2041dd8c3".equals(paramString2.O000000o); return bool; } } while (i < j) { int k = paramString2[i]; i += 1; } }
從上面的函式可以知道,最後結果為
“820e52333de3bcb42467f0a20564c145af5edbf2e923df33be21f0af159710c92cbc43f79f94ec930a7ae86021af5b3ae263369299de5436b85f297be08a032a28dc357391961ecc26931bfc97d67a5e74d8781fb4105b9afbe613a2041dd8c3”時flag正確。
函式開始是進行了編碼轉換後執行如下語句
函式開始是進行了編碼轉換後執行如下語句
paramString1 = new OO0OOOO().OO0OOOO(paramString2, paramString1.getBytes());
其是一個native函式,原型如下:
public native byte[] OO0OOOO(byte[] paramArrayOfByte1, byte[] paramArrayOfByte2);
這個native函式輸入的是一個base64加密後的結果。將其返回的結果進行bytetochar轉換後與上面的標定字串比較相等即為正確。因此後續核心分析轉為native函式 OO0OOOO。首先要定位到此函式。
三、libmydvp.so初始化函式分析
init_array:000E0AD8 75 4A 01 00 DCD init_sshho+1 .init_array:000E0ADC 09 2A 02 00 DCD init_tracePidStringDecode+1 .init_array:000E0AE0 91 31 02 00 DCD init_nop+1 .init_array:000E0AE4 29 5B 07 00 DCD init_decryptString+1 .init_array:000E0AE8 E9 87 07 00 DCD init_decryptString2+1 .init_array:000E0AEC A1 5D 08 00 DCD init_decrypt3+1 .init_array:000E0AF0 F5 EF 08 00 DCD init_decrypt4+1 .init_array:000E0AF4 ED 04 09 00 DCD init_decrypt5+1 .init_array:000E0AF8 B9 05 09 00 DCD int_nop1+1 .init_array:000E0AFC E9 4A 09 00 DCD init_decrypt6+1 .init_array:000E0B00 C9 1B 01 00 DCD init_g_sspbahh1+1 .init_array:000E0B04 E9 AE 01 00 DCD init_antiDebug1_CreateThread_AndSet_g_antiDebugGlobal+1 .init_array:000E0B08 55 ED 01 00 DCD init_antiDebug2_fork+1 .init_array:000E0B0C CD 1B 01 00 DCD sub_11BCC+1 .init_array:000E0B10 A9 1C 01 00 DCD sub_11CA8+1 .init_array:000E0B14 85 1D 01 00 DCD sub_11D84+1 .init_array:000E0B18 C1 1D 01 00 DCD sub_11DC0+1 .init_array:000E0B1C D1 1D 01 00 DCD sub_11DD0+1
2、反除錯函式init_antiDebug1_CreateThread_AndSet_g_antiDebugGlobal(1AEE8)
由於此函式使用ollvm混淆,並且加入了一些垃圾程式碼使得F5失效,簡單寫個指令碼去掉相關垃圾程式碼,F5即可成功。如下:
#coding=utf-8 import struct from idaapi import * from idc import * from idautils import * def patchNopCode(ea, endAddr, data, len): while ea < endAddr: ea = find_binary(ea, SEARCH_DOWN| SEARCH_NEXT, data, radix=16) if BADADDR == ea: break patch_bytes(ea, '\xC0\x46' * len) print 'ea = ' + str(hex(ea)) ea = ea + len def KillNop(): lajiData = '2D E9 F0 47 BD E8 F0 47 13 E0 BD E8 F0 47 05 E0 00 F1 01 00 0A E0 1B 46 0E E0 10 E0 B1 B5 01 E0 12 46 01 E0 82 B0 FB E7 02 B0 F1 E7 A0 F1 01 00 F1 E7 2D E9 F0 47 E8 E7 BD E8 B1 40 ED E7 2D E9 F0 47 BD E8 F0 47' nopData = '\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46' nopCode1 = 'B1 B5 82 B0 12 46 02 B0 00 F1 01 00 A0 F1 01 00 1B 46 BD E8 B1 40 01 F1 01 01 A1 F1 01 01' lajiData2 = '2D E9 F0 40 BD E8 F0 40 13 E0 BD E8 F0 47 05 E0 00 F1 01 00 0A E0 1B 46 14 E0 16 E0 B1 B5 01 E0 12 46 01 E0 82 B0 FB E7 02 B0 F1 E7 A0 F1 01 00 F1 E7 2D E9 F0 47 EF F3 00 84 10 B4 10 BC 84 F3 00 89 E2 E7 BD E8 B1 40 E7 E7 2D E9 F0 47 BD E8 F0 47 FF E7' libBase = GetLibAddr('libmydvp.so') startAddr = libBase endAddr = libBase + 0x8916A print 'startAddr= ' + str(hex(startAddr)) print 'endAddr = ' + str(hex(endAddr)) patchNopCode(startAddr, endAddr, lajiData, 35) KillNop()
F5之後的程式碼也很多,將近2000行,只說下核心程式碼:
pthread_create((pthread_t *)&g_antiDebug_thread, 0, (void *(*)(void *))antidebugThread1, 0); pid = getpid(); sub_B7BB0(&v230); buf_1 = std::operator|(16, 8); sub_A83CC((int)&v249, buf_1); sub_A37CC((int)&v250, pid); sub_951B4(&v249, &v230);
2. 然後剩下的將近1800多行就是個tracepid反除錯。
3. 如果tracepid的值不為0 ,則執行如下程式碼:
if ( v161 != -942489079 ) break; LODWORD(v181) = fd; // 反除錯設定為1 HIDWORD(v181) = pid; *(_QWORD *)&g_antiDebug_Global = v181; v161 = -1048192996;
如果在除錯狀態下 全域性變數g_antiDebug_Global(E3550)將被設定為檔案控制程式碼值,在 antidebugThread1會訪問在這個值,實際上其應該是一個類似於jvm,env的指標,被設定成檔案控制程式碼後,會使程式訪問其時發生異常崩潰。知道這些後過掉次函式反除錯就比較容易了。(後面會有一個過掉所有反除錯的腳步)。
3、反除錯執行緒 antidebugThread1(174CC)
這個函式看上去依然很大(2000多行),起始程式碼也很簡單,貼一下核心程式碼。
usleep(0x3E8u);
2)如果不為0,而是之前反除錯的結果為檔案控制程式碼,將發生異常程式退出。
3)如果不為0,而是一個合法的值,執行如下程式碼:
nativeRegisterFlag = __PAIR__(realNativeFun, decryptString50((int)&v294));
(*(void (**)(void))(*(_DWORD *)((unsigned int)&dword_E3554 & ~dword_E3554 | dword_E3554 & ~(unsigned int)&dword_E3554)+ 860))();其中 realNativeFun(14A9C)實際上就是之前我們要尋找的native函式。但是由於 g_antiDebug_Global = 0,所以次部分程式碼沒有執行。
4)剩下其他1800多號程式碼又是個tracepid反除錯功能。與init_antiDebug1_CreateThread_AndSet_g_antiDebugGlobal類似過掉方法也一樣。
4、初始化函式init_antiDebug2_fork(1ED54)
核心程式碼如下:
核心程式碼如下:
newthread = &v410; haystack = (char *)&v409; v417 = (char *)&v409; v416 = (char *)&v406; v413 = &v405; v421 = &v404; v418 = (char *)&v404; needle = (char *)&v403; v419 = &v402; v426 = &v401; v410 = pthread_self(); pipe(&g_pipe); antidebugptraceFork(); v392 = pthread_create(newthread, 0, (void *(*)(void *))sub_1D404, 0); v393 = &v411; v394 = &v409; v395 = &savedregs; v396 = -1357197669; v22 = getpid();
核心程式碼如下:
sprintf((char *)&v125, v24, pid); sonPid = fork();
1)fork一個子程式2)子程式呼叫 ptrace(0, 0, 0, 0, ststusBuf_2);3)子程式讀取tracepid,並將其值通過管道write給父程式:write(dword_E356C,&buf,4u);4)父程式流程比較簡單,直接函式返回了。5)父程式的執行緒 sub_1D404為讀取管道,程式碼如下:
int sub_1D404() { signed int v0; // r0 int v1; // r1 __pid_t v2; // r0 int result; // r0 int v4; // [sp+8h] [bp-20h] int v5; // [sp+Ch] [bp-1Ch] v4 = -1; close(dword_E356C); read(g_pipe, &v4, 4u); sleep(1u); LABEL_3: v0 = 1819773075; while ( 1 ) { v1 = v0 & 0x7FFFFFFF; if ( (v0 & 0x7FFFFFFF) == 340946481 ) { v4 = -1; goto LABEL_3; } if ( v1 == 1314826255 ) break; if ( v1 == 1819773075 ) { read(g_pipe, &v4, 4u); v0 = 1314826255; if ( !v4 ) v0 = 340946481; } } kill(g_sonPid, 9); v2 = getpid(); kill(v2, 9); result = _stack_chk_guard - v5; if ( _stack_chk_guard == v5 ) result = 0; return result; }
瞭解了以上反除錯功能後:
首先可以直接將父程式的執行緒 sub_1D404殺掉,讓其直接返回。然後nop掉fork指令,並使其結果r0=0就可以。
至此,初始化函式就分析完了。
int __fastcall JNI_OnLoad(int a1) { char v1; // r1 char v2; // r1 unsigned int v3; // r2 signed int v4; // r1 signed int v5; // r0 unsigned int v6; // r6 int v7; // r0 unsigned int v8; // r12 signed int v9; // r3 signed int v10; // r4 bool v11; // zf signed int v12; // r3 int v13; // r0 struct globalInfo *v14; // r0 int v15; // r0 signed int v16; // r1 signed int v17; // r3 signed int v18; // r3 clock_t v19; // r0 signed int v20; // r0 signed int v21; // r2 struct _JNIEnv **v22; // r0 int *v23; // r1 struct _JNIEnv **v24; // r2 struct _JNIEnv *v25; // r0 clock_t clockTime2; // r0 int size; // r0 signed int v28; // r1 int result; // r0 int v30; // [sp-1Ch] [bp-84h] unsigned int v31; // [sp+4h] [bp-64h] int *g_dword_E35501; // [sp+8h] [bp-60h] _DWORD *v33; // [sp+Ch] [bp-5Ch] struct _JavaVM *JVM; // [sp+10h] [bp-58h] char v35; // [sp+16h] [bp-52h] char v36; // [sp+17h] [bp-51h] struct _JNIEnv **v37; // [sp+18h] [bp-50h] int *v38; // [sp+1Ch] [bp-4Ch] char v39; // [sp+23h] [bp-45h] clock_t clockTime1; // [sp+24h] [bp-44h] int v41; // [sp+28h] [bp-40h] v33 = &_stack_chk_guard; v1 = 0; if ( y_47 < 10 ) v1 = 1; v36 = v1; v2 = 0; if ( !((~-x_46 * x_46 ^ 0xFFFFFFFE) & ~-x_46 * x_46) ) v2 = 1; v35 = v2; JVM = (struct _JavaVM *)a1; v3 = (unsigned int)&g_antiDebug_Global & 0x1A04BD11; g_dword_E35501 = &g_antiDebug_Global; v31 = (~(unsigned int)&g_antiDebug_Global & 0xE5FB42EE | (unsigned int)&g_antiDebug_Global & 0x1A04BD11) ^ (a1 & 0x1A04BD11 | ~a1 & 0xE5FB42EE); v4 = 1974858797; while ( 1 ) { while ( 1 ) { while ( 1 ) { while ( 1 ) { v5 = v4; v6 = v3; if ( v4 <= 286463644 ) break; if ( v4 > 1387993772 ) { if ( v4 > 1974858796 ) { if ( v4 == 2007388415 ) { v4 = 102368831; } else if ( v4 == 1974858797 ) { v4 = 1146459914; if ( v36 ) v4 = -217046703; if ( !v35 ) v4 = 1146459914; if ( v35 != v36 ) v4 = -217046703; } } else if ( v4 == 1387993773 ) { v3 = -1; v4 = -1667146202; } else if ( v4 == 1813294739 ) { v16 = 0; if ( y_47 < 10 ) v16 = 1; v17 = 0; if ( (~(x_46 * (x_46 - 1)) | 0xFFFFFFFE) == -1 ) v17 = 1; v11 = v16 == v17; v18 = 407986737; if ( !v11 ) v18 = -1082095904; v4 = v18; if ( (~(x_46 * (x_46 - 1)) | 0xFFFFFFFE) == -1 ) v4 = -1082095904; if ( y_47 >= 10 ) v4 = v18; } } else if ( v4 <= 719945599 ) { if ( v4 == 286463645 ) { v4 = 0xEFC89CB9; if ( v39 ) v4 = 0x52BB1AAD; } else if ( v4 == 407986737 ) { v4 = -1082095904; } } else { v3 = 65540; v4 = -1667146202; if ( v5 != 719945600 ) { if ( v5 != 1146459914 ) { v28 = 795111493; goto LABEL_92; } v30 = 0; getENV(JVM); v3 = v6; v4 = -217046703; } } } if ( v4 <= -770792315 ) break; if ( v4 > -123417836 ) { if ( v4 == 0xF8A4CB15 ) { size = readSSSAndGetSize(g_apkPath, (int)algn_E353C); v4 = -1557264936; if ( !size ) v4 = 1813294739; g_sssSize = size; v3 = v6; } else if ( v4 == 102368831 ) { v19 = clock(); v4 = 0x2AE97F80; if ( (double)(signed int)(v19 - clockTime1) / 1000000.0 > 5.0 ) v4 = 0xD20EA486; v3 = v6; } } else if ( v4 == -272065351 ) { clockTime1 = clock(); callnativeRegister(*v37); v22 = v37; v23 = g_dword_E35501; *g_dword_E35501 = v31; v24 = v37; v23[1] = (unsigned int)(v23 + 1) & ~(unsigned int)*v22 | (unsigned int)*v22 & ~(unsigned int)(v23 + 1); v25 = *v24; g_apkPath = (int)GetApkPath(); clockTime2 = clock(); v4 = 0xF8A4CB15; if ( (double)(signed int)(clockTime2 - clockTime1) / 1000000.0 > 2.0 ) v4 = 0x2F647045; v3 = v6; } else if ( v4 == -217046703 ) { v37 = (struct _JNIEnv **)&v30; v38 = &v30; v30 = 0; v7 = getENV(JVM); v8 = (x_46 * ~-x_46 ^ 0xFFFFFFFE) & x_46 * ~-x_46; v9 = 0; if ( !v8 ) v9 = 1; v10 = 0; if ( y_47 < 10 ) v10 = 1; v11 = v10 == v9; v12 = 1146459914; if ( !v11 ) v12 = 286463645; v4 = v12; if ( !v8 ) v4 = 286463645; if ( v7 ) LOBYTE(v7) = 1; v39 = v7; if ( y_47 >= 10 ) v4 = v12; v3 = v6; } else { v28 = -770792314; LABEL_92: v11 = v5 == v28; v3 = v6; v4 = v5; if ( v11 ) abort(); } } if ( v4 <= -1509585911 ) break; v4 = 102368831; if ( v5 != -1509585910 ) { v4 = v5; if ( v5 == -1082095904 ) { v4 = 407986737; if ( !(x_46 * (x_46 - 1) & (x_46 * (x_46 - 1) ^ 0xFFFFFFFE)) ) v4 = -1509585910; if ( y_47 >= 10 ) v4 = 407986737; v20 = 0; if ( !(x_46 * (x_46 - 1) & (x_46 * (x_46 - 1) ^ 0xFFFFFFFE)) ) v20 = 1; v21 = 0; if ( y_47 < 10 ) v21 = 1; if ( v21 != v20 ) v4 = -1509585910; v3 = v6; } } } if ( v4 == -1667146202 ) break; if ( v4 == 2737702360 ) { v13 = operator new(0x84u); v14 = (struct globalInfo *)sub_85FB0(v13); g_globalInfo = v14; v15 = initSSSGlobal((int *)v14, *(int *)algn_E353C, g_sssSize); v4 = 2007388415; if ( v15 ) v4 = 102368831; v3 = v6; } } result = *v33 - v41; if ( *v33 == v41 ) result = v3; return result; }
1) 獲得env:etENV(JVM);2) 獲得當前clockTime1 = clock();3)呼叫函式 callnativeRegister(*v37);4)呼叫g_apkPath = (int)GetApkPath();5)呼叫clockTime2 = clock();6)執行 clockTime2- clockTime1
2、 callnativeRegister函式(16CFC)
unsigned int __fastcall callnativeRegister(struct _JNIEnv *env) { char v1; // r2 char v2; // r1 unsigned int result; // r0 signed int v4; // r1 signed int v5; // r2 unsigned int v6; // r12 signed int v7; // r3 signed int v8; // r1 bool v9; // zf signed int v10; // r3 struct _JNIEnv *env1; // [sp+Ch] [bp-24h] unsigned __int8 v12; // [sp+11h] [bp-1Fh] char v13; // [sp+12h] [bp-1Eh] unsigned __int8 v14; // [sp+13h] [bp-1Dh] env1 = env; v1 = 0; v2 = 0; if ( y_45 < 10 ) v2 = 1; v13 = v2; result = (~((x_44 - 1) * x_44) | 0xFFFFFFFE) + 1; if ( (~((x_44 - 1) * x_44) | 0xFFFFFFFE) == -1 ) v1 = 1; v12 = v1; v4 = -1230185785; do { while ( 1 ) { while ( 1 ) { while ( v4 <= 1146544159 ) { switch ( v4 ) { case -1230185785: result = v12; v5 = 626878466; if ( v12 != (unsigned __int8)v13 ) v5 = 1146544160; v4 = v5; if ( v13 ) v4 = 1146544160; if ( !v12 ) v4 = v5; break; case -299872326: result = v14; v4 = 1603580720; if ( v14 ) v4 = 1673631727; break; case 626878466: result = nativeRegister(env1); v4 = 1146544160; break; } } if ( v4 != 1146544160 ) break; result = nativeRegister(env1); v6 = (x_44 * ~-x_44 ^ 0xFFFFFFFE) & x_44 * ~-x_44; v7 = 0; if ( !v6 ) v7 = 1; v8 = 0; if ( y_45 < 10 ) v8 = 1; v9 = v8 == v7; v10 = 626878466; if ( !v9 ) v10 = -299872326; v4 = v10; if ( !v6 ) v4 = -299872326; v14 = result; if ( y_45 >= 10 ) v4 = v10; } if ( v4 != 1603580720 ) break; v4 = 1673631727; } } while ( v4 != 1673631727 ); return result; }
看出是一個native註冊函式,其又呼叫152C0進行真正的nativeregister,其部分程式碼如下:
v73 = NewGlobalRef(&env1->functions, v107); dword_E3558 = (unsigned int)&dword_E3558 & ~v73 | v73 & ~(unsigned int)&dword_E3558; RegisterNatives(&env1->functions, v107, v106, 1);
其實這個函式是個虛假的native函式。這裡就不分析了。
3、第二部分
建立一個結構體 ,bin進行初始化:
v13 = operator new(0x84u); v14 = (struct globalInfo *)sub_85FB0(v13); g_globalInfo = v14; v15 = initSSSGlobal((int *)v14, *(int *)algn_E353C, g_sssSize);
整個這部分程式碼的功能就是分析apk中的assets\ssspbahh.so檔案。這個檔案實際上是個變形的dex檔案。其格式如下:
偏移 大小 功能 +0x00 8byte magic +0x08 int 檔案頭大小 +0x18 int method個數 +0x1c int field type個數 +0x20 int field offset +0x5c int[0x0e]t 每個filed type字串長度 +0x94 char[0x0e*2] filed type字串 offset +0xb0 int[0x0e] field type value +0xC8 int method物件 +0xd0 int accessflag +0xd4 int 函式輸入引數個數 +0xd8 int 暫存器個數 +0xe0 char[2] 輸入引數型別字串 +0xe4 short[0x574] 指令碼(變形的smali
當然在jni_onload中會將env\jvm指標賦給 g_antiDebug_Global ,從而使得執行緒 antidebugThread1 再次註冊jni函式:14A9C。
native函式14A9C虛擬機器引擎
再次吐槽下這個函式的混淆。之前的反除錯已經可以了,沒必要再在虛擬機器裡面加反除錯了,只是徒增工作量而已。這裡增加了2中新的反跳檢測:
#coding=utf-8 import struct from idaapi import * from idc import * from idautils import * '''==================================================== 函式名:GetLibAddr 用 途:根據模組名獲得模組起始地址 備 注:此函式在SHT被破壞時將不起作用 =====================================================''' def GetLibAddr(targetName): targetBase = 0 for i in Modules(): #print i.name if targetName in i.name: targetBase = int("%x"%(i.base), 16) #print 'targetName:=' + targetName + 'targetBase:=' + str(targetBase) break if targetBase == 0: #print 'targetBase None!!!' return False return targetBase '''==================================================== 函式名:GetSegAddrByName 用 途:根據段名獲得段的起始地址 備 注: =====================================================''' def GetSegAddrByName(targetName): tempAddr = FirstSeg() while tempAddr!=0: if tempAddr == 0: break name = SegName(tempAddr) if targetName == name: return tempAddr tempAddr = NextSeg(tempAddr) return 0 '''==================================================== 函式名:writeMemoryForAddr 用 途:向模組地址寫入指定shellcode 備 注: =====================================================''' def writeMemoryForAddr(targetName, offset, buf): targetBase = 0 targetBase = GetSegAddrByName(targetName) if targetBase == 0: #print 'targetBase None!!!' return False addr = targetBase + offset if not dbg_write_memory(addr, buf): return False refresh_debugger_memory() return True '''==================================================== 函式名:addBptForAddr 用 途:給模組指定偏移下斷點 備 注: =====================================================''' def addBptForAddr(targetName, offset, comm): soAddr = GetSegAddrByName(targetName) if soAddr > 0 : AddBpt(soAddr + offset) SetBptAttr(soAddr + offset, BPTATTR_FLAGS, BPT_ENABLED | BPT_BRK) MakeComm(soAddr + offset, comm) else : print 'create point fail' return True '''==================================================== 函式名:DelBptForAddr 用 途:刪除模組指定偏移的斷點 備 注: =====================================================''' def DelBptForAddr(targetName, offset): soAddr = GetSegAddrByName(targetName) if soAddr > 0 : AddBpt(soAddr + offset) DelBpt(soAddr + offset) return True '''==================================================== 函式名:makenameForAddr 用 途:給指定模組函式重新命名 備 注: =====================================================''' def makenameForAddr(targetName, offset, comm): soAddr = GetSegAddrByName(targetName) if soAddr > 0 : MakeName(soAddr + offset, comm) else : print 'makenameForAddr fail' return True '''==================================================== 函式名:makeCommForAddr 用 途:給指定模組地址下注釋 備 注: =====================================================''' def makeCommForAddr(targetName, offset, comm): soAddr = GetSegAddrByName(targetName) if soAddr > 0 : MakeComm(soAddr + offset, comm) else : print 'makeCommForAddr fail' return True '''==================================================== 函式名:dumpMem 用 途:dump 指定大小的記憶體資料 備 注: =====================================================''' def dumpMem(start, l, target): block = 1024 c = l / block left = l % block fd = open(target, 'wb') if c > 0: for i in range(c): rawdex = idaapi.dbg_read_memory(start, block) fd.write(rawdex) start += block if left > 0: rawdex = idaapi.dbg_read_memory(start, left) print rawdex == True fd.write(rawdex) fd.close() '''==================================================== 函式名:doDump 用 途:通過視窗dump記憶體資料 備 注: =====================================================''' def doDump(): start = AskAddr(0, 'Input Addr start in hex: ') print('start is ' + str(hex(start))) end = AskAddr(0, 'Input Addr end in hex: ') print('end is ' + str(hex(end))) l = end - start target = AskStr('./dump.mem', 'Input the dump file path') if l > 0 and start > 0x0 and target and AskYN(1, 'start is 0x%0x, len is %d, dump to %s' % (start, l, target)) == 1: dumpMem(start, l, target) print('Dump Finish') '''==================================================== 函式名:readIntFromMemory 用 途:從記憶體中讀取一個int 備 注: =====================================================''' def readIntFromMemory(addr): flag=0 temp = addr temp1= temp if temp%2: temp1= temp-1 flag=1 else: temp1=temp m4=dbg_read_memory(temp1, 4+flag) return struct.unpack('i', m4)[0+flag] '''==================================================== 函式名:dbg_read_every_memory 用 途:從記憶體中讀取一個int 備 注:優化過的 =====================================================''' def dbg_read_every_memory(start, len): tempStart=start - (start%0x1000) maxLen = (start%0x1000) + len #print maxLen #print tempStart rawdex = idaapi.dbg_read_memory(tempStart, maxLen) #print rawdex left = start%0x1000 #print left #print rawdex[left:maxLen] return bytearray(rawdex[left:maxLen]) '''==================================================== 函式名:readShortFromMemory 用 途:從記憶體中讀取一個short 備 注: =====================================================''' def readShortFromMemory(addr): m2=dbg_read_memory(addr, 2) return struct.unpack('h', m2)[0] '''==================================================== 函式名:readCharFromMemory 用 途:從記憶體中讀取一個char 備 注: =====================================================''' def readCharFromMemory(addr): m1=dbg_read_memory(addr, 1) return struct.unpack('c', m2)[0] '''==================================================== 函式名:readUIntFromMemory 用 途:從記憶體中讀取一個uint 備 注: =====================================================''' def readUIntFromMemory(addr): m4=dbg_read_memory(addr, 4) return struct.unpack('I', m4)[0] '''==================================================== 函式名:readUShortFromMemory 用 途:從記憶體中讀取一個ushort 備 注: =====================================================''' def readUShortFromMemory(addr): m2=dbg_read_memory(addr, 2) return struct.unpack('H', m2)[0] '''==================================================== 函式名:readUCharFromMemory 用 途:從記憶體中讀取一個uchar 備 注: =====================================================''' def readUCharFromMemory(addr): m1=dbg_read_memory(addr, 1) return struct.unpack('C', m1)[0] '''==================================================== 函式名:copyMem 用 途:拷貝記憶體到另外一個區域r 備 注: =====================================================''' def copyMem(start, l, target): addrTemp = target print 'copy start' block = 1024 c = l / block left = l % block if c > 0: for i in range(c): print str(hex(start)) print str(hex(addrTemp)) rawdex = idaapi.dbg_read_memory(start, block) dbg_write_memory(addrTemp, rawdex) refresh_debugger_memory() start += block addrTemp += block if left > 0: rawdex = idaapi.dbg_read_memory(start, left) dbg_write_memory(addrTemp, rawdex) refresh_debugger_memory() print 'copy end' '''==================================================== 函式名:get_uleb128 用 途: 備 注: =====================================================''' def get_uleb128(content): value = 0 for i in xrange(0,5): tmp = ord(content[i]) & 0x7f value = tmp << (i * 7) | value if (ord(content[i]) & 0x80) != 0x80: break if i == 4 and (tmp & 0xf0) != 0: print "parse a error uleb128 number" return -1 return i+1, value '''==================================================== 函式名:get_uleb128_forIda 用 途: 備 注: =====================================================''' def get_uleb128_forIda(addr): flag=0 temp = addr if temp%2: temp1= temp-1 flag=1 else: temp1=temp staticFieldSizeAddr=dbg_read_memory(temp1, 5+flag) s,v1 = get_uleb128(staticFieldSizeAddr[flag:]) return s,v1 '''==================================================== 函式名:FindData 用 途:從指定位置查詢指定位元組的資料 備 注:返回第一個找到的位置 =====================================================''' def FindData(addr, target): a = -1 segStart = SegStart(addr) segEnd = SegEnd(addr) len=segEnd-segStart for i in range(len): rawdex1 = idaapi.dbg_read_memory(segStart, 1024) a = rawdex1.find(target) if a>= 0: a = a + segStart break; segStart = segStart+512 return a '''==================================================== 函式名:FindDataEx 用 途:從指定位置查詢指定位元組的資料 備 注:返回第一個找到的位置 =====================================================''' def FindDataEx(addr, target,offset, target2): a = -1 segStart = SegStart(addr) segEnd = SegEnd(addr) len=segEnd-segStart for i in range(len): rawdex1 = idaapi.dbg_read_memory(segStart, 1024) a = rawdex1.find(target) if a>= 0: rawdex2 = dbg_read_every_memory(segStart+a+offset, 512) b = rawdex2.find(target2) if b == 0: a = a + segStart break; segStart = segStart+512 return a #set debug breakpoint C_Linker_InitAddr0=0x18B9A C_Linker_InitAddr=0x1892E C_Dvm_JniOnloadAddr=0x22988C C_Dvm_IsDebugAddr=0x2D5768 C_Dvm_NativeRegisterAddr = 0x29AD1C C_Dvm_JarLoadedAddr=0x29EE44 '''------------------------------------------------------------- 在偵錯程式開始時,只執行一次 -------------------------------------------------------------''' if not 'setflag' in dir(): print 'set breakpoint' #addBptForAddr('linker', C_Linker_InitAddr0, 'C_Linker_InitAddr0') addBptForAddr('linker', C_Linker_InitAddr, 'C_Linker_InitAddr') #addBptForAddr('libart.so', C_Dvm_JniOnloadAddr,'jni_onload') #addBptForAddr('libart.so', C_Dvm_NativeRegisterAddr, 'native register') # set nativeregister setflag = "tmp" '''------------------------------------------------------------- 執行init 處理 -------------------------------------------------------------''' pcValue=GetRegValue('pc') libBase=GetLibAddr('linker') if pcValue-libBase == C_Linker_InitAddr: print 'C_Linker_InitAddr start' #bypass tracepid writeMemoryForAddr('libmydvp.so', 0x1C888, '\xc0\x46\xc0\x46\xc0\x46\xc0\x46\xc0\x46') # bypass traspid1 writeMemoryForAddr('libmydvp.so', 0x22456, '\x00\x20\xc0\x46') # bypass traspid2 writeMemoryForAddr('libmydvp.so', 0x18DFC, '\x00\x20\xc0\x46') # bypass traspidThread writeMemoryForAddr('libmydvp.so', 0x1D404, '\x70\x47\xc0\x46') #bypass antidebugThread2 writeMemoryForAddr('libmydvp.so', 0x1D906, '\xf8\x20\xc0\x46') # bypass fork writeMemoryForAddr('libmydvp.so', 0x49D52, '\x00\x20\xc0\x46') # bypass ooooo tracepid writeMemoryForAddr('libmydvp.so', 0x5F09C, '\x00\x20\xc0\x46') # bypass ooooo wchan writeMemoryForAddr('libmydvp.so', 0x67290, '\x00\x20\xc0\x46') # bypass ooooo 23946 writeMemoryForAddr('libmydvp.so', 0x68318, '\x00\x20\xc0\x46') # bypass ooooo wchan 2 writeMemoryForAddr('libmydvp.so', 0x60C96, '\x01\x20\xc0\x46') # bypass ooooo wchan 3 writeMemoryForAddr('libmydvp.so', 0x44878, '\x00\x20\xc0\x46') # bypass ooooo tracepid 2 writeMemoryForAddr('libmydvp.so', 0x4FD1A, '\x00\x20\xc0\x46') # bypass ooooo clock writeMemoryForAddr('libmydvp.so', 0x34BD6, '\x00\x20\xc0\x46') # bypass ooooo clock 2 addBptForAddr('libmydvp.so', 0x242E0, 'ooooooooo') addBptForAddr('libmydvp.so', 0x14A9C, 'native') KillNop()
2)當斷在初始化位置時,再次執行指令碼,會bypss所有反除錯,同時bypass掉部分垃圾指令,使得F5可以成功。
3) 然後就虛擬機器入口設定斷點。
2、關於虛擬機器的分析
.data.rel.ro:000E0B20 ; sub_242E0+4552A↑w .data.rel.ro:000E0B21 37 DCB 0x37 ; 7 .data.rel.ro:000E0B22 03 DCB 3 .data.rel.ro:000E0B23 00 DCB 0 .data.rel.ro:000E0B24 1E 3F 03 00 DCD loc_33F1E .data.rel.ro:000E0B28 F0 41 03 00 DCD loc_341F0 .data.rel.ro:000E0B2C FE 75 03 00 DCD loc_375FE .data.rel.ro:000E0B30 4A 3D 03 00 DCD loc_33D4A .data.rel.ro:000E0B34 F2 A8 03 00 DCD loc_3A8F2 .data.rel.ro:000E0B38 E2 A9 03 00 DCD loc_3A9E2 .data.rel.ro:000E0B3C BE D5 02 00 DCD loc_2D5BE .data.rel.ro:000E0B40 54 AA 03 00 DCD loc_3AA54 .data.rel.ro:000E0B44 2C 62 02 00 DCD loc_2622C .data.rel.ro:000E0B48 2C 62 02 00 DCD loc_2622C .data.rel.ro:000E0B4C 2C 62 02 00 DCD loc_2622C .data.rel.ro:000E0B50 2C 62 02 00 DCD loc_2622C .data.rel.ro:000E0B54 2C 62 02 00 DCD loc_2622C .data.rel.ro:000E0B58 2C 62 02 00 DCD loc_2622C .data.rel.ro:000E0B5C 78 46 03 00 DCD loc_34678 .data.rel.ro:000E0B60 48 AE 03 00 DCD loc_3AE48 .data.rel.ro:000E0B64 76 AE 03 00 DCD loc_3AE76 .data.rel.ro:000E0B68 48 96 03 00 DCD loc_39648 .data.rel.ro:000E0B6C AE AE 03 00 DCD loc_3AEAE
找到這個分支就好辦了,直接在每個分支上設定斷點,分析各個opcode的具體功能就可以恢復了。
addBptForAddr('libmydvp.so', 0x3377C, '0x00 iput-wide vx,vy, field_id') addBptForAddr('libmydvp.so', 0x33F1E, 'ins 0x01') addBptForAddr('libmydvp.so', 0x341F0, 'ins 0x02') addBptForAddr('libmydvp.so', 0x375FE, 'ins 0x03 if-nez vx,target') addBptForAddr('libmydvp.so', 0x33D4A, '0x04 aget-byte vx,vy,vz') addBptForAddr('libmydvp.so', 0x3A8F2, 'ins 0x05') addBptForAddr('libmydvp.so', 0x3A9E2, 'ins 0x06') addBptForAddr('libmydvp.so', 0x2D5BE, '0x07 iget-wide vx,vy,field_id') addBptForAddr('libmydvp.so', 0x3AA54, '0x08 iget vx, vy, field_id') addBptForAddr('libmydvp.so', 0x2622C, 'ins 0x09') addBptForAddr('libmydvp.so', 0x2622C, 'ins 0x0a') addBptForAddr('libmydvp.so', 0x2622C, 'ins 0x0b') addBptForAddr('libmydvp.so', 0x2622C, 'ins 0x0c') addBptForAddr('libmydvp.so', 0x2622C, 'ins 0x0d') addBptForAddr('libmydvp.so', 0x2622C, 'ins 0x0e') addBptForAddr('libmydvp.so', 0x34678, '0x0f ?????') addBptForAddr('libmydvp.so', 0x3AE48, 'ins 0x10') addBptForAddr('libmydvp.so', 0x3AE76, 'ins 0x11') addBptForAddr('libmydvp.so', 0x39648, 'ins 0x12') addBptForAddr('libmydvp.so', 0x3AEAE, '0x13 if-ne vx,vy,target') addBptForAddr('libmydvp.so', 0x3AEDC, 'ins 0x14') addBptForAddr('libmydvp.so', 0x26302, 'ins 0x15') addBptForAddr('libmydvp.so', 0x3AF34, 'ins 0x16') addBptForAddr('libmydvp.so', 0x3AF7E, '0x17 if-eq vx,vy,target ') addBptForAddr('libmydvp.so', 0x3AFBE, 'ins 0x18') addBptForAddr('libmydvp.so', 0x3AFFE, 'ins 0x19') addBptForAddr('libmydvp.so', 0x2E49A, 'ins 0x1a') addBptForAddr('libmydvp.so', 0x3B048, 'ins 0x1b') addBptForAddr('libmydvp.so', 0x3B0FC, 'ins 0x1c') addBptForAddr('libmydvp.so', 0x39576, 'ins 0x1d') addBptForAddr('libmydvp.so', 0x3147E, 'ins 0x1e') addBptForAddr('libmydvp.so', 0x32FE6, 'ins 0x1f') addBptForAddr('libmydvp.so', 0x3B19C, 'ins 0x20') addBptForAddr('libmydvp.so', 0x2D26E, 'ins 0x21') addBptForAddr('libmydvp.so', 0x3B1F4, 'ins 0x22') addBptForAddr('libmydvp.so', 0x3B27A, '0x23 goto/16 target') addBptForAddr('libmydvp.so', 0x37F20, '0x24 goto target ') addBptForAddr('libmydvp.so', 0x3E14E, 'ins 0x25') addBptForAddr('libmydvp.so', 0x3B2E0, '0x26 get-quick vx,vy,offset') addBptForAddr('libmydvp.so', 0x37870, 'ins 0x27') addBptForAddr('libmydvp.so', 0x3E946, 'ins 0x28') addBptForAddr('libmydvp.so', 0x33FA2, '0x29 new-array vx,vy,type_id') addBptForAddr('libmydvp.so', 0x3B3FC, 'ins 0x2a') addBptForAddr('libmydvp.so', 0x3B434, '0x2B array-length vx,vy') addBptForAddr('libmydvp.so', 0x2524A, 'ins 0x2c') addBptForAddr('libmydvp.so', 0x2F532, 'ins 0x2d') addBptForAddr('libmydvp.so', 0x3E494, 'ins 0x2e') addBptForAddr('libmydvp.so', 0x3B48E, 'ins 0x2f') addBptForAddr('libmydvp.so', 0x3B4EE, 'ins 0x30') addBptForAddr('libmydvp.so', 0x3948C, 'ins 0x31') addBptForAddr('libmydvp.so', 0x3B546, 'ins 0x32') addBptForAddr('libmydvp.so', 0x2B056, 'ins 0x33') addBptForAddr('libmydvp.so', 0x3E32C, '0x34 const-wide vx, lit64') addBptForAddr('libmydvp.so', 0x3B5B4, 'ins 0x35') addBptForAddr('libmydvp.so', 0x3B620, '0x36 const/16 vx,lit16') addBptForAddr('libmydvp.so', 0x27908, 'ins 0x37') addBptForAddr('libmydvp.so', 0x30C46, 'ins 0x38') addBptForAddr('libmydvp.so', 0x3B6A4, '0x39 const/16 vx,lit16') addBptForAddr('libmydvp.so', 0x3B720, '0x3A const/4 vx,lit4') addBptForAddr('libmydvp.so', 0x3B7E6, 'ins 0x3b') addBptForAddr('libmydvp.so', 0x3B836, 'ins 0x3c') addBptForAddr('libmydvp.so', 0x3B896, 'ins 0x3d') addBptForAddr('libmydvp.so', 0x3B906, 'ins 0x3e') addBptForAddr('libmydvp.so', 0x3E7CA, 'ins 0x3f') addBptForAddr('libmydvp.so', 0x275F0, 'ins 0x40') addBptForAddr('libmydvp.so', 0x3B96C, 'ins 0x41') addBptForAddr('libmydvp.so', 0x3B9EC, 'ins 0x42') addBptForAddr('libmydvp.so', 0x27E20, 'ins 0x43') addBptForAddr('libmydvp.so', 0x37D52, '0x44 move/from16 vAA, vBBBB') addBptForAddr('libmydvp.so', 0x39F3A, 'ins 0x45') addBptForAddr('libmydvp.so', 0x3E4F4, 'ins 0x46') addBptForAddr('libmydvp.so', 0x3BA96, 'ins 0x47') addBptForAddr('libmydvp.so', 0x3E1DC, '0x48 move vx,vy') addBptForAddr('libmydvp.so', 0x3BB1A, 'ins 0x49') addBptForAddr('libmydvp.so', 0x3BB9A, '0x44 move/from16 vAA, vBBBB') addBptForAddr('libmydvp.so', 0x3BC26, '0x4B move vx,vy int to int') addBptForAddr('libmydvp.so', 0x3BC8C, 'ins 0x4c') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x4d') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x4e') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x4f') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x50') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x51') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x52') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x53') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x54') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x55') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x56') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x57') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x58') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x59') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x5a') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x5b') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x5c') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x5d') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x5e') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x5f') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x60') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x61') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x62') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x63') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x64') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x65') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x66') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x67') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x68') addBptForAddr('libmydvp.so', 0x2515C, 'ins 0x69') addBptForAddr('libmydvp.so', 0x31746, 'ins 0x6a') addBptForAddr('libmydvp.so', 0x3C180, 'ins 0x6b') addBptForAddr('libmydvp.so', 0x3C1DA, 'ins 0x6c') addBptForAddr('libmydvp.so', 0x3C246, 'ins 0x6d') addBptForAddr('libmydvp.so', 0x3C2AE, 'ins 0x6e') addBptForAddr('libmydvp.so', 0x3C354, 'ins 0x6f') addBptForAddr('libmydvp.so', 0x3E9B6, '0x70 div-long vx, vy, vz ') addBptForAddr('libmydvp.so', 0x2D138, 'ins 0x71') addBptForAddr('libmydvp.so', 0x2D42C, '0x72 mul-int vx, vy, vz') addBptForAddr('libmydvp.so', 0x3C3B8, 'ins 0x73') addBptForAddr('libmydvp.so', 0x3EA38, '0x74 add-int/lit8 vx,vy,lit8 ') addBptForAddr('libmydvp.so', 0x3E3EC, 'ins 0x75') addBptForAddr('libmydvp.so', 0x3C42C, 'ins 0x76') addBptForAddr('libmydvp.so', 0x397AC, '0x77 and-int/lit16 vx,vy,lit16') addBptForAddr('libmydvp.so', 0x3054E, 'ins 0x78') addBptForAddr('libmydvp.so', 0x3C4F6, 'ins 0x79') addBptForAddr('libmydvp.so', 0x3A4EE, 'ins 0x7a') addBptForAddr('libmydvp.so', 0x2A9AE, 'ins 0x7b') addBptForAddr('libmydvp.so', 0x3C550, 'ins 0x7c') addBptForAddr('libmydvp.so', 0x3C550, 'ins 0x7d') addBptForAddr('libmydvp.so', 0x3C66C, 'ins 0x7e') addBptForAddr('libmydvp.so', 0x3C71C, 'ins 0x7f') addBptForAddr('libmydvp.so', 0x3C7C2, 'ins 0x80') addBptForAddr('libmydvp.so', 0x3C880, 'ins 0x81') addBptForAddr('libmydvp.so', 0x313A2, 'ins 0x82') addBptForAddr('libmydvp.so', 0x3C8DA, 'ins 0x83') addBptForAddr('libmydvp.so', 0x34EEE, 'ins 0x84') addBptForAddr('libmydvp.so', 0x3C97C, 'ins 0x85') addBptForAddr('libmydvp.so', 0x3E72C, 'ins 0x86') addBptForAddr('libmydvp.so', 0x3CA24, 'ins 0x87') addBptForAddr('libmydvp.so', 0x3CAE0, '0x88 shr-int vx, vy, vz') addBptForAddr('libmydvp.so', 0x29B88, '0x89 shl-long/2addr vx, vy ') addBptForAddr('libmydvp.so', 0x3E838, 'ins 0x8a') addBptForAddr('libmydvp.so', 0x35932, 'ins 0x8b') addBptForAddr('libmydvp.so', 0x3CBA8, 'ins 0x8c') addBptForAddr('libmydvp.so', 0x3E57A, 'ins 0x8d') addBptForAddr('libmydvp.so', 0x373C6, 'ins 0x8e') addBptForAddr('libmydvp.so', 0x3CC84, 'ins 0x8f') addBptForAddr('libmydvp.so', 0x3E64E, 'ins 0x90') addBptForAddr('libmydvp.so', 0x3E388, 'ins 0x91') addBptForAddr('libmydvp.so', 0x2D048, 'ins 0x92') addBptForAddr('libmydvp.so', 0x3CD3A, 'ins 0x93') addBptForAddr('libmydvp.so', 0x32D22, '0x94 shl-int/2addr vx, vy') addBptForAddr('libmydvp.so', 0x3CDE4, 'ins 0x95') addBptForAddr('libmydvp.so', 0x30D26, 'ins 0x96') addBptForAddr('libmydvp.so', 0x3CE8A, 'ins 0x97') addBptForAddr('libmydvp.so', 0x3CF32, '0x98 div-int vx,vy,vz') addBptForAddr('libmydvp.so', 0x307C4, 'ins 0x99') addBptForAddr('libmydvp.so', 0x3EADE, 'ins 0x9a') addBptForAddr('libmydvp.so', 0x2EF5A, 'ins 0x9b') addBptForAddr('libmydvp.so', 0x3CFA8, '0x9c add-int/2addr vx,vy') addBptForAddr('libmydvp.so', 0x3CFA8, '0x9c add-int/2addr vx,vy') addBptForAddr('libmydvp.so', 0x3D002, 'ins 0x9e') addBptForAddr('libmydvp.so', 0x3D0CC, 'ins 0x9f') addBptForAddr('libmydvp.so', 0x327A4, 'ins 0xa0') addBptForAddr('libmydvp.so', 0x260EE, 'ins 0xa1') addBptForAddr('libmydvp.so', 0x3D18C, 'ins 0xa2') addBptForAddr('libmydvp.so', 0x3D246, 'ins 0xa3') addBptForAddr('libmydvp.so', 0x3D304, 'ins 0xa4') addBptForAddr('libmydvp.so', 0x3D3C6, 'ins 0xa5') addBptForAddr('libmydvp.so', 0x37BCE, 'ins 0xa6') addBptForAddr('libmydvp.so', 0x387F8, 'ins 0xa7') addBptForAddr('libmydvp.so', 0x3D494, '0xA8 shr-long vx,vy,vz') addBptForAddr('libmydvp.so', 0x26E2E, '0xA9 shl-long vx, vy, vz ') addBptForAddr('libmydvp.so', 0x39B4E, '0xAA xor-long vx, vy, vz ') addBptForAddr('libmydvp.so', 0x28F88, '0xAB or-long/2addr vx, vy') addBptForAddr('libmydvp.so', 0x3D574, '0xAC and-long vx, vy, vz') addBptForAddr('libmydvp.so', 0x344F6, 'ins 0xad') addBptForAddr('libmydvp.so', 0x3D67C, 'ins 0xae') addBptForAddr('libmydvp.so', 0x3D70C, 'ins 0xaf') addBptForAddr('libmydvp.so', 0x3E27C, 'ins 0xb0') addBptForAddr('libmydvp.so', 0x3D764, 'ins 0xb1') addBptForAddr('libmydvp.so', 0x3E5F4, 'ins 0xb2') addBptForAddr('libmydvp.so', 0x3E2D4, 'ins 0xb3') addBptForAddr('libmydvp.so', 0x3308E, 'ins 0xb4') addBptForAddr('libmydvp.so', 0x3D838, 'ins 0xb5') addBptForAddr('libmydvp.so', 0x3581A, 'ins 0xb6') addBptForAddr('libmydvp.so', 0x38AD6, 'ins 0xb7') addBptForAddr('libmydvp.so', 0x342FA, 'ins 0xb8') addBptForAddr('libmydvp.so', 0x361C2, 'ins 0xb9') addBptForAddr('libmydvp.so', 0x3D8E8, 'ins 0xba') addBptForAddr('libmydvp.so', 0x2AF9C, 'ins 0xbb') addBptForAddr('libmydvp.so', 0x3D97E, 'ins 0xbc') addBptForAddr('libmydvp.so', 0x3982A, 'ins 0xbd') addBptForAddr('libmydvp.so', 0x312CC, 'ins 0xbe') addBptForAddr('libmydvp.so', 0x3D9EC, '0xBF move vx,vy byte to int') addBptForAddr('libmydvp.so', 0x3DA44, 'ins 0xc0') addBptForAddr('libmydvp.so', 0x33E50, 'ins 0xc1') addBptForAddr('libmydvp.so', 0x2D97C, 'ins 0xc2') addBptForAddr('libmydvp.so', 0x3DAD0, 'ins 0xc3') addBptForAddr('libmydvp.so', 0x29652, 'ins 0xc4') addBptForAddr('libmydvp.so', 0x368E4, 'ins 0xc5') addBptForAddr('libmydvp.so', 0x3DB68, 'ins 0xc6') addBptForAddr('libmydvp.so', 0x3DBFE, 'ins 0xc7') addBptForAddr('libmydvp.so', 0x3DC5A, '0xC8 move-wide long to int') addBptForAddr('libmydvp.so', 0x3DCF4, 'ins 0xc9') addBptForAddr('libmydvp.so', 0x36CBE, 'ins 0xca') addBptForAddr('libmydvp.so', 0x3DD58, '0xCB move-wide') addBptForAddr('libmydvp.so', 0x3DDB0, 'ins 0xcc') addBptForAddr('libmydvp.so', 0x3DE52, 'ins 0xcd') addBptForAddr('libmydvp.so', 0x3DEE6, 'ins 0xce') addBptForAddr('libmydvp.so', 0x2F446, 'ins 0xcf') addBptForAddr('libmydvp.so', 0x399CA, 'ins 0xd0') addBptForAddr('libmydvp.so', 0x3DF40, 'ins 0xd1') addBptForAddr('libmydvp.so', 0x3DF40, 'ins 0xd2') addBptForAddr('libmydvp.so', 0x3DF40, 'ins 0xd3') addBptForAddr('libmydvp.so', 0x3E06E, 'ins 0xd4') addBptForAddr('libmydvp.so', 0x39E2E, 'ins 0xd5') addBptForAddr('libmydvp.so', 0x3E0E0, 'ins 0xd6') addBptForAddr('libmydvp.so', 0x324A6, 'ins 0xd7') addBptForAddr('libmydvp.so', 0x26546, 'ins 0xd8') addBptForAddr('libmydvp.so', 0x26546, 'ins 0xd9') addBptForAddr('libmydvp.so', 0x3E008, 'ins 0xda') addBptForAddr('libmydvp.so', 0x3DF98, 'ins 0xdb') addBptForAddr('libmydvp.so', 0x3394A, 'ins 0xdc') addBptForAddr('libmydvp.so', 0x3C5F6, 'ins 0xdd') addBptForAddr('libmydvp.so', 0x2622C, 'ins 0xde') addBptForAddr('libmydvp.so', 0x391E6, 'ins 0xdf') addBptForAddr('libmydvp.so', 0x39BC8, 'ins 0xe0') addBptForAddr('libmydvp.so', 0x3C128, 'ins 0xe1') addBptForAddr('libmydvp.so', 0x3C0E6, 'ins 0xe2') addBptForAddr('libmydvp.so', 0x3C076, 'ins 0xe3') addBptForAddr('libmydvp.so', 0x2A662, 'ins 0xe4') addBptForAddr('libmydvp.so', 0x3BFCE, 'ins 0xe5') addBptForAddr('libmydvp.so', 0x2E7B0, 'ins 0xe6') addBptForAddr('libmydvp.so', 0x2EBCA, 'ins 0xe7') addBptForAddr('libmydvp.so', 0x3BF68, 'ins 0xe8') addBptForAddr('libmydvp.so', 0x3BF0E, 'ins 0xe9') addBptForAddr('libmydvp.so', 0x37206, 'ins 0xea') addBptForAddr('libmydvp.so', 0x27770, 'ins 0xeb') addBptForAddr('libmydvp.so', 0x3BEB4, 'ins 0xec') addBptForAddr('libmydvp.so', 0x2CB42, 'ins 0xed') addBptForAddr('libmydvp.so', 0x3BE50, 'ins 0xee') addBptForAddr('libmydvp.so', 0x3A190, 'ins 0xef') addBptForAddr('libmydvp.so', 0x25B6E, 'ins 0xf0') addBptForAddr('libmydvp.so', 0x36B28, 'ins 0xf1') addBptForAddr('libmydvp.so', 0x2A78E, 'ins 0xf2') addBptForAddr('libmydvp.so', 0x377D6, 'ins 0xf3') addBptForAddr('libmydvp.so', 0x3BDBC, 'ins 0xf4') addBptForAddr('libmydvp.so', 0x27502, 'ins 0xf5') addBptForAddr('libmydvp.so', 0x2EE7E, 'ins 0xf6') addBptForAddr('libmydvp.so', 0x3A2FC, 'ins 0xf7') addBptForAddr('libmydvp.so', 0x38610, 'ins 0xf8') addBptForAddr('libmydvp.so', 0x3BD3C, 'ins 0xf9') addBptForAddr('libmydvp.so', 0x3BCC6, 'ins 0xfa') addBptForAddr('libmydvp.so', 0x3AD66, 'ins 0xfb') addBptForAddr('libmydvp.so', 0x37DF6, 'ins 0xfc') addBptForAddr('libmydvp.so', 0x3ACEC, '0xFD aput-char vx,vy,vz') addBptForAddr('libmydvp.so', 0x3AC14, 'ins 0xfe') addBptForAddr('libmydvp.so', 0x3AB38, 'ins 0xff')
真個變形的smali大概有40多個指令,具體如下:(可能有部分不準確,不過不影響分析):
指令 含義 0x00 iput-wide vx,vy, field_id 0x03 if-nez vx,target 0x04 aget-byte vx,vy,vz 0x07 iget-wide vx,vy,field_id 0x08 iget vx, vy, field_id 0x0f ????? 0x13 if-ne vx,vy,target 0x17 if-eq vx,vy,target 0x23 goto/16 target 0x24 goto target 0x26 get-quick vx,vy,offset 0x29 new-array vx,vy,type_id 0x2B array-length vx,vy 0x34 const-wide vx, lit64 0x36 const-wide/16 vx, lit16 0x39 const/16 vx,lit16 0x3A const/4 vx,lit4 0x48 move vx,vy 0x44 move/from16 vAA, vBBBB 0x4A move/from16 vAA, vBBBB 0x4B move vx,vy 0x70 div-long vx, vy, vz 0x72 mul-int vx, vy, vz 0x74 add-int/lit8 vx,vy,lit8 0x77 and-int/lit16 vx,vy,lit16 0x88 shr-int vx, vy, vz 0x89 shl-long/2addr vx, vy 0x94 shl-int/2addr vx, vy 0x98 div-int vx,vy,vz 0x9c add-int/2addr vx,vy 0x9d add-int/2addr vx,vy 0xA8 shr-long vx,vy,vz 0xA9 shl-long vx, vy, vz 0xAA xor-long vx, vy, vz 0xAB or-long vx, vy, vz 0xAC and-long vx, vy, vz 0xBF move vx,vy 0xC8 move-wide 0xCB move-wide 0xFD aput-char vx,vy,vz
六、虛擬機器演算法還原
程式前部分程式碼是生成一個long型的陣列。
1)是生成一個16個byte 陣列:21 26 23 28 25 2A 27 2C 29 2E 2B 30 2D 32 2F 34 。
for(int i = 0; i < 8; i++) { p3[i] = 0x21 + i*2; p3[i+1] = 0x25+i*2; }
2)與 C6 BA B2 A3 50 33 AA 16 97 91 4D 67 DC 32 70 B2進行亦或。
3)然後再次與一個long g_constTable[0x40] 與char g_constTable1[256]進行異或及索引後生成如下常量table。
8F1A4404 FFF2E7BB 1E937DC8 00708BD4 7F7F73C3 0074F871 B328B155 FFE90BBF 1009F1C3 FFDCFEB9 FFE4254A 00330BFC F2BA3ED7 0021DED8 FE2D5F02 FF9A6DF5 C917FCF0 FFFF7EED 1A19EDF0 00329985 33E7164F 0026EF98 8B35A805 FFB28997 BB562A98 FF849AFA C92B0A64 00693E49 BCC4EAC9 0004C9FE C451397E FF812002 223F99D9 FFA6F1DE 713992DA 0007FC82 0B4E883C 005F48FA 6106622D FFC51D0A E0B88ACA FFE720EF 03996681 0072D9F4 0A7CBE18 003B4522 81836FEA FF8C0AF6 062A5D20 FFE3A7D6 14BAC6DF 004C7B9E FF98252C 005BF4AD CE471681 FF9405EF BD0958EC FFE4EFA2 03A69C77 00317701 1C0A19C3 004F960B D6334DF1 FFFDF083
然後開始對輸入的sn進行加密,演算法如下:
long long constkey[0x20] = { 0xFFF2E7BB8F1A4404, 0x00708BD41E937DC8, 0x0074F8717F7F73C3, 0xFFE90BBFB328B155, 0xFFDCFEB91009F1C3, 0x00330BFCFFE4254A, 0x0021DED8F2BA3ED7, 0xFF9A6DF5FE2D5F02, 0xFFFF7EEDC917FCF0, 0x003299851A19EDF0, 0x0026EF9833E7164F, 0xFFB289978B35A805, 0xFF849AFABB562A98, 0x00693E49C92B0A64, 0x0004C9FEBCC4EAC9, 0xFF812002C451397E, 0xFFA6F1DE223F99D9, 0x0007FC82713992DA, 0x005F48FA0B4E883C, 0xFFC51D0A6106622D, 0xFFE720EFE0B88ACA, 0x0072D9F403996681, 0x003B45220A7CBE18, 0xFF8C0AF681836FEA, 0xFFE3A7D6062A5D20, 0x004C7B9E14BAC6DF, 0x005BF4ADFF98252C, 0xFF9405EFCE471681, 0xFFE4EFA2BD0958EC, 0x0031770103A69C77, 0x004F960B1C0A19C3, 0xFFFDF083D6334DF1, }; unsigned char constkey0[256] = { 0x37, 0x23, 0xEC, 0x34, 0xA2, 0x4B, 0x65, 0xA1, 0x35, 0x9C, 0x2B, 0x46, 0x8A, 0xD2, 0x54, 0xF9, 0x43, 0xCE, 0x4A, 0x47, 0xCD, 0x4F, 0x5B, 0x78, 0xC9, 0x30, 0x69, 0xFC, 0x56, 0x95, 0xDA, 0x50, 0x74, 0x1C, 0xC4, 0xE7, 0xA3, 0x1E, 0xAE, 0xF4, 0x09, 0xDF, 0x55, 0x83, 0x6B, 0xAC, 0x5F, 0xD8, 0xFF, 0x7D, 0x6A, 0x57, 0xC3, 0x0E, 0xE9, 0xFE, 0x28, 0x03, 0x71, 0x42, 0x61, 0x68, 0xD1, 0x52, 0x01, 0xED, 0xE1, 0xA8, 0x04, 0xB3, 0x82, 0x49, 0x29, 0xB2, 0xB5, 0xC1, 0x88, 0x84, 0x86, 0x1B, 0x87, 0xB6, 0xBE, 0x58, 0x0F, 0xA0, 0x20, 0xB8, 0x3E, 0x2C, 0x63, 0xD0, 0x99, 0x9D, 0xF0, 0xAB, 0x31, 0xBD, 0x91, 0x18, 0xE0, 0x5E, 0xFD, 0x26, 0x19, 0x15, 0x67, 0xB4, 0x48, 0x92, 0x79, 0xB7, 0xF1, 0x44, 0x93, 0xC0, 0xD9, 0x77, 0x41, 0xD7, 0xF8, 0xC7, 0x60, 0xFB, 0xEB, 0x7B, 0xAF, 0xB9, 0xA5, 0x80, 0xCC, 0x24, 0x7E, 0x9B, 0xDD, 0x1D, 0x0D, 0x97, 0x4C, 0xBF, 0xEE, 0x13, 0x22, 0x45, 0x8F, 0xF3, 0x9F, 0xA7, 0x11, 0x64, 0xC6, 0x3A, 0x59, 0x89, 0xAD, 0x6F, 0xF5, 0x8B, 0xF2, 0x75, 0xFA, 0x06, 0x5C, 0x70, 0xEF, 0x2A, 0xEA, 0xCF, 0x2F, 0x08, 0x6D, 0x00, 0x73, 0xE3, 0xBC, 0x2E, 0x07, 0xE4, 0xA9, 0x33, 0xC2, 0x9E, 0x36, 0xBA, 0x85, 0x6C, 0x2D, 0x3C, 0x5A, 0x05, 0x8D, 0x25, 0x7A, 0x0C, 0x16, 0x17, 0x7C, 0x02, 0xD3, 0x5D, 0x96, 0xE2, 0x3B, 0x90, 0x53, 0x21, 0xDE, 0x76, 0x4E, 0xF6, 0xDC, 0xCB, 0x51, 0x3D, 0x0B, 0x94, 0x8E, 0xE6, 0x72, 0xC5, 0x8C, 0xA6, 0x6E, 0x7F, 0xD5, 0x9A, 0x1A, 0xA4, 0x39, 0x27, 0x1F, 0xB1, 0xE5, 0x98, 0xC8, 0x4D, 0x10, 0xDB, 0x40, 0x66, 0x3F, 0xCA, 0xF7, 0x12, 0xAA, 0xD6, 0xE8, 0x62, 0xBB, 0x38, 0x32, 0x14, 0x0A, 0xD4, 0xB0, 0x81, }; unsigned char* encode(unsigned char* key, int keylen, int* outLen) { //補齊 unsigned char left = 0x10 - keylen % 0x10; int key1Len = keylen + left; unsigned char* key1 = (unsigned char*)malloc(key1Len); unsigned char* outKey = (unsigned char*)malloc(key1Len); memset(outKey, 0x00, key1Len); memset(key1, 0x00, key1Len); long long inputKey[0x24]; memset((char*)&inputKey, 0x00, sizeof(inputKey)); for (int i = 0; i < key1Len; i++) { if (i < keylen) key1[i] = key[i]; else key1[i] = left; } int maxCnt = key1Len / 0x10; for (int m = 0; m < maxCnt; m++) { memset((char*)&inputKey, 0x00, sizeof(inputKey)); for (int j = 0; j < 4; j++) { inputKey[j] = (long long)key1[m * 0x10 + j * 4 + 0] << 24; inputKey[j] |= (long long)key1[m * 0x10 + j * 4 + 1] << 16; inputKey[j] |= (long long)key1[m * 0x10 + j * 4 + 2] << 8; inputKey[j] |= (long long)key1[m * 0x10 + j * 4 + 3] << 0; } for (int i = 0; i < 0x20; i++) { if (i == 0x1f) { int xxx = 0; } long long r15 = inputKey[i + 1] ^ inputKey[i + 2] ^ inputKey[i + 3]; long long r19 = constkey[i] ^ r15; unsigned char tempkey4[4]; tempkey4[0] = (r19 >> 24) & 0xff; tempkey4[1] = (r19 >> 16) & 0xff; tempkey4[2] = (r19 >> 8) & 0xff; tempkey4[3] = (r19)& 0xff; unsigned char tempkey5[4]; tempkey5[0] = constkey0[tempkey4[0]]; tempkey5[1] = constkey0[tempkey4[1]]; tempkey5[2] = constkey0[tempkey4[2]]; tempkey5[3] = constkey0[tempkey4[3]]; //將 tempkey5 按位元組置換 long long r4 = ((long long)tempkey5[0] << 24); r4 |= ((long long)tempkey5[1] << 16); r4 |= ((long long)tempkey5[2] << 8); r4 |= (long long)tempkey5[3]; long long r13 = (r4 << 2) | (r4 >> 30); long long r16 = r13 ^ r4; r13 = (r4 << 0x0a) | (r4 >> 0x16); long long r17 = r13 ^ r16; r13 = (r4 << 0x12) | (r4 >> 0x0e); long long r18 = r13 ^ r17; r13 = (r4 << 0x18) | (r4 >> 0x8); r18 = r13 ^ r18; inputKey[i + 4] = inputKey[i] ^ r18; } for (int j = 0; j < 4; j++) { outKey[m * 0x10 + j * 4 + 0] = (inputKey[0x23 - j] >> 24) & 0x0ff; outKey[m * 0x10 + j * 4 + 1] = (inputKey[0x23 - j] >> 16) & 0x0ff; outKey[m * 0x10 + j * 4 + 2] = (inputKey[0x23 - j] >> 8) & 0x0ff; outKey[m * 0x10 + j * 4 + 3] = (inputKey[0x23 - j]) & 0x0ff; } int xx = 1; } *outLen = key1Len; return outKey; } unsigned char* decode(unsigned char* key, int keylen, int* outLen) { //補齊 unsigned char left = 0; if (keylen % 0x10) left = 0x10 - keylen % 0x10; int key1Len = keylen + left; unsigned char* key1 = (unsigned char*)malloc(key1Len+100); unsigned char* outKey = (unsigned char*)malloc(key1Len + 100); memset(outKey, 0x00, key1Len + 100); memset(key1, 0x00, key1Len + 100); long long inputKey[0x24]; memset((char*)&inputKey, 0x00, sizeof(inputKey)); for (int i = 0; i < key1Len; i++) { if (i < keylen) key1[i] = key[i]; else key1[i] = left; } int maxCnt = key1Len / 0x10; for (int m = 0; m < maxCnt; m++) { memset((char*)&inputKey, 0x00, sizeof(inputKey)); for (int j = 0; j < 4; j++) { inputKey[0x23 - j] = (long long)key1[m * 0x10 + j * 4 + 0] << 24; inputKey[0x23 - j] |= (long long)key1[m * 0x10 + j * 4 + 1] << 16; inputKey[0x23 - j] |= (long long)key1[m * 0x10 + j * 4 + 2] << 8; inputKey[0x23 - j] |= (long long)key1[m * 0x10 + j * 4 + 3] << 0; } for (int i = 0x1F; i >=0; i--) { long long r15 = inputKey[i+1] ^ inputKey[i+2] ^ inputKey[i+ 3]; long long r19 = constkey[i] ^ r15; unsigned char tempkey4[4]; tempkey4[0] = (r19 >> 24) & 0xff; tempkey4[1] = (r19 >> 16) & 0xff; tempkey4[2] = (r19 >> 8) & 0xff; tempkey4[3] = (r19)& 0xff; unsigned char tempkey5[4]; tempkey5[0] = constkey0[tempkey4[0]]; tempkey5[1] = constkey0[tempkey4[1]]; tempkey5[2] = constkey0[tempkey4[2]]; tempkey5[3] = constkey0[tempkey4[3]]; //將 tempkey5 按位元組置換 long long r4 = ((long long)tempkey5[0] << 24); r4 |= ((long long)tempkey5[1] << 16); r4 |= ((long long)tempkey5[2] << 8); r4 |= (long long)tempkey5[3]; long long r13 = (r4 << 2) | (r4 >> 30); long long r16 = r13 ^ r4; r13 = (r4 << 0x0a) | (r4 >> 0x16); long long r17 = r13 ^ r16; r13 = (r4 << 0x12) | (r4 >> 0x0e); long long r18 = r13 ^ r17; r13 = (r4 << 0x18) | (r4 >> 0x8); r18 = r13 ^ r18; inputKey[i] = inputKey[i+4] ^ r18; } for (int j = 0; j < 4; j++) { outKey[m * 0x10 + j * 4 + 0] = (inputKey[j] >> 24) & 0x0ff; outKey[m * 0x10 + j * 4 + 1] = (inputKey[j] >> 16) & 0x0ff; outKey[m * 0x10 + j * 4 + 2] = (inputKey[j] >> 8) & 0x0ff; outKey[m * 0x10 + j * 4 + 3] = (inputKey[j]) & 0x0ff; } int xx = 1; } *outLen = key1Len; outKey[key1Len] = 0; return outKey; }
其中 encode為加密,decode為解密。
unsigned char realKey[96] = { 0x82, 0x0e, 0x52, 0x33, 0x3d, 0xe3, 0xbc, 0xb4, 0x24, 0x67, 0xf0, 0xa2, 0x05, 0x64, 0xc1, 0x45, 0xaf, 0x5e, 0xdb, 0xf2, 0xe9, 0x23, 0xdf, 0x33, 0xbe, 0x21, 0xf0, 0xaf, 0x15, 0x97, 0x10, 0xc9, 0x2c, 0xbc, 0x43, 0xf7, 0x9f, 0x94, 0xec, 0x93, 0x0a, 0x7a, 0xe8, 0x60, 0x21, 0xaf, 0x5b, 0x3a, 0xe2, 0x63, 0x36, 0x92, 0x99, 0xde, 0x54, 0x36, 0xb8, 0x5f, 0x29, 0x7b, 0xe0, 0x8a, 0x03, 0x2a, 0x28, 0xdc, 0x35, 0x73, 0x91, 0x96, 0x1e, 0xcc, 0x26, 0x93, 0x1b, 0xfc, 0x97, 0xd6, 0x7a, 0x5e, 0x74, 0xd8, 0x78, 0x1f, 0xb4, 0x10, 0x5b, 0x9a, 0xfb, 0xe6, 0x13, 0xa2, 0x04, 0x1d, 0xd8, 0xc3 }; int outLen = 0; unsigned char* inSn = decode(realKey, 96, &outLen);
執行上述程式碼得到如下資料:
ae 29 63 d8 2b cb 72 65 2b cb 72 65 ae e9 7d 44 2b e9 1c 7e 2b e9 1c 7e ae e9 7d 44 ae 29 63 d8 29 2f 36 72 2b 4e 03 2b 26 4d 03 2a 7e 63 1c 44 63 63 a2 5e 4e d7 a2 5e 4e d7 1c 44 63 63 98 7e e8 26 98 63 63 e8 1c 46 cc 63 1c 1d db 36 74 74
當java呼叫native函式是傳入上述資料,將會提示sn正確。
base64解密
1. 對於第一次輸入,base64解碼後得到一個字串“ 請本本不不載請微+422991479不卸請要 ”;
2. 再次逆推,得到一個long型字串“ 42219914789985746 ”
3. 可得到一個解:m}y8hm8yecc002。
相關文章
- 看雪.紐盾 KCTF 2019 Q2 | 第九題點評及解題思路2019-07-04
- 看雪.紐盾 KCTF 2019 Q3 | 第四題點評及解題思路2019-09-29
- 看雪.紐盾 KCTF 2019 Q3 | 第七題點評及解題思路2019-09-30
- 看雪.紐盾 KCTF 2019 Q3 | 第一題點評及解題思路2019-09-25
- 看雪.紐盾 KCTF 2019 Q3 | 第六題點評及解題思路2019-10-08
- 看雪.紐盾 KCTF 2019 Q3 | 第八題點評及解題思路2019-10-08
- 看雪.紐盾 KCTF 2019 Q3 | 第十題點評及解題思路2019-10-08
- 看雪.紐盾 KCTF 2019 Q3 | 第十一題點評及解題思路2019-10-08
- 看雪.紐盾 KCTF 2019 Q3 | 第十二題點評及解題思路2019-10-08
- 看雪.紐盾 KCTF 2019 Q3 | 第十三題點評及解題思路2019-10-08
- 看雪.紐盾 KCTF 2019 Q2 | 第七題點評及解題思路2019-07-02
- 看雪.紐盾 KCTF 2019 Q2 | 第六題點評及解題思路2019-07-01
- 看雪.紐盾 KCTF 2019 Q2 | 第十題點評及解題思路2019-07-05
- 看雪.紐盾 KCTF 2019 Q2 | 第八題點評及解題思路2019-07-03
- 看雪.紐盾 KCTF 2019 Q2 | 第一題點評及解題思路2019-07-01
- 看雪.紐盾 KCTF 2019 Q2 | 第二題點評及解題思路2019-07-01
- 看雪.紐盾 KCTF 2019 Q2 | 第三題點評及解題思路2019-07-01
- 看雪.紐盾 KCTF 2019 Q2 | 第五題點評及解題思路2019-07-01
- 2019KCTF 晉級賽Q1 | 第九題點評及解題思路2019-04-04
- 看雪·眾安 2021 KCTF 秋季賽 | 第九題設計思路及解析2021-12-09
- 看雪·深信服 2021 KCTF 春季賽 | 第九題設計思路及解析2021-05-28
- 2020 KCTF秋季賽 | 第一題點評及解題思路2020-11-20
- 2020 KCTF秋季賽 | 第四題點評及解題思路2020-11-24
- 2019 KCTF 晉級賽Q1 | 第三題點評及解題思路2019-03-28
- 2019KCTF 晉級賽Q1 | 第十題點評及解題思路2019-04-08
- 看雪·眾安 2021 KCTF 秋季賽 | 第十題設計思路及解析2021-12-16
- 看雪·眾安 2021 KCTF 秋季賽 | 第七題設計思路及解析2021-12-03
- 看雪·眾安 2021 KCTF 秋季賽 | 第六題設計思路及解析2021-12-01
- 看雪·眾安 2021 KCTF 秋季賽 | 第五題設計思路及解析2021-11-29
- 看雪·眾安 2021 KCTF 秋季賽 | 第四題設計思路及解析2021-11-25
- 看雪·眾安 2021 KCTF 秋季賽 | 第三題設計思路及解析2021-11-22
- 看雪·深信服 2021 KCTF 春季賽 | 第十題設計思路及解析2021-05-31
- 看雪·深信服 2021 KCTF 春季賽 | 第七題設計思路及解析2021-05-25
- 看雪·深信服 2021 KCTF 春季賽 | 第八題設計思路及解析2021-05-25
- 看雪·深信服 2021 KCTF 春季賽 | 第六題設計思路及解析2021-05-21
- 看雪·深信服 2021 KCTF 春季賽 | 第三題設計思路及解析2021-05-14
- 看雪·深信服 2021 KCTF 春季賽 | 第四題設計思路及解析2021-05-17
- 看雪·深信服 2021 KCTF 春季賽 | 第五題設計思路及解析2021-05-17