reversing

Layer7 리버싱 6차시 과제(1) - ELF, PE 파일 정리

leesu0605 2022. 8. 3. 20:01

목차

1. ELF
2. PE



1. ELF


ELF란? Executable and Linkable File의 약자로, 리눅스 상에서 컴파일을 하게 되면 ' ... -> 오브젝트 파일 -> 링킹 -> 실행 파일'이 되는데, 이 오브젝트 파일과 실행 파일 같이 뭔가 실행할 수 있는 파일이 바로 ELF파일이다.
또한, 라이브러리도 ELF파일이다.

이 ELF 파일은 ELF 헤더와 프로그램에서 쓰이는 데이터로 구성된다.

ELF 파일은 이렇게 구성되어 있다.
저 ELF Header 부분이 파일 구조를 알려주는 부분이고, 나머지는 파일의 데이터를 저장한다.

ELF 헤더부터 알아보자.


이것이 ELF 헤더에 어떤 값이 들어가야할지 정의한 표이다.
표의 앞부분 몇가지를 한 번 알아보자.

· 0x00오프셋부터 표에 매직 넘버라는 말이 나오는데, 이는 ELF파일임을 표시하기 위한 4바이트 숫자로 \x7f\x45\x4c\x46가 들어가는데, \x45\x4c\x46을 아스키코드로 바꿔보면 "ELF"라는 글자가 나온다.

· 0x04오프셋부터 1바이트가 바로 주소를 32비트로 써야하는지 64비트로 써야하는지 나타내준다.

· 0x05오프셋부터 1바이트는 데이터 저장방식을 리틀엔디언으로 할지 빅엔디언으로 할지 나타낸다.
* 리틀엔디언은 모든 데이터를 1바이트씩 끊어 그 순서를 반대로 뒤집어 저장하는 방식이고,
  빅엔디언은 데이터를 원래 순서 그대로 저장하는 방식이다.

· 0x06오프셋부터 1바이트는 ELF파일의 버전을 나타내며, 보통 1이 사용된다.

· 0x07오프셋부터 1바이트는 운영 체제의 ABI를 구별하는데, ABI란, 응용프로그램 이진 인터페이스라는 뜻으로, API랑 많이 헷갈린다고 한다.
API는 소스코드 상의 호환이라하면, ABI는 바이너리 상의 호환이다.
말 그대로 어셈블리어의 add, mov 같은 명령어들이 호환된다는 뜻이다.

· 0x18오프셋부터 4(32bit)/8(64bit)바이트는 디버깅할 때 많이 봤던 엔트리포인트(즉, 시작 주소)가 담겨 있다.


ELF 헤더에선 이런 식의 파일 구조를 저장한다고 생각하면 이해하기 수월할 것이다.
참고로 ELF 헤더는 readelf -h 옵션을 통해 확인할 수 있다.

아까 봤던 매직 넘버, 운영체제 유형, 리틀엔디언, 빅엔디언 등 정보가 나온다.

ELF 헤더는 충분히 알아본 것 같으니, 섹션 헤더 테이블에 대해 알아보자.
섹션 헤더 테이블은 여러 개의 섹션으로 나뉘어져 있는데 하나씩 보면,

1. .text : 명령어 기록
2. .data : 초기화된 전역 변수, 정적 변수(static) 기록
3. .bss : 초기화되지 않은 전역 변수, 정적 변수 기록 -> 값은 0으로 들어감
4. .rodata : 읽기 전용 데이터 기록 -> 문자열, 상수 기록
5. .symtab : 소스코드 상의 변수나 함수 이름 기록
5. .dynsym : #include로 가져와 사용하는 모든 심볼(변수나 함수 이름) 기록

말로만 들으면 기억에 안 남으니 직접 확인해보자.

이런 C코드를 짜고 컴파일해 readelf로 섹션 정보를 보도록 하겠다.

컴파일하고 실행시켜봤더니 뭔가 이상한 숫자들과 영어들이 보인다.
모두 섹션이며, 우리는 아까 배운 영역만 확인하도록 하자.

가져와봤다.
각 표의 첫 번째 숫자는 영역의 크기이며, 네 번째는 오프셋이다.
아까 설명했던 대로 변수들이 어떻게 선언됐는지에 따라 각 영역에 저장되어있을 것이다.
ex) "Hello World"(문자열) -> .data 섹션

다음으론, readelf -s 명령어로 dynsym과 symtab에 들어있는 값들을 출력해봤다.
dynsym에는 puts 같은 stdio.h 헤더파일에서 #include로 가져온 심볼이 들어있는 걸 확인할 수 있다.
또한, symtab의 하단을 보면, hello.c, bss2, data2 같이 소스코드 이름, 변수명이 출력되는 것도 확인할 수 있다.

마지막으로, 프로그램 헤더 테이블이라는 게 있는데, 이는 프로세스에 메모리 프로그램을 올릴 때 어떻게 올릴 건지 기술돼있다.


2. PE


ELF파일이 리눅스의 실행파일이라면, PE파일은 윈도우의 실행파일이라고 이해하면 된다.

한번 PE파일에 관해서도 알아보자.

PE파일의 구조는 이렇게 된다.
PE Header에는 파일에 관한 다양한 정보들이 구조체 형식으로 저장되어 있으며, PE body에는 아까 ELF에서도 봤던 여러 섹션이 보인다.

그런데, 아까 ELF에선 보이지 않았던 NULL Padding이라는 것이 보인다.
이는 최소단위라는 개념이 있어야 이해할 수 있는데, 최소단위란 데이터를 더 효율적으로 처리할 수 있도록 메모리 내용을 어떠한 최소한의 단위에 따라 구성하게 하는 것이다.
즉, 나는 1바이트만을 써도 되는데 최소단위가 8바이트면 8바이트를 다 써야한다는 내용이다.
그래서 중간에 NULL Padding로 남는 공간을 메워주는 것이다.
참고로 NULL의 뜻은, "아무것도 없는"이다.

PE 파일의 가장 중요한 특징은 바로 RVA를 사용한다는 것이다.
RVA는 상대 주소로 해석할 수 있는데, 정확하게 설명하자면 컴퓨터가 어떤 기준점(ImageBase)를 잡고, 그 기준점을 이용해 어떠한 주소에 접근하는 것이다.
예를 들어, VA(절대 주소)가 5라고 하고, ImageBase를 2라고 해보자.
그럼 RVA는 기준점(ImageBase)에서 3만큼 더한 주소라는 걸 표시해주면 되므로, 3이 될 것이다.
즉, 다음 식이 성립한다.
VA = RVA + ImageBase
이걸 위의 상황에 적용해보면,
VA(5) = RVA(3) + ImageBase(2)
이런 식으로 사용할 수 있게 된다.
이런 식으로 사용하는 이유는 더 효율적으로 작동할 수 있기 때문이다.

나머지 .test, .data 섹션은 아까 ELF에서도 봤던 섹션들이다.
그런데 못 봤던 섹션이 하나 나왔다.
".rsrc"라는 녀석이다.

.rsrc는 gui프로그램의 그래픽 정보를 저장하는 섹션으로, cli기반인 리눅스에서 이게 보이지 않았던 이유이다.
그러나 윈도우는 gui기반이기 때문에 그래픽 정보를 저장할 필요가 있다.