Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
ExportHider — ExportHider: DLL 파일에서 내보낸 함수를 숨기기 위해 런타임 중 Export Table을 생성합니다. | Kitploit
도구/GitHubGitHub/frkngksl/exporthider
Dynamic Code Analysis (DAST)ExploitationReverse EngineeringMalware AnalysisBinary AnalysisRed TeamingPayload Development
GitHubfrkngksl/exporthider

ExportHider

ExportHider: DLL 파일에서 내보낸 함수를 숨기기 위해 런타임 중 Export Table을 생성합니다.

저장소 보기
3324개월 전Kitploit 검토 완료

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

ExportHider

ExportHider는 내보내기된 함수들을 파일 시스템의 DLL 내보내기 디렉터리(Export Directory)에서 숨길 수 있는 코드 스텁(code stub)이 포함된 C++ DLL 템플릿을 생성합니다. 함수 정의를 작성하고 파일을 컴파일한 후에는 CFF Explorer와 같은 PE 파일 뷰어를 통해 숨겨진 내보내기 함수가 보이지 않게 됩니다. 그러나 템플릿 내의 코드 스텁이 런타임 중에 내보내기 디렉터리를 재생성하므로 정당한 GetProcAddress 호출은 성공적으로 실행됩니다. 이 방법은 동적 DLL 로딩 또는 사용자 정의 DLL 로더 시나리오에서만 작동합니다.

작동 원리

일반적으로 C 또는 C++에서 DLL 파일에 내보내기 함수를 정의하려면 함수 이름 앞에 __declspec(dllexport) 키워드를 붙이거나 .def 파일을 생성합니다. 컴파일 후 컴파일러는 내보내기 함수와 관련된 정보를 저장하기 위해 내보내기 디렉터리라는 특수 테이블을 만듭니다. 내보내기 디렉터리의 구조는 아래와 같습니다:

프로세스가 DLL 파일의 함수를 사용하려고 할 때 Windows 로더(Loader)는 이 구조를 파싱(parsing)하여 AddressOfFunctions, AddressOfNames, AddressOfNameOrdinals 배열을 사용해 요청된 함수를 가져옵니다.

함수 가져오기(importing)에 대한 구체적인 루틴은 ferreirasc의 블로그 글에 자세히 설명되어 있지만, 간단히 말해 이름으로 함수를 가져오는 경우 로더는 AddressOfNames 배열(이 배열의 값은 RVA 값)을 반복하며 주어진 이름을 검색합니다. 로더가 "i" 위치에서 일치하는 항목을 찾으면 AddressOfNameOrdinals 배열의 i번째 인덱스를 참조하여 이 함수와 관련된 서수(ordinal)를 얻습니다. 서수를 얻은 후 로더는 AddressOfFunctions에서 해당 서수 값 위치를 참조하여 가져온 함수와 연결된 RVA를 최종적으로 얻습니다.

여기서 중요한 점은 LoadLibrary가 호출될 때 Windows 로더가 수행하는 모든 검색 및 접근 작업이 DLL이 프로세스 주소 공간에 매핑된 후에 이루어진다는 것입니다. DLL 매핑 중에는 PE 헤더를 포함한 전체 DLL 파일이 메모리에 기록되고, 로더는 메모리 상의 헤더를 파싱하여 내보내기 디렉터리에 도달합니다. 즉, DLL 자체가 프로세스에 연결된 후 메모리 상의 PE 헤더를 덮어써서 내보내기 디렉터리 주소(단순히 DataDirectory의 0번째 인덱스)를 변경할 수 있다면, 로더는 컴파일러가 추가한 내보내기 디렉터리 대신 임의의 내보내기 디렉터리에서 함수를 찾을 수 있게 됩니다.

명령줄 매개변수

root@kitploit:~

   __                       _          _     _
  /__\_  ___ __   ___  _ __| |_  /\  /(_) __| | ___ _ __
 /_\ \ \/ / '_ \ / _ \| '__| __|/ /_/ / |/ _` |/ _ \ '__|
//__  >  <| |_) | (_) | |  | |_/ __  /| | (_| |  __/ |
\__/ /_/\_\ .__/ \___/|_|   \__\/ /_/ |_|\__,_|\___|_|
          |_|
                     by @R0h1rr1m

Usage of C:\Users\Public\DLLDemo\ExportHider.exe:

    -h | --help                                 도움말 메시지를 표시합니다.
    -i | --input <입력 경로>                    숨길 함수 이름 목록 파일의 경로입니다. (필수)
    -o | --output <출력 경로>                   DLL 템플릿의 출력 경로입니다. (필수)
    -n | --name <DLL 이름>                      내보내기 디렉터리를 위한 DLL 이름입니다. (필수)
    -c | --count <숨기지 않을 다른 내보내기 함수 개수>    숨기지 않을 다른 내보내기 함수의 개수입니다.

-i | --input <입력 경로> 매개변수와 관련하여, 숨길 함수 이름을 한 줄에 하나씩 저장한 입력 파일의 경로를 지정해야 합니다. 예시 입력 파일의 내용은 다음과 같습니다:

root@kitploit:~
TestFunction1
TestFunction2
TestFunction3

-c | --count 매개변수와 관련하여, 모든 내보내기 함수를 숨기고 싶지 않은 경우(즉, __declspec(dllexport) 또는 .def 파일로 내보내기된 함수 중 일부가 파일 시스템 상의 DLL 내보내기 디렉터리에 PE 파일 뷰어를 통해 나타나는 경우), 이 매개변수를 사용하여 해당 함수의 개수를 지정하면 됩니다. 도구가 메모리 계산 시 이 정보가 필요하기 때문입니다.

빠른 데모 비디오

해결 방안 (Workarounds)

이 기법을 가지고 놀고 싶다면, 프로젝트 개발 중에 마주친 두 가지 흥미로운 점이 있습니다. 프로젝트를 변경하기 전에 이를 알아두어야 할 수도 있습니다:

  • Windows 로더는 이름으로 GetProcAddress 함수를 호출할 때 이진 검색(Binary Search)과 유사한 알고리즘을 사용하여 내보내기 함수를 찾습니다. 따라서 숨겨진 함수를 포함한 모든 내보내기 함수의 이름은 AddressOfNames 배열에서 정렬되어야 합니다. 그렇지 않으면 GetProcAddress 함수가 NULL을 반환합니다. 그래서 이 배열의 멤버를 정렬하기 위해 버블 정렬(Bubble Sort) 알고리즘을 사용했습니다.
  • 위에서 언급했듯이 AddressOfNames 배열, AddressOfFunctions 배열, DataDirectory 배열 및 기타 일부 필드는 RVA(Relative Virtual Address) 값을 필요로 합니다. 또한 이러한 RVA 값을 DWORD 크기의 필드에 저장합니다. VirtualAlloc이나 HeapAlloc 같은 동적 메모리 할당 함수를 사용하여 새로운 임의 내보내기 디렉터리를 위한 메모리 영역을 할당하면, 할당된 주소는 DLL이 매핑된 영역에서 멀게 위치하여 RVA 값이 DWORD 크기 필드에 맞지 않아 정수 오버플로우가 발생합니다. 따라서 DLL 템플릿에서 메모리 요구 사항을 위해 바이트 배열 형식의 전역 변수를 사용했습니다.

정적으로 가져온 DLL (a.k.a. DLL 사이드로딩(Sideloading)) 경우

이 프로젝트를 만든 첫 번째 목표는 내보내기가 누락되었거나(또는 완전히 내보내기 테이블이 없음) 새로 생성된 프로세스에 의해 성공적으로 로드되는 DLL을 생성하는 것이었습니다. 이러한 동작이 DLL 사이드로딩 페이로드에 새로운 놀이터를 제공할 것이라고 생각했습니다. 그러나 Windows 로더가 내보내기 DLL 함수를 확인하기 전에 내보내기 디렉터리 수정 스텁(stub)이 실행될 수 있는 함수나 방법을 찾지 못했습니다.

dll_timing_problem (1)

더 기술적으로 말하면, NTDLL의 Windows 로더 관련 코드 섹션에서 각 DLL에 대해 다음과 같은 흐름과 함수 호출을 발견했습니다 (오래된 노트에서 가져온 것이며, 전문 리버스 엔지니어가 아니므로 일부 오류가 있을 수 있습니다):

root@kitploit:~
1. LdrpMapDll - DLL이 프로세스 주소 공간에 매핑되는 곳입니다. DLL 이름 조건을 만족하는 모든 DLL은 직접 메모리에 올라가며, 파일 시스템 버전에 대한 사전 검사는 없습니다.
2. LdrpSnapModule - Windows 로더가 가져오기(import)를 해결하기 시작하는 곳입니다. 각 가져오기 디스크립터에 대해 PE 구조를 파싱하고, 내보내기 테이블을 확인하며, 가져온 함수를 이진 검색하고, 해당 RVA를 계산한 후, 이 함수 내에서 호출 프로세스의 Import Address Table 항목에 주소를 기록합니다.
3. LdrpDoPostSnapWork - 2단계가 각 가져온 함수에 대해 성공하면, 이 함수에서 메모리 보호, TLS 초기화, CFG 활성화 등이 수행됩니다.
4. LdrpInitializeNode - 3단계가 성공하면, 이 단계에서 모듈 연결 함수가 있습니다.
5. LdrpCallTlsInitializers - DllMain 함수 전에 TLS 콜백이 호출되는 곳입니다.
6. LdrpCallInitRoutine - 가져온 DLL에 대해 처음으로 DllMain이 호출되는 곳입니다. 원래 솔루션에서는 이 함수가 내보내기 테이블을 수정하기에는 너무 늦습니다.

DLL을 가져오는 실행 파일을 실행할 때 Windows 로더가 DLL의 내보내기 테이블에서 필요한 함수 이름을 찾지 못하면 실행을 중단하고 LdrpSnapModule 이후에 실행되는 함수들을 실행하지 않습니다.

DLL 사이드로딩의 경우 호출 프로세스를 수정할 수 없습니다. 따라서 내보내기 테이블을 동적으로 수정할 수 있는 유일한 기회는 LdrpMapDll과 LdrpSnapModule 함수 사이에 코드 실행 기회를 찾는 것뿐입니다. 로더가 LdrpSnapModule 함수 검사 중에 즉시 중단되기 때문입니다. TLS 콜백, 두 번째 DLL 로드, 전달된 내보내기(forwarded exports) 및 기타 몇 가지 해결 방법을 시도했지만 그러한 위치를 찾는 데 도움이 되는 것은 없었습니다. 따라서 안타깝게도 이 방법은 DLL 사이드로딩이나 정적으로 가져온 DLL에는 직접 작동하지 않습니다. 이 문제에 대한 해결책이나 실행 가능한 해결 방안을 발견하신다면, 아이디어를 논의하거나 접근 방식을 함께 고민하거나 구현하는 등 더 탐구해 보고 싶습니다. 이 방향으로의 모든 기여는 크게 환영합니다.

DLL 사이드로딩 경우에 이 방법을 사용할 수 있는 한 가지 가능한 방법은 전체 작업을 프록시 DLL(Proxy DLL)과 페이로드 DLL(Payload DLL)이라는 두 개의 DLL로 나누는 것입니다. 프록시 DLL은 EXE가 예상하는 이름을 가지며 로더의 정적 가져오기 검사를 충족하는 가시적인 내보내기를 가지고 있습니다. 반면 페이로드 DLL은 실제 숨겨진 기능을 포함하며, 내보내기가 누락되어 있고 DllMain에서 내보내기 테이블을 재구성합니다. 이것이 이 문제에 대한 좋은 해결 방안이라고 생각하지 않으므로 구현하지 않았습니다.

참고 자료

  • https://rioasmara.com/2021/10/10/analyze-dll-export-with-pe-bear/
  • https://ferreirasc.github.io/PE-Export-Address-Table/

면책 조항

공인된 보안 테스트에만 사용하십시오. 명시적 허가 없이 이 도구를 시스템에 오용하는 것은 불법입니다.

도구 다운로드