<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>reverse-engineering on Himanshu Anand :: Security &amp; Other Notes</title>
    <link>https://blog.himanshuanand.com/tags/reverse-engineering/</link>
    <description>Recent content in reverse-engineering on Himanshu Anand :: Security &amp; Other Notes</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Thu, 11 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.himanshuanand.com/tags/reverse-engineering/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Reading a patch tuesday diff for fun: the dhcp client memcpy that copies more than four bytes (CVE-2026-44815)</title>
      <link>https://blog.himanshuanand.com/2026/06/reading-a-patch-tuesday-diff-for-fun-the-dhcp-client-memcpy-that-copies-more-than-four-bytes-cve-2026-44815/</link>
      <pubDate>Thu, 11 Jun 2026 00:00:00 +0000</pubDate>
      
      <guid>https://blog.himanshuanand.com/2026/06/reading-a-patch-tuesday-diff-for-fun-the-dhcp-client-memcpy-that-copies-more-than-four-bytes-cve-2026-44815/</guid>
      <description>TLDR; June 2026 was the biggest Patch Tuesday Microsoft has ever shipped 208 CVEs. One of them, CVE-2026-44815 (This is as bad as Etner blue, Wanna cry isse), is a CVSS 9.8 &amp;ldquo;DHCP Client Service Remote Code Execution.&amp;rdquo; I pulled the patched dhcpcore.dll, diffed it against last month&amp;rsquo;s build and the whole bug fits on one screen: a function called GetOriginalSubnetMask does a memcpy into a 4-byte buffer using a length field that came off the wire from a DHCP server, with no check that the length is actually 4.</description>
      <content>&lt;h2 id=&#34;tldr&#34;&gt;TLDR;&lt;/h2&gt;
&lt;p&gt;June 2026 was the biggest Patch Tuesday Microsoft has ever shipped 208 CVEs. One of them,
&lt;strong&gt;CVE-2026-44815&lt;/strong&gt; (This is as bad as Etner blue, Wanna cry isse), is a CVSS 9.8 &amp;ldquo;DHCP Client Service Remote Code Execution.&amp;rdquo;
I pulled the patched &lt;code&gt;dhcpcore.dll&lt;/code&gt;, diffed it against last month&amp;rsquo;s build and the whole bug fits on one screen: a function
called &lt;code&gt;GetOriginalSubnetMask&lt;/code&gt; does a &lt;code&gt;memcpy&lt;/code&gt; into a &lt;strong&gt;4-byte&lt;/strong&gt; buffer using a length field that came
off the wire from a DHCP server, with &lt;strong&gt;no check that the length is actually 4&lt;/strong&gt;. The June patch adds
exactly one check: &lt;code&gt;if (len != 4) bail&lt;/code&gt;. This post walks the entire 1 day workflow end to end how I
got the binaries, how I diffed them, how I read the patch and how I reasoned about reachability so
that if you are new to 1-day analysis (PAtch diffing) you can do the next one yourself. I am deliberately &lt;strong&gt;not&lt;/strong&gt; publishing
the last mile to a weaponized exploit and there&amp;rsquo;s a Snort rule at the bottom for my blue team bros.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.himanshuanand.com/images/disaster.jpg&#34; alt=&#34;disaster&#34;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;why-even-look-at-the-dhcp-client&#34;&gt;why even look at the dhcp client&lt;/h2&gt;
&lt;p&gt;When I triage a Patch Tuesday for 1-day candidates, I sort by three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Severity + &amp;ldquo;exploitation more likely.&amp;rdquo;&lt;/strong&gt; CVSS 9.8 with RCE in the title goes to the top.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A single, self-contained binary.&lt;/strong&gt; Kernel TCP/IP is great but &lt;code&gt;tcpip.sys&lt;/code&gt; is 3.5 MB of pain. A
client DLL that parses one protocol is a much friendlier first target.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Attacker-controlled input with an obvious wire format.&lt;/strong&gt; DHCP is a TLV (type length value) protocol.
TLV + &amp;ldquo;RCE&amp;rdquo; in the same sentence is almost always a length-handling bug. That is a pattern you learn to
smell.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;CVE-2026-44815&lt;/code&gt; hits all three. It&amp;rsquo;s in the &lt;strong&gt;DHCP client&lt;/strong&gt;, which means the attacker is a &lt;em&gt;server&lt;/em&gt; a
rogue or compromised DHCP server on the local link answering your &lt;code&gt;DHCPDISCOVER&lt;/code&gt;. The client is the
victim. That&amp;rsquo;s a classic, underrated surface: every laptop that joins a coffee shop network is a DHCP
client.&lt;/p&gt;
&lt;p&gt;One more thing made this irresistible. The public ZDI writeup flagged an &amp;ldquo;odd incongruity&amp;rdquo;: the CVSS
vector says &lt;strong&gt;no privileges required&lt;/strong&gt;, but Microsoft&amp;rsquo;s text says it needs an &lt;strong&gt;authenticated&lt;/strong&gt; user.
Those two statements look contradictory. By the end of this post you&amp;rsquo;ll see they are both true and &lt;em&gt;why&lt;/em&gt;
is the most interesting part of the bug.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;step-1-get-both-binaries-you-do-not-need-a-windows-box&#34;&gt;step 1: get both binaries (you do not need a windows box)&lt;/h2&gt;
&lt;p&gt;You need two versions of &lt;code&gt;dhcpcore.dll&lt;/code&gt;: the &lt;strong&gt;patched&lt;/strong&gt; one (June) and the &lt;strong&gt;previous&lt;/strong&gt; one (May). The
classic way to do this is to download the MSU from the Microsoft Update Catalog and unpack it but modern
Windows updates ship &lt;em&gt;forward-delta&lt;/em&gt; patches that need the Windows-only &lt;code&gt;msdelta&lt;/code&gt; API to reconstruct.
Annoying if you&amp;rsquo;re on macOS or Linux.&lt;/p&gt;
&lt;p&gt;The shortcut: &lt;strong&gt;&lt;a href=&#34;https://winbindex.m417z.com/&#34;&gt;Winbindex&lt;/a&gt;&lt;/strong&gt;. It indexes essentially every Windows binary
ever shipped, by version and hash, and links each one to the Microsoft &lt;strong&gt;symbol server&lt;/strong&gt;, which serves the
&lt;em&gt;full&lt;/em&gt; PE (not a delta). So you can grab any historical full binary with &lt;code&gt;curl&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The symbol-server URL is keyed by the PE&amp;rsquo;s &lt;code&gt;TimeDateStamp&lt;/code&gt; and &lt;code&gt;SizeOfImage&lt;/code&gt;, concatenated as hex:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;https://msdl.microsoft.com/download/symbols/&amp;lt;name&amp;gt;/&amp;lt;TimeDateStamp:08X&amp;gt;&amp;lt;SizeOfImage:X&amp;gt;/&amp;lt;name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pull the Winbindex JSON for &lt;code&gt;dhcpcore.dll&lt;/code&gt;, find the June 2026 entry and the one right before it, build
those two keys, and download both. On Win11 24H2 that&amp;rsquo;s:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;build&lt;/th&gt;
&lt;th&gt;KB&lt;/th&gt;
&lt;th&gt;role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;10.0.26200.8524&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;KB5089573 (May)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;OLD&lt;/strong&gt; (vulnerable)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;10.0.26200.8655&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;KB5094126 (June)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;NEW&lt;/strong&gt; (patched)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;While you are on this page, grab the &lt;strong&gt;PDBs&lt;/strong&gt; too. Microsoft publishes &lt;em&gt;public&lt;/em&gt; symbols for system binaries on the
same server. Parse the PE&amp;rsquo;s CodeView (RSDS) debug record to get the PDB GUID+age and download:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;https://msdl.microsoft.com/download/symbols/dhcpcore.pdb/&amp;lt;GUID&amp;gt;&amp;lt;AGE&amp;gt;/dhcpcore.pdb
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This step is the single biggest force multiplier in Windows 1-day work. With public PDBs applied, every
function in your decompiler has its &lt;strong&gt;real name&lt;/strong&gt;. You diff &lt;code&gt;GetOriginalSubnetMask&lt;/code&gt; vs &lt;code&gt;GetOriginalSubnetMask&lt;/code&gt;,
not &lt;code&gt;FUN_18001a100&lt;/code&gt; vs &lt;code&gt;FUN_18000ff44&lt;/code&gt;. Do not skip the PDBs.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;step-2-diff-the-two-builds&#34;&gt;step 2: diff the two builds&lt;/h2&gt;
&lt;p&gt;I run &lt;strong&gt;Ghidra headless&lt;/strong&gt; and dump every function&amp;rsquo;s decompiled C to JSON, once per build, then compare by
function name. Two beginner-grade gotchas worth calling out, because both cost me time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Apple Silicon.&lt;/strong&gt; Ghidra&amp;rsquo;s decompiler is a native binary that only ships for &lt;code&gt;linux_x86_64&lt;/code&gt;. On an
arm64 host (or arm64 container) &lt;code&gt;openProgram&lt;/code&gt; silently fails and &lt;em&gt;every&lt;/em&gt; function decompiles to an empty
string and then your diff cheerfully reports &amp;ldquo;nothing changed.&amp;rdquo; Run Ghidra in an &lt;strong&gt;amd64 container under
emulation&lt;/strong&gt; and it just works. If your diff says a security patch changed nothing, suspect your tooling
before you believe it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Diff noise.&lt;/strong&gt; Don&amp;rsquo;t diff raw decompiler text. Microsoft rotates WPP trace GUIDs and bumps trace message
IDs every month, the linker shuffles addresses, and the decompiler renumbers locals. All of that makes
&lt;em&gt;unchanged&lt;/em&gt; functions look different. &lt;strong&gt;Normalize&lt;/strong&gt; first: strip hex literals, &lt;code&gt;FUN_/DAT_/LAB_&lt;/code&gt; autonames,
and local-variable suffixes, collapse whitespace, then compare. After normalizing, the real patch pops out
of the noise.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For &lt;code&gt;dhcpcore.dll&lt;/code&gt;, after filtering the CRT/thunk noise, the meaningful changes were tiny:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;changed (most-changed first):
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  0.516  LeaseIpAddressEx
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  0.603  GetOriginalSubnetMask     &amp;lt;-- 245 -&amp;gt; 367 bytes, address moved a lot
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  0.852  RenewIpAddressLeaseEx
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  ... + 4 brand-new helper functions
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;GetOriginalSubnetMask&lt;/code&gt; grew by ~50% and moved. A small parsing function that suddenly gains code on a
9.8-RCE month is exactly where you look first.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;step-3-read-the-patch&#34;&gt;step 3: read the patch&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s the real diff straight out of the decompiler (Ghidra&amp;rsquo;s &lt;code&gt;DecompInterface&lt;/code&gt;, OLD vs NEW, public
PDB applied). I&amp;rsquo;ve trimmed the pure variable-rename noise the &lt;code&gt;...&lt;/code&gt; marks omitted lines but the
security relevant change is verbatim:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; undefined4 GetOriginalSubnetMask(longlong param_1, undefined4 *param_2)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   ...
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   AcquireSRWLockShared((PSRWLOCK)(param_1 + 0x2a0));
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   puVar4 = *(undefined8 **)(param_1 + 0x2b8);
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   do {                                          // walk stored DHCP option list
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;     ...
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   } while (*(int *)(puVar4 + 2) != 0xdd);       // node tagged 0xdd
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gd&#34;&gt;-  memcpy(param_2, (void *)puVar3[6], (ulonglong)*(uint *)(puVar3 + 7));   // copy len bytes, UNCHECKED
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gd&#34;&gt;&lt;/span&gt;&lt;span class=&#34;gi&#34;&gt;+  uVar1 = *(uint *)(puVar4 + 7);                // uVar1 = server-controlled option length
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gi&#34;&gt;+  if (g_fVelocityDhcpGetOriginalSubnetMaskFix == 0) {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gi&#34;&gt;+    memcpy(param_2, (void *)puVar4[6], (ulonglong)uVar1);   // legacy path = STILL unchecked
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gi&#34;&gt;+  }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gi&#34;&gt;+  else {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gi&#34;&gt;+    if (uVar1 != 4) {                           // &amp;lt;-- THE FIX: a subnet mask is exactly 4 bytes
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gi&#34;&gt;+      WPP_SF_DJ(0x401, 0x3c, 0x180057630, uVar1);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gi&#34;&gt;+      goto LAB_18001a181;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gi&#34;&gt;+    }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gi&#34;&gt;+    memcpy(param_2, (void *)puVar4[6], (ulonglong)uVar1);   // len == 4, safe
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gi&#34;&gt;+  }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gi&#34;&gt;&lt;/span&gt;   ...
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; LAB_18001a181:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   ReleaseSRWLockShared((PSRWLOCK)(param_1 + 0x2a0));
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   return 0;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here&amp;rsquo;s the same function in the &lt;strong&gt;patched&lt;/strong&gt; (June) build, written out cleanly:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;undefined4&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;GetOriginalSubnetMask&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;longlong&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;undefined4&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;out&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;out&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;mh&#34;&gt;0x57&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;          &lt;span class=&#34;c1&#34;&gt;// ERROR_INVALID_PARAMETER
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;  &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;out&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;AcquireSRWLockShared&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ctx&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;mh&#34;&gt;0x2a0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;// walk the per-lease stored-option linked list at ctx+0x2b8,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;// looking for the node whose internal tag == 0xDD
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;  &lt;span class=&#34;n&#34;&gt;node&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;**&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ctx&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;mh&#34;&gt;0x2b8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;cur&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;node&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;cur&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ctx&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;mh&#34;&gt;0x2b8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;cm&#34;&gt;/* not found */&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;goto&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;done&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;node&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;**&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;cur&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;cur&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;mh&#34;&gt;0x10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt; &lt;span class=&#34;mh&#34;&gt;0xdd&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;uint&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;len&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;uint&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;cur&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;mh&#34;&gt;0x38&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;       &lt;span class=&#34;c1&#34;&gt;// &amp;lt;-- length of the stored option
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;g_fVelocityDhcpGetOriginalSubnetMaskFix&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;n&#34;&gt;memcpy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;out&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;**&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;cur&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;mh&#34;&gt;0x30&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;   &lt;span class=&#34;c1&#34;&gt;// &amp;lt;-- OLD, UNCHECKED path
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;      &lt;span class=&#34;p&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;len&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;4&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;cm&#34;&gt;/* WPP log, bail */&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;goto&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;done&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;   &lt;span class=&#34;c1&#34;&gt;// &amp;lt;-- THE FIX
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;      &lt;span class=&#34;n&#34;&gt;memcpy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;out&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;**&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;cur&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;mh&#34;&gt;0x30&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nl&#34;&gt;done&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;ReleaseSRWLockShared&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ctx&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;mh&#34;&gt;0x2a0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the &lt;strong&gt;old&lt;/strong&gt; (May) build is just the &lt;code&gt;g_fVelocityDhcpGetOriginalSubnetMaskFix == 0&lt;/code&gt; branch with no
sibling i.e unconditionally:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;memcpy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;out&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;**&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;cur&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;mh&#34;&gt;0x30&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;uint&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;cur&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;mh&#34;&gt;0x38&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s the whole bug. Let me spell out why it&amp;rsquo;s bad.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;out&lt;/code&gt; is the caller&amp;rsquo;s output buffer for a &lt;strong&gt;subnet mask&lt;/strong&gt;. A subnet mask is an IPv4 address &lt;strong&gt;4 bytes&lt;/strong&gt;.
The caller hands over a pointer to a 4-byte slot.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;len&lt;/code&gt; (&lt;code&gt;*(uint*)(cur + 0x38)&lt;/code&gt;) is the &lt;strong&gt;length of a stored DHCP option&lt;/strong&gt;, which was populated from a DHCP
server&amp;rsquo;s response packet.&lt;/li&gt;
&lt;li&gt;The old code copies &lt;code&gt;len&lt;/code&gt; bytes into the 4-byte slot. If &lt;code&gt;len &amp;gt; 4&lt;/code&gt;, you overflow the destination by
&lt;code&gt;len - 4&lt;/code&gt; bytes with &lt;strong&gt;fully attacker-controlled content&lt;/strong&gt; (&lt;code&gt;*(void**)(cur + 0x30)&lt;/code&gt; is the option&amp;rsquo;s value,
also straight off the wire).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The fix is one line: a subnet mask is always 4 octets (RFC 2132 §3.3 literally says &amp;ldquo;its length is 4
octets&amp;rdquo;), so reject anything that isn&amp;rsquo;t 4. The bug existed because nobody enforced an invariant the RFC
already guaranteed &lt;em&gt;if the other side plays by the rules&lt;/em&gt;. Attackers don&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;This is the single most common shape of bug you will find in TLV parsers: &lt;strong&gt;trusting the length field of a
value whose size is &amp;ldquo;supposed to be&amp;rdquo; fixed.&lt;/strong&gt; Once you&amp;rsquo;ve seen it once, you&amp;rsquo;ll see it everywhere.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.himanshuanand.com/images/pre_june_patch.jpg&#34; alt=&#34;Pre-June dhcpcore.dll&#34;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;step-4-where-does-the-length-actually-come-from-reachability&#34;&gt;step 4: where does the length actually come from? (reachability)&lt;/h2&gt;
&lt;p&gt;A diff tells you &lt;em&gt;what&lt;/em&gt; changed. It does not tell you whether an attacker can reach it. That&amp;rsquo;s the part
beginners skip and reviewers reject findings over. So let&amp;rsquo;s trace it.&lt;/p&gt;
&lt;p&gt;Two questions: &lt;strong&gt;who fills the linked list&lt;/strong&gt; at &lt;code&gt;ctx+0x2b8&lt;/code&gt;, and &lt;strong&gt;who calls &lt;code&gt;GetOriginalSubnetMask&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Who fills the list.&lt;/strong&gt; The same per-context option list at &lt;code&gt;+0x2b8&lt;/code&gt; is touched by the functions that
process a received lease option storage, context refill, the &amp;ldquo;media connected&amp;rdquo; path that kicks off a
new DISCOVER/REQUEST when you join a network. In other words, the node tagged &lt;code&gt;0xDD&lt;/code&gt; and its &lt;code&gt;len&lt;/code&gt;/&lt;code&gt;value&lt;/code&gt;
fields are populated from &lt;strong&gt;the DHCP server&amp;rsquo;s reply options&lt;/strong&gt;. The attacker who controls those bytes is
whoever answers your DHCP request: a rogue server, or an on-path box doing a DHCP race. No authentication,
no client trust relationship just &amp;ldquo;be the first DHCP server to answer.&amp;rdquo; That matches the CVSS&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AV:Network / PR:None&lt;/strong&gt; exactly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Who calls it.&lt;/strong&gt; &lt;code&gt;GetOriginalSubnetMask&lt;/code&gt; has exactly two callers, and they&amp;rsquo;re both RPC entry points:
&lt;code&gt;RpcSrvGetOriginalSubnetMask&lt;/code&gt; and &lt;code&gt;RpcSrvGetOriginalSubnetMask_New&lt;/code&gt;. So the dangerous &lt;code&gt;memcpy&lt;/code&gt; fires when
&lt;strong&gt;something invokes that RPC&lt;/strong&gt; against the DHCP client service (running in &lt;code&gt;svchost.exe&lt;/code&gt; as
&lt;code&gt;NetworkService&lt;/code&gt;). That&amp;rsquo;s the &amp;ldquo;authenticated user&amp;rdquo; half of Microsoft&amp;rsquo;s description a local caller pokes
the RPC that reads back the stored mask.&lt;/p&gt;
&lt;p&gt;Put the two halves together and the &amp;ldquo;incongruity&amp;rdquo; dissolves:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;malicious data&lt;/strong&gt; is planted with &lt;strong&gt;no privileges&lt;/strong&gt; by a DHCP server on the link.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;trigger&lt;/strong&gt; that copies it into the undersized buffer is a &lt;strong&gt;local RPC&lt;/strong&gt; call.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both statements in the advisory are true. They describe two different ends of the same data flow. Knowing
this is what turns &amp;ldquo;interesting diff&amp;rdquo; into &amp;ldquo;I understand the bug class and the threat model,&amp;rdquo; which is the
actual skill 1-day analysis is teaching you.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;step-5-how-far-does-this-go-and-what-im-not-publishing&#34;&gt;step 5: how far does this go (and what I&amp;rsquo;m not publishing)&lt;/h2&gt;
&lt;p&gt;The answer from static analysis alone: this is a &lt;strong&gt;controlled-content overflow of a small output
buffer inside a network service&lt;/strong&gt;, reachable with attacker-controlled data and an attacker-controlled
&lt;em&gt;overflow length&lt;/em&gt; (the option length field is a &lt;code&gt;uint&lt;/code&gt;). That is a textbook memory-corruption primitive,
and Microsoft rating it 9.8 RCE is consistent with the destination being a heap/stack buffer adjacent to
something useful in the RPC server context.&lt;/p&gt;
&lt;p&gt;What I am &lt;strong&gt;not&lt;/strong&gt; going to hand you, because the four-byte fix is the kind of thing an LLM can weaponize in
an afternoon and a lot of machines won&amp;rsquo;t have rebooted yet:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the exact DHCP option that lands under internal tag &lt;code&gt;0xDD&lt;/code&gt;, and the precise wire layout that stores an
over-length value there;&lt;/li&gt;
&lt;li&gt;the specific RPC method sequence and arguments that drive the read-back;&lt;/li&gt;
&lt;li&gt;the allocation/grooming details that turn the overflow into control of execution.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If yo are learning, that&amp;rsquo;s fine the value here is the &lt;em&gt;method&lt;/em&gt;, not a copy paste 0day. You now know how
to get the binaries, how to diff them, how to read the patch and how to argue reachability. Reproducing
the corruption in a VM with Driver Verifier / pageheap on the DHCP client service is a great next exercise,
and it stays on your own lab network.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;the-twist-nobody-talks-about-the-vulnerable-code-still-ships&#34;&gt;the twist nobody talks about: the vulnerable code still ships&lt;/h2&gt;
&lt;p&gt;Look at that patched function again:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;g_fVelocityDhcpGetOriginalSubnetMaskFix&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;memcpy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;out&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;...,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;   &lt;span class=&#34;c1&#34;&gt;// the original, unchecked code is STILL HERE
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;len&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;4&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;bail&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;memcpy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;out&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;...,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src=&#34;https://blog.himanshuanand.com/images/Patch_status.jpg&#34; alt=&#34;Patch-status enlightenment, 2026 edition.&#34;&gt;&lt;/p&gt;
&lt;p&gt;The fix is behind a &lt;strong&gt;runtime feature flag&lt;/strong&gt; (&lt;code&gt;g_fVelocityDhcpGetOriginalSubnetMaskFix&lt;/code&gt; a WIL/Velocity
gate). Microsoft increasingly ships security fixes this way so they can roll them out gradually and roll
them &lt;em&gt;back&lt;/em&gt; via KIR (Known Issue Rollback) if the patch breaks something. I found the same pattern on
&lt;em&gt;every&lt;/em&gt; flagship June fix I looked at: &lt;code&gt;tcpip.sys&lt;/code&gt;, &lt;code&gt;http.sys&lt;/code&gt;, &lt;code&gt;win32k&lt;/code&gt;, the RDP client.&lt;/p&gt;
&lt;p&gt;The implication for defenders is uncomfortable: a machine can be fully &amp;ldquo;patched&amp;rdquo; the June &lt;code&gt;dhcpcore.dll&lt;/code&gt;
is on disk and &lt;strong&gt;still run the vulnerable branch&lt;/strong&gt; if the feature isn&amp;rsquo;t enabled yet (staged rollout
fraction, a KIR rollback, or an admin override). &amp;ldquo;Patched&amp;rdquo; and &amp;ldquo;fixed&amp;rdquo; are no longer the same statement.
When you&amp;rsquo;re tracking exposure, you can&amp;rsquo;t just diff the file version anymore; you have to know the flag
state too.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;detection&#34;&gt;detection&lt;/h2&gt;
&lt;p&gt;You can detect this on the wire because the malicious packet has to violate an RFC: a &lt;strong&gt;Subnet Mask option
(DHCP option code 1) whose length byte is not 4&lt;/strong&gt;, sent from a DHCP server to a client. Legitimate servers
never do this. Same idea generalizes to any fixed width option arriving over length in a BOOTP reply.&lt;/p&gt;
&lt;h3 id=&#34;snort--suricata-rule&#34;&gt;Snort / Suricata rule&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;# CVE-2026-44815 DHCP Subnet Mask option (code 1) with illegal length (!= 4)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;# in a BOOTP/DHCP server-&amp;gt;client reply. Subnet mask MUST be 4 octets (RFC 2132 3.3).
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;# An over-length value is the wire artifact of the dhcpcore.dll GetOriginalSubnetMask overflow.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;alert udp $EXTERNAL_NET 67 -&amp;gt; $HOME_NET 68 (
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    msg:&amp;#34;WINDOWS-DHCP rogue server subnet-mask option oversized length (CVE-2026-44815 attempt)&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    content:&amp;#34;|63 82 53 63|&amp;#34;;            # DHCP magic cookie, start of options
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    byte_test:1,&amp;gt;,0,240,relative;       # there is at least one option after the cookie (sanity)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    content:&amp;#34;|01|&amp;#34;;                     # option code 1 = Subnet Mask
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    distance:0;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    byte_test:1,&amp;gt;,4,0,relative;         # length byte immediately after code is &amp;gt; 4  --&amp;gt; illegal
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    detection_filter:track by_src, count 1, seconds 60;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    classtype:attempted-admin;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    reference:cve,2026-44815;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    sid:2026448150; rev:1;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A couple of caveats so you deploy this with your eyes open:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DHCP options are a flat TLV stream; the &lt;code&gt;content:&amp;quot;|01|&amp;quot;&lt;/code&gt; + relative &lt;code&gt;byte_test&lt;/code&gt; approach can mis-fire if
the byte &lt;code&gt;0x01&lt;/code&gt; appears as option &lt;em&gt;data&lt;/em&gt; rather than an option &lt;em&gt;code&lt;/em&gt;. For production, prefer a proper
DHCP parser (Suricata&amp;rsquo;s &lt;code&gt;dhcp&lt;/code&gt; keyword / a Zeek &lt;code&gt;dhcp&lt;/code&gt; analyzer script) that walks options correctly and
alerts when option 1&amp;rsquo;s length field &lt;code&gt;!= 4&lt;/code&gt;. The rule above is the &amp;ldquo;I need something in the IDS today&amp;rdquo;
version.&lt;/li&gt;
&lt;li&gt;Scope it to &lt;strong&gt;server-&amp;gt;client&lt;/strong&gt; (sport 67, dport 68). The bug is in the &lt;em&gt;client&lt;/em&gt;; a normal client never
sends option 1 as a mask, so direction matters.&lt;/li&gt;
&lt;li&gt;Pair it with host telemetry: crashes or WER reports in &lt;code&gt;svchost.exe&lt;/code&gt; hosting &lt;code&gt;Dhcp&lt;/code&gt;, and the
&lt;code&gt;Microsoft-Windows-Dhcp-Client&lt;/code&gt; operational log around lease events.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Zeek one-liner equivalent (concept): in &lt;code&gt;dhcp_message&lt;/code&gt;, if &lt;code&gt;options&lt;/code&gt; contains a subnet-mask option whose
raw length field isn&amp;rsquo;t 4, log it. That&amp;rsquo;s far more robust than byte-matching.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;what-to-take-away-if-youre-starting-1-day-analysis&#34;&gt;what to take away if you&amp;rsquo;re starting 1-day analysis&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Patches are the best vuln reports you&amp;rsquo;ll ever get.&lt;/strong&gt; They tell you the exact function and the exact
missing check. Reading &lt;code&gt;git log&lt;/code&gt; / a binary diff is reading the answer key.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PDBs change everything on Windows.&lt;/strong&gt; Named functions turn a 9000-function haystack into a short list.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Trust your method, not your tools.&lt;/strong&gt; &amp;ldquo;The security patch changed nothing&amp;rdquo; almost always means your
decompiler or your normalization is broken (hello, arm64 Ghidra).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A diff is half the work. Reachability is the other half.&lt;/strong&gt; Always answer &amp;ldquo;who controls the input&amp;rdquo; and
&amp;ldquo;who calls the sink.&amp;rdquo; That&amp;rsquo;s what separates a finding from a daydream and it&amp;rsquo;s what resolved the
CVSS-vs-advisory contradiction here.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TLV + fixed-width field = check the length.&lt;/strong&gt; This bug is &amp;ldquo;copy a server-controlled length into a
4-byte mask.&amp;rdquo; You will meet its cousins constantly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;ldquo;Patched&amp;rdquo; is now a flag state, not a file version.&lt;/strong&gt; Feature-gated fixes mean the vulnerable code can
still be live on an updated box.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And the meta-point the same one I keep hammering: the gap between &amp;ldquo;patch ships&amp;rdquo; and &amp;ldquo;exploit exists&amp;rdquo; is
basically gone. I read this entire bug off a public diff in an afternoon with commodity tools. Assume
someone less friendly did too, the same Tuesday. Patch and &lt;strong&gt;verify the feature flag is actually on&lt;/strong&gt;
now, not next maintenance window.&lt;/p&gt;
&lt;p&gt;Stay safe out there. If you reproduce the corruption in a lab, keep it in the lab.&lt;/p&gt;
</content>
    </item>
    
  </channel>
</rss>
