亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

詳解C語言之緩沖區(qū)溢出

 更新時間:2021年06月14日 11:32:48   作者:clover_toeic  
緩沖區(qū)是一塊連續(xù)的計算機(jī)內(nèi)存區(qū)域,可保存相同數(shù)據(jù)類型的多個實例。緩沖區(qū)可以是堆棧、堆和靜態(tài)數(shù)據(jù)區(qū)。在C/C++語言中,通常使用字符數(shù)組和malloc/new實現(xiàn)緩沖區(qū)。溢出指數(shù)據(jù)被添加到分配給該緩沖區(qū)的內(nèi)存塊之外。緩沖區(qū)溢出是最常見的程序缺陷

一、緩沖區(qū)溢出原理

棧幀結(jié)構(gòu)的引入為高級語言中實現(xiàn)函數(shù)或過程調(diào)用提供直接的硬件支持,但由于將函數(shù)返回地址這樣的重要數(shù)據(jù)保存在程序員可見的堆棧中,因此也給系統(tǒng)安全帶來隱患。若將函數(shù)返回地址修改為指向一段精心安排的惡意代碼,則可達(dá)到危害系統(tǒng)安全的目的。此外,堆棧的正確恢復(fù)依賴于壓棧的EBP值的正確性,但EBP域鄰近局部變量,若編程中有意無意地通過局部變量的地址偏移竄改EBP值,則程序的行為將變得非常危險。

由于C/C++語言沒有數(shù)組越界檢查機(jī)制,當(dāng)向局部數(shù)組緩沖區(qū)里寫入的數(shù)據(jù)超過為其分配的大小時,就會發(fā)生緩沖區(qū)溢出。攻擊者可利用緩沖區(qū)溢出來竄改進(jìn)程運(yùn)行時棧,從而改變程序正常流向,輕則導(dǎo)致程序崩潰,重則系統(tǒng)特權(quán)被竊取。

例如,對于下圖的棧結(jié)構(gòu):

若將長度為16字節(jié)的字符串賦給acArrBuf數(shù)組,則系統(tǒng)會從acArrBuf[0]開始向高地址填充棧空間,導(dǎo)致覆蓋EBP值和函數(shù)返回地址。若攻擊者用一個有意義的地址(否則會出現(xiàn)段錯誤)覆蓋返回地址的內(nèi)容,函數(shù)返回時就會去執(zhí)行該地址處事先安排好的攻擊代碼。最常見的手段是通過制造緩沖區(qū)溢出使程序運(yùn)行一個用戶shell,再通過shell執(zhí)行其它命令。若該程序有root或suid執(zhí)行權(quán)限,則攻擊者就獲得一個有root權(quán)限的shell,進(jìn)而可對系統(tǒng)進(jìn)行任意操作。

除通過使堆棧緩沖區(qū)溢出而更改返回地址外,還可改寫局部變量(尤其函數(shù)指針)以利用緩沖區(qū)溢出缺陷。

注意,本文描述的堆棧緩沖區(qū)溢出不同于廣義的“堆棧溢出(Stack OverFlow)”,后者除局部數(shù)組越界和內(nèi)存覆蓋外,還可能由于調(diào)用層次太多(尤其應(yīng)注意遞歸函數(shù))或過大的局部變量所導(dǎo)致。

二、緩沖區(qū)溢出實例

本節(jié)給出若干緩沖區(qū)溢出相關(guān)的示例性程序。前三個示例為手工修改返回地址或?qū)崊ⅲ髢蓚€示例為局部數(shù)組越界訪問和緩沖區(qū)溢出。更加深入的緩沖區(qū)溢出攻擊參見相關(guān)資料。

示例函數(shù)必須包含stdio.h頭文件,并按需包含string.h頭文件(如strcpy函數(shù))。

【示例1】改變函數(shù)的返回地址,使其返回后跳轉(zhuǎn)到某個指定的指令位置,而不是函數(shù)調(diào)用后緊跟的位置。實現(xiàn)原理是在函數(shù)體中修改返回地址,即找到返回地址的位置并修改它。代碼如下:

//foo.c
void foo(void){
    int a, *p;
    p = (int*)((char *)&a + 12);  //讓p指向main函數(shù)調(diào)用foo時入棧的返回地址,等效于p = (int*)(&a + 3);
    *p += 12;    //修改該地址的值,使其指向一條指令的起始地址
}
int main(void){
    foo();
    printf("First printf call\n");
    printf("Second printf call\n");
    return 0;
}

編譯運(yùn)行,結(jié)果輸出Second printf call,未輸出First printf call。

下面詳細(xì)介紹代碼中兩個12的由來。

編譯(gcc main.c –g)和反匯編(objdump a.out –d)后,得到匯編代碼片段如下:

從上述匯編代碼可知,foo后面的指令地址(即調(diào)用foo時壓入的返回地址)是0x80483b8,而進(jìn)入調(diào)用printf("Second printf call“)的指令地址是0x80483c4。兩者相差12,故將返回地址的值加12即可(*p += 12)。

指令<804838a>將-8(%ebp)的地址賦值給%eax寄存器(p = &a)??芍猣oo()函數(shù)中的變量a存儲在-8(%ebp)地址上,該地址向上8+4=12個單位就是返回地址((char *)&a + 12)。修改該地址內(nèi)容(*p += 12)即可實現(xiàn)函數(shù)調(diào)用結(jié)束后跳轉(zhuǎn)到第二個printf函數(shù)調(diào)用的位置。

用gdb查看匯編指令剛進(jìn)入foo時棧頂?shù)闹?%esp),如下所示:

可見%esp值的確是調(diào)用foo后main中下條待執(zhí)行指令的地址,而代碼所修改的也正是該值。%eip則指向當(dāng)前程序(foo)的指令地址。

【示例2】暫存RunAway函數(shù)的返回地址后修改其值,使函數(shù)返回后跳轉(zhuǎn)到Detour函數(shù)的地址;Detour函數(shù)內(nèi)嘗試通過之前保存的返回地址重回main函數(shù)內(nèi)。代碼如下:

//RunAway.c
int gPrevRet = 0; //保存函數(shù)的返回地址
void Detour(void){
    int *p = (int*)&p + 2;  //p指向函數(shù)的返回地址
    *p = gPrevRet;
    printf("Run Away!\n"); //需要回車,或打印后fflush(stdout);刷新緩沖區(qū),否則可能在段錯誤時無法輸出
}
int RunAway(void){
    int *p = (int*)&p + 2;
    gPrevRet = *p;
    *p = (int)Detour;
    return 0;
}
int main(void){
    RunAway();
    printf("Come Home!\n");
    return 0;
}

編譯運(yùn)行后輸出:

Run Away!

Come Home!

Run Away!

Come Home!

Segmentation fault

運(yùn)行后出現(xiàn)段錯誤?There must be something wrong!錯誤原因留待讀者思考,下面給出上述代碼的另一版本,借助匯編獲取返回地址(而不是根據(jù)棧幀結(jié)構(gòu)估算)。

register void *gEbp __asm__ ("%ebp");
void Detour(void){
    *((int *)gEbp + 1) = gPrevRet;
    printf("Run Away!\n");
}
int RunAway(void){
    gPrevRet = *((int *)gEbp + 1);
    *((int *)gEbp + 1) = Detour;
    return 0;
}

【示例3】在被調(diào)函數(shù)內(nèi)修改主調(diào)函數(shù)指針變量,造成后續(xù)訪問該指針時程序崩潰。代碼如下:

//Crasher.c
typedef struct{
    int member1;
    int member2;
}T_STRT;
T_STRT gtTestStrt = {0};
register void *gEbp __asm__ ("%ebp");

void Crasher(T_STRT *ptStrt){
    printf("[%s]: ebp    = %p(0x%08x)\n", __FUNCTION__, gEbp, *((int*)gEbp));
    printf("[%s]: ptStrt = %p(%p)\n", __FUNCTION__, &ptStrt, ptStrt);
    printf("[%s]: (1)    = %p(0x%08x)\n", __FUNCTION__, ((int*)&ptStrt-2), *((int*)&ptStrt-2));
    printf("[%s]: (2)    = %p(0x%08x)\n", __FUNCTION__, (int*)(*((int*)&ptStrt-2)-4), *(int*)(*((int*)&ptStrt-2)-4));
    printf("[%s]: (3)    = %p(0x%08x)\n", __FUNCTION__, (int*)(*((int*)&ptStrt-2)-8), *(int*)(*((int*)&ptStrt-2)-8));
    *(int*)( *( (int*)&ptStrt - 2 ) - 8 ) = 0;  //A:此句將導(dǎo)致代碼B處發(fā)生段錯誤
}

int main(void){
    printf("[%s]: ebp    = %p(0x%08x)\n", __FUNCTION__, gEbp, *((int*)gEbp));
    T_STRT *ptStrt = &gtTestStrt;
    printf("[%s]: ptStrt = %p(%p)\n", __FUNCTION__, &ptStrt, ptStrt);

    Crasher(ptStrt);
    printf("[%s]: ptStrt = %p(%p)\n", __FUNCTION__, &ptStrt, ptStrt);
    ptStrt->member1 = 5;  //B:需要在此處崩潰
    printf("Try to come here!\n");
    return 0;
}

運(yùn)行結(jié)果如下所示:

根據(jù)打印出的地址及其存儲內(nèi)容,可得到以下堆棧布局:

&ptStrt為形參地址0xbff8f090,該地址處在main函數(shù)棧幀中。(int*)&ptStrt - 2地址存儲主調(diào)函數(shù)的EBP值,根據(jù)該值可直接定位到main函數(shù)棧幀底部。(*((int*)&ptStrt - 2) - 8)為主調(diào)函數(shù)中實參ptStrt的地址,而*(int*) (*((int*)&ptStrt - 2) - 4) = 0將該地址內(nèi)容置零,即實參指針ptStrt設(shè)置為NULL(不再指向全局結(jié)構(gòu)gtTestStrt)。這樣,訪問ptStrt->member1時就會發(fā)生段錯誤。

注意,雖然本例代碼結(jié)構(gòu)簡單,但不能輕率地推斷main函數(shù)中局部變量ptStrt位于幀基指針EBP-4處(實際上本例為EBP-8處)。以下改進(jìn)版本用于自動計算該偏移量:

static int gOffset = 0;
void Crasher(T_STRT *ptStrt){
   *(int*)( *(int*)gEbp - gOffset ) = 0;
}

int main(void){
    T_STRT *ptStrt = &gtTestStrt;
    gOffset = (char*)gEbp - (char*)(&ptStrt);
    Crasher(ptStrt);
    ptStrt->member1 = 5;  //在此處崩潰
    printf("Try to come here!\n");
    return 0;
}

當(dāng)然,該版本已失去原有意義(不借助寄存器層面手段),純?yōu)槭纠?/p>

【示例4】越界訪問造成死循環(huán)。代碼如下:

//InfinteLoop.c
void InfinteLoop(void){ 
    unsigned char ucIdx, aucArr[10]; 
    for(ucIdx = 0; ucIdx <= 10; ucIdx++)
        aucArr[ucIdx] = 1;
}

在循環(huán)內(nèi)部,當(dāng)訪問不存在的數(shù)組元素aucArr[10]時,實際上在訪問數(shù)組aucArr所在地址之后的那個位置,而該位置存放著變量ucIdx。因此aucArr[10] = 1將ucIdx重置為1,然后繼續(xù)循環(huán)的條件仍然成立,最終將導(dǎo)致死循環(huán)。

【示例5】緩沖區(qū)溢出。代碼如下:

//CarelessPapa.c
register int *gEbp __asm__ ("%ebp");
void NaughtyBoy(void){
    printf("[2]EBP=%p(%#x), EIP=%p(%#x)\n", gEbp, *gEbp, gEbp+1, *(gEbp+1));
    printf("Catch Me!\n");
}
void CarelessPapa(const char *pszStr){
    printf("[1]EBP=%p(%#x)\n", gEbp, *gEbp);
    printf("[1]EIP=%p(%#x)\n", gEbp+1, *(gEbp+1));
    char szBuf[8];
    strcpy(szBuf, pszStr);
}
int main(void){
    printf("[0]EBP=%p(%#x)\n", gEbp, *gEbp);
    printf("Addr: CarelessPapa=%p, NaughtyBoy=%p\n", CarelessPapa, NaughtyBoy);
    char szArr[]="0123456789AB\xe4\x83\x4\x8\x23\x85\x4\x8";
    CarelessPapa(szArr);
    printf("Come Home!\n");
    printf("[3]EBP=%p\n", gEbp);
    return 0;
}

編譯運(yùn)行結(jié)果如下:

可見,當(dāng)CarelessPapa函數(shù)調(diào)用結(jié)束后,并未直接執(zhí)行Come Home的輸出,而是轉(zhuǎn)而執(zhí)行NaughtyBoy函數(shù)(輸出Catch Me),然后回頭輸出Come Home。該過程重復(fù)一次后發(fā)生段錯誤(具體原因留待讀者思考)。

結(jié)合下圖所示的棧幀布局,詳細(xì)分析本示例緩沖區(qū)溢出過程。注意,本示例中地址及其內(nèi)容由內(nèi)嵌匯編和打印輸出獲得,正常情況下應(yīng)通過gdb調(diào)試器獲得。

首先,main函數(shù)將字符數(shù)組szArr的地址作為參數(shù)(即pszStr)傳遞給函數(shù)CarelessPapa。該數(shù)組內(nèi)容為"0123456789AB\xe4\x83\x4\x8\x23\x85\x4\x8",其中轉(zhuǎn)義字符串"\xe4\x83\x4\x8"對應(yīng)NaughtyBoy函數(shù)入口地址0x080483e4(小字節(jié)序),而"\x23\x85\x4\x8"對應(yīng)調(diào)用CarelessPapa函數(shù)時的返回地址0x8048523(小字節(jié)序)。CarelessPapa函數(shù)內(nèi)部調(diào)用strcpy庫函數(shù),將pszStr所指字符串內(nèi)容拷貝至szBuf數(shù)組。因為strcpy函數(shù)不進(jìn)行越界檢查,會逐字節(jié)拷貝直到遇見'\0'結(jié)束符。故pszStr字符串將從szBuf數(shù)組起始地址開始向高地址覆蓋,原返回地址0x8048523被覆蓋為NaughtyBoy函數(shù)地址0x080483e4。

這樣,當(dāng)CarelessPapa函數(shù)返回時,修改后的返回地址從棧中彈出到EIP寄存器中,此時棧頂指針ESP指向返回地址上方的空間(esp+4),程序跳轉(zhuǎn)到EIP所指地址(NaughtyBoy函數(shù)入口)開始執(zhí)行,首先就是EBP入?!⑽聪裾U{(diào)用那樣先壓入返回地址,故NaughtyBoy函數(shù)棧幀中EBP位置相對CarelessPapa函數(shù)上移4個字節(jié)!此時,"\x23\x85\x4\x8"可將EBP上方的EIP修改為CarelessPapa函數(shù)的返回地址(0x8048523),從而保證正確返回main函數(shù)內(nèi)。

注意,返回main函數(shù)并輸出Come Home后,main函數(shù)棧幀的EBP地址被改為0x42413938("89AB"),該地址已非堆??臻g,最終產(chǎn)生段錯誤。EBP地址會隨每次程序執(zhí)行而改變,故試圖在szArr字符串中恢復(fù)EBP是非常困難的。

從main函數(shù)return時將返回到調(diào)用它的啟動例程(_start函數(shù))中,返回值被啟動例程獲得并用其作為參數(shù)調(diào)用exit函數(shù)。exit函數(shù)首先做一些清理工作,然后調(diào)用_exit系統(tǒng)調(diào)用終止進(jìn)程。main函數(shù)的返回值最終傳給_exit系統(tǒng)調(diào)用,成為進(jìn)程的退出狀態(tài)。以下代碼在main函數(shù)中直接調(diào)用exit函數(shù)終止進(jìn)程而不返回到啟動例程:

//CarelessPapa.c
register int *gEbp __asm__ ("%ebp");
void NaughtyBoy(void){
    printf("[2]EBP=%p(%#x), EIP=%p(%#x)\n", gEbp, *gEbp, gEbp+1, *(gEbp+1));
    printf("Catch Me!\n");
}
void CarelessPapa(const char *pszStr){
    printf("[1]EBP=%p(%#x)\n", gEbp, *gEbp);
    printf("[1]EIP=%p(%#x)\n", gEbp+1, *(gEbp+1));
    char szBuf[8];
    strcpy(szBuf, pszStr);
}
int main(void){
    printf("[0]EBP=%p(%#x)\n", gEbp, *gEbp);
    printf("Addr: CarelessPapa=%p, NaughtyBoy=%p\n", CarelessPapa, NaughtyBoy);
    char szArr[]="0123456789AB\x14\x84\x4\x8\x33\x85\x4\x8"; //轉(zhuǎn)義字符串稍有變化
    CarelessPapa(szArr);
    printf("Come Home!\n");
    printf("[3]EBP=%p\n", gEbp);
    exit(0); //#include <stdlib.h>
}

編譯運(yùn)行結(jié)果如下:

這次沒有重復(fù)執(zhí)行,也未出現(xiàn)段錯誤。

三、緩沖區(qū)溢出防范

防范緩沖區(qū)溢出問題的準(zhǔn)則是:確保做邊界檢查(通常不必?fù)?dān)心影響程序效率)。不要為接收數(shù)據(jù)預(yù)留相對過小的緩沖區(qū),大的數(shù)組應(yīng)通過malloc/new分配堆空間來解決;在將數(shù)據(jù)讀入或復(fù)制到目標(biāo)緩沖區(qū)前,檢查數(shù)據(jù)長度是否超過緩沖區(qū)空間。同樣,檢查以確保不會將過大的數(shù)據(jù)傳遞給別的程序,尤其是第三方COTS(Commercial-off-the-shelf)商用軟件庫——不要設(shè)想關(guān)于其他人軟件行為的任何事情。

若有可能,改用具備防止緩沖區(qū)溢出內(nèi)置機(jī)制的高級語言(Java、C#等)。但許多語言依賴于C庫,或具有關(guān)閉該保護(hù)特性的機(jī)制(為速度而犧牲安全性)。其次,可以借助某些底層系統(tǒng)機(jī)制或檢測工具(如對C數(shù)組進(jìn)行邊界檢查的編譯器)。許多操作系統(tǒng)(包括Linux和Solaris)提供非可執(zhí)行堆棧補(bǔ)丁,但該方式不適于這種情況:攻擊者利用堆棧溢出使程序跳轉(zhuǎn)到放置在堆上的執(zhí)行代碼。此外,存在一些偵測和去除緩沖區(qū)溢出漏洞的靜態(tài)工具(檢查代碼但并不運(yùn)行)和動態(tài)工具(執(zhí)行代碼以確定行為),甚至采用grep命令自動搜索源代碼中每個有問題函數(shù)的實例。

但即使采用這些保護(hù)手段,程序員自身也可能犯其他許多錯誤,從而引入缺陷。例如,當(dāng)使用有符號數(shù)存儲緩沖區(qū)長度或某個待讀取內(nèi)容長度時,攻擊者可將其變?yōu)樨?fù)值,從而使該長度被解釋為很大的正值。經(jīng)驗豐富的程序員還容易過于自信地"把玩"某些危險的庫函數(shù),如對其添加自己總結(jié)編寫的檢查,或錯誤地推論出使用潛在危險的函數(shù)在某些特殊情況下是"安全"的。

本節(jié)將主要討論一些已被證明危險的C庫函數(shù)。通過在C/C++程序中禁用或慎用危險的函數(shù),可有效降低在代碼中引入安全漏洞的可能性。在考慮性能和可移植性的前提下,強(qiáng)烈建議在開發(fā)過程中使用相應(yīng)的安全函數(shù)來替代危險的庫函數(shù)調(diào)用。

以下分析某些危險的庫函數(shù),較完整的列表參見表3-1。

3.1、gets

該函數(shù)從標(biāo)準(zhǔn)輸入讀入用戶輸入的一行文本,在遇到EOF字符或換行字符前,不會停止讀入文本。即該函數(shù)不執(zhí)行越界檢查,故幾乎總有可能使任何緩沖區(qū)溢出(應(yīng)禁用)。

gcc編譯器下會對gets調(diào)用發(fā)出警告(the `gets' function is dangerous and should not be used)。

3.2、strcpy

該函數(shù)將源字符串復(fù)制到目標(biāo)緩沖區(qū),但并未指定要復(fù)制字符的數(shù)目。若源字符串來自用戶輸入且未限制其長度,則可能引發(fā)危險。規(guī)避的方法如下:

1) 若知道目標(biāo)緩沖區(qū)大小,則可添加明確的檢查(不建議該法):

if(strlen(szSrc) >= dwDstSize){
    /* Do something appropriate, such as throw an error. */
}
else{
    strcpy(szDst, szSrc);
}

2) 改用strncpy函數(shù):

strncpy(szDst, szSrc, dwDstSize-1);
szDst[dwDstSize-1] = '\0';  //Always do this to be safe!

若szSrc比szDst大,則該函數(shù)不會返回錯誤;當(dāng)達(dá)到指定長度(dwDstSize-1)時,停止復(fù)制字符。第二句將字符串結(jié)束符放在szDst數(shù)組的末尾。

3) 在源字符串上調(diào)用strlen()來為其分配足夠的堆空間:

pszDst = (char *)malloc(strlen(szSrc));
strcpy(pszDst, szSrc);

4) 某些情況下使用strcpy不會帶來潛在的安全性問題:

strcpy(szDst, "Hello!");  //Usually by initialization, such as char szDst[] = “Hello!”;

即使該操作造成szDst溢出,但這幾個字符顯然不會造成危害——除非用其它方式覆蓋字符串“Hello”所在的靜態(tài)存儲區(qū)。

安全的字符串處理函數(shù)通常體現(xiàn)在如下幾個方面:

  • 顯式指明目標(biāo)緩沖區(qū)大小
  • 動態(tài)校驗
  • 返回碼(以指明成功或失敗原因)

與strcpy函數(shù)具有相同問題的還有strcat函數(shù)。

3.3、 strncpy/strncat

該對函數(shù)是strcpy/strcat調(diào)用的“安全”版本,但仍存在一些問題:

1) strncpy和strncat要求程序員給出剩余的空間,而不是給出緩沖區(qū)的總大小。緩沖區(qū)大小一經(jīng)分配就不再變化,但緩沖區(qū)中剩余的空間量會在每次添加或刪除數(shù)據(jù)時發(fā)生變化。這意味著程序員需始終跟蹤或重新計算剩余的空間,而這種跟蹤或重新計算很容易出錯。

2) 在發(fā)生溢出(和數(shù)據(jù)丟失)時,strncpy和strncat返回結(jié)果字符串的起始地址(而不是其長度)。雖然這有利于鏈?zhǔn)奖磉_(dá),但卻無法報告緩沖區(qū)溢出。

3) 若源字符串長度至少和目標(biāo)緩沖區(qū)相同,則strncpy不會使用NUL來結(jié)束字符串;這可能會在以后導(dǎo)致嚴(yán)重破壞。因此,在執(zhí)行strncpy后通常需要手工終止目標(biāo)字符串。

4) strncpy還可復(fù)制源字符串的一部分到目標(biāo)緩沖區(qū),要復(fù)制的字符數(shù)目通常基于源字符串的相關(guān)信息來計算。這種操作也會產(chǎn)生未終止字符串。

5) strncpy會在源字符串結(jié)束時使用NUL來填充整個目標(biāo)緩沖區(qū),這在源字符串較短時存在性能問題。

3.4、sprintf

該函數(shù)使用控制字符串來指定輸出格式,該字符串通常包括"%s"(字符串輸出)。若指定字符串輸出的精確指定符,則可通過指定輸出的最大長度來防止緩沖區(qū)溢出(如%.10s將復(fù)制不超過10個字符)。也可以使用"*"作為精確指定符(如"%.*s"),這樣就可傳入一個最大長度值。精確字段僅指定一個參數(shù)的最大長度,但緩沖區(qū)需要針對組合起來的數(shù)據(jù)的最大尺寸調(diào)整大小。

注意,"字段寬度"(如"%10s",無點號)僅指定最小長度——而非最大長度,從而留下緩沖區(qū)溢出隱患。

3.5、scanf

scanf系列函數(shù)具有一個最大寬度值,函數(shù)不能讀取超過最大寬度的數(shù)據(jù)。但并非所有規(guī)范都規(guī)定了這點,也不確定是否所有實現(xiàn)都能正確執(zhí)行這些限制。若要使用這一特性,建議在安裝或初始化期間運(yùn)行小測試來確保它能正確工作。

3.6、streadd/strecpy

這對函數(shù)可將含有不可讀字符的字符串轉(zhuǎn)換成可打印的表示。其原型包含在libgen.h頭文件內(nèi),編譯時需加-lgen [library ...]選項。

char *strecpy(char *pszOut, const char *pszIn, const char *pszExcept);

char *streadd(char *pszOut, const char *pszIn, const char *pszExcept);

strecpy將輸入字符串pszIn(連同結(jié)束符)拷貝到輸出字符串pszOut中,并將非圖形字符展開為C語言中相應(yīng)的轉(zhuǎn)義字符序列(如Control-A轉(zhuǎn)為“\001”)。參數(shù)pszOut指向的緩沖區(qū)大小必須足夠容納結(jié)果字符串;輸出緩沖區(qū)大小應(yīng)為輸入緩沖區(qū)大小的四倍(單個字符可能轉(zhuǎn)換為\abc共四個字符)。出現(xiàn)在參數(shù)pszExcept字符串內(nèi)的字符不被展開。該參數(shù)可設(shè)為空串,表示擴(kuò)展所有非圖形字符。strecpy函數(shù)返回指向pszOut字符串的指針。

streadd函數(shù)與strecpy相同,只不過返回指向pszOut字符串結(jié)束符的指針。

考慮以下代碼:

#include <libgen.h>
int main(void){
    char szBuf[20] = {0};
    streadd(szBuf, "\t\n", "");
    printf(%s\n", szBuf);
    return 0;
}

打印輸出\t\n,而不是所有空白。

3.7、strtrns

該函數(shù)將pszStr字符串中的字符轉(zhuǎn)換后復(fù)制到結(jié)果緩沖區(qū)pszResult。其原型包含在libgen.h頭文件內(nèi):

char * strtrns(const char *pszStr, const char *pszOld, const char *pszNew, char *pszResult);

出現(xiàn)在pszOld字符串中的字符被pszNew字符串中相同位置的字符替換。函數(shù)返回新的結(jié)果字符串。

如下示例將小寫字符轉(zhuǎn)換成大寫字符:

#include <libgen.h>
int main(int argc,char *argv[]){
    char szLower[] = "abcdefghijklmnopqrstuvwxyz";
    char szUpper[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    if(argc < 2){
        printf("USAGE: %s arg\n", argv[0]);
        exit(0);
    }
    char *pszBuf = (char *)malloc(strlen(argv[1]));
    strtrns(argv[1], szLower, szUpper, pszBuf);
    printf("%s\n", pszBuf);
    return 0;
}

以上代碼使用malloc分配足夠空間來復(fù)制argv[1],因此不會引起緩沖區(qū)溢出。

3.8、realpath

該函數(shù)在libc 4.5.21及以后版本中提供,使用時需要limits.h和stdlib.h頭文件。其原型為:

char *realpath(const char *pszPath, char *pszResolvedPath);

該函數(shù)展開pszPath字符串中的所有符號鏈接,并解析pszPath中所引用的/./、/../和'/'字符(相對路徑),最終生成規(guī)范化的絕對路徑名。該路徑名作為帶結(jié)束符的字符串存入pszResolvedPath指向的緩沖區(qū),長度最大為PATH_MAX字節(jié)。結(jié)果路徑中不含符號鏈接、/./或/../。

若pszResolvedPath為空指針,則realpath函數(shù)使用malloc來分配PATH_MAX字節(jié)的緩沖區(qū)以存儲解析后的路徑名,并返回指向該緩沖區(qū)的指針。調(diào)用者應(yīng)使用free函數(shù)去釋放該該緩沖區(qū)。

若執(zhí)行成功,realpath函數(shù)返回指向pszResolvedPath(規(guī)范化絕對路徑)的指針;否則返回空指針并設(shè)置errno以指示該錯誤,此時pszResolvedPath的內(nèi)容未定義。

調(diào)用者需要確保結(jié)果緩沖區(qū)足夠大(但不應(yīng)超過PATH_MAX),以處理任何大小的路徑。此外,不可能為輸出緩沖區(qū)確定合適的長度,因此POSIX.1-2001規(guī)定,PATH_MAX字節(jié)的緩沖區(qū)足夠,但PATH_MAX不必定義為常量,且可以通過pathconf函數(shù)獲得。然而,pathconf輸出的結(jié)果可能超大,以致不適合動態(tài)分配內(nèi)存;另一方面,pathconf函數(shù)可返回-1表明結(jié)果路徑名超出PATH_MAX限制。pszResolvedPath為空指針的特性被POSIX.1-2008標(biāo)準(zhǔn)化,以避免輸出緩沖區(qū)長度難以靜態(tài)確定的缺陷。

應(yīng)禁用或慎用的庫函數(shù)如下表所示:

表3-1

函數(shù)

危險性

解決方案

gets

最高

禁用gets(buf),改用fgets(buf, size, stdin)

strcpy

檢查目標(biāo)緩沖區(qū)大小,或改用strncpy,或動態(tài)分配目標(biāo)緩沖區(qū)

strcat

改用strncat

sprintf

改用snprintf,或使用精度說明符

scanf

使用精度說明符,或自己進(jìn)行解析

sscanf

使用精度說明符,或自己進(jìn)行解析

fscanf

使用精度說明符,或自己進(jìn)行解析

vfscanf

使用精度說明符,或自己進(jìn)行解析

vsprintf

改為使用vsnprintf,或使用精度說明符

vscanf

使用精度說明符,或自己進(jìn)行解析

vsscanf

使用精度說明符,或自己進(jìn)行解析

streadd

確保分配的目標(biāo)參數(shù)緩沖區(qū)大小是源參數(shù)大小的四倍

strecpy

確保分配的目標(biāo)參數(shù)緩沖區(qū)大小是源參數(shù)大小的四倍

strtrns

手工檢查目標(biāo)緩沖區(qū)大小是否至少與源字符串相等

getenv

不可假定特殊環(huán)境變量的長度

realpath

高(或稍低,實現(xiàn)依賴)

分配緩沖區(qū)大小為PATH_MAX字節(jié),并手工檢查參數(shù)以確保輸入?yún)?shù)和輸出參數(shù)均不超過PATH_MAX

syslog

高(或稍低,實現(xiàn)依賴)

將字符串輸入傳遞給該函數(shù)之前,將所有字符串輸入截成合理大小

getopt

高(或稍低,實現(xiàn)依賴)

將字符串輸入傳遞給該函數(shù)之前,將所有字符串輸入截成合理大小

getopt_long

高(或稍低,實現(xiàn)依賴)

將字符串輸入傳遞給該函數(shù)之前,將所有字符串輸入截成合理大小

getpass

高(或稍低,實現(xiàn)依賴)

將字符串輸入傳遞給該函數(shù)之前,將所有字符串輸入截成合理大小

getchar

若在循環(huán)中使用該函數(shù),確保檢查緩沖區(qū)邊界

fgetc

若在循環(huán)中使用該函數(shù),確保檢查緩沖區(qū)邊界

getc

若在循環(huán)中使用該函數(shù),確保檢查緩沖區(qū)邊界

read

若在循環(huán)中使用該函數(shù),確保檢查緩沖區(qū)邊界

bcopy

確保目標(biāo)緩沖區(qū)不小于指定長度

fgets

確保目標(biāo)緩沖區(qū)不小于指定長度

memcpy

確保目標(biāo)緩沖區(qū)不小于指定長度

snprintf

確保目標(biāo)緩沖區(qū)不小于指定長度

strccpy

確保目標(biāo)緩沖區(qū)不小于指定長度

strcadd

確保目標(biāo)緩沖區(qū)不小于指定長度

strncpy

確保目標(biāo)緩沖區(qū)不小于指定長度

vsnprintf

確保目標(biāo)緩沖區(qū)不小于指定長度

以上就是詳解C語言之緩沖區(qū)溢出的詳細(xì)內(nèi)容,更多關(guān)于C語言 緩沖區(qū)溢出的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C語言類的雙向鏈表詳解

    C語言類的雙向鏈表詳解

    大家好,本篇文章主要講的是C語言類的雙向鏈表詳解,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下
    2022-01-01
  • C語言實現(xiàn)銀行管理系統(tǒng)(文件操作)

    C語言實現(xiàn)銀行管理系統(tǒng)(文件操作)

    這篇文章主要為大家詳細(xì)介紹了C語言實現(xiàn)銀行管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • C語言實現(xiàn)獲取文件MD5值

    C語言實現(xiàn)獲取文件MD5值

    MD5(Message?Digest?Algorithm?5)是一種常用的哈希函數(shù)算法,這篇文章主要介紹了C語言如何獲取文件MD5值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-08-08
  • C語言實現(xiàn)航班售票系統(tǒng) C語言實現(xiàn)航班管理系統(tǒng)

    C語言實現(xiàn)航班售票系統(tǒng) C語言實現(xiàn)航班管理系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了C語言實現(xiàn)航班售票系統(tǒng),C語言實現(xiàn)航班管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-12-12
  • Qt?timerEvent實現(xiàn)簡單秒表功能

    Qt?timerEvent實現(xiàn)簡單秒表功能

    這篇文章主要為大家詳細(xì)介紹了Qt?timerEvent實現(xiàn)簡單秒表功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-08-08
  • VsCode安裝和配置c/c++環(huán)境小白教程(圖文)

    VsCode安裝和配置c/c++環(huán)境小白教程(圖文)

    本文主要介紹了VsCode安裝和配置c/c++環(huán)境小白教程,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • 減少OpenCV讀取高分辨率圖像的時間示例

    減少OpenCV讀取高分辨率圖像的時間示例

    今天小編就為大家分享一篇減少OpenCV讀取高分辨率圖像的時間示例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-12-12
  • getdate()函數(shù)的用法實例

    getdate()函數(shù)的用法實例

    getdate()函數(shù)的用法實例,需要的朋友可以參考一下
    2013-03-03
  • C++實現(xiàn)的求解多元一次方程示例

    C++實現(xiàn)的求解多元一次方程示例

    這篇文章主要介紹了C++實現(xiàn)的求解多元一次方程,涉及C++矩陣運(yùn)算相關(guān)操作技巧,需要的朋友可以參考下
    2018-01-01
  • C語言實現(xiàn)簡易井字棋游戲

    C語言實現(xiàn)簡易井字棋游戲

    這篇文章主要為大家詳細(xì)介紹了C語言實現(xiàn)簡易井字棋游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-04-04

最新評論