科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件利用钩子技术控制进程创建(附源代码)

利用钩子技术控制进程创建(附源代码)

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

本文大胆假设,目标进程是以一种用户模式(外壳函数,CreateProcess(),用一系列的本机API调用的手工的进程创建,等等)创建的。

作者:朱先忠编译 来源:天极网 2007年10月17日

关键字:

  • 评论
  • 分享微博
  • 分享邮件
三、 控制进程创建

  我们的解决方案由一个内核模式驱动程序和一个用户模式应用程序组成。为了开始监视进程创建,我们的应用程序要把服务索引(相应于NtCreateSection())以及交换缓冲区的地址传递到我们的驱动程序。这是由下列代码所完成的:

//打开设备
device=CreateFile("\\\\.\\PROTECTOR",GENERIC_READ|GENERIC_WRITE,
0,0,OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM,0);
//得到NtCreateSection的索引并把它连同输出缓冲区的地址传递给设备
DWORD * addr=(DWORD *)
(1+(DWORD)GetProcAddress(GetModuleHandle("ntdll.dll"),"NtCreateSection"));
ZeroMemory(outputbuff,256);
controlbuff[0]=addr[0];
controlbuff[1]=(DWORD)&outputbuff[0];
DeviceIoControl(device,1000,controlbuff,256,controlbuff,256,&dw,0);

  此代码是显然的-唯一需要注意的是我们得到服务索引的方式。所有来自于ntdll.dll的代理都从一行代码MOV EAX,ServiceIndex开始-它可以适用于任何版本和风味的Windows NT。这是一条5字节长的指令,以MOV EAX操作码作第一字节,服务索引作为留下的4字节。因此,为了得到相应于一些特别的本机API函数的服务索引,所有你要做的是从该地址读取4个字节,-位于从这个代理开始1字节距离的地方。

  现在让我们看一下我们的驱动程序做什么,当它收到来自我们的应用程序的IOCTL时:

NTSTATUS DrvDispatch(IN PDEVICE_OBJECT device,IN PIRP Irp)
{
 UCHAR*buff=0; ULONG a,base;
 PIO_STACK_LOCATION loc=IoGetCurrentIrpStackLocation(Irp);
 if(loc->Parameters.DeviceIoControl.IoControlCode==1000)
 {
  buff=(UCHAR*)Irp->AssociatedIrp.SystemBuffer;
  //钩住服务调度表
  memmove(&Index,buff,4);
  a=4*Index+(ULONG)KeServiceDescriptorTable->ServiceTable;
  base=(ULONG)MmMapIoSpace(MmGetPhysicalAddress((void*)a),4,0);
  a=(ULONG)&Proxy;
  _asm
  {
   mov eax,base
   mov ebx,dword ptr[eax]
   mov RealCallee,ebx
   mov ebx,a
   mov dword ptr[eax],ebx
  }
  MmUnmapIoSpace(base,4);
  memmove(&a,&buff[4],4);
  output=(char*)MmMapIoSpace(MmGetPhysicalAddress((void*)a),256,0);
 }
 Irp->IoStatus.Status=0;
 IoCompleteRequest(Irp,IO_NO_INCREMENT);
 return 0;
}

  正如你所见,这里没有什么特别的-我们只是通过MmMapIoSpace()来把交换缓冲区映射到内核中,另外把我们的代理函数的地址写到服务表(当然,我们这是在把实际的服务执行的地址保存到全局变量RealCallee以后这样做的)。为了改写服务表的适当入口,我们通过MmMapIoSpace()来映射目标地址。为什么我们要这样做?不管怎么说,我们已经可以存取服务表了,不是吗?问题是,服务表可能驻留在一段只读内存中。因此,我们必须检查一下是否我们有对目标空间写的权限,而如果我们没有这个权限,那么在改写服务表之前,我们必须改变页面保护。你不认为这样以来工作太多了吗?因此,我们仅用MmMapIoSpace()来映射我们的目标地址,这样以来,我们就不必担心任何的页面保护问题了-从现在开始,我们假定已有到目标页面写的权限了。现在让我们看一下我们的代理函数:

//这个函数用来确定是否我们应该允许NtCreateSection()调用成功
ULONG __stdcall check(PULONG arg)
{
 HANDLE hand=0;PFILE_OBJECT file=0;
 POBJECT_HANDLE_INFORMATION info;ULONG a;char*buff;
 ANSI_STRING str; LARGE_INTEGER li;li.QuadPart=-10000;
 //检查标志。如果所要求的存取方式不是PAGE_EXECUTE,
 //这并不要紧
 if((arg[4]&0xf0)==0)return 1;
 if((arg[5]&0x01000000)==0)return 1;
 //经由文件句柄得到文件名
 hand=(HANDLE)arg[6];
 ObReferenceObjectByHandle(hand,0,0,KernelMode,&file,&info);
 if(!file)return 1;
  RtlUnicodeStringToAnsiString(&str,&file->FileName,1);
  a=str.Length;buff=str.Buffer;
  while(1)
  {
   if(buff[a]=='.'){a++;break;}
   a--;
  }
  ObDereferenceObject(file);
  //如果它是不可执行的,这也不要紧
  //返回1
  if(_stricmp(&buff[a],"exe")){RtlFreeAnsiString(&str);return 1;}
   //现在,我们要询问用户的选择。
   //把文件名写入缓冲区,并等待直到用户显示响应
   //(第一个DWORD为1意味着我们可以继续)
   //同步存取该缓冲区
   KeWaitForSingleObject(&event,Executive,KernelMode,0,0);
   //把缓冲区的前两个DWORD置为0,
   //把字符串复制到该缓冲区中,并循环下去,直到用户把每一个
   //DWORD置为1.
   //第二个DWORD的值指明用户的响应
  strcpy(&output[8],buff);
  RtlFreeAnsiString(&str);
  a=1;
  memmove(&output[0],&a,4);
  while(1)
  {
   KeDelayExecutionThread(KernelMode,0,&li);
   memmove(&a,&output[0],4);
   if(!a)break;
  }
  memmove(&a,&output[4],4);
  KeSetEvent(&event,0,0);
  return a;
 }
 //仅保存执行上下文并调用check()
 _declspec(naked) Proxy()
 {
  _asm{
   //保存执行上下文并调用check()
   //-后面的依赖于check()所返回的值
   // 如果返回值是1,继续实际的调用。
   //否则,返回STATUS_ACCESS_DENIED
   pushfd
   pushad
   mov ebx,esp
   add ebx,40
   push ebx
   call check
   cmp eax,1
   jne block
   //继续实际的调用
   popad
   popfd
   jmp RealCallee
   //返回STATUS_ACCESS_DENIED
   block:popad
   mov ebx, dword ptr[esp+8]
   mov dword ptr[ebx],0
   mov eax,0xC0000022L
   popfd
   ret 32
  }
 }

  Proxy()保存寄存器和标志,把一个指向服务参数的指针压入栈中并调用check()。其它的依赖于check()所返回的值。如果check()返回TRUE(也就是,我们想要继续请求),那么,Proxy()将恢复寄存器和标志,并且把控制权交给服务实现部分。否则,Proxy()将把STATUS_ACCESS_DENIED写入EAX,恢复ESP并返回-从调用者的观点来看,这就象对NtCreateSection()的调用失败一样-以错误状态STATUS_ACCESS_DENIED返回。
    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章