
У нас возник резкий рост количества вылетов Explorer из-за того, что указатель команд оказывался в пустоте.
0:000> r eax=00000001 ebx=008bf8aa ecx=77231cf3 edx=00000000 esi=008bf680 edi=008bf8a8 eip=7077c100 esp=008bf664 ebp=008bf678 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246 7077c100 ?? ???
Возможно, нам о чём-то скажет адрес возврата.
0:000> u poi esp 008bf6d4 test eax,eax 008bf6d6 je 008bf6b9 008bf6d8 xor edi,edi 008bf6da cmp dword ptr [esi+430h],edi
Странно, что мы исполняем код из какого-то места, не имеющего имени. Если приглядеться, то можно увидеть, что мы исполняем код из стека: esp — это 008bf664, то есть вызывающий проблемы код находится в стеке.
Кто исполняет код из стека?
Конечно, зловреды.
Давайте посмотрим, что пытается сделать это зловредное ПО.
Дизассемблирование кода рядом с последним известным хорошим кодом даёт нам следующее:
008bf6c4 call dword ptr [esi+214h] 008bf6ca inc dword ptr [ebp+8] 008bf6cd push edi 008bf6ce call dword ptr [esi+210h] ; здесь выполняется вызов в пустоту 008bf6d4 test eax,eax 008bf6d6 je 008bf6b9 008bf6d8 xor edi,edi 008bf6da cmp dword ptr [esi+430h],edi 008bf6e0 je 008bf70d
Похоже, полезная нагрузка сохранила указатели функций в esi+210 и esi+214. Давайте посмотрим, что там. Вероятно, там полезная нагрузка хранит все свои цели вызова.
0:000> dps @esi+200 008bf880 1475ff71 008bf884 00000004 008bf888 76daecf0 kernel32!WaitForSingleObject 008bf88c 76daeb00 kernel32!CloseHandle 008bf890 7077c100 008bf894 76dada90 kernel32!SleepStub 008bf898 76db6a40 kernel32!ExitProcessImplementation 008bf89c 76daf140 kernel32!RemoveDirectoryW 008bf8a0 76da6e30 kernel32!GetLastErrorStub 008bf8a4 770d53f0 user32!ExitWindowsEx 008bf8a8 003a0043 008bf8ac 0050005c 008bf8b0 006f0072 008bf8b4 00720067 008bf8b8 006d0061
Да, здесь находится полезная нагрузка указателей функций. Похоже, этот зловред собирается чего-то дождаться, а затем или выйти из процесса, или удалить папку, или выйти из Windows. Эти байты после user32!ExitWindowsEx похожи на строку Unicode, так что давайте сдампим их как строку:
0:000> du 008bf8a8 008bf8a8 "C:\Program Files\Contoso\contoso_update.exe"
Постойте-ка, что? Весь этот беспорядок наводит программа автоматического обновления Contoso?
Давайте внимательнее приглядимся к полезной нагрузке зловреда. Возможно, мы разберёмся, что происходит. Похоже, он использует в качестве оперативного плацдарма esi , так что давайте начнём дизассемблирование с esi.
008bf684 push ebp ; создание кадра стека 008bf685 mov ebp,esp 008bf687 push ebx ; сохранение ebx 008bf688 push esi ; сохранение esi 008bf689 mov esi,dword ptr [ebp+8] ; параметр 008bf68c push edi ; сохранение edi 008bf68d push 0FFFFFFFFh ; INFINITE 008bf68f push dword ptr [esi+204h] ; данные->hProcess 008bf695 lea ebx,[esi+22Ah] ; адрес пути + 2 008bf69b call dword ptr [esi+208h] ; WaitForSingleObject 008bf6a1 push dword ptr [esi+204h] ; данные->hProcess 008bf6a7 call dword ptr [esi+20Ch] ; CloseHandle 008bf6ad and dword ptr [ebp+8],0 ; count = 0 008bf6b1 lea edi,[esi+228h] ; адрес пути 008bf6b7 jmp 008bf6cd ; вход в цикл 008bf6b9 cmp dword ptr [ebp+8],28h ; ждали слишком долго? 008bf6bd jge 008bf6d8 ; тогда останов 008bf6bf push 1F4h ; 500 008bf6c4 call dword ptr [esi+214h] ; Sleep 008bf6ca inc dword ptr [ebp+8] ; count++ 008bf6cd push edi ; путь 008bf6ce call dword ptr [esi+210h] ; DeleteFile 008bf6d4 test eax,eax ; Удалён ли файл? 008bf6d6 je 008bf6b9 ; Если нет (N), входим в цикл и пробуем снова 008bf6d8 xor edi,edi 008bf6da cmp dword ptr [esi+430h],edi ; данные->fRemoveDirectory? 008bf6e0 je 008bf70d ; Нет? Пропускаем 008bf6e2 jmp 008bf6f0 ; Входим в цикл для урезания имени файла 008bf6e4 cmp ax,5Ch ; Обратная косая черта? 008bf6e8 jne 008bf6ed ; Нет? Игнорируем 008bf6ea mov dword ptr [ebp+8],ebx ; Запоминаем место последней обратной косой черты 008bf6ed add ebx,2 ; Переходим к символу 008bf6f0 movzx eax,word ptr [ebx] ; Получаем следующий символ 008bf6f3 cmp ax,di ; Конец строки? 008bf6f6 jne 008bf6e4 ; Нет? Продолжаем искать 008bf6f8 mov ecx,dword ptr [ebp+8] ; Получаем место последней обратной косой черты 008bf6fb xor eax,eax ; eax = 0 008bf6fd mov word ptr [ecx],ax ; Завершаем строку на последней обратной косой черте 008bf700 lea eax,[esi+228h] ; Получаем путь (теперь без имени файла) 008bf706 push eax ; Push адреса 008bf707 call dword ptr [esi+21Ch] ; RemoveDirectory 008bf70d cmp dword ptr [esi+434h],edi ; данные->fExitWindows? 008bf713 je 008bf71e ; Нет? Пропускаем 008bf715 push edi ; dwReason = 0 008bf716 push 12h ; EWX_REBOOT | EWX_FORCEIFHUNG 008bf718 call dword ptr [esi+224h] ; ExitWindowsEx 008bf71e push edi ; dwExitCode = 0 008bf71f call dword ptr [esi+218h] ; ExitProcess 008bf725 pop edi 008bf726 pop esi 008bf727 pop ebx 008bf728 pop ebp 008bf729 ret ; Похоже, этот код не используется 008bf72a push ebp 008bf72b mov ebp,esp 008bf72d push esi 008bf72e mov esi,dword ptr [ebp+10h] 008bf731 test esi,esi 008bf733 jle 008bf746 ...
Выполнив реверс-компиляцию обратно на C, получаем
struct Data { char code[0x0204]; HANDLE hProcess; DWORD (CALLBACK* WaitForSingleObject)(HANDLE, DWORD); BOOL (CALLBACK* CloseHandle)(HANDLE); DWORD (CALLBACK* MysteryFunction)(PCWSTR); void (CALLBACK* Sleep)(DWORD); void (CALLBACK* ExitProcess)(UINT); BOOL (CALLBACK* RemoveDirectoryW)(PCWSTR); DWORD (CALLBACK* GetLastError)(); BOOL (CALLBACK* ExitWindowsEx)(UINT, DWORD); wchar_t path[MAX_PATH]; BOOL fRemoveDirectory; BOOL fExitWindows; }; void Payload(Data* data) { // Ждём, пока процесс выполнит выход data->WaitForSingleObject(data->hProcess, INFINITE); data->CloseHandle(data->hProcess); // 20 секунд пытаемся сделать что-то с файлом for (int count = 0; !data->MysteryFunction(data->path) && count < 40; count++) { Sleep(500); } if (data->fRemoveDirectory) { PWSTR p = &data->path[1]; PWSTR lastBackslash = p; while (*p != L'\0') { if (*p == L'\\') lastBackslash = p; p++; } *lastBackslash = L'\0'; RemoveDirectoryW(data->path); } if (data->fExitWindows) { ExitWindowsEx(EWX_REBOOT | EWX_FORCEIFHUNG, 0); } }
Ага! Это не зловредное ПО, это деинсталлятор!
Скорее всего, загадочная функция — это DeleteFileW. Она ждёт, пока будет выполнен выход из основного деинсталлятора, чтобы удалить двоичный файл.
На CodeProject есть страница, показывающая, как написать самоудаляющийся файл; похоже многие компании решили использовать этот код в своих деинсталляторах. (Не знаю, соблюдают ли они лицензионные требования этого кода.)
Ну ладно, а почему же происходит вылет? Что не так с DeleteFileW?
Согласно файлу дампа, место, где должен находиться DeleteFileW , содержит 7077c100. Это указатель функции в какой-то загадочной DLL, которая не загружена. Как же так получилось?
Предположу, что функция DeleteFileW создала обходной путь (detour) в деинсталляторе Contoso. Когда деинсталлятор пытался создать таблицу полезных функций, он получил не адрес DeleteFileW , а адрес обходного пути. Затем он попытался вызвать из полезной нагрузки этот обходной путь, но поскольку обходной путь не установлен в Explorer (или если он есть, обходной путь находится в каком-то другом месте), в конечном итоге вызов выполнился в пустое пространство.
Ни инъецирование кода, ни создание обходных путей официально не поддерживаются. Возможно, кто-то добавил в деинсталлятор обходной путь, не понимая, что деинсталлятор инъецирует вызов обходного пути в Explorer. А может, обходной путь был инъецирован антивирусом. Или, возможно, обходной путь инъецирован собственным слоем совместимости приложений Windows. Как бы то ни было, результатом становился вылет Explorer.
Из-за этого люди вроде меня тратят много времени на изучение этих вылетов лишь для того, чтобы прийти к выводу, что их причиной стали другие люди, неправильно использующие систему.
Если вам нужно создать самоудаляющийся двоичный файл, пожалуйста, не используйте инъецирование кода в чей-то чужой процесс. Вот как можно удалить двоичный файл, не оставляя следов:
Создайте временный файл cleanup.js со следующим содержимым:
var fso = new ActiveXObject("Scripting.FileSystemObject"); fso.DeleteFile("C:\Users\Name\AppData\Local\Temp\cleanup.js"); var path = "C:\Program Files\Contoso\contoso_update.exe"; for (var count = 0; fso.FileExists(path) && count < 40; count++) { try { fso.DeleteFile(path); break; } catch (e) { } WSH.Sleep(500); }
Этот скрипт удаляет себя, а затем в течение двадцати секунд пытается удалить contoso_update.exe. Запустите его командой wscript cleanup.js и позвольте сделать свою работу. Никакого инъецирования кода, никаких обходных путей, всё задокументировано.
