<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Anay Pandya</title>
    <description>The latest articles on DEV Community by Anay Pandya (@anay_pandya_bfac6bcdbb055).</description>
    <link>https://dev.to/anay_pandya_bfac6bcdbb055</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3622537%2Fc802b7fd-a1ca-4e14-8934-db2ba492a31e.jpg</url>
      <title>DEV Community: Anay Pandya</title>
      <link>https://dev.to/anay_pandya_bfac6bcdbb055</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anay_pandya_bfac6bcdbb055"/>
    <language>en</language>
    <item>
      <title>Dual Boot broken after Windows Updates - Fixing LUKS, NVIDIA and Time Sync</title>
      <dc:creator>Anay Pandya</dc:creator>
      <pubDate>Tue, 11 Aug 2026 13:42:52 +0000</pubDate>
      <link>https://dev.to/anay_pandya_bfac6bcdbb055/dual-boot-broken-after-windows-updates-fixing-luks-nvidia-and-time-sync-3c98</link>
      <guid>https://dev.to/anay_pandya_bfac6bcdbb055/dual-boot-broken-after-windows-updates-fixing-luks-nvidia-and-time-sync-3c98</guid>
      <description>&lt;p&gt;When my windows updated itself without asking me last night, I rebooted my PC, and GRUB menu didn’t show. It had happened, windows update messed up with my dual-boot setup again. &lt;/p&gt;

&lt;p&gt;Here’s all the things I had to do again after the update to get my Fedora back to normal. The main purpose of writing this blog is to have a list of things that needs to be done everytime this happens (this wasn’t the first, probably won’t be last), and maybe help someone with the same.&lt;/p&gt;

&lt;p&gt;Now, like all good engineers, I thought about automating this, cause who wants to spend 5 minutes, maybe in a couple of months doing this manually, when they can automate.&lt;/p&gt;

&lt;p&gt;Here’s the link to my &lt;a href="https://github.com/ADPer0705/bash/blob/97150587a9282fa7ea3a81655e3ac54db322e2b1/fed_fuck_windows_updates.sh" rel="noopener noreferrer"&gt;bash script&lt;/a&gt;. Please don’t mind the name. Everything below goes through what the script does.&lt;/p&gt;

&lt;h1&gt;
  
  
  LUKS2 + TPM2 Auto-Unlock
&lt;/h1&gt;

&lt;p&gt;Being a good security student, I have my disk encryption enabled in Fedora. But I didn’t want to enter the decryption password everytime I boot up, I have my login password for protection. So I had a Auto-Unlock setup with LUKS2 + TPM2, which no longer worked after the update.&lt;/p&gt;

&lt;p&gt;I already have a blog going through how to setup it up for yourself &lt;a href="https://adper.me/blog/encrypt-once-boot-forever" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, we first wipe existing TPM2 slot if any are present. This makes sure we don’t clutter the LUKS header keyslots on your NVMe drive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemd-cryptenroll &lt;span class="nt"&gt;--wipe-slot&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tpm2 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LUKS_DEVICE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we have to enroll a new fresh TPM2 key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemd-cryptenroll &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--tpm2-device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;auto &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--tpm2-pcrs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;7 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LUKS_DEVICE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we have the keys setup for our disc encryption. For this to work, we have rebuild the INITRAMFS, but that is also needed for the following steps, so we’ll just do that at the end.&lt;/p&gt;

&lt;h1&gt;
  
  
  NVIDIA
&lt;/h1&gt;

&lt;p&gt;I have a RTX4060 in my laptop. Updates from windows, not just system updates but also when you update only the NVIDIA driver from windows, tends to break the GPU setup for linux in a dual boot setup. Here’s how we fix that.&lt;/p&gt;

&lt;p&gt;First, we check is nvidia-smi can connect to the NVIDIA drivers. we do that by simply running &lt;code&gt;nvidia-smi&lt;/code&gt; and seeing if we get our card in the output. &lt;/p&gt;

&lt;p&gt;Next, we check is &lt;code&gt;akmods&lt;/code&gt; service is running properly. This is the handles the compilation of proprietary NVIDIA driver on your machine based on your specific linux kernel version. &lt;code&gt;akmods&lt;/code&gt; is how fedora handles the proprietary drivers, in different systems it can also be &lt;code&gt;dkms&lt;/code&gt;. Apart from that, most systems use a reverse-engineered open source driver called &lt;code&gt;nouveau&lt;/code&gt; for graphics cards. Make sure that if you are using NVIDIA proprietary drivers you don’t have a conflict between the two, you have to properly disable &lt;code&gt;nouveau&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;once we have made sure &lt;code&gt;akmods&lt;/code&gt; (or the driver your system has) is properly running, we need to make sure that our module is available and loaded. The script I wrote handles this by &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;trying to fetch the info of graphics module : &lt;code&gt;modinfo "nvidia"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;checking if the module is running : &lt;code&gt;lsmod | grep "nvidia"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If either of the checks fails&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if nvidia-smi cannot connect to the driver&lt;/li&gt;
&lt;li&gt;if akmods is not available / running&lt;/li&gt;
&lt;li&gt;if the module is not available / not loaded&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;then we rebuild the NVIDIA module, by first removing any cached mods&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/cache/akmods/nvidia/&lt;span class="k"&gt;*&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then force rebuilding the modules :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;akmods &lt;span class="nt"&gt;--force&lt;/span&gt; &lt;span class="nt"&gt;--rebuild&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the script and the code here is specific to my machine, if yours uses a different driver or a different graphics card, the exact details needs to be tweaked accordingly.&lt;/p&gt;

&lt;h1&gt;
  
  
  Time Sync
&lt;/h1&gt;

&lt;p&gt;I frequently had to switch between windows and fedora, so I had synced the clocks on my hardware for both of them. When windows updated, it changed that clock to my local time zone, which is IST. But, fedora and most other linux environments expects time to be in UTC.&lt;/p&gt;

&lt;p&gt;We can check what the current datetime configurations are by doing &lt;code&gt;timedatectl status&lt;/code&gt;. It’ll show what our timezone is and also if the time synchronization (i.e. NTP synchronization) is turned on. When you set it up, you also might have to change some configs in windows. &lt;/p&gt;

&lt;p&gt;To go back to having the hardware clock use UTC and not having to sync the clock everytime you switch between the two, you have to set the hardware clock (RTC) to UTC :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;timedatectl set-local-rtc 0 &lt;span class="nt"&gt;--adjust-system-clock&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then enable the NTP synchonization :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;timedatectl set-ntp &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;NOTE : don’t worry, your system time will still be the time you set, this just sets the time on the hardware in your machine that holds the time, cause linux systems expect that to be in UTC, and may cause troubles in some cases.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Lastly, once everything is done, we rebuild our INITRAMFS :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dracut &lt;span class="nt"&gt;--force&lt;/span&gt; &lt;span class="nt"&gt;--regenerate-all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Dracut&lt;/code&gt; is the module handling INITRAMFS for fedora and other similar distros. For debian, ubuntu and it’s derivatives you need to use &lt;code&gt;update-initramfs&lt;/code&gt; and for Arch and other systems similar to that, you might have to use &lt;code&gt;mkinitcpio&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The last thing to do is to reboot your system, and everything hopefully will be back to normal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secure Boot and MOK Enrollment
&lt;/h3&gt;

&lt;p&gt;If you have Secure Boot enabled in your BIOS, rebuilding the module isn’t enough. You have to tell the motherboard to trust the newly built driver.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate a new key and sign the kernel modules:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;kmodgenca
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Import that key into the UEFI’s trust database
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;mokutil &lt;span class="nt"&gt;--import&lt;/span&gt; /etc/pki/akmods/certs/public_key.der
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This’ll prompt you to create a temporary password. Remember that password. When you reboot, you’ll be intercepted by a blue &lt;strong&gt;MOK Management&lt;/strong&gt; screen before GRUB. Select Enroll MOK, hit continue, and type that temporary password. &lt;/p&gt;

&lt;p&gt;If you are using &lt;code&gt;akmods&lt;/code&gt; then it handles the signing of the drivers by itself and you don’t have to do that manually. It too when you run the command rebuild will prompt you for the password discussed above, and at reboot will show the blue screen. Follow the same instructions as discussed before. &lt;/p&gt;

&lt;h3&gt;
  
  
  Hardware
&lt;/h3&gt;

&lt;p&gt;Most of the commands are specific to my system, which is a Acer Predator, dual booted with windows and Fedora, and it has a dedicated NVIDIA RTX graphics card. But you can still refer to this from a checklist point of view and tweak the commands specific to your system and configuration.&lt;/p&gt;

</description>
      <category>fedora</category>
      <category>microsoft</category>
      <category>scripting</category>
    </item>
    <item>
      <title>How does an LLM "know" the sky is blue?</title>
      <dc:creator>Anay Pandya</dc:creator>
      <pubDate>Tue, 11 Aug 2026 13:41:08 +0000</pubDate>
      <link>https://dev.to/anay_pandya_bfac6bcdbb055/how-does-an-llm-know-the-sky-is-blue-5gap</link>
      <guid>https://dev.to/anay_pandya_bfac6bcdbb055/how-does-an-llm-know-the-sky-is-blue-5gap</guid>
      <description>&lt;p&gt;For most people, LLMs are a magic box that spews out language. Somehow, that language is very similar to "&lt;strong&gt;what a human would say&lt;/strong&gt;". "LLMs work on neural networks", "LLMs predict the next words in a sentence", "They're trained on a large set of data", are some of the common things people say about these mysterious models. &lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;HOW&lt;/strong&gt;?? How can a machine, which was mostly working on logic, 1s and 0s, following instructions, how can that lead to such varied, random, and complex outputs in the form of language that is also mostly correct, not just structurally but also factually. What kind of logical steps can lead to "&lt;strong&gt;human-like intelligence&lt;/strong&gt;"? &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hint: The answer says more about us than the machine.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But to understand that, for those who are unfamiliar with the working of a model, here's a brief introduction of how a LLM works at a bird's-eye view. For those familiar with it, can skip to The Big Question.&lt;/p&gt;

&lt;h1&gt;
  
  
  How LLMs work.
&lt;/h1&gt;

&lt;p&gt;The most common conception of working of an LLM is that it predicts the next word. It is true, LLMs predicts what is called a Token, it is either a word or a part of a word. Some even go so far as to call LLMs a state-of-the-art auto complete.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;But we ask a question and it answers the question, how is that predicting the next word?? That is more like a conversation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yes, but behind the interface, there are two parts to programs like ChatGPT. There is the program, the plain old computer logic, and then there's the model. Model is what makes the predictions that we talked about above. The computer program interacts with the model. How it handles our prompts is that it makes a long string, like a dialogue between us and the model, and feed that to the model, so all the model sees is a dialogue a user has with a system, and apart from that a lot of other information, think of it as a PS. Thus, the model is just trying to "auto-complete" the dialogue writing.&lt;/p&gt;

&lt;p&gt;The models are a result of fairly complex mathematics, but in use, a language model's role is to predict the next word. &lt;/p&gt;

&lt;p&gt;Raw next-token prediction is only the first stage of training. After that, models go through a process called fine-tuning — what it is is, humans rate thousands of model outputs for helpfulness and accuracy, and the model is adjusted to produce more of what gets rated highly. This is why the same underlying math that could generate Reddit comments can also answer your coding questions coherently. The base architecture learns language; the fine-tuning teaches it a job.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Big Question
&lt;/h1&gt;

&lt;p&gt;What almost no-one questions is how these models are so intelligent. Intelligent in the sense that "The sky is red" is just as grammatically correct as "The sky is blue". So if we feed the model "The sky is ", what is preventing it from predicting the word "red".&lt;/p&gt;

&lt;p&gt;The mathematics we mentioned above that makes these models, what it does at a very bird's-eye view is it takes in a bunch of sentences, and the algorithm is trained to predict, given a fragment like "The sky is ", the word that should come next, "based on the examples I trained on". So, it takes in the sentences and "&lt;strong&gt;according to those sentences&lt;/strong&gt;", it predicts the word. Thus the predictions depend on what sentences the maths was applied to to train the model.&lt;/p&gt;

&lt;p&gt;In order to train the text models that we all know, we needed many such sentences. So, these sentences were scraped from books, blogs, websites, etc. The internet and the written literature was indeed a good source of human written sentences. So the models were trained on these. &lt;/p&gt;

&lt;p&gt;Now, the models know what a human would say given a set of words. And, a human is an intelligent being. Given the words "The sky is ", most humans in most situations would truthfully say "blue". Thus, the model doesn't actually know that the sky is blue. What the model knows is that a human would say the word "blue" after "The sky is " in most cases.&lt;/p&gt;

&lt;p&gt;This is of-course a very high level view of how these models become smart. Whether this counts as 'knowing' is itself debated. But to me this idea that our language acts as a proxy to our intelligence is intriguing. How we communicate our thoughts inherently carries some essence of our intelligence, and through mimicking how we communicate, a machine can in-turn mimic the intelligence, the thoughts behind the language. &lt;/p&gt;

</description>
      <category>llm</category>
      <category>ai</category>
      <category>techphilosophy</category>
    </item>
    <item>
      <title>Virtualization — QEMU/KVM</title>
      <dc:creator>Anay Pandya</dc:creator>
      <pubDate>Tue, 11 Aug 2026 13:39:37 +0000</pubDate>
      <link>https://dev.to/anay_pandya_bfac6bcdbb055/virtualization-qemukvm-3j3h</link>
      <guid>https://dev.to/anay_pandya_bfac6bcdbb055/virtualization-qemukvm-3j3h</guid>
      <description>&lt;p&gt;I recently had to set up a network of VMs for my Information and Network Security class. I decided to go with a QEMU/KVM setup instead of the usual VirtualBox/VMware route. Reason? I enjoy the feeling I get when I am tinkering with something. A VirtualBox/VMware setup would have been reliable and straightforward, but I was chasing the &lt;strong&gt;“I did something”&lt;/strong&gt; emotion. That is also the same reason I started using Neovim for coding; it has a dexterous feel to it.&lt;/p&gt;

&lt;p&gt;Before we start, this paragraph is a quick introduction to QEMU and KVM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;KVM&lt;/strong&gt; stands for &lt;strong&gt;Kernel-based Virtual Machine&lt;/strong&gt;. It is a Linux kernel subsystem that allows Linux to use hardware virtualization extensions provided by the CPU. On an Intel machine, this is generally VT-x; on AMD it is AMD-V. As all things in linux KVM too is exposed through a file. It is similar to a device node, and is exposed via &lt;code&gt;/dev/kvm&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;QEMU&lt;/strong&gt; is the machine emulator/virtualizer. It provides the virtual hardware: disks, network cards, display devices, firmware, PCI devices, USB controllers, and so on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;KVM&lt;/strong&gt; provides the kernel-level acceleration that lets QEMU execute guest CPU instructions using the host CPU’s virtualization capabilities rather than emulating every instruction in software.&lt;/p&gt;

&lt;p&gt;So, very roughly:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvd8yhfi1mnus7du42bwk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvd8yhfi1mnus7du42bwk.png" alt="Virtualization Stack on a linux system with QEMU/KVM" width="721" height="973"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;QEMU/KVM&lt;/code&gt; is what enables virtualization on linux. Then, there is &lt;strong&gt;libvirt&lt;/strong&gt;. It is like a management layer and API for virtualization technologies such as QEMU/KVM. It manages things such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VM definitions&lt;/li&gt;
&lt;li&gt;CPU and memory configuration&lt;/li&gt;
&lt;li&gt;virtual disks&lt;/li&gt;
&lt;li&gt;networking&lt;/li&gt;
&lt;li&gt;storage pools&lt;/li&gt;
&lt;li&gt;firmware&lt;/li&gt;
&lt;li&gt;snapshots&lt;/li&gt;
&lt;li&gt;VM lifecycle&lt;/li&gt;
&lt;li&gt;devices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;virsh&lt;/code&gt; is the command-line interface to libvirt. &lt;code&gt;virt-manager&lt;/code&gt; is the graphical management interface. It talks to &lt;code&gt;libvirt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There is also GNOME Boxes, which is another graphical frontend for QEMU/KVM/libvirt. Boxes is meant to make virtualization simpler and hide a lot of the underlying configuration. &lt;code&gt;virt-manager&lt;/code&gt; exposes much more of the actual virtual hardware and libvirt configuration.&lt;/p&gt;

&lt;p&gt;Since my goal wasn’t just to get a VM running but to understand what was happening, I went with &lt;code&gt;virt-manager&lt;/code&gt; and &lt;code&gt;virsh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is the stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 virt-manager
                      │
                    virsh
                      │
                      ▼
                   libvirt
                      │
                      ▼
                  QEMU / KVM
                      │
             ┌────────┴────────┐
             │                 │
         virtual CPU      virtual devices
             │                 │
             └────────┬────────┘
                      ▼
                   Guest OS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Before anything else: does KVM actually work?
&lt;/h1&gt;

&lt;p&gt;Before building anything, I ran a sanity check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virt-host-validate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This walks through the host’s virtualization capabilities and flags anything that is missing.&lt;/p&gt;

&lt;p&gt;This is useful because it can catch problems such as hardware virtualization not being available or required virtualization components not being configured properly.&lt;/p&gt;

&lt;p&gt;It is much better to discover that here than to discover it three VMs deep when &lt;code&gt;virt-install&lt;/code&gt; throws some cryptic KVM error.&lt;/p&gt;

&lt;p&gt;I could also check that the KVM device actually exists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /dev/kvm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the point where the &lt;code&gt;/dev/kvm&lt;/code&gt; explanation from earlier becomes useful. KVM is part of the kernel, but userspace virtualization software interacts with it through this device interface.&lt;/p&gt;




&lt;h1&gt;
  
  
  The first quirk: &lt;code&gt;qemu:///session&lt;/code&gt; vs &lt;code&gt;qemu:///system&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;I had to do sudo to make VMs since some of the operations like networking needed elevated privileges. So I started looking into ways to avoid having to use sudo again and again.&lt;/p&gt;

&lt;p&gt;Turns out libvirt has something called the &lt;strong&gt;connection scopes&lt;/strong&gt;. There was the session scope which was associated to &lt;strong&gt;my user session&lt;/strong&gt; (&lt;code&gt;qemu:///session&lt;/code&gt;). Then there was the &lt;strong&gt;system-wide libvirt daemon&lt;/strong&gt;, connecting to the system scope (&lt;code&gt;qemu:///system&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;This is the usual choice when creating infrastructure-like VMs, because resources such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;system networks&lt;/li&gt;
&lt;li&gt;system storage pools&lt;/li&gt;
&lt;li&gt;shared VM definitions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;are managed centrally.&lt;/p&gt;

&lt;p&gt;This also explained the situation where:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh net-list &lt;span class="nt"&gt;--all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;showed nothing, while:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;virsh net-list &lt;span class="nt"&gt;--all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;showed the default network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name      State   Autostart   Persistent
--------------------------------------------
default   active  yes         yes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To put it simply I was looking at two different libvirt worlds.&lt;/p&gt;

&lt;p&gt;The important thing here is that &lt;code&gt;qemu:///session&lt;/code&gt; and &lt;code&gt;qemu:///system&lt;/code&gt; aren’t two different versions of QEMU. They are two different libvirt connection scopes.&lt;/p&gt;

&lt;p&gt;So, in order to work with the system-wide VMs and networks, I decided to use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;qemu:///system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can be done with the flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;--connect&lt;/span&gt; qemu:///system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For persistence, you can set the following environment variable by updating your &lt;code&gt;.zshrc&lt;/code&gt; or &lt;code&gt;.bashrc&lt;/code&gt; files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LIBVIRT_DEFAULT_URI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"qemu:///system"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, after refreshing the terminal session, the scope can be checked with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh uri
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which should return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;qemu:///system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is another part to this though: the user still needs permission to access the system libvirt service. On my Fedora setup, I was already a member of the relevant groups:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;groups&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which included:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kvm
libvirt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why I could eventually work with &lt;code&gt;qemu:///system&lt;/code&gt; without having to put &lt;code&gt;sudo&lt;/code&gt; in front of every command.&lt;/p&gt;




&lt;h1&gt;
  
  
  Building the lab network
&lt;/h1&gt;

&lt;p&gt;The whole point of this exercise was networking.&lt;/p&gt;

&lt;p&gt;But before actually creating the VMs, I needed somewhere to put them.&lt;/p&gt;

&lt;p&gt;The important thing here is that I ended up with &lt;strong&gt;two completely separate networks&lt;/strong&gt;, because they serve two completely different purposes.&lt;/p&gt;

&lt;p&gt;The first is &lt;code&gt;labnet&lt;/code&gt;. This is the network for my &lt;strong&gt;Information and Network Security class&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The second is libvirt’s &lt;code&gt;default&lt;/code&gt; network. This is what I use for my standalone &lt;strong&gt;ParrotOS HTB machine&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;These are intentionally separate.&lt;/p&gt;

&lt;p&gt;The INS lab looks roughly like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         Fedora Host
                              │
                           labnet
                     192.168.100.0/24
                              │
              ┌───────────────┼───────────────┐
              │               │               │
           Windows       Metasploitable      Kali
            target          target         Distrobox
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While Parrot is completely separate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  Fedora Host
                       │
                    default
                       │
                    Internet
                       │
                    ParrotOS
                       │
                    HTB VPN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parrot is not part of &lt;code&gt;labnet&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;labnet&lt;/code&gt; is for my INS class. Parrot is a standalone machine for HTB.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why &lt;code&gt;labnet&lt;/code&gt; exists
&lt;/h1&gt;

&lt;p&gt;This wasn’t just tidiness.&lt;/p&gt;

&lt;p&gt;Metasploitable is deliberately full of vulnerable and unpatched services. I did not want to put a deliberately exploitable machine directly onto the physical network at NFSU.&lt;/p&gt;

&lt;p&gt;If I had bridged Metasploitable directly onto my physical NIC, I could potentially have exposed its vulnerable services to other machines on the university network.&lt;/p&gt;

&lt;p&gt;Instead, I created a private libvirt network.&lt;/p&gt;

&lt;p&gt;The lab machines can communicate with each other, and the Fedora host can communicate with the lab, without putting the vulnerable machines directly onto the university LAN.&lt;/p&gt;

&lt;p&gt;The network is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;labnet
192.168.100.0/24
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with the libvirt host-side gateway at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;192.168.100.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The network is described using a libvirt XML definition.&lt;/p&gt;

&lt;p&gt;I used something along these lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;network&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;name&amp;gt;&lt;/span&gt;labnet&lt;span class="nt"&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;forward&lt;/span&gt; &lt;span class="na"&gt;mode=&lt;/span&gt;&lt;span class="s"&gt;"nat"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;bridge&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"labnet_lab"&lt;/span&gt; &lt;span class="na"&gt;stp=&lt;/span&gt;&lt;span class="s"&gt;"on"&lt;/span&gt; &lt;span class="na"&gt;delay=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;ip&lt;/span&gt; &lt;span class="na"&gt;address=&lt;/span&gt;&lt;span class="s"&gt;"192.168.100.1"&lt;/span&gt; &lt;span class="na"&gt;netmask=&lt;/span&gt;&lt;span class="s"&gt;"255.255.255.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dhcp&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;range&lt;/span&gt; &lt;span class="na"&gt;start=&lt;/span&gt;&lt;span class="s"&gt;"192.168.100.10"&lt;/span&gt; &lt;span class="na"&gt;end=&lt;/span&gt;&lt;span class="s"&gt;"192.168.100.100"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dhcp&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ip&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/network&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I saved this as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;labnet.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a few important things happening here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;name&amp;gt;&lt;/span&gt;labnet&lt;span class="nt"&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;gives the libvirt network its name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;bridge&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"labnet_lab"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;tells libvirt to create a virtual bridge with that name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ip&lt;/span&gt; &lt;span class="na"&gt;address=&lt;/span&gt;&lt;span class="s"&gt;"192.168.100.1"&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;sets the address of the host-side interface on this virtual network.&lt;/p&gt;

&lt;p&gt;And:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dhcp&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;range&lt;/span&gt; &lt;span class="na"&gt;start=&lt;/span&gt;&lt;span class="s"&gt;"192.168.100.10"&lt;/span&gt; &lt;span class="na"&gt;end=&lt;/span&gt;&lt;span class="s"&gt;"192.168.100.100"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dhcp&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;provides DHCP addresses to the VMs.&lt;/p&gt;

&lt;p&gt;The:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;forward&lt;/span&gt; &lt;span class="na"&gt;mode=&lt;/span&gt;&lt;span class="s"&gt;"nat"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means that traffic leaving the virtual network can be NATed through the Fedora host.&lt;/p&gt;

&lt;p&gt;So the topology is approximately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                Physical Network
                       │
                   Fedora Host
                       │
                 ┌─────┴─────┐
                 │           │
              Internet    labnet
                           │
                    192.168.100.1
                           │
              ┌────────────┼────────────┐
              │            │            │
           Windows    Metasploitable   Kali
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important security property here is that &lt;code&gt;labnet&lt;/code&gt; is &lt;strong&gt;not simply a bridge to my physical Ethernet/Wi-Fi network&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Creating the network
&lt;/h1&gt;

&lt;p&gt;Once I had the XML file, I defined the network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh net-define labnet.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then started it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh net-start labnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And made it start automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh net-autostart labnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I could check it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh net-list &lt;span class="nt"&gt;--all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which should show something along the lines of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name      State   Autostart   Persistent
--------------------------------------------
default   active  yes         yes
labnet    active  yes         yes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I can inspect the network with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh net-info labnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and see the actual XML libvirt is using with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh net-dumpxml labnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I can also see the bridge from Fedora:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip &lt;span class="nb"&gt;link &lt;/span&gt;show labnet_lab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip addr show labnet_lab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was particularly useful because I could finally see that the “network” wasn’t some abstract thing inside libvirt. There was an actual bridge interface on my Fedora host.&lt;/p&gt;

&lt;p&gt;One thing I ran into here was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error: Failed to start network labnet
error: error creating bridge interface labnet_lab:
Operation not permitted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was another consequence of me mixing the session and system libvirt scopes. I was trying to start a system-level network from the wrong libvirt connection.&lt;/p&gt;

&lt;p&gt;Once I consistently used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;qemu:///system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the network was being managed by the same libvirt instance as my VMs.&lt;/p&gt;

&lt;p&gt;If I ever want to shut the network down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh net-destroy labnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and if I want to remove the persistent network definition entirely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh net-undefine labnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Connecting the machines to &lt;code&gt;labnet&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;Once the network exists, attaching a VM to it is surprisingly simple.&lt;/p&gt;

&lt;p&gt;For example, my Windows VM has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;--network&lt;/span&gt; &lt;span class="nv"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;labnet,model&lt;span class="o"&gt;=&lt;/span&gt;virtio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and Metasploitable has the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;--network&lt;/span&gt; &lt;span class="nv"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;labnet,model&lt;span class="o"&gt;=&lt;/span&gt;virtio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells libvirt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Give this VM a virtual network interface and connect it to the existing &lt;code&gt;labnet&lt;/code&gt; network.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Fedora host itself also has an interface on that bridge, which means I can communicate with the lab from the host.&lt;/p&gt;

&lt;p&gt;I also use Kali through &lt;strong&gt;Distrobox&lt;/strong&gt; on Fedora. It is not another VM in this setup. Since it is running as a containerized userspace environment on the Fedora host, it can use the host’s networking and therefore participate in the lab from the host side.&lt;/p&gt;

&lt;p&gt;This gives me the INS lab topology I actually wanted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                     Fedora Host
                           │
                     labnet_lab
                           │
          ┌────────────────┼────────────────┐
          │                │                │
       Windows        Metasploitable       Kali
        target           target          Distrobox
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is my &lt;strong&gt;INS class network&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Creating the Metasploitable VM
&lt;/h1&gt;

&lt;p&gt;For my first target, I had a Metasploitable QCOW2 image.&lt;/p&gt;

&lt;p&gt;Initially I kept the image in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/VMs/libvirt/metasploitable.qcow2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and tried &lt;code&gt;virt-install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I got:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cannot access storage file
'/home/adper/VMs/libvert/metasploitable.qcow2'
(as uid:107, gid:107): Permission denied
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fact that I could read the file did not mean the QEMU process could.&lt;/p&gt;

&lt;p&gt;The system libvirt QEMU process runs under a restricted account, in this case the &lt;code&gt;qemu&lt;/code&gt; user. It also needs permission to traverse the directories leading to the file.&lt;/p&gt;

&lt;p&gt;So, ideally, we are supposed to put the VM storage at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/var/lib/libvirt/images/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I copied the disk there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo cp&lt;/span&gt; ~/VMs/libvirt/metasploitable.qcow2 &lt;span class="se"&gt;\&lt;/span&gt;
    /var/lib/libvirt/images/metasploitable.qcow2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Fedora, SELinux can also matter when moving files into locations used by virtualization, so it is useful to make sure the file has the expected SELinux context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;restorecon &lt;span class="nt"&gt;-v&lt;/span&gt; /var/lib/libvirt/images/metasploitable.qcow2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I then created the VM with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virt-install &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--connect&lt;/span&gt; qemu:///system &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; metasploitable &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--memory&lt;/span&gt; 512 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--vcpus&lt;/span&gt; 1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disk&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/lib/libvirt/images/metasploitable.qcow2,bus&lt;span class="o"&gt;=&lt;/span&gt;virtio &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; &lt;span class="nv"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;labnet,model&lt;span class="o"&gt;=&lt;/span&gt;virtio &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--import&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--os-variant&lt;/span&gt; ubuntu16.04 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--graphics&lt;/span&gt; spice &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--noautoconsole&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important thing here is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--import
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;because I already had an operating system disk. I wasn’t asking &lt;code&gt;virt-install&lt;/code&gt; to install an OS from an ISO. I was asking it to build a libvirt VM around an existing disk image.&lt;/p&gt;

&lt;p&gt;The VM can then be started with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh start metasploitable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and its graphical display can be opened with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virt-viewer &lt;span class="nt"&gt;--connect&lt;/span&gt; qemu:///system metasploitable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although in a security lab, I don’t necessarily need to “enter” Metasploitable at all.&lt;/p&gt;

&lt;p&gt;The interesting architecture is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  labnet
                     │
          ┌──────────┴──────────┐
          │                     │
       Kali                 Metasploitable
     Distrobox                  target
          │                     │
          └─────── attacks ─────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;This is Kali attacking Metasploitable, not Parrot.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Kali is running through Distrobox on my Fedora host and is participating in the INS &lt;code&gt;labnet&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I can interact with Metasploitable from Kali over the lab network using things such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nmap
ssh
ftp
http
smb
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;depending on what services are exposed.&lt;/p&gt;

&lt;p&gt;This is the point of having a vulnerable target inside a private lab.&lt;/p&gt;




&lt;h1&gt;
  
  
  Creating the Windows VM
&lt;/h1&gt;

&lt;p&gt;Similarly, I also setup a Windows VM with this &lt;code&gt;virt-install&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virt-install &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--connect&lt;/span&gt; qemu:///system &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; windows-lab &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--memory&lt;/span&gt; 4096 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--vcpus&lt;/span&gt; 4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disk&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/lib/libvirt/images/windows-lab.qcow2,size&lt;span class="o"&gt;=&lt;/span&gt;60,bus&lt;span class="o"&gt;=&lt;/span&gt;virtio,format&lt;span class="o"&gt;=&lt;/span&gt;qcow2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cdrom&lt;/span&gt; /var/lib/libvirt/boot/windows11.iso &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disk&lt;/span&gt; /usr/share/virtio-win/virtio-win.iso,device&lt;span class="o"&gt;=&lt;/span&gt;cdrom &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; &lt;span class="nv"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;labnet,model&lt;span class="o"&gt;=&lt;/span&gt;virtio &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--graphics&lt;/span&gt; spice &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--video&lt;/span&gt; qxl &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--os-variant&lt;/span&gt; win11 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--boot&lt;/span&gt; uefi &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tpm&lt;/span&gt; backend.type&lt;span class="o"&gt;=&lt;/span&gt;emulator,backend.version&lt;span class="o"&gt;=&lt;/span&gt;2.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;virt-install&lt;/code&gt; is basically telling libvirt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Create a VM with these characteristics.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The important pieces are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--memory 4096
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4 GB RAM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--vcpus 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4 virtual CPUs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--disk ... size=60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 60 GB virtual disk.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--network network=labnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Connect the VM to my INS lab network.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--graphics spice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use SPICE for the graphical console.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--boot uefi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use UEFI firmware.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--tpm backend.type=emulator,backend.version=2.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Provide a virtual TPM 2.0 device.&lt;/p&gt;

&lt;p&gt;And:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--cdrom /var/lib/libvirt/boot/windows11.iso
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;makes the Windows ISO the installation media.&lt;/p&gt;

&lt;p&gt;I downloaded the Windows ISO directly from Microsoft’s website and used the unactivated installation for the lab. Since this wasn’t going to be my daily Windows machine, I didn’t particularly care about activation or the watermark.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Third quirk: VirtIO
&lt;/h1&gt;

&lt;p&gt;Windows setup booted successfully.&lt;/p&gt;

&lt;p&gt;Then I reached the disk selection screen, there was no disk.&lt;/p&gt;

&lt;p&gt;The VM had a 60GB virtual disk. QEMU had created it. The disk existed but Windows couldn’t see it.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because I had explicitly said:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bus=virtio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;in the &lt;code&gt;virt-install&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;VirtIO is a paravirtualized device interface designed to provide efficient virtual I/O. Instead of pretending to be some old physical disk controller, QEMU exposes a VirtIO device and the guest uses a VirtIO driver to communicate with it.&lt;/p&gt;

&lt;p&gt;Linux generally has excellent VirtIO support.&lt;/p&gt;

&lt;p&gt;Windows, however, didn’t have the storage driver available during installation.&lt;/p&gt;

&lt;p&gt;Luckily I had also attached:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/usr/share/virtio-win/virtio-win.iso
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;as a second virtual CD-ROM.&lt;/p&gt;

&lt;p&gt;From the Windows installer I selected &lt;strong&gt;Load Driver&lt;/strong&gt;, browsed to the VirtIO CD, and loaded the storage driver.&lt;/p&gt;

&lt;p&gt;After that, the 60 GB virtual disk appeared.&lt;/p&gt;

&lt;p&gt;The important part here is that the disk wasn’t missing.&lt;/p&gt;

&lt;p&gt;It was simply being presented through a virtual controller for which Windows didn’t yet have a driver.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                     Fedora
                        │
                /var/lib/libvirt/
                   images/
                        │
                        ▼
              windows-lab.qcow2
                        │
                        ▼
                      QEMU
                        │
                   VirtIO disk
                        │
                        ▼
                     Windows
                        │
                VirtIO driver
                        │
                        ▼
                  Windows disk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same thing happened with networking.&lt;/p&gt;

&lt;p&gt;The VM had a VirtIO network interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--network network=labnet,model=virtio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but Windows needed the VirtIO network driver before it could use that interface.&lt;/p&gt;

&lt;p&gt;This was a good reminder that virtual hardware is still hardware from the guest’s perspective.&lt;/p&gt;

&lt;p&gt;The device doesn’t physically exist, but the operating system still needs to know how to communicate with it.&lt;/p&gt;




&lt;h1&gt;
  
  
  UEFI is also virtualized
&lt;/h1&gt;

&lt;p&gt;When booting the VM as well we get the screen from UEFI with &lt;code&gt;Please select boot device&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is the guest’s virtual firmware.&lt;/p&gt;

&lt;p&gt;The VM is essentially being presented with an entire virtual computer as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host OS
  │
  └── QEMU
       │
       ├── virtual CPU
       ├── virtual RAM
       ├── virtual disk
       ├── virtual NIC
       └── virtual UEFI
             │
             └── virtual hardware
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one of those things that is easy to forget when using a GUI VM application.&lt;/p&gt;

&lt;p&gt;The VM isn’t just “a program running Windows”.&lt;/p&gt;

&lt;p&gt;QEMU is presenting Windows with a computer.&lt;/p&gt;




&lt;h1&gt;
  
  
  Secure Boot and OVMF
&lt;/h1&gt;

&lt;p&gt;I also wanted to setup a Parrot VM to perform practical tasks from HackTheBox. It was a procedure similar to what was described above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This VM is separate from my INS lab.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I did not put Parrot on &lt;code&gt;labnet&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Instead, Parrot uses libvirt’s &lt;code&gt;default&lt;/code&gt; network so that it can have normal outbound Internet connectivity and be used as my standalone HTB machine.&lt;/p&gt;

&lt;p&gt;The Parrot HTB edition was available to me as an ISO, so unlike downloading a ready-made QCOW2 image, I had to create the QCOW2 disk myself and install Parrot into it.&lt;/p&gt;

&lt;p&gt;I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4 GB RAM
4 vCPUs
60 GB QCOW2
VirtIO disk
VirtIO network
UEFI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The VM was created with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virt-install &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--connect&lt;/span&gt; qemu:///system &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; parrot-htb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--memory&lt;/span&gt; 4096 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--vcpus&lt;/span&gt; 4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disk&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/lib/libvirt/images/parrot-htb.qcow2,size&lt;span class="o"&gt;=&lt;/span&gt;60,bus&lt;span class="o"&gt;=&lt;/span&gt;virtio,format&lt;span class="o"&gt;=&lt;/span&gt;qcow2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cdrom&lt;/span&gt; /var/lib/libvirt/boot/parrot-htb.iso &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; &lt;span class="nv"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default,model&lt;span class="o"&gt;=&lt;/span&gt;virtio &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--graphics&lt;/span&gt; spice &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--os-variant&lt;/span&gt; debian13 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--boot&lt;/span&gt; &lt;span class="nv"&gt;firmware&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;efi,firmware.feature0.name&lt;span class="o"&gt;=&lt;/span&gt;secure-boot,firmware.feature0.enabled&lt;span class="o"&gt;=&lt;/span&gt;no
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I initially had a Secure Boot problem.&lt;/p&gt;

&lt;p&gt;When the VM booted, there was a blue UEFI error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Verification failed: (0x1A) Security Violation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This wasn’t KVM rejecting Parrot.&lt;/p&gt;

&lt;p&gt;It wasn’t QEMU rejecting Parrot.&lt;/p&gt;

&lt;p&gt;The virtual UEFI firmware was rejecting the bootloader because Secure Boot was enabled.&lt;/p&gt;

&lt;p&gt;The firmware being used by QEMU in this setup is OVMF, which provides UEFI firmware for virtual machines.&lt;/p&gt;

&lt;p&gt;I inspected the host’s capabilities with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh domcapabilities
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and found:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;loader&lt;/span&gt; &lt;span class="na"&gt;supported=&lt;/span&gt;&lt;span class="s"&gt;'yes'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    ...
    &lt;span class="nt"&gt;&amp;lt;enum&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;'secure'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;value&amp;gt;&lt;/span&gt;no&lt;span class="nt"&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/enum&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/loader&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My host supported UEFI with Secure Boot disabled.&lt;/p&gt;

&lt;p&gt;I checked the supported boot sub-options with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virt-install &lt;span class="s1"&gt;'--boot=?'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which showed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;firmware
firmware.feature[0-9]*.enabled
firmware.feature[0-9]*.name
loader.secure
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So instead of blindly specifying a firmware binary, I could explicitly configure the firmware feature.&lt;/p&gt;

&lt;p&gt;I wanted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UEFI:        ON
Secure Boot: OFF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the relevant part of the command was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--boot firmware=efi,firmware.feature0.name=secure-boot,firmware.feature0.enabled=no
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was another useful lesson: UEFI and Secure Boot are not the same thing.&lt;/p&gt;

&lt;p&gt;I wanted UEFI, just without Secure Boot enforcement.&lt;/p&gt;




&lt;h1&gt;
  
  
  Installing Parrot
&lt;/h1&gt;

&lt;p&gt;Once the firmware configuration was correct, Parrot booted into its installer.&lt;/p&gt;

&lt;p&gt;The installer detected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vda - 60.00 GiB (/dev/vda)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was my virtual disk.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;/dev/vda&lt;/code&gt; name is again related to VirtIO.&lt;/p&gt;

&lt;p&gt;The actual chain is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fedora
  │
  └── parrot-htb.qcow2
         │
         ▼
       QEMU
         │
         ▼
      VirtIO disk
         │
         ▼
      /dev/vda
         │
         ▼
       Parrot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I chose &lt;strong&gt;Erase disk&lt;/strong&gt; because this was a brand-new virtual 60GB disk.&lt;/p&gt;

&lt;p&gt;This does not erase my Fedora disk.&lt;/p&gt;

&lt;p&gt;It formats the virtual disk that QEMU presented to Parrot.&lt;/p&gt;

&lt;p&gt;So the actual storage relationship is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Physical SSD
└── Fedora filesystem
    └── /var/lib/libvirt/images/
        └── parrot-htb.qcow2
              │
              ▼
             QEMU
              │
              ▼
           /dev/vda
              │
              ├── EFI partition
              └── Parrot filesystem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is probably one of the most important mental models to have when working with VMs.&lt;/p&gt;




&lt;h1&gt;
  
  
  Parrot and the &lt;code&gt;default&lt;/code&gt; network
&lt;/h1&gt;

&lt;p&gt;Unlike the INS machines, Parrot is attached to libvirt’s &lt;code&gt;default&lt;/code&gt; network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;--network&lt;/span&gt; &lt;span class="nv"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default,model&lt;span class="o"&gt;=&lt;/span&gt;virtio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is intentional.&lt;/p&gt;

&lt;p&gt;Parrot is not part of my university lab.&lt;/p&gt;

&lt;p&gt;It is a standalone machine that I use for Hack The Box, so I want it to have normal outbound connectivity.&lt;/p&gt;

&lt;p&gt;The topology is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  Fedora Host
                       │
                    default
                       │
                    Internet
                       │
                    ParrotOS
                       │
                    HTB VPN
                       │
                   Hack The Box
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;default&lt;/code&gt; network is separate from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;labnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which is my INS class network.&lt;/p&gt;

&lt;p&gt;This separation also means that accidentally attacking something from my HTB Parrot VM does not mean I am accidentally attacking the machines in my university lab network.&lt;/p&gt;




&lt;h1&gt;
  
  
  Creating Snapshots
&lt;/h1&gt;

&lt;p&gt;This was one of the main reasons I wanted a VM in the first place.&lt;/p&gt;

&lt;p&gt;HTB encourages having a disposable machine because you’re going to install things, modify configurations, break packages, change network settings, and generally do things that aren’t necessarily good for a normal workstation.&lt;/p&gt;

&lt;p&gt;Once I had Parrot installed and configured, I shut it down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh shutdown parrot-htb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I checked that it had actually shut down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh domstate parrot-htb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once it reported:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;shut off
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I created the snapshot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh snapshot-create-as &lt;span class="se"&gt;\&lt;/span&gt;
    parrot-htb &lt;span class="se"&gt;\&lt;/span&gt;
    clean-parrot-htb &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"Clean Parrot HTB baseline"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where &lt;code&gt;parrot-htb&lt;/code&gt; was my VM and &lt;code&gt;clean-parrot-htb&lt;/code&gt; is my snapshot.&lt;/p&gt;

&lt;p&gt;I could inspect it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh snapshot-list parrot-htb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh snapshot-list parrot-htb &lt;span class="nt"&gt;--tree&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I have a clean baseline that I can return to after destroying the machine with some experiment.&lt;/p&gt;

&lt;p&gt;The workflow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 clean-parrot-htb
                        │
                        ▼
                   Boot Parrot
                        │
                  Do HTB stuff
                        │
              ┌─────────┼─────────┐
              │         │         │
          install     modify    break
           tools      configs   packages
              │         │         │
              └─────────┼─────────┘
                        │
                       💥
                        │
                        ▼
                snapshot-revert
                        │
                        ▼
                 clean-parrot-htb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To revert:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh shutdown parrot-htb
virsh snapshot-revert parrot-htb clean-parrot-htb
virsh start parrot-htb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Snapshots are not backups though.&lt;/p&gt;

&lt;p&gt;If the physical SSD dies, the VM and its snapshot data can disappear together.&lt;/p&gt;

&lt;p&gt;Snapshots are rollback points, not a replacement for backups.&lt;/p&gt;

&lt;p&gt;I also don’t want to create a snapshot every five minutes. I would rather have a clean baseline and a few meaningful checkpoints such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;clean-parrot-htb
tools-configured
before-experiment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Managing the VMs after creation
&lt;/h1&gt;

&lt;p&gt;Once the VMs are created, I don’t need to run &lt;code&gt;virt-install&lt;/code&gt; every time I want to use them.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;virt-install&lt;/code&gt; is primarily for creating and installing the VM.&lt;/p&gt;

&lt;p&gt;After that, the normal lifecycle is handled through &lt;code&gt;virsh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;List VMs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh list &lt;span class="nt"&gt;--all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start a VM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh start parrot-htb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open its graphical console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virt-viewer &lt;span class="nt"&gt;--connect&lt;/span&gt; qemu:///system parrot-htb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gracefully shut it down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh shutdown parrot-htb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check its state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh domstate parrot-htb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Forcefully power it off:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh destroy parrot-htb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, &lt;code&gt;destroy&lt;/code&gt; sounds much more dramatic than it actually is.&lt;/p&gt;

&lt;p&gt;It does not delete the VM.&lt;/p&gt;

&lt;p&gt;It is basically the equivalent of pulling the power cable.&lt;/p&gt;

&lt;p&gt;If I actually want to remove the libvirt domain definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh undefine parrot-htb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it uses UEFI NVRAM, I may have to do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh undefine parrot-htb &lt;span class="nt"&gt;--nvram&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  The UEFI NVRAM surprise
&lt;/h1&gt;

&lt;p&gt;When I first tried to remove my Parrot VM, I ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh undefine parrot-htb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and got:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Requested operation is not valid:
cannot undefine domain with nvram
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is because the UEFI VM had its own NVRAM / firmware variable store.&lt;/p&gt;

&lt;p&gt;So the VM was more than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;domain definition
+
disk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It was also:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;domain definition
+
QCOW2 disk
+
UEFI NVRAM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the proper removal command was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh undefine parrot-htb &lt;span class="nt"&gt;--nvram&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another reminder that a VM is more than a disk image.&lt;/p&gt;




&lt;h1&gt;
  
  
  Storage pools
&lt;/h1&gt;

&lt;p&gt;Libvirt also has the concept of &lt;strong&gt;storage pools&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh pool-list &lt;span class="nt"&gt;--all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and saw pools such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;images
libvert
libvert-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A storage pool is basically a libvirt-managed source of storage from which volumes can be created and managed.&lt;/p&gt;

&lt;p&gt;A virtual disk can therefore be thought of as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;storage pool
     │
     └── volume
            │
            └── QCOW2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is another abstraction layer.&lt;/p&gt;

&lt;p&gt;One confusing moment was trying:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh pool-info default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and getting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Storage pool not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s because &lt;code&gt;default&lt;/code&gt; in my case was the &lt;strong&gt;network&lt;/strong&gt;, not a storage pool.&lt;/p&gt;

&lt;p&gt;Libvirt uses names such as &lt;code&gt;default&lt;/code&gt; in multiple contexts, so this is something to keep in mind.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why QCOW2?
&lt;/h1&gt;

&lt;p&gt;QCOW2 is QEMU’s Copy-On-Write disk image format.&lt;/p&gt;

&lt;p&gt;Instead of immediately allocating the entire advertised capacity, the image can grow as data is written.&lt;/p&gt;

&lt;p&gt;So a:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;60GB QCOW2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;doesn’t necessarily mean 60GB is consumed immediately.&lt;/p&gt;

&lt;p&gt;It can start much smaller and grow towards its virtual capacity.&lt;/p&gt;

&lt;p&gt;This also becomes interesting when snapshots are involved because copy-on-write allows the VM to maintain changed storage state relative to an earlier state.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Base disk
   │
   ├── original blocks
   │
   └── changed blocks
          │
          ▼
       snapshot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For disposable security labs, this is very convenient.&lt;/p&gt;




&lt;h1&gt;
  
  
  Vagrant: the next abstraction
&lt;/h1&gt;

&lt;p&gt;Vagrant is essentially an environment automation layer for virtual machines.&lt;/p&gt;

&lt;p&gt;Instead of manually running commands such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virt-install ...
virsh ...
virsh ...
virsh ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you can describe an environment in a &lt;code&gt;Vagrantfile&lt;/code&gt; and let Vagrant construct it.&lt;/p&gt;

&lt;p&gt;The stack becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Vagrant
                    │
                Vagrantfile
                    │
              Vagrant provider
                    │
                 libvirt
                    │
                QEMU / KVM
                    │
                    ▼
                   VM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vagrant itself is not the hypervisor.&lt;/p&gt;

&lt;p&gt;It doesn’t replace QEMU, KVM or libvirt.&lt;/p&gt;

&lt;p&gt;It sits above them and automates the creation and configuration of machines.&lt;/p&gt;

&lt;p&gt;This becomes particularly interesting for my lab because instead of manually creating every target, I can describe parts of the environment as code.&lt;/p&gt;




&lt;h1&gt;
  
  
  Using Vagrant with &lt;code&gt;labnet&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;This is where I think Vagrant can actually become useful for my setup.&lt;/p&gt;

&lt;p&gt;The interesting use of Vagrant here is &lt;strong&gt;the INS lab&lt;/strong&gt;, not my Parrot HTB VM.&lt;/p&gt;

&lt;p&gt;Parrot already exists as a manually configured standalone VM on the &lt;code&gt;default&lt;/code&gt; network. I don’t need Vagrant to manage it.&lt;/p&gt;

&lt;p&gt;Instead, I can use Vagrant to reproduce machines that belong on my existing &lt;code&gt;labnet&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The architecture becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  Vagrantfile
                       │
                       ▼
                    Vagrant
                       │
                 libvirt provider
                       │
                       ▼
                    libvirt
                       │
                     labnet
                       │
          ┌────────────┼────────────┐
          │            │            │
       VM target    VM target     etc.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vagrant’s libvirt provider can attach a VM to an existing libvirt network using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="ss"&gt;libvirt__network_name: &lt;/span&gt;&lt;span class="s2"&gt;"labnet"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Vagrant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;

  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt; &lt;span class="s2"&gt;"lab-target"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;

    &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"debian/bookworm64"&lt;/span&gt;

    &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network&lt;/span&gt; &lt;span class="s2"&gt;"private_network"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;ip: &lt;/span&gt;&lt;span class="s2"&gt;"192.168.100.20"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;libvirt__network_name: &lt;/span&gt;&lt;span class="s2"&gt;"labnet"&lt;/span&gt;

    &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provider&lt;/span&gt; &lt;span class="ss"&gt;:libvirt&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;
      &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cpus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important bit is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="ss"&gt;libvirt__network_name: &lt;/span&gt;&lt;span class="s2"&gt;"labnet"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Don’t invent another network. Connect this VM to my existing libvirt &lt;code&gt;labnet&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;can create the VM and connect it to the lab.&lt;/p&gt;

&lt;p&gt;The interesting part is that the environment definition lives in the repository rather than only existing in my head.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INS-Lab/
├── Vagrantfile
├── provisioning/
│   ├── target.sh
│   └── setup.sh
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I can potentially recreate the lab on another machine with the same basic structure.&lt;/p&gt;

&lt;p&gt;Vagrant can also provision the guest after creating it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provision&lt;/span&gt; &lt;span class="s2"&gt;"shell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;path: &lt;/span&gt;&lt;span class="s2"&gt;"provisioning/setup.sh"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where Vagrant starts becoming much more interesting than simply being a replacement for &lt;code&gt;virt-install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It gives me &lt;strong&gt;Infrastructure-as-Code for the virtual lab&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Vagrant vs Docker
&lt;/h1&gt;

&lt;p&gt;There is an important distinction here.&lt;/p&gt;

&lt;p&gt;Docker containers generally look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host kernel
    │
    ├── container
    ├── container
    └── container
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A VM looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host
 │
 └── hypervisor
       │
       ├── VM
       ├── VM
       └── VM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Containers share the host kernel.&lt;/p&gt;

&lt;p&gt;Virtual machines have their own guest kernel.&lt;/p&gt;

&lt;p&gt;For a cybersecurity lab where I want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Linux attacker
Windows target
vulnerable Linux target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;VMs make much more sense because I am interested in interacting with different operating systems and kernels, not just isolated applications.&lt;/p&gt;

&lt;p&gt;Vagrant doesn’t change this.&lt;/p&gt;

&lt;p&gt;It simply makes the VM environment easier to reproduce.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why I didn’t start with Vagrant
&lt;/h1&gt;

&lt;p&gt;Ironically, Vagrant could have made the whole process much easier.&lt;/p&gt;

&lt;p&gt;But that would have defeated part of the purpose.&lt;/p&gt;

&lt;p&gt;If I had started with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I might have learned:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“This command makes a VM.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead, I wanted to understand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/dev/kvm
   ↓
KVM
   ↓
QEMU
   ↓
libvirt
   ↓
virt-install / virsh / virt-manager
   ↓
virtual hardware
   ↓
guest OS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if I eventually use Vagrant, I understand what it is automating.&lt;/p&gt;

&lt;p&gt;That is a much better position to be in.&lt;/p&gt;




&lt;h1&gt;
  
  
  The final topology
&lt;/h1&gt;

&lt;p&gt;I ended up with two separate virtual environments because they serve two completely different purposes.&lt;/p&gt;

&lt;h2&gt;
  
  
  INS lab
&lt;/h2&gt;

&lt;p&gt;This is the network I built for my Information and Network Security class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         Fedora Host
                              │
                          labnet_lab
                              │
              ┌───────────────┼───────────────┐
              │               │               │
           Windows       Metasploitable      Kali
            target          target         Distrobox
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The vulnerable machines live here.&lt;/p&gt;

&lt;p&gt;Kali is my attacker environment for this network.&lt;/p&gt;

&lt;p&gt;The Fedora host itself can also interact with the network.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTB environment
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         Fedora Host
                              │
                           default
                              │
                           ParrotOS
                              │
                           HTB VPN
                              │
                         Hack The Box
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parrot is my standalone HTB workstation.&lt;/p&gt;




&lt;h1&gt;
  
  
  My setup
&lt;/h1&gt;

&lt;p&gt;This is the setup I ended up using while writing this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hardware
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Laptop:
Acer Predator PHN16-71

CPU:
Intel Core i9-13900HX

GPU:
NVIDIA RTX 4060 Laptop GPU

RAM:
16 GB

Display:
2560 × 1600
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 16 GB RAM is probably the biggest constraint when running multiple VMs.&lt;/p&gt;

&lt;p&gt;For example, running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Parrot      4 GB
Windows     4 GB
Metasploit  512 MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;already puts a significant chunk of the machine’s memory into VMs before Fedora, QEMU, libvirt and everything else are considered.&lt;/p&gt;

&lt;p&gt;This is why I generally kept the main machines around 4 GB rather than throwing 8 or 16 GB at them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Host OS
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fedora Linux
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The virtualization stack is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Linux
  │
KVM
  │
QEMU
  │
libvirt
  │
virt-install / virsh / virt-manager
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My user is also part of the relevant groups:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;groups&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which includes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wheel
kvm
libvirt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Virtualization versions
&lt;/h2&gt;

&lt;p&gt;At the time of writing, the versions on my machine were:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;libvirt:
12.0.0

QEMU:
10.2.2

virt-install:
5.1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I checked these with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Compiled against library: libvirt 12.0.0
Using library: libvirt 12.0.0
Using API: QEMU 12.0.0
Running hypervisor: QEMU 10.2.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virt-install &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5.1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also checked the OS variants known to &lt;code&gt;osinfo&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;osinfo-query os | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; debian
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and had:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;debian13 | Debian 13 | 13
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;available, which is what I used as the &lt;code&gt;--os-variant&lt;/code&gt; for Parrot.&lt;/p&gt;




&lt;h2&gt;
  
  
  VMs
&lt;/h2&gt;

&lt;p&gt;The main VMs in the setup are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;metasploitable
    │
    └── vulnerable target for INS lab

windows-lab
    │
    └── Windows target for INS lab

parrot-htb
    │
    └── standalone HTB workstation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The storage lives under:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/var/lib/libvirt/images/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;so the disks look roughly like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/var/lib/libvirt/images/
├── metasploitable.qcow2
├── windows-lab.qcow2
└── parrot-htb.qcow2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Windows installation ISO and VirtIO driver ISO were kept under:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/var/lib/libvirt/boot/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Networks
&lt;/h2&gt;

&lt;p&gt;There are two important libvirt networks in my setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;default
labnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;default&lt;/code&gt; is used by my standalone Parrot HTB VM.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;labnet&lt;/code&gt; is my private INS class network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;192.168.100.0/24
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with the virtual gateway:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;192.168.100.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and a DHCP range such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;192.168.100.10
-
192.168.100.100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The intended architecture is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         Fedora Host
                              │
              ┌───────────────┴────────────────┐
              │                                │
            labnet                           default
              │                                │
        ┌─────┼─────┐                      Parrot
        │     │     │
     Windows Meta   Kali
               Distrobox
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two networks are deliberately separate.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final thoughts
&lt;/h1&gt;

&lt;p&gt;The biggest thing I took away wasn’t how to install a VM.&lt;/p&gt;

&lt;p&gt;It was that &lt;strong&gt;a VM isn’t a single thing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When I say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I have a Windows VM.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I’m actually talking about a collection of layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Physical hardware
      │
      ▼
Linux kernel
      │
      ▼
KVM
      │
      ▼
QEMU
      │
      ├── virtual CPU
      ├── virtual RAM
      ├── virtual disk
      ├── virtual NIC
      ├── virtual TPM
      ├── virtual display
      └── virtual firmware
              │
              ▼
          Guest OS
              │
              ▼
         Applications
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And sitting above QEMU/KVM is another layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;virt-manager
virsh
virt-install
      │
      ▼
   libvirt
      │
      ▼
   QEMU/KVM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then there are even more abstractions around specific resources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;libvirt
 ├── domains       → VMs
 ├── networks      → virtual networks
 ├── storage pools → storage management
 ├── volumes       → virtual disks
 └── snapshots     → rollback points
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once I understood that, a lot of the weird errors stopped being mysterious.&lt;/p&gt;

&lt;p&gt;A permission error on a QCOW2 wasn’t “QEMU being weird”; it was the &lt;strong&gt;QEMU process having a different identity&lt;/strong&gt; from my shell.&lt;/p&gt;

&lt;p&gt;A missing Windows disk wasn’t “Windows failing”; it was the guest &lt;strong&gt;missing a VirtIO driver&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A UEFI security violation wasn’t “Parrot being broken”; it was &lt;strong&gt;virtual firmware enforcing Secure Boot&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;qemu:///session&lt;/code&gt; and &lt;code&gt;qemu:///system&lt;/code&gt; weren’t two versions of QEMU; they were &lt;strong&gt;two different libvirt scopes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And &lt;code&gt;/dev/kvm&lt;/code&gt; wasn’t a virtual machine; it was the &lt;strong&gt;kernel interface through which QEMU gets hardware-assisted virtualization&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The fact that I could put Windows, Metasploitable, my Fedora host, and Kali into one private network while keeping Parrot in a completely separate HTB environment also made the networking side of virtualization much more tangible.&lt;/p&gt;

&lt;p&gt;That, more than anything, is why I’m glad I didn’t just install VirtualBox.&lt;/p&gt;

&lt;p&gt;I wanted the feeling of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“I did something.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And the extra tinkering actually taught me something.&lt;/p&gt;

</description>
      <category>virtualization</category>
      <category>fedora</category>
    </item>
    <item>
      <title>Encrypt Once, Boot Forever: TPM2 Auto-Unlock on Fedora 44</title>
      <dc:creator>Anay Pandya</dc:creator>
      <pubDate>Wed, 20 May 2026 07:39:07 +0000</pubDate>
      <link>https://dev.to/anay_pandya_bfac6bcdbb055/encrypt-once-boot-forever-tpm2-auto-unlock-on-fedora-44-533i</link>
      <guid>https://dev.to/anay_pandya_bfac6bcdbb055/encrypt-once-boot-forever-tpm2-auto-unlock-on-fedora-44-533i</guid>
      <description>&lt;p&gt;When I set up Fedora 44 on my Predator Helios Neo 16, I enabled full disk encryption using LUKS. Non-negotiable for a cybersecurity student.&lt;/p&gt;

&lt;p&gt;The problem: every boot starts with a password prompt before anything loads. Fine in principle, friction in practice — especially when you're rebooting frequently during a fresh system setup.&lt;/p&gt;

&lt;p&gt;The solution: seal the decryption key inside the TPM chip and let the hardware unlock the drive automatically on legitimate boots. No password prompt, full encryption intact.&lt;/p&gt;

&lt;p&gt;This is how I set it up.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding the pieces
&lt;/h2&gt;

&lt;h3&gt;
  
  
  LUKS
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;LUKS (Linux Unified Key Setup)&lt;/strong&gt; is the standard disk encryption layer on Linux. When enabled, your entire partition is encrypted at rest. Before the OS can boot, something needs to provide the decryption key — normally that's you, typing a passphrase.&lt;/p&gt;

&lt;p&gt;LUKS2 (the modern version, default on Fedora) supports multiple keyslots and tokens. Think of keyslots as different keys to the same lock — you can have a passphrase keyslot and a TPM keyslot simultaneously. Either one unlocks the drive.&lt;/p&gt;

&lt;h3&gt;
  
  
  TPM2
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;TPM (Trusted Platform Module)&lt;/strong&gt; is a dedicated security chip on your motherboard. It's not software — it's physical hardware that stores cryptographic secrets and enforces conditions on when those secrets are&lt;br&gt;
released.&lt;/p&gt;

&lt;p&gt;The relevant capability here: the TPM can &lt;strong&gt;seal&lt;/strong&gt; a secret to specific system state measurements. It will only release that secret if the system is in the expected state. Move the SSD to another machine? No TPM. Different hardware state? Key not released. Your data stays encrypted.&lt;/p&gt;
&lt;h3&gt;
  
  
  PCRs
&lt;/h3&gt;

&lt;p&gt;PCR stands for &lt;strong&gt;Platform Configuration Register&lt;/strong&gt;. These are values inside the TPM that represent measurements of different stages of your boot process — fingerprints of your firmware, bootloader, Secure Boot&lt;br&gt;
configuration, and so on.&lt;/p&gt;

&lt;p&gt;When you seal a key to a PCR, you're saying: "only release this key if PCR X has this exact value." The key relevant PCRs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PCR&lt;/th&gt;
&lt;th&gt;What it measures&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;UEFI firmware&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Secure Boot state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Shim/bootloader&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I bound to &lt;strong&gt;PCR 7&lt;/strong&gt; — the Secure Boot state. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The key is released as long as Secure Boot policy hasn't changed&lt;/li&gt;
&lt;li&gt;Firmware updates don't break it (PCR 0 would)&lt;/li&gt;
&lt;li&gt;It's the right balance of security and usability for a daily driver&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  systemd-cryptenroll
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;systemd-cryptenroll&lt;/code&gt; is a tool built directly into systemd for enrolling hardware security tokens — including TPM2 — as LUKS keyslots.&lt;/p&gt;

&lt;p&gt;I initially tried &lt;strong&gt;clevis&lt;/strong&gt;, the older and more commonly documented approach. The bind command ran without errors, the tokens appeared in the LUKS dump, dracut rebuilt successfully — but the password prompt kept&lt;br&gt;
appearing at boot. After debugging, the clevis dracut module simply wasn't making it into the initramfs reliably on Fedora 44.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;systemd-cryptenroll&lt;/code&gt; solved it on the first try. On Fedora 44 with modern systemd, it's the right tool — tighter initramfs integration, no additional packages needed, and it just works.&lt;/p&gt;


&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Fedora 44 with LUKS2 full disk encryption enabled&lt;/li&gt;
&lt;li&gt;Secure Boot on (verify in BIOS)&lt;/li&gt;
&lt;li&gt;TPM2 chip (verify below)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Step 1: Verify TPM2 is available
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; /dev/tpm&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="c"&gt;# Expected: /dev/tpm0  /dev/tpmrm0&lt;/span&gt;

systemd-cryptenroll &lt;span class="nt"&gt;--tpm2-device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;list
&lt;span class="c"&gt;# Expected: shows your TPM2 device with driver info&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;My output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;PATH        DEVICE      DRIVER
/dev/tpmrm0 INTC6001:00 tpm_crb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;tpm_crb&lt;/code&gt; driver is the Intel TPM 2.0 on the i9-13900HX platform.&lt;br&gt;
If you see output here, you're good.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2: Identify your LUKS partition
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lsblk
&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/crypttab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;My crypttab:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;luks-67669a3d-... &lt;span class="nv"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;67669a3d-... none discard,x-initrd.attach
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The partition is &lt;code&gt;/dev/nvme0n1p7&lt;/code&gt;, UUID &lt;code&gt;67669a3d-...&lt;/code&gt;. Note your UUID&lt;br&gt;
— you'll need it.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3: Enroll the TPM2 key
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemd-cryptenroll &lt;span class="se"&gt;\\&lt;/span&gt;
  &lt;span class="nt"&gt;--tpm2-device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;auto &lt;span class="se"&gt;\\&lt;/span&gt;
  &lt;span class="nt"&gt;--tpm2-pcrs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;7 &lt;span class="se"&gt;\\&lt;/span&gt;
  /dev/nvme0n1p7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Enter your existing LUKS passphrase when prompted. This authorizes adding a new keyslot. The command:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generates a new random key&lt;/li&gt;
&lt;li&gt;Seals it inside the TPM against the current PCR 7 value&lt;/li&gt;
&lt;li&gt;Adds it as a new LUKS2 keyslot with a &lt;code&gt;systemd-tpm2&lt;/code&gt; token&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your original passphrase keyslot is untouched.&lt;/p&gt;

&lt;p&gt;Verify the enrollment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;cryptsetup luksDump /dev/nvme0n1p7 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A3&lt;/span&gt; &lt;span class="s2"&gt;"Tokens"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a &lt;code&gt;systemd-tpm2&lt;/code&gt; token entry.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Update crypttab
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/crypttab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change the line from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;luks-UUID &lt;span class="nv"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;UUID none discard,x-initrd.attach
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;luks-UUID &lt;span class="nv"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;UUID - discard,x-initrd.attach,tpm2-device&lt;span class="o"&gt;=&lt;/span&gt;auto,tpm2-pcrs&lt;span class="o"&gt;=&lt;/span&gt;7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two changes: &lt;code&gt;none&lt;/code&gt; becomes &lt;code&gt;-&lt;/code&gt; (means "no keyfile, use token"), and&lt;br&gt;
&lt;code&gt;tpm2-device=auto,tpm2-pcrs=7&lt;/code&gt; is appended to the options. This tells&lt;br&gt;
the initramfs to attempt TPM2 unlock before falling back to passphrase.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Rebuild initramfs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dracut &lt;span class="nt"&gt;--force&lt;/span&gt; &lt;span class="nt"&gt;--regenerate-all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This rebuilds the initramfs for all installed kernels, baking in the TPM2 unlock logic. Let it complete fully before rebooting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Reboot
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The boot sequence should now go straight from GRUB to the KDE login screen — no passphrase prompt. The TPM provides the key automatically&lt;br&gt;
during the initramfs stage.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security model
&lt;/h2&gt;

&lt;p&gt;This setup gives you encryption at rest against the realistic threat:&lt;br&gt;
physical theft of the device or SSD.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Protected against:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SSD removed and accessed on another machine — no TPM, no key&lt;/li&gt;
&lt;li&gt;Cold boot attacks on a powered-off or hibernated system&lt;/li&gt;
&lt;li&gt;Forensic disk imaging without your hardware&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Not protected against:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Someone stealing your running, logged-in laptop&lt;/li&gt;
&lt;li&gt;Evil maid attacks with full hardware access over time&lt;/li&gt;
&lt;li&gt;Your passphrase being compromised (keep it safe regardless)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The passphrase remains your fallback. If the TPM fails, if you flash firmware, or if Secure Boot state changes, the TPM unlock breaks and&lt;br&gt;
you'll be prompted for the passphrase. Enter it, boot normally, then re-run the &lt;code&gt;systemd-cryptenroll&lt;/code&gt; command to re-seal to the new state.&lt;/p&gt;




&lt;h2&gt;
  
  
  What breaks the seal
&lt;/h2&gt;

&lt;p&gt;These events change PCR 7 and will require re-enrollment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flashing BIOS/UEFI firmware&lt;/li&gt;
&lt;li&gt;Toggling Secure Boot in BIOS settings&lt;/li&gt;
&lt;li&gt;TPM hardware failure or reset&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When this happens: passphrase at boot → login → re-run step 3 → rebuild initramfs → done.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hardware
&lt;/h2&gt;

&lt;p&gt;Acer Predator Helios Neo 16&lt;br&gt;
Intel i9-13900HX · RTX 4060 · 16GB RAM · 1TB NVMe&lt;br&gt;
Fedora Linux 44 (KDE Plasma Desktop Edition)&lt;/p&gt;

</description>
      <category>linux</category>
      <category>fedora</category>
      <category>security</category>
      <category>encryption</category>
    </item>
    <item>
      <title>The Bayesian Trap - A Mathematical Case for Trying Something New</title>
      <dc:creator>Anay Pandya</dc:creator>
      <pubDate>Sat, 11 Apr 2026 10:52:17 +0000</pubDate>
      <link>https://dev.to/anay_pandya_bfac6bcdbb055/the-bayesian-trap-a-mathematical-case-for-trying-something-new-1bol</link>
      <guid>https://dev.to/anay_pandya_bfac6bcdbb055/the-bayesian-trap-a-mathematical-case-for-trying-something-new-1bol</guid>
      <description>&lt;p&gt;Humans are notoriously terrible at intuitive probability.&lt;/p&gt;

&lt;p&gt;Imagine you wake up feeling slightly off. You visit the doctor, and she runs a battery of tests. A week later, she calls with bad news: you tested positive for a rare disease that affects 0.1% of the population.&lt;/p&gt;

&lt;p&gt;Panicked, you ask how accurate the test is. "It correctly identifies 99% of people who have the disease," she says, "and only gives a false positive to 1% of healthy people."&lt;/p&gt;

&lt;p&gt;If you rely on your gut, you probably assume you have a 99% chance of being sick. But here's where the math kicks in.&lt;/p&gt;

&lt;p&gt;Most people latch onto one thing the doctor said — &lt;em&gt;"99% accurate"&lt;/em&gt; — and conclude that a positive result means a 99% chance of illness. The human brain is terrible at remembering &lt;strong&gt;the prior&lt;/strong&gt; when presented with shiny new evidence. We fixate on the test's accuracy and forget the other crucial piece of information:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"...a rare disease that affects 0.1% of the population."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So which is it — 0.1% or 99%? Neither. The true probability depends on &lt;strong&gt;both&lt;/strong&gt; — the accuracy of the test &lt;em&gt;and&lt;/em&gt; the baseline rarity of the disease. The complete question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Given that 0.1% of the population has this disease AND that you tested positive on a test that is 99% accurate, what are the actual chances you are sick?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To answer this properly, we need Bayes' theorem.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Pure Logic
&lt;/h3&gt;

&lt;p&gt;Formulated in the 18th century by Thomas Bayes, the theorem calculates the probability of a hypothesis being true given new evidence:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2tlnzl8vc1mkabnauaex.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2tlnzl8vc1mkabnauaex.png" alt="P(A∣B)=\frac{P(B \mid A) \times P(A)}{P(B)}​" width="800" height="147"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In plain English: the probability of your hypothesis (A) being true given new evidence (B) depends heavily on &lt;strong&gt;P(A)&lt;/strong&gt; — the &lt;em&gt;prior probability&lt;/em&gt;, the baseline reality &lt;em&gt;before&lt;/em&gt; the new evidence arrived.&lt;/p&gt;

&lt;p&gt;In our medical example, the prior is the rarity of the disease: 0.1%. Because the disease is so rare, &lt;strong&gt;the sheer volume of false positives generated from the healthy population completely drowns out the true positives&lt;/strong&gt;. Bayes' theorem forces us to respect that baseline reality before we let a single test result dictate our conclusions.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Philosophy of Belief
&lt;/h3&gt;

&lt;p&gt;When Richard Price first published Bayes' work, he compared it to a man emerging from a cave and seeing the sun rise for the first time. The man doesn't know if the sunrise is a permanent feature of the universe or a bizarre one-off event. But every subsequent sunrise updates his mental &lt;em&gt;prior&lt;/em&gt;. With each new piece of evidence, his certainty approaches 100%.&lt;/p&gt;

&lt;p&gt;We all do this subconsciously. Bayes' theorem is the algorithm running under the hood of human experience. And that is precisely where the trap lies.&lt;/p&gt;

&lt;p&gt;Imagine you're trying to learn beatboxing. Let &lt;code&gt;S&lt;/code&gt; be the probability of success, and let every approach you try be an action:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Action A&lt;/strong&gt; — You follow YouTube tutorials. Doesn't help much.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action B&lt;/strong&gt; — You read a blog on technique. No real progress.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action C&lt;/strong&gt; — You find a dedicated tutorial website. Still not clicking.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With every failure, your brain updates its prior belief in success. Because your environment has been consistently hostile, P(S) begins to drift toward zero.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Mathematical Imperative
&lt;/h3&gt;

&lt;p&gt;Here is the danger of being &lt;em&gt;too&lt;/em&gt; good at Bayesian updating. In Bayes' equation, if your prior P(A) drops to zero, the entire calculation zeroes out — permanently.&lt;/p&gt;

&lt;p&gt;If you get stuck on Action C and your prior belief in success hits zero, you subconsciously conclude the game is unwinnable. You stop experimenting. You fall into a self-fulfilling prophecy: no new actions means no success, which perfectly validates your pessimistic prior.&lt;/p&gt;

&lt;p&gt;But here's what most people miss. Failing at Action A doesn't mean P(S) is low. It means P(S|A) — the probability of success &lt;em&gt;via that specific approach&lt;/em&gt; — is low. The true probability of success remains unknown. You've only ruled out one path.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Implication
&lt;/h3&gt;

&lt;p&gt;This leads to two uncomfortable truths:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;You never truly know the probability of success.&lt;/strong&gt; You only know the probability of success given your prior experiences — which are, by definition, limited.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Since you can't know the real P(S), Bayes' theorem tells you that you cannot definitively conclude something is impossible&lt;/strong&gt; — only that your current approach isn't working.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The logical response? Keep experimenting. Try a different vector. The colloquial wisdom of &lt;em&gt;"f*ck around and find out"&lt;/em&gt; turns out to have rigorous mathematical backing.&lt;/p&gt;




&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Let's return to the disease dilemma and actually do the math.&lt;/p&gt;

&lt;p&gt;Plugging in the numbers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;P(Disease)&lt;/strong&gt; = 0.001 &lt;em&gt;(the prior — 0.1% prevalence)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;P(Positive | Disease)&lt;/strong&gt; = 0.99 &lt;em&gt;(test correctly catches 99% of sick people)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;P(Positive | No Disease)&lt;/strong&gt; = 0.01 &lt;em&gt;(1% false positive rate)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First, the total probability of testing positive:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhml5enow917ggadjv1z2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhml5enow917ggadjv1z2.png" alt="P(\text{Positive}) = (0.99 \times 0.001) + (0.01 \times 0.999) = 0.00099 + 0.00999 = 0.01098" width="800" height="24"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now applying Bayes' theorem:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9bepr8faid3u3gdvp29u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9bepr8faid3u3gdvp29u.png" alt="P(\text{Disease} \mid \text{Positive}) = \frac{0.99 \times 0.001}{0.01098} \approx \textbf{9\" width="800" height="88"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not 99%. Not 0.1%. Just 9% — surprising, but mathematically undeniable.&lt;/p&gt;

&lt;p&gt;This is the Bayesian Trap in full effect. A 99% accurate test sounds ironclad until you factor in how rare the disease is. The prior swamps the evidence. Most people who test positive are, in fact, perfectly healthy — not because the test is bad, but because the disease is rare enough that false positives vastly outnumber true ones.&lt;/p&gt;

&lt;p&gt;The lesson extends well beyond medicine. Whenever we encounter compelling new evidence — a positive test, a failed experiment, a string of rejections — our instinct is to let that evidence rewrite everything. Bayes' theorem says: slow down. Ask what you already knew before the evidence arrived, and how much weight that prior deserves. Strong evidence should update your beliefs — but it should never erase your baseline understanding of reality.&lt;/p&gt;

&lt;p&gt;The framework is both humbling and liberating. Humbling, because it reveals how easily our intuition misleads us. Liberating, because it tells us that one data point — one test result, one failed attempt, one closed door — is almost never the whole story.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;If you want to go deeper on this:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://youtu.be/R13BD8qKeTg?si=McU2zULHplBrLTFn" rel="noopener noreferrer"&gt;The Bayesian Trap&lt;/a&gt; by Veritasium — a masterclass in how Bayesian reasoning shapes everything from science to superstition.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=HZGCoVF3YvM" rel="noopener noreferrer"&gt;Bayes theorem, the geometry of changing beliefs&lt;/a&gt; by 3Blue1Brown — a visual, intuition-building walkthrough that makes the math feel inevitable.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;P.S.&lt;/strong&gt; I didn't rediscover Bayes' theorem in a statistics textbook or a philosophy seminar. I found it buried in a PDF about Machine Learning methodologies while grinding through AI Red Teamer material on Hack The Box. The context was oddly perfect: in offensive security, every failed payload quietly erodes your confidence. Your internal P(success) bleeds toward zero with each dead end, and at some point you stop probing and start doubting whether the box is even solvable. Bayes reframes that spiral. A failed exploit doesn't indict the goal — it indicts the method. The prior on the target's vulnerabilities hasn't changed; only your evidence about one particular vector has. That distinction — between &lt;em&gt;this approach failed&lt;/em&gt; and &lt;em&gt;success is impossible&lt;/em&gt; — is the entire difference between a good hacker and one who gives up at the first hardened firewall.&lt;/p&gt;

</description>
      <category>mathematics</category>
      <category>probability</category>
      <category>philosophy</category>
    </item>
    <item>
      <title>You Don’t Need "Prompt Engineering" to Talk to AI</title>
      <dc:creator>Anay Pandya</dc:creator>
      <pubDate>Tue, 25 Nov 2025 10:22:40 +0000</pubDate>
      <link>https://dev.to/anay_pandya_bfac6bcdbb055/you-dont-need-prompt-engineering-to-talk-to-ai-1pei</link>
      <guid>https://dev.to/anay_pandya_bfac6bcdbb055/you-dont-need-prompt-engineering-to-talk-to-ai-1pei</guid>
      <description>&lt;h3&gt;
  
  
  A simple guide to getting what you want from Large Language Models, from a student who lives in the lab.
&lt;/h3&gt;

&lt;p&gt;There is a lot of noise right now about "Prompt Engineering." You see people selling courses, sharing 50-line "super prompts," and treating AI interaction like it’s a complex coding language.&lt;/p&gt;

&lt;p&gt;And sure, if you are building a complex software application, that engineering matters. But for the everyday user—for my friends, my dad, or my peers in non-tech fields—you don't need to study the science of prompts to get good results.&lt;/p&gt;

&lt;p&gt;If you can hold a conversation, you can master an LLM. You don't need a degree in "Prompt Engineering." You just need to understand the psychology of the machine.&lt;/p&gt;

&lt;p&gt;I’m a senior student studying AI and Cybersecurity, so I spend a &lt;em&gt;lot&lt;/em&gt; of time looking under the hood of these models. Here is my take on how to interact with them effectively, without making it a chore.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Understand How It "Thinks" (The Prediction Game)
&lt;/h2&gt;

&lt;p&gt;To talk to an LLM (Large Language Model), you just need to understand one basic concept: &lt;strong&gt;It predicts the next word.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That’s it. It takes the text you typed, looks at the context, and guesses what word likely comes next. That is why you sometimes see the answer typing out one word at a time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Think of it like this:&lt;/strong&gt; Imagine you are having a conversation with a friend. You’re struggling to find the words, so your friend starts guessing what you mean based on:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Who they think you are.&lt;/li&gt;
&lt;li&gt;What you were just talking about.&lt;/li&gt;
&lt;li&gt;The vibe of the conversation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The difference is, this "friend" (the AI) has read almost everything on the internet, so it has something to say about &lt;em&gt;everything&lt;/em&gt;. But it still relies on &lt;strong&gt;you&lt;/strong&gt; to set the scene. If you give it nothing, it guesses blindly. If you give it context, it reads your mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Context is Currency
&lt;/h2&gt;

&lt;p&gt;This is the single biggest mistake people make: asking a naked question.&lt;/p&gt;

&lt;p&gt;The AI doesn't know you, your job, or your style—unless you paste it in. The quality of your output depends entirely on the "Context" you provide. Think of context as the raw material the AI needs to build your answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't just ask:&lt;/strong&gt; "Write an email to my boss." &lt;strong&gt;Do this instead:&lt;/strong&gt; "Here is a draft of an email I wrote. Here are three bullet points I need to add. Rewrite this to sound more professional but keep it under 100 words."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro Tip:&lt;/strong&gt; You can paste in old reports, rough drafts, or even screenshots (if the model supports images). The more "reference material" you give it, the less it has to guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Give It a "Role" (The Before &amp;amp; After)
&lt;/h2&gt;

&lt;p&gt;Since the AI predicts words based on patterns, the easiest hack is to tell it &lt;em&gt;who&lt;/em&gt; it is supposed to be. This changes the vocabulary and tone instantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❌ The Lazy Approach:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt; "Explain quantum physics." &lt;strong&gt;AI:&lt;/strong&gt; &lt;em&gt;Gives a dry, Wikipedia-style definition that is boring and hard to read.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;✅ The "Role" Approach:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt; "Act as a &lt;strong&gt;friendly high school science teacher&lt;/strong&gt;. Explain quantum physics to a class of 15-year-olds using an analogy about video games." &lt;strong&gt;AI:&lt;/strong&gt; &lt;em&gt;"Okay class! Imagine the universe is like a game rendering engine..."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;See the difference? The prompt wasn't "engineered." You just gave the AI a persona.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Treat It Like a Podcast Interview
&lt;/h2&gt;

&lt;p&gt;A common mistake is asking one giant, complex question and hoping for a perfect answer. That rarely works well.&lt;/p&gt;

&lt;p&gt;Instead, treat the chat like you are interviewing someone. You want to "steer" the conversation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start broad:&lt;/strong&gt; "Tell me about photography basics."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drill down:&lt;/strong&gt; "Okay, you mentioned 'ISO'. What is that?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apply it:&lt;/strong&gt; "How would I use ISO to take a picture of the night sky?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review:&lt;/strong&gt; "Here is a photo I took; critique it based on what we discussed."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps the AI on track. You are building a "thread" of context that makes every subsequent answer smarter than the last.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Direct the Conversation
&lt;/h2&gt;

&lt;p&gt;The AI is always looking for cues from you. To get the best result, you need to be in the "driver's seat" regarding three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Assumptions:&lt;/strong&gt; What should it take for granted? (e.g., "Assume I have a limited budget.")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ambiguity:&lt;/strong&gt; What are you okay with being vague?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Specifics:&lt;/strong&gt; What &lt;em&gt;must&lt;/em&gt; be in the answer? (e.g., "Give me a list, not a paragraph.")&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are vague, the AI will make assumptions for you—and they are usually boring ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Don't Over-Explain (The Brevity Rule)
&lt;/h2&gt;

&lt;p&gt;Beginners often think that longer prompts are smarter. They write 300 words of instructions before getting to the point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't do this.&lt;/strong&gt; Over-explaining confuses the model. It forgets the beginning of your sentence by the time it gets to the end. Be clear, be direct, and use constraints.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bad:&lt;/strong&gt; "I want you to write a story that is kinda sad but also happy and maybe involves a cat but don't make it too long..."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Good:&lt;/strong&gt; "Write a 200-word story about a cat. Tone: Bittersweet. Ending: Hopeful."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. The "Yes-Man" Problem
&lt;/h2&gt;

&lt;p&gt;There is a hidden quirk in these models you need to know about: &lt;strong&gt;They are instructed to be nice.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The creators of these models train them to be helpful, harmless, and friendly. While that sounds good, it often means the AI becomes a "Yes-Man." It acts like that overly polite friend who tells you your bad haircut looks great because they don't want to hurt your feelings.&lt;/p&gt;

&lt;p&gt;If you have a fundamentally flawed idea, the AI might try to "sugar-coat" it or find a way to make it work, rather than telling you, "No, that's impossible."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; explicitly tell the AI to take off the kid gloves.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Please critique my idea brutally. Don't sugar-coat it. Tell me exactly why this might fail."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  8. How to Fix a Bad Answer
&lt;/h2&gt;

&lt;p&gt;Sometimes, the AI just gets it wrong. It hallucinates, it rambles, or it misses the point. Don't start over from scratch—just steer it back.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Clarify:&lt;/strong&gt; "You misunderstood. I didn't mean X, I meant Y."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Format Fix:&lt;/strong&gt; "This is too dense. Rewrite it as a bulleted list."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Reset:&lt;/strong&gt; "Ignore the previous instruction. Let's try a different angle."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Reasoning:&lt;/strong&gt; "Explain exactly how you got to that number."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  9. Watch Out for "The Confidence Trap"
&lt;/h2&gt;

&lt;p&gt;Because the AI is designed to predict the next word that &lt;em&gt;sounds&lt;/em&gt; correct, it will sometimes lie to you with 100% confidence.&lt;/p&gt;

&lt;p&gt;If the AI doesn't know the answer, it might make one up because it fits the "pattern" of the sentence. &lt;strong&gt;Always double-check facts&lt;/strong&gt;, especially for math, citations, or medical advice. Treat it like a smart friend who has had a few too many espressos—brilliant, but prone to exaggeration.&lt;/p&gt;




&lt;h3&gt;
  
  
  📝 The 10-Second Cheat Sheet
&lt;/h3&gt;

&lt;p&gt;Save this for your next chat. To get a perfect response, just check these boxes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Give it a Role:&lt;/strong&gt; "Act as a..."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provide Context:&lt;/strong&gt; Paste in drafts, data, or background info.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set Constraints:&lt;/strong&gt; "Under 200 words," "Use a table," "No jargon."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Steer the Ship:&lt;/strong&gt; If it goes off track, correct it immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invite Critique:&lt;/strong&gt; Ask "What am I missing?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify:&lt;/strong&gt; Never trust a fact you didn't check yourself.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You don't need to be an engineer. You just need to be a good conversationalist.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;em&gt;About the Author&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;I’m a senior undergrad student exploring Cybersecurity and AI. I like breaking tech down so it actually makes sense.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How I Vibe Coded a Custom Telegram Downloader (Because Browser Throttling is the Worst)</title>
      <dc:creator>Anay Pandya</dc:creator>
      <pubDate>Sat, 22 Nov 2025 06:59:51 +0000</pubDate>
      <link>https://dev.to/anay_pandya_bfac6bcdbb055/how-i-vibe-coded-a-custom-telegram-downloader-because-browser-throttling-is-the-worst-3alb</link>
      <guid>https://dev.to/anay_pandya_bfac6bcdbb055/how-i-vibe-coded-a-custom-telegram-downloader-because-browser-throttling-is-the-worst-3alb</guid>
      <description>&lt;p&gt;We have all been there. You find a course file, a movie, or a project archive on Telegram that is over 1GB. You start the download via the Web or Desktop client, watch the progress bar pick up speed, and then you make the mistake of switching tabs or walking away to grab a coffee.&lt;/p&gt;

&lt;p&gt;Ten minutes later, you check back. The progress bar hasn’t moved. The speed is 0 B/s. The download is stuck at 99%.&lt;/p&gt;

&lt;p&gt;That gigabyte of data you successfully pulled is now a useless, orphaned file, forcing you to start the transfer from scratch.&lt;/p&gt;

&lt;p&gt;While reading about this, realized the problem isn’t that Telegram is slow. &lt;strong&gt;The problem is that browsers and OSs hate long-running background tasks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So, I created &lt;strong&gt;TeleDM&lt;/strong&gt; (Telegram Download Manager). It solves this fundamental frustration by providing a robust, “fire and forget” way to download files from Telegram using the core protocol, eliminating the need to keep a tab in focus.&lt;/p&gt;

&lt;h1&gt;
  
  
  What is the Problem We’re Addressing?
&lt;/h1&gt;

&lt;p&gt;The core problem TeleDM addresses is the fragility of large file transfers within the standard Telegram ecosystem.&lt;/p&gt;

&lt;p&gt;Telegram is amazing for chat, but when you use it to download files exceeding 2GB, you hit a wall. Users consistently report:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Downloads failing silently when the app is &lt;/li&gt;
&lt;li&gt;The “Start From Zero” bug: If a download is interrupted, the official Desktop client often fails to resume and restarts from 0%.&lt;/li&gt;
&lt;li&gt;Link Expiry: Browser-based downloads rely on signed URLs that expire after an hour. If your internet is slow and the download takes 70 minutes, it will fail at the 60-minute mark.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  The Technical Reality: Why Downloads Fail
&lt;/h1&gt;

&lt;p&gt;It’s not just “bad Wi-Fi.” It is a software friction problem.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Browser/OS Sabotage&lt;/strong&gt;: Modern operating systems (iOS, Android, Windows) and browsers (Chrome, Edge) are obsessed with battery life and RAM. If a tab is in the background, the OS aggressively throttles its network access. A 2GB download requires a sustained, active connection. The moment you multitask, the OS sees the background process as “unnecessary” and kills the socket.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Bot API Limit&lt;/strong&gt;: Most developers try to solve this by building a simple Telegram Bot. But the standard Bot API has a hard limit: it cannot download files larger than 20MB. It is useless for the files that actually cause problems (movies, datasets, archives).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  How TeleDM Solves It: The Architecture
&lt;/h1&gt;

&lt;p&gt;TeleDM fixes these issues by abandoning the “Chat Client” approach and adopting a “Download Manager” architecture.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Bypassing the Bot Limit (MTProto)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;TeleDM uses the native &lt;strong&gt;Telegram Client API (MTProto)&lt;/strong&gt; via the &lt;a href="https://docs.telethon.dev/en/stable/" rel="noopener noreferrer"&gt;&lt;strong&gt;Telethon&lt;/strong&gt;&lt;/a&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It does not act like a &lt;/li&gt;
&lt;li&gt;It authenticates as &lt;strong&gt;you&lt;/strong&gt; (the user).&lt;/li&gt;
&lt;li&gt;This removes the file size limit, allowing downloads of up to 2GB (or 4GB for Premium users) directly.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The Headless Advantage&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;TeleDM runs as a system process, not a browser tab. It will happily utilize 100% of your bandwidth to download a file while you play a game, lock your screen, or switch workspaces. The OS does not throttle it because it doesn’t rely on UI focus to stay alive.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Robust Queue Management&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Instead of trying to swallow the file in one go, TeleDM manages downloads in a persistent queue.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Persistence: If your internet cuts out, TeleDM keeps track of the download state in a local database.&lt;/li&gt;
&lt;li&gt;Automatic Retries: If a download fails, the manager automatically queues it for retry, ensuring temporary network blips don’t kill your download.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Current Features (v1.0.0)
&lt;/h1&gt;

&lt;p&gt;As of today, TeleDM is a functional application designed to do one thing well: Download reliably.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;MTProto Integration&lt;/strong&gt;: Direct, high-speed connection to Telegram’s Datacenters.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;GUI Interface&lt;/strong&gt;: A clean, modern graphical interface for managing downloads.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;CLI Interface&lt;/strong&gt;: Simple command-line usage for pasting links and starting downloads.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Large File Support&lt;/strong&gt;: Successfully tested on files &amp;gt;2GB.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Roadmap: What’s Coming Next?
&lt;/h1&gt;

&lt;p&gt;We are actively working to turn this from a script into a full-fledged application. Here is what is in the pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Resumable Downloads&lt;/strong&gt;: Checks existing file size on disk before starting; resumes automatically if a partial file is found.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Threading&lt;/strong&gt;: optimizing chunk requests to maximize bandwidth saturation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker Support&lt;/strong&gt;: A containerized version to run TeleDM on your NAS or Home Server easily.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task Scheduling&lt;/strong&gt;: Schedule heavy downloads for 3 AM when data is cheap or speed is fast.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Contributions Are Most Welcome!
&lt;/h1&gt;

&lt;p&gt;This project was born out of personal frustration, but I know I’m not the only one facing this. TeleDM is open-source, and we need help to make it the ultimate Telegram utility.&lt;/p&gt;

&lt;p&gt;Whether you are a Python pro, a UI designer, or just someone who wants to test it and break it — your contributions are appreciated.&lt;/p&gt;

&lt;p&gt;Check out the repo, star it, and open a PR: 👉 &lt;a href="//github.com/ADPer0705/TeleDM"&gt;github.com/ADPer0705/TeleDM&lt;/a&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>buildinpublic</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
