0. 개요

1. C/C++

... 1.1 General

... 1.2 Optimization

... 1.3 Code Generation

... 1.4 Language

... 1.5 Advanced

2. Linker

... 2.1 Manifest File

... 2.2 Debugging

... 2.3 Advanced





0. 개요

  이 문서에서는 VC++의 옵션들에 대해서 다룰 것이며 특히 리버싱과 관련된 즉 생성되는 어셈블리 코드들을 변화시키는 옵션들 위주로 정리한다. 





1. C/C++

1.1 General

1.1.1 Debug Information Format [ 디버그 정보 형식 ] :  /Z7  /Zi  /Zl

  " /Zi "를 설정하면 디버거에서 사용할 형식 정보와 기호 디버깅 정보를 포함하는 프로그램 데이터베이스인 .pdb 파일이 생성된다. 그리고 자동으로 링커 옵션의 /debug가 설정된다. 빌드 후 확인해 보면 실행 파일과 같은 폴더에 pdb 파일이 생성된 것을 알 수 있다.


* PDB

  심볼 파일(.pdb)은 함수, 변수 이름 등의 정보를 갖으며, 디버깅 시에 어셈블리와 함께 이러한 정보들을 같이 볼 수 있음으로서 디버깅을 훨씬 쉽게 해준다. 즉 클래스, 메서드 및 기타 코드의 소스 파일을 만드는 식별자를 프로젝트의 컴파일된 실행 파일에 사용되는 식별자에 매핑(소스 코드의 문을 실행 파일의 실행 명령에 매핑)한다.



1.1.2 SDL checks :  /sdl  /sdl-

  권장되는 Security Development Lifecycle 검사를 추가한다. 추가되는 것은 2가지가 있는데 첫 째는 오류와 같은 추가 보안 관련 경고이고 둘 째는 추가 보안 코드 생성 기능이다. 즉 첫번째는 컴파일 시 여러 경고를 오류로 활성화 한다. 예를들면 위험한 CRT 함수를 사용한 경우에 경고가 뜨면서 컴파일을 할 수 없을 때가 있는데 (C4996 경고) 이것은 이 옵션 때문이며, 프로젝트 생성시에 SDL 검사를 해제하면 된다. 


  다른 하나는 런타임 검사의 경우 여러 검사를 런타임에 수행하기 위한 코드를 생성한다. MSDN에 다르면 다음과 같다.


- /GS로 컴파일할 때와 동일한 #pragma strict_gs_check(push, on) 런타임 버퍼 오버런 검색에 대해 strict 모드를 활성화합니다.

- 제한된 포인터 삭제를 수행합니다. 역참조를 포함하지 않으며 사용자 정의된 소멸자가 없는 식에서 포인터 참조는 delete에 대한 호출 이후 유효하지 않은 주소로 설정됩니다. 이렇게 하면 오래된 포인터 참조가 재사용되지 않습니다.

- 클래스 멤버 초기화를 수행합니다. 개체 인스턴스화 시(생성자 실행 전) 모든 클래스 멤버를 자동으로 0으로 초기화합니다. 이렇게 하면 생성자가 명시적으로 초기화하지 않는 클래스 멤버와 연관된 초기화되지 않은 데이터를 사용하지 않도록 방지할 수 있습니다.




1.2 Optimization

1.2.1 Optimization [ 최적화 ] :  /Od  /O1  /O2  /Ox

  /Od의 경우 최적화 기능을 사용하지 않으면 디폴트 옵션이다. 참고로 디버깅 시에 어셈블리 명령어들의 양이 늘어나 있는 것을 볼 수 있다. 어떤 면에서는 최적화되지 않고 풀어써져 있어서 이해하기 어려운 코드들이 줄어들지만 또 어떤 면에서는 왜 삽입되어 있는지 모를 명령어들이 보이기도 한다. 더 분석해봐야 겠다.


  /O1의 경우 크기 최적화로서 다음 옵션들(/Og /Os /Oy /Ob2 /Gs /GF /GY)이 자동으로 포함된다. 일반적으로 가장 작은 크기의 코드를 생성한다. /O2의 경우 속도 최적화로서 다음 옵션들(/Og /Oi /Ot /Oy /Ob2 /Gs /GF /GY)이 자동으로 포함된다. Release 빌드의 디폴트 설정이며 일반적으로 가장 속도가 빠른 코드를 생성한다.


  /Ox의 경우 최대 최적화로서 다음 옵션들(/Ob2 /Og /Oi /Ot /Oy)이 자동으로 포함된다. 일반적으로 /Ox 대신 /O2를 사용한다고 한다. 



1.2.2 Inline Function Expansion [ 인라인 함수 확장 ] :  /Ob0  /Ob1  /Ob2

  기본적으로 컴파일러는 자신의 판단에 따라 함수를 인라인 확장할지를 결정한다. 함수를 호출하는 경우에는 인자를 push하고 함수를 call하는 등 함수 호출 오버헤드가 존재하는데 만약 함수의 크기가 작거나 하는 경우 따로 함수를 호출하는 대신 코드를 통합함으로써 이러한 오버헤드를 줄일 수 잇는 것이다.


  /Ob0은 인라인 확장을 사용하지 않도록 설정한다. /Ob1은 inline, __inline, __forceinline으로 표시된 함수나 클래스 선언된 C++ 멤버 함수만 확장한다. /Ob2는 디폴트 옵션으로서 inline, __inline, __forceinline으로 표시된 함수 외에도 컴파일러가 판단하여 선택한 기타 함수들을 인라인 확장한다. 또한 위에서 살펴보았듯이 /O1, /O2, /Ox를 사용할 때도 적용된다. 



1.2.3 Enable Intrinsic Functions [ 내장 함수 사용 ] :  /Oi

  응용 프로그램이 더 빨리 실행될 수 있도록 일부 함수 호출을 내장 함수나 특정한 형태의 함수로 교체한다. 즉 특정한 함수 호출을 컴파일러의 내장 함수로 대체하는 것이다. 참고로 인라인 함수와 헷갈릴 수 있는데 인라인 함수의 경우 특정 함수를 호출하는 형태에서 호출 대신 자체적으로 통합시키는 것이며 이것은 컴파일러가 특정 함수를 대체시킨다는 면에서는 비슷하다. 하지만 대체하는 내장 함수는 컴파일러가 자체적으로 그것에 대한 지식이 있으므로 상응하는 내장 함수가 존재한다면 그 함수 호출을 더 나은 방식으로 통합시킬 수 있다. 



1.2.4 Favor Size Or Speed [ 크기 또는 속도 ] :  /Os  /Ot

  /Os는 코드 크기 우선으로서 속도보다 크기를 우선적으로 처리하도록 컴파일러에 지시하며 /Ot는 코드 속도 우선으로서 크기보다는 속도를 우선적으로 처리하도록 컴파일러에 지시한다. 이 옵션들도 각각 /O1과 /O2에 포함된다.



1.2.5 Omit Frame Pointers [ 프레임 포인터 생략 ] :  /Oy  /Oy-

  호출 스택에서 프레임 포인터를 생성하지 않으며 이에 따라 함수 호출 속도가 빨라진다. 이것은 x86 컴파일러에서만 사용할 수 있다. /Oy를 사용하면 프레임 포인터가 생략되며 /Oy-를 사용하면 프레임 포인터 생략이 비활성화된다.


  사실 리버싱을 공부할 때 스택 프레임을 배우면서 EBP를 이용한 방식의 메커니즘을 배우게 된다. 이 옵션을 사용하면 즉 /Oy가 설정되면 이 EBP를 스택 프레임으로서 사용하지 않고 General Purpose로 사용하게 된다. 이렇게 됨으로써 몇 개 되지 않은 범용 목적의 사용 가능한 레지스터가 하나 더 추가될 수 있지만 리버싱하는 입장에서는 문제점이 더 많이 발생하게 된다.


  먼저 Call Stack을 확인할 필요가 있는 경우 디버거의 명령어나 기능을 통해서든 아니면 직접 확인하여 찾든지 간에 이 EBP를 이용해야 할 것인데 이것이 사용되지 않으므로 콜 스택을 구별할 수가 없어진다. 참고로 x64의 경우에도 레지스터를 이용하므로 이런 방식을 통한 콜 스택 추적이 불가능하다. 또한 PDB 형식도 이 EBP를 이용하여 로컬 변수 같은 정보들이 저장되어 있다. 물론 윈도우에서 제공되는 함수들을 확인해 보면 디버깅을 지원하기 위하여 이 옵션을 사용하지 않아서인지 프레임 포인터가 계속 쓰이고 있는것으로 보인다.



1.2.6 Whole Program Optimization [ 전체 프로그램 최적화 ] :  /GL

  전체 프로그램 최적화를 사용하지 않으면 모듈(컴파일)별로 최적화가 수행된다. 




1.3 Code Generation

1.3.1 Enable C++ Exceptions :  /EHa  /EHs  /EHsc

  기본적으로 SEH는 운영체제 차원에서 지원된다. 즉 __try, __except 구문을 사용하면 된다. 아래의 옵션들은 C++ 문법의 catch문에서 SEH나 extern "C"로 선언된 함수의 예외를 catch할 수 있는지의 여부를 판단한다.


  /EHa를 설정하면 C++ EH 뿐만 아니라 SEH도 catch 구문에서 catch할 수 있다. /EHs를 설정하면 catch 구문에서 C++ EH를 catch하지만 SEH는 받을 수 없다. 또한 컴파일러에 extern "C"로 선언된 함수가 예외를 throw할 수 있다. /EHsc를 설정하면 catch 구문에서 C++ EH를 catch하지만 SEH는 받을 수 없다. 또한 컴파일러에 extern "C"로 선언된 함수가 예외를 throw할 수 없다.



1.3.2 Runtime Library [ 런타임 라이브러리 ] :  /MT  /MTd  /MD  /MDd  /LD

  /MT는 런타임 라이브러리의 다중 스레드 정적 버전을 사용한다. 즉 런타임 라이브러리의 API 함수를 사용할 경우 실행 파일에 정적으로 링크한다. /MTd는 디버그 버전을 링크한다. /MD는 런타임 라이브러리의 다중 스레드 별 및 DLL 별 버전을 사용한다. 즉 실행 파일이 런타임 라이브러리의 DLL을 임포트하여 필요한 함수를 호출하여 사용하는 방식으로서 이것을 동적 링크라고 한다. /MDd는 디버그 버전을 링크한다. /LD는 DLL 개발 시의 옵션이다.



1.3.3 Security Check [ 보안 검사 ] :  /GS  /GS-

  /GS는 스택 버퍼를 위한 보안 검사를 추가한다. 즉 함수의 반환 주소나 예외 핸들러의 주소에 쿠키를 삽입하여 버퍼가 오버플로우 되었는지를 검사한다.



1.3.4 Control Flow Guard [ 행 가드 제어 ] :  /guard:cf

  제어 흐름 보호 (CFG : Control Flow Guard). vtable을 이용한 가상 함수 호출이나 콜백 함수의 경우 특정 실행 시점에서 실행될 함수가 컴파일 시에 정적으로 결정되는 것이 아니라 런타임 시에 함수 포인터를 이용해 결정된다. 그렇기 때문에 이러한 함수 호출을 간접 호출이라고 한다. 어셈블리 루틴으로 보자면 다음과 같다.


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

MOV ESI, DWORD PTR DS:[ESI]

MOV ECX, ESI

PUSH 1

CALL ESI

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


  제어 흐름 보호는 이러한 간접 호출을 보호하기 위한 기법으로서 간접 호출 직전에 검사하는 것이다. 생성되는 코드는 다음과 같이 __guard_check_icall_fptr이라는 래퍼 함수 호출이 추가되어 있다.


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

MOV ESI, DWORD PTR DS:[ESI]

MOV ECX, ESI

PUSH 1

CALL DWORD PTR DS:[__guard_check_icall_fptr]

CALL ESI

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


  이 래퍼 함수는 /guard:cf 옵션이 꺼져 있다면 단지 retn만 존재하는 루틴을 가리키지만 옵션이 켜져 있다면 LdrpValidateUserCallTarget()를 가리킨다. 또한 IMAGE_LOAD_CONFIG_DIRECTORY 구조체에 관련 값들이 추가된다. 컴파일 시에 생성되는 이 값들과 호출할 함수 포인터를 이용해 유효한 함수 포인터인지 비교하여 검사한다.




1.4 Language

1.4.1 Enable Run-Time Type Information [ 런타임 형식 정보 사용 (RTTI) ] :  /GR

  런타임에 개체 형식을 검사하는 코드를 추가한다. 일반적으로 코드에서 dynamic_cast 연산자 또는 typeid를 사용할 경우 이 옵션이 필요하다. 이것을 사용하면 .rdata 섹션 크기가 증가한다. 그러므로 dynamic_cast, typeid를 사용하지 않는 경우 /GR-를 사용해서 이미지 크기를 줄일 수 있다.




1.5 Advanced

1.5.1 Calling Convention [ 호출 규칙 ] :  /Gd  /Gr  /Gz  /Gv

  /Gd는 __cdecl, /Gr는 __fastcall, /Gz는 __stdcall, /Gv는 __vectorcall을 의미한다.



1.5.2 Compile As [ 컴파일 옵션 ] :  /TC  /TP

  /TC는 C 코드로 컴파일하며 /TP는 C++ 코드로 컴파일한다.





2. Linker

2.1 Manifest File

2.1.1 Generate Manifest [ 메니페스트 생성 ] :  /MANIFEST

  xml 파일로 생성된다고 하지만 일반적으로 리소스 섹션에 통합되는 것으로 보인다. 여기서는 UAC 관련된 내용만 다루겠다.



2.1.2 Enable User Account Control (UAC) [ 사용자 계정 컨트롤 사용 ] : /MANIFESTUAC

  프로그램 매니페스트에 UAC 정보를 포함할지 여부를 지정한다.



2.1.3 UAC Execution Level [ UAC 실행 수준 ] :  /level='asInvoker' or 'highestAvailable' or 'requireAdministrator'

  레벨에는 asInvoker (응용 프로그램을 시작한 프로세스와 동일한 권한으로 응용 프로그램 시작), highestAvailable (최대한 높은 권한 수준으로 응용 프로그램 실행), requireAdministrator (관리자 권한으로 실행)이 있다.


* UAC

  리소스 섹션을 보면 xml 형태의 문자열이 보인다. 여기서 requestedExecutionLevel level="highestAvailable" 같은 형태의 문자열을 볼 수 있다. 파일을 실행할 때 참고할 UAC 값이 여기에 존재한다.




2.2 Debugging

2.2.1 Generate Debug Info [ 디버그 정보 생성 ] :  /DEBUG  /DEBUG:FASTLINK

  참고로 /Zi가 설정되어 있으면 자동으로 /DEBUG가 설정되며 실행 파일들에 디버그 정보를 넣는다. 실제로 생성된 실행 파일을 분석해 보면 디버그 섹션이 추가되어 있고 이 섹션에 디렉토리들의 주소가 들어가 있어서 소스 코드라던지 .pdb 파일의 위치라던지 하는 정보가 들어가 있다. 참고로 디버그 섹션은 .rdata 섹션에 통합되는 경우가 많다.


  사족으로 프로그램을 직접 작성한 후 리버싱하려고 할 때 pdb 파일 없이 분석하려고 한다면 이 pdb 파일을 지우거나 이동시키고 바이너리 이름을 변경해야 한다. pdb 파일을 지우거나 이동시킨다는 것은 바이너리 내부에 pdb의 경로가 저장되어 있으므로 실행 파일만 옮긴다고 해서 pdb를 읽어오지 못하는 거이 아니라는 점이다. 아예 그 경로에 pdb 파일이 없어야 읽어올 수 없다. 그리고 바이너리 이름을 변경해야 한다는 것은 해당 바이너리를 처음 읽어올 때 대표적인 디버거들의 경우 이것을 저장해 놓는 경우가 많다. 그래서 pdb가 이미 없더라도 처음 읽어왔을 때 저장한 정보를 가지고 pdb를 이용해 분석된 내용이 계속 존재할 것이다. 그래서 바이너리의 이름을 변경함으로써 이전에 저장된 분석 내용을 불러오지 않게 하는 것이다. 



2.2.2 Strip Private Symbols [ 전용 기호 제거 ] :  /PDBSTRIPPED

  빌드할 때 두 번쨰 PDB가 만들어지는데 여기에는 고객에게 제공하지 않을 기호가 생략된다. 즉 여기에는 공용 기호, 개체 파일의 목록과 개체 파일에서 제공하는 실행 파일의 일부, 스택을 통과시키는데 사용된 FPO(프레임 포인터 최적화) 디버그 레코드가 생략되며 포함되지 않는 것으로는 형식 정보, 줄 번호 정보, 개체 파일별 CodeView 기호(함수, 지역 및 정적 데이터에 대한 기호 등)이 있다.


  참고로 실행 파일 및 원래 pdb 파일이 생성되는 release 폴더가 아니라 프로젝트 폴더에 생성된다. 그리고 비교해 보면 두 번째 PDB의 크기가 훨씬 작다는 것을 확인할 수 있다.



2.2.3 Generate Map File [ 맵 파일 생성 ] :  /MAP

  맵 파일이 생성된다. 이것도 실행 파일 및 원래 pdb 파일이 생성되는 release 폴더가 아니라 프로젝트 폴더에 생성된다. 그리고 pdb와는 달리 텍스트 파일의 형태이다. 개인적으로 pdb의 경우 디버거를 통해 읽혀져 많은 디버깅 정보를 얻을 수 있는 유용한 파일로 알고 있지만 map 파일은 굳이 pdb 파일이 있는데 어디에 필요한지 잘 모르겠다. 어쨌든 pdb가 없는 경우라면 텍스트 파일로 되어 있는 map 파일을 직접 읽어서 유용하게는 사용할 수 있을 것 같다. 


  MSDN에 따르면 맵 파일에 들어있는 정보는 다음과 같다.

- 파일의 기본 이름인 모듈 이름

- 파일 시스템이 아니라 프로그램 파일 헤더의 타임스탬프

- 프로그램의 그룹 목록. 각 그룹의 시작 주소(section:offset), 길이, 그룹 이름 및 클래스가 함- 께 표시됩니다.

- 공용 기호 목록. 각 주소(section:offset), 기호 이름, 플랫 주소, 기호가 정의된 .obj 파일이 함께 표시됩니다.

- 진입점(section:offset)


* 추가

  pdb와 map 파일 간의 차이점에 대해서 찾아보았는데 "John Robbins"가 "Debugging Applications"라는 책에서 한 설명을 보면 맵 파일은 프로그램의 전역 심볼들과 소스 그리고 줄 번호에 관한 정보를 텍스트 형태로 보여주는 파일이라고 한다. 이것은 마이크로소프트가 심볼 테이블 형식을 주기적으로 바꾸게 됨으로써 오래된 프로그램의 경우 오래된 심볼 엔진 버전을 찾기가 힘들 수 있는데 맵 파일이 텍스트 형태로 존재함으로써 분석 시에 더 용이함을 줄 수 있다는 것이다.

[ http://stackoverflow.com/questions/14640676/why-should-we-need-the-map-file-when-pdb-file-is-available-in-windows-platform ]




2.3 Advanced

2.3.1 [ 기준 주소 ] : /BASE

  ASLR을 사용하지 않을 경우 바이너리의 ImageBase 주소는 보통 0x00400000이다. 아래의 "임의 기준 주소" 옵션을 비활성화하고, 이 옵션을 사용하며 주소를 0x01000000으로 설정한다면 바이너리의 ImageBase 주소가 0x01000000으로 설정된 것을 볼 수 있다. 거의 사용되지 않겠지만 가끔은 사용할 수도 있는 옵션으로 보인다.



2.3.2 Randomized Base Address [ 임의 기준 주소 ] :  /DYNAMICBASE

  ASLR 기능을 활성화한다.



2.3.3 Data Execution Prevention (DEP) [ 데이터 실행 방지 ] :  /NXCOMPAT

  데이터 실행 방지 (DEP) 기능 활성화



2.3.4 Image Has Safe Execption Handlers [ 이미지에 안전한 예외 처리기 포함 ] :  /SAFESEH

  SEH 오버플로우 공격을 방어하는 기법으로 /GS 옵션도 있지만(SEH 프레임에 GS 쿠키 사용) /SAFESEH 옵션도 존재한다. 이것은 간략하게 말해서 핸들러가 유효한 핸들러인지를 검사해 준다. 


  /SAFESEH 설정 시에 PE 헤더에 IMAGE_LOAD_CONFIG_DIRECTORY 구조체가 생성된 것을 볼 수 있다. 이 구조체의 여러 멤버 중에서 여기에 사용되는 것은 SEHandlerTable 필드와 SEHandlerCount 필드이다. SEHandlerCount 필드는 프로그램에서 설치되는 SEH 핸들러의 개수이며 SEHandlerTable 필드는 핸들러들의 주소들을 담는 __safe_se_handler_table의 주소이다.


  로더는 PE가 로드될 때 IMAGE_LOAD_CONFIG_DIRECTORY 구조체의 SEHandlerTable 필드의 값인 __safe_se_handler_table에 이 프로그램에서 사용되는 핸들러들의 주소를 설정하고 그 개수는 IMAGE_LOAD_CONFIG_DIRECTORY 구조체의 SEHandlerCount 필드에 설정한다. 이후 예외가 발생하면 KiUserExceptionDispatcher()를 지나 RtlDispatchException()에서 호출할 특정 핸들러와 __safe_se_handler_table의 엔트리들을 비교해서 상응하는지를 검사한다.



'악성코드 분석' 카테고리의 다른 글

리눅스 안티바이러스 구현에 관한 정리  (0) 2017.07.04
Windbg, Gdb 명령어 정리  (0) 2017.06.27
다형성 바이러스  (4) 2017.05.16
API Sets  (0) 2017.05.12
윈도우의 예외 처리  (0) 2017.05.09
Posted by SanseoLab

블로그 이미지
Malware Analyst
SanseoLab

태그목록

공지사항

Yesterday
Today
Total

달력

 « |  » 2024.4
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30

최근에 올라온 글

최근에 달린 댓글

글 보관함