第二次参加领红包活动,这次有两题是 apk。

【春节】解题领红包之一

公众号回复直接得到口令:

【春节】解题领红包之二

查壳发现有 ASPack 壳,直接上 ESP 定律把壳脱掉:

然后用 OD 看一下 dump 下来的程序,先搜索字符串,可以看到输入正确后返回的字符串:

然后定位到具体的函数位置,看下来感觉有点复杂,但大概可以看到涉及到了三个字符串,可以判断出是类似 MD5 的哈希摘要:

00617D34   .  8D4D F0       LEA ECX,DWORD PTR SS:[EBP-0x10]
00617D37   .  8B15 04786200 MOV EDX,DWORD PTR DS:[0x627804]   ;  dumped_.0062B6AC
00617D3D   .  8B12          MOV EDX,DWORD PTR DS:[EDX]
00617D3F   .  8D45 FC       LEA EAX,DWORD PTR SS:[EBP-0x4]
00617D42   .  E8 597DE1FF   CALL dumped_.0042FAA0
00617D47   .  8B45 F0       MOV EAX,DWORD PTR SS:[EBP-0x10]
00617D4A   .  BA B87E6100   MOV EDX,dumped_.00617EB8          ;  E7EE5F4653E31955CACC7CD68E2A7839
00617D4F   .  E8 A42DDFFF   CALL dumped_.0040AAF8
00617D54   .  0F9445 E7     SETE BYTE PTR SS:[EBP-0x19]
00617D58   .  33C0          XOR EAX,EAX
00617D5A   .  5A            POP EDX
00617D5B   .  59            POP ECX
00617D5C   .  59            POP ECX
00617D5D   .  64:8910       MOV DWORD PTR FS:[EAX],EDX
00617D60   .  68 757D6100   PUSH dumped_.00617D75
00617D65   >  8D45 F0       LEA EAX,DWORD PTR SS:[EBP-0x10]
00617D68   .  E8 4F1FDFFF   CALL dumped_.00409CBC
00617D6D   .  C3            RETN
00617D6E   .- E9 6515DFFF   JMP dumped_.004092D8
00617D73   .^ EB F0         JMP SHORT dumped_.00617D65
00617D75   .  807D E7 00    CMP BYTE PTR SS:[EBP-0x19],0x0
00617D79   .  74 57         JE SHORT dumped_.00617DD2
00617D7B   .  33C0          XOR EAX,EAX
00617D7D   .  55            PUSH EBP
00617D7E   .  68 CB7D6100   PUSH dumped_.00617DCB
00617D83   .  64:FF30       PUSH DWORD PTR FS:[EAX]
00617D86   .  64:8920       MOV DWORD PTR FS:[EAX],ESP
00617D89   .  8D45 EC       LEA EAX,DWORD PTR SS:[EBP-0x14]
00617D8C   .  E8 2B1FDFFF   CALL dumped_.00409CBC
00617D91   .  8D4D EC       LEA ECX,DWORD PTR SS:[EBP-0x14]
00617D94   .  8B15 04786200 MOV EDX,DWORD PTR DS:[0x627804]   ;  dumped_.0062B6AC
00617D9A   .  8B12          MOV EDX,DWORD PTR DS:[EDX]
00617D9C   .  8D45 F8       LEA EAX,DWORD PTR SS:[EBP-0x8]
00617D9F   .  E8 7C7CE1FF   CALL dumped_.0042FA20
00617DA4   .  8B45 EC       MOV EAX,DWORD PTR SS:[EBP-0x14]
00617DA7   .  BA 087F6100   MOV EDX,dumped_.00617F08          ;  ea6b2efbdd4255a9f1b3bbc6399b58f4
00617DAC   .  E8 472DDFFF   CALL dumped_.0040AAF8
00617DB1   .  0F9445 E6     SETE BYTE PTR SS:[EBP-0x1A]
00617DB5   .  33C0          XOR EAX,EAX
00617DB7   .  5A            POP EDX
00617DB8   .  59            POP ECX
00617DB9   .  59            POP ECX
00617DBA   .  64:8910       MOV DWORD PTR FS:[EAX],EDX
00617DBD   .  68 D67D6100   PUSH dumped_.00617DD6
00617DC2   >  8D45 EC       LEA EAX,DWORD PTR SS:[EBP-0x14]
00617DC5   .  E8 F21EDFFF   CALL dumped_.00409CBC
00617DCA   .  C3            RETN
00617DCB   .- E9 0815DFFF   JMP dumped_.004092D8
00617DD0   .^ EB F0         JMP SHORT dumped_.00617DC2
00617DD2   >  C645 E6 00    MOV BYTE PTR SS:[EBP-0x1A],0x0
00617DD6   .  807D E6 00    CMP BYTE PTR SS:[EBP-0x1A],0x0
00617DDA   .  74 6D         JE SHORT dumped_.00617E49
00617DDC   .  33C0          XOR EAX,EAX
00617DDE   .  55            PUSH EBP
00617DDF   .  68 2C7E6100   PUSH dumped_.00617E2C
00617DE4   .  64:FF30       PUSH DWORD PTR FS:[EAX]
00617DE7   .  64:8920       MOV DWORD PTR FS:[EAX],ESP
00617DEA   .  8D45 E8       LEA EAX,DWORD PTR SS:[EBP-0x18]
00617DED   .  E8 CA1EDFFF   CALL dumped_.00409CBC
00617DF2   .  8D4D E8       LEA ECX,DWORD PTR SS:[EBP-0x18]
00617DF5   .  8B15 04786200 MOV EDX,DWORD PTR DS:[0x627804]   ;  dumped_.0062B6AC
00617DFB   .  8B12          MOV EDX,DWORD PTR DS:[EDX]
00617DFD   .  8D45 F4       LEA EAX,DWORD PTR SS:[EBP-0xC]
00617E00   .  E8 1B7CE1FF   CALL dumped_.0042FA20
00617E05   .  8B45 E8       MOV EAX,DWORD PTR SS:[EBP-0x18]
00617E08   .  BA 587F6100   MOV EDX,dumped_.00617F58          ;  c8d46d341bea4fd5bff866a65ff8aea9
00617E0D   .  E8 E62CDFFF   CALL dumped_.0040AAF8
00617E12   .  0F9445 E5     SETE BYTE PTR SS:[EBP-0x1B]
00617E16   .  33C0          XOR EAX,EAX
00617E18   .  5A            POP EDX
00617E19   .  59            POP ECX
00617E1A   .  59            POP ECX
00617E1B   .  64:8910       MOV DWORD PTR FS:[EAX],EDX
00617E1E   .  68 337E6100   PUSH dumped_.00617E33
00617E23   >  8D45 E8       LEA EAX,DWORD PTR SS:[EBP-0x18]
00617E26   .  E8 911EDFFF   CALL dumped_.00409CBC
00617E2B   .  C3            RETN
00617E2C   .- E9 A714DFFF   JMP dumped_.004092D8
00617E31   .^ EB F0         JMP SHORT dumped_.00617E23
00617E33   .  807D E5 00    CMP BYTE PTR SS:[EBP-0x1B],0x0
00617E37   .  74 10         JE SHORT dumped_.00617E49
00617E39   .  83C9 FF       OR ECX,-0x1
00617E3C   .  83CA FF       OR EDX,-0x1
00617E3F   .  B8 A87F6100   MOV EAX,dumped_.00617FA8          ;  请把答案回复到论坛公众号!
00617E44   .  E8 236BF5FF   CALL dumped_.0056E96C

抛弃 OD,把 dump 下来的程序导入 IDA,根据前面找到的 unicode 字符串定位到函数,反编译后得到如下代码:

int __fastcall TForm1_edtPwdChange(int a1)
{
  int v1; // ebx
  int v2; // edx
  int len; // eax
  int md5Handler; // esi
  char v5; // zf
  unsigned int v7; // [esp-18h] [ebp-58h]
  int *v8; // [esp-14h] [ebp-54h]
  char *v9; // [esp-10h] [ebp-50h]
  unsigned int v10; // [esp-Ch] [ebp-4Ch]
  void *v11; // [esp-8h] [ebp-48h]
  int *v12; // [esp-4h] [ebp-44h]
  int v13; // [esp+8h] [ebp-38h]
  int v14; // [esp+Ch] [ebp-34h]
  int v15; // [esp+10h] [ebp-30h]
  int v16; // [esp+14h] [ebp-2Ch]
  int v17; // [esp+18h] [ebp-28h]
  int v18; // [esp+1Ch] [ebp-24h]
  int v19; // [esp+20h] [ebp-20h]
  char v20; // [esp+25h] [ebp-1Bh]
  char v21; // [esp+26h] [ebp-1Ah]
  char v22; // [esp+27h] [ebp-19h]
  char *string3; // [esp+28h] [ebp-18h]
  char *string2; // [esp+2Ch] [ebp-14h]
  char *string1; // [esp+30h] [ebp-10h]
  char s3; // [esp+34h] [ebp-Ch]
  char s2; // [esp+38h] [ebp-8h]
  char s1; // [esp+3Ch] [ebp-4h]
  int savedregs; // [esp+40h] [ebp+0h]

  v1 = a1;
  v12 = &savedregs;
  v11 = &loc_617E9C;
  v10 = __readfsdword(0);
  __writefsdword(0, (unsigned int)&v10);
  sub_541DB8(*(Controls::TControl **)(a1 + 976), &v19);
  len = v19;
  if ( v19 )
    len = *(_DWORD *)(v19 - 4);
  if ( len == 15 )                              // input's length should be 15
  {
    LOBYTE(v2) = 1;
    md5Handler = sub_616B84(&cls_IdHashMessageDigest_TIdHashMessageDigest5, v2);// get MD5 handler
    v9 = &s1;
    sub_541DB8(*(Controls::TControl **)(v1 + 976), &v17);
    sub_50F2EC(v17, 7, &v18);
    registerFunc(md5Handler, v18, 0, (int)&s1);
    v9 = &s2;
    v8 = &v16;
    sub_541DB8(*(Controls::TControl **)(v1 + 976), &v15);
    Compprod::TComponentsPageProducer::HandleTag(&v16);
    registerFunc(md5Handler, v16, 0, (int)&s2);
    v9 = &s3;
    sub_541DB8(*(Controls::TControl **)(v1 + 976), &v13);
    unknown_libname_807(v13, 4, &v14);
    registerFunc(md5Handler, v14, 0, (int)&s3);
    v9 = (char *)&savedregs;
    v8 = (int *)&loc_617D6E;
    v7 = __readfsdword(0);
    __writefsdword(0, (unsigned int)&v7);
    freeMem(&string1);
    sub_42FAA0((int *)&s1, 0, (int *)&string1);
    compareStr(string1, (char *)L"E7EE5F4653E31955CACC7CD68E2A7839");// compare string1
    v22 = v5;
    __writefsdword(0, v7);
    v9 = (char *)&loc_617D75;
    freeMem(&string1);
    if ( v22 )
    {
      v9 = (char *)&savedregs;
      v8 = (int *)&loc_617DCB;
      v7 = __readfsdword(0);
      __writefsdword(0, (unsigned int)&v7);
      freeMem(&string2);
      sub_42FA20(&s2, 0, &string2);
      compareStr(string2, (char *)L"ea6b2efbdd4255a9f1b3bbc6399b58f4");// compare string2
      v21 = v5;
      __writefsdword(0, v7);
      v9 = (char *)&loc_617DD6;
      freeMem(&string2);
    }
    else
    {
      v21 = 0;
    }
    if ( v21 )
    {
      v9 = (char *)&savedregs;
      v8 = (int *)&loc_617E2C;
      v7 = __readfsdword(0);
      __writefsdword(0, (unsigned int)&v7);
      freeMem(&string3);
      sub_42FA20(&s3, 0, &string3);
      compareStr(string3, (char *)L"c8d46d341bea4fd5bff866a65ff8aea9");// compare string3
      v20 = v5;
      __writefsdword(0, v7);
      v9 = (char *)&loc_617E33;
      freeMem(&string3);
      if ( v20 )                                // Success
        createDialog((int)L"请把答案回复到论坛公众号!", -1, -1);
    }
  }
  __writefsdword(0, v10);
  v12 = (int *)&loc_617EA3;
  freeMem(&v13);
  freeMem(&v14);
  freeMem(&v15);
  freeMem(&v16);
  freeMem(&v17);
  freeMem(&v18);
  freeMem(&v19);
  return sub_409D1C(&string3, 6);
}

反编译后的代码也比较含糊,但可以猜到,输入的字符串长度为 15,字符串被分成了 3 部分,每部分分别进行 MD5 哈希,并与内存中的字符串进行比较,字符串正确就会弹出一个正确的对话窗口。MD5 在理论上是不可逆的,但可以在通过搜索引擎查找网上已经被爆破出的对应的明文。第一部分的解密结果:

第二部分的解密结果:

第三部分的解密结果:

将得到的字符串拼接并进行验证:

回复公众号得到口令

【春节】解题领红包之三

这题给的是一个 apk,先使用 jdax 打开,查看程序入口点 MainActivity,得到如下代码:

package com.wuaipojie.crackme01;

import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity implements OnClickListener {
    private Button btn_click;
    private EditText editText;

    private native boolean checkFlag(String str);

    public native void onClick(View view);

    static {
        /* JADX: method processing error */
/*
Error: java.lang.NullPointerException
    at jadx.core.dex.visitors.regions.ProcessTryCatchRegions.searchTryCatchDominators(ProcessTryCatchRegions.java:75)
    at jadx.core.dex.visitors.regions.ProcessTryCatchRegions.process(ProcessTryCatchRegions.java:45)
    at jadx.core.dex.visitors.regions.RegionMakerVisitor.postProcessRegions(RegionMakerVisitor.java:63)
    at jadx.core.dex.visitors.regions.RegionMakerVisitor.visit(RegionMakerVisitor.java:58)
    at jadx.core.dex.visitors.DepthTraversal.visit(DepthTraversal.java:31)
    at jadx.core.dex.visitors.DepthTraversal.visit(DepthTraversal.java:17)
    at jadx.core.ProcessClass.process(ProcessClass.java:37)
    at jadx.api.JadxDecompiler.processClass(JadxDecompiler.java:280)
    at jadx.api.JavaClass.decompile(JavaClass.java:62)
*/
        /*
        r0 = "crack_j2c";     Catch:{ UnsatisfiedLinkError -> 0x0005 }
        java.lang.System.loadLibrary(r0);     Catch:{ UnsatisfiedLinkError -> 0x0005 }
    L_0x0005:
        return;
        */
        throw new UnsupportedOperationException("Method not decompiled: com.wuaipojie.crackme01.MainActivity.<clinit>():void");
    }

    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView((int) R.layout.activity_main);
        this.editText = (EditText) findViewById(R.id.input_flag);
        Button button = (Button) findViewById(R.id.button);
        this.btn_click = button;
        button.setOnClickListener(this);
    }
}

主要有三个函数,onCreate() 在 Java 层中实现,可以看出整个界面中有一个文本框和一个按钮,并设置了一个按钮的监听事件,即 onClick;onClick()checkFlag() 可以看到是在 Native 层进行实现的,可以从 lib 文件夹中找到 so 文件,接下来用 IDA 对 so 中的两个函数进行分析。导入 IDA 后,通过函数名可以看出两个函数通过静态注册:

然后先来看 onClick 函数。这边略过一些导入 jni.h 等一些分析的过程(一般静态注册函数的第一个参数是 JNIEnv 等等),在分析的过程中大致猜测出两个函数 sub_5288sub_539C 两个函数分别用来获取指定的方法(getMethod)或者是域(getField)。接下来直接来看分析过后的代码:

int __fastcall Java_com_wuaipojie_crackme01_MainActivity_onClick__Landroid_view_View_2(_JNIEnv *env, int a2, int a3)
{
  _JNIEnv *env_; // r4
  int v4; // r5
  int v5; // r9
  int v6; // r0
  int v7; // r5
  jstring (__cdecl *v8)(JNIEnv *, const char *); // r2
  int v9; // r6
  int v10; // r8
  int v11; // r6
  int v12; // r8
  int len; // r5
  int v14; // r5
  const char *v15; // r1
  int v16; // r6
  int v17; // r5
  int result; // r0
  JNINativeMethod method; // [sp+4h] [bp-74h]
  int v20; // [sp+10h] [bp-68h]
  int v21; // [sp+14h] [bp-64h]
  int v22; // [sp+18h] [bp-60h]
  int v23; // [sp+1Ch] [bp-5Ch]
  int v24; // [sp+20h] [bp-58h]
  int v25; // [sp+24h] [bp-54h]
  int a3a; // [sp+28h] [bp-50h]
  int v27; // [sp+2Ch] [bp-4Ch]
  int v28; // [sp+30h] [bp-48h]
  int v29; // [sp+34h] [bp-44h]
  int v30; // [sp+38h] [bp-40h]
  int a2a; // [sp+3Ch] [bp-3Ch]
  int v32; // [sp+40h] [bp-38h]
  int v33; // [sp+48h] [bp-30h]
  int v34; // [sp+50h] [bp-28h]
  int v35; // [sp+58h] [bp-20h]

  env_ = env;
  v4 = a3;
  a2a = 0;
  v29 = 0;
  v30 = 0;
  v27 = 0;
  v28 = 0;
  v25 = 0;
  a3a = 0;
  v23 = 0;
  v24 = 0;
  v21 = 0;
  v22 = 0;
  method.fnPtr = 0;
  v20 = 0;
  v5 = ((int (__fastcall *)(_JNIEnv *, int))env->functions->NewLocalRef)(env, a2);
  v6 = ((int (__fastcall *)(_JNIEnv *, int))env_->functions->NewLocalRef)(env_, v4);
  if ( !v5 )
    goto LABEL_38;
  v7 = v6;
  method.name = "editText";
  method.signature = "Landroid/widget/EditText;";
  if ( getFields(env_, &a2a, &a3a, 0, "com/wuaipojie/crackme01/MainActivity", method) )
    goto LABEL_39;
  v9 = ((int (__fastcall *)(_JNIEnv *, int, int))env_->functions->GetObjectField)(env_, v5, a3a);
  if ( ((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
    goto LABEL_39;
  if ( v7 )
    ((void (__fastcall *)(_JNIEnv *, int))env_->functions->DeleteLocalRef)(env_, v7);
  if ( !v9 )
    goto LABEL_38;
  if ( !v25 )
  {
    method.name = "getText";
    method.signature = "()Landroid/text/Editable;";
    if ( getMethods(env_, &v30, &v25, 0, "android/widget/EditText", method) )
      goto LABEL_39;
  }
  v10 = ((int (__fastcall *)(_JNIEnv *, int))env_->functions->CallObjectMethodA)(env_, v9);// get input string
  if ( ((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
    goto LABEL_39;
  ((void (__fastcall *)(_JNIEnv *, int))env_->functions->DeleteLocalRef)(env_, v9);
  if ( !v10 )
    goto LABEL_38;
  if ( !v24 )
  {
    method.name = "toString";
    method.signature = "()Ljava/lang/String;";
    if ( getMethods(env_, &v29, &v24, 0, "java/lang/Object", method) )
      goto LABEL_39;
  }
  v11 = ((int (__fastcall *)(_JNIEnv *, int))env_->functions->CallObjectMethodA)(env_, v10);// convert object to string
  if ( ((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
    goto LABEL_39;
  ((void (__fastcall *)(_JNIEnv *, int))env_->functions->DeleteLocalRef)(env_, v10);
  if ( !v11 )
    goto LABEL_38;
  if ( !v23 )
  {
    method.name = "trim";
    method.signature = "()Ljava/lang/String;";
    if ( getMethods(env_, &v28, &v23, 0, "java/lang/String", method) )
      goto LABEL_39;
  }
  v12 = ((int (__fastcall *)(_JNIEnv *, int))env_->functions->CallObjectMethodA)(env_, v11);// trim string
  if ( ((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
    goto LABEL_39;
  ((void (__fastcall *)(_JNIEnv *, int))env_->functions->DeleteLocalRef)(env_, v11);
  if ( !v12 )
    goto LABEL_38;
  if ( !v22 )
  {
    method.name = "length";
    method.signature = "()I";
    if ( getMethods(env_, &v28, &v22, 0, "java/lang/String", method) )
      goto LABEL_39;
  }
  len = ((int (__fastcall *)(_JNIEnv *, int))env_->functions->CallIntMethodA)(env_, v12);// get string's length
  if ( ((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
    goto LABEL_39;
  if ( len == 30 )                              // len(flag) == 30
  {
    if ( !v21 )
    {
      method.name = "checkFlag";
      method.signature = "(Ljava/lang/String;)Z";
      if ( getMethods(env_, &a2a, &v21, 0, "com/wuaipojie/crackme01/MainActivity", method) )
        goto LABEL_39;
    }
    v32 = v12;
    v14 = ((int (__fastcall *)(_JNIEnv *, int))env_->functions->CallBooleanMethodA)(env_, v5);// invoke checkFlag method
    if ( ((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
      goto LABEL_39;
    ((void (__fastcall *)(_JNIEnv *, int))env_->functions->DeleteLocalRef)(env_, v12);
    v8 = env_->functions->NewStringUTF;
    if ( !v14 )
      goto LABEL_40;
    v15 = "正确!!!回复你输入的内容到吾爱破解论坛公众号";            // correct
  }
  else
  {
    ((void (__fastcall *)(_JNIEnv *, int))env_->functions->DeleteLocalRef)(env_, v12);
    v15 = "flag长度必须为30位";                       // flag's length must equal to 30
    v8 = env_->functions->NewStringUTF;
  }
  while ( 1 )
  {
    v16 = ((int (__fastcall *)(_JNIEnv *, const char *))v8)(env_, v15);
    if ( v20
      || (method.name = "makeText",
          method.signature = "(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;",
          !getMethods(env_, &v27, &v20, 1, "android/widget/Toast", method)) )
    {
      v33 = v16;
      v32 = v5;
      v34 = 0;
      v17 = ((int (__fastcall *)(_JNIEnv *, int))env_->functions->CallStaticObjectMethodA)(env_, v27);
      if ( !((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
      {
        if ( v16 )
          ((void (__fastcall *)(_JNIEnv *, int))env_->functions->DeleteLocalRef)(env_, v16);
        if ( v17 )
        {
          if ( method.fnPtr
            || (method.name = "show",
                method.signature = "()V",
                !getMethods(env_, &v27, (int *)&method.fnPtr, 0, "android/widget/Toast", method)) )
          {
            ((void (__fastcall *)(_JNIEnv *, int))env_->functions->CallVoidMethodA)(env_, v17);
            ((void (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_);
          }
        }
        else
        {
LABEL_38:
          sub_4EC0(env_, "java/lang/NullPointerException", "NullPointerException");
        }
      }
    }
LABEL_39:
    result = _stack_chk_guard - v35;
    if ( _stack_chk_guard == v35 )
      break;
LABEL_40:
    v15 = "验证错误,继续加油";                          // wrong
  }
  return result;
}

onClick 函数中的内容主要为在点击按钮后获取输入内容,并判断输入的字符串长度是否为 30,然后调用 checkFlag 函数对字符串进行判断。接下来再看看 checkFlag 函数,这个函数比较长,分成几段来看。首先调用了 isDebuggerConnected() 函数,猜测应该是用来反调试:

  method1.name = "isDebuggerConnected";
  method1.signature = "()Z";
  if ( !getMethods(env_, &jclass, &jmethodid, 1, "android/os/Debug", method1) )// anti-debug??
  {
    t1 = (unsigned int)&t_;
    v8 = ((int (__fastcall *)(_JNIEnv *, int, int, int *))env_->functions->CallStaticBooleanMethodA)(
           env_,
           jclass,
           jmethodid,
           &t_);
    if ( !(((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) | v8) )
    {
      key1 = ((int (__fastcall *)(_JNIEnv *, signed int))env_->functions->NewByteArray)(env_, 9);
      if ( !((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
        goto LABEL_7;
    }
    goto LABEL_4;
  }

接下来将一串字符串分成三部分(key1 = “thisiskey”,key2 = “52pojie_2020_happy_chinese_new_year”,key3 = “20200125”)并分给了三个变量:

    v6 = 0;
    ((void (__fastcall *)(_JNIEnv *, int, _DWORD, signed int, const char *))env_->functions->SetByteArrayRegion)(
      env_,
      key1,
      0,
      9,
      "thisiskey52pojie_2020_happy_chinese_new_year20200125");// key1 = "thisiskey"
    key2 = ((int (__fastcall *)(_JNIEnv *, signed int))env_->functions->NewByteArray)(env_, 35);
    if ( !((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
    {
      v6 = 0;
      ((void (__fastcall *)(_JNIEnv *, int, _DWORD, signed int, char *))env_->functions->SetByteArrayRegion)(
        env_,
        key2,
        0,
        35,
        "52pojie_2020_happy_chinese_new_year20200125");// key2 = "52pojie_2020_happy_chinese_new_year"
      key3 = ((int (__fastcall *)(_JNIEnv *, signed int))env_->functions->NewByteArray)(env_, 8);
      if ( !((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
      {
        v6 = 0;
        ((void (__fastcall *)(_JNIEnv *, int, _DWORD, signed int, char *))env_->functions->SetByteArrayRegion)(
          env_,
          key3,
          0,
          8,
          "20200125");                          // key3 = "20200125"

然后新建了一个 35 位的 Byte 数组,做一个循环,当 i 不为 0 且 i 是 4 的倍数时,下标设置为 (i >> 2) - 1,取 key3 中的值来 append 到数组中;反之,下标设置为 i,取 key2 中的值来 append 到数组中:

        arr = ((int (__fastcall *)(_JNIEnv *, signed int))env_->functions->NewByteArray)(env_, 35);
        if ( !((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
        {
          i = 0;
          arr_ = arr;
          key1_ = key1;
          do
          {
            if ( !i || i & 3 )
            {
              if ( !key2 )
                goto LABEL_41;
              pointer = key2;
              i_ = i;
              GetByteArrayRegion_ = env_->functions->GetByteArrayRegion;
            }
            else                                // if i != 0 and i % 4 == 0
            {
              pointer = key3;
              if ( !key3 )
                goto LABEL_41;
              GetByteArrayRegion_ = env_->functions->GetByteArrayRegion;
              i_ = (i >> 2) - 1;                // 0,1,2,3,4,5,6,7
            }
            ((void (__fastcall *)(_JNIEnv *, int, int, signed int, unsigned int))GetByteArrayRegion_)(
              env_,
              pointer,
              i_,
              1,
              t1);
            key1 = (unsigned __int8)t_;
            if ( ((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
              goto LABEL_4;
            if ( !arr_ )
            {
LABEL_41:
              sub_4EC0(env_, "java/lang/NullPointerException", "NullPointerException");
              goto LABEL_4;
            }
            LOBYTE(t_) = key1;
            ((void (__fastcall *)(_JNIEnv *, int, unsigned int, signed int, unsigned int))env_->functions->SetByteArrayRegion)(
              env_,
              arr_,
              i,
              1,
              t1);
            if ( ((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
              goto LABEL_4;
          }
          while ( i++ < 0x22 );                 // for i in range(35)

接下来对 byte 数组进行 MD5 哈希,然后取摘要生成 16 位的 byte 数组:

          md5Str = ((int (__fastcall *)(_JNIEnv *, const char *))env_->functions->NewStringUTF)(env_, "MD5");
          if ( !v47 )
          {
            method2.name = "getInstance";
            method2.signature = "(Ljava/lang/String;)Ljava/security/MessageDigest;";
            if ( getMethods(env_, &v52, &v47, 1, "java/security/MessageDigest", method2) )
              goto LABEL_88;
          }
          t_ = md5Str;
          v18 = ((int (__fastcall *)(_JNIEnv *, int))env_->functions->CallStaticObjectMethodA)(env_, v52);// md5 function
          if ( ((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
            goto LABEL_88;
          if ( md5Str )
            ((void (__fastcall *)(_JNIEnv *, int))env_->functions->DeleteLocalRef)(env_, md5Str);
          if ( !v18 )
          {
LABEL_87:
            sub_4EC0(env_, "java/lang/NullPointerException", "NullPointerException");
            goto LABEL_88;
          }
          if ( !v46 )
          {
            method2.name = "digest";
            method2.signature = "([B)[B";
            if ( getMethods(env_, &v52, &v46, 0, "java/security/MessageDigest", method2) )
              goto LABEL_88;
          }
          t_ = arr_;
          md5Digest = ((int (__fastcall *)(_JNIEnv *, int))env_->functions->CallObjectMethodA)(env_, v18);// get hash digest
          if ( ((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
            goto LABEL_88;
          ((void (__fastcall *)(_JNIEnv *, int))env_->functions->DeleteLocalRef)(env_, v18);
          if ( !md5Digest )
            goto LABEL_87;

然后做一个循环,对数组中的元素和 key1 进行逐位异或:

          len = ((int (__fastcall *)(_JNIEnv *, int))env_->functions->GetArrayLength)(env_, md5Digest);
          if ( ((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
            goto LABEL_88;
          idx = 0;
          while ( 1 )                           // for i in range(16)
          {
            t1 = 0x38E38E39 * (unsigned __int64)(unsigned int)idx >> 32;// div 9? useless
            if ( idx >= len )
              break;
            ((void (__fastcall *)(_JNIEnv *, int, int, signed int, int *))env_->functions->GetByteArrayRegion)(
              env_,
              md5Digest,
              idx,
              1,
              &t_);
            key1 = (unsigned __int8)t_;
            if ( ((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
              goto LABEL_88;
            if ( !key1_ )
              goto LABEL_87;
            ((void (__fastcall *)(_JNIEnv *, int, unsigned int, signed int, int *))env_->functions->GetByteArrayRegion)(
              env_,
              key1_,
              idx % 9u,                         // mod 9
              1,
              &t_);
            ch = t_;
            if ( !((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
            {
              LOBYTE(t_) = ch ^ key1;           // xor
              ((void (__fastcall *)(_JNIEnv *, int, int, signed int, int *))env_->functions->SetByteArrayRegion)(
                env_,
                md5Digest,
                idx,
                1,
                &t_);
              if ( !((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
              {
                len = ((int (__fastcall *)(_JNIEnv *, int))env_->functions->GetArrayLength)(env_, md5Digest);
                ++idx;
                if ( !((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
                  continue;
              }
            }
            goto LABEL_88;
          }

接下来,将得到的 byte 数组逐位转成 hex 字符串,如果小于 0xF,即只有一位,高位补 0。将结果逐位 append 到一个新的字符串中,得到一个 32 位的字符串:

          len__ = ((int (__fastcall *)(_JNIEnv *, int))env_->functions->GetArrayLength)(env_, md5Digest);
          if ( ((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
            goto LABEL_88;
          if ( len__ >= 1 )
          {
            j = 0;
            classInteger = "java/lang/Integer";
            toHexString_ = "toHexString";
            method2.fnPtr = "(I)Ljava/lang/String;";
            zeroPad = 0;
            while ( 1 )
            {
              ((void (__fastcall *)(_JNIEnv *, int, int, signed int, int *))env_->functions->GetByteArrayRegion)(
                env_,
                md5Digest,
                j,
                1,
                &t_);
              t1 = (unsigned __int8)t_;
              if ( ((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
                break;
              if ( t1 <= 0xF )                  // if x < 0xF then append a zero
              {
                if ( zeroPad )
                  ((void (__fastcall *)(_JNIEnv *, int))env_->functions->DeleteLocalRef)(env_, zeroPad);
                zeroPad = ((int (__fastcall *)(_JNIEnv *, const char *))env_->functions->NewStringUTF)(env_, "0");
                if ( !v44 )
                {
                  method2.name = "append";
                  method2.signature = "(Ljava/lang/String;)Ljava/lang/StringBuilder;";
                  if ( getMethods(env_, &v51, &v44, 0, "java/lang/StringBuilder", method2) )
                    break;
                }
                t_ = zeroPad;
                v24 = ((int (__fastcall *)(_JNIEnv *, int))env_->functions->CallObjectMethodA)(env_, v22);// append zero
                if ( ((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
                  break;
                if ( v24 )
                  ((void (__fastcall *)(_JNIEnv *, int))env_->functions->DeleteLocalRef)(env_, v24);
              }
              if ( !v43 )
              {
                *(_QWORD *)&method2.name = __PAIR__((unsigned int)method2.fnPtr, (unsigned int)toHexString_);
                if ( getMethods(env_, &v50, &v43, 1, classInteger, method2) )// get toHexString method
                  break;
              }
              t_ = t1;
              t1 = ((int (__fastcall *)(_JNIEnv *, int))env_->functions->CallStaticObjectMethodA)(env_, v50);
              if ( ((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
                break;
              if ( arr_ )
                ((void (__fastcall *)(_JNIEnv *, int))env_->functions->DeleteLocalRef)(env_, arr_);
              if ( !v44 )
              {
                method2.name = "append";
                method2.signature = "(Ljava/lang/String;)Ljava/lang/StringBuilder;";
                if ( getMethods(env_, &v51, &v44, 0, "java/lang/StringBuilder", method2) )
                  break;
              }
              t_ = t1;
              key1 = ((int (__fastcall *)(_JNIEnv *, int))env_->functions->CallObjectMethodA)(env_, v22);
              if ( ((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
                break;
              if ( key1 )
                ((void (__fastcall *)(_JNIEnv *, int))env_->functions->DeleteLocalRef)(env_, key1);
              ++j;
              arr_ = t1;
              if ( j >= len__ )
                goto LABEL_75;
            }

最后将取字符串字符串的 1~31 位作为新的字符串,并与我们的输入进行比较:

if ( !v42 )
          {
            method2.name = "toString";
            method2.signature = "()Ljava/lang/String;";
            if ( getMethods(env_, &v51, &v42, 0, "java/lang/StringBuilder", method2) )
              goto LABEL_88;
          }
          v25 = ((int (__fastcall *)(_JNIEnv *, int))env_->functions->CallObjectMethodA)(env_, v22);// convert StringBuilder to string
          if ( ((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
            goto LABEL_88;
          ((void (__fastcall *)(_JNIEnv *, int))env_->functions->DeleteLocalRef)(env_, v22);
          if ( !v25 )
            goto LABEL_87;
          if ( !v41 )
          {
            method2.name = "substring";
            method2.signature = "(II)Ljava/lang/String;";
            if ( getMethods(env_, &v49, &v41, 0, "java/lang/String", method2) )
              goto LABEL_88;
          }
          v55 = 31;                             // slice 1-31
          t_ = 1;
          v26 = ((int (__fastcall *)(_JNIEnv *, int))env_->functions->CallObjectMethodA)(env_, v25);
          if ( ((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
            goto LABEL_88;
          ((void (__fastcall *)(_JNIEnv *, int))env_->functions->DeleteLocalRef)(env_, v25);
          if ( !v26 )
            goto LABEL_87;
          if ( !v40 )
          {
            method2.name = "equals";
            method2.signature = "(Ljava/lang/Object;)Z";
            if ( getMethods(env_, &v49, &v40, 0, "java/lang/String", method2) )
              goto LABEL_88;
          }
          t_ = v39;
          v6 = ((int (__fastcall *)(_JNIEnv *, int))env_->functions->CallBooleanMethodA)(env_, v26);// compare string
          if ( ((int (__fastcall *)(_JNIEnv *))env_->functions->ExceptionCheck)(env_) )
            goto LABEL_88;

只需要正向地实现就能得到对应的字符串,我这里用 Python 实现了一下:

#!/usr/bin/env python
import hashlib

key = 'thisiskey52pojie_2020_happy_chinese_new_year20200125'
key1 = key[:0x7D-0x74]
key2 = key[0x7D-0x74:0xA0-0x74]
key3 = key[0xA0-0x74:]

arr = ''
for i in range(35):
    if not i or i & 3:
        arr += key2[i]
    else:
        arr += key3[(i >> 2) - 1]
print arr

md5str = hashlib.md5(arr).digest()
print md5str.encode('hex')

xorlist = []
for i in range(16):
    xorlist.append(ord(key1[i % 9]) ^ ord(md5str[i]))
print xorlist

flag = ''
for i in range(16):
    flag += hex(xorlist[i])[2:].zfill(2)
print flag
flag = flag[1:31]
print flag
assert len(flag) == 30

跑出来后在手机上验证一下结果的正确性:

回复公众号得到口令:

【春节】解题领红包之四

用 jadx 分析,在 onCreate() 函数中,需要输入 uid 和目标字符串,其中 uid 用 0 补齐成 7 位。然后调用在 Native 层实现的 checkSn() 函数:

package com.wuaipojie.crackme02;

import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    private Button btn;
    private EditText input_flag;
    private EditText input_uid;

    public native boolean checkSn(String str, String str2);

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

    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView((int) R.layout.activity_main);
        this.input_uid = (EditText) findViewById(R.id.input_uid);
        this.input_flag = (EditText) findViewById(R.id.input_flag);
        Button button = (Button) findViewById(R.id.button);
        this.btn = button;
        button.setOnClickListener(new OnClickListener() {
            public void onClick(View view) {
                view = MainActivity.this.input_uid.getText().toString().trim();
                if (view.length() < 7) {
                    int length = 7 - view.length();
                    StringBuilder stringBuilder = new StringBuilder();
                    for (int i = 0; i < length; i++) {
                        stringBuilder.append("0");
                    }
                    StringBuilder stringBuilder2 = new StringBuilder();
                    stringBuilder2.append(stringBuilder.toString());
                    stringBuilder2.append(view);
                    view = stringBuilder2.toString();
                }
                MainActivity mainActivity = MainActivity.this;
                if (mainActivity.checkSn(view, mainActivity.input_flag.getText().toString().trim()) != null) {
                    Toast.makeText(MainActivity.this, "\u6b63\u786e\uff01\uff01\uff01\u8bf7\u628a\u7ed3\u679c\u76f4\u63a5\u63d0\u4ea4\u5230\u8bba\u575b\u9886\u53d6\u5956\u52b1\u5427", 0).show();
                } else {
                    Toast.makeText(MainActivity.this, "\u9a8c\u8bc1\u9519\u8bef\uff0c\u7ee7\u7eed\u52a0\u6cb9", 0).show();
                }
            }
        });
    }
}

把 unicode 输出,可以看到通过了 checkSn 的检测就成功了:

In [1]: print u"\u6b63\u786e\uff01\uff01\uff01\u8bf7\u628a\u7ed3\u679c\u76f4\u63a5\u63d0\u4ea4\u5230\u8bba\u575b\u9886\u53d6\u5956\u52b1\u5427"
正确!!!请把结果直接提交到论坛领取奖励吧

In [2]: print u"\u9a8c\u8bc1\u9519\u8bef\uff0c\u7ee7\u7eed\u52a0\u6cb9"
验证错误,继续加油

接下来打开 IDA 分析一下 so 文件。没有找到 checkSn 函数,基本上都被混淆了,说明函数是被动态注册的。JNI_OnLoad 的部分:

void __fastcall __noreturn JNI_OnLoad(_JavaVM *vm)
{
  _JavaVM *vm_; // r11
  signed int v2; // r2
  int v3; // lr
  int (__fastcall *v4)(_JavaVM *, int *, signed int); // r4
  int v5; // lr
  signed int i; // r12
  int v7; // [sp+8h] [bp-C0h]
  int *v8; // [sp+10h] [bp-B8h]
  int v9; // [sp+18h] [bp-B0h]
  int v10; // [sp+20h] [bp-A8h]
  int v11; // [sp+28h] [bp-A0h]
  int v12; // [sp+30h] [bp-98h]
  int *v13; // [sp+38h] [bp-90h]
  int v14; // [sp+40h] [bp-88h]
  signed int v15; // [sp+48h] [bp-80h]
  int v16; // [sp+50h] [bp-78h]
  int *v17; // [sp+7Ch] [bp-4Ch]
  void *v18; // [sp+80h] [bp-48h]
  int *v19; // [sp+84h] [bp-44h]
  int *v20; // [sp+88h] [bp-40h]
  int *v21; // [sp+8Ch] [bp-3Ch]
  int *v22; // [sp+90h] [bp-38h]
  int *v23; // [sp+94h] [bp-34h]
  int *v24; // [sp+98h] [bp-30h]
  void **v25; // [sp+9Ch] [bp-2Ch]
  signed int *v26; // [sp+A0h] [bp-28h]

  vm_ = vm;
  v23 = &Oo0O_8;
  v24 = &O0OO_9;
  v18 = &_stack_chk_guard;
  v2 = 0;
  if ( O0OO_9 < 10 )
    v2 = 1;
  v3 = v2 | ~((Oo0O_8 - 1) * Oo0O_8) & 1;
  if ( v3 != 1 )
    goto LABEL_5;
  while ( 1 )
  {
    v26 = &v15;
    v15 = 0x58D3C185;
    v12 = 0x58D3C185;
    v13 = &v10;
    if ( v3 )
    {
      v22 = &v11;
      v17 = (int *)&v13;
      v20 = &v16;
      v21 = &v12;
      v4 = (int (__fastcall *)(_JavaVM *, int *, signed int))(*((_DWORD *)off_1D7B0 + 0x1FEC2733) - 0x58D3C184);
      v14 = v4(vm, &v10, 0x10004);
      v5 = *v23;
      for ( i = *v24; *v24 >= 10 && ((_BYTE)v5 - 1) * (_BYTE)v5 & 1; i = *v24 )
      {
        v14 = v4(vm_, &v10, 0x10004);
        v14 = v4(vm_, &v10, 0x10004);
        v5 = *v23;
      }
      v19 = &v14;
      v25 = &off_1D7B0;
      if ( i < 10 || !(((_BYTE)v5 - 1) * (_BYTE)v5 & 1) )
        JUMPOUT(__CS__, *((_DWORD *)off_1D7B4 + 0x1FEC27A2) - 0x49F16B40);
      while ( 1 )
        ;
    }
LABEL_5:
    v9 = 0x58D3C185;
    v8 = &v7;
  }
}

References

https://www.52pojie.cn/thread-732955-1-1.html
https://www.52pojie.cn/thread-749955-1-1.html
https://stackoverflow.com/questions/27260524/need-explanation-on-assembly-instructions-of-kr-fahr-to-cels-example
https://www.52pojie.cn/thread-1101266-1-1.html
https://www.bodkin.ren/index.php/archives/533/
https://www.52pojie.cn/thread-778654-1-1.html


wp re

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

Android逆向入门(三)
Android逆向入门(二)