ABOUT ME

Today
Yesterday
Total
  • 18. UPack PE 헤더 상세 분석
    리버싱 엔지니어링/리버싱 핵심 원리 2022. 3. 30. 01:40

     UPack 

    • PE 패커로, PE 헤더를 독특하게 변형함
    • 특이한 PE 헤더로 각종 PE 유틸리티(PEViewer 등)들이 정상적으로 동작하지 않음.
    • 따라서, 악성 코드를 UPak으로 실행 압축하여 배포함
      • 현재는 UPack으로 실행 압축된 파일을 악성파일로 진단/삭제함

     

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

    UPack 다운로드 : https://blog.naver.com/wjdeh313/222290932166

     

    리버싱 핵심원리 : UPack

    UPack dwing이라는 사람이 만든 패커 매우 독특한 기법으로 PE 헤더를 변형한다고 한다. 검색해서 자료...

    blog.naver.com

     

    UPack

    • notepad.exe를 불러와 [Pack!] 버튼을 통해 실행 압축 : notepad_upack.exe 

     

    notepad_upack.exe PE View로 실행 

    • PE View가 PE 헤더를 제대로 읽어들이지 못하고 있음 (더 예전 버전은 비정상 종료되기도 함)

    Stud_PE 이용

    : UPack파일의 PE 헤더를 분석 가능

     

     PE 헤더 비교 

    : Hex Editor로 notepad.exe 와 notepad_upack의 헤더 부분을 비교

     

     notepad.exe(원본)의 PE의 헤더 

    • 전형적인 PE 헤더

     

     notepad_upack.exe(실행 압축)의 PE의 헤더 

    • MZ, PE 시그니처가 매우 가깝게 붙어있음
    • DOS Stub가 없어짐
      • 원본에서의 DOS Stub

    notepad.exe DOS stub

    • 코드도 보이는 듯함 (GetProcAddress)

     UPack의 PE 헤더 분석 

     헤더 겹쳐쓰기 

    : MZ헤더와 PE 헤더를 교묘하게 겹쳐쓰는 것 

    • 헤더를 겹쳐씀으로 해서 헤더 공간을 절약할 수 있음
    • 복잡성을 증가시켜 분석을 어렵게 함(ex. PE 관련툴)
    • Stub_PE를 통한 MZ 헤더 분석(Headers > Basic HEADERS tree view in hexeditor)

    • MZ 헤더(IMAGE_DOS_HEADER)에서 중요한 2개의 멤버
    (offset  0) e_magic : Magic number = 45DA('MZ')
    (offset 3C) e_lfanew : File address of new exe header​
    • e_Ifanew의 값에 따라 IMAGE_NT_HEADERS의 시작위치가 결정됨
      • 정상적인 프로그램에서의 e_Ifanew 값 = MZ 헤더크기(40) + DOS Stub 크기(가변 : VC++의 경우 보통 A0) = E0
      • UPack에서는 e_Ifanew 값이 10, MZ 헤더와 PE 헤더의 겹쳐쓰기가 가능 

     

     IMAGE_FILE_HEADER.SizeOfOptionalHeader의 값 변경 

    : 이 값을 조작해 헤더 안에 디코딩 코드 삽입

     

    • SizeOfOptionalHeader 값 의미 : IMAGE_OPTIONAL_HEADER 구조체의 크기(E0)

             → UPack은 아래 사진과 같이 148로 변경함

    SizeOfOptionalHeader

    • IMAGE_OPTIONAL_HEADER 구조체의 크기는 '구조체'이기 때문에 E0로 크기가 결정되었음. 하지만 왜 크기를 따로 입력하게 했을까?
      • 의도 : PE 파일 형태에 따라 각각 다른 IMAGE_OPTIONAL_HEADER 형태의 구조체로 바꿀 수 있도록 설계
      • ex) 64비트용 PE32+의 IMAGE_OPTIONAL_HEADER 구조체 크기 : F0

     

    • SizeOfOptionalHeader은 섹션 헤더(IMAGE_SECTION_HEADER)의 시작 옵셋도 결정함
      • IMAGE_OPTIONAL_HEADER 시작 옵셋에 SizeOfOptionalHeader 값을 더한 위치(옵셋)부터 IMAGE_SECTION_HEADER가 나타남
      • UPack은 SizeOfOptionalHeader 값을 148로 설정하여 정상적인 값(E0, F0) 보다 크게 설정
    • UPack이 SizeOfOptionalHeader 값을 바꾼 이유?
      • 값을 늘려 IMAGE_OPTIONAL_HEADER와 IMAGE_SECTION_HEADER 사이에 공간 확보 가능
      • 따라서, 해당 영역에 디코딩 코드를 추가

    디코딩 영역

     

     IMAGE_0PTIONAL_HEADER.NumberOfRvaAndSizes 값 변경 

    : 헤더에 자신의 코드를 삽입하기 위한 목적

    • NumberOfRvaAndSizes 값의 의미 : IMAGE_DATA_DIRECTORY 구조체 배열의 원소 개수
      • 정상적인 파일에서의 값 : 10
      • UPack에서의 값 : A 

    NumberOfRvaAndSizes

    • UPack의 경우 IMAGE_DATA_DIRECTORY 구조체 배열의 뒤쪽 6개 원소들은 무시된다 (10 - A = 6)
      • 무시된 영역에 자신의 코드를 덮어씀

     

     IMAGE_SECTION_HEADER 

    : 사용되지 않는 항목에 자신의 데이터를 기록

     

    • SizeOfOptionalHeader 값 : 148
      • IMAGE_SECTION_HEADER = IMAGE_OPTIONAL_HEADER 시작 옵셋(28) + SizeOfOptionalHeader(148) = 170 

    • 파일 옵셋 1B0 위치에 있는 offser to relocations의 값 0100739D는 원본 notepad.exe의 EP 값

     

     섹션 겹쳐쓰기 

    • Stud_PE > Sections

    • 첫번째 섹션과 세번째 파일 시작 옵셋이(RawOffset) 10으로 되어있음
      • 옵셋 10은 헤더 영역인데 UPack에서는 이곳부터 섹션이 시작됨
    • 첫번째 섹션과 세번째 섹션의 파일 시작 옵셋(RawOffset)과 파일 크기(RawSize)가 완전히 동일. 하지만, 섹션의 메모리 시작 RVA(VirtualOffset) 항목과 메모리 크기값(VirtualSize)이 다름

    → UPack은 PE 헤더, 첫번째 섹션, 세번째 섹션이 겹쳐 있다. 

     

    겹쳐쓰기 특징

    • 섹션헤더(IMAGE_SECTION_HEADER)에 정의된 값에 의해 PE로더는 파일 옵셋 0~01FF 영역을 다른 메모리 위치(헤더, 첫번째 섹션, 세번째 섹션)에 각각 매핑함
    • 두번째 섹션(2nd section)영역의 크기(AE28)은 원본 파일(notepad.exe)가 압축되어 있음.
    • 메모리에서의 첫번째 섹션
      • 섹션의 메모리 크기 : 14000(= 원본 파일의 Size Of Image)
      • 즉, 두 번째 섹션에 압축된 파일 이미지를 첫 번째 섹션에 그대로 압축해제
        • 원본에서는 3개의 섹션으로 나누어져있음

    압축 해제된 첫 번째 섹션

    → 두 번째 섹션 영역에 압축된 notepad가 들어 있고, 압축이 풀리며 첫 번째 섹션 영역에 기록

     

     RVA to RAW 

    일반적인 RVA → RAW 변환 방법

    RAW = RVA - VirtualAddress + PointerToRawData

     

    위 공식을 이용한 EP의 파일 옵셋(RAW) 계산

    AddressOfEnrtyPoint

    (섹션 겹쳐쓰기 이미지 참고)

    • UPack EP : RVA 1018
    • RAW = 1018 - 1000 +10 =28 (t)

    RAW hex editor를 통해 살펴보기

    • RAW 28 영역은 코드가 아니라 "LoadLibraryA" 문자열 영역
    • UPack의 트릭 : PointerToRawData
      • PointerToRawData 값은 FileAlignment의 배수가 되어야함.
      • UPack FileAlignment는 200, 따라서 PointerToRawDat 값은 0, 200, 400, 600 등의 값을 가져야함. 
    • 정상적인 RVA → RAW 변환 : RAW = 1018 - 1000 + 0 = 18
      • PointerToRawData = 0
    • 확인해보기

    hex editor
    OllyDbg

     

     Import Table 

    Directory Table에서 Import Directory Table 주소 얻기

    • 앞 4바이트가 Import Table의 주소(RVA), 뒤 4바이트가 Import Table의 크기(size)
    • Import Table의 RVA : 271EE

    RVA → RAW 변환 : 271EE

    • 메모리 주소 271EE는 메모리에서 세 번째 섹션 영역
    • 271EE - 27000 + 0 = 1EE
      • RawOffset 0으로 강제 변환

     

    Hex Editor로 살펴보기

    • IMAGE_IMPORT_DESCRIPTION 구조체
    typedef struct _IMAGE_IMPORT_DESCRIPTOR {
        union {
            DWORD   Characteristics;            
            DWORD   OriginalFirstThunk;       // INT
        };
    
        DWORD   TimeDateStamp;
        DWORD   ForwarderChain; 
        DWORD   Name;                   
        DWORD   FirstThunk;                   // IAT
    } IMAGE_IMPORT_DESCRIPTOR;
    • Import Table은 IMAGE_IMPORT_DESCRIPTION 구조체 배열로 이루어지고 마지막은 NULL 구조체로 끝나야함

     

    • 선택된 영역 : IMAGE_IMPORT_DESCRIPTION
    • 201 뒤는 두번째 구조체도, NULL 구조체도 아님

     IAT 

    • IMAGE_IMPORT_DESCRIPTOR 구조체 중요 멤버
    offset member RVA
    1EE OriginalFirstThunk(INT) 0
    1FA Name 2
    1FE FirstThunk(IAT) 11E8
    • Name의 RVA 값 = 2, 헤더 영역(헤더 영역은 RVA와 RAW 값이 같음)

    • "KERNEL32.DLL" 문자열
      • DOS 헤더의 사용되지 않는 영역이라서 이곳에 Import DLL 이름을 씀
    • INT를 쫓아가면 API 문자열이 나오지만, 0일 때는 IAT값을 따라가도 됨
      • 11E8 : 첫 번째 섹션 영역
      • 11E8 - 1000 + 0 = 1E8 

     

    IAT

    • 2개의 API : RVA 28, BE
    • RVA 위치에 Import 함수 [ordinal + 이름 문자열]

    • 'LoadLibraryA','GerProcAddress' API를 임포트

    댓글

Designed by Tistory.