반응형

DLL Injection 에 대해서 자세히 알아보겠습니다.

DLL Injection 기법은 다른 프로세스에 침투하는 가장 쉽고 강력한 방법입니다.



DLL Injection 이란?


다른 프로세스에 특정 DLL 파일을 강제로 삽입시키는 것입니다.

더 정확히 표현하면 다른 프로세스에게 LoadLibrary() API 를 호출하도록 명령하여 내가 원하는 DLL 을 loading 시키는 것입니다.

따라서 DLL Injection 이 일반적인 DLL loading 과 다른점은 loading 대상이 되는 프로세스가 내 자신이냐 아니면 다른 프로세스냐 하는 것입니다.

아래 그림을 봐주세요.


<Fig. 1>

notepad 프로세스에 myhack.dll 을 강제로 삽입시켰습니다.
(원래 notepad 는 myhack.dll 을 로딩하지 않습니다.)

notepad 프로세스에 로딩된 myhack.dll 은 notepad 프로세스 메모리에 대한 (정당한) 접근권한이 생겼기 때문에 사용자가 원하는 어떤 일이라도 수행할 수 있습니다. (예: notepad 에 통신기능을 추가하여 메신저나 텍스트 웹브라우저 등으로 바꿔버릴 수도 있습니다.)



DLL Injection 사용 목적


LoadLibrary() API 를 이용해서 어떤 DLL 을 로딩시키면 해당 DLL 의 DllMain() 함수가 실행됩니다.

DLL Injection 의 동작원리는 외부에서 다른 프로세스로 하여금 LoadLibrary() API 를 호출하도록 만드는 것이기 때문에 (일반적인 DLL loading 과 마찬가지로) 강제 삽입된 DLL 의 DllMain() 함수가 실행됩니다.

또한 삽입된 DLL 은 해당 프로세스의 메모리에 대한 접근권한을 갖기 때문에 사용자가 원하는 다양한 일(버그 패치, 기능 추가, 기타)을 수행 할 수 있습니다.

그런데 문제는 대부분 악의적인 용도로 사용된다는 것입니다.

- 악성 코드

정상적인 프로세스(winlogon.exe, services.exe, svchost.exe, explorer.exe, etc)에 몰래 숨어들어가 악의적인 짓들을 합니다. 다른 악성 파일을 다운받거나, 몰래 백도어를 열어두고 외부에서 접속하거나, 키로깅 등의 나쁜짓을 하는 것이죠.

정상 프로세스 내부에서 벌어지는 일이라서 일반적인 PC 사용자들은 눈치채기 어렵습니다.
만약 explorer.exe 가 80 포트로 어떤 사이트에 접속시도를 한다고 생각해보세요.
일반인들은 그냥 정상 프로세스의 정상적인 동작으로 생각하기 쉽습니다.

- 유해 프로그램, 사이트 차단 프로그램

부모님들께서 아이들의 건전한 PC 사용을 위해 설치하는 '프로세스 차단 프로그램' 등이 있습니다.
주로 게임 프로그램과 성인 사이트 접속 등을 방지하는 역할을 하지요.

아이들 입장에서야 원수(?)같은 프로그램이라서 미친듯이 해당 프로세스를 종료하려 듭니다.
DLL Injection 기법으로 정상 프로세스에 살짝 숨어들어간다면 들키지도 않고, 프로세스 강제 종료에도 안전하게 됩니다. (Windows 핵심 프로세스를 종료하면 시스템이 같이 종료되기 때문에 결국 '차단' 하려는 목적을 달성하는 셈이지요.)

- 기능 개선 및 (버그) 패치 

만약 어떤 프로그램의 소스 코드가 없거나 수정이 여의치 않을 때 DLL Injection 을 사용하여 전혀 새로운 기능을 추가하거나 (PlugIn 개념) 문제가 있는 코드, 데이타를 수정할 수 있습니다.
일단 프로세스 메모리에 침투한 DLL 은 해당 프로세스의 메모리에 자유롭게 접근 할 수 있기 때문에 코드, 데이타를 고칠 수 있습니다.
저도 예전에 인터넷에서 다운받은 헥사 에디터에 버그가 있길래 DLL Injection 기법을 이용해서 해당 버그 코드를 간단히 패치해서 사용한 적이 있습니다. (그냥 다른 헥사 에디터를 다운 받아도 써도 되지만 고쳐 쓰고 싶은 마음에... ^^)

- API Hooking

다음번에 설명드릴 주제인데요, API Hooking 에 DLL Injection 기법이 많이 사용됩니다.
정상적인 API 호출을 중간에서 후킹하여 제가 원하는 기능을 추가 (혹은 기존 기능을 제거)하는 목적으로 사용됩니다.

이 역시 삽입된 DLL 은 해당 프로세스의 메모리에 대한 접근 권한을 가지고 있다는 특성을 잘 활용한 것입니다.



DLL Injection 간단 실습


notepad.exe 프로세스에 myhack.dll 을 injection 시키도록 하겠습니다.
=> myhack.dll 은 www.naver.com/index.html 을 다운받도록 프로그래밍 하였습니다.

myhack.dll

InjectDll.exe


1) InjectDll.exe 와 myhack.dll 을 다운받아 C:\ 에 복사합니다.
2) 메모장(notepad.exe) 을 실행합니다.
3) InjectDll.exe 를 실행합니다.

Process Explorer 를 이용하여 notepad.exe 프로세스에 Injection 된 myhack.dll 을 확인해 보겠습니다.


<Fig. 2>

notepad.exe 프로세스 메모리 공간에 myhack.dll 이 들어와 있는게 보이시죠?

C:\index.html (네이버 초기화면 html) 파일이 정상적으로 생겼는지 볼까요?


<Fig. 3>

index.html 파일을 메모장으로 열어보겠습니다.


<Fig. 4>

정상적으로 notepad.exe 프로세스에 myhack.dll 이 Injection 되어 http://www.naver.com/index.html 파일을 받아왔습니다.

이와 같이 다른 프로세스에 침투하여 원하는 동작을 마음껏 수행할 수 있는 Dll Injection 기법의 원리와 구현 방법에 대해서 알아보겠습니다.


Dll Injection - 다른 프로세스에 침투하기 (2)


ReverseCore

반응형
  1. 베리굿 2009.11.09 19:22

    잘만 사용하면 무서운 기능이군요

    • reversecore 2009.11.09 21:36 신고

      네, 맞습니다.
      다른 프로세스에 자유롭게 드나들면서 메인 프로그램과는 별도로 다양한 작업을 할 수 있습니다.

  2. bywords 2010.10.29 23:47

    윈 7에서 실행이 안되는 것은 왜그러는 것인가요~

    • reversecore 2010.11.03 00:39 신고

      안녕하세요.

      실행이 안된다는 것인가요?
      아니면 index.html 을 받지 못한다는 말씀이신지요...

      제가 XP 와 7 에서 테스트를 완료하였습니다만...
      index.html 파일을 다운 받지 못하는 사례가 있습니다. 다양한 이유를 생각해 볼 수 있습니다. (보안 프로그램, 접속 사용자 권한, 네트워크 방화벽 정책, 기타)

      위의 예제는 DLL Injection 첫 예제로는 좋지 않은 것 같아서 바꿀 예정입니다.

      감사합니다.

  3. dngchn 2010.12.13 11:19

    위 컬럼과 유사하게 테스트 중입니다. notepad 대신 activeX 컨트롤을 포함하고 있는 타겟을 대상으로 dll을 인젝션시켰습니다. 인젝션 시킨 dll 내부에서 dll attach타이밍에 해당 activeX 컨트롤의 핸들을 얻어와서 CWnd::FromHandle을 통해 해당 컨트롤을 제어하려고 해보았는데(해당 ActiveX의 컨트롤이 CWnd를 상속받음), Debug Assertion 실패 에러가 나오네요. (이 의미가 서로 다른 쓰레드에서 해당 컨트롤을 접근하여 발생한 듯 한데..), 그런데, 타겟의 버튼 컨트롤에 대해서는 위와 같은 시도가 정상적으로 동작합니다. dll attach 타이밍에 핸들을 찾아 이를 통해 FromHandle하여 얻은 오브젝트를 Button으로 캐스팅하면 해당 버튼을 온전히 제어할 수 있습니다. Button과 ActiveX와 어떤 차이가 있을까요? ActiveX에 포함된 컨트롤을 제어하려면 어떻게 해야 할까요?

    • reversecore 2010.12.16 15:24 신고

      안녕하세요.

      메일로도 비슷한 질문을 해주셨죠?

      일반 버튼 컨트롤은 문제 없는데 유독 ActiveX 컨트롤만 Debug Assertion 에러가 나왔다구요?

      저도 일반 어플리케이션의 콘트롤들을 가지고 노는건 많이 해봤지만 ActiveX 객체는 한번도 해본 적이 없군요. 제가 부족한 부분이라고 생각됩니다.

      향후에는 ActiveX 관련 리버싱에 대해서도 공부를 잘 해놓아야 할것 같습니다.

      감사합니다.

  4. ASK! 2011.01.28 21:46

    안녕하세요..
    전 CORE님 책으로 리버싱 입문하고 싶어서
    책 나오기만을 기다리고 있는 미래의 독자(?)입니다..
    윗 댓글을 보니 머지않아 볼 수 있는건가요?+_+
    ㅎ 제가 글을 쓰느 이유는 궁금한게 있어서요.

    1. 책을 볼려면 어셈정도는 알고 봐야 하나요?
    2. C + API조금 알고 있는 상태에서 책을 구입하여 읽으면 괜찮을까요?

    음.. 써놓고 보니 1번 문제는 어쩌면 당연한거고..
    2번은 약간 질문에 모순이 있는거 같기도 하고..
    그냥 책을 읽기 위한 '선지식'에 대해 궁금해서요..

    글이 이것도 아니고 저것도 아니게 됬네요..
    쩝.. 아무튼 책 얼릉 보고싶네요! 수고하세요!

    • reversecore 2011.01.31 11:40 신고

      안녕하세요.

      일단 제 책을 기다려 주셔서 감사드립니다. ^^~

      1. 제 경우 어셈블리를 전혀 모르는 상태에서 리버싱에 입문하였습니다. 입문과정에서는 어셈블리 지식 보다는 디버거 사용법, Windows 구조 등이 좀 더 중요한 것 같습니다.

      2. C 언어와 Win32 API 는 어차피 잘 배우셔야 하므로 잘 알면 잘 알수록 좋지요. 너무 방대하다는게 문제지만요. 근데 모르는 사람들도 어찌어찌 잘 들 하십니다. 크게 걱정하지 마세요. 부딪혀서 그때 그때 자료 조사해서 나갈 수 있습니다.

      문의한 내용들은 어쩌면 리버싱 중/고급 과정으로 올라가기 위해 필요하다고 볼 수 있고요, 입문/초급 과정에는 심각하게 중요한 것은 아니라고 생각합니다. ^^

      감사합니다.

  5. unuruu 2011.12.04 10:10

    안녕하세요~

    혹시 dll 을 제가 정한 인젝터에만 연동 되게 하려면 어떻게해야하나요?
    이 소스 한번 봐줄수있으신지요 이렇게 했는데요
    이건 윈도우 폴더만 인식하더라구요
    그 정한 인젝터를 프로세스에서 실행됬을때로
    바꿀려면 어떻게해야하죠?
    int main(){

    HWND hwnd;

    hwnd = FindWindow(NULL,"test.exe");


    if (hwnd == 0){

    MessageBox(0,"text인젝터가 실행중이지 않잖아!","",0);

    return 0;
    }

    if (hwnd == NULL){


    MessageBox(0,"프로세스 없음","",0);
    }
    else{
    MessageBox(0,"전용 인젝터군!","",0);

    }
    return 0;
    }

    어디가 문제인지 알려줄수있으신가요?

    • reversecore 2011.12.21 01:48 신고

      안녕하세요.

      먼저 인젝터(Injector) 라는 용어를 DLL Injection 을 시켜주는 프로그램(InjectDll.exe) 으로 사용하신게 맞으시죠?

      윈도우 폴더만 인식한 다는 말씀은 잘 이해가 안가네요.

      코드를 보자면 "test.exe" 캡션을 가진 윈도우를 찾아서 없다면 "text인젝터가..." 메시지를 출력하고, 만약 있다면 "전용..." 메시지가 출력되겠네요.

      이 main() 코드가 인젝터 내부의 코드인가요?

      감사합니다.

  6. 독자 2013.01.07 11:29

    myhack.dll을 다음에서 악성코드로 분류했기 때문에 다운로드 할 수가 없습니다. 조치를 취해주시거나 새로 압축해서 올려주시면 감사하겠습니다.

    • reversecore 2013.01.12 01:02 신고

      안녕하세요.

      아, 그렇군요...

      제 실습예제에 포함되어 있으므로 그걸로 테스트 해보시기 바랍니다.

      http://www.reversecore.com/104

      23장의 myhack.dll 파일입니다. (블로그와 약간 주소가 달라질 수 있습니다.)

      감사합니다.

  7. 실습생 2013.02.07 20:03

    음 책에서 나온 예제를 따라하고 있는데 아래와 같은 오류로 진행이 안되고 있습니다. 어떻게 진행을 해야 할까요?
    C:\work>InjectDll.exe 4032 c:\work\myhack.dll
    The token does not have the specified privilege.

    • reversecore 2013.02.08 17:13 신고

      안녕하세요.

      아마 23장 예제를 따라하시는것 같습니다.
      그 예제는 32bit OS 에서 정상동작하는데요.

      사용하시는 OS 와 제가 만든 실습 예제가 맞는지(혹시 직접 만드신 건지) 확인해 주시기 바랍니다.

      또한 특정 시스템 프로세스에는 인젝션 되지 않습니다. (Windows Vista/7 에서는 약간 다른 방법을 사용합니다. 43장을 참고하시기 바랍니다.)

      감사합니다.

  8. BSUN 2013.03.15 15:29

    안녕하십니까.
    좋은 글을 올려주셔 감사합니다.
    플래시파일(swf)에도 인젝이 가능합니까.
    흥미삼아 IE로 플래시파일을 로드하여 여기에 DLL을 인젝시키려고 하였는데 제대로 동작하지 않습니다. 혹시 불가능한걸 제가 하겠다고 시도하는지...
    좀 가르쳐주세요.

    • reversecore 2013.04.01 22:27 신고

      안녕하세요.

      swf 파일은 일종의 데이터 파일이지요. DLL Injection 은 프로세스에게만 가능합니다. 따라서 플래쉬 플레이어 프로세스에게 인젝션 하는 것이 맞습니다.

      질문 내용에 따르면 IE 에게 DLL 인젝션 시키셔야 합니다.

      감사합니다.

  9. 모듈이.. 2014.01.17 10:21

    모듈이 접근 제한ㄴ이 걸렸습니다...

반응형


PE File Format


지금까지 오랜 시간에 걸쳐 PE(Portable Executable) File Format 에 대해 살펴보았습니다.

PE 스펙을 보면 각 구조체 멤버 하나하나 자세히 기술하고 있지만
리버싱에서 주목해야 하는 멤버들만 추려서 설명드렸습니다.

특히 IAT, EAT 에 관한 내용은 실행압축(Run-Time Packer), Anti-Debugging, DLL Injection, API Hooking
매우 다양한 중/고급 리버싱 주제들의 기반 지식이 됩니다.

hex editor 와 연필, 종이만 가지고 IAT/EAT 의 주소를 하나하나 계산해서
파일/메모리 에서 실제 주소를 찾는 훈련을 많이 해보시기 바랍니다.

쉽지 않은 내용이지만 그만큼 리버싱에서 중요한 위치를 차지하고 있기 때문에
고급 리버싱을 원하는 분들께서는 반드시 습득하셔야 합니다.



PEView.exe


간단하고 사용하기 편리한 PE Viewer 프로그램(PEView.exe)을 소개해 드립니다.
(개인이 만든 무료 공개 SW 입니다.)

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

아래는 PEView.exe 의 실행 화면입니다.


PE Header 를 각 구조체 별로 보기 쉽게 표현해주고, RVA <-> File Offset 변환을 간단히 수행해줍니다.
(제가 설명드렸던 내용과 용어 사용에 있어서 약간 틀릴 수 있습니다. 둘 다 익혀두시는게 의사소통에 좋습니다.)

위와 같은 PE Viewer 를 직접 제작해 보시는 것을 추천드립니다.
저 또한 처음 PE Header 를 공부할 때 (검증을 위해) 콘솔 기반의 PE Viewer 를 만들어서 지금까지 잘 사용하고 있습니다.
직접 제작하다 보면 자신이 잘 몰랐거나 잘 못 이해하던 부분을 정확히 파악하고 제대로 공부할 수 있습니다.



PE Patch


PE 스펙은 말 그대로 권장 스펙이기 때문에 각 구조체 내부에 보면 사용되지 않는 많은 멤버들이 많이 있습니다.

또한 말 그대로 스펙만 맞추면 PE 파일이 되기 때문에 일반적인 상식을 벗어나는 PE 파일을 만들어 낼 수 있습니다.

PE Patch 란 바로 그런 PE 파일을 말합니다.
PE 스펙에 어긋나지는 않지만 굉장히 창의적인(?) PE Header 를 가진 파일들입니다.
(정확히 표현하면 PE Header 를 이리저리 꼬아 놨다고 할 수 있습니다.)

PE Patch 만 해도 따로 고급 주제로 다뤄야 할 만큼 (리버싱에 있어서) 넓고도 깊은 분야입니다.

한가지만 소개해 드리겠습니다.
지금까지 배웠던 PE Header 에 대한 상식이 사뿐히 깨지는 경험을 하실 수 있습니다.
(그러나 PE 스펙에 벗어난건 없답니다.)

아래 사이트는 tiny pe 라고 가장 작은 크기의 PE 파일을 만드는 내용입니다.

http://blogs.securiteam.com/index.php/archives/675

411 byte 크기의 (정상적인) PE 파일을 만들어 냈습니다.
IMAGE_NT_HEADERS 구조체 크기만 해도 248 byte 라는걸 생각하면 이것은 매우 작은 크기의 PE 파일입니다.

다른 사람들이 계속 도전해서 304 byte 크기의 파일까지 나타나게 됩니다.

그리고 마지막으로 어떤 다른 사람이 위 사이트를 본 후 자극을 받아서
아래와 같이 극단적이고 매우 황당한 PE 파일을 만들어 내었습니다.

http://www.phreedom.org/solar/code/tinype/

이곳에 가면 Windows XP 에서 정상 실행되는 97 byte 짜리 PE 파일을 다운 받을 수 있습니다.
(2009년 4월 현재까지 최고 기록입니다.)
또한 PE Header 와 tiny pe 제작과정에 대한 내용을 자세히 설명하고 있어서 읽어보시면 크게 도움이 되실 겁니다.
(약간의 assembly 언어에 대한 지식이 요구됩니다.)

모두 다운 받아서 하나씩 분석해 보시기 바랍니다. 분명 크게 도움이 됩니다.



Epilogue


이러한 PE patch 파일들은 저뿐만 아니라 일반적인 리버서들의 고정관념을 깨트리는 내용이며
그래서 리버싱 공부가 더 즐겁습니다.

PE Header 에 대해서 다시 한번 강조 하고 싶은 내용은 아래와 같습니다.

- PE 스펙은 그저 스펙일 뿐이다. (만들어 놓고 사용되지 않는 내용이 많다.)
- 내가 지금 알고 있는 PE Header 에 대한 지식도 잘 못된 부분이 있을 수 있다.
   (tiny pe 외에도 PE header 를 조작하는 여러 창의적인 기법들이 계속 쏟아져 나온다.)
- 항상 모르는 부분을 체크해서 보강하자.

앞으로 제 블로그에서 다양한 형태의 PE 파일들을 분석할 예정입니다.
재밌고 특이한 PE Header 조작 기법에 대해서는 그때 그때 소개해 드리도록 하겠습니다.




반응형
  1. Ezbeat 2009.12.26 12:12

    오호... 97Byte짜리 PE파일... 신기하네요..

    • ReverseCore 2009.12.28 01:14

      Ezbeat님, 안녕하세요.

      그 파일을 한번 받아 보세요.
      프로그램이라고 할 수조차 없지만, PE 파일인건 확실합니다. ^^

  2. hd5535 2010.01.22 18:04

    앗! PE뷰어 찾고 있었는데, 잘쓸게요^ㅡ^*
    PE 내용도 잘 읽어보고 갑니다

    • reversecore 2010.01.23 14:39

      hd5535님, 안녕하세요.
      방문 감사드립니다. ^^

  3. straycat45 2010.01.27 03:10

    ^^* 우와~ ㅋㅋ
    파일 오프셋(RAW)구하는거랑, 임포트, 익스포트 함수가 이해 안돼서
    엄청 고생하고 있었는데, ㅠㅠㅋㅋ
    설명이 넘 잘돼있어요 ^^ㅋㅋ
    잘 보구 갑니다 !!

    • reversecore 2010.01.27 20:05 신고

      straycat45님, 안녕하세요.

      잘 봐주셔서 감사합니다.

      또 들러주세요~

  4. Vi0 2010.08.04 22:45

    정말 이런블로그가 있다는것에 감사합니다 !
    설명이 너무 자세하게 잘나와있네요.
    웬만하면 댓글같은거 달지않는데
    질문도 올리고 이렇게 좋은 설명 받아먹으면서
    감사하단 말도 써놓지 않기엔 양심이 걸려서..
    정말로 감사드립니다 : )
    www.reversecore.com/2 부터 쭉 읽고있는데
    다 읽으면 제 리버싱에 엄청난 발전이 있을거같은 기대감이 ~.~

  5. 초보 2011.03.16 00:58

    PE에 대한 글을 읽고 궁금증이 생겼습니다.
    Relese로 만들어진 파일도 이렇게 분석이 가능한가요?
    Debug로 만들어진 파일만..?

    • reversecore 2011.03.16 21:33 신고

      안녕하세요.

      Debug/Release 상관없이 전부 해석 가능합니다.
      두 버전의 차이는 주로 디버깅을 위한 추가적인 코드가 있느냐 없느냐의 차이입니다.

      감사합니다.

  6. 궁금한너구리 2011.07.17 03:39

    리버스코어님 강의 너무 좋습니다. (쵝오!!~^^b)
    이제야겨우 PE부분 다 봤네요.
    앞으로도 강의 열심히 습득하겠습니다.~ㅋㅋㅋㅋㅋㅋ

    아.. 그리고 하나 궁금한게 있는데요. 문뜩 떠오른건데
    notepad.exe 하고 보통 .txt로 저장된 파일하고, 아이콘모양은 같애보여도 다른파일이지요?
    예를들어 메모장에 문자열"AAAA"를 입력하고 저장한 파일은 메모장(notepad.exe)에 연결된
    파일이지만 실질적으로 "AAAA"문자열이 바이너리의 전부인거죠? 파일입출력하려는데 약간
    헷갈려서 질문합니다. ㅋㅋㅋ

    아!.. 또생각났는데요;; 만약 위에 AAAA 파일 바이너리가 AAAA뿐이라면 이 .txt파일의 제목은 어디다 저장하죠?

    • reversecore 2011.07.20 08:43 신고

      안녕하세요.

      파일은 확장자 보다는 파일의 형식에 의해서 구분되는 것이죠. 다만 윈도우즈 기본 쉘인 탐색기에서 확장자를 보고 아이콘을 연결해 준 것이구요.

      파일이름은 파일시스템(NTFS, FAT32)에 저장됩니다. 파일에 저장되지 않습니다.

      감사합니다.

  7. Genesis 2012.03.08 01:59

    오늘 dll 인젝션 공부할려고 검색하다가 왔는데 API 후킹에, PE 까지 잘 배우고 갑니다^^

  8. taso 2012.07.05 06:55

    리버싱을 본격적으로 시작한지 별로 안되서
    아직, 배운 내용이 얼마나 활용될진 아직 예측도 못하겠지만,,
    계속 공부하다보면 감사하단 말 안써놓은게 후회될거같아서 이렇게 댓글남겨요 ㅋ
    좋은 내용 잘 배우고 갑니다.
    계속 공부 열심히할게요 ㅋ

  9. ace 2012.07.12 14:51

    정말 글 잘읽었습니다. 힘들게 공부하신거 이렇게 쉽게 가져가도 될까 싶을정도로 쉽게 잘 정리해놓으셨네요!! 자주 들러서 공부하겠습니다. 감사합니다~

반응형


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. 이전 댓글 더보기
  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

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

    • reversecore 2010.02.21 11:36 신고

      graythief님, 안녕하세요.

      네~ 실습 잘 하시고 또 들러 주세요~

      감사합니다.

  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

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

    • reversecore 2011.11.18 18:34 신고

      안녕하세요.

      네, 사실 초보자에게 리버싱은 어려운 학문입니다.

      그래서 더 매력이 있는것 같습니다.

      감사합니다.

  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

    안녕하세요 ^^

    궁금한게 있어서 이렇게 글을 남깁니다

    kernel32.dll 의 ImageBase = 7C7D0000h

    ImageBase = 7C7D0000h

    라는걸 어떻게 알수있어요 ???

  20. 정말 좋은 글 많이 읽었습니다.
    이제야 Window API 공부하고 있는 초보가 메모리 부분 공부하다가 PE Header를 검색해보라는 말만 듣고 와서 읽었는데... ㅎㅎ 머리 깨지는 것 같네요.
    리버싱은 아직 제가 건드릴 수 있는 분야가 아닌것 같지만 적어도 파일과 메모리에 대해 깊이있게 이해하게 된 것 같아 기쁩니다.
    감사합니다.

  21. windy21 2017.12.28 11:05

    DLL 파일은 OllyDbg에서 실행이 안되는데
    어떻게 실습하신건가요?.?

반응형


IAT (Import Address Table)


PE Header 를 처음 배울때 최대 장벽은 IAT(Import Address Table) 입니다.

IAT 에는 Windows 운영체제의 핵심 개념인 process, memory, DLL 구조 등에 대한 내용이 함축되어 있습니다.
즉, IAT 만 잘 이해해도 Windows 운영체제의 근간을 이해한다고 할 수 있습니다.

IAT 란 쉽게 말해서 프로그램이 어떤 라이브러리에서 어떤 함수를 사용하고 있는지를 기술한 테이블 입니다.



DLL (Dynamic Linked Library)


IAT 를 설명하기 앞서 Windows OS 의 근간을 이루는 DLL(Dynamic Linked Library) 개념을 짚고 넘어가야 합니다.
(뭐든지 이유를 알면 이해하기 쉬운 법이지요...)

DLL 을 우리말로 '동적 연결 라이브러리' 라고 하는데요, 그 이유를 알아 보겠습니다.

16 bit DOS 시절에는 DLL 개념이 없었습니다. 그냥 'Library' 만 존재하였습니다.

예를 들면 C 언어에서 printf() 함수를 사용할 때 컴파일러는 C 라이브러리에서
해당 함수의 binary 코드를 그대로 가져와서 프로그램에 삽입(포함)시켜 버렸습니다.
즉, 실행 파일내에 printf() 함수의 바이너리 코드를 가지고 있는 것입니다.

Windows OS 에서는 Multi-Tasking 을 지원하기 때문에 이러한 라이브러리 포함 방식이 비효율적이 되어 버렸습니다.

32 bit Windows 환경을 제대로 지원하기 위해 기본적으로 매우 많은 라이브러리 함수(process, memory, window, message, etc)를 사용해야 합니다.

여러 프로그램이 동시에 실행되야 하는 상황에서 모든 프로그램마다 위와 같이 동일한 라이브러리가 포함되어서 실행된다면
심각한 메모리 낭비를 불러오게 됩니다. (물론 디스크 공간의 낭비도 무시할 수 없지요.)

그래서 Windows OS 설계자들은 (필요에 의해) 아래와 같은 DLL 개념을 고안해 내었습니다.

"프로그램내에 라이브러리를 포함시키지 말고 별도의 파일(DLL)로 구성하여 필요할 때마다 불러쓰자."
"일단 한번 로딩된 DLL 의 코드, 리소스는 Memory Mapping 기술로 여러 Process 에서 공유해 쓰자."
"라이브러리가 업데이트 되었을때 해당 DLL 파일만 교체하면 되니 쉽고 편해서 좋다."


실제 DLL 로딩 방식은 2가지 입니다.
프로그램내에서 사용되는 순간에 로딩하고 사용이 끝나면 메모리에서 해제 시키는 방법(Explicit Linking)
프로그램 시작할 때 같이 로딩되어 프로그램 종료 할 때 메모리에서 해제되는 방법(Implicit Linking)이 있습니다.

IAT 는 바로 Implicit Linking 에 대한 매카니즘을 제공하는 역할을 합니다.

IAT 의 확인을 위해 OllyDbg notepad.exe 를 열어보겠습니다.
아래 그림은 kernel32.dll CreateFileW 를 호출하는 코드입니다.


CreateFileW 를 호출할 때 직접 호출하지 않고 01001104 주소에 있는 값을 가져와서 호출합니다.
(모든 API 호출은 이런 방식으로 되어 있습니다.)

01001104 주소는 notepad.exe 의 ".text" 섹션 메모리 영역입니다. (더 정확히는 IAT 메모리 영역입니다.)
01001104 주소의 값은 7C8107F0 이며, 
7C8107F0 주소가 바로 notepad.exe 프로세스 메모리에 로딩된 kernel32.dll 의 CreateFileW 함수 주소입니다.

여기서 한가지 의문이 생깁니다.
"그냥 CALL 7C8107F0 이라고 하면 더 편하고 좋지 않나요?"
컴파일러가 CALL 7C8107F0 이라고 정확히 써줬다면 더 좋지 않냐는 의문이 들 수 있습니다만,
그건 바로 위에서 설명 드렸던 DOS 시절의 방식입니다.

notepad.exe 제작자가 프로그램을 컴파일(생성)하는 순간에는 이 notepad.exe 프로그램이
어떤 Windows(9X, 2K, XP, Vista, etc), 어떤 언어(KOR, ENG, JPN, etc), 어떤 Service Pack 에서
실행 될 지 도저히 알 수 없습니다.

위에서 열거한 모든 환경에서 kernel32.dll 의 버전이 틀려지고, CreateFileW 함수의 위치(주소)가 틀려집니다.
모든 환경에서 CreateFileW 함수 호출을 보장하기 위해서 컴파일러는 CreateFileW 의 실제 주소가 저장될 위치(01001104)를
준비하고 CALL DWORD PTR DS:[1001104] 형식의 명령어를 적어두기만 합니다.


파일이 실행되는 순간 PE Loader 가 01001104 의 위치에 CreateFileW 의 주소를 입력해줍니다.

또 다른 이유는 DLL Relocation 때문입니다.
일반적인 DLL 파일의 ImageBase 값은 10000000h 입니다.

예를 들어 어떤 프로그램이 a.dll 과 b.dll 을 사용한다고 했을때,
PE Loader는 먼저 a.dll 을 ImageBase 값인 메모리 10000000h 에 잘 로딩합니다.
그 다음 b.dll 을 ImageBase 값인 메모리 10000000h 에 로딩하려고 봤더니, 이미 그 주소는 a.dll 이 사용하고 있었습니다.
그래서 PE Loader 는 다른 비어있는 메모리 공간(ex:3E000000h) 을 찾아서 b.dll 을 로딩시켜 줍니다.

이것이 DLL Relocation 이며 실제 주소를 하드코딩 할 수 없는 이유입니다.
또한 PE Header 에서 주소를 나타낼때 VA 를 쓰지 못하고 RVA 를 쓰는 이유이기도 합니다.

* DLL 은 PE Header 에 명시된 ImageBase 에 로딩된다고 보장할 수 없습니다.
 
반면에 process 생성 주체가 되는 EXE 파일은 자신의 ImageBase 에 정확히 로딩되지요. 
  (자신만의 가상 메모리 공간을 가지기 때문입니다.)


이것은 매우 중요한 설명입니다. 다시 한번 잘 읽어보시기 바랍니다.

이제 IAT 의 역할을 이해할 수 있으실 겁니다.
(아래에서 설명드릴 IAT 구조가 왜 이리 복잡해야 하는지에 대해서도 약간 이해가 되실 겁니다.)



IMAGE_IMPORT_DESCRIPTOR


PE 파일은 자신이 어떤 라이브러리를 Import 하고 있는지 IMAGE_IMPORT_DESCRIPTOR 구조체에 명시하고 있습니다.

* Import : library 한테서 서비스(함수)를 제공 받는 일
* Export : library 입장에서 다른 PE 파일에게 서비스(함수)를 제공 하는 일


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

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            
        DWORD   OriginalFirstThunk;       // INT(Import Name Table) address (RVA)
    };
    DWORD   TimeDateStamp;
    DWORD   ForwarderChain; 
    DWORD   Name;                         // library name string address (RVA)
    DWORD   FirstThunk;                   // IAT(Import Address Table) address (RVA)
} IMAGE_IMPORT_DESCRIPTOR;

typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD    Hint;                         // ordinal
    BYTE    Name[1];                      // function name string

} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

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

일반적인 프로그램에서는 여러 개의 Library 를 Import 하기 때문에 
Library 의 갯수 만큼 위 구조체의 배열 형식으로 존재하게 되며, 구조체 배열의 마지막은 NULL 구조체로 끝나게 됩니다.

IMAGE_IMPORT_DESCRIPTOR 구조체에서 중요한 멤버는 아래와 같습니다. (전부 RVA 값을 가집니다.)

  • OriginalFirstThunk : INT(Import Name Table) 의 주소(RVA)
  • Name : Library 이름 문자열의 주소(RVA)
  • FirstThunk : IAT(Import Address Table) 의 주소(RVA)

* PE Header 에서 'Table' 이라고 하면 '배열' 을 뜻합니다.
* INT 와 IAT 는 long type (4 byte 자료형) 배열이고 NULL 로 끝납니다. (크기가 따로 명시되어 있지 않습니다.)
* INT 에서 각 원소의 값은 IMAGE_IMPORT_BY_NAME 구조체 주소값을 가지고 있습니다.
   (IAT 도 같은 값을 가지는 경우가 있습니다.)
* INT 와 IAT 의 크기는 같아야 합니다.


아래 그림은 notepad.exe 의 kernel32.dll 에 대한 IMAGE_IMPORT_DESCRIPTOR 구조를 표시하고 있습니다.


<Fig. IAT 구조>

PE Loader
가 Import 함수 주소를 IAT 에 입력하는 기본적인 순서를 설명드리겠습니다.

1. IID 의 Name 멤버를 읽어서 라이브러리의 이름 문자열("kernel32.dll")을 얻습니다.
2. 해당 라이브러리("kernel32.dll")를 로딩합니다.
3. IID 의 OriginalFirstThunk 멤버를 읽어서 INT 주소를 얻습니다.
4. INT 에서 배열의 값을 하나씩 읽어 해당 IMAGE_IMPORT_BY_NAME 주소(RVA)를 얻습니다.
5. IMAGE_IMPORT_BY_NAME 의 Hint(ordinal) 또는 Name 항목을 이용하여 해당 함수("GetCurrentThreadId")의 시작 주소를 얻습니다.
6. IID 의 FirstThunk(IAT) 멤버를 읽어서 IAT 주소를 얻습니다.
7. 해당 IAT 배열 값에 위에서 구한 함수 주소를 입력합니다.
8. INT 가 끝날때까지 (NULL 을 만날때까지) 위 4 ~ 7 과정을 반복합니다.


위 그림에서는 INT 와 IAT 의 각 원소가 동시에 같은 주소를 가리키고 있지만 그렇지 않은 경우도 많습니다.
(변칙적인 PE 파일에 대해서는 향후 많은 파일을 접해보면서 하나씩 배워 나가야 합니다.)



notepad.exe 를 이용한 실습


실제로 notepad.exe 를 대상으로 하나씩 살펴 보겠습니다.

그런데 실제 IMAGE_IMPORT_DESCRIPTOR 구조체 배열은 PE 파일의 어느 곳에 존재할까요?
PE Header 가 아닌 PE Body 에 위치합니다.

그곳을 찾아가기 위한 정보는 역시 PE Header 에 있습니다.
바로 IMAGE_OPTIONAL_HEADER32.DataDirectory[1].VirtualAddress 값이 
실제 IMAGE_IMPORT_DESCRIPTOR 구조체 배열의 시작 주소 입니다. (RVA 값입니다.)

IMAGE_IMPORT_DESCRIPTOR 구조체 배열을 다른 용어로는 IMPORT Directory Table 이라고도 합니다.
(위 용어를 전부 알아두셔야 남들과 의사소통이 원활해 집니다.)

IMAGE_OPTIONAL_HEADER32.DataDirectory[1] 구조체 값은 아래와 같습니다.
(첫번째 4 byte 가 VirtualAddress, 두번째 4 byte 가 Size 멤버입니다.)


 offset   value   description
----------------------------------------------
...
00000158 00000000 RVA  of EXPORT Directory

0000015C 00000000 size of EXPORT Directory

00000160 00007604 RVA  of IMPORT Directory
00000164 000000C8 size of IMPORT Directory

00000168 0000B000 RVA  of RESOURCE Directory
0000016C 00008304 size of RESOURCE Directory
...

* 위 구조체에 대해 궁금하신 분들께서는 IMAGE_OPTIONAL_HEADER 설명 을 참고하시기 바랍니다.
* DataDirectory 구조체에서 Size 멤버는 중요하지 않습니다. (PE Loader 에서 사용되지 않는 값입니다.)

위 그림에서 보듯이 RVA 가 7604h 이니까 File Offset 은 6A04h 입니다.
파일에서 6A04h 를 보면 아래 그림과 같습니다.


그림에서 파란색으로 표시된 부분이 전부 IMAGE_IMPORT_DESCRIPTOR 구조체 배열이고,
빨간 테두리로 되어 있는 부분은 구조체 배열의 첫번째 원소입니다. 
(참고로 배열의 마지막은 NULL 구조체로 되어 있는 것도 확인 할 수 있습니다.)

빨간 테두리의 IMAGE_IMPORT_DESCRIPTOR 구조체를 각 멤버별로 살펴보겠습니다.

OriginalFirstThunk (INT) = 7990h (file offset : 6D90h)
TimeDateStamp            = FFFFFFFFh
ForwarderChain           = FFFFFFFFh

Name                     = 7AACh (file offset : 6EACh)
FirstThunk (IAT)         = 12C4h (file offset : 6C4h)

우리는 IAT 를 공부하는 입장이기 때문에 hex editor 를 이용하여 하나하나 따라가도록 하겠습니다.
(편의를 위해 위 구조체 값(RVA) 를 미리 file offset 으로 변환해 놓았습니다.)

* RVA 를 file offset 으로 변환하는 방법에 대해서는 IMAGE_SECTION_HEADER 설명을 참고하세요.

그럼 순서대로 진행해 볼까요?


1. 라이브러리 이름 (Name)

Name 멤버를 따라가면 쉽게 구할 수 있습니다. (RVA : 7AACh -> file offset : 6EACh)


2. OriginalFirstThunk - INT(Import Name Table)

OriginalFirstThunk 멤버를 따라 갑니다. (RVA : 7990h -> file offset : 6D90h)


위 그림이 INT 입니다. 주소 배열 형태로 되어 있습니다. (배열의 끝은 NULL 로 되어 있습니다.)
주소값 하나 하나가 각각의 IMAGE_IMPORT_BY_NAME 구조체를 가리키고 있습니다. (<Fig. IAT 구조> 참고)

배열의 첫번째 값인 7A7Ah (RVA) 를 따라가 볼까요?

3. IMAGE_IMPORT_BY_NAME

RVA 값 7A7Ah 는 file offset 으로 6E7Ah 입니다.


앞에 2 byte 는 Hint (ordinal) 로써 라이브러리에서 함수의 고유번호 입니다.
ordinal 뒤로 "PageSetupDlgW" 함수 이름 문자열이 보이시죠? (문자열 마지막은 '\0' - C 언어와 동일)

여기까지 정리하면 INT 는 "함수 이름 주소 배열" 인데 첫번째 원소가 가리키는 함수 이름은 "PageSetupDlgW" 였습니다.

이제 IAT 에 해당 함수가 실제 메모리에 매핑된 주소를 얻어서 (GetProcAddress API 참고) IAT 에 입력하면 됩니다.

4. FirstThunk - IAT (Import Address Table)

IAT 의 RVA 값은 12C4h 이고 file offset 으로는 6C4h 입니다.


위 그림이 "comdlg32.dll" 라이브러리에 해당하는 IAT 입니다.
INT 와 마찬가지로 주소 배열 형태로 되어 있으며 배열의 끝은 NULL 입니다.

IAT 의 첫번째 원소값은 이미 76324906h 로 하드 코딩되어 있습니다.
notepad.exe 파일이 메모리에 로딩될 때 이 값은 위 3번에서 구한 정확한 주소값으로 대체 됩니다.

* 사실 제 시스템(Windows XP SP3) 에서 76324906h 주소는 comdlg32.dll!PageSetupDlgW 함수의 정확한 주소값입니다.
* MS 가 서비스팩을 배포하면서 관련 시스템 파일을 재빌드 할때 이미 정확한 주소를 하드 코딩 한것입니다.
  (일반적인 DLL 은 IAT 에 실제 주소가 하드 코딩되어 있지 않고, INT 와 같은 값을 가지는 경우가 많습니다.)
* 참고로 일반적인 DLL 파일은 ImageBase 가 10000000h 으로 되어 있어서 보통 DLL relocation 이 발생하지만,
   Windows 시스템 DLL 파일들(kernel32, user32, gdi32, etc)은 고유의 ImageBase 가 있어서 
   DLL relocation 이 발생하지 않습니다.


OllyDbg 를 이용해서 notepad.exe 의 IAT 를 확인해 보겠습니다.


notepad.exe 의 ImageBase 값은 01000000h 입니다.
따라서 comdlg32.dll!PageSetupDlgW 함수의 IAT 주소는 010012C4h 이며 76324906h 로 정확한 값이 들어와 있습니다.

* XP SP3 notepad.exe 를 다른 OS (2000, Vista, etc) 혹은 다른 ServicePack(SP1, SP2) 에서 실행하면,
  010012C4h 주소에는 다른 값이 세팅됩니다. (그 OS 혹은 ServicePack 에 있는 comdlg32.dll!PageSetupDlgW 의 주소)


해당 주소(76324906h)로 가면 아래와 같이 comdlg32.dll 의 PageSetupDlgW 함수 시작이 나타납니다.




이상으로 IAT(Import Address Table) 에 대한 기본 설명을 마치겠습니다.

IAT 는 Windows 리버싱에서 중요한 개념이기 때문에 반드시 잘 익혀두셔야 합니다.

향후 변칙적인 IAT 를 가지는 PE Patch 을 볼 때 IAT 를 다시 한번 살펴볼 기회가 있을것입니다.

다음번에는 PE Header 설명의 마지막으로 EAT(Export Address Table) 에 대해서 공부해 보겠습니다.



반응형
  1. 이전 댓글 더보기
  2. Ezbeat 2010.09.02 13:55

    으음.. 예전 내용을 까먹어.. 다시 봐보던중 궁금한 점이 생겨서 질문드려보겠습니다.
    전 실행파일을 notepad.exe로 하지 않고 그냥 VS에서 직접 만든 파일로 했습니다.

    환경 : Windows 7, Visual Studio 2008 ( Release, /MT )

    직접 만든 파일의 IAT를 따라가본 결과 OrigianlFirstThunk에는 아무런 값이 없었습니다.
    하지만 FirstThunk를 따라가보니 사용될 함수명들이 쭉있더군요.
    ( 하드코딩된 API함수의 주소가 아닌 단순 문자열 )

    그리고 메모리에 실행파일을 올린 후 해당 테이블을 보니 실제 API주소가 올라와 있었습니다.
    (FirstThunk에 있던 문자열 주소 부분에 실제 주소로 바뀌어 있었음)

    위 설명에선 OriginalFirstThunk에 있던 문자열을 가지고 실제 주소를 얻어와 FirstThunk가 가리키는 테이블에 실제주소를 넣는다는 말과는 쫌 다르길래 이렇게 질문을 드려봅니다 ;;

    컴파일 환경에 따라 이렇게 달라질수도 있는건가요?? XD

    • ReverseCore 2010.09.04 16:48

      네, 그런식으로 만들어진 PE 파일도 많습니다.

      OriginalFirstThunk(INT), FirstThunk(IAT) 에서 보듯이 둘 다 FirstThunk 가 맞습니다.

      PE Loader 는 OriginalFirstThunk 로 API 이름 문자열을 찾을 수 없다면 FirstThunk 로 시도를 합니다.

      둘 중 어디서 이름 문자열을 찾았다 하더라도 그 API 의 실제 로딩된 주소는 (로딩시점에) IAT 에 세팅됩니다.

      실행 압축 파일들을 다양하게 보신다면 별의별 특이한 구조의 IAT 를 볼 수 있습니다.

      감사합니다.

  3. Ezbeat 2010.09.08 08:37

    오호.. 그렇군요.. :-) 이해가 잘 되었습니다.
    항상 친절한 답변 감사합니다~!

  4. kyungchan 2010.10.30 01:41

    머리에 쏙쏙 들어오네요ㅎㅎ 정말 감사합니다^^;

  5. 리버스코어님... 2011.01.11 21:37

    죄송한데 라이브러리가 무엇인지...?

    • reversecore 2011.03.16 21:41 신고

      안녕하세요.

      쉽게 얘기해서 OS 에서 제공된 함수 모음이라고 생각하시면 됩니다. Win32 API(Application Programming Interface)라고 합니다.

      어떤 OS 던지 라이브러리를 제공해주지 않으면 개발자가 응용프로그램을 개발할 수 없습니다.

      예를 들어서 C 언어의 printf() 함수를 생각해 보겠습니다. 콘솔창에 문자열을 표시하는 라이브러리 함수인데요. 저 함수가 없다면... 제가 직접 화면출력, 키보드입력, 메모리제어, 콘솔창 생성 등을 전부 만들어야 합니다. 거의 미니 OS 를 만드는 셈이지요. ^^

      감사합니다.

  6. 내이름조용범 2011.01.12 10:18

    ===================내용정리=======================
    옛날 도스시적때에는 라이브러리 함수를 호출할때 컴파일러가 호출할려는 함수 코드를 그대로 삽입하였다.windows는 멀티캐스팅(동시에 여러가지일)이 가능해서 도스시절의 방식은 비효율적[그래서 windows에선 함수를 그대로 삽입하지않는다는말인가요?]또한 도스때에는 함수주소를 직접사용했는데 윈도우에서는(윈도우 종류가 많으니까...2000/xp 등등)각각 다르기때문에 주소를 그대로 사용하지 않는다.[그러면 라이브러리함수는 모두 주소가 정해져있단 말인가요?,단지 운영체제 종류에 따라 설계가 다를뿐?]그래서 윈도우계열은 어떠한주소에 담아서 쓴다
    =================맞나요?======================

    • reversecore 2011.03.16 22:21 신고

      안녕하세요.

      제가 답변이 너무 늦었네요~ 죄송합니다.

      네, 개념을 비슷하게 잡으신것 같습니다.

      그런데 약간 거칠은 그 개념을 확실히 자기것으로 만드시려면 PE 파일 포멧의 IAT 와 EAT 를 실제로 따라가 보셔야 합니다. 그 과정을 통해서 개념이 점점 정교해 지는 것이지요.

      감사합니다.

  7. vio 2011.03.14 22:38

    번외질문 같은데요
    IMAGE_IMPORT_BY_NAME 구조체에서
    BYTE NAME[1]
    이 변수에 들어있는 값은 함수 이름의 첫번째 글자인가요?

    • reversecore 2011.03.16 21:45 신고

      안녕하세요.

      그것은 가변적인 크기의 문자열을 표시할 때 사용되는 C 언어만의 기법이라고 보시면 됩니다.

      문자열이 오긴 오는데... 크기는 가변적이기 때문에 배열의 크기를 명확히 지정할 수 는 없습니다.

      사용할 때는 먼저 문자열 크기만큼 메모리를 할당 받아서 사용합니다. 그때 NAME 은 그 문자열의 시작 주소가 됩니다.

      따라서 문의하신 내용에 대한 답변은 첫번째 글자도 맞는 말이지만, 좀 더 기술적인 표현은 문자열의 시작주소, 즉 문자열이란 얘기지요. ^^

      감사합니다.

    • vio 2011.03.18 00:11

      어떻게 구현이 되는건지 감이안잡히는군요..
      저도 처음에 그 추축을 해보긴 했지만
      포인터변수도 아닌 배열이기때문에,,
      char asdf[10];
      asdf=0x1234;//불가
      배열의 이름이 나타내는 주소값은 변경할 수 없잖아요
      포인터변수가 아닌 저렇게 표현한건 합당한 이유가있을거같은데 전 모르겠군요 ㅠㅠ..
      코딩하는게 아니라 시스템이 돌아가는 것이기 때문에 가능한건가..? 신기하네요

    • vio 2011.03.25 16:38

      제가 너무 코딩을 하는쪽의 입장에서 생각한거같기도하네요
      어차피 컴파일러가 올바를 위치에 주소값을 써주는 방식일텐데요
      그런데 다른 주소값음 DWORD로 표시하고 저부분은 BYTE 배열로 한 이유는..궁금하네요ㅎ

    • reversecore 2011.04.13 10:35 신고

      일종의 C 언어의 프로그래밍의 기법인데요...

      아래와 같은 코드로 동작한다고 이해하시면 될 것 같습니다.

      BYTE *pBuf = new BYTE[1024];
      memset(pBuf, 0, 1024);

      PIMAGE_IMPORT_BY_NAME pImportByName = (PIMAGE_IMPORT_BY_NAME)pBuf;

      ...

      // Name 세팅
      // 구조체 정의에 따르면 Name 은 1 바이트 크기의 배열 이지만...
      // 현재 pImportByName->Name 의 크기는 1022 바이트가 됩니다.
      strcpy(pImportByName->Name, "ReverseCore");


      BYTE 배열을 쓰는 이유는 위 코드에서 보시는 것처럼 직접 그 위치에 문자열을 배치시키기 위함이지요.


      C 언어 컴파일러는 자료형의 타입을 체크하기 때문에 프로그래밍이 까다로울 수 있는데요. 개념을 이해하시면 타입 변환이 훨씬 쉬워집니다.

      감사합니다.

  8. TeamKhan 2011.04.08 20:00

    댓글에 달린 질문들을 읽어보다가 문득 궁금증이 생겨서 여쭤보게되는데요
    e_lfanew 에 값에따라서 IMAGE_OPTIONAL_HEADER 값이 바뀐다고 하셧는데
    e_lfanew 에는 NT_HEADER의 주소값이 명시되어잇는데
    NT_HEADER주소값에 따라서 당연히 INAGE_OPTION_HEADER의 주소 값이 바뀌는건 알겟는데요
    어떻게 NT_HEADER시작지점에서부터 여기까지가 FILE_HEADER이다~
    여기서부터 여기까지는 OPTIONAL_HEADER 이내~인걸 파악할수 잇으신건가요 ?
    FILE_HEADER 와 OPTIONAL_HEADER 들의
    크기를 알고계셔서 그런걸 파악할수 잇으셧던건가여?
    그리고 OPTIONAL_HEADER의 시작위치를
    파악하엿을떄 virtual[1].address의 시작지점을
    어떻게 파악하실수 잇으셧던건가요 ?
    상세하게 설명좀 부탁드려요ㅠ
    그리고 PEview 로 분석하는 습관을 들이는게좋나요
    아니면 헥스에디터가 좋은가요 ?
    저는 개인적으로 핵스에디터로 설명을 해주셔서
    그런지 핵스에디터가 더 편한거같기도한데..

    • reversecore 2011.04.12 21:48 신고

      안녕하세요.

      IMAGE_FILE_HEADER, IMAGE_OPTIONAL_HEADER 위치를 어떻게 그리 쉽게 알 수 있었냐는 질문이신거죠?

      그건 IMAGE_NT_HEADERS 구조체의 정의를 살펴보시면 쉽게 이해하실 수 있습니다.

      typedef struct _IMAGE_NT_HEADERS {
      DWORD Signature;
      IMAGE_FILE_HEADER FileHeader;
      IMAGE_OPTIONAL_HEADER32 OptionalHeader;
      } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

      바로 IMAGE_FILE_HEADER 와 IMAGE_OPTIONAL_HEADER32 는 IMAGE_NT_HEADERS 의 멤버이기 때문이지요. C 언어의 구조체는 크기가 결정되어 있기 때문에 IMAGE_NT_HEADERS 구조체의 시작 주소를 알면 내부 멤버는 쉽게 알아낼 수 있습니다. ^^

      참고로 IMAGE_NT_HEADERS 위치는 e_lfanew 값으로 결정되구요.

      그리고 처음 공부하시는 입장에서는 PEView 와 Hex Editor 를 같이 놓고 보시는 것이 좋습니다.

      감사합니다.

  9. reversingk 2011.05.18 13:42

    좋은 강의 감사 드립니다.^^ 강의를 보다 궁굼한점이 생겨 이렇게 질문을 드립니다.

    IAT하는 과정에서 IMAGE_IMPORT_DESCRIPTOR 구조체 값중 제일 먼저
    NAME 멤버 값을 가져와 LOADLIBRARY 함수를 이용하여 DLL을 인젝션 시킨다고 되어있는데여
    KELNER32.DLL이 매핑 되어 있지않다면 LOADLIBRARY 함수도 사용 못하지 안나여?? 그럼
    LOADLIBRARY 함수를 사용하기 위해서 KELNER32.dll은 따로 매핑이 되는지 된다면 어떻게 매핑이 이루어 지는지 궁굼해서 질문을 드리게되었습니다^^

    • reversecore 2011.05.18 19:56 신고

      안녕하세요.

      좋은 질문입니다~ 개념을 제대로 이해하신것 같습니다.

      지금 다시 본문을 읽어보니 <Fig. IAT 구조> 그림 밑의 박스 설명에 제가 분명히 "LoadLibrary("kernel32.dll") 과 GetProcAddress("GetCurrentThreadId") 라고 명시를 하였군요.

      개념적으로 저 API 의 의미와 같다는 뜻이었는데요. 확실히 오해의 소지가 있습니다. 수정 하도록 하겠습니다. ^^

      reversingk 님께서도 개념적으로 같다고 이해해 주시면 되겠습니다. 실제 프로세스를 생성하고 ntdll.dll, kernel32.dll 라이브러리를 로딩하는 것은 커널의 역할 입니다.

      감사합니다.

  10. reversingk 2011.05.18 16:47

    하나더 궁굼한것이 생겼습니다.^^;;
    귀찬게 하는거 아닌지 모르겠네여 ㅠㅠ

    windows7에서 실습을 하였습니다. 대상 notepad.exe

    강의를 보고 하나씩 따라가다보니 dll파일은 ADVAPI32.DLL파일이면
    첫 임포트 시키는 함수는 RegSetValuExW 함수입니다.
    peview 에서 RegSetValouExW 함수의 주소가 77 C7 1C 82로 나왔습니다.

    ImageBase(01000000) + FirstTrunk(1000) = 01001000
    상대주소를 구하고 ollydbg로 확인을 해보니 그런 주소가 없다고 나오기레

    ollydbg의 기능중 All Intermodule calls를 이용해서 RegSetValueExW함수를 찾아
    들어 가보니 상대주소 값이 B81000으로 나오고 RegSetValueExW 함수의 주소는
    76 7f 1c 82로 나왔습니다.

    이상해서 peviwe 로 구한 regSetValueExW (77 c7 1c 82)값이 였는데 왜 이런 값이 나올까
    생각 하다. 이 peviwe 의 RegSetValuExW값과 ollydbg RegSetValueExW 값을 빼보니00B80000 으로 나왔습니다. 여기서 00B80000 + FirstTrunk(1000)값을 더하니 메모리에 매핑 되었을때의
    IAT 주소가 구해졌습니다.

    그리고 ollydbg로 notepad.exe를 디버깅 할때 마다 그 값이 틀려 지던군요
    ImageBase 값은 010000000 으로 나왔는데 왜 이렇게 값이 나오는지 궁굼 합니다.

    pe 파일 포멧 (4) 에서도 제가 질문한 것이 비슷 한거 같은데 그 이유가 같다면
    여기 하나에만 답해주셔도 감사 하겠습니다 ^^

    매번 좋은 강의 보고 질문만 드려 귀찬게 하는것 같다 마음이 아픈데 어떻게 보담을
    드려야 할지 모르겠습니다(__) 너무 감사합니다. !!!

    • reversecore 2011.05.18 19:57 신고

      안녕하세요.

      Vista, 7 에서 추가된 ASLR(Address Space Layout Randomization) 기법 때문입니다.

      아래 포스트를 참고하시기 바랍니다.

      http://www.reversecore.com/69

      감사합니다.

  11. 정민 2011.05.30 16:20

    설명이 너무 쉽고 깔끔하게 잘 되있네요~ 정말 감사합니다! ^^

  12. 말라뮤트 2011.06.08 14:59

    첫번째 값이 INT라고 하셨는데. .

    그럼 Characteristics 값은 어디에 저장이 된것인가요 ?

    C언어를 너무 오래전에 배워서 잘 기억이 안나는데..

    공용체는 맴버들끼리 메모리를 공유하지 않던가요.. ?

    그 메모리에 INT 주소값이 써진다면 Characteristics 멤버는 왜 있는 것인지.. 그냥 궁금증이 생기네요..

  13. tintin 2011.06.23 02:10

    안녕하세요?
    강의를 보다가 이상한게 나와서 질문드립니다.ㅠㅠ
    제 컴은 xp구요

    강의쭉 따라가다가

    OriginalFirstThunk (INT) = 7840h (file offset : 6C40h)
    TimeDateStamp = FFFFFFFFh
    ForwarderChain = FFFFFFFFh
    Name = 7AFAh (file offset : 6EFAh)
    FirstThunk (IAT) = 1174h (file offset : 574h)

    이렇게 구해서 접근해봤는데

    함수이름은 은 shell32.dragfinish 로 잘 나오거든요

    사진참고 : http://blog.naver.com/0orjsgmlo0/90116377273

    이와같이 틀린주소값이 나오네요..

    주소끝이 9d / 18 로 틀린데 왜그런걸까요..?

    실수했나해서 여러번해봣는데도 결과는똑같네요

  14. 질문 2011.09.23 16:18

    안녕하세요. PE format 관련 질문이 있습니다.
    언패커를 공부하다가 INT가 비어있는 경우가 종종 보이는데요, 이럴때는 IAT에 Hint와 Name의 주소가 들어있었습니다. 이럴때에는 PELoader가 IAT를 따라가서 Hint(ordinal)과 Name을 읽어서 그 IAT 위치에 바로 실제함수주소를 적어주나요?
    그리고 한번 PEloader에 의해 IAT가 채워진 다음에는 INT는 더이상 참조되지 않나요?

    • reversecore 2011.09.28 20:47 신고

      안녕하세요.

      네, 말씀하신 내용 대로입니다.

      PE Loader 는 INT 에서 정보를 얻을 수 없다면 IAT 에서 시도해 봅니다.

      감사합니다.

  15. 2012.02.03 15:31

    INT에 있는 함수의 시작주소(HINT)가 해당 dll를 dependency walker로 열어봤을때 의 HINT값인건가요? 이 HINT값을 보고 entry point의 값과 dll의 preferred base값을 이용해서 구한 값이 함수의 주소 값이되는건가요? 그리고 HINT값 뒤의 스트링은 DLL name 처럼 함수의 이름으로 사용되는게 맞나요? ㅎ

  16. 리버서 API 2012.04.11 01:41

    안녕하세요. 요즘 PE 를 계속 실습 중 인데요.
    OS 는 winXP 서비스팩3 입니다.

    계산기, 지뢰찾기 kernel32.dll 의 IAT 안에 있는 값과 디버깅 값이 다릅니다. PEview 로 체크해봐도 77--- 로 되어있더라구요. 디버깅해보면 7C--- 로 되어있구요.

    이유가 뭔가요?

  17. 초급 2012.07.28 19:49

    안녕하세요.
    공부하다 설명에서 궁금한점이 있어 글남깁니다.

    "그림에서 보듯이 RVA 가 7604h 이니까 File Offset 은 6A04h" 라고 하셨는데요
    7604h는 값에서 알수 있지만 6A04h가 도출하기까지의 식을 알 수 있을까요?
    RVA, RAW등 설명을 찬찬히 보면서 하룻동안 꼬박 생각했는데도 알 수 없어 글남깁니다.
    답변 기다리겠습니다.

    • 떼르미 2012.07.31 13:42

      바로 앞 강좌를 보시면 image section header 구조체 설명하는 부분 아래쪽에 notepad.exe 헤더를 덤프뜬 내용이 있습니다.

      거기서 7604h 주소는 .text image section header 영역에 포함되어 있으니까

      7604h - 1000h(RVA of .text image section header) + 400h(pointer to raw data of .text image section header) = 6A04h

      이렇게 되네요.

  18. Ange 2012.12.11 18:17

    윈도우 실행 정보 @ http://pe101.corkami.com

  19. 조영용 2013.01.23 09:32

    win7 32bit 환경에서 HxD를 사용하여 notepad를 열었는데
    offset 160에서 04 76 00 00 값이 아니라 48 A0 00 00이라는 다른 값이 나옵니다.

    다른 분들의 댓글을 살펴보니 win7 32bit 환경에서 A048 번지는 .data영역이 아니라 .text영역이라고 하셨는데 어째서 인가요?

    .data 영역의 범위는 01009000 에서 0100B000 인데 A048 은 당연히 .data 영역 아닌가요?

  20. 코코넛냠냠 2018.03.22 20:14 신고

    신입생 때 이걸 첨 보고 너무 복잡해서 따라가기 힘들었는데 학부 졸업하고 다시 보니까 쭉쭉 읽히네요 명쾌한 해설이라는 말이 와닿습니다

  21. earth 2021.01.05 19:01

    그냥 정리 느낌으로
    INT와 IAT는 같은 주소를 가리키고 INT가 함수 이름 문자열을 받아오면 PE 로더가 그에 매칭 되는 함수를 IAT에 매핑시켜준다...

    맞나요?

반응형


Section Header


각 Section 의 속성(property)을 정의한 것이 Section Header 입니다.

section header 구조체를 보기 전에 한번 생각을 해보겠습니다.

앞서 PE 파일은 code, data, resource 등을 각각의 section 으로 나눠서 저장한다고 설명드렸습니다.
분명 PE 파일 포멧을 설계한 사람들은 어떤 장점이 있기 때문에 그랬을 겁니다.

PE 파일을 여러개의 section 구조로 만들었을때 (제가 생각하는) 장점은 바로 프로그램의 안정성입니다.

code 와 data 가 하나의 섹션으로 되어 있고 서로 뒤죽박죽 섞여 있다면, (실제로 구현이 가능하긴 합니다.)
그 복잡한은 무시하고라도 안정성에 문제가 생길 수 있습니다.

가령 문자열 data 에 값을 쓰다가 어떤 이유로 overflow 가 발생(버퍼 크기를 초과해서 입력) 했을때
바로 다음의 code (명령어) 를 그대로 덮어써버릴 것입니다. 프로그램은 그대로 뻗어 버리겠죠.

즉, code/data/resource 마다 각각의 성격(특징, 엑세스 권한)이 틀리다는 것을 알게 된 것입니다.

  • code - 실행, 읽기 권한
  • data - 비실행, 읽기, 쓰기 권한
  • resource - 비실행, 읽기 권한


그래서 PE 파일 포멧 설계자들은 비슷한 성격의 자료를 section 이라고 이름 붙인 곳에 모아두기로 결정하였고,
각각의 section 의 속성을 기술할 section header 가 필요하게 된 것입니다.
(section 의 속성에는 file/memory 에서의 시작위치, 크기, 엑세스 권한 등이 있어야 겠지요.)

이제 section header 가 무슨 역할을 하는지 이해 되셨나요?



IMAGE_SECTION_HEADER


section header 는 각 section 별 IMAGE_SECTION_HEADER 구조체의 배열로 되어있습니다. 

#define IMAGE_SIZEOF_SHORT_NAME              8

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
    union {
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc;
    DWORD   VirtualAddress;
    DWORD   SizeOfRawData;
    DWORD   PointerToRawData;

    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

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


IMAGE_SECTION_HEADER 구조체에서 알아야 할 중요 멤버는 아래와 같습니다. (나머지는 사용되지 않습니다.)

  • VirtualSize      : 메모리에서 섹션이 차지하는 크기
  • VirtualAddress   : 메모리에서 섹션의 시작 주소 (RVA)
  • SizeOfRawData    : 파일에서 섹션이 차지하는 크기
  • PointerToRawData : 파일에서 섹션의 시작 위치
  • Characteristics  : 섹션의 특징 (bit OR)


VirtualAddress 와 PointerToRawData 의 값은 아무 값이나 가질 수 없고,
각각 (IMAGE_OPTIONAL_HEADER32 에 정의된) SectionAlignment 와 FileAlignment 에 맞게 결정됩니다.

VirtualSize 와 SizeOfRawData 는 일반적으로 서로 틀린값을 가집니다.
즉, 파일에서의 섹션 크기와 메모리에 로딩된 섹션의 크기는 틀리다는 얘기가 되는 거죠.

Characteristics 는 아래 값들의 조합(bit OR)으로 이루어 집니다.

#define IMAGE_SCN_CNT_CODE                   0x00000020  // Section contains code.
#define IMAGE_SCN_CNT_INITIALIZED_DATA       0x00000040  // Section contains initialized data.
#define IMAGE_SCN_CNT_UNINITIALIZED_DATA     0x00000080  // Section contains uninitialized data.
#define IMAGE_SCN_MEM_EXECUTE                0x20000000  // Section is executable.
#define IMAGE_SCN_MEM_READ                   0x40000000  // Section is readable.
#define IMAGE_SCN_MEM_WRITE                  0x80000000  // Section is writeable.


마지막으로 Name 항목에 대해서 얘기해보겠습니다.

Name 멤버는 C 언어의 문자열처럼 NULL 로 끝나지 않습니다. 또한 ASCII 값만 와야한다는 제한도 없습니다.
PE 스펙에는 섹션 Name 에 대한 어떠한 명시적인 규칙이 없기 때문에 어떠한 값을 넣어도 되고 심지어 NULL 로 채워도 됩니다.

또한 개발 도구에 따라서 섹션 이름/갯수 등이 달라집니다.

따라서 섹션의 Name 은 그냥 참고용 일뿐 어떤 정보로써 활용하기에는 100% 장담할 수 없습니다.
(데이타 섹션 이름을 ".code" 로 해도 되거든요.)


자 그러면 실제 notepad.exe 의 Section Header 배열을 살펴보죠. (총 3 개의 섹션이 있습니다.)


구조체 멤버별로 살펴보면 아래와 같습니다.

[ IMAGE_SECTION_HEADER ]

 offset   value   description
-------------------------------------------------------------------------------
000001D8 2E746578 Name (.text)
000001DC 74000000
000001E0 00007748 virtual size
000001E4 00001000 RVA
000001E8 00007800 size of raw data
000001EC 00000400 offset to raw data
000001F0 00000000 offset to relocations
000001F4 00000000 offset to line numbers
000001F8     0000 number of relocations
000001FA     0000 number of line numbers
000001FC 60000020 characteristics
                    IMAGE_SCN_CNT_CODE
                    IMAGE_SCN_MEM_EXECUTE
                    IMAGE_SCN_MEM_READ

00000200 2E646174 Name (.data)
00000204 61000000
00000208 00001BA8 virtual size
0000020C 00009000 RVA
00000210 00000800 size of raw data
00000214 00007C00 offset to raw data
00000218 00000000 offset to relocations
0000021C 00000000 offset to line numbers
00000220     0000 number of relocations
00000222     0000 number of line numbers
00000224 C0000040 characteristics
                    IMAGE_SCN_CNT_INITIALIZED_DATA
                    IMAGE_SCN_MEM_READ
                    IMAGE_SCN_MEM_WRITE

00000228 2E727372 Name (.rsrc)
0000022C 63000000
00000230 00008304 virtual size
00000234 0000B000 RVA
00000238 00008400 size of raw data
0000023C 00008400 offset to raw data
00000240 00000000 offset to relocations
00000244 00000000 offset to line numbers
00000248     0000 number of relocations
0000024A     0000 number of line numbers
0000024C 40000040 characteristics
                             IMAGE_SCN_CNT_INITIALIZED_DATA
                             IMAGE_SCN_MEM_READ




RVA to RAW



Section Header 를 잘 이해하셨다면 이제부터는 PE 파일이 메모리에 로딩되었을때
각 섹션에서 메모리의 주소(RVA)와 파일 옵셋을 잘 매핑할 수 있어야 합니다.

이러한 매핑을 일반적으로 "RVA to RAW" 라고 부릅니다.
방법은 아래와 같습니다.

1) RVA 가 속해 있는 섹션을 찾습니다.
2) 간단한 비례식을 사용해서 파일 옵셋(RAW)을 계산합니다.

IMAGE_SECTION_HEADER 구조체에 의하면 비례식은 이렇습니다.

RAW - PointerToRawData = RVA - VirtualAddress
                   RAW = RVA - VirtualAddress + PointerToRawData


간단한 퀴즈를 내보겠습니다.
아래 그림은 notepad.exe 의 File 과 Memory 에서의 모습입니다.
각각 RVA 를 계산해 보세요. (계산기 calc.exe 를 Hex 모드로 세팅하시면 계산이 편합니다.)



Q1)  RVA = 5000h 일때 File Offset = ?
A1) 먼저 해당 RVA 값이 속해 있는 섹션을 찾아야 합니다.
      => RVA 5000h 는 첫번째 섹션(".text")에 속해있습니다. (ImageBase 01000000h 를 고려하세요.)

      비례식 사용
      => RAW = 5000h(RVA) - 1000h(VirtualAddress) + 400h(PointerToRawData) = 4400h

Q2) RVA = 13314h 일때 File Offset = ?
A2) 해당 RVA 값이 속해 있는 섹션을 찾습니다.
      => 세번째 섹션(".rsrc")에 속해있습니다.

      비례식 사용
      => RAW = 13314h(RVA) - B000h(VA) + 8400h(PointerToRawData) = 10714h

Q3) RVA = ABA8h 일때 File Offset = ?
A2) 해당 RVA 값이 속해 있는 섹션을 찾습니다.
      => 두번째 섹션(".data")에 속해있습니다.

      비례식 사용
      => RAW = ABA8h(RVA) - 9000h(VA) + 7C00h(PointerToRawData) = 97A8h (X)
      => 계산 결과로 RAW = 97A8h 가 나왔지만 이 옵셋은 세번째 섹션(".rsrc")에 속해 있습니다.
           RVA 는 두번째 섹션이고, RAW 는 세번째 섹션이라면 말이 안되지요.
           이 경우에 "해당 RVA(ABA8h)에 대한 RAW 값은 정의할 수 없다" 라고 해야 합니다.
           이런 이상한 결과가 나온 이유는 위 경우에 두번째 섹션의 VirtualSize 값이 SizeOfRawData 값 보다 크기 때문입니다.

PE 파일의 섹션에는 Q3) 의 경우와 같이 VirtualSize 와 SizeOfRawData 값이 서로 틀려서 벌어지는
이상하고 재미있는(?) 일들이 많이 있습니다. (앞으로 살펴보게 될 것입니다.)




이것으로 PE Header 의 기본 구조체들에 대한 설명을 마쳤습니다.

다음에는 PE Header 의 핵심인 IAT(Import Address Table), EAT(Export Address Table) 에 대해서 공부해 보겠습니다.

(continue)



반응형
  1. 쵸밥 2009.11.26 03:50

    cOrE 님~ 벌써 6장을 향해 달려가고 있네요 ^^* (아 뿌듯해 ㅋ)

    질문이 하나 있어요.

    예전에 뒷장에서 Hello World! 파일을 리버싱 했던거 기억 나시죠?
    그때 "World!" 문자열을 "Reverse" 문자열로 바꿀때 Olly로 대충 끝자락 널 패딩에 데이터를 썼다가
    삑살 났었죠?

    그 때 PE header 을 보고 메모리에 올라가는 영역중에 안쓰는 곳을 골라서 Patch 해 내야 한다고.

    그래서 질문이 하나 있어요!

    제가 지금 Olly 와 이 사이트에서 얻은 PEview 를 사용중이거든요?
    윗 그림을 보면 .data area 의 시작점 01009000 (Image Base + RVA 값) 값은 Olly와 PEview 에 동일하게 표시되요.

    그런데 끝자락을 표시하는 부분이 Olly 와 PEview 가 틀리네요?

    1. Olly 의 데이터창을 보면 0100AFFF 까지 표시되요.

    2. PEview 의 .data section 을 보면 010097FF 까지로 Olly 보다 훨씬 쪼금이네요?

    3. 그런데 이번에는 계산기로 두두려보면 메모리 주소로서의 (VA) .data area 최대값은 0100ABA8 이고 . .

    4. 또 계산기를 두드려보면 파일 (RAW) 에서 메모리로 (VA) 맵핑될 수 있는 최대값은 010097FF 로 PEview 가 보여주는 수준하고 같네요?

    볼수 있는 주소의 한계를 작은 순서대로 정렬해보면 . .
    PEview 의 .data 영역 = (RAW) .data 계산기 < (VA) .data 계산기 < Ollydbg data view
    Ollydbg 가 .data area 를 .rsrc 직전까지로 제일 많이 보여주는데 . .

    우리 맘대로 "Reverse!!" 문자열을 .data area 에 집어 넣는다면 . . 그리고 파일로 패치해 낼 거라면?

    010097FF PEview 가 보여주는 수준 (RAW) 가 (VA) 에 매칭될 수 있는 최대값 안에 "Reverse!!" 문자열을 넣으면 되는건가요 ???

    • reversecore 2009.11.26 15:12 신고

      쵸밥님, 안녕하세요.

      잠도 안주무시고 이렇게 열심히 하시다니요... @@~

      제 블로그를 아껴 주셔서 감사합니다.

      답변을 드리자면...
      PEView 에서 섹션 크기 와 실제 실행시 메모리에서 섹션 크기가 왜 틀리냐는 말씀이시죠?

      그건 바로 Section Alignment 항목 때문입니다.
      (IMAGE_OPTIONAL_HEADER 멤버)

      PE Header 의 섹션 정보에는 그 프로그램의 해당 섹션이 메모리에서 점유할 크기가 기록됩니다.

      실제 PE Loader 는 메모리를 Section Header 의 배수크기로 잡기 때문에 실제로는 더 큰값이 되는 것입니다. (효율성 때문입니다.)

      그리고 메모리에 문자열을 쓰실거면 실제 잡힌 크기 내에서 쓰시면 되고요, 파일에서 쓰실려면 적당히 빈 곳에 쓰시면 됩니다.

      감사합니다.

  2. 쵸밥 2009.11.27 00:55

    오 . . PE 로더의 동작에 대해서 알아봐야 겠네요 ㄷㄷ;;

  3. 메롱이 2009.11.30 02:50

    강좌 잘 읽다가 정말 모르는 부분이 있어서요..

    퀴즈문제(총 3문제)에서 예를 들면 RVA=13314h일때 13314 주소가 세번째 섹션에 속한다라고

    하셨는데 어떤게 세번째 섹션에 속하는지 몰라서요..

    세번째 섹션 주소 범위가 100B00h~1008304h <- 이거 맞죠?

    주소 범위를 10진수 2진수 둘다 바꿔 교해봐도 3314가 범위안에 안들어 가는거 같던데..

    제가 계산하는게 맞는지 모르겠네요..

    워낙 생초보라...ㅎㅎ;;

    수고스러우시겠지만..좀더 설명좀 해주셔용...ㅎㅎ;;

    리버싱은...마치 컴퓨터 안에서 보물찾기 하는거 같은 느낌이나서 공부 할수록 잼있네요..ㅎㅎ;;

    • ReverseCore 2009.11.30 09:08

      안녕하세요. 메롱이님.

      위 그림을 보시면 RVA 13314 는 세번째 섹션이에 속한 주소라는걸 아실 수 있습니다. (이 그림은 섹션 헤더의 정보를 기반으로 작성되었습니다.)

      세번째 섹션 범위는 아래와 같습니다.
      파일 8400 ~ 10800
      메모리 100B000 ~ 1014000

      섹션 헤더를 다시 한번 살펴보시기 바랍니다.

  4. Ezbeat 2009.12.23 17:37

    강의 잘 보고 있습니다 ^^
    근데 NT header라는 것이 OptionalHeader까지 인것으로 알고있는데
    마지막에 Section header까지 전부 설명하신 후에 NT header의 모든 멤버에 대해 설명을
    마쳤다고 하셔서 쫌 이상한거 같아요 ;;

    • ReverseCore 2009.12.24 02:19

      Ezbeat님, 안녕하세요.

      정확한 지적이십니다. ^^

      열심히 읽어주시고 오류를 바로 잡아 주셔서 감사합니다.

      Ezbeat님 덕분에 제 글의 완성도가 높아져서 기분 좋습니다.

      항상 감사합니다. ^^

  5. h0ney 2010.01.12 18:45

    제가 최근에 C# 을 사용하여 PE Viewer 를 하나 만들고 있습니다.

    분석중 파일이 Packing 이 되어있는지에 대한 여부를 확인할 때

    이리저리 궁리를 하다가 가장 효과적인 방법이 EP Section 의 Characteristics 을 살펴본 뒤

    이 값이 IMAGE_SCN_MEM_WRITE 플레그가 켜져있을 경우에는

    Packing 이 되었다고 판단하도록 만들었는데 이 부분에 대해서는

    어떻게 생각하시는지요?

    • reversecore 2010.01.13 16:22 신고

      h0ney님, 안녕하세요.

      말씀하신대로 EP Section 에 WRITE 속성이 있다면 일단 일반적인 PE 파일은 아닐겁니다. 그렇게 만드는 컴파일러는 본적이 없구요.
      아마 패커 또는 크립터 계열로 작업된 파일일 확률이 매우 높을 겁니다.

      하지만 목표가 packing 된 파일을 찾는거라고 하시면 위 알고리즘은 100% 동작하지는 않을 겁니다.
      이유는 EP 섹션 속성을 건드리지 않는 패커도 꽤 있거든요.
      쉽게 볼 수 있는 파일은 RAR SFX 계열의 파일들입니다.

      다른 분야에서도 팩 여부를 확인하는 것은 중요합니다.
      어떤 논문들을 보면 파일의 entropy 를 확인한다고 합니다.

      저도 자세히 본건 아니라 정확한 설명은 어렵지만 쉽게 얘기해 파일의 '복잡함정도'를 측정하는 것입니다.

      압축알고리즘은 결국 자주 나타나는 패턴을 특정 작은 값으로 매핑시켜주기 때문에 0 ~ FF 의 값이 파일 전반에 걸쳐 골고루 나타날 겁니다. (일반적인 PE 파일에는 0 이 압도적으로 많지요.)

      말씀하신 방법으로도 구현해보시고, 관련해서 검색을 좀 해보시면 다양한 아이디어를 보실 수 있으실 겁니다.

      감사합니다.

  6. graythief 2010.02.21 21:04

    파일 오프셋을 구할 때는 그럼 저 위의 File의 offset 값과 메모리의 address 부분을 구해놓고 계산을 해야하는건가요? 그렇다면 파일마다 틀릴것인데 (섹션크기가 더 크다던지 stub 부분이 더 커진다던지 말이에요 ) 일일이 다 일단 저것을 해놓고 분석해야 하는건가요?

    • reversecore 2010.02.21 23:52 신고

      graythief님, 안녕하세요.

      RVA <-> File Offset 변환은 보통 유틸리티를 이용합니다. PEView 나 StudPE 같은 유틸리티를 사용하시면 되구요.

      좀더 익숙해 지시면 간단한건 그냥 쉽게 변환하실 수 있습니다.

      감사합니다.

  7. 할라 2010.04.22 05:54

    안녕하세요~
    열심히 강좌를 보면서 학습을 하고있습니다.
    정말 볼수록 잘 쓰셧다고 감탄사가 흘러나오고 있습니다 ㅋ
    제가 필요한 부분만 추려내고 싶어도 너무 정리가 잘되있어서 결국 다보게되는..ㅋㅋ
    질문드릴께있는데요..
    특정 RVA값이 주어졌을때 그 부분을 계산하는 도중 의문점이 생겼는데요..
    저도 메모장을 peview로 열어서 각 섹션에 사이즈와 시작주소 등등을
    살펴보았는데, 이 그림 예제와 살짝다른... 곳을 발견했는데요
    첫 번째 text에서 버츄얼사이즈가 다르더군요 그러닌까 다른 섹션들도 값이
    다 틀려지는거같은데요.. 그리고 Size of RawData값들도 틀리고요.. 음.. 어째서 이런건지요??
    혹시 이게 시스템마다 다를 수도있는건가요??

    • reversecore 2010.04.23 22:56 신고

      할라님, 칭찬 감사합니다. ^^

      질문하신대로 notepad.exe 는 OS 버전에 따라 모두 틀려집니다.
      (다른 모든 시스템 파일도 마찬가지입니다.)

      즉, OS 마다 시스템 파일 버전이 다 틀립니다.
      재빌드 해서 배포한다는 뜻이지요.

      따라서 위 그림과 다르게 보일 수 있습니다.

      감사합니다.

  8. 보름♬달★콤 2010.06.07 16:43

    1) VirtualSize 와 SiezOfRawData 설명에 대하여
    : 두개의 설명이 뒤바뀐것 아닌지요?
    VIrtualSize가 File 내의 Section 크기이고 SizeOfRawData가 메모리상에 올려진 setion의 크기 아닌가요? 보통의 경우 SizeOfRawData가 align배수로 roundup된 크기이더군요..
    이름도 이상하게 헷갈리게 되어있는데..

    2) 질문입니다.
    .data섹션의 경우, 이상하게 VIrtualSize보다 SizeOfRawData값이 크게 나와있는데요
    (제가 해본 결과에도 그렇게 나타나는 듯)
    이 경우는 어떻게 fiel offset을 구해야 하는지 알려주세요..
    차후에 설명해주신다고 했는데.. 어디에 써주셨는지 잘 모르겠네요...

    부탁드립니다. 수고하세요!

    • reversecore 2010.06.08 15:38 신고

      안녕하세요~

      1) VirtualSize 가 메모리이고, SizeOfRawData 가 파일입니다.
      virtual 은 virtual address (가상메모리) 를 의미한다고 기억하시면 헷갈리지 않으실 거에요.
      SizeOfRawData 가 FileAlignment 의 배수로 된 것이 맞습니다.

      2) 둘은 1:1 매칭 되지 않기 때문에 항상 RVA 에 대한 FileOffset 을 계산할 수 있는 것은 아닙니다. 계산할 수 없는 경우도 있지요.
      예를 들어 PointerToRawData = 1000, VA = 1000 이고 SizeOfRawData = 200, VirtualSize = 400 인 경우라면...
      RVA 가 1200 보다 크다면 그에 해당되는 FileOffset 은 없는 겁니다.

      http://www.reversecore.com/22 글의 Q3) 문제를 참고하시기 바랍니다.

      감사합니다.

  9. blackjack 2010.06.28 20:44

    이렇게 올리는거 맞나?
    하여튼 안녕하세요. 요세 한창 리버싱 공부 중인 초짜임니다.
    질문드릴게 있는데 저 RAW계산하는게 저렇게만 봐서 잘 이해가 안갑니다.
    ImageBase 값을 참고하시라고 했는데요. 그걸 어떻게 황용하는지도 모르겠고
    그리고 예를 들어 RVA가5000h인 섹션이 어디인지 어떻게 알아내는지도 모르겟습니다 .^^

    • reversecore 2010.06.29 22:52 신고

      안녕하세요.

      저 그림만 놓고 생각해 보시기 바랍니다.

      RVA 가 5000 인 섹션은 그림상에서 ".text" 섹션입니다.
      ImageBase 를 고려하라는 말은 저 그림이 VA 기준으로 되어 있기 때문에 ".text" 섹션의 VA 값 01001000 에서 ImageBase 값 01000000 을 빼면 결국 ".text" 섹션은 RVA 1000 에서 시작한다는 뜻입니다. (VA 01001000 에서 시작한다와 같은 말이지요.)

      설명이 좀 어렵지요? ^^

      잘 이해안가시면 다시 질문해 주세요~

  10. blackjack 2010.06.30 05:18

    아 이제 알겠습니다.
    RVA가5000이면 속해있는 섹션이 .text섹션이라는게
    .text섹션이 400~7C00이니까 이 안에 속해있어서 그랬군요.
    그럼 VA는 절대주소를 말하는거였잖아요?
    근데 계산 하는걸 보니까 ImageBase값을 빼준 나머지 즉 RVA로 계산해서 값을 구하네요
    그건 왜 그런가요?

    • reversecore 2010.07.03 15:00

      .text 섹션은 파일에서 400~7C00 영역이고, 메모리에서는 01001000 ~ 01009000 영역(VA)입니다.

      굳이 VA 를 RVA 로 변환해서 계산하는 이유는 Section Header 의 정보가 전부 RVA 로 되어있기 때문에 VA 를 RVA 로 변환후 계산해야 편리하기 때문입니다. (PE 헤더 정보들 대부분 RVA 로 되어있습니다.)

      PE 헤더에 RVA 만 사용하는 이유는... PE 파일이 꼭 ImageBase 에 로딩된다는 보장이 없기 때문에 RVA 로 써주는 것입니다...

      감사합니다.

  11. Vi0 2010.08.02 20:11

    안녕하세요
    덕분에 열심히 리버싱공부 하고있습니다
    강좌를 보다 궁금증이 생겨서 질문드리는데요

    첫번째 문제에서 RVA가 5000일때 RAW가 4400이라 구하셨는데
    RVA5000인 곳에 asdf라는 데이터가 있으면
    RAW4400이란 곳에 asdf라는 똑같은 데이터가 있다는 말..맞나요?

    또 RVA는 존재하는데 RAW는 존재하지 않는경우는
    프로그램이 실행되면서 데이터가 push된(표현이 맞나 모르겠습니다..) 경우인가요?

    • reversecore 2010.08.04 22:00 신고

      안녕하세요.

      첫번째 질문에 대한 답변은 '네' 이구요...
      (그 영역이 메모리에 로딩되는 경우라면 말이죠.)

      두번째 질문은... 조금 난해한데요... ^^
      패커같은 경우에 특정 섹션의 파일 크기가 0 이면서 메모리 크기는 매우 큰 경우가 많습니다. 즉, 파일에서는 사용되지 않지만 메모리에서는 뭔가 사용할 필요가 있어서 예약해놓은 공간이라고 보시면 됩니다. 그대로 메모리는 할당되지요. (보통 이경우에 unpack 된 코드를 이 영역에 풀어내는 경우가 많아요.)

      감사합니다.

  12. tintin 2011.06.21 00:32

    안녕하세요?
    강의잘보고잇습니다^^
    강의를쭉 읽다가 제가 햇갈리는게 잇어서 질문을 합니다

    PE 파일 첫번째 강의에서

    VA (Virtual Address) 는 프로세스 가상 메모리의 절대 주소를 말하며,
    RVA (Relative Virtual Address) 는 어느 기준위치(ImageBase) 에서부터의 상대 주소를 말합니다.
    VA 와 RVA 의 관계는 아래 식과 같습니다.
    RVA + ImageBase = VA


    이렇게 말씀하셨는데요

    질문.
    이번 강의에서
    VirtualAddress : 메모리에서 섹션의 시작 주소 (RVA)
    이말은 무슨뜻인가요..?ㅠ VirtualAddress는줄여서 VA일테고
    그럼 VA = RVA ?? ,, 무지 햇갈리네요..
    혹시 메모리에서 섹션의 시작 주소 (RVA) 여기에서 오타가 나신건가요..?
    그리고 첫번째 강의에서 VA 랑 두번쨰 강의에서 VA는 같은건가요?


    아오 무슨질문을 했는지도모르겠네요 ㅎ ㅋㅋㅋ 죄송합니다

    • reversecore 2012.11.04 00:47 신고

      여기서의 VirtualAddress 는 IMAGE_SECTION_HEADER 구조체의 멤버 변수입니다. 섹션의 메모리에서 시작 위치를 RVA 값으로 나타내지요.

      이름이 저렇게 되어 있어서 헷갈리실만 합니다.

      감사합니다.

    • 크빗 2013.08.17 08:20 신고

      저도 같은 부분을 헷갈렸는데 이 글 보고 정확하게 짚어가네요 ㅋㅋ

  13. Bitstream0 2011.08.11 15:40

    'VirtualAddress : 메모리에서 섹션의 시작 주소 (RVA)' 라는 말은,

    "VirtualAddress의 값이 RVA로 표현되어 있다"는 말로 이해하시면 될 것 같습니다.

    즉, 특정 섹션의 VirtualAddress의 값이 VirtualAddress : 00001000과 같이 표현되어 있다면,

    해당 섹션의 가상메모리 상의 실제 위치(VirtualAddress)는 ImageBase + 00001000가

    되겠지요..... 지나가다가 글 남겨 봅니다.

  14. 강준성 2012.11.02 03:39

    RVA to RAW부분이
    이해가안되서 몇일설치다가..조금 쉽게 써봤어요..저처럼 머리나쁜사람있을까봐..

    일단 PE를 실행하면 섹션헤더를 참고해서 메모리를로드하는데 아래와같다.
    1. virtual address(rva)로 이동
    2. virtual size만큼의 크기를 할당
    3. 파일에있는 해당섹션을 메모리로 로드




    RAW = RVA - VirtualAddress + PointerToRawData 이 공식의 의미는
    PE로더가 파일의 섹션을 메모리의 섹션으로 로드하니까
    메모리가 가르키는값, 즉 RVA에서 섹션시작주소를빼고
    (이경우 메모리의 섹션시작부터 RVA가 가르키는곳까지의 크기를 뜻하게됨)
    파일섹션의 시작주소를합하는 공식
    (메모리의 크기를 구해서 파일의 시작주소를합해서
    파일의 오프셋으로 치환하는..말로는 아주 간단한 공식..)

    아 드디어 읽을수있다...ㅠㅠ
    근데 SizeofRawData(파일상 섹션의 크기)랑 Virtual Size(메모리상의 크기)가 다른이유는 전혀 이해가안가네요..

    • reversecore 2012.11.04 00:54 신고

      안녕하세요.

      RVA -> RAW 변환은 리버스 엔지니어링에서 매우 중요한 개념입니다. 그리고 처음에는 좀 어렵게 느껴질 수 있습니다.

      지금이야 SizeOfRawData 와 VirtualSize 의 크기가 달라서 이득 볼게 별로 없습니다.

      그러나 PE가 처음 설계될 때는 HDD 가 매우 비쌌지요. 메모리는 가상 메모리 개념을 썼기 때문에 프로세스당 4GB 라고 보면 되는데요. 그당시 실제 컴퓨터 HDD 저장 공간은 10~100MB 수준이었습니다. 그래서 최대한 파일 크기를 작게 만들어야 좋았습니다. 메모리는 넉넉한 대신 파일은 팍팍하게 줄 수 있도록 자유도를 부여한 거라고 보시면 됩니다.

      지금은 패커/프로텍터에게 다양한 창의적인 기법이 가능하도록 빌미를 제공한 셈이 되었지만요... ^^~

      감사합니다.

  15. bach 2013.10.24 10:11

    안녕하세요,

    rva를 raw로 변환하는 과정에서

    virtual address와 pointer to rawdata 값이 필요한데,

    예제에서는 저 표를 보고 구했잖아요

    다른 파일들은 virtual address와 pointer to rawdata 값을 어떤것을 보고

    알수 있는지 궁금합니다.

    • jaehyuk 2014.02.17 13:54

      peview같은 것으로 볼 수 있고, hxd같은 hexa editor로 따라 읽어가면서 보실수 있습니다

  16. 알 수 없는 사용자 2016.02.16 14:13

    안녕하세요

    궁금한게 있어서 이렇게 글을 남깁니다

    노트패드 PEview 열었습니다.

    IMAGE_SETION_HEADER.rsrc 부분에

    Virtual Size 8304
    RVA B000
    Size of Raw Data 8400
    Pointer to Raw Data 8400

    나머지 부분은 다 이해 했는데 아래

    Virtual Size 에 8304 가 왜 8304 가 되는지 궁금합니다.

    아니면 어떤 공식에 의해 8304가 나오는지

    도저히 이부분은 아무리 해도 이해가 안되어서

    이렇게 글을 남깁니다.

    즉 PE 헤더의 섹션 추가 및 삭제 하는 연습을 하는데 저부분을

    입력을 해야 하는데 몰라서요

  17. hde 2019.06.20 21:44 신고

    ㅇㄴㄻ

+ Recent posts