개발/WIN32-MFC

DLL 시리즈 #9 커플링을 이용한 보안 방법

-=HaeJuK=- 2024. 9. 12. 16:07

 

커플링 심화를 이용한
DLL 포인터 마스킹 후 역 마스킹하여 Caller 검증 방법

요구 사항

  1. CLASS A를 만들고, 함수 BOO, FOO 구현
  2. CLASS AImplnew로 할당
  3. 할당된 AImpl에 특정 수로 마스크 처리
  4. DLL에서 Caller에게 마스크된 포인터를 리턴
  5. Caller는 BOO, FOO 호출 시 해당 포인터를 전달
  6. DLL에서 마스킹을 풀어 리턴된 포인터가 올바른지 체크
  7. 정상적이면 AImpl 내부 함수 호출

코드 예제

1. DLL 내 CLASS A 정의 및 포인터 마스킹 처리

DLL에서 CLASS AAImpl을 정의하고, 포인터를 특정 값으로 마스킹 처리한 후 Caller에게 반환합니다.


// MyDLL.cpp
#include <windows.h>
#include <iostream>

class A {
public:
    virtual void BOO() = 0;
    virtual void FOO() = 0;
    virtual ~A() {}
};

class AImpl : public A {
public:
    void BOO() override {
        std::cout << "AImpl::BOO 호출됨!" << std::endl;
    }
    void FOO() override {
        std::cout << "AImpl::FOO 호출됨!" << std::endl;
    }
};

// 포인터 마스킹에 사용할 값
constexpr uintptr_t MASK = 0x5A5A5A5A;

// 포인터를 마스킹하여 반환
extern "C" __declspec(dllexport) uintptr_t CreateClassA() {
    AImpl* pImpl = new AImpl();
    return reinterpret_cast<uintptr_t>(pImpl) ^ MASK;  // 마스킹 처리
}

// 마스킹된 포인터를 역마스킹하여 실제 포인터로 변환
AImpl* UnmaskPointer(uintptr_t maskedPtr) {
    return reinterpret_cast<AImpl*>(maskedPtr ^ MASK);  // 역마스킹
}

// BOO 함수 호출
extern "C" __declspec(dllexport) void CallBOO(uintptr_t maskedPtr) {
    AImpl* pImpl = UnmaskPointer(maskedPtr);
    if (pImpl) {
        pImpl->BOO();
    }
}

// FOO 함수 호출
extern "C" __declspec(dllexport) void CallFOO(uintptr_t maskedPtr) {
    AImpl* pImpl = UnmaskPointer(maskedPtr);
    if (pImpl) {
        pImpl->FOO();
    }
}

// 클래스 해제
extern "C" __declspec(dllexport) void DeleteClassA(uintptr_t maskedPtr) {
    AImpl* pImpl = UnmaskPointer(maskedPtr);
    if (pImpl) {
        delete pImpl;
    }
}

2. DEF 파일 설정 (NONAME 방식으로 export)

DLL의 DEF 파일에서 CreateClassACallBOO, CallFOO 등을 NONAME 방식으로 export 합니다.


LIBRARY MyDLL
EXPORTS
    CreateClassA @1 NONAME
    CallBOO @2 NONAME
    CallFOO @3 NONAME
    DeleteClassA @4 NONAME

3. Caller가 DLL의 함수 호출

Caller는 마스킹된 포인터를 사용하여 DLL의 함수 BOOFOO를 호출합니다. DLL 내부에서 포인터가 올바른지 확인한 후 함수가 실행됩니다.


// main.cpp
#include <windows.h>
#include <iostream>

typedef uintptr_t (*CreateClassA)();
typedef void (*CallBOO)(uintptr_t);
typedef void (*CallFOO)(uintptr_t);
typedef void (*DeleteClassA)(uintptr_t);

int main() {
    // DLL 로드
    HMODULE hModule = LoadLibrary("MyDLL.dll");
    if (!hModule) {
        std::cerr << "DLL 로드 실패" << std::endl;
        return 1;
    }

    // Ordinal 값으로 함수 가져오기
    CreateClassA createClassA = (CreateClassA)GetProcAddress(hModule, MAKEINTRESOURCE(1));  // @1
    CallBOO callBOO = (CallBOO)GetProcAddress(hModule, MAKEINTRESOURCE(2));                 // @2
    CallFOO callFOO = (CallFOO)GetProcAddress(hModule, MAKEINTRESOURCE(3));                 // @3
    DeleteClassA deleteClassA = (DeleteClassA)GetProcAddress(hModule, MAKEINTRESOURCE(4));  // @4

    if (createClassA && callBOO && callFOO && deleteClassA) {
        // 마스킹된 포인터 받기
        uintptr_t maskedPtr = createClassA();

        // BOO와 FOO 호출
        callBOO(maskedPtr);
        callFOO(maskedPtr);

        // 객체 해제
        deleteClassA(maskedPtr);
    }

    // DLL 언로드
    FreeLibrary(hModule);
    return 0;
}

보안 및 관리 측면

  • 포인터 검증: 포인터를 마스킹하고, 역마스킹하여 DLL 내부에서만 사용되도록 하여 보안을 강화합니다. 마스킹된 포인터는 외부에서 조작하기 어렵습니다.
  • 메모리 관리: DLL에서 객체의 생성과 해제를 모두 처리하므로, 메모리 누수나 충돌을 방지할 수 있습니다.
  • 보안 강화: NONAME 기술을 사용해 함수 이름을 숨기고, ordinal 값으로 함수에 접근하게 하여 외부에서 함수의 이름을 알아내기 어렵습니다.

결론

NONAME 기술을 사용하여 DLL에서 포인터 마스킹역마스킹을 통해 보안을 강화하는 방법은 외부에서 조작된 포인터로 인한 공격을 방지하고, 메모리 관리의 일관성을 유지하는 데 매우 효과적입니다. 이 기법을 통해 DLL 함수 호출 시 포인터가 정상적인지 확인하고, 신뢰할 수 있는 경우에만 객체의 메서드를 호출할 수 있습니다.

반응형