windbg分析栈溢出

  本文根据《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); 
}

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Windbg是Windows平台上的一款强大的调试工具,可以用于分析dump文件。当一个程序崩溃或异常退出时,系统会生成一个dump文件,其中包含了程序在崩溃前的内存状态、寄存器的值以及调用等信息。通过分析dump文件,可以帮助我们确定程序崩溃的原因。 使用Windbg分析dump文件的步骤如下: 首先,打开Windbg并选择“File”菜单中的“Open Crash Dump”,然后选择要分析的dump文件。打开dump文件后,Windbg会加载其中的调试信息,包括程序、模块、符号等。 在Windbg的命令窗口中,可以输入一系列的命令来分析dump文件。其中一些常用的命令如下: 1. "!analyze -v":分析dump文件并提供详细的分析报告,报告中包含了崩溃的原因和相关的线程堆信息。 2. "kb":显示当前线程的调用,可以根据调用信息来查找崩溃的位置。 3. "lm":显示加载的模块信息,可以查看程序中加载的模块和其对应的版本号。 4. ".exr -1":显示当前异常的记录,包括异常的类型和相关的寄存器的值。 5. ".reload /f":强制重新加载符号文件,以确保符号信息的准确性。 通过分析命令的执行结果,我们可以逐步追踪问题并找到程序崩溃的原因。在分析过程中,还可以使用其他的命令来查看内存的内容、寄存器的值以及线程的信息等。 总的来说,Windbg是一款功能强大的调试工具,通过分析dump文件可以帮助我们深入了解程序崩溃的原因,从而进行相应的调试和修复。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值