• 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
    ",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
    ",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!
    ";
        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!1200"
        .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

      

     

     

     

  • 相关阅读:
    Java中Runnable和Thread的区别
    Callable,Runnable比较及用法
    如何实现视差滚动效果的网页?
    【175】Easy CHM的使用
    【174】C#添加非默认字体
    【173】双显示器随便切换位置
    【172】outlook邮箱设置
    【171】IDL读取HDF文件
    怎样实现二级联动
    Java 23种设计模式详尽分析与实例解析之二--结构型模式
  • 原文地址:https://www.cnblogs.com/L0g4n-blog/p/13968404.html
Copyright © 2020-2023  润新知