Дата публикации статьи: 18.06.2005 10:26

ANDLL
Удаление приложения после завершения работы

Задача

Надо:
1) Удалить саму себя или любой другой файл после завершения работы программы.
2) Запретить вмеру воспитанному пользователю каким бы то не было способом воспрепятствовать удалению файла(ов).

Типичное решение

Написать bat-файл, который запускает нашу программу а потом удалаяет ее.
Полностью реализует пункт 1. И совершенно проваливает пункт 2: пользователю достаточно закрыть окно cmd, и подтвердить "закрытие зависшего приложения".

Пункт 2 под микроскопом

Итак, что может сделать пользователь, что бы восприпятствовать удалению фалйа:
1) Завершить процесс, который этот файл будет удалять.
2) Экстренно перезагрузить компьютер(скажем, нажатием на "reset")
Мы будем решать вопрос лишь 2.1. 2.2 нас не интересует, ибо тут все элементарно просто: пишем bat-файл, кидаем его в RunOnce, и все... Если только наш вмеру воспитанный пользователь не сообразит загрузить компьютер в безопасном режиме

О том, как мы будем решать пункт 2.1

Идея до смешного проста: мы находим некоторый системны процесс, который пользователю не придет в голову завершать(он ведь не знает метод нашей защиты). В нашем случае это будет explorer, который мы будем находить, как процесс окна Shell_TrayWnd. Кого этот способ не устраивает может модифицировать его по вкусу...
Далее мы записываем простенький код, аналог которого на VB выглядит так:

    Dim hProcess As Long 
    hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, OurProcessId) 
    WaitForSingleObject(hProcess, -1) 
    CloseHandle hProcess 
    DeleteFile OurFileName(в нашем случае это будет путь к нашему EXE-файлу) 
    ExitThread 0 

Причем записываем его в адресное пространство нашего системного процесса. Затем этот код запускает(в отдельном потоке, естественно). Как видите, методика до смешного проста. Увы, как мы скором увидим, реализация этой методики далеко не так проста...

Наш код в удаленном процесcе(RemoteThread)

Ясное дело, что писать VB-код в другой процесс было бы чистым... В-общем мы так делать не будем. Мы напишем альтернативу этому коду на ассемблере и сохраним его в файл RemoteThread.asm

BITS 32 
jmp StartPoint 

OpenProcess                 dd 0 ;функция OpenProcess 
WaitForSingleObject         dd 0 ;функция WaitForSingleObject 
CloseHandle                 dd 0 ;функция CloseHandle 
DeleteFile                  dd 0 ;функция DeleteFile 
ExitThread                  dd 0 ;функция ExitThread 
ProcessId                   dd 0 ;идентификатор нашего процесса 
fName                       dd 0,0,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0, 
                               0,0,0,0, 0,0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0,0,0,0, 
                               0,0,0,0,0, 0,0,0,0 ,0,0,0,0,0,0, 0,0,0 
; fName - имя нашего файла(не должно быть более 255 символов(если теоретически оно будет больше, надо просто поменять эту константу) 

CallPageBase: ;эта процедура определит базовый адрес страницы и поместит его в ebp 
mov ebp,[esp] 
sub ebp,SomePoint 
ret 

StartPoint: ;тут начинается наша программа 

mov ecx,SomePoint 
call CallPageBase 
SomePoint:;сюда будет указывать [esp] 

;Теперь в ebp хранится адрес начала нашего кода 

push dword [ebp+ProcessId] ;| 
push dword 0               ;| 
push dword 01f0fffh        ;|==>OpenProcess(PROCESS_ALL_ACCESS,0,ProcessId); где PROCESS_ALL_ACCESS=0x1f0fff 
mov edx,[ebp+OpenProcess]  ;|    Открываем наш процесс 
call edx                   ;| 
mov ebx,eax ;будем храниться описатель нашего процесса в ebx 

;Теперь в ebx хранится hProcess нашей программы 

push dword -1                     ;| 
push ebx                          ;|==>WaitForSingleObject(ebx,-1); 
mov edx,[ebp+WaitForSingleObject] ;|    Ждем, пока наш процесс угаснет 
call edx                          ;| 

push ebx                  ;| 
mov edx,[ebp+CloseHandle] ;|==>CloseHandle(ebx) 
call edx                  ;| 

mov edx,ebp   ;| 
add edx,fName ;|==>edx=fName 

push edx                 ;| 
mov edx,[ebp+DeleteFile] ;|==>DeleteFile(edx) 
call edx                 ;| 

push dword 0             ;| 
mov edx,[ebp+ExitThread] ;|==>ExitThread(0) 
call edx                 ;| 
int 3 ;===| 
int 3 ;===| 
int 3 ;===|==>собственно, маленькая отладочная заглушка 
int 3 ;===| 
int 3 ;===| 

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

WaitToDeleteFile

Функция принимает в качестве параметра Id процесса, в который надо "вживлятся", имя файла, который надо удалить и сам код, который будет удалять. Функцию можно вызывать сколько угодно раз, для разный файлов и процессов. Можно вживлятся несколько раз в один процесс. Можно удалять один файл сразу несколькими процессами(для верности).

Private Sub WaitToDeleteFile(ByVal RemoteProcessId As Long, ByVal fName As String, ByRef Code() As Byte) 
    
    'Преобразуем имя файла из Unicode в ASCII 
    Dim nStrFName As String 
    nStrFName = StrConv(fName, vbFromUnicode) 
    If LenB(nStrFName) > 255 Then Err.Raise 126, , "File path is too long": Exit Sub 
    Dim hRemoteProcess As Long 
    
    'Сохраняем в переменную начало нашего кода 
    Dim nFirstByteAddr As Long 
    nFirstByteAddr = VarPtr(Code(0)) 
    
    'Загружаем kernel32.dll(точнее, она уже загружена, так что просто получаем ее hInstance) 
    Dim hKernelLib As Long, pProc As Long 
    hKernelLib = LoadLibrary("kernel32.dll") 
    
    'Тут слегка модифицируем код. Записываем в него имя нашего файла, Id нашего процесса и адреса нужных ему системных API-функций 
    pProc = GetProcAddress(hKernelLib, "OpenProcess") 
    Call CopyMemory(nFirstByteAddr + &H5, VarPtr(pProc), 4) 
    pProc = GetProcAddress(hKernelLib, "WaitForSingleObject") 
    Call CopyMemory(nFirstByteAddr + &H9, VarPtr(pProc), 4) 
    pProc = GetProcAddress(hKernelLib, "CloseHandle") 
    Call CopyMemory(nFirstByteAddr + &HD, VarPtr(pProc), 4) 
    pProc = GetProcAddress(hKernelLib, "DeleteFileA") 
    Call CopyMemory(nFirstByteAddr + &H11, VarPtr(pProc), 4) 
    pProc = GetProcAddress(hKernelLib, "ExitThread") 
    Call CopyMemory(nFirstByteAddr + &H15, VarPtr(pProc), 4) 
    pProc = GetCurrentProcessId 
    Call CopyMemory(nFirstByteAddr + &H19, VarPtr(pProc), 4) 
    Call CopyMemory(nFirstByteAddr + &H1D, StrPtr(nStrFName), LenB(nStrFName)) 
    
    'Открываем "системный" процесс 
    hRemoteProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, RemoteProcessId) 
    
    'Резервируем в нем страницу для нашего кода 
    'Здесь есть один недостаток: эта страница так и останется в оперативной помяти, т.к. нет кода, который бы ее выгружал. 
    'Итого здесь мы потеряем 4Кб оперативной памяти 
    Dim hMem As Long 
    hMem = VirtualAllocEx(hRemoteProcess, 0, UBound(Code) + 1, MEM_COMMIT, PAGE_READWRITE) 
    
    'теперь копируем наш код в эту страницу и выставляем ей права чтения и выполнения(PAGE_EXECUTE_READ) 
    Dim tmpLng As Long 
    WriteProcessMemory hRemoteProcess, hMem, nFirstByteAddr, UBound(Code) + 1, tmpLng 
    VirtualProtectEx hRemoteProcess, hMem, UBound(Code) + 1, PAGE_EXECUTE_READ, tmpLng 
    
    'Запускаем наш код в отдельном потоке 
    CreateRemoteThread hRemoteProcess, 0, 0, hMem, 0, 0, tmpLng 
    
    'Закрываем "системный" процесс(в смысле его описатель...) 
    CloseHandle hRemoteProcess 
    
    'Закрываем описатель kernel32.dll 
    FreeLibrary hKernelLib 
End Sub 
Заключение

Собственно все... Сам ассемблерный код можно хранить в отдельном файле или в ресурсах(как я это делаю). Использованный компилятор ассемблера: nasmw. Спасибо его авторам.

Благодарности

Особая благодарность tyomitch'у и GSerg'у за помощь в преобретении знаний, необходимых для написания всего этого.