今天我们一起来学习下如何实现ring3程序直接访问硬件。这部分内容跟windows保护模式中的I/O保护部分有直接的关系。
rootkit直接访问硬件准备写三篇,今天是开篇。后续请大家继续跟贴。高手飘过,呵呵。
我们来看看Windows系统I/O访问保护的方法:
80386采用I/O特权级IPOL和I/O许可位图的方法来控制输入/输出,实现输入/输出保护.
I/O许可位图位于任务状态段TSS中。I/O特权级IPOL就是EFLAGS寄存器中IOPL位。
采用的保护规则是:
(1)若CPL<=IOPL,则直接转步骤(8);
(2)取得I/O位图开始偏移;
(3)计算I/O地址对应位所在字节在I/O许可位图内的偏移;
(4)计算位偏移以形成屏蔽码值,即计算I/O地址对应位在字节中的第几位;
(5)把字节偏移加上位图开始偏移,再加1,所得值与TSS界限比较,若越界,则产生出错码为0的通用保护故障;
(6)若不越界,则从位图中读对应字节及下一个字节;
(7)把读出的两个字节与屏蔽码进行与运算,若结果不为0表示检查未通过,则产生出错码为0的通用保护故障;
(8)进行I/O访问。
其中windows中IOPL值是为0,从第一条规则我们可以了解到为什么ring0下可以直接访问I/O了,因为ring0中CPL = 0 ,完全满足第一条。而ring3下CPL = 3 ,第一条不能满足。
我们知道每个任务使用各自的EFLAGS值和拥有自己的TSS,所以每个任务可以有不同的IOPL。所以想在ring3下访问I/O,最简单的方法当然是修改当前进程IOPL的值为3了。
根据前面了解到IPOL就是EFLAGS寄存器中IOPL位,所以我们尝试下修改eflags寄存器看看。如图:
我们写段代码看看:
#include "stdio.h"
int main(int argc, char* argv[])
{
_asm
{
pushfd
pop eax
or eax,0x3000 //设置iopl = 3
push eax
popfd
pushfd //看看修改后的效果
pop eax //eflags寄存器的值保存到eax中
}
return 0;
}
通过上面代码,我们发现不能直接修改eflags中iopl的值。呵呵,继续想办法吧。
每个Windows进程都有一个相对应的执行体进程(EPROCESS),EPROCESS不仅包括了进程的许多属性,还包扩了许多指向其他数据结构的指针,其中包含了大量有用的信息.
为了找出思路,我们还是先看看进程eprocess吧。
lkd> dt _eprocess
ntdll!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x06c ProcessLock : _EX_PUSH_LOCK
+0x070 CreateTime : _LARGE_INTEGER
+0x078 ExitTime : _LARGE_INTEGER
+0x080 RundownProtect : _EX_RUNDOWN_REF
+0x084 UniqueProcessId : Ptr32 Void
+0x088 ActiveProcessLinks : _LIST_ENTRY
+0x090 QuotaUsage : [3] Uint4B
+0x09c QuotaPeak : [3] Uint4B
+0x0a8 CommitCharge : Uint4B
+0x0ac PeakVirtualSize : Uint4B
+0x0b0 VirtualSize : Uint4B
+0x0b4 SessionProcessLinks : _LIST_ENTRY
+0x0bc DebugPort : Ptr32 Void
+0x0c0 ExceptionPort : Ptr32 Void
+0x0c4 ObjectTable : Ptr32 _HANDLE_TABLE
+0x0c8 Token : _EX_FAST_REF
+0x0cc WorkingSetLock : _FAST_MUTEX
+0x0ec WorkingSetPage : Uint4B
+0x0f0 AddressCreationLock : _FAST_MUTEX
+0x110 HyperSpaceLock : Uint4B
+0x114 ForkInProgress : Ptr32 _ETHREAD
+0x118 HardwareTrigger : Uint4B
+0x11c VadRoot : Ptr32 Void
+0x120 VadHint : Ptr32 Void
+0x124 CloneRoot : Ptr32 Void
+0x128 NumberOfPrivatePages : Uint4B
+0x12c NumberOfLockedPages : Uint4B
+0x130 Win32Process : Ptr32 Void
+0x134 Job : Ptr32 _EJOB
+0x138 SectionObject : Ptr32 Void
+0x13c SectionBaseAddress : Ptr32 Void
+0x140 QuotaBlock : Ptr32 _EPROCESS_QUOTA_BLOCK
+0x144 WorkingSetWatch : Ptr32 _PAGEFAULT_HISTORY
+0x148 Win32WindowStation : Ptr32 Void
+0x14c InheritedFromUniqueProcessId : Ptr32 Void
+0x150 LdtInformation : Ptr32 Void
+0x154 VadFreeHint : Ptr32 Void
+0x158 VdmObjects : Ptr32 Void
+0x15c DeviceMap : Ptr32 Void
+0x160 PhysicalVadList : _LIST_ENTRY
+0x168 PageDirectoryPte : _HARDWARE_PTE_X86
+0x168 Filler : Uint8B
+0x170 Session : Ptr32 Void
+0x174 ImageFileName : [16] UChar
+0x184 JobLinks : _LIST_ENTRY
+0x18c LockedPagesList : Ptr32 Void
+0x190 ThreadListHead : _LIST_ENTRY
+0x198 SecurityPort : Ptr32 Void
+0x19c PaeTop : Ptr32 Void
+0x1a0 ActiveThreads : Uint4B
+0x1a4 GrantedAccess : Uint4B
+0x1a8 DefaultHardErrorProcessing : Uint4B
+0x1ac LastThreadExitStatus : Int4B
+0x1b0 Peb : Ptr32 _PEB
+0x1b4 PrefetchTrace : _EX_FAST_REF
+0x1b8 ReadOperationCount : _LARGE_INTEGER
+0x1c0 WriteOperationCount : _LARGE_INTEGER
+0x1c8 OtherOperationCount : _LARGE_INTEGER
+0x1d0 ReadTransferCount : _LARGE_INTEGER
+0x1d8 WriteTransferCount : _LARGE_INTEGER
+0x1e0 OtherTransferCount : _LARGE_INTEGER
+0x1e8 CommitChargeLimit : Uint4B
+0x1ec CommitChargePeak : Uint4B
+0x1f0 AweInfo : Ptr32 Void
+0x1f4 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
+0x1f8 Vm : _MMSUPPORT
+0x238 LastFaultCount : Uint4B
+0x23c ModifiedPageCount : Uint4B
+0x240 NumberOfVads : Uint4B
+0x244 JobStatus : Uint4B
+0x248 Flags : Uint4B
+0x248 CreateReported : Pos 0, 1 Bit
+0x248 NoDebugInherit : Pos 1, 1 Bit
+0x248 ProcessExiting : Pos 2, 1 Bit
+0x248 ProcessDelete : Pos 3, 1 Bit
+0x248 Wow64SplitPages : Pos 4, 1 Bit
+0x248 VmDeleted : Pos 5, 1 Bit
+0x248 OutswapEnabled : Pos 6, 1 Bit
+0x248 Outswapped : Pos 7, 1 Bit
+0x248 ForkFailed : Pos 8, 1 Bit
+0x248 HasPhysicalVad : Pos 9, 1 Bit
+0x248 AddressSpaceInitialized : Pos 10, 2 Bits
+0x248 SetTimerResolution : Pos 12, 1 Bit
+0x248 BreakOnTermination : Pos 13, 1 Bit
+0x248 SessionCreationUnderway : Pos 14, 1 Bit
+0x248 WriteWatch : Pos 15, 1 Bit
+0x248 ProcessInSession : Pos 16, 1 Bit
+0x248 OverrideAddressSpace : Pos 17, 1 Bit
+0x248 HasAddressSpace : Pos 18, 1 Bit
+0x248 LaunchPrefetched : Pos 19, 1 Bit
+0x248 InjectInpageErrors : Pos 20, 1 Bit
+0x248 VmTopDown : Pos 21, 1 Bit
+0x248 Unused3 : Pos 22, 1 Bit
+0x248 Unused4 : Pos 23, 1 Bit
+0x248 VdmAllowed : Pos 24, 1 Bit
+0x248 Unused : Pos 25, 5 Bits
+0x248 Unused1 : Pos 30, 1 Bit
+0x248 Unused2 : Pos 31, 1 Bit
+0x24c ExitStatus : Int4B
+0x250 NextPageColor : Uint2B
+0x252 SubSystemMinorVersion : UChar
+0x253 SubSystemMajorVersion : UChar
+0x252 SubSystemVersion : Uint2B
+0x254 PriorityClass : UChar
+0x255 WorkingSetAcquiredUnsafe : UChar
+0x258 Cookie : Uint4B
大致上看了下,没有iopl相关的项,我们继续看看里面的子项内容。
看看 +0x000 Pcb : _KPROCESS这个结构体。
lkd> dt _kprocess
ntdll!_KPROCESS
+0x000 Header : _DISPATCHER_HEADER
+0x010 ProfileListHead : _LIST_ENTRY
+0x018 DirectoryTableBase : [2] Uint4B
+0x020 LdtDescriptor : _KGDTENTRY
+0x028 Int21Descriptor : _KIDTENTRY
+0x030 IopmOffset : Uint2B
+0x032 Iopl : UChar
+0x033 Unused : UChar
+0x034 ActiveProcessors : Uint4B
+0x038 KernelTime : Uint4B
+0x03c UserTime : Uint4B
+0x040 ReadyListHead : _LIST_ENTRY
+0x048 SwapListEntry : _SINGLE_LIST_ENTRY
+0x04c VdmTrapcHandler : Ptr32 Void
+0x050 ThreadListHead : _LIST_ENTRY
+0x058 ProcessLock : Uint4B
+0x05c Affinity : Uint4B
+0x060 StackCount : Uint2B
+0x062 BasePriority : Char
+0x063 ThreadQuantum : Char
+0x064 AutoAlignment : UChar
+0x065 State : UChar
+0x066 ThreadSeed : UChar
+0x067 DisableBoost : UChar
+0x068 PowerState : UChar
+0x069 DisableQuantum : UChar
+0x06a IdealNode : UChar
+0x06b Flags : _KEXECUTE_OPTIONS
+0x06b ExecuteOptions : UChar
在这里,我们找到了Iopl和IopmOffset 这两项。其中IopmOffset指该进程I/O许可位图在tss区域的偏移,也就是TSS中的IoMapBase值。而Iopl是一个布尔值,该值决定了进程中线程切换时,EFLAGS寄存器中IOPL位是设置为0还是为3。 如果进程Iopl值为TRUE,
则EFLAGS寄存器中IOPL值为3,如果进程Iopl值为FALSE,则EFLAGS寄存器中IOPL值为0.
到这里我们更进了一步。呵呵,离成功不远了。
接下来我们发现在ntdll.dll中有一个导出函数NtSetInformationProcess。
我们可以在ring3下直接调用NtSetInformationProcess函数的ProcessUserModeIOPL功能将进程中Iopl值为TRUE。但是有个前提,需要当前用户具有SeTcbPrivilege这个权限才行,并且当前用户必须是administrator.
SeTcbPrivilege表示当前用户的操作代表了系统的操作。并且这个权限只有system有,Administrator都没有这个权限。因此,我们需要首先得到这个权限,并且将这个权限使能。然后再调用NtSetInformationProcess的ProcessUserModeIOPL功能。思路很清晰了,这个我们在ring3下就能实现。贴代码:
#include <string.h>
#include <windows.h>
#include <tchar.h>
#include <ntsecapi.h>
#include <process.h>
BOOL EnablePrivilege(PTCHAR Privilege)
{
BOOL rc = FALSE;
HANDLE hToken;
LUID luid;
TOKEN_PRIVILEGES tokenPrivilege;
DWORD dwProcID = GetCurrentProcessId();
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwProcID);
//
// Open the current process' token.
//
rc = OpenProcessToken(
GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
&hToken);
if (rc)
{
rc = LookupPrivilegeValue(NULL, Privilege, &luid);
if (rc)
{
tokenPrivilege.PrivilegeCount = 1;
tokenPrivilege.Privileges[0].Luid = luid;
tokenPrivilege.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
//
// Assign the given privilege.
//
rc = AdjustTokenPrivileges(
hToken,
FALSE,
&tokenPrivilege,
sizeof(tokenPrivilege),
NULL,
NULL);
}
}
if (hToken)
{
CloseHandle(hToken);
}
if(hProc)
CloseHandle(hProc);
return rc;
}
BOOL EnableProcPrivilege()
{
BOOL rc = TRUE;
LSA_HANDLE PolicyHandle;
PSID Sid=0;
DWORD cbSid=0;
LPTSTR ReferencedDomainName=0;
DWORD cbReferencedDomainName=0;
SID_NAME_USE peUse;
PUNICODE_STRING UserRights=0; //UnicodeString Pointer to PRIVILEGE
ULONG Count=0; //
HANDLE token=0;
PTOKEN_PRIVILEGES TokenInformation=0;
BOOL owned=0;
OSVERSIONINFO osv;
char username[30];
DWORD cb = 30;
LSA_OBJECT_ATTRIBUTES ObjectAttributes;
ZeroMemory(&ObjectAttributes,sizeof(ObjectAttributes));
ZeroMemory(&osv,sizeof(osv));
osv.dwOSVersionInfoSize = sizeof(osv);
//
//判断当前系统是否为nt及以上
//
GetVersionEx(&osv);
if(!(osv.dwPlatformId & VER_PLATFORM_WIN32_NT))
{
rc = FALSE;
}
//
// 判断当前用户是否为administrator
//
GetUserName(username,&cb);
if(stricmp(username,"administrator"))
{
rc = FALSE;
}
//
//First open LSA policy database
//the call returns a NTSTATUS. NTSTATUS 0 means everything is OK.
//
if (LsaOpenPolicy(
0,
&ObjectAttributes,
GENERIC_EXECUTE|GENERIC_READ|GENERIC_WRITE,
&PolicyHandle
))
{
rc = FALSE;
}
Sid=new char[500];
ReferencedDomainName=new CHAR[100];
cbSid=500;
cbReferencedDomainName=100;
//
//Show Administrator SID
//
if (!LookupAccountName(
0,
"Administrator",
Sid,
&cbSid,
ReferencedDomainName,
&cbReferencedDomainName,
&peUse
))
{
rc = FALSE;
}
//
//Add SeTchPrivilege to Administrator if not owned yet!
//
UserRights=new LSA_UNICODE_STRING;
UserRights->Buffer=L"SeTcbPrivilege";
UserRights->MaximumLength=28;
UserRights->Length=28;
if (LsaAddAccountRights(
PolicyHandle,
Sid,
UserRights,
1
))
{
rc = FALSE;
}
if(Sid)
delete Sid;
if(ReferencedDomainName)
delete ReferencedDomainName;
if (UserRights)
delete UserRights;
if (TokenInformation)
delete TokenInformation;
if (token)
CloseHandle(token);
if (PolicyHandle)
LsaClose(PolicyHandle);
rc = EnablePrivilege(SE_TCB_NAME);
return rc;
}
BOOL EnableUserModeHardwareIO()
{
typedef ULONG (__stdcall* pfn_ZwSetInformationProcess)(
HANDLE,
ULONG,
PVOID,
ULONG); BOOL rc = FALSE;
HMODULE hNtDll = NULL;
ULONG IOPL = 1;
INT ProcessUserModeIOPL = 16;
pfn_ZwSetInformationProcess ZwSetInformationProcess;
DWORD dwProcessID = GetCurrentProcessId();
HANDLE hProc= OpenProcess( PROCESS_ALL_ACCESS, TRUE,dwProcessID);
hNtDll = GetModuleHandle("ntdll.dll");
if (hNtDll)
{
ZwSetInformationProcess = (pfn_ZwSetInformationProcess)
GetProcAddress(hNtDll, "ZwSetInformationProcess"); if (ZwSetInformationProcess)
{
//
// Enable SeTcbPrivilege
//
rc = EnableProcPrivilege();
if (rc)
{
//
// Grant user mode hardware IO access.
//
rc = ZwSetInformationProcess(
hProc,
ProcessUserModeIOPL,
&IOPL,
sizeof(IOPL));
//
// An NTSTATUS is returned, so zero is success.
//
if (!rc)
{
rc = TRUE;
}
}
}
}
CloseHandle(hProc);
return rc;
} int __cdecl main(int argc, char* argv[])
{
if (EnableUserModeHardwareIO())
{
//
// 直接给键盘发一个指令,让机器重启
//
__asm mov dx, 0x64
__asm mov al, 0xFE
__asm out dx, al
}
return 0;
}
毕竟ring3下访问还是比较费劲,后面附上一个ring3通过驱动访问硬件的例子。由于驱动加载部分代码网上很多,就不再罗嗦了。
注:本机环境 winxp sp2.
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
上传的附件: