SECCON Beginners CTF 2021 Writeup (一部)

# 概要

SECCON Beginners CTF 2021 にソロで参加した。
昨年別のイベントで初めてCTFに触れて以来、2度目のCTFだったがwelcome含めて7問解くことができた。
勉強と備忘録も兼ねて、解けた問題と惜しかった問題について自分なりのWriteupを書いていく。

# [crypto]simple_RSA
与えられたファイルを解凍すると、以下の2つのファイルが入っていた。

```output.txt
n = 17686671842400393574730512034200128521336919569735972791676605056286778473230718426958508878942631584704817342304959293060507614074800553670579033399679041334863156902030934895197677543142202110781629494451453351396962137377411477899492555830982701449692561594175162623580987453151328408850116454058162370273736356068319648567105512452893736866939200297071602994288258295231751117991408160569998347640357251625243671483903597718500241970108698224998200840245865354411520826506950733058870602392209113565367230443261205476636664049066621093558272244061778795051583920491406620090704660526753969180791952189324046618283
e = 3
c = 213791751530017111508691084168363024686878057337971319880256924185393737150704342725042841488547315925971960389230453332319371876092968032513149023976287158698990251640298360876589330810813199260879441426084508864252450551111064068694725939412142626401778628362399359107132506177231354040057205570428678822068599327926328920350319336256613
```

```problem.py
from Crypto.Util.number import *
from flag import flag

flag = bytes_to_long(flag.encode("utf-8"))

p = getPrime(1024)
q = getPrime(1024)
n = p * q
e = 3

assert 2046 < n.bit_length()
assert 375 == flag.bit_length()

print("n =", n)
print("e =", e)
print("c =", pow(flag, e, n))
```
最初はmsieveとyafuで`n`の素因数分解をしてみたが終わらず、解き方が違うと考えて他の方法を探った。
調べてみると`e`が小さいときに使える**Low Public Exponent Attack**なるものを発見。
これを利用して複合に成功し、flagを得ることができた。
参考：[公開鍵暗号系の処理メモ「Low Public-Exponent Attack」](http://norio567.hatenablog.com/entry/2019/06/07/214800#RSA%E3%81%B8%E3%81%AE%E6%94%BB%E6%92%83Low-Public-Exponent-Attacke%E3%81%8C%E5%B0%8F%E3%81%95%E3%81%84e3%E7%AD%89%E5%A0%B4%E5%90%88)

```solve.py
from Crypto.Util.number import *
import gmpy2

n = 17686671842400393574730512034200128521336919569735972791676605056286778473230718426958508878942631584704817342304959293060507614074800553670579033399679041334863156902030934895197677543142202110781629494451453351396962137377411477899492555830982701449692561594175162623580987453151328408850116454058162370273736356068319648567105512452893736866939200297071602994288258295231751117991408160569998347640357251625243671483903597718500241970108698224998200840245865354411520826506950733058870602392209113565367230443261205476636664049066621093558272244061778795051583920491406620090704660526753969180791952189324046618283
e = 3
c = 213791751530017111508691084168363024686878057337971319880256924185393737150704342725042841488547315925971960389230453332319371876092968032513149023976287158698990251640298360876589330810813199260879441426084508864252450551111064068694725939412142626401778628362399359107132506177231354040057205570428678822068599327926328920350319336256613

m,result = gmpy2.iroot(c,e)

print(m)
print(bytes.fromhex(format(m,'x')).decode('utf-8'))
```

```flag
ctf4b{0,1,10,11...It's_so_annoying.___I'm_done}
```

# [reversing]only_read
バイナリが配布されたので、とりあえず`strings`で中身を見てみるが、flagらしきものは無い。
実行してみると、入力を受け付けた後`Incorrect`と表示される。
Ghidraで解析してみると以下のような部分があったので、この通りに読んでflagを得た。

```c
  if (((((((char)local_28 == 'c') && (local_28._1_1_ == 't')) && (local_28._2_1_ == 'f')) &&
       (((local_28._3_1_ == '4' && (local_28._4_1_ == 'b')) &&
        ((local_28._5_1_ == '{' && ((local_28._6_1_ == 'c' && (local_28._7_1_ == '0')))))))) &&
      (((char)local_20 == 'n' &&
       ((((((local_20._1_1_ == '5' && (local_20._2_1_ == 't')) && (local_20._3_1_ == '4')) &&
          ((local_20._4_1_ == 'n' && (local_20._5_1_ == 't')))) &&
         ((local_20._6_1_ == '_' && ((local_20._7_1_ == 'f' && ((char)local_18 == '0')))))) &&
        (local_18._1_1_ == 'l')))))) &&
     ((((local_18._2_1_ == 'd' && (local_18._3_1_ == '1')) && ((char)local_14 == 'n')) &&
      ((local_14._1_1_ == 'g' && (local_12 == '}')))))) {
    puts("Correct");
  }
  else {
    puts("Incorrect");
  }
```

```flag
ctf4b{c0n5t4nt_f0ld1ng}
```

# [pwnable]rewriter
用意されたサーバーに接続してみると以下のように表示された。
また、サーバーで動いているプログラムも配布された。

```
[Addr]              |[Value]
====================+===================
 0x00007ffc3a4cb4a0 | 0x0000000000000000  <- buf
 0x00007ffc3a4cb4a8 | 0x0000000000000000
 0x00007ffc3a4cb4b0 | 0x0000000000000000
 0x00007ffc3a4cb4b8 | 0x0000000000000000
 0x00007ffc3a4cb4c0 | 0x0000000000000000  <- target
 0x00007ffc3a4cb4c8 | 0x0000000000000000  <- value
 0x00007ffc3a4cb4d0 | 0x0000000000401520  <- saved rbp
 0x00007ffc3a4cb4d8 | 0x00007f3d74a36bf7  <- saved ret addr
 0x00007ffc3a4cb4e0 | 0x0000000000000001
 0x00007ffc3a4cb4e8 | 0x00007ffc3a4cb5b8

Where would you like to rewrite it?
>
```

```src.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <err.h>

#define BUFF_SIZE 0x20

void win() {
    execve("/bin/cat", (char*[3]){"/bin/cat", "flag.txt", NULL}, NULL);
}

void show_stack(unsigned long *stack);

int main() {
    unsigned long target = 0, value = 0;
    char buf[BUFF_SIZE] = {0};
    show_stack(buf);
    printf("Where would you like to rewrite it?\n> ");
    buf[read(STDIN_FILENO, buf, BUFF_SIZE-1)] = 0;
    target = strtol(buf, NULL, 0);
    printf("0x%016lx = ", target);
    buf[read(STDIN_FILENO, buf, BUFF_SIZE-1)] = 0;
    value = strtol(buf, NULL, 0);
    *(long*)target = value;
}

void show_stack(unsigned long *stack) {
    printf("\n%-20s|%-20s\n", "[Addr]", "[Value]");
    puts("====================+===================");
    for (int i = 0; i < 10; i++) {
        printf(" 0x%016lx | 0x%016lx ", &stack[i], stack[i]);
        if (&stack[i] == stack)
            printf(" <- buf");
        if (&stack[i] == ((unsigned long)stack + BUFF_SIZE))
            printf(" <- target");
        if (&stack[i] == ((unsigned long)stack + BUFF_SIZE + 0x8))
            printf(" <- value");
        if (&stack[i] == ((unsigned long)stack + BUFF_SIZE + 0x10))
            printf(" <- saved rbp");
        if (&stack[i] == ((unsigned long)stack + BUFF_SIZE + 0x18))
            printf(" <- saved ret addr");
        puts("");
    }
    puts("");
}

__attribute__((constructor))
void init() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    alarm(60);
}
```

指定したアドレスを書き換えるプログラムのようなので、どうにかして`win()`を呼んでやれば良いと考た。
`gdb`で`win()`のアドレスを探ると`0x4011f6`と判明。
`saved ret addr`を`0x4011f6`に書き換えるように入力するとflagを得られる。
※この書き換えをローカル環境でやってもflagファイルが無いのでエラーとなる。私は、なぜかその後リモートで試すことなく諦めてしまった。長時間やりすぎたのに加え、他の問題に気を取られて頭が回って居なかったのだと思う。

```
ctf4b{th3_r3turn_4ddr355_15_1n_th3_5t4ck}
```

# [web]osoba
指定のULRに飛ぶとこんなページになっていた。

>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1054727/2f0fb1e7-0776-5cc6-9c3f-69d769e8c452.png)

ページの詳細を見ると`/?page=public/wip.html`にアクセスし、エラーが表示される。
問題文に「フラグはサーバの /flag にあります！」と書かれていたので`/?page=/flag`に書き換えてみるとflagが表示された。

```
ctf4b{omisoshiru_oishi_keredomo_tsukuruno_taihen}
```

# [misc]git-leak
ファイルを解凍すると、以下のような構成だった。

>dist/
> ├.git/
> ├README.md
> └note.

`git reflog`で履歴を見てみると、以下のようになっていた。

```bash
e0b545f (HEAD -> master) HEAD@{0}: commit (amend): feat: めもを追加
80f3044 HEAD@{1}: commit (amend): feat: めもを追加
b3bfb5c HEAD@{2}: rebase -i (finish): returning to refs/heads/master
b3bfb5c HEAD@{3}: commit (amend): feat: めもを追加
7387982 HEAD@{4}: rebase -i: fast-forward
36a4809 HEAD@{5}: rebase -i (start): checkout HEAD~2
7387982 HEAD@{6}: reset: moving to HEAD
7387982 HEAD@{7}: commit: feat: めもを追加
36a4809 HEAD@{8}: commit: feat: commit-treeの説明を追加
9ac9b0c HEAD@{9}: commit: change: 順番を変更
8fc078d HEAD@{10}: commit: feat: git cat-fileの説明を追加
d3b47fe HEAD@{11}: commit: feat: fsckを追記する
f66de64 HEAD@{12}: commit: feat: reflogの説明追加
d5aeffe HEAD@{13}: commit: feat: resetの説明を追加
a4f7fe9 HEAD@{14}: commit: feat: git logの説明を追加
9fcb006 HEAD@{15}: commit: feat: git commitの説明追加
6d21e22 HEAD@{16}: commit: feat: git addの説明を追加
656db59 HEAD@{17}: commit: feat: add README.md
c27f346 HEAD@{18}: commit (initial): initial commit
```
`reset`しているあたりが怪しいと思い、その前まで戻してみると、`flag.txt`を発見。
中身を確認するとflagが表示された。

```bash
$ git reset --hard "HEAD@{7}"
$ git checkout
$ ls
README.md  flag.txt  note.md
$ cat flag.txt
```

```
ctf4b{0verwr1te_1s_n0t_c0mplete_1n_G1t}
```

# [misc]Mail_Address_Validator
用意されたサーバーにアクセスすると、以下のように表示された。
また、サーバーで動いているプログラムも配布された。

```
I check your mail address.
please puts your mail address.
```

```main.rb
#!/usr/bin/env ruby
require 'timeout'

$stdout.sync = true
$stdin.sync = true

pattern = /\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i

begin
  Timeout.timeout(60) {
    Process.wait Process.fork {
      puts "I check your mail address."
      puts "please puts your mail address."
      input = gets.chomp
      begin
        Timeout.timeout(5) {
          if input =~ pattern
            puts "Valid mail address!"
          else
            puts "Invalid mail address!"
          end
        }
      rescue Timeout::Error
        exit(status=14)
      end
    }
    
    case Process.last_status.to_i >> 8
    when 0 then
      puts "bye."
    when 1 then
      puts "bye."
    when 14 then
      File.open("flag.txt", "r") do |f|
        puts f.read
      end
    else
      puts "What's happen?"
    end
  } 
rescue Timeout::Error
  puts "bye."
end
```

メールアドレスのチェックに5秒以上かかるとタイムアウトしてflagを表示するようなので、正規表現でのマッチングのステップ数を表示してくれるサイトを参考に、この条件でのマッチングに時間がかかる文字列を組み上げてflagを得た。
参考：[regex101.com](https://regex101.com/)

```resolv.txt
ab.a@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz.
```

```
ctf4b{1t_15_n0t_0nly_th3_W3b_th4t_15_4ff3ct3d_by_ReDoS}
```


# [misc]depixelization
ファイルを解凍すると、以下の画像ファイルとプログラムが入っていた。
>output.png
>![output.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1054727/47f3dab7-a88c-1998-fbbc-e9dc63a405fe.png "output.png")

```pixelization.py
import cv2
import numpy as np

flag = "**********flag**********"

print("FLAG: " + flag)

images = np.full((100, 85, 3), (255,255,255), dtype=np.uint8)

for i in flag:

    # char2img
    img = np.full((100, 85, 3), (255,255,255), dtype=np.uint8)
    cv2.putText(img, i, (0, 80), cv2.FONT_HERSHEY_PLAIN, 8, (0, 0, 0), 5, cv2.LINE_AA)

    # pixelization
    cv2.putText(img, "P", (0, 90), cv2.FONT_HERSHEY_PLAIN, 7, (0, 0, 0), 5, cv2.LINE_AA)
    cv2.putText(img, "I", (0, 90), cv2.FONT_HERSHEY_PLAIN, 8, (0, 0, 0), 5, cv2.LINE_AA)
    cv2.putText(img, "X", (0, 90), cv2.FONT_HERSHEY_PLAIN, 9, (0, 0, 0), 5, cv2.LINE_AA)
    simg = cv2.resize(img, None, fx=0.1, fy=0.1, interpolation=cv2.INTER_NEAREST) # WTF :-o
    img = cv2.resize(simg, img.shape[:2][::-1], interpolation=cv2.INTER_NEAREST)

    # concat
    if images.all():
        images = img
    else:
        images = cv2.hconcat([images, img])

cv2.imwrite("output.png", images)
```

このプログラムを使ってモザイク化されたflagの画像のようなので、同じようにflagのフォーマットである`[a-z],[A-Z],[0-9],[{}_!#$%&.]`の画像を生成し、マッチングするプログラムを作成すればflagが得られると考えた。
しかし、pythonで画像処理のコードを書いたことが無く残り時間も少なかったため、フラグの文字数が30文字程度でピクセルもはっきり見えていることから、**これから調べてコード書くより目視でマッチングした方が早いのではないかという結論に至った。**
したがって`pixelization.py`を少し書き換えて以下のようにそれぞれの文字をモザイク化した画像を生成し、目視でマッチングしてflagを得た。

>[a-z].png
>![[a-z].png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1054727/9f21131a-1493-1c37-74eb-dbf78955357a.png)

>[A-Z].png
>![[A-Z].png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1054727/688ddcee-4347-1f84-5edd-16005f7ac1eb.png)

>[1234567890].png
<img alt="[1234567890].png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1054727/0e69bbdd-3bc9-80a3-7c7d-b9d04e4a3b27.png" width=41%>

>[_!#$%&.].png
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1054727/5442ee19-cd8e-f724-90e4-be9370d932d6.png" width=32%>

```
ctf4b{1f_y0u_p1x_y0u_c4n_d3p1x}
```

# 感想
最後の問題のようにゴリ押しで解いてしまった問題や、分かっていたのに解けなかった問題もあり課題も残るが、自分としてはよく解けた方だと思う。今回が2回目のCTFだったが、今後も色々勉強しながらまた機会があれば挑戦して行きたい。