首页
社区
课程
招聘
[原创]写一个简单的VMP-不造轮子,何以知轮之精髓?
发表于: 2025-9-11 14:33 14325

[原创]写一个简单的VMP-不造轮子,何以知轮之精髓?

2025-9-11 14:33
14325

本文记录了一次从零开始,利用 LLVM Pass 技术为 Android Native 函数实现 VMP(Virtual Machine Protection)的完整心路历程。文章从一个核心观点出发:逆向工程的深度与正向开发的能力紧密相连。为了真正理解 VMP 的工作原理,笔者选择亲手“造轮子”,设计并实现了一个小型的虚拟机(VM)。文中详细阐述了从最初构思、CPU 状态模拟、指令集(ISA)设计,到解释器实现的全过程,并坦诚地分享了在处理函数调用、全局变量等复杂问题时遇到的两大认知误区与失败尝试。最终,通过借鉴社区成熟方案的思路,成功构建了一个可用的原型 SmallVMP。这不仅是一篇技术实现指南,更是一次关于“通过创造来学习”的深度思考与复盘。

近来,VMP 技术在软件保护领域的讨论热度居高不下。作为一名技术探索者,与其临渊羡鱼,不如退而结网。我坚信,一个人的逆向功底始终与其开发水平呈现正相关性,若能洞悉其底层原理,那么逆向分析时必将如虎添翼。

本着“我学会,就等于大家学会”的分享精神,我决定开启这次 VMP 的探索之旅。这篇文章旨在纯粹的技术交流,记录我如何一步步领略 VMP 的风采。若能抛砖引玉,得到前辈大佬的指点,那更是幸事一桩。

核心思路:能否利用强大的 LLVM 框架来构建一个 VMP?经过一番调研,我发现这恰恰是业界许多成熟方案的选择。我的初步构想是:

在 C/C++ 层实现一个微型虚拟 CPU,包含解释器,负责执行自定义的字节码。

编写一个 LLVM Pass,在编译期间将目标函数的 LLVM IR (Intermediate Representation) 翻译成我们的自定义字节码。

同时,将原函数体清空,替换为一个“跳板”(Stub),负责引导程序流程进入我们的虚拟 CPU 解释器。

理论上,这个方案完全可行。那么,让我们开始吧!

万丈高楼平地起,VMP 的核心在于那个“VM”。我们需要先设计一个虚拟的 CPU。参考 ARM64 架构,我们可以定义出它的核心组成部分。

一个 CPU 最核心的就是它的寄存器状态。我们将其极度简化,只保留通用寄存器和状态旗标。

没错,一个极简 CPU 的模型就是这么纯粹。

为了方便操作 VMState,我们封装一些工具函数,用于寄存器的读写和状态旗标的更新。

有了 CPU,就需要它能理解的语言——指令集。我们设计一套定长的 32 位指令格式(LearnVMP ISA),便于处理。

我们将 32 位的指令字划分成不同字段,用于表示操作码、寄存器索引和立即数。

为了让解释器能识别和加载我们的字节码,定义一个文件头结构。

解释器是 VM 的大脑,它是一个巨大的 switch-case 循环,根据 PC 指针取出指令,解码并执行。

下面是部分关键指令的实现逻辑:

至此,一个简单的 VM 框架已经搭建完成。我曾天真地以为,下一步只需将 LLVM IR 直接翻译成这套指令就大功告成了。然而,现实很快给了我沉重的一击。

当我尝试直接翻译 call 指令时,问题暴露了。LLVM IR 中的 call 是符号化的,例如: %call = call noalias ptr @fopen(ptr noundef @.str.38, ptr noundef @.str.39)

它并没有提供 @fopen 的绝对地址。IR 是一种更高层的抽象,重定位(Relocation)是在链接阶段才完成的。我的第一版 VM 完全没有处理符号解析和重定位的能力,因此这条路走不通。

吸取教训后,我构思了第二版方案:

LLVM Pass 负责收集所有遇到的外部调用符号(如 fopen),并为它们生成唯一的 ID。

VM 解释器端维护一个符号表,当遇到 OP_CALL_SYM 这样的指令时,根据 ID 查找函数名字符串。

通过 dlsym 等方式在运行时动态解析符号地址,然后执行调用。

这个方案看似可行,但很快又遇到了新的、更棘手的问题:全局变量静态变量。如果被 VMP 的函数引用了全局变量,我该如何处理?难道要再维护一个全局变量表吗?如果一个外部调用本身又依赖了其他全局状态呢?这种手动模拟链接器行为的复杂度呈指数级增长,很快就让我意识到,这又是一条歧路。

教训总结:这两次失败让我深刻理解到,VMP 的本质不仅仅是指令翻译,更是一个微型的、自洽的运行时(Runtime)环境。我们不应该试图手动模拟编译、链接过程中的所有复杂工作。

在陷入困境后,我开始研究社区的成熟项目,如 xvmp。学习其源码后,我恍然大悟:我应该把链接和符号解析这些脏活累活,再次交给 LLVM 自己来处理!

正确的思路是:

收集与桥接:LLVM Pass 在处理函数时,将所有对外部函数、全局变量的引用收集起来。为每一个引用生成一个“桥接函数”(Thunk)。这个桥接函数是原生的、未被VMP的,它的唯一作用就是执行原始的调用或访问。

符号替换:在生成字节码时,将原来对 @fopen 的调用,替换为对 __thunk_fopen 的调用,并赋予其一个 ID。

VM 调用:VM 解释器通过 OP_BL 指令,根据 ID 调用对应的原生 Thunk 函数。由于 Thunk 函数是编译器正常生成的,它自然就拥有了所有正确的链接信息和地址。

通过这种方式,我们巧妙地将 VM 世界和原生世界连接起来,所有复杂的符号问题都迎刃而解。

基于上述思路,我的 SmallVMP 终于诞生了。它集成在一个修改版的 LLVM (内置 Hikari 混淆框架) 中。

使用方法:

编译并配置好定制版的 LLVM/Clang 环境变量。

在代码中引入 VMP.h 头文件。

使用 IRVM_SECTION 宏来标记需要 VMP 加固的函数。

编译时,你会看到类似如下的日志,表明 VMP Pass 已经生效:

-mllvm -enable-bcfobf (伪控制流)

-mllvm -enable-splitobf (基本块分割)

-mllvm -enable-subobf (指令替换)

-mllvm -enable-allobf (开启所有)

...等等

效果展示:

这是要混淆的函数:

vm后变成了这样:


这是未混淆的vm解释器

加一点 混淆 后 这里只 添加伪控制流 , 平坦化开启 ida 就无法 显示cfg图了

目前 SmallVMP 仍处于实验阶段,它成功实现了对目标函数核心逻辑的抽取和解释执行。但它仍有局限,例如对某些复杂的 LLVM IR 指令(如 select)尚未支持,遇到这类函数会自动跳过加固,保证编译的稳定性。

未来的工作可以围绕以下几点展开:

Code 加密:对生成的字节码进行加密存储,在解释执行前动态解密,执行后再加密回去,对抗静态分析。

动态分发:动态生成 Handler 映射表,让操作码与处理函数的对应关系不再固定。

嵌套 VM:实现二级 VM,即解释器本身也被另一层 VM 保护,进一步提升分析难度。

从最初一个简单的想法,到经历两次失败,再到最终实现一个可用的原型,这个“造轮子”的过程让我对 VMP 的理解产生了质的飞跃。我不再仅仅是知道 VMP“是什么”,而是深刻体会到它“为什么是这样”。

我已将这个过程中的代码开源,包括那些失败的尝试,希望能为同样在探索路上的朋友们提供一些参考。代码尚不完美,欢迎各位大佬批评指正。

另外我在学习过程中的失败产物 我放到这个仓库里了:
a6fK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6z5K9g2c8A6j5h3&6q4M7W2S2A6L8X3M7$3y4U0k6Q4x3V1k6Q4x3X3c8m8L8X3c8J5L8$3W2V1f1X3g2$3k6i4u0K6k6g2)9J5c8Y4c8J5k6h3g2Q4x3V1k6E0j5h3W2F1i4K6u0r3g2V1#2b7i4K6t1#2c8e0N6Q4x3U0g2m8c8W2)9J5y4e0R3%4

不造轮子,何以知轮之精髓? 希望我的分享,能为你带来一点启发。

可以配合混淆 来提高vmp 的强度

下面我把demo 进行vmp 的样本放出来 供大家 看看

c
/*
 * VMState:虚拟机的“CPU 状态”。
 * - 32 个 64 位通用寄存器 (v0..v31),其中 v31 约定为零寄存器 (读恒为 0 / 写操作被忽略)。
 * - NZCV 旗标:Negative/Zero/Carry/Overflow,语义与 ARM 保持一致。
 */
typedef struct {
    uint64_t R[32];   // 通用寄存器堆
    uint8_t  N,Z,C,V; // 四个一位的状态旗标
} VMState;
c
/*
 * VMState:虚拟机的“CPU 状态”。
 * - 32 个 64 位通用寄存器 (v0..v31),其中 v31 约定为零寄存器 (读恒为 0 / 写操作被忽略)。
 * - NZCV 旗标:Negative/Zero/Carry/Overflow,语义与 ARM 保持一致。
 */
typedef struct {
    uint64_t R[32];   // 通用寄存器堆
    uint8_t  N,Z,C,V; // 四个一位的状态旗标
} VMState;
c
#pragma once // 防止头文件被重复包含
#include <stdint.h> // 使用固定宽度整数类型
 
// --- 寄存器读写 (封装 v31=0 的约定) ---
static inline uint64_t VR(const VMState* s, uint8_t i) { return i == 31 ? 0ull : s->R[i]; }
static inline void VW(VMState* s, uint8_t i, uint64_t v) { if (i != 31) s->R[i] = v; }
 
// --- 根据结果 r 更新 N/Z 旗标 ---
static inline void setNZ(VMState* s, uint64_t r) { s->Z = (r == 0); s->N = (uint8_t)((r >> 63) & 1); }
 
/*
 * 关键:按照 ARM 语义计算 C/V (进位/溢出)。
 * - 加法:
 *   C = (r < a)                 // 无符号进位
 *   V = (~(a^b) & (a^r)) >> 63  // 有符号溢出
 * - 减法:
 *   C = (a >= b)                // ARM 约定:C=1 表示“无借位”
 *   V = ((a^b) & (a^r)) >> 63   // 有符号溢出
 */
static inline void setNZ_add(VMState* s, uint64_t a, uint64_t b, uint64_t r) {
    setNZ(s, r);
    s->C = (r < a);
    s->V = (uint8_t)((~(a ^ b) & (a ^ r)) >> 63);
}
static inline void setNZ_sub(VMState* s, uint64_t a, uint64_t b, uint64_t r) {
    setNZ(s, r);
    s->C = (a >= b);
    s->V = (uint8_t)(((a ^ b) & (a ^ r)) >> 63);
}
c
#pragma once // 防止头文件被重复包含
#include <stdint.h> // 使用固定宽度整数类型
 
// --- 寄存器读写 (封装 v31=0 的约定) ---
static inline uint64_t VR(const VMState* s, uint8_t i) { return i == 31 ? 0ull : s->R[i]; }
static inline void VW(VMState* s, uint8_t i, uint64_t v) { if (i != 31) s->R[i] = v; }
 
// --- 根据结果 r 更新 N/Z 旗标 ---
static inline void setNZ(VMState* s, uint64_t r) { s->Z = (r == 0); s->N = (uint8_t)((r >> 63) & 1); }
 
/*
 * 关键:按照 ARM 语义计算 C/V (进位/溢出)。
 * - 加法:
 *   C = (r < a)                 // 无符号进位
 *   V = (~(a^b) & (a^r)) >> 63  // 有符号溢出
 * - 减法:
 *   C = (a >= b)                // ARM 约定:C=1 表示“无借位”
 *   V = ((a^b) & (a^r)) >> 63   // 有符号溢出
 */
static inline void setNZ_add(VMState* s, uint64_t a, uint64_t b, uint64_t r) {
    setNZ(s, r);
    s->C = (r < a);
    s->V = (uint8_t)((~(a ^ b) & (a ^ r)) >> 63);
}
static inline void setNZ_sub(VMState* s, uint64_t a, uint64_t b, uint64_t r) {
    setNZ(s, r);
    s->C = (a >= b);
    s->V = (uint8_t)(((a ^ b) & (a ^ r)) >> 63);
}
c
typedef uint32_t vm_insn_t; // 字节码中一条“指令”就是一个 32 位 word
 
// --- 操作码枚举:按功能分组,数值稳定利于调试 ---
enum vm_op {
    OP_NOP=0,     // 空操作
    OP_LIMM,      // 加载 64 位立即数
    OP_MOVrr,     // 寄存器拷贝
    // 算术逻辑运算
    OP_ADD, OP_ADDI, OP_SUB, OP_SUBI,
    OP_AND, OP_ORR, OP_EOR,
    OP_LSL, OP_LSR, OP_ASR,
    // 比较
    OP_CMPrr, OP_CMPri,
    // 访存
    OP_LDRB, OP_LDRH, OP_LDRW, OP_LDRX,
    OP_STRB, OP_STRH, OP_STRW, OP_STRX,
    // 控制流
    OP_B, OP_BCC,  // 无条件/条件分支
    OP_BL,         // 调用宿主函数 (thunk)
    OP_RET,        // 从 VM 返回
    OP_TRAP,       // 陷阱 (异常)
    OP_MAX_
};
c
typedef uint32_t vm_insn_t; // 字节码中一条“指令”就是一个 32 位 word
 
// --- 操作码枚举:按功能分组,数值稳定利于调试 ---
enum vm_op {
    OP_NOP=0,     // 空操作
    OP_LIMM,      // 加载 64 位立即数
    OP_MOVrr,     // 寄存器拷贝
    // 算术逻辑运算
    OP_ADD, OP_ADDI, OP_SUB, OP_SUBI,
    OP_AND, OP_ORR, OP_EOR,
    OP_LSL, OP_LSR, OP_ASR,
    // 比较
    OP_CMPrr, OP_CMPri,
    // 访存
    OP_LDRB, OP_LDRH, OP_LDRW, OP_LDRX,
    OP_STRB, OP_STRH, OP_STRW, OP_STRX,
    // 控制流
    OP_B, OP_BCC,  // 无条件/条件分支
    OP_BL,         // 调用宿主函数 (thunk)
    OP_RET,        // 从 VM 返回
    OP_TRAP,       // 陷阱 (异常)
    OP_MAX_
};
c
/*
 * LearnVMP ISA 布局:
 * [31..24]=opcode | [23..19]=rd | [18..14]=ra | [13..9]=rb | [8..0]=imm9 (signed)
 * 分支类指令使用 21 位有符号相对位移。
 */
 
// --- 解码工具 (取字段) ---
static inline uint8_t op(vm_insn_t x)  { return (x >> 24) & 0xFF; }
static inline uint8_t rd(vm_insn_t x)  { return (x >> 19) & 0x1F; }
static inline uint8_t ra(vm_insn_t x)  { return (x >> 14) & 0x1F; }
static inline uint8_t rb(vm_insn_t x)  { return (x >> 9)  & 0x1F; }
static inline int32_t imm9(vm_insn_t x){
    int32_t v = (int32_t)(x & 0x1FF); return (v << 23) >> 23;
}
static inline int32_t br_off_se21(vm_insn_t x){
    int32_t v = (int32_t)(x & 0x1FFFFF); return (v << 11) >> 11;
}
 
// --- 编码工具 (写字段) ---
static inline vm_insn_t ENC_RRR(uint8_t o, uint8_t d, uint8_t a, uint8_t b){...}
static inline vm_insn_t ENC_RI (uint8_t o, uint8_t d, uint8_t a, int32_t i){...}
// ... 其他编码函数
c
/*
 * LearnVMP ISA 布局:
 * [31..24]=opcode | [23..19]=rd | [18..14]=ra | [13..9]=rb | [8..0]=imm9 (signed)
 * 分支类指令使用 21 位有符号相对位移。
 */
 
// --- 解码工具 (取字段) ---
static inline uint8_t op(vm_insn_t x)  { return (x >> 24) & 0xFF; }
static inline uint8_t rd(vm_insn_t x)  { return (x >> 19) & 0x1F; }
static inline uint8_t ra(vm_insn_t x)  { return (x >> 14) & 0x1F; }
static inline uint8_t rb(vm_insn_t x)  { return (x >> 9)  & 0x1F; }
static inline int32_t imm9(vm_insn_t x){
    int32_t v = (int32_t)(x & 0x1FF); return (v << 23) >> 23;
}
static inline int32_t br_off_se21(vm_insn_t x){
    int32_t v = (int32_t)(x & 0x1FFFFF); return (v << 11) >> 11;
}
 
// --- 编码工具 (写字段) ---
static inline vm_insn_t ENC_RRR(uint8_t o, uint8_t d, uint8_t a, uint8_t b){...}
static inline vm_insn_t ENC_RI (uint8_t o, uint8_t d, uint8_t a, int32_t i){...}
// ... 其他编码函数
c
typedef struct __attribute__((packed)) {
    char     magic[4];      // 固定魔数 'L','V','M','P'
    uint8_t  version;       // 版本号
    uint8_t  flags;         // 控制标志位 (如 TRACE 开关)
    uint16_t reserved;      // 预留
    uint32_t code_words;    // 指令数量
} vmp_bc_header_t;
c
typedef struct __attribute__((packed)) {
    char     magic[4];      // 固定魔数 'L','V','M','P'
    uint8_t  version;       // 版本号
    uint8_t  flags;         // 控制标志位 (如 TRACE 开关)
    uint16_t reserved;      // 预留
    uint32_t code_words;    // 指令数量
} vmp_bc_header_t;
c
// 加载64位立即数
case OP_LIMM: {
    uint8_t dst = rd(ins);
    // LIMM 指令占用 3 个 word: [opcode|dst] [imm_low32] [imm_high32]
    uint64_t val = ((uint64_t)code[pc+1]) | (((uint64_t)code[pc+2]) << 32);
    VW(&S, dst, val);
    pc += 3;
    break;
}
 
// 加法 (寄存器-寄存器)
case OP_ADD: {
    uint8_t dst = rd(ins), a_reg = ra(ins), b_reg = rb(ins);
    uint64_t a = VR(&S, a_reg), b = VR(&S, b_reg);
    uint64_t r = a + b;
    VW(&S, dst, r);
    setNZ_add(&S, a, b, r); // 注意:真实实现中,是否更新旗标应由指令定义
    pc++;
    break;
}
 
// 比较 (寄存器-寄存器)
case OP_CMPrr: {
    uint8_t a_reg = ra(ins), b_reg = rb(ins);
    uint64_t a = VR(&S, a_reg), b = VR(&S, b_reg);
    uint64_t r = a - b;
    setNZ_sub(&S, a, b, r); // 只更新旗标,不写回结果
    pc++;
    break;
}
 
// 无条件分支
case OP_B: {
    int32_t off = br_off_se21(ins);
    int nxt_pc = pc + off;
    // ...边界检查...
    pc = nxt_pc; // 直接跳转
    break;
}
 
// 条件分支
case OP_BCC: {
    uint8_t cond = rd(ins) & 0xF;
    bool take = false;
    switch(cond) { // 根据 NZCV 旗标判断是否跳转
        case 0: take = S.Z; break; // EQ
        case 1: take = !S.Z; break; // NE
        // ... 其他条件判断 ...
    }
    if (take) {
        int32_t off = br_off_se21(ins);
        pc += off;
    } else {
        pc++;
    }
    break;
}
 
// 调用宿主函数
case OP_BL: {
    uint32_t thunk_idx = code[pc] & 0xFFFFu; // 函数在 thunk 表中的索引
    if (is_valid(thunk_idx)) {
        // R[0]..R[7] 作为参数
        long long ret = thunks[thunk_idx](S.R);
        VW(&S, 0, (uint64_t)ret); // 返回值写入 v0
    }
    pc++;
    break;
}
 
// 从 VM 返回
case OP_RET: {
    return (long long)VR(&S, 0); // 从 v0 获取返回值并返回给宿主
}

传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2025-9-11 17:39 被逆天而行编辑 ,原因:
上传的附件:
收藏
免费 347
支持
分享
最新回复 (231)
雪    币: 4596
活跃值: (5884)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
2

删除

最后于 2025-9-15 16:41 被逆天而行编辑 ,原因:
2025-9-11 14:34
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
6
2025-9-11 14:37
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
6
2025-9-11 14:38
0
雪    币: 0
活跃值: (1175)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
666
2025-9-11 14:39
0
雪    币: 374
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
学习学习mark
2025-9-11 14:41
0
雪    币: 204
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
666
2025-9-11 14:44
0
雪    币: 11
活跃值: (720)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
学习学习
2025-9-11 14:48
0
雪    币: 244
活跃值: (3050)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
666,学习下~
2025-9-11 15:09
0
雪    币: 1699
活跃值: (2327)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
10
不造轮子,何以知轮之精髓?
2025-9-11 15:27
0
雪    币: 16865
活跃值: (8153)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11

vmp到底是安卓还是x86 win?
如果是安卓Native一般是arm指令,你怎么是x86指令?vm后效果展示显示就是x86指令

最后于 2025-9-11 15:38 被tDasm编辑 ,原因:
2025-9-11 15:33
0
雪    币: 7
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
12
感谢分享
2025-9-11 15:33
0
雪    币: 4596
活跃值: (5884)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
13
tDasm vmp到底是安卓还是x86&nbsp;win?如果是安卓Native一般是arm指令,你怎么是x86指令?vm后效果展示显示就是x86指令
通用的 理论上 所有平台都支持
2025-9-11 15:41
0
雪    币: 16865
活跃值: (8153)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
逆天而行 通用的 理论上 所有平台都支持
不可能通用!
arm是简单指令系统,是等长指令
X86是复杂指令系统,是不等长指令。
操作码不同,对每个操作码的解释代码也完全不同。
2025-9-11 16:05
0
雪    币: 1046
活跃值: (1456)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
15
tDasm 不可能通用! arm是简单指令系统,是等长指令 X86是复杂指令系统,是不等长指令。 操作码不同,对每个操作码的解释代码也完全不同。
指的是这种利用栈式虚拟机去模拟执行代码的保护思想通用吧..至于指令集的问题你自己想办法解决就行
2025-9-11 16:09
0
雪    币: 4596
活跃值: (5884)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
16
tDasm 不可能通用! arm是简单指令系统,是等长指令 X86是复杂指令系统,是不等长指令。 操作码不同,对每个操作码的解释代码也完全不同。
因为 llvm 是支持交叉编译的,我相当于给编译器 植入了 一段我的 代码,llvm 能编译出啥 都是 vmp的 当然 你只能用c语言编译,我讲的很清楚,如果还不懂 你可以去看看 llvm 的资料。也可以问问 人工智能 
2025-9-11 16:20
0
雪    币: 1762
活跃值: (1255)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
17
大牛
2025-9-11 16:21
0
雪    币: 4596
活跃值: (5884)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
18
llvm 会把所有的指令 转化伪 通用的 ir 指令 我只需要 翻译 ir 就行了 其他的不用管
2025-9-11 16:22
0
雪    币: 2071
活跃值: (6225)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
19
2025-9-11 16:32
0
雪    币: 274
活跃值: (1661)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
20
厉害
2025-9-11 16:41
0
雪    币: 766
活跃值: (1111)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
21
大牛恐怖如斯
2025-9-11 16:41
0
雪    币: 0
活跃值: (115)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
感谢分享
2025-9-11 16:49
0
雪    币: 343
活跃值: (1761)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
66666
2025-9-11 17:21
0
雪    币: 4596
活跃值: (5884)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
24
tDasm 不可能通用! arm是简单指令系统,是等长指令 X86是复杂指令系统,是不等长指令。 操作码不同,对每个操作码的解释代码也完全不同。
我已经上传了 x86 和 arm 版的样本了
2025-9-11 17:40
1
雪    币: 10022
活跃值: (6819)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
25
我们学习本身就是在造轮子。
2025-9-11 18:02
1
游客
登录 | 注册 方可回帖
返回