首页 > C/C++开发工具专区 > VC技术 > 使用 Windows 钩子获取丢失的密码
2009
08-26

使用 Windows 钩子获取丢失的密码

作者:Brian Friesen
编译:VCKBASE


原文出处:PasswordSpy – Retrieving lost passwords using Windows hooks

下载源代码


关键字:钩子 进程间通讯 单实例 操作系统版本


简介


  几年前我在CodeGuru 上下载了一个叫 Eureka的程序,如果你忘记了密码,你可以用程序把密码“取”回来。它不是密码破解程序,相反,它利用了一个Windows的安全漏洞来拷贝另外一个运行中的程序的密码。我对这个程序很感兴趣,决定写一个自己的版本。后来,Windows 2000 发布,我失望地发现,微软修补了那个漏洞,这样一来那个程序在Windows 2000上也就不灵了。经过一番尝试,我终于找到一个方法拷贝任何在32-位Windows 系统上运行程序的密码。


本文例子程序:



使用方法:


  PasswordSpy程序的使用非常简单。你只要运行包含忘记了密码的程序,再运行PasswordSpy。然后将放大镜拖动到密码输入域上,PasswordSpy则会将密码显示出来。PasswordSpy程序并没有恶意,开发它的目的只是想把密码找回来,该程序在Win95/98/ME and WinNT/2K/XP/Windows 2003 上测试通过。


功能说明:


除了PasswordSpy本身的用途之外,它还示范了一些有用和有趣的代码:



  • 单实例应用——如果用户启动了PasswordSpy的第二个实例,系统会找到第一个实例,并在所有窗口的最前端显示出PasswordSpy界面;
  • Always on top——总是在最顶层,只用一行代码就可以在你的程序中启动和禁用这个功能;
  • 进程间通讯——PasswordSpy 使用几种形式的IPC,包括WM_COPYDATA消息以及内存映射文件;
  • 设置窗口钩子——为了在Windows 2000/Windows XP中吸取密码,你得使用在远程进程中置入一个钩子。

代码实现细节:


  到目前为止,PasswordSpy 程序最有趣的部分其实是使用 SetWindowsHookEx API.函数设置Windows 钩子。利用该函数你可以将钩子安装到操作系统中或者某个特定的进程中。钩子的种类有很多种,每种钩子作用也不尽相同,用来监视特定的一组事件。当某一类事件发生时,钩子代码被调用。PasswordSpy使用WH_GETMESSAGE钩子,它监视对GetMessage 和PeekMessage 的调用。关于钩子更详细的信息请参考MSDN库的SetWindowsHookEx。
  我在网上、书本以及MSDN上找到几个有关钩子的例子,每个都至少有一个Bug。本文我用自己的方案解决了这些问题。使用Windows钩子最难的部分是妥善存储钩子句柄。设置钩子之前,你需要做两件事情:



  1. 包含钩子函数的DLL;
  2. 钩子要作用的线程ID;

  现在假设进程A要在进程B中设置钩子。钩子注入进程B以后,该钩子句柄被返回到进程A,同时DLL被映射到进程B的地址空间。当进程B中某个钩子事件发生时,你的钩子代码便在进程B中被调用。(应该注意的一点是你的钩子代码是从远程进程空被调用的!钩子代码里,如果调用GetCurrentProcessId 函数,那么获得的是钩子进程的ID,而不是设置钩子的进程ID)。你可以在钩子代码中做任何想做的事情,但是在钩子代码退出之前,你应该调用CallNextHookEx。如果这个函数调用失败,其它任何已经安装的钩子无法获得消息。因为CallNextHookEx需要该钩子句柄,但我们当前是在进程B中,而句柄被返回到进程A,因此,此时需要进程间通讯来传输钩子句柄。
通常解决这个问题的方法是通过在DLL中创建一个“共享”内存区:

#pragma data_seg(“Shared”)
HHOOK g_hHook = NULL;
#pragma data_seg()
#pragma comment(linker, “/section:Shared,rws”)
  其实,此处创建了一个变量,所有已经被加载的DLL实例共享这个变量。但这个方法有几个问题,第一,有些编译器不支持这个选项,第二,如果微软要是在未来的Windows中改变“共享”内存区的工作模式该怎么办?这意味着这个技术不再适用。此外,这个方法不是线程同步的,由于有多个线程要访问这个变量,线程同步就变得很重要。为了解决这些问题,我在进程间通讯(IPC)过程中使用了内存映射文件,并利用互斥机制进程线程同步。我将这方面的代码全部封装在一个类中,这个类就是 CIPC。通过使用内存映射文件,我解决了特定编译器选项问题,不用再考虑编译器是否支持共享内存区;仅仅借助Win32 API调用以及用MMFs来提供多进程间共享数据的机制就可以解决问题,在未来的Windows版本中,这些东西是不太可能改变的。互斥保证了线程存取的同步:
//***********************************************
// IPC.h
//***********************************************
#ifndef _IPC_H_
#define _IPC_H_

#define IPC_SHARED_MMF _T(“{34F673E0-878F-11D5-B98A-00B0D07B8C7C}”)
#define IPC_MUTEX _T(“{34F673E1-878F-11D5-B98A-00B0D07B8C7C}”)

// 使用内存映射文件进行进程间通讯的封装类
class CIPC
{
public:
CIPC();
virtual ~CIPC();

bool CreateIPCMMF(void);
bool OpenIPCMMF(void);
void CloseIPCMMF(void);

bool IsOpen(void) const {return (m_hFileMap != NULL);}

bool ReadIPCMMF(LPBYTE pBuf, DWORD &dwBufSize);
bool WriteIPCMMF(const LPBYTE pBuf,
const DWORD dwBufSize);

bool Lock(void);
void Unlock(void);

protected:
HANDLE m_hFileMap;
HANDLE m_hMutex;
};

#endif

//***********************************************
// IPC.cpp
//***********************************************
#include “IPC.h”

//***********************************************
CIPC::CIPC() : m_hFileMap(NULL), m_hMutex(NULL)
{
}

//***********************************************
CIPC::~CIPC()
{
CloseIPCMMF();
Unlock();
}

//***********************************************
bool CIPC::CreateIPCMMF(void)
{
bool bCreated = false;

try
{
if(m_hFileMap != NULL)
return false; // Already created

// Create an in-memory 4KB memory mapped
// file to share data
m_hFileMap = CreateFileMapping((HANDLE)0xFFFFFFFF,
NULL,
PAGE_READWRITE,
0,
4096,
IPC_SHARED_MMF);
if(m_hFileMap != NULL)
bCreated = true;
}
catch(…) {}

return bCreated;
}

//***********************************************
bool CIPC::OpenIPCMMF(void)
{
bool bOpened = false;

try
{
if(m_hFileMap != NULL)
return true; // Already opened

m_hFileMap =
OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE,
FALSE,
IPC_SHARED_MMF);
if(m_hFileMap != NULL)
bOpened = true;
}
catch(…) {}

return bOpened;
}

//***********************************************
void CIPC::CloseIPCMMF(void)
{
try
{
if(m_hFileMap != NULL)
CloseHandle(m_hFileMap), m_hFileMap = NULL;
}
catch(…) {}
}

//***********************************************
bool CIPC::ReadIPCMMF(LPBYTE pBuf, DWORD &dwBufSize)
{
_ASSERTE(pBuf);

bool bSuccess = true;

try
{
if(m_hFileMap == NULL)
return false;

DWORD dwBaseMMF = (DWORD)MapViewOfFile(m_hFileMap,
FILE_MAP_READ | FILE_MAP_WRITE,
0, 0, 0);
_ASSERTE(dwBaseMMF);

// The first DWORD in the MMF contains the size of the data
DWORD dwSizeofInBuf = dwBufSize;
CopyMemory(&dwBufSize, (LPVOID)dwBaseMMF, sizeof(DWORD));

if(dwSizeofInBuf != 0)
{
if(dwBufSize > dwSizeofInBuf)
bSuccess = false;
else
CopyMemory(pBuf,
(LPVOID)(dwBaseMMF + sizeof(DWORD)),
dwBufSize);
}

UnmapViewOfFile((LPVOID)dwBaseMMF);
}
catch(…) {}

return bSuccess;
}

//***********************************************
bool CIPC::WriteIPCMMF(const LPBYTE pBuf, const DWORD dwBufSize)
{
_ASSERTE(pBuf);

bool bSuccess = true;

try
{
if(m_hFileMap == NULL)
return false;

DWORD dwBaseMMF = (DWORD)MapViewOfFile(m_hFileMap,
FILE_MAP_READ | FILE_MAP_WRITE,
0, 0, 0);
_ASSERTE(dwBaseMMF);

// The first DWORD in the MMF contains the size of the data
CopyMemory((LPVOID)dwBaseMMF, &dwBufSize, sizeof(DWORD));
CopyMemory((LPVOID)(dwBaseMMF + sizeof(DWORD)),
pBuf,
dwBufSize);

UnmapViewOfFile((LPVOID)dwBaseMMF);
}
catch(…) {}

return bSuccess;
}

//***********************************************
bool CIPC::Lock(void)
{
bool bLocked = false;

try
{
// First get the handle to the mutex
m_hMutex = CreateMutex(NULL, FALSE, IPC_MUTEX);
if(m_hMutex != NULL)
{
// Wait to get the lock on the mutex
if(WaitForSingleObject(m_hMutex, INFINITE) ==
WAIT_OBJECT_0)
bLocked = true;
}
}
catch(…) {}

return bLocked;
}

//***********************************************
void CIPC::Unlock(void)
{
try
{
if(m_hMutex != NULL)
{
ReleaseMutex(m_hMutex);
CloseHandle(m_hMutex);
m_hMutex = NULL;
}
}
catch(…) {}
}

PasswordSpy的反向工程

  上述内容是关于通过编程途径从其它程序“拷贝”密码,下面的内容我们将讨论:如何防止PasswordSpy这样的程序从你的程序中获取密码信息?如果你的应用程序存储并显示密码,你有特别关注安全问题,你可能会考虑让应用堤防类似PasswordSpy这样的程序。
  因为PasswordSpy可以拷贝其它程序的密码,为了防范这样的程序,你首先就决不能将真正的密码信息显示出来。最佳办法是在密码框中显示假密码。这样一来,如果有人使用PasswordSpy 获取密码,那他们得到的是一个假密码。本文附带的程序AntiPwdSpy示范了如何保护密码框控件免遭“侦测”。AntiPwdSpy实际上类似于Windows NT服务对话框和Windows NT 用户管理程序。
  防范诸如PasswordSpy这类程序的其它方法还可以通过截获WM_GETTEXT消息来实现。但使用虚假密码的方法有额外的好处,用假密码替代真密码,仅检查密码控件是无法确定密码的长度的。如果某个程序显示密码控件中的文本“***”,那么就可以知道密码是三位长度。这样的信息肯定危及密码的安全。但是,如果将密码控件中的信息显示成“**************”便可以有效地保护密码,微软的很多程序产品就是这么做的。


参考资料



使用 Windows 钩子获取丢失的密码》有 2 条评论

  1. coolker 说:

    作者:Brian Friesen
    编译:VCKBASE

    原文出处:PasswordSpy – Retrieving lost passwords using Windows hooks

    下载源代码

    关键字:钩子 进程间通讯 单实例 操作系统版本

    简介

      几年前我在CodeGuru 上下载了一个叫 Eureka的程序,如果你忘记了密码,你可以用程序把密码“取”回来。它不是密码破解程序,相反,它利用了一个Windows的安全漏洞来拷贝另外一个运行中的程序的密码。我对这个程序很感兴趣,决定写一个自己的版本。后来,Windows 2000 发布,我失望地发现,微软修补了那个漏洞,这样一来那个程序在Windows 2000上也就不灵了。经过一番尝试,我终于找到一个方法拷贝任何在32-位Windows 系统上运行程序的密码。

    本文例子程序:

    http://www.vckbase.com/document/journal/vckbase55/images/pwdspy.gif','Image‘);” onmouseover=”this.style.cursor=’hand’” onmouseout=”this.style.cursor=”” src=”http://www.vckbase.com/document/journal/vckbase55/images/pwdspy.gif” height=”410″ width=”670″ border=”0″ />

    使用方法:

      PasswordSpy程序的使用非常简单。你只要运行包含忘记了密码的程序,再运行PasswordSpy。然后将放大镜拖动到密码输入域上,PasswordSpy则会将密码显示出来。PasswordSpy程序并没有恶意,开发它的目的只是想把密码找回来,该程序在Win95/98/ME and WinNT/2K/XP/Windows 2003 上测试通过。

    功能说明:

    除了PasswordSpy本身的用途之外,它还示范了一些有用和有趣的代码:

    • 单实例应用——如果用户启动了PasswordSpy的第二个实例,系统会找到第一个实例,并在所有窗口的最前端显示出PasswordSpy界面;
    • Always on top——总是在最顶层,只用一行代码就可以在你的程序中启动和禁用这个功能;
    • 进程间通讯——PasswordSpy 使用几种形式的IPC,包括WM_COPYDATA消息以及内存映射文件;
    • 设置窗口钩子——为了在Windows 2000/Windows XP中吸取密码,你得使用在远程进程中置入一个钩子。

    代码实现细节:

      到目前为止,PasswordSpy 程序最有趣的部分其实是使用 SetWindowsHookEx API.函数设置Windows 钩子。利用该函数你可以将钩子安装到操作系统中或者某个特定的进程中。钩子的种类有很多种,每种钩子作用也不尽相同,用来监视特定的一组事件。当某一类事件发生时,钩子代码被调用。PasswordSpy使用WH_GETMESSAGE钩子,它监视对GetMessage 和PeekMessage 的调用。关于钩子更详细的信息请参考MSDN库的SetWindowsHookEx。
      我在网上、书本以及MSDN上找到几个有关钩子的例子,每个都至少有一个Bug。本文我用自己的方案解决了这些问题。使用Windows钩子最难的部分是妥善存储钩子句柄。设置钩子之前,你需要做两件事情:

    1. 包含钩子函数的DLL;
    2. 钩子要作用的线程ID;

      现在假设进程A要在进程B中设置钩子。钩子注入进程B以后,该钩子句柄被返回到进程A,同时DLL被映射到进程B的地址空间。当进程B中某个钩子事件发生时,你的钩子代码便在进程B中被调用。(应该注意的一点是你的钩子代码是从远程进程空被调用的!钩子代码里,如果调用GetCurrentProcessId 函数,那么获得的是钩子进程的ID,而不是设置钩子的进程ID)。你可以在钩子代码中做任何想做的事情,但是在钩子代码退出之前,你应该调用CallNextHookEx。如果这个函数调用失败,其它任何已经安装的钩子无法获得消息。因为CallNextHookEx需要该钩子句柄,但我们当前是在进程B中,而句柄被返回到进程A,因此,此时需要进程间通讯来传输钩子句柄。
    通常解决这个问题的方法是通过在DLL中创建一个“共享”内存区:

    #pragma data_seg(“Shared”)
    HHOOK g_hHook = NULL;
    #pragma data_seg()
    #pragma comment(linker, “/section:Shared,rws”)
    

      其实,此处创建了一个变量,所有已经被加载的DLL实例共享这个变量。但这个方法有几个问题,第一,有些编译器不支持这个选项,第二,如果微软要是在未来的Windows中改变“共享”内存区的工作模式该怎么办?这意味着这个技术不再适用。此外,这个方法不是线程同步的,由于有多个线程要访问这个变量,线程同步就变得很重要。为了解决这些问题,我在进程间通讯(IPC)过程中使用了内存映射文件,并利用互斥机制进程线程同步。我将这方面的代码全部封装在一个类中,这个类就是 CIPC。通过使用内存映射文件,我解决了特定编译器选项问题,不用再考虑编译器是否支持共享内存区;仅仅借助Win32 API调用以及用MMFs来提供多进程间共享数据的机制就可以解决问题,在未来的Windows版本中,这些东西是不太可能改变的。互斥保证了线程存取的同步:

    //***********************************************
    // IPC.h
    //***********************************************
    #ifndef _IPC_H_
    #define _IPC_H_
    
    #define IPC_SHARED_MMF  _T(“{34F673E0-878F-11D5-B98A-00B0D07B8C7C}”)
    #define IPC_MUTEX       _T(“{34F673E1-878F-11D5-B98A-00B0D07B8C7C}”)
    
    // 使用内存映射文件进行进程间通讯的封装类
    class CIPC
    {
    public:
      CIPC();
      virtual ~CIPC();
    
      bool CreateIPCMMF(void);
      bool OpenIPCMMF(void);
      void CloseIPCMMF(void);
    
      bool IsOpen(void) const {return (m_hFileMap != NULL);}
    
      bool ReadIPCMMF(LPBYTE pBuf, DWORD &dwBufSize);
      bool WriteIPCMMF(const LPBYTE pBuf,
                       const DWORD dwBufSize);
    
      bool Lock(void);
      void Unlock(void);
    
    protected:
      HANDLE m_hFileMap;
      HANDLE m_hMutex;
    };
    
    #endif
    
    //***********************************************
    // IPC.cpp
    //***********************************************
    #include “IPC.h”
    
    //***********************************************
    CIPC::CIPC() : m_hFileMap(NULL), m_hMutex(NULL)
    {
    }
    
    //***********************************************
    CIPC::~CIPC()
    {
        CloseIPCMMF();
        Unlock();
    }
    
    //***********************************************
    bool CIPC::CreateIPCMMF(void)
    {
      bool bCreated = false;
    
      try
      {
         if(m_hFileMap != NULL)
            return false;    // Already created
    
         // Create an in-memory 4KB memory mapped
         // file to share data
         m_hFileMap = CreateFileMapping((HANDLE)0xFFFFFFFF,
             NULL,
             PAGE_READWRITE,
             0,
             4096,
             IPC_SHARED_MMF);
         if(m_hFileMap != NULL)
            bCreated = true;
      }
      catch(…) {}
    
      return bCreated;
    }
    
    //***********************************************
    bool CIPC::OpenIPCMMF(void)
    {
        bool bOpened = false;
    
        try
        {
            if(m_hFileMap != NULL)
                return true;    // Already opened
    
            m_hFileMap =
              OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE,
                FALSE,
                IPC_SHARED_MMF);
            if(m_hFileMap != NULL)
                bOpened = true;
        }
        catch(…) {}
    
        return bOpened;
    }
    
    //***********************************************
    void CIPC::CloseIPCMMF(void)
    {
        try
        {
            if(m_hFileMap != NULL)
                CloseHandle(m_hFileMap), m_hFileMap = NULL;
        }
        catch(…) {}
    }
    
    //***********************************************
    bool CIPC::ReadIPCMMF(LPBYTE pBuf, DWORD &dwBufSize)
    {
      _ASSERTE(pBuf);
    
      bool bSuccess = true;
    
      try
      {
         if(m_hFileMap == NULL)
             return false;
    
         DWORD dwBaseMMF = (DWORD)MapViewOfFile(m_hFileMap,
                    FILE_MAP_READ | FILE_MAP_WRITE,
             0, 0, 0);
         _ASSERTE(dwBaseMMF);
    
         // The first DWORD in the MMF contains the size of the data
         DWORD dwSizeofInBuf = dwBufSize;
         CopyMemory(&dwBufSize, (LPVOID)dwBaseMMF, sizeof(DWORD));
    
         if(dwSizeofInBuf != 0)
         {
             if(dwBufSize > dwSizeofInBuf)
                 bSuccess = false;
             else
                  CopyMemory(pBuf,
                      (LPVOID)(dwBaseMMF + sizeof(DWORD)),
                      dwBufSize);
         }
    
         UnmapViewOfFile((LPVOID)dwBaseMMF);
      }
      catch(…) {}
    
      return bSuccess;
    }
    
    //***********************************************
    bool CIPC::WriteIPCMMF(const LPBYTE pBuf, const DWORD dwBufSize)
    {
        _ASSERTE(pBuf);
    
        bool bSuccess = true;
    
        try
        {
            if(m_hFileMap == NULL)
                return false;
    
            DWORD dwBaseMMF = (DWORD)MapViewOfFile(m_hFileMap,
                FILE_MAP_READ | FILE_MAP_WRITE,
                0, 0, 0);
            _ASSERTE(dwBaseMMF);
    
            // The first DWORD in the MMF contains the size of the data
            CopyMemory((LPVOID)dwBaseMMF, &dwBufSize, sizeof(DWORD));
            CopyMemory((LPVOID)(dwBaseMMF + sizeof(DWORD)),
                                pBuf,
                                dwBufSize);
    
            UnmapViewOfFile((LPVOID)dwBaseMMF);
        }
        catch(…) {}
    
        return bSuccess;
    }
    
    //***********************************************
    bool CIPC::Lock(void)
    {
        bool bLocked = false;
    
        try
        {
            // First get the handle to the mutex
            m_hMutex = CreateMutex(NULL, FALSE, IPC_MUTEX);
            if(m_hMutex != NULL)
            {
                // Wait to get the lock on the mutex
                if(WaitForSingleObject(m_hMutex, INFINITE) ==
                                       WAIT_OBJECT_0)
                    bLocked = true;
            }
        }
        catch(…) {}
    
        return bLocked;
    }
    
    //***********************************************
    void CIPC::Unlock(void)
    {
        try
        {
            if(m_hMutex != NULL)
            {
                ReleaseMutex(m_hMutex);
                CloseHandle(m_hMutex);
                m_hMutex = NULL;
            }
        }
        catch(…) {}
    }
    

    PasswordSpy的反向工程

      上述内容是关于通过编程途径从其它程序“拷贝”密码,下面的内容我们将讨论:如何防止PasswordSpy这样的程序从你的程序中获取密码信息?如果你的应用程序存储并显示密码,你有特别关注安全问题,你可能会考虑让应用堤防类似PasswordSpy这样的程序。
      因为PasswordSpy可以拷贝其它程序的密码,为了防范这样的程序,你首先就决不能将真正的密码信息显示出来。最佳办法是在密码框中显示假密码。这样一来,如果有人使用PasswordSpy 获取密码,那他们得到的是一个假密码。本文附带的程序AntiPwdSpy示范了如何保护密码框控件免遭“侦测”。AntiPwdSpy实际上类似于Windows NT服务对话框和Windows NT 用户管理程序。
      防范诸如PasswordSpy这类程序的其它方法还可以通过截获WM_GETTEXT消息来实现。但使用虚假密码的方法有额外的好处,用假密码替代真密码,仅检查密码控件是无法确定密码的长度的。如果某个程序显示密码控件中的文本“***”,那么就可以知道密码是三位长度。这样的信息肯定危及密码的安全。但是,如果将密码控件中的信息显示成“**************”便可以有效地保护密码,微软的很多程序产品就是这么做的。

    参考资料

留下一个回复