발자취

악성코드 Pre-Lab3. PE 패치를 이용한 DLL 로딩 본문

3-2/악성코드

악성코드 Pre-Lab3. PE 패치를 이용한 DLL 로딩

해린 2023. 12. 16. 09:00

본 실습은 리버싱 핵심원리 교재 25장의 내용을 진행하며,

03_DLL_Injection > 25_PE_패치를_이용한_DLL_로딩의 TextView.cpp, myhack3.cpp 코드를 사용한다.

(https://github.com/reversecore/book)

최종 목표는 TextView_Patch.exe에 myhack3.dll을 패치하여 어떠한 동작을 하도록 만드는 것이다.

 

TextView.exe와 myhack3.dll을 만든 뒤, 같은 파일에 둔다.

TextView.exe의 복사본을 만들어서 TextView_Patch.exe 파일을 만든다.

TextView.exe는 원본 파일, TextView_Patch.exe는 직접 패치를 할 파일이다.

 

1. 실습 진행

1. 옵션 헤더의 사이즈 변경

기존에 없던 것들을 새롭게 넣어줄 것이기 때문에 테이블의 사이즈, 테이블의 시작 주소 등이 바뀔 수 밖에 없다.

따라서 옵션 헤더에 있는 정보를 바꿔줄 것이다.

 

PEview에서 TextView.exe를 열어줬다.

IMAGE_NT_HEADERS > IMAGE_OPTIONAL_HEADER에서 'IMPORT Table'에 대한 정보를 확인한다.

RVA라고 적힌 라인은 IMPORT Table의 시작 주소(RVA, 즉 상대주소로 표현됨)이고, 그 아래 라인은 사이즈를 의미한다.

이 두 정보를 바꿔줄 것이다.

 

SECTION .rdata > IMPORT Directory Table(IDT)의 모습이다. 우리는 myhack3.dll을 IDT에 새롭게 패치해줘야 하는데, 문제는 이 곳에 바로 추가할 수 없다는 것이다. 실제로 이 IDT의 끝 주소와 IMPORT Name Table(INT)의 시작 주소가 굉장히 가깝기 때문에 새로운 dll 정보를 넣어줄 공간이 없다. 

따라서 우리는 이 IDT를 통째로 복사해서 다른 넓은 공간으로 옮겨준 뒤, myhack3.dll의 정보를 추가해줄 것이다.

이 작업을 하고 나면 IMPORT Table의 시작 주소와 사이즈가 달라질 것이고, 이 달라지는 시작 주소와 사이즈를 계산하여 옵션 헤더에서 수정하는 것이 우리의 첫번째 목표이다.

 

SECTION .rdata에서 쭉 내리다보면 null Padding 공간을 마주할 수 있다.

 

이 공간이 실제로 null Padding이 맞는지, 아니면 어떠한 공간인지 확인하기 위해서 IMAGE_SECTION_HEADER .rdata로 왔다.

 

Size of Raw Data - Virtual Size = null padding의 크기

 

위 값을 계산해서 IMAGE_OPTIONAL_HEADER에 있는 IMPORT Table의 값(=기존 IDT 크기)와 비교했을 때 이 Size 값보다 크면 넣을 수 있다.

 

- null padding의 크기: 00001200 - 0000105E = 1A2

- 기존 IDT 크기 + myhack3.dll 관련 정보 크기: DC + 14 (20바이트를 Hex로 표현한 값) = F0

  → 1A2 > F0 이기 때문에 최종 IDT 사이즈보다 null padding 공간의 크기가 더 크므로 이 공간에 넣을 수 있다. 

 

 

이제 실제로 패치해 볼 것이다.

null padding 공간 중 아무 곳에 패치해도 상관없기 때문에 '00004080' 위치부터 패치할 것이다.

IMPORT Table의 RVA의 위치는 00000180, Size의 위치는 00000184이다.

 

HxD에서 TextView_Patch.exe를 열어주었다.

00000180 위치로 가서 '선택 영역 채우기'로 패치할 시작 주소(00004080)을 넣어준다. 이때 리틀 엔디안 기법에 따라 '80 40 00 00'으로 넣어줘야 한다. Size 값도 마찬가지로 'F0 00 00 00'으로 채워준다.

 

첫번째 작업이 끝났다.

 

 

2. IDT 패치

옵션 헤더값을 변경해줬으니 이제 실제로 IDT를 복사해서 옮겨줘야 한다.

 

PEView > IDT에서 주소를 'pFile'로 바꾼다. (HxD에서는 pFile 주소체계를 사용하기 때문)

첫 시작 주소(1E64)와 마지막 주소 (1F2C-1=1F2B)를 확인한다. 

 

HxD에서 위에서 알아낸 주소에 있는 내용만큼만 복사한다.

 

아까 알아둔 null padding의 주소 (pFile로는 2680이다)에 '붙여넣기 삽입'해줬다.

이제 이 바로 뒷 부분에 myhack3.dll 관련 정보를 넣어주면 된다.

 

우리가 패치할 구조에 대해 설명하자면, INT RVA와 IAT RVA를 따라가면 API RVA 주소가 나오는데 이 주소를 또 따라가면 우리가 사용하는 함수 이름인 "dummy" 문자열이 나온다. 그리고 Name RVA 주소 번지를 따라가보면 "myhack3.dll"이라는 문자열이 나온다.

    INT/IAT RVA → API RVA → "dummy"

    Name RVA → "myhack3.dll"

 

*그렇다면 왜 INT와 IAT는 같은 것을 가리키는가?

IAT는 Import Address Table의 약자로, 함수의 주소를 가리킨다. 그러나 "dummy"라는 함수의 주소는 처음부터 알 수 있는 것이 아니고 실행을 해야 알 수 있기 때문에 실행 전에 함수 이름의 주소로 초기화를 시켜두는 것이다. 나중에 실행되면 실제 "dummy" 함수의 주소를 갖게 된다.

 

3. INT, IAT, Name RVA 패칭

그럼 이제 진짜로 패칭 작업을 해볼 것이다.

 

아까 붙여넣기한 IDT의 마지막 주소 라인이다. (pFile로 2740)

 

다시 RVA로 바꿔 보았다. IDT의 마지막 주소 라인의 RVA는 4140이다.

여기서 세 줄 내려간 4170을 Name RVA, 4180을 INT RVA, 4190을 IAT RVA로 사용할 것이다.

 

드래그 해둔 부분이 아까 복붙한 IDT 영역이다.

그 바로 뒤부터 '70 41 00 00 / 00 00 00 00 / 00 00 00 00 / 80 41 00 00 / 90 41 00 00'으로 채워준다.

이렇게 하면 myhack3.dll의 정보(Name RVA, INT, IAT 위치)가 다 들어간 것이다.

이제 실제로 이 위치에 값을 넣어주기만 하면 된다.

 

4170(2770): "dummy"라는 문자열이 있는 주소 위치값

4180(2780): 실제로 "myhack3.dll"이라는 문자열이 존재하는 위치

4190(2790): "dummy"라는 문자열이 있는 주소 위치값

41A0(27A0): 실제로 "dummy"라는 문자열이 존재하는 위치

 

마지막으로 지금 현재는 READ 권한만 가지고 있는데, WRITE 권한이 있어야 나중에 실행했을 때 "dummy" 함수 주소가 실제로 IAT에 써질 수 있다. 권한을 변경해줘야 한다.

0244 위치에 권한이 설정되어 있는 것을 확인할 수 있다. HxD에서 이 위치로 가서 권한을 '40 00 00 C0'으로 변경해주면 WRITE 권한도 가지도록 해줄 수 있다.

 

이렇게 변경해줬다.

 

이것으로 패칭 작업은 완료되었다!

 

패칭한 내용을 저장한 뒤, TextView_Patch.exe 파일을 실행해보면 index.html이 새롭게 생성된다.

 

실행해보면 이렇게 구글 페이지로 연결된다.

 

성공적으로 패칭이 된 결과이다!

 

 

2. 코드 분석

myhack3.cpp의 내용만 분석한다.

 

DllMain 함수부터 살펴보겠다.

 

[179] DLL_PROCESS_ATTACH: dll을 프로세스와 연결해줌.

인젝션 됐을 때, dll 정보가 TextView 안에 들어가 연결이 된 후, dummy 함수를 호출해서 여러가지 기능을 쓸 수 있게 해줌.

 

[19] dummy 함수: 실제 함수 기능을 하는 것은 아니고, 이름만 함수임.

 

[149] ThreadProc: 스레드가 하는 일을 정의해둠. 다운로드 하는 기능이 들어가 있다.

[154] OutputDebugString: 디버그 발생하면 출력. 해당 프로시저가 제대로 실행됐는지 확인하기 위해 있는 것.

[156] 현재 프로세스 경로명을 얻어서 szPath에 넣어줌

[158] 가장 마지막에 '\\'가 나오는 위치를 찾아서 p에 넣어준다. (파일명을 찾기 위해)

[160] 현재 프로세스가 있는 경로명을 찾아서 index.html을 이 곳에 복사한다. (다운로드 받기 전에 이름을 미리 넣어두는 것이다.)

 

DownloadURL - 1

[27] 실제로 index.html을 다운로드 받는 곳.

[36] InternetOpen을 통해서  파일 오픈하듯이 인터넷을 오픈해 인터넷을 사용한다.

"ReverseCore": 인터넷 오픈을 할 때 호출하는 주체에게 "ReverseCore"라는 이름으로 오픈하겠다고 말하는 것이다. 아무 이름이나 들어가도 상관없다!

[37] INTERNET_OPEN_TYPE_PRECONFIG: 기존에 있던 거 오픈하겠다는 의미이다.

 

[47] 오픈한 결과물을 가지고 URL을 오픈한다. (실제로 다운로드 받는 사이트 URL)

여기서부터 실제적인 URL 접근이다.

 

DownloadURL - 2

[59] 다운로드 받은 내용들을 적기 위한 파일을 오픈한다. 

wt: 텍스트 모드로 쓰기

새로 실행할 때마다 원래 내용을 지우고 새로 적는다.

 

[65] InternetReadFile: 파일 오픈을 성공했다면 내용을 읽어온다. 실제로 인터넷에서 내용을 가져오는 부분이다. pBuf은 읽어온 내용을 적는 "창"이다.

 

[70] pFile: index.html을 다운로드 받은 경로에서 index.html을 다운받아온다. index.html이 즉 pFile이다.

 

=> 다운로드 끝. 이제 파일을 드랍해야 한다.

 

DropFile의 일부

[124] DropFile의 형식만큼 크기를 잡아서 GlobalAlloc으로 메모리 안에 할당을 해준다.

GMEM_ZEROINIT: 메모리 영역을 초기화해준다.

 

[130] 최소한 이 작업을 하는 동안은 내용이 바뀌지 않게끔 메모리 차단을 하기 위해 락을 걸어준다.

[132] pBuf를 드랍파일 형태로 바꾸고,

[134] pFile에는 다운로드 받은 내용이 들어가 있는데 이 내용을 pBuf에 복사한다.

=> pFile에 해당하는 index.html 내용을 pBuf에 해당하는 화면 창에 복사해서 화면 창에 index.html 내용이 띄워지도록 하는 것이다. (TextView.exe를 실행해보면 내용이 보인다.)

 

 

3. 생각해보기

1. 공격자가 PE 파일에 악성 DLL 파일을 패치하는 방식으로 악성 DLL을 실행한다고 가정하면, 악성 DLL이 실행되기 전에 탐지하기 위한 방법은 무엇인가?

제일 좋은 방법은 PE 파일 자체를 분석해서, 나중에 실행하기 전에 다시 분석했을 때와 처음에 분석했을 때를 비교해서 PE 파일에 새로 생긴 dll을 알아차리는 방법이다. 그러나, 모든 PE 파일을 파악하고 있어야 하기 때문에 쉽지 않은 방법이다.

 

따라서 정적 분석을 통해 어떤 기능을 하는 dll인지, 어떤 동작을 하는지를 알아내 새로 인젝션된 dll을 찾아내는 방식이 가장 좋다.

 

PE 패치를 통한 DLL 인젝션 실습 끝!