当前位置: 首页 > ops >正文

Windows平台Ring3下DLL注入(HOOK)方法整理汇总

1.dll劫持,粗略整理了下,可以劫持的dll有(持续更新):

lpk.dll、usp10.dll、msimg32.dll、midimap.dll、ksuser.dll、comres.dll、ddraw.dll

以lpk为例,在win7下由于lpk被加入KnownDLLs且该注册表值不可修改,使得lpk强制从系统目录加载,

不过可以将lpk.dll加入ExcludeFromKnownDlls来解决,具体可以创建一个lpk.reg文件:

Windows Registry Editor Version 5.00[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager]
"ExcludeFromKnownDlls"=hex(7):6c,00,70,00,6b,00,2e,00,64,00,6c,00,6c,00,00,00,\
00,00

成功导入后需要重新启动电脑才能生效。

参考:http://support.microsoft.com/?scid=kb%3Ben-us%3B164501&x=4&y=12

另外win7下的lpk在编写方面需要注意:

WIN7有的程序调用LPK.DLL的LpkInitialize输出函数在LPK的初始化前面.
要在LpkInitialize这个函数中加入一些处理,并且这部分代码不能加密.

因此为了兼容各个系统,可以在DllMain和LpkInitialize里均做判断,如果没有初始化就进行初始化。下面贴出完整代码:

复制代码
// lpk.cpp : Defines the entry point for the DLL application.
//


// 头文件
#include "stdafx.h"

// 导出函数
#pragma comment(linker, "/EXPORT:LpkInitialize=_AheadLib_LpkInitialize,@1")
#pragma comment(linker, "/EXPORT:LpkTabbedTextOut=_AheadLib_LpkTabbedTextOut,@2")
#pragma comment(linker, "/EXPORT:LpkDllInitialize=_AheadLib_LpkDllInitialize,@3")
#pragma comment(linker, "/EXPORT:LpkDrawTextEx=_AheadLib_LpkDrawTextEx,@4")
//#pragma comment(linker, "/EXPORT:LpkEditControl=_AheadLib_LpkEditControl,@5")
#pragma comment(linker, "/EXPORT:LpkExtTextOut=_AheadLib_LpkExtTextOut,@6")
#pragma comment(linker, "/EXPORT:LpkGetCharacterPlacement=_AheadLib_LpkGetCharacterPlacement,@7")
#pragma comment(linker, "/EXPORT:LpkGetTextExtentExPoint=_AheadLib_LpkGetTextExtentExPoint,@8")
#pragma comment(linker, "/EXPORT:LpkPSMTextOut=_AheadLib_LpkPSMTextOut,@9")
#pragma comment(linker, "/EXPORT:LpkUseGDIWidthCache=_AheadLib_LpkUseGDIWidthCache,@10")
#pragma comment(linker, "/EXPORT:ftsWordBreak=_AheadLib_ftsWordBreak,@11")

// 宏定义
#define EXTERNC extern "C"
#define NAKED __declspec(naked)
#define EXPORT __declspec(dllexport)#define ALCPP EXPORT NAKED
#define ALSTD EXTERNC EXPORT NAKED void __stdcall
#define ALCFAST EXTERNC EXPORT NAKED void __fastcall
#define ALCDECL EXTERNC NAKED void __cdecl

//LpkEditControl导出的是数组,不是单一的函数(by Backer)
EXTERNC void __cdecl AheadLib_LpkEditControl(void);   
EXTERNC __declspec(dllexport) void (*LpkEditControl[14])() = {AheadLib_LpkEditControl};   
//添加全局变量
BOOL g_bInited = FALSE;  
// AheadLib 命名空间
namespace AheadLib
{HMODULE m_hModule = NULL;    // 原始模块句柄// 加载原始模块
    BOOL WINAPI Load(){TCHAR tzPath[MAX_PATH];TCHAR tzTemp[MAX_PATH * 2];GetSystemDirectory(tzPath, MAX_PATH);lstrcat(tzPath, TEXT("\\lpk.dll"));OutputDebugString(tzPath);m_hModule=LoadLibrary(tzPath);if (m_hModule == NULL){wsprintf(tzTemp, TEXT("无法加载 %s,程序无法正常运行。"), tzPath);MessageBox(NULL, tzTemp, TEXT("AheadLib"), MB_ICONSTOP);};return (m_hModule != NULL);    }// 释放原始模块
    VOID WINAPI Free(){if (m_hModule){FreeLibrary(m_hModule);}}// 获取原始函数地址
    FARPROC WINAPI GetAddress(PCSTR pszProcName){FARPROC fpAddress;CHAR szProcName[16];TCHAR tzTemp[MAX_PATH];fpAddress = GetProcAddress(m_hModule, pszProcName);if (fpAddress == NULL){if (HIWORD(pszProcName) == 0){wsprintf(szProcName, "%d", pszProcName);pszProcName = szProcName;}wsprintf(tzTemp, TEXT("无法找到函数 %hs,程序无法正常运行。"), pszProcName);MessageBox(NULL, tzTemp, TEXT("AheadLib"), MB_ICONSTOP);ExitProcess(-2);}return fpAddress;}
}
using namespace AheadLib;
  
//函数声明
void WINAPIV Init(LPVOID pParam);


void WINAPIV Init(LPVOID pParam)
{//在这里添加DLL加载代码
    return; 
} 
// 入口函数
BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved)
{if (dwReason == DLL_PROCESS_ATTACH){DisableThreadLibraryCalls(hModule);if ( g_bInited==FALSE ){Load();g_bInited = TRUE;}//LpkEditControl这个数组有14个成员,必须将其复制过来    
        memcpy((LPVOID)(LpkEditControl+1), (LPVOID)((int*)GetAddress("LpkEditControl") + 1),52);   _beginthread(Init,NULL,NULL);}else if (dwReason == DLL_PROCESS_DETACH){Free();}return TRUE;
}

// 导出函数
ALCDECL AheadLib_LpkInitialize(void)
{if ( g_bInited==FALSE ){Load();g_bInited = TRUE;}GetAddress("LpkInitialize");__asm JMP EAX;
}

// 导出函数
ALCDECL AheadLib_LpkTabbedTextOut(void)
{GetAddress("LpkTabbedTextOut");__asm JMP EAX;
}

// 导出函数
ALCDECL AheadLib_LpkDllInitialize(void)
{GetAddress("LpkDllInitialize");__asm JMP EAX;
}

// 导出函数
ALCDECL AheadLib_LpkDrawTextEx(void)
{GetAddress("LpkDrawTextEx");__asm JMP EAX;
}

// 导出函数
ALCDECL AheadLib_LpkEditControl(void)
{GetAddress("LpkEditControl");__asm jmp DWORD ptr [EAX];//这里的LpkEditControl是数组,eax存的是函数指针
}

// 导出函数
ALCDECL AheadLib_LpkExtTextOut(void)
{GetAddress("LpkExtTextOut");__asm JMP EAX;
}

// 导出函数
ALCDECL AheadLib_LpkGetCharacterPlacement(void)
{GetAddress("LpkGetCharacterPlacement");__asm JMP EAX;
}

// 导出函数
ALCDECL AheadLib_LpkGetTextExtentExPoint(void)
{GetAddress("LpkGetTextExtentExPoint");__asm JMP EAX;
}

// 导出函数
ALCDECL AheadLib_LpkPSMTextOut(void)
{GetAddress("LpkPSMTextOut");__asm JMP EAX;
}

// 导出函数
ALCDECL AheadLib_LpkUseGDIWidthCache(void)
{GetAddress("LpkUseGDIWidthCache");__asm JMP EAX;
}

// 导出函数
ALCDECL AheadLib_ftsWordBreak(void)
{GetAddress("ftsWordBreak");__asm JMP EAX;
}
复制代码



 

2.通过CreateRemoteThread创建远程线程。

XP以下使用代码:

复制代码
BOOL WINAPI RemoteLoadLibrary(LPCTSTR pszDllName, DWORD dwProcessId)
{// 打开目标进程
    HANDLE hProcess = ::OpenProcess(PROCESS_VM_WRITE|PROCESS_CREATE_THREAD|PROCESS_VM_OPERATION, FALSE, dwProcessId);if(hProcess == NULL)return FALSE;// 在目标进程申请空间,存放字符串pszDllName,作为远程线程的参数
    int cbSize = (::lstrlen(pszDllName) + 1);LPVOID lpRemoteDllName = ::VirtualAllocEx(hProcess, NULL, cbSize, MEM_COMMIT, PAGE_READWRITE);::WriteProcessMemory(hProcess, lpRemoteDllName, pszDllName, cbSize, NULL);// 取得LoadLibraryA函数的地址,我们将以它作为远程线程函数启动
    HMODULE hModule=::GetModuleHandle (_T("kernel32.dll"));LPTHREAD_START_ROUTINE pfnStartRoutine = (LPTHREAD_START_ROUTINE)::GetProcAddress(hModule, "LoadLibraryA");// 启动远程线程
    HANDLE hRemoteThread = ::CreateRemoteThread(hProcess, NULL, 0, pfnStartRoutine, lpRemoteDllName, 0, NULL);if(hRemoteThread == NULL){::CloseHandle(hProcess);return FALSE;}::CloseHandle(hRemoteThread);::CloseHandle(hProcess);return TRUE;
}
复制代码

这段代码在vista,win7下不能成功,需要改进,参考:http://bbs.pediy.com/showthread.php?t=101469&highlight=Vista+Win7+CreateRemoteThread

我参考上面资料和代码,稍作整理使之编译通过并能使用,目标进程打开时最好使用PROCESS_ALL_ACCESS权限。

vista的较为简单些,只要修改一个内存里的数值,这里不再实现。

复制代码
typedef struct _CLIENT_ID {HANDLE UniqueProcess;HANDLE UniqueThread;
} CLIENT_ID,*PCLIENT_ID;typedef struct _INITIAL_TEB
{PVOID PreviousStackBase;PVOID PreviousStackLimit;PVOID StackBase;PVOID StackLimit;PVOID AllocatedStackBase;
} INITIAL_TEB, *PINITIAL_TEB;typedef NTSTATUS (NTAPI *TZwAllocateVirtualMemory)(__in     HANDLE ProcessHandle,__inout  PVOID *BaseAddress,__in     ULONG_PTR ZeroBits,__inout  PSIZE_T RegionSize,__in     ULONG AllocationType,__in     ULONG Protect);static TZwAllocateVirtualMemory ZwAllocateVirtualMemory = (TZwAllocateVirtualMemory)GetProcAddress(GetModuleHandle("ntdll.dll"),"ZwAllocateVirtualMemory");typedef NTSYSAPI NTSTATUS (NTAPI *TZwWriteVirtualMemory)    (    IN HANDLE     ProcessHandle,IN PVOID     BaseAddress,IN PVOID     Buffer,IN SIZE_T     NumberOfBytesToWrite,OUT PSIZE_T     NumberOfBytesWritten     );
static TZwWriteVirtualMemory ZwWriteVirtualMemory = (TZwWriteVirtualMemory)GetProcAddress(GetModuleHandle("ntdll.dll"),"ZwWriteVirtualMemory");typedef NTSYSAPI NTSTATUS (NTAPI *TZwProtectVirtualMemory)    (    IN HANDLE     ProcessHandle,IN PVOID *     BaseAddress,IN SIZE_T *     NumberOfBytesToProtect,IN ULONG     NewAccessProtection,OUT PULONG     OldAccessProtection     );
static TZwProtectVirtualMemory ZwProtectVirtualMemory = (TZwProtectVirtualMemory)GetProcAddress(GetModuleHandle("ntdll.dll"),"ZwProtectVirtualMemory");typedef NTSYSAPI NTSTATUS (NTAPI *TZwGetContextThread)    (    IN HANDLE     ThreadHandle,OUT PCONTEXT     Context     );
static TZwGetContextThread ZwGetContextThread = (TZwGetContextThread)GetProcAddress(GetModuleHandle("ntdll.dll"),"ZwGetContextThread");typedef NTSYSAPI NTSTATUS (NTAPI *TZwCreateThread)    (    OUT PHANDLE     ThreadHandle,IN ACCESS_MASK     DesiredAccess,IN POBJECT_ATTRIBUTES ObjectAttributes     OPTIONAL,IN HANDLE     ProcessHandle,OUT PCLIENT_ID     ClientId,IN PCONTEXT     ThreadContext,IN PINITIAL_TEB     UserStack,IN BOOLEAN     CreateSuspended     );
static TZwCreateThread ZwCreateThread = (TZwCreateThread)GetProcAddress(GetModuleHandle("ntdll.dll"),"ZwCreateThread");typedef NTSYSAPI NTSTATUS (NTAPI *TZwResumeThread)    (    IN HANDLE     ThreadHandle,OUT PULONG     SuspendCount     );
static TZwResumeThread ZwResumeThread = (TZwResumeThread)GetProcAddress(GetModuleHandle("ntdll.dll"),"ZwResumeThread");HANDLE WINAPI myCreateRemoteThread(HANDLE hProcess,LPSECURITY_ATTRIBUTES lpThreadAttributes,SIZE_T dwStackSize,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,DWORD dwCreationFlags,LPDWORD lpThreadId)
{//by 80695073(QQ) //email kiss2008ufo@yahoo.com.cn
    CONTEXT    context = {CONTEXT_FULL}; CLIENT_ID  cid={hProcess}; DWORD    ret; HANDLE    hThread = NULL;DWORD    StackReserve;DWORD    StackCommit = 0x1000;ULONG_PTR  Stack = 0;INITIAL_TEB InitialTeb={};ULONG    x; const CHAR myBaseThreadInitThunk[] = {//   00830000    8BFF            mov     edi, edi
        '\x8B','\xFF',//   00830002    55              push    ebp
        '\x55',//   00830003    8BEC            mov     ebp, esp
        '\x8B','\xEC',//   00830005    51              push    ecx   //ntdll.RtlExitUserThread
        '\x51',//   00830006    53              push    ebx   //参数
        '\x53',//   00830007    FFD0            call    eax   //函数地址
        '\xFF','\xD0',//   00830009    59              pop     ecx   //恢复结束函数地址
        '\x59',//   0083000A    50              push    eax   //将刚才的结果压栈
        '\x50',//   0083000B    FFD1            call    ecx   //调用RtlExitUserThread 结束
        '\xFF','\xD1',//  0083000D    90              nop
        '\x90'};PVOID  pBaseThreadThunk = NULL; //不能释放//0、分配非OS的加载函数
    StackReserve = 0x1000;ret = ZwAllocateVirtualMemory(hProcess, /*&stack.ExpandableStackBottom*/(PVOID*)&pBaseThreadThunk, 0, &StackReserve,MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (ret >= 0x80000000){//失败
        TRACE("Error IN myCreateRemoteThread ZwAllocateVirtualMemory0 !\n");goto myCreateRemoteThreadRet;//end
    }ret = ZwWriteVirtualMemory(hProcess,pBaseThreadThunk,(LPVOID)myBaseThreadInitThunk,sizeof(myBaseThreadInitThunk),&x);if (ret >= 0x80000000){//失败
        TRACE("Error IN myCreateRemoteThread ZwAllocateVirtualMemory0 !\n");goto myCreateRemoteThreadRet;//end
    }//1、准备堆栈
    StackReserve = 0x10000;ret = ZwAllocateVirtualMemory(hProcess, /*&stack.ExpandableStackBottom*/(PVOID*)&Stack, 0, &StackReserve,MEM_RESERVE, PAGE_READWRITE); if (ret >= 0x80000000){//失败
        TRACE("Error IN myCreateRemoteThread ZwAllocateVirtualMemory1!\n");goto myCreateRemoteThreadRet;//end
    }TRACE("OK myCreateRemoteThread:ZwAllocateVirtualMemory 0x%08x\n",Stack);InitialTeb.AllocatedStackBase = (PVOID)Stack;InitialTeb.StackBase = (PVOID)(Stack + StackReserve);/* Update the Stack Position */Stack += StackReserve - StackCommit;Stack -= 0x1000;StackCommit += 0x1000;/* Allocate memory for the stack */ret = ZwAllocateVirtualMemory(hProcess,(PVOID*)&Stack,0,&StackCommit,MEM_COMMIT,PAGE_READWRITE);if (ret >= 0x80000000){//失败
        TRACE("Error IN myCreateRemoteThread ZwAllocateVirtualMemory2!\n");goto myCreateRemoteThreadRet;//end
    }TRACE("OK myCreateRemoteThread:ZwAllocateVirtualMemory 2 0x%08x\n",Stack);InitialTeb.StackLimit = (PVOID)Stack;StackReserve = 0x1000; ret = ZwProtectVirtualMemory(hProcess, (PVOID*)&Stack, &StackReserve, PAGE_READWRITE | PAGE_GUARD, &x); if (ret >= 0x80000000){//失败
        TRACE("Error IN myCreateRemoteThread ZwProtectVirtualMemory!\n");goto myCreateRemoteThreadRet;//end
    }/* Update the Stack Limit keeping in mind the Guard Page */InitialTeb.StackLimit = (PVOID)((ULONG_PTR)InitialTeb.StackLimit - 0x1000);//2、准备CONTEXT//  CONTEXT context = {CONTEXT_FULL}; 
    ret = ZwGetContextThread(GetCurrentThread(),&context); if (ret >= 0x80000000){//失败
        TRACE("Error IN myCreateRemoteThread ZwGetContextThread!\n");goto myCreateRemoteThreadRet;//end
    }context.Esp = (DWORD)InitialTeb.StackBase; context.Eip = (DWORD)pBaseThreadThunk; //这里填写需要加载的地址,不过需要自己终结自己
    context.Ebx = (DWORD)lpParameter;//other init//must
    context.Eax = (DWORD)lpStartAddress;context.Ecx = (DWORD)GetProcAddress(GetModuleHandle("ntdll.dll"),"RtlExitUserThread");//0x778B0859;/*win7*///0x77AEEC01;/*vista*/ //ntdll.RtlExitUserThread
    context.Edx = 0x00000000; //nouse
ret = ZwCreateThread(&hThread, THREAD_ALL_ACCESS, 0, hProcess, &cid, &context, &InitialTeb, TRUE); if (ret >= 0x80000000){//失败
        TRACE("Error %d\n",GetLastError());goto myCreateRemoteThreadRet;//end
    }if(lpThreadId){*lpThreadId = (DWORD)cid.UniqueThread;}if (!(dwCreationFlags & CREATE_SUSPENDED)){ZwResumeThread(hThread, NULL);}
myCreateRemoteThreadRet:return hThread;
}
复制代码


 最后通用的使用方法是:

复制代码
// 启动远程线程
    HANDLE hRemoteThread = NULL;OSVERSIONINFO svex = {sizeof(OSVERSIONINFO)};GetVersionEx(&svex);if( svex.dwMajorVersion<=5 ){hRemoteThread = ::CreateRemoteThread(hProcess, NULL, 0, pfnStartRoutine, lpRemoteDllName, 0, NULL);}else{hRemoteThread = myCreateRemoteThread(hProcess, NULL, 0, pfnStartRoutine, lpRemoteDllName, 0, NULL);}
复制代码

 

3.通过SetWindowsHookEx安装钩子,如WH_CALLWNDPROC,WH_KEYBOARD,WH_MOUSE,WH_GETMESSAGE钩子可以实现全局注入。

SetWindowsHookEx(WH_MOUSE,(HOOKPROC)MouseProc,AfxGetInstanceHandle(),dwThreadId);

 

4.AppInit_DLLs方式:

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs注入到所有加载了user32.dll的进程。

win7下会被映射到:HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Windows,

并且需要设置LoadAppInit_DLLs为1时AppInit_DLLs才会被启用,默认为0。

例如在xp下创建一个.reg文件:

Windows Registry Editor Version 5.00[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows]
"AppInit_DLLs"="c:\\message.dll"

手动导入后是可以加载指定dll的,但是在win7下面就不行,通过该.reg文件操作的注册表子键路径并没有被重定向到

HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Windows,

但是通过写代码的方式是成功的:

复制代码
void LoadLibByAppInit_DLLs(LPCTSTR pszDllName,BOOL bInstall)
{HKEY hKey = NULL;DWORD dwRet = 0;//win7下会被映射到:HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Windows
    if ( RegCreateKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows"), 0, 0, 0, KEY_ALL_ACCESS, 0, &hKey, 0)!=ERROR_SUCCESS ){return;}dwRet = RegSetValueEx(hKey,_T("LoadAppInit_DLLs"),0,REG_DWORD,(const BYTE *)&bInstall,sizeof(bInstall));if ( bInstall ){dwRet = RegSetValueEx(hKey,_T("AppInit_Dlls"),0,REG_SZ,(const BYTE *)pszDllName,lstrlen(pszDllName));}else{dwRet = RegSetValueEx(hKey,_T("AppInit_DLLs"),NULL,REG_SZ,NULL,0);}RegCloseKey(hKey);
}
复制代码

在win7下还有一个值RequireSignedAppInit_DLLs,如果为1表示则只加载有签名的dll,默认为0表示不对dll进行验证。

参见:http://msdn.microsoft.com/en-us/library/dd744762(v=vs.85).aspx

 

5.ShellExecuteHooks方式:

local_machine\software\microsoft\windows\currentversion\Explorer\ShellExecuteHooks注入到explorer.exe进程。


6.输入法注入:http://code.google.com/p/windows-config/wiki/Win32IME


7.lsp,SPI过滤注入网络进程:http://www.vckbase.com/document/viewdoc/?id=643

 http://www.vckbase.com/document/viewdoc/?id=808

 

8.BHO。

 

9.输入表方式注入,原理就是为目标pe文件增加一个导入函数,这个导入的函数是在要注入的dll中。

这样当目标PE文件被加载时会由系统来完成它的导入库的装载工作,这样你的DLL就能被加载进去了。

可以使用类似DIYTools的PE工具来完成,代码我就不写了。

 

10.CreateProcess以挂起的方式创建目标进程,修改入口代码加载指定dll,恢复入口代码唤醒进程。

11.使用微软提供的detours库函数DetourCreateProcessWithDll创建进程并为进程加载指定dll,这个方法原理上就是上面的CreateProcess方法,

只不过更简单更稳定了,拿来主义嘛。

12.通过DXG方式注入使用DirectX的进程,暂无资料。

13.RegisterUserApiHook

http://www.xdnf.cn/news/11755.html

相关文章:

  • 安装Quartus_II_9.0
  • 最全整理反面角色谁更适合饰演老大角色,你认识多少?(已收藏)
  • 冰点还原标准版-中文版(全面支持Windows 7)7.0.020.3172(最新版)下载与注册
  • 最新民间偏方大全,个人收集整理,绝对值得收藏
  • 塞班时代JAVA_回忆S60(塞班)年代的JAVA游戏:有没有哪一款是你在课堂偷偷玩的...
  • 夏目友人帐所有妖怪名单
  • 大话运维-IT国产化浪潮下的运维管理
  • 记一次msyql InnoDB导致数据库崩溃,数据库重启失败的问题
  • 学习OpenCV:滤镜系列(15)——羽化(模糊边缘)
  • 服务硬件及RAID配置
  • flowchart流程图编程语言下载_C语言流程图生成工具(AutoFlowchart)
  • 【Python爬虫与数据分析】爬虫代理IP与访问控制
  • 完整版搭建hadoop集群
  • 有关嵌入式、单片机、51单片机、STM32、的一些概念详解
  • 解决系统缺少找不到zipfldr.dll文件的问题
  • 酷盘 文件服务器,酷盘是什么 酷盘怎么使用【使用方法】
  • 五款免费pdf转换成word软件
  • python交易是什么意思_py交易什么梗?起源及流行原因
  • HTML简洁大气带进度条的URL跳转页面源码
  • 应用开放平台 (Open Platform)
  • dedecms织梦去除版权powered by dedecms方法
  • 解决NVIDIA软件或驱动安装包出错
  • 安卓手机启动广告让人心烦?学会这招自动跳过APP广告
  • 三分钟了解TMS系统和WMS仓库管理软件,教你玩转供应链
  • c#winform使用WebBrowser 大全
  • Windows 8 32位简体中文 消费者预览版(Consumer Preview) 安装体验全过程
  • MBR、主分区、扩展分区、逻辑分区、活动分区、系统分区、启动分区讲解
  • 科普:生成式 AI 简介
  • 汉字编码问题
  • C++面向对象(一):面向对象程序设计概述