分类归档: C++

PE 格式学习总结(一)– PE文件概述

零 前言

    PE格式,是Windows的可执行文件的格式。Windows中的 exe文件,dll文件,都是PE格式。PE 就是Portable Executable 的缩写。Portable 是指对于不同的Windows版本和不同的CPU类型上PE文件的格式是一样的,当然CPU不一样了,CPU指令的二进制编码是不一样的。只是文件中各种 东西的布局是一样的。

图 1.1

    图1.1是 JIURL PEDUMP 打开 Win2K 中的 explorer.exe 的截图。JIURL PEDUMP 是我写的一个小工具,从文件开始的 Dos Header 一直到 Section Table,打开PE文件之后,点击相应结构,就会高亮显示文件中相应的部分。不过没有Sections。对了解 PE 格式有所帮助,可以很好的配合后面的介绍。可以到我的主页 http://jiurl.yeah.net 上下载。

一   PE文件格式概述

PE文件结构的总体层次分布如下所示

————–
|DOS MZ Header |
|————–|
|DOS Stub      |
|————–|
|PE Header     |
|————–|
|Section Table |
|————–|
|Section 1     |
|————–|
|Section 2     |
|————–|
|Section …   |
|————–|
|Section n     |
————–

 

1.1 DOS Header

    PE文件最开始是一个简单的 DOS MZ header,它是一个 IMAGE_DOS_HEADER 结构。有了它,一旦程序在DOS下执行,DOS就能识别出这是有效的执行体,然后运行紧随 MZ Header 之后的 DOS Stub。

1.2   DOS Stub   

    DOS Stub 是一个有效的 DOS 程序。当程序在DOS下运时,输出象 "This program cannot be run in DOS mode" 这样的提示。在 图1.1中就可以看到字符串 "This program cannot be run in DOS mode"。这是编译器生成的默认stub程序。你也可以通过链接选项 /STUB:filename 指定任何有效的MS-DOS可执行文件来替换它。

1.3 PE Header

    紧接着 DOS Stub 的是 PE Header。它是一个 IMAGE_NT_HEADERS 结构。其中包含了很多PE文件被载入内存时需要用到的重要域。执行体在支持PE文件结构的操作系统中执行时,PE装载器将从 DOS MZ header 中找到 PE header 的起始偏移量。因而 ?DOS stub 直接定位到真正的文件头 PE header。

1.4 Section Table

    PE Header 接下来的数组结构 Section Table (节表)。如果PE文件里有5个节,那么此 Section Table 结构数组内就有5个成员,每个成员包含对应节的属性、文件偏移量、虚拟偏移量等。图1中的节表有4个成员。

1.5 Sections

    PE文件的真正内容划分成块,称之为sections(节)。Sections 是以其起始位址来排列,而不是以其字母次序来排列。通过节表提供的信息,我们可以找到这些节。图1.1所示的 explorer.exe 中有4个节。程序的代码,资源等等就放在这些节中。

二 PE文件格式中的结构及其作用

这部分内容请参考下面的几篇文章,使用工具 JIURL PEDUMP 有助于快速了解。
大家不要因此,而失望不看,本文重点在后三篇,本篇只是为了有个交代,和介绍些相关内容。
注意,在WINNT.H中,有所有PE相关结构的定义。我们用到的结构定义都来自那里。

Microsoft Portable Executable and Common Object File Format Specification
MSDN

《Windows95系统程式设计大奥秘》
第8章 PE 与COFF OBJ 档案格式
Matt Pietrek 着 侯杰译

Iczelion的PE教程

PE学习笔记(一) rivershan
PE学习笔记(二) rivershan

Inside Windows
An In-Depth Look into the Win32 Portable Executable File Format

Matt Pietrek
已经被人翻译了。

Inside Windows
An In-Depth Look into the Win32 Portable Executable File Format
Matt Pietrek

三 几个要注意的问题

3.1 文件中大量的空白

    在 PE Header结构 中的 OptionalHeader 结构中的成员 FileAlignment 的值是文件中节的对齐粒度,单位是字节,这个值应该是2的n次方,范围从512到64k。如果这里的值是512,那么PE文件中的节的长度都是512字节 的整数倍,内容不够的部分用0填充。比如一个PE文件的 FileAlignment 为200h(十进制512),它的第一个节在400h处,长度为100h,那么从文件400h到500h中为这一节的内容,而文件对齐粒度是200h,所 以为了使这一节长度为FileAlignment的整数倍,500h到600h会被用零填充。而下一个节的开始地址为600h。用16进制编辑器打开PE 文件,就可以看到这种情况,PE文件头的内容结束到第一个节开始之间的地方,每一个节中内容结束到下一节开始的地方都会有大量的空白。VC6编译链接时默 认的FileAlignment为1000h(4k),可以使用链接选项 /ALIGN:number 来改变这个值。比如把4k改成512时,可以明显减小生成文件的大小。

3.2 big-endian和little-endian

    PE Header中的 FileHeader 的成员 Machine 中的值,根据WINNT.H中的定义,对于 Intel CPU 应该为 0x014c。但是你用16进制编辑器打开PE文件,看到这个WORD显示的却是 4c 01 。你看到的并没有错,你看到的 4c 01 就是 0x014c,只不过由于 intel cpu 是ittle-endian,所以显示出来是这样的。对于 big-endian 和 little-endian,请看下面的例子。

比如一个整形int变量。长为四个字节。
这个变量的地址比如为n。
则这个变量的4个字节地址分别为n,n+1,n+2,n+3。

当 这个整形变量 的值为 0x12345678 时,

对于 big-endian 来说
地址n+0的那个字节中的值为 0x12
地址n+1的那个字节中的值为 0x34
地址n+2的那个字节中的值为 0x56
地址n+3的那个字节中的值为 0x78

按如下方式就会显示为
n n+1 n+2 n+3
12 34 56 78

对于 ittle-endian 来说
地址n+0的那个字节中的值为 0x78
地址n+1的那个字节中的值为 0x56
地址n+2的那个字节中的值为 0x34
地址n+3的那个字节中的值为 0x12

按如下方式就会显示为
n n+1 n+2 n+3
78 56 34 12

Intel使用的是 ittle-endian 。

一个整形 int 变量 i,的地址是&i,那么这个i的四个字节是&i,&i+1,&i+2,&i+3。
可以用这样一个程序看到。

#include <stdio.h>
#include <conio.h>

void main()
{
int i;
char* p;
p=(char*)&i;

printf("i: ");
scanf("%x",&i);
printf("n");

printf("&i+0: %xn",*p);
printf("&i+1: %xn",*(p+1));
printf("&i+2: %xn",*(p+2));
printf("&i+3: %xn",*(p+3));

printf("n");
printf("&i-4: %xn",*(p-4));
printf("&i-3: %xn",*(p-3));
printf("&i-2: %xn",*(p-2));
printf("&i-1: %xn",*(p-1));

printf("n");
printf("&i+4: %xn",*(p+4));
printf("&i+5: %xn",*(p+5));
printf("&i+6: %xn",*(p+6));
printf("&i+7: %xn",*(p+7));

getch();

}

当我们输入 12345678 的时候可以看到,输出

i: 12345678

&i+0: 78
&i+1: 56
&i+2: 34
&i+3: 12

&i-4: 7c
&i-3: ffffffff
&i-2: 12
&i-1: 0

&i+4: ffffffc0
&i+5: ffffffff
&i+6: 12
&i+7: 0
正是&i,&i+1,&i+2,&i+3这四个字节中储存了i的值。

对于int,WORD,DWORD等等都要注意 big-endian 和 little-endian 。

3.3 RVA (Relative Virtual Address) 相对虚拟地址

    RVA是一个简单的相对于PE载入点的内存偏移。比如,PE载入点为0X400000,那么代码节中的地址0X401000的RVA为(target address) 0x401000 – (load address)0x400000 = (RVA)0x1000.换句话说 RVA是0x1000,载入点为0X400000,那么该RVA的在内存中的实际地址就是0X401000。注意一下RVA是指内存中,不是指文件中。是 指相对于载入点的偏移而不是一个内存地址,只有RVA加上载入点的地址,才是一个实际的内存地址。

3.4 三种不同的地址

    PE的各种结构中,涉及到很多地址,偏移。有些是指在文件中的偏移,有的是指在内存中的偏移。一定要搞清楚,这个地址或者是偏移,是指在文件中,还是指在 内存中。第一种,文件中的地址。比如用16进制编辑器打开PE文件,看到的地址(偏移)就是文件中的地址,我们使用某个结构的文件地址,就可以在文件中找 到该结构。第二种,文件被整个映射到内存时,比如某些PE分析软件, 把整个PE文件映射到内存中,这时是内存中的地址,如果知道某一个结构在文件中的地址的话,那么这个PE文件被映射到内存之后该结构的在内存中的地址,可 以用文件中的地址加上映射内存的地址,就可以得到在该结构内存中的地址。第三种,执行PE时,PE文件会被载入器载入内存,这时经常需要的是RVA。比如 知道一个结构的RVA,那么载入点加上RVA就可以得到内存中该结构的实际地址。比如,某个程序,我们用16进制编辑器打开它,看到PE Header开始在16进制编辑器显示为000000C8的地方。于是我们在16进制编辑器显示为000000FC的地方找到了 OptionalHeader的ImageBase,值为400000h,那么当这个程序被执行时,如果内存中400000h处没有使用,该程序就会被载 入到那里。而我用CreateFileMapping将这个PE文件映射到内存中时,可以得到块内存的地址为5505024。对于映射入内存的这个PE文 件,我们就可以在内存中000000FCh+05505024h=5505120处找到这个PE的OptionalHeader的ImageBase。

3.5 几个重要结构的说明

PE Header 的 FileHeader 的 NumberOfSections:这是一个很重要的字段,用来确定文件中节的数目。

PE Header 的 OptionalHeader 的 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]:一个IMAGE_DATA_DIRECTORY 结构数组。到目前为止这个数组的长度是固定的,有16个元素,这16个元素分别代表
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory
// IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor
每个元素是一个IMAGE_DATA_DIRECTORY结构,IMAGE_DATA_DIRECTORY定义如下。
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
第一个字段是一个RVA,第二个字段是一个大小。

Section Table 节表紧跟在OptionalHeader之后,是一个IMAGE_SECTION_HEADER结构的数组。该数组中成员的个数由 File Header (IMAGE_FILE_HEADER) 结构中 NumberOfSections 域的域值来定。节表中的成员是IMAGE_SECTION_HEADER 结构,IMAGE_SECTION_HEADER 结构的长度固定,长40个字节。整个Section Table 的长度不固定,等于 NumberOfSections*sizeof(IMAGE_SECTION_HEADER)。IMAGE_SECTION_HEADER 结构中,

VirtualAddress:本节的RVA(相对虚拟地址)。
PointerToRawData:这是本节基于文件的偏移量。

3.6 DOS MZ Header 中的 MZ

    MZ是MZ格式的主要作者 Mark Zbikowski 的名字的缩写。

Read: 924

[转]visual c++对大型数据文件的读取

  笔者前不久曾遇到一个问题,解决之后的经验愿与大家分享。问题是这样的,有一批数据文件,数据格式如下:

日期,开盘,最高,最低,收盘,成交量,成交金额

1996年5月13日,636.96,636.96,636.96,636.96,0,0,

1996年5月14日,641.61,641.61,641.61,641.61,0,0,

1996年5月15日,637.83,637.83,637.83,637.83,0,0,

………….

要求将数据填写到四张表中,以便作相应的分析。笔者开始用CFile和CStdioFile类的方法读取件。Cfile类提供了基于二进制流的文件操 作,功能类似于C语言中的fread()和fwrite()函数。CStdioFile提供了基于字符串流的文件操作,功能类似于C语言中fgets() 和fputs()函数。但是笔者发现,使用这两个类进行文件操作时,对于一次文件读写的数据量的大小必须限制在65535字节以内。究其原因是在VC中访 问大于65535字节的缓冲区需要Huge型指针,而在CFile和CStdioFile类中,使用的是Far型的指针。由于Far型指针不具有跨段寻址 的能力,因此限制了一次文件读写的长度小于65535字节。如果传递给CFile和CStdioFile两个类的成员函数的数据缓冲区的大小大于 65535字节的时候,VC就会产生ASSERT错误。

针对文件格式特点,笔者改用CArchive类进行读取如下:

CFile SourceFile;//数据文件

CString SourceData;//定义一临时变量保存一条记录

SourceFile.Open(…….);

CArchive ar(&SourceFile,CArchive::load);

while(NULL!=ar.ReadString(SourceData))//循环读取文件,直到文件结束

{

if(SourceData=="日期,开盘,最高,最低,收盘,成交量,成交金额"||SourceData=="")

continue;//跳过文件头部的提示信息

//分析并填充//

}

在进行分析时,笔者采取了逐步分析并修改的办法,过程如下:

int nYear;

CString Year= SourceData.Left(SourceData.Find("年"));//截取年前面的字符串

nYear=atoi(Year);//类型转换

SourceData=SourceData.Righ(SourceData.GetLength()-SourceData.Find("年")-2);//将年以及前面的字符删除。

重复上面分析过程,直到记录末尾。

通过上述方法,笔者成功地将文件读取并分析填充。

Read: 666

用拷贝钩子实现对文件夹的监控

本文原出处已不知。原作:webber84, 经ccrun(老妖)修改并在BCB在调试通过。
ICopyHook 是一个用于创建拷贝钩子处理程序COM接口,它决定一个文件夹或者打印机对象是否可以被移动,拷贝,重命名或删除。Shell在执行这些操作之前,会调用 ICopyHook接口的CopyCallback方法对它们进行验证。CopyCallback返回一个int值指示Shell是否应该继续执行这个操 作。返回值IDYES表示继续,而返回值IDNO和IDCANCEL则表示终止。

一个文件夹对象可以安装多个拷贝钩子处理程序。如果出现这种情况,Shell会依次调用每个处理程序。只有当每个处理程序都返回IDYES时,Shell才真正执行用户请求的操作。

拷 贝钩子处理程序的作用是在上述四种操作执行前对它们进行验证,但是Shell并不会把操作的结果通知给拷贝钩子处理程序。而windows提供的API函 数FindFirstChangeNotification和FindNextChangeNotification却可以实现这个功能。因此,只有把这 种两种方法结合起来,才能对一个文件夹的状态进行完全的监控。

拷贝钩子处理程序实现并不困难,首先创建一个作为进程内组件的COM对象, 它只需要暴露一个ICopyHook接口(当然还有IUnknown)。然后用regsrv32.exe注册这个COM组件。最后一步是向Shell注册 你的这个拷贝钩子处理程序,方法是在注册表HKEY_CLASSES_ROOTDirectoryShellexCopyHookHandlers 下创建一个名称任意的sub key,在此sub key中创建一个类型为REG_SZ的项并将你的COM对象的CLSID作为它的默认值就可以了。

下面就是一个拷贝钩子的实现程序(注:以下代码经老妖改动并添加了详细操作过程,在BCB6中成功编译并通过测试)

1. 从ICopyHook接口创建TCopyHook,从IClassFactory接口创建TClassFactory:

// TCopyHook.h
// TCopyHook类实现了ICopyHook接口,TClassFactory实现了IClassFactory接口
//—————————————————————————
#define NO_WIN32_LEAN_AND_MEAN
#include <shlobj.h>
//—————————————————————————
class TCopyHook: public ICopyHook
{
public:
     TCopyHook():m_refcnt(0) {}
     STDMETHODIMP QueryInterface(REFIID iid,void **ppvObject);
     STDMETHODIMP_(ULONG) AddRef();
     STDMETHODIMP_(ULONG) Release();
     STDMETHODIMP_(UINT) CopyCallback(HWND hwnd, UINT wFunc, UINT wFlags,
             LPCTSTR pszSrcFile, DWORD dwSrcAttribs,
             LPCTSTR pszDestFile, DWORD dwDestAttribs);
private:
     int m_refcnt;
};
//—————————————————————————
class TClassFactory : public IClassFactory
{
public:
     TClassFactory():m_refcnt(0) {}
     STDMETHODIMP QueryInterface(REFIID iid, void **ppvObject);
     STDMETHODIMP_(ULONG) AddRef();
     STDMETHODIMP_(ULONG) Release();
     STDMETHODIMP CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject);
     STDMETHODIMP LockServer(BOOL fLock);
private:
     int m_refcnt;
};

// TCopyHook.cpp
// TCopyHook对象和TClassFactory对象的实现文件
#include <stdio.h>
#include "TCopyHook.h"
//—————————————————————————
extern LONG nLocks;           // 对象计数,用于DllCanUnloadNow
ULONG __stdcall TCopyHook::AddRef()
{
     if(m_refcnt == 0)
         nLocks++;
     m_refcnt++;
     return m_refcnt;
}
//—————————————————————————
ULONG __stdcall TCopyHook::Release()
{
     int nNewCnt = –m_refcnt;
     if(nNewCnt <= 0)
     {
         nLocks–;
         delete this;
     }
     return nNewCnt;
}
//—————————————————————————
HRESULT __stdcall TCopyHook::QueryInterface(REFIID dwIID, void **ppvObject)
{
     if(dwIID == IID_IUnknown)
         *ppvObject = static_cast<IUnknown*>(this);
     else
         if(dwIID == IID_IShellCopyHook)
             *ppvObject = static_cast<ICopyHook*>(this);
         else
             return E_NOINTERFACE;
     reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
     return S_OK;
}
//—————————————————————————
// 这就是CopyCallback方法,拷贝钩子的所有功能由它实现。参数的具体值参看MSDN
UINT __stdcall TCopyHook::CopyCallback(HWND hwnd, UINT wFunc, UINT wFlags,
         LPCTSTR pszSrcFile, DWORD dwSrcAttribs,
         LPCTSTR pszDestFile, DWORD dwDestAttribs)
{
     char szMessage[MAX_PATH+14];
     sprintf(szMessage, "对%s进行的操作,是否继续?", pszSrcFile);
     return MessageBox(NULL, szMessage, "确认", MB_YESNO | MB_ICONEXCLAMATION);
}
//—————————————————————————
ULONG __stdcall TClassFactory::AddRef()
{
     if(m_refcnt==0)
         nLocks++;
     m_refcnt++;
     return m_refcnt;
}
//—————————————————————————
ULONG __stdcall TClassFactory::Release()
{

     int nNewCnt = –m_refcnt;

     if(nNewCnt <= 0)
     {
         nLocks–;
         delete this;
     }
     return nNewCnt;
}
//—————————————————————————
HRESULT __stdcall TClassFactory::QueryInterface(REFIID dwIID, void **ppvObject)
{
     if(dwIID == IID_IUnknown)
         *ppvObject = static_cast<IUnknown*>(this);
     else
         if(dwIID == IID_IClassFactory)
             *ppvObject = static_cast<IClassFactory*>(this);
         else
             return E_NOINTERFACE;
     reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
     return S_OK;
}
//—————————————————————————
HRESULT __stdcall TClassFactory::CreateInstance(IUnknown* pUnkownOuter,
         REFIID riid, void** ppvObj)
{
     if(pUnkownOuter != NULL)
         return CLASS_E_NOAGGREGATION;
     TCopyHook *pObj = new TCopyHook;
     pObj->AddRef();
     HRESULT hr = pObj->QueryInterface(riid, ppvObj);
     pObj 文章

Read: 738

软件看门狗–别让你的程序没有响应

原作者姓名 陆其明
文章原始出处 http://hqtech.nease.net

正文
一.概述
一些重要的程 序,必须让它一直跑着;而且还要时时关心它的状态——不能让它出现死锁现象。当然,如果一个主程序会出现死锁,肯定是设计或者编程上的失误。我们首要做的 事是,把这个Bug揪出来。但如果时间紧迫,这个Bug又“飘忽不定”,那么,我们还是先写一个软件“看门狗”,暂时应一下急吧。

“看门狗”的需求描述:“看门狗”的运行不出现界面窗口,具有一定的隐蔽性;定时判断目标进程是否运行在当前系统中,如果没有则启动目标进程;判断目标进程是否“没有响应”,如果是则终止目标进程;如果目标进程“没有响应”的次数超过一定的数量,则将计算机系统重启。

二.预备知识
首 先要介绍两个主要的函数,能够判断目标进程是否“没有响应”。在User32.dll中(没有文档公开),Win2k/NT下的 IsHungAppWindow和Win9X下的IsHungThread;前者是以一个窗口句柄作为参数,后者是以线程ID作为参数。我们可以通过VC 开发工具的Depends查到这两个函数。
要使用这两个函数,我们必须先动态导入,如下:
if (m_hUser32 == NULL)
{
     m_hUser32 = GetModuleHandle("USER32.DLL");
}
if (m_hUser32)
{
     m_IsHungNT   = (HUNG_FUNNT) GetProcAddress(m_hUser32, "IsHungAppWindow");
     m_IsHung9X   = (HUNG_FUN9X) GetProcAddress(m_hUser32, "IsHungThread");
}
另外,还有如下知识点:
1.     如何让窗口隐藏(当然通过Windows任务管理器还是可以看到的)
在框架窗口类的PreCreateWindow中修改窗口风格,如下:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
     if( !CFrameWnd::PreCreateWindow(cs) )
         return FALSE;
     // TODO: Modify the Window class or styles here by modifying
     //   the CREATESTRUCT cs

     cs.dwExStyle |= WS_EX_TOOLWINDOW;   // Make invisible in taskbar
     cs.style       = WS_POPUP;           // Hide the main window

     return TRUE;
}
2.     如何让“看门狗”只运行一个进程
使用互斥量。在CWatchDogApp::InitInstance()中,执行如下代码:
bool CWatchDogApp::IsUniqueCopyInProc()
{
     m_Mutex = CreateMutex(NULL, TRUE, "System Watch Dog");
     if (GetLastError() == ERROR_ALREADY_EXISTS)
     {
         return false;
     }
     return true;
}
该函数如果返回false,说明已经有一个WatchDog进程在运行了,当前进程就没有必要再执行下去了。在InitInstance如下处理:
if (!IsUniqueCopyInProc())
return FALSE;
3.     如何判断当前操作系统类型
bool CWatchDogApp::IsWinNT()
{
     OSVERSIONINFO OSVersionInfo;
     OSVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
     GetVersionEx(&OSVersionInfo);
     if (OSVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT)
     {
         return true;
     }
     return false;
}
4.     如何自动重启计算机
在Win9x和Win2k/NT下,重启计算机的处理略有不同:
if (theApp.IsWinNT())
{
     // 在Win NT/2000下赋予关闭系统的权限
     static HANDLE hToken;
     static TOKEN_PRIVILEGES tp;
     static LUID luid;
::OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &hToken ) ;
     ::LookupPrivilegeValue( NULL, SE_SHUTDOWN_NAME, &luid );
     tp.PrivilegeCount            = 1;
     tp.Privileges[0].Luid        = luid;
     tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
     ::AdjustTokenPrivileges( hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL );
     return ::ExitWindowsEx(EWX_REBOOT | EWX_FORCE, 0);
}
else
{
     return ::ExitWindowsEx(EWX_REBOOT | EWX_FORCE, 0);
}
5.     如何启动、结束其他进程
启动进程用CreateProcess,终止进程用TerminateProcess。参考代码如下:
bool CWatchDogView::RunTheSysProc()
{
     char     szPath[MAX_PATH];
     GetModuleFileName(NULL, szPath, MAX_PATH);
     CString strPath = szPath;
     strPath = strPath.Left(strPath.ReverseFind(‘\’)) + "\HungDemo.exe";

     STARTUPINFO             StartInfo;
     PROCESS_INFORMATION     procStruct;
     memset(&StartInfo,0,sizeof(STARTUPINFO));
     StartInfo.cb = sizeof(STARTUPINFO);

     if (!::CreateProcess(
         (LPCTSTR) strPath,
         NULL,
         NULL,
         NULL,
         FALSE,
         NORMAL_PRIORITY_CLASS,
         NULL,
         NULL,
         &StartInfo,
         &procStruct))
         return false;
     return true;
}
需要提醒的是,TerminateProcess是在万不得已的情况下使用的,它不会进入进程使用的DLL的入口点通知“脱离”(Detaching)状态。有时候,这样做是很危险的(DLL内部的全局数据可能受影响较大)。
6.     如何让Win2k/NT自动登录
修 改注册表。在HKEY_LOCAL_MACHINE目录下的SoftwareMicrosoftWindows NT CurrentVersionWinLogon下的AutoAdminLogon(字符串型)设置成1,并在DefaultUserName设置默认登 录用户,DefaultPassword设置默认用户的密码。
7.     如何让Win2k/NT登录成功后直接执行你的程序(而不是默认的文件浏览器)
修改注册表。在注册表HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindows NT CurrentVersionWinlogonShell的值从原先的explorer.exe修改为自己程序的绝对路径。

三.功能演示(Win2k/NT下)
友 情提醒:开始演示之前,请先将你目前的工作保存。运行“看门狗”WatchDog;同时使用Ctrl+Alt+Del打开“Windows任务管理器”。 稍候片刻,可以看到目标程序HungDemo会被启动(这个程序模拟了“没有响应”)。然后,WatchDog发现这个程序“没有响应”,则把它杀掉,然 后重新启动一个新的HungDemo进程。如此的处理重复六次以后,系统会自动重启。

正文完
源程序:http://www.vchelp.net/ASP/ibr_upload/357.zip

Read: 776

钩子的应用: 程序运行监视

作者:Victor Chen
来自:C++ 爱好者
http://www.cppfans.com

——————————————————————————–

程序介绍:

利用这个程序:
1.可以监视在你的电脑运行的程序, 把在你的电脑运行过的程序的时间和名字记录下来;
2.可以阻止你规定的禁用程序的执行, 比如不让玩游戏。
3.这个程序需要加入注册表, 在系统启动时就运行, 达到监视的目的。注册表大概都不陌生,就是这里:
HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionRun

程序的记录格式:

2003-02-03 17:31:25 – [System Startup – Windows XP 5.01.2600]
2003-02-03 17:31:29 "CabinetWClass" -> "我的电脑"
2003-02-03 17:31:59 "Red Alert" -> "Red Alert" (关闭禁用程序)
2003-02-03 17:32:19 "扫雷" -> "扫雷" (关闭禁用程序)
2003-02-03 17:32:35 "OpusApp" -> "Microsoft Word"
2003-02-03 17:32:50 – [System Shutdown – 0 days, 0 hrs, 1 mins, 25 secs]

2003-02-03 17:35:37 – [System Startup – Windows 98 SE 4.10.2222]
2003-02-03 17:35:53 "扫雷" -> "扫雷" (关闭禁用程序)
2003-02-03 17:36:05 "CabinetWClass" -> ""
2003-02-03 17:36:31 "Red Alert" -> "Red Alert" (关闭禁用程序)
2003-02-03 17:36:56 "ExploreWClass" -> ""
2003-02-03 17:37:07 – [System Shutdown – 0 days, 0 hrs, 1 mins, 30 secs]

程序运行只需要3个文件:
hwhpapp.exe 可执行文件
hwhpdrv.dll 安装钩子的动态链接库
hwhpapp.cfg 禁用软件黑名单, 可用记事本修改
程序运行会自动产生记录文件:
hwhpapp.sys 可以用记事本打开看

程序原理:

一.钩子
利用 API 函数 SetWindowsHookEx() 安装一个全局钩子, 钩子类型为 WH_SHELL。
WH_SHELL 钩子可监视所有应用程序的主窗口创建或者关闭。
最典型的应用就是 Windows 的状态栏,当程序运行时,把主窗口的标题加入状态栏,程序退出时,从状态栏删除。
如果你截获这个钩子,可以做到禁止状态栏的显示,也可以自己作一个状态栏,或者作一个历史记录,把所有在你电脑曾经运行的程序记录下来。如果运行的程序不是你希望的,你可以直接关闭这个程序,达到禁止运行的目的。

二.动态链接库
由于钩子是全局的,必须把这个钩子定义到 .DLL 的动态链接库里,这就涉及到建立动态链接库。

三.共享内存
由于钩子是安装到系统里的,钩子的运行是在操作系统里面,因此这个钩子不能使用你的程序所定义的任何全局变量!
既 然如此,有什么办法解决呢?在本程序里利用共享内存技术,利用 API 函数 CreateFileMapping() 可创建共享内存,这个内存可以在任何运行的程序中使用,也就是说任何运行的 .EXE、.DLL 和其他程序都可以使用这块内存。在本程序中直接使用了 Victor 串口 VCL 控件里的 TSharedMemory 共享内存类。

四.记录文件和软件黑名单文件
把所有在你的电脑执行的程序记录保存在一个文本文件里, 因为扩展名为 .txt 很容易被发现,因此采用扩展名 .sys
软件黑名单文件保存在 .cfg 文件里,同样因为 .ini 文件很容易发现并且打开修改。
这两个文件都保存在与你的 .exe 文件的相同文件夹里,并且与 .exe 文件同名.

五.保证你的程序只能同时运行一个
如果你同时运行两个程序,记录文件就会乱套,所以必须保证只能运行一个。
当你的程序刚开始运行的时候,就是在 WinMain() 函数的最开始,就要判断是否已经运行了,如果已经运行,就直接退出。
判断的方法很简单,就是检查程序共享的内存是否存在,如果检查到共享的内存已经存在,那就是已经运行了。

六.程序隐身, 不能显示在任务栏和任务管理器里面
这个也很简单,只要在主程序的 Application->Run(); 前面加一句话:
SetWindowLong(Application->Handle, GWL_EXSTYLE, GetWindowLong(Application->Handle, GWL_EXSTYLE)|WS_EX_TOOLWINDOW);
就可以了。

——————————————————————————–

程序介绍
.DLL 文件:这是最关键的钩子的代码:

#include <vcl.h>
#include "yb_base.h" //Victor 串口控件里面的一个头文件

#define MYAPPMARK "VICTOR_APPMONI_20010612" //共享内存标志

class __export THookedProcs
{
    public:
        THookedProcs();
        ~THookedProcs();
        void WINAPI InitFuncs(void);
        void WINAPI UninitFuncs(void);
    private:
        HHOOK hThisHook; //保存钩子的句柄
        static LRESULT CALLBACK HookedShellProc(int nCode, WPARAM wParam, LPARAM lParam);
};

//定义共享的数据结构
typedef struct
{
   HHOOK hHook; //当前使用的 HOOK
//… 此处可增加其他共享的数据
} THookSharedData;

THookedProcs::THookedProcs()
{
   hThisHook = NULL;
}

THookedProcs::~THookedProcs()
{
    UninitFuncs();
}

void WINAPI THookedProcs::InitFuncs(void)
{
    UninitFuncs();
    hThisHook = SetWindowsHookEx(WH_SHELL, (HOOKPROC) HookedShellProc, HInstance, 0);
    TSharedMemory AppMem(MYAPPMARK,4096); //在 EXE 文件里共享的内存
    THookSharedData *HookSharedData = ((THookSharedData*)(AppMem.AppInfo->Data)); //共享的数据
    HookSharedData->hHook = hThisHook; //把 hThisHook 保存到共享内存里
}

void WINAPI THookedProcs::UninitFuncs(void)
{
    if(hThisHook)
    {
        UnhookWindowsHookEx(hThisHook);
        hThisHook = NULL;
    }
}

LRESULT CALLBACK THookedProcs::HookedShellProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    TSharedMemory AppMem(MYAPPMARK, 4096); //使用在 .EXE 文件里共享的内存
    if(AppMem.Valid)if(AppMem.Exists) //如果共享内存存在
    {
        HWND hMainWnd = AppMem.AppInfo->hMainForm;

        if(hMainWnd)
        {
            if(nCode==HSHELL_WINDOWCREATED)
            {
                PostMessage(hMainWnd, WM_USERCMD, UC_WINHOOK, wParam);
            }
        }
    }
    //在 Hook 里无法调用 hThisHook, 必须用共享内存里面的 hHook
    THookSharedData *HookSharedData = ((THookSharedData*)(AppMem.AppInfo->Data)); //共享的数据
    return CallNextHookEx(HookSharedData->hHook, nCode, wParam, lParam);
}

——————————————————————————–

EXE文件主程序的代码:

WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR lpCmdLine, int)
{
    if(!AppMem.Valid)
    {
        return 1;
    }
    if(AppMem.Exists) //已经存在 (程序以前已经运行过了, 并且正在运行)
    {
        if(stricmp(lpCmdLine, "/SHOW")==0) //如果监测到命令行参数 /SHOW 就显示出已经运行的程序的主窗口
        {
            PostMessage(AppMem.AppInfo->hMainForm, WM_USERCMD, UC_SHOWWIN, 0);
        }
        return 0;
    }
    AppMem.ClearBuffer();

    try
    {
        Application->Initialize();

Read: 635