<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/tips/5535

            前言


            這一期的wargame難度明顯比之前的leviathan要高,而且已經涉及到相對完善的Linux溢出相關知識了。但是在overthewire上這才居然只是2/10的難度,看來我差得很遠啊。

            narnia首頁,有如下提示,使用初始賬號和密碼登陸到目標機器,關于本wargame的所有文件都在/narnia文件夾下面。Let's go

            To login to the first level use:
            
            Username: narnia0
            Passowrd: narnia0
            Data for the levels can be found in /narnia/.
            

            level 0


            narnia_level0_login.png

            從目標機器的文件夾中我們可以看到,每個 level 都給了源代碼和編譯后的可執行文件,每個可執行文件都有set-uid權限。只要溢出該可執行文件,得到下一個 level 的 shell,就可以在/etc/narnia_pass/文件夾下面得到下一個 level 的密碼了。

            首先正常執行一下narnia0這個文件,看看有什么提示沒有。

            narnia_level0_run.png

            從執行結果來看,應該是溢出緩沖區,然后修改棧中的另外一個自動變量,以此來過后面的邏輯判斷。從下面的源代碼文件也可以看到,我的猜想是正確的。

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

            可以看到,輸入緩沖區buf,和待溢出變量都在main函數的棧幀中,這題很簡單,只要正確構造輸入就可以了。一開始,我只是構造了這樣的一個輸入:

            narnia_level0_cmd1

            很明顯,明明正確溢出修改了val的值,但是沒有得到想要的 shell,后來,發現原來是管道輸出給了程序之后,就會自動關閉了,造成程序返回的 shell 無法打開。于是,修改shellcode如下,發現正確得到了密碼。

            narnia_level0_crack

            level 1


            執行可執行程序,可以得到一個很有用的提示如下。

            narnia_level1_login

            好像只要把shellcode放到正確的環境變量中就可以了,從程序代碼中,看到環境變量EGG的地址被作為函數指針調用了。

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

            構造如下的帶有shellcode的環境變量就可以正確溢出了。關于環境變量的地址計算,很多溢出類書籍都會提到,上百度google一下就可以得到想要的方法。

            narnia_level1_crack

            level 2


            narnia_level2_login

            從程序執行結果來看,似乎需要給一個輸入作為main函數的參數,估計是通過這個輸入來做為溢出點。

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

            這也是一個很基礎的溢出題目,正確的覆蓋main函數的返回地址就可以了。解法如下圖所示。我在這里,將shellcode放在了EGG這個環境變量中,所以只要使用EGG的地址覆蓋main函數的返回地址就可以了。

            narnia_level2_crack

            level 3


            從這道題目開始,難度開始慢慢加大了,不過依然都在控制之內。

            narnia_level3_login

            從程序執行結果來看,似乎將某個文件作為參數傳給程序,然后程序打開輸出到/dev/null這個設備中去。

            #!c
            #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);
            }
            

            很神奇的事情,字符數組char ofile[16] = "/dev/null";居然可以這樣初始化,我記得當年的譚老師的課本里不是這樣寫的啊。。。。

            從源代碼來看,之前的猜測是對的。我一開始的想法是,重定向/dev/null設備到某個文件,這樣,將密碼的存儲文件作為參數傳給程序,程序將密碼文件中的內容輸出到我重定向的目標文件中,就可以正確得到了。后來google了半天,沒有找到有效的重定向的方法。另辟蹊徑,我想到可以溢出緩沖區的內容,修改輸出文件路徑,這樣就可以將結果輸出到某個文件中去了。我構造了/tmp/narnia3/AAAAAAAAAAAAAAAAAAA/tmp/pass這個路徑下的一個文件,該文件被軟鏈接到密碼文件。同時,該字符串被作為參數輸入給了程序,/tmp/pass這部分子串被溢出到輸出文件路徑的存儲緩沖區中,這樣輸入是密碼文件的一個軟鏈接,輸出是/tmp/pass這樣的一個文件。具體操作如下圖所示,因為待溢出程序具有set-uid權限,所以執行是的有效用戶是下一個 level,注意/tmp下文件夾的訪問權限問題。

            narnia_level3_crack

            level 4


            narnia_level4_login

            這一關程序執行居然沒有輸出,看來只能通過分析源代碼來找溢出點了。

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

            從代碼來看,程序清空了所有的環境變量,這樣在環境變量中存放shellcode的方法不可用了,不過,程序將輸入的main函數參數無限制拷貝到了buffer中,這樣就給了我們緩沖區溢出的漏洞,很基礎的一個緩沖區溢出題目,只要將shellcode放入到棧中,然后正確覆蓋函數返回地址就可以了。在猜測shellcode地址的時候,可能是因為棧偏移的問題,導致gdb中的棧地址和shell中運行時的實際地址有所偏移,不過添加一些NOP Sled就可以了。結果如下圖所示。

            narnia_level4_crack

            level 5


            這一關從程序執行來看,也是通過溢出修改某個變量的值,但是從源代碼看,并不是簡單的溢出就可以修改了。

            narnia_level5_login

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

            從來看,變量i和緩沖區buffer在棧中相鄰,但是,緩沖區輸入的時候使用了安全的snprintf()函數,這導致不能通過溢出來覆蓋變量i的值,但是snprintf()在調用的時候的格式化字符串是由用戶作為main()函數的輸入,我們可以控制這個格式化字符串,導致了格式化字符串漏洞。

            narnia_level5_check_vul

            驗證的確有格式化字符串漏洞,利用這個漏洞,可以讀寫任意地址的值,所以我們構造一個合適的格式化字符串,就可以修改變量i的值,得到一個高權限的shell。具體操作如下圖所示。

            narnia_level5_crack

            level 6


            這一關需要兩個輸入作為main()函數的參數,猜測應該有很明顯的溢出點,難度就在于如何構造合適的溢出字符串。

            narnia_level6_login

            #!c
            #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);
                    fp(b1);
                    exit(1);
            

            從源代碼中可以看到,環境變量和多余的main()函數參數都被清空了,導致無法在其中安放shellcode。緩沖區b1b2在棧中緊鄰,接下來是一個指向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的長度需要考慮。實際操作如下。

            narnia_level6_crack

            level 7


            簡單的程序輸出已經提供不了太多的有效信息了,但是還是可以看到有輸入,就有可能有緩沖區溢出問題。

            narnia_level7_login

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

            這個代碼有點點長,不過思路還是很清楚的,在vuln()函數中,有我們的輸入,有一個函數指針緊鄰著緩沖區,使用了安全的snprintf()函數來復制我們的輸入到緩沖區中,依然有格式化字符串漏洞。不過這題的難度在于,我們輸入的格式化參數沒有打印出來結果,導致我們無法根據輸出來調整輸入的格式化參數,而且由于棧偏移的問題,導致gdb中的地址和shell中實際執行的地址差距很大,基本上不能利用。好字啊程序打印出來了足夠的地址信息,我們知道修改后的值和待修改的地址。于是,我先構造了含有目的地址和目標長度的格式化字符串\x0c\xd5\xff\xff.%134514432d.,然后在該字符串后面添加寫入的格式化參數%n,依次嘗試猜測,運氣不錯,猜到了第六個就得到了shell

            本來是寫了一個Python腳本來嘗試爆破的,但是技術太爛,導致腳本執行的結果不太好,還是人工爆破來做的。其實這里猜測的風險很大,因為如果字符串的存儲地址不是四字節對齊的話,這樣我們在字符串中存放的地址就需要調整偏移,但是因為沒有輸出,導致無法知道這個偏移到底存在與否。好在題目設計得不是太難。

            narnia_level7_crack

            level 8


            既然程序執行已經無法提供太多有效的信息了,還是直接看代碼吧。

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

            看起來似乎很簡單,只是一個很簡單的緩沖區溢出,但是實際操作的時候發現,變量blah和緩沖區bok在棧中是相鄰的,導致如果輸入的字符串太長的話,就會覆蓋blah這個變量,這個變量又是我們的輸入字符串的基地址指針,如果被修改了,就無法正確訪問我們輸入的字符串。

            narnia_level8_run_check

            從執行結果來看,輸入太長的字符串,都會導致后面的字符串沒有復制到緩沖區中,這樣也就無法覆蓋函數的返回地址,溢出失敗。于是改變了思路,既然太長的輸入字符串會修改原來的基地址值,那么就用原來的基地址值再覆蓋回去,這樣就相當于沒有修改了。只需要猜測原來的基地址值就可以了。從程序中可以看到,緩沖區被復制之后,沒有正確的結束符,這樣給了我們打印blah變量原來的值的可能。

            narnia_level8_run_check2

            緩沖區長度是20,所以輸入長度為20的字符串正好可以覆蓋緩沖區,同時又沒有正確的結束符,就可以看到blah變量的值了。在這里是0xffffd7c7,根據測試,我們輸入的字符串長度每增加1,這個基地址值就會減少1,通過計算,就可以得到正確的基地址值了。具體操作過程如下圖所示。

            narnia_level8_crack

            end of the game


            終于結束了這次的wargame,想到這一期的wargame難度只有2/10,我就知道后面還會有更多更好玩的東西。畢竟這里還有沒涉及到ASLRstack canary等緩沖區溢出保護策略,不過這些在后面的游戲都會有的。盡請期待~

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线