【Android 逆向】看雪题目:找出flag 如果输入正确则屏幕上的 hello会变成success

1. apk 安装到手机,只有一个输入框,随便输入点什么,提示error

2. apk拖入到jadx中

public class MainActivity extends AppCompatActivity {
    static int n = 0;
    public static byte[] su;

    public native String stringFromJNI();

    static {
        System.loadLibrary("native-lib");
        su = new byte[]{-66, -81, 25, -66, 122, -8, 42, -10, 78, -117, 104, -17, 118, -27, 40, -80, -20, 40, -60, -80, -11, -5, 75, 5, 100, 47, -48, 42, -119, -60, -66, 113};
    }

    /* JADX INFO: Access modifiers changed from: protected */
    @Override // androidx.appcompat.app.AppCompatActivity, androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
        Button aa = (Button) findViewById(R.id.button);
        aa.setOnClickListener(new View.OnClickListener() { // from class: com.r0ysue.test.MainActivity.1
            @Override // android.view.View.OnClickListener
            public void onClick(View v) {
                TextView ss = (TextView) MainActivity.this.findViewById(R.id.editTextTextPassword);
                String mingwen = MainActivity.string2hex(ss.getText().toString());
                if (mingwen.length() != 26) {
                    tv.setText("error");
                    return;
                }
                String ming1 = mingwen.substring(0, 8);
                String ming2 = mingwen.substring(8, 16);
                String ming3 = mingwen.substring(16, 24);
                String ming4 = mingwen.substring(24, 26);
                int a1 = Integer.parseInt(ming1, 16);
                int a2 = Integer.parseInt(ming2, 16);
                int a3 = Integer.parseInt(ming3, 16);
                int a4 = Integer.parseInt(ming4, 16);
                if (MainActivity.this.encrypt(a1, a2, a3, a4) == 1) {
                    tv.setText("success");
                } else {
                    tv.setText("error");
                }
            }
        });
    }

    public int encrypt(int a, int b, int c, int d) {
        try {
            String content = Integer.toString(a, 16) + Integer.toString(b, 16) + Integer.toString(c, 16) + Integer.toString(d, 16);
            String password = "r0ysue-yyds";
            while (password.length() < 16) {
                password = password + "0";
            }
            SecretKeySpec secretKeySpec = new SecretKeySpec(password.getBytes(), "AES");
            Cipher cipher = Cipher.getInstance("AES");
            byte[] byteContent = content.getBytes("utf-8");
            cipher.init(1, secretKeySpec);
            byte[] result = cipher.doFinal(byteContent);
            int n2 = 0;
            while (true) {
                byte[] bArr = su;
                if (n2 >= bArr.length) {
                    return 1;
                }
                if (result[n2] != bArr[n2]) {
                    return 0;
                }
                n2++;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    public static String string2hex(String string) {
        StringBuffer unicode = new StringBuffer();
        for (int i = 0; i < string.length(); i++) {
            char c = string.charAt(i);
            unicode.append(Integer.toHexString(c));
        }
        return unicode.toString();
    }
}

看起来很简单,java写个解密过程,填入密码,还是不对。
看来so里有问题

2. so拖入IDA中,发现了一个函数.init_proc 查了下,是最早运行的一个函数

unsigned __int64 init_proc()
{
	......

  v10 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
  strcpy(v9, "com/r0ysue/test/MainActivity");
  for ( i = 0; i < (unsigned __int64)__strlen_chk(v9, 0x1Du); ++i )
    byte_54A0[i] = v9[i] ^ 0x3C;
  strcpy((char *)&v8, "encrypt");
  for ( j = 0; j < (unsigned __int64)__strlen_chk((const char *)&v8, 8u); ++j )
    byte_54D2[j] = v9[j - 8] ^ 0x3D;
  strcpy((char *)&v7, "(IIII)I");
  for ( k = 0; k < (unsigned __int64)__strlen_chk((const char *)&v7, 8u); ++k )
    byte_5504[k] = *((_BYTE *)&v7 + k) ^ 0x3F;
  for ( m = 0; m <= (unsigned __int64)__strlen_chk(v9, 0x1Du); ++m )
  {
    byte_54A0[m] ^= 0x3Cu;
    if ( m == (unsigned __int64)__strlen_chk(v9, 0x1Du) )
      byte_54A0[m] = 0;
  }
  for ( n = 0; n < (unsigned __int64)__strlen_chk((const char *)&v8, 8u); ++n )
  {
    byte_54D2[n] ^= 0x3Du;
    if ( n == (unsigned __int64)__strlen_chk(v9, 0x1Du) )
      byte_54D2[n] = 0;
  }
  for ( ii = 0; ; ++ii )
  {
    result = __strlen_chk((const char *)&v7, 8u);
    if ( ii >= result )
      break;
    byte_5504[ii] ^= 0x3Fu;
    if ( ii == (unsigned __int64)__strlen_chk(v9, 0x1Du) )
      byte_5504[ii] = 0;
  }
  _ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
  return result;
}

可以看到貌似在搜集java层 encrypt 方法的信息

byte_54A0 v9  MainActivity
byte_54D2 v8  encrypt
byte_5504 v7 (IIII)I

点进Java_com_r0ysue_test_MainActivity_stringFromJNI看看

__int64 __fastcall Java_com_r0ysue_test_MainActivity_stringFromJNI(JNIEnv *env)
{
......

  v11 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
  pthread_create_sym = find_sym_sub_1604((__int64)"/system/lib64/libc.so", (__int64)"pthread_create");
  v6 = 1;
  __android_log_print(6, "r0ysue", "%x", pthread_create_sym);
  v5 = fopen("/proc/self/maps", "r");
  while ( __fgets_chk(v10, 1024LL, v5, 1024LL) )
  {
    if ( strstr(v10, "libc.so") )
    {
      if ( v6 == 2 )
      {
        v1 = strtok(v10, "-");
        v7 = strtoul(v1, 0LL, 16);
      }
      else
      {
        strtok(v10, "-");
      }
      v2 = strtok(0LL, " ");
      strtoul(v2, 0LL, 16);
      ++v6;
    }
  }
  ret_chars = sub_20C0(pthread_create_sym, v7);
  sub_1D40((__int64)env, (__int64)sub_272C, (__int64)byte_54A0, (__int64)byte_54D2, (__int64)byte_5504, 0);
  result = sub_3104((__int64)env, (__int64)ret_chars);
  _ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
  return result;
}

这一行 sub_1D40((__int64)env, (__int64)sub_272C, (__int64)byte_54A0, (__int64)byte_54D2, (__int64)byte_5504, 0);把上面的encrypt的三个信息全取出来了,还传入了一个函数sub_272C,点进去看看是在干啥

_QWORD *__fastcall sub_1D40(
        JNIEnv *env,
        __int64 t_func,
        const char *class_name,
        const char *method_name,
        const char *sig_name,
        int arg_num)
{
.......

  v23 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
  v20 = method_name;
  v21 = sig_name;
  v22 = t_func;
  if ( !dword_5350 )
  {
    v12 = 1;
    v11 = fopen("/proc/self/maps", "r");
    while ( __fgets_chk(v19, 1024LL, v11, 1024LL) )
    {
      if ( strstr(v19, "libart.so") )
      {
        __android_log_print(6, "r0ysue", "%s", v19);
        if ( v12 == 1 )
        {
          v6 = strtok(v19, "-");
          libart_startp_qword_53F8 = strtoul(v6, 0LL, 16);
        }
        else
        {
          strtok(v19, "-");
        }
        v7 = strtok(0LL, " ");
        strtoul(v7, 0LL, 16);
        ++v12;
      }
    }
    ++dword_5350;
  }
  v10 = find_sym_sub_1604((__int64)"/system/lib64/libart.so", (__int64)"art_quick_generic_jni_trampoline");
  jclass_mainActivity = (__int64)find_class_sub_2040(env, class_name);
  jmethodId = (_QWORD *)getMethodId_sub_2078(env, (void *)jclass_mainActivity, method_name, sig_name);
  qword_5358[arg_num] = *(_QWORD *)((char *)jmethodId + 4);
  qword_53A8[arg_num] = jmethodId[5];
  *(_QWORD *)((char *)jmethodId + 4) ^= 0x80100uLL;// *(_DWORD *) (a1 + 4) & 0x80000 == 0为是否为native的标志
  jmethodId[5] = libart_startp_qword_53F8 + v10 - 0x25000;// //需要-0x25000是因为libart.so的程序头在偏移为0x25000 , 这里没遍历偷懒了
  jmethodId[4] = t_func;
  _ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
  return jmethodId;
}

看到了一个关键的字符串art_quick_generic_jni_trampoline 和下面修改jmethod 成员的操作,和 开源项目sandhook 十分相似,应该就是使用native方法去替换java层方法的行为

参考: https://github.com/asLody/SandHook

那么可以推测 t_func 即 sub_272C 就是替换用的native函数

bool __fastcall sub_272C(JNIEnv *env, jobject jobject, int a, int b, int c, int d)
{
  if ( sub_2128(a, qword_5000, 101) == 0x384B099F681D8BA5LL )
  {
    if ( sub_2128(b, qword_5000, 101) == 0xCA6AFBB12E68AFF0LL )
      return sub_2128(c, qword_5000, 101) == 0x910E2DB62AE48A6ELL
          && sub_2128(d, qword_5000, 101) == 0x1664C6F89BFD8183LL;
    else
      return 0;
  }
  else
  {
    return 0;
  }
}
.data:0000000000005000 79 2D 65 75 73 79 30 72       qword_5000 DCQ 'r0ysue-y'               ; DATA XREF: sub_272C+C↑o
.data:0000000000005000                                                                       ; sub_272C+4C↑r
.data:0000000000005008           

从这里可以推测出sub_2128是某种加密算法, 密钥为 r0ysue-y,开始寻找算法特征

DES 算法 参考 https://www.cnblogs.com/zhangzixu/p/14471822.html

.data:0000000000005008 3A 32 2A 22 1A 12 0A 02 3C 34+ip_byte_5008 DCB 0x3A, 0x32, 0x2A, 0x22, 0x1A, 0x12, 0xA, 2, 0x3C, 0x34, 0x2C, 0x24, 0x1C, 0x14, 0xC, 4, 0x3E, 0x36
.data:0000000000005008 2C 24 1C 14 0C 04 3E 36 2E 26+                                        ; DATA XREF: sub_2128+B4↑o
.data:0000000000005008 1E 16 0E 06 40 38 30 28 20 18+DCB 0x2E, 0x26, 0x1E, 0x16, 0xE, 6, 0x40, 0x38, 0x30, 0x28, 0x20, 0x18, 0x10, 8, 0x39, 0x31, 0x29, 0x21
.data:0000000000005008 10 08 39 31 29 21 19 11 09 01+DCB 0x19, 0x11, 9, 1, 0x3B, 0x33, 0x2B, 0x23, 0x1B, 0x13, 0xB, 3, 0x3D, 0x35, 0x2D, 0x25, 0x1D, 0x15
.data:0000000000005008 3B 33 2B 23 1B 13 0B 03 3D 35+DCB 0xD, 5, 0x3F, 0x37, 0x2F, 0x27, 0x1F, 0x17, 0xF, 7

.data:0000000000005080 01 01 02 02 02 02 02 02 01 02+left_move_byte_5080 DCB 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
.data:0000000000005080 02 02 02 02 02 01         

从算法中使用到的数组来看,这明显是一个DES加密算法
那么只要对 0x384B099F681D8BA5LL 0xCA6AFBB12E68AFF0LL 0x910E2DB62AE48A6ELL 0x1664C6F89BFD8183LL 进行解密即可得出

使用 https://gchq.github.io/CyberChef 进行解密操作得到....i-lo....ve-r....0ysu.......e
flag即为i-love-r0ysue

posted @ 2023-03-07 16:18  明月照江江  阅读(141)  评论(0编辑  收藏  举报