'악성코드 분석'에 해당되는 글 56건

  1. 2017.04.23 윈도우의 드라이버 개발과 루트킷 그리고 AV
  2. 2017.04.23 간단한 패커 개발
  3. 2017.04.23 Yoda's Protector 분석
  4. 2017.04.23 패커들 분석 5
  5. 2017.04.23 윈도우의 자료형 정리 1
  6. 2017.04.23 Ollydbg [올리디버거] 2.01 매뉴얼 1

-1. 현재

http://sanseolab.tistory.com/33 ] 해당 글과 같이 공부 및 실습 중이며 간단한 프로젝트를 해볼 생각이다.





0. 서론

  커널 관련 공부를 시작하면서 드라이버를 직접 개발해보는 것이 공부하는데 많은 도움이 될 것이라고 생각해 왔다. 루트킷의 원리를 이해하고 안티바이러스나 게임 보안 등의 프로그램을 개발해 보는 것도 관련 지식을 쌓는데 큰 도움이 될 것 같아서 드라이버 개발과 관련된 내용을 공부하고 정리하고 있다. 하지만 가장 큰 단점 중 하나는 인터넷에서 떠도는 대부분의 자료들이 너무 오래된 것들이고 심지어는 최근에 올라오는 자료들도 이러한 SSDT 후킹 같은 기술들 위주로 올라오는 것이 개인적으로는 마음에 들지 않았다.

  안티바이러스를 보더라도 국내 뿐만 아니라 외국의 자료들도 마찬가지인 것이 대부분 안티바이러스 개발과 관련된 자료는 스캐너 뿐이며 이것도 사용자 영역에서 시그니처를 기반으로한 것이 대부분이다. 하지만 분명히 내가 모르는 수많은 기술들과 자료들이 있을 것이라는 생각으로 많이 찾아보았지만 능력의 한계로 개략적인 방식과 관련된 자료들만을 겨우 얻을 수 있었다.





1. 드라이버 개발과 관련된 변화

  현재는 32비트 운영체제 보다는 64비트 운영체제가 주류를 이루고 있다. 윈도우 비스타 x64부터 시작해서 64비트 운영체제에는 많은 변화가 추가되었고 더 추가되고 있는 중이다. 눈여겨 볼 것 중 하나는 필터 관리자가 커널에 추가되었다는 점이다. 이것으로 인해 특히 안티바이러스 업체의 경우에 큰 변화가 생기게 되었는데 미니필터 드라이버를 개발함으로써 과거에 했던 방식보다 더 진보된 방식의 개발이 가능해지게 되었다고 한다. 커널의 필터 관리자가 제공해주는 기능을 사용함으로써 더 쉽고 빠르게 더 안전한 안티바이러스를 개발할 수 있게 되었다는 것이다.

  또한 드라이버와 관련된 정책에서도 여러 변경 사항이 추가되었다. 첫 번째는 KPP(Kernel Patch Protection)이다. 이것은 다른 말로 패치 가드라고도 불리는데 x64 버전의 비스타부터는 이 보호 정책으로 인하여 SSDT 후킹 같은 커널 영역의 수정이 불가능해 졌다. 즉 이것을 우회하지 않고서는 과거처럼 SSDT 후킹을 이용한 루트킷이 존재할 수 없으며 이것은 안티바이러스 프로그램에게도 마찬가지이다. 대신 앞에서 말한 미니필터 드라이버 외에도 후술할 MS에서 제공하는 다른 기능들을 사용할 수 있게 되었다.

  두 번째 변화는 KMCS(Kernel Mode Code Signing)이다. 이것도 x64 버전의 비스타부터 시행되는 정책으로서 간단히 말해서 모든 드라이버는 서명이 필요하다는 것이다. 즉 테스트 모드로 설정을 하지 않는 이상 모든 드라이버는 서명이 되어 있지 않다면 커널에 설치할 수 없게되었다. 

  지금까지 언급한 것들 이외에도 많은 변화들이 있을 것이며 추가될 것으로 여겨진다. 확실한 사실은 과거와는 달리 이제는 윈도우의 보안 정책들을 우회하는 취약점을 사용하지 않고서는 과거처럼 간단하게 루트킷을 만들고 감염시킬 수 없다는 점이다. 

  참고로 이런 저런 시도를 해보는 도중에 알게된 것은 윈도우 7의 32비트 버전의 경우에 32비트이기 때문에 KPP와 KMCS가 적용되지 않는다는점 외에도 필터 관리자도 커널에 존재한다는 장점이 있다. 하지만 추후에 살펴볼 PsSetCreateProcessNotifyRoutineEx()나 ObRegisterCallbacks() 같은 확장된 함수나 새로 추가된 함수들을 사용하기 위해서는 32비트 윈도우 7에서도 드라이버에 서명이 필요한 것은 마찬가지라는 것이다.

  개인적으로 KPP 같은 강력한 보안 및 코드 서명 등으로 인한 루트킷 수의 감소와 (물론 더 복잡하고 강력한 방식으로 계속되겠지만) 드라이버 관련 개발과 연구가 아마추어 개발자 입장에서는 쉽게 이루어질 수 없는 현실(코드 서명을 통해)로 인해서 루트킷의 원리를 공부하고자 하는 것 뿐만 아니라 안티 바이러스나 게임 보안 프로그램의 원리를 공부하려고 했던 계획이 잘 진행이 되지 않고 있다. 또한 SSDT 후킹 같은 과거의 기술을 공부해야 하는지에 등의 생각 때문에 아직도 혼란스럽다.





2. 안티바이러스 개발에 대한 정리

  비스타에서 여러 API들과 필터 관리자 등의 변화가 생기기 전까지만 해도 대부분의 AV들은 루트킷처럼 만들어져서 감시할 API들을 후킹하였다고 한다. 하지만 MS에서 기능들을 지원함에 따라 후킹 기반 모니터에서 이제는 미니 필터 드라이버와 커널 통지 콜백들을 사용한다.


2.1 자가 보호

  안티바이러스 프로그램은 특성상 자기 자체를 보호할 필요가 있다. 안티바이러스에 대한 공격으로는 프로세스 종료 외에도 메모리 변조 같은 공격이 있을 수 있다. 과거에는 SSDT 후킹을 사용해서 프로세스 관련 함수를 후킹하여 자가 보호를 구현하였다면 KPP에 의한 커널 변경 방지로 인해 최근에는 관련 함수가 제공되고 있다. 

  ObRegisterCallbacks() 함수는 비스타부터 제공되는 함수로서 프로세스 및 스레드 객체에 대한 특정한 사전 / 사후 동작에 관한 통지를 받는 콜백 함수를 등록하는 함수이다. 즉 각 객체에 대한 핸들의 생성 및 복사 등의 동작에 대해 콜백 함수를 등록해 주는 것이다. 예를들면 이로 인해 유저 모드에서 프로세스나 스레드에 접근하려고 하는 경우 이 콜백이 먼저 호출되고 만약 이러한 접근을 차단하거나 권한을 수정하는 내용을 등록했다면 유저 모드에서는 접근할 수 없게되는 것이다.


2.2 파일 스캐닝

  확실하진 않지만 일반적인 스캐너에서 사용하는 방식이라기 보다는 실시간 감시 같은 기능에서 파일이 생성될 때 하는 검사에서 사용되는것으로 보인다. 이것은 파일 시스템 미니필터 드라이버를 사용해서 구현한다. 과거에는 파일 생성과 관련된 API들에 대한 SSDT 후킹을 통해서 구현했던 것으로 보인다.

  조금 더 자세히 설명하자면 커널의 필터 관리자를 이용하는 함수들을 통해 구현하는데 예를들면 FltRegisterFilter()는 필터링 할 IRP에 해당하는 콜백 정보를 구성하고 필터 관리자에게 이 정보를 등록하도록 요청하며 FltStartFiltering()은 등록된 미니필터 드라이버의 필터링을 시작한다. 이 외에도 많은 함수들이 지원된다. 

[ FltRegisterFilter(), FltBuildDefaultSecurityDescriptor(), FltCreateCommunicationPort(), FltFreeSecurityDescriptor(), FltStartFiltering(), FltReadFile(), FltSendMessage(), FltCancelFileOpen(), FltCloseCommunicationPort(), FltUnregisterFilter() ]

  참고로 MS는 이것과 관련된 드라이버 예제 샘플도 제공하는데 Scanner는 AV에서 사용되는 파일 데이터 스캐너 관련 예제이며 AVScan은 transaction-aware 파일 스캐너와 관련된 에제이다. 


2.3 프로세스 / 스레드 보호

  예전부터 이것과 관련된 함수들이 지원되어 왔다. PsSetCreateProcessNotifyRoutine()은 프로세스의 생성과 소멸을 모니터링하기 위한 콜백 루틴을 등록해주는 함수이다. 비스타 이후에서는 이것의 확장형인 PsSetCreateProcessNotifyRoutineEx() 함수가 제공되며 최신 AV들도 이 함수를 사용하고 있다. 이 확장된 함수는 이전 버전과 달리 프로세스의 객체 등의 많은 정보들 뿐만 아니라 결과적으로 프로세스의 생성을 막을 수 있는 기능도 제공된다.

  이것과 비슷한 함수로 PsSetCreateThreadNotifyRoutine()이 있으며 대상은 스레드이다. 예를들면 외부 코드를 일반 프로세스의 내부에서 스레드로 실행 못하게 하는데 사용될 수도 있다. 윈도우 10부터는 PsSetCreateThreadNotifyRoutineEx() 함수가 추가되었다.

  마지막으로 PsSetLoadImageNotifyRoutine() 함수가 있는데 이것은 이름처럼 이미지가 로드되었을 때 통지해주는 콜백을 등록할 수 있다. 예를들면 이미지가 올라온 경우 아직 프로세스가 시작되지 않았다고 하더라도 어떤 dll을 또는 어떤 드라이버를 로드할지 알 수 있기 때문에 유용하게 사용될 수 있다. 


2.4 레지스트리 보호

  레지스트리 또한 자가 보호에서나 시스템 보호에서나 중요한 보호 대상 중 하나이다. MS는 레지스트리에 대해서도 레지스트리 필터링 드라이버를 제공한다. 옛날부터 RegistryCallback 루틴을 등록하는 CmRegisterCallback() 함수가 제공되어 왔다. 이 콜백 루틴은 프로세스가 레지스트리 관련 동작을 수행하기 전에 각 레지스트리 관련 동작에 관한 통지를 받을 수 있다. 최근 비스타부터는 확장된 버전인 CmRegisterCallbackEx() 함수를 사용할 수 있다.


2.5 네트워크 보호

  안티바이러스에서도 기본적으로 방화벽 기능이 제공된다. 과거에는 NDIS 및 TDI 필터 같은 드라이버를 개발해야 했지만 최근 비스타 부터는 WFP(Windows Filtering Platform)라는 플랫폼을 제공한다. 이것은 네트워크 필터링 애플리케이션을 만들기 위해 제공되는 플랫폼으로서 API와 시스템 서비스들의 집합이다. 참고로 과거의 NDIS / TDI는 다른 필터 드라이버들보다 개발하기 위한 난이도가 있었지만 WFP라는 간단한 개발 플랫폼이 제공됨으로 인해 최근 버전의 윈도우에서 돌아가는 안티바이러스 프로그램들은 이것을 사용한다고 한다.



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

윈도우의 예외 처리  (0) 2017.05.09
링크 및 책 정리  (0) 2017.04.23
간단한 패커 개발  (0) 2017.04.23
Yoda's Protector 분석  (0) 2017.04.23
패커들 분석  (5) 2017.04.23
Posted by SanseoLab



Source Code : https://github.com/101196/SimplePacker





0. 개요

  사실 패커 개발과 관련된 자료는 흔치 않게 찾아볼 수 있지만 제대로 된 설명도 부족하고 처음 배우기에는 너무 많은 것을 다루는 경향이 있어보인다. 그래서 공부를 하면서 동시에 이렇게 정리해 보기로 하였다. 여러가지 패커들을 분석하면서 직접 개발을 해보는 것도 나쁘지 않겠다는 생각이 들었기 때문이다. 


  이미 오픈 소스로 제공되는 라이브러리들도 존재하고 PE를 다루는것도 직접 해보고 싶기도 해서 기본적인 목적만 달성할 수 있을 정도의 패커를 만들어 보았다. 원래는 다른 오픈 소스 패커들을 보면서 공부하는 것으로 끝내려 했지만 대부분의 것들이 코드가 방대하고 공부할 수 있을 정도로 제대로 정리되어 있다고 보기도 힘들었기 때문이다.


  개인적으로 간단하게 끝날 수 있을 거라고 생각했지만 이 정도 간단한 것을 만드는 데에도 상당한 시간이 들었다. PE를 다루는 것이 제공되는 구조체를 사용한다 하더라도 굉장히 까다로웠고 IAT를 복구하기 위한 과정들도 생각과 달랐다. 이것을 개발하면서 메모리를 다루는 일이 많았고 그래서인지 C 언어가 왜 고급 언어인지도 새삼 깨닫게 되었다. 소스 코드 뿐만 아니라 많은 자료들을 포함해서 여러 부분을 다른 곳에서 참고하였지만 개발에 익숙하지 않았기 때문에 겨우 간단한 패커를 만들 수 있었다. 참고로 다시 볼 때마다 수정할것이 그리고 추가하고 싶은 부분이 본인 눈에도 계속 보일 정도이므로 직접 해보았다는 것에 중점을 두고자 한다.




1. 정리

  기본적으로 패커는 원본 실행 파일을 읽어서 압축을 하고 그 내용을 새롭게 만들어질 실행 파일에 쓴다. 새롭게 만들어질 이 실행 파일에는 압축한 부분을 디코딩할 디코딩 루틴이 추가되어야 한다. 또한 압축 해제된 실행 파일은 정상적인 PE 로더가 처리하는 과정을 거치지 않았기 때문에 임포트 테이블 부분이 일반 파일과 같이 그대로 존재하므로 PE 로더가 수행하는 과정을 직접 수행할 수 있도록 디코딩 루틴에 추가해 주어야 한다. 이 외에도 이 모든 것들을 처리하려면 PE 헤더를 적절하게 수정해 주어야 한다.


  소스 코드를 보면 크게 디코딩 루틴, 몇몇 함수들(여러 곳에서 참조하였다) 마지막으로 main 함수가 보일 것이다. 각 장에서 main 함수를 기준으로 차례대로 정리해 보겠다. 그보다 먼저 패킹된 파일을 보면서 결과물을 기준으로 설명한 후에 소스 코드를 설명하겠다. 사용 방법은 다음과 같이 간단하며 옵션도 없다.


> packer.exe main.exe


  또한 이 패커는 콘솔 프로그램만 지원하는데 이것은 .text 섹션, .rdata 섹션 그리고 .data 섹션만 인식하고 나머지는 버릴 것이기 때문이다. 물론 콘솔 프로그램 중에서도 몇몇 추가적인 섹션들도 들어가 있는 경우가 있지만 실행하는데 크게 의미가 없는 경우도 있기 때문에 간단한 프로그램 정도는 동작할 것이다.


  어쨌든 패킹된 파일을 보면 섹션이 3개 존재하는 것을 볼 수 있다. 이것은 UPX의 특징을 살려서 만들었다. .nothing 섹션은 UPX의 UPX0 섹션과 같이 바이너리 상에서는 크기가 0이지만 메모리에 올라올 경우 실제 PE의 역할을 하는 섹션이다. 즉 뒤에 나올 압축된 내용이 여기에 풀리게 된다. 두 번째 섹션인 .packedc 섹션은 압축된 내용이 들어가고 뒤에 디코딩 루틴이 추가된다. 즉 UPX1 섹션과 같다. 사실 압축도 그냥 하는 것이 아니라 미리 특별한 처리를 해놓고 하기 때문에 그것은 뒤에서 알아볼 것이다. 마지막으로 UPX2 섹션과 비슷한 .importt 섹션이 존재한다. 여기에는 디코딩 루틴에서 사용할 API들의 임포트 테이블이 들어간다.


  이렇게 기본적인 사항을 알아보았지만 다음에 설명하듯이 소스 코드를 보다 보면 생각보다 많은 부분을 고려해야 한다는 것을 알 수 있을 것이다. 위에서도 언급하였듯이 최대한 간단한 패커를 만들려고 했고 콘솔 프로그램이라는 제한까지 있는데도 불구하고 말이다.




2. 소스 코드 분석 - 디코딩 루틴 및 여러 함수들

  가장 먼저 aplib.lib 라이브러리를 사용한다는 것을 알 수 있다. 이 라이브러리는 Ibsen Software[ http://ibsensoftware.com/products_aPLib.html ]에서 제공하는 압축 알고리즘 라이브러리이다. 실제로 여러 패커들에서 이 라이브러리를 사용한다. 이것을 사용함으로써 우리는 압축 알고리즘을 직접 만든다거나 할 필요없이 간단하게 제공되는 함수를 사용해서 개발을 할 수 있다. 또한 뒤에서 알아보겠지만 디코딩 루틴도 어셈블리어로 제공되기 때문에 약간 수정을 해서 사용할 수도 있다. 어쨌든 다운로드한 디렉토리를 열면 lib/coff/ 디렉토리에 aplib.h와 aplib.lib 파일이 존재하는 것을 볼 수 있으므로 이것을 소스 코드가 있는 디렉토리에 옮겨 넣는다.


  다음에 나오는 부분은 디코딩 루틴이다. 정확히 말하자면 APLib에서 제공되는 src/32bit/depack.asm을 수정한 것이다. 참고로 제공되는 어셈블리 프로그램은 fasm 문법으로 되어있기 때문에 약간 수정을 가한 것이다. 이 외에도 추가된 부분이 여럿 있는데 하나는 0xAAAAAAAA (추후에 압축된 내용이 들어갈 .packedc 섹션의 시작 주소가 들어갈 위치), 0xBBBBBBBB (압축 해제된 내용이 들어갈 .nothing 섹션의 시작 주소가 들어갈 위치), 0xCCCCCCCC(실제 OEP 주소가 들어갈 위치)가 보인다. 이 부분은 뒤에서 실제 주소로 수정할 것이다. 즉 나중에 수정할 때 찾기 쉽게 하기 위하여 이런 값을 사용한 것이다. 또한 nop을 여러개 추가한 것도 각 값을 읽어올 때 32비트 단위로 읽어오기 때문에 단위를 맞추기 위한 것이다. 나중에 직접 리버싱을 해보면 무슨 의미인지 알 수 있을 것이다. 물론 다른 방법도 있겠지만 여기에까지 많은 시간을 쏟을 수 없어서 참조한 곳의 소스를 최대한 따를 수 있을 만큼 따른 결과물이다.


  디코딩 루틴 다음에는 0xDDDDDDDD (추후에 보겠지만 수정된 임포트 테이블의 시작 주소가 들어갈 위치), 0xEEEEEEEE (임포트 테이블 복구 루틴에서 사용할 LoadLibraryA() 및 GetProcAddress() 함수를 사용하기 위한 주소가 들어갈 위치)가 보이고 임포트 테이블 복구 루틴이 보인다. 임포트 테이블 복구 루틴은 fsg나 kkrunchy 등에서 사용되는 루틴이다. 사실 이 루틴을 사용하기 위해서는 파일에 존재하는 임포트 테이블 부분을 그대로 사용하면 안되고 이것도 수정해 주어야 한다. 뒤에서 살펴볼 것이다.


  다음에 나오는 함수들은 PE 헤더를 다룰 수 있게 도와주는 함수이다. align_to_boundary() 함수는 PE의 특성상 Alignment 즉 경계를 맞추어야 하기 때문에 사용되는 함수이다. 뒤에서 사용될 때 설명할 것이다. my_int 함수는 union의 경우에 바이트를 다룰 때 사용된다. 이것도 직접 사용될 때 설명할 것이다.




3. 소스 코드 분석 - Main 함수

  처음은 전형적인 메모리 맵 파일 방식을 통해 바이너리를 읽어온다. CreateFile()로 패킹할 파일의 파일 오브젝트를 오픈해서 핸들을 얻고 CreateFileMapping() 함수를 통해 파일 매핑 커널 오브젝트를 생성한 후에 MapViewOfFile()로 파일 매핑 오브젝트를 프로세스의 주소 공간에 매핑시킨다. 반환 값은 매핑된 view의 시작 주소가 되며 소스 코드에서는 lpFile 변수를 사용했다.


  이후에는 PE 구조체를 정의하는 코드가 나온다. 여기서 이용하는 PE 헤더 구조체들로는 IMAGE_NT_HEADERS, IMAGE_FILE_HEADER, IMAGE_OPTIONAL_HEADER, IMAGE_SECTION_HEADER 등이 있다. PE 구조에 익숙하다면 이 구조체를 통해 쉽게 PE 헤더를 사용할 수 있을 것이다. 에를들면 GetFirstSectionHeader() 함수의 경우에는 간단하게 첫 번째 섹션 헤더를 구할 수 있게 해준다.


a. OEP 저장 및 임포트 테이블 수정 

  가장 먼저 OEP 즉 원본 파일의 엔트리 포인트를 미리 저장해 놓는다. 이제부터 상당히 까다로운 부분이 존재한다. 위에서 임포트 테이블 복구 루틴을 설명했는데 저 루틴은 파일 상태 그대로의 임포트 테이블을 복구하는 루틴이 아니다. 미리 특별한 형태로 저장된 데이터를 이용해서 임포트 테이블을 복구하는 것이다. 그러므로 우리는 그 특별한 형태로 임포트 테이블을 저장해 놓아야 한다. 즉 여기서 할 일은 현재 원본 바이너리 상태로 있는 임포트 테이블 부분을 다음과 같은 형태로 변경하는 것이다.


01 [첫 번쨰 DLL의 IAT 주소] [첫 번째 DLL 이름] 00 [api 이름] 00 [api 이름] 00 ... [api 이름] 00 01 [두 번째 DLL의 IAT 주소] [두 번째 DLL 이름] 00 [api 이름] 00 [api 이름] 00 ... 00 02


  참고로 여기서 api의 이름도 약간 다른데 이름의 첫 문자에 0x02만큼 값을 추가한 것이다. 이것은 예제를 들면서 이 루틴이 어떻게 수행되는지를 설명하겠다.


01 34 20 40 | 00 56 43 52 | 55 4E 54 49 | 4D 45 31 34   | 4 @ VCRUNTIME14

30 2E 64 6C | 6C 00 6F 65 | 6D 73 65 74 | 00 61 5F 74   | 0.dll oemset a_t

65 6C 65 6D | 65 74 72 79 | 5F 6D 61 69 | 6E 5F 72 65   | elemetry_main_re

74 75 72 6E | 5F 74 72 69 | 67 67 65 72 | 00 61 65 78   | turn_trigger aex

63 65 70 74 | 5F 68 61 6E | 64 6C 65 72 | 34 5F 63 6F   | cept_handler4_co

6D 6D 6F 6E | 00 61 5F 74 | 65 6C 65 6D | 65 74 72 79   | mmon a_telemetry

5F 6D 61 69 | 6E 5F 69 6E | 76 6F 6B 65 | 5F 74 72 69   | _main_invoke_tri

67 67 65 72 | 00 01 B0 20 | 40 00 61 70 | 69 2D 6D 73   | gger ° @ api-ms

2D 77 69 6E | 2D 63 72 74 | 2D 73 74 64 | 69 6F 2D 6C   | -win-crt-stdio-l

31 2D 31 2D | 30 2E 64 6C | 6C 00 61 5F | 70 5F 5F 63   | 1-1-0.dll a_p__c

6F 6D 6D 6F | 64 65 00 61 | 5F 73 74 64 | 69 6F 5F 63   | ommode a_stdio_c

6F 6D 6D 6F | 6E 5F 76 66 | 70 72 69 6E | 74 66 5F 73   | ommon_vfprintf_s

00 61 5F 61 | 63 72 74 5F | 69 6F 62 5F | 66 75 6E 63   |  a_acrt_iob_func

...

00 02 00 00 .. 


  가장 먼저 01이 있다. 이것은 다음에 올 것이 DLL의 IAT 주소라는 것을 알려준다. 즉 0x01만큼 뺐을 때 0이 된다면 다음이 DLL의 IAT 주소라는 것이다. 그리고 다음에 오는 00 40 20 34를 이 DLL의 IAT 시작 주소로 설정한다. 바로 뒤에는 00으로 끝나는 DLL의 이름 문자열이 온다. 즉 이 널 종료 문자열은 DLL의 이름이 되고 이 문자열을 LoadLibraryA()에 넣고 DLL의 핸들을 얻어온다. 그리고 다음 값을 검사하는데 0x01만큼을 빼고 0이 아닌 경우가 된다. 이것은 다음에 올 것이 DLL이 아니라는 것이다. 다시 0x01만큼 또 뺀다. 그래서 00이 아니면 이것은 API의 이름으로 여기고 GetProcAddress()로 API 이름과 LoadLibraryA()에서의 핸들 값을 통해 이 API의 주소를 얻어오고 아까 설정한 DLL의 IAT 시작 주소에 써 넣는다. 위에서 보면 API 이름이 oemset인 것을 볼 수 있다. 여기서 첫 번째 문자 o에서 0x02만큼 뺀다면 memset인 것을 알 수 있다. 만약 0x01을 빼고 다시 한 번 0x01을 뺐을 때 즉 0x02만큼 뺀 경우에 값이 0x00이 되면 이 루틴은 종료하게 된다. 그래서 마지막 부분을 보면 값이 0x02로 끝난 것을 볼 수 있다. 어쨌든 이 부분은 원본 바이너리의 임포트 테이블을 가지고 이런 식으로 만들어주는 역할을 한다.


b. 메모리에 정리해서 올리기

  바이너리가 파일 상태로 있는 것과 메모리에 올라온 것은 서로 다를 수 밖에 없다. 즉 파일은 파일 오프셋 주소를 통해 보면 알겠지만 File Alignment 단위로 나뉘어서 붙어있고 메모리에 올라온 경우에는 Section Alignment 단위로 올라온다. 그래서 PE 로더처럼 이것들을 메모리에 올라온 것 처럼 위치를 바꾸어줄 필요가 있다.


  참고로 더 설명해 보자면 이 상태로 압축을 할 것이고 이에 따라 압축이 해제된 모습도 이 상태가 될 것이다. 또한 압축한 이후에는 모든 섹션들과 안의 내용이 의미가 없기 때문에 모든 부분을 새로 만들 것이다. 왜냐하면 .nothing 섹션은 아무것도 없으며 .packedc 섹션도 곧 만들 압축된 내용과 뒤에서 추가할 디코딩 루틴으로만 이루어져 있고 마지막으로 .importt 섹션도 직접 만들 것이기 때문이다. 즉 버퍼에 압축된 내용을 넣은 후로 이제 새로운 내용을 써 넣어갈 것이다.


c. 섹션 압축하기

  aPLib에서 제공하는 aP_pack() 함수를 사용해서 지금까지 설정한 메모리의 범위를 압축한다. 참고로 이 함수의 반환값은 패킹한 데이터의 결과물의 크기이다. 이 함수를 사용하기 이전에 malloc()을 이용해 여러 버퍼를 할당하였는데 workmem 버퍼의 경우 패킹이 수행되는 동안 필요한 작업 영역이며 이것은 aP_workmem_size() 함수를 통해 크기를 미리 구할 수 있다. 즉 인자로 패킹할 대상의 크기를 넣으면 반환값으로 작업 영역의 최대 크기가 반환된다. 이 값을 malloc()으로 할당한다. 나머지는 패킹된 결과가 들어갈 버퍼로 이것은 aP_max_packed_size() 함수로 구하는데 이 함수는 인자로 들어온 크기의 데이터를 패킹할 경우 결과물의 최대값을 반환한다. 


d. 섹션 3개 처리하고 나머지 부분 지우기

  1에서 언급한 것처럼 3개의 섹션을 설정해 준다. 그리고 나머지 섹션은 그냥 지워버린다. 참고로 위에서 설명하였듯이 Section Alignment와 File Alignment를 고려해서 정리해야 한다. 섹션 헤더의 속성(Characteristics)에 대해서 좀 더 알아보자. 다음은 주요 속성들이다.


00000020 : IMAGE_SCN_CNT_CODE

00000040 : IMAGE_SCN_CNT_INITIALIZED_DATA

20000000 : IMAGE_SCN_MEM_EXECUTE

40000000 : IMAGE_SCN_MEM_READ

80000000 : IMAGE_SCN_MEM_WRITE


  예를들면 코드 섹션의 경우 속성은 IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_CNT_CODE가 더해진 0x60000020가 된다. 하지만 실질적으로 우리가 생각해야할 것은 IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_WRITE이다. PE 로더가 신경쓰고 우리에게 영향을 미칠만한 속성은 앞의 3개가 전부이다. .nothing 섹션의 경우 언패킹된 데이터가 쓰여져야 하며 추후에 실행되어야 할 섹션이기 때문에 0xE0000020을 준다. .packedc 섹션의 경우 압축된 데이터가 저장되기 때문에 메모리를 읽을 뿐만 아니라 여기에 디코딩 루틴도 쓰여져 있으므로 0xE0000040을 주기로 한다. 마지막으로 .importt 섹션의 경우 실행할 코드가 있지 않고 단지 .rdata 섹션과 같으므로 0xC0000040을 주기로 한다.


e. .packedc 섹션에 쓰기

  압축했던 것을 .packedc 섹션에 써 넣는다.


f. 데이터 디렉토리 처리하기

  데이터 디렉토리의  두 번째 즉 Import Table 부분의 크기와 위치를 설정한다. 참고로 .importt 섹션을 아직 처리하지도 않았는데 어떻게 설정하냐고 물을 수 있지만 이 부분은 미리 정해놓아서 즉 크기까지 구해놓았기 때문에 미리 설정할 수 있었다.


g. 디코딩 루틴 처리하기

  디코딩 루틴의 주소 부분을 정리한 후에 (0xAAAAAAAA 이런거) .packedc 섹션에서 패킹된 데이터의 뒤에 붙인다.


h. 기타 섹션 헤더 처리하기

  EP를 이 디코딩 루틴의 시작 주소로 놓고 새로 만들어질 바이너리의 크기 같은 기타 섹션 헤더들을 맞게 수정해 준다.


i. .importt 섹션 처리하기

  UPX와 비슷한 방식의 임포트 섹션을 순수하게 직접 만들어서 넣어준다. UPX로 패킹된 바이너리의 UPX2 섹션과 거의 비슷하므로 이것을 보면서 이해하면 될 것이다.


j. 파일 줄이기

  SetFilePointer()와 SetEndOfFile() 함수를 사용해서 파일의 크기를 줄인다. 즉 뒤에 0x00으로 채워지는 부분들을 아예 삭제하는 것이다. 참고로 나머지 뒷 부분이 모두 0으로 되어 있어야 통하는 것으로 보인다.




4. 결론

  사실 이것은 매우 단순화한 패커일 뿐이다. 패킹할 수 있는 대상도 한정되어 있고 여러가지 문제점이 존재할 수 있다. 예를들면 UPX의 경우 upx0, upx 섹션의 write 속성을 제거한다. 임포트 테이블에서 VirtualProtect()도 포함하였기 때문에 UPX처럼 write 속성을 제거해도 된다. 그렇지 않으면 매우 취약한 프로그램이 될 것이다. 이것은 UPX의 디코딩 루틴을 참고하여 간단하게 몇 줄의 어셈블리어를 추가하면 될 것이다.




5. 참고

http://ibsensoftware.com/products_aPLib.html : aPLib

- StackOverflow : align_to_boundary() 및 my_int() 함수

https://0x00sec.org/t/pe-file-infection/401 : 어셈블리 루틴 다루기



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

링크 및 책 정리  (0) 2017.04.23
윈도우의 드라이버 개발과 루트킷 그리고 AV  (0) 2017.04.23
Yoda's Protector 분석  (0) 2017.04.23
패커들 분석  (5) 2017.04.23
윈도우의 자료형 정리  (1) 2017.04.23
Posted by SanseoLab



0. 프로필

1. 개요

2. Packed PE

3. 알고리즘

4. MUP




0. 프로필

라이선스 : 프리웨어

상태 : v1.03

주소 : https://sourceforge.net/projects/yodap/files/Yoda%20Protector/



1. 개요

  Yoda's Protector는 Ashkbiz Danehkar이 Visuaal C++ 환경에서 제작하었다. 이것은 기존의 Yoda's Crypter에 데이터와 코드 섹션을 압축하는 기능을 추가한 버전이다. 기본적으로 현재 프로그램이 디버깅되는지를 판단해 안티 디버깅을 수행하고 다형성 코드 방식의 암호화와 압축 기술(LZO라고 한다)을 사용한다.


  안티 디버깅 기법으로 "Jumping into the middle of an instruction" 방식과 IsDebuggerPresent(), BlockInput() 그리고 SoftICE의 드라이버를 검사하는 것 등이 있으며 부모 프로세스를 검사해서 Explorer.exe가 아닌 경우에는 중단시키는 기술도 존재한다. 또한 코드의 다형성을 위해 다형성 암호화와 압축 기술을 사용한다. 


옵션을 보면

- Protection

Anti-SoftICE Protection (default)

Checksum Protection (default)

API Redirection (default)

Anti-Dump Protection (default)

Clear Import Information (default)

Clear PE header


- Advanced

Remove .reloc section (default)

Remove debug information (default)

Eliminate MS-DOS header

Optimize MS-DOS header


- Other

Create backup copy (BAK-file) (default)

Auto run after loading

Exit when done

Section's Name


- Compress Option

1 - 10



2. Packed PE

  일반 섹션들은 이름이 지워지고 .rsrc 섹션은 그대로 있다. .yP 섹션이 추가되는데 여기에는 Import Table과 stub 코드가 있다. Import Table을 보면 기본적으로 LoadLibraryA()와 GetProcAddress()가 필요하다. EP는 .yP 섹션의 Import Table 바로 뒤에 존재한다.



3. 알고리즘

  처음에는 CALL과 예외를 이용한 안티 디스어셈블리 방식이 존재한다. 일반적으로 "Jumping into the middle of an instruction"라고도 불리는데 CALL 명령어를 사용해 디스어셈블러의 분석이 실패하도록 주소를 호출한다. 물론 Ollydbg의 경우에는 Ctrl+A 단축키 또는 "Analyse Code"를 통해 깔끔하게 정리된 디스어셈블을 볼 수 있다. 정확히 표현하자면 명령어 중간에 "DB E9" 즉 무의미한 바이트를 써 넣고 call이나 jmp 명령어로 이 주소 다음 주소로 분기하도록 하여 실제 흐름에는 영향이 없지만 디스어셈블러가 명령어를 분석하는데 실패하도록 하는 방식이다. 이것도 여기서는 간단하게 사용되서 감당할만 한데 과하게 사용되는 경우에는 Ollydbg를 사용해도 분석하기 매우 까다롭다. Ollydbg의 경우 분석 이후 화면이 전환되기도 하고 그 부분만 분석하는 기능도 한계가 있기 때문이다. 더 좋은 디스어셈블러들의 경우 깔끔하게 정리된 것을 보여주기도 한다. 또한 동시에 다음 EIP 주소를 스택에 PUSH하는 CALL의 특징을 사용하는데 예외 핸들러를 설치할 때 이 PUSH된 주소가 예외 핸들러의 주소로 가게 된다. 그리고 INT 3을 이용해 예외를 발생시킨다. 이러한 부분이 5번 존재한다. 이 부분은 Step Into를 사용해 진행하다가 SEH가 설치되면 그 핸들러의 주소에 BP를 걸고 INT 3 명령어로 인한 예외를 디버거가 받으면 Shift+F9 키를 사용해 예외를 프로그램에게 돌려준다. 물론 옵션을 사용해 이 예외를 받지 않게할 수도 있다.


  사실 특이한 점으로는 예외 발생 시에 예외 핸들러가 호출되고 이것이 반환된 후에 다음 코드가 실행되는 것이 아니고 예외 핸들러 실행 도중에 다른 예외가 발생하여 다른 예외 핸들러가 호출되는 방식이 5번이나 존재한다는 것이다.


  어쨌든 이후에 복호화 루틴이 존재한다. 진행하다 보면 LOOP로 만들어진(즉 LOOP로 끝나는 프로시저) 복호화 루틴이 있는데 이 부분에 주의해야 할 것이 이 부분도 CALL과 JMP를 이용한 "Jumping into the middle of an instruction" 기법이 사용되기 때문에 코드가 상태가 좋지 않아서 조심스럽게 Step Into로 진행해야 한다. 이곳은 LOOP 명령어에 Conditional BP를 걸면 되는데, "ECX == 2"를 입력하고 실행(F9)시켜야 한다. 그러면 쉽게 LOOP 부분을 빠져나올 수 있다. 참고로 이런 루프가 하나 더 존재한다. 여기서 LOOP 명령어 뒤의 명령어에 F4를 눌러서 하는 방식은 통하지 않기 때문에(왜냐하면 이 LOOP 명령어 바로 뒤의 명령어가 현재 루프의 복호화 과정으로 인해 수정되기 때문에) 조건부 BP를 걸어야 하는 것이다.


  자세히 살펴보면 이 복호화 루프는 복호화해서 넣는 주소가 바로 다음 주소부터 0x3031만큼이라는 것을 알 수 있다. 이후 복호화가 끝나면 다음 명령어를 수행하게 된다. 그리고 앞에서 언급하였듯이 다음에도 또 다른 복호화 루프가 존재한다. 이 루프가 하는 행위를 보면 파일에 존재했던 Import Table 말고 스텁 루틴에서 사용할 Import Table을 복구한다. 복구되는 위치는 이미 존재하는 Import Table의 바로 뒤이다. 


  이 루프가 끝나면 예외를 이용한 안티디버깅이 하나 더 존재한다. 평소와 다른 차이점은 예외 관련 지식이 있으면 알겠지만 앞 부분과 달리 일반적인 방식처럼 호출한 예외 핸들러가 종료하여 반환을 하게 되는데 그렇기 때문에 KiUserExceptionDispatcher() 내부에서 NtContinue()까지 실행된다. 여기서 저장된 Context를 통해 복귀할 주소를 알아내는 방법을 보겠다. NtContinue()에서 받는 pContext 인자의 주소를 보면 스택에 존재하는 것을 볼 수 있다. 이 주소가 0x0019E41C라고 하자. 여기에서 0xB8만큼 더한 값 즉 0x0019E4D4 주소에 들어있는 값인 0x0041A7C1이 예외 핸들러에서 복귀한 후에 갈 주소이다. 


  사실 일반적인 경우라면 예외 핸들러가 호출된 후에 다음 명령어로 복귀될 것이다. 그래서 이렇게 복잡한 방식 대신 그냥 다음 명령어에 BP를 걸면 될 것이다. 하지만 이 부분도 복호화되서 변하는 부분이므로 미리 BP를 걸어놓고 실행하면 종료되어 버리기 때문에 이렇게 복잡한 방식을 사용하였다.


  이 부분을 지나면 API들의 주소를 찾는다. 참고로 원본 프로그램에서 사용할 API들이 아니라 Yoda가 압축 해제와 보호용으로서 사용할 API들이다. 전형적인 방식으로서 LoadLibraryA()와 GetProcAddress()를 통해 처리한다. 아까 구했던 이름을 가지고 주소를 얻어서 이름 다음 부분에 차례로 집어넣는다. 이후 스텁에서는 이 주소를 가지고 API들을 호출하게 된다. 패커가 아닌 프로텍터여서 그런지 사용하는 API들이 상당히 많다. 다음은 그 목록이다.


- Kernel32.dll

GetModuleHandleA(), VirtualProtect(), GetModuleFileNameA(), CreateFileA(), GlobalAlloc(), GlobalFree(), ReadFile(), GetFileSize(), CloseHandle(), IsDebuggerPresent(), CreateToolhelp32Snapshot(), GetCurrentProcess(), GetCurrentProcessId(), Process32First(), Process32Next(), Module32First(), Module32Next(), Thread32First(), Thread32Next(), OpenThread(), OpenProcess(), TerminateProcess(), SetPriorityClass(), GetPriorityClass(), ExitThread(), GetWindowsDirectoryA(), CreatWindowA(), GetCurrentThread(), SetThreadPriority(), SuspendThread(), Resumethread(), GetLastError(), GetSystemTime(), GetTickCount(), GetVersion(), DebugActiveProcess(), DebugActiveProcessStop()


- User32.dll

MessageBox(), SendMessageA(), WaitForInputIdle(), BlockInput(), GetWindowLongA(), SetWindowLongA(), GetForegroundWindow(), FindWindowA(), GetTopWindow()


- Advapi32.dll

RegCreateKeyExA(), RegOpenKeyExA(), RegCloseKey(), RegSetValueExA(), RegQueryValueExA(), CryptAcquireContextA(), CryptReleaseContext(), CryptCreateHash(), CryptDestroyHash(), CryptHashData(), CryptDeriveKey(), CryptDestroyKey(), CryptEncrypt(), CryptDecrypt()


  참고로 CALL을 통한 "Jumping into the middle of an instruction" 기법으로 인해 스택은 계속 쌓여 가지만 큰 의미는 없어보인다. 그리고 언패킹을 하면서 상황에 맞게 Code Analysis를 수행했다 말았다를 해야할 것이다. 


  어쨌든 이 부분이 끝나면 여러 안티 디버깅 기법들이 나온다. 먼저 GetVersion(), GetForegroundWindow(), FindWindowA(), GetTopWindow()를 사용하는 프로시저가 나온다. 이후 프로시저에서는 GetCurrentProcess(), GetPriorityClass(), SetPriorityClass()를 사용하고 BlockInput()을 사용한다. 참고로 이 API는 안티디버깅에 사용되는데 해결하는 방법은 Step Into로 따라 들어가서 RETN 4를 제외하고 모든 부분을 NOP으로 바꾸면 된다. 그리고 암호화와 관련한 루프문이 나온다. CryptAcquireContextA(), CryptCreateHash(), CryptHashData(), CryptDeriveKey(), CryptDestroyHash() 등의 API들을 사용한다.


  다음 프로시저에서는 SoftICE와 관련한 것이 나온다. 먼저 CreateFileA()로 "\\.\SICE"를 오픈하고 다음으로 "\\.\NTICE"를 오픈한다. 만약 이 파일들이 존재한다면 즉, 반환 값이 -1이 되면 정상적으로 진행되는데, 아닌 경우에는 CloseHandle()과 RtlExitUserThread()를 수행하여 종료한다.


  다음 프로시저에서는 GetCurrentProcessId()를 호출하는데 이것도 안티디버깅에 사용되므로 처리가 필요하다. Step Into로 들어가서 FS:[18]을 통해 PID를 구하는 부분을 직접 올리디버거의 PID를 구해서 이 값을 넣는다. (작업관리자에서 올리디버거의 PID를 찾은 후 이 깂은 10진수이므로 계산기에서 이 값을 16진수로 변환해서 얻는다) 즉 다음과 같다.


  MOV EAX, DWORD PTR FS:[18}

  MOV EAX, DWORD PTR DS:[EAX+20]

  RETN


이것을


  MOV EAX,14AC ; 참고로 16진수로.

  NOP

  NOP

  NOP

  NOP

  RETN


으로 바꾼다. 이후에 CreateToolhelp32Snapshot(), Process32First(), Process32Next() 등의 여러 API들의 결합을 이용해서 모든 실행중인 프로세스들의 PID 번호를 구하고 현재 실행된 프로세스를 검색한다. 타겟 프로세스의 PID와 스스로의 PID를 비교한 다음 서로의 PID가 다르면 그 프로세스를 종료시킨다.


  지겨울 정도로 진행하다 보면 PE 헤더부터 복구화 루틴이 진행되는 것을 볼 수 있다. 마지막 즈음에 IsDebuggerPresent()를 통한 안티디버깅이 존재하는데 이것은 그냥 이 API 호출 후에 EAX를 0으로 바꿔주면 된다. 이후에도 PID 관련한 부분이 반복되고 CryptDestroyKey()와 CryptReleaseContext() 등을 호출한다. 다음으로는 실제 Import Table을 복구한다. 즉 원본 프로그램에서 사용하는 API들을 위한 Import Table이 복구된다.


  이제 GetTickCount()를 통한 안티 디버깅 기법이 나온다. 결과 값이 들어올 EAX의 값을 아래의 비교문에서 비교할 값보다는 적게 맞추어야 한다.


  이제 끝이 다가오면서 BlockInput()을 또 수행하고(이미 이 API 내부를 수정하였기 때문에 추가적으로 할 일은 없다) GetCurrentProcess(), SetPriorityClass(), SetWindowLongA()를 수행한다. 이후에도 이전에 나왔던 IsDebuggerPresent() 외에도 GetCurrentProcessId(), SoftIce 관련 안티디버깅 등이 나오고 어느 정도 진행되다가 핸들러의 주소로 OEP를 넣고 예외를 발생시킨다. 이제 핸들러로 가게 되면 그곳이 OEP가 된다.



4. MUP

  특이점으로는 MUP가 끝난 바이너리는 원본 파일과 같다는 점이다. 다른 상용 프로텍터들과는 달리 Stub을 지난 후에도 안티 디버깅 기법들이 삽입되어 있거나 수정되어 있지 않다는 점에서 일반적인 패커와 비슷한 느낌이 든다.

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

윈도우의 드라이버 개발과 루트킷 그리고 AV  (0) 2017.04.23
간단한 패커 개발  (0) 2017.04.23
패커들 분석  (5) 2017.04.23
윈도우의 자료형 정리  (1) 2017.04.23
Ollydbg [올리디버거] 2.01 매뉴얼  (1) 2017.04.23
Posted by SanseoLab



  패커와 관련된 대부분의 문서에서는 단지 MUP 방식만 간단하게 설명되어 있는것 같다. 물론 리버스 엔지니어링을 하는 입장에서는 대부분의 패커가 프로텍터처럼 안티 디버깅 방식을 사용하는 것도 아니고, 압축 알고리즘까지 공부할 필요는 없다고 생각하기 때문일 것으로 여겨진다. 하지만 압축 알고리즘은 그렇다고 하더라도 전체적인 흐름과 방식에 대해서는 기본적으로 알아야할 필요는 있다고 생각하였기에 이렇게 정리를 하려고 한다.


  본문은 약 11개의 유명 패커들 각각을 기준으로 설명하고 있다. 물론 패커라는 소프트웨어들은 전체적으로 동작하는 방식이 유사한 점이 많기 때문에 처음에는 자세히 설명되어 있지만 다음 패커로 넘어갈수록 내용은 줄어들 것이다. 또한 하나하나 세부적으로 설명하기도 어렵고 능력도 안되며 압축 알고리즘에 대해서는 관심조차 없기 때문에 그냥 정리된 문서라고 여기고 읽어보면 될 것이다.


  각 패커는 기본 정보 외에도 라이선스와 업데이트 상태 그리고 관련 정보나 다운로드를 받을 수 있는 사이트 주소가 정리되어 있다. 또한 패킹된 실행 파일에 대한 설명과 전체적인 알고리즘 그리고 결론적으로 MUP 방식에 대해서도 설명한다.


  압축 알고리즘에 대해서는 조금도 알지 못하지만 그래도 어떤게 있는지 정도는 정리해 보도록 하겠다. 가장 많이 사용되는 것으로서 aPlib이라는게 있는데 이것은 라이브러리의 이름이다. 자세히 보자면 LZ77 알고리즘 기반의 무손실 데이터 압축 라이브러리로서 프리웨어이며 상업용으로도 사용 가능해서 많은 패커들이 이 라이브러리를 사용해서 압축을 구현하고 있다. 이 외에 많이 사용되는 알고리즘으로는 LZMA 알고리즘이 있다. 그리고 UPX에서 사용되는 상용 알고리즘인 NRV와 이것을 오픈 소스로 구현한 UCL, MPRESS 패커에서 사용되는 LZMAT 같이 많이 사용되지 않는 알고리즘도 있고 상용 프로그램에서 만든 경우 방식을 공개하지 않은 여러 알고리즘이 존재할 것이다.


  기본적으로 패커의 동작 원리를 미리 설명하자면 다음과 같다. 먼저 레지스터 Context를 저장한다. 이를 위해 일반적으로 PUSHAD를 사용한다. 그리고 코드와 데이터 섹션을 디코딩하고 압축을 해제한다(디코딩 루프). 이후에는 사용할 라이브러리를 로드하고 그 라이브러리에서 사용되는 API들의 주소를 가져온다(임포트 테이블 복구). 이제 처음에 EP에서 저장시켰던 레지스터 Context를 복구한다. 일반적으로 POPAD를 사용한다. 마지막으로 실제 OEP로 이동하여 실행된다.




목록

1. UPX

2. Upack

3. ASPack

4. Petite

5. MEW

6. MPress

7. KKrunchy

8. RLPack Basic

9. FSG 1.33

10. FSG 2.0

11. nPack




1. UPX

라이선스 : GPL (소스 코드 존재)

상태 : 최신(version 3.91 - 2013.9)

주소 : https://upx.github.io/ (소스 코드 : https://github.com/upx/upx)


1.1 개요

  UPX는 Ultimate Packer for Executables의 약자로서 1998년도에 시작되어 최근까지도 업데이트가 이루어지고 있는 패커이다. 또한 GPL 라이선스를 가지고 있으며 소스 코드도 공개되어 있다. 또한 패킹 외에도 "-d" 옵션을 통해 언패킹도 수행할 수 있는 특징이 있다.

  UPX는 UCL이라고 불리는 알고리즘을 사용하는데 이것은 상용 알고리즘인 NRV(Not Really Vanished)를 오픈 소스로 구현한 것이다. 이 외에도 최신 버전에서는 LZMA(Lempei-Ziv-Markov chain-Algorithm) 알고리즘을 사용할 수 있다. 


  옵션은 여러 방식들이 존재하는 것으로 여겨지는데 --best, --brute, --ultra-brute 등이 있다. 하지만 기본적인 방식은 디폴트인 UCL 알고리즘과 LZMA 알고리즘 이렇게 두 가지로 여겨진다.



1.2 Packed PE

  패킹된 바이너리를 보면 기본 헤더는 바뀌지 않았다는 것을 알 수 있다. 하지만 섹션을 보면 크게 UPX0, UPX1, UPX2로 나뉘어 있는 것을 알 수 있다. 참고로 .rsrc 즉 리소스 섹션이 존재하는 경우에는 UPX2 섹션 대신 .rsrc 섹션이 그대로 있는 것을 볼 수 있다.


  UPX0 섹션의 RawDataSize을 보면 값이 0인데 이것은 파일에서의 크기가 0이라는 것을 의미한다. 하지만 VirtualSize의 값은 크게 나온 것을 알 수 있다. 사실 UPX는 실행되면서 UPX1에 있는 인코딩 즉 압축된 내용을 디코딩해서 UPX0 섹션에 써넣는다. 즉 디코딩 루틴이 끝난 후에는 이 섹션이 원래의 코드 섹션의 역할을 한다. 


  UPX1 섹션은 위에서 설명하였듯이 압축된 내용이 들어있다. 그리고 마지막 부분에는 디코딩 루틴이 존재한다. 즉 우리가 처음 실행하면 EP는 이 디코딩 루틴의 시작이다. 추후에 디코딩이 끝나고 OEP로 점프할 때 원래 패킹하기 전의 바이너리의 EP로 이동하게 된다. 


  UPX는 리소스 부분을 압축하지 않는다. 그렇기 때문에 .rsrc 섹션은 그대로 존재하는 것을 알 수 있다. 하지만 차이점이 존재하는데, 디코딩 루틴에서도 기본적으로 필요한 API들이 존재한다. 이 API들을 사용하기 위해서는 Import Table이 필요한데 이 Import Table이 .rsrc 섹션에 추가된다. 물론 .rsrc 섹션이 존재하지 않는 경우에는 UPX2 섹션이 만들어져서 이곳에 들어가게 된다. 


  그럼 원래의 Import table은 어디로 가는걸까. UPX가 암호화하는 섹션은 코드 섹션과 데이터 섹션이다. 이것은 .text, .data 뿐만 아니라 일반적으로 Import Table이 존재하는 섹션인 .rdata 섹션도 마찬가지이다. 그렇기 때문에 디코딩 루틴이 끝난 후에야 이 섹션이 복구된다.


  조금 더 자세히 알아보자면 UPX의 디코딩 루틴에서 기본적으로 사용하는 API들은 다음과 같다. LoadLibraryA(), GetProcAddress(), VirtualProtect(), VirtualAlloc(), VirtualFree(), ExitProcess()이 그것이다. LoadLibraryA()와 GetProcAddress(), VirtualProtect()는 알고리즘 부분에서 설명할 것이고, ExitProcess()는 디코딩 루프를 돌다가 잘못된 경우에 프로그램을 종료시킬 때 사용된다. 그리고 UCL이나 LZMA 알고리즘에서도 모두 VirtualAlloc(), VirtualFree()는 사용되지 않는 것으로 보이는데 확실치 않다.



1.3 알고리즘

  디코딩 루틴은 위에서 설명한 것과 같다. 하지만 조금은 더 자세히 설명해 보겠다. EP는 다음과 같이 시작한다.


PUSHAD

MOV ESI, 00410000 // 두 번째 섹션 시작 주소(UPX1)

LEA EDI, [ESI+크기] // 첫 번째 섹션 시작 주소(UPX0)


  당연히 알겠지만 PUSHAD로 시작한다. 그리고 자세히 보면 Source를 나타내는 ESI에는 두 번째 섹션의 시작 주소를 넣고, Destination을 나타내는 EDI에는 첫 번째 섹션의 주소를 넣는다. 위에서 설명하였듯이 두 번째 섹션의 내용을 디코딩하여 첫 번째 섹션에 넣는다는 것을 유추할 수 있다.


  이것을 지나 디코딩 루프가 나온다. 루프가 돌아가는 동안 디코딩이 계속된다. 몇몇 루프가 끝난 후에는 마지막으로 loop로 끝나는 루프가 나온다. 이 루프는 위의 디코딩 루프와는 약간 다르다. 이것은 CALL/JMP 명령어의 주소를 복원시켜주는 역할을 한다. 직접 살펴보면 이 루프를 돌기 전까지는 CALL/JMP 명령이 원래와 다르다가 이 루프를 지나서 복구된다는 것을 알 수 있다.


  이제 Import Table을 복구하는 루프가 나온다. 먼저 디코딩된 라이브러리의 이름을 가지고 LoadLibraryA()로 라이브러리를 로드하고 마찬가지로 디코딩된 API들의 이름을 가지고 GetProcAddress()로 API들의 주소를 구한 후에 Import Table에 저장한다. 이렇게 Import Table의 복구가 끝난다. 참고로 말해보자면 원래 바이너리를 보면 섹션별로 .text, .data, .rdata 이런 식으로 나누어져 있어서 올리디버거로 보면 .text 섹션만 CPU 패널에 보일 것이다. 하지만 UPX는 이 섹션들이 모두 통합이되서 UPX0, UPX1 섹션이 모두 올리디버거의 CPU 패널에 보인다. 그렇기 때문에 Import Table이 복구되는 과정도 좀 더 편하게 직접 지켜볼 수 있다. 


  사실 다른 패커들도 마찬가지이기 때문에 Import Table을 복구하는 것에 대해서는 조금 더 설명이 필요하다. 확실한 이유인지는 모르겠지만 실제 IAT에서 함수의 이름을 가지고 Import Table을 복구하는 어셈블리 루틴은 상당히 복잡한 것으로 생각된다. 그래서인지 대부분의 패커들은 패킹하기 전에 api들의 이름과 dll들의 이름 및 IAT 주소를 따로 정리해서 저장해 놓고 이것을 패킹한다. 언패킹 루틴은 순서로 정리된 api와 dll의 이름과 이것을 위치시킬 IAT 주소를 가지고 간단하게 Import Table을 복구할 수 있게 된다.


  이제는 VirtualProtect() API를 사용할 차례이다. 이것을 사용해서 헤더 부분을 쓰기 가능하게 만든 후에 수정하고 다시 쓰기 가능한 속성을 없앤다. 수정하는 내용은 UPX0, UPX1 섹션을 쓰기 가능에서 읽기로 바꾸는 것이다. 참고로 말하자면 원래 .text 섹션은 읽기만 가능한게 일반적이다. 코드 섹션에서 코드를 읽고 실행을 하지 수정할 일은 잘 없기 때문이다. 하지만 디코딩 루틴이 이 섹션에 디코딩된 내용을 써야하므로 이 섹션이 쓰기 가능이었던 것이다. 그것을 이제 다시 쓰기 불가능하게 만드는 과정이다.


  LZMA 알고리즘으로 압축한 경우에는 디코딩 루틴만 다를 뿐 이후에 Import Table을 복구한다는 것 같이 기본적으로 하는 일은 동일하기 때문에 진행하다 보면 충분히 구별할 수 있다.



1.4 MUP

  전형적인 방법으로서 PUSHAD 이후에 디코딩 루틴을 수행하고 임포트 테이블을 복구하고난 후에 마지막으로 POPAD 명령어를 실행하는데 이 바로 다음의 JMP 명령어를 실행하면 OEP로 들어간다. 아니면 H/W BP로 ESP에 BP를 거는 방법도 상관없다.





2. Upack

라이선스 : 프리

상태 : 2006년 (WinUpack v0.39)

주소 : http://www.softpedia.com/get/PORTABLE-SOFTWARE/Compression-Tools/Windows-Portable-Applications-Portable-WinUpack.shtml


2.1 개요

  UPack은 유명한 패커이기도 하고 그만큼 오래된 패커이기도 하다(최근 업데이트가 약 10여년 전이다). 이것은 수정된 LZMA 알고리즘을 사용하는 것으로 알려져 있다. 옵션은 다음과 같다. 


- Reserve extra data

- Strip export table

- Strip base relocation table

- Relocate base address to ..

- Preserve original file date and time



2.2 Packed PE

  다음은 [ revealing Packed malware ( Published by the ieee ComPuter soCiety

- ieee seCurity & PrivaCy) ]에서 참고한 내용이다. 

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

| PE header                     |

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

| OEP                             |            // Original code section

 ------------------------------ // EP   -------------------------------

| LZMA decompression      |

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

| E8/E9 decompression      |

 ------------------------------      // .Upack section

| Import table rebuilding    |

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

| OEP Jumping                 |

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

| Compressed data            |            // .rsrc section

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


  UPack은 수정된 LZMA 압축해제, E8/E9(JMP나 CALL) 압축해제, import table 복구, OEP 점프로 나뉜다. 그러나 UPack은 압축 성능에 영향을 미치지 않게 normal LZMA 디코더를 수정하기 위해 LZMA 파라미터를 바꾼다. Import Table 복구 단계에서 UPack은 DLL 이름들과 thunk table 및 API들의 주소를 extract한다. UPack은 재배치 데이터 블록들의 RVA를 저장해서 재배치 테이블이 재배치가 필요한 경우 재배치된다. 즉 간단히 설명해서 LZMA 압축 해제를 하고 CALL/JMP문 복구 그리고 Import Table을 복구한 후에 OEP로 점프한다. 


  패킹된 바이너리를 보면 헤더부터 깨져있어서 PEView로는 정보를 얻을 수 없다. 그리고 안티바이러스가 악성코드로 인식해서 그냥 삭제해 버린다. 그나마 정적 분석으로 얻을 수 있는 정보는 임포트하는 함수가 LoadLibraryA()와 GetProcAddress()라는것 정도이다.



2.3 알고리즘

  EP는 그냥 일반 섹션에서 시작되며 디코딩 루프가 시작된다. 실행하다 보면 JB로 끝나는 루프를 볼 수 있는데 이 루프가 LZMA 디코딩 루프라고 할 수 있다. 이 비교문을 자세히 보자면 CMP/JB 명령어를 통해서 EDI 값이 ESI+34에 있는 값이 될 때까지 계속 루프를 돈다. EDI는 쓸 섹션의 처음부터 시작하다가 끝까지 가는거고 끝 주소는 ESI+34에 들어있다는 것이다. 디코딩 루틴을 조금 더 자세히 보면 


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


그리고


STOS BYTE PTR ES:[EDI]


이런 명령이 있는데 이것은 압축을 해제한 후에 EDI 값이 가리키는 메모리에 쓰라는 것이다. 위에서 설명했듯이 EDI는 복구할 원래 섹션의 처음 위치의 주소부터 시작해서 끝 주소까지 점점 증가하는 값을 갖는다.


  그 다음에는 바로 LOOP 명령어를 이용한 루프가 나오는데 이것은 CALL/JMP 명령어를 복구하는 루프이다. 이후 LoadLibraryA()와 GetProcAddress()로 Import Table을 복구하고 RETN하면 OEP로 간다.



2.4 MUP

  가장 처음 부분에서 EAX를 PUSH할 때 그 EAX가 바로 OEP이다. 이 OEP에 HW BP를 걸고 실행시키면 된다.


MOV ESI, 주소

LODS DWORD PTR DS:[ESI] ; ESI가 가리키는 주소에서 4바이트를 읽어서 EAX에 넣으라는 의미.

PUSH EAX





3. ASPack

라이선스 : 상용

상태 : 최신(version 2.39 - 2016.3)

주소 : http://www.aspack.com/


3.1 개요

  상용 프로그램으로서 전형적인 패커이다. 매년 수 회씩 꾸준히 업데이트 중이며 가장 유명한 상용 패커일 것이다. 이 외에도 프로텍터로는 ASProtect가 있다. 어느 문서에서는 isDebuggerPresent() API와 INT3을 통한 안티 디버깅 기법이 존재한다고 하는데 직접 사용해본 버전은 평가판 버전이기 때문인지 안티 디버깅 기법이 적용되어 있지 않았다. 


  그리고 옵션을 살펴보면 리소스 섹션도 압축할지 여부와 Max 압축 여부 등을 선택할 수 있고 섹션 이름도 변경할 수 있다는 것을 알 수 있다. 리소스 섹션 압축은 기본으로 설정되어 있어서 사용했지만 평가판 버전에서는 Max 압축이 불가능했기 때문에 이것을 제외하고 사용하였다.



3.2 Packed PE

  패킹된 바이너리를 보면 섹션들은 그대로 존재하고 대신 .aspack과 .adata 섹션이 추가되어 있다. 물론 존재하던 섹션들은 당연히 압축되어 있고 .aspack 섹션은 디코딩 루틴과 새로운 Import Table이 들어가 있다. 사용되는 API들은 다음과 같다.


GetProcAddress(), GetModuleHandle(), LoadLibraryA()


  참고로 메모리에 올라왔을 때 올리디버거의 Memory map을 보면 .adata 섹션을 제외한 다른 섹션들은 하나로 여겨지는 것을 볼 수 있다. 그리고 원본 파일과 크기를 비교해보면 .aspack과 .adata 섹션은 전체 섹션 마지막에 덧붙여진 것을 알 수 있다. 어찌되었든 EP를 보면 마지막 섹션이 끝난 바로 뒤인 .aspack 섹션의 시작 주소가 디코딩 루틴이라는 것을 알 수 있다. Import Table은 디코딩 루틴의 마지막에 존재한다. 참고로 진행하다보면 알겠지만 API 이름들이 중간중간에 껴있어서 진행하면서 몇몇 API들을 미리 임포트하기도 한다.



3.3 알고리즘

  시작부터 디버거로 분석하기 귀찮게 만드는 CALL/JMP를 통한 트릭이 사용된다. 이것은 안티 디스어셈블리 방식으로서 디스어셈블러가 명령어들을 제대로 해석하지 못하게 만들어 준다. 올리디버거의 경우 Ctrl + A 또는 "Analyse Code"를 이용하여 어느 정도 해석할 수 있다. 물론 간편하고 확실하게 되지는 않지만 이 부분이 그렇게 심하지 않으므로 감당할 정도는 된다. 정확한 이름은 존재하지 않는 것 같고 일반적으로 영어권에서 "Jumping into the middle of an instruction" 방식으로 불리는 것 같다. 우리는 PUSHAD로 시작한 것을 알았으니 POPAD로 복구할 수 있겠다는 예상을 할 수 있다. 차근차근 분석해 보면 GetModuleHandleA() 함수로 KERNEL32.dll의 핸들을 얻어오고 여기서 GetProcAddress()로 VirtualAlloc(), VirtualFree(), VirtualProtect()를 임포트한다. 


  ASPack에서는 UPX와는 다르게 압축 해제를 위해서 메모리 할당이 필요하다. VirtualAlloc()으로 메모리를 할당하고 디코딩하고 VirtualFree()로 할당된 메모리를 해제하는 것을 반복한다. 직접 확인해보고 싶다면 Ollydbg의 Memory map 패널을 켜놓고 VirtualAlloc()을 실행하면 새로운 영역이 생기는 것이 눈에 띌 것이다. 


  이제 다른 패커들과 같이 GetProcAddress()와 GetModuleHandle()로 Import Table을 복구한다. 그리고 VirtualProtect()로 헤더 속성을 변경하고 섹션들의 속성도 변경한다. 예를들어서 .text 섹션의 write 속성을 제거하고 read, execute만 남긴다던지 같이. 마지막으로 복귀할 OEP의 주소를 PUSH하고 RETN함으로써 OEP로 점프한다.



3.4 MUP

  POPAD 찾아서 그다음 부근에서 RETN 명령어를 실행한다. 그러면 OEP로 가게 되는데 참고로 Ctrl+A로 분석해야 제대로된 코드를 볼 수 있다.





4. Petite

라이선스 : 프리웨어

상태 : 최신(version 2.4 - 2015.10)

주소 : http://www.un4seen.com/petite/


4.1 개요

  Petite는 과거 수 년 동안 업데이트가 되지 않았지만 어떤 이유에선지 최근에 2.4로 업데이트가 되었다. 프리웨어이지만 압축 알고리즘 조차 알려져 있지 않다. 옵션을 보면 압축 수준을 1부터 9까지 정할 수 있고 "Virus detection" 기능을 사용하면 로드 시에 자체 검사하여 메시지를 보여준다. 이 외에도 "Strip debug info", "Compress exports", "Compress exports and EXE table", "Mangle imports", "Strip relocations" 등의 옵션도 존재한다.



4.2 Packed PE

  패킹된 바이너리를 보면 이름없는 섹션과 .rsrc 섹션, .petite 섹션이 존재한다. 이것은 .text, .rdata, .data, .idata 같은 섹션들이 압축되어 이름 없는 섹션에 하나로 통합되어 있고 리소스 섹션은 건드리지 않으며, Import Table은 .petite 섹션에 존재한다는 것을 보여준다.


  다른 패커보다는 임포트하는 함수들이 많은데 기본적으로 kernel32.dll에서는


ExitProcess(), GetModuleHandleA(), GetProcAddress(), VirtualProtect(), VirtualAlloc(), VirtualFree(), LoadLibraryA()


를 임포트하고, user32.dll에서는 다음을 임포트한다.


MessageBoxA(), wsprintfA()



4.3 알고리즘

  상당히 특이하다. 먼저 EP는 임포트 섹션인 .petite 섹션의 주소를 EAX에 넣고 PUSHAD 함으로써 시작한다. 그리고 VirtualAlloc()으로 메모리를 할당하고 현 프로시저의 바로 아래에 위치한 디코딩 루프를 호출한다. 이 디코딩 루프는 할당된 영역에 진짜 디코딩 루틴을 쓰기 위한 디코딩 루틴이다. 이것이 반환되면 RETN하게 되는데 이 때 스택에 아까 할당했던 주소가 들어가 있어서 EIP가 할당한 메모리로 이동되며 다른(실제) 디코딩 루프가 수행된다.


  이 디코딩 루프는 원래의 각 섹션 별로 VirtualProtect()로 쓰기 속성을 추가한 후에 디코딩을 수행하고 디코딩 된 내용을 써 넣는다. 그리고 디코딩 루프가 끝나면 일반적인 패커와 같이 Import Table을 복구한다. 이 때 사용되는 API들도 일반적인 GetModuleHandleA()(또는 LoadLibraryA())와 GetProcAddress()이다. 그리고 VirtualProtect()로 쓰기 속성을 제거한 후에 POPAD를 하고 다음 명령어를 수행한다.


00460308   FF60 20     JMP DWORD PTR DS:[EAX+20]           ; KERNEL32.VirtualFree


  참고로 이 때 인자로 4개를 스택에 넣는데 마지막에 넣는 인자(EDX)가 OEP이다. 그래서 특이하게도 (JMP 명령어이므로) 이 API의 내부로 들어가게 되고 내부에서 일이 다 끝나서 retn을 수행하면 아까 넣었던 마지막 인자(OEP)로 인해 OEP로 리턴하게 된다.



4.4 MUP

  할당된 영역(실제 디코딩 루틴)에서 +308이 VirtualFree()를 호출하는 JMP이다. 그러므로 EP가 존재하는 프로시저의 마지막 RET으로 간 후에 Step Into하면 할당된 영역으로 가는데 그 주소보다 +308인 곳에 JMP 명령어가 있다. 이 명령어를 실행하기 전에 인자를 보면 마지막 인자가 OEP이다. 즉 EDX에 들어가 있는 인자를 말한다.





5. MEW

라이선스 : 프리웨어

상태 : 2004년 (MEW11 SE v1.2)

주소 : http://www.softpedia.com/get/Programming/Packers-Crypters-Protectors/MEW-SE.shtml


5.1 개요

  MEW는 어느 정도 알려진 패커 중 하나이지만 그만큼 오래되엇기도 하다. 이것은 LZMA와 aPlib을 기반으로 만들어졌다. 사실 이건 정확히는 파악할 수 없었던게 옵션 중에서 "use LZMA algorithm too"와 "Special LZMA (E8 E9)"가 있고 이 중 첫 번째는 디폴트로 체크되어 있기 때문이다. 참고로 여기서 분석은 디폴트 옵션으로 하였다. 이 외에도 둘 중 아무것도 선택하지 않을 수도 있고 둘 다 선택할 수도 있다. 참고로 후자만 선택하는 것은 불가능하고 전자가 선택되어야 후자도 선택할 수 있다. 


  다음은 제공하는 특징이다. 


- TLS support

- strip reloc tables

- strip Delphi resources

- strip unused resources

- no antivirus warning (normal header)

- imports handeling but work with no-import files too

- aPPack & LZMA compression

- overlays support(Flash,Multimedia Builder)

- special windows gui

- command line support

  그리고 다음은 알려진 버그들이다.

- no .NET support (work in process)

- no export and delay import support

- no DLL support (work in process)

- no reloc handle support (just strip)

- no 64 bit support

  참고로 위에서 aPPack은 aPLib을 이용한 패커이다.



5.2 Packed PE

  패킹된 바이너리를 보면 섹션이 2개 존재한다는 것을 알 수 있다. 첫 번쨰 섹션은 MEW 섹션인데 이것은 크기가 0이지만 메모리에 올라오면 큰 크기를 갖는데 일종의 UPX0 섹션과 같다고 여기면 된다. 또 하나의 섹션은 이름이 깨져 있는데 여기에 인코딩된 코드와 데이터, 그리고 Import Table까지 들어가 있다. 참고로 Import Table은 마지막 부분에 붙어있다.


  올리디버거에서 메모리에 올라간 것을 보면 앞 부분은 0으로 채워진 MEW 섹션이 차지하고 있고 뒷부분에는 인코딩된 데이터들, 그뒤에 바로 Import Table이 있는 것을 볼 수 있는데 특이한 점은 이 Import Table 바로 다음에 EP가 존재한다는 것이다. 이 EP는 단지 JMP문(헤더 부분으로 가는) 한 개이다.


  Import Table을 보면 임포트 하는 함수는 LoadLibraryA()와 GetProcAddress() 두 개이다. 



5.3 알고리즘

  위에서도 설명했지만 EP에는 JMP문이 있는데 신기하게도 헤더 영역으로 점프한다. 간단한 예를 들면 보통 코드 섹션이 00401000에 시작하는 일반적인 프로그램일 경우 헤더 부분은 00400000에서 시작한다. MEW로 패킹된 바이너리는 기본 헤더들 바로 뒤에 디코딩 루틴이 붙어있는 것이다. 참고로 Ollydbg 2의 경우 "Analysis Code"를 해제하여야 코드를 볼 수 있다.


  이 부분을 수행하다 보면 헤더 부분까지 가서 몇몇 명령어를 실행하는 것을 볼 수 있는데 헤더에서 쓸모없는 작은 부분까지도 명령어를 저장할 위치로 사용할 정도로 개발자가 프로그램의 크기를 줄이는데 큰 관심을 보인 것을 알 수 있다. 디코딩을 수행하다 보면 깨진 이름의 섹션으로 이동해서 디코딩을 수행하기도 하는데 마지막에는 Import Section을 복구하고 RETN을 하면 OEP로 이동한다.


  옵션이 추가될 때마다 용량은 더 줄어들지만 알고리즘은 옵션들에 따라 큰 차이는 나지 않는 것으로 보인다.



5.4 MUP

  헤더 영역으로 JMP해서 명령어 몇 개 실행하다 보면 PUSH EAX를 수행하는데 여기에 들어있는 값이 OEP이다. 또는 헤더 영역으로 점프했을 때의 그 프로시저의 가장 마지막에 RETN을 수행하면 OEP로 이동할 수 있다.





6. MPress

라이선스 : 프리웨어

상태 : 2012년 (v2.19)

주소 : https://autohotkey.com/mpress/mpress_web.htm


6.1 개요

  MPress는그래도 윈도우 7까지는 지원하는 나름대로 최신 패커 중 하나이다. 이것은 LZMAT라는 알고리즘을 사용한다. 이것은 LZ77 알고리즘과 매우 비슷하지만 몇몇 이점이 있다고 하는데 Custom-made라서 문서화가 되어있지 않다고 한다. 어떤 문서에 따르면 작은 파일에는 LZMAT를, 큰 파일에는 LZMA를 사용한다고 하며 이것들의 차이는 EP를 보면 구별할 수 있다고 한다. 옵션을 보면 "force to use LZMAT"라는 옵션이 존재한다.



6.2 Packed PE

  섹션을 보면 .MPRESS1와 .MPRESS2가 있으며 리소스 섹션이 존재할 시에는 .rsrc 섹션도 그대로 존재한다. .MPRESS1 섹션에는 인코딩된 데이터가 존재하며, .MPRESS2 섹션에는 Import Table과 바로 뒤에 디코딩 루틴이 존재한다. 임포트하는 함수는 GetModuleHandleA(), GetProcAddress() 두 가지이다. 



6.3 알고리즘

  EP는 .MPRESS2 섹션의 Import Table 바로 뒤에서 시작하며 첫 명령어는 PUSHAD이다. 그냥 분석해대던 패커들과는 달리 CALL 명령어를 사용해서 디버깅하기 은근히 귀찮고 까다롭게 만들어 놓았다. 


  조금 자세히 말하자면 초반 부분에 나오는 큰 루프는 그냥 Step Over하면 된다. 이 부분은 언패킹 루틴으로써 상당히 크다. 이 부분을 넘어서 진행하다 보면 윗 부분으로 멀리 JMP하는 부분을 볼 수 있다. 여기서부터는 VirtualProtect()를 임포트하고 헤더 영역을 수정하는데 .MPRESS1과 .MPRESS2 섹션에 쓰기 가능 속성을 제거한다. 또한 이후 Import Table을 복구하고 POPAD를 한 후에 JMP 명령어로 OEP로 이동한다. 


  생각보다는 간단하게 분석할 수 있지만 처음부터 Ollydbg 2의 분석이 먹히는 것도 아니고(후반부에는 Analyse Code가 통해서 조금 더 보기 편해진다) CALL을 이용한 "Jumping into the middle of an instruction" 기법도 있고 해서 은근히 불편하다. 하지만 디코딩 루틴만 건너 뛴다면 그다지 난이도 높은 기술은 필요 없다.


  LZMAT 옵션을 사용해서 패킹한 것도 그다지 눈에 띄는 차이점은 없는것으로 보인다.



6.4 MUP

PUSHAD한 위치에 H/W BP를 걸면 된다.





7. Kkrunchy

라이선스 : BSD (소스 공개)

상태 : 2006년 (v0.23a, v0.23a2)

주소 : http://www.farbrausch.de/~fg/kkrunchy/, 소스 : https://github.com/farbrausch/fr_public

기타 정보 : https://fgiesen.wordpress.com/2011/01/24/x86-code-compression-in-kkrunchy/http://www.pouet.net/prod.php?which=26088


7.1 개요

  kkrunchy는 현재 두 버전이 있는데 v0.23a와 v0.23a2가 그것이다. 알파 버전은 매우 기본적인 LZ + arithmetic 알고리즘(LZMA가 아니다)을 사용한다. 이것은 압축이 느리지만 더 빨리 압축 해제를 수행한다. 알파2 버전은 PAQ, crinkler와 비슷한 context mixing 기반 알고리즘이다. 이것은 알파 버전보다는 압축이 빠르지만 압축 해제는 좀 더 느리다. 옵션을 보면 디폴트는 good packer frontend를 사용하는 것이고 best packer frontend를 사용할 수도 있다. 이것은 더 느리다고 한다. 



7.2 Packed PE

  기본적으로 PEview에서 잘 인식하지 못하지만 리소스 섹션을 포함해서 모든 섹션들이 따로 구분되어 있지는 않다. 임포트하는 API들은 LoadLibraryA()와 GetProcAddress()이다.



7.3 알고리즘

  먼저 알파 버전부터 설명한다. EP는 전체 섹션의 가장 처음 부분이다. 처음 프로시저는 먼저 특정 영역에 디코딩 루틴을 생성한다. 진행하다 보면 이 루틴이 끝나기 전에 LoadLibraryA()와 GetProcAddress()로 Import Table을 복구한다. 이 처음 프로시저가 RETN하면 지금까지 썼던 영역으로 이동한다. 이곳에서 디코딩 루프를 진행하다 보면 (사실 직접 살펴봐도 될 정도로 거의 바로 아랫 부분에 존재한다) 루프들 중에서 아래와 같은 명령어가 있을 것이다.


005F0A29   > \3B75 00           CMP ESI, DWORD PTR SS:[EBP]

005F0A2C   .  0F84 07CB0100    JE 0060D539


저 JE의 주소값이 OEP이다.


  알파2 버전은 처음 부분이 상당히 다르다. EP부터 굉장히 큰 프로시저가 존재한다. 그렇기 때문에 올리디버거2의 Analyse Code를 수행하고 이후 명령어들을 살펴보다 보면 MOV EBX, <&KERNEL32.LoadLibraryA> 부분이 있고 아래에 CALL DWORD PTR DS:[EBX] 부분이 있는걸로 봐서 여기가 디코딩 루틴 수행 후에 실행되는 임포트 테이블 복구 루틴인 것을 알 수 있다. 이 부분을 실행하면서 분석하다 보면 마지막에 POP EBP와 RETN이 나온다. 이제 알파 버전과 같이 생성된 디코딩 루틴으로 가게 되고 여기서도 똑같이 JE 부분을 보면 알 수 있다.



7.4 MUP

  처음 디코딩 루틴을 생성하는 부분에서 복잡한 부분이 존재하는데 LoadLibraryA()와 GetProcAddress()를 호출할 만한 부분을 찾아서 BP를 걸어야 한다. 이후 Import Table 복구 루프를 지나 ret을 하고 이후 부터는 쉽게 찾을 수 있다.





8. RLPack Basic

라이선스 : GPL (소스 공개)

상태 : 2008 (v1.21)

주소 : http://www.softpedia.com/get/Programming/Packers-Crypters-Protectors/RLPack-Basic-Edition.shtml


8.1 개요

  패커를 다운로드 받으면 소스 코드도 같이 들어있다. 알고리즘은 패킹할 때 선택할 수 있는데 aPlib 0.43과 LZMA 4.30이 있다. 옵션을 보면 이 외에도 "Strip TLS", "Strip relocations", "Strip export table", "Don't strip unimportant resources", "Use Windows DLL loader", "Preserve overlay" 등이 존재한다. 특징으로는 Upack처럼 V3가 바로 제거해 버린다.



8.2 Packed PE

  패킹된 바이너리를 보면 .packed 섹션과 .RLPack 섹션으로 나뉜다. .packed 섹션은 크기가 0인 UPX0 섹션과 같은 형태이고 .packed 섹션에는 리소스와 Import Table이 존재한다. 임포트하는 API들은 다음과 같다. 이 중에서 VirtualProtect()는 사용되지 않는 것으로 보이는데 확실치 않다.


LoadLibraryA(), GetProcAddress(), VirtualAlloc(), VirtualFree(), VirtualProtect()


  기본적인 형태는 aPlib 0.43과 LZMA 4.30 모두 동일하다.



8.3 알고리즘

  aPlib 0.43 알고리즘부터 보겠다. EP는 PUSHAD로 시작한다. 이후 .RLPack 섹션의 내용을 디코딩하여 .packed 섹션으로 풀어 쓴다. 그리고 VirtualAlloc()으로 메모리를 할당하고 이곳에 Import Table의 인코딩된 버전을 쓴다. 이후 할당된 메모리에서 라이브러리의 이름을 읽어와(라이브러리 이름은 인코딩되어 있지 않고 각 API 이름만 인코딩되어 있다) LoadLibraryA()로 로드한 후에 인자가 2개인 함수(이 함수 내부에서 GetProcAddress()를 호출하며 인자로는 받는 값은 할당된 메모리의 값으로서 인코딩된 API 이름이다)를 이용해 Import Table을 복구한다. VritualFree()로 할당된 메모리를 해제한다. 이후 POPAD 후 JMP하면 OEP로 이동한다.


  LZMA 4.30 알고리즘은 aPlib 0.43 알고리즘과 디코딩 루틴에서만 차이가 있다. 즉 PUSHAD로 시작한다는 것이나 Import Table을 복구하는 것 부터는 모두 동일하다. 디코딩 루틴은 VirtualAlloc()를 통한 할당된 메모리를 사용한다는 점에서 차이가 있다.


  디코딩 루틴을 호출하는 부분이 CALL로 구별하기 쉽게 보이고 기본적인 프로시저가 깔끔하므로 디코딩 루틴을 깊게 들어가지 않는 이상 간단하게 언패킹 과정을 분석할 수 있다.



8.4 MUP

  전형적인 PUSHAD와 POPAD를 이용한 방식을 사용하면 된다.





9. FSG 1.33

라이선스 : 프리웨어

상태 : X

주소 : http://freestyler03.uw.hu/tools.html (공식 사이트는 아니지만 유일하게 찾은 링크이다. 압축을 풀면 이상한 파일들도 존재하는데 설치하지 말고 안에 있는 폴더에 들어가면 독립된 실행 파일이 존재한다.)


9.1 개요

  FSG는 2002년 1월 14일 dulek와 bart가 만들었으며 2002년 11월 15일 발표된 것이 1.33버전이다. FSG는 aPLib을 사용하는 것으로 알려져 있다. 특이사항은 최근 VC++로 컴파일한 실행 파일들에는 제대로 동작하지 않는다는 점이 있다. 따로 사용할 수 있는 옵션은 없다.



9.2 Packed PE

  특별한 이름의 섹션을 갖지는 않고 PEview로 보면 그냥 SECTION 안에 인코딩된 부분과 Import Table이 존재하는 것을 알 수 있다. 참고로 EP 즉 복호화 루틴은 저 통합된 섹션의 끝 부분 쯤에 존재한다. 임포트하는 함수는 평범하게 LoadLibraryA(), GetProcAddress()가 있다.



9.3 알고리즘

  JMP로 끝나는 몇 겹의 복호화 루틴이 존재하는데 다른 것들과 비교해서 상당히 구분하기 쉬운 편이다. 기본적인 방식은 ESI의 주소에 있는 인코딩된 내용을 디코딩해서 EDI의 주소에 쓰는 것이다. Import Table 복구 루틴도 똑같이 매우 간단하데 복호화 루틴의 거의 바로 뒤에 나온다.



9.4 MUP

  특이하게 이것은 OEP로 가는 명령어가 하드코딩되어 있다. Import Table 복구 루틴 중간 부분에 JZ 명령어로 존재하며(복호화 루틴 내의 주소가 아니라서 쉽게 구분할 수 있다) 복구를 마친 후에 바로 이동하게 된다. 이 명령어가 뛰는 곳에 BP를 걸면 된다.





10. FSG 2.0

라이선스 : 프리웨어

상태 : X

주소 : http://freestyler03.uw.hu/tools.html (공식 사이트는 아니지만 유일하게 찾은 링크이다. 압축을 풀면 이상한 파일들도 존재하는데 설치하지 말고 안에 있는 폴더에 들어가면 독립된 실행 파일이 존재한다.)


10.1 개요

  FSG 2.0은 2004년 5월 24일 bart가 발표한 버전이다. 이것도 마찬가지로 최근 버전의 VC++에서 만든 실행 파일에는 제대로 동작하지 않는다. 이것도 마찬가지로 따로 사용할 수 있는 옵션은 없다.



10.2 Packed PE

  FSG 1.33 버전과 같다. 임포트하는 함수들도 같다.



10.3 알고리즘

  1.33 버전과 달리 섹션 헤더 부분에 EP 즉 복호화 루틴이 존재한다. 예를들면 나의 경우 EP는 00400154이다. 복호화 루틴은 1.33 버전보다 더 간단해 보인다. 기본적으로 ESI와 EDI를 이용한 방식이다. 또한 JMP 명령어로 끝나는 몇 겹의 루프문으로 둘러싸여 있다. 이후에 거의 바로 Import Table 복구 루틴이 존재한다.



10.4 MUP

  이것도 Import Table 복구 루틴 안에 OEP로 가는 분기문이 존재한다. 나의 경우는 다음과 같다.


004001CD JS SHORT 004001C2

004001CF JNE SHORT 004001D4

004001D1 JMP DWORD PTR DS:[EBX+0C]

004001D4 PUSH EAX


  이런 식으로 되어 있어서 저 JNE가 PUSH EAX 명령어로 뛰지 않고 JMP 명령어가 실행될 때 가는 곳이 OEP이다. 그러므로 그냥 간단하게 저 JMP 명령어에 BP를 걸면 된다.





11. nPack

카피라이트 : Underground InformatioN Center (url : uinc.ru)

상태 : 2008.3.3 (v1.1.800.2008)

주소 : 소스코드 구입(http://www.shareit.com/product.html?productid=300161907)



11.1 개요

  NEOx(neox@petools.org.ru)가 만들었으며 소스 코드는 위의 주소에서 부가세 포함 11달러에 구매할 수 있다. 옵션에 나온 기능을 제외하고 기본적인 특징은 다음과 같다. 제공되는 자료에 따르면 윈도우 비스타 x32까지 지원된다고 한다.


- exe, dll, ecx 등 모든 종류의 PE 파일 지원

- 코드, 데이터, 리소스 압축

- 섹션 이름 짓기 제공

- 빠른 복호화 루틴

- Relocation 지원

- TLS 지원

- Strip debug information

  지원하는 옵션은 다음과 같다.

- Compress resources

- Create backup copy(.bak file)

- Strip Relocations

- Rebuild file

- Save overlay (for installers)

- Skip Shared Sections



11.2 Packed PE

  PE를 보면 기본 섹션들은 그대로 있는데 안의 내용은 거의 지워져 있다. 추가적으로 디폴트 옵션인 경우 .nPack 섹션이 생성되며 여기에 인코딩된 내용들과 Import Table, 그리고 복호화 루틴이 있다. 즉 EP도 .nPack 섹션에 존재한다. 임포트하는 함수는 다음과 같다.


kernel32.dll : LoadLibraryA(), ExitProcess(), GetProcAddress()

user32.dll : wsprintfA(), MessageBoxA()


  최신 VC++로 컴파일한 실행 파일에는 제대로 동작하지 않는다.



11.3 알고리즘

  Ctrl+A로 분석해보면 쉽게 구조를 파악할 수 있다. 현재 EP가 존재하는 프로시저를 보면 마지막에 2개의 RETN이 존재하는 것을 볼 수 있다. 이 중에서 첫 RETN이 실행되면 OEP로 이동한다. 이 사이에는 약 6개의 CALL문이 존재한다.


  첫 호출문은 LoadLibraryA()로 kernel32.dll을 로드하고 GetProcAddress()로 VirtualAlloc(), VirtualFree()의 주소를 얻어온다. 두번째 호출문에서는 VirtualAlloc()으로 메모리를 할당하고 복호화 루틴을 수행하며 마지막으로 할당한 메모리를 VirtualFree()로 회수한다. Import Table은 4번째 호출문에서 복구된다.



11.4 MUP

  첫 프로시저의 끝 부분에 존재하는 2개의 RETN 중에서 첫번째 RETN을 실행하면 OEP로 이동한다.



Posted by SanseoLab


PDF 문서 다운로드 링크

윈도우의 자료형 정리.pdf




0. 개요

1. void 계열

2. 정수 계열

3. 부동 소수점 계열

4. 기타

5. 정리





0. 개요


B를 A로

typedef B A

#define A B


윈도우는 32비트는 ILP32, 64비트는 LLP64

리눅스는 32비트는 ILP32, 64비트는 LP64


즉, 윈도우의 경우 32비트와 64비트의 차이는 포인터 뿐이다.


ILP32 LLP64 LP64

char 8 8 8

short 16 16 16

int 32 32 32

long 32 32 64

long long 64 64 64

pointer 32 64 64


_WIN64 ,  not _WIN64

UNICODE    ,  not UNICODE

_M_IX86      ,  not _M_IX86





1. void 계열

1.1 void

--

void  -> VOID

void * -> LPVOID, PVOID

PVOID -> HANDLE

        HANDLE -> HINSTANCE, HKEY 등의 핸들

   HKEY * -> PHKEY

   HINSTANCE -> HMODULE

   HICON -> HCURSOR

LPVOID -> SC_LOCK

CONST void * -> LPCVOID





2. 정수 계열

2.1 [ true, false ]

1바이트 : bool

--

bool : x



2.2 [ -128 ~ 127 ]

1바이트 : __int8, char, signed char

--

__int8 : x

char -> CHAR, CCHAR

CHAR * -> LPSTR, PCHAR, PSTR

   LPSTR ->   PTSTR, LPTSTR

CONST CHAR *       -> PCSTR

char -> TCHAR

TCHAR * -> PTCHAR

__nullterminated CONST CHAR *  -> LPCSTR

LPCSTR -> PCTSTR, LPCTSTR

signed char -> INT8

INT8 * -> PINT8



2.3 [ 0 ~ 255 ]

1바이트 : unsigned __int8, unsigned char

--

unsigned char -> BYTE, UCHAR, UINT8

BYTE         -> BOOLEAN

   BOOLEAN * -> PBOOLEAN

BYTE * -> PBYTE

BYTE far * -> LPBYTE

UCHAR * -> PUCHAR

UINT8 * -> PUINT8

unsigned char -> TBYTE

   TBYTE * -> PTBYTE



2.4 [ –32,768 ~ 32,767 ]

2바이트 : __int16, short, short int, signed short int

--

short -> SHORT

SHORT * -> PSHORT

short -> HALF_PTR

HALF_PTR * -> PHALF_PTR

signed short -> INT16

   INT16 * -> PINT16



2.5 [ 0 ~ 65,535 ]

2바이트 : unsigned __int16, unsigned short, unsigned short int

--

unsigned short         -> UINT16, USHORT, WORD

   UINT16 *         -> PUINT16

   USHORT * -> PUSHORT

   WORD -> ATOM, LANGID

   WORD *         -> LPWORD, PWORD

unsigned short -> UHALF_PTR

   UHALF_PTR * -> PUHALF_PTR


2바이트 : wchar_t, __wchar_t  [0 ~ 65,535]

--

wchar_t -> WCHAR

   WCHAR -> TBYTE, TCHAR

   TBYTE * -> PTBYTE

   TCHAR *         -> PTCHAR

   WCHAR *         ->  LPWSTR, PWCHAR, PWSTR

   LPWSTR         ->  PTSTR, LPTSTR

CONST WCHAR *       ->     LPCWSTR, PCWSTR

 LPCWSTR       ->      PCTSTR, LPCTSTR



2.6 [ –2,147,483,648 ~ 2,147,483,647 ]

4바이트 : __int32, int, signed int, long, long int, signed long int

--

int -> BOOL, HFILE, INT

BOOL * -> PBOOL

BOOL far *         -> LPBOOL

int -> INT_PTR

INT_PTR *         -> PINT_PTR

int -> HALF_PTR

HALF_PTR *         -> PHALF_PTR

int * -> LPINT, PINT

signed int -> INT32, LONG32

INT32 * -> PINT32

LONG32 *         -> PLONG32

long -> LONG     ->     HRESULT

LONG *     ->     PLONG

long -> LONG_PTR     ->     SSIZE_T, LPARAM, LRESULT

    SSIZE_T * -> PSSIZE_T

LONG_PTR *    ->     PLONG_PTR

long * -> LPLONG



2.7 [ 0 ~ 4,294,967,295 ]

4바이트 : unsigned __int32, unsigned int, unsigned int, unsigned long, unsigned long int

--

unsigned int -> DWORD32, UINT, UINT32, ULONG32

   DWORD32 * -> PDWORD32

   UINT * -> PUINT

   UINT32 *         -> PUINT32

   ULONG32 *      -> PULONG32

unsigned int ->  UINT_PTR       ->     WPARAM

   UINT_PTR * -> PUINT_PTR

unsigned int ->  UHALF_PTR

   UHALF_PTR * -> PUHALF_PTR

unsigned long ->  DWORD, ULONG

   DWORD         -> COLORREF, LCID, LCTYPE, LGRPID

   DWORD * -> LPCOLORREF, LPDWORD, PDWORD

   PDWORD     ->     PLCID

   ULONG *         -> PULONG

unsigned long ->  ULONG_PTR     ->     SIZE_T, DWORD_PTR

SIZE_T * -> PSIZE_T

DWORD_PTR * ->    PDWORD_PTR

 ULONG_PTR *   -> PULONG_PTR



2.8 [ –9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 ]

8바이트 : __int64, long long, signed long long

--

__int64 -> LONG64

   LONG64 * : PLONG64

__int64 -> LONG_PTR, INT_PTR

   LONG_PTR -> SSIZE_T, LPARAM, LRESULT

   SSIZE_T *     ->     PSSIZE_T

   LONG_PTR * -> PLONG_PTR

   INT_PTR * -> PINT_PTR

__int64 -> LONGLONG       ->     USN

   LONGLONG * -> PLONGLONG

signed __int64 -> INT64

   INT64 * -> PINT64



2.9 [ 0 ~ 18,446,744,073,709,551,615 ]

8바이트 : unsigned __int64, unsigned long long, long long, unsigned long long

--

unsigned __int64 -> DWORD64, DWORDLONG, QWORD, ULONG64, UINT64

DWORDLONG * -> PDWORDLONG

ULONG64 * -> PULONG64

DWORD64 * -> PDWORD64

UINT64 * -> PUINT64

unsigned __int64 -> ULONG_PTR, UINT_PTR

ULONG_PTR ->    SIZE_T, DWORD_PTR

  SIZE_T *        ->    PSIZE_T

 DWORD_PTR *   ->   PDWORD_PTR

ULONG_PTR * PULONG_PTR

UINT_PTR ->  WPARAM

UINT_PTR * -> PUINT_PTR

unsigned __int64 -> ULONGLONG

ULONGLONG * -> PULONGLONG





3. 부동 소수점 계열

3.1 4바이트  float

--

float -> FLOAT

FLOAT * -> PFLOAT


3.2 8바이트  double, long double

--

double -> LONGLONG, ULONGLONG

LONGLONG         -> USN

LONGLONG * -> PLONGLONG

ULONGLONG * -> PULONGLONG





4. 기타

__stdcall -> CALLBACK, WINAPI

WINAPI -> APIENTRY

const -> CONST

__sptr -> POINTER_SIGNED 

__uptr -> POINTER_UNSIGNED

if(WINVER >= 0x0500) HANDLE -> HMONITOR


__ptr32 : 32비트 시스템의 네이티브 포인터

__ptr64 : 64비트 시스템의 네이티브 포인터

__sptr

__uptr


typedef struct _UNICODE_STRING {

  USHORT  Length;

  USHORT  MaximumLength;

  PWSTR  Buffer;

} UNICODE_STRING;

UNICODE_STRING * -> PUNICODE_STRING;

const UNICODE_STRING * -> PCUNICODE_STRING;


#if defined(_WIN64)

 #define POINTER_32 __ptr32

#else

 #define POINTER_32

#endif


#if (_MSC_VER >= 1300)

 #define POINTER_64 __ptr64

#else

 #define POINTER_64

#endif





5. 정리

5.1 WIN64

HALF_PTR(PHALF_PTR) - short

                              - int

UHALF_PTR(PUHALF_PTR) - unsigned short

                                  - unsigned int

INT_PTR(PINT_PTR) - int

                          - __int64

WPARAM - UINT_PTR(PUINT_PTR) - unsigned int

                                             - unsigned __int64

SSIZE_T(PSSIZE_T), LPARAM, LRESULT   -   LONG_PTR(PLONG_PTR)   -   long

                                                                                          - __int64

SIZE_T(PSIZE_T), DWORD_PTR(PDWORD_PTR) - ULONG_PTR(PULONG_PTR) 

    - unsigned long

                                                                                    - unsigned __int64




5.2 유니코드

PTSTR, LPTSTR      - LPSTR       - CHAR *                                      - char *

                         - LPWSTR    - WCHAR *                                    - wchar_t *

PCTSTR, LPCTSTR  - LPCSTR      - __nullterminated CONST CHAR *     - const char *

                         - LPCWSTR   - CONST WCHAR *                    - const wchar_t *

PTCHAR               - TCHAR *    - char *

                                           - WCHAR *                -     wchar_t

PTBYTE                - TBYTE *      - unsigned char *

                                            - WCHAR *               -     wchar_t *



5.3 _M_IX86

USN - LONGLONG(PLONGLONG) - __int64

                                            - double

ULONGLONG(PULONGLONG)      - unsigned __int64

                                            - double







참고]

1. void *

  void형 포인터는 어떠한 변수의 주소 값이든지 담을 수 있으며 햠수의 주소 값도 담을 수 있다. 윈도우에서는 PVOID, HANDLE 등에 사용된다.

void far *

  32비트에서는 far가 의미가 없다. 윈도우에서는 LPVOID라고 불리며 PVOID와 같이 "void *"라고 생각하면 된다.


2. const

    const int * p = &a

  p는 상수만을 가리키는 정수형 포인터. p는 정수 중에서 상수만을 가리키게 되므로 *p = 20 같이 대상(상수)의 값을 변경할 수 없고 단지 주소만을 가리킬 수 있다. 즉 가리키는 값이 상수이다. 그리고 대상이 상수라서 그 값을 변경할 수 없다. 대신 이 포인터는 p = &b 같이 다른 즉 다른 상수는 가리킬 수 있다.

    int * const p = &a

  p는 정수형 변수를 가리키는 포인터 상수. 그래서 p = &b 같이 다른 값을 가리킬 수 없다. 이미 b를 가리키고 있기 때문. 즉 p가 상수이다.


3. 통합형

문자열상수

Ansi : "string"

Unicode : L"string"

통합형 : Window API => TEXT("string"), MFC => _T("string")



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

윈도우의 드라이버 개발과 루트킷 그리고 AV  (0) 2017.04.23
간단한 패커 개발  (0) 2017.04.23
Yoda's Protector 분석  (0) 2017.04.23
패커들 분석  (5) 2017.04.23
Ollydbg [올리디버거] 2.01 매뉴얼  (1) 2017.04.23
Posted by SanseoLab





I. 개요

II. 본론

A. 윈도우

  1. CPU 윈도우

  2. Log

  3. Executable modules 윈도우

  4. Memory map

  5. List of Windows

  6. Threads

  7. Handles

  8. Watches

  9. Search Results

  10. Run trace

  11. Patches

  12. INT3 breakpoints

  13. Memory breakpoints

  14. Hardware breakpoints

  15. VEH/SEH chain

  16. Call stack

  17. Source files

B. 메뉴

  1. File

  2. View

  3. Debug

  4. Trace

  5. Plugins

  6. Options

  7. Windows

  8. Help

C. 우클릭

  1. 디스어셈블러 패널에서 우클릭

  2. 레지스터 패널에서 우클릭

  3. 스택 패널에서 우클릭

  4. 덤프 패널에서 우클릭

D. 옵션

  1. Code

    1.1 Mnemonics

    1.2 Operands

    1.3 Dump

    1.4 Strings

  2. Debugging

    2.1 Debugging data

    2.2 Start

    2.3 Events

    2.4 Exceptions

    2.5 Run trace

    2.6 Hit trace

    2.7 SFX

    2.8 Just-in-time

  3. Analysis

    3.1 Advanced

    3.2 Invalid commands

  4. Search

  5. CPU

    5.1 More CPU

  6. Directories

  7. Errors and warnings

  8. Appearance

    8.1 Defaults

    8.2 Startup

    8.3 Fonts

    8.4 Colours

    8.5 Code highlighting

    8.6 Text-to-speech

  9. Miscellaneous

III. 결론

IV. 참고문헌




I. 개요

  올리디버거에는 디버깅을 하는데 많은 도움이 되는 여러가지 기능들이 있지만 실질적으로 알고 사용하는 기능은 몇 개 안된다는 생각에 이렇게 정리를 하게 되었습니다. 이 문서의 목적은 올리디버거 사용법이 아니라 기본적인 사용법을 아는 사람들에게 여러 가지 기능들을 소개하는 것입니다. 아직 이걸 쓰고 있는 본인도 이렇게 정리하면서 처음 알게 된 것이 대부분이므로 이 문서의 내용은 많이 부족한게 사실이며 주기적으로 업데이트를 할 것입니다. 오타나 추가하고 싶은 내용은 메일이나 블로그의 댓글에 달아주시면 감사히 수정하겠습니다.


  참고로 OllyDbg 2.01은 시작 시 초기 루틴에서 SeDebugPrivilege를 Enable하려고 시도한다. 그래서 윈도우 비스타 이후 부터는 관리자 권한으로 실행시키지 않으면 권한이 없으므로 제한된 기능만 사용 가능하다는 메시지 박스가 나온다. 윈도우 XP에서도 현재 계정이 Administrators Group 등에 속해있지 않는 이상 해당 메시지 박스를 볼 수 있다. 분석 시에 조심해야 할 것이 디버기가 자식 프로세스로 실행되는데 올리디버거의 권한 즉 SeDebugPrivilege를 상속하기 때문에 디버기 분석 시 권한과 관련해서는 이것을 생각하고 진행해야 한다.





II. 본론


A. 윈도우

1. CPU 윈도우

  올리디버거의 기본 윈도우(CPU 윈도우)는 크게 5개의 패널로 나뉜다.


1.1 디스어셈블러 패널

  Address, Hex dump, Command, Comments를 보여준다. Comments 부분의 경우 API들의 파라미터까지 보여주므로 쉬운 디버깅 환경을 제공해 준다.


1.2 인포 패널

  디스어셈블러 패널 아래에 존재하는 작은 패널로서 선택된 명령어에 대한 추가적인 정보를 보여준다. 예를들면 어셈블리어로 RETN 명령어를 실행할 차례라면 이 공간에는 Return to “Address” 같이 어디로 리턴할지 보기 쉽게 해준다. 특히 MOV 명령어의 경우에는 출발지와 목적지 주소를 가르쳐주기도 하고 계산도 직접해서 보여주므로 도움이 많이 되는 부분이다. 또한 소스 코드 존재 시에는 소스 코드를 같이 보여주기도 한다.


1.3 덤프 패널

  주소와 거기에 따른 Hex dump를 볼 수 있다. 그리고 3번째 부분에서는 이 Raw Data를 ASCII, UNICODE 또는 Multibyte (UTF-8) 형식으로 볼 수 있다(윗 부분을 누르면 차례대로 바뀐다).


1.4 스택 패널

  스택의 주소, 주소에 들어있는 값, 문자열 그리고 설명을 보여준다. 설명에 자주 보이는 값들로는 먼저 SEH와 관련된 SE handler, Pointer to next SEH record가 있다. 그 외에도 함수를 호출하기 전에 파라미터들도 정리해서 보여주며(특히 API의 파라미터를 보여주므로 많은 도움이 된다) 호출된 함수 별로 구분해서 보여주는 역할도 한다. 즉 함수 호출 시에 스택을 보면 "RETURN from 주소1 to 주소2"와 같이 쓰여져 있는데 여기서 "주소2"는 이 프로시저가 복귀할 주소이며 "주소1"은 현재 호출된 프로시저의 주소(첫 부분)이다. 이것을 보고 함수들의 콜 스택을 구분할 수도 있다.


1.5 레지스터 패널

  가장 위에서는 자주 확인하게 될 EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI 그리고 EIP의 값과 설명을 보여준다. 그 아래에는 EFLAGS 즉 플래그 비트를 보여준다. C(Carry Flag), P(Parity Flag), A(Adjust Flag), Z(Zero Flag), S(Sign Flag), T(Trap Flag), D(Direction Flag), O(Overflow Flag)가 그것이다. 플래그 비트 아래에는 “표 1-4”와 같은 Condition Code들이 있다. 

  이것들은 위에서 설명한 플래그 비트들을 가지고 분기 명령어의 조건을 알 수 있다. 예를들면 현재 명령어가 JAE라고 하자. 이 때 아래 "표 1-5"를 찾아보면 JAE에서 AE는 NB와 같다는 것을 알 수 있다. 이것은 CF == 0이라는 것을 의미하므로 EFLAGS 즉 플래그 비트의 Carry Flag가 1인지 0인지를 보고 판단할 수 있다. 하지만 BE와 같이 여러 개의 플래그들을 가지고 할 경우 헷갈릴 수 있는데 이 때 도움이 되는것이 이것이다. 

  예를 들어 보겠다. 현재 실행하려는 분기문이 JAE이다. 그리고 옆의 레지스터 창을 보니 다음과 같은 부분이 보인다.

EFL 00000202 ( NO, NB, NE, A, NS, PO, GE, G)

  이 때 AE가 NB와 같다고 했는데 NB가 셋팅되어 있는것을 보니 (J)AE 명령어는 분기할 것이다. 물론 올리디버거의 화살표의 색깔이 빨간색으로 변했으므로 굳이 이것을 볼 필요는 없겠지만 여러가지로 해석하는데 도움을 준다는 것을 알 수 있다.

----- 표 1-5 ------------------------------------------------------

Mnemonic Condition Tested For             Status Flags Setting

O         Overflow                            OF == 1

NO       No overflow                        OF == 0

B Below                                CF == 1

NAE      Neither above nor equal        ''

NB Not below                          CF == 0

AE        Above or equal                    ''

E Equal                                 ZF == 1

Z Zero                                  ''

NE Not equal                           ZF == 0

NZ        Not zero                            ''

BE Below or qual                      CF == 1 || ZF == 1

NA        Not above                           ''

A Above                                CF == 0 && ZF == 0

NBE  Neither below nor equal         ''

S Sign                                   SF == 1

NS No sigh                              SF == 0

PE Parity even                          PF == 1

P    Parity                                 ''

PO Parity odd                           PF == 0

NP   No parity                            ''

GE Greater or equal                   SF == OF

NL   Not less                              ''

L    Less                                   SF != OF

NGE   Neigher greater nor equal      ''

LE   Less or equal                       ZF == 1 || SF != OF

NG     Not greater                         ''

G    Greater                               ZF == 0 && SF == OF

NLE  Neither less nor equal            ''

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

  다시 위를 봐서 플래그 비트의 오른편에는 ES(Extra Segment), CS(Code Segment), SS(Stack Segment), DS(Data Segment), FS(F Segment), GS(G Segment)라는 세그먼트 레지스터가 보인다. 마지막에 있는 LastErr는 레지스터가 아니라 윈도우 API 함수들이 에러 코드를 저장하는 메모리의 내용이다. 


참고]

  Win32 API는 오류가 발생하면 NULL을 반환하는 구조로서 단지 오류 발생 유무만 확인할 수 있다. 하지만 오류의 원인을 알 수 없기 때문에 이 때 사용할 수 있는 것이 GetLastError() API이다. 이것을 사용하면 오류 코드를 확인할 수 있다. 더 깊게 들어가서 이 API는 실제로는 TEB의 LastErrorValue 필드의 값을 읽어오므로 직접 이 구조체를 통해 확인할 수도 있다. 올리디버거에서도 이 필드의 값이 위의 LastErr에 보여진다.


  플래그 레지스터 아래에는 st0~st7 까지 8개의 부동 소수점 레지스터 등이 있다.

부동 소수점 레지스터 아래에는 SSE(Streaming SIMD Extensions)를 위한 XMM 레지스터 등이 있다. 사실 이 부분은 그다지 관심이 없어서 그냥 넘어가겠다.


2. Log

  주소와 거기서 발생한 메시지들을 보여준다. 메시지들의 예로 Access violation 예외가 발생했다던지 BP에 걸렸다던지 등이 있다.


3. Executable modules

  메모리에 로드된 실행 파일과 DLL들을 보여준다. Base 주소, 크기, 엔트리 주소, 이름, 타입, 파일 버전, 정적 링크, 경로가 있다. 예를들면 현재 디버깅 중인 실행 파일부터 kernel32.dll, ntdll.dll 같은 것들이 올라와 있다. 


4. Memory map

  현재 애플리케이션의 메모리 맵을 보여준다. 실행 파일부터 로드된 DLL들의 메모리 맵과 간단한 정보를 볼 수 있다. 주소별로 크기와 Owner, 섹션, 포함된 내용, 종류, 접근 권한 등을 보여준다.

  예를들어 VirtualAlloc()으로 메모리를 할당하면 해당 주소 영역이 보여지기도 하여 눈으로 직접 확인할 수 있고(생성된 부분은 빨간색으로 보여진다), 마우스 오른쪽 클릭 후 메모리 BP를 걸 수도 있다. 이것은 굉장히 유용하게 사용될 수 있는데, 예를들어 현재 DLL 내부의 어느 부분인줄 모를 때 .text 섹션에 BP를 걸어놓고 실행시키면 해당 부분에서 멈출 수 있기 때문이다.

  문제는 더 복잡한 바이너리를 분석할 때이다. 이 때는 간단하게 DLL에서 복귀하는 부분이 아니라 할당한 메모리에 코드를 생성하고 여기에서 복잡한 연산을 수행하는 것이 그 예가 될 것이다. 물론 이러한 경우에도 위의 방식이 유용하게 사용될 수 있지만 메모리 BP이기 때문에 해당 영역에서 코드가 실행될 때 뿐만 아니라 해당 영역을 참조할 때도 멈추게 된다. 만약 돌아오고 싶은 영역이 단지 실행될 때 멈추고 싶은데 계속 해당 영역에 대한 읽고 쓰기가 반복되어 참조할 때마다 멈춘다면 BP의 의미가 없어질 것이다.

  이 때는 간단하게 마우스 오른쪽 클릭 후 "set access"에서 "Execute"만 설정하면 된다.


5. List of Windows

  윈도우의 목록을 보여준다. 핸들, 설명, 부모의 핸들, WinProc, ID/menu, Type, Style, ExtStyle, Thread, ClsProc, ClsName이 있다. 콘솔 프로그램이 아닌 윈도우 프로그램 분석 시 필요한 정보들이다.


6. Threads

  현재 스레드들을 보여준다. 순서, Ident, 윈도우 이름, Last error, Entry, TIB, Suspend, Priority, User time, System time이 있다. 멀티스레드일 경우 여길 보고 찾을 수 있다.


7. Handles

  현재 핸들들을 보여준다. 핸들, Type, Refs, Access, Tag, Info, Translated name이 있다. 문제는 파일 같은 경우 생성된 핸들을 잘 보여주는데 CreateProcess로 프로세스를 생성한 경우에는 보여주지 않는다. x64dbg의 경우 자세한 내용은 보여주지 않아도 이 경우 프로세스와 쓰레드는 보여주는데 OllyDbg는 다른 경우도 그렇고 많이 보여주는 편은 아닌것 같다. 분석 시에 핸들을 따로 적어놓을 필요가 있다. Windbg의 경우 불편하긴 하지만 프로세스 핸들의 PID를 보여줌으로써 추적이 가능하는 등 기능 자체는 많이 제공한다.


8. Watches

  설정한 Expression과 그 값을 보여준다. Expression을 추가한 후 각 라인 실행 후마다 결과를 볼 수 있다(예를들면 expression에 “EAX+ESI”라는 값을 설정해 놓으면 결과 값이 각 라인 실행 후마다 보여진다).


9. Search Results

  여러 검색 사용 시에 결과들을 보여주는 윈도우이다.


10. Run trace

  Run trace 결과를 보여준다. Back, Thread, Module, Address, Command, Referenced memory (decoded), Registers modified by command가 있다.


11. Patches

  수정한 기록을 보여준다.


12. INT3 breakpoints

  설정한 소프트웨어 브레이크 포인트(INT3) 즉, 단축키 F2를 통해 만든 BP들을 보여준다.


13. Memory breakpoints

  설정한 메모리 브레이크 포인트들을 보여준다.


14. Hardware breakpoints

  설정한 하드웨어 브레이크 포인트를 보여준다.


15. VEH/SEH chain

  VEH와 SEH 체인을 보여준다.


16. Call stack

  콜스택을 보여준다. 즉 호출한 서브루틴을 차곡차곡 쌓아서 보여준다.


17. Source files

  Module, Source, Path.






B. 메뉴


1. File

- Open : 리버싱할 파일 오픈. 드래그로 대신할 수도 있다. 파일을 선택하면서 argument도 같이 추가할 수 있다.

- Set new arguments… : 시작할 때 넣은 arguments 대신 다른 것을 넣을 수 있다.

- Attach... : 현재 실행중인 프로세스를 디버깅하고 싶을 때 사용.

- Detach

- Exit : 종료

- GUI language : 올리디버거가 위치한 디렉토리에 확장자가 .lng인 파일(올리디버거 2부터는 다른 언어를 지원한다)이 존재할 때 그 .lng 파일에서 지원하는 언어를 선택할 수 있다.


2. View

- Log : 올리디버거 실행 이후 로그를 보여준다. 

- Executable Modules : 메모리에 로드된 실행 파일과 DLL들을 보여준다.

- Memory map : 현재 애플리케이션의 메모리 맵을 보여준다.

- List of windows : 윈도우 목록

- Threads : 쓰레드 목록. 멀티스레드일 경우 여길 보고 찾을 수 있다.

- CPU : 현재 기본 윈도우

- Handles : 핸들 목록

- Watches : Expression을 추가한 후 각 라인 실행 후마다 결과를 볼 수 있다(예를들면 expression에 “EAX+ESI”라는 값을 설정해 놓으면 결과 값이 각 라인 실행 후마다 보여진다).

- Search results : 마우스 오른쪽 버튼으로 search한 결과들이 보여지는 창.

- Run trace : trace한 부분들을 볼 수 있다.

- Patches : 수정한 부분들. 예를들어 어셈블리를 수정한 경우 여기에 추가된다.

- INT3 breakpoints : 설정된 BP 리스트

- Memory breakpoints : 메모리에 BP 설정한 경우

- Hardware breakpoints : 하드웨어 BP 설정한 경우

- VEH/SEH chain : VEH, SEH 체인을 보여준다.

- Call stack : “Call stack”을 보여준다. 즉, 호출한 서브루틴을 차곡차곡 쌓아서 보여준다.

- Source files 

- File… : 파일 창. 예를들면 어셈블리를 수정하고 저장하고 싶을 때 (수정만 하면 메모리 상에서만 수정된 것이므로), 마우스 우클릭 -> Edit -> Copy to executable을 하면 이 창이 뜨고 이 창에서 마우스 우클릭 -> Save file 하면 저장된다.

- Drive… : 드라이브를 보여준다.


3. Debug

- Run [F9] : 실행 (BP가 걸려 있으면 그곳에서 실행이 정지된다.)

- Run thread [F11] : 현재 쓰레드만 실행한다.

- Step into [F7] : 한 줄씩 실행. CALL 명령 실행 시 그 함수로 따라 들어간다.

- Step over [F8] : CALL 명령 실행 시 그 함수를 실행한다. REP의 경우 만족할 때까지 계속 실행. 

- Execute till return [Ctrl+F9] : 함수 코드 내에서 RETN 명령까지 실행 (함수 탈출)

- Restart [Ctrl+F2] : 재시작

- Close [ALT+F2] : 종료

- Set affinity : 멀티프로세서인 경우 태스크를 수행할 CPU를 고른다.

- Create function library : 아래에 [참고]를 보자. 올리디버거는 이미 알고 있는 것들 외에도 표준 라이브러리 함수들을 추가할 수 있다. 이 기능을 통해서 라이브러리를 불러온 후에 .udl 파일로 저장하고 Directories 옵션에서 설정한 위치에서 이 .udl 파일을 불러와서 분석에 사용한다. 또한 Advanced 옵션에 나와있는 항목을 체크해야 사용할 수 있다.


참고] .udl

  올리디버거는 아래의 라이브러리들에 있는 2300개 이상의 API 함수들에 대한 정보를 갖는다. 또한 10000개 이상의 심볼릭 상수들을 알고 있다. 그렇기 때문에 리버싱을 하다 보면 GetModuleHandleA() 같은 함수들을 분석해서 이름과 파라미터 등에 대한 정보가 나와서 편하게 분석할 수 있는 것이다. 하지만 이것들 외에도 여러 라이브러리가 사용될 수 있기 때문에 위에서 설명하였 듯이 표준 라이브러리 함수들을 추가하는 방식 또한 제공한다.

kerne32.dll, gdi32.dll, user32.dll, ntdll.dll, version.dll, advapi32.dll, shlwapi.dll, comdlg32.dll, msvcrt.dll


4. Trace

- Open run trace : Run trace 윈도우를 연다.

- Clear run trace : Run trace를 모두 지운다.

- Close run trace : Run trace 윈도우를 닫는다.

- Animate into, Animate over : Step into, Step over와 비슷하지만 아래에 나올 condition을 건 곳까지 계속 실행한다. 중간에 멈추려면 F12. Run trace 윈도우를 보면 실행한 명령어와 추가 정보들이 기록되어 있다. 아래에 나오는 condition을 보면 알겠지만 일반적인 BP보다 훨씬 다양한 조건들을 지정할 수 있다.

- Trace into, Trace over : Animate into, Animate over와 거의 같지만 Animate가 명령어를 실행하면서 그것을 따라가는것과는 달리 이것은 각 명령어를 실행할 때마다 일일이 따라가지 않는다는 차이점이 있다. 즉 현재 디스어셈블러창을 그대로 두고 실행된다. 그것 외에는 Run trace 윈도우에 기록된다는 점이나 condition을 지정할 수 있다는 점 등 모두 같다.

- Run hit trace, Discard hit trace : Run hit trace를 선택하면 condition이 걸린 곳까지 실행되는데 이 때 실행한 경로가 빨간색 점으로 어셈블리어 왼쪽에 표시된다. 즉, 어디서부터 어디까지 어떤 경로로 실행되었는지를 CPU 윈도우에서 명시적으로 볼 수 있다. 이 기록을 지우고 싶다면 Discard hit trace를 누르면 된다.

- Set condition… : Run trace와 Hit trace를 중지할 조건을 지정한다.

- Set protocol… : Run trace 윈도우에 저장할 내용 지정(call만 저장한다든지 등등).


5. Plugins

플러그인을 추가하면 이곳에 등록된다.


6. Options

- Options...

- Load options… : 옵션을 불러온다.

- Save options… : 옵션을 저장한다.

- Edit shortcuts… : 단축키 설정


7. Windows

- Always on top : 항상 맨 위로

- Cascade : View들을 정렬해서 보여준다.

- Tile horizontal : View들을 수평으로 정렬한다.

- Tile vertical : View들을 수직으로 정렬한다.

- Arrange icons : 아이콘 정렬

- Close all : 모든 View들을 끈다.

- Restore window : View를 원상복귀

- Maximize window : VIew 최대화

- Previous : 이전 View를 보여준다.

- Next : 다음 View를 보여준다.


8. Help

- About

- License






C. 우클릭


1. 디스어셈블러, 덤프 패널에서 우클릭


1.1 Backup

- Create backup : 백업 파일을 만든다.

- Read executable file

- Load backup from file… : 백업 파일을 로드한다.

- Save data to file… : 백업 파일로 저장한다.


1.2 Edit

- Copy as table : 선택한 영역 테이블 형식으로 복사. 바이너리를 복사하고 붙여넣기할 때 사용되는 방식이 아니라 텍스트로서 복사하는 방식이다.

- Copy address : 선택한 영역 주소 복사

- Binary copy : 바이너리 복사. Copy as table과는 달리 오직 바이너리 부분만 복사한다. 

- Binary paste : 바이너리 붙여넣기 Binary copy로 복사한 후 붙여넣기를 하거나 HxD에서 복사한 후에 붙여넣기를 할 수 있다.

- Binary edit… : 바이너리 수정

- Fill with zeros : zero(00)들로 채워 넣는다.

- Fill with NOPs : NOP(90)들로 채워 넣는다.

- Select all : 전체 선택

- Select procedure : 현재 커서가 위치한 프로시저 선택

- Copy to executable : 위의 “File…” 윈도우에서 설명했다.


1.3 Add label… [ : ]

라벨을 단다. 콜론(:)을 사용해도 된다. 아래의 Label 항목 참조.


1.4 Assemble… 

어셈블리어를 수정한다.


1.5 Add comment… [ ; ]

주석을 단다. 세미콜론(;)을 사용해도 된다. CPU 윈도우의 디스어셈블러 패널의 오른쪽 부분인 Comment 창에서 주석을 볼 수 있다.


1.6 Breakoint

- Toggle : BP 설정, 제거

- Conditional… : BP 조건 설정. 예를들면 루프문에서 [ ECX == 1 ] 같이 사용할 수 있다. 이 경우에 실행시키면 ECX가 1이될 때 까지 실행하게된다.

- Conditional log… : conditional의 로그를 보여준다.

- Run to selection [F4] : 선택된 곳까지 실행.

- Memory… : 메모리 BP(메모리에 BP 설정). Read access, Write access, Execution이 있다.

- Momory log… : 메모리 BP 로그

- Hardware… : 하드웨어 BP. Execution, Access(R/W), Write가 있다.

- Hardware log… : 하드웨어 BP 로그

- Limit run trace protocol to selection : 선택한 부분만 Run trace에 저장.

- Limit run trace protocol to current procedure : 현재 프로시저만 Run trace에 저장.


1.7 New origin here

EIP의 값을 선택한 명령어의 주소로 바꾼다. 즉 다음으로 실행할 명령어를 이곳으로 설정한다.


1.8 Folow [Enter]

  직접 Step into할 필요 없이 다음 실행할 명령어로 이동한다. 예를들어서 CALL 명령어라면 어느 부분을 호출하는지 디버깅하거나 주소로 직접 찾아가는 방법 대신 Enter를 누르면 보여준다. 다시 -를 눌러서 되돌아 와서 Step into를 할지 Step over를 할지 고를 수 있다.


1.9 Follow in Dump

- Memory Address : 현재 지정한 주소를 아래의 덤프 패널에서 보여준다.

- Immediate constants : 명령어의 상수 값이 나타내는 위치를 덤프 패널에서 보여준다. 

- Selection : 이 명령어의 위치를 덤프 패널에서 보여준다.


1.10 Go to

- Origin : EP로 이동

- Expression… : 특정 위치로 이동. 여기에는 직접 주소를 쓸수도 있고 모듈의 함수(예를들면 API)를 쓸 수도 있다. 

- Previous location : 이전 위치로 이동

- Next location : 다음 위치로 이동 (Previous location 한 후에 가능)

- Previous procedure : 이전 프로시저로 이동

- Next procedure : 다음 프로시저로 이동

- Executable file : 메모리가 아닌 실행 파일에서 선택한 명령어를 보여준다.


1.11 Select module

현재 올라온 모듈들 중에서 선택한다.


1.12 Select thread

현재 쓰레드 중에서 선택한다.


1.13 Open in a separate dump window

다른 덤프 윈도우에서 선택한 부분을 보여준다.


1.14 Search for

- Names : 존재하는 모든 Name들을 보여준다.

- Command… : 명령어 검색 (예를들면 “call 00401E2B”)

- Sequence of commands… : 명령어들 검색 

- Constant… : 상수 검색

- Binary string… : 문자열 검색

- All intermodular calls : Import 된 함수들의 목록을 본다. (대부분 API)

- All commands…: 찾는 모든 명령어의 목록을 보여준다.

- All command sequences… : 찾는 모든 명령어들의 목록을 보여준다.

- All constants… : 찾는 모든 상수들을 보여준다.

- All modifications : 모든 수정 사항들을 보여준다.

- All referenced strings : 모든 문자열들을 보여준다.

- All referenced GUIDs : 모든 GUID들을 보여준다. 

- All user comments : 사용자가 설정한 모든 주석들을 보여준다.

- All found procedures : 모든 프로시저들을 보여준다.

- All found switches : 스위치 구문도 찾아준다.

- All floating constants : 모든 부동 소수점 상수들을 보여준다.


1.15 Find references to

- Selected command : 역참조. 어디서 이곳을 호출하는지 보여준다. 예를들면 일반적으로 프로그램은 스텁 코드를 지나 main() 함수로 진입한다. 즉 스텁 코드에서 main()을 호출하는 부분, 다시 말해서 main()의 처음 명령어를 호출하는 부분이 있을 것이다. 이 경우 main() 함수의 첫 명령어에 대해 Selected command를 수행하면 스텁 영역에서 main()을 호출한 명령어가 나올 것이다. 또한 검색 결과는 같은 모듈의 경우에만 검색하여 보여준다. 예를들어 대상이 API 함수인 경우 어디 어디에서 해당 API 함수를 호출하는지 알고 싶어서 API 함수의 첫 명령어에서 이것을 호출한 경우에는 원하는 답을 얻을 수 없다. API 함수가 구현된 모듈은 각각의 해당 라이브러리일 것이기 때문이다. 이 경우에는 아래의 항목을 사용할 수 있다. 또한 call 명령어 뿐만 아니라 jmp 류의 명령어도 해당 위치로 분기한다면 이것도 찾아준다.

- Call destination : CALL한 목적지가 같은 곳들을 보여준다. 즉 같은 곳을 호출하는 명령어들을 찾아준다. 당연히 call 명령어를 대상으로 수행할 경우에만 볼 수 있다. Selected command와 다른 점은 호출되는 명령어에서 사용하는 대신 호출하는 call 명령어에서 이것을 수행한다는 점이다. 그래서 위에서 언급하였듯이 특정 API 함수를 어디 어디에서 호출하는지 알고 싶은 경우 해당 API를 호출하는 명령어에 대해 Call destination을 수행하면 어디 어디에서 해당 API 함수를 호출하는지 알 수 있다. 이것도 같은 모듈의 경우에만 해당된다.

- Jump destination : Jump한 목적지가 같은 곳들을 보여준다. Call destination과 같으며 차이점은 jmp 류의 분기문을 대상으로 수행할 경우에만 볼 수 있다. 이것도 분기하는 jmp 문들에서 사용될 때 같은 위치로 분기하는 jmp 문들을 찾아준다.

- Address constant : 같은 상수를 사용하는 명령어들을 찾아준다. 주소로 상수를 갖는 명령어를 대상으로 수행한 경우에 볼 수 있는 항목이다. 예를들어 call 명령어 중에서 옵코드가 FF15인 것은 분기할 주소를 상수로 가지고 있다. 조금 더 자세히 설명하자면 호출할 함수의 주소가 IAT에 존재하게 되는데 호출할 함수에 대한 IAT의 주소를 상수로 가지고 있는 것이다. 어쨌든 이 경우와 같이 주소를 상수로 가지고 있는 경우 항목이 보여지며 수행할 경우 이것처럼 해당 IAT 주소를 참조하여 분기하는 call 들을 볼 수 있다.

- Immediate constant : 해당 인자와 같은 인자를 사용하는 명령어들을 찾아준다. 상수를 인자로 갖는 명령어를 대상으로 수행할 경우에만 볼 수 있다. 예를들어 "push 1" 명령어에 Immediate constant를 수행하면 같은 모듈 안에서 "mov al, 1", "cmp esi, 1" 등 1을 인자로 사용하는 명령어들을 모두 찾아준다.


1.16 Highlight register

- Stop highlighting

- EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI

선택한 레지스터를 강조 한다.


1.17 Addressing

- Absolute, Relative to selection, Relative to module base

디스어셈블리 윈도우에서 주소를 어떻게 보여줄지 정한다.


1.18 Comments

- Show comments : 코멘트를 보여준다.

- Show source : 소스를 보여준다.

- Show profile : 코멘트와 프로필을 보여준다. 프로필은 trace했을 때 몇 번이나 지나갔는지를 나타내는 숫자를 보여준다.


1.19 Analysis

- Analyse code [Ctrl+A] : 어셈블리 분석

- Remove analysis from selection : 선택한 부분 분석 제거

- Remove analysis from module : 모듈에서 분석 제거. 올리디버거의 분석이 실패했을 때. 특정 코드 부분을 데이터로 해석한 경우에 분석을 제거하면 원래 코드로 보일 때가 있다.


1.20 Help on command

명령어에 대한 설명


1.21 Appearance

참고로 옵션에서 설정한 이후 이것을 통해 직접 선택해야 적용시킬 수 있다. 적용은 디스어셈블리 패널에 한정한다.

- Always on top : View들 사이에서 항상 맨위로

- Show bar : 위에 Address, Hex dump 같은 bar 표시

- Show horizontal scroll : 수평 스크롤 표시

- Default columns

- Font

- Colours

- Highlighting - No highlighting, Christmas tree, Jumps and calls, Memory access, Hilite 4, Hilite 5, Hilite 6, Hilite 7






2. 레지스터 패널에서 우클릭

- Increment : 특정 레지스터의 값을 1 올린다.

- Decrement

- Zero

- Set to 1

- Modify ...

- Copy to clipboard

- Copy all registers : 내용을 복사한다.

- Follow in disassembler

- Follow in dump

- View MMX registers

- View 3DNow! registers

- View SSE as 32-bit floats

- View SSE as 64-bit doubles

- Appearance : 이것도 디스어셈블리 패널과 같다.





3. 스택 패널에서 우클릭

- Lock address : 디버깅을 할 때마다 PUSH, POP 등을 수행하며 ESP 값에 따라 위아래로 움직이는데 이것을 움직이지 않게 고정해 놓는다.

- Backup

- Edit

- Push DWORD… : DWORD 크기의 값을 PUSH한다.

- Pop DWORD : DWORD 크기의 값을 POP한다.

- Breakpoint

- Follow in Disassembler, Follow in Dump : 디스어셈블러나 덤프에서 여기에 대응하는 위치를 보여준다.

- Go to

- Select module

- Decode as structure…, Decode as pointer to structure… : 아래의 구조체 항목 참조.

- Search for

- Addressing

- Columns

- Select ASCII code page

- Appearance : 이것도 디스어셈블리 패널과 같다.





참고] 명령어 검색


  가끔씩 어셈블리 명령어를 검색할 일이 생긴다. 만약 다음과 같이 확실한 명령어를 찾는다면 직접 검색해도 될 것이다.


push 18

mov ebx, dword ptr ss:[ebp-48]


  [ Ctrl + F ]로 Search 윈도우를 켠 후 위의 명령어를 입력하면 결과를 얻을 수 있다. 하지만 이 방식은 불편하기 때문에 마우스 오른쪽 키를 클릭하고 [ Search for - All commands .. ] 또는 [ Search for - All commands sequences .. ]를 이용하여 전체 결과를 얻는 것이 훨씬 편한 방법으로 보인다. 


  어쨌든 위와 같이 확실한 명령어를 검색할 일도 있지만 단지 패턴만 알고 있는 경우가 대부분일 것이다. OllyDbg는 이러한 경우도 지원해 준다. 가장 넓은 범위는 ANY라는 키워드이다. 다음을 검색하면 push ebp부터 시작해서 push 18까지 모든 결과를 볼 수 있다.


push any


  만약 위와 같이 상수만을 보고 싶다면 const 키워드를 이용한다.


push const


  레지스터의 경우도 지원된다. 여러가지가 있겠지만 대부분 r32일 것이며 이외에도 r16, r8도 지원된다. push ebp 같은 명령어를 찾고 싶다면 다음을 검색에 사용한다.


push r32


  또한 메모리 주소도 사용할 수 있다. 위의 mov ebx, dword ptr ss:[ebp-48]를 검색하는데 다음을 사용할 수 있다.


mov r32, [any]


  또는 다음과 같이 사용할 수도 있다.


mov r32, [r32-const]


  가장 많이 활용되는 방식 중 하나로 다음과 같이 call 명령어와 관련된 것이 있다.


call [any]

call [const]


  그리고 xor eax, eax 같이 두 레지스터가 같이 사용되는 경우를 찾는 것도 존재한다.


xor ra, rb

xor r32a, r32b


  마지막으로 다음의 키워드들도 제공된다.


JCC : (JB, JNE, JE 등)

SETCC : (SETB, SETNE 등)

CMOVCC : (CMOVB, CMOVNE 등)

FCMOVCC : (FCMOVB, FCMOVE 등)





4. 덤프 패널에서 우클릭

- Backup

- Edit

- Add label...

- Assemble...

- Breakpoint

- Follow DWORD in Dump

- Go to : 덤프 패널도 디스어셈블리 패널처럼 (-), (+) 키를 통해 이전과 이후로 전환할 수 있다.

- Decode as structure ... : 아래의 구조체 항목 참조

- Decode as pointer to structure ... : 아래의 구조체 항목 참조

- Open in a separate dump window

- Search for

- Addressing

- Hex

- Text

- Integer

- Float

- Disassemble : 데이터나 스택에 코드를 써놓은 경우 Follow Dump를 통해 덤프 창에서 보여지게 한 후 이것을 선택하면 코드를 간략하게 볼 수 있다.

- Select ASCII code page

- Appearance : 이것도 디스어셈블리 패널과 같다.





참고] 구조체

  레지스터, 스택 덤프 패널에서 해당 값 또는 해당 주소가 가리키는 포인터를 구조체로 참조하게 만들 수 있다. 이를 선택하면 어떤 구조체인지 선택할 수 있는데 대표적으로 TEB, PEB, Context, 예외 관련 등 많이 볼 수 있다. 인터넷에서 직접 찾을 수 있지만 이렇게 일목요연하게 보여준다는 것이 괜찮은 기능인 것 같다.


  [ Decode as structure ... ]의 경우 덤프 패널에서 선택한 경우 선택된 해당 주소를 구조체로 지정할 수 있다. 스택 패널의 경우 해당 ESP를 구조체로 여기게 된다. [ Decode as pointer to structure ... ]는 해당 주소까지 갈 필요 없이 특정 메모리 주소의 값이 해당 구조체일 경우 선택 가능하다. 스택 패널의 경우 해당 ESP에 들어있는 값을 구조체로 여기게 된다.


  찾아보니 이 구조체를 사용자가 직접 추가할 수도 있다. https://reverseengineering.stackexchange.com/questions/11893/creating-my-own-custom-structure-in-ollydbg ]


  어떤 면에서는 유용해 보이지 않을 수도 있지만 자동으로 Label을 달아주는 기능이 있다면 동적으로 분석할 때에도 괜찮아 보인다. 사실 정적 분석 시에는 한계가 있으며 Ida pro를 이용해 정적으로 몇 개 찾은 것도 .map 파일을 만들어서 올리디버거로 올리면 이 부분이 보이지 않으므로 (대부분 지역 변수 즉 스택을 이용하기 때문으로 추측) 해당 주소에 대해 구조체 형태로 보여주는 기능이 있으니 자동으로 Label까지 달아준다면 분석에 더 용이해 보인다. 특히 델파이나 C++의 경우 분석이 까다로운 경우가 있는데 어느 정도는 도움을 받을 수 있다고 생각한다.





참고] Label

  Ollydbg는 Windbg나 Gdb와 다르게 .udd 파일에 분석 결과를 저장함으로써 디스어셈블러의 기능을 가지고 있다. 이 기능을 통해 디스어셈블러처럼 함수나 변수에 이름을 달 수 있다. (스택 영역 즉 지역변수는 불가능하다)

  함수의 시작 주소에 Label을 달면 Comments 창에 보이지는 않지만 이후 이 함수를 호출할 때 주소 대신 설정한 이름을 보여준다. 또한 덤프 패널에서 사용할 수도 있는데 이를 통해 전역 변수 즉 데이터 영역의 값을 참조할 때도 이름을 붙일 수 있다.









D. 옵션


1. Code

Disassembling syntax : [MASM, IDEAL, HLA, Linux(AT&T)]

; 선택한 어셈블리어 문법 형식으로 보여준다.

Disassemble in lowercase

; 어셈블리어를 소문자로 표시한다.

Tab between mnemonics and arguments

; 니모닉과 인수 사이를 Tab으로 띈다.

Extra space between arguments

; 인자들 사이를 더 띈다.

Show default segments

; 기본 세그먼트를 보여준다.

Always show size of memory operands

; 메모리 오퍼랜드의 크기를 보여준다. (DWORD PTR 같은) 하지만 뒤에 따라 붙는 상수의 크기 해석이 모호한 경우 즉 DWORD, DWORD BYTE 모두 가능한 경우에는 계속 붙어있는다.

Show NEAR jump modifiers

; NEAR jump의 경우 NEAR를 표시한다.

Show local module name

; 주소 앞에 모듈 이름도 같이 표시한다.

Show symbolic addresses

; 심볼릭 주소 표시 (00400000 -> OFFSET <STRUCT IMAGE_DOS_HEADER>로 표시)

Demangle symbolic names

; Mangling 된 C++ Name을 Demangling 해서 보여준다.


1.1 Mnemonics

Guess alternative forms of conditional commands

; 조건 명령어의 경우 ollydbg가 분석을 해서 상황에 맞게 JE를 JZ로, JAE를 JC로 같이 더 나은 명령어로 바꿔준다.

Form of string commands : [Long, Short]

; String operand를 가정해서 명시적으로 표현하거나 하지 않는다. 예를들면 그냥 MOVSB로 표현하든지 아니면 "MOVS [DWORD EDI], [DWORD ESI]" 같이 표현한다.

Decode near returns as : [RETN, RET]

; NEAR Return인 경우 RETN으로 표시해서 near를 강조하거나 그냥 RET로 표시

Decode size-sensitive 16/32-bit mnemonics like : [PUSHA/PUSHAD, MOVS/MOVSD…, PUSHAW/PUSHAD, MOVSW/MOVSD... , PUSHAW/PUSHA, MOVSW/MOVS...]

; 첫 번째는 32비트인 경우 접미사로 D를 붙이고, 두 번째는 16비트는 W를 32비트는 D를 붙이고, 세 번째는 16비트만 W를 붙인다. 

Decode jump hints as

; 점프 힌트와 관련된 옵션.


1.2 Operands

Decode top of FPU stack as

Decode size of 16/32-byte SSE operands as


1.3 Dump

Underline fixups

; CPU 윈도우의 Hex dump 칼럼에서 fixup 주소(로더가 재설정한 주소)인 경우 밑줄을 긋는다.

Use wide characters in UNICODE & multibyte dumps

; UNICODE와 multibyte의 경우 wide character를 사용한다.

Disable GDI scripting support

Replace non-printable ASCII characters with dots

; 덤프 패널에서 보여줄 수 없는 문자는 점으로 표시한다.

Display address in address column as : [(Hex, Symbol), (Symbol, Hex), (Symbol or Hex)]

; CPU 윈도우의 디스어셈블러 패널의 Address 칼럼을 늘려 보면 Hex 값의 주소 뿐만 아니라 Symbolic name도 보여지는 것을 알 수 있다. 이것의 순서를 바꿀 수 있다.

Highlight symbolic names in address column

; Symbolic name을 강조한다.

Doubleclick on address : [Sets relative addressing, Labels address]

; 주소를 더블클릭할 시에 상대주소로 바꾸거나 사용자 정의 라벨을 붙인다.

ASCII code page, Multibyte code page

; ASCII 코드와 Multibyte 코드의 경우 사용되는 코드 페이지


1.4 Strings

Decode Pascal-style string constants

Allow diacritical symbols [...]

; 다양한 문자까지 인식해서 보여준다.

To recognize UNICODE strings []

; UNICODE 문자열을 ASCII와 같은 부분집합만 인식하거나, 윈도우가 결정하게 한다. 

Mode of string decoding [Plain, Assembler, C]

; 문자열을 Plain의 경우 [“abc|def”], Assembler의 경우 [“abc”,LF”def”], C의 경우 [“abc\ndef”] 형식으로 보여준다.



2. Debugging

Assume flat selectors

; CS, DS, SS, ES가 항상 zero 오프셋을 갖는다고 가정한다. 즉, 레지스터 패널에서 값이 사라진다.

After Execute till return, step over RET

; 원래 Ctrl+F9 즉, “Execute till return”를 하면 RET에서 멈추는데 이걸 선택하면 리턴까지 실행하고 멈춘다.

Allow fast command emulation

; 자주 사용되는 CPU 명령어들을 내부적으로 emulate하게 해서 디버깅을 빠르게 해준다.

Auto backup user code

; 시스템 코드 섹션이 아닌 사용자 코드는 자동으로 백업한다.

Use HW breakpoints for stepping

; 디버깅 시 INT3 대신 HW BP를 사용한다. 사실 디버거는 소프트웨어 브레이크포인트(INT3)를 통해서 디버깅을 진행하는데 우리 눈에는 보이지 않지만 Step into의 경우 다음 명령어의 뒤에 브레이크포인트를 걸어 놓아서 한 명령어만 실행하고 멈출 수 있게 한다.

Hide unimportant handles

; 중요하지 않은 핸들은 Handle 윈도우에서 숨긴다.

Set permanent breakpoints on system calls

; KERNEL32.UnhandledExceptionFilter(), NTDLL.KiUserExceptionDispatcher(), NTDLL.ZwContinue(),  NTDLL.NtQueryInformationProcess() 같은 시스템 호출에 INT3, 즉 브레이크포인트를 설정한다.

To pause running application : [Set breakpoint in every thread, Call DebugBreakProcess]

; 실행 중인 애플리케이션을 중지하기 위해 모든 스레드에 메모리 트랩이나 싱글 스텝 브레이크를 건다. 또는 OS가 지원한다면 DebugBreakProcess()라는 특별한 시스템 호출을 사용한다.

When terminating the running application : [Immediately call TerminateProcess(), Try to use ExitProcess() first]

; 실행 중인 애플리케이션 종료 시 사용 옵션. 첫 번째는 빠르고, 두 번째는 더 안전하다.

Command used as a soft breakpoint

; 소프트 브레이크포인트로 사용할 명령어. 기본은 INT3이다.


2.1 Debugging data

Use debugging data

; DBGHELP.DLL을 허용한다. 이 파일이 올리디버거가 있는 디렉토리에 같이 있어야 한다.

Use dbghelp.dll for stack walk

; 스택 탐색을 위해 DBGHELP.StackWalk64()를 사용한다.

Directories containing debugging data (.pdb, .dbg...). Dbghelp.dll will also recurse into subdirectories.

; .pdb 같은 디버깅 데이터가 존재하는 디렉토리 경로. 참고로 안에 있는 서브 디렉토리도 검사한다. 그리고 그곳에 소스 코드도 있다면 그것도 분석할 때 소스 코드 윈도우에서 참고할 수 있다.

Allow access to Microsoft Symbol Server

; SymSrv.dll 파일을 이 디렉토리에 복사해 넣어야 한다.

Don’t list missing source files

Don’t list internal compiler labels

Skip leading spaces from source lines in comments


참고] DbgHelp.dll

  디버깅을 도와주는 여러 API들을 제공한다. 예를들면 dbghelp.stackwalk64() API는 콜스택을 보여준다. [ http://www.codeproject.com/Articles/11132/Walking-the-callstack ]


참고]

  이미 생성된 .udd 파일 때문에 헷갈릴 수 있으므로 즉 한 번 분석된 경우 .udd 파일에 분석 내용이 저장되므로 이것을 지워야 pdb와 관련된 조사를 할 수 있다. 올리디버거는 실행 파일이 있는 폴더에서 pdb 파일을 찾아서 자동으로 사용한다. 저 위의 디렉토리 옵션은 pdb가 존재하는 추가적인 디렉토리를 설정해 줄 수 있다. 참고로 비주얼 스튜디오로 컴파일 시 /DEBUG 옵션을 사용해서 디버그 섹션이 추가되면 이 섹션 내부에 디렉토리 정보가 추가되어 있기 때문에 디렉토리 설정을 하지 않아도 자동으로 pdb가 있는 디렉토리를 찾아 읽어서 분석해 준다. 



2.2 Start

When starting application, make first pause at : [System breakpoint, TLS callback (if defined), Entry point of main module, WinMain (if location is known), No pause]

; 맨 처음 디버깅 시에 어디서 시작할 것인지. Entry point of main module로 설정하는 것이 좋다.

When attaching to application, make first pause at : [System breakpoint, Application code, No pause]

; 실행중인 프로그램 Attach할 시

When loading DLL, make first pause at : [Entry point of LOADDLL.EXE, DLL entry point (if defined), After call to LoadLibrary(), No pause]

; DLL 로드 시


2.3 Events

Warn on frequent events

Pause on new module (DLL)

; 새로운 DLL이 로드되면, EP에서 중지한다. 즉, 이것을 설정하고 실행하면 새로운 DLL을 로드할 때마다 자동으로 BP가 걸린다.

Pause on module (DLL) unloading

; DLL이 메모리에서 제거되면 중지한다.

Only on the following modules

; 오직 여기서 추가한 모듈만 중지한다.

Pause on new thread

; 새로운 스레드가 시작되면 EP에서 중지한다.

Pause on thread end

; 스레드가 끝나면 중지한다.

Pause on debug string

; 애플리케이션이 디버그 문자열을 내보내면 중지한다.

Debug child processes

; 디버기가 자식 프로세스를 만들면 새로운 ollydbg를 시작해서 디버깅한다.


2.4 Exceptions

Ignore memory access violations in KERNEL32

; kernel32.dll 모듈에서 발생하는 access violation 예외를 무시한다.

Step over INT3 breaks in MSCORWKS

; .NET 엔진이 디버거에게 이벤트를 알린다.

Ignore (pass to debugged program) following exceptions : [INT3 breaks, Single-step breaks, Memory access violations, Integer division by 0, Invalid or privileged instructions, All FPU exceptions, All service exceptions]

; 해당하는 예외를 무시한다. 이것은 디버거로 넘어온 예외를 Shift+Run/Step을 하지 않고 자동으로 다시 디버기에게 넘기겠다는 것을 의미한다.

Ignore also the following custom exceptions or ranges

; 나머지 예외들을 직접 설정할 수 있다.

Pass unprocessed exceptions to Unhandled Exception Filter

; 처리하지 않은 예외들을 Unhandled Exception Filter에 넘긴다.

  일반적으로 SetUnhandledExceptionFilter() 함수를 사용해 Unhandled Exception을 처리하는 경우에는 디버깅 시에 등록된 Unhandled Exception Handler가 실행되지 않고 중간에 프로세스가 종료되 어 버린다. 이것은 예외 발생 시 KiUserExceptionDispatcher() 및 RtlDispatchException()를 지나 등록한 Unhandled Exception Handler를 실행하기 이전에 ProcessDebugPort를 인자로 넣는 NtQueryInformationProcess()가 호출되는데 이 때 결과가 FFFFFFFF이기 때문이다. 그래서 이 값을 0으로 설정해 주어야 디버거에서도 정상적으로 실행될 수 있다. 이 옵션을 설정하면 NtQueryInformationProcess()에 BP를 걸고 확인하는 수고로움 없이 정상적으로 디버깅을 수행할 수 있게 된다.

  간단히 설명해서 디폴트로 이 옵션이 선택되어 있지 않다. 이 경우 Unhandled Exception Filter 함수를 등록할 때 등록한 이 함수에 BP를 걸고 예외 발생 시에 Ctrl+F9를 통해 BP로 가려고 해도 가지지 않는다. 그래서 이 함수가 사용되는 경우에는 이 옵션을 선택해야 앞에서 말한 방식으로 간단하게 해당 예외 필터 함수로 갈 수 있다.

Report ignored exceptions to log

; 무시한 예외들을 로그에 기록한다.


참고] 예외

  명령어를 실행시키는 중 예외가 발생할 경우 맨 아래를 보면 "Shift+Run/Step to pass exception to the program"이라는 내용이 보인다. 이 때 Shift+F7/F8/F9를 누름으로써 예외 처리 루틴으로 넘어갈 수 있다. 이것은 원래 디버거라는 것이 디버기에 대한 제어를 갖기 때 문에 당연히 예외 발생 시에도 디버거에게 제어가 넘어와 일어나는 일인데 위의 키를 누름으 로써 예외를 다시 프로그램으로 넘겨주는 것이다. 

  이것을 리버싱하는 방법은 SEH의 경우에는 등록된 핸들러의 주소에 BP를 거는 방법이 있겠고 내부까지 들어가서 직접 분석하기 위해서는 ntdll.KiUserExceptionDispatcher()의 진입점에 BP를 걸면 예외 발생 후 커널로 넘겨진 제어가 커널로부터 복귀한 그 시점부터 바로 분석할 수 있다.

  그리고 위에 나온 예외를 무시한다는 옵션은 위에서 말했듯이 디버거로 넘어온 예외에 대해 Shift+Run/Step 등을 선택하지 않고 자동으로 다시 디버기에게 넘기겠다는 것을 의미한다.



2.5 Run trace

Size of run trace buffer

; Run trace 버퍼의 크기 설정

When tracing or animating into : [Don’t enter system DLLs] [Always trace over string commands]

; Trace나 Animate할 때 system DLLs 함수 즉, API 내부로도 들어갈 지. 그리고 REP MOVSB 같은 string command는 항상 Trace over할지.

Remember commands

; 트레이스된 명령어의 사본을 trace 버퍼에 저장한다. 프로그램이 자가 변조 코드를 사용할 때(패커나 프로텍터 처럼) 유용하다.

Remember memory

; 메모리 값의 실제 내용도 저장한다.

Remember FPU registers

; 부동소수점 레지스터도 저장한다.

Synchronize CPU and run trace

; CPU display와 Run trace를 동기화한다.

Animation delay


2.6 Hit trace

When starting hit test : [Set breakpoints on known callbacks]

; 모든 알려진 콜백 함수들에 trace BP를 설치해서, SendMessage() 같은 윈도우 API 함수들도 trace될 수 있게 한다.

When indirect or table-based jump or call is hit : [Check destination each time, Use analysis data to guess destinations]

; 간접 점프와 호출의 경우 첫번째는 신뢰할 만한 trace를 수행하며, 두번째는 추정해서 

사용하므로 속도가 더 빠르다.

When next destination is analysed as data : [Continue hit trace, Pause hit trace]

; 다음 목적지가 데이터로 여겨지는 경우 계속 할지

When next destination is outside the code section : [Continue hit trace, Pause hit trace, Trace code command by command]

; 다음 목적지가 코드 섹션이 아닐 경우 계속 할지

Keep hit trace between sessions

; hit trace가 .udd 파일에 저장되고, 애플리케이션을 다시 시작할 때 복구된다.


참고] .udd

  올리디버거가 자동으로 저장하는 프로그램과 모듈의 정보로서 다시 로드할 떄 이것의 내용을 가지고 복구한다. 이 정보들로는 라벨, 주석, 브레이크포인트, watch, 분석 데이터, condition 등이 있다.


2.7 SFX

Unpack SFX modules automatically : [Use hit trace(fast!), Use run trace (reliable)]

; 자체 압축 풀림 기능을 가진 모듈을 자동으로 언패킹한다.

Use real entry from previous run

; 이전 실행 시 찾은 EP를 사용한다. 즉 실제 EP를 알게된 경우 SFX extract를 가속화한다.

Pass exceptions to SFX extractor

; extract 하는 동안 모든 예외를 extractor에 보냄으로써 무시한다.


2.8 Just-in-time

Current just-in-time debugger

Previous just-in-time debugger

Confirm before attaching

; Just-in-time 디버거란 프로그램 실행 중 Crash 발생 시 원하는 디버거로 연결해서 디버깅을 수행하는 것이다. 즉, 여기서는 ollydbg를 Just-in-time 디버거로 설정할 수 있다.


참고]

  현재 시스템에 설정된 JIT 디버거는 다음의 레지스트리 키에서 확인할 수 있다.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug



3. Analysis

Recognition of procedures : [Strict, Fuzzy]

; 확실한 프로시저만 인식하거나, 연결된 것들은 다 프로시저로 인식한다.

Automatical module analysis : [Off, Main module, Non-system modules, All modules]

; 자동 모듈 분석을 끄거나, Main 모듈만, 시스템 외부 모듈 모두, 로드된 모든 모듈

Show predicted register values

; 디스어셈블러 패널의 comments 창에 예상되는 레지스터 값을 보여준다.

Don’t predict register contents for system DLLs

; 시스템 DLL의 경우 레지스터의 내용을 예상하지 않는다.

Show recognized ARGs and LOCALs in disassembly, Show recognized ARGs and LOCALs in comments

; EBP로 표시되던 로컬 변수와 파라미터가 각각 LOCAL.1 같은 형식과 ARG.1 같은 형식으로 변환되서 보여진다. 이것은 가독성을 매우 높여준다.

Use symbolic names for ARGs, if known

; ARG.1 대신 ARG.hCurrInstance 처럼 보여준다.


3.1 Advanced

Decode tricky code sequences

; (주로 직쩝 짜인) tricky한 코드를 해독한다.

Decode cascaded IFs as switches

; ‘IF i==1 … ELSEIF i==2 … ELSE … ‘ 같은 형식을 switch로 여긴다.

Allow arguments renaming for known functions

; 알려진 함수의 경우 argument들의 이름을 다시 짓는다.

Guess number of arguments of unknown functions

; 알려지지 않은 함수들의 경우 argument들의 수를 추정한다.

Extract arguments from mangled names

; arguments를 추측하기 위해 mangled name들에 숨겨진 정보를 이용한다.

Guess meaning of unknown arguments

; 알려지지 않은 argument들의 의미를 추정한다.

Show arguments even if their number varies

; 다른 호출에서 인식된 argument들의 수가 다르다고 해도 argument들을 보여준다.

Detect standard library functions (*.udl)

; 만들어진 .udl 파일들을 통해 표준 라이브러리 함수들을 탐색한다.

Comment constant operands of known functions : [All known, Only those marked as important]

; 알려진 함수들의 상수 오퍼랜드에 Comment 한다. 

Unknown functions preserve registers : [None, EBP, (EBX, EBP, ESI and EDI)]

; 알려지지 않은 함수가 모든 레지스터를 수정할 수 있는지, 프레임 포인터인 EBP는 보존해야 하는지, stdcall을 따라야 하는지 선택한다.

Ignore braces in .udd path

; .udd 파일을 열 때 {}로 감싸진 부분을 무시한다.


3.2 Invalid commands

Accept following potentially invalid commands

; 다음처럼 잠재적으로 유효하지 않은 명령어들을 허용한다.

Far calls and returns

Modifications of segment registers

Privileged commands

I/O commands

Commands equivalent to NOP

Shifts out of range 1..31

Superfluous prefixes

LOCK prefixes

Default selectors

Unaligned stack operations

Suspicious ESP modifications

Undocumented commands

Access to nonexisting memory

Interrupt commands



4. Search

Use predictions in search

; 검색 수행 시 레지스터들의 예상된 contents을 사용한다.

Include indirect call/jump destinations in references

; 참조 시 간접 call/jump 목적지도 포함한다. 

Add origin to the list of found commands

Preferable language of resource data



5. CPU

Gray inactive panes

; 활성화되지 않은 패널 창은 회색으로 표시

Gray register names

; 레지스터 패널 창에서 레지스터를 회색으로 표시

Show direction of jumps

; 점프 방향을 빨간색 화살표로 표시

Show jump path

; 점프 경로 표시

Show grayed path if jump is not taken

; Jump가 아닐 시(조건이 맞지 않아서) 점프 경로를 회색으로 표시

Enable SSE registers

; 레지스터 패널에 SSE 레지스터를 보인다.

Decode contents of registers : [Only for current EIP, For EIP and selected command, For any command]

; 레지스터들의 내용을 해석한다.

Hide ‘current registers’ warning

Automatic FPU/MMX/3DNow! registers : [Never, On events, Always]


5.1 More CPU

When letter key is pressed in Disassembler : [Add label, Assemble command, Add comment, Repeat last action]

; 디스어셈블러 패널에서 글자가 눌러지면 선택된 위치의 명령어에서 할 일. 즉, Assemble command라면 a키를 누르면 Space 키를 누른 것처럼 Assemble 창이 뜬다.



6. Directories

Directory for .udd files

; .udd 파일이 저장될 위치. udd는 백업 파일 형식이다. 즉 올리디버거가 분석을 하고난 후에 이 파일이 생성되며 이후 다시 실행 파일을 로드할 때 이미 분석된 정보가 있는 이 파일을 가지고 빨리 사용할 수 있다.

  올리디버거가 자동으로 분석한 내용들 외에도 사용자가 지정한 주석이나 라벨 정보도 저장된다. 이를 통해 주석이나 라벨을 달면서 분석할 수 있는 등 디스어셈블러 기능도 유용하여 IDA Pro를 이용해 분석하는 것과 비슷하게 사용될 수 있다. 문제는 이 정보가 .udd에 항상 저장이 되지만 이 파일에는 경로명 또한 저장되기 때문에 다른 환경으로 .udd 파일을 옮겨서 그곳에서 분석하고자 한다면 제대로 인식하지 못할 것이다.

Directory for standard function libraries (.udl)

; 위에서 보았듯이 만들어진 .udl 파일들이 저장된 위치.

Plugin directory

; 플러그인들이 위치하는 디렉토리 설정

Location of API help file (.hlp or .chm)



7. Errors and warnings

Warn if not administrator

; 관리자 계정으로 실행하지 않았을 때 경고창을 띄운다. 참고로 디버거의 경우 애플리케이션과 마찬가지로 관리자 권한 유무에 따라 권한이 달라진다. 애초에 관리자 권한으로 실행되는 애플리케이션에 대한 디버깅(Attach 뿐만 아니라 Open도)이 불가능하기도 하며, 애플리케이션의 루틴이 그렇다면 관리자 권한 유무에 따라 분기가 달라질 수도 있다.

Restore all errors and warnings

Restore all actions



8. Appearance

8.1 Defaults

8.2 Startup

8.3 Fonts

8.4 Colours

8.5 Code highlighting

8.6 Text-to-speech



9. Miscellaneous





III. 결론

  아직 정리할 것들이 많이 남아있으므로 틀린 내용이나 오타, 추가하고 싶은 내용이 있다면 위의 이메일 주소로 피드백을 보낼 수 있다.




IV. 참고문헌

1. OllyDbg 프로그램

2. OllyDbg 2.01 Brief Help 문서



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

윈도우의 드라이버 개발과 루트킷 그리고 AV  (0) 2017.04.23
간단한 패커 개발  (0) 2017.04.23
Yoda's Protector 분석  (0) 2017.04.23
패커들 분석  (5) 2017.04.23
윈도우의 자료형 정리  (1) 2017.04.23
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

최근에 올라온 글

최근에 달린 댓글

글 보관함