본문 바로가기

리버싱 핵심원리

[리버싱 핵심원리] 25장 - PE 패치를 이용한 DLL 로딩

대상 프로그램의 실행 파일을 직접 수정하여 DLL을 강제로 로딩하는 방법에 대해 알아본다. 해당 방법은 한 번 적용해 놓으면 별도의 인젝션 과정 없이 프로세스가 시작할 때마다 원하는 DLL을 로딩하게 만들 수 있다. (일종의 크랙이다.)

25.1 실습 파일

실습 목표 : TextView.exe 파일을 직접 수정해 실행 시 myhack3.dll을 로딩하도록 만드는 것

1. TextView.exe

TextView.exe에서 직접 임포트하는 DLL 파일들을 볼 수 있다.

2. TextView_patch.exe

TextView.exe의 IDT에서 myhack3.dll이 추가된 모습을 볼 수 있다. TextView_patch.exe는 실행 시에 myhack3.dll 파일을 로딩한다. 파일을 실행시키면 아래와 같은 화면을 볼 수 있다.

 

25.2 소스코드 - myhack3.cpp

myhack3.dll 파일의 소스코드에서 사용되는 함수들에 대해 간략히 알아보자.

 

1. DLLMain() : 사용자 스레드를 실행시킨다. 실행시킨 스레드 프로시저에서 DownloadURL()과 DropFile()을 호출해 실제적인 작업을 수행한다.

 

2. DownloadURL() : 파라미터로 넘어온 인터넷 파일을 다운받아 파라미터로 넘어온 경로에 저장한다. (구글 사이트에 접속해 index.html 파일을 받아온다.)

 

3. DropFile() : 다운받은 파일을 TextView_Patch.exe 프로세스에 드롭시켜 그 내용을 보여준다. PID로부터 Window Handle을 구하여 PostMessage(WM_DROPFILES) API를 호출하는 것이 주요내용이다.

 

4. dummy() : myhack3.dll 파일에서 외부로 서비스하는 익스포트 함수이다. 형식적인 완전성을 위해 존재하는 함수로 아무런 기능을 하지 않는다. (PE 파일에서 어떤 DLL을 임포트 한다는 것은 파일의 코드 내에서 그 DLL이 제공하는 익스포트 함수를 호출한다는 의미이기 때문에 형식적인 완성성을 위해 최소한 하나 이상 제공해야 한다.)

 

25.3 TextView.exe 파일 패치 준비 작업

1. 패치 아이디어 및 사전 조사

앞에서 본 것처럼 TextView.exe의 IDT 마지막에 myhack3.dll을 추가한다. 해당 작업을 수행하기 전 dll 파일이 들어갈 여유공간이 있는지 먼저 확인한다. 

TextView.exe의 IDT는 '.rdata' 섹션에 존재하며 IDT는 IMAGE_IMPORT_DESCRIPTOR 구조체 배열로 이루어져 있고 배열의 마지막은 NULL 구조체로 끝난다. IDT의 크기는 14로 IID 영역은 (14*5) 64의 크기를 가진다. 

위 사진을 통해 볼 수 있듯이(파란색 표시된 부분이 IDT), IDT 이후에 바로 다른 값이 나오며 myhack3.dll을 붙일 공간이 없음을 알 수 있다. 따라서 IDT를 이동시키는 다른 방법을 이용한다.

 

3. IDT 이동

IDT 전체를 다른 넓은 위치로 옮길 때, 위치를 선택하는 방법은 3가지가 있다.

1. 파일의 다른 빈 영역을 찾는다.

2. 파일 마지막 섹션의 크기를 늘린다.

3. 파일 끝에 새로운 섹션을 추가한다.

 

첫 번째 방법인 '파일의 다른 빈 영역을 찾는다.'을 시도해본다. IDT가 존재하는 .rdata 섹션의 맨 아래부분으로 내려가면 사진처럼 빈 공간을 찾을 수 있다.

이 Null-Padding 영역(RVA : 8C60 ~ 8DFF)에서 적당한 위치에 기존 IDT를 옮길 것이다.

그 전에, 해당 영역이 진짜 Null-Padding 영역인지 확인해야 한다. 파일에 있는 영역이 무조건 프로세스 가상 메모리에도 로딩되는 것은 아니고 섹션 헤더에 명시된 영역만큼만 메모리에 로딩되기 때문이다. 

.rdata 섹션 헤더를 통해 파일과 메모리에서의 섹션의 크기 차이를 확인할 수 있다. 파일에서의 크기는 2E00이고 메모리에 로딩된 크기는 2C56이다. 따라서 실제 프로그램에서 사용되지 않는 영역의 크기는 1AA(2E00 - 2C56)이다. 

1AA > 64 로, 해당 위치에 IDT를 옮기는 것은 아무런 문제가 없다.

25.4 TextView.exe 패치 작업

1. IMPORT Table의 RVA 값 변경

IMAGE_OPTIONAL_HEADER의 IMPORT Table의 구조체 멤버는 IDT의 위치와 크기를 알려준다. 

TextView.exe에서 IMPORT Table의 RVA 값은 84CC이다. 해당 주소를 앞에서 찾는 주소(8C80)으로 변경한다. 또한, 마지막에 myhack3.dll을 위한 구조체를 추가해야 하기 때문에 현재 크기(64)에 구조체의 크기(14)를 더한 값인 78로 Size 값을 수정한다.

2. BOUND IMPORT TABLE 변경

myhack3.dll을 정상적으로 임포트하기 위해선, 해당 BOUND IMPORT TABLE에도 정보를 추가해야 한다. 이 테이블은 옵션으로 반드시 존재할 필요가 없고 현제 파일에도 값이 0으로 되어있기 때문에 무시하고 지나가도 된다.

만약 이 값이 존재하면서 정보가 잘못 기재되어 있다면 실행 시 에러가 발생한다. 따라서, 다른 파일을 패치할 때는 이 값을 잘 확인해야 한다!

3. 새로운 IDT 생성

file offset으로 나타낸 주소이다.

기존의 위치에 있던 IDT 값들을 새로운 주소로 옮긴다. 그리고 이 상태에서 myhack3.dll을 위한 IID 구성을 위해 IDT의 끝(RAW : 7ED0) 에 값을 추가한다. 추가한 내용은 아래와 같다.

4. Name, INT, IAT 세팅

  RVA RAW
INT 8D00 7F00
Name 8D10 7F10
IAT 8D20 7F20

앞에서 추가한 IID 구조체 멤버들은 또 다른 자료구조를 가리키는 RVA 값을 가진다. 따라서 패치된 파일이 정상적으로 실행되기 위해선 이런 자료구조를 정확히 세팅해야 한다. 앞에서 추가한 멤버들의 값들의 RVA/RAW은 위 표와 같다.

해당 값들은 새로 생성한 IDT의 바로 아래쪽에 위치하고 해당 위치로 가 아래 사진처럼 값을 입력한다.

위 영역을 PEView로 열어 RVA 값으로 변경해 하나씩 살펴보자.

먼저, 8CD0 주소에 myhack3.dll을 위한 IID 구조체가 존재한다. 3개의 중요 멤버(RVA of INT, RVA of Name, RVA of IAT)에 입력된 값들의 의미는 실제 INT, Name, IAT의 포인터이다.

먼저 INT에 대해 알아보자. INT는 배열의 각 원소가 임포트하는 함수의 Ordinal(2byte) + Func Name String 구조체 형식의 주소(RVA)의 배열이다. 따라서 INT 배열에 존재하는 하나의 원소 8D30이 가리키는 곳엔

[00 00(Ordinal) + 00 79 6D 6D 75 64 (dummy - Func Name String)] 값이 존재하는 것을 확인할 수 있다. 또한, INT는 NULL로 끝나는 것을 알 수 있다.

Name은 임포트 하는 함수를 제공하는 DLL 파일의 이름 문자열이다. 8D10 주소에 "myhack3.dll" 문자열을 볼 수 있다.

IAT도 INT와 동일하게 RVA 배열이다. 배열의 각 원소는 INT와 같은 값을 가져도 되고 INT가 정확하다면 다른 값을 가져도 된다. 실행 시에 PE 로더에 의해 메모리상의 IAT 위치는 실제 함수로 덮어 쓰기 때문이다.

5. IAT 섹션의 Characteristics 변경

앞서 설명했듯이 IAT 값은 실행 시에 실제 함수로 덮어 쓰여진다. 따라서 해당 섹션(.rdata)은 반드시 WRITE 속성을 가지고 있어야 한다. 아래 사진을 보면 변경 전의 .rdata 섹션의 속성을 볼 수 있다.(Write 속성 존재하지 않는다.)

쓰기 속성의 값은 80000000(IMAGE_SCN_MEM_WRITE)이다. Characteristic 값은 bit OR 연산을 이용하기 때문에 계산을 하면 최종 Characteristic 값은 C0000040이 된다. 

여기서 한 가지 생각해 볼 점은 기존 IDT 내부의 IID 구조체들에서도 IAT 값이 메모리에 로딩되며 정확한 함수 값으로 변경되는 경우가 존재했을텐데 WRITE 속성이 없었다는 점이다.  이는 'IMPORT Address Table' 때문이다. 이곳에 명시된 주소 영역에 IAT가 존재하면 그 섹션의 쓰기 속성이 없어도 되기 때문이다.

25.5 검증

이렇게 패치가 완료된 파일을 저장해 실행시키면 다음과 같이 잘 실행되는 것을 볼 수 있다.

 

 

출처 : 리버싱 핵심원리 (이승원, 인사이트)