日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

windbg调试堆破坏

發(fā)布時間:2024/4/11 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 windbg调试堆破坏 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

堆破壞

所謂的堆破壞,是說沒控制好自己的指針,把不屬于你分配的那塊內(nèi)存給寫覆蓋了。這塊內(nèi)存可能是你程序的數(shù)據(jù),也可能是堆的管理結(jié)構(gòu)。那么這個會導(dǎo)致怎樣的后果呢?可能的情況我們來yy下

  • 把程序里的計(jì)算結(jié)果覆蓋了,這也許會讓你重復(fù)看了N次代碼,校驗(yàn)了N次計(jì)算邏輯也搞不明白為何計(jì)算結(jié)果還是有問題
  • 堆管理結(jié)構(gòu)被破壞了,new/delete,或者malloc/free操作失敗
  • 等等等等~
  • 堆破壞較為理想的情況是被修改的數(shù)據(jù)會馬上導(dǎo)致程序crash,最差的情況是你的堆數(shù)據(jù)莫名其妙在今天被改了,但明天才crash。這個時候在去分析crash,就如我們的警察叔叔現(xiàn)在接手一樁10年前的案子一般----無從下手。老外稱之為heap corruption是很貼切的,有時候咱堆數(shù)據(jù)被意外篡改是無聲無息的,你也許沒法從界面甚至日志文件中看到它被篡改的一點(diǎn)跡象,當(dāng)?shù)侥骋粋€時刻,這種錯誤會暴露出來,然而這個時候查看堆信息也許會是毫無頭緒。所以對于堆破壞,咱的策略是盡早發(fā)現(xiàn)我們的堆被篡改了,最好能夠在堆數(shù)據(jù)被意外篡改的那一時刻誘發(fā)一個異常來提醒我們----兄弟,你的堆被腐蝕了。

    微軟提供了一些方案,來幫助我們診斷堆破壞。一般來說,堆破壞往往都是寫數(shù)據(jù)越界造成的(yy的第二種情況,如果是第一種情況其實(shí)還簡單,下個內(nèi)存斷點(diǎn)就好),所以微軟在堆分配上,給程序員門額外提供了2種堆分配模式--完全頁堆(full page heap),準(zhǔn)頁堆(normal page heap),用來檢測堆被寫越界的情況。

    ?

    完全頁堆(FULL PAGE HEAP)

    檢測原理

    完全頁堆的檢測基本思路是通過分配相鄰的一個頁,并將其設(shè)為不可訪問屬性,然后用戶數(shù)據(jù)塊會被分配到內(nèi)存頁的最末端,從而實(shí)現(xiàn)越界訪問的檢測。當(dāng)我們對堆中分配的內(nèi)存讀寫越界后便會訪問到那個不可讀的頁,系統(tǒng)捕獲到改次異常后會試圖中斷執(zhí)行并將該異常上報(bào)給debugger,或者崩潰。具體的內(nèi)存組織結(jié)構(gòu)如下圖

    摘自《軟件調(diào)試》

    ?

    與普通堆不同的是,內(nèi)存塊前面的HEAP_ENTRY結(jié)構(gòu)被DPH_BLOCK_INFORMATION結(jié)構(gòu)取代,這個結(jié)構(gòu)內(nèi)部記錄了頁堆模式下這個內(nèi)存塊的一些基本信息。如果用戶數(shù)據(jù)區(qū)前面的數(shù)據(jù),也就是DPH_BLOCK_INFORMATION結(jié)構(gòu)被破壞了,那么在釋放內(nèi)存塊的時候系統(tǒng)會報(bào)錯,如果編程者對這塊內(nèi)存塊讀寫越界了,當(dāng)然,這里越界有幾種情況:

  • 讀越界,但只是訪問了塊尾填充部分?jǐn)?shù)據(jù),那么系統(tǒng)不會報(bào)錯
  • 寫越界,但只篡改了圖中塊尾填充的部分,那么在堆塊釋放的時候會報(bào)錯
  • 讀越界,且超過了塊尾填充的部分,訪問到了柵欄頁,那么系統(tǒng)會立即拋出一個異常并中斷執(zhí)行
  • 寫越界,且超過了塊尾填充部分,寫到了柵欄頁,那么系統(tǒng)會立即拋出一個異常并中斷執(zhí)行
  • 這里需要注意的還是塊尾填充不一定存在,塊尾填充是因?yàn)橐獫M足堆內(nèi)存的最小分配粒度,如果本身內(nèi)存塊的分配粒度就已經(jīng)是最小分配粒度的倍數(shù)了,那么塊尾填充就不存在了,比如堆內(nèi)存分配粒度是是8 bytes,那么如果申請了14 bytes的話會有2 bytes的大徐小的塊尾填充塊,如果申請了24bytes,那么就沒有塊尾填充了,因?yàn)?4正好是8的倍數(shù)

    ?

    示例

    開啟全頁堆(用windbg目錄下的gflags或者裝一個appverifier都可以開啟),通過自己寫的一個heap.exe來看一下如何使用全頁堆檢測堆破壞情況heap.exe代碼如下:

    <span style="background-color: rgb(255, 255, 255);">#include "windows.h"int main() {HANDLE heap_handle = HeapCreate( NULL , 1024 , 0 ) ;char *temp = NULL ;char *buffer = (char*)HeapAlloc(heap_handle , NULL , 128) ;char *buffer1 = (char*)HeapAlloc(heap_handle , NULL , 121) ;temp = buffer ;for( int i = 0 ; i < 138 ; ++i ){*(temp++) = 'a' ;}HeapFree(heap_handle, 0 , buffer ) ;HeapFree(heap_handle, 0 , buffer1 ) ;HeapDestroy( heap_handle) ;return 0 ; }</span>

    在第14行向buffer寫入138字節(jié),這顯然越界了,然后在用windbg啟動heap.exe,直接運(yùn)行,會發(fā)現(xiàn)報(bào)錯如下

    0:000> g
    (1f50.1f54): Access violation - code c0000005 (first chance)
    First chance exceptions are reported before any exception handling.
    This exception may be expected and handled.
    eax=00000080 ebx=00000000 ecx=02596000 edx=02596000 esi=00000001 edi=00193374
    eip=00191068 esp=0016fdc8 ebp=0016fddc iopl=0???????? nv up ei ng nz ac pe cy
    cs=001b? ss=0023? ds=0023? es=0023? fs=003b? gs=0000???????????? efl=00010297
    heap!main+0x68:
    00191068 c60161????????? mov???? byte ptr [ecx],61h???????? ds:0023:02596000=??

    報(bào)了一個內(nèi)存訪問錯誤,然后看一下調(diào)用堆棧

    0:000> kb
    ChildEBP RetAddr? Args to Child??????????????
    0016fddc 0019120f 00000001 023fbfd0 0239df48 heap!main+0x68 [d:\projects\heap\main.cpp @ 14]
    0016fe20 765b1114 7ffd3000 0016fe6c 778eb429 heap!__tmainCRTStartup+0x10f [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 582]
    0016fe2c 778eb429 7ffd3000 757369d8 00000000 kernel32!BaseThreadInitThunk+0xe
    0016fe6c 778eb3fc 00191357 7ffd3000 00000000 ntdll!__RtlUserThreadStart+0x70
    0016fe84 00000000 00191357 7ffd3000 00000000 ntdll!_RtlUserThreadStart+0x1b

    可以看到是第14行報(bào)的錯,但是14行的代碼運(yùn)行了那么多次,我們再看一下這個時候變量i的值是多少

    0:000> dv i
    ????????????? i = 0n128

    顯然,在填充第128字節(jié)的時候,我們的temp指針訪問到了柵欄頁,從而報(bào)出了一個內(nèi)存違規(guī)的異常。

    這里順帶看一下如果我們分配的內(nèi)存不是8 bytes的情況(一般堆內(nèi)存分配粒度是8 bytes,所以申請128 bytes的內(nèi)存時是不會有塊尾填充部分的)

    那我們接下來看另外一段代碼

    我們把第10行的temp = buffer改成temp = buffer1

    因?yàn)閎uffer1申請了121 bytes,也就是說它有7 bytes的填充字節(jié)

    0:000> g
    (1ba0.1ba4): Access violation - code c0000005 (first chance)
    First chance exceptions are reported before any exception handling.
    This exception may be expected and handled.
    eax=00000080 ebx=00000000 ecx=024c8000 edx=024c8000 esi=00000001 edi=00033374
    eip=00031068 esp=002cfb80 ebp=002cfb94 iopl=0???????? nv up ei ng nz ac pe cy
    cs=001b? ss=0023? ds=0023? es=0023? fs=003b? gs=0000???????????? efl=00010297
    heap!main+0x68:
    00031068 c60161????????? mov???? byte ptr [ecx],61h???????? ds:0023:024c8000=??
    0:000> dv i
    ????????????? i = 0n128

    可以看到變量i還是128,也就是說我們還是在訪問到第128字節(jié)后才引發(fā)訪問異常,而不是我們期望的121字節(jié)后就引發(fā)異常。

    這里也就是說如果我們的代碼中對申請的堆內(nèi)存寫越界了,寫數(shù)據(jù)覆蓋塊尾填充部分的時候并不會引發(fā)異常!

    但是,這并不代表我們的寫越界問題不會被發(fā)現(xiàn)。塊尾填充部分是會被填充上固定數(shù)據(jù)的,系統(tǒng)在適合的時機(jī)(比如銷毀堆的時候)會校驗(yàn)塊尾填充塊,如果發(fā)現(xiàn)塊尾填充塊數(shù)據(jù)有變,那么便會報(bào)一個verifier異常,比如我們把代碼中的for循環(huán)次數(shù)改為124

    <span style="background-color: rgb(255, 255, 255);"> for( int i = 0 ; i < 124 ; ++i )</span>

    那么windbg會中斷在第19行

    <span style="background-color: rgb(255, 255, 255);"> HeapDestroy( heap_handle) ;</span>

    提示內(nèi)容如下
    =======================================
    VERIFIER STOP 0000000F: pid 0x1E3C: Corrupted suffix pattern for heap block.

    ????025A1000?: Heap handle used in the call.
    ????025A7F80?: Heap block involved in the operation.
    ??? 00000079 : Size of the heap block.
    ????025A7FF9?: Corruption address.


    =======================================
    This verifier stop is not continuable. Process will be terminated?
    when you use the `go' debugger command.

    =======================================

    (1e3c.143c): Break instruction exception - code 80000003 (first chance)
    eax=6c75e994 ebx=6c75cf58 ecx=00000002 edx=002bf461 esi=00000000 edi=000001ff
    eip=6c753c38 esp=002bf6b4 ebp=002bf8b8 iopl=0???????? nv up ei pl nz na po nc
    cs=001b? ss=0023? ds=0023? es=0023? fs=003b? gs=0000???????????? efl=00000202
    vrfcore!VerifierStopMessageEx+0x543:
    6c753c38 cc????????????? int???? 3

    提示說的很清楚了,appverifier指出了堆和具體的內(nèi)存塊,我們這個時候查看buffer1的值是0x025a7f80?,正好就是出問題的堆塊,出問題的地址是0x025a7ff79,正好就是buffer1內(nèi)存塊的邊界,錯誤原因是Corrupted suffix pattern for heap block,也就是說咱塊尾填充部分(suffix pattern for heap block)被破壞(corrupted)了

    結(jié)論:只要寫越界,系統(tǒng)都能夠檢測出來,只不過如果寫越界寫到了柵欄頁會理解觸發(fā)異常中斷,而寫越界只寫了塊尾填充部分,那么系統(tǒng)在適當(dāng)時機(jī)(比如堆被銷毀,或者這塊內(nèi)存被重新分配等時機(jī))會對塊尾填充部分做完整性檢測,如果發(fā)現(xiàn)被破壞了,就會報(bào)錯。當(dāng)然,你可以根據(jù)錯誤號(藍(lán)色字體部分)信息去appverifier的幫助文檔中查找更詳細(xì)的錯誤說明。

    結(jié)構(gòu)詳解

    這次咱來倒敘,先從最基本的內(nèi)存堆塊結(jié)構(gòu)DPH_BLOCK_INFORMATION開始介紹,DPH_BLOCK_INFORMATION結(jié)構(gòu)微軟也有對應(yīng)文檔介紹

    (摘自MSDN)

    ?

    其中prefix start magic和prefix end magic是校驗(yàn)塊,用來檢測DPH_BLOCK_INFORMATION是否被破壞,這些檢測部分屬于DPH_BLOCK_INFORMATION結(jié)構(gòu)。我們先來用windbg探究下DPH_BLOCK_INFORMATION這個最基本的結(jié)構(gòu).再一次,我們打開windbg調(diào)試heap.exe.運(yùn)行到第10行,這個時候變量的值是

    0:000> dv heap_handle
    ??? heap_handle =?0x024a0000
    0:000> dv buffer
    ???????? buffer =?0x024a5f80?"???"
    0:000> dv buffer1
    ??????? buffer1 =?0x024a7f80?"???"

    這里可以看到一個很有趣的現(xiàn)象,buffer1和buffer的地址正好相差8K,也就是兩個頁的大小.這當(dāng)然是因?yàn)轫摱训脑蚶?其實(shí)這兩塊內(nèi)存分配是相鄰著的,虛擬內(nèi)存結(jié)構(gòu)如下圖所示

    buffer內(nèi)存塊(4K) 柵欄頁(4K) buffer1內(nèi)存塊(4K) 柵欄頁(4K)

    ?

    由于buffer和buffer1分配的大小是一樣的(buffer1加上尾部填充塊和buffer的大小相同),所以這兩塊內(nèi)存正好相差8K

    而DPH_BLOCK_INFORMATION就在我們申請的內(nèi)存塊指針的前0x20字節(jié)處,用dt命令看的結(jié)果如下:

    0:000> dt _DPH_BLOCK_INFORMATION 0x024a5f80-0x20
    verifier!_DPH_BLOCK_INFORMATION
    ?? +0x000 StartStamp?????? : 0xabcdbbbb
    ?? +0x004 Heap???????????? : 0x024a1000 Void
    ?? +0x008 RequestedSize??? : 0x80
    ?? +0x00c ActualSize?????? : 0x1000
    ?? +0x010 Internal???????? : _DPH_BLOCK_INTERNAL_INFORMATION
    ?? +0x018 StackTrace?????? : 0x003d9854 Void
    ?? +0x01c EndStamp???????? : 0xdcbabbbb

    ?

    0x024a5f80-0x20就是DPH_BLOCK_INFORMATION結(jié)構(gòu)的地址。DPH_BLOCK_INFORMATION結(jié)構(gòu)在已分配和已釋放的狀態(tài)下,StartStamp和EndStamp(也就是MSDN圖中的prefix start magic和prefix end magic)是不同的,顯然dt輸出的結(jié)果看來,這個內(nèi)存塊是已分配狀態(tài)。StackTrace記錄了分配這個內(nèi)存塊時的調(diào)用棧,可以用dds來看一下這個內(nèi)存塊被分配時候的調(diào)用棧

    0:000> dds 0x003d9854?
    003d9854? 00000000
    003d9858? 00004001
    003d985c? 00090000
    003d9860? 5b3b8e89 verifier!AVrfDebugPageHeapAllocate+0x229
    003d9864? 776d5c4e ntdll!RtlDebugAllocateHeap+0x30
    003d9868? 77697e5e ntdll!RtlpAllocateHeap+0xc4
    003d986c? 776634df ntdll!RtlAllocateHeap+0x23a
    003d9870? 003b1030 heap!main+0x30 [d:\projects\heap\main.cpp @ 8]
    003d9874? 003b120c heap!__tmainCRTStartup+0x10f [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 582]
    003d9878? 76451114 kernel32!BaseThreadInitThunk+0xe
    003d987c? 7766b429 ntdll!__RtlUserThreadStart+0x70
    003d9880? 7766b3fc ntdll!_RtlUserThreadStart+0x1b

    輸出結(jié)果我們可以看到這個內(nèi)存塊是在main.cpp,也就是我們的示例代碼的第8行分配的,第8行是char *buffer = (char*)HeapAlloc(heap_handle , NULL , 128) 正好就是分配buffer內(nèi)存的那條語句。這個結(jié)構(gòu)的其它字段,顧名思義,ActualSize指明了實(shí)際分配字節(jié)數(shù),0x1000 bytes也就是4K大小,Internal這個字段保存了個內(nèi)部結(jié)構(gòu),用windbg也看不出這個結(jié)構(gòu)信息。

    當(dāng)然為了防止內(nèi)存塊前面的數(shù)據(jù)被沖刷掉,除了DPH_BLOCK_INFORMATION外,系統(tǒng)還通過DPH_HEAP_BLOCK保存了所分配內(nèi)存塊的信息,

    通過!heap –p –h [address] 可以查看到頁堆的信息

    0:000> !heap -p -h?0x024a0000??????????????????????????? //heap_handle的值
    ??? _DPH_HEAP_ROOT @ 24a1000
    ??? Freed and decommitted blocks
    ????? DPH_HEAP_BLOCK : VirtAddr VirtSize
    ??? Busy allocations
    ????? DPH_HEAP_BLOCK : UserAddr? UserSize - VirtAddr VirtSize
    ????????024a1f6c?: 024a5f80 00000080 - 024a5000 00002000
    ??????? 024a1f38 : 024a7f80 00000079 - 024a7000 00002000


    可以看到,buffer內(nèi)存塊對應(yīng)的DPH_HEAP_BLOCK結(jié)構(gòu)地址是024a1f6c

    0:000> dt _DPH_HEAP_BLOCK 024a1f6c
    verifier!_DPH_HEAP_BLOCK
    ?? +0x000 NextFullPageHeapDelayedNode : 0x024a1020 _DPH_HEAP_BLOCK
    ?? +0x004 DelayQueueEntry? : _DPH_DELAY_FREE_QUEUE_ENTRY
    ?? +0x000 LookasideEntry?? : _LIST_ENTRY [ 0x24a1020 - 0x0 ]
    ?? +0x000 UnusedListEntry? : _LIST_ENTRY [ 0x24a1020 - 0x0 ]
    ?? +0x000 VirtualListEntry : _LIST_ENTRY [ 0x24a1020 - 0x0 ]
    ?? +0x000 FreeListEntry??? : _LIST_ENTRY [ 0x24a1020 - 0x0 ]
    ?? +0x000 TableLinks?????? : _RTL_BALANCED_LINKS
    ?? +0x010 pUserAllocation? : 0x024a5f80? "???"
    ?? +0x014 pVirtualBlock??? : 0x024a5000? "???"
    ?? +0x018 nVirtualBlockSize : 0x2000
    ?? +0x01c Flags??????????? : _DPH_HEAP_BLOCK_FLAGS
    ?? +0x020 nUserRequestedSize : 0x80
    ?? +0x024 AdjacencyEntry?? : _LIST_ENTRY [ 0x24a1f5c - 0x24a1fc4 ]
    ?? +0x02c ThreadId???????? : 0x3f4
    ?? +0x030 StackTrace?????? : 0x003d9854 Void

    從dt的數(shù)據(jù)看來,這個結(jié)構(gòu)大小為0x34,buffer和buffer1的DPH_HEAP_BLOCK結(jié)構(gòu)首地址正好也是相差0x34,說明這兩個結(jié)構(gòu)是緊挨著的,下一步在讓我們來看看DPH_HEAP_BLOCK結(jié)構(gòu)是如何組織的。

    摘自《軟件調(diào)試》

    ?

    這個是整個的頁堆結(jié)構(gòu)圖,我們先來說說DPH_HEAP_BLOCK的組織吧,在圖中0x16d00000是頁堆的首地址,也就是頁堆的句柄,我們調(diào)試器中,頁堆首地址則是0x024a0000,為了數(shù)據(jù)統(tǒng)一,我還是拿0x024a0000作為堆句柄來講解。我們的DPH_HEAP_BLOCK其實(shí)就在堆塊節(jié)點(diǎn)池里邊,我們可以近似把這個節(jié)點(diǎn)池看成一個大型的DPH_HEAP_BLOCK數(shù)組,但有個地方在軟件調(diào)試中沒有提到,就是在win7下,運(yùn)行時這些DPH_HEAP_BLOCK結(jié)構(gòu)都是以二叉平衡數(shù)的結(jié)構(gòu)來組織的,這個樹的結(jié)構(gòu)的入口正是在TableLinks字段內(nèi),這么做的原因也大概是因?yàn)槟軌蛟诜峙鋾r更快的索。我們再看看DPH_HEAP_ROOT結(jié)構(gòu),這個結(jié)構(gòu)儲存了整個頁堆的必要信息,它就相當(dāng)于普通堆的_HEAP結(jié)構(gòu)。

    0:000> dt _dph_heap_root 24a1000
    verifier!_DPH_HEAP_ROOT
    ?? +0x000 Signature??????? : 0xffeeddcc
    ?? +0x004 HeapFlags??????? : 0x1002
    ?? +0x008 HeapCritSect???? : 0x024a16cc _RTL_CRITICAL_SECTION
    ?? +0x00c NodesCount?????? : 0x2c
    ?? +0x010 VirtualStorageList : _LIST_ENTRY [ 0x24a1fa0 - 0x24a1fa0 ]
    ?? +0x018 VirtualStorageCount : 1
    ?? +0x01c PoolReservedLimit : 0x024a5000 Void
    ?? +0x020?BusyNodesTable?? : _RTL_AVL_TABLE
    ?? +0x058 NodeToAllocate?? : (null)?
    ?? +0x05c nBusyAllocations : 2
    ?? +0x060 nBusyAllocationBytesCommitted : 0x4000
    ?? +0x064 pFreeAllocationListHead : (null)?
    ?? +0x068 FullPageHeapDelayedListTail : (null)?
    ?? +0x06c DelayFreeQueueHead : (null)?
    ?? +0x070 DelayFreeQueueTail : (null)?
    ?? +0x074 DelayFreeCount?? : 0
    ?? +0x078 LookasideList??? : _LIST_ENTRY [ 0x24a1078 - 0x24a1078 ]
    ?? +0x080 LookasideCount?? : 0
    ?? +0x084 UnusedNodeList?? : _LIST_ENTRY [ 0x24a1ed0 - 0x24a16e4 ]
    ?? +0x08c UnusedNodeCount? : 0x28
    ?? +0x090 nBusyAllocationBytesAccessible : 0x2000
    ?? +0x094 GeneralizedFreeList : _LIST_ENTRY [ 0x24a1f04 - 0x24a1f04 ]
    ?? +0x09c FreeCount??????? : 1
    ?? +0x0a0 PoolCommitLimit? : 0x024a2000 Void
    ?? +0x0a4 NextHeap???????? : _LIST_ENTRY [ 0x5b3e9a58 - 0x23a10a4 ]
    ?? +0x0ac ExtraFlags?????? : 3
    ?? +0x0b0 Seed???????????? : 0xfed6f13a
    ?? +0x0b4 NormalHeap?????? : 0x027d0000 Void
    ?? +0x0b8 CreateStackTrace : 0x003d9824 _RTL_TRACE_BLOCK
    ?? +0x0bc ThreadInHeap???? : (null)?
    ?? +0x0c0 BusyListHead???? : _LIST_ENTRY [ 0x24a10c0 - 0x24a10c0 ]
    ?? +0x0c8 SpecializedFreeList : [64] _LIST_ENTRY [ 0x24a10c8 - 0x24a10c8 ]
    ?? +0x2c8 DelayFreeListLookup : [257] (null)?
    ?? +0x6cc HeapCritSectionStorage : _RTL_CRITICAL_SECTION

    這里邊維護(hù)了很多運(yùn)行時信息,比如說DPH_BLOCK_INFORMATION中的那個二叉樹入口其實(shí)就是保存在BusyNodesTable?字段,這里面記錄了所有被分配了的內(nèi)存塊所對應(yīng)的DPH_BLOCK_INFORMATION。當(dāng)然,這里面一些信息軟件調(diào)試?yán)锩娑加薪榻B,很多看名字也能夠猜到大概意思,看名字猜不到啥意思的字段,其實(shí)我也猜不到。。。-_-|||在創(chuàng)建頁堆后,所有內(nèi)存分配都分配在頁堆中,通過分配的地址也能看得出來(我們分配的內(nèi)存都是024a打頭),而非普通頁堆中,普通頁堆也僅僅只是保存一些系統(tǒng)內(nèi)部使用的數(shù)據(jù)。一般來說,堆塊節(jié)點(diǎn)池加上DPH_HEAP_ROOT結(jié)構(gòu)大小正好是4個內(nèi)存頁,也就是16K。

    優(yōu)缺點(diǎn)

    缺點(diǎn):消耗大量虛擬內(nèi)存,每塊內(nèi)存的分配粒度是2個頁(8K),

    優(yōu)點(diǎn):能夠立即捕獲越界讀寫操作,通過調(diào)用棧就可以追溯到問題源頭。能夠快速定位問題代碼。

    ?

    五、????????堆的調(diào)試支持

    HTC???HFC???HPC???HVC ??UST????DPH

    1)???????全局標(biāo)志:

    可以查看下圖注冊表路徑中該程序名子健下的GlobalFlag鍵值。



    或者使用Gflags?工具。

    如果在調(diào)試器中運(yùn)行一個程序,而且注冊表中沒有設(shè)置GlobalFlags鍵值,那么操作系統(tǒng)會默認(rèn)啟用htchfchpc三項(xiàng)堆調(diào)試功能。

    windbg中可以使用??!gflag?命令查看。如果是附加到一個已經(jīng)運(yùn)行的進(jìn)程,則系統(tǒng)會默認(rèn)設(shè)置為0

    ?

    2)???????釋放檢查:

    為了防止兩次釋放一個堆而產(chǎn)生錯誤。

    ?

    如果啟用了堆調(diào)試功能,RtlAllocateHeap會調(diào)用RtlAllocateHeapSlowly函數(shù)執(zhí)行真正的堆分配功能。同理RtlFreeHeap也會調(diào)用RtlFreeHeapSlowly函數(shù)執(zhí)行堆的釋放。這會導(dǎo)致執(zhí)行速度的下降。

    ?

    3)???????棧回溯數(shù)據(jù)庫(UST

    記錄分配堆塊時的函數(shù)調(diào)用,即棧回溯的記錄。

    ?

    管理結(jié)構(gòu):STACK_TRACE_DATABASE

    使用全局變量ntdll!RtlpStackTraceDatabase指向這個內(nèi)存區(qū)域。

    具體過程?略

    ?

    堆分配函數(shù)把RtlLogStackBackTrace返回的回溯記錄的索引號存入堆結(jié)尾的HEAP_ENTRY_EXTRA數(shù)據(jù)結(jié)構(gòu)中。

    HEAP_ENTRY_EXTRA結(jié)構(gòu):?兩個字節(jié)為UST記錄,兩個字節(jié)為堆塊標(biāo)記號,其余四個字節(jié)用于存儲用戶設(shè)置的數(shù)值。如果沒有設(shè)置則為0

    ?

    4)???????調(diào)用時驗(yàn)證(HVC

    為了檢查堆中的異常情況,在堆管理器的堆函數(shù)每次被調(diào)用時都對堆進(jìn)行檢查。

    RtlpValidateHeap函數(shù)是驗(yàn)證堆的函數(shù),他會對堆進(jìn)行全面檢查。這個函數(shù)是由上述XXslowly函數(shù)間接調(diào)用的。

    如果是在調(diào)試器中運(yùn)行,則會觸發(fā)斷點(diǎn)異常(INT 3),然后切換到調(diào)試器。

    如果不是,則不會觸發(fā)斷點(diǎn)異常,但仍會檢測到錯誤。

    HVC可以防止堆溢出的破壞。

    ?

    5)???????堆尾檢查(HTC

    主要是為了檢測堆溢出,但是存在滯后性。

    方法是在堆塊的用戶申請的區(qū)域后加上8個字節(jié)的0xAB。(這個數(shù)值由ntdll!CheckHeapFillPattern值決定)。

    如果要觸發(fā)堆管理器檢查這個模式是否被破壞,HFCHPC也要同時開啟。(具體設(shè)置前面有述,也可以用!gflag +htc +hfc +hpc

    ?

    實(shí)際觀察堆的結(jié)構(gòu)(調(diào)試狀態(tài)和非調(diào)試狀態(tài)):

    Code

    #include <windows.h>

    int main()

    {

    HLOCAL h1,h2,h3,h4,h5,h6;

    HANDLE hp;

    //hp = HeapCreate(0,0x1000,0x10000);//?不可擴(kuò)展的堆

    ?

    hp = HeapCreate(0,0,0);

    __asm int 3;

    //h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,260);

    h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,3);

    h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,5);

    h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,6);

    h4 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);

    h5 = HeapAlloc(hp,HEAP_ZERO_MEMORY,19);

    h6 = HeapAlloc(hp,HEAP_ZERO_MEMORY,24);

    ?

    HeapFree(hp,0,h1);

    HeapFree(hp,0,h3);

    HeapFree(hp,0,h5);

    HeapFree(hp,0,h4);

    ?

    return 0;

    }

    對于第一個堆:

    調(diào)試狀態(tài):



    ?

    0004為一共分配的大小:4*8=32個字節(jié)。其中用戶申請了3個字節(jié),按照粒度對齊到了8個字節(jié),feee是填充的字段。再加上8個字節(jié)的HEAP_ENTRY結(jié)構(gòu),再加上HTC檢測用的8個字節(jié)的0xab,再加上8個字節(jié)的HEAP_ENTRY_EXTRA結(jié)構(gòu)。共32個字節(jié)。

    07flag標(biāo)志:1busy+2(有EXTRA結(jié)構(gòu))+4(固定模式填充)

    00段的序號

    我們也看到對于沒有分配的空閑堆塊堆管理器是用feee填充的。

    ?

    非調(diào)試狀態(tài):



    ?

    000216個字節(jié),即8個字節(jié)的HEAP_ENTRY加上8個字節(jié)經(jīng)過填充的申請的數(shù)據(jù)。

    ?

    ?

    6)???????頁堆

    上述的所有堆的調(diào)試支持都有一定的滯后性。只有在堆的函數(shù)再次調(diào)用的時候才會去驗(yàn)證堆的完整性(通過調(diào)用xxSlowly的函數(shù)),所以當(dāng)我們發(fā)現(xiàn)溢出的時候可能已經(jīng)離溢出的發(fā)生地點(diǎn)很遠(yuǎn)了,這也主要是因?yàn)樯鲜龅恼{(diào)試支持并不是專門為了檢測溢出類漏洞而生的。

    頁堆是檢測溢出類漏洞的一個很有效的辦法,盡管這是用大量浪費(fèi)的空間和性能換來的.

    ?????????

    ?

    其中的DPH_HEAP_ROOT結(jié)構(gòu)是頁堆的真正管理結(jié)構(gòu),他相當(dāng)于HEAP結(jié)構(gòu)的功能。

    ?



    開啟:gflags /p /enable Heap.exe /full

    windbg中使用如下命令查看是否開啟頁堆:




    ?

    ?

    查看頁堆:

    可以使用命令?!heap –p ,!heap –p –h address查看,不過貌似在windbg6.10.x?到?6.11.x中有bug。我的版本是6.11.0001.402,運(yùn)行時?有點(diǎn)問題。



    ?

    + 140000是進(jìn)程堆的地址,每個頁堆都會附帶一個普通的堆,這個普通堆地址是240000

    因?yàn)槲业?span lang="EN-US">Windbgbug,下面使用《軟件調(diào)試》里的截圖:



    ?

    堆塊結(jié)構(gòu):

    頁堆的堆塊結(jié)構(gòu)跟普通的堆塊是有很大的區(qū)別的,一個堆塊至少占用兩個頁面(8K),其中前一個頁面用于存儲用戶的數(shù)據(jù),后一個頁面用于檢測溢出。為了能迅速發(fā)現(xiàn)溢出,用戶區(qū)的數(shù)據(jù)建立在第一個頁面的結(jié)尾處,這樣如果溢出發(fā)生,溢出的數(shù)據(jù)就會寫入到柵欄頁造成異常。



    ?

    (除了上圖的結(jié)構(gòu)外,對于每一個頁堆堆塊,在頁堆的節(jié)點(diǎn)池中還會有一個DPH_HEAP_BLOCK結(jié)構(gòu),即DPH節(jié)點(diǎn)結(jié)構(gòu)。)

    ?

    其中DPH_BLOCK_INFORMATION結(jié)構(gòu)的長度是32個字節(jié)。

    使用**alloc分配堆塊時,返回的是用戶區(qū)的地址,這個地址減去32個字節(jié)就是DPH_BLOCK_INFORMATION結(jié)構(gòu):

    為了保護(hù)這個結(jié)構(gòu)的完整性,在開始和結(jié)尾都加上了固定的值。

    ?

    關(guān)于頁堆的填充字段可看下圖:

    ??????????????????????占用堆塊??????????????????????????空閑堆塊

    ?

    ?

    ?

    我們直接在內(nèi)存上觀察新分配的堆塊:



    ?

    可以看到32個字節(jié)的DPH_BLOCK_INFORMATION后面就是申請的3個字節(jié)和用于填充的5個字節(jié)(被初始化為D0

    ??是柵欄區(qū)。



    ?

    (頁堆用的句柄是016e1000,這個是DPH_HEAP_ROOT結(jié)構(gòu),可見當(dāng)這個堆被當(dāng)作頁堆理解時,這個堆的句柄是DPH_HEAP_ROOT結(jié)構(gòu)而不是上一頁(4K)偽裝的HEAP結(jié)構(gòu))

    ?

    檢測溢出:

    Code

    #include <windows.h>

    int main()

    {

    HLOCAL h1,h2,h3,h4,h5,h6;

    HANDLE hp;

    //hp = HeapCreate(0,0x1000,0x10000);//?不可擴(kuò)展的堆

    //__asm int 3;

    hp = HeapCreate(0,0,0);

    //__asm int 3;

    //h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,260);

    char *p;

    __asm int 3;

    p= (char *)HeapAlloc(hp,0,9);

    for(int i=0;i<20; i++)

    ???????????*(p+i)=i; }

    ?

    ?

    ?

    在發(fā)生的溢出點(diǎn)出錯。

    ?

    7)??????準(zhǔn)頁堆

    由于完全頁堆浪費(fèi)大量的內(nèi)存,故可能在調(diào)試大型程序時較慢(not try),故有時可以采用準(zhǔn)頁堆的方法,其實(shí)個人感覺準(zhǔn)頁堆的方式和HTC(堆尾檢查)是很像的,都有一定的滯后性。

    開啟方法:

    gflags /p /enable Heap.exe

    結(jié)構(gòu):



    ?

    填充模式:

    ?

    占用堆塊

    空閑堆塊

    ?

    頁堆

    準(zhǔn)頁堆

    頁堆

    準(zhǔn)頁堆

    頭結(jié)構(gòu)起始簽名

    ABCDBBBB

    ABCDAAAA

    ABCDAAA9

    ABCDBBBA

    頭結(jié)構(gòu)結(jié)束簽名

    DCBABBBB

    DCBABBBB

    DCBAAAA9

    DCBABBBA

    用戶區(qū)

    C0

    E0

    F0

    F0

    柵欄字節(jié)

    N/A

    A0

    N/A

    N/A

    補(bǔ)齊字節(jié)

    D0

    00

    N/A

    N/A

    ?

    如果分配(調(diào)用HeapAlloc)時指定了參數(shù)HEAP_ZERO_MEMORY,那么用戶區(qū)會被填充為0

    準(zhǔn)頁堆是從上述的那個頁堆的附屬的普通堆上分配堆塊的。

    ?

    返回的句柄是用戶區(qū)的地址,減去40個字節(jié)(32個字節(jié)的HEAP_BLOCK_INFORMATION再加上8個字節(jié)的HEAP_ENTRY),即是HEAP_ENTRY的起始地址:



    ?

    用戶區(qū)數(shù)據(jù)(e0)后面的8個字節(jié)的0xa0即是柵欄字節(jié)



    ?

    0x31為實(shí)際的大小,即HEAP_ENTRY結(jié)構(gòu)到柵欄數(shù)據(jù)(0xa0)結(jié)束之間的字節(jié)數(shù)。

    32+9+8 = 49

    總結(jié)

    以上是生活随笔為你收集整理的windbg调试堆破坏的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。