這一期的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 都給了源代碼和編譯后的可執行文件,每個可執行文件都有set-uid權限。只要溢出該可執行文件,得到下一個 level 的 shell,就可以在/etc/narnia_pass/文件夾下面得到下一個 level 的密碼了。
首先正常執行一下narnia0這個文件,看看有什么提示沒有。
從執行結果來看,應該是溢出緩沖區,然后修改棧中的另外一個自動變量,以此來過后面的邏輯判斷。從下面的源代碼文件也可以看到,我的猜想是正確的。
#!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
函數的棧幀中,這題很簡單,只要正確構造輸入就可以了。一開始,我只是構造了這樣的一個輸入:
很明顯,明明正確溢出修改了val
的值,但是沒有得到想要的 shell,后來,發現原來是管道輸出給了程序之后,就會自動關閉了,造成程序返回的 shell 無法打開。于是,修改shellcode
如下,發現正確得到了密碼。
執行可執行程序,可以得到一個很有用的提示如下。
好像只要把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
一下就可以得到想要的方法。
從程序執行結果來看,似乎需要給一個輸入作為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
函數的返回地址就可以了。
從這道題目開始,難度開始慢慢加大了,不過依然都在控制之內。
從程序執行結果來看,似乎將某個文件作為參數傳給程序,然后程序打開輸出到/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
下文件夾的訪問權限問題。
這一關程序執行居然沒有輸出,看來只能通過分析源代碼來找溢出點了。
#!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
就可以了。結果如下圖所示。
這一關從程序執行來看,也是通過溢出修改某個變量的值,但是從源代碼看,并不是簡單的溢出就可以修改了。
#!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()
函數的輸入,我們可以控制這個格式化字符串,導致了格式化字符串漏洞。
驗證的確有格式化字符串漏洞,利用這個漏洞,可以讀寫任意地址的值,所以我們構造一個合適的格式化字符串,就可以修改變量i
的值,得到一個高權限的shell
。具體操作如下圖所示。
這一關需要兩個輸入作為main()
函數的參數,猜測應該有很明顯的溢出點,難度就在于如何構造合適的溢出字符串。
#!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
。緩沖區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
的長度需要考慮。實際操作如下。
簡單的程序輸出已經提供不了太多的有效信息了,但是還是可以看到有輸入,就有可能有緩沖區溢出問題。
#!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腳本來嘗試爆破的,但是技術太爛,導致腳本執行的結果不太好,還是人工爆破來做的。其實這里猜測的風險很大,因為如果字符串的存儲地址不是四字節對齊的話,這樣我們在字符串中存放的地址就需要調整偏移,但是因為沒有輸出,導致無法知道這個偏移到底存在與否。好在題目設計得不是太難。
既然程序執行已經無法提供太多有效的信息了,還是直接看代碼吧。
#!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
這個變量,這個變量又是我們的輸入字符串的基地址指針,如果被修改了,就無法正確訪問我們輸入的字符串。
從執行結果來看,輸入太長的字符串,都會導致后面的字符串沒有復制到緩沖區中,這樣也就無法覆蓋函數的返回地址,溢出失敗。于是改變了思路,既然太長的輸入字符串會修改原來的基地址值,那么就用原來的基地址值再覆蓋回去,這樣就相當于沒有修改了。只需要猜測原來的基地址值就可以了。從程序中可以看到,緩沖區被復制之后,沒有正確的結束符,這樣給了我們打印blah
變量原來的值的可能。
緩沖區長度是20
,所以輸入長度為20
的字符串正好可以覆蓋緩沖區,同時又沒有正確的結束符,就可以看到blah
變量的值了。在這里是0xffffd7c7
,根據測試,我們輸入的字符串長度每增加1
,這個基地址值就會減少1
,通過計算,就可以得到正確的基地址值了。具體操作過程如下圖所示。
終于結束了這次的wargame
,想到這一期的wargame
難度只有2/10,我就知道后面還會有更多更好玩的東西。畢竟這里還有沒涉及到ASLR
、stack canary
等緩沖區溢出保護策略,不過這些在后面的游戲都會有的。盡請期待~