LK03(Dexter)에서는 Double Fetch라고 불리는 취약점에 대해 배웁니다. 먼저 연습 문제 LK03 파일을 다운로드해 주세요.
QEMU 실행 옵션
LK03에서는 SMEP, KASLR, KPTI가 활성화되어 있고, SMAP가 비활성화되어 있습니다. 또한, 이번에 다루는 취약점은 경쟁(race)에 관한 버그이므로, 멀티 코어로 동작시키고 있다는 점에 주의하세요.[1]
권한 상승을 간단하게 하기 위해 SMAP를 무효화하고 있을 뿐, 취약점 자체는 SMAP가 활성화되어 있어도 발현됩니다.
사용자로부터 데이터를 복사하기 전에 verify_request로 크기를 확인하고 있기 때문에, Heap Buffer Overflow는 언뜻 보기에 존재하지 않는 것처럼 보입니다.
Double Fetch
Double Fetch는 커널 공간에서 발생하는 데이터 경쟁의 일종에 붙여진 이름입니다. 이름 그대로, 커널 측에서 같은 데이터를 2번 fetch(읽기) 함으로써 발생하는 경쟁을 가리킵니다.
다음과 같이, 커널 공간이 사용자 공간에서 같은 데이터를 2번 읽을 때, 그 사이에 다른 스레드가 데이터를 다시 쓸 가능성이 있습니다.
이때 1번째와 2번째 fetch에서 데이터 내용이 다르기 때문에, 정합성을 취할 수 없게 됩니다. 이러한 데이터 경쟁을 Double Fetch라고 부릅니다. LK01에서 다룬 경쟁과 크게 다른 점은, 이 버그는 커널 측에서 mutex를 잡아도 대처할 수 없다는 점입니다.
이번 드라이버에서는 verify_request와 copy_data_to_user/copy_data_from_user에서 사용자로부터의 요청 데이터를 fetch 하고 있습니다. 즉, verify_request에서는 올바른 크기를 전달하고, 거기서부터 copy_data_to_user 혹은 copy_data_from_user가 실행될 때까지의 사이에 크기를 잘못된 값으로 다시 쓰면, Heap Buffer Oveflow를 일으킬 수 있습니다.
사용자 공간의 데이터를 여러 번 다룰 때는, 처음에 커널 공간에 복사한 것을 사용해야 해.
취약점의 발현
먼저 올바른 사용법을 사용해 봅시다. 다음과 같이 드라이버에 데이터를 저장할 수 있습니다.
for (int i = 0; i < 0x100; i += 8) { printf("%02x: 0x%016lx\n", i, *(unsignedlong*)&buf[i]); }
close(fd); return0; }
메인 스레드에서 CMD_GET을 올바른 크기로 호출하고, 서브 스레드에서 사용자 공간에 있는 크기 정보를 잘못된 값으로 다시 씁니다. verify_request가 호출되고 나서 copy_data_to_user가 호출될 때까지의 사이에 서브 스레드가 크기 정보를 다시 쓰면, 잘못된 크기로 데이터가 복사되기 때문에, Heap Buffer Overflow가 일어납니다.
CMD_GET에 관해서는 실제로 버퍼 사이즈를 넘어서 데이터가 읽혔는지를 확인하면 되지만, CMD_SET에서 버퍼 오버플로우가 성공했는지는 어떻게 확인하면 좋을까요? 방법은 몇 가지 있겠지만, 이번에는 상수회 루프로 범위 밖 쓰기(오버플로우)를 시도하고, 종료 후에 오버플로우가 성공했는지를 범위 밖 읽기로 확인하기로 했습니다.
seq_operations는 sysfs, debugfs, procfs 등의 특수 파일을 사용자 공간에서 읽을 때 커널 측에서 호출되는 핸들러를 기술하는 구조체입니다. 따라서 /proc/self/stat 등의 특수 파일을 여는 것으로 확보할 수 있습니다.
함수 포인터이므로 커널 주소를 유출할 수 있고, 예를 들어 read를 호출하면 seq_operations의 start가 호출되므로, RIP의 제어도 가능합니다.
kmalloc-32가 사용되는 구조체는 이외에도 많이 있어.
자세한 것은 예제에서 보자.
권한 상승
이번에는 SMAP가 비활성화이므로 사용자 공간에 Stack Pivot 할 수 있습니다. 각자 ROP chain을 짜서 권한 상승을 시도해 보세요.