【Android 逆向】【攻防世界】黑客精神

1. apk 安装到手机,提示输入注册码

2. jadx打开apk

MainActivity.java

 @Override // android.app.Activity
    public void onCreate(Bundle savedInstanceState) {
        String str2;
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d("com.gdufs.xman m=", "Xman");
        MyApp myApp = (MyApp) getApplication();
        int m = MyApp.m;
        if (m == 0) {
            str2 = "未注册";
        } else if (m == 1) {
            str2 = "已注册";
        } else {
            str2 = "已混乱";
        }
        setTitle("Xman" + str2);
        this.btn1 = (Button) findViewById(R.id.button1);
        this.btn1.setOnClickListener(new View.OnClickListener() { // from class: com.gdufs.xman.MainActivity.1
            @Override // android.view.View.OnClickListener
            public void onClick(View v) {
                MyApp myApp2 = (MyApp) MainActivity.this.getApplication();
                int m2 = MyApp.m;
                if (m2 == 0) {
                    MainActivity.this.doRegister();
                    return;
                }
                ((MyApp) MainActivity.this.getApplication()).work();
                Toast.makeText(MainActivity.this.getApplicationContext(), MainActivity.workString, 0).show();
            }
        });
    }

    public void doRegister() {
        new AlertDialog.Builder(this).setTitle("注册").setMessage("Flag就在前方!").setPositiveButton("注册", new DialogInterface.OnClickListener() { // from class: com.gdufs.xman.MainActivity.3
            @Override // android.content.DialogInterface.OnClickListener
            public void onClick(DialogInterface dialog, int which) {
                Intent intent = new Intent();
                ComponentName cpname = new ComponentName(BuildConfig.APPLICATION_ID, "com.gdufs.xman.RegActivity");
                intent.setComponent(cpname);
                MainActivity.this.startActivity(intent);
                MainActivity.this.finish();
            }
        }).setNegativeButton("不玩了", new DialogInterface.OnClickListener() { // from class: com.gdufs.xman.MainActivity.2
            @Override // android.content.DialogInterface.OnClickListener
            public void onClick(DialogInterface dialog, int which) {
                Process.killProcess(Process.myPid());
            }
        }).show();
    }

    public void work(String str) {
        workString = str;

MyApp.java

public class MyApp extends Application {
    public static int m = 0;

    public native void initSN();

    public native void saveSN(String str);

    public native void work();

    static {
        System.loadLibrary("myjni");
    }

    @Override // android.app.Application
    public void onCreate() {
        initSN();
        Log.d("com.gdufs.xman m=", String.valueOf(m));
        super.onCreate();
    }
}

大概理了一下逻辑,App 先执行 initSN ,然后点击界面按钮,按钮根据APP.m的值判断是走RegActivity还是往下执行App.work(),然后弹出toast

3. IDA 打开so看看

函数都是JNI_OnLoad中注册的

int __fastcall initSN(JNIEnv *env)
{
  FILE *v2; // r0
  FILE *v3; // r4
  JNIEnv *t_env; // r0
  int file_len; // r7
  void *v6; // r5
  JNIEnv *tt_env; // r0
  int v9; // r1

  v2 = fopen("/sdcard/reg.dat", "r+");
  v3 = v2;
  if ( !v2 )
  {
    t_env = env;
    return setValue(t_env, 0);
  }
  fseek(v2, 0, 2);
  file_len = ftell(v3);
  v6 = malloc(file_len + 1);
  if ( !v6 )
  {
    fclose(v3);
    t_env = env;
    return setValue(t_env, 0);
  }
  fseek(v3, 0, 0);
  fread(v6, file_len, '\x01', v3);
  *((_BYTE *)v6 + file_len) = 0;
  if ( !strcmp((const char *)v6, "EoPAoY62@ElRD") )
  {
    tt_env = env;
    v9 = 1;
  }
  else
  {
    tt_env = env;
    v9 = 0;
  }
  setValue(tt_env, v9);
  return j_fclose(v3);
}

initSn 大概逻辑,就是读一个文件,看文件的值是不是EoPAoY62@ElRD,是就反射写App.m = 1 其他情况写0;(得到关键信息EoPAoY62@ElRD)

jmethodID __fastcall n3Work(JNIEnv *env)
{
  jint Value; // r0
  JNIEnv *t_env; // r0
  const char *v4; // r1
  bool v5; // zf

  initSN(env);
  Value = getValue(env);
  if ( Value )
  {
    v5 = Value == 1;
    t_env = env;
    if ( v5 )
      v4 = "输入即是flag,格式为xman{……}!";
    else
      v4 = "状态不太对。。";
  }
  else
  {
    t_env = env;
    v4 = "还不行呢!";
  }
  return callWork(t_env, (int)v4);
}

work的逻辑就是执行以下initSN 然后就判断m的值是不是1,如果是1,就输出输入即是flag,格式为xman{……}!

int __fastcall n2_saveSn(JNIEnv *env, jobject a2, jstring snKey)
{
  FILE *v5_fp; // r7
  _DWORD *p_v21; // r4
  const char *v8; // r3
  int v9; // r0
  int v10; // r1
  _WORD *v11; // r5
  JNIEnv *v12; // r0
  int v13; // r4
  JNIEnv v14; // r3
  signed int v15_i; // r6
  const char *t_snKey_chars; // r9
  char *p_snKey_chars; // r5
  signed int v18_len; // r10
  char v19; // r2
  char v20; // r3
  _BYTE v21[56]; // [sp+0h] [bp-38h] BYREF

  v5_fp = fopen("/sdcard/reg.dat", "w+");
  if ( !v5_fp )
    return j___android_log_print(3, "com.gdufs.xman", byte_D0C96DCA);
  p_v21 = v21;
  v8 = "W3_arE_whO_we_ARE";
  do
  {
    v9 = *(_DWORD *)v8;
    v8 += 8;
    v10 = *((_DWORD *)v8 - 1);
    *p_v21 = v9;
    p_v21[1] = v10;
    v11 = p_v21 + 2;
    p_v21 += 2;
  }
  while ( v8 != "E" );
  v12 = env;
  v13 = 2016;
  *v11 = *(_WORD *)v8;
  v14 = *env;
  v15_i = 0;
  t_snKey_chars = v14->GetStringUTFChars(v12, snKey, 0);
  p_snKey_chars = (char *)t_snKey_chars;
  v18_len = strlen(t_snKey_chars);
  while ( v15_i < v18_len )
  {
    if ( v15_i % 3 == 1 )
    {
      v13 = (v13 + 5) % 16;
      v19 = v21[v13 + 1];                       // v21[v13 - 23]
    }
    else if ( v15_i % 3 == 2 )
    {
      v13 = (v13 + 7) % 15;
      v19 = v21[v13 + 2];
    }
    else
    {
      v13 = (v13 + 3) % 13;
      v19 = v21[v13 + 3];                       // v21[v13 -21]
    }
    v20 = *p_snKey_chars;
    ++v15_i;
    *p_snKey_chars++ = v20 ^ v19;
  }
  fputs(t_snKey_chars, v5_fp);
  return j_fclose(v5_fp);
}

里面有两个重要逻辑,

  1. 算出初始密码
  v8 = "W3_arE_whO_we_ARE";
 do
 {
   v9 = *(_DWORD *)v8;
   v8 += 8;
   v10 = *((_DWORD *)v8 - 1);
   *p_v21 = v9;
   p_v21[1] = v10;
   v11 = p_v21 + 2;
   p_v21 += 2;
 }
 while ( v8 != "E" );
 v12 = env;
 v13 = 2016;
这里有个重要细节,这里卡了我很久: (_DWORD *)v8 相当于把v8 转为一个 _DWORD * 指针,对这种指针取值时 一次取四个字节的数据, *((_DWORD *)v8 - 1) 相当于指针值 - 4 后再取四个字节的数据

转换位python为

v8 = 'W3_arE_whO_we_ARE'

i = 0
key_v8 = v8[0]
ret = ''
while (v8[i] != 'E'):
   v9 = v8[i:i+4]
   i += 8
   v10 = v8[i-4:i]
   ret += v9
   ret += v10

ret+='E'
print(ret)

# 输出还是 W3_arE_whO_we_ARE,开始觉得搞错了,其实时障眼法

然后时加密计算逻辑

v18_len = strlen(t_snKey_chars);
 while ( v15_i < v18_len )
 {
   if ( v15_i % 3 == 1 )
   {
     v13 = (v13 + 5) % 16;
     v19 = v21[v13 + 1];                       // v21[v13 - 23]
   }
   else if ( v15_i % 3 == 2 )
   {
     v13 = (v13 + 7) % 15;
     v19 = v21[v13 + 2];
   }
   else
   {
     v13 = (v13 + 3) % 13;
     v19 = v21[v13 + 3];                       // v21[v13 -21]
   }
   v20 = *p_snKey_chars;
   ++v15_i;
   *p_snKey_chars++ = v20 ^ v19;
 }

从算法上看时不用细看时怎么操作的,可以发现输入和输出时一一对应的,且密钥也是一一对应的,那么把输出当作输入,就可以得到原来的输入
python还原

v8 = 'W3_arE_whO_we_ARE'

i = 0
key_v8 = v8[0]
ret = ''
while (v8[i] != 'E'):
   v9 = v8[i:i+4]
   i += 8
   v10 = v8[i-4:i]
   ret += v9
   ret += v10

ret+='E'
print(ret)

in_str = 'EoPAoY62@ElRD'
v13 = 2016
result = ''
for i in range(0, len(in_str)):
   if (i % 3 == 1):
       v13 = (v13 + 5) % 16
       v19 = ret[v13+1]
   elif i % 3 == 2:
       v13 = (v13 + 7) % 15
       v19 = ret[v13+2]
   else:
       v13 = (v13 + 3) % 13
       v19 = ret[v13+3]
   v20 = in_str[i]
   result += chr(ord(v20) ^ ord(v19))

print(result)

# 日志
W3_arE_whO_we_ARE
201608Am!2333

成功得到flag

posted @ 2023-03-20 17:03  明月照江江  阅读(95)  评论(0编辑  收藏  举报