Задача
Надо
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 ;имя нашего файла(не должно быть более 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. Спасибо его авторам.
Во вложении можете просмотреть работоспособный пример.
Если gaidar захочет, он может кинуть эту статью на vbstreets.
Особая благодарность tyomitch'у и GSerg'у за помощь в преобретении знаний, необходимых для написания всего этого.