원문 -> http://linuxgazette.net/issue83/sandeep.html

-----------------------------------------------------------------------------

   이전 part1에서 우리는 ptrace의 기본적인 특성에 대해서 알아 보았습니다. 우리는 이제
  다시한번 작은 예제를 볼 것입니다. 제가 미리 말 한것 처럼 ptrace의 메인 어플리케이션
  은 실행될 프로세스의 메모리나 레지스터에 접근하는 것입니다.(그것이 디버깅의 목적
  이든 나쁜목적이든 말입니다.) 그래서 먼저 우리는 몇가지 기본적인 실행파일의 바이너리
  구조에 대해서 알 필요가 있습니다.- 그 다음 우리가 알 것은 어떻게 그리고 어디에 접근
  해야 하는것입니다.. 그래서 저는 먼저 ELF의 기본적인 튜터리얼을 제공할 것입니다.
  ELF는 리눅스에서 사용되는 바이너리 포맷입니다. 이 문서의 끝 부분에서 우리는 다른
  프로세스의 메모리와 레지스터에 접근하여 그들에게 추가적인 코드를 주입함으로 써
  출력을 다르게 만들어 볼 것입니다.

    NOTE. 혼란 스러워하지 마세요!! 확실히 이 문서는  ptrace에 관한 것이지 ELF에 관한
    것이 아닙니다. 그러나 ELF의 기본적인 지식이 프로세스의 이미지에 접근한데 필요
    합니다. 그래서 이것을 먼저 설명하는 것입니다.

1. What is ELF?

        ELF는 Executale and Linking Format 입니다. 이것은 리눅스에서 사용되는 실행
        가능한 바이너리를 정의합니다 - 그리고 공유 오브젝트와 코어 덤프 파일, 재배치
        들도 정의합니다. ELF는 링커와 로더에 의해 모두 사용됩니다. 그들은 두 가지 측면
        으로부터 ELF를 보게 됩니다. 그래서 둘 다 일반적인 인터페이스를 가집니다.

        ELF의 구조는 많은 섹션과 세그먼트를 가집니다. 재배치 파일은 섹션 헤더 테이블을
        가지고 실행가능한 파일은 프로그램 헤더 테이블을 가지고, 공유 오브젝트 파일은 둘
        다 가지고 있습니다. 다음에 오는 섹션에서는 이 헤더들이 무엇인지 설명을 하겠습니다.


2. ELF Headers
 
    모든 ELF 파일은 ELF 헤더를 가지고 있습니다. 이것은 항상 0 오프셋(파일의 가장 처음
    부분)에 있습니다. 이것은 바이너리 파일의 중요한 사항을 가지고 있습니다. - 무슨 자료
    구조들이 파일과 관련되었는지 알기 위해서는 꼭 인터럽트 되어야 합니다.(참조 되어야
    합니다)

    헤더의 포맷은 아래와 같습니다.(이 파일은 /usr/src/include/linux/elf.h에 있습니다)

   --------------------------------------------------------
   #define EI_NIDENT       16

   typedef struct elf32_hdr{
         unsigned char e_ident[EI_NIDENT];
          Elf32_Half    e_type;
          Elf32_Half    e_machine;
          Elf32_Word    e_version;
          Elf32_Addr    e_entry;  /* Entry point */
          Elf32_Off     e_phoff;
          Elf32_Off     e_shoff;
          Elf32_Word    e_flags;
          Elf32_Half    e_ehsize;
          Elf32_Half    e_phentsize;
          Elf32_Half    e_phnum;
          Elf32_Half    e_shentsize;
          Elf32_Half    e_shnum;
          Elf32_Half    e_shstrndx;
   } Elf32_Ehdr;
   --------------------------------------------------------

    각각의 필드에 대해서 간단히 설명하면 다음과 같습니다

    1. e_ident : 바이너리 파일을 어떻게 다루어야 할지에 대한 정보가 담겨 있습니다
                 플랫폼에 의존적입니다
   
    2. e_type : 바이너리 파일을 어떻게 사용해야 할지에 대한 정보가 담겨 있고 타입은
                재배치 가능한지, 실행가능한지, 공유가능한지 그리고 코어파일인지 입니다
   
    3. e_machine : 예상했던데로 이 필드는 아키텍쳐를 나타냅니다 - Intel 386, Alpha, Sparc

    4. e_version : 오브젝트 파일의 버전을 나타냅니다

    5. e_phoff : 프로그램 헤더의 시작 오프셋을 가지고 있습니다

    6. e_shoff : 섹션 헤더의 시작 오프셋을 가지고 있습니다

    7. e_flags : 프로세서 의존적인 플래그이다. i386에서는 사용되지 않습니다

    8. e_ehsize : ELF헤더의 size를 가지고 있습니다

    9. e_phentsize & e_shentsize : 프로그램 헤더와 섹션헤더의 size를 나타냅니다

    10. e_phnum & e_shnum : 프로그램 헤더아 섹션헤더의 갯수를 나타내며. 프로그램 헤더
                     테이블은 프로그램 헤더의 배열입니다. 비슷하게 섹션헤더도 마찬가지
                                     입니다
   
    11. e_shstrndx : 섹션 헤더 테이블안에서 섹션이 가지고 있는 섹션의 이름입니다. 이것은
                    테이블안의 섹션을 가리키는 인덱스 입니다
                                    (아래에서 좀더 볼 것이다)
                                   

3. Sections And Segments

    위에서 말 한것 처럼 링커는 섹션 헤더 테이블에서 묘사된것 처럼 논리적인 헤더의 집합
    인것 처럼 파일을 다룹니다 어플리케이션 들은 아마도 고유의 방법으로 인터럽트를 할 것
    입니다.

    섹션헤더들의 배열인 섹션헤더 테이블이 있습니다. 이 테이블의 가장 첫번째 인 0 엔트리는
    항상 NULL 이고 바이너리의 어느 부분도 나타내지 않습니다. 각각 섹션 헤더는 다음과 같은
    포맷을 가집니다.


   --------------------------------------------------------
    typedef struct elf32_shdr {
         Elf32_Word sh_name;           /* Section name, index in string tbl (yes Elf32) */
         Elf32_Word sh_type;           /* Type of section (yes Elf32) */
         Elf32_Word sh_flags;          /* Miscellaneous section attributes */
         Elf32_Addr sh_addr;           /* Section virtual addr at execution */
         Elf32_Off sh_offset;          /* Section file offset */
         Elf32_Word sh_size;           /* Size of section in bytes */
         Elf32_Word sh_link;           /* Index of another section (yes Elf32) */
         Elf32_Word sh_info;           /* Additional section information (yes Elf32) */
         Elf32_Word sh_addralign;      /* Section alignment */
         Elf32_Word sh_entsize;        /* Entry size if section holds table */
    } Elf32_Shdr;
   --------------------------------------------------------

  이제 각 필드의 자세한 설명을 봅시다.

    1. sh_name : 이것은 해당 인덱스의 e_shstrndx 문자열 테이블의 내용을 가집니다. 이 인덱스는
              Null로 끝나는 스트링의 시작 부분이며 이것은 섹션의 이름으로 사용됩니다. 많은
                        섹션 이름들이 있으며 아래는 그 중 일부 입니다.

                        .text : 이 섹션은 프로그램의 실행가능한 명령어를 가집니다.
                        .data : 이 섹션은 프로그램 이미지에 사용되는 초기화 된 자료를 가집니다.
                        .init : 이 섹션은 프로세스 초기화에 관련된 코드를 가집니다.

    2. sh_type : 프로그램 데이타, 심볼테이블, 스트링 테이블 등 섹션의 타입을 나타냅니다.

    3. sh_flags : 섹션의 내용을 어떻게 다룰 것인지에 대한 정보를 가집니다.

    4. sh_addralign : 섹션 내용을이 어떤 정렬방법이 요구되는지 나타냅니다.. 일반적으로 0/1
                 (둘 다 정렬을 하지 않는다는 의미) 또는 4를 사용합니다

  나머지 필드는 스스로 찾아 봅시다

    3.2. ELF 세그먼트 와 프로그램 헤더들

    ELF 세그먼트들은 로딩되는 중에 사용됩니다 ie. 프로세스 이미지가 코어안에서 만들어 질때
    각각 세그먼트는 프로그램 헤더에 의해서 묘사 됩니다. 프로그램 헤더 테이블이라는 것이 존재
    하는데(보통 ELF 헤더 근처에 있습니다) 테이블은 프로그램 헤더들의 배열입니다. 프로그램 헤더
    는 다음과 같은 포맷을 가집니다.

    --------------------------------------------------------
    typedef struct
    {
             Elf32_Word    p_type;                 /* Segment type */
             Elf32_Off     p_offset;               /* Segment file offset */
             Elf32_Addr    p_vaddr;                /* Segment virtual address */
             Elf32_Addr    p_paddr;                /* Segment physical address */
             Elf32_Word    p_filesz;               /* Segment size in file */
             Elf32_Word    p_memsz;                /* Segment size in memory */
             Elf32_Word    p_flags;                /* Segment flags */
             Elf32_Word    p_align;                /* Segment alignment */
    } Elf32_Phdr;
    --------------------------------------------------------

    1. p_type : 내용을 어떻게 다룰 것인지에 대한 정보를 제공합니다. 이것은 다음과 같은 프로그램의
                타입을 제공합니다.

                            - unused
                            - loadable
                            - Dynamic linking information
                            - reserved

                            etc ..

    2. p_vaddr : 세그먼트가 로드 될 것으로 예상되는 가상 메모리 주소 입니다.

    3. p_paddr : 세그먼트가 로드 될 것으로 예상되는 물리 메모리 주소입니다.
                 (역자 주. i386 리눅스는 가상메모리만을 사용하기 때문에 실제 p_paddr은 p_vaddr과
                               동일한 값을 가집니다.)
   
  4. p_flags : 보호 플래그를 가집니다. - read/write/execute 권한

    5. p_align : 메모리안의 세그먼트 정렬에 관한 내용을 가집니다. 만약 세그먼트가 loadable 타입
                 이라면 정렬은 page 크기로 예측될 수 있습니다.
   
    나머지 필드는 직접 알아내 봅시다 :D

4. Loading the ELF File

  우리는 ELF 오브젝트 파일에 대한 어느정도 지식을 가지게 되었습니다. 이제 우리는 실행을 위해
    파일이 어떻게 그리고 어디에로드 되는지 알아야 합니다. 보통 우리는 단순히 프로그램 이름을
    쉘 프롬프트에 입력합니다. 사실 많은 흥미있는 것들이 엔터를 친 후에 일어나게 됩니다.

    먼저 쉘은 커널 루틴을 호출하는 표준 libc 함수를 호출 합니다. 이제 공(역자 주 - 프로그램의
    흐름을 말하겠죠??)은 커널의 코트로 넘어왔습니다. 커널은 파일을 열고 실행파일의 타입과 포맷
    을 알아냅니다. 그리고는 ELF와 요구되는 라이브러리들을 로드하고 프로그램의 스택을 초기화
    하며 마침내 프로그램 코드에게 컨트롤을 넘깁니다.

    프로그램은 0x8048000에 로드되고(이것은 /proc/PID/maps로 확인할 수 있습니다) 프로그램의
    스택은 0xbfffffff에서 시작합니다.

5. Code Injection

   우리는 메모리에 프로그램이 로드되는 것을 자세히 보았습니다. 그래서 프로세스가 주어지고
   이것의 메모리 공간을 알고 있을때 우리는 이것을 추적할 수 있으며(만약 권한을 우리가 가지고
   있을경우) 프로세스의 개인적인 자료구조에 접근할 수 있습니다. 이것은 말이 쉽지 실제로
   하기에는 쉽지 않습니다. 한번 시도해 보지 않겠습니까??
   가장 먼저 다른 프로그램의 레지스터에 접근하고 이것을 수정하는 프로그램을 작성해 봅시다.
   여기서 우리는 다음과 같은 request 값을 사용할 것입니다.

     - PTRACE_ATTACH : 특정 pid의 프로세스를 붙입니다.
     - PTRACE_DETACH : 특정 pid의 프로세스를 때어냅니다.

       NOTE. 이것을(역자 주- PTRACE_DETACH를 말하는듯?) 호출하는 것을 잊지 마십시오.
                 그렇지 않으면 프로세스는 정지 모드로 있을것이며 이것은 복구하기 힘듭니다.

     - PTRACE_GETREGS : 프로세스의 레지스터를 data에 의해 가리켜지는 구조체에 복사합니다
                                  (여기서 addr 인자는 무시됩니다.). 이 구조체는 user_regs_struct 이며
                                  다음과 같이  정의되어 있습니다. 이것은 asm/user.h 에 있습니다.

    --------------------------------------------------------
    struct user_regs_struct {
                long ebx, ecx, edx, esi, edi, ebp, eax;
                unsigned short ds, __ds, es, __es;
                unsigned short fs, __fs, gs, __gs;
                long orig_eax, eip;
                unsigned short cs, __cs;
                long eflags, esp;
                unsigned short ss, __ss;
       };
    --------------------------------------------------------

    - PTRACE_SETREGS : 이것은 GETREGS의 반대 입니다.
    - PTRACE_POKETEXT : 이것은 추적되는 프로세스의 addr 주소안에 있는 데이타에 의해 가리켜
                              지는 곳에서 32bit를 복사합니다.
   
    이제 우리는 우리의 작은 코드 조각을 추적될 프로세스의 이미지에 삽입하고 강제로 프로세스의
    명령어 포인터(역자 주 - EIP를 말하는 것이겠죠?)를 변경시켜서 우리의 코드를 실행하게 만들
    것입니다. 이제 프로세스를 실행시키고 삽입된 코드를 실행시킬 것입니다.

    우리는 두가지 소스 파일을 가지고 있습니다. 한 가지는 추적되는 프로세스에 삽입될 어셈블리코드
    입니다. 우리가 추적할 조그마한 프로그램을 제공합니다.
    (역자 주 - 프로그램 소스들은 모두 현재 이 포스트에 첨부파일로 올리겠습니다 :D)

    소스 파일은 다음과 같습니다

      - Tracer.c
        - Code.S
        - Sample.c

  이제 파일을 컴파일 합니다.

  ---------------------------------
    #cc Sample.c -o loop
    #cc Tracer.c Code.S -o catch
    ---------------------------------

    이제 다른 콘솔로 가서 샘플 프로그램을 다음과 같이 실행합니다.

  ---------------------------------
    #./loop
    ---------------------------------

    다시 처음 콘솔로 돌아와서 loop 프로그램을 잡아서 이것의 출력을 변경할 프로그램을 다음과 같이
    실행합니다.

    ----------------------------------------------------
    #./catch `ps ax | grep "loop" | cut -f 3 -d ' '`
    ----------------------------------------------------
    (역자 주 - 그냥 직접 PID를 알아내서 입력해도 되겠죠 :D)

    이제 loop가 실행되고 있는 콘솔로가서 이것의 출력이 어떻게 변하였는지 확인해 봅니다.
    ptrace와의 놀이가 이제 시작되었습니다.

6. Looking Forward

  첫번째 파트에서 우리는 프로세스를 추적하고 이것의 명령어 갯수를 카운트 하였습니다. 이
  파트에서 우리는 ELF파일에 대해서 공부하고 작은 코드 조각을 프로세스에 주입 해 보았습니다.
  다음 장에서 저는 어떤 프로세스의 메모리 공간에 접근해 볼 것입니다.
 
그럼 그때까지 안녕히 계세요

    from Sandeep S.

※ 역자 주 - 이 문서의 아이디어는 매우 좋습니다. 마치 윈도우에서 원격에서 쓰레드를 만들어서 코드를 주입하는 것과 비교할 수 있겠습니다. 그러나 문서에서는 자세한 내용을 다루지 못하고 있습니다 .. 

단순히 아이디어만을 제공한 수준 같군요 ..프로그램이 실제 main()으로 넘어오는 과정도 자세히 설명하면 매우 복잡하고 알면 좋은 지식이니 따로 찾아서 공부해볼 필요가 있을것 같고 ..실제 코드를 주입하는 Tracer의 경우 현재의 리눅스에서 사용하기 위해서는 변형이 필요할 것으로 보입니다.
                    
 
저작자 표시 비영리
Posted by 티엘로