0x01上dex2jar:
得到关键代码:
private boolean a(String paramString) {
try {
return ncheck((new a()).a(paramString.getBytes()));
} catch (Exception exception) {
return false;
}
}
private native boolean ncheck(String paramString);
protected void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
setContentView(2130968603);
findViewById(2131427446).setOnClickListener(new View.OnClickListener(this, (Context)this) {
public void onClick(View param1View) {
EditText editText = (EditText)((MainActivity)this.a).findViewById(2131427445);
if (MainActivity.a(this.b, editText.getText().toString())) {
Toast.makeText(this.a, "You are right!", 1).show();
return;
}
Toast.makeText(this.a, "You are wrong! Bye~", 1).show();
}
});
}
以及a.class:
package com.a.easyjni;
public class a {
private static final char[] a = new char[] {
'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 String a(byte[] paramArrayOfbyte) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i <= paramArrayOfbyte.length - 1; i += 3) {
byte[] arrayOfByte = new byte[4];
int j = 0;
byte b = 0;
while (j <= 2) {
if (i + j <= paramArrayOfbyte.length - 1) {
arrayOfByte[j] = (byte)(b | (paramArrayOfbyte[i + j] & 0xFF) >>> j * 2 + 2);
b = (byte)(((paramArrayOfbyte[i + j] & 0xFF) << (2 - j) * 2 + 2 & 0xFF) >>> 2);
} else {
arrayOfByte[j] = b;
b = 64;
}
j++;
}
arrayOfByte[3] = b;
for (j = 0; j <= 3; j++) {
if (arrayOfByte[j] <= 63) {
stringBuilder.append(a[arrayOfByte[j]]);
} else {
stringBuilder.append('=');
}
}
}
return stringBuilder.toString();
}
}
我们先看a.class,可以看到是一个变种的base64,我们将这个变种base64的码表丢进正常base64解码的代码,替换码表:
public final class Base64 {
// private static final char S_BASE64CHAR[] = {
// 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
// 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
// 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
// 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
// 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
// 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
// '8', '9', '+', '/'
// };
private static final char S_BASE64CHAR[] = {
'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'
};
private static final char S_BASE64PAD = 61;
private static final byte S_DECODETABLE[];
static {
S_DECODETABLE = new byte[128];
for(int i = 0; i < S_DECODETABLE.length; i++)
S_DECODETABLE[i] = 127;
for(int j = 0; j < S_BASE64CHAR.length; j++)
S_DECODETABLE[S_BASE64CHAR[j]] = (byte)j;
}
private Base64() { }
public static byte[] decode(String s) {
char ac[] = new char[4];
int i = 0;
byte abyte0[] = new byte[(s.length() / 4) * 3 + 3];
int j = 0;
for(int k = 0; k < s.length(); k++) {
char c = s.charAt(k);
if(c == '=' ||
c < S_DECODETABLE.length &&
S_DECODETABLE[c] != 127)
{
ac[i++] = c;
if(i == ac.length) {
i = 0;
j += decode0(ac, abyte0, j);
}
}
}
if(j == abyte0.length) {
return abyte0;
} else {
byte abyte1[] = new byte[j];
System.arraycopy(abyte0, 0, abyte1, 0, j);
return abyte1;
}
}
public static String encode(byte abyte0[]) {
return encode(abyte0, 0, abyte0.length);
}
public static String encode(byte abyte0[], int i, int j) {
if(j <= 0)
return "";
char ac[] = new char[(j / 3) * 4 + 4];
int k = i;
int l = 0;
int i1;
for(i1 = j - i; i1 >= 3; i1 -= 3) {
int j1 = ((abyte0[k] & 0xff) << 16) + ((abyte0[k + 1] & 0xff) << 8) + (abyte0[k + 2] & 0xff);
ac[l++] = S_BASE64CHAR[j1 >> 18];
ac[l++] = S_BASE64CHAR[j1 >> 12 & 0x3f];
ac[l++] = S_BASE64CHAR[j1 >> 6 & 0x3f];
ac[l++] = S_BASE64CHAR[j1 & 0x3f];
k += 3;
}
if(i1 == 1) {
int k1 = abyte0[k] & 0xff;
ac[l++] = S_BASE64CHAR[k1 >> 2];
ac[l++] = S_BASE64CHAR[k1 << 4 & 0x3f];
ac[l++] = '=';
ac[l++] = '=';
} else if(i1 == 2) {
int l1 = ((abyte0[k] & 0xff) << 8) + (abyte0[k + 1] & 0xff);
ac[l++] = S_BASE64CHAR[l1 >> 10];
ac[l++] = S_BASE64CHAR[l1 >> 4 & 0x3f];
ac[l++] = S_BASE64CHAR[l1 << 2 & 0x3f];
ac[l++] = '=';
}
return new String(ac, 0, l);
}
private static int decode0(char ac[], byte abyte0[], int i) {
byte byte0 = 3;
if(ac[3] == '=')
byte0 = 2;
if(ac[2] == '=')
byte0 = 1;
byte byte1 = S_DECODETABLE[ac[0]];
byte byte2 = S_DECODETABLE[ac[1]];
byte byte3 = S_DECODETABLE[ac[2]];
byte byte4 = S_DECODETABLE[ac[3]];
switch(byte0) {
case 1: // '\001'
abyte0[i] = (byte)(byte1 << 2 & 0xfc | byte2 >> 4 & 3);
return 1;
case 2: // '\002'
abyte0[i++] = (byte)(byte1 << 2 & 0xfc | byte2 >> 4 & 3);
abyte0[i] = (byte)(byte2 << 4 & 0xf0 | byte3 >> 2 & 0xf);
return 2;
case 3: // '\003'
abyte0[i++] = (byte)(byte1 << 2 & 0xfc | byte2 >> 4 & 3);
abyte0[i++] = (byte)(byte2 << 4 & 0xf0 | byte3 >> 2 & 0xf);
abyte0[i] = (byte)(byte3 << 6 & 0xc0 | byte4 & 0x3f);
return 3;
}
throw new RuntimeException("Internal Errror");
}
public static void main(String[] args) {
String a="test";
byte [] b=null;
b=a.getBytes();
String encodeString=Base64.encode(b);
System.out.println(encodeString);
byte[] decodeByte=Base64.decode(encodeString);
System.out.println(new String(decodeByte));
}
}
就可以得到变种base64的解码代码。
0x02 本地方法ncheck:
打开IDA32位版本,把lib\armeabi-v7a\libnative.so拖进IDA,找到ncheck:
点击进这个函数,无脑F5,得到伪代码:
signed int __fastcall Java_com_a_easyjni_MainActivity_ncheck(int a1, int a2, int a3)
{
int v3; // r8
int v4; // r5
int v5; // r8
const char *v6; // r6
int v7; // r0
char *v8; // r2
char v9; // r1
int v10; // r0
bool v11; // nf
unsigned __int8 v12; // vf
int v13; // r1
signed int result; // r0
char s1[32]; // [sp+3h] [bp-35h]
char v16; // [sp+23h] [bp-15h]
int v17; // [sp+28h] [bp-10h]
v17 = v3;
v4 = a1;
v5 = a3;
v6 = (const char *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0);
if ( strlen(v6) == 32 )
{
v7 = 0;
do
{
v8 = &s1[v7];
s1[v7] = v6[v7 + 16];
v9 = v6[v7++];
v8[16] = v9;
}
while ( v7 != 16 );
(*(void (__fastcall **)(int, int, const char *))(*(_DWORD *)v4 + 680))(v4, v5, v6);
v10 = 0;
do
{
v12 = __OFSUB__(v10, 30);
v11 = v10 - 30 < 0;
v16 = s1[v10];
s1[v10] = s1[v10 + 1];
s1[v10 + 1] = v16;
v10 += 2;
}
while ( v11 ^ v12 );
v13 = memcmp(s1, "MbT3sQgX039i3g==AQOoMQFPskB1Bsc7", 0x20u);
result = 0;
if ( !v13 )
result = 1;
}
else
{
(*(void (__fastcall **)(int, int, const char *))(*(_DWORD *)v4 + 680))(v4, v5, v6);
result = 0;
}
return result;
}
根据其他大神的wp,v6是传入的编码好的字符串。
现在来分析如下代码段:
v7 = 0;
do
{
v8 = &s1[v7];
s1[v7] = v6[v7 + 16];
v9 = v6[v7++];
v8[16] = v9;
}
while ( v7 != 16 );
看第一轮循环这段代码做了什么:
1.v8 = &s1[v7]; //指针v8指向s1[0]
2.s1[v7] = v6[v7+16]; //s1[0] = v6[16]
3.v9 = v6[v7++]; //字符变量v9 = v6[0],然后v7自增1,这行代码执行完v7=1
4.v8[16] = v9; //由于前面v8指向s1[0],故这里s1[16] = v9 = v6[0]
综上,第一轮循环结束后, 输出s1[0] = v6[16], s1[16] = v6[0]
假如v6的数据结构如下:(空表示无数据,这里只考虑第一轮循环使用的数据)
v6 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
---|
可以看出该代码是将输入v6分为两段之后互换。则第一轮循环结束后s1的结构如下:
数组下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
v6 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
s1 | 16 | 0 |
现在来看第二轮循环:
1.v8 = &s1[v7]; //指针v8指向s1[1]
2.s1[v7] = v6[v7+16]; //s1[1] = v6[17]
3.v9 = v6[v7++]; //字符变量v9 = v6[1],然后v7自增1,这行代码执行完v7=2
4.v8[16] = v9; //由于前面v8指向s1[1],故这里s1[17] = v9 = v6[1]
第二轮循环结束后,s1与v6的结构如下:
数组下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
v6 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
s1 | 16 | 17 | 0 | 1 |
很明显了,当循环全部结束的时候,s1的结构如下:
数组下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
v6 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
s1 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
上述代码段就是将v6从中间砍成两半,然后把头放到尾,尾放到头。
接下来继续分析下面的代码段,可以看到,循环结束条件是v11与v12相同(即异或为0):
v10 = 0;
do
{
v12 = __OF SUB__(v10, 30);
v11 = v10 - 30 < 0;
v16 = s1[v10];
s1[v10] = s1[v10 + 1];
s1[v10 + 1] = v16;
v10 += 2;
}
while ( v11 ^ v12 );
v13 = memcmp(s1, "MbT3sQgX039i3g==AQOoMQFPskB1Bsc7", 0x20u);
result = 0;
看看第一轮循环代码做了什么:
1.v12 = __OF SUB__(v10, 30); //OFSUB是IDA自带的宏,检查v10-30是否溢出,若没溢出返回0,此时v12=0
2.v11 = v10 - 30 < 0; //因为v10=0,v10-30=0-30<0,故v11=1
3.v16 = s1[v10]; //字符变量v16 = s1[0]
4.s1[v10] = s1[v10 + 1]; //s1[0] = s1[1]
5.s1[v10 + 1] = v16; //s1[1] = v16 = s1[0]
6.v10 += 2; //v10 = 2
此轮循环结束后,s1的数据结果如下:
s1 | 17 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|
第二轮循环不用看了,可以看到所有循环结束后s1的数据是这样:
s1 | 17 | 16 | 19 | 18 | 21 | 20 | 23 | 22 | 25 | 24 | 27 | 26 | 29 | 28 | 31 | 30 | 1 | 0 | 3 | 2 | 5 | 4 | 7 | 6 | 9 | 8 | 11 | 10 | 13 | 12 | 15 | 14 |
---|
两段循环代码走完就是比较了,比较s1的内容是不是“MbT3sQgX039i3g==AQOoMQFPskB1Bsc7”
因此,根据上述逻辑可以得出根据最后结果反推回去输入的字符串的Java代码(Base64.decode方法就是上面给出的解码类):
package Test;
import java.io.UnsupportedEncodingException;
import com.sta.encrypt.Base64;
public class Test {
public static void main(String[] args) throws UnsupportedEncodingException {
String encrypt = "MbT3sQgX039i3g==AQOoMQFPskB1Bsc7";
String encrypt_p = "";
//两位两位互换
for(int i=0;i<31;i+=2) {
encrypt_p+=encrypt.charAt(i+1);
encrypt_p+=encrypt.charAt(i);
}
//前半段后半段互换
String base64 = "";
base64+=encrypt_p.substring(16,32);
base64+=encrypt_p.substring(0, 16);
//变种base64解码
System.out.println(new String(Base64.decode(base64)));
}
}
输出flag:
将apk拖到JEB2:
核心代码:
protected void onCreate(Bundle arg3) {
super.onCreate(arg3);
this.setContentView(2130968603);
this.findViewById(2131427446).setOnClickListener(new View$OnClickListener() {
public void onClick(View arg8) {
if(new Base64New().Base64Encode(MainActivity.this.findViewById(2131427445).getText().toString().getBytes()).equals("5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs=")) {
Toast.makeText(MainActivity.this, "验证通过!", 1).show();
}
else {
Toast.makeText(MainActivity.this, "验证失败!", 1).show();
}
}
});
}
看看Base64New().Base64Encode()方法:
package com.testjava.jack.pingan1;
public class Base64New {
private static final char[] Base64ByteToStr = null;
private static final int RANGE = 255;
private static byte[] StrToBase64Byte;
static {
Base64New.Base64ByteToStr = new char[]{'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', '+', '/'};
Base64New.StrToBase64Byte = new byte[128];
}
public Base64New() {
super();
}
public String Base64Encode(byte[] arg9) {
int v7 = 3;
StringBuilder v3 = new StringBuilder();
int v1;
for(v1 = 0; v1 <= arg9.length - 1; v1 += 3) {
byte[] v0 = new byte[4];
byte v4 = 0;
int v2;
for(v2 = 0; v2 <= 2; ++v2) {
if(v1 + v2 <= arg9.length - 1) {
v0[v2] = ((byte)((arg9[v1 + v2] & 255) >>> v2 * 2 + 2 | v4));
v4 = ((byte)(((arg9[v1 + v2] & 255) << (2 - v2) * 2 + 2 & 255) >>> 2));
}
else {
v0[v2] = v4;
v4 = 64;
}
}
v0[v7] = v4;
for(v2 = 0; v2 <= v7; ++v2) {
if(v0[v2] <= 63) {
v3.append(Base64New.Base64ByteToStr[v0[v2]]);
}
else {
v3.append('=');
}
}
}
return v3.toString();
}
}
可以看到是一个变种的base64,也是上述题目的一部分代码.因此上述变换码表的代码也能用:
public class Test {
public static void main(String[] args) throws Exception {
String mw = "5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs=";
System.out.println(new String(Base64.decode(mw)));
}
}
得到一串字符串之后加上flag{}就是flag.
------end-----