동적 연결 라이브러리 DLL의 로딩·공유·보안 구조

DLL의 런타임 로딩과 프로세스 간 메모리 공유, 암시적·명시적 연결 방식, 플랫폼별 형식과 보안 위험을 정리한다.

2026-08-14 · 최초 발행 2026-01-03

실행 중에 연결되는 공유 코드

동적 연결 라이브러리(DLL, Dynamic Link Library)는 프로그램이 실행될 때 필요에 따라 메모리에 올리는 코드와 데이터의 집합이다. 실행 파일에 라이브러리 코드가 포함되는 정적 라이브러리와 달리, DLL은 런타임에 로드되고 여러 프로세스가 같은 복사본을 공유할 수 있다.

공통 기능을 라이브러리로 분리하면 실행 파일 크기를 줄이고 기능 단위의 개발과 교체가 가능해진다. 반면 라이브러리 버전 충돌, 누락된 파일, 검색 경로를 악용하는 공격처럼 운영과 보안에서 관리해야 할 지점도 생긴다.

정적 라이브러리는 컴파일 시 실행 파일에 포함된다. Windows에서는 .lib, Linux에서는 .a 형식을 사용하며 프로그램마다 코드 복사본이 존재하므로 실행 파일 크기가 증가한다. 동적 라이브러리는 런타임에 로드되며 Windows의 .dll, Linux의 .so, macOS의 .dylib 형식을 쓴다. 여러 프로그램이 한 복사본을 공유할 수 있어 실행 파일 크기를 낮춘다.

라이브러리정적라이브러리동적라이브러리컴파일포함실행 파일크기 증가런타임로드메모리공유실행 파일크기 감소

하나의 DLL을 여러 주소 공간에 매핑하는 방식

하나의 DLL은 여러 프로세스가 공유할 수 있다. 물리 메모리에는 하나의 복사본만 유지하고, 각 프로세스의 가상 주소 공간에 이를 매핑한다.

Process A: kernel32.dll → 물리 메모리 0x1000
Process B: kernel32.dll → 물리 메모리 0x1000 (동일)
Process C: kernel32.dll → 물리 메모리 0x1000 (동일)

예를 들어 100개 프로세스가 1MB DLL을 사용할 때, 각 프로세스에 코드가 복사되는 정적 방식은 100MB가 필요하다. 공유되는 동적 방식에서는 1MB로 유지된다.

DLL을 올리는 시점은 연결 방식에 따라 달라진다. 암시적 연결(Implicit Linking)은 프로그램 시작과 함께 DLL을 자동으로 로드하며, 실행 파일의 Import Table에 DLL 정보가 들어간다. 명시적 연결(Explicit Linking)은 프로그램이 필요할 때 LoadLibrary()로 DLL을 직접 로드한다. 지연 로딩(Delay Loading)은 DLL 함수를 처음 호출할 때 로드하여 시작 시간을 단축한다.

DLL 파일만 교체해도 이를 사용하는 프로그램에 변경을 적용할 수 있어 실행 파일을 다시 컴파일할 필요가 없다. 다만 호환되지 않는 버전이 교체되면 DLL Hell이 발생할 수 있으며, Side-by-Side(SxS) 기술은 이런 충돌을 다루기 위한 방법이다.

시작 시 연결할지, 필요할 때 열지

암시적 연결에서는 컴파일 단계에서 Import Library(.lib)를 링크하고, 실행 파일에 Import Table이 생성된다. 프로그램이 시작되면 OS가 DLL을 로드하고 Import Table의 주소를 해석한다.

// example.c
#include <windows.h>

int main() {
    MessageBoxA(NULL, "Hello", "DLL", MB_OK);  // user32.dll 함수
    return 0;
}

// 컴파일: cl example.c user32.lib
// 실행 시 user32.dll 자동 로드

사용은 단순하고 로딩도 자동이지만, 필요한 DLL이 없으면 프로그램 시작 자체가 실패하며 런타임 제어 폭은 좁다.

명시적 연결은 LoadLibrary()로 DLL을 로드하고, GetProcAddress()에서 함수 주소를 얻은 뒤 함수 포인터로 호출한다. 사용이 끝난 DLL은 FreeLibrary()로 언로드한다.

#include <windows.h>

int main() {
    // DLL 로드
    HMODULE hDll = LoadLibrary("user32.dll");
    if (hDll == NULL) {
        printf("DLL 로드 실패\n");
        return 1;
    }

    // 함수 주소 획득
    typedef int (WINAPI *MessageBoxFunc)(HWND, LPCSTR, LPCSTR, UINT);
    MessageBoxFunc msgBox = (MessageBoxFunc)GetProcAddress(hDll, "MessageBoxA");

    if (msgBox != NULL) {
        msgBox(NULL, "Hello", "DLL", MB_OK);
    }

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

이 방식은 DLL이 없어도 프로그램을 시작할 수 있고 런타임에 대상 DLL을 선택할 수 있다. 대신 함수 포인터를 관리해야 하므로 코드가 복잡해진다.

Import Table과 Export Table이 만나는 지점

DLL은 자신이 제공하는 함수와 변수 목록을 Export Table에 기록한다. 실행 파일은 사용할 DLL과 함수의 목록을 Import Table에 둔다. 동적 링커는 이 두 정보를 바탕으로 Import Table의 주소를 실제 함수 주소로 해석한다.

Windows에서는 PE Loader가 이 역할을 수행하고, Linux에서는 ld.so가 담당한다.

실행 파일ImportTableDLL 이름함수 이름DLLExportTable함수 이름함수 주소동적 링커주소해석

공유와 모듈화가 주는 이점

메모리 공유 효과는 DLL 크기와 사용하는 프로세스 수가 늘수록 분명해진다.

시나리오: 100개 프로세스가 각각 5MB DLL 사용

정적 라이브러리:
  총 메모리: 100 × 5MB = 500MB

동적 라이브러리:
  총 메모리: 1 × 5MB = 5MB (공유)
  절약: 495MB (99%)

DLL별로 개발을 분리하면 인터페이스를 유지하는 한 내부 구현을 바꿀 수 있다. 팀이 각각 DLL을 개발한 뒤 통합 시 링크하는 방식도 가능하다. Photoshop 플러그인이나 VST 플러그인처럼 기능을 추가하거나 제거하는 플러그인 아키텍처에도 이 구조가 쓰인다.

버그 수정이나 기능 개선은 DLL 파일 교체만으로 반영할 수 있다. Windows Update에서 system32 DLL을 업데이트하면 모든 프로그램이 즉시 새 버전을 사용한다.

버전 충돌과 로딩 지연

DLL Hell은 여러 프로그램이 서로 호환되지 않는 DLL 버전을 요구할 때 발생한다.

Program A: foo.dll v1.0 필요
Program B: foo.dll v2.0 필요 (v1.0과 호환 안 됨)

→ 하나만 설치 가능
→ 한 프로그램은 실행 불가

Side-by-Side(SxS)는 여러 버전의 DLL을 동시에 설치하는 방식이며, Windows에서는 WinSxS 디렉토리와 Manifest 파일의 버전 명시가 이를 지원한다.

DLL을 로드하는 시간은 프로그램 시작에 더해지고, 사용하는 DLL이 많으면 누적된다. 지연 로딩을 적용하거나 필요한 DLL만 로드하는 방식으로 이를 줄일 수 있다.

필요한 DLL이 없는 Missing DLL 상태에서는 프로그램을 실행할 수 없다. DLL을 실행 파일과 함께 배포하거나, 일부 DLL에 Static Linking을 적용하고, Installer로 DLL을 설치하는 방식이 대응책이다.

DLL의단점DLL Hell(버전 충돌)로딩 시간증가의존성문제SxS해결지연 로딩DLL함께 배포

운영체제마다 다른 공유 라이브러리 형식

Windows DLL은 .dll 확장자를 사용하며 PE(Portable Executable) 형식이다. 시스템 DLL 경로는 C:\Windows\System32(64비트)와 C:\Windows\SysWOW64(32비트, 64비트 시스템에서)다. DLL 검색은 실행 파일 디렉토리, 시스템 디렉토리(System32), Windows 디렉토리, 현재 디렉토리, PATH 환경 변수 디렉토리 순서로 진행된다.

Linux의 공유 객체는 .so 확장자와 ELF(Executable and Linkable Format) 형식을 사용한다. 시스템 라이브러리는 /lib, /usr/lib, /lib64, /usr/lib64(64비트)에 놓이며, 동적 링커는 ld.so(32비트)와 ld-linux-x86-64.so.2(64비트)다.

export LD_LIBRARY_PATH=/path/to/libs
// libfoo.so
void foo() { printf("Hello from foo\n"); }

// 사용
gcc -shared -o libfoo.so foo.c  // 생성
gcc -o app app.c -L. -lfoo      // 링크
LD_LIBRARY_PATH=. ./app         // 실행

macOS의 동적 라이브러리는 .dylib 확장자와 Mach-O 형식을 쓴다. /usr/lib/System/Library/Frameworks가 시스템 라이브러리 경로이며, 동적 링커는 dyld(dynamic linker)다.

export DYLD_LIBRARY_PATH=/path/to/libs

Windows에서 DLL을 만들고 연결하는 예

DLL을 내보낼 함수에는 __declspec(dllexport)를 선언할 수 있다.

// mathlib.c
#include <windows.h>

__declspec(dllexport) int Add(int a, int b) {
    return a + b;
}

__declspec(dllexport) int Multiply(int a, int b) {
    return a * b;
}
cl /LD mathlib.c  # mathlib.dll, mathlib.lib 생성

암시적 연결을 사용할 애플리케이션은 __declspec(dllimport)로 함수를 선언하고 Import Library와 함께 컴파일한다.

// app.c
#include <stdio.h>

__declspec(dllimport) int Add(int a, int b);
__declspec(dllimport) int Multiply(int a, int b);

int main() {
    printf("3 + 5 = %d\n", Add(3, 5));
    printf("3 * 5 = %d\n", Multiply(3, 5));
    return 0;
}
cl app.c mathlib.lib

명시적 연결에서는 DLL의 함수 주소를 직접 가져온다.

// app.c
#include <windows.h>
#include <stdio.h>

typedef int (*AddFunc)(int, int);

int main() {
    HMODULE hDll = LoadLibrary("mathlib.dll");
    if (hDll == NULL) {
        printf("DLL 로드 실패\n");
        return 1;
    }

    AddFunc Add = (AddFunc)GetProcAddress(hDll, "Add");
    if (Add != NULL) {
        printf("3 + 5 = %d\n", Add(3, 5));
    }

    FreeLibrary(hDll);
    return 0;
}
cl app.c

DLL 검색 경로는 보안 경계가 된다

DLL Hijacking은 정상 DLL 대신 악의적인 DLL이 로드되도록 만드는 공격이다. DLL 검색 순서를 악용해 실행 파일 디렉토리에 악성 DLL을 배치하고, 프로그램이 시스템 DLL이 아니라 이를 로드하게 만든다.

정상: app.exe → C:\Windows\System32\kernel32.dll
공격: app.exe → C:\AppDir\kernel32.dll (악성)

DLL Injection은 실행 중인 프로세스에 DLL을 강제로 주입하고 프로세스 메모리를 조작하는 방식이다. CreateRemoteThread()로 원격 쓰레드를 생성한 뒤 LoadLibrary()를 호출해 DLL을 로드할 수 있다. 키로거와 정보 탈취에 악용될 수 있으며, 디버깅, 치트 탐지, 게임 핵에도 사용된다.

하이재킹 방어에는 DLL을 절대 경로로 로드하고, SetDllDirectory()로 검색 경로를 제한하며, 디지털 서명을 확인하는 방법이 있다. 주입 대응에는 코드 서명 검증, ASLR(Address Space Layout Randomization), DEP(Data Execution Prevention)를 적용한다. Windows의 Safe DLL Search Mode, Known DLLs(레지스트리), Secure Boot도 관련 보안 기능이다.

DLL동적 라이브러리운영체제동적 링킹보안