純正新人CTF選手的誤打誤撞上分過程
“這題目上頭是上頭但是咱不上分啊”
Crypto - InfantRSA
題目:
真*簽到題
p = 681782737450022065655472455411;
q = 675274897132088253519831953441;
e = 13;
c = pow(m,e,p*q) = 275698465082361070145173688411496311542172902608559859019841
還有一段用來加密的腳本
#!/usr/bin/env python3
from secret import flag
assert flag.startswith(b'hgame{') and flag.endswith(b'}')
m = int.from_bytes(flag, byteorder='big')
p = 681782737450022065655472455411
q = 675274897132088253519831953441
e = 13
c = pow(m, e, p*q)
assert c == 275698465082361070145173688411496311542172902608559859019841
開始上手時沒去看腳本(挖坑)直接去搜了一下RSA的相關知識,因為平時不太使用Python所以最初想到的是用WolframAlpha來手動計算大數。草稿如下:
p 681782737450022065655472455411
q 675274897132088253519831953441
n=p*q 460390767897997184102969941508880171690097589571068900519251
φ=(p-1)(q-1) 460390767897997184102969941507523114055515479251893596110400
e(1<e<φ, gcd(e,φ)=1) 13
d(e*d mod φ=1) 141658697814768364339375366617699419709389378231351875726277
c=m ^ e mod n 275698465082361070145173688411496311542172902608559859019841
m 39062110472669388914389428064087335236334831991333245
其中計算私鑰d時將公式代入WolframAlpha直接作為方程求解,得到的解為
460390767897997184102969941507523114055515479251893596110400 * n + 141658697814768364339375366617699419709389378231351875726277, n ∈ Z
取n=0(n為其它整數時也有效),用WolframAlpha計算c ^ d mod n得到明文m
m怎么轉換為flag呢?查看題目里面的腳本並結合百度,得知在Python中使用m.to_bytes()函數即可。當中有兩個必填參數,byteorder根據腳本內容設置為big,length經測試只要大於flag文本的長度就能得到flag文本,所以填個100就差不多了
最終flag:hgame{t3Xt6O0k_R5A!!!}
后記:
《如果早知道,Python也能算大數...》(下次要把題目信息先理清楚再下手,如果一開始就查看題目里面的腳本則會發現py的這一特性並提高解題速度)
理論上公鑰e和私鑰d可以互換,即明文通過d加密得到密文,密文通過e解密得到明文
后期百度到d的一個解為:e ^ -1 mod φ,即d為e模φ的逆元,這里的e ^ -1不是1/e的意思
Crypto - Affine
題目:
Some basic modular arithmetic...
題干在一個py腳本中
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import gmpy2
from secret import A, B, flag
assert flag.startswith('hgame{') and flag.endswith('}')
TABLE ='zxcvbnmasdfghjklqwertyuiop1234567890QWERTYUIOPASDFGHJKLZXCVBNM'
MOD = len(TABLE)
cipher = ''
for b in flag:
i = TABLE.find(b)
if i == -1:
cipher += b
else:
ii = (A*i + B) % MOD
cipher += TABLE[ii]
print(cipher)
# A8I5z{xr1A_J7ha_vG_TpH410}
分析一下程序的邏輯,大概是取flag當中的每個字符,將其在TABLE中的下標i經過運算得到新的下標ii,並用ii在TABLE中對應的字符替換原字符,得到密文
A8I5z{xr1A_J7ha_vG_TpH410}
因為加密操作是針對單個字符進行的,並且加密過程與字符在flag當中的位置無關,所以可以嘗試恢復部分flag
首先是flag的頭部。A815z一定是由hgame加密得來的,通過分析每個字符的下標得到了以下對應關系(括號內數字為字符在TABLE中的下標)
h(12)->A(46)
g(11)->8(33)
a(7)->I(43)
m(6)->5(30)
e(18)->z(0)
分析用的python腳本如下
TABLE = 'zxcvbnmasdfghjklqwertyuiop1234567890QWERTYUIOPASDFGHJKLZXCVBNM'
cry = 'hgame-A8I5z{xr1A_J7ha_vG_TpH410}'
for b in cry:
print(TABLE.find(b),end=' ')
加密的核心在於ii = (A * i + B) % MOD,MOD是TABLE的長度(62),雖然對於取余的性質不太熟悉,但是考慮到g+h,m+a兩組變換中均存在i+1->ii+13,有理由相信A的值就是13(不會仔細求證,因為不會)
同時注意到m+g,a+h兩組變換的i差值在乘上A后反而大於ii差值,說明(A * 11 + B)的值已經大於MOD了,也就說明((6 * 13+B)-30) * n=((11 * 13+B)-33),n為大於等於2的整數
整理一下得到關系式
B=-48+62/(n-1)
得到B的可能取值為14、-17、-46、-47(因為小數好像無法mod?所以就直接取整數解了)
考慮字符e的變換結果剛好為0,則又能得到一個關鍵的式子
13 * 18+B=62 * n
B=62n-234
基本確定B是14
將A,B代入原始加密腳本中並對hgame加密,發現當A=13,B=14時可以得到結果A8I5z,說明A,B應該是正確的加密參數
(將原代碼中的from secret行和所有assert行刪除,手動定義A,B和flag即可驗證加密)
進一步猜想,密文當中有長度為6的部分(TpH410),很有可能是crypto
將其加密,得到的密文是TjR41q,與原密文非常相似,基本確定flag最后一段是crypto
對crypto進行大小寫變換以及相似數字的替換並加密驗證,最終得到cRYpt0
同理,結合語義分析,密文中長度為2的部分(vG)很可能是is,但s、S、5加密后的結果均不是G,遂嘗試in,最終得到iN
在上述過程中得到了1<-t,A<-h的變換,遂結合語義猜測xr1A對應的結果為myth,但經多次嘗試后證明了這一猜想是錯誤的
之前考慮到mod會導致i與ii之間並不是一一對應關系(其實是的),因此沒有逐個尋找字符與密文的對應關系,但現在無路可走且考慮到有了對應關系后可以縮小搜索范圍,遂對A ~ Z,a ~ z和0 ~ 9全部加密,得到對應關系如下
原文:abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890
密文:IbTazt8AvBfi5wq4QjX1JKF2RkLWgeh6OClsy9xG3YDpnEcoMNHSUZmr7PVdu0
(其實是一一對應的)
查表即可得到flag
最終flag:hgame{M4th_u5Ed_iN_cRYpt0}
后記:
這里其實是仿射密碼,解密表達式為i=(A ^ -1)(ii - B) % MOD,A ^ -1為A模MOD的逆元
Crypto - not_One-time
題目:
In cryptography, the one-time pad (OTP) is an encryption technique that cannot be cracked, but...
Just XOR ;P
nc 47.98.192.231 25001
hint: reduced key space
還有一個腳本:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os, random
import string, binascii, base64
from secret import flag
assert flag.startswith(b'hgame{') and flag.endswith(b'}')
flag_len = len(flag)
def xor(s1, s2):
#assert len(s1)==len(s2)
return bytes( map( (lambda x: x[0]^x[1]), zip(s1, s2) ) )
random.seed( os.urandom(8) )
keystream = ''.join( [ random.choice(string.ascii_letters+string.digits) for _ in range(flag_len) ] )
keystream = keystream.encode()
print( base64.b64encode(xor(flag, keystream)).decode() )
先嘗試直接試着在linux系統下運行了這個命令
nc 47.98.192.231 25001
得到了以下結果
JTcLBwQzE3czQV8UPkoIYWtbVT4PYnB2CUFxUQEzVhw8IVYvR0cdZXUJTg==
經過嘗試,每一次運行命令所得到的結果都不一樣,猜測得到的密文便是由上述腳本加密而得的,而題目應該是需要從密文當中恢復出flag
題目給了提示“reduced key space”,同時校內群里面也給了一個相關知識的網站,然而看完網站之后只大概了解到當密鑰空間小於明文空間時加密並不安全(應該是這個意思)。
對代碼進行分析,發現里面出現了自定的隨機數種子,猜測此題可能和偽隨機數有關,然而,進一步搜索發現os.urandom()函數就是用來做隨機加密key的,其執行結果難以預料,說明偽隨機數這個方向行不通
在從“reduced key space”角度多次進行搜索無果后,我嘗試從題目“one time pad”的角度進行搜索,發現OTP極其安全。。。這題沒法做了
然而,在搜索過程中接觸到MTP(Many Time Pad),通過進一步搜索,找到了一篇文章講解相關內容,發現文中提到的Many Time Pad與本題有些類似。文中題目是若干明文由相同key加密得到若干密文,根據密文求key,並通過key解密目標密文;而本題是flag被隨機key加密,需要解出flag。看上去兩者所求不同,但是由於加密算法當中的異或運算滿足交換律,本題也可看作是明文(隨機key)經相同key(flag)加密得到一系列密文,求key(flag)。
繼續研究文章,得到了幾條重要信息
1.若a ^ x=b,則b ^ x=a(異或的自反性)
2.設明文為m1,m2,密文為c1,c2,則有c1 ^ c2=(m1 ^ key) ^ (m2 ^ key)=m1 ^ key ^ key ^ m2=m1 ^ m2
3.文章中推論得當c1 ^ c2為有意義的英文字母時,對應的m1,m2很可能一個是英文字母一個是空格
第三條概括下來就是,通過尋找有意義的c1 ^ c2來獲得m1,m2可能的解
對應到本題,可以寫出以下偽代碼:
定義密文集合
定義每一位的解集S
定義臨時解集TMP
for(密文中的每一位i):
for(密文集合中的密文1,密文2):
結果=密文1[i] ^ 密文2[i]
for(任意字符j,任意字符k):
if(任意字符j ^ 任意字符k==結果):
TMP.加入元素(密文1[i] ^ j,密文2[i] ^ j,密文1[i] ^ k,密文2[i] ^ k)
if(S[i]是空集):
S[i]=TMP
else:
S[i]=求交集(TMP,S[i])
輸出結果
根據題目中的腳本所提供的條件,j和k均在字母和數字的范圍內,這就相當於一個c1 ^ c2有意義的限定條件。而求交集的過程,則是取每一組密文對所獲得的解的公共部分,以此縮小解的范圍,不過感覺本質上還是暴力
之后自己構造了flag和key用原腳本加密,並自己寫代碼去解出flag。寫代碼過程中因為自己埋的坑太多(邏輯錯誤、flag范圍錯誤等等)導致調試了很久,最終代碼如下:
# # -*- coding: utf-8 -*-
import base64, string
from socket import socket
table = (string.ascii_letters + string.digits).encode() # 源於題目中隨機key
# result = (string.ascii_letters + string.digits + '''{}_''').encode() #坑1
result = (string.printable).encode()
ciphers = []
for i in range(30): # 設定獲取密文數量
sock = socket()
sock.connect(('47.98.192.231', 25001))
text=sock.recv(500)
ciphers.append(text)
sock.close()
for i in range(len(base64.b64decode(ciphers.pop(0)))):
lis=set()
for cur1 in ciphers:
for cur2 in ciphers:
cy1 = base64.b64decode(cur1)
cy2 = base64.b64decode(cur2)
if (cy1 == cy2):
continue
res=set()
tmp1 = cy1[i] ^ cy2[i]
for j in range(len(table)):
for k in range(len(table)):
if (j == k):
continue
tmp2 = table[j] ^ table[k]
if (tmp1 == tmp2): # 使c1 ^ c2有意義
res1 = int(cy2[i] ^ table[j]).to_bytes(1, 'big')
res2 = int(cy2[i] ^ table[k]).to_bytes(1, 'big')
if (result.find(res1) != -1): # 縮小解范圍
res.add(res1)
if (result.find(res2) != -1): # 坑2
res.add(res2)
if (lis == set()):
lis = res
if (res != set()):
tmp = lis.intersection(res)
if (tmp != set()):
lis.intersection_update(res)
else:
lis.update(res)
print(i,''.encode().join(lis))
坑1:
天真地認為flag當中除了數字和字母意外就只有}{_了,導致索引為13、16、21、22、23、24、36的解很奇怪(即使密文數量很大解卻總是不唯一,而且沒有公共部分)
坑2:
一開始的判斷條件是當res1和res2都在result表內時才將res1,res2加入res內,很明顯邏輯錯誤,因為當res1符合條件時res2的結果是未知的,錯誤的判斷條件導致解集為空
sockXXX:
公告里面有給nc命令的替代方案,借此可以實現自動獲取密文
把密文數量開到40 ~ 60基本就能唯一確定flag了,經測試開到100大概能在30min內跑完,性能還是可以(吧)
最終flag:hgame{r3us1nG+M3$5age-&&~rEduC3d_k3Y-5P4Ce}
Crypto - Reorder
題目:
We found a secret oracle and it looks like it will encrypt your input…
nc 47.98.192.231 25002
用nc訪問該ip和端口,發現是一個交互式的程序,輸入的字符會被打亂順序輸出
root@tesla:~# nc 47.98.192.231 25002
> 123
2 1 3
> acbvasdsdfasdfwefagda
fdcseaadadfswvsb a a f d g
> 1234567890
726 591 08 4 3
而且在若干次輸入后直接得到了flag?
Rua!!!
mjg{L+e$hItUpm5a!u_m}iRA3nTT!e0P
很明顯flag的順序被打亂了,結合題目名稱,應該是根據一個對應關系將flag還原,而這個對應關系應該也是輸入輸出的對應關系
測試情況如下
root@tesla:~# nc 47.98.192.231 25002
> 1234567890zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
zz3687952zz0z14zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz zzzzzzz z zz
> zzzzzzzzzz1234567890zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
14zzzzzzz23z6zz5zz9zzzzz8zzzz70zzzzzzzzzzzzzzzzzz zzzzzzz z zz
> zzzzzzzzzzzzzzzzzzzz1234567890zzzzzzzzzzzzzzzzzzzzzzzzzzzzz
zzzzzzzzzzzzzzzz70z24351z896zzzzzzzzzzzzzzzzzzzzz zzzzzzz z zz
> zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz1234567890zzzzzzzzzzzzzzzzzzz
zzzzzzzzzzzzzzzzzzzzzzzzzzzz2zz1zz5809z74zzzz36zz zzzzzzz z zz
> zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz1234567890zzzzzzzzz
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz36zzzz1zz4528zz7z zzzzzz0 z 9z
> zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz1234567890
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz9 146573z0 8 z2
> zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz1234567890
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz2zzzzzzzz1z4zz3 70 96 58
> z
z
> z
z
> z
z
Rua!!!
+ma{Uj$eg5ItLhmpi!PmTuAR_0nT}3e!
對應找一下就能知道flag了
最終flag:hgame{jU$t+5ImpL3_PeRmuTATi0n!!}
Misc - 歡迎參加HGame!
題目:
歡迎大家參加 HGAME 2020!
來來來,簽個到吧~
Li0tIC4uLi0tIC4tLi4gLS4tLiAtLS0tLSAtLSAuIC4uLS0uLSAtIC0tLSAuLi0tLi0gLi4tLS0gLS0tLS0gLi4tLS0gLS0tLS0gLi4tLS4tIC4uLi4gLS0uIC4tIC0tIC4uLi0t
注:若解題得到的是無hgame{}字樣的flag花括號內內容,請手動添加hgame{}后提交。
【Notice】解出來的字母均為大寫
按照CTF的套路(可能是這樣),不知道的明文先丟base64解密一遍,得到一串摩爾斯電碼
.-- ...-- .-.. -.-. ----- -- . ..--.- - --- ..--.- ..--- ----- ..--- ----- ..--.- .... --. .- -- ...--
再對應解密,得到flag括號中的內容(解密時不能使用中文摩爾斯電碼加解密工具,因為此工具會先將明文/密文轉為Unicode,無法將以上電碼直接對應到字符)
w3lc0me_to_2020_hgam3
之前題目沒有【Notice】部分,導致我嘗試更改字形提交了很久,最后全改為大寫后才提交成功
最終flag:hgame{W3LC0ME_TO_2020_HGAM3}
Misc - 壁紙
題目:
某天,ObjectNotFound給你發來了一個壓縮包。
“給你一張我的新老婆的壁紙!怎樣,好看嗎?”
正當你疑惑不解的時候,你突然注意到了壓縮文件的名字——“Secret”。
莫非其中暗藏玄機?
壓縮包放不上來就不放了
看題目名字就知道是個圖種了,直接把壓縮包中的圖片拖出來,重命名為zip后綴,然后打開
發現flag.txt被加密,注釋里面說需要找到原圖的P站ID才能打開
思路一:百度得知P站ID一般為8位數字,那么直接bruteforce應該可行(手上沒工具,未實現)
思路二:識圖
考慮到識圖網站處理圖片文件的方式應該和本地的圖片查看器方式相同,因此沒有做圖片與壓縮包的分離操作,直接把源文件用來識圖了
先丟百度識圖上面直接識圖,無果
后百度“P站識圖”,找到了一個P站以圖找ID的網站,查到圖片的ID是76953815
打開flag.txt,卻並沒有看到flag,而是以下內容
\u68\u67\u61\u6d\u65\u7b\u44\u6f\u5f\u79\u30\u75\u5f\u4b\u6e\u4f\u57\u5f\u75\u4e\u69\u43\u30\u64\u33\u3f\u7d
兩個16進制數結合前面的u前綴,猜測以上內容為utf-8編碼,而且下划線的16進制utf-8編碼為5f,且密文中剛好有多處\u5f,基本確定這是utf-8編碼。但是由於缺少解碼工具,只能手動解碼出flag
解到一半想起百度提交搜索內容時是將中文轉為utf-8編碼以%xx的形式通過GET提交,遂將flag.txt中的\u替換為%,然后在百度官網地址后面添加以下內容
baidu?wd=%68%67%61%6d%65%7b%44%6f%5f%79%30%75%5f%4b%6e%4f%57%5f%75%4e%69%43%30%64%33%3f%7d
訪問后即可在搜索框位置得到flag
最終flag:hgame{Do_y0u_KnOW_uNiC0d3?}
后記:
utf-8指的並不是只用8位數據表示一個字符,而是指可變長編碼以若干個8位(若干個字節)來表示一個字符,解題的時候望文生義了
Web - 雞尼泰玫
題目:
聽說你球技高超?
之后給出了一個游戲網址,打開之后是一個類似BBTAN的游戲,每一次擊球可以獲得100分,當總分超過30000分時即可獲得flag
游戲菜雞自然是不會玩游戲的,直接F12,發現是一個js寫的小游戲,當中定義了幾個游戲中的類
然后在game類當中找到了globalScore和storageScore兩個重要參數
嘗試用以下命令修改
_main.game.storageScore=30000
_main.game.globalScore=30000
發現修改之后數據確實變成了30000,但是每次球撞到方塊之后分數會變回原始分數
之后嘗試在球即將落地時修改參數,成功
最終flag:hgame{j4vASc1pt_w1ll_tel1_y0u_someth1n9_u5efu1?!}
Web - Code World
題目:
Code is exciting!
直接訪問目標地址,看到了403 Forbidden
感覺挺無從下手的,於是試了一下F12,發現源碼里面有這么一行
console.log("This new site is building....But our stupid developer Cosmos did 302 jump to this page..F**k!")
百度了一下有關302的信息,結果直接找到一篇文章講CTF題目當中遇到302的處理方法。Linux下使用curl訪問網址默認是不會根據302跳轉跳到新網址的,遂用curl訪問該網址,得到以下結果
root@tesla:~# curl http://codeworld.hgame.day-day.work/
<html>
<head><title>405 Not Allowed</title></head>
<body bgcolor="white">
<center><h1>405 Not Allowed</h1></center>
<hr><center>nginx/1.14.0 (Ubuntu)</center>
</body>
</html>
405 Not Allowed說明當前的請求方法不被允許。因為curl不能發送自定義的請求頭,所以決定還是用Fiddler的Composer功能試一下
用Fiddler抓了包,意外地發現Fiddler能抓到訪問題目網址時的302返回和403返回,且302返回當中的內容正好是curl所得到的結果,說明用Fiddler直接訪問題目網址即可。
打開Composer選項卡,填入網址,把所有的請求方法全部試了一遍
發現使用POST方法訪問之后得到了以下結果
<center><h1>人雞驗證</h1><br><br>目前它只支持通過url提交參數來計算兩個數的相加,參數為a<br><br>現在,需要讓結果為10</center>
嘗試直接在請求體部分添加a=10,沒有反應;再嘗試a=5+5,也沒有反應
后來發現審題不仔細,應該是在url當中提交參數。遂在網址后添加?a=10,沒有反應;再嘗試?a=5%2B5,得到了以下結果(+號經url編碼后得到%2B,可在百度搜索框當中嘗試)
<center><h1>人雞驗證</h1><br><br>目前它只支持通過url提交參數來計算兩個數的相加,參數為a<br><br>現在,需要讓結果為10<br><h1>The result is: 10</h1><br>hgame{C0d3_1s_s0_S@_sO_C0ol!}</center>
最終flag:hgame{C0d3_1s_s0_S@_sO_C0ol!}
Web - 接 頭 霸 王
題目:
HGAME Re:Dive 開服啦~
直接訪問題目地址,得到以下提示
You need to come from https://vidar.club/.
通過百度得知需要在請求頭里面加上
Referer: https://vidar.club/
於是用Fiddler的Composer補完請求頭之后提交,得到以下提示
You need to visit it locally.
之后基本就是得到提示→百度→改頭的過程了,題目中涉及到的修改操作如下
You need to come from https://vidar.club/.
Referer: https://vidar.club/
You need to visit it locally.
X-Forwarded-For: 127.0.0.1
You need to use Cosmos Brower to visit.
User-Agent: Cosmos Brower
Your should use POST method :)
把請求方法由默認的GET改為POST
The flag will be updated after 2077, please wait for it patiently.
If-Unmodified-Since: Fri, 01 Jan 2077 00:00:00 GMT
其中最后一條只需要這個時間比響應頭中的Last-Modified時間晚即可(相同也可以)
前幾條百度上都挺好找的,就是最后一條網上似乎沒有明確的介紹,於是就把所有和日期有關的頭都試了一遍,找到了結果
最終flag:hgame{W0w!Your_heads_@re_s0_many!}
Web - Cosmos的博客
題目:
這里是 Cosmos 的博客,雖然什么東西都還沒有,不過歡迎大家!
打開網頁后內容如下:
大茄子讓我把 flag 藏在我的這個博客里。但我前前后后改了很多遍,還是覺得不滿意。不過有大茄子告訴我的版本管理工具以及 GitHub,我改起來也挺方便的。
按F12,沒有發現任何有用信息。嘗試直接
git clone http://cosmos.hgame.n3ko.co/
也沒有結果
之后直接找了一下CTF中有關git的題目,根據網上的信息,訪問
http://cosmos.hgame.n3ko.co/.git/HEAD
得到以下內容
ref: refs/heads/master
說明該網站存在git泄露。在網上下了一個githacker的腳本,把網站上的git倉庫直接下載下來,然后直接目錄內搜索hgame,無果
根據網上的操作步驟,嘗試查看倉庫的歷史版本,無果
之后隨便翻閱倉庫內的文件,意外發現GitHub上的倉庫地址
clone到本地后再次嘗試查看歷史版本,結果與上一次不一樣
嘗試恢復,得到了flag文件,base64解碼即可得到flag
最終flag:hgame{g1t_le@k_1s_danger0us_!!!!}
第一次做CTF類的題目,感覺自己還是太菜了,做出來的題目不足1/2,而且用時很長。之后看一下別人的writeup學習一下吧
2020.01.24