mirror of
https://github.com/Metabolix/HackBGRT.git
synced 2025-12-06 17:15:42 -08:00
Report own entry status (missing, disabled, after windows, enabled). Also improve README instructions on the topic.
299 lines
9.3 KiB
C#
299 lines
9.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.ComponentModel;
|
|
using Microsoft.Win32;
|
|
|
|
/**
|
|
* Methods for handling EFI variables.
|
|
*/
|
|
public class Efi {
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
private static extern UInt32 GetFirmwareEnvironmentVariableEx(string lpName, string lpGuid, [MarshalAs(UnmanagedType.LPArray)] byte[] pBuffer, UInt32 nSize, out UInt32 pdwAttributes);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
private static extern UInt32 SetFirmwareEnvironmentVariableEx(string lpName, string lpGuid, [MarshalAs(UnmanagedType.LPArray)] byte[] pBuffer, UInt32 nSize, UInt32 pdwAttributes);
|
|
|
|
[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
|
|
private static extern bool AdjustTokenPrivileges(IntPtr htoken, bool disable, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);
|
|
|
|
[DllImport("kernel32.dll", ExactSpelling = true)]
|
|
private static extern IntPtr GetCurrentProcess();
|
|
|
|
[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
|
|
private static extern bool OpenProcessToken(IntPtr h, int acc, out IntPtr phtok);
|
|
|
|
[DllImport("advapi32.dll", SetLastError = true)]
|
|
private static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
private static extern bool CloseHandle(IntPtr hObject);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
private static extern UInt32 GetSystemFirmwareTable(UInt32 provider, UInt32 table, [MarshalAs(UnmanagedType.LPArray)] byte[] buffer, UInt32 len);
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
private struct TokPriv1Luid {
|
|
public int Count;
|
|
public long Luid;
|
|
public int Attr;
|
|
}
|
|
|
|
/**
|
|
* Information about an EFI variable.
|
|
*/
|
|
public class Variable {
|
|
public string Name, Guid;
|
|
public UInt32 Attributes;
|
|
public byte[] Data;
|
|
|
|
/**
|
|
* Convert to string.
|
|
*
|
|
* @return String representation of this object.
|
|
*/
|
|
public override string ToString() {
|
|
if (Data == null) {
|
|
return $"{Name} Guid={Guid} Attributes={Attributes} Data=null";
|
|
}
|
|
var hex = BitConverter.ToString(Data).Replace("-", " ");
|
|
var text = new string(Data.Select(c => 0x20 <= c && c <= 0x7f ? (char) c : ' ').ToArray());
|
|
return $"{Name} Guid={Guid} Attributes={Attributes} Text='{text}' Bytes='{hex}'";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GUID of the global EFI variables.
|
|
*/
|
|
public const string EFI_GLOBAL_GUID = "{8be4df61-93ca-11d2-aa0d-00e098032b8c}";
|
|
|
|
/**
|
|
* GUID for HackBGRT EFI variables.
|
|
*/
|
|
public const string EFI_HACKBGRT_GUID = "{03c64761-075f-4dba-abfb-2ed89e18b236}";
|
|
|
|
/**
|
|
* Directory containing EFI variables in Linux.
|
|
*/
|
|
public const string LinuxEfiDir = "/sys/firmware/efi/efivars";
|
|
|
|
/**
|
|
* Enable the privilege to access EFI variables.
|
|
*/
|
|
public static void EnablePrivilege() {
|
|
if (Directory.Exists(LinuxEfiDir)) {
|
|
var linuxEfiFile = $"{LinuxEfiDir}/BootOrder-8be4df61-93ca-11d2-aa0d-00e098032b8c";
|
|
if (File.Exists(linuxEfiFile)) {
|
|
using (FileStream fs = File.OpenWrite(linuxEfiFile)) {
|
|
// OpenWrite throws an exception on error.
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
const int SE_PRIVILEGE_ENABLED = 0x00000002;
|
|
const int TOKEN_QUERY = 0x00000008;
|
|
const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
|
|
const string SE_SYSTEM_ENVIRONMENT_NAME = "SeSystemEnvironmentPrivilege";
|
|
IntPtr htoken = IntPtr.Zero;
|
|
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out htoken)) {
|
|
throw new Exception("EnablePrivilege: OpenProcessToken failed: " + Marshal.GetLastWin32Error());
|
|
}
|
|
try {
|
|
TokPriv1Luid tp;
|
|
tp.Count = 1;
|
|
tp.Luid = 0;
|
|
tp.Attr = SE_PRIVILEGE_ENABLED;
|
|
if (!LookupPrivilegeValue(null, SE_SYSTEM_ENVIRONMENT_NAME, ref tp.Luid)) {
|
|
throw new Exception("EnablePrivilege: LookupPrivilegeValue failed: " + Marshal.GetLastWin32Error());
|
|
}
|
|
if (!AdjustTokenPrivileges(htoken, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero)) {
|
|
throw new Exception("EnablePrivilege: AdjustTokenPrivileges failed: " + Marshal.GetLastWin32Error());
|
|
}
|
|
} finally {
|
|
CloseHandle(htoken);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get an EFI variable.
|
|
*
|
|
* @param name Name of the EFI variable.
|
|
* @param guid GUID of the EFI variable.
|
|
* @return Information about the EFI variable.
|
|
*/
|
|
public static Variable GetVariable(string name, string guid = EFI_GLOBAL_GUID) {
|
|
Variable result = new Variable();
|
|
result.Name = name;
|
|
result.Guid = guid;
|
|
result.Data = null;
|
|
result.Attributes = 0;
|
|
|
|
if (Directory.Exists(LinuxEfiDir)) {
|
|
var linuxEfiFile = $"{LinuxEfiDir}/{name}-{guid.Substring(1, guid.Length - 2)}";
|
|
if (File.Exists(linuxEfiFile)) {
|
|
var d = File.ReadAllBytes(linuxEfiFile);
|
|
result.Attributes = (UInt32)(d[0] + 0x100 * d[1] + 0x10000 * d[2] + 0x1000000 * d[3]);
|
|
result.Data = d.Skip(4).ToArray();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
for (UInt32 i = 4096; i <= 1024*1024; i *= 2) {
|
|
byte[] buf = new byte[i];
|
|
UInt32 len = GetFirmwareEnvironmentVariableEx(name, guid, buf, (UInt32) buf.Length, out result.Attributes);
|
|
if (len == buf.Length) {
|
|
continue;
|
|
}
|
|
var error = Marshal.GetLastWin32Error();
|
|
const int ERROR_ENVVAR_NOT_FOUND = 0xCB;
|
|
if (error == ERROR_ENVVAR_NOT_FOUND) {
|
|
return result;
|
|
}
|
|
if (len > 0 || error == 0) {
|
|
result.Data = new byte[len];
|
|
Array.Copy(buf, 0, result.Data, 0, len);
|
|
return result;
|
|
}
|
|
const int ERROR_INVALID_FUNCTION = 0x1;
|
|
var msg = $"GetVariable: error 0x{error:X08}: ${new Win32Exception(error).Message}";
|
|
throw error == ERROR_INVALID_FUNCTION ? new NotImplementedException(msg) : new Exception(msg);
|
|
}
|
|
throw new Exception("GetVariable: result exceeds 1MB");
|
|
}
|
|
|
|
/**
|
|
* Set an EFI variable.
|
|
*
|
|
* @param v Information of the variable.
|
|
* @param dryRun Don't actually set the variable.
|
|
*/
|
|
public static void SetVariable(Variable v, bool dryRun = false) {
|
|
Setup.WriteLine($"Writing EFI variable {v.Name} (see log for details)");
|
|
Setup.Log($"Writing EFI variable: {v}");
|
|
if (dryRun) {
|
|
return;
|
|
}
|
|
|
|
if (Directory.Exists(LinuxEfiDir)) {
|
|
var linuxEfiFile = $"{LinuxEfiDir}/{v.Name}-{v.Guid.Substring(1, v.Guid.Length - 2)}";
|
|
var a = v.Attributes;
|
|
var b = new byte[] { (byte) a, (byte) (a >> 8), (byte) (a >> 16), (byte) (a >> 24) };
|
|
// FIXME: Just writing won't work: File.WriteAllBytes(linuxEfiFile, b.Concat(v.Data).ToArray());
|
|
Setup.WriteLine("FIXME: Can't yet write EFI variables in Linux.");
|
|
return;
|
|
}
|
|
|
|
UInt32 r = SetFirmwareEnvironmentVariableEx(v.Name, v.Guid, v.Data, (UInt32) v.Data.Length, v.Attributes);
|
|
if (r == 0) {
|
|
switch (Marshal.GetLastWin32Error()) {
|
|
case 87:
|
|
throw new Exception("SetVariable: Invalid parameter");
|
|
case 1314:
|
|
throw new Exception("SetVariable: Privilege not held");
|
|
default:
|
|
throw new Exception("SetVariable: error " + Marshal.GetLastWin32Error());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if Secure Boot is enabled.
|
|
*
|
|
* @return 0 for disabled, 1 for enabled, other for unknown.
|
|
*/
|
|
public static int GetSecureBootStatus() {
|
|
// GetVariable("SecureBoot") reports always 1 (on Lenovo E335).
|
|
// Windows registry seems to work better, though.
|
|
try {
|
|
return (int) Registry.GetValue(
|
|
"HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\SecureBoot\\State",
|
|
"UEFISecureBootEnabled",
|
|
-1
|
|
);
|
|
} catch {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if it's possible to reboot to EFI setup.
|
|
*
|
|
* @return True, if it's possible to reboot to EFI setup.
|
|
*/
|
|
public static bool CanBootToFW() {
|
|
try {
|
|
Variable tmp = GetVariable("OsIndicationsSupported");
|
|
return tmp.Data != null && (tmp.Data[0] & 1) != 0;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mark that the next reboot should go to EFI setup.
|
|
*/
|
|
public static void SetBootToFW() {
|
|
Variable tmp = GetVariable("OsIndications");
|
|
if (tmp.Data == null) {
|
|
tmp.Data = new byte[8];
|
|
tmp.Attributes = 7;
|
|
}
|
|
tmp.Data[0] |= 1;
|
|
SetVariable(tmp);
|
|
}
|
|
|
|
/**
|
|
* Convert bytes into UInt16 values.
|
|
*
|
|
* @param bytes The byte array.
|
|
* @return An enumeration of UInt16 values.
|
|
*/
|
|
public static IEnumerable<UInt16> BytesToUInt16s(byte[] bytes) {
|
|
// TODO: return bytes.Chunk(2).Select(b => (UInt16) (b[0] + 0x100 * b[1])).ToArray();
|
|
return Enumerable.Range(0, bytes.Length / 2).Select(i => (UInt16) (bytes[2 * i] + 0x100 * bytes[2 * i + 1]));
|
|
}
|
|
|
|
/**
|
|
* Retrieve HackBGRT log collected during boot.
|
|
*/
|
|
public static string GetHackBGRTLog() {
|
|
try {
|
|
var log = GetVariable("HackBGRTLog", EFI_HACKBGRT_GUID);
|
|
if (log.Data == null) {
|
|
return "Boot log is empty.";
|
|
}
|
|
return new string(BytesToUInt16s(log.Data).Select(i => (char)i).ToArray());
|
|
} catch (NotImplementedException e) {
|
|
throw e;
|
|
} catch (Exception e) {
|
|
return $"Boot log not found: {e.ToString()}";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Log the BGRT table (for debugging).
|
|
*/
|
|
public static void LogBGRT() {
|
|
try {
|
|
const UInt32 acpiBE = 0x41435049, bgrtLE = 0x54524742;
|
|
UInt32 size = GetSystemFirmwareTable(acpiBE, bgrtLE, null, 0);
|
|
byte[] buf = new byte[size];
|
|
var ret = GetSystemFirmwareTable(acpiBE, bgrtLE, buf, size);
|
|
if (ret == size) {
|
|
var hex = BitConverter.ToString(buf).Replace("-", " ");
|
|
Setup.Log($"LogBGRT: {size} bytes: {hex}");
|
|
} else if (ret == 0) {
|
|
Setup.Log($"LogBGRT: Win32Error {Marshal.GetLastWin32Error()}");
|
|
} else {
|
|
Setup.Log($"LogBGRT: Size problems: spec {0x38}, buf {size}, ret {ret}");
|
|
}
|
|
} catch (Exception e) {
|
|
Setup.Log($"LogBGRT failed: {e}");
|
|
}
|
|
}
|
|
}
|