• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            Shuffy

            不斷的學習,不斷的思考,才能不斷的進步.Let's do better together!
            posts - 102, comments - 43, trackbacks - 0, articles - 19

            windows進程中的內存結構

            Posted on 2007-04-13 22:49 Shuffy 閱讀(131) 評論(0)  編輯 收藏 引用 所屬分類: VC++/C/C++/C#瀏覽集合

            信息來源:中國源碼下載站

            接觸過編程的人都知道,高級語言都能通過變量名來訪問內存中的數據。那么這些變量在內存中是如何存放的呢?程序又是如何使用這些變量的呢?下面就會對此進行深入的討論。下文中的C語言代碼如沒有特別聲明,默認都使用VC編譯的release版。 
            首先,來了解一下 C 語言的變量是如何在內存分部的。C 語言有全局變量(Global)、本地變量(Local),靜態變量(Static)、寄存器變量(Regeister)。每種變量都有不同的分配方式。先來看下面這段代碼: 
            #include <stdio.h> 
            int g1=0, g2=0, g3=0; 
            int main() 

            static int s1=0, s2=0, s3=0; 
            int v1=0, v2=0, v3=0; 
            //打印出各個變量的內存地址 
            printf("0x%08x\n",&v1); //打印各本地變量的內存地址 
            printf("0x%08x\n",&v2); 
            printf("0x%08x\n\n",&v3); 
            printf("0x%08x\n",&g1); //打印各全局變量的內存地址 
            printf("0x%08x\n",&g2); 
            printf("0x%08x\n\n",&g3); 
            printf("0x%08x\n",&s1); //打印各靜態變量的內存地址 
            printf("0x%08x\n",&s2); 
            printf("0x%08x\n\n",&s3); 
            return 0; 

            編譯后的執行結果是: 
            0x0012ff78 
            0x0012ff7c 
            0x0012ff80 
            0x004068d0 
            0x004068d4 
            0x004068d8 
            0x004068dc 
            0x004068e0 
            0x004068e4 
            輸出的結果就是變量的內存地址。其中v1,v2,v3是本地變量,g1,g2,g3是全局變量,s1,s2,s3是靜態變量。你可以看到這些變量在內存是連續分布的,但是本地變量和全局變量分配的內存地址差了十萬八千里,而全局變量和靜態變量分配的內存是連續的。這是因為本地變量和全局/靜態變量是分配在不同類型的內存區域中的結果。對于一個進程的內存空間而言,可以在邏輯上分成3個部份:代碼區,靜態數據區和動態數據區。動態數據區一般就是“堆棧”。“棧(stack)”和“堆(heap)”是兩種不同的動態數據區,棧是一種線性結構,堆是一種鏈式結構。進程的每個線程都有私有的“棧”,所以每個線程雖然代碼一樣,但本地變量的數據都是互不干擾。一個堆棧可以通過“基地址”和“棧頂”地址來描述。全局變量和靜態變量分配在靜態數據區,本地變量分配在動態數據區,即堆棧中。程序通過堆棧的基地址和偏移量來訪問本地變量。 

            ├———————┤低端內存區域 
            │ …… │ 
            ├———————┤ 
            │ 動態數據區 │ 
            ├———————┤ 
            │ …… │ 
            ├———————┤ 
            │ 代碼區 │ 
            ├———————┤ 
            │ 靜態數據區 │ 
            ├———————┤ 
            │ …… │ 
            ├———————┤高端內存區域 

            堆棧是一個先進后出的數據結構,棧頂地址總是小于等于棧的基地址。我們可以先了解一下函數調用的過程,以便對堆棧在程序中的作用有更深入的了解。不同的語言有不同的函數調用規定,這些因素有參數的壓入規則和堆棧的平衡。windows API的調用規則和ANSI C的函數調用規則是不一樣的,前者由被調函數調整堆棧,后者由調用者調整堆棧。兩者通過“__stdcall”和“__cdecl”前綴區分。先看下面這段代碼: 
            #include <stdio.h> 
            void __stdcall func(int param1,int param2,int param3) 

            int var1=param1; 
            int var2=param2; 
            int var3=param3; 
            printf("0x%08x\n",&para;m1); //打印出各個變量的內存地址 
            printf("0x%08x\n",&para;m2); 
            printf("0x%08x\n\n",&para;m3); 
            printf("0x%08x\n",&var1); 
            printf("0x%08x\n",&var2); 
            printf("0x%08x\n\n",&var3); 
            return; 

            int main() 

            func(1,2,3); 
            return 0; 

            編譯后的執行結果是: 
            0x0012ff78 
            0x0012ff7c 
            0x0012ff80 
            0x0012ff68 
            0x0012ff6c 
            0x0012ff70 

            ├———————┤<—函數執行時的棧頂(ESP)、低端內存區域 
            │ …… │ 
            ├———————┤ 
            │ var 1 │ 
            ├———————┤ 
            │ var 2 │ 
            ├———————┤ 
            │ var 3 │ 
            ├———————┤ 
            │ RET │ 
            ├———————┤<—“__cdecl”函數返回后的棧頂(ESP) 
            │ parameter 1 │ 
            ├———————┤ 
            │ parameter 2 │ 
            ├———————┤ 
            │ parameter 3 │ 
            ├———————┤<—“__stdcall”函數返回后的棧頂(ESP) 
            │ …… │ 
            ├———————┤<—棧底(基地址 EBP)、高端內存區域 

            上圖就是函數調用過程中堆棧的樣子了。首先,三個參數以從又到左的次序壓入堆棧,先壓“param3”,再壓“param2”,最后壓入“param1”;然后壓入函數的返回地址(RET),接著跳轉到函數地址接著執行(這里要補充一點,介紹UNIX下的緩沖溢出原理的文章中都提到在壓入RET后,繼續壓入當前EBP,然后用當前ESP代替EBP。然而,有一篇介紹windows下函數調用的文章中說,在windows下的函數調用也有這一步驟,但根據我的實際調試,并未發現這一步,這還可以從param3和var1之間只有4字節的間隙這點看出來);第三步,將棧頂(ESP)減去一個數,為本地變量分配內存空間,上例中是減去12字節(ESP=ESP-3*4,每個int變量占用4個字節);接著就初始化本地變量的內存空間。由于“__stdcall”調用由被調函數調整堆棧,所以在函數返回前要恢復堆棧,先回收本地變量占用的內存(ESP=ESP+3*4),然后取出返回地址,填入EIP寄存器,回收先前壓入參數占用的內存(ESP=ESP+3*4),繼續執行調用者的代碼。參見下列匯編代碼: 
            ;--------------func 函數的匯編代碼------------------- 
            :00401000 83EC0C sub esp, 0000000C //創建本地變量的內存空間 
            :00401003 8B442410 mov eax, dword ptr [esp+10] 
            :00401007 8B4C2414 mov ecx, dword ptr [esp+14] 
            :0040100B 8B542418 mov edx, dword ptr [esp+18] 
            :0040100F 89442400 mov dword ptr [esp], eax 
            :00401013 8D442410 lea eax, dword ptr [esp+10] 
            :00401017 894C2404 mov dword ptr [esp+04], ecx 
            ……………………(省略若干代碼) 
            :00401075 83C43C add esp, 0000003C ;恢復堆棧,回收本地變量的內存空間 
            :00401078 C3 ret 000C ;函數返回,恢復參數占用的內存空間 
            ;如果是“__cdecl”的話,這里是“ret”,堆棧將由調用者恢復 
            ;-------------------函數結束------------------------- 

            ;--------------主程序調用func函數的代碼-------------- 
            :00401080 6A03 push 00000003 //壓入參數param3 
            :00401082 6A02 push 00000002 //壓入參數param2 
            :00401084 6A01 push 00000001 //壓入參數param1 
            :00401086 E875FFFFFF call 00401000 //調用func函數 
            ;如果是“__cdecl”的話,將在這里恢復堆棧,“add esp, 0000000C” 
            聰明的讀者看到這里,差不多就明白緩沖溢出的原理了。先來看下面的代碼: 
            #include <stdio.h> 
            #include <string.h> 
            void __stdcall func() 

            char lpBuff[8]="\0"; 
            strcat(lpBuff,"AAAAAAAAAAA"); 
            return; 

            int main() 

            func(); 
            return 0; 

            編譯后執行一下回怎么樣?哈,“"0x00414141"指令引用的"0x00000000"內存。該內存不能為"read"。”,“非法操作”嘍!"41"就是"A"的16進制的ASCII碼了,那明顯就是strcat這句出的問題了。"lpBuff"的大小只有8字節,算進結尾的’\0’,那strcat最多只能寫入7個"A",但程序實際寫入了11個"A"外加1個’\0’。再來看看上面那幅圖,多出來的4個字節正好覆蓋了RET的所在的內存空間,導致函數返回到一個錯誤的內存地址,執行了錯誤的指令。如果能精心構造這個字符串,使它分成三部分,前一部份僅僅是填充的無意義數據以達到溢出的目的,接著是一個覆蓋RET的數據,緊接著是一段shellcode,那只要著個RET地址能指向這段shellcode的第一個指令,那函數返回時就能執行shellcode了。但是軟件的不同版本和不同的運行環境都可能影響這段shellcode在內存中的位置,那么要構造這個RET是十分困難的。一般都在RET和shellcode之間填充大量的NOP指令,使得exploit有更強的通用性。 

            ├———————┤<—低端內存區域 
            │ …… │ 
            ├———————┤<—由exploit填入數據的開始 
            │ │ 
            │ buffer │<—填入無用的數據 
            │ │ 
            ├———————┤ 
            │ RET │<—指向shellcode,或NOP指令的范圍 
            ├———————┤ 
            │ NOP │ 
            │ …… │<—填入的NOP指令,是RET可指向的范圍 
            │ NOP │ 
            ├———————┤ 
            │ │ 
            │ shellcode │ 
            │ │ 
            ├———————┤<—由exploit填入數據的結束 
            │ …… │ 
            ├———————┤<—高端內存區域

            国产产无码乱码精品久久鸭| 人妻少妇久久中文字幕一区二区| 国产精品久久久久久久| 久久人人爽人人爽人人片AV麻豆| 无码人妻精品一区二区三区久久久 | 亚洲国产香蕉人人爽成AV片久久| 亚洲va久久久噜噜噜久久天堂| 色欲综合久久中文字幕网| 99久久综合狠狠综合久久止| 欧洲性大片xxxxx久久久| 亚洲欧美日韩精品久久| 久久九九精品99国产精品| 欧美黑人激情性久久| 国内精品欧美久久精品| 丁香五月综合久久激情| 国产精品美女久久久久av爽 | 色狠狠久久AV五月综合| 精品人妻伦九区久久AAA片69| 一本色道久久综合亚洲精品| yy6080久久| 亚洲精品乱码久久久久久按摩| 99久久免费国产精品| 久久夜色精品国产噜噜麻豆| 久久久亚洲精品蜜桃臀| 99久久精品国产高清一区二区| 久久笫一福利免费导航 | 丁香五月网久久综合| 思思久久精品在热线热| 久久精品国产精品亚洲| 久久婷婷五月综合成人D啪| aaa级精品久久久国产片| 香蕉久久av一区二区三区| 天堂无码久久综合东京热| 久久精品免费网站网| 久久91精品国产91久久小草| 国内精品久久久久久久亚洲| 成人久久精品一区二区三区| AAA级久久久精品无码片| 久久精品国产99久久无毒不卡| 日产精品久久久久久久| 国产产无码乱码精品久久鸭|