今天我们一起来学习下如何实现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.

上传的附件 drv.rar
exe.rar