1. 程式人生 > 其它 >PWN學習之格式化字串漏洞

PWN學習之格式化字串漏洞

目錄

PWN學習之格式化字串漏洞

格式化輸出函式

可變引數:https://blog.csdn.net/smstong/article/details/50751121 (C語言可變參函式的實現)

首先我們瞭解格式化字串漏洞前,需要對格式化輸出的函式進行一個瞭解,在C中格式化輸出函式一共有如下:

fprintf()  "按照格式字串將輸出寫入流中。三個引數分別是流、格式字串和變參列表。"
printf()   "等同於fprintf(),但是它的輸出流為stdout。"
sprintf()  "等同於fprintf(),但是它的輸出不是寫入流而是寫入陣列。在寫入的字串末尾必須新增一個空字元。"
snprintf() "等同於sprintf(),但是它指定了可寫入字元的最大值size。超過第size-1的部分會被捨棄,並且會在寫入陣列的字串末尾新增一個空字元。"
dprintf()  "等同於fprintf(),但是它的輸出不是寫入流而是一個檔案描述符fd。"

"分別與上面的函式對應,但是它們將變參列表換成了va_list型別的引數。"
vfprint()、vprintf()、vsprintf()、vsnprintf()、vdprintf() 

格式化字串漏洞

格式化字串漏洞從2000年左右開始流行起來,幾乎在各種軟體中都能見到它的身影,隨著技術的發展,軟體安全性的提升,如今它在桌面端已經比較少見了,但在物聯網裝置 IoT上依然層出不窮。

#include <stdio.h>
void main()
{
    printf("%s %d %s %x %x %x %3$s","Hello World!",233,"\n");
}

我們輸入的引數只有三個,但是格式化字串中還有3個%x和一個%3$s,其中3個%x由於沒有引數他會洩漏出棧的地址

接下來繼續來看一個例子,其中fgets來接受使用者輸入的字串,但是如果是hack他就會輸入控制字串來洩漏出棧地址,這點感覺和Web中的xss有點像,程式設計師沒有過濾敏感字元導致被攻擊。由此可以總結出,格式字串漏洞發生的條件就是格式字串

要求的引數和實際提供的引數不匹配

#include <stdio.h>
void main()
{
    char buf[50];
    if(fgets(buf,sizeof buf,stdin) == NULL)
        return;
    printf(buf);
}

漏洞利用

對於格式化字串漏洞的利用主要有:

  • 使程式崩潰
  • 棧資料洩露
  • 任意地址記憶體洩露
  • 棧資料覆蓋
  • 任意地址記憶體覆蓋

使程式崩潰

造成程式崩潰原因:printf需要在棧中取一個數字視為地址,然後打印出地址所指向的記憶體,知道出現空白字元;獲取的某個數字可能並不是一個地址;獲得的陣列確實是一個地址,但改地址受保護。

在Linux中,存取無效的指標會使程序收到SIGSEGV訊號。

printf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s")

棧資料洩露

#include <stdio.h>
void main()
{
    char format[128];
    int arg1 = 0x00123456,arg2 = 0x11111111,arg3 = 0x22222222;
    char arg4[4] = "ABCD";
    scanf("%s",format);
    printf(format,arg1,arg2,arg3,arg4);
    printf("\n");
}
輸入 %p-%p-%p-%p-%p 

可以根據洩漏出來的棧的資料,然後挨個的計算出引數的位置,因為棧中的資料一般都是挨著的,可以看到在0xffffd484的下個數據就是字串ABCD字串的地址。

現在我們已經知道了如何按順序洩露棧資料,那麼如果想直接洩露指定的某個資料,則可以使用與下面類似的格式字串,這裡的n表示位於格式字串後的第n個數據%n$p

%<arg#>$<format>
%n$x

分別獲取arg3、arg1、arg2、arg2、arg4 以及棧上經跟引數的兩個值
"%3$x-%1$p-%2$p-%2$p-%4$p-%5$p-%6$p"

任意地址記憶體洩漏

攻擊者使用類似%s的格式規範就可以洩露出引數(指標)所指向記憶體的資料,程式會將它作為一個ASCII字串處理,直到遇到一個空字元。所以,如果攻擊者能夠操縱這個引數的值,那麼就可以洩露任意地址的內容。

AAAA-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p
AAAA代表地址

棧資料覆蓋

#include <stdio.h>
void main()
{
    int i;
    char str[] = "hello";
    printf("%s %n\n",str,&i);
    printf("%d\n",i);
}

這個例子i被賦值成了6,因為遇到轉換指示符之前一共寫入了6個字元("hello"加上一個空格)。在沒有長度修飾符時,預設寫入一個int型別的值。有關詳細內容可以參考:https://blog.csdn.net/FollowGodSteps/article/details/74115138

以下例子參考文章:c語言中對欄位寬度的理解?

/*************************************************************************
    > File Name: printf.c
    > Author: Mr.Yang
    > Purpose:演示printf的用法
    > Created Time: 2017年05月21日 星期日 10時07分44秒
************************************************************************/

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
        float i = 10000.123;

        printf("%5f\n",i);
        printf("%10f\n",i);
        printf("%15f\n",i);
        printf("%20f\n",i);
        printf("%25f\n",i);

        return 0;
}

輸出內容:

10000.123047
10000.123047
   10000.123047
        10000.123047
             10000.123047

回到一開始的程式,我們嘗試將arg2的值更改為任意值(例如0x00000020,十進位制32),於是構造格式字串\x28\xcd\xff\xff%08x%08x%012d%13$n,其中\x28\xcd\xff\xff是arg2的地址,佔4位元組,“%08x%08x”表示兩個8字元寬的十六進位制數,佔16位元組,“%012d”佔12位元組,三個部分加起來共佔4+16+12=32位元組,也就是把arg2賦值為0x00000020。格式字串最後一部分“%13$n”是最重要的一部分,表示格式字串的第13個引數,即寫入0xffffcd28的地方(0xffffcd58),printf()通過該地址找到被覆蓋資料。

對比printf()執行前後的棧,可以看到其首先解析“%13$n”,從0xffffcd58找到地址0xffffcd28,然後將其資料覆蓋為“0x00000020”。

任意地址記憶體覆蓋

也許已經有人發現了問題,使用上面的方法,值最小隻能是4,因為光地址就佔去了4個位元組,那麼怎樣覆蓋比4小的值呢?利用整數溢位是一個方法,但是在實踐中這樣做很難成功。再想一下,前面的輸入中,地址都位於格式字串之前,這樣做真的有必要嗎,能否將地址放在中間呢?我們來試一下,使用格式字串“AA%15\(nA"+"\x38\xd5\xff\xff”,開頭的“AA”佔2個位元組,即將地址賦值為2,中間“%15\)n”佔5個位元組(這裡不是%13$n,因為地址被放在了後面),是第15個引數,後面跟上一個“A”佔用1個位元組。於是前半部分總共佔用2+5+1=8個位元組,剛好是兩個引數的寬度,這裡的8位元組對齊十分重要。最後,輸入我們要覆蓋的地址“\x38\xd5\xff\xff”,如下所示。