天天看點

十三、MIPS彙編指令-main函數

十三、MIPS彙編指令-main函數

本次的分享,我們将以一段簡單的C語言程式為例,使用mips-linux的gcc編譯器對其進行編譯後,使用ida對編譯後的程式進行反彙編,并通過對照C語言代碼的方式了解MIPS彙編指令。

首先我們來看這段C代碼(test.c):

#include <stdio.h>
#include <string.h>

void B() {
        printf(".....\n");
}

void A(char *content) {
        char buf[32];
        strcpy(buf, content);
        printf("buf: %s\n", buf);
        B();
}

void main(int argc, char **argv) {
        A(argv[1]);
}
           

在代碼中,main函數直接調用A函數,并将第一個指令行參數作為函數A的參數傳遞過去,在函數A中,使用strcpy函數将參數儲存到局部變量buf中,然後是使用printf函數列印buf變量。最後函數A調用了函數B,在函數B中隻是簡單的調用了printf函數列印了一串字元串常量。

我們使用mips-linux-gnu-gcc指令将源碼編譯,得到名為test的可執行檔案:

編譯後,使用qemu-mips指令執行這個test程式:

$ cp /usr/bin/qemu-mips-static .
$ ./qemu-mips-static  ./test  AAAAAAAA
buf: AAAAAAAA
.....
           

可以看到,我們編譯的程式是可以正常運作的。

然後我們使用IDA保持一路預設打開這個test程式:

十三、MIPS彙編指令-main函數

在IDA視窗左側的Function window視窗中,我們可以看到,IDA識别出test中包含了main函數、A函數、B函數。

輕按兩下這個main函數,就可以開始進入彙編世界了:

十三、MIPS彙編指令-main函數

在main函數的起始處,有4個變量定義,分别是var_8、var_4、arg_0、arg_4。為了更便于了解,我在文稿中繪制了目前main函數的棧幀圖示:

十三、MIPS彙編指令-main函數

main函數實際上是由__libc_start_main函數調用起來的,是以在main函數被執行後,main函數首先要為自己配置設定函數棧空間,對應的彙編指令就是0x00400970位址處的addiu s p , − 0 x 20 指 令 。 a d d 是 加 法 , i 代 表 立 即 數 , u 代 表 無 符 号 數 。 而 sp, -0x20指令。add是加法,i代表立即數,u代表無符号數。而 sp,−0x20指令。add是加法,i代表立即數,u代表無符号數。而sp就是棧頂指針,通過将棧頂指針向下移動0x20個位元組,這0x20個位元組就是main函數的函數棧空間,對應圖中的綠色部分。

十三、MIPS彙編指令-main函數

為了保證在main函數傳回後,程式流程繼續回到__lib_start_main函數中繼續執行,需要将__lib_start_main函數中調用main函數完成後的下一條指令的位址儲存到棧空間,也就是将RA寄存器的值存儲到main函數的函數棧中,對應的彙編指令為:sw r a , 0 x 20 + v a r 4 ( ra, 0x20+var_4( ra,0x20+var4​(sp),sw指令是将ra寄存器的值存儲到$sp+0x20-4位址處,這個位址就是變量var_4所在的記憶體。

類似的,彙編代碼中也将fp寄存器做了同樣的處理,關于這個fp寄存器,為了不與棧頂指針sp混淆,我們目前可以不必關心。

十三、MIPS彙編指令-main函數

緊接着在彙編代碼的0x00400980位址處,分别使用sw指令将a0、a1寄存器的值存儲到了__lib_start_main函數的參數調用空間中,也就是上面圖檔中的arg_0、arg_4。a0和a1分别代表了main函數的兩個參數,也就是argc和argv。其中arg_4代表了前面使用指令行執行test程式時傳遞的指令函參數,是一個字元串數組。

十三、MIPS彙編指令-main函數

緊接着,使用lw指令将arg_4參數取出,并複制給v0寄存器。并在v0寄存器指向的main函數指令行參數數組中取出argv[1],也就是“AAAAAAAA”,然後将這個“AAAAAAAA”再次複制給v0寄存器,并最終指派給a0寄存器:

十三、MIPS彙編指令-main函數

上一篇的分享中,我們提到過,在MIPS架構程式做函數調用時,會将前4個參數分别使用a0~a3寄存器進行儲存,是以,代碼分析到這裡,我們已經能夠發現,為了調用函數A,彙編代碼為此準備好了第一個參數,也就是a0寄存器,它對應了字元串“AAAAAAAA”。

然後在main函數中,使用了jal指令将程式流程跳轉到A函數執行:

十三、MIPS彙編指令-main函數

這裡我們應該知道的一個關鍵點是,使用jal指令進行函數跳轉時,會隐式的将調用函數A完成後的下一條指令的位址儲存到RA寄存器中。是以RA寄存器在這裡被覆寫了,是以,在main函數的末尾處應該從main函數的中空間中将RA寄存器的值進行恢複。

我們假設函數A已經執行完成,在函數A的尾部會通過RA寄存器,将程式的執行流程跳轉回main函數,也就是main函數中的jal指令後面的位址處:

十三、MIPS彙編指令-main函數

然後,main函數的結尾處幾行彙編代碼會将 s p 、 sp、 sp、ra、$fp寄存器從main函數的棧空間中進行值恢複,然後使用jr指令,将程式的執行流程傳回到__libc_start_main函數中執行。注意,這裡使用的jr指令并不會改變RA寄存器的值,jal指令才會改變RA寄存器的值。

我們通過今天分享,通過将我們自己編寫的C代碼編譯為mips-linux架構的程式,并使用IDA對其進行逆向分析,初探了MIPS彙編代碼。對于繁雜的彙編代碼,看似要記住的指令有很多,但它們不過是一些英文單詞的縮寫組合而成的。比如add代表加法、i代表Immediat,就是立即數、u代表unsigned,就是無符号、lw中的l代表load,就是加載的意思,w代表word,就是4個位元組的意思、jal指令中的j代表jump,就是跳轉的意思、a代表address,就是跳轉到某個位址的意思、l代表long,就是跳轉的位址距離jal這條指令很遠的意思(這個l代表long純屬亂猜,隻是為了友善自己記憶)。

希望本節的分析能夠為你打開進入MIPS彙編世界的大門。下次分享中,我們會繼續通過分析main函數調用的A函數的反彙編代碼,以此來增強我們對彙編語言的了解。

最後希望本次的分享能夠為你帶來幫助,謝謝大家。

繼續閱讀