二進制各種漏洞原理實戰分析總結


 

 

本部分將對常見的二進制漏洞做系統分析,方便在漏洞挖掘過程中定位識別是什么類型漏洞,工欲善其事,必先利其器。

0x01棧溢出漏洞原理

棧溢出漏洞屬於緩沖區漏洞的一種,實例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <string.h>
 
int  main()
{
     char  * str  =  "AAAAAAAAAAAAAAAAAAAAAAAA" ;
     vulnfun( str );
     return ;
}
 
int  vulnfun(char  * str )
{
     char stack[ 10 ];
     strcpy(stack, str );         / /  這里造成溢出!   
}

編譯后使用windbg運行

 

 

直接運行到了地址0x41414141,這個就是字符串AAAA,就是變量str里面的字符串通過strcpy拷貝到棧空間時,沒有對字符串長度做限制,導致了棧溢出,最后覆蓋到了返回地址,造成程序崩潰。

 

溢出后的棧空間布局如下:

 

 

棧溢出原理圖

0x02 堆溢出漏洞原理

使用以下代碼演示堆溢出漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <windows.h>
#include <stdio.h>
 
int  main ( )
{
     HANDLE hHeap;
     char  * heap;
     char  str []  =  "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" ;
 
     hHeap  =  HeapCreate(HEAP_GENERATE_EXCEPTIONS,  0x1000 0xffff );
     getchar();     / /  用於暫停程序,便於調試器加載
 
     heap  =  HeapAlloc(hHeap,  0 0x10 );
     printf( "heap addr:0x%08x\n" ,heap);
 
     strcpy(heap, str );     / /  導致堆溢出
     HeapFree(hHeap,  0 , heap);     / /  觸發崩潰
 
     HeapDestroy(hHeap);
     return  0 ;
}

由於調試堆和常態堆的結構不同,在演示代碼中加入getchar函數,用於暫停進程,方便運行heapoverflowexe后用調試器附加進程。debug版本和Release版本實際運行的進程中各個內存結構和分配過程也不同,因此測試的時候應該編譯成release版本。

 

運行程序,使用windbg附加調試(一定要附加調試),g運行后程序崩潰

 

 

上面的ecx已經被AAAA字符串覆蓋掉了,最后在引用該地址的時候導致崩潰,通過前面的棧回溯定位到了main函數入口,找到復制字符串的函數下斷點

 

 

此時堆塊已經分配完畢,對應的分配地址位於0x007104a0,0x007104a0是堆塊數據的起始地址,並非堆頭信息的起始地址,對於已經分配的堆塊,開頭有8字節的HEAP_ENTRY結構,因此heap的HEAP_ENTRY結構位於0x007104a0-8=0x710498。

 

 

在windbg上查看兩個堆塊的信息,這兩個堆塊目前處於占用狀態,共有0x10大小空間

1
2
3
4
5
0 : 000 > !heap  - - 0x710498
     address  00710498  found  in
     _HEAP @  710000
       HEAP_ENTRY Size Prev Flags    UserPtr UserSize  -  state
         00710498  0005  0000   [ 00 ]    007104a0     00010  -  (busy)

在windbg中,使用!heap查看HeapCreate創建的整個堆塊信息,可以發現堆塊heap后面還有一個空閑堆塊0x007104c0:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
0 : 000 > !heap
         Heap Address      NT / Segment Heap
 
               560000               NT Heap
               800000               NT Heap
               710000               NT Heap
0 : 000 > !heap  - 710000
HEAPEXT: Unable to get address of ntdll!RtlpHeapInvalidBadAddress.
Index   Address  Name      Debugging options enabled
   3 :    00710000
     Segment at  00710000  to  00720000  ( 00001000  bytes committed)
     Flags:                 40001064
     ForceFlags:            40000064
     Granularity:           8  bytes
     Segment Reserve:       00100000
     Segment Commit:        00002000
     DeCommit Block Thres:  00000200
     DeCommit Total Thres:  00002000
     Total Free Size:       00000164
     Max . Allocation Size:  7ffdefff
     Lock Variable at:      00710248
     Next  TagIndex:         0000
     Maximum TagIndex:      0000
     Tag Entries:           00000000
     PsuedoTag Entries:     00000000
     Virtual Alloc  List :    0071009c
     Uncommitted ranges:    0071008c
             00711000 0000f000   ( 61440  bytes)
     FreeList[  00  ] at  007100c0 007104c8  007104c8 
         007104c0 00028  00b20  [ 104 -  free
 
     Segment00 at  00710000 :
         Flags:            00000000
         Base:             00710000
         First Entry:      00710498
         Last Entry:       00720000
         Total Pages:      00000010
         Total UnCommit:   0000000f
         Largest UnCommit: 00000000
         UnCommitted Ranges: ( 1 )
 
     Heap entries  for  Segment00  in  Heap  00710000
          address: psize . size  flags   state (requested size)
         00710000 00000  00498  [ 101 -  busy ( 497 )
         00710498 00498  00028  [ 107 -  busy ( 10 ), tail fill  / / heap的占用堆塊
         007104c0 00028  00b20  [ 104 ] free fill   / / 空閑堆塊
         00710fe0 00b20  00020  [ 111 -  busy ( 1d )
         00711000 :       0000f000       -  uncommitted bytes.

在復制字符串的時候,原本只有0x10大小的堆塊,填充過多的字符串的時候就會覆蓋到下方的空閑堆塊007104c0,在復制前007104c0空閑堆塊的HEAP_FREE_ENTRY結構數據如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
0 : 000 > dt _HEAP_FREE_ENTRY  0x007104c0
ntdll!_HEAP_FREE_ENTRY
    + 0x000  HeapEntry        : _HEAP_ENTRY
    + 0x000  UnpackedEntry    : _HEAP_UNPACKED_ENTRY
    + 0x000  Size             :  0x6298
    + 0x002  Flags            :  0x16  ''
    + 0x003  SmallTagIndex    :  0xac  ''
    + 0x000  SubSegmentCode   :  0xac166298
    + 0x004  PreviousSize     :  0xcfb9
    + 0x006  SegmentOffset    :  0  ''
    + 0x006  LFHFlags         :  0  ''
    + 0x007  UnusedBytes      :  0  ''
    + 0x000  ExtendedEntry    : _HEAP_EXTENDED_ENTRY
    + 0x000  FunctionIndex    :  0x6298
    + 0x002  ContextValue     :  0xac16
    + 0x000  InterceptorValue :  0xac166298
    + 0x004  UnusedBytesLength :  0xcfb9
    + 0x006  EntryOffset      :  0  ''
    + 0x007  ExtendedBlockSignature :  0  ''
    + 0x000  Code1            :  0xac166298
    + 0x004  Code2            :  0xcfb9
    + 0x006  Code3            :  0  ''
    + 0x007  Code4            :  0  ''
    + 0x004  Code234          :  0xcfb9
    + 0x000  AgregateCode     :  0x0000cfb9 `ac166298
    + 0x008  FreeList         : _LIST_ENTRY [  0x7100c0  -  0x7100c0  ]
1
2
3
4
5
0 : 000 > dt _LIST_ENTRY  0x007104c0 + 8
ntdll!_LIST_ENTRY
  0x7100c0  -  0x7100c0  ]
    + 0x000  Flink            :  0x007100c0  _LIST_ENTRY [  0x7104c8  -  0x7104c8  ]
    + 0x004  Blink            :  0x007100c0  _LIST_ENTRY [  0x7104c8  -  0x7104c8  ]

覆蓋后0x007104c0空閑塊的HEAP_FREE_ENTRY結構數據如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
0 : 000 > g
( 2c08 . 234 ): Access violation  -  code c0000005 (!!! second chance !!!)
eax = 007e04a0  ebx = 007e0498  ecx = 41414141  edx = 007e0260  esi = 007e04b8  edi = 007e0000
eip = 7775919d  esp = 0019fdb0  ebp = 0019fea8  iopl = 0          nv up ei ng nz na po cy
cs = 0023   ss = 002b   ds = 002b   es = 002b   fs = 0053   gs = 002b              efl = 00010283
ntdll!RtlpFreeHeap + 0x5bd :
7775919d  8b11             mov     edx,dword ptr [ecx]  ds: 002b : 41414141 = ????????
0 : 000 > dt _HEAP_FREE_ENTRY  0x007104c0
ntdll!_HEAP_FREE_ENTRY
    + 0x000  HeapEntry        : _HEAP_ENTRY
    + 0x000  UnpackedEntry    : _HEAP_UNPACKED_ENTRY
    + 0x000  Size             : ??
    + 0x002  Flags            : ??
    + 0x003  SmallTagIndex    : ??
    + 0x000  SubSegmentCode   : ??
    + 0x004  PreviousSize     : ??
    + 0x006  SegmentOffset    : ??
    + 0x006  LFHFlags         : ??
    + 0x007  UnusedBytes      : ??
    + 0x000  ExtendedEntry    : _HEAP_EXTENDED_ENTRY
    + 0x000  FunctionIndex    : ??
    + 0x002  ContextValue     : ??
    + 0x000  InterceptorValue : ??
    + 0x004  UnusedBytesLength : ??
    + 0x006  EntryOffset      : ??
    + 0x007  ExtendedBlockSignature : ??
    + 0x000  Code1            : ??
    + 0x004  Code2            : ??
    + 0x006  Code3            : ??
    + 0x007  Code4            : ??
    + 0x004  Code234          : ??
    + 0x000  AgregateCode     : ??
    + 0x008  FreeList         : _LIST_ENTRY
Memory read error  007104c0

整個空閑堆頭信息都被覆蓋了,包括最后的空閑鏈表中的前后向指針都被成了0x41414141,后面調用HeapFree釋放堆塊的時候,就會將buf2和后面的空閑堆塊0x007104c0合並,修改兩個空閑堆塊的前后向指針就會引用0x41414141,最后造成崩潰。

 

如果把上面釋放堆塊的操作換成分配堆塊HeapAlloc(),也會導致崩潰,因為在分配堆塊的時候會去遍歷空閑鏈表指針,會造成地址引用異常,當內存中已經分配多個堆塊的時候,可能覆蓋到的就是已經分配到的堆塊,此時可能就是覆蓋HEAP_ENTRY結構,而不是HEAP_FREE_ENTRY結構。

 

 

堆溢出原理圖

0x03 堆調試技巧

微軟提供了一些調試選項用於輔助堆調試,可以通過windbg提供的gflag.exe或者!gflag命令來設置:

1
2
3
4
5
6
htc:堆尾檢查,是否發生溢出
hfc:堆釋放檢查
hpc:堆參數檢查
hpa:啟用頁堆
htg:堆標志
ust:用戶態棧回溯

對heapoverflow.exe添加堆尾檢查和頁堆,去掉堆標志:

1
gflags.exe  - i F:\vulns\Release\heapoverflow  + htc  + hpa  + htg

堆尾檢查

主要是在每個堆塊的尾部,用戶數據之后添加8字節,通常是連續的2個0xabababab,該數據段被破壞就可能發生了溢出。

 

對heapoverflow.exe開啟hpc和htc,用windbg加載對heapoverflow程序,附加進程無法在堆尾添加額外標志,使用以下命令開啟堆尾檢查和堆參數檢查:

1
2
3
4
5
0 : 000 > !gflag  + htc  + hpc
Current NtGlobalFlag contents:  0x00000070
     htc  -  Enable heap tail checking
     hfc  -  Enable heap free checking
     hpc  -  Enable heap parameter checking
1
2
3
4
5
6
7
0 : 000 :x86> g
HEAP[heapoverflow.exe]: Heap block at  001E0498  modified at  001E04B0  past requested size of  10
( 13d0 . 3c9c ): WOW64 breakpoint  -  code  4000001f  (first chance)
First chance exceptions are reported before  any  exception handling.
This exception may be expected  and  handled.
ntdll_77710000!RtlpBreakPointHeap + 0x13 :
777f07c7  cc               int      3

執行命令g后,按下回車鍵程序會斷下來

1
2
3
4
5
6
7
8
9
10
11
12
13
0 : 000 :x86> kb
  # ChildEBP RetAddr  Args to Child             
00  0019fd18  777dd85b  00000000  001e0000  001e0498  ntdll_77710000!RtlpBreakPointHeap + 0x13
01  0019fd30  77793e9c  001e0000  00000000  77786780  ntdll_77710000!RtlpCheckBusyBlockTail + 0x1a2
02  0019fd4c  777ef9f1  7772e4dc  9ceeef49  001e0000  ntdll_77710000!RtlpValidateHeapEntry + 0x633d9
03  0019fda4  7775991d  001e04a0  9ceeec45  001e0498  ntdll_77710000!RtlDebugFreeHeap + 0xbf
04  0019fea8  77758b98  001e0498  001e04a0  001e04c1  ntdll_77710000!RtlpFreeHeap + 0xd3d
* * *  WARNING: Unable to verify checksum  for  F:\vulns\Release\heapoverflow.exe
05  0019fefc  00401094  001e0000  00000000  001e04a0  ntdll_77710000!RtlFreeHeap + 0x758
WARNING: Stack unwind information  not  available. Following frames may be wrong.
06  001e0000  01000709  ffeeffee  00000000  001e00a4  heapoverflow + 0x1094
07  001e0004  ffeeffee  00000000  001e00a4  001e00a4  0x1000709
08  001e0008  00000000  001e00a4  001e00a4  001e0000  0xffeeffee
1
HEAP[heapoverflow.exe]: Heap block at  001E0498  modified at  001E04B0  past requested size of  10

上面一句調試輸出信息的意思是,在大小為0x10的堆塊0x001E0498的0x001E04B0覆蓋破壞了,0x10大小的空間加上堆頭的8字節一共0x18字節,0x001E04B0-0x001E0498=0x18,也就是說0x001E04B0位於堆塊數據的最后一個字節上,基於上面的信息,可以分析出程序主要是因為向0x10的堆塊中復制過多數據導致的堆溢出。

頁堆

在調試漏洞的時候,經常需要定位導致漏洞的代碼和函數,比如導致堆溢出的字節復制指令rep movsz等,前面的堆尾檢查方式主要是堆被破壞的場景,不利於定位導致漏洞的代碼。為此。引入了頁堆的概念,開啟后,會在堆塊中增加不可訪問的柵欄頁,溢出覆蓋到柵欄頁就會觸發異常。

 

開啟頁堆:

1
gflags.exe  - i F:\vulns\Release\heapoverflow   + hpa

 

用windbg加載heapoverflow,運行!gflag命令開啟了頁堆,然后g運行后在cmd按下回車鍵斷下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
0 : 000 > g
( 46c .b74): Access violation  -  code c0000005 (!!! second chance !!!)
eax = 00000021  ebx = 01795ff0  ecx = 00000004  edx = 77d364f4  esi = 0012ff38  edi = 01796000
eip = 00401084  esp = 0012ff10  ebp = 01790000  iopl = 0          nv up ei pl nz na po nc
cs = 001b   ss = 0023   ds = 0023   es = 0023   fs = 003b   gs = 0000              efl = 00010202
image00400000 + 0x1084 :
00401084  f3a5            rep movs dword ptr es:[edi],dword ptr [esi]
0 : 000 > dd esi
0012ff38   41414141  41414141  41414141  41414141
0012ff48   00407000  00401327  00000001  01699fb0
0012ff58   0169bf70  00000000  00000000  7ffdd000
0012ff68   c0000005  00000000  0012ff5c  0012fb1c
0012ff78   0012ffc4  00402c50  004060b8  00000000
0012ff88   0012ff94  76281174  7ffdd000  0012ffd4
0012ff98   77d4b3f5  7ffdd000  77cb48a4  00000000
0012ffa8   00000000  7ffdd000  c0000005  76292b35
0 : 000 > dc edi
01796000   ???????? ???????? ???????? ????????  ????????????????
01796010   ???????? ???????? ???????? ????????  ????????????????
01796020   ???????? ???????? ???????? ????????  ????????????????
01796030   ???????? ???????? ???????? ????????  ????????????????
01796040   ???????? ???????? ???????? ????????  ????????????????
01796050   ???????? ???????? ???????? ????????  ????????????????
01796060   ???????? ???????? ???????? ????????  ????????????????
01796070   ???????? ???????? ???????? ????????  ????????????????

可以發現程序在復制A字符串的時候觸發了異常,程序復制到0x11字節的時候被斷下,此時異常還未破壞到堆塊,直接定位導致溢出的復制指令rep movs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
0 : 000 > kb
ChildEBP RetAddr  Args to Child             
WARNING: Stack unwind information  not  available. Following frames may be wrong.
0012ff48  00401327  00000001  01699fb0  0169bf70  image00400000 + 0x1084
* * *  ERROR: Symbol  file  could  not  be found.  Defaulted to export symbols  for  C:\Windows\system32\kernel32.dll  -
0012ff88  76281174  7ffdd000  0012ffd4  77d4b3f5  image00400000 + 0x1327
0012ff94  77d4b3f5  7ffdd000  77cb48a4  00000000  kernel32!BaseThreadInitThunk + 0x12
0012ffd4  77d4b3c8  0040b000  7ffdd000  00000000  ntdll!RtlInitializeExceptionChain + 0x63
0012ffec  00000000  0040b000  7ffdd000  00000000  ntdll!RtlInitializeExceptionChain + 0x36
0 : 000 > ub image00400000 + 0x1327
image00400000 + 0x1301 :
00401301  e847120000      call    image00400000 + 0x254d  ( 0040254d )
00401306  e8af0e0000      call    image00400000 + 0x21ba  ( 004021ba )
0040130b  a150994000      mov     eax,dword ptr [image00400000 + 0x9950  ( 00409950 )]
00401310  a354994000      mov     dword ptr [image00400000 + 0x9954  ( 00409954 )],eax
00401315  50               push    eax
00401316  ff3548994000    push    dword ptr [image00400000 + 0x9948  ( 00409948 )]
0040131c  ff3544994000    push    dword ptr [image00400000 + 0x9944  ( 00409944 )]
00401322  e8d9fcffff      call    image00400000 + 0x1000  ( 00401000 )

根據棧回溯,調用rep movs的上一層函數位於image00400000+0x1084的上一條指令,也就是00401322,此處調用了00401000函數,很容易發現這是主入口函數:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
0 : 000 > uf  00401000
image00400000 + 0x1000 :
00401000  83ec24           sub     esp, 24h
00401003  b908000000      mov     ecx, 8
00401008  53               push    ebx
00401009  55               push    ebp
0040100a  56               push    esi
0040100b  57               push    edi
0040100c  be44704000      mov     esi,offset image00400000 + 0x7044  ( 00407044 )
00401011  8d7c2410         lea     edi,[esp + 10h ]
00401015  f3a5            rep movs dword ptr es:[edi],dword ptr [esi]
00401017  68ffff0000       push     0FFFFh
0040101c  6800100000       push     1000h      / / 堆塊大小壓入
00401021  6a04             push     4
00401023  a4              movs    byte ptr es:[edi],byte ptr [esi]
00401024  ff150c604000    call    dword ptr [image00400000 + 0x600c  ( 0040600c )] / / 調用HeapCreate創建堆塊
0040102a  8be8             mov     ebp,eax
0040102c  a16c704000      mov     eax,dword ptr [image00400000 + 0x706c  ( 0040706c )]
00401031  48               dec     eax
00401032  a36c704000      mov     dword ptr [image00400000 + 0x706c  ( 0040706c )],eax
00401037  7808             js      image00400000 + 0x1041  ( 00401041 )
 
image00400000 + 0x1039 :
00401039  ff0568704000    inc     dword ptr [image00400000 + 0x7068  ( 00407068 )]
0040103f  eb0d            jmp     image00400000 + 0x104e  ( 0040104e )
 
image00400000 + 0x1041 :
00401041  6868704000       push    offset image00400000 + 0x7068  ( 00407068 )
00401046  e896000000      call    image00400000 + 0x10e1  ( 004010e1 )
0040104b  83c404           add     esp, 4
 
image00400000 + 0x104e :
0040104e  6a10             push     10h
00401050  6a00             push     0
00401052  55               push    ebp
00401053  ff1508604000    call    dword ptr [image00400000 + 0x6008  ( 00406008 )]   / / 調用HeapAlloc分配 0x10 的堆塊
00401059  8bd8             mov     ebx,eax    / / 分配的堆塊地址
0040105b  53               push    ebx
0040105c  6830704000       push    offset image00400000 + 0x7030  ( 00407030 )
00401061  e84a000000      call    image00400000 + 0x10b0  ( 004010b0 )
00401066  8d7c2418         lea     edi,[esp + 18h ]
0040106a  83c9ff           or       ecx, 0FFFFFFFFh
0040106d  33c0             xor     eax,eax
0040106f  83c408           add     esp, 8
00401072  f2ae            repne scas byte ptr es:[edi]
00401074  f7d1             not      ecx    / / 獲取 str 長度
00401076  2bf9             sub     edi,ecx
00401078  53               push    ebx
00401079  8bc1             mov     eax,ecx
0040107b  8bf7             mov     esi,edi   / / str  =  0x20
0040107d  8bfb             mov     edi,ebx   / / 分配的堆塊只有 0x10
0040107f  6a00             push     0
00401081  c1e902          shr     ecx, 2
00401084  f3a5            rep movs dword ptr es:[edi],dword ptr [esi]
00401086  8bc8             mov     ecx,eax
00401088  55               push    ebp
00401089  83e103           and      ecx, 3
0040108c  f3a4            rep movs byte ptr es:[edi],byte ptr [esi]   / / 0x20  0x10  循環復制導致溢出
0040108e  ff1504604000    call    dword ptr [image00400000 + 0x6004  ( 00406004 )]
00401094  55               push    ebp
00401095  ff1500604000    call    dword ptr [image00400000 + 0x6000  ( 00406000 )]
0040109b  5f               pop     edi
0040109c  5e               pop     esi
0040109d  5d               pop     ebp
0040109e  33c0             xor     eax,eax
004010a0  5b               pop     ebx
004010a1  83c424           add     esp, 24h
004010a4  c3              ret

0x04整數溢出漏洞原理

整數分為有符號和無符號兩類,有符號數以最高位作為符號位,正整數最高位為1,負整數最高位為0,不同類型的整數在內存中有不同的取值范圍,unsigned int = 4字節,int = 4字節,當存儲的數值超過該類型整數的最大值就會發生溢出。

 

在一些有符號和無符號轉換的過程中最有可能發生整數溢出漏洞。

 

 

基於棧的整數溢出

基於棧的整數溢出的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "stdio.h"
#include "string.h"
 
int  main( int  argc, char  * argv){
 
     int  i;
     char buf[ 8 ];     / /  棧緩沖區
     unsigned short  int  size;     / /  無符號短整數取值范圍: 0  65535
     char overflow[ 65550 ];
 
     memset(overflow, 65 ,sizeof(overflow));     / /  填充為“A”字符
 
     printf( "請輸入數值:\n" );
     scanf( "%d" ,&i);        / / 輸入 65540
 
     size  =  i;
     printf( "size:%d\n" ,size);     / /  輸出系統識別出來的size數值   4
     printf( "i:%d\n" ,i);     / /  輸出系統識別出來的i數據    65540
 
     if  (size >  8 )   / / 邊界檢查
         return  - 1 ;
     memcpy(buf,overflow,i);         / /  棧溢出
 
     return  0 ;
 
}

代碼中size變量是無符號短整型,取值范圍是0~65535,輸入的值大於65535就會發生溢出,最后得到size為4,這樣會通過邊界檢查,但是用memcpy復制數據的時候,使用的是int類型的參數i,這個值是輸入的65540,就會發生棧溢出:

 

基於堆的整數溢出

基於堆的整數溢出的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include "stdio.h"
#include "windows.h"
 
 
int  main( int  argc, char  *  argv)
{
     int *  heap;
     unsigned short  int  size;   / /  無符號短整數取值范圍: 0  65535
     char  * pheap1,  * pheap2;
     HANDLE hHeap;
 
     printf( "輸入size數值:\n" );
     scanf( "%d" ,&size);
 
     hHeap  =  HeapCreate(HEAP_GENERATE_EXCEPTIONS,  0x100 0xfff );        / / 創建一個堆塊
 
     if  (size < =  0x50 )
     {
         size  - =  5 ;               / / 輸入 2 ,size = - 3 = 65533
         printf( "size:%d\n" ,size);
         pheap1  =  HeapAlloc(hHeap,  0 , size);         / /     pheap1會分配過大的堆塊,導致溢出!
         pheap2  =  HeapAlloc(hHeap,  0 0x50 );
     }
 
     HeapFree(hHeap,  0 , pheap1);
     HeapFree(hHeap,  0 , pheap2);
 
     return  0 ;
}

代碼中的size是unsigned short int類型,當輸入小於5,size減去5會得到負數,但由於unsigned short int取值范圍的限制無法識別負數,得到正數65533,最后分配得到過大的堆塊,溢出覆蓋了后面的堆管理結構:

 

0x05 格式化字符串漏洞原理

格式化漏洞產生的原因主要是對用戶輸入的內容沒有做過濾,有些輸入數據都是作為參數傳遞給某些執行格式化操作的函數的,比如:printf,fprintf,vprintf,sprintf。

 

惡意用戶可以使用%s和%x等格式符,從堆棧和其他內存位置輸出數據,也可以使用格式符%n向任意地址寫入數據,配合printf()函數就可以向任意地址寫入被格式化的字節數,可能導致任意代碼執行,或者讀取敏感數據。

 

以下面的代碼為例講解格式化字符串漏洞原理:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <string.h>
 
int  main ( int  argc, char  * argv[])
{
         char buff[ 1024 ];     / /  設置棧空間
 
         strncpy(buff,argv[ 1 ],sizeof(buff) - 1 );
         printf(buff);  / / 觸發漏洞
 
         return  0 ;
}

可以發現當輸入數據包含%s和%x格式符的時候,會意外輸出其他數據:

 

 

用ollydbg附加調試程序,執行前需要先設置命令行參數,調試-參數-命令行:test-%x

 

 

在運行程序后,傳遞給printf的參數只有test-%x,但是他把輸入參數test-%x之后的另一個棧上數據當做參數傳給了printf函數,因為printf基本類型是:

1
printf(“格式化控制符”,變量列表);

傳遞給printf的參數只有一個,但是程序默認將棧上的下一個數據作為參數傳遞給了printf函數,剛好下一個數據是strcpy()函數的目標地址,就是buff變量,buff剛好指向test-%x的地址0x0019fec4,所以程序會輸出0x0019fec4,如果后面再加一個%x就會將src參數的值也輸出了,這樣就可以遍歷整個棧上數據了。

 

 

除了利用%x讀取棧上數據,還可以用%n寫入數據修改返回地址來實現漏洞利用。

0x06 雙重釋放漏洞原理

Double Free漏洞是由於對同一塊內存進行二次釋放導致的,利用漏洞可以執行任意代碼,編譯成release示例代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include "windows.h"
 
int  main ( int  argc, char  * argv[])
{
     void  * p1, * p2, * p3;
 
     p1  =  malloc( 100 );   
     printf( "Alloc p1:%p\n" ,p1);
     p2  =  malloc( 100 );
     printf( "Alloc p2:%p\n" ,p2);
     p3  =  malloc( 100 );
     printf( "Alloc p3:%p\n" ,p3);
 
     printf( "Free p1\n" );
     free(p1);
     printf( "Free p3\n" );
     free(p3);
     printf( "Free p2\n" );
     free(p2);
 
     printf( "Double Free p2\n" );  / / 二次釋放
     free(p2);
 
     return  0 ;
}

在二次釋放p2的時候就會發生程序崩潰,但是並不是每次出現Double Free都會發生崩潰,要有堆塊合並的動作發生才會發生崩潰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include "windows.h"
 
int  main ( int  argc, char  * argv[])
{
     void  * p1, * p2, * p3;
 
     p1  =  malloc( 100 );   
     printf( "Alloc p1:%p\n" ,p1);
     p2  =  malloc( 100 );
     printf( "Alloc p2:%p\n" ,p2);
     p3  =  malloc( 100 );
     printf( "Alloc p3:%p\n" ,p3);
 
     printf( "Free p2\n" );
     free(p2);
     printf( "Double Free p2\n" );
     free(p2);
     printf( "Free p1\n" );
     free(p1);
     printf( "Free p3\n" );
     free(p3);       
 
     return  0 ;
}

 

 

雙重釋放原理圖

 

在釋放過程中,鄰近的已經釋放的堆塊存在合並操作,這會改變原有堆頭信息,之后再對其地址引用釋放就會發生訪問異常。

0x07釋放后重引用漏洞原理

通過以下代碼理解UAF漏洞原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
 
#define size 32
 
int  main( int  argc, char  * * argv) {
 
     char  * buf1;
     char  * buf2;
 
     buf1  =  (char  * ) malloc(size);
     printf( "buf1:0x%p\n" , buf1);
     free(buf1);
 
     / /  分配 buf2 去“占坑”buf1 的內存位置
     buf2  =  (char  * ) malloc(size);
     printf( "buf2:0x%p\n\n" , buf2);
 
     / /  對buf2進行內存清零
     memset(buf2,  0 , size);
     printf( "buf2:%d\n" * buf2);
 
     / /  重引用已釋放的buf1指針,但卻導致buf2值被篡改
     printf( "==== Use After Free ===\n" );
     strncpy(buf1,  "hack" 5 );
     printf( "buf2:%s\n\n" , buf2);
 
     free(buf2);
}

 

buf2 “占坑”了buf1 的內存位置,經過UAF后,buf2被成功篡改了

 

程序通過分配和buf1大小相同的堆塊buf2實現占坑,似的buf2分配到已經釋放的buf1內存位置,但由於buf1指針依然有效,並且指向的內存數據是不可預測的,可能被堆管理器回收,也可能被其他數據占用填充,buf1指針稱為懸掛指針,借助懸掛指針buf1將內存賦值為hack,導致buf2也被篡改為hack。

 

如果原有的漏洞程序引用到懸掛指針指向的數據用於執行指令,就會導致任意代碼執行。

 

在通常的瀏覽器UAF漏洞中,都是某個C++對象被釋放后重引用,假設程序存在UAF的漏洞,有個懸掛指針指向test對象,要實現漏洞利用,通過占坑方式覆蓋test對象的虛表指針,虛表指針指向虛函數存放地址,現在讓其指向惡意構造的shellcode,當程序再次引用到test對象就會導致任意代碼執行。

 

 

UAF漏洞利用原理圖

0x08 數組越界訪問漏洞

先區分一下數組越界漏洞和溢出漏洞:數組越界訪問包含讀寫類型,溢出屬於數據寫入;部分溢出漏洞本質確實就是數組越界漏洞。

 

數組越界就像是倒水的時候倒錯了杯子,溢出就像是水從杯子里溢出來。

 

下面代碼為例分析數組越界訪問漏洞:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "stdio.h"
 
int  main(){
     int  index;
     int  array[ 3 =  { 0x11 0x22 0x33 };
 
     printf( "輸入數組索引下標:" );
     scanf( "%d" , &index);
 
     printf( "輸出數組元素:array[%d] = 0x%x\n" , index, array[index]);  / / 數組越界讀操作
     / / array[index]  =  1  / / 數組越界寫操作
     return  0 ;
}

 

執行生成的程序,然后分別輸入12345,輸出結果如上,當輸入的數組下標分別是12的時候,會得到正常數值,但是從索引3開始就超出了原來的數組array的范圍,比如輸入5,就會數組越界訪問array數組,導致讀取不在程序控制范圍內的數值。

 

使用ollydbg調試發現array[5]就是從array開始的第六個數據0x4012A9,已經讀取到了array之外的數據,如果越界訪問距離過大,就會訪問到不可訪問的內存空間,導致程序崩潰。

0x09類型混淆漏洞原理

類型混淆漏洞(Type Confusion)一般是將數據類型A當做數據類型B來解析引用,這就可能導致非法訪問數據從而執行任意代碼,比如將Unit轉成了String,將類對象轉成數據結構。

 

類型混淆漏洞是現在瀏覽器漏洞挖掘的主流漏洞,這類漏洞在java,js等弱類型語言中非常常見。

 

下面的代碼,A類被混淆成B類,就可能導致私有域被外部訪問到:

class A {
    private int value;
};

class B {
    public int value;
};

B attack = AcastB(var); //將A類型混淆轉成B類型
attack.value = 1; //導致可以訪問私有域

以IE/Edge類型混淆漏洞(CVE-2017-0037)為例講解,漏洞原因是函數處理時,沒有對對象類型進行嚴格檢查,導致類型混淆。

 

PoC如下:在PoC中定義了一個table,標簽中定義了表id為th1,在boom()中引用,然后是setInterval設定事件。

 

 

運行PoC,用Windbg附加並加載運行出現崩潰

 

 

從崩潰點可以看到eax作為指針,引用了一個無效地址,導致崩潰,而上一條指令是一個call,這個無效的返回值來自這個call,在這個call處下斷點,ecx作為參數,存放的對象是一個Layout::FlowItem::`vftable虛表

 

 

這里讀取虛表中+4的值,為0時this指針賦值v1,隨后v1+16后返回,因此,Layout::FlowItem::`vftable所屬指針的這個情況是正常的,函數會正常返回進入后續處理邏輯

 

 

讓程序繼續運行,會再次調用該函數,此時ecx並不是一個虛表對象,而是一個int Array對象,這里我們可以通過條件斷點來跟蹤兩個對象的創建過程,重點關注兩個對象創建的函數,一個是FlowItem::`vftable對應的虛表對象,另一個是引發崩潰的int Array對象。這兩個函數的返回值,也就是eax寄存器中存放的指向這兩個創建對象的指針。

 

 

通過跟蹤可以看到第一次調用Readable函數時ecx是一個正常的FlowItem對象,而第二次調用的時候ecx是一個int Array Object。Layout::Patchable >::Readable函數是處理虛表對象的函數,由於boom()函數中引用th1.align導致Readable函數得到第二次引用,由於沒有進行對象屬性檢查,導致第二次調用時將table對象傳入,最終發生類型混淆崩潰。

0x10競爭條件漏洞原理

競爭條件(Race Condition)是由於多個線程/對象/進程同時操作同一資源,導致系統執行違背原有邏輯設定的行為,這類漏洞在linux,內核層面非常多見,在windows和web層面也存在。

 

互斥鎖的出現就是為了解決此類漏洞問題,保證某一對象在特定資源訪問時,其他對象不能操作此資源。

1
競爭條件”發生在多個線程同時訪問同一個共享代碼、變量、文件等沒有進行鎖操作或者同步操作的場景中。 ——Wikipedia - computer_science

比如如下代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#-*-coding:utf-8-*-
import  threading
COUNT  =  0
 
def  Run(threads_name):
     global  COUNT
     read_value  =  COUNT
     print  "COUNT in Thread-%s is %d"  %  ( str (threads_name), read_value)
     COUNT  =  read_value  +  1
 
def  main():
     threads  =  []
     for  in  range ( 10 ):
         =  threading.Thread(target = Run,args = (j,))
         threads.append(t)
         t.start()
     for  in  range ( len (threads)):
         threads[i].join()
     print ( "Finally, The COUNT is %d"  %  (COUNT,))
 
if  __name__  = =  '__main__' :
     main()

執行結果如下:

 

 

按照我們的預想,結果應該都是10,但是發現結果可能存在非預期解,原因就在於我們沒有對變量COUNT做同步制約,導致可能Thread-7在讀COUNT,還沒來得及更改COUNT,Thread-8搶奪資源,也來讀COUNT,並且將COUNT修改為它讀的結果+1,由此出現非預期。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM