はじめに
UECTF 2022に参加しました。高専SECCON以来2年ぶりくらいのCTF参加です。
環境
OS | 用途 |
---|---|
Fedora 36 GNU/Linux | デスクトップPC。WriteUp書いたり数問解いたり |
Ubuntu 22.04 GNU/Linux | 問題を解く用。ソフトウェアをたくさん入れました。普段使いのPCでCTFは少しこわいのでVM(CTFer?はどうされているんでしょう) |
WELCOME (10pts)
UECTFへようこそ! Discordサーバーにあるflagを提出してください!!
Discordへログインして招待リンクからサーバへ参加しflagゲット。
FLAG : UECTF{C4PTURE_TH3_FL4G_2022}
caesar (100pts)
ガイウス・ユリウス・カエサル Gaius Iulius Caesar
シーザー暗号で暗号化するプログラムと暗号化されたフラグ文が渡される。
アルファベットと記号をプログラムに入力して変換テーブルを作り、それを元にフラグ文を複号するコードを書いた(C言語)。
AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
#include <stdio.h>
int main(void) {
FILE *fp = fopen("caesar_output.txt","r");
char str[1024];
char *from = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~";
char *to = "HhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~AaBbCcDdEeFfGg";
fscanf(fp, "%s", &str);
for (int i = 0; i < strlen(str); i++) {
for (int j = 0; j < strlen(to); j++) {
if (str[i] == to[j]) {
str[i] = from[j];
goto a;
}
}
a:
}
printf("%s\n", str);
}
FLAG : UECTF{Th15_1s_a_b1t_Diff1Cult_c43seR}
A file (50pts)
誰かがファイルの拡張子を消してしまった。どのような中身のファイルなのか?
デスクトップPC(Fedora GNU/Linux)でファイルをダウンロードするとアイコンが圧縮ファイルになっている。展開するとファイルが入っているのでstringsで中身を見るとフラグがあった。
$ strings chall | grep CTF
UECTF{Linux_c0mm4nDs_ar3_50_h3LPFU1!}
FLAG : UECTF{Linux_c0mm4nDs_ar3_50_h3LPFU1!}
RSA (50pts)
RSA暗号でフラグを暗号化してみました!解読してみてください。
RSA暗号で暗号化するプログラムとフラグを暗号化したデータが渡される。
cipher text: 40407051770242960331089168574985439308267920244282326945397
p: 1023912815644413192823405424909
q: 996359224633488278278270361951
e: 65537
RSA暗号を複号するプログラムを作成してflagゲット。
p, q, eからd(秘密鍵)を求める際は拡張ユークリッド互除法を使う。
他の方のWriteUpを見て思いましたがもっと楽なライブラリありましたね。
def dec(c_text, p, q, e):
N=p*q
#c_text=pow(p_text,e,N)
#cipher_text=plain_text^e mod N
l = (p-1) * (q-1)
(aaa,d,aaa2) = exEuclid(e,l) # exEuclidは拡張ユークリッド互除法を求める関数。ネットから拾ったため省略
p_text=pow(c_text,d,N)
print('plain text:',long_to_bytes(p_text))
dec(40407051770242960331089168574985439308267920244282326945397, 1023912815644413192823405424909, 996359224633488278278270361951, 65537)
FLAG : UECTF{RSA-iS-VeRy-51Mp1e}
Deleted (100pts)
USBメモリに保存してたフラグの情報消しちゃった。このイメージファイルからどうにか取り出せないものか…
USBメモリのimageファイルをphotorecで修復し、その後マウントしてフラグのかかれた画像ファイル(f0001144.png)を見つけた。
$ photorec image.raw
FLAG : UECTF{TH1S_1M4G3_H4S_N0T_B33N_D3L3T3D}
redaction gone wrong 1 (100pts)
NOBODY SHOULD JUST COPY AND PASTE MY FILES!
何人もコピペすべからず!
FLAGのかかれたところが黒塗りされていてコピーできないPDFファイル。
Libreoffice DrawでPDFファイルを開き編集し、黒塗りの矩形を取り外したところFLAGゲット。
FLAG : UECTF{PDFs_AR3_D1ffiCulT_74d21e8}
revPython (100pts)
What does this pyc file do?
これは?
pycファイルとflag.jpgが渡される。
pycdcを使ってpycファイルをdecompileしたところ、完全にはできなかったものの
# Source Generated with Decompyle++
# File: a.cpython-39.pyc (Python 3.9)
from hashlib import sha256
prefix = 'UECTF{'
user_key = input('flag: ')
(略)
def xor_image(data = None, key = None):
if type(key) != 'bytes':
key = bytes(key, 'latin1')
return None((lambda .0 = None: for i, d in .0:
d ^ key[i % len(prefix)])(enumerate(data)))
(略)
のような記述を発見。prefix文字列を使って画像ファイルのXORをとっていると当たりをつけて画像ファイルのXORをとって出力するプログラムを作成。
実行するとフラグのかかれた画像ファイルが出力された。
#!/usr/bin/env python3
prefix = b"UECTF{"
with open("flag.jpg", "rb") as f:
b1 = f.read()
b2 = bytearray(b1)
for i in range(len(b1)):
b2[i] = b1[i] ^ prefix[i%len(prefix)]
with open("flag_out.jpg", "wb") as f:
f.write(b2)
FLAG : UECTF{oh..did1s0meh0wscr3wup??}
GIF1 (100pts)
GIFアニメの中にフラグを隠したよ。え?隠れてないって?そんなぁ…
I tried to hide the flag with GIF animation. Huh? Not hidden...? Oh no...
GIFアニメを見ると途中でフラグ文字列らしきものが出力されている。
ffmpegでコマごとに画像ファイル化して確認。
$ ffmpeg -i UEC_Anime.gif gif/%06d.jpg
FLAG : UECTF{G1F_4N1M4T10NS_4R3_GR34T!!}
redaction gone wrong 2 (100pts)
We have found this image floating on the internet. Can you tell us what is the redacted text?
インターネット上でこの画像を見つけた。隠されたテキストは何だろうか?
フラグが見えないように上から黒く塗られている画像。Fedora GNU/Linux上のUbuntu VMだからなのか(関係ないかも?)見づらいが加工等せずに読めたので回答。
FLAG : UECTF{N3ver_ever_use_A_p3n_rofl}
buffer_overflow (50pts)
バッファオーバーフローを知っていますか?
Do you know buffer overflow?
コンパイルオプションは-fno-stack-protectorをつけています。
gcc ./bof_source.c -fno-stack-protectornc uectf.uec.tokyo 30002
debug_flagが1になればフラグが出力される。16文字入力(末尾は1)してバッファオーバーフローさせた。
特にgdb等は使わなかった。
#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);
}
$ nc uectf.uec.tokyo 30002
What is your name?
>1111111111111111
[DEBUG]:flag is UECTF{ye4h_th1s_i5_B0f_flag}
Hello 1111111111111111.
^C
FLAG : UECTF{ye4h_th1s_i5_B0f_flag}
Compare (100pts)
新しく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.
ロゴファイルはbmp形式。md5sumで見ると違うファイル。
$ md5sum UECTF_*
d8d05971856eaf7bf5a0d966988d6838 UECTF_new.bmp
b1b43c8a0851d56a94952478cd5f449a UECTF_org.bmp
cmpで比較
$ cmp -l UECTF_new.bmp UECTF_org.bmp
101845 125 377
102818 105 377
103839 103 377
104752 124 377
105401 106 377
106038 173 377
106639 143 377
107228 157 377
107757 155 377
108406 160 377
109103 141 377
109848 162 377
110605 145 377
111218 137 377
111855 164 377
112504 167 377
113141 157 377
113754 137 377
114379 146 377
115040 151 377
115593 154 377
116326 145 377
116939 163 377
117480 137 377
118285 142 377
118874 171 377
119523 164 377
120040 145 377
120605 137 377
121170 142 377
121819 171 377
122384 137 377
123033 142 377
123574 171 377
124103 164 377
124668 145 377
125065 175 377
2列目がASCIIコードに見えるので2列目を抜き出して変換。10進数ではなく8進数のため注意が必要だった。
$ cmp -l UECTF_new.bmp UECTF_org.bmp | awk '{print $2}' > data.txt
#include <stdio.h>
int main(void) {
FILE *fp = fopen("data.txt", "r");
int val;
while (fscanf(fp, "%o", &val) != EOF) { // %o : 8進数
printf("%c", val);
}
}
FLAG : UECTF{compare_two_files_byte_by_byte}
Discord 1 (127pts)
数日前、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...
Zipファイルを展開するとDiscord(おそらくデスクトップアプリ?)のデータのディレクトリが入っていた。Cacheフォルダが怪しそうなので見ると画像ファイルがいくつかあった。GNOME環境なので拡張子がなくともプレビューされ、FLAGのファイル(f_00003a)をすぐ見つけられた。
FLAG : UECTF{D1SC0RD_1S_V3RY_US3FUL!!}
GIF2 (127pts)
今度こそGIFアニメにフラグを隠したよ。人の目で見えるものだけが全てじゃないよ。
I tried to hide the flag in a GIF animation. It's not all about what people can see.
GIF1と同様にffmpegでpngファイルで画像ファイルにしてみる。
$ ffmpeg -i UECTF.gif %d.png
コマを一つGIMPで開いてペイントツールで背景を塗る。FLAGが現れた。threshold(しきい値)は0にしないと全部塗りつぶされてしまう。
最初はjpgファイルに変換していたためうまくいかなかった。GIFのバージョンがGIF89aのため透過GIFを使用しているのかもとpngに変換したのが功を奏した。
$ file UECTF.gif
UECTF.gif: GIF image data, version 89a, 602 x 352
OSINT (436pts)
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をまだやっているはずなんだけどなぁ🤔
アカウント名を変えたTwitterアカウントを特定する問題。
まずInternet Archiveのサイトでユーザのページが過去に存在しているか調べてみるとアーカイブに存在した。
また、調べるとTwitterアカウントにはTwitter idという一意のIDが振られているらしいと分かり、アーカイブのページから取得できるのではと考えた。
自分のID (@Rinwasyu) で試してみる。
ログインした状態でソースコードを表示し、id_strを探す。
"id_str":"757227979655122945"
今度はログインしていない状態でDeveloperツールでDom treeを表示し757227979655122945
を探す。(ソースコード表示では探せなかった)
data-testid="757227979655122945-follow"
取得できそうなので、アーカイブのサイトでDeveloperツールからdata-testid=
を探す。
data-testid="1585261641125416961-follow"
Twitter idは1585261641125416961
今度はidでユーザを追跡する。
idtwi.comというWebサイトを利用しidから表示名(アカウント名)を特定。
ユーザIDが分かったのでTwitterにアクセスし、ツイートのpastebinリンクからFLAGゲット。
https://twitter.com/ftceu
FLAG : UECTF{ur_a_tw1tter_mast3r__arent_y0u}
PDF (400pts)
一貫性のあるPDF
Consistent PDF
PDFを見るとページ番号が不規則なのが気になった。DUMMY PAGEとかかれたページを除いたページのページ番号を並べていった。
VUVDVEZ7RG8teTBVLWtOb3ctN2hBVC1QZGYtcGE5RS1OdW1CM1I1LUNBTi1VU0UtTEV0N2VSUy0wN2hFci1USDROLVJPbUBuLU5VTTNSNDEkP30
Base64 decodeをしたところFLAGゲット。
文章だとこれだけですがPDFの構造を調べたりページ分割してみたり迷走していたため時間がかかりました。
FLAG : UECTF{Do-y0U-kNow-7hAT-Pdf-pa9E-NumB3R5-CAN-USE-LEt7eRS-07hEr-TH4N-ROm@n-NUM3R41$?}
webapi (100pts)
サーバーからフラグを取ってきて表示する web ページを作ったけど、上手く動かないのはなんでだろう?
I created a web page that fetches flags from the server and displays them, but why doesn't it work?
http://uectf.uec.tokyo:4447
Developerツールで見ると
https://i5omltk3rg2vbwbymc73hnpey40eowfq.lambda-url.ap-northeast-1.on.aws/
にアクセスしていてCORSでエラーとなっているぽい。
直接サイトにアクセスしてFLAGゲット。
FLAG : UECTF{cors_is_browser_feature}
request-validation (323pts)
GET リクエストでオブジェクトを送ることはできますか? ※ まずは、自分の環境でフラグ取得を確認してください。
Can you request a object?
First, please check the flag acquisition in your environment.
docker-compose.yml、node.js、package-lock.jsonが渡された。
$ sudo docker-compose up -d
package.jsonがないためエラーが出たが追加し解決(不備があったようです)
{
"name": "app",
"descripotion": "app",
"main": "node.js",
"dependencies": {
"dotenv": "*",
"express": "*"
}
}
ソースコードを見るとGETリクエストで「q」のクエリがobjectだとフラグがもらえるらしい。
page?q=hoge
※この場合qはstring
if (req.query.q && typeof req.query.q === 'object') {
res.send(FLAG)
} else {
res.send('invalid request' + typeof req.query.q)
}
どうやったらいいか色々調べたところ以下ツイートを発見。
http://uectf.uec.tokyo:4446/?q[a]=a
にアクセスし無事FLAGゲット。
試したこと
- curlでJSON投げられるか試した
- objectにはならなかった(はず?)
WHEREAMI (400pts)
あなたの元に友人から「私はどこにいるでしょう?」という件名の謎の文字列が書かれたメールが送られてきました。 さて、これは何を示しているのでしょうか?
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?
謎の文字列が渡される。
7RJP2C22+2222222
7RJP2G22+2222222
7VJM2C22+2222222
7VJM2G22+2222222
(略)
ヒントがあった。
彼はこの文字列はPlus codeだと言っていましたがよく分かりません。
He said it was a "plus code", but I have no idea what plus code is. Is any string with "+" character in it a "plus code"???
よくわかりました。
Plus codeはgoogleが提供しているコードで、Open location codeとも言い、場所を表すらしい。
この問題では位置情報でフラグを書いているのだろうと予想。
表示のさせ方を考える。
ライブラリで表示 (断念)
googleの出すライブラリは各言語処理系でライブラリがあるので使えそうだが実装に時間がかかりそう。
緯度・経度が分かったとしても表示をしないといけない。
GNOME Maps (断念)
GNOME Mapsでインポートできないかと調べてみたが無理そう。検索欄にplus codeを入力しても正しい場所にいかない。
手書き(断念)
一旦手で絵をかくことにした。
仕様の確認をする。
東西・南北に20分割して位置を表している
2, 3, 4, 5, 6, 7, 8, 9, C, F, G, H, J, M, P, Q, R, V, W, X
上位の桁から下位の桁へ、2桁ごとに大きいエリアから小さなエリアへ分割して東西・南北の位置を表している。
AABBCCDD+EEFFGGH
今回のコードは7Rエリアと7Vエリアだけを使っていて、上位6桁しか値が変化しないので20*20*2=800
マスで絵をかける。
LibreOffice Calcを使用し、方眼紙のようにマスを準備。
ここで与えられたplus codeの数を確認したところたくさんあり手動では無理そうなので断念。
画像にプロットするプログラム作成
上で述べたようにマスを埋められればいいので画像を出力することにした。
PGM形式で出力するプログラムを作成。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
FILE *out = fopen("pluscode.pgm", "w");
FILE *mail = fopen("mail.txt", "r");
short *img = (short *)malloc(sizeof(short) * 2*20*20*20*20);
memset(img, 0, sizeof(short) * 2 * 20 * 20 * 20 * 20);
int w[3] = {2, 20, 20};
int h[3] = {1, 20, 20};
char *name = "23456789CFGHJMPQRVWX";
fprintf(out, "P1\n%d %d\n#Hi!\n1\n", 2*20*20, 1*20*20);
char code[20];
int x[3] = {0};
int y[3] = {0};
while (fscanf(mail, "%s", code) != EOF) {
if (code[1] == 'R') {
x[0] = 0;
} else {
x[0] = 1;
}
y[0] = 0;
for (int i = 0; i < strlen(name); i++) {
if (code[2] == name[i]) {
x[1] = i;
}
if (code[3] == name[i]) {
y[1] = i;
}
}
for (int i = 0; i < strlen(name); i++) {
if (code[4] == name[i]) {
x[2] = i;
}
if (code[5] == name[i]) {
y[2] = i;
}
}
printf("%d %d %d : %d %d %d\n", x[0], x[1], x[2], y[0], y[1], y[2]);
img[2*20*20*(20*20*y[0]+20*y[1]+y[2]) + 20*20*x[0] + 20*x[1] + x[2]] = 1;
}
for (int i = 0; i < 2*20*20*20*20; i++) {
fprintf(out, "%d\n", img[i]);
}
fclose(mail);
fclose(out);
}
出力。上下左右を間違えてしまったようだがFLAGが出力された。首を傾けながらメモ。
FLAG : UECTF{D1d_y0u_Kn0w_aB0ut_Km1?}
Discord2 (323pts)
前に思いついたフラグ送信しようとして止めたんだけど、やっぱりあれが良かったなぁ… でもちゃんと思い出せないなぁ。このフォルダにはキャッシュとかも残ってるし、どこかに編集履歴みたいなの残ってないかなぁ…
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...
ローカルストレージが怪しそう。
どこにデータが入っているのか手元で確認。
DraftStoreのdraftに入っている。
$ strings * | grep draft
{"_state":{"1039033893849944084":{"1039070178207617074":{"0":{"timestamp":1667806462142,"draft":"UECTF{Y0U_C4N_S33_Y0UR_DRAFT}"}}}},"_version":2}
FLAG : UECTF{Y0U_C4N_S33_Y0UR_DRAFT}
leveldb (失敗)
Chromiumのローカルストレージはleveldbを使っているらしく、中身を見ようとleveldbを入れたが使い方が分からなかった。
$ sudo apt-get install libsnappy-dev wget curl build-essential cmake gcc sqlite3
$ git clone --recurse-submodules https://github.com/google/leveldb.git
$ cd leveldb
$ mkdir -p build
$ cd build
$ cmake -DCMAKE_BUILD_TYPE=Release .. && cmake --build .
$ sudo make install
guess (356pts)
Please guess my password.
私のパスワードを推測してください。
※総当たりする必要はございません。そういった行為はお控えください。
nc uectf.uec.tokyo 9001
パスワードと入力文字列が同じだとフラグが出力される。
char buf[32];
char pw[32];
(略)
scanf("%32s", buf);
bufとpwの長さを0にするためbufの1byte目とpwの1byte目(バッファオーバーフローさせる)を0にした。
$ echo -en "\0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" | ./chall
Guess my password
> Correct!!!
flag{fake_flag}
$ echo -en "\0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" | nc uectf.uec.tokyo 9001
Guess my password
> Correct!!!
UECTF{Wow_are_you_Esper?}
FLAG : UECTF{Wow_are_you_Esper?}
captain-hook (323pts)
haha, good luck solving this
運も実力のうち!
disassembleしてみる
$ objdump -d captainhook > captainhook.S
randの関数を呼んで取ってきた値を比較し、結果をもとにジャンプしている。
randを2回実行して二回の数値がある値にならないとfwrite関数へジャンプするようになっている
rand関数の値を制御してジャンプしないようにする。
(略)
00000000000011a0 <.text>:
11a0: f3 0f 1e fa endbr64
11a4: 41 54 push %r12
11a6: e8 f5 02 00 00 call 14a0 <fwrite@plt+0x310>
11ab: 31 ff xor %edi,%edi
11ad: e8 6e ff ff ff call 1120 <time@plt>
11b2: 48 89 c7 mov %rax,%rdi
11b5: e8 76 ff ff ff call 1130 <srand@plt>
11ba: e8 31 ff ff ff call 10f0 <rand@plt>
11bf: 41 89 c4 mov %eax,%r12d
11c2: e8 29 ff ff ff call 10f0 <rand@plt>
11c7: 83 c0 03 add $0x3,%eax
11ca: 41 0f af c4 imul %r12d,%eax
11ce: 85 c0 test %eax,%eax
11d0: 75 34 jne 1206 <fwrite@plt+0x76>
11d2: e8 19 ff ff ff call 10f0 <rand@plt>
11d7: 41 89 c4 mov %eax,%r12d
11da: e8 11 ff ff ff call 10f0 <rand@plt>
11df: 41 83 c4 01 add $0x1,%r12d
11e3: 83 c0 03 add $0x3,%eax
11e6: 44 0f af e0 imul %eax,%r12d
11ea: 45 85 e4 test %r12d,%r12d
11ed: 75 17 jne 1206 <fwrite@plt+0x76>
11ef: 48 8d 3d 2a 0e 00 00 lea 0xe2a(%rip),%rdi # 2020
(略)
そこでLD_PRELOAD
環境変数を使った共有ライブラリの関数フックをしてみた。
まずフックする自分用ライブラリのrand関数を作った。rand()が呼ばれた際にscanf()で任意の値を指定できる。
#include <stdio.h>
int rand(void) {
int value;
scanf("%d\n", &value);
return value;
}
共有ライブラリ(.so)としてビルド
$ gcc -g -Wall -D_GNU_SOURCE -fPIC -shared -o myrand.so myrand.c -ldl
フックして実行。
$ LD_PRELOAD=./myrand.so ./captainhook
0
0
0
-3
-3
success
UECTF{hmmmm_how_did_you_solve_this?}
rand関数がアセンブリでは4回callされているが実際は5回呼ばれているように見えるがなぜか分からない。
gdb (失敗)
gdbで見てみるがdebuggerを使っていると見抜かれてしまいできない。
$ gdb ./captainhook
Reading symbols from ./captainhook...
(No debugging symbols found in ./captainhook)
(gdb) r
Starting program: /home/washy/Downloads/captain/captainhook
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
debugger detected!!! bye!!
[Inferior 1 (process 7651) exited with code 01]
(gdb)
運も実力のうち! (失敗)
問題文から、運がよい人は実行しただけで成功するのではと考えた。
アセンブリを見るとrandのシード値でtime()関数の値を使っている。大会中のどこかの時間で成功するタイミングがあるかもしれない。
UNIX時間でいうと1668769200秒から1668942000秒の間。
#include <time.h>
#define TT 1668820449
time_t time(time_t *second) {
time_t t = TT;
// 1668769200 to 1668942000
if (second != NULL)
*second = t;
return t;
}
共有ライブラリの関数フックを使い、time()の返す時間(unix time)を1秒ずつずらしながら順に実行していった。大会終了の1668942000秒まではできなかった(途中で無謀なことに気づいた)。
値をファイル読み書きするのはうまくできずソースファイルの書き換え・リビルドで対応した。
$ for n in {1668769200..1668942000};
> do n=$((n+1)); # 追記:不要では
> cat mytime.c | sed -s 's/#define TT [0-9]*/#define TT '$((n))'/' > mytime.c.new;
> cp mytime.c.new mytime.c;
> gcc -g -Wall -D_GNU_SOURCE -fPIC -shared -o mytime.so mytime.c -ldl;
> LD_PRELOAD=./mytime.so ./captainhook | grep -v failure;
> done
discrete (400pts)
Jumping around in memory
記憶の中でジャンプする
実行ファイルを実行し、適当にパスワードを入力してみる。
$ ./chall
flag: aa
invalid input length
パスワードの長さを特定する。
$ for len in {1..100}; do echo -n "$len: "; cat /dev/urandom | base64 | fold -w $len | head -n 1 | ./chall; echo; done
1: flag: invalid input length
2: flag: invalid input length
3: flag: invalid input length
4: flag: invalid input length
(略)
33: flag: invalid input length
34: flag: Wrong!
35: flag: invalid input length
36: flag: invalid input length
(略)
100: flag: invalid input length
gdbで実行しながら挙動を確認する。strncmpにブレークポイントを仕掛けてみる。
$ gdb ./chall
(略)
Reading symbols from ./chall...
(No debugging symbols found in ./chall)
(gdb) b strncmp
Breakpoint 1 at 0x10a0
(gdb) r
Starting program: /home/washy/Downloads/discrete/chall
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
flag: UECTF{aaaaaaaaaaaaaaaaaaaaaaaaaaa}
Breakpoint 1, __strncmp_avx2 () at ../sysdeps/x86_64/multiarch/strcmp-avx2.S:83
83 ../sysdeps/x86_64/multiarch/strcmp-avx2.S: No such file or directory.
(gdb) x/s $rax
0x7fffffffdd0d: "UEC"
(gdb) x/s $rcx
0x7fffffffdbc0: "UECTF{", 'a' <repeats 27 times>, "}"
(gdb)
x/s $rax
とx/s $rcx
でstrncmpの比較対象文字列を見ながら、一致するようにデータをセットしていった。
何度も比較すると徐々にフラグの全体像が分かってくる。
以下を順に実行した。
set {char}0x7fffffffdbd0 = 'U', {char}0x7fffffffdbd1 = 'E', {char}0x7fffffffdbd2 = 'C', {char}0x7fffffffdbd3 = 0
continue
set {char}0x7fffffffdbd1 = 'U', {char}0x7fffffffdbd2 = 'E', {char}0x7fffffffdbd3 = 'C', {char}0x7fffffffdbd4 = 0
continue
set {char}0x7fffffffdbd2 = 'U', {char}0x7fffffffdbd3 = 'E', {char}0x7fffffffdbd4 = 'C', {char}0x7fffffffdbd5 = 0
continue
set {char}0x7fffffffdbd3 = 'T', {char}0x7fffffffdbd4 = 'F', {char}0x7fffffffdbd5 = '{', {char}0x7fffffffdbd6 = 0
continue
set {char}0x7fffffffdbd4 = 'T', {char}0x7fffffffdbd5 = 'F', {char}0x7fffffffdbd6 = '{', {char}0x7fffffffdbd7 = 0
continue
set {char}0x7fffffffdbd5 = 'T', {char}0x7fffffffdbd6 = 'F', {char}0x7fffffffdbd7 = '{', {char}0x7fffffffdbd8 = 0
continue
set {char}0x7fffffffdbd6 = 'd', {char}0x7fffffffdbd7 = 'y', {char}0x7fffffffdbd8 = 'n', {char}0x7fffffffdbd9 = 0
continue
set {char}0x7fffffffdbd7 = 'd', {char}0x7fffffffdbd8 = 'y', {char}0x7fffffffdbd9 = 'n', {char}0x7fffffffdbda = 0
continue
set {char}0x7fffffffdbd8 = 'd', {char}0x7fffffffdbd9 = 'y', {char}0x7fffffffdbda = 'n', {char}0x7fffffffdbdb = 0
continue
set {char}0x7fffffffdbd9 = 'a', {char}0x7fffffffdbda = 'm', {char}0x7fffffffdbdb = 'i', {char}0x7fffffffdbdc = 0
continue
set {char}0x7fffffffdbda = 'a', {char}0x7fffffffdbdb = 'm', {char}0x7fffffffdbdc = 'i', {char}0x7fffffffdbdd = 0
continue
set {char}0x7fffffffdbdb = 'a', {char}0x7fffffffdbdc = 'm', {char}0x7fffffffdbdd = 'i', {char}0x7fffffffdbde = 0
continue
set {char}0x7fffffffdbdc = 'c', {char}0x7fffffffdbdd = '_', {char}0x7fffffffdbde = 's', {char}0x7fffffffdbdf = 0
continue
set {char}0x7fffffffdbdd = 'c', {char}0x7fffffffdbde = '_', {char}0x7fffffffdbdf = 's', {char}0x7fffffffdbe0 = 0
continue
set {char}0x7fffffffdbde = 'c', {char}0x7fffffffdbdf = '_', {char}0x7fffffffdbe0 = 's', {char}0x7fffffffdbe1 = 0
continue
set {char}0x7fffffffdbdf = 't', {char}0x7fffffffdbe0 = 'a', {char}0x7fffffffdbe1 = 't', {char}0x7fffffffdbe2 = 0
continue
set {char}0x7fffffffdbe0 = 't', {char}0x7fffffffdbe1 = 'a', {char}0x7fffffffdbe2 = 't', {char}0x7fffffffdbe3 = 0
continue
set {char}0x7fffffffdbe1 = 't', {char}0x7fffffffdbe2 = 'a', {char}0x7fffffffdbe3 = 't', {char}0x7fffffffdbe4 = 0
continue
set {char}0x7fffffffdbe2 = 'i', {char}0x7fffffffdbe3 = 'c', {char}0x7fffffffdbe4 = '_', {char}0x7fffffffdbe5 = 0
continue
set {char}0x7fffffffdbe3 = 'i', {char}0x7fffffffdbe4 = 'c', {char}0x7fffffffdbe5 = '_', {char}0x7fffffffdbe6 = 0
continue
set {char}0x7fffffffdbe4 = 'i', {char}0x7fffffffdbe5 = 'c', {char}0x7fffffffdbe6 = '_', {char}0x7fffffffdbe7 = 0
continue
set {char}0x7fffffffdbe5 = 's', {char}0x7fffffffdbe6 = 't', {char}0x7fffffffdbe7 = 'r', {char}0x7fffffffdbe8 = 0
continue
set {char}0x7fffffffdbe6 = 's', {char}0x7fffffffdbe7 = 't', {char}0x7fffffffdbe8 = 'r', {char}0x7fffffffdbe9 = 0
continue
set {char}0x7fffffffdbe7 = 's', {char}0x7fffffffdbe8 = 't', {char}0x7fffffffdbe9 = 'r', {char}0x7fffffffdbea = 0
continue
set {char}0x7fffffffdbe8 = 'i', {char}0x7fffffffdbe9 = 'n', {char}0x7fffffffdbea = 'g', {char}0x7fffffffdbeb = 0
continue
set {char}0x7fffffffdbe9 = 'i', {char}0x7fffffffdbea = 'n', {char}0x7fffffffdbeb = 'g', {char}0x7fffffffdbec = 0
continue
set {char}0x7fffffffdbea = 'i', {char}0x7fffffffdbeb = 'n', {char}0x7fffffffdbec = 'g', {char}0x7fffffffdbed = 0
continue
set {char}0x7fffffffdbeb = 's', {char}0x7fffffffdbec = '_', {char}0x7fffffffdbed = '2', {char}0x7fffffffdbee = 0
continue
set {char}0x7fffffffdbec = 's', {char}0x7fffffffdbed = '_', {char}0x7fffffffdbee = '2', {char}0x7fffffffdbef = 0
continue
set {char}0x7fffffffdbed = 's', {char}0x7fffffffdbee = '_', {char}0x7fffffffdbef = '2', {char}0x7fffffffdbf0 = 0
continue
set {char}0x7fffffffdbee = '0', {char}0x7fffffffdbef = '2', {char}0x7fffffffdbf0 = '2', {char}0x7fffffffdbf1 = 0
continue
set {char}0x7fffffffdbef = '0', {char}0x7fffffffdbf0 = '2', {char}0x7fffffffdbf1 = '2', {char}0x7fffffffdbf2 = 0
continue
set {char}0x7fffffffdbf0 = '0', {char}0x7fffffffdbf1 = '2', {char}0x7fffffffdbf2 = '2', {char}0x7fffffffdbf3 = 0
continue
FLAG : UECTF{dynamic_static_strings_2022}
rot13 (unsolved)
We love ROT13.
みんな大好きROT13
nc uectf.uec.tokyo 9003
PWN系の問題。libc-2.31.soが渡されていたのでreturn to libc攻撃などをするのかと思ったがよく分からなかった。
buffer_overflow_2 (unsolved)
I made it a little harder.
ちょっと難しくしました。
nc uectf.uec.tokyo 9002
PWN系の問題。バッファオーバーフローさせればよいのは分かった(それはそう)が何をすればよいのか分からなかった。
shellを起動させてみるのがいいのだろうか?(できるのかもわからないが)
dotnet (unsolved)
簡単にデコンパイルできるフレームワークを使って書いたので、難読化を施しました。 なので、難読化が正しく行われていれば秘密情報にはアクセスできないはずです・・・ (アプリケーションは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)
REV系の問題。デコンパイルしようといろいろなツールを入れようとしたがうまくいかなかった。gdbで挙動を見てみたがうまくいかず。
monodis (失敗)
動かない?
$ sudo apt install mono-complete
$ monodis --assembly ./chall_x86_64_linux
Error while trying to process ./chall_x86_64_linux
結果・まとめ
4175点をとれ8位になりました。
CTF楽しかったです。またやりたいです。
PWN系が比較的苦手だと分かりました。
gdbはほとんど使ったことがありませんでしたが、スタックの見方など知見が得られました。
個人的に特に好きな問題はOSINTとWHEREAMIとcaptain-hookです!試行錯誤でき達成感がありました。
UECTF2022のみなさん、ありがとうございました!