В Windows 8.1 появился новый механизм подсчета ссылок. Этот механизм описан в
статье Alex Ionescu
The Case Of The Bloated Reference Count: Handle Table Entry Changes in Windows 8.1.
Визуально новый механизм бросается в глаза черезмерно большим счетчиком ссылок
у объектов. Суть статьи Ionescu можно выделить в следующем:
- Each time a new handle is opened to an object, the reference count goes up by 0x7FFF, or 32767, on x64 Windows. On x86 Windows, the same behavior is seen by the way, but with 0x1F instead.
- Each time an existing handle to an object is used, the reference count goes down by 1.
Вся статья строится на статическом анализе счетчиков ссылок и описателей
объектов. Но для тех, кто (как я) лучше понимает происходящее в динамике,
предлагаю ниже трассировку простого теста: CreateFile + WriteFile + CloseHandle.
Файловый объект после создания (состояние счетчика "как раньше"):
kd> !handle 0x74 3 ffffe0000070f080
PROCESS ffffe0000070f080
SessionId: 1 Cid: 0978 Peb: 7ffdf000 ParentCid: 0910
DirBase: 1846c000 ObjectTable: ffffc00007e24980 HandleCount: <Data Not Accessible>
Image: test.exe
Handle Error reading handle count.
0074: Object: ffffe0000212c910 GrantedAccess: 00120196 (Inherit) (Audit) Entry: ffffc00002e591d0
Object: ffffe0000212c910 Type: (ffffe00000086dc0) File
ObjectHeader: ffffe0000212c8e0 (new version)
HandleCount: 1 PointerCount: 1
Directory Object: 00000000 Name: \Users\xx\AppData\Local\Temp\50214E8.tmp {HarddiskVolume2}
kd> !trueref ffffe0000212c910
ffffe0000212c910: HandleCount: 1 PointerCount: 1 RealPointerCount: 1
Ставим точку останова на изменение счетчика количества ссылок в объекте:
kd> dt nt!_OBJECT_HEADER ffffe0000212c8e0 PointerCount
+0x000 PointerCount : 1
kd> ba w 4 ffffe0000212c8e0
Ставим точку останова на изменение счетчика количества ссылок в элементе
таблицы описателей (захватывая соседние битовые поля):
kd> dt nt!_HANDLE_TABLE_ENTRY ffffc00002e591d0
+0x000 VolatileLowValue : 0xe0000212`c8e00001
+0x000 LowValue : 0xe0000212`c8e00001
+0x000 InfoTable : 0xe0000212`c8e00001 _HANDLE_TABLE_ENTRY_INFO
+0x000 Unlocked : 0y1
+0x000 RefCnt : 0y0000000000000000 (0)
+0x000 Attributes : 0y000
+0x000 ObjectPointerBits : 0y11100000000000000000001000010010110010001110 (0xe0000212c8e)
+0x008 HighValue : 0x0000001e`00120196
+0x008 NextFreeHandleEntry : 0x0000001e`00120196 _HANDLE_TABLE_ENTRY
+0x008 LeafHandleValue : _EXHANDLE
+0x008 GrantedAccessBits : 0y0000100100000000110010110 (0x120196)
+0x008 NoRightsUpgrade : 0y0
+0x008 Spare : 0y000000 (0)
+0x00c TypeInfo : 0x1e
kd> ba w 8 ffffc00002e591d0
И ставим еще одну точку останова на уничтожение объекта файла:
kd> bp nt!IopDeleteFile "j @rcx==ffffe0000212c910 'k 5';'gc'"
Первый останов - NtWriteFile сбрасывает поле Unlocked в элементе таблицы описателей:
kd> g
Breakpoint 1 hit
nt!NtWriteFile+0x9ef:
fffff802`ef629d6b 75e2 jne nt!NtWriteFile+0x9d3 (fffff802`ef629d4f)
kd> dt nt!_HANDLE_TABLE_ENTRY ffffc00002e591d0
+0x000 VolatileLowValue : 0xe0000212`c8e00000
+0x000 LowValue : 0xe0000212`c8e00000
+0x000 InfoTable : 0xe0000212`c8e00000 _HANDLE_TABLE_ENTRY_INFO
+0x000 Unlocked : 0y0
+0x000 RefCnt : 0y0000000000000000 (0)
+0x000 Attributes : 0y000
+0x000 ObjectPointerBits : 0y11100000000000000000001000010010110010001110 (0xe0000212c8e)
+0x008 HighValue : 0x0000001e`00120196
+0x008 NextFreeHandleEntry : 0x0000001e`00120196 _HANDLE_TABLE_ENTRY
+0x008 LeafHandleValue : _EXHANDLE
+0x008 GrantedAccessBits : 0y0000100100000000110010110 (0x120196)
+0x008 NoRightsUpgrade : 0y0
+0x008 Spare : 0y000000 (0)
+0x00c TypeInfo : 0x1e
Следующий останов - изменение счетчика в элементе таблицы описателей:
kd> g
Breakpoint 1 hit
nt!ExSlowReplenishHandleTableEntry+0x32:
fffff802`ef3651ea 418bc0 mov eax,r8d
kd> k 3
# Child-SP RetAddr Call Site
00 ffffd000`20fc7aa8 fffff802`ef629d8e nt!ExSlowReplenishHandleTableEntry+0x32
01 ffffd000`20fc7ab0 fffff802`ef3d48b3 nt!NtWriteFile+0xa12
02 ffffd000`20fc7bd0 00000000`77912772 nt!KiSystemServiceCopyEnd+0x13
kd> dt nt!_HANDLE_TABLE_ENTRY ffffc00002e591d0
+0x000 VolatileLowValue : 0xe0000212`c8e0fffe
+0x000 LowValue : 0xe0000212`c8e0fffe
+0x000 InfoTable : 0xe0000212`c8e0fffe _HANDLE_TABLE_ENTRY_INFO
+0x000 Unlocked : 0y0
+0x000 RefCnt : 0y0111111111111111 (0x7fff)
+0x000 Attributes : 0y000
+0x000 ObjectPointerBits : 0y11100000000000000000001000010010110010001110 (0xe0000212c8e)
+0x008 HighValue : 0x0000001e`00120196
+0x008 NextFreeHandleEntry : 0x0000001e`00120196 _HANDLE_TABLE_ENTRY
+0x008 LeafHandleValue : _EXHANDLE
+0x008 GrantedAccessBits : 0y0000100100000000110010110 (0x120196)
+0x008 NoRightsUpgrade : 0y0
+0x008 Spare : 0y000000 (0)
+0x00c TypeInfo : 0x1e
Далее наблюдаем останов, при котом происходит скачкообразное изменение
количества ссылок у объекта при разыменовывании описателя:
kd> g
Breakpoint 0 hit
nt!NtWriteFile+0xa1f:
fffff802`ef629d9b 4885c0 test rax,rax
kd> !trueref ffffe0000212c910
ffffe0000212c910: HandleCount: 1 PointerCount: 32769 RealPointerCount: 2
kd> k2
# Child-SP RetAddr Call Site
00 ffffd000`20fc7ab0 fffff802`ef3d48b3 nt!NtWriteFile+0xa1f
01 ffffd000`20fc7bd0 00000000`77912772 nt!KiSystemServiceCopyEnd+0x13
Сразу видим новое значение количества ссылок после referenc'а описателя (32769 = 00000000`00008001).
Следующий останов - NtWriteFile взводит обратно поле Unlocked в элементе таблицы описателей:
kd> g
Breakpoint 1 hit
nt!NtWriteFile+0xa32:
fffff802`ef629dae 498d4d30 lea rcx,[r13+30h]
kd> dt nt!_HANDLE_TABLE_ENTRY ffffc00002e591d0
+0x000 VolatileLowValue : 0xe0000212`c8e0ffff
+0x000 LowValue : 0xe0000212`c8e0ffff
+0x000 InfoTable : 0xe0000212`c8e0ffff _HANDLE_TABLE_ENTRY_INFO
+0x000 Unlocked : 0y1
+0x000 RefCnt : 0y0111111111111111 (0x7fff)
+0x000 Attributes : 0y000
+0x000 ObjectPointerBits : 0y11100000000000000000001000010010110010001110 (0xe0000212c8e)
+0x008 HighValue : 0x0000001e`00120196
+0x008 NextFreeHandleEntry : 0x0000001e`00120196 _HANDLE_TABLE_ENTRY
+0x008 LeafHandleValue : _EXHANDLE
+0x008 GrantedAccessBits : 0y0000100100000000110010110 (0x120196)
+0x008 NoRightsUpgrade : 0y0
+0x008 Spare : 0y000000 (0)
+0x00c TypeInfo : 0x1e
Потом видим парные остановы изменения количества ссылок объекта при работе с IRP:
kd> g
Breakpoint 0 hit
nt!ObfReferenceObject+0x25:
fffff802`ef2e10c5 48ffc3 inc rbx
kd> !trueref ffffe0000212c910
ffffe0000212c910: HandleCount: 1 PointerCount: 32770 RealPointerCount: 3
kd> k3
# Child-SP RetAddr Call Site
00 ffffd000`20fc79a0 fffff802`ef62a211 nt!ObfReferenceObject+0x25
01 ffffd000`20fc79e0 fffff802`ef629acc nt!IopSynchronousServiceTail+0x2f1
02 ffffd000`20fc7ab0 fffff802`ef3d48b3 nt!NtWriteFile+0x750
kd> g
Breakpoint 0 hit
nt!IopCompleteRequest+0x35d:
fffff802`ef2baaad 48ffc8 dec rax
kd> !trueref ffffe0000212c910
ffffe0000212c910: HandleCount: 1 PointerCount: 32769 RealPointerCount: 2
kd> k8
# Child-SP RetAddr Call Site
00 ffffd000`20fc7400 fffff802`ef2bc384 nt!IopCompleteRequest+0x35d
01 ffffd000`20fc7500 fffff800`00cc8b85 nt!IopfCompleteRequest+0x6a4
02 ffffd000`20fc7610 fffff800`00ccad9c Ntfs!NtfsCommonWrite+0x2f58
03 ffffd000`20fc7840 fffff800`0041cf3e Ntfs!NtfsFsdWrite+0x1dc
04 ffffd000`20fc7900 fffff800`0041bae6 fltmgr!FltpLegacyProcessingAfterPreCallbacksCompleted+0x25e
05 ffffd000`20fc7980 fffff802`ef62a08c fltmgr!FltpDispatch+0xb6
06 ffffd000`20fc79e0 fffff802`ef629acc nt!IopSynchronousServiceTail+0x16c
07 ffffd000`20fc7ab0 fffff802`ef3d48b3 nt!NtWriteFile+0x750
Затем NtWriteFile "вернул" количество ссылок:
kd> g
Breakpoint 0 hit
nt!ObDereferenceObjectDeferDeleteWithTag+0x27:
fffff802`ef2b4ac3 48ffc8 dec rax
kd> !trueref ffffe0000212c910
ffffe0000212c910: HandleCount: 1 PointerCount: 32768 RealPointerCount: 1
kd> k3
# Child-SP RetAddr Call Site
00 ffffd000`20fc79a0 fffff802`ef62a221 nt!ObDereferenceObjectDeferDeleteWithTag+0x27
01 ffffd000`20fc79e0 fffff802`ef629acc nt!IopSynchronousServiceTail+0x301
02 ffffd000`20fc7ab0 fffff802`ef3d48b3 nt!NtWriteFile+0x750
Очередной останов - сброс флага Unlocked в элементе таблицы описателей (работы
системного вызова NtClose):
kd> g
Breakpoint 1 hit
nt!ExMapHandleToPointer+0x3a:
fffff802`ef646d4a 75e6 jne nt!ExMapHandleToPointer+0x22 (fffff802`ef646d32)
kd> dt nt!_HANDLE_TABLE_ENTRY ffffc00002e591d0
+0x000 VolatileLowValue : 0xe0000212`c8e0fffe
+0x000 LowValue : 0xe0000212`c8e0fffe
+0x000 InfoTable : 0xe0000212`c8e0fffe _HANDLE_TABLE_ENTRY_INFO
+0x000 Unlocked : 0y0
+0x000 RefCnt : 0y0111111111111111 (0x7fff)
+0x000 Attributes : 0y000
+0x000 ObjectPointerBits : 0y11100000000000000000001000010010110010001110 (0xe0000212c8e)
+0x008 HighValue : 0x0000001e`00120196
+0x008 NextFreeHandleEntry : 0x0000001e`00120196 _HANDLE_TABLE_ENTRY
+0x008 LeafHandleValue : _EXHANDLE
+0x008 GrantedAccessBits : 0y0000100100000000110010110 (0x120196)
+0x008 NoRightsUpgrade : 0y0
+0x008 Spare : 0y000000 (0)
+0x00c TypeInfo : 0x1e
kd> k3
# Child-SP RetAddr Call Site
00 ffffd000`20fc7af0 fffff802`ef6462e0 nt!ExMapHandleToPointer+0x3a
01 ffffd000`20fc7b20 fffff802`ef3d48b3 nt!NtClose+0xa0
02 ffffd000`20fc7c40 00000000`77912772 nt!KiSystemServiceCopyEnd+0x13
Далее видим корректировку количества ссылок в элементе таблицы описателей:
kd> g
Breakpoint 1 hit
nt!ExDestroyHandle+0x40:
fffff802`ef646db0 4c8936 mov qword ptr [rsi],r14
kd> dt nt!_HANDLE_TABLE_ENTRY ffffc00002e591d0
+0x000 VolatileLowValue : 0xe0000212`c8e00000
+0x000 LowValue : 0xe0000212`c8e00000
+0x000 InfoTable : 0xe0000212`c8e00000 _HANDLE_TABLE_ENTRY_INFO
+0x000 Unlocked : 0y0
+0x000 RefCnt : 0y0000000000000000 (0)
+0x000 Attributes : 0y000
+0x000 ObjectPointerBits : 0y11100000000000000000001000010010110010001110 (0xe0000212c8e)
+0x008 HighValue : 0x0000001e`00120196
+0x008 NextFreeHandleEntry : 0x0000001e`00120196 _HANDLE_TABLE_ENTRY
+0x008 LeafHandleValue : _EXHANDLE
+0x008 GrantedAccessBits : 0y0000100100000000110010110 (0x120196)
+0x008 NoRightsUpgrade : 0y0
+0x008 Spare : 0y000000 (0)
+0x00c TypeInfo : 0x1e
kd> k3
# Child-SP RetAddr Call Site
00 ffffd000`20fc7ae0 fffff802`ef646360 nt!ExDestroyHandle+0x40
01 ffffd000`20fc7b20 fffff802`ef3d48b3 nt!NtClose+0x120
02 ffffd000`20fc7c40 00000000`77912772 nt!KiSystemServiceCopyEnd+0x13
Элемент таблицы описателей нам больше не интересен:
kd> bc1
Следующий останов - NtClose корректирует количество ссылок на объект (в
заголовке объекта) с учетом закрытия описателя:
kd> g
Breakpoint 0 hit
nt!NtClose+0x203:
fffff802`ef646443 ba4f62486e mov edx,6E48624Fh
kd> dt nt!_OBJECT_HEADER ffffe0000212c8e0 PointerCount
+0x000 PointerCount : 1
kd> !trueref ffffe0000212c910
ffffe0000212c910: HandleCount: 0 PointerCount: 1 RealPointerCount: 1
Затем NtClose снимает последнюю ссылку с объекта:
kd> g
Breakpoint 0 hit
nt!ObfDereferenceObjectWithTag+0x29:
fffff802`ef2e1109 48ffcb dec rbx
kd> !trueref ffffe0000212c910
ffffe0000212c910: HandleCount: 0 PointerCount: 0 RealPointerCount: 0
kd> k3
# Child-SP RetAddr Call Site
00 ffffd000`20fc7ae0 fffff802`ef646450 nt!ObfDereferenceObjectWithTag+0x29
01 ffffd000`20fc7b20 fffff802`ef3d48b3 nt!NtClose+0x210
02 ffffd000`20fc7c40 00000000`77912772 nt!KiSystemServiceCopyEnd+0x13
И последний останов - ожидаемое разрушение объекта:
kd> g
# Child-SP RetAddr Call Site
00 ffffd000`20fc7a78 fffff802`ef62af28 nt!IopDeleteFile
01 ffffd000`20fc7a80 fffff802`ef2e116f nt!ObpRemoveObjectRoutine+0x64
02 ffffd000`20fc7ae0 fffff802`ef646450 nt!ObfDereferenceObjectWithTag+0x8f
03 ffffd000`20fc7b20 fffff802`ef3d48b3 nt!NtClose+0x210
04 ffffd000`20fc7c40 00000000`77912772 nt!KiSystemServiceCopyEnd+0x13
nt!IopDeleteFile:
fffff802`ef645fbc 488bc4 mov rax,rsp
ΞρεΤΙκ