linux64位系統下程序調用順序

0x00
最近在學習緩衝區溢出,在開始階段,一直沒有弄懂相關資料中的溢出大小是怎麼計算出來的,在自己摸索下,終於找到了一些線索。爲了實現緩衝區攻擊,我們得先知道程序在內存中的調用順序,這樣才能清楚緩衝區溢出的空間大小。

0X01:基本知識

1、在操作系統中,地址有三種:邏輯地址、線性地址、物理地址。
邏輯地址:是指是包含在機器語言中用來指定 一個操作數或一條指令的地址;在採用分段機制的內存中,邏輯地址分爲段地址和偏移地址。
線性地址:也稱虛擬地址,它是邏輯地址經過分段系統進行運算得到的地址。
物理地址:是與內存單元相對應的地址,是線性地址經過分頁系統進行運算得到的地址。
三種地址之間的轉換
2、頁與頁框:頁對應的地址就是線性地址塊,頁框是指物理地址塊,頁的大小和頁框一樣,但是他們之間對應關係不是一一對應的,他們的對應方式採用頁表進行映射。
3、程序在運行之前在內存中是二進制文件,直到運行它,才從磁盤中調入內存中,創建進程。例如,我們編寫簡單的hello程序。
#include <stdio.h>
int main()
{
printf ("hello world!\n");
return 0;
}

使用gcc編譯,使用gcc -o hello hello.c編譯命令,將c程序文件編譯成爲可執行文件,使用ls hello -l命令可以查看可執行文件,file hello可以查看此文件是在linux-X86-64位系統下編譯的。Size hello命令是顯示文件的分區,在沒有調入內存運行之前,程序中分爲text(代碼區)、data(數據區)、bss(未初始化數據區);text區中的數據主要是代碼指令和局部變量,data區中的數據是存放全局變量、靜態變量和常量,bss區中存放的是全局但未初始化的變量。這三個區是在程序未運行時,經編譯生成的,大小不會改變。
hello文件靜態分區
在程序運行是,程序在內存中的分區會增加堆區、棧區和環境/參數節。
程序運行時
0x2:轉入正題
現在我們開始調試程序,一步步揭開程序在內存中的祕密。
現在的內存管理機制中,有了許多安全機制,其中的地址隨機我們得關掉,不然我們無法繼續學習。
我們先編寫程序。
#include <stdio.h>
int fun1(int a,int b)
{
int c = fun2(a, b);
return c;

}
int fun2(int a,int b)
{
int c = a+b;
return c;
}

int main ()
{
int a = 1;
int b = 3;
int c = fun1(a,b);
printf(“%d\n”,c);
return 0;
}`

使用gcc -o fun3 fun3.c編譯成功之後,使用gdb -q fun3加載調試文件,
分別使用disass main、disass fun1、disass fun2命令查看對應函數的彙編代碼。
這裏寫圖片描述
這裏寫圖片描述
這裏寫圖片描述
這些彙編代碼中,都分成三部分,函數開始(棧幀初始化)、函數執行(調用)、消除棧幀(返回父函數)。拿主函數作爲解釋。
push %rbp #將父函數的棧底值壓入棧中,rsp+8
mov %rsp %rbp #將rsp和rbp指向同一塊地址,開闢新的棧幀
sub 0x100x0000000000400552<+8>:movl 0x1,-0x4(%rbp) 0x0000000000400559 <+15>: movl $0x3,-0x8(%rbp)
0x0000000000400560 <+22>: mov -0x8(%rbp),%edx
0x0000000000400563 <+25>: mov -0x4(%rbp),%eax
0x0000000000400566 <+28>: mov %edx,%esi
這部分是將參數值壓入main棧幀中,並將值傳入寄存器中,這是x86的區別之一,參數的傳遞是通過寄存器來傳遞的。其中eax有存放函數返回值的功能。

接下來就是call指令,這時函數調用指令,對於我們明白程序的流轉至關重要。call指令主要完成兩個功能:
1、實現程序跳轉。
2、將call指令的下一條指令壓入棧中,rsp+8.這樣就能實現程序的正常返回。
所以我們看見call指令就需要注意它的下一條指令的地址。
這裏我們記住main_returnadd =0x000000000040056f。

第三部分是爲了實現返回父函數,並將rip的值賦值爲棧中的返回地址。
0x000000000040058b <+65>: leaveq
0x000000000040058c <+66>: retq

以上就是函數中的彙編介紹。下面我們就開始程序的調試。

我們在main函數的fun1函數call指令處下斷點,命令b *mian+32。
在fun1函數的fun2函數的call指令處下斷點,命令爲b *fun1+29.
在fun2函數中的pop指令前下斷點,命令爲:b *fun2+24這時整個函數棧幀的最頂端,再往下執行命令就開始銷燬棧。
這裏寫圖片描述
在重複一下,main_return = 0x000000000040056f,fun1_return = 0x0000000000400528 .
現在我們開始運行程序。命令爲r。
在main函數的斷點停下。我們需記下rsp,rbp的值,記錄下棧幀的開闢區域。使用i r rsp rbp命令。
這裏寫圖片描述
記錄下rsp=e5b0;rbp=e5c0;接下來就是call指令了,所以我們推測main_return = 0x000000000040056a存放在E5A8處。
繼續執行。輸出c命令。程序在fun1函數中停下,繼續查詢rsp、rbp的值。
這裏寫圖片描述
這裏我們看見rsp = E580。rbp = E5A0。rbp的值是在上一棧幀的基礎上rsp-8得到的。所以我們可以知道fun1函數劃分除了0x20字節的大小。接下來call指令存放在棧中的數據爲fun1_return = 0x0000000000400528存放在E578處。
繼續執行程序。

程序在fan2函數中的pop指令出。在fun2函數中,沒有調用其他函數,只是使用了寄存器計算。用eax寄存器將結果返回。
所以我們畫出程序的大致棧情況,還原程序的調用過程。
這裏寫圖片描述
現在我們使用x /88bx $rsp查看內存中的值。
這裏寫圖片描述
和我們分析的完全一致。

0x03總結
函數的調用流程總的來說不難,主要掌握了關鍵的彙編指令,以及64位系統中的地址大小,還有就是三種地址的區別,彙編語言中的地址,是指線性地址。而0x7fff開頭的地址是物理地址。還有就是棧是從高地址往低地址中寫內容,從這裏你也可以看出誰是線性地址,誰是物理地址。以上就是我在學習函數條用中的心得。

本人第一次寫博客,有不足還望大家多多指教,不勝感激。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章