Низкоуровневое программирование для Дzenствующих
Вид материала | Документы |
СодержаниеДампер процессов System_thread_information th[1] Получение списка загруженных процессом модулей Определение ImageBase и ImageSize |
- Низкоуровневое программирование, 108.99kb.
- Курс является базовым как для изучения других математических дисциплин, так и для более, 36.89kb.
- 1 Обобщенное программирование. Обобщенное программирование это еще одна парадигма программирования,, 55.18kb.
- Введение в линейное программирование линейное программирование (ЛП), 139.72kb.
- Учебно-методический комплекс для студентов заочного обучения специальности Прикладная, 63.23kb.
- Аттестационное тестирование в сфере профессионального образования, 72.49kb.
- Лекции по дисциплине «Социальное моделирование и программирование», 44.69kb.
- Программа дисциплины Линейное программирование Семестр, 17.93kb.
- Программа дисциплины "Программирование" для направления, 488.76kb.
- Рабочая программа по дисциплине Программирование на языке высокого уровня для специальности, 182.97kb.
Дампер процессов
Основой получения списка процессов является функция NtQuerySystemInformation. Читатель может заворчать – вот, опять Native API. Да, именно так. Только учитывайте, что бывают РАЗНЫЕ уровени недокументированности! Скажем, весьма и весьма вероятно, что реализация некоторых Mi*-функций, рассмотренная нами ранее, может и, скорее всего, будет, варьироваться от ОС к ОС и даже от SP к SP, в то время как некоторые Native API функции весьма стабильны и едва ли изменятся в будущем. Во всяком случае, прототип, название и смысл должны сохранится. А смысл этой функции настолько велик, что Шрайбер назвал ее «кладезем» информации о системе. Также Гарри Неббет (Gary Nebbet) неплохо осветил эту тему в 1 главе своей книги. Кое-что из этой информации мы повторим здесь. Приступим:
NTSYSAPI NTSTATUS NTAPI NtQuerySystemInformation (
IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength OPTIONAL
);
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation,
SystemProcessorInformation,
SystemTimeOfDayInformation,
SystemPathInformation,
SystemProcessInformation,
SystemCallCountInformation,
SystemDeviceInformation,
SystemProcessorPerformanceInformation,
SystemFlagsInformation,
SystemCallTimeInformation,
SystemModuleInformation,
SystemLocksInformation,
SystemStackTraceInformation,
SystemPagedPoolInformation,
SystemNonPagedPoolInformation,
SystemHandleInformation,
SystemObjectInformation,
SystemPageFileInformation,
SystemVdmInstemulInformation,
SystemVdmBopInformation,
SystemFileCacheInformation,
SystemPoolTagInformation,
SystemInterruptInformation,
SystemDpcBehaviorInformation,
SystemFullMemoryInformation,
SystemLoadGdiDriverInformation,
SystemUnloadGdiDriverInformation,
SystemTimeAdjustmentInformation,
SystemSummaryMemoryInformation,
SystemNextEventIdInformation,
SystemEventIdsInformation,
SystemCrashDumpInformation,
SystemExceptionInformation,
SystemCrashDumpStateInformation,
SystemKernelDebuggerInformation,
SystemContextSwitchInformation,
SystemRegistryQuotaInformation,
SystemExtendServiceTableInformation,
SystemPrioritySeperation,
SystemPlugPlayBusInformation,
SystemDockInformation,
SystemPowerInformation,
SystemProcessorSpeedInformation,
SystemCurrentTimeZoneInformation,
SystemLookasideInformation
} SYSTEM_INFORMATION_CLASS;
Где, SystemInformationClass – тип требуемой информации, нас интересует только информация о процессах, т.е. SystemInformationClass = 5, SystemInformation – указатель на буфер данных, SystemInformationLength – размер буфера данных, ReturnLength – размер записанных в буфер данных. Если выделенного буфера недостаточно, то в параметр ReturnLength будет возвращён требуемый размер буфера. Рекомендуемый размер = sizeof(SYSTEM_PROCESS_INFORMATION) * 1024, так как вряд ли у кого то будет запущенно 1024 процесса.
Далее если не произошло ошибок, в буфер SystemInformation будет передана структура SYSTEM_PROCESS_INFORMATION, описывающая отдельный процесс в списке. Первым членом структуры (NextEntryOffset) будет смещение на следующий процесс в списке, если же оно равно NULL, значит это последний процесс в списке.
typedef struct _SYSTEM_PROCESS_INFORMATION {
ULONG NextEntryOffset;
ULONG NumberOfThreads;
LARGE_INTEGER SpareLi1;
LARGE_INTEGER SpareLi2;
LARGE_INTEGER SpareLi3;
LARGE_INTEGER CreateTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
UNICODE_STRING ImageName;
KPRIORITY BasePriority;
HANDLE UniqueProcessId; // PID
HANDLE InheritedFromUniqueProcessId;
ULONG HandleCount;
ULONG SpareUl2;
ULONG SpareUl3;
ULONG PeakVirtualSize;
ULONG VirtualSize;
ULONG PageFaultCount;
ULONG PeakWorkingSetSize;
ULONG WorkingSetSize;
ULONG QuotaPeakPagedPoolUsage;
ULONG QuotaPagedPoolUsage;
ULONG QuotaPeakNonPagedPoolUsage;
ULONG QuotaNonPagedPoolUsage;
ULONG PagefileUsage;
ULONG PeakPagefileUsage;
ULONG PrivatePageCount;
SYSTEM_THREAD_INFORMATION TH[1];
} SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;
Простой пример использования функции, подразумевается, что буфер выделен, и NtQuerySystemInformation вызвалась без ошибок:
SYSTEM_PROCESS_INFORMATION *pSysInfo;
...
NtQuerySystemInformation(…);
while(1)
{
// Здесь уже можно использовать структуру
...
// Проверям – последний это процесс в списке? Если да, то
// выходим из цикла
if(!pSysInfo->NextEntryOffset)
break;
// Переходим к следующему процессу
pSysInfo = (SYSTEM_PROCESS_INFORMATION)((PVOID)pSysInfo + pSysInfo->NextEntryOffset);
}
Внимание: важный момент, перед вызовом NtQuerySystemInformation рекомедуется установить своему приложению привилегию отладки программ. Это нужно для получения списка системных процессов (System Idle process). Но это возможно только под правами Администратора. Всё это проделывает функция EnableDebugPrivilege. Чтобы установить привилегию, первый и единственный параметр должен быть равен TRUE, чтобы убрать FALSE.
BOOL EnableDebugPrivilege(BOOL bEnable)
{
HANDLE hToken;
BOOL bOk = FALSE;
if(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
{
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
tp.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : 0;
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
bOk = (GetLastError() == ERROR_SUCCESS);
CloseHandle(hToken);
}
return bOk;
}
Получение списка загруженных процессом модулей
Для получения списка модулей служит функция RtlQueryProcessDebugInformation.
RtlQueryProcessDebugInformation(HANDLE hPID, DWORD dwInfo, PVOID pRtlBuff);
где,
- hPID – идентификатор процесса (PID),
- dwInfo – код запроса требуемой информации,
- pRtlBuff – буфер куда будет передана информация о модулях процесса. Ниже приведена структура этого буфера:
typedef struct _DEBUGMODULEINFO
{
DWORD ImageBase;
DWORD ImageSize;
DWORD unknown1;
USHORT DllSequenceNum
USHORT NumDlls;
DWORD GrantedAccess;
CHAR Name[MAX_PATH];
DWORD unknown;
} DEBUGMODULEINFO,PDEBUGMODULEINFO;
Для работы с функцией RtlQueryProcessDebugInformation необходимо создать (RtlCreateQueryDebugBuffer) рабочий буфер для хранения информации о модулях. И после окончания работы, вызвать функцию RtlDestroyQueryDebugBuffer, чтобы освободить этот самый буфер.
PDWORD RtlCreateQueryDebugBuffer(DWORD, DWORD);
DWORD RtlDestroyQueryDebugBuffer(PDWORD);
Пример:
QUERYDEBUGBUFFER *pModuleInfo; // Информация о модулях
DWORD dwNtStatus; // Код возврата функции
// RtlQueryProcessDebugInformation
DWORD dwPID; // PID процесса
// Выделяем буфер
DWORD *pRtlBuffer = RtlCreateQueryDebugBuffer(NULL, NULL);
if(!pRtlBuffer)
{
// Error!
}
// Запрашиваем информацию о модулях
dwNtStatus = RtlQueryProcessDebugInformation((HANDLE *)dwPID, 0x01, pRtlBuffer);
if(!dwNtStatus)
{
pModuleInfo = (QUERYDEBUGBUFFER*)pRtlBuffer;
// Производим перечисление модулей процесса
for(int i = 0; i < pModuleInfo->dwNumNames; i++)
{
printf(“ImageBase: 0x%0.8Xl”, pModuleInfo[i]->ImageBase);
printf(“ImageSize: 0x%0.8Xl”, pModuleInfo[i]->ImageSize);
...
}
}
else if(dwNtStatus == DEBUG_ACCESS_DENIED)
{
// Error
}
// Освобождаем рабочий буфер
RtlDestroyQueryDebugBuffer(pModuleInfo);
Определение ImageBase и ImageSize
ImageBase и ImageSize определяются достаточно легко. Нужно сделать перечисление модулей того процесса, для которого определяются эти значения. И в СПИСКЕ МОДУЛЕЙ найти имя нужного процесса, как правило оно идёт первым. Далее взять значения из структуры DEBUGMODULEINFO:
QUERYDEBUGBUFFER *pModuleInfo;
...
pModuleInfo->ImageBase;
pModuleInfo->ImageSize;
НО! Если процесс использует антидамповые приёмы (один из которых можно посмотреть во введении – нечего было его проскакивать!), то, как правило, ImageSize имеет неправильное значение. Поэтому рекомендуется его считать из PE заголовка процесса, но можно и из файла на диске (последнее даже надежнее). Ниже приведён пример функции считывающей ImageSize из заголовка:
//Первый параметр - это PID процесса, второй - ImageBase.
DWORD GetRealSizeOfImage(DWORD dwPID, PVOID pModBase)
{
IMAGE_DOS_HEADER pDosh = {0};
IMAGE_NT_HEADERS pNT = {0};
HANDLE hProcess = OpenProcess(PROCESS_VM_READ, FALSE, dwPID);
if(hProcess)
{
ReadProcessMemory(hProcess,
pModBase,
&pDosh,
sizeof(IMAGE_DOS_HEADER),
NULL);
if(IMAGE_DOS_SIGNATURE == pDosh.e_magic)
ReadProcessMemory(hProcess,
(PBYTE)pModBase + pDosh.e_lfanew,
&pNT,
sizeof(IMAGE_NT_HEADERS), NULL);
CloseHandle(hProcess);
if(pNT.Signature == IMAGE_NT_SIGNATURE)
return pNT.OptionalHeader.SizeOfImage;
}
return NULL;
}
Однако даже наличие таких методик в PE Tools все равно не спасет от защиты-драйвера (например, Extreme-Protector). Поэтому, видно, пришло время и PE Tools переходить на драйверный движок, который мало в чем будет опираться на структуры кольца-3. Так правильнее.