本部分將對常見的二進制漏洞做系統分析,方便在漏洞挖掘過程中定位識別是什么類型漏洞,工欲善其事,必先利其器。
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
-
p
-
a
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
-
a
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
j
in
range
(
10
):
t
=
threading.Thread(target
=
Run,args
=
(j,))
threads.append(t)
t.start()
for
i
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,由此出現非預期。