
DirtyPipe에서 영감을 받은 Linux 커널의 익스플로잇 프리미티브
DirtyPipe(CVE-2022-0847)에서 영감을 받은 Linux 커널의 익스플로잇 프리미티브입니다.
얼마 전, 저는 많은 보안 선배들처럼 DirtyPipe(CVE-2022-0847) 취약점을 학습하고 재현하면서 이 취약점이 얼마나 유용한지 깊이 느꼈습니다. 이 취약점은 메모리 초기화 누락 문제에서 시작해 임의 파일 수정으로 이어지며, 그 과정에 KASLR 누출이나 ROP, JOP 같은 작업이 필요하지 않습니다. 따라서 SMEP, SMAP 같은 보호를 우회할 필요도 없습니다.
재현을 마친 뒤, 저는 DirtyPipe 취약점이 왜 이렇게 유용한지 생각해 보기 시작했습니다. DirtyPipe가 수정되면 한순간의 꽃처럼 사라져서 향후 취약점 공격에 아무런 학습 가치도 남기지 못하는 것일까요?
그러던 중 갑자기 DirtyPipe가 사용하는 구조체인 struct pipe_buffer가 매우 익숙하다는 사실을 깨달았습니다. 아! 그것은 바로 GFP_KERNEL_ACCOUNT 플래그로 할당되고 slab-1k에 위치하며, 제가 KASLR 누출과 RIP 하이재킹에 자주 사용하던 ops 필드를 포함하는 바로 그 구조체 아닌가요?
그 순간 저는 제가 바보처럼 느껴졌습니다. 왜 그때 pipe_buffer의 ops를 수정해 ROP를 하려고 했을까요? pipe_buffer의 flags를 직접 수정하고 splice와 함께 사용하면 바로 임의 파일 쓰기가 가능하지 않나요! 그러면 KASLR을 누출할 필요도 없고, 커널 버전별로 익스플로잇을 조정할 필요도 없으며, gadgets에 신경 쓸 필요도 없고, SMEP, SMAP, KPTI 같은 보호를 우회할 필요도 없습니다.
저는 즉시 이전에 재현하며 학습했던 CVE-2021-22555 및 기타 pipe_buffer 구조체와 관련된 익스플로잇을 꺼내 약간만 수정했고, 곧바로 패치되지 않은 여러 커널 버전에서 성공적으로 익스플로잇할 수 있었습니다. 다음은 몇 가지 예시입니다:
그 이전까지 이 방법을 Linux 커널 익스플로잇에서 하나의 프리미티브로 사용하는 사례를 본 적이 없었기 때문에, 제 임의로 이를 “Pipe Primitive”라고 부르겠습니다 XD.
요약하자면, Pipe 구조체를 수정할 수 있는 능력을 확보하면 됩니다. 예를 들어 slab-1k에서의 UAF, 또는 slab-1k에서 오프셋이 있는(즉, 순서대로 이어지는 직접적인 overflow가 아닌) 경계를 넘는 쓰기(out-of-bounds write)가 될 수 있습니다.
이렇게 하면 kernel >= 5.8에서는 pipe_buffer의 splice 페이지에서 flag |= PIPE_BUF_FLAG_CAN_MERGE만 수정하면 됩니다(능력이 된다면 겸사겸사 offset과 len도 0으로 바꿔 파일의 처음부터 쓸 수 있습니다). kernel < 5.8에서는 먼저 pipe_buffer의 anon_pipe_ops를 누출(leak)한 다음, splice 페이지의 ops를 anon_pipe_ops로 변경해야 합니다(< 5.8 버전에서는 merge 가능 여부가 ops에 의해 결정되기 때문입니다). (능력이 된다면 여전히 offset과 len을 0으로 바꿀 수 있습니다).
그리고 이렇게 하면 컨테이너 탈출은 어떻게 하느냐고 묻는다면? 제가 하려는 것은 아마 CVE-2019-5736과 비슷할 것입니다 XD.