GKLBB

当你经历了暴风雨,你也就成为了暴风雨

导航

应用安全 --- app加固 之 字符串混淆 + RC4 + Base64

这里最难用静态分析的部分就是 字符串混淆,但是使用frida就非常简单,直接hook寄存器的值即可。

这里的解密思路就是用ida和claud分析代码的加密逻辑在安卓so文件。

通过分析我们发现安卓so中,先将输入值进行rc4,在base64,最后在将一个混淆后的字符串进行了比较,如果真就输出成功。

解密很简单。我们重点介绍如何反混淆字符串。

java层

package cn.pojie52.cm01;

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

/* loaded from: classes.dex */
public class MainActivity extends AppCompatActivity {
    public native boolean check(String str);

    static {
        System.loadLibrary("native-lib");
    }

    /* JADX INFO: Access modifiers changed from: protected */
    @Override // androidx.appcompat.app.AppCompatActivity, androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.activity_main);
        final EditText editText = (EditText) findViewById(R.id.flag);
        findViewById(R.id.check).setOnClickListener(new View.OnClickListener() { // from class: cn.pojie52.cm01.MainActivity.1
            @Override // android.view.View.OnClickListener
            public void onClick(View view) {
                String trim = editText.getText().toString().trim();
                if (trim.length() != 30) {
                    Toast.makeText(MainActivity.this, "flag格式错误,请重试", 0).show();
                } else if (MainActivity.this.check(trim)) {
                    Toast.makeText(MainActivity.this, "恭喜你,验证正确!", 0).show();
                } else {
                    Toast.makeText(MainActivity.this, "flag错误,再接再厉", 0).show();
                }
            }
        });
    }
}

因此输入的字符串是30并且 MainActivity.this.check(trim) 为真。check在 native-lib 的so中定义。

我们用ida打开so,

使用ai提示分析说明每个函数的作用是什么,是系统函数,还是编译器生成函数,还是用户自定义函数,简述每个函数的作用

ARM64汇编代码文件中的所有函数,解释它们各自的作用,并将其归类为以下三种类型之一:

  • 系统函数 (System Function): 由标准库(如 libc)提供的函数,程序通过动态链接导入并使用。

  • 反编译辅助函数 (Compiler/Linker Generated Function): 由编译器或链接器自动生成的辅助代码,用于处理程序启动、清理、动态链接跳转等底层任务。

  • 用户编写函数 (User-Written Function): 由程序开发者编写的,实现程序核心业务逻辑的函数。


函数分析总览

函数名 地址范围 分类 作用简述
sub_6E0 及 .plt 段的其他函数 0x6E0 - 0x780 反编译辅助函数 过程链接表(PLT)的跳转存根,用于实现对外部系统函数的动态调用。
开始 0x790 - 0x798 反编译辅助函数 共享库的入口点之一,用于注册库卸载时需要执行的清理函数。
sub_79C 0x79C - 0x7B0 反编译辅助函数 作为回调函数被 __cxa_atexit 注册,用于执行全局/静态对象的析构。
sub_7B4 0x7B4 - 0x7C8 反编译辅助函数 __cxa_atexit 的封装函数,用于在库卸载时安排清理任务。
sub_7CC 0x7CC - 0x7F8 用户编写函数 一个自定义的 memcmp 函数,按字节比较两块内存区域是否相等。
Java_cn_pojie52_cm01_MainActivity_check 0x7FC - 0xB8C 用户编写函数 (核心函数) JNI接口函数,实现主要的校验逻辑,是整个so库的核心。
sub_B90 0xB90 - 0xD8C 用户编写函数 实现了RC4流加密算法,用于对输入字符串进行变换。
sub_D90 0xD90 - 0x1128 用户编写函数 实现了一个自定义的Base64解码算法,用于对输入字符串进行解码。

各函数详细解释

1. sub_6E0 及 .plt 段的其他函数

  • 地址0x6E0 开始

  • 分类反编译辅助函数 (Compiler/Linker Generated Function)

  • 作用:
    这部分代码位于 .plt (Procedure Linkage Table) 段,是动态链接的核心机制。这些函数本质上是“跳转存根”。

    • sub_6E0: 这是PLT的第一个特殊条目。当程序首次调用任何一个外部函数时,会先跳转到这里。它的作用是调用动态链接器来解析该外部函数的真实地址,并将地址填入全局偏移表(GOT, Global Offset Table)。

    • : 这些是针对每一个外部导入函数的存根。例如,当程序第一次调用 strlen 时,会跳转到 .strlen 这个存根,该存根会触发 sub_6E0 的解析流程。解析完成后,后续对 strlen 的调用就会直接跳转到其在libc中的真实地址,不再需要解析。

2. start

  • 地址0x790

  • 分类反编译辅助函数 (Compiler/Linker Generated Function)

  • 作用:
    这是在 .fini_array(终止函数数组)中注册的函数。当这个共享库被从内存中卸载时,该函数会被执行。它调用了 __cxa_finalize,这是一个C++运行时函数,用于执行全局或静态对象的析构函数,完成资源清理工作。

3. sub_79C 和 sub_7B4

  • 地址0x79C 和 0x7B4

  • 分类反编译辅助函数 (Compiler/Linker Generated Function)

  • 作用:
    这两个函数协同工作,是编译器为管理C++全局/静态对象生命周期而生成的标准代码。

    • sub_7B4: 这个函数调用了系统函数 __cxa_atexit

    • sub_79C: 这个函数被 sub_7B4 作为参数传递给 __cxa_atexit
      它们的作用是注册一个在程序退出或库卸载时需要执行的清理回调 (sub_79C)。这通常用于确保所有全局/静态对象的析构函数都能被正确调用。

4. sub_7CC

  • 地址0x7CC

  • 分类用户编写函数

  • 作用:
    这是一个自定义的内存比较函数,功能等同于标准库的 memcmp

    • 输入X0 (指针1), X1 (指针2), X2 (长度)。

    • 逻辑: 在一个循环中,逐字节比较 X0 和 X1 指向的内存,直到比较完 X2 个字节或者发现不匹配的字节。

    • 输出W0 返回0表示两块内存相等,否则返回非0。

5. Java_cn_pojie52_cm01_MainActivity_check

  • 地址0x7FC

  • 分类用户编写函数 (核心函数)

  • 作用:
    这是该so库暴露给Java层的核心JNI(Java Native Interface)函数,用于执行校验逻辑。

    1. 输入检查: 首先通过JNI函数 GetStringUTFLength 检查从Java层传入的字符串长度是否为 0x1E (30)。如果长度不符,直接返回失败。

    2. 字符串处理: 使用 GetStringUTFChars 和 strncpy 将Java字符串转换为C风格字符串并复制到栈上的一块缓冲区 dest 中。

    3. 加密/变换:

      • 调用 sub_B90,以 dest 和一个固定的密钥 "areyousure??????" 作为输入,对 dest 进行第一次变换。

      • 调用 sub_D90,对 dest 进行第二次变换。

    4. 最终校验: 在经过一系列复杂的位运算、异或、加法等操作后,生成一个最终的字节序列。

    5. 比较: 将用户输入的原始字符串与这个内部生成的字节序列进行比较。

    6. 返回结果: 如果完全匹配,返回 1 (true),表示校验成功;否则返回 0 (false)。

6. sub_B90

  • 地址0xB90

  • 分类用户编写函数

  • 作用:
    这个函数实现的是 RC4流加密算法

    • 输入X0 (要加密/解密的数据指针), W1 (数据长度), X2 (密钥指针, 即 "areyousure??????")。

    • 逻辑:

      1. 密钥调度算法 (KSA): 首先初始化一个256字节的S-box(状态数组),然后使用传入的密钥 "areyousure??????" 对S-box进行置换,生成初始状态。

      2. 伪随机生成算法 (PRGA): 进入一个循环,根据S-box的当前状态生成一个伪随机字节(密钥流),并将其与输入数据的一个字节进行异或(XOR)操作。

    • 输出: 对输入的数据进行原地加密/解密。由于RC4是对称加密,同一个函数可以用于加密和解密。

7. sub_D90

    • 地址0xD90

    • 分类用户编写函数

    • 作用:
      这个函数实现的是一个自定义的 Base64解码算法

      • 输入X0 (Base64编码的字符串指针), X1 (字符串长度)。

      • 逻辑:

        1. 内存分配: 首先分配一块内存用于存放解码后的结果。

        2. 解码循环: 遍历输入的Base64字符串。它以4个字符为一组,通过查找一个内部的解码表(硬编码在代码中),将这4个字符(每个占6位有效数据)转换回3个原始字节(每个8位)。

        3. 处理Padding: 它能正确处理Base64末尾可能存在的 = 填充符。

        4. 内存管理: 在循环中,它会动态地使用 realloc 调整输出缓冲区的大小。

      • 输出: 返回一个指向新分配内存的指针,该内存中包含了Base64解码后的二进制数据。这个函数使用了大量复杂的SIMD指令(NEON指令),可能是为了性能优化,也可能是一种代码混淆手段。

 

找到导出函数打开这个check函数

.text:00000000000007FC
.text:00000000000007FC ; =============== S U B R O U T I N E =======================================
.text:00000000000007FC
.text:00000000000007FC ; Attributes: bp-based frame
.text:00000000000007FC
.text:00000000000007FC                 EXPORT Java_cn_pojie52_cm01_MainActivity_check
.text:00000000000007FC Java_cn_pojie52_cm01_MainActivity_check ; DATA XREF: LOAD:0000000000000448↑o
.text:00000000000007FC
.text:00000000000007FC var_A0          = -0xA0
.text:00000000000007FC var_87          = -0x87
.text:00000000000007FC dest            = -0x68
.text:00000000000007FC var_58          = -0x58
.text:00000000000007FC var_48          = -0x48
.text:00000000000007FC var_38          = -0x38
.text:00000000000007FC var_28          = -0x28
.text:00000000000007FC var_20          = -0x20
.text:00000000000007FC var_10          = -0x10
.text:00000000000007FC var_s0          =  0
.text:00000000000007FC
.text:00000000000007FC ; __unwind {
.text:00000000000007FC                 SUB             SP, SP, #0xB0
.text:0000000000000800                 STP             X22, X21, [SP,#0xA0+var_20]
.text:0000000000000804                 STP             X20, X19, [SP,#0xA0+var_10]
.text:0000000000000808                 STP             X29, X30, [SP,#0xA0+var_s0]
.text:000000000000080C                 ADD             X29, SP, #0xA0
.text:0000000000000810                 MRS             X22, TPIDR_EL0
.text:0000000000000814                 LDR             X8, [X22,#0x28]
.text:0000000000000818                 MOV             X1, X2
.text:000000000000081C                 MOV             X19, X0
.text:0000000000000820                 MOV             X20, X2
.text:0000000000000824                 STUR            X8, [X29,#var_28]
.text:0000000000000828                 LDR             X8, [X0]
.text:000000000000082C                 LDR             X8, [X8,#0x540]
.text:0000000000000830                 BLR             X8
.text:0000000000000834                 CMP             W0, #0x1E
.text:0000000000000838                 B.NE            loc_B54
.text:000000000000083C                 LDR             X8, [X19]
.text:0000000000000840                 MOV             X0, X19
.text:0000000000000844                 MOV             X1, X20
.text:0000000000000848                 MOV             X2, XZR
.text:000000000000084C                 LDR             X8, [X8,#0x548]
.text:0000000000000850                 BLR             X8
.text:0000000000000854                 MOVI            V0.2D, #0
.text:0000000000000858                 MOV             X21, X0
.text:000000000000085C                 STUR            Q0, [SP,#0xA0+var_38]
.text:0000000000000860                 STUR            Q0, [SP,#0xA0+var_48]
.text:0000000000000864                 STUR            Q0, [SP,#0xA0+var_58]
.text:0000000000000868                 STUR            Q0, [SP,#0xA0+dest]
.text:000000000000086C                 BL              .strlen
.text:0000000000000870                 MOV             X2, X0  ; n
.text:0000000000000874                 ADD             X0, SP, #0xA0+dest ; dest
.text:0000000000000878                 MOV             X1, X21 ; src
.text:000000000000087C                 BL              .strncpy
.text:0000000000000880                 LDR             X8, [X19]
.text:0000000000000884                 MOV             X0, X19
.text:0000000000000888                 MOV             X1, X20
.text:000000000000088C                 MOV             X2, X21
.text:0000000000000890                 LDR             X8, [X8,#0x550]
.text:0000000000000894                 BLR             X8
.text:0000000000000898                 ADD             X0, SP, #0xA0+dest ; s
.text:000000000000089C                 BL              .strlen
.text:00000000000008A0                 ADRP            X2, #aAreyousure@PAGE ; "areyousure??????"
.text:00000000000008A4                 MOV             X1, X0  ; int
.text:00000000000008A8                 ADD             X2, X2, #aAreyousure@PAGEOFF ; "areyousure??????"
.text:00000000000008AC                 ADD             X0, SP, #0xA0+dest ; int
.text:00000000000008B0                 BL              sub_B90
.text:00000000000008B4                 ADD             X0, SP, #0xA0+dest ; s
.text:00000000000008B8                 BL              .strlen
.text:00000000000008BC                 MOV             X1, X0
.text:00000000000008C0                 ADD             X0, SP, #0xA0+dest
.text:00000000000008C4                 BL              sub_D90
.text:00000000000008C8                 ADRL            X9, xmmword_11A1
.text:00000000000008D0                 LDP             Q1, Q2, [X9]
.text:00000000000008D4                 LDUR            Q3, [X9,#0x19]
.text:00000000000008D8                 ADRP            X11, #xmmword_1130@PAGE
.text:00000000000008DC                 ADRP            X9, #xmmword_1140@PAGE
.text:00000000000008E0                 STP             Q1, Q2, [SP,#0xA0+var_A0]
.text:00000000000008E4                 LDUR            Q2, [SP,#0xA0+var_A0+1]
.text:00000000000008E8                 STUR            Q3, [SP,#0xA0+var_87]
.text:00000000000008EC                 LDR             Q3, [X11,#xmmword_1130@PAGEOFF]
.text:00000000000008F0                 LDR             Q6, [X9,#xmmword_1140@PAGEOFF]
.text:00000000000008F4                 MOVI            V0.16B, #0xB2
.text:00000000000008F8                 ADD             V2.16B, V2.16B, V0.16B
.text:00000000000008FC                 MOV             W10, #0x35 ; '5'
.text:0000000000000900                 EOR             V2.16B, V2.16B, V3.16B
.text:0000000000000904                 MOVI            V4.16B, #0xFE
.text:0000000000000908                 STRB            W10, [SP,#0xA0+var_A0]
.text:000000000000090C                 ADRP            X10, #xmmword_1150@PAGE
.text:0000000000000910                 ADD             V2.16B, V2.16B, V6.16B
.text:0000000000000914                 LDR             Q3, [X10,#xmmword_1150@PAGEOFF]
.text:0000000000000918                 EOR             V2.16B, V2.16B, V4.16B
.text:000000000000091C                 USHR            V6.16B, V2.16B, #7
.text:0000000000000920                 SHL             V2.16B, V2.16B, #1
.text:0000000000000924                 LDRB            W10, [SP,#0xA0+var_87+8]
.text:0000000000000928                 MOVI            V5.16B, #1
.text:000000000000092C                 ORR             V2.16B, V6.16B, V2.16B
.text:0000000000000930                 SUB             V2.16B, V5.16B, V2.16B
.text:0000000000000934                 MOVI            V1.16B, #0x3E ; '>'
.text:0000000000000938                 ADRP            X11, #xmmword_1160@PAGE
.text:000000000000093C                 EOR             V2.16B, V2.16B, V3.16B
.text:0000000000000940                 ADD             V2.16B, V2.16B, V1.16B
.text:0000000000000944                 LDR             Q3, [X11,#xmmword_1160@PAGEOFF]
.text:0000000000000948                 MOV             W11, #0xFFFFFFB6
.text:000000000000094C                 SUB             W10, W10, #0x4E ; 'N'
.text:0000000000000950                 STUR            Q2, [SP,#0xA0+var_A0+1]
.text:0000000000000954                 LDUR            Q2, [SP,#0x11]
.text:0000000000000958                 EOR             W10, W10, W11
.text:000000000000095C                 SUB             W10, W10, #0x71 ; 'q'
.text:0000000000000960                 ADRP            X9, #xmmword_1170@PAGE
.text:0000000000000964                 EOR             W10, W10, #0xFFFFFFFE
.text:0000000000000968                 LDR             Q6, [X9,#xmmword_1170@PAGEOFF]
.text:000000000000096C                 ADRP            X9, #xmmword_1180@PAGE
.text:0000000000000970                 LSL             W11, W10, #1
.text:0000000000000974                 ADD             V0.16B, V2.16B, V0.16B
.text:0000000000000978                 LDR             Q2, [X9,#xmmword_1180@PAGEOFF]
.text:000000000000097C                 MOV             W9, #1
.text:0000000000000980                 BFXIL           W11, W10, #7, #1
.text:0000000000000984                 MOV             W10, #0x21 ; '!'
.text:0000000000000988                 SUB             W11, W9, W11
.text:000000000000098C                 EOR             W10, W11, W10
.text:0000000000000990                 ADD             W10, W10, #0x3E ; '>'
.text:0000000000000994                 STRB            W10, [SP,#0xA0+var_87+8]
.text:0000000000000998                 LDRB            W10, [SP,#0xA0+var_87+9]
.text:000000000000099C                 MOV             W11, #0xFFFFFFB5
.text:00000000000009A0                 EOR             V0.16B, V0.16B, V3.16B
.text:00000000000009A4                 ADD             V0.16B, V0.16B, V6.16B
.text:00000000000009A8                 SUB             W10, W10, #0x4E ; 'N'
.text:00000000000009AC                 EOR             W10, W10, W11
.text:00000000000009B0                 SUB             W10, W10, #0x72 ; 'r'
.text:00000000000009B4                 EOR             W10, W10, #0xFFFFFFFE
.text:00000000000009B8                 LSL             W11, W10, #1
.text:00000000000009BC                 BFXIL           W11, W10, #7, #1
.text:00000000000009C0                 SUB             W10, W9, W11
.text:00000000000009C4                 EOR             W10, W10, #0x22222222
.text:00000000000009C8                 ADD             W10, W10, #0x3E ; '>'
.text:00000000000009CC                 STRB            W10, [SP,#0xA0+var_87+9]
.text:00000000000009D0                 LDRB            W10, [SP,#0xA0+var_87+0xA]
.text:00000000000009D4                 MOV             W11, #0xFFFFFFB4
.text:00000000000009D8                 EOR             V0.16B, V0.16B, V4.16B
.text:00000000000009DC                 USHR            V3.16B, V0.16B, #7
.text:00000000000009E0                 SUB             W10, W10, #0x4E ; 'N'
.text:00000000000009E4                 EOR             W10, W10, W11
.text:00000000000009E8                 SUB             W10, W10, #0x73 ; 's'
.text:00000000000009EC                 EOR             W10, W10, #0xFFFFFFFE
.text:00000000000009F0                 LSL             W11, W10, #1
.text:00000000000009F4                 BFXIL           W11, W10, #7, #1
.text:00000000000009F8                 MOV             W10, #0x23 ; '#'
.text:00000000000009FC                 SUB             W11, W9, W11
.text:0000000000000A00                 EOR             W10, W11, W10
.text:0000000000000A04                 ADD             W10, W10, #0x3E ; '>'
.text:0000000000000A08                 STRB            W10, [SP,#0xA0+var_87+0xA]
.text:0000000000000A0C                 LDRB            W10, [SP,#0xA0+var_87+0xB]
.text:0000000000000A10                 MOV             W11, #0xFFFFFFB3
.text:0000000000000A14                 SHL             V0.16B, V0.16B, #1
.text:0000000000000A18                 ORR             V0.16B, V3.16B, V0.16B
.text:0000000000000A1C                 SUB             W10, W10, #0x4E ; 'N'
.text:0000000000000A20                 EOR             W10, W10, W11
.text:0000000000000A24                 SUB             W10, W10, #0x74 ; 't'
.text:0000000000000A28                 EOR             W10, W10, #0xFFFFFFFE
.text:0000000000000A2C                 LSL             W11, W10, #1
.text:0000000000000A30                 BFXIL           W11, W10, #7, #1
.text:0000000000000A34                 MOV             W10, #0x24 ; '$'
.text:0000000000000A38                 SUB             W11, W9, W11
.text:0000000000000A3C                 EOR             W10, W11, W10
.text:0000000000000A40                 ADD             W10, W10, #0x3E ; '>'
.text:0000000000000A44                 STRB            W10, [SP,#0xA0+var_87+0xB]
.text:0000000000000A48                 LDRB            W10, [SP,#0xA0+var_87+0xC]
.text:0000000000000A4C                 MOV             W11, #0xFFFFFFB2
.text:0000000000000A50                 SUB             V0.16B, V5.16B, V0.16B
.text:0000000000000A54                 EOR             V0.16B, V0.16B, V2.16B
.text:0000000000000A58                 SUB             W10, W10, #0x4E ; 'N'
.text:0000000000000A5C                 EOR             W10, W10, W11
.text:0000000000000A60                 SUB             W10, W10, #0x75 ; 'u'
.text:0000000000000A64                 EOR             W10, W10, #0xFFFFFFFE
.text:0000000000000A68                 LSL             W11, W10, #1
.text:0000000000000A6C                 BFXIL           W11, W10, #7, #1
.text:0000000000000A70                 MOV             W10, #0x25 ; '%'
.text:0000000000000A74                 SUB             W11, W9, W11
.text:0000000000000A78                 EOR             W10, W11, W10
.text:0000000000000A7C                 ADD             W10, W10, #0x3E ; '>'
.text:0000000000000A80                 STRB            W10, [SP,#0xA0+var_87+0xC]
.text:0000000000000A84                 LDRB            W10, [SP,#0xA0+var_87+0xD]
.text:0000000000000A88                 MOV             W11, #0xFFFFFFB1
.text:0000000000000A8C                 MOV             X8, XZR
.text:0000000000000A90                 ADD             V0.16B, V0.16B, V1.16B
.text:0000000000000A94                 SUB             W10, W10, #0x4E ; 'N'
.text:0000000000000A98                 EOR             W10, W10, W11
.text:0000000000000A9C                 SUB             W10, W10, #0x76 ; 'v'
.text:0000000000000AA0                 EOR             W10, W10, #0xFFFFFFFE
.text:0000000000000AA4                 LSL             W11, W10, #1
.text:0000000000000AA8                 BFXIL           W11, W10, #7, #1
.text:0000000000000AAC                 MOV             W10, #0x26 ; '&'
.text:0000000000000AB0                 SUB             W11, W9, W11
.text:0000000000000AB4                 EOR             W10, W11, W10
.text:0000000000000AB8                 ADD             W10, W10, #0x3E ; '>'
.text:0000000000000ABC                 STRB            W10, [SP,#0xA0+var_87+0xD]
.text:0000000000000AC0                 LDRB            W10, [SP,#0xA0+var_87+0xE]
.text:0000000000000AC4                 MOV             W11, #0xFFFFFFB0
.text:0000000000000AC8                 STUR            Q0, [SP,#0x11]
.text:0000000000000ACC                 SUB             W10, W10, #0x4E ; 'N'
.text:0000000000000AD0                 EOR             W10, W10, W11
.text:0000000000000AD4                 SUB             W10, W10, #0x77 ; 'w'
.text:0000000000000AD8                 EOR             W10, W10, #0xFFFFFFFE
.text:0000000000000ADC                 LSL             W11, W10, #1
.text:0000000000000AE0                 BFXIL           W11, W10, #7, #1
.text:0000000000000AE4                 MOV             W10, #0x27 ; '''
.text:0000000000000AE8                 SUB             W11, W9, W11
.text:0000000000000AEC                 EOR             W10, W11, W10
.text:0000000000000AF0                 ADD             W10, W10, #0x3E ; '>'
.text:0000000000000AF4                 STRB            W10, [SP,#0xA0+var_87+0xE]
.text:0000000000000AF8                 LDRB            W10, [SP,#0xA0+var_87+0xF]
.text:0000000000000AFC                 SUB             W10, W10, #0x4E ; 'N'
.text:0000000000000B00                 EOR             W10, W10, #0xFFFFFFBF
.text:0000000000000B04                 SUB             W10, W10, #0x78 ; 'x'
.text:0000000000000B08                 EOR             W10, W10, #0xFFFFFFFE
.text:0000000000000B0C                 LSL             W11, W10, #1
.text:0000000000000B10                 BFXIL           W11, W10, #7, #1
.text:0000000000000B14                 SUB             W9, W9, W11
.text:0000000000000B18                 MOV             W10, #0x28 ; '('
.text:0000000000000B1C                 EOR             W9, W9, W10
.text:0000000000000B20                 ADD             W9, W9, #0x3E ; '>'
.text:0000000000000B24                 STRB            W9, [SP,#0xA0+var_87+0xF]
.text:0000000000000B28                 MOV             X9, SP
.text:0000000000000B2C
.text:0000000000000B2C loc_B2C                                 ; CODE XREF: Java_cn_pojie52_cm01_MainActivity_check+34C↓j
.text:0000000000000B2C                 LDRB            W10, [X0,X8]
.text:0000000000000B30                 LDRB            W11, [X9,X8]
.text:0000000000000B34                 CMP             W10, W11
.text:0000000000000B38                 B.NE            loc_B5C
.text:0000000000000B3C                 CBZ             W10, loc_B4C
.text:0000000000000B40                 ADD             X8, X8, #1
.text:0000000000000B44                 CMP             X8, #0x29 ; ')'
.text:0000000000000B48                 B.NE            loc_B2C
.text:0000000000000B4C
.text:0000000000000B4C loc_B4C                                 ; CODE XREF: Java_cn_pojie52_cm01_MainActivity_check+340↑j
.text:0000000000000B4C                 MOV             W19, #1
.text:0000000000000B50                 B               loc_B60
.text:0000000000000B54 ; ---------------------------------------------------------------------------
.text:0000000000000B54
.text:0000000000000B54 loc_B54                                 ; CODE XREF: Java_cn_pojie52_cm01_MainActivity_check+3C↑j
.text:0000000000000B54                 MOV             W19, WZR
.text:0000000000000B58                 B               loc_B64
.text:0000000000000B5C ; ---------------------------------------------------------------------------
.text:0000000000000B5C
.text:0000000000000B5C loc_B5C                                 ; CODE XREF: Java_cn_pojie52_cm01_MainActivity_check+33C↑j
.text:0000000000000B5C                 MOV             W19, WZR
.text:0000000000000B60
.text:0000000000000B60 loc_B60                                 ; CODE XREF: Java_cn_pojie52_cm01_MainActivity_check+354↑j
.text:0000000000000B60                 BL              .free
.text:0000000000000B64
.text:0000000000000B64 loc_B64                                 ; CODE XREF: Java_cn_pojie52_cm01_MainActivity_check+35C↑j
.text:0000000000000B64                 LDR             X8, [X22,#0x28]
.text:0000000000000B68                 LDUR            X9, [X29,#var_28]
.text:0000000000000B6C                 CMP             X8, X9
.text:0000000000000B70                 B.NE            loc_B8C
.text:0000000000000B74                 MOV             W0, W19
.text:0000000000000B78                 LDP             X29, X30, [SP,#0xA0+var_s0]
.text:0000000000000B7C                 LDP             X20, X19, [SP,#0xA0+var_10]
.text:0000000000000B80                 LDP             X22, X21, [SP,#0xA0+var_20]
.text:0000000000000B84                 ADD             SP, SP, #0xB0
.text:0000000000000B88                 RET
.text:0000000000000B8C ; ---------------------------------------------------------------------------
.text:0000000000000B8C
.text:0000000000000B8C loc_B8C                                 ; CODE XREF: Java_cn_pojie52_cm01_MainActivity_check+374↑j
.text:0000000000000B8C                 BL              .__stack_chk_fail
.text:0000000000000B8C ; } // starts at 7FC
.text:0000000000000B8C ; End of function Java_cn_pojie52_cm01_MainActivity_check
.text:0000000000000B8C

 

我们大致浏览一下这个函数,还是比较复杂的。

我们重点关注,bl指令和cmp指令。一个是函数调用,一个是if比较的关键点。

// 引入必要的库文件
#include <jni.h>        // JNI库:用于Java和C之间的交互通信。JNI是Java Native Interface的缩写,是Java虚拟机(JVM)的一部分,它允许Java代码与其他语言(如C/C++)编写的代码进行交互。
#include <string.h>     // 字符串库:提供strcpy、strlen等字符串操作函数。这是C语言处理字符串的标准库。
#include <stdlib.h>     // 标准库:提供malloc、free等内存管理函数。`malloc`用于动态分配内存,`free`用于释放内存,防止内存泄漏。
#include <stdint.h>     // 整数类型库:定义uint8_t等固定长度的整数类型。`uint8_t`表示一个无符号的8位整数(即一个字节),这在处理原始二进制数据时非常有用。

// // // 预定义的数据常量 - 这些是"魔法数字表",用于复杂的数据变换
// // // xmmword_1130: 第一个变换表,包含16个预设的十六进制数值
// // // 作用:在SIMD向量操作中用作变换参数,就像密码本一样。这些固定的数值表在加密算法中很常见,用于数据混淆,增加逆向分析的难度。
// static const uint8_t xmmword_1130[] = {
//     0x96, 0x95, 0x94, 0x93, 0x92, 0x91, 0x90, 0x9F, 0x9E,  // 前9个数字
//     0x9D, 0x9C, 0x9B, 0x9A, 0x99, 0x98, 0x87               // 后7个数字
// };

// // // xmmword_1140: 第二个变换表,也是16个预设数值
// // // 作用:配合第一个表进行数据变换,增加加密复杂度。多个不同的表可以组合使用,形成更复杂的变换规则。
// static const uint8_t xmmword_1140[] = {
//     0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8, 0xA7,  // 从0xAF递减的9个数
//     0xA6, 0xA5, 0xA4, 0xA3, 0xA2, 0xA1, 0xA0               // 继续递减的7个数
// };

// // // xmmword_1150: 第三个变换表,从1到16的连续数字
// // // 作用:用于异或运算,提供规律性的变换参数。这种有规律的表通常用于引入一个可预测但能有效改变数据位的变换层。
// static const uint8_t xmmword_1150[] = {
//     0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,        // 1到8
//     0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10         // 9到16
// };

// // // xmmword_1160: 第四个变换表,另一组预设数值
// // // 作用:同样是用于数据混淆的常量表,在算法的不同阶段可能被使用。
// static const uint8_t xmmword_1160[] = {
//     0x86, 0x85, 0x84, 0x83, 0x82, 0x81, 0x80, 0x8F, 0x8E,
//     0x8D, 0x8C, 0x8B, 0x8A, 0x89, 0x88, 0xB7
// };

// // xmmword_1170: 第五个变换表,另一组预设数值
// // 作用:继续为算法提供混淆用的数据。
// static const uint8_t xmmword_1170[] = {
//     0x9F, 0x9E, 0x9D, 0x9C, 0x9B, 0x9A, 0x99, 0x98, 0x97,
//     0x96, 0x95, 0x94, 0x93, 0x92, 0x91, 0x90
// };

// // xmmword_1180: 第六个变换表,从0x11到0x20的连续数字
// // 作用:类似于xmmword_1150,提供另一组规律性的变换参数。
// static const uint8_t xmmword_1180[] = {
//     0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
//     0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20
// };

// RC4加密的固定密钥字符串
// 作用:这是硬编码在程序里的密钥,用于RC4加密算法。任何需要通过验证的输入都必须与这个密钥相关。
// 内容:"areyousure??????" (你确定吗??????)
static const char aAreyousure[] = "areyousure??????";

// 神秘的加密数据块 - 这是程序的核心秘密!
// 作用:这个数据是验证流程的“最终答案”。用户的输入经过一系列加密和编码后,必须得到与这个数据块(或其变体)完全相同的结果,才能通过验证。
// 前30字节:加密后的目标字符串 "52pojieHappyChineseNewYear2021" 经过某种变换后的结果。
// 后面字节:可能是校验码、填充数据或无用数据,用来混淆视听。
static const uint8_t xmmword_11A1[] = {
    0x2A, 0x13, 0x3F, 0x28, 0x2D, 0x34, 0x92, 0x9D, 0xBB,  // 前9字节
    0x36, 0x92, 0x92, 0xCD, 0x48, 0xB7, 0x13,              // 中间7字节
    0x39, 0xCB, 0xC9, 0xC7, 0xA9, 0x4C, 0xCB, 0x46, 0x43,  // 继续9字节
    0x4D, 0x24, 0xDA, 0x50, 0xD9, 0x4C, 0x4F,              // 再7字节
    0x0B, 0x0D, 0x05, 0x0A, 0x13, 0x07, 0x81, 0x21, 0xA0,  // 剩余数据
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00                      // 零填充
};

// RC4算法的S-box初始化数据 - RC4加密的"魔法盒子"
// 作用:这是一个包含了0到255所有数字的有序数组,是RC4算法的初始状态。
// 这个盒子会被密钥(aAreyousure)打乱,然后用来生成加密用的伪随机密钥流。
static const uint8_t rc4_sbox_init[] = {
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
    0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
    0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
    0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
    0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
    0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
    0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
    0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
    0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
};

// Base64编码相关数据 - 自定义的Base64变换表
// 作用:这些表不是标准的Base64表,而是程序特有的编码表,用于在Base64编码前后进行额外的数据混淆,让常规的Base64解码工具失效。
static const uint8_t base64_decode_table[] = { // 这个表在当前代码中并未被使用,但可能存在于原始程序的其他部分
    0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2, 0xF1, 0xF0,
    0xD2, 0xD1, 0xD0, 0xCF, 0xCE, 0xCD, 0xCC, 0xCB, 0xCA, 0xC9, 0xC8, 0xC7, 0xC6, 0xC5, 0xC4, 0xC3,
    0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8, 0xE7, 0xE6, 0xE5, 0xE4, 0xE3, 0xE2, 0xE1, 0xE0,
    0xC2, 0xC1, 0xC0, 0xBF, 0xBE, 0xBD, 0xBC, 0xBB, 0xBA, 0xB9, 0xB8, 0xB7, 0xB6, 0xB5, 0xB4, 0xB3,
    0xDF, 0xDE, 0xDD, 0xDC, 0xDB, 0xDA, 0xD9, 0xD8, 0xD7, 0xD6, 0xD5, 0xD4, 0xD3, 0xD2, 0xD1, 0xD0,
    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30,
    0xB2, 0xB1, 0xB0, 0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8, 0xA7, 0xA6, 0xA5, 0xA4, 0xA3,
    0xCF, 0xCE, 0xCD, 0xCC, 0xCB, 0xCA, 0xC9, 0xC8, 0xC7, 0xC6, 0xC5, 0xC4, 0xC3, 0xC2, 0xC1, 0xC0,
    0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40,
    0xA2, 0xA1, 0xA0, 0x9F, 0x9E, 0x9D, 0x9C, 0x9B, 0x9A, 0x99, 0x98, 0x97, 0x96, 0x95, 0x94, 0x93
};

static const uint8_t base64_encode_table[] = { // 这个表在`sub_D90`函数中被用来变换数据。
    0x2C, 0x26, 0x44, 0x3E, 0x4C, 0x46, 0x6C, 0x66, 0x6C, 0x76, 0x87, 0x83, 0x93, 0x9F, 0xA7, 0xAB,
    0xCB, 0xBF, 0xB7, 0xB3, 0xF3, 0xDF, 0xD7, 0xDB, 0x0B, 0xFF, 0x1F, 0x1B, 0x2B, 0x57, 0x3F, 0x43,
    0x33, 0x87, 0x9F, 0x9B, 0x9B, 0x67, 0x7F, 0x78, 0x82, 0xCC, 0xDE, 0xE8, 0xEA, 0xB4, 0xBE, 0xC8,
    0xB2, 0x2C, 0x1E, 0x18, 0xBF, 0xBB, 0x33, 0x27, 0x1F, 0x23, 0x0B, 0x17, 0xFF, 0x0B, 0x37, 0x27
};

// 前向声明 (Forward Declaration)
// 提前告诉编译器,后面会定义这两个函数,这样在它们被定义之前就可以被调用。
int64_t sub_B90(uint8_t* data, int data_len, const char* key);
char* sub_D90(uint8_t* data, int data_len);

/**
 * RC4加密函数 (函数名sub_B90可能是逆向工程时根据内存地址命名的。该函数sub_B90是标准的RC4实现,可直接用于加密/解密。其与公开的RC4算法描述(如RFC 6229)完全一致)  这个函数的本质就是通过密钥生成一个伪随机的密钥流,然后与数据做异或运算。
 * @param data 要加密/解密的数据的指针 (RC4是对称加密,加密和解密是同一个操作)
 * @param data_len 数据的长度
 * @param key 加密/解密用的密钥字符串
 * @return 总是返回0,这里返回值的意义不大,主要操作是直接修改传入的data。
 */
int64_t sub_B90(uint8_t* data, int data_len, const char* key) {
    uint8_t sbox[256]; // 定义一个RC4的S盒(状态盒)
    uint8_t temp;      // 定义一个临时变量,用于交换S盒中的元素
    int i, j, k;       // 定义循环计数器
    int key_len = strlen(key); // 获取密钥的长度
    
    // 步骤1: 初始化S盒
    // 将S盒按照0, 1, 2, ..., 255的顺序填充好。就像一副全新的扑克牌,按顺序排列。
    for (i = 0; i < 256; i++) {
        sbox[i] = rc4_sbox_init[i]; // 从预定义的有序数组复制数据
    }
    
    // 步骤2: KSA (Key Scheduling Algorithm) - 密钥调度算法
    // 这是RC4的核心步骤之一,用密钥来打乱S盒。就像用一种特殊的方法洗牌。
    j = 0; // 初始化索引 j
    for (i = 0; i < 256; i++) {
        // 根据S盒当前元素、密钥的对应字符和上一次的j值,计算出新的j
        j = (j + sbox[i] + key[i % key_len]) & 0xFF; // `& 0xFF`确保结果在0-255之间
        
        // 交换 sbox[i] 和 sbox[j] 的位置
        temp = sbox[i];       // 把sbox[i]的值存到临时变量
        sbox[i] = sbox[j];    // 把sbox[j]的值赋给sbox[i]
        sbox[j] = temp;       // 把原来的sbox[i]的值赋给sbox[j]
    }
    
    // 步骤3: PRGA (Pseudo-Random Generation Algorithm) - 伪随机生成算法
    // 使用打乱后的S盒来生成一串伪随机的字节流(密钥流),然后用这个流来加密数据。
    if (data_len > 0) { // 只有在有数据需要处理时才执行
        i = 0; // 重置索引 i
        j = 0; // 重置索引 j
        for (k = 0; k < data_len; k++) { // 遍历每一个需要加密的字节
            // 更新索引 i 和 j,进一步扰乱S盒
            i = (i + 1) & 0xFF;
            j = (j + sbox[i]) & 0xFF;
            
            // 再次交换 sbox[i] 和 sbox[j]
            temp = sbox[i];
            sbox[i] = sbox[j];
            sbox[j] = temp;
            
            // 生成一个密钥流字节(keystream)
            // 通过S盒中的两个值相加,再把结果作为索引去S盒里取出一个值,这个值就是密钥流字节。
            uint8_t keystream = sbox[(sbox[i] + sbox[j]) & 0xFF];
            
            // 将原始数据的字节与密钥流字节进行异或(XOR)操作,完成加密/解密
            data[k] ^= keystream; // `^`是异或运算符。 A ^ B ^ B = A,所以加密解密是同一个操作。
        }
    }
    
    return 0; // 函数结束,返回0
}

/**
 * 自定义Base64编码函数 (函数名sub_D90同样可能来自逆向工程)
 * @param data 要编码的数据的指针
 * @param data_len 数据的长度
 * @return 返回一个指向新分配内存的字符串指针,该字符串是编码后的结果。调用者需要负责释放这块内存。
 */
char* sub_D90(uint8_t* data, int data_len) {
    if (data_len == 0) { // 如果输入数据长度为0
        char* result = (char*)malloc(1); // 分配1个字节的内存
        if (result) result[0] = '\0';    // 如果分配成功,存入字符串结束符,返回一个空字符串
        return result;                   // 返回
    }
    
    // 计算标准Base64编码后的字符串长度。每3个字节的输入会变成4个字节的输出。
    int encoded_len = ((data_len + 2) / 3) * 4;
    // 分配足够的内存来存储编码后的字符串和结尾的'\0'
    char* result = (char*)malloc(encoded_len + 1);
    if (!result) return NULL; // 如果内存分配失败,返回空指针
    
    // 标准的Base64编码字符集
    const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    
    int i, j = 0; // i用于遍历输入数据,j用于写入结果字符串
    uint8_t char_array_3[3]; // 临时存放3个输入字节
    uint8_t char_array_4[4]; // 临时存放转换后的4个6位索引
    
    // **核心混淆步骤**:在进行标准Base64编码之前,先对原始数据进行一系列复杂的变换
    uint8_t transformed_data[data_len]; // 创建一个新数组来存放变换后的数据
    for (i = 0; i < data_len; i++) {
        // 应用复杂的变换算法
        uint8_t byte = data[i]; // 取出当前字节
        
        // 1. 使用预定义的变换表进行异或
        if (i < sizeof(base64_encode_table)) { // 如果当前位置在表范围内
            byte ^= base64_encode_table[i % sizeof(base64_encode_table)]; // 与表中的对应字节异或
        }
        
        // 2. 应用位操作变换 (循环右移2位)
        byte = ((byte >> 2) | (byte << 6)) & 0xFF; // 将字节的位向右移动2位,高位补到低位
        
        // 3. 再次异或
        byte ^= 0x82; // 与一个固定的魔法数异或
        
        // 4. 加法变换
        byte = (byte + 0x3E) & 0xFF; // 加上一个固定的魔法数,`& 0xFF`防止溢出
        
        transformed_data[i] = byte; // 将变换后的字节存入新数组
    }
    
    // **标准Base64编码过程**:对`transformed_data`进行编码
    for (i = 0; i < data_len; ) { // 循环处理变换后的数据
        // 从输入数据中取出3个字节
        char_array_3[0] = transformed_data[i++];
        char_array_3[1] = (i < data_len) ? transformed_data[i++] : 0; // 如果不够3个,用0填充
        char_array_3[2] = (i < data_len) ? transformed_data[i++] : 0; // 如果不够3个,用0填充
        
        // 将这3个字节(24位)重新组合成4个6位的数字
        char_array_4[0] = (char_array_3[0] & 0xFC) >> 2;                                     // 第1个6位
        char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xF0) >> 4); // 第2个6位
        char_array_4[2] = ((char_array_3[1] & 0x0F) << 2) + ((char_array_3[2] & 0xC0) >> 6); // 第3个6位
        char_array_4[3] = char_array_3[2] & 0x3F;                                            // 第4个6位
        
        // 用这4个6位的数字作为索引,从`base64_chars`表中查找对应的字符,并存入结果字符串
        for (int k = 0; k < 4; k++) {
            if (j < encoded_len) {
                result[j++] = base64_chars[char_array_4[k]];
            }
        }
    }
    
    // **处理填充字符'='**
    // Base64要求输出长度必须是4的倍数。如果原始数据长度不是3的倍数,末尾需要用'='填充。
    int padding = data_len % 3; // 计算原始数据长度除以3的余数
    if (padding) { // 如果余数不为0
        for (i = padding; i < 3; i++) { // 根据缺少的字节数决定填充'='的数量
            if (j > 0) result[--j] = '='; // 将结果字符串的末尾替换为'='
        }
        j = encoded_len; // 将索引j重置为编码后的总长度
    }
    
    result[j] = '\0'; // 在字符串末尾添加结束符
    return result;    // 返回编码后的字符串
}

/**
 * 生成期望的比较字符串 (这个函数被设计得非常复杂,目的是隐藏最终要比较的字符串)
 * @param buffer 输出缓冲区,用于存放生成的字符串
 * @param size 缓冲区的大小
 */
void generate_expected_string(uint8_t* buffer, int size) {
    // 从预定义的神秘数据块`xmmword_11A1`中获取原始数据
    const uint8_t* source_data = xmmword_11A1;
    
    // 复杂的字符串生成算法
    buffer[0] = 0x35; // '5', 第一个字符是固定的'5'
    
    // 这里的循环和逻辑是原始汇编代码的模拟,看起来非常晦涩,其唯一目的就是动态地计算出正确的字符串,而不是直接写在代码里。
    // 这是一种代码混淆技术。
    for (int i = 1; i < size && i < 41; i++) { // 循环生成后续的字符
        uint8_t byte = source_data[i]; // 从源数据中取出一个字节
        
        // 应用复杂的变换
        if (i >= 8 && i <= 15) { // 对索引在8到15之间的字节应用一套特殊的变换规则
            // 这一整块都是为了混淆,将原始数据变得面目全非,最终得到目标字符。
            // 每一行运算都是一个步骤,我们不需要理解其数学意义,只需要知道这是生成“正确答案”的过程。
            byte -= 0x4E;
            
            switch (i) { // 根据索引i进行不同的异或操作
                case 8:  byte ^= 0xFFFFFFB6; break;
                case 9:  byte ^= 0xFFFFFFB5; break;
                case 10: byte ^= 0xFFFFFFB4; break;
                case 11: byte ^= 0xFFFFFFB3; break;
                case 12: byte ^= 0xFFFFFFB2; break;
                case 13: byte ^= 0xFFFFFFB1; break;
                case 14: byte ^= 0xFFFFFFB0; break;
                case 15: byte ^= 0xFFFFFFBF; break;
            }
            
            byte -= (0x71 + (i - 8)); // 减法操作
            byte ^= 0xFFFFFFFE;        // 异或操作
            
            // 位操作
            uint8_t temp = (byte << 1) | (byte >> 7); // 循环左移1位
            byte = 1 - temp;                         // 减法操作
            byte ^= (0x21 + (i - 8));                // 异或操作
            byte += 0x3E;                            // 加法操作
        }
        
        buffer[i] = byte; // 将最终计算出的字节存入缓冲区
    }
}

/**
 * JNI主验证函数,Java代码会调用这个函数来进行验证
 * @param env JNI环境指针,提供了访问JVM的各种函数
 * @param thiz Java对象引用,代表调用这个本地方法的Java对象
 * @param input 用户从Java层输入的jstring对象
 * @return jint 验证结果 (1代表成功, 0代表失败)
 */
JNIEXPORT jint JNICALL
Java_cn_pojie52_cm01_MainActivity_check(JNIEnv *env, jobject thiz, jstring input) {
    // 栈上分配一些缓冲区,用于存放中间数据
    uint8_t stack_buffer[0xA0]; // 一个较大的缓冲区,可能用于防止栈溢出或作为临时空间
    uint8_t dest_buffer[0x68];  // 用于存放从Java传来的输入字符串
    uint8_t expected_string[0x29]; // 41字节,用于存放`generate_expected_string`生成的"正确答案"
    const char* input_str;       // C风格的字符串指针,指向输入内容
    char* base64_result;         // 指向Base64编码后的结果字符串
    int input_len;               // 存放输入字符串的长度
    int result = 0;              // 初始化验证结果为0 (失败)
    
    // 缓冲区清零,这是一个良好的安全编程习惯,防止使用未初始化的内存
    memset(stack_buffer, 0, sizeof(stack_buffer));
    memset(dest_buffer, 0, sizeof(dest_buffer));
    memset(expected_string, 0, sizeof(expected_string));
    
    // 通过JNI函数获取Java字符串的UTF-8编码长度
    input_len = (*env)->GetStringUTFLength(env, input);
    
    // **验证流程第一关:检查输入长度**
    // 要求的输入长度必须是30个字节 (0x1E)
    if (input_len != 0x1E) {
        return 0; // 如果长度不匹配,直接返回失败
    }
    
    // 将Java的jstring对象转换为C语言可以使用的UTF-8字符串
    input_str = (*env)->GetStringUTFChars(env, input, NULL);
    if (!input_str) { // 如果转换失败
        return 0; // 返回失败
    }
    
    // 将C字符串复制到我们自己的缓冲区`dest_buffer`中,以便后续操作
    strncpy((char*)dest_buffer, input_str, strlen(input_str));
    
    // 释放由 `GetStringUTFChars` 分配的内存,否则会造成JVM内存泄漏
    (*env)->ReleaseStringUTFChars(env, input, input_str);
    
    // **验证流程第二关:RC4加密**
    // 使用固定的密钥 "areyousure??????" 对用户的输入(现在在dest_buffer里)进行RC4加密。
    // 这个操作会直接修改`dest_buffer`的内容。
    sub_B90(dest_buffer, strlen((char*)dest_buffer), aAreyousure);
    
    // **验证流程第三关:自定义Base64编码**
    // 对刚刚RC4加密后的数据进行我们自定义的Base64编码。
    base64_result = sub_D90(dest_buffer, strlen((char*)dest_buffer));
    if (!base64_result) { // 如果编码过程(例如内存分配)失败
        return 0; // 返回失败
    }
    
    // **验证流程第四关:生成目标字符串**
    // 调用那个复杂的函数,动态地生成用于比较的"正确答案"
    generate_expected_string(expected_string, sizeof(expected_string));
    
    // **验证流程最后一关:字符串比较**
    // 逐个字节地比较我们处理后的用户输入 (`base64_result`) 和动态生成的正确答案 (`expected_string`)
    int i;
    for (i = 0; i < 0x29; i++) { // 比较41个字节
        uint8_t char1 = (uint8_t)base64_result[i]; // 取出我们结果中的一个字节
        uint8_t char2 = expected_string[i];      // 取出正确答案中的一个字节
        
        if (char1 != char2) { // 如果在任何一个位置上,字节不匹配
            result = 0; // 验证失败
            break;      // 立刻跳出循环,没必要再比下去了
        }
        
        // 这个检查可能有点多余,因为循环本身限制了长度,但它确保了如果提前遇到字符串结束符也能正确处理
        if (char1 == 0) {
            result = 1; // 如果两个字符串同时在匹配的位置结束,说明完全匹配
            break;
        }
    }
    
    // 如果上面的循环完整地执行了41次都没有因为不匹配而中断
    if (i == 0x29) {
        result = 1; // 说明所有41个字节都匹配,验证成功!
    }
    
    // 释放由 `sub_D90` (malloc) 分配的内存,防止内存泄漏
    free(base64_result);
    
    return result; // 将最终的验证结果 (1或0) 返回给Java层
}


// ============== 以下是辅助函数,用于开发和调试,在主验证逻辑中没有被调用 ==============

/**
 * 辅助函数:打印十六进制数据(调试用)
 * @param label 打印时显示的前缀标签
 * @param data 要打印的数据指针
 * @param len 要打印的数据长度
 */
void print_hex_data(const char* label, const uint8_t* data, int len) {
    printf("%s: ", label); // 打印标签
    for (int i = 0; i < len; i++) { // 遍历数据
        printf("%02X ", data[i]); // 以两位十六进制的格式打印每个字节,不足两位的前面补0
    }
    printf("\n"); // 打印换行符
}

/**
 * 辅助函数:验证算法的正确性(测试用)
 * 这个函数可以用来在C环境中独立测试整个加密和比较流程是否正确,而无需依赖Java和JNI环境。
 * @param test_input 一个已知的正确输入字符串
 * @return 如果测试通过返回1,否则返回0
 */
int test_algorithm(const char* test_input) {
    uint8_t test_buffer[64]; // 用于存放测试输入的缓冲区
    uint8_t expected[41];    // 用于存放生成的期望字符串
    char* base64_result;     // 用于存放编码结果
    
    // 复制测试输入到缓冲区
    strncpy((char*)test_buffer, test_input, strlen(test_input));
    
    // 执行与JNI函数中完全相同的处理流程
    sub_B90(test_buffer, strlen(test_input), aAreyousure);      // 1. RC4加密
    base64_result = sub_D90(test_buffer, strlen(test_input));   // 2. 自定义Base64编码
    generate_expected_string(expected, sizeof(expected));     // 3. 生成期望字符串
    
    // 使用 `memcmp` 比较内存块,这是比较二进制数据最高效的方法
    int match = (memcmp(base64_result, expected, 41) == 0);
    
    if (base64_result) { // 如果`base64_result`不是空指针
        free(base64_result); // 释放内存
    }
    
    return match; // 返回比较结果 (0或1)
}

这里的字符串解密可能有问题

 

 

举例说明:编码字符串 "A"

让我们用单个字符 "A" (ASCII值为0x41) 来演示两者之间的巨大差异。

1. 使用标准Base64编码 "A"

  • 输入'A' -> 0x41

  • 步骤1 (取数据和填充): 输入只有1个字节,不足3个。我们需要用两个0x00字节来填充,得到字节序列: 0x41, 0x00, 0x00

  • 步骤2 (转换为二进制):
    01000001 00000000 00000000

  • 步骤3 (分组为4个6位):
    010000 010000 000000 000000

  • 步骤4 (转换为十进制索引):
    16 16 0 0

  • 步骤5 (查表):

    • 索引16 -> 'Q'

    • 索引16 -> 'Q'

    • 索引0 -> 'A'

    • 索引0 -> 'A'
      得到 QQAA

  • 步骤6 (处理填充): 因为我们填充了2个字节,所以需要用2个 = 替换末尾的字符。

  • 最终结果QQ==


2. 使用 sub_D90 函数编码 "A"

  • 输入'A' -> data[0] = 0x41

  • 【关键步骤】进入混淆层处理 

    1. 第一次变换 (异或):
      byte = data[0] ^ base64_encode_table[0]
      byte = 0x41 ^ 0x2C = 0x6D

    2. 第二次变换 (循环右移2位):
      byte 的二进制是 01101101
      循环右移2位后变成 01011011
      byte = 0x5B

    3. 第三次变换 (异或):
      byte = byte ^ 0x82
      byte = 0x5B ^ 0x82 = 0xDD

    4. 第四次变换 (加法):
      byte = (byte + 0x3E) & 0xFF
      byte = (0xDD + 0x3E) & 0xFF = 0x11B & 0xFF = 0x1B

  • 混淆处理完成: 原始字节 'A' (0x41) 经过混淆后,变成了 0x1B。现在,sub_D90 会用这个新的值  去执行后续的Base64编码。

  • 后续步骤 (与标准Base64类似,但输入不同):

    • 取数据和填充: 输入是 0x1B,填充后得到 0x1B, 0x00, 0x00

    • 转换为二进制:
      00011011 00000000 00000000

    • 分组为4个6位:
      000110 110000 000000 000000

    • 转换为十进制索引:
      6 48 0 0

    • 查表:

      • 索引6 -> 'G'

      • 索引48 -> 'w'

      • 索引0 -> 'A'

      • 索引0 -> 'A'
        得到 GwAA

    • 处理填充: 同样,用2个 = 替换末尾字符。

  • 最终结果Gw==

对比结果

编码方式 输入 "A" 输出
标准Base64 0x41 QQ==
自定义  0x41 -> 0x1B Gw==

通过这个例子,你可以清楚地看到,仅仅因为在编码前增加了一系列混淆操作,即使是同一个输入,得到的Base64编码结果也截然不同。这就是这段代码实现反调试和反破解的关键所在:它创建了一种“私有”的编码协议,不了解其内部混淆算法的人,无法从其输出反推出原始输入。

 

字符串混淆这里过于复杂,无法完美还原,这里用hook的解决直接打印结果。

console.log("=== sub_B90 和 sub_D90 函数分析脚本 ===");

// 兼容的模块查找函数
function findModuleBase(moduleName) {
    // 优先尝试新方法
    if (typeof Module !== 'undefined' && typeof Module.findBaseAddress === 'function') {
        try {
            return Module.findBaseAddress(moduleName);
        } catch (e) {
            // 忽略错误,继续尝试其他方法
        }
    }

    // 回退到兼容方法
    if (typeof Process !== 'undefined' && typeof Process.enumerateModules === 'function') {
        try {
            var modules = Process.enumerateModules();
            for (var i = 0; i < modules.length; i++) {
                if (modules[i].name === moduleName) {
                    return modules[i].base;
                }
            }
        } catch (e) {
            // 忽略错误
        }
    }

    return null;
}

// 读取字符串内容 - 优化版本
function readString(ptr, maxLen) {
    if (!ptr || ptr.isNull()) return "NULL";
    try {
        maxLen = maxLen || 256;

        // 首先尝试读取为C字符串
        var str = ptr.readCString(maxLen);
        if (str && str.length > 0) {
            return str;
        }

        // 如果C字符串失败,尝试UTF-8
        str = ptr.readUtf8String(maxLen);
        if (str && str.length > 0) {
            return str;
        }

        return "无法读取字符串";
    } catch (e) {
        return "读取字符串失败: " + e.message;
    }
}

// 专门用于读取Base64字符串的函数
function readBase64String(ptr, maxLen) {
    if (!ptr || ptr.isNull()) return "NULL";
    try {
        maxLen = maxLen || 512;

        // 读取原始字节数据
        var data = ptr.readByteArray(maxLen);
        if (!data) return "无法读取数据";

        var bytes = new Uint8Array(data);
        var result = "";

        // 逐字节读取,直到遇到null终止符
        for (var i = 0; i < bytes.length; i++) {
            if (bytes[i] === 0) break; // null终止符
            if (bytes[i] >= 32 && bytes[i] <= 126) { // 可打印ASCII字符
                result += String.fromCharCode(bytes[i]);
            } else {
                // 遇到非可打印字符,可能不是有效的Base64字符串
                break;
            }
        }

        return result || "空字符串";
    } catch (e) {
        return "读取Base64字符串失败: " + e.message;
    }
}

// 安全读取可打印字符串
function readPrintableString(ptr, maxLen) {
    if (!ptr || ptr.isNull()) return "NULL";
    try {
        maxLen = maxLen || 256;
        var data = safeReadMemory(ptr, maxLen);
        if (!data) return "无法读取数据";

        var bytes = new Uint8Array(data);
        var result = "";
        var hasNonPrintable = false;

        for (var i = 0; i < bytes.length && i < maxLen; i++) {
            if (bytes[i] === 0) break; // null终止符
            if (bytes[i] >= 32 && bytes[i] <= 126) {
                result += String.fromCharCode(bytes[i]);
            } else {
                hasNonPrintable = true;
                result += "\\x" + bytes[i].toString(16).padStart(2, '0');
            }
        }

        return result + (hasNonPrintable ? " (包含非可打印字符)" : "");
    } catch (e) {
        return "读取失败: " + e.message;
    }
}

// 安全读取内存
function safeReadMemory(ptr, size) {
    if (!ptr || ptr.isNull()) return null;
    try {
        return ptr.readByteArray(size);
    } catch (e) {
        return null;
    }
}



// 分析sub_B90函数参数 - RC4加密函数
function analyzeSub_B90Args(args) {
    console.log("🔍 sub_B90 函数分析 (RC4加密算法):");
    console.log("   参数0 (数据指针): " + args[0]);
    console.log("   参数1 (数据长度): " + args[1].toInt32());
    console.log("   参数2 (密钥指针): " + args[2]);

    // 读取要加密的数据
    if (args[0] && !args[0].isNull()) {
        var dataLen = args[1].toInt32();
        if (dataLen > 0 && dataLen < 1024) {
            var data = safeReadMemory(args[0], dataLen);
            if (data) {
                console.log("   📄 输入数据 (" + dataLen + " 字节):");
                console.log("     字符串: " + readPrintableString(args[0], dataLen));
                console.log("     十六进制: " + Array.from(new Uint8Array(data)).map(b => b.toString(16).padStart(2, '0')).join(' '));
            }
        }
    }

    // 读取密钥
    if (args[2] && !args[2].isNull()) {
        var keyStr = readString(args[2], 64);
        console.log("   🔑 RC4密钥: " + keyStr);
        var keyData = safeReadMemory(args[2], Math.min(keyStr.length, 64));
        if (keyData) {
            console.log("   🔑 密钥十六进制: " + Array.from(new Uint8Array(keyData)).map(b => b.toString(16).padStart(2, '0')).join(' '));
        }
    }
}

// 分析sub_D90函数参数 - Base64编码函数
function analyzeSub_D90Args(args) {
    console.log("🔍 sub_D90 函数分析 (Base64编码):");
    console.log("   参数0 (数据指针): " + args[0]);
    console.log("   参数1 (数据长度): " + args[1].toInt32());

    // 读取要编码的数据
    if (args[0] && !args[0].isNull()) {
        var dataLen = args[1].toInt32();
        if (dataLen > 0 && dataLen < 1024) {
            var data = safeReadMemory(args[0], dataLen);
            if (data) {
                console.log("   📄 输入数据 (" + dataLen + " 字节) [二进制数据]:");
                console.log("     十六进制: " + Array.from(new Uint8Array(data)).map(b => b.toString(16).padStart(2, '0')).join(' '));

                // 尝试显示可打印字符
                var printable = readPrintableString(args[0], dataLen);
                if (printable && printable !== "NULL") {
                    console.log("     可打印部分: " + printable);
                }
            }
        }
    }
}

// 分析返回值
function analyzeReturnValue(retval, funcName) {
    if (!retval || retval.isNull()) {
        console.log("📋 " + funcName + " 返回值: NULL");
        return;
    }

    console.log("📋 " + funcName + " 返回值: " + retval);

    // 如果是sub_D90,返回值是Base64编码后的字符串指针
    if (funcName === "sub_D90") {
        var resultStr = readBase64String(retval, 512);
        console.log("   📄 Base64编码结果: " + resultStr);

        // 额外显示十六进制数据用于调试
        var rawData = safeReadMemory(retval, 64);
        if (rawData) {
            console.log("   🔍 返回值原始数据 (前64字节): " + Array.from(new Uint8Array(rawData)).map(b => b.toString(16).padStart(2, '0')).join(' '));
        }
    }
}

// 完善的 Hook 函数
function simpleHook() {
    function tryHook() {
        try {
            var addr_libnative = findModuleBase("libnative-lib.so");

            if (!addr_libnative) {
                setTimeout(tryHook, 2000);
                return;
            }

            var sub_B90 = addr_libnative.add(0xB90);
            var sub_D90 = addr_libnative.add(0xD90);
            var main_check = addr_libnative.add(0x7FC);
            var compare_loop = addr_libnative.add(0xB2C);

            // Hook sub_B90 - RC4加密函数
            Interceptor.attach(sub_B90, {
                onEnter: function (args) {
                    console.log("\n=== sub_B90 函数调用 (RC4加密) ===");

                    // 保存参数和原始数据
                    this.args = [];
                    this.originalData = null;
                    this.dataLen = 0;

                    for (var i = 0; i < 3; i++) {
                        this.args[i] = args[i];
                    }

                    // 保存原始数据用于对比
                    if (args[0] && !args[0].isNull()) {
                        this.dataLen = args[1].toInt32();
                        if (this.dataLen > 0 && this.dataLen < 1024) {
                            this.originalData = safeReadMemory(args[0], this.dataLen);
                        }
                    }

                    analyzeSub_B90Args(args);
                },

                onLeave: function (retval) {
                    console.log("📤 sub_B90 函数返回:");

                    // 显示加密后的数据
                    if (this.args[0] && !this.args[0].isNull() && this.originalData && this.dataLen > 0) {
                        var encryptedData = safeReadMemory(this.args[0], this.dataLen);
                        if (encryptedData) {
                            console.log("   🔒 RC4加密后数据:");
                            console.log("     十六进制: " + Array.from(new Uint8Array(encryptedData)).map(b => b.toString(16).padStart(2, '0')).join(' '));

                            // 对比原始数据和加密数据
                            var original = new Uint8Array(this.originalData);
                            var encrypted = new Uint8Array(encryptedData);
                            var changed = false;
                            for (var i = 0; i < Math.min(original.length, encrypted.length); i++) {
                                if (original[i] !== encrypted[i]) {
                                    changed = true;
                                    break;
                                }
                            }
                            console.log("   📊 数据是否改变: " + (changed ? "" : ""));
                        }
                    }

                    analyzeReturnValue(retval, "sub_B90");
                    console.log("================\n");
                }
            });

            // Hook sub_D90 - Base64编码函数
            Interceptor.attach(sub_D90, {
                onEnter: function (args) {
                    console.log("\n=== sub_D90 函数调用 (Base64编码) ===");

                    // 保存参数
                    this.args = [];
                    for (var i = 0; i < 2; i++) {
                        this.args[i] = args[i];
                    }

                    analyzeSub_D90Args(args);
                },

                onLeave: function (retval) {
                    console.log("📤 sub_D90 函数返回:");
                    analyzeReturnValue(retval, "sub_D90");
                    console.log("================\n");
                }
            });

            // Hook Java_cn_pojie52_cm01_MainActivity_check 主函数
            Interceptor.attach(main_check, {
                onEnter: function (args) {
                    console.log("\n=== Java_cn_pojie52_cm01_MainActivity_check 函数调用 ===");
                    console.log("🔍 JNI函数参数:");
                    console.log("   env: " + args[0]);
                    console.log("   thiz: " + args[1]);
                    console.log("   input: " + args[2]);

                    // 读取输入字符串
                    if (args[2] && !args[2].isNull()) {
                        try {
                            // JNI字符串需要特殊处理
                            var inputStr = Java.vm.getEnv().getStringUtfChars(args[2], null).readCString();
                            console.log("   📄 用户输入: " + inputStr);
                        } catch (e) {
                            console.log("   📄 用户输入: 无法读取JNI字符串");
                        }
                    }
                },

                onLeave: function (retval) {
                    console.log("📤 Java_cn_pojie52_cm01_MainActivity_check 函数返回:");
                    var result = retval.toInt32();
                    console.log("   📋 返回值: " + result + " (" + (result === 1 ? "✅ 验证通过" : "❌ 验证失败") + ")");
                    console.log("================\n");
                }
            });

            // Hook 字符串比较循环 - 监控w10和w11寄存器
            Interceptor.attach(compare_loop, {
                onEnter: function (args) {
                    // 这里我们需要读取寄存器状态
                    var context = this.context;
                    var x0 = context.x0;  // 第一个字符串指针
                    var x8 = context.x8;  // 当前比较位置索引
                    var x9 = context.x9;  // 第二个字符串指针(栈指针)

                    // 读取w10和w11的值(从内存中读取)
                    try {
                        var char1 = x0.add(x8.toInt32()).readU8();  // W10 = [X0,X8]
                        var char2 = x9.add(x8.toInt32()).readU8();  // W11 = [X9,X8]

                        console.log("🔍 字符串比较循环 (位置 " + x8.toInt32() + "):");
                        console.log("   W10 (字符1): 0x" + char1.toString(16).padStart(2, '0') + " ('" +
                            (char1 >= 32 && char1 <= 126 ? String.fromCharCode(char1) : '?') + "')");
                        console.log("   W11 (字符2): 0x" + char2.toString(16).padStart(2, '0') + " ('" +
                            (char2 >= 32 && char2 <= 126 ? String.fromCharCode(char2) : '?') + "')");
                        console.log("   比较结果: " + (char1 === char2 ? "✅ 相等" : "❌ 不等"));

                        // 如果不相等,显示更多调试信息
                        if (char1 !== char2) {
                            console.log("   🚨 字符不匹配! 验证将失败");

                            // 尝试读取完整的字符串进行对比
                            try {
                                var str1 = x0.readCString(64);
                                var str2 = x9.readCString(64);
                                console.log("   📄 完整字符串1: " + str1);
                                console.log("   📄 完整字符串2: " + str2);
                            } catch (e) {
                                console.log("   📄 无法读取完整字符串");
                            }
                        }
                    } catch (e) {
                        console.log("🔍 字符串比较循环 - 读取寄存器失败: " + e.message);
                    }
                }
            });

            console.log("✅ Hook 设置成功!");
            console.log("📍 main_check 地址: " + main_check + " (主验证函数)");
            console.log("📍 compare_loop 地址: " + compare_loop + " (字符串比较循环)");
            console.log("📍 sub_B90 地址: " + sub_B90 + " (RC4加密函数)");
            console.log("📍 sub_D90 地址: " + sub_D90 + " (Base64编码函数)");
            console.log("🎯 开始监控函数调用...");

        } catch (e) {
            console.log("❌ Hook 失败:", e.message);
            setTimeout(tryHook, 2000);
        }
    }

    tryHook();
}

// 启动
if (typeof Java !== 'undefined') {
    try {
        Java.perform(function () {
            setTimeout(simpleHook, 3000);
        });
    } catch (e) {
        setTimeout(simpleHook, 3000);
    }
} else {
    setTimeout(simpleHook, 3000);
}

 

 

这是binary ninja 反编译 

004007fc    {
004007fc        uint64_t x22 = _ReadMSR(tpidr_el0);
00400814        int64_t x8 = *(uint64_t*)(x22 + 0x28);
00400838        int32_t x19_1;
00400838        
00400838        if ((*(uint64_t*)(*(uint64_t*)arg1 + 0x540))(arg1, arg3) != 0x1e)
00400b54            x19_1 = 0;
00400838        else
00400838        {
00400850            char* s2 = (*(uint64_t*)(*(uint64_t*)arg1 + 0x548))(arg1, arg3, 0);
00400868            int128_t s1;
00400868            __builtin_memset(&s1, 0, 0x40);
0040087c            strncpy(&s1, s2, strlen(s2));
00400894            (*(uint64_t*)(*(uint64_t*)arg1 + 0x550))(arg1, arg3, s2);
004008b0            sub_400b90(&s1, strlen(&s1), "areyousure??????");
004008c4            void* x0_11;
004008c4            int128_t v0_1;
004008c4            int128_t v1_1;
004008c4            int128_t v2_1;
004008c4            uint128_t v3_1;
004008c4            int128_t v4_1;
004008c4            int128_t v5_1;
004008c4            uint128_t v6_1;
004008c4            x0_11 = sub_400d90(&s1, strlen(&s1));
004008d0            data_4011a1;
004008d0            data_4011b1;
004008d4            (*(int64_t*)((char*)data_4011b1 + 9));
004008e0            int128_t var_b0;
004008e0            __builtin_memcpy(&var_b0, 
004008e0                "\x2a\x13\x3f\x28\x2d\x34\x92\x9d\xbb\x36\x92\x92\xcd\x48\xb7\x13\x39\xcb\xc9\x"
004008e0            "c7\xa9\x4c\xcb\x46\x43\x4d\x24\xda\x50\xd9\x4c\x4f\x0b\x0d\x05\x0a\x13\x07\x81"
004008e0            "21\xa0", 
004008e0                0x29);
004008f4            (uint8_t)v0_1 = 0xb2;
004008f4            *(uint8_t*)((char*)v0_1)[1] = 0xb2;
004008f4            *(uint8_t*)((char*)v0_1)[2] = 0xb2;
004008f4            *(uint8_t*)((char*)v0_1)[3] = 0xb2;
004008f4            *(uint8_t*)((char*)v0_1)[4] = 0xb2;
004008f4            *(uint8_t*)((char*)v0_1)[5] = 0xb2;
004008f4            *(uint8_t*)((char*)v0_1)[6] = 0xb2;
004008f4            *(uint8_t*)((char*)v0_1)[7] = 0xb2;
004008f4            *(uint8_t*)((char*)v0_1)[8] = 0xb2;
004008f4            *(uint8_t*)((char*)v0_1)[9] = 0xb2;
004008f4            *(uint8_t*)((char*)v0_1)[0xa] = 0xb2;
004008f4            *(uint8_t*)((char*)v0_1)[0xb] = 0xb2;
004008f4            *(uint8_t*)((char*)v0_1)[0xc] = 0xb2;
004008f4            *(uint8_t*)((char*)v0_1)[0xd] = 0xb2;
004008f4            *(uint8_t*)((char*)v0_1)[0xe] = 0xb2;
004008f4            *(uint8_t*)((char*)v0_1)[0xf] = 0xb2;
00400904            (uint8_t)v4_1 = 0xfe;
00400904            *(uint8_t*)((char*)v4_1)[1] = 0xfe;
00400904            *(uint8_t*)((char*)v4_1)[2] = 0xfe;
00400904            *(uint8_t*)((char*)v4_1)[3] = 0xfe;
00400904            *(uint8_t*)((char*)v4_1)[4] = 0xfe;
00400904            *(uint8_t*)((char*)v4_1)[5] = 0xfe;
00400904            *(uint8_t*)((char*)v4_1)[6] = 0xfe;
00400904            *(uint8_t*)((char*)v4_1)[7] = 0xfe;
00400904            *(uint8_t*)((char*)v4_1)[8] = 0xfe;
00400904            *(uint8_t*)((char*)v4_1)[9] = 0xfe;
00400904            *(uint8_t*)((char*)v4_1)[0xa] = 0xfe;
00400904            *(uint8_t*)((char*)v4_1)[0xb] = 0xfe;
00400904            *(uint8_t*)((char*)v4_1)[0xc] = 0xfe;
00400904            *(uint8_t*)((char*)v4_1)[0xd] = 0xfe;
00400904            *(uint8_t*)((char*)v4_1)[0xe] = 0xfe;
00400904            *(uint8_t*)((char*)v4_1)[0xf] = 0xfe;
00400908            (uint8_t)var_b0 = 0x35;
00400918            uint128_t v2_5 = (((var_b0 + v0_1) ^ data_401130) + data_401140) ^ v4_1;
0040091c            (uint8_t)v6_1 = (uint8_t)v2_5 >> 7;
0040091c            *(uint8_t*)((char*)v6_1)[1] = *(uint8_t*)((char*)v2_5)[1] >> 7;
0040091c            *(uint8_t*)((char*)v6_1)[2] = *(uint8_t*)((char*)v2_5)[2] >> 7;
0040091c            *(uint8_t*)((char*)v6_1)[3] = *(uint8_t*)((char*)v2_5)[3] >> 7;
0040091c            *(uint8_t*)((char*)v6_1)[4] = *(uint8_t*)((char*)v2_5)[4] >> 7;
0040091c            *(uint8_t*)((char*)v6_1)[5] = *(uint8_t*)((char*)v2_5)[5] >> 7;
0040091c            *(uint8_t*)((char*)v6_1)[6] = *(uint8_t*)((char*)v2_5)[6] >> 7;
0040091c            *(uint8_t*)((char*)v6_1)[7] = *(uint8_t*)((char*)v2_5)[7] >> 7;
0040091c            *(uint8_t*)((char*)v6_1)[8] = *(uint8_t*)((char*)v2_5)[8] >> 7;
0040091c            *(uint8_t*)((char*)v6_1)[9] = *(uint8_t*)((char*)v2_5)[9] >> 7;
0040091c            *(uint8_t*)((char*)v6_1)[0xa] = *(uint8_t*)((char*)v2_5)[0xa] >> 7;
0040091c            *(uint8_t*)((char*)v6_1)[0xb] = *(uint8_t*)((char*)v2_5)[0xb] >> 7;
0040091c            *(uint8_t*)((char*)v6_1)[0xc] = *(uint8_t*)((char*)v2_5)[0xc] >> 7;
0040091c            *(uint8_t*)((char*)v6_1)[0xd] = *(uint8_t*)((char*)v2_5)[0xd] >> 7;
0040091c            *(uint8_t*)((char*)v6_1)[0xe] = *(uint8_t*)((char*)v2_5)[0xe] >> 7;
0040091c            *(uint8_t*)((char*)v6_1)[0xf] = *(uint8_t*)((char*)v2_5)[0xf] >> 7;
00400920            (uint8_t)v2_5 <<= 1;
00400920            *(uint8_t*)((char*)v2_5)[1] <<= 1;
00400920            *(uint8_t*)((char*)v2_5)[2] <<= 1;
00400920            *(uint8_t*)((char*)v2_5)[3] <<= 1;
00400920            *(uint8_t*)((char*)v2_5)[4] <<= 1;
00400920            *(uint8_t*)((char*)v2_5)[5] <<= 1;
00400920            *(uint8_t*)((char*)v2_5)[6] <<= 1;
00400920            *(uint8_t*)((char*)v2_5)[7] <<= 1;
00400920            *(uint8_t*)((char*)v2_5)[8] <<= 1;
00400920            *(uint8_t*)((char*)v2_5)[9] <<= 1;
00400920            *(uint8_t*)((char*)v2_5)[0xa] <<= 1;
00400920            *(uint8_t*)((char*)v2_5)[0xb] <<= 1;
00400920            *(uint8_t*)((char*)v2_5)[0xc] <<= 1;
00400920            *(uint8_t*)((char*)v2_5)[0xd] <<= 1;
00400920            *(uint8_t*)((char*)v2_5)[0xe] <<= 1;
00400920            *(uint8_t*)((char*)v2_5)[0xf] <<= 1;
00400928            (uint8_t)v5_1 = 1;
00400928            *(uint8_t*)((char*)v5_1)[1] = 1;
00400928            *(uint8_t*)((char*)v5_1)[2] = 1;
00400928            *(uint8_t*)((char*)v5_1)[3] = 1;
00400928            *(uint8_t*)((char*)v5_1)[4] = 1;
00400928            *(uint8_t*)((char*)v5_1)[5] = 1;
00400928            *(uint8_t*)((char*)v5_1)[6] = 1;
00400928            *(uint8_t*)((char*)v5_1)[7] = 1;
00400928            *(uint8_t*)((char*)v5_1)[8] = 1;
00400928            *(uint8_t*)((char*)v5_1)[9] = 1;
00400928            *(uint8_t*)((char*)v5_1)[0xa] = 1;
00400928            *(uint8_t*)((char*)v5_1)[0xb] = 1;
00400928            *(uint8_t*)((char*)v5_1)[0xc] = 1;
00400928            *(uint8_t*)((char*)v5_1)[0xd] = 1;
00400928            *(uint8_t*)((char*)v5_1)[0xe] = 1;
00400928            *(uint8_t*)((char*)v5_1)[0xf] = 1;
00400934            (uint8_t)v1_1 = 0x3e;
00400934            *(uint8_t*)((char*)v1_1)[1] = 0x3e;
00400934            *(uint8_t*)((char*)v1_1)[2] = 0x3e;
00400934            *(uint8_t*)((char*)v1_1)[3] = 0x3e;
00400934            *(uint8_t*)((char*)v1_1)[4] = 0x3e;
00400934            *(uint8_t*)((char*)v1_1)[5] = 0x3e;
00400934            *(uint8_t*)((char*)v1_1)[6] = 0x3e;
00400934            *(uint8_t*)((char*)v1_1)[7] = 0x3e;
00400934            *(uint8_t*)((char*)v1_1)[8] = 0x3e;
00400934            *(uint8_t*)((char*)v1_1)[9] = 0x3e;
00400934            *(uint8_t*)((char*)v1_1)[0xa] = 0x3e;
00400934            *(uint8_t*)((char*)v1_1)[0xb] = 0x3e;
00400934            *(uint8_t*)((char*)v1_1)[0xc] = 0x3e;
00400934            *(uint8_t*)((char*)v1_1)[0xd] = 0x3e;
00400934            *(uint8_t*)((char*)v1_1)[0xe] = 0x3e;
00400934            *(uint8_t*)((char*)v1_1)[0xf] = 0x3e;
00400940            int128_t v2_9 = ((v5_1 - vorrq_s8(v6_1, v2_5)) ^ data_401150) + v1_1;
00400944            v3_1 = data_401160;
00400950            var_b0 = v2_9;
00400964            char var_8f;
00400964            int32_t x10_5 = ((((uint32_t)var_8f - 0x4e) ^ 0xffffffb6) - 0x71) ^ 0xfffffffe;
00400994            char var_8f_1 = ((1
00400994                - (((char)(x10_5 << 1) & 0xfe) | (char)((x10_5 & 0x80) >> 7))) ^ 0x21) + 0x3e;
004009b4            char var_8e;
004009b4            int32_t x10_12 = ((((uint32_t)var_8e - 0x4e) ^ 0xffffffb5) - 0x72) ^ 0xfffffffe;
004009cc            char var_8e_1 = ((1
004009cc                - (((char)(x10_12 << 1) & 0xfe) | (char)((x10_12 & 0x80) >> 7))) ^ 0x22)
004009cc                + 0x3e;
004009d8            int128_t var_a0;
004009d8            uint128_t v0_5 = (((var_a0 + v0_1) ^ v3_1) + data_401170) ^ v4_1;
004009dc            (uint8_t)v3_1 = (uint8_t)v0_5 >> 7;
004009dc            *(uint8_t*)((char*)v3_1)[1] = *(uint8_t*)((char*)v0_5)[1] >> 7;
004009dc            *(uint8_t*)((char*)v3_1)[2] = *(uint8_t*)((char*)v0_5)[2] >> 7;
004009dc            *(uint8_t*)((char*)v3_1)[3] = *(uint8_t*)((char*)v0_5)[3] >> 7;
004009dc            *(uint8_t*)((char*)v3_1)[4] = *(uint8_t*)((char*)v0_5)[4] >> 7;
004009dc            *(uint8_t*)((char*)v3_1)[5] = *(uint8_t*)((char*)v0_5)[5] >> 7;
004009dc            *(uint8_t*)((char*)v3_1)[6] = *(uint8_t*)((char*)v0_5)[6] >> 7;
004009dc            *(uint8_t*)((char*)v3_1)[7] = *(uint8_t*)((char*)v0_5)[7] >> 7;
004009dc            *(uint8_t*)((char*)v3_1)[8] = *(uint8_t*)((char*)v0_5)[8] >> 7;
004009dc            *(uint8_t*)((char*)v3_1)[9] = *(uint8_t*)((char*)v0_5)[9] >> 7;
004009dc            *(uint8_t*)((char*)v3_1)[0xa] = *(uint8_t*)((char*)v0_5)[0xa] >> 7;
004009dc            *(uint8_t*)((char*)v3_1)[0xb] = *(uint8_t*)((char*)v0_5)[0xb] >> 7;
004009dc            *(uint8_t*)((char*)v3_1)[0xc] = *(uint8_t*)((char*)v0_5)[0xc] >> 7;
004009dc            *(uint8_t*)((char*)v3_1)[0xd] = *(uint8_t*)((char*)v0_5)[0xd] >> 7;
004009dc            *(uint8_t*)((char*)v3_1)[0xe] = *(uint8_t*)((char*)v0_5)[0xe] >> 7;
004009dc            *(uint8_t*)((char*)v3_1)[0xf] = *(uint8_t*)((char*)v0_5)[0xf] >> 7;
004009ec            char var_8d;
004009ec            int32_t x10_20 = ((((uint32_t)var_8d - 0x4e) ^ 0xffffffb4) - 0x73) ^ 0xfffffffe;
00400a08            char var_8d_1 = ((1
00400a08                - (((char)(x10_20 << 1) & 0xfe) | (char)((x10_20 & 0x80) >> 7))) ^ 0x23)
00400a08                + 0x3e;
00400a14            (uint8_t)v0_5 <<= 1;
00400a14            *(uint8_t*)((char*)v0_5)[1] <<= 1;
00400a14            *(uint8_t*)((char*)v0_5)[2] <<= 1;
00400a14            *(uint8_t*)((char*)v0_5)[3] <<= 1;
00400a14            *(uint8_t*)((char*)v0_5)[4] <<= 1;
00400a14            *(uint8_t*)((char*)v0_5)[5] <<= 1;
00400a14            *(uint8_t*)((char*)v0_5)[6] <<= 1;
00400a14            *(uint8_t*)((char*)v0_5)[7] <<= 1;
00400a14            *(uint8_t*)((char*)v0_5)[8] <<= 1;
00400a14            *(uint8_t*)((char*)v0_5)[9] <<= 1;
00400a14            *(uint8_t*)((char*)v0_5)[0xa] <<= 1;
00400a14            *(uint8_t*)((char*)v0_5)[0xb] <<= 1;
00400a14            *(uint8_t*)((char*)v0_5)[0xc] <<= 1;
00400a14            *(uint8_t*)((char*)v0_5)[0xd] <<= 1;
00400a14            *(uint8_t*)((char*)v0_5)[0xe] <<= 1;
00400a14            *(uint8_t*)((char*)v0_5)[0xf] <<= 1;
00400a28            char var_8c;
00400a28            int32_t x10_27 = ((((uint32_t)var_8c - 0x4e) ^ 0xffffffb3) - 0x74) ^ 0xfffffffe;
00400a44            char var_8c_1 = ((1
00400a44                - (((char)(x10_27 << 1) & 0xfe) | (char)((x10_27 & 0x80) >> 7))) ^ 0x24)
00400a44                + 0x3e;
00400a64            char var_8b;
00400a64            int32_t x10_34 = ((((uint32_t)var_8b - 0x4e) ^ 0xffffffb2) - 0x75) ^ 0xfffffffe;
00400a80            char var_8b_1 = ((1
00400a80                - (((char)(x10_34 << 1) & 0xfe) | (char)((x10_34 & 0x80) >> 7))) ^ 0x25)
00400a80                + 0x3e;
00400a8c            int64_t x8_7 = 0;
00400aa0            char var_8a;
00400aa0            int32_t x10_41 = ((((uint32_t)var_8a - 0x4e) ^ 0xffffffb1) - 0x76) ^ 0xfffffffe;
00400abc            char var_8a_1 = ((1
00400abc                - (((char)(x10_41 << 1) & 0xfe) | (char)((x10_41 & 0x80) >> 7))) ^ 0x26)
00400abc                + 0x3e;
00400ac8            var_a0 = ((v5_1 - vorrq_s8(v3_1, v0_5)) ^ data_401180) + v1_1;
00400ad8            char var_89;
00400ad8            int32_t x10_48 = ((((uint32_t)var_89 - 0x4e) ^ 0xffffffb0) - 0x77) ^ 0xfffffffe;
00400af4            char var_89_1 = ((1
00400af4                - (((char)(x10_48 << 1) & 0xfe) | (char)((x10_48 & 0x80) >> 7))) ^ 0x27)
00400af4                + 0x3e;
00400b08            char var_88;
00400b08            int32_t x10_55 = ((((uint32_t)var_88 - 0x4e) ^ 0xffffffbf) - 0x78) ^ 0xfffffffe;
00400b24            char var_88_1 = ((1
00400b24                - (((char)(x10_55 << 1) & 0xfe) | (char)((x10_55 & 0x80) >> 7))) ^ 0x28)
00400b24                + 0x3e;
00400b24            
00400b2c            while (true)
00400b2c            {
00400b2c                uint32_t x10_56 = (uint32_t)*(uint8_t*)((char*)x0_11 + x8_7);
00400b2c                
00400b38                if (x10_56 != (uint32_t)*(uint8_t*)(&var_b0 + x8_7))
00400b38                {
00400b5c                    x19_1 = 0;
00400b5c                    break;
00400b38                }
00400b38                
00400b3c                if (x10_56)
00400b3c                {
00400b40                    x8_7 += 1;
00400b40                    
00400b48                    if (x8_7 != 0x29)
00400b48                        continue;
00400b3c                }
00400b3c                
00400b4c                x19_1 = 1;
00400b50                break;
00400b2c            }
00400b2c            
00400b60            free(x0_11);
00400838        }
00400838        
00400b70        if (*(uint64_t*)(x22 + 0x28) == x8)
00400b8c            return (uint64_t)x19_1;
00400b8c        
00400b8c        __stack_chk_fail();
00400b8c        /* no return */
004007fc    }

 

 

由此可以看出难点是字符串混淆的静态解密,这里重点分析

好的,我们来逐行分析这段 ARM64 汇编代码。这段代码看起来是在执行一些加密或数据混淆操作,常见于逆向工程中的 flag 校验函数。


codeArmasm
 
 
 
.text:00000000000008C0                 ADD             X0, SP, #0xA0+dest
  • 注解: 将当前栈指针 SP 的地址加上一个偏移量 (0xA0+dest,这是 IDA Pro 为了可读性标记的栈变量位置),得到的结果存入 X0 寄存器。这个结果是栈上变量 dest 的内存地址。通常这是为了给接下来的函数调用准备一个参数,比如一个缓冲区的地址。

codeArmasm
 
 
 
.text:00000000000008C4                 BL              sub_D90
  • 注解BL (Branch with Link) 指令,表示调用地址为 sub_D90 的子函数。调用前,下一条指令的地址 (0x8C8) 会被保存在 X30 (链接寄存器 LR) 中,以便函数返回。根据上一条指令,这个函数很可能使用了 X0 寄存器中的地址作为参数,比如向该地址写入数据。

codeArmasm
 
 
 
.text:00000000000008C8                 ADRL            X9, xmmword_11A1
  • 注解ADRL 是一个伪指令,通常会扩展成两条指令 (ADRP + ADD)。它的作用是加载一个距离当前指令较远的标签地址。这里是将 xmmword_11A1 这个数据标签的完整 64 位地址加载到 X9 寄存器。xmmword 暗示这是一个 128 位(16 字节)的数据。

codeArmasm
 
 
 
.text:00000000000008D0                 LDP             Q1, Q2, [X9]
  • 注解LDP (Load Pair) 指令。从 X9 寄存器指向的内存地址处,连续加载两个 128 位(16 字节)的数据,第一个 16 字节存入 Q1 寄存器,紧接着的第二个 16 字节存入 Q2 寄存器。总共加载了 32 字节。

codeArmasm
 
 
 
.text:00000000000008D4                 LDUR            Q3, [X9,#0x19]
  • 注解LDUR (Load Register Unscaled) 指令。从 X9 寄存器的地址加上 0x19 (十进制 25) 的偏移处,加载一个 128 位(16 字节)的数据到 Q3 寄存器。

codeArmasm
 
 
 
.text:00000000000008D8                 ADRP            X11, #xmmword_1130@PAGE
  • 注解ADRP (Address of Page) 指令。计算 xmmword_1130 标签所在内存页(大小通常为 4KB)的基地址,并将这个基地址存入 X11 寄存器。这是为了访问程序中的全局/静态数据。

codeArmasm
 
 
 
.text:00000000000008DC                 ADRP            X9, #xmmword_1140@PAGE
  • 注解: 同上,计算 xmmword_1140 所在内存页的基地址,存入 X9 寄存器。

codeArmasm
 
 
 
.text:00000000000008E0                 STP             Q1, Q2, [SP,#0xA0+var_A0]
  • 注解STP (Store Pair) 指令。将 Q1 和 Q2 寄存器中的内容(总共 32 字节)成对地存储到栈上,起始地址为 SP + #0xA0+var_A0

codeArmasm
 
 
 
.text:00000000000008E4                 LDUR            Q2, [SP,#0xA0+var_A0+1]
  • 注解: 从栈上的 SP + #0xA0+var_A0+1 位置加载 16 字节数据到 Q2 寄存器。这是一个有 1 字节偏移的加载,可能是一种数据对齐或混淆技巧。

codeArmasm
 
 
 
.text:00000000000008E8                 STUR            Q3, [SP,#0xA0+var_87]
  • 注解STUR (Store Register Unscaled) 指令。将 Q3 寄存器的内容存储到栈上 SP + #0xA0+var_87 的位置。

codeArmasm
 
 
 
.text:00000000000008EC                 LDR             Q3, [X11,#xmmword_1130@PAGEOFF]
  • 注解LDR (Load Register) 指令。在 X11 存储的页基地址上,加上 xmmword_1130 的页内偏移量,从计算出的完整地址加载 16 字节数据到 Q3 寄存器(这会覆盖 Q3 之前的值)。

codeArmasm
 
 
 
.text:00000000000008F0                 LDR             Q6, [X9,#xmmword_1140@PAGEOFF]
  • 注解: 同上,从 xmmword_1140 的完整地址加载 16 字节数据到 Q6 寄存器。

codeArmasm
 
 
 
.text:00000000000008F4                 MOVI            V0.16B, #0xB2
  • 注解MOVI (Move Immediate) 指令。将 V0 寄存器(Q0 的别名)视为 16 个独立的字节 (.16B),并将每个字节都设置为立即数 0xB2

codeArmasm
 
 
 
.text:00000000000008F8                 ADD             V2.16B, V2.16B, V0.16B
  • 注解ADD 指令。将 V2 寄存器中的 16 个字节与 V0 寄存器中对应的 16 个字节分别相加,结果存回 V2。相当于对 V2 的每个字节都加 0xB2

codeArmasm
 
 
 
.text:00000000000008FC                 MOV             W10, #0x35 ; '5'
  • 注解: 将立即数 0x35 (ASCII 字符 '5') 移动到 32 位寄存器 W10 中。

codeArmasm
 
 
 
.text:0000000000000900                 EOR             V2.16B, V2.16B, V3.16B
  • 注解EOR (Exclusive OR) 指令。将 V2 寄存器中的 16 个字节与 V3 寄存器中对应的 16 个字节分别进行异或操作,结果存回 V2

codeArmasm
 
 
 
.text:0000000000000904                 MOVI            V4.16B, #0xFE
  • 注解: 将 V4 寄存器的 16 个字节全部设置为立即数 0xFE

codeArmasm
 
 
 
.text:0000000000000908                 STRB            W10, [SP,#0xA0+var_A0]
  • 注解STRB (Store Register Byte) 指令。将 W10 寄存器的低 8 位(也就是 0x35)存储到栈上 SP + #0xA0+var_A0 的位置。


接下来的几组指令,是对 V2 寄存器进行一个复杂的变换,可以看作一个加密轮函数的一部分

codeArmasm
 
 
 
.text:000000000000090C                 ADRP            X10, #xmmword_1150@PAGE
.text:0000000000000910                 ADD             V2.16B, V2.16B, V6.16B
.text:0000000000000914                 LDR             Q3, [X10,#xmmword_1150@PAGEOFF]
.text:0000000000000918                 EOR             V2.16B, V2.16B, V4.16B
  • 注解:

    • ADRP: 获取 xmmword_1150 所在内存页的基地址到 X10

    • ADD: 将 V2 的 16 个字节与 V6 对应的字节相加。

    • LDR: 从 xmmword_1150 加载 16 字节数据到 Q3

    • EOR: 将 V2 的 16 个字节与 V4 对应的字节(都是 0xFE)进行异或。

codeArmasm
 
 
 
.text:000000000000091C                 USHR            V6.16B, V2.16B, #7
.text:0000000000000920                 SHL             V2.16B, V2.16B, #1
.text:000000000000092C                 ORR             V2.16B, V6.16B, V2.16B
  • 注解: 这三条指令组合起来,对 V2 寄存器中的 16 个字节分别执行“循环左移 1 位” 的操作。

    • USHR: 将 V2 的每个字节无符号右移 7 位,结果存入 V6 (这实际上是把每个字节的最高位移到了最低位)。

    • SHL: 将 V2 的每个字节左移 1 位 (最高位丢失,最低位补 0)。

    • ORR: 将前两步的结果进行或运算,就把之前移到 V6 最低位的原始最高位,补到了 V2 的最低位上,从而完成了循环左移。

codeArmasm
 
 
 
.text:0000000000000924                 LDRB            W10, [SP,#0xA0+var_87+8]
.text:0000000000000928                 MOVI            V5.16B, #1
.text:0000000000000930                 SUB             V2.16B, V5.16B, V2.16B
.text:0000000000000934                 MOVI            V1.16B, #0x3E ; '>'
.text:0000000000000938                 ADRP            X11, #xmmword_1160@PAGE
.text:000000000000093C                 EOR             V2.16B, V2.16B, V3.16B
.text:0000000000000940                 ADD             V2.16B, V2.16B, V1.16B
.text:0000000000000944                 LDR             Q3, [X11,#xmmword_1160@PAGEOFF]
  • 注解: 这部分继续对 V2 进行一系列变换。

    • LDRB: 从栈上加载一个字节到 W10

    • MOVI: 将 V5 的所有字节设为 1。

    • SUBV2 中的每个字节 b 变为 1 - b

    • MOVI: 将 V1 的所有字节设为 0x3E ('>')。

    • ADRP/LDR: 加载 xmmword_1160 的数据到 Q3

    • EORV2 与 V3 逐字节异或。

    • ADDV2 的每个字节加上 0x3E


接下来的大段代码是一个重复的模式,它逐个字节地从栈上读取数据,进行一套复杂的、固定的数学变换,然后写回原处。这是一种典型的代码混淆,目的是让逆向分析变得困难。

我们详细分析第一个字节的处理流程(0x94C 到 0x994),后续的流程与此类似,只是常量不同。

codeArmasm
 
 
 
.text:000000000000094C                 SUB             W10, W10, #0x4E ; 'N'
.text:0000000000000950                 STUR            Q2, [SP,#0xA0+var_A0+1]
.text:0000000000000954                 LDUR            Q2, [SP,#0x11]
.text:0000000000000958                 EOR             W10, W10, W11
.text:000000000000095C                 SUB             W10, W10, #0x71 ; 'q'
.text:0000000000000960                 ADRP            X9, #xmmword_1170@PAGE
.text:0000000000000964                 EOR             W10, W10, #0xFFFFFFFE
.text:0000000000000968                 LDR             Q6, [X9,#xmmword_1170@PAGEOFF]
.text:000000000000096C                 ADRP            X9, #xmmword_1180@PAGE
.text:0000000000000970                 LSL             W11, W10, #1
.text:0000000000000974                 ADD             V0.16B, V2.16B, V0.16B
.text:0000000000000978                 LDR             Q2, [X9,#xmmword_1180@PAGEOFF]
.text:000000000000097C                 MOV             W9, #1
.text:0000000000000980                 BFXIL           W11, W10, #7, #1
.text:0000000000000984                 MOV             W10, #0x21 ; '!'
.text:0000000000000988                 SUB             W11, W9, W11
.text:000000000000098C                 EOR             W10, W11, W10
.text:0000000000000990                 ADD             W10, W10, #0x3E ; '>'
.text:0000000000000994                 STRB            W10, [SP,#0xA0+var_87+8]
  • 注解: 这是一个字节变换的完整流程 (以 [SP,#0xA0+var_87+8] 的字节为例,该字节之前已加载到 W10)

    • 0x94CW10 的值减去 'N' (0x4E)。

    • 0x958W10 与 W11 (值为 0xFFFFFFB6) 进行异或。

    • 0x95C: 结果再减去 'q' (0x71)。

    • 0x964: 结果再与 0xFFFFFFFE 进行异或(相当于翻转除最低位外的所有位)。

    • 0x970 和 0x980: 这两条指令(LSL 和 BFXIL)组合起来,实现了对 W10 中字节的 循环左移 1 位,结果存在 W11

    • 0x97CMOV W9, #1,将 1 存入 W9

    • 0x988: 用 1 减去循环左移后的结果 (W11 = 1 - W11)。

    • 0x984 和 0x98C: 将上一步的结果与常量 '!' (0x21) 进行异或。

    • 0x990: 结果再加上常量 '>' (0x3E)。

    • 0x994STRB (Store Byte),将最终计算出的新字节存回到它原来的栈位置。

    • 中间夹杂的 


后续代码块 (

  • 从 : 处理 [SP,#0xA0+var_87+9] 的字节,使用的常量变为 0xFFFFFFB5'r' (0x72), 0x22222222

  • 从 : 处理 [SP,#0xA0+var_87+0xA] 的字节,使用的常量变为 0xFFFFFFB4's' (0x73), '#' (0x23)。

  • 从 : 处理 [SP,#0xA0+var_87+0xB] 的字节,使用的常量变为 0xFFFFFFB3't' (0x74), '$' (0x24)。

  • 从 : 处理 [SP,#0xA0+var_87+0xC] 的字节,使用的常量变为 0xFFFFFFB2'u' (0x75), '%' (0x25)。

  • 从 : 处理 [SP,#0xA0+var_87+0xD] 的字节,使用的常量变为 0xFFFFFFB1'v' (0x76), '&' (0x26)。

  • 从 : 处理 [SP,#0xA0+var_87+0xE] 的字节,使用的常量变为 0xFFFFFFB0'w' (0x77), ''' (0x27)。

  • 从 : 处理 [SP,#0xA0+var_87+0xF] 的字节,使用的常量变为 0xFFFFFFBF'x' (0x78), '(' (0x28)。


结尾部分

codeArmasm
 
 
 
.text:0000000000000B28                 MOV             X9, SP
  • 注解: 将栈指针 SP 的当前值移动到 X9 寄存器。

codeArmasm
 
 
 
.text:0000000000000B2C loc_B2C                                 ; CODE XREF: Java_cn_pojie52_cm01_MainActivity_check+34C↓j
  • 注解: 这是一个代码标签 loc_B2C,表示一个跳转目标。注释提示有一个来自 Java_cn_pojie52_cm01_MainActivity_check 函数的跳转会跳到这里。

codeArmasm
 
 
 
.text:0000000000000B2C                 LDRB            W10, [X0,X8]
  • 注解LDRB (Load Register Byte)。从基地址 X0 加上偏移量 X8 的内存位置加载一个字节到 W10 寄存器。这通常是循环处理一个字符串或字节数组的开始,X8 作为循环索引。

codeArmasm
 
 
 
.text:0000000000000B30                 LDRB            W11, [X9,X8]
  • 注解: 同上,从基地址 X9 (也就是栈 SP) 加上偏移量 X8 的内存位置加载一个字节到 W11 寄存器。

  • 综合来看: 这里很可能是在比较 X0 指向的缓冲区(可能是用户输入)和 X9 指向的缓冲区(经过前面复杂计算得到的正确结果)的内容是否一致,X8 作为索引,逐字节进行比较。

 

 

 

 

 

 

 

 

 

 

 

 

posted on 2025-08-17 01:44  GKLBB  阅读(23)  评论(0)    收藏  举报