找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1526

积分

0

好友

222

主题
发表于 9 小时前 | 查看: 2| 回复: 0

在上一篇文章分析了q参数之后,我们继续深入探讨sign参数的生成逻辑。与q参数的定位方式类似,可以通过堆栈回溯,或者对getByteHashMap等关键方法进行Hook来找到线索。

调用栈追踪

最终,调用链指向一个名为a的方法,其中核心是md5_crypt方法。通过Hook其传入参数,可以快速验证其输出结果。确定加密点后,便可以编写Unidbg代码进行模拟调用。

Unidbg调用验证

public void md5_crypt() {
    DvmClass CryptoHelper = vm.resolveClass("com/luckincoffee/safeboxlib/CryptoHelper");
    byte[] bytes = "hello".getBytes();
    String a2 = "u7Su25kSE9PxcTgQZkRgL0kJ+lDaV2IQcqdsfGGuNDs=";
    byte[] bytes2 = Base64.getDecoder().decode(a2.replace('-', '+').replace('_', '/').getBytes());
    ByteArray reval = CryptoHelper.callStaticJniMethodObject(emulator, "md5_crypt([BI[B)[B", bytes, 1, bytes2);
    System.out.println("md5_crypt:" + new String(reval.getValue()));
}
=================================================
JNIEnv->FindClass(com/luckincoffee/safeboxlib/CryptoHelper) was called from RX@0x400450c8[libcryptoDD.so]0x450c8
...
Find native function Java_com_luckincoffee_safeboxlib_CryptoHelper_md5_1crypt => RX@0x40043bbc[libcryptoDD.so]0x43bbc
...
md5_crypt:7391766891689134774185162898388901200

成功打印出md5_crypt函数的结果。我们跟进到0x43bbc地址,这就是计算sign的核心函数。该函数经过了OLLVM混淆,但代码量不大,其中主要包含两个关键子函数:sub_4559Csub_4095C

IDA分析视图

首先需要修正JNIEnv相关结构,以便IDA能正确识别部分JNI函数。

修正JNIEnv后的代码

代码中出现了两个关键函数。我们先Hook 0x4559C函数,观察其参数。可以发现从Java层传入的a5(即Base64解码后的值)被传递给了此函数。运行Hook并打印其输入与输出。

public void HookByConsoleDebugger() {
    emulator.attach().addBreakPoint(module.base + 0x4559C, new BreakPointCallback() {
        @Override
        public boolean onHit(Emulator<?> emulator, long address) {
            RegisterContext context = emulator.getContext();
            UnidbgPointer input = context.getPointerArg(1);
            DvmObject<?> dvmbytes = vm.getObject(input.toIntPeer());
            byte[] result = (byte[]) dvmbytes.getValue();
            System.out.println("传入数据: " + bytesTohexString(result));
            emulator.attach().addBreakPoint(context.getLRPointer().peer, new BreakPointCallback() {
                @Override
                public boolean onHit(Emulator<?> emulator, long address) {
                    RegisterContext returnContext = emulator.getContext();
                    UnidbgPointer v12Value = returnContext.getPointerArg(0);
                    DvmObject<?> v12Obj = vm.getObject(v12Value.toIntPeer());
                    byte[] v12Data = (byte[]) v12Obj.getValue();
                    System.out.println("结果: " + new String(v12Data));
                    return true;
                }
            });
            return true;
        }
    });
}

函数4559C的Hook结果

Hook结果显示,传入参数是bbb4aedb991213d3f17138106644602f4909fa50da57621072a76c7c61ae343b,这正是Base64字符串解码后的Hex表示。函数输出结果为ooL1Bqoa8lF6ZE54

接着分析sub_4095C。该函数有三个参数,Hook后观察:第一个参数是hello4559C函数输出结果的拼接,即helloooL1Bqoa8lF6ZE544095C的运行结果与md5_crypt函数的最终输出一致。因此,需要继续深入4095C函数内部。

public void HookByConsoleDebugger1() {
    emulator.attach().addBreakPoint(module.base + 0x4095C, new BreakPointCallback() {
        @Override
        public boolean onHit(Emulator<?> emulator, long address) {
            RegisterContext context = emulator.getContext();
            UnidbgPointer input = context.getPointerArg(0);
            UnidbgPointer output = context.getPointerArg(2);
            System.out.println("传入数据: " + input.getString(0));
            emulator.attach().addBreakPoint(context.getLRPointer().peer, new BreakPointCallback() {
                @Override
                public boolean onHit(Emulator<?> emulator, long address) {
                    byte[] v27Data = output.getPointer(0).getByteArray(0, 37);
                    System.out.println("结果: " + new String(v27Data));
                    return true;
                }
            });
            return true;
        }
    });
}

函数4095C的Hook结果

进入函数后,跟踪入参a1,发现它被传入了0x41084方法。根据上下文,a1是输入数据,a2是长度。我们在该函数返回时Hook第三个参数。

41084函数内部

public void HookByConsoleDebugger2() {
    emulator.attach().addBreakPoint(module.base + 0x41084, new BreakPointCallback() {
        @Override
        public boolean onHit(Emulator<?> emulator, long address) {
            RegisterContext context = emulator.getContext();
            UnidbgPointer input = context.getPointerArg(0);
            UnidbgPointer output = context.getPointerArg(2);
            System.out.println("传入数据: " + input.getString(0));
            emulator.attach().addBreakPoint(context.getLRPointer().peer, new BreakPointCallback() {
                @Override
                public boolean onHit(Emulator<?> emulator, long address) {
                    Inspector.inspect(output.getByteArray(0, 16), "0x41084 arg3");
                    return true;
                }
            });
            return true;
        }
    });
}

41084函数Hook结果

Hook结果表明,0x41084函数是一个标准的MD5算法实现,但其输出结果与我们最终得到的sign不同,说明MD5的结果后续还经过了其他处理。

为了方便分析,在IDA中将0x41084函数重命名为md5。继续分析,发现几个可疑的函数。其中0x4108C也调用了0x3D1F4

可疑函数调用链

// attributes: thunk
__int64 __fastcall sub_4108C(__int64 a1, __int64 a2) {
    return sub_3D1F4(a1, a2);
}
__int64 __fastcall sub_4109C(__int64 result) {
    if (result >= 0)
        return result;
    else
        return -result;
}

分析发现,程序会调用sub_3D1F4方法,每次处理MD5结果的4个字节。运算结果经过sub_4109C进行取绝对值(或根据与0的比较结果决定取本身还是取反)操作,然后格式化输出,最后通过strcat拼接得到最终结果。因此,核心逻辑在sub_3D1F4函数中。

v17 = sub_3D1F4(v32, 0LL);
v19 = sub_3D1F4(v32, 4LL);
v10 = sub_3D1F4(v32, 8LL);
v13 = sub_3D1F4(v32, 12LL);

来到sub_3D1F4,代码简短但逻辑不直观,使用Debugger进行动态分析。

3D1F4函数断点

第一次调用3D1F4

断点后可以看到,第一个参数x1是MD5的结果,第二个参数是0,对应代码v17 = sub_3D1F4(v32, 0LL);。在函数返回处(BLR指令后)继续执行,观察x0寄存器,发现其值为0xd3f10f0f(对应十进制-739176689),而739176689正是最终sign结果的第一部分。

继续Debug,执行v19 = sub_3D1F4(v32, 4LL);

第二次调用3D1F4

观察返回结果x0=0x64ae26b6,转换为十进制1689134774,是sign结果的第二部分。后续两次调用结果分别为0x0b095c920x172e2950,转换为十进制185162898388901200

将四部分拼接:7391766891689134774185162898388901200,与最终结果7391766891689134774185162898388901200对比,发现最后两部分被合并了(185162898 + 388901200 => 185162898388901200)。

分析结果对照

通过动态调试发现,如果函数返回值为负,则最终结果会发生变化;如果为正值,结果就是MD5对应4字节转成的十进制数。为了彻底弄清其逻辑,在函数执行时进行汇编指令级别的Trace。

public void HookByConsoleDebugger2() {
    emulator.attach().addBreakPoint(module.base + 0x3D1F4, new BreakPointCallback() {
        @Override
        public boolean onHit(Emulator<?> emulator, long address) {
            emulator.traceCode(module.base, module.base + module.size);
            RegisterContext context = emulator.getContext();
            int a2 = context.getIntArg(1);
            System.out.println("===========入参=================");
            System.out.println("传入数据: " + a2);
            emulator.attach().addBreakPoint(context.getLRPointer().peer, new BreakPointCallback() {
                @Override
                public boolean onHit(Emulator<?> emulator, long address) {
                    RegisterContext returnContext = emulator.getContext();
                    int Value = returnContext.getIntArg(0);
                    System.out.println("============出参================" + Value);
                    return true;
                }
            });
            return true;
        }
    });
}

(Trace日志较长,此处仅截取关键部分)

传入数据: 0
...
[19:36:50 573] [1f000071] 0x4004109c: "cmp w0, #0" => nzcv: N=1, Z=0, C=1, V=0 w0=0xd3f10f0f
[19:36:50 573] [0054805a] 0x400410a0: "cneg w0, w0, mi" nzcv: N=1, Z=0, C=1, V=0 w0=0xd3f10f0f => w0=0x2c0ef0f1
[19:36:50 575] [c0035fd6] 0x400410a4: "ret"

重点关注这三行ARM汇编指令。其逻辑是:

  1. cmp w0, #0:比较w0寄存器(存储着MD5的4字节数据)与0。
  2. cneg w0, w0, mi:当条件为mi(负数,即N=1)时,对w0的值进行“条件取反”(计算其二进制补码,即按位取反后加1)。
  3. 0xd3f10f0f为负数,因此执行cneg后变为0x2c0ef0f1(即-739176689的补码表示)。

cneg指令原理

至此,算法逻辑已清晰:对MD5结果的每4个字节作为一个有符号整数进行判断,若为负数,则取其补码(绝对值)对应的无符号整数;若为正数,则直接使用。最后将所有结果转换为十进制字符串并拼接。

使用Python复现该逻辑。首先对拼接后的字符串计算MD5,得到9f8b2056b6cb87149b196da5b305059a,然后进行上述运算,结果与Unidbg运行结果一致。

hex_str = "9f8b2056b6cb87149b196da5b305059a"
result = ''.join(str(int(hex_str[i:i+8],16) if int(hex_str[i:i+8],16) < 0x80000000 else (~int(hex_str[i:i+8],16)+1) & 0xFFFFFFFF) for i in range(0,32,8))
print(result)

Python验证结果

此案例虽然使用了OLLVM混淆,但整体代码量不大,算法逻辑清晰,非常适合作为使用Unidbg进行Android Native层逆向分析的入门练习。它完整展示了从定位加密点、动态调试分析到算法还原的整个过程。

技术总结




上一篇:X x.ai API参数滥用漏洞剖析:如何利用n与temperature参数造成巨额损失
下一篇:PNG隐写技术与ClickFix社会工程:解析新型信息窃取攻击链
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-24 17:17 , Processed in 0.231143 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表