In operating systems of the Windows family, management of device drivers is carried out by means of calls to the DeviceIoControl function responsible for sending special FSCTL/IOCTL commands. The FS prefix denotes that this command belongs to the file system management group , and is of no interest to us in the context of this book. The commands with the IO prefix relate to the input/output device, or, to be more precise ”to its driver.
The DeviceIoControl function simply passes on these commands as is, without going deeper into its physical sense. Therefore, it doesn t make any sense to look for a list of available IOCTL commands in the description of DeviceIoControl . The description doesn t contain anything like this! It merely provides the list of standard IOCTL commands, while all remaining information related to this topic is provided by Windows DDK.
There you will discover, in particular, that the IOCTL_CDROM_READ_TOC command is used for reading disc TOC, while for listing session block addresses of multi-session discs, there is the IOCTL_CDROM_GET_LAST_SESSION command. It is also necessary to pay attention to the IOCTL_CDROM_READ_Q_CHANNEL command, which ensures the retrieval of the information from the Q-channel of subcode (this is important for retrieving key marks).
Reading CD-ROM sectors in the Raw mode is carried out by the IOCTL_CDROM_RAW_READ command, the capabilities of which, unfortunately , are limited to CDDA discs only. Reading data from CDDATA discs, sector by sector, is not supported on Raw or on user levels. According to the adopted security policy, no application has the right to bypass the security subsystem. If this were not the case, intruders would easily be able to access confidential data by simply reading the disc at the sector level. Built-in drivers supplied as part of the Windows operating system fully comply with this requirement, although third-party developers may violate this restriction if they so chose. Windows NT DDK includes the source code of a demo CD-ROM driver (NTDDK\ src\storage\class\ cdrom \). After introducing some small modifications, this driver will agree to read discs of all types without asking any silly questions. To do this, simply open the cdrom.c file, find the string "if (rawReadInfo-> TrackMode == CDDA) {" , and go to the branch whose OperationCode is equal to SCSIOP_READ . Then, modify the code in such a way as to ensure that this branch gets control in all other cases.
Note | The IRP_MJ_READ function, which is present in DDK and which, in the theory, ensures that it is possible to read individual logical blocks, is an internal function of the driver. Access to this function from the application level is closed and it doesn t make any sense to use it in combination with DeviceIoControl . |
IOCTL command | Description |
---|---|
IOCTL_CDROM_CHECK_VERIFY , IOCTL_STORAGE_CHECK_VERIFY (0x24800h) | Detects the fact of disc replacement (opening/closing the tray) |
IOCTL_CDROM_CLOSE_DOOR [*] , IOCTL_STORAGE_LOAD_MEDIA (0x2D480Ch) | Closes the drive tray |
IOCTL_CDROM_FIND_NEW_DEVICES , IOCTL_STORAGE_FIND_NEW_DEVICES (0x24818h) | Lists new drives connected after OS startup or since the last call to this command |
IOCTL_CDROM_GET_CONTROL | Reports the current position of audio playback |
IOCTL_CDROM_GET_DRIVE_GEOMETRY (0x2404Ch) | Determines the disc type and its geometry (number of sectors on disc, sector size , etc.) |
IOCTL_CDROM_GET_LAST_SESSION (0x24038h) | Lists starting addresses of sessions and writes them to the TOC buffer read by IOCTL_CDROM_READ_TOC |
IOCTL_CDROM_GET_VOLUME (0x24014h) | Returns the current volume from CD-ROM |
IOCTL_CDROM_PAUSE_AUDIO (0x2400Ch) | Temporarily pauses audio playback |
IOCTL_CDROM_PLAY_AUDIO_MSF (0x24018h) | Initiates audio playback process from specified position up to the specified position |
IOCTL_CDROM_RAW_READ (0x2403Eh) | Reads raw sectors from audio discs |
IOCTL_CDROM_READ_Q_CHANNEL (0x2402Ch) | Reads data from the Q subcode channel |
IOCTL_CDROM_READ_TOC (0x24000h) | Reads the disc TOC |
IOCTL_CDROM_RESUME_AUDIO (0x24010h) | Resumes audio playback |
IOCTL_CDROM_SEEK_AUDIO_MSF (0x24004h) | Positions optical head |
IOCTL_CDROM_SET_VOLUME (0x24028h) | Sets volume from the CD-ROM |
IOCTL_CDROM_STOP_AUDIO (0x24008h) | Stops audio playback |
[*] “obsolete and currently removed from the DDK |
The DeviceIoControl function is always preceded by a call to the CreateFile function, which returns the handle of the appropriate device specified in the following format: \\. \X: , where X is the letter of the drive, with which you are going to work. Note that the dwCreationDisposition flag must be set to OPEN_EXISTING , or your attempt to access the drive will fail. A typical example of a call to this function is provided in Listing 4.1.
HANDLE hCD; // drive descriptor hCD=CreateFile("\\.\X:", GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0,0); if (hCD == INVALID_HANDLE_VALUE) // error
Note | Windows NT registers the CD drive under the following name : \\. \CdRomx , where x is the number of the drive (starting from zero) that references the same drive as the drive letter, and provides the same set of functions. |
The prototype of the DeviceIoControl function itself appears as shown in Listing 4.2.
BOOL DeviceIoControl(HANDLE hDevice, // Device descriptor DWORD dwIoControlCode, // IOCTL code of the command to be executed LPVOID lpInBuffer, // Pointer to the input buffer // (Irp >AssociatedIrp.SystemBuffer) DWORD nInBufferSize, // Size of the input buffer, in bytes LPVOID lpOutBuffer, // Pointer to the output buffer // (Irp >AssociatedIrp.SystemBuffer) DWORD nOutBufferSize, // Size of the output buffer, in bytes LPDWORD lpBytesReturned, // Pointer to the counter of returned bytes LPOVERLAPPED lpOverlapped // Pointer to the structure for asynchronous operations);
Here:
hDevice ”the descriptor that was just returned by CreateFile .
dwIoControlCode ”the IOCTL code for our operation.
lpInBuffer ”the pointer to the buffer that contains the data prepared for passing to the device (as a rule, these are command arguments). In the course of function execution, the buffer contents are copied into Irp ˆ’ > AssociatedIrp.SystemBuffer . This is mentioned here in order to prevent you from feeding the entire IRP structure to DeviceIoControl when you see this form of abracadabra in DDK.
nInBufferSize ”the size of the input buffer in bytes. In the course of function execution, it is copied into the Parameters.DeviceIoControl.InputBufferLength Structure.
lpOutBuffer ”the pointer to the output buffer, where the contents of Irp ˆ’ > AssociatedIrp.SystemBuffer are returned.
nOutBuffersSize ”pointer to the double word, where the number of bytes returned by the driver via output buffer will be written.
If the operation has been accomplished successfully, the function will return a non-zero value. Otherwise, it will return zero. For more detailed information on the error, call GetLastError .
Passing of IOCTL commands to the device doesn t require that you have administrative privileges (except for cases where the device is opened with the GENERIC_WRITE flag). This significantly improves the ergonomic properties of protection mechanisms based on this function. (Let us consider protection mechanisms for a moment, or, to be more precise, their ability to resist the attempts at cracking them. Since, obviously, the DeviceIoControl function isn t employed in common programs very often, it unmasks the headquarters of the protection mechanism. Consequently, it becomes quite easy to "get a bearing " on it. It is enough to set a breakpoint to the DeviceIoControl function and wait until the IOCTL command passed to it takes one of the above-listed values. Setting a breakpoint to CreateFile is not wise, because it will produce a large number of garbage debugger popups ( CreateFile is called any time when a file is opened/created). However, it makes sense to search for the "\\.\" string in the program body. If you succeed, the only thing left to do is to click on the cross-reference and then press <Enter>. That s it! Here is the protection code for you.
In order to ensure a better understanding of this method of interaction between an application and the device driver, consider a key fragment of the function that carries out this type of interaction (error handling has been omitted for the sake of simplicity).
//--[ReadCDDA]--------------------------------------------------- // // Reads RAW sectors from CDDA discs // ========================================== // ARC: // drive - name of the device from which to read // (for example, "\\A\X:") // start_sector - number of the first sector to read // n_sec - number of sectors to read // // RET: // == 0 - error // != 0 - pointer to the buffer containing read sectors // // NOTE: // This function supports discs only of the types supported by // the CDFS driver, which is the one that it uses. // The built-in Windows NT driver supports only CDDA discs //----------------------------------------------------------------- char* ReadCDDA(char *drive, int start_sector, int n_sec) { // Supported track types typedef enum _TRACK_MODE_TYPE { YellowMode2, // native MODE 2 (not CD-data) XAForm2, // XA MODE 2 Form 2 (Video-CD) CDDA // Audio-CD } TRACK_MODE_TYPE, *PTRACK_MODE_TYPE; // the arugment of the IOCTL_RAW_READ command typedef struct __RAW_READ_INFO { LARGE_INTEGER DiskOffset; // logical block offset in bytes ULONG SectorCount; // number of sectors to read TRACK_MODE_TYPE TrackMode; // mode of the track to read } RAW_READ_INFO, *PRAW_READ_INFO; #define CDROM_RAW_SECTOR_SIZE 2352 #define CDROM_SECTOR_SIZE 2048 int a; HANDLE hCD; DWORD x_size; char *szDrive; BOOL fResult = 0; unsigned char *buf; RAW_READ_INFO rawRead; // PREPARING THE RAW_READ_INFO STRUCTURE, passed to the CD-ROM driver rawRead.TrackMode = CDDA; // disc type - Audio CD rawRead.SectorCount = n_sec; // number of sectors to read rawRead.DiskOffset.QuadPart = start_sector * CDROM_SECTOR_SIZE; // ^^^^^^^^^^^^^^^^^ // The starting sector is specified by the number of its first byte, // rather than by its logical number. Theoretically, // pass-through numbering of bytes from the first // to the last bytes of the disc ensures full abstraction // from the hardware (sector size is returned by the // IOCTRL_CDROM_GET_DRIVE_GEOMETRY command). // In practice, however, driver architects // have made a blunder, as a result of which the driver, // instead of the pass-through byte numbers, accepts // start_address * CDROM_SECTOR_SIZE, // where CDROM_SECTOR_SIZE is the logical block size, // which, in this case, is equal to the standard sector size // of the CDDATA disc (2048 bytes), while the sector size // of CDDA discs is 2352 bytes. Therefore, DiskOffset // is equal to start_secor * CDROM_SECTOR_SIZE, // while buffer size must be equal to // start_secor * CDROM_RAW_SECTOR_SIZE // ALLOCATING MEMORY buf = malloc(CDROM_RAW_SECTOR_SIZE * n_sec); // GETTING THE DEVICE DESCRIPTOR hCD = CreateFile(drive, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); if (hCD != INVALID_HANDLE_VALUE) // PASSING THE IOCTL_CDROM_RAW_READ COMMAND TO THE DRIVER fResult = DeviceIoControl(hCD, 0x2403E /* IOCTL_CDROM_RAW_READ */, &rawRead, sizeof(RAW_READ_INFO) , buf, CDROM_RAW_SECTOR_SIZE*n_sec, &x_size, (LPOVERLAPPED) NULL); // OUTPUT OF THE RESULT (if there is any) if (fResult) for (a = 0; a <= x_size; ++a) printf("%02X%s", buf[a], (a%24)?" ":"\n") else printf("-ERROR"); printf("\n"); // EXITING CloseHandle(hCD); return (fResult)?buf:0; }
Studying TOC contents may be useful when analyzing some protected discs.
Listing 4.4 is another demo example. It illustrates the technique of reading the TOC (Table of Content) ”in audio CDs, it represents an analog of the partition table.
/*------------------------------------------------------ * * READING AND DECODING TOC * ======================= * * build 0x001 @ 26.05.2003 --------------------------------------------------------*/ main(int argc, char **argv) { int a; HANDLE hCD; unsigned char *buf; WORD TOC_SIZE; BYTE n_track; DWORD x_size,b; #define DEF_X "\\.\G:" // default drive #define argCD ((argc>1) ?argv[1] :DEF_X) // CHECKING ARGUMENTS if (argc < 2) {fprintf(stderr, "USAGE: IOCTL.read.TOC \\.\X:\n"); return 0;} // TITLE fprintf(stderr, "simple TOC reader via IOCTL\n"); // ALLOCATING MEMORY buf = (char *) malloc(buf_len); // OPENING THE DEVICE hCD=CreateFile(argv[1], GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); // EXIT IN CASE OF ERROR if (hCD == INVALID_HANDLE_VALUE) {fprintf(stderr, "-ERR: %x\n", GetLastError()); return 0;} // PASSING THE CDROM_READ_TOC COMMAND TO THE DRIVER if (DeviceIoControl(hCD, 0x24000 /* IOCTL_READ_TOC */, 0, 0, buf, buf_len, &x_size, 0) != 0) { // GETTING TOC LENGTH (it is written in reverse order) TOC_SIZE = buf[0]*0x100L + buf[1]; printf("TOC Data Length........%d\n", TOC_SIZE); // DECODING OTHER INFORMATION printf("First Session Number...%d\n", buf[2]); printf("Last Session Number....%d\n\n", (n_track=buf[3])); for (a = 1; a <= n_track; a++) { printf("track %d\n{\n",a); printf("\treserved.............%x\n", buf[a * 8 - 4]); printf("\tADRcontrol..........%d\n", buf[a * 8 - 3]); printf("\ttrack number.........%d\n", buf[a * 8 - 2]); printf("\treserved.............%d\n", buf[a * 8 - 1]); printf("\treserved.............%d\n", buf[a * 8 + 0]); printf("\tmin..................%d\n", buf[a * 8 + 1]); printf("\tsec..................%d\n", buf[a * 8 + 2]); printf("\tframe................%d\n", buf[a * 8 + 3]); printf("}\n\n"); } // PRINTING TOC CONTENTS IN RAW FORMAT printf("\n\t\t\t* * * RAW * * *\n"); for (a = 0; a < x_size; a++) printf("%02X%s", (unsigned char)buf[a], ((a+1)%22)?" ":"\n"); printf("\n\t\t\t* * * * * * *\n"); } }