오래전에 작업하고 더 이상 쓰지 않고 놔뒹굴고 있어서 이곳에 정리합니다.
32Bit, 64Bit 둘 다 정상적으로 동작합니다.
물론 64Bit에서는 패치가드가 보호하는 모듈은 후킹을 시도하지 말아야겠죠.
후킹하려는 대상 모듈이 scsiport.sys이고 이 모듈이 ntoskrnl!IoCreateSymbolicLink 함수를 호출하는 것을
모두 후킹하고 싶다면 다음과 같이 코드를 작성합니다.
#define SCSIPORT_SYS_FILEPATH L"\\SystemRoot\\System32\\drivers\\scsiport.sys"
#define SCSIPORT_SYS_FILENAME "ScsiPort.sys"
PVOID pScsiPortImageAddress;
ULONG uIoCreateSymbolicLinkRVA;
ULONG uImageSize;
// 전체 파일경로로부터 특정 모듈에 대한 IAT 함수(IoCreateSymbolicLink) RVA를 가져옵니다.
uIoCreateSymbolicLinkRVA = IATFunctionRVAFromFile(SCSIPORT_SYS_FILEPATH, "ntoskrnl", "IoCreateSymbolicLink");
if (uIoCreateSymbolicLinkRVA == 0)
{
KdPrint(("HookIoCreateSymbolicLinkHookForScsiPort : IATFunctionRVAFromFile fail.\n"));
return ;
}
// 후킹하는 모듈에 메모리 주소를 가져옵니다.
pScsiPortImageAddress = GetImageAddress(SCSIPORT_SYS_FILENAME, &uImageSize);
if (pScsiPortImageAddress == NULL)
{
KdPrint(("HookIoCreateSymbolicLinkHookForScsiPort : GetImageAddress fail.\n"));
return ;
}
// RVA가 이미지 크기보다 크다면 잘 못 된 값을 가져온 것입니다.
if (uIoCreateSymbolicLinkRVA >= uImageSize)
{
KdPrint(("HookIoCreateSymbolicLinkHookForScsiPort : uIoCreateSymbolicLinkRVA >= uImageSize\n"));
return ;
}
// IAT 후킹을 하며, Scsiport.sys에서 IoCreateSymbolicLink 함수를 호출 시 DummyIoCreateSymbolicLink함수가 호출되게 됩니다.
ExchangeProtectedIATPointer((PVOID)((ULONG_PTR)pScsiPortImageAddress + (ULONG_PTR)uIoCreateSymbolicLinkRVA), DummyIoCreateSymbolicLink);
NTSTATUS
DummyIoCreateSymbolicLink(
IN PUNICODE_STRING SymbolicLinkName,
IN PUNICODE_STRING DeviceName
)
{
NTSTATUS ntStatus;
KdPrint(("hi DummyIoCreateSymbolicLink :)\n"));
ntStatus = MyIoCreateUnprotectedSymbolicLink(SymbolicLinkName, DeviceName);
if (NT_SUCCESS(ntStatus))
{
KdPrint(("Successed.\n"));
}
return ntStatus;
}
/////////////////////////////////////////////////////////////////////////////////////////
// 여기서부터 불러다 쓰면 되는 함수가 정의되어 있습니다.
/////////////////////////////////////////////////////////////////////////////////////////
ULONG IATFunctionRVAFromFile(WCHAR *FileName, UCHAR *ModuleName, UCHAR *FunctionName)
{
NTSTATUS ntStatus;
HANDLE FileHandle;
IMAGE_DOS_HEADER DosHeader;
//
// 참고 !! : NtImage.h 헤더를 Include하였고 내부적으로 64비트인지 32비트인지에 따라서
// IMAGE_NT_HEADERS는 IMAGE_NT_HEADERS32, IMAGE_NT_HEADERS64로 나누어집니다.
//
IMAGE_NT_HEADERS NTHeader;
IMAGE_SECTION_HEADER ImageSectionHeader;
IMAGE_IMPORT_DESCRIPTOR ImageImportDescriptor;
PUCHAR pImageImportByNameBuffer;
ULONG ImageImportByNameBufferLength;
ULONG FunctionNameLength;
PUCHAR pModuleNameBuffer;
ULONG ModuleNameLength;
ULONG ImportTableRVA;
ULONG ImportNameTableRVA;
// ImportNameTable 각 배열의 크기는 32비트 PE Format에 경우 32비트이며 64비트에 경우 64비트입니다.
ULONG_PTR ImportNameTable;
LARGE_INTEGER Offset;
LARGE_INTEGER SectionHeaderOffset;
LARGE_INTEGER ImportDescriptorOffset;
LARGE_INTEGER ImportNameTableOffset;
int i;
int j;
int iCount;
if (FileName == NULL)
{
return 0;
}
if (ModuleName == NULL)
{
return 0;
}
if (FunctionName == NULL)
{
return 0;
}
FunctionNameLength = strlen(FunctionName);
if (FunctionNameLength == 0)
{
return 0;
}
ModuleNameLength = strlen(ModuleName);
if (ModuleNameLength == 0)
{
return 0;
}
ntStatus = IoOpenNormalFile(FileName, &FileHandle);
if (NT_SUCCESS(ntStatus) == FALSE)
{
return 0;
}
if (FileHandle == NULL)
{
return 0;
}
ImageImportByNameBufferLength = FunctionNameLength + sizeof(USHORT);
pImageImportByNameBuffer = ExAllocatePoolWithTag(PagedPool, ImageImportByNameBufferLength, POOL_TAG);
if (pImageImportByNameBuffer == NULL)
{
ZwClose(FileHandle);
return 0;
}
pModuleNameBuffer = ExAllocatePoolWithTag(PagedPool, ModuleNameLength, POOL_TAG);
if (pModuleNameBuffer == NULL)
{
ExFreePoolWithTag(pImageImportByNameBuffer, 0);
ZwClose(FileHandle);
return 0;
}
__try
{
Offset.QuadPart = 0x00;
if (IoReadNormalFile(FileHandle, &DosHeader, &Offset, sizeof(DosHeader)) == FALSE)
{
return 0;
}
// 올바른 시그내쳐를 갖고 있는지 확인합니다.
if (DosHeader.e_magic != 0x5A4D)
{
KdPrint(("Invalid dos_header.\n"));
return 0;
}
// NTHeader 파일 오프셋을 구합니다.
Offset.QuadPart = DosHeader.e_lfanew;
if (IoReadNormalFile(FileHandle, &NTHeader, &Offset, sizeof(NTHeader)) == FALSE)
{
return 0;
}
// 올바른 시그내쳐를 갖고 있는지 확인합니다.
if (NTHeader.Signature != 0x00004550)
{
KdPrint(("Invalid nt_header\n"));
return 0;
}
if (NTHeader.FileHeader.NumberOfSections == 0)
{
// 섹션의 개수가 0입니다.
KdPrint(("Invalid NumberOfSections\n"));
return 0;
}
// Import Table에 RVA 주소를 저장합니다.
ImportTableRVA = NTHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
SectionHeaderOffset.QuadPart = Offset.QuadPart + sizeof(NTHeader);
for (i = 0; i < (int)NTHeader.FileHeader.NumberOfSections; i++)
{
if (IoReadNormalFile(FileHandle, &ImageSectionHeader,
&SectionHeaderOffset, sizeof(ImageSectionHeader)) == FALSE)
{
return 0;
}
if
((NTHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress
>= ImageSectionHeader.VirtualAddress)
&&
(NTHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress
< (ImageSectionHeader.VirtualAddress +
ImageSectionHeader.Misc.VirtualSize)))
{
// 임포트 테이블에 해당하는 섹션을 찾았습니다.
// IMAGE_IMPORT_DESCRIPTOR 구조체가 있는 파일 오프셋을 구합니다.
ImportDescriptorOffset.QuadPart =
NTHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress
- ImageSectionHeader.VirtualAddress +
ImageSectionHeader.PointerToRawData;
for (j = 0; j < 10; j++)
{
if (IoReadNormalFile(FileHandle,
&ImageImportDescriptor, &ImportDescriptorOffset,
sizeof(ImageImportDescriptor)) == FALSE)
{
return 0;
}
if (ImageImportDescriptor.OriginalFirstThunk == 0x00000000)
{
// NULL 임포트 디스크립터라면 임포트 테이블을 모두 순회한 것입니다.
return 0;
}
// 라이브러리 이름 RVA 주소에 대한 파일 오프셋을 구합니다.
Offset.QuadPart = ImageImportDescriptor.Name -
ImageSectionHeader.VirtualAddress + ImageSectionHeader.PointerToRawData;
if (IoReadNormalFile(FileHandle, pModuleNameBuffer, &Offset, ModuleNameLength) == FALSE)
{
return 0;
}
if (memcmp(pModuleNameBuffer, ModuleName, ModuleNameLength) == 0)
{
// ntoskrnl 이 맞다면 원하는 임포트 디스크립터 테이블을 찾은 것입니다. ( RVA 주소에 대한 파일 오프셋을 구합니다. )
ImportNameTableOffset.QuadPart =
ImageImportDescriptor.OriginalFirstThunk -
ImageSectionHeader.VirtualAddress + ImageSectionHeader.PointerToRawData;
iCount = 0;
while (TRUE)
{
if (IoReadNormalFile(FileHandle,
&ImportNameTable, &ImportNameTableOffset,
sizeof(ImportNameTable)) == FALSE)
{
break;
}
if (ImportNameTable == 0)
{
// 값이 0 이라면 모두 순회한 것입니다.
break;
}
// IMAGE_IMPORT_BY_NAME 오프셋을 구합니다. ( RVA 주소에 대한 파일 오프셋을 구합니다.
Offset.QuadPart = ImportNameTable -
ImageSectionHeader.VirtualAddress + ImageSectionHeader.PointerToRawData;
if (IoReadNormalFile(FileHandle,
pImageImportByNameBuffer, &Offset, ImageImportByNameBufferLength) ==
FALSE)
{
break;
}
if (memcmp(pImageImportByNameBuffer + sizeof(USHORT), FunctionName, FunctionNameLength) == 0)
{
// 찾았습니다.
return (ImageImportDescriptor.FirstThunk
+ (iCount * sizeof(PVOID))/* + NTHeader.OptionalHeader.ImageBase*/);
}
ImportNameTableOffset.QuadPart += sizeof(ImportNameTable);
iCount++;
}
// 위에 while 문으로터 검색에 실패하였습니다.
return 0;
}
// 다음 임포트 디스크립터를 검색합니다.
ImportDescriptorOffset.QuadPart += sizeof(ImageImportDescriptor);
}
// 위에서 루프를 돌았지만 검색에 실패하였습니다.
return 0;
}
// 다음 섹션을 검사합니다.
SectionHeaderOffset.QuadPart += sizeof(ImageSectionHeader);
}
}
__finally
{
if (pModuleNameBuffer != NULL)
{
ExFreePoolWithTag(pModuleNameBuffer, POOL_TAG);
}
if (pImageImportByNameBuffer != NULL)
{
ExFreePoolWithTag(pImageImportByNameBuffer, POOL_TAG);
}
ZwClose(FileHandle);
}
return 0;
}
PVOID GetImageAddress(CHAR *ImageFileName, ULONG *pImageSize)
{
NTSTATUS Status;
PRTL_PROCESS_MODULES ModuleInfo;
PRTL_PROCESS_MODULE_INFORMATION ModuleEntry;
ULONG ReturnedLength;
ULONG i;
PVOID pImageAddress;
//
// Figure out how much size we need
//
Status = ZwQuerySystemInformation(SystemModuleInformation,
NULL,
0,
&ReturnedLength);
if (Status != STATUS_INFO_LENGTH_MISMATCH) return NULL;
//
// Allocate a buffer large enough
//
//ModuleInfo = RtlAllocateHeap(RtlGetProcessHeap(), 0, ReturnedLength);
ModuleInfo = (PRTL_PROCESS_MODULES)ExAllocatePoolWithTag(NonPagedPool, ReturnedLength, POOL_TAG);
if (!ModuleInfo) return NULL;
//
// Now query the data again
//
Status = ZwQuerySystemInformation(SystemModuleInformation,
ModuleInfo,
ReturnedLength,
&ReturnedLength);
if (!NT_SUCCESS(Status))
{
ExFreePoolWithTag(ModuleInfo, POOL_TAG);
return NULL;
}
//
// Loop all the drivers
//
for (i = 0; i < ModuleInfo->NumberOfModules; i++)
{
//
// Get the current entry and check if the pointer is within it
//
ModuleEntry = &ModuleInfo->Modules[i];
if (_stricmp(ModuleEntry->FullPathName + ModuleEntry->OffsetToFileName, ImageFileName) == 0)
{
// 주소를 미리 얻은 후에 ExFreePoolWithTag 함수를 호출해야 합니다.
// ( 그렇지 않으면 블루 스크린이 발생할 수 있습니다. )
pImageAddress = ModuleEntry->ImageBase;
if (pImageSize != NULL)
{
*pImageSize = ModuleEntry->ImageSize;
}
ExFreePoolWithTag(ModuleInfo, POOL_TAG);
return (pImageAddress);
}
}
ExFreePoolWithTag(ModuleInfo, POOL_TAG);
return NULL;
}
void ExchangeProtectedIATPointer(PVOID IATPointerAddress, PVOID NewAddress)
{
PMDL pMdl;
PUCHAR pBuffer;
pMdl = IoAllocateMdl(IATPointerAddress, sizeof(PVOID), FALSE, FALSE, NULL);
if (pMdl == NULL)
{
return ;
}
MmBuildMdlForNonPagedPool(pMdl);
pBuffer = MmMapLockedPagesSpecifyCache(pMdl, KernelMode, MmNonCached, NULL, FALSE, NormalPagePriority);
if (pBuffer == NULL)
{
IoFreeMdl(pMdl);
return ;
}
//
// InterlockedExchangePointer(&pBuffer, NewAddress); <= 이렇게 하면 안되고
// InterlockedExchangePointer(pBuffer, NewAddress); <= 이렇게 해야 합니다.
// 포인터의 값을 바꾸는것이 아니라 포인터가 가르키는 실제 값을 바꾸는 것이기 때문입니다.
// 꼭 Interlocked 함수를 사용하지 않고 바로 대입해도 상관이 없습니다.
// 그러나 한 번에 대입되는 방식이 아닌 Byte단위나 Word 단위 등에 방식은 동기화 문제를 발생시킬 수 있습니다.
//
InterlockedExchangePointer((PVOID *)pBuffer, NewAddress);
MmUnmapLockedPages(pBuffer, pMdl);
IoFreeMdl(pMdl);
}