`LoadLibraryExW` вызывает исключение` 0xC0000023` из `NtMapViewOfSection` - PullRequest
0 голосов
/ 13 декабря 2018

Будет очень сложно уменьшить объем этого вопроса, но здесь мы идем.

Контекст

Я нахожусь в контексте 32-битного элемента управления ActiveX, которыйзагружен в хост (TstCon.exe).После выгрузки и перезагрузки элемента управления я получаю серию ошибок от NtMapViewOfSection, первая из которых возникает, когда odbc32.dll использует LoadLibraryExW для загрузки C:\Windows\system32\odbcint.dll.В этот момент исключение SEH генерируется где-то внутри NtMapViewOfSection с кодом 0xC0000023 (AKA STATUS_BUFFER_TOO_SMALL в соответствии с отладчиком).

Aftermath

Вот как выглядит стек вызововкогда отладчик перехватывает исключение:

ntdll.dll!_NtMapViewOfSection@40()
KernelBase.dll!BasepLoadLibraryAsDataFileInternal()
KernelBase.dll!BasepLoadLibraryAsDataFile()
KernelBase.dll!LoadLibraryExW()
odbc32.dll!_InitializeDll@0()
odbc32.dll!_SQLAllocEnv@4()
<OurDll>.dll!<OurFunction>()
...

В этот момент я использовал совершенно вменяемые методы для извлечения аргументов для вызова NtMapViewOfSection, выполнив thisдокументация :

*(void**)(ESP + 4 + 0)           /*SectionHandle*/      0x000003b0              void *
*(void**)(ESP + 4 + 4)           /*ProcessHandle*/      0xffffffff              void *
*(void**)(ESP + 4 + 8)           /*BaseAddress*/        0x00daae30              void *
*(unsigned long*)(ESP + 4 + 12)  /*ZeroBits*/           0x00000000              unsigned long
*(unsigned long*)(ESP + 4 + 16)  /*CommitSize*/         0x00000000              unsigned long
*(long long**)(ESP + 4 + 20)     /*SectionOffset*/      0x00000000 {???}        __int64 *
*(unsigned long**)(ESP + 4 + 24) /*ViewSize*/           0x00daae28 {0x00000000} unsigned long *
*(int*)(ESP + 4 + 28)            /*InheritDisposition*/ 0x00000001              int
*(unsigned long*)(ESP + 4 + 32)  /*AllocationType*/     0x00800000              unsigned long
*(unsigned long*)(ESP + 4 + 36)  /*Protect*/            0x00000002              unsigned long

Пошаговое руководство по сборке

Первоначально я обнаружил исключение, включив функцию отрыва на броске в отладчике VS, затем я смог точно определить первый сбойпозвоните и установите точку останова прямо перед собой.Вот что я вижу по отладке внутри разборки (> отмечает текущую инструкцию):

  _NtMapViewOfSection@40:
  76F2EF60  mov         eax,28h  
  76F2EF65  mov         edx,offset _Wow64SystemServiceCall@0 (76F43430h)  
> 76F2EF6A  call        edx  
  76F2EF6C  ret         28h  
  76F2EF6F  nop  

... шаг в :

  _Wow64SystemServiceCall@0:
> 76F43430  jmp         dword ptr [_Wow64Transition (76FD2218h)]  

... шаг в :

> 74A37000  jmp         0033:74A37009  
  74A37007  add         byte ptr [eax],al  
  74A37009  inc         ecx  
  74A3700A  jmp         dword ptr [edi+0F8h]  

... шаг в :

  _NtQueryObject@20:
  76F2EDC0  mov         eax,10h  
  76F2EDC5  mov         edx,offset _Wow64SystemServiceCall@0 (76F43430h)  
  76F2EDCA  call        edx  
> 76F2EDCC  ret         14h  
  76F2EDCF  nop  

И следующий шагв запускается исключение.


Нарушает среду программы, например:

  • Обновление компиляторов и сред выполнения (между MSVC90 и MSVC141), которые выявляли ошибку впервое место;
  • Переключение между конфигурациями выпуска и отладки;
  • Форсирование базового адреса для OCX с помощью флага компоновщика /base;
  • Запуск с подключенным отладчиком;
  • Мониторинг системных вызовов с помощью drstrace.exe;

... изменение, которое вызывает NtMapViewOfSection, будет успешным или неуспешным, на первый взгляд случайным образом: не все из них терпят неудачу, нозначительное число делают.Фактически, первое возникновение ошибки, вероятно, не указывает на то, откуда на самом деле возникает проблема, поскольку я редко мог вызвать ее аварийное завершение раньше (после выгрузки элемента управления), и даже получил сбой, при котором нет нашего кода находилось в стеке вызовов (путем непосредственного выхода из TstCon.exe).

Я не могу найти никакой документации (официальной или иной), упоминающей код STATUS_BUFFER_TOO_SMALL или 0xC0000023 в этом контексте,Мне не удалось найти образец в сбойных вызовах, и я не увидел соответствующих ошибок доступа после запуска Dr. Memory.

Итак ... Что может происходить в этом процессе, чтобы появились такие симптомы

1 Ответ

0 голосов
/ 10 января 2019

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

Кто-то когда-то хотел иметь возможность давать имена потокам.Для этого они использовали документированную технику 0x406D1388 (на самом деле код в значительной степени скопирован из связанной документации).Это само по себе хорошо.Но затем они хотели (или я так понял) программно получить имя, что не поддерживается методом пользовательских исключений.Это было все до того, как SetThreadDescription / GetThreadDescription существовало, поэтому они искали другой путь.И человек они были изобретательны.

Наряду с вызовом функции, которая вызвала пользовательское исключение, были следующие строки:

// Grab the TIB.
P_T_TIB pTib = GetTIB();

if (pTib == NULL)
    return false;

// If someone has already written to the arbitrary field, I don't
// want to be overwriting it.
if (pTib->pvArbitrary == NULL)
{
    // Nothing's there. Set the name.
    pTib->pvArbitrary = (void *)pszName;
}

Что, конечно, естьопределенно не "ничего".GetTIB определяется следующим образом:

// A static function to get the TIB.
static P_T_TIB GetTIB()
{
    P_T_TIB pTib = NULL;

    _asm
    {
        MOV  EAX, FS:[18h]
        MOV  pTib, EAX
    }

    return pTib;
}

Этот фрагмент очень подозрительной сборки, как я узнал, получает указатель на Информационный блок потока текущего потока.Определение T_TIB соответствует тому, что описано на этой странице, и позволяет функции именования потоков сохранять указатель на имя в произвольном слоте данных.Этот же метод использовался в функции получения имени для извлечения указателя и возврата имени.

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

Для любопытства я полностью удалил все это, поскольку он больше нигде не использовался, и просто использовал переменную thread_local для хранения имени.Дело закрыто!

...