Access via the SCSI Port

Communication via Input/Output Ports

The Windows NT operating system carefully guards Input/Output ports from application s attempts at accessing them. This measure was implemented due to the requirements of the chosen security policy. The freedom of applications is intentionally limited in such a way as to prevent any attempts at damaging the system or unauthorized access to confidential data. The right to access the hardware directly is provided only to drivers and DLLs executing in the kernel mode (see Access via SCSI Miniport ) .

To paraphrase Benjamin Franklin, a nation that has exchanged essential liberties for a little temporary safety deserves neither of these things. Oh yes ”as if it were impossible to hang the system via SPTI/ASPI! This is even more true when you recall that administrative privileges aren t required for this. In fact, security policy and discretionary access are useless, because ASPI provides access to the disk at the sector level without checking if this operation is legitimate . For example, nothing could be simpler than infecting the boot sector with a boot virus. At the same time, the lack of access to Input/Output ports significantly complicates the task of managing the equipment, not to mention the development of reliable and strong protection mechanisms.

Operating systems of the Windows 9 x family behave more democratically, but this level of indulgence is applicable only to MS-DOS programs, while Win32 applications are deprived of direct access to the ports.

Nevertheless, it is still possible to control the hardware from the application level. There are at least two ways of solving this problem: a) The development of the passthrough driver implementing more or less transparent interface for communicating with ports using the IOCTL mechanism and b) Modifying the I/O Permission Map (IOPM) in such a way as to have port access migrate to the list of non-privileged operations that can be carried out from the application level. Both methods will be covered in detail. Let us start with the interface driver.

Windows NT DDK includes the rather interesting PORTIO demo driver, which creates a virtual device and implements a special IOCTL interface. Using this interface, applications are able to manipulate the ports of this device in any way they like. The source code of this demo driver with the required minimum of comments can be found in the following directory: \NTDDK\src\general\portio. Clearly, a virtual device is not exactly what you need, since the range of its Input/Output ports can t overlap with the ports belonging to other devices, as the system would detect an error and warn the user of the hardware conflict in Device Manager. Although such a conflict doesn t influence system usability, users won t really benefit from viewing conflicting devices labeled with the exclamation mark icon.

In fact, nothing prevents the kernel-mode driver from accessing any port. To read the entire range of ports, it is enough to delete the following lines from the source code of the image from book  genport.c program.

Listing 4.24: Checking whether addresses of accessed ports belong to the range of virtual device ports created by the driver
image from book
 if (nPort >= pLDI->PortCount            (nPort + DataBufferSize) > pLDI->PortCount                 (((ULONG_PTR)pLDI->PortBase + nPort) & (DataBufferSize - 1)) != 0)  {          return STATUS_ACCESS_VIOLATION;   // Illegal port number  } 
image from book
 

It is necessary to pay attention to the fact that the driver expects to receive a relative port address rather than an absolute one. A relative port address is counted from the base port address specified when adding a virtual device to the system. For instance, consider the following listing:

Listing 4.25: Calculating the actual port address via the base address
image from book
 case IOCTL_GPD_READ_PORT_UCHAR:  *(PUCHAR)pIOBuffer=READ_PORT_UCHAR((PUCHAR)((  ULONG_PTR)pLDI>PortBase  + nPort));  break; 
image from book
 

Obviously, the text highlighted in bold must be deleted. In this case, the driver will be able to operate with absolute ports rather than with the relative ones. Consequently, this will allow us to access any port in the system! If you port the modified driver to Windows 9 x , our applications will run in both operating systems and will depend only on the hardware. On the other hand, anyone who wants to control these ports must understand clearly for what purpose and what difficulties this might generate.

Naturally, since the possibility of uncontrolled access to all existing Input/Output ports significantly weakens the operating system, which is vulnerable even without it, it would be wise to introduce some additional checks and limitations in the driver. For example, it would be useful to deny direct access to anything that does not represent a CD-ROM drive. Otherwise, in the event that your program becomes popular and widely used, crowds of vandals will rush to write malicious code, such as Trojans. Note that the destructive power of these tools would be practically unlimited, and it would be very difficult to bring the situation under control. On the other hand, during ASPI s existence, no attempts at using it for destructive purposes have been reported , although the possibility still exists.

Another drawback in the suggested method of controlling devices is its catastrophically low performance. The calls to the DeviceIoControl split into thousands of machine commands (!), because of which the request-processing time becomes too long, while measurements of the physical characteristics of the spiral track (if you actually need to measure these characteristics) becomes imprecise. Furthermore, the DeviceIoControl function is too bulky and ungraceful. The most unpleasant thing is that it is very easy to set a Break Point to this function. Therefore, the fate of such protection is a foregone conclusion. In the days of MS-DOS, when controlling equipment was carried out using the IN and OUT machine commands, it was much more difficult to locate the protection code in the program body. However, controlling devices using these commands was considerably easier, and such communications were characterized by significantly higher performance.

It is assumed that under Windows NT direct access to the ports is possible only at the kernel level, while applications must access the ports via the high-level interface provided by a driver. Although this interface can be completely transparent (nothing is easier for the driver than to intercept the exception thrown in the course of an attempt at reading or writing to the port from the application level, and do it on its own), it is still not quite what you need

Actually, the IN/OUT commands can also be executed at the application level. However, to achieve this, you ll need to use undocumented features of the operating system, as well as documented but little known features of the protected mode implementation in Intel 80386+ processors. Let us start our discussion with processors. Open the Instruction Set Reference and view how the OUT machine command is implemented. Among other useful information, we ll find its pseudo-code, which appears approximately as follows :

Listing 4.26: Pseudo-code of the OUT instruction
image from book
 if ((PE == 1) && ((CPL > IOPL)   (VM == 1)))  {  /* Protected mode with CPL > IOPL or virtual-8086 mode */   if (Any I/O Permission Bit for I/O port being accessed == 1)  #GP(0);  /* I/O operation is not allowed */  else               DEST   SRC;  /* Writes to the selected I/O port */  }          else  {  /* Real Mode or Protected Mode with CPL <= IOPL */  DEST   SRC;  /* Writes to the selected I/O port */  } 
image from book
 

Attention! Having detected that the privileges of the current level are insufficient for the execution of this machine instruction, the processor is in hurry to throw out the General Protection Fault exception. On the contrary, it gives this instruction another chance by carrying out the check of the state of the I/O permission bitmap. If the memory bit corresponding to the current port is not set to 1, the output to this port is carried out in spite of any prohibitions from CPL!

Thus, in order to have access to ports from the application level, it is sufficient to correct the I/O permission bitmap, after which the Windows NT security subsystem will stop interfering with our work, since the control of access to the ports is carried out at the hardware level, rather than at the software level. Consequently, if the processor stops throwing out exceptions, the operating system will never know what s happening!

The main problem is that the vast majority of the authors of the books on assembler never mention the I/O permission bitmap. Programmers that know about its existence are few. These are mainly individuals who prefer original documentation to poor translations and retellings.

From the Architecture Software Developer s Manual Volume 1: Basic Architecture , you know that the I/O permission bitmap is located in the TSS (Task State Segment). To be more precise, its actual offset from the TSS starting point is defined by the 32-bit field located in the bytes 0x66 and 0x67 of the Task State Segment. The zero bit of this map is responsible for the zero port, the first bit controls the first port, and so on, up to the most significant bit ”bit 0x2000 , which controls port 65535 . The bitmap is terminated by the so-called terminator byte, which has the value of 0xFF . That s all. The ports with their bits set to zero are available from the application level without any limitations. Naturally, the I/O permission bitmap is available only to drivers, and not to applications. Therefore, it is impossible to proceed any further without writing a custom driver. However, this driver will operate only at the stage of its initialization, and all subsequent I/O operations will be carried out directly, even if you unload the driver from the memory.

Now for the bad news. In Windows NT, the offset of the I/O permission bitmap by default is located beyond the limits of the Task State Segment. Therefore, modifying the I/O bitmap is not an easy task, since it is simply missing (there is no such thing). The processor reacts to this situation adequately. However, it denies access to the Input/Output ports from the application level.

The Input/Output map actually does exist in the TSS, but it is intentionally disabled by the system in order to prevent applications from behaving in a way that is not allowed. The only exception is made for high-performance graphic libraries that access Input/Output ports from the application mode. As can be easily guessed, such a trick provides Microsoft with a significant advantage over its competitors , since they are forced to control the ports either from the kernel level or via the interface provided by the video driver. Naturally, both methods are outclassed by direct access to the ports.

However, attempts to correct the pointer to the Input/Output map don t produce the desired result, since Windows NT stores the copy of this value in the process context. Therefore, the pointer to the previous copy of the I/O map is restored automatically after context switching. On one hand, this is good, because every process can have its own I/O map. On the other, Microsoft s documentation doesn t contain any tips on using this map.

It is possible, however, to use the trick of increasing the TSS size in such a way as to make the I/O map address, which earlier pointed to somewhere beyond the TSS limits, reside in the valid and available memory area. Since there are only 0xF55 bytes in the tail of the last page occupied by TSS, the maximum size of the map that you can create in this gap spans only 31,392 I/O ports. To be honest, other ports are unlikely to be necessary. Therefore, this limitation doesn t cause any serious inconvenience.

Nevertheless, there are more elegant methods for solving this problem. The efforts of Dale Roberts have resulted in the discovery of three completely undocumented functions: Ke386SetIoAccessMap () , Ke386QueryIoAccessMap () , and Ke386IoSetAccessProcess () . As follows from their names , these functions ensure a legal method of controlling the I/O map. When I say that these functions are absolutely undocumented, I mean that even header files from the DDK do not contain the prototypes for these functions. As a matter of fact, DDK header files list a wide range of undocumented functions. Still, the NTOSKRNL library exports them, and these functions are easily available from the driver level.

More detailed information on this topic can be read in the article written by their discoverer ”Dale Roberts. Here, you will cover them only briefly . So, the Ke386SetIoAccessMap function takes two arguments: a DWORD , which, being set to one, makes the function copy the I/O map to the pointer, which is passed to it with the second argument. The Ke386QueryIoAccessMap function accepts the same arguments, but carries out an inverse operation. Namely, it retrieves the current I/O map from the TSS and copies it to the specified buffer. Finally, the Ke386IoSetAccessProcess function accepts the pointer passed to it with its second argument and pointing to the structure of the process received by means of calling on the GetCurrentProcess () documented function. The first argument plays the same role as the first argument of the previous two functions. A zero value moves the pointer to the I/O map outside the limits of the TSS, thus denying access to the ports from the application level, while a value of 1 activates the I/O map passed earlier.

The example provided in Listing 4.27 demonstrates the use of these functions.

Listing 4.27: [/etc/GIVEIO.c] A demo example of the drive opening direct access to the I/O ports from the application level
image from book
  /*----------------------------------------------------------------------------   *   *      This driver allows the execution of the   *  IN/OUT machine command from the application level   *  =================================   *   *  ATTENTION: I, Chris Kaspersky, have not been involved in the creation of and assume   * no responsibility for this program!   * _________________________________________________________________________   *   *  GIVEIO.SYS: by Dale Roberts   *  COMPILING: Use the DDK BUILD tools   *  GOAL: Providing access to direct I/O from the user mode   -------------------------------------------------------------------------------------*/  #include <ntddk.h>  /* The name of our device driver */  #define DEVICE_NAME_STRING L"giveio"  // The IOPM structure is simply an array of bytes having the size of 0x2000   // and containing 8K * 8 bits == 64K bits IOPM, which cover the entire 64 K   // address space of x86 processors.   // Each bit set to 0 provides access to the appropriate port   // to the user-mode process; each bit set to 1 denies access via the respective   // port.  #define IOPM_SIZE 0x2000  typedef UCHAR IOPM[IOPM_SIZE];  // The array of zeroes that is copied to the actual IOPM in the TSS by means   // of calling the dsKe386SetIoAccessMap() function   // Required memory is allocated in the course of driver loading  IOPM *IOPM_local = 0;  // These are two undocumented functions that we use in order,   // to provide I/O access to the calling process   // * Ke386IoSetAccessMap()      - copies the passed I/O map to the TSS   // * Ke386IoSetAccessProcess () - changes the pointer to the IOPM offset,   //                                after which the I/O map just copied   //                                begins to be used  void Ke386SetIoAccessMap(int, IOPM *);  void Ke386QueryIoAccessMap(int, IOPM *);  void Ke386IoSetAccessProcess(PEPROCESS, int);  // RELEASE ALL EARLIER ALLOCATED OBJECTS  VOID GiveioUnload(IN PDRIVER_OBJECT DriverObject)  {  UNICODE_STRING uniDOSString;  WCHAR DOSNameBuffer [] = L"\DosDevices\" DEVICE_NAME_STRING;  if (IOPM_local) MmFreeNonCachedMemory (IOPM_local, sizeof (IOPM));  RtlInitUnicodeString (&uniDOSString, DOSNameBuffer);  IoDeleteSymbolicLink (&uniDOSString);  IoDeleteDevice (DriverObject->DeviceObject);  }  //------------------------------------------------------------------------   //  Setting IOPM of the calling process in such a way as to   //  provide it with full access to I/O ports. The IOPM_local [ ] array   //  contains zeroes, consequently, IOPM will be reset to zero.   //  If OnFlag == 1, the process is provided access to I/O;   //  If this flag is set to 0, access is denied.   // ----------------------------------------------  VOID SetIOPermissionMap(int OnFlag)  {  Ke386IoSetAccessProcess(PsGetCurrentProcess(), OnFlag);  Ke386SetIoAccessMap(1, IOPM_local);  }  void Give 10 (void)  {  SetIOPermissionMap(1);  }  // --------------------------------------------------------   //  The handler for processing user-mode call to CreateProcess ().   //  This function is introduced into the function call table using   //  DriverEntry(). When user-mode application calls CreateFileQ ,   //  this function gets control in the context of the calling application,   //  but with CPL (current privilege level of the processor) set to 0.   //  This allows for the carrying out of operations possible only in kernel mode.   //  GiveIO is called for providing the calling process with access to I/O.   //  All that the user-mode application needing access to I/O must do,   //  is open this device using CreateFile().   //  No other actions are needed.   // --------------------------------------------------------  NTSTATUS GiveioCreateDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)  {  GiveIO(); // give the calling process I/O access  Irp->IoStatus. Information = 0;  Irp->IoStatus.Status = STATUS_SUCCESS;  IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS;  }  // --------------------------------------------------------   //  The driver entry procedure. This procedure is called only once after   //  loading the driver into the memory. It allocates the resources required   //  for driver operation. In our case, it allocates the memory for the IOPM array   //  and creates the device that can be opened by the user-mode application.   //  This function also creates a symbolic link to the device driver.   //  This allows the user-mode application to access our driver   //  using the \.\giveio notation.   //----------------------------------------------------------------------------  NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) { NTSTATUS    status; PDEVICE_OBJECT  deviceObject; UNICODE_STRING  uniNameString, uniDOSString; WCHAR NameBuffer[] = L"\Device\" DEVICE_NAME_STRING; WCHAR DOSNameBuffer[] = L"\DosDevices\" DEVICE_NAME_STRING;  // Allocating the buffer for local IOPM and setting it to zero  IOPM_local = MmAllocateNonCachedMemory(sizeof(IOPM)); if(IOPM_local == 0) return STATUS_INSUFFICIENT_RESOURCES; RtlZeroMemory(IOPM_local, sizeof(IOPM));  // Initializing the device driver and device object  RtlInitUnicodeString(&uniNameString, NameBuffer); RtlInitUnicodeString(&uniDOSString, DOSNameBuffer); status = IoCreateDevice(DriverObject, 0, &uniNameString, FILE_DEVICE_UNKNOWN, 0, FALSE, &deviceObject); if(!NT_SUCCESS(status)) return status; status = IoCreateSymbolicLink (&uniDOSString, &uniNameString); if (!NT_SUCCESS(status)) return status;  // Initializing driver entry points in the driver object   // All that we need are Create and Unload operations  DriverObject->MajorFunction[IRP_MJ_CREATE] = GiveioCreateDispatch; DriverObject->DriverUnload = GiveioUnload;                                   return STATUS SUCCESS; } 
image from book
 
Listing 4.28: [/etc/GIVEIO.demo.c] An example of input/output to the port from the application level
image from book
  /*------------------------------------------------------------   *   *      Demonstration of the in/out call at the application level   *  (ATTENTION: The GIVEIO.SYS driver must be previously loaded.)   *  ====================================================   *   *  ATTENTION: I, Chris Kaspersky, have not been involved in the creation of and assume   *  no responsibility for this program!   *----------------------------------------------------   *   * GIVEIO.TST: by Dale Roberts   * GOAL: Testing the GIVEIO driver by carrying out some I/O operation   *   (for example, accessing the internal PC speaker)   --------------------------------------------------*/  #include <stdio.h>  #include <windows.h>  #include <math.h>  #include <conio.h>  typedef struct {  short int pitch;  short int duration;  } NOTE;  // NOTES TABLE  NOTE notes[] = {{14, 500}, {16, 500}, {12, 500}, {0, 500}, {7, 1000}};  // SETTING THE SPEAKER FREQUENCY IN HZ   // THE SPEAKER IS CONTROLLED BY THE INTEL 8253/8254 TIMER WITH 0X400X43 I/O PORTS  void setfreq(int hz)  {  hz = 1193180 / hz;  // Speaker base frequency 1.19MHz  _outp(0x43, 0xb6) ;  // Choosing timer 2, write operation, mode 3  _outp(0x42, hz) ;  // Setting frequency divider  _outp(0x42, hz >> 8);  // Most significant bit of the divider  }  // -----------------------------------------------------   //  Note that duration is specified in fractions of the 400 Hz frequency, the number 12   //  specifies the scale. The speaker is controlled via port 0x61. Setting two   //  least significant bits allows channel 2 of the 8253/8254 timer   //  and activates the speaker.   // -----------------------------------------------------  void playnote (NOTE note)  {  _outp(0x61, _inp(0x61)  0x03);  // Activating the speaker  setfreq((int) (400 * pow(2, note.pitch / 12.0))); Sleep (note.duration);  _outp(0x61, _inp(0x61) & ~0x03);  // Deactivating the speaker  }  // ----------------------------------------------------------------------------   //  Opening and closing the GIVEIO device, which gives us direct access to I/O;   //  then trying to play music   // ----------------------------------------------------------------------------  int main()  {  int      i;  HANDLE       h;  h = CreateFile("\\. \giveio", GENERIC_READ, 0, NULL, OPEN_EXISTING,  FILE_ATTRIBUTE_NORMAL, NULL);  if (h == INVALID_HANDLE_VALUE)  {  printf ("Couldnt access giveio device\n"); return -1;  }  CloseHandle (h);  for (i = 0; i < sizeof (notes) /sizeof (int); ++i) playnote(notes [i]);                                              return 0;  } 
image from book
 

Now let us discuss how this method of accessing I/O ports can be used for the benefit of protection mechanisms. Suppose that our protection is based on the presence of a physical defect of the CD surface. In this case, all you need to do is to make reading this sector remain as unnoticeable as possible: if this sector is actually is unreadable, then you are dealing with an original disc. Otherwise, this disc is an illegal copy. Direct control over I/O ports will not be picked up with close to 100 percent probability. This is true even if you are dealing with experienced hackers. They are simply unlikely to guess such a ruse. The only thing that you should care about is preventing them from detecting the protection code by cross-references left by the error message that is displayed on the screen in the event that the disc in question is considered to be illegal.

Nevertheless, qualified hackers won t swallow this kind of bait. With a malicious grin, they will just set a breakpoint to the Input/Output operations to the ports 0x1F7/0x177 (for Primary and Secondary drives , respectively). To avoid drowning in the mess of API calls to the drive, they will use conditional breakpoints, instructing the debugger to show up only in case when the address of machine command that carries out I/O operation is below 0x70000000 (i.e. that it belongs to the application rather than to the kernel).

However, is there anything that prevents us from executing the I/O command using the address belonging to the kernel from the application level? To do so, it is enough to scan the upper half of the address space for the presence of the following commands: OUT DX , AL (0xEE opcode) and IN AL , DX ( 0xEC opcode). The question can be asked: How are you going to return the control? The answer to this question is straightforward ”this can be carried out by means of handling structural exceptions. If the machine command that follows IN/OUT throws out an exception (by the way, such commands are a numerous ), then by catching this exception and handling it, you can continue program execution as if nothing happened .

The advantage of this technique is that the breakpoint set by the hacker to the Input/Output ports won t work (to be more precise, it will actually work, but it will be immediately swallowed by the filter). The drawback of this approach is the complication of the protection mechanism.



CD Cracking Uncovered. Protection against Unsanctioned CD Copying
CD Cracking Uncovered: Protection Against Unsanctioned CD Copying (Uncovered series)
ISBN: 1931769338
EAN: 2147483647
Year: 2003
Pages: 60

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net