반응형

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

반응형
반응형


Process Explorer


Windows 최고의 프로세스 관리 도구 Process Explorer 입니다.

https://technet.microsoft.com/en-us/sysinternals/bb896653.aspx

저 유명한 sysinternals (현재는 MS에 인수되었음) 의 Mark Russinovich 씨가 만든 프로세스 관리 유틸리티입니다.

이분은 Windows 운영체제에 대해서 매우 해박한 지식을 갖고 있으며,
유용한 유틸리티(FileMon, RegMon, TcpView, DbgView, AutoRuns, Rootkit Revealer, etc) 들을 만들어 공개하였습니다.

언젠가 한번은 FileMon 과 RegMon 의 소스 코드를 공개하신적이 있습니다. (지금은 없어졌지요.)
Windows 운영체제 초창기에 (맨땅에 헤딩하던) 시스템 드라이버 개발자들에게 있어서
그 소스들은 그야말로 사막의 오아시스 같은 존재였습니다.

각설하고 Process Explorer 의 실행화면을 보실까요.


< Fig.1 - Process Explorer 실행화면 >

Windows 작업관리자와는 비교 할 수 없는 아주 뛰어난 화면 구성을 보여주고 있습니다.

화면 위의 좌측에는 현재 실행중인 프로세스들을 Parent/Child 의 트리 구조로 표시하고 있습니다. (무려 아이콘 까지!!!)
우측에는 프로세스 각각의 PID, CPU 점유율, 등록정보 등을 보여줍니다. (Option 을 통해 더 추가 가능)

화면 아래(욥션)에는 선택된 프로세스에 매핑된 DLL 정보 또는 해당 프로세스에서 오픈한 object handle 을 표시합니다.



구체적으로 뭐가 좋은거죠?


좋아 보이기는 하는데 구체적으로 뭐가 좋은건지 궁금하시죠?

저는 리버싱 할 때 항상 Process Explorer 를 같이 띄워놓고 합니다.
제가 Process Explorer 를 좋아하는 이유는 이렇습니다.

  • Parent/Child 프로세스 트리 구조
  • 프로세스 실행/종료시 각각의 색깔(초록/빨강)로 표시
  • 프로세스 suspend 기능 (실행 중지)
  • 프로세스 종료(kill) 기능 (Kill Process Tree 기능 지원)
  • DLL/Handle 검색 (프로세스에 인젝션된 DLL 검색 또는 특정 파일을 오픈한 프로세스 검색)

  • 이외에도 다양한 기능들이 있습니다만, 위 기능들이 특히 리버싱 할 때 많이 사용되는 기능들입니다.
    그리고 꾸준한 업데이트(버그 수정, 기능 추가)도 큰 장점입니다.



    sysinternals


    sysinternals 홈페이지에 가보시면 Process Explorer 의 미니 콘솔 버전들이 있습니다.
    (PsKill, PsSuspend, PsList, etc)

    이 유틸들도 받아서 실행해 보세요. Process Explorer 의 기능을 축소시킨 멋진 콘솔 버전 프로그램들입니다.

    리버싱을 위해 Windows 내부구조를 공부하시는 분들께서는 간단히 이런 콘솔 프로그램을 따라 만들어 보세요.
    프로세스와 DLL 등에 대한 이해를 높일 수 있습니다.
    (제 경험상 따라 만드는 방법이 실력 향상을 위한 가장 좋은 방법입니다. ^^)



    이상으로 최고의 프로세스 관리 유틸리티 Process Explorer 에 대한 소개를 마칩니다.



    반응형

    'tool' 카테고리의 다른 글

    PEView.exe  (21) 2013.01.27
    HxD.exe 기능 추가!  (12) 2012.06.14
    리버싱 현업에서 사용되는 디버거(Debugger)들  (33) 2010.09.29
    InjDll.exe – DLL Injection/Ejection 전용 도구  (43) 2010.03.15
    www.virustotal.com  (3) 2009.03.20
    www.google.com  (2) 2009.03.06
    반응형


    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 조작 기법에 대해서는 그때 그때 소개해 드리도록 하겠습니다.




    반응형
    반응형


    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) 에 대해서 공부해 보겠습니다.



    반응형
    반응형


    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)



    반응형

    + Recent posts