2016. 1. 17. 20:58

Chap 18 UPack PE 헤더 상세 분석

1. UPack 설명 

   UPack은 중국의 dwing이라는 사람이 만든 PE 패커 입니다.

Upack039.7z

    참고: UPack을 상세 분석하려면 시스템에 동작 중인 Anti-Virus 제품의 ‘실시간 감시 ’ 기능을 꺼놓고 하기 바랍니다(대부분의 AV 제품에서 UPack을진단/삭제해 버립니다). 향후분석이 다끝나면 다시 실시간 감시 기능을 커기 바랍니다.

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

  먼저 적절한 곳에 upack. exe와 notepad. exe를 복사합니다. 

  커밴드창에서 그림와같이 명령을 내립니다(원래 몇 가지 실행 옵션이 있지만 그냥 default옵션으로도 충분합니다).

  *UPack은 원본 파일 자체를 실행 압축하며 따로 백업하지 않으니,중요 파일을 실행 압축하기 전에는 꼭 미리 백업해두기 바랍니다.

3. Stud_PE 이용

   최강의 PE Viewer 유틸리티 인 PEView가 정상 동작하지 않으니 비슷한 다른 유틸리티 인 Stud PE를 소개합니다.(필자가 이 책을 썼을 때 이렇게 설명했지만 지금 PE Viewer도 사용할 수 있습니다)

Stud_PE.zip

   PE View보다는좀 더 복잡한화면 구성이지만나름장점이 많은유틸리티입니다(UPack도 잘 보이구요).

    그림은 Stud_PE의 실행 화면입니다.

4. PE 헤더 비교

   Hex Editor로 두 파일 (notepad.exe ,notepad_upack. exe)의 헤더 부분을 비교해서 보겠습니다.

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

    평범한 PE 헤더의 모습입니다. lMAGE_DOS_HEADER, DOS Stub, lMAGE_NT_HEADERS,lMAGE_SECTION_HEADER 순으로 전형 적 인 PE 헤더 를 보여주고 있습니다.

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

    MZ, PE 시그니처가 매우 가깝게 붙어있고,DOS Stub은 아예 없어졌고,여러 문자열이 보이고,중간에 코드가 존재하는 것 같기도 하고말이죠.한마디로 수상한 점투성이 입니다.

5. UPack의 PE 헤더 분석

    5.1 헤더 겹쳐쓰기

        헤더(IMAGE_DOS_HEADER)와 PE 헤더(IMAGE_NT HEADERS)를 교묘하게 겹쳐쓰는 것입니다.

        Stud_PE를 이용해서 MZ 헤더 를 살펴보겠습니다.‘Headers’ 탭의 [Basic HEADERS tree view in hexeditor] 버튼을 누릅니다.

(제 자신의 캡처,환경은:win7 32bit)

(필자의 캡처,환경은:win xp)

          MZ 헤더 (IMAGE_DOS_HEADER)에서 아래 2가지 멤버가 중요합니다.

          그외 나머지는 크게 중요하지 않습니다(프로그램 실행에 아무 의미가 없습니다).

        문제는 PE File Format의 스펙 에 따라서 lMAGE_NT_HEADERS의 시작위치가‘가변적’이라는 것입니다.즉 e_lfanew의 값에 따라서 lMAGE_NT_HEADERS의 시작 위치가 결정됩니다. 보통 정상적인 프로그램에서는 e_lfanew 값은(빌드 환경마다 틀리긴 하지만) 아래와 같습니.

        UPack에서는 e_lfanew 값이 10입니다. PE 스펙에 어긋나진 않았습니다. 그냥 스펙 자체의 허술함을 이용한 것이지요. 이런 식으로 MZ 헤더와 PE 헤더의 겹쳐쓰기가 가능해집니다.

    5.2 IMAGE_FILE_HEADER.SizeOfOptionaIHeader

        lMAGE_FILE_HEADER.SizeOfOptionalHeader의 값을 변경해서 헤더 안에 디코딩 코드를 삽입할 수 있습니다.

        값의 의미는 PE 헤더에서 바로 뒤따르는 lMAGE_OPTIONAL_HEADER 구조체의 크기 (E0) 입니다. UPack은 이 값을 그림와 같이 148로 변경합니다(아래 그림에서 박스로 표시된 부분).

        UPack의 특징은 기본적으로 PE 헤더를 파배기처럼 꼬아놓고 헤더 안에 디코딩에 필요한 코드를 적절히 끼워 넣는 것입니다. SizeOfOptionalHeader 값을 늘리면 lMAGE_OPTIONAL_HEADER와 lMAGE_SECTION_HEADER 사이에 추가적인 공간을 확보할 수 있습니다.UPack은 바로 이 영역에 디코딩 코드를 추가합니다. PE 헤더에 대한 일반상식을 뛰어넘는 교묘한 방법입니다.

         실제로 문제의 영역을 살펴보겠습니다. lMAGE_OPTIONAL_HEADER의 끝은 D7이고,lMAGE_SECTION_HEADER 시작은 170입니다.이 사이 영역을 Hex Editor로 보면 그림과 같습니다.

  5.3 IMAGE_OPTIONAL_HEADER.NumberOfRvaAndSizes

        lMAGE_OPTIONAL_HEADER.NumberDfRvaAndSizes 값을 변경해서 자신의 코드를 삽입할 수 있습니다.

        이 값의 의미는 바로 뒤에 이어지는 lMAGE_DATA_DIRECTORY 구조체 배열의 원소 개수를 나타납니다. 정상적인 파일에서는 lMAGE_DATA_DIRECTORY 배열의 원소 개수는 10개이지만,UPack에서는 A개로 변경됩니다(그림의 박스 영역 참고).

        lMAGE_DATA_DIRECTORY 구조체 배열의 각 항목들을 표에 설명하였습니다.

        굵은 이댈릭제로 쓰여진 항목들은 잘못 변경했을 때 실행에러가 발생하는 항목들입니다.

       UPack은 lMAGE_OPTIONAL_HEADER. NumberOfRvaAndSizes 값을 A로 변경하여 LOAD_CONFIG 항목(파일 옵셋 D8 이후)부터는 시용하지 않습니다. 그리고 바로 그 무시된 lMAGE_DATA_DlRECTORY 영역에 자신의 코드를 덮어써버렸습니다. 정말 UPack은 헤더의 1바이트까지 알뜰하게 사용하는군요.

       hex editor로 lMAGE_DATA_DlRECTORY 구조체 배열 영역을 보도록 하겠습니다.

        그림에서 흐리게 표시된 부분이 정상파일의 IMAGE_DATA_DlRECTORY 구조체 배열 영역이고,그 밑으로 진하게 표시된 부분이 UPack에서 무시되는 부분입니다(D8~107 영역 = LOAD_CONFIG Directory 이후). 무시 되는 영역을 디버거로 확인하면 UPack 자체의 디코딩 볼 수 있습니다.

      참고로 NumberOfRvaAndSizes 값이 변경되면 OllyDbg에서 파일을 열 때 그림와 같은 에러 메시지가 나타납니다.

      OllyDbg의 PE 파일 검증 과정에서 NumberOfRvaAndSizes 값이 10인지 체크하는데,중요한 메시지는 아니므로 무시해도 됩니다. 별도의 Plugln을 사용하면 아예 없앨 수도 있습니다. 참고하기 바랍니다.

  5.4 IMAGE SECTION HEADER

       lMAGE_SECTION_HEADER 구조체에서 프로그램 실행에 사용되지 않는 항목들에게 UPack 자신의 데이터를 기록합니다.

       Hex Editor로 lMAGE_SECTION_HEADER 구조체를 보겠습니다(용셋 170~lE7 영역).

       그림에서 보이는 영역을 lMAGE_SECTION_HEADER 구조체에 맞게 보기 좋게 뽑아보면 다음과 같습니다.

       위에 박스로 표시된 구조체 멤버들은 프로그램 실행에 아무런 의미 없는 멤버들입니다. 일례로 파일 옵셋 1B0 위치에 있는 offset to relocations의 값 0100739D는 원본 notepad.exe의 EP(Entry Point) 값입니다.

   5.5 섹션 겹쳐쓰기

        UPack의 주요 특징 중 하나가 바로 섹션과 헤더를 마구 겹쳐쓰는 것입니다.

        간략한 보기 를 제공하는 Stud_PE를 이용해서 UPack의 lMAGE_SECTION_HEADER를 살펴보겠습니다. Stud PE의 ‘Sections’탬을 선택합니다.

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

        숫자만 봐서는 이게 무슨 의미인지 잘 와닿지 않습니다.이해를 돕기 위해서 그림을 살펴봅시다.

        왼쪽은 파일에서의 섹션 정보를,오른쪽은 메모리에서의 섹션 정보를보여 주고 있습니다.

        파일의 헤더(첫째/셋째 섹션) 영역의 크기는 200 입니다. 사실 매우 작은 크기입니다. 반면 두 번째 섹션(2nd Section) 영역의 크기 (AE28)는 파일의 대부분을 차지할 정도로 큽니다. 바로 이곳에 원본 파일(notepad.exe)이 압축되어 있습니다.

        또 하나 주목해야 하는 부분은 메모리에서의 첫 번째 섹션 영역입니다. 섹션의메모리 크기는 14000 입니다. 이는 원본 파일 (notepad.exe)의 Size of Image와 같은 값입니다. 즉 두 번째 섹션에 압축된 파일 이미지를 첫 번째 섹션에 (notepad의 메모리 이미지) 그대로 압축해제하는 것입니다.

        압축이 해제된 첫 번째 섹션은 그림과 같습니다.

        디시 한 번 정리하변 메모리 두 번째 섹션 영역에 압축된 notepad가들어 있고,압축이 풀리면서 첫 번째 섹션 영역에 기록됩니다. 중요한 건 notepad.exe(원본파일)의 메모리 이미지가 통째로 풀리기 때문에 프로그램이 정상적으로 실행될 수 있습니다(주소가 정확히 일치하게 됩니다).

   5.6 RVA to RAW

        RVA → RAW 변환방법은 아래와 같습니다.

        UPack의 EP는RVA 1018입니다.

        RVA 1018은 1st Section에 존재합니다. 따라서 공식에 그대로 대입하면 아래와 같습니다.

        RAW28 영역을 hex editor로 살펴보겠습니다.

       RAW 28 영역은 코드가 아니라 (ordinal:0l0B) “LoadLibrary A" 문자열 영역입니다.

         일반적으로 섹션 시작의 파일 옵셋을 가리키는 PointerToRawData 값은 FileAlignment의 배수가 되어야 합니다. UPack의 FileAlignment는 200이므로 PointerToRawData 값은 0, 200, 400, 600 등의 값을 가져야 합니다. 따라서 PE 로더 는 첫 번째 섹션의 PointerToRawData(10)가 FileAlignment(200)의 배수가 아니므로 강제로 배수에 맞춰서 인식합니다(이 경우는 0).

        정상적인 RVA → RAW 변환은 아래와 같습니다.

        디버거로 해당 영역의 코드를 살펴보겠습니다.

   5.7 lmport Table(lMAGEJMPORT_DESCRIPTOR array)

        UPack의 Import Table은 매우 특이하게 구성되어 있습니다.

        Hex Editor를 이용해서 lMAGE_IMPORT_DESCRIPTOR 구조체를 따라가보겠습니다.

        여기가 바로 UPack의 섹션을 이용한 트릭 이 숨겨진 곳입니다.

        PE 스펙에 따르면 Import Table은 lMAGE_IMPORT_DESCRIPTOR 구조체 배열로 이루어지고 마지막은 NULL 구조체로 끝나야 합니다.그림를 보면 선택된 영역이 Import Table을 나타내는lMAGE_IMPORT_DESCRIPTOR 구조제 배열입니다. 1EE~201 옵셋까지 첫 번째 구조체이며, 그 뒤는 두 번째 구조제도 아니고,그렇다고(Import Table의 끝을 나타내는) NULL 구조체도 아닙나다.

        얼핏 보면 이는 분명 PE 스펙에 어긋난 듯이 보입니다. 하지만 그림 18.24의 200 옵셋 위에 있는 굵은선을 주목하기 바랍나다. 이 선은 파일에서 세 번째 섹션의 끝을 의미합니다. 따라서 실행할 때 200 옵셋 이하는 세 번째 섹션 메모리에 매핑되지 않습니다. 그림을 봅시다.

        세 번째 섹션이 메모리에 로딩될 때 파일 옵셋 0~1FF 영역이 메모리 주소27000~271FF에 매핑되고 (세 번째 섹션의 남은 메모리 영역인) 27200~28000 영역은NULL로 채워집니다. 똑같은 영역을 디버거로 확인해보겠습니다.

        다시 PE 스펙의 Import Table 조건으로 돌아가서 01027202 주소 이후부터 NULL 구조체가 나타난다고 본다면 PE 스펙에 어긋나지 않는 셈입니다. 이것이 바로 섹션을 이용한 UPack의 트릭입니다. 파일로 볼 때는 Import Table이 깨진 것처럼 보이지만,실제 메모리에서는 Import Table이 정확히 나타나는것입니다.

   5.8 IAT(lmport Address Table)

        UPack이 어떤 DLL에서 어떤 API를 입포트하는지 실제로 IAT를 따라가서 확인해보겠습니다.

        먼저 Name의 RVA 값은 2 이고, 이는 Header 영역에 속해 있습니다(첫 번째 섹션이 RVA 1000에서 시작하기 때문입니다).헤더 영역에서는 그냥 RVA와 RAW 값이 같으므로 Hex Editor로 파일 옵셋(RAW) 2를 살펴보겠습니다.

        “KERNEL32.DLL" 문자열이 보이는군요.

        IAT 값 11E8은 첫 번째 섹션 영역이므로 IAT의 파일 옵셋은 1E8입니다.

        그림의 박스로 표시된 영역은 IAT이면서 동시에 INT 역할도 하고 있습니다. 즉 이곳은 Name Pointer(RVA) 배열이고 배열의 끝은 NULL입니다. 또 2개의 API를 임포트하는 것을 알 수 있습니다. 각각 RVA 28과 BE입니다.

그림를보면 ‘LoadLibraryA’와 ‘GetProcAddress’ API를 임포트한다는 것을 알 수 있습니다.

Chap 19 UPack 디버깅 -OEP 찾기

   1. OllyDbg 실행 에러

       UPack은 lMAGE_OPTIONAL_HEADER에서 NumberOfRvaAndSizes 값을 A로 변경 (기본 값 10)하기 때문에 OllyDbg는 EP로 가지 못하고, 그림와 같이 ntdll.dll 영역에서 멈춰버립니다.


      이는 어디까지나 OllyDbg의 버그(혹은 엄격한 PE 체크) 때문에 이런 현상이 발생한것이므로 강제로 EP를 설정해줘야 합니다.

        그림을 보면 ImageBase가 0100000이고,EP의 RVA 값이 1018 입니다. 따라서 EP의 VA 값은 01001018입니다.OllyDbg의 Code 창에서 01001018로 이동 한 후 ‘New origin here' 명령을 이용하여 강제로 EIP를 변경합니다.

    2. 디코딩 루프

        모든 패커에는 디코딩 루프(Decoding Loop)가 존재합니다.이러한 디코딩 루프를 디버깅할 때는 조건 분기를 적절히 건너뒤어서 루프를 탈출해야 합니다. 경우에 따라서는 한눈에 루프가 파악되지 않을 수도 있습니다.레지스터를 잘 보면서 어떤주소에 값을쓰고 있는지 잘살펴야 합니다(사실 이건 많은 경험이 필요한 부분입니다).

        처음두 명령은 010011B0 주소에서 4바이트를 읽어서 EAX에 저장하는 명령입니다.EAX는 0100739D 값을 가지는데, 이는 원본 notepad의 OEP입니다.사실이 값이 OEP 인 것을 알고 있다면, 하드웨어 BP를 설치한 후 설행 [F9] 하면 정확히 OEP에서 멈줍니다.

        조금 진행하다 보면 그림과 같은 함수 호출 코드가 나타납니다.

        이때 ESI 값은 0101FCCB 이고, 이게 바로 decode() 함수의 주소입니다. 앞으로 이 함수가 반복적으로 호출될 것입니다. decode() 함수(101FCCB)를 살짝 살펴보겠습니다.

        Steplnto[F7]명령으로 트레이성을 계속 하면 그림과 같은 코드를 만나게 됩니다.

       0101FE57과 0101FE5D 주소에는 EDI 값이 가리키 는 곳에 뭔기를 쓰는 명령어(MOVS, STOS)가 있습니다. 이때 EDI 값은 첫 번째 섹션 내의 주소를 가리 겁니다.즉 압축을 해제한 후 실제 메모리에 쓰는 명령어들입니다.0101FE5E와 0101FE61 주소에는 CMP/JB 명령어를 통해서 EDI 값이 01014B5A가 될 때까지 계속 루프를 돌게 됩니다([ESI]+341 = 01014B5A). 0101FE61 주소가 바로 디코딩 루프의 끝부분입니다. 실제로 이 루프를 반복해서 트레이싱하면 EDI가 가리키는 주소에 어떤 값들이 쓰여지는 것을 볼 수 있습니다.

   3. IAT 세팅

        UPack이 임포트히는 2개 의 함수 LoadLibrary A와 GetProcAddress를 이용하여 루프를 돌면서 원본 notepad의 IAT를 구성합니다.이 과정 이 끝나면 0101FEAF 주소의 RETN 명령어에 의해서 드디어 OEP로 갑니다.

chap 20 인라인 패치실습

   1. 인라인 때치

        인라인 코드 패치(Inline Code Patch) 혹은 줄여서 인라인 패치 (Inline Patch)라고 하는 기법은 원하는 코드를 직접 수정하기 어려울때 간단히 코드케이브(Code Cave)라고 하는 패치 코드를 삽입한 후 실행해 프로그램을 패치시키는 기법입니다. 주로 대상 프로그램이 실행 압축(혹은암호화)되어 있어서 파일을직접 수정하기 어려운 경우 많이 시용되는 기법입니다. 자세한 설명을 위해 그림을 살펴보겠습니다.

    2. 실습 - Patchme

        실행하면 위그림와 같이 메시지 박스가 나타납니다. 메시지를 읽어보니 표시문자열을 변경하라고 합니다. [확인] 버튼을 누르면 밑그림과 같은 다이얼로그가 표시됩니다.

    3. 디버깅 - 코드 흐름 살펴보기

       시행화면에서 보이는 메시지를 찾기 위해서 마우스 우측 메뉴의‘Search for All referenced text strings’를 선택합니다.

      모든 문자열이 암호화되어 있기 때문에 이 상태로는 원히는 문자열을 찾을 수 없습니다.401001 주소의 CALL 명령의 함수(4010E9)를 따라들어가서 조금 진행하다 보면 그림과같은 코드를 만나게 됩니다.

       이 코드가 바로 복호화 루프입니다.4010A3 주소의 XOR BYTE PTR DS: [EBXl ,44 명령을 사용하여 특정 영역 (4010F5~401248)을 XOR 명령으로 복호화합니다.4010C8 주소의 XOR 명령에 의해서 401007~401085 영역이 복호화되고, 다시 4010DB 주소의 XOR 명령에 의해서 4010F5~401248 영역이 복호화됩니다.복호화가 완료한 후에 함수 (401039)로 따라 들어가면 그림과 같은 코드가 나타납니다.

        401046 주소에 있는 Checksum 계산 루프입니다.루프를 종료하면 EDX 례지스터에는 특정한 Checksum 값이 저장됩니다.이 Checksum 계산 영역은 위에서 설명했던 이중으로 암호화되어 있는 영역입니다. 바로 이 영역 에 우리가 패치하려는 문자열이 존재할 것으로 예상됩니다.

      이부분은 OEP 코드 입니다.내용은 다이얼로그를 실행시키는 것 입니다. 40123E 주소의 CALL user32.DialogBoxParamA()명령을 실행하면 다이얼로그가 나타납니다. 아래는 DialogBoxParam( ) API의 정의입니다.

       DialogBoxParam() API 의 4 번째 lpDialogFunc 파라미터가 Dialog Box Procedure 주소(4010F5)입니다.

       박스로 표시된 부분이 우리가 패치해야 할 문자열들 입니다.

   4. 코드구조

대략적인 코드 흐름은 다음과 같습니다.

[EP Code]는 단순히 [Decoding Code]를 호출히는 역할만 하며 , 실제 [Decoding Code] 에서 디코딩 작업이 이루어집니다. [B] - [A] - [B] 순서로 디코딩 (XOR)을 하고 암호가 해제된 [A] 영역 코드를 실행합니다. [A] 영역 코드 내에서 [B] 영역의 Checksum을구하여 [B] 영역의 변경 여부를판별합니다.그런다음 [C] 영역을 디코딩 (XOR)한 디음 마지막으로 OEP(0040121E)로 점프합니다.

   5. 인라인 때치 실습

       우선은 파일의 적절한 위치에 문자열을 패치시키는 코드를 삽입합니다. 패치 코드를 어디에 설치한지 세 가지 정도의 방법이 있습니다.

       ①파일의 빈 영역에 설치

       ②마지막 섹션을 획장한 후 설치

       ③새로운 섹션을 추가한 후 설치

       보통 패치 코드의 크기가 작은 경우 번방법을 사용합니다.

       

       첫 번째 섹션(.text)의 Size of Raw Data는 400이고,Virtual Size는 280입니다.즉 첫 번째 섹션의 (파일에서) 크기는 400이지만, 여기서 280 크기만 메모리에 로딩한다는 얘기이지요. 나머지 영역 (680~800)은 쓰이지 않는 영역입니다. 바로 이곳이 우리가 찾는 빈 영역 (Null-Padding 영역)입니다.

      빈 영역의 파일 옵셋(file offset)은 680~800 입니다. 이를 프로세스 가상 메모리 (VA: Virtual Address)로 변환하면 401280~401400입나다.OllyDbg의 ‘Assemble’ [Space] 명령과 ‘Edit’ [Ctrl+E] 명령을이용하여 아래와같이 편집하세요.

       다음에는 앞에서 만든 패치 코드(코드 케이브)가 실행되도록 파일을 직접 수정해야 합니다.

       여기서 주의할 것은 이 영역 (401083)은 원래 암호화되어 있는 영역이라는 것입니다.이 Instruction(E9 F8 01)을 그대로 쓰면 안 되고 복호화를 고려해서 XOR7 명령을 수행 한 후 써야 합니다.

       마지막으러 결과를 확인합니다.


'리버싱핵심원리' 카테고리의 다른 글

Reverse Engineering Chap 25~26  (0) 2016.01.20
Reverse Engineering Chap 24  (0) 2016.01.20
Reverse Engineering Chap 23  (0) 2016.01.17
Reverse Engineering Chap 14~15  (0) 2016.01.14
Reverse Engineering Chap 16-17  (0) 2016.01.14
Posted by 알 수 없는 사용자