<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.5">Jekyll</generator><link href="https://valinet.ro/feed.xml" rel="self" type="application/atom+xml" /><link href="https://valinet.ro/" rel="alternate" type="text/html" /><updated>2024-07-23T01:51:37+00:00</updated><id>https://valinet.ro/feed.xml</id><title type="html">valinet</title><subtitle>Engineering and hacking</subtitle><author><name>Valentin Radu</name></author><entry><title type="html">Reserve RAM properly (and easily) on embedded devices running Linux</title><link href="https://valinet.ro/2024/07/23/Proper-and-easy-RAM-reservation-for-embedded-devices-running-Linux.html" rel="alternate" type="text/html" title="Reserve RAM properly (and easily) on embedded devices running Linux" /><published>2024-07-23T00:00:00+00:00</published><updated>2024-07-23T00:00:00+00:00</updated><id>https://valinet.ro/2024/07/23/Proper-and-easy-RAM-reservation-for-embedded-devices-running-Linux</id><content type="html" xml:base="https://valinet.ro/2024/07/23/Proper-and-easy-RAM-reservation-for-embedded-devices-running-Linux.html"><![CDATA[<p>Nowadays, we are surrounded by a popular class of embedded devices: specialized hardware powered by an FPGA which is tightly coupled with a CPU on the same die which most of the times only runs mostly software that configures, facilitates and monitors the design. Such examples are the ZYNQ devices from AMD (formely Xilinx).</p>

<p>Sometimes, the application in question has to share some large quantity of data from the “hardware side” to the “software side”. For example, suppose you have a video pipeline configured in hardware that acquires images from a camera sensor and displays them out over HDMI. The system also has some input device attached, so the user can press some key there (just an example) and have a screenshot saved to the storage medium (an SD card, let’s say). A whole frame is quite big (1920 columns * 1080 lines * 2 color channels bytes, for a 4:2:0 1080p image let’s say means approx. 4.2MB), so wasting hardware resources (like LUT/BRAM) is not really possible.</p>

<p>Most of the times, designs of such systems have shared RAM modules that both sides can access at the same time: the FPGA can write to “some” memory area, and applications running on the CPU can read from that “some” memory area. Thus, the video pipeline could write the frames to RAM as well, and the software is free to grab frames from there whenever the user presses a button. Well, technically a bit after the user requests so, as frame grabbing would have to take place at specific moments (probably when the video pipeline just finished writing a new frame) in order to avoid having the image teared (saving the data when the video pipeline is just writing a new frame), but for the sake of this exercise let’s not bother with that and the fact that the thing would be merely an illusion and not immediate (interrupt style, let’s say).</p>

<p>So the hardware part is actually easy: we’ll have some design that we can configure from software at runtime and tell what physical address in RAM to write to. Done.</p>

<p>The software part is tricky though. Most of the times, ideally, one wants to dedicate all “unused” RAM to the CPU, so there is flexibility for the applications running there. In practice, mostly just an application, since these are not general purpose computing platforms usually, but run custom made “distributions” which only include the (Linux) kernel, the minimum strictly necessary user land and the custom apps that help the system do whatever it has to do. For this goal, <a href="https://buildroot.org/">Buildroot</a> is a popular open way to achieve highly customized, very specialized and lightweight system images. Or such designs forgo Linux entirely and run a single binary bare metal, especially when running real time is a goal. Anyway, the point is, the “coordinating” app is mostly the single process running for the entire lifetime of the system.</p>

<p>Despite that, Linux is not really thought out with that expectation in mind - it reasonably expects a couple of processes to run at any given time. We cannot just pick out some random address in RAM and write to it - there could be processes that were allocated memory there. So what’s to do?</p>

<p>At the moment, I know three solutions, and I will go over each of them and tell you why I picked the last one.</p>

<h2 id="1-limit-the-ram-the-os-sees">1. Limit the RAM the OS ‘sees’</h2>

<p>This is done using the <a href="https://en.wikipedia.org/wiki/Devicetree">devicetree</a>. The hardware is then free to write data there without fearing it is going to hurt something in the software/CPU side. The problem here is that the OS cannot ever access that memory, but this technique works when the hardware needs to buffer data for itself and the CPU doesn’t really need access to it.</p>

<h2 id="2-reserve-some-cma">2. Reserve some CMA</h2>

<p><a href="https://stackoverflow.com/questions/56415606/why-is-contiguous-memory-allocation-is-required-in-linux">Contiguous Memory Allocation</a> is a feature where the kernel reserves some memory area for hardware devices that need support for physical contigous memory. It can be enabled using the device tree or the <code class="language-plaintext highlighter-rouge">cma=128MB</code> boot flag (that one, for example, instructs the kernel to reserve 128MB at the end of the physical memory space for CMA).</p>

<p>The workflow then would be to write a kernel module that requests the amount of CMA memory you want from the kernel, then configure the hardware with the physical address of that. And this approach works just fine. The problem is, you have to write a kernel module, implement communication from the user land with it - probably some <code class="language-plaintext highlighter-rouge">ioctl</code> where you tell the driver how much memory to request from the kernel and via which it tells you back the physical address it got. Certainly doable on a system we fully control, but do we really have to bother with a writing a driver and risk introducing instability in the one area where it hurts the most?</p>

<p>Actually, we can hack away a solution to this: simply pick some address in the CMA region without telling the kernel about it and configure the hardware to use it. From the software side, to access pages in that physical area, you <code class="language-plaintext highlighter-rouge">mmap</code> it using the <code class="language-plaintext highlighter-rouge">/dev/mem</code> device (an in-tree driver which allows full access to the entire visible RAM from the user space), which gives you a virtual address you can work with that points for x number of pages in the physical area you requested. Something like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>uintptr_t phys_addr = 0x17000000;
int size = 1024;
int fd = open("/dev/mem", O_RDWR | O_SYNC);
char* virt_addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, phys_addr);
strcpy(virt_addr, "Hello, world");
// ...
</code></pre></div></div>

<p>Pretty simple, so what’s wrong with it? 2 main things I’d say. Firstly, maybe you do not want access to the entire memory. What if you screw something up and write out of bounds? Yeah, there’s that <code class="language-plaintext highlighter-rouge">size</code> parameter that protects you but still… what if some other process misbehaves, or some unwanted program is loaded…? Access like this to the entire memory is not really desired and not really needed, as you’ll see in a bit. Secondly, and this is the big problem, the kernel knows nothing about what we are doing. We did not tell it we are using memory from there. The CMA is not for us to do what we want with it. It’s a feature the kernel expects to use to service drivers that request memory from it. If you happen to have some driver that requests memory there and you use in your code the starting address of the CMA to do your own stuff as I showed you above, you’re in for some funny times let’s say - things will be screwed up in weird ways.</p>

<p>You can still hack around this: you could query from the user land and find out how much CMA the kernel handed out to requesting parties. It is allocated in number of pages from the beginning. There is no direct syscall to work with the CMA from the user space, as far as I know, since one’s not really supposed to, but there is actually <a href="https://stackoverflow.com/questions/65202674/finding-out-whats-taking-up-the-cma-contiguous-memory-allocation-in-linux">a <code class="language-plaintext highlighter-rouge">debugfs</code> interface</a> which can tell you how much is used, but really… there is no guarantee some device won’t request more. Sure, if you control the hardware and know about it as well, just work with some memory a bit further away from where you expect/see the requests finish at and 99.99% of the time you can get away with this. But it’s… hacky. Ugly. We need something better.</p>

<h2 id="3-properly-reserve-memory">3. Properly reserve memory</h2>

<p>Specifically for this requirement outlines above, the kernel actually has a dedicated mechanism: reserved memory. It’s configured using the device tree, where you introduce an entry that looks like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>	reserved-memory {
        #address-cells = &lt;1&gt;;
        #size-cells = &lt;1&gt;;
        ranges ;

        reserved: buffer@0x2000000 {
            no-map;
            reg = &lt;0x2000000 0x4000000&gt;;
        };
    };
</code></pre></div></div>

<p>What matters in the syntax above is the <code class="language-plaintext highlighter-rouge">reg</code> line, which reads like this: reserve <code class="language-plaintext highlighter-rouge">0x4000000</code> number of bytes starting from address <code class="language-plaintext highlighter-rouge">0x2000000</code>. This is on 32-bit. On 64-bit, there are 4 numbers, the first and third one the high part of the 64-bit number (so it looks something like this: <code class="language-plaintext highlighter-rouge">reg = &lt;0 0x2000000 0 0x4000000&gt;</code>). Correct values for <code class="language-plaintext highlighter-rouge">#address-cells</code> and <code class="language-plaintext highlighter-rouge">#size-cells</code> can be determined from the documentation of your SoC (or examples you find online/the provided examples of your platform).</p>

<p>At runtime, that simply divides the system RAM in 2 regions, and the OS can use either part but not the reservation that you made in the middle. You are guaranteed that, that’s great. Even better, it’s also excluded from tools like <code class="language-plaintext highlighter-rouge">htop</code>, as opposed to the CMA approach. If you check <code class="language-plaintext highlighter-rouge">/proc/iomem</code>, it looks something like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>00000000-01ffffff : System RAM
  00008000-007fffff : Kernel code
  00900000-00951eef : Kernel data
06000000-1fffffff : System RAM
44a01000-44a0ffff : serial
...
</code></pre></div></div>

<p>And, how do you access that from the user space? Well, at this point, still with <code class="language-plaintext highlighter-rouge">/dev/mem</code>, so we only solved half of the problem. For the other half, we can employ a safe hack this time: overlay a <code class="language-plaintext highlighter-rouge">generic-uio</code> device over it (again, using the device tree), which is a simple driver which just maps memory to user space of a certain size. That size and physical address is, you guessed it, specified in the device tree. In the user land, it is accessible at <code class="language-plaintext highlighter-rouge">/sys/class/uio/...</code>.</p>

<p>Finally, the device tree additions are these:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>	reserved-memory {
        #address-cells = &lt;1&gt;;
        #size-cells = &lt;1&gt;;
        ranges ;

        reserved: buffer@0x2000000 {
            no-map;
            reg = &lt;0x2000000 0x4000000&gt;;
        };
    };
	reserved_memory1@0x2000000 {
		compatible = "generic-uio";
		reg = &lt;0x2000000 0x4000000&gt;;
	};
</code></pre></div></div>

<p>From the user land, you access that memory like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>int size;
int fd = open("/dev/uio0", O_RDWR);
char* virt_addr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
strcpy(virt_addr, "Hello, world");
// ...
</code></pre></div></div>

<p>The code does not really need to have knowledge in advance about the physical address of the memory area or its size. These can be queried directly from properties exposed by the UIO device:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># cat /sys/class/uio/uio0/maps/map0/addr
# cat /sys/class/uio/uio0/maps/map0/size
</code></pre></div></div>

<p>And each uio has a <code class="language-plaintext highlighter-rouge">name</code> file associated which contains the name you specified in the device tree. You can read those for each folder in <code class="language-plaintext highlighter-rouge">/sys/class/uio</code> to learn which entry in there coresponds to which device you have set up in your device tree:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># cat /sys/class/uio/uio0/name
reserved_memory1
</code></pre></div></div>

<h2 id="useful-links">Useful links</h2>

<p><a href="https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18841683/Linux+Reserved+Memory">Linux Reserved Memory</a></p>

<p><a href="https://support.xilinx.com/s/question/0D52E00006hpK3vSAE/device-tree-reserve-memory-invalid-reg-property">Device tree Reserve Memory “Invalid reg property”</a></p>]]></content><author><name>Valentin Radu</name></author><summary type="html"><![CDATA[Nowadays, we are surrounded by a popular class of embedded devices: specialized hardware powered by an FPGA which is tightly coupled with a CPU on the same die which most of the times only runs mostly software that configures, facilitates and monitors the design. This article showcases a (rather old) technique which enables easy data sharing between hardware and software using the RAM.]]></summary></entry><entry><title type="html">Access valinet.ro over the Tor network</title><link href="https://valinet.ro/2024/06/16/Access-valinet-over-the-Tor-network.html" rel="alternate" type="text/html" title="Access valinet.ro over the Tor network" /><published>2024-06-16T00:00:00+00:00</published><updated>2024-06-16T00:00:00+00:00</updated><id>https://valinet.ro/2024/06/16/Access-valinet-over-the-Tor-network</id><content type="html" xml:base="https://valinet.ro/2024/06/16/Access-valinet-over-the-Tor-network.html"><![CDATA[<p>It is my pleasure to announce that, for almost 2 weeks now, valinet.ro is available over the Tor network as well, at <a href="https://valinet6l6tq6d5yohaa6gdsf2ho4qcqcxyj2nahp5kd4z7nsa6ycdqd.onion/">https://valinet6l6tq6d5yohaa6gdsf2ho4qcqcxyj2nahp5kd4z7nsa6ycdqd.onion/</a>. I have presented this setup in an assignment for one of the classes I take in uni, so here’s the write-up that I have made for it.</p>

<p>Setup consists of an Alpine Linux-based VM which runs the tor server service and an Nginx Proxy Manager docker container which proxies the actual web site which, as you know, is publicly available on <a href="https://github.com/valinet/valinet.github.io">GitHub</a>. This way, I do not have to “build” the web site in 2 places (I use a static site generator), it is still hosted on GitHub Pages and relayed through that proxy to the clients accessing it via Tor.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Client =&gt; Tor network =&gt; Tor server on VM =&gt; nginx reverse proxy =&gt; github.io
</code></pre></div></div>

<p>I chose Alpine because it is lightweight, easy to setup and manage and I am familiar with it, and Nginx Proxy Manager because it is just easier to use a GUI to configure everything rather than nginx config files.</p>

<p>The config for <code class="language-plaintext highlighter-rouge">/etc/tor/torrc</code> looks like this (based on <a href="https://medium.com/axon-technologies/hosting-anonymous-website-on-tor-network-3a82394d7a01">this</a>):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HiddenServiceDir /var/lib/tor/hidden_service
HiddenServiceVersion 3
HiddenServicePort 80 127.0.0.1:80
HiddenServicePort 443 127.0.0.1:443
</code></pre></div></div>

<p>The nginx docker container is configured to only listen on localhost; anyway, the VM runs in an isolated network anyway (I control the hypervisor and the infrastructure there - it’s running on a Proxmox cluster at my workplace), and the web site is also available publicly via the clearnet anyway, so I do not really care if some may hypothetically access it bypassing the Tor network; that’s why I decided running it over unix sockets was not worth the hassle, but if you want that, <a href="https://kushaldas.in/posts/using-onion-services-over-unix-sockets-and-nginx.html">check this out</a>.</p>

<p>For the Nginx Proxy Manager config for the web site, besides what is offered through the GUI (forwarding valinet…onion to https valinet.ro 443), I have also set a custom location for “/”, again that directs to https valinet.ro 443, which allows me to set custom nginx directives. This is what I used:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>proxy_set_header Host valinet.ro;
proxy_redirect https://valinet.ro/ https://valinet6l6tq6d5yohaa6gdsf2ho4qcqcxyj2nahp5kd4z7nsa6ycdqd.onion/;
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">proxy_set_header</code> is necessary so that GitHub knows which web site it hosts we want fetched (a lot of web sites are “stored” at the same IP). Without this, of course GitHub returns a 404 Not Found error.</p>

<p><code class="language-plaintext highlighter-rouge">proxy_redirect</code> sets the text that is changed in the “Location” and “Refresh” headers. Basically, this changes the address from “valinet.ro” to “valinet…onion”.</p>

<p>To make the address look nice, following <a href="https://opensource.com/article/19/8/how-create-vanity-tor-onion-address">this</a>, I have used <a href="https://github.com/cathugger/mkp224o.git">mkp224o</a> and generated a few addresses and picked up the one that I liked the most. On a powerful AMD Ryzen 5900x-based machine it only took around 15-20 minutes to generate 3 options to choose from. It was not initially in plan, but I saw this on Kushal’s web site and I have decided I must also have that.</p>

<p>The Tor Project has a <a href="https://community.torproject.org/onion-services/">web page</a> showing the addresses of some well known services, like The New York Times, Facebook etc. Some of these run over https, and I thought it wouldn’t hurt to run my service over https as well for extra swag.</p>

<p>According to <a href="https://kushaldas.in/posts/get-a-tls-certificate-for-your-onion-service.html">this</a>, there are only 2 providers of certificates for .onion address: Digicert which is expensive through the roof, and HARICA (Hellenic Academic and Research Institutions Certificate Authority) which has more normal prices. The setup was very easy, I verified by generating a CSR signed with my service’s key as described in their instructions using <a href="https://github.com/HARICA-official/onion-csr">an open tool they host</a> and got my certificates. Then, uploading and associating them with the domain was a breeze through Nginx Proxy Manager’s GUI.</p>

<p>In the end, you can also use Nginx Proxy Manager to hide certain headers that you do not want proxied, using directives like:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>add_header Server custom;
proxy_hide_header ETag;
</code></pre></div></div>

<p>This is it, happy onioning!</p>]]></content><author><name>Valentin Radu</name></author><summary type="html"><![CDATA[It is my pleasure to announce that, for almost 2 weeks now, valinet.ro is available over the Tor network as well, at https://valinet6l6tq6d5yohaa6gdsf2ho4qcqcxyj2nahp5kd4z7nsa6ycdqd.onion/. I have presented this setup in an assignment for one of the classes I take in uni, so here's the write-up that I have made for it.]]></summary></entry><entry><title type="html">Convert Alpine Linux containers to VMs</title><link href="https://valinet.ro/2024/06/01/Convert-Alpine-Linux-container-to-VM.html" rel="alternate" type="text/html" title="Convert Alpine Linux containers to VMs" /><published>2024-06-01T00:00:00+00:00</published><updated>2024-06-01T00:00:00+00:00</updated><id>https://valinet.ro/2024/06/01/Convert-Alpine-Linux-container-to-VM</id><content type="html" xml:base="https://valinet.ro/2024/06/01/Convert-Alpine-Linux-container-to-VM.html"><![CDATA[<p>Lately I have decided to migrate away from Proxmox containers to full blown virtual machines, due to reasons like the inability of containers to host Docker inside the root volume when using zfs, so having to resort to all kinds of shenanigans, like storing <code class="language-plaintext highlighter-rouge">/var/lib/docker</code> in a separate xfs volume which nukes the built-in high availability, failover and hyperconvergence (using Ceph) features for that container, plus wanting to make the setup more portable and less dependant on the underlaying OS - with a VM, you basically copy the virtual disk and that’s it, and if you write that on physical media it boots and works just as expected, whereas containers only run on top of an underlaying OS like Proxmox. Anyway, despite <a href="https://forum.proxmox.com/threads/proxmox-8-1-zfs-2-2-docker-in-privileged-container.137128/">recent progress</a>, mainly Docker in proxmox CTs on ZFS is not that recommended, whereas running them in VMs is fine.</p>

<p>Here’re the steps, with little comments where relevant:</p>

<ol>
  <li>
    <p>Create a new VM with the desired features in Proxmox. Attach Arch Linux or any Linux live CD to it. Make sure to add a serial port to it, so we can later enable xterm.js on it (allows copy/paste), in addition to the noVNC console.</p>
  </li>
  <li>
    <p>Enable ssh (and allow root authentication with password) in the Alpine Linux CT, install rsync on it and kill all running Dockers/services inside it.</p>
  </li>
  <li>
    <p>Boot Arch Linux on the new VM.</p>
  </li>
  <li>
    <p>Format the disk inside the VM according to your needs. For my setup, I created a 100MB EFI partition and the rest as ext4 for the file system. That <code class="language-plaintext highlighter-rouge">tunr2fs</code> command is neccessary in order to fix the <a href="https://www.linuxquestions.org/questions/slackware-14/grub-install-error-unknown-filesystem-4175723528/"><code class="language-plaintext highlighter-rouge">unsupported filesystem</code> error</a> which would occur later on when we install grub.</p>
  </li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wipefs -a /dev/sda
cfdisk /dev/sda
mkfs.vfat -F32 /dev/sda1
mkfs.ext4 -O "^has_journal,^64bit" /dev/sda2
tune2fs -O "^metadata_csum_seed" /dev/sda2
</code></pre></div></div>

<ol>
  <li>Mount the new partitions.</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mount /dev/sda2 /mnt
mkdir -p /mnt/boot/efi
mount /dev/sda1 /mnt/boot/efi
</code></pre></div></div>

<ol>
  <li>Optionally, asign an IP address for your Arch Linux live CD, if your netowrk is not running DHCP (in the example, <code class="language-plaintext highlighter-rouge">10.2.2.222</code> is the IP for the booted Arch Linux, and <code class="language-plaintext highlighter-rouge">10.2.2.1</code> is the IP of the gateway).</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ip address add 10.2.2.222/24 broadcast + dev enp6s18
ip route add default via 10.2.2.1 dev enp6s18
</code></pre></div></div>

<ol>
  <li>Copy the actual files from the container to the new disk, excluding special stuff like kernel services exposed via the file system etc (in the example, <code class="language-plaintext highlighter-rouge">10.2.2.111</code> is the IP of the Alpine Linux container).</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rsync -ahPHAXx --delete --exclude="{/dev/*,/proc/*,/sys/*,/tmp/*,/run/*,/mnt/*,/media/*,/lost+found}" root@10.2.2.111:/ /mnt/
</code></pre></div></div>

<p>Alternatively, here you could set up the file system with fresh files from <code class="language-plaintext highlighter-rouge">minirootfs</code>, a 2MB base system image of Alpine Linux destined for containers - we later on update it with the minimum required to boot on bare metal, a la Arch Linux.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget -O- https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/x86_64/alpine-minirootfs-3.20.0-x86_64.tar.gz | tar -C /mnt -xzpf -
</code></pre></div></div>

<ol>
  <li>Bind mount live kernel services to the just copyied file system, generate a correct fstab and chroot into the new file system.</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>for fs in dev dev/pts proc run sys tmp; do mount -o bind /$fs /mnt/$fs; done
genfstab /mnt &gt;&gt; /mnt/etc/fstab
chroot /mnt /bin/sh -l
</code></pre></div></div>

<ol>
  <li>Install and update base Alpine Linux packages needed. For example, we now need a kernel, since we won’t be using the kernel of the hypervisor anymore.</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apk add --update alpine-base linux-lts grub grub-efi efibootmgr
</code></pre></div></div>

<ol>
  <li>Install grub on the new EFi partition. For some reason, EFI variables and commands di not work under my Proxmox VM, so <code class="language-plaintext highlighter-rouge">efibootmgr</code> is unable to set a boot entry. However, simply placing the bootloader in <code class="language-plaintext highlighter-rouge">/EFI/Boot/bootx64.efi</code> will have the UEFI boot it by default, so that’s my workaround.</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>grub-install --target=x86_64-efi --efi-directory=/boot/efi
mkdir -p /boot/efi/EFI/Boot
cp /boot/efi/EFI/alpine/grupx64.efi /boot/efi/EFI/Boot/bootx64.efi
</code></pre></div></div>

<ol>
  <li>Again, with my setup, grub won’t boot if <code class="language-plaintext highlighter-rouge">root=</code> is specified using a <code class="language-plaintext highlighter-rouge">PARTUUID</code>. My workaround is to explicitly use <code class="language-plaintext highlighter-rouge">/dev/sda2</code>. For this, edit the <code class="language-plaintext highlighter-rouge">/boot/grub/grub.cfg</code> file. Also, add <code class="language-plaintext highlighter-rouge">modules=ext4 rootfstype=ext4</code> to that line, otherwise the bootloader doesn’t recognize the filesystem of the root partition. Lately, also specify <code class="language-plaintext highlighter-rouge">console=tty0 console=ttyS0,115200 earlyprintk=ttyS0,115200 consoleblank=0</code> in order to enable system messages over the serial port we have added earlier. You basically have to go from something like this:</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>linux /boot/vmlinuz-lts root=PARTUUID=84035c78-3729-4cc9-b1c5-d34d1189cefd ro
</code></pre></div></div>

<p>Replace it with:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>linux /boot/vmlinuz-lts root=/dev/sda2 ro modules=ext4 rootfstype=ext4 console=tty0 console=ttyS0,115200
</code></pre></div></div>

<ol>
  <li>
    <p>Some bootstraper scripts/templates for this containers set the system type to <code class="language-plaintext highlighter-rouge">lxc</code>. This has the effect of skipping various things at startup that are not required in a container environment, like checking the disks for errors, which is needed for remounting <code class="language-plaintext highlighter-rouge">/</code> as read write. Without undoing this, you will have problems booting Alpine Linux because the root file system will be read only. I have spent a ton of time figuring this out. You need to edit the <code class="language-plaintext highlighter-rouge">/etc/rc.conf</code> file and comment out <code class="language-plaintext highlighter-rouge">rc_sys="lxc"</code>.</p>
  </li>
  <li>
    <p>Install some aditional tools that only make sense not in a container; for example, file systems need to be checked at boot - for ext4, <code class="language-plaintext highlighter-rouge">mkfs.ext4</code> is in <code class="language-plaintext highlighter-rouge">e2fsprogs</code>, while for FAT32 (EFI partition), <code class="language-plaintext highlighter-rouge">mkfs.vfat</code> is in <code class="language-plaintext highlighter-rouge">dosfstools</code>. You also need <code class="language-plaintext highlighter-rouge">qemu-guest-agent</code> in order for the shutdown command to gracefully shut down the virtualized operating system and only then turn off the VM.</p>
  </li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apk add chrony qemu-guest-agent e2fsprogs dosfstools
</code></pre></div></div>

<ol>
  <li>Enable the following services. These are a mirror of what an actual install of regular Alpine Linux has enabled. You may be able to skip some of those, although I haven’t tried.</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rc-update add qemu-guest-agent
rc-update add acpid default
rc-update add chronyd default
rc-update add devfs sysinit
rc-update add dmesg sysinit
rc-update add hwclock boot
rc-update add hwdrivers sysinit
rc-update add loadkmap boot
rc-update add mdev sysinit
rc-update add modules boot
rc-update add mount-ro shutdown
rc-update add swap boot
rc-update add sysctl boot
rc-update add urandom boot
</code></pre></div></div>

<p>If <code class="language-plaintext highlighter-rouge">rc-update add urandom boot</code> doesn’t work, please note that is is called <code class="language-plaintext highlighter-rouge">seedrng</code> in newer versions of Alpine Linux, so use <code class="language-plaintext highlighter-rouge">rc-update add seedrng boot</code> instead.</p>

<p>You can display the status of all services using <code class="language-plaintext highlighter-rouge">rc-update show -v | less</code>.</p>

<ol>
  <li>Boot and enjoy. If you did not enable the serial console, you may find that the first tty doesn’t work, or rather it duplicates output. A workaround is to use <code class="language-plaintext highlighter-rouge">Ctrl+Alt+F2</code> to switch to an alternative tty.</li>
</ol>

<h3 id="references">References</h3>

<ol>
  <li><a href="https://www.reddit.com/r/AlpineLinux/comments/13ochmy/how_to_make_mini_rootfs_bootable/">How to make mini rootfs bootable? : r/AlpineLinux</a></li>
  <li><a href="https://forum.proxmox.com/threads/convert-proxmox-lxc-to-a-regular-vm.141687/">Convert Proxmox LXC to a regular VM - Proxmox Support Forum</a></li>
  <li><a href="https://forum.proxmox.com/threads/migrate-lxc-to-kvm.56298/">Migrate LXC to KVM - Proxmox Support Forum</a></li>
  <li><a href="https://wiki.archlinux.org/title/Network_configuration">Network configuration - ArchWiki</a></li>
  <li><a href="https://forum.proxmox.com/threads/proxmox-8-1-zfs-2-2-docker-in-privileged-container.137128/">Proxmox 8.1 / ZFS 2.2: Docker in Privileged Container - Proxmox Support Forum</a></li>
  <li><a href="https://www.linuxquestions.org/questions/slackware-14/grub-install-error-unknown-filesystem-4175723528/">SOLVED grub-install: error: unknown filesystem</a></li>
  <li><a href="https://superuser.com/questions/709176/how-to-best-clone-a-running-system-to-a-new-harddisk-using-rsync">ubuntu - How to best clone a running system to a new harddisk using rsync? - Super User</a></li>
</ol>]]></content><author><name>Valentin Radu</name></author><summary type="html"><![CDATA[Lately I have decided to migrate away from Proxmox containers to full blown virtual machines, due to reasons like the inability of containers to host Docker inside the root volume when using zfs, so having to resort to all kinds of shenanigans, like storing `/var/lib/docker` in a separate xfs volume which nukes the built-in high availability, failover and hyperconvergence (using Ceph) features for that container, plus wanting to make the setup more portable and less dependant on the underlaying OS.]]></summary></entry><entry><title type="html">Install Intel Unison on Windows 11 22000-based builds</title><link href="https://valinet.ro/2023/01/03/Install-Intel-Unison-on-Windows-11-22000-based-builds.html" rel="alternate" type="text/html" title="Install Intel Unison on Windows 11 22000-based builds" /><published>2023-01-03T00:00:00+00:00</published><updated>2023-01-03T00:00:00+00:00</updated><id>https://valinet.ro/2023/01/03/Install-Intel-Unison-on-Windows-11-22000-based-builds</id><content type="html" xml:base="https://valinet.ro/2023/01/03/Install-Intel-Unison-on-Windows-11-22000-based-builds.html"><![CDATA[<p>From the ashes of Dell Mobile Connect, which was based on Screenovate technologies which were purchased by Intel last year, a similar app with a different name has arrived - this is a quick guide showing you how to install it on 21H2 (22000-based) Windows 11 builds, despite it only officially working on 22H2 (22621-based) builds and up.</p>

<p>The problem: if you attempt to install the app via the Microsoft Store on 22000-based builds, it will tell you that the app is not supported, suggesting you upgrade to 22H2. If you cannot or do not have time to do that, as a workaround, I suggest this:</p>

<ol>
  <li>
    <p>Source the application. For this, I recommend using the <a href="https://store.rg-adguard.net/">adguard store</a>: from the drop down, choose “ProductId” and as product ID, use “9PP9GZM2GN26”. Choose the retail channel, click the checkbox, wait a bit, and then download the <code class="language-plaintext highlighter-rouge">AppUp.IntelTechnologyMDE_2.2.2315.0_neutral_~_8j3eq9eme6ctt.msixbundle</code> file.</p>
  </li>
  <li>
    <p>Using 7zip or something similar, extract the file as if it were a regular archive file. In the resulting folder, you’ll get a <code class="language-plaintext highlighter-rouge">WebPhonePackaging_2.2.2315.0_x64.msix</code> file. Repeat the process, unzipping that as well.</p>
  </li>
  <li>
    <p>In the resulting folder, identify and delete the <code class="language-plaintext highlighter-rouge">AppxMetadata</code> folder, and the <code class="language-plaintext highlighter-rouge">[Content_Types].xml</code>, <code class="language-plaintext highlighter-rouge">AppxBlockMap.xml</code>, and <code class="language-plaintext highlighter-rouge">AppxSignature.p7x</code> files.</p>
  </li>
  <li>
    <p>Edit the <code class="language-plaintext highlighter-rouge">AppxManifest.xml</code> file. Identify the line which mentions OS build 22621 as minimum, and replace it to specify build 22000 instead as a minimum. So, turn line 11 from:</p>
  </li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    &lt;TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.22621.0" MaxVersionTested="10.0.22621.0" /&gt;
</code></pre></div></div>

<p>To:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    &lt;TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.22000.0" MaxVersionTested="10.0.22000.0" /&gt;
</code></pre></div></div>

<ol>
  <li>
    <p>Enable developer mode on your PC. For this, open Windows Settings -&gt; Privacy &amp; Security -&gt; For developers -&gt; Developer Mode -&gt; ON. This will enable sideloading unsigned apps on your PC. The Intel Unison app’s signature is broken since we edited the <code class="language-plaintext highlighter-rouge">AppxManifest.xml</code> file, and we removed it altogether by deleting the folder and mentioned files in step 3.</p>
  </li>
  <li>
    <p>Open an administrative PowerShell window and browse to the folder containing the <code class="language-plaintext highlighter-rouge">AppxManifest.xml</code> file we have just edited in step 4. When in that directory, issue the following command in order to install the application on your system:</p>
  </li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Add-AppxPackage -Register .\AppxManifest.xml
</code></pre></div></div>

<p>That’s it! You should now be able to find the app in your Start menu, and after launhing it, it is business as usual - I was able to use it to sync my iPhone to my PC, make calls and so on, almost as if I were doing the same thing with a Mac. Certainly better than nothing, that’s for sure - it makes owning a PC, yet Apple devices otherwise a less clunky experience.</p>

<p>The way it works is it sets up your PC as if it were a Bluetooth handsfree device - your phone ‘sees’ the PC as if it were your tpical car stereo. It’s a fine way to do it, and I am amazed that it takes a multti-million dollar company to attempt this, instead of a hobby, open source project hosted on GitHub. The things is doable at that scale in my opinion; it could open the door to neat implementations, like having a Windows or Android based tablet as a car stereo, whihc of course comes with plenty advantages, like having a software package which you are more in control of, compared to what car manufacturers offer. But yeah, maybe another day… I am sure there are clunky Chinese implementations on Aliexpress already, but those aren’t as polished as what a DIY solution could achieve.</p>

<p>If you get an error on step 6, you might need to install the other prerequiste packages for the app to work. These are offered on the adguard store as well: just manually download the relevant package (like <code class="language-plaintext highlighter-rouge">Microsoft.VCLibs.140.00.UWPDesktop_14.0.30704.0_x64__8wekyb3d8bbwe.appx</code> for example) and install it by double clicking it in File Explorer. If you do not have the App Installer app installed on your Windows install, you can alternatively open an administrative PowerShell window where you have downloaded the file and issue a command like this in order to install the package:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Add-AppxPackage .\Microsoft.VCLibs.140.00.UWPDesktop_14.0.30704.0_x64__8wekyb3d8bbwe.appx
</code></pre></div></div>

<p>Happy New Year, 2023!</p>]]></content><author><name>Valentin Radu</name></author><summary type="html"><![CDATA[From the ashes of Dell Mobile Connect, which was based on Screenovate technologies which were purchased by Intel last year, a similar app with a different name has arrived - this is a quick guide showing you how to install it on 21H2 (22000-based) Windows 11 builds, despite it only officially working on 22H2 (22621-based) builds and up.]]></summary></entry><entry><title type="html">Working with shadow copies is kind of broken on Windows 11 22H2</title><link href="https://valinet.ro/2022/08/29/Working-with-shadow-copies-is-kind-of-broken-on-Windows-11-22H2.html" rel="alternate" type="text/html" title="Working with shadow copies is kind of broken on Windows 11 22H2" /><published>2022-08-29T00:00:00+00:00</published><updated>2022-08-29T00:00:00+00:00</updated><id>https://valinet.ro/2022/08/29/Working-with-shadow-copies-is-kind-of-broken-on-Windows-11-22H2</id><content type="html" xml:base="https://valinet.ro/2022/08/29/Working-with-shadow-copies-is-kind-of-broken-on-Windows-11-22H2.html"><![CDATA[<p>As a continuation of my <a href="https://valinet.ro/2022/08/28/Shadow-copies-under-Windows-10-and-11.html">previous article</a> on shadow copies in Windows, I investigate how they behave under 22621+-based builds of Windows 11 (version 22H2, yet to be released).</p>

<p>Under build 22622, the Previous Versions tab displays no snapshots:</p>

<p><img src="https://user-images.githubusercontent.com/6503598/187235227-9ff0c900-e6bb-4195-b3a3-f7fa110d6cef.png" alt="Previous Versions tab on 22622-based builds" /></p>

<p>Attaching with a debugger and looking on the disassembly of <code class="language-plaintext highlighter-rouge">twext.dll</code>, the component that hosts the “Previous Versions” tab, I found out that the following call to <code class="language-plaintext highlighter-rouge">NtFsControlFile</code> errors out:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">__int64</span> <span class="kr">__fastcall</span> <span class="nf">IssueSnapshotControl</span><span class="p">(</span><span class="n">__int64</span> <span class="n">a1</span><span class="p">,</span> <span class="n">_DWORD</span> <span class="o">*</span><span class="n">a2</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">a3</span><span class="p">)</span>
<span class="p">{</span>
  <span class="n">HANDLE</span> <span class="n">EventW</span><span class="p">;</span> <span class="c1">// rsi</span>
  <span class="kt">int</span> <span class="n">v7</span><span class="p">;</span> <span class="c1">// ebx</span>
  <span class="kt">unsigned</span> <span class="n">__int64</span> <span class="n">v8</span><span class="p">;</span> <span class="c1">// r8</span>
  <span class="kt">signed</span> <span class="kt">int</span> <span class="n">v9</span><span class="p">;</span> <span class="c1">// ecx</span>
  <span class="kt">signed</span> <span class="kt">int</span> <span class="n">v11</span><span class="p">;</span> <span class="c1">// eax</span>
  <span class="kt">signed</span> <span class="kt">int</span> <span class="n">LastError</span><span class="p">;</span> <span class="c1">// eax</span>
  <span class="kt">int</span> <span class="n">v13</span><span class="p">;</span> <span class="c1">// [rsp+50h] [rbp-18h] BYREF</span>
  <span class="n">wil</span><span class="o">::</span><span class="n">details</span><span class="o">::</span><span class="n">in1diag3</span> <span class="o">*</span><span class="n">retaddr</span><span class="p">;</span> <span class="c1">// [rsp+68h] [rbp+0h]</span>

  <span class="n">memset_0</span><span class="p">(</span><span class="n">a2</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">a3</span><span class="p">);</span>
  <span class="n">EventW</span> <span class="o">=</span> <span class="n">CreateEventW</span><span class="p">(</span><span class="mi">0</span><span class="n">i64</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="n">i64</span><span class="p">);</span>
  <span class="k">if</span> <span class="p">(</span> <span class="n">EventW</span> <span class="p">)</span>
  <span class="p">{</span>
    <span class="n">v7</span> <span class="o">=</span> <span class="n">NtFsControlFile</span><span class="p">(</span><span class="n">a1</span><span class="p">,</span> <span class="n">EventW</span><span class="p">,</span> <span class="mi">0</span><span class="n">i64</span><span class="p">,</span> <span class="mi">0</span><span class="n">i64</span><span class="p">,</span> <span class="p">...,</span> <span class="n">FSCTL_GET_SHADOW_COPY_DATA</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">a2</span><span class="p">,</span> <span class="n">a3</span><span class="p">);</span>
<span class="p">...</span>
</code></pre></div></div>

<p>This is called when the window tries to enumerate all snapshots in the system. The call stack looks something like this:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">CTimewarpResultsFolder</span><span class="o">::</span><span class="n">EnumSnapshots</span>
<span class="n">CTimewarpResultsFolder</span><span class="o">::</span><span class="n">_AddSnapshotShellItems</span>
<span class="n">SHEnumSnapshotsForPath</span>
<span class="n">QuerySnapshotNames</span>
<span class="n">IssueSnapshotControl</span>
</code></pre></div></div>

<p>Of note is that during <code class="language-plaintext highlighter-rouge">CTimewarpResultsFolder::EnumSnapshots</code>, the extension also loads in items corresponding to the following, in addition to shadow copy snapshots:</p>

<ul>
  <li>File History (<code class="language-plaintext highlighter-rouge">AddFileHistoryShellItems</code>)</li>
  <li>Windows 7 Backup (<code class="language-plaintext highlighter-rouge">AddSafeDocsShellItems</code>)</li>
</ul>

<p>At first, I thought that the device call no longer supports being called with insufficient space in the buffer (the first call to this <code class="language-plaintext highlighter-rouge">IssueSnapshotControl</code> has the buffer of size <code class="language-plaintext highlighter-rouge">0x10</code>, enough for the call to populate it with information about the space it requires to contain all the data; you can read more about this device IO control call <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntfscontrolfile">here</a>, <a href="https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb/5a43eb29-50c8-46b6-8319-e793a11f6226">here</a>, and <a href="https://wiki.wireshark.org/SMB2/Ioctl/Function/FILE_DEVICE_NETWORK_FILE_SYSTEM/FSCTL_GET_SHADOW_COPY_DATA">here</a>).</p>

<p>This being said, I quickly scratched out a project in Visual Studio where I set a large enough buffer. Unfortunately, this still did not produce the expected result. The call still said there are no snapshots available despite being offered enough space in the buffer to copy the names there.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="n">HRESULT</span> <span class="n">hr</span> <span class="o">=</span> <span class="n">S_OK</span><span class="p">;</span>
    <span class="n">DWORD</span> <span class="n">sz</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="s">L"@GMT-YYYY.MM.DD-HH.MM.SS"</span><span class="p">);</span>

    <span class="n">HANDLE</span> <span class="n">hFile</span> <span class="o">=</span> <span class="n">CreateFileW</span><span class="p">(</span><span class="s">L"</span><span class="se">\\\\</span><span class="s">localhost</span><span class="se">\\</span><span class="s">C$"</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="p">(</span><span class="n">FILE_SHARE_READ</span> <span class="o">|</span> <span class="n">FILE_SHARE_WRITE</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">,</span> <span class="p">(</span><span class="n">CREATE_NEW</span> <span class="o">|</span> <span class="n">CREATE_ALWAYS</span><span class="p">),</span> <span class="n">FILE_FLAG_BACKUP_SEMANTICS</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">hFile</span> <span class="o">||</span> <span class="n">hFile</span> <span class="o">==</span> <span class="n">INVALID_HANDLE_VALUE</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"CreateFileW: 0x%x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">GetLastError</span><span class="p">());</span>
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="n">HANDLE</span> <span class="n">hEvent</span> <span class="o">=</span> <span class="n">CreateEventW</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="n">TRUE</span><span class="p">,</span> <span class="n">FALSE</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">hEvent</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"CreateEventW: 0x%x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">GetLastError</span><span class="p">());</span>
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">typedef</span> <span class="k">struct</span> <span class="nc">_IO_STATUS_BLOCK</span> <span class="p">{</span>
        <span class="k">union</span> <span class="p">{</span>
            <span class="n">NTSTATUS</span> <span class="n">Status</span><span class="p">;</span>
            <span class="n">PVOID</span>    <span class="n">Pointer</span><span class="p">;</span>
        <span class="p">};</span>
        <span class="n">ULONG_PTR</span> <span class="n">Information</span><span class="p">;</span>
    <span class="p">}</span> <span class="n">IO_STATUS_BLOCK</span><span class="p">,</span> <span class="o">*</span> <span class="n">PIO_STATUS_BLOCK</span><span class="p">;</span>
    <span class="n">NTSTATUS</span><span class="p">(</span><span class="o">*</span><span class="n">NtFsControlFile</span><span class="p">)(</span>
        <span class="n">HANDLE</span>           <span class="n">FileHandle</span><span class="p">,</span>
        <span class="n">HANDLE</span>           <span class="n">Event</span><span class="p">,</span>
        <span class="n">PVOID</span>  <span class="n">ApcRoutine</span><span class="p">,</span>
        <span class="n">PVOID</span>            <span class="n">ApcContext</span><span class="p">,</span>
        <span class="n">PIO_STATUS_BLOCK</span> <span class="n">IoStatusBlock</span><span class="p">,</span>
        <span class="n">ULONG</span>            <span class="n">FsControlCode</span><span class="p">,</span>
        <span class="n">PVOID</span>            <span class="n">InputBuffer</span><span class="p">,</span>
        <span class="n">ULONG</span>            <span class="n">InputBufferLength</span><span class="p">,</span>
        <span class="n">PVOID</span>            <span class="n">OutputBuffer</span><span class="p">,</span>
        <span class="n">ULONG</span>            <span class="n">OutputBufferLength</span>
        <span class="p">)</span> <span class="o">=</span> <span class="n">GetProcAddress</span><span class="p">(</span><span class="n">GetModuleHandleW</span><span class="p">(</span><span class="s">L"ntdll"</span><span class="p">),</span> <span class="s">"NtFsControlFile"</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">NtFsControlFile</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"GetProcAddress: 0x%x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">GetLastError</span><span class="p">());</span>
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="n">NTSTATUS</span> <span class="n">rv</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="n">DWORD</span> <span class="n">max_theoretical_size</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">DWORD</span><span class="p">)</span> <span class="o">+</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">DWORD</span><span class="p">)</span> <span class="o">+</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">DWORD</span><span class="p">)</span> <span class="o">+</span> <span class="mi">512</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="s">L"@GMT-YYYY.MM.DD-HH.MM.SS"</span><span class="p">)</span> <span class="o">+</span> <span class="k">sizeof</span><span class="p">(</span><span class="s">L""</span><span class="p">);</span>
    <span class="kt">char</span><span class="o">*</span> <span class="n">buff</span> <span class="o">=</span> <span class="n">calloc</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">max_theoretical_size</span><span class="p">);</span>
    <span class="n">DWORD</span><span class="o">*</span> <span class="n">buff2</span> <span class="o">=</span> <span class="n">buff</span><span class="p">;</span>
    <span class="n">IO_STATUS_BLOCK</span> <span class="n">status</span><span class="p">;</span>
    <span class="n">ZeroMemory</span><span class="p">(</span><span class="o">&amp;</span><span class="n">status</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">IO_STATUS_BLOCK</span><span class="p">));</span>
<span class="cp">#define FSCTL_GET_SHADOW_COPY_DATA 0x144064
#define STATUS_PENDING 0x103
</span>    <span class="n">rv</span> <span class="o">=</span> <span class="n">NtFsControlFile</span><span class="p">(</span><span class="n">hFile</span><span class="p">,</span> <span class="n">hEvent</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">status</span><span class="p">,</span> <span class="n">FSCTL_GET_SHADOW_COPY_DATA</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">buff</span><span class="p">,</span> <span class="n">max_theoretical_size</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">rv</span> <span class="o">==</span> <span class="n">STATUS_PENDING</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">WaitForSingleObject</span><span class="p">(</span><span class="n">hEvent</span><span class="p">,</span> <span class="n">INFINITE</span><span class="p">);</span>
        <span class="n">rv</span> <span class="o">=</span> <span class="n">status</span><span class="p">.</span><span class="n">Status</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">rv</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"NtFsControlFile (NTSTATUS): 0x%x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">rv</span><span class="p">);</span>
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"%d %d %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">buff2</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">buff2</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">buff2</span><span class="p">[</span><span class="mi">2</span><span class="p">]);</span>
</code></pre></div></div>

<p>Okay, so maybe the entire call is broken altogether. Indeed, if we craft a replacement for <code class="language-plaintext highlighter-rouge">NtFsControlFile</code> when <code class="language-plaintext highlighter-rouge">FsControlCode</code> set to <code class="language-plaintext highlighter-rouge">FSCTL_GET_SHADOW_COPY_DATA</code> that uses the Volume Shadow Service APIs instead of this device IO control call and run the program as administrator, we indeed get the snapshots. It is interesting to note that on my machine running Windows 11 22000.856 this method returned all the snapshots that both <code class="language-plaintext highlighter-rouge">vssadmin list shadows</code> and <a href="https://www.nirsoft.net/utils/shadow_copy_view.html">ShadowCopyView</a> listed, while the original <code class="language-plaintext highlighter-rouge">NtFsControlFile</code> call returned less snapshots, for some reasons. I compared the returned snapshots, but couldn’t find anything relevant regarding the missing ones.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;initguid.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;vss.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;Windows.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;objbase.h&gt;</span><span class="cp">
</span>
<span class="k">typedef</span> <span class="n">interface</span> <span class="n">IVssBackupComponents</span> <span class="n">IVssBackupComponents</span><span class="p">;</span>

<span class="k">typedef</span> <span class="k">struct</span> <span class="nc">IVssBackupComponentsVtbl</span>
<span class="p">{</span>
    <span class="n">BEGIN_INTERFACE</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">QueryInterface</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">,</span>
        <span class="cm">/* [in] */</span> <span class="n">__RPC__in</span> <span class="n">REFIID</span> <span class="n">riid</span><span class="p">,</span>
        <span class="cm">/* [annotation][iid_is][out] */</span>
        <span class="n">_COM_Outptr_</span>  <span class="kt">void</span><span class="o">**</span> <span class="n">ppvObject</span><span class="p">);</span>

    <span class="n">ULONG</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">AddRef</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">ULONG</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">Release</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">GetWriterComponentsCount</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">,</span>
        <span class="cm">/* [out] */</span> <span class="n">_Out_</span> <span class="n">UINT</span><span class="o">*</span> <span class="n">pcComponents</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">GetWriterComponents</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">,</span>
        <span class="cm">/* [in] */</span> <span class="n">_In_</span> <span class="n">UINT</span> <span class="n">iWriter</span><span class="p">,</span>
        <span class="cm">/* [out] */</span> <span class="n">_Out_</span> <span class="kt">void</span><span class="o">**</span> <span class="n">ppWriter</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">InitializeForBackup</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">,</span>
        <span class="cm">/* [in_opt] */</span> <span class="n">_In_opt_</span> <span class="n">BSTR</span> <span class="n">bstrXML</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g6</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g7</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g8</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g9</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g10</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g11</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g12</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g13</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g14</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g15</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g16</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g17</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g18</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g19</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g20</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g21</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g22</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g23</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g24</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g25</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g26</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g27</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g28</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g29</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g30</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g31</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g32</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g33</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g34</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">SetContext</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">,</span>
        <span class="n">_In_</span> <span class="n">LONG</span> <span class="n">lContext</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g36</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g37</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g38</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g39</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g40</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g41</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">g42</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">);</span>

    <span class="n">HRESULT</span><span class="p">(</span><span class="n">STDMETHODCALLTYPE</span><span class="o">*</span> <span class="n">Query</span><span class="p">)(</span>
        <span class="n">__RPC__in</span> <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">This</span><span class="p">,</span>
        <span class="n">_In_</span> <span class="n">REFIID</span>        <span class="n">QueriedObjectId</span><span class="p">,</span>
        <span class="n">_In_</span> <span class="n">INT64</span>    <span class="n">eQueriedObjectType</span><span class="p">,</span>
        <span class="n">_In_</span> <span class="n">INT64</span>    <span class="n">eReturnedObjectsType</span><span class="p">,</span>
        <span class="n">_In_</span> <span class="kt">void</span><span class="o">**</span> <span class="n">ppEnum</span><span class="p">);</span>

    <span class="n">END_INTERFACE</span>
<span class="p">}</span> <span class="n">IVssBackupComponentsVtbl</span><span class="p">;</span>

<span class="n">interface</span> <span class="n">IVssBackupComponents</span> <span class="c1">// : IUnknown</span>
<span class="p">{</span>
    <span class="n">CONST_VTBL</span> <span class="k">struct</span> <span class="nc">IVssBackupComponentsVtbl</span><span class="o">*</span> <span class="n">lpVtbl</span><span class="p">;</span>
<span class="p">};</span>

<span class="n">NTSTATUS</span> <span class="n">MyNtFsControlFile</span><span class="p">(</span>
    <span class="n">HANDLE</span>           <span class="n">FileHandle</span><span class="p">,</span>
    <span class="n">HANDLE</span>           <span class="n">Event</span><span class="p">,</span>
    <span class="n">PVOID</span>            <span class="n">ApcRoutine</span><span class="p">,</span>
    <span class="n">PVOID</span>            <span class="n">ApcContext</span><span class="p">,</span>
    <span class="n">PIO_STATUS_BLOCK</span> <span class="n">IoStatusBlock</span><span class="p">,</span>
    <span class="n">ULONG</span>            <span class="n">FsControlCode</span><span class="p">,</span>
    <span class="n">PVOID</span>            <span class="n">InputBuffer</span><span class="p">,</span>
    <span class="n">ULONG</span>            <span class="n">InputBufferLength</span><span class="p">,</span>
    <span class="n">PVOID</span>            <span class="n">OutputBuffer</span><span class="p">,</span>
    <span class="n">ULONG</span>            <span class="n">OutputBufferLength</span>
<span class="p">)</span>
<span class="p">{</span>
    <span class="n">NTSTATUS</span><span class="p">(</span><span class="o">*</span><span class="n">NtFsControlFile</span><span class="p">)(</span>
        <span class="n">HANDLE</span>           <span class="n">FileHandle</span><span class="p">,</span>
        <span class="n">HANDLE</span>           <span class="n">Event</span><span class="p">,</span>
        <span class="n">PVOID</span>  <span class="n">ApcRoutine</span><span class="p">,</span>
        <span class="n">PVOID</span>            <span class="n">ApcContext</span><span class="p">,</span>
        <span class="n">PIO_STATUS_BLOCK</span> <span class="n">IoStatusBlock</span><span class="p">,</span>
        <span class="n">ULONG</span>            <span class="n">FsControlCode</span><span class="p">,</span>
        <span class="n">PVOID</span>            <span class="n">InputBuffer</span><span class="p">,</span>
        <span class="n">ULONG</span>            <span class="n">InputBufferLength</span><span class="p">,</span>
        <span class="n">PVOID</span>            <span class="n">OutputBuffer</span><span class="p">,</span>
        <span class="n">ULONG</span>            <span class="n">OutputBufferLength</span>
        <span class="p">)</span> <span class="o">=</span> <span class="n">GetProcAddress</span><span class="p">(</span><span class="n">GetModuleHandleW</span><span class="p">(</span><span class="s">L"ntdll"</span><span class="p">),</span> <span class="s">"NtFsControlFile"</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">FsControlCode</span> <span class="o">==</span> <span class="n">FSCTL_GET_SHADOW_COPY_DATA</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">HRESULT</span> <span class="n">hr</span> <span class="o">=</span> <span class="n">S_OK</span><span class="p">;</span>
        <span class="n">DWORD</span> <span class="n">sz</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="s">L"@GMT-YYYY.MM.DD-HH.MM.SS"</span><span class="p">);</span>
        <span class="n">hr</span> <span class="o">=</span> <span class="n">CoInitialize</span><span class="p">(</span><span class="nb">NULL</span><span class="p">);</span>

        <span class="n">BY_HANDLE_FILE_INFORMATION</span> <span class="n">fi</span><span class="p">;</span>
        <span class="n">ZeroMemory</span><span class="p">(</span><span class="o">&amp;</span><span class="n">fi</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">BY_HANDLE_FILE_INFORMATION</span><span class="p">));</span>
        <span class="n">GetFileInformationByHandle</span><span class="p">(</span><span class="n">FileHandle</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">fi</span><span class="p">);</span>

        <span class="n">GUID</span> <span class="n">zeroGuid</span><span class="p">;</span>
        <span class="n">memset</span><span class="p">(</span><span class="o">&amp;</span><span class="n">zeroGuid</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">zeroGuid</span><span class="p">));</span>
        <span class="n">HMODULE</span> <span class="n">hVssapi</span> <span class="o">=</span> <span class="n">LoadLibraryW</span><span class="p">(</span><span class="s">L"VssApi.dll"</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">hVssapi</span><span class="p">)</span> <span class="k">return</span> <span class="n">STATUS_INSUFFICIENT_RESOURCES</span><span class="p">;</span>

        <span class="n">FARPROC</span> <span class="n">CreateVssBackupComponents</span> <span class="o">=</span> <span class="n">GetProcAddress</span><span class="p">(</span><span class="n">hVssapi</span><span class="p">,</span> <span class="s">"?CreateVssBackupComponents@@YGJPAPAVIVssBackupComponents@@@Z"</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">CreateVssBackupComponents</span><span class="p">)</span> <span class="n">CreateVssBackupComponents</span> <span class="o">=</span> <span class="n">GetProcAddress</span><span class="p">(</span><span class="n">hVssapi</span><span class="p">,</span> <span class="s">"?CreateVssBackupComponents@@YAJPEAPEAVIVssBackupComponents@@@Z"</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">CreateVssBackupComponents</span><span class="p">)</span> <span class="n">CreateVssBackupComponents</span> <span class="o">=</span> <span class="n">GetProcAddress</span><span class="p">(</span><span class="n">hVssapi</span><span class="p">,</span> <span class="p">(</span><span class="n">LPCSTR</span><span class="p">)</span><span class="mi">14</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">CreateVssBackupComponents</span><span class="p">)</span> <span class="k">return</span> <span class="n">STATUS_INSUFFICIENT_RESOURCES</span><span class="p">;</span>

        <span class="n">FARPROC</span> <span class="n">VssFreeSnapshotProperties</span> <span class="o">=</span> <span class="n">GetProcAddress</span><span class="p">(</span><span class="n">hVssapi</span><span class="p">,</span> <span class="s">"VssFreeSnapshotProperties"</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">VssFreeSnapshotProperties</span><span class="p">)</span> <span class="k">return</span> <span class="n">STATUS_INSUFFICIENT_RESOURCES</span><span class="p">;</span>

        <span class="n">IVssBackupComponents</span><span class="o">*</span> <span class="n">pVssBackupComponents</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
        <span class="n">hr</span> <span class="o">=</span> <span class="n">CreateVssBackupComponents</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pVssBackupComponents</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pVssBackupComponents</span><span class="p">)</span> <span class="k">return</span> <span class="n">STATUS_INSUFFICIENT_RESOURCES</span><span class="p">;</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">SUCCEEDED</span><span class="p">(</span><span class="n">hr</span> <span class="o">=</span> <span class="n">pVssBackupComponents</span><span class="o">-&gt;</span><span class="n">lpVtbl</span><span class="o">-&gt;</span><span class="n">InitializeForBackup</span><span class="p">(</span><span class="n">pVssBackupComponents</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">)))</span>
        <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">SUCCEEDED</span><span class="p">(</span><span class="n">hr</span> <span class="o">=</span> <span class="n">pVssBackupComponents</span><span class="o">-&gt;</span><span class="n">lpVtbl</span><span class="o">-&gt;</span><span class="n">SetContext</span><span class="p">(</span><span class="n">pVssBackupComponents</span><span class="p">,</span> <span class="n">VSS_CTX_ALL</span><span class="p">)))</span>
            <span class="p">{</span>
                <span class="n">IVssEnumObject</span><span class="o">*</span> <span class="n">pEnumObject</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">SUCCEEDED</span><span class="p">(</span><span class="n">hr</span> <span class="o">=</span> <span class="n">pVssBackupComponents</span><span class="o">-&gt;</span><span class="n">lpVtbl</span><span class="o">-&gt;</span><span class="n">Query</span><span class="p">(</span><span class="n">pVssBackupComponents</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">zeroGuid</span><span class="p">,</span> <span class="n">VSS_OBJECT_NONE</span><span class="p">,</span> <span class="n">VSS_OBJECT_SNAPSHOT</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pEnumObject</span><span class="p">)))</span>
                <span class="p">{</span>
                    <span class="n">ULONG</span> <span class="n">cnt</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
                    <span class="n">VSS_OBJECT_PROP</span> <span class="n">props</span><span class="p">;</span>
                    <span class="n">DWORD</span><span class="o">*</span> <span class="n">data</span> <span class="o">=</span> <span class="n">calloc</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">DWORD</span><span class="p">));</span>
                    <span class="k">while</span> <span class="p">(</span><span class="n">pEnumObject</span><span class="o">-&gt;</span><span class="n">lpVtbl</span><span class="o">-&gt;</span><span class="n">Next</span><span class="p">(</span><span class="n">pEnumObject</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">props</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">cnt</span><span class="p">),</span> <span class="n">cnt</span><span class="p">)</span>
                    <span class="p">{</span>
                        <span class="n">DWORD</span> <span class="n">sn</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
                        <span class="n">GetVolumeInformationW</span><span class="p">(</span><span class="n">props</span><span class="p">.</span><span class="n">Obj</span><span class="p">.</span><span class="n">Snap</span><span class="p">.</span><span class="n">m_pwszOriginalVolumeName</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">sn</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
                        <span class="k">if</span> <span class="p">(</span><span class="n">fi</span><span class="p">.</span><span class="n">dwVolumeSerialNumber</span> <span class="o">!=</span> <span class="n">sn</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>

                        <span class="n">data</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">++</span><span class="p">;</span>
                        <span class="n">SYSTEMTIME</span> <span class="n">SystemTime</span><span class="p">;</span>
                        <span class="n">ZeroMemory</span><span class="p">(</span><span class="o">&amp;</span><span class="n">SystemTime</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">SYSTEMTIME</span><span class="p">));</span>
                        <span class="n">BOOL</span> <span class="n">x</span> <span class="o">=</span> <span class="n">FileTimeToSystemTime</span><span class="p">(</span><span class="o">&amp;</span><span class="n">props</span><span class="p">.</span><span class="n">Obj</span><span class="p">.</span><span class="n">Snap</span><span class="p">.</span><span class="n">m_tsCreationTimestamp</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">SystemTime</span><span class="p">);</span>
                        <span class="n">WCHAR</span> <span class="n">Buffer</span><span class="p">[</span><span class="n">MAX_PATH</span><span class="p">];</span>
                        <span class="n">swprintf_s</span><span class="p">(</span>
                            <span class="n">Buffer</span><span class="p">,</span>
                            <span class="n">MAX_PATH</span><span class="p">,</span>
                            <span class="s">L"@GMT-%4.4d.%2.2d.%2.2d-%2.2d.%2.2d.%2.2d"</span><span class="p">,</span>
                            <span class="n">SystemTime</span><span class="p">.</span><span class="n">wYear</span><span class="p">,</span>
                            <span class="n">SystemTime</span><span class="p">.</span><span class="n">wMonth</span><span class="p">,</span>
                            <span class="n">SystemTime</span><span class="p">.</span><span class="n">wDay</span><span class="p">,</span>
                            <span class="n">SystemTime</span><span class="p">.</span><span class="n">wHour</span><span class="p">,</span>
                            <span class="n">SystemTime</span><span class="p">.</span><span class="n">wMinute</span><span class="p">,</span>
                            <span class="n">SystemTime</span><span class="p">.</span><span class="n">wSecond</span><span class="p">);</span>
                        <span class="kt">void</span><span class="o">*</span> <span class="n">new_data</span> <span class="o">=</span> <span class="n">realloc</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="mi">3</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">DWORD</span><span class="p">)</span> <span class="o">+</span> <span class="n">data</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="n">sz</span><span class="p">);</span>
                        <span class="k">if</span> <span class="p">(</span><span class="n">new_data</span><span class="p">)</span> <span class="n">data</span> <span class="o">=</span> <span class="n">new_data</span><span class="p">;</span>
                        <span class="k">else</span> <span class="k">break</span><span class="p">;</span>
                        <span class="n">memcpy_s</span><span class="p">((</span><span class="kt">char</span><span class="o">*</span><span class="p">)</span><span class="n">data</span> <span class="o">+</span> <span class="mi">3</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">DWORD</span><span class="p">)</span> <span class="o">+</span> <span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="n">sz</span><span class="p">,</span> <span class="n">sz</span><span class="p">,</span> <span class="n">Buffer</span><span class="p">,</span> <span class="n">sz</span><span class="p">);</span>
                        <span class="n">VssFreeSnapshotProperties</span><span class="p">(</span><span class="o">&amp;</span><span class="n">props</span><span class="p">.</span><span class="n">Obj</span><span class="p">.</span><span class="n">Snap</span><span class="p">);</span>
                    <span class="p">}</span>
                    <span class="kt">void</span><span class="o">*</span> <span class="n">new_data</span> <span class="o">=</span> <span class="n">realloc</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="mi">3</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">DWORD</span><span class="p">)</span> <span class="o">+</span> <span class="n">data</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="n">sz</span> <span class="o">+</span> <span class="mi">2</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">WCHAR</span><span class="p">));</span>
                    <span class="k">if</span> <span class="p">(</span><span class="n">new_data</span><span class="p">)</span>
                    <span class="p">{</span>
                        <span class="n">DWORD</span><span class="o">*</span> <span class="n">OutBuf</span> <span class="o">=</span> <span class="n">OutputBuffer</span><span class="p">;</span>
                        <span class="n">data</span> <span class="o">=</span> <span class="n">new_data</span><span class="p">;</span>
                        <span class="o">*</span><span class="p">(</span><span class="n">WCHAR</span><span class="o">*</span><span class="p">)((</span><span class="kt">char</span><span class="o">*</span><span class="p">)</span><span class="n">data</span> <span class="o">+</span> <span class="mi">3</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">DWORD</span><span class="p">)</span> <span class="o">+</span> <span class="n">data</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="n">sz</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
                        <span class="o">*</span><span class="p">(</span><span class="n">WCHAR</span><span class="o">*</span><span class="p">)((</span><span class="kt">char</span><span class="o">*</span><span class="p">)</span><span class="n">data</span> <span class="o">+</span> <span class="mi">3</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">DWORD</span><span class="p">)</span> <span class="o">+</span> <span class="n">data</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="n">sz</span> <span class="o">+</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">WCHAR</span><span class="p">))</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
                        <span class="n">data</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="n">sz</span> <span class="o">+</span> <span class="mi">2</span><span class="p">;</span>
                        <span class="k">if</span> <span class="p">(</span><span class="n">OutputBufferLength</span> <span class="o">&lt;</span> <span class="n">data</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">+</span> <span class="mi">3</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">DWORD</span><span class="p">))</span>
                        <span class="p">{</span>
                            <span class="n">data</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
                            <span class="n">OutBuf</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
                            <span class="n">OutBuf</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
                            <span class="n">OutBuf</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span>
                        <span class="p">}</span>
                        <span class="k">else</span>
                        <span class="p">{</span>
                            <span class="n">data</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
                            <span class="n">OutBuf</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
                            <span class="n">OutBuf</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
                            <span class="n">OutBuf</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span>
                            <span class="n">memcpy_s</span><span class="p">(</span><span class="n">OutBuf</span> <span class="o">+</span> <span class="mi">3</span><span class="p">,</span> <span class="n">data</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="n">data</span> <span class="o">+</span> <span class="mi">3</span><span class="p">,</span> <span class="n">data</span><span class="p">[</span><span class="mi">2</span><span class="p">]);</span>
                        <span class="p">}</span>
                        <span class="n">printf</span><span class="p">(</span><span class="s">"%d %d %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">OutBuf</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">OutBuf</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">OutBuf</span><span class="p">[</span><span class="mi">2</span><span class="p">]);</span>
                    <span class="p">}</span>
                    <span class="n">free</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>
                    <span class="n">pEnumObject</span><span class="o">-&gt;</span><span class="n">lpVtbl</span><span class="o">-&gt;</span><span class="n">Release</span><span class="p">(</span><span class="n">pEnumObject</span><span class="p">);</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="n">pVssBackupComponents</span><span class="o">-&gt;</span><span class="n">lpVtbl</span><span class="o">-&gt;</span><span class="n">Release</span><span class="p">(</span><span class="n">pVssBackupComponents</span><span class="p">);</span>

        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">NtFsControlFile</span><span class="p">(</span><span class="n">FileHandle</span><span class="p">,</span> <span class="n">Event</span><span class="p">,</span> <span class="n">ApcRoutine</span><span class="p">,</span> <span class="n">ApcContext</span><span class="p">,</span> <span class="n">IoStatusBlock</span><span class="p">,</span> <span class="n">FsControlCode</span><span class="p">,</span> <span class="n">InputBuffer</span><span class="p">,</span> <span class="n">InputBufferLength</span><span class="p">,</span> <span class="n">OutputBuffer</span><span class="p">,</span> <span class="n">OutputBufferLength</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>In fact, if I run File Explorer under the built-in Administrator account (necessary so that calls using the VSS API work, specifically <code class="language-plaintext highlighter-rouge">CreateVssBackupComponents</code>) and replace IAT patch the call to <code class="language-plaintext highlighter-rouge">NtFsControlFile</code> with the function above (of course, I do this by modifying ExplorerPatcher), I do indeed get the snapshots to show under the “Previous versions” tab, but only if I set <code class="language-plaintext highlighter-rouge">ShowAllPreviousVersions</code> (DWORD) to <code class="language-plaintext highlighter-rouge">1</code> under <code class="language-plaintext highlighter-rouge">HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer</code>:</p>

<p><img src="https://user-images.githubusercontent.com/6503598/187245754-3884a4c9-ef70-4f62-95a0-3405a0c7be51.png" alt="Previous Versions tab with ShowAllPreviousVersions" /></p>

<p>What is <code class="language-plaintext highlighter-rouge">ShowAllPreviousVersions</code>? It’s a setting that tells the window whether to display all snapshots or filter only the ones that actually contain the file that we are querying. The check is performed in <code class="language-plaintext highlighter-rouge">twext.dll</code> in <code class="language-plaintext highlighter-rouge">BuildSnapshots</code>:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">if</span> <span class="p">(</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">int</span><span class="p">)</span><span class="n">SHGetRestriction</span><span class="p">(</span>
                         <span class="s">L"Software</span><span class="se">\\</span><span class="s">Microsoft</span><span class="se">\\</span><span class="s">Windows</span><span class="se">\\</span><span class="s">CurrentVersion</span><span class="se">\\</span><span class="s">Explorer"</span><span class="p">,</span>
                         <span class="mi">0</span><span class="n">i64</span><span class="p">,</span>
                         <span class="s">L"ShowAllPreviousVersions"</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span> <span class="p">)</span>
</code></pre></div></div>

<p>Okay, so it seems that this solves the problem. Well, no really, since trying to open files that are clearly in the snapshot we have selected produces an error (which also seems to read from some illegal buffer, as signified by the Chinese characters, but that’s a bug for some other time):</p>

<p><img src="https://user-images.githubusercontent.com/6503598/187246098-f9c98125-f922-4c27-8c63-f11800681a25.png" alt="Error when opening the file from Previous Versions tab" /></p>

<p>Looking a bit on the code, after the call to <code class="language-plaintext highlighter-rouge">QuerySnapshotNames</code>, there are calls to <code class="language-plaintext highlighter-rouge">BuildSnapshots -&gt; TestSnapshot -&gt; GetFileAttributesExW</code>. The first parameter to <code class="language-plaintext highlighter-rouge">GetFileAttributesExW</code> is a UNC-like path that references the snapshot we are looking into for the file, things like: <code class="language-plaintext highlighter-rouge">\\localhost\C$\@GMT-2022.08.29-14.29.52\Users\Administrator\Downloads\ep_setup.exe</code>.</p>

<p>Traditionally, we can explore the contents of a snapshot using File Explorer by visiting a path like: <code class="language-plaintext highlighter-rouge">\\localhost\C$\@GMT-2022.08.29-14.29.52</code>. Also, opening a path like <code class="language-plaintext highlighter-rouge">\\localhost\C$\@GMT-2022.08.29-14.29.52\Users\Administrator\Downloads\ep_setup.exe</code> using “Run”, for example, will launch the file in the associated application, or simply execute it, if it’s an executable. But under build 22622, this is also broken. For example, trying to visit the folder <code class="language-plaintext highlighter-rouge">\\localhost\C$\@GMT-2022.08.29-14.29.52</code> (by pasting the line in “Run” and hitting Enter) yields this error:</p>

<p><img src="https://user-images.githubusercontent.com/6503598/187240832-64f2d036-c827-42cd-84e2-d677c05064c1.png" alt="Error when opening a VSS path" /></p>

<p>At this point, I kind of stopped my investigation. It’s clear that something big changed in the backend. It seems like calls that interact with the snapshots using any other means then the VSS API, so things like <code class="language-plaintext highlighter-rouge">NtFsControlFile</code> with <code class="language-plaintext highlighter-rouge">FSCTL_GET_SHADOW_COPY_DATA</code>, or <code class="language-plaintext highlighter-rouge">GetFileAttributesExW</code> on a path like <code class="language-plaintext highlighter-rouge">\\localhost\C$\@GMT-2022.08.29-14.29.52\Users\Administrator\Downloads\ep_setup.exe</code> somehow get denied along the way. This may stem from the wish to close off programatic access to snapshot information to applications that run under a low priviledge context, as the VSS API only works when the app runs as an administrator. If that’s the case, it’s not necessarily bad, but it seems it also broke a lot of stuff which really should be fixed. The thing is, it’s ind of expected for File Explorer to run under a standard user account and have the users use it to view and restore previous versions of their files originating from shadow copies. There’s been a recent vulnerability related to VSS (<a href="https://support.microsoft.com/en-us/topic/kb5015527-shadow-copy-operations-using-vss-on-remote-smb-shares-denied-access-after-installing-windows-update-dated-june-14-2022-6d460245-08b6-40f4-9ded-dd030b27850b">here</a>), maybe the fix for this had something to do with this change in behavior?</p>

<p>I would really appreciate an official resolution on the matter, as things are pretty broken at the moment, unfortunately. I really like the functionality offered by “Previous versions” powered by shadow copies and would like to see it continue to work in the traditional UI offered with past Windows 10 and 11 releases.</p>]]></content><author><name>Valentin Radu</name></author><summary type="html"><![CDATA[As a continuation of my previous article on shadow copies in Windows, I investigate how they behave under 22621+-based builds of Windows 11 (version 22H2, yet to be released).]]></summary></entry><entry><title type="html">Shadow copies under Windows 10 and Windows 11</title><link href="https://valinet.ro/2022/08/28/Shadow-copies-under-Windows-10-and-11.html" rel="alternate" type="text/html" title="Shadow copies under Windows 10 and Windows 11" /><published>2022-08-28T00:00:00+00:00</published><updated>2022-08-28T00:00:00+00:00</updated><id>https://valinet.ro/2022/08/28/Shadow-copies-under-Windows-10-and-11</id><content type="html" xml:base="https://valinet.ro/2022/08/28/Shadow-copies-under-Windows-10-and-11.html"><![CDATA[<p>Ever since I got into the world of managing servers, working with VMs etc, I have kind of grown acustomed to being able to quickly restore the systems I work with to a previous point in time. But having this confidence with most of the tools I manage (the company servers, the hypervisors I use), the OS has always been a sore spot for me - I was always scared of some program installing and then messing up my Windows install in some irreversible way.</p>

<p>It was only until relatively recently that I have discovered that Windows does indeed supports snapshots on its default file system (NTFS). And not only on recent versions, but for almost 20 years now, ever since the days of Windows XP. It’s called shadow copies and saw its prime time during the Windows Vista/7 era, only to see it kind of dismissed with the launch of Windows 8 for reasons I do not really understand. This feature is EXTREMLY useful to have - not only can you restore the entire C: drive in case some software messes it up, but you can also restore previous versions of the files you have been working on. This is immensily useful and time saving, so since I have discovered this, I really cannot live without it.</p>

<p>The journey begun when I wondered how to have the “Previous versions” tab (hosted by <code class="language-plaintext highlighter-rouge">twext.dll</code>; <code class="language-plaintext highlighter-rouge">tw</code> stands for “timewarp”) in the Properties sheet of an item from File Explorer actually populated with previous versions. Items displayed in there can come from one of these providers:</p>

<ul>
  <li>Volume Shadow Copy (“snapshot”) - Windows’ disk snapshots functionality</li>
  <li>Windows 7 Backup / Restore (“safedocs”) - the legacy backup solution in Windows</li>
  <li>File History (“fhs”) - Windows 8-era replacement for safedocs</li>
  <li>System Restore points</li>
</ul>

<p>Despite that, the instructions in the window say that files displayed there only come from File History or restore points; File History creates backups of your files on an external drive on specified intervals. This works, but it’s been always extremly buggy, with Windows sometimes failing to recognize the backups once you reinstalled the OS, plus some random slowdowns when copying files, or even the feature not being able to make an initial snapshot. Plus this worked for specific folders, not entire drives. I think even Microsoft knew how poor and laughable their implementation was, plus they surely do anything to push you over to storing backups in OneDrive, so, with Windows 11, they completely removed the UI for configuring this via the Settings app (as with other things), putting a final nail in the coffin for this feature that never worked right (there is remaining UI in the legacy Control Panel, but that alone can’t be used to configure all aspects of this feature).</p>

<p>But as I have discovered, snapshots taken using the volume shadow copy service also generate entries in the “Previous versions” tab. Cool! So it means that ll we have to do is enable snapshots for our volumes and we’ll start seeing entries there, right?</p>

<p><img src="https://user-images.githubusercontent.com/6503598/187086678-e8decbec-33d0-4058-b0a8-259dd29636b3.png" alt="Previous versions tab in the Properties sheet" /></p>

<p>Well, yeah. Unfortunately, due to another questionable Microsoft move, they have completely removed the UI for configuring shadow copies from client Windows (it’s still available in server SKUs). But the functionality is still there, they just don’t deem it important, for some reason, for regular users.</p>

<p>So, becuase we have no UI for configuring a schedule for snapshots, I have resorted to setting up a scheduled task that runs the following command every 15 minutes:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wmic shadowcopy call create Volume=C:\
</code></pre></div></div>

<p>What this does is create a snapshot of drive C: every 15 minutes. By policy, snapshots cannot take more than 10 seconds, and I experience no slowdowns anyway, so I am fine with executing them this often. This means I can lose, at worst, a maximum of 15 minutes of saved work, which sounds really good to me.</p>

<p>This is enough to get you the basics. Even if you stop reading here to return at a later time, you’ll be covered against the basics. Follow on to learn more.</p>

<h3 id="increase-the-number-of-snapshots">Increase the number of snapshots</h3>

<p>By default, Windows is pretty conservative in the number of snapshots you can take (64) before older snapshots are deleted. The system allows for a more reasonable maximum of 512, which you can configure by setting <code class="language-plaintext highlighter-rouge">MaxShadowCopies</code> (DWORD) under <code class="language-plaintext highlighter-rouge">HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\VSS\Settings</code> as per <a href="https://docs.microsoft.com/en-us/windows/win32/backup/registry-keys-for-backup-and-restore">these instructions</a>. Reboot after making the changes.</p>

<h3 id="increase-the-disk-quota-snapshots-can-take">Increase the disk quota snapshots can take</h3>

<p>By default, snapshot contents are stored on the same drive you are snapshotting. You can increase the maximum space snapshots can take on your hard driver by issuing such a command in an administrative prompt:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vssadmin Resize ShadowStorage /For=C: /On=C: /MaxSize=UNBOUNDED
</code></pre></div></div>

<p>The command above removed any quota on the space snapshots can take - be careful, this can quickly fill your disk; you can also specify something like <code class="language-plaintext highlighter-rouge">100GB</code> instead of <code class="language-plaintext highlighter-rouge">UNBOUNDED</code> there and so on. Find out more about the command by running: <code class="language-plaintext highlighter-rouge">vssadmin Resize ShadowStorage</code>.</p>

<h3 id="view-all-snapshots">View all snapshots</h3>

<p>You may notice, depending on your usage, that some snapshots are not displayed in the “Previous versions” tab (only 64 of them or so may be displayed). It’s indeed a display bug that sometimes happens; to consult the entire list, use:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vssadmin List Shadows
</code></pre></div></div>

<p>Alternatively, you can use a full featured third party GUI like <a href="https://www.shadowexplorer.com/downloads.html">ShadowExplorer</a> or <a href="https://www.nirsoft.net/utils/shadow_copy_view.html">ShadowCopyView</a>.</p>

<p>Do note that under Windows 11 22H2-based builds (22621+), this functionality seems to be broken altogether; read more about my investigation regarding this <a href="https://valinet.ro/2022/08/29/Working-with-shadow-copies-is-kind-of-broken-on-Windows-11-22H2.html">here</a>.</p>

<h3 id="restore-an-entire-drive">Restore an entire drive</h3>

<p>This is where thing get a bit more complicated. But, first of all, do not right click a drive and go to Properties - Previous versions and attempt to restore from there. It’s inneficcient and takes a ton of time and will likely mess your system. That feature basically copies over files from the snapshot over your current files, which is very unlikely to work properly, so do not goi that route. You have been warned.</p>

<p>Instead, remember how I told you that Microsoft ‘inteligentlly’ removed the GUI portion of shadow copies from client SKUs? Well, not only that; you see, <code class="language-plaintext highlighter-rouge">vssadmin</code> under client SKUs offers fewer option than it does under server SKUs.</p>

<p>For example, here’s <code class="language-plaintext highlighter-rouge">vssadmin /?</code> under Windows 11:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vssadmin 1.1 - Volume Shadow Copy Service administrative command-line tool
(C) Copyright 2001-2013 Microsoft Corp.

---- Commands Supported ----

Delete Shadows        - Delete volume shadow copies
List Providers        - List registered volume shadow copy providers
List Shadows          - List existing volume shadow copies
List ShadowStorage    - List volume shadow copy storage associations
List Volumes          - List volumes eligible for shadow copies
List Writers          - List subscribed volume shadow copy writers
Resize ShadowStorage  - Resize a volume shadow copy storage association
</code></pre></div></div>

<p>And here it is under Windows Server 2022:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vssadmin 1.1 - Volume Shadow Copy Service administrative command-line tool
(C) Copyright 2001-2013 Microsoft Corp.

---- Commands Supported ----

Add ShadowStorage     - Add a new volume shadow copy storage association
Create Shadow         - Create a new volume shadow copy
Delete Shadows        - Delete volume shadow copies
Delete ShadowStorage  - Delete volume shadow copy storage associations
List Providers        - List registered volume shadow copy providers
List Shadows          - List existing volume shadow copies
List ShadowStorage    - List volume shadow copy storage associations
List Volumes          - List volumes eligible for shadow copies
List Writers          - List subscribed volume shadow copy writers
Resize ShadowStorage  - Resize a volume shadow copy storage association
Revert Shadow         - Revert a volume to a shadow copy
Query Reverts         - Query the progress of in-progress revert operations.
</code></pre></div></div>

<p>Again, why they decided to do this is beyond my understanding, probably some business decision. Fortunately, as described <a href="https://www.reddit.com/r/sysadmin/comments/w7fwgp/restore_shadow_copies_from_cli/">in this Reddit post</a>, you can trick <code class="language-plaintext highlighter-rouge">vssadmin</code> into thinking it’s running under a server SKU by using <code class="language-plaintext highlighter-rouge">WinPrivCmd</code>, a redirector for some API calls programs use to find various information from Windows, including whether the program runs under a client or server SKU. To get the server <code class="language-plaintext highlighter-rouge">vssadmin</code>, run this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>winprivcmd /ServerEdition vssadmin /?
</code></pre></div></div>

<p>If the drive you’re trying to restore is not the system drive, than you can use a command like this one to revert to a snapshot of it:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vssadmin Revert Shadow /Shadow={c5946237-af12-3f23-af80-51aadb3b20d5} /ForceDismount
</code></pre></div></div>

<p>Again, you can identify the exact snapshot GUID using <code class="language-plaintext highlighter-rouge">vssadmin List Shadows</code>. Actually, I don’t really know if this works from client SKUs anyway, even if you trick it into recognizing it like this - I don’t know if the backend supports it. Anyway, read on for a workaround that works for all scenarios, including your system drive.</p>

<p>First of all, you can’t restore a snapshot on the system drive from an online system. To workaround this, I recommend booting into a Windows Server-based live image. The easiest way to do this is to grab a pen drive and use <a href="https://rufus.ie/en/">Rufus</a> to create a Windows To Go installation on it using a Windows Server ISO. Then, boot that. When making the bootable drive, make sure to uncheck the “Prevent Windows To Go from accessing internal disks” option when clicking “Start” in Rufus.</p>

<p>Then, there are a couple of pre-requisties for the revert operation to work. I skipped the documentation and looked directly on the disassembly of the <code class="language-plaintext highlighter-rouge">VSSVC.exe</code> executale (the Volume Shadow Copy service):</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">bool</span> <span class="kr">__fastcall</span> <span class="n">CVssCoordinator</span><span class="o">::</span><span class="n">IsRevertableVolume</span><span class="p">(</span><span class="n">CVssCoordinator</span> <span class="o">*</span><span class="k">this</span><span class="p">,</span> <span class="k">const</span> <span class="kt">unsigned</span> <span class="kr">__int16</span> <span class="o">*</span><span class="n">a2</span><span class="p">)</span>
<span class="p">{</span>
  <span class="n">CVssCoordinator</span> <span class="o">*</span><span class="n">v3</span><span class="p">;</span> <span class="c1">// rcx</span>
  <span class="kt">char</span> <span class="n">v4</span><span class="p">;</span> <span class="c1">// bl</span>
  <span class="n">CVssCoordinator</span> <span class="o">*</span><span class="n">v5</span><span class="p">;</span> <span class="c1">// rcx</span>
  <span class="n">CVssCoordinator</span> <span class="o">*</span><span class="n">v6</span><span class="p">;</span> <span class="c1">// rcx</span>

  <span class="n">v4</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
  <span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="n">CVssCoordinator</span><span class="o">::</span><span class="n">IsSystemVolume</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="n">a2</span><span class="p">)</span>
    <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">CVssCoordinator</span><span class="o">::</span><span class="n">IsBootVolume</span><span class="p">(</span><span class="n">v3</span><span class="p">,</span> <span class="n">a2</span><span class="p">)</span>
    <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">CVssCoordinator</span><span class="o">::</span><span class="n">IsPagefileVolume</span><span class="p">(</span><span class="n">v5</span><span class="p">,</span> <span class="n">a2</span><span class="p">)</span> 
	<span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">CVssCoordinator</span><span class="o">::</span><span class="n">IsSharedClusterVolume</span><span class="p">(</span><span class="n">v6</span><span class="p">,</span> <span class="n">a2</span><span class="p">)</span> <span class="p">)</span>
  <span class="p">{</span>
    <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="k">return</span> <span class="n">v4</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>So, basically, you cannot restore a system or booted volume, so that’s why you can’t restore the <code class="language-plaintext highlighter-rouge">C:</code> drive while the OS s running. Kind of makes sense. You also cannot restore it if it’s a shared cluster volume; I don’t know exactly what that is, but on regular configurations, it’s not. All that remains is not to have a page file on the volume. That’s easy to get around: in Start, search for “advanced system settings”, under “Performance”, click “Settings”, “Advanced”, “Virtual memory” section, “Change”, untick “Automatically manage paging file size for all drives”, then select your “C:” drive, check “No paging file”, click “Set”, then “OK”, “OK”, “OK” and reboot the system.</p>

<p>Most of these checks are also performed in <code class="language-plaintext highlighter-rouge">VSSUI.dll</code>, which is the DLL that hosts the UI for the shadow copies tab in Windows Server, in the <code class="language-plaintext highlighter-rouge">IsValidRevertVolume</code> function. Now, with the page file disabled, you’r ready to boot into the Windows Server environment.</p>

<p>A quick note before that: you can also edit the <code class="language-plaintext highlighter-rouge">VSSVC.exe</code> and <code class="language-plaintext highlighter-rouge">VSSUI.dll</code> binaries and nullify those checks. Does it work then? Can it work from a live booted OS restoring the system drive itself? I don’t know, I haven’t tried it, since I was doing all of this on my main workstation which I was trying to revert, but hopefully one day I play more with this under a VM and see what happens when doing this.</p>

<p>Now that you have the Windows Server environemnt, boot into it. When you get to the desktop, open File Explorer and identify your main OS system drive. Right click on it and choose “Configure Shadow Copies”.</p>

<p>In there, select your drive in the list and identify the snapshot you want to revert to, then click the “Revert” button. A pop-up window will ask you to confirm the operation. Check the box, then click to proceed. After a brief freeze of the interface, the restore operation will commence. It shouldn’t take long - it took around 20 minutes when I restored a snapshot on my NVMe drive filled with around 400GB, although it of course varies based on the hardware that you have.</p>

<p><img src="https://user-images.githubusercontent.com/6503598/187089970-1520f5d5-448d-472a-b2f6-5ee4fc0a3053.png" alt="Restoring a shadow copy" /></p>

<p>You can also monitor the progress in the command line using this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vssadmin Query Reverts
</code></pre></div></div>

<h3 id="conclusion">Conclusion</h3>

<p>Shadow Copies are a VERY useful feature. I am actually impressed they aren’t much more popularized. They indeed may require quite a bit of space, depending on your configured options, but overall, the piece of mind and convenience they bring make them worth using despite the aritifical roadblocks imposed by Microsoft. I hope the functionality will continue to live on in future Windows versions, considering how it lived and did its job just fine for the past 20 years, despite alarming signs from Microsoft in their latest OS versions.</p>

<p>Also, keep in mind that these are not a replacement for a backup system! They are though a complement to your computing habits, and came in handy to me: I researched this article while trying to revert back to a config of my system before I messed up with it trying various programs and hacks to get my WhatsApp messages transfered from my iPhone to Android/Windows Subsystem for Android, in order to archive them and free up the storage on my phone (they take quite a bit of space). Maybe I will tell you about this in a future story. For now, this will do, peace!</p>]]></content><author><name>Valentin Radu</name></author><summary type="html"><![CDATA[Ever since I got into the world of managing servers, working with VMs etc, I have kind of grown acustomed to being able to quickly restore the systems I work with to a previous point in time. But having this confidence with most of the tools I manage (the company servers, the hypervisors I use), the OS has always been a sore spot for me - I was always scared of some program installing and then messing up my Windows install in some irreversible way. Today, it's time to address this.]]></summary></entry><entry><title type="html">Set default apps in Windows 11 (restore the Windows 10 Settings app in Windows 11)</title><link href="https://valinet.ro/2022/05/24/Set-default-apps-in-Windows-11.html" rel="alternate" type="text/html" title="Set default apps in Windows 11 (restore the Windows 10 Settings app in Windows 11)" /><published>2022-05-24T00:00:00+00:00</published><updated>2022-05-24T00:00:00+00:00</updated><id>https://valinet.ro/2022/05/24/Set-default-apps-in-Windows-11</id><content type="html" xml:base="https://valinet.ro/2022/05/24/Set-default-apps-in-Windows-11.html"><![CDATA[<p>I just got fed up with the new idiotic way in which Microsoft wants to force us to set defaults in Windows 11. Instead of the good old Settings - Apps - Default apps page from Windows 10 that easily let you assign an app for an entire category (which is pretty logical, for example, you could set the default music player in a pinch, which immediatly associated the app with mp3, wav etc files), you now have to go through each extension and assign it the app that you want, or go to the app and manually assign each extension to it. I mean, why?</p>

<p>Other than that, the Windows 11 Settings app is fine, it looks nice, but this omission is a huge downgrade. People have resorted to all sorts of hacks, and even big players like Firefox <a href="https://www.reddit.com/r/firefox/comments/pnemdm/mozilla_has_defeated_microsofts_default_browser/">have hacked their way into bypassing Microsoft’s protections</a> because the current way for setting defaults is just awful.</p>

<p>Well, here I am again, fixing Microsoft’s OS. This time, I decided enough is enough and looked into a way to bring back the Settings app from Windows 10 which thankfully included the beloved “Default apps” page.</p>

<p><img src="https://user-images.githubusercontent.com/6503598/170130743-33352050-25fb-43f4-b0ab-4253a5f36d16.png" alt="image" /></p>

<h3 id="how-to">How to?</h3>

<p>This time, you’ll have to do things manually; I have yet to decide on a way to automate this, if any - suggestions are welcome in the comments section; should this be integrated in ExplorerPatcher, and if so, how to go around the technical details? Anyway, for the tutorial, read on.</p>

<p>You’ll need the following things from build 22000.1, the single public build of Windows 11 that ever shipped with the legacy Settings app:</p>

<ul>
  <li>the <code class="language-plaintext highlighter-rouge">C:\Windows\ImmersiveControlPanel</code> folder; this is the main folder where the UWP Settings app lives</li>
  <li>the <code class="language-plaintext highlighter-rouge">C:\Windows\SystemResources\Windows.UI.SettingsAppThreshold</code> folder; this is the folder that contains the resources used by the Settings app</li>
  <li>the <code class="language-plaintext highlighter-rouge">C:\Windows\System32\SettingsEnvironment.Desktop.dll</code> file</li>
</ul>

<p>If you stop at only the 2 things above, the legacy Settings app will work, but none of the built-in pages will actually work. If you connect with a debugger to the running <code class="language-plaintext highlighter-rouge">SystemSettings.exe</code> process, some first-time exceptions will be thrown, and a message from the Windows Runtime along the lines of <code class="language-plaintext highlighter-rouge">WinRT information: Cannot find a resource with the given key: DefaultEntityItemSettingPageTemplate</code> will be mentioned. Needless to say, the debug info is totally worthless; only by looking on the modules list and testing file by file afterwards was I able to successfully determine what was still required to be brought over, i.e. this file.</p>

<p>So, on a live system:</p>

<ul>
  <li>Take ownership of <code class="language-plaintext highlighter-rouge">C:\Windows\ImmersiveControlPanel</code> and rename it to <code class="language-plaintext highlighter-rouge">ImmersiveControlPanelO</code>, and paste the <code class="language-plaintext highlighter-rouge">C:\Windows\ImmersiveControlPanel</code> folder from 22000.1 in there.</li>
  <li>Again, Windows is bugged up and won’t let you replace the <code class="language-plaintext highlighter-rouge">C:\Windows\SystemResources\Windows.UI.SettingsAppThreshold</code> folder, even after taking ownership of it. I don’t really understand what is wrong with this OS, but the solution is to manually rename the 3 items inside (<code class="language-plaintext highlighter-rouge">pris</code> to <code class="language-plaintext highlighter-rouge">prisO</code>, <code class="language-plaintext highlighter-rouge">SystemSettings</code> to <code class="language-plaintext highlighter-rouge">SystemSettingsO</code> and <code class="language-plaintext highlighter-rouge">Windows.UI.SettingsAppThreshold.pri</code> to <code class="language-plaintext highlighter-rouge">Windows.UI.SettingsAppThreshold.priO</code>) and copy in there the corresponding 3 items from 22000.1.</li>
  <li>Instead of replacing the <code class="language-plaintext highlighter-rouge">SettingsEnvironment.Desktop.dll</code> in <code class="language-plaintext highlighter-rouge">C:\Windows\System32</code>, I recommend copying it from 22000.1 in <code class="language-plaintext highlighter-rouge">C:\Windows\ImmersiveControlPanel</code>. This DLL is loaded in a “classic” manner so to speak, thus the loader first looking in the applicaiton folder, and then in the system directories; thus, the DLL placed in <code class="language-plaintext highlighter-rouge">C:\Windows\ImmersiveControlPanel</code> can override the one in <code class="language-plaintext highlighter-rouge">C:\Windows\System32</code>.</li>
</ul>

<p>That’s it. If you’ve done everything correctly, open the Settings app using Start, for example - you should be greeted by the legacy Settings app from Windows 10:</p>

<p><img src="https://user-images.githubusercontent.com/6503598/170138015-0b01ec18-4661-42a5-99b0-fc24821b0d50.png" alt="image" /></p>

<p>To spare you having to spin up a VM and gather the required files, you can <a href="https://github.com/valinet/ExplorerPatcher/files/8766287/LegacySettingsAppForWindows10Files.zip">download the following archive</a> which contains the 2 folders from 22000.1: <code class="language-plaintext highlighter-rouge">C:\Windows\ImmersiveControlPanel</code> (already contains the proper <code class="language-plaintext highlighter-rouge">SettingsEnvironment.Desktop.dll</code> file in it as well), and <code class="language-plaintext highlighter-rouge">C:\Windows\System32\SettingsEnvironment.Desktop.dll</code>. All you have to do is to place these instead of the built-in folders that you have, of course, taking backups of the originals.</p>

<p>Needless to say, this will probably be reverted by Windows Update, but nevertheless, it’s a solution for at least doing some maintenance tasks that you can’t really do with the new Settings app. As I said, we can discuss in the comments section about a proper way to automate this, if interested.</p>

<p>If you stumble upon any problem and would like to restore a clean slate, you can run the following command and restart the computer when it finishes:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sfc /scannow
</code></pre></div></div>

<h3 id="bonus">Bonus</h3>

<p>Yeah, since we restored the full Windows 10 Settings app, you can now use the other UIs that were also forgotten by Microsoft and never replicated in the new Settings app. For example, I could finally properly adjust File History settings (Settings - Update &amp; Security - Backup) which is another omission from the new app.</p>

<h3 id="conclusion">Conclusion</h3>

<p>What can I say, another stupid move by Microsoft. I am VERY glad that this hack works after all, but the fact that we needed it is just mind boggling. Hopefully, things will improve with the upcoming updates, but I honestly doubt it. Anyway, in the mean time, we have this.</p>

<p>Looking forward to hearing your thoughts in the comments below.</p>]]></content><author><name>Valentin Radu</name></author><summary type="html"><![CDATA[Today I spent time fixing yet another questionable decision of Microsoft, restoring the legacy Windows 10 Settings app in Windows 11 so that I could finally properly adjust app defaults (and change File History settings).]]></summary></entry><entry><title type="html">Upload desktop.ini when using Nextcloud</title><link href="https://valinet.ro/2022/04/12/Upload-desktop-ini-when-using-Nextcloud.html" rel="alternate" type="text/html" title="Upload desktop.ini when using Nextcloud" /><published>2022-04-12T00:00:00+00:00</published><updated>2022-04-12T00:00:00+00:00</updated><id>https://valinet.ro/2022/04/12/Upload-desktop-ini-when-using-Nextcloud</id><content type="html" xml:base="https://valinet.ro/2022/04/12/Upload-desktop-ini-when-using-Nextcloud.html"><![CDATA[<p>Again, much time has passed since the last post here. Again, <a href="https://github.com/valinet/ExplorerPatcher">ExplorerPatcher</a> is the “culprit”. Anyway, I have decided to take a small break off it, for now.</p>

<p>Without further do, let’s begin.</p>

<h3 id="background">Background</h3>

<p>Recently, I have decided to find a solution to keep files syncronized between my home and work PC. I am using my own server for this, as I kind of hate storing personal files in the public clouds offered by various vendors, for obvious reasons. After going through the pitfalls of offline files (XP-era tech) and Work Folders (8.1-era tech), I decided those simply won’t cut it. It didn’t help that these features are largely forgotten and left by Microsoft in a zombie state in the latest iterations of the OS.</p>

<p>Anyway, having picked up Nextcloud next, I turned to their rather good (and open source) desktop client. Things went relatively smoothly, except I had this persisting problem: there is this <code class="language-plaintext highlighter-rouge">desktop.ini</code> file that Windows creates in various folders whenever you customize it in one way or another, and for some reason I have later learnt about, Nextcloud refuses to sync that, so it always remains in a pending state when looked at using File Explorer.</p>

<p>If you just see the pending icon on the parent folder, yet all files inside seemed to have synced, than you might simply not be able to see the <code class="language-plaintext highlighter-rouge">desktop.ini</code> files. To enable these, go to “Folder Options - View tab - Hide protected operating system files (Recommended)”.</p>

<h3 id="investigation">Investigation</h3>

<p>So, what about this? First, I used Google to search for stuff realted to this. I came up with <a href="https://git.furworks.de/opensourcemirror/nextcloud-desktop/commit/a480a318fd4bd2c6bbfa980ccb901e976a83e4aa">this link</a> which in turn was a mirror of <a href="https://github.com/nextcloud/desktop/commit/a480a318fd4bd2c6bbfa980ccb901e976a83e4aa">the official repo</a> which contains information about why they have chosen not to sync <code class="language-plaintext highlighter-rouge">desktop.ini</code> files: see, they create such a file to specify information for File Explorer that the Nextcloud folder should use a custom icon, yet they can’t sync that because the path where that folder is located may differ from one PC to another, depending on where you have Nextcloud installed. I mean, sure, they could just patch the file for each computer instead, or just enforce this restriction in the root folder, yet it seems they exclude files named like this in all the subdirectories and this is what I don’t really get why.</p>

<p>Anyway, next step was to look on the current source code and see how it looks currently. So:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/nextcloud/desktop nextcloud-desktop
</code></pre></div></div>

<p>Then, use <code class="language-plaintext highlighter-rouge">grep</code> to find where <code class="language-plaintext highlighter-rouge">desktop.ini</code> is mentioned:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>grep -rn "desktop.ini"
</code></pre></div></div>

<p>It mentions these places:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ChangeLog:307:* Excludes: Hardcode desktop.ini
src/csync/csync_exclude.cpp:201:    /* Do not sync desktop.ini files anywhere in the tree. */
src/csync/csync_exclude.cpp:202:    const auto desktopIniFile = QStringLiteral("desktop.ini");
</code></pre></div></div>

<p>Of interest is <code class="language-plaintext highlighter-rouge">csync_exclude.cpp</code>. The mentioned lines look like this:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="cm">/* Do not sync desktop.ini files anywhere in the tree. */</span>
    <span class="k">const</span> <span class="k">auto</span> <span class="n">desktopIniFile</span> <span class="o">=</span> <span class="n">QStringLiteral</span><span class="p">(</span><span class="s">"desktop.ini"</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">blen</span> <span class="o">==</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="n">qsizetype</span><span class="o">&gt;</span><span class="p">(</span><span class="n">desktopIniFile</span><span class="p">.</span><span class="n">length</span><span class="p">())</span> <span class="o">&amp;&amp;</span> <span class="n">bname</span><span class="p">.</span><span class="n">compare</span><span class="p">(</span><span class="n">desktopIniFile</span><span class="p">,</span> <span class="n">Qt</span><span class="o">::</span><span class="n">CaseInsensitive</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">CSYNC_FILE_SILENTLY_EXCLUDED</span><span class="p">;</span>
    <span class="p">}</span>
</code></pre></div></div>

<p>Of course, I could recompile the whole application in order to take that out, but for taking an <code class="language-plaintext highlighter-rouge">if</code> out, all the dance (setting up a build environment etc) is not really necessary. We could binary patch the existing files, a strategy I really love when it comes to these types of modifications. It’s much easier and takes way less time and effort, and it also teaches you things along the way.</p>

<p>So it’s time to identify where that piece of code ends in the binaries that are actually shipped with the Windows client. Go to <code class="language-plaintext highlighter-rouge">C:\Program Files\Nextcloud</code> and look there. Of course, <code class="language-plaintext highlighter-rouge">nextcloud_csync.dll</code> seems like a good candidate, but suppose it wouldn’t have been so obvious, you can use the <a href="https://docs.microsoft.com/en-us/sysinternals/downloads/strings">Strings</a> tool from Sysinternals to look for the string in all the files in the folder, like so:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>strings64.exe -o "C:\Program Files\Nextcloud\*" | findstr /i "desktop.ini"
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">-o</code> flag will have it print the offsets to the string in the file, like so (for <code class="language-plaintext highlighter-rouge">nextcloud_csync.dll</code>):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>979304:/Desktop.ini
979712:Desktop.ini
979792:Remove Desktop.ini from
1184248:desktop.ini
</code></pre></div></div>

<p>Eventually, we conclude the “culprit” is indeed <code class="language-plaintext highlighter-rouge">nextcloud_csync.dll</code>. Of course, the next step is to load this in IDA and look on it.</p>

<p>After loading, search for <code class="language-plaintext highlighter-rouge">desktop.ini</code> (<code class="language-plaintext highlighter-rouge">Alt</code>+<code class="language-plaintext highlighter-rouge">T</code>) and we get these results:</p>

<p><img src="https://user-images.githubusercontent.com/6503598/162841414-205162f4-4171-461c-9cfc-bc15588064c4.png" alt="image" /></p>

<p>By exploring those, we can see that the disassembly does not match anything that resembles the source code. So, what’s up, does IDA miss our string, does it not live in this file after all? To clear the mystery, I learnt about the “String” window in IDA (View - Open subviews - Strings). There, right click and choose “Setup…”, and in there you can choose to display “Unicode C-style (16 bits)” strings as well. With <code class="language-plaintext highlighter-rouge">Ctrl</code>+<code class="language-plaintext highlighter-rouge">F</code> afterwards, we can search for it in the String window and identify it.</p>

<p>How do I know it’s Unicode 16-bits? Well, you can tell by limiting <code class="language-plaintext highlighter-rouge">strings64.exe</code> to searching only for Unicode 16-bits strings (with the <code class="language-plaintext highlighter-rouge">-u</code> flag), in which case it will identify that. But IDA knows those, how did it not pick it up? Well, it doesn’tr ecognize it as a string, before it’s nowhere referenced in the disassembly. But how could that be, the source code clearly accesses that string. Well, not really…</p>

<p>First, if we go to the string location we identified using the “Strings” window in IDA, it looks like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.data:00000001801237F8                 db  64h ; d
.data:00000001801237F9                 db    0
.data:00000001801237FA                 db  65h ; e
.data:00000001801237FB                 db    0
.data:00000001801237FC                 db  73h ; s
.data:00000001801237FD                 db    0
.data:00000001801237FE                 db  6Bh ; k
.data:00000001801237FF                 db    0
.data:0000000180123800                 db  74h ; t
.data:0000000180123801                 db    0
.data:0000000180123802                 db  6Fh ; o
.data:0000000180123803                 db    0
.data:0000000180123804                 db  70h ; p
.data:0000000180123805                 db    0
.data:0000000180123806                 db  2Eh ; .
.data:0000000180123807                 db    0
.data:0000000180123808                 db  69h ; i
.data:0000000180123809                 db    0
.data:000000018012380A                 db  6Eh ; n
.data:000000018012380B                 db    0
.data:000000018012380C                 db  69h ; i
.data:000000018012380D                 db    0
.data:000000018012380E                 db    0
.data:000000018012380F                 db    0
</code></pre></div></div>

<p>Why is that? The clue is right there in the source code: the string is defined as <code class="language-plaintext highlighter-rouge">QStringLiteral("desktop.ini")</code> actually, which is actually a specialized container for string stuff. The disassembly never accesses the string data directly, rather, as we can clearly see from the source code, it’s passed to helper implementations that do stuff for us, like <code class="language-plaintext highlighter-rouge">.compare(</code> aka <code class="language-plaintext highlighter-rouge">QStringRef::compare</code>. What these helpers receieve is a pointer to this <code class="language-plaintext highlighter-rouge">QStringLiteral</code>, so the address of where its data is in memory. And how does it look in memory? Well, it indeed contains the actual string, but it actually starts with the length of the entire string. Thus, things like <code class="language-plaintext highlighter-rouge">.length</code> can be easily performed without iterating over the characters until reaching a <code class="language-plaintext highlighter-rouge">\0</code>, also enabling the possibility of strings that are not null terminated. The disassembly clearly tells us this story, actually:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.data:00000001801237E0 unk_1801237E0   db 0FFh                 ; DATA XREF: sub_18001F690:loc_18001F943↑o
.data:00000001801237E0                                         ; csync_is_windows_reserved_word(QStringRef const &amp;)+276↑o
.data:00000001801237E1                 db 0FFh
.data:00000001801237E2                 db 0FFh
.data:00000001801237E3                 db 0FFh
.data:00000001801237E4 dword_1801237E4 dd 0Bh                  ; DATA XREF: sub_18001F690+2BE↑r
.data:00000001801237E8                 align 10h
.data:00000001801237F0                 db  18h
.data:00000001801237F1                 db    0
.data:00000001801237F2                 db    0
.data:00000001801237F3                 db    0
.data:00000001801237F4                 db    0
.data:00000001801237F5                 db    0
.data:00000001801237F6                 db    0
.data:00000001801237F7                 db 0
.data:00000001801237F8                 db  64h ; d
.data:00000001801237F9                 db    0
.data:00000001801237FA                 db  65h ; e
.data:00000001801237FB                 db    0
.data:00000001801237FC                 db  73h ; s
.data:00000001801237FD                 db    0
.data:00000001801237FE                 db  6Bh ; k
.data:00000001801237FF                 db    0
.data:0000000180123800                 db  74h ; t
.data:0000000180123801                 db    0
.data:0000000180123802                 db  6Fh ; o
.data:0000000180123803                 db    0
.data:0000000180123804                 db  70h ; p
.data:0000000180123805                 db    0
.data:0000000180123806                 db  2Eh ; .
.data:0000000180123807                 db    0
.data:0000000180123808                 db  69h ; i
.data:0000000180123809                 db    0
.data:000000018012380A                 db  6Eh ; n
.data:000000018012380B                 db    0
.data:000000018012380C                 db  69h ; i
.data:000000018012380D                 db    0
.data:000000018012380E                 db    0
.data:000000018012380F                 db    0
</code></pre></div></div>

<p>So, actually, IDA identified the beginning of the <code class="language-plaintext highlighter-rouge">QStringLiteral</code> instance, and called it <code class="language-plaintext highlighter-rouge">unk_1801237E0</code>. Where is that referenced in the disassembly? Well, exactly where the <code class="language-plaintext highlighter-rouge">QStringLiteral</code> is used, to the portion of the disassembly that corresponds to the source code I presented above. In pseudocode form, it looks like this:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>      <span class="n">v25</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">unk_1801237E0</span><span class="p">;</span>
	  <span class="k">if</span> <span class="p">(</span> <span class="n">v9</span> <span class="o">!=</span> <span class="n">dword_1801237E4</span> <span class="o">||</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">int</span><span class="p">)</span><span class="n">QStringRef</span><span class="o">::</span><span class="n">compare</span><span class="p">(</span><span class="o">&amp;</span><span class="n">v19</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">v25</span><span class="p">,</span> <span class="mi">0</span><span class="n">i64</span><span class="p">)</span> <span class="p">)</span>
      <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span> <span class="n">v2</span> <span class="o">&amp;&amp;</span> <span class="n">OCC</span><span class="o">::</span><span class="n">Utility</span><span class="o">::</span><span class="n">isConflictFile</span><span class="p">(</span><span class="n">v3</span><span class="p">,</span> <span class="n">v15</span><span class="p">)</span> <span class="p">)</span>
          <span class="n">v5</span> <span class="o">=</span> <span class="mi">9</span><span class="p">;</span>
        <span class="k">else</span>
          <span class="n">v5</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
      <span class="p">}</span>
</code></pre></div></div>

<p>So, <code class="language-plaintext highlighter-rouge">v9 != dword_1801237E4</code> basically says “if the length of the current string (v9) is different from the length of the <code class="language-plaintext highlighter-rouge">desktop.ini</code> string (which is just the memory location <code class="language-plaintext highlighter-rouge">dword_1801237E4</code> from the above, as, you can see, right there at the beginning, after some zeros, it contains the length of the string at <code class="language-plaintext highlighter-rouge">dword_1801237E4</code> (<code class="language-plaintext highlighter-rouge">0xB</code> which is 11, as <code class="language-plaintext highlighter-rouge">desktop.ini</code> has 11 characters)”. Current string in this conetxt means a string containing the name of the current file that the program is working with.</p>

<p>Then, <code class="language-plaintext highlighter-rouge">(unsigned int)QStringRef::compare(&amp;v19, &amp;v25, 0i64)</code> says “if the current string is not equal to the <code class="language-plaintext highlighter-rouge">desktop.ini</code> string, represented by that <code class="language-plaintext highlighter-rouge">QStringLiteral</code> instance”. If we want IDA to pick that string when searching text using <code class="language-plaintext highlighter-rouge">Alt</code>+<code class="language-plaintext highlighter-rouge">T</code>, we can define the portion where the string is located as a string by clicking on the beginning of it, then <code class="language-plaintext highlighter-rouge">Alt</code>+<code class="language-plaintext highlighter-rouge">A</code> and then choosing the “Unicode C-style (16 bits)” option.</p>

<p>The entire condition from the <code class="language-plaintext highlighter-rouge">if</code> check in the source code is negated, so to say. The statements are mathematically proven to be equivalent, you can look into <a href="https://en.wikipedia.org/wiki/De_Morgan%27s_laws">De Morgan’s laws</a> for that. Basically:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>if (blen == static_cast&lt;qsizetype&gt;(desktopIniFile.length()) &amp;&amp; bname.compare(desktopIniFile, Qt::CaseInsensitive) == 0)

is equivalent to NOT the following:

if ( v9 != dword_1801237E4 || (unsigned int)QStringRef::compare(&amp;v19, &amp;v25, 0i64) )
</code></pre></div></div>

<p>So, in the pseudocode generated from the disassembly, what’s inside the if branches is what followed if we wouldn’t have entered the if branch in the original source code.</p>

<p>To patch this, we basically want the if check to disappear and always continue with that it has inside, equivalent to failing the if on the original source code and continuing with what was next there. To devise a patch, a look on the disassembly is required:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:000000018001F943 loc_18001F943:                          ; CODE XREF: sub_18001F690+268↑j
.text:000000018001F943                 lea     rax, unk_1801237E0
.text:000000018001F94A                 mov     [rbp+arg_18], rax
.text:000000018001F94E                 movsxd  rax, cs:dword_1801237E4
.text:000000018001F955                 cmp     r15, rax
.text:000000018001F958                 jnz     short loc_18001F96F
.text:000000018001F95A                 xor     r8d, r8d
.text:000000018001F95D                 lea     rdx, [rbp+arg_18]
.text:000000018001F961                 lea     rcx, [rbp+var_20]
.text:000000018001F965                 call    cs:?compare@QStringRef@@QEBAHAEBVQString@@W4CaseSensitivity@Qt@@@Z ; QStringRef::compare(QString const &amp;,Qt::CaseSensitivity)
.text:000000018001F96B                 test    eax, eax
.text:000000018001F96D                 jz      short loc_18001F990
.text:000000018001F96F
.text:000000018001F96F loc_18001F96F:                          ; CODE XREF: sub_18001F690+2C8↑j
.text:000000018001F96F                 test    r12b, r12b
.text:000000018001F972                 jz      short loc_18001F98E
.text:000000018001F974                 mov     rcx, rsi        ; this
.text:000000018001F977                 call    ?isConflictFile@Utility@OCC@@YA_NAEBVQString@@@Z ; OCC::Utility::isConflictFile(QString const &amp;)
.text:000000018001F97C                 test    al, al
.text:000000018001F97E                 jz      short loc_18001F98E
.text:000000018001F980                 mov     edi, 9
.text:000000018001F985                 jmp     short loc_18001F990
</code></pre></div></div>

<p>So, the first part of the <code class="language-plaintext highlighter-rouge">if</code> check is this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:000000018001F958                 jnz     short loc_18001F96F
</code></pre></div></div>

<p>Only if the comparison is not zero, we jump to the area inside the brackets (<code class="language-plaintext highlighter-rouge">loc_18001F96F</code>), but instead, we always want to jump there.</p>

<p>How do the opcodes for that look? Of course, since the thing is so close in the disassembly, a short conditional jump instruction is used: <code class="language-plaintext highlighter-rouge">0x75 0x15</code>: <code class="language-plaintext highlighter-rouge">0x75</code> means <code class="language-plaintext highlighter-rouge">jnz</code> and <code class="language-plaintext highlighter-rouge">0x15</code> is the offset relative to the current instruction. To jump there all the times, the patch is easy: turn <code class="language-plaintext highlighter-rouge">0x75</code> to <code class="language-plaintext highlighter-rouge">0xEB</code> which is an unconditional jump. It’s very handy that only a single byte has to be patched after all.</p>

<p>Of course, I replace that byte in a hex editor, close Nextcloud, replace the original file with the modified one, reopen Nextcloud, wait a bit, and yeah, indeed, the client now simply uploaded the <code class="language-plaintext highlighter-rouge">desktop.ini</code> files as well. Nice.</p>

<h3 id="future">Future</h3>

<p>How to find this easily in the future? By examining the source code, we can try to identify based on the call to <code class="language-plaintext highlighter-rouge">OCC::Utility::isConflictFile</code> from below. That function has 2 overloads; in the case of our source code, the one taking in a <code class="language-plaintext highlighter-rouge">QString</code> appears to be used, since <code class="language-plaintext highlighter-rouge">path</code> is of type <code class="language-plaintext highlighter-rouge">QString</code>, as defined in the function prototype. Of course, overloads only exist in the C++ world - when everything gets compiled, 2 actual methods are generated, with symbol names that reflect the arguments for each of the methods, since the name is not enough anymore to distinguish the two, but we know that each overload has different arguments (in that, <code class="language-plaintext highlighter-rouge">void foo(char a)</code> and <code class="language-plaintext highlighter-rouge">void foo(char b)</code> is illegal in C++).</p>

<p>So, open the DLL in IDA, search for <code class="language-plaintext highlighter-rouge">OCC::Utility::isConflictFile</code> in the Functions window, pick the method that takes in a <code class="language-plaintext highlighter-rouge">QString</code>. <code class="language-plaintext highlighter-rouge">Ctrl</code>+<code class="language-plaintext highlighter-rouge">X</code> will find its references, and at the moment there are only 3 of them:</p>

<p><img src="https://user-images.githubusercontent.com/6503598/162845134-c94267e0-431e-435a-8872-57e09b54e396.png" alt="image" /></p>

<p>Out of those, as you can see, only one of them is from an actual method, so probably what we are interested in. If we go there, we can see that was indeed the case, it gets us right to where that check for whether the file is called <code class="language-plaintext highlighter-rouge">desktop.ini</code> is performed in the code. From there, identifying the byte to patch shouldn’t be too difficult.</p>

<h3 id="conclusion">Conclusion</h3>

<p>Is it worth binary patching open source programs? Well, it’s always worth binary patching programs in general, if you can. With open source programs, the BIG advantage is that you always have the source code at hand, which can help you much easily and better understand what the heck is happening in the final executable. Sometimes, the compiler takes decisions that definitely help in some way, like reducing space, increasing execution speed, but which make human reading the program very tought to actually understand, and of course, having the source code is of big help.</p>

<p>And the truth is, upstreaming this is pretty tough. It’s a very niche use case that I have, maybe it just glitches on my end or maybe the thing really is troublesome on the long run for some other reason. Idk, offering this behavior as an option may be too much work indeed, as well.</p>

<p>The trouble with binary patching are updates to the patched software. Anyway, here, since it’s just a single byte, the program is open source, maintaining it wouldn’t be that big of a pain. But still, it would be nice if computers were smart as to perform the steps described above (identifying <code class="language-plaintext highlighter-rouge">isConflictFile</code> etc) themselves on new versions and patch it automatically using the knowledge above. A framework where that would help in doing things like this with less struggle than the tradiitional methods. But yeah, unless I am missing something, that’s still a dream, and a big burden, as I have learnt many times, with having such a patch apply at runtime, would be the difficulty it would take to have our piece of code loaded in the Nextcloud process, and then determining a reliable strategy of identifying that byte and patching it - most of the times without symbols/help generated by a tool like IDA. Idk, maybe one day though… Although, yeah, I am curious whether anyone knows a tool available today that would facilitate these kinds of binary patches - I personally don’t know of any, at the moment.</p>]]></content><author><name>Valentin Radu</name></author><summary type="html"><![CDATA[Is it worth binary patching open source programs, when you need (or fee like needing) that teeny-tiny change? And then, how do you approach it? Learn my take on this matter, with a practical example on the Nextcloud desktop client for Windows that refused to syncronize some files on my end.]]></summary></entry><entry><title type="html">Disable the rounded corners in Windows 11</title><link href="https://valinet.ro/2022/01/21/Disable-the-rounded-corners-in-Windows-11.html" rel="alternate" type="text/html" title="Disable the rounded corners in Windows 11" /><published>2022-01-21T00:00:00+00:00</published><updated>2022-01-21T00:00:00+00:00</updated><id>https://valinet.ro/2022/01/21/Disable-the-rounded-corners-in-Windows-11</id><content type="html" xml:base="https://valinet.ro/2022/01/21/Disable-the-rounded-corners-in-Windows-11.html"><![CDATA[<p>This is another article related to <a href="https://github.com/valinet/ExplorerPatcher">ExplorerPatcher</a> and Windows 11. This time, I wanted to take a more in-depth look at the changes in the compositor shipped with Windows 11 (the “Desktop Window Manager”).</p>

<p>Quite a few things have changed regarding it compared to Windows 10, like new window animations when maximizing and restoring down a window, while some things have regressed: Aero Peek is more broken than ever, basically a barely working artifact that is due for removal, apparently. Some things are the same, like the bug where clicks (on the very top of the title bar, or on the corner of the close button) on a maximized System-enhanced scaled foreground window displayed on a monitor with 150% and 3840x2160 resolution go to the window behind it, potentially accidentally closing it.</p>

<p>There is a rather visible new addition though: the window corners are now rounded. The whole design aesthetic for Windows 11 proposes rounded things everywhere. Personally I like them, but a lot of people do not, and for good reason: it would have been logical for Microsoft to offer at least a hidden option to enable the legacy behavior from Windows 10, where the corners are sharp, 90-degree angle. Unfortunately, they did not, so we are once again left to scramble through their executables and see what we can find.</p>

<p>The Desktop Window Manager is powered by many libraries; the one that apparently is tasked with doing the actual drawing on the screen surface is called <code class="language-plaintext highlighter-rouge">uDWM.dll</code>. I am a bit familiar with it from my work on <a href="https://github.com/valinet/WinCenterTitle">WinCenterTitle</a>, a program which let you center the text displayed in the non-client title bar of windows, as in Windows 8 and 8.1. Take notice, I said non-client: the compositor is only responsible for drawing the title bar of windows that do not elect to do it themselves (and inform it so). More and more applications are moving to custom-drawn title bars (or how the GNU/Linux/GNOME user land calls them, “client-side decorations”); whether that’s a good or a bad thing, that’s a subject of much debate. Thus, the effect of the patch becomes less and less impressive and consistent, as some applications will have their text centered while a whole host of others won’t. The operating system does not provide a mechanism to inform applications how the text should be drawn: it is generally implied that the text is left-aligned, while Windows 8 and 8.1 should be treated as the exception and the title be custom-drawn centered there. Again, very poor design from Microsoft, if you ask me.</p>

<p>Okay, so let’s start with <code class="language-plaintext highlighter-rouge">uDWM.dll</code>. If you look through the method list for it, one can quickly find a very interesting function: <code class="language-plaintext highlighter-rouge">CTopLevelWindow::GetEffectiveCornerStyle</code>. Here’s its pseudocode:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">__int64</span> <span class="kr">__fastcall</span> <span class="n">CTopLevelWindow</span><span class="o">::</span><span class="n">GetEffectiveCornerStyle</span><span class="p">(</span><span class="n">__int64</span> <span class="n">a1</span><span class="p">)</span>
<span class="p">{</span>
  <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">v2</span><span class="p">;</span> <span class="c1">// ecx</span>
  <span class="kt">int</span> <span class="n">v3</span><span class="p">;</span> <span class="c1">// ebx</span>
  <span class="kt">char</span> <span class="n">Variant</span><span class="p">;</span> <span class="c1">// al</span>

  <span class="k">if</span> <span class="p">(</span> <span class="o">*</span><span class="p">((</span><span class="n">_BYTE</span> <span class="o">*</span><span class="p">)</span><span class="n">CDesktopManager</span><span class="o">::</span><span class="n">s_pDesktopManagerInstance</span> <span class="o">+</span> <span class="mi">27</span><span class="p">)</span>
    <span class="o">&amp;&amp;</span> <span class="o">!*</span><span class="p">((</span><span class="n">_BYTE</span> <span class="o">*</span><span class="p">)</span><span class="n">CDesktopManager</span><span class="o">::</span><span class="n">s_pDesktopManagerInstance</span> <span class="o">+</span> <span class="mi">28</span><span class="p">)</span>
    <span class="o">||</span> <span class="o">*</span><span class="p">((</span><span class="kt">int</span> <span class="o">*</span><span class="p">)</span><span class="n">CDesktopManager</span><span class="o">::</span><span class="n">s_pDesktopManagerInstance</span> <span class="o">+</span> <span class="mi">8</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="mi">2</span> <span class="p">)</span>
  <span class="p">{</span>
    <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="k">else</span>
  <span class="p">{</span>
    <span class="n">v2</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)(</span><span class="o">*</span><span class="p">(</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)(</span><span class="n">a1</span> <span class="o">+</span> <span class="mi">752</span><span class="p">)</span> <span class="o">+</span> <span class="mi">184</span><span class="n">i64</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="n">v2</span> <span class="p">)</span>
    <span class="p">{</span>
      <span class="n">v3</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)(</span><span class="n">a1</span> <span class="o">+</span> <span class="mi">608</span><span class="p">);</span>
      <span class="k">if</span> <span class="p">(</span> <span class="p">(</span><span class="n">v3</span> <span class="o">&amp;</span> <span class="mi">2</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span> <span class="p">)</span>
      <span class="p">{</span>
        <span class="k">return</span> <span class="mi">3</span><span class="p">;</span>
      <span class="p">}</span>
      <span class="k">else</span>
      <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kr">__int8</span><span class="p">)</span><span class="n">IsOpenThemeDataPresent</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">v3</span> <span class="o">&amp;</span> <span class="mi">6</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span> <span class="p">)</span>
          <span class="k">return</span> <span class="mi">2</span><span class="p">;</span>

        <span class="n">Variant</span> <span class="o">=</span> <span class="n">wil</span><span class="o">::</span><span class="n">details</span><span class="o">::</span><span class="n">FeatureImpl</span><span class="o">&lt;</span><span class="n">__WilFeatureTraits_Feature_VTFrame</span><span class="o">&gt;::</span><span class="n">__private_GetVariant</span><span class="p">(</span><span class="o">&amp;</span><span class="err">`</span><span class="n">wil</span><span class="o">::</span><span class="n">Feature</span><span class="o">&lt;</span><span class="n">__WilFeatureTraits_Feature_VTFrame</span><span class="o">&gt;::</span><span class="n">GetImpl</span><span class="err">'</span><span class="o">::</span><span class="err">`</span><span class="mi">2</span><span class="err">'</span><span class="o">::</span><span class="n">impl</span><span class="p">);</span>
        <span class="n">v2</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span> <span class="n">Variant</span> <span class="o">==</span> <span class="mi">1</span> <span class="p">)</span>
          <span class="k">return</span> <span class="mi">2</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="k">return</span> <span class="n">v2</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Let’s analyze it a bit. For once, we can determine which branch in that main <code class="language-plaintext highlighter-rouge">if</code> is taken at run time on a system where rounded corners work in Windows 11 (more on that a bit later), and also determine the actual return value.</p>

<p>Because we need to attach a debugger to <code class="language-plaintext highlighter-rouge">dwm.exe</code>, things are a bit more complicated: you have to debug it on a virtual machine (or on a remote system in general). You can’t debug it on your development box as breaking into <code class="language-plaintext highlighter-rouge">dwm.exe</code> will prevent it from drawing updates to the screen, and thus you won’t be able to operate the computer.</p>

<p>As usual, I use WinDbg. Start it on the remote computer. Now, there are 2 methods to go on:</p>

<ul>
  <li>Attach to <code class="language-plaintext highlighter-rouge">dwm.exe</code>. When it breaks, the output will freeze, but the command text box will be focused. You can type in there <code class="language-plaintext highlighter-rouge">.server tcp:port=5005</code> followed by <code class="language-plaintext highlighter-rouge">g</code>. That will have the debugger listen on port 5005 and then continue executing <code class="language-plaintext highlighter-rouge">dwm.exe</code>. The output window will display a connection string that you can use to connect to this instance from the development box.</li>
  <li>Attach to <code class="language-plaintext highlighter-rouge">winlogon.exe</code> - this is the process that spawns new <code class="language-plaintext highlighter-rouge">dwm.exe</code> instances in case it crashes. After attaching, use <code class="language-plaintext highlighter-rouge">.childdbg 1</code> to have it break and also attach to child processes spawned by it. Press <code class="language-plaintext highlighter-rouge">g</code> to continue, and then kill <code class="language-plaintext highlighter-rouge">dwm.exe</code>. WinDbg will break and from there you can interact with the newly spawned <code class="language-plaintext highlighter-rouge">dwm.exe</code>.</li>
</ul>

<p>Okay, so inspecting this function at runtime, we can see that it returns a <code class="language-plaintext highlighter-rouge">2</code>. If we statically patch it to return a <code class="language-plaintext highlighter-rouge">0</code> or a <code class="language-plaintext highlighter-rouge">1</code>, window corners will draw not rounded. So, that would be it, right? Actually, this patch was already implemented in my previous <a href="https://github.com/valinet/Win11DisableRoundedCorners">Win11DisableOrRestoreRoundedCorners</a> utility, so why all the fuss with this article (I mean, it was quite popular, to the point that it got featured in a <a href="https://www.youtube.com/watch?v=GZPRrYLGrhI&amp;t=461s">LinusTechTips video</a>. Well, I have 2 more goals for this:</p>

<ul>
  <li>Get rid of the dependency on symbol data</li>
  <li>Fix the context menus not having a proper shadows applied when using this mode</li>
</ul>

<p>For the first bullet point, let’s look at this part of the <code class="language-plaintext highlighter-rouge">if</code> statement: <code class="language-plaintext highlighter-rouge">*((_BYTE*)CDesktopManager::s_pDesktopManagerInstance + 27) &amp;&amp; !*((_BYTE *)CDesktopManager::s_pDesktopManagerInstance + 28)</code>. At runtime, we see that the first expression is a <code class="language-plaintext highlighter-rouge">false</code>, while the <code class="language-plaintext highlighter-rouge">true</code>. So, we would need to make <code class="language-plaintext highlighter-rouge">*((_BYTE*)CDesktopManager::s_pDesktopManagerInstance + 27)</code> behave as if it were <code class="language-plaintext highlighter-rouge">true</code>, for example. Looking on the opcodes, we can see that <code class="language-plaintext highlighter-rouge">80 78 1B 00</code> (which coresponds to <code class="language-plaintext highlighter-rouge">cmp  byte ptr [rax+1Bh], 0</code>) is unique through the entire program. So we have an easy pattern match, right, just modify the comparison so that it behaves like a <code class="language-plaintext highlighter-rouge">jz</code> on the next instruction instead of <code class="language-plaintext highlighter-rouge">jnz loc_18007774E</code>, or modify the jump. Easy, but there’s still bullet point 2. Also, what does this <code class="language-plaintext highlighter-rouge">if</code> statement really check? Maybe it hides some registry setting which we could use to enable this easily without patching the executable…? let’s break down the <code class="language-plaintext highlighter-rouge">if</code> statement in parts:</p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">!*((_BYTE *)CDesktopManager::s_pDesktopManagerInstance + 28)</code></p>

    <p>Naturally, I have started looking in the constructor for <code class="language-plaintext highlighter-rouge">CDesktopManager</code> (which is an object but it is used as if it is a singleton throughout the entire program, it’s only instantiated once, and its reference kept in the <code class="language-plaintext highlighter-rouge">s_pDesktopManagerInstance</code> global variable).</p>

    <p><code class="language-plaintext highlighter-rouge">CDesktopManager::CDesktopManager</code> just zeroizes fields and fills in the virtual function tables properly, but let’s see where it is called from: <code class="language-plaintext highlighter-rouge">CDesktopManager::Create</code> (which in turn is called by <code class="language-plaintext highlighter-rouge">DwmClientStartup</code>, which is exported by ordinal 101 and looks like the entry point of this library). After the constructor, a call to <code class="language-plaintext highlighter-rouge">CDesktopManager::Initialize</code> is made.</p>

    <p>Right in the beginning of that, some registry calls are performed. What’s interesting is that one of them writes to <code class="language-plaintext highlighter-rouge">*((_BYTE *)this + 28) = 1;</code>. Bingo, so DWORD <code class="language-plaintext highlighter-rouge">ForceEffectMode</code> in <code class="language-plaintext highlighter-rouge">HKLM\Software\Microsoft\Windows\Dwm</code> sets <code class="language-plaintext highlighter-rouge">*((_BYTE *)CDesktopManager::s_pDesktopManagerInstance + 28)</code>. So the condition is <code class="language-plaintext highlighter-rouge">true</code> when the registry value is NOT set to <code class="language-plaintext highlighter-rouge">2</code>.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">*((_BYTE *)CDesktopManager::s_pDesktopManagerInstance + 27)</code></p>

    <p>This one was a bit more complicated. I now know it’s written in <code class="language-plaintext highlighter-rouge">CDesktopManager::CreateMonitorRenderTargetsLegacy</code> by <code class="language-plaintext highlighter-rouge">*((_BYTE *)this + 27) = IsWarpAdapterLuid;</code>. <code class="language-plaintext highlighter-rouge">IsWarpAdapterLuid</code> is the value returned by <code class="language-plaintext highlighter-rouge">CDWMDXGIEnumeration::IsWarpAdapterLuid</code>.</p>

    <p>But first, how do I know this was written here? Well, as Cheat Engine, WinDbg also has a very nice feature where it can break when a memory location is accessed: <code class="language-plaintext highlighter-rouge">ba w 1 address</code> (w is for write-access, 1 is for 1-byte length). So, just break at some early point where you have access to <code class="language-plaintext highlighter-rouge">CDesktopManager::s_pDesktopManagerInstance</code> and then set a breakpoint on access for writes to that <code class="language-plaintext highlighter-rouge">+ 27</code> and that’s it.</p>

    <p>Okay, so what is <code class="language-plaintext highlighter-rouge">CDWMDXGIEnumeration::IsWarpAdapterLuid</code>. From its body (and name), we see that it tries to determine whether the graphics adapter (used) is the WARP (software rendered) adapter. This is plenty obvious as well once we take a look at this <code class="language-plaintext highlighter-rouge">if</code> statement specifically: <code class="language-plaintext highlighter-rouge">if ( a2 == *(_QWORD *)(v5 + 336) &amp;&amp; *(_DWORD *)(v5 + 296) == 5140 &amp;&amp; *(_DWORD *)(v5 + 300) == 140 )</code> - <code class="language-plaintext highlighter-rouge">5140</code> is <code class="language-plaintext highlighter-rouge">0x1414</code> in hex, and <code class="language-plaintext highlighter-rouge">140</code> is <code class="language-plaintext highlighter-rouge">0x8c</code>. According to <a href="https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/d3d10-graphics-programming-guide-dxgi#new-info-about-enumerating-adapters-for-windows-8">this</a>, those IDs corespond to the “Microsoft Basic Render Driver”, which is basically the software-based graphics adapter that is used as a fallback when graphics drivers for the real adapter are not installed etc. Why is this important? Well, we can observe that on such a setup, the rounded corners are disabled. So, it has to be that in such scenarios,<code class="language-plaintext highlighter-rouge">*((_BYTE *)CDesktopManager::s_pDesktopManagerInstance + 27)</code> is set to <code class="language-plaintext highlighter-rouge">true</code>, as the adapter used was the software one, and then <code class="language-plaintext highlighter-rouge">!*((_BYTE *)CDesktopManager::s_pDesktopManagerInstance + 28)</code> is still 1 so the main branch is taken, the function returns <code class="language-plaintext highlighter-rouge">1</code> and rounded corners are disabled. Well, that means we are onto something: if we somehow make <code class="language-plaintext highlighter-rouge">!*((_BYTE *)CDesktopManager::s_pDesktopManagerInstance + 28)</code> return <code class="language-plaintext highlighter-rouge">false</code>, we can enable rounded corners when the software display adapter is used. But that’s pretty easy: all we have to do is to set <code class="language-plaintext highlighter-rouge">ForceEffectMode</code> in the registry to <code class="language-plaintext highlighter-rouge">2</code> and test it out:</p>

    <p><img src="https://user-images.githubusercontent.com/6503598/150562231-d0b71810-e7f8-461c-b8e6-c2710153475d.png" alt="image" /></p>

    <p>So, the opposite of what we want works (including context menus). Great start =))</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">*((int *)CDesktopManager::s_pDesktopManagerInstance + 8) &gt;= 2</code></p>

    <p>Using a similar break-on-access trick as described above, we can see that this is set in <code class="language-plaintext highlighter-rouge">CDesktopManager::UpdateRemotingMode</code> in possibly a couple of places. But that happens only when <code class="language-plaintext highlighter-rouge">GetSystemMetrics(4096)</code> is <code class="language-plaintext highlighter-rouge">true</code>, otherwise it is set in the end of the function to <code class="language-plaintext highlighter-rouge">0</code>: <code class="language-plaintext highlighter-rouge">*((_DWORD *)this + 8) = 0;</code>. So what does <code class="language-plaintext highlighter-rouge">GetSystemMetrics(4096)</code> do? Well, that’s sparsely documented on various forums on the Internet: it check whether the current session is a remote desktop session. From my impression, what this does is disable rounded corners under certain remote desktop scenarios. So yeah, we won’t bother anymore with that, it’s pretty useless for this experiment. I mean, it would actually be if we decided to inject the remote process, as we could IAT patch the call to <code class="language-plaintext highlighter-rouge">GetSystemMetrics</code> and return <code class="language-plaintext highlighter-rouge">true</code> when we get a <code class="language-plaintext highlighter-rouge">4096</code> so that we enter the <code class="language-plaintext highlighter-rouge">if</code> and from there IAT patch away the rest until we make sure we leave some number at that memory address.</p>
  </li>
</ul>

<p>Yeah, so the conclusion is: rounded corners are disabled when the software display adapter is used, or when connected via certain remote sessions. Also, there are a couple of avenues. At this point, my idea was simple: have the <code class="language-plaintext highlighter-rouge">CTopLevelWindow::GetEffectiveCornerStyle</code> function somehow return <code class="language-plaintext highlighter-rouge">1</code> via the methods described above.</p>

<p>Well, I did that, but it has at least one nasty effect: context menus are drawn without a shadow. It kind of makes sense, if we look at where the function is called from at runtime: <code class="language-plaintext highlighter-rouge">CTopLevelWindow::UpdateWindowVisuals</code>. In there, of interest is a <code class="language-plaintext highlighter-rouge">float</code> that is initialized to <code class="language-plaintext highlighter-rouge">0.0</code> and then assigned <em>some</em> value when the corners are determined to be rounded - probably the radius of the curve. When corners are not touched, the number stays at <code class="language-plaintext highlighter-rouge">0.0</code>. If we look further down, there is an <code class="language-plaintext highlighter-rouge">if</code> check: <code class="language-plaintext highlighter-rouge">if ( (v10 &amp; 0x20) == 0 &amp;&amp; (IsOpenThemeDataPresent() &amp;&amp; (v10 &amp; 6) != 0 || v4 &gt; 0.0) )</code>. I tired messing with it in all the ways, in combination with patching <code class="language-plaintext highlighter-rouge">CTopLevelWindow::GetEffectiveCornerStyle</code> as well - I always broke some scenario: I had windows without shadows, windows that are drawn square cornered even when rounded corners are enabled (tooltips, for example) display visual glitches, the mouse display visual glitches (a transparent box behind it when clicked). I was stuck…</p>

<p>Then, after playing with it a bit more, I cam around with some other idea: how would we go about modifying the least amount of code and logic and still achieve what we want? Well, since the radius seems to be a <code class="language-plaintext highlighter-rouge">float</code>, what if instead of <code class="language-plaintext highlighter-rouge">0.0</code>, which would give us 90-degree angles, we’d make it <code class="language-plaintext highlighter-rouge">0.00001</code> let’s say. That so small it would look like a square on the screen. Also, keep in mind that the thing is rasterized in the end and you have only a handful of pixels, so a very small float that’s not zero is basically zero.</p>

<p>Okay, so let’s inspect what happens with the value we get from <code class="language-plaintext highlighter-rouge">CTopLevelWindow::GetEffectiveCornerStyle</code>:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="p">{</span>  
    <span class="n">EffectiveCornerStyle</span> <span class="o">=</span> <span class="n">CTopLevelWindow</span><span class="o">::</span><span class="n">GetEffectiveCornerStyle</span><span class="p">((</span><span class="n">__int64</span><span class="p">)</span><span class="k">this</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span> <span class="n">EffectiveCornerStyle</span> <span class="o">==</span> <span class="mi">2</span> <span class="p">)</span>
    <span class="p">{</span>
<span class="nl">LABEL_8:</span>
      <span class="n">wil</span><span class="o">::</span><span class="n">details</span><span class="o">::</span><span class="n">FeatureImpl</span><span class="o">&lt;</span><span class="n">__WilFeatureTraits_Feature_VTFrame</span><span class="o">&gt;::</span><span class="n">GetCachedVariantState</span><span class="p">(</span>
        <span class="p">(</span><span class="k">volatile</span> <span class="kt">signed</span> <span class="n">__int64</span> <span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="err">`</span><span class="n">wil</span><span class="o">::</span><span class="n">Feature</span><span class="o">&lt;</span><span class="n">__WilFeatureTraits_Feature_VTFrame</span><span class="o">&gt;::</span><span class="n">GetImpl</span><span class="err">'</span><span class="o">::</span><span class="err">`</span><span class="mi">2</span><span class="err">'</span><span class="o">::</span><span class="n">impl</span><span class="p">,</span>
        <span class="p">(</span><span class="n">__int64</span><span class="p">)</span><span class="o">&amp;</span><span class="n">v118</span><span class="p">);</span>
      <span class="n">v4</span> <span class="o">=</span> <span class="p">(</span><span class="kt">float</span><span class="p">)</span><span class="n">v119</span><span class="p">;</span>
      <span class="k">goto</span> <span class="n">LABEL_9</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span> <span class="n">EffectiveCornerStyle</span> <span class="o">!=</span> <span class="mi">3</span> <span class="p">)</span>
    <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span> <span class="n">EffectiveCornerStyle</span> <span class="o">!=</span> <span class="mi">4</span> <span class="p">)</span>
        <span class="k">goto</span> <span class="n">LABEL_9</span><span class="p">;</span>

      <span class="k">goto</span> <span class="n">LABEL_8</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">wil</span><span class="o">::</span><span class="n">details</span><span class="o">::</span><span class="n">FeatureImpl</span><span class="o">&lt;</span><span class="n">__WilFeatureTraits_Feature_VTFrame</span><span class="o">&gt;::</span><span class="n">GetCachedVariantState</span><span class="p">(</span>
      <span class="p">(</span><span class="k">volatile</span> <span class="kt">signed</span> <span class="n">__int64</span> <span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="err">`</span><span class="n">wil</span><span class="o">::</span><span class="n">Feature</span><span class="o">&lt;</span><span class="n">__WilFeatureTraits_Feature_VTFrame</span><span class="o">&gt;::</span><span class="n">GetImpl</span><span class="err">'</span><span class="o">::</span><span class="err">`</span><span class="mi">2</span><span class="err">'</span><span class="o">::</span><span class="n">impl</span><span class="p">,</span>
      <span class="p">(</span><span class="n">__int64</span><span class="p">)</span><span class="o">&amp;</span><span class="n">v118</span><span class="p">);</span>
    <span class="n">v4</span> <span class="o">=</span> <span class="p">(</span><span class="kt">float</span><span class="p">)</span><span class="n">v119</span> <span class="o">*</span> <span class="mf">0.5</span><span class="p">;</span>
  <span class="p">}</span>

<span class="nl">LABEL_9:</span>
</code></pre></div></div>

<p>Actually, the pseudocode this time is pretty bad. The raw diassembly is much better:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:0000000180029B3D                 call    ?GetEffectiveCornerStyle@CTopLevelWindow@@AEAA?AW4CORNER_STYLE@@XZ ; CTopLevelWindow::GetEffectiveCornerStyle(void)
.text:0000000180029B42                 mov     ecx, eax
.text:0000000180029B44                 sub     ecx, 2
.text:0000000180029B47                 jz      short loc_180029B57
.text:0000000180029B49                 sub     ecx, r15d
.text:0000000180029B4C                 jz      loc_180029C6A
.text:0000000180029B52                 cmp     ecx, r15d
.text:0000000180029B55                 jnz     short loc_180029B74
.text:0000000180029B57
.text:0000000180029B57 loc_180029B57:                          ; CODE XREF: CTopLevelWindow::UpdateWindowVisuals(void)+97↑j
.text:0000000180029B57                 lea     rdx, [rsp+1F0h+var_190]
.text:0000000180029B5C                 lea     rcx, ?impl@?1??GetImpl@?$Feature@U__WilFeatureTraits_Feature_VTFrame@@@wil@@CAAEAV?$FeatureImpl@U__WilFeatureTraits_Feature_VTFrame@@@details@3@XZ@4V453@A ; wil::details::FeatureImpl&lt;__WilFeatureTraits_Feature_VTFrame&gt; `wil::Feature&lt;__WilFeatureTraits_Feature_VTFrame&gt;::GetImpl(void)'::`2'::impl
.text:0000000180029B63                 call    ?GetCachedVariantState@?$FeatureImpl@U__WilFeatureTraits_Feature_VTFrame@@@details@wil@@AEAA?ATwil_details_FeatureStateCache@@XZ ; wil::details::FeatureImpl&lt;__WilFeatureTraits_Feature_VTFrame&gt;::GetCachedVariantState(void)
.text:0000000180029B68                 mov     eax, [rsp+1F0h+var_18C]
.text:0000000180029B6C                 xorps   xmm6, xmm6
.text:0000000180029B6F                 cvtsi2ss xmm6, rax
</code></pre></div></div>

<p>So, if the corner style is <code class="language-plaintext highlighter-rouge">2</code>, we jump to that place where we make the weird <code class="language-plaintext highlighter-rouge">GetCachedVariantState</code> call. Then, we move some value from the stack in <code class="language-plaintext highlighter-rouge">rax</code> and from there convert it to a single-precision floating point number.</p>

<p>At runtime, the value we obtain is <code class="language-plaintext highlighter-rouge">0x8</code>. It’s pretty weird… I investigated with different values, it doesn’t seem to actually hold a meaningful IEE754 single-precision floating point value, or maybe it just didn’t tick for me how to work with it. Anyway, by experimenting, I saw that a <code class="language-plaintext highlighter-rouge">0x0</code> there is essentially <code class="language-plaintext highlighter-rouge">CTopLevelWindow::GetEffectiveCornerStyle</code> returning <code class="language-plaintext highlighter-rouge">1</code>, while if we set to <code class="language-plaintext highlighter-rouge">0x1</code>… boom, we get the nice 90-degree corners from Windows 10, complete with the context menu working.</p>

<p>Okay, so it seems the way to go is to make the radius as small as to be basically square nut not mathematically square, so <code class="language-plaintext highlighter-rouge">dwm.exe</code> would still think it works with rounded corners.</p>

<p>How do we patch? Well, if we look at the disassembly, we see that <code class="language-plaintext highlighter-rouge">xorps  xmm6, xmm6; cvtsi2ss  xmm6, rax</code> only appears in constructs specific to a check similar to the one showed here after getting the corner style. So it’s actually safe to patch based on this pattern. But how? Well, again, if we look at all the <code class="language-plaintext highlighter-rouge">4</code> matches, we see they are all preceded by a <code class="language-plaintext highlighter-rouge">mov</code> instruction that fetches the value that will be ultimately converted to a <code class="language-plaintext highlighter-rouge">float</code>. So we can overwrite that safely, and in all places is even better, it is as if it would have read that value. So, how long is the <code class="language-plaintext highlighter-rouge">mov</code>? Well, 4 bytes. We need to write a 1 there, so <code class="language-plaintext highlighter-rouge">mov eax, 1</code> which is <code class="language-plaintext highlighter-rouge">b8 01 00 00 00</code> which is… 5 bytes long :(… CISC baby, what do we do now? Well, we take advantage of the fact that <code class="language-plaintext highlighter-rouge">x86</code> has a billion instructions and opcodes, so we can trick it in 4 bytes like so: <code class="language-plaintext highlighter-rouge">xor eax, eax; inc eax</code> - <code class="language-plaintext highlighter-rouge">31 c0 ff c0</code>. Oh yeah, good old <code class="language-plaintext highlighter-rouge">inc</code>.</p>

<p>So the patch is simple. Find this pattern <code class="language-plaintext highlighter-rouge">0x0F, 0x57, 0xF6, 0xF3, 0x48, 0x0F</code> and replace the preceding 4 bytes with <code class="language-plaintext highlighter-rouge">0x31, 0xC0, 0xFF, 0xC0</code>.</p>

<p>Lastly, how do we patch this time? The problem with <code class="language-plaintext highlighter-rouge">dwm.exe</code> is that it runs either as SYSTEM account, either under some obscure service accounts (<code class="language-plaintext highlighter-rouge">DWM-1</code>, <code class="language-plaintext highlighter-rouge">DWM-2</code> etc). We cannot, obviously, inject it from a process running with standard rights. Not even from an administrator one. We have to inject it from a process running as SYSTEM, only though there does it work.</p>

<p>Naturally, the way to go for this is to create a service, as that always runs as SYSTEM. So, we create a service that enumerates the Desktop Window Manager processes and patches 4 bytes at various locations. Since we only do that, no need to run remote code, we can forego injecting in <code class="language-plaintext highlighter-rouge">dwm.exe</code> and instead use <code class="language-plaintext highlighter-rouge">ReadProcessMemory</code> and <code class="language-plaintext highlighter-rouge">WriteProcessMemory</code> to alter its memory.</p>

<p>An example implementation is <a href="https://github.com/valinet/ep_dwm">here (ep_dwm)</a>. it can be compiled as a library and then called from your own application, for example.</p>

<p><img src="https://user-images.githubusercontent.com/6503598/150561707-3c6eeae2-298a-4512-8a35-9de33d49b888.png" alt="image" /></p>

<p>That’s it! The functionality has been incorporated in the latest ExplorerPatcher, version 22000.434.41.10. Hopefully it will serve you well.</p>

<p>Update (29/08/2022): I also recommend checking out <a href="https://github.com/oberrich/win11-toggle-rounded-corners">this project</a> on GitHub. It basically patches <code class="language-plaintext highlighter-rouge">CDesktopManager::s_pDesktopManagerInstance + 28</code> to be a <code class="language-plaintext highlighter-rouge">1</code>, with all the quirks described above (context menus and some windows, like the UAC prompt, do not have shadows). Still, it is an alternative to my strategy here. Hopefully, one day, I will have the time and maybe integrate this into ExplorerPatcher as an option.</p>]]></content><author><name>Valentin Radu</name></author><summary type="html"><![CDATA[This is another article related to ExplorerPatcher and Windows 11. This time, I wanted to take a more in-depth look at the changes in the compositor shipped with Windows 11 (the Desktop Window Manager). In this article, we learn how to disable the rounded corners in dwm.exe.]]></summary></entry><entry><title type="html">Functional Windows 10 flyouts in Windows 11</title><link href="https://valinet.ro/2021/11/18/Functional-Windows-10-flyouts-on-Windows-11.html" rel="alternate" type="text/html" title="Functional Windows 10 flyouts in Windows 11" /><published>2021-11-18T00:00:00+00:00</published><updated>2021-11-18T00:00:00+00:00</updated><id>https://valinet.ro/2021/11/18/Functional-Windows-10-flyouts-on-Windows-11</id><content type="html" xml:base="https://valinet.ro/2021/11/18/Functional-Windows-10-flyouts-on-Windows-11.html"><![CDATA[<p>Quite some time has passed since my last post here. And that’s for no ordinary reason: the past months, I have been pretty busy with work, both at my workplace and while working on <a href="https://github.com/valinet/ExplorerPatcher">ExplorerPatcher</a>. So many have happened regarding that since I last talked about it here, that it’s just simpler to check it out yourself, and look through the <a href="https://github.com/valinet/ExplorerPatcher/blob/master/CHANGELOG.md">CHANGELOG</a> if you want, then for me to reiterate all the added funcionality. Basically, it has become a full fledged program in its own.</p>

<p>But today’s topic is not really about ExplorerPatcher. Or rather it is, but not directly about it, but about how I enabled a certain functionality for it and how it can be achieved even without it and what are the limitations.</p>

<h3 id="background">Background</h3>

<p>As you are probably aware, Windows 11 has recently launched, and with it, a new radical design change. To keep it short, there are a couple of utilities that let you restore the old Windows 10 UI, one of which being ExplorerPatcher (there is also <a href="https://www.startallback.com/">StartAllBack</a>, for example).</p>

<p>One problem that was still unsolved was how to restore the functionality of the Windows 10 network, battery and language switcher flyouts in the taskbar. While volume and clock work well, and the language switcher is the one from Windows 11 at least, the network and battery ones just do not launch properly. So, what gives?</p>

<h3 id="investigation">Investigation</h3>

<p>I started my investigation… well, it took me the better part of a whole day to figure my way around how Windows does those things, but to spare you the time, let me tell you directly how it works.</p>

<p>First, most flyouts seem to be invoked by using some “experience manager” interface. Let’s generically call that <code class="language-plaintext highlighter-rouge">IExperienceManager</code>. It derives from <code class="language-plaintext highlighter-rouge">IInspectable</code> and the next 2 methods are the aptly named <code class="language-plaintext highlighter-rouge">ShowFlyout</code> and <code class="language-plaintext highlighter-rouge">HideFlyout</code> methods.</p>

<p>These usually live in <code class="language-plaintext highlighter-rouge">twinui.dll</code>. At least the ones common to all types of Windows devices (for example, the battery, clock, sound one). By the way, sound is called “MtcUvc”, whatever that means. The more specific ones live some places else, but are instanced by <code class="language-plaintext highlighter-rouge">twinui.dll</code>, so you usually can find the required interface IDs there. Also in there are the required names for these interfaces (dump all string containing <code class="language-plaintext highlighter-rouge">Windows.Internal.ShellExperience</code> from <code class="language-plaintext highlighter-rouge">twinui.dll</code> using <code class="language-plaintext highlighter-rouge">strings64.exe</code>  from Sysinternals, for example).</p>

<p>So, by looking on some disassembly in <code class="language-plaintext highlighter-rouge">twinui.dll</code>, I figured out that the way to invoke these flyouts is something like this:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">InvokeFlyout</span><span class="p">(</span><span class="n">BOOL</span> <span class="n">bAction</span><span class="p">,</span> <span class="n">DWORD</span> <span class="n">dwWhich</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">HRESULT</span> <span class="n">hr</span> <span class="o">=</span> <span class="n">S_OK</span><span class="p">;</span>
    <span class="n">IUnknown</span><span class="o">*</span> <span class="n">pImmersiveShell</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
    <span class="n">hr</span> <span class="o">=</span> <span class="n">CoCreateInstance</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="n">CLSID_ImmersiveShell</span><span class="p">,</span>
        <span class="nb">NULL</span><span class="p">,</span>
        <span class="n">CLSCTX_NO_CODE_DOWNLOAD</span> <span class="o">|</span> <span class="n">CLSCTX_LOCAL_SERVER</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">IID_IServiceProvider</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">pImmersiveShell</span>
    <span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">SUCCEEDED</span><span class="p">(</span><span class="n">hr</span><span class="p">))</span>
    <span class="p">{</span>
        <span class="n">IShellExperienceManagerFactory</span><span class="o">*</span> <span class="n">pShellExperienceManagerFactory</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
        <span class="n">IUnknown_QueryService</span><span class="p">(</span>
            <span class="n">pImmersiveShell</span><span class="p">,</span>
            <span class="o">&amp;</span><span class="n">CLSID_ShellExperienceManagerFactory</span><span class="p">,</span>
            <span class="o">&amp;</span><span class="n">CLSID_ShellExperienceManagerFactory</span><span class="p">,</span>
            <span class="o">&amp;</span><span class="n">pShellExperienceManagerFactory</span>
        <span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">pShellExperienceManagerFactory</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">HSTRING_HEADER</span> <span class="n">hstringHeader</span><span class="p">;</span>
            <span class="n">HSTRING</span> <span class="n">hstring</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
            <span class="n">WCHAR</span><span class="o">*</span> <span class="n">pwszStr</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
            <span class="k">switch</span> <span class="p">(</span><span class="n">dwWhich</span><span class="p">)</span>
            <span class="p">{</span>
            <span class="k">case</span> <span class="n">INVOKE_FLYOUT_NETWORK</span><span class="p">:</span>
                <span class="n">pwszStr</span> <span class="o">=</span> <span class="s">L"Windows.Internal.ShellExperience.NetworkFlyout"</span><span class="p">;</span>
                <span class="k">break</span><span class="p">;</span>
            <span class="k">case</span> <span class="n">INVOKE_FLYOUT_CLOCK</span><span class="p">:</span>
                <span class="n">pwszStr</span> <span class="o">=</span> <span class="s">L"Windows.Internal.ShellExperience.TrayClockFlyout"</span><span class="p">;</span>
                <span class="k">break</span><span class="p">;</span>
            <span class="k">case</span> <span class="n">INVOKE_FLYOUT_BATTERY</span><span class="p">:</span>
                <span class="n">pwszStr</span> <span class="o">=</span> <span class="s">L"Windows.Internal.ShellExperience.TrayBatteryFlyout"</span><span class="p">;</span>
                <span class="k">break</span><span class="p">;</span>
            <span class="k">case</span> <span class="n">INVOKE_FLYOUT_SOUND</span><span class="p">:</span>
                <span class="n">pwszStr</span> <span class="o">=</span> <span class="s">L"Windows.Internal.ShellExperience.MtcUvc"</span><span class="p">;</span>
                <span class="k">break</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="n">hr</span> <span class="o">=</span> <span class="n">WindowsCreateStringReference</span><span class="p">(</span>
                <span class="n">pwszStr</span><span class="p">,</span>
                <span class="n">pwszStr</span> <span class="o">?</span> <span class="n">wcslen</span><span class="p">(</span><span class="n">pwszStr</span><span class="p">)</span> <span class="o">:</span> <span class="mi">0</span><span class="p">,</span>
                <span class="o">&amp;</span><span class="n">hstringHeader</span><span class="p">,</span>
                <span class="o">&amp;</span><span class="n">hstring</span>
            <span class="p">);</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">hstring</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="n">IUnknown</span><span class="o">*</span> <span class="n">pIntf</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
                <span class="n">pShellExperienceManagerFactory</span><span class="o">-&gt;</span><span class="n">lpVtbl</span><span class="o">-&gt;</span><span class="n">GetExperienceManager</span><span class="p">(</span>
                    <span class="n">pShellExperienceManagerFactory</span><span class="p">,</span>
                    <span class="n">hstring</span><span class="p">,</span>
                    <span class="o">&amp;</span><span class="n">pIntf</span>
                <span class="p">);</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">pIntf</span><span class="p">)</span>
                <span class="p">{</span>
                    <span class="n">IExperienceManager</span><span class="o">*</span> <span class="n">pExperienceManager</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
                    <span class="n">pIntf</span><span class="o">-&gt;</span><span class="n">lpVtbl</span><span class="o">-&gt;</span><span class="n">QueryInterface</span><span class="p">(</span>
                        <span class="n">pIntf</span><span class="p">,</span>
                        <span class="n">dwWhich</span> <span class="o">==</span> <span class="n">INVOKE_FLYOUT_NETWORK</span> <span class="o">?</span> <span class="o">&amp;</span><span class="n">IID_NetworkFlyoutExperienceManager</span> <span class="o">:</span>
                        <span class="p">(</span><span class="n">dwWhich</span> <span class="o">==</span> <span class="n">INVOKE_FLYOUT_CLOCK</span> <span class="o">?</span> <span class="o">&amp;</span><span class="n">IID_TrayClockFlyoutExperienceManager</span> <span class="o">:</span>
                            <span class="p">(</span><span class="n">dwWhich</span> <span class="o">==</span> <span class="n">INVOKE_FLYOUT_BATTERY</span> <span class="o">?</span> <span class="o">&amp;</span><span class="n">IID_TrayBatteryFlyoutExperienceManager</span> <span class="o">:</span>
                                <span class="p">(</span><span class="n">dwWhich</span> <span class="o">==</span> <span class="n">INVOKE_FLYOUT_SOUND</span> <span class="o">?</span> <span class="o">&amp;</span><span class="n">IID_TrayMtcUvcFlyoutExperienceManager</span> <span class="o">:</span> <span class="o">&amp;</span><span class="n">IID_IUnknown</span><span class="p">))),</span>
                        <span class="o">&amp;</span><span class="n">pExperienceManager</span>
                    <span class="p">);</span>
                    <span class="k">if</span> <span class="p">(</span><span class="n">pExperienceManager</span><span class="p">)</span>
                    <span class="p">{</span>
                        <span class="n">RECT</span> <span class="n">rc</span><span class="p">;</span>
                        <span class="n">SetRect</span><span class="p">(</span><span class="o">&amp;</span><span class="n">rc</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
                        <span class="k">if</span> <span class="p">(</span><span class="n">bAction</span> <span class="o">==</span> <span class="n">INVOKE_FLYOUT_SHOW</span><span class="p">)</span>
                        <span class="p">{</span>
                            <span class="n">pExperienceManager</span><span class="o">-&gt;</span><span class="n">lpVtbl</span><span class="o">-&gt;</span><span class="n">ShowFlyout</span><span class="p">(</span><span class="n">pExperienceManager</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">rc</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
                        <span class="p">}</span>
                        <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">bAction</span> <span class="o">==</span> <span class="n">INVOKE_FLYOUT_HIDE</span><span class="p">)</span>
                        <span class="p">{</span>
                            <span class="n">pExperienceManager</span><span class="o">-&gt;</span><span class="n">lpVtbl</span><span class="o">-&gt;</span><span class="n">HideFlyout</span><span class="p">(</span><span class="n">pExperienceManager</span><span class="p">);</span>
                        <span class="p">}</span>
                        <span class="n">pExperienceManager</span><span class="o">-&gt;</span><span class="n">lpVtbl</span><span class="o">-&gt;</span><span class="n">Release</span><span class="p">(</span><span class="n">pExperienceManager</span><span class="p">);</span>
                    <span class="p">}</span>

                <span class="p">}</span>
                <span class="n">WindowsDeleteString</span><span class="p">(</span><span class="n">hstring</span><span class="p">);</span>
            <span class="p">}</span>
            <span class="n">pShellExperienceManagerFactory</span><span class="o">-&gt;</span><span class="n">lpVtbl</span><span class="o">-&gt;</span><span class="n">Release</span><span class="p">(</span><span class="n">pShellExperienceManagerFactory</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="n">pImmersiveShell</span><span class="o">-&gt;</span><span class="n">lpVtbl</span><span class="o">-&gt;</span><span class="n">Release</span><span class="p">(</span><span class="n">pImmersiveShell</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Full source code: <a href="https://github.com/valinet/ExplorerPatcher/blob/master/ExplorerPatcher/ImmersiveFlyouts.c">ImmersiveFlyouts.c</a>, <a href="https://github.com/valinet/ExplorerPatcher/blob/master/ExplorerPatcher/ImmersiveFlyouts.h">ImmersiveFlyouts.h</a></p>

<p>Okay, so with the above one can pretty much manually invoke the flyouts for any of the system icons shown in the notification area. Yet still, network and battery crash.</p>

<p>So, I attached with WinDbg to <code class="language-plaintext highlighter-rouge">explorer.exe</code> and clicked the network icon. Then, i saw it spits some error message:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(a0a0.6880): Windows Runtime Originate Error - code 40080201 (first chance)
(a0a0.6880): C++ EH exception - code e06d7363 (first chance)
(a0a0.6880): C++ EH exception - code e06d7363 (first chance)
(a0a0.6880): C++ EH exception - code e06d7363 (first chance)
(a0a0.6880): C++ EH exception - code e06d7363 (first chance)
pcshell\twinui\viewmanagerinterop\lib\windowmanagerbridge.cpp(216)\twinui.pcshell.dll!00007FFE213CA625: (caller: 00007FFE212B91D2) ReturnHr(11) tid(6880) 80070490 Element not found.
    Msg:[Platform::Exception^: Element not found.

pcshell\twinui\viewmanagerinterop\lib\windowmanagerbridge.cpp(174)\twinui.pcshell.dll!00007FFE212B94ED: (caller: 00007FFE212B91D2) Exception(7) tid(6880) 80070490 Element not found.
] 
shell\twinui\experiencemanagers\lib\networkexperiencemanager.cpp(107)\twinui.dll!00007FFE16C266DD: (caller: 00007FFE16C45B05) LogHr(2) tid(30d8) 80070578 Invalid window handle.
pcshell\twinui\viewmanagerinterop\lib\windowmanagerbridge.cpp(148)\twinui.pcshell.dll!00007FFE212B966C: (caller: 00007FFE212B91D2) LogHr(11) tid(84b0) 80070490 Element not found.
pcshell\twinui\viewmanagerinterop\lib\windowmanagerbridge.cpp(211)\twinui.pcshell.dll!00007FFE212B96A6: (caller: 00007FFE212B91D2) Exception(8) tid(84b0) 80070490 Element not found.
(a0a0.84b0): Windows Runtime Originate Error - code 40080201 (first chance)
(a0a0.84b0): C++ EH exception - code e06d7363 (first chance)
(a0a0.84b0): C++ EH exception - code e06d7363 (first chance)
(a0a0.84b0): C++ EH exception - code e06d7363 (first chance)
(a0a0.84b0): C++ EH exception - code e06d7363 (first chance)
pcshell\twinui\viewmanagerinterop\lib\windowmanagerbridge.cpp(216)\twinui.pcshell.dll!00007FFE213CA625: (caller: 00007FFE212B91D2) ReturnHr(12) tid(84b0) 80070490 Element not found.
    Msg:[Platform::Exception^: Element not found.

pcshell\twinui\viewmanagerinterop\lib\windowmanagerbridge.cpp(211)\twinui.pcshell.dll!00007FFE212B96A6: (caller: 00007FFE212B91D2) Exception(8) tid(84b0) 80070490 Element not found.
]
</code></pre></div></div>

<p>Okay… also, not much else can be done by attaching to <code class="language-plaintext highlighter-rouge">explorer.exe</code>, as the flyouts run out of process. After some trial and error, I figured out that the process they run into is <code class="language-plaintext highlighter-rouge">ShellExperienceHost.exe</code>. This just crashes when the 2 flyouts are invoked. So, let’s attach to it. To have it spawn and be able to attach to it, besides <code class="language-plaintext highlighter-rouge">.child dbg 1</code> in the parent process, in this instance we can see that once a flyout is launched, the process remains resident in memory, and gets suspended. So, I opened the sound flyout, and then attached with WinDbg. Clicked network and then the error was trapped. The call stack looked like this (up to the first frame that did not look like some error handler):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[0x0]   KERNELBASE!RaiseFailFastException + 0x152   
[0x1]   Windows_UI_QuickActions!wil::details::WilDynamicLoadRaiseFailFastException + 0x53   
[0x2]   Windows_UI_QuickActions!wil::details::WilRaiseFailFastException + 0x22   
[0x3]   Windows_UI_QuickActions!wil::details::WilFailFast + 0xbc   
[0x4]   Windows_UI_QuickActions!wil::details::ReportFailure_NoReturn&lt;3&gt; + 0x29f   
[0x5]   Windows_UI_QuickActions!wil::details::ReportFailure_Base&lt;3,0&gt; + 0x30   
[0x6]   Windows_UI_QuickActions!wil::details::ReportFailure_Msg&lt;3&gt; + 0x67   
[0x7]   Windows_UI_QuickActions!wil::details::ReportFailure_HrMsg&lt;3&gt; + 0x6e   
[0x8]   Windows_UI_QuickActions!wil::details::in1diag3::_FailFast_HrMsg + 0x33   
[0x9]   Windows_UI_QuickActions!wil::details::in1diag3::FailFast_HrIfNullMsg&lt;Windows::UI::Xaml::ResourceDictionary ^ __ptr64,0&gt; + 0x5c   
[0xa]   Windows_UI_QuickActions!QuickActions::QuickActionTemplateSelector::[Windows::UI::Xaml::Controls::IDataTemplateSelectorOverrides]::SelectTemplateCore + 0x99a   
</code></pre></div></div>

<p>So, it looks like the error is in that <code class="language-plaintext highlighter-rouge">SelectTemplateCore</code>. Also, the Windows Runtime again spat out something in the debugger:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>minkernel\mrt\mrm\mrmex\priautomerger.cpp(65)\MrmCoreR.dll!00007FFE3F2561BD: (caller: 00007FFE3F2676AD) LogHr(4) tid(3394) 80070002 The system cannot find the file specified.
ModLoad: 00007ffe`3cba0000 00007ffe`3cbd5000   C:\Windows\System32\Windows.Energy.dll
shellcommon\shell\windows.ui.shell\quickactions\quickactions\QuickActionTemplateSelector.cpp(84)\Windows.UI.QuickActions.dll!00007FFD8848A8DE: (caller: 00007FFD884954B7) FailFast(1) tid(3394) 80070490 Element not found.
    Msg:[QuickActionTemplateSelector missing a valid TemplateDictionary.] 
(8c30.3394): Security check failure or stack buffer overrun - code c0000409 (!!! second chance !!!)
Subcode: 0x7 FAST_FAIL_FATAL_APP_EXIT 
KERNELBASE!RaiseFailFastException+0x152:
00007ffe`57e01de2 0f1f440000      nop     dword ptr [rax+rax]
</code></pre></div></div>

<p>What’s interesting in that is it coroborates with what we have got from <code class="language-plaintext highlighter-rouge">explorer.exe</code>: that <code class="language-plaintext highlighter-rouge">80070490</code> HRESULT is the same we got in Explorer. Okay, so it seems some “dictionary” is broken for some quick action toggle, apparently… I don’t know exactly their internal structure, I am just guessing, of course.</p>

<p>Let’s load <code class="language-plaintext highlighter-rouge">Windows.UI.QuickActions.dll</code> in IDA and take a look at it. You can find this in the same folder as <code class="language-plaintext highlighter-rouge">ShellExperienceHost.exe</code>: <code class="language-plaintext highlighter-rouge">C:\Windows\SystemApps\ShellExperienceHost_cw5n1h2txyewy</code>.</p>

<p>We find that function quickly, among the sea of symbols, especially if we search for that <code class="language-plaintext highlighter-rouge">QuickActionTemplateSelector missing a valid TemplateDictionary</code> error string.</p>

<p>That code area gets called if either branch of some <code class="language-plaintext highlighter-rouge">if</code> check eventually fails. So we’re presumably on one of the branches. Without looking too much beside that, I thought: what if we would be on the other branch? A small note on that:</p>

<blockquote>
  <p>This thought didn’t really came that out of the blue. For the better part of a whole day, I started trying to find a way to do the same thing the lock screen (<code class="language-plaintext highlighter-rouge">LockApp.exe</code>) does: that can show the Windows 10 network flyout just fine. So I knew that has to be in some other condition, and I intially thought that maybe this <code class="language-plaintext highlighter-rouge">if</code> check here is the determinant for the lock screen’s working condition versus it failing on the desktop. Again, just a wild guess.</p>

  <p>Also, since we are talking about that, as I learned when I studied the language switcher, there is some global flag in the network flyout implementation, let’s called it generically, that determines what mode to show. The implementation mostly lives in <code class="language-plaintext highlighter-rouge">C:\Windows\ShellExperiences\NetworkUX.dll</code>. In there, look for a method called <code class="language-plaintext highlighter-rouge">NetworkUX::ViewContext::SetNetworkUXMode</code>. That’s the single thing that sets a global variable that is used all around the place to determine the type of UX to show, called <code class="language-plaintext highlighter-rouge">s_networkUXMode</code>.</p>

  <p>The desktop seems to set <code class="language-plaintext highlighter-rouge">s_networkUXMode</code> to 0. The lock screen sets that to 7 (also, it cannot be launched in desktop mode, it crashes for some other reason which needs to be investigated as well). There are also other interesting modes: the Windows 10 OOBE screen is 4, which looks quite funny when enabled instead of the regular one:</p>

  <p><img src="https://user-images.githubusercontent.com/6503598/142466279-066793bf-4d81-486b-9115-ed4313b82bf1.png" alt="image" /></p>

  <p>And no, clicking Next does not advance you to the next in the “desktop OOBE” =)))</p>

  <p>The Windows 11 one is 5 if I remember correctly. Find out for yourself. The assembly instructions where that is set look like:</p>

  <pre><code class="language-asm">.text:000000018006BC0C                 mov     Ns_networkUXMode, edi ; 
.text:000000018006BC12                 mov     rcx, [rbp+var_8]
.text:000000018006BC16                 xor     rcx, rsp        ; StackCookie
.text:000000018006BC19                 call    __security_check_cookie
</code></pre>

  <p>So, there’s plenty of space to write something like:</p>

  <pre><code class="language-asm">mov     edi, 7
mov     Ns_networkUXMode, edi ; 
</code></pre>

  <p>Just <code class="language-plaintext highlighter-rouge">nop</code> the stack protector check to gain the necessary space and make sure to adjust that relative <code class="language-plaintext highlighter-rouge">mov     Ns_networkUXMode, edi ;</code> if you shift it a few bytes down due to the <code class="language-plaintext highlighter-rouge">mov edi, 7</code>.</p>

</blockquote>

<p>Back to the main story, so what if we would be on the other branch? What controls that <code class="language-plaintext highlighter-rouge">if</code> check? Scrolling a few lines above, we see this pseudocode:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="p">{</span>
      <span class="n">Init_thread_header</span><span class="p">(</span><span class="o">&amp;</span><span class="n">dword_18057130C</span><span class="p">);</span>
      <span class="k">if</span> <span class="p">(</span> <span class="n">dword_18057130C</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span> <span class="p">)</span>
      <span class="p">{</span>
        <span class="n">byte_180571308</span> <span class="o">=</span> <span class="n">FlightHelper</span><span class="o">::</span><span class="n">CalculateRemodelEnabled</span><span class="p">();</span>
        <span class="n">Init_thread_footer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">dword_18057130C</span><span class="p">);</span>
      <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span> <span class="n">byte_180571308</span> <span class="p">)</span>
    <span class="p">{</span>
</code></pre></div></div>

<p>That <code class="language-plaintext highlighter-rouge">byte_180571308</code> is the <code class="language-plaintext highlighter-rouge">if</code> check we are talking about. So it seems that the branch we take ultimately gets determined by that <code class="language-plaintext highlighter-rouge">FlightHelper::CalculateRemodelEnabled();</code> call. Let’s hop into that: it’s a mostly useless call, as it does not seem to have any other return path other than a plain <code class="language-plaintext highlighter-rouge">return 1</code> at the end (maybe the stuff in there can hard fail, but it doesn’t seem to be the case here, we seem to reach that <code class="language-plaintext highlighter-rouge">return 1</code> at the end). Okay, so according to this, <code class="language-plaintext highlighter-rouge">byte_180571308</code> is going to be a 1. So let’s try setting it to 0.</p>

<p>Looking on the disassembly, the ending is something like:</p>

<pre><code class="language-asm">.text:00000001800407C9                 mov     r8b, 3
.text:00000001800407CC                 mov     dl, 1
.text:00000001800407CE                 lea     rcx, ?impl@?1??GetImpl@?$Feature@U__WilFeatureTraits_Feature_TestNM@@@wil@@CAAEAV?$FeatureImpl@U__WilFeatureTraits_Feature_TestNM@@@details@3@XZ@4V453@A ; wil::details::FeatureImpl&lt;__WilFeatureTraits_Feature_TestNM&gt; `wil::Feature&lt;__WilFeatureTraits_Feature_TestNM&gt;::GetImpl(void)'::`2'::impl
.text:00000001800407D5                 call    ?ReportUsage@?$FeatureImpl@U__WilFeatureTraits_Feature_TestNM@@@details@wil@@QEAAX_NW4ReportingKind@3@_K@Z ; wil::details::FeatureImpl&lt;__WilFeatureTraits_Feature_TestNM&gt;::ReportUsage(bool,wil::ReportingKind,unsigned __int64)
.text:00000001800407DA                 mov     al, 1
.text:00000001800407DC                 jmp     short loc_1800407E0
.text:00000001800407DE ; ---------------------------------------------------------------------------
.text:00000001800407DE                 xor     al, al
.text:00000001800407E0
.text:00000001800407E0 loc_1800407E0:                          ; CODE XREF: FlightHelper__CalculateRemodelEnabled+F0↑j
.text:00000001800407E0                 add     rsp, 20h
.text:00000001800407E4                 pop     rdi
.text:00000001800407E5                 pop     rsi
.text:00000001800407E6                 pop     rbx
.text:00000001800407E7                 retn
</code></pre>

<p>Coresponding to pseudocode looking like this:</p>

<pre><code class="language-asm">  LOBYTE(v4) = 3;
  LOBYTE(v3) = 1;
  wil::details::FeatureImpl&lt;__WilFeatureTraits_Feature_TestNM&gt;::ReportUsage(
    &amp;`wil::Feature&lt;__WilFeatureTraits_Feature_TestNM&gt;::GetImpl'::`2'::impl,
    v3,
    v4);
  return 1;
</code></pre>

<p>But the disassembly is more interesting. Specifically, if you look at address <code class="language-plaintext highlighter-rouge">00000001800407DE</code>, you see that <code class="language-plaintext highlighter-rouge">xor al, al</code> that is bypassed altogether by the preceding unconditional jump to the instruction below it. That’s great, we do not even have to insert too much stuff ourselves. That jump is 2 bytes: <code class="language-plaintext highlighter-rouge">EB 02</code>. Let’s nop those, so replace with <code class="language-plaintext highlighter-rouge">90 90</code>.</p>

<p>Drop the file instead of the original <code class="language-plaintext highlighter-rouge">Windows.UI.QuickActions.dll</code> near <code class="language-plaintext highlighter-rouge">ShellExperienceHost.exe</code>, make sure to reload it if it’s loaded in memory, click the network icon and…:</p>

<p><img src="https://user-images.githubusercontent.com/6503598/142405580-5db9e79a-deb4-4314-bd82-603090847e3c.png" alt="image" /></p>

<p>YEEEEEY!!! Call it sheer luck or whatever, but it works. The battery fix came for free as well, after this, as it seemed to suffer from the same thing:</p>

<p><img src="https://user-images.githubusercontent.com/6503598/142454619-7954e217-3965-4796-9540-00ad58a9f149.png" alt="image-20211118174828628" /></p>

<p>Okay, so it’s only 2 bytes Microsoft had to fix to make this happen, but yet again I have to come and clean up the mess…? Well, almost. See, there’s a caveat with this: it enables this behavior generally in <code class="language-plaintext highlighter-rouge">ShellExperienceHost.exe</code>. Try launching <code class="language-plaintext highlighter-rouge">ms-availablenetworks:</code>, which in Windows 11 should open a list like this:</p>

<p><img src="https://user-images.githubusercontent.com/6503598/142406030-e83231f9-6139-4da2-a188-38d9fe5037ce.png" alt="image" /></p>

<p>Instead, it will now open the Windows 11 notification center combined with calendar thing. So, the way it seems to me is that setting <code class="language-plaintext highlighter-rouge">byte_180571308</code> to 0 globally enables some legacy behavior in <code class="language-plaintext highlighter-rouge">ShellExperienceHost.exe</code>, let’s call it. That’s great for network and battery, as it fixes those, but it disables newer stuff, like the Windows 11 WiFi list or the Windows 11 action center. It is like an <code class="language-plaintext highlighter-rouge">UndockingDisabled</code> but for this case. Ideally, we would want to keep the best of the both worlds. This is discussed in the next part, where I develop a shippable solution as functionality for ExplorerPatcher.</p>

<h3 id="implementation">Implementation</h3>

<p>Okay, so how do we wrap this knwoledge to deliver something that’s actually shippable?</p>

<p>As you saw, the static patch is kind of limited in the sense that it enables only either of the 2 worlds. Of course, we could patch in our own assembly, but that’s too much of a hassle, if what we did until now wasn’t.</p>

<p>So let’s consider dynamic patching. How to approach that?</p>

<p>First of all, let’s consider what the common methods of executing our code there would be:</p>

<ul>
  <li>
    <p>Exploiting the library search order by placing our custom library in the same folder as the target executable, with the name and exports of a well known library. ExplorerPatcher already does that by masqueraiding as <code class="language-plaintext highlighter-rouge">dxgi.dll</code> (DirectX Graphics Infrastructure) for <code class="language-plaintext highlighter-rouge">explorer.exe</code> and <code class="language-plaintext highlighter-rouge">StartMenuExperienceHost.exe</code>.</p>

    <p>Looking on the list of imports from <code class="language-plaintext highlighter-rouge">dxgi.dll</code> for <code class="language-plaintext highlighter-rouge">ShellExperienceHost.exe</code>, similar to <code class="language-plaintext highlighter-rouge">explorer.exe</code>, it only calls <code class="language-plaintext highlighter-rouge">DXGIDeclareAdapterRemovalSupport</code> very early in the execution stage, so that is a viable option and candidate for our entry point.</p>
  </li>
  <li>
    <p>Hooking and patching the executable at run time. For this, there are a plethora of methods: the basic idea is to <code class="language-plaintext highlighter-rouge">CreateRemoteThread</code> in the target process which executes shellcode we wrote there using <code class="language-plaintext highlighter-rouge">WriteProcessMemory</code> that loads our library in the target process and executes its entry point. I used this in the past in plenty of places, including as recent as hooking <code class="language-plaintext highlighter-rouge">StartMenuExperienceHost.exe</code> with that. Unfortunately, due to some reason that’s still unknown to me at the moment, calling <code class="language-plaintext highlighter-rouge">LoadLibraryW</code> when using this method in the remote process to load our library <em>sometimes</em> fails with <code class="language-plaintext highlighter-rouge">ERROR_ACCESS_DENIED</code>. Same config, it only happens on some systems. It’s very weird, seems like a half baked “security” feature oby Microsoft, since I can execute the shell code just fine, it is only <code class="language-plaintext highlighter-rouge">LoadLibraryW</code> that fails. If this is indeed considered “security”, it’s just plain stupid because the solution is obviously for one to write their own library loader, which I will certainly do at some point for future projects. Prefferably in amd64 assembly, so that it’s just one <code class="language-plaintext highlighter-rouge">char*</code> array in C into which I write some offsets at runtime, write it to the remote process, execute it and off to the races.</p>
  </li>
</ul>

<p>So, considering the above, I will go with the first option. For ExplorerPatcher, this is advantageous as this is basically the same infrastructure as for the 2 other executables that are hooked (<code class="language-plaintext highlighter-rouge">explorer.exe</code> and <code class="language-plaintext highlighter-rouge">StartMenuExperienceHost.exe</code>), so it keeps the implementation rather tidy.</p>

<p>Now, what do we do once we get to execute code in the target? We need to patch that <code class="language-plaintext highlighter-rouge">jmp</code> from before basically, or patch out the entire function maybe? It depends on the strategy that we choose. Let’s recall our options:</p>

<ul>
  <li>
    <p>Hook/inject functions exported by system libraries used by the target application. This is used extensively though ExplorerPatcher and it is the prefferable method, when possible. It involves hooking some known function exported by libraries such as <code class="language-plaintext highlighter-rouge">user32.dll</code>, <code class="language-plaintext highlighter-rouge">shell32.dll</code> etc. This works when the thing we want to modify is calling some function like this, or depends on it etc. The patch is done by altering the delay import table or the import table for a library from the target. The limitations are that you need to have the target actually call some known function from some library (which is less likely in these new “Windows Runtime” executables Microsoft is flooding more and more of the OS with) and sometimes it’s hard to filter calls from other actors from calls from actors you are interested in.</p>
  </li>
  <li>
    <p>Hook/inject any function. When I say “any”, I mean the rest of the functions, those actually contained in the target we want to exploit. Conviniently, Microsoft makes symbols available for its system files. These files tell you human-friendly names for various things in the executable code, including function sites. Hooking is usually done by installing a trampoline, basically patching the first few bytes of the function to jump to our custom crafted function where we do what we want and then we call the original we saved a pointer to. There are plenty of libraries to aid with this, like <a href="https://github.com/kubo/funchook">funchook</a> or <a href="https://github.com/microsoft/Detours">Microsoft Detours</a>. I have done it manually as well in the past, the big problem is that in order to automate this, you need a disassembler at runtime: recall that x86 is CISC (instructions length varies). Say your trampoline takes 7 bytes, you need to patch the first 7 bytes + whatever it takes to complete the last instruction. To determine what that additional amount is, obviously the program needs to be able to understand x86 instructions at runtime.</p>

    <p>The disadvantage is that this is global, affects the entire executable address space, plus the symbol addresses change with each version compiled, so those have to be updated for every new Windows build somehow (ExplorerPatcher uses a combination of downloading the symbols for your current build from Microsoft and extracting the info from there, and hardcoding the addresses for some known file releases). This is used in ExplorerPatcher for the <code class="language-plaintext highlighter-rouge">Win</code>+<code class="language-plaintext highlighter-rouge">X</code> menu build function from <code class="language-plaintext highlighter-rouge">twinui.pcshell.dll</code>, the context menu owner draw functions still from there and for hooking some function calls in <code class="language-plaintext highlighter-rouge">StartMenuExperienceHost.exe</code>; this is usually a last resort method.</p>
  </li>
  <li>
    <p>Pattern matching. This is the classic of the classics. Basically, you try to determine as generic of a strategy as possible to identify some bytes in your target function that is likely to work on future versions and that is unique to your function. If you are smart, you can pull it off, if you are lucky, it also resists more than one build.</p>
  </li>
</ul>

<p>I feel lucky today, or rather, I feel that some things in that function are pretty unique and likely not to change based on my previous experiences diassembling Microsoft’s stuff, so I will go with this. The reason is also that I want to try to minimize the symbols fiasco (Microsoft recently delays publishing the symbols for some unknown reason for some builds, and then people running beta builds start flooding the forums with requests for me to “fix” it); it’s just one thing to patch, I don’t want to introduce the need for other symbols, and if we anyway have 2 methods of hooking stuff already present in ExplorerPatcher, what can a third one do…?</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">InjectShellExperienceHost</span><span class="p">()</span>
<span class="p">{</span>
<span class="cp">#ifdef _WIN64
</span>    <span class="n">HMODULE</span> <span class="n">hQA</span> <span class="o">=</span> <span class="n">LoadLibraryW</span><span class="p">(</span><span class="s">L"Windows.UI.QuickActions.dll"</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">hQA</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">PIMAGE_DOS_HEADER</span> <span class="n">dosHeader</span> <span class="o">=</span> <span class="n">hQA</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">dosHeader</span><span class="o">-&gt;</span><span class="n">e_magic</span> <span class="o">==</span> <span class="n">IMAGE_DOS_SIGNATURE</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">PIMAGE_NT_HEADERS64</span> <span class="n">ntHeader</span> <span class="o">=</span> <span class="p">(</span><span class="n">PIMAGE_NT_HEADERS64</span><span class="p">)((</span><span class="n">u_char</span><span class="o">*</span><span class="p">)</span><span class="n">dosHeader</span> <span class="o">+</span> <span class="n">dosHeader</span><span class="o">-&gt;</span><span class="n">e_lfanew</span><span class="p">);</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">ntHeader</span><span class="o">-&gt;</span><span class="n">Signature</span> <span class="o">==</span> <span class="n">IMAGE_NT_SIGNATURE</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="kt">char</span><span class="o">*</span> <span class="n">pSEHPatchArea</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
                <span class="kt">char</span> <span class="n">seh_pattern1</span><span class="p">[</span><span class="mi">14</span><span class="p">]</span> <span class="o">=</span>
                <span class="p">{</span>
                    <span class="c1">// mov al, 1</span>
                    <span class="mh">0xB0</span><span class="p">,</span> <span class="mh">0x01</span><span class="p">,</span>
                    <span class="c1">// jmp + 2</span>
                    <span class="mh">0xEB</span><span class="p">,</span> <span class="mh">0x02</span><span class="p">,</span>
                    <span class="c1">// xor al, al</span>
                    <span class="mh">0x32</span><span class="p">,</span> <span class="mh">0xC0</span><span class="p">,</span>
                    <span class="c1">// add rsp, 0x20</span>
                    <span class="mh">0x48</span><span class="p">,</span> <span class="mh">0x83</span><span class="p">,</span> <span class="mh">0xC4</span><span class="p">,</span> <span class="mh">0x20</span><span class="p">,</span>
                    <span class="c1">// pop rdi</span>
                    <span class="mh">0x5F</span><span class="p">,</span>
                    <span class="c1">// pop rsi</span>
                    <span class="mh">0x5E</span><span class="p">,</span>
                    <span class="c1">// pop rbx</span>
                    <span class="mh">0x5B</span><span class="p">,</span>
                    <span class="c1">// ret</span>
                    <span class="mh">0xC3</span>
                <span class="p">};</span>
                <span class="kt">char</span> <span class="n">seh_off</span> <span class="o">=</span> <span class="mi">12</span><span class="p">;</span>
                <span class="kt">char</span> <span class="n">seh_pattern2</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span> <span class="o">=</span>
                <span class="p">{</span>
                    <span class="c1">// mov r8b, 3</span>
                    <span class="mh">0x41</span><span class="p">,</span> <span class="mh">0xB0</span><span class="p">,</span> <span class="mh">0x03</span><span class="p">,</span>
                    <span class="c1">// mov dl, 1</span>
                    <span class="mh">0xB2</span><span class="p">,</span> <span class="mh">0x01</span>
                <span class="p">};</span>
                <span class="n">BOOL</span> <span class="n">bTwice</span> <span class="o">=</span> <span class="n">FALSE</span><span class="p">;</span>
                <span class="n">PIMAGE_SECTION_HEADER</span> <span class="n">section</span> <span class="o">=</span> <span class="n">IMAGE_FIRST_SECTION</span><span class="p">(</span><span class="n">ntHeader</span><span class="p">);</span>
                <span class="k">for</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">ntHeader</span><span class="o">-&gt;</span><span class="n">FileHeader</span><span class="p">.</span><span class="n">NumberOfSections</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span>
                <span class="p">{</span>
                    <span class="k">if</span> <span class="p">(</span><span class="n">section</span><span class="o">-&gt;</span><span class="n">Characteristics</span> <span class="o">&amp;</span> <span class="n">IMAGE_SCN_CNT_CODE</span><span class="p">)</span>
                    <span class="p">{</span>
                        <span class="k">if</span> <span class="p">(</span><span class="n">section</span><span class="o">-&gt;</span><span class="n">SizeOfRawData</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">bTwice</span><span class="p">)</span>
                        <span class="p">{</span>
                            <span class="n">DWORD</span> <span class="n">dwOldProtect</span><span class="p">;</span>
                            <span class="n">VirtualProtect</span><span class="p">(</span><span class="n">hQA</span> <span class="o">+</span> <span class="n">section</span><span class="o">-&gt;</span><span class="n">VirtualAddress</span><span class="p">,</span> <span class="n">section</span><span class="o">-&gt;</span><span class="n">SizeOfRawData</span><span class="p">,</span> <span class="n">PAGE_EXECUTE_READWRITE</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">dwOldProtect</span><span class="p">);</span>
                            <span class="kt">char</span><span class="o">*</span> <span class="n">pCandidate</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
                            <span class="k">while</span> <span class="p">(</span><span class="n">TRUE</span><span class="p">)</span>
                            <span class="p">{</span>
                                <span class="n">pCandidate</span> <span class="o">=</span> <span class="n">memmem</span><span class="p">(</span>
                                    <span class="o">!</span><span class="n">pCandidate</span> <span class="o">?</span> <span class="n">hQA</span> <span class="o">+</span> <span class="n">section</span><span class="o">-&gt;</span><span class="n">VirtualAddress</span> <span class="o">:</span> <span class="n">pCandidate</span><span class="p">,</span>
                                    <span class="o">!</span><span class="n">pCandidate</span> <span class="o">?</span> <span class="n">section</span><span class="o">-&gt;</span><span class="n">SizeOfRawData</span> <span class="o">:</span> <span class="p">(</span><span class="kt">uintptr_t</span><span class="p">)</span><span class="n">section</span><span class="o">-&gt;</span><span class="n">SizeOfRawData</span> <span class="o">-</span> <span class="p">(</span><span class="kt">uintptr_t</span><span class="p">)(</span><span class="n">pCandidate</span> <span class="o">-</span> <span class="p">(</span><span class="n">hQA</span> <span class="o">+</span> <span class="n">section</span><span class="o">-&gt;</span><span class="n">VirtualAddress</span><span class="p">)),</span>
                                    <span class="n">seh_pattern1</span><span class="p">,</span>
                                    <span class="k">sizeof</span><span class="p">(</span><span class="n">seh_pattern1</span><span class="p">)</span>
                                <span class="p">);</span>
                                <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pCandidate</span><span class="p">)</span>
                                <span class="p">{</span>
                                    <span class="k">break</span><span class="p">;</span>
                                <span class="p">}</span>
                                <span class="kt">char</span><span class="o">*</span> <span class="n">pCandidate2</span> <span class="o">=</span> <span class="n">pCandidate</span> <span class="o">-</span> <span class="n">seh_off</span> <span class="o">-</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">seh_pattern2</span><span class="p">);</span>
                                <span class="k">if</span> <span class="p">(</span><span class="n">pCandidate2</span> <span class="o">&gt;</span> <span class="n">section</span><span class="o">-&gt;</span><span class="n">VirtualAddress</span><span class="p">)</span>
                                <span class="p">{</span>
                                    <span class="k">if</span> <span class="p">(</span><span class="n">memmem</span><span class="p">(</span><span class="n">pCandidate2</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">seh_pattern2</span><span class="p">),</span> <span class="n">seh_pattern2</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">seh_pattern2</span><span class="p">)))</span>
                                    <span class="p">{</span>
                                        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pSEHPatchArea</span><span class="p">)</span>
                                        <span class="p">{</span>
                                            <span class="n">pSEHPatchArea</span> <span class="o">=</span> <span class="n">pCandidate</span><span class="p">;</span>
                                        <span class="p">}</span>
                                        <span class="k">else</span>
                                        <span class="p">{</span>
                                            <span class="n">bTwice</span> <span class="o">=</span> <span class="n">TRUE</span><span class="p">;</span>
                                        <span class="p">}</span>
                                    <span class="p">}</span>
                                <span class="p">}</span>
                                <span class="n">pCandidate</span> <span class="o">+=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">seh_pattern1</span><span class="p">);</span>
                            <span class="p">}</span>
                            <span class="n">VirtualProtect</span><span class="p">(</span><span class="n">hQA</span> <span class="o">+</span> <span class="n">section</span><span class="o">-&gt;</span><span class="n">VirtualAddress</span><span class="p">,</span> <span class="n">section</span><span class="o">-&gt;</span><span class="n">SizeOfRawData</span><span class="p">,</span> <span class="n">dwOldProtect</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">dwOldProtect</span><span class="p">);</span>
                        <span class="p">}</span>
                    <span class="p">}</span>
                    <span class="n">section</span><span class="o">++</span><span class="p">;</span>
                <span class="p">}</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">pSEHPatchArea</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">bTwice</span><span class="p">)</span>
                <span class="p">{</span>
                    <span class="n">DWORD</span> <span class="n">dwOldProtect</span><span class="p">;</span>
                    <span class="n">VirtualProtect</span><span class="p">(</span><span class="n">pSEHPatchArea</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">seh_pattern1</span><span class="p">),</span> <span class="n">PAGE_EXECUTE_READWRITE</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">dwOldProtect</span><span class="p">);</span>
                    <span class="n">pSEHPatchArea</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x90</span><span class="p">;</span>
                    <span class="n">pSEHPatchArea</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x90</span><span class="p">;</span>
                    <span class="n">VirtualProtect</span><span class="p">(</span><span class="n">pSEHPatchArea</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">seh_pattern1</span><span class="p">),</span> <span class="n">dwOldProtect</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">dwOldProtect</span><span class="p">);</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="cp">#endif
</span><span class="p">}</span>
</code></pre></div></div>

<p>Firstly, I match by this pattern:</p>

<pre><code class="language-asm">.text:00000001800407DA                 mov     al, 1
.text:00000001800407DC                 jmp     short loc_1800407E0
.text:00000001800407DE ; ---------------------------------------------------------------------------
.text:00000001800407DE                 xor     al, al
.text:00000001800407E0
.text:00000001800407E0 loc_1800407E0:                          ; CODE XREF: FlightHelper__CalculateRemodelEnabled+F0↑j
.text:00000001800407E0                 add     rsp, 20h
.text:00000001800407E4                 pop     rdi
.text:00000001800407E5                 pop     rsi
.text:00000001800407E6                 pop     rbx
.text:00000001800407E7                 retn
</code></pre>

<p>Then skip some bytes (the load address of and function call to that address, some telemetry call) and try to match this which is also a pattern I have seen in many of their libraries, I haven’t bothered to understand but it seems to stay there:</p>

<pre><code class="language-asm">.text:00000001800407C9                 mov     r8b, 3
.text:00000001800407CC                 mov     dl, 1
</code></pre>

<p>On builds 22000.318 and 22000.346, first pattern yields 2 results, while adding the last one yields only the result we are interested in. Additionally, I patch only if I find a single match, as otherwise it is likely the file actually changed drastically.</p>

<p>Okay, so we do this patch at runtime, now what? It’s still going to behave like the static patch, ain’t it?</p>

<p>If we leave it like this, indeed it is. So, we have to enhance it a bit. At first I tried signaling <code class="language-plaintext highlighter-rouge">ShellExperienceHost.exe</code> to switch between the 2 modes, by patching and reverting those 2 bytes. Unfortunately, this doesn’t really work: once a mode is set, it stays like that: things already load in that mode and calling stuff only available in the other mode crashes <code class="language-plaintext highlighter-rouge">ShellExperienceHost.exe</code> apparently.</p>

<p>So what next? From the way I toggle the Windows 11 WiFi list, I already have code that opens something that is a <code class="language-plaintext highlighter-rouge">Windows.UI.Core.CoreWindow</code> and waits for it to close. The principle would be simple here: when the network or battery flyout is invoked, kill <code class="language-plaintext highlighter-rouge">ShellExperienceHost.exe</code> if running, somehow signal the instance that will open that we want it to run in legacy mode, wait for the flyout to be dismissed by the user (using the infrastructure from above), and then kill <code class="language-plaintext highlighter-rouge">ShellExperienceHost.exe</code> again so that Windows 11 things still open normally.</p>

<p>Last quest: how do we signal <code class="language-plaintext highlighter-rouge">ShellExperienceHost.exe</code> that we want it to run in legacy mode. Better said, how do we signal out code running in there this?</p>

<p>Well, UWP apps are kind of jailed. They have limited access to the file system and registry. I experienced this as well when working in <code class="language-plaintext highlighter-rouge">StartMenuExperienceHost.exe</code>. I don’t know exactly how to determine what they have/do not have access to, as that also seems to vary quite a lor based on the actual executable we are talking about, plus I do not really care. I care about making this working. So, after failing getting it to work with named events, I looked into what keys in the registry this program acesses. Indeed, I found this path in <code class="language-plaintext highlighter-rouge">HKCU</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Control Panel\Quick Actions\Control Center\QuickActionsStateCapture
</code></pre></div></div>

<p>So, to finish this off, to signal the legacy mode, when it’s time to invoke the network/battery flyout, I create an <code class="language-plaintext highlighter-rouge">ExplorerPatcher</code> key in there. When our library gets injected in <code class="language-plaintext highlighter-rouge">ShellExperienceHost.exe</code>, we check whether that key exists and only then patch the executable; add this to the function above, right at the beginning:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="n">HKEY</span> <span class="n">hKey</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">RegOpenKeyW</span><span class="p">(</span><span class="n">HKEY_CURRENT_USER</span><span class="p">,</span> <span class="n">_T</span><span class="p">(</span><span class="n">SEH_REGPATH</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">hKey</span><span class="p">))</span>
    <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
</code></pre></div></div>

<p>After the flyout is dismissed, we make sure to delete the key, and then terminate <code class="language-plaintext highlighter-rouge">ShellExperienceHost.exe</code>, so that the new instance that will be launched eventually loads normally, in Windows 11 mode.</p>

<p>That would be it here.</p>

<h3 id="bonus-the-language-switcher">Bonus: the language switcher</h3>

<p>I mentioned this in the beginning, so let’s talk about this a bit as well: I recently needed a way to detect when the input language changes for sws (my custom, written from scratch implementation of a Windows 10-like <code class="language-plaintext highlighter-rouge">Alt</code>-<code class="language-plaintext highlighter-rouge">Tab</code> switcher). The reason I needed to detect this is so that I can change the key mapping for <code class="language-plaintext highlighter-rouge">Alt</code>+<code class="language-plaintext highlighter-rouge">~</code>, which shows the window switcher but only for windows of the current application. I need to change it because <code class="language-plaintext highlighter-rouge">~</code> has a different virtual key code depending on the layout that it is loaded: it is either <code class="language-plaintext highlighter-rouge">VK_OEM_3</code>, either <code class="language-plaintext highlighter-rouge">VK_OEM_5</code>, alrgely. Or rather, not <code class="language-plaintext highlighter-rouge">~</code> specifically, but the key above <code class="language-plaintext highlighter-rouge">Tab</code>. I always wanted to map that key in combination with <code class="language-plaintext highlighter-rouge">Alt</code>. I know it by scan code (29 if I remember correctly), but <code class="language-plaintext highlighter-rouge">RegisterHotKey</code> only accepts virtual key codes, to the hot key has to be unregistered and registered every time the user changes the layout.</p>

<p>As it is pretty standard with Windows these days, there are a couple of old APIs that largely do not work anymore or are weird:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">WM_INPUTLANGCHANGE</code> is received only by the active window, which in 99.99% of the cases is not the window switcher</li>
  <li>this thing (<a href="https://docs.microsoft.com/en-us/windows/win32/api/msctf/nn-msctf-itflanguageprofilenotifysink?redirectedfrom=MSDN">ITfLanguageProfileNotifySink</a>) does not really work, or I could not get it ot work, or rather, when I was seeing how much time I was wasting trying ot make it work and with Google failing to provide me with input from users facing the same hurdles I was facing, I decided to give up on it</li>
</ul>

<p>So, what to do? Naturally, disassemble their executables and see how they do it. Isn’t this normal? Isn’t a closed system you are discouraged from tempering and playing with the norm these days?</p>

<p>I looked again though <code class="language-plaintext highlighter-rouge">explorer.exe</code>: in one of its <code class="language-plaintext highlighter-rouge">CoCreateInstance</code> calls, it requests some <code class="language-plaintext highlighter-rouge">IID_InputSwitchControl</code> interface from <code class="language-plaintext highlighter-rouge">CLSID_InputSwitchControl</code> (actual GUIDs are in ExplorerPatcher’s source code):</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">__int64</span> <span class="kr">__fastcall</span> <span class="n">CTrayInputIndicator</span><span class="o">::</span><span class="n">_RegisterInputSwitch</span><span class="p">(</span><span class="n">CTrayInputIndicator</span> <span class="o">*</span><span class="k">this</span><span class="p">)</span>
<span class="p">{</span>
  <span class="n">LPVOID</span> <span class="o">*</span><span class="n">ppv</span><span class="p">;</span> <span class="c1">// rbx</span>
  <span class="n">HRESULT</span> <span class="n">Instance</span><span class="p">;</span> <span class="c1">// edi</span>

  <span class="n">ppv</span> <span class="o">=</span> <span class="p">(</span><span class="n">LPVOID</span> <span class="o">*</span><span class="p">)((</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="k">this</span> <span class="o">+</span> <span class="mi">328</span><span class="p">);</span>
  <span class="n">Microsoft</span><span class="o">::</span><span class="n">WRL</span><span class="o">::</span><span class="n">ComPtr</span><span class="o">&lt;</span><span class="n">IVirtualDesktopNotificationService</span><span class="o">&gt;::</span><span class="n">InternalRelease</span><span class="p">((</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="k">this</span> <span class="o">+</span> <span class="mi">328</span><span class="p">);</span>
  <span class="n">Instance</span> <span class="o">=</span> <span class="n">CoCreateInstance</span><span class="p">(</span><span class="o">&amp;</span><span class="n">CLSID_InputSwitchControl</span><span class="p">,</span> <span class="mi">0</span><span class="n">i64</span><span class="p">,</span> <span class="mi">1u</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">IID_InputSwitchControl</span><span class="p">,</span> <span class="n">ppv</span><span class="p">);</span>
  <span class="k">if</span> <span class="p">(</span> <span class="n">Instance</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="p">)</span>
  <span class="p">{</span>
    <span class="n">Instance</span> <span class="o">=</span> <span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="n">__int64</span> <span class="p">(</span><span class="kr">__fastcall</span> <span class="o">**</span><span class="p">)(</span><span class="n">LPVOID</span><span class="p">,</span> <span class="n">_QWORD</span><span class="p">))(</span><span class="o">*</span><span class="p">(</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="o">*</span><span class="n">ppv</span> <span class="o">+</span> <span class="mi">24</span><span class="n">i64</span><span class="p">))(</span><span class="o">*</span><span class="n">ppv</span><span class="p">,</span> <span class="mi">0</span><span class="n">i64</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span> <span class="n">Instance</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="p">)</span>
    <span class="p">{</span>
      <span class="n">Microsoft</span><span class="o">::</span><span class="n">WRL</span><span class="o">::</span><span class="n">ComPtr</span><span class="o">&lt;</span><span class="n">IVirtualDesktopNotificationService</span><span class="o">&gt;::</span><span class="n">InternalRelease</span><span class="p">(</span><span class="n">ppv</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">else</span>
    <span class="p">{</span>
      <span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="kt">void</span> <span class="p">(</span><span class="kr">__fastcall</span> <span class="o">**</span><span class="p">)(</span><span class="n">LPVOID</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="p">))(</span><span class="o">*</span><span class="p">(</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="o">*</span><span class="n">ppv</span> <span class="o">+</span> <span class="mi">32</span><span class="n">i64</span><span class="p">))(</span><span class="o">*</span><span class="n">ppv</span><span class="p">,</span> <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="k">this</span> <span class="o">+</span> <span class="mi">16</span><span class="p">);</span>
      <span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="kt">void</span> <span class="p">(</span><span class="kr">__fastcall</span> <span class="o">**</span><span class="p">)(</span><span class="n">LPVOID</span><span class="p">))(</span><span class="o">*</span><span class="p">(</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="o">*</span><span class="n">ppv</span> <span class="o">+</span> <span class="mi">64</span><span class="n">i64</span><span class="p">))(</span><span class="o">*</span><span class="n">ppv</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="k">return</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">int</span><span class="p">)</span><span class="n">Instance</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Okay, next step for determining the virtual table of this interface is to consult the registry and see which library actually implements it:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{B9BC2A50-43C3-41AA-A086-5DB14E184BAE}\InProcServer32
</code></pre></div></div>

<p>It points us to:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>C:\Windows\System32\InputSwitch.dll
</code></pre></div></div>

<p>How do you know how the interface is named? Well, you don’t, but look around in the DLL. There should be some methods called <code class="language-plaintext highlighter-rouge">CreateInstance</code> that corespond to the factories for a COM object. If something has a <code class="language-plaintext highlighter-rouge">CreateInstance</code> method, it’s probably an interface. In <code class="language-plaintext highlighter-rouge">InputSwitchControl.dll</code>, there aren’t many, and there’s one called <code class="language-plaintext highlighter-rouge">CInputSwitchControl</code>. They usually name the interfaces after the implementations, prefixed with “C”.</p>

<p>Let’s take that as a candidate. The strategy next is to go though all the functions of that “class” and see if they are mentioned in any virtual table, and then try to see if the positions in that virtual table match with whatever code in <code class="language-plaintext highlighter-rouge">explorer.exe</code> is calling it. We eventually get to a virtual table that looks like this one:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dq offset ?QueryInterface@?$RuntimeClassImpl@U?$RuntimeClassFlags@$01@WRL@Microsoft@@$00$0A@$0A@UIInputSwitchControl@@@Details@WRL@Microsoft@@UEAAJAEBU_GUID@@PEAPEAX@Z
dq offset ?AddRef@?$RuntimeClassImpl@U?$RuntimeClassFlags@$01@WRL@Microsoft@@$00$0A@$0A@UIInputSwitchControl@@@Details@WRL@Microsoft@@UEAAKXZ ; Microsoft::WRL::Details::RuntimeClassImpl&lt;Microsoft::WRL::RuntimeClassFlags&lt;2&gt;,1,0,0,IInputSwitchControl&gt;::AddRef(void)
dq offset ?Release@?$RuntimeClassImpl@U?$RuntimeClassFlags@$01@WRL@Microsoft@@$00$0A@$0A@UIInputSwitchControl@@@Details@WRL@Microsoft@@UEAAKXZ ; Microsoft::WRL::Details::RuntimeClassImpl&lt;Microsoft::WRL::RuntimeClassFlags&lt;2&gt;,1,0,0,IInputSwitchControl&gt;::Release(void)
dq offset ?Init@CInputSwitchControl@@UEAAJW4__MIDL___MIDL_itf_inputswitchserver_0000_0000_0001@@@Z ; CInputSwitchControl::Init(__MIDL___MIDL_itf_inputswitchserver_0000_0000_0001)
dq offset ?SetCallback@CInputSwitchControl@@UEAAJPEAUIInputSwitchCallback@@@Z ; CInputSwitchControl::SetCallback(IInputSwitchCallback *)
...
</code></pre></div></div>

<p>Is this the virtual table we’re after?</p>

<p>If we look back on the disassembly from <code class="language-plaintext highlighter-rouge">explorer.exe</code>, we see 2 calls are made for functions within this interface:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Instance</span> <span class="o">=</span> <span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="n">__int64</span> <span class="p">(</span><span class="kr">__fastcall</span> <span class="o">**</span><span class="p">)(</span><span class="n">LPVOID</span><span class="p">,</span> <span class="n">_QWORD</span><span class="p">))(</span><span class="o">*</span><span class="p">(</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="o">*</span><span class="n">ppv</span> <span class="o">+</span> <span class="mi">24</span><span class="n">i64</span><span class="p">))(</span><span class="o">*</span><span class="n">ppv</span><span class="p">,</span> <span class="mi">0</span><span class="n">i64</span><span class="p">);</span>
</code></pre></div></div>

<p>And</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="kt">void</span> <span class="p">(</span><span class="kr">__fastcall</span> <span class="o">**</span><span class="p">)(</span><span class="n">LPVOID</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="p">))(</span><span class="o">*</span><span class="p">(</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="o">*</span><span class="n">ppv</span> <span class="o">+</span> <span class="mi">32</span><span class="n">i64</span><span class="p">))(</span><span class="o">*</span><span class="n">ppv</span><span class="p">,</span> <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="k">this</span> <span class="o">+</span> <span class="mi">16</span><span class="p">);</span>
</code></pre></div></div>

<p>That means the first 2 functions after <code class="language-plaintext highlighter-rouge">QueryInterface</code>, <code class="language-plaintext highlighter-rouge">AddRef</code> and <code class="language-plaintext highlighter-rouge">Release</code>, which would mean <code class="language-plaintext highlighter-rouge">Init</code> and <code class="language-plaintext highlighter-rouge">SetCallback</code> from above. Besides obviously checking at runtime (it’s easy since the called library is an in-process server and handler), we can also be more sure by looking at the second call, specifically how it sends a pointer, <code class="language-plaintext highlighter-rouge">(char *)this + 16</code> to the library. That’s equivalent to <code class="language-plaintext highlighter-rouge">(_QWORD*)this + 2</code> (an INT64 or QWORD is made up of 8 bytes or chars). If we look in the constructor of <code class="language-plaintext highlighter-rouge">CTrayInputIndicator</code> from <code class="language-plaintext highlighter-rouge">explorer.exe</code> (<code class="language-plaintext highlighter-rouge">CTrayInputIndicator::CTrayInputIndicator</code>), we see this on the first lines:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="o">*</span><span class="p">(</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="k">this</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">CTrayInputIndicator</span><span class="o">::</span><span class="err">`</span><span class="n">vftable</span><span class="err">'</span><span class="p">{</span><span class="k">for</span> <span class="err">`</span><span class="n">CImpWndProc</span><span class="err">'</span><span class="p">};</span>
  <span class="o">*</span><span class="p">((</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="k">this</span> <span class="o">+</span> <span class="mi">2</span><span class="p">)</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">CTrayInputIndicator</span><span class="o">::</span><span class="err">`</span><span class="n">vftable</span><span class="err">'</span><span class="p">{</span><span class="k">for</span> <span class="err">`</span><span class="n">IInputSwitchCallback</span><span class="err">'</span><span class="p">};</span>
  <span class="o">*</span><span class="p">((</span><span class="n">_QWORD</span> <span class="o">*</span><span class="p">)</span><span class="k">this</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="n">i64</span><span class="p">;</span>
</code></pre></div></div>

<p>So, <code class="language-plaintext highlighter-rouge">(char *)this + 16</code> is the virtual table for an <code class="language-plaintext highlighter-rouge">IInputSwitchCallback</code>. That’s what the <code class="language-plaintext highlighter-rouge">SetCallback</code> method from above also states it takes and what you would expect from a <code class="language-plaintext highlighter-rouge">SetCallback</code> method. So yeah, that pretty much seems to be it.</p>

<p>Now, how do you use this?</p>

<p>As shown above. First of all you initialize the input switch control by calling <code class="language-plaintext highlighter-rouge">Init</code> with a paramater 0. What’s that 0? Let’s see what <code class="language-plaintext highlighter-rouge">InputSwitchControl.dll</code> does with it; it’s a long function (<code class="language-plaintext highlighter-rouge">CInputSwitchControl::_Init</code>), but at some point it does this:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">v5</span> <span class="o">=</span> <span class="n">IsUtil</span><span class="o">::</span><span class="n">MapClientTypeToString</span><span class="p">(</span><span class="n">a2</span><span class="p">);</span>
</code></pre></div></div>

<p>Sounds very interesting. And it looks even better:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">const</span> <span class="kt">wchar_t</span> <span class="o">*</span><span class="kr">__fastcall</span> <span class="n">IsUtil</span><span class="o">::</span><span class="n">MapClientTypeToString</span><span class="p">(</span><span class="kt">int</span> <span class="n">a1</span><span class="p">)</span>
<span class="p">{</span>
  <span class="kt">int</span> <span class="n">v1</span><span class="p">;</span> <span class="c1">// ecx</span>
  <span class="kt">int</span> <span class="n">v2</span><span class="p">;</span> <span class="c1">// ecx</span>
  <span class="kt">int</span> <span class="n">v4</span><span class="p">;</span> <span class="c1">// ecx</span>
  <span class="kt">int</span> <span class="n">v5</span><span class="p">;</span> <span class="c1">// ecx</span>

  <span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="n">a1</span> <span class="p">)</span>
    <span class="k">return</span> <span class="s">L"DESKTOP"</span><span class="p">;</span>

  <span class="n">v1</span> <span class="o">=</span> <span class="n">a1</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
  <span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="n">v1</span> <span class="p">)</span>
    <span class="k">return</span> <span class="s">L"TOUCHKEYBOARD"</span><span class="p">;</span>

  <span class="n">v2</span> <span class="o">=</span> <span class="n">v1</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
  <span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="n">v2</span> <span class="p">)</span>
    <span class="k">return</span> <span class="s">L"LOGONUI"</span><span class="p">;</span>

  <span class="n">v4</span> <span class="o">=</span> <span class="n">v2</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
  <span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="n">v4</span> <span class="p">)</span>
    <span class="k">return</span> <span class="s">L"UAC"</span><span class="p">;</span>

  <span class="n">v5</span> <span class="o">=</span> <span class="n">v4</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
  <span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="n">v5</span> <span class="p">)</span>
    <span class="k">return</span> <span class="s">L"SETTINGSPANE"</span><span class="p">;</span>

  <span class="k">if</span> <span class="p">(</span> <span class="n">v5</span> <span class="o">==</span> <span class="mi">1</span> <span class="p">)</span>
    <span class="k">return</span> <span class="s">L"OOBE"</span><span class="p">;</span>

  <span class="k">return</span> <span class="s">L"OTHER"</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now I understand. So <code class="language-plaintext highlighter-rouge">explorer.exe</code> seems to want the “desktop” user interface (kind of like the <code class="language-plaintext highlighter-rouge">SetNetworkUXMode</code> from above). There are other interfaces too. One that looks like the Windows 10 one I determined, by testing, to be <code class="language-plaintext highlighter-rouge">1</code>, aka <code class="language-plaintext highlighter-rouge">TOUCHKEYBOARD</code> (I exposed the rest via ExplorerPatcher’s Properties GUI as well, each work to a certain degree).</p>

<p>Also, in my window switcher, I don’t want any UI, I just want to benefit from using the callback, as you’ll see in a moment. Again, by experimentation, it seems that providing any value not in that list (like 100) makes it not draw a UI, but the callback still works, the thing still works fine I mean. That’s great!</p>

<p>That’s cool. What about the callback? Well, I think the vtable explians it pretty well:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CTrayInputIndicator::QueryInterface(_GUID const &amp;,void * *)
[thunk]:CTrayInputIndicator::AddRef`adjustor{16}' (void)
[thunk]:CTrayInputIndicator::Release`adjustor{16}' (void)
CTrayInputIndicator::OnUpdateProfile(__MIDL___MIDL_itf_inputswitchserver_0000_0000_0002 const *)
CTrayInputIndicator::OnUpdateTsfFloatingFlags(ulong)
CTrayInputIndicator::OnProfileCountChange(uint,int)
CTrayInputIndicator::OnShowHide(int,int,int)
CTrayInputIndicator::OnImeModeItemUpdate(__MIDL___MIDL_itf_inputswitchserver_0000_0000_0003 const *)
CTrayInputIndicator::OnModalitySelected(__MIDL___MIDL_itf_inputswitchserver_0000_0000_0005)
CTrayInputIndicator::OnContextFlagsChange(ulong)
CTrayInputIndicator::OnTouchKeyboardManualInvoke(void)
</code></pre></div></div>

<p>The method of interest for me in my window switcher was <code class="language-plaintext highlighter-rouge">OnUpdateProfile</code>, so I looked a bit on that. By trial and error and looking around, I determined its signature to actually be something like:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">HRESULT</span> <span class="n">STDMETHODCALLTYPE</span> <span class="n">_IInputSwitchCallback_OnUpdateProfile</span><span class="p">(</span><span class="n">IInputSwitchCallback</span><span class="o">*</span> <span class="n">_this</span><span class="p">,</span> <span class="n">IInputSwitchCallbackUpdateData</span> <span class="o">*</span><span class="n">ud</span><span class="p">)</span>
</code></pre></div></div>

<p>Where the <code class="language-plaintext highlighter-rouge">IInputSwitchCallbackUpdateData</code> looks like this:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="nc">IInputSwitchCallbackUpdateData</span>
<span class="p">{</span>
    <span class="n">DWORD</span> <span class="n">dwID</span><span class="p">;</span> <span class="c1">// OK</span>
    <span class="n">DWORD</span> <span class="n">dw0</span><span class="p">;</span> <span class="c1">// always 0</span>
    <span class="n">LPCWSTR</span> <span class="n">pwszLangShort</span><span class="p">;</span> <span class="c1">// OK ("ENG")</span>
    <span class="n">LPCWSTR</span> <span class="n">pwszLang</span><span class="p">;</span> <span class="c1">// OK ("English (United States)")</span>
    <span class="n">LPCWSTR</span> <span class="n">pwszKbShort</span><span class="p">;</span> <span class="c1">// OK ("US")</span>
    <span class="n">LPCWSTR</span> <span class="n">pwszKb</span><span class="p">;</span> <span class="c1">// OK ("US keyboard")</span>
    <span class="n">LPCWSTR</span> <span class="n">pwszUnknown5</span><span class="p">;</span>
    <span class="n">LPCWSTR</span> <span class="n">pwszUnknown6</span><span class="p">;</span>
    <span class="n">LPCWSTR</span> <span class="n">pwszLocale</span><span class="p">;</span> <span class="c1">// OK ("en-US")</span>
    <span class="n">LPCWSTR</span> <span class="n">pwszUnknown8</span><span class="p">;</span>
    <span class="n">LPCWSTR</span> <span class="n">pwszUnknown9</span><span class="p">;</span>
    <span class="n">LPCWSTR</span> <span class="n">pwszUnknown10</span><span class="p">;</span>
    <span class="n">LPCWSTR</span> <span class="n">pwszUnknown11</span><span class="p">;</span>
    <span class="n">LPCWSTR</span> <span class="n">pwszUnknown12</span><span class="p">;</span>
    <span class="n">LPCWSTR</span> <span class="n">pwszUnknown13</span><span class="p">;</span>
    <span class="n">LPCWSTR</span> <span class="n">pwszUnknown14</span><span class="p">;</span>
    <span class="n">LPCWSTR</span> <span class="n">pwszUnknown15</span><span class="p">;</span>
    <span class="n">LPCWSTR</span> <span class="n">pwszUnknown16</span><span class="p">;</span>
    <span class="n">LPCWSTR</span> <span class="n">pwszUnknown17</span><span class="p">;</span>
    <span class="n">DWORD</span> <span class="n">dwUnknown18</span><span class="p">;</span>
    <span class="n">DWORD</span> <span class="n">dwUnknown19</span><span class="p">;</span>
    <span class="n">DWORD</span> <span class="n">dwNumber</span><span class="p">;</span> <span class="c1">// ???</span>
<span class="p">}</span> <span class="n">IInputSwitchCallbackUpdateData</span><span class="p">;</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">dwID</code> is what contains a HKL combined with a language ID. Its an entire theory that is kind of well explained here that you understand in one evening, you make use of it and then you quickly forget as its too damaging for the human brain to remember such over engineered complications:</p>

<p>https://referencesource.microsoft.com/#system.windows.forms/winforms/Managed/System/WinForms/InputLanguage.cs</p>

<p>Also, take a look on my window switcher’s implementation, to see how I extract the HKL from that:</p>

<p>https://github.com/valinet/sws/blob/74a906c158a91100377a6e8220b0a3c5a8e98657/SimpleWindowSwitcher/sws_WindowSwitcher.c#L3</p>

<p>So, back to <code class="language-plaintext highlighter-rouge">explorer.exe</code>, how do we make it load the Windows 10 switcher instead? That 0 seems to be hardcoded in the code there.</p>

<p>Well, it is, but if we look at the disassembly:</p>

<pre><code class="language-asm">.text:000000014013B21E                 call    cs:__imp_CoCreateInstance
.text:000000014013B225                 nop     dword ptr [rax+rax+00h]
.text:000000014013B22A                 mov     edi, eax
.text:000000014013B22C                 test    eax, eax
.text:000000014013B22E                 js      short loc_14013B294
.text:000000014013B230                 mov     rcx, [rbx]
.text:000000014013B233                 mov     rax, [rcx]
.text:000000014013B236                 mov     r10, 0ABF9B6BC16DC1070h
.text:000000014013B240                 mov     rax, [rax+18h]
.text:000000014013B244                 xor     edx, edx
.text:000000014013B246                 call    cs:__guard_xfg_dispatch_icall_fptr
</code></pre>

<p>Maybe patching the virtual table of the COM interface could be a solution, but it is a bit difficult because the executable is protected by control flow guard. How do you tell? Besides confirming with <code class="language-plaintext highlighter-rouge">dumpbin</code>, it’s right there in front of you. That <code class="language-plaintext highlighter-rouge">mov     r10, 0ABF9B6BC16DC1070h</code> is the canary that is written before the target call site. So you have to write that + 1 (<code class="language-plaintext highlighter-rouge">0ABF9B6BC16DC1071h</code>, you can confirm by looking above <code class="language-plaintext highlighter-rouge">?Init@CInputSwitchControl</code> in <code class="language-plaintext highlighter-rouge">InputSwitchControl.dll</code>) above your target function. Doable, I think. Haven’t tried, but will probably will when the hack that I did breaks, or with some other oppotunity (I have to figure out how to have the compiler place that number there automatically, without me patching the binary afterwards manually). Also, I don’t know if those numbers change between versions of the libraries, I presume not, as that would break compatibility between versions, but I do really have to learn more about CFG.</p>

<p>For this, I instead obted to hack it away, by observing that <code class="language-plaintext highlighter-rouge">edx</code> is never changed beside that <code class="language-plaintext highlighter-rouge">xor edx, edx</code>. So, I just have to neuter that, and then set it myself and immediatly return from my <code class="language-plaintext highlighter-rouge">CoCreateInstance</code> hook. Like this:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//globals:</span>
<span class="kt">char</span> <span class="n">mov_edx_val</span><span class="p">[</span><span class="mi">6</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span> <span class="mh">0xBA</span><span class="p">,</span> <span class="mh">0x00</span><span class="p">,</span> <span class="mh">0x00</span><span class="p">,</span> <span class="mh">0x00</span><span class="p">,</span> <span class="mh">0x00</span><span class="p">,</span> <span class="mh">0xC3</span> <span class="p">};</span>
<span class="kt">char</span><span class="o">*</span> <span class="n">ep_pf</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>

<span class="c1">//...</span>
<span class="c1">// in CoCreateInstance hook:</span>
<span class="kt">char</span> <span class="n">pattern</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span> <span class="mh">0x33</span><span class="p">,</span> <span class="mh">0xD2</span> <span class="p">};</span>
<span class="n">DWORD</span> <span class="n">dwOldProtect</span><span class="p">;</span>
<span class="kt">char</span><span class="o">*</span> <span class="n">p_mov_edx_val</span> <span class="o">=</span> <span class="n">mov_edx_val</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ep_pf</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">ep_pf</span> <span class="o">=</span> <span class="n">memmem</span><span class="p">(</span><span class="n">_ReturnAddress</span><span class="p">(),</span> <span class="mi">200</span><span class="p">,</span> <span class="n">pattern</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">ep_pf</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="c1">// Cancel out `xor edx, edx`</span>
		<span class="n">VirtualProtect</span><span class="p">(</span><span class="n">ep_pf</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="n">PAGE_EXECUTE_READWRITE</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">dwOldProtect</span><span class="p">);</span>
		<span class="n">memset</span><span class="p">(</span><span class="n">ep_pf</span><span class="p">,</span> <span class="mh">0x90</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
		<span class="n">VirtualProtect</span><span class="p">(</span><span class="n">ep_pf</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="n">dwOldProtect</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">dwOldProtect</span><span class="p">);</span>
	<span class="p">}</span>
	<span class="n">VirtualProtect</span><span class="p">(</span><span class="n">p_mov_edx_val</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="n">PAGE_EXECUTE_READWRITE</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">dwOldProtect</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ep_pf</span><span class="p">)</span>
<span class="p">{</span>
	<span class="c1">// Craft a "function" which does `mov edx, whatever; ret` and call it</span>
	<span class="n">DWORD</span><span class="o">*</span> <span class="n">pVal</span> <span class="o">=</span> <span class="n">mov_edx_val</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
	<span class="o">*</span><span class="n">pVal</span> <span class="o">=</span> <span class="n">dwIMEStyle</span><span class="p">;</span>
	<span class="kt">void</span><span class="p">(</span><span class="o">*</span><span class="n">pf_mov_edx_val</span><span class="p">)()</span> <span class="o">=</span> <span class="n">p_mov_edx_val</span><span class="p">;</span>
	<span class="n">pf_mov_edx_val</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>https://github.com/valinet/ExplorerPatcher/blob/ff26abe9a39fb90510450356ba2a807fb97cfa69/ExplorerPatcher/dllmain.c#L4247</p>

<p>The result?</p>

<p><img src="https://user-images.githubusercontent.com/6503598/142456688-87640438-7812-467f-ad1b-00e1db8a594d.png" alt="image" /></p>

<h3 id="conclusion">Conclusion</h3>

<p>Not the most beautiful patches in the world, but they work out rather nicely. Quite some stuff has been achieved with the limited resources available at our disposal. Of course, Microsoft fixing the interface would still be the preferable option, like, in some cases, it only takes 2 bytes give or take, but they chose to deliver a label-less taskbar instead, that’s simply a productivity nightmare. Oh, well… at least <a href="https://github.com/valinet/ExplorerPatcher">ExplorerPatcher</a> exists.</p>

<p>Yeah, a long post, but hopefully it gave you some ideas. Let’s hack away!</p>

<p>P.S. Before asking, no, the 2 battery icons are not some “side effect” or a bug or anything like that: one is Windows’ icon, and the other is the icon of the excellent and versatile <a href="https://github.com/tarcode-apps/BatteryMode">Battery Mode</a> app one can use as a better replacement to what Windows offers in any of its modes.</p>]]></content><author><name>Valentin Radu</name></author><summary type="html"><![CDATA[One problem that was still unsolved was how to restore the functionality of the Windows 10 network, battery and language switcher flyouts in the taskbar. While volume and clock work well, and the language switcher is the one from Windows 11 at least, the network and battery ones just do not launch properly. So, what gives?]]></summary></entry></feed>