어떤 보안 프로그램( 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_RANDOM_ACCESS | 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_RANDOM_ACCESS | 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가 생성되게 됩니다.



참고로 데몬과 같은 경우에는 드라이버 바이너리를 얻어왔다고 하여도 리버스엔지니어링을 막기 위해 코드 프로텍션이 걸려있습니다.

사용자 삽입 이미지


위에 그림에서 보시면 스택이 깨졌다고 IDA에서 보여주는데 안티 디스어셈블링은 아닌 것 같으며 확실하진 않지만
코드 가상화일 가능성이 높습니다. ( 코드 가상화 기법에 대해서는 추후에 설명하겠습니다. )

국내에 보안 프로그램들도 위에서처럼 드라이버를 숨기게 됩니다.
밑에 그림은 특정 은행에 들어갔을 때 키보드 보안 프로그램을 설치하게 되는데 이 때 로드 된 드라이버입니다.

사용자 삽입 이미지


이 드라이버에 경우에는 코드 프로텍션이 들어가 있지 않기 때문에 리버스 엔지니어링에 노출되게 됩니다.

결론은 드라이버가 로드되기전에 PsSetLoadImageNotifyRoutine를 이용하면 로드 후 바로 삭제되는 로딩방식의 드라이버 파일을
가져올 수 있다는 것입니다.

그리고 마지막으로 참고삼아서 말씀드리자면 PsSetLoadImageNotifyRoutine를 설명하면 항상 따라다니는 함수가 존재하는데
다음과 같은 함수들입니다.
이름만 봐도 무슨 함수인지 알기 때문에 설명은 생략하겠습니다.

PsSetCreateProcessNotifyRoutine


NTSTATUS
  PsSetCreateProcessNotifyRoutine(
    IN PCREATE_PROCESS_NOTIFY_ROUTINE  NotifyRoutine,
    IN BOOLEAN  Remove
    );


PsSetCreateThreadNotifyRoutine

NTSTATUS
  PsSetCreateThreadNotifyRoutine(
    IN PCREATE_THREAD_NOTIFY_ROUTINE  NotifyRoutine
    );


이올린에 북마크하기(0) 이올린에 추천하기(0)
2009/03/23 10:47 2009/03/23 10:47

trackback url :: http://blog.spaceinter.com/spaceinter/trackback/252

댓글을 달아 주세요

write a comment