1) 프로세스 명령어나 코드 또는 데이터 저장을 위한 메모리 영역을 따로 분리하는 CPU의 기술
2) NX 특성으로 지정된 모든 메모리 구역은 데이터 저장을 위해서만 사용되며, 프로세서 명령어가 그 곳에 상주하지 않음으로써 실행되지 않도록 만들어 줌.
1) 마이크로소프트 윈도우 운영체제에 포함된 보안 기능, 악의적인 코드가 실행되는 것을 방지하기 위해 메모리를 추가로 확인하는 하드웨어 및 소프트웨어 기술
2) 하드웨어 DEP: 메모리에 명시적으로 실행 코드가 포함되어 있는 경우를 제외하고 프로세스의 모든 메모리 위치에서 실행할 수 없도록 표시함. 대부분의 최신 프로세서는 하드웨어 적용 DEP 지원
3) 소프트웨어 DEP: CPU가 하드웨어 DEP를 지원하지 않을 경우 사용
4) 예) 공격자가 Heap, Stack 영역에 Shellcode를 저장해서 실행하기 위해서는 해당 영역에 실행 권한이 있어야 함. DEP가 적용되지 않았을 경우에는 쉘 코드 실행, 적용된 경우에는 실행권한이 없으므로 쉘 코드가 실행되지 않음.
5)
메모리 맵에서 메모리 영역별 설정된 권한 확인 가능
DEP enabled의 경우 실행권한을 가지고 있는 영역은 5곳
DEP disabled의 경우 실행권한을 가지고 있는 영역은 17곳
1) Biniry
- readelf 명령어 이용하여 파일의 세그먼트 헤더 정보에서 NX 여부 확인
- 파일의 세그먼트 헤더 정보에서 ‘GNU_STACK’의 fla 값이 ‘RWE’라면 NX가 활성화되었다고 판단
2) Process
- Binary의 확인 방식과 동일, 전달되는 파일의 경로가 다름
ex) /proc/<PID>/exe
3) CPU
- “/proc/cpuinfo” 파일에서 ‘nx’ 문자가 있는지 확인
1) 메모리 손상 취약점 공격을 방지하기 위한 기술
2) 스택, 힙, 라이브러리 등의 주소를 랜덤한 영역에 배치하여 공격에 필요한 Target address를 예측하기 어렵게 만듦. 프로그램이 실행 될 때마다 각 주소들이 변경 됨
1) cmd: echo 0 > /proc/sys/kernel/randomize_va_space
2) option
- 0: ASLR 해제
- 1: 랜덤 스택 & 랜덤 라이브러리 설정
- 2: 랜덤 스택 & 랜덤 라이브러리 & 랜덤 힙 설정
- system-wide ASLR (kernel.randomize_va_space): On (Settig: 2) 확인 가능
1) “/proc//maps” 파일을 통해 프로세스의 메모리 구조 및 주소 확인 가능
2) randomize_va_space에 2를 설정한 환경
프로그램을 처음 실행했을 때와 두번째 실행했을 때 메모리 배치가 다른 것을 확인 가능
3) .data 영역의 주소는 변경되지 않음. 해당 영역의 주소도 매번 새로운 주소에 할당하기 위해서는 PIE를 적용해야 함
1) “/proc/1/status” 파일 내에 ‘PaX’ 단어를 검색하고 검색 결과에서 ‘R’이 존재하는지 확인
2) “/proc/1/status” 파일 내에 ‘PaX’ 단어가 없을 경우 ‘sysctl’ 명령어를 이용해 확인. ‘sysctl’ 명령어를 통해 출력된 내용 중 “kernel.randomize_va_space=”의 값을 확인해 ASLR 설정을 판단
1) Terminator canaries: Canary의 값을 문자열의 끝을 나타내는 문자들을 이용해 생성
- 값은 NULL(0x00), CR(0x0d), LF(0x0a) 및 EOF(0xff)로 구성됨
- 공격자는 canaries를 우회하기 위해 Return address를 쓰기 전 null문자를 써야 함
- null 문자로 인해 overflow를 방지하게 됨 (strcpy()는 null문자의 위치까지 복사)
- 공격자는 잠재적으로 Canary를 알려진 값으로 겹쳐 쓰고 정보를 틀린 값들로 제어해서 Canary 검사 코드를 통과할 수 있음.
2) Random canaries: Canary의 값을 랜덤하게 생성함.
- 일반적으로 익스플로잇을 이용하여 Canary를 읽는 것은 논리적으로 불가능
- 프로그램 초기 설정 시 전역변수에 Canary 값이 저장됨. 이 값은 보통 매핑되지 않은 페이지에 저장됨. 해당 메모리를 읽으려는 시도를 할 경우 segmaentation fault가 발생하고 프로그램이 종료됨.
- 공격자가 canary 값이 저장된 stack address를 알거나 스택의 값을 읽어올 수 있는 프로그램이 있다면 canary의 값을 확인할 수 있음
3) Random XOR canaries: Canary의 값을 모든 제어 데이터 또는 일부를 사용해 XOR-scramble하여 생성함
- 제어 데이터가 오염되면 Canary의 값이 틀려짐.
- Random Canaries와 동일한 취약점을 가짐. 단지 Canary의 값을 stack에서 읽어오는 방법이 조금 더 복잡해짐. 공격자는 canary를 다시 인코딩하기 위해 original canary 값, 알고리즘, 제어 데이터가 필요함.
gcc -fstack-protector -param ssp-buffer-size=N xx.c // byte 변경
gcc -fstack-protector-all xx.c // 모든 함수 보호
사용자 값이 저장되는 영역은 0x7ffffffffe180
해당 영역에 코드에서 할당한 길이의 문자를 저장 (‘A’ *32)
0x400610 코드 영역에서 rax 레지스터에 rbp – 0x8 영역에 저장된 값을 저장
rbp(0x7fffffffe1b0) – 0x8 = 0x7fffffffe1a8
0x7fffffffe1a8 영역에 저장된 값: 0x3a3b864735c7b300
0x400614 코드 영역에서 rax 레지스터에 저장된 값과 fs:0x28 레지스터에 저장된 값을 xor 연산
0x40061d 코드 영역에서 rax 레지스터의 값이 0과 같으면 0x400624 영역으로 이동함. 이로 인해 정상적으로 프로그램 종료
1) Binary
- **readelf 명령어**를 이용해 해당 파일의 심볼 테이블 정보를 가져와 Canary 설정여부를 확인
- 파일의 심볼 테이블에 **“__stack_chk_fail”**가 있으면 canary가 적용되었다고 판단함.
2) Process
- Binary의 확인 방식과 비슷하며, 전달되는 파일의 경로가 다름 (ex. /proc/<PID>/exe)
- ‘/proc/<PID>/exe’ 파일에 ‘Symbol table’ 정보가 있는지 확인
NO RELRO, Partial RELRO, Full RELRO의 차이점
1) Partial RELRO를 적용하게 되면 ‘Program Header’에 ‘RELRO’ 영역이 생성됨. 해당 영역의 권한은 Read only, 해당 영역에 포함되는 Section은 INIT_ARRAY, FINI_ARRAY, 즉 GOT 영역을 덮어쓸 수 있음.
2) Full RELRO를 적용하게 되면 ‘Program Header’에 ‘RELRO’ 영역이 생성됨. 해당 영역의 권한은Read only이며 포함되는 Section은 INIT_ARRAY, FINI_ARRAY, PLTGOT. section 영역에서 PLTRELSZ, PLTREL, JMPREL가 제거되고, ‘BIND_NOW’, ‘FLAGS_1’ Section이 추가됨. GOT 영역을 덮어쓸 수 없음.
1) No RELRO
“__isoc99_scnaf”의 GOT Address는 0x600c68이며, 아래와 같이 해당 영역에 값을 변경할 수 있음
헤더 정보와 메모리 맵. “__isoc99_scnaf”의 주소값은 ‘.got.plt’영역에 저장되어 있음. ‘.got.plt’ 영역의 시작 주소는 0x600c20
메모리 맵을 통해 해당 영역(0x00600000 ~ 0x00601000)에 ‘W” 쓰기 권한이 설정되어 있음
2) Partial RELRO
“__isoc99_scnaf”의 GOT Address는 0x601048이며 해당 영역에 값을 변경할 수 있음
헤더 정보와 메모리 맵 확인. ‘.got.plt’ 영역의 시작 주소는 0x6010000. 메모리 맵에서 RELRO가 적용되지 않은 프로그램과 다른 부분을 확인할 수 있음. 0x600000~0x601000 영역의 권한은 r—p. 해당 영역에넌 .init_array, fini_array, .jcr, .dynamic, .got 헤더가 포함됨. 0x601000~0x602000 영역의 권한은 rw-p. 해당 영역에는 .got.plt 등의 헤더가 포함되고 이로인해 .got.plt 영역의 값을 변경할 수 있음.
3) Full RELRO
GOT 영역의 값을 변경할 수 없음.
디버거에서 ‘_isoc99_scnaf’의 심볼 정보를 찾을 수 없음.
디스어셈블 코드에서 호출되는 함수를 분석해보면 0x4007fd 영역의 코드에서 0x4005f8 영역을 호출. 0x4005f8 영역의 코드에서 “rip+0x2009fa” 영역에 저장된 주소로 이동함. “rip+0x2009fa” 영역은 0x600ff8이며, 해당 영역에 저장된 값은 0x00007ffff7a784d0
0x00007ffff7a784d0 영역은 __isoc99_scnaf 함수의 시작 주소
해당 프로그램의 헤더 구성은 No RELRO, Partial RELRO와 달리 헤더 정보에 ‘.rela.plt’, ‘.got.plt’ 헤더가 존재하지 않음.
1) Partial RELRO
- main 함수에서 printf 함수를 사용하기 위해 메모리 주소 0x4005b0을 호출. 메모리 주소 0x4005b0은 “.plt” 영역. “.plt” 영역은 0x400590 ~ 0x400610.
- 0x4005b0 영역의 코드는 “jmp QWORD PTR [rip+0x200a6a]” 즉, 메모리 주소 0x601020은 “got.plt” 영역. “.got.plt” 영역은 0x601000 ~ 0x601050
- 메모리 주소 0x601020에 저장된 값은 동적 라이브러리의 주소가 아닌 ‘.plt’ 영역. 이는 해당 프로그램에서 printf 함수가 호출되지 않았기 때문에 Stub 코드(“printf@plt+6”)의 주소값이 저장되어 있음.
- 프로그램을 실행하고 printf 함수가 호출되기 시작하면 메모리 주소 0x601020(“.got.plt” 영역) 영역에 동적라이브러리의 printf 함수의 시작 주소 값이 저장됨.
- Partial RELRO가 적용된 바이너리는 “.got.plt”영역이 Write가 가능하도록 설정되어 있기 때문에 “.got.plt” 영역에 저장된 값을 변경할 수 있음
아직 호출되지 않은 함수들의 GOT 값 확인
main 함수에서 scanf 함수를 사용하기 위해 메모리 주소 0x400600(“.plt”)을 호출. 0x400600 영역의 코드는 “jmp QWORD PTR [rip+0x200a42]”이며, 0x601048 영역에 저장된 주소로 이동함. 0x601048 영역에 저장된 값은 0x400606이며, 해당 영역은 Stub 코드가 저장되어 있음. scanf 함수가 아직 호출된 적이 없기 때문에 0x601048(“.got.plt”) 영역에 동적 라이브러리의 scanf 함수의 시작 주소 값이 저장되어 있지 않음. Partial RELRO에 lazy binding을 사용하기 때문에 함수를 호출하지 않으면 동적 라이브러리의 주소 값을 “.got.plt” 영역에 저장되지 않음.
2) Full RELRO
- main 함수에서 printf 함수를 사용하기 위해 메모리 주소 0x4005c8을 호출. 메모리 주소 0x4005c8은 “.plt.got” 영역. “.plt.got” 영역은 0x4005c0 ~ 0x400600
- 0x4005c8 영역의 코드는 “jmp QWORD PTR [rip+0x2009fa]”. 메모리 주소 0x600fc8에 저장된 주소로 JUMP. 메모리 주소 0x600fc8은 “.got” 영역. “.got.plt” 영역은 0x600fa8 ~ 0x601000
- 메모리 주소 0x600fc8에 아무런 값도 저장되어 있지 않음. 이는 해당 프로그램에서 printf 함수가 호출되지 않았기 때문. 프로그램을 실행하고 printf 함수가 호출되기 시작하면 메모리 주소 0x600fc8(“.got” 영역) 영역에 동적라이브러리의 printf 함수의 시작 주소 값이 저장됨
- Full RELRO가 적용된 바이너리는 “.got” 영역이 Read-only로 설정되기 때문에 “.got” 영역에 저장된 값을 변경할 수 없음.
아직 호출되지 않은 함수들의 GOT 값 확인
main 함수에서 scanf 함수를 사용하기 위해 메모리 주소 0x4005f8(“.plt.got”를 호출. 0x4005f8 영역의 코드는 “jmp QWORD PTR [rip+0x2009fa]”이며, 0x600ff8 영역에 저장된 주소로 이동함. 0x600ff8 영역에 저장된 값은 0x00007ffff7a784d0이며, 해당 영역은 동적 라이브러리의 scanf 함수의 시작 주소 값. Full RELRO에서는 Now binding을 사용하기 때문에 프로그래밍 메모리에 로드 될 때 해당 프로그램에서 사용되는 모든 동적 함수의 주소를 “.got” 영역에 저장 됨.
1) Binary
- ‘readelf’ 명령어를 이용해 해당 파일의 프로그램 헤더와 Dynamic section 정보를 가져와 RELRO의 설정여부를 확인
- 파일의 프로그램 헤더에 ‘GNU_RELRO’가 있으면 RELRO가 적용되었다고 판단
- Dynamic section에 BIND_NOW가 있으면 Full RELRO가 적용되었다고 판단
- Dynamic section에 BIND_NOW가 없으면 Partial RELRO가 적용되었다고 판단
2) Process
- Binary의 확인 방식과 비슷하며, 전달되는 파일의 경로가 다름 (ex. /proc/<PID>/exe)
- ‘/proc/<PID>/exe’ 파일에 ‘Program Headers’ 정보가 있는지 확인
1) 일반적으로 공유 라이브러리에서 사용되며, 동일한 라이브러리 코드는 각 프로그램의 메모리 영역에 로드됨.
2) 각 프로세스들은 PIC를 서로 다른 주소에서 실행할 수 있으며, 실행 시 재배치가 필요 없음.
3) 공유 라이브러리를 만들 때 -fPIC 옵션을 이용하여 소스를 컴파일 함.
4) Relocatable code: 재배치가 필요한 코드. 재배치 과정은 동적 링커에 의해 코드에 생성 된 label과 symbol의 주소를 수정하는 것.
1) Section Headers: PIC가 적용된 바이너리에는 “.rela.plt” 섹션이 추가됨. PIC와 nostartfiles 옵션이 적용된 바이너리에는 “.rela.dyn”, “.init”, “.plt.got”, “.fini”, “.init_array”, “.fini_array”, “.jcr”, “.got”, “.data”, “.gss” 섹션이 없음.
2) Dynamic section: PIC가 적용되지 않은 파일에는 TEXTREL 섹션이 존재하며, PLTRELSZ, PLTREL, JMPPEL 섹션은 존재하지 않음. 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 옵션이 적용된 파일의 경우 재배치가 필요 없음.
RELA: 상대주소 재배치 테이블 주소
RELASZ: 상대주소 재배치 테이블 크기
RELAENT: 상대 주소 재배치 엔트리 크기
RELACOUNT: 재배치 횟수
3) Code
- NonPIC: PIC가 적용되지 않은 바이너리의 경우 함수를 호출할 때 rdx 레지스터에 저장된 주소를 호출함.
디버깅을 통해 함수 호출을 분석할 수 있음. main 함수는 lazenca 함수를 호출하기 위해 0x400570(lazenca@plt) 영역을 호출함. 0x400699 영역에 Break point를 설정 후 프로그램을 실행함. lazenca 함수의 실제 주소가 0x601020 영역에 재배치 됨. 공유 라이브러리가 프로그램에 로드되어 lazenca 함수를 Disassemble 할 수 있음. rdx 레지스터에 0x7ffff7860800이 저장되고 호출됨. 0x7ffff7860800 영역은 “/lib/x86_64-linux-gnu/libc.so.6”의 .text 영역.
- PIC: PIC가 적용된 바이너리는 함수를 호출 할 때 .plt 영역의 해당 함수의 주소를 호출.
디버깅을 통해 함수 호출을 분석할 수 있음. main 함수는 lazenca 함수를 호출하기 위해 0x400570(lazenca@plt) 영역을 호출함. 0x400599 영역에 Break point를 설정 후 프로그램을 실행. lazenca 함수의 실제 주소가 0x601020 영역에 재배치 됨. 공유 라이브러리가 프로그램에 로드되어 lazenca 함수를 Disassemble 할 수 있음. lazenca 함수는 printf 함수를 호출하기 위해 0x7ffff7bd5580 영역을 호출함. 0x7ffff7bd5580 영역은 “/home/lazenca0x0/Documents/Definition/protection/PIC/libPIC.so”의 .plt 영역
1) NoPIE
2) PIE
1) NoPIE: PIE가 적용되지 않았기 때문에 프로그램을 실행할 때마다 전역 변수와 사용자 정의 함수의 주소가 같음
2) PIE: PIE가 적용되었기 때문에 프로그램을 실행할 때마다 전역 변수와 사용자 정의 함수의 주소가 매번 달라짐.
3) PIE와 NoPIE의 코드 차이
- PIE가 적용되지 않은 바이너리의 경우 코드 영역의 값이 고정된 주소값
- PIE가 적용된 바이너리의 경우 코드 영역의 값이 고정된 주소 값이 아닌 offset 값
- 해당 offset 값을 이용해 할당된 메모리 영역에 동적으로 위치할 수 있음.
- 할당받은 메모리 영역(0x555555554000) + .text 영역의 main함수 코드의 offset 값 (0x7c3) = main 함수의 시작 주소 (0x00005555555547c3)
NoPIE
PIE
1) Binary
- ‘readelf’ 명령어를 이용해 해당 파일의 ELF Header 정보를 가져와 PIE 설정 여부 확인
- “Type:” 값이 “EXEC”일 경우 PIE가 적용되지 않았다고 판단
- “Type:”의 값이 “DYN”일 경우 PIE가 적용되었을 가능성이 있다고 판단
- ‘readelf’ 명령어를 이용해 해당 파일의 ‘Dynamic section” 정보를 가져와 “DEBUG” section이 있으면 PIE가 적용되었다고 판단
2) Process
- Binary의 확인 방식과 비슷하며, 전달되는 파일의 경로가 다음과 같이 다름 (ex. /proc/<PID>/exe)