攻防世界mobile新手區之easy jni 以及easy-apk的write up

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:

 

這裏順便給出easy-apk的write up:

將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-----

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章