时隔一年又参加了解题领红包活动,这次有两道 Android 的题目,也花了四五天时间深一步地学习。关于脱壳和 so 分析的一些基本步骤都没有做详细记录,解题过程主要是静态分析。
PS:第三题在 IDA 中的分析中,是导入了 jni.h 头文件后我对各个参数分析过后的结果,具体的步骤在论坛中的“教我兄弟学 Android 逆向教程”系列里有涉及。
【春节】解题领红包之一
公众号回复直接得到口令:
【春节】解题领红包之二
查壳发现有 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_5288
和 sub_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
跑出来后在手机上验证一下结果的正确性:
回复公众号得到口令: