<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title>Articles tagged linux at The Segfault Garden</title>
  <link rel="alternate" type="text/html"
        href="https://blog.segv.page/tags/linux/"/>
  <link rel="self" type="application/atom+xml"
        href="https://blog.segv.page/tags/linux/feed/"/>
  <updated>2026-06-05T17:18:02Z</updated>
  <id>urn:uuid:9d7de7b3-b357-464b-a963-da196bdd5954</id>

  <author>
    <name>Luna</name>
    <uri>https://blog.segv.page</uri>
    <email>frgmntedflower@linux.com</email>
  </author>

  
    
  <entry>
    <title>Ignite — TryHackMe Writeup</title>
    <link rel="alternate" type="text/html" href="https://blog.segv.page/blog/2026/06/02/thm-ignite-room-writeup/"/>
    <id>urn:uuid:2b4c9d7e-8f6a-4e31-90b2-d1c5b7e3f60b</id>
    <updated>2026-06-02T14:00:00Z</updated>
    <category term="exploitation"/><category term="linux"/><category term="security"/>
    <content type="html">
      <![CDATA[<p>A walkthrough of the Ignite TryHackMe room — an easy Linux box running an outdated Fuel CMS with a public RCE, escalated via the PwnKit (CVE-2021-4034) pkexec vulnerability.</p>

<h2 id="enumeration">Enumeration</h2>

<p>I first started with enumeration, since we are already given the target IP when doing TryHackMe boxes there is no need to perform much host discovery. How I usually start is by scanning the target using NMAP, a simple fast scan using the <code class="language-plaintext highlighter-rouge">-F</code> flag is enough here to see that there is a web server running on port 80 HTTP. Sometimes I’d perform a scan over all the ports just to make sure using <code class="language-plaintext highlighter-rouge">nmap -T4 -Pn -sV -sC -O -p- IP</code> but this time since it’s an easy box and the description already hinted a faulty web server configuration I opted to just trust my gut and use the fast scan.</p>

<p>After I took a look at the index page the server was hosting, I immediately spotted that a CMS called “Fuel CMS” is installed. Not only that, it seems like it was still in the installation phase, being able to see the setup screen and everything. I also found out that they were using <code class="language-plaintext highlighter-rouge">version 1.4</code> which is pretty old. Before looking up if there are any exploits, I dug deeper into the system by logging in with the default credentials which are comfortably <code class="language-plaintext highlighter-rouge">admin/admin</code>.</p>

<h2 id="exploitation">Exploitation</h2>

<p>I was sent to the management dashboard of the CMS and found a bunch of menus to look at. I found a page to upload assets and instantly thought about uploading some kind of reverse shell to get access to the server. After some attempting I resulted to looking up any exploits that might be applicable for this version of the CMS.</p>

<p>Indeed I found one — <code class="language-plaintext highlighter-rouge">CVE-2018-16763</code> — that allows unauthorized Remote Code Execution, bingo. I started up my netcat listener and ran the python script to invoke a reverse shell. It hit.</p>

<p>I was in, got a reverse shell onto the server and I was logged in as <code class="language-plaintext highlighter-rouge">www-data</code> on an Ubuntu machine. I knew from here I won’t have any root privileges and the task was to find the user and the root flag on the machine. First one was pretty easy, I found the user flag in the home directory of my user.</p>

<h2 id="privilege-escalation">Privilege Escalation</h2>

<p>The second one I knew I had to do some privilege escalation, since it’s a root flag I was prepared to do some horizontal pivoting. I first took a look at the crontab and the <code class="language-plaintext highlighter-rouge">SUID</code> binaries, seeing if anything is misconfigured or there is anything interesting that has the <code class="language-plaintext highlighter-rouge">SUID</code> bit set and is running as root.</p>

<p>After some looking around I didn’t find any interesting <code class="language-plaintext highlighter-rouge">SUID</code> binaries but after running the <code class="language-plaintext highlighter-rouge">LinEnum</code> script I always keep up my sleeve I found out that the <code class="language-plaintext highlighter-rouge">pkexec</code> binary is pretty old, so old in fact that it predates <code class="language-plaintext highlighter-rouge">version 0.117</code> meaning, we just found our way in.</p>

<p>After looking up some <code class="language-plaintext highlighter-rouge">pkexec</code> exploits, I found <code class="language-plaintext highlighter-rouge">CVE-2021-4034</code> aka <code class="language-plaintext highlighter-rouge">PwnKit</code>, a classic privilege escalation. It roughly works by abusing the way arguments are stored in C. Effectively there is a way to just not pass any arguments to <code class="language-plaintext highlighter-rouge">pkexec</code> so <code class="language-plaintext highlighter-rouge">argv</code> is empty, the program then skips proper security checks. <code class="language-plaintext highlighter-rouge">pkexec</code> tries to recover by reading from environment variables like <code class="language-plaintext highlighter-rouge">GCONV_PATH</code> to find error handling paths. By leaving <code class="language-plaintext highlighter-rouge">argv</code> empty, so not providing arguments, and manipulating the aforementioned environment variables we can force <code class="language-plaintext highlighter-rouge">pkexec</code> to execute any code we want, but with root privileges!</p>

<p>Another problem I had was: how do I get the exploit onto the machine? <code class="language-plaintext highlighter-rouge">curl</code> wasn’t installed and <code class="language-plaintext highlighter-rouge">wget</code> also didn’t work to just pull it. I ended up copy-pasting the C code and Makefile onto the target machine and then compiling it myself using <code class="language-plaintext highlighter-rouge">gcc</code>. Since we had missing components on the system, it wasn’t able to compile for 32-bit targets but since our machine was a 64-bit target it didn’t really matter. I stripped the 32-bit targets from the Makefile using <code class="language-plaintext highlighter-rouge">sed</code>. Compiled and executed the binary and boom, we’re in.</p>

<p>Now that we got a root shell, it was just as simple as going into the root home directory and grabbing that sweet <code class="language-plaintext highlighter-rouge">root.txt</code> flag.</p>

<h2 id="remediation">Remediation</h2>

<p>To counteract such an attack it’s crucial to keep the system and its components up to date. Patching polkit to <code class="language-plaintext highlighter-rouge">&gt;= 0.117</code> and removing any unnecessary <code class="language-plaintext highlighter-rouge">SUID</code> bits will prevent attacks like this from happening. And of course don’t just let any old CMS lie around open and unconfigured but who would do that, right?</p>
]]>
    </content>
  </entry>
    
  
    
  <entry>
    <title>wad: Building an ELF Packer — Coming Soon</title>
    <link rel="alternate" type="text/html" href="https://blog.segv.page/blog/2026/05/15/wad-elf-packer/"/>
    <id>urn:uuid:2b4c9d7e-8f6a-4e31-90b2-d1c5a7e3f60b</id>
    <updated>2026-05-15T14:00:00Z</updated>
    <category term="c"/><category term="assembly"/><category term="linux"/><category term="security"/>
    <content type="html">
      <![CDATA[<p>Working on a deep dive into building <code class="language-plaintext highlighter-rouge">wad</code> — an ELF packer from scratch.</p>

<p>The write-up will cover ELF segment parsing, position-independent assembly
stubs, embedded encryption, and the whole pack/unpack flow. No libc, no
dependencies, no nonsense.</p>

<p>Coming once the stubs are solid and I’ve shaken out the edge cases.</p>
]]>
    </content>
  </entry>
    
  
    
  
    
  <entry>
    <title>peachykeen32: A Toy ARM32 Shell in Assembly</title>
    <link rel="alternate" type="text/html" href="https://blog.segv.page/blog/2025/06/20/peachykeen32-bare-metal-ARM-userspace-programming/"/>
    <id>urn:uuid:1a9b8c3d-5e7f-4a62-b0d4-6c8e1f2a9d70</id>
    <updated>2025-06-20T16:15:00Z</updated>
    <category term="assembly"/><category term="arm"/><category term="linux"/>
    <content type="html">
      <![CDATA[<p>peachykeen32 is a toy shell for ARM32 Linux written entirely in hand-written ARM assembly. No libc, no CRT, no linker scripts. Everything — input parsing, string comparison, I/O — goes through direct Linux syscalls via <code class="language-plaintext highlighter-rouge">svc #0</code>. It’s about 400 lines of <code class="language-plaintext highlighter-rouge">.s</code> and runs under QEMU or on bare-metal ARM hardware.</p>

<h2 id="the-entry-point">The Entry Point</h2>

<p>There is no <code class="language-plaintext highlighter-rouge">_start</code> from a C runtime. The binary’s entry point is a minimal trampoline that sets up the stack pointer and jumps to the main loop:</p>

<pre><code class="language-asm">.globl _start
_start:
    ldr sp, =stack_top
    bl main_loop
    mov r7, #1
    svc #0
</code></pre>

<p>The stack is allocated in <code class="language-plaintext highlighter-rouge">.bss</code> — 4kB, zeroed at load time by the kernel. The <code class="language-plaintext highlighter-rouge">svc #0</code> after <code class="language-plaintext highlighter-rouge">main_loop</code> returns is the <code class="language-plaintext highlighter-rouge">exit</code> syscall; if the loop ever falls through, the process terminates cleanly.</p>

<h2 id="the-repl-loop">The REPL Loop</h2>

<p>The shell presents a <code class="language-plaintext highlighter-rouge">peachykeen32&gt;</code> prompt, reads a line from stdin, and parses it into up to three null-terminated argument buffers. Command dispatch is a series of <code class="language-plaintext highlighter-rouge">better_strcmp</code> calls against known command strings.</p>

<pre><code class="language-asm">check_cmd:
    ldr r0, =cmd_buf
    ldr r1, =str_exit
    bl better_strcmp
    cmp r0, #1
    beq exit_handle
    ldr r0, =cmd_buf
    ldr r1, =str_help
    bl better_strcmp
    cmp r0, #1
    beq help_handle
    ...
</code></pre>

<p><code class="language-plaintext highlighter-rouge">better_strcmp</code> compares two null-terminated strings byte by byte and returns 1 on match, 0 otherwise. It’s not optimised — it doesn’t need to be. The input is at most a few dozen bytes per line.</p>

<h2 id="commands">Commands</h2>

<table>
  <thead>
    <tr>
      <th>Command</th>
      <th>Implementation</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">help</code></td>
      <td>Writes a help string to stdout via <code class="language-plaintext highlighter-rouge">sys_write</code>. The string is in <code class="language-plaintext highlighter-rouge">.rodata</code>.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">exit</code></td>
      <td>Calls <code class="language-plaintext highlighter-rouge">sys_exit(0)</code> — <code class="language-plaintext highlighter-rouge">mov r7, #1; svc #0</code>.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">rand</code></td>
      <td>Opens <code class="language-plaintext highlighter-rouge">/dev/urandom</code> via <code class="language-plaintext highlighter-rouge">sys_openat</code> (AT_FDCWD, 322), reads 16 bytes, prints them as hex digits.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">fwrite</code></td>
      <td>Placeholder — parses args, prints the command name. File write is WIP.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ndir</code></td>
      <td>Placeholder — parses args, <code class="language-plaintext highlighter-rouge">mkdir</code> syscall not yet wired up.</td>
    </tr>
  </tbody>
</table>

<p>The <code class="language-plaintext highlighter-rouge">rand</code> command is the most interesting because it touches three different syscall interfaces: <code class="language-plaintext highlighter-rouge">openat</code> (to get a file descriptor), <code class="language-plaintext highlighter-rouge">read</code> (to get entropy), and <code class="language-plaintext highlighter-rouge">write</code> (to display it). The syscall numbers are defined as equates at the top of the file.</p>

<pre><code class="language-asm">.equ sys_read,   3
.equ sys_write,  4
.equ sys_openat, 322
</code></pre>

<h2 id="why-assembly">Why Assembly</h2>

<p>There is no practical reason to write a shell in assembly. The point is to understand exactly what happens between <code class="language-plaintext highlighter-rouge">svc #0</code> and the kernel’s return. Every instruction is visible, every syscall is explicit, and there is no magic — no <code class="language-plaintext highlighter-rouge">printf</code> buffering, no <code class="language-plaintext highlighter-rouge">errno</code>, no dynamic linker. The entire binary is a static ELF with no <code class="language-plaintext highlighter-rouge">PT_INTERP</code> segment.</p>

<h2 id="building-and-running">Building and Running</h2>

<p>The project includes scripts for cross-compilation from x86:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>arm-linux-gnueabihf-as src/m_peachykeen32.s <span class="nt">-o</span> build/peachykeen32.o
arm-linux-gnueabihf-ld <span class="nt">-Ttext</span><span class="o">=</span>0x10000 <span class="nt">--no-dynamic-linker</span> <span class="se">\</span>
    <span class="nt">-nostdlib</span> build/peachykeen32.o <span class="nt">-o</span> build/peachykeen32
</code></pre></div></div>

<p>Run under QEMU or copy to an ARM board:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>qemu-arm ./build/peachykeen32
</code></pre></div></div>

<p>There are also dependency installers for Arch, Debian, and Gentoo, plus GDB scripts for single-stepping through the syscall interface.</p>

<h2 id="what-i-learned">What I Learned</h2>

<p>The Linux syscall ABI on ARM32 is straightforward — arguments in <code class="language-plaintext highlighter-rouge">r0</code>–<code class="language-plaintext highlighter-rouge">r5</code>, syscall number in <code class="language-plaintext highlighter-rouge">r7</code>, trap with <code class="language-plaintext highlighter-rouge">svc #0</code>. The return value is in <code class="language-plaintext highlighter-rouge">r0</code>. Negative values indicate errors, but without <code class="language-plaintext highlighter-rouge">errno</code> you either check the raw value or ignore it. String parsing in assembly is tedious but mechanical. The hardest part was managing the fixed-size argument buffers — a line longer than 256 bytes silently truncates, which is fine for a toy but would be exploitable in anything real.</p>
]]>
    </content>
  </entry>
    
  
    
  
    
  

</feed>
