들어가며
ASLR : 스택, 힙, 라이브러리 등의 위치가 무작위로 랜덤하게 설정됨
- 한계 : 코드 영역과 데이터 영역의 위치는 고정되는 단점
- 우회 :
- 라이브러리 주소를 알아내면 고정된 오프셋을 통해 원하는 함수로 overwrite할 수 있음
- PIE가 적용되지 않으면 코드 세그먼트의 주소는 고정되므로 PLT를 overwrite할 수 있음
- 고정된 주소의 리턴 가젯(코드 영역에 위치) 등을 이용하여 실행 흐름을 조작할 수 있음
아래는 addr.c라는 파일을 PIE를 적용하지 않고 컴파일한 경우이다.
buf_stack, buf_heap, libc_base, printf의 주소는 바뀌지만 main 함수와 사용자 정의 함수 my_func의 위치는 고정되고 있음
-> 만약 리턴 가젯등의 주소가 실행마다 달라진다면 저런 공격을 막을 수 있지 않을까?
-> 코드 영역의 주소도 랜덤화하면 되겠다!
(여기서 헷갈릴 수 있는 점: printf는 함수인데 왜 코드 세그먼트에 존재하지 않나요? -> 여기서 printf는 라이브러리 내의 함수이기 때문이다!! 만약 우리가 코드에서 사용자 정의 함수를 정의하면 그 함수는 코드 세그먼트에 작성되어서 랜덤화되지 않는다.)
공유 오브젝트 파일(SO)와 PIC(Position Independent Code)
ELF의 종류 : 실행 파일(Executable), 공유 오브젝트(SO, Shared Object) 파일
실행 파일은 우리가 아는 그냥 그 실행파일이고, 공유 오브젝트는 libc.so와 같은 라이브러리 파일 등이 해당된다.
링크 과정을 떠올려보자. 우리의 코드를 컴파일 하면 중간에 오브젝트 파일로 바뀌고, 그뒤에 링크를 통해 라이브러리에서 함수들의 정의를 가져와서 이를 합치는 과정이 있었다.(오브젝트 파일이 궁금하면 다음 링크 참고 :
[🖥️ Computer Science/System] - [System] 컴파일 과정의 링크(Link)와 오브젝트 파일(Object File) 또는 [🖥️ Computer Science/System] - [ROP 시리즈 (1)] 라이브러리와 링크) 정확히 말하면 우리의 오브젝트 파일과 라이브러리 공유 오브젝트 파일을 같이 합치는 것을 말하는 것이다. 이때의 라이브러리 파일이 공유 오브젝트의 대표적인 예라고 이해하면 된다.
공유 오브젝트 파일에 대해 좀더 자세히 살펴보자.
공유 오브젝트 파일의 큰 특징은 재배치(Relocation)이 가능하다는 것이다.
재배치가 뭔데? 어떤 코드가 재배치가 가능하다는 것은 코드가 메모리에 어느 곳에 위치해도 그 의미를 훼손하지 않는다는 것을 의미한다. 이게 무슨 의미인지 잘 감이 안 잡힌다면 아래 예시를 보자!
"의미를 훼손하지 않는다는 것이 무슨 뜻일까??"
예를 들어 mov rdi, 0x1234 라는 코드가 있다고 가정해보자. rdi를 0x1234라는 곳으로 옮기겠다는 뜻이다. 그런데 주소가 계속 랜덤화된다면, 0x1234에 있는 데이터의 의미가 계속 바뀔 것이다. 예를 들어 우리는 0x1234에 rsp가 있어서 mov rdi, 0x1234를 하고 싶은데 rsp의 위치가 계속 랜덤화되어서 0x1234, 0xffff, 0x14fa 뭐 이런식으로 계속 바뀐다면 mov rdi, 0x1234를 하는 이유가 없다. 즉 rsp의 위치를 가져오겠다는 의미가 훼손된 것이다.
따라서 우리는 어떤 방식으로 이를 해결하냐?
초록색으로 하이라이트 된 코드를 보면 절대주소(0x4005a1) 대신 [rip+0xa2]를 쓰고 있다. 그러니까 코드의 위치가 바뀌어도 언제나 rip와 0xa2만큼 떨어진 곳의 데이터를 가져오기 때문에 항상 같은 값을 가져올 수 있다. 왜냐하면 오프셋은 계속 동일할 것이기 때문이다.
이처럼 절대주소를 모두 rip+offset 처럼 바꿔주는 방식을 사용하면 코드의 의미가 훼손되지 않는다. 이를 상대 참조라고 한다(Relative Addressing)
이와 같이 재배치가 가능한(어디에 배치해도 의미가 훼손되지 않는) 코드를 우리는 PIC(Position-Independent Code)라고 부른다. 따라서 공유 오브젝트 파일은 PIC이다!
🥧PIE
PIC가 Relocation이 가능한 코드였다면, PIE(Position-Independent Executable)은 Relocation(재배치)가 가능한 실행파일이다. (🥧, π <-이 파이 아님)
ASLR가 도입되고 난 후, 실행파일도 무작위 주소에 배치하고 싶었으나 호환성 문제 때문에 어려웠다. 그래서 우리는 공유 오브젝트 파일을 실행파일로 사용하기로 하였다. 왜냐하면 공유 오브젝트 파일은 재배치(Relocation)이 가능하기 때문이었다.
예를 들어 /bin/sh와 같은 명령어 실행파일이 대표적인 공유 오브젝트 파일이다.
현대의 gcc는 따로 옵션을 주지 않는 이상 PIE를 자동으로 적용한다.
포스트의 맨 위 코드에서는 pie를 적용하지 않아 스택과 힙의 주소만 랜덤화되고 코드 세그먼트의 주소는 고정되었었다.
아래 예시는 이제 pie를 적용하여 컴파일한 코드를 실행한 것이다.
스택, 힙, 라이브러리 주소, 라이브러리의 printf 주소 뿐만 아니라 내가 정의한 my_func의 주소와 main함수의 주소도 무작위로 배치되고 있다.
그렇다면 이제 코드 세그먼트에 있는 함수들의 주소는 절대 탈취 불가능한 것일까??? 그건 또 아니다...
PIE 우회
일단 libc_base와 printf를 유심히 보자. 코드를 실행할때마다 앞부분이 동일하다 (맨 아래 실행결과를 예로 들면 0x7f9741c가 동일) 이는 PIE 때문은 아니고, 라이브러리 주소 안의 오프셋이 동일하기 때문이었다. (그래서 위 캡쳐본 노란색 박스 옆에있는 libc_base와 printf의 5bit들을 빼면 계속 동일하다) 이를 보여준 포스트가 여기에 있다. -> [🔐 Security/System] - [gdb] 로컬/서버의 라이브러리 다른 경우
이제 main과 my_func을 보자.실행 때마다 주소가 달라지는듯 보이지만 초록색 부분은 main과 my_func이 항상 일치한다.
왜냐하면 위의 라이브러리 오프셋이 일정한것과 같이 코드 세그먼트도 오프셋이 동일하기 때문이다.
따라서 코드 세그먼트 안의 임의 주소를 읽고 오프셋을 빼서 코드 세그먼트 베이스가 적재되는 주소를 구하면 코드 세그먼트 안의 어떤 코드라도 접근할 수 있다.
만약 위처럼 코드 세그먼트 베이스의 주소를 구하기 어렵다면 어떻게 할까?
위 코드 실행결과를 다시 보면 뒤의 main과 my_func 의 12bit가 184와 169로 항상 동일하다. 하위 12bit만 똑같이 유지되는 이유는 페이지 기법 때문이다. 페이지 기법에 대해서는 OS 포스트에서 따로 다루도록 하겠다.
우리가 main함수에서 main함수 caller로 돌아가는 주소를 덮는다고 가정해보자. 그 주소를 A라고 하자. 그럼 주소 A는 코드 세그먼트 안에 있는 주소일 것이다. 이때 B의 주소가 A의 주소가 하위 한바이트만 다르다고 해보자
이때 우리는 A를 우리가 정의한 함수의 주소 B로 덮고 싶다. B의 주소는 코드 세그먼트 안에 있을 것이다.
이때 우리가 A의 하위 한바이트만 B의 하위 한 바이트로 overwrite하면 공격할 수 있을 것이다. 이를 Partial Overwrite라고 한다.
코드 세그먼트 안에선 최대 12bit만 차이나므로 A와 B의 하위 2byte가 다른 경우 또한 생각할 수 있다.
이런 경우 16bit중 12bit는 그냥 덮으면 되지만 나머지 4bit는 random화되기 때문에 bruteforce attack으로 공격해야한다(하지만 이경우는 16가지 경우만 하면된다)
이후 3byte이상 다른경우 이제 그냥 bruteforcing이다..
정리
- ALSR : 스택, 힙, 라이브러리만 랜덤화 -> 코드 세그먼트 주소는 랜덤화하지 않음. -> PLT Overwrite, 코드 세그먼트 안의 리턴 가젯을 이용한 ROP 가능
- PIC : Position Independent Code (재배치 가능한 코드). rip등으로 구현.
- PIE : Position Independent Executable (재배치 가능한 실행 파일). 대표적으로 공유 오브젝트 파일이 있음. 컴파일할 때 따로 옵션 안 주면 PIE로 함. 코드 세그먼트 위치도 랜덤화! -> 코드 세그먼트 안의 오프셋 동일 -> 베이스 구하거나 Partial Ovewrite등으로 우회 가능.
Reference
https://dreamhack.io/lecture/courses/113
'Linux Exploitation > Fundamentals' 카테고리의 다른 글
[Pwnable] SECCOMP (0) | 2023.07.10 |
---|---|
[Pwnable] .init_array, .fini_array, _rtld_global (0) | 2023.07.08 |
[gdb] 로컬/서버의 라이브러리 다른 경우 (0) | 2023.04.28 |
[ROP 시리즈 (3)] 드림핵(Dreamhack) - Return to Library 개념 및 실습 (1) | 2023.04.18 |
[ROP 시리즈 (2)] PLT, GOT (0) | 2023.04.17 |