ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [리버싱 핵심원리 study] 18장 UPack PE 헤더 상세 분석
    Reverse Engineering 2020. 12. 30. 21:38

    1. UPack 설명

    UPack은 중국의 dwing이라는 사람이 만든 PE 패커로, UPack의 등장으로 많은 PE 유틸리티들이 정상적으로 동작하지 않았다. 이러한 특징을 이용해 많은 악성 코드 제작자들이 자신의 코드를 UPack으로 실행 압축하여 배포하였고, 현재 대부분의 AV 제품들은 UPack으로 실행 압축된 파일들을 악성코드로 분류한다. (이번 실습을 진행하면서 Windows Defender에 지속적으로 탐지되어 매우 애먹었다.)

     

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

    UPack의 윈도우형 최신 버전인 WinUPack 0.39를 이용하였다.

    UPack으로 notepad.exe 압축하기

    PEView로 압축된 notepad.exe를 살펴보자.

    notepad.exe

    위와 같이 헤더 부분을 정확하게 읽어내지 못하는 것을 확인할 수 있다. 예전 버전의 PEView는 아예 비정상 종료되었다고 한다.

     

    3. Stud_PE 이용

    보다 강력한 기능을 제공하는 Stud_PE 유틸리티를 이용해보자.

     

    CGSoftLabs Download Script Page

    Download Panel (here you'll find the latest CGSoftLabs releases) File Name & Version File Size Get It Buy Date eXPressor 1.8.0.1 2.9 MB 14 jan 2010 Stud_PE 2.6.1.0 1.14 MB free 2 jun 2012 CableMon 1.9.0.5 1.7 MB free 6 sep 2011 eZWeather 1.8.0.7 824 KB fre

    www.cgsoftlabs.ro

    위 링크에서 파일을 다운로드할 수 있다.

    Stud_PE

    Stud_PE를 실행시키고 notepad.exe를 open 하면 위와 같은 화면을 볼 수 있다. PEView 보다는 복잡해 보이지만 PE 헤더를 읽어내는 모습을 확인할 수 있다.

     

    4. PE 헤더 비교

    HxD로 압축되기 전후의 notepad.exe를 살펴보자.

    실행압축 이전 notepad.exe
    실행압축 이후 notepad.exe

    실행 압축 이후 MZ, PE signature가 매우 가깝게 붙어 있고 Dos Stub도 없어지고 무언가 달라진 것으로 보인다.

     

    5. UPack의 PE 헤더 분석

    1) 헤더 겹쳐쓰기

    다른 패커에서도 많이 사용하는 기법으로, IMAGE_DOS_HEADER와 IMAGE_NT_HEADERS를 교묘하게 겹쳐 쓰는 방식이다. 헤더를 겹쳐 씀으로써 보다 공간을 절약할 수 있다. Stud_PE를 이용하여 MZ 헤더를 살펴보자.

    헤더 겹쳐쓰기

    00 ~ 3F 영역은 IMAGE_DOS_HEADER에 해당하고 중요 멤버는 e_magic과 e_lfanew가 있다. e_lfanew는 IMAGE_NT_HEADERS의 시작 위치를 가리키는데 여기서 00000010으로 특이하게 e_lfanew 보다 앞을 가리키는 구조이다.

     

    2) IMAGE_FILE_HEADER.SizeOfOptionalHeader

    해당 멤버는 IMAGE_OPTIONAL_HEADER 구조체의 크기를 나타내는 값이다. 기본적으로 32 bit용 IMAGE_OPTIONAL_HEADER는 E0로 크기가 고정되어 있는데 148로 바꾸어 디코딩 코드를 삽입한다.

    SizeOfOptionalHeader

    이렇게 함으로써 IMAGE_OPTIONAL_HEADER의 끝부터 IMAGE_SECTION_HEADER의 시작 전까지의 영역에 디버깅 코드를 삽입한다. HxD와 OllyDbg로 해당 영역을 확인해보자.

    HxD
    OllyDbg

    위와 같이 헤더 부분에 코드가 들어간 것을 확인할 수 있다.

     

    3) IMAGE_OPTIONAL_HEADER.NumberOfRvaAndSizes

    해당 멤버는 IMAGE_OPTIONAL_HEADER의 마지막 부분에 있는 IMAGE_DATA_DIRECTORY 구조체 배열의 원소 개수를 나타낸다. 본래는 10(hex)개이지만, UPack에서는 A개로 세팅한다.

    NumberOfRvaAndSizes

    이렇게 설정함으로써 A번째 이후의 디렉토리에는 UPack의 코드로 덮이게 된다.

     

    4) IMAGE_SECTION_HEADER

    IMAGE_SECTION_HEADER 구조체에서 프로그램 실행에 사용되지 않는 항목들에 UPack의 데이터를 기록한다.

     

    5) 섹션 겹쳐쓰기

    UPack의 특징 중 하나로, 섹션과 헤더를 겹쳐 쓰는 특징이다. Stud_PE를 이용해 UPack의 IMAGE_SECTION_HEADER를 살펴보자.

    Sections

    3개의 섹션이 있는데 눈 여겨 볼 점은 첫 번째 섹션과 세 번째 섹션이다. 희한하게도 두 섹션의 RawSize와 RawOffset이 같다. 그러나 두 섹션의 메모리 상에서의 RVA와 size는 다른 것을 확인할 수 있다. 그림으로 도식화해보면 다음과 같다.

    header와 section을 겹쳐쓰기

    두 번째 섹션에서 RawSize가 헤더에 비해 크게 나타나는데 이는 notepad의 압축된 원본으로 압축이 풀리면서 해당 내용이 첫 번째 섹션에 기록되는 구조다.

     

    6) RVA to RAW

    기존의 RVA to RAW를 이용해서 UPack을 접근하면 오류가 난다. (잘못된 메모리 참조 발생) 일반적인 비례식에 의해 접근하게 되면 RAW가 엉뚱한 곳을 가리키게 되는데 이는 RAW = RVA - VirtualAddress + PointerToRawData에서 PointerToRawData에 문제가 있기 때문이다. UPack의 FileAlignment는 200으로 PointerToRawData는 200의 배수가 되어야 한다.

     

    7) Import Table(IMAGE_IMPORT_DESCRIPTOR array)

    UPack에서는 Import Table 또한 재미있는 방식으로 구성되어 있다. Stud_PE에서 Import Directory Table을 살펴보자.

    Import Table 주소

    위의 표시된 부분에서 앞의 4바이트는 Import Table의 RVA를, 뒤의 4바이트는 Size를 나타낸다. 271EE를 RVA to RAW를 통해 계산하면(file alignment 주의) 1EE가 된다.

    파일 offset 1EE

    위 부분이 바로 첫 번째 IMAGE_IMPORT_DESCRIPTOR 구조체이다. 두 번째 부터는 NULL 구조체도 아니고 그렇다고 내용이 있는 구조체도 아니다. 얼핏 보면 PE 스펙에 어긋나는 듯한 이 구조는, 200 offset을 기준으로 헤더와 섹션으로 나뉘므로 빨간 선을 기준으로 아래 부분은 메모리의 header 부분에 올라가지 않고 남은 헤더 영역이 0으로 채워진다. 파일로 볼 때는 깨진 것 같지만, 메모리에서는 정확하게 Import Table이 나타난다.

     

    8) IAT(Import Address Table)

    UPack이 어떤 DLL에서 어떤 API를 임포트하는지 실제로 IAT를 따라가 보자. 위 캡처를 기준으로 주요 멤버를 나타내면 다음과 같다. (각 값은 RVA)

     

    OriginalFirstThunk(INT) : 0

    Name : 2

    FirstThunk(IAT) : 11E8

     

    우선 Name을 통해 2 offset을 찾아가보자.

    offset 2

    위와 같이 kernel32.dll로 부터 임포트 하는 것을 확인할 수 있다. 이제 FirstThunk를 따라가 어떤 API가 있는지 확인해보자. (RVA to RAW를 이용하여 변환하면 1E8 offset)

     

    offset 1E8

    두 주소가 나오고 마지막은 NULL로 끝나는 것을 확인할 수 있다. RVA가 28, BE이므로 RAW는 28, BE이다.

    API import

    위와 같이 LoadLibraryA와 GetProcAddress API를 임포트 하는 것을 확인할 수 있다.

    반응형

    댓글

Designed by Tistory.