首页
社区
课程
招聘
[求助]NtUserBuildHwndList的一点小问题
发表于: 2011-4-6 17:42 15244

[求助]NtUserBuildHwndList的一点小问题

2011-4-6 17:42
15244
昨天一直在纠结这个问题~ NtUserBuildHwndList在内核中直接调用总是返回0xC0000008(无效句柄)这个错误,麻烦好心人帮我看看~

这里是我的调用代码
HWND      HwnBuf[256]={0};
ULONG     BufSize=256*sizeof(HWND);
ULONG     MaxNum=0x100;

 status=Addr_NtUserBuildHwndList(0,
	                                                 0,
				        		 FALSE,
							 ThreadId,
							 MaxNum,
         						 HwnBuf,
		         				 &BufSize);

其中ThreadId是我hook PspCreateThread中得到的,用process Exlorer验证过,绝对没问题……

按照reactOS里的源码,我如此传参调用NtUserBuildHwndList无论如何也不可能返回0xC0000008这个错误的……我另外贴上reactOS里的源码吧。实在是纠结了……完全不懂哪里出错了, 在线等哦~

NTSTATUS
APIENTRY
NtUserBuildHwndList(
   HDESK hDesktop,
   HWND hwndParent,
   BOOLEAN bChildren,
   ULONG dwThreadId,
   ULONG lParam,
   HWND* pWnd,
   ULONG* pBufSize)
{
   NTSTATUS Status;
   ULONG dwCount = 0;

   if (pBufSize == 0)
       return ERROR_INVALID_PARAMETER;

   if (hwndParent || !dwThreadId)
   {
      PDESKTOP Desktop;
      PWINDOW_OBJECT Parent, Window;

      if(!hwndParent)
      {
         if(hDesktop == NULL && !(Desktop = IntGetActiveDesktop()))
         {
            return ERROR_INVALID_HANDLE;
         }

         if(hDesktop)
         {
            Status = IntValidateDesktopHandle(hDesktop,
                                              UserMode,
                                              0,
                                              &Desktop);
            if(!NT_SUCCESS(Status))
            {
               return ERROR_INVALID_HANDLE;
            }
         }
         hwndParent = Desktop->DesktopWindow;
      }
      else
      {
         hDesktop = 0;
      }

      if((Parent = UserGetWindowObject(hwndParent)) &&
         (Window = Parent->spwndChild))
      {
         BOOL bGoDown = TRUE;

         Status = STATUS_SUCCESS;
         while(TRUE)
         {
            if (bGoDown)
            {
               if(dwCount++ < *pBufSize && pWnd)
               {
                  _SEH2_TRY
                  {
                     ProbeForWrite(pWnd, sizeof(HWND), 1);
                     *pWnd = Window->hSelf;
                     pWnd++;
                  }
                  _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
                  {
                     Status = _SEH2_GetExceptionCode();
                  }
                  _SEH2_END
                  if(!NT_SUCCESS(Status))
                  {
                     SetLastNtError(Status);
                     break;
                  }
               }
               if (Window->spwndChild && bChildren)
               {
                  Window = Window->spwndChild;
                  continue;
               }
               bGoDown = FALSE;
            }
            if (Window->spwndNext)
            {
               Window = Window->spwndNext;
               bGoDown = TRUE;
               continue;
            }
            Window = Window->spwndParent;
            if (Window == Parent)
            {
               break;
            }
         }
      }

      if(hDesktop)
      {
         ObDereferenceObject(Desktop);
      }
   }
   else
   {
      PETHREAD Thread;
      PTHREADINFO W32Thread;
      PLIST_ENTRY Current;
      PWINDOW_OBJECT Window;

      Status = PsLookupThreadByThreadId((HANDLE)dwThreadId, &Thread);
      if(!NT_SUCCESS(Status))
      {
         return ERROR_INVALID_PARAMETER;
      }
      if(!(W32Thread = (PTHREADINFO)Thread->Tcb.Win32Thread))
      {
         ObDereferenceObject(Thread);
         DPRINT("Thread is not a GUI Thread!\n");
         return ERROR_INVALID_PARAMETER;
      }

      Current = W32Thread->WindowListHead.Flink;
      while(Current != &(W32Thread->WindowListHead))
      {
         Window = CONTAINING_RECORD(Current, WINDOW_OBJECT, ThreadListEntry);
         ASSERT(Window);

         if(bChildren || Window->spwndOwner != NULL)
         {
             if(dwCount < *pBufSize && pWnd)
             {
                Status = MmCopyToCaller(pWnd++, &Window->hSelf, sizeof(HWND));
                if(!NT_SUCCESS(Status))
                {
                   SetLastNtError(Status);
                   break;
                }
             }
             dwCount++;
         }
         Current = Current->Flink;
      }

      ObDereferenceObject(Thread);
   }

   *pBufSize = dwCount;
   return STATUS_SUCCESS;
}

[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
免费 0
支持
分享
最新回复 (21)
雪    币: 67
活跃值: (91)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
2
哎……悲剧了 =。-
2011-4-6 19:43
0
雪    币: 7651
活跃值: (523)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
3
句柄是不是有效,得看在哪个进程里,别的进程里的有效句柄在别一个进程里不一定有效
2011-4-6 21:22
0
雪    币: 67
活跃值: (91)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
4
嘿嘿,回教主……你指的进程句柄表吧,其实它返回的错误是无效句柄,但是和我传入的参数无关的……按照reactOS的源码,是在第一个分支,验证if(hwndParent || !dwThreadId)里才会返回无效句柄的,那样是和hDesk , hwndParent 的有效性有关,但是我两者都设为0,dwThreadId为非0,所以根本就不会进入第一个分支,更不应该返回无效句柄错误了……这才是我郁闷的地方。
我把那部分代码弄到应用层去,再传给驱动试试……我监视过,应用层的EnumThreadWindows调用是木有问题的,而且传参方式和我的一样的

无论如何,谢教主的回复咯~ 大家都不理我
2011-4-6 22:42
0
雪    币: 7651
活跃值: (523)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
5
发重复了。。。
最好的办法还是自己调试吧~~
2011-4-7 11:36
0
雪    币: 7651
活跃值: (523)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
6
ReactOS里面的代码只能当做参考,离真正的Win代码还有很多差距呢~~
我看了一下,错误可能出在下面这个地方,希望对你有点帮助

NTSTATUS __stdcall ValidateHdesk(HANDLE Handle, KPROCESSOR_MODE Object, ACCESS_MASK DesiredAccess, int a4)
{
  int v4; // eax@1
  NTSTATUS laststatus; // edi@1
  int v6; // esi@1
  NTSTATUS status; // eax@1
  char v9; // sf@1

  status = ObReferenceObjectByHandle(
             Handle,
             DesiredAccess,
             (POBJECT_TYPE)ExDesktopObjectType,
             Object,
             (PVOID *)&Object,
             0);
  v6 = a4;
  laststatus = status;
  v9 = status < 0;
  v4 = Object;
  *(_DWORD *)a4 = Object;
  if ( v9 )
  {
    SetLastNtError(laststatus);
  }
  else
  {
    if ( *(_DWORD *)v4 != gSessionId || *(_BYTE *)(v4 + 23) & 0xE0 )
    {
      laststatus = 0xC0000008u;
      SetLastNtError(0xC0000008u);
      ObfDereferenceObject(*(PVOID *)v6);
    }
  }
  return laststatus;
}
2011-4-7 11:37
0
雪    币: 7651
活跃值: (523)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
7
终于搞定这个问题了~~
根本原因是NtUserBuildHwndList的最后两个参数必须是ring3地址!
text:BF830E80 loc_BF830E80:                           ; CODE XREF: sub_BF830D9F-31j
.text:BF830E80                 push    4               ; Alignment
.text:BF830E82                 mov     eax, edi
.text:BF830E84                 shl     eax, 2
.text:BF830E87                 push    eax             ; Length
.text:BF830E88                 push    dword ptr [ebp+1Ch] ; Address
.text:BF830E8B                 call    ds:ProbeForWrite(x,x,x) //对HwndBuffer验证
.text:BF830E8B
.text:BF830E91                 mov     edx, [ebp+20h]  ; pBufSize
.text:BF830E94                 mov     eax, _Win32UserProbeAddress //0x7fff0000 
.text:BF830E99                 cmp     edx, eax
.text:BF830E9B                 jnb     loc_BF830D73    ; 若传入的是内核地址,这时将会跳走,然后产生异常


以上就是NtUserBuildHwndList中对参数的验证,对倒数第二个参数使用ProbeForWrite进行检查,显然这必须是一个ring3地址才行。如果检查未通过将产生异常,在异常处理函数里有如下代码:
.text:BF830D9F sub_BF830D9F    proc near               ; DATA XREF: .rdata:BF98B2F8o
.text:BF830D9F
.text:BF830D9F ; FUNCTION CHUNK AT .text:BF830D38 SIZE 00000050 BYTES
.text:BF830D9F ; FUNCTION CHUNK AT .text:BF830EE9 SIZE 00000063 BYTES
.text:BF830D9F
.text:BF830D9F                 mov     esp, [ebp-18h]
.text:BF830DA2                 mov     edi, 0C0000008h //0xC0000008状态码在这儿
.text:BF830DA7                 or      dword ptr [ebp-4], 0FFFFFFFFh
.text:BF830DAB                 mov     esi, [ebp-20h]
.text:BF830DAE                 jmp     loc_BF830EC5 //跳回原函数的收尾部分,edi将赋值给eax,即返回值NTSTATUS



正是以上代码导致了0xC0000008错误码的产生,而不是我上面说的那个检查DesktopHandle的函数。
而接下会对最后一个参数也会进行检查,要求必须小于Win32UserProbeAddress,也就是0x7fff0000,如果这里不满足就会跳走,然后程序会访问0x7fff0000产生一个异常,异常后同样会跳到上面的异常处理代码中,最后产生一个0xC0000008的状态码。

经我测试,把最后两个地址改成ring3的地址就可以成功了,这个地址可以由ring3传来,也可以使用ZwAllocateVirtuelMemory申请,总之是ring3的地址就行了~~
后面附上我的一个测试代码.

使用方法:打开DbgView,先加载驱动,然后运行exe,点击"Help"->"About",程序将会向驱动发送一个DeviceIoControl,然后就可以查看结果了~

ps:大概只有我这么蛋疼的人才会花这么多时间去解决这么一个蛋疼的问题吧。。。
不过更蛋疼的是为什么NtUserBuildHwndList会先去BuildHwndList,然后才会检查传入的参数啊。。。
发现参数不正确,然后再FreeHwndList,早知如此,何必当初呢?真是蛋疼的很~~
而且参数不正确的话,为什么返回的是0xC0000008而不是0xC000000D(STATUS_INVALID_PARAMETER)之类呢?
上传的附件:
2011-4-7 14:40
0
雪    币: 67
活跃值: (91)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
8
哈哈,葱白下你的反汇编功底~ 我也反过那个函数,但是看着看着就趴着睡着了。
我改改,把代码修正下~不过看起来的确是个很蛋疼的问题,谢教主了
2011-4-7 15:17
0
雪    币: 7651
活跃值: (523)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
9
想起来了,它先BuildHwndList的目的是为了告诉你它到底需要多大的缓冲区~~
2011-4-7 15:19
0
雪    币: 7651
活跃值: (523)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
10
主要还是调试啊。。。
2011-4-7 15:19
0
雪    币: 67
活跃值: (91)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
11
换句话说,也只有你会这么乐意帮助别人了……嘿嘿
2011-4-7 15:19
0
雪    币: 67
活跃值: (91)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
12
我看你用的IDA哦,我的IDA不知道为啥很多符号解析不了……所以一看就晕菜了。然后用windbg直接u 一下,更晕了
2011-4-7 15:21
0
雪    币: 7651
活跃值: (523)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
13
先调试找出现错误的地方,然后IDA分析,IDA可以加载pdb符号的啊。。。
2011-4-7 15:32
0
雪    币: 67
活跃值: (91)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
14
嗯嗯,自己工程生成的PDB是可用……但是我反 win32k.sys这个文件时,那些未导出符号全部用sub_xxxx 这种毫无意义的名称表示,不知道怎么能像windbg一样,那些函数名一类的,全部能解析出来……蛋疼啊~它说它会自动搜索符号文件,我咋就没看见它搜索了呢,依旧是sub_fuck  
2011-4-7 15:40
0
雪    币: 7651
活跃值: (523)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
15
最简单方法,把pdb和bin放在同一目录,然后IDA菜单里"Load Pdb",就OK了~~
2011-4-7 15:49
0
雪    币: 67
活跃值: (91)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
16
哦? 我试试看哈~

有了PDB,妈妈再也不用担心我反汇编了,哪里不对反哪里~so easy =.=
2011-4-7 15:55
0
雪    币: 67
活跃值: (91)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
17
教主,符号可以全部解析出来了, 我也发现你说的那个验证了,一个probeForWrite ,一个Win32UserProbeAddress对比,嗯嗯……这下IDA好看多了~

教我这么基本的技巧,肯定很郁闷吧……哈哈
2011-4-7 16:07
0
雪    币: 7651
活跃值: (523)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
18
呵呵,有收获就好~
2011-4-7 17:11
0
雪    币: 67
活跃值: (91)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
19
教主,我刚调戏了一下,其实还有些问题…… hwndBuffer可以不是ring3的缓冲区,但是BufferSize必须是。返回0xC0000008的原因不止是这两个缓冲区的验证哦,还有一个PtiFromThreadId这个函数,因为我是在PspCreateThread后获取的ThreaId,这时PtiFromThreadId内部调用PsGetThreadWin32Thread会失败……然后紧接着PtiFromThreadId的跳转分支就会返回0xC0000008
2011-4-7 18:59
0
雪    币: 7651
活跃值: (523)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
20
hwndBuffer必须是ring3的地址,ProbeForWrite是专门用来验证ring3地址的,自己看MSDN好了~~
如果不是,ProbeForWrite就会引起异常的,不信可以自已看ProbeForWrite的反汇编代码~~
2011-4-7 19:25
0
雪    币: 67
活跃值: (91)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
21
cmp     eax,dword ptr [nt!MmUserProbeAddress (80559a54)]


了了~ 我一直没反过这个函数,一直以为它只会校验一下页属性,对齐一类的~
2011-4-7 19:35
0
雪    币: 208
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
教主,为什么我导入不了user32的符号?我用的windbg 
2019-4-25 11:08
0
游客
登录 | 注册 方可回帖
返回
//