攻防世界-Mobile-easyjni
本文首發於博主公衆號LISTONE,歡迎關注哦!
這個題分析起來邏輯比上個easyjava要簡單許多,做題速度快了很多。
首先還是用jeb打開,打開後首先看這個配置文件(對於這道比較簡單的題來說,不看也可,不過爲了培養一下好的習慣,看一下比較好)。
打開配置文件後,就找下面這個標籤中的 name
屬性。
然後我們點擊那個 dex
,可以看到反編譯之後的目錄結構了,根據上面那個 name
屬性,我們就可以找到主文件。
接下來還是分析 onCreate
方法。
看到它調用了 MainActivity.a
方法,我們繼續跟進去看看這個 a
方法幹了什麼。
可以看到它有調用了另外一個 a
方法,去分析它。
在第二個 a
方法中可以看到有一個關鍵語句
v0_1 = this.ncheck(new a().a(arg3.getBytes()));
先實例化了一個 a
類的對象,然後調用了 ncheck
,這個ncheck
是我們的 so
文件裏的一個函數。
我們先來看這個 a
類的java代碼,先對它分析一下,我們再去看 so
文件。
我們主要分析 a
函數,看到有一個 a.a
靜態變量,這時我們猜測,這個是否是變種的base64編碼,我們寫一個java文件來調用一下這個類裏的 a
函數,進行測試一下
public class test{
public static void main(String[] args){
String s = "listone";//對listone進行變種base64編碼
System.out.println(new a().a(s.getBytes()));
}
}
class a {
private static final char[] a = {'i', '5', 'j', 'L', 'W', '7', 'S', '0', 'G', 'X', '6', 'u', 'f', '1', 'c', 'v', '3', 'n', 'y', '4', 'q', '8', 'e', 's', '2', 'Q', '+', 'b', 'd', 'k', 'Y', 'g', 'K', 'O', 'I', 'T', '/', 't', 'A', 'x', 'U', 'r', 'F', 'l', 'V', 'P', 'z', 'h', 'm', 'o', 'w', '9', 'B', 'H', 'C', 'M', 'D', 'p', 'E', 'a', 'J', 'R', 'Z', 'N'};
public a() {
super();
}
public String a(byte[] arg10) {
int v8 = 3;
StringBuilder v4 = new StringBuilder();
int v0;
for(v0 = 0; v0 <= arg10.length - 1; v0 += 3) {
byte[] v5 = new byte[4];
int v3 = 0;
byte v2 = 0;
while(v3 <= 2) {
if(v0 + v3 <= arg10.length - 1) {
v5[v3] = ((byte)(v2 | (arg10[v0 + v3] & 255) >>> v3 * 2 + 2));
v2 = ((byte)(((arg10[v0 + v3] & 255) << (2 - v3) * 2 + 2 & 255) >>> 2));
}
else {
v5[v3] = v2;
v2 = 64;
}
++v3;
}
v5[v8] = v2;
int v2_1;
for(v2_1 = 0; v2_1 <= v8; ++v2_1) {
if(v5[v2_1] <= 63) {
v4.append(a[v5[v2_1]]);
}
else {
v4.append('=');
}
}
}
return v4.toString();
}
}
將上面的代碼保存爲 test.java
,然後在終端運行
可以看到對 listone
的加密結果爲 bSt9kSRzQ3==
。
然後我又找了一個python實現的對變種base64的加密實現。
base64_charset = ['i', '5', 'j', 'L', 'W', '7', 'S', '0', 'G', 'X', '6', 'u', 'f', '1', 'c', 'v', '3', 'n', 'y', '4', 'q', '8', 'e', 's', '2', 'Q', '+', 'b', 'd', 'k', 'Y', 'g', 'K', 'O', 'I', 'T', '/', 't', 'A', 'x', 'U', 'r', 'F', 'l', 'V', 'P', 'z', 'h', 'm', 'o', 'w', '9', 'B', 'H', 'C', 'M', 'D', 'p', 'E', 'a', 'J', 'R', 'Z', 'N']
def encode(origin_bytes):
# 將每一位bytes轉換爲二進制字符串
base64_bytes = ['{:0>8}'.format(str(bin(b)).replace('0b', '')) for b in origin_bytes]
resp = ''
nums = len(base64_bytes) // 3
remain = len(base64_bytes) % 3
integral_part = base64_bytes[0:3 * nums]
while integral_part:
# 取三個字節,以每6比特,轉換爲4個整數
tmp_unit = ''.join(integral_part[0:3])
tmp_unit = [int(tmp_unit[x: x + 6], 2) for x in [0, 6, 12, 18]]
# 取對應base64字符
resp += ''.join([base64_charset[i] for i in tmp_unit])
integral_part = integral_part[3:]
if remain:
# 補齊三個字節,每個字節補充 0000 0000
remain_part = ''.join(base64_bytes[3 * nums:]) + (3 - remain) * '0' * 8
# 取三個字節,以每6比特,轉換爲4個整數
# 剩餘1字節可構造2個base64字符,補充==;剩餘2字節可構造3個base64字符,補充=
tmp_unit = [int(remain_part[x: x + 6], 2) for x in [0, 6, 12, 18]][:remain + 1]
resp += ''.join([base64_charset[i] for i in tmp_unit]) + (3 - remain) * '='
return resp
if __name__ == '__main__':
s = 'listone'.encode()
local_base64 = encode(s)
print('使用本地base64加密:', local_base64)
然後我們對 listone
進行加密
可以看到結果與java運行的一致,也因此證明了我們的猜想。
通過解壓軟件解壓apk文件,然後可以在lib目錄裏找到這個so文件。
接下來就是看 so
文件裏的 ncheck
函數是幹什麼的了。
這個v6我們可以大膽地猜測它爲我們傳入的字符串
然後我們看那個循環幹了什麼
v7 = 0;
do
{
//將s1[v7]的地址賦值給v8
v8 = &s1[v7];
//第一輪就是 s1[0] = v6[16]
s1[v7] = v6[v7 + 16];
//第一輪就是先 v9 = v6[0],然後v7 = v7+1 = 1
v9 = v6[v7++];
//第一輪是v8[16]=s1[16]=v9
v8[16] = v9;
}
while ( v7 != 16 );
分析之後就可以知道,這個循環就是將我們傳入的字符串的前16位與後16位字符對調。
然後繼續分析下一個循環
v10 = 0;
do
{
v12 = __OFSUB__(v10, 30);
v11 = v10 - 30 < 0;
//第一輪v16=s1[0]
v16 = s1[v10];
//第一輪s1[0] = s1[1]
s1[v10] = s1[v10 + 1];
//第一輪s1[1] = v16 = s1[0]
s1[v10 + 1] = v16;
v10 += 2;
}
while ( v11 ^ v12 );
可以發現這個循環是將字符串兩兩對調位置。
現在再來分析循環的結束條件,這個結束條件一開始直接把我整懵逼了,查了些資料,__OFSUB__
表示x-y是否溢出,溢出返回1,沒有溢出返回0。在此情況下,應該不存在溢出,則返回均爲0,即v12=0,當v10>30時,v11=0。此時異或得0,循環結束。
這裏返回值是與一個字符串比較的結果,那我們就將該字符串逆推回去,寫出逆向腳本。
寫出腳本爲:
def decode(base64_str):
base64_charset = "i5jLW7S0GX6uf1cv3ny4q8es2Q+bdkYgKOIT/tAxUrFlVPzhmow9BHCMDpEaJRZN"
base64_bytes = ['{:0>6}'.format(str(bin(base64_charset.index(s))).replace('0b', '')) for s in base64_str if
s != '=']
resp = bytearray()
nums = len(base64_bytes) // 4
remain = len(base64_bytes) % 4
integral_part = base64_bytes[0:4 * nums]
while integral_part:
tmp_unit = ''.join(integral_part[0:4])
tmp_unit = [int(tmp_unit[x: x + 8], 2) for x in [0, 8, 16]]
for i in tmp_unit:
resp.append(i)
integral_part = integral_part[4:]
if remain:
remain_part = ''.join(base64_bytes[nums * 4:])
tmp_unit = [int(remain_part[i * 8:(i + 1) * 8], 2) for i in range(remain - 1)]
for i in tmp_unit:
resp.append(i)
return resp
if __name__ == '__main__':
flag_1 = 'MbT3sQgX039i3g==AQOoMQFPskB1Bsc7'
print('加密後的字符串:',flag_1)
# 前後16位調換位置
flag_2 = flag_1[len(flag_1)//2:] + flag_1[0:len(flag_1)//2]
print('前後16位調換位置:',flag_2)
flag_3 = ''
for i in range(len(flag_2)//2):
flag_3 += flag_2[i*2+1] + flag_2[i*2]
print('前後兩位調換位置:',flag_3)
print('解密後:',decode(flag_3))
最後運行腳本:
就解出flag的值了。
這個題總體來說邏輯比上一個easyjava要簡單一些,不過這個涉及到了對so文件的簡單分析,也算是學到了一個小知識吧。