2016. 1. 27. 15:13

Charpter 33 스텔스 프로세스


 이번 챕터에서는 API 코드 패치를 통한 API 후킹 방법에 대해서 공부한다. 또한 모든 프로세스를 후킹하는 글로벌 후킹에 대해 공부하고, 이것을 사용하여 특정 프로세스를 감추는 스텔스 기법에 대해 알아볼것이다.


 IAT 후킹 방식이 프로세스의 특정 IAT 값을 조작해서 후킹을 하는 방식이라면, 코드 패치 방식은 실제 API 코드 시작 5바이트 값을 JMP XXXXXXXX 명령어로 패치하는 방식이다.


다음은 후킹 되기 전의 정상적인 프로세스 메모리의 모습이다.


procexp.exe 코드에서 ntdll.ZwQuerySystemInformation() API를 호출하면 코드의 흐름은 다음과 같다.


1. procexp.exe의 00422CF7 주소의 명령어는 ZwQuerySystemInformation API를 호출하고 있다.

2. 해당 API 는 실행이 완료되면, 호출코드 다음 명령어 주소로 되돌아 간다.


다음은 API가 후킹된 프로세스 메모리의 모습니다. stealth.dll을 인젝션하여 ntdll.ZwQuerySystemInformation API를 코드 패치하였다.


 위 그림의 호출 흐름은 다음과 같다.

1. 422CF7 주소에서 ntdll.ZwQuerySystemInformation() API를 호출(7C93D92E)

2. 7C93D92E 주소에 있는 패치된 코드 JMP 10001120에 의해서 10001120주소로 점프한다. 1000116A 주소의 CALL unhook() 명령어에 의해서 ntdll.ZwQuerySystemInformation API 시작 5바이트는 원래대로 복원된다.

3. 1000119B 주소의 CALL EAX(7C93D92E) 명령어에 의해서 원본 함수(ntdll.ZwQuerySystemInformation)가 호출된다.(언훅 상태이기 때문에 정상적으로 실행됨.

4.ntdll.ZwQuerySystemInformation()의 실행이 완료되면 7C93D93A 주소의 RETN 10 명령에 의해 stealth.dll 코드  영역으로 리턴된다. 그리고 10001212 주소의 CALL hook() 명령어에 의해서 ntdll.ZwQuerySystemInformation API를 다시 후킹한다.(시작 5바이트를 JMP로 패치)

5.  stealth.MyZwQuerySystemInformation의 실행이 완료되면, 10001233주소의 RETN 10 명령에 의해서 procexp.exe 프로세스 코드 위치로 리턴된다.


 이 챕터에서 설명하는 프로세스 은폐의 동작 원리는 '프로세스를 검색하는 API를 변조시키는 것'이다. 유저모드에서 프로세스를 검색하기 위한 API는 2종류가 있는데, CreateToolhelp32Snapshot과 EnumProcesses API 이다. 이 2가지 API들은 모두 내부적으로 ntdll.ZwQuerySystemInformation API를 호출하기 때문에, 위 2가지 API를 수정 할 것이 아니라 ZwQuerySystemInformation API만 수정 한다면 유저모드에서 프로세스 검색을 우회할 수 있다.


 글로벌 API 후킹이란 현재 실행 중인 모든 프로세스와 앞으로 실행될 모든 프로세스에 대해서 API 후킹을 하는 것이다. 새로운 프로세스가 생성되려면 kernel32.CreateProcess API를 사용해야 한다. 프로세스를 실행시키는 다른 API들도 결국 내부적으로는 CreateProcess API를 호출한다. 따라서 현재 실행중인 모든 프로세스에 stealth.dll을 인젝션하고, stealth.dll에서 CreateProcess() API까지 같이 후킹하면 이후 실행되는 프로세스에게도 자동으로 stealth.dll을 인젝션 하도록 만들 수 있다. 

 모든 프로세스는 부모 프로세스(보통 explorer.exe)에서 CreateProcess를 이용하여 생성해주기 때문에 부모 프로세스의 CreateProcess API를 후킹하여 자식 프로세스에게 stealth.dll을 인젝션하도록 만들면 된다.


CreateProcess API를 후킹할 때는 kernel32.CreateProcessA(), kernel32.CreateProcessW() 두개의 API를 각각 후킹해야 한다.(ASCII 버전과 유니코드 버전)


허나 CreateProcess API를 후킹하는 것은 안정적이지 못하다. API를 후킹할때는 최대한 Low level의 API를 후킹하는 것이 좋다. 하여 Ntdll.ZwResumeThread() API를 후킹할 것이다. 글로벌 후킹을 하기 위해 이 API를 후킹하는 것이 현재 상황에서 가장 적합하다. 허나 ZwResumeThread API는 undocumented API라서 언제 바뀔지 알수가 없으므로, 안정성을 보장할 수 없다. 그렇기에 본 실습의 소스코드에서는 CreateProcessA, CreateProcessW API를 각각 후킹한다.


이제 소스코드 분석을 하게 될것이다.




위 함수는 HideProc.cpp 파일의 InjectAllProcess 함수이다. 소스코드를 보면 실행중인 모든 프로세스를 검색하여 각각 DLL 인젝션/이젝션을 수행한다. PID 100이하의 프로세스는 시스템 프로세스가 다수 있기 때문에 시스템의 안정성을 위해 DLL 인젝션을 하지 않는다.





위 소스코드는 stealth.dll 파일의 dllmain 함수이다. 소스코드를 보면 프로세스에 DLL 인젝션이 되었을때, CreateProcessA, CreateProcessW, ZwQuerySystemInformation API를 각각 후킹을 한다. 그리고 DLL 이젝션이 될때 각 API를 언후킹 해준다.




 위 함수는 ZwQuerySystemInformation 함수를 후킹하여 사용하게 될 함수이다. STR_HIDE_PROCESS_NAME 변수는 숨기고자 하는 프로세스의 이름이 들어있는 변수이다. 그래서 소스코드를 읽어보면 모든 프로세스에 대해서 숨길 프로세스의 이름과 같은지 비교하고 이름이 같다면 ZwQuerySystemInformation 함수의 결과에서 숨길 프로세스의 내용을 지운다. 위 함수의 기본적인 순서는

unhook -> ZwQuerySystemInformation 실행 -> 다시 hook

위 순서를 따른다.




CreateProcess API의 후킹 함수의 경우 전체적인 unhook->정상 코드 실행-> 다시 hook의 뼈대는 같고 프로세스를 실행할때 dll을 인젝션 해준다. 


7바이트 코드패치

 다음은 일반적인 API의 시작 코드부분이다.


API의 최상위 부분을 보면 MOV EDI,EDI가 있는 것을 알 수 잇다. 이는 실행을 해도 아무의미가 없는 명령이다. 7바이트 코드 패치는 MOV EDI, EDI 명령과 이 명령위의 5개의 NOP 명령 총 7바이트를 패치하여 후킹을 시도하는 방식이다.


위 7바이트를 패치할 경우 위의 5바이트 패치와는 다르게 코드를 실행할때 API의 주소+2의 위치를 호출하면 정상적인 API 코드를 실행할 수 있으므로 unhook, hook의 과정을 거칠 필요가 없다. 


대부분의 API는 함수 최상위 부분이 5개의 NOP과 MOV EDI,EDI로 구성 되어 있으나 그렇지 않은 API의 경우 7바이트 코드 패치를 적용시킬수 없다. 그렇기에 이러한 경우에는 5바이트 코드패치를 하는 수밖에 없다.



'리버싱핵심원리' 카테고리의 다른 글

Reverse Engineering Chap 34  (0) 2016.01.27
Reverse Engineering Chap 3-6  (0) 2016.01.27
Reverse Engineering Chap 32  (0) 2016.01.24
Reverse Engineering Chap 28  (0) 2016.01.24
Reverse Engineering Chap 25~26  (0) 2016.01.20
Posted by 키흐
2016. 1. 24. 20:54

32장은 실행중인 프로세스의 IAT를 변경하여 API를 후킹하는 방식을 설명한다.


DLL의 함수들은 함수의 간접적으로 호출된다. 위 그림에서는 user32.dll의 SetWindowTextW 함수를 호출하는 장면이다. CALL DWORD PTR DS:[01001110]을 실행하고, 주소 01001110에는 user32.dll 내부의 SetWindowTextW 함수의 주소인 7E42960E가 저장되어있다.


프로세스 실행중 동적으로 IAT를 변경하여 간단하게 함수를 후킹할 수 있다. 후킹하려는 함수를 가리키는 곳(위에서는 01001110)에 저장된 함수의 주소를 우리가 새로 만든 DLL의 후킹용 함수로 변경하면 된다.


예제 소스

이 DLL은 계산기의 결과창에 출력되는 숫자를 변경한다. 변경된 숫자는 원본 숫자의 모든 자리의 수에 1을 더한 값이다.(1->2, ... 8->9, 9->0) 숫자를 출력하는 함수가 SetWindowTextW이기 때문에 이 함수를 후킹하는 것이 목적이다.


위의 예제는 3개의 함수로 구성되어있다.

main 함수는 user32.dll의 SetWindowTextW 함수의 주소를 가져오고, IAT를 변경하는 hook_iat 함수를 호출한다.

MySetWindowTextW 함수는 후킹되는 함수인 SetWindowTextW 함수 대신 실행되고, 자신의 코드를 실행한 후 SetWindowTextW 함수를 실행한다. 이 함수는 인자로 전달된 숫자의 모든 자리에 1을 더한다.

hook_iat 함수가 API 호출시 참조하는 곳을 변경한다. 이 함수가 IAT 후킹을 하기 위해 필요한 함수이다.


hook_iat 함수의 작동 방식은 아래와 같다.

  1. 프로세스에서 PIMAGE_IMPORT_DESCRIPTOR의 위치를 찾는다.

  2. 그중에서 user32.dll에 해당하는 것을 찾는다.

  3. PIMAGE_THUNK_DATA를 찾는다.

  4. PIMAGE_THUNK_DATA에서 SetWindowTextW의 주소가 저장된 곳을 찾는다.

  5. 그곳의 값을 새로운 함수인 MySetWindowTextW의 주소로 변경한다.

프로세스의 IAT에 저장된 값을 변경하는 것은 위의 5개 과정을 거친다. 이 과정을 이해하기 위해서는 PE구조와 DLL의 작동 박식을 먼저 이해해야한다.

아래는 위 과정에 대한 자세한 설명이다.

1. 프로세스에서 PIMAGE_IMPORT_DESCRIPTOR의 위치를 찾는다.

GetModuleHandle 함수로프로세스의 핸들을 가져온다. 인자로 NULL을 전달하면 함수를 실행한 프로세스의 핸들을 가져온다. 따라서 DLL이 인젝션된 후 실행되면 대상 프로세스의 핸들을 가져오게 된다.

46번 줄에서 pAddr은 프로세스의 ImageBase를 가진다.

48번 줄에서 60(0x3C)바이트 를 더해서 pAddr은 NT 헤더를 가리킨다.

50번 줄에서 dwRVA는 IMPORT TABLE의 위치(RVA)를 가리킨다.

52번 줄에서 최종적으로 pImportDesc가 IMPORT TABLE을 가리킨다.


2. user32.dll을 찾는다.

테이블의 각 원소를 hook_iat의 인자로 받은 user32.dll(szDllName)와 비교하여 user32.dll의 IID를 찾는다.


3. PIMAGE_THUNK_DATA를 찾는다.

59번 줄의 연산으로 인해 pThunk에 user32.dll의 IAT가 저장된다.


4. SetWindowTextW의 주소가 저장된 곳을 찾는다.

pfnOrg는 hook_iat의 인자로서 후킹하려는 함수의 주소가 저장되어있다. 이경우 SetWindowTextW가 저장되어있다.


5. 후킹하려는 함수의 주소를 새로운 함수의 주소로 변경한다.

71번 줄에서 IAT를 변경한다. 함수의 주소를 새로운 함수의 주소로 변경한다. 이를 위해서는 66번 줄처럼 VirtualProtect 함수를 사용하여 수정하려는 곳의 속성을 쓰기 가능하도록 변경한다. 변경 이후에는 73번 줄처럼 원래대로 복구한다.


DLL 인젝션을 실행하면 아래와 같은 결과를 볼 수 있다.

'리버싱핵심원리' 카테고리의 다른 글

Reverse Engineering Chap 3-6  (0) 2016.01.27
Reverse Engineering Chap 33  (0) 2016.01.27
Reverse Engineering Chap 28  (0) 2016.01.24
Reverse Engineering Chap 25~26  (0) 2016.01.20
Reverse Engineering Chap 24  (0) 2016.01.20
Posted by 지환태
2016. 1. 24. 19:08

Chap28 어셈블리 언어를 이용한 Code 인젝션

1.ThreadProc() 작성

   마지막으로 생성한 코드를 잘 저장합니다.

2.인젝터 제작

  2.1 ThreadProc() 함수의 바이너리 코드 얻기

       이 영역을 선택한 후 마우스 우측 메뉴의 ‘Copy - To file' 항목을 눌러서 file로 저장합니다.

      텍스트 파일을 편집합니다. 불필요한 부분을 제거하고 모든 바이트마다 ‘0x’ 표시를 붙여주고 ‘,’로 연결합니다.

   2.2 Codelnjection2.cpp

3.Codelnjection2.exe 실행


'리버싱핵심원리' 카테고리의 다른 글

Reverse Engineering Chap 33  (0) 2016.01.27
Reverse Engineering Chap 32  (0) 2016.01.24
Reverse Engineering Chap 25~26  (0) 2016.01.20
Reverse Engineering Chap 24  (0) 2016.01.20
Reverse Engineering Chap 18-20  (0) 2016.01.17
Posted by 알 수 없는 사용자
2016. 1. 20. 22:25

chap25. PE 패치를 이용한 DLL 로딩


25.1. 목표 & 실습파일

이전까지는 실행 중인 프로세스에 DLL를 강제로 인젝션하는 방법이였다면, 이번 챕터에서는 실행파일을 직접 수정하여 DLL을 강제로 로딩하는 방법에 대한 내용이다.

아래의 TextView.exe 파일을 수정하여 실행 시 myhack3.dll을 로딩하도록 만드는 것이 최종 목표이다.



myhack3.dll

 

TextView.exe

 

TextView.exe 파일은 간단한 텍스트 뷰어이며, 원하는 파일을 마우스로 드롭하면 열 수 있다.

아래는 PEView를 이용하여 TextView.exe 실행파일의 IDT(Import Directory Table)를 살펴본 화면이다.

KERNEL32.dll, USER32.dll, GDI32.dll, SHELL32.dll 을 임포트하고 있다.


25.2. 소스코드 - myhack3.cpp

- DLLMain()

DLLMain()에서는 ThreadProc() 스레드를 실행시키고, ThreadProc() 에서는 DownloadURL()과 DropFile() 함수를 호출한다.

- DownloadURL()

DownloadURL() 함수는 szURL에 명시된 인터넷 파일을 다운받아서 szFile 경로에 저장하는 기능을 수행한다. (이 예제에서는 www.google.com 사이트에서 index.html 파일을 받아옴)

- DropFile()

DropFile() 함수는 다운받은 index.html 파일을 현재 프로세스에 드롭시켜서 그 내용을 보여준다. 즉, 이후에 TextView.exe 파일에 myhack3.dll을 임포트시키게 되면 TextView.exe 파일에서는 index.html 파일을 보여주게 된다.

- dummy()

dummy()함수는 myhack3.dll 파일에서 외부로 서비스하는 Export함수이다. 코드에서 보면 함수내에서 아무런 기능도 하지 않는 것을 확인할 수 있다. 아무런 기능을 하지 않는 함수를 추가한 이유는 TextView.exe 파일의 임포트 테이블에 myhack3.dll이 추가될 수 있도록 형식적인 완전성을 갖추기 위해서이다. PE 파일에서 DLL을 임포트한다는 것은 파일의 코드 내에서 그 DLL이 제공하는 Export 함수를 호출한다는 뜻이기 때문에 DLL 파일은 최소한 하나 이상의 Export 함수를 제공해야 한다.


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

25.1에서 알아보았듯이 '.rdata' 섹션에 IDT가 있고, IDT는 IMAGE_IMPORT_DESCRIPTOR 구조체 배열로 이루어져 있다. (마지막은 NULL 구조체임)

위와 같이 IID 구조체의 크기는 14(hex)이며, 실제로 TextView.exe 에서도 IID 영역이 RVA:84CC~852F 이고 5개의 IID가 있으므로 각 IID의 크기는 14, 전체 크기는 64임을 알 수 있다.

PEView에서 주소 보기 옵션을 'File Offset'으로 변환하면 다음과 같이 IDT의 파일 오프셋은 76CC이다.

HxD 유틸리티에서 TextView.exe 파일을 열어 76CC~772F 영역을 확인하면 다음과 같다.

IDT바로 다음 부분은 다른 데이터가 존재하기 때문에 myhack3.dll을 위한 IID 구조체를 덧붙일 수 없는 것을 확인할 수 있다. 따라서 IDT 전체를 다른 위치로 옮긴 후 새로운 IID를 추가시켜야 한다.

<새로운 위치를 선택하는 방법>

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

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

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

첫번째 방법으로 해결하기 위해 '.rdata'에서 빈 공간을 찾으면 다음과 같다.

RVA:8C60~8DFF 영역이 Null-Padding 영역으로 보이는데, 진짜 Null-Padding 영역인지는 헤더를 통해 확인해야 한다.

'.rdata'의 헤더를 보면 Size of Raw Data(파일의 크기)는 2E00 이지만, Virtual Size(메모리의 크기)는 2C56 인 것을 알 수 있다. 즉, 뒷 부분의 1AA(2E00 - 2C56) 영역은 사용되지 않는 부분이기 때문에 이 위치에 IDT를 재구성하는 것이 문제가 되지 않는다는 것을 알 수 있다.

'.rdata'의 1AA만큼의 영역을 HxD 유틸리티를 이용하여 확인하면 다음과 같다.


25.4. TextView.exe 패치 작업

<Import Table의 RVA 값 변경>

기존의 TextView.exe 파일을 복사하여 새로운 이름 TextVeiw_Patch.exe를 하나 만들고, TextView.exe는 PEView로 열어서 PE 정보를 확인하고, TextView_Patch.exe는 HxD로 열어서 실제로 값을 변경시키는 방식으로 진행한다.

기존에 Import Table의 위치는 84CC(RVA)이고, 사이즈는 64이다.

이를 RVA 위치를 8C80, 크기를 78(64+14)로 변경한다.


<Bound Import Table 제거>

IMAGE_OPTIONAL_HEADER에 Bound Import Table이 있는데, 이는 DLL 로딩 속도를 향상시킬 수 있는 기법이다. 이 테이블은 옵션이기 때문에 반드시 존재할 필요가 없다. 따라서 0으로 변경하면 된다. (존재하지 않는 것은 상관 없지만, 존재하면서 정보가 잘못 입력되어 있으면 실행시 에러가 발생한다.) TextView.exe 파일은 이미 0으로 설정되어 있기 때문에 변경할 필요가 없다.


<새로운 IDT 생성>

기존의 IDT(RAW : 76CC~772F)를 복사하여 새로운 IDT 위치(RAW : 7E80)에 덮어쓴다.


<Name, INT, IAT 세팅>

이 상태에서 새로 생성한 IDT의 마지막 구조체 영역에(RAW : 7ED0)에 myhack3.dll을 위한 IID를 구성하여 추가시켜야 한다.

다음 그림과 같이 추가시킬 것이다. RVA 주소를 다음과 같이 정한 이유는 해당 공간이 비어있고 IID 바로 다음 영역이기 때문이다.


HxD의 왼쪽 파란색 글씨는 파일 오프셋이고, 빨간 글씨로 수정한 부분은 RVA를 입력한 것이므로 RVA와 RAW를 구분할 필요가 있다.

여기서 INT, Name, IAT의 주소는 빈 공간에 임의로 설정한 것이다. 따라서 해당 공간에는 정해진 형식에 따라 데이터를 채워야 한다. INT(Import Name Table)은 RVA 배열인데, 각 원소는 IMAGE_IMPORT_BY_NAME 구조체의 주소이다. IAT(Import Address Table) 또한 RVA 배열이며, 각 원소는 INT와 같은 값을 가져도 된다. INT가 정확하다면 IAT는 다른 값을 가져도 상관 없다. 어차피 메모리상에서 IAT의 영역은 PE로더에 의해 실제 함수 주소로 덮여 써지게 된다.

형식에 맞게 데이터를 추가시키면 다음과 같다.


<IAT 섹션의 Characteristics 변경>

IAT는 PE로더에 의해서 메모리에 로딩될 때 실제 함수 주소로 덮어 쓰여지게 되므로 해당 섹션은 반드시 WRITE 속성을 가져야 한다. '.rdata' 섹션의 헤더를 보면 다음과 같다.

Characteristics 속성에 IMAGE_SCN_MEM_WRITE (80 00 00 00)을 bit OR로 추가하게 되면 최종 Characteristics는 C0 00 00 40이 된다.


25.5. 검증

새로 만든 TextView_Patch.exe 파일을 PEView로 열어서 IDT를 확인하면 다음과 같이 myhack3.dll이 등록된 것을 확인할 수 있다.

또한 INT에 dummy() 함수가 추가된 것을 확인할 수 있다.

TextView_Patch.exe를 실행시켰을 때, Process Exploler로 프로세스를 살펴보면 myhack3.dll이 로딩되어 있는 것을 확인할 수 있다.



Chap26. PE Tools

다운로드 경로 : http://uinc.ru/files/neox/PE_Tools.shtml

PE_Tools.zip


26.1. 프로세스 메모리 덤프

리버싱에서 덤프(Dump)라는 것은 메모리의 내용을 그대로 파일로 저장시킨다는 뜻이다. 실행 압축된 프로그램의 경우 덤프롤 통해 내부 문자열 등을 빠르고 간단하게 확인할 수 있다는 장점이 있다.

화면 구성은 위의 사진과 같이 실행중인 프로세스가 상단에 위치하고, 프로세스를 선택하면 해당 프로세스에 로딩된 DLL이 하단에 표시된다. 프로세스를 우클릭하면 세가지 종류의 덤프를 선택할 수 있다.

<Dump Full>

메모리에 로딩되었을 때의 모습인 PE Image를 모두 덤프한다.(프로세스의 PE 헤더를 검사하여 ImageBase 주소에서부터 SizeOfImage 크기만큼)

<Dump Partial>

사용자가 지정하는 주소에서부터 지정하는 크기만큼 덤프한다.

<Dump Region>

프로세스 메모리(유저 영역)의 모든 분할 영역이 표시되며 state가 COMMIT인 메모리 영역에 대해 덤프 작업을 수행할 수 있다.


26.2. PE Editor

PE 파일을 수동으로 패치할 때에는 PE 헤더 정보를 수정해야 하는데, 이 때 PE Editor 기능을 사용하면 편리하다.

PE 파일을 드래그하거나 Tools - PE Editor 항목을 선택하여 이용할 수 있다.


'리버싱핵심원리' 카테고리의 다른 글

Reverse Engineering Chap 32  (0) 2016.01.24
Reverse Engineering Chap 28  (0) 2016.01.24
Reverse Engineering Chap 24  (0) 2016.01.20
Reverse Engineering Chap 18-20  (0) 2016.01.17
Reverse Engineering Chap 23  (0) 2016.01.17
Posted by 알 수 없는 사용자
2016. 1. 20. 14:16

Chap 24 DLL 이젝션


챕터를 시작하기 앞서 이번 챕터를 공부할 때 있으면 좋은 툴은 ProcessExplorer라는 툴이다.

 

procexp.exe

 

위 툴은 각 프로세스의 PID, 그리고 그 프로세스가 로드한 DLL 파일 목록을 알 수 있어 DLL 인젝션을 하는데 용이하다.

다음은 DLL ejection 실습에 사용한 파일들이다.

 

eject.exe

 

inject.exe

 

myhack.dll

 


 앞 챕터에서 배웠던 CreateRemoteThread() API를 이용한 DLL 인젝션의 동작 원리는 다음과 같다.

"대상 프로세스로 하여금 LoadLibrary() API를 호출하도록 만드는 것"

 이와 비슷하게 DLL 이젝션의 동작 원리는 다음과 같다.

"대상 프로세스로 하여금 FreeLibrary() API를 호출하도록 만드는 것"


DLL 이젝션의 소스코드는 다음과 같다.





위 소스코드에서 FreeLibrary() API를 호출하도록 하는 함수가 바로 EjectDll() 함수이다. 이 함수에 대해서 살펴 볼 것이다.


CreateToolhelp32Snapshot() API를 이용하면 프로세스에 로딩된 DLL의 정보를 얻을수 있다. hSnapshot 변수에는 이러한 모듈들의 정보가 들어가게 된다. 여기서 Module32First(), Module32Next() 함수에 넘겨주는 MODULEENTRY32 구조체에 해당 모듈의 정보가 세팅이 된다. 이 구조체의 szModule 멤버는 DLL의 이름을 의미한다.


그 후 PID를 이용해서 OpenProcess 함수를 통해 프로세스의 핸들을 구할 수 있다. hProcess 변수는 notepad.exe 프로세스의 핸들이다. 

FreeLibrary() API는 kernel32.dll 모듈에 속해 있기 때문에 먼저 kernel32.dll 의 핸들을 구하기 위해 GetModuleHandle 함수를 이용하여 kernel32.dll의 핸들을 구한다. 그 후

GetProcAddress 함수를 통해 FreeLibrary() 의 주소를 알아낸다.


이제 notepad.exe 프로세스의 핸들, FreeLibrary() API의 주소를 이용하여 CreateRemoteThread 함수를 통해 FreeLibrary() API를 실행한다.


이제 DLL Ejection 실습을 할 것이다.

 

ProcessExplorer의 화면이다. notepad.exe 프로세스에 myhack.dll을 인젝션하여 하단의 dll 목록에서 myhack.dll이 있음을 알 수 있다. injection을 하는 방법은 앞의 23챕터에서 다루었으므로 생략한다.

 

eject.exe 파일을 실행한다.  

 

ejection에 성공하여 dll 목록에서 myhack.dll 이 사라졌음을 알 수 있다.

'리버싱핵심원리' 카테고리의 다른 글

Reverse Engineering Chap 28  (0) 2016.01.24
Reverse Engineering Chap 25~26  (0) 2016.01.20
Reverse Engineering Chap 18-20  (0) 2016.01.17
Reverse Engineering Chap 23  (0) 2016.01.17
Reverse Engineering Chap 14~15  (0) 2016.01.14
Posted by 키흐
2016. 1. 17. 20:58

Chap 18 UPack PE 헤더 상세 분석

1. UPack 설명 

   UPack은 중국의 dwing이라는 사람이 만든 PE 패커 입니다.

Upack039.7z

    참고: UPack을 상세 분석하려면 시스템에 동작 중인 Anti-Virus 제품의 ‘실시간 감시 ’ 기능을 꺼놓고 하기 바랍니다(대부분의 AV 제품에서 UPack을진단/삭제해 버립니다). 향후분석이 다끝나면 다시 실시간 감시 기능을 커기 바랍니다.

2. UPack으로 notepad.exe 실행 압축하기

  먼저 적절한 곳에 upack. exe와 notepad. exe를 복사합니다. 

  커밴드창에서 그림와같이 명령을 내립니다(원래 몇 가지 실행 옵션이 있지만 그냥 default옵션으로도 충분합니다).

  *UPack은 원본 파일 자체를 실행 압축하며 따로 백업하지 않으니,중요 파일을 실행 압축하기 전에는 꼭 미리 백업해두기 바랍니다.

3. Stud_PE 이용

   최강의 PE Viewer 유틸리티 인 PEView가 정상 동작하지 않으니 비슷한 다른 유틸리티 인 Stud PE를 소개합니다.(필자가 이 책을 썼을 때 이렇게 설명했지만 지금 PE Viewer도 사용할 수 있습니다)

Stud_PE.zip

   PE View보다는좀 더 복잡한화면 구성이지만나름장점이 많은유틸리티입니다(UPack도 잘 보이구요).

    그림은 Stud_PE의 실행 화면입니다.

4. PE 헤더 비교

   Hex Editor로 두 파일 (notepad.exe ,notepad_upack. exe)의 헤더 부분을 비교해서 보겠습니다.

    4.1 notepad.exe(원본)의 PE 헤더

    평범한 PE 헤더의 모습입니다. lMAGE_DOS_HEADER, DOS Stub, lMAGE_NT_HEADERS,lMAGE_SECTION_HEADER 순으로 전형 적 인 PE 헤더 를 보여주고 있습니다.

    4.2 notepad_upack.exe(실행 압축)의 PE 헤더

    MZ, PE 시그니처가 매우 가깝게 붙어있고,DOS Stub은 아예 없어졌고,여러 문자열이 보이고,중간에 코드가 존재하는 것 같기도 하고말이죠.한마디로 수상한 점투성이 입니다.

5. UPack의 PE 헤더 분석

    5.1 헤더 겹쳐쓰기

        헤더(IMAGE_DOS_HEADER)와 PE 헤더(IMAGE_NT HEADERS)를 교묘하게 겹쳐쓰는 것입니다.

        Stud_PE를 이용해서 MZ 헤더 를 살펴보겠습니다.‘Headers’ 탭의 [Basic HEADERS tree view in hexeditor] 버튼을 누릅니다.

(제 자신의 캡처,환경은:win7 32bit)

(필자의 캡처,환경은:win xp)

          MZ 헤더 (IMAGE_DOS_HEADER)에서 아래 2가지 멤버가 중요합니다.

          그외 나머지는 크게 중요하지 않습니다(프로그램 실행에 아무 의미가 없습니다).

        문제는 PE File Format의 스펙 에 따라서 lMAGE_NT_HEADERS의 시작위치가‘가변적’이라는 것입니다.즉 e_lfanew의 값에 따라서 lMAGE_NT_HEADERS의 시작 위치가 결정됩니다. 보통 정상적인 프로그램에서는 e_lfanew 값은(빌드 환경마다 틀리긴 하지만) 아래와 같습니.

        UPack에서는 e_lfanew 값이 10입니다. PE 스펙에 어긋나진 않았습니다. 그냥 스펙 자체의 허술함을 이용한 것이지요. 이런 식으로 MZ 헤더와 PE 헤더의 겹쳐쓰기가 가능해집니다.

    5.2 IMAGE_FILE_HEADER.SizeOfOptionaIHeader

        lMAGE_FILE_HEADER.SizeOfOptionalHeader의 값을 변경해서 헤더 안에 디코딩 코드를 삽입할 수 있습니다.

        값의 의미는 PE 헤더에서 바로 뒤따르는 lMAGE_OPTIONAL_HEADER 구조체의 크기 (E0) 입니다. UPack은 이 값을 그림와 같이 148로 변경합니다(아래 그림에서 박스로 표시된 부분).

        UPack의 특징은 기본적으로 PE 헤더를 파배기처럼 꼬아놓고 헤더 안에 디코딩에 필요한 코드를 적절히 끼워 넣는 것입니다. SizeOfOptionalHeader 값을 늘리면 lMAGE_OPTIONAL_HEADER와 lMAGE_SECTION_HEADER 사이에 추가적인 공간을 확보할 수 있습니다.UPack은 바로 이 영역에 디코딩 코드를 추가합니다. PE 헤더에 대한 일반상식을 뛰어넘는 교묘한 방법입니다.

         실제로 문제의 영역을 살펴보겠습니다. lMAGE_OPTIONAL_HEADER의 끝은 D7이고,lMAGE_SECTION_HEADER 시작은 170입니다.이 사이 영역을 Hex Editor로 보면 그림과 같습니다.

  5.3 IMAGE_OPTIONAL_HEADER.NumberOfRvaAndSizes

        lMAGE_OPTIONAL_HEADER.NumberDfRvaAndSizes 값을 변경해서 자신의 코드를 삽입할 수 있습니다.

        이 값의 의미는 바로 뒤에 이어지는 lMAGE_DATA_DIRECTORY 구조체 배열의 원소 개수를 나타납니다. 정상적인 파일에서는 lMAGE_DATA_DIRECTORY 배열의 원소 개수는 10개이지만,UPack에서는 A개로 변경됩니다(그림의 박스 영역 참고).

        lMAGE_DATA_DIRECTORY 구조체 배열의 각 항목들을 표에 설명하였습니다.

        굵은 이댈릭제로 쓰여진 항목들은 잘못 변경했을 때 실행에러가 발생하는 항목들입니다.

       UPack은 lMAGE_OPTIONAL_HEADER. NumberOfRvaAndSizes 값을 A로 변경하여 LOAD_CONFIG 항목(파일 옵셋 D8 이후)부터는 시용하지 않습니다. 그리고 바로 그 무시된 lMAGE_DATA_DlRECTORY 영역에 자신의 코드를 덮어써버렸습니다. 정말 UPack은 헤더의 1바이트까지 알뜰하게 사용하는군요.

       hex editor로 lMAGE_DATA_DlRECTORY 구조체 배열 영역을 보도록 하겠습니다.

        그림에서 흐리게 표시된 부분이 정상파일의 IMAGE_DATA_DlRECTORY 구조체 배열 영역이고,그 밑으로 진하게 표시된 부분이 UPack에서 무시되는 부분입니다(D8~107 영역 = LOAD_CONFIG Directory 이후). 무시 되는 영역을 디버거로 확인하면 UPack 자체의 디코딩 볼 수 있습니다.

      참고로 NumberOfRvaAndSizes 값이 변경되면 OllyDbg에서 파일을 열 때 그림와 같은 에러 메시지가 나타납니다.

      OllyDbg의 PE 파일 검증 과정에서 NumberOfRvaAndSizes 값이 10인지 체크하는데,중요한 메시지는 아니므로 무시해도 됩니다. 별도의 Plugln을 사용하면 아예 없앨 수도 있습니다. 참고하기 바랍니다.

  5.4 IMAGE SECTION HEADER

       lMAGE_SECTION_HEADER 구조체에서 프로그램 실행에 사용되지 않는 항목들에게 UPack 자신의 데이터를 기록합니다.

       Hex Editor로 lMAGE_SECTION_HEADER 구조체를 보겠습니다(용셋 170~lE7 영역).

       그림에서 보이는 영역을 lMAGE_SECTION_HEADER 구조체에 맞게 보기 좋게 뽑아보면 다음과 같습니다.

       위에 박스로 표시된 구조체 멤버들은 프로그램 실행에 아무런 의미 없는 멤버들입니다. 일례로 파일 옵셋 1B0 위치에 있는 offset to relocations의 값 0100739D는 원본 notepad.exe의 EP(Entry Point) 값입니다.

   5.5 섹션 겹쳐쓰기

        UPack의 주요 특징 중 하나가 바로 섹션과 헤더를 마구 겹쳐쓰는 것입니다.

        간략한 보기 를 제공하는 Stud_PE를 이용해서 UPack의 lMAGE_SECTION_HEADER를 살펴보겠습니다. Stud PE의 ‘Sections’탬을 선택합니다.

        UPack은 PE 헤더,첫 번째 섹션,세 번째 섹션이 겹쳐 있습니다.

        숫자만 봐서는 이게 무슨 의미인지 잘 와닿지 않습니다.이해를 돕기 위해서 그림을 살펴봅시다.

        왼쪽은 파일에서의 섹션 정보를,오른쪽은 메모리에서의 섹션 정보를보여 주고 있습니다.

        파일의 헤더(첫째/셋째 섹션) 영역의 크기는 200 입니다. 사실 매우 작은 크기입니다. 반면 두 번째 섹션(2nd Section) 영역의 크기 (AE28)는 파일의 대부분을 차지할 정도로 큽니다. 바로 이곳에 원본 파일(notepad.exe)이 압축되어 있습니다.

        또 하나 주목해야 하는 부분은 메모리에서의 첫 번째 섹션 영역입니다. 섹션의메모리 크기는 14000 입니다. 이는 원본 파일 (notepad.exe)의 Size of Image와 같은 값입니다. 즉 두 번째 섹션에 압축된 파일 이미지를 첫 번째 섹션에 (notepad의 메모리 이미지) 그대로 압축해제하는 것입니다.

        압축이 해제된 첫 번째 섹션은 그림과 같습니다.

        디시 한 번 정리하변 메모리 두 번째 섹션 영역에 압축된 notepad가들어 있고,압축이 풀리면서 첫 번째 섹션 영역에 기록됩니다. 중요한 건 notepad.exe(원본파일)의 메모리 이미지가 통째로 풀리기 때문에 프로그램이 정상적으로 실행될 수 있습니다(주소가 정확히 일치하게 됩니다).

   5.6 RVA to RAW

        RVA → RAW 변환방법은 아래와 같습니다.

        UPack의 EP는RVA 1018입니다.

        RVA 1018은 1st Section에 존재합니다. 따라서 공식에 그대로 대입하면 아래와 같습니다.

        RAW28 영역을 hex editor로 살펴보겠습니다.

       RAW 28 영역은 코드가 아니라 (ordinal:0l0B) “LoadLibrary A" 문자열 영역입니다.

         일반적으로 섹션 시작의 파일 옵셋을 가리키는 PointerToRawData 값은 FileAlignment의 배수가 되어야 합니다. UPack의 FileAlignment는 200이므로 PointerToRawData 값은 0, 200, 400, 600 등의 값을 가져야 합니다. 따라서 PE 로더 는 첫 번째 섹션의 PointerToRawData(10)가 FileAlignment(200)의 배수가 아니므로 강제로 배수에 맞춰서 인식합니다(이 경우는 0).

        정상적인 RVA → RAW 변환은 아래와 같습니다.

        디버거로 해당 영역의 코드를 살펴보겠습니다.

   5.7 lmport Table(lMAGEJMPORT_DESCRIPTOR array)

        UPack의 Import Table은 매우 특이하게 구성되어 있습니다.

        Hex Editor를 이용해서 lMAGE_IMPORT_DESCRIPTOR 구조체를 따라가보겠습니다.

        여기가 바로 UPack의 섹션을 이용한 트릭 이 숨겨진 곳입니다.

        PE 스펙에 따르면 Import Table은 lMAGE_IMPORT_DESCRIPTOR 구조체 배열로 이루어지고 마지막은 NULL 구조체로 끝나야 합니다.그림를 보면 선택된 영역이 Import Table을 나타내는lMAGE_IMPORT_DESCRIPTOR 구조제 배열입니다. 1EE~201 옵셋까지 첫 번째 구조체이며, 그 뒤는 두 번째 구조제도 아니고,그렇다고(Import Table의 끝을 나타내는) NULL 구조체도 아닙나다.

        얼핏 보면 이는 분명 PE 스펙에 어긋난 듯이 보입니다. 하지만 그림 18.24의 200 옵셋 위에 있는 굵은선을 주목하기 바랍나다. 이 선은 파일에서 세 번째 섹션의 끝을 의미합니다. 따라서 실행할 때 200 옵셋 이하는 세 번째 섹션 메모리에 매핑되지 않습니다. 그림을 봅시다.

        세 번째 섹션이 메모리에 로딩될 때 파일 옵셋 0~1FF 영역이 메모리 주소27000~271FF에 매핑되고 (세 번째 섹션의 남은 메모리 영역인) 27200~28000 영역은NULL로 채워집니다. 똑같은 영역을 디버거로 확인해보겠습니다.

        다시 PE 스펙의 Import Table 조건으로 돌아가서 01027202 주소 이후부터 NULL 구조체가 나타난다고 본다면 PE 스펙에 어긋나지 않는 셈입니다. 이것이 바로 섹션을 이용한 UPack의 트릭입니다. 파일로 볼 때는 Import Table이 깨진 것처럼 보이지만,실제 메모리에서는 Import Table이 정확히 나타나는것입니다.

   5.8 IAT(lmport Address Table)

        UPack이 어떤 DLL에서 어떤 API를 입포트하는지 실제로 IAT를 따라가서 확인해보겠습니다.

        먼저 Name의 RVA 값은 2 이고, 이는 Header 영역에 속해 있습니다(첫 번째 섹션이 RVA 1000에서 시작하기 때문입니다).헤더 영역에서는 그냥 RVA와 RAW 값이 같으므로 Hex Editor로 파일 옵셋(RAW) 2를 살펴보겠습니다.

        “KERNEL32.DLL" 문자열이 보이는군요.

        IAT 값 11E8은 첫 번째 섹션 영역이므로 IAT의 파일 옵셋은 1E8입니다.

        그림의 박스로 표시된 영역은 IAT이면서 동시에 INT 역할도 하고 있습니다. 즉 이곳은 Name Pointer(RVA) 배열이고 배열의 끝은 NULL입니다. 또 2개의 API를 임포트하는 것을 알 수 있습니다. 각각 RVA 28과 BE입니다.

그림를보면 ‘LoadLibraryA’와 ‘GetProcAddress’ API를 임포트한다는 것을 알 수 있습니다.

Chap 19 UPack 디버깅 -OEP 찾기

   1. OllyDbg 실행 에러

       UPack은 lMAGE_OPTIONAL_HEADER에서 NumberOfRvaAndSizes 값을 A로 변경 (기본 값 10)하기 때문에 OllyDbg는 EP로 가지 못하고, 그림와 같이 ntdll.dll 영역에서 멈춰버립니다.


      이는 어디까지나 OllyDbg의 버그(혹은 엄격한 PE 체크) 때문에 이런 현상이 발생한것이므로 강제로 EP를 설정해줘야 합니다.

        그림을 보면 ImageBase가 0100000이고,EP의 RVA 값이 1018 입니다. 따라서 EP의 VA 값은 01001018입니다.OllyDbg의 Code 창에서 01001018로 이동 한 후 ‘New origin here' 명령을 이용하여 강제로 EIP를 변경합니다.

    2. 디코딩 루프

        모든 패커에는 디코딩 루프(Decoding Loop)가 존재합니다.이러한 디코딩 루프를 디버깅할 때는 조건 분기를 적절히 건너뒤어서 루프를 탈출해야 합니다. 경우에 따라서는 한눈에 루프가 파악되지 않을 수도 있습니다.레지스터를 잘 보면서 어떤주소에 값을쓰고 있는지 잘살펴야 합니다(사실 이건 많은 경험이 필요한 부분입니다).

        처음두 명령은 010011B0 주소에서 4바이트를 읽어서 EAX에 저장하는 명령입니다.EAX는 0100739D 값을 가지는데, 이는 원본 notepad의 OEP입니다.사실이 값이 OEP 인 것을 알고 있다면, 하드웨어 BP를 설치한 후 설행 [F9] 하면 정확히 OEP에서 멈줍니다.

        조금 진행하다 보면 그림과 같은 함수 호출 코드가 나타납니다.

        이때 ESI 값은 0101FCCB 이고, 이게 바로 decode() 함수의 주소입니다. 앞으로 이 함수가 반복적으로 호출될 것입니다. decode() 함수(101FCCB)를 살짝 살펴보겠습니다.

        Steplnto[F7]명령으로 트레이성을 계속 하면 그림과 같은 코드를 만나게 됩니다.

       0101FE57과 0101FE5D 주소에는 EDI 값이 가리키 는 곳에 뭔기를 쓰는 명령어(MOVS, STOS)가 있습니다. 이때 EDI 값은 첫 번째 섹션 내의 주소를 가리 겁니다.즉 압축을 해제한 후 실제 메모리에 쓰는 명령어들입니다.0101FE5E와 0101FE61 주소에는 CMP/JB 명령어를 통해서 EDI 값이 01014B5A가 될 때까지 계속 루프를 돌게 됩니다([ESI]+341 = 01014B5A). 0101FE61 주소가 바로 디코딩 루프의 끝부분입니다. 실제로 이 루프를 반복해서 트레이싱하면 EDI가 가리키는 주소에 어떤 값들이 쓰여지는 것을 볼 수 있습니다.

   3. IAT 세팅

        UPack이 임포트히는 2개 의 함수 LoadLibrary A와 GetProcAddress를 이용하여 루프를 돌면서 원본 notepad의 IAT를 구성합니다.이 과정 이 끝나면 0101FEAF 주소의 RETN 명령어에 의해서 드디어 OEP로 갑니다.

chap 20 인라인 패치실습

   1. 인라인 때치

        인라인 코드 패치(Inline Code Patch) 혹은 줄여서 인라인 패치 (Inline Patch)라고 하는 기법은 원하는 코드를 직접 수정하기 어려울때 간단히 코드케이브(Code Cave)라고 하는 패치 코드를 삽입한 후 실행해 프로그램을 패치시키는 기법입니다. 주로 대상 프로그램이 실행 압축(혹은암호화)되어 있어서 파일을직접 수정하기 어려운 경우 많이 시용되는 기법입니다. 자세한 설명을 위해 그림을 살펴보겠습니다.

    2. 실습 - Patchme

        실행하면 위그림와 같이 메시지 박스가 나타납니다. 메시지를 읽어보니 표시문자열을 변경하라고 합니다. [확인] 버튼을 누르면 밑그림과 같은 다이얼로그가 표시됩니다.

    3. 디버깅 - 코드 흐름 살펴보기

       시행화면에서 보이는 메시지를 찾기 위해서 마우스 우측 메뉴의‘Search for All referenced text strings’를 선택합니다.

      모든 문자열이 암호화되어 있기 때문에 이 상태로는 원히는 문자열을 찾을 수 없습니다.401001 주소의 CALL 명령의 함수(4010E9)를 따라들어가서 조금 진행하다 보면 그림과같은 코드를 만나게 됩니다.

       이 코드가 바로 복호화 루프입니다.4010A3 주소의 XOR BYTE PTR DS: [EBXl ,44 명령을 사용하여 특정 영역 (4010F5~401248)을 XOR 명령으로 복호화합니다.4010C8 주소의 XOR 명령에 의해서 401007~401085 영역이 복호화되고, 다시 4010DB 주소의 XOR 명령에 의해서 4010F5~401248 영역이 복호화됩니다.복호화가 완료한 후에 함수 (401039)로 따라 들어가면 그림과 같은 코드가 나타납니다.

        401046 주소에 있는 Checksum 계산 루프입니다.루프를 종료하면 EDX 례지스터에는 특정한 Checksum 값이 저장됩니다.이 Checksum 계산 영역은 위에서 설명했던 이중으로 암호화되어 있는 영역입니다. 바로 이 영역 에 우리가 패치하려는 문자열이 존재할 것으로 예상됩니다.

      이부분은 OEP 코드 입니다.내용은 다이얼로그를 실행시키는 것 입니다. 40123E 주소의 CALL user32.DialogBoxParamA()명령을 실행하면 다이얼로그가 나타납니다. 아래는 DialogBoxParam( ) API의 정의입니다.

       DialogBoxParam() API 의 4 번째 lpDialogFunc 파라미터가 Dialog Box Procedure 주소(4010F5)입니다.

       박스로 표시된 부분이 우리가 패치해야 할 문자열들 입니다.

   4. 코드구조

대략적인 코드 흐름은 다음과 같습니다.

[EP Code]는 단순히 [Decoding Code]를 호출히는 역할만 하며 , 실제 [Decoding Code] 에서 디코딩 작업이 이루어집니다. [B] - [A] - [B] 순서로 디코딩 (XOR)을 하고 암호가 해제된 [A] 영역 코드를 실행합니다. [A] 영역 코드 내에서 [B] 영역의 Checksum을구하여 [B] 영역의 변경 여부를판별합니다.그런다음 [C] 영역을 디코딩 (XOR)한 디음 마지막으로 OEP(0040121E)로 점프합니다.

   5. 인라인 때치 실습

       우선은 파일의 적절한 위치에 문자열을 패치시키는 코드를 삽입합니다. 패치 코드를 어디에 설치한지 세 가지 정도의 방법이 있습니다.

       ①파일의 빈 영역에 설치

       ②마지막 섹션을 획장한 후 설치

       ③새로운 섹션을 추가한 후 설치

       보통 패치 코드의 크기가 작은 경우 번방법을 사용합니다.

       

       첫 번째 섹션(.text)의 Size of Raw Data는 400이고,Virtual Size는 280입니다.즉 첫 번째 섹션의 (파일에서) 크기는 400이지만, 여기서 280 크기만 메모리에 로딩한다는 얘기이지요. 나머지 영역 (680~800)은 쓰이지 않는 영역입니다. 바로 이곳이 우리가 찾는 빈 영역 (Null-Padding 영역)입니다.

      빈 영역의 파일 옵셋(file offset)은 680~800 입니다. 이를 프로세스 가상 메모리 (VA: Virtual Address)로 변환하면 401280~401400입나다.OllyDbg의 ‘Assemble’ [Space] 명령과 ‘Edit’ [Ctrl+E] 명령을이용하여 아래와같이 편집하세요.

       다음에는 앞에서 만든 패치 코드(코드 케이브)가 실행되도록 파일을 직접 수정해야 합니다.

       여기서 주의할 것은 이 영역 (401083)은 원래 암호화되어 있는 영역이라는 것입니다.이 Instruction(E9 F8 01)을 그대로 쓰면 안 되고 복호화를 고려해서 XOR7 명령을 수행 한 후 써야 합니다.

       마지막으러 결과를 확인합니다.


'리버싱핵심원리' 카테고리의 다른 글

Reverse Engineering Chap 25~26  (0) 2016.01.20
Reverse Engineering Chap 24  (0) 2016.01.20
Reverse Engineering Chap 23  (0) 2016.01.17
Reverse Engineering Chap 14~15  (0) 2016.01.14
Reverse Engineering Chap 16-17  (0) 2016.01.14
Posted by 알 수 없는 사용자
2016. 1. 17. 04:30

프로세스의 DLL 확인

DLL 인젝션을 진행하기 전 프로세스에 로드된 DLL을 확인하는 방법을 설명한다.




프로세스에 로드된 DLL은 올리디버거나 Process Explorer를 이용하여 확인할 수 있다. 올리디버거에서는 E버튼을 클릭하여 열린 Excutable modules 윈도우에서 확인할 수 있다. 그리고 Process Explorer에서는 View > Lower Pane View > DLLs를 클릭하면 하단에서 확인할 수 있다.


DLL 인젝션

DLL 인젝션이란 말 그대로 실행중인 프로세스에 임의의 DLL을 주입하는 것이다. 윈도우즈의 프로세스들은 DLL을 로드할 때 자신의 메모리 영역에 DLL의 코드를 로드한다. 그렇기 때문에 주입된 DLL은 해당 프로세스의 메모리 영역에 접근할 수 있는 권한을 갖게 되고 많은 일을 할 수 있다. 이후부터 DLL 인젝션을 수행하기 위한 방법을 설명한다.


DLL 코드

DLL 인젝션이 성공하여 DLL의 엔트리 포인트인 DllMain함수가 실행되면 간단히 메시지 박스를 띄운다.


원격 스레드 생성

윈도우즈 API를 이용하여 쉽게 DLL 인젝션을 수행할 수 있다.

DLL 인젝션을 수행하는 코드. 인자로 PID와 DLL의 경로를 입력받고 DLL 인젝션을 수행한다.

OpenProcess 

VirtualAllocEx

WriteProcessMemory

GetModuleHandle

GetProcAddress

CreateRemoteThread

DLL 인젝션은 위의 표에 있는 API만 이용하면 수행할 수 있다. CreateRemoteThread함수가 DLL 인젝션에 핵심적인 역할을 하며 나머지 함수들은 모두 CreateRemoteThread함수를 위해 사용된다. 이 함수를 중심으로 코드를 분석해보자.


26번째 줄의 CreateRemoteThread함수는 다른 프로세스에 특정한 스레드를 실행하기 위해 사용된다. 인자로 전달되는 process, threadProc, buffer의 용도를 살펴보자.


process가 사용된 곳의 인자에는 프로세스의 핸들이 전달되어야한다. 16번째 줄에서 인자로 쓰였던 process 변수가 정의된다. OpenProcess함수를 이용하여 프로그램에 인자로 전달했던 pid로부터 프로세스의 핸들을 가져온다.


threadProc가 사용된 곳의 인자에는 스레드로 실행시킬 함수의 포인터가 전달되어야한다. 23, 24번째 줄에서 kernel32.dll의 LoadLibraryW 함수의 포인터 주소를 가져오고 LoadLibraryW 함수의 포인터가 threadProc에 저장된다.


buffer가 사용된 곳의 인자에는 스레드로 실행되는 함수에 전달될 인자가 전달되어야한다. 주의할 점은 다른 프로세스에서 LoadLibraryW 함수가 실행되기 때문에 전달되는 인자는 다른 프로세스의 환경에 맞춰주어야 한다는 것이다. 18, 21번째 줄에서 이 작업을 한다. VirtualAllocEx 함수로 다른 프로세스에 메모리 영역을 동적으로 할당하고, 이 영역에 WriteProcessMemory 함수를 이용하여 인자로 전달 될 DLL의 경로를 메모리에 기록한다.


과정을 요약하면 다음과 같다.

  1. OpenProcess 함수로 DLL 인젝션을 수행되는 프로세스의 핸들을 가져온다.
  2. VirtualAllocEx 함수로 DLL 인젝션이 수행되는 프로세스에서 메모리 동적 할당을 한다.
  3. WriteProcessMemory 함수로 DLL 인젝션이 수행되는 프로세스에 DLL의 경로를 기록한다.(2번 과정에서 얻은 주소에 기록)
  4. GetModuleHandle 함수와 GetProcAddress 함수를 이용하여 kernel32.dll의 LoadLibraryW 함수의 주소를 얻는다.
  5. CreateRemoteThread 함수로 DLL 인젝션을 수행한다.
CreateRemoteThread 함수로 다른 프로세스에서 LoadLibraryW 함수가 실행되고, LoadLibraryW 함수의 인자에는 우리가 주입하기 원하는 DLL의 경로가 전달되기 때문에 DLL 인젝션이 수행된다.

아래의 예시는 PID가 5436인 Process Explorer에 DLL 인젝션을 수행한 모습이다.

00FA0000 주소에 injected.dll 문자열이 저장되어 있다.


참고
CreateRemoteThread 함수를 사용할 때 인자들은 첫 번째 인자에 해당하는 프로세스의 환경에 맞추어 전달하라고 했다. buffer는 그렇게 전달하였으나 LoadLibraryW 함수의 포인터는 그냥 현재 프로세스에서 구한 것을 그대로 전달하였다. 이렇게 해도 실행에 문제가 없는 이유는, kernel32.dll 처럼 시스템 dll 들은 재배치가 일어나지 않아서 모든 프로세스에서 같은 주소에 로드되기 때문이다.

레지스트리
아주 간단한 방법으로 DLL 인젝션을 수행할 수 있다.

레지스트리에서 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows 경로로 이동한 후 AppInit_DLLs를 수정한다.

운영체제에 의해 AppInit_DLLs의 값에 있는 DLL은 실행되는 모든 프로세스에 주입된다. 이는 재부팅 이후부터 적용되니 재부팅을 하여 확인해보자.


이렇게 블루스크린이 뜨는 걸 보면 부팅 프로세스부터 잘 적용이 되는 것 같다.


후킹 API 사용

'리버싱핵심원리' 카테고리의 다른 글

Reverse Engineering Chap 24  (0) 2016.01.20
Reverse Engineering Chap 18-20  (0) 2016.01.17
Reverse Engineering Chap 14~15  (0) 2016.01.14
Reverse Engineering Chap 16-17  (0) 2016.01.14
Reverse Engineering Chap 13  (0) 2016.01.12
Posted by 지환태
2016. 1. 15. 16:09

버튼 하나만 있는 아주 간단하고 문제의 목적을 알 수 있다. PEiD로 확인해보면 VB프로그램인 것을 알 수 있다.


VB 디컴파일러로 확인해보면 하나밖에 없는 버튼을 클릭했을 때 어느 코드가 실행되는 지 알 수 있다.


디컴파일러에서 확인한 0x402240의 주소를 확인하니 클릭 시 실행되는 게 맞다. 이후에 나오는 코드들은 모두 클릭 후에 실행되는 코드들이다.


처음으로 denied로 점프하는 분기문이 있는 부분이다. 이 곳에서는 __vbaLenBstr함수를 호출하여 입력한 문자열의 길이를 확인한다. 문자열의 길이가 7이 아니면 denied를 출력하는 곳으로 점프한다.


입력한 문자열의 길이가 7인 경우 점프되지 않고 아래의 이 코드들이 실행된다. __vbaObjSet, __vbaHresultCheckObj, rtcMidCharBstr, __vbaStrMove, __vbaStrI2, __vbaFreeStrList, __vbaFreeObj, __vbaFreeVar 함수들이 실행된다.

할당과 해제에 관련된 함수를 제외하고 살펴보자. rtcMidCharBstr 함수는 입력한 문자열에서 한 문자를 가져온다. __vbaStrI2 함수는 문자열을 다른 문자열로 바꿔주는 역할을 한다. 이러한 코드가 총 7개가 있다.

4번째와 6번째에는 __vbaStrI2 함수가 없는 것만 빼면 동일하다.


이 코드들이 실행되면 어떤 값들이 저장되는 지 알아보기 위해 문자열 1234567 을 입력해보았다.

위의 코드와 비슷한 코드 7개들 모두 실행한 후의 스택 영역이다. 스택에는 문자열을 가리키는 포인터들이 저장된다. 중간에 문자열로 표시되지 않는 곳을 따라가보면 아래와 같은 값이 저장되어있다.




스택이 가리키는 문자열은 순서대로 "49", "50", "51", "\x34", "53", "\x36", "55" 이다. 1씩 증가하는 문자열을 입력했고, 스택에서도 1씩 커지는 문자열들을 가리키고 있다. 여기서 __vbaStrI2 함수가 한 문자를 10진수 아스키 코드 문자열로 변환하는 것을 알 수 있다. 4, 6 번째 문자를 처리할 때는 __vbaStrI2 함수가 없기 때문에 문자의 16진수 값이 그대로 저장된다.

char 

10 

16 

49 

31 

50 

32

51 

33 

52 

34 

53 

35 

54 

36 

55 

37 

코드를 실행하면 위의 표와 같이 문자열이 생성되고 각 스택에서 문자열들을 가리킨다.


각 문자를 처리하면 위 사진의 코드가 실행된다. 문자열을 비교하는 __vbaStrCmp 함수가 7번 실행된다. 비교하는 문자열이 다를 경우 denied를 출력하는 곳으로 점프한다. 7번의 __vbaStrCmp에는 순서대로 "69", "99", "104", "\x33", "108", "\x30", "110"의 문자열들이 비교된다.

이 조건을 만족하기 위해서는 아래의 표와 같이 문자열을 구성해야한다.

char 

10 

16 

E

69

45

c

99

63

h

104

68

3

51

33

l

108

6C

0

48

30

n

110

6E 







'WriteUp' 카테고리의 다른 글

Crackit2.exe Writeup  (0) 2016.02.01
Crackit2.exe Writeup  (0) 2016.01.14
[예시] Easy Keygen (100pt)  (0) 2016.01.11
Posted by 지환태
2016. 1. 14. 16:52

이번에 풀 문제는 Crackit2.exe이다.

먼저 기본적으로 실행을 해본다.

 

입력 값을 요구하는 것으로 보아 키 값을 찾아야 하는 문제로 추측된다.

 

대충 아무거나 입력해봤더니 "ACCESS DENIED"가 떴다.

위 문자열은 키 값을 '판단' 한 후 뜨는 문자열이므로 ollydbg에서 위 문자열을 띄우는 루틴을 찾는 다면

키 값을 비교하는 루틴도 찾을 수 있을 것으로 판단된다. 

 



crackit2.exe를 ollydbg에서 실행한다.

 



ollydbg상에서 오른쪽 클릭 -> Search for -> all referenced text strings 를 클릭한다. 예상대로 문자열 목록을 보면

"ACCESS DENIED"가 존재한다. 또한 "ACCESS GRANTED"가 있는 것으로 보아 키값 비교 성공시 ACESS GRANTED가 뜰것으로 예상된다.

 

 referenced string 창에서 ACCESS DENIED를 더블클릭하면 위 사진처럼 "ACCESS DENIED"의 위치가 나온다.

comment 부분을 살펴보면 위쪽에 ACESS GRANTED가 있음을 알 수 있다.

ACCESS GRANTED 메세지가 push되고 함수가 호출되어야 키값이 맞다는 뜻인데, 004027A9를 보면 ACESS GRANTED 문자열이 있는 부분을 건너뛰고 ACCESS DENIED가 있는 쪽으로 JUMP하는 명령이 있다. 이 부분의 조건이 키값을 바르게 입력했는지 여부를 나타낸다. 4027A9 분기점은 EAX 값이 결정하고 이 값은 4027A2의 CALL EDI에서 결정된다. 

 



CALL EDI 부분에 브레이크포인트를 걸고 확인해보니 vbaStrCmp 함수를 호출함을 알 수 있다. 비교하는 문자열은 "yes"인데, 스택을 확인해보니 또다른 비교 문자열이 비어있다. 402781의 "yes" 문자열이 스택에 넣어지지 못했음을 의미한다. 

 



 하여 402781 주소의 "yes"가 어디서 건너뛰어졌는지를 찾아보면 402793으로 점프 되는 곳이 있음이 보인다. 이를 따라가본다.



40230c 주소에서 JUMP가 이루어졌음을 알 수 있다. 어떤 조건으로 점프되었는지를 살펴보면 상단에 vbaLenBstr 함수가 있는데, 이는 문자열의 길이를 나타내는 함수이다. 그리고 그 밑에 CMP 명령으로 7과 비교하는 부분이 있다. 즉, 입력하는 문자열의 길이가 7이 되어야 함을 알 수 있다.

 



길이가 7이 되어야 함을 알아냈으므로, 밑으로 내리다 보면 핵심 루틴이 보인다. 바로 문자열을 한캐릭터씩 비교하는 부분이다. 유니코드로 친절하게 69,99,104,108,110이 제공되어있다. 허나 이는 5자리 뿐이다. 자세히 보면 comment에는 보이지 않지만 104와 108 사이, 108과 110 사이에도 비교하는 값들이 있음을 알 수 있다. 이 값들은 디버깅을 통해 파악하도록 한다. 앞 세글자인 69,99,104는 아스키 코드 상에서 E,c,h이다.

 



 위 처럼 브레이크 포인트를 건다. 파라미터로 00401CD0를 넣음을 알 수 있다. 헥스 덤프 창에서 ctrl+g 버튼을 눌러 해당 주소로 가본다.

4010CD0에는 값 33이 들어있음을 알 수 있다.

 

같은 방식으로 6번째 문자는 0이 들어있음을 알 수 있다.

 

그리하여 위 아스키 값들을 문자로 변환하면 키값은 Ech3l0n이다

 

 



clear

'WriteUp' 카테고리의 다른 글

Crackit2.exe Writeup  (0) 2016.02.01
Crackit2.exe Writeup  (0) 2016.01.15
[예시] Easy Keygen (100pt)  (0) 2016.01.11
Posted by 키흐
2016. 1. 14. 09:59

Chap 14. 실행 압축


14.1. 데이터 압축

적절한 알고리즘을 사용하면 파일의 크기를 줄일 수 있다. 압축된 파일을 100% 원래대로 되돌릴 수 있으면 “비손실 압축”, 원래대로 복원할 수 없으면 “손실 압축” 이라고 한다.

- 비손실 압축 : Run-Length, Lempel-Ziv, Huffman 등의 알고리즘이 있다. (ZIP, RAR 등의 포맷)

- 손실 압축 : 의도적으로 파일에 손상을 주어서 압축률을 높인다. jpg, mp3, mp4 등의 파일이 있고, 사람이 차이를 구분할 수 없을 정도의 손상을 준다.


14.2. 실행 압축

행 파일(PE : Portable Executable)을 압축하여 실행되는 순간에 메모리에서 압축을 해제하여 실행하는 기술이다. 파일 내부에 압축 해제 코드를 포함하고 있다. 실행 압축된 파일 또한 PE 파일이며, EP(Entry Point) 코드에 decoding 루틴이 실행되면서 메모리에서 압축을 해제한 후 실행된다.

--> 주요 차이점 : 실행 압축은 PE 파일의 실행이 가능하다는 것이다.

일반 PE 파일을 실행 압축 파일로 만들어주는 유틸리티를 패커라고 하며, 더 Anti-Reversing 기법에 특화된 패커를 프로텍터라고 한다.


14.2.1. 패커

- 목적 : PE파일의 크기를 줄임, 내부 코드와 리소스를 감춤

- 현황 : DOS 시절에는 PC속도가 느리기 때문에 압축을 해제하는 과정이 큰 오버헤드 였지만, 현재는 널리 사용되고 있음


14.2.2. 프로텍터
목적 : 크래킹 방지, 코드 및 리소스 보호
- 현황 : 게임 보안 프로그램과 같이 보안에 민감한 프로그램들이 사용

14.3. 실행 압축 테스트

<UPX 패커>

http://upx.sourceforge.net 에서 다운받을 수 있다.



upx.exe 파일이 있는 곳에 notepad.exe 파일을 옮긴 후 커맨드 창에 다음과 같이 실행하면 실행 압축 파일이 만들어진다.


67586 -> 48128로 파일 크기가 줄어든 것을 확인할 수 있다.


두 파일을 PE file format 관점에서 보면 다음과 같다.


<특징>

- PE header의 크기는 동일(0 ~ 400)

- 섹션 이름 변경

- 첫 번째 섹션의 RawDataSize = 0 (파일에서의 크기가 0)

- upx 파일의 EP(Entry Point)는 두번째 섹션에 위치

- .rsrc의 크기는 거의 변하지 않음


<PEview로 notepad_upx.exe 파일을 열어서 확인한 결과>


UPX0의 헤더를 확인하면 Size of Raw Data(파일에서 섹션이 차지하는 크기)가 0인데 반해, Virtual Size(메모리에서 섹션이 차지하는 크기)는 10000으로 세팅되어 있음을 확인할 수 있다. 즉, UPX로 실행 압축된 파일은 실행되는 순간에 파일에 있는 압축된 코드를 메모리에 있는 첫번째 섹션에 풀어 버린다. 파일에서 압축해제 코드와 압축된 원본 코드는 두번째 섹션에 존재한다. —> 15장은 디버거를 이용하여 실제 압축 해제 과정을 디버깅 함


Chap 15. UPX 실행 압축된 notepad 디버깅


15.1. notepad.exe의 EP code


010073AC 주소에서 GetModuleHandleA() API를 호출하여 ImageBase를 구하고 있다. 또한 010073B4, 010073C0 주소에서 각각 MZ, PE 시그니처를 비교하고 있다.


15.2. notepad_upx.exe의 EP code



EP 주소는 01015330이며, PUSHAD 명령을 통해 EAX ~ EDI 레지스터의 값을 스택에 저장한다. 그리고 ESI 레지스터에는 두번째 섹션의 시작 주소인 01011000을 저장하고, EDI 레지스터에는 첫번째 섹션의 시작 주소인 01001000을 저장한다. 아래의 그램은 01015336 주소의 명령을 실행한 직후의 레지스터 값이다.



따라서 두번째 섹션(ESI)로부터 데이터를 읽어서 압축을 해제한 후 첫번째 섹션(ESI)에 저장할 것을 예측할 수 있다.

참고로 원본 EP를 OEP(Original Entry Point)라고 하는데, 우리의 목표는 실행 압축 파일의 EP를 통해 OEP를 찾아내는 것이다.


15.3. UPX 파일 트레이싱
트레이스(Trace)는 코드 하나하나 실행하면서 쫓아가는 것을 의미한다.

압축을 해제하는 과정에는 많은 루프가 있는데, 루프를 적절히 탈출해야 빠르게 진행할 수 있다. OllyDbg에서는 아래의 단축키를 사용하면 편리하다.


Animate Over 명령어를 사용하여 진행하다 보면 첫번째 루프를 만나게 된다. 



루프의 내용은 EDX(01001000)에서 한 바이트를 읽어서 EDI(01001001)에 쓰는 것이다. 즉, 메모리에서의 첫번째 섹션을 NULL로 채우는 것이다. 루프를 내용을 확인했기 때문에 빠져나오면 된다. 빠져나오는 방법은 루프의 다음 줄인 010153E6에 Break Point[F2]를 설치한 후 실행 명령[F9]을 하면 된다. 다음과 같이 두번째 루프를 만나게 된다.



두번째 루프는 본격적인 docoding을 하는 루프이다. 내용은 다음과 같다.



ESI가 가리키는 두번째 섹션에서 차례대로 값을 읽어서 적절한 연산을 거쳐 EDI가 가리키는 첫번째 섹션에 써주는 것이다.


두번째 섹션을 넘어가기 위해 01015402 주소에 BP를 설치하고 실행한다.

다시 트레이싱을 시작하면 세번째 루프를 만날 수 있다.



세번째 루프의 내용은 원본 코드의 CALL/JMP 명령어의 destination 주소를 복원시켜주는 것이다. 이 루프도 01015436에 BP를 설치하고 실행하여 탈출한다. 이어서 네번째 루프를 만나게 된다.



이 과정은 IAT(Import Address Table)을 세팅하는 것이다. 첫번째 줄에서 EDI가 01014000으로 세팅되는데, 해당 주소를 dump를 확인하면 다음과 같다.



API 이름들이 나열되어 있다. 이는 원본 파일 notepad.exe를 UPX가 압축시킬 때, IAT를 분석하여 API 이름의 목록을 뽑아놓은 것이다. 따라서 네번째 루프의 내용은 API 목록을 확인하고 01015467 주소에서 GetProcAddress()를 호출하여 API 시작 주소를 얻은 후 IAT 영역에 입력하는 과정이다. 일반 적인 실행 압축 파일은 이 과정이 끝나면 OEP로 점프하게 된다.



010154AD 에서는 최초에 PUSHAD 명령과 대응되는 POPAD 명령을 하게 된다. 즉, 스택에 있는 레지스터 값들을 복원시키는 명령이다. 최종적으로 010154BB 주소의 JMP 명령에 의해서 OEP로 점프한다.


15.4. UPX의 OEP를 빨리 찾는 방법


<첫번째 방법>

UPX 패커의  특징은 EP코드가 PUSHAD/POPAD 명령어로 둘러싸여 있고, OEP 코드로 가는 JMP 명령어는 POPAD 명령어 바로 이후에 나타난다는 것이다. 따라서 해당 JMP 명령어를 찾아 BP를 설치하면 바로 OEP로 갈 수 있다.


<두번째 방법>

PUSHAD/POPAD의 특성을 이용하는 방법이다. PUSHAD를 하면 스택에 레지스터 값이 쌓이게 된다.

스택의 가장 윗 부분인 0006FFA4를 Dump 창에서 찾는다. 



정확히 0006FFA4를 클릭한 후, 우측 메뉴에서 Breakpoint -> Hardware, on access -> Byte를 설정한다. 이는 하드웨어 BP인데, 하드웨어 BP란 CPU에서 지원하는 BP이며, 5개까지 설치 가능하다. POPAD가 호출되는 순간 0006FFA4 주소를 엑세스하면 제어가 멈추게 된다.

'리버싱핵심원리' 카테고리의 다른 글

Reverse Engineering Chap 18-20  (0) 2016.01.17
Reverse Engineering Chap 23  (0) 2016.01.17
Reverse Engineering Chap 16-17  (0) 2016.01.14
Reverse Engineering Chap 13  (0) 2016.01.12
Reverse Engineering Chap7-12  (0) 2016.01.10
Posted by 알 수 없는 사용자