드라이버에서 SSDT를 후킹하기 전에 후킹하고자 하는 함수의 인덱스를 알아야합니다
windows api는 호출이되면 함수 내부에서 일부 검증과 변환 같은 처리를 한 뒤에 ntdll에 있는 Nt로 시작하는 함수들을 호출합니다
Nt함수들은 다음 사진과 같이 SSDT 인덱스를 eax레지스터에 넣고 syscall을 호출합니다
syscall을 하기전에 mov eax, 00000055 와 같이 SSDT 인덱스를 넣는 모습을 볼 수 있습니다
이번 글에서는 Nt함수의 시작부분에서 가까운 위치의 mov명령어를 찾고 그 뒤에 있는 SSDT 인덱스를 가져오는 방법에 대해서 알아봅니다
실습은 exe프로그램에서 진행하고 추후에 드라이버로 이식합니다
64비트에서 실습을 진행합니다(제가 64비트 쓸거라서ㅋㅋㅋ)
ntdll.dll은 프로젝트 폴더로 복사한 뒤 실습을 진행합니다
파일을 읽기만 하는거라 괜찮은데 혹시 모를 실수를 방지합니다(주로 파일 오픈 모드, 쓰기모드 실수)
ntdll.dll로드
디스크에 있는 ntdll.dll을 로드합니다
커널드라이버에서도 유사한 방법을 사용하기 위해서 메모리에 이미 로드된 ntdll.dll을 사용하지 않습니다
LPCWSTR file_name = L"\\ntdll.dll";
WCHAR file_path[MAX_PATH] = { 0, };
GetCurrentDirectoryW(MAX_PATH, file_path);
//GetSystemDirectoryW(szFilePath, MAX_PATH);
StringCchCatW(file_path, MAX_PATH, file_name);
DWORD file_size = 0;
HANDLE file_handle = CreateFile(file_path, GENERIC_READ, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
unique_ptr<BYTE[], std::function<void(BYTE[])>> file_buffer;
if (file_handle == INVALID_HANDLE_VALUE)
{
cout << "Open Fail..." << endl;
return 0;
}
file_size = GetFileSize(file_handle, NULL);
if (file_size == INVALID_FILE_SIZE)
{
cout << "Get File Size Fail.." << endl;
CloseHandle(file_handle);
return 0;
}
file_buffer = unique_ptr<BYTE[], std::function<void(BYTE[])>>(new BYTE[file_size], [](BYTE* p) {delete[] p; });
if (!ReadFile(file_handle, file_buffer.get(), file_size, NULL, NULL))
{
cout << "Read Fail..." << endl;
CloseHandle(file_handle);
return 0;
}
ntdll.dll을 읽고 버퍼를 할당한 뒤 모두 읽어들입니다
함수의 오프셋 구하기
pe헤더를 분석하면서 찾으려고 하는 함수가 위치한 오프셋을 구합니다
헤더에 있는 주소들은 파일 오프셋 말고도 RVA를 사용하기 때문에 RVA를 파일 오프셋으로 변환하는 유틸함수를 작성합니다
다음은 RVA를 파일 오프셋으로 변환하는 함수입니다
#define PE_ERROR ((UINT_PTR)-1)
using namespace std;
UINT_PTR RVAToOffset(PIMAGE_NT_HEADERS64 pinh, UINT_PTR RVA, DWORD file_size)
{
PIMAGE_SECTION_HEADER ish = IMAGE_FIRST_SECTION(pinh);
INT number_of_section = pinh->FileHeader.NumberOfSections;
for (INT idx = 0; idx < number_of_section; idx++)
{
if (ish->VirtualAddress <= RVA && RVA < ish->VirtualAddress + ish->Misc.VirtualSize)
{
RVA -= ish->VirtualAddress;
RVA += ish->PointerToRawData;
return RVA < file_size ? RVA : PE_ERROR;
}
ish++;
}
return PE_ERROR;
}
모든 섹션을 반복하면서 해당 RVA가 어떤 섹션에 있는지 체크한 뒤 계산합니다
파일 오프셋은 다음과 같은 방법으로 구할 수 있습니다
파일 오프셋 = RVA - VirtualAddress + PointerToRawData
이제 pe를 분석하면서 특정 함수의 주소를 구합니다
UINT_PTR GetExportOffset(UINT_PTR file_data, DWORD file_size, LPCSTR func_name)
{
PIMAGE_DOS_HEADER idh = (PIMAGE_DOS_HEADER)file_data;
if (idh->e_magic != IMAGE_DOS_SIGNATURE)
{
cout << "IMAGE_DOS_SIGNATURE error" << endl;
return PE_ERROR;
}
PIMAGE_NT_HEADERS64 inh = (PIMAGE_NT_HEADERS64)(file_data + idh->e_lfanew);
if (inh->Signature != IMAGE_NT_SIGNATURE)
{
cout << "IMAGE_NT_SIGNATURE error" << endl;
return PE_ERROR;
}
PIMAGE_FILE_HEADER ifh = (PIMAGE_FILE_HEADER)&inh->FileHeader;
PIMAGE_OPTIONAL_HEADER64 ioh = (PIMAGE_OPTIONAL_HEADER64)&inh->OptionalHeader;
PIMAGE_DATA_DIRECTORY idd = (PIMAGE_DATA_DIRECTORY)&ioh->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
UINT_PTR ied_offset = RVAToOffset(inh, idd->VirtualAddress, file_size);
PIMAGE_EXPORT_DIRECTORY ied = (PIMAGE_EXPORT_DIRECTORY)(file_data + ied_offset);
UINT_PTR export_name_table_offset = RVAToOffset(inh, ied->AddressOfNames, file_size);
UINT_PTR export_ordinal_table_offset = RVAToOffset(inh, ied->AddressOfNameOrdinals, file_size);
UINT_PTR export_function_table_offset = RVAToOffset(inh, ied->AddressOfFunctions, file_size);
if (export_name_table_offset == PE_ERROR
|| export_ordinal_table_offset == PE_ERROR
|| export_function_table_offset == PE_ERROR)
{
cout << "export director content error" << endl;
return PE_ERROR;
}
PDWORD export_name_table = (PDWORD)(file_data + export_name_table_offset);
PWORD export_ordinal_table = (PWORD)(file_data + export_ordinal_table_offset);
PDWORD export_function_table = (PDWORD)(file_data + export_function_table_offset);
UINT_PTR export_function_offset = PE_ERROR;
for (INT idx = 0; idx < ied->NumberOfNames; idx++)
{
UINT_PTR name_offset = RVAToOffset(inh, export_name_table[idx], file_size);
char* name = (char*)(file_data + name_offset);
if (lstrcmpA(func_name, name) == 0)
{
export_function_offset = RVAToOffset(inh, export_function_table[export_ordinal_table[idx]], file_size);
break;
}
}
return export_function_offset;
}
RVA로 된 값을 파일 오프셋으로 변환한 뒤 파일버퍼의 시작주소에 더해주는 방식으로 진행합니다
위와 같은 방법으로 해당 함수가 위치한 파일 오프셋을 구할 수 있습니다
SSDT Index구하기
앞에서 구한 함수의 시작 위치부터 mov를 검색합니다
mov바로 뒤에 있는 값은 SSDT 인덱스로 볼 수 있습니다
반복 도중에 RET명령어를 만나면 루프를 탈출합니다
INT GetExportSSDTIndex(UINT_PTR file_data, DWORD file_size, LPCSTR func_name)
{
UINT_PTR export_function_offset = GetExportOffset(file_data, file_size, func_name);
if (export_function_offset == PE_ERROR)
{
cout << "export function offset error" << endl;
return -1;
}
INT SSDT_index = -1;
PBYTE function_data = (PBYTE)(file_data + export_function_offset);
for (INT idx = 0; idx < 32 && export_function_offset + 1 < file_size; idx++)
{
if (function_data[idx] == 0xC2 || function_data[idx] == 0xC3) // ret
break;
if (function_data[idx] == 0xB8) //mov
{
SSDT_index = *(INT*)(function_data + idx + 1);
break;
}
}
if (SSDT_index == PE_ERROR)
cout << "SSDT index not found" << endl;
return SSDT_index;
}
이제 함수 이름으로 SSDT 인덱스를 구할 수 있습니다
테스트
실제로 올바른 SSDT 인덱스를 가져오는지 테스트를 합니다
vector<string> function_names = { "NtOpenProcess", "NtQuerySystemInformation", "NtCreateFile" };
for (string& name : function_names)
{
INT ssdt_index = GetExportSSDTIndex((UINT_PTR)file_buffer.get(), file_size, name.c_str());
cout << name << " SSDT Index : " << ssdt_index << endl;
}
출력은 다음과 같습니다
디버거 또는 메모리뷰어로 확인합니다
이번 글에서는 치트엔진을 사용해서 확인했습니다
올바르게 구해온 모습을 확인했습니다
사용된 코드
#include <iostream>
#include <memory>
#include <functional>
#include <Windows.h>
#include <strsafe.h>
#include <vector>
#define PE_ERROR ((UINT_PTR)-1)
using namespace std;
UINT_PTR RVAToOffset(PIMAGE_NT_HEADERS64 pinh, UINT_PTR RVA, DWORD file_size)
{
PIMAGE_SECTION_HEADER ish = IMAGE_FIRST_SECTION(pinh);
INT number_of_section = pinh->FileHeader.NumberOfSections;
for (INT idx = 0; idx < number_of_section; idx++)
{
if (ish->VirtualAddress <= RVA && RVA < ish->VirtualAddress + ish->Misc.VirtualSize)
{
RVA -= ish->VirtualAddress;
RVA += ish->PointerToRawData;
return RVA < file_size ? RVA : PE_ERROR;
}
ish++;
}
return PE_ERROR;
}
UINT_PTR GetExportOffset(UINT_PTR file_data, DWORD file_size, LPCSTR func_name)
{
PIMAGE_DOS_HEADER idh = (PIMAGE_DOS_HEADER)file_data;
if (idh->e_magic != IMAGE_DOS_SIGNATURE)
{
cout << "IMAGE_DOS_SIGNATURE error" << endl;
return 0;
}
PIMAGE_NT_HEADERS64 inh = (PIMAGE_NT_HEADERS64)(file_data + idh->e_lfanew);
if (inh->Signature != IMAGE_NT_SIGNATURE)
{
cout << "IMAGE_NT_SIGNATURE error" << endl;
return 0;
}
PIMAGE_FILE_HEADER ifh = (PIMAGE_FILE_HEADER)&inh->FileHeader;
PIMAGE_OPTIONAL_HEADER64 ioh = (PIMAGE_OPTIONAL_HEADER64)&inh->OptionalHeader;
PIMAGE_DATA_DIRECTORY idd = (PIMAGE_DATA_DIRECTORY)&ioh->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
UINT_PTR ied_offset = RVAToOffset(inh, idd->VirtualAddress, file_size);
PIMAGE_EXPORT_DIRECTORY ied = (PIMAGE_EXPORT_DIRECTORY)(file_data + ied_offset);
UINT_PTR export_name_table_offset = RVAToOffset(inh, ied->AddressOfNames, file_size);
UINT_PTR export_ordinal_table_offset = RVAToOffset(inh, ied->AddressOfNameOrdinals, file_size);
UINT_PTR export_function_table_offset = RVAToOffset(inh, ied->AddressOfFunctions, file_size);
if (export_name_table_offset == PE_ERROR
|| export_ordinal_table_offset == PE_ERROR
|| export_function_table_offset == PE_ERROR)
{
cout << "export director content error" << endl;
return 0;
}
PDWORD export_name_table = (PDWORD)(file_data + export_name_table_offset);
PWORD export_ordinal_table = (PWORD)(file_data + export_ordinal_table_offset);
PDWORD export_function_table = (PDWORD)(file_data + export_function_table_offset);
UINT_PTR export_function_offset = PE_ERROR;
for (INT idx = 0; idx < ied->NumberOfNames; idx++)
{
UINT_PTR name_offset = RVAToOffset(inh, export_name_table[idx], file_size);
char* name = (char*)(file_data + name_offset);
if (lstrcmpA(func_name, name) == 0)
{
export_function_offset = RVAToOffset(inh, export_function_table[export_ordinal_table[idx]], file_size);
break;
}
}
return export_function_offset;
}
INT GetExportSSDTIndex(UINT_PTR file_data, DWORD file_size, LPCSTR func_name)
{
UINT_PTR export_function_offset = GetExportOffset(file_data, file_size, func_name);
if (export_function_offset == PE_ERROR)
{
cout << "export function offset error" << endl;
return -1;
}
INT SSDT_index = -1;
PBYTE function_data = (PBYTE)(file_data + export_function_offset);
for (INT idx = 0; idx < 32 && export_function_offset + 1 < file_size; idx++)
{
if (function_data[idx] == 0xC2 || function_data[idx] == 0xC3) // RET
break;
if (function_data[idx] == 0xB8)
{
SSDT_index = *(INT*)(function_data + idx + 1);
break;
}
}
if (SSDT_index == PE_ERROR)
cout << "SSDT index not found" << endl;
return SSDT_index;
}
int main()
{
LPCWSTR file_name = L"\\ntdll.dll";
WCHAR file_path[MAX_PATH] = { 0, };
GetCurrentDirectoryW(MAX_PATH, file_path);
//GetSystemDirectoryW(szFilePath, MAX_PATH);
StringCchCatW(file_path, MAX_PATH, file_name);
DWORD file_size = 0;
HANDLE file_handle = CreateFile(file_path, GENERIC_READ, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
unique_ptr<BYTE[], std::function<void(BYTE[])>> file_buffer;
if (file_handle == INVALID_HANDLE_VALUE)
{
cout << "Open Fail..." << endl;
return 0;
}
file_size = GetFileSize(file_handle, NULL);
if (file_size == INVALID_FILE_SIZE)
{
cout << "Get File Size Fail.." << endl;
CloseHandle(file_handle);
return 0;
}
file_buffer = unique_ptr<BYTE[], std::function<void(BYTE[])>>(new BYTE[file_size], [](BYTE* p) {delete[] p; });
if (!ReadFile(file_handle, file_buffer.get(), file_size, NULL, NULL))
{
cout << "Read Fail..." << endl;
CloseHandle(file_handle);
return 0;
}
vector<string> function_names = { "NtOpenProcess", "NtQuerySystemInformation", "NtCreateFile" };
for (string& name : function_names)
{
INT ssdt_index = GetExportSSDTIndex((UINT_PTR)file_buffer.get(), file_size, name.c_str());
cout << name << " SSDT Index : " << ssdt_index << endl;
}
return 0;
}
끝
'날먹을 위한 몸부림 > 리버싱' 카테고리의 다른 글
win32u.dll는 뭐임? (1) | 2022.05.30 |
---|---|
ssdt index 뷰어 (0) | 2022.05.27 |
HamsterHide 사용법 - 상세 설정 (9) | 2022.04.20 |
HamsterHide 사용법 - 간편 사용 (0) | 2022.04.20 |
HamsterHide v0.2.2 (4) | 2022.04.19 |