-
-
[原创]内核NTSTATUS状态码和应用层错误代码的关系:
-
发表于: 2022-4-15 16:00 17266
-
(注:以下为win32环境下的结果。)
今天我在编写一个内核和应用层通信代码时出现了一个问题,当我在应用层使用DeviceIoControl来进行IRP操作时,如果DeviceIoControl调用失败不会得到错误码,需要调用GetLastError函数才能得到错误码,而这个错误码和内核的错误码不相同我根本无法定位。
DeviceIoControl function (ioapiset.h) - Win32 apps | Microsoft Docs
GetLastError function (errhandlingapi.h) - Win32 apps | Microsoft Docs
而且我得到的应用层错误码也很令人疑惑:
有更多数据可用?这是啥错误。于是乎我就开始了逆向分析了,找到该错误代码和我的内核代码中的关联。
(注:以下源代码参考WRK和ReactOS)
首先抛出结论方便大家直接使用。
内核中会通过RtlNtStatusToDosErrorNoTeb函数来将NTSTATUS错误码设置成GetLastError中的操作系统错误码,并将错误码设置成线程环境快TEB中的LastErrorValue字段的内容。
调用GetLastError函数得到的错误码就是线程环境快Teb中LastErrorValue字段的内容。
函数原型:(参考wrk和ReactOS)
首先添加一个断点来方便调试:
然后通过双机调试断在该断点处,并查看栈空间:
(注:打码的地方为自己的代码空间)
这里可以看到在真正调用到我们内核写的DeviceIoControl函数时前面的函数:
因为DeviceIoControl是有一个status作为返回值的所以往上肯定有代码会接受这个返回值,于是我就开始往上一个一个分析函数:
首先是IofCallDriver,可以看到它的前缀是nt!,表明是nt内核模块,采用lm来查看该模块对应的文件:
可以看到该nt模块对应的是ntkrpamp.exe,该exe包含了Windows NT 内核空间的内核和执行层等很多东西。
这里我采用wrk的源码来分析该函数:
可以看到该函数就调用了我们内核代码中的DeviceIocontrol然后返回对应的返回值,所以继续往上查看IopSynchronousServiceTail函数的内容:
在IDA中该函数莫名其妙用了一个PDEVICE_OBJECT变量来接受前面本应返回NTSTATUS变量的IofCallDriver函数的返回值,这里让我觉得很诡异,所以我就用WRK来看了,就没用IDA分析了。
该函数虽然调用了IoCallDriver函数,不是我们前面那个函数,但是在WRK中有一个宏定义
这样就解释得通了,IopSynchronousServiceTail函数还是得到了一个返回值,然后继续返回,所以继续向上查看IopXxxControlFile:
该函数结束时返回了IopSynchronousServiceTail函数的值,所以继续往上查看NtDeviceIoControlFile的值:
结果同上,再往上就是:
这三个函数,这两个没用实质性内容,主要是用来从用户层进入内核层,当在应用层调用ZwDeviceIoControl后会通过这上面的函数来进入内核层调用NtDeviceIoControlFile,具体原理与这里的内容无关,暂时跳过。
所以现在需要分析:
函数的内容,它是在KERNELBASE.dll模块中:
首先说明在ntdll.dll中 Zw开头和Nt开头后面名称一样的函数内容是相同的,比如这里:
然后 DeviceIoControl函数,在调用NtDeviceIoControlFile函数得到返回值NTSTAUTS类型时调用了BaseSetLastNTError函数来处理该返回值,步入查看BaseSetLastNTError函数:
这个函数调用RtlNtStatusToDosError来处理NTSTATUS Status参数,然后返回了一个ULONG类型的变量,就很像我们想要的内容,将内核的NTSTATUS对应的错误代码转换成GetLastError对应的错误代码。在ntdll.dll中逆向分析该函数:
返回了一个RtlNtStatusToDosErrorNoTeb函数,继续分析:
这个函数就是关键函数了,一看就是将传进来的NTSTAUTS变量一顿操作后,变成了一个int类型的,然后返回了。
我的DeviceIoControl中有三种我自己指定的返回值:
我猜234对应的是和STATUS_BUFFER_OVERFLOW的值,然后编写一个简单的驱动代码来打印该NTSTATUS对应的应用层error:
结果如下:
ERROR_MORE_DATA
234
(
0xEA
)
More data
is
available.
ERROR_MORE_DATA
234
(
0xEA
)
More data
is
available.
ULONG
RtlNtStatusToDosErrorNoTeb (
IN NTSTATUS Status
)
/
*
+
+
Routine Description:
This routine converts an NT status code to its DOS
/
OS
2
equivalent
and
returns the translated value.
Arguments:
Status
-
Supplies the status value to convert.
Return Value:
The matching DOS
/
OS
2
error code.
-
-
*
/
{
ULONG Offset;
ULONG Entry;
ULONG Index;
/
/
/
/
Convert
any
HRESULTs to their original form of a NTSTATUS
or
a
/
/
WIN32 error
/
/
if
(Status &
0x20000000
) {
/
/
/
/
The customer bit
is
set
so lets just
pass
the
/
/
error code on thru
/
/
return
Status;
}
else
if
((Status &
0xffff0000
)
=
=
0x80070000
) {
/
/
/
/
The status code was a win32 error already.
/
/
return
(Status &
0x0000ffff
);
}
else
if
((Status &
0xf0000000
)
=
=
0xd0000000
) {
/
/
/
/
The status code
is
a HRESULT
from
NTSTATUS
/
/
Status &
=
0xcfffffff
;
}
/
/
/
/
Scan the run length table
and
compute the entry
in
the translation
/
/
table that maps the specified status code to a DOS error code.
/
/
Entry
=
0
;
Index
=
0
;
do {
if
((ULONG)Status >
=
RtlpRunTable[Entry
+
1
].BaseCode) {
Index
+
=
(RtlpRunTable[Entry].RunLength
*
RtlpRunTable[Entry].CodeSize);
}
else
{
Offset
=
(ULONG)Status
-
RtlpRunTable[Entry].BaseCode;
if
(Offset >
=
RtlpRunTable[Entry].RunLength) {
break
;
}
else
{
Index
+
=
(Offset
*
(ULONG)RtlpRunTable[Entry].CodeSize);
if
(RtlpRunTable[Entry].CodeSize
=
=
1
) {
return
(ULONG)RtlpStatusTable[Index];
}
else
{
return
(((ULONG)RtlpStatusTable[Index
+
1
] <<
16
) |
(ULONG)RtlpStatusTable[Index]);
}
}
}
Entry
+
=
1
;
}
while
(Entry < (sizeof(RtlpRunTable)
/
sizeof(RUN_ENTRY)));
/
/
/
/
The translation to a DOS error code failed.
/
/
/
/
The redirector maps unknown OS
/
2
error codes by ORing
0xC001
into
/
/
the high
16
bits. Detect this
and
return
the low
16
bits
if
true.
/
/
if
(((ULONG)Status >>
16
)
=
=
0xC001
) {
return
((ULONG)Status &
0xFFFF
);
}
return
ERROR_MR_MID_NOT_FOUND;
}
ULONG
RtlNtStatusToDosErrorNoTeb (
IN NTSTATUS Status
)
/
*
+
+
Routine Description:
This routine converts an NT status code to its DOS
/
OS
2
equivalent
and
returns the translated value.
Arguments:
Status
-
Supplies the status value to convert.
Return Value:
The matching DOS
/
OS
2
error code.
-
-
*
/
{
ULONG Offset;
ULONG Entry;
ULONG Index;
/
/
/
/
Convert
any
HRESULTs to their original form of a NTSTATUS
or
a
/
/
WIN32 error
/
/
if
(Status &
0x20000000
) {
/
/
/
/
The customer bit
is
set
so lets just
pass
the
/
/
error code on thru
/
/
return
Status;
}
else
if
((Status &
0xffff0000
)
=
=
0x80070000
) {
/
/
/
/
The status code was a win32 error already.
/
/
return
(Status &
0x0000ffff
);
}
else
if
((Status &
0xf0000000
)
=
=
0xd0000000
) {
/
/
/
/
The status code
is
a HRESULT
from
NTSTATUS
/
/
Status &
=
0xcfffffff
;
}
/
/
/
/
Scan the run length table
and
compute the entry
in
the translation
/
/
table that maps the specified status code to a DOS error code.
/
/
Entry
=
0
;
Index
=
0
;
do {
if
((ULONG)Status >
=
RtlpRunTable[Entry
+
1
].BaseCode) {
Index
+
=
(RtlpRunTable[Entry].RunLength
*
RtlpRunTable[Entry].CodeSize);
}
else
{
Offset
=
(ULONG)Status
-
RtlpRunTable[Entry].BaseCode;
if
(Offset >
=
RtlpRunTable[Entry].RunLength) {
break
;
}
else
{
Index
+
=
(Offset
*
(ULONG)RtlpRunTable[Entry].CodeSize);
if
(RtlpRunTable[Entry].CodeSize
=
=
1
) {
return
(ULONG)RtlpStatusTable[Index];
}
else
{
return
(((ULONG)RtlpStatusTable[Index
+
1
] <<
16
) |
(ULONG)RtlpStatusTable[Index]);
}
}
}
Entry
+
=
1
;
}
while
(Entry < (sizeof(RtlpRunTable)
/
sizeof(RUN_ENTRY)));
/
/
/
/
The translation to a DOS error code failed.
/
/
/
/
The redirector maps unknown OS
/
2
error codes by ORing
0xC001
into
/
/
the high
16
bits. Detect this
and
return
the low
16
bits
if
true.
/
/
if
(((ULONG)Status >>
16
)
=
=
0xC001
) {
return
((ULONG)Status &
0xFFFF
);
}
return
ERROR_MR_MID_NOT_FOUND;
}
DWORD
WINAPI
GetLastError(VOID)
{
/
*
Return the current value
*
/
return
NtCurrentTeb()
-
>LastErrorValue;
}
DWORD
WINAPI
GetLastError(VOID)
{
/
*
Return the current value
*
/
return
NtCurrentTeb()
-
>LastErrorValue;
}
/
/
假设内核中的DeviceIoControl返回时的代码如下:
su
=
STATUS_BUFFER_OVERFLOW;
Irp
-
>IoStatus.Status
=
su;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return
su;
/
/
假设内核中的DeviceIoControl返回时的代码如下:
su
=
STATUS_BUFFER_OVERFLOW;
Irp
-
>IoStatus.Status
=
su;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return
su;
su
=
STATUS_BUFFER_OVERFLOW;
DbgBreakPoint();
Irp
-
>IoStatus.Status
=
su;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return
su;
su
=
STATUS_BUFFER_OVERFLOW;
DbgBreakPoint();
Irp
-
>IoStatus.Status
=
su;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return
su;
02
a7b04af4
84083eec
nt!IofCallDriver
+
0x63
03
a7b04b14
840872a8
nt!IopSynchronousServiceTail
+
0x1f8
04
a7b04bd0
840ce7d3
nt!IopXxxControlFile
+
0x82f
05
a7b04c04
83e8ca6a
nt!NtDeviceIoControlFile
+
0x2a
06
a7b04c04
77c86c04
nt!KiSystemServicePostCall
07
0028f790
77c853ec
ntdll!KiFastSystemCallRet
08
0028f794
75e0ab4d
ntdll!ZwDeviceIoControlFile
+
0xc
09
0028f7f4
77babbc6
KERNELBASE!DeviceIoControl
+
0xf6
0a
0028f820
00f228bc
kernel32!DeviceIoControlImplementation
+
0x80
02
a7b04af4
84083eec
nt!IofCallDriver
+
0x63
03
a7b04b14
840872a8
nt!IopSynchronousServiceTail
+
0x1f8
04
a7b04bd0
840ce7d3
nt!IopXxxControlFile
+
0x82f
05
a7b04c04
83e8ca6a
nt!NtDeviceIoControlFile
+
0x2a
06
a7b04c04
77c86c04
nt!KiSystemServicePostCall
07
0028f790
77c853ec
ntdll!KiFastSystemCallRet
08
0028f794
75e0ab4d
ntdll!ZwDeviceIoControlFile
+
0xc
09
0028f7f4
77babbc6
KERNELBASE!DeviceIoControl
+
0xf6
0a
0028f820
00f228bc
kernel32!DeviceIoControlImplementation
+
0x80
0
: kd> lm
start end module name
83e4c000
84274000
nt (pdb symbols) d:\symbol\ntkrpamp.pdb\
5D110DC0022948A3B3FAF52F08E778402
\ntkrpamp.pdb
0
: kd> lm
start end module name
83e4c000
84274000
nt (pdb symbols) d:\symbol\ntkrpamp.pdb\
5D110DC0022948A3B3FAF52F08E778402
\ntkrpamp.pdb
NTSTATUS __fastcall IofCallDriver(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
NTSTATUS result;
/
/
eax
unsigned
int
v4;
/
/
eax
unsigned __int8 v5;
/
/
cl
char v6;
/
/
al
void
*
retaddr;
/
/
[esp
+
Ch] [ebp
+
4h
]
if
( pIofCallDriver )
return
pIofCallDriver(DeviceObject, Irp, retaddr);
if
(
-
-
Irp
-
>CurrentLocation <
=
0
)
KeBugCheckEx(
0x35u
, (ULONG_PTR)Irp,
0
,
0
,
0
);
v4
=
Irp
-
>Tail.Overlay.PacketType
-
36
;
Irp
-
>Tail.Overlay.PacketType
=
v4;
v5
=
*
(_BYTE
*
)v4;
*
(_DWORD
*
)(v4
+
20
)
=
DeviceObject;
if
( v5
=
=
22
&& ((v6
=
*
(_BYTE
*
)(v4
+
1
), v6
=
=
2
) || v6
=
=
3
) )
result
=
IopPoHandleIrp();
else
result
=
DeviceObject
-
>DriverObject
-
>MajorFunction[v5](DeviceObject, Irp);
return
result;
}
NTSTATUS __fastcall IofCallDriver(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
NTSTATUS result;
/
/
eax
unsigned
int
v4;
/
/
eax
unsigned __int8 v5;
/
/
cl
char v6;
/
/
al
void
*
retaddr;
/
/
[esp
+
Ch] [ebp
+
4h
]
if
( pIofCallDriver )
return
pIofCallDriver(DeviceObject, Irp, retaddr);
if
(
-
-
Irp
-
>CurrentLocation <
=
0
)
KeBugCheckEx(
0x35u
, (ULONG_PTR)Irp,
0
,
0
,
0
);
v4
=
Irp
-
>Tail.Overlay.PacketType
-
36
;
Irp
-
>Tail.Overlay.PacketType
=
v4;
v5
=
*
(_BYTE
*
)v4;
*
(_DWORD
*
)(v4
+
20
)
=
DeviceObject;
if
( v5
=
=
22
&& ((v6
=
*
(_BYTE
*
)(v4
+
1
), v6
=
=
2
) || v6
=
=
3
) )
result
=
IopPoHandleIrp();
else
result
=
DeviceObject
-
>DriverObject
-
>MajorFunction[v5](DeviceObject, Irp);
return
result;
}
PDEVICE_OBJECT __userpurge IopSynchronousServiceTail@<eax>(
int
a1@<eax>, struct _DEVICE_OBJECT
*
DeviceObject, PVOID
Object
,
int
a4, KPROCESSOR_MODE WaitMode, char a6, NTSTATUS a7)
{
PDEVICE_OBJECT DeviceObjecta;
/
/
[esp
+
20h
] [ebp
+
8h
]
...
省略
...
LABEL_47:
a7
=
IofCallDriver(DeviceObject, (PIRP)a1);
if
( !a6 )
ObDereferenceObjectDeferDelete(v7);
DeviceObjecta
=
(PDEVICE_OBJECT)a7;
if
( (_BYTE)a4 && a7 !
=
259
)
{
HIBYTE(a4)
=
KfRaiseIrql(
1u
);
IopCompleteRequest(a1
+
64
, v17, v18, &
Object
, v18);
KfLowerIrql(HIBYTE(a4));
}
if
( a6 )
{
if
( a7
=
=
259
)
{
v15
=
KeWaitForSingleObject(v7
+
92
, Executive, WaitMode, (
*
((_DWORD
*
)v7
+
11
) &
4
) !
=
0
,
0
);
if
( v15
=
=
257
|| v15
=
=
192
)
IopCancelAlertedRequest(a1);
DeviceObjecta
=
(PDEVICE_OBJECT)
*
((_DWORD
*
)v7
+
7
);
}
_InterlockedExchange((volatile __int32
*
)v7
+
17
,
0
);
if
(
*
((_DWORD
*
)v7
+
16
) )
KeSetEvent((PRKEVENT)(v7
+
76
),
0
,
0
);
ObfDereferenceObject(v7);
}
return
DeviceObjecta;
}
PDEVICE_OBJECT __userpurge IopSynchronousServiceTail@<eax>(
int
a1@<eax>, struct _DEVICE_OBJECT
*
DeviceObject, PVOID
Object
,
int
a4, KPROCESSOR_MODE WaitMode, char a6, NTSTATUS a7)
{
PDEVICE_OBJECT DeviceObjecta;
/
/
[esp
+
20h
] [ebp
+
8h
]
...
省略
...
LABEL_47:
a7
=
IofCallDriver(DeviceObject, (PIRP)a1);
if
( !a6 )
ObDereferenceObjectDeferDelete(v7);
DeviceObjecta
=
(PDEVICE_OBJECT)a7;
if
( (_BYTE)a4 && a7 !
=
259
)
{
HIBYTE(a4)
=
KfRaiseIrql(
1u
);
IopCompleteRequest(a1
+
64
, v17, v18, &
Object
, v18);
KfLowerIrql(HIBYTE(a4));
}
if
( a6 )
{
if
( a7
=
=
259
)
{
v15
=
KeWaitForSingleObject(v7
+
92
, Executive, WaitMode, (
*
((_DWORD
*
)v7
+
11
) &
4
) !
=
0
,
0
);
if
( v15
=
=
257
|| v15
=
=
192
)
IopCancelAlertedRequest(a1);
DeviceObjecta
=
(PDEVICE_OBJECT)
*
((_DWORD
*
)v7
+
7
);
}
_InterlockedExchange((volatile __int32
*
)v7
+
17
,
0
);
if
(
*
((_DWORD
*
)v7
+
16
) )
赞赏
- [原创]内核NTSTATUS状态码和应用层错误代码的关系: 17267
- [原创]Windows内核-句柄 18515
- x86-页式管理(Paging) 16944
- [原创]Windows API调用详解 18359