One of the most interesting architectural features of the Windows NT operating system is its ability to interact with IDE devices via the SCSI interface! Unfortunately , this technology is poorly documented. For instance, sources such as Platform SDK, MSDN, and DDK contain only odds and ends. Thus, only a true professional or very clever and inquisitive beginner can make sense of this information.
As one could conclude from reading most discussions in teleconferences, most programmers haven t properly mastered the techniques of device management via the SCSI interface. Therefore, it is useful to cover this problem in more detail.
To solve this problem, we ll need the following items:
Description of the SCSI interface (see the SCSI Architecture Model ”3 document, which describes the main concepts of SCSI architecture, and the SCSI Primary Commands ”3 document that determines the basic set of commands for all SCSI devices; draft versions of both documents are available for downloading from: http://www.t10.org/ftp/t10/drafts/sam3/sam3r08.pdf and http://www.t10.org/ftp/t10/drafts/spc3/spc3r14.pdf, respectively. As a quick-start manual, a good recommendation is to study the The Linux SCSI programming HOWTO document, which can be downloaded from http://www. ibiblio .org/pub/Linux/docs/HOWTO/other-formats/pdf/SCSI-Programming-HOWTO.pdf).
Description of SCSI commands specific for CD-ROM drives (see the Multimedia Commands ”4 document describing the principles of programming CD-ROM/R/RW drives. The electronic version of this document can be found here: http://www.t10.org/ftp/t10/drafts/mmc4/mmc4r02b.pdf).
Description of ATAPI interface for CD-ROM/DVD drives (for instance, see the ATA Packet Interface for CD-ROMs and Specification for ATAPI DVD Devices documents. Note that DVD specifications provide better and more comprehensive descriptions of the CD-ROM architecture than native documentation written specially for CD-ROM. Versions of these documents (not the newest, but still quite suitable revisions) can be found here: www. stanford .edu/~csapuntz/ specs /INF-8020.PDF and ftp.seagate.com/sff/INF-8090.PDF. Descriptions of SCSI and ATAPI commands duplicate each other in many respects. However, some particularly difficult aspects are sometimes described better in one document than the other. Therefore, professional programmers should have both of them on hand.
Description of data storage formats, used with CDs (see the Data interchange on read-only 120 mm optical data disks ECMA-130 standard, known as the Yellow Book), which can be found here: http://www.ecma-international.org/ publications /files/ecma-st/Ecma-130.pdf. This is a basic standard for CD-ROM drives.
Besides this, any literature that in any way considers the aspects of CD-ROM programming is useful. So, what is SCSI? It is the standardized, platform-independent interface that ensures coordinated interaction of different devices and high-level applications. In fact, the acronym SCSI stands for Small Computer System Interface . Thanks to SCSI for low-level device management, it is not necessary to write custom drivers (writing a driver with the only purpose to overcome API limitations is absolutely senseless), and this task can be solved at the application level by means of sending special CDB blocks to the device. These CDB blocks might contain either standard or device-specific control commands, along with all of the parameters that they require. In fact, CDB stands for Command Descriptor Block . An example of such a block is provided below.
Offset, bytes | Contents | |
---|---|---|
0 —0 | 0 —28 | Code of the read sector command |
0 —1 | 0 —00 | Reserved |
0 —2 | 0 —00 | Sector number ”0 —69 |
0 —3 | 0 —00 | |
0 —4 | 0 —00 | |
0 —5 | 0 —69 | |
0 —6 | 0 —00 | Number of sectors |
0 —7 | 0 —01 | |
0 —8 | 0 —00 | Reserved |
0 —9 | 0 —00 | Reserved |
0 —A | 0 —00 | Reserved |
The first byte of the block stands for the operation command (in our case: — 28 ” read one or more sectors), and all other bytes of the block are parameters of this command . Pay special attention to the fact that the least significant byte of the word resides at higher address, i.e., the exact opposite to what you are accustomed to on the IBM PC! Therefore, if you transmit the — 69 0 — 00 0 — 00 0 — 00 sequence as the number of the first sector, the sector with the number — 6900000 will be read instead of sector — 00000069 , as would be expected.
A brief description of standard SCSI commands can be found in the The Linux SCSI programming HOWTO document mentioned earlier. However, this is unlikely to be sufficient for our purposes. Therefore, the commands specific to CD-ROM drives will be covered separately. Nevertheless, you must first understand how CDB blocks are encapsulated in SRB envelopes (SRB stands for SCSI Request Block ). Without these envelopes, the operating system will simply be unable to understand what you are going to do (as a matter of fact, any computer program only does the things that it is instructed to do. Sometimes, this is exactly the thing that the user wanted it to do, but not always).
The structure of the SRB block is described in detail in NT DDK. Therefore, you won t cover it here in detail but, rather, will only briefly consider its main fields.
typedef struct _SCSI_REQUEST_BLOCK { USHORT Length; // The length of the SCSI_REQUEST_BLOCK structure UCHAR Function; // Function (usually, SRB_FUNCTION_EXECUTE_SCSI == 0, // e.g., send the command for execution to the device) UCHAR SrbStatus; // Here, the device displays the command execution // progress. The most frequently encountered values are: // SRB_STATUS_SUCCESS == 01 - the command // completed successfully. // SRB_STATUS_PENDING == 00 - the command // is being executed. // SRB_STATUS_ERROR == 04 - an error was encountered. // Other values are also possible, // for a complete list see DDK. UCHAR ScsiStatus; // Here the device returns the command completion status. // If the command didn't return SUCCESS, // then there was an ERROR. UCHAR PathId // SCSI port to which the device controller is attached. // For virtual SCSI devices, this is always set to 0. UCHAR TargetId; // Controller of the device on the bus. // For IDE devices usually set to the following values: // 0 - primary, 1 - secondary UCHAR Lun; // Logical device ID within the controller // For IDE devices usually set to the following values: // 0 - master, 1 - slave CHAR QueueTag; // Not used, as a rule, and must be equal to zero CHAR QueueAction; // Not used, as a rule, and must be equal to zero CHAR CdbLength; // Length of the CDB block. // For ATAPI devices always set to 12 (0Ch) CHAR SenseInfoBufferLength; // the length of the SENSE buffer (see later) LONG SrbFlags; // Flags that usually take two values: // SRB_FLAGS_DATA_IN == 040 - data move from // device to computer (read) // SRB_FLAGS_DATA_OUT == 080 - data move from // computer to device (write) ULONG DataTransferLength; // Length of data block to be read or written LONG TimeOutValue; // Time-out value in seconds PVOID DataBuffer; // The pointer to the buffer containing // data to be read/written PVOID SenseInfoBuffer; // The pointer to the SENSE buffer (see later) struct _SCSI_REQUEST_BLOCK *NextSrb; // The pointer to the next SRB. // Not executed, as a rule PVOID OriginalRequest; // The pointer to IRP. Practically not used PVOID SrbExtension; // Not used, as a rule, and must be equal to zero UCHAR Cdb[16]; // The CDB block per sec } SCSI_REQUEST_BLOCK, *PSCSI_REQUEST_BLOCK;
After filling the fields of the SCSI_REQUEST_BLOCK structure appropriately, you can pass the SRB to the chosen device by calling on the DeviceIoControl function. All you have to do is specify an appropriate IOCTL code. That s it! Having swallowed the bait, the operating system will pass the CDB block to the appropriate device, and that device will (or won t) carry out the command contained in the CDB block. Pay special attention: The CDB block is not processed by the device driver . Instead, it is the device itself that processes the CDB. Because of this, you have virtually unlimited capabilities for device management. Note that you get all of these capabilities from the application level.
However, everything has its dark side. The device management procedure is rather capricious. A single error in filling in one of the fields can result in that the device will refuse to carry out the commands passed to it. Instead of command execution, it will either return an error code, or nothing at all. Besides this, even the slightest negligence can ruin all of the data on all of the hard drives. Therefore, it is necessary to be especially careful when choosing the TargetID and lun values! ( To determine automatically the CD-ROM address, it is possible to use the SCSI_INQUIRY command ”see the DDK demo example, which you can find in the \NTDDK\src\win_me\block\wnaspi32 file ) . However, let s forget about the dangers (after all, the life would be boring without them). Now you are going to discuss the most interesting aspect of our problem, namely, searching the IOCTL code passed on in the specific SRB.
As it turns out, it isn t easy to carry out this procedure directly. In fact, using only legal tools, it is simply impossible ! For a number of reasons, the Windows developers decided only to provide full access to the fields of the SCSI_REQUEST_BLOCK structure to driver developers. As for application developers, they are limited to using structures such as SCSI_PASS_THROUGH and SCSI_PASS_THROUGH_DIRECT . Actually, these structures have a goal similar to SRB, but are somewhat limited in their functionality. Fortunately, there are no limitations on the contents of CDB blocks and, therefore, there is still the possibility of low-level control over the hardware. Further details on this topic can be found in the section 9.2 SCSI Port I/O Control Codes in the Windows NT DDK documentation and in the source code of the demo example that can be found in the \NTDDK\src\storage\class\spti directory. Also, pay special attention to the spti.h tm file in the same directory. This file provides a detailed description of how to control the device via the SCSI interface.
In line with the name of the directory containing this demo example, this method of interaction with the device is known as SPTI (standing for SCSI Pass Through IOCTLs ). Let us briefly list the main, specific features and limitations of SPTI.
First, you must have administrative privileges for passing CDBs to devices. This isn t always convenient (although it is a positive situation from the security point of view).
Second, using of multi-target commands is not permitted (it means that you cannot issue the command for copying data from device A to device B that bypasses the processor ”although, contemporary drives support such commands and it would be wonderful if it were possible to copy CDs without loading the processor).
Third, there is no support for the reversible (i.e., bidirectional) movement of data. At any given moment, data can be moved either from device to the computer or from computer to the device, but simultaneous bidirectional data movement is impossible).
Fourth, if a class driver has been installed for the target device, CDBs must be directed to the class driver, rather than to the SCSI device itself. This means that to control the CD-ROM drive, you must interact with it via the \\.\X: device, where x is the letter representing the CD-ROM drive. An attempt to access the \\.\Scsi0 : device will return error code. As experience has shown, this is the main stumbling block for all inexperienced programmers who rush ahead without reading the documentation first.
Note | It is possible to address to the device as \\.\CdRom0 or \\.\CdRom1, without the terminating colon , where 0 or 1 stands for the ordinal number of the CD-ROM drive in the system. In contrast to a common fallacy, stating that the \\.\cdRom0 device is located at the lower level as compared to \\.\X: . From the operating system s point of view, these are synonyms. To make sure that this is true, it is enough to view the contents of the object table ( objdir \DosDevice ), which shows that \\.\X: is just a symbolic link to \\.\CdRom N . |
Fifth, there are strict limitations on the maximum size of the data being sent ( MaximumTransferLength ). These limitations are imposed by specific features of the hardware device and the miniport driver serving it. The limitations relate to the maximum size allowed for the data block and to the number of physical pages that it takes up. In order to determine specific characteristics, it is necessary to send the IOCTL_SCSI_GET_CAPABILITIES command to the device. This command will return the IO_SCSI_CAPABILITIES structure (its definition can be found in the NTDDSCSI.h file). Along with other information, this structure contains the MaximumTransferLength and MaximumPhysicalPages_in_bytes values. The maximum size of the data to be sent is calculated using the following formula: largest transfer = min (MaximumTransferLength , MaximumPhysicalPages_in_bytes ). Another way is to limit the data blocks to 64 Kbytes, which, guaranteed , will be supported by all devices. The buffer must be aligned by the value equal to AlignmentMask , which is returned in the IO_SCSI_CAPABILITIES structure. The alignment level ensured by the malloc function is sufficient for this, and no problems will arise when using it. The situation is different where memory allocation is carried out by the char buf [BUF_SIZE] construction ”in this case, there is no guarantee that your program will operate properly.
Sixth, the SCSI_PASS_THROUGH_DIRECT structure itself contains a significantly smaller number of fields and, at the same time, the values contained in fields such as PathId , TargetId , and Lun are simply ignored ! The physical address of the device on the bus is determined directly by the operating system by means of the symbolic name of the descriptor for the device, to which the SCSI_PASS_THROUGH_DIRECT request is sent.
typedef struct _SCSI_PASS_THROUGH_DIRECT { USHORT Length; // Structure size SCSI_PASS_THROUGH_DIRECT UCHAR ScsiStatus; // Status of the command execution // by SCSI device UCHAR PathId; // Ignored UCHAR TargetId; // Ignored UCHAR Lun; // Ignored UCHAR CdbLength; // Length of the CDB packet sent to the // device (in bytes) UCHAR SenseInfoLength; // Length of the SENSE buffer // to return error UCHAR *DataIn; // Direction of the data transmission ULONG DataTransferLength; // Size of the data exchange buffer // (in bytes) ULONG TimeOutValue; // Time-out value PVOID DataBuffer; // Pointer to the data exchange buffer ULONG SenseInfoOffset; // Pointer to the SENSE buffer // with error information UCHAR Cdb[16]; // Buffer containing CDB packet // (16 bytes maximum) }SCSI_PASS_THROUGH_DIRECT, *PSCSI_PASS_THROUGH_DIRECT;
Fortunately, the censorship mainly relates to fields that are rarely used in practice anyway. Therefore, nothing has been lost. Just fill the remaining fields, and the structure will be ready!
Naturally, before passing it to the device, it is necessary to first get the descriptor for the required device. This can be done as follows :
HANDLE hCD = CreateFile ("\\.\X:", GENERIC_WRITE GENERIC_READ, FILE_SHARE_READ FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
Making sure that hCD is not equal to INVALID_HANDLE_VALUE , pass the received descriptor along with the IOCTL_SCSI_PASS_THROUGHT_DIRECT structure to DeviceIoControl function. The function can be called as follows:
DeviceIoControl(hCD, 0x4D014h / * IOCTL_SCSI_PASS_THROUGH_DIRECT */, &srb, sizeof(SCSI PASS THROUGH DIRECT), sense buf, SENSE SIZE, &returned, 0);
Here, srb is the filled instance of the IOCTRL_SCSI_PASS_THROUGHT_DIRECT structure, and returned is the variable, to which the number of bytes returned by the device will be written. The sense_buf is the buffer, in which the filled instance of the IOCTL_SCSI_PASS_THROUGHT_DIRECT structure will be returned, along with the sense info ”error code of the operation being executed. If the operation is completed without errors, sense info is not returned, and sense_buf contains only IOCTL_SCSI_PASS_THROUGHT . The position of the sense info location within the buffer is determined by the contents of the SenseInfoOffset field. The value of this field must be chosen in such a way as to avoid overlapping with the IOCTRL_SCSI_ PASS_THROUGHT structure. Simply speaking, the minimum possible offset of the Sense Info is equal to: srb.SenseInfoOffset = sizeof (SCSI_PASS_THROUGH_DIRECT) . Note that SenseInfoOffset is not a pointer to the sense info but, rather, it is the index of the first byte of sense info in the returned buffer!
To detect an error, it is necessary to analyze the number of bytes returned by the DeviceIoControl function in the returned variable. If it exceeds the size of the IOCTL_SCSI_PASS_THROUGHT structure, then the buffer contains sense info. If this is the case, the sense info presence indicates that there is an error! The sense info format is shown in Fig. 4.1.
The first byte specifies the error type and usually takes the value of 70h (current error) or 71h (deferred error) . Error codes from 72h to 7Eh are reserved, and errors with the code 7Eh indicate vendor-specific sense-info format. Error codes from 00h to 6Fh are not defined in the ATAPI CD-ROM specification. Therefore, their use is undesirable (this warning is intended mainly to hardware developers, and not programmers).
Error description is encoded by the following three numbers : Sense Key, Additional Sense Code (ASC for short) and Additional Sense Code Qualifier (ASCQ). On top of this hierarchy is the Sense Key, containing the generic error categories, followed by the ASC, which describes the error in more detail. At the lowest hierarchical level, there is the ASCQ, which qualifies the additional sense code itself. If the error is exhaustively described only by the Sense Key and ASC, the ASCQ is missing (or, to be more precise, is undefined).
Description of the main error codes is provided in two tables below. It should be pointed out that the Sense Key value is not critical for error analysis, since each ASC will belong to the one Sense Key value. In contrast to this, the same ASCQ can belong to different ASCs and, therefore, it makes no sense without the precisely specified ASC value.
Sense Key | Description |
---|---|
00h | NO SENSE. No additional sense info. The operation was completed successfully. |
01h | RECOVERED ERROR. The operation has completed successfully. However, some problems were encountered in the course of its execution. These problems were eliminated by the drive itself. Additional information is provided by ASC and ASCQ. |
02h | NOT READY. The device is not ready. |
03h | MEDIUM ERROR. An irrecoverable error was encountered in the course of operation execution. Most probably, this error was caused by medium defects or incorrect data. This sense key may also be returned in cases when the drive is unable to distinguish the medium defect from hardware failure. |
04h | HARDWARE ERROR. Irrecoverable hardware error (for instance, controller failure). |
05h | ILLEGAL REQUEST. Illegal parameters passed to the drive in the CDB packet (for instance, the starting address is larger than the ending address). |
06h | UNIT ATTENTION. The medium has been replaced or the device controller has been reset. |
07h | DATA PROTECT. An attempt at reading protected data. |
8h “0Ah | Reserved. |
0Bh | ABORTED COMMAND. Command execution was for some reason aborted. |
0Eh | MISCOMPARE. Source data do not correspond to the data read from the medium. |
0Fh | Reserved. |
ASC | ASCQ | DROM | Description |
---|---|---|---|
00 | 00 | DROM | No additional sense information |
00 | 11 | R | Play operation in progress |
00 | 12 | R | Play operation paused |
00 | 13 | R | Play operation successfully completed |
00 | 14 | R | Play operation stopped due to error |
00 | 15 | R | No current audio status to return |
01 | 00 | R | Mechanical positioning or changer error |
02 | 00 | DROM | No seek complete |
04 | 00 | DROM | Logical drive not ready ”cause not reportable |
04 | 01 | DROM | Logical drive not ready ”in progress of becoming ready |
04 | 02 | DROM | Logical drive not ready ”initializing command required |
04 | 03 | DROM | Logical drive not ready ”manual intervention required |
05 | 01 | DROM | Media load ”eject failed |
06 | 00 | DROM | No reference position found |
09 | 00 | DRO | Track following error |
09 | 01 | RO | Tracking servo failure |
09 | 02 | RO | Focus servo failure |
09 | 03 | RO | Spindle servo failure |
11 | 00 | DRO | Unrecovered read error |
11 | 06 | RO | CIRC unrecovered error |
15 | 00 | DROM | Random positioning error |
15 | 01 | DROM | Mechanical positioning or changer error |
15 | 02 | DRO | Positioning error detected by read of medium |
17 | 00 | DRO | Recovered data with no error correction applied |
17 | 01 | DRO | Recovered data with retries |
17 | 02 | DRO | Recovered data with positive head offset |
17 | 03 | DRO | Recovered data with negative head offset |
17 | 04 | RO | Recovered data with retries and/or CIRC applied |
17 | 05 | DRO | Recovered data using previous sector ID |
18 | 00 | DRO | Recovered data with error correction applied |
18 | 01 | DRO | Recovered data with error correction & retries applied |
18 | 02 | DRO | Recovered data ”the data were auto-reallocated |
18 | 03 | R | Recovered data with CIRC |
18 | 04 | R | Recovered data with L-EC |
1A | 00 | DROM | Parameter list length error |
20 | 00 | DROM | Invalid command operation code |
21 | 00 | DROM | Logical block address out of range |
24 | 00 | DROM | Invalid field in command packet |
26 | 00 | DROM | Invalid field in parameter list |
26 | 01 | DROM | Parameter not supported |
26 | 02 | DROM | Parameter value invalid |
28 | 00 | ROM | Not ready to ready transition, medium may have changed |
29 | 00 | ROM | Power on, reset, or bus device reset occurred |
2A | 00 | ROM | Parameters changed |
2A | 01 | ROM | Mode parameters changed |
30 | 00 | ROM | Incompatible medium installed |
30 | 01 | RO | Cannot read medium ”unknown format |
30 | 02 | RO | Cannot read medium ”incompatible format |
39 | 00 | ROM | Saving parameters not supported |
3A | 00 | ROM | Medium not present |
3F | 00 | ROM | ATARI CD-ROM drive operating conditions have changed |
3F | 01 | ROM | Microcode has been changed |
40 | NN | ROM | Diagnostic failure on component NN ( 80h “FFh ) |
44 | 00 | ROM | Internal ATARI CD-ROM drive failure |
4E | 00 | ROM | Overlapped commands attempted |
53 | 00 | ROM | Media load or eject failed |
53 | 02 | ROM | Medium removal prevented |
57 | 00 | R | Unable to recover table of contents |
5A | 00 | DROM | Operator request or state change input (unspecified) |
5A | 01 | DROM | Operator medium removal request |
63 | 00 | R | End of user area encountered on this track |
64 | 00 | R | Illegal mode for this track |
B9 | 00 | R | Play operation aborted |
BF | 00 | R | Loss of streaming |
As you can see, it s easy! The only thing you have yet to clarify is ATAPI. Since you aren t going to interact with ATAPI directly (thanks to Windows architects , you don t have this capability), let us consider its main aspects and features only briefly. As a matter of fact, ATAPI specification was adopted in 1996 for devices logically different from hard disks, including optical, magneto-optical, and tape drives. This is a packet extension of the interface, which allows for the reception of blocks of control information whose structure was borrowed from SCSI, via ATA bus. This fact allows us to understand why Windows is so dashing when turning ATAPI devices into SCSI ones. If you neglect the hardware differences between interfaces, which are not visible at the application level, ATAPI will be very similar to SCSI. Control over ATAPI devices is carried out using the same CDBs that you have considered earlier.
Naturally, in order to control the device, it is necessary to know its controlling commands. To get this information, you ll need the ATAPI Packet Commands for CD-ROM devices reference manual. Open this manual to the description of the READ CD command ( BEh code), and you ll find the following table.
Let s try to clarify this table. The first byte, representing the code of the command being executed, doesn t raise any questions. However, it is followed by the Expected Sector Type field specifying the type of required sector. If you jump ahead a few pages, you ll find the codes corresponding to all of the existing sector types: CDDA, Mode 1, Mode 2, Mode 2 Form 1, and Mode 2 Form 2. If sector type is not known beforehand, pass the 0x0 value in this field, meaning that any type of sector is acceptable.
The next four bytes contain the address of the first sector to be ready specified in the LBA (Logical Block Address) format. This abbreviation hides elegant method of pass-through sector numbering. If you have ever programmed ancient hard disks, you ll recall the bulky computations that had to be carried out in order to determine to which head, cylinder, and sector each byte belongs. Now it is possible to do this without all that fuss. The first sector has the number 0, followed by 1, 2, 3 and so on, until the last sector of the disk is reached. The only important thing that you should bear in mind is that the byte order in this double word is inverse, which means that the most significant byte of the most significant word comes first.
Bytes with numbers from six to eight are occupied by the parameter specifying the number of sectors to be read. Note that for sector address, four bytes are allocated, while for the number of sectors to be read ”there are only three. However, you are not going to read the entire disc in one operation! Byte order in this case is also inverse, so be careful to avoid errors. Otherwise, you ll request a reading of half of the entire disc when attempting to read only a single sector.
The ninth byte is especially interesting, because it stores flags that determine which parts of the sector must be read. Besides user data, you can request sync bytes, the header, EDC/ECC codes, and even read error flags (for cracking some protection mechanisms, this information is indispensable , although, unfortunately, not every drive supports this feature).
The tenths bit is responsible for retrieving subchannel data. However, since the same data are already present in the header, it is possible, in principle, to do without this information.
Finally, the last (eleventh, counting from zero) byte, isn t used because it is reserved. Therefore, in order to ensure compatibility with newer drive models, it must be set to zero.
Naturally, depending on the type and volume of the requested data, the length of the returned sector can vary to a great degree.
Data to be transferred | Flag Bits | CD-DA | Mode1 | Mode 2 nonXA | Mode 2 Form1 | Mode 2 Form 2 |
---|---|---|---|---|---|---|
User Data | 10h | 2352 | 2048 | 2336 | 2048 | 2338 |
User Data+ EDC/ECC | 18h | (10h) | 2336 | (10h) | 2336 | (10h) |
Header Only | 20h | (10h) | 4 | 4 | 4 | 4 |
Header Only+ EDC/ECC | 28h | (10h) | Illegal | Illegal | Illegal | Illegal |
Header & User Data | 30h | (10h) | 2052 | 2340 | Illegal | Illegal |
Header & User Data + EDC/ECC | 38h | (10h) | 2344 | (30h) | Illegal | Illegal |
Sub Header Only | 40h | (10h) | 8 | 8 | 8 | 8 |
Sub Header Only+ EDC/ECC | 48h | (10h) | Illegal | Illegal | Illegal | Illegal |
Sub Header & User Data | 50h | (10h) | (10h) | (10h) | 2056 | 2336 |
Sub Header & User Data+ EDC/ECC | 58h | (10h) | (10h) | (10h) | 2344 | (50h) |
All Header Only | 60h | (10h) | 12 | 12 | 12 | 12 |
All Header Only+ EDC/ECC | 68h | (10h) | Illegal | Illegal | Illegal | Illegal |
All Header & User Data | 70h | (10h) | (30h) | (30h) | 2060 | 2340 |
All Header & User Data + EDC/ECC | 78h | (10h) | (30h) | (30h) | 2340 | 2340 |
Sync & User Data | 90h | (10h) | Illegal | Illegal | Illegal | Illegal |
Sync & User Data+ EDC/ECC | 98h | (10h) | Illegal | Illegal | Illegal | Illegal |
Sync & Header Only | A0h | (10h) | 16 | 16 | 16 | 16 |
Sync & Header Only+ EDC/ECC | A8h | (10h) | Illegal | Illegal | Illegal | Illegal |
Sync & Header & User Data | B0h | (10h) | 2064 | 2352 | Illegal | Illegal |
Sync & Header & User Data+ EDC/ECC | B8h | (10h) | 2344 | (30h) | Illegal | Illegal |
Sync & Sub Header Only | C0h | (10h) | Illegal | Illegal | Illegal | Illegal |
Sync & Sub Header Only+ EDC/ECC | C8h | (10h) | Illegal | Illegal | Illegal | Illegal |
Sync & Sub Header & User Data | D0h | (10h) | (10h) | (10h) | Illegal | Illegal |
Sync & Sub Header & User Data+ EDC/ECC | D8h | (10h) | (10h) | (10h) | Illegal | Illegal |
Sync & All Headers Only | E0h | (10h) | 24 | 24 | 24 | 24 |
Sync & All Headers Only +EDC/ECC | E8h | (10h) | Illegal | Illegal | Illegal | Illegal |
Sync & All Headers & User Data | F0h | (10h) | 2064 | 2352 | 2072 | 2352 |
Sync & All Headers & User Data+ EDC/ECC | F8h | (10h) | 2352 | (F0h) | 2352 | (F0h) |
Repeat All Above and Add Error Flags | 02h | 294 | 294 | 294 | 294 | 294 |
Repeat All Above and Add Block & Error Flags | 04h | 296 | 296 | 296 | 296 | 296 |
Note | At the application level, IDE devices are interpreted as SCSI devices. Naturally, the drive doesn t undergo any changes at the physical level. Therefore, IDE CD-ROM drive remains an IDE drive, with all of its advantages and drawbacks. However, IRP requests to these drivers, while passing via the Storage Class Driver, are translated into SRBs (SCSI request blocks). SRB requests then go to the Storage port driver (e.g., directly to the device driver), where they are once again translated into physical commands specific to that device (see Fig. 4.3). Detailed information on this absorption process are provided in NT DDK (see section 1.1 Storage Driver Architecture ). Here, it is enough to emphasize the fact that, beside the commands from the IRP_MJ_EEE family, you can also pass SRB requests, which provide significantly more freedom and flexibility, to the device. However, it is impossible to organize this interaction directly from the application level because IRP commands are private ones, while the DeviceIoControl API function passes only public commands that can be explicitly processed by the driver in IRP_MJ_DEVICE_CONTROL manager. Fig. 4.3: Windows NT internals |
Now, to get some practical experience, let us create a program reading raw sectors from CDs. The key fragment of such a program (along with the required comments) is provided below:
#define RAW_READ_CMD 0xBE // ATAPI RAW READ #define WHATS_READ 0xF8 // Sync & All Headers & User Data + EDC/ECC #define PACKET_LEN 2352 // length of one sector //#define WHATS_READ 0x10 // User Data //#define PACKET_LEN 2048 // length of one sector //-[SPTI_RAW_SECTOR_READ]------------------------------------------------------------------------------------ // The function reads one or more sectors from the CDROM in RAW format, // according to the flags passed to it. // // ARG: // CD - The drive to be opened // (something like "\\.\X:" or "\\.\CdRom0") // buf - The buffer, to which the data must be read // buf_len - Buffer size in bytes // StartSec - Number of the starting sector, counting from zero // N_SECTOR - Number of sectors to read // flags - What information must be read // (see SCSI/ATAPI specification) // // RET: // != 0 - The function was executed successfully // == 0 - The function returned error // // NOTE: // - Works only under NT/W2K/XP and requires // administrative privileges // // - 64 K of data per operation at maximum //------------------------------------------------------------------------- SPTI_RAW_SECTOR_READ (char *CD,char *buf,int buf_len,int StartSec,int N_SEC,char flags) { HANDLE hCD; SCSI_PASS_THROUGH_DIRECT srb; DWORD returned, length, status; // OPENING THE DEVICE hCD = CreateFile (driver, GENERIC_WRITEGENERIC_READ, FILE_SHARE_READFILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0); if (hCD == INVALID_HANDLE_VALUE) {printf("-ERR: open CD\n"); return 0;} // FORMING SRB memset(&srb,0,sizeof(SCSI PASS THROUGH DIRECT)); // Initialization srb.Length = sizeof (SCSI_PASS_THROUGH_DIRECT); srb.PathId = 0; // SCSI controller ID srb.TargetId = 6; // Ignored srb.Lun = 9; // Ignored srb.CdbLength = 12; // Length of the CDB // packet srb.SenseInfoLength = 0; // SenseInfo not needed srb.DataIn = SCSI_IOCTL_DATA_IN; // We are going to read. srb.DataTransferLength = PACKET_LEN*N_SECTOR; // Length of data // to read srb.TimeOutValue = 200; // TimeOut value srb.DataBufferOffset = buf; // Pointer to the // buffer srb.SenseInfoOffset = 0; // SenseInfo is // not needed // CDB packet containing ATAPI commands srb.Cdb[0] = RAW_READ_CMD; // Read RAW sector. srb.Cdb[1] = 0x0; // Any type of the disk // format is acceptable. // The number of the first sector to be read. Most significant byte // of the most significant word goes first, least significant byte // of the least significant word goes last srb.Cdb[2] = HIBYTE(HIWORD(StartSector)); srb.Cdb[3] = LOBYTE(HIWORD(StartSector)); srb.Cdb[4] = HIBYTE(LOWORD(StartSector)); srb.Cdb[5] = LOBYTE(LOWORD(StartSector)); // Number of sectors to be read srb.Cdb[6] = LOBYTE(HIWORD(N_SECTOR)); srb.Cdb[7] = HIBYTE(LOWORD(N_SECTOR)); srb.Cdb[8] = LOBYTE(LOWORD(N_SECTOR)); srb.Cdb[9] = flags; // What should be read srb.Cdb[10] = 0; // Subchannel Data Bits srb.Cdb[11] = 0; // Reserved // SENDING SRB TO ATAPI device status = DeviceIoControl(hCD, IOCTL_SCSI_PASS_THROUGH_DIRECT, &srb, sizeof (SCSI_PASS_THROUGH_DIRECT), &srb, 0, &returned, 0); return 1; }
It only remains to note that protection mechanisms that interact with the disc via SPTI can be cracked easily by setting a breakpoint to the CreateFile/ DeviceIoControl functions. To prevent extra popups of the debugger, the breakpoint filter must react only to those calls to the CreateFile function, whose leftmost argument is set to \\.\x: or to \\.\CdRomN . The second left argument of the DeviceIoControl must automatically represent either IOCTL_SCSI_PASS_ THROUGHT , or IOCTL_SCCI_PASS_THROUGHT_DIRECT , the hex codes of which are equal to 0x4D004 and 0x4D014 , respectively.