首页
社区
课程
招聘
[原创]内核NTSTATUS状态码和应用层错误代码的关系:
发表于: 2022-4-15 16:00 17316

[原创]内核NTSTATUS状态码和应用层错误代码的关系:

2022-4-15 16:00
17316

​ (注:以下为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) )

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 6
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//