MIPS匯編學習


MIPS匯編學習

  mips匯編不同於x86匯編,屬於精簡指令集,常見於路由器等一些嵌入式設備中。

  mips匯編沒有對堆棧的直接操作,也就是沒有push和pop指令,mips匯編中保留了32個通用寄存器,但是不同於x86匯編,mips匯編中沒有ebp/rbp寄存器。

  mips每條指令都用固定的長度,每條指令都是四個字節,所以內存數據的訪問必須以32位嚴格對齊,這一點也不同於x86匯編。

  通過一個demo,用mips-linux-gnu-gcc編譯,通過IDA遠程調試,來理解mips匯編中的一些概念。

#include<stdio.h> int sum(int a,int b){ return a+b; } int main() { int a=1,b=2,c;     c=sum(a,b);     printf("%d\n",c);  return 0; }

32個通用寄存器的功能和使用約定定義如下:

mips匯編中重要的寄存器:

   1.堆棧指針$sp,也就是$29指向堆棧的棧頂,類似於x86中的ebp和rbp指針;

  2.$0寄存器的值始終為常數0;

  3.PC寄存器保留程序執行的下一條指令,相當於x86架構中的eip寄存器;

  4.參數傳遞的時候,$a0-$a3寄存器保存函數的前四個參數,其他的參數保存在棧中;

  5.$ra寄存器,保存着函數的返回地址,這一點也不同於x86匯編中將返回地址保存在棧中。在函數A執行到調用函數B的指令時,函數調用指令復制當前的$PC寄存器的值到$RA寄存器,然后跳轉到B函數去執行,即當前$RA寄存器的值就是函數執行結束時的返回地址。

  如上圖所示,調用sum函數之前,$ra寄存器的值是0x7f62eca8。

  進入分支延遲槽之后,$ra寄存器的值被賦值為$pc寄存器的下一條指令地址。在結束sun函數調用之后,通過:jr  $ra指令跳轉回main函數繼續執行。

  5.mips架構下,對靜態數據段的訪問,通過$gp寄存器配合基址尋址來實現;

  7.$30寄存器表示幀指針,指向正在被調用的棧楨,mips和x86由於堆棧結構的區別,調用棧時會出現一些不同。mips硬件並不直接支持堆棧,x86有單獨的push和pop指令,但是mips沒有棧操作指令,所有對棧的操作都是統一的內存訪問方式。

x86中,棧楨入口點開辟棧楨的操作:

push ebp
mov ebp,esp
sub esp,0x30

x86中,棧楨出口點退棧的操作:

leave    #  push ebp
         #  mov ebp,esp
ret      #  pop eip

  由以上函數入口點和出口點的操作我們可以清晰地看出,x86架構中,同一個函數中,esp始終指向函數調用棧的棧頂,ebp始終指向函數調用棧的棧基。

  但是在mips架構下,沒有指向棧基的寄存器,這時候如何確定函數調用的棧楨呢?

  $fp(幀指針)和  $sp(棧指針)  來確定函數的調用棧。$sp寄存器作為堆棧寄存器,始終指向棧頂。進入一個函數是,需要將當前棧指針向下移動n字節,這個大小為n字節的存儲空間就是此函數的Stack Frame的存儲區域,此后棧指針便不再移動,只通過函數返回是將棧指針加上偏移量恢復棧現場。由於不能隨便移動棧指針,所以寄存器壓棧和出棧時都要指定偏移量。

         可以看到,mips架構中,在函數入口處以addiu   $sp,-0x30來開辟棧楨,當程序運行到0x400770地址處時,$fp寄存器的值被保留到了棧上,$fp寄存器的值為0。

   繼續單步執行,看到將$sp寄存器的值賦值給了$fp寄存器,這時候堆棧寄存器和幀指針同時指向當前調用棧的棧頂。

   繼續單步執行,0x40079c地址處,我們在sum函數的入口處下一個斷點,由於mips架構的分支延遲機制,nop指令就是一個分支延遲槽。執行完nop指令之后,接下來我們會步進到sum函數中,進入sum函數之后,我們再來觀察$sp寄存器和$fp寄存器的變化情況。

   可以看到,addiu指令再次開辟了8字節大小的棧楨。

   單步執行到0x40073c地址處,$fp寄存器的值在賦值前被保留在棧上,$fp寄存器被再次賦值,指向當前調用棧的棧頂。

   結束函數調用之后,$fp和$sp還原為指向main函數調用棧的棧頂。可以看到,$fp寄存器主要用來進行基址尋址。所有針對棧區變量的取址,都通過基址尋址來對內存進行訪問。

mips匯編函數調用過程中與x86架構的區別:

  1. mips架構和x86架構中,棧的增長方向相同,都是從高地址向低地址增長,但是沒有棧底指針,所以調用一個函數是,需要將當前棧向低地址處移動n比特這個大小為n比特的空間就是此函數的棧楨存儲區域;
  2. mips架構中有葉子函數和非葉子函數的區別,葉子函數就是此函數自身不再調用別的函數,非葉子函數就是此函數自身調用別的函數。如果函數A調用函數B,調用者函數會在自己的棧頂預留一部分空間來保存被調用者(函數B)的參數,稱之為參數調用空間;
  3. 函數調用過程中,父函數調用子函數,復制當前$PC的值到$RA寄存器,然后跳轉到子函數執行。到子函數是,子函數如果為非葉子函數,則子函數的返回地址會先存入堆棧
  4. 參數傳遞方式,前四個參數通過$a0-$a3來傳遞,多於的參數會放入調用參數空間(參數會被保存在棧上),可以類比x86_64參數傳遞規則來進行記憶。

mips架構的四種取址方式:

  1.基址尋址;(load-store)

    根據我們上面的例子可以看到,基址尋址是對mips架構下堆棧數據進行存儲和加載的主要方式。

  2.立即數尋址;(load-store)

  3.寄存器尋址;(R型指令)

  4.偽立即數尋址;(J型指令)

葉子函數和非葉子函數:

  一個函數如果不再調用其他的函數,那么這個函數是葉子函數,一個函數如果調用其他的函數,那么這個函數是非葉子函數。一般來說,函數都是非葉子函數。

  葉子函數和非葉子函數在存放返回地址的時候,存在差異。葉子函數只把返回地址保存在$ra寄存其中,結束函數調用的時候,通過jr $ra指令返回即可。非葉子函數把在函數調用初始把$ra寄存器中的返回地址保存在棧中,然后結束函數調用的時候將棧中保存的返回地址加載到$ra寄存器中,再通過jr $ra指令返回。

  舉例如下:

  葉子函數函數入口:

   非葉子函數函數入口:

   葉子函數函數出口:

   非葉子函數函數出口:

   葉子函數和非葉子函數的差別,造成棧溢出漏洞利用的差別。對於非葉子函數而言,如果我們的溢出可以覆蓋棧上保存的$ra寄存器的值,這時候在棧上的值返回給$ra寄存器的時候,我們就可以劫持程序的數據流。

   通過《揭秘家用路由器0day漏洞挖掘技術》這本書中的一個例子來展示MIPS32架構下函數的參數傳遞及堆棧布局的變化:

#include<stdio.h>

int more_reg(int a,int b,int c,int d,int e) { char dst[100]={0}; sprintf(dst,"%d%d%d%d%d\n",a,b,c,d,e); } int main() { int a1=1,a2=2,a3=3,a4=4,a5=5; more_reg(a1,a2,a3,a4,a5); return 0; }

  靜態鏈接,編譯選項:

mips-linux-gnu-gcc -o demo1 demo1.c -static

   由上圖看到,函數傳遞了5個參數,前四個參數首先保存在棧上,然后在調用more_reg函數的時候,把棧區的變量傳遞到$a0-$a3這四個寄存器中。其中第五個參數保留在0x40+var_30($sp)這個地址處。可以看到,雖然函數more_reg的前四個參數是由$a0-$a3這四個寄存器傳遞的,但是棧區仍然保留了16個字節的參數空間,就是0x40+var_20($fp)到0x40+var_30($fp)這段空間。

  斷點0x400660處,就是將變量值從臨時變量$v0中取出,存儲到0x40+var_30($fp)處,然后0x40+var_30($fp)作為第五個參數傳遞到more_reg函數中去。

MIPS系統調用

  mips架構中,syscall用於從內核請求服務。對於MIPS,必須在$v0中傳遞服務編號/代號,然后將參數賦值給$a0,$a1,$a2三個,然后使用syscall指令觸發中斷,來調用相應函數。

  如果linux中搭建了mips的交叉編譯環境的話,mips的系統調用號可以在/usr/mips-linux-gnu/include/asm/unistd.h中看到,調用號是從4000開始的。

#define __NR_Linux            4000
#define __NR_syscall            (__NR_Linux +    0)
#define __NR_exit            (__NR_Linux +    1)
#define __NR_fork            (__NR_Linux +    2)
#define __NR_read            (__NR_Linux +    3)
#define __NR_write            (__NR_Linux +    4)
#define __NR_open            (__NR_Linux +    5)
#define __NR_close            (__NR_Linux +    6)
#define __NR_waitpid            (__NR_Linux +    7)
#define __NR_creat            (__NR_Linux +    8)
#define __NR_link            (__NR_Linux +    9)
#define __NR_unlink            (__NR_Linux +  10)
#define __NR_execve            (__NR_Linux +  11)
#define __NR_chdir            (__NR_Linux +  12)
#define __NR_time            (__NR_Linux +  13)
#define __NR_mknod            (__NR_Linux +  14)
#define __NR_chmod            (__NR_Linux +  15)
#define __NR_lchown            (__NR_Linux +  16)
#define __NR_break            (__NR_Linux +  17)
#define __NR_unused18            (__NR_Linux +  18)
#define __NR_lseek            (__NR_Linux +  19)
#define __NR_getpid            (__NR_Linux +  20)
#define __NR_mount            (__NR_Linux +  21)
#define __NR_umount            (__NR_Linux +  22)
#define __NR_setuid            (__NR_Linux +  23)
#define __NR_getuid            (__NR_Linux +  24)
#define __NR_stime            (__NR_Linux +  25)
#define __NR_ptrace            (__NR_Linux +  26)
#define __NR_alarm            (__NR_Linux +  27)
#define __NR_unused28            (__NR_Linux +  28)
#define __NR_pause            (__NR_Linux +  29)
#define __NR_utime            (__NR_Linux +  30)
#define __NR_stty            (__NR_Linux +  31)
#define __NR_gtty            (__NR_Linux +  32)
#define __NR_access            (__NR_Linux +  33)
#define __NR_nice            (__NR_Linux +  34)
#define __NR_ftime            (__NR_Linux +  35)
#define __NR_sync            (__NR_Linux +  36)
#define __NR_kill            (__NR_Linux +  37)
#define __NR_rename            (__NR_Linux +  38)
#define __NR_mkdir            (__NR_Linux +  39)
#define __NR_rmdir            (__NR_Linux +  40)
#define __NR_dup            (__NR_Linux +  41)
#define __NR_pipe            (__NR_Linux +  42)
#define __NR_times            (__NR_Linux +  43)
#define __NR_prof            (__NR_Linux +  44)
#define __NR_brk            (__NR_Linux +  45)
#define __NR_setgid            (__NR_Linux +  46)
#define __NR_getgid            (__NR_Linux +  47)
#define __NR_signal            (__NR_Linux +  48)
#define __NR_geteuid            (__NR_Linux +  49)
#define __NR_getegid            (__NR_Linux +  50)
#define __NR_acct            (__NR_Linux +  51)
#define __NR_umount2            (__NR_Linux +  52)
#define __NR_lock            (__NR_Linux +  53)
#define __NR_ioctl            (__NR_Linux +  54)
#define __NR_fcntl            (__NR_Linux +  55)
#define __NR_mpx            (__NR_Linux +  56)
#define __NR_setpgid            (__NR_Linux +  57)
#define __NR_ulimit            (__NR_Linux +  58)
#define __NR_unused59            (__NR_Linux +  59)
#define __NR_umask            (__NR_Linux +  60)
#define __NR_chroot            (__NR_Linux +  61)
#define __NR_ustat            (__NR_Linux +  62)
#define __NR_dup2            (__NR_Linux +  63)
#define __NR_getppid            (__NR_Linux +  64)
#define __NR_getpgrp            (__NR_Linux +  65)
#define __NR_setsid            (__NR_Linux +  66)
#define __NR_sigaction            (__NR_Linux +  67)
#define __NR_sgetmask            (__NR_Linux +  68)
#define __NR_ssetmask            (__NR_Linux +  69)
#define __NR_setreuid            (__NR_Linux +  70)
#define __NR_setregid            (__NR_Linux +  71)
#define __NR_sigsuspend            (__NR_Linux +  72)
#define __NR_sigpending            (__NR_Linux +  73)
#define __NR_sethostname        (__NR_Linux +  74)
#define __NR_setrlimit            (__NR_Linux +  75)
#define __NR_getrlimit            (__NR_Linux +  76)
#define __NR_getrusage            (__NR_Linux +  77)
#define __NR_gettimeofday        (__NR_Linux +  78)
#define __NR_settimeofday        (__NR_Linux +  79)
#define __NR_getgroups            (__NR_Linux +  80)
#define __NR_setgroups            (__NR_Linux +  81)
#define __NR_reserved82            (__NR_Linux +  82)
#define __NR_symlink            (__NR_Linux +  83)
#define __NR_unused84            (__NR_Linux +  84)
#define __NR_readlink            (__NR_Linux +  85)
#define __NR_uselib            (__NR_Linux +  86)
#define __NR_swapon            (__NR_Linux +  87)
#define __NR_reboot            (__NR_Linux +  88)
#define __NR_readdir            (__NR_Linux +  89)
#define __NR_mmap            (__NR_Linux +  90)
#define __NR_munmap            (__NR_Linux +  91)
#define __NR_truncate            (__NR_Linux +  92)
#define __NR_ftruncate            (__NR_Linux +  93)
#define __NR_fchmod            (__NR_Linux +  94)
#define __NR_fchown            (__NR_Linux +  95)
#define __NR_getpriority        (__NR_Linux +  96)
#define __NR_setpriority        (__NR_Linux +  97)
#define __NR_profil            (__NR_Linux +  98)
#define __NR_statfs            (__NR_Linux +  99)
#define __NR_fstatfs            (__NR_Linux + 100)
#define __NR_ioperm            (__NR_Linux + 101)
#define __NR_socketcall            (__NR_Linux + 102)
#define __NR_syslog            (__NR_Linux + 103)
#define __NR_setitimer            (__NR_Linux + 104)
#define __NR_getitimer            (__NR_Linux + 105)
#define __NR_stat            (__NR_Linux + 106)
#define __NR_lstat            (__NR_Linux + 107)
#define __NR_fstat            (__NR_Linux + 108)
#define __NR_unused109            (__NR_Linux + 109)
#define __NR_iopl            (__NR_Linux + 110)
#define __NR_vhangup            (__NR_Linux + 111)
#define __NR_idle            (__NR_Linux + 112)
#define __NR_vm86            (__NR_Linux + 113)
#define __NR_wait4            (__NR_Linux + 114)
#define __NR_swapoff            (__NR_Linux + 115)
#define __NR_sysinfo            (__NR_Linux + 116)
#define __NR_ipc            (__NR_Linux + 117)
#define __NR_fsync            (__NR_Linux + 118)
#define __NR_sigreturn            (__NR_Linux + 119)
#define __NR_clone            (__NR_Linux + 120)
#define __NR_setdomainname        (__NR_Linux + 121)
#define __NR_uname            (__NR_Linux + 122)
#define __NR_modify_ldt            (__NR_Linux + 123)
#define __NR_adjtimex            (__NR_Linux + 124)
#define __NR_mprotect            (__NR_Linux + 125)
#define __NR_sigprocmask        (__NR_Linux + 126)
#define __NR_create_module        (__NR_Linux + 127)
#define __NR_init_module        (__NR_Linux + 128)
#define __NR_delete_module        (__NR_Linux + 129)
#define __NR_get_kernel_syms        (__NR_Linux + 130)
#define __NR_quotactl            (__NR_Linux + 131)
#define __NR_getpgid            (__NR_Linux + 132)
#define __NR_fchdir            (__NR_Linux + 133)
#define __NR_bdflush            (__NR_Linux + 134)
#define __NR_sysfs            (__NR_Linux + 135)
#define __NR_personality        (__NR_Linux + 136)
#define __NR_afs_syscall        (__NR_Linux + 137) /* Syscall for Andrew File System */

  可以寫一個write.c的,然后生成匯編代碼,看一下write函數的系統調用。

#include<stdio.h> #include<stdlib.h>

int main() { char *dst="Hello world!\n"; write(1,dst,13); return 0; }

  以下命令編譯:

 mips-linux-gnu-gcc write_syscall.c -S -o write_syscall.s

  生成匯編代碼如下所示:

    .file    1 "write_syscall.c"
    .section .mdebug.abi32
    .previous
    .nan    legacy
    .module    fp=xx
    .module    nooddspreg
    .abicalls
    .text
    .rdata
    .align    2
$LC0:
    .ascii    "Hello world!\012\000"
    .text
    .align    2
    .globl    main
    .set    nomips16
    .set    nomicromips
    .ent    main
    .type    main, @function
main:
    .frame    $fp,40,$31        # vars= 8, regs= 2/0, args= 16, gp= 8
    .mask    0xc0000000,-4
    .fmask    0x00000000,0
    .set    noreorder
    .set    nomacro
    addiu    $sp,$sp,-40
    sw    $31,36($sp)
    sw    $fp,32($sp)
    move    $fp,$sp
    lui    $28,%hi(__gnu_local_gp)
    addiu    $28,$28,%lo(__gnu_local_gp)
    .cprestore    16
    lui    $2,%hi($LC0)
    addiu    $2,$2,%lo($LC0)
    sw    $2,28($fp)
    li    $6,13            # 0xd   $a2寄存器
    lw    $5,28($fp)    # $a1 寄存器
    li    $4,1            # 0x1  $a0寄存器
    lw    $2,%call16(write)($28)  # $v0寄存器
    move    $25,$2  
    .reloc    1f,R_MIPS_JALR,write   
1:    jalr    $25
    nop

    lw    $28,16($fp)
    move    $2,$0
    move    $sp,$fp
    lw    $31,36($sp)
    lw    $fp,32($sp)
    addiu    $sp,$sp,40
    jr    $31
    nop

    .set    macro
    .set    reorder
    .end    main
    .size    main, .-main
    .ident    "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"

  簡化之后,write.s的系統調用可以寫為如下形式:

.section .text .globl __start .set noreorder __start: addiu $sp,$sp,-32 lui $t6,0x4142 ori $t6,$t6,0x430a sw $t6,0($sp) li $a0,1 addiu $a1,$sp,0 li $a2,5 li $v0,4004 syscall

  shell腳本編譯為二進制可執行文件:

#!/bin/sh # $ sh name.sh <source file><excute file> src=$1 dst=$2 mips-linux-gnu-as $src -o s.o mips-linux-gnu-ld s.o -o $dst rm s.o

  readelf -S write定位text段入口地址:

pwndbg> disass /r 0x4000d0 Dump of assembler code for function _ftext: 0x004000d0 <+0>:    27 bd ff e0    addiu    sp,sp,-32
   0x004000d4 <+4>:    3c 0e 41 42    lui    t6,0x4142
   0x004000d8 <+8>:    35 ce 43 0a    ori    t6,t6,0x430a
   0x004000dc <+12>:    af ae 00 00    sw    t6,0(sp) 0x004000e0 <+16>:    24 04 00 01    li    a0,1
   0x004000e4 <+20>:    27 a5 00 00    addiu    a1,sp,0
   0x004000e8 <+24>:    24 06 00 05    li    a2,5
   0x004000ec <+28>:    24 02 0f a4    li    v0,4004
   0x004000f0 <+32>:    00 00 00 0c syscall 0x004000f4 <+36>:    00 00 00 00 nop 0x004000f8 <+40>:    00 00 00 00 nop 0x004000fc <+44>:    00 00 00 00 nop End of assembler dump.

   可以提取16進制數來寫shellcode。

qemu: uncaught target signal 4 (Illegal instruction) - core dumped

Illegal instruction (core dumped)

  在我們前面的write程序執行的時候,會出現這樣兩行報錯。出現的原因是調用write系統調用之后,沒有調用exit系統調用退出,繼續執行了非法代碼,下面寫入exit系統調用來使程序正常退出:

.section .text .globl __start .set noreorder __start: addiu $sp,$sp,-32 lui $t6,0x4142 ori $t6,$t6,0x430a sw $t6,0($sp) li $a0,1 addiu $a1,$sp,0 li $a2,5 li $v0,4004
syscall li $a0,1 li $v0,4001 li $a1,0 li $a2,0
syscall

  

 

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM