Great Код (Text): если иркл меньше 2, то обращаться к выгруженной памяти можно и она сама подгрузится. Код (Text): PLDR_TABLE = pListEntryNext; oldirql = KeGetCurrentIrql(); DbgPrint(" * * * * * IRQL: %d\n", oldirql); if (oldirql < 1) // 2 if(_stricmp((PWCHAR) PLDR_TABLE->FullDllName.Buffer, L"kernel32.dll",12) == 0) kernel = (ULONG) PLDR_TABLE->DllBase; -------------------------------------------------------------- WINDBG WATCH FullDllName | struct _UNICODE_STRING "--- memory read error at address 0x24a48d00 ---" -------------------------------------------------------------- WINDBG * * * * * IRQL: 0 <---------- *** Fatal System Error: 0x0000007e (0xC0000005,0x80538F17,0xBAD07B20,0xBAD0781C) FAULTING_IP: nt!__ascii_stricmp+17 80538f17 8a27 mov ah,byte ptr [edi] ; edi==24a48d00 адрес FullDllName EXCEPTION_RECORD: bad07b20 -- (.exr 0xffffffffbad07b20) ExceptionAddress: 80538f17 (nt!__ascii_stricmp+0x00000017) ExceptionCode: c0000005 (Access violation) ExceptionFlags: 00000000 NumberParameters: 2 Parameter[0]: 00000000 Parameter[1]: 24a48d00 Attempt to read from address 24a48d00 CONTEXT: bad0781c -- (.cxr 0xffffffffbad0781c) eax=24a48d6b ebx=00000000 ecx=80526ed0 edx=7c901005 esi=badfb681 edi=24a48d00 eip=80538f17 esp=bad07be8 ebp=bad07bf4 iopl=0 nv up ei ng nz na pe nc cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010286 nt!__ascii_stricmp+0x17: 80538f17 8a27 mov ah,byte ptr [edi] ds:0023:24a48d00=?? SYMBOL_STACK_INDEX: 1 SYMBOL_NAME: _KEXP03!RunProcess+20f FOLLOWUP_NAME: MachineOwner MODULE_NAME: _KEXP03 IMAGE_NAME: _KEXP03.sys DEBUG_FLR_IMAGE_TIMESTAMP: 4c2bc5bb STACK_COMMAND: .cxr 0xffffffffbad0781c ; kb FAILURE_BUCKET_ID: 0x7E_BADMEMREF__KEXP03!RunProcess+20f BUCKET_ID: 0x7E_BADMEMREF__KEXP03!RunProcess+20f пробовал KeLowerIrql, KeRaiseIrql, не помогло, IRQL и так 0
нельзя понижать irql ниже уровня, на котором был вызван твой код. в регистре edi адрес тычет вникуда. к тому же, код 7e - KMODE_EXCEPTION_NOT_HANDLED. это обрабатывается в __try/__except.
at0s Некоторое время назад была сделана ссылка на брутфорс – тогда мы хотели намекнуть на непригодность карьерных методов программирования, особенно в ядре. Без аллюзий: если перед началом составления кода воспринять совсем немного первичной теории, то отпадёт огромное множество простейших базовых вопросов. А в твоём случае – когда выполняется итеративная работа над задачами смежных классов – знание основ просто жизненно необходимо. TLDR: rtfm. JhanGhuangxi >MmProbeForRead не подойдет? Great >а смысл ее юзать? ProbeForRead() подойдёт. Более того, при дереференсах указателей, полученных из юзермода, её использование необходимо. Почему? Да потому что неконтролируемые внешние указатели могут ссылаться на ядерную память. А обычно ведь никто сознательно не добавляет в свои коды скрытый функционал в виде DoS или EoP, не так ли? Или, даже если оставить злые умыслы снаружи – кто гарантирует, что полученные из r3 указатели не указывают на невалидную ядерную память? Ведь блок try\except не отлавливает такие дереференсы – они просто приводят к мгновенному бсоду [и это правильно и логично]. at0s Если всё же необходимо просмотреть базу данных ldr (для сравнения с реальной ситуацией, например), то вот некоторые направляющие (с акцентом на референс указателей и порядок пробирования): Код (Text): UNICODE_STRING ReferenceDllName = L"kernel32.dll"; Peb = ...; if (!Peb) return STATUS_BOLT; LDR* Ldr = Peb->Ldr; ENTRY *EntryHead, *CurrentEntry, *PreviousEntry; EntryHead = CurrentEntry = PreviousEntry = NULL; try { ProbeForRead(Ldr); CurrentEntry = EntryHead = CONTAINING_RECORD(&Ldr.XxxList, LDR_DATA_TABLE_ENTRY, XxxLinks); while (CurrentEntry = CONTAINING_RECORD(CurrentEntry->XxxLinks.Flink, LDR_DATA_TABLE_ENTRY, XxxLinks), CurrentEntry != EntryHead) { ProbeForRead(CurrentEntry); PreviousEntry = CurrentEntry; UNICODE_STRING CapturedDllName = CurrentEntry->DllName; ProbeForRead(CapturedDllName.Buffer, CapturedDllName.Length, ALIGN_OF(WCHAR)); if (RtlCompareUnicodeString(&CapturedDllName, &ReferenceDllName) == 0) { // match ... } } } except(EXCEPTION_EXECUTE_HANDLER) { // какие-то указатели невалидны (ссылаются на неаллоцированный регион, ядерную память, etc). ... } Также обрати внимание, что для сравнения строк используется RtlCompareUnicodeString(). Сравнивать через wcscmp() [ и тем более через strcmp() ] – неправильно. Да, неправильно даже тогда, когда сравнение происходит со строкой заранее известной длины. При референсе юзермодной памяти по пробированному указателю это не опасно, но в других ситуациях это может быть фатально: Код (Text): PUNICODE_STRING String = GetAbsolutelyValidPointerToAbsolutelyValidUnicodeString(); try { // // Мы в try-блоке, а 'String' – валидный пойнтер, указывающий на абсолютно валидную структуру. // 'String->Buffer' также валиден. IRQL == Passive. // Несмотря на всё это, выполнение следующей строки может привести к мгновенному бсоду: // if (_wcsnicmp(String->Buffer, L"kernel32.dll", 12) == 0) { ... } } except(EXCEPTION_EXECUTE_HANDLER) { ... }
Clerk Great спасибо, вы правы ошибка была в структуре #if (VER_PRODUCTBUILD >= 2600) почему-то не сработал.... Sol_Ksacap Тогда это меня немного задело, но подумав, я понял что ты прав, как и впрочем сейчас. Я отлично понимаю что ты имеешь ввиду. [off] Как-то, в 1994 году ( кажется вчера ) решил написать прогу под Windows (3.1). К этому времени за спиной было много "подвигов": крякал все что попадалось под руку, на спор ( если за 5 - 10 мин, то ящик пива ), писал защиты, шифровшики, драйвера для драйверов (если помнишь хаос, который тогда был), которые шеф удачно продавал ( работал системшиком в програмисткой фирме), вирусы-шутки, во всех дочкиных играх вместо 3-х жизней было где-то 20, набор работаюшей .сом програмы (примитивной) прямо в нортон редакторе ( ящик пива ). и т. д. И когда для того чтоб открыть одно окно в Windows и написать "Hello World", мне пришлось набрать 4 страницы кода, я решил что это не мое, благо работы и так было много т.к. мастдай на серьезных системах никто не ставил. Последние 9 лет не писал системных прог, когда же решил вернуться, ... имеем то, что имеем.... расплачиваюсь за недальновидность. TLDR это в назидание "молодым" - если остановитесь, то начинать придется не с нуля, а с минуса [off end] спасибо за дельные замечания, уже исправил как обычно, в 7 утра, запускаю дебагер Read This F***ing Manual Умирает молодой талантливый программист. Попадает Богу на суд, и плачется: - Господи, почему я умер молодым? Ведь я был хорошим, жене не изменял, вирусов не писал, на порносайты не лазил, за что ты меня лишил жизни? Бог поднимает библию, и грозит программисту пальчиком: - RTFM, батенька, RTFM...
В данном случае (теоретически) может быть проведена "Argument Switch Attack", то есть значение Buffer может быть инвалидировано сторонним кодом после проверки String (защитой является замена типа String на "значение", а не "указатель"). В то же время, такой атаке подвержена и предполагаемая RtlCompareUnicodeString, поскольку принимает указатели на структуры, которые находятся не известно где. Для сравнения далее приведен исходник и псевдокод упомянутых функций. Код (Text): LONG RtlCompareUnicodeString( IN PUNICODE_STRING String1, IN PUNICODE_STRING String2, IN BOOLEAN CaseInSensitive ) /*++ Routine Description: The RtlCompareUnicodeString function compares two counted strings. The return value indicates if the strings are equal or String1 is less than String2 or String1 is greater than String2. The CaseInSensitive parameter specifies if case is to be ignored when doing the comparison. Arguments: String1 - Pointer to the first string. String2 - Pointer to the second string. CaseInsensitive - TRUE if case should be ignored when doing the comparison. Return Value: Signed value that gives the results of the comparison: Zero - String1 equals String2 < Zero - String1 less than String2 > Zero - String1 greater than String2 --*/ { PWCHAR s1, s2, Limit; LONG n1, n2; WCHAR c1, c2; s1 = String1->Buffer; s2 = String2->Buffer; n1 = String1->Length; n2 = String2->Length; ASSERT((n1 & 1) == 0); ASSERT((n2 & 1) == 0); ASSERT(!(((((ULONG_PTR)s1 & 1) != 0) || (((ULONG_PTR)s2 & 1) != 0)) && (n1 != 0) && (n2 != 0))); Limit = (PWCHAR)((PCHAR)s1 + (n1 <= n2 ? n1 : n2)); if (CaseInSensitive) { while (s1 < Limit) { c1 = *s1++; c2 = *s2++; if (c1 != c2) { // // Note that this needs to reference the translation table! // c1 = NLS_UPCASE(c1); c2 = NLS_UPCASE(c2); if (c1 != c2) { return (LONG)(c1) - (LONG)(c2); } } } } else { while (s1 < Limit) { c1 = *s1++; c2 = *s2++; if (c1 != c2) { return (LONG)(c1) - (LONG)(c2); } } } return n1 - n2; } Код (Text): int _wcsnicmp(const wchar_t *String1, const wchar_t *String2, size_t count) { int result; const wchar_t *s1; const wchar_t *s2; wchar_t c1; wchar_t c2; result = 0; if ( count ) { s1 = String1; s2 = String2; do { c1 = *s1++; if ( c1 >= 0x41 && c1 <= 0x5A ) c1 += 32; c2 = *s2++; if ( c2 >= 0x41 && c2 <= 0x5A ) c2 += 32; } } while ( !(--count) && c1 && c1 == c2 ); result = c1 - c2; } return result; } Во втором случае злоумышленник может исказить длину String1 и добиться неверного результата, но подобное можно получить и в первой функции, если строки не за-capture-ны. Что еще, не считая NLS в первом варианте?