2016. 12. 31. 14:31 :: 리버싱

 

안녕하세요. Message 입니다.

오늘은 오랜만에 리버싱과 관련된 2가지 주제로 포스팅을 하려 합니다.

① UPack PE 헤더 상세 분석

② 디버깅 - OEP 찾기

 

<리버싱 핵심 원리 - 이승원님> 책에 있는 내용을 베이스로 하지만,

공부하는 입장에서 진행과정을 조금 더 보강하거나

당연한 내용이기에 자연스럽게 책에서 생략된(저는 모르는..) 내용들도 추가하려 합니다.

얼마전 실행압축으로 인해 분석을 보류했던 악성코드가 있었습니다. 각종 도구들을 이용해도 탐지가 안되더군요.

이번 공부가 패킹된 악성코드와 PE구조를 이해하는데 좋은 밑거름이 되었으면 좋겠습니다.

 


 

-----------------------------------------------------------------------------------------------------------------

0x00 준비

-----------------------------------------------------------------------------------------------------------------

 

홈페이지가 변경되어, 책에 있는 링크가 아닌 아래의 링크에서 UPack 0.39 final 버전을 다운받습니다.

Windows 버전도 있는듯 하지만, CMD 버전으로 다운로드 받았습니다.

URL : http://www.geocities.jp/dwingj/mycomp.htm

 

명령어도 매우 간단합니다. UPack으로 notepad.exe 파일을 패킹합니다.

사실 저는 처음에 OK! 문구 하나만 확인하고 그냥 창을 꺼버렸습니다만,

UPack이 어떻게 패킹을 수행하는지 대략적인 흐름을 알 수 있는 문구들이 있습니다.

지금은 그냥 읽어보고 넘어가세요. 분석 중간중간에 해당 사항들을 언급하겠습니다.

 - Rebuilding import table [00C8]

 - Recompiling resuorce [008304]

 - Remocing debug data [001C]

 - Transforming code

 - Compressing data <Use 128KB dict>

 - Building new PE data

 

파일의 원본과 UPack 으로 패킹된 프로그램의 헤더를 살펴봅니다.

섹션헤더는 모두 사라졌고, NT_Header 에서 중요한 OPTIONAL_Header 역시 찾아볼 수 없습니다.

또한 IMAGE_DOS_Header에서 KERNEL32.DLL을 비롯한 API 이름까지 보이고 있습니다.

확실히 정상적인 PE구조와는 확연히 다름을 알 수 있습니다.

 

 

 

-----------------------------------------------------------------------------------------------------------------

0x01 헤더분석

-----------------------------------------------------------------------------------------------------------------

 

1. 헤더 겹쳐쓰기(=e_lfanew 조작)

 

DOS_Header에서는 2가지 멤버가 중요합니다.

그 외는 프로그램 실행에 아무 의미가 없습니다. 위치상으로는 첫번째와 마지막 멤버입니다.

① e_magic : DOS Signature (4D5A, "MZ")

② e_lfanew : NT_header의 시작주소(파일에 따라 가변적)

 

정상적인 프로그램의 e_lfanew 값이 계산되는 방식은 통상적으로 아래와 같습니다.

e_lfanew = DOS_Header(40) + DOS_Stub(A0, 가변) = E0(224)

하지만 Upack에서는 e_lfanew 값이 10(리틀엔디안) 입니다.

 

PE 스펙에 어긋나진 안지만 스펙 자체의 허술함을 이용한 것이며,

NT_Header를 의미하는 COFF_File_Header를 클릭하면 두번째 라인부터 시작되며,

헤더의 크기 측면으로 보면 E0 → 10 으로 변경되면서 D0(208) 의 공간이 절약되었습니다.

 

 

 

2. IMAGE_FILE_HEADER.SizeOfOptionalHeader 조작

 

UPack은 NT_Hedaer 안에 있는 FILE_Header에서 SizeOfOptionalHeader 값을 조작합니다.

이 값을 변경하여 헤더 안에 디코딩 코드를 삽입하기 위한 목적입니다.

원래 SizeOfOptionalHeader 값의 의미는 말그대로 뒤따르는 OPTIONAL_Header의 구조체 크기입니다.

원래 정상적이라면 E0(224) 값을 가져야 하지만, Upack은 148로 변경합니다.

 

그렇다면 왜 UPack은 SizeOfOptionalHedaer 의 값을 변경할까요?

SECTION_HeaderOPTIONAL_Header 바로 뒤에 위치하는게 당연할거라고 생각되지만

OPTIONAL_Header의 시작주소(Offset)SizeOfOptionalHedaer 값을 더한 위치에서 시작합니다.

즉, 간단하게 헤더의 시작위치에서 헤더의 크기를 더하는 공식에서 헤더의 크기인 SizeOfOptionalHedaer 값을 조작한거죠.

아래 그림은 원본파일(notepad.exe)의 1st 섹션(.text)이 계산된 과정을 보여줍니다.

 

UPack은 이러한 특성을 이용하여 PE 헤더를 꽈배기처럼 꼬아놓고

남는 공간에 디코딩에 필요한 코드를 적절히 끼워 넣는 특성을 가집니다.

SECTION_Header1D8이 아닌 170에서 시작하고 있습니다.

 

결과적으로, 아래 그림처럼 OPTIONAL_Header의 뒷부분에 존재하는 DATA_DIRECTORY가 끝나는 108(264) 부터

SECTION_Header가 시작되는 28(OPTIONAL_Header Offset) + 148(SizeOfOptionalHedaer) = 170(368) 사이에

68(104)의 공간이 생겼습니다. byte 길이 계산하실때 실수를 방지하기 위해 HxD 툴을 사용하면 편리합니다.

 

 

 

3. IMAGE_OPTIONAL_HEADER.NumberOfRvaAndSizes 조작

 

Upack은 OPTIONAL_Hedaer 안에 있는 NumberOfRvaAndSizes 값 역시 변경합니다.

이 값의 의미는 바로 뒤에 이어지는 DATA_DIRECTORY 구조체 배열의 원소 개수입니다.

해당 값을 변경하는 이유는 방금전 위에서 확보했던 68(104)만큼의 공간에 추가적인 공간을 확보하여

자신의 코드를 삽입하기 위한 목적입니다.

 

정상적인 파일에서는 DATA_DIRECTORY 구조체 배열의 원소 개수는 이미 10(16) 이지만,

UPack에서는 A(10) 으로 변경됩니다. 따라서 TLS_Table 원소 뒤에 존재하는 6개의 원소는 무시됩니다.

사실, DATA_DIRECTORY에서 중요한 원소는 EXPORT, IMPORT, RESOURCE, TLS Directory 이므로,

UPack이 TLS_Directory 원소 이후의 원소를 무시하는것은 자연스러운 결과일지도 모르겠습니다.

 

결국, 위에서 SizeOfOptionalHedaer의 값을 변경하여 얻은 68 크기의 공간에서

NumberOfRvaAndSizes 값 변경으로 DATA_DIRECTORY 6개 원소 크기 30을 더해 98 만큼의 공간이 생겼습니다.

 

UPack에서는 해당 공간에 아래와 같은 디코딩 코드를 삽입합니다.

처음엔 NT_HedaderSECTION_Header 사이에 코드를 삽입해서 어떻게 사용할건지 의문이 들었습니다.

해당 영역은 메모리에 올라가면 010000D8 주소에 있을테니까요.

 

아래 부분을 이해하려면 바로 아래 파트의 "섹션 겹쳐 쓰기"를 먼저 공부해야합니다.

하지만 SECTION_Header에 섹션의 시작 오프셋을 가리키는 PointerToRawData10임을 주목해야 합니다.

책에 있는 그림 18.17 [UPack의 겹쳐쓰기 특징] 처럼 PE 헤더 영역에 속한 D8 주소에 있는 데이터가

1st 섹션 영역에서 동일하게 나타남을 의미합니다. 아래 그림처럼 해당 섹션의 VirtualOffset(1000)이 추가된 영역에서 말이죠.

눈치 빠르신 분은 010010D8 주소에서 PointerToRawData10을 뺀 주소가 아니라 의아함을 느끼실 수 있습니다만,

RVA to RAW 파트에서 해당 부분에 대해 자세하게 살펴보겠습니다. 어쨋든 동일한 코드가 존재한다는게 중요합니다.

  헤더영역

  섹션영역

 

사실 저같은 경우, 분석을 하면서 NumberOfRvaAndSizes 값이 변경되었다는 사실을 단번에 파악하기엔

초보운전자가 도로상황을 전부 파악하기 힘든것과 일맥상통하다고 생각합니다.

하지만 시각적인 단서가 있다면 좀더 빨리 알아차릴 수 있겠죠.

아래 그림처럼 CFF에서 살펴보면, UPack과 원본파일의 DATA_DIRECTORY 구조체 개수와

Invalid 항목등을 통하여 NumberOfRvaAndSizes 값 변경을 통한 패킹을 의심해볼만 합니다.

 

 

 

 

4. 섹션 겹쳐쓰기

 

UPack은 SECTION_Header에서도 프로그램 실행시 사용되지 않는 항목에 자신의 데이터를 기록합니다.

Stud_PE를 이용해서 UPack의 SECTION_Header를 살펴보면 수상한 점이 2개입니다.

 

첫번째 수상한 점은 1st 섹션 & 3st 섹션이 겹쳐있다는 점입니다.

Stud_PE를 보면 RawOffset, RawSize 두가지 요소가 각각 10(16), 1F0(496)으로 동일합니다.

(참고로 오프셋은 10(16)은 아까 보았던 NT_Header가 시작되는 영역입니다.)

UPack은 동일한 파일 이미지로 각각 다른 위치/크기의 메모리 이미지를 만들 수 있는 헛점을 이용했습니다.

 

두번째 수상한 점은 1st 섹션 & 3st 섹션과는 달리, 매우 큰 2st 섹션의 크기입니다.

2st 섹션의 크기는 무려 AE28(44,584) 이며, 파일의 대부분을 차지하고 있습니다.

또한 1st 섹션의 VirtualSize 역시 파일의 RawSize에 비해 매우 큰 14,000(81,920) 입니다.

일반적으로 SECTION_HeaderVirtual SizeSize of Raw Data보다 월등히 크다면 패킹을 의심합니다.

 

Upack이 이러한 특성을 보이는 이유는 2st 섹션에 원본파일(notepad.exe)이 압축되어 있기 때문이며,

두번째 섹션이 메모리에 로딩될때 압축이 풀리며 1st 섹션에 기록하기 때문입니다.

VirtualSize 멤버 값은 메모리에서 섹션이 차지하는 크기를 의미하는데,

원본파일(notepad.exe)이 메모리에 로딩될때의 사이즈인 OPTIONAL_Header - SizeOfImage 값과 동일합니다.

따라서 AE28(44,584) 크기로 압축된 2st 섹션이 메모리에 로딩될 때 온전하게 1st 섹션에 기록될 수 있습니다.

또한, 원본파일(notepad.exe)의 이미지가 통째로 풀리기 때문에 프로그램이 정상적으로 실행됩니다.

 

  

 

5. RVA to RAW

 

각종 PE 유틸리티들이 UPack으로 패킹된 PE를 만나서 강제 종료되었던 이유는

RVA → RAW 변환에 어려움을 겪었기 때문이라고 합니다.

UPack의 제작자는 많은 테스트를 통해서 Windows PE 로더의 버그를 알아낸 후 이를 UPack에 적용합니다.

아래는 책에 기재되어 있는 RVA → RAW 변환 공식입니다.

RAW - PointerToRawData = RVA - VirtualAddress

RAW = RVA - VirtualAddress + PointerToRawData

-----------------------------------------------------------

*PointerToRawData : 파일에서 섹션의 시작 위치

*VirtualAddress : 메모리에서 섹션의 시작주소 = RVA

 

위 공식대로 UPack 샘플에서 EP의 RAW(파일오프셋)를 계산해봅니다.

위의 공식대로 RAW를 계산하려면 3가지를 알아야 합니다.

RVA

VirtualAddress

PointerToRawData

 

RVA 값은 OPTIONAL_HedaerAddressOfEntryPoint 1018(4,120) 입니다.

 

VirtualAddress 값은 RVA값이 속해 있는 섹션의 메모리 오프셋입니다.

1018(4,120) 값은 Stud_PE에서 VirtualSize의 값과 VirtualOffset을 고려하여 판단합니다.

첫번째 섹션 영역이 1000 ~ 14FFFF 이므로, 해당 주소는 첫번째 섹션에 속합니다. 

따라서 VirtualAddress = 1000(4096) 입니다.

자연스럽게 PointerToRawData 값은 첫번째 섹션의 RawOffset = 10(16) 입니다.

 

구한 값들을 바탕으로 공식을 적용하면 아래와 같습니다.

RAW = 1018 - 1000 + 10 = 28

Hex editor로 살펴보면, 뭔가 이상합니다. 코드가 아니라 문자열이 존재하기 때문이지요.

이것은 UPack의 특성중 하나인, PointerToRawData 값을 이용한 트릭입니다.

 

일반적으로 섹션 시작의 파일 오프셋을 가리키는 PointerToTawData 값은 FileAlignment의 배수입니다.

UPack의 FileAlignment의 값은 NT_Header - OPTIONAL_Header에서 200으로 명시되어 있으며, 

일반적으로도 PointerToTawData 값은 0, 200, 400 등의 값을 가진다고 합니다.

 

하지만 UPack으 PointerToRawData 값이 10으로 지정되어 있습니다.

FileAlignment(200)의 배수가 아니기 때문에 PE로더는 강제로 FileAlignment 배수에 맞춰서 인식합니다. (이경우는 0)

이것이 바로 UPack 파일이 정상적으로 실행은 되지만, PE 유틸리티에서 에러가 발생한 이유입니다.

따라서 이것을 적용한 공식은 아래와 같습니다.

RAW = 1018 - 1000 + 0 = 18

 

그렇다면 이제 디버거를 통해 메모리에 올라간 이미지와(1018) 파일의 오프셋(18)에 있는 데이터를 비교하여

코드가 동일하다면, RVA → RAW 변환이 잘 이루어진 것입니다. (아래그림 참조)

 

 

 

6. Import Table(IMAGE_IMPORT_DESCRIPTOR array)

 

① 원본파일 Import Table

UPack의 IMPORT_Table 역시 매우 특이하게 구성되어 있습니다.

차이점을 알기 위해서 원본파일(notepad.exe)의 정상적인 구조부터 살펴보겠습니다.

PE파일은 자신이 어떤 라이브러리를 임포트하는지 IMAGE_IMPORT_DESCRIPTOR(IID) 구조체에 명시합니다.

IID는 이번 소단원의 제목과 같이 IMPORT_Directory_Table(IDT)이라는 용어로 부르기도 합니다.

아래 그림은 VIsual Studio 에서 확인한 IMAGE_IMPORT_DESCRIPTOR(IID) 구조체입니다.

 

IID or IDT는 NT_Header 내부의 OPTIONAL_Header에 위치하며,

DATA_DIRECTORY 구조체의 두번째 멤버 IMPORT_Table 항목에 RVA, Size 값이 명시되어 있습니다.

 

해당 주소값을 찾아가보면, IMPORT_Table이 존재하는 곳은 PE 헤더가 아닌 PE 바디입니다.

PE View 도구에서는 IMPORT_Directory_Table 항목으로 명시해주고 있으며,

20byte 단위의 IMAGE_IMPORT_DESCRIPTOR(IID) 구조체 목록이 존재함을 확인할 수 있습니다.

마지막은 NULL 구조체로 구성됨을 체크하고 넘어갑니다.

 

IID에서는 API 목록 RVA 값을 가지는 멤버 2개가 존재합니다.

OriginalFirstThunk = INT(Import Name Table)

FirstThunk = IAT(Import Address Table)

해당 멤버들이 실제 PEView에서 아래와 같은 위치에 표현되고 있음을 체크합니다.

 

실제로 INT(Import Name Table), IAT(Import Address Table) 내부를 들여다보면

HintName이 존재하는것은 동일하지만, IAT는 VA, INT는 RVA값이 존재하고 있습니다.

 

마지막으로 해당 IMAGE_IMPORT_DESCRIPTOR(IID) 구조체 목록을 파일 RAW에서도 살펴봅니다.

20byte의 구조체 목록임을 확인하고, IMPORT_Table이 NULL 구조체로 끝나는지 확인합니다.

START RAW = 7604 - 1000 + 400 = 6A04

END RAW = 76C8 - 1000 + 400 = 6AC8

 

 

② UPack Import Table

정상적인 원본파일(notepad.exe)의 IMPORT_Table을 살펴보았으니, 이제 UPack의 차례입니다.

UPack에서는 PEView가 정상적으로 동작하지 않으므로, 직접 파일의 RAW에서 값을 살펴봅니다.

방법은 동일하게 DATA_DIRECTORY에서 RVA를 얻습니다. 값은 271EE 입니다. (리틀엔디안 주의)

 

271EE가 속하는 섹션은 3st 섹션의 VirtualOffsetVirtualSize를 통해 27000 임을 알 수 있습니다.

PointerToRawData의 경우 10 이지만, 위에서의 트릭에 유의하여 0으로 수정합니다.

이를 이용한 계산 결과는 아래와 같습니다.

RAW 271EE 27000 - 0 = 1EE

 

Hex Editor를 통해 해당 주소값을 살펴보면 아래와 같습니다.

구조체의 대부분이 0 이기 때문에, 각 멤버의 값을 분별하기 어렵지만 구조체를 보면서 값을 채워봅니다.

 

4byte씩 끊어서 보면 아래와 같으며, Name 멤버는 KERNEL32.DLL을 나타냅니다.

계속 섹션에서 RVA → RAW 계산 하느라 정신없지만, 헤더영역은 RVA와 RAW값이 동일합니다.

- INT :: OriginalFirstThunk = 0000 0000  (0일 경우 IAT값 참조)

- TimeDataStamp = 0000 0000

- FowarderChain = 0000 0000

- Name = 0000 0002 (KERNEL32.DLL)

- IAT :: FirstThunk = 0000 11E8 (Little Endian)

 

하지만, 문제는 그 다음 구조체입니다.

Name 멤버가 가르키는 문자열도 없으며, IMPORT_Table의 끝을 나나태는 NULL 구조체도 아닙니다.

- INT :: OriginalFirstThunk = 0000 0000 (0일 경우 IAT값 참조)

- TimeDataStamp = 0000 0000

- FowarderChain = 0000 0000

- Name = 0003 0008

- IAT :: FirstThunk = 0005 0000 (Little Endian)

 

이것은 PE스펙에 어긋난 듯이 보이지만, 섹션이 로딩될때의 헛점을 노린 UPack의 트릭입니다.

다시한번 Stud_PE에서 3st 섹션의 멤버값들을 확인해보겠습니다.

3st 섹션의 크기는 1F0이며, 시작지점은 10이므로, 로딩되는 영역은 10 ~ 1FF 입니다.

정확한 크기값은 Hex Editor로 체크하면 정확합니다.

 

하지만 RawOffset은 PE로더에 의해 10 0 으로 강제 변환 되므로

결론적으로 VA 27000 위치에 파일의 0 ~ 1FF 영역이 로딩되며, 길이는 200입니다.

실제로 3st 섹션의 시작부분인 VA 27000을 살펴보면, DOS_Header의 시그니처인 "MZ"로 시작되고 있습니다.

 

또 염두해두어야 할 부분은 3st의 VirtualSize1000 이므로, 271FF 영역 이후부터는

NULL 값으로 채워집니다. 즉, IMPORT_Table의 마지막 멤버가 NULL 구조체가 아니어도 정상 실행됩니다.

 

그렇다면 마지막으로 UPack이 KERNEL32.DLL에서 어떤 API를 임포트하는지

실제로 IAT를 따라가서 확인해 보겠습니다. IAT의 RVA 값은 11E8 이었으므로, RAW값은 아래와 같습니다.

RAW = 11E8 - 1000 + 0 = 1E8

 

1E8 주소로 가보면, INT가 있습니다.

INT, IATIMAGE_IMPORT_BY_NAME 구조체를 가리키는 Name_Pointer(RVA) 배열이며, 끝은 NULL입니다.

따라서 UPack의 경우 2개의 API를 임포트하고 있음을 알 수 있습니다.

 

IMAGE_IMPORT_BY_NAME 구조체는 2byte Hint 멤버와 Name 배열 멤버로 구성됩니다.

 

첫번째 주소는 28이며, Hint0B01과 함께 LoadLibraryA API 이름이 존재합니다.

두번째 주소는 BE이며, Hint0000과 함께 GetProcAddress API 이름이 존재합니다.

 

 

 

-----------------------------------------------------------------------------------------------------------------

0x02 디버깅 - OEP 찾기

-----------------------------------------------------------------------------------------------------------------

 

1. OllyDbg 실행 에러 

Upack은 PE헤더를 독특하게 변경하는 것이 문제가 되고, 안티 디버깅 기법은 없습니다.

일단 OllyDbg를 이용해 패킹된 파일을 실행시키면 에러 메시지가 출력된다고 합니다.

하지만 OllyDbg 201 버전에서 실행하면 오류는 커녕 EP까지 잘 찾아주는 현상(?)이 발생했습니다.

 

크리티컬한 에러가 아니라고 저자분께서 적어 주셨기 때문에, 그냥 넘어가도 될것 같지만.. 

디버깅을 연습도 할겸 버전을 낮추어 실행해보기로 했습니다.

OllyDbg 110 버전에서 실행시키면 아래와 같이 오류창이 뜹니다.

에러의 원인은 UPack이 OPTIONAL_Header에서 NumberOfRvaAndSizes 값을 10(16)  A(10)

변경했기 때문에 OllyDbg의 초기검증 과정에서 에러가 발생합니다.

 

또한 위와 같은 에러로 인하여 OllyDbg는 EP로 가지 못하고 아래와 같이 ntdll.dll 영역에서 멈춥니다.

OllyDbg의 버그 또는 엄격한 PE체크 때문에 발생하는 현상입니다.

 

그렇다면 직접 EP를 설정해주기 위해 Stud_PE를 통해 기본 정보를 수집합니다.

EntryPoint의 RVA값은 1018이며, ImageBase01000000입니다.

따라서 EntryPoint의 VA값은 01001018입니다.

 

OllyDbg의 Code창에서 01001018로 이동한후

"New Origin here" 명령을 이용하여 강제로 EIP를 변경합니다.

이미 EP로 설정된 곳에서 우클릭하면 해당 메뉴가 나타나지 않으니 주의하세요.

 

 

 

2. BP걸고 달리기

 

모든 패커에는 디코딩(Decoding Loop) 루프가 존재한다고 합니다.

압축/해제 알고리즘 자체가 많은 조건 분기와 루프로 구성되어 있다보니 필연적입니다.

이러한 디코딩 루프를 디버깅할 때에는 조건 분기를 적절히 건너뛰어서 루프를 탈출해야 합니다.

레지스터를 잘 보면서 어떤 주소에 값을 쓰고 있는지 잘 살펴야 하지만.. 많은 경험이 필요합니다.

 

Upack은 두번째 섹션에 압축된 원본 데이터가 존재하고,

이 데이터를 디코딩 루프를 돌면서 첫번째 섹션에 압축해제 합니다.

그럼 EP코드부터 디버깅을 시작합니다!

 

처음 두명령은 010011B0 주소에서 4byte를 읽어서 EAX에 저장하는 명령어입니다.

이는 원본 notepad의 OEP(Original Entry Point) 입니다만, 아직은 모른다는 가정하에 진행합니다.

LODS 명령어는 ESI가 가르키는 주소에서 4byte를 읽어서 EAX 레지스터에 저장하는 기능이며, ESI 값을 증가시킵니다.

 

EAX에 저장된 명령어가 OEP인줄 알고 있다면, BP를 걸고 달리면 된다고 합니다.

해당 주소로 가보면 NULL로 채워져 있는 공간 뿐이어서 당황했습니다.

 

하지만 BP를 걸고 달리면 없던 코드가 생겨나면서 OEP로 추정되는 코드가 나타납니다.

패킹되지 않은 원본파일의 OEP와 비교해보니, 오른쪽의 설명을 제외한 코드가 모두 일치했습니다.

이로서 "BP를 걸고 달린다"의 의미를 어느정도 알 것 같네요.

  UPack      정상

 

 

 

 

3.압축해제, 디코딩을 위한 초기 세팅(?)

이어서 트레이싱을 계속 진행합니다.

OEP와 LoadLibrary 함수를 스택에 PUSH한 이후에 JMP 명령어로 010010A0 주소로 이동합니다.

GetProcAddress 함수와 01013FFF 주소에 있는 값을 동일하게 PUSH 합니다.

해당값들은 추후에 IAT를 새롭게 구성할때 POP 하여 사용합니다.

 

이후에는 아래 명령어를 실행하게 되는데, 결론부터 말하면 3st 섹션 → 2st 섹션으로 복사하는 코드입니다.

REP 명령어로 27(ECX) 횟수 만큼 반복문을 수행하기 위해 EDI, ESI레지스터에 주소값을 저장합니다.

EDI 레지스터에는 2st 섹션의 0101FE28 주소값이 저장되며, 어떤 영역인지 계산해보면

15000(VA) + AE28(RawSize) = 1FE28 이므로, 2st 섹션 끝에 채워진 NULL(0) 영역임을 알 수 있습니다.

REP MOVS DWORD PTR ES : [EDI], DWORD PTR DS : [ESI]

 

ESI 레지스터에는 3st 섹션의 010270F0 주소값이 저장되며, 구조상 1st 섹션과 동일하므로

정상적이라면 F0 주소에는 Delay_Import_Descriptor 값이 존재해야 하지만,

위에서도 살펴보았듯이 UPack이 OPTIONAL_Header에서 NumberOfRvaAndSizes 값을 10(16)  A(10)

변경하여 본인의 디코딩 코드를 삽입하기 위해 만들었던 공간 영역입니다.

 

다만, 해당 영역의 모두를 복사하고 있지 않으며, JMP 구문 이후에 영역을 복사합니다.

아마 디코딩 코드들을 한군데에 몰아넣고, 필요한 부분만 꺼내 쓰는 모양입니다.

 

아래 사진을 보시면, 위에서 공백이었던 0101FE28 주소에 4byte씩 데이터가 복사되고 있음을 알 수 있습니다.

PE헤더 분석에서 2st 섹션에는 원본파일이 압축되어 있다고 분석한바 있습니다.

왜 3st 섹션에 있는 명령어 코드를 2st 섹션 빈공간으로 복사하고 있는지는 모르겠지만, 일단은 넘어가기로 했습니다.

 

반복문이 종료된 이후의 코드를 계속 살펴봅니다.

PUSH 명령어로 DS : [ESI+4] = 190 주소가 가리키는 값 132를 Stack에 저장합니다.

3st 섹션은 1st 섹션과 동일하므로, 1st 섹션에서 190이 어떤 값인지 살펴보면 Relocations Number 입니다.

이후에 무슨 이유인지는 모르겠지만 EAX 값을 FFFFFFFF로 설정합니다.

이것이 문자열의 길이를 알아낼때 반복문의 최대치를 설정한 것인지, -1로 설정한 것인지 아직 파악이 되지 않습니다.

 

JMP 명령을 하기전에 ES : [EDI] = 0101FEC4 주소값에 EAX레지스터 값을 저장합니다.

해당 주소값은 3st 섹션에 있는 데이터를 2st 섹션에 복사한곳의 바로 뒷부분, NULL(0) 공간입니다.

 

JMP 010010D8 명령은, 방금 위에서는 뒷부분만 복사했던 디코딩 코드의 처음으로 이동합니다.

INC EAX 명령은 EAX값을 0으로 되돌리는 목적으로 보이며,

STOS 명령으로 EAX값을 저장하는데, EAX값이 0이므로 헤딩 주소의 NULL(0) 값이 유지됩니다.

 

이후에 REP명령어로 Write할 값 EAX = 1 을 저장하고, 반복횟수 ECX = 4 를 저장한뒤

아래 그림과 같이 16byte에 EAX값을 Write 합니다. (리틀엔디안 주의)

 

다음 명령어 역시 동일한 방법으로 EAX = 400, ECX = 1C00(7,168) 를 레지스터에 저장한뒤 

아래 그림과 같이 0101FEDC 주소부터 01026ECB 까지 1C00(7,168) 횟수만큼 EAX값을 Write 합니다.

이후에 "MZKERNEL32.DLL" 문자열과 ECX(=0) 레지스터를 스택에 PUSH합니다.

 

 

 

4. decode()

 

JMP 0101FD13 명령어를 통해 해당 주소로 넘어옵니다.

세번째 라인을 보면, CALL DWORD PTR DS : [ESI] 명령이 존재합니다.

이때 ESI 레지스터의 값은 0101FCCB 이며, 이게 바로 decode() 함수의 주소입니다.

 

책에서 decode() 함수라고 적혀 있어서 미리 알게되었지만,

어떤 행동을 하는지 한눈에 파악이 안됩니다만, 계속 트레이싱을 하다보면 굉장히 자주 호출됨을 알 수 있습니다.

 

잘 파악이 안될때에는 일단 실행 결과를 보면서 파악하곤 합니다.

일단 해당 함수를 CALL 하기 전에 특정 인자를 PUSH 한것은 아니지만,

레지스터로 인자를 전달하는 함수도 있으므로, 바로 전에 EDX에 할당한 주소를 체크합니다. (아래 그림 파란색)

그리고 F8 트레이싱을 하게되면, 0101FEC4 주소의 4byteFFFFFFFF 7FFFFC00 으로 변경됩니다.

정확히 어떤 메커니즘인지는 모르겠지만, 일단 2st 섹션의 특정 값을 변동시킵니다.

 

해당 작업이 다 이루어지고 나면 아래와 같이 대부분의 데이터가 채워집니다.

아마 어떤 방식으로든 채운 데이터를 활용하여 1st 섹션에 원본파일을 기록할 것으로 생각됩니다.

 

 

 

5. 디코딩 루프의 끝 + IAT 세팅

 

UPack의 디코딩 어셈블리 코드를 세세하게 분석하기엔, IDA Hex-ray에서 제공해주는 코드조차 매우 난잡합니다.

하지만 패킹의 경우, 특히 UPack은 메모리상에서 2st 섹션의 압축 원본이 1st 섹션이 풀리므로

디버깅의 초점을 섹션간의(3st 2st 또는 2st 1st) Write 행위로 바꾸었습니다.

그렇게 진행하다보면 책의 저자분이 책에 기재해놓으신 코드들 위주로 눈에 들어오게 됩니다.

 

먼저 1st 섹션에 데이터가 Write 되는 순간을 포착하기 위해 Hardware Breakpoint를 설정합니다.

이때 단일 Data size 에만 Breakpoint를 설정하면 그냥 넘어가는 케이스가 발생하여

동일주소에 3가지 Data size를 모두 설정하였습니다.

 

Breakpoint를 설정하고 F9를 눌러 첫번째 트레이싱을 진행하면 STOS BYTE OTR ES: [EDI] 코드에서 처음으로 멈춥니다.

EDI 주소에 EAX 레지스터의 값을 BYTE 단위만큼 저장하는 명령어입니다.

실제로 해당 주소에 가보면, Breakpoint를 걸었던곳의 1byte가 NULL(0)로 채워진것을 볼 수 있습니다.

반복적으로 트레이싱을 하면, UPack이 1st 섹션의 데이터를 지울때 항상 해당 명령어로 1byte를 먼저 지우는 패턴을 보입니다.

 

두번째로 멈추는곳은 몇줄 위에 있는 0101FE57 주소입니다.

REP 명령어로 ECX 레지스터에 할당된 횟수만큼 반복하며 EDI 레지스터가 가리키는 주소에 값을 씁니다.

 

F8 트레이싱으로 REP 명령어를 실행시키면

아래와 같이 1st 섹션이 NULL(0) 값으로 채워짐을 볼 수 있습니다.

UPack은 2st 섹션의 데이터를 1st 섹션에 압축해제하기 이전에 이와 같이 NULL(0) 값으로 덮어씁니다.

 

트레이싱을 계속 진행하면 NULL(0) 값으로 써진 1st 섹션에 새로운 데이터가 써집니다.

원본파일(notepad.exe)의 1st 섹션인 SECTION.text 값과 바이너리를 비교해보면 초반부는 동일합니다.

 

OllyDump와 같은 플러그인을 이용해서 원본파일(notepad.exe)의 메모리값과 비교해보면 완벽하게 일치하지 않습니다.

압축해제 되는 과정에서 약간의 변화 또는 손실이 일어나는것으로 보이지만, 실행되는데 문제는 없습니다.

첫번째로 원본과 틀린 부분은 1350 주소부터 시작하는 IMAGE_DEBUG_DIRECTORY 입니다. 

  원본 IDD

  원본 IDD

   UPack Dump

 

IMAGE_DEBUG_DIRECTORY의 경우에는 UPack으로 실행압축을 하는 과정에서

"Removing debug data [001C]" 라는 출력 문구를 통해, 실행압축 과정에서 삭제되었음을 유추할 수 있습니다.

001C의 경우 원본파일 IMAGE_DEBUG_DIRECTORY 사이즈 값과 동일한데, 사이즈를 출력해준것으로 판단됩니다.

 

두번째로 원본과 틀린 부분은 7604 주소의 IDT, INT, Hints/Names&DLL_names 입니다.

해당 멤버들은 하나같이 Import_Table과 관련된 항목들인데, NULL(0)로 채워져 있습니다.

하지만 해당 부분들이 아예 사라진것은 아닙니다. IAT세팅에서 다시 들여다 보겠습니다.

  원본 IDT

  원본 IDT

  UPack Dump

 

이부분도 역시 실행압축 과정에서 출력된 문구에서 리빌딩, 리컴파일로 언급되어 있는 사항들입니다.

대괄호로 출력된 16진수는 원본파일 IDT, RDT 항목들 사이즈와 일치합니다.

 

REP 명령어는 0101FE5E 주소에 있는 CMP 명령어가 만족될때까지 루프를 돌며 여러번 수행되며

[ ESI + 34 ] 주소가 가리키는 값은 0102718C + 34 = 010271C0 으로서, 4byte 01014B5A 값입니다.

해당값은 1st 섹션이 끝나는 주소인 15000(VA)과 매우 밀접합니다.

 

 

 

6. IAT 세팅 

 

Upack을 포함한 일반적인 패커는 디코딩 루프가 끝나면 원본 파일에 맞게 IAT를 새롭게 구성합니다.

위에서 분석했다 싶이, UPack은 kernel32.dll 하나만을 임포트하고 있으며,

 

IAT를 따라가 보면 LoadLibraryA, GetProcAddress 두개의 API 만이 존재하고 있음을 확인했습니다.

두개의 함수를 호출하여 0101FEAC 주소에 있는 STOS 명령으로 1st 섹션에 IAT를 채웁니다.

 

 

이때 참고하는 영역이 원본 파일에는 없는 01014000 주소 영역입니다.

원래 원본파일은 OllyDbg, Dump 등을 살펴보면 010133F0 주소 근처가

PADDING으로 채워진 이미지의 마지막 영역입니다.

 

하지만 UPack은 IAT를 재구성 하기 위해 01014000 주소 영역에

원본파일(notepad.exe)의 IDT 리스트 순서대로 DLL이름과 API 이름들을 저장해두었습니다.

이것을 읽어들이면서 1st 섹션의 IAT를 복원합니다.

 

 

 

7. BONUS - IDA에서 살펴보기

 

OllyDbg를 이용하여 디버깅을 진행하다보니

복잡한 압축해제 알고리즘을 Hex-ray로 보면 좀 더 쉬워질까? 라는 생각이 들었습니다.

결과적으로 도움이 되지는 않았지만, 다른 케이스에서는 도움이 되길 바라는 마음으로 남깁니다.

 

IDA로 패킹된 notepad.exe 를 Open하면 정상적인 PE구조가 아니기 때문에

아래와 같은 오류창이 3~4개 정도 연달아 뜹니다. 당황하지 말고 모두 OK 눌러줍니다.

 

"Can't find translation..." 관련 오류창만 짚고 넘어가겠습니다. (다른 케이스에 종종 나타나므로..)

해당 오류가 발생하는 곳은, DATA_DIRECTORY 영역으로서, Relocation Directory RVA, Size 멤버가 위치합니다.

CFF에서 Invalid 값으로 표현해주고 있군요.

 

지금까지 UPack을 분석하면서의 짧은 경험으로도, 해당 값은 RVA로 사용되기엔 너무 큽니다.

차근차근 분석해보면...일단 UPack은 1st 섹션의 시작 위치를 10으로 잡기 때문에, B0 주소의 데이터는

메모리에 로딩되면 1st 섹션 RVA(1000) + ImageBase(01000000) = 010010B0에 로딩됩니다.

주소에 찾아가보면 REP MOVS DWORD PTR ES : .... 명령어 코드가 존재하며

 

위에서 분석했던 "디코딩, 압축해제를 위한 초기 세팅" 부분의 코드임을 알 수 있습니다.

UPack이 NT_Header 영역 또한 일부 무시하고 디코딩 코드를 삽입했기 때문에 이러한 현상이 발생하는 것이고

이와 비슷한 오류창이 뜬다면, 해당 영역이 소스코드로 사용되는 패킹일 수 있음을 염두해둡니다.

 

말이 나온김에, 이와 같은 사실을 이용해서 PE헤더 영역에서 코드로 사용된 부분을 다시 살펴보면

아래 그림의 영역까지 포함하여 총 2개(1018 ~ 1023, 10A0 ~ 10BB) 영역입니다.

 

해당 영역을 노란색으로 그려보면 헤더 분석시에 그렸던 그림이 아래와 같은 그림으로 변경됩니다.

 

결과적으로 OPTIONAL_HeaderDATA_DIRECTORY에서 아래에 표시된 상당수의 변수가 무시되고, 

UPack의 디코딩 코드가 삽입 되었음을 알 수 있습니다. 아마 무시되도 실행에는 문제가 없는 멤버겠지요.

 

다시 본론으로 돌아와서...

로딩이 완료되면 Function window에 start 함수 달랑 1개만 존재함을 볼 수 있습니다.

이때 F5를 눌러 Hex-ray를 호출하면, 어느정도 복잡한 코드가 생성되지만 온전한 코드는 아닙니다.

사실 지금 생성된 코드만 해도 수십개의 변수가 존재하며, 코드도 엄청 복잡합니다. (스샷엔 2/5 정도만 담김)

 

온전한 코드가 생성되지 않는 정확한 이유를 단정지어 설명드리기 어렵지만,

개인적인 생각으로는 IDA가 UPack이 생성한 바이너리를 소스코드로 인식하지 않아서 발생하는 문제로 보입니다.

아래 스샷은 0101FCCB 주소로 Jump 했을때 이동된 장소이며, 위에서 보았던 decode() 함수입니다.

어셈블리 코드로 해석되지 않고, 16진수 데이터로 표현되어 있습니다.

 

디버깅을 진행하다보면 실행 흐름에 따라 16진수 데이터가 새로운 어셈블리 코드로 변경됩니다.

이러한 점을 이용하여 IDA에서 어느정도 트레이싱을 진행한 다음 Hex-ray를 호출하면 됩니다.

보통 OllyDbg에서 OEP를 걸고 디버깅 하듯이, IDA에서도 동일하게 진행합니다.

일단 Debugger의 종류를 Local Win32 debugger로 설정하고 Start합니다.

 

보통 OllyDbg에서 Ollydump 플러그인을 이용한 언패킹을 진행할때에는

책에서 학습한대로 OEP에 브레이크 포인트(F2)를 걸고 달리게 되지만,

Upack의 경우 원본파일이 우리가 관찰해야될 1st 섹션의 코드 부분에 압축해제 됩니다.

디코딩 코드를 관찰하기 위해 1st 섹션에 데이터를 쓰는 0101FE57 주소의 명령어가

실행되기 전의 주소에(근처) BP를 걸고 달립니다. 

 

어느정도 트레이싱이 진행되었으므로, Debugger - Take memory snapshot을 실행합니다.

저장할 세그먼트는 무엇을 해도 상관없지만, all segments를 선택할 경우 너무 많은 함수목록이

Function window에 나타나서 번잡스럽게 느껴졌습니다.

이후 정지버튼 또는 Debugger-Terminate process 기능을 이용해 디버깅을 종료시킵니다.

 

이후에 Options - General - Analysis 옵션에서 Reanalyze program을 실행하고서

Hex-ray를 실행하면 더 길고 복잡한 온전한(?) 코드를 얻을 수 있습니다.

처음으로, 차라리 OllyDbg를 이용한 트레이싱이 더 낫겠다...라는 생각을 하게 만들더군요. 

 

 

-----------------------------------------------------------------------------------------------------------------

0x03 마무리

-----------------------------------------------------------------------------------------------------------------

 

언젠간 해야지 해야지...하고 미뤄두기만 했었던 실행압축에 대해 공부했습니다.

모르는 부분을 겸사겸사 계속 추가했더니 내용이 또 길어졌네요.

그래도 이번 기회에 PE구조에 대해 좀더 익숙해졌고, 리버싱에 대한 막연함이 많이 사라진것 같습니다.


개인적으로..

그냥 공부했다면 너무 어려웠을 내용을 책으로 쉽게 내주신 이승원 저자분에게 감사드립니다.

잘못된 부분이나 오타는 댓글 남겨주세요.

 

 

posted By Message.

Commit your way to the LORD, trust in him and he will do this. [PSALms 37:5]

 

':: 리버싱' 카테고리의 다른 글

[정보 보안 개론] Chapter 06 악성코드  (0) 2016.11.07
Red_Seek :: 스택 프레임  (0) 2014.01.29
[Red_Seek] abex' crackme #1 (크랙미 #1)  (0) 2014.01.23
[Red_Seek] 스택  (0) 2014.01.22
[Red_Seek] IA-32 Register  (0) 2014.01.14
posted by Red_Message
2016. 11. 7. 03:29 :: 리버싱

Chapter 06 악성코드

Section 01 악성코드

- 제작자가 의도적으로 사용자에게 피해를 주고자 만든 모든 악의적 목적을 가진 프로그램 및 매크로, 스크립트 등 컴퓨터 상에서 작동하는 모든 실행 가능한 형태

 

1. 악성코드의 분류

가. 악성코드의 분류

이름(코드) 

설명 

바이러스 

- 사용자 컴퓨터(네트워크로 공유된 컴퓨터 포함) 내에서 사용자 몰래 프로그램이나 실행 가능한 부분을 변형해 자신 또는 자신의 변형을 복사하는 프로그램

- 가장 큰 특성은 복제와 감염

- 네트워크의 컴퓨터로 스스로 전파되지는 않음 

웜 

- 인터넷 또는 네트워크를 통해서 컴퓨터에서 전파되는 악성 프로그램

- 윈도우의 취약점 또는 응용 프로그램의 취약점을 이용하거나 이메일이나 공유 폴더를 통해 전파, 최든에는 공유 프로그램(P2P)을 이용하여 전파되기도 한다.

- 바이러스와 달리 스스로 전파되는 특성 

트로이 목마 

- 바이러스나 웜처럼 컴퓨터에 직접적인 피해를 주지는 않지만, 악의적인 공격자가 컴퓨터에 침투하여 사용자의 컴퓨터를 조종할 수 있는 프로그램

- 고의적으로 만들어졌다는 점에서 프로그래머의 실수인 버그와는 다름

- 자시 자신을 다른 파일에 복사하지 않는다는 점에서 바이러스와 구별 

인터넷 악성코드 

- 인가되지 않은 성인 사이트나 크랙 사이트 등에 접속할 때 감염

- 예전에는 인터넷 악성코드로 끝나는 경우가 많았으나 최근에는 웜의 형태로 전이되고 있다. 

스파이웨어 

- 자신이 설치된 시스템의 정보를 원격지의 특정한 서버에 주기적으로 보내는 프로그램

- 사용자가 주로 방문하는 사이트, 검새어 등 취향을 파악하기 위한 것도 있지만 패스워드 등과 같은 특정 정보를 원격지에 보내는 스파이웨어도 존재 


나. 악성 프로그램으로 인해 발생할 수 있는 증상

대분류 

소분류 

설명 

시스템 관련

시스템 설정 정보 변경 

변경 레지스트리 키 값을 변경하여 시스템의 정보를 변경

FAT 파괴 

시스템의 파일 시스템을 파괴 

CMOS 변경 

CMOS 내용을 변경하여 부팅 때 에러를 발생시킴 

CMOS 정보 파괴 

CMOS의 일부를 파괴 

기본 메모리 감소 

시스템의 기본 메모리를 줄인다. 

시스템 속도 저하 

시스템의 속도를 저하시킨다. 

프로그램 자동 실행 

레지스트리 값을 변경해 시스템을 부팅할 때 특정 프로그램을 자동으로 실행 

프로세스 종료 

특정 프로세스를 강제로 종료 

시스템 재부팅 

시스템을 재부팅 

네트워크 관련 

메일 발송 

특정 사용자에게 메일을 발송

정보 유출 

사용자의 정보를 네트워크를 통해서 공격자로 전송 

네트워크 속도 저하 

감염된 컴퓨터가 속한 네트워크가 느려짐 

메시지 전송 

메시지를 네트워크를 통해 다른 컴퓨터로 전송 

특정 포트 오픈 

특정 백도어 포트를 연다 

하드 디스크 관련 

하드 디스크 포맷 

하드 디스크를 포맷 

부트 섹터 파괴 

하드 디스크의 특정 ㅂ분을 파괴 

파일 관련 

파일 생성 

특정 파일(주로 백도어 파일)을 생성 

파일 삭제 

특정 파일이나 디렉터리를 삭제 

파일 감염 

바이러스가 특정 파일을 감염 

파일 손상 

바이러스가 특정 파일에 겹쳐 쓰기 형태로 감염되면 파일이 손상된다. 

특이 증상 

이상 화면 출력 

출력 화면에 특정 내용이 나타남 

특정음 

발생 컴퓨터에서 특정음이 발생 

메시지 상자 출력 

화면에 특정 메시지 상자가 나타남 

증상 없음 

특이한 증상 없음 



Section 02 바이러스

1. 1세대 : 원시형 바이러스

가. 부트 바이러스

- 플로피 디스크나 하디 디스크의 부트 섹터에 감염되는 바이러스로 MBR과 함께 PC 메모리에 저장

- 부팅할 때 자동으로 동작하여 부팅 후에 사용되는 모든 프로그램을 감염시킨다.

- 컴퓨터의 부팅 순서

■ 1단계 : POST

- POST(Power On Self Test)는 운영체제와 관련 있는 부분은 아님. 설치할때 하드웨어 자체가 시스템에 문제가 없는지 기본 사항을 스스로 체크하는 과정이다.

- BIOS(Basin Input/Outpur System)에 의해 실행

■ 2단게 : CMOS

- CMOS(Complementary Metal-Oxicle Semiconductor)에서는 기본 장치에 대한 설정과 부팅 순서를 설정할 수 있으며 BIOS는 CMOS에서 이런 기본 설정 사항을 읽어 시스템에 적용한다.

■ 3단계 : 운영체제 위치 정보 로드

- 윈도우 2003 이전 : 마스터 부트 레코드(MBr : Master Boot Record) 정보를 읽음. CMOS 정보를 읽어 부팅 매체를 확인할 뒤에는 부팅 매체의 MBR 정보를 읽는다.

- MBR은 운영체제가 어디에, 어떻게 위치해 있는지를 식별하여 컴퓨터의 주 기억장치에 적재될 수 있도록 하기 위한 정보

- 하드디스크나 디스켓의 첫 번째 섹터에 저장되어 있다.

- 메모리에 적재될 운영체제가 저장된 파티션의 부트 섹터 레코드를 읽을 수 있는 프로그램(부트 섹터 레코드는 운영체제의 나머지 부분을 메모리에 적재시키는 프로그램을 담고 있다.)


- 윈도우 2008 이후 : 윈도우 부트 서브 시스템(Window Boot Manager)이 실행됨.

- MBR에서 NTLDR이 실행되지 않고, 윈도우 부트 서브시스템이 실행된다. 윈도우 부트 서브 시스템은 bootmgr.exe가 실행되고 부트 설정 데이터(BCD, Boot Configuration Data)를 읽어 실행 가능한 운영체제의 목록을 보여주는데, 이것은 NTLDR이 boot.ini을 읽어 실행 가능한 운영체제의 목록을 보여주는 것과 같다.

- 부트 바이러스는 바로 이 3단계에서 동작.

- 과거 부트 바이러스에 감염된 플로피 디스크로 운영체제를 구동시키면 바이러스가 MBR과 함께 PC메모리에 저장되고 부팅 후에 사용되는 모든 프로그램에 자신을 감염시킨다.

- 부트 바이러스의 종류로는 브레인, 몽키, 미켈란젤로 바이러스가 있다.


나. 파일 바이러스

- 파일을 직접 감염시키는 바이러스.

- 바이러스에 감염된 실행 파일이 실행될 때 바이러스 코드를 실행한다.

- 프로그램을 덮어쓰는 경우, 프로그램 앞부분에 실행 코드를 붙이는 경우, 프로그램의 뒷부분에 코드를 붙이는 경우가 있다.

- 파일 바이러스에는 예루살렘 바이러스, 썬데이(Sunday), 스콜피온(Scorpion), 크로우(Crow), FCL, CIH 바이러스가 있다.


2. 2세대 : 암호형 바이러스

- 바이러스 코드를 쉽게 파악하고 제거할 수 없도록 암호화한 바이러스

- 암호형 바이러스의 종류로는 슬로우(Slow), 캐스케이드(Cascade), 원더러(Wanderer), 버글러(burglar) 바이러스가 있다.


3. 3세대 : 은폐형 바이러스

- 바이러스에 감염된 파일들이 일정한 잠복 기간을 가지게 한 바이러스

- 은폐형 바이러스에는 브레인(Brain), 조시(Joshi), 512, 4096 바이러스가 있다.


4. 4세대 : 다형성 바이러스

- 코드 조합을 다양하게 할 수 있는 조합(Mutation) 프로그램을 암호형 바이러스에 덧붙여 감염된다. 실행될 때마다 바이러스 코드 자체를 변경시켜 식별자를 통해 구분하기 어렵게 한다.


5. 5세대 : 매크로 바이러스

- 응용 프로그램 및 사무용 관련 프로그램이 개발되면서 스크립트 형태의 실행 환경을 이용하여 전파되는 바이러스이다. 주로 MS 오피스 프로그램의 매크로 기능을 이용하여 감염된다.



Section 03 웜

- 인터넷 또는 네트워크를 통해서 컴퓨터에서 컴퓨터로 전파되는 프로그램이다.

- 다른 컴퓨터의 취약점을 이용하여 스스로 전파되거나 메일로 전파된다.


1. MASS Mailer 형 웜

- 자기 자신을 포함하는 대량 메일 발송을 통해 확산되는 웜이다.

- 제목이 없거나 특정 제목으로 전송되는 메일을 읽었을 때 감염되며, 시스템 내부에서 메일 주소를 수집하여 메일을 계속 발송한다.

- 주요 증상

◆ 감염된 시스템이 많으면 SMTP(TCP 25)의 네트워크 트래픽이 증가

◆ 베이글은 웜 파일을 실행할 때 'Can't find a viewer associated with the file' 같은 가짜 오류 메시지를 출력

◆ 넷스카이는 윈도우 디렉터리 밑에 CSRSS.exe을 만든다.

- 베이글(Bagle), 넷스카이(Netsky), 두마루(Dumaru), 소빅(Sobig) 등이 있다.

- MASS Mailer 형 웜은 내부 교육을 통해 예방가능


2. 시스템 공격형 웜

- 운영체제 고유의 취약점을 통해 내부 정보를 파괴 혹은 컴퓨터를 사용할 수 없는 상태로 만들거나 외부 공격자가 시스템 내부에 접속할 수 있도록 백도어를 설치한다.

- 주요 증상

◆ 과다한 TCP 135, 445 트래픽이 증가

◆ windows, windows/system32, winnt, winnt/system32 폴더에 SVCHOST.EXE 등의 파일을 설치

◆ 공격 성공 후 UDP 5599 등의 특정 포트를 열어 외부 시스템과 통신한다.

- 아고봇(Agobot), 블래스터(Blaster worm), 웰치아(Welchia) 등이 있다.


3. 네트워크 공격형 웜

- 특정 네트워크나 시스템에 대해 서비스 거부(DoS) 공격을 수행한다.

- 주요 증상

◆ 네트워크가 마비되거나, 급속도로 느려진다.

◆ 네트워크 장비가 비정상적으로 동작한다.

- 져봇(Zerbot), 클레즈(Klea) 등이 있다.



Section 04 기타 악성코드

1. 백도어와 트로이 목마

- 정상적인 인증 과정을 거치지 않고 운영체제나 프로그램 등에 접근할 수 있도록 만든 일종의 관리 목적의 통로이다.


2. 인터넷 악성코드

- 인가되지 않은 성인 사이트나 크랙 사이트 등에 접속할 때 감염된다.

- 예전에는 인터넷 악성코드로 끝나는 경우가 많았으나 최근에는 웜의 형태로 전이되고 있다.

- 주요 증상

◆ 인터넷 익스플로러의 시작 페이지가 계속 다른 곳으로 변경

◆ 인터넷 속도가 느려지거나 끊긴다.

◆ 시스템의 비정상적인 작동으로 운영체제가 사용 불능의 상태가 된다.


3. 스파이웨이

- 자신이 설치된 시스템의 정보를 원격지의 특정한 서버에 주기적으로 보내는 프로그램이다.

- 사용자가 주로 방문하는 사이트, 검색어 등 취향을 파악하기 위한 것도 있지만 패스워드 등과 같은 특정 정보를 원격지에 보내는 스파이웨어도 존재



Section 05 악성코드 탐지 및 대응책

- 악성코드 탐지에 사용되는 툴은 다음과 같다.

■ 윈도우 7 악성코드 : Win-Trojan.Pearmor

■ Process Explorer

■ Total Commander(http://www.ghisler.com)

■ CPorts(http://www.nirsoft.net/utils/cports.html)


1. 네트워크 상태 점검하기

- 상당수의 백도어는 외부(해커, 악성코드 작성자)와의 통신을 위해 서비스 포트를 생성한다.

- 주요 백도어의 사용 포트

포트번호 

트로이 목마 

포트번호 

트로이 목마 

21

 TrojanFore

1080 

WinHole 

23 

Tiny Telnet Server[TTS]

1090 

Xtreme 

25 

NaebiHappy

1150 

Orion 

31 

Agent, ParadiseMasters

1234 

Ultors Trojan 

41 

DeepThroat Foreplay 

1243 

Backdoor G 

80 

WWW Tunnel 

1245 

VooDoo Doll 

119 

Happy 99 

1257 

Frenzy 2000 

133 

Farnaz 

1272 

The Matrix 

137 

ChodeMSint(UDP) 

1441 

Remote Storm 

514 

RPCBackdoor 

1524 

Trin00 

555 

Seven Eleven 

1999 

Sub Seven 

666 

ServeU 

2140 

Deep Throat 1.3 

667 

SniperNet 

2255 

Nirvana

777 

AIM Spy

2583 

WinCrash

808 

WinHole

2773 

Sub Seven Gold 2.1 

999 

Deep Throat 

3459 

Eclipse 2000 

1001 

Silencer 

5400 

Blade Runner 

1016 

Doly Trojan 

5880 

Y3K Rat 

1024 

NetSpy 

8787 

BackOrifice  

- 시스템에서는 netstat 와 같은 명령으로 열려 있는 포트를 확인할 수 있다.


2. 정상적인 프로세스와 비교하기

- 대부분의 백도어는 티가 나지 않기 때문에, 윈도우와 유닉스 시스템 등의 정상적인 프로세스를 외워두면 비정상적인 프로세스를 식별하는데 많은 도움이 된다.

- 현재 동작 중인 정상적인 프로세스를 아는 것도 매우 중요. 윈도우의 작업 관리자에서 확인 가능하다.

- 다음의 20여 개의 프로세스는 윈도우 시스템이 동작힉 위한 기본 프로세스로 알아두면 유용하다.

● Csrss.EXE(Client/Server Runtime SubSystem : Win 32)

 - 윈도우 콘솔을 관장하고, 스레드를 생성/삭제하며 32비트 가장 MS-DOS 모드를 지원한다.

● Explorer.exe

 - 작업표시줄, 바탕화면과 같은 사용자 셸을 지원한다.

● Lsass.exe(Local Security Authentication Server)

 - Winlogon 서비스에 필요한 인증 프로세스를 담당한다.

● Mstask.exe(Window Task Scheduler)

 - 시스템에 대한 백업이나 업데이트 등에 관련된 작업의 스케줄러이다.

● Smss.exe(Session Manager SubSystem)

 - 사용자 세션을 시작하는 기능을 담당한다.

 - Winlogon, Win32(Csrss.exe)을 구동시키고, 시스템 변수를 설정한다.

 - 또한 Smss는 Wonlogon이나 Csrss가 끝나기를 기다려 정상적인 Winlogon, Csrss 종료시 시스템을 종료시킨다.

● Spoolsv.exe(Printer Spooler Service)

 - 프린터와 팩스의 스풀링 기능을 담당한다.

● Svchost.exe(Service Host Process)

 - DLL(Dynamic Link Libraries)에 의해 실행되는 프로세스의 기본 프로세스이다.

 - 한 시스템에서 여러 개의 svchost 프로세스를 볼 수 있다.

- 이 중에 웜/바이러스가 주로 사용하는 서비스명은 Csrss와 Svchost이다.

● Services.exe(Service Control Manager)

 - 시스템 서비스를 시작/정지시키고, 그들간의 상호 작용하는 기능을 수행한다.

● System

 - 대부분의 커널 모드 스레드의 시작점이 되는 프로세스이다.

● System Idle Process

 - 각 CPU마다 하나씩 실행되는 스레드로서 CPU의 잔여 프로세스 처리량을 %로 나타낸 값이다.

● Taskmgr.exe(Task Manager)

 - 작업관리자 자신

● Winlogon.exe(Windows Logon Process)

 - 사용자 로그인/로그오프를 담당하는 프로세스이다.

 - 윈도우의 시작/종료 시에 활성화되며 Ctrl+Alt+Delete 키를 눌렀을 경우에도 활성화된다.

● Winmgmt.exe(Window Management Service)

 - 장치에 대한 관리 및 계정 관리, 네트워크 등의 동작에 관련한 스크립트를 위한 프로세스이다.

● msdtc.exe(Distriduted Transaction Coordinator)

 - 웹 서버 및 SQL 서버 구동 시에 다른 서버와의 연동을 위한 프로세스이다.

● ctfmon.exe(Alternative User Input Services)

 - 키보드, 음성, 손으로 적은 글 등 여러가지 텍스트 입력에 대한 처리를 할 수 있도록 지원하는 프로세스이다.

● dfssvc.exe(Distributed File System (DFS))

 - 분산 파일 시스템에 대한 지원을 위해 백그라운드로 실행되고 있는 프로세스이다.


3. 백도어의 실질적인 파일 확인하기

- 네트워크 상태와 프로세스 분석을 통해 확인한 백도의 실질적인 파일을 확인한다.

- 악성코드 파일을 확인할 때는 total commander 같은 툴을 사용한다.

- 윈도우 탐색기가 악성코드에 이미 공격을 받았을 때는 특정한 파일이 숨겨져서 보이지 않거나 삭제되지 않는 경우도 있다.

- 파일 확인 과정에서 반드시 확인하고 가야할 폴더인 C:\Windows\system32 가 있다. 이 폴더에는 주요 DLL과 운영체제의 기본적인 실행 파일이 존재하는데, 대부분의 악성코드는 이 폴더를 공략한다.

- 폴더 내에서 가장 최근에 생성된 파일 중에 이상한 것이 없는지 확인하는 방법을 우선 이용한다.


4. 시작 프로그램과 레지스트리 확인하기

- 시작 프로그램 목록은 'msconfig' 명령을 통해 쉽게 확인할 수 있다.


5. 백도어 제거하기

- 확인한 백도어를 삭제하는 절차는 다음과 같이 할 수 있다.

① 백도어 프로세스의 중지

② 백도어 파일의 삭제

③ 레지스트리 삭제

 - regedit 으로 레지스트리 경로를 확인할 수 있다.





출처 : 정보 보안 개론(한 권으로 배우는 보안 이론의 모든 것) / 양대일 저 / 한빛아카데미 출판

':: 리버싱' 카테고리의 다른 글

UPack PE 헤더 분석 + 디버깅  (1) 2016.12.31
Red_Seek :: 스택 프레임  (0) 2014.01.29
[Red_Seek] abex' crackme #1 (크랙미 #1)  (0) 2014.01.23
[Red_Seek] 스택  (0) 2014.01.22
[Red_Seek] IA-32 Register  (0) 2014.01.14
posted by Red_Seek
2014. 1. 29. 16:13 :: 리버싱

안녕하세요. Seek입니다.

오늘은 디버깅할 때 큰 도움이 되는 내용을 공부하고자 합니다.

바로 스택 프레임입니다. 스택 프레임을 이해하고 나면 스택에 저장된 함수 변수와 함수 로컬 변수 등이 쉽게 파악되기 때문에 디버깅에 큰 도움이 됩니다.

 

1. 스택 프레임

 

   스택 프레임이란 ESP(스택 포인터)가 아닌 EBP(베이스 포인터)레지스터를 사용하여 스택 내의 로컬 변수, 파라미터, 복귀 주소에 접근하는 기법을 말합니다.

 

   ESP 레지스터는 스택 포인터 역할을 하고, EBP 레지스터는 베이스 포인터 역할을 합니다. ESP 레지스터의 값은 프로그램 안에서 수시로 변경되기 때문에 스택에 저장된 변수, 파라미터에 접근하고자 할 때 ESP 값을 기준으로 하면 프로그램을 만들기 어려워집니다. 따라서 어떤 기준 시점(함수 시작)의 ESP 값을 EBP에 저장하고 이를 함수 내에서 유지해주면, ESP 값이 아무리 바뀌어도 EBP를 기준으로 안전하게 해당 함수의 변수, 파라미터, 복귀 주소에 접근할 수 있게 되는 것입니다. 이러한 역할을 하는것이 EBP레지스터입니다.

 

<스택 프레임의 구조>

스택 프레임을 어셈블리 언어로 보았을때 이런 구조로 되어 있습니다. 

 PUSH EBP

; 함수시작(EBP를 사용하기 전에 초기 값을 스택에 저장) 

 MOV EBP, ESP  

; 현재의 ESP를 EBP에 저장 

 

 

 ...

; 함수의 본체 

; 여기서 ESP가 변경되더라도 EBP가 변경되지 않으므로 

; 안전하게 로컬변수와 파라미터를 엑세스할 수 있음 

 

 

 MOV ESP, EBP 

; ESP를 정리(함수가 시작했을 때의 초기값으로 복원) 

 POP EBP

; 리턴되기 전에 저장해 놓았떤 원래 EBP 값으로 복원

 RETN

; 함수 종료 

이런 식으로 스택 프레임을 이용해서 관리를 한다면 아무리 함수 호출이 복잡해져도 스택을 완벽하게 관리할 수 있게 됩니다.

 

 

 

2. Stackframe.exe(실습 예제)

 

   실습예제를 통해 실제 어떻게 동작하는지 보도록 하겠습니다.

 

<C언어 실습 코드>

 

<OllyDbg로 실습 프로그램 실행> / 잘안보이시면 클릭해서 확대해서 보세요~

 

실행시키시고 Ctrl + G로 40100 주소로 간 화면입니다. 그곳에 add()함수와 main()함수가 있기 때문입니다.

 

2-1. main()함수 시작 & 스택 프레임 생성

  

C언어코드에서 아래 코드 부분에 해당되는 내용입니다.

 

 

<main()>

 

main()함수 401020에 BP(Break Point)를 설치[F2]한 후 실행[F9]하시면 BP에서 멈출 것입니다.

 

<main() 함수 시작 시 스택의 상태> 

 

위 사진에서 ESP=18FF44, EBP=18FF88 입니다. 특히 ESP(18FF44)에 저장된 값 401250은 main()함수의 실행 이 끝난 후 돌아갈 리턴 주소입니다.

 

이제 한줄씩 알아보도록 하겠습니다.

 

[ 00401020    PUSH EBP ]

   main()함수는 시작하자마자 스택 프레임을 생성합니다. PUSH EBP는 'EBP 값을 스택에 집어널어라'입니다. main()함수에서 EBP가 베이스 포인터 역할을 하게 되니 EBP가 이전에 가지고 있던 값을 스택에 백업을 한다고 생각하시면 됩니다.

(main()함수 마지막에 함수가 종료되기 전에 이 값을 회복시키는것을 보실수 있을겁니다.)

 

[ 00401021     MOV EBP, ESP ]

   'ESP의 값을 EBP로 옮겨라'라는 의미입니다. 이 명령 이후에는 EBP는 ESP와 같은 값을 가지게 됩니다. 그리고 main()함수 끝날 때까지 EBP는 값이 고정됩니다. 이 의미는 스택에 저장된 함수 파라미터와 로컬 변수들을 EBP를 통하여 접근하겠다는 것입니다.

   401020과 401021의 주소에 두 명령어에 의해서 main()함수의 대한 스택 프레임이 생성되었습니다.

 

   OllyDbg에서 오른쪽 아래 스택창에서 우클릭후 'Address'에서 'Relative to EBP'를 선택해 주시면 이제부터 EBP의 위치를 확일하여 어떻게 이동하고 변동되는지 확인하실 수 있습니다.(EBP 기준으로 표시되기 때문에 좀 더 이해에 도움이 될것입니다.)

 

<main()에서 스택프레임 생성 후 초기 값>

 

 

위 사진을 보시면 ESP, EBP는 18FF40으로 동일해졌습니다. 이유는 처음 EBP인 18FF88 주소를 PUSH 명령어로 스택 18FF40에 저장하면서 ESP가 18FF40을 가르키게되었고 사진에서 보시면 18FF40에 18FF88이 들어가 있는것을 확인하실 수 있습니다. 그리고 MOV명령어로 ESP(18FF40)를 EBP에 옮겼기 때문에 두개의 값이 같아 졌습니다. (18FF88은 EBP가 함수 시작할 때 가지고 있던 초기값입니다.)

 

2-2. 로컬 변수 세팅

 

C언어코드에서 아래 코드 부분에 해당되는 내용입니다. 

 

스택에 두 로컨 변수인 a, b를 위한 공간을 만들고 값을 입력하는 부분입니다.

 

[ 401023     SUB ESP, 8 ]

    SUB는 빼기 명령어 입니다. 'ESP 값에서 8을 빼라' 라는 의미입니다. 현재 ESP는 18FF40입니다.( 사진<main()에서 스택프레임 생성 후 초기 값> 참고) 왜 ESP에서 8을 빼는 빼냐면 함수의 로컬 변수는 스택에 저장된다고 했습니다. 그리고 a, b는 long 타입으로 각각 4바이트 크기이고 2개의 변수를 스택에 저장해야되기 때문에 8바이트가 필요함으로 8을 빼서 두 변수에게 필요한 메모리 공간을 미리 확보해두는 의미입니다. 이렇게 미리 확보 해두었기 때문에 이제부턴 ESP 값이 변해도 a, b의 메모리 공간은 훼손되지 않게 됩니다.

 

[ 401026     MOV DWORD PTR SS:[EBP-4], 1] ; a

[ 40102D     MOV DWORD PTR SS:[EBP-8], 2] ; b

   위 명령어를 해석하면 'EBP-4 주소에서 4바이트 크기의 메모리에 1을 넣고, EBP-8 주소에서 4바이트 크기의 메모리에 2를 넣어라' 가 됩니다. 즉, EBP-4는 a를 의미하게되고 EBP-8은 b를 의미하게 됩니다. 여기까지 실행 결과 사진이 아래의 사진입니다. 

 

위 사진을 보시면 EBP는 계속 18FF40을 유지하고 있는것을 확인하시면서 넘어가시면 되겠습니다. 그리고 EBP-4에 1이 EBP-8에 2가 저장되어 있는 것을 확인하실수 있습니다.

  

2-3. add()함수 파라미터 입력 및 add()함수 호출

 

C언어코드에서 아래 코드 부분에 해당되는 내용입니다. 

 

 

[ 401034     MOV EAX, DWORD PTR SS:[EBP-8] ] ; b

[ 401037     PUSH EAX ]

[ 401038     MOV ECX, DWORD PTR SS:[EBP-4] ] ; a

[ 40103B     PUSH ECX ]

[ 40103C     CALL 00401000]

 

위에 5줄의 코드는 a, b를 스택에 넣어주고 add()함수를 호출하는 코드입니다. 한줄식 살펴보도록 하겠습니다. 401034는 EBP-8에 있는 b를 EAX 레지스터에 넣어주라는 의미입니다. 그다음줄 401037에서는 b가 들어가있는 EAX를 스택에 넣어주게 됩니다. 401038에서는 a(EBP-4)를 ECX에 넣어주게 되며 4번쨰 줄은 역시 a가 들어가있는 ECX를 스택에 넣게 됩니다. 마지막 40103C에서는 401000을 불러오라고 되있는데 401000은 add()함수를 의미합니다.

 

여기서 보시면 a를 먼저 꺼내서 스택에 넣는것이 아니라 b를 먼저 꺼내서 스택에 넣으라고 되어있습니다. C언어와는 반대로 저장이 되는데 이것을

'함수 파라미터의 역순 저장'이라고 합니다. 이렇게 거꾸로 넣어야지 나중에 다시 꺼낼때 C언어와 동일한 순서로 불러올수 있게 되는것입니다.

 

아래 사진은 여기까지 실행한 결과입니다. 

 

이제는 add()함수 401000 으로 가보겠습니다.

 

 

가기전에 CALL 명령어가 실행되어서 다른 함수로 가기전에 다시 돌아와야할 주소(복귀주소:return address)를 CPU는 무조건 스택에 저장합니다. 위 사진을 보시면 빨강색 표시된 부분을 보시면 40103C에서 CALL 401000 이 되어서 add()함수로 가라고 되어있습니다. 그리고 다음줄 401041주소가 add()함수가 끝나고 돌아와야할 복귀주소입니다.

 

그래서 아래의 사진은 CALL 명령이 실행되고 난 후 스택의 모습입니다. 

 

EBP-14에 복귀주소인 401041 주소가 들어가어 있는것을 보실 수 있습니다.

 

2-4. add()함수 시작 & 스택 프레임 생성

 

C언어코드에서 아래 코드 부분에 해당되는 내용입니다.  

 

 

add()함수에서도 역시 함수가 시작될때 자신만의 스택프레임을 생성하게 됩니다.

[ 401000     PUSH EBP ]

[ 401001     MOV EBP, ESP ]

 

 

위 2둘을 실행하고 add()함수에서 스택프레임이 생성된 결과입니다. 보시면 main()에서 사용하던 18FF40 의 값이 백업되어서 스택에 새롭게 저장이되고 이것을 가르키고 있는 새로운 주소 18FF28으로 EBP와 ESP가 다시 세팅된것을 보실수 있습니다.

 

2-5. add() 함수의 로컬 변수(x, y) 세팅

 

C언어코드에서 아래 코드 부분에 해당되는 내용입니다. 

 

이제 main함수에서 넘어온 a, b를 x, y에 대입하게 됩니다.

 

[ 401003     SUB ESP, 8 ]

 

변수 x, y에 대한 스택에서 메모리를 확보하기 위한 코드입니다.

 

[ 401006     MOV EAX, DWORD PTR SS:[EBP+8] ]

[ 401009     MOV DWORD PTR SS:[EBP-8], EAX ]

[ 40100C    MOV ECX, DWORD PTR SS:[EBP+C] ]

[ 40100F    MOV DWORD PTR SS:[EBP-4], ECX ]

 

add() 함수에서 새롭게 스택프레임이 생성되면서 EBP 값이 변했습니다. EBP+8 은 a를 가리키고, EBP+C는 b를 가리킵니다.

그리고 EBP-8은 x, EBP-4는 ,y를 의미합니다. 아래 사진은 여기까지 실행시킨 스택의 모습입니다.

 

 

위 사진에서 보시면 설명이 이해가 가실껍니다. EBP+8 는 1이 저장되어있는 a, EBP+C는 2가 저장되어있는 b를 의미합니다. 그리고 add()함수 시작 할 때 새롭게 지정된 EBP 기준으로 위로 EBP-8이 x, EBP-4가 ,y 가 됩니다.

 

2-6. ADD연산

 

C언어코드에서 아래 코드 부분에 해당되는 내용입니다. 

 

[ 401012     MOV EAX, DWORD PTR SS:[EBP-8] ]

EAX에 변수 x의 값(EBP-8 = 1)을 넣습니다.

 

[ 401015     ADD EAX, DWORD PTR SS:[EBP-4] ]

의미는 'EAX에 변수 y(EBP-4 = 2)를 더하라'입니다. 그러면 이 윗줄에서 이미 EAX에 1을 넣었고 거기에 방금 2를 더했으니 EAX에는 3이란 값이 들어있게 됩니다.

 

참고로 EAX는 레지스터를 예전에 설명할때 산술 연산에 사용되며 다른 특수 용도로는 리턴 값으로 사용된다고 설명하였습니다. 지금 처럼 리턴 직전에 EAX에 값이 입력되어 있다면 그대로 리턴 값으로 출력되게 됩니다.

 

지금까지의 실행시키고 스택의 변화는 없습니다. 이유는 방금 2줄의 코드에서는 스택을 변경한것은 없기때문에 EAX의 변화만 있었고 스택의 변화는 없습니다.

 

2-7. add() 함수의 스택 프레임 해제 & 함수 종료(리턴)

 

C언어코드에서 아래 코드 부분에 해당되는 내용입니다. 

 

위 2-6이랑 같은 코드 부분입니다. 이제 더하기 연산은 종료가 되어 최종적인 값인 3이 EAX에 저장되어있습니다. 다시 설명드리면 EAX는 리턴 값으로 사용됩니다.

 

이제 리턴되기 전에 스택프레임을 해제해야 합니다.

 

[ 401018     MOV ESP, EBP ]

 

함수 시작당시의 값으로 ESP를 원래 상태로 돌려 놓는것입니다. 함수 초반에 EBP를 스택에 저장하여 백업하고 ESP를 EBP 넣었었습니다. 이제 다시 복구하는 것이죠. 그리고 EBP가 ESP로 들어가면서 x, y 변수를 만들때 사용된 [SUB ESP, 8] 이란 명령어의 효과는 사라집니다. ESP는 현재의 스택의 끝을 나타내니까 다시 떙겨졌다고 생각하면 되겠습니다. 아래 사진을 보시면 이해되실거 같습니다.

 

 

검정색 표시된 부분이 ESP입니다. 그리고 그위에 두줄이 아까 지정하고 add 연산에 사용된 변수 x, y의 스택 메모리 공간입니다. 방금 명령으로 인해 아래로 댕겨졌으니 저 두 변수의 공간은 무효해진것이라 생각하시면 됩니다.

 

[ 40101A     POP EBP ]

 

마지막으로 add() 시작 시에 백업했던 EBP의 값을 스택에서 꺼내서 복원합니다. 복원 값은 18FF40이 되겠네요.

< POP EBP 명령 실행 전 > 

< POP EBP 명령 실행 후 > 

 

 

 

 

 

아래 사진은 스택 프레임을 해제난 후에 결과 사진입니다. 

 

ESP = 18FF2C 에 저장되어 있는 값인 401041 은 앞에서 main()함수에서 CALL 401000 명령으로 CPU가 스택에 입력한 복귀 주소입니다. EBP는 백업했던 값으로 돌아온것을 확인 할 수 있습니다. 즉, add()함수가 호출되기 직전의 상태로 돌아온것입니다.

 

[ 40101B     RETN ]

 

위 명령이 실행되면 저장되어있던 복귀 주소 401041 로 돌아오게 됩니다. 아래 사진은 그 결과입니다. 

 

이 사진과 add()함수에 들어가기 직전인 CALL 명령이 실행되기 전의 사진(2-3 부분을 보시면 있는 사진)과 비교해보세요. 스택의 값이 동일합니다.

 

프로그램들은 이런 식으로 스택을 관리하기 때문에 함수 호출등이 계속 충첩되더라도 스택이 겹치거나 깨지지 않고 잘 유지 됩니다.

 

2-8. add() 함수 파라미터 제거(스택 정리)

 

이제 RETN 명령으로 인하여 main()함수로 돌아왔습니다.

 

[ 401041     ADD ESP, 8 ]

 

위쪽에 401041으로 돌아온 결과 사진을 보시면 EBP-10, EBP-C 가 있습니다. 이는 add()함수로 넘겨준 a, b입니다. 이제는 add()함수가 완전히 종료되었기 때문에 필요가 없습니다. 그래서 현재 ESP인 18FF30에 8을 더하여 두변수를 제거하면서 스택을 정리해주는 것입니다.(a, b 파라미터는 각각 long 타입으로 4바이트이기 때문에 8바이트입니다.)

 

여기까지 실행 후 스택의 결과사진입니다. 

 

EBP-10, EBP-C가 당겨진 모습입니다. 두 변수가 사라진것이죠.

 

2-9. printf() 함수 호출

 

C언어코드에서 아래 코드 부분에 해당되는 내용입니다. 

 

 

[ 401044     PUSH EAX ]

[ 401045     PUSH 0040B384 ]

[ 40104A    CALL 00401067 ]

[ 40104F    ADD ESP, 8 ]

 

첫번째 명령 PUSH EAX 는 add()함수에서 넘어온 리턴값인 "3"입니다. 즉, 의미는 'EAX인 3을 스택에 넣어라'가 되겠죠. 그 다음줄은 위에 C언어 코드를 보시면 printf 함수에 파라미터가 2개인것을 보실 수 있습니다. "%d\n" 와 add(a,b) 이렇게 2개입니다.

첫번째 명령인 EAX를 스택에 넣는 것은 printf 에서 add(a, b) 인자가 되겠지요. 그리고 다음중 PUSH 0040B384 은 %d\n 를 의미한다고 알아두시면 될거 같습니다.

 

즉, 파라미터가 2개이니까 두번 PUSH를 한다. 그중에 하나는 저희가 알고 있고 지금까지 계산해온 EAX를 넣는다. 이정도가 되겠습니다. 

PUSH EAX, PUSH 40B384 이 2개 명령을 실행 한 결과 사진입니다. 

printf 함수가 호출 되기전의 스택의 모습에서 추가가 된것을 보실수 있습니다.

EBP-C 에 EAX가 들어갔으니 3이 들어있는 모습

EBP-10 에 %d\n이 들어간 모습 입니다.

 

3번째 줄은 CALL 401067 으로 바로 printf 함수를 불러오는 것입니다. 2개의 파라미터를 앞에서 스택에 넣어줬으니 마지막에 함수호출을 하여 넣어둔 2개의 파라미터를 이용하면 되겠지요?

 

여기서 401067 은 Visual C++ 에서 생성한 C표준 라이브러리 입니다. 이부분은 이정도만 코멘트하고 넘어 가겠습니다.

 

그리고 마지막 4번째 줄에서 스택을 다시 정리하는 명령인것을 알수 있습니다.

3번째 줄에서 printf 함수를 호출하고 종료되고 돌아왔기 때문에 사용 완료된 스택을 정리하는 것입니다.

 

2-10. 리턴 값 세팅

 

C언어코드에서 아래 코드 부분에 해당되는 내용입니다. 

 

main() 함수의 리턴 값을 0으로 초기화 세팅해줍니다.

 

[ 401052     XOR EAX, EAX ]

 

XOR 명령은 Exclusive OR bit 연산입니다. 연산 방법은 같은 값을 XOR 하면 0이 되는 방식입니다.

여기까지 실행 한 스택 결과 사진입니다.

 

 

보시면 스택을 변동하는 명령어는 없기때문에 그대로이고 EAX가 0으로 바뀐것을 보실수 있습니다. 위에서 리턴값은 EAX의 값이라고 했습니다. 목적 역시 main()함수까지도 끝났으니 리턴값을 초기화 0으로 세팅한다고 했으니 원하는대로 0으로 세팅된것을 확인할 수 있습니다.

 

2-11. main() 함수 스택 프레임 해제 & main() 함수 종료

 

C언어코드에서 아래 코드 부분에 해당되는 내용입니다.  

 

main()함수가 종료되는 부분입니다. 위에서 했던 add()함수 종료시에 했던 스택 프레임 해제와 동일합니다.

 

[ 401054     MOV ESP, EBP ]

[ 401056     POP EBP ]

 

위 두함수로 인하여 스택프레임은 해제 되었습니다. 동시에 main()함수의 변수인 a, b 역시 더이상 무효하게 되었습니다. 여기까지 실행한 후 스택의 결과 사진입니다.

어디서 많이 본 스택의 모습이지 않습니까? 기억 못하시겠지만 main()함수 시작할 때 스택의 모습입니다.

2-1<main() 함수 시작 시 스택의 상태> 사진을 보시면 동일하다는 것을 확인할 수 있습니다.

기억은 못하시더라도 이런식으로 main() 함수가 종료되면서 스택프레임을 해제하면 초기의 스택모습으로 복원 된다는것을 계속해서 인식하면 될것입니다.

 

[ 401057     RETN ]

 

이제 main()함수가 종료되면서 리턴주소인 401250 으로 돌아가게(점프) 됩니다. 이러함으로써 프로그램이 종료되게 됩니다.

 

3. 마무리

 

스택 프레임을 간단히 설명하자면 수시로 변경되는 ESP 레지스터 대신 EBP 레지스터를 사용하여 로컨변수, 파라미터, 복귀 주소 등을 관리하는 방법입니다. 아마 여기까지 잘 이해하셨다면 디버깅에 대해 조금 자신감(?)이 생겼을거라 생각됩니다. 저도 이부분을 공부하면서 함수의 로컬변수, 파라미터, 복귀주소, 리턴 값 등이 어떻게 처리되고 어떻게 동작하는 원리를 이해했기 때문입니다. 많은 도움 되셨기를 바랍니다.

 

 

참고서적 : 리버싱 핵심 원리 / 저자 : 이승원

 

':: 리버싱' 카테고리의 다른 글

UPack PE 헤더 분석 + 디버깅  (1) 2016.12.31
[정보 보안 개론] Chapter 06 악성코드  (0) 2016.11.07
[Red_Seek] abex' crackme #1 (크랙미 #1)  (0) 2014.01.23
[Red_Seek] 스택  (0) 2014.01.22
[Red_Seek] IA-32 Register  (0) 2014.01.14
posted by Red_Seek
2014. 1. 23. 15:45 :: 리버싱

드디어 처음으로 하는 실습입니다ㅎㅎ

제목처럼 해당 파일을 크랙해보라고 도발(?)하는 이름이네요...

크랙미는 크랙 연습을 목적으로 작성되어 공개된 프로그램입니다. 그리고 매우 간단해서 초보자에게 알맞습니다. (저같은..)

그럼 이제 그 도발에 넘어가서 크랙을 해보도록 하겠습니다.

 

 

1. abex' crackme #1

 

   크랙을 하기전에 디버깅을 하기전에 어떤 프로그램인지 알아보록하겠습니다.

 

위 사진이 실행되며 확인 버튼을 눌러보면 아래와 같은 문구가 나옵니다.

 

에러메시지가 뜨네요. 저희가 크랙해야될 내용이 보입니다. HD(하드디스크)가 CD-ROM으로 인식되게 하면 되겠네요.

디버깅을 해보도록 하겠습니다.

 

실행시켜보니 이제까지 디버깅해왔던 HelloWorld.exe와는 코드길이 엄청나게 차이가 나네요. 이유는 지금 하고있는 abex crackme는 어셈블리언어로 만들어진파일이라 코드 길이가 짧습니다. HelloWorld는 C언어등의 언어로 작성된것을 어셈블리로 변환된것이라 긴것입니다.(잘안보이시면 사진을 클릭하여 크게 보세요~)

 

 

본론으로 돌아와서 코드분석을 해보겠습니다. (아래 사진 잘 안보이시면 클릭하여 크게 보세요~)

위 사진처럼 분석해보았습니다.

 

 

참고. abex' crackme #1에서 사용된 어셈블리 명령어 

 명령어

 설명 

PUSH 

스택에 값을 입력 

CALL 

지정된 주소의 함수를 호출 

INC 

값을 1 증가 

DEC 

값을 1 감소 

JMP 

지정된 주소로 점프 

CMP 

주어진 두 개의 값을 비교

(SUB 명령어와 동일하나 값이 변경되지 않고 EFLAGS 레지스터만 변경됨,

두 값이 동일하다면 SUB 결과는 0이고 ZF = 1 로 세팅됨) 

JE 

조건 분기(Jump if Equal)

(ZF = 1 이면 점프) 

 

 

2. 크랙

 

   이제 코드를 패치해서 크랙을 해보도록 하겠습니다. 위에서 말씀드렸듯이 HD를 CD-ROM으로 인식하게 하면 되겠네요.

 

 

위 조건분기 코드를 보시면 CMP EAX, ESI를 비교를 먼저 합니다. 그 다음 JE 명령어에서 위에서 비교된 결과값을 가지고 해당 주소 40103D로 점프하라고 되어있습니다.(명령어의 설명을 위쪽 크랙미1에서 사용된 어셈블리 명령어 표를 보시면 됩니다.)

 

코드분석을 해놓은 그림을 보시면 아시겠지만 EAX, ESI는 같지 않습니다. 그러므로 CMP에서도 불일치하다는 결과값을 출력하기 때문에 JE명령어에서 일치할때 점프하라고 되어있는 주소로 점프하지 못하고 바로 다음줄로 이동하게 됩니다.

 

바로 다음줄은 조건 불일치 시 라고 명시를 해둔 곳인데 결과적으로 HD를 CD-ROM으로 인식하지 못했다는 메시지박스를 출력하게 되는겁니다.

 

저희는 이제 조건분기 부분에서 JE명령어 부분을 크랙해서 CMP에서 비교한 값이 어느값이 나오든지 바로 HD를 CD-ROM으로 인식했다는 결과가 출력되도록 해보겠습니다.

 

"JE SHOR 0040103D" 라는 명령어를 "JMP SHORT 0040103D"로 바꾸시면 되겠습니다. 바꾸시는 방법은 JE명령어 코드를 선택하시고 [Space]버튼 또는 더블클릭해서 바꾸시면 되겠습니다. 그다음 바뀐 프로그램을 새롭게 저장해야되는데요. 먼저 코드를 바꾸신 다음에 작은 팝업창을 닫으시고(JE명령어 코드만 바꾸세요...) 아무코드에서나 마우스 우클릭후에 'Copy to executable' 메뉴에서 'All modifications' 선택하세요. 그 다음 'Copy all'을 누르시면 새로운 코드파일이 생성됩니다. 거기서 마우스 우클릭 후에 'save file'를 선택하여 새롭게 저장하시고 생성되는 exe 프로그램을 실해보시면 HD를 CD-ROM으로 인식했다는 메시지박스를 보실수 있을것입니다.

 

JE 명령어 코드를 바꾸는것 이외에도 다른 부분을 패치해서 하는 방법도 있습니다. 참고하시면 되겠습니다.

 

 

3. 스택에 변수를 전달하는 방법

 

   크랙미#1을 마무리하면서 스택에 변수를 전달하는 방법에 대해 알아보고 마무리하겠습니다.

 

 

1번에서 크랙미#1 코드분석을 보시면 MessageBoxA() 함수가 3개 나옵니다. 그리고 바로 위 코드를 보시면 MessageBoxA() 함수가 실행되기전에 4번의 PUSH 명령어를 사용하여 필요한 변수를 역순으로 입력하고 있습니다.

 

위 코드를 C언어로 변형하면

[ MessageBox(NULL, "Make me think your HD is a CD-ROM.", "abex' 1st crackme", MB_OK:MB_APPLMODAL); ]

이렇게 됩니다. C언어에서는 함수에 넘기는 변수의 순서가 어셈블리 언어에서는 역순으로 넘어가는 것을 알고계세요. 이유는 스택은 FILO(First In Last Out) 구조이기 때문입니다.

 

디버거를 EIP=0040100E 시점까지 f4 키를 통해 진행한 다음 해당 스택을 보시면 위 사진처럼 보실수 있습니다.

예전에 공부했던 스택부분으로 기억하시면서 보시면 거꾸로 저장되는것을 확인할수 있습니다. 그렇게 저장을 다하고 맨마지막에 MessageBoxA()가 실행되면서 스택에 있는 변수를 꺼내게 되는데 스택의 구조는 FILO, LIFO(Last In First Out)으로 마지막에 들어간것이 처음으로 나오게 되면서 MessageBoxA()입장에서는 변수들이 순서대로 꺼내는 입장이 되게 됩니다.

 

 

이상으로 크랙미#1에 대한 공부를 마치도록 하겠습니다. 이처럼 크랙에 대해 자세히 분석해보았고 실제 실습을 해보면서 공부하며 핵심 원리를 파악하시고 많이 연습하시면 좋을거 같습니다.

 

참고서적 : 리버싱 핵심 원리 / 저자 : 이승원

 

 

':: 리버싱' 카테고리의 다른 글

[정보 보안 개론] Chapter 06 악성코드  (0) 2016.11.07
Red_Seek :: 스택 프레임  (0) 2014.01.29
[Red_Seek] 스택  (0) 2014.01.22
[Red_Seek] IA-32 Register  (0) 2014.01.14
[Red_Seek] 엔디언 표기법  (0) 2014.01.09
posted by Red_Seek
2014. 1. 22. 16:01 :: 리버싱

안녕하세요.

Seek 입니다~

 

오늘은 스택에 대해서 공부하도록 하겠습니다.

스택은 컬 변수 저장, 함수 파라미터 전달, 복귀 주소 저장 등 다양한 용도로 사용됩니다. 디버깅을 할 때 스택 메모리를 확인하는 일이 많기 때문에 스택의 동작 원리를 잘 알아두면 디버깅 실력 향상에 크게 도움이 될것입니다.

 

 

1. 스택

 

   프로세서에서 스택 메모리의 역할은 다음 세가지입니다.

   

    1) 함수 내의 로컬 변수 임시 저장

    2) 함수 호출 시 파라미터 전달

    3) 복귀 주소(return address) 저장

 

   스택의 FILO(First In Last Out) 구조가 위와 같은 역할을 수행하기에는 아주 좋습니다.

 

2. 스택의 특징

 

   프로세스에서 스택 포인터(ESP)의 초기값은 Stack Bottom쪽(스택메모리의 바닥쪽)에 가깝습니다. PUSH 명령에 의해서 Stack에 값이 추가되면 바닥부터 메모리가 저장되므로 스택 포인터는 Stack Top을 향해 바닥에서 위쪽으로 움직이고, POP 명령에 의해 스택에서 값이 제거되면 다시 스택 포인터는 Stack Bottom을 향해 아래쪽(바닥쪽)으로 움직입니다. 즉, 높은 주소에서 낮은 주소 방향으로 스택이 자라납니다. 스택 메모리에서 바닥에서 위쪽으로 값이 저장되면서 윗 방향으로 스택이 자랍니다. 이러한 스택의 특성 때문에 '스택이 거꾸로 자란다.'라는 표현을 쓰기도합니다. 이렇게 표현하는 이유는 스택이라는 단어는 뭔가를 쌓는다는 뜻이기 때문에 쌓을수록 위로 올라오는 것이 더 직관적이기 때문입니다.

 

3. 스택 동작 예제

 

   실제로 스택이 어떤 식으로 동작하는지 이해를 돕기 위해 예제를 실습해보겠습니다.

 

위 그림은 초기상태의 스택입니다. 스택포인터(ESP) 값은 18FF8C입니다. 그아래 스택 창을 보면 ESP가 가리키는 주소와 그 값을 보여줍니다. 이제 왼쪽에 PUSH 100명령을 실행해보겠습니다.

 

 

위 사진은 PUSH 100을 실행 시킨 결과입니다. 스택포인터(ESP)를 보시면 18FF8C 에서 18FF88로 4바이트만큼 줄어들어서 값이 위로 올라가신것을 보실수 있습니다. 그리고 그 아래 스택 창을 보시면 18FF88에 100이 PUSH명령에 의해 저장되어 있는 것을 보실수 있습니다.

 

즉, 스택에 값을 집어 넣었더니 ESP가 위쪽 방향으로 이동한 것을 볼 수 있습니다. (ESP값이 4만큼 줄었습니다.) 다음에는 POP EAX를 실행해서 값을 다시 꺼내보도록 하겠습니다.

 

 

위 사진은 POP EAX 명령으로 다시 스택에서 값을 꺼낸 결과를 보여주는 사진입니다. 다시 ESP는 4바이트만큼 증가하여 18FF8C로 되었고, 그 아래 스택 창을 보시면 초기 상태와 같이 바꼈습니다.

 

즉, 스택에서 값을 꺼냈더니 ESP는 아래쪽 방향으로 이동하였습니다.

 

결과입니다.

"스택에 값을 입력(PUSH)하면 스택 포인터(ESP)는 감소(위로 이동)하고, 스택에서 값을 꺼내면(POP) 스택 포인터(ESP)는 증가(아래로 이동)합니다."

 

추가로 스택 포인터의 초기 값은 스택 메로리의 맨 아래쪽에 있음을 다시 알아두기 바랍니다.

 

 

 

 

 

오늘 공부한 간단한 예제는 스택에 값이 입/출력 될 때 스택포인터의 변화를 잘 보여주고 있습니다. 응용 프로그램의 디버깅을 위하여 스택과 스택포인터의 변화를 눈여겨 보시면서 공부하시길 바랍니다.

 

 

참고서적 : 리버싱 핵심 원리 / 저자 : 이승원

 

':: 리버싱' 카테고리의 다른 글

Red_Seek :: 스택 프레임  (0) 2014.01.29
[Red_Seek] abex' crackme #1 (크랙미 #1)  (0) 2014.01.23
[Red_Seek] IA-32 Register  (0) 2014.01.14
[Red_Seek] 엔디언 표기법  (0) 2014.01.09
[Red_Seek] 리버싱(Reversing) 이란?  (0) 2014.01.08
posted by Red_Seek
2014. 1. 14. 16:17 :: 리버싱

안녕하세요~ Seek 입니다.

오늘은 IA-32 레지스터에 대해 공부해보겠습니다.

(여기서 IA-32는 Intel Architecture 32비트를 의미합니다.)

 

1. CPU 레지스터

 

  레지스터란 CPU 내부에 존재하는 다목적 저장 공간입니다. 일반적으로 메모리라고 하는 RAM과는 조금 성격이 다릅니다. CPU가 RAM에 있는 데이터를 접근하기 위해서는 물리적으로 먼 길을 돌아가기 때문에 시간이 오래 걸리니다. 하지만 레지스터는 CPU내부에 있는 저장 공간이기 때문에 고속으로 데이터를 처리할 수 있습니다.

 

  리버싱 초급 단계에서 애플리케이션 디버깅을 잘 하려면 디버거가 해석해주는(디스어셈) 어셈블리 명령어를 공부해야 합니다. 어셈블리 명령어의 대부분은 레지스터를 조작하고 그 내용을 검사하는 것들이기 때문에 정작 레지스터를 모르면 명령어 자체도 이해하기 힘들기 때문에 레지스터를 공부해야합니다. 다들 화이팅 합시다! 저도 화이팅 중입니다 ㅎ

 

2. IA-32 레지스터

 

  IA-32 레지스터는 지원하는 기능도 많고 그만큼 수도 많이 있습니다. 애플리케이션 디버깅의 초급 단계에서는 Basic program execution register에 대해서 알아두어야 합니다. 디버깅할 때 가장 많이 보게 될 레지스터입니다.

 

 2-1. Basic program execution registers

위 그림처럼 크게 4가지의 종류가 있습니다.

   - General Purpose Registers (32비트 - 8개)

   - Segment Registers (16비트 - 6개)

   - Program Status and Control Register (32비트 - 1개)

   - Instruction Pointer (32비트 - 1개)

 

(참고 : 레지스터 이름 앞에 E(Extended)가 붙은 경우는 예전 16비트 CPU인 IA-16 시절부터 존재하던 16비트크기의 레지스터들을 32비트로 확장시켰다는 뜻입니다.)

 

 

   1) 범용 레지스터(General Purpose Registers)

 

      범용 레지스터는 이름처럼 범용적으로 사용됩니다. '막'쓰는' 막'레지스터들이라고 생각하세요. 32비트(4바이트)이며, 보통 상수/주소 등을 저장할 때 주로 사용되며, 특정 어셈블리 명령어에서는 특정 레지스터를 조작하기도 합니다.

 

위 사진은 범용 레지스터입니다.

 - EAX : 0~31(32비트)

 - AX : 0~15(16비트)

 - AH : 8~15(8비트)

 - AL : 0~7(8비트)

EAX를 기준으로 간단히 설명을 드리면, 4바이트(32비트)를 다 사용하고 있을 때는 EAX를 사용하고, 2바이트(16비트)만 사용할 때는 EAX의 하위 16비트 부분인 AX를 사용하면 됩니다. AX는 다시 상위 1바이트(8비트)인 AH와 하위 1바이트(8비트)인 AL로 나뉘어집니다. 이런식으로 32비트 레지스터를 상황에 맞게 크기에 맞게 나누어 알뜰하게 사용할 수 있습니다.

 

각 레지스터의 이름입니다.

 - EAX : Accumulator for operands and results data

 - EBX : Pointer to data in the DS segment

 - ECX : Counter for string and loop operations

 - EDX : I/O pointer

위 4개의 레지스터들은 주로 산술연산(ADD, SUB, XOR, OR등) 명령어에서 상수/변수 값의 저장 용도로 많이 사용됩니다. ECX와 EAX는 특수한 용도로도 사용됩니다. ECX는 반복문 명령어(LOOP)에서 반복 카운트(loop count)로 사용됩니다.(루프 돌 때마다 1씩 감소) EAX는 일반적으로 함수 리턴 값에 사용됩니다. 모든 Win32 API함수들은 리턴 값을 EAX에 저장한 후 리턴합니다.

(참고 : Win32 API 함수들은 내부에서 ECX와 EDX를 사용합니다. 이런 API가 호출되면 ECX, EDX의 값이 변경되기 때문에 ECX, EDX에 중요한 값이 저장되어 있다면 API 호철 전에 다른 레지스터나 스택에 백업해야 됩니다.)

 

나머지 레지스터들의 이름입니다.

 - EBP : Pointer to data on the stack (in the SS segment)

 - ESI : Source pointer for string operations

 - EDI : Destination pointer for string operations

 - ESP : Stack pointer (in the SS segment)

위 4개의 레지스터들은 주로 메모리 주소를 저장하는 포인터로 사용됩니다. ESP는 스택 메모리 주소를 가리킵니다. 스택 메모리 관리는 프로그램에서 매우 중요하기 때문에 ESP를 다른 용도로 사용하지 말아야 합니다. EBP는 함수가 호출되었을 때 그 순간의 ESP를 저장하고 있다가, 함수가 리턴하기 직전에 다시 ESP에 값을 되돌려줘서 스택이 깨지지 않도록 합니다. 이것을 Stack Frame 기법이라고 합니다. ESI와 EDI는 특정 명령어들(LODS, STOS, REP MOVS등)과 함께 주로 메모리 복사에 사용됩니다.

 

 

   2) 세그먼트 레지스터(Segment Registers)

 

    세그먼트 레지스터를 이해하기 위해 세그먼트에 대해 먼저 설명하겠습니다. 세그먼트는 IA-32의 메모리 관리 모델에서 나오는 용어입니다. IA-32보호모드에서 세그먼트메모리를 조각내어 각 조각마다 시작주소, 범위, 접근권한 등을 부여해서 메모리를 보호하는 기법을 말합니다. 또한 세그먼트는 페이징 기법과 함께 가상 메모리를 실제 물리 메모리로 변경할 때 사용됩니다.

 

세그먼트 메모리는 Segment Descriptor Table(SDT)이라는 곳에 기술되어 있습니다. 세그먼트 레지스터는 바로 이 SDT의 index를 가지고 있는것입니다.

 

세그먼트 레지스터는 총 6개(CS, SS, DS, ES, FE, GS)이며 각각의 크기는 16비트(2바이트)입니다.

 - CS : Code Segment

 - SS : Stack Segment

 - DS : Data Segment

 - ES : Extra(Data) Segment

 - FS : Data Segment

 - GS : Data Segment

 

CS는 프로그램 코드 세그먼트를 나타내며, SS는 스택 세그먼트, DS는 데이터 세그먼트를 나타냅니다. ES, FS, GS 세그먼트는 추가적인 데이터 세그먼트입니다.

 

 

   3) 프로그램 상태와 컨트롤 레지스터(Program Status and Control Register)

 

    - EFLAGS : Flag Register

 

    플래그 레지스터의 이름은 EFLAGS이며 32비트(4바이트) 크기입니다. (이역시 앞에 'E'가 붙어있어서 기존 16비트에서 32비트로 확장된 형태를 의미합니다.)

 

<EFLAGS Register>

 

 

위 그림은 EFLAGS 레지스터이며 각각의 비트마다 의미를 가지고 있습니다. 각 비트는 1 또는 0의 값을 가지는데, 이는 On/Off 혹은 True/False를 의미합니다. 일부 비트는 시스템에서 직접 세팅하고, 일부 비트는 프로그램에서 사용된 명령의 수행 결과에 따라 세팅되기도 합니다.

 

리버싱 입문단계에서는 애플리케이션 디버깅에 필요한 3가지 Flag(ZF, OF, CF)에 대해서만 잘 이해하시면 됩니다.

 

 - Zero Flag(ZF)

   연산 명령 후에 결과 값이 0이 되면 ZF가 1(True)로 세팅됩니다.

 

 - Overflow Flag(OF)

   부호 있는 수(Signed integer)의 오버플로가 발생했을 때 1로 세팅됩니다. 그리고 MSB(Most Significant Bit)가 변경되었을 때도 1로 세팅됩니다.

 

 - Carry Flag(CF)

   부호 없는 수(unsigned integer)의 오버플로가 발생했을 때 1로 세팅됩니다.

 

 

   4) Instruction Pointer

 

    - EIP : Instruction Pointer

 

    Instruction Pointer는 CPU가 처리할 명령어의 주소를 나타내는 레지스터이며, 크기는 32비트(4바이트)입니다. (이역시 16비트에서 32비트로 확장 되었습니다.) CPU는 EIP에 저장된 메모리 주소의 명령어(instruction)를 하나 처리하고 난 후 자동으로 그 명령어 길이만큼 EIP를 증가시킵니다. 이런 식으로 계속 명령어를 처리해 나갑니다.

 

범용레지스터와는 다르게 EIP는 그 값을 직접 변경할 수 없도록 되어 있어서 다른 명령어를 통하여 간접적으로 변경해야 합니다. EIP를 변경하고 싶을 때에는 특정 명령어(JMP, Jcc, CALL, RET)를 사용하거나 인터럽트(interrupt), 예외(exception)를 발생 시켜야 합니다.

 

 

 

이렇게 간단하게 레지스터들에 대해서 간략히 살펴보았습니다. 레지스터는 디버깅에 기초가 되는 어셈블러 명령어에 의해 조작되기 때문에 레지스터를 잘 알면 디버깅을 하는 데 크게 도움이 될 것 입니다.

 

 

참고서적 : 리버싱 핵심 원리 / 저자 : 이승원

':: 리버싱' 카테고리의 다른 글

Red_Seek :: 스택 프레임  (0) 2014.01.29
[Red_Seek] abex' crackme #1 (크랙미 #1)  (0) 2014.01.23
[Red_Seek] 스택  (0) 2014.01.22
[Red_Seek] 엔디언 표기법  (0) 2014.01.09
[Red_Seek] 리버싱(Reversing) 이란?  (0) 2014.01.08
posted by Red_Seek
2014. 1. 9. 17:12 :: 리버싱

안녕하세요.

오늘은 컴퓨터에서 메모리에 데이터를 저장하는 방식을 의미하는

바이트 오더링(Byte-Ordering)의 엔디언 표기법에 대해서 공부하겠습니다.

 

크게 두가지 리틀 엔디언(Little Endian)빅 엔디언(Big Endian)이 있습니다.

 

 

1. 바이트 오더링

 

  바이트 오더링이란 데이터를 저장하는 방식입니다. 애플리케이션의 디버깅을 할 때 알아두어야 하는 기본 개념중 하나입니다. 이 안에 리틀 엔디언, 빅 엔디언 두가지 방식이 있습니다.

 

예제 코드입니다.

 

총 4개의 서로 크기가 다른 자료형이 있습니다.

아래의 표는 위에서 말한 두가지 엔디언 방식에 따라서 같은 데이터를 각각 어떻게 저장했는지 나타내는 표입니다.

TYPE

Name 

SIZE 

빅 엔디언 스타일 

리틀 엔디언 스타일 

BYTE 

[12] 

[12] 

WORD 

[12][34] 

[34][12] 

DWORD 

dw 

[12][34][56][78] 

[78][56][34][12] 

char [] 

str 

[61][62][63][64][65][00] 

 [61][62][63][64][65][00] 

(참고 : ASCII 문자 'a'는 0x61, 'e'는 0x65와 같습니다. 그리고 문자열 마지막은 NULL로 끝납니다. 0x는 16진수의 표시입니다.)

 

위에 예제 코드와 표에 WORD, DWORD 타입으로 보시면 빅 엔디언 스타일은 앞에서부터 순차적으로 저장을 합니다.

하지만 리틀 엔디언 스타일역순으로 저장을 합니다.

 

그리고 역순으로 저장하는 리틀 엔디언도 바이트 자체는 정상적인 순서로 저장이 됩니다. 2byte, 4byte의 자료형과 같이 멀티바이트인 경우 각 바이트가 역순으로 저장되는 것입니다.

(멀티바이트 : a,b,c(1byte)와 같이 아스키코드로만 문자를 다 표현할 수 없어 아스키코드에 다른 문자(2byte)를 포함한 문자 집합)

 

또한 str 문자열은 엔디언 형식에 상관없이 동일하게 저장됩니다.

그 이유는 문자열은 결국 캐릭터(char)배열이기 때문에 각 바이트를 하나씩 연속해서 저장한다고 생각해보면 리틀 엔디언에서도 순차적으로 저장이 되기 때문에 동일합니다.

 

 

2. 빅 엔디언

 

  데이터를 순차적으로 저장하는 방식입니다. 빅 엔디언의 장점은 사람이 보기에 직관적이다. 그리고 빅 엔디언은 대형 UNIX 서버에 사용되는 RISC 계열의 CPU에서 많이 사용됩니다. 또한 네트워크 프로토콜에 빅 엔디언이 사용됩니다. 이는 x86 계열의 응용 프로그램 개발자와 리버서에게 중요한 의미를 가지고 있습니다.이유는 애플리케이션 개발에 사용된 데이터를 네트워크로 송수신할 때 엔디언 타입을 변경해야 하기 때문입니다.

 

 

3. 리틀 엔디언

 

  데이터를 역순으로 저장하는 방식입니다. Intel x86 CPU에서 리틀 엔디언 방식을 사용합니다. 따라서 저희 같은 windows 계열 리버서들은 리틀 엔디언에 대해서 잘 알아야 합니다. 장점으로는 산술 연산과 데이터의 타입이 확장/축소될 때 더 효율적입니다.

 

 

4. 디버거 프로그램(OllyDbg)에서 리틀 엔디언 확인

 

예제 코드

 

위에 예제 코드를 Visual C++에서 빌드하여 LittleEndian.exe 실행 파일을 만들어 OllyDbg로 디버깅 후 변수들이 저장된 모습의 사진입니다. 

 

바로 위 사진에서 보시면 더 위쪽에 빅 엔디언과 리틀 엔디언 방식을 비교해둔 표 처럼 리틀 엔디언 방식처럼 역순으로 저장된 것을 확인할 수 있습니다.

 

 

 

위에서 말씀 드렸듯이 windows 계열에서 리버싱을 하는 저희 같은 리버서들은 리틀 엔디언을 잘 알아야 합니다. windows 기반이 모두 리틀 엔디언 방식으로 저장 되어 있으니까요.

 

 

참고서적 : 리버싱 핵심 원리 / 저자 : 이승원

 

':: 리버싱' 카테고리의 다른 글

Red_Seek :: 스택 프레임  (0) 2014.01.29
[Red_Seek] abex' crackme #1 (크랙미 #1)  (0) 2014.01.23
[Red_Seek] 스택  (0) 2014.01.22
[Red_Seek] IA-32 Register  (0) 2014.01.14
[Red_Seek] 리버싱(Reversing) 이란?  (0) 2014.01.08
posted by Red_Seek
2014. 1. 8. 17:57 :: 리버싱

보안과 프로그램을 공부하면서

처음으로 공부하기 시작한 리버싱.

기초 및 이론은 "이승원님 / 리버싱 핵심 원리" 책을 참고하여 작성 하겠습니다.

저 역시 "리버싱 핵심 원리" 책으로 공부중입니다.

 

그럼 리버싱이란 무엇일까요?

 

 

1. 리버스 엔지니어링(Reverse Engineering)

 

  일반적인 의미에서는 '역공학'이라고 하며 어느 특정 물건, 장치 등이 있으면 그것에 대한 구조, 기능, 동작 등을 역으로 따라가며 분석하고 그 원리를 이해하며 부족한 부분을 보완하며 새로운 기능 등을 추가하는 작업입니다.

 

 

2. 리버스 코드 엔지니어링(Reverse Code Engineering)

 

  RCE(Reverse Code Engineering)는 소프트웨어 분야의 리버스 엔지니어링 이라고 생각하시면 될것같습니다. 제가 공부하는 것 역시 소프트웨어 분야의 작업이기 때문에 위에서 말한 리버싱(Reversing)이 여기에 해당하겠습니다.

 

 

#리버싱# : 소프트웨어 분야에서 해당 프로그램의 구조, 기능, 동작 등의 원리를 역으로 따라가며 이해하고 분석하여 부족한 부분이 있거나 추가 되었으면 하는 새로운 기능 등을 추가하는 전체적 행위가 되겠습니다.

 

 

3. 리버싱 방법

 

  - 정적분석 : 파일의 겉모습을 관찰하여 분석하는 방법입니다. 파일을 실행하지 않고 파일의 종류(exe,dll, doc, zip 등), 크기, 헤더정보, 실행 압축 여부, 등록 정보, 디지털 인증서 등의 내용을 확인하는 것입니다. 또한 디스어셈블러를 이용해서 내부코드와 그 구조를 확인하는 방법입니다.

 

  - 동적방법 : 파일을 직접 실행시켜서 그 행위를 분석하고, 디버깅을 통하여 코드의 흐름과 메모리의 상태 등을 자세히 살펴보는 방법입니다. 레지스트리, 네트워크 등을 관찰하면서 프로그램의 행위를 분석하고 디버거를 이용하여 프로그램 내부 구조와 동작 원리를 분석하기도 합니다.(디버깅은 리버싱에서 비중이 큰 작업이지만 리버싱의 하위 개념입니다.)

 

 

4. Source Code, Hex Code, Assembly Code

  리버싱에서는 보통 실행파일을 취급합니다.

 

  - Source Code

    Visual C++에서 소스코드(HelloWorld.cpp)를 실행 시킨 사진입니다.

 

 

  - Hex Code

    위에 소스코드에서 Visual C++에서 빌드하여 생성된 실행 파일은 컴퓨터가 이해할 수 있는 2진수 형식으로 되어 있는데 그것을 16진수인 Hex Code로 변환된 사진입니다.

 

 

  - Assembly Code

    솔직하게 Hex Code역시 보기 쉬운 형태는 아닙니다. 따라서 사람이 이해하기 쉬운 어셈블리코드 형태로 보기 위해 디버거 프로그램(OllyDbg)으로 위 소스코드를 빌드해서 생성한 실행파일 HelloWorld.exe를 디버거한 사진입니다.

 

이렇게 어셈블리 코드를 가지고 해당 실행파일을 분석하게 됩니다.

 

 

 

 

 

저도 보안공부와 프로그램공부를 하면서 역공학, 리버싱이란 단어만 들었지 그것이 무엇을 하는지 정확히 모르고 있었습니다.

이제부터 자세히 공부해보록 하겠습니다.^^

 

 

 

 

참고서적 : 리버싱 핵심 원리 / 저자 : 이승원

':: 리버싱' 카테고리의 다른 글

Red_Seek :: 스택 프레임  (0) 2014.01.29
[Red_Seek] abex' crackme #1 (크랙미 #1)  (0) 2014.01.23
[Red_Seek] 스택  (0) 2014.01.22
[Red_Seek] IA-32 Register  (0) 2014.01.14
[Red_Seek] 엔디언 표기법  (0) 2014.01.09
posted by Red_Seek