吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 9217|回复: 68
收起左侧

[C&C++ 原创] [vc]代码虚拟机设计

  [复制链接]
舒默哦 发表于 2020-7-14 18:23
本帖最后由 舒默哦 于 2020-7-14 18:35 编辑




0x00
具体可以参考《加密与解密》第四版 第二十章。这章内容有点抽象,可以画堆栈图,来辅助分析。



0x01
代码虚拟化,我的理解就是把一条指令分解,变形膨胀,本质还是代码混淆。只要代码进入虚拟机,就是八仙过海,各显神通了。
举个例子:
[Asm] 纯文本查看 复制代码
void _declspec(naked) code_vm_test()
{
    _asm {

        mov eax,10h
        mov ebx,30h
        add eax,ebx
        mov g_num,eax
        retn
    }
}

上面这个函数加上VM,没加垃圾指令,VM后的代码如下:
[Asm] 纯文本查看 复制代码
mov eax, 0x0
 shl eax, 0x2
 mov eax, dword ptr ds : [eax + edi] 
 push eax
 mov eax, filenew.0040A500
 jmp eax
 mov eax, 0x10
 push eax
 mov eax, filenew.0040A500
 jmp eax
 mov eax, dword ptr ss : [esp + 0x4] 
 mov ebx, dword ptr ss : [esp] 
 mov eax, ebx
 add esp, 0x8
 push eax
 mov eax, filenew.0040A500
 jmp eax
 mov eax, 0x0
 pop dword ptr ds : [edi + eax * 4]
 mov eax, filenew.0040A500
 jmp eax
 mov eax, 0x3
 shl eax, 0x2
 mov eax, dword ptr ds : [eax + edi] 
 push eax
 mov eax, filenew.0040A500
 jmp eax
 mov eax, 0x30
 push eax
 mov eax, filenew.0040A500
 jmp eax
 mov eax, dword ptr ss : [esp + 0x4] 
 mov ebx, dword ptr ss : [esp] 
 mov eax, ebx
 add esp, 0x8
 push eax
 mov eax, filenew.0040A500
 jmp eax
 mov eax, 0x3
 pop dword ptr ds : [edi + eax * 4] 
 mov eax, filenew.0040A500
 jmp eax
 mov eax, 0x0
 shl eax, 0x2
 mov eax, dword ptr ds : [eax + edi] 
 push eax
 mov eax, filenew.0040A500
 jmp eax
 mov eax, 0x3
 shl eax, 0x2
 mov eax, dword ptr ds : [eax + edi] 
 push eax
 mov eax, filenew.0040A500
 jmp eax
 mov eax, dword ptr ss : [esp + 0x4] 
 mov ebx, dword ptr ss : [esp] 
 add eax, ebx
 add esp, 0x8
 push eax
 mov eax, filenew.0040A500
 jmp eax
 mov eax, 0x0
 pop dword ptr ds : [edi + eax * 4] 
 mov eax, filenew.0040A500
 jmp eax
 mov eax, filenew.00406030
 push eax
 mov eax, filenew.0040A500
 jmp eax
 mov eax, 0x0
 shl eax, 0x2
 mov eax, dword ptr ds : [eax + edi] 
 push eax
 mov eax, filenew.0040A500
 jmp eax
 mov eax, dword ptr ss : [esp + 0x4]
 mov ebx, dword ptr ss : [esp] 
 mov dword ptr ds : [eax] , ebx
 add esp, 0x8
 mov eax, filenew.0040A500
 jmp eax
 mov eax, 0x0
 mov ebx, dword ptr ss : [ebp] 
 add ebp, eax
 push ebx
 push ebp
 push dword ptr ds : [edi + 0x20]
 push dword ptr ds : [edi + 0x1C]
 push dword ptr ds : [edi + 0x18]
 push dword ptr ds : [edi + 0x14]
 push dword ptr ds : [edi + 0xC]
 push dword ptr ds : [edi + 0x8]
 push dword ptr ds : [edi + 0x4] 
 push dword ptr ds : [edi] 
 pop eax
 pop ecx
 pop edx
 pop ebx
 pop ebp
 pop esi
 pop edi
 popfd
 pop esp
 retn




0x03
编写流程,可以围绕三个handler来写(普通handler、操作数handler、pop目的操作数的handler),普通handler就是指令handler,操作数handler包括目的操作数handler和源操作数handler。
虚拟机并不完善,因为时间原因,只完成了第一步,没有处理JCC指令、call指令、特殊指令、以及一些改变标志位的指令,以后有时间处理好了我会发布在github上。
测试exe是code_vm_test.exe,源文件:
[Asm] 纯文本查看 复制代码
#include <iostream>
#include <Windows.h>
using namespace std;
int g_num = 0;

void _declspec(naked) code_vm_test()
{
    //MessageBoxA(NULL, 0, 0, 0);
    _asm {

        mov eax,10h
        mov ebx,30h
        add eax,ebx
        mov g_num,eax
        retn
    }
}

int main()
{
    code_vm_test();
    std::cout << g_num << endl;
    system("pause");
}





项目源码传上来了,整个加VM的流程放到一个CPP中:
[Asm] 纯文本查看 复制代码
#include <iostream>
#include <list>
#include "vmtest.h"
#include "ASM/disasm.h"
#include "Common/header.h"

//存放wmcode的代码的链表
list<VHandler> g_vHandlerList;

int g_total = 0;//记录有多少个handler;
int g_retnaddr = 0;//记录返回地址

//编译生成的Handler代码,并添加到Handler
BOOL CompileHandler(char* asmtext)
{
    char Code[CODEMAXLEN] = { 0 };//暂存机器码
    int codelen = 0;
    char errtext[256] = { 0 };
    t_asmmodel am;
    char linestr[TEXTLEN] = { 0 };
    int len = strlen(asmtext);
    for (int i = 0; i < len;)
    {
        CString str =asmtext;
        int calc = str.LeftFindChar(asmtext+i, '\n');      
        memset(linestr, 0, TEXTLEN);
        memcpy(linestr, asmtext+i, calc - 1);
        Assemble(linestr, 0, &am, 0, 4, errtext);
        memcpy(Code + codelen, am.code, am.length);
        codelen += am.length;
        i += calc;
    }
    VHandler temp_vhandler;
    memcpy(temp_vhandler.AssembleCode, Code, codelen);
    temp_vhandler.CodeLen = codelen;
    
    g_vHandlerList.push_back(temp_vhandler);
}

int GetScalestr(int bit, OUT char* scalestr)
{
    int sizeidx = 0;
    if (bit == 8)
    {
        sizeidx = 0;
        strcpy_s(scalestr, 6, "byte");
    }
    else if (bit == 16)
    {
        sizeidx = 1;
        strcpy_s(scalestr, 6, "word");
    }
    else if (bit == 32)
    {
        sizeidx = 2;
        strcpy_s(scalestr, 6, "dword");
    }
    return sizeidx;
}

//处理普通handler
void GeneralHandler(t_disasm disasm,int tab_index,int opt_num)
{
    
    CString str_general;
    char optnum[32] = { 0 };
    if (opt_num == 2)//双操作数
    {
        str_general = "mov eax, [esp + 4]\n";
        str_general += "mov ebx, [esp]\n";
        str_general += vmtable[tab_index].strInstruction;
        if (vmtable[tab_index].optype[0]== MEMTYPE)//如果第一个操作数是内存地址
        {           
            int sizeidx = GetScalestr(vmtable[tab_index].bitnum[0], optnum);
            if (0 == sizeidx)
            {
                str_general = str_general + " " + optnum + " ptr [eax],bl\n";
            }
            if (1 == sizeidx)
            {
                str_general = str_general + " " + optnum + " ptr [eax],bx\n";
            }
            if (2 == sizeidx)
            {
                str_general = str_general + " " + optnum + " ptr [eax],ebx\n";
            }
            str_general += "add esp, 8\n";
        }
        else if (vmtable[tab_index].optype[1] == MEMTYPE)//如果第二个操作数是内存地址
        {
            int sizeidx = GetScalestr(vmtable[tab_index].bitnum[0], optnum);
            if (0 == sizeidx)
            {
                str_general = str_general + " bl,"+ optnum+" ptr [eax]\n";
            }
            if (1 == sizeidx)
            {
                str_general = str_general + " bx," + optnum + " ptr [eax]\n";
            }
            if (2 == sizeidx)
            {
                str_general = str_general + " ebx," + optnum + " ptr [eax]\n";
            }
            str_general += "add esp, 8\n";
            str_general += "push ebx\n";
        }
        else
        {
            str_general += " eax,ebx \n"; 
            str_general += "add esp, 8\n";
            str_general += "push eax\n";
        }

    }
    if (opt_num == 1)//单操作数
    {
        str_general = "mov ebx, [esp]\n";
        str_general += vmtable[tab_index].strInstruction;
        if ( vmtable[tab_index].optype[0] == MEMTYPE)
        {
            int sizeidx = GetScalestr(vmtable[tab_index].bitnum[0], optnum);
            if (0 == sizeidx)
            {
                str_general = str_general + " " + optnum + " ptr [ebx]\n";
            }
            if (1 == sizeidx)
            {
                str_general = str_general + " " + optnum + " ptr [ebx]\n";
            }
            if (2 == sizeidx)
            {
                str_general = str_general + " " + optnum + " ptr [ebx]\n";
            }
        }
        else
        {
            str_general += " ebx\n";
        }
       
        str_general += "add esp, 4\n";
        if (vmtable[tab_index].optype[0] == REGTYPE)
        {
            str_general += "push ebx\n";
        }
    }

    if (opt_num == 0)//没有操作数
    {
        //判断是不是retn
        if (_stricmp("retn", vmtable[tab_index].strInstruction)==0)
        {
            int retnlength = GetFunSize(vRetn);
            AllocMemory _alloc;
            char* tempbuff = _alloc.auto_malloc<char*>(retnlength);

            memcpy(tempbuff, vRetn, retnlength);

            //修改vRetn的操作数
            *(DWORD*)(tempbuff + 1) = disasm.immconst;

            VHandler temp_vhandler;
            temp_vhandler.CodeLen = retnlength;
            memcpy(temp_vhandler.AssembleCode, tempbuff, retnlength);


            //添加到链表
            g_vHandlerList.push_back(temp_vhandler);

            return;
        }
        else
        {
            str_general = vmtable[tab_index].strInstruction;
        }

    }
    str_general += "\nmov eax, 0x66668888\njmp eax\n";

    //编译生成的Handler代码,并添加到vHandlerList链表
    CompileHandler(str_general.GetString());
}

//pop目的操作数的handler
void PopHandler(t_disasm disasm, int tab_index, int opt_index)
{
    CString str_pop;
    //1、处理辅助handler
    int t1 = vmtable[tab_index].optype[opt_index];
    switch (t1)
    {
    case NONETYPE: //无操作数

        break;
    case IMMTYPE: //立即数
    {

    }

        break;
    case REGTYPE: //寄存器
    {
        unsigned int regnum = disasm.reg[0];
        str_pop = str_pop+ "mov eax," + disasm.reg[0] + "\n";

        if (vmtable[tab_index].bitnum[0] == 8)//操作数为8位
        {
            //这儿判断高位(ah)还是低位(al)
            if (true == disasm.highbit[0])
            {
                str_pop += "xor ebx,ebx\n";
                str_pop += "mov bx,word ptr [edi+eax]\n";
                str_pop += "and bx, 0xFF00\n";
                str_pop += "mov word ptr[edi + eax], 0\n";
                str_pop += "pop dword ptr [edi+eax*4]\n";
                str_pop += "or dword ptr [edi+eax],ebx\n";
            }
            else
            {
                str_pop += "xor ebx,ebx\n";
                str_pop += "mov bx,word ptr [edi+eax]\n";
                str_pop += "and bx, 0xFF\n";
                str_pop += "mov word ptr[edi + eax], 0\n";
                str_pop += "pop dword ptr [edi+eax*4]\n";
                str_pop += "or dword ptr [edi+eax],ebx\n";
            }
        }
        if (vmtable[tab_index].bitnum[0] == 16)//操作数为16位
        {
            str_pop += "pop word ptr[edi + eax*4]\n";
        }
        if (vmtable[tab_index].bitnum[0] == 32)//操作数为32位
        {
            str_pop += "pop dword ptr[edi + eax*4]\n";
        }
        str_pop += "mov eax, 0x66668888\njmp eax\n";
    }
    break;
    case MEMTYPE: //内存(包括段寄存器)
        //2、判断是否是段寄存器
        if (vmtable[tab_index].Segment != 0)
        {
           
        }
        else
        {

        }

        break;

    default:
        break;
    }
    //编译生成的Handler代码,并添加到vHandlerList链表
    CompileHandler(str_pop.GetString());
}

//处理辅助handler
void AssistHandler(t_disasm disasm,int tab_index,int opt_index)
{
    //1、处理辅助handler
    CString str_assist;
    int t1 = vmtable[tab_index].optype[opt_index];
    switch (t1)
    {
    case NONETYPE: //无操作数

        break;
    case IMMTYPE: //立即数
    {
        str_assist = "mov eax,";
        char cstr[56];
        sprintf_s(cstr, 56, "%X", disasm.immconst);//十进制数字转换为十六进制字符串
        str_assist += cstr;
    }

        break;
    case REGTYPE: //寄存器
    {
        str_assist = "mov eax,";

        str_assist = str_assist +disasm.reg[opt_index]+"\nshl eax,2\n";

        if (vmtable[tab_index].bitnum[opt_index]==8)//操作数为8位
        {
            //这儿判断高位(ah)还是低位(al)
            if (true == disasm.highbit[opt_index])
            {
                str_assist += "mov ah,byte ptr[edi+eax]";
            }
            else
            {
                str_assist += "mov al,byte ptr[edi+eax]";
            }
        }
        if (vmtable[tab_index].bitnum[opt_index] == 16)//操作数为16位
        {
            str_assist += "mov ax,word ptr[edi+eax]";
        }
        if (vmtable[tab_index].bitnum[opt_index] == 32)//操作数为32位
        {
            str_assist += "mov eax,dword ptr[edi+eax]";
        }

    }
    break;
    case MEMTYPE: //内存(包括段寄存器)
        //2、判断是否是段寄存器
        if (vmtable[tab_index].Segment != -1)
        {
            //段寄存器不用查看操作数的位数,直接翻译
            int index = vmtable[tab_index].optype[opt_index];
            const char* reg = vregname[2][index + 8];
            
            //构造辅助handler
            str_assist = "mov eax,";
            int decnumb = disasm.adrconst;
            char cstr[56];       
            sprintf_s(cstr, 56, "%X", decnumb);//十进制数字转换位十六进制字符串
            str_assist = str_assist + reg + ":["+ cstr +"]";   
        }
        else
        {
            str_assist = "mov eax,";
            int decnumb = disasm.adrconst;
            char cstr[56];
            sprintf_s(cstr, 56, "%X", decnumb);//十进制数字转换位十六进制字符串
            str_assist = str_assist + cstr;
        }

        break;
       
    default:
        break;
    }

    str_assist += "\npush eax\nmov eax,0x66668888\njmp eax\n";
    printf("%s\n", str_assist.GetString());

    //编译生成的Handler代码,并添加到vHandlerList链表
    CompileHandler(str_assist.GetString());
}

void testvmp(char* TargetCode,_Out_ int &CodeLength)
{
    /*char TargetCode[] = {
        0xC2,0x08,0x00,
         0xFE ,0x05  ,0x00  ,0x10  ,0x40  ,0x00,
        0xBB,0x20,0x00,0x00,0x00,
        0x8A,0x24,0x24,
        0x8B,0xC3,
        0x64 ,0xA1 ,0x30 ,0x00 ,0x00 ,0x00,
        0x66, 0xA1 ,0x00 ,0x10 ,0x40 ,0x00
    };*/
    t_disasm disasm;
    Disasm(TargetCode, 20, (ulong)TargetCode, &disasm, 3);
    CodeLength = disasm.codelen;
    int index = -1;

    for (int i = 0; i < VMTABLEMAXLEN; i++)
    {   
        if (0 == strcmp(vmtable[i].VMInstrName, disasm.vm_name))
        {
            printf("%s\n", vmtable[i].strInstruction);
            if (_stricmp(vmtable[i].strInstruction,"retn")==0)
            {
                GeneralHandler(disasm, i, 0);
                return;
            }
            index = i;
            break;
        }        
    }
    if (-1 != index)
    {
        //1、构造辅助handler
        
        if (-1 != vmtable[index].optype[0])//目的操作数
        {
            AssistHandler(disasm, index, 0);
        }
       
        if (-1 != vmtable[index].optype[1])//源操作数
        {
            AssistHandler(disasm, index, 1);
        }

        //2、普通handler处理
        int opt_num = (vmtable[index].optype[0] ? 1 : 0) + (vmtable[index].optype[1] ? 1 : 0);
        GeneralHandler(disasm,index, opt_num);

        //3、pop到目的操作数
        if (opt_num)//如果有目的操作数才处理
        {
            PopHandler(disasm, index, 0);
        }
       
    }
}

#define VMSTART (0x2000)    //.vmp1节的前0x2000个字节留给VStartVM函数和其他需要处理的东西
#define VMTABLE (0x1000)    //.vmp1节的0x1000地址处存放跳转表
#define VMDISPATCH (0x500) //.vmp1节的0x500地址处存放调度器
int g_handleraddr_calc = 0;//记录handler存放到哪里了
int g_table_calc = 0;//记录跳转表存放到哪里了
//处理g_vHandlerList链表
void SolutionWmcode(char* filebuff,PEInfo peinfo)
{
    PE pe;
    ULONG_PTR ulSizeOfImage =peinfo.ImageBase + pe.AlignSize(peinfo.SizeofImage, peinfo.SectionAlignment);//.vmp1节的开始地址
    ULONG_PTR vHandlerAddr = (ULONG_PTR)(filebuff + VMSTART+ g_handleraddr_calc);//handler存放的地址
    ULONG_PTR vJmpTable = (ULONG_PTR)(filebuff + VMTABLE+ g_table_calc);  //跳转表
    ULONG_PTR vMmDispatcher = ulSizeOfImage + VMDISPATCH;//调度器地址
    list<VHandler>::iterator iter = g_vHandlerList.begin();
    for (int i = 0; i < g_vHandlerList.size(); i++)
    {
        int codelength = (*iter).CodeLen;
        char* assemblecode = (*iter).AssembleCode;
 
        //修复handler跳转到调度器的地址
        *(DWORD*)(assemblecode + codelength - 6) = vMmDispatcher;

        //把handler的地址存入跳转表
        *(WORD*)vJmpTable = g_handleraddr_calc;

        //存入handler代码
        memcpy((char*)vHandlerAddr, assemblecode, codelength);

        vHandlerAddr += codelength;
        vJmpTable += 2;
        g_table_calc += 2;
        g_handleraddr_calc += codelength;
        ++iter;
        ++g_total;//记录handler的个数
    }
    g_vHandlerList.clear();
}


#define path_in "C:\\Users\\86188\\Desktop\\code_vm_test.exe"
#define path_out "C:\\Users\\86188\\Desktop\\加了VM的code_vm_test.exe"
//加载文件,加vm
void LoadFile(char* path)
{
    AllocMemory _alloc;
    PEInfo peinfo;
    PE pe;
    pe.GetPEInformation_(path, &peinfo);

    //去掉随机基址
    peinfo.OptionalHeader->DllCharacteristics = 0;

    //拉伸文件
    ULONG_PTR StretchAddr = pe.StretchFile(peinfo.FileBuffer, peinfo.SizeofImage);
    //更新peinfo信息
    pe.GetPEInformation_1((char*)StretchAddr, &peinfo, peinfo.FileSize);

    //申请内存
    int newsectionsize = 0x1000 * 20;//.vmp1所在节的大小,设置为80k
    char* Vmp1_Buff = _alloc.auto_malloc<char*>(newsectionsize);

    //选择要vm的起始和结束地址
    ULONG_PTR StartAddr = 0x401110 - peinfo.ImageBase + StretchAddr;
    ULONG_PTR EndAddr = 0x401122 - peinfo.ImageBase + StretchAddr;
    int CodeLength = 0;
    int TotelLength = EndAddr - StartAddr;
    for (int i = 0; i < TotelLength;)
    {
        testvmp((char*)StartAddr+i, CodeLength);//把code转换为WMcode
        SolutionWmcode(Vmp1_Buff, peinfo);//处理g_vHandlerList链表
        i += CodeLength;
    }
 
//#define VMSTART (0x2000)    //.vmp1节的前0x2000个字节留给VStartVM函数和其他需要处理的东西
//#define VMTABLE (0x1000)    //.vmp1节的0x1000地址处存放跳转表
//#define VMDISPATCH (0x500) //.vmp1节的0x500地址处存放调度器

    ULONG_PTR ulSizeOfImage = peinfo.ImageBase + pe.AlignSize(peinfo.SizeofImage, peinfo.SectionAlignment);//.vmp1节的开始地址

    ULONG_PTR ulDispatcherAddr = (ULONG_PTR)Vmp1_Buff + VMDISPATCH;

    //填充VStartVM
    int start_len = GetFunSize(VStartVM);
    memcpy(Vmp1_Buff, VStartVM, start_len);
    //修复VMstartVM里的数据
    *(ULONG_PTR*)(Vmp1_Buff + start_len - 6) = ulSizeOfImage + VMDISPATCH;
    *(ULONG_PTR*)(Vmp1_Buff + start_len - 11) = ulSizeOfImage + VMTABLE;

    //填充调度器
    int dispatcher_len = GetFunSize(VMDispatcher);
    memcpy(Vmp1_Buff+ VMDISPATCH, VMDispatcher, dispatcher_len);
    //修复调度器里数据
    *(ULONG_PTR*)(Vmp1_Buff + VMDISPATCH + 1) = ulSizeOfImage + VMSTART;

    //抹掉目标文件里加了vm的代码
    for (int i = 0; i < TotelLength; i++)
    {
        *(char*)(StartAddr + i) = 0x90;
    }

    //在目标文件添加跳转地址 
    int jump_len = GetFunSize(JumpToVmp);
    memcpy((char*)StartAddr, JumpToVmp, jump_len);
    //修复数据
    *(ULONG_PTR*)(StartAddr + 1) = ulSizeOfImage;

    //还原成源文件大小
    char* filebuff = pe.ImageBuff_To_FileBuff((char*)StretchAddr, peinfo.FileSize);

    //添加新节
    pe.addSeciton((ULONG_PTR)filebuff, newsectionsize, (char*)".vmp1");
 
    //申请内存,合并节
    char* NewFileBuff = _alloc.auto_malloc<char*>(newsectionsize + peinfo.FileSize);
    memcpy(NewFileBuff, filebuff, peinfo.FileSize);
    memcpy(NewFileBuff + peinfo.FileSize, Vmp1_Buff, newsectionsize);

    //保存文件
    FileOperation fileopt;
    fileopt.SaveFile_(NewFileBuff, peinfo.FileSize + newsectionsize, (char*)path_out);
}

//主函数
int main()
{
    //加载文件
    LoadFile((char*)path_in);

    std::cout << "Hello World!\n";
    system("pause");
}

代码虚拟机设计.7z

1.06 MB, 下载次数: 135, 下载积分: 吾爱币 -1 CB

免费评分

参与人数 31威望 +2 吾爱币 +126 热心值 +29 收起 理由
太阳王 + 1 我很赞同!
淡DSJ然 + 1 + 1 优秀
阳光下的少年 + 1 很优秀
不愿鞠躬车马前 + 1 + 1 谢谢@Thanks!学到了!
wmslecz + 1 + 1 谢谢@Thanks!
Suk-Lees + 1 + 1 我很赞同!
kuletco + 1 谢谢@Thanks!
hjj134 + 1 + 1 谢谢@Thanks!
Conngas + 1 + 1 够硬核,我喜欢
Earrow + 1 + 1 很厉害的样子,俺得好好学习了
快乐的小星猪 + 1 谢谢@Thanks!
刘罐罐 + 1 我很赞同!
whoisboss + 1 + 1 热心回复!
liuguang123456 + 1 + 1 我很赞同!
zctsir + 1 谢谢@Thanks!
woyucheng + 1 + 1 我很赞同!
luochunyan + 1 + 1 谢谢@Thanks!
石碎大胸口 + 1 + 1 谢谢@Thanks!
烦烦烦 + 1 + 1 谢谢@Thanks!
fengbolee + 1 + 1 用心讨论,共获提升!
gaosld + 1 + 1 用心讨论,共获提升!
独行风云 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
qq3bot + 1 + 1 谢谢@Thanks!
shenjiyuan2hao + 1 + 1 热心回复!
Lixinist + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
莫流云 + 1 + 1 用心讨论,共获提升!
xhp1976 + 1 + 1 用心讨论,共获提升!
yixi + 1 + 1 谢谢@Thanks!
dongzi0712 + 1 + 1 用心讨论,共获提升!
苏紫方璇 + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
Ravey + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

renny 发表于 2020-7-15 19:50
学习了,很深奥
andersgong 发表于 2020-8-5 08:25
fancotse 发表于 2020-8-5 08:56
prontosil 发表于 2020-8-3 18:31
赞一个,原来可以这么设计虚拟机
亲爱的靳萌萌 发表于 2020-8-3 13:38
学到了 楼主厉害  感谢分享
不凡_不懂 发表于 2020-8-1 09:38
学到了学到了,楼主牛逼
coder014 发表于 2020-8-1 09:47
楼主厉害 学习了
xueli 发表于 2020-7-31 10:22
谢谢分享
李斯隆 发表于 2020-7-30 22:07
好好学习!。
chboy 发表于 2020-7-14 19:33
担心违规 内容已删除
Mirana 发表于 2020-7-14 20:19
支持一下
 楼主| 舒默哦 发表于 2020-7-14 20:50
chboy 发表于 2020-7-14 19:33
担心违规 内容已删除

要不得老铁
二娃 发表于 2020-7-14 22:22
我这几天刚好也在写这东西
头都裂开了
 楼主| 舒默哦 发表于 2020-7-14 23:15
二娃 发表于 2020-7-14 22:22
我这几天刚好也在写这东西
头都裂开了

思路理清就很容易了,就那几个handler,细节有问题,可以边调试边改&#129300;
二娃 发表于 2020-7-15 00:40
舒默哦 发表于 2020-7-14 23:15
思路理清就很容易了,就那几个handler,细节有问题,可以边调试边改&#129300;

要考虑的东西挺多的
chboy 发表于 2020-7-15 09:11

我准备说你一发帖就置顶了有点牛逼 但是发出后担心违规就删除了
caicaiwuguo 发表于 2020-7-15 12:42
高深莫测,云里雾里
Lixinist 发表于 2020-7-15 15:11
我在写代码变异,比vm好写点

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
马广顺 + 1 + 1 我很赞同!

查看全部评分

您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-4-19 10:10

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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