首页
社区
课程
招聘
[分享]采用关闭句柄的方式去掉程序多开的限制
发表于: 2009-12-2 16:38 21786

[分享]采用关闭句柄的方式去掉程序多开的限制

2009-12-2 16:38
21786

下面的文字,主要是为了分享给和我一样的菜鸟,而且这些东西都是很多大牛的文章中的一个非常小的部分。您们直接飘过吧。

    一些程序限制多开的方法很多,比如采用窗口名,进程名,内核对象等等,论坛中也有很多关于这方面的介绍,但是好像没有一个具体的例子,估计是觉得太简单了吧。我研究的这程序的限制多开的方法采用的是“内核对象信号量”。

    要破解其多开也有很多方法:

    1、直接找到其创建信号量的代码,暴力修改相关的跳转代码。
    但是这种方法,需要定位代码位置,然后修改它,随着这个程序的更新,其位置也会改变,需要根据特征码来获得其地址,比较麻烦,不是很通用。
     

    2、hook api,hook创建信号量的API,然后根据创建的名字判断是否是限制多开的信号量,如果是,修改其返回值来解决问题。
    这个方法看起来比较好,但是对于一些带驱动保护,又带代码校验的程序来说,就有些困难。而且也需要修改程序的代码。 

    3、获取这个信号量的句柄,直接关闭它,减少其引用计数,从而解决问题。
    自己认为这个方法相对比较好,首先不需要修改代码,其次对于采用内核对象进行多开的程序,具有通用性和移植性。

    当然还有其他的方法。不过我没想到。所以我选择方案3。

    要解决这个问题:
    1)去掉它的驱动保护。在拜读论坛前辈的相关文章后已经实现。
    2)将DLL插入到这个程序的进程中。这个也已经实现。
    3)根据信号量的名字获取它的句柄。未实现。
    4)关闭这个句柄。 很容易,直接CloseHandle就可以了。

    因此关键是根据名字获取它的句柄这个步骤。在论坛胡乱goole了一通,第一次没找到头绪,太菜,连搜索找东西都慢。于是就在论坛发个求助帖,请大牛们指点。等待的回复还是挺折磨人的,一边刷新求助的页面,一边自己找方法。

    在搜索的过程中,我发现很多前辈写的文章中,有类似的应用,虽然不是针对信号量的,但是比如枚举系统的所有进程等。我就是从枚举进程入手的。这里用到的一个 native API函数 "ZwQuerySystemInformation"。使用这个函数可以获取整个系统的所有句柄的信息,在论坛提供的“Crack_New_Year_Presents_2009”中的“Windows NT 2000 Native API Reference.pdf”,我找到了这个函数的具体应用。
    “Crack_New_Year_Presents_2009”中的很多工具和资料对我的帮助很大,省去了我搜索下载的大量时间,马上2010就到了,期待kanxue老大的“Crack_New_Year_Presents_2010”。

   下面的代码很多是在前辈代码基础上修改的,感谢他们。
  
    更加通用的是不需DLL插入到进程中,而是直接采用打开进程,复制对象,进行名字判断,如果是,则采用创建远线程的方式关闭对象。但是对于我这个程序来说,由于有进程保护,所以没有采用。

#pragma once
#include "StdAfx.h"
#include <malloc.h>
#include <memory.h>

typedef enum _SYSTEM_INFORMATION_CLASS 
{
  SystemBasicInformation = 0,          // 0
  SystemProcessorInformation,          // 1
     SystemPerformanceInformation,        // 2
  SystemTimeOfDayInformation,          // 3
  SystemNotImplemented1,            // 4
  SystemProcessesAndThreadsInformation = 5,  // 5
  SystemCallCounts,              // 6
  SystemConfigurationInformation,        // 7
  SystemProcessorTimes,            // 8
  SystemGlobalFlag,              // 9
  SystemNotImplemented2,            // 10
  SystemModuleInformation,          // 11
  SystemLockInformation,            // 12
  SystemNotImplemented3,            // 13
  SystemNotImplemented4,            // 14
  SystemNotImplemented5,            // 15
  SystemHandleInformation,          // 16
  SystemObjectInformation,          // 17
  SystemPagefileInformation,          // 18
  SystemInstructionEmulationCounts,      // 19
  SystemInvalidInfoClass1,          // 20
  SystemCacheInformation,            // 21
  SystemPoolTagInformation,          // 22
  SystemProcessorStatistics,          // 23
  SystemDpcInformation,            // 24
  SystemNotImplemented6,            // 25
  SystemLoadImage,              // 26
  SystemUnloadImage,              // 27
  SystemTimeAdjustment,            // 28
  SystemNotImplemented7,            // 29
  SystemNotImplemented8,            // 30
  SystemNotImplemented9,            // 31
  SystemCrashDumpInformation,          // 32
  SystemExceptionInformation,          // 33
  SystemCrashDumpStateInformation,      // 34
  SystemKernelDebuggerInformation,      // 35
  SystemContextSwitchInformation,        // 36
  SystemRegistryQuotaInformation,        // 37
  SystemLoadAndCallImage = 38,        // 38
  SystemPrioritySeparation,          // 39
  SystemNotImplemented10,            // 40
  SystemNotImplemented11,            // 41
  SystemInvalidInfoClass2,          // 42
  SystemInvalidInfoClass3,          // 43
  SystemTimeZoneInformation,          // 44
  SystemLookasideInformation,          // 45
  SystemSetTimeSlipEvent,            // 46
  SystemCreateSession,            // 47
  SystemDeleteSession,            // 48
  SystemInvalidInfoClass4,          // 49
  SystemRangeStartInformation,        // 50
  SystemVerifierInformation,          // 51
  SystemAddVerifier,              // 52
  SystemSessionProcessesInformation      // 53
} SYSTEM_INFORMATION_CLASS;

typedef enum _OBJECT_INFORMATION_CLASS 
{
  ObjectBasicInformation = 0,
  ObjectNameInformation = 1,
  ObjectTypeInformation = 2,
} OBJECT_INFORMATION_CLASS;

typedef struct _SYSTEM_HANDLE_INFORMATION 
{
  ULONG ProcessId;                  //进程标识符
  UCHAR ObjectTypeNumber;           //打开的对象的类型
  UCHAR Flags;                   //句柄属性标志
  USHORT Handle;                 //句柄数值,在进程打开的句柄中唯一标识某个句柄         
  PVOID Object;                  //句柄对应的EPROCESS的地址
  ACCESS_MASK GrantedAccess;          //句柄对象的访问权限
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;

typedef struct _SYSTEM_HANDLE_INFORMATION_EX 
{
  ULONG NumberOfHandles;            //句柄数目
  SYSTEM_HANDLE_INFORMATION Information[1];
} SYSTEM_HANDLE_INFORMATION_EX, *PSYSTEM_HANDLE_INFORMATION_EX;

typedef struct _UNICODE_STRING {
  USHORT Length;
  USHORT MaximumLength;
  PWSTR  Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

typedef struct _OBJECT_NAME_INFORMATION 
{
  UNICODE_STRING  Name;

} OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION;

typedef LONG NTSTATUS;

#define NT_SUCCESS(Status)    (((NTSTATUS)(Status)) >= 0)

typedef NTSTATUS (WINAPI *ZWQUERYSYSTEMINFORMATION)(DWORD, PVOID, DWORD, PDWORD);
typedef NTSTATUS (WINAPI *ZWQUERYOBJECT)(IN HANDLE OPTIONAL,IN OBJECT_INFORMATION_CLASS,OUT PVOID OPTIONAL,IN ULONG,OUT PULONG OPTIONAL);

/***********************************************************************************************
* 函数名称 : CloseObjectByName
* 函数用途 : 根据输入的对象名,将其关闭。
* 输入参数 : char  *pObjectName 要关闭的对象名。
* 返    回 : 略
* 注    意 : 只适用用于本进程。
/**********************************************************************************************/
bool CloseObjectByName(char  *pObjectName)
{
  int i;
  ULONG pid;
  ULONG ulSize;
  ULONG* pHandleInfor;
  CHAR pName[200];
  NTSTATUS ntStatus;
  HMODULE hHanlde;
  POBJECT_NAME_INFORMATION ObjName;
  PSYSTEM_HANDLE_INFORMATION_EX Handles;
  ZWQUERYOBJECT ZwQueryObject;
  ZWQUERYSYSTEMINFORMATION ZwQuerySystemInformation;
  
  
  //初始化变量
  ulSize = 0x4000;
  pHandleInfor = NULL;
  ZwQueryObject = NULL;
  ZwQuerySystemInformation =NULL;

  //由于ZwQueryObject和ZwQuerySystemInformation是未导出的函数,需要动态加载Ntdll,dll,然后通过函数GetProcAddress
  //得到它们的函数地址,由于这个dll一般的进程都会在创建的时候加载,所以省略加载,直接获取其模块地址
  hHanlde = GetModuleHandle("ntdll.dll");
  if(NULL == hHanlde)
  {
    //加载Ntdll.dll失败
    return false;
  }

  //获取ZwQuerySystemInformation函数地址  
  ZwQuerySystemInformation = (ZWQUERYSYSTEMINFORMATION)GetProcAddress(hHanlde, "ZwQuerySystemInformation");
  if(NULL == ZwQuerySystemInformation)
  {
    //获取ZwQuerySystemInformation函数地址失败
    return false;
  }
  
  //获取ZwQueryObject函数地址
  ZwQueryObject = (ZWQUERYOBJECT)GetProcAddress(hHanlde, "ZwQueryObject");
  if(NULL == ZwQueryObject)
  {
    //获取ZwQueryObject函数地址失败
    return false;
  }

  //获取系统所有句柄信息
  do
  {
    //申请内存
    pHandleInfor = (ULONG*)malloc(ulSize);
    if(NULL == pHandleInfor)
    {
      //申请内存失败
      return false;
    }

    ntStatus = ZwQuerySystemInformation( SystemHandleInformation, pHandleInfor, ulSize, NULL);    
    if(!NT_SUCCESS(ntStatus))
    {
      //空间不足继续申请。
      free(pHandleInfor);
      ulSize = ulSize * 2;  
      
      //为防止ZwQuerySystemInformation一直失败,程序陷入死循环,当申请的空间超过64M时则返回失败
      if(ulSize > 0x4000000)
      {
        return false;
      }
    }
  }while(!NT_SUCCESS(ntStatus));
    
  //转换数据结构类型
     Handles = (PSYSTEM_HANDLE_INFORMATION_EX)pHandleInfor;
  if(NULL == Handles)
  {
    return false;
  }

  //获取当前进程pid
  pid = GetCurrentProcessId();

  //申请空间,用于存储对象的名字信息
     ObjName =  (POBJECT_NAME_INFORMATION)malloc(0x2000 );
     
  //开始搜索获取的句柄信息,并对句柄对应的对象名进行比较,如果与要求关闭的名字相同,则关闭此句柄
  for(i = 0; i < Handles->NumberOfHandles; i++)
  {  
    
    //对于不是本进程的句柄对象,直接pass掉,如果要实现关闭其它进程的对象,则可以首先根据PID打开这个句柄所在的进程,
    //然后复制此对象,然后进行名字比较,如果相同,则可以通过创建远程线程的方式,关闭掉。
    if(pid != Handles->Information[i].ProcessId)
    {      
      continue;
    }
  
    //获取这个对象的名字信息
    ntStatus = ZwQueryObject((HANDLE)Handles->Information[i].Handle, ObjectNameInformation, ObjName, 0x2000, NULL);
    if(!NT_SUCCESS(ntStatus))
    {
      //查询对象失败,进行下一个
      continue;
    }

    //将unicode 字串转换为 ansi字串
    WideCharToMultiByte(CP_ACP, 0, ObjName->Name.Buffer, -1, pName, 200, NULL, NULL);    
    if( 0 == strcmp(pName, pObjectName))
    {
      //找到对应名字的对象,将其关闭
      CloseHandle((HANDLE)Handles->Information[i].Handle);
    }
  }
  
  //释放申请的空间
  free(Handles);
  free(ObjName);

  return true;
}


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

收藏
免费 7
支持
分享
最新回复 (19)
雪    币: 335
活跃值: (55)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
帖子太乱了,总编辑出错。
2009-12-2 16:39
0
雪    币: 202
活跃值: (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
3
学习学习~~~多发一些东东出来,支持了。
2009-12-2 16:51
0
雪    币: 635
活跃值: (101)
能力值: ( LV12,RANK:420 )
在线值:
发帖
回帖
粉丝
4
ntStatus = ZwQueryObject((HANDLE)Handles->Information[i].Handle, ObjectNameInformation, ObjName, 0x2000, NULL);
   

我很害怕,我看到卡死了
2009-12-2 17:21
0
雪    币: 335
活跃值: (55)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
5
[QUOTE=qihoocom;720799]ntStatus = ZwQueryObject((HANDLE)Handles->Information[i].Handle, ObjectNameInformation, ObjName, 0x2000, NULL);
   

我很害怕,我看到卡死了[/QUOTE]

呵呵,您的评论都很精彩的,很多时候我都看不懂哈,如果有这个函数调用有问题,请您帮我改正下。
我没特别研究过,只是发现所以用上述语句没有产生特别的问题。比较浮躁,没有沉淀的心情。
2009-12-2 18:33
0
雪    币: 7651
活跃值: (523)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
6
ZwQueryObject是有可能卡死的,我以前碰到过,卡死的那个Handle是File类型的,其实是个Device,在ProcessExplorer和Unlocker里都可以得到正确名称,但是我用ZwQueryObject就会卡死不动,只好另外开个线程靠超时机制来避免整个程序卡死
2009-12-2 19:51
0
雪    币: 7651
活跃值: (523)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
7
另外你应该先判断句柄的对象类型,是信号量再复制过来查询,这样可以快很多~
2009-12-2 19:54
0
雪    币: 7651
活跃值: (523)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
8
OMG,你竟然没有复制,那你还能查询,太神奇了。。。
2009-12-2 19:57
0
雪    币: 335
活跃值: (55)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
9
呵呵,谢谢了,可能是我说的不明白,我在前面中有写到,这个只是适应本进程的,所以不需要复制。

    另外为了让这段程序能够不只是针对信号量对象,其他对象也可以,所以我没有加入对信号类型的判断。
   
    对于XP的系统信号量的类型是0x0d。
2009-12-2 20:16
0
雪    币: 335
活跃值: (55)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
10
   了解,看来加入类型判断会更加安全一些。

   可以加入如下代码:

  if (Handles->Information[i].ObjectTypeNumber == 0x0d)  //0x0d 信号量类型值

  谢谢提醒。
2009-12-2 20:19
0
雪    币: 412
活跃值: (30)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
11
查询的句柄指向的是NamedPipe就会卡死
2009-12-2 20:20
0
雪    币: 279
活跃值: (33)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
12
我以前在写枚举句柄程序的时候发现光用线程超时机制是防不住卡死的,这时整个程序都死掉了,应该在NtQueryObject之前先用NtQueryInformationFile查询一下,成功再NtQueryObject。这样再结合线程方式就可以了,但坏处是有些句柄查不出来
2009-12-3 12:18
0
雪    币: 522
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
13
不错的想法  
不过没必要插dll那么麻烦
复制句柄函数有个功能可以直接关掉目标原句柄
2009-12-4 08:50
0
雪    币: 335
活跃值: (55)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
14
一般情况是这样的,但是复制句柄有个限制是必须能打开的进程。
2009-12-7 00:47
0
雪    币: 7651
活跃值: (523)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
15


难道插线程这种XX事就不需要打开目标进程了?想清楚了~~
2009-12-7 10:05
0
雪    币: 722
活跃值: (123)
能力值: ( LV12,RANK:300 )
在线值:
发帖
回帖
粉丝
16
//由于ZwQueryObject和ZwQuerySystemInformation是未导出的函数,需要动态加载Ntdll,dll,然后通过函数GetProcAddress
  //得到它们的函数地址,由于这个dll一般的进程都会在创建的时候加载,所以省略加载,直接获取其模块地址

未文档的和未导出的不是一回事。
2009-12-7 15:45
0
雪    币: 335
活跃值: (55)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
17
     其实这个是否插入DLL不是很重要,确实一般的都是采用打开进程,复制句柄的方式。

   但是对于我研究的这个程序确实是不需要打开进程就可以插入DLL。只要把窗口保护去掉就可以了。
2009-12-13 01:56
0
雪    币: 33
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
不用打开进程就可以插入啊?厉害厉害!

不知道这个去掉窗口保护是什么意思啊?
2009-12-13 07:34
0
雪    币: 335
活跃值: (55)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
19
谢谢achillis 、qihoocom 和轩辕小聪大牛的回复,本人小菜一个,通过你们的指点,使我知道了对象查询的细节以及一些常识。
        
     写这帖子是因为我当我去掉XX程序多个实例限制时,查找如何关闭有名对象的句柄时我没有很快的找到。后来参考各位大牛的代码才勉强实现以上的代码,不敢私自使用,写出来让能够让和我一样的菜鸟有个参考。
    因为XX程序的驱动保护,我借助RKU直接去掉它的窗口保护,然后使用SetWindowsHookExA插入DLL。本想使用创建远程线程来插入DLL,但是由于它的进程保护,而我写的驱动太烂,都是绝对地址,换系统就蓝,所以没有使用。
2009-12-14 18:07
0
雪    币: 203
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
如果有安全描述的句柄是不是就关不了?
2010-10-10 00:28
0
游客
登录 | 注册 方可回帖
返回
//