攻防世界新手練習題_MOBILE(移動)
一、easy-apk
看到某個學習安卓逆向的路線,看到攻防世界。。。然后就去看了下題目
好久沒碰安卓了,工作偶爾需要查看源碼,一直沒有深入學習
1.安裝apk
拿到apk的思路,安裝查看一遍,安裝后發現需要驗證
在安卓模擬器安裝
2.反編譯源碼
使用jadx-gui直接打開apk
一般在MainActivity
就可以看到程序一開始運行的操作
發現需要輸入和該字符串類似的才能驗證通過
找到核心加密的編碼方法
public void onClick(View view) { if (new Base64New().Base64Encode(((EditText) MainActivity.this.findViewById(C0295R.C0297id.editText)).getText().toString().getBytes()).equals("5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs=")) { Toast.makeText(MainActivity.this, "驗證通過!", 1).show(); } else { Toast.makeText(MainActivity.this, "驗證失敗!", 1).show(); }
發現后面有“=”,與base64的編碼類似,但是到在線的base64解密是顯示解密失敗或者亂碼
代碼段意思是需要驗證輸入的文本經過Base64New().Base64Encode后的字符與"5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs="相等,則驗證通過
所以我們找到Base64New().Base64Encode
方法
這是對字符串編碼的過程
我自己沒看懂。。。百度一波后才發現,真的是base64編碼,但是解密的字符換了
然后百度了一波原理,最后還是百度找的代碼
使用了java進行解密
參考了知乎大神:https://zhuanlan.zhihu.com/p/116110552的解題思路,這里自己只是記錄一下
3.解碼
最后腳本:
package mobile; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; public class Base64New { private static final char[] Base64ByteToStr = {'v', 'w', 'x', 'r', 's', 't', 'u', 'o', 'p', 'q', '3', '4', '5', '6', '7', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'y', 'z', '0', '1', '2', 'P', 'Q', 'R', 'S', 'T', 'K', 'L', 'M', 'N', 'O', 'Z', 'a', 'b', 'c', 'd', 'U', 'V', 'W', 'X', 'Y', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', '8', '9', '+', '/'}; private static final int RANGE = 255; private static byte[] StrToBase64Byte = new byte[128]; //base64 原編碼 public String Base64Encode(byte[] bytes) { StringBuilder res = new StringBuilder(); for (int i = 0; i <= bytes.length - 1; i += 3) { byte[] enBytes = new byte[4]; byte tmp = 0; for (int k = 0; k <= 2; k++) { if (i + k <= bytes.length - 1) { enBytes[k] = (byte) (((bytes[i + k] & 255) >>> ((k * 2) + 2)) | tmp); tmp = (byte) ((((bytes[i + k] & 255) << (((2 - k) * 2) + 2)) & 255) >>> 2); } else { enBytes[k] = tmp; tmp = 64; } } enBytes[3] = tmp; for (int k2 = 0; k2 <= 3; k2++) { if (enBytes[k2] <= 63) { res.append(Base64ByteToStr[enBytes[k2]]); } else { res.append('='); } } } return res.toString(); } //base64 解碼 網上找的 public static byte getIndex(char x) { byte index = -1; String talbe = new String(Base64New.Base64ByteToStr); if(x != '=') { index = (byte) talbe.indexOf(x); } else { index = 0; } return index; } public static String Base64Decode(String str) { String flag = ""; String flag_temp = ""; for(int i = 0;i < str.length();i+=4) { String enf = str.substring(i,i+4); byte flag1 = (byte)(((getIndex(enf.charAt(0)) & 255) << 2 | ((getIndex(enf.charAt(1)) & 255) >>> 4))); byte flag2 = (byte)(((getIndex(enf.charAt(1)) & 255) << 4 | ((getIndex(enf.charAt(2)) & 255) >>> 2))); byte flag3 = (byte)(((getIndex(enf.charAt(2)) & 255) << 6 | ((getIndex(enf.charAt(3)) & 255)))); flag_temp = "" + (char)flag1 + (char)flag2 + (char)flag3; flag += flag_temp; } System.out.println(flag); return flag; } public static void main(String[] args) { String str="5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs="; String flag=Base64New.Base64Decode(str); } }
輸出結果:
還找到一個python解法的,直接原鏈接:https://blog.csdn.net/sun8890446/article/details/102688191
base64原理解析:https://blog.csdn.net/wo541075754/article/details/81734770
4.輸入flag
最后輸入flag,
還有標准格式flag{}
估計后面的都是要這樣的。。
所以最終答案為flag{05397c42f9b6da593a3644162d36eb01}
感想:
平時工作就是直接使用工具反編譯,然后看懂app的大概思路,找后台傳輸數據或者app的核心運行原理思路,突然一接觸這題目,學習路程漫漫
app1
最重要的一步:下載app,安裝然后認真的去運行:
- 隨意輸入會收到提示“再接再厲,加油~”,不輸入的話提示“年輕人不要耍小聰明噢”。除了被調戲沒有可獲取信息的了,所以只能靠逆向扒了她:
- 使用(1)jeb直接打開apk 或者 (2)dex2jar反編譯apk,使用jd-gui打開jar文件,搜索上述提到的關鍵字:可以看到主要邏輯了,我們要闖關成功,所以要做到下面這個條件才可以:
但首先我們要完成這個條件:
(有過代碼基礎的同學可以輕易看出來,沒有基礎的同學可以把代碼復制到百度上,有相關的解釋)
其實就是一個異或運算,而異或比較的兩個值就是 str1 和 I , 將關鍵詞在代碼中查詢,可找到:
然后繼續====》
所以到這就很清晰了, 將 15和 X<cP[?PHNB<P?aj 進行異或。
異或過程把字符串轉為ascii碼, 15轉成16進制,將每個字符轉成的ascii碼與 “15的16進制”進行異或:
C代碼:
Python代碼:
a 和 b 的16進制自己去填
a= b=[] flag='' for j in b: flag +=(a^j) print(flag)
最后結果為:W3l_T0_GAM3_0ne
app2
使用APKIDE打開apk文件,我們發現這里多了一個libc的文件夾,libc好像是什么函數動態鏈接庫
查看一下,里面好像有個什么解密的????
先不管,繼續往下走~~
主函數里面沒看出什么鬼東西,不過發現了一個類中有一個特殊的字符串,需要解密啥的:
看見import com.tencent.testvuln.c.Encryto;,直接進入其中:
發現好熟悉,就顯示我們剛剛在libc中看見的那個東西???一個解密的函數???
直接是看libc中的這個文件,直接解壓apk2,找到libc中的這個文件
.os文件直接使用ida打開,發現有一個encode函數,直接查看偽代碼:
根據名字感覺是AES加密,不過AES需要密鑰,我們可以看見上面有一串字符串:thisisatestkey==
猜測這就是密鑰,試試:
好像就是,,,,提交正確,,,,
說實話這里面的函數完全沒看懂,,,全靠猜,,
一臉懵逼
easyjni
下載文件,發現是個APK文件,直接APKIDE分析一波,發現有lib,看一看名字:
直接java反編譯,找到主函數:
調用了lib里面的庫。同時還調用了a類:
母庸置疑,和我們之前做的一個base64變碼表的一樣,應該也是一個變換碼表的base64:
進入到libc中的native中看看,解壓后用ida打開:
emmmm,好像邏輯還是挺簡單的,前面16個和后面的16個換一下位置,然后兩兩交換一下位置:
得到一個字符串:QAoOQMPFks1BsB7cbM3TQsXg30i9g3==
使用上次自己寫的base64變換碼表腳本,變換一下碼表,跑一下:
get flag:flag{just_ANot#er_@p3}
easy-so
拖進模擬器是一個驗證框,我們直接上jeb:
調用本地方法public static native int CheckString(String arg0),若驗證一致返回1,否則返回0.
將apk重命名為zip后解壓,在lib目錄將.so文件拖進IDA,找到函數CheckString,代碼如下:
_BOOL4 __cdecl Java_com_testjava_jack_pingan2_cyberpeace_CheckString(int a1, int a2, int a3) { const char *v3; // ST1C_4 size_t v4; // edi char *v5; // esi size_t v6; // edi char v7; // al char v8; // al size_t v9; // edi char v10; // al v3 = (const char *)(*(int (__cdecl **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0); v4 = strlen(v3); v5 = (char *)malloc(v4 + 1); memset(&v5[v4], 0, v4 != -1); memcpy(v5, v3, v4); if ( strlen(v5) >= 2 ) { v6 = 0; do { v7 = v5[v6]; v5[v6] = v5[v6 + 16]; v5[v6++ + 16] = v7; } while ( v6 < strlen(v5) >> 1 ); } v8 = *v5; if ( *v5 ) { *v5 = v5[1]; v5[1] = v8; if ( strlen(v5) >= 3 ) { v9 = 2; do { v10 = v5[v9]; v5[v9] = v5[v9 + 1]; v5[v9 + 1] = v10; v9 += 2; } while ( v9 < strlen(v5) ); } } return strcmp(v5, "f72c5a36569418a20907b55be5bf95ad") == 0; }
我們大膽推測const char * v3是傳入的字符串,接下來逐個分析代碼邏輯:
v4 = strlen(v3); //取變量v4=v3的字符串長度,假設v3="abcd",v4=4 v5 = (char *)malloc(v4 + 1); //為字符指針v5請求一塊長度為v4+1的內存空間 memset(&v5[v4], 0, v4 != -1); //將v5擴增一倍並后面擴增的部分初始化為0,此行代碼結束,v5=----0000 memcpy(v5, v3, v4); //將v3的內容復制到v5中 if ( strlen(v5) >= 2 ) //若v5的長度大於等於2則執行花括號內的內容 { v6 = 0; //初始化v6=0 do //執行循環 { v7 = v5[v6]; //從第0個開始讀取v5的每個字符 v5[v6] = v5[v6 + 16]; //逐個將v5的第v6個字符與第v6+16個字符交換位置 v5[v6++ + 16] = v7; //v6自增1 } while ( v6 < strlen(v5) >> 1 ); }
假設傳入字符串為abcd,則上述代碼執行完之后的v5為cdab
繼續分析接下來的代碼:
v8 = *v5; //指針v8指向v5 if ( *v5 ) //v5存在,執行花括號內的邏輯 { *v5 = v5[1]; v5[1] = v8; if ( strlen(v5) >= 3 ) //v5的長度大於等於3 { v9 = 2; //初始化v9=2 do { v10 = v5[v9]; v5[v9] = v5[v9 + 1]; v5[v9 + 1] = v10; v9 += 2; } while ( v9 < strlen(v5) ); } }
這段代碼很簡單,就是兩兩交換。
根據上述我們直接手動得到flag的code:
1.將f72c5a36569418a20907b55be5bf95ad兩兩交換得到7fc2a5636549812a90705bb55efb59da
2.將7fc2a5636549812a90705bb55efb59da從中間砍斷,頭拼接到尾,得到90705bb55efb59da7fc2a5636549812a
3.加上flag{}就是flag。
easyjava
解題過程
1、將該apk安裝進夜神模擬器中,發現有一個輸入框和一個按鈕,隨便輸入信息,點擊按鈕,發現彈出信息You are wrong!Bye~
。
2、將該APK拖進AndroidKiller中反編譯,反編譯完成后,搜索字符串You are wrong
,發現該字符串位於MainActivity.java中,如下圖所示:
3、用jeb反編譯該apk,反編譯完成后直接將smali層代碼轉為java代碼,發現在onCreate
函數出現該字符串,發現將輸入框中的字符串作為參數傳入MainActivity的a(string)
函數中,返回的布爾值確認flag是否正確。
4、而該a函數調用了MainActivity的b函數,b函數返回一個布爾值,b函數作用為首先獲取輸入框的字符串截取前5個符flag{
與最后一個字符}
之間的字符串。
然后實例化了一個b類,並且傳入了一個參數2,而b類的構造函數首先為一個整形數組復制,然后左移該整形數組,左移的次數就是傳進來的整數,這里是2。
在然后實例化了一個a類,傳入了一個參數3,而a類的構造函數與b類的構造函數基本相同,就不再說了。
之后遍歷截取下來的字符串的每個字符,然后將調用MainActivity中的a(string,b,a)
函數返回回來的字符串添加到變量v3的尾部,遍歷完后,將得到的字符串與字符串wigwrkaugala
比較,若結果為真,返回True
,否則返回False
。
5、接着來看MainActivity的a(string,b,a)
函數,該函數首先將傳進來的第一個字符串作為調用b類的a函數的參數傳入,然后將返回回來的結果作為調用a類的a函數的參數傳入,最后返回一個字符。
再來看b類的a函數,該函數首先獲取傳進來的字符在字符串b.b中的索引,然后得到在b類中定義的整形數組中與該該索引相等的在數組中的索引,然后調用b類的a()
函數,該函數作用為將b類中數組與字符串左移一位,然后返回該數組索引。
接着再來看a類中的a(int)
函數,該函數首先獲取與傳進來的參數相等的數組中的值的索引,然后獲取在字符串中索引為該數組索引的字符,最后返回該字符,當然,其中也調用a()
函數,但是該函數要求等於25,所以該函數木有任何作用。
6、上面幾步以及詳細講述了該apk的flag加密流程,要獲取flag,逆向解密即可,解密腳本跑出結果如下:
三、附件
解密腳本:(要求python版本3.6或者與上)
cipherText = 'wigwrkaugala' aArray = [21,4,24,25,20,5,15,9,17,6,13,3,18,12,10,19,0,22,2,11,23,1,8,7,14,16] aString = 'abcdefghijklmnopqrstuvwxyz' bArray = [17,23,7,22,1,16,6,9,21,0,15,5,10,18,2,24,4,11,3,14,19,12,20,13,8,25] bString = 'abcdefghijklmnopqrstuvwxyz' def changeBArrayandString(): global bString global bArray chArray = bArray[0] chString = bString[0:1] for i in range(len(bArray) - 1): bArray[i] = bArray[i + 1] bArray[len(bArray) - 1] = chArray bString = bString[1:] bString += chString def getBchar(ch): v2 = bArray[ch] arg = bString[v2] changeBArrayandString() return arg def getAint(ch): global aString global aArray v1 = aString.index(ch) arg5 = aArray[v1] return arg5 print('flag{',end='') for k in cipherText: v0 = getAint(k) print(getBchar(v0),end='') print('}')