
C++코드로, 파라미터로 a, b를 받아 둘을 더한 결과를 리턴하는 함수를 dll로 만든 코드이다.
DllMain은 dll파일이 로드되거나, 언로드 될때 조작할 수 있도록 예시로 작성하였다.
해당 파일은 컴파일시 g++ -shared -o example_dll.dll example_dll.cpp 이런식으로 작성하 주면 해당 코드가 dll형식으로 빌드된다.

dll에 있는 함수를 호출하는 코드이다. 마찬가지로 C++로 작성하였으며, 윗 부분에 AddNumbersFunc가 선언돼 있는 것을 볼 수 있다.
그 다음 HMODULE hDLL = LoadLibraryW(L'example_dll.dll");코드가 있는데 함수 명에서도 알 수 있듯이 example_dll.dll 을 메모리에 로드하는 코드이다.
AddNumbersFunc AddNumbers = (AddNumbersFunc)GetProcAddress(hDLL, "AddNumbers"); 코드를 통해 dll에서 AddNumberse 함수의 주소를 가져온다.

PEView 프로그램을 이용하여 PE를 분석한 사진이다.

ImageBase : PE 파일이 메모리에 로드될 때 기본적으로 로드되는 시작주소

사진은 IDA로 실행파일을 분석한 결과이다. 코드의 시작주소는 Imagebase + Base of Code가 된다. 코드의 시작주소는6DC41000이고, ImageBase의 주소는 6DC40000이다. Base Of Code는 1000인 것을 알 수 있다.

데이터 시작주소는 Imagebase + Base of Data가 된다. 시작주소는 6Dc42000인 것을 볼 수 있고, Base of Data는 2000이 된다.

위에서 작성한 dll파일을 로드하여 함수를 호출하는 코드에서 .dll파일을 2개 로드해 보았다.
IDA를 통해 확인해본 결과

서로 다른 위치에 dll파일이 로드되는 것을 볼 수 있다.
PE헤더에서 기존 주소에 로드되지 못했을 경우 재배치 섹션을 제공한다.

위에서 AddNumbers라는 함수를 dll파일에서 선언을 했었다.

IDA를 통해 export 된 함수를 보면 AddNumbers가 있는 것을 볼 수 있다.
이 외에 3개의 함수가 더 있는 것을 볼 수 있는데 각각 살펴보면
dll의 main을 IDA를 이용해서 찾아 보았다. cmp, jz 가 반복적으로 나오는 것을 볼 수 있는데 이는 main에 있는 switch case문이다.
그러면 eax = ul_reason_for_call 이라는 것을 알 수 있고, 어셈블리 코드에서 eax = [ebp+fdwReason] 인 것을 볼 수 있다.
먼저 ul_reason_for_call은 dll이 특정 이벤트에 의해 호출될 때, 호출된 이유를 나타내는 값을 전달한다.
fdwReason을 이용하여 스택프레임에 있는 값을 가져와 분기를 하는 방식이다.
먼저 dll의 메인문에서는 dll을 로드하거나 언로드할 대 실행되는 초기화/정리하는 함수이다.
해당 위치에 전역 변수 초기화, 후킹, 백도어 설치, 안티티버깅 코드 삽입 등 의 작업이 들어갈 수 있다.
악성 dll의 경우, dll의 main에서 실제 악성 페이로드를 실행할 수 있다.
이러한 이유로 리버싱 관점에서 분석자는 dll main을 중요하게 살펴 보아야 한다.
왜 switch-case로 찾을까? 그 이유는 switch-case 기반의 패턴으로 dll의 main을 찾는 것이 실용적이고 흔히 쓰이는 벙법이다.
하지만 최적화 옵션에 따라 switch 대신 jump table을 사용하는 경우도 있고, 난독화나 VMProtect 같은 보호가 들어간 경우에는 사용하기 어려운 방법이다.
출처 : 리버스 엔지니어링 바이블(지은이 강병탁, 2012)