Wargame narnia writeup
Preface
This phase of wargame is much more difficult than leviathan, and involves a relatively complete knowledge of Linux overflow. However, on overthewire, this is only 2/10 difficult. It seems that I am far away.
On the narnia homepage, follow the prompts below to log on to the target machine using the initial account and password. All the files about this wargame are under the/narnia folder.
To login to the first level use:Username: narnia0Passowrd: narnia0Data for the levels can be found in /narnia/.
Level 0
From the folder of the target machine, we can see that each level gives the source code and compiled executable files, and each executable file has the set-uid permission. If the executable file overflows to get the shell of the next level, you can get the password of the next level under the/etc/narnia_pass/folder.
First, execute the narnia0 file normally to see if there is any prompt.
From the execution result, it should be an overflow buffer, and then modify another automatic variable in the stack, so as to come to the subsequent logic judgment. The following source code file also shows that my guess is correct.
#include <stdio.h>#include <stdlib.h> int main(){ long val=0x41414141; char buf[20]; printf("Correct val's value from 0x41414141 -> 0xdeadbeef!\n"); printf("Here is your chance: "); scanf("%24s",&buf); printf("buf: %s\n",buf); printf("val: 0x%08x\n",val); if(val==0xdeadbeef) system("/bin/sh"); else { printf("WAY OFF!!!!\n"); exit(1); } return 0;}
We can see that the input buffer buf and the variables to be overflows are in the stack frame of the main function. This is very simple, as long as the input is correctly constructed. At the beginning, I constructed such an input:
Obviously, the val value is modified due to overflow, but the desired shell is not obtained. Later, it is automatically closed after the pipeline is output to the program, the shell returned by the program cannot be opened. Modify shellcode as follows and find that the password is correct.
Level 1
To execute the executable program, you can get a very useful prompt as follows.
It seems that you only need to put shellcode in the correct environment variable. From the program code, we can see that the address of the Environment Variable EGG is called as a function pointer.
#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;}
Construct the following environment variable with shellcode to correctly overflow. For Address Calculation of environment variables, many overflow books will mention that the desired method can be obtained by google over a hundred degrees.
Level 2
From the execution results of the program, it seems that an input must be used as a parameter of the main function. It is estimated that this input is used as an overflow point.
#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 also a basic overflow question. You can override the return address of the main function correctly. Shows the solution. Here, I put shellcode in the EGG environment variable, so we only need to use the EGG address to overwrite the return address of the main function.
Level 3
Starting from this question, the difficulty gradually increases, but it is still under control.
From the execution results of the program, it seems that a file is passed as a parameter to the program, and then the program is opened and output to the/dev/null device.
#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);}
The magic thing is that the character array char ofile [16] = "/dev/null"; can be initialized like this, I remember this was not the case in Miss Tan's textbooks ....
From the source code, the previous guess is correct. In my first thought, the/dev/null device was redirected to a file. In this way, the stored file of the password is passed as a parameter to the program, the program outputs the content in the password file to the target file of my redirection. After google for half a day, no valid redirection method was found. Another way is to modify the output file path so that the result can be output to a file. I constructed a file in the path/tmp/narnia3/AAAAAAAAAAAAAAAAA/tmp/pass, which is soft-linked to the password file. At the same time, the string is input to the program as a parameter, and the/tmp/pass molecular string overflows into the storage buffer of the output file path. In this way, the input is a soft link to the password file, the output is a file such as/tmp/pass. As shown in the specific operation, because the program to be overflows has the set-uid permission, the valid user is the next level. Note the folder access permission under/tmp.
Level 4
There is no output in the execution of this program. It seems that only the source code can be analyzed to find the overflow.
#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;}
From the code point of view, the program clears all the environment variables, so that the method for storing shellcode in the environment variables is unavailable. However, the program copies the input main function parameters to the buffer without restrictions, this gives us a buffer overflow vulnerability, a very basic buffer overflow problem, as long as the shellcode is put into the stack, and then correctly overwrite the return address of the function. When we guess the shellcode address, it may be because of the stack offset, which may lead to the offset between the stack address in gdb and the actual address in the shell, but we can add some NOP Sled. Shows the result.
Level 5
From the perspective of program execution, this function also modifies the value of a variable through overflow, but from the perspective of source code, it is not a simple overflow that can be modified.
#include <stdio.h>#include <stdlib.h>#include <string.h> int main(int argc, char **argv){ int i = 1; char buffer[64]; snprintf(buffer, sizeof buffer, argv[1]); buffer[sizeof (buffer) - 1] = 0; printf("Change i's value from 1 -> 500. "); if(i==500){ printf("GOOD\n"); system("/bin/sh"); } printf("No way...let me give you a hint!\n"); printf("buffer : [%s] (%d)\n", buffer, strlen(buffer)); printf ("i = %d (%p)\n", i, &i); return 0;}
From the Perspective, variable I and buffer are adjacent to each other in the stack. However, a secure snprintf () function is used when the buffer is input, which prevents overflow to overwrite the value of variable I, however, when snprintf () is called, The formatted string is input by the user as the main () function. We can control this formatted string, leading to the formatting String Vulnerability.
This vulnerability allows you to read and write arbitrary address values. Therefore, you can modify the value of variable I by constructing a suitable formatted string, get a shell with high permissions. See.
Level 6
This level requires two inputs as parameters of the main () function. It is obvious to guess how to construct a suitable overflow string.
#include <stdio.h>#include <stdlib.h>#include <string.h> extern char **environ; // tired of fixing values...// - morlaunsigned long get_sp(void) { __asm__("movl %esp,%eax\n\t" "and $0xff000000, %eax" );} int main(int argc, char *argv[]){ char b1[8], b2[8]; int (*fp)(char *)=(int(*)(char *))&puts, i; if(argc!=3){ printf("%s b1 b2\n", argv[0]); exit(-1); } /* clear environ */ for(i=0; environ[i] != NULL; i++) memset(environ[i], '\0', strlen(environ[i])); /* clear argz */ for(i=3; argv[i] != NULL; i++) memset(argv[i], '\0', strlen(argv[i])); strcpy(b1,argv[1]); strcpy(b2,argv[2]); //if(((unsigned long)fp & 0xff000000) == 0xff000000) if(((unsigned long)fp & 0xff000000) == get_sp()) exit(-1); fp(b1); exit(1);
From the source code, we can see that the environment variables and redundant main () function parameters are cleared, and shellcode cannot be placed in them. Buffer b1 and b2 are close to each other in the stack, followed by a function pointer pointing to the puts () function, so we have the idea of overwriting this function pointer. The function pointer uses b1 as the parameter to call the function. Therefore, the idea is to overwrite the fp value with the system () address, and then fill the/bin/sh string in the buffer b1, in this way, the system ("/bin/sh") function will be called at the end of the program to obtain a high-level shell. In the program, first strcpy (b1) and then strcpy (b2). We need to consider the length of the string when constructing a string with the/bin/sh substring, enables the string to end normally. In this way, first use the buffer b1 overflow to overwrite the fp value, use the system () Address to overwrite the value, and then use the buffer b2 overflow to add a substring such as/bin/sh to b1, the length of B2. The actual operation is as follows.
Level 7
Simple program output does not provide much valid information, but it can still be seen that there is input, there may be a buffer overflow problem.
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <stdlib.h>#include <unistd.h> int goodfunction();int hackedfunction(); int vuln(const char *format){ char buffer[128]; int (*ptrf)(); memset(buffer, 0, sizeof(buffer)); printf("goodfunction() = %p\n", goodfunction); printf("hackedfunction() = %p\n\n", hackedfunction); ptrf = goodfunction; printf("before : ptrf() = %p (%p)\n", ptrf, &ptrf); printf("I guess you want to come to the hackedfunction...\n"); sleep(2); ptrf = goodfunction; snprintf(buffer, sizeof buffer, format); return ptrf();}int main(int argc, char **argv){ if (argc <= 1){ fprintf(stderr, "Usage: %s <buffer>\n", argv[0]); exit(-1); } exit(vuln(argv[1]));}int goodfunction(){ printf("Welcome to the goodfunction, but i said the Hackedfunction..\n"); fflush(stdout); return 0;}int hackedfunction(){ printf("Way to go!!!!"); fflush(stdout); system("/bin/sh"); return 0;}
This code is a bit long, but the idea is quite clear. In the vuln () function, there is our input, and a function pointer is adjacent to the buffer, using a safe snprintf () function to copy our input to the buffer. The formatting string vulnerability still exists. However, the difficulty of this question is that the input formatting parameters are not printed, so we cannot adjust the input formatting parameters according to the output, and due to the stack offset problem, as a result, the address in gdb differs greatly from the address actually executed in shell, which is basically not usable. The program prints enough address information. We know the modified value and address to be modified. Therefore, I constructed a formatted string \ x0c \ xd5 \ xff. % 134514432d ., then add the written formatting parameter % n after the string, and try to guess in sequence. If you have guessed the sixth one, you will get the shell.
I originally wrote a Python script and tried to crack it. However, the technology is too bad. As a result, the script execution result is not very good, but it was manually cracked. In fact, there is a high risk of speculation here, because if the storage address of the string is not 4-byte aligned, we need to adjust the offset for the address stored in the string, but because there is no output, as a result, you cannot know whether the offset exists or not. Fortunately, the question is not too difficult to design.
Level 8
Since program execution cannot provide too much valid information, let's look at the Code directly.
#include <stdio.h>#include <stdlib.h>#include <string.h>// gcc's variable reordering fucked things up// to keep the level in its old style i am // making "i" global unti i find a fix // -morla int i; void func(char *b){ char *blah=b; char bok[20]; //int i=0; memset(bok, '\0', sizeof(bok)); for(i=0; blah[i] != '\0'; i++) bok[i]=blah[i]; printf("%s\n",bok);} int main(int argc, char **argv){ if(argc > 1) func(argv[1]); else printf("%s argument\n", argv[0]); return 0;}
It seems very simple, but a very simple buffer overflow, but in actual operations, we found that the variable blah and the buffer bok are adjacent in the stack, resulting in if the input string is too long, it will overwrite the blah variable, which is the base address pointer of our input string. If it is modified, it will not be able to access the input string correctly.
From the execution result, if the input is too long, the subsequent strings are not copied to the buffer, so that the return address of the function cannot be overwritten and overflow fails. So I changed my mind. Since the input string is too long and will modify the original base address value, I will overwrite it with the original base address value, which is equivalent to no modification. You only need to guess the original base address value. From the program, we can see that after the buffer is copied, there is no correct Terminator, which gives us the possibility to print the original value of the blah variable.
The buffer length is 20, so the string with the input length of 20 can overwrite the buffer, and there is no correct Terminator, you can see the value of the blah variable. Here is 0xffffd7c7. According to the test, each increase of the input string length by 1 reduces the base address value by 1. After calculation, the correct base address value is obtained. The procedure is shown in.
End of the game
Finally, this wargame was completed, and it was only 2/10 difficult to think of this phase of wargame. I knew there would be more and more interesting things in the future. After all, there are no buffer overflow protection policies, such as ASLR and stack canary, which will be available in subsequent games. Please wait ~