[Linux Kernel] pipe_buffer를 이용한 기법들

dandb3·2024년 8월 12일
0

linux kernel

목록 보기
15/21

우선 UAF 취약점이 있다고 가정을 한다.

read-only file write

사실상 Dirty Pipe 취약점(CVE 2022-0847)의 연장선에 해당한다고 봐도 될 것이다.

UAF를 통해 만들어진 victim object가 할당된 pipe_buffer 배열을 가리키는 상태를 생각해보자.
splice()를 통해 pipe에 파일 내용 쓰기를 하면 해당 파일의 page cache가 pipe_buffer에 저장되게 된다.
이 때, victim object를 통해 pipe_buffer->flagsPIPE_BUF_FLAG_CAN_MERGE 비트만 set으로 바꾼다.
그러면 이후 해당 pipe로의 write를 통해 실제 file의 page cache의 값이 수정되고, 결과적으로 실제 file도 수정할 수 있다.

pipe_buffer를 이용한 ROP

pipe_buf_operations의 주소를 변경하는 방법이다.
pipe가 할당 해제될 때 pipe_buffer를 순회하며 pipe_buffer->ops->release()가 호출된다는 점을 이용한다.

특히나 victim object가 object에 큰 크기의 write를 할 수 있는 경우 ROP의 형태로 변경할 수 있는 방법이다.

다음과 같은 과정을 통해 이루어진다.
1. pipe생성 및 임의의 데이터를 집어넣는다.
2. pipe를 제거한다. (close 호출) -> pipe_buffer->ops->release() 가 호출됨.
3. overwrite된 ops에 의해 임의의 코드 가젯이 실행됨. 이 때 이 코드 가젯에 의해 stack pivot이 이루어짐, rsp는 pipe_buffer를 가리키게 된다.
4. ROP

한 가지 주목할 점은, release()의 프로토 타입이다.

void (*release)(struct pipe_inode_info *, struct pipe_buffer *);

두 번째 인자가 struct pipe_buffer *이다.
즉, 리턴 가젯을 통해 rsi의 값을 rsp로 옮겨줄 수 있게 된다.

사실 mov rsp, rsi ; ret 가젯이 있으면 좋겠지만, 일단 내가 푼 문제에서는 적어도 없었다. push rsi ; pop rsp ; ret의 꼴도 없거나 제대로 작동하지 않았다.
이 외의 방법도 존재하는데, 바로 아래의 두 가젯을 이용하는 것이다.

  • push rsi ; jmp QWORD PTR [rsi + 0x39]
  • pop rsp ; ret

물론 환경에 따라 정확한 offset은 다를 수도 있다.
하지만 pipe_buffer에 write이 가능한 상황을 가정하고 있고, rsi가 바로 pipe_buffer를 가리키고 있으므로 시작 가젯을 push rsi ; jmp QWORD PTR [rsi + 0x39]로 하고, pipe_buffer + 0x39의 위치에 pop rsp ; ret를 집어넣으면 성공적으로 stack pivot이 가능하다.

여기서부터 한 가지 고려할 점이 생긴다.
stack pivot은 pipe_buffer로 성공했는데, 앞서 opspipe_buffer + 0x39를 덮어야 지금 단계까지 올 수 있다. 즉, stack은 pipe_buffer로 pivot 되었지만, 그 아래의 데이터는 이미 전 단계에 쓰였기 때문에 ROP를 위해 쓸 수 없게 된다.

그 때 쓸 수 있는 것이 바로 add rsp, 0x50 ; ret 꼴의 가젯이다.
이 가젯을 이용하면 앞서 사용했던 영역들을 건너뛰고 깔끔한 ROP가 가능해진다.

사실 이 방법이 좋은 이유는 따로 있다.
바로 pipe_buffer의 할당 크기를 유저 임의대로 조절할 수 있다는 점.
그래서 특정 크기의 UAF가 발생했을 때 그 크기에 맞춰서 exploit을 진행할 수 있다.

profile
공부 내용 저장소

0개의 댓글

관련 채용 정보