APIHOOK之在NT系列操作系统里让自己“消失”下

2007-03-31 09:05
=====[ 9. 句柄 ]=========================================     用类SystemHandleInformation来调用NtQuerySystemInformation会在_SYSTEM_HANDLE_INFORMATION_EX结构中获取所有被打开的句柄的数组。     #define SystemHandleInformation 0x10     typedef struct _SYSTEM_HANDLE_INFORMATION {         ULONG ProcessId;         UCHAR ObjectTypeNumber;         UCHAR Flags;         USHORT Handle;         PVOID Object;         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;     ProcessId标明了拥有句柄的进程。ObjectTypeNumber是句柄类型。NumberOfHandles是Information数组中元素的数量。隐藏其中一项是很麻烦的,我们要去掉所有之后的元素并减少NumberOfHandles。去掉之后所有元素是必须的,因为数组中句柄是按ProcessId分组的。这意味着一个来自同一个进程中的所有句柄都在一块儿。对于一个进程变量Handle的数量是不断增加的。     现在回想一下这个函数(NtQuerySystemInformation)使用SystemProcessAndThreadsInformation类来调用时返回的结构_SYSTEM_PROCESSES。这里我们能够看到每个进程都有它自己的句柄的数量在HandleCount中。如果我们想要做得更完美我们就应该修改HandleCount,因为用SystemProcessesAndThreadsInformation类调用这个函数时隐藏了不少句柄。但校正是非常浪费时间的。在系统正常运行的一小段时间里就会有很多句柄正在打开或关上。所以在对这个函数两次紧挨着的调用句柄的数量被更改是很正常的,所以我们根本不需要改变HandleCount。=====[ 9.1 命名句柄并获取类型 ]===================================     隐藏句柄很麻烦,但找出哪个句柄该被隐藏更困难一些。比方说我们要隐藏一个进程就要隐藏它的所有句柄并隐藏所有和它有联系的句柄。我们比较句柄的ProcessId参数和想要隐藏的进程的PID,如果它们相等就隐藏这个句柄。但是其它进程的句柄在我们能比较任何东西之前不得不先命名。系统中句柄的数量通常很庞大,所以最好在尝试命名之前先比较句柄类型。命名类型可以为我们不感兴趣的句柄省不少时间。     命名句柄和句柄类型通过调用NtQueryObject来完成。     NTSTATUS ZwQueryObject(         IN HANDLE ObjectHandle,         IN OBJECT_INFORMATION_CLASS ObjectInformationClass,         OUT PVOID ObjectInformation,         IN ULONG ObjectInformationLength,         OUT PULONG ReturnLength OPTIONAL     );     ObjectHandle是我们想要获取有关信息的句柄,ObjectInformationClass是信息类型,保存在以字节计算长度为ObjectInformationLength的缓冲区ObjectInformation中。     我们对OBJECT_INFORMATION_CLASS使用的类是ObjectNameInformation和ObjectAllTypesInformation。ObjectNameInfromation类在缓冲区中返回OBJECT_NAME_INFORMATION结构,而ObjectAllTypesInformation类返回OBJECT_ALL_TYPES_INFORMATION结构。     #define ObjectNameInformation 1     #define ObjectAllTypesInformation 3     typedef struct _OBJECT_NAME_INFORMATION {         UNICODE_STRING Name;     } OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION;     Name决定了句柄的名字。     typedef struct _OBJECT_TYPE_INFORMATION {         UNICODE_STRING Name;         ULONG ObjectCount;         ULONG HandleCount;         ULONG Reserved1[4];         ULONG PeakObjectCount;         ULONG PeakHandleCount;         ULONG Reserved2[4];         ULONG InvalidAttributes;         GENERIC_MAPPING GenericMapping;         ULONG ValidAccess;         UCHAR Unknown;         BOOLEAN MaintainHandleDatabase;         POOL_TYPE PoolType;         ULONG PagedPoolUsage;         ULONG NonPagedPoolUsage;     } OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;     typedef struct _OBJECT_ALL_TYPES_INFORMATION {         ULONG NumberOfTypes;         OBJECT_TYPE_INFORMATION TypeInformation;     } OBJECT_ALL_TYPES_INFORMATION, *POBJECT_ALL_TYPES_INFORMATION;     Name决定类型对象名字,类型对象紧跟在每个OBJECT_TYPE_INFORMATION结构后面。下一个OBJECT_TYPE_INFORMATION结构跟在这个Name后面,间隔4个字节。             SYSTEM_HANDLE_INFORMATION结构中的ObjectTypeNumber是TypeInformation数组中的索引。     比较困难的是获取其他进程中句柄的名字。这里有两种命名的可能性。一是通过调用NtDuplicateObject把句柄拷贝到我们的进程中然后命名它。这种方法对某些特殊类型的句柄会失败。但由于它失败的次数比较少,所以我们采用这种方法。     NtDuplicateObject(         IN HANDLE SourceProcessHandle,         IN HANDLE SourceHandle,         IN HANDLE TargetProcessHandle,         OUT PHANDLE TargetHandle OPTIONAL,         IN ACCESS_MASK DesiredAccess,         IN ULONG Attributes,         IN ULONG Options     );     SourceHandle是我们想要拷贝的句柄,SourceProcessHandle是拥有SourceHandle的进程的句柄。TargetProcessHandle是想要拷贝到的进程的句柄,在这里是我们进程的句柄。TargetHandle是指向保存原始句柄拷贝的指针。DesiredAccess应该被设为PROCESS_QUERY_INFORMATION,Attributes和Options设为0。     第二种命名方法对所有句柄都有效,就是使用系统驱动。源代码可以在的OpHandle项目里找到。=====[ 10. 端口 ]==========================================     枚举打开端口最简单的方法是调用AllocateAndGetTcpTableFromStack和AllocateAndGetUdpTableFromStack函数,或者AllocateAndGetTcpExTableFromStack和AllocateAndGetUdpExTableFromStack函数,它们都来自iphlpapi.dll。带Ex的函数从Windows XP才开始有效。     typedef struct _MIB_TCPROW {         DWORD dwState;         DWORD dwLocalAddr;         DWORD dwLocalPort;         DWORD dwRemoteAddr;         DWORD dwRemotePort;     } MIB_TCPROW, *PMIB_TCPROW;     typedef struct _MIB_TCPTABLE {         DWORD dwNumEntries;         MIB_TCPROW table[ANY_SIZE];     } MIB_TCPTABLE, *PMIB_TCPTABLE;     typedef struct _MIB_UDPROW {         DWORD dwLocalAddr;         DWORD dwLocalPort;     } MIB_UDPROW, *PMIB_UDPROW;     typedef struct _MIB_UDPTABLE {         DWORD dwNumEntries;         MIB_UDPROW table[ANY_SIZE];     } MIB_UDPTABLE, *PMIB_UDPTABLE;     typedef struct _MIB_TCPROW_EX     {         DWORD dwState;         DWORD dwLocalAddr;         DWORD dwLocalPort;         DWORD dwRemoteAddr;         DWORD dwRemotePort;         DWORD dwProcessId;     } MIB_TCPROW_EX, *PMIB_TCPROW_EX;     typedef struct _MIB_TCPTABLE_EX     {         DWORD dwNumEntries;         MIB_TCPROW_EX table[ANY_SIZE];     } MIB_TCPTABLE_EX, *PMIB_TCPTABLE_EX;     typedef struct _MIB_UDPROW_EX     {         DWORD dwLocalAddr;         DWORD dwLocalPort;         DWORD dwProcessId;     } MIB_UDPROW_EX, *PMIB_UDPROW_EX;     typedef struct _MIB_UDPTABLE_EX     {         DWORD dwNumEntries;         MIB_UDPROW_EX table[ANY_SIZE];     } MIB_UDPTABLE_EX, *PMIB_UDPTABLE_EX;     DWORD WINAPI AllocateAndGetTcpTableFromStack(         OUT PMIB_TCPTABLE *pTcpTable,         IN BOOL bOrder,         IN HANDLE hAllocHeap,         IN DWORD dwAllocFlags,         IN DWORD dwProtocolVersion;     );     DWORD WINAPI AllocateAndGetUdpTableFromStack(         OUT PMIB_UDPTABLE *pUdpTable,         IN BOOL bOrder,         IN HANDLE hAllocHeap,         IN DWORD dwAllocFlags,         IN DWORD dwProtocolVersion;     );     DWORD WINAPI AllocateAndGetTcpExTableFromStack(         OUT PMIB_TCPTABLE_EX *pTcpTableEx,         IN BOOL bOrder,         IN HANDLE hAllocHeap,         IN DWORD dwAllocFlags,         IN DWORD dwProtocolVersion;     );     DWORD WINAPI AllocateAndGetUdpExTableFromStack(         OUT PMIB_UDPTABLE_EX *pUdpTableEx,         IN BOOL bOrder,         IN HANDLE hAllocHeap,         IN DWORD dwAllocFlags,         IN DWORD dwProtocolVersion;     );         还有另外一种方法。当程序创建了一个套接字并开始监听时,它就会有一个为它和打开端口的打开句柄。我们在系统中枚举所有的打开句柄并通过NtDeviceIoControlFile把它们发送到一个特定的缓冲区中,来找出这个句柄是否是一个打开端口的。这样也能给我们有关端口的信息。因为打开句柄太多了,所以我们只检测类型是File并且名字是/Device/Tcp或/Device/Udp的。打开端口只有这种类型和名字。     如果你看一下iphlpapi.dll里函数的代码,就会发现这些函数同样调用NtDeviceIoControlFile并发送到一个特定缓冲区来获得系统中所有打开端口的列表。这意味着我们要想隐藏端口只需要挂钩NtDeviceIoControlFile函数。     NTSTATUS NtDeviceIoControlFile(         IN HANDLE FileHandle         IN HANDLE Event OPTIONAL,         IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,         IN PVOID ApcContext OPTIONAL,         OUT PIO_STATUS_BLOCK IoStatusBlock,         IN ULONG IoControlCode,         IN PVOID InputBuffer OPTIONAL,         IN ULONG InputBufferLength,         OUT PVOID OutputBuffer OPTIONAL,         IN ULONG OutputBufferLength     );         我们感兴趣的成员变量有这几个:FileHandle标明了要通信的设备的句柄,IoStatusBlock指向接收最后完成状态和请求操作信息的变量,IoControlCode是指定要完成的特定的I/O控制操作的数字,InputBuffer包含了输入的数据,长度为按字节计算的InputBufferLength,相似的还有OutputBuffer和OutputBufferLength。        =====[ 10.1 WinXP下使用Netstat OpPorts FPort ]=========================     在Windoes XP获得所有打开端口的列表可以使用一些软件比方OpPorts、FPort和Netstat。     这里程序用IoControlCode0x000120003调用了NtDeviceIoControlFile两次。输出缓冲区在第二次调用时被填满。FileHandle的名字这里总是/Device/Tcp。InputBuffer因不同类型的调用而不同:     1) 为获得MIB_TCPROW数组InputBuffer看起来是这样:第一次调用:0x00 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 第二次调用:0x00 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00      2) 为获得MIB_UDPROW数组:第一次调用:0x01 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 第二次调用:0x01 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00      3) 为获得MIB_TCPROW_EX数组:第一次调用:0x00 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 第二次调用:0x00 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x02 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00      4) 为获得MIB_UDPROW_EX数组:第一次调用:0x01 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 第二次调用:0x01 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x02 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00      你可以看到缓冲区只有少数字节不同。我们现在比较清晰地简要说明一下:         我们感兴趣的调用是InputBuffer[1]为0x04且InputBuffer[17]为0x01。只有使用这些输入数据才能使OutputBuffer里为我们想要的表。如果我们想要获得TCP端口信息我们就把InputBuffer[0]设为0x00,想获得UDP端口信息就把它设为0x01。如果我们还需要额外的输出表(MIB_TCPROW_EX或MIB_UDPROW_EX)我们就在第二次调用里把InfputBufer[16]设为0x02。     如果我们发现使用了这几个参数的调用我们就修改输出缓冲区。获取输出缓冲区中ROW的数量可以很容易根据ROW的大小分开IoStatusBlock结构里的Infotmation变量。隐藏其中一个ROW就会变的容易,只需要用之后的ROW改写他并删掉最后一个ROW。不要忘了修改OutputBufferLength和IoStatusBlock。=====[ 10.2 Win2k和NT4下使用OpPorts, Win2k下使用FPort ]==========================     我们用IoControlCode0x00210012调用NtDeviceIoControlFile来判断这个拥有类型File和名字/Device/Tcp或/Device/Udp是否是打开端口的句柄。     所以最先我们比较IoControlCode然后是类型和句柄名字。如果这些都符合就接着比较输入缓冲区长度,它应该和结构TDI_CONNECTION_IN长度一样,为0x18。输出缓冲区的结构是TDI_CONNECTION_OUT。     typedef struct _TDI_CONNETION_IN     {         ULONG UserDataLength,         PVOID UserData,         ULONG OptionsLength,         PVOID Options,         ULONG RemoteAddressLength,         PVOID RemoteAddress     } TDI_CONNETION_IN, *PTDI_CONNETION_IN;     typedef struct _TDI_CONNETION_OUT     {         ULONG State,         ULONG Event,         ULONG TransmittedTsdus,         ULONG ReceivedTsdus,         ULONG TransmissionErrors,         ULONG ReceiveErrors,         LARGE_INTEGER Throughput         LARGE_INTEGER Delay,         ULONG SendBufferSize,         ULONG ReceiveBufferSize,         ULONG Unreliable,         ULONG Unknown1[5],         USHORT Unknown2     } TDI_CONNETION_OUT, *PTDI_CONNETION_OUT;     具体判断句柄是不是一个打开端口的方法请参考OpPorts的源代码,在上可以找到。我们现在来隐藏指定端口。我们已经比较过了InputBufferLength和IoControlCode,现在来比较RemoteAddressLength,对打开端口来说它总是3或4。最后要做的是比较OutputBufferBuffer里的ReceiveTsdus,用网络上的端口和要隐藏的端口列表比较。区别TCP和UDP的做法是句柄的名字不一样。在删除了OutputBuffer、修改IoStatusBlock并返回STATUS_INVALID_ADDRESS后我们就已经隐藏了这个端口了。        =====[ 11. 结束语 ]===============================================     具体细节请参考Hacker defender rootkit version 1.0.0的源代码,在和都可以找到。     在将来我还会加入更多有关的技术。这篇文档的更新版本会改进现有的方法和并加入新的思想。     特别感谢Ratter提供了很多完成这篇文档和Hacker defender代码所需要的技术。===================================[ End ]==============================后记:     其实只要我们对Windows的内核有一定程度的了解我们都知道单纯靠挂钩函数是不能真正做到隐藏的,这样做只不过是欺骗操作系统的使用者,却欺骗不了操作系统自己。线程要想被运行就必须获得时间片,将自己加入调度链表中,从而暴露自己。内核维护一组被称为调度程序数据库的数据结构来做出线程调度的决策。其中最重要的结构是调度程序就绪队列(KiDispatckerReadyListHead)。它里面有64个DWORD,分别对应于32个线程优先级的队列,队列包含处于就绪状态的线程,正在等待调度执行。还有两个队列KiWaitInListHead和KiWaitOutListHead保存着处于等待状态的线程。可以很简单的枚举这3个链表中的所有元素从而列出系统中的所有线程。因此要想彻底从Windows系统里“消失”就要从Windows的内核下手(Windows的内核只负责线程调度,其它功能由执行程序组件完成)。这个功能还有待完成。
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值