V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
waruqi
V2EX  ›  编程

分享个用 c 写的 x86 汇编指令脚本虚拟机

  •  
  •   waruqi · 2016-08-07 13:05:25 +08:00 · 4202 次点击
    这是一个创建于 3032 天前的主题,其中的信息可能已经有所发展或是发生改变。

    简介

    这是一个可以直接解释执行从 ida pro 里面提取出来的 x86 汇编代码的虚拟机。

    非常精简,整体架构上不能跟那些成熟的虚拟机相比,主要目标是够用、能用、轻量就行,如果觉得代码架构设计的不是很好的话,也不用过于吐槽哈。。

    虽然我还有写过两个比较成熟的虚拟机项目( jvm 和 avm ),虽然架构上比这个更完善,更容易扩展,功能也更强大

    但是毕竟是给公司写的,没法拿出来分享。。

    背景

    先说说,为什么要写这个东西。。

    之前有段时间,我在用 ida 逆向分析某些程序的算法,并且要把它提取出来将其跨平台运行,这个时候我首先考虑到是 ida 的 F5 插件

    毕竟这个可以直接反成 c/c++代码,还是很强大的,基本上 98%的 x86 汇编代码,我在通过 f5 还原成 c/c++代码后,都能正常运行。

    原本我以为可以万事大吉了,不过就在当我沾沾自喜的时候,发现其中某个汇编函数的 c 代码,死活就是运行不正常,输出结果不对。

    而且那个函数偏偏代码量出奇的大,光 c 代码就有上万行,而且里面还对数据结构和明文都做了变换和加密,要是慢慢调试的话,得痛苦死。。哎。。

    没办法,只好另想出路,既然 ida 还原 c 有时候不一定完全准确,但是其汇编代码的准确度还是可以保证的,并且从 ida 中提取的汇编代码 基本上,不用怎么改,就能编译通过,因此,我先验证了下直接编译汇编代码,运行看看结果对不对。。

    结果跟我想的一样,是 ok 的。。那么问题来了。。

    既然汇编运行结果正常,那怎么把它整成跨平台运行呢,直接从编译后 x86 的指令集进行模拟?工作量有点大,得不偿失。。

    有没有取巧些办法呢?当然有,那就是直接解析和运行源码级的 x86 汇编代码,相当于写个轻量级的精简版 x86 的脚本虚拟机,来把它运行起来。。

    听上去,貌似更麻烦了,其实由于这里只要能够跑通部分需要的汇编指令就行了,因此写个精简版的还是很方便,不需要多少工作量

    我前前后后,也就花了一个礼拜就搞定了,非常精简,当然也不完善(也没必要哈,不能跟那些大部头相比)

    我的目标就是够用就行,因此我写的差不多厚,就尝试去加载之前有问题的汇编代码,如果发现有指令没实现,那就去实现它,直到跑通为主。。

    最后测试结果:

    可以正常跑通那个十几万行的汇编代码,并且在 arm 下运行的性能还算 ok ,至少满足我的个人需求了。。: )

    特性

    • 跨平台运行支持,可以在 windows 、 linux 、 macosx 以及 android, ios 上运行 x86 的汇编代码。。
    • 支持常用 x86 汇编指令(例如,逻辑操作,跳转,循环,调用,压栈等指令)
    • 支持函数间跳转,以及第三方 api 调用
    • 支持参数传入,以及运行结束后,返回值的获取
    • 虚拟机的运行粒度为单个函数,函数间的跳转可以通过多个虚拟机实例来完成(轻量的,性能影响不大)
    • 支持线程安全
    • 暂时不支持 arm64 ,只能在 32 位下运行(有兴趣的同学可以自行修改)

    例子

    我们先从 ida 中提取一段汇编代码,这段汇编主要是printf库函数打印外部传入的数值

    sub_hello	proc near 
    arg_0		= dword	ptr  8 
    .data 
            format db \"hello: %x\", 0ah, 0dh, 0 
     
    off_5A74B0	dd offset loc_6B2B50	; DATA XREF: sub_589100+1832
    		dd offset loc_58A945	; jump table for switch	statement 
     
    .code 
            ; hi
            push	ebp ;hello 
    		mov	ebp, esp 
     
        loc_6B2B50:				; CODE XREF: sub_6B2B40+8
            push    eax 
    		mov	eax, [ebp+arg_0] 
            push eax 
            mov eax, offset format 
            push eax 
            call printf 
            add esp, 4 
            pop eax 
            
            mov ecx, 1
            jmp ds:off_5A74B0[ecx*4]
     
    loc_58A945:
            push    eax 
    		mov	eax, [ebp+arg_0] 
            push eax 
            mov eax, offset format 
            push eax 
            call printf 
            add esp, 4 
            pop eax 
            
      end:
            mov	esp, ebp 
    		pop	ebp 
            retn 
    sub_hello    endp 
    

    如果用 c 来调用的话,就是

    sub_hello(31415926);
    

    输出结果:

    hello: 31415926
    hello: 31415926
    

    接下来我们把这段汇编直接放到我们的虚拟机里面执行:

    static tb_void_t vm86_demo_proc_exec_hello(tb_uint32_t value)
    {
        // 上述汇编代码的字符串表示
        static tb_char_t const s_code_sub_hello[] = 
        {
    "sub_hello	proc near \n\
    arg_0		= dword	ptr  8 \n\
    .data \n\
            format db \"hello: %x\", 0ah, 0dh, 0 \n\
     \n\
    off_5A74B0	dd offset loc_6B2B50	; DATA XREF: sub_589100+1832 \n\
    		dd offset loc_58A945	; jump table for switch	statement \n\
     \n\
    .code \n\
            ; hi\n\
            push	ebp ;hello \n\
    		mov	ebp, esp \n\
     \n\
        loc_6B2B50:				; CODE XREF: sub_6B2B40+8\n\
            push    eax \n\
    		mov	eax, [ebp+arg_0] \n\
            push eax \n\
            mov eax, offset format \n\
            push eax \n\
            call printf \n\
            add esp, 4 \n\
            pop eax \n\
            \n\
            mov ecx, 1\n\
            jmp ds:off_5A74B0[ecx*4]\n\
     \n\
    loc_58A945:\n\
            push    eax \n\
    		mov	eax, [ebp+arg_0] \n\
            push eax \n\
            mov eax, offset format \n\
            push eax \n\
            call printf \n\
            add esp, 4 \n\
            pop eax \n\
            \n\
      end:\n\
            mov	esp, ebp \n\
    		pop	ebp \n\
            retn \n\
    sub_hello    endp \n\
        "
        };
    
        // 定义一个虚拟机
        vm86_machine_ref_t machine = vm86_machine();
        if (machine)
        {
            // 锁定虚拟机,保证线程安全(这个根据需要,可选)
            tb_spinlock_ref_t lock = vm86_machine_lock(machine);
            tb_spinlock_enter(lock);
    
            // 获取虚拟机的堆栈
            vm86_stack_ref_t stack = vm86_machine_stack(machine);
    
            // 编译上面的汇编代码,并生成一个过程对象的引用
            vm86_proc_ref_t proc = vm86_text_compile(vm86_machine_text(machine), s_code_sub_hello, sizeof(s_code_sub_hello));
            if (proc)
            {
                // 添加汇编里面需要调用到的外部库函数
                vm86_machine_function_set(machine, "printf", vm86_demo_proc_func_printf);
    
                // 初始化调用参数
                vm86_stack_push(stack, value);
    
                // 执行这个汇编代码
                vm86_proc_done(proc);
    
                // 恢复堆栈,获取返回值(这里是 void 的,传 null 就行了)
                vm86_stack_pop(stack, tb_null);
            }
    
            // 解锁虚拟机
            tb_spinlock_leave(lock);
        } 
    }
    
    int main(int argc, char** argv)
    {
        // 执行这个汇编函数: sub_hello(0x31415926)
        vm86_demo_proc_exec_hello(0x31415926);    
    }
    

    如果 ok ,那么输出结果当然也是:

    hello: 31415926
    hello: 31415926
    

    源码

    编译

    需要先安装xmake

    在 macosx 上编译

    $ sudo brew install xmake
    $ xmake f -a i386
    $ xmake
    

    在 linux 上编译

    $ git clone https://github.com/waruqi/xmake.git
    $ cd xmake
    $ sudo ./install
    $
    $ cd vm86
    $ xmake f -a i386
    $ xmake
    

    在 windows 上编译

    下载 https://github.com/waruqi/xmake/archive/master.zip

    解压运行里面的 install.bat 安装 xmake 后进行编译:

    $ xmake
    

    编译 android 版本

    $ cd vm86
    $ xmake f -p android --ndk=/xxx/ndk
    $ xmake
    

    运行

    运行测试程序:

    $ xmake r demo
    

    后话

    最后,在项目的 idc 目录下,有两个脚本工具:export_function.idcexport_data.idc 可以用来辅助我们从 ida 中导出指定的汇编函数和数据

    项目主页:http://www.tboox.org

    原文出处:http://www.tboox.org/cn/2016/07/26/x86-script-instruction-virtual-machine/

    7 条回复    2016-08-07 14:59:43 +08:00
    woodrat
        1
    woodrat  
       2016-08-07 13:24:50 +08:00
    赞一个
    snnn
        2
    snnn  
       2016-08-07 13:45:55 +08:00 via Android
    拜大神
    incompatible
        3
    incompatible  
       2016-08-07 13:55:02 +08:00 via iPhone
    好奇 po 主公司做 jvm 是为了应对什么场景,能透露一下吗?
    yzld2002
        4
    yzld2002  
       2016-08-07 14:22:56 +08:00
    膜拜…
    waruqi
        5
    waruqi  
    OP
       2016-08-07 14:31:37 +08:00
    7sDream
        6
    7sDream  
       2016-08-07 14:31:47 +08:00
    好厉害 顺便赞一下愿意开源~
    akira
        7
    akira  
       2016-08-07 14:59:43 +08:00
    good , 好东西
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3982 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 00:57 · PVG 08:57 · LAX 16:57 · JFK 19:57
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.