wargame narnia writeup
前言
這一期的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
從目標機器的資料夾中我們可以看到,每個 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
如下,發現正確得到了密碼。
level 1
執行可執行程式,可以得到一個很有用的提示如下。
好像只要把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
一下就可以得到想要的方法。
level 2
從程式執行結果來看,似乎需要給一個輸入作為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
函式的返回地址就可以了。
level 3
從這道題目開始,難度開始慢慢加大了,不過依然都在控制之內。
從程式執行結果來看,似乎將某個檔案作為引數傳給程式,然後程式開啟輸出到/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
下資料夾的訪問許可權問題。
level 4
這一關程式執行居然沒有輸出,看來只能透過分析原始碼來找溢位點了。
#!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
就可以了。結果如下圖所示。
level 5
這一關從程式執行來看,也是透過溢位修改某個變數的值,但是從原始碼看,並不是簡單的溢位就可以修改了。
#!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
。具體操作如下圖所示。
level 6
這一關需要兩個輸入作為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
的長度需要考慮。實際操作如下。
level 7
簡單的程式輸出已經提供不了太多的有效資訊了,但是還是可以看到有輸入,就有可能有緩衝區溢位問題。
#!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指令碼來嘗試爆破的,但是技術太爛,導致指令碼執行的結果不太好,還是人工爆破來做的。其實這裡猜測的風險很大,因為如果字串的儲存地址不是四位元組對齊的話,這樣我們在字串中存放的地址就需要調整偏移,但是因為沒有輸出,導致無法知道這個偏移到底存在與否。好在題目設計得不是太難。
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
這個變數,這個變數又是我們的輸入字串的基地址指標,如果被修改了,就無法正確訪問我們輸入的字串。
從執行結果來看,輸入太長的字串,都會導致後面的字串沒有複製到緩衝區中,這樣也就無法覆蓋函式的返回地址,溢位失敗。於是改變了思路,既然太長的輸入字串會修改原來的基地址值,那麼就用原來的基地址值再覆蓋回去,這樣就相當於沒有修改了。只需要猜測原來的基地址值就可以了。從程式中可以看到,緩衝區被複制之後,沒有正確的結束符,這樣給了我們列印blah
變數原來的值的可能。
緩衝區長度是20
,所以輸入長度為20
的字串正好可以覆蓋緩衝區,同時又沒有正確的結束符,就可以看到blah
變數的值了。在這裡是0xffffd7c7
,根據測試,我們輸入的字串長度每增加1
,這個基地址值就會減少1
,透過計算,就可以得到正確的基地址值了。具體操作過程如下圖所示。
end of the game
終於結束了這次的wargame
,想到這一期的wargame
難度只有2/10,我就知道後面還會有更多更好玩的東西。畢竟這裡還有沒涉及到ASLR
、stack canary
等緩衝區溢位保護策略,不過這些在後面的遊戲都會有的。盡請期待~
相關文章
- SSCTF Writeup2020-08-19
- BCTF Writeup2020-08-19
- JCTF Writeup2020-08-19
- HCTF writeup(web)2020-08-19Web
- 太湖杯writeup2020-11-22
- 0ctf writeup2020-08-19
- 360hackgame writeup2020-08-19GAM
- Wargama-leviathan Writeup2020-08-19GAM
- CoolShell解密遊戲的WriteUp2020-08-19解密遊戲
- guestbook(hackme web部分writeup)2020-10-31Web
- 三道MISC的writeup2022-12-08
- xss挑戰賽writeup2020-08-19
- web_ping的writeup2018-08-19Web
- CTFSHOW-WEB入門 writeup2020-09-29Web
- Alictf2014 Writeup2020-08-19TF2
- cmseasy&內網滲透 Writeup2021-08-19內網
- 2016hctf writeup2017-03-04
- Hack.lu 2014 Writeup2020-08-19
- 31C3 CTF web關writeup2020-08-19Web
- CTF-safer-than-rot13-writeup2021-07-20
- Flare-on5 Challenge6 magic -Writeup2018-09-20
- CTF——WriteUp(2020招新)2020-11-04
- Web_Bugku_WriteUp | 變數12024-03-17Web變數
- Misc_BUUCTF_WriteUp | 面具下的flag2024-10-30
- 無聲杯 xss 挑戰賽 writeup2020-08-19
- 技術分享 | "錦行杯"比賽 Writeup2021-02-02
- 網鼎杯-writeup-第二場-babyRSA2018-08-24
- xctf攻防世界—Web新手練習區 writeup2020-12-22Web
- 2020湖湘杯部分writeup2020-11-02
- 【Writeup】Pwnable.kr 0x02 collision2017-07-29
- XSS挑戰第一期Writeup2020-08-19
- XSS挑戰第二期 Writeup2020-08-19
- 第五季極客大挑戰writeup2020-08-19
- 【阿菜Writeup】Security Innovation Smart Contract CTF2021-08-26
- 第一屆BMZCTF公開賽-WEB-Writeup2020-12-30Web
- 阿里雲CTF逆向題“尤拉”詳細Writeup2024-04-30阿里
- 32C3 CTF 兩個Web題目的Writeup2020-08-19Web
- 幾期『三個白帽』小競賽的writeup2020-08-19