【攻防世界】mobile easyjni writeup

首先观察MainActivity的代码:

public class MainActivity extends c {
    static {
        System.loadLibrary("native");
    }

    private boolean a(String s) {
        try {
            return this.ncheck(new a().a(s.getBytes()));
        }
        catch(Exception exception0) {
            return false;
        }
    }

    private native boolean ncheck(String arg1) {
    }

    @Override  // android.support.v7.app.c
    protected void onCreate(Bundle bundle0) {
        super.onCreate(bundle0);
        this.setContentView(0x7F04001B);  // layout:activity_main
        this.findViewById(0x7F0B0076).setOnClickListener(new View.OnClickListener() {  // id:button
            @Override  // android.view.View$OnClickListener
            public void onClick(View view0) {
                String s = ((EditText)((MainActivity)this).findViewById(0x7F0B0075)).getText().toString();  // id:edit
                if(MainActivity.this.a(s)) {
                    Toast.makeText(this, "You are right!", 1).show();
                    return;
                }

                Toast.makeText(this, "You are wrong! Bye~", 1).show();
            }
        });
    }
}

由上面的代码可知,输入(设为s)被传入到“a"函数做处理。“a”函数会调用so文件里的ncheck函数来验证输入的字符串是否合法,而它的参数是一个"a" 类实例,这个实例是以输入s为参数生成的。

因为我们要做逆向,所以先解决ncheck函数,然后在处理“a”。

bool __fastcall Java_com_a_easyjni_MainActivity_ncheck(int a1, int a2, int a3)
{
  const char *v5; // r6
  int i; // r0
  char *v7; // r2
  char v8; // r1
  int v9; // r0
  bool v10; // cc
  _BOOL4 result; // r0
  char v12[32]; // [sp+3h] [bp-35h] BYREF
  char v13; // [sp+23h] [bp-15h]

  v5 = (const char *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0);
  if ( strlen(v5) == 32 )
  {
    for ( i = 0; i != 16; ++i )
    {
      v7 = &v12[i];
      v12[i] = v5[i + 16];
      v8 = v5[i];
      v7[16] = v8;
    }
    (*(void (__fastcall **)(int, int, const char *))(*(_DWORD *)a1 + 680))(a1, a3, v5);
    v9 = 0;
    do
    {
      v10 = v9 < 30;
      v13 = v12[v9];
      v12[v9] = v12[v9 + 1];
      v12[v9 + 1] = v13;
      v9 += 2;
    }
    while ( v10 );
    result = memcmp(v12, "MbT3sQgX039i3g==AQOoMQFPskB1Bsc7", 0x20u) == 0;
  }
  else
  {
    (*(void (__fastcall **)(int, int, const char *))(*(_DWORD *)a1 + 680))(a1, a3, v5);
    result = 0;
  }
  return result;
}

这个反编译的c++代码有点乱,让我用python重写一遍(最后省略了字符串比较的内容,输出经过处理的字符串):

def ncheck(a3):
    v5 = bytearray(a3, 'utf-8') 
    v12 = bytearray(32) 
    length = len(v5)
    if length == 32 :
        for i in range(16):
            v12[i] = v5[i+16]
            v12[i+16] = v5[i]
    print("v12="+v12.decode("utf-8"))
    v9=0
    while v9 < 31:
        v13 = v12[v9]
        v12[v9] = v12[v9+1]
        v12[v9+1] = v13
        v9+=2
    result = v12.decode('utf-8')
    print("result="+result)

参数a3包含32个字符串,把a3的前16位和后16位交换,然后按顺序每两个字符进行交换,得到结果跟"MbT3sQgX039i3g==AQOoMQFPskB1Bsc7"做对比(这是原c++代码的内容)。很明显a3经过上面的代码处理后得到result,同样result经过上面的代码也可以得到a3。

以"MbT3sQgX039i3g==AQOoMQFPskB1Bsc7"作为参数,得到的输出是:

result=QAoOQMPFks1BsB7cbM3TQsXg30i9g3==

但这还不是答案,它是经过“a”类处理的

public class a {
    private static final char[] a;

    static {
        a.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[] arr_b) {
        StringBuilder stringBuilder0 = new StringBuilder();
        for(int v = 0; v <= arr_b.length - 1; v += 3) {
            byte[] arr_b1 = new byte[4];
            byte b = 0;
            for(int v1 = 0; v1 <= 2; ++v1) {
                if(v + v1 <= arr_b.length - 1) {
                    arr_b1[v1] = (byte)(b | (arr_b[v + v1] & 0xFF) >>> v1 * 2 + 2);
                    b = (byte)(((arr_b[v + v1] & 0xFF) << (2 - v1) * 2 + 2 & 0xFF) >>> 2);
                }
                else {
                    arr_b1[v1] = b;
                    b = 0x40;
                }
            }

            arr_b1[3] = b;
            for(int v2 = 0; v2 <= 3; ++v2) {
                if(arr_b1[v2] <= 0x3F) {
                    stringBuilder0.append(a.a[arr_b1[v2]]);
                }
                else {
                    stringBuilder0.append('=');
                }
            }
        }

        return stringBuilder0.toString();
    }
}

“a”类的代码很熟悉,之前在easy-apk里遇到过,在easy-apk里叫做Base64New,“a”实际上就是类似Base64编码,只是调换了表的顺序:

  static {
        a.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'};
    }

而Base64的顺序是这样的:

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

要对其进行解码,有两种方法,第一种就是想easy-apk里那样自己写解码程序,详细请看我之前的writeup

而第二种方法就是替换字符:使用str.maketrans()函数创建一个字符映射,然后用str.translate()函数将new字符串中的字符替换为对应的ori字符。

import base64
result = "QAoOQMPFks1BsB7cbM3TQsXg30i9g3=="
new="i5jLW7S0GX6uf1cv3ny4q8es2Q+bdkYgKOIT/tAxUrFlVPzhmow9BHCMDpEaJRZN"  
ori = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

change = result.translate(str.maketrans(new,ori))
print("after changing:"+change)
print(base64.b64decode(change))

你可能感兴趣的:(android)