被VMP HOOK的ZwProtectVirtualMemory介绍

ZwProtectVirtualMemory,是一个修改内存输入的API函数,VirtualProtect和VirtualProtectEx修改内存属性都会通过ZwProtectVirtualMemory这个API函数.

在VMP壳的程序中,注入DLL进行一定的内存修改,但会发现,对WINAPI或者程序的代码段等进行WriteProcessMemory操作,会发现修改错误,错误提示是没有修改权限.

而WriteProcessMemory在调用更底层的ZwWriteVirtualMemory前会先调用ZwProtectVirtualMemory修改原有程序的内存属性.

但VMP HOOK了ZwProtectVirtualMemory,就导致了无法随意修改内存属性

778D0068     E9 A2FFA088     jmp 002E000F                             ; 被HOOK的ZwProtectVirtualMemory
778D006D     33C9            xor ecx,ecx
778D006F     8D5424 04       lea edx,dword ptr ss:[esp+0x4]
778D0073     64:FF15 C000000>call dword ptr fs:[0xC0]
778D007A     83C4 04         add esp,0x4
778D007D     C2 1400         retn 0x14
778D0068     B8 4D000000     mov eax,0x4D                         ; 没被HOOK前的ZwProtectVirtualMemory
778D006D     33C9            xor ecx,ecx
778D006F     8D5424 04       lea edx,dword ptr ss:[esp+0x4]
778D0073     64:FF15 C000000>call dword ptr fs:[0xC0]
778D007A     83C4 04         add esp,0x4
778D007D     C2 1400         retn 0x14

从上面的代码可以看出,被修改的5字节是SSDT操作码,我们需要精准的知道原操作码是多少,但在WIN7下ZwProtectVirtualMemory的操作码是0x4D,而WIN10以及XP,WIN8又是不同的操作码,怎么样可以准确的获取到当前系统的操作码,并且在不修改HOOK的前提下调用ZwProtectVirtualMemory修改内存属性就是本贴的主题.

思路

首先想获取操作码有很多方法.
1.写驱动在驱动层获取SSDT操作码.
首先驱动就不写了.......复杂...而且x64驱动没签名...放弃....
2.读取其他进程的前5字节.
从其他进程读前5字节是可以考虑的,但电脑安装了某些安全软件,他们的防护逻辑是全局DLL注入HOOK....所以你读出来的也可能是被HOOK后的opcode........这个方法也不考虑....放弃
3.从ntdll.dll文件内获取操作码.
这个方法相对而言是最好的...因为你电脑的ntdll.dll被修改的机率还是非常非常低的(反正我没见过),就这个方法了吧,盘他.

详解

1.获取文件数据

首先把ntdll.dll读入到内存中,然后才可以进行下一步操作,直接贴代码好了,没什么难度

        HANDLE hFile = CreateFile(_T("C:\\Windows\\System32\\ntdll.dll"), GENERIC_READ, FILE_SHARE_READ, NULL,OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL);if (hFile == INVALID_HANDLE_VALUE){MessageBox(0, _T("CreateFileError"), _T("error"), MB_OK);return;}SIZE_T ntdllAddr = (SIZE_T)LoadLibrary(_T("ntdll.dll"));SIZE_T apiAddr = (SIZE_T)GetProcAddress((HMODULE)ntdllAddr, "ZwProtectVirtualMemory");if (apiAddr == NULL || ntdllAddr == NULL){MessageBox(0, _T("LoadLibrary|GetProcAddress,Error"), _T("error"), MB_OK);CloseHandle(hFile);return;}//把Ntdll读入进内存DWORD dwHighSize = 0;DWORD dwLowSize = GetFileSize(hFile, &dwHighSize);BYTE* pBuff = new BYTE[dwLowSize + dwHighSize]();DWORD  dwFileSize = 0;ReadFile(hFile, pBuff, dwLowSize + dwHighSize, &dwFileSize, NULL);

2.获取ZwProtectVirtualMemory在文件中的位置

ZwProtectVirtualMemory地址 - ntdll地址 = RVA

先通过RVA找到所在区段,再用RVA - 当前区段RVA + 当前区段文件偏移 = F0A

最后用F0A找到相对的opcode

//寻找所在区段PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBuff;PIMAGE_NT_HEADERS32 pNtHeader = (PIMAGE_NT_HEADERS32)(pDosHeader->e_lfanew + ((SIZE_T)pDosHeader));PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)(((SIZE_T)(pNtHeader)) + sizeof(IMAGE_NT_HEADERS32));SIZE_T Deviation = 0;//ZwProtectVirtualMemory 地址 - ntdll地址 = RVASIZE_T Rva = apiAddr - ntdllAddr;// 先通过RVA找到所在区段,再用RVA-当前区段RVA+当前区段文件偏移 = F0A// 最后用F0A找到相对的opcode,复制到新内存执行再跳转for (WORD i = 0; i < pNtHeader->FileHeader.NumberOfSections; ++i){if (Rva > pSectionHeader[i].VirtualAddress && Rva < pSectionHeader[i].VirtualAddress + pSectionHeader[i].SizeOfRawData){//通过RVA找到所在区段,再用RVA-当前区段RVA+文件偏移 = F0ADeviation = Rva - pSectionHeader[i].VirtualAddress + pSectionHeader[i].PointerToRawData;break;}}if (Deviation == 0){MessageBoxA(0, "计算段内偏移错误", "error", MB_OK);delete[] pBuff;CloseHandle(hFile);return;}

3.绕过HOOK

得到函数在文件内的位置后,可以获取原opcode进行调用,然后再跳转回没被HOOK的位置,还是来个图片好解释
...应该

        // 最后用段内偏移找到相对的opcodeg_pAddr = (BYTE*)VirtualAlloc(0, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);//申请一段堆空间执行被修改的汇编代码if (g_pAddr == NULL){MessageBoxA(0, "VirtualAlloc Error", "error", MB_OK);delete[] pBuff;CloseHandle(hFile);return;}memcpy(g_pAddr, pBuff + Deviation, 5);//x32下VMP修改了前5字节,把文件内的原5字节拷贝过去g_pAddr[5] = 0x68;//pushapiAddr += 5;memcpy(g_pAddr + 6, &apiAddr, 4);//push的地址是ZwProtectVirtualMemory+5的地方g_pAddr[10] = 0xc3;//使用retn过去delete[] pBuff;CloseHandle(hFile);

代码

上面的代码都是一个函数被拆分上传了.x64的也有,和x32不同的地方就只有x64不能直接push太长的地址,所以用了下面的方式来跳转

        mov rax ,地址   48 b8 00 00 00 00 00 00 00 00   len == 9push rax        50                              len == 10mov r10,rcxmov eax,操作码retn

下面是x32和x64完整的代码

#define DefineFuncPtr(name,base) decltype(name) *My_##name = (decltype(name)*)base
#include "windows.h"
#include "tchar.h"
BYTE* g_pAddr = NULL;//ZwProtectVirtualMemory的调用函数
BYTE* g_pHookAddr = NULL;typedef SIZE_T(CALLBACK* PZwProtectVirtualMemory)(HANDLE ProcessHandle,PVOID* BaseAddress,SIZE_T* NumberOfBytesToProtect,ULONG NewAccessProtection,PULONG OldAccessProtection);
PZwProtectVirtualMemory pZwProtectVirtualMemory;void Init64()
{if (g_pAddr != NULL){return;}HANDLE hFile = CreateFile(_T("C:\\Windows\\System32\\ntdll.dll"), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL);if (hFile == INVALID_HANDLE_VALUE){MessageBox(0, _T("CreateFileError"), _T("error"), MB_OK);return;}SIZE_T ntdllAddr = (SIZE_T)LoadLibrary(_T("ntdll.dll"));SIZE_T apiAddr = (SIZE_T)GetProcAddress((HMODULE)ntdllAddr, "ZwProtectVirtualMemory");if (apiAddr == NULL || ntdllAddr == NULL){MessageBox(0, _T("LoadLibrary|GetProcAddress,Error"), _T("error"), MB_OK);CloseHandle(hFile);return;}//把Ntdll读入进内存DWORD dwHighSize = 0;DWORD dwLowSize = GetFileSize(hFile, &dwHighSize);BYTE* pBuff = new BYTE[dwLowSize + dwHighSize]();DWORD  dwFileSize = 0;ReadFile(hFile, pBuff, dwLowSize + dwHighSize, &dwFileSize, NULL);//寻找所在区段PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBuff;PIMAGE_NT_HEADERS64 pNtHeader = (PIMAGE_NT_HEADERS64)(pDosHeader->e_lfanew + ((SIZE_T)pDosHeader));PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)(((SIZE_T)(pNtHeader)) + sizeof(IMAGE_NT_HEADERS64));SIZE_T Deviation = 0;//ZwProtectVirtualMemory 地址 - ntdll地址 = RVASIZE_T Rva = apiAddr - ntdllAddr;// 先通过RVA找到所在区段,再用RVA-当前区段RVA+当前区段文件偏移 = F0A// 最后用F0A找到相对的opcode,复制到新内存执行再跳转for (WORD i = 0; i < pNtHeader->FileHeader.NumberOfSections; ++i){if (Rva > pSectionHeader[i].VirtualAddress && Rva < pSectionHeader[i].VirtualAddress + pSectionHeader[i].SizeOfRawData){//通过RVA找到所在区段,再用RVA-当前区段RVA+文件偏移 = F0ADeviation = Rva - pSectionHeader[i].VirtualAddress + pSectionHeader[i].PointerToRawData;break;}}if (Deviation == 0){MessageBoxA(0, "计算段内偏移错误", "error", MB_OK);delete[] pBuff;CloseHandle(hFile);return;}// 最后用段内偏移找到相对的opcodeg_pAddr = (BYTE*)VirtualAlloc(0, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);//申请一段堆空间执行被修改的汇编代码if (g_pAddr == NULL){MessageBoxA(0, "VirtualAlloc Error", "error", MB_OK);delete[] pBuff;CloseHandle(hFile);return;}/*mov rax ,地址   48 b8 00 00 00 00 00 00 00 00   len == 9push rax        50                              len == 10mov r10,rcxmov eax,操作码retn*/g_pAddr[0] = 0x48;g_pAddr[1] = 0xb8;apiAddr += 8;memcpy(g_pAddr + 2, &apiAddr, 8);//地址是ZwProtectVirtualMemory+7g_pAddr[10] = 0x50;//push raxmemcpy(g_pAddr + 11, pBuff + Deviation, 8);//x64下VMP修改了前8字节,把文件内的原8字节拷贝过去g_pAddr[19] = 0xc3;//retn过去delete[] pBuff;CloseHandle(hFile);
}
void Init32()
{if (g_pAddr != NULL){return;}HANDLE hFile = CreateFile(_T("C:\\Windows\\System32\\ntdll.dll"), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL);if (hFile == INVALID_HANDLE_VALUE){MessageBox(0, _T("CreateFileError"), _T("error"), MB_OK);return;}SIZE_T ntdllAddr = (SIZE_T)LoadLibrary(_T("ntdll.dll"));SIZE_T apiAddr = (SIZE_T)GetProcAddress((HMODULE)ntdllAddr, "ZwProtectVirtualMemory");if (apiAddr == NULL || ntdllAddr == NULL){MessageBox(0, _T("LoadLibrary|GetProcAddress,Error"), _T("error"), MB_OK);CloseHandle(hFile);return;}//把Ntdll读入进内存DWORD dwHighSize = 0;DWORD dwLowSize = GetFileSize(hFile, &dwHighSize);BYTE* pBuff = new BYTE[dwLowSize + dwHighSize]();DWORD  dwFileSize = 0;ReadFile(hFile, pBuff, dwLowSize + dwHighSize, &dwFileSize, NULL);//寻找所在区段PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBuff;PIMAGE_NT_HEADERS32 pNtHeader = (PIMAGE_NT_HEADERS32)(pDosHeader->e_lfanew + ((SIZE_T)pDosHeader));PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)(((SIZE_T)(pNtHeader)) + sizeof(IMAGE_NT_HEADERS32));SIZE_T Deviation = 0;//ZwProtectVirtualMemory 地址 - ntdll地址 = RVASIZE_T Rva = apiAddr - ntdllAddr;// 先通过RVA找到所在区段,再用RVA-当前区段RVA+当前区段文件偏移 = F0A// 最后用F0A找到相对的opcode,复制到新内存执行再跳转for (WORD i = 0; i < pNtHeader->FileHeader.NumberOfSections; ++i){if (Rva > pSectionHeader[i].VirtualAddress && Rva < pSectionHeader[i].VirtualAddress + pSectionHeader[i].SizeOfRawData){//通过RVA找到所在区段,再用RVA-当前区段RVA+文件偏移 = F0ADeviation = Rva - pSectionHeader[i].VirtualAddress + pSectionHeader[i].PointerToRawData;break;}}if (Deviation == 0){MessageBoxA(0, "计算段内偏移错误", "error", MB_OK);delete[] pBuff;CloseHandle(hFile);return;}// 最后用段内偏移找到相对的opcodeg_pAddr = (BYTE*)VirtualAlloc(0, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);//申请一段堆空间执行被修改的汇编代码if (g_pAddr == NULL){MessageBoxA(0, "VirtualAlloc Error", "error", MB_OK);delete[] pBuff;CloseHandle(hFile);return;}memcpy(g_pAddr, pBuff + Deviation, 5);//x32下VMP修改了前5字节,把文件内的原5字节拷贝过去g_pAddr[5] = 0x68;//pushapiAddr += 5;memcpy(g_pAddr + 6, &apiAddr, 4);//push的地址是ZwProtectVirtualMemory+5的地方g_pAddr[10] = 0xc3;//使用retn过去delete[] pBuff;CloseHandle(hFile);
}
SIZE_T MyZwProtectVirtualMemory(HANDLE ProcessHandle, //进程句柄PVOID* BaseAddress,//地址的指针SIZE_T* NumberOfBytesToProtect,//修改的大小,函数调用后会改成成功修改的大小ULONG NewAccessProtection,//新的内存属性PULONG OldAccessProtection)//旧的内存属性
{#ifdef _WIN64Init64();
#elseInit32();
#endifpZwProtectVirtualMemory = (PZwProtectVirtualMemory)g_pAddr;return pZwProtectVirtualMemory(ProcessHandle, BaseAddress, NumberOfBytesToProtect, NewAccessProtection, OldAccessProtection);}

验证下效果

1.程序介绍

一个加了VMP 3.2壳的易语言程序.

已知程序是获取CPU序号的程序,并且这个程序只调用了CPUID这个汇编代码获取了CPU序号,无法对相关的API进行HOOK,而且是个加了VMP壳的程序,只能让其代码段解密后修改,但想精准的修改还需在代码没运行前进行拦截,这里可以通过DLL进行API HOOK判断代码段是否解码,然后再HOOK拦截修改EAX或者EDX

2.寻找代码段解码后一个调用的函数


先看连接器信息,10.00,可能是一个vs2010连接器生成的EXE,vs2010连接器生成的代码在运行main函数之前第一个调用的API是GetSystemTimeAsFileTime

通过测试发现,GetSystemTimeAsFileTime 这个API第一次调用的时候,0x0040112B已经解密完成,并且对其下断能正常断下,可以使用这个API进行代码段是否解码完成.

3.HOOK GetSystemTimeAsFileTime

这里就没什么好说的,相信大家都对HOOK很熟悉了,调用上面我写好的MyZwProtectVirtualMemory函数来修改内存属性.
HOOK GetSystemTimeAsFileTime-> 判断代码是否解码->修改内存属性,然后HOOK 0040112B->稍微修改下CPUID调用后的EAX

case DLL_PROCESS_ATTACH:{// HOOK GetSystemTimeAsFileTime来判断代码段是否解密//保存GetSystemTimeAsFileTime前5字节,并且push  retn跳到GetSystemTimeAsFileTime + 5 位置g_pHookAddr = (BYTE*)VirtualAlloc(0, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);//GetSystemTimeAsFileTime调用接口PVOID addr = (PVOID)GetProcAddress(LoadLibraryA("KernelBase.dll"), "GetSystemTimeAsFileTime");memcpy(g_pHookAddr, addr, 5);//拷贝原5字节g_pHookAddr[5] = 0x68;//psuh  GetSystemTimeAsFileTime + 5 SIZE_T TempAddr = ((SIZE_T)addr) + 5;//GetSystemTimeAsFileTime + 5 memcpy(g_pHookAddr + 6, &TempAddr, 4);g_pHookAddr[10] = 0xc3;//retnSIZE_T size = 5;//e9 00 00 00 00 sizeULONG OldProtect;MyZwProtectVirtualMemory((HANDLE)-1, &addr, &size, PAGE_EXECUTE_READWRITE, &OldProtect);//修改内存属性addr = (PVOID)GetProcAddress(LoadLibraryA("KernelBase.dll"), "GetSystemTimeAsFileTime");BYTE opcode[8] = {};opcode[0] = 0xe9;//jmpSIZE_T MyApiAddr = (((SIZE_T)MyGetSystemTimeAsFileTime) - ((SIZE_T)addr) - 5);memcpy(opcode + 1, &MyApiAddr, 4);memcpy(addr, opcode, 5);//HOOK GetSystemTimeAsFileTime这个函数,并且JMP到MyGetSystemTimeAsFileTimeMyZwProtectVirtualMemory((HANDLE)-1, &addr, &size, OldProtect, &OldProtect);//恢复内存属性break;}

判断代码是否解码

VOID WINAPI MyGetSystemTimeAsFileTime(_Out_ LPFILETIME lpSystemTimeAsFileTime)
{WORD opcode;memcpy(&opcode, (PVOID)(((SIZE_T)GetModuleHandleA(nullptr)) + 0x112B), 2);if (opcode == 0xA20F)//EXEtest.vmp.exe + 0x112B{PVOID addr = (PVOID)(((SIZE_T)GetModuleHandleA(nullptr)) + 0x112B);SIZE_T size = 5;SIZE_T OldProtect;MyZwProtectVirtualMemory((HANDLE)-1, &addr, &size, PAGE_EXECUTE_READWRITE, &OldProtect);//修改属性addr = (PVOID)(((SIZE_T)GetModuleHandleA(nullptr)) + 0x112B);memcpy(addr, new BYTE[5]{ 0xE9, 0x54 , 0x9E , 0x09 , 0x00 }, 5);size = 5;MyZwProtectVirtualMemory((HANDLE)-1, &addr, &size, OldProtect, &OldProtect);//恢复属性addr = (PVOID)(((SIZE_T)GetModuleHandleA(nullptr)) + 0x9AF84);size = 15;MyZwProtectVirtualMemory((HANDLE)-1, &addr, &size, PAGE_EXECUTE_READWRITE, &OldProtect);//修改属性addr = (PVOID)(((SIZE_T)GetModuleHandleA(nullptr)) + 0x9AF84);size = 15;memcpy(addr, new BYTE[15]{ 0x0F ,0xA2 ,0x89 ,0x55 ,0xFC ,0xB8 ,0x66 ,0x66 ,0x66 ,0x66 ,0xE9 ,0x9D ,0x61 ,0xF6 ,0xFF }, 15);MyZwProtectVirtualMemory((HANDLE)-1, &addr, &size, OldProtect, &OldProtect);//恢复属性}DefineFuncPtr(GetSystemTimeAsFileTime, g_pHookAddr);return My_GetSystemTimeAsFileTime(lpSystemTimeAsFileTime);
}

HOOK后对0x0040112B的处理

0040112B   E9 549E0900     jmp EXEtest_.0049AF840049AF84    0FA2            cpuid
0049AF86    8955 FC         mov dword ptr ss:[ebp-0x4],edx
0049AF89    B8 66666666     mov eax,0x66666666
0049AF8E    E9 9D61F6FF     jmp EXEtest_.00401130

完整的DLL代码在附件内,同测试程序同时上传吧.

WIN10测试图片:

# 附录

文件解压密码:52pojie

DLL源码:  dllmain.zip (2.77 KB, 下载次数: 108)

测试程序下载:太大了,上传蓝奏吧:https://lanzous.com/ibjcj7i   解压密码:52pojie

提示:测试用的exe是易语言编写,并且关闭了随机机制,还有修改oep的代码来调用LoadLibrary,详情可以去看没加壳前的exe.

DLL用注入器注入到程序内需要修改下GetSystemTimeAsFileTime的HOOK,因为他只有在开始的时候调用了一次,如果程序已经运行起来再注入是不会生效的.

加壳的测试程序可用<SharpOD 反反调试插件 v0.6b> + <吾爱破解OD>  进行调试观察.

DLL巧妙的绕过被VMP壳HOOK的ZwProtectVirtualMemory相关推荐

  1. 逆向VMP壳的基本思路

    逆向VMP壳的基本思路 发现需求 故事最早发生在2020年的8月份,我参加数学建模大赛,为了赛前准备购买了全套数学课,2021年5月份的时候我准备换台笔记本玩,在整理文件的时候看到了当时买的这个正版软 ...

  2. 某vmp壳原理分析笔记----ELF文件的加载,链接,IDAPYTHON

    某vmp壳原理分析笔记 分析的样本为某数字公司最新免费壳子.之前的壳子已经被很多大佬分析了,这篇笔记的主要目的是比较详细的分析下该vmp壳子的原理,数字壳子主要分为反调试,linker,虚拟机三部分. ...

  3. 【Android 安全】深思数盾 Virbox 加固应用 ( 购买加固服务 | 下载加固软件 | 启动加固软件 | 函数 VMP 壳设置 | 加密选项 | 资源加密 | SO 保护 )

    文章目录 一.购买加固服务 二.下载加固软件 三.启动加固软件 四.函数 VMP 壳设置 五.加密选项 六.资源加密 七.SO 保护 八.开始加固 一.购买加固服务 深思数盾官网地址 : https: ...

  4. X86逆向4:VMP壳内寻找注册码

    本节课将讲解一下重启验证,重启验证在软件中也是非常的常见的,重启验证的原理很简单,用户在注册界面输入注册码以后程序会自动将输入的注册信息保存到配置文件中,这里可能保存到注册表,也可能使用INI文件来保 ...

  5. C#中将dll汇入exe,并加壳

    < DOCTYPE html PUBLIC -WCDTD XHTML StrictEN httpwwwworgTRxhtmlDTDxhtml-strictdtd> 1.合并file1.dl ...

  6. C/C++劫持技术(函数劫持、dll注入、动态库注入、HOOK)

    目录 劫持 detours 实现劫持 步骤: 1. 安装Detours 2. 编译Detours工程 3. 把静态库和头文件引入工程 4. 函数指针与函数的定义 5.拦截 劫持QQ 实现劫持syste ...

  7. 绕过AMSI详细指南:如何利用DLL hijack轻松绕过AMSI

    在RingZer0开设的Red Teaming课程中,研究了DoubleAgent攻击手法.攻击者可以利用DoubeleAgent将任意代码注入到任何一个他想注入的进程中.并且由于注入发生在进程启动的 ...

  8. 大家看看这个vmp壳如何下手脱壳?

    这个程序使用od加载正常,下了任何断点 都断不住,都会断到90 db处,使用工具读取数据目录表,读取到的各段都没有起始地址和大小,最后通过peid查看 里边包括vmp0 vmp1段,经过问度娘,知道是 ...

  9. Windows Dll Injection、Process Injection、API Hook、DLL后门/恶意程序入侵技术

    catalogue 1. 引言 2. 使用注册表注入DLL 3. 使用Windows挂钩来注入DLL 4. 使用远程线程来注入DLL 5. 使用木马DLL来注入DLL 6. 把DLL作为调试器来注入 ...

最新文章

  1. mysql的条件替换_MySQLwhere条件替换疑问?
  2. C_Free引用链接库
  3. IT从业者都应关注的软件行业的变化
  4. 数据分析工具有多好用?Yonghong Z-Suite V8.8 发布,实现20余项功能新改变!
  5. 微信iOS 7.0.9版本更新:今天的朋友圈是一片欢乐的海洋!
  6. Windows系统 notepad命令详解,Windows系统打开记事本
  7. C# OO(初级思想)。
  8. Java读取Rinex 2.11格式的观测值o文件
  9. C#试玩程序设计试题——定向越野(迷宫)
  10. 微软雅黑字体包替换XP的宋体
  11. 香港银行账户被关,应如何取走余额
  12. 微擎修改服务器域名,微擎服务器ip地址修改
  13. java 文件尾部_java 在file的尾部添加数据的两种方法总结
  14. 蒲丰投针计算机模拟ppt,蒲丰投针实验模课件.doc
  15. 电子制造仓储条码管理系统解决方案
  16. 免费下载 | FANUC机器人全套资料!(编程、维护、保养...)
  17. 自制工具将excel文件批量导入到mongodb
  18. 在网页插入朝鲜文字(九千多个朝鲜的文字)
  19. oracle maxdatafiles,Oracle db_files和maxdatafiles参数
  20. android实时识别,Android-中国象棋-实时识别-实时AI

热门文章

  1. 聚观早报 |梅西将于14日淘宝开播;李斌回应蔚来全系车型降3万元
  2. Flutter 实现淡入淡出(Fade)动画效果
  3. 1 - 前端基础--HTML
  4. 创建自签名证书, 对exe文件进行数字签名
  5. 通过adb pull和adb push 手机与电脑之间传输文件
  6. moco框架的简单搭建
  7. php filereader,FileReader API的使用
  8. 通过花生壳,把内网地址转公网地址
  9. Opencore 常见kext驱动详解
  10. 智能家居来了,未来的你需要一个会思考的房子