h_nosonの日記

競プロ、CTFなど

SECCON 2019 国際決勝 Writeup

SECCON 2019 国際決勝にHarekazeとしてhiww, st98, hikaliumと参加して11位でした。

競技形式はKing of the hillという、Jeopardy形式のAttack flagの得点とDefense Keywordを書き込み続けて得る得点で競うものでした。

大問は6問あって自分が触ったのは壱、四、六(の一部)。

バイナリが与えられていて

Access to port (((your network address) >> 8) % 50) + 10000) on address 10.2.1.1

と書いてある。が、アクセス出来ない…。バイナリを見たら正しくは(((your network address) >> 8) % 50) * 1000 + 10000)でアクセスしたらAttack flagが降ってきた。

tcpで繋いだ後に何度か開けられるポートにudpで繋ぐとtokenがフラグページに書き込まれてDefense Pointが入る。ポート番号はrand()で決められていたけどsrand(0); srand(rand());をしていたので予測できる。pythonでCのrand()を呼ぶ方法がわからなかったので乱数を返すだけのプログラムを呼んだ。(今から考えればポート番号は固定だから埋め込みでよかった)

#include <stdio.h>

int main(void) {
    srand(0);
    srand(rand());
    for (;;) {
        printf("%d\n", rand());
        getchar();
    }
    return 0;
}
#!/usr/bin/env python
from pwn import *
import requests

if len(sys.argv) == 1:
    ip = 2130706433
    index = ((ip >> 8) % 50)
    print index
    process(['./gameserv.bin', str(index)])
    address = 'localhost'
else:
    address = '10.2.1.1'
    ip = 3232238389
    index = ((ip >> 8) % 50)
port = index * 1000 + 10000

randgen = process('./randgen')

def rand():
    randgen.sendline('')
    return int(randgen.recvline(False))

COOKIES = {
              'PHPSESSID': '27a9d61c90d3e5c5d50839f38ce33344fbd84d56'
          }
URL = 'http://score-int.seccon/flagwords/'

def get_keyword():
    r = requests.get(URL, cookies=COOKIES).content.decode('utf-8')
    return re.findall(r'Harekaze</th><td>(.+)</th>', r)[0]

context.log_level = 'DEBUG'
token = get_keyword()
s = remote(address, port)
s.sendlineafter('Token? :', token)

for i in range(10):
    time.sleep(1)
    x = rand() % 1000
    while x == 306:
        x = rand() % 1000
    t = remote(address, port + x, typ='udp')
    t.sendline(token)
    t.recv()
    t.close()
s.interactive()

ここからが問題で、一度でも間違えてサーバー側のコネクションが切れないままにしてしまうと再度接続できなくなった。作問者に聞きに行ったらポート1つにつき1つしかコネクションを張れないようにしているらしく増やしてくれとお願いしたらそれはできないと一蹴された。でも可哀想だから再起動してあげると再起動してもらったら1度だけうまくいって情けの10点をもらった。その後うまくいったのは2日目の始めだけで結局28点しか取れなかった。そして解けたのはこれだけ…

SECCON 2019 国内本選 writeup | にろきのメモ帳によるとUDPを消費すると復活したらしい。

SECCON 2019 Online CTFのfollow-meに似た問題でIntel Pinを使ったトレース結果を再現する入力を探す問題。Attack Pointはhikaliumさんが取ってくれた。 hikalium.hatenablog.jp

Defense Pointは1時間毎に更新されるバイナリとトレース結果に対しての入力を見つけると入る。入力の形式は全部同じで12341234a5678bのような数値が4桁固定の逆ポーランド記法。取り敢えずfollow-meのwriteupで自動化しているものを探したらWriteUpz/2019_SECCON_CTF/follow-me at master · thebabush/WriteUpz · GitHubを見つけたのでこれを使うことにした。今回の問題に合わせたりうまくいかない部分を直してそろそろ乱択ではない方法を考えるかというところで時間が終了した。

使った方法ではQBDIを使っていたけどIntel Pin使った方がdiffが綺麗に取れるしよかったなと反省。解き方としては全ての入力に対する出力とトレースのテーブルを作って(可能なら)貪欲で決めていくのがいいのだろうか。

[Fuzzing] syzbot panic

Solve 5 questions listed below, and submit concatenated answers in SECCON{$answer_for_Q1+$answer_for_Q2+$answer_for_Q3+$answer_for_Q4+$answer_for_Q5} format.
Hint: Total length of answer string in SECCON{$answer_for_Q1+$answer_for_Q2+$answer_for_Q3+$answer_for_Q4+$answer_for_Q5} format will be 73 characters.

(Q1) What is FQDN (in all lower characters) of a website that manages the state of bugs syzbot has found?

(Q2) What is SHA-1 (only first 10 characters, in lower hexadecimal format) of a commit that explains the following improvement?
syzbot was unable to parse Linux kernel's messages when multiple threads concurrently emitted kernel messages. As a result, many reports had been discarded as "corrupted" indicating that "something went wrong but syzbot could not understand what has happened". After this patch was merged, ability to understand what has happened improved significantly. This patch was written by Tetsuo Handa.

(Q3) What is SHA-1 (only first 10 characters, in lower hexadecimal format) of a commit that fixed the following problem?
A local unprivileged user was able to trigger soft lockup using a reproducer shown below. This patch was written by Linus Torvalds.

  ----------------------------------------
  #define _GNU_SOURCE
  #include 
  #include 
  #include 
  #include 
  #include 
  #include 
  #include 
  #include 
  
  static void *thr(void *arg)
  {
    char c;
    read(*(int *) arg, &c, 1);
    return 0;
  }
  
  int main(int argc, char *argv[])
  {
    char buf[sizeof(struct termios) + 64] = { };    
    int zero = 0;   
    int ptyno = 0;  
    int fd = open("/dev/ptmx", O_RDONLY);   
    int fd2;    
    pthread_t th;   
    buf[0x9] = 0xfd;    
    buf[0xc] = buf[0xd] = buf[0xe] = buf[0xf] = buf[0x10] = 0xff;   
    ioctl(fd, TCSETS, buf); 
    ioctl(fd, TIOCSPTLCK, &zero);   
    ioctl(fd, TCXONC, TCIOFF);  
    if (ioctl(fd, TIOCGPTN, &ptyno))    
      return -1;
    sprintf(buf, "/dev/pts/%d", ptyno); 
    fd2 = open(buf, O_RDONLY);  
    pthread_create(&th, 0, thr, &fd2);  
    sleep(1);   
    ioctl(fd2, FIONREAD, buf);  
    return 0;
  }
  ----------------------------------------
(Q4) What is SHA-1 (only first 10 characters, in lower hexadecimal format) of a commit that fixed the following problem?
Since passing arbitrary arguments to system calls as root user causes legitimate problems (e.g. hang up, reboot), fuzzers are currently forced to blacklist some arguments that legitimately damage the target. In April 2018, syzbot generated a report which confused developers because it was believed that arguments which are known to generate this report are blacklisted. But it turned out that syzbot was by error passing arguments which should have been blacklisted during the fuzz testing.

(Q5) What is SHA-1 (only first 10 characters, in lower hexadecimal format) of a commit that explains the following improvement?
Currently, syzbot might by error generate C reproducer programs using incorrect structure definition (e.g. "struct serial_struct"). Therefore, a utility to validate correctness of structure definition was added.

syzbotとLinux kernelのbugsを直したり説明したりしているcommitを探してhashの先頭10文字を答える問題。+が文字列の結合なのかフラグに含まれる文字なのかわからなかったけど長さが73文字というヒントからおそらく文字としてフラグに含まれる。

それぞれそれらしいcommitなどを見つけたけどどれが間違っているかわからないまま終了…

Q1: syzkaller.appspot.com
Q2: 15ff2069cb https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=15ff2069cb7f967d
Q3: 966031f340 https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=966031f340185eddd05affcf72b740549f056348
Q4: 95fe19c19e https://github.com/google/syzkaller/commit/95fe19c19e596446412626b048d950de6ce8c886
Q5: 77f435b4f7 https://github.com/google/syzkaller/commit/77f435b4f716cca71ebd55ae083fcfa4b220ebdc

[QR Rev Pwn] QR Decoder

QRコードを送れるページとバイナリが渡される。
1つ目のフラグはHello, world!QRコードにして投げれば出てくるらしくst98さんが取ってくれた。2つ目はpwnらしいということで回ってきた。

バイナリは小さくて単純。

undefined8 main(int iParm1,char **ppcParm2)

{
  int __fd;
  char *__file;
  undefined8 uVar1;
  FILE *__stream;
  char local_1c8 [8];
  undefined auStack448 [248];
  stat sb;
  
  __file = __xpg_basename(*ppcParm2);
  if (iParm1 == 2) {
    __file = ppcParm2[1];
    __fd = stat(__file,&sb);
    if (__fd == 0) {
      sprintf(local_1c8,"zbarimg %s",__file);
      __stream = popen(local_1c8,"r");
      if (__stream == (FILE *)0x0) {
        perror(local_1c8);
        uVar1 = 1;
      }
      else {
        local_1c8[0] = '\0';
        fread(local_1c8,1,sb.st_size & 0xffffffff,__stream);
        fclose(__stream);
        printf("The decoded string is: %s\n",auStack448);
        __fd = strcmp(local_1c8,"QR-Code:Hello, world!\n");
        if (__fd == 0) {
          __fd = open("./flag1.html",0);
          if (__fd < 0) {
            uVar1 = 1;
          }
          else {
            read(__fd,local_1c8,0x100);
            close(__fd);
            printf("%s",local_1c8);
            uVar1 = 0;
          }
        }
        else {
          uVar1 = 0;
        }
      }
    }
    else {
      perror(__file);
      uVar1 = 1;
    }
  }
  else {
    fprintf(stderr,"Usage: %s <filename>\n",__file);
    uVar1 = 1;
  }
  return uVar1;
}

見た感じ怪しいのはpopenの入力が外から渡されているのとfreadでSBOFするところ。popenの入力にはファイル名が入ってきてるけどたとえファイル名を操作できたとしても(ファイル名が/tmp/xxxxになっていたから出来なそうだが)statでファイルが存在するかチェックしているのでそこからシェルを取ったりするのは厳しい。オーバーフローの方はcanaryが無効化されているのでそれっぽいけど印字可能な文字しかQRコードとして渡せないのと__streamが潰されてfcloseで落ちるのでこれも厳しかった。残るはzbarimg脆弱性だったけど見つからなかったので大人しくwriteupを待つことに…が、懇親会で解いた人たちに聞いてみたらhttp://10.1.5.1:8182/cgi-bin/flag2.txtにフラグがあったらしい(は?)。しばらくしたら見れなくなったという噂もあるので想定解がそれ以外にあることを願います。

感想

多くの人が言っているように問題に不備が多かったり、使い回しだったり、guess要素があったり、pwn, web, cryptoがなくて普段CTFに参加している人たちにとっては不満があったと思います。ただ個人的には一箇所に大勢集まってCTFをやることはなかなか出来ないのでそういった場に参加できてとても楽しめました。運営の皆様ありがとうございました。