ISCC2019 Mobile Mobile01 writeup

与其说是逆向题,不如说是一道简单的算法题。

分析

对文件进行解压、反汇编后查看文件,定位到MainActivity.java

MainActivity

// 
// Decompiled by Procyon v0.5.30
// 

package com.iscc.crackme;

import android.content.Context;
import android.widget.Toast;
import android.view.View;
import android.view.View$OnClickListener;
import android.widget.EditText;
import android.widget.Button;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity
{
    static {
        System.loadLibrary("native-lib");
    }

    private boolean checkFirst(final String s) {
        if (s.length() != 16) {
            return false;
        }
        for (int i = 0; i < s.length(); ++i) {
            if (s.charAt(i) > '8') {
                return false;
            }
            if (s.charAt(i) < '1') {
                return false;
            }
        }
        return true;
    }

    public native boolean checkSecond(final String p0);

    @Override
    protected void onCreate(final Bundle bundle) {
        super.onCreate(bundle);
        this.setContentView(2131296284);
        this.findViewById(2131165218).setOnClickListener((View$OnClickListener)new View$OnClickListener() {
            final /* synthetic */ EditText val$editText = this.findViewById(2131165240);

            public void onClick(final View view) {
                final String trim = this.val$editText.getText().toString().trim();
                if (MainActivity.this.checkFirst(trim) && MainActivity.this.checkSecond(trim)) {
                    // '注册成功!'
                    Toast.makeText((Context)MainActivity.this, (CharSequence)"\u6ce8\u518c\u6210\u529f!", 0).show();
                    return;
                }
                // '注册失败!'
                Toast.makeText((Context)MainActivity.this, (CharSequence)"\u6ce8\u518c\u5931\u8d25!", 0).show();
            }
        });
    }
}

checkFirst

先进行初步检查,要求字符长度为16,而且字符只能是数字1-8的ascii码

private boolean checkFirst(final String s) {
    if (s.length() != 16) {
        return false;
    }
    for (int i = 0; i < s.length(); ++i) {
        if (s.charAt(i) > '8') {
            return false;
        }
        if (s.charAt(i) < '1') {
            return false;
        }
    }
    return true;
}

checkSecond应该在libnative-lib.so里面,用IDA进行查看。

Java_com_iscc_crackme_MainActivity_checkSecond

char __fastcall Java_com_iscc_crackme_MainActivity_checkSecond(__int64 a1, __int64 a2, __int64 a3)
{
  char result; // al
  char v4; // [rsp+6h] [rbp-8Ah]
  char v5; // [rsp+13h] [rbp-7Dh]
  char v6; // [rsp+40h] [rbp-50h]
  char v7; // [rsp+58h] [rbp-38h]
  char v8; // [rsp+70h] [rbp-20h]
  unsigned __int64 v9; // [rsp+88h] [rbp-8h]

  v9 = __readfsqword(0x28u);
  jstring2str((__int64)&v8, a1, a3);
  v5 = 0;
  std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::basic_string(&v7, &v8);
  v4 = 0;
  if ( checkfirst((__int64)&v7) & 1 )
  {
    std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::basic_string(&v6, &v8);
    v5 = 1;
    v4 = checkAgain((__int64)&v6);
  }
  if ( v5 & 1 )
    std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::~basic_string(&v6);
  std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::~basic_string(&v7);
  std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::~basic_string(&v8);
  result = v4 & 1;
  if ( __readfsqword(0x28u) == v9 )
    result = v4 & 1;
  return result;
}

可以看出这里面又有两个检查。

checkfirst

int __fastcall checkfirst(char *str)
{
  char *v2; // [rsp+0h] [rbp-118h]
  char *v3; // [rsp+18h] [rbp-100h]
  signed int i; // [rsp+30h] [rbp-E8h]
  char v5; // [rsp+37h] [rbp-E1h]

  for ( i = 1; i < 8; ++i )
  {
    if ( *str & 1 )
      v3 = (char *)*((_QWORD *)str + 2);
    else
      v3 = str + 1;
    if ( *str & 1 )
      v2 = (char *)*((_QWORD *)str + 2);
    else
      v2 = str + 1;

    // 后面的字符的码值必须要比前面的大
    if ( v3[i] <= v2[i - 1] )
    {
      v5 = 0;
      return v5 & 1;
    }
  }
  v5 = 1;
  return v5 & 1;
}

这里检查前面的八个字符,规则就是后面的字符的码值必须要比前面的大。那么很明显就是前面八个字符就是12345678了。

这里提一点:

if ( *str & 1 )
  v3 = (char *)*((_QWORD *)str + 2);
else
  v3 = str + 1;

对于上面的判断我们不用太在意,应该是JAVA的一种规定,字符串的第一个值是用来存放标志位的。总之我们可以直接看成是v3 = str + 1;语句就对了。

checkAgain

int __fastcall checkAgain(char *str)
{
  int result; // eax
  char *v2; // [rsp+10h] [rbp-170h]
  char *v3; // [rsp+20h] [rbp-160h]
  int l; // [rsp+3Ch] [rbp-144h]
  int k; // [rsp+40h] [rbp-140h]
  int j; // [rsp+44h] [rbp-13Ch]
  signed int i; // [rsp+48h] [rbp-138h]
  char key; // [rsp+4Fh] [rbp-131h]
  int v9[8]; // [rsp+130h] [rbp-50h]
  int v10[10]; // [rsp+150h] [rbp-30h]
  unsigned __int64 v11; // [rsp+178h] [rbp-8h]

  v11 = __readfsqword(0x28u);
  for ( i = 0; i < 8; ++i )
  {
    if ( *str & 1 )
      v3 = (char *)*((_QWORD *)str + 2);
    else
      v3 = str + 1;
    v10[i] = v3[i] - '1';
  }
  for ( j = 0; j < 8; ++j )
  {
    if ( *str & 1 )
      v2 = (char *)*((_QWORD *)str + 2);
    else
      v2 = str + 1;
    v9[j] = v2[j + 8] - '1';
  }
  result = v9[7] + v9[0];
  if ( v9[7] + v9[0] == 5 )
  {
    result = v9[6] + v9[1];
    if ( v9[6] + v9[1] == 12 )
    {
      result = v9[0];
      if ( v9[0] < v9[7] )
      {
        for ( k = 1; k < 8; ++k )
        {
          for ( l = 0; l < k; ++l )
          {
            result = k;                         // 不能相等
            if ( v10[l] == v10[k] )
            {
              key = 0;
              goto LABEL_34;
            }
            result = k;
            if ( v9[l] == v9[k] )
            {
              key = 0;
              goto LABEL_34;
            }
            result = l;                         // 后面的减去差不能相等
            if ( v10[k] - v10[l] == v9[k] - v9[l] )
            {
              key = 0;
              goto LABEL_34;
            }
            result = k;                         // 这里把上面的判断反过来了,也就是对负数进行了判断
            if ( v10[k] - v10[l] == v9[l] - v9[k] )
            {
              key = 0;
              goto LABEL_34;
            }
          }
          result = k + 1;
        }
        key = 1;
      }
      else
      {
        key = 0;
      }
    }
    else
    {
      key = 0;
    }
  }
  else
  {
    key = 0;
  }
LABEL_34:
  LOBYTE(result) = key;
  if ( __readfsqword(0x28u) == v11 )
    result = key & 1;
  return result;
}

这里就考验我们的基础算法了,只要写出对应的算法进行解方程就行。

算法脚本

#!/usr/bin/python
# -*- coding:utf-8 -*-

import itertools

array = [0, 1, 2, 3, 4, 5, 6, 7]

list_all = list(itertools.permutations(array, 8))

for v in list_all:
    if( v[0] + v[7] == 5 and
        v[1] + v[6] == 12 and
        v[0] < v[7]):
        end = False
        for i in range(1, 8):
            if(end):
                break

            j = 0
            while(j < i):
                if( i - j == v[j] - v[i] or 
                    i - j == v[i] - v[j]):
                    end = True
                    break
                j += 1

        if(end == False):
            print(v)

            output = ''
            for value in v:
                output += str(value + 1)

            print(output)
            # exit(0)

运行实例

ex@Ex:~/test$ python main.py 
(2, 5, 1, 6, 4, 0, 7, 3)
36275184

所以说后面的八个字符就是36275184,结合前面的字符,那么flag就是1234567836275184了。

总结

算法是非常重要的,以后一定要好好学习算法。