惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Proofpoint News Feed
S
SegmentFault 最新的问题
The Last Watchdog
The Last Watchdog
人人都是产品经理
人人都是产品经理
C
Check Point Blog
O
OpenAI News
V
Visual Studio Blog
S
Security @ Cisco Blogs
I
InfoQ
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
T
Troy Hunt's Blog
S
Secure Thoughts
大猫的无限游戏
大猫的无限游戏
Attack and Defense Labs
Attack and Defense Labs
www.infosecurity-magazine.com
www.infosecurity-magazine.com
SecWiki News
SecWiki News
月光博客
月光博客
U
Unit 42
博客园 - Franky
V2EX - 技术
V2EX - 技术
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
腾讯CDC
量子位
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
N
News and Events Feed by Topic
Engineering at Meta
Engineering at Meta
PCI Perspectives
PCI Perspectives
Cisco Talos Blog
Cisco Talos Blog
Google DeepMind News
Google DeepMind News
博客园 - 司徒正美
T
Tailwind CSS Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
TaoSecurity Blog
TaoSecurity Blog
Project Zero
Project Zero
WordPress大学
WordPress大学
A
Arctic Wolf
H
Help Net Security
Blog — PlanetScale
Blog — PlanetScale
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
I
Intezer
雷峰网
雷峰网
Security Latest
Security Latest
N
News and Events Feed by Topic
AI
AI
V
Vulnerabilities – Threatpost
S
Schneier on Security
Vercel News
Vercel News
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Manipulating QEMU Hardwares with Bootkit
bekoo · 2026-06-20 · via DEV Community

Boot a fresh QEMU instance and run dmidecode -t 1 inside it:

System Information
    Manufacturer: QEMU
    Product Name: Standard PC (Q35 + ICH9, 2009)
    Version: pc-q35-8.2
    Serial Number: Not Specified
    UUID: ...
    Family: Not Specified

That output is not just cosmetic. Every piece of software that has a reason to know whether it is running inside a virtual machine reads exactly this table — EDRs, anti-cheat engines, sandbox detectors, hypervisor fingerprinting routines. The string "QEMU" in the Manufacturer field is, for most of them, a hard stop. Analysis terminates, behavior changes, the sample goes quiet.

The question is where this data comes from and whether it can be changed before any of those tools get a chance to read it. The answer to the second part is yes — and the mechanism is a DXE driver that runs inside the firmware, before the OS loader is even invoked. This is what makes it a bootkit in the strict sense: code that executes at the firmware level and shapes what the operating system inherits.

What SMBIOS Actually Is

SMBIOS — System Management BIOS — is a specification maintained by DMTF. It defines a standard format for firmware to expose hardware identity information to the operating system. The spec has nothing to do with BIOS in the sense of boot firmware. It is purely a data contract: a layout agreement that says "here is where we put the machine's identity, and here is the structure you use to read it."

At some point during POST, the firmware constructs a contiguous block of records in physical memory and registers its location in one of two places depending on the SMBIOS version. For versions below 3.0, a 32-bit entry point structure sits somewhere between 0xF0000 and 0xFFFFF** carrying the signature SM. For 3.0 and above, a 64-bit entry point with SM3 can sit anywhere in the low 4GB. The OS finds this entry point, pulls the table address out of it, and from that point on it has everything it needs.

The table itself is a flat list. No index, no hash map, no random access mechanism. Records sit in memory back to back, and the only way to find a specific one is to walk from the beginning.

Each record is called a structure, and each structure has a type number. Type 0 is BIOS information. Type 1 is system information — manufacturer, product name, serial number, UUID. Type 2 is baseboard. Type 4 is processor. The list goes on to Type 127, which is the End-of-Table marker. When you hit 127, you stop walking.

A Single Record's Memory Layout

Each structure has two distinct sections sitting contiguously in memory.

The first section is the formatted region: a fixed-length header followed by typed fields. The header is always four bytes — one byte for the type, one for the length of the formatted region, two for a handle that uniquely identifies this record. After the header come the actual fields, and their sizes and meanings are defined by the spec per type. For Type 1, the formatted region is 27 bytes total.

The second section is the string pool. Immediately after the last byte of the formatted region, the firmware writes a sequence of null-terminated ASCII strings back to back. The entire pool is terminated by a double null — an empty string that signals the end.

Here is the part that trips people up: the fields in the formatted region do not hold strings. They hold one-byte indices. The Manufacturer field in Type 1 is not "QEMU" — it is 0x00, meaning "first string in the pool." ProductName is 0x02. SerialNumber is 0x03. An index of zero means the field is not populated.

So in memory, QEMU's Type 1 record looks roughly like this:

[ Type=1 | Len=27 | Handle=0x0001 | Manufacturer=1 | ProductName=2 | Version=3 | ... ]
[ "QEMU\0" ][ "Standard PC (Q35 + ICH9, 2009)\0" ][ "pc-q35-8.2\0" ][ \0 ]

To read Manufacturer, you skip Len bytes from the start of the record to land in the string pool, then walk forward past (index - 1) null terminators to reach the target string. This is not an abstraction — this is literally what every tool that reads SMBIOS does at the bottom of its call stack.

Why This Structure Exists

The split between fixed fields and a string pool is not accidental. The fixed region has a known, spec-defined size per type, which means a parser can jump over any record it does not understand without reading its contents — it just takes Hdr.Length bytes, skips to the string pool terminator, and moves on. This forward-compatibility property is intentional. A parser written against SMBIOS 2.4 can safely traverse a 3.1 structure it has never seen before.

The string pool being variable-length and trailing means the spec does not have to reserve fixed-width character buffers inside the struct for every string field. Serial numbers vary. Product names vary. Packing them into the formatted region would either waste space with padding or impose arbitrary length limits. The trailing pool sidesteps both problems.

The Code

Now that the memory layout is clear, the driver makes considerably more sense.

#include <Uefi.h>

#include <IndustryStandard/SmBios.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Protocol/Smbios.h>

#include "CloakProfile.h"

typedef struct {
  EFI_SMBIOS_HANDLE Handle;
  UINT8             StrNo;
  CONST CHAR8      *Val;
} CLOAK_STR;

#define CLOAK_MAX_STR 64
STATIC CLOAK_STR mStr[CLOAK_MAX_STR];
STATIC UINTN     mStrCount = 0;

#define CLOAK_PUSH(H, IDX, VAL)                                  \
  do {                                                           \
    if ((IDX) != 0 && mStrCount < CLOAK_MAX_STR) {               \
      mStr[mStrCount].Handle = (H);                              \
      mStr[mStrCount].StrNo  = (UINT8)(IDX);                     \
      mStr[mStrCount].Val    = (VAL);                            \
      mStrCount++;                                               \
    }                                                            \
  } while (0)

EFI_STATUS
EFIAPI
VmCloakDxeEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE *SystemTable
  )
{
  EFI_SMBIOS_PROTOCOL     *SmBios;
  EFI_STATUS               Status;
  EFI_SMBIOS_HANDLE        Handle;
  EFI_SMBIOS_TABLE_HEADER *Rec;
  SMBIOS_TABLE_TYPE1      *Type = NULL;

  Status = gBS->LocateProtocol (&gEfiSmbiosProtocolGuid, NULL, (VOID **)&SmBios);
  if (EFI_ERROR (Status)) {
    Print (L"Failed to locate SMBIOS protocol: %r\n", Status);
    return Status;
  }

  mStrCount = 0;
  Handle    = SMBIOS_HANDLE_PI_RESERVED;

  while (SmBios->GetNext (SmBios, &Handle, NULL, &Rec, NULL) == EFI_SUCCESS) {
    switch (Rec->Type) {
      case EFI_SMBIOS_TYPE_SYSTEM_INFORMATION:
        Type = (SMBIOS_TABLE_TYPE1 *)Rec;

        UINT8 Uuid[16] = CLOAK_SYS_UUID;
        CopyMem (&Type->Uuid, Uuid, sizeof (Uuid));

        CLOAK_PUSH (Handle, Type->Manufacturer, CLOAK_SYS_MANUFACTURER);
        CLOAK_PUSH (Handle, Type->ProductName,  CLOAK_SYS_PRODUCT);
        CLOAK_PUSH (Handle, Type->Version,      CLOAK_SYS_VERSION);
        CLOAK_PUSH (Handle, Type->SerialNumber, CLOAK_SYS_SERIAL);
        CLOAK_PUSH (Handle, Type->SKUNumber,    CLOAK_SYS_SKU);
        CLOAK_PUSH (Handle, Type->Family,       CLOAK_SYS_FAMILY);
        break;
    }
  }

  for (UINTN i = 0; i < mStrCount; i++) {
    EFI_SMBIOS_HANDLE h  = mStr[i].Handle;
    UINTN             sn = mStr[i].StrNo;
    SmBios->UpdateString (SmBios, &h, &sn, (CHAR8 *)mStr[i].Val);
  }

  // UpdateString may have reallocated — Type pointer is now stale. Re-fetch.
  Handle = SMBIOS_HANDLE_PI_RESERVED;
  while (SmBios->GetNext (SmBios, &Handle, NULL, &Rec, NULL) == EFI_SUCCESS) {
    if (Rec->Type == EFI_SMBIOS_TYPE_SYSTEM_INFORMATION) {
      Type = (SMBIOS_TABLE_TYPE1 *)Rec;
      break;
    }
  }

  if (Type != NULL) {
    CHAR8 *StrPtr = (CHAR8 *)Type + Type->Hdr.Length;
    UINT8  Idx    = Type->Manufacturer;
    for (UINT8 i = 1; i < Idx; i++) {
      while (*StrPtr) StrPtr++;
      StrPtr++;
    }
    Print (L"Manufacturer after update: %a\n", StrPtr);
  }

  return EFI_SUCCESS;
}

Locating the Protocol

Status = gBS->LocateProtocol (&gEfiSmbiosProtocolGuid, NULL, (VOID **)&SmBios);

The driver's first move is finding EFI_SMBIOS_PROTOCOL. This is the firmware's own interface for reading and modifying SMBIOS records at runtime — before the OS is handed control. LocateProtocol walks the installed protocol list and hands back a pointer to the implementation. If this call fails, there is nothing to work with and the driver bails immediately.

Walking the Table

Handle = SMBIOS_HANDLE_PI_RESERVED;
while (SmBios->GetNext (SmBios, &Handle, NULL, &Rec, NULL) == EFI_SUCCESS) {
  switch (Rec->Type) {
    case EFI_SMBIOS_TYPE_SYSTEM_INFORMATION:

SMBIOS_HANDLE_PI_RESERVED is the sentinel value that tells GetNext to start from the beginning of the table. Each call advances Handle to the next record and writes the record pointer into Rec. This is the same traversal described in the layout section — no shortcut exists, so the driver walks every record until it finds what it needs. The switch on Rec->Type means everything except Type 1 passes through untouched.

UUID vs. Strings — Two Different Problems

Inside the Type 1 handler, the driver deals with two categories of fields.

UUID is a binary field — 16 raw bytes embedded directly in the formatted region. It has no pool index, it does not live in the string section. The write is direct:

UINT8 Uuid[16] = CLOAK_SYS_UUID;
CopyMem (&Type->Uuid, Uuid, sizeof (Uuid));

CopyMem overwrites the bytes in place. No pool traversal, no protocol call needed.

String fields are a different problem. Manufacturer, ProductName, SerialNumber and the rest are one-byte indices into the string pool. To change what they resolve to, you cannot touch the index byte — it already points to the right slot. You have to rewrite the actual string sitting at that slot in the pool, which may be a different length than what was there before. That is UpdateString's job, and it is the reason the queue exists.

The Queue — Why Not Call UpdateString Immediately

When UpdateString replaces a string with one of a different length, it has to resize the record in memory. The firmware may reallocate the entire block. If it does, Type — the pointer the driver is currently holding — becomes stale. It points to an address the firmware has already moved on from.

The driver avoids this by never calling UpdateString during the traversal loop. Every pending update is queued into mStr instead:

#define CLOAK_PUSH(H, IDX, VAL)                                  \
  do {                                                           \
    if ((IDX) != 0 && mStrCount < CLOAK_MAX_STR) {               \
      mStr[mStrCount].Handle = (H);                              \
      mStr[mStrCount].StrNo  = (UINT8)(IDX);                     \
      mStr[mStrCount].Val    = (VAL);                            \
      mStrCount++;                                               \
    }                                                            \
  } while (0)

IDX != 0 guards against unpopulated fields — index zero means "not present" in the SMBIOS spec, and passing it to UpdateString is undefined territory. Everything else is pushed onto the queue with its handle, its string slot number, and the replacement value.

Once the traversal loop exits and Type is no longer being touched, the driver drains the queue:

for (UINTN i = 0; i < mStrCount; i++) {
  EFI_SMBIOS_HANDLE h  = mStr[i].Handle;
  UINTN             sn = mStr[i].StrNo;
  SmBios->UpdateString (SmBios, &h, &sn, (CHAR8 *)mStr[i].Val);
}

Reallocation can now happen freely. Nothing holds a live pointer into the record at this point.

Refreshing the Pointer and Verifying

After UpdateString runs, the old Type pointer cannot be trusted regardless of whether reallocation actually occurred — the driver has no way to determine that. So it runs GetNext again from scratch to obtain a fresh pointer to the Type 1 record, then manually traverses the string pool to confirm the write landed:

CHAR8 *StrPtr = (CHAR8 *)Type + Type->Hdr.Length;
UINT8  Idx    = Type->Manufacturer;
for (UINT8 i = 1; i < Idx; i++) {
  while (*StrPtr) StrPtr++;
  StrPtr++;
}
Print (L"Manufacturer after update: %a\n", StrPtr);

Type + Hdr.Length lands exactly at the start of the string pool — the formatted region ends there and strings begin immediately after. The loop skips forward (Manufacturer - 1) null terminators to reach the correct slot. The inner while (*StrPtr) StrPtr++ walks past the current string's characters; the StrPtr++ after it steps over the null terminator. What remains at StrPtr is the target string, read back directly from the record the firmware now holds.