日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

越过 __chkesp 检测的缓冲区溢出

發布時間:2024/1/17 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 越过 __chkesp 检测的缓冲区溢出 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

  本文的起源,來自于在學校BBS上的C++版上,有一個人問了一個問題,然后我給他已解答。這個帖子的原文是這樣的:

?

代碼 發信人: lisanbai (李三白), 板面: C++
標 題: 這個怎么一直不停輸出啊,菜鳥求教
發信站: 飄渺水云間 (Mon Sep
20 16:52:30 2010), 轉信

看了半天找不出毛病

char str1[]="go?";
char str2[]="back.";
int i=0;
int j=0;
int count=0;
while(str1[i])
i
++;
while(str2[j])
j
++;
for(;count<j;count++)
str1[i
++]=str2[count];
str1[i]
='\0';
printf(
"\n%s\n",str1);
return 0;
--
※ 來源:·飄渺水云間 zju88.org·[FROM: lisanbai]

?

    上面的代碼看起來,是把 str2 的內容附加到 str1 的尾部(完成 strcat 的功能),很顯然,他的錯誤是 str1 的空間不足夠容納 str2,作者之所以犯了這個錯誤,可能是因為他對內存管理不夠熟悉導致的。下面是我對該貼的回復:

?

    (1)你的str1恐怕不夠容納str2的內容,換句話說,你寫內存的時候越界了。

    (2)我用IDA看了下在函數的棧上的分布,大致情況如下:

      低地址:(棧頂部方向)

?     -14h j:???????? ....
      -10h i:?????? [0][0][0][0]
    ??? -0Ch str2:? [b][a][c][k][.][0][0][0]
    ??? -04h str1:? [g][o][?][0]
    ????? 00h????? :??被保護的寄存器值(如果有的話)
????????????         ebp
?????????????????? ???????????? 返回跳轉地址

      高地址:

    你附加字符的時候,把函數返回地址那里給覆蓋了。換句話說,這個函數的Stack Frame被你給破壞掉了。

?

    (3)假設這些代碼放在 main 函數里,用 IDA 運行這個程序,在函數返回前,可以手工把棧里的被破壞掉的ebp復原,但是返回地址好像沒法復原(沒法直接改棧上的數據),因為返回地址不對(返回地址最低位的字節被改成0)又跳回到 mainCRTStartup 里面的比較靠前的地方去了,正好跳到調用GetVersion的地方。然后過會又執行到調用 main 函數,然后又進入我在 main 函數里設的斷點位置(如果反復手工修改被破壞的ebp,就形成了一個死循環狀態)。

    如果不手工復原 ebp ,回到 mainCRTStartup 里面的時候,在會報一個內存不能寫的異常。。。(還好是在自己的進程空間里)因為ebp的原來的值是 mainCRTStartup 里的可能也是用于訪問棧的一個指針,總之在 mainCRTStartup 的開頭的地方有mov ebp, esp。

    這里需強調的是,這個代碼是可以通過 __chkesp 的檢測。因為 __chkesp 只檢測ebp的當前值(函數入口點的棧頂地址)和函數釋放棧上空間以后的esp是否一致。這個代碼不會影響到 ebp 當前值(函數入口點的棧頂),也不會沒有破壞 esp 當前值,而是破壞了 ebp 的原值(在上一個函數中的值)和返回地址。因此這個代碼屬于緩沖區溢出,__chkesp 對此情況無法檢測。

?

    (4)注:改正方法很簡單,第一行代碼改成例如 char str[32]="go?" 即可。該數字保證大于 str1+str2 的長度即可。

?

    =================================================================


    盡管這個問題應該說很容易解答,到這里也基本算可以了。不過我在 IDA 調試的時候發現編譯器附加的 __chkesp 函數對這個問題里的代碼是不起作用的,這引起了我的注意。通常人們不可能故意的讓自己的函數產生緩沖區溢出這樣的錯誤(本文的提問者是無意的),常見的比較底層的方法例如使用 FlushInstructionCache 修改函數入口地址來完成一些 hook。但是如果我們自己故意讓我們的代碼產生緩沖區溢出則另當別論了,所以我按照這個代碼的思路,可修改函數返回時跳轉的地址,讓函數返回時進入另一個函數,這也是比較有趣的一個事情。為了不能讓系統覺察到異常,必須再無痕跡跳轉回正確的位置,相當于我們自己hack自己了。

?

    下面我提供一個演示的代碼,首先簡單介紹以下原理,這里存在一些沒有保障的假設,例如在進入函數的時候,我們認為函數的棧是這樣的分布:

?

    ebp的原值(通常是上一個函數中的棧指針)

    函數返回跳轉地址(調用者中的某個地址)

    

    然后我修改函數(NormalFunc)的返回地址,讓他跳轉到另一個函數(Test2),注意這和常規的函數調用不同!如果這個函數有編譯器產生的開場白(prolog),必須手動先添加一個收場白(epilog)去抵消掉開場白的影響(稍后我再介紹這一點)。為了簡單起見,我使用 naked 關鍵字,要求編譯器不要添加開場白和收場白,這樣進入這個函數的時候可以直接去執行我們自己的代碼,執行完用戶代碼以后再跳回正確的地址(調用者 main 的內部)。這樣在系統不知道的情況下,我們就用“神不知鬼不覺的方式”“調用”了另一個函數(Test2)!

?

    下面是范例的代碼,使用VC6.0,Console Application:

?

?

code_buf_overflow // BufferOverflow.cpp : Defines the entry point for the console application.
//

#include
"stdafx.h"

//保存函數返回地址(跳回到main)
unsigned int retAddress;

void Test2();

void NormalFunc()
{
//data[1]: ebp的值;data[2]:函數返回地址
unsigned int data[1] = { 0x0 };
//保存返回地址
retAddress = data[2];
data[
2] = (unsigned int)Test2;
return;
}

//naked函數(手工指定prolog 和 epilog)
__declspec(naked) void Test2()
{
printf(
"Naked: hello world!\n");
//跳回到main函數體中!
__asm
{
jmp [retAddress]
}
}

int main(int argc, char* argv[])
{
NormalFunc();
printf(
"before exit\n");
return 0;
}

?

    這個函數產生下面的輸出,看起來就和調用了 Test2 一樣:

?

    Naked: hello world!
    before exit

?

    在 main 函數里本質上調用的是 NormalFunc 函數, 在這個函數里我修改了它返回時的跳轉地址,同時也把正確的返回時跳轉地址保存到了一個全局變量(retAddress)中。然后這個函數返回時進入了 Test2, 在 Test2 里執行了一些代碼以后,再通過全局變量跳回到 main 中的正確位置,這個過程對編譯器和系統來說是透明的。

?

    在 Test2 里我們使用 naked 關鍵字防止編譯器自動產生那些開場白和收場白。如果沒有加這個關鍵字,函數的開頭和結尾會有系統產生的那些開場白和收場白,因為我們并非常規的函數調用,可以理解為我們還位于原來的函數體中,所以在執行我們自己的代碼前,需要手工抵消掉函數的開場白(只需要把編譯器產生的收場白嵌入到函數的用戶代碼前面即可,為此,首先觀察編譯器產生的開場白和收場白(函數主體部分省略):

?

asm_test ;函數的開頭部分
.text:00401070 push ebp
.
text:00401071 mov ebp, esp
.
text:00401073 sub esp, 40h
.
text:00401076 push ebx
.
text:00401077 push esi
.
text:00401078 push edi
.
text:00401079 lea edi, [ebp+var_40]
.
text:0040107C mov ecx, 10h
.
text:00401081 mov eax, 0CCCCCCCCh
.
text:00401086 rep stosd??

;函數的結尾部分
.text:0040109B pop edi
.
text:0040109C pop esi
.
text:0040109D pop ebx
.
text:0040109E add esp, 40h
.
text:004010A1 cmp ebp, esp
.
text:004010A3 call __chkesp
.
text:004010A8 mov esp, ebp
.
text:004010AA pop ebp
.
text:004010AB retn

?

    現在我們寫一個普通的函數(不加 naked 關鍵字),我們在函數前面嵌入“收場白”的等效匯編代碼,則非 naked 的 Test2 函數的代碼如下,具體的開場白有可能會依賴編譯器,嵌入的收場白代碼怎樣寫,最好還是用反匯編查看一下再確定(本例使用的是VC6.0):

?

code_test2_normal void Test2()
{
//普通函數,我們必須手工抵消函數的開頭
__asm
{
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
}
//現在做一些事情
printf(
"hello world!\n");

//跳回到main函數體中!
__asm
{
jmp [retAddress]
}
}

?

    范例中的 Test2 函數很顯然是不能直接調用的,因為全局變量 retAddress 的初值是 0,直接調用會導致進程異常終止。但如果我們先調用 NormalFunc 是全局變量(retAddress)被賦正確的值?,Test2 就可以正常調用了,但是 Test2 返回時是從 NormalFunc 函數調用后面的語句繼續執行的,所以這樣會產生死循環。所以我們可以少許改造下 Test2,讓它最多被調用 5 次以后進程退出(否則因為死循環屏幕將一直輸出字符串)。改造后的代碼可以在屏幕上打印五行字符串內容:

?

code_buf_overflow_2 #include "stdafx.h"
#include
<stdlib.h>

//保存函數返回地址(跳回到main)
unsigned int retAddress;

void Test2();

void NormalFunc()
{
//data[1]: 可能是
unsigned int data[1] = { 0x0 };
//保存返回地址
retAddress = data[2];
data[
2] = (unsigned int)Test2;
return;
}


//naked函數(手工指定prolog 和 epilog)
__declspec(naked) void Test2()
{
static int i;
printf(
"Naked: hello world!\n");
i
++;
if(i == 5)
exit(
0
);

//跳回到main函數體中!
__asm
{
jmp [retAddress]
}
}

int main(int argc, char* argv[])
{
NormalFunc();
Test2();
printf(
"before exit\n");
return 0;
}

?

?

    --hoodlum1980

    --2010-9-20

轉載于:https://www.cnblogs.com/hoodlum1980/archive/2010/09/20/1832048.html

總結

以上是生活随笔為你收集整理的越过 __chkesp 检测的缓冲区溢出的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。