本文根据《windows高级调试》5.2.2章节提供的示例进行的实验,在win10平台,使用该书提供的bin文件进行调试,因平台不一样在实验过程中发现跟书上有些不同,本章书上的一些调试方法的正确性有待商榷(可以留言讨论)。
调试准备
(1)代码,见文章末尾;
(2)将以下注册表保存到.reg后缀的文件,点击添加到注册表;
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Test]
"Value1"=dword:00000001
"Value2"=dword:00000002
(3)下载《windows高级调试》的随书附件,或者自己编译下面的代码,我直接使用随书附件的bin和符号表。
调试步骤
首先使用windbg打开05async.exe,启动后按g继续执行,我们先后键入Test和5000会得到正确的结果,不会出现异常,如下:
我们继续键入Test,2000后,等待几秒钟,程序就会崩溃,程序界面:
windbg调试器界面:
此时查看堆栈信息
发现程序在DisplayError中崩溃了,具体在什么位置暂时不得而知,我们可以看下出现崩溃的地址,反编译下:
想要查看程序各个模块地址范围,可以使用lm命令,如下:
可以发现,该段代码地址不在程序的任何模块地址上,可见它是一个错误的地址,我们可以将当前栈顶指针esp的一些值打印出来查看:
我们可以看到,有个地址是在程序模块地址里面的,应该是DisplayError函数中调用的某个函数的返回地址,可以把DisplayError函数反汇编下:
该地址正好是DisplayError调完Sleep的返回地址,这个地址还在栈上面,证明函数没有返回,也就是说程序在DisplayError函数调用的Sleep函数中崩溃了,我们接着反汇编Sleep函数,如下:
Sleep函数内只调用了一个SleepEx函数,SleepEx的返回地址应该是0x742270ef,但实际却为0x5a3a11,应该是某个线程将该值给改了,我们需要把堆栈上存储该值的地址找到,我们看到在调用SleepEx时push了2个参数,SleepEx return后esp应该有执行+8的操作,所以SleepEx的返回值应在esp-8的位置,我们来看下:
第一个地址正好是0x5a3a10,与0x5a3a11非常接近,0x00dfd00就是存储SleepEx返回地址的栈地址,此时0x00dfd00应该存的是0x742270ef但实际却是 0x5a3a10,所以需要在0x00dfd00上打一个硬件断点,到底哪个线程往这个地方写入了数据。重新启动该程序,在DisplayError函数打个断点,在DisplayError中断之后单步到SleepEx函数,此时查看0x00dfd00的内容,是正确的,然后在 0x00dfd00上打一个硬件断点:ba w4 000dfd00,继续执行,执行中断,堆栈如下:
往该地址写入的却是另外一个线程,可以很快找到对应的代码段,为此找到了栈被破坏的根源,分析over。
与书中异同
1.我在调试的时候,程序崩溃,我是能看到堆栈的,书中是看不到,然后通过打印栈的信息,通过ln命令一步步查找的,其实我这边也可以那样找,但是没必要,kb命令就已经显示出我想看的东西了;
2.以下这段描述,我认为是错误的,首先我们只知道程序在DisplayError中崩溃,但是不知道是在哪个函数执行完后pop给eip的地址有问题,这需要我们去分析,最终分析结果esp-8其实是SleepEx的返回地址,不是Sleep,导致作者后面的一连串说法都是错的;
3.对于硬件断点,我们应该打在esp-8的这个地址上,按照书上给出的地址应该是0x0007fcf8而不是0x0006fcf8,这个不知道是书的笔误还是什么;
4.如果有不同意见,欢迎留言。
本程序的代码
/*++
Copyright (c) Advanced Windows Debugging (ISBN 0321374460) from Addison-Wesley Professional. All rights reserved.
THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
PURPOSE.
--*/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#define MAX_VALUE_NAME 256
#define MAX_REG_VALUE_COUNT 2
#define MAX_LEN 256
#define ENUM_TIME_LEN 3000
class CRegValue
{
public:
CRegValue() : m_pwszName(NULL), m_dwValue(0) {};
~CRegValue()
{
if(m_pwszName)
{
delete[] m_pwszName;
m_pwszName=NULL;
}
}
const WCHAR* GetName() const { return m_pwszName; }
DWORD GetValue() const { return m_dwValue; }
VOID SetProperties(WCHAR* pwszName, DWORD dwValue)
{
m_pwszName=pwszName;
m_dwValue = dwValue;
}
private:
WCHAR* m_pwszName;
DWORD m_dwValue ;
} ;
typedef struct
{
CRegValue* pRegValues;
DWORD dwRegValuesCount;
HKEY hKey;
} CRegEnumData;
BOOL RegEnum(WCHAR* pwszPath, DWORD dwTimeout);
HANDLE RegEnumAsync(CRegEnumData* pRegData);
DWORD WINAPI RegThreadProc(LPVOID lpParameter);
VOID DisplayError(WCHAR* pwszPath, DWORD dwType, DWORD dwTimeout, BOOL bFullError);
int __cdecl wmain (int argc, WCHAR* args[])
{
WCHAR wszRegPath[MAX_LEN] ;
int iTimeout=0;
BOOL bEnd = FALSE;
while(!bEnd)
{
wprintf(L"Enter registry key path (\"quit\" to quit): ");
wscanf(L"%s", wszRegPath, MAX_LEN);
if(!_wcsicmp(wszRegPath, L"quit"))
{
bEnd=TRUE;
continue;
}
wprintf(L"Enter timeout for enumeration: ");
wscanf(L"%d", &iTimeout);
if(iTimeout==0)
{
wprintf(L"Invalid timeout specified...\n");
bEnd=TRUE;
}
else
{
//
// Enumerate
//
if ( RegEnum(wszRegPath, iTimeout) == FALSE )
{
DisplayError(wszRegPath, REG_DWORD, iTimeout, TRUE);
}
}
}
return 0;
}
BOOL RegEnum(WCHAR* pwszPath, DWORD dwTimeout)
{
CRegValue regValues[MAX_REG_VALUE_COUNT];
CRegEnumData* pData=NULL;
BOOL bRet=FALSE;
HANDLE hWaitHandle = NULL;
DWORD dwRet=0;
HKEY hKey=NULL;
LONG lRes=0;
if(!pwszPath)
{
return FALSE;
}
lRes=RegOpenKeyEx(HKEY_CURRENT_USER, pwszPath, 0, KEY_QUERY_VALUE, &hKey);
if(lRes==ERROR_SUCCESS)
{
pData=new CRegEnumData;
if(pData)
{
pData->pRegValues=regValues;
pData->dwRegValuesCount=MAX_REG_VALUE_COUNT;
pData->hKey=hKey;
hWaitHandle=RegEnumAsync(pData);
if(hWaitHandle!=NULL)
{
dwRet=WaitForSingleObject(hWaitHandle, dwTimeout);
if(dwRet==WAIT_TIMEOUT)
{
wprintf(L"Timeout occured...\n");
}
else
{
for(int i=0; i<MAX_REG_VALUE_COUNT; i++)
{
if(regValues[i].GetName()!=NULL)
{
wprintf(L"Value %d Name: %s\n", i+1, regValues[i].GetName());
wprintf(L"Value %d Data: %d\n", i+1, regValues[i].GetValue());
bRet=TRUE;
}
else
{
break;
}
}
}
CloseHandle(hWaitHandle);
}
else
{
wprintf(L"Async registry enumeration failed...");
}
}
}
return bRet;
}
HANDLE RegEnumAsync(CRegEnumData* pRegData)
{
return CreateThread(NULL, 0, RegThreadProc, pRegData, 0, NULL);
}
DWORD WINAPI RegThreadProc(LPVOID lpParameter)
{
DWORD dwIndex=0;
BOOL bRet=FALSE;
Sleep(ENUM_TIME_LEN);
CRegEnumData* pRegData=reinterpret_cast<CRegEnumData*>(lpParameter);
while(!bRet && dwIndex<pRegData->dwRegValuesCount)
{
DWORD dwType=0;
LONG lRes=0;
DWORD dwData=0; // Only get DWORD values
DWORD dwDataSize=sizeof(DWORD);
DWORD dwNameLen=MAX_VALUE_NAME;
WCHAR* pwszValueName=new WCHAR[MAX_VALUE_NAME];
if(pwszValueName)
{
lRes=RegEnumValue(pRegData->hKey, dwIndex, pwszValueName, &dwNameLen, NULL, &dwType, (LPBYTE)&dwData, &dwDataSize);
if(lRes==ERROR_SUCCESS && dwType==REG_DWORD)
{
pRegData->pRegValues[dwIndex].SetProperties(pwszValueName, dwData);
dwIndex++;
}
else
{
delete[] pwszValueName;
bRet=TRUE;
}
}
else
{
bRet=TRUE;
}
}
RegCloseKey(pRegData->hKey);
delete pRegData;
return 0 ;
}
VOID DisplayError(WCHAR* pwszPath, DWORD dwType, DWORD dwTimeout, BOOL bFullError)
{
if(bFullError)
{
if(dwType==REG_DWORD)
{
wprintf(L"Error enumerating DWORDS in HKEY_CURRENT_USER\\%s within %d ms!\n", pwszPath, dwTimeout);
}
else
{
wprintf(L"Error enumerating <unknown type> in HKEY_CURRENT_USER\\%s within %d ms!\n", pwszPath, dwTimeout);
}
}
else
{
wprintf(L"Error enumerating key values!\n");
}
//
// Simulate wait for user confirmation
//
Sleep(6000);
}