使用 Unicorn 还原变异 CRC32 算法

版权归作者所有,如有转发,请注明文章出处:cyrus-studio.github.io/blog/

ARM64Emulator

ARM64Emulator 是基于 Unicorn 实现一个轻量级的 ARM64 模拟器,具备代码加载、内存映射、指令执行、反汇编、寄存器监控、Hook、Patch、字符串处理等功能

项目地址:github.com/CYRUS-STUDI...

这里主要使用 ARM64Emulator 模拟执行 so 中的汇编指令实现算法还原。

目标应用信息

app 中实现一个 CRC32 算法变形,具体实现在 so 中 modifiedCRC32 函数,现在要通过 unicorn 和 IDA Pro 逆向还原 so 中的算法。

项目地址:github.com/CYRUS-STUDI...

目标 so 文件地址:github.com/CYRUS-STUDI...

使用 ARM64Emulator 加载 so 并执行 modifiedCRC32 函数的汇编指令实现算法还原。

python 复制代码
from unicorn.arm64_const import *
import struct
import re

from ARM64Emulator import ARM64Emulator


def modifiedCRC32(data):
    emulator = ARM64Emulator("libcrc32.so")

    mu = emulator.mu

    # 字符串地址
    str_addr = emulator.STACK_BASE + emulator.STACK_SIZE
    emulator.mu.mem_map(str_addr, 0x1000)  # 4KB
    
    ...

    # 初始化传参
    emulator.set_x0(0) # JNIEnv*
    emulator.set_x1(0) # jobject
    emulator.set_x2(str_addr) # input

    # 运行
    emulator.run(0x1C040, 0x1C2D8)

    return hex(mu.reg_read(UC_ARM64_REG_X4))

if __name__ == "__main__":
    result = modifiedCRC32("546NBypEyvgBt")
    print(f"modifiedCRC32 result: '{result}'")

但汇编指令中有调用到一些 JNI 接口函数和系统函数,需要分析汇编代码并替换成对应的 Python 实现。

关键汇编代码分析

_ReadStatusReg

汇编代码如下:

arduino 复制代码
.text:000000000001C05C 59 D0 3B D5                   MRS             X25, #3, c13, c0, #2
.text:000000000001C060 3A 01 00 D0                   ADRP            X26, #modified_crc32_table_ptr@PAGE
.text:000000000001C064 F4 03 02 AA                   MOV             X20, X2
.text:000000000001C068 28 17 40 F9                   LDR             X8, [X25,#0x28]
.text:000000000001C06C F3 03 00 AA                   MOV             X19, X0
.text:000000000001C070 A8 83 1F F8                   STUR            X8, [X29,#var_8]

MRS X25, #3, c13, c0, #2

  • MRS(Move from System Register)用于 读取系统寄存器。

  • #3, c13, c0, #2 对应 TPIDR_EL1(线程特定寄存器,常用于存储 TLS 线程本地存储指针)。

  • 这行指令 将 TPIDR_EL1 读入 X25,可能是为了访问某个线程局部存储的数据。

ADRP X26, #modified_crc32_table_ptr@PAGE

  • ADRP(Address of Page)用于 获取 modified_crc32_table_ptr 所在的内存页地址,存入 X26。

  • 这个指令不会提供完整地址,需要 结合 ADD 或 LDR 获取最终地址。

MOV X20, X2

  • 保存 X2 到 X20

LDR X8, [X25,#0x28]

  • 从 X25(即 TPIDR_EL1)的偏移 0x28 处读取一个 64-bit 值,存入 X8。

  • X25 指向 TLS(线程局部存储),所以 0x28 偏移量可能是线程相关的变量。

MOV X19, X0

  • 备份 X0 到 X19,可能是 函数的第一个参数,用于后续计算。

STUR X8, [X29,#var_8]

  • 把 X8 存入 X29(即 FP,帧指针)的 var_8 位置,通常是局部变量或栈空间的一部分。

  • STUR(Store Register Unscaled)是 STR 的变种,支持负偏移。

这段汇编代码中,主要通过 MRS 指令读取系统寄存器中内存访问异常相关状态信息信息,只需要把相关的两条指令 nop 掉就好了。

scss 复制代码
# v49 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
emulator.patch_nop([0X1C05C, 0X1C068])

GetStringUTFChars

汇编代码如下:

makefile 复制代码
.text:000000000001C160 68 02 40 F9                   LDR             X8, [X19]
.text:000000000001C164 E0 03 13 AA                   MOV             X0, X19
.text:000000000001C168 E1 03 14 AA                   MOV             X1, X20
.text:000000000001C16C E2 03 1F AA                   MOV             X2, XZR
.text:000000000001C170 08 A5 42 F9                   LDR             X8, [X8,#0x548]
.text:000000000001C174 00 01 3F D6                   BLR             X8
.text:000000000001C174
.text:000000000001C178 F5 03 00 AA                   MOV             X21, X0

逐行分析

arduino 复制代码
.text:000000000001C160 68 02 40 F9                   LDR             X8, [X19]
  • LDR X8, [X19]

  • 从 X19 指向的地址加载 X8。

  • X19 存放的是 JNIEnv 指针。

  • X8 现在包含了 X19 所指向的 第一个成员变量(通常是虚表指针)。

arduino 复制代码
.text:000000000001C164 E0 03 13 AA                   MOV             X0, X1
  • MOV X0, X19

  • 将 X19 赋值给 X0。

  • 函数调用的第一个参数(JNIEnv*)。

arduino 复制代码
.text:000000000001C168 E1 03 14 AA                   MOV             X1, X20
  • MOV X1, X20

  • 将 X20 赋值给 X1。

  • X20 之前存的是 X2,可能是某个参数(比如 jstring 或者 字符串指针)。

arduino 复制代码
.text:000000000001C16C E2 03 1F AA                   MOV             X2, XZR
  • MOV X2, XZR

  • 将 X2 设为 0(XZR 是零寄存器)。

  • 可能表示 NULL 指针或默认参数。

arduino 复制代码
.text:000000000001C170 08 A5 42 F9                   LDR             X8, [X8,#0x548]
  • LDR X8, [X8,#0x548]

  • 从 X8 + 0x548 处加载函数指针到 X8。

  • X8 之前存的是 X19 的 虚表指针,所以这一步可能是 从虚表中读取函数地址。

  • 0x548 可能是 JNI 虚函数表的偏移量,指向一个 函数指针。

arduino 复制代码
.text:000000000001C174 00 01 3F D6                   BLR             X8
  • BLR X8

  • 跳转到 X8 指向的函数,并执行。

  • 这个 X8 是 从虚表偏移 0x548 处加载的函数指针。

  • 这里是 JNI 方法 GetStringUTFChars

arduino 复制代码
.text:000000000001C178 F5 03 00 AA                   MOV             X21, X0
  • MOV X21, X0

  • 把 X0 的返回值存入 X21

  • 是 JNI 调用的返回值,这里是 char*(GetStringUTFChars)。

把 LDR X8, [X19] 和 BLR X8 nop 掉,再替换成自定义的 hook 函数 get_string_utf_chars,在函数中实现自定义的 GetStringUTFChars

scss 复制代码
data = "546NBypEyvgBt"

# 字符串地址
str_addr = emulator.STACK_BASE + emulator.STACK_SIZE
emulator.mu.mem_map(str_addr, 0x1000)  # 4KB

# v33 = (*env)->GetStringUTFChars(env, input, 0LL);
def get_string_utf_chars():
    emulator.get_string_utf_chars(data, str_addr)

emulator.patch_nop_range(0X1C160, 0X1C174)
emulator.register_hook(0X1C174, get_string_utf_chars)

strlen

arduino 复制代码
.text:000000000001C178 F5 03 00 AA                   MOV             X21, X0
.text:000000000001C17C A1 85 00 94                   BL              .strlen

把调用 strlen 的地址 nop 掉再替换成 Python 中的 len 就好

scss 复制代码
# v34 = strlen(v33);
def strlen():
    emulator.set_x0(len(data))
emulator.patch_nop([0x1c17c])
emulator.register_hook(0x1c17c, strlen)

memmove(v36, v33, v35);

arduino 复制代码
.text:000000000001C1D4 E0 03 17 AA                   MOV             X0, X23                 ; dest
.text:000000000001C1D8 E1 03 15 AA                   MOV             X1, X21                 ; src
.text:000000000001C1DC E2 03 16 AA                   MOV             X2, X22                 ; n
.text:000000000001C1E0 90 85 00 94                   BL              .memmove

memmove 函数用于在内存中复制数据,它的作用是安全地从源地址拷贝指定字节数到目标地址,即使源和目标内存区域重叠也能正确处理。

原型:

arduino 复制代码
void *memmove(void *dest, const void *src, size_t n);

参数:

  • dest:目标内存地址。

  • src:源内存地址。

  • n:要复制的字节数。

在 Unicorn 中模拟 memmove 可以通过 mem_read 和 mem_write 实现,具体方式如下:

  1. 获取 X0 (dest)、X1 (src)、X2 (n) 寄存器的值

  2. 读取 src 地址的 n 字节数据

  3. 写入 dest 地址

  4. 设置 X0 返回 dest

ini 复制代码
# memmove(v36, v33, v35);
def memmove():
    dest = mu.reg_read(UC_ARM64_REG_X0)
    src = mu.reg_read(UC_ARM64_REG_X1)
    n = mu.reg_read(UC_ARM64_REG_X2)

    if n == 0:
        return  # 不需要拷贝

    print(f"memmove Hooked: Copying {n} bytes from {hex(src)} to {hex(dest)}")

    # 读取 src 地址的数据,确保是 bytes
    data = bytes(mu.mem_read(src, n))

    # 如果 src 和 dest 有重叠,`memmove` 需要支持前后移动
    if src < dest < src + n:
        # `memmove` 需要从后往前复制以避免覆盖
        for i in range(n - 1, -1, -1):
            mu.mem_write(dest + i, data[i:i + 1])
    else:
        # 正常拷贝
        mu.mem_write(dest, data)

    # `memmove` 的返回值是 `dest`
    mu.reg_write(UC_ARM64_REG_X0, dest)

emulator.patch_nop([0x1C1E0])
emulator.register_hook(0x1C1E0, memmove)

ReleaseStringUTFChars

arduino 复制代码
.text:000000000001C1E4 FF 6A 36 38                   STRB            WZR, [X23,X22]
.text:000000000001C1E8 68 02 40 F9                   LDR             X8, [X19]
.text:000000000001C1EC 08 A9 42 F9                   LDR             X8, [X8,#0x550]
.text:000000000001C1F0                               ;   try {
.text:000000000001C1F0 E0 03 13 AA                   MOV             X0, X19
.text:000000000001C1F4 E1 03 14 AA                   MOV             X1, X20
.text:000000000001C1F8 E2 03 15 AA                   MOV             X2, X21
.text:000000000001C1FC 00 01 3F D6                   BLR             X8
arduino 复制代码
.text:000000000001C1E8 68 02 40 F9  LDR X8, [X19]
.text:000000000001C1EC 08 A9 42 F9  LDR X8, [X8,#0x550]
  • LDR X8, [X19] 取 X19 指向的地址的值,并存入 X8

  • LDR X8, [X8,#0x550] 从 X8 + 0x550 处加载值(可能是一个函数指针)

arduino 复制代码
.text:000000000001C1FC  00 01 3F D6  BLR X8
  • BLR X8 间接跳转,调用 X8 指向的函数

  • 这里是 ReleaseStringUTFChars

ReleaseStringUTFChars 是用于释放由 GetStringUTFChars 获取的 UTF-8 编码的字符串。

由于这里的 GetStringUTFChars 替换成了 Python 的实现所有不需要了。直接 nop 掉就好。

scss 复制代码
# (*env)->ReleaseStringUTFChars(env, input, v33);
emulator.patch_nop([0x1C1FC, 0x1c1ec])

vsnprintf

arduino 复制代码
.text:000000000001C3A4 2B 85 00 94                   BL              .vsnprintf

vsnprintf 函数用于格式化字符串并将结果写入缓冲区。

vsnprintf 函数原型

arduino 复制代码
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
  • str:输出缓冲区

  • size:缓冲区大小

  • format:格式化字符串

  • ap:变长参数

在 Unicorn 中 Hook vsnprintf

  1. 读取 format 字符串

  2. 解析 va_list 参数

  3. 使用 Python 格式化字符串

  4. 写入 str 缓冲区

  5. 返回格式化后的字符串长度

python 复制代码
import struct
import re

# vsnprintf(a1, 9u, "%08x", arg)
def vsnprintf():
    X0 = mu.reg_read(UC_ARM64_REG_X0)  # char *str
    X1 = mu.reg_read(UC_ARM64_REG_X1)  # size_t size
    X2 = mu.reg_read(UC_ARM64_REG_X2)  # const char *format
    X3 = mu.reg_read(UC_ARM64_REG_X3)  # va_list ap

    # 读取 format 字符串
    fmt_bytes = mu.mem_read(X2, 100)  # 读取格式字符串(假设最长 100 字节)
    fmt_str = fmt_bytes.split(b'\x00')[0].decode('utf-8')

    print(f"vsnprintf Hooked: format = '{fmt_str}', buffer = {hex(X0)}, size = {X1}, va_list = {hex(X3)}")

    # 解析 va_list 参数
    args = []
    ap = X3
    format_specifiers = re.findall(r"%[#0-9]*[dxslu]", fmt_str)  # 解析格式

    for spec in format_specifiers:
        if spec[-1] in 'di':  # 解析 %d, %i (整数)
            val = struct.unpack("<i", mu.mem_read(ap, 4))[0]
            args.append(val)  # 确保是整数
            ap += 8
        elif spec[-1] in 'xX':  # 解析 %x, %X (十六进制)
            val = struct.unpack("<I", mu.mem_read(ap, 4))[0]
            args.append(int(val))  # **关键修正:存储整数**
            ap += 8
        elif spec[-1] in 'lu':  # 解析 %lu (long unsigned)
            val = struct.unpack("<Q", mu.mem_read(ap, 8))[0]
            args.append(int(val))  # 确保是整数
            ap += 8
        elif spec[-1] in 's':  # 解析 %s (字符串)
            ptr = struct.unpack("<Q", mu.mem_read(ap, 8))[0]
            str_bytes = mu.mem_read(ptr, 100).split(b'\x00')[0]  # 读取字符串
            args.append(str_bytes.decode('utf-8'))
            ap += 8
        elif spec[-1] in 'c':  # 解析 %c (字符)
            val = struct.unpack("<B", mu.mem_read(ap, 1))[0]
            args.append(chr(val))
            ap += 8

    # 使用 Python 进行格式化
    try:
        formatted_str = fmt_str % tuple(args)
    except TypeError as e:
        print(f"Format error: {e}, fmt_str: '{fmt_str}', args: {args}")
        return

    print(f"vsnprintf result: '{formatted_str}'")

    # 写入目标缓冲区
    output_bytes = formatted_str.encode('utf-8')[:X1 - 1]  # 不能超过 size
    mu.mem_write(X0, output_bytes + b'\x00')

    # 返回字符串长度
    mu.reg_write(UC_ARM64_REG_X0, len(output_bytes))

emulator.patch_nop([0x1C3A4])
emulator.register_hook(0x1C3A4, vsnprintf)

NewStringUTF

vbnet 复制代码
.text:000000000001C284 68 02 40 F9                   LDR             X8, [X19]
.text:000000000001C288 08 9D 42 F9                   LDR             X8, [X8,#0x538]
.text:000000000001C28C                               ;   try {
.text:000000000001C28C A1 47 00 D1                   SUB             X1, X29, #-var_11
.text:000000000001C290 E0 03 13 AA                   MOV             X0, X19
.text:000000000001C294 00 01 3F D6                   BLR             X8

逐条指令解析

  1. LDR X8, [X19]
  • 从 X19 指向的地址加载一个值到 X8,这个值通常是一个指针。

  • X19 很可能是某个对象的 基址 (比如 this 指针,或者一个全局结构体)。

  1. LDR X8, [X8,#0x538]
  • 在 X8 存储的地址上 偏移 0x538 处 再次读取值,并存入 X8。

  • X8 现在 存储了一个函数指针,通常是一个 虚函数表 (vtable) 的偏移,或者 全局函数指针。

  1. SUB X1, X29, #-var_11
  • X29 是 FP (Frame Pointer),用来管理当前栈帧。

  • var_11 是栈上的一个局部变量,SUB 操作实际上是 计算局部变量的地址,然后传递给 X1。

  • X1 可能是一个 指向结构体或缓冲区的指针,用于存储结果。

  1. MOV X0, X19
  • 把 X19 (对象或结构体的基址) 传递给 X0,作为第一个参数。
  1. BLR X8
  • 调用 X8 指向的函数。

  • (*env)->NewStringUTF(env, v48)

python 中模拟 NewStringUTF

scss 复制代码
# result = (*env)->NewStringUTF(env, v48);
def new_string_utf():
    # 获取 X1 = UTF-8 字符串地址
    utf8_addr = mu.reg_read(UC_ARM64_REG_X1)

    # 读取字符串内容
    utf8_string = emulator.read_c_string(utf8_addr)
    print(f"NewStringUTF Hooked: Creating Java String for '{utf8_string}'")

    # 返回字符串地址
    mu.reg_write(UC_ARM64_REG_X0, utf8_addr)

emulator.patch_nop([0x1c288, 0x1c294])
emulator.register_hook(0x1c294, new_string_utf)

stack_chk_fail

__stack_chk_fail 是一个用于检测 栈溢出 的安全函数,通常由编译器自动插入到程序中。

ini 复制代码
.text:000000000001C314 28 17 40 F9     LDR     X8, [X25,#0x28]      ; 读取 canary 值
.text:000000000001C318 A9 83 5F F8     LDUR    X9, [X29,#var_8]     ; 读取局部变量存储的 canary 值
.text:000000000001C31C 1F 01 09 EB     CMP     X8, X9               ; 比较 canary
.text:000000000001C320 61 00 00 54     B.NE    loc_1C32C            ; 如果不匹配,跳转到 __stack_chk_fail
.text:000000000001C324 E0 03 13 AA     MOV     X0, X19              ; 准备参数
.text:000000000001C328 7D 75 00 94     BL      sub_3991C            ; 调用 sub_3991C
.text:000000000001C32C 45 85 00 94     BL      .__stack_chk_fail    ; 触发栈溢出保护机制

这个代码片段是标准的 Stack Canary 保护机制:

  • 检测栈溢出,防止 buffer overflow 攻击。

  • 如果检测失败,调用 __stack_chk_fail,触发崩溃。

找到所有调用 __stack_chk_fail 的地方并 nop 掉就好了。

arduino 复制代码
.text:000000000001C32C                               loc_1C32C                               ; CODE XREF: Java_com_cyrus_example_crc32_CRC32Utils_modifiedCRC32+27C↑j
.text:000000000001C32C                                                                       ; Java_com_cyrus_example_crc32_CRC32Utils_modifiedCRC32+2A8↑j
.text:000000000001C32C                                                                       ; Java_com_cyrus_example_crc32_CRC32Utils_modifiedCRC32+2E0↑j
.text:000000000001C32C 45 85 00 94                   BL              .__stack_chk_fail

在 loc_1C32C 上按 X 找到所有跳转到 __stack_chk_fail 的地址

nop掉

ini 复制代码
# __stack_chk_fail
emulator.patch_nop([0x1C2E8, 0x1C320, 0x1C2BC])

这样就可以让 Unicorn 继续执行而不会因为 canary 校验失败而崩溃。

完整代码

python 复制代码
from unicorn.arm64_const import *
import struct
import re

from ARM64Emulator import ARM64Emulator


def modifiedCRC32(data):
    emulator = ARM64Emulator("libcrc32.so")

    mu = emulator.mu

    # 字符串地址
    str_addr = emulator.STACK_BASE + emulator.STACK_SIZE
    emulator.mu.mem_map(str_addr, 0x1000)  # 4KB

    # v49 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
    emulator.patch_nop([0X1C05C, 0X1C068])

    # v33 = (*env)->GetStringUTFChars(env, input, 0LL);
    def get_string_utf_chars():
        emulator.get_string_utf_chars(data, str_addr)

    emulator.patch_nop_range(0X1C160, 0X1C174)
    emulator.register_hook(0X1C174, get_string_utf_chars)

    # v34 = strlen(v33);
    def strlen():
        emulator.set_x0(len(data))
    emulator.patch_nop([0x1c17c])
    emulator.register_hook(0x1c17c, strlen)

    # memmove(v36, v33, v35);
    def memmove():
        dest = mu.reg_read(UC_ARM64_REG_X0)
        src = mu.reg_read(UC_ARM64_REG_X1)
        n = mu.reg_read(UC_ARM64_REG_X2)

        if n == 0:
            return  # 不需要拷贝

        print(f"memmove Hooked: Copying {n} bytes from {hex(src)} to {hex(dest)}")

        # 读取 src 地址的数据,确保是 bytes
        data = bytes(mu.mem_read(src, n))

        # 如果 src 和 dest 有重叠,`memmove` 需要支持前后移动
        if src < dest < src + n:
            # `memmove` 需要从后往前复制以避免覆盖
            for i in range(n - 1, -1, -1):
                mu.mem_write(dest + i, data[i:i + 1])
        else:
            # 正常拷贝
            mu.mem_write(dest, data)

        # `memmove` 的返回值是 `dest`
        mu.reg_write(UC_ARM64_REG_X0, dest)

    emulator.patch_nop([0x1C1E0])
    emulator.register_hook(0x1C1E0, memmove)

    # (*env)->ReleaseStringUTFChars(env, input, v33);
    emulator.patch_nop([0x1C1FC, 0x1c1ec])

    # vsnprintf(a1, 9u, "%08x", arg)
    def vsnprintf():
        X0 = mu.reg_read(UC_ARM64_REG_X0)  # char *str
        X1 = mu.reg_read(UC_ARM64_REG_X1)  # size_t size
        X2 = mu.reg_read(UC_ARM64_REG_X2)  # const char *format
        X3 = mu.reg_read(UC_ARM64_REG_X3)  # va_list ap

        # 读取 format 字符串
        fmt_bytes = mu.mem_read(X2, 100)  # 读取格式字符串(假设最长 100 字节)
        fmt_str = fmt_bytes.split(b'\x00')[0].decode('utf-8')

        print(f"vsnprintf Hooked: format = '{fmt_str}', buffer = {hex(X0)}, size = {X1}, va_list = {hex(X3)}")

        # 解析 va_list 参数
        args = []
        ap = X3
        format_specifiers = re.findall(r"%[#0-9]*[dxslu]", fmt_str)  # 解析格式

        for spec in format_specifiers:
            if spec[-1] in 'di':  # 解析 %d, %i (整数)
                val = struct.unpack("<i", mu.mem_read(ap, 4))[0]
                args.append(val)  # 确保是整数
                ap += 8
            elif spec[-1] in 'xX':  # 解析 %x, %X (十六进制)
                val = struct.unpack("<I", mu.mem_read(ap, 4))[0]
                args.append(int(val))  # **关键修正:存储整数**
                ap += 8
            elif spec[-1] in 'lu':  # 解析 %lu (long unsigned)
                val = struct.unpack("<Q", mu.mem_read(ap, 8))[0]
                args.append(int(val))  # 确保是整数
                ap += 8
            elif spec[-1] in 's':  # 解析 %s (字符串)
                ptr = struct.unpack("<Q", mu.mem_read(ap, 8))[0]
                str_bytes = mu.mem_read(ptr, 100).split(b'\x00')[0]  # 读取字符串
                args.append(str_bytes.decode('utf-8'))
                ap += 8
            elif spec[-1] in 'c':  # 解析 %c (字符)
                val = struct.unpack("<B", mu.mem_read(ap, 1))[0]
                args.append(chr(val))
                ap += 8

        # 使用 Python 进行格式化
        try:
            formatted_str = fmt_str % tuple(args)
        except TypeError as e:
            print(f"Format error: {e}, fmt_str: '{fmt_str}', args: {args}")
            return

        print(f"vsnprintf result: '{formatted_str}'")

        # 写入目标缓冲区
        output_bytes = formatted_str.encode('utf-8')[:X1 - 1]  # 不能超过 size
        mu.mem_write(X0, output_bytes + b'\x00')

        # 返回字符串长度
        mu.reg_write(UC_ARM64_REG_X0, len(output_bytes))

    emulator.patch_nop([0x1C3A4])
    emulator.register_hook(0x1C3A4, vsnprintf)

    # result = (*env)->NewStringUTF(env, v48);
    def new_string_utf():
        # 获取 X1 = UTF-8 字符串地址
        utf8_addr = mu.reg_read(UC_ARM64_REG_X1)

        # 读取字符串内容
        utf8_string = emulator.read_c_string(utf8_addr)
        print(f"NewStringUTF Hooked: Creating Java String for '{utf8_string}'")

        # 返回字符串地址
        mu.reg_write(UC_ARM64_REG_X0, utf8_addr)

    emulator.patch_nop([0x1c288, 0x1c294])
    emulator.register_hook(0x1c294, new_string_utf)

    # __stack_chk_fail
    emulator.patch_nop([0x1C2E8, 0x1C320, 0x1C2BC])

    # 初始化传参
    emulator.set_x0(0) # JNIEnv*
    emulator.set_x1(0) # jobject
    emulator.set_x2(str_addr) # input

    # 监控寄存器X4的变化
    emulator.watch_registers("X4")

    # 运行
    emulator.run(0x1C040, 0x1C2D8)

    return hex(mu.reg_read(UC_ARM64_REG_X4))

if __name__ == "__main__":
    result = modifiedCRC32("546NBypEyvgBt")
    print(f"modifiedCRC32 result: '{result}'")

运行输出如下:

可以看到结果和 app 中的是一样的。

完整源码地址:github.com/CYRUS-STUDI...

相关推荐
奋进的小暄1 小时前
贪心算法(7)(java) 分发饼干
数据结构·算法·贪心算法
大模型铲屎官1 小时前
从零精通机器学习:线性回归入门
开发语言·人工智能·python·算法·机器学习·回归·线性回归
试剂界的爱马仕1 小时前
投资早报 3.14
人工智能·深度学习·算法·机器学习·区块链·ai写作
ksbglllllll1 小时前
ccf3401矩阵重塑(其一)
c++·算法·矩阵
king-xxz2 小时前
力扣No.673.最长递增子序列的个数
数据结构·算法·leetcode
飞奔的马里奥2 小时前
力扣Hot100——169. 多数元素
java·算法·leetcode
一只_程序媛2 小时前
【leetcode hot 100 105】从前序与中序遍历序列构造二叉树
算法·leetcode·职场和发展
阿饼2402 小时前
算法——图论——最短路径(多边权)
c++·算法·动态规划·图论
无名之逆3 小时前
探索Hyperlane:用Rust打造轻量级、高性能的Web后端框架
服务器·开发语言·前端·后端·算法·rust