阅读:3197回复:0
wargame narnia writeup
前言
这一期的wargame难度明显比之前的leviathan要高,而且已经涉及到相对完善的Linux溢出相关知识了。但是在overthewire上这才居然只是2/10的难度,看来我差得很远啊。 在narnia首页,有如下提示,使用初始账号和密码登陆到目标机器,关于本wargame的所有文件都在/narnia文件夹下面。Let's go To login to the first level use:Username: narnia0Passowrd: narnia0Data for the levels can be found in /narnia/.level 0 图片:2015040903025390689.png 从目标机器的文件夹中我们可以看到,每个 level 都给了源代码和编译后的可执行文件,每个可执行文件都有set-uid权限。只要溢出该可执行文件,得到下一个 level 的 shell,就可以在/etc/narnia_pass/文件夹下面得到下一个 level 的密码了。 首先正常执行一下narnia0这个文件,看看有什么提示没有。 图片:2015040903025390689.png 从执行结果来看,应该是溢出缓冲区,然后修改栈中的另外一个自动变量,以此来过后面的逻辑判断。从下面的源代码文件也可以看到,我的猜想是正确的。 #!c#include #include 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: %sn",buf); printf("val: 0x%08xn",val); if(val==0xdeadbeef) system("/bin/sh"); else { printf("WAY OFF!!!!n"); exit(1); } return 0;}可以看到,输入缓冲区buf,和待溢出变量都在main函数的栈帧中,这题很简单,只要正确构造输入就可以了。一开始,我只是构造了这样的一个输入: 图片:2015040903025390689.png 很明显,明明正确溢出修改了val的值,但是没有得到想要的 shell,后来,发现原来是管道输出给了程序之后,就会自动关闭了,造成程序返回的 shell 无法打开。于是,修改shellcode如下,发现正确得到了密码。 图片:2015040903025390689.png level 1 执行可执行程序,可以得到一个很有用的提示如下。 图片:2015040903025390689.png 好像只要把shellcode放到正确的环境变量中就可以了,从程序代码中,看到环境变量EGG的地址被作为函数指针调用了。 #!c#include int main(){ int (*ret)(); if(getenv("EGG")==NULL){ printf("Give me something to execute at the env-variable EGGn"); exit(1); } printf("Trying to execute EGG!n"); ret = getenv("EGG"); ret(); return 0;}构造如下的带有shellcode的环境变量就可以正确溢出了。关于环境变量的地址计算,很多溢出类书籍都会提到,上百度google一下就可以得到想要的方法。 图片:2015040903025390689.png level 2 图片:2015040903025390689.png 从程序执行结果来看,似乎需要给一个输入作为main函数的参数,估计是通过这个输入来做为溢出点。 #!c#include #include #include int main(int argc, char * argv[]){ char buf[128]; if(argc == 1){ printf("Usage: %s argumentn", argv[0]); exit(1); } strcpy(buf,argv[1]); printf("%s", buf); return 0;}这也是一个很基础的溢出题目,正确的覆盖main函数的返回地址就可以了。解法如下图所示。我在这里,将shellcode放在了EGG这个环境变量中,所以只要使用EGG的地址覆盖main函数的返回地址就可以了。 图片:2015040903025390689.png level 3 从这道题目开始,难度开始慢慢加大了,不过依然都在控制之内。 图片:2015040903025390689.png 从程序执行结果来看,似乎将某个文件作为参数传给程序,然后程序打开输出到/dev/null这个设备中去。 #!c#include #include #include #include #include #include #include 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/nulln",argv[0]); exit(-1); } /* open files */ strcpy(ifile, argv[1]); if((ofd = open(ofile,O_RDWR)) < 0 ){ printf("error opening %sn", ofile); exit(-1); } if((ifd = open(ifile, O_RDONLY)) < 0 ){ printf("error opening %sn", 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);} 很神奇的事情,字符数组char ofile[16] = "/dev/null";居然可以这样初始化,我记得当年的谭老师的课本里不是这样写的啊。。。。从源代码来看,之前的猜测是对的。我一开始的想法是,重定向/dev/null设备到某个文件,这样,将密码的存储文件作为参数传给程序,程序将密码文件中的内容输出到我重定向的目标文件中,就可以正确得到了。后来google了半天,没有找到有效的重定向的方法。另辟蹊径,我想到可以溢出缓冲区的内容,修改输出文件路径,这样就可以将结果输出到某个文件中去了。我构造了/tmp/narnia3/AAAAAAAAAAAAAAAAAAA/tmp/pass这个路径下的一个文件,该文件被软链接到密码文件。同时,该字符串被作为参数输入给了程序,/tmp/pass这部分子串被溢出到输出文件路径的存储缓冲区中,这样输入是密码文件的一个软链接,输出是/tmp/pass这样的一个文件。具体操作如下图所示,因为待溢出程序具有set-uid权限,所以执行是的有效用户是下一个 level,注意/tmp下文件夹的访问权限问题。 图片:2015040903025390689.png level 4 图片:2015040903025390689.png 这一关程序执行居然没有输出,看来只能通过分析源代码来找溢出点了。 #!c#include #include #include #include extern char **environ;int main(int argc,char **argv){ int i; char buffer[256]; for(i = 0; environ != NULL; i++) memset(environ, '', strlen(environ)); if(argc>1) strcpy(buffer,argv[1]); return 0;}从代码来看,程序清空了所有的环境变量,这样在环境变量中存放shellcode的方法不可用了,不过,程序将输入的main函数参数无限制拷贝到了buffer中,这样就给了我们缓冲区溢出的漏洞,很基础的一个缓冲区溢出题目,只要将shellcode放入到栈中,然后正确覆盖函数返回地址就可以了。在猜测shellcode地址的时候,可能是因为栈偏移的问题,导致gdb中的栈地址和shell中运行时的实际地址有所偏移,不过添加一些NOP Sled就可以了。结果如下图所示。 图片:2015040903025390689.png level 5 这一关从程序执行来看,也是通过溢出修改某个变量的值,但是从源代码看,并不是简单的溢出就可以修改了。 图片:2015040903025390689.png #!c#include #include #include 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("GOODn"); 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;}从来看,变量i和缓冲区buffer在栈中相邻,但是,缓冲区输入的时候使用了安全的snprintf()函数,这导致不能通过溢出来覆盖变量i的值,但是snprintf()在调用的时候的格式化字符串是由用户作为main()函数的输入,我们可以控制这个格式化字符串,导致了格式化字符串漏洞。 图片:2015040903025390689.png 验证的确有格式化字符串漏洞,利用这个漏洞,可以读写任意地址的值,所以我们构造一个合适的格式化字符串,就可以修改变量i的值,得到一个高权限的shell。具体操作如下图所示。 图片:2015040903025390689.png level 6 这一关需要两个输入作为main()函数的参数,猜测应该有很明显的溢出点,难度就在于如何构造合适的溢出字符串。 图片:2015040903025390689.png #!c#include #include #include extern char **environ;// tired of fixing values...// - morlaunsigned long get_sp(void) { __asm__("movl %esp,%eaxnt" "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 b2n", argv[0]); exit(-1); } /* clear environ */ for(i=0; environ != NULL; i++) memset(environ, '', strlen(environ)); /* clear argz */ for(i=3; argv != NULL; i++) memset(argv, '', strlen(argv)); 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);从源代码中可以看到,环境变量和多余的main()函数参数都被清空了,导致无法在其中安放shellcode。缓冲区b1和b2在栈中紧邻,接下来是一个指向puts()函数的函数指针,于是有了覆盖这个函数指针的思路。函数指针以b1为参数,进行函数调用,于是思路是用system()的地址覆盖fp的值,然后在缓冲区b1中填充/bin/sh字符串,这样在程序结束的时候就会有system("/bin/sh")这个函数调用,得到一个高一级的shell。在程序中,首先strcpy(b1),然后再strcpy(b2),我们在构造带有/bin/sh这个子串的字符串时需要考虑到字符串的长度问题,使得字符串能够正常结束。这样,先使用缓冲区b1溢出覆盖fp的值,使用system()的地址覆盖该值,然后使用缓冲区b2溢出往b1中添加/bin/sh这样的子串,b2的长度需要考虑。实际操作如下。 图片:2015040903025390689.png level 7 简单的程序输出已经提供不了太多的有效信息了,但是还是可以看到有输入,就有可能有缓冲区溢出问题。 图片:2015040903025390689.png #!c#include #include #include #include #include int goodfunction();int hackedfunction();int vuln(const char *format){ char buffer[128]; int (*ptrf)(); memset(buffer, 0, sizeof(buffer)); printf("goodfunction() = %pn", goodfunction); printf("hackedfunction() = %pnn", 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 |
|
|