Depixを利用したモザイク画像の復元と検証

このブログは、株式会社Armorisアルバイトのyanorei32がVRChatの沼底から書いています。 あるもりすぶろぐの内容は個人の意見です。


本稿では、beurtschipper/Depix を中心にDepixを紹介する。

概要を README.md から抜粋する。

Depix is a tool for recovering passwords from pixelized screenshots.

This implementation works on pixelized images that were created with a linear box filter. In this article I cover background information on pixelization and similar research.

具体的には以下のようなスクリーンショットからの文字列復元処理が行えるという主張である。 f:id:Armoris:20220308172039p:plain

元となったとされているRecovering passwords from pixelized screenshots を読むと、どうやら非常に古典的な手法であることがわかる。

紹介記事に GPUAI といった記述を見た為、 CNN的手法を予感したが、どうやら異なるようだ。 (ソースコードを見ても単純なfor文が書かれているだけだ。)

このソフトウェアの動作を知る為には、まず、モザイク処理に関して深く知る必要がある。

1. モザイク処理の基礎

モザイク処理は実際にはどの様な処理を行っているのだろうか。画像編集ソフトで以下の様なフィルター設定を見たことがあるかもしれない。

Dibas32のモザイク処理UI

これは、非常に単純なフィルタで、近傍NxNピクセルの平均色を新しいピクセル色にするものである。(値として平均化する場合と、linear sRGB空間で行う場合などもある。各ソフトウェアにより実装が異なる場合がある。)

実際に処理すると以下の様になる。

モザイク処理例

動作は単純だが、適切なサイズでモザイク処理を行うと、処理結果から得られる情報は少なく非常に強力だ。右図から左図をヒントなしに推測することは困難だろう。

2. ヒントありのモザイク復元

ではヒントが有る場合はどうだろうか。例えば以下のような制約を課してみよう。

  • フォント/レンダリングが既知
  • 文字単位でのモザイク処理の連結である
    • 16x16の文字が丁度4x4単位でモザイク処理されているなど

この場合だと偶然の重複がない限り、1:1対応で元の文字を復元することができる。

ではそれらの制約は現実に満たすことができるだろうか。

2-1. フォント/レンダリングが既知

この条件を満たすことは比較的容易だろう。 例えば、以下の様な画像を見たとき、人はxtermで有ることや、そのデフォルトフォント fixed で有ること、xtermの標準描画であることが推測できるだろう。

また、アプリケーションの他、OS標準フォントなどから推測することも可能であることから、比較的容易であると言える。

2-2. 文字位置が既知/モザイクと揃っている

制約「文字単位でのモザイク処理の連結である」を満たすのは困難であると言える。実際、Depixではこの制約なしに復元を試みている。

どの様に行っているのだろうか。

3. 連続文字列のリファレンス (Depix)

まず以下を仮定する。

事前に多くの連続文字のリファレンスを用意し、それに対するモザイク化を全通り行う。(サイズ5のピクセル化ならば25通り)

そこから、モザイク化された色情報から文字片への変換辞書を作る。

変換辞書を元に、辞書・入力データ間の距離を測りマッチング処理を行うアプローチがDepixの本質である。

3.1 beurtschipper/Depix の試用

以下に示すbeurtschipper/Depix 内の images/searchimages/debruinseq_notepad_Windows10_closeAndSpaced.png 等が辞書にあたる。

以下の様にスクリプトを実行し、サンプルの入出力を確認した。

git clone https://github.com/beurtschipper/Depix.git
cd Depix
python3 -m pip install -r requirements.txt
for f in images/testimages/testimage?_pixels.png; do
  python3 depix.py \
    -p $f \
    -s images/searchimages/debruinseq_notepad_Windows10_closeAndSpaced.png \
    -o $f.depix.png
done

3.1.1 testimage1_pixels.png

3.1.2 testimage2_pixels.png

3.1.3 testimage3_pixels.png

目で読むより明らかに良い精度での出力が得られていることがわかる。

しかし、よく見ると密度が近い別のグリフが参照されている箇所も多く見られる。 testimage1_pixels.png は、列が単純であるため推測可能だが、文脈が無いため誤読しやすい。

これは原理的な制約であると言えよう。

3.2 beurtschipper/Depixの実用

3.2.1 helloworld

次に、筆者環境 (Windows 11 21H2 旧メモ帳) で先程の辞書と同一のフォント (Consolas 11) にてレンダリングした Hello World という文字列を入力してみた。

original:

モザイク化 genpixed.py

出力 depix.py

残念ながら、何も得られなかった。

3.2.2 アンチエイリアスの無効化

ClearTypeによるアンチエイリアスにより、事態が複雑化している事を懸念しClearTypeが存在しない環境で施行してみようと考えた。 そこで、筆者のWindows XP (SP3)の環境にて、メモ帳に debruinseq.txtレンダリングさせ、以下の画像を辞書に、その一行目を入力に、再試行した。

辞書:

入力と出力:

1 のハット部分や j のディティールはかなり復元できているのではなかろうか。 しかし、期待した品質ではなく、人間が直接目で読む精度とあまり変わらない。

3.2.3 様々な試行

xtermのレンダリング、異なるフォントサイズのレンダリング等、いくつかの条件を試したが、先の Hello World の例と同様に、殆どマッチングが取れなかった。

ここで、サンプルに付属する辞書を入力とし、それの復元を試みる方針を建てた。 これならば、レンダリング差異の問題は起こらず、また、当然、辞書とのマッチングも取りやすいはずである。

export F=images/searchimages/debruinseq_notepad_Windows10_closeAndSpaced.png

python3 genpixed.py \
  -i $F \
  -o $F.pix.png

python3 depix.py \
  -p $F.pix.png \
  -s $F \
  -o $F.depix.png

得られた結果を以下に示す。最左列以外マッチングが取れていない。

beurtschipper/Depix f7d1850 現在、汎用性は無く、このレポジトリの成果物により直ちにモザイクが復元可能であるとは言えない様だ。

4. 考察

現状、復元は理想的な状況のみで行える様だが、「文脈/制約付きならば高精度に復元し得る」という主張は大変興味深い。

以下は筆者の所感である。

  • 本実装のアイデアはそのままに、グリフ位置の制約をより強くする為、辞書/入力を1行にする事で精度向上し得るのではないか。
  • 入力を自然言語に限定した上 (文脈的制約)、RNNの様な機械学習的アプローチを用いることで、より精度向上が期待できるのではないだろうか。

残念ながら筆者は、機械学習のリサーチャーで無いため、このアイデアを実装/実験する技術は持ち合わせていない。

beurtschipper/Depix は、筆者の様な初学者にも文脈/制約付き高精度デピクセライザーの登場を予感させる、非常に良い PoC であると言えよう。

Ansibleを使ってWindows環境を構築する編 [その3]

このブログは、N高等学校とVRChatの世界からやってきた株式会社ArmorisアルバイトのShaderoが書いています。
あるもりすぶろぐの内容は個人の意見です。

弊社が提供する様々なトレーニングプログラムでは、トレーニングを行う際に、演習端末としてWindows環境を用意しています。
そこで先日、Ansibleというツールを使用して、端末の環境構築を効率的に行えるようにしました。
もっとも、実際に使いこなすには様々な試行錯誤がありました。
そこで今回は備忘も兼ねて、その際にいろいろ試したことなども含めてまとめました。

前回はホストサーバーに新しくユーザーを作成する所まで行いました。
今回は作成したユーザーにトレーニングに必要なソフトのインストールや、レジストリの編集を行います!
(本シリーズで構築する環境の概要は前回をご覧ください。)

環境

ホストサーバー

Name Version
Windows 10 Pro 20H2
WinRM 3.0

Ansibleサーバー

Name Version
MacOS Big Sur 11.4
Ansible 2.11.6

別ユーザーで特定のタスクを実行できるようにする

前回作成したPlaybookを改良して、作成したユーザーにWindows Terminalのインストールやレジストリの編集を行います。

ソフトのインストールとレジストリの編集をするタスクを書いて終わり!…と行きたいところですが、普通に記述すると作成したユーザーではなく、Ansibleでログインしているユーザーでタスクが実行されてしまい、Ansibleでログインしているユーザーのレジストリが変わったりソフトがインストールされてしまいます。
そこでAnsibleでログインしているユーザーではなく、作成したユーザーでタスクを実行するためには少し準備が必要です。

/etc/ansible/hostsファイルにansible_become_method=runasを追加して下記のようにします。

    [servers]
    (ホストサーバーのIPアドレス)

    [servers:vars]
    ansible_user='(Ansibleでログインする際に使うホストサーバーのユーザー名)'
    ansible_password='(Ansibleでログインする際に使うホストサーバーのユーザーのパスワード)'
    ansible_port=5986
    ansible_connection=winrm
    ansible_winrm_server_cert_validation=ignore
    ansible_become_method=runas

これによりAnsibleでログインしているユーザーとは別のユーザーでタスクを実行出来るようになります。

作成したユーザーでレジストリの編集やソフトウェアのインストールを行う

準備を行ったので、さっそく作成したユーザーにWindows Terminalのインストールやレジストリの編集を行います。
前回作成したPlaybookに以下を追記します。

    - name: 作成したユーザーのエクスプローラー上で、ファイルの拡張子と隠しファイルを表示するようにレジストリを変更
      win_regedit:
        path: '{{ item.path }}'
        name: '{{ item.name }}'
        type: dword
        data: '{{ item.data }}'
      with_items:
        - path: HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced
          name: HideFileExt
          data: 0
        - path: HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced
          name: Hidden
          data: 1
      become: yes
      become_user: '{{ userName }}'
      vars:
        ansible_become_pass: '{{ password }}'

    - name: 作成したユーザーにGitをインストール (Bucketを追加するのに必要)
      community.windows.win_scoop:
        name: git
      become: yes
      become_user: '{{ userName }}'
      vars:
        ansible_become_pass: '{{ password }}'

    - name: Windows Terminalをインストールをするために必要なBucketを追加
      community.windows.win_scoop_bucket:
        name: extras
      become: yes
      become_user: '{{ userName }}'
      vars:
        ansible_become_pass: '{{ password }}'

    - name: 作成したユーザーにWindows Terminalをインストール
      community.windows.win_scoop:
        name: windows-terminal
      become: yes
      become_user: '{{ userName }}'
      vars:
        ansible_become_pass: '{{ password }}'

下記の部分がAnsibleで実行しているユーザーとは別のユーザーで特定のタスクを実行するために必要な記述です。
上記のPlaybookではPlaybook内で作成したユーザーで実行するようにしており、これにより新しく作成したユーザーに、特定のソフトのインストールや、レジストリの書き換えを実現しています。

become: yes
  become_user: '(実行したい別のユーザー名)'
  vars:
    ansible_become_pass: '(実行したい別のユーザーのパスワード)'

少し脱線

今回Windows TerminalのインストールにはScoopを用いました。
Windows向けのパッケージマネージャとしてはwingetやChocolateyあたりが有名だと思うのですが、大体のソフトをユーザーディレクトリにインストールしてくれ、環境が汚れにくいため今回はScoopを採用しました。

また、今回は本Playbookで作成したユーザーの初回ログイン前にソフトをインストールする場合を想定してPlaybookを構築しているのですが、Chocolateyだと一部ソフト(WSL版Ubuntu)を初回ログイン前にインストールしようとすると失敗したので別の物を使おうとしたのも理由の一つです。
(正直なところ何故失敗したのかは調べきれておらず、完全にChocolateyが原因なのか断定出来ないため、これは参考程度にしていただけると幸いです。)

ちなみにScoopモジュールは任意のソフトをインストールする時にScoop本体がない場合、自動的にScoopをインストールしてくれるため事前にインストールする必要はありません(とても便利)

動作確認

上記のPlaybookを実行すると、今までの動作に加え、作成したユーザーでエクスプローラーを開いた時にファイルの拡張子と隠しファイルが表示され、Windows Terminalがインストールされます。

f:id:Armoris:20220215164014p:plain

f:id:Armoris:20220215164028p:plain

おかたづけ

このままだとユーザーが作成されっぱなしで困るので、下記のPlaybookファイルを作成、実行してユーザーを削除します。
(削除するユーザーは予めサインオフさせてください。サインオフしていない場合ユーザープロファイルを削除するタスクが失敗する可能性があります。)

- hosts: servers
  gather_facts: false
  vars:
    username: AnsibleTest
  tasks:
    - name: ユーザーを削除
      win_user:
        name: '{{ username }}'
        state: absent

    - name: ユーザープロファイルを削除
      win_user_profile:
        name: '{{ username }}'
        state: absent

f:id:Armoris:20220201161822p:plain

感想

Playbookに行わせたい処理をガシガシ書くだけで複数のデバイスの環境を自動的に構築することが出来るのはとても時短になるので良いポイントだと思いました。
また、PlaybookもYAML形式でかなり見通し良く出来るため、後から見た時に「ここなにやってたっけ?」となるのが少なくなるのでオススメです!

本記事がお役に立てましたら幸いです。

Ansibleを使ってWindows環境を構築する編 [その2]

このブログは、N高等学校とVRChatの世界からやってきた株式会社ArmorisアルバイトのShaderoが書いています。
あるもりすぶろぐの内容は個人の意見です。

弊社が提供する様々なトレーニングプログラムでは、トレーニングを行う際に、演習端末としてWindows環境を用意しています。
そこで先日、Ansibleというツールを使用して、端末の環境構築を効率的に行えるようにしました。
もっとも、実際に使いこなすには様々な試行錯誤がありました。
そこで今回は備忘も兼ねて、その際にいろいろ試したことなども含めてまとめました。

構築したい環境

前回はAnsibleを用いてホストサーバーに接続出来るように様々な準備を行いました。
今回からは以下を満たす環境を実際に作ります!

  • 以下の条件を満たした環境を準備
    • 環境要件
      • 管理者アカウントが存在するWindows環境を準備する
    • 構築内容

f:id:Armoris:20220216183747p:plain

今回はホストサーバーにトレーニング用のユーザーを作成する所までを行います!
作成したユーザーにトレーニングに必要なソフトをインストールしたり、レジストリを編集するなどは次回行います。

環境

ホストサーバー

Name Version
Windows 10 Pro 20H2
WinRM 3.0

Ansibleサーバー

Name Version
MacOS Big Sur 11.4
Ansible 2.11.6

Playbookとは

Playbookとは、YAML形式のファイルです。
そのファイルにホストサーバーに行わせたい処理を書くことで、望んだ環境にする事が出来ます。
また、Playbookがあればコマンド一つで複数のデバイスを自動的に設定することもできるため、手作業で行うよりも大幅に時間を短縮する事が出来ます。

ユーザーを作成するPlaybookを書いてみる

さっそくPlaybookを書いていきます。
ここではホストサーバーに新しいユーザーを作成するPlaybookを書きます。

下記のPlaybookを記述したymlファイルを作成します。(ファイル名として本記事ではbefore.ymlを使用)

- hosts: training          # 実行対象のホストの指定
  gather_facts: false      # 対象ホストの情報を取得して変数に格納するかどうかの設定
  vars:                    # 変数の記述
    # userNameやpasswordの値は、作成するユーザーに合わせて要変更
    userName: AnsibleTest
    password: password
  tasks:
    - name: ユーザーを作成
      win_user:
        name: '{{ userName }}'
        password: '{{ password }}'
        state: present
        groups:
          - Users
          - Remote Desktop Users

    - name: ユーザープロファイルを作成
      win_user_profile:
        username: '{{ userName }}'
        state: present

以下、上記コードの解説。

  • 1行目
    • 対象サーバーの指定。
    • 記述する際は、インベントリ内のグループ名やホスト名などを指定。
    • allも指定可能で、その場合はインベントリ内の全てのサーバーが対象になります。
  • 2行目
    • 対象ホストのOSやホストネームなどのシステム情報を収集し、変数(後述)に格納するかどうかの設定。
    • システム情報を収集するのは僅かに時間がかかるため、使わない場合はfalseにすることをお勧めします。
  • 3~6行目
    • 変数の記述。
    • 予め変数を定義したり、タスクの実行結果を変数として格納し使用することが可能。
    • 変数を使用する際は、値全体ををシングル、又はダブルクォートで囲った上で{{ (変数名) }}と記述。
  • 7行目以降
    • タスクと呼ばれる、対象サーバーに行わせたい処理を記述する箇所。
    • このPlaybookでは、ホストサーバーに新しいユーザーが作成されます。
    • ユーザーを作成するタスク以外に、ホストサーバーにファイルを転送するタスクや、レジストリを変更するタスクなど、様々な種類がありますが、各種タスクの使い方についてはAnsible公式ドキュメントを参照する事をお勧めします。

下記のコマンドで実行するとホストサーバーに新しくユーザーが作成されたのが分かります。

$ ansible-playbook before.yml

f:id:Armoris:20220215155414p:plain

f:id:Armoris:20220215155903p:plain

エラーが出る場合

AnsibleをMacOSで実行するとERROR! A worker was found in a dead stateと書かれたエラーが出る場合があります。
その場合は下記のコマンドで環境変数を登録すると正常に動く場合があります。(.bash_profileに登録するとなお良い)

$ export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES

あとがき

今回はホストサーバーにユーザーを作成するPlaybookを作成しました。
次回は今回作成したPlaybookを改良して、作成したユーザーにソフトをインストールしたり、レジストリを編集するタスクを追加します!

本記事がお役に立てましたら幸いです。

Ansibleを使ってWindows環境を構築する編 [その1]

このブログは、N高等学校とVRChatの世界からやってきた株式会社ArmorisアルバイトのShaderoが書いています。
あるもりすぶろぐの内容は個人の意見です。

弊社が提供する様々なトレーニングプログラムでは、トレーニングを行う際に、演習端末としてWindows環境を用意しています。
そこで先日、Ansibleというツールを使用して、端末の環境構築を効率的に行えるようにしました。
もっとも、実際に使いこなすには様々な試行錯誤がありました。
そこで今回は備忘も兼ねて、その際にいろいろ試したことなども含めてまとめました。

本記事ではAnsibleを用いてホストサーバーに接続するための準備を行います。
すでにAnsibleサーバーとホストサーバー(Windows)のセットアップ及び疎通確認が完了している方は、本記事に書かれている操作を行う必要はありません。

Ansibleとは

オープンソースの構成管理ツールです。

Playbookと呼ばれるYAML形式のファイルに処理を書くことで、環境を自動的に構築することが出来ます。
また、Playbookがあればコマンド一つで複数のデバイスを自動的に設定することもできるため、手作業で行うよりも大幅に時間を短縮する事が出来ます。

環境

ホストサーバー

Name Version
Windows 10 Pro 20H2
WinRM 3.0

Ansibleサーバー

Name Version
MacOS Big Sur 11.4
Ansible 2.11.6

WinRMの設定

さっそくAnsibleのインストール!と行きたいのですが、その前にホストサーバー側でWinRMと呼ばれるWindowsを遠隔で操作する際に必要になる管理プロトコルの設定を、Ansibleが使えるように変更します。

下記のスクリプトを管理者権限で起動されたPowerShell上で実行します。

$url = "https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1"
$file = "$env:temp\ConfigureRemotingForAnsible.ps1"

(New-Object -TypeName System.Net.WebClient).DownloadFile($url, $file)

powershell.exe -ExecutionPolicy ByPass -File $file

Ansibleのインストール

ここから先はAnsibleサーバー側での作業になります。

下記のコマンドを実行してAnsibleをインストールします。

$ brew install ansible

またAnsibleでWindowsを動かす場合に必要になるpywinrmもインストールします。

$ pip3 install pywinrm

hostsファイルの編集

/etc/ansible/ディレクトリ内にあるhostsファイルを下記のように編集します。
ファイル、またはディレクトリが無かったら作ってください。

[servers]
(ホストサーバーのIPアドレス)

[servers:vars]
ansible_user='(Ansibleでログインする際に使うホストサーバーのユーザー名)'
ansible_password='(Ansibleでログインする際に使うホストサーバーのユーザーのパスワード)'
ansible_port=5986
ansible_connection=winrm
ansible_winrm_server_cert_validation=ignore

接続テスト

正常にホストサーバーと接続できるかを確認するため、下記のコマンドを実行します。
エラーが出なければ成功です。

$ ansible -m win_ping (ホストサーバーのIPアドレス)

f:id:Armoris:20220201161623p:plain

あとがき

今回はAnsibleでホストサーバーに接続できるよう準備を行いました。
次回は実際にホストサーバーの環境を構築するためのPlaybookを書いていく予定です!

本記事がお役に立てましたら幸いです。

フィッシングサイトの調査をしてみた - 可視化したデータの分析

このブログは、数年前にN高等学校を卒業し株式会社Armorisにやってきたアルバイト Kaepi が書いています。

あるもりすぶろぐの内容は個人の意見です。

概要

今回はフィッシングサイトの情報について、twitterで投稿をしている2つのアカウントからデータを取得し、IPや地域情報などの様々な要素の傾向を調べるために、elasticsearch+kibanaでデータの分析と可視化をしてみました。
また、今回は記事を3回に分けて投稿します。
1. フィッシングサイトの調査をしてみた - データ収集とパース編
2. フィッシングサイトの調査をしてみた - elasticsearch+kibanaを使ってデータの可視化
3. フィッシングサイトの調査をしてみた - 可視化したデータの分析(この記事)

この記事は3回目になります。

elasticsearchとkibanaの構築を解説して、投入したデータの可視化を行いました。集計期間は 2021/11/26 から 12月22日までの約一か月です。

情報元にさせていただいたアカウント
@secbird1
@pingineer_jp

目次

  1. データからフィッシングサイトの傾向を考える
  2. フィッシングサイトの対策方法
  3. まとめ

データからフィッシングサイトの傾向を考える

傾向を考えるにあたって、今回は4つの視点から分析をしてみました。
1. 名前を騙られたブランドについて
2. 国や地域について
3. ドメインやそのレジストラ
4. 総合的な傾向

名前を騙られたブランドについて

今回情報元にしたアカウントでは日本企業のブランドを騙ったフィッシングサイトのみを発信しています。
まずは名前を騙られたブランドの傾向ですが、銀行やクレジットカード、携帯電話の大手キャリアなどが多くみられました。
これは騙された利用者から個人情報(住所や名前、クレジットカードの番号など)を盗み、他の第三者に情報を売ったりクレジットカードを不正に利用し現金化するなど、違法な手段でお金を稼ぐことが目的だからだと考えられます。

f:id:Armoris:20211223160910p:plain
名前を騙られたサービスのリスト

国や地域について

次にドメインに割り当てられていた IP アドレスの地域情報を GeoIP のデータを用いて確認すると、約3/4がアメリカでその次に韓国となりました。

f:id:Armoris:20211223161134p:plain f:id:Armoris:20211223161144p:plain

これは海外のレンタルサーバに依存した結果となるため、フィッシングサイトを作成している人の国と直接関連するものではないと思われます。

ドメインレジストラ

ドメインについて

まずフィッシングサイトのドメインの特徴について分析してみます。

f:id:Armoris:20211223140154p:plain
フィッシングサイトのURLのリスト(discover)
まずは上から5個のドメインですが、名前などではないランダムな文字列に.cfというあまり見かけないドメインになっています。
また、6個目のdqq-amazon[.]comというドメインwww.jp.mercari.clppa[.]comなど、フィッシングサイトのドメインにブランド名の一部が含まれるケースでは、.comなどが使われています。
このように使用されているドメインに差がでるのは、フィッシングサイトを作成している攻撃者のスタンスの違いだと考えられます。
前者のようなURLを見ればすぐに偽物だとわかるフィッシングサイトは短時間で大量に作成されており、フィッシングサイト1個あたりのコストが低いと考えられます。
後者のURLにブランド名を含めたり.comドメインを使用する、見分けがつけにくいフィッシングサイトでは現時点で収集したデータを見る限り少数派で、フィッシングサイト1個あたりのコストが高いと考えられます。

レジストラについて

こちらはwhoisをして入手したレジストラの情報です。

f:id:Armoris:20211224143144p:plain
フィッシングサイトのドメインwhoisして得たデータなので表記ゆれがあり、同じレジストラが2つ以上ある場合があります。
現在フィッシングサイトのドメインの登録で一番使われている广州云讯信息科技有限公司はこのようになっており、見たところ使用されているトップレベルドメイン.cnのみのようです。 f:id:Armoris:20211224144626p:plain 次に多いWeb Commerce Communications Limited dba WebNic.ccでは.comドメインのみで、同じドメインを使いまわして多数のフィッシングサイトが作成されているようです。
また同じような構成のURLで複数ブランドのフィッシングサイトがあることから、同一人物またはグループが様々なブランドのフィッシングサイトを作成している可能性が大きいと思われます。 f:id:Armoris:20211224145833p:plain

総合的な傾向

これは11/26~12/21までの25日間でブランドごとの報告数を積み上げ棒グラフにしたものです。 f:id:Armoris:20211221140628p:plain このグラフを見ると2つの特徴が見えるので、それについて分析します。

  1. メルカリのフィッシングサイト(グラフの緑色のバー)
  2. 三井住友カードのフィッシングサイト(グラフの青色のバー)
  3. auのフィッシングサイト(グラフの紫色のバー)
メルカリのフィッシングサイトについて

メルカリのフィッシングサイトは毎日平均20~30件ほど報告されていて、継続的に攻撃者の攻撃ターゲットとなっている可能性が考えられます。
f:id:Armoris:20211221141639p:plain 次に地域とレジストラについて分析します。
レジストラは約半数がWeb Commerce Communications (Singapore) Pte. Ltd. (dba WebNIC)という会社であり、アジアではかなり大手のレジストラのようです。
またこの会社はAlibaba Cloudと連携していて、webホスティングサービスを提供していました。 f:id:Armoris:20211221143029p:plain 最後にIPアドレスからフィッシングサイトをホストしているサービスを調べてみました。
12月のはじめあたりから21日までにある緑色のバーは23.95.122[.]118となっており、このIPアドレスを検索してみるとColoCrossingというサービスだということがわかりました。
こちらは先程のWebNICやAlibaba Cloudとはまた別の会社のようですが、格安でWebホスティングができるので利用されていると考えられます。
このことから、今月のメルカリを騙ったフィッシングサイトはWebNICでドメインを取得しColoCrossingでホストされていることが多いということがわかりました。

三井住友カードのフィッシングサイトについて

三井住友カードのフィッシングサイトは12/15以前では報告数が平均20件程度だったのに対し、12/15から一週間後には一日あたり平均70件と大幅に報告数が増えていました。
また、三井住友カードのフィッシングサイトの報告数が多い日はJCBのフィッシングサイトの報告数も増えており、おそらく攻撃者がクレジットカードの締日が近いタイミングを見計らってフィッシングサイトを作成しているものと思われます。

f:id:Armoris:20211222143345p:plain
三井住友カードのフィッシングサイトの報告数
f:id:Armoris:20211223161818p:plain
JCBのフィッシングサイトの報告数
そしてこれは三井住友カードのフィッシングサイトのIPアドレスを表示したものです。
特に目立つのは、急激に報告数が増えた12/16の204.44.75[.]4(緑色のバー)というIPアドレスで、ipinfo.ioというサービスで調べてみたところ、QuadraNet Enterprises LLCというアメリカのホスティングサービスを提供している会社だということがわかりました。
f:id:Armoris:20211222173751p:plain

auのフィッシングサイトについて

auのフィッシングサイトは先に挙げたメルカリや三井住友カードのフィッシングサイト報告数にくらべ少ないですが、不定期で一日60~100件と比較的他のサービスよりも多く報告されています。

f:id:Armoris:20211222144802p:plain
ブランドごとの報告数を表示した積み上げ棒グラフ
f:id:Armoris:20211222155043p:plain auのフィッシングサイトに絞ってdiscoverをみると、URLの構成が似ていたりIPアドレスが同じことから、同一人物またはグループが作成したフィッシングサイトであると思われます。
またこれは推測ですが、大量に作成したフィッシングサイトが様々な理由で使用できなくなったら新しくフィッシングサイトを作り直すということを繰り返しているためだと考えます。
また、このようなURLを見ればすぐにわかるフィッシングサイトを大量に作る意図としては、フィッシングサイトのURLなどがGoogle Safe Browsingに報告されることで、このようにブラウザに警告が出るようになるからだと思われます。

f:id:Armoris:20211129153114p:plainf:id:Armoris:20211129152731p:plain

なので、Google Safe Browsingにフィッシングサイトとして登録されてもいいように、似た名前のドメインを登録するのではなく、使い捨てのランダムなドメインを使用しているのだと考えられます。
この場合はすぐ使えなくなってもいいように安いドメイン(.top .ga .cfなど)を使用しているようです。

フィッシング被害を防ぐには

フィッシングサイトの被害に遭わないために個人でできることは、フィッシングサイトにアクセスしない・個人情報を入力しないことです。
フィッシングサイトのURLを含む悪意のあるメール・SMSなどを見分ける基準として、
・日本語がおかしい
・個人情報の入力を求める
・緊急や重要などを強調しアクセスを急かす
などがあります。
またIDやパスワードの使いまわしをやめることで、被害を最小限に抑えることができます。

出典・参考資料: フィッシング対策ガイドライン

まとめ

「フィッシングサイトの調査をしてみた」の3回目の記事でした。
IPアドレスから位置情報を取得して地図に並べたり、関連性のありそうな項目を並べてグラフにするなど、大量のデータを集めて可視化をすると見えてくる傾向を調べて分析してみました。
データの取得編と可視化編につづく今回の分析編でこのシリーズは一旦終わりですが、数ヶ月単位でデータが集まって新しい傾向がみえたらまたブログにしようと思います。
ここまで読んでいただきありがとうございました。

フィッシングサイトの調査をしてみた - elasticsearch+kibanaを使ってデータの可視化

このブログは、数年前にN高等学校を卒業し株式会社Armorisにやってきたアルバイト Kaepi が書いています。

あるもりすぶろぐの内容は個人の意見です。

概要

今回はフィッシングサイトの情報について、twitterで投稿をしている2つのアカウントからデータを取得し、IPや地域情報などの様々な要素の傾向を調べるために、elasticsearch+kibanaでデータの分析と可視化をしてみました。
また、今回は記事を3回に分けて投稿します。
1. フィッシングサイトの調査をしてみた - データ収集とパース編
2. フィッシングサイトの調査をしてみた - elasticsearch+kibanaを使ってデータの可視化(この記事)
3. フィッシングサイトの調査をしてみた - 可視化したデータの分析

この記事は2回目になります。
・前回の内容まとめ
前回はtwitterからツイートを収集して、データの追加とパース・elasticsearchに投入するところまでをnode.jsを使って自動化しました。

情報元にさせていただいたアカウント
@secbird1
@pingineer_jp

目次

  1. elasticsearch+kibanaのインストール
  2. elasticsearchの基本操作
  3. kibanaのdashboardを使ったデータの可視化
  4. まとめ

elasticsearch+kibanaのインストール

elasticsearchのインストール

$ wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
$ echo "deb https://artifacts.elastic.co/packages/7.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elastic-7.x.list
$ sudo apt-get update && sudo apt-get install elasticsearch
$ sudo systemctl start elasticsearch

動作確認

$ curl http://localhost:9200
{
  "name" : "test",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "███████████████",
  "version" : {
    "number" : "7.15.1",
    "build_flavor" : "default",
    "build_type" : "deb",
    "build_hash" : "83c34f456ae29d60e94d886e455e6a3409bba9ed",
    "build_date" : "2021-10-07T21:56:19.031608185Z",
    "build_snapshot" : false,
    "lucene_version" : "8.9.0",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

kibanaのインストール

$ sudo apt-get install kibana
$ sudo vim /etc/kibana/kibana.yml

# The default is 'localhost', which usually means remote machines will not be able to connect.
# To allow connections from remote users, set this parameter to a non-loopback address.
server.host: 0.0.0.0

$ sudo systemctl start kibana

http://ubuntuのip:5601でkibanaの画面を開くことができれば構築は完了です。 f:id:Armoris:20211105154153p:plain

elasticsearchの基本操作

Indexの作成

elasticsearchの操作はcurlなどで行うこともできますが、kibanaのDev Toolsでも同じことができます。

PUT /indexの名前?pretty

上記の内容をelasticsearchに送ることでindexが作成されます。

mappingについて

mappingとは、Indexに入るデータの構造を設定することを指します。 基本的には、名前とデータ型がセットになっていてjsonに似ています。
こちらは今回の調査で作成したindexです。

PUT /secbird1/_mapping
{
      "properties" : {
        "country" : {
          "type" : "keyword"
        },
        "created_at" : {
          "type" : "date"
        },
        "ip" : {
          "type" : "keyword"
        },
        "whois": {
          "type" : "text"
        },
        "location" : {
          "type" : "geo_point"
        },
        "name" : {
          "type" : "keyword"
        },
        "org" : {
          "type" : "keyword"
        },
        "url" : {
          "type" : "keyword"
        }
      }
}

ここで注意しないといけないことは、keywordtextの違いです。 どちらも文字列が入りますが、後述のdashboardを使ったグラフの作成にはtextは使うことができません。
また、可視化することを踏まえて座標データが入るようにするなどの工夫をしました。

kibanaのdashboardを使ったデータの可視化

左側にあるハンバーガーメニューを開くとAnalyticsの項目の下にDashboardがあります。
Dashboardを開くと右側にCreate dashboardのボタンがあり、ここからDashboardを作成することができます。

f:id:Armoris:20211119145223p:plain
作成後のDashboard
Create visualizationをクリックするとグラフを作成する画面になります。 f:id:Armoris:20211110133523p:plain グラフの作成は非常に簡単で、グラフにしたいデータをドラッグアンドドロップするだけで作成できます。 f:id:Armoris:20211119135819p:plain グラフの種類は下に表示されているサジェスト以外にも、上にあるプルダウンメニューから使用できるタイプを選ぶことができます。
f:id:Armoris:20211124123511p:plain
今回の分析で作成したグラフ
このような機能を使って、今回収集したデータを可視化してみました。
それぞれのパネルは、

  1. Table
  2. Pie
  3. Map
  4. Area stacked

というvisualizationを使用しています。
Tableには名前を騙られたサービスを報告が多い順に並べて表示しています。
地域情報(国名)と、フィッシングサイトをホストしているサービス名の比率がわかりやすいようにPie(円グラフ)を選択しました。
また、フィッシングサイトがどこでホストされているのかがわかりやすいようにMapを使って投入された座標データの地点にマークを表示させ、Heat mapで分布がわかりやすくなるようにしました。
最後のArea stackedは、取得したツイートの数を時間ごとに表示しています。

まとめ

今回はelasticsearch・kibanaの構築と前回用意したデータの可視化をしました。
自分で構築やデータの投入・可視化をするのは初めてだったので、調べながら手探りで可視化までできましたが、慣れるといろいろなデータの分析が簡単にできそうな便利なツールなので、勉強しておいて損はないと思いました。
次の記事では今回可視化したデータについて分析した結果について書くので、次回もよろしくお願いします。

フィッシングサイトの調査をしてみた - データ収集とパース編

このブログは、数年前にN高等学校を卒業し株式会社Armorisにやってきたアルバイト Kaepi が書いています。

あるもりすぶろぐの内容は個人の意見です。

概要

今回はフィッシングサイトの情報について、twitterで投稿をしている2つのアカウントからデータを取得し、IPや地域情報などの様々な要素の傾向を調べるために、elasticsearch+kibanaでデータの分析と可視化をしてみました。
また、今回は記事を3回に分けて投稿します。
1. フィッシングサイトの調査をしてみた - データ収集とパース編(この記事)
2. フィッシングサイトの調査をしてみた - elasticsearch+kibanaを使ってデータの可視化
3. フィッシングサイトの調査をしてみた - 可視化したデータの分析

情報元にさせていただいたアカウント
@secbird1
@pingineer_jp

今回の調査に必要なツイートの収集からelasticsearchへのデータ投入までを自動化するために、node.jsでプログラムを書きました。

目次

  • 全体図
  • twitterからツイートを収集する
  • ツイート収集の自動化
  • 収集したデータのパース
  • まとめ

全体図

構成はこの様になっており、twitterから取得したツイートを一旦elasticsearchに投入してから、別のプログラムでデータの追加とパースを行います。

f:id:Armoris:20211130131404p:plain

twitterからツイートを収集する

今回情報源にさせていただいたツイートを収集するために、Twitter API v2Search Tweetsという機能を使用しました。

ツイート収集の自動化

Twitter API v2で取得したツイートをelasticsearchに投入するところまでを自動化するために、node.jsでプログラムを書きました。

大まかな機能は

  1. Twitter API v2からツイートを取得する
  2. 送られてきたjsonをツイートごとに分割してelasticsearchに投入する。

となっており、これをnode-cronという指定した時間おき(今回は1時間に1回)に実行するライブラリを使って、自動でツイートを収集するようにしました。

const cron = require('node-cron')
const axiosBase = require('axios')
const twitter = axiosBase.create({
    baseURL: 'https://api.twitter.com/2/tweets/search',
    headers: {
        'Content-type': 'application/json',
        'Authorization': 'Bearer ' + bearar
    }
})

const elastic = axiosBase.create({
    headers: {
        'Content-Type': 'application/json',
        'X-Requested-With': 'XMLHttpRequest'
    }
})

async function dump(account_name) {
  let resCount = 0
  const _date = new Date()
  const date = `${_date.getFullYear()}-${_date.getMonth()+1}-${_date.getDate()}T${_date.getHours()-1}:00:00+09:00`
  twitter.get('/recent', {
    params: {
      'query': `from:${account_name}`,
      'start_time': date,
      'tweet.fields': 'created_at',
      'max_results': 100,
    }
  })
    .then(res => {
      for (let i of res.data.data) {
        const data = { account_name: account_name, full_text: i.text, created_at: i.created_at}
        postData(data, 'tweets')
        resCount++
      }
      postData({state: 'log', log: `Dumped. \naccount: ${account_name}\ndata count: ${resCount}`, time: _date}, 'log')
    })
    .catch(err => {
      const data = err.response.data
      const message = `title: ${data.title}\ndetail: ${data.detail}\nerrors: ${data.errors[0].message}`
      postData({state: 'error', log: message, time: _date}, 'log')
    })
}

async function postData(data, index) {
    elastic.post(`http://localhost:9200/${index}/_doc/`, JSON.stringify(data, null))
      .then(res => { console.log(res.data) })
      .catch(err => { console.log(err.response.data) })
}

cron.schedule('0 * * * *', () => {
    Promise.all(
      [
          dump('secbird1'),
          dump('pingineer_jp')
      ]).catch(e => console.log(e.response.data))
})

beararAPIにアクセスするためのトーク

twitter API v2で取得したデータはこのようなjsonになっています。

[
  {
    created_at: '2021-11-30T03:33:05.000Z',
    id: '1465524255723261953',
    text: '三井住友カードのフィッシングサイトと思われます。だまされてはいけません。\n' +
      '\n' +
      'hxxp://wcermxoj.ml/\n' +
      '( IP:Cloudflare Country:US Org:AS13335 Cloudflare, Inc. )\n' +
      '\n' +
      '#Phishing #フィッシング #三井住友カード #SMBC'
  },
  {
    created_at: '2021-11-30T03:03:01.000Z',
    id: '1465516691312758785',
    text: '三井住友カードのフィッシングサイトと思われます。だまされてはいけません。\n' +
      '\n' +
      'hxxp://wcermxoj.tk/\n' +
      '( IP:Cloudflare Country:US Org:AS13335 Cloudflare, Inc. )\n' +
      '\n' +
      '#Phishing #フィッシング #三井住友カード #SMBC'
  },
  ...
]

収集したデータのパース

ツイートの内容のままだとelasticsearchで扱いにくいので、ツイートの内容を切り分けてそこから更に情報を追加していきます。

まずは上記のようなツイートから以下の要素に切り分けます。

  • 名前を騙られたサービス名
  • フィッシングサイトのURL
  • IP
  • 地域情報
  • org(使用されているホスティングサービスの組織名)

さらに、geoipを用いてIPアドレスから大まかな座標を取得したり、ドメインに対してwhoisを行うなどして情報の追加を行いました。
外部コマンド(whoisやnslookupなど)を実行するときは、この関数から実行することでエラーが発生したかどうかを判別しています。

async function run(command) {
    let res
    try {
        res = await exec(command)
    } catch (e) { res = e }
    if ( Error[Symbol.hasInstance](res)) return
    return res.stdout
}

IPアドレスから座標を取得するためにgeoip-liteというnode.jsのライブラリを使用しています。

console.log(geoip.lookup('107.150.11.137'))

IPアドレスを引数にわたすだけで様々な情報を取得できます。

{
  range: [ 1804992512, 1804996607 ],
  country: 'US',
  region: 'CA',
  eu: '0',
  timezone: 'America/Los_Angeles',
  city: 'Los Angeles',
  ll: [ 34.0549, -118.2578 ],
  metro: 803,
  area: 1000
}

ツイートを行ごとに分けてから、正規表現で要素の抽出と外部コマンドを実行をしています。
処理されたデータはPOSTでelasticsearchに送られます。

async function secbird(res) {
    for (let i of res.body.hits.hits) {
        i = i._source
        let text = i.full_text.split('\n')
        if (text.length > 5) {
            text = text.filter(n => n !== '')
            const name = text[0].match(/.+の/)[0].slice(0, -1)
            const url = text[1].replace('hxxp', 'http')
            const domain = url.replace(/http:\/\/|https:\/\/|/, '').replace('/', '').replace('/index', '')
            let ip = await nslookup(domain) || text[2].match(/IP:[Cloudflare|\d\.]+/)[0].slice(3)
            let whois = await run('whois '+domain) || "failed"
            const country = text[2].match(/Country:\w+/)[0].slice(8)
            const ll = await getll(ip)
            if (ll === "") continue
            const org = text[2].match(/Org:[-,.\s\w]+/)[0].trim().slice(4)
            const data = { name: name, url: url, ip: ip, whois: whois, country: country, location: ll, org: org, created_at: i.created_at}
            postData(data, 'secbird1')
        }
    }
}

elasticsearchに投入するパースされたjsonがこちらです。

{
  "name": "PayPay銀行",
  "url": "https://www.amazon.jp.nqg2.xyz",
  "ip": "107.150.11.137",
  "whois": "The queried object does not exist: DOMAIN NOT FOUND\n",
  "country": "US",
  "location": {
    "lat": 34.0549,
    "lon": -118.2578
  },
  "org": "AS8100 QuadraNet Enterprises LLC",
  "created_at": "2021-11-11T00:45:51+00:00"
}

※このjsonに含まれるurlはフィッシングサイトのurlです。絶対にアクセスしないでください。

整形したデータはelasticsearchに投入してデータ収集とパースは終了です。

まとめ

今回はtwitterからツイートを収集して、データの追加とパース・elasticsearchに投入するところまでをnode.jsを使って自動化しました。
特にnode.jsから外部コマンド(nslookupやwhoisなど)を実行することが多く、コードを書いているwindowsのパソコンからではテストができなかったので大変でした。
次の記事ではelasticsearchの構築と投入したデータの可視化について書くので、次回もよろしくお願いします。