This higher level API is provided to application developers in order to count IO transactions for a process, or a job object (group of processes). Even with such an innocent face, it can easily be used to determine if the process has an active debug port.
The IO_COUNTERS structure, which is filled as a result of the call, tells us operation counts, and byte transfer counts. If you don't already know, it's pretty simple:
Read operation count - Pending or completed IO with NtReadFile
Write operation count - Pending or completed IO with NtWriteFile
Other operation count - IO with NtCreateFile/NtDeviceIoControlFile/NtFsControlFile (not limited to these, the list goes on NtCancelIoFileEx, NtQueryDirectoryFile, etc).
When a debugger is attached and it's target calls NtMapViewOfSection (hint, mapping a dll image) for a section object that is an image, it will queue a debug event. Included in this debug event, is a file handle to the image, the debugger thread waiting on the port then calls ObDuplicateObject to provide a file handle as part of it's debug message to the application.
In Peter Ferrie's anti-debug paper, he describes how to deduce that a debugger is attached due to the debugger end not closing it's duplicated handle thereby preventing exclusive access to the file.
This method however is not based off whether the debugger code forgets to close the handle, or uses it (either way preventing exclusive file access) but instead will work regardless, even if the debugger does not use the file handle and closes it upon reception. This is because the initial handle is opened within the context of the target via NtOpenFile (therefore increasing OtherOperationCount by 1), and although closed before NtMapViewOfSection returns, the fact that it incremented means the process has a debug port to dispatch messages to. Otherwise NtOpenFile would never be called, and the other operation count would not increment.
So detection can be as a simple as:
GetProcessIoCounters((HANDLE)-1,pio_counters);
//store other operation count somewhere
MapViewOfFile(); //remember, only builds a debug event if it's an image
GetProcessIoCounters((HANDLE)-1,pio_counters);
//check otheroperationcount, if incremented, asplode.