어떤 보안 프로그램( nProtect, 키보드보안 ) 이나 SPTD를 사용하는 가상 시디 소프트웨어 ( Daemon, Alchol
120% ) 에 경우에는 드라이버가 설치되어 있지만 윈도우즈 탐색기에 \\system32\\drivers\\에 가보면 해당
드라이버 파일들이 존재하지 않는다는 것을 알 수 있습니다.
데몬을 예로 들자면 다음과 같이 설치가 완료되면 장치 관리자에 저장소 컨트롤러가 추가되게 됩니다.
그러나 그림에서 보는 경로에 파일은 존재하지 않으며 gmer과 같은 루트킷 체크프로그램으로 파일 시스템을 직접적으로 읽어도 파일이 존재하지 않습니다.
이러한 방식은 드라이버 파일을 숨기는 방식이 아니며 실제로 하드 디스크에 존재하지 않는 경우입니다.
드라이버를 로드하면 로드 후 바로 파일을 하드 디스크상에 삭제함으로 메모리상에만 존재하게 되는 것입니다.
이렇게 드라이버 파일을 바로 삭제하는 이유는 리버스 엔지니어링을 방지하기 위함인데
이러한 경우 어떻게 해당 드라이버 파일을 가져올 수 있는지에 대한 방법에 대해 소개할까합니다.
사실 방법은 매우 간단하며 다음과 같은 시스템에서 제공해주는 함수를 이용하면 쉽게 드라이버 파일인 .sys를 흭득 할 수 있습니다.
PsSetLoadImageNotifyRoutine
NTSTATUS
PsSetLoadImageNotifyRoutine(
IN PLOAD_IMAGE_NOTIFY_ROUTINE
NotifyRoutine
);
훅킹쪽에 관심이 있으신 분이라면 거의 알만한 함수죠..
이 함수는 하나의 콜백함수를 인자로 받으며 이 콜백함수는
"시스템에서 어떠한 모듈이 로드 될 때 ( Dll, Sys )" 호출되게 됩니다.
이쯤이면 거의 답이 나왔다시피 합니다.
한 번 실제 구현코드를 보겠습니다.
void CallBackLoadImageNotifyRoutine(__in PUNICODE_STRING FullImageName, __in HANDLE ProcessId, __in PIMAGE_INFO ImageInfo)
{
HANDLE hReadHandle;
HANDLE hWriteHandle;
CHAR pFileBuffer[1024];
NTSTATUS ntStatus;
IO_STATUS_BLOCK IoStatusBlock;
OBJECT_ATTRIBUTES ObjectAttributes;
LARGE_INTEGER Offset;
UNICODE_STRING uniWritFileName;
KdPrint(("모듈이 로드되었습니다. : %ws ( Irql : %#x )\n", FullImageName->Buffer, KeGetCurrentIrql()));
// 드라이버의 파일은 확장자가 .sys 이므로 확장자가 s로 끝나는 이름이 있는지 확인합니다.
if ((FullImageName->Buffer[wcslen(FullImageName->Buffer) - 1] == 's')
|| (FullImageName->Buffer[wcslen(FullImageName->Buffer) - 1] == 'S'))
{
// 드라이버 파일이 로드되었습니다.
InitializeObjectAttributes(&ObjectAttributes, FullImageName, OBJ_CASE_INSENSITIVE, NULL, NULL);
ntStatus = ZwCreateFile(&hReadHandle,
GENERIC_READ | SYNCHRONIZE,
&ObjectAttributes,
&IoStatusBlock, NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_OPEN,
FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0);
if (NT_SUCCESS(ntStatus) == FALSE)
{
KdPrint(("파일을 생성하지 못 하였습니다.\n"));
return ;
}
else
{
Offset.LowPart = 0;
Offset.HighPart = 0;
RtlInitUnicodeString(&uniWritFileName, L"\\??\\C:\\Output.Sys");
InitializeObjectAttributes(&ObjectAttributes, &uniWritFileName, OBJ_CASE_INSENSITIVE, NULL, NULL);
ntStatus = ZwCreateFile(&hWriteHandle,
GENERIC_WRITE | SYNCHRONIZE | FILE_APPEND_DATA,
&ObjectAttributes,
&IoStatusBlock,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_CREATE,
FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0);
if (NT_SUCCESS(ntStatus) == FALSE)
{
KdPrint(("쓰기 파일을 생성 할 수 없음 %#x\n", ntStatus));
ZwClose(hReadHandle);
return ;
}
while (TRUE)
{
ntStatus = ZwReadFile(hReadHandle, NULL, NULL, NULL, &IoStatusBlock, pFileBuffer, 1024, &Offset, NULL);
if((NT_SUCCESS(ntStatus) == FALSE) || (IoStatusBlock.Information != 1024))
{
ntStatus = ZwWriteFile(hWriteHandle, NULL, NULL, NULL,
&IoStatusBlock, pFileBuffer, IoStatusBlock.Information, NULL, NULL);
if (NT_SUCCESS(ntStatus) == FALSE)
{
ZwClose(hWriteHandle);
ZwClose(hReadHandle);
return ;
}
KdPrint(("루프를 빠져나갑니다.\n"));
// 파일을 모두 작성하였으니 루프를 빠져나갑니다.
ZwClose(hWriteHandle);
ZwClose(hReadHandle);
break;
}
else
{
ntStatus = ZwWriteFile(hWriteHandle, NULL, NULL, NULL, &IoStatusBlock, pFileBuffer, 1024, NULL, NULL);
if (NT_SUCCESS(ntStatus) == FALSE)
{
KdPrint(("쓰기 실패...\n"));
ZwClose(hWriteHandle);
ZwClose(hReadHandle);
return ;
}
}
Offset.LowPart += 1024;
}
KdPrint(("완료...\n"));
}
}
}
void Hook()
{
if (NT_SUCCESS(PsSetLoadImageNotifyRoutine(CallBackLoadImageNotifyRoutine)) == TRUE)
{
KdPrint(("성공...\n"));
}
else
{
KdPrint(("실패...\n"));
}
}
Hook() 이라는 함수는 DriverEntry와 같은 함수에서 호출하거나 또는 다른 PASSIVE_LEVEL에서 호출하면 됩니다.
콜백함수에서는 파일의 이름에서 끝에 이름이 'S' 또는 's'일 경우라면 "Output.sys"라는 파일로 "C:\" 루트에다가 드라이버 파일을 생성하게 됩니다.
실제로 이 드라이버 파일을 컴파일하고 로드를 미리 시켜놓은 다음에 데몬을 설치하고 설치가 완료되면 가상 장치 업데이트
창에서 드라이버를 로드하게 되는데 다음과 같이 DbgView에서 드라이버 파일이 로드됨을 볼 수 있습니다.
완료라는 메세지가 출력되고 C드라이브에 가보면 다음과 같이 Outpup.sys가 생성되게 됩니다.
국내에 보안 프로그램들도 위에서처럼 드라이버를 숨기게 됩니다.
밑에 그림은 특정 은행에 들어갔을 때 키보드 보안 프로그램을 설치하게 되는데 이 때 로드 된 드라이버입니다.
결론은 드라이버가 로드되기전에
PsSetLoadImageNotifyRoutine를 이용하면 로드 후 바로 삭제되는 로딩방식의 드라이버 파일을
가져올 수 있다는 것입니다.