Analysis of the principle of user-state direct read and write port in NT environment

zhaozj2021-02-12  215

http://www.blogcn.com/User8/flier_lu/index.html?id=1957096

Regarding the code of the user state directly in the NT environment, this should be the thing that is 95-96 NT architecture just discussed. Now, it is really a thing. Because there are friends asking questions in the TSS a few days ago, there is only a general discussion, and there is no principle analysis of empty implementation methods. There is no way to directly reference. These articles are quoted from Dale Roberts in May 1996 in Dr. Dobb's Journal on the Direct Port I / O and Windows NT in Dobb's Journal, but unfortunately this article wants the membership to see, I Waiting for the beauty of the knife, I can do it yourself. As for the application article based on this principle, it can be seen everywhere. If someone makes it simple package porttalk, it is enough to meet most of the needs:

Porttalk - A Windows NT I / O Port Device Driver Porttalk - Port Drivers for Windows NT / 2000 (Translation)

The following will be analyzed in combination with the NT source code on its implementation principles, and how the relevant functions can be implemented, and why do you implement this.

Unlike the DOS and WIN9X environments, NT user state programs are run in a stringent environment, so some special resources, such as ports, cannot be directly exposed to users, avoiding conflicts or impact system stability. As we are well known, the port operation instruction IN / OUT is classified as a privilege, and an exception can be triggered in a user state program.

Screen.width / 2) this.width = Screen.width / 2 "vSpace = 2 border = 0>

This limitation is implemented through two-layer mechanism: IOPL (I / O Privilege Level) in the EFLAGS flag register and IOPM (I / O Permission Bit Map) in TSS (Task State Segment) provides flexible Two-level control mechanism. (Intel Architecture Sofware Developer's Manual V1: 12.5) We know that the X86 architecture is generally divided into 0-3 four rings, while the NT environment is only core state Ring 0 and user Ring 3. The EFLAGS flag register specifies which privilege levels in the current Task via the IOPL flag can use the I / O command. This flag is saved by the EFLAGS register, and the privilege level of the I / O instruction must be less than or equal to the current value of IOPL. And this flag is typically set to 0 and can only be modified in Ring 0 through the POPF and IRET instructions (Ring 3 changes without exception, but there is no effect). This ensures that the kernel can fully limit the user state program that cannot directly use the I / O equivalent instructions, but can be opened by the IOPM network to be discussed immediately. (I / O Snsitive instructions constrained by IOPL include: IN, INS, OUT, OUTS, CLI, STI) If the current privilege CPL (CURRENT Privilege Level is greater than IOPL, the system will further determine if the IOPM in TSS is allowed to allow Access to this port. TSS is the basic information required for each task-related status storage area, saved to switch the required basic information, such as general registers (EAX, ESP, etc.), segment selector (CS, DS, etc.), EFLAGS, EIP, etc. Dynamically changed, including static content such as CR3, LDT, IOPM. Intel Manual Defines the minimum length of TSS to 104 bytes, one Word at the end is the offset of IOPM relative to TSS. The operating system generally makes a certain degree of customization of TSS, such as the TSS structure under NT architecture (NTOSINCI386.H: 879) is approximately as follows: The following is quoted:

Typedef struct _ktss

{

Ushort backlink;

// ...

Ushort flags;

Ushort omapbase;

KIIO_ACCESS_MAP IOMAPS [IOPM_COUNT];

Kint_direction_map intdirectionmap;} ktss, * pktss;

Because the TSS of the current task has a separate TR (Task Register) register (Intel Architecture Sofware Developer's Manual V3: 6.2.3) Save its 16-bit segment selector and 32-bit base base offset, so the system pair I / O instructions Processing pseudo code can be expressed as follows:

The following is quoted:

Typedef struct {

UNSIGNED LIMIT: 16;

UNSIGNED BASELO: 16;

UNSIGNED BASEMID: 8;

Unsigned Type: 4;

Unsigned system: 1;

UNSIGNED DPL: 2;

Unsigned Present: 1;

Unsigned Limithi: 4;

Unsigned available: 1;

Unsigned ZERO: 1;

Unsigned size: 1;

UNSIGNED Gran Guity: 1;

UNSIGNED Basehi: 8;

} Gdten;

TypeDef struct {unsigned short limit; gdten-} gdtreg; bool checkiopiopiPermission (Word port) {if (CPL <= EFLAGS.IOPL) Return True;

GDTREG GDTREG; WORD TASKSEG;

_ASM CLI; // Disable interrupt _ASM SGDT GDTREG; // Get GDT address _ASM Str Taskseg; // Get TSS Select Subside Index

Gdtent * ptaskgdt = gdtreg.base (taskseg >> 3); // Get TSS descriptor address

KTSS * PTSS = (PVOID) (PtaskGdt-> Baselo | (PTASKGDT-> BASEMID << 16) | (ptaskgdt-> basehi << 24)); // Calculate TSS base address

Char * piopm = ((char *) PTSS PTSS-> omapbase); // calculate the IOPM base address

SIZE_T POS = Port >> 3, IDX = Port & 0xF;

IF ((Piopm POS)> (PTSS TaskGDt-> Limit) {throw generalprotectionException ();

_asm STI;

Return (Piopm [POS] & (1 << IDX)) == (1 << IDX);

First, the system detects if the current privilege level CPL is less than the IOPL of EFLAGS; then obtains the TSS selection sub-index from the TR register, and calculates the TSS descriptor address; the base address and IOPM offset can be obtained by TSS can be obtained; finally according to port query IOPM content, it is determined whether this port is allowed to operate. We can actually take a look at the relevant situation in a system using Windbg Livekd tools:

The following is quoted:

// Show PCR

KD>! PCR

Kpcr for Processor 0 At ffdff000:

Major 1 minor 1

NTTIB.EXCEPTIONLIST: F460FBFC

NTTIB.STACKBASE: 00000000

NTTIB.STACKLIMIT: 00000000

NTTIB.SUBSYSTEMTIB: 80042000

NTTIB.VERSION: 2568915F

Nttib.userpointer: 00000001

NTTIB.SELFTIB: 7FFDD000

Selfpcr: FFDFF000 PRCB: FFDFF120 IRQL: 00000000 IRR: 00000000 IDR: ffffffff interruptmode: 00000000 ID: 8003F400 GDT: 8003F000 TSS: 80042000

CurrentThread: 826a7788 Nextthread: 00000000 IDLethread: 80569280

DPCQueue:

// display TSS kd> dd 80042000 80042000 eb3d76f6 f460fde0 0d8b0010 00441f30 80042010 0674c085 24f8448b 048b03eb 33026a0b 80042020 e85051c9 fffffd6c 1474c085 50413881 80042030 08744349 04c38347 c972fe3b 0272fe3b 80042040 5e5fc033 8b55c35b 8b5151ec 008b0845 80042050 4453523d f8458954 30a10a75 e900441f80042060 00000000 20ac0000 18000004 00000018 // IOPM offset, KTSS. Iomapbase

80042070 00000000 00000000 00000000 00000000 80042080 00000000 00000000 ffffffff ffffffff // TSS built IOPM, KTSS.IoMaps [0] 80042090 ffffffff ffffffff ffffffff ffffffff ... 80044080 ffffffff ffffffff ffffffff 18000004 80044090 00000018 00000000 00000000 00000000 800440a0 00000000 00000000 00000000 cbb70fd9 800440b0 75ff5051 fc4d890c 0009e6e8 06896600

You can see the content of TSS is saved at 0x80042000; its 0x64 offset content 0x20ac is the current IOPM offset; and a bunch of 0xfffffffff at the 0x88 offset is KTSS.iomaps [0], this IOPM table content will be Detailed analysis; and 0x20ac is the IOPM content actually used.

Based on this principle, Dale Roberts proposes several ways to enable user mode access ports, and the end of the root is the IOPM offset and content of TSS.

There is a detailed discussion of this attempt, but unfortunately is Russian. Find a few translation software, and finally found the online translation provided by www.freetranslation.com, first translated into English, huh, huh.

In addition, some articles are also used in the same principle, such as "all Ring 3 processes in NT I / O". It is worth noting that the original article selection increases the TSS length limit to 0xF00, which actually limits the free access port must be less than 0xF00 * 8 = 30720. This hardship restriction should be considered when using this method. The limitations of 0xF00 are to ensure that the length of the TSS length will not cause page errors. Because the original TSS length is generally 0x20ab, it will not cause cross-page issues after increasing 0xF00. This issue is described below in Totalio.c:

The following is quoted:

Since WE CAN SAFELY EXTEND The TSS ONLY TO The End of The Physical Memory Page In Which It Lies, The I / O Access IS Grand Only Up To Port 0xF00. Accesses Beyond This Port Address Will Still Generate Exceptions.

View the GDT entry method for TSS in the actual environment is as follows (0x28 >> ​​3 = 5):

The following is quoted:

KD> RM 0x100

Last Set Context

KD> RLAST SET CONTEXT:

GDTR = 8003F000 GDTL = 03FF IDTR = 8003F400 IDTL = 07FF TR = 0028 LDTR = 0000

kd> dd 8003f000 8003f000 00000000 00000000 0000ffff 00cf9b00 8003f010 0000ffff 00cf9300 0000ffff 00cffb00 8003f020 0000ffff 00cff300 200020ab 80008b04 // TSS Limit 8003f030 f0000001 ffc093df d0000fff 7f40f3fd

The full code set in TSS length limit in Totalio.c is as follows:

The following is quoted:

Void Settslimit (int size)

{

GDTREG GDTREG;

Gdten * g;

Short taskseg;

_ASM CLI; // DON't Get Interrupted! _ASM SGDT gdtreg; // Get GDT Address_ASM Str Taskseg; // Get TSS Selector Index G = GDTREG.BASE (Taskseg >> 3); // Get Ptr To TSS Descriptor g-> limited = size; // modify tss segment limited // // must set segment Type Field To 9, to indeicate the task is // not busy. Otherwise the LTR instruction Causes a fault. // g-> type = 9; // mark TSS AS "NOT Buys" /// We Must Do a Load of the Task Register, Else The Processor // NEL SEES The New TSS Selector Limit. //_ASM LTR TASKSEG; // Reload Task Register Tr )_ASM STI; // Let Interrupts Continue}

Here, TSS TYPE is set to 9, indicating that this descriptor type is 32-bit TSS non-Busy descriptor (Intel Architecture Sofware Developer's Manual V3: 3.5). This approach achieves allowed access to the system's restricted port by direct operating system registers, but is not a perfect solution. Relatively speaking, a special port allowed to access the specific ports that implement the independent process by operating the operating system Unapproming function Ke386SetioAccessMap, Ke386QueryioAccessMap, and Ke386ioseetAccessProcess implementation. Let's take a closer look at the principles and use of these functions. Through the analysis of the KTSS structure and actual memory in the NT system, we can understand: NT environment, each process maintains a TSS memory area separately, where a total of the IOPM table of all flag position 1 is maintained inside the TSS, The TSS is also maintained at another IOPM table that actually undertake port management. Ke386SetioAccessMap function (NTOSKEI386IOPM.C: 80) and ke386queryioAccessmap function (NTOSKEI386IOPM.C: 235) is a function that provides the system to read and write the two IOPM tables. The Ke386ioseetAccessProcess function (NTOSKEI386IOPM.C: 318) specifies which IOPM table that uses the process to use. The following is quoted:

Boolean Ke386QueryioAccessMap (Ulong MapNumber, PKIO_ACCESS_MAP IOACCESSMAP);

Boolean Ke386SetioAccessMap (Ulong MapNumber, PKIO_ACCESS_MAP IOACCESSMAP);

Boolean Ke386ioseetAccessProcess (PkProcess Process, Ulong MapNumber);

For the first two functions, MapNumber specifies which table to be operated. The system defines an IO_ACCESS_MAP_NONE = 0 constant indicates that the true IOPM table behind the TSS, while other indexes correspond to an array of KTSS.IOMAPS []. In most cases in this array, there is only one entry, that is, when MapNumber is 0, the IOPM behind TSS is indicated by TSS; 1 when it represents KTSS.iomaps [0] in TSS. The Ke386QueryioAccessMap function is just simple to determine the IOCCESSMAP content (MapNumber = 0) or from the TSS (0

The following is quoted:

#define ipm_count 1

#define Iopm_size 8192 // Size of Map Callers CAN Set.

Boolean Ke386QueryioAccessmap (Ulong MapNumber, PKIO_ACCESS_MAP IOACCESSMAP) {if (MapNumber> IOPM_COT) RETURN FALSE;

IF (MapNumber == IO_ACCESS_MAP_NONE) {MEMSET (IOACCESSMAP, -1, IOPM_SIZE);} else {void * piopm = & (kipcr () -> tss-> omaps [mapnumber-1] .iomap;

Memcpy (IOPM_SIPM, IOPM_SIZE);} Return True;} and Ke386SetioAccessmap return false directly when MapNumber is 0, because the TSS is not allowed to modify; for other cases, the function copies the contents of IoaccessMap back to TSS In the IOPM table, and in the case of multiprocessor, notify other processors reload the IOPM table. The pseudo code is as follows:

The following is quoted:

Boolean Ke386SetioAccessMap (Ulong MapNumber, PKIO_ACCESS_MAP IOACCESSMAP)

{

IF ((MapNumber> IOPM_COUNT) || (MapNumber == IO_ACCESS_MAP_NONE)) RETURN FALSE;

Void * piopm = & (kipcr () -> tss-> omaps [MapNumber-1] .iomap;

Memcpy (Piopm, IoaccessMap, IOPM_SIZE);

Kipcr () -> TSS-> omapbase = getCurrentProcess () -> Iopmoffset;

// Notify other processors to reset IOPM

Return True;}

The Ke386ioseetAccessProcess function simply modifies the current TSS IOPM offset to mapNumber specified IOPM table offset, and inform other CPU reload IOPM offset in the case of multiple CPUs. The calculation offset algorithm is as follows:

The following is quoted:

#define kicomputeiopmoffset (MapNumber)

(MapNumber == IO_ACCESS_MAP_NONE)?

(USHORT) (SIZEOF (KTSS):

(Ushort) (Field_offset (KTSS, IOMAPS [MapNumber-1] .iomap)

USHORT MAPOFFSET = KicomputeiopMoffset (MapNumber);

The complete use process code is as follows:

The following is quoted:

#define Iopm_size 8192 // Size of Map Callers CAN Set.

TYPEDEF UCHAR KIO_ACCESS_MAP [IOPM_SIZE]; TYPEDEF KIO_ACCESS_MAP * PKIO_ACCESS_MAP;

PKIO_ACCESS_MAP IOPM_LOCAL = MmallocatenonCachedMemory (SIZEOF (IOPM)); if (IOPM_LOCAL == 0) Return Status_insuffect_resources;

Ke386QueryioAccessMap (1, IOPM_LOCAL);

/ / Modify IOPM_LOCAL content to open the port you need to use

Ke386SetioAccessMap (1, IOPM_LOCAL); Ke386ioseetAccess (psgetcurrentprocess (), 1)

The specific code can refer to the source code of PortTalk and Totalio, which is not in Luo.

转载请注明原文地址:https://www.9cbs.com/read-6568.html

New Post(0)