Форум: "WinAPI";
Поиск по всему сайту: delphimaster.net;
Текущий архив: 2002.02.18;
Скачать: [xml.tar.bz2];




Вниз

Какой ресурс в NT жестко ассоциирован с хэндлом со значением 4 ? 


Digitman   (2001-11-05 13:30) [0]

См. не так давно обсуждавшуюся тему про самоудаление файла в разделе "Общие вопросы", фрагмент кода именно для WinNT

вот эта строчка :

CloseHandle(THandle(4));

Итак, что мы здесь закрываем в действительности, господа ?
Пошевелим мозгами вместе ?



Digitman   (2001-11-05 13:36) [1]

чтобы долго не искать - вот ссылка для интересующихся :
http://delphi.mastak.ru/cgi-bin/forum.pl?look=1&id=1002986892&n=0



Digitman   (2001-11-05 14:45) [2]

Что-то тишина гробовая сразу)
А где ж господа, гордо и неприкрыто именующие себя трояно-вирусо-творцами ?))))))))))))))))))))))))))))))))



Anatoly Podgoretsky   (2001-11-05 17:38) [3]

Читал я про этот хендл и трюк, но вроде бы только для НТ, по крайней мере пример на Win9x не отработал.
Но подробности и ссылку уже забыл, за ненадобностью.



paul_shmakov   (2001-11-05 17:51) [4]

Шевелить мозгами здесь абсолютно бесполезно - нужно посмотреть таблицу хэндлов процесса. так уж сложилось, что в nt хэндл со значением 4 всегда указывает на некий объект ядра memory mapped file. вероятно, это как раз хэндл на mmf самого exe-шника процесса.



Digitman   (2001-11-06 09:36) [5]

>Anatoly Podgoretsky
Да, в Win9x/Me это не работает, но и в W2k/XP - тоже !

>paul_shmakov
Если предположить твой вариант ("хэндл на mmf самого exe-шника процесса"), тогда - нестыковка получается, судя по коду (см. ссылку) :

сначала - CloseHandle(THandle(4))
а затем - UnmapViewOfFile





Anatoly Podgoretsky   (2001-11-06 11:11) [6]

Digitman © (06.11.01 09:36)

Сообственно меня в свое время один друг просил проверить на Win9x и он тоже также жаловался на статью и ее автора. :-)



Digitman   (2001-11-06 11:53) [7]

>Anatoly Podgoretsky
Нет, для Win9x/Me в этом трюк-коде есть индивидуальная ветка (FreeLibrary вместо CloseHandle + UnmapViewOfFile). И по ней-то как раз вопросов никаких - логика трюка на этой платформе совершенно прозрачна. Правда, и работать тоже будет не при любых условиях - трюк устойчиво работает лишь при отсутствии стандартных прологов и эпилогов, автоматически вставляемых компилятором Делфи из System.pas (там много чего делается, и это как раз в ряде случаев может и препятствовать устойчивому прохождению трюка)



Anatoly Podgoretsky   (2001-11-06 13:01) [8]

Меня в принципе эта тема никогда не интересовала, так случайно с ней столкнулся.



paul_shmakov   (2001-11-06 13:27) [9]

2 Digitman:
"нестыковка получается, судя по коду (см. ссылку): сначала - CloseHandle(THandle(4)), а затем - UnmapViewOfFile"

Честно скажу - я этот код даже не пробовал запустить. Но, если он все же работает, могу предположить, что у объекта с хэндлом со значением 4 счетчик ссылок (reference count) равен, например, 2. Т.о. первый вызов CloseHandle вызывает уменьшение счетчика до 1.



Digitman   (2001-11-06 14:04) [10]

>paul_shmakov
Так запусти !) Если ты действительно любопытный человек !)
Код-то, в 1-м приближении, работает ...
Тем не менее - вопрос остается открытым ...



Polevi   (2001-11-06 18:39) [11]

2Digitman ©
под Win98 SE не работают оба варианта (CloseHandle и FreeLibrary)



paul_shmakov   (2001-11-07 20:49) [12]

конечно я очень любопытный :) инета у меня дома не было, а как
пример работал я забыл. поэтому пришлось свой написать под nt.
после недолгих экспериментов все оказалось достаточно просто:
pe-загрузчик создает адресное пространство для процесса, а затем
проецирует туда exe-шник (т.е. создает объект ядра memory mapped file
(CreateFileMapping), проецирует mmf на адресное пространство
(MapViewOfFile), начиная с адреса image base, указанного в
pe-заголовке). как раз его (mmf) хэндл со значением 4 и находится
в таблице хэндлов каждого процесса в winnt.
итак, что нам мешает удалить исполняемый файл? а мешает нам то,
что файл как раз спроецирован на адресное пространство процесса.
решение казалось бы на ладони: убираем проекцию и закрывыем хэндл
объекта mmf, после чего можно удалить файл:

UnmapViewOfFile(GetModuleHandle(0));
CloseHandle(4);
DeleteFile(filename);

или тоже самое на асме

push 0
call GetModuleHandle
push eax
call UnmapViewOfFile
push 4
call CloseHandle
lea eax, filename
push eax
call DeleteFile

но появляется проблема: после вызова UnmapViewOfFile виртуальные
адреса памяти, по которым находилась проекция, освободятся и,
когда поток перейдет к выполнению следующей команды (вызов
DeleteFile) произойдет нарушение доступа.
но направление движения правильное! только осуществлять эти
вызовы нужно несколько иначе. поместим все в стек потока (уж
стек никуда не денется до завершения работы потока).
поместим туда и адреса функций и их параметры. кроме того
вызовем после удаления файла ExitProcess, потому что если после
удаления файла (т.е. вызова DeleteFile) поток вернется опять
же в нашу функцию, снова произойдет нарушение доступа.

push 0
lea eax, szFileName
push eax
push pExitProcess
push 4
push pDeleteFile
push hInstance
push pCloseHandle
push pUnmapViewOfFile
ret

как это работает:
1) команда ret берет из стека двойное слово с вершины
стека и передает управление по этому адресу. т.о. управление
переходит на функцию UnmapViewOfFile.
2) UnmapViewOfFile принимает один параметр (hInstance), но
кроме этого параметра в стек нужно запихнуть и адрес возврата.
поэтому в стек помещается адрес на функцию CloseHandle.
3) Отработав, UnmapViewOfFile удаляет из стека свой единственный
параметр и передает управление на CloseHandle. эта функция
тоже принимает один параметр (4), и ей также нужен в стеке
адрес возврата (теперь это указатель на DeleteFile).
4) далее аналогично.
следует заметить, что после вызова ExitProcess все потоки
процесса завершаются, и о никакой передачи управления далее
по стеку и речи быть не может.

резюме: этот способ использует недокументированные особенности
winnt 4.0 и работать будет, скорее всего, только в этой версии
виндов. тем не менее, я не вижу никаких ошибок в этом коде -
все совершенно законно. правда, теоритически возможна ситуация,
когда после вызовов CloseHandle и UnmapViewOfFile система не успеет
освободить файл, и вызов DeleteFile потерпит неудачу (хотя мне
этого добиться не удалось - все работает как часы).

и еще одно замечание. неважно, что вызвать первым - CloseHandle
или UnmapViewOfFile - это независимые между собой вызовы.
главное - вызвать и то, и другое :) независимы они потому что
MapViewOfFile увеличивает reference count объекта mmf. ну а
UnmapViewOfFile, соответственно, его уменьшает. поэтому
CloseHandle можно вызвать в любой момент.

вот небольшой пример на c++, реализующий подход.

/*
test.cpp
deletes itself. winnt only
bcc32 -tW -B test.cpp
*/
#include <windows.h>

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
char szFileName[MAX_PATH];
GetModuleFileName(GetModuleHandle(0), szFileName, MAX_PATH);

HMODULE hkrnl = GetModuleHandle("kernel32.dll");

void* pUnmapViewOfFile = GetProcAddress(hkrnl, "UnmapViewOfFile");
void* pDeleteFile = GetProcAddress(hkrnl, "DeleteFileA");
void* pExitProcess = GetProcAddress(hkrnl, "ExitProcess");
void* pCloseHandle = GetProcAddress(hkrnl, "CloseHandle");

unsigned hInstance = (unsigned)GetModuleHandle(0);

asm {
push 0
lea eax, szFileName
push eax
push pExitProcess
push 4
push pDeleteFile
push hInstance
push pCloseHandle
push pUnmapViewOfFile
ret
};

return 0; /* never get here */
}


p.s. насчет win9x ничего сказать не могу, т.к. у меня ее нет.



paul_shmakov   (2001-11-07 20:50) [13]

тоже самое на асме

;
; test.asm
; deletes itself. winnt only
; tasm32 /ml test.asm
; ilink32 test,,,import32.lib
;
.386P

.MODEL flat

EXTERN GetModuleHandleA: PROC
EXTERN GetModuleFileNameA: PROC
EXTERN GetProcAddress: PROC

.DATA

szKernel db "kernel32.dll", 0
szUnmapViewOfFile db "UnmapViewOfFile", 0
szCloseHandle db "CloseHandle", 0
szExitProcess db "ExitProcess", 0
szDeleteFile db "DeleteFileA", 0

MAX_PATH equ 260
VAR_COUNT equ 6
VAR_SIZE equ (MAX_PATH + VAR_COUNT * 4)

hInstance equ dword ptr [ebp]
pUnmapViewOfFile equ dword ptr [ebp + 4]
pCloseHandle equ dword ptr [ebp + 8]
pExitProcess equ dword ptr [ebp + 0Ch]
pDeleteFile equ dword ptr [ebp + 10h]
szFileName equ dword ptr [ebp + 14h]

.CODE

main PROC

push ebp
sub esp, VAR_SIZE
mov ebp, esp

push 0
call GetModuleHandleA
mov hInstance, eax

push MAX_PATH
lea ebx, szFileName
push ebx
push eax
call GetModuleFileNameA

mov eax, OFFSET szKernel
push eax
call GetModuleHandleA
mov ebx, eax

push OFFSET szUnmapViewOfFile
push ebx
call GetProcAddress
mov pUnmapViewOfFile, eax

push OFFSET szCloseHandle
push ebx
call GetProcAddress
mov pCloseHandle, eax

push OFFSET szExitProcess
push ebx
call GetProcAddress
mov pExitProcess, eax

push OFFSET szDeleteFile
push ebx
call GetProcAddress
mov pDeleteFile, eax

push 0
lea eax, szFileName
push eax
push pExitProcess
push 4
push pDeleteFile
push hInstance
push pCloseHandle
push pUnmapViewOfFile
ret

pop ebp ; never get here
ret

main ENDP

END main



paul_shmakov   (2001-11-07 20:51) [14]

ну и для любителей delphi

{
test.pas
deletes itself. winnt only
dcc32 test.pas
}
program test;

uses Windows;

var
FileName: PChar;
hkrnl: HMODULE;
pUnmapViewOfFile: Pointer;
pDeleteFile: Pointer;
pExitProcess: Pointer;
pCloseHandle: Pointer;
hInstance: Cardinal;
begin
{ используем динамически выделяемую память из кучи, потому как иначе
компилятор размещает массив FileName вовсе не в стеке потока }
FileName := HeapAlloc(GetProcessHeap, 0, MAX_PATH);
GetModuleFileName(GetModuleHandle(nil), FileName, MAX_PATH);

hkrnl := GetModuleHandle("kernel32.dll");

pUnmapViewOfFile := GetProcAddress(hkrnl, "UnmapViewOfFile");
pDeleteFile := GetProcAddress(hkrnl, "DeleteFileA");
pExitProcess := GetProcAddress(hkrnl, "ExitProcess");
pCloseHandle := GetProcAddress(hkrnl, "CloseHandle");

hInstance := Cardinal(GetModuleHandle(nil));

asm
push 0
mov eax, FileName
push eax
push pExitProcess
push 4
push pDeleteFile
push hInstance
push pCloseHandle
push pUnmapViewOfFile
ret
end;
end.



Digitman   (2001-11-08 11:33) [15]

>Polevi
У меня работает и под Win98SE и под NT4(SP6). Именно - в оригинальном виде.

>paul_shmakov
Спасибо за детальное исследование темы. Я двигался тем же курсом и, в принципе, получил те же результаты в NT - закрытие хэндла 4 и UnmapViewOfFile можно свободно менять местами (действительно, здесь работает RefCount обного и того же объекта - mmf)

Про "как это работает" (формирование в стеке последовательности адресов возвратов по Ret из вложенных вызовов) можно было не упоминать - понятно, как и для чего это делается.

А вот что ты скажешь про пролог/эпилог Делфи-приложения, когда в нем задействуется и инициализируется/деинициализируется TApplication ? Ведь тот же ExitProcess вызывается в эпилоге лишь после корректного освобождения ресурсов, занятых объектом Application и его деинициализации !
А занятие ресурсов в контексте этого объекта может быть связано и с дополнительным инкрементом mmf.RefCount ... Какие у тебя соображения на эту тему ?



Алексей Петров   (2001-11-08 11:51) [16]

> Digitman © (08.11.01 11:33)
> А занятие ресурсов в контексте этого объекта может быть связано и с дополнительным инкрементом mmf.RefCount ... Какие у тебя соображения на эту тему ?

Можно мне?
На сколько я понимаю, TApplication явно mmf не использует => refCount не должен меняться.



Digitman   (2001-11-08 12:22) [17]

>Алексей Петров
Явно - нет. Но - погляди внимательно на call @Halt0.....



Алексей Петров   (2001-11-08 12:57) [18]

Посмотрел - Packages он выгружает. Себя - нет.



Digitman   (2001-11-08 13:06) [19]

>Алексей Петров
А Packages ведь грузятся в то же адр.пр-во ! И сч-ки у их хэндлов не сами по себе работают ! Или ты по иному мыслишь сие ?



paul_shmakov   (2001-11-08 16:26) [20]

Что делает эпилог (для тех, кому лениво посмотреть в окне CPU :) :
1) вызывает зарегистрированные процедурой AddExitProc
функции-обработчики завершения приложения
2) вызывает Finalization у всех unit-ов
3) выгружает продгруженные package-ы
4) убирает обработчик исключений
5) вызывает ExitProcess, на чем все и заканчивается

Если мы используем код для удаления процессом своего файла (см.выше),
то пункты с 1 по 4 не будут вызваны. В некоторых ситуациях это может
быть опасным. Например, какой-нить объект ядра, семафор к примеру, не
будет корректно освобожден, что может отразиться на других
работающих процессах, если они совместно используют эти объекты.

А что касается mmf, то все будет нормально. Если конечно один из юнитов
или package не вызвал во время своей инициализации или работы
MapViewOfFile(4,....) или DuplicateHandle(GetCurrentProcess(), 4, ....),
что увеличивает reference count объекта. Но если он это вызвал, значит
он тоже как-то пытается использовать наш недокументированный трюк,
который мы тут обсуждаем. Это крайне маловероятно!

А насчет того, что "Packages ведь грузятся в то же адр.пр-во", то и здесь
все нормально, т.к. загрузка package, как и обычной dll, счетчик mmf
exe-шника не трогает.



Digitman   (2001-11-08 16:53) [21]

>paul_shmakov
Ок. Сойдемся во мнении, что невыполнение п.п 2, 4 может привести (с достаточной долей вероятности) к непредсказуемым последствиям для системы в целом при выполнении трюка.

Похали дальше ?
Исх.текст эпилога в System.pas править неразумно. Попробуем оценить потенциально возможные варианты внедриться в код эпилога ("перехватить" его вызов) в run-time. Трезво оценивая при этом проблемы совместимости с различными версиями Делфи (для простоты, скажем - от Д3 до Д6)



Digitman   (2001-11-08 17:20) [22]

Давайте, чтобы не объясняться на пальцах, "оперировать" скелет :

label Epiloque;

begin
Application.Initialize;
Application.CreateForm(Form1, TForm1);
Application.Run;

... здесь - предположительно - трюковой код

Epiloque: // адрес точки вызова п/п эпилога (call -$000023b5 у меня здесь)
end.



paul_shmakov   (2001-11-09 13:59) [23]

более реально (и правильно) будет не в эпилог внедряться, а перехватывать ExitProcess, что просто. там выполняем трюковый код и вызываем оригинальный ExiotProcess.



Digitman   (2001-11-09 14:19) [24]

>paul_shmakov
В момент вызова ExitProcess из эпилога как ты обратишься, например, к тому же объекту Application (если это необходимо по внутренней логике приложения) ? Его уже нет ! Как нет уже и многого из того, что может быть крайне необходимо на момент работы с "освобожденным" файлом после трюковых действий, но разрушено при деинициализации модулей в эпилоге.



paul_shmakov   (2001-11-09 14:39) [25]

а зачем нам Application? я же предлагаю перехватывать ExitProcess своей процедурой примерно такого содержания:

procedure OurExitProcess(uExitCode: UINT); stdcall;
var
FileName: PChar;
hkrnl: HMODULE;
pUnmapViewOfFile: Pointer;
pDeleteFile: Pointer;
pCloseHandle: Pointer;
hInstance: Cardinal;
begin
{ используем динамически выделяемую память из кучи, потому как иначе
компилятор размещает массив FileName вовсе не в стеке потока }
FileName := HeapAlloc(GetProcessHeap, 0, MAX_PATH);
GetModuleFileName(GetModuleHandle(nil), FileName, MAX_PATH);

hkrnl := GetModuleHandle("kernel32.dll");

pUnmapViewOfFile := GetProcAddress(hkrnl, "UnmapViewOfFile");
pDeleteFile := GetProcAddress(hkrnl, "DeleteFileA");
pCloseHandle := GetProcAddress(hkrnl, "CloseHandle");

hInstance := Cardinal(GetModuleHandle(nil));

asm
push 0
mov eax, FileName
push eax
push pOriginalExitProcess // !!!!!!!!!!!!!!!!!!!!
push 4
push pDeleteFile
push hInstance
push pCloseHandle
push pUnmapViewOfFile
ret
end;
end.



Digitman   (2001-11-09 14:55) [26]

>paul_shmakov

Ты ведь рассматриваешь частный случай - удаление файла. А если стоит задача самомодификации ? И исх.данные для модификации на момент вызова эпилога находятся в некоем экземпляре некоего объекта одного из классов, порожденных от TObject ?



paul_shmakov   (2001-11-09 15:29) [27]

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



Digitman   (2001-11-09 15:43) [28]

>paul_shmakov
imho, это решение через ж. ) мало чем принципиально отличается от варианта с *.bat



paul_shmakov   (2001-11-09 16:15) [29]

это единственное нормальное решение, которое позволяет хоть как-то что-либо сделать с исполняемым файлом.
и чем, собственно, оно "через ж."? то, что мы уже не сможем из этого кода обратиться к объекту Application, как и к многим другим - это факт. поэтому следует использовать механизмы winapi для передачи нужных данных, и mmf для этого очень хорошо подходит.



Digitman   (2001-11-09 16:52) [30]

>paul_shmakov
Именно поэтому оно и "через ж." . В этом и потенциальные преимущества - не разрушая объектную Делфи-среду суметь воспользоваться трюком)



paul_shmakov   (2001-11-09 17:00) [31]

ну это вряд ли осуществимо... к сожалению



Milz   (2001-12-17 14:24) [32]

На счёт самомодификации: посмотрите http://z0mbie.host.sk/w9xshare.html, работает, но только один раз, наверное "нестандартный" поток неверно завершается. Может удастся адаптировать это для дельфов?.



paul_shmakov   (2001-12-18 19:21) [33]

2 Milz:
да, здорово, но, к сожалению, только для win9x. может даже в winme уже работать не будет :(
а вот бы для nt такое найти.




Форум: "WinAPI";
Поиск по всему сайту: delphimaster.net;
Текущий архив: 2002.02.18;
Скачать: [xml.tar.bz2];




Наверх





Память: 0.82 MB
Время: 0.038 c
3-56979           Максим С.             2002-01-23 13:32  2002.02.18  
Нужен пример работы с Interbase на Delphi5


14-57202          MJH                   2001-12-28 12:59  2002.02.18  
Scooter - Ramp


14-57217          Merlin                2001-12-26 11:29  2002.02.18  
Хороший программист характеризуется умением доказать почему задачу невозможно выполнить, когда ему просто лень её выполнять...


1-57141           MystiX                2002-02-03 20:26  2002.02.18  
Заголовок окна


3-56995           vopros                2002-01-22 15:03  2002.02.18  
Опять paradox. опять индексы...Теперь пишет Index is ReadOnly