UPack 의 추억

column 2009.09.21 01:12


<사진 출처 : UPack 제작자 dwing's homepage>


그 동안 총 5 회에 걸쳐서 UPack 의 PE Header 분석과 Debugging 에 대해서 연재를 하였습니다.

UPack 상세 분석 – PE Header 완전 정복 (1)
UPack 상세 분석 – PE Header 완전 정복 (2)
UPack 상세 분석 – PE Header 완전 정복 (3)
UPack 상세 분석 – PE Header 완전 정복 (4)
UPack 디버깅 - OEP 찾기


다른 Packer 도 많은데 굳이 이 UPack 에 이렇게 많은 공(?)을 들인 까닭은 제 개인적인 추억(경험) 때문입니다.

예전에 PE 공부를 마치고 PE File Format 에 자신 있던 그때에, 전혀 새로운 PE 세계가 있다는 걸 알려준 소중한 Packer 입니다. 또한 PE 스펙은 그냥 스펙일 뿐이고, 실제 구현은 PE Loader 개발자에 의해 좌우되기 때문에 OS 별로 실제 테스트를 해봐야 한다는 깨우침(?)을 주었습니다.

제 블로그를 방문하시는 여러분들께서도 저와 같은 경험과 느낌을 가져보시라는 뜻으로 UPack 을 상세히 소개하였습니다.

물론 UPack 에서 소개된 내용이 PE Header Patch 의 전부는 아닙니다.

하지만 제가 분명히 장담할 수 있는 것은 "UPack 을 정복한 사람에겐 앞으로 어떤 PE Header 변형이 나타나더라도 두렵지 않다." 는 것입니다. PE Header 에서 실제 사용되는 값들과 사용되지 않는 값들을 잘 숙지 하고 있다면 어떤 변형도 무리 없이 분석이 가능합니다. (제 경험입니다. ^^)

감사합니다.


ReverseCore


저작자 표시 비영리 변경 금지
신고
  1. 신화창조 2009.09.21 23:25 신고 댓글주소 | 수정 | 삭제 | 댓글

    열심히 써주신 글들 RSS로 정말 잘 읽었습니다. ^^

    확실히 국내 몇 안되게 체계적이고 내용도 쉬운 것(!) 같습니다. 고맙습니다!

  2. Sun2Day 2009.09.22 10:01 신고 댓글주소 | 수정 | 삭제 | 댓글

    좌절의 UPack..;;

    주말에 가지고 놀아보다가.. 안되면 문의 드리러 올께요 (+__)ㅋ

  3. 2009.09.24 01:03 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다

  4. 2010.12.09 20:47 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다

    • reversecore 2010.12.03 16:23 신고 댓글주소 | 수정 | 삭제

      안녕하세요.

      사실 합법/불법을 저조차도 딱부러지게 100% 판단하기는 어렵습니다.

      다만 제 기준은 이렇습니다.

      누군가에게 피해를 끼쳤느냐 아니냐로 구분하는 것이죠.

      가령 리버싱을 해서 패커의 고유 압축 알고리즘을 공개해버리면 소중한 지식을 공개한 셈이 되지 않을까요? 제작자 입장에서는 피해를 보는 상황이라고 생각합니다.

      저는 법에 대해 문외한 이므로 참고만 하시기 바랍니다. ^^

      감사합니다.

  5. プラダ トートバッグ 2013.04.25 16:28 신고 댓글주소 | 수정 | 삭제 | 댓글

    누구냐고 물어보면 윤동주라고

  6. netinfil 2013.10.08 14:38 신고 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요. 책을 구매한 후에 Upack의 OEP 찾는 부분을 진행하고있습니다.

    그런데 압축되어있는 실습예제의 notepad.exe가 언패킹이 되지 않은 것 같습니다.

    참고해주셨으면 합니다!

    또한 책 전반에 걸쳐서 16진수와 10진수의 구분이 애매하게 되어있어서

    PE구조를 분석할때 자주 헷갈리네요. 혹시 다음에 개정판을 내실 의향이 있으시다면

    '0x'와 같이 분류를 해주셨으면 합니다^^




EAT (Export Address Table)


Windows 운영체제에서 라이브러리(Library) 란 다른 프로그램에서 불러 쓸 수 있도록
관련 함수들을 모아놓은 파일(DLL/SYS)입니다.

Win32 API 가 대표적인 Library 이며, 그 중에서도 kernel32.dll 파일이 가장 대표적인 Library 파일이라고 할 수 있습니다.

EAT(Export Address Table) 은 라이브러리 파일에서 제공하는 함수를
다른 프로그램에서 가져다 사용할 수 있도록 해주는 매커니즘 입니다.

앞서 설명드린 IAT 와 마찬가지로 PE 파일내에 특정 구조체(IMAGE_EXPORT_DIRECTORY)에 정보를 저장하고 있습니다.

라이브러리의 EAT 를 설명하는 IMAGE_EXPORT_DIRECTORY 구조체는 PE 파일에 하나만 존재합니다. 

* 참고로 IAT 를 설명하는 IMAGE_IMPORT_DESCRIPTOR 구조체는 여러개의 멤버를 가진 배열 형태로 존재합니다.
  왜냐하면 PE 파일은 여러개의 라이브러리를 동시에 Import 할 수 있기 때문이지요

PE 파일내에서 IMAGE_EXPORT_DIRECTORY 구조체의 위치는 PE Header 에서 찾을 수 있습니다.
IMAGE_OPTIONAL_HEADER32.DataDirectory[0].VirtualAddress 값이 
실제 IMAGE_EXPORT_DIRECTORY 구조체 배열의 시작 주소 입니다. (RVA 값입니다.)

아래는 kernel32.dll 파일의 IMAGE_OPTIONAL_HEADER32.DataDirectory[0].VirtualAddress 를 보여주고 있습니다.
(첫번째 4 byte 가 VirtualAddress, 두번째 4 byte 가 Size 멤버입니다.)


 offset   value   description
----------------------------------------------
...
00000160
00000000 loader flags

00000164 00000010 number of directories

00000168 0000262C RVA  of EXPORT Directory
0000016C 00006D19 size of EXPORT Directory

00000170 00081898 RVA  of IMPORT Directory
00000174 00000028 size of IMPORT Directory
...

* IMAGE_OPTIONAL_HEADER32 구조체에 대해서 궁금하신 분은

IMAGE_OPTIONAL_HEADER 설명 을 참고하시기 바랍니다.

RVA 값이 262Ch 이므로 File offset 은 1A2Ch 입니다.
(RVA 와 File offset 간의 변환과정이 잘 이해 안가시는 분은 IMAGE_SECTION_HEADER 설명을 참고하시기 바랍니다.)



IMAGE_EXPORT_DIRECTORY



IMAGE_EXPORT_DIRECTORY 구조체는 아래와 같습니다.

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;          // creation time date stamp
    WORD    MajorVersion;
    WORD    MinorVersion;
    DWORD   Name;                   // address of library file name
    DWORD   Base;                   // ordinal base
    DWORD   NumberOfFunctions;      // number of functions
    DWORD   NumberOfNames;          // number of names
    DWORD   AddressOfFunctions;     // address of function start address array
    DWORD   AddressOfNames;         // address of functino name string array
    DWORD   AddressOfNameOrdinals;  // address of ordinal array
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

* 출처 : Microsoft 의 Visual C++ 에서 제공하는 winnt.h

중요 멤버들에 대한 설명입니다. (여기에 나오는 주소는 모두 RVA 입니다.)

NumberOfFunctions : 실제 export 함수 갯수
NumberOfNames : export 함수중에서 이름을 가지는 함수 갯수 (<= NumberOfFunctions)
AddressOfFunctions : export 함수들의 시작 위치 배열의 주소 (배열의 원소개수 = NumberOfFunctions)
AddressOfNames : 함수 이름 배열의 주소 (배열의 원소개수 = NumberOfNames)
AddressOfOrdinals : ordinal 배열의 주소 (배열의 원소개수 = NumberOfNames)


아래 그림은 kernel32.dll 파일의 IMAGE_EXPORT_DIRECTORY 의 구조를 나타내고 있습니다.



<Fig. EAT 구조>


라이브러리에서 함수 주소를 얻는 API 는 GetProcAddress() 입니다.

GetProcAddress() 함수가 함수 이름을 가지고 어떻게 함수 주소를 얻어내는 순서를 설명드리겠습니다.


 

1. AddressOfNames 멤버를 이용해 "함수 이름 배열" 로 갑니다.
2. "함수 이름 배열"은 문자열 주소가 저장되어 있습니다. 문자열 비교(strcmp)를 통하여 원하는 함수 이름을 찾습니다.
   이 때의 배열 인덱스를 name_index 라고 하겠습니다.
3. AddressOfNameOrdinals 멤버를 이용해 "ordinal 배열" 로 갑니다.
4. "ordinal 배열" 에서 name_index 로 해당 ordinal_index 값을 찾습니다.
5. AddressOfFunctions 멤버를 이용해 "함수 주소 배열 - EAT" 로 갑니다.
6. "함수 주소 배열 - EAT" 에서 아까 구한 ordinal_index 를 배열 인덱스로 하여 원하는 함수의 시작 주소를 얻습니다.


위 <Fig. EAT 구조> 는 kernel32.dll 의 경우를 보여주고 있습니다.

kernel32.dll 은 export 하는 모든 함수에 이름이 존재하며,
AddressOfNameOrdinals 배열의 값이 index = ordinal 형태로 되어있습니다.

하지만 모든 DLL 파일이 이와 같지는 않습니다.
export 하는 함수 중에 이름이 존재하지 않을 수 도 있으며 (ordinal 로만 export 함)
AddressOfNameOrdinals 배열의 값이 index != ordinal 인 경우도 있습니다.

따라서 위 순서를 따라야만 정확한 함수 주소를 얻을 수 있습니다.

* 참고로 함수 이름 없이 ordinal 로만 export 된 함수의 주소를 찾을 수 도 있습니다.




kernel32.dll 을 이용한 실습



실제 kernel32.dll 파일의 EAT 에서 AddAtomW 함수 주소를 찾는 실습을 해보겠습니다.
(<Fig. EAT 구조> 를 참고하세요.)

앞에서 kernel32.dll 의 IMPORT_EXPORT_DIRECTORY 구조체 file offset 은 1A2Ch 라고 하였습니다.
hex editor 로 1A2Ch 주소로 갑니다.


각 구조체 멤버별로 나타내 보겠습니다.

Characteristics       = 00000000h
TimeDateStamp         = 49C4D12Eh
MajorVersion          =     0000h
MinorVersion          =     0000h
Name                  = 00004B98h
Base                  = 00000001h
NumberOfFunctions     = 000003BAh
NumberOfNames         = 000003BAh
AddressOfFunctions    = 00002654h
AddressOfNames        = 0000353Ch
AddressOfNameOrdinals = 00004424h


위에서 알려드린 순서대로 진행하겠습니다.


1. "함수 이름 배열"

AddressOfNames 멤버의 값은 RVA = 353Ch 이므로 file offset = 293Ch 입니다.


4 byte 의 RVA 로 이루어진 배열입니다. 배열 원소의 갯수는 NumberOfNames (3BAh) 입니다.
저 모든 RVA 값을 하나하나 따라가면 함수 이름 문자열이 나타납니다.


2. 원하는 함수 이름 찾기

설명의 편의를 위해 우리가 찾는 "AddAtomW" 함수 이름 문자열은 배열의 세번째 원소의 값(주소)를 따라가면 됩니다.

RVA = 4BBDh 이므로 file offset = 3FBDh 입니다.


이때 배열의 인덱스(index) 는 2 입니다.


3. "Ordinal 배열"

AddressOfNameOrdinals 멤버의 값은 RVA = 4424h 이므로 file offset = 3824h 입니다.


2 byte 의 ordinal 로 이루어진 배열이 나타납니다.


4. ordinal

위에서 구한 index 값 2 를 위의 "ordinal 배열" 에 적용하면 ordinal 2 를 구할 수 있습니다. 

AddressOfNameOrdinals[index(2)] = ordinal(2)



5. "함수 주소 배열(EAT)"

AddressOfFunctions 멤버의 값은 RVA = 2654h 이므로 file offset = 1A54h 입니다.


4 byte 함수 주소 RVA 배열이 나타납니다.


6. AddAtomW 함수 주소

위에서 구한 ordinal_index 를 "함수 주소 배열(EAT)" 에 적용하면 해당 함수의 RVA (000326F1h)를 얻을 수 있습니다.

AddressOfFunctions[ordinal(2)] = 326F1


kernel32.dll 의 ImageBase = 7C7D0000h 입니다.
따라서 "AddAtomW" 함수의 실제 주소(VA)는 7C8026F1h 입니다.

OllyDbg 를 이용해서 확인해 보겠습니다.


네, 정확히 7C8026F1h 주소(VA)에 우리가 찾는 "AddAtomW" 함수가 나타납니다.


+---+

이상으로 EAT(Export Address Table) 에 대해서 알아보았습니다.

이제 가장 기본적이면서 중요한 부분은 전부 끝났습니다.
힘드셨나요? 아마 쉽지는 않으셨을 겁니다.
이해 가지 않는 부분은 실습 위주로 차근차근 따라 해보시기 바랍니다.

다음에는 PE Header 마무리 시간을 갖도록 하겠습니다.


저작자 표시 비영리 변경 금지
신고
    이전 댓글 더보기
  1. 쵸밥 2009.11.27 02:10 신고 댓글주소 | 수정 | 삭제 | 댓글

    ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 갑자기 0x262C 의 RVA 변환하는게 기억이 안나서 한참을 찾다가 헤메서 ㅋㅋㅋㅋㅋㅋㅋ 결국 계산해서 0x1A2C 를 찾았는데 ㅋㅋㅋㅋㅋㅋㅋㅋ

    찾고보니깐 . . PEview 에 RVA 보기로 그냥 0x262C 찾아가면 되는거였어요 ???? ㅠ_______ㅠ ㅋㅋㅋㅋㅋㅋㅋㅋㅋ

    아 . . 허무해 . . ㅋㅋㅋㅋㅋㅋㅋㅋ

    RVA to RAW 는 이번 기회에 확실히 알았네요 ㅋㅋㅋ
    PEview 에 RVA 메뉴가 왜있는지 .. 했었는데 ㅋㅋ

    • reversecore 2009.11.27 14:36 신고 댓글주소 | 수정 | 삭제

      쵸밥님, 안녕하세요.

      RVA to RAW 를 확실히 배우셨다면 그것만도 엄청난 수확입니다.
      나중에 http://www.reversecore.com/47 글도 한번 읽어보시기 바랍니다.
      PE Header 실력을 점검할 수 있는 좋은 계기가 될 것입니다.

  2. 쵸밥 2009.11.27 02:15 신고 댓글주소 | 수정 | 삭제 | 댓글

    아 . . 핵사 뷰어로 따라가느라 RAW를 찾으신 거였구나 ^^;; ㅋㅋ

  3. 메롱이 2009.11.30 23:38 신고 댓글주소 | 수정 | 삭제 | 댓글

    강좌 6까지 어느정도 이해하면서 강좌7 까지 오는데 대략 3일이나 걸린거 같아요...ㄷㄷ;;

    저도 RVA to RAW를 리버스코어님 덕택에 잘 이해한거 같아요...휴휴..;;;

    근데요 강좌 7에서 저 같은 경우느 메모장 RVA of EXPORT TABLE 이 262C가 아니고

    전부 0000 로 표시되던데...저 같은 경우도 있는건가요?


    친척형이 그러는데 PE헤더 개념 공부 하는데

    책에에서나 설명할법한 내용을 이렇게 리버스코어님

    블로그처럼 자세히 설명해준곳은 없다고 그러네요..

    전...너무 초보라...그렇게 자세한 설명도

    이해할까 말까인데 말이죵...ㅜ_ㅜ

    감사합니다.!!

    • ReverseCore 2009.12.01 12:48 신고 댓글주소 | 수정 | 삭제

      메롱이님, 안녕하세요.

      열심히 하시는 모습이 보기좋습니다. ^^

      RVA of EXPORT Table = 262C 는 kernel32.dll 의 값이구요. 메모장에서는 0 이 맞습니다.

      친척형님께서도 제 블로그에 방문하셨나 보네요.

      모두 감사 드립니다.

  4. graythief 2010.02.19 09:24 신고 댓글주소 | 수정 | 삭제 | 댓글

    이 글을 읽다가 너무 이해가 안되서 글을 남깁니다
    일단 PE header의 IMAGE_OPTIONAL_HEADER32 에 따르면
    그 부분에 IMAGE_DATA_DIRECTORY_DataDirctory[16<--define으로 되어있으니 그냥 씁니다]
    에 의해 dataDirectory[0]~[16]을 쓰게 되는데요
    여기서 IMPORT Dirctory 와 EXPORT Directory를 가르킨다고 이해하고있습니다

    그런데 그 가르키는 값이 IMAGE_DATA_DIRECTORY 구조체이기 때문에 사이즈와 가상주소를 저장하고 있다고 이해하고 있습니다

    즉 IMPORT에 해당하는 주소와 EXPORT에 해당하는 주소를 말이죠
    그런데 IMPORT는 사용하는 dll마다 IMAGE_IMPORT_DESCRIPTOR 구조체를 주소에할

    당하고 배열을 잡히게 되므로 그 배열 상태를 테이블이라고 이해하고 있습니다

    그런데 EXPORT는 배열이 아닌 IMAGE_EXPORT_DIRECTORY 이 구조체 하나만을 사용하기때문에 배열이 안된다고 이해 했습니다


    KERNEL32.DLL의 IMAGE_EXPORT_DIRECTORY 구조를 나타내는 그림에 1번 번호가 적힌 부분 RVA LIST의 주소에 매핑된 문자열은 어디서 참조된건가요? KERNEL32.DLL안에 있는 함수들의 명을 매핑시킨건가요?

    그리고 문자열를 STRCMP 로 비교한다고 했는데 그 원래의 함수는 어디에 저장되어있다가 비교하는건가요 ? IMAGE_EXPORT_DIRECTORY 함수명을 저장하는곳은 없어보이던데요..

    그리고 라이브러리 함수 주소를 얻는 API GETPROCADDRESS()함수는 참조하게 될수 있는 순간 다른 라이브러리의 함수도 주소를 얻을수 있는건가요?

    그리고 KERNEL32.DLL은 EXPORT 하는 모든 함수에 이름이 존재한다는데 이 말 뜻이 조금 이해가 안됩니다

    그리고 질문 많이 해서 죄송합니다 계속 읽으면서 이해하고 있는데 잡아 줄사람이 없어서 고민 끝에 글을 남깁니다
    혹시라도 이해 안되시면 답변 안달아주셔도 되고요..
    부탁드리겠습니다 ㅜㅜ

    • reversecore 2010.02.18 12:49 신고 댓글주소 | 수정 | 삭제

      안녕하세요.

      먼저 제 블로그에 방문 감사드리고요. ^^
      문의하신 내용을 좀 정리해서 답변해 드립니다.

      <Fig. 1> 의 IMAGE_EXPORT_DIRECTORY.AddressOfNames 를 말씀하시는것 같네요. 이 값은 이름 RVA 배열을 가리킵니다. 각 RVA 를 따라가보면 함수 이름 문자열이 존재합니다. 즉, DLL 파일내에 자신이 이름으로 Export 하는 함수의 이름 문자열을 가지고 있다는 것이지요.

      Kernel32!GetProcAddress() 함수의 역할은 이 AddressOfNames 을 죽~ 훑어서 그 함수의 실제 시작 주소를 리턴하는 것이고요. 그 방법은 본문에 설명되어 있습니다.

      그리고 DLL 이 어떤 함수를 Export 할때 "이름" 으로 할 수 있지만, "번호(Ordinal)"만으로 Export 할수도 있습니다. 즉, 모든 Export 함수에 이름이 있다라고 생각하시면 안됩니다. 이 경우 GetProcAddress(hLib, 123) 이런식으로 호출하시면 됩니다.

      질문 있으시면 더 올려주세요~ ^^

      * 그리고 이런 좋은 질문은 '공개'로 해주시면 다른 분들께서도 보시고 도움이 많이 될것 같습니다. ^^

      감사합니다.

  5. graythief 2010.02.19 11:01 신고 댓글주소 | 수정 | 삭제 | 댓글

    정말 감사합니다 하지만 이 글을 읽고 나서 제가 잘못 접근 한것이 아닌가해서 다시 질문 좀 염치없이 드려봅니다 ㅜㅜ

    저위의 <FIG. 1> 의 IMAGE_EXPORT_DIRECTORY 저것은
    kernel32.dll의 pe구조에서 나온건가요?

    만약 그렇다면 보통 exe파일이나 dll이 아닌 파일에는 IMAGE_EXPORT_DIRECTORY 쓰지 않는 경우도 있겠네요??
    이게 이해가 조금 안되신다면 kernel32.dll이 다른 dll을 참조 안하고 모든 함수가 여기있다고 한다면
    kernel32.dll에서는 import table같은게 없다는것인가요??(있긴 있지만 사용 안한다는)
    그리고 이게 맞다면?

    일반 exe pe구조의 INT,IAT가 IMAGE_IMPORT_BY_NAME 구조체의 주소를 가르켜서 함수의 주소를 알아온다라고 이해했느데요

    그렇다면 kernel32.dll의 EAT가 IMAGE_IMPORT_BY_NAME 라는것입니까?
    아니라면 EAT랑 IMAGE_IMPORT_BY_NAME이것 이랑 어떻게든 매핑이 된다는것인데
    그 매핑은 누가 시켜주는거죠?
    이걸 확인할 방법이 혹시나 있나요 ㅜㅜ??

    그리고 일반적인 exe 파일에서 kernel32.dll에 있는 함수를 쓸려고 한다면 결국은 일단 일반적인 exe파일에서 IMAGE_EXPORT_DISCRPITOR을 이용해 kernel32.dll을 로딩을 시킨뒤에 kernel32.dll에 있는 IMAGE_EXPORT_DIRECTORY 이것을 읽어서 EAT까지의 순서가 실행된 후 일반적인 exe파일에서 GetProcAddress() 함수를 사용하면 kernel32.dll의 함수 주소를 알아온다라고 전체적인 흐름을 잡으면되나요?

    바쁘실텐데 염치없이 일거리 드려서 정말 죄송할 따름입니다 하지만 꼭 재대로 이해하고 싶습니다!!!ㅜㅜ

    • reversecore 2010.02.20 00:03 신고 댓글주소 | 수정 | 삭제

      graythief님, 안녕하세요.

      정확히 이해 하고자 하는 의욕이 너무 보기 좋습니다. ^^

      질문의 윗부분은 생각하신 그대로가 맞습니다.

      "그렇다면 kernel32.dll의 EAT가 IMAGE_IMPORT_BY_NAME 라는것입니까?"
      => 아닙니다. EAT 는 전혀 별개의 구조체 입니다.

      EXPORT 하는 DLL 에서 EAT 를 제공하고, IMPORT 하려는 EXE(DLL) 에서 IAT 를 제공하는 것이지요.

      EAT 는 라이브러리 입장에서 "내가 이런 함수를 제공합니다. 함수 이름 & Ordinal 은 이러이러한 게 있구요. 함수의 실제 주소는 여기여기에 있어요" 라고 말하는 것이지요.

      IAT 는 PE 파일이 PE Loader 에게 하는 말입니다. "내가 이러이러한 함수가 필요하니, 너(PE Loader)는 이 DLL 로부터 이러이러한 함수의 주소를 구해서 여기(IAT) 에 채워줘" 라고 말하는 것입니다.

      본문에는 설명하지 않았는데요, PEView 라는 프로그램을 가지고 kernel32.dll 의 EAT 구조체를 보시면서 제 글을 다시 읽어보시면 좀 더 이해가 쉽게 되실 것입니다.

      http://www.magma.ca/~wjr/PEview.zip

      또 질문 올려주세요~

      감사합니다.

  6. graythief 2010.02.20 02:24 신고 댓글주소 | 수정 | 삭제 | 댓글

    와 정말 대단하십니다 이제 감이 잡히네요
    이 감잡은걸로 실습하고 오겠습니다 후후후후
    또 모르는 것 있으면 언제든지 질문 할께요~~~

  7. graythief 2010.02.23 21:12 신고 댓글주소 | 수정 | 삭제 | 댓글

    다시 공부하다가 질문이 있어서요..
    그럼 결국 소스파일에서 getProcAddress() API를 집어넣고 컴파일 시켰다면
    exe로 만들어지잔아요 그래서 실행을 시키면 그때는 어차피 import 과정을 거쳐서 getProcAddress()까지는 쓸수 있게 되는거잔아요
    그 상태 이후로 코드영역의 GetProcAddress()가 실행되는 순간 내가 필요한 함수의 dll 을 로드시키고 그 dll의 <fig EAT구조> 를 그대로 거처서 함수의 주소를 얻는다 라고 이해가 되는데요 맞는건가요? ㅜㅜ
    그리고 만약 그렇게해서 얻은 주소는 어디에 저장되는건가요?바로 exe파일의 iat로 가서 저장이 되는건가요?
    계속 보고 또 보고 하는데도 볼때마다 해석이 틀려지니 죽겠습니다 -_ㅜ;;

    그리고 구조체 설명하는 밑 부분에
    addressofFunctions
    AddressOfNames
    AddressofOrdinals <-- 이거 옆에 배열크기라고 적혀있는부분
    확실하게 한다면
    DWORD크기 * numberofFunctions
    DWORD크기 * numberofName
    DWORD크기 * numberofOrdinals 아닌가요?????


    그리고요
    IAT는요 컴파일 되어서 EXE 파일이 만들어지잔아요
    그렇게 되면 그 IAT나 INT 나 다른 IMPORT 테이블이나 IMAGE_IMPORT_BY_NAME 이부분까지 다 값이나 주소가 세팅이 되어있겠네요? 대신 IAT는 메모리에 올라가면 값이 변하게 되고요 맞나요?

    • reversecore 2010.02.24 01:03 신고 댓글주소 | 수정 | 삭제

      graythief님, 안녕하세요. ^^

      정확히 말하자면 LoadLibrary() 가 dll 을 로드시키는 역할이구요, GetProcAddress() 는 함수 주소를 얻는 역할이지요.
      GetProcAddress() 의 내부 구현을 보면 해당 DLL 파일의 EAT 를 조사해서 원하는 함수를 찾아내는게 맞습니다.

      PE 파일 실행 원리가 잘 이해안가시는것 같아서 간단히 설명해드리겠습니다.

      PE Loader 는 CreateProcess() API 라고 생각하시면 쉽습니다.
      탐색기에서 EXE 파일을 더블 클릭하면 탐색기 프로세스는 해당 EXE 파일을 CreateProcess() 로 실행시켜줍니다.

      실행과정은 프로세스를 만들고 해당되는 가상메모리를 할당하고 섹션헤더의 정보에 맞게 파일을 메모리로 올려줍니다. 그리고 IAT 를 구성하는데, 이 파일에서 사용되는 모든 API 의 주소를 동적으로 구해서 정해진 영역(IAT)에 입력해주는 것이죠. 그 과정에서 필요한 DLL 파일의 EAT 정보가 사용됩니다. (이건 간단히 설명해서 LoadLibrary()/GetProcAddress() 루프로 구성되지요.) 즉, PE Loader 내부 구현을 보면 LoadLibrary()/GetProcAddress() 가 반복해서 사용된다는 뜻입니다.

      제 나름대로 쉽게 설명한다고 해도 처음 접하시는 분들께서는 어려워 하시는게 당연합니다. 완전 생소한 개념이니까요.

      천천히 읽어보시고, 또 질문 올려주세요~ ^^

      감사합니다.

    • reversecore 2010.02.24 01:10 신고 댓글주소 | 수정 | 삭제

      배열크기라는 용어가 잘못 되었네요...

      정확하게는 배열 원소 개수라고 해야 맞고요.

      배열크기는 graythief 님의 공식이 맞습니다.

      좋은 지적 감사합니다. ^^~

      그리고 IAT 영역을 파일과 메모리에서 보시면 보통 다릅니다.
      그 이유는 IAT 설명을 참조해 주세요~

      감사합니다.

  8. graythief 2010.02.24 03:43 신고 댓글주소 | 수정 | 삭제 | 댓글

    소스를 짜게 되면 hdll = loadlibrary("kernel32.dll");
    생략
    getProcaddress(hdll,함수이름);
    대충 이런식으로 쓰잔아요 그럼 eat 부분에 설명해놓은 getProcaddress랑은 틀린건가요
    답변을 해놓은거 제가 이해가 재대로 안되서인지 pe loader 의 내부구현이 loadlibrary()/getProcaddress()이니까 eat에 설명해놓은 getProcaddress()는 pe loader에서 내부구현에 쓰인 함수다 즉 pe loader 안에 따로 구현되어있는 함수이다 이런식으로 이해했는데요..

    아니라면 loadlibrary()/getProcaddress()가 결국은 pe loader의 역할을 하기위해서 쓰는 코드이다..
    이런식으로 이해햐아하나요?

    그리고 그럼 결국 getProcAddress(" 함수") 이렇게 되어있다면 함수명은 INT로 찾고 힌트 또는 함수명을 얻은뒤 getProcAddress를 사용하여 eat쪽의 함수이름을 비교하여 함수 주소를 얻은뒤에 IAT에 쓴다라고 이해해도 될까요?

    • reversecore 2010.02.24 23:49 신고 댓글주소 | 수정 | 삭제

      graythief님, 안녕하세요.

      저 위에 설명된 GetProcAddress() 설명은 일종의 구현 알고리즘을 설명 드린거구요. 문의하신 kernel32!GetProcAddress() 는 MS 에서 제공한 API 이지요. 구현원리는 크게 다르지 않을 겁니다. ^^

      그리고 kernel32!LoadLibrary() & kernel32!GetProcAddress() API 는 범용적으로 널리 사용되는 함수입니다. PE Loader 에서도 물론 사용되구요.

      마지막 질문은 PE Loader 가 INT 에서 IAT 에 주소를 채워 넣는 방법을 문의하신 것 같네요. 그렇게 이해하시는게 맞습니다. ^^

      PE 헤더에서는 IAT, INT 가 제일 어려운 개념입니다.
      하지만 이것만 이해하시면 PE 헤더를 정복하는 것이지요.

      열심히 질문하시는 모습이 보기 좋습니다.

      감사합니다.

  9. graythief 2010.02.25 01:51 신고 댓글주소 | 수정 | 삭제 | 댓글

    아 그렇군요 답변 정말 감사드립니다
    제 어처구니 없는 질문이고 공부안하고 질문 올린거 같아서 죄송하고요..(나름 한다고는 하는데 글을 읽을때마다 해석이 달라져서...;;)정말 큰 도움이 되었습니다 ~
    정말 감사드립니다

    • reversecore 2010.02.26 20:26 신고 댓글주소 | 수정 | 삭제

      graythief님, 안녕하세요. ^^

      제가 도움이 되어드렸다니 기쁘네요~
      리버싱 공부 열심히 하시고요, 또 막히실때 질문 올려주세요.
      같이 고민해봐요~

      감사합니다.

  10. Vi0 2010.08.04 22:27 신고 댓글주소 | 수정 | 삭제 | 댓글

    http://www.reversecore.com/23

    에서 질문올렸던거 여기서 해결이됐네요..
    한참 해맷었는데..
    몰랐어도 다음꺼로 한번 건너뛰어볼것을..

    정말 훌륭한 강좌 감사드립니다 ..

  11. kunhui 2011.01.21 21:25 신고 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요? 유익한 강좌 늘 감사하며 읽고 있답니다^^
    질문 몇 가지만 드려볼까 해서 이렇게 글을 남깁니다.

    1. 운영체제는 프로세스가 생성될 때마다 독립적인 4G바이트의 주소 공간을 생성하고 물리적인 메모리를 논리적인 주소 공간에 연결(Map)한다. 고 알고 있습니다.(API정복 참고했습니다.) 여기서 각 프로세스의 가상주소공간(범위 4G)과 가상메모리(물리 메모리+하드디스크 페이징 파일), PE파일 내부의 값들이 가리키는 주소... 이것들의 상관관계가 머릿속에서 확실하게 자리를 못 잡고 있습니다. 제가 제대로 이해하고 있는 건지 봐주시겠습니까?

    dll이 메모리에 처음 로딩될 때, 가상 메모리(물리메모리+하드디스크 페이징 파일)위의 임의의 장소에 코드 및 리소스 등이 로드되고(사용자가 알 수 없는 OS의 규칙에 의해 무작위로 공간을 찾아 물리메모리+페이징파일에 올려짐) 그 가상메모리의 무작위 주소들은 페이지 테이블에 의해 dll을 로드한 프로세스의 가상주소공간에 PE Header의 값에 따라 깔끔하게 매핑된다.

    이런 개념으로 이해하는 게 맞나요?ㅠㅠ
    깔끔하게 정리해주신다면 감사하겠습니다.......!!

    2. EAT를 설명해주신 부분에서, 리버스코어님의 문서에 의하면 AddressOfNames가 가리키는 배열과 AddressOfOrdinals가 가리키는 배열 둘다 (배열의 원소개수=NumberOfNames)로 되어있습니다. 그런데 예를 들어 총 함수의 개수는 10개이고 이중 이름을 가진 함수의 개수가 8개라면, Ordinal 배열 원소의 개수는 10개가 되어야 하지 않나요? 즉 Ordinal 배열 원소의 개수는 NumberOfNames가 아니고 NumberOfFunctions와 같은 값이어야 하지 않은가..하는 의문이 생겼습니다. 제가 잘못 이해한걸까요? 그리고 GetProcAddress()함수의 동작원리를 설명해주신 부분에 [4. "ordinal배열"에서 name_index로 해당 ordinal_index값을 찾습니다.]라고 되어있는데 구체적으로 어떤 방법을 통해 name_index로써 ordinal_index값을 찾는 건지도 궁금합니다.

    3. IAT에서 OriginalFirstThunk가 가리키는 배열의 원소는 IMAGE_IMPORT_BY_NAME 구조체 타입으로 되어있습니다. 그 배열의 Hint와 Name이 각각 EAT에서의 Ordinal list와 AddressOfName이 가리키는 배열의 값을 참조해서 인덱스 순서에 맞춰 셋팅되는 것이 맞나요? 그게 아니라면..Hint와 Name이 최초에 값을 셋팅받는 방법에 대해 알려주셨으면 합니다.

    적다보니 궁금한 게 너무 많네요;; 두서도 없고.. 여유생기실 때 답변 주시면 정말 감사하겠습니다!

    • reversecore 2011.01.25 23:18 신고 댓글주소 | 수정 | 삭제

      안녕하세요.

      상세한 질문을 해주셨네요~ ^^
      환영입니다~

      1. 아마 처음부터 물리 메모리 (페이징 + 세그멘테이션) 를 껴서 포괄적으로 개념을 잡으려고 하시다 보니... 헷갈리시는 것 같습니다...

      잘라서 개념을 잡아보시면 될 거에요~

      - 프로세스에게는 무조건 4GB 크기의 가상메모리가 할당됩니다.
      - 프로세스의 가상메모리에 실제 EXE 이미지(PE 파일이 메모리에 로딩된 모습을 이미지라고 표현합니다.)는 PE 헤더 정보에 맞게 로딩이 되지요.
      - PE 파일은... 보통은 파일로써의 모습과 메모리에 로딩된 모습이 서로 틀립니다. 그런 정보들이 PE 헤더(섹션 헤더)에 명시되어 있지요.
      - 이제 물리 메모리에서 페이지가 어떻게 잡히는지 개념을 잡으시면 되겠죠~ (저도 예전에 공부한 거라 다 까먹었어요... 개념과 레지스터 등만 흐릿하게 기억하고 있죠.)

    • reversecore 2011.01.25 23:50 신고 댓글주소 | 수정 | 삭제

      3. 제가 질문을 제대로 이해했나 모르겠군요...
      일단 Hint 와 Name 을 세팅하는 것은 컴파일/링커입니다. (아마 링커겠지요.)

      빌드되는 환경에서 값을 읽어와서 세팅할 것으로 "추정" 됩니다.

      저도 좀 두서없이 답변해서 오류가 있을지 모르겠네요~
      혹시 질문 내용이 다른 거라면 다시 질문 올려주세요~

      감사합니다.

    • reversecore 2011.01.26 06:36 신고 댓글주소 | 수정 | 삭제

      2. ordinal 배열 크기는 NumberOfNames 가 맞습니다. 배열 이름이 NameOrdinals 입니다.

      실제로 개념은 간단한데 제 설명이 복잡해서 혼란스러우신거 같습니다. (책에서는 좀 다듬고 보강해야 겠어요.)

      이렇게 이해하시면 될 것 같네요.

      ordinal 이 그 자체로 AddressOfFunctions 배열의 인덱스가 되기 때문에 ordinal 로만 export 되는 함수들도 문제없이 주소를 구할 수 있지요.

      AddressOfNameOrdinals 배열은 이름으로 export 되는 함수들의 ordinal 을 구하기 위해서 존재한다고 생각하시면 됩니다. (ordinal 은 AddressOfFunctions 배열의 인덱스이기 때문에 이를 알아야 함수 주소를 알 수 있지요.)

      즉, AddressOfNameOrdinal[index] = ordinal 이고, AddressOfFunctions[ordinal] = address 의 관계인 것입니다.
      (본문에 위 공식을 추가하였습니다.)

      본문의 설명이 매우 복잡하다는걸 깨달았습니다. 저에게 크게 도움이 되는 유익한 질문이네요. 감사합니다. ^^~

  12. kunhui 2011.01.21 21:31 신고 댓글주소 | 수정 | 삭제 | 댓글

    으으.. 또 질문드릴 게 있는데요;
    메시지 훅에 관한 것입니다. 제가 스크린 세이버를 설정해 놓은 상태에서 설정된 시간이 지났을 때에 그 실행을 거부하도록 하는 프로그램을 만드려고 합니다.(=reject screen saver) WM_SYSCOMMAND 메시지를 받았을 때 wParam값과 스크린세이버가 일치하는지 알아보고 그러하다면 처리하지 않고 바로 리턴하는 형식으로 해보았는데(API정복 참고) 이 경우에는 해당 프로세스가 활성화되어있을 때에만 가능하더라구요..알트탭에 의해 다른 프로세스를 활성화시킨다면 그 프로세스는 아무 메시지도 받지 못하니 당연한 거겠지요... 프로세스 액티브 여부에 상관없이 스크린 세이버 전환 메시지를 지속적으로 감시하여 일정 시간이 지나 화면보호기가 동작하려할 때 그걸 거부하게 하려면 어떤 방법을 써볼 수 있을까요? 도움이 될 만한 작은 힌트나 기법을 소개해주시면 감사하겠습니다..

    - 정말 이용료를 지불해도 아깝지 않을 만큼 훌륭한 홈페이지라고 생각합니다. 좋은 자료 늘 감사드립니다 ㅠㅠ Q&A도 비록 처음 이용하는 거지만 다른분들의 것도 공부에 많은 참고가 됐답니다. 책 발간하시면 꼭 살거에요! 수고하세요.

  13. 궁금이 2011.01.26 06:43 신고 댓글주소 | 수정 | 삭제 | 댓글

    동기란게 참 중요한 것 같아요. 제가 WINAPI정복이란 책을 볼때 PE파일 구조를 설명하는데 읽어도 도통 눈에 들어오지도 않고, 읽긴 읽었지만 "이거 필요 없는 내용아냐"라고 생각하며 읽었습니다. 그런데 후킹방법중에 EAT,IAT후킹이란게 있다는걸 알고나니까 PE파일 구조가 무척 재밌더라구요..

    • reversecore 2011.01.31 11:57 신고 댓글주소 | 수정 | 삭제

      참 좋은 지적이십니다.

      저도 관심이 없는 내용에 대해서는 그냥 스쳐지나 가더라구요.

      나중에 "아~ 그게 그거였나?" 라고 기억만 나도 다행이라고 생각합니다.

      감사합니다.

  14. reversingk 2011.05.19 15:21 신고 댓글주소 | 수정 | 삭제 | 댓글

    ㅎㅎ 좋은강의 잘봤습니다.

    드디어 pe 구조 끝이 보입니다 ㅠㅠ

    • reversecore 2011.05.24 20:28 신고 댓글주소 | 수정 | 삭제

      와~ 축하합니다.

      이제 고급 PE 에 대해서 공부를 하시면 좋으실 것 같습니다.

      UPack 상세 분석 – PE Header 완전 정복 (1)
      (http://www.reversecore.com/47)

      DLL Injection - 다른 프로세스에 침투하기 (4)
      (http://www.reversecore.com/43)

      감사합니다. ^^~

  15. TeamKhan 2011.05.23 19:17 신고 댓글주소 | 수정 | 삭제 | 댓글

    AddressOfNameOrdinals 배열의 값이 index = ordinal 형태로 되어있습니다
    라는 본문글중에 index = ordinal 이 무슨의미인지 이해가잘안가내요
    그리고 ordinal 이 무엇인가요 ?
    export 하는 함수 중에 이름이 존재하지 않을 수 도 있으며 (ordinal 로만 export 함
    라는 본문글중에서도
    이름이 존재하지않는경우자체도 이해가 안가구요..(어떻게 함수가 이름이 업을수가...)
    그리고 ordinal로만 export한다는것도 이해가안가구요
    ordinal이 무엇인지도 모르니 이해가 안가는게 당연한거같내요 ^^:

    • reversecore 2011.05.24 20:33 신고 댓글주소 | 수정 | 삭제

      안녕하세요.

      매우 좋은 질문을 해주셨습니다.

      ordinal 은 그냥 export function 의 고유 번호라고 생각하시면 됩니다.

      함수의 이름은 존재합니다만 export 할 때 외부에 이름을 공개하지 않고 고유 번호(ordinal) 만 공개하는 경우가 있습니다. import 해서 쓰는 쪽에서 번호로 해당 함수 주소를 찾아서 호출할 수 있습니다.

      감사합니다.

  16. TeamKhan 2011.05.23 19:40 신고 댓글주소 | 수정 | 삭제 | 댓글

    아 제가 본문글을 전부 안읽고 섣부르게 질문을 드렷내요^^;;
    밑에 내용을 보니 제가 미루어 짐작한게 맞는거같내요 ㅎ
    아무런 보상도없이 이런 좋은글들을 리버싱을 공부하는사람들을위해
    집필해주셔서 항상 감사드린다는 말씀 전해드리고 싶습니다.

  17. LuckySh 2011.11.17 02:33 신고 댓글주소 | 수정 | 삭제 | 댓글

    초보자인 제게는 어려운 내용이군요 ^^;;

  18. lena짝퉁 2012.02.12 03:44 신고 댓글주소 | 수정 | 삭제 | 댓글

    설명의 편의를 위해 우리가 찾는 "AddAtomW" 함수 이름 문자열은 배열의 세번째 원소의 값(주소)를 따라가면 됩니다.
    RVA = 4BBDh 이므로 file offset = 3FBDh 입니다.
    이때 배열의 인덱스(index) 는 2 입니다.

    그림이 잘못 되었습니다.
    배열의 인덱스가 2라고 언급하셨는데 그렇다면 0,1,2 즉, 3번째 입니다. 그런데 그림에는 AddAtomW가 드래그 되어 있네요.
    제가 직접 찾아본 결과로는 AddConsoleAliasA가 드래그가 되어져야 정상입니다.
    즉, AddAtomA, AddAtomW, AddConsoleAliasA중에 3번째인 AddConsoleAliasA가 드래그 되어야 합니다.
    수정 바랍니다.

    좋은 강좌 감사 드립니다. 항상 잘 보고 있습니다.

    • reversecore 2012.04.10 21:28 신고 댓글주소 | 수정 | 삭제

      안녕하세요.

      WinXP SP3의 kernel32.dll 파일의 경우...

      ActivateActCtx, AddAtomA, AddAtomW, ...

      요렇게 AddAtomW 가 3번째 입니다.

      감사합니다.

  19. 누구에 2016.02.17 11:50 댓글주소 | 수정 | 삭제 | 댓글

    관리자의 승인을 기다리고 있는 댓글입니다

  20. 코딩하는 경제학도 2017.02.03 18:40 댓글주소 | 수정 | 삭제 | 댓글

    관리자의 승인을 기다리고 있는 댓글입니다




Introduction


Windows 운영체제의 PE(Portable Executable) File Format 에 대해서 아주 상세히 공부해 보도록 하겠습니다.

PE format 을 공부하면서 Windows 운영체제의 가장 핵심적인 부분인
Process, Memory, DLL 등에 대한 내용을 같이 정리할 수 있습니다.



PE(Portable Executable) File Format


PE 파일의 종류는 아래와 같습니다.

  • 실행 파일 계열 : EXE, SCR
  • 라이브러리 계열 : DLL, OCX
  • 드라이버 계열 : SYS
  • 오브젝트 파일 계열 : OBJ

엄밀히 얘기하면 OBJ(오브젝트) 파일을 제외한 모든 파일들은 실행 가능한 파일 입니다.

DLL, SYS 파일등은 쉘(Explorer.exe) 에서 직접 실행 할 수는 없지만,
다른 형태의 방법(디버거, 서비스, 기타)을 이용하여 실행이 가능한 파일들입니다.

* PE 공식 스펙 에는 컴파일 결과물인 OBJ(오브젝트) 파일도 PE 파일로 간주합니다.
  하지만 OBJ 파일 자체로는 어떠한 형태의 실행도 불가능하므로 리버싱에서 관심을 가질 필요는 없습니다.


간단한 설명을 위해서 노트패드(notepad.exe) 파일을 hex editor 를 이용해서 열어보겠습니다.


<Fig. 1>

<Fig. 1> 은 notepad.exe 파일의 시작 부분이며, PE 파일의 헤더 (PE header) 부분입니다.

바로 이 PE header 에 notepad.exe 파일이 실행되기 위해 필요한 모든 정보가 적혀있습니다.

어떻게 메모리에 적재되고, 어디서부터 실행되어야 하며, 실행에 필요한 DLL 들은 어떤것들이 있고,
필요한 stack/heap 메모리의 크기를 얼마로 할지 등등...


수 많은 정보들이 PE header 에 구조체 형식으로 저장되어 있습니다.

즉, PE File Format 을 공부한다는 것은 PE header 구조체를 공부한다는 것과 같은 말입니다.



Basic Structure


일반적인 PE 파일의 기본 구조입니다. (notepad.exe)


<Fig. 2>

<Fig. 2> 는 notepad.exe 파일이 메모리에 적재(loading 또는 mapping)될 때의 모습을 나타낸 그림입니다.
많은 내용을 함축하고 있는데요, 하나씩 살펴보겠습니다.


  • DOS header 부터 Section header 까지를 PE Header, 그 밑의 Section 들을 합쳐서 PE Body 라고 합니다.

  • 파일에서는 offset 으로, 메모리에서는 VA(Virtual Address) 로 위치를 표현합니다.

  • 파일이 메모리에 로딩되면 모양이 달라집니다. (Section 의 크기, 위치 등)

  • 파일의 내용은 보통 코드(".text" 섹션), 데이타(".data" 섹션), 리소스(".rsrc") 섹션에 나뉘어서 저장됩니다.
    반드시 그런것은 아니며 개발도구(VB/VC++/Delphi/etc)와 빌드 옵션에 따라서
    섹션의 이름, 크기, 개수, 저장내용 등은 틀려집니다. 중요한 것은 섹션이 나뉘어서 저장 된다는 것입니다.

  • Section Header 에 각 Section 에 대한 파일/메모리에서의 크기, 위치, 속성 등이 정의 되어 있습니다.

  • PE Header 의 끝부분과 각 Section 들의 끝에는 NULL padding 이라고 불리우는 영역이 존재합니다.
    컴퓨터에서 파일, 메모리, 네트워크 패킷 등을 처리할 때 효율을 높이기 위해 최소 기본 단위 개념을 사용하는데,
    PE 파일에도 같은 개념이 적용된 것입니다.

  • 파일/메모리에서 섹션의 시작위치는 각 파일/메모리의 최소 기본 단위의 배수에 해당하는 위치여야 하고,
    빈 공간은 NULL 로 채워버립니다. (<Fig. 2> 를 보면 각 섹션의 시작이 이쁘게 딱딱 끊어지는 걸 볼 수 있습니다.)



VA & RVA



VA (Virtual Address) 는 프로세스 가상 메모리의 절대 주소를 말하며,
RVA (Relative Virtual Address) 는 어느 기준위치(ImageBase) 에서부터의 상대 주소를 말합니다.

VA 와 RVA 의 관계는 아래 식과 같습니다.

RVA + ImageBase = VA

PE header 내의 많은 정보는 RVA 형태로 된 것들이 많습니다.
그 이유는 PE 파일(주로 DLL)이 프로세스 가상 메모리의 특정 위치에 로딩되는 순간
이미 그 위치에 다른 PE 파일(DLL) 이 로딩 되어 있을 수 있습니다.

그럴때는 재배치(Relocation) 과정을 통해서 비어 있는 다른 위치에 로딩되어야 하는데,
만약 PE header 정보들이 VA (Virtual Address - 절대주소) 로 되어 있다면 정상적인 엑세스가 이루어지지 않을것입니다.

정보들이 RVA (Relative Virtual Address - 상대주소) 로 되어 있으면 Relocation 이 발생해도
기준위치에 대한 상대주소는 변하지 않기 때문에 아무런 문제없이 원하는 정보에 엑세스 할 수 있을 것입니다.




이어지는 강좌에서 PE Header 구조체를 하나씩 상세히 살펴보도록 하겠습니다.



(continue)



저작자 표시 비영리 변경 금지
신고
    이전 댓글 더보기
  1. 장황제 2010.01.19 18:29 신고 댓글주소 | 수정 | 삭제 | 댓글

    좋은 자료와 설명잘해주신 강의로 열심히 공부하고 있습니다!!

    정말 감사합니다~ㅋㅋ

    질문 드릴께 있는데요.

    padding의 개념이 잘 이해가 않가네여;;

    • reversecore 2010.01.21 01:56 신고 댓글주소 | 수정 | 삭제

      장황제님, 안녕하세요.

      PE 파일의 padding 개념에 대해서 궁금하시군요.


      패딩은 '기본단위' 를 맞추기 위해서 추가하는 덤 이라고 보시면 됩니다.

      이러한 '기본단위' 개념은 컴퓨터 뿐만 아니라 일상 생활에서도 많이 볼 수 있습니다.
      가령 귤을 (많이) 보관할 때는 낱개로 보관하는것이 아니고 박스에 담아서 창고에 보관하지요. 그때 박스가 바로 '기본단위' 인 것입니다. 귤이 몇개라고 하지 않고 귤박스가 몇개라고 말을 합니다.

      왜냐구요? 편하니까요. 귤박스가 아주 많아지면 보관 창고를 더 늘려야 하고, 그때는 귤이 몇박스라고 하지 않고 귤이 창고 몇개에 보관되어 있다라고 할 수 있습니다.

      이러한 '기본단위' 개념은 컴퓨터 설계에도 그대로 녹아 있어서 메모리, 하드디스크 등에도 적용되어 있습니다. (하드디스크가 섹터단위로 구분된다고 들어보셨지요?)

      마찬가지로 PE File Format 의 '섹션'에도 '기본단위(크기)' 개념이 적용되어 있습니다.

      제가 프로그래밍한 코드가 (기계어로 컴파일되어) 크기가 100 byte 밖에 안된다고 하더라도, 만약 섹션의 기본단위가 400h(1000d) byte 라면 코드 섹션의 크기는 1000d 가 되는 것입니다.

      100 byte 코드가 채워지고 남은 900 byte 의 영역에는 그냥 0 으로 채워지게 되고, 이를 padding 이라고 하는 것입니다.

      메모리도 같은 개념이 적용됩니다. (보통 파일보다는 단위 크기가 좀 더 크지요.)

      PE 파일에서 padding 은 누가 어떻게 만들까요?
      네, 당연히 개발도구(VC++, VB, etc) 에서 PE 파일 생성시 자신의 옵션에 따라서 결정됩니다. ^^


      만약 잘 이해 안되시면 다시 질문해 주세요~

      감사합니다.

  2. 장황제 2010.01.21 21:48 신고 댓글주소 | 수정 | 삭제 | 댓글

    와..읽으면서..웃음이 나옵니다..ㅋㅋ

    이해가 퐉왔습니다~;;

    정말 개념만 보면 쉬운거였네요..ㅋㅋ

    이해하기 쉽게 알려주셔서 감사합니다~

    • ReverseCore 2010.01.21 22:47 신고 댓글주소 | 수정 | 삭제

      장황제님, 안녕하세요.

      이해가 잘 되셨다니 다행입니다.

      사실 저렇게 귤을 보관하면 검색(찾기)이 편리해 집니다.
      "몇번 창고 몇번 박스내의 몇번 귤" 하면 되니까요.

      즉, 대량의 데이타를 보관할 때 묶어서 보관하면 정리도 잘되고 검색도 쉬워지는 장점이 있지요.

      감사합니다.

  3. 장황제 2010.01.30 10:45 신고 댓글주소 | 수정 | 삭제 | 댓글

    이번에는 패딩사이즈질문좀 드릴께요..ㅠ

    파일에 패딩하고 메모리에 로드되었을때의 패딩 크기가 알고싶어서요.

    저는 만약 sectionalignment에서 ox00001000이면 4096바이트의 배수가 되는거고.

    filealignment가 ox00002000 이면 8192바이트의 배수가 된다고 생각을 했는데....

    제가 잘못생각하고 있는건가요??

    • reversecore 2010.01.31 23:19 신고 댓글주소 | 수정 | 삭제

      장황제님, 안녕하세요.

      패딩크기 말씀이시죠?

      실제 코드(데이타) 의 크기를 각 alignment 에 맞게 확장시켜 주기 때문에 그때 그때 달라집니다.

      그리고 각 섹션의 크기는 장황제님 말씀대로 입니다.

      보통 file alignment 는 0x200, 0x400, 0x1000 이 많이 쓰입니다.
      Section Alignment 는 0x1000 이 많이 쓰이구요.

      감사합니다.

  4. tanosi 2010.04.06 01:02 신고 댓글주소 | 수정 | 삭제 | 댓글

    머리속에 '파밧' 하고 개념이 잘 서는 포스팅을 보고 놀랐습니다.
    출간 준비중 이라고 하시니 기대됩니다~~
    최근 나온 리버스엔지니어링 역분석 구조와 원리 라는책을 보고 PE 에서 딱 막혔는데, 다시한번 달리게 되었습니다.
    많은 도움이 되고 있습니다~~

  5. 할라 2010.04.15 22:29 신고 댓글주소 | 수정 | 삭제 | 댓글

    리버싱 입문에 들어서는 할라라고 합니다^^;;
    요즘 리버싱이 재밌어지려고 하는 찰라에
    많은걸 학습해야 하는걸 깨닫고 관련된 자료를 찾아다니는 찰나에
    이런 좋은 글을 발견했네요 ㅎㅎ
    괜찮으시면 이 글을 토대로 제가 따로 글을 올려놓고 싶은데
    괜찮나요?^^;

    아무튼 여기서 많은 걸 배워갑니다^^

    • reversecore 2010.04.15 23:42 신고 댓글주소 | 수정 | 삭제

      할라님, 안녕하세요. ^^

      저의 저작권 정책만 지켜주시면
      여기서 배우신 내용을 가지고 본인의 글을 쓰시는건
      아무런 문제가 없습니다.

      감사합니다.

  6. 할라 2010.04.19 16:39 신고 댓글주소 | 수정 | 삭제 | 댓글

    음.. 저기 VA/RVA 부분에서요
    VA와 RVA에 관계를 보시면
    RVA + ImageBase = VA라고 되어있는데요
    +가 맞나요??? -아닌가요???

    • reversecore 2010.04.19 23:01 신고 댓글주소 | 수정 | 삭제

      + 가 맞습니다.

      RVA 의 의미가 Relative Virtual Address (to ImageBase) 이거든요.

      따라서 VA = RVA + ImageBase 이고,
      RVA = VA - ImageBase 이지요.

      감사합니다.

  7. kvirus 2010.05.27 10:39 신고 댓글주소 | 수정 | 삭제 | 댓글

    책으로 출판하시는군요...
    꼭 소장하겠습니다!!!
    기대되네요 :)

  8. MyKim 2010.06.17 23:47 신고 댓글주소 | 수정 | 삭제 | 댓글

    이렇게 체계적으로 잘 정리된 내용이라니 ...
    참 좋은 내용를 제공해 주셔서 감사합니다.
    또한 박수와 격려를 보내드립니다.
    그런데 언제 책이 출간되나요 ?
    빨리 사서 공부해야 겠습니다....

    Have reverence for God, and obey his commands, because this is all that man was created for. (Ecclesia 12:13)

  9. 박두현 2010.08.09 19:02 신고 댓글주소 | 수정 | 삭제 | 댓글

    Fig2. 그림을 사용하고 싶습니다.
    프로그램 실행에 관해서 블로깅을 하고 있는데
    Fig2 그림이 너무나 필요합니다.
    물론 출처는 남기겠습니다.

    • reversecore 2010.08.10 12:43 신고 댓글주소 | 수정 | 삭제

      안녕하세요.

      가져가시기 전에 먼저 문의해 주셔서 감사합니다. ^^

      이글의 제일 처음 댓글을 보시면 showtime 님께서 동일한 문의를 주셨는데요.

      그쪽에 제가 답변 달아놓은 대로만 지켜주시면 사용하실 수 있도록 해드리겠습니다.

      감사합니다.

  10. maruhun 2010.08.11 23:55 신고 댓글주소 | 수정 | 삭제 | 댓글

    pe구조에 궁금한점있어서 문의드립니다.

    인터넷으로찾아보았지만 시원한 대답이 없어서 이렇게문의 드립니다.

    페이징 파일이란 가상메모리라고 알고있습니다. 그럼 페이지는 CPU가 엑세스하는 메모리인가요?

    그리고 공부를 하다보니깐 처음에 CPU가 메모리를 vitual address를 발생시킨다고 하는데

    cpu는 가상주소밖에 모르는건가요?

    실제 주소 번지(가상주소)=이미지 로드 시작 번지+RVA 인데

    실제 주소 번지가(가상주소)<----메모리 주소이면 실제주소는 어딘지..

    VAS,MMF,VMM 등 찾아보고 연습장에 내용이랑 찾아놓았지만 연결이 되지를 않습니다..

    간단하게 연결만 시켜주셨으면 감사하겠습니다..

    • reversecore 2010.08.15 22:48 신고 댓글주소 | 수정 | 삭제

      CPU 는 하드웨어이고 컴퓨터의 두뇌 역할이지요.
      따라서 메모리를 직접 access 할 수 있습니다.

      내부적으로 치열하게 물리메모리<->가상메모리 변환을 통해 정확한 주소에 접근할 수 있습니다.

      물리주소에 접근하는걸 궁금하시는게 아니라면...

      위의 설명은 이렇게 이해하시면 됩니다.

      PE 파일이 실행되면 프로세스가 생성되는데,
      프로세스는 4 GB 의 가상메모리를 할당 받고 그중 하위 2GB 를 유저영역으로 사용할 수 있습니다.

      이 프로세스 가상 메모리 주소를 표시할 때 VA 로 나타냅니다.

      음... 어떻게 이해가 되시는지요...

      궁금하신 내용은 또 질문 올려주세요~

      감사합니다.

  11. pe공부 2011.01.01 10:52 신고 댓글주소 | 수정 | 삭제 | 댓글

    리버스코어님 제가 PE포맷에 대해서 공부하다가 의문점이 생겨서 질문드립니다.
    ------------------------------------------------------------------------------------------------
    현재 PE파일포맷형식이 A라고 하면(현재 PE구조)
    PE파일포맷형식을 다른 형식인 B로 만들어도 되는가?(물론,프로그램이 실행해야할 필수정보는 있다.)
    WIN32 OS들은 모두 B형식을 인식할수있다고 치자

    • reversecore 2011.01.03 01:04 신고 댓글주소 | 수정 | 삭제

      안녕하세요.

      네, 말씀하신 대로 가능합니다.

      packer 류의 프로그램이 바로 정확하게 그 일을 수행하고 있지요.

      감사합니다.

  12. 금빛 star 2011.04.27 11:54 신고 댓글주소 | 수정 | 삭제 | 댓글

    좋은 자료에 감사드립니다.
    근데. 가상메모리->물리메모리변환은 누가 해주는가요? OS인가요?
    무식한넘. 감히 질문합니다요.

  13. wjsdlfdhkd 2011.05.01 20:14 신고 댓글주소 | 수정 | 삭제 | 댓글

    wjsdlfdhkd@nate.com 네이트온친추좀 해주세요 ㅠ

  14. PE 2011.05.18 11:43 신고 댓글주소 | 수정 | 삭제 | 댓글

    제가 PE관련하여 발표를 하게 되었습니다.

    ppt자료에 그림을 쓰고 싶습니다.

    사용해도 되는지 먼저 문의 드립니다.

  15. cropin 2011.05.30 01:28 신고 댓글주소 | 수정 | 삭제 | 댓글

    제가 리버싱입문한지 얼마안되서 죄송한데
    offset과 address의 차이점이 무엇인지 설명해주실수 있나요?...

    • reversecore 2011.06.03 22:22 신고 댓글주소 | 수정 | 삭제

      안녕하세요.

      둘 다 거의 같은 개념인데요... 보통 리버싱에서는 아래와 같이 사용됩니다.

      offset 은 어떤 기준점에서 떨어진 거리(변위)를 나타내며 주로 파일내의 위치를 표현할 때 사용합니다. (상대 거리를 나타낼 때 주로 사용됨)

      address 는 주로 메모리의 절대 주소를 나타낼 때 사용됩니다. (절대 주소를 나타낼 때 주로 사용됨)

      감사합니다.

  16. 금강이 2011.09.19 16:43 신고 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요 요즘은 하루하루 방문해서 조금씩 공부하고 있습니다. ㅎㅎ
    사내에서 스터디 같은걸 하는데 PE 관련 교육을 하려고 합니다. 그림2번 PPT 파일에 사용해도 될지요?

  17. 2012.02.03 11:06 신고 댓글주소 | 수정 | 삭제 | 댓글

    조금 햇갈리는게 있는데 각 널패딩은 하위주소(그림에서 윗부분)에 종속되는건가요? 그러니까 각각의 헤더 및 섹션들의 널패딩은 그 아래부분인건가요?

  18. 라마르틴 2013.02.26 17:00 신고 댓글주소 | 수정 | 삭제 | 댓글

    dllinjection 먼저 읽고 pe구조 몰라서 읽었는데 역시 깔끔합니다. 그동안 몰랐던 것들이 머릿속에서 깔끔하게 정리되는 느낌.

  19. 이정민 2014.10.27 12:20 신고 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요.
    글을 읽다가 궁금한 점이 하나 생겨서 여쭤봅니다.
    RVA 와 VA에 대해서
    DLL파일 같은 경우 이미 그 위치에 다른 파일이있다면
    재배치 과정을 통해 다른 위치로 로딩되어야 하는데
    VA주소로 되어있다면 정상적인 접근이 안된다는게 이해가 안되는데요,

    첫번째로는
    각 프로세스마다 독립된 가상메모리를 사용하는데
    어떻게 메모리 주소가 겹칠 수 있는지를 모르겠고,

    두번째로는
    RVA 주소든 VA 주소든 하나의 고유한 메모리 주소인데
    VA주소로는 왜 접근이 안되는건지 모르겠습니다.

  20. 코딩하는 경제학도 2017.02.03 08:09 댓글주소 | 수정 | 삭제 | 댓글

    관리자의 승인을 기다리고 있는 댓글입니다





티스토리 툴바