четверг, 31 марта 2011 г.

История одного бага

Баг был простой - на машине тестера все зависало, при воспроизведении же на моей машине вместо зависания я увидел BSOD:

TERMINAL_SERVER_DRIVER_MADE_INCORRECT_MEMORY_REFERENCE (cf)
Arguments:
Arg1: 8c8d715c, memory referenced
Arg2: 00000008, value 0 = read operation, 1 = write operation
Arg3: 8c8d715c, If non-zero, the instruction address which referenced the bad memoryaddress.
Arg4: 00000002, Mm internal code.
    A driver has been incorrectly ported to Terminal Server. It is referencing session space addresses from the system process context. 
    Probably from queueing an item to a system worker thread. The broken driver's name is displayed on the screen.

Debugging Details:
------------------

WRITE_ADDRESS:  8c8d715c

FAULTING_IP:
win32k!NtGdiFlushUserBatch+0
8c8d715c ??              ???

IMAGE_NAME:  win32k.sys
...
STACK_TEXT: 
8570b674 818d873f 00000003 85700d84 00000000 nt!RtlpBreakWithStatusInstruction
8570b6c4 818d91ac 00000003 c04646b8 00000000 nt!KiBugCheckDebugBreak+0x1c
8570ba70 818a9ef2 00000050 8c8d715c 00000008 nt!KeBugCheck2+0x5f4
8570bae8 8188fa74 00000008 8c8d715c 00000000 nt!MmAccessFault+0x106
8570bae8 8c8d715c 00000008 8c8d715c 00000000 nt!KiTrap0E+0xdc
8570bb70 8188c90c c000000d 83925000 00000120 win32k!NtGdiFlushUserBatch
8570bb8c 8187f751 badb0d00 8570bc04 82004398 nt!KiFastCallEntry+0xcc
8570bbfc 81a7755e 80000150 00000000 00000000 nt!ZwWriteFile+0x11
8570bc4c 81a7721b 83925000 83925000 00000001 nt!EtwpRealtimeSaveBuffer+0x113
8570bc60 81a70ccd 83925000 00000000 83925000 nt!EtwpFlushBufferToRealtime+0x54
8570bc84 81a7105f 00000000 00000000 82f84648 nt!EtwpFlushBuffer+0xa2
8570bd3c 81a70a49 00000001 00000000 82f84398 nt!EtwpFlushActiveBuffers+0x25d
8570bd7c 81a254a8 82f84648 85700680 00000000 nt!EtwpLogger+0x233
8570bdc0 8189145e 81a70816 82f84648 00000000 nt!PspSystemThreadStartup+0x9d
00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x16

Первой мыслью было посмотреть, какой же логгер ответственнен за эти вызовы.

В ядре есть массив этих самых логгеров:

kd> dds WmipLoggerContext
818ff800  00000000
818ff804  00000000
818ff808  839960c8
818ff80c  82f84648 <========= а вот и виновник торжества
818ff810  82b43908
818ff814  82b43388
818ff818  82fb5008
818ff81c  82fe9a48
...
Каждый логгер представлен структурой WMI_LOGGER_CONTEXT.
Чтобы посмотреть имя:

kd> dt nt!_WMI_LOGGER_CONTEXT 82f84648 LoggerName
   +0x030 LoggerName : _UNICODE_STRING "Eventlog-Security"

Информации было достаточно для того, чтобы поискать проблему в google:
Eventlog-Security+win32k.sys+TERMINAL_SERVER_DRIVER_MADE_INCORRECT_MEMORY_REFERENCE, но поиск ничего не дал.

Вернувшись к проблемному адресу:
WRITE_ADDRESS:  8c8d715c  

Видно, что тот невалиден:

kd> u 8c8d715c
win32k!NtGdiFlushUserBatch:
8c8d715c ??              ???

Далее была мысль, что smss.exe не успел стартовать и загрузить win32k.sys, но в списке загруженных модулей тот присутствовал:

kd> lm
start    end        module name
...    
8b835000 8b850000   luafv      (pdb symbols)          c:\symbols\luafv.pdb\C177291432194CC8A5D6B9E0834207602\luafv.pdb
8c800000 8c9ff000   win32k     (pdb symbols)          c:\symbols\win32k.pdb\593611FD42154641A8CC37E05A5F704D2\win32k.pdb
95000000 95009000   TSDDD      (pdb symbols)          c:\symbols\TSddd.pdb\C75665DBDE5247248B6C0D9389C4D3261\TSddd.pdb
...

И уже после этого я вспомнил, что win32k.sys грузится в сессионное адресное пространство, проверим, установив контекст сессии:

kd> !session -s 0

После чего он стал валидным:
kd> u 8c8d715c
win32k!NtGdiFlushUserBatch:
8c8d715c 6838010000      push    138h
8c8d7161 6860739c8c      push    offset win32k!__safe_se_handler_table+0x50d8 (8c9c7360)
8c8d7166 e839100000      call    win32k!_SEH_prolog4 (8c8d81a4)

То есть, логгер "Eventlog-Security" при наступлении какого-то события писал в real-time режиме в файл, и дальше вызывал код из win32k.sys который был недоступен из системного контекста, т.к. располагался в АП сессии.

Теперь, когда проблема понятна, непонятно было, что ее вызвало, т..к. в стеке никаких упоминаний о моих драйверах не было.

Сперва была мысль о коллауте, который вызывался из менеджера системных сервисов:

KiSystemServiceAccessTeb:
        or      ebx, [ecx]+TbGdiBatchCount ; may cause an inpage exception
        jz      short Kss40             ; if z, no batched calls
        push    edx                     ; save address of user arguments
        push    eax                     ; save service number
        call    [_KeGdiFlushUserBatch]  ; flush GDI user batch

Однако бряк поставленный сюда не сработал, да и непонятно с чего бы системному потоку быть gui-потоком.

После определенного времени в отладчике, при трассировке мне попалось на глаза имя перехваченной ф-ции, и я вспомнил о не так давно добавленной фиче, которая перехватывала некоторые ф-ции в sdt. Когда проект большой, не всегда можно помнить все мелочи, и это бывает реальной проблемой( к счастью не в этом случае ).

Таким образом нашлась и причина падения - некорректные id у ф-ций NtCreateThread и NtWriteProcessMemory для vista sp0, которые я взял из таблицы в инете(http://www.metasploit.com/users/opcode/syscalls.html), т.к. на тот момент не было возможности проверить на живой машине.

Т.е. принцип "недоверяй ничему, пока сам не проверишь" в ядерном программировании с использованием всяких недокументированных вещей справедлив как никогда =].

понедельник, 28 марта 2011 г.

Еще один способ детекта скрытых процессов и потоков

Давным-давно появились штуки типа DKOM, позволяющие скрывать потоки/процессы/(да и другие объекты ядра) от посторонних глаз, через удаление их из списков ОС( PsActiveProcessLinks/SessionProcessLinks и т.д. ).

Но так получилось, что теже самые потоки/процессы удаленные из одних мест, вполне себе продолжали жить в других(как списках, так и модулях).
Яркий пример подобного можно было наблюдать в 2008м, когда EP_X0FF опубликовал детект процессов(CsrWalker) через перечисление их в сервере подсистемы вин32(csrss).

Однако скрыв потоки/процессы и в этом месте нужно не забыть скрыть их и в третьем.
Третье место находится в ядре графической подсистемы - в win32k.sys.

Каким образом в этом списке оказываются процессы и потоки?

При конвертации потока в gui-поток, то есть тогда, когда поток делает свой первый вызов из shadow sdt.

Конвертация осуществляется ф-цией PsConvertToGuiThread, которая создает новый, больший по размеру ядерный стек для потока(для механизма рекурсивных вызовов шадоу сервисов) и налету заменяет его, устанавливает у потока поле ServiceTable, а также вызывает 2 коллаута:

typedef NTSTATUS (*PKWIN32_PROCESS_CALLOUT)( IN PEPROCESS Process, IN BOOLEAN Initialize );
typedef NTSTATUS (*PKWIN32_THREAD_CALLOUT)( IN PETHREAD Thread, IN PSW32THREADCALLOUTTYPE CalloutType );

extern PKWIN32_PROCESS_CALLOUT PspW32ProcessCallout;
extern PKWIN32_THREAD_CALLOUT  PspW32ThreadCallout;

Которые и добавляют потоки в списки win32k.sys.

Что такое коллаут и с чем его едят?

При создании сессии smss.exe загружает ядро графической подсистемы win32k.sys через полудокументированный вызов: 

NtSetSystemInformation( SystemLoadAndCallImage, ... ), который маппит win32k.sys в сессинное пространство и вызывает точку входа драйвера.

А в точке входа вызывается ф-ция:

PsEstablishWin32Callouts( W32pProcessCallout,
                              W32pThreadCallout,
                              UserGlobalAtomTableCallout,
                              UserPowerEventCallout,
                              UserPowerStateCallout,
                              UserJobCallout,
                              (PVOID)NtGdiFlushUserBatch);

Которая инициализирует указатели валидными адресами ф-ций.
То есть коллаут это просто указатель который будет проинициализирован при загрузке win32k.sys.

Таким образом при конвертации потока в гуишный вызовется соответствующие коллауты для потока и процесса.
Внутри которых будут выделены новые entry принадлежащие win32k.sys и представленные структурами PROCESSINFO и THREADINFO.
Данные коллауты также вызываются и при уничтожении потока, чтобы соответственно отлинковаться из списков.

PPROCESSINFO gppiList;     // глобальный список процессов
ULONG gdwGuiThreads;       // счетчик гуишных потоков, апдейтится каждый раз при создании нового гуишного треда

Добравшись до процесса можно получить список его потоков:

typedef struct tagPROCESSINFO
{
    W32PROCESS;
    PTHREADINFO     ptiList;
...
}PROCESSINFO;

Ну и соот-но для любого потока есть указатель на структуру процесса:

typedef struct tagTHREADINFO
{
    W32THREAD;
    PTL             ptl;
    PPROCESSINFO    ppi;
...
}THREADINFO;

Кроме того, в данных структурах много интересной информации, такой как очереди сообщений(input/post), Integrity Level (vista+, т.к. UIPI появилась с висты), инфа о win station и десктопе и т.д.

Таким образом, недостаточно хорошо спрятанный процесс/поток может быть найден через перечисление процессов и их потоков в ядре графической подсистемы.
Ограничение: НЕ-gui потоки не будут найдены.

вторник, 15 марта 2011 г.

Легальный способ уронить windows в BSOD из юзермода


Ага, есть и такой. Правда нужны привелегии администратора.

Фича связана с аудитом, в реестре есть параметр:
HKLM\SYSTEM\CurrentControlSet\Control\Lsa\CrashOnAuditFail(http://technet.microsoft.com/en-us/library/cc963220.aspx), который вызовет BSOD при переполнении журнала аудита.

Как воспроизвести:

1) HKLM\SYSTEM\CurrentControlSet\Control\Lsa\CrashOnAuditFail устанавливаем в 1 (The feature is on. The system halts when it cannot record an event in the Security Log.)

2) restart ( чтобы изменения вступили в силу )

2) ставим для журнала аудита минимальный размер ( 64 кб ), чтобы он переполнился поскорее

3) включаем аудит на создание и удаление процессов

4) выполняем нечто вроде:

for (i; i < MAX_PROCESS_COUNT; i++)
        WinExec("C:\\WINDOWS\\NOTEPAD.EXE", SW_SHOW );

чтобы заполнить журнал аудита

5) как только журнал заполнится система сгенерит BSOD с багчеком 0xc0000244.

После BSOD, CrashOnAuditFail будет сброшен системой( иначе невозможно было бы загрузится из-за повторных бсодов ), а в отладчике можно увидеть сообщение, что на данное значение полагаться не стоит (т.е. впоследствии его нужно переустановить вручную).

Но фишка моей заметки не описать то, что описано в мсдн, а копнуть немного глубже, а именно в сторону возможности перехвата процессов.

Если посмотреть стек вызовов на момент бсода, то:

kd> k
ChildEBP RetAddr 
f7a34900 804f7bad nt!RtlpBreakWithStatusInstruction
f7a3494c 804f879a nt!KiBugCheckDebugBreak+0x19
f7a34d2c 804f8cc5 nt!KeBugCheck2+0x574
f7a34d4c 80646c0b nt!KeBugCheckEx+0x1b
f7a34d74 80534c12 nt!PopGracefulShutdown+0x15d
f7a34dac 805c621c nt!ExpWorkerThread+0x100
f7a34ddc 80541de2 nt!PspSystemThreadStartup+0x34
00000000 00000000 nt!KiThreadStartup+0x16

Можно увидеть, что код выполняется в рабочем потоке, т.е. не в контексте вызывающего.
А вот если бы бсод случался в контексте вызывающего, то через перехват соответствующих ф-ций (любым способом) генерирующих багчек, и через откат состояний можно было бы возвращать управление на саму проверку аудита. Таким образом можно было бы перехватить кучу действий при включенном аудите (http://technet.microsoft.com/ru-ru/library/cc779526%28WS.10%29.aspx), причем обнаружить подобный перехват было бы нелегко.
Но увы и ах, не судьба =]