/// <summary> /// allocate size bytes starting at a particular address /// </summary> public MemoryBlock(ulong start, ulong size) { if (OSTailoredCode.CurrentOS != OSTailoredCode.DistinctOS.Windows) { throw new InvalidOperationException("MemoryBlock ctor called on Unix"); } if (!WaterboxUtils.Aligned(start)) { throw new ArgumentOutOfRangeException(); } if (size == 0) { throw new ArgumentOutOfRangeException(); } size = WaterboxUtils.AlignUp(size); _handle = Kernel32.CreateFileMapping(Kernel32.INVALID_HANDLE_VALUE, IntPtr.Zero, Kernel32.FileMapProtection.PageExecuteReadWrite | Kernel32.FileMapProtection.SectionCommit, (uint)(size >> 32), (uint)size, null); if (_handle == IntPtr.Zero) { throw new InvalidOperationException($"{nameof(Kernel32.CreateFileMapping)}() returned NULL"); } Start = start; End = start + size; Size = size; _pageData = new Protection[GetPage(End - 1) + 1]; }
/// <summary> /// take a snapshot of the entire memory block's contents, for use in GetXorStream /// </summary> public override void SaveXorSnapshot() { if (_snapshot != null) { throw new InvalidOperationException("Snapshot already taken"); } if (!Active) { throw new InvalidOperationException("Not active"); } // temporarily switch the entire block to `R`: in case some areas are unreadable, we don't want // that to complicate things if (mprotect(Z.US(Start), Z.UU(Size), MemoryProtection.Read) != 0) { throw new InvalidOperationException("mprotect() returned -1!"); } _snapshot = new byte[Size]; var ds = new MemoryStream(_snapshot, true); var ss = GetStream(Start, Size, false); ss.CopyTo(ds); XorHash = WaterboxUtils.Hash(_snapshot); ProtectAll(); }
/// <summary> /// set r/w/x protection on a portion of memory. rounded to encompassing pages /// </summary> public void Protect(ulong start, ulong length, Protection prot) { if (length == 0) { return; } int pstart = GetPage(start); int pend = GetPage(start + length - 1); var p = GetKernelMemoryProtectionValue(prot); for (int i = pstart; i <= pend; i++) { _pageData[i] = prot; // also store the value for later use } if (Active) // it's legal to Protect() if we're not active; the information is just saved for the next activation { var computedStart = WaterboxUtils.AlignDown(start); var computedEnd = WaterboxUtils.AlignUp(start + length); var computedLength = computedEnd - computedStart; Kernel32.MemoryProtection old; if (!Kernel32.VirtualProtect(Z.UU(computedStart), Z.UU(computedLength), p, out old)) { throw new InvalidOperationException($"{nameof(Kernel32.VirtualProtect)}() returned FALSE!"); } } }
/// <summary> /// take a snapshot of the entire memory block's contents, for use in GetXorStream /// </summary> public void SaveXorSnapshot() { if (_snapshot != null) { throw new InvalidOperationException("Snapshot already taken"); } if (!Active) { throw new InvalidOperationException("Not active"); } // temporarily switch the entire block to `R`: in case some areas are unreadable, we don't want // that to complicate things Kernel32.MemoryProtection old; if (!Kernel32.VirtualProtect(Z.UU(Start), Z.UU(Size), Kernel32.MemoryProtection.READONLY, out old)) { throw new InvalidOperationException($"{nameof(Kernel32.VirtualProtect)}() returned FALSE!"); } _snapshot = new byte[Size]; var ds = new MemoryStream(_snapshot, true); var ss = GetStream(Start, Size, false); ss.CopyTo(ds); XorHash = WaterboxUtils.Hash(_snapshot); ProtectAll(); }
/// <summary> /// set r/w/x protection on a portion of memory. rounded to encompassing pages /// </summary> public override void Protect(ulong start, ulong length, Protection prot) { if (length == 0) { return; } int pstart = GetPage(start); int pend = GetPage(start + length - 1); var p = GetMemoryProtectionValue(prot); for (int i = pstart; i <= pend; i++) { _pageData[i] = prot; // also store the value for later use } if (Active) // it's legal to Protect() if we're not active; the information is just saved for the next activation { var computedStart = WaterboxUtils.AlignDown(start); var computedEnd = WaterboxUtils.AlignUp(start + length); var computedLength = computedEnd - computedStart; if (mprotect(Z.US(computedStart), Z.UU(computedLength), p) != 0) { throw new InvalidOperationException("mprotect() returned -1!"); } } }
/// <exception cref="InvalidOperationException">failed to protect memory</exception> public override void Protect(ulong start, ulong length, Protection prot) { if (length == 0) { return; } var pstart = GetPage(start); var pend = GetPage(start + length - 1); for (var i = pstart; i <= pend; i++) { _pageData[i] = prot; // also store the value for later use } if (!Active) { return; // it's legal to call this method if we're not active; the information is just saved for the next activation } var computedStart = WaterboxUtils.AlignDown(start); var protEnum = prot.ToMemoryProtection(); var exitCode = mprotect( Z.US(computedStart), Z.UU(WaterboxUtils.AlignUp(start + length) - computedStart), protEnum ); if (exitCode != 0) { throw new InvalidOperationException($"{nameof(mprotect)}() returned {exitCode}!"); } }
/// <summary>allocate <paramref name="size"/> bytes starting at a particular address <paramref name="start"/></summary> /// <exception cref="ArgumentOutOfRangeException"><paramref name="start"/> is not aligned or <paramref name="size"/> is <c>0</c></exception> protected MemoryBlockBase(ulong start, ulong size) { if (!WaterboxUtils.Aligned(start)) { throw new ArgumentOutOfRangeException(nameof(start), start, "start address must be aligned"); } if (size == 0) { throw new ArgumentOutOfRangeException(nameof(size), size, "cannot create 0-length block"); } Size = WaterboxUtils.AlignUp(size); AddressRange = start.RangeToExclusive(start + Size); _pageData = new Protection[1 + GetPage(AddressRange.EndInclusive)]; }
protected MemoryBlockBase(ulong start, ulong size) { if (!WaterboxUtils.Aligned(start)) { throw new ArgumentOutOfRangeException(); } if (size == 0) { throw new ArgumentOutOfRangeException(); } Start = start; Size = WaterboxUtils.AlignUp(size); End = Start + Size; _pageData = new Protection[GetPage(End - 1) + 1]; }
/// <summary> /// take a hash of the current full contents of the block, including unreadable areas /// </summary> /// <returns></returns> public override byte[] FullHash() { if (!Active) { throw new InvalidOperationException("Not active"); } // temporarily switch the entire block to `R` if (mprotect(Z.US(Start), Z.UU(Size), MemoryProtection.Read) != 0) { throw new InvalidOperationException("mprotect() returned -1!"); } var ret = WaterboxUtils.Hash(GetStream(Start, Size, false)); ProtectAll(); return(ret); }
/// <summary> /// take a hash of the current full contents of the block, including unreadable areas /// </summary> public byte[] FullHash() { if (!Active) { throw new InvalidOperationException("Not active"); } // temporarily switch the entire block to `R` Kernel32.MemoryProtection old; if (!Kernel32.VirtualProtect(Z.UU(Start), Z.UU(Size), Kernel32.MemoryProtection.READONLY, out old)) { throw new InvalidOperationException($"{nameof(Kernel32.VirtualProtect)}() returned FALSE!"); } var ret = WaterboxUtils.Hash(GetStream(Start, Size, false)); ProtectAll(); return(ret); }
/// <exception cref="InvalidOperationException"><see cref="MemoryBlockBase.Active"/> is <see langword="false"/> or failed to make memory read-only</exception> public override byte[] FullHash() { if (!Active) { throw new InvalidOperationException("Not active"); } // temporarily switch the entire block to `R` var exitCode = mprotect(Z.US(AddressRange.Start), Z.UU(Size), MemoryProtection.Read); if (exitCode != 0) { throw new InvalidOperationException($"{nameof(mprotect)}() returned {exitCode}!"); } var ret = WaterboxUtils.Hash(GetStream(AddressRange.Start, Size, false)); ProtectAll(); return(ret); }
/// <summary> /// allocate size bytes starting at a particular address /// </summary> /// <param name="start"></param> /// <param name="size"></param> public MemoryBlockUnix(ulong start, ulong size) { if (!WaterboxUtils.Aligned(start)) { throw new ArgumentOutOfRangeException(); } if (size == 0) { throw new ArgumentOutOfRangeException(); } size = WaterboxUtils.AlignUp(size); _fd = memfd_create("MemoryBlockUnix", 0); if (_fd == -1) { throw new InvalidOperationException("memfd_create() returned -1"); } Start = start; End = start + size; Size = size; _pageData = new Protection[GetPage(End - 1) + 1]; }
/// <summary> /// allocate size bytes starting at a particular address /// </summary> /// <param name="start"></param> /// <param name="size"></param> public MemoryBlockWin32(ulong start, ulong size) { if (!WaterboxUtils.Aligned(start)) { throw new ArgumentOutOfRangeException(); } if (size == 0) { throw new ArgumentOutOfRangeException(); } size = WaterboxUtils.AlignUp(size); _handle = Kernel32.CreateFileMapping(Kernel32.INVALID_HANDLE_VALUE, IntPtr.Zero, Kernel32.FileMapProtection.PageExecuteReadWrite | Kernel32.FileMapProtection.SectionCommit, (uint)(size >> 32), (uint)size, null); if (_handle == IntPtr.Zero) { throw new InvalidOperationException("CreateFileMapping() returned NULL"); } Start = start; End = start + size; Size = size; _pageData = new Protection[GetPage(End - 1) + 1]; }
/// <exception cref="InvalidOperationException">snapshot already taken, <see cref="MemoryBlockBase.Active"/> is <see langword="false"/>, or failed to make memory read-only</exception> public override void SaveXorSnapshot() { if (_snapshot != null) { throw new InvalidOperationException("Snapshot already taken"); } if (!Active) { throw new InvalidOperationException("Not active"); } // temporarily switch the entire block to `R`: in case some areas are unreadable, we don't want that to complicate things var exitCode = mprotect(Z.US(AddressRange.Start), Z.UU(Size), MemoryProtection.Read); if (exitCode != 0) { throw new InvalidOperationException($"{nameof(mprotect)}() returned {exitCode}!"); } _snapshot = new byte[Size]; GetStream(AddressRange.Start, Size, false).CopyTo(new MemoryStream(_snapshot, true)); XorHash = WaterboxUtils.Hash(_snapshot); ProtectAll(); }