반응형

race condition을 활용한 kernel exploit에 대해 이해하기 위해 알아보다가 pm에게 자료를 받았다.

그리하여 본 글을 적게 되었다.

 

★본 글은 아래의 링크에 나오는 발표에 기반하여 정리하였고, 본인 메모용으로 작성된 글입니다.

★아직 미숙하여 설명이 모호하거나, 부족한 부분이 많습니다.

★주인장이 글을 시간날 떄마다 조금씩 써서 매우 자유분방합니다...양해좀해주세요

 

영상:  https://media.ccc.de/v/SHA2017-295-race_for_root_the_analysis_of_the_linux_kernel_race_condition_exploit#t=121

ppt: https://a13xp0p0v.github.io/img/Alexander_Popov-Race_for_Root_SHA2017.pdf

 


CVE-2017-2636 : https://nvd.nist.gov/vuln/detail/CVE-2017-2636

상기의 취약점은 linux kernel 내에서 발생하는 LPE취약점이다.

n_hdlc라는 kernel내의 driver에서 race condition이 트리거되며 모든 배포판에 있어 제공되고 load가능한 module이기에 모든 메이저 배포판들이 영향을 받았다(CONFIG_N_HDLC=m).

 

HDLC(High-Level Data Link Control)  driver는 현재는 device간의 연결을 지원을 위해 주로 사용된다. 해당 드라이버는 data link계층의 프로토콜이며, 각 frame들을 통해 serial line들을 전송한다.

 

본 취약점은 2009.06.22에 처음 소개되었으며 2017.02.01에 발제자의 syzkaller에서 detect되었고, 2017.02.03에 race condition repro가 생성되었다. 최종적으로 2017.02.28에 exploit PoC를 구현하였으며  patch로 적용되었다.

(이후에 나오는 일정은 해당 취약점에 대해 처리되는 일정...)

 

본래 HDLC 드라이버는 selt-made singly linked lists(단일연결리스트) 로 구현되어 data를 저장하기 위한 buffer로 구현되었다. 이 때의 데이터는 line(?)을 의미한다.

debuff 변수를 통해 buffer를 가리키는 pointer를 저장하고, 해당 pointer는 이미 전송된 위치에 대해 재전송(resend)하는 경우에 사용된다고 한다.

이때까지는 괜찮았으나, 이후에 be10eb75893 commit에서 buffer flushing이 추가되었다. 그리고 이로 인하여 race condition이 발생할 여지가 생겼다.

 

buffer를 flushing하는 function과 sending하는 function이 특정 buffer에 대해 free할 list로 두번 지정하게 된다. 이 상황으로 인하여 사용자가 terminal을 닫을 시에 insanely wrong locking(비정상 lock)이 발생하고, n_hdlc_release()에 의해 double-free가 발생된다.

 

이후에 exploit 환경에 대해 나오는데 SMAP는 적용되어 있지 않고, SMEP만 적용된 linux mint 4.4.0-53 generic환경에서 수행되었다.

 

소개된 exploit순서는 아래와 같다.

1. Prepare N_HDLC line discipline

2. Hitting the race condition to get double-free

3. Heap spraying for turning double-free into use-after-free

4. Another heap spraying to exploit use after free

5. Heap stabilization

6. SMEP bypass (without ROP)

 

우선 1번 과정에 대한 diagram이다.

사진 1

 

main logic의 line discipline에서 수행되며, master slave로 각각 생성된 terminal이 있다.

==================== ==================== ====================

( 아마 추측하건데, tty로 전환해서 쓰는 터미널이 master terminal이고, usermode에서 직접 켜서 사용하는 terminal이 salve terminal인 듯하다..? 맞게 이해한건지 모르겠다.)

일단 GPT로 찾아보니, 

'pty master'와 'pty slave'는 일반적으로 이러한 가상 터미널을 만들 때 사용되는 두 가지 연결된 파일 디스크립터를 나타냅니다. 'pty master'는 터미널 세션의 마스터 엔드(master end)를 나타내며, 실제 사용자 입력 및 출력과 연결되어 있습니다. 'pty slave'는 터미널 세션의 슬레이브 엔드(slave end)를 나타내며, 응용 프로그램이 이를 통해 실제 터미널과 상호 작

뭐 이래 말해주긴하는데... 느낌상 내가 이해한 표현이 맞는 듯하다..! 아시는 분 댓글 좀요..

==================== ==================== ====================

line discipline은 kernel code조각이며, backspace를 수행하거나 input에 대해 echo를 하는 로직( 즉 PIO(programmed input output)에 대해 terminal에서 xterm으로 돌아가는 것을 예시로 할 수 있다)이 존재한다.

 

cf. terminal vs xterminal

gnome에서는 기본적으로 xterminal이 깔려있지 않지만, 다른 OS에서는 xterminal이 깔려있는 것을 볼 수 있다.

일반적으로 그냥 terminal은 text 기반 환경에서 작동하며, xterminal은x window와 함께 사용되는emulator를 의미한다.

해당 부분에 대해서는 필자는 사용해 본 적이 없어서 자세한 사항은 따로 조사해봐야겠다.

 

여하튼 이후에 vulnerable driver와 hdlc driver hdlc discipline을 제공하고 있으며, 이를 활용하여 exploit을 수행한다.

 

exploit 구현을 위해 단일 CPU를 사용하여 모든 buffer의 할당이 하나의 할당된 cache를 활용하도록 하였다.

(추측이지만, 일부러 하나의 cache에서 사용하여 racy 환경을 더 쉽게 구축하기 위해서 이렇게 구성한 듯 하다.)

 

이후에 pseudotermninal master/ slave 쌍을 생성한다.

ptmd = open("/dev/ptmx", O_RDWR);

pseudoterminal(이하 ptmd)이란, 가상 터미널(?) 디바이스의 한 형태로써, 프로세스간 통신, 터미널 세션 모의화, 디버깅 등의 역할을 수행한다고 한다. 아마 해당 터미널은 생성된 pty master 와 pty slave라는 두가지 연결된 fd를 사용하여 가상 터미널을 제어하고 데이터를 전송하는데 사용하기 위해 생성하는 것 같다.

 

공부할거 산더미다 ㅎ...

 

이제 ptmd를 위한 line discipline을 세팅한다.

==================== ==================== ====================

그냥 넘어가려 했는데 자꾸 나와서 한번 정리해야겠다.

line discipline은 터미널/시리얼 통신과 관련된 개념이며, 데이터의 흐름 혹은 문자 데이터의 처리를 제어하는 

데에 사용된다.

==================== ==================== ====================

이렇게 세팅하면, vulnerable module(n_hdlc.ko)는 자동으로 생성된다고 한다.

const int ldisc = N_HDLC;
ioctl(ptmd, TIOCSETD, &ldisc);

 

이로써 exploit을 위한 환경 구성이 대략 끝난 것 같다.

 

이제 위에서 언급하였듯 race condition은 sending과 flushing이 이전에 보낸 영역(?)에 대해 수행되는 경우 발생된다. 그리고 이러한 상태를 만들어주기 위하여 terminal에서 현재 출력을 일시 적으로 중단하거나 일시 정지시켰다(일반적으로 ctrl+S로 출력 중단, ctrl + Q로 출력 재시작).

ioctl(ptmd, TCXONC, TCOOFF); //suspend the pty output
write(ptmd, buf, size); //write one data buffer(saved in n_hdlc.tbuf)

전송에 실패한 buffer에 대해 재전송하기 위해 사용되는 pointer(buffer를 가리키는) 를 tbuf에 변수에 저장한다.

 

이제 진짜 끝난거 같다 ㅋㅋ. 그리고 exploit이 수행되는 스레드들을 모든 CPU에서 수행한다.

현재 두 스레드의 상태이다. 하나는 flush하고, 하나는 suspend(일시 출력 중지)되었다.

사진 2

 

첫 스레드는 flush하며 iocontrol(ioctl)을 수행한다.  다른 스레드는 저장된 debuff라는 racy변수를 재전송할 것이다.

 

다음의 상황이다.

사진 3

위의 상황은 lag(지연)을 발생시켜 exploit이 더 빨리 수행되고, race condition을 빨리 구현되게 한 모습이다.

이 때 두 스레드는 pthread_barrier에 의해 동기화가 진행된다.

==================== ==================== ====================

pthread barrier는 여러 thread가 동시에 어떤 지점에서 모이는 것을 기다릴때 사용한다.

thread가 작업을 수행한 이후에 pthread_barrier_wait()를 호출하여 임계지점(barrier)에서 다른 thread를 기다리고,

모든 thread가 임계지점에 도달하면 다시 모든 thread가 동시에 해당 지점에서 실행된다.

즉, thread간의 동기화를 달성하고, 작업 속도를 조절할 수 있다.

==================== ==================== ====================

상기의 사진 기준으로 임계지점은 sync 부분으로 보여진다.

사진처럼 flushing thread가 loop에서 spinning중일때, 즉 대기하면서 지속적으로 CPU시간을 소모할 때, 이미 다른 thread는  vulnerable driver와 통신을 이미 시작한 상태이다. 

그리고, 사진은 error지점에서 둘이 동시에 tbuf 변수(n_hdlc.tbuf)를 사용하게 되며 이 때, race condition이 발생한다.

 

해당 영상에서 발제자가 말하길 race condition은 위와 같은 상황에서 시스템에서 발생되는 상황이며 어떠한 동작의 순서에 의존하고 비결정론적인 상황(결과를 예측하기 어려운 상황)이다.  또한, 위 상황에서 tbuf변수에 대한 결과는 어떻게 thread collision이 발생하는지에 의존한다고 한다.

(어찌보면 당연한 말이다, 충돌이 적절한 시점에 이루어져야 하니까..)

 

본 취약점 구현  중 지연에 대한 부분은 아래와 같다고 한다.

사진 4

그리고 최대 지연시간은 50microseconds라고 한다.

 

이제 double free에 대한 부분이다.

아래의 도식을 따른다.

사진 5

1. 객체 A를 할당한다.

2. 할당한 객체 A를 해제한다. 이 A를 두번 해제하게되어 double free 에러가 발생한다.

3. A와 같은 크기를 가진 객체 B를 할당한다. 이때, B는 A가 해제되었던 주소와 같은 위치에 들어간다. kernel allocator는 

방금 해제된 영역에 대해 우선적으로 할당하려 시도한다. 가장 빠르게 access할 수 있기 때문이다.

4. 하지만 해당 위치는 double free가 발생하는 위치이고, B가 실질적으로 해제된다.

5. 이제 2번째 heap spray를 수행한다. 객체 X를 할당하는데, 이는 payload를 가지고 있으며 역시 사이즈는 B와 같기에 이전에 B가 해제되었던 위치에 들어간다.

6. 마지막으로 code가 B를 사용하려 할때,  X에 담긴 paylaod가 수행된다.

 

정리하면 첫번째heap spray를 통해 double free취약점을 use after free취약점으로 전환하는 것이다. B를 활용함으로써  use after free취약점을 이용하는 것이다.

 

흠... 뭔가 두루뭉슬한데 어렴풋이 감은 온다.

 

이제 다시 우리가 원래 보던 버퍼쪽으로 돌아간다.

n_hdlc_buf는 kmalloc-8192 slab cache로 할당되어 있다. 그리고 해당 cache를 대상으로 double free취약점을 이용하려한다. 이제 우리는 두가지 kernel 객체를 필요로 한다. 첫번째 heap spray에 활용될 function pointer와 이 function pointer를 덮어쓰고 shell code를 수행할 payload가 필요하다.

이때, 사용할 버퍼로는 sk_buff(socket buffer)가 가장 적당하다고 한다. 우리가 exploit에 활용하고, 덮어쓸 수 있 첫번째 function pointer를 가지고 있으며, cache를 할당받을 수 있고, network frame들을 저장할 수 있는 객체로 linux kernel에서 사용된다. sk_buff는 그리고 meta정보를 저장하고 있게된다.

 

하지만 첫번쨰 heap spray과정이 쉬운 과정은 아니다. double free되는 부분에 있어서 n_hdlc_release()는 13개의 n_hdlc_buf 를 바로 한번에 쉬지 않고 해제한다. hdlc driver내의 free list 내에는 13개의buffer들이 존재하고, 모든 buffer는 allocator에 의해 관리 된다. 우리가 double free에 사용할 요소는 free list에 앞부분에 존재하고 원하는 요소를 바로 찾아내서 사용하기에 쉽지 않다. (하긴 어떻게 알고 바로 띡하고 찾아내냐 ㅋㅋ)

 

발제자는 buffer를 구분하기 위해 free되는 요소들에 대해 data를 사이사이에 추가해주려는 것 같았으나 실패했다고 한다.

슬라이드는 요로코롬..

사진 6

실패 이유에 대해서는 single CPU에서는 interrupt과정이 전혀 없으므로 사이사이에 추가할 수 없다고 한다....!

 

결국, 해당 부분은 아마 interrupt가 사이사이에 발생될 때, data를 추가해서 구분지어야하는데, interrupt가 없으므로 추가를 못한다는 말로 정리할 수 있을 것 같다.

 

다시 ppt로 돌아가서, buffer에 대한 free()가 포함된  n_hdlc_release()는 kernel에서 crash를 유발하지 않는다고 한다.

이는 즉 SLUB allocator가 buffer(same address)에 대한 연속적인 free()동작에 있어서 허용을 해준다는 말이다.

(이전 ppt에서 13번 n_hdlc_buf에 대해 해제 하는 내용 상기)

 

그리하여 발제자는 n_hdlc_release이후에 heap spray를 수행해 두면... free list로 부터 buffer를 하나씩 가져올 수 있고, 

최종적으로 아래와 같은 상황이 벌어진다.

==================== ==================== ====================

그전에 잠시 heap spray에 대한 설명을 그냥 하고 넘어가야할 것 같아서 적어둔다.

 

kernel heap spray: kernel heap영역에 원하는 데이터를 spraying하는 기법, 이 기법은 직접적인 영향을 주지는 않지만 몇몇 경우에 따른 exploit의 수행에 있어서 필요할 때가 있다.

 

유저 공간에서는 kmalloc()를 호출할 수 없기에, 다양한 syscall을 이용하여 kmalloc()을 간접적으로 호출

 

kernel heap spray의 조건1. 유저가 원하는 크기의 slab object 할당 가능2. 유저가 원하는 데이터를 할당한 slab object에 복사할 수 있음3. 할당한 slab object에 불필요한 header data가 없아야함4. exploit이 끝날때까지 spraying한 slab obhect가 해제되지 않음

 

-ref-

https://www.inflearn.com/course/%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%BB%A4%EB%84%90-%ED%95%B4%ED%82%B9/ -> ㄹㅇ 킹갓 강의.. 돈이 아깝지 않음 

 

heap spray시 유의 사항: malloc()은 할당할 Heap의 크기가 0x20000(128 * 1024) 이상일 경우 Heap 영역을 확장하지 않고 mmap을 사용하여 새로 할당 된 메모리 영역에 데이터를 저장

 

-heap spray ref-

https://www.youtube.com/watch?v=Ec4UEtO7dPI -> window OS를 기반으로 설명하였지만, 이해하기에 좋다.

https://duasynt.com/blog/linux-kernel-heap-spray

 

물론 본인이 기재해둔 링크들 외에 더 좋은 곳들도 많이 있을 것이다^^(난잡해서 여기에 다 기재안하기도 했고...)

==================== ==================== ====================

 

사진 7

같은 데이터를 가리키고 있는 두개의 socker buffer를 가지고 있는 경우, 둘중에 하나의 buffer를 받게 된 이후에, 남은 buffer를 user-after-free취약점을 위해 사용할 수 있기 때문이다.

 

double-free취약점에서 user-after-free취약점으로 이어지게 하기 위해 8KB의 사이즈를 가진 UDP packet들을 race이후에 생성하였고, kernel memory에 저장하게 하였다.

하지만 이 과정은 network packet들을 위한 socket queue의 사이즈가 제한적이기에 쉽지 않은 과정이라고 한다.

이 제한적 상황으로 인해 overflow가 발생하지 않도록 많은 socket queue들을 만들어서 저장하였다고 한다. 그리고 한 쌍의 socket buffer를 받아 use-after-free취약점을 유발하였다.

 

heap spray의 수행은 아래의 사진과 같다.

사진 8

우선, heap spray를 위한 200개의 server socket들을 생성한 후에 전송한다. 이때,  어떤 패킷이 동일한 데이터를 가리킬 가능성이 있는지 경험적으로 안다고 한다.....(이걸 어케앎;;;) 

암튼 전용 server socket으로 전송하여 use-after-free error가 발생하고, 전용 서버로부터 몇몇 패킷들을 받은 뒤, allocator의 상태를 초기의 위치(?)로 다시 돌려둔다고 한다. 아마 위의 사진처럼 한번에 너무 많은 데이터를 받아 처리할떄, slab allocator가 고갈되어 kernel crush가 발생할 수 있으므로, 이를 방지한다는 얘기인듯 싶다.

 

이로써 첫번째 heap spray과정이 끝났다.

이젠 한쌍의 패킷들을 받아 LPE(Local Privilege Escalation을 위한 overwriting수행한다.

 

heap spraying #2(사진 5 참고)는 socker buffer내의 destructor_arg를 덮어쓰기 위한 과정이다. 하지만, 다른 socket buffer를 대상으로는 할 수 없다고 한다. 

그이유는 우리가 data로 덮어써야할 구조체 이후에 사진 7을 참고하여보면, skb_shared_info 구조체가 바로 나오며, skb_shared_info구조체의 descturctor_arg와 우리가 활용하는 sk_buff구조체의 .head가 둘다 시작으로부터 같은 offset에 위치하므로, 조작하지 않는다고 한다.

 

그니까 결국 offset이 겹쳐서 조작하기 힘드니까 sk_buff로 destructor_arg까지는 overwrite하지 않고, 다른 kernel 객체로 destructor_arg를 overwrite한다는 말이다.

 

이때 활용되는 것이 add_key syscall이다.

add_key syscall을 통해 payload를 커널 메모리에 저장할 수 있고, cache에 할당할 수 있음을 알게 되었다고 한다.

(상기의 heap spray 에 대한 reference 중 inflearn강의에서도 add_key syscall을 사용하시는 것이 나왔던 것으로 기억한다.)

// linux/net/core/skbuff.c in skb_release_data()

if (shinfo->tx_flags & SKBTX_DEV_ZEROCOPY) {
	struct ubuf_info *uarg;
	uarg = shinfo->destructor_arg;
    if (uarg->callback)
	uarg->callback(uarg, true);
}

위의 코드는 데이터 버퍼를 해제할 때 호출되며, 특정 조건이 충족되면 SKBTX_DEV_ZEROCOPY 플래그를 사용하여Zero-Copy 모드로 전환되어 있는 경우 호출된다. shinfo 구조체 에서 destructor_arg field를 통해 uarg 구조체를 얻는다.

이후에 callback이 수행된다.

즉, 네트워크 패킷 전송에 있어서 zero-copy 모드에서 데이터를 해제할때의 작업 수행에 대한 부분이다.

 

고로 두번째 heap spray는 data를 overwrite하고, 상기의 특수한 플래그를 넣어주고, destructor를 user space에 할당된 payload로 덮어 씌우는 과정이다. shell code역시 user space에 존재한다. 하단의 사진을 참고.

사진 9

 

상기의 사진에서 destructor_arg 포인터가 ubuf_info구조체를 역참조할 경우, SMAP에 의해 막힌다.

kernel의 포인터가 user - level에 접근못하게 막는 것이 SMAP이기 때문이다.

==================== ==================== ====================

SMAP: 커널이 유저 레벨 메모리 접근 불가

SMEP: 커널이 유저 레벨 코드 실행 불가

(cr4 register, 뭐 자세한 설명 등등은... 기재 ㄴㄴ.. 이미 너무 길어서;;)

==================== ==================== ====================

발제자의 exploit에서는 SMEP에 대한 bypass만을 다룬다.

우선 bypass이 수월하지 않다고 한다. key data의 할당은 root에 의해 조작되며 우리에게 주어진 payload의 size는 20000byte이기 때문이다.

 

root에 의해 조작해야하는데 어떻게 할까...? 싶다가 내용이 추후에 나오게 된다.

 

이 20000byte는 key syscall 수행을 고작 두번밖에 못하기에 spraying하기에는 턱없이 부족한 상태이다. 여기서 왜 두번 밖에 수행 못하냐면, 8KB 페이로드를 동시에 저장해야하기 때문이라고 한다.

(8KB에 대한 내용은 아마 이전에 다른 CVE분석에 언급되어 있을텐데.. 아마 우선 페이지가 주로 4KB이나 8KB이고, OS내에서 편의적으로 고려되는 사이즈이기에 그랬던것 같다.)

 

다음 슬라이드에서 좀 신기한 이야기를 한다.

heap spray의 성공은 add_key syscall의 호출여부에 의존하지 않는다고 한다. 그리하여 현재 root권한이 아닌 발제자가 add_key syscall을 수행하는 것이 실패하겠지만, 실질적으로는 payload가 kernel memory에 존재하며, socket buffer를 덮어쓸 수 있었다. 고로 호출을 실패해도 그냥 둔다.

 

이제 최종적인 spraying은 아래와 같다.

server socket을 대상으로 뿌릴 20개의 패킷을 만들고, 경험적으로 미루어 보았을때, 12, 13, 14, 15번째 패킷들은 각각 같은 데이터를 가리키는 상태가 될 것이라고 하였다.

12~15의 4개의 패킷들을 각각 하나씩 받아가서 overwrite을 위해 add_key syscall을 수행하게 한다. 최종적으로 recv() (==free()) 이후에 다시 초기 상태의 allocator를 저장하기 위해 추가적으로 15개의 패킷을 다른 서버로 보낸다. 이 수행은 exploit이 안정적으로 수행되게 도와주며, 방금의 15개의 패킷을 보내는 과정이 없을 시 krenel crach가 발생할 수 있다. 이유는 slab이 완전히 free되었을때, double free를 감지하기 때문에 이를 방지하고자 보내는 것이다. 이러한 기법을 "Slab exhaustion" 이라고 부른다.

==================== ==================== ====================

Slab exhaustion에 대해 좀 더 안내를 하고 넘어가면, 

정확히는 "Slab이나 다른 메모리 할당 기법에서 할당된 메모리가 과도하게 소비되어 더 이상 사용 가능한 메모리가 없는 상태" 를 가리킨다. 그리고, 이러한 상황이 발생 시, Slab allocator는 새로운 page를 메모리에 할당해준다. 그렇기에 Slab Exhaustion은 새로운 페이지에 할당되는 구조 즉, 메모리 할당이 서로 다른 페이지에서 이루어지게 되어 메모리가 인접하지 않는 구조를 유지, 를 통해, 메모리가 완전히 해제된 후에 해당 메모리를 다시 할당하는 시도를 막는다. 이는 곳, double free의 발생을 막아주는 것이다.

 

한마디로, 안전한 exploit 수행을 위해 메모리 할당을 통제하고, 메모리가 완전히 해제되면서도 안정적인 상태를 유지하는 데에 사용된다는 것이다.

 

추가적으로 더 알아보고 싶은 부분은 아래의 논문을 보면 될 듯하다. 필자는 일부분만을 보았으며 추후에 한번 읽어볼 듯 싶다. heap layout에 대한 내용이 많이 있는 듯하다.

https://openreview.net/pdf?id=UqszJh5h6v

==================== ==================== ====================

 

이제 key syscall에 대한 예시를 보여준다.

해당 부분은 우리의 payload가 kernel space에 저장되기 위한 공간이며 invalidation을 위한 두개의 add_key syscall만이 

성공적으로 수행된다.

 

최종적으로 정리하면 kernel이 user space에서의 명령어 수행을 실행할 때, 우리는 fault를 확인할 수 있다.

이후에 아래와 같이 SMEP에 대해 설명하는데

 

사실 SMEP와 SMAP는 각각 CR4 register의 20번과 21번 bit가 0이냐 1이냐로 on/off가 결정된다

그리고 여기서는 SMEP에 대한 bypass만을 설명하므로, 20번 비트를 0으로 만들면 꺼진다는 얘기만으로 간략하게 설명한다.

 

그리고 SMEP의 BYpass technique이 있다는 것을 알려주는데 많이 사용되는 기법이라고 한다. 필자도 overwriting한다는 개념만 알고 있었기에 추후에 한번 살펴볼 듯 싶다.

여하튼 이 두가지 기법은 AAW가 가능해야 한다는 제한적인 조건이다.

그리하여 발제자를 더 간단하게 SMEP를 bypassing하기 위한 더 쉬운 기법을 소개한다.

우선 커널 내에는 아래와 같이 이미 우리에게 필요한 함수가 존재한다.

해당 함수는 cr4 register를 0으로 설정할 수 있는 함수이다. 느낌상... 이를 이용하여 set상태의 register를 callback을 통한 동기화 때 호출하여 unset해줄 듯 하다.

 

이제 위의 native_write_cr4()가 어떻게 쓰이는지 알아본다.

(껄껄 역시나인가..?)

상기의 함수의 동작은 buf구조체의 주소를  signed long형태로 첫 인자로써 call한다.

그러므로 우리가 만약 native_write_cr4()를 ubuf_info.callback으로써 활용하는 경우, 즉 ubuf_info의 주소에  mmapped syscall로부터 받아온 특정 addr인 0x406e0를 삽인 하는 경우에 bypass가 가능하다.

uarg -> callback(uarg, true) == native_write_cr4(0x406e0)

 

발제자의 machine에서는 cr4레지스터의 SMEP에 해당하는 21번을 unset하는 데에 사용할 native_write_cr4()의 주소가 0x406e0이므로 해당 주소를 사용한 것이고, 결론적으로 ROP를 사용하지 않고도 SMEP를 bypass하게 된 것이다.

주솟값은 CPU에 의한  userspace와 x86의 CPU OID instruction를 통해 결정된다.

 

이로써 kernel에서의 exploit을 위한 환경은 완료되었고, SMEP가 disabled된 이후엔  shell code를 실행할 수 있는 두번째 race를 수행한다. 

 

이후부턴 patch에 대한 내용으로 이어진다.

본래에 vulnerable한 buffer는 자체적으로 개발한 구조체가 아닌 standard란 linked list를 사용하고 있었고, 발제자는 race condition을 유발하는 n_hdlc.tbuf를 제거하도록 하였다. 그리고 전송에 실패하는 경우, tx_buf_list를 현재에 있는 data buffer앞에 위치하게 하여 추후에 전송하도록 만들었다고 한다.

 

그리하여 현재의 n_hdlc구조체가 아래와 같은 형태를 가지게 되는 것 같고, 주석을 참고하여보면
보류 중인 전송 프레임 buffer list가 구조상 앞쪽에 있다.

/**
 * struct n_hdlc - per device instance data structure
 * @tbusy: reentrancy flag for tx wakeup code
 * @woke_up: tx wakeup needs to be run again as it was called while @tbusy
 * @tx_buf_list: list of pending transmit frame buffers
 * @rx_buf_list: list of received frame buffers
 * @tx_free_buf_list: list unused transmit frame buffers
 * @rx_free_buf_list: list unused received frame buffers
 */
struct n_hdlc {
	bool			tbusy;
	bool			woke_up;
	struct n_hdlc_buf_list	tx_buf_list;
	struct n_hdlc_buf_list	rx_buf_list;
	struct n_hdlc_buf_list	tx_free_buf_list;
	struct n_hdlc_buf_list	rx_free_buf_list;
	struct work_struct	write_work;
	struct tty_struct	*tty_for_write_work;
};

 

이젠 SLUB에 대한 patch이다.

이전에 잠시 언급되었듯 glibc에 의한 'double free or corruption(fasttop)' security check을 피하기위해 같은 객체에 대해 진행되는 두 free 사이에 다른 청크가 free된다.

a = malloc(10);     // 0xa04010
b = malloc(10);     // 0xa04030
c = malloc(10);     // 0xa04050

free(a);
free(b);  // To bypass "double free or corruption (fasttop)" check
free(a);  // Double Free

d = malloc(10);     // 0xa04010
e = malloc(10);     // 0xa04030
f = malloc(10);     // 0xa04010   - Same as 'd'

위의 예시는 fastbin으로 관리되는 경우에만 해당된다.

-ref-

https://oiehso0.tistory.com/entry/09Double-Free

이러한 우회를 막기 위해 다른 두 객체를 해제한 뒤, 주소를 검사하여 이전의 주소와 같지 않은지 검사한다. 위처럼 같은 주소를 가리키는 상황을 막기 위해서 보호기법을 적용한 것이다.

==================== ==================== ====================

이로써 발표는 마치게 되고 나머지 내용은 Q&A 부분이랑 그외에 이것저것인데 간단히만 정리하였다.

==================== ==================== ====================

Q&A(특히나 번역도 빡세고, 여기서 나온 키워드들을 기반으로 더 searching이 이루어져야겠다.)

Q: 해당 컨퍼런스가 발표되는 시점에서 좀전에 언급된 보호 기법이 mainline에 적용되지 않아있는데 이유는?

A: 이미 slub debug의 특징에는 비슷한 보호기법이 적용되어 있다고 한다. 그렇기에 중복되는 보호기법을 적용하고 싶지 않아하는 상황이지만, default kernel은 이러한 보호기법이 걸려있지 않다고 한다. 그렇기에 allocator는 double free에 대해 용한다.

또한, 다른 솔루션으로 제시된 것은 SLAB_FREELIST_HARDENED라는 kernel config option이다. 이 옵션은 freelist내부의 item들의 주소를 랜덤하게 바꿔주는 옵션이다. 이는 공격자가 heapoverflow exploit을 통해 kernel내의 포인터를 덮어서 다음 freelist의 포인터를 조작하려는 것을 어렵게 만든다. 공격자는 이 때,  고유한 장소에 저장되어 있는 쿠키를 통하여 유추해 내야하며, 발제자가 제시한 보호기법은 이 옵션에 포함될 것이라고 한다.

(글을 작성하는 현시점에는 slub allocator가 채택되어 default allocator로 사용되고 있다.)

 

다음질문은 architecture 자체에 대한 내용인데, 말이 상당히 빨랐어서 간단히만 얘기하면 user space와 kernel space가 mapping되어 있지 않아도 본 exploit이 수행될 수 있는지 여부를 묻는 것 같다. 답변으로는 발제자의 exploit은 userspace단에서 동작하는 프로그램이며 몇몇 systemcall을 수행하기에 kernel은 현재 실행되는 프로세스에 대한 정보를 알아야한다고는 하는데....

아직 더 알아봐야하는 부분인 듯하다.

 

나머지 질문들은 약간 여담의 느낌이라 한번 스윽 들어보는 정도여도 괜찮을 듯 싶다.

위양성에 대한 얘기도 나오는 것 같고...thread sanitizer에 대해서도 언급하고.. 그외 등등...

 

그리고 중간에 segmentation이라는 말이 있을텐데, 이는 메모리 접근 방식에서의 segmant 방식을 의미할 것이다.

 

 

회사다니면 거진 2주간 짬짬 열심히 작성하였고 많은 공부가 된 컨퍼런스였다.

 

띄엄띄엄 써서 글이 자유분방하지만... 여기서 얻은 키워드들을 바탕으로 더 공부해 나아가야겠다는 생각이 든다

 

-ref-

https://github.com/torvalds/linux/blob/master/drivers/tty/n_hdlc.c

https://jeongzero.oopy.io/132fed8f-5cfd-4f43-990c-61584744b4d0

그외 구선생님...♥

 

 

 

추후에 볼만한 링크도 함께 달아둬야지...(사실 내가 볼 용도 ㅎㅎ;)

OffensiveCon23 의 race condition exploit 파트이다.

https://www.youtube.com/watch?v=9wgHENj_YNk

반응형

'DEBUG Project(one-day analysis)' 카테고리의 다른 글

CVE-2021-34866  (0) 2023.06.02
CVE-2022-1103  (0) 2023.05.31
CVE-2021-3490  (0) 2022.12.15
정보보안 공부용 자료들 링크  (1) 2022.11.01
Integer overflow and heap overflow that occur sequentially in the BPF module  (0) 2022.10.21

+ Recent posts