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. 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 알 수 없는 사용자
2016. 1. 14. 04:39

Chapter 16. Base Relocation Table


16.1 PE 재배치

1) 개념 : PE파일이 ImageBase에 로딩되지 못하고 다른 주소에 로딩될 때 수행되는 작업

- PE파일이 프로세스 가상 메모리에 로딩될 때 PE헤더의 ImageBase주소에 로딩됨

- DLL의 경우 ImageBase위치에 이미 다른 DLL파일이 로딩되어 있다면 다른 비어있는 주소 공간에 로딩됨


2) SDK(Software Development Kit) / Visual C++ 로 생성된 PE파일

- EXE의 ImageBase = 00400000

- DLL의 ImageBase = 10000000


3) DDK(Driver Development Kit)로 생성된 SYS파일

- SYS의 ImageBase = 10000

  


16.1.1. DLL/SYS


위 그림은 TEST.EXE 프로세스에 A.DLL이 10000000주소에 로딩되어 있는 상태를 나타낸다. 이후 B.DLL이 같은 주소(10000000)에 로딩을 시도하면 PE로더는 비어있는 주소에 B.DLL을 로딩시킨다. 



16.1.2. EXE

1) 프로세스가 생성될 때, EXE파일이 가장 먼저 메모리에 로딩되므로 EXE는 재배치를 고려할 필요가 없었음

2) Windows Vista 이후부터는 ASLR(Address Space Layout Randomization)기능으로 인해 EXE파일이 실행 때마다 임의의 주소로 로딩됨 (ASLR은 41장 참고)




16.2 PE 재배치 발생시 수행되는 작업

* Windows7의 notepad.exe 프로그램으로 PE 재배치 확인

1) PEView를 이용한 notepad.exe의 ImageBase 확인 (ImageBase = 01000000)


2) OllyDbg로 nodepad.exe 실행


위 그림은 notepad.exe의 EP코드 부분이다. 앞서 언급한 바와 같이 Windows Vista 이후 버전에서는 보안기능인 ASLR로 인하여 실행될 때마다 주소값이 매번 변경된다. 우선 각 박스 표시된 부분을 살펴보자. Instruction 박스 표시에는 프로세스 메모리 주소가 하드코딩이 되어있음을 알 수 있다.

- B210FC, B21100는 .text 섹션의 IAT 영역

- B2C0A4주소는 .data 섹션의 전역변수

상기 부분은 올리디버거에서 notepad.exe를 재실행할 때마다 로딩 주소에 맞게 수시로 변경된다. 이렇듯 프로그램상에 하드코딩된 메모리주소를 현재 로딩된 메모리주소에 맞게 변경해주는 작업을 "PE재배치"라 한다.


3) HxD를 이용하여 EP영역 확인

파일(ImageBase : 01000000) 

프로세스 메모리 (로딩주소 : 00B20000) 

 010010FC

B210FC 

 01001100

B21100

 0100C0A4

B2C0A4 

notepad.exe 파일이 빌드될 때, 실제 어느 주소에 로딩이 될지 예측할 수 없으므로 ImageBase기준으로 하드코딩 주소를 적었다. 그러나 실행되는 순간에 PE재배치 과정을 거치면서 이 주소들은 모두 로딩 주소 기준으로 변경된다.




16.3 PE 재배치 동작 원리

* PE재배치 작업의 기본 동작 원리

- 프로그램에서 하드코딩된 주소 위치를 찾는다.

- 값을 읽은 후 ImageBase만큼 뺀다(VA → RVA).

- 실제 로딩 주소를 더한다(RVA→VA)


16.3.1. Base Relocation Table

Base Relocation Table 주소는 PE헤더에서 DataDirectory배열의 여섯 번째 항목에 있다.

(IMAAGE_NT_HEADERS \ IMAGE_OPTIONAL_HEADER \ IMAGE_DATA_DIRECTORY[5])


PEView에서 notepad.exe의 Base Relocation Table 주소는 아래 그림과 같다.

Base Relocation Table의 주소는 RVA 2F000이며, 이 주소를 통해 아래 그림과 같이 하드코딩 주소들의 Offset을 파악할 수 있다.



16.3.2. IMAGE_BASE_RELOCATION 구조체

Base Relocation Table은 IMAGE_BASE_RELOCATION 구조체 배열이다. IMAGE_BASE_RELOCATION구조체 정의는 다음과 같다.

typedef struct _IMAGE_BASE_RELOCATION {

DWORD VirtualAddress; // Base Address (RVA 값)

DWORD SizeOfBlock;   // 각 단위 블록의 크기

// WORD TypeOffset[l];

} lMAGE_BASE_RELOCATION;

typedef lMAGE_BASE_RELOCATION UNALIGNED * PlMAGE_BASE_RELOCATION;



16.3.3. Base Relocation Table 해석방법

다음 표는 Base Relocation Table의 일부를 표현한 것이다. 

- Virtual Address (시작 주소) : 1000

- Size of Block (블록의 전체크기 ) : 150

- TypeOffset (= Type + Offset)

→ Type(4bit) : PE파일에서 일반적으로 3(IMAGE_REL_BASED_HIGHLOW)

 : PE+파일에서 일반적으로 A(IMAGE_REL_BASED_DIR64)

→ Offset(12bit) : 실제 사용되는 위치

- VirtualSize + Offset = RVA


(예시) 0002F008주소의 3420의 경우 3 + 420으로 분류할 수 있다. 따라서 시작주소와 Offset값을 더하면 1420이 된다. 실제 VA를 구하기 위해서는 RVA + ImageBase(로딩주소)를 계산해야하므로, 앞선 예제의 ImageBase(00B20000)를 이용한다면, VA = 00B21420이 된다. 이 주소에는 IAT주소가 저장되어 있다.


16.3.4. 실습

앞서 언급한 환경을 기준으로 설명한다.

1) 프로그램에서 하드코딩된 주소 위치를 찾는다.

앞서 구한 RVA 1420을 이용한다. PEView를 통해 실제 RVA 1420 주소의 내용은 다음 그림과 같다.

RVA 1420에는 프로그램의 하드코딩된 주소 0100010C4 값이 들어있다.


2) 값을 읽은 후 ImageBase만큼 뺀다. (VA  RVA)  ** RVA + imageBase = VA

010010C4 - 01000000 = 000010C4


3) 실제 로딩 주소를 더한다. (RVA  VA)

000010C4 + 00B20000 = 00B210C4


PE로더는 프로그램 내에 하드코딩된 주소를 위와 같은 과정을 거쳐서 실제 로딩된 메모리 주소에 맞게 보정을 한 겂을 같은 위치에 덮어쓴다.






Chapter 17. 실행파일에서 .reloc 섹션 제거하기


17.1 .reloc 섹션

EXE형식의 PE파일에서 Base Relocation Table항목은 실행에 큰 영향을 미치지 않는다. 실제로 이 부분을 제거한 후 실행을 시켜도 정상적인 동작을 수행한다. 다만, EXE와 다르게 DLL, SYS의 경우 Base Relocation Table이 반드시 필요하다.

VC++에서 생성된 PE파일의 Relocation 섹션 이름은 ".reloc"이다. 이 .reloc 섹션이 제거되면 PE파일의 크기가 줄어든다. 보통 이 섹션은 마지막에 위치한다.


17.2. reloc.exe

.reloc을 제거하기 위해서는 다음과 같은 네 단계의 작업을 수행해야 한다.

- 1단계 : .reloc 섹션 헤더 정리

- 2단계 : .reloc 섹션 제거

- 3단계 : IMAGE_FILE_HEADER 수정

- 4단계 : IMAGE_OPTIONAL_HEADER 수정


17.2.1. .reloc 섹션 헤더 정리

- File Offset : 270

- Header Size : 28

- Range : [270, 297] <헤더의 마지막 부분을 참고>

해당 범위 부분을 0으로 덮어 쓴다(HxD 이용). 



17.2.2. .reloc 섹션 제거

.reloc섹션의 시작 offset은 C000이다. 이 섹션은 가장 마지막 부분에 위치하므로, C000부터 끝까지 삭제하면 된다. 다음 그림과 같이 HxD로 C000부터 파일의 끝까지 삭제한다.



17.2.3. IMAGE_FILE_HEADER 수정

앞서 물리적으로 .reloc섹션을 제거하였으나, 다른 PE헤더 정보들이 수정되지 않았기 때문에 파일이 정상적으로 실행되지 않는다. 현재 .reloc섹션 하나를 지웠으므로 전체 섹션의 개수에서 하나를 줄여야 한다. IMAGE_FILE_HEADER에서 Number of Sections항목을 5에서 4로 수정한다.



17.2.4. IMAGE_OPTIONAL_HEADER 수정

.rection 섹션이 제거되면서 전체 크기가 해당 섹션만큼 줄어들었다. 이미지 크기는 IMAGE_OPTIONAL_HEADER - Size of Image값에 명시되어있다.

위 그림과 같이 이미지의 크기는 11000이다. .reloc섹션의 VirtualSize값은 E40이고, 이를 Section Alignment에 맞게 확장하면 1000이 된다. 따라서 Size of Image값을 1000만큼 빼야한다. 

위 과정이 모두 완료되면 해당 파일은 정상적으로 실행이 된다.


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

Reverse Engineering Chap 23  (0) 2016.01.17
Reverse Engineering Chap 14~15  (0) 2016.01.14
Reverse Engineering Chap 13  (0) 2016.01.12
Reverse Engineering Chap7-12  (0) 2016.01.10
Reverse Engineering Chap1-2  (0) 2016.01.10
Posted by 알 수 없는 사용자
2016. 1. 12. 16:26

Chapter 13 PE File Format

 이번 챕터의 내용은 PE File Format에 대한 내용이다. 내용 요약에 앞서, PE File Format 분석에 잘 쓰이는 툴을 먼저 소개하겠다. 챕터에 대한 요약 중 사용 될 툴이므로 다운 받아 사용하면 된다.

 PEView

PEview.exe

   PE 파일 포맷은 여러 섹션으로 나뉘어져 있고 또 이 섹션은 여러 구조체, 데이터로 나뉘어 있다. PEView는 이러한 섹션과 구조체를 보기 쉽게 정리하여 보여준다.

 HexEditor

HxD.exe

실행 파일을 원하는 진수의 값으로 보여준다. 주로 16진수를 이용하며 실행시 파일의 내부 값을 변경할 수 있으므로 patching을 할때 자주 쓰인다.

 

1. PE 파일이란?

PE(Portable Executable) 파일은 Windows 운영체제에서 사용되는 실행 파일 형식이다. PE 파일은 32비트 형태의 실행 파일을 의미하며 PE32라는 용어를 사용하기도 한다. 64비트 형태의 실행 파일은 PE+ 또는 PE32+라고 부르며 PE 파일의 확장 형태이다.

 

2. PE File Format

PE 파일의 종류는 다음과 같다.

2.1 기본 구조

PE 파일의 기본 구조에 대해 알아보겠다. notepad.exe 파일은 pe 파일의 기본 구조이므로 이 파일을 예시로 한다.

 

그림 13.2를 보면 PE 파일의 기본 구조를 알 수 있다. DOS header부터 Section header까지를 PE 헤더, 그 밑의 Section들을 합쳐서 PE 바디라고 한다. 파일에서는 Offset으로, 메모리에서는 VA(Virtual Address)로 위치를 표현한다. 파일이 메모리에 로딩되면 모양이 달라진다. 파일의 내용은 보통 코드(.text), 데이터(.data), 리소스(.rsrc) 섹션에 나뉘어 저장된다.

 섹션헤더에는 각 Section에 대한 파일/메모리에서의 크기, 위치, 속성 등이 정의 되어있다. PE 헤더의 끝부분과 각 섹션의 끝에는 NULL padding이라는 영역이 존재한다. 파일/메모리의 최소 단위를 맞추기 위한 공간이라고 생각하면 된다.

2.2 VA & RVA

VA(Virtual Address)는 프로세스 가상 메모리의 절대주소를 말하며, RVA(Relative Virtual Address)는 어느 기준 위치(ImageBase)에서 부터의 상대주소를 말한다. VA와 RVA의 관계는 다음과 같다.

RVA + ImageBase = VA

3. PE 헤더

PE 헤더는 많은 구조체로 이루어져 있다. 지금부터 각 중요한 구조체에 대해서 살펴보겠다.

3.1 DOS Header

 PE File Format은 DOS 파일에 대한 하위 호환성을 고려해서 만들어졌다. 그 결과로 PE 헤더의 가장 앞부분에는 기존 DOS EXE Header를 확장시킨 IMAGE_DOS_HEADER 구조체가 존재한다.

 

IMAGE_DOS_HEADER 구조체의 크기는 40이다. 이 구조체에서 중요한 멤버는 e_magic, e_lfanew이다. e_magic은 DOS signature로 모든 PE 파일은 e_magic의 값이 "MZ"이다. 또한 e_lfanew의 값이 가리키는 위치는 NT Header 구조체의 위치이다.

 

그림 13.3을 보면 맨 첫 2바이트의 값이 4D 5A, 즉 MZ 임을 알 수 있다. 또한 IMAGE_DOS_HEADER의 크기는 40이므로 offset 00000030의 마지막 4바이트를 보면 E0 00 00 00 이다. 이는 IMAGE_DOS_HEADER의 마지막 멤버인 e_lfanew를 의미하고 이 값은 리틀 엔디언이므로 NT Header의 위치는 000000E0 임을 알 수 있다.

3.2 NT Header

NT header 구조체인 IMAGE_NT_HEADERS는 다음과 같다.

 

코드 13.2를 보면 IMAGE_NT_HEADERS 구조체는 3개의 멤버로 구성 되어있다. 첫 멤버는 Signature로 헥스값 50450000을 갖는다. 이 중 앞 2바이트는 "PE"를 뜻한다. IMAGE_NT_HEADERS의 크기는 F8로 상당히 큰 구조체이다.


3.3 NT Header - File Header

IMAGE_FILE_HEADER 구조체는 파일의 개략적인 속성을 나타낸다.


코드 13.3은 IMAGE_FILE_HEADER 구조체를 나타낸다. 위 멤버 중 중요한 멤버로는 Machine, NumberOfSections, SizeOfOptionalHeader, Characteristics 가 있다. 각 멤버의 특징은 다음과 같다.

Machine : CPU별로 고유한 값을 가지며, 32비트 Intel x86 호환 칩은 값 14C 를 갖는다.

NumberOfSections : 섹션의 개수를 나타냄. 이 값은 항상 0보다 크다.

SizeOfOptionHeader : IMAGE_OPTIONAL_HEADER32 구조체의 크기를 나타냄

Characteristics : 파일의 속성이 bit OR 형식으로 조합됨. (코드 13.5 참고)



3.4 NT Header - Optional Header

다음은 PE 헤더 구조체 중 가장 크기가 큰 IMAGE_OPTIONAL_HEADER32 이다.



IMAGE_OPTIONAL_HEADER32 구조체에서 중요한 멤버는 Magic, AddressOfEntryPoint, ImageBase, SectionAlignment/FileAlignment, SizeOfImage, SizeOfHeader, Subsystem, NumberOfRvaAndSizes, DataDirectory이다. 각 멤버의 특징은 다음과 같다.

Magic : Magic 넘버는 IMAGE_OPTIONAL_HEADER32면 10B, IMAGE_OPTIONAL_HEADER64면 20B 값을 갖는다.

AddressOfEntryPoint : AddressOfEntryPoint는 Entry Point의 RVA 값이다.

ImageBase : ImageBase는 PE 파일이 로딩되는 시작 주소를 나타낸다. 일반적으로 개발도구들이 만들어 내는 EXE  파일의 Image Base 값은 00400000이고, DLL 파일의 ImageBase 값은 10000000이다. 파일이 메모리에 로딩 된 직후의 EIP 값은 ImageBase + AddressOfEntryPoint 이다.

SectionAlignment/FileAlignment : 파일에서 섹션의 최소 단위를 나타내는 것이 FileAlignment이고 메모리에서 섹션의 최소단위를 나타내는 것이 SectionAlignment이다. 파일/메모리의 섹션 크기는 반드시 각 Alignment 값의 배수가 되어야한다.

SizeOfImage : SizeOfImage는 PE 파일이 메모리에 로딩 되었을 때 가상 메모리에서 PE Image가 차지하는 크기를 나타낸다. 

SizeOfHeader : SizeOfHeader는 PE 헤더의 전체 크기를 나타낸다. 이 값 역시 FileAlignment의 배수여야 한다. 

Subsystem : 이 Subsystem 값을 보고 어떤 파일인지 구분 할 수 있다. 각 값의 의미는 다음과 같다.

NumberOfRvaAndSizes : NumberOfRvaAndSizes는 IMAGE_OPTIONAL_HEADER32 구조체의 마지막 멤버인 DataDirectory 배열의 개수를 나타낸다. 

DataDirectory :  DataDirectory는 IMAGE_DATA_DIRECTORY 구조체의 배열로, 배열의 각 항목마다 정의된 값을 갖는다. 각 항목은 다음과 같다. 코드 13.7에서의 Directory란 어떤 구조체의 배열이라고 생각하면 된다. IMPORT, EXPORT Directory는 나중에 따로 설명하겠다.


3.5 섹션 헤더

섹션 헤더는 각 섹션의 속성을 정의한 것이다. 


IMAGE_SECTION_HEADER

섹션 헤더는 각 섹션별 IMAGE_SECTION_HEADER 구조체의 배열로 되어있다.

IMAGE_SECTION_HEADER에서 알아야할 중요한 멤버와 의미는 다음과 같다.

4.  RVA to RAW

각 섹션에서 메모리의 주소(RVA)와 파일 오프셋을 잘 매핑 할 수 있어야한다. 이러한 매핑을 RVA to RAW라고 한다. 방법은 아래와 같다.

1. RVA가 속해 있는 섹션을 찾는다.

2. 간단한 비례식을 사용하여 파일 오프셋(RAW)을 계산한다.

IMAGE_SECTION_HEADER 구조체에 의하면 비례식은 다음과 같다.




5. IAT

IAT(Import Address Table)에는 Windows 운영체제의 핵심 개념인 process, memory, DLL 구조 등에 대한 내용이 함축 되어 있다. 

5.1 IMAGE_IMPORT_DESCRIPTOR

PE 파일은 자신이 어떤 라이브러리를 임포트하고 있는지 IMAGE_IMPORT_DESCRIPTOR 구조체에 명시하고 있다. IMAGE_IMPORT_DESCRIPTOR의 위치는 IMAGE_OPTIONAL _HEADER의 DataDirectory[1]의 값에 정의 되어있다.


위 사진을 보면 DataDirectory의 두번째 요소는 A048(RVA) 값을 갖고 있다. 이는 RVA A048 주소에 IMAGE_IMPORT_DESCRIPTOR이 있다는 의미이다.

 위 사진에서 A048(RVA)에서 IMPORT Directory Table이 시작됨을 알 수 있다.

IMAGE_IMPORT_DESCRIPTOR 구조체의 멤버 중 중요한 멤버는 다음과 같다.

1.Import Name Table(OriginalFirstThunk)

임포트하는 함수의 정보(Ordinal, Name)가 담긴 구조체 포인터 배열이다. Ordinal은 함수의 고유 번호이다. 이 정보를 얻어야 프로세스 메모리에 로딩된 라이브러리에서 해당 함수의 시작 주소를 정확히 구할 수 있다.

2.Name

라이브러리의 이름을 의미한다. 주소를 따라가보면 *.dll이 있음을 알 수 있다.

3.Import Address Table(FirstThunk)

값에 적혀있는 주소(RVA)를 따라가면 임포트하는 함수들의 시작 주소들이 포인터 배열로 존재한다.

6. EAT 

Windows 운영체제에서 라이브러리란 다른 프로그램에서 불러 쓸 수 있도록 관련 함수들을 모아놓은 파일(DLL/SYS)이다. Win32 API가 대표적인 라이브러리이다. EAT(Export Address Table)는 라이브러리 파일에서 제공하는 함수를 다른 프로그램에서 가져다 쓸 수 있게 해주는 핵심 메커니즘이다. 즉 EAT를 통해서만 해당 라이브러리에서 익스포트하는 함수의 시작 주소를 정확히 구할 수 있다. 


 PE 파일에서 IMAGE_EXPORT_DIRECTORY 구조체의 위치는 IMAGE_OPTIONAL_HEADER32.DataDirectory[0].VirtualAddress 값으로 알 수 있다. 이 값은 IMAGE_EXPORT_DIRECTORY 구조체 배열의 시작 주소이다. IMAGE_EXPORT_DIRECTORY 구조체의 중요 멤버는 다음과 같다. 




 

 

 

 

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

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
Reverse Engineering Chap7-12  (0) 2016.01.10
Reverse Engineering Chap1-2  (0) 2016.01.10
Posted by 키흐
2016. 1. 10. 22:43

스택 프레임

스택 프레임은 x86 어셈블리를 구성하는 매우 중요한 개념이다. 함수가 실행될 때마다 함수의 스택을 생성한다는 말은 들어보았을 것이다. 스택 프레임은 어셈블리 수준에서 함수를 구현하기 위해 필수적으로 이해해야 한다. 스택 프레임이라는 이름 중 스택은 자료구조 중 스택과 이름이 같다. 실제로 스택 프레임은 스택이라는 자료구조의 작동 방식을 이용한다. 스택에서 top을 가리키는 방법은 아래 그림 1과 같이 두 가지가 있는데 x86 어셈블리는 왼쪽의 방법을 사용한다.

|    ....    |
+------------+
| last input | <- top
+------------+
|    data    |
+------------+
|    data    |
+------------+
|    data    |
+------------+
|    ....    |
|    ....    | <- top
+------------+
| last input |
+------------+
|    data    |
+------------+
|    data    |
+------------+
|    data    |
+------------+
|    ....    |

그림 1 스택을 표현하는 두 가지 방법


x86 어셈블리는 스택을 위한 두 개의 레지스터(EBP, ESP)를 제공한다. ESP는 스택의 top에 해당하는 곳의 메모리 주소를 저장한다. 주소가 작은 쪽이 스택의 top에 해당되고 4바이트 단위로 이동한다. Push를 수행하면 데이터가 스택에 삽입되고 ESP는 4만큼 작아진다. 반대로 pop을 수행하면 데이터가 꺼내어지고 ESP는 4만큼 커진다. EBP는 이전 스택으로 복귀하기 위해 사용한다. 이전 스택이라는 말이 이해가 가지 않을 테니 다음의 예시로 설명하겠다.


실행 환경

Microsoft Visual Studio Community 2015

버전 14.0.24720.00 Update 1

Microsoft .NET Framework

버전 4.6.01038

 

설치된 버전: Community

...

Visual C++ 2015   00322-20000-00000-AA691

Microsoft Visual C++ 2015

...

그림 2 디버깅 정보 있으면 VS도 좋음

위의 프로그램을 이용하여 예제 프로그램을 디버깅하면서 설명한다.


예제 코드

int frame(int a, int b, int c, int d)

{

int localA;

int localB;

int localC;

int localD;


localA = a;

localB = b;

localC = c;

localD = d;


return 1;

}


int main(int argc, char *argv[])

{

frame(1, 2, 3, 4);

}


int frame(int a, int b, int c, int d)

{

00C81000  push        ebp  

00C81001  mov         ebp,esp  

00C81003  sub         esp,50h  

00C81006  push        ebx  

00C81007  push        esi  

00C81008  push        edi  

int localA;

int localB;

int localC;

int localD;


localA = a;

00C81009  mov         eax,dword ptr [ebp+8]  

00C8100C  mov         dword ptr [ebp-4],eax  

localB = b;

00C8100F  mov         eax,dword ptr [ebp+0Ch]  

00C81012  mov         dword ptr [ebp-8],eax  

localC = c;

00C81015  mov         eax,dword ptr [ebp+10h]  

00C81018  mov         dword ptr [ebp-0Ch],eax  

localD = d;

00C8101B  mov         eax,dword ptr [ebp+14h]  

00C8101E  mov         dword ptr [ebp-10h],eax  


return 1;

00C81021  mov         eax,1  

}

00C81026  pop         edi  

00C81027  pop         esi  

00C81028  pop         ebx  

00C81029  mov         esp,ebp  

00C8102B  pop         ebp  

00C8102C  ret  

00C8102D  int         3  

00C8102E  int         3  

00C8102F  int         3

int main(int argc, char *argv[])

{

00C81030  push        ebp  

00C81031  mov         ebp,esp  

00C81033  sub         esp,40h  

00C81036  push        ebx  

00C81037  push        esi  

00C81038  push        edi  

frame(1, 2, 3, 4);

00C81039  push        4  

00C8103B  push        3  

00C8103D  push        2  

00C8103F  push        1  

00C81041  call        00C81000  

00C81046  add         esp,10h  

}

00C81049  xor         eax,eax  

00C8104B  pop         edi  

00C8104C  pop         esi  

00C8104D  pop         ebx  

00C8104E  mov         esp,ebp  

00C81050  pop         ebp  

00C81051  ret


4바이트 주소로 시작하지 않는 줄은 Visual Studio에서 디버깅 용으로 삽입한 참고용 정보이다.


Argument 전달

opcode 

registers 

	frame(1, 2, 3, 4);
  00C81039  push        4  
  00C8103B  push        3  
  00C8103D  push        2  
  00C8103F  push        1  
->00C81041  call        00C81000
  00C81046  add         esp,10h
EAX = 0F9A3700 EBX = 0037E000
ECX = 00000001 EDX = 00000000
ESI = 00C81470 EDI = 00C81470
EIP = 00C81041 ESP = 0018FDD8
EBP = 0018FE34 EFL = 00000200

 stack

0x0018FDD8  00000001  ....
0x0018FDDC  00000002  ....
0x0018FDE0  00000003  ....
0x0018FDE4  00000004  ....

그림 3 argument 전달 방식

Call 명령으로 함수를 호출하기 직전의 상황이다. 0x00C81039부터 0x00C81041까지 argument를 역순으로 push하였다. 역순으로 push를 하였기 때문에 스택에는 1, 2, 3, 4의 순서로 저장되었다.


함수 호출

opcode 

registers 

  int frame(int a, int b, int c, int d)
  {
->00C81000  push        ebp  
  00C81001  mov         ebp,esp
EAX = 0F9A3700 EBX = 0037E000
ECX = 00000001 EDX = 00000000
ESI = 00C81470 EDI = 00C81470
EIP = 00C81000 ESP = 0018FDD4
EBP = 0018FE34 EFL = 00000200

 stack

0x0018FDD4  00c81046  F.?.
0x0018FDD8  00000001  ....
0x0018FDDC  00000002  ....
0x0018FDE0  00000003  ....
0x0018FDE4  00000004  ....

그림 4 call 이후 스택

Call 명령을 수행한 직후의 상황이다. 위의 결과에서 볼 수 있듯이 call을 수행하면 스택에 push가 수행된다. Push된 값 0x00C81046은 그림 3에서 볼 수 있다. 이 값은 수행된 call 명령 바로 다음의 명령을 가리키는 주소다. 이처럼 call 명령은 함수가 완료된 후 복귀할 주소를 push한다.


스택 프레임 생성

opcode 

registers 

  int frame(int a, int b, int c, int d)
  {
  00C81000  push        ebp  
  00C81001  mov         ebp,esp  
->00C81003  sub         esp,50h
EAX = 0F9A3700 EBX = 0037E000
ECX = 00000001 EDX = 00000000
ESI = 00C81470 EDI = 00C81470
EIP = 00C81003 ESP = 0018FDD0
EBP = 0018FDD0 EFL = 00000200

 stack

0x0018FDD0  0018fe34  4?..
0x0018FDD4  00c81046  F.?.
0x0018FDD8  00000001  ....
0x0018FDDC  00000002  ....

그림 5 스택 프레임 생성

함수 내부에서 첫 두 명령이 새로운 스택 프레임을 생성한다. 0x00C81000에서 이전 스택 프레임의 EBP를 스택에 저장하고, 0x00C81001에서 현재 ESP(스택의 top)를 EBP에 복사한다.


지역변수 공간 할당

opcode 

registers 

  int frame(int a, int b, int c, int d)
  {
  00C81000  push        ebp  
  00C81001  mov         ebp,esp  
  00C81003  sub         esp,50h  
->00C81006  push        ebx  
  00C81007  push        esi  
  00C81008  push        edi
EAX = 0F9A3700 EBX = 0037E000
ECX = 00000001 EDX = 00000000
ESI = 00C81470 EDI = 00C81470
EIP = 00C81006 ESP = 0018FD80
EBP = 0018FDD0 EFL = 00000200 

 stack

0x0018FDD0 0018fe34 4?.. 0x0018FDD4 00c81046 F.?. 0x0018FDD8 00000001 .... 0x0018FDDC 00000002 ....

그림 6 지역변수 공간 할당

새로 진입한 함수에서 지역 변수들은 새로 생성된 스택 프레임에 저장된다. 이 공간을 확보하기 위해 스택의 top을 위로 옮긴다(sub esp, 50h). 이렇게 스택의 top을 위로 옮겼기 때문에 push와 pop으로 스택의 값이 변경되어도 아래쪽의 지역변수에는 영향을 주지 않는다.


지역 변수 확인

opcode 

registers 

	localA = a;
  00C81009  mov         eax,dword ptr [ebp+8]  
  00C8100C  mov         dword ptr [ebp-4],eax  
	localB = b;
  00C8100F  mov         eax,dword ptr [ebp+0Ch]  
  00C81012  mov         dword ptr [ebp-8],eax  
	localC = c;
  00C81015  mov         eax,dword ptr [ebp+10h]  
  00C81018  mov         dword ptr [ebp-0Ch],eax  
	localD = d;
  00C8101B  mov         eax,dword ptr [ebp+14h]  
  00C8101E  mov         dword ptr [ebp-10h],eax  

	return 1;
->00C81021  mov         eax,1
EAX = 00000004 EBX = 0037E000
ECX = 00000001 EDX = 00000000
ESI = 00C81470 EDI = 00C81470
EIP = 00C81021 ESP = 0018FD74
EBP = 0018FDD0 EFL = 00000200

 stack

0x0018FDC0  00000004  ....
0x0018FDC4  00000003  ....
0x0018FDC8  00000002  ....
0x0018FDCC  00000001  ....
0x0018FDD0  0018fe34  4?..
0x0018FDD4  00c81046  F.?.
0x0018FDD8  00000001  ....
0x0018FDDC  00000002  ....
0x0018FDE0  00000003  ....
0x0018FDE4  00000004  ....

그림 7 지역 변수와 argument는 EBP를 기준으로 접근

0x00C81009부터 0x00C8101E까지 함수에서 정의한 코드를 실행했다. 4개의 지역 변수에 각각 argument의 값(1, 2, 3, 4)을 할당한다. 지역 변수 a, b, c, d의 주소는 각각 [EBP-4], [EBP-8], [EBP-0Ch], [EBP-10h]에 해당한다. 먼저 선언된 변수는 스택의 깊은 곳에 위치하고 나중에 선언된 변수는 스택의 얕은 곳에 저장된다. 스택의 상태를 보면 4, 3, 2, 1 순서로 역순으로 저장되어있는 것을 볼 수 있다.


함수 종료

opcode 

registers 

	return 1;
  00C81021  mov         eax,1  
  }
  00C81026  pop         edi  
  00C81027  pop         esi  
  00C81028  pop         ebx  
  00C81029  mov         esp,ebp  
  00C8102B  pop         ebp  
->00C8102C  ret
EAX = 00000004 EBX = 0037E000
ECX = 00000001 EDX = 00000000
ESI = 00C81470 EDI = 00C81470
EIP = 00C8102C ESP = 0018FD64
EBP = 0018FE34 EFL = 00000200

 stack

0x0018FDD4  00c81046  F.?.
0x0018FDD8  00000001  ....
0x0018FDDC  00000002  ....
0x0018FDE0  00000003  ....
0x0018FDE4  00000004  ....

그림 8 함수 종료

함수에서 정의한 작업을 다 마치면 0x00D7102F와 0x00D71030의 두 명령으로 이전 스택 프레임을 복구한다. 두 명령 mov esp, ebp; pop ebp는 함수 처음에 실행했던 두 명령 push ebp; mov ebp, sbp와 완전히 반대의 역할을 한다. 이러한 명령을 통해 스택 프레임은 완전히 이전의 스택 프레임으로 복구될 수 있다. 하지만 아직 완전히 복구된 건 아니고 스택의 top에 call명령으로 저장된 복귀 주소가 있다. ret명령은 이 주소를 pop하고 이 주소로 EIP를 세팅한다.


함수 종료 직후

opcode 

registers 

	frame(1, 2, 3, 4);
  00C81039  push        4  
  00C8103B  push        3  
  00C8103D  push        2  
  00C8103F  push        1  
  00C81041  call        00C81000  
->00C81046  add         esp,10h
EAX = 00000001 EBX = 0037E000
ECX = 00000001 EDX = 00000000
ESI = 00C81470 EDI = 00C81470
EIP = 00C81046 ESP = 0018FDD8
EBP = 0018FE34 EFL = 00000200

 stack

0x0018FDD8  00000001  ....
0x0018FDDC  00000002  ....
0x0018FDE0  00000003  ....
0x0018FDE4  00000004  ....

그림 9 함수 종료 직후

함수를 호출하기 직전인 그림 3과 비교해보자. 스택 프레임을 구성하는 EBP와 ESP의 값이 같고 스택 프레임의 내용이 동일하다. 스택 프레임이 함수 호출 이전의 상태로 완전히 복구되었다. 그리고 add esp, 10h 명령으로 스택에 argument들을 저장하기 이전의 상태로 스택의 top을 이동한다.


종합

스택 프레임의 생성과 소멸을 한 과정씩 살펴보았다. 아래의 내용은 스택 프레임에 대한 전체적인 설명이다.

스택 프레임은 그림 10의 4개 명령으로 생성되고 소멸된다. 첫 두 명령은 함수 prologue, 마지막 두 명령은 함수 epilogue라고 한다.

EBP는 스택 프레임의 base역할을 하며 EBP가 가리키고 있는 곳에는 이전 스택의 EBP가 저장되어있다. 이 곳은 SFP(Stack Frame Pointer)라고 한다. EBP의 값과 EBP가 가리키고 있는 값의 의미를 잘 구별해야한다. SFP를 통해 이전 스택으로 복귀할 수 있으며 이전 스택의 EBP를 push하여 저장하였기 때문에 pointer라는 이름을 갖는다.

지역 변수와 argument는 EBP를 기준으로 접근한다. 그림 11을 보면 argument의 저장 순서, 지역변수 할당 순서를 쉽게 기억할 수 있다. EBP에 가까울수록 index가 작고 EBP에서 멀어질수록 index가 커진다.


push ebp

mov ebp, sbp

...

Function code

... 

mov esp, ebp

pop ebp

그림 10 함수 prologue와 epilogue


+---------+---------+---------+---------+-----+-----+-------+-------+-------+-------+

| LOCAL 4 | LOCAL 3 | LOCAL 2 | LOCAL 1 | SFP | RET | ARG 1 | ARG 2 | ARG 3 | ARG 4 |

+---------+---------+---------+---------+-----+-----+-------+-------+-------+-------+

lower address                              |                           higher address

                                           +-EBP

그림 11 스택 프레임의 전체적 모습


참고

Visual Studio에서 위의 예제를 재현하면 어셈블리 코드가 달라질 수 있다. 위와 같은 어셈블리에서 디버깅을 진행하려면, 프로젝트 속성에서 C/C++ -> 코드 생성 -> 기본 런타임 검사를 /RTCu, 링커 -> 일반 -> 증분링크 사용을 /INCREMENTAL:NO로 설정해야한다.

https://msdn.microsoft.com/library/8wtf2dfz(v=vs.110).aspx

https://msdn.microsoft.com/en-us/library/4khtbfyf.aspx


함수 호출 규약

함수를 호출하는 방법에는 __cdecl, __stdcall, __fastcall, __clrcall, __thiscall ,__vectorcall 등 여러 방법이 있다. 주로 보게 되는 앞의 3개에 대해 설명한다.


예제 코드

void __cdecl cde(int a, int b, int c, int d) {}


void __stdcall stdcall(int a, int b, int c, int d) {}


void __fastcall fastcall(int a, int b, int c, int d) {}


class test

{

public:

void thiscall(int a, int b, int c, int d) {}

};


int main(int argc, char **argv)

{

cde(1, 2, 3, 4);

stdcall(1, 2, 3, 4);

fastcall(1, 2, 3, 4);

test call = test();

call.thiscall(1, 2, 3, 4);

}


void __cdecl cde(int a, int b, int c, int d) {}

01081000  push        ebp  

01081001  mov         ebp,esp  

01081003  sub         esp,40h  

01081006  push        ebx  

01081007  push        esi  

01081008  push        edi  

01081009  pop         edi  

0108100A  pop         esi  

0108100B  pop         ebx  

0108100C  mov         esp,ebp  

0108100E  pop         ebp  

0108100F  ret  


void __fastcall fastcall(int a, int b, int c, int d) {}

01081010  push        ebp  

01081011  mov         ebp,esp  

01081013  sub         esp,48h  

01081016  push        ebx  

01081017  push        esi  

01081018  push        edi  

01081019  mov         dword ptr [ebp-8],edx  

0108101C  mov         dword ptr [ebp-4],ecx  

0108101F  pop         edi  

01081020  pop         esi  

01081021  pop         ebx  

01081022  mov         esp,ebp  

01081024  pop         ebp  

01081025  ret         8  


void __stdcall stdcall(int a, int b, int c, int d) {}

01081030  push        ebp  

01081031  mov         ebp,esp  

01081033  sub         esp,40h  

01081036  push        ebx  

01081037  push        esi  

01081038  push        edi  

01081039  pop         edi  

0108103A  pop         esi  

0108103B  pop         ebx  

0108103C  mov         esp,ebp  

0108103E  pop         ebp  

0108103F  ret         10h  


class test

{

public:

void thiscall(int a, int b, int c, int d) {}

01081050  push        ebp  

01081051  mov         ebp,esp  

01081053  sub         esp,44h  

01081056  push        ebx  

01081057  push        esi  

01081058  push        edi  

01081059  mov         dword ptr [ebp-4],ecx  

0108105C  pop         edi  

0108105D  pop         esi  

0108105E  pop         ebx  

0108105F  mov         esp,ebp  

01081061  pop         ebp  

01081062  ret         10h  

};


스택을 정리하는 두 가지 방법

스택 프레임을 살펴볼 때, 함수가 완료된 후 함수를 호출한 쪽에서 add esp, x를 실행하여 전달했던 인자를 정리했다. 제목과 같이 이와 반대로 호출된 함수 내부에서 스택을 정리할 수도 있다.

예제 코드에서 stdcall을 호출하는 곳(0x0108109B)을 보면 cde를 호출하는 곳과 달리 add esp, x 명령이 없다. 대신 stdcall의 마지막 부분(0x0108103F )을 보면 ret 10h라는 명령이 있다. 이는 전달된 4개의 argument의 크기인 16(10h)바이트 만큼 스택을 정리하라는 것이다.


__cdecl

void __cdecl cde(int a, int b, int c, int d) {}

01081000  push        ebp  

01081001  mov         ebp,esp  

01081003  sub         esp,40h  

01081006  push        ebx  

01081007  push        esi  

01081008  push        edi  

01081009  pop         edi  

0108100A  pop         esi  

0108100B  pop         ebx  

0108100C  mov         esp,ebp  

0108100E  pop         ebp  

0108100F  ret

cde(1, 2, 3, 4);

01081083  push        4  

01081085  push        3  

01081087  push        2  

01081089  push        1  

0108108B  call        01081000  

01081090  add         esp,10h

 

이 방식은 C언어에서 기본적으로 사용하는 방식이다. Argument를 역순으로 저장한다. 스택의 정리는 호출한 쪽에서 수행한다.


__stdcall

void __stdcall stdcall(int a, int b, int c, int d) {}

01081030  push        ebp  

01081031  mov         ebp,esp  

01081033  sub         esp,40h  

01081036  push        ebx  

01081037  push        esi  

01081038  push        edi  

01081039  pop         edi  

0108103A  pop         esi  

0108103B  pop         ebx  

0108103C  mov         esp,ebp  

0108103E  pop         ebp  

0108103F  ret         10h


stdcall(1, 2, 3, 4);

01081093  push        4  

01081095  push        3  

01081097  push        2  

01081099  push        1  

0108109B  call        01081030

 

이 방식은 Win32API를 호출할 때 기본적으로 사용하는 방식이다. Argument를 역순으로 저장한다. 스택의 정리는 호출된 쪽에서 수행한다.


__fastcall

void __fastcall fastcall(int a, int b, int c, int d) {}

01081010  push        ebp  

01081011  mov         ebp,esp  

01081013  sub         esp,48h  

01081016  push        ebx  

01081017  push        esi  

01081018  push        edi  

01081019  mov         dword ptr [ebp-8],edx  

0108101C  mov         dword ptr [ebp-4],ecx  

0108101F  pop         edi  

01081020  pop         esi  

01081021  pop         ebx  

01081022  mov         esp,ebp  

01081024  pop         ebp  

01081025  ret         8


fastcall(1, 2, 3, 4);

010810A0  push        4  

010810A2  push        3  

010810A4  mov         edx,2  

010810A9  mov         ecx,1  

010810AE  call        01081010

 

Argument 중 왼쪽에서 두 개의 argument는 각각 ECX와 EDX 레지스터에 저장된다. 그 밖의 argument는 역순으로 스택에 저장된다. 스택의 정리는 호출된 쪽에서 수행한다.


__thiscall

class test

{

public:

void thiscall(int a, int b, int c, int d) {}

01081050  push        ebp  

01081051  mov         ebp,esp  

01081053  sub         esp,44h  

01081056  push        ebx  

01081057  push        esi  

01081058  push        edi  

01081059  mov         dword ptr [ebp-4],ecx  

0108105C  pop         edi  

0108105D  pop         esi  

0108105E  pop         ebx  

0108105F  mov         esp,ebp  

01081061  pop         ebp  

01081062  ret         10h


call.thiscall(1, 2, 3, 4);

010810B3  push        4  

010810B5  push        3  

010810B7  push        2  

010810B9  push        1  

010810BB  lea         ecx,[ebp-5]  

010810BE  call        01081050

 

이 방식은 C++ 클래스의 내부 멤버 함수를 호출할 때 사용된다. Argument는 역순으로 스택에 저장되고, this포인터가 ECX 레지스터에 저장된다.


종합

 이름 

 스택 정리 

 argument 전달 

 기타

 __cdecl

 Caller

 역순

 C언어 기본

 __stdcall

 Caller

 역순

 Win32API

 __fastcall

 Caller

 역순

 왼쪽의 두 인자는 ECX, EDX에 저장

 __thiscall

 Caller

 역순

 멤버함수 호출 방식. this포인터가 ECX에 저장

스택 정리, argument 전달 외에 이름 데코레이션, 대소문자 변환, 64비트 환경에서 허용 여부, argument 속성에 따른 변화 등 많은 항목이 calling convention마다 다르다. 더 자세한 내용은 MSDN(https://msdn.microsoft.com/en-us/library/984x0h58.aspx)을 참고하길 바란다.


Abex crackme 2

시리얼 인증하는 문제이다.


디버거 실행 후 문자열들을 보면 메시지 박스들에 쓰이는 문자열들을 찾을 수 있다.


실패 메시지를 출력하는 부분 위에 성공 메시지를 출력하는 부분이 있다.


Jump from 00403332을 보아 실패의 경우 00403332에서 jump됨을 알 수 있다.


00403332부분을 보면 AX가 0일 경우 실패 메시지를 출력하는 곳으로 jump한다.


00403332의 jump문을 nop로 patch한다.


Patch할 경우 key는 맞다고 출력되지만 serial은 틀렸다고 출력된다.


이 부분이 serial이 틀렸다고 출력하는 곳이다. 이 곳에서 위쪽으로 jump문들을 찾아보자.


00403424에서 serial이 틀렸을 경우 jump되지 않고 실패 메시지가 출력된다.


이 부분을 무조건 jump인 jmp로 수정한다.


Lena’s reversing for newbies

모든 Nag를 없애고 registration코드를 찾는 문제이다. 디버거에서 파일을 실행한 후 Nag Screen이 출력되었을 때 pause를 눌러 스택 프레임을 살펴보겠다.


스택 프레임을 보는 이유는 이 창을 띄운 부분의 위치를 찾기 위함이다. Return 주소가 프로그램 코드 영역인 곳이 나올 때까지 깊게 들어가다 보면 00402D03에서 이 메시지 박스를 호출했다는 걸 알 수 있다.



메시지 박스를 출력하는 00402CFE를 NOP으로 patch한다.


Patch를 하고 실행한 경우 오류가 발생한다. 스택 프레임을 조사하여 어느 코드에서 오류가 발생했는지 찾아보자.


가장 가까운 프로그램 코드 영역인 00402D5A를 수행하다가 오류가 발생하였다.


이 오류를 피하기 위해 NOP 대신 오류가 발생하는 00402D5A 코드 바로 다음의 코드인 00402D5D영역으로 jump하도록 patch한다.


Nag Screen은 오류 없이 뜨지 않게 되었다. 코드를 인증하는 부분에 아무 코드를 입력하고 Register me! 버튼을 클릭하면 위처럼 실패 메시지가 출력된다.


아까와 같은 방법으로 스택 프레임을 조사하여 어느 부분에서 메시지 박스를 출력하는지 찾아본다. 00402AED에서 메시지 박스를 출력한다.


Jump from을 따라가본다.


Jump from을 따라가다 보면 위 부분까지 오게 된다. 이 부분이 성공 여부를 결정한다. 그 위에 문자열을 비교하는 것처럼 보이는 __vbaStrCmp함수를 호출한다.


이 함수를 호출할 때 스택에 어떤 argument들을 넣는지 보니 처음에 입력한 문자열 “abcd”와 “I’mlena151” 이라는 두 문자열이 들어간다.



다시 돌아와서 Regcode에 I’mlena151을 넣어 실행해보면 성공 메시지가 출력된다.



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

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
Reverse Engineering Chap 13  (0) 2016.01.12
Reverse Engineering Chap1-2  (0) 2016.01.10
Posted by 지환태