ETC

CVE-2021-1732 LPE vulnerability analysis

wsoh9812 2022. 4. 8. 11:21

분석 환경

             Windows10 1909 x64

설명

CVE-2021-1732 취약점은

 win32kfull!xxxCreateWindowEx xxxClientAllocWindowClassExtraBytes 콜백으로 인해tagWND.WndExtra 해당 플래그의 설정이 동기화되지 않아 커널 내의 임의의 메모리 값에 읽기 쓰기가 가능하여 LPE 가능하다.

 xxxCreateWindowEx WndExtra 영역이 있는 창을 만들때 win32kfull!xxxClientAllocWindowClassExtraBytes 호출하여 콜백을 트리거할 것이고, 콜백은 WndExtra 영역을 할당하기 위해 사용자모드로 돌아간다. 이때 후킹된 콜백함수에서 NtUserConsoleControl() 인자로 현재 창의 핸들을 넣어주면 tagWndExtra 오프셋으로 변경되고 오프셋임을 나타내는 플래그가 설정이된다. NtCallbackReturn 임의의 값을 반환해 준다

이때 콜백이 종료되고 커널 모드로 돌아가면 반환 값이 tagWND.WndExtra 덮어쓰지만 해당 플래그는 지워지지 않는다. 확인되지 않은 오프셋 값은 메모리 주소 지정을 위해 커널 코드에서 직접 사용되어 경계를 벗어난 액세스를 유발한다.

 

 

기본 개념

Win32k

   Graphical (GUI) component of the Microsoft Windows Subsystem

tagWND

   - tagWND is a kernel structure which represents a WINDOW object in kernel memory

   - Windows에서 CreateWindowExW() API 통해 창을 생성하게 되면, 커널 내부에서 tagWND 구조체로 객체가 생성된다.

   –  Win10 부터 Windbg에서 확인이 불가능하다.

   –  Win10에서 win32k tagWND 구조체가 많이 바뀌었지만, 여전히 Win7에서의 tagWND 구조체를 많이 참고하고 있다.

   –  tagWND 주소를 leak하는 방법 가장 알려진 방법은 HMValidateHandle() 이용하는 것이다.

HMValidateHandle() API

  – This function allows to map the tagWND object in the user mode memory space

Desktop heap

  - 윈도우 데스크탑의 , 메뉴, 아이콘 등의 개체를 특수한 메모리 힙을 사용하여 관리

 NtCallbackReturn() API

   –  쉽게 말해 불려진 콜백 함수에서 return 해줄때 사용을 한다.

   –  내부적으로 여러개의 callback함수가 불려질 것이고, 이를 그냥 return으로 값을 보내면 운영체제?에서 콜백함수가 동작이 끝났는지 모른다. 그래서 NtCallbackReturn() 통해 return해주면 어떤 함수가 끝났는지 있다.

 

 

상세 분석

CreateWindowEx 사용하여 창을 만들때 cbWndExtra 필드를 통해 사용자가 원하는 만큼의 메모리 공간을 tagWND 개체 바로 뒤에 할당할 있다.

, cbWndExtra 0 아닌 경우 win32kfull!xxxCreateWindowEx 내부적으로 커널 콜백 메커니즘을 통해 user32!__xxxClientAllocWindowClassExtraBytes 호출한다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

이전에 cbWndExtra 32 넣어 주었고, 내부적으로 0x20만큼 힙을 할당해 주는 것을 있다.

 

 

사진은 참고

 

 

여기까지 일반적으로 윈도우 창을 만들때의 흐름이다.

 

 

우리는 xxxClientAllocWindowClassExtraBytes 후킹해서 NtUserConsoleControl() 실행시켜야 한다.

 

xxxClientAllocWindowClassExtraBytes 콜백 함수로 KernelCallbackTable 저장이 되어있다. 그래서 사진과 같이 PEB -> KernelCallbackTable -> xxxClientAllocWindowClassExtraBytes 구한다.

우리는 xxxClientAllocWindowClassExtraBytes 후킹해서 NtUserConsoleControl() 실행시켜야 한다.

 

 

xxxClientAllocWindowClassExtraBytes 콜백 함수로 KernelCallbackTable 저장이 되어있다. 그래서 사진과 같이 PEB -> KernelCallbackTable -> xxxClientAllocWindowClassExtraBytes 구한다.

 

 

 

먼저, 정상적인 윈도우 창을 10 정도를 만들어준다.

 

이때

우리에게 실질적으로 필요한건 윈도우 창들 BaseAddress 가장 작은 것부터 2개이다.

그래서 과정을 통해 최소 BaseAddress 구해준다.

 

 

 

이렇게 메모리에서 가장 낮은 주소를 찾았고, 윈도우 창을 tagWND0, tageWND1이라고 하겠다.

추가로 두개의 창은 서로 가까운 메모리에 생성이 것이다.

 

 

이렇게 윈도우 10개를 만드는 과정 중간에 창의 Handle 가지고 HMValidateHandle() 가지고 tagWND 구해서 배열에 저장을 한다.

이를 통해 우리는 Handle 1:1 매칭되는 tagWND 가지고 있다.

 

 

[중요]

추가로 tagWND 커널 영역에 있는 구조체 인데 HMValidateHandle() 하면 커널 주소가 반환이 되느냐? 정답은 아니다. 유저영역의 tagWND 주소가 반환이 된다.

, 유저영역의 주소에 offset 더해 특정 멤버의 값을 들고 오는 거나 커널 영역의 주소에서 offset으로 멤버의 값을 들고 오나 같은 결과이다.

 

위에서 윈도우 10개를 만들기 전에 ClientAllocWindowClassExtraBytes 후킹 했었다.

말은 10개의 창을 만들면서 후킹 함수가 계속해서 호출 되었을 것이다.

 

 

다음 코드를 살펴보기 전에 창을 만들면서 실행되었을 후킹 함수를 살펴보자.

GuessHwnd() 통해 CreateWindowEx 호출 했을 반환되는 Handle 구해서

 NtUserConsoleContorl() 전달한다. 그러면 2가지 일이 발생한다.

1.          해당 창은 콘솔 창로 인식하기 위해 tagWND+0xE8(ExtraFlag) 플래그가 0x800 바뀐다.

2.          tagWND.ExtraBytes 주소로 인식하는 것이 아닌 offset으로 인식한다.

               (원래 tagWND.ExtraBytes에는 DesktopAlloc으로 할당한 주소가 들어간다.)

 이후 NtCallbackReturn 해줌으로써 사용자가 원하는 offset tagWND.ExtraBytes 넣어 있다.

 -> 요약하자면, 콜백이 완료되고 커널 모드로 돌아가면 반환 값이 오프셋 멤버를 덮어 씌어지고 해당 플래그는 지워지지 않습니다.

 

Windbg 확인을 해보면 [rax+0x128] 사용자가 원하는 rax값을 넣어 준다.

 

이렇게 return되는 offset 이용하면 tagWND0에서 tagWND1까지 침범하는 OOB 발생할 있다.

 

 

현재까지의 과정을 살펴보면 아래 그림과 같다.

 

계속해서 다음 코드를 살펴보기 전에 알아두어야할 개념이 있다.

윈도우 창을 생성해주면 tagWND영역도 있고, 윈도우 전체를 포함?하고 있는 영역이 있다.

파란색 처럼 말이다. 그래서 전체 tagWND base 존재하고, tagWND Base 존재한다.

 

 

이제 코드를 보자.

arrayEntryDesktop tagWND 주소가 들어 있다. tagWND+0x8 전체 tagWND영역의 base로부터의 offset이다. , 그림에서 파란색 영역으로부터의 offset이다.

 

위에서 "유저영역의 주소에서 offset 값과 커널영역에서 offset 값이 같다."라고 한번 언급했었다.

 

사진은 Windbg에서 offset_from_base 구하는 과정인데, 유저영역 주소에서 offset 결과와 커널 영역에서 offset 결과가 동일한 것을 확인할 있다.

 

 

처음에 10개의 윈도우 창을 생성해서 각종 정보를 구한 다음 3-10번째 창을 파괴시킨다.

실질적으로 우리는 0번과 1 창을 사용하는데, 3-10번까지 만들었는지 모르겠다.

 

취약점은 결국 tagWND0으로 offset 이용해서 tagWND1 OOB 가능해 진다는 것이다.

그래서 tagWND0 Offset으로 바꾸어 주는 작업이다.

 

 

이제 우리는 WND_Malicious 만들어서 WND0 조작해 것이다.

 

후킹 함수에서 if 조건을 보면 WND_Malicious 작업을 해주는 것을 있다.

NtCallbackReturn() 통해 return되는 값을 WND0 offset으로 줌으로써

WND_Malicious.ExtraBytes WND0 offset 들어갈 것이고, WND_Malicious 통해 WND0 컨트롤 있게 된다.

 

Malicious Window 만든 SetWindowLongW() 사용해 WND_Malicious 핸들을 가지고 0x128 값을 넣어주는 것을 있다.

WND_Malicious 후킹함수를 거쳐서 WND0 가리키고 있는 것을 잊지마라.

, SetWindowLong() WND_Malicious 핸들을 넣고 0x128 떨어진 곳에 값을 넣어준다는 것은 WND0+0x128 값을 써주는 것으로 해석할 있다.

 

 

작업은 커널 메모리의 tagWND 구조 ExtraBytes 넘어 WND1 내의 필드에 있게 하기 위해서이다.

 

 

 

이제 커널의 주소를 leak하기 위해서 하는 작업들이다.

 

윈도우 창을 묘사하는 커널 데이터 구조인 "spmenu" 이용하여 커널 주소를 leak 것이다.

 

먼저, spmenu 대해 살펴보자.

 

The function NtUserSetWindowLongPtr replaces the target window’s spmenu field with the function's argument without any checks when using GWLP_ID and the target window's style is WS_CHILD.

 

 

설명과 같이 WS_CHILD GWLP_ID 통해 spmenu 필드에 담긴 커널 주소를 leak 있다.

 

참고로 SetWindowLongW() 반환 값은 해당 위치의 이전 데이터를 반환한다. (=커널 주소)

 

과정을 통해 담긴 커널 주소를 얻을 있다.

 

 

디버거로 확인해 보면

SetWindowLongPtrA()으로 tagWDN+0x98떨어진 곳에 fake addr 바뀌어진 것을 있다.

하지만! leak되는 주소는 커널이고, 이전의 값이 커널 주소여야하는게 정상이다...

이전에 값을 살펴보면 0x48f40이다.. 커널 주소여야하는데,,,

 

 

사진은 win32kfull!xxxsetwindowlong()함수의 일부분이다.

Index -12일때 if문으로 style 조건을 확인한 동작을 보자.

0x98만큼 떨어진 곳에 fake_addr 들어가는 것은 이미 확인 하였다.(바로 그림)

v14 = tagWND_base+0x15에서 커널 주소를 저장해 두고 이를 return한다.

return 14; 하기 때문에 tag_base+0x15 값을 leak 있다.

 

 

이후 leak addr EPROCCESS까지 구하는 과정이다.

 

System 토큰을 구해서 write해주면 LPE 가능하다.

 

 

 

 

 

참고자료

             https://www.mcafee.com/blogs/enterprise/mcafee-enterprise-atr/technical-analysis-of-cve-2021-1732/

             https://developpaper.com/cve-2021-1732-lpe-vulnerability-analysis/

             https://www.coresecurity.com/core-labs/articles/analysis-cve-2022-21882-win32k-window-object-type-confusion-exploit

             https://www.real-sec.com/2022/01/technical-analysis-of-cve-2021-1732/

             https://getdigitaltech.com/technical-evaluation-of-cve-2021-1732-mcafee-blogs/

             https://venzux.com/technical-evaluation-of-cve-2021-1732-mcafee-weblog/

             https://www.blackhat.com/docs/eu-16/materials/eu-16-Liang-Attacking-Windows-By-Windows.pdf

             https://bromiumlabs.wordpress.com/2016/11/08/thoughts-on-the-recent-ntsetwindowlongptr-vulnerability/

             https://bromiumlabs.wordpress.com/2016/11/08/thoughts-on-the-recent-ntsetwindowlongptr-vulnerability/

             https://googleprojectzero.github.io/0days-in-the-wild/0day-RCAs/2021/CVE-2021-1732.html

             https://www.wanganke.com/web/article/show/4601

             https://ti.dbappsecurity.com.cn/blog/articles/2021/02/10/windows-kernel-zero-day-exploit-is-used-by-bitter-apt-in-targeted-attack/