注:本文中所有題面數據均經過處理,並未復現現場數據,僅作輔助理解過程之用。
0x00 前言
一次偶然,我經歷了一場奇異的CTF解題過程,盡管上下折騰許久,但終有所收獲。現將解題過程與思路分享。
0x01 開場
開題 github.com/xxxx8888/AaBbcCdD 打開后只有一個README
5KB才6行?Raw加載原始文件
一串神秘字符和一段熟悉的base64編碼,由編碼中的等號發現,是由兩段base64組成的。
先看第一段神秘字符串,乍一看摸不着頭腦。仔細看看,有見 : / 等字符,大膽猜測這是url的變形。
對半拆分后看到熟悉的字眼,發現是字符的移位變換。
hts/x_lj3w33lgic.eiwn_e tp:/xsfhta25.lthm/_atky
按上到下,左到右的規則重新拼合得到鏈接
https://xx_slfjh3twa3235l.glitch.me/i_want_key
打開后得到一組RSA公鑰,且先保存。
-----BEGIN PUBLIC KEY----- MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANE6WKCgLluv3Fn5kqg9HR+XwJ6iattQ CmqV/xEOxJU9HVUl30hoVvRspz1a5rQ8xiZhueWRhNMkdkmyO18itPkCAwEAAQ== -----END PUBLIC KEY-----
再解析base64字符串,第一組得
經過了解,這是一種叫 jjencode 的混淆算法,通過在線工具解密得到又一組base64,解密得亂碼。想到可能與RSA解密相關。暫存
看方才第二組編碼 繼續解碼得到兩個鏈接
dive.giggle.cow/file/d/26TTOq3zCg-2nrBdeTWtV7tHgg4z-xxxXx/view?usp=sharing s1.ax1x.com/2000/01/01/bCZFqK.png
分別是一張gif → (文件名:This_is_d.gif) 與一張二維碼圖片。等等,二維碼好像不對勁?
通過查閱資料了解到,二維碼(QR Code)的的關鍵部位就在三個基准點和黑白相間的基准線上
其中黑白相間的基准線立刻引起了我的注意。在對畸形的二維碼進行分析后,發現左半側的圖像經過了旋轉變換
我通過python腳本來修復二維碼(Photoshop,GIMP等工具亦可)
1 from PIL import Image 2 3 img_origin = Image.open('qr.png') 4 img = img_origin.copy() 5 img_origin.close() 6 width = img.width 7 height = img.height 8 left_img = img.crop((6,6,width//2-10,height-6)) 9 left_img = left_img.rotate(180) 10 img.paste(left_img,(6,6)) 11 img.show()
得到原圖 通過在線工具得出其中內容,還是base64
******************************************************************* U2FsdGVkX18I1TdtQoyW2bd02ewoSz+5Zo4c3Tit1r3mA0Z+jwMw9uhwIR6o+kN/ nK2pbsHjs8U5CqN9oKC6wg== *******************************************************************
解析 得到Salted__開頭的加密字符串,根據經驗,判斷這是aes加密的字符串,保存。
順利解決的部分就告一段落。總結整理,得到一張閃動的gif,一組RSA公鑰以及對應的密文,一組aes密文。
0x02 困境
前半段告結。剩下的數據令人有些摸不着頭腦。黑白閃動的圖片?難道是摩爾斯電碼?
使用identify工具查看各幀的顯示時間,剔除重復項后發現間隔長短不一,從0.1-1.1s不等。
而查閱資料得摩爾斯電碼的時間點最多只有1,3,5,7四種。此路不通。只能換個任務思考。
RSA密文怎么解密?我可只有公鑰,而RSA在應用場景中只有私鑰才能解密(理論中公鑰與私鑰是可以交換的)。
我先想到了針對RSA的攻擊。先通過openssl查看公鑰的$n$,$e$。
唔,512bit不好暴力拆解n,抱着試試看的心理,上傳到factordb,出現意想不到的一幕
???一個合法的$n$竟然無法分解?再仔細看,發現是機(jiǎo)智(huá)的出題人故意上傳了無解的結果來欺騙我們。。
一條能走的路都沒有了,我只好一邊用yafu跑着暴力分解,一邊苦苦思索解題思路。
0x03 突破
終於,發現提示就近在眼前!RSA中的私鑰$d$,莫非就是This_is_d.gif所指?
黑白閃動,不就是01信號嘛!幀長度就是行程!立刻動手,使用python解析隱藏的二進制數據。
1 from PIL import Image 2 3 img = Image.open('This_is_d.gif') 4 frame = 1 5 img.seek(img.tell()) 6 7 try: 8 while True: 9 rgb_img = img.convert('RGB') 10 color = rgb_img.getpixel((5, 5)) 11 duration = img.info['duration'] 12 if color == (255, 255, 255): 13 state = '1' 14 else: 15 state = '0' 16 print(state * (duration//100),end='') 17 img.seek(img.tell() + 1) 18 frame += 1 19 except EOFError: 20 pass
二進制數據轉16進制得
得到了$d$,又有$n$,根據RSA解密公式 $c^{d}\equiv m\pmod N$,編寫python腳本
1 import base64 2 import binascii 3 4 n = 0xd13a58a0a02e5bafdc59f992a83d1d1f97c09ea26adb500a6a95ff110ec4953d1d5525df486856f46ca73d5ae6b43cc62661b9e59184d3247649b23b5f22b4f9 5 d = 0x63cd1e0b787cc4756d7969a7c0226eaaec3b10304f224fdab81ed66d2f8b2bd7ff4010a5f498faf01a32139ac371c4a32fddc3bcac47ad53914eee70ea4bb651 6 with open('task2_base64.txt') as f: 7 str_base64 = f.read() 8 encrypted = int(binascii.b2a_hex(base64.b64decode(str_base64)),16) 9 plain = hex(pow(encrypted,d,n))[2:] 10 padding_end = plain.find('00')+2 #--!-!--注意 RSA PKCS#1 padding--!-!-- 11 str_plain = binascii.a2b_hex(plain[padding_end:]) 12 print(str_plain)
Succeed ! 解密得到字符串 839$nc*!9vx_ ,很有可能就是aes密文的密碼。使用openssl工具進行解密成功。得到一組字符串。
大寫字母,2個以上的=號,莫不是base編碼家族中的base32?通過在線工具解析, 最終拿到flag。
0x04 總結
總的來看,這道題難度一般,綜合了Crypto,Misc等知識點,又通過“此地無銀三百兩”的手法設置障礙。本次解題過程前半部分一切順利,失誤就在於缺乏對提示的敏感性(其實是太菜),導致進入了錯誤的解題方向,需要我整理思考。
0x05 后記
本次解題過程中使用的幾種工具與python模塊:
factordb [http://www.factordb.com/index.php]
Pillow [https://pypi.org/project/Pillow/]
identify [https://imagemagick.org/script/identify.php]
openssl [https://github.com/openssl/openssl]
我再推薦幾種常用的、本文中未使用的工具與python模塊:
rsatool [https://github.com/ius/rsatool]
gmpy2 [https://pypi.org/project/gmpy2/]
yafu [https://sourceforge.net/projects/yafu/]
pycrypto [https://pypi.org/project/pycrypto/]
具體安裝及用法則不再贅述。
0xFF 題外話
在本題RSA解密中,由於加密前明文采用PKCS#1標准進行padding,我在編寫腳本解密時未考慮到,導致解密不出明文。
通過查閱規范文本【RFC 8017】了解該標准下的解密方式后解密成功,下附規范文本中的解密步驟:
7.2.2. Decryption Operation RSAES-PKCS1-V1_5-DECRYPT (K, C) Input: K recipient's RSA private key C ciphertext to be decrypted, an octet string of length k, where k is the length in octets of the RSA modulus n Moriarty, et al. Informational [Page 29] RFC 8017 PKCS #1 v2.2 November 2016 Output: M message, an octet string of length at most k - 11 Error: "decryption error" Steps: 1. Length checking: If the length of the ciphertext C is not k octets (or if k < 11), output "decryption error" and stop. 2. RSA decryption: a. Convert the ciphertext C to an integer ciphertext representative c (see Section 4.2): c = OS2IP (C). b. Apply the RSADP decryption primitive (Section 5.1.2) to the RSA private key (n, d) and the ciphertext representative c to produce an integer message representative m: m = RSADP ((n, d), c). If RSADP outputs "ciphertext representative out of range" (meaning that c >= n), output "decryption error" and stop. c. Convert the message representative m to an encoded message EM of length k octets (see Section 4.1): EM = I2OSP (m, k). 3. EME-PKCS1-v1_5 decoding: Separate the encoded message EM into an octet string PS consisting of nonzero octets and a message M as EM = 0x00 || 0x02 || PS || 0x00 || M. If the first octet of EM does not have hexadecimal value 0x00, if the second octet of EM does not have hexadecimal value 0x02, if there is no octet with hexadecimal value 0x00 to separate PS from M, or if the length of PS is less than 8 octets, output "decryption error" and stop. (See the note below.) 4. Output M.
另外,我們只要具有一定密碼學基礎,在已經得到$d$的情況下,就可以展開$d$泄漏攻擊,還原出完整的pem私鑰。
首先當$d$泄露之后,我們自然可以解密所有加密的消息。我們甚至還可以對模數$N$進行分解。其基本原理如下
我們知道$ed \equiv 1 \bmod \varphi(n)$,那么存在一個$k$使得
$ed-1=k\varphi(n)$
又$\forall a\in {Z}_n^*$,滿足$a^{ed-1}\equiv1(\bmod n)$。令
$ed-1=2^st$
其中,$t$是一個奇數。然后可以證明對於至少一半的$a\in {Z}_n^*$,存在一個$i\in[1,s]$,使得
$a^{2^{i-1}t}\not\equiv\pm1(\bmod n),a^{2^{i}t}\equiv1(\bmod n)$
成立。如果$a$,$i$滿足上述條件,$gcd(a^{2^{i-1}t}-1,n)$是$n$的一個非平凡因子,所以可以對$n$進行暴力分解。
下面使用rsatool得到私鑰,並用openssl直接解密密文。