
Hint/Name Table에 shellcode를 임베딩하기 위한 PoC 프로젝트
HintInject은 제가 PE 파일과 Import Directory Table 구조를 가지고 실험하면서 개발한 셸코드 임베더 및 로더입니다. 로더 실행 파일에 가짜로 추가된 DLL 엔트리의 Import Lookup Table을 통해 접근 가능한 Hint/Name Table 항목에 셸코드를 덩어리로 넣습니다. 로더는 이 덩어리들을 병합하여 셸코드를 실행합니다. 이 기술이 새로운 것인지는 모르지만 재미로 공유하고 싶었습니다.
HintInject가 어떻게 작동하는지 설명하기 전에, Stack Overflow의 게시물을 참고하여 DLL 로딩 프로세스에 대해 간략히 언급하고자 합니다.
필요한 DLL 및 함수/임포트에 대한 정보를 얻으려면 먼저 Import Directory Table을 살펴봐야 합니다. Import Directory Table은 항목들의 테이블로, 임포트된 각 DLL마다 하나의 항목이 있습니다. 이러한 항목들은 임포트된 DLL 이름에 대한 포인터, Import Lookup Table에 대한 포인터, Import Address Table에 대한 포인터 및 기타 다양한 정보 필드를 포함합니다.
간단히 말해, Import Lookup Table은 임포트에 대한 정보를 가리키고, Import Address Table은 임포트의 주소를 가리킵니다. 그러나 실행 파일이 디스크에 있을 때, 즉 DLL 로딩 프로세스 직전에는 Import Address Table이 Import Lookup Table과 동일합니다. Import Address Table의 내용은 DLL 로딩 프로세스 중에 임포트의 주소로 덮어씌워집니다.
자세한 내용은 생략하고, 아래 다이어그램을 통해 세 테이블 간의 관계를 살펴볼 수 있습니다.
조금 더 자세히 들어가면, Import Lookup Table은 임포트 이름을 직접 보유하지 않습니다. 이름으로 임포트되는 함수의 경우, Hint/Name Table 항목의 RVA를 보유합니다. 이러한 항목은 함수 이름을 Null 종료 ASCII 문자열로 저장합니다. 따라서 Hint/Name Table 항목의 구조체 정의는 다음과 같습니다:
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
CHAR Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
여기서 Hint 필드는 실제로 DLL의 내보내기 이름 포인터 테이블에 대한 인덱스입니다. 해당 임포트의 위치를 찾는 속도를 높이는 데 사용됩니다. 따라서 아래 다이어그램으로 Import Lookup Table과 Hint/Name Table 간의 관계를 요약할 수 있습니다.
결론적으로, DLL과 그 임포트를 찾으려면 다음 단계를 따라야 합니다:
최근에 x86matthew가 공유한 ImportDLLInjection 기술을 조사하면서, 메모리에 로드된 DLL의 Import Directory에 가짜 항목을 추가하는 방법을 보게 되었습니다. 그 후, 디스크에 있는 바이너리의 임포트 테이블에 직접 가짜 항목을 추가하는 작은 프로젝트를 재미와 지식 복습을 겸해 개발하고 싶었습니다. 이 프로젝트를 개발하기 위해 DLL 로딩 프로세스를 검토하는 과정에서, 이 프로세스에 사용되는 Hint/Name Table 항목의 Hint 필드가 눈에 띄었습니다.
MSDN 문서에 따르면, 이 필드는 Windows Loader가 이름으로 임포트된 해당 임포트의 주소를 DLL의 내보내기 이름 테이블에서 직접 찾는 데 사용된다고 배웠습니다. 그러나 같은 문서에서 이 필드를 사용하여 함수를 찾을 수 없는 경우, 해당 함수는 DLL의 내보내기 이름 테이블에서 이진 검색을 통해 검색된다고 명시되어 있었습니다. 이 문장을 바탕으로, 이 필드에 잘못된 값을 넣어도 DLL 로딩 프로세스에 문제가 생기지 않을 것이라고 생각했습니다.
위에서 언급했듯이, 임포트할 각 함수에 해당하는 Hint/Name 테이블 항목이 있습니다. 즉, 각 임포트마다 사용 가능한 2바이트가 있습니다. 여러 임포트를 결합하면 악성 셸코드를 저장할 수 있을 만큼 충분한 Hint 필드를 얻을 수 있습니다. 결과적으로, 로더 바이너리를 사용하여 가짜 임포트 DLL 항목의 Hint/Name 항목에 셸코드를 임베드할 수 있습니다. 해당 로더 바이너리는 런타임에 실행될 셸코드를 병합하기 위해 이러한 항목에 접근할 수 있습니다.
HintInject는 Hint/Name 테이블에 셸코드를 보유하는 이러한 로더를 생성하는 데 사용할 수 있습니다. 먼저 .rrdata라는 새 섹션을 만들고 현재 임포트 디렉터리를 이 섹션에 복사합니다. 그런 다음, 해당 임포트가 입력 셸코드를 보유하는 데 사용될 새로운 가짜 항목을 추가합니다. 섹션의 나머지 바이트는 새 가짜 항목의 Import Lookup Table, Import Address Table, DLL 이름 및 Hint/Name 테이블을 저장하는 데 사용됩니다. 마지막 단계로, HintInject는 임포트의 Hint 필드를 사용하여 입력 셸코드의 덩어리를 배치합니다.
로더 바이너리의 임포트 예시:
HintInject.exe를 실행하여 셸코드 로더를 만듭니다: HintInject.exe <Shellcode File> <Output Name>Loader.exe <PID>HintInjectLoader/Main.cpp 파일의 InjectShellcode 함수를 수정하여 셸코드 인젝션 방법을 변경할 수 있습니다.Windows 10 19044 및 Visual Studio 2019에서 테스트함:
DllNamesForFakeImports.h 파일의 dllNames 배열에 다른 DLL을 추가할 수 있습니다.DllNamesForFakeImports.h 파일의 dllNames 배열도 편집해야 합니다. 기본적으로 HintInject는 배열 순서에 따라 DLL에서 함수를 선택합니다. 즉, 도구는 먼저 user32.dll에서 내보낸 함수를 선택하고, 셸코드 크기가 2 * user32.dll 내보내기 수보다 크면 advapi32.dll 및 다른 DLL의 내보내기도 사용하기 시작합니다.static LPCSTR dllNames[] = {"user32.dll","advapi32.dll","gdi32.dll","wininet.dll","comctl32.dll","shell32.dll","wsock32.dll","oleaut32.dll","ws2_32.dll","urlmon.dll"};
승인된 보안 테스트 용도로만 사용하십시오. 명시적 허가 없이 시스템에 이 도구를 오용하는 것은 불법입니다.