2021. 5. 31. 23:11ㆍLayer7/Layer7_Reverse Engineering
ELF 파일이란
보통 실행파일은 .exe로 많이 보았을 것이다. 우리가 윈도우를 많이 사용하기 때문에 .exe 실행파일을 볼 일이 많다.
그러나 .exe는 리눅스에서 실행이 불가능하다. 그러면 리눅스에서 사용되는 실행파일은 어떤 것이 있을까?
이것이 바로 ELF 파일이다. ELF는 Executable and Linkable Format의 약자이며, 이는 실행이 가능하고 링킹이 가능하다는 의미이다. 리눅스에서 사용되는 .exe 파일이라고 생각하면 편하다.
ELF 파일의 구조
ELF Header : ELF 파일의 구성을 나타낸다. 파일의 첫 부분을 차지하여 파일의 특성에 대해서 알 수 있다.
File Data : ELF Header를 제외한 모든 내용으로, 이 곳에 실제 데이터가 담긴다.
- 프로그램 헤더 테이블 : 여러 세그먼트들을 담고 있어 시스템이 파일을 실행해서 프로세스 이미지를 만들 때 프로세스 이미지를 어떻게 만들 것인지 정의해 준다.
- 섹션 헤더 테이블 : 파일의 섹션들에 대해서 알려준다. 섹션은 실행 파일의 정보가 담긴 곳이다. text, bss, stack과 같은 모든 섹션들은 이 테이블에 하나의 entry를 가져야 하고 이 entry들은 섹션의 이름이나 크기 같은 정보들을 알려준다. 링커가 섹션 헤더에 있는 정보들을 가지고 링킹 작업을 하는 데 사용된다.
ELF 헤더
hex(16진수) 값으로 실행파일의 첫 부분을 출력해 보자.
가장 먼저 나오는 것이 ELF 헤더이다. ELF 헤더에는 파일의 구성, 파일에 대한 일반적인 정보들이 포함되어 있다.
16진수 두 문자가 한 바이트이며 사진에서는 두 바이트씩 끊어서 표시되어 있다.
한 바이트 한 바이트 다 뜻이 있으며 뜻을 알아보기 위해 다음 사이트를 참고한다. (몇 번째 바이트가 무엇을 의미하는지 다 외우고 다닐 수는 없으니까)
0x00 옵셋부터 4바이트 간 차례대로 7f, 45, 4c, 46이 있다. 이는 ELF 파일이라는 것을 알아낼 수 있도록 붙여진 고유의 4바이트이며, ELF의 헤더에 항상 있는 정보이다. 이와 같이 앞에 있는 바이트들이 고유성을 가져 파일의 종류를 알아낼 수 있도록 하는 숫자를 magic number라 한다.
그다음 0x04에는 02라는 값이 있는데, 이는 파일이 32bit 아키텍처에서 동작할 때 1이, 64bit일 때 2가 있다. 즉 우리가 분석하고 있는 파일은 x64라는 것을 알 수 있다.
0x05는 Little Endian과 Big Endian을 구분한다. Little Endian이면 2바이트씩 묶인 것을 뒤에서 한 바이트씩 읽어야 하며, 이때 0x05는 1이다. 반면 Big Endian은 그냥 있는 그대로 보는 것이며 이때 0x05는 2여야 한다. 지금 우리가 분석하는 파일은 Little Endian인 것을 알 수 있다.
0x06은 ELF 파일의 현재 버전을 사용하는 것이면 1이 된다.
0x07은 운영체제의 ABI의 종류를 보여준다. 0x00은 ABI가 System V라는 것을 알려준다. 0x08은 ABI의 버전을 알려준다.
0x09는 패딩이다. 미래에 사용될 수도 있어 미리 비어둔 공간이지만 아직은 사용하지 않아서 0으로 채운다.
0x10은 목적 파일의 종류를 보여준다. 0x0300이... 아니라 Little Endian이므로 뒤에서부터 읽어야 하므로 0x0003이 되고, 이는 ET_DYN이다.
0x11은 해당 실행파일이 작성된 ISA (명령어 처리 구조)를 가리킨다. 0x3e는 AMD x86-x64이다. 즉 해당 프로그램이 사용하는 아키텍처는 AMD의 x86과 x64 방식을 사용한다는 것이다.
0x14는 ELF의 버전을 설정하는 것이며, 1이다.
0x18에는 프로세스가 시작되는 메모리의 주소 (엔트리 포인트)가 담겨 있다.
0x1C 또는 0x20부터는 프로그램 헤더 테이블을 가리킨다.
0x20 또는 0x28부터는 섹션 헤더 테이블을 가리킨다.
0x28, 0x34는 이 ELF 헤더의 크기를 보여준다. 64bit의 경우에는 64바이트, 32bit의 경우에는 52바이트이다.
0x2A, 0x36은 프로그램 헤더의 크기를 보여준다.
0x2C, 0x38은 프로그램 헤더 테이블에 있는 엔트리의 수를 알려준다.
0x2E, 0x3A는 섹션 헤더 테이블의 엔트리의 크기를 보여준다.
0x30, 0x3C는 섹션 헤더 테이블 내에서 엔트리의 수를 보여준다.
0x32, 0x3E는 섹션들의 이름을 저장하고 있는 섹션 헤더 테이블의 주소를 보여준다.
0x34, 0x40 - ELF 파일 헤더 끝
사실 이 모든 것을 한눈에 볼 수 있도록 정리해 주는 프로그램이 있었다.
프로그램 헤더
세그먼트들에 대한 정보들로 프로세스 이미지를 어떻게 구성하고 만들 것인지에 관한 지시가 있다.
세그먼트는 프로그램이 실행되는 시간 동안 사용될 메모리의 정보이다. 즉, 몇 번째부터 몇 번째까지가 어느 영역이고 어떠한 권한을 가지는지 등등 모두 프로그램 헤더에 담겨 있다.
시스템은 프로그램 헤더를 참조하여 프로세스 메모리를 구성한다.
섹션 헤더
섹션들에 대한 정보를 모아둔 곳이다. 섹션은 실제 실행 파일의 정보가 담긴 공간이다. 이 곳에서 코드나 데이터 등이 있다.
섹션에서는 4개의 필수적인 섹션이 있다.
.text - 기계어로 된 실행 명령어들이 저장되어 있는 곳이다. read와 execute, 즉 읽기와 실행 권한이 있고 .text 섹션이 위치한 메모리 영역은 한번 써지고 절대로 지워지거나 변경되지 않는다. (명령어들은 변경될 일도 없겠죠?)
.data - 초기화된 전역 변수와 정적 변수가 들어간다. 읽기와 쓰기 권한이 있다.
.bss - 초기화되지 않은 데이터가 들어간다. 초기화되지 않은 전역 변수와 정적 변수도 이 공간에 들어간다. bss 섹션은 프로세스가 시작될 때 자동으로 0으로 초기화되는 특징이 있어 C언어에서 전역 변수와 정적 변수를 초기화하지 않으면 0이 들어간다. (다른 지역 변수에는 기본적으로 쓰레기 값이 들어가 있다.)
.rodata - 프로세스 이미지에서 문자열이나 상수와 같은 읽기 전용 데이터들을 담고 있다. 읽기 외에는 아무런 권한이 없다.
이렇게 권한을 제한하는 이유는 무작정 권한을 주었다가는 각종 해킹 기법들이 사용될 수도 있기 때문이다. 정해진 섹션에 꼭 필요한 권한만 주어 메모리 영역을 보호하고 있다.
심볼 테이블
사용하는 함수의 이름들에 대한 정보를 가지고 있는 테이블이다.
dynsym, dynstr - 사용하는 공유 라이브러리 (dynamic) 함수의 심볼 (이름)을 저장한다.
symtab, strtab - 해당 프로그램에서 사용하는 모든 함수의 심볼 (이름)을 저장하고 있다.
동적 링킹을 할 때 필요한 섹션들이 있다. 동적 링킹이란, 실행파일이 실행될 때 실행파일과 라이브러리가 최초로 연결되는 구조이다.
PLT와 GOT
PLT (Procedure Linkage Table)
외부 라이브러리 함수의 주소와 연결하기 위해 존재하는 테이블이다.
GOT (Global Offset Table)
외부 라이브러리에 저장되어 있는 함수의 주소를 가지고 있는 테이블이다.
call printf를 하면 먼저 PLT에게 가서 GOT의 주소를 알려주고, 그다음 GOT한테 가서 printf가 저장되어 있는 주소를 알려달라고 한다. 이때 GOT는 printf의 주소를 우리에게 알려준다. 이 과정을 dl_resolve라 한다.
'Layer7 > Layer7_Reverse Engineering' 카테고리의 다른 글
리버싱 실습 (0) | 2021.05.27 |
---|---|
어셈블리 동적 분석 실습 (0) | 2021.05.24 |
어셈블리 - Assembly x64 (0) | 2021.05.18 |
Pwndbg 명령어 (0) | 2021.05.16 |
실행파일이 만들어지는 과정 (0) | 2021.05.13 |