2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

SECCON Beginners CTF 2021 Writeup (一部)

Posted at

概要

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」

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'))
ctf4b{0,1,10,11...It's_so_annoying.___I'm_done}

[reversing]only_read

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

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

ctf4b{th3_r3turn_4ddr355_15_1n_th3_5t4ck}

[web]osoba

指定のULRに飛ぶとこんなページになっていた。

image.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で履歴を見てみると、以下のようになっていた。

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が表示された。

$ 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

resolv.txt
ab.a@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz.
ctf4b{1t_15_n0t_0nly_th3_W3b_th4t_15_4ff3ct3d_by_ReDoS}

[misc]depixelization

ファイルを解凍すると、以下の画像ファイルとプログラムが入っていた。

output.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

[A-Z].png
[A-Z].png

[1234567890].png

[1234567890].png

[_!#$%&.].png

ctf4b{1f_y0u_p1x_y0u_c4n_d3p1x}

感想

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

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?