技术 开发 windows开发 ZEROKO14 2023-11-24 2025-08-22 windows开发相关知识点,涵盖部分驱动级内容
Win32需要学习哪些内容
字符
多线程
线程同步
窗口的本质
windows消息机制
子窗口的使用
进程
内存管理
文件系统
内存映射
DLL
远程注入
模块隐藏
进程通信
HOOK专题(各种类型HOOK/绕过全代码校验)
malloc在windows上的底层也是win32实现的
win32的API 主要是存放在C:/IWINDOWS/system32和C:/IWINDOWS/SysWow64 下面的所有dll
SysWow64 存的是32位dll,而system32存的是64位DLL
几个重要的DLL:
Kernel32.dll:最核心的功能模块,比如管理内存,进程和线程相关的函数等。
User32.dll:是Windows用户界面相关应用程序接口,如创建窗口和发送消息等。
GDI32.dll:全程是Graphical Device Interface(图形设备接口),包含用于画图和显示文本的函数。
使用win32的api只需要包含头文件:#include<windows.h>
windows错误码查询 微软官方错误码大全
win32的类型 LPCSTR -> CONST CHAR \*->const char\*
看起来很复杂但其实只是起的别名
原类型
windows.h起的别名
指针别名
byte
BYTE
PBYTE
word
WORD
PWORD
dword
DWORD
PDWORD
char(ANSI)
CHAR
PCHAR/PSTR
unsigned char
UCHAR
PUCHAR
short
SHORT
PSHORT
unsigned short
USHORT
PUSHORT
int
INT
PINT
unsigned int
UINT
PUINT
bool
BOOL
wchar_t(UTF-16)
WCHAR
PWSTR
WIN32中使用字符串 字符类型:
1 2 3 CHAR szStr[]="中国" ; WCHAR sezStr[]=L"中国" ; TCHAR stzStr[]=TEXT ("中国" );
字符串指针:
1 2 3 PSTR pszStr="中国" ; PWSTR pwszStr=L"中国" ; PTSTR ptszStr=TEXT ("中国" );
由于windows的底层全是unicode,所以unicode性能更好。
编程的时候推荐,函数用宏,类型用PTSTR和TCHAR,字符串用TEXT,项目设置为unicode。
进程 进程提供程序所需的资源,如:数据,代码等等
进程内存空间的地址划分
分区
X86 32位Windows
空指针赋值区(前64KB)
0x00000000~0x0000FFFF
用户模式区
0x00010000~0x7FFEFFFF
64KB禁入区(后64KB)
0x7FFF0000~0x7FFFFFFF
内核
0x80000000~0xFFFFFFFF
每个进程的内核部分其实是同一份
进程的创建 任何进程都是别的进程创建的(第一个进程是操作系统内核创建的)
当我们双击运行的时候,实际上是explorer.exe调用了CreateProcess函数帮我们创建了进程
进程的创建过程
映射EXE文件
创建内核对象EPROCESS
映射系统DLL(ntdll.dll)
创建线程内核对象ETHREAD
系统启动线程
映射DLL(ntdll.LdrInitializeThunk)
线程开始执行
对抗点: 映射DLL之前注入DLL,映射DLL之前替换整个进程等等
创建进程的同时也创建了线程。
CreateProcess函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 BOOL CreateProcessA ( LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ) ;BOOL CreateProcessW ( LPCWSTR lpApplicationName, LPWSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ) ;
dwCreationFlags字段设置挂起启动的意义 :
映射EXE文件
创建内核对象EPROCESS
映射系统DLL(ntdll.dll)
创建线程内核对象ETHREAD
如果是挂起的方式启动
。。。(为所欲为,比如提前注入)
恢复以后再继续执行(ResumeThread函数恢复)
映射DLL(ntdll.LdrInitializeThunk)
线程开始执行
LPSTARTUPINFO结构体指针指向的结构体 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 typedef struct _STARTUPINFOA { DWORD cb; LPSTR lpReserved; LPSTR lpDesktop; LPSTR lpTitle; DWORD dwX; DWORD dwY; DWORD dwXSize; DWORD dwYSize; DWORD dwXCountChars; DWORD dwYCountChars; DWORD dwFillAttribute; DWORD dwFlags; WORD wShowWindow; WORD cbReserved2; LPBYTE lpReserved2; HANDLE hStdInput; HANDLE hStdOutput; HANDLE hStdError; } STARTUPINFOA, *LPSTARTUPINFOA; typedef struct _STARTUPINFOW { DWORD cb; LPWSTR lpReserved; LPWSTR lpDesktop; LPWSTR lpTitle; DWORD dwX; DWORD dwY; DWORD dwXSize; DWORD dwYSize; DWORD dwXCountChars; DWORD dwYCountChars; DWORD dwFillAttribute; DWORD dwFlags; WORD wShowWindow; WORD cbReserved2; LPBYTE lpReserved2; HANDLE hStdInput; HANDLE hStdOutput; HANDLE hStdError; } STARTUPINFOW, *LPSTARTUPINFOW;
对抗点 :_STARTUPINFOA如果由explorer.exe创建的进程填写的和由调试器创建,在不作处理的情况下,给_STARTUPINFOA填写的不一致,(explorer.exe 使用 shell32 中 ShellExecute 的来运行程序, ShellExecute 会清不用的值)(ollydbg 会向 STARTUPINFO 中的 dwFlags 设置 STARTF_FORCEOFFFEEDBACK,而 explorer 不会)
1 2 3 4 5 6 typedef struct _PROCESS_INFORMATION { HANDLE hProcess; HANDLE hThread; DWORD dwProcessId; DWORD dwThreadId; } PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;
LPSECURITY_ATTRIBUTES结构体指针指向的结构体 1 2 3 4 5 typedef struct _SECURITY_ATTRIBUTES { DWORD nLength; LPVOID lpSecurityDescriptor; BOOL bInheritHandle; } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
如果bInheritHandle填的是不允许被继承,那么不需要填写这个字段,直接整个_SECURITY_ATTRIBUTES结构体字段填NULL就可以了,表明不被继承。即父进程[[句柄表]]中字段为0。
创建进程案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 BOOL CreateChildProcess (PTCHAR szChildProcssName,PTCHAR szCommandLine) { STARTUPINFO si; PROCESS_INFORMATION pi; ZeroMemory (&pi,sizeof (pi)); ZeroMemory (&pi,sizeof (si)); si.cb=sizeof (si); if (!CreateProcess ( szChildProcssName, szCommandLine, NULL , NULL , FALSE, 0 , NULL , NULL , &si, &pi) ) { printf ("创建子进程失败,错误:%d\n" ,GetLastError ()); return FALSE; } CloseHandle (pi.hProcess); CloseHandle (pi.hThread); return TRUE; }
WIN32的部分api是通过GetLastError()获取错误原因和返回值。
main函数的参数 1 2 3 4 void main (int argc,char * argv[]) { }
句柄表(与上一章紧密相连) 内核对象 像进程,线程,文件,互斥体,事件等在内核都有一个对应的结构体,这些结构体由内核负责管理。我们管这样的对象叫做内核对象。
内核对象有如下:
Access token
Communications device
Console input
Console screen buffer
Event 事件
File 文件
File mapping 文件映射
I/O completion port
Job
Mailslot
Memory resource notification
Mutex 互斥体
Named pipe 命名管道
Pipe 管道
Process 进程
Semaphore 信号量
Thread 线程
Transaction
Waitable timer
内核对象的共同特征,其对应创建函数中有安全描述符,即LPSECURITY_ATTRIBUTES参数。
如何管理内核对象
内核结构的地址一定是大于0x80000000的,属于内核区,如果应用层访问内核区的地址将直接蓝屏 。为了避免这种情况,不能直接暴露内核地址给应用层,从根源上解决这种隐患。**[[句柄表]]**就是为了不直接暴露内核地址做的隔离层。
句柄表 每一个进程都有一个句柄表 只有进程内核对象才有[[句柄表]]
[[句柄表]]:通过句柄表访问内核对象,而不直接通过内核地址。蓝色表格中的编号就是句柄,即 应用层通过进程中的句柄表中的句柄访问内核对象 。
句柄 :当前进程私有的一个内核对象的索引
句柄就是一道防火墙,隔离应用层和内核层,防止应用层直接访问内核层地址。
句柄就是应用层访问内核对象的安全方式
多进程共享一个内核对象
上图可知,句柄的值只针对当前进程才有意义
A上面的2表示计数器,两个进程的句柄表都记录了A的句柄,所以计数器为2。closeHandle关闭句柄,只有当计数器变为0了,才能真正地释放该内核对象
线程内核对象释放 有一点例外:要真正释放一个线程内核对象,必须线程执行结束并且关闭所有该线程句柄,才能真正释放该线程内核对象。
进程里的唯一线程被释放了,进程才真正被释放。
句柄可以被继承
父进程的句柄表中绿色表格部分表示该句柄是否可以被子进程继承 。
创建子进程的bInheritHandles参数如果指定了true,表示该子进程继承父进程的句柄表,但只能继承绿色表格部分为1的句柄 。
总结,多进程共享内核对象的方式:
通过函数(例:OpenProcess)来得到对应内核对象句柄。
通过继承(设置继承与否相关等属性后,直接通过命令行参数传句柄具体值)
WIN32中句柄的种类
HANDLE 指向内核对象的句柄
HWND 指向窗口的句柄
HDC 指向设备上下文的句柄
HINSTANCE 指向模块的句柄
……(H开头的都是句柄)
进程ID与线程ID 操作系统有一张[[句柄表#全局句柄表|全局句柄表]],里面包含了所有进程和线程,进程ID和线程ID 就是这张表中的句柄
进程ID和线程ID是全局的,可以跨进程 的。ID是唯一 的,不同时存在重复的。
线程 线程独享各自的栈 ,堆是各个线程可以共享的
线程和进程的区别
从内存上 :进程创建时会被分配地址空间,并且包含以下几种内存空间:堆区、栈区、代码区、全局变量区。
线程创建时会分配线程的私有栈,包括:维护参数和局部变量线程栈区,程序计数器(维护线程挂起再运行),寄存器集合等。
线程共享进程中除了线程上下文外的所有内存空间,包括(文件、系统资源等)
从效率上 :进程包含线程,并且拥有更多的数据结构需要维护。所以切换或者创建,进程的效率要慢于线程。
安全性上 :进程间有独立的地址空间,安全性较好;线程间虽然有私有的栈区,当理论上只要知道栈帧地址即可修改其他线程的变量。
创建线程
线程是附属在进程上的执行实体 ,是代码的执行流程。
一个进程可以包含多个线程,但一个进程至少要包含一个线程。
1 2 3 4 5 6 7 8 9 HANDLE CreateThread ( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, __drv_aliasesMem LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId ) ;
上文句柄表中内核对象部分有提到:要真正释放一个线程内核对象,必须线程执行结束并且关闭所有该线程句柄,才能真正释放该线程内核对象。
即线程释放 的两个条件
所以如果创建的线程后续不需要使用句柄再操作该线程,则可以直接创建线程后马上CloseHandle。线程正常执行,线程执行结束后系统释放线程内核对象。
创建线程的各种方法 1) Create/EndThread是Win32方法开始/结束一个线程 2) _beginthreadx/_endthreadex是C RunTime方式开始/结束一个线程 3) AfxBeginThread是在[[MFC]]中开始/结束一个线程
C++11之前 是用_beginthreadex创建线程,内部实现是调用CreareThread,但一般不推荐直接使用CreateThread,因为前者做了许多安全保护的工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 unsigned long _beginthread( void (_cdecl *start_address)(void *), unsigned stack_size, void *arglist ); unsigned long _beginthreadex( void *security, unsigned stack_size, unsigned (_stdcall *start_address)(void *), void *argilist, unsigned initflag, unsigned *thrdaddr );
C++11之后 thread (thread.h中) 使用方式:所有可执行的对象都可以放入thread中,包括,全局函数、类的成员函数、lambda表达式等。
[[C++多线程#概述|详情参考C++多线程]]
线程回调函数 1 2 3 DWORD WINAPI ThreadProc ( _In_ LPVOID lpParameter ) ;
不一定 要按照上面的格式定义现线程回调函数,只要填入CreateThread的时候进行强制转换一下。
线程传参要注意: 因为传的是指针,必须保证该指针指向的变量的生命周期在线程使用完该变量之后结束
除了线程传参外,全局变量线程函数是可以直接使用的。
向线程函数传递变量:
线程参数
全局变量
线程控制 让线程停下 让当前线程停下来
挂起别的线程,即线程挂起计数++
恢复线程挂起计数,即线程挂起计数–
线程挂起计数为0,线程才会真正恢复执行
等待线程结束 线程的四种状态:
新建状态(New):刚被创建
准备状态(Runnable):加载所需的所有资源,等待CPU
运行状态(Running):被CPU执行
挂起状态(Blocked):阻塞,等待唤醒
WaitForSingleObject() 1 2 3 4 5 DWORD WaitForSingleObject ( HANDLE hHandle, DWORD dwMilliseconds ) ;
1 2 3 4 5 6 DWORD WaitForMultipleObjects ( DWORD nCount, const HANDLE *lpHandles, BOOL bWaitAll, DWORD dwMilliseconds ) ;
GetExitCodeThread() 1 2 3 4 5 6 BOOL GetExitCodeThread ( HANDLE hThread, LPDWORD lpExitCode ) ;
线程上下文 CONTEXT结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 typedef struct DECLSPEC_ALIGN (16 ) _CONTEXT { DWORD64 P1Home; DWORD64 P2Home; DWORD64 P3Home; DWORD64 P4Home; DWORD64 P5Home; DWORD64 P6Home; DWORD ContextFlags; DWORD MxCsr; WORD SegCs; WORD SegDs; WORD SegEs; WORD SegFs; WORD SegGs; WORD SegSs; DWORD EFlags; DWORD64 Dr0; DWORD64 Dr1; DWORD64 Dr2; DWORD64 Dr3; DWORD64 Dr6; DWORD64 Dr7; DWORD64 Rax; DWORD64 Rcx; DWORD64 Rdx; DWORD64 Rbx; DWORD64 Rsp; DWORD64 Rbp; DWORD64 Rsi; DWORD64 Rdi; DWORD64 R8; DWORD64 R9; DWORD64 R10; DWORD64 R11; DWORD64 R12; DWORD64 R13; DWORD64 R14; DWORD64 R15; DWORD64 Rip; union { XMM_SAVE_AREA32 FltSave; struct { M128A Header[2 ]; M128A Legacy[8 ]; M128A Xmm0; M128A Xmm1; M128A Xmm2; M128A Xmm3; M128A Xmm4; M128A Xmm5; M128A Xmm6; M128A Xmm7; M128A Xmm8; M128A Xmm9; M128A Xmm10; M128A Xmm11; M128A Xmm12; M128A Xmm13; M128A Xmm14; M128A Xmm15; } DUMMYSTRUCTNAME; } DUMMYUNIONNAME; M128A VectorRegister[26 ]; DWORD64 VectorControl; DWORD64 DebugControl; DWORD64 LastBranchToRip; DWORD64 LastBranchFromRip; DWORD64 LastExceptionToRip; DWORD64 LastExceptionFromRip; } CONTEXT, *PCONTEXT; typedef struct _WOW64_CONTEXT { DWORD ContextFlags; DWORD Dr0; DWORD Dr1; DWORD Dr2; DWORD Dr3; DWORD Dr6; DWORD Dr7; WOW64_FLOATING_SAVE_AREA FloatSave; DWORD SegGs; DWORD SegFs; DWORD SegEs; DWORD SegDs; DWORD Edi; DWORD Esi; DWORD Ebx; DWORD Edx; DWORD Ecx; DWORD Eax; DWORD Ebp; DWORD Eip; DWORD SegCs; DWORD EFlags; DWORD Esp; DWORD SegSs; BYTE ExtendedRegisters[WOW64_MAXIMUM_SUPPORTED_EXTENSION]; } WOW64_CONTEXT;
获取线程上下文
1 2 3 4 BOOL GetThreadContext ( HANDLE hThread, LPCONTEXT lpContext ) ;
设置线程上下文
1 2 3 4 BOOL SetThreadContext ( HANDLE hThread, const CONTEXT *lpContext ) ;
单核多线程 的实现原理就是保存当前线程上下文,读取待切换的线程的上下文,继续执行那个线程。
线程安全问题 每个线程都有自己的栈,而局部变量是存储在栈中的,这就意味着每个线程都有一份自己的“局部变量”,如果线程仅仅使用“局部变量”那么就不存在线程安全问题。
那如果多个线程共用一个全局变量(或者堆空间)呢?
多线程修改同一个内存地址会产生线程安全问题 !
单核多线程也是同样存在这个问题,因为cpu时间片随机分配,在线程回调函数的任意进行状态下,cpu都可能切换线程。即无法保证线程回调函数是一个整体接一个整体。下图表现出来就是cpu会执行图内上面的线程函数的某一句,就可能突然跳到图内下面的线程函数的某一句。
如下两个线程函数:全局变量存剩余票数
产生的问题如下:
解决方法:
要使两个线程函数,必须某个线程函数执行完,另一个线程函数才可以开始执行。
我们把涉及多线程修改的变量设置为临界资源
访问临界资源的那段代码称为临界区
临界区能保证,一次只能有一个线程执行临界区的代码(原子操作) 。
原子操作:不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。
临界区之线程锁的代码实现 线程锁是实现临界区的其中一种方式。
创建全局结构体变量
初始化全局结构体变量
1 InitializeCriticalSection (&cs);
实现临界区
1 2 3 EnterCriticalSection (&cs); LeaveCriticalSection (&cs);
原理图:
真正的原理如何实现的参考内核笔记
互斥体
内核级临界资源怎么办?
要既可以A进程中的X线程可以访问,又要进程b中的y线程可以访问,同时还必须线程安全
互斥体是内核对象。
互斥体就是类比于线程锁中的令牌,只是线程锁图中的令牌是应用层令牌,而互斥体是内核层令牌,所以可以实现跨进程访问。
创建互斥体 1 2 3 4 5 6 7 8 9 10 11 HANDLE CreateMutexA ( LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCSTR lpName ) ;HANDLE CreateMutexW ( LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCWSTR lpName ) ;
bInitialOwner参数 互斥对象是一个内核对象,这个内核对象中有两个特殊的域,一个是用来保存哪个线程当前正拥有这个互斥对象,另一个域是一个递归计数器。当互斥对象的用来保存线程ID的域为0时,表示这个互斥对象没有被任何线程拥有,换句话说,任何一个进程中的线程调用wait函数时,都会马上返回,并将自己的线程ID设置到互斥对象的这个域当中和设置递归计数器的值为1,这时候,如果有其他的线程调用wait等待这个互斥对象时,那么这个线程就会被挂起,直到这个互斥对象用来保存线程ID的域为0时为止。这里面有一个特殊情况,就是,在调用wait函数等待互斥对象时,如果系统发现调用wait的线程ID和互斥对象中保存的线程ID相等,则会马上返回而不是挂起等待 ,这时候,返回的同时,系统会让互斥对象的递归计数器加1。
若为TRUE,互斥器对象内部会记录创建它的线程的线程ID号 并将递归计数设置为1,由于该线程ID非零,所以互斥器处于未触发状态,表示互斥器为创建线程拥有,此时Mutex的状态是无信号的,其他线程中的WaitForSingleObject都将堵塞。
若为FALSE,那么互斥量对象内部的线程ID号将设置为NULL,递归计数设置为0,这意味互斥器不为任何线程占用,处于触发状态。 也就是说,只有当递归计数为0时,该互斥器才属于触发状态,即:解锁。
深入理解bInitialOwner参数:
使用bInitialOwner=TRUE ,互斥锁创建者会自动获取互斥锁。 然后,当您调用WaitForSingleObject ,它再次 获取了互斥锁。 由于win32互斥锁是递归互斥锁,因此每次获取互斥锁时都必须释放一次 - 因此初始创建者需要两次ReleaseMutex调用(但是每个其他线程只能释放一次!)
您可以通过不使用bInitialOwner或通过跳过第一个循环 上的WaitForSingleObject来避免这种bInitialOwner ,只有在GetLastError() != ERROR_ALREADY_EXISTS才有。 如果选择后者,则需要在CreateMutex之前调用SetLastError(0)来清除错误代码。
如果您只需要bInitialOwner进行某种初始设置,如果在进入公共循环之前删除互斥锁,它将简化您的代码。 否则,我强烈建议不要使用bInitialOwner ,除非你有令人信服的理由这样做。
1 2 3 4 HANDLE hMutex = CreateMutex (NULL , false , TEXT ("1" )); WaitForSingleObject (hMutex, INFINITE);HANDLE hMutex = CreateMutex (NULL , true , TEXT ("1" ));
**销毁 当程序不再需要互斥锁时,要减少它的句柄使用计数让系统有机会摧毁他。 **
互斥体方式实现临界区代码 1 2 3 4 5 6 7 8 9 10 11 12 13 HANDLE g_hMutex=CreateMutex (NULL ,FALSE,"XYZ" ); WaitForSingleObject (g_hMutex,INFINITE);for (int i=0 ;i<10 ;i++){ Sleep (1000 ); printf ("A进程的x线程:%d\n" ,i); } ReleaseMutex (g_hMutex);
互斥体实现临界区与线程锁实现临界区的区别
线程锁只能用于单个进程间的线程控制
互斥体可以设定等待超时,但线程锁不能
线程意外终结时,互斥体可以避免无限等待
互斥体效率没有线程锁高(仅仅需要在一个进程控制原子操作的话还是用线程锁)
线程意外终结时会自动释放互斥体!
互斥体可以用于实现只能实例化一个进程 命名互斥锁 如果CreateMutex函数的第三个参数传入一个字符串,那么所创建的锁就是命名的。当一个命名的锁被创建出来以后,当前进程和其他进程如果试图创建相同名字的锁,CreateMutex会返回原来那把锁的句柄,并且GetLastError函数会返回ERROR_ALREADY_EXISTS。这个特点可以使一个程序在同一时刻最多运行一个实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 HANDLE hMutex=CreateMutexA (NULL ,FALSE,"防止多开" ); if (hMutex){ if (GetLastError ()==ERROR_ALREADY_EXISTS) { CloseHandle (hMutex); return 0 ; } } else { CloseHandle (hMutex); return 0 ; }
事件 内核对象,事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 HANDLE CreateEventA ( LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCSTR lpName ) ;HANDLE CreateEventW ( LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCWSTR lpName ) ;
两种事件对象(bManualReset):
true:该函数将创建一个手动重置事件对象,这需要使用 ResetEvent函数将事件状态设置为无信号。就是说只要调用了SetEvent函数就可以通过无数个wait
false:则该函数创建一个自动重置事件对象,系统会在释放单个等待线程后自动将事件状态重置为无信号状态。也就实现了互斥效果,SetEvent一次只能通过一个wait
设置事件有无信号 1 2 3 4 5 6 7 8 9 10 BOOL SetEvent ( HANDLE hEvent ) ;BOOL ResetEvent ( HANDLE hEvent ) ;
线程同步
线程互斥
线程同步
线程互斥 对于共享的进程系统资源,在各单个线程访问时的排他性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其他要使用该资源的线程必须等待,直到占用资源者释放该资源。
线程同步 线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒 $$ 同步=互斥+有序 $$
伪同步与真同步的区别(重点) 经典案例:生产者与消费者案例:通过以下代码也能实现伪同步
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 int g_time = 10 ;int g_Number = 0 ;HANDLE g_hMutex=NULL ; void thread1 () { int current = 0 ; for (int i = 0 ; i < g_time; i++) { WaitForSingleObject (g_hMutex, INFINITE); if (g_Number==0 ) { g_Number = 1 ; printf ("生产者线程生产第%d个\r\n" ,++current); } else { printf ("==============\r\n" ); i--; } ReleaseMutex (g_hMutex); } } void thread2 () { int current = 0 ; for (int i = 0 ; i < g_time; i++) { WaitForSingleObject (g_hMutex, INFINITE); if (g_Number == 1 ) { g_Number=0 ; printf ("消费者线程消费第%d个\r\n" , ++current); } else { printf ("==============\r\n" ); i--; } ReleaseMutex (g_hMutex); } } void main () { g_hMutex = CreateMutex (NULL , false , NULL ); cout << "g_hMutex:" << g_hMutex << endl; HANDLE threadHandleArray[2 ]; threadHandleArray[0 ] = CreateThread (NULL , 0 , (LPTHREAD_START_ROUTINE)thread1, NULL , 0 , NULL ); threadHandleArray[1 ] = CreateThread (NULL , 0 , (LPTHREAD_START_ROUTINE)thread2, NULL , 0 , NULL ); WaitForMultipleObjects (2 , threadHandleArray, true , INFINITE); printf ("测试结束\r\n" ); }
打印结果:
但其实并非真同步,只是显示出来是同步。实际上损耗了cpu很多性能(消耗了更多cpu时间片),上面代码1处和2处会打印很多========,之所以没打印可能是新系统优化好了。但要真正实现同步还是不能靠上面代码,而应该是下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 int g_time = 10 ;int g_Number = 0 ;HANDLE g_hEvent =NULL ; void thread1 () { int current = 0 ; for (int i = 0 ; i < g_time; i++) { WaitForSingleObject (g_hEvent, INFINITE); if (g_Number == 0 ) { g_Number = 1 ; printf ("生产者线程生产第%d个\r\n" , ++current); } else { printf ("==============\r\n" ); i--; } SetEvent (g_hEvent); } } void thread2 () { int current = 0 ; for (int i = 0 ; i < g_time; i++) { WaitForSingleObject (g_hEvent, INFINITE); if (g_Number == 1 ) { g_Number=0 ; printf ("消费者线程消费第%d个\r\n" , ++current); } else { printf ("==============\r\n" ); i--; } SetEvent (g_hEvent); } } void main () { g_hEvent = CreateEvent (NULL , false ,true ,NULL ); HANDLE threadHandleArray[2 ]; threadHandleArray[0 ] = CreateThread (NULL , 0 , (LPTHREAD_START_ROUTINE)thread1, NULL , 0 , NULL ); threadHandleArray[1 ] = CreateThread (NULL , 0 , (LPTHREAD_START_ROUTINE)thread2, NULL , 0 , NULL ); WaitForMultipleObjects (2 , threadHandleArray, true , INFINITE); printf ("测试结束\r\n" ); }
打印结果:
虽然结果都一样,但使用事件的才是真同步!
正是因为SetEvent后被唤醒的线程优先级会被提升,所以事件才能保证执行顺序,而互斥体ReleaseMutex后可能下次cpu时间片还是分到当前线程执行。
【总结】互斥体无法实现真正的同步,事件才可以实现真正的同步 。
窗口
高2G是内核空间,多进程公用,里面有很多系统模块。
其中和窗口最相关的主要是两个系统模块:
kernel32.dll只是一个接口,它真正调用的是ntoskrnl.exe的模块,user32.dll和gdi32.dll也只是win32k.sys的内核模块的接口
GUI 图形用户接口 就是使用user32.dll 表示使用别人已经绘制好的组件。
GDI 图形设备接口 就是使用gdi32.dll 表示自己绘制。
HWND窗口句柄 HWND是窗口的句柄
并且HWND是全局 的索引
桌面的窗口句柄是NULL。
获取窗口句柄的方法有三种
使用FindWindow函数获取指定窗口句柄
获取所有顶层窗口以及它们的子窗口
使用EnumWindows和EnumChildWindows函数以及相对的回调函数EnumWindowsProc和EnumChildWindowsProc获取所有顶层窗口以及它们的子窗口
使用GetDesktopWindow和GetNextWindow函数得到所有的子窗口
获取窗口句柄代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 HWND findWindowOneByOne (char * windowName) { HWND hd = GetDesktopWindow(); hd = GetWindow(hd, GW_CHILD); char s[200 ] = { 0 }; int num = 1 ; while (hd != NULL ) { memset (s, 0 , 200 ); GetWindowText(hd, s, 200 ); if (strcmp (s, windowName) == 0 ) return hd; hd = GetNextWindow(hd, GW_HWNDNEXT); } return 0 ; }
GDI 图形设备接口 Graphics Device Interface
设备对象(HWID)
DC(设备上下文,Device Contexts)
图形对象
图像对象
作用
画笔(Pen)
影响线条,包括颜色,粗细,虚实,箭头形状等
画刷(Brushes)
影响对形状,区域等操作,如使用的颜色,是否有阴影等
字体(Fonts)
影响文字输出的字体
位图(Bitmaps)
影响位图创建,位图操作和保存等
我们绘图的时候,首先是在设备上下文画的,就是一块内存,然后把内存中画好的形状直接打印到设备对象上,也就是窗口句柄上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 HWND hwnd; HDC hdc; HPEN hpen; HBRUSH hBrush; hwnd=(HWND)0x000E0244 ; hdc=GetDC (hwnd); hpen=CreatePen (PS_SOLID,5 ,RGB (0xFF ,00 ,00 )); hBrush=(HBRUSH)GetStockObject (DC_BRUSH); SelectObject (hdc,hpen);SelectObject (hdc,hBrush);MoveToEx (hdc,0 ,400 ,NULL );LineTo (hdc,400 ,400 );SetDCBrushColor (hdc,RGB (0xFF ,0xFF ,00 ));Rectangle (hdc,0 ,0 ,100 ,100 );DeleteObject (hpen);DeleteObject (hBrush);ReleaseDC (hwnd,hdc);
上面H开头的类型全是句柄,所谓句柄就是隔离用户层和内核层的工具,其真正所在都在零环。
消息队列
Q:什么是消息?
A:当我们点击鼠标的时候,或者当我们按下键盘的时候,操作系统都要把这些动作记录下来,存储到一个结构体中,这个结构体就是消息 。
每个窗口都是在内核层有一个结构体记录了一切信息,供操作系统索引。
每个线程只有一个消息队列
操作系统先捕获到我们的键盘鼠标操作,然后根据每个窗口在内核的结构体中的信息,找到对应窗口的对应负责消息队列的线程,将消息放进他的消息队列 。
窗口对象中有个成员记录了负责消息队列的线程的指针。
一个线程可以有很多个窗口,多个窗口共用一个消息队列线程。
每个窗口只属于一个线程
红线是操作系统分发消息的过程。键盘,鼠标,内核程序的操作被操作系统捕获,封装成消息结构体,根据消息针对的窗口的对象找到对应的消息队列线程,将消息放进对应消息队列线程的消息队列
第一个windows窗口程序 控制台程序的默认入口是main,windows程序的默认入口是WinMain
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { TCHAR className[]=TEXT ("My First Window" ); WNDCLASS wndclass={0 }; wndclass.hbrBackground=(HBRUSH)COLOR_BACKGROUND; wndclass.lpszClassName=className; wndclass.hInstance=hInstance; wndclass.lpfnWndProc=myWindowProc; RegisterClass (&wndclass); HWND hwnd=CreateWindow (className,TEXT ("我的第一个窗口" ),WS_OVERLAPPEDWINDOW,10 ,10 ,600 ,300 ,NULL ,NULL ,hInstance,NULL ); if (hwnd==NULL ) { char szOutBuff[0x80 ]; sprintf (szOutBuff,"Error:%d" ,GetLastError ()); OutputDebugString (szOutBuff); return 0 ; } ShowWindow (hwnd,SW_SHOW); MSG msg; bool bRet; while ((bRet=GetMessage (&msg,NULL ,0 ,0 ))!=0 ) { if (bRet==-1 ) { char szOutBuff[0x80 ]; sprintf (szOutBuff,"Error:%d" ,GetLastError ()); OutputDebugString (szOutBuff); } else { TranslateMessage (&msg); DispatchMessage (&msg); } } }
【总结】
先定义窗口类,指明了窗口是怎么样的,窗口的回调函数是谁。
创建窗口,该函数在内核层创建真正的窗口对象,并且在该线程内核对象创建消息队列
线程循环从消息队列中取消息进行处理,分发给对应的窗口回调函数。
做完上述操作后,若操作系统捕获到用户输入,将其封装成消息,根据用户输入的窗口对象找到对应的消息队列线程对象,将消息放到其消息队列中。当线程调用DispatchMessage,操作系统拿着MSG结构体中的HWND找到窗口对象,由操作系统调用对应窗口对象的回调函数。
下面有上面代码中函数的详解
WNDCLASS结构体 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 typedef struct tagWNDCLASSA { UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCSTR lpszMenuName; LPCSTR lpszClassName; } WNDCLASSA, *PWNDCLASSA, NEAR *NPWNDCLASSA, FAR *LPWNDCLASSA; typedef struct tagWNDCLASSW { UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCWSTR lpszMenuName; LPCWSTR lpszClassName; } WNDCLASSW, *PWNDCLASSW, NEAR *NPWNDCLASSW, FAR *LPWNDCLASSW;
lpfnWndProc成员(窗口函数) 处理发送到窗口的消息的应用程序定义的回调函数,格式如下:(函数名可以随便改)
1 2 3 4 5 6 7 8 9 LRESULT CALLBACK myWindowProc ( _In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam ) { return DefWindowProc (hwnd,uMsg,wParam,lParam); }
窗口回调函数实际上是由操作系统发起调用
CreateWindow创建窗口函数 创建重叠窗口、弹出窗口或子窗口。它指定窗口类、窗口标题、窗口样式和(可选)窗口的初始位置和大小。该函数还指定窗口的父级或所有者(如果有)以及窗口的菜单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 void CreateWindowA ( lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam ) ;void CreateWindowW ( lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam ) ;
只要线程调用创建窗口函数,就会有消息队列
ShowWindow显示窗口函数 1 2 3 4 BOOL ShowWindow ( HWND hWnd, int nCmdShow ) ;
GetMessage函数 取消息队列中的消息的函数,若消息队列中没有消息他会阻塞直到有消息才继续执行
1 2 3 4 5 6 7 8 9 10 11 BOOL GetMessage ( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax ) ;
DispatchMessage分发消息函数 由于每个窗口都有对应的消息处理函数,该函数就是为了让操作系统调用对应窗口的消息处理函数
1 2 3 4 LRESULT DispatchMessage ( const MSG *lpMsg ) ;
消息结构中有窗口HWND,下面有MSG结构体详解
TranslateMessage转换消息函数 将虚拟键消息转换为字符消息。字符消息被发送到调用线程的消息队列,以便在线程下次调用GetMessage 或PeekMessage 函数时读取。
写了TranslateMessage函数能转换出WM_CHAR这样的消息。不写TranslateMessage,按下键盘的时候不存在WM_CHAR这样的消息。
1 2 3 4 5 BOOL TranslateMessage ( const MSG *lpMsg ) ;
WM_CHAR的wParam参数直接为键盘的字符
1 2 3 4 5 6 7 8 9 10 11 12 case WM_KEYDOWN: OutputDebugStringA ("WM_KEYDOWN" ); break ; case WM_KEYUP: OutputDebugStringA ("WM_KEYUP" ); break ; case WM_CHAR: char szOutBuff[0x80 ]; sprintf (szOutBuff, "%c" , wParam); OutputDebugStringA (szOutBuff); break ;
有TranslateMessage的情况
无TranslateMessage的情况
windows程序调试信息的输出 1 2 3 char szOutBuff[0x80 ];sprintf (szOutBuff,"Error:%d" ,GetLastError ());OutputDebugStringA (szOutBuff);
MSG结构体 1 2 3 4 5 6 7 8 9 10 typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; DWORD lPrivate; } MSG, *PMSG, *NPMSG, *LPMSG;
hwnd,message,wParam,lParam这四个参数就是操作系统调用窗口回调函数的时候会给你传进来的参数。 在窗口回调函数中可以针对性的进行处理
WINDOWS提供了编号的宏,message参数用于判断消息类型,罗列几个出来,如下:
1 2 3 4 5 6 7 8 9 10 #define WM_NULL 0x0000 #define WM_CREATE 0x0001 #define WM_DESTROY 0x0002 #define WM_MOVE 0x0003 #define WM_SIZE 0x0005 #define WM_ACTIVATE 0x0006
在微软官方在线开发者手册,可以搜索信息名,比如WM_KEYDOWM可以查到wParam和lParam的具体含义。
子窗口
WINDOWS提供了几个预定义的窗口类以方便我们的使用,我们一般把它们叫做子窗口控件,简称控件
控件会自己处理消息,并在自己状态发生改变时通知父窗口
预定义的控件有:按钮,复选框,编辑框,静态字符串标签和滚动条等。(还有些通用控件,需要自己安装模块)
父窗口的消息处理回调函数中可以直接处理子窗口穿过来的消息。
实际项目中因为win32窗口不好看,很少有直接用win32来开发界面的。
定义子窗口不需要写WNDCLASS结构体和注册窗口类。
创建子窗口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 CreatWindow ( "EDIT" , "" , WS_CHILD|WS_VISIBLE|WS_VSCROLL|ES_MULTILINE, 10 , 10 , 500 , 300 , hWnd, (HMENU)1 , hInst, NULL ); CreatWindow ( "BUTTON" , "设置" , WS_CHILD|WS_VISIBLE, 520 , 180 , 60 , 30 , hWnd, (HMENU)2 , hInst, NULL );
如何查特殊样式
微软在线开发者手册CreatWindow函数详解页中的dwStyle参数罗列了所有的通用样式属性。但是还有特有样式属性,这种特有的样式属性要针对子控件去找。比如说要找编辑框的特有样式属性就搜索Edit Control Styles 。
SeTDlgItemText函数 设置控件的标题或文本
1 2 3 4 5 6 7 8 9 10 BOOL SetDlgItemTextA ( HWND hDlg, int nIDDlgItem, LPCSTR lpString ) ;BOOL SetDlgItemTextW ( HWND hDlg, int nIDDlgItem, LPCWSTR lpString ) ;
内存相关 虚拟内存与物理内存
虚拟内存 参考《现代操作系统》交换技术讲解。
虚拟内存地址划分:
分区
X86 32位Windows
空指针赋值区(前64KB)
0x00000000~0x0000FFFF
用户模式区
0x00010000~0x7FFEFFFF
64KB禁入区(后64KB)
0x7FFF0000~0x7FFFFFFF
内核
0x80000000~0xFFFFFFFF
说明:
线性地址有4G,但未必都能访问(未申请的内存不能访问)
所以需要记录哪些地方分配了
物理内存
因特尔x86CPU架构将物理内存按照4KB的方式分成一页来进行页式管理
物理页和内存条之间还有一层映射。
可供使用的物理内存
MmNumberOfPhysicalPages(物理页数)*4=物理内存
硬盘伪装的物理内存。(可选)
能够识别的物理内存
32位系统最多可以识别物理内存为64G,但由于操作系统的限制,比如XP,只能识别4G(Windows 2003服务器版本 可以识别4G以上)
物理内存不够用,可以把硬盘当成内存,操作如下:
自定义大小中的初始大小可以修改硬盘当物理内存使用的大小。该大小可以在c盘直观地看到一个叫pagefile.sys的文件,占用就和你设置的值一样。
对于程序员来说感知不到物理内存的存在 是因为操作系统做了下面这些事:
已经分配的虚拟内存也可能没有物理页 ,因为可能被临时移动到硬盘去给物理内存腾出空间放新的进程了。
私有内存与申请释放
私有内存(又叫线性内存):当前物理页只能某个进程使用,别的进程无法使用的。
共享内存(又叫Mapped内存):多个进程都可以共用的物理页
申请内存的两种方式
通过VirtualAlloc/VirtualAllocEx申请的:Private Memory(私有内存)
通过CreateFileMapping映射的:Mapped Memory(映射内存,共享内存)
真正的申请内存只有上述这两个方式,其他如malloc,new其实和内存没有关系。
new=malloc+构造函数;
而malloc是从已经由操作系统申请好的内存中再拿一小块来用。程序启动的时候,操作系统已经为程序分配好了内存
malloc的本质就是malloc->HeapAlloc->没有进内核。因为内存实际上已经分配好了
私有内存申请与释放 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 LPVOID VirtualAlloc ( LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect ) ;BOOL VirtualFree ( LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType ) ;LPVOID VirtualAllocEx ( HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect ) ;
可在微软官方开发者手册搜索Memory Protection Constants,有flProtect参数的可选项罗列
公有内存申请释放 公有内存申请释放案例 1 2 3 4 5 6 7 8 9 10 11 HANDLE hMapFile=CreateFileMapping ((HANDLE)-1 , NULL , PAGE_READWRITE, 0 , 0x1000 , NULL ); DWORD newMemAddress=(DWORD)MapViewOfFile (hMapFile, FILE_MAP_ALL_ACCESS, 0 , 0 , 0x1000 ); *(DWORD*)newMemAddress = 0x12345678 ; printf ("%p虚拟地址中的值为:%p\r\n" , newMemAddress, *(DWORD*)newMemAddress);UnmapViewOfFile ((LPCVOID)newMemAddress);CloseHandle (hMapFile);
结果:
下断点查看那块内存:
CreateFileMapping函数(重点) 创建或打开文件映射内核对象
功能上:申请物理页或者申请物理页并把文件映射到物理页
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 HANDLE CreateFileMappingA ( HANDLE hFile, LPSECURITY_ATTRIBUTES lpFileMappingAttributes, DWORD flProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCSTR lpName ) ;HANDLE CreateFileMappingW ( HANDLE hFile, LPSECURITY_ATTRIBUTES lpFileMappingAttributes, DWORD flProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCWSTR lpName ) ;
文件映射内核对象 就可以理解成 是一个物理页内核对象
CreateFileMapping映射到物理页并不会按照[[PE]]格式从硬盘到内存伸缩展开,而是按照文件原来的二进制格式映射到内存中
他的释放要当所有进程都不用这个物理页的时候,操作系统会将他清除。不使用的时候CloseHandle关闭物理页内核对象句柄。
MapViewOfFile物理页映射到虚拟地址函数 CreateFileMapping只是申请物理页或者申请物理页并把文件映射到物理页,但并没有映射物理页到进程的虚拟地址空间,下面函数就是实现这个过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 LPVOID MapViewOfFile ( HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow, SIZE_T dwNumberOfBytesToMap ) ;BOOL UnmapViewOfFile ( LPCVOID lpBaseAddress ) ;
【重点】其实一个进程中绝大多数内存都是映射内存,只有如堆和栈还有自己virtualAlloc的空间才是私有内存。
文件系统 文件系统是操作系统用来管理磁盘上文件的方法和数据结构;简单点说就是在磁盘上如何组织文件的方法 。
NTFS
FAT32
磁盘分区容量
2T(2048G)
32G
单个文件容量
4G以上
最大4G
EFS加密
支持
不支持
磁盘配额
支持
不支持
EFS加密 :是让同一个电脑上别的用户无法访问
下图设置加密:
磁盘配额 :让别的用户只能固定使用某个磁盘多少空间
文件系统:
文件相关API 文件相关API不需要区分文件系统的概念。windows API已经屏蔽了文件系统的底层实现。
卷相关API 硬盘分成多个虚拟逻辑驱动器,这个虚拟逻辑驱动器就是卷 。
卷 是文件系统最上层的组织形式。
获取卷(有哪些卷,是什么)GetLogicalDrives
获取一个卷的盘符的字符串GetLogicalDriveStrings
获取卷的类型GetDriveType
获取卷的信息GetVolumeInformation
GetLogicalDrives 1 2 3 DWORD GetLogicalDrives () ;
GetLogicalDriveStrings 1 2 3 4 5 6 7 8 9 10 11 DWORD GetLogicalDriveStringsA ( DWORD nBufferLength, LPSTR lpBuffer ) ;DWORD GetLogicalDriveStringsW ( DWORD nBufferLength, LPWSTR lpBuffer ) ;
lpBuffer获取到的字符串如下:
GetDriveType 1 2 3 4 5 6 7 UINT GetDriveTypeA ( LPCSTR lpRootPathName ) ;UINT GetDriveTypeW ( LPCWSTR lpRootPathName ) ;
详细信息查询文档
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 BOOL GetVolumeInformationA ( LPCSTR lpRootPathName, LPSTR lpVolumeNameBuffer, DWORD nVolumeNameSize, LPDWORD lpVolumeSerialNumber, LPDWORD lpMaximumComponentLength, LPDWORD lpFileSystemFlags, LPSTR lpFileSystemNameBuffer, DWORD nFileSystemNameSize ) ;BOOL GetVolumeInformationW ( LPCWSTR lpRootPathName, LPWSTR lpVolumeNameBuffer, DWORD nVolumeNameSize, LPDWORD lpVolumeSerialNumber, LPDWORD lpMaximumComponentLength, LPDWORD lpFileSystemFlags, LPWSTR lpFileSystemNameBuffer, DWORD nFileSystemNameSize ) ;
使用案例 1 2 3 4 5 6 TCHAR szVolumneName[260 ] = { 0 }; DWORD dwVolumneSerial = 0 ; DWORD dwMaxLength = 0 ; DWORD dwFileSystem = 0 ; TCHAR szFileSystem[260 ] = { 0 }; GetVolumeInformation (TEXT ("C://" ), szVolumneName, 260 , &dwVolumneSerial, &dwMaxLength, &dwFileSystem, szFileSystem, 260 );
目录相关API
创建目录CreateDirectory
删除现有空目录RemoveDirectory
修改目录名称MoveFile
获取程序当前目录GetCurrentDirectory
设置程序当前目录SetCurrentDirectory
要具备管理员权限
CreateDirectory 目录也是个内核对象
1 2 3 4 5 6 7 8 9 10 11 12 BOOL CreateDirectoryA ( LPCSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes ) ;BOOL CreateDirectoryW ( LPCWSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes ) ;
RemoveDirectory 只能删除现有的空目录
1 2 3 4 5 6 7 8 BOOL RemoveDirectoryA ( LPCSTR lpPathName ) ;BOOL RemoveDirectoryW ( LPCWSTR lpPathName ) ;
MoveFile 移动 现有文件或目录,包括其子目录。(可以用于改名 )
1 2 3 4 5 6 7 8 BOOL MoveFileA ( LPCSTR lpExistingFileName, LPCSTR lpNewFileName ) ;BOOL MoveFileW ( LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName ) ;
GetCurrentDirectory/SetCurrentDirectory 获取和设置工作目录绝对路径
1 2 3 4 5 6 7 8 9 DWORD GetCurrentDirectory ( DWORD nBufferLength, LPTSTR lpBuffer ) ;BOOL SetCurrentDirectory ( LPCTSTR lpPathName ) ;
文件相关API
创建或打开文件或 I/O 设备CreateFile
关闭文件CloseHandle
获取文件长度GetFileSize
获取文件的属性和信息GetFileAttributes()/GetFileAttributesEx
读/写/拷贝/删除 文件ReadFile()/WriteFile()/CopyFile()/DeleteFile
遍历某个盘查找文件FindFirstFile()/FindNextFile
CreateFile 创建或打开文件或 I/O 设备。最常用的 I/O 设备如下:文件、文件流、目录、物理磁盘、卷、控制台缓冲区、磁带驱动器、通信资源、邮槽和管道。该函数返回一个句柄,该句柄可用于根据文件或设备以及指定的标志和属性为各种类型的 I/O 访问文件或设备。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 HANDLE CreateFileA ( LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile ) ;HANDLE CreateFileW ( LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile ) ;
案例 1 2 3 4 5 6 7 8 9 10 HANDLE hFile = CreateFile ( TEXT ("C:\\A.txt" ), GENERIC_READ|GENERIC_WRITE, 0 , NULL , CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL );
GetFileSize 1 2 3 4 5 6 DWORD GetFileSize ( HANDLE hFile, LPDWORD lpFileSizeHigh ) ;
该函数成功的话,对于大文件来说,返回值和lpFileSizeHigh参数共同组成文件大小
GetFileAttributes()/GetFileAttributesEx 检索指定文件或目录的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 BOOL GetFileAttributesExA ( LPCSTR lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId, LPVOID lpFileInformation ) ;BOOL GetFileAttributesExW ( LPCWSTR lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId, LPVOID lpFileInformation ) ;DWORD GetFileAttributesA ( LPCSTR lpFileName ) ;DWORD GetFileAttributesW ( LPCWSTR lpFileName ) ;
WIN32_FILE_ATTRIBUTE_DATA 结构
1 2 3 4 5 6 7 8 typedef struct _WIN32_FILE_ATTRIBUTE_DATA { DWORD dwFileAttributes; FILETIME ftCreationTime; FILETIME ftLastAccessTime; FILETIME ftLastWriteTime; DWORD nFileSizeHigh; DWORD nFileSizeLow; } WIN32_FILE_ATTRIBUTE_DATA, *LPWIN32_FILE_ATTRIBUTE_DATA;
FILETIME结构体
1 2 3 4 typedef struct _FILETIME { DWORD dwLowDateTime; DWORD dwHighDateTime; } FILETIME, *PFILETIME, *LPFILETIME;
要将 FILETIME 结构转换为易于向用户显示的时间,请使用 FileTimeToSystemTime 函数。
ReadFile()/WriteFile()/CopyFile()/DeleteFile 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 BOOL ReadFile ( HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped ) ;BOOL WriteFile ( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped ) ;BOOL CopyFile ( LPCTSTR lpExistingFileName, LPCTSTR lpNewFileName, BOOL bFailIfExists ) ;BOOL DeleteFileA ( LPCSTR lpFileName ) ;BOOL DeleteFileW ( LPCWSTR lpFileName ) ;
读取文件案例 test.txt如图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 HANDLE hFile = CreateFile ( TEXT ("C:\\Users\\Administrator\\Desktop\\test.txt" ), GENERIC_READ | GENERIC_WRITE, 0 , NULL , OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); cout<<hFile<<endl; if (hFile) { char * pszBuffer; DWORD lowSize =GetFileSize (hFile, NULL ); if (lowSize) { pszBuffer = (char *)malloc (lowSize+1 ); ZeroMemory (pszBuffer, lowSize + 1 ); SetFilePointer (hFile, 0 , NULL , FILE_BEGIN); DWORD dwReadLength = 0 ; ReadFile (hFile, pszBuffer, lowSize, &dwReadLength, NULL ); cout<<pszBuffer<<endl; CloseHandle (hFile); free (pszBuffer); } }
案例输出如图:
红线是bom头
写文件案例 1 2 3 char szBuffer[] = "中国123" ;DWORD dwWritten = 0 ; WriteFile (hFile, szBuffer, strlen (szBuffer), &dwWritten, NULL );
结果如图:
拷贝文件和删除文件案例 1 2 3 4 CopyFileA ("C:\\test.txt" ,"C:\\test2.txt" ,FALSE);DeleteFileA ("C:\\test.txt" );
FindFirstFile()/FindNextFile 根据文件名查找文件。该函数到一个文件夹(包括子文件夹)去搜索指定文件(或部分名称,如果使用通配符)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 HANDLE FindFirstFileA ( LPCSTR lpFileName, LPWIN32_FIND_DATAA lpFindFileData ) ;HANDLE FindFirstFileW ( LPCWSTR lpFileName, LPWIN32_FIND_DATAW lpFindFileData ) ;BOOL FindNextFileA ( HANDLE hFindFile, LPWIN32_FIND_DATAA lpFindFileData ) ;BOOL FindNextFileW ( HANDLE hFindFile, LPWIN32_FIND_DATAW lpFindFileData ) ;
WIN32_FIND_DATA结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 typedef struct _WIN32_FIND_DATAA { DWORD dwFileAttributes; FILETIME ftCreationTime; FILETIME ftLastAccessTime; FILETIME ftLastWriteTime; DWORD nFileSizeHigh; DWORD nFileSizeLow; DWORD dwReserved0; DWORD dwReserved1; CHAR cFileName[MAX_PATH]; CHAR cAlternateFileName[14 ]; DWORD dwFileType; DWORD dwCreatorType; WORD wFinderFlags; } WIN32_FIND_DATAA, *PWIN32_FIND_DATAA, *LPWIN32_FIND_DATAA;
dwFileAttributes可以是如下属性,通过这个字段可以检查找到的究竟是一个文件还是一个子目录
1 2 3 4 5 6 7 8 ●FILE_ATTRIBUTE_ARCHIVE——文件包含归档属性。 ●FILE_ATTRIBUTE_COMPRESSED——文件和目录被压缩。 ●FILE_ATTRIBUTE_DIRECTORY——找到的是一个目录。 ●FILE_ATTRIBUTE_HIDDEN——文件包含隐含属性。 ●FILE_ATTRIBUTE_NORMAL——文件没有其他属性。 ●FILE_ATTRIBUTE_READONLY——文件包含只读属性。 ●FILE_ATTRIBUTE_SYSTEM——文件包含系统属性。 ●FILE_ATTRIBUTE_TEMPORARY——文件是一个临时文件。
查找文件案例
1 2 3 4 5 _WIN32_FIND_DATAA p; HANDLE h = FindFirstFileA ("C:\\Users\\Administrator\\Desktop\\123\\*.txt" , &p); puts (p.cFileName); while (FindNextFileA (h, &p)) puts (p.cFileName);
内存映射文件实现读写文件 内存映射文件:把一个硬盘里的文件直接映射到物理页上,直接再把物理页映射到进程的虚拟内存里(如图)
优点:对于大文件的读写性能非常好,操作方便,可以在多个进程中共享
步骤:
CreateFile得到文件句柄
CreateFileMapping创建物理页并映射文件
MapViewOfFile将物理页映射到虚拟内存
【注意】 文件大小要修正的情况
如果要修改文件的大小,CreateFileMapping这个函数共有6个参数。它的第五个跟第六个函数告诉我们我们要映射文件的高32位的最大值,与低32位的最小值,如果两个都为零的话,它默认映射的大小就是原文件的大小,所以你在试图修改它的映射文件并把它映射回去的时候,会出现比原文件大的情况,这样就出现映射回去的时候只有原文件大小截断部分的问题了。所有,我们在映射文件到到内存之前,最好求出我们修改后的文件大小,给CreateFileMapping函数。
在参数dwMaximumSizeHigh和dwMaximumSizeLow中指定内存映射文件的大小,如果指定的值大于实际的文件,则实际的文件将增长到指定的大小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 HANDLE hFile = CreateFile ( TEXT ("C:\\Users\\Administrator\\Desktop\\test.txt" ), GENERIC_READ | GENERIC_WRITE, 0 , NULL , OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if (hFile==INVALID_HANDLE_VALUE){ cout<<"创建文件失败" <<endl; return 0 ; } HANDLE hMapFile = CreateFileMapping (hFile, NULL , PAGE_READWRITE, 0 , 0 , NULL ); if (hMapFile==NULL ){ cout<<"创建文件映射失败" <<endl; CloseHandle (hFile); return 0 ; } LPVOID lpAddr = MapViewOfFile (hMapFile, FILE_MAP_ALL_ACCESS, 0 , 0 , 0 ); if (lpAddr==NULL ){ cout<<"物理页映射到虚拟地址失败" <<endl; CloseHandle (hMapFile); CloseHandle (hFile); return 0 ; } char tmp = *(char *)lpAddr;cout << "读到的第一个字符是:" << tmp << endl; *(char *)lpAddr = 'a' ; FlushViewOfFile (lpAddr, 1 );cout<<"修改后的第一个字符为:" << *(char *)lpAddr <<endl; UnmapViewOfFile (lpAddr);CloseHandle (hMapFile);CloseHandle (hFile);
程序结果:
原本的文件:
修改后的文件:
FlushViewOfFile 【注意】 内存映射文件这种方式的写入,为了保证效率,所以其写入不是实时写入,而是释放资源的时候才会做收尾工作,即真正改写映射的文件(把物理页的值写回到文件里)。但可以通过下面的函数强制马上写入:
将文件映射指定范围的字节即刻写入硬盘(强制更新缓存)
1 2 3 4 5 6 BOOL FlushViewOfFile ( LPCVOID lpBaseAddress, SIZE_T dwNumberOfBytesToFlush ) ;
系统文件共享的注意点(重点理解)
如kernel32.dll,user32.dll和ntdll.dll等系统模块,他在内存中就是文件映射到进程中的。
Q: 那么如果你在进程A中修改了系统文件的物理页,那么进程B不就直接受到影响了吗。
但事实上是并不会,例如进程A在kernel32.dll的代码上下软件断点(实际上就是把一个字节改成了0xCC表示汇编int 3),当前A进程执行到这里会断下来,但进程B执行到那里却不会断下来
A: 如果在MapViewOfFile映射的时候指定的是写拷贝FILE_MAP_COPY 的话,就会产生上述效果,原理如下:
当进程A修改的时候,并不会对原映射的物理页做修改,而是拷贝一份修改处的最少一页的物理页。然后再修改新出现的物理页副本 。并且修改虚拟地址对物理页的映射关系(如上面右图)。最后映射结束的时候,对物理页的副本做的所有操作也并不会写回到文件中。
如上图,进程A下断点修改的CC实际上是修改到了上面标着CC的物理页副本,对原来的没影响
链接库 静态链接库 编写静态链接库文件
在vc6.0中创建新项目,项目类型:Win32 Static Library
在项目中创建xxxx.h和xxxx.cpp 编写代码,然后构建即可
在项目目录中会生成xxxx.lib文件
静态链接库的使用(两种方式)
将生成的.h和.lib文件复制到项目根目录,然后再代码中引用:
1 2 #include "xxxx.h" #pragma comment(lib,"xxxx.lib" )
将xxxx.h与xxxx.lib文件复制到vc6安装目录,与库文件放在一起。然后在工程->设置->连接->对象/库模块中添加xxxx.lib
静态链接库的缺点:
使用静态链接生成的可执行文件体积较大
包含相同的公共代码,造成浪费
静态库会和所写代码编到一个模块
动态链接库 动态链接库(Dynamic Link Library,缩写为DLL),是微软公司在微软Windows操作系统中,实现共享函数库概念的一种方式。
这些库函数的拓展名是”.dll”,”.ocx”(包含ActiveX控制的库)
DLL的入口点 动态链接库中的可选入口点 (DLL) 。 当系统启动或终止进程或线程时,它将使用进程的第一个线程为每个加载的 DLL 调用入口点函数。 使用 LoadLibrary 和 FreeLibrary 函数加载或卸载 DLL 时,系统还会为其调用入口点函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 BOOL WINAPI DllMain ( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved ) { switch ( fdwReason ) { case DLL_PROCESS_ATTACH: break ; case DLL_THREAD_ATTACH: break ; case DLL_THREAD_DETACH: break ; case DLL_PROCESS_DETACH: break ; } return TRUE; }
创建动态链接库(两种方法)
函数声明用以下方式声明表明该函数要导出
1 extern "C" _declspec(dllexport) 调用约定 返回类型 函数名 (参数列表);
使用.def文件:
1 2 3 EXPORTS 函数名 @编号 函数名 @编号 NONAME
使用序号导出的好处:
名字是一段程序最精华的注释,通过名字可以直接猜测到函数的功能,通过使用序号,可以达到隐藏的目的
使用动态链接库(两种方法)
显示链接
隐式链接
显示链接 显示链接只需要用到一个DLL
步骤如下:
定义函数指针,如
1 2 typedef int (__stdcall *lpPlus) (int ,int ) ;typedef int (__stdcall *lpSub) (int ,int ) ;
声明函数指针变量,如:
1 2 lpPlus myPlus; lpSub mySub;
动态加载DLL到内存中,如:
1 HINSTANCE hModule = LoadLibrary ("DllDemo.dll" );
获取函数地址,如:
1 2 myPlus=(lpPlus)GetProcAddress (hModule,"Plus" ); mySub=(lpSub)GetProcAddress (hModule,(char *)0x10 );
调用函数,如:
1 2 myPlus (10 ,2 );mySub (10 ,2 );
释放动态链接库,如:
隐式链接 隐式链接需要用到DLL和LIB文件
静态链接库的LIB文件中包含了所有代码;而动态链接库的LIB文件中只包含辅助信息,真正的代码在DLL中
隐式链接步骤如下:
将*.dll和*.lib放到工程目录下面
将#pragma comment(lib,”DLL名.lib”)添加到调用文件中。
加入函数的声明
1 2 __declspec(dllimport) __stdcall int Plus (int x,int y) ; __declspec(dllimport) __stdcall int Sub (int x,int y) ;
之后就可以正常使用该函数了
【注意】
如果导出的时候,是下面这样导出的
1 extern "C" _declspec(dllexport) 调用约定 返回类型 函数名 (参数列表);
则上述第三步的位置也要对应写成
1 2 extern "C" __declspec(dllimport) __stdcall int Plus (int x,int y) ;extern "C" __declspec(dllimport) __stdcall int Sub (int x,int y) ;
隐式链接和显示链接的区别 本质上没有什么区别,只是显示链接是我们自己调用LoadLibrary等函数,而隐式链接是操作系统帮我们调用。
静态库和动态库在汇编的不同 1 2 3 4 5 //静态库函数的call 0x4101234直接是函数首地址,直接调用 call 0x4101234 //动态库函数的call 0x0042a190内存中存的值才是函数首地址,是个间接调用 //程序真正运行的时候,才会把对应的函数首地址填入0x0042a190内存的位置 call dword ptr:[0x0042a190]
远程线程 CreateRemoteThread 给别的进程中创建线程
1 2 3 4 5 6 7 8 9 10 11 HANDLE CreateRemoteThread ( HANDLE hProcess, LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId ) ;
远线程注入 Q:什么是注入?
A:所谓注入就是在第三方进程不知道或者不允许的情况下将模块或者代码写入对方进程空间,并设法执行的技术。
在安全领域,“注入”是非常重要的一种技术手段,注入与反注入也一直处于不断变化的,而且正处于愈来愈激烈的对抗当中
已知的注入方式:
远程线程注入
APC注入
消息钩子注入
注册表注入
导入表注入
输入法注入
等等
远程线程注入的流程
在进程A中分配空间,存储DLL的路径
获取LoadLibrary函数的地址
创建远程线程,将线程函数指向为LoadLibrary
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 BOOL WinTool::remoteThreadInject (LPTSTR szProcessname, LPTSTR szDllName) { HANDLE hProcess = OpenProcess (PROCESS_ALL_ACCESS, NULL , getProcessIdByName (szProcessname)); if (!hProcess|| hProcess == INVALID_HANDLE_VALUE) { outDebugString ("WinTool OpenProcess ERROR\n" ); return FALSE; } int dwStrLength=(lstrlen (szDllName)+1 ) * sizeof (TCHAR); LPVOID dwDllStrAddress= VirtualAllocEx (hProcess, NULL , dwStrLength, MEM_COMMIT, PAGE_READWRITE); if (!dwDllStrAddress) { outDebugString ("WinTool VirtualAllocEx ERROR\n" ); CloseHandle (hProcess); return FALSE; } BOOL bRet = WriteProcessMemory (hProcess, dwDllStrAddress, szDllName, dwStrLength, NULL ); if (!bRet) { outDebugString ("WinTool WriteProcessMemory ERROR\n" ); VirtualFreeEx (hProcess, dwDllStrAddress, 0 , MEM_RELEASE); CloseHandle (hProcess); return FALSE; } HMODULE hModule = GetModuleHandle (TEXT ("kernel32.dll" )); if (!hModule) { outDebugString ("WinTool GetModuleHandle ERROR\n" ); VirtualFreeEx (hProcess, dwDllStrAddress, 0 , MEM_RELEASE); CloseHandle (hProcess); return FALSE; } #ifdef UNICODE DWORD dwLoadLibraryAddress = (DWORD)GetProcAddress (hModule, "LoadLibraryW" ); #else DWORD dwLoadLibraryAddress = (DWORD)GetProcAddress (hModule, "LoadLibraryA" ); #endif if (!dwLoadLibraryAddress) { outDebugString ("WinTool GetProcAddress ERROR\n" ); VirtualFreeEx (hProcess, dwDllStrAddress, 0 , MEM_RELEASE); CloseHandle (hProcess); CloseHandle (hModule); return FALSE; } HANDLE hThread = CreateRemoteThread (hProcess, NULL , 0 , (LPTHREAD_START_ROUTINE)dwLoadLibraryAddress, dwDllStrAddress, 0 , NULL ); if (!hThread|| hThread == INVALID_HANDLE_VALUE) { outDebugString ("WinTool CreateRemoteThread ERROR\n" ); VirtualFreeEx (hProcess, dwDllStrAddress, 0 , MEM_RELEASE); CloseHandle (hProcess); CloseHandle (hModule); return FALSE; } VirtualFreeEx (hProcess, dwDllStrAddress, 0 , MEM_RELEASE); CloseHandle (hThread); CloseHandle (hProcess); CloseHandle (hModule); return TRUE; }
进程间通信 进程间通信有很多种手段
管道
消息队列
信号量
共享内存
等等
看似很多,但本质上都是共享内存
注入游戏的DLL带窗口是没有实用价值的,因为特征太明显了。
三环遍历进程代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 PROCESSENTRY32 pe32; pe32.dwSize = sizeof (pe32); HANDLE hSnapshot_proc = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0 ); if (hSnapshot_proc != INVALID_HANDLE_VALUE) { BOOL check = Process32First(hSnapshot_proc, &pe32); while (check) { myOutPutDebug("进程PID = %d 进程名 = %s\n" , pe32.th32ProcessID, pe32.szExeFile); check = Process32Next(hSnapshot_proc, &pe32); } } CloseHandle(hSnapshot_proc);
模块隐藏 直接注入的DLL是很容易被目标程序检测到的。
所以需要模块隐藏
TEB和PEB都是三环结构体。
模块隐藏之断链 windows的三环API查的就是这些数据块,TEB和PEB
就是注入后,模块已经在进程内存空间中了,在PEB中的Ldr指向的模块双向链表中把注入的模块给断链。用户层API就无法找到你的模块了
TEB 《1》TEB(Thread Environment Block),它记录的相关线程的信息,每个线程都有自己的TEB,FS:[0]即是当前线程 的TEB。 $$ mov\ \ eax,fs:[0] $$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 typedef struct _TEB { PVOID Reserved1[12 ]; PPEB ProcessEnvironmentBlock; PVOID Reserved2[399 ]; BYTE Reserved3[1952 ]; PVOID TlsSlots[64 ]; BYTE Reserved4[8 ]; PVOID Reserved5[26 ]; PVOID ReservedForOle; PVOID Reserved6[4 ]; PVOID TlsExpansionSlots; } TEB, *PTEB; kd> dt _teb nt!_TEB +0x000 NtTib : _NT_TIB +0x01c EnvironmentPointer : Ptr32 Void +0x020 ClientId : _CLIENT_ID +0x028 ActiveRpcHandle : Ptr32 Void +0x02c ThreadLocalStoragePointer : Ptr32 Void +0x030 ProcessEnvironmentBlock : Ptr32 _PEB +0x034 LastErrorValue : Uint4B +0x038 CountOfOwnedCriticalSections : Uint4B +0x03c CsrClientThread : Ptr32 Void +0x040 Win32ThreadInfo : Ptr32 Void +0x044 User32Reserved : [26 ] Uint4B +0x0ac UserReserved : [5 ] Uint4B +0x0c0 WOW32Reserved : Ptr32 Void +0x0c4 CurrentLocale : Uint4B +0x0c8 FpSoftwareStatusRegister : Uint4B +0x0cc SystemReserved1 : [54 ] Ptr32 Void +0x1a4 ExceptionCode : Int4B +0x1a8 ActivationContextStack : _ACTIVATION_CONTEXT_STACK +0x1bc SpareBytes1 : [24 ] UChar +0x1d4 GdiTebBatch : _GDI_TEB_BATCH +0x6b4 RealClientId : _CLIENT_ID +0x6bc GdiCachedProcessHandle : Ptr32 Void +0x6c0 GdiClientPID : Uint4B +0x6c4 GdiClientTID : Uint4B +0x6c8 GdiThreadLocalInfo : Ptr32 Void +0x6cc Win32ClientInfo : [62 ] Uint4B +0x7c4 glDispatchTable : [233 ] Ptr32 Void +0xb68 glReserved1 : [29 ] Uint4B +0xbdc glReserved2 : Ptr32 Void +0xbe0 glSectionInfo : Ptr32 Void +0xbe4 glSection : Ptr32 Void +0xbe8 glTable : Ptr32 Void +0xbec glCurrentRC : Ptr32 Void +0xbf0 glContext : Ptr32 Void +0xbf4 LastStatusValue : Uint4B +0xbf8 StaticUnicodeString : _UNICODE_STRING +0xc00 StaticUnicodeBuffer : [261 ] Uint2B +0xe0c DeallocationStack : Ptr32 Void +0xe10 TlsSlots : [64 ] Ptr32 Void +0xf10 TlsLinks : _LIST_ENTRY +0xf18 Vdm : Ptr32 Void +0xf1c ReservedForNtRpc : Ptr32 Void +0xf20 DbgSsReserved : [2 ] Ptr32 Void +0xf28 HardErrorsAreDisabled : Uint4B +0xf2c Instrumentation : [16 ] Ptr32 Void +0xf6c WinSockData : Ptr32 Void +0xf70 GdiBatchCount : Uint4B +0xf74 InDbgPrint : UChar +0xf75 FreeStackOnTermination : UChar +0xf76 HasFiberData : UChar +0xf77 IdealProcessor : UChar +0xf78 Spare3 : Uint4B +0xf7c ReservedForPerf : Ptr32 Void +0xf80 ReservedForOle : Ptr32 Void +0xf84 WaitingOnLoaderLock : Uint4B +0xf88 Wx86Thread : _Wx86ThreadState +0xf94 TlsExpansionSlots : Ptr32 Ptr32 Void +0xf98 ImpersonationLocale : Uint4B +0xf9c IsImpersonating : Uint4B +0xfa0 NlsCache : Ptr32 Void +0xfa4 pShimData : Ptr32 Void +0xfa8 HeapVirtualAffinity : Uint4B +0xfac CurrentTransactionHandle : Ptr32 Void +0xfb0 ActiveFrame : Ptr32 _TEB_ACTIVE_FRAME +0xfb4 SafeThunkCall : UChar +0xfb5 BooleanSpare : [3 ] UChar
1 2 3 4 5 6 7 8 9 10 11 12 13 FS:[000 ] 指向SEH链指针 FS:[004 ] 线程堆栈顶部 FS:[00 8] 线程堆栈底部 FS:[00 C] SubSystemTib FS:[010 ] FiberData FS:[014 ] ArbitraryUserPointer FS:[01 8] 指向TEB自身 FS:[020 ] 进程PID FS:[024 ] 线程ID FS:[02 C] 指向线程局部存储指针 FS:[030 ] PEB结构地址(进程结构) FS:[034 ] 上个错误号
NtTib成员 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 typedef struct _NT_TIB { struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList; PVOID StackBase; PVOID StackLimit; PVOID SubSystemTib; union { PVOID FiberData; DWORD Version; }; PVOID ArbitraryUserPointer; struct _NT_TIB *Self; } NT_TIB; typedef NT_TIB *PNT_TIB;ntdll!_NT_TIB +0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD +0x004 StackBase : Ptr32 Void +0x008 StackLimit : Ptr32 Void +0x00c SubSystemTib : Ptr32 Void +0x010 FiberData : Ptr32 Void +0x010 Version : Uint4B +0x014 ArbitraryUserPointer : Ptr32 Void +0x018 Self : Ptr32 _NT_TIB
_CLIENT_ID成员
1 2 3 4 kd> dt _CLIENT_ID nt! CLIENT_ID +0x000 UniqueProcess : Ptr32 Void +0x004 UniqueThread : Ptr32 Void
32位系统FS三环时指向TEB,零环时用KPCR,而64位系统三环时用GS指向TEB ,零环时GS指向KPCR(之前32位windows下根本没用过GS),也就是说,在64位下不再用FS指向KPCR和TEB,64位下用GS取代了FS,FS则继续留给32位程序继续用
PEB 《2》PEB(Process Environment Block,进程环境块)存放进程信息,每个进程都有自己的PEB信息,TEB偏移0x30的位置就是当前进程的PEB $$ mov\ \ eax,fs:[0x30] $$ 不同的windows版本中TEB结构会不同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 typedef struct _PEB { 000 h UCHAR InheritedAddressSpace; 001 h UCHAR ReadImageFileExecOptions; 002 h UCHAR BeingDebugged; 003 h UCHAR SpareBool; 004 h HANDLE Mutant; 00 8h HINSTANCE ImageBaseAddress; 00 Ch struct _PEB_LDR_DATA *Ldr 010 h struct _RTL_USER_PROCESS_PARAMETERS *ProcessParameters; 014 h ULONG SubSystemData; 01 8h HANDLE DefaultHeap; 01 Ch KSPIN_LOCK FastPebLock; 020 h ULONG FastPebLockRoutine; 024 h ULONG FastPebUnlockRoutine; 02 8h ULONG EnvironmentUpdateCount; 02 Ch ULONG KernelCallbackTable; 030 h LARGE_INTEGER SystemReserved; 03 8h struct _PEB_FREE_BLOCK *FreeList 03 Ch ULONG TlsExpansionCounter; 040 h ULONG TlsBitmap; 044 h LARGE_INTEGER TlsBitmapBits; 04 Ch ULONG ReadOnlySharedMemoryBase; 050 h ULONG ReadOnlySharedMemoryHeap; 054 h ULONG ReadOnlyStaticServerData; 05 8h ULONG AnsiCodePageData; 05 Ch ULONG OemCodePageData; 060 h ULONG UnicodeCaseTableData; 064 h ULONG NumberOfProcessors; 06 8h LARGE_INTEGER NtGlobalFlag; 070 h LARGE_INTEGER CriticalSectionTimeout; 07 8h ULONG HeapSegmentReserve; 07 Ch ULONG HeapSegmentCommit; 0 80h ULONG HeapDeCommitTotalFreeThreshold; 0 84h ULONG HeapDeCommitFreeBlockThreshold; 0 88h ULONG NumberOfHeaps; 0 8Ch ULONG MaximumNumberOfHeaps; 0 90h ULONG ProcessHeaps; 0 94h ULONG GdiSharedHandleTable; 0 98h ULONG ProcessStarterHelper; 0 9Ch ULONG GdiDCAttributeList; 0 A0h KSPIN_LOCK LoaderLock; 0 A4h ULONG OSMajorVersion; 0 A8h ULONG OSMinorVersion; 0 ACh USHORT OSBuildNumber; 0 AEh USHORT OSCSDVersion; 0B0 h ULONG OSPlatformId; 0 B4h ULONG ImageSubsystem; 0 B8h ULONG ImageSubsystemMajorVersion; 0 BCh ULONG ImageSubsystemMinorVersion; 0 C0h ULONG ImageProcessAffinityMask; 0 C4h ULONG GdiHandleBuffer[0x22 ]; 14 Ch ULONG PostProcessInitRoutine; 150 h ULONG TlsExpansionBitmap; 154 h UCHAR TlsExpansionBitmapBits[0x80 ]; 1 D4h ULONG SessionId; } PEB, *PPEB;
_PEB_LDR_DATA PEB loader data 进程环境块装载机信息
1 2 3 4 5 6 7 8 9 typedef struct _PEB_LDR_DATA { ULONG Length; BOOLEAN Initialized; PVOID SsHandle; LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; }PEB_LDR_DATA,*PPEB_LDR_DATA;
LIST_ENTRY 1 2 3 4 5 6 7 8 nt!_LIST_ENTRY +0x000 Flink : Ptr32 _LIST_ENTRY +0x004 Blink : Ptr32 _LIST_ENTRY typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; struct _LIST_ENTRY *Blink; } LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;
链表中保存的是_LDR_DATA_TABLE_ENTRY结构体的信息,给结构体如下。
_LDR_DATA_TABLE_ENTRY 该结构未导出,需要自己定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 typedef struct _LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderLinks; LIST_ENTRY InInitializationOrderLinks; PVOID DllBase; PVOID EntryPoint; ULONG32 SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; UINT32 Unknow[17 ]; }LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; typedef struct _LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderLinks; LIST_ENTRY InInitializationOrderLinks; PVOID DllBase; PVOID EntryPoint; ULONG32 SizeOfImage; UINT8 Unknow0[0x4 ]; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; }LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; @Windows XP Professional Service Pack 3 (x86) (5.1 , Build 2600 ) lkd> dt -b _LDR_DATA_TABLE_ENTRY nt!_LDR_DATA_TABLE_ENTRY +0x000 InLoadOrderLinks : _LIST_ENTRY +0x000 Flink : Ptr32 +0x004 Blink : Ptr32 +0x008 InMemoryOrderLinks : _LIST_ENTRY +0x000 Flink : Ptr32 +0x004 Blink : Ptr32 +0x010 InInitializationOrderLinks : _LIST_ENTRY +0x000 Flink : Ptr32 +0x004 Blink : Ptr32 +0x018 DllBase : Ptr32 +0x01c EntryPoint : Ptr32 +0x020 SizeOfImage : Uint4B +0x024 FullDllName : _UNICODE_STRING +0x000 Length : Uint2B +0x002 MaximumLength : Uint2B +0x004 Buffer : Ptr32 +0x02c BaseDllName : _UNICODE_STRING +0x000 Length : Uint2B +0x002 MaximumLength : Uint2B +0x004 Buffer : Ptr32 +0x034 Flags : Uint4B +0x038 LoadCount : Uint2B +0x03a TlsIndex : Uint2B +0x03c HashLinks : _LIST_ENTRY +0x000 Flink : Ptr32 +0x004 Blink : Ptr32 +0x03c SectionPointer : Ptr32 +0x040 CheckSum : Uint4B +0x044 TimeDateStamp : Uint4B +0x044 LoadedImports : Ptr32 +0x048 EntryPointActivationContext : Ptr32 +0x04c PatchInformation : Ptr32 @Windows 7 Ultimate (x64) (6.1 , Build 7600 ) lkd> dt -b _LDR_DATA_TABLE_ENTRY nt!_LDR_DATA_TABLE_ENTRY +0x000 InLoadOrderLinks : _LIST_ENTRY +0x000 Flink : Ptr64 +0x008 Blink : Ptr64 +0x010 InMemoryOrderLinks : _LIST_ENTRY +0x000 Flink : Ptr64 +0x008 Blink : Ptr64 +0x020 InInitializationOrderLinks : _LIST_ENTRY +0x000 Flink : Ptr64 +0x008 Blink : Ptr64 +0x030 DllBase : Ptr64 +0x038 EntryPoint : Ptr64 +0x040 SizeOfImage : Uint4B +0x048 FullDllName : _UNICODE_STRING +0x000 Length : Uint2B +0x002 MaximumLength : Uint2B +0x008 Buffer : Ptr64 +0x058 BaseDllName : _UNICODE_STRING +0x000 Length : Uint2B +0x002 MaximumLength : Uint2B +0x008 Buffer : Ptr64 +0x068 Flags : Uint4B +0x06c LoadCount : Uint2B +0x06e TlsIndex : Uint2B +0x070 HashLinks : _LIST_ENTRY +0x000 Flink : Ptr64 +0x008 Blink : Ptr64 +0x070 SectionPointer : Ptr64 +0x078 CheckSum : Uint4B +0x080 TimeDateStamp : Uint4B +0x080 LoadedImports : Ptr64 +0x088 EntryPointActivationContext : Ptr64 +0x090 PatchInformation : Ptr64 +0x098 ForwarderLinks : _LIST_ENTRY +0x000 Flink : Ptr64 +0x008 Blink : Ptr64 +0x0a8 ServiceTagLinks : _LIST_ENTRY +0x000 Flink : Ptr64 +0x008 Blink : Ptr64 +0x0b8 StaticLinks : _LIST_ENTRY +0x000 Flink : Ptr64 +0x008 Blink : Ptr64 +0x0c8 ContextInformation : Ptr64 +0x0d0 OriginalBase : Uint8B +0x0d8 LoadTime : _LARGE_INTEGER +0x000 LowPart : Uint4B +0x004 HighPart : Int4B +0x000 u : <unnamed-tag> +0x000 LowPart : Uint4B +0x004 HighPart : Int4B +0x000 QuadPart : Int8B
每个加载到进程中的DLL模块都对应一个_LDR_DATA_TABLE_ENTRY结构体,这些结构体相互链接,最终形成了_LIST_ENTRY双向链表。_PEB_LDR_DATA结构体中存在3种_LIST_ENTRY双向链表,也就是说,存在多个_LDR_DATA_TABLE_ENTRY结构体,并且有三种链接方法可以将它们链接起来。
结构示意图:
上面的图实际上都不是那么准确,下面这张图才是真正的结构。
_UNICODE_STRING的定义 1 2 3 4 5 6 typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; }UNICODE_STRING,*PUNICOID_STRING;
DefaultHeap与NtGlobalFlag反调试相关(只对XP有效) EB.ProcessHeap与PEB.NtGlobalFlag成员(像PEB.BeingDebugged成员一样)应用于反调试技术。若进程处于调试状态,则ProcessHeap与NtGlobalFlag成员就持有特定值。由于它们具有这一个特征,所以常常应用于反调试技术。 PEB.ProcessHeap成员是指向HEAP结构体的指针,HEAP结构体如下。
1 2 3 4 5 6 7 8 9 +0 ×000 Entry :_HEAP_ENTRY+0 ×00 8 Signature :Uint4B+0 ×00 c Flags :Uint4B+0 ×010 ForceFlags :Uint4B+0 ×014 VirtualMemoryThreshold :Uint4B+0 ×01 8 SegmentReserve :Uint4B+0 ×01 c SegmentCommit :Uint4B+0 ×020 DeCommitFreeBlockThreshold :Uint4B...
进程处于被调试状态时,Flags(+0xC)与Force Flags(+ox10)成员被设置成特定的值。 PEB.ProcessHeap(PEB结构体中偏移0x18的位置)成员既可以从PEB结构体中直接获得,也可以通过GetProcessHeap() API获得。
当进程运行正常时Heap.Flagsh成员的值为0x2,Heap. ForceFlags成员的值位0x0,进程处于被调试状态时这些值也会随之改变
破解之法 只要将HEAP.Flags与HEAP.ForceFlags的值重新设置为2与0即可(HEAP.Flags=2,HEAP.ForceFlags=0)。 注意:该方法仅在WindowsXP系统中有效,Windows7系统不存在以上特征。此外,将运行中的进程附加到调试器时,也不会出现上述特征。
实现代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 void WinTool::hideModuleByCutLink32 (LPTSTR szModuleName) { _PEB_LDR_DATA* ldr; _LIST_ENTRY* head, *cur; LDR_DATA_TABLE_ENTRY* ldm; HMODULE hMod = GetModuleHandle (szModuleName); __asm { mov eax,fs:[0x30 ] mov eax,[eax+0x0C ] mov ldr,eax } head = &(ldr->InLoadOrderModuleList); cur = head->Flink; do { ldm = CONTAINING_RECORD (cur, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks); if (hMod == ldm->DllBase) { ldm->InLoadOrderLinks.Blink->Flink = ldm->InLoadOrderLinks.Flink; ldm->InLoadOrderLinks.Flink->Blink = ldm->InLoadOrderLinks.Blink; ldm->InInitializationOrderLinks.Blink->Flink = ldm->InInitializationOrderLinks.Flink; ldm->InInitializationOrderLinks.Flink->Blink = ldm->InInitializationOrderLinks.Blink; ldm->InMemoryOrderLinks.Blink->Flink = ldm->InMemoryOrderLinks.Flink; ldm->InMemoryOrderLinks.Flink->Blink = ldm->InMemoryOrderLinks.Blink; } cur = cur->Flink; } while (cur!=head); }
1 2 3 4 5 6 7 8 9 WinTool myTool; myTool.showModuleFromPIDByAPI32 (GetCurrentProcessId ()); cout<<"按任意键隐藏kernel32.dll模块" <<endl; getchar ();myTool.hideModuleByCutLink32 (TEXT ("kernel32.dll" )); cout<<"隐藏成功" <<endl; myTool.showModuleFromPIDByAPI32 (GetCurrentProcessId ()); getchar ();
模块隐藏之VAD树 PEB中,断链只可以让API访问不到该module
但是VAD树是内核层访问的,依然可以在里面找到断链后的module
即使VAD树也隐藏了自己想隐藏的模块,但是依然可以遍历内存搜索PE指纹。
试图模块彻底隐藏 通杀99%的隐藏模块的方法有三步:
PEB断链指定模块信息
VAD树去除指定模块信息
找到内存中指定模块的位置,修改PE指纹(不会影响程序运行)
上述操作相对简单的操作:先注入dll,再复制一份dll,再通过代码释放原来的dll,然后在原位置申请内存,再把复制的dll拷贝回去(就不需要重定位了)。再修改特征码,这样VAD树和PEB中就没有记录了
hook LoadLibrary三环和零环对应函数来检测是否有DLL被加载
最好的隐藏:无模块注入,也就是代码注入 ,连模块都没有。
代码注入的弊端:工作量特别大
代码注入 避免了所有模块的特征。
远线程执行CreateFileA系统函数的案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 struct CreateFileParam { LPVOID CreateFileAddress; char fileName[9 ]; DWORD dwDesiredAccess; DWORD dwShareMode; LPSECURITY_ATTRIBUTES lpSecurityAttributes; DWORD dwCreationDisposition; DWORD dwFlagsAndAttributes; HANDLE hTemplateFile; }; void myThreadCreateFileFunc (LPVOID pParam) { typedef HANDLE (*PCreateFile) (LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) ; CreateFileParam* tmpStruct = (CreateFileParam*)pParam; PCreateFile createFileFunc = (PCreateFile)(tmpStruct->CreateFileAddress); (*createFileFunc)(tmpStruct->fileName, tmpStruct->dwDesiredAccess, tmpStruct->dwShareMode, tmpStruct->lpSecurityAttributes,tmpStruct->dwCreationDisposition,tmpStruct->dwFlagsAndAttributes,tmpStruct->hTemplateFile); } int main () { WinTool myTool; DWORD pid = myTool.getProcessIdByName (TEXT ("target.exe" )); CreateFileParam param; param.hTemplateFile = 0 ; memcpy (param.fileName, "C:\\A.txt" , strlen ("C:\\A.txt" )+1 ); param.dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL; param.dwCreationDisposition = CREATE_NEW; param.dwShareMode = NULL ; param.dwDesiredAccess = GENERIC_READ | GENERIC_WRITE; HMODULE hModule=GetModuleHandleA ("kernel32.dll" ); LPVOID CreateFileAddress =GetProcAddress (hModule, "CreateFileA" ); param.CreateFileAddress = CreateFileAddress; param.lpSecurityAttributes = NULL ; LPVOID targetParam=myTool.remoteInjectMemory (pid, ¶m, sizeof (CreateFileParam)); DWORD realThreadFunc = (DWORD)myThreadCreateFileFunc; if (*(BYTE*)realThreadFunc ==0xE9 ) { realThreadFunc = realThreadFunc + 5 + *(DWORD*)(realThreadFunc + 1 ); } int threadFuncSize = 0x55 ; LPVOID targetThreadFunc = myTool.remoteInjectMemory (pid, (PVOID)realThreadFunc, threadFuncSize); HANDLE hProcess=OpenProcess (PROCESS_ALL_ACCESS,NULL , pid); HANDLE hThread= CreateRemoteThread (hProcess, NULL , 0 , (LPTHREAD_START_ROUTINE)targetThreadFunc, targetParam, 0 , NULL ); WaitForSingleObject (hThread, INFINITE); cout<<"执行结束" <<endl; CloseHandle (hProcess); return EXIT_SUCCESS; }
【注意】上面代码要把项目属性中的代码生成中的基本运行时检查选项设为默认值
将函数转换成shellCode的时候要注意:
上面的代码也可以实现代码注入,但是非常的繁琐 ,因此可以直接注入ShellCode
注入ShellCode 什么是ShellCode?
ShellCode:不依赖环境,放到任何地方都可以执行的机器码 。
ShellCode的编写原则 :
不能有全局变量
不能使用常量字符串
不能使用[[系统调用]](因为系统调用是间接调用,call的地址要等到exe启动的时候由操作系统根据导入表填写的)
不能嵌套调用其他函数(把整个模块按照ShellCode写过去 ,暂时还做不到)
Q:为什么不能使用系统调用
A:导入表要目标进程本身就使用了这个[[系统调用]],而且目标进程的该系统调用间接存放点也要这么巧刚好是这个地址,才能使用系统调用。。。(概率几乎为0)
解决上述痛点的方式:
不写就解决了
用局部数组写字符串
因为调用其他PE的函数都是采用间接CALL的调用方式,实际上是由编译器填写的。系统调用都是在其他DLL中的,所以ShellCode中不能使用系统调用。
类似kernel32.dll中的函数,在同一个操作系统中,其在各个进程中的函数地址是一样的。可以现在别的进程找好要用的函数地址,把函数地址送进去目标进程。
shellCode通过PEB找到kernel32.dll的模块句柄,然后通过[[PE#导出表|PE中的导出表]]找到GetProcAddress函数,就可以确定GetProcAddress在目标进程中的真实函数地址。到这一步就可以配合PEB中的所有模块句柄遍历找到所有函数。
shellCode案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 char szKernel32[] = { 'K' ,'\0' ,'E' ,'\0' ,'R' ,'\0' ,'N' ,'\0' ,'E' ,'\0' ,'L' ,'\0' ,'3' ,'\0' ,'2' ,'\0' ,'.' ,'\0' ,'D' ,'\0' ,'L' ,'\0' ,'L' ,'\0' ,0 ,0 }; char szUser32[] = { 'U' ,'S' ,'E' ,'R' ,'3' ,'2' ,'.' ,'d' ,'l' ,'l' ,0 }; char szGetProcAddr[] = { 'G' ,'e' ,'t' ,'P' ,'r' ,'o' ,'c' ,'A' ,'d' ,'d' ,'r' ,'e' ,'s' ,'s' ,0 }; char szMessageBoxA[] = { 'M' ,'e' ,'s' ,'s' ,'a' ,'g' ,'e' ,'B' ,'o' ,'x' ,'A' ,0 }; char szLoadLibraryA[] = { 'L' ,'o' ,'a' ,'d' ,'L' ,'i' ,'b' ,'r' ,'a' ,'r' ,'y' ,'A' ,0 }; typedef FARPROC (*PGETPROCADDRESS) (HMODULE hModule, LPCSTR lpProcName) ; PGETPROCADDRESS pGetProcAddress; typedef HMODULE (*PLOADLIBRARYA) (LPCSTR lpLibFileName) ; PLOADLIBRARYA pLoadLibraryA; typedef int (*PMESSAGEBOXA) (HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) ; PMESSAGEBOXA pMessageBoxA; _LDR_DATA_TABLE_ENTRY* pBeg, *pPLD; DWORD dwKernelBase; __asm { mov eax, fs:[0x30 ] mov eax, [eax + 0x0c ] add eax, 0x0c mov pBeg, eax mov eax, [eax] mov pPLD, eax } while (pPLD != pBeg) { WORD* pLast = (WORD*)pPLD->BaseDllName.Buffer; WORD* pFirst = (WORD*)szKernel32; while (*pFirst&&*pFirst == *pLast) pFirst++, pLast++; if (*pFirst == *pLast) { dwKernelBase = (DWORD)pPLD->DllBase; break ; } pPLD = (_LDR_DATA_TABLE_ENTRY*)pPLD->InLoadOrderLinks.Flink; } IMAGE_DOS_HEADER *pIDH = (IMAGE_DOS_HEADER *)dwKernelBase; IMAGE_NT_HEADERS *pINGs = (IMAGE_NT_HEADERS *)((DWORD)dwKernelBase + pIDH->e_lfanew); IMAGE_EXPORT_DIRECTORY* pIED = (IMAGE_EXPORT_DIRECTORY*)((DWORD)dwKernelBase + pINGs->OptionalHeader.DataDirectory[0 ].VirtualAddress); DWORD *pAddOfFun_Raw = (DWORD*)((DWORD)dwKernelBase + pIED->AddressOfFunctions); WORD* pAddOfOrd_Raw = (WORD*)((DWORD)dwKernelBase + pIED->AddressOfNameOrdinals); DWORD* pAddOfNames_Raw = (DWORD*)((DWORD)dwKernelBase + pIED->AddressOfNames); DWORD dwCnt = 0 ; char * pFinded = NULL , *pSrc = szGetProcAddr; for (; dwCnt < pIED->NumberOfNames; dwCnt++) { pFinded = (char *)((DWORD)dwKernelBase + pAddOfNames_Raw[dwCnt]); while (*pFinded&&*pFinded == *pSrc) pFinded++, pSrc++; if (*pFinded == *pSrc) { pGetProcAddress = (PGETPROCADDRESS)((DWORD)dwKernelBase + pAddOfFun_Raw[pAddOfOrd_Raw[dwCnt]]); break ; } pSrc = szGetProcAddr; } pLoadLibraryA = (PLOADLIBRARYA)pGetProcAddress ((HMODULE)dwKernelBase, szLoadLibraryA); HMODULE hUser32 = pLoadLibraryA (szUser32); pMessageBoxA = (PMESSAGEBOXA)pGetProcAddress (hUser32, szMessageBoxA); pMessageBoxA (0 , 0 , 0 , 0 );
该案例的代码放到哪里都可以跑
什么是ShellCode?
ShellCode:不依赖环境,放到任何地方都可以执行的机器码 。
ShellCode的编写原则 :
不能有全局变量
不能使用常量字符串
不能使用[[系统调用]]
不能嵌套调用其他函数(把整个模块按照ShellCode写过去,暂时还做不到)
解决上述痛点的方式:
不写就解决了
用局部数组写字符串
因为调用其他PE的函数都是采用间接CALL的调用方式,实际上是由编译器填写的。系统调用都是在其他DLL中的,所以ShellCode中不能使用系统调用。
HOOK Virtual Tabel Hook
什么是HOOK HOOK是用来获取,更改程序执行时的某些数据,或者是用于更改程序执行流程的一种技术。
HOOK的两种主要形式
改函数代码
INLINE HOOK
改函数地址 (其实就是改表)
IAT HOOK(3环的表,只影响一个进程)
VT HOOK(虚表,C++的结构)
SSDT HOOK(这个以及下面3个表都是0环的表)
IDT HOOK
EAT HOOK
IRP HOOK
…
虚表HOOK(VT HOOK) 虚表hook是通过更改虚表
有虚函数就有虚表
实现原理如下图
没改前:
修改后:
虚表HOOK案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Base { public : virtual void Print () { cout<<"我是base" <<endl; } }; void myHookFunc () { cout<<"你被hook了" <<endl; } void main () { Base* base=new Base; cout<<sizeof (base)<<endl; DWORD vtAddress = *(DWORD*)base; DWORD oldFunAddress = *(DWORD*)vtAddress; DWORD oldProtect; VirtualProtect ((DWORD*)vtAddress, 4 , PAGE_READWRITE, &oldProtect); *(DWORD*)vtAddress = (DWORD)myHookFunc; base->Print (); }
缺点:只能HOOK虚表中的函数
IAT HOOK Import Address Table 由于导入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于一个或者多个DLL 中.当PE 文件被装入内存的时候,Windows 装载器才将DLL 装入,并将调用导入函数的指令和函数实际所处的地址联系起来(动态连接),这操作就需要导入表完成.其中导入地址表就指示函数 实际地址。
任何一个进程中都存在一张表,这张表会把当前进程用到的所有API都放到导入地址表中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 DWORD WinTool::setIATHook (DWORD dwOldAddress, DWORD dwNewAddress) { BOOL bFlag = FALSE; PDWORD pFuncAddr = NULL ; DWORD dwImageBase = (DWORD)GetModuleHandleA (NULL ); PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(dwImageBase + ((PIMAGE_DOS_HEADER)dwImageBase)->e_lfanew); IMAGE_OPTIONAL_HEADER32 optionHeader = (IMAGE_OPTIONAL_HEADER32)(pNtHeader->OptionalHeader); PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(dwImageBase + optionHeader.DataDirectory[1 ].VirtualAddress); while (pImportDescriptor->FirstThunk != 0 && bFlag == FALSE) { pFuncAddr = (PDWORD)(dwImageBase + pImportDescriptor->FirstThunk); while (*pFuncAddr) { if (dwOldAddress == *pFuncAddr) { DWORD dwOldProtect; VirtualProtect (pFuncAddr, sizeof (DWORD), PAGE_READWRITE, &dwOldProtect); *pFuncAddr = dwNewAddress; VirtualProtect (pFuncAddr, sizeof (DWORD), dwOldProtect, NULL ); bFlag = TRUE; break ; } pFuncAddr++; } pImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pImportDescriptor + sizeof (IMAGE_IMPORT_DESCRIPTOR)); } if (bFlag) { return dwOldAddress; } return NULL ; } DWORD g_oldAddress; int myMessageBoxA ( HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType ) { char myText[]= { 'z' ,'e' ,'r' ,'o' ,'k' ,'o' ,'1' ,'4' ,0 }; typedef int (*PMESSAGEBOXA) (HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) ; PMESSAGEBOXA pMessageBoxA = (PMESSAGEBOXA)g_oldAddress; return pMessageBoxA (hWnd, myText, lpCaption, uType); } void main () { WinTool myTool; cout << "未HOOK前调用MessageBoxA(0,0,0,0)函数" << endl; MessageBoxA (0 , 0 , 0 , 0 ); cout<<"执行IAT HOOK,HOOK MessageBoxA函数" <<endl; HMODULE hModule = GetModuleHandleA ("user32.dll" ); DWORD pMessageBoxAAddr=(DWORD)GetProcAddress (hModule, "MessageBoxA" ); g_oldAddress=myTool.setIATHook (pMessageBoxAAddr, (DWORD)myMessageBoxA); cout<<"按回车键调用IAT HOOK后的MessageBoxA(0,0,0,0)函数" <<endl; getchar (); MessageBoxA (0 , 0 , 0 , 0 ); cout<<"解除IAT HOOK" <<endl; myTool.setIATHook ((DWORD)myMessageBoxA, g_oldAddress); cout << "按回车键调用解除IAT HOOK后的MessageBoxA(0,0,0,0)函数" << endl; getchar (); MessageBoxA (0 , 0 , 0 , 0 ); }
显示结果:
IAT HOOK的缺点:
容易被检测到
只能HOOK IAT表里的函数(即非自身模块的函数 )
INLINE HOOK inline hook是最有价值的HOOK
手动HOOK的流程如下:
找到要HOOK的函数头部:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 119 : void add (int a, int b) 120: { 00702730 55 push ebp 00702731 8 B EC mov ebp,esp 00702733 83 EC 40 sub esp,40 h 00702736 53 push ebx 00702737 56 push esi 0070273 8 57 push edi 121 : cout<<a+b<<endl; 0070273 9 68 AF 10 70 00 push offset std::endl<char ,std::char_traits<char > > (07010 AFh) 0070273 E 8 B 45 0 8 mov eax,dword ptr [a] 00702741 03 45 0 C add eax,dword ptr [b] 00702744 50 push eax 00702745 8 B 0 D 7 C E1 70 00 mov ecx,dword ptr [_imp_?cout@std@@3 V?$basic_ostream@DU?$char_traits@D@std@@@1 @A (070E17 Ch)] 0070274 B FF 15 70 E1 70 00 call dword ptr [__imp_std::basic_ostream<char ,std::char_traits<char > >::operator << (070E170 h)] 00702751 8 B C8 mov ecx,eax 00702753 FF 15 6 C E1 70 00 call dword ptr [__imp_std::basic_ostream<char ,std::char_traits<char > >::operator << (070E16 Ch)] 122 : } 0070275 9 5 F pop edi 0070275 A 5 E pop esi 0070275 B 5 B pop ebx 0070275 C 8 B E5 mov esp,ebp 0070275 E 5 D pop ebp 0070275 F C3 ret
将函数头部的5个字节修改为,我们自己添加的函数假如在70E89D处
1 2 3 4 5 6 7 119 : void add (int a, int b) 120: { 00702730 E9 68 C10000 JMP 0070E89 D 00702735 90 nop00702736 53 push ebx 00702737 56 push esi 0070273 8 57 push edi
70E89D处添加自己要做的事情,并补齐前面修改的代码,再跳回原来修改位置的下一行代码地址00702736处
1 2 3 4 5 6 7 8 9 0070E89 D C74424 04 02000000 MOV DWORD [ESP+4 ],2 0070E8 A5 C74424 0 8 03000000 MOV DWORD [ESP+8 ],3 0070E8 AD 55 PUSH EBP0070E8 AE 8 B EC MOV EBP,ESP0070E8 B0 83 EC 40 SUP ESP,40 h0070E8 B3 E9 7E3 EFFFF JMP 00702736
Inline Hook案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 void add (int a, int b) { cout << a << "+" << b << "=" << a + b << endl; } void _declspec(naked) myInLineHookFunc (){ __asm { mov[esp + 4 ], 3 mov[esp + 8 ], 4 push ebp mov ebp, esp sub esp, 40 h jmp add+6 } } void main () { cout << "未HOOK之前调用add(1,2)函数,结果为:" << endl; add (1 , 2 ); DWORD oldProtect; WinTool myTool; VirtualProtect ((LPVOID)add, 5 , PAGE_EXECUTE_READWRITE, &oldProtect); *(BYTE*)add = 0xE9 ; *(DWORD*)(((BYTE*)add) + 1 ) = (DWORD)myInLineHookFunc - 5 - (DWORD)add; VirtualProtect ((LPVOID)add, 5 , oldProtect, NULL ); cout << "Inline HOOK之后调用add(1,2)函数,结果为:" << endl; add (1 , 2 ); }
发现此处不需要修正函数地址,add函数名称直接是add的真实地址。目测是VS2015有自动修正。
上面代码的VirtualProtect中PAGE_EXECUTE_READWRITE改为PAGE_READWRITE会报错:
裸函数不可以有局部变量!
Inline HOOK注入dll版 写在DLL中:
InlineHook.h 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #pragma once #include <windows.h> class InlineHook { public : InlineHook (); ~InlineHook (); BOOL SetHook (LPSTR moduleName, LPSTR funcName, PROC hookFunc) ; void UnHook () ; BOOL ReHook () ; private : char oldMem[5 ] ; char newMem[5 ] ; PROC funcAddress=NULL ; };
InlineHook.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 #include "InlineHook.h" InlineHook::InlineHook () { memset (newMem, 0 , 5 ); memset (oldMem, 0 , 5 ); } InlineHook::~InlineHook () { UnHook (); memset (newMem, 0 , 5 ); memset (oldMem, 0 , 5 ); funcAddress = NULL ; } BOOL InlineHook::SetHook (LPSTR moduleName, LPSTR funcName,PROC hookFunc) { funcAddress=GetProcAddress (GetModuleHandleA (moduleName), funcName); if (!funcAddress) { return false ; } DWORD retHaveRead; ReadProcessMemory (GetCurrentProcess (), funcAddress, &oldMem, 5 , &retHaveRead); if (retHaveRead!=5 ) { return false ; } newMem[0 ] = 0xE9 ; *(DWORD*)(&newMem[1 ]) = (DWORD)hookFunc- (DWORD)funcAddress-5 ; DWORD retHaveWritten; WriteProcessMemory (GetCurrentProcess (), funcAddress, &newMem, 5 , &retHaveWritten); if (retHaveWritten != 5 ) { return false ; } return true ; } void InlineHook::UnHook () { if (funcAddress!=NULL ) { WriteProcessMemory (GetCurrentProcess (), funcAddress, &oldMem, 5 , NULL ); } } BOOL InlineHook::ReHook () { if (funcAddress != NULL ) { WriteProcessMemory (GetCurrentProcess (), funcAddress, &newMem, 5 , NULL ); return TRUE; } return FALSE; }
dllmain.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include "stdafx.h" #include "InlineHook.h" InlineHook myHook; int WINAPI MyMessageBoxA ( _In_opt_ HWND hWnd, _In_opt_ LPCSTR lpText, _In_opt_ LPCSTR lpCaption, _In_ UINT uType) { myHook.UnHook (); MessageBoxA (hWnd, "HOOK成功" , "提示" , MB_OK); myHook.ReHook (); return 1 ; } BOOL APIENTRY DllMain ( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: myHook.SetHook ("User32.dll" , "MessageBoxA" , (PROC)MyMessageBoxA); break ; case DLL_THREAD_ATTACH: break ; case DLL_THREAD_DETACH: break ; case DLL_PROCESS_DETACH: myHook.UnHook (); break ; } return TRUE; }
未HOOK前:
HOOK后:
Inline HOOK改进版 裸函数不会帮你生成任何汇编代码,所以不可以使用局部变量。
改进版Inline HOOK
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 #define PATCH_LENGTH 6 DWORD g_realAddTargetAddr; void addTarget (int a, int b) { cout << a << "+" << b << "=" << a + b << endl; } void myFunc (int a, int b) { char str[]= { 'I' ,'n' ,'l' ,'i' ,'n' ,'e' ,'H' ,'o' ,'o' ,'k' ,' ' ,'S' ,'u' ,'c' ,'c' ,'e' ,'e' ,'d' ,'!' ,0 }; MessageBoxA (0 , str, 0 , 0 ); } void _declspec(naked) myInLineHookFunc (){ __asm { pushad pushfd push [esp+0x28 ] push [esp+0x30 ] call myFunc add esp,0x8 popfd popad push ebp mov ebp, esp sub esp, 40 h push g_realAddTargetAddr add [esp],PATCH_LENGTH retn } } void main () { cout << "未HOOK之前调用addTarget(1,2)函数,结果为:" << endl; addTarget (1 , 2 ); WinTool myTool; g_realAddTargetAddr = (DWORD)myTool.repairFuncAddr ((DWORD)addTarget); DWORD oldProtect; VirtualProtect ((LPVOID)g_realAddTargetAddr, 5 , PAGE_EXECUTE_READWRITE, &oldProtect); *(BYTE*)g_realAddTargetAddr = 0xE9 ; *(DWORD*)(((BYTE*)g_realAddTargetAddr) + 1 ) = (DWORD)myInLineHookFunc - 5 - (DWORD)g_realAddTargetAddr; VirtualProtect ((LPVOID)g_realAddTargetAddr, 5 , oldProtect, NULL ); cout << "Inline HOOK之后调用addTarget(1,2)函数,结果为:" << endl; addTarget (1 , 2 ); }
结果:
通过这种间接调用函数的方式,就可以实现在上面代码的myFunc函数体中使用局部变量了!
很多时候,防守的一方都会通过检测E9,即JMP来判断自己的程序是否被HOOK了
因此将JMP过去再JMP回来的方式改为CALL过去再RET回来 的方式实现
jmp是E9,CALL是E8
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #define PATCH_LENGTH 6 DWORD g_realAddTargetAddr; void addTarget (int a, int b) { cout << a << "+" << b << "=" << a + b << endl; } void myFunc (int a, int b) { char str[]= { 'I' ,'n' ,'l' ,'i' ,'n' ,'e' ,'H' ,'o' ,'o' ,'k' ,' ' ,'S' ,'u' ,'c' ,'c' ,'e' ,'e' ,'d' ,'!' ,0 }; MessageBoxA (0 , str, 0 , 0 ); } void _declspec(naked) myInLineHookFunc (){ __asm { add esp, 4 pushad pushfd push [esp+0x28 ] push [esp+0x30 ] call myFunc add esp,0x8 popfd popad push ebp mov ebp, esp sub esp, 40 h push g_realAddTargetAddr add [esp],PATCH_LENGTH retn } } void main () { cout << "未HOOK之前调用addTarget(1,2)函数,结果为:" << endl; addTarget (1 , 2 ); WinTool myTool; g_realAddTargetAddr = (DWORD)myTool.repairFuncAddr ((DWORD)addTarget); DWORD oldProtect; VirtualProtect ((LPVOID)g_realAddTargetAddr, 5 , PAGE_EXECUTE_READWRITE, &oldProtect); *(BYTE*)g_realAddTargetAddr = 0xE8 ; *(DWORD*)(((BYTE*)g_realAddTargetAddr) + 1 ) = (DWORD)myInLineHookFunc - 5 - (DWORD)g_realAddTargetAddr; VirtualProtect ((LPVOID)g_realAddTargetAddr, 5 , oldProtect, NULL ); cout << "Inline HOOK之后调用addTarget(1,2)函数,结果为:" << endl; addTarget (1 , 2 ); }
实际上只修改了!!!!!!!!!!!标识的两处位置
Inline HOOK攻防(重点) 阶段1
(防)检测JMP(原函数无E9),检测跳转范围(原函数有E9)
(破)想方设法绕
阶段2
寻找检测代码 OD跳转到检测代码必须用硬件访问断点,内存断点的本质是修改当前命令第一个字节为CC,即int 3,也会触发检测代码,因此必须要硬件访问断点。
在OD的代码段中只能下硬件执行断点,但内存跟踪窗口可以下硬件访问断点,所以用内存跟踪窗口跟到代码段再下硬件访问断点就可以了
CRC循环冗余校验码 一种比全代码校验的更优秀的代码校验算法
CRC ,即**循环冗余校验码(Cyclic Redundancy Check)**:是数据通信领域中最常用的一种查错校验码
计算机网络中数据链路层的差错控制技术
循环冗余检查(CRC)是一种数据传输检错功能,对数据进行多项式计算,并将得到的结果附在帧的后面,接收设备也执行类似的算法,以保证数据传输的正确性和完整性。
FCS 是添加在数据后面的冗余码。FCS可以用CRC这种方法得出,但CRC并非用来获得FCS的唯一方法。
CRC最重要的是模二运算 。模二运算就是不进位的运算,模二减法和模二加法本质上就是异或运算 ,模二乘法与模二除法过程中涉及到的加减也就是模二加法与模二减法。
异或运算 的本质:A 异或 B :就相当于A按照B二进制表示中的1所对应的位进行反转。(即B中的1表示A对应位要反转)
CRC原理视频讲解
实际工程中多使用CRC-16校验
阶段3 大多数现在的程序采用的手段
(防)先对相关API全代码校验(防止提前修改好API),多个线程互相检测,并检测线程是否在活动中
(破)使用瞬时HOOK/硬件HOOK
A线程->B线程->C线程->要保护的代码
A检测B是否被挂起并且代码是否被修改,B检测C是是否被挂起并且代码否被修改,C检测要保护的代码是否被挂起并且代码是否被修改
实际上解决上面检测的方向只有两种
与当前检测线程死磕(找漏洞,检测线程无论如何逻辑上一定有漏洞!):瞬时HOOK
不改代码,但又把想做的事情做了:硬件HOOK
循环检测的伪代码:(下图相当于上面的C线程)
该循环检测本身也被B线程CRC检测中,所以不能直接修改上图中的表层二进制代码。
瞬时HOOK(临时HOOK) 瞬时HOOK是属于与当前检测线程死磕的情况
瞬时HOOK要针对具体的检测代码来实现。
对于上图的循环检测来讲,VirtualProtect函数并未被检测的情况下,我们就可以在VirtualProtect函数中做文章,HOOK Virtual Protect函数中的头部对堆栈进行回溯看是不是A处调用的他,如果不是就什么额外动作也不做;如果是A调用的他,则这个时候给ExitProcess挂钩子,钩子函数内容为检测是否是A调用的他(堆栈回溯),若不是则什么额外的都不做,如果是则取消自己给ExitProcess下的钩子。
如下线程保护,以下代码也被保护中,该如何破解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 UINT CMFCtestDlg::ThreadFunc (LPVOID pParam) { AfxMessageBox (TEXT ("线程开始执行" )); while (1 ) { u16 tmpExitCRC = crc16_MAXIM ((u8*)exitProcessAddr, 0x13 ); u16 tmpTarget = crc16_MAXIM ((u8*)messageBoxAAddr, 0x49 ); if (compareTarget != tmpTarget|| compareExit!= tmpExitCRC) { ::MessageBoxW (0 , L"检测到hook" , 0 , 0 ); ExitProcess (0 ); } Sleep (1000 ); } }
HOOK相关练习(高并发hook,了解cmpxchg8b指令) 在多核环境下,如何保证对一个高并发的内核函数进行HOOK而不会出错? 问题出在当hook位置处于高并发状态下的时候,由于一次要修改5个字节,因此当一条指令修改4个字节的时候,第五个字节还未修改的时候,其他线程依然正在执行,则导致错误
两种解决办法
先进行一个短跳中转一下(比较麻烦)
找一种可以一次修改大于等于5个字节的指令:cmpxchg8b/cmpxchg16b
但还有低概率的问题可能发生
形如如下的情况
1 2 3 4 5 6 7 //每个push占两个字节 push 1; ;hook位置: push 1; push 1; push 1; call xxxxxxxx;
上面情况
CPU在执行完第一个hook位置后第一个push,正好进行hook。
会导致执行出无法预料的问题(执行的代码都会被识别成完全不同的代码),并且堆栈百分百不平衡
如图,对于执行到中途被HOOK代码处的线程,会将[[硬编码]]解读成完全不一样的指令:(解读成了下方右图的代码)
解决方案就是不要hook这样的位置,找不影响堆栈的地方,找单条的长指令hook,或者短跳(2字节),甚至用中断(2字节)。
该问题主要处在内核层,因为内核层高并发,并且内核出问题直接蓝屏。
如何卸载高并发HOOK 卸载HOOK的前提是:没有任何线程在我们的HOOK代码中才能卸载
对于足够高并发的程序来说,没有什么好办法
对于驱动级而言,关机重启更简单。程序则是关了重开。。。
API小列表
函数
作用
CreateProcess
创建进程
OpenProcess
通过进程id获得进程句柄
TerminateProcess
通过进程句柄强制终止进程
ResumeThead
恢复线程挂起计数
CloseHandle
关闭内核对象句柄,减少内核对象计数
GetModuleFileName
根据模块名得到模块文件路径
GetCurrentDirectory
获取当前进程工作目录路径
GetCurrentProcessId
获取当前进程ID
GetCurrentProcess
获取当前进程句柄
GetCommandLine
获取当前进程命令行
GetStartupInfo
获取当前进程的启动信息
GetCurrentThreadId
获取线程id
EnumProcesses
遍历进程ID
CreateToolhelp32Snapshot
快照
CreateThread
创建线程
Sleep
当前线程停止多少毫秒
SuspendThread
挂起别的线程
WaitForSingleObject
等待单个内核对象状态发生变化
WaitForMultipleObjects
等待多个内核对象状态发生变化
GetExitCodeThread
获取线程回调函数的返回结果
GetThreadContext
获取线程上下文
SetThreadContext
设置线程上下文
CreateMutex
创建或打开互斥体
GetLastError
获取错误码
CreateEvent
创建或打开事件
SetEvent
指定事件设为有信号(优先wait线程)
ResetEvent
指定事件设为无信号(优先wait线程)
GDI等一大批函数
绘图
CreateWindow
创建窗口并创建消息队列
ShowWindow
显示窗口
GetMessage
从消息队列取消息
DispatchMessage
分发消息,通知操作系统调用对应窗口回调
TranslateMessage
使键盘按下产生WM_CHAR消息
OutputDebugString
显示调试信息
SeTDlgItemText
设置控件的标题或文本
PostQuitMessage
向系统指示线程已请求终止(退出)。它通常用于响应WM_DESTROY 消息。
VirtualAlloc
给自己申请私有内存
VirtualAllocEx
给别的进程申请私有内存
VirtualFree
释放私有内存或仅释放物理页
CreateFileMapping
创建或打开文件映射内核对象用于:申请物理页或者申请物理页并把文件映射到物理页
MapViewOfFile
物理页映射到虚拟地址函数
UnmapViewOfFile
取消物理页到虚拟地址函数的映射关系
GetLogicalDrives
获取卷(有哪些卷,是什么)
GetLogicalDriveStrings
获取一个卷的盘符的字符串
GetDriveType
获取卷的类型
GetVolumeInformation
获取卷的信息(可查看文件系统等)
CreateDirectory
创建目录
RemoveDirectory
删除现有空目录
MoveFile
移动文件或目录(可改名)
GetCurrentDirectory/SetCurrentDirectory
获取/设置当前程序工作目录
CreateFile
创建或打开文件或 I/O 设备
GetFileSize
获取文件大小
GetFileAttributes()/GetFileAttributesEx
检索指定文件或目录的属性。
ReadFile()/WriteFile()/CopyFile()/DeleteFile
读/写/复制/删除文件
FindFirstFile()/FindNextFile
查找文件或目录
FlushViewOfFile
将文件映射指定范围的字节即刻写入硬盘
LoadLibrary
加载动态链接库
FreeLibrary
卸载动态链接库
FreeLibraryAndExitThread
当前由动态链接库创建的线程卸载动态链接库并退出线程
GetProcAddress
获取导出函数在对应模块中的地址
CreateRemoteThread
创建远线程
WriteProcessMemory
写进程内存
ReadProcessMemory
读内存进程
GetModuleHandle
获取模块句柄
VirtualProtect
更改对调用进程虚拟地址空间中已提交页面区域的保护。
VirtualProtectEx
更改对别的进程调用进程虚拟地址空间中已提交页面区域的保护
自定义消息 Windows程序与其它类型程序的区别就是使用消息,例如键盘或鼠标消息等,在dos系统下的程序没有定义消息。在windows操作系统中,消息不但可以用于进程内的通信,也可以用于进程间的通信。而我这篇博文将讲使用自定义消息实现进程间的通信。
我们都知道,在windows中消息分为两种,即系统消息和用户自定义消息,系统消息定义从0到0x3ff,可以使用0x400到0x7fff定义自己的消息。windows把0x400定义为WM_USER,如果想定义自己的一个消息,可以在WM_USER上加上一个值。当然了,还有另外一种方法,这里就不讲了,而是使用RegisterWindowsMessage()函数。
要想用消息实现进程间通信,则需要在这两个程序中定义或注册相同的消息,才能保证数据通信顺利进行。
使用这种方式实现进程间通信,但是传送的数据只能是长整型的数据,不能是字符串。所以这个就是这种方式的局限。
键鼠模拟 用户按下按键—–键盘驱动程序将此事件传递给操作系统—–操作系统将键盘事件插入消息队列—–键盘消息被发送到当前活动窗口
模拟键盘的方法有三种:
keybd_event()
PostMessage() /SendMessage()
SendInput()
keybd_event是全局模拟按键的,只对前台窗口 (即当前的活动窗口)才可以,但是如果模拟的按键正好也是某个窗口的全局热键消息,那该窗口也能接收到的
而SendMessage 、PostMessage是对指定句柄窗口都其作用的,对于做一些一外挂是非常有用的。例如可以做成这样的效果:即用SendMessage/PostMessage在某一个窗口模拟动作,而同时自己可以在其他窗口做其他事情,互不影响的!
PostMessage中的窗口句柄参数,可以设置为HWND_BROADCAST,即广播,但不要理所当然地认为是对所有的窗口都起作用!!!它只对系统的顶层窗口起作用,子窗口是收不到这个消息的!!!
SendMessage是没有HWND_BROADCAST参数的,那是因为,SendMessage总是等发送的消息在对应的窗口消息队列 中处理完毕后才返回的(这是一种负责的行为)
SendMessage和PostMessage的区别 是PostMessage函数直接把消息仍给目标程序就不管了,而SendMessage把消息发出去后,还要等待目标程序返回些什么东西才好。这里要注意的是,模拟键盘消息一定要用PostMessage函数才好,用SendMessage是不正确的(因为模拟键盘消息是不需要返回值的,不然目标程序会没反应)
**如果用PostMessage发送局部消息模拟按键不成功的话,你可以试一试全局级的键盘消息keybd_event() **
PostMessageA 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 PostMessage ( hwnd, WM_RBUTTONDOWN, 0 , MAKELPARAM (200 ,200 ) ); PostMessage ( hwnd, WM_KEYDOWN, 0x41 , 0 );
虚拟键码VK_code查询
keybd_event() 这个函数对大部分的窗口程序都有效,可是仍然有一部分游戏对它产生的键盘事件熟视无睹,这时候,你就要用上bScan这个参数了。一般的,bScan都传0,但是如果目标程序是一些DirectX游戏,那么你就需要正确使用这个参数传入扫描码,用了它可以产生正确的硬件事件消息,以被游戏识别。
SendInput函数 也可以模拟全局键盘事件。SendInput可以直接把一条消息插入到消息队列中,算是比较底层的了
钩子模拟键鼠操作 除了以上这些,用全局钩子也可以模拟键盘消息。如果你对windows中消息钩子的用法已经有所了解,那么你可以通过设置一个全局HOOK来模拟键盘消息,比如,你可以用WH_JOURNALPLAYBACK这个钩子来模拟按键。WH_JOURNALPLAYBACK是一个系统级的全局钩子,它和WH_JOURNALRECORD的功能是相对的,常用它们来记录并回放键盘鼠标操作。WH_JOURNALRECORD钩子用来将键盘鼠标的操作忠实地记录下来,记录下来的信息可以保存到文件中,而WH_JOURNALPLAYBACK则可以重现这些操作。当然亦可以单独使用WH_JOURNALPLAYBACK来模拟键盘操作。你需要首先声明SetWindowsHookEx函数,它可以用来安装消息钩子: Declare Function SetWindowsHookEx Lib “user32” Alias “SetWindowsHookExA” (ByVal idHook As Long,ByVal lpfn As Long, ByVal hmod As Long, ByVal dwThreadId As Long) As Long 先安装WH_JOURNALPLAYBACK这个钩子,然后你需要自己写一个钩子函数,在系统调用它时,把你要模拟的事件传递给钩子参数lParam所指向的EVENTMSG区域,就可以达到模拟按键的效果。不过用这个钩子模拟键盘事件有一个副作用,就是它会锁定真实的鼠标键盘,不过如果你就是想在模拟的时候不会受真实键盘操作的干扰,那么用用它倒是个不错的主意。
驱动级模拟 直接读写键盘的硬件端口
有一些使用DirectX接口的游戏程序,它们在读取键盘操作时绕过了windows的消息机制,而使用DirectInput.这是因为有些游戏对实时性控制的要求比较高,比如赛车游戏,要求以最快速度响应键盘输入。而windows消息由于是队列形式的,消息在传递时会有不少延迟,有时1秒钟也就传递十几条消息,这个速度达不到游戏的要求。而DirectInput则绕过了windows消息,直接与键盘驱动程序打交道,效率当然提高了不少。因此也就造成,对这样的程序无论用PostMessage或者是keybd_event都不会有反应,因为这些函数都在较高层。对于这样的程序,只好用直接读写键盘端口的方法来模拟硬件事件了。要用这个方法来模拟键盘,需要先了解一下键盘编程的相关知识。
我的理解:
全局到局部窗口的消息由操作系统进程分发
全局理解点 进程结束的时候,操作系统会在进程之后进行全面的清除,使得所有操作系统资源都不会保留下来。这意味着进程使用的所有内存均被释放,所有打开的文件全部关闭,所有内核对象的使用计数均被递减,同时所有的用户对象和GDI对象均被撤消。
图形图像处理 easyx库 ``
bmp格式的图片如何描述
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 typedef struct tagBITMAPFILEHEADER { WORD bfType; DWORD bfSize; WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits; } BITMAPFILEHEADER, FAR *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER; typedef struct tagBITMAPINFOHEADER { DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;
tagBITMAPINFOHEADER官方信息查阅
bmp文件是的像素点数据是反的,最先的像素点数据在文件最末尾.
一个像素是3个字节
坐标关系转换相关查阅
windows脚本 参考
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @echo off setlocal REM 打印当前操作:删除net6.0-windows文件夹 echo 正在删除当前目录下名为net6.0-windows的文件夹... if exist "net6.0-windows" ( rmdir /s /q "net6.0-windows" echo 删除完成。 ) else ( echo 文件夹不存在,跳过删除步骤。 ) REM 打印当前操作:解压net6.0-windows.zip echo 正在解压当前目录名为net6.0-windows的zip压缩包... if exist "net6.0-windows.zip" ( powershell -command "Expand-Archive -Path 'net6.0-windows.zip' -DestinationPath '.'" echo 解压完成。 ) else ( echo 压缩包不存在,跳过解压步骤。 ) REM 打印当前操作:拷贝Config文件夹 echo 正在将当前目录下的Config文件夹拷贝到net6.0-windows文件夹下覆盖... if exist "Config" ( xcopy /s /e /y "Config" "net6.0-windows\Config" echo 拷贝完成。 ) else ( echo Config文件夹不存在,跳过拷贝步骤。 ) REM 打印当前操作:启动程序 echo 正在启动程序... cd "net6.0-windows" IonImplantationSystem --console endlocal
windows开发调试器 linux下开发调试器参考链接
linux下主要使用系统API,ptrace来实现
windows下可以通过下面的winAPI实现简单调试器,至于在某些高级调试场景中,可以通过开发内核模式驱动程序访问目标进程的底层信息。内核驱动可以直接操作内存、设置断点或注入代码
常用的调试函数
DebugActiveProcess : 附加到目标进程,使当前进程成为调试器。
DebugActiveProcessStop : 结束对目标进程的调试。
WaitForDebugEvent : 等待目标进程中的调试事件(例如断点、异常、线程创建等)。
ContinueDebugEvent : 继续目标进程的执行。
ReadProcessMemory / WriteProcessMemory : 读取和写入目标进程的内存。
GetThreadContext / SetThreadContext : 获取和设置线程的 CPU 寄存器。
SuspendThread / ResumeThread : 暂停和恢复目标线程。
调试器的基本流程 以下是调试器与目标进程交互的典型步骤:
调用 DebugActiveProcess 附加到目标进程。
调用 WaitForDebugEvent 等待调试事件。
根据事件类型(如异常或断点),执行相应操作。
使用 ContinueDebugEvent 继续目标进程的执行。
通过 ReadProcessMemory、WriteProcessMemory 或 GetThreadContext 操作目标进程的状态。
Windows调试 Sysinternals
Sysinternals 工具包是由微软官方提供的一套免费、专业级的 Windows 系统诊断与管理工具集,由知名系统专家 Mark Russinovich 和 Bryce Cogswell 创建,后于 2006 年被微软收购并持续更新至今。它被广泛誉为“Windows 管理员的瑞士军刀”,适用于系统故障排查、性能优化、安全分析及开发调试等场景。
Sysinternals 工具包是 Windows 系统管理的终极工具箱 ,尤其适合 IT 运维、安全工程师及开发者。其通过底层监控能力 和模块化工具设计 ,解决了从日常维护(如清理启动项)到深度故障(如内核级 Rootkit)的复杂问题。对于普通用户,建议优先掌握 Process Explorer、Autoruns和 Process Monitor三大核心工具;专业用户则可结合脚本(如 PowerShell 调用 PsTools)实现自动化管理
下载与配置 下载地址
官方文档
相关书籍: 《Windows Sysinternals 实战指南》(中文版已出版)
将工具路径添加到系统环境变量(永久生效)
按 Win + S 搜索 “编辑系统环境变量” → 打开。
点击 “环境变量” → 在 “系统变量” 中找到 Path→ 点击 编辑 。
点击 新建 → 输入工具解压目录路径(如 C:\Sysinternals)→ 连续点击 确定 保存。
优势 深度系统访问
深度系统访问: 直接调用 Windows 内核 API,提供任务管理器、资源监视器等原生工具无法获取的底层数据(如句柄类型、线程堆栈
轻量化与高效性: 工具均为绿色单文件,体积小巧(多数 <1MB),运行时资源占用低,适合生产环境部署
跨平台与持续更新: 支持 Windows XP 至 Windows 11,含 x86、x64 及 ARM 架构版本,微软每月推送安全及功能更新
开源与社区生态: 部分工具开源(如 ProcDump),拥有活跃社区和官方博客(Mark Russinovich 定期发布案例解析)
工具盘点 Sysinternals 包含 70 余款工具,按功能可分为以下类别
进程与线程管理
Process Explorer :替代任务管理器,实时显示进程树、加载的 DLL、句柄(文件/注册表)及资源占用,支持搜索和强制结束进程
Process Monitor :动态监控文件系统、注册表、网络和进程活动,集成 FileMon 和 RegMon 功能,支持高级过滤与线程堆栈跟踪
PsTools :命令行工具集,含 PsExec(远程执行命令)、PsKill(终止进程)、PsInfo(获取系统信息)等
系统启动项与自启动管理
Autoruns :深度扫描注册表、服务、计划任务等 50+ 位置,管理开机启动项,识别恶意软件注入
文件与磁盘工具
Disk2vhd :将物理磁盘转换为 VHD/VHDX 虚拟磁盘,支持 P2V 迁移
Contig :单文件碎片整理工具,提升高频访问文件性能
Handle :查看进程占用的文件句柄和注册表项,定位资源泄漏(如 IAStorIcon.exe句柄异常)
网络与安全分析
TCPView :图形化显示所有 TCP/UDP 连接及关联进程,快速识别恶意端口占用
RootkitRevealer :检测隐藏内核级 Rootkit 恶意软件
SigCheck :验证文件数字签名,识别篡改或未签名程序
内存与系统信息
RAMMap :分析物理内存使用详情,包括缓存、分页池等
VMMap :可视化进程虚拟内存分配,诊断内存泄漏
其他实用工具
BgInfo :在桌面背景动态显示系统信息(IP、主机名等)
ZoomIt :屏幕缩放与标注工具,适用于演示和教学
全部工具盘点
工具名称
主要作用
最常用法
AccessChk
检查文件、注册表项或服务的访问权限
accesschk.exe C:\Windows
AccessEnum
扫描文件/目录权限变化
accessenum.exe D:\Data
AdExplorer
Active Directory 浏览器
替代 ADSI 编辑器实时查看域对象
AdInsight
LDAP 实时监控工具
监控应用程序的 AD 查询请求
AdRestore
恢复已删除的 AD 对象
adrestore.exe -r "CN=User"
Autologon
自动登录凭证配置
autologon.exe user domain pwd
Autoruns
启动项深度管理
autoruns.exe
BgInfo
桌面系统信息展示
bginfo.exe config.bgi /timer:0
BlueScreen
模拟蓝屏崩溃
bluescreen.exe -accepteula
CacheSet
文件系统缓存管理
cacheset.exe -s 200
ClockRes
查询系统时钟分辨率
clockres.exe
Contig
单文件碎片整理
contig.exe -s D:\file.iso
Coreinfo
CPU 特性检测
coreinfo.exe -c
Ctrl2Cap
Ctrl/CapsLock键互换
ctrl2cap.exe /install
DebugView
实时捕获系统日志
dbgview.exe
Desktops
虚拟桌面管理
desktops.exe
Disk2vhd
物理盘转虚拟盘
disk2vhd.exe C: D:\disk.vhdx
DiskExt
分区偏移量查询
diskext.exe
DiskMon
磁盘I/O实时监控
捕获文件读写操作日志
DiskView
磁盘区块可视化
查看文件物理分布位置
Disk Usage (DU)
目录空间分析
du.exe -c C:\Users
EFSDump
加密文件信息查看
efsdump.exe C:\secret.docx
FindLinks
硬链接检测
findlinks.exe C:\file.txt
Handle
句柄查看器
handle.exe -p explorer.exe
Hex2dec
进制转换器
hex2dec.exe FFF→ 4095
Junction
NTFS连接点管理
junction.exe D:\link C:\target
LDMDump
逻辑磁盘管理器导出
导出磁盘分区元数据
ListDLLs
DLL依赖检测
listdlls.exe -d notepad.exe
LiveKd
物理内存调试
livekd.exe -kl
LoadOrder
驱动加载顺序
查看驱动加载时序
LogonSessions
登录会话列表
logonsessions.exe -p
MoveFile
重启后移动文件
movefile.exe C:\old.dll C:\new.dll
NotMyFault
崩溃/死锁测试
主动触发蓝屏测试系统稳定性
NTFSInfo
NTFS卷详情
ntfsinfo.exe C:
PendMoves
挂起操作查看
显示重启后执行的文件操作
PipeList
命名管道列表
pipelist.exe -h
PortMon
串/并口监控
已淘汰,由Process Monitor替代
ProcDump
进程内存转储
procdump.exe -ma notepad.exe dump.dmp
Process Explorer
进程资源管理器
替代任务管理器查看句柄/DLL
Process Monitor
实时文件/注册表/网络监控
过滤分析特定进程操作
PsExec
远程命令执行
psexec \\server cmd.exe
PsFile
远程打开文件查看
psfile \\server -c
PsGetSid
账号SID查询
psgetsid.exe Administrator
PsInfo
系统信息收集
psinfo.exe -h -s
PsKill
进程终止
pskill.exe notepad.exe
PsList
进程详情列表
pslist.exe -t -m
PsLoggedOn
登录用户查询
psloggedon.exe -l
PsLogList
事件日志导出
psloglist.exe -c
PsPasswd
密码修改工具
pspasswd.exe \\PC user newpwd
PsPing
网络延迟测试
psping.exe google.com:80
PsService
服务管理
psservice.exe restart WinDefend
PsShutdown
远程关机
psshutdown.exe -r -f \\PC
PsSuspend
进程挂起
pssuspend.exe chrome.exe
PsTools
Ps系列工具套件
包含所有Ps开头的工具
RAMMap
物理内存分析
查看内存分配和缓存情况
RDCMan
远程桌面集中管理
批量管理多台服务器连接
RegDelNull
删除含空字符注册表
清理异常注册表项
RegHide
注册表项隐藏
reghide.exe create "HKLM\Secret"
RegJump
快速跳转注册表路径
regjump HKCU\Software
Registry Usage (RU)
注册表空间统计
计算注册表占用大小
SDelete
安全删除
sdelete.exe -c -s D:\data
ShareEnum
网络共享扫描
检测开放共享权限
ShellRunas
凭据运行程序
替代右键”运行为”功能
Sigcheck
文件签名验证
sigcheck.exe -u -e C:\Windows\System32
Streams
NTFS数据流检测
streams.exe -s -d C:\Files
Strings
二进制文本提取
strings.exe malware.exe > output.txt
Sync
强制写入磁盘缓存
sync.exe
Sysmon
高级系统监控
配置日志记录进程创建/网络连接
TCPView
网络连接查看
实时监控TCP/UDP连接
VMMap
进程内存分析
诊断内存泄漏问题
VolumeID
修改卷序列号
volumeid.exe C: 1234-5678
WhoIs
域名信息查询
whois.exe sysinternals.com
WinObj
内核对象管理器
查看GLOBAL??等内核对象目录
ZoomIt
屏幕标注工具
演讲时放大屏幕区域并标注
应用场景
故障诊断
系统蓝屏 :通过 ProcDump 捕获崩溃转储,结合 Process Monitor 分析异常操作链。
文件占用冲突 :Handle 定位锁定文件的进程,强制解除占用。
性能优化
内存泄漏 :RAMMap 分析缓存分布,VMMap 定位进程内存异常增长。
磁盘瓶颈 :DiskMon 监控实时 I/O,Contig 整理关键文件碎片。
安全加固
启动项审计 :Autoruns 清除恶意自启动项,修复注册表劫持。
恶意进程分析 :Process Explorer 验证签名和父进程链,识别注入 DLL。
开发与测试
API 调用追踪 :Process Monitor 记录应用程序的注册表和文件操作,辅助调试。
压力测试 :NotMyFault 主动触发系统崩溃或内存泄漏,验证容错机制。