- RELRO(RELocation Read-Only)

 = ELF 바이너리 / 프로세스의 데이터 섹션의 보안을 강화하는 일반적인 기술

 

< RELRO 모드 >

  • Partial RELRO
  • Full RELRO

 

 

- 바이너리 파일 보호기법 적용 확인 방법

checksec.sh --file ./RELRO-Relro

 

 

- Program header & Dynamic Section

 

→ RELRO 적용시 "Program Header""Dynamic Section"의 변화 확인가능 

 

˙Partial RELRO

Program Header'에 'RELRO' 영역(Read only)이 생성

해당 영역에 생성되는 Section: INIT_ARRAY, FINI_ARRAY

→    GOT영역을 덮어쓰기 가능

 

˙Full RELRO

Program Header'에 'RELRO' 영역(Read only)이 생성

해당 영역에 생성되는 Section: INIT_ARRAY, FINI_ARRAY, PLTGOT

 

제거: PLTRELSZ, PLTREL, JMPREL

추가: 'BIND_NOW', 'FLAGS_1' Section

→    GOT영역을 덮어쓰기 불가능

 

 

- 함수 호출 비교

참고: www.lazenca.net/display/TEC/04.RELRO#id-04.RELRO-Comparisonoffunctioncalls

 

˙ Partial RELRO

".plt" 영역은 0x400590 ~ 0x400610

".got.plt" 영역은 0x601000 ~ 0x601050

 

main 함수에서 printf 함수를 사용하기 위해 메모리 주소 0x4005b0을 호출 (PLT)

0x4005b0 영역의 코드는 "jmp QWORD PTR [rip+0x200a6a]"    →    메모리 주소 0x601020에 저장된 주소로 JUMP (GOT)

 

프로그램에서 printf 함수가 호출되기 전 메모리 주소 0x601020에 저장된 값: ("printf@plt+6")의 주소 값

프로그램을 실행하고 printf 함수가 호출된 후 메모리 주소 0x601020(".got.plt" 영역)에 저장된 값: 동적라이브러리의 printf 함수의 시작 주소 값

 

즉, Partial RELRO가 적용된 바이너리는 ".got.plt"영역이 Write가 가능하도록 설정되어 있기 때문에 ".got.plt" 영역에 저장된 값을 변경 가능

 

˙ Full RELRO

".plt.got" 영역은 0x4005c0 ~ 0x400600

".got.plt" 영역은 0x600fa8 ~ 0x601000

 

main 함수에서 printf 함수를 사용하기 위해 메모리 주소 0x4005c8을 호출 (PTL)

0x4005c8 영역의 코드는 "jmp QWORD PTR [rip+0x2009fa]"    →    메모리 주소 0x600fc8에 저장된 주소로 JUMP (GOT)

 

프로그램에서 printf 함수가 호출되기 전 메모리 주소 0x600fc8에 저장된 값: X

프로그램을 실행하고 printf 함수가 호출된 후 메모리 주소 0x600fc8(".got" 영역)에 저장된 값: 동적라이브러리의 printf 함수의 시작 주소 값

 

즉, Full RELRO가 적용된 바이너리는 ".got"영역이 Read-only로 설정되지 때문에 ".got" 영역에 저장된 값을 변경 불가능

 

 

- RELRO 설정여부 확인

 

˙Binary

'readelf' 명령어 사용    →    프로그램 헤더와 Dynamic section 정보를 가져와 RELRO를 설졍여부를 확인

 

파일의 프로그래 헤더에 'GNU_RELRO'가 있으면 RELRO가 적용되었다고 판단

Dynamic section에 BIND_NOW가 있으면 Full RELRO가 적용되었다고 판단

Dynamic section에 BIND_NOW가 없으면 Partial RELRO가 적용되었다고 판단

 

˙Process 

Binary의 확인 방식과 비슷, 전달되는 파일의 경로가 다름    →    /proc/<PID>/exe

추가된 동작은 '/proc/<PID>/exe' 파일에 'Program Headers' 정보가 있는지 확인

 

 

- Lazy binding(lazy linking, on-demand symbol resolution)

= 함수가 처음 호출될 때 주소를 찾는 방식

Lazy Binding은 심볼이 실제로 사용될 때까지 심볼의 라이브러리 파일의 주소 값 확인이 수행되지 않음

 

 

- Lazy binding behavior flow

모든 동적라이브러리 함수는 PLT(Procedure Linkage Table) stub코드를 통해 호출.

PLT의 stub 코드는 상대 주소를 이용하여, 사용할 GOT(Global Offset Table)의 주소 값을 검색.

PLT는 GOT의 위치를 알고 있으며, 해당 GOT에 저장된 대상 함수의 주소를 읽고 이동.

 

Lazy Binding은 함수 호출이 처음 요청될 때 stub 코드를 이용해 GOT영역에 해당 함수의 심볼 주소를 생성.

Stub 코드는 런타임 링커가 제공하는 바인딩 함수에 필요한 정보를 설정하는 역할을 하며, 설정이 완료되면 스텁 코드는 바인딩 함수로 이동.

바인딩 함수는 "_dl_runtime_resolve" 함수에 대한 인수를 설정 후에 해당 함수를 호출,  "_dl_runtime_resolve" 함수에서 반환 된 주소로 이동.

이후부터는 유저가 함수를 호출 할 때 PLT stub code는 호출한 함수로 바로 이동.

 

 

- PIC(Position Independent Code)

= 주기억 장치의 어딘가에 배치되어 절대 주소와 관계없이 모든 메모리 주소에서 수정없이 실행되는 기계 코드 (해당 기술은 보호 기술은 아님)

 

PIC는 일반적으로 공유 라이브러리에서 사용되며, 동일한 라이브러리 코드는 각 프로그램의 메모리 영역에 로드됨.

각 프로세서 들은 PIC를 서로 다른 주소에서 실행 할 수 있으며, 실행 시 재배치가 필요 없음.

 

 

- Relocatable code

= 재배치가 필요한 코드를 의미

재배치 과정은 동적 링커에 의해 코드에 생성 된 label과 symbol의 주소를 수정하는 것을 의미.

 

 

- Compare files(Non-PIC vs PIC vs NoStart) - Section Headers

PIC가 적용된 바이너리:  ".rela.plt" 섹션이 추가 되어 있음.

 

PIC와 nostartfiles 옵션이 적용된 바이너리:  ".rela.dyn", ".init", ".plt.got", ".fini", ".init_array", ".fini_array", ".jcr", ".got", ".data", ".bss" 섹션이 없음.

 

 

- Compare files(Non-PIC vs PIC vs NoStart) - Dynamic section

PIC가 적용되지 않은 파일: TEXTREL 섹션이 존재하며, PLTRELSZ, PLTREL, JMPREL 섹션은 존재하지 않음.

 

PIC가 적용된 파일: PLTRELSZ, PLTREL, JMPREL 섹션이 존재하며, TEXTREL 섹션은 존재하지 않음.

 

PIC와 nostartfiles 옵션이 적용된 파일: PLTRELSZ, PLTREL, JMPREL 섹션이 존재하며, INIT, FINI, INIT_ARRAY, INIT_ARRAYSZ, FINI_ARRAY, FINI_ARRAYSZ, RELA, RELASZ, RELAENT, RELACOUNT 섹션은 존재하지 않음.

 

-각 바이너리는 다음과 같은 재배치 정보를 포함

 

PIC가 적용되지 않은 라이브러리의 경우 재배치 필요.

PIC가 적용된 라이브러리의 경우도 재배치 필요.

-nostartfiles 옵션이 적용된 파일의 경우 재배치 불필요.

 

 

- Static Library

= 여러 오브젝트 파일을 하나의 아카이브 파일(.a)로 만든 것

 

정적 라이브러리를 사용하는 실행 파일을 빌드 할 경우 정적 라이브러리의 코드를 복사하여 실행 파일 생성.

정적 라이브러리를 이용해 여러 프로그램을 빌드 하게 되면 모든 파일에 똑같은 정적 라이브러리 함수의 코드가 포함됨.

 

˙gcc의 -c 옵션을 사용하여 오브젝트 코드 생성

$ gcc -c ~.c

 

˙ar 프로그램을 이용하여 라이브러리 파일(.a) 생성

$ ar vr ~.a ~.o

 

 

- Shared Library

= 여러 오브젝트 파일을 하나의 오브젝트 파일로 만들어 이를 공유할 수 있도록 한 것

 

실행시 공유 라이브러리를 참조하는 방식으로 링크. (실행 파일에 포함X)

동적 링커 로더(ld.so)가 해당 실행 파일에서 필요한 공유 라이브러리를 찾아내어 실행시 해당 프로세스의 메모리 맵을 조작해서 공유 라이브러리와 실행 바이너리가 같은 프로세스 공간을 사용하도록 만듬.

실제 라이브러리 코드는 공유 라이브러리에만 존재. (실행 파일에 포함X)

실행 파일과 공유 라이브러리를 함께 배포해야 함. (함게 배포되지 않을 시 라이브러리를 찾을 수 없다는 에러 메시지 출력)

 

˙Build shared library

$ gcc -fPIC -shared -o libPIC.so lazenca.c

 

˙Setting shared library

ld.so.conf 파일을 설정 

→    include /etc/ld.so.conf.d/*.confe

 

"/etc/ld.so.conf.d" 디렉토리 안에 libmy.conf라는 파일을 생성 후 공유 라이브러리 파일(.so)의 전체 경로를 입력

→    /home/autolycos/Documents/

 

공유라이브러리를 다른 모든 실행파일 내에서 사용할 수 있도록 하기 위해 캐쉬 갱신

→    $ sudo ldconfig

 

 

- Check for shared library in the executable file

ldd(List Dynamic Dependencies)는 프로그램에서 요구하는 공유 라이브러리를 출력해 주는 프로그램.

ldd를 이용하여 dynamic_test 프로그램에서 libmy.so라는 공유 라이브러리를 참조하는 것을 확인 가능.

 

 

- PIE(Position Independent Executable)

= 위치 독립 코드로 이루어진 실행 가능한 바이너리

 

˙PIE 파일 빌드 방법

$ gcc -fPIE -pie -o PIE PIE.c

 

˙PIE 적용 여부 비교

PIE가 적용되지 않은 파일은 프로그램을 실행할 때마다 전역 변수와 사용자 정의 함수의 주소가 변경되지 않음.

PIE가 적용된 파일은 프로그램을 실행할 때마다 전역 변수와 사용자 정의 함수의 주소가 매번 달라짐.

 

PIE가 적용되지 않은 바이너리의 경우 코드 영역의 값이 고정된 주소값.

PIE가 적용된 바이너리의 경우 코드 영역의 값이 offset 값(offset 값을 이용해 할당된 메모리 영역에 동적으로 위치 가능).

 

→ 할당받은 메모리 영역 + offset 값 = 함수의 시작 주소

 

 

- PIE 설정여부 확인

 

˙Binary

'readelf' 명령어를 이용    →    해당 파일의 ELF Header 정보를 가져와 PIE 설졍여부를 확인

 

"Type:"의 값이 "EXEC"일 경우 PIE가 적용되지 않았다고 판단.

"Type:"의 값이 "DYN"일 경우 PIE가 적용되었을 가능성이 있다고 판단.

해당 파일의 "Dynamic section" 정보를 가져와 "DEBUG" section이 있으면 PIE가 적용되었다고 판단.

 

˙Process

Binary의 확인 방식과 비슷하며, 전달되는 파일의 경로가 다름    →    /proc/<PID>/exe

복사했습니다!