import os import struct import sys # This script analyses the memory dumps produced by qemu_runner.py, which # runs privesc.cc in a QEMU VM. It estimates what proportion of bit flips # in page table entries (PTEs) would be exploitable. page_size = 0x1000 # Returns the index of the highest set bit, plus 1. def HighestBit(val): assert val >= 0 bit = 0 while val != 0: bit += 1 val >>= 1 return bit def Main(): filename = 'out/memory_dump' fh = open(filename) pfn_count = os.stat(filename).st_size / page_size print 'Memory dump size: %i MB' % (os.stat(filename).st_size >> 20) print 'PFN count:', pfn_count def ReadBytes(offset, size): fh.seek(offset) return fh.read(size) def GetPtes(pfn): return struct.unpack('512Q', ReadBytes(pfn * page_size, page_size)) # Locate the data pages via the markers they contain. Look for the # markers written by privesc.cc. data_pages_by_pfn = {} data_pages_count = 0 marker = struct.pack('Q', 0x43215678) for pfn in xrange(pfn_count): if ReadBytes(pfn * page_size, 8) == marker: val = struct.unpack('Q', ReadBytes(pfn * page_size + 8, 8))[0] data_pages_by_pfn[pfn] = val data_pages_count = max(data_pages_count, val + 1) print 'Found data pages:', data_pages_count assert len(data_pages_by_pfn) == data_pages_count # Locate the page tables via their characteristic contents. expected_pte = dict((idx, (1 << 63) | (pfn << 12)) for pfn, idx in data_pages_by_pfn.iteritems()) mask = ((1 << 64) - 1) & ~0xfff target_pts = {} for pfn in xrange(pfn_count): vals = GetPtes(pfn) # Since we're using fast mode, only check the first entry. for i in xrange(data_pages_count / 512): if vals[0] & mask == expected_pte[i * 512]: target_pts[pfn] = i print '\nDiagram of page types in physical memory:' for pfn in xrange(pfn_count): ch = '.' if pfn in data_pages_by_pfn: ch = 'D' elif pfn in target_pts: ch = str(target_pts[pfn]) sys.stdout.write(ch) if (pfn + 1) % 64 == 0: sys.stdout.write(' %i MB\n' % (pfn >> (20 - 12))) # Count how many bit flips cause PTEs to point to page tables. print '\nBit flip analysis:' total_hits_pts = 0 total_hits_data = 0 for bit in xrange(HighestBit(pfn_count)): hits_pts = 0 hits_data = 0 for data_pfn in data_pages_by_pfn.iterkeys(): new_pfn = data_pfn ^ (1 << bit) if new_pfn in target_pts: hits_pts += 1 total_hits_pts += 1 elif new_pfn in data_pages_by_pfn: hits_data += 1 total_hits_data += 1 print 'bit %2i: %3i PT hits (%4.1f%%) (and %3i data page hits) out of %i' % ( bit, hits_pts, float(hits_pts) / data_pages_count * 100, hits_data, data_pages_count) print 'total hits for page tables (exploitable): %i' % total_hits_pts print 'total hits for data pages (unexploitable): %i' % total_hits_data print 'page table pages: %i' % len(target_pts) Main()