AssassinQ 发表于 2020-2-9 16:01

2020春节解题领红包之一、二、三WP

本帖最后由 AssassinQ 于 2020-2-11 19:19 编辑

时隔一年又参加了解题领红包活动,这次有两道 Android 的题目,也花了四五天时间深一步地学习。关于脱壳和 so 分析的一些基本步骤都没有做详细记录,解题过程主要是静态分析。

PS:第三题在 IDA 中的分析中,是导入了 jni.h 头文件后我对各个参数分析过后的结果,具体的步骤在论坛中的“教我兄弟学 Android 逆向教程”系列里有涉及。

# 【春节】解题领红包之一

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



# 【春节】解题领红包之二

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



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



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

```
00617D34   .8D4D F0       LEA ECX,DWORD PTR SS:
00617D37   .8B15 04786200 MOV EDX,DWORD PTR DS:   ;dumped_.0062B6AC
00617D3D   .8B12          MOV EDX,DWORD PTR DS:
00617D3F   .8D45 FC       LEA EAX,DWORD PTR SS:
00617D42   .E8 597DE1FF   CALL dumped_.0042FAA0
00617D47   .8B45 F0       MOV EAX,DWORD PTR SS:
00617D4A   .BA B87E6100   MOV EDX,dumped_.00617EB8          ;E7EE5F4653E31955CACC7CD68E2A7839
00617D4F   .E8 A42DDFFF   CALL dumped_.0040AAF8
00617D54   .0F9445 E7   SETE BYTE PTR SS:
00617D58   .33C0          XOR EAX,EAX
00617D5A   .5A            POP EDX
00617D5B   .59            POP ECX
00617D5C   .59            POP ECX
00617D5D   .64:8910       MOV DWORD PTR FS:,EDX
00617D60   .68 757D6100   PUSH dumped_.00617D75
00617D65   >8D45 F0       LEA EAX,DWORD PTR SS:
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:,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:
00617D86   .64:8920       MOV DWORD PTR FS:,ESP
00617D89   .8D45 EC       LEA EAX,DWORD PTR SS:
00617D8C   .E8 2B1FDFFF   CALL dumped_.00409CBC
00617D91   .8D4D EC       LEA ECX,DWORD PTR SS:
00617D94   .8B15 04786200 MOV EDX,DWORD PTR DS:   ;dumped_.0062B6AC
00617D9A   .8B12          MOV EDX,DWORD PTR DS:
00617D9C   .8D45 F8       LEA EAX,DWORD PTR SS:
00617D9F   .E8 7C7CE1FF   CALL dumped_.0042FA20
00617DA4   .8B45 EC       MOV EAX,DWORD PTR SS:
00617DA7   .BA 087F6100   MOV EDX,dumped_.00617F08          ;ea6b2efbdd4255a9f1b3bbc6399b58f4
00617DAC   .E8 472DDFFF   CALL dumped_.0040AAF8
00617DB1   .0F9445 E6   SETE BYTE PTR SS:
00617DB5   .33C0          XOR EAX,EAX
00617DB7   .5A            POP EDX
00617DB8   .59            POP ECX
00617DB9   .59            POP ECX
00617DBA   .64:8910       MOV DWORD PTR FS:,EDX
00617DBD   .68 D67D6100   PUSH dumped_.00617DD6
00617DC2   >8D45 EC       LEA EAX,DWORD PTR SS:
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:,0x0
00617DD6   .807D E6 00    CMP BYTE PTR SS:,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:
00617DE7   .64:8920       MOV DWORD PTR FS:,ESP
00617DEA   .8D45 E8       LEA EAX,DWORD PTR SS:
00617DED   .E8 CA1EDFFF   CALL dumped_.00409CBC
00617DF2   .8D4D E8       LEA ECX,DWORD PTR SS:
00617DF5   .8B15 04786200 MOV EDX,DWORD PTR DS:   ;dumped_.0062B6AC
00617DFB   .8B12          MOV EDX,DWORD PTR DS:
00617DFD   .8D45 F4       LEA EAX,DWORD PTR SS:
00617E00   .E8 1B7CE1FF   CALL dumped_.0042FA20
00617E05   .8B45 E8       MOV EAX,DWORD PTR SS:
00617E08   .BA 587F6100   MOV EDX,dumped_.00617F58          ;c8d46d341bea4fd5bff866a65ff8aea9
00617E0D   .E8 E62CDFFF   CALL dumped_.0040AAF8
00617E12   .0F9445 E5   SETE BYTE PTR SS:
00617E16   .33C0          XOR EAX,EAX
00617E18   .5A            POP EDX
00617E19   .59            POP ECX
00617E1A   .59            POP ECX
00617E1B   .64:8910       MOV DWORD PTR FS:,EDX
00617E1E   .68 337E6100   PUSH dumped_.00617E33
00617E23   >8D45 E8       LEA EAX,DWORD PTR SS:
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:,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 字符串定位到函数,反编译后得到如下代码:

```cpp
int __fastcall TForm1_edtPwdChange(int a1)
{
int v1; // ebx
int v2; // edx
int len; // eax
int md5Handler; // esi
char v5; // zf
unsigned int v7; //
int *v8; //
char *v9; //
unsigned int v10; //
void *v11; //
int *v12; //
int v13; //
int v14; //
int v15; //
int v16; //
int v17; //
int v18; //
int v19; //
char v20; //
char v21; //
char v22; //
char *string3; //
char *string2; //
char *string1; //
char s3; //
char s2; //
char s1; //
int savedregs; //

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,得到如下代码:

```java
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_5288` 和 `sub_539C` 两个函数分别用来获取指定的方法(getMethod)或者是域(getField)。接下来直接来看分析过后的代码:

```cpp
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; //
int v20; //
int v21; //
int v22; //
int v23; //
int v24; //
int v25; //
int a3a; //
int v27; //
int v28; //
int v29; //
int v30; //
int a2a; //
int v32; //
int v33; //
int v34; //
int v35; //

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()` 函数,猜测应该是用来反调试:

```cpp
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")并分给了三个变量:

```cpp
    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 到数组中:

```cpp
      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 数组:

```cpp
          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 进行逐位异或:

```cpp
          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 位的字符串:

```cpp
          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 位作为新的字符串,并与我们的输入进行比较:

```cpp
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 实现了一下:

```python
#!/usr/bin/env python
import hashlib

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

arr = ''
for i in range(35):
    if not i or i & 3:
      arr += key2
    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) ^ ord(md5str))
print xorlist

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

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



回复公众号得到口令:

Hmily 发表于 2020-2-9 16:26

本题思路看点在第三题,所以还是移动到移动安全区来。

JuncoJet 发表于 2020-2-9 17:21

然而我的IDA是这样的,分析全靠猜




求问下楼主什么IDA什么插件

vista_info 发表于 2020-2-9 16:37

看起看来很厉害

340621 发表于 2020-2-9 16:55

小白一个,不是很懂。勉强可以举一反三。

JammyLin 发表于 2020-2-9 16:59

emmmm,第三题看见了公众号提示hook,然后去学了皮毛的frida,各种尝试,最后查到可以hook equal函数,然后直接自己输出结果了。

Zeno___Lee 发表于 2020-2-9 17:22

真心不会-.- 坐等大佬

MaHaMa 发表于 2020-2-9 17:24

先666666为敬

AssassinQ 发表于 2020-2-9 17:29

JuncoJet 发表于 2020-2-9 17:21
然而我的IDA是这样的,分析全靠猜




学一下论坛里教程“叫我兄弟学逆向教程”,里面有提到IDA分析so的时候可以导入jni.h头文件来分析,而且一般so里静态注册的函数的参数是特征的,第一个参数一般都是JNIEnv。

暗夜星光i 发表于 2020-2-9 17:34

前来膜拜大佬
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 2020春节解题领红包之一、二、三WP